This post is older than 2 years and might not be relevant anymore
More Info: Consider searching for newer posts

Duty Cycle, Sequence Values, and Top Value Relationship to achieve 50% duty cycle

I'm using Ubuntu 20.04, SES 4.52c, SDK 16 nRF52840 DK board.

I'm going to need to use the PWM driver to supply clock pulses to an I2S microphone and the I2S peripheral running in slave mode. So I decided to practice a little with the PWM driver. I started out with the PWM Library but it quickly became apparent that the PWM Library wasn't going to work for my needs.

I'm using the following code to mess around with square wave production. The square wave should have a 50% duty cycle by definition. All I want is two square waves with two different frequencies.

#define PWM_I2S_SCK_PIN     NRF_GPIO_PIN_MAP(1, 01)           // I2S SCK pin for PWM-synthesized I2S clocks
#define PWM_I2S_WS_PIN      NRF_GPIO_PIN_MAP(1, 02)           // I2S LRCK pin for PWM-synthesized I2S clocks
#define I2S_MIC_POWER       NRF_GPIO_PIN_MAP(1, 03)

#define TOP_VAL           4
static nrf_pwm_values_common_t seq_values[] = {(TOP_VAL / 2)};
nrf_pwm_sequence_t const seq =
{
    .values.p_common     = seq_values,
    .length              = NRF_PWM_VALUES_LENGTH(seq_values),
    .repeats             = 0,
    .end_delay           = 0
};

nrfx_pwm_config_t const config_sck =
{
    .output_pins =
    {
        PWM_I2S_SCK_PIN,       // channel 0
        NRFX_PWM_PIN_NOT_USED, // channel 1
        NRFX_PWM_PIN_NOT_USED, // channel 2
        NRFX_PWM_PIN_NOT_USED  // channel 3
    },
    .irq_priority = APP_IRQ_PRIORITY_LOWEST,
    .base_clock   = NRF_PWM_CLK_8MHz,
    .count_mode   = NRF_PWM_MODE_UP,
    .top_value    = TOP_VAL,
    .load_mode    = NRF_PWM_LOAD_COMMON,
    .step_mode    = NRF_PWM_STEP_AUTO
};
static nrfx_pwm_t m_pwm2_sck = NRFX_PWM_INSTANCE(2);


nrfx_pwm_config_t const config_lrck =
{
    .output_pins =
    {
        PWM_I2S_WS_PIN,        // channel 0
        NRFX_PWM_PIN_NOT_USED, // channel 1
        NRFX_PWM_PIN_NOT_USED, // channel 2
        NRFX_PWM_PIN_NOT_USED  // channel 3
    },
    .irq_priority = APP_IRQ_PRIORITY_LOWEST,
    .base_clock   = NRF_PWM_CLK_8MHz,
    .count_mode   = NRF_PWM_MODE_UP,
    .top_value    = TOP_VAL,
    .load_mode    = NRF_PWM_LOAD_COMMON,
    .step_mode    = NRF_PWM_STEP_AUTO
};
static nrfx_pwm_t m_pwm3_lrck = NRFX_PWM_INSTANCE(3);


int main(void)
{
    bool              result = true;
    char              c = 0;
    uint32_t          err_code = NRF_SUCCESS;

    // "To ensure correct behavior in the PWM module, the pins that are used must be configured in the GPIO peripheral
    // as Output and Output 0 before the PWM module is enabled:"
    nrf_gpio_cfg_output(PWM_I2S_SCK_PIN);
    nrf_gpio_pin_clear(PWM_I2S_SCK_PIN);
    nrf_gpio_cfg_output(PWM_I2S_WS_PIN);
    nrf_gpio_pin_clear(PWM_I2S_WS_PIN);

    for (;;)
    {

        c = SEGGER_RTT_WaitKey(); // will block until data is available
        if (c == 's')
        {
            // "If NULL is passed, event notifications are not done and PWM interrupts are disabled."
            err_code = nrfx_pwm_init(&m_pwm2_sck, &config_sck, NULL);
            APP_ERROR_CHECK(err_code);
            err_code = nrfx_pwm_simple_playback(&m_pwm2_sck, &seq, 1, NRFX_PWM_FLAG_LOOP);
        }
        else if (c == 'd')
        {
            err_code = nrfx_pwm_init(&m_pwm3_lrck, &config_lrck, NULL);
            APP_ERROR_CHECK(err_code);
            err_code = nrfx_pwm_simple_playback(&m_pwm3_lrck, &seq, 1, NRFX_PWM_FLAG_LOOP);
        }
        else if (c == 'c')
        {
            // "If NULL is passed, event notifications are not done and PWM interrupts are disabled."
            err_code = nrfx_pwm_init(&m_pwm2_sck, &config_sck, NULL);
            APP_ERROR_CHECK(err_code);
            err_code = nrfx_pwm_simple_playback(&m_pwm2_sck, &seq, 1, NRFX_PWM_FLAG_LOOP);

            err_code = nrfx_pwm_init(&m_pwm3_lrck, &config_lrck, NULL);
            APP_ERROR_CHECK(err_code);
            err_code = nrfx_pwm_simple_playback(&m_pwm3_lrck, &seq, 1, NRFX_PWM_FLAG_LOOP);
        }
        else if (c == 'v')
        {
            result = nrfx_pwm_is_stopped(&m_pwm2_sck);
            if (!result)
            {
                // If 2nd parameter is true, the function will not return until the playback is stopped.
                result = nrfx_pwm_stop(&m_pwm2_sck, 1);
            }
            nrfx_pwm_uninit (&m_pwm2_sck);

            result = nrfx_pwm_is_stopped(&m_pwm3_lrck);
            if (!result)
            {
                result = nrfx_pwm_stop(&m_pwm3_lrck, 1);
            }
            nrfx_pwm_uninit (&m_pwm3_lrck);
        }

    }
}

