Decoder with EasyDMA

nRF54L15 | nRF54L10 | nRF54L05 Datasheet

The decoder uses EasyDMA to take PWM parameters stored in RAM and update the internal compare registers of the wave counter, based on the mode of operation.

PWM parameters are organized into a sequence containing at least one half word (16 bit). Its most significant bit[15] denotes the polarity of the OUT[n] while bit[14:0] is the 15-bit compare value.

Bit number 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
Id B A A A A A A A A A A A A A A A
Reset 0x00000000 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
Id RW Field Value Id Value Description
A RW

COMPARE

Duty cycle setting - value loaded to internal compare register

B RW

POLARITY

Edge polarity of GPIO.

RisingEdge

0

First edge within the PWM period is rising

FallingEdge

1

First edge within the PWM period is falling

The DECODER register controls how the RAM content is interpreted and loaded into the internal compare registers. The LOAD field controls if the RAM values are loaded to all compare channels, or to update a group or all channels with individual values. The following figure illustrates how parameters stored in RAM are organized and routed to various compare channels in different modes:

Figure 4. Decoder memory access modes

Decoder memory access modes

A special mode of operation is available when DECODER.LOAD is set to WaveForm. In WaveForm mode, up to three PWM channels can be enabled - OUT[0] to OUT[2]. In RAM, four values are loaded at a time: the first, second and third location are used to load the values, and the fourth RAM location is used to load the COUNTERTOP register. This way one can have up to three PWM channels with a frequency base that changes on a per PWM period basis. This mode of operation is useful for arbitrary wave form generation in applications, such as LED lighting.

The register SEQ[n].REFRESH=N (one per sequence n=0 or 1) will instruct a new RAM stored pulse width value on every (N+1)th PWM period. Setting the register to zero will result in a new duty cycle update every PWM period, as long as the minimum PWM period is observed.

Note that registers SEQ[n].REFRESH and SEQ[n].ENDDELAY are ignored when DECODER.MODE=NextStep. The next value is loaded upon every received NEXTSTEP task.

SEQ[n].PTR is the pointer used to fetch COMPARE values from RAM. If the SEQ[n].PTR is not pointing to a RAM region, an EasyDMA transfer may result in a HardFault or RAM corruption. See Memory for more information about the different memory regions. After the SEQ[n].PTR is set to the desired RAM location, the SEQ[n].MAXCNT register must be set to the number of bytes in the sequence. It is important to observe that the Grouped mode requires one half word #concept_wxj_hnw_nr__load_data_fn per group, while the Single mode requires one half word #concept_wxj_hnw_nr__load_data_fn per channel, thus increasing the RAM size occupation. If PWM generation is not running when the DMA.SEQ[n].START task is triggered, the task will load the first value from RAM and then start the PWM generation. A SEQSTARTED[n] event is generated as soon as the EasyDMA has read the first PWM parameter from RAM and the wave counter has started executing it. When LOOP.MAXCNT=0, sequence n=0 or 1 is played back once. After the last value in the sequence has been loaded and started executing, a SEQEND[n] event is generated. The PWM generation will then continue with the output defined in the IDLEOUT register. The following figure illustrates an example of a simple playback.

Figure 5. Simple sequence example

Simple sequence example

The following source code is used for configuration and timing details in a sequence where only sequence 0 is used and only run once with a new PWM duty cycle for each period.

    
NRF_PWM0->PSEL.OUT[0] = (first_port << PWM_PSEL_OUT_PORT_Pos) | 
                        (first_pin << PWM_PSEL_OUT_PIN_Pos) | 
                        (PWM_PSEL_OUT_CONNECT_Connected <<
                                                 PWM_PSEL_OUT_CONNECT_Pos);
NRF_PWM0->ENABLE      = (PWM_ENABLE_ENABLE_Enabled << PWM_ENABLE_ENABLE_Pos);
NRF_PWM0->MODE        = (PWM_MODE_UPDOWN_Up << PWM_MODE_UPDOWN_Pos);
NRF_PWM0->PRESCALER   = (PWM_PRESCALER_PRESCALER_DIV_1 <<
                                                 PWM_PRESCALER_PRESCALER_Pos);
