Beware that this post is related to an SDK in maintenance mode
More Info: Consider nRF Connect SDK for new designs
This post is older than 2 years and might not be relevant anymore
More Info: Consider searching for newer posts

NRFX PWM driver timing

Hello,

I'm working on some LED sequences using the NRFX PWM driver and I can't get the durations I want right.

Let's say that I want a semi-blinking LED (100% - 50% - 100%...) with a period of 500 ms on each state and I initialize the PWM driver as follows:

nrfx_pwm_config_t const pwm_config =
{
	.output_pins =
	{
		led.red | led.polarity,     // Channel 0
		led.green | led.polarity,   // Channel 1
		led.blue | led.polarity,    // Channel 2
		NRFX_PWM_PIN_NOT_USED       // Channel 3
	},
	.irq_priority = APP_IRQ_PRIORITY_LOW,
	.base_clock   = NRF_PWM_CLK_16MHz,						// NRF_PWM_CLK_125kHz,
	.count_mode   = NRF_PWM_MODE_UP_AND_DOWN,				// For symmetric color mixing
	.top_value    = 255,
	.load_mode    = NRF_PWM_LOAD_INDIVIDUAL,
	.step_mode    = NRF_PWM_STEP_AUTO
};
err_code = nrfx_pwm_init(&m_pwm0, &pwm_config, pwm_event_handler);
APP_ERROR_CHECK(err_code);

As far as I understand I could do this to get my timing right:

void setup_single_color_sequence(nrf_pwm_sequence_t * sequence, color_t color, uint32_t duration_in_us) {
	uint32_t top = 255;
	uint32_t pwm_period = top / 16; 						// In µs (top / clock_frequency in MHz)
	uint16_t pwm_pulses_for_duration = duration_in_us / (pwm_period * NRF_PWM_VALUES_LENGTH(pwm_values_single_color));

	for (uint8_t i = 0; i < 2; i++) {
		pwm_values_single_color[i].channel_0 = color.r;
		pwm_values_single_color[i].channel_1 = color.g;
		pwm_values_single_color[i].channel_2 = color.b;
		pwm_values_single_color[i].channel_3 = 0;
	}

	sequence->values.p_individual		= pwm_values_single_color;
	sequence->length					= NRF_PWM_VALUES_LENGTH(pwm_values_single_color);
	sequence->repeats					= pwm_pulses_for_duration;
	sequence->end_delay					= 0;
}

That is using the .repeats field to make each duty cycle repeat in order to get my desired timing. That however doesn't really work, I get shorter periods.

My logic is: The PWM counts up to 255. If I'm running the PWM at 16 MHz, then each duty cycle has a duration of 255/16 MHz = ~16 us. If I want the PWM to stay at that level for 500 ms, then I divide that duration (in us) by the duration of the duty cycle and I get the number of times that I need to repeat each duty cycle in order to get my target duration.

Is that logic wrong?

Parents
  • I'm not sure if I follow your logic. Can you show me how  you would use this function to generate a signal of "(100% - 50% - 100%...) with a period of 500 ms on each state "?

    How much shorter than expected are the periods?

  • If I understand correctly I have two options:

    Create a sequence with both values, i.e. [100%, 50%] and call the function with a duration of 1 second. This probably wouldn't work because of the size of repeats (uint16), but I could do [100%, 100%, 50%, 50%] and maybe then it'd work. The code then would look like this maybe:

    for (uint8_t i = 0; i < 4; i++) {
        // Make the second half at 50%
        uint8_t divide = (i >= 2) ? 2 : 1;
        
    	pwm_values_single_color[i].channel_0 = color.r / divide;
    	pwm_values_single_color[i].channel_1 = color.g / divide;
    	pwm_values_single_color[i].channel_2 = color.b / divide;
    	pwm_values_single_color[i].channel_3 = 0;
    }

    This is not what I'm doing.

    What I'm doing is the second choice which would be to create two sequences and then playing them one after the other like this:

    nrfx_pwm_complex_playback(&m_pwm0, &pwm_sequence_1, &pwm_sequence_2, 1, NRFX_PWM_FLAG_LOOP);

    I am doing this because I'm not using just a static full lit LED, but rather I'm playing back a fade in/out sequence with a sine sequence. I want to use a single color at 0% or 50% as sequence #2 to space the waveform. So I would then call setup_single_color_sequence() to generate this pause with a specific duration.

    I hope that makes sense.

Reply
  • If I understand correctly I have two options:

    Create a sequence with both values, i.e. [100%, 50%] and call the function with a duration of 1 second. This probably wouldn't work because of the size of repeats (uint16), but I could do [100%, 100%, 50%, 50%] and maybe then it'd work. The code then would look like this maybe:

    for (uint8_t i = 0; i < 4; i++) {
        // Make the second half at 50%
        uint8_t divide = (i >= 2) ? 2 : 1;
        
    	pwm_values_single_color[i].channel_0 = color.r / divide;
    	pwm_values_single_color[i].channel_1 = color.g / divide;
    	pwm_values_single_color[i].channel_2 = color.b / divide;
    	pwm_values_single_color[i].channel_3 = 0;
    }

    This is not what I'm doing.

    What I'm doing is the second choice which would be to create two sequences and then playing them one after the other like this:

    nrfx_pwm_complex_playback(&m_pwm0, &pwm_sequence_1, &pwm_sequence_2, 1, NRFX_PWM_FLAG_LOOP);

    I am doing this because I'm not using just a static full lit LED, but rather I'm playing back a fade in/out sequence with a sine sequence. I want to use a single color at 0% or 50% as sequence #2 to space the waveform. So I would then call setup_single_color_sequence() to generate this pause with a specific duration.

    I hope that makes sense.

Children
No Data
Related