Question 1: How do you use .top_value and nrf_pwm_values_common_t seq_values[] to produce a 50% duty cycle? 

I've figured out that for a 50% duty cycle, sequence has to be 1/2 of top value but when you look at these two screen shots you can see that the peak and the trough have different widths. Is that to be expected and is it unavoidable?

Question 2: Is there anyway of starting two separate nrfx_pwm_config_t instances simultaneously?

You can see in the screen shots that the 2nd channel is slightly ahead of the 1st channel. This delay is no doubt caused by the sequential calls to nrfx_pwm_simple_playback to start each of the channels. I guess the only way is to use two of the four channels in one instances of a nrfx_pwm_config_t. If I wanted my 2nd channel to have a duty cycle that was 32x's larger than my first channel, how would I put the sequence array together?

Question 3: Is there any reason GPIO pins 0.00 through 0.10 wouldn't be available for use by the PWM Driver?

I originally tried to use those pins on the DK board but never got a signal out. Pins 1.01 through 1.15 work just fine.

Thank you for any advice and help.

Parents
  • Why can't you generate the MCK with the I2S peripheral?

    I think you can use just one PWM for both your outputs if you intend to use the same PWM frequency for both outputs. 

    Question 1: How do you use .top_value and nrf_pwm_values_common_t seq_values[] to produce a 50% duty cycle? 

     The nrf_pwm_values_common_t is an array of uint16_t, so I think it's just the raw compare value of the timer. Ie. half your COUNTERTOP value for 50% duty-cycle. 

    I've figured out that for a 50% duty cycle, sequence has to be 1/2 of top value but when you look at these two screen shots you can see that the peak and the trough have different widths. Is that to be expected and is it unavoidable?

     That's not what your scope show. You've measured a difference in time of two full periods, not a peak and a trough. What you have shown is that there is a variance in the PWM frequency, which could be due to either capacitive loading or an inaccurate clock. IF the HFCLK is running from the internal 64MHz RC oscillator then there is usually some jitter, but I think a more likely culprit is capacitive loading. The Saleae probes are ~10pF and the pin is 3pF, if there's other stuff connected then the capacitance increases further. I suggest you start the HFXO and see if that helps, if it does not then it's definitely due to capacitive loading. 


     

    Question 2: Is there anyway of starting two separate nrfx_pwm_config_t instances simultaneously?

     You can use the EGU to generate an event and connect it to the TASKS_SEQSTART[n] task of both PWM instances (you need to 'fork' the event). 

     

    If I wanted my 2nd channel to have a duty cycle that was 32x's larger than my first channel, how would I put the sequence array together?

     Do they still share the same PWM frequency (COUNTERTOP)? If so the sequence value must be 32x larger.

     

    Question 3: Is there any reason GPIO pins 0.00 through 0.10 wouldn't be available for use by the PWM Driver?

     See Connector interface


Reply
  • Why can't you generate the MCK with the I2S peripheral?

    I think you can use just one PWM for both your outputs if you intend to use the same PWM frequency for both outputs. 

    Question 1: How do you use .top_value and nrf_pwm_values_common_t seq_values[] to produce a 50% duty cycle? 

     The nrf_pwm_values_common_t is an array of uint16_t, so I think it's just the raw compare value of the timer. Ie. half your COUNTERTOP value for 50% duty-cycle. 

    I've figured out that for a 50% duty cycle, sequence has to be 1/2 of top value but when you look at these two screen shots you can see that the peak and the trough have different widths. Is that to be expected and is it unavoidable?

     That's not what your scope show. You've measured a difference in time of two full periods, not a peak and a trough. What you have shown is that there is a variance in the PWM frequency, which could be due to either capacitive loading or an inaccurate clock. IF the HFCLK is running from the internal 64MHz RC oscillator then there is usually some jitter, but I think a more likely culprit is capacitive loading. The Saleae probes are ~10pF and the pin is 3pF, if there's other stuff connected then the capacitance increases further. I suggest you start the HFXO and see if that helps, if it does not then it's definitely due to capacitive loading. 


     

    Question 2: Is there anyway of starting two separate nrfx_pwm_config_t instances simultaneously?

     You can use the EGU to generate an event and connect it to the TASKS_SEQSTART[n] task of both PWM instances (you need to 'fork' the event). 

     

    If I wanted my 2nd channel to have a duty cycle that was 32x's larger than my first channel, how would I put the sequence array together?

     Do they still share the same PWM frequency (COUNTERTOP)? If so the sequence value must be 32x larger.

     

    Question 3: Is there any reason GPIO pins 0.00 through 0.10 wouldn't be available for use by the PWM Driver?

     See Connector interface


Children
  • Thank you haakonsh.

    Question 3 - I missed the Connector Interface section for the nRF52840 DK. I've got to open my eyes.

    Question 2 - I am working on your 2nd suggestion and think it will do the job. I'll definitely check out your first suggestion.

    Question 1 - This all originated from Devzone case ID's: 115712, 114746, 112874, 235644, etc. I'm using the same Knowles microphone and need 24bit data from a 1 to 4 MHz SCK. I think the only solution is to use the PWM Driver to supply the SCK and WS/LRCK to the I2S Peripheral setup in Slave Mode. Decoding the I2S Peripheral's output will be another challenge.

    "You've measured a difference in time of two full periods, not a peak and a trough. " That was a key observation for me, I didn't understand that control of the PWM, (top value / sequence value), was an entire period for the PWM. Putting these PWM outputs together produces a "square wave" with a given period. It is beginning to make sense to me now.

    Thanks again for your fast response.

Related