NRF_PWM0->COUNTERTOP  = (16000 << PWM_COUNTERTOP_COUNTERTOP_Pos); //1 msec
NRF_PWM0->LOOP        = (PWM_LOOP_CNT_Disabled << PWM_LOOP_CNT_Pos);
NRF_PWM0->DECODER   = (PWM_DECODER_LOAD_Common << PWM_DECODER_LOAD_Pos) | 
                      (PWM_DECODER_MODE_RefreshCount << PWM_DECODER_MODE_Pos);
NRF_PWM0->DMA.SEQ[0].PTR  = ((uint32_t)(seq0_ram) << PWM_DMA_SEQ_PTR_PTR_Pos);
NRF_PWM0->DMA.SEQ[0].MAXCNT  = (sizeof(seq0_ram) << PWM_DMA_SEQ_MAXCNT_MAXCNT_Pos);
NRF_PWM0->SEQ[0].REFRESH  = 0;
NRF_PWM0->SEQ[0].ENDDELAY = 0;
NRF_PWM0->TASKS_DMA.SEQ[0].START = 1;
    
   

To completely stop the PWM generation and force the associated pins to a defined state, a STOP task can be triggered at any time. A STOPPED event is generated when the PWM generation has stopped at the end of the currently running PWM period, and the pins go into their idle state as defined by the IDLEOUT register. PWM generation can then only be restarted through a DMA.SEQ[n].START task. DMA.SEQ[n].START will resume PWM generation after having loaded the first value from the RAM buffer defined in the SEQ[n].PTR register.

The following table indicates when specific registers get sampled by the hardware. Care should be taken when updating these registers to avoid that values are applied earlier than expected.

Table 1. When to safely update PWM registers
Register Taken into account by hardware Recommended (safe) update
SEQ[n].PTR When sending the DMA.SEQ[n].START task After having received the SEQSTARTED[n] event
SEQ[n].MAXCNT When sending the DMA.SEQ[n].START task After having received the SEQSTARTED[n] event
SEQ[0].ENDDELAY

When sending the SEQSTART[0] task

Every time a new value from sequence [0] has been loaded from RAM and gets applied to the Wave Counter (indicated by the PWMPERIODEND event)

Before starting sequence [0] through a SEQSTART[0] task

When no more value from sequence [0] gets loaded from RAM (indicated by the SEQEND[0] event)

At any time during sequence [1] (which starts when the SEQSTARTED[1] event is generated)

SEQ[1].ENDDELAY

When sending the SEQSTART[1] task

Every time a new value from sequence [1] has been loaded from RAM and gets applied to the Wave Counter (indicated by the PWMPERIODEND event)

Before starting sequence [1] through a SEQSTART[1] task

When no more value from sequence [1] gets loaded from RAM (indicated by the SEQEND[1] event)

At any time during sequence [0] (which starts when the SEQSTARTED[0] event is generated)

SEQ[0].REFRESH

When sending the SEQSTART[0] task

Every time a new value from sequence [0] has been loaded from RAM and gets applied to the Wave Counter (indicated by the PWMPERIODEND event)

Before starting sequence [0] through a SEQSTART[0] task

At any time during sequence [1] (which starts when the SEQSTARTED[1] event is generated)

SEQ[1].REFRESH

When sending the SEQSTART[1] task

Every time a new value from sequence [1] has been loaded from RAM and gets applied to the Wave Counter (indicated by the PWMPERIODEND event)

Before starting sequence [1] through a SEQSTART[1] task

At any time during sequence [0] (which starts when the SEQSTARTED[0] event is generated)

COUNTERTOP

In DECODER.LOAD=WaveForm: this register is ignored.

In all other LOAD modes: at the end of current PWM period (indicated by the PWMPERIODEND event)

Before starting PWM generation through a DMA.SEQ[n].START task

After a STOP task has been triggered, and the STOPPED event has been received.

MODE Immediately

Before starting PWM generation through a DMA.SEQ[n].START task

After a STOP task has been triggered, and the STOPPED event has been received.

DECODER Immediately

Before starting PWM generation through a DMA.SEQ[n].START task

After a STOP task has been triggered, and the STOPPED event has been received.

PRESCALER Immediately

Before starting PWM generation through a DMA.SEQ[n].START task

After a STOP task has been triggered, and the STOPPED event has been received.

LOOP Immediately

Before starting PWM generation through a DMA.SEQ[n].START task

After a STOP task has been triggered, and the STOPPED event has been received.

PSEL.OUT[n] Immediately Before enabling the PWM instance through the ENABLE register
Note: SEQ[n].REFRESH and SEQ[n].ENDDELAY are ignored at the end of a complex sequence, indicated by a LOOPSDONE event. The reason for this is that the last value loaded from RAM is maintained until further action from software (restarting a new sequence, or stopping PWM generation).

The following figure shows a more complex example using the register LOOP.

Figure 6. Example using two sequences

Example using two sequences

In this case, an automated playback takes place, consisting of SEQ[0], delay 0, SEQ[1], delay 1, then again SEQ[0], etc. The user can choose to start a complex playback with SEQ[0] or SEQ[1] through sending the SEQSTART[0] or SEQSTART[1] task. The complex playback always ends with delay 1.

The two sequences 0 and 1 are defined by the addresses of value tables in RAM (pointed to by SEQ[n].PTR) and the buffer size (SEQ[n].MAXCNT). The rate at which a new value is loaded is defined individually for each sequence by SEQ[n].REFRESH. The chaining of sequence 1 following the sequence 0 is implicit, the LOOP.CNT register allows the chaining of sequence 1 to sequence 0 for a determined number of times. In other words, it allows to repeat a complex sequence a number of times in a fully automated way.

In the following code example, sequence 0 is defined with SEQ[0].REFRESH set to 1, meaning that a new PWM duty cycle is pushed every second PWM period. This complex sequence is started with the SEQSTART[0] task, so SEQ[0] is played first. Since SEQ[0].ENDDELAY=1 there will be one PWM period delay between last period on sequence 0 and the first period on sequence 1. Since SEQ[1].ENDDELAY=0 there is no delay 1, so SEQ[0] would be started immediately after the end of SEQ[1]. However, as LOOP.CNT is 1, the playback stops after having played SEQ[1] only once, and both SEQEND[1] and LOOPSDONE are generated (their order is not guaranteed in this case).

    
  NRF_PWM0->PSEL.OUT[0] = (first_port << PWM_PSEL_OUT_PORT_Pos) | 
                        (first_pin << PWM_PSEL_OUT_PIN_Pos) | 
                        (PWM_PSEL_OUT_CONNECT_Connected <<
                                                 PWM_PSEL_OUT_CONNECT_Pos);
  NRF_PWM0->ENABLE      = (PWM_ENABLE_ENABLE_Enabled << PWM_ENABLE_ENABLE_Pos);
  NRF_PWM0->MODE        = (PWM_MODE_UPDOWN_Up << PWM_MODE_UPDOWN_Pos);
  NRF_PWM0->PRESCALER   = (PWM_PRESCALER_PRESCALER_DIV_1 <<
                                                  PWM_PRESCALER_PRESCALER_Pos);
  NRF_PWM0->COUNTERTOP  = (16000 << PWM_COUNTERTOP_COUNTERTOP_Pos); //1 msec
  NRF_PWM0->LOOP        = (1 << PWM_LOOP_CNT_Pos);
  NRF_PWM0->DECODER   = (PWM_DECODER_LOAD_Common << PWM_DECODER_LOAD_Pos) | 
                        (PWM_DECODER_MODE_RefreshCount << PWM_DECODER_MODE_Pos);
  NRF_PWM0->DMA.SEQ[0].PTR   = ((uint32_t)(seq0_ram) << PWM_DMA_SEQ_PTR_PTR_Pos);
  NRF_PWM0->DMA.SEQ[0].MAXCNT  = (sizeof(seq0_ram) << PWM_DMA_SEQ_MAXCNT_MAXCNT_Pos);
  NRF_PWM0->SEQ[0].REFRESH  = 1;
  NRF_PWM0->SEQ[0].ENDDELAY = 1;
  NRF_PWM0->DMA.SEQ[1].PTR   = ((uint32_t)(seq1_ram) << PWM_DMA_SEQ_PTR_PTR_Pos);
  NRF_PWM0->DMA.SEQ[1].MAXCNT  = (sizeof(seq1_ram) << PWM_DMA_SEQ_MAXCNT_MAXCNT_Pos);
  NRF_PWM0->SEQ[1].REFRESH  = 0;
  NRF_PWM0->SEQ[1].ENDDELAY = 0;
  NRF_PWM0->TASKS_DMA.SEQ[0].START = 1;
    
   

The decoder can also be configured to asynchronously load new PWM duty cycle. If the DECODER.MODE register is set to NextStep, then the NEXTSTEP task will cause an update of internal compare registers on the next PWM period.

The following figures provide an overview of each part of an arbitrary sequence, in various modes (LOOP.CNT=0 and LOOP.CNT>0). In particular, the following are represented:
  • Initial and final duty cycle on the PWM output(s)
  • Chaining of SEQ[0] and SEQ[1] if LOOP.CNT>0
  • Influence of registers on the sequence
  • Events generated during a sequence
  • DMA activity (loading of next value and applying it to the output(s))
Figure 7. Single shot (LOOP.CNT=0)

Single shot (LOOP.CNT=0)

Note: The single-shot example also applies to SEQ[1]. Only SEQ[0] is represented for simplicity.
Figure 8. Complex sequence (LOOP.CNT>0) starting with SEQ[0]

Complex sequence (LOOP.CNT>0) starting with SEQ[0]

Figure 9. Complex sequence (LOOP.CNT>0) starting with SEQ[1]

Complex sequence (LOOP.CNT>0) starting with SEQ[1]

Note: If a sequence is in use in a simple or complex sequence, it must have a length of SEQ[n].MAXCNT > 0.
This example shows how the PWM module can be configured to repeat a single sequence until stopped.
NRF_PWM0->PSEL.OUT[0] = (first_port << PWM_PSEL_OUT_PORT_Pos) | 
                        (first_pin << PWM_PSEL_OUT_PIN_Pos) |
                        (PWM_PSEL_OUT_CONNECT_Connected <<
                                                PWM_PSEL_OUT_CONNECT_Pos);
NRF_PWM0->ENABLE      = (PWM_ENABLE_ENABLE_Enabled << PWM_ENABLE_ENABLE_Pos);
NRF_PWM0->MODE        = (PWM_MODE_UPDOWN_Up << PWM_MODE_UPDOWN_Pos);
NRF_PWM0->PRESCALER   = (PWM_PRESCALER_PRESCALER_DIV_1 <<
                                                PWM_PRESCALER_PRESCALER_Pos);
NRF_PWM0->COUNTERTOP  = (16000 << PWM_COUNTERTOP_COUNTERTOP_Pos); //1 msec
// Enable the shortcut from LOOPSDONE event to DMA.SEQ1.START task for infinite loop
NRF_PWM0->SHORTS      = (PWM_SHORTS_LOOPSDONE_DMA_SEQ1_START_Enabled <<
                                        PWM_SHORTS_LOOPSDONE_DMA_SEQ1_START_Pos);
// LOOP_CNT must be greater than 0 for the LOOPSDONE event to trigger and enable looping
NRF_PWM0->LOOP        = (1 << PWM_LOOP_CNT_Pos);
NRF_PWM0->DECODER     = (PWM_DECODER_LOAD_Common << PWM_DECODER_LOAD_Pos) | 
                    (PWM_DECODER_MODE_RefreshCount << PWM_DECODER_MODE_Pos);
// To repeat a single sequence until stopped, it must be configured in SEQ[1]
NRF_PWM0->DMA.SEQ[1].PTR  = ((uint32_t)(seq0_ram)) << PWM_DMA_SEQ_PTR_PTR_Pos;
NRF_PWM0->DMA.SEQ[1].MAXCNT  =(sizeof(seq0_ram) << PWM_DMA_SEQ_MAXCNT_MAXCNT_Pos);
NRF_PWM0->SEQ[1].REFRESH  = 0;
NRF_PWM0->SEQ[1].ENDDELAY = 0;
NRF_PWM0->TASKS_DMA.SEQ[1].START = 1;