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

Infrared Transmitter only with PWM driver on nrf52

Hi all,

I need to transmit an infrared beacon every X seconds and the beacon does not change over time. I am using the PWM driver on nrf52 to generate the 38kHz carrier frequency and I am currently working on using a timer and PPI to turn on and off the carrier appropriately (mark and space).

However, I was wondering if it is possible to do the same thing only using the PWM driver. This would make the implementation simpler with less resources used.

I have the impression that, given the fact that my data is static, I can create a sequence of duty cycles that represents my data and then play it with the PWM driver. The actual duty cycles used would be only two, 50% for the marks and 0% for the spaces. The important thing (that I didn't figure out if it is possible or not yet) is to get the right timing for the marks and spaces. Maybe it is possible to use the playback_count in nrf_drv_pwm_simple_playback to define how long we should keep the 50% and 0% duty cycle, or maybe it is possible to use repeats and end_delay in nrf_pwm_sequence_t. I still don't have clear how these three parameters work together.

Any ideas/thoughts?

Thanks!

  • Hello Alessandro

    The playback_count in the nrf_drv_pwm_simple_playback decides how many times the entire sequence will be played. 1 will only play the sequence once, 2 will play it twice etc.

    The repeats set in nrf_pwm_sequence_t decides how many times each duty cycle defined in the sequence will be repeated, in addition to its initial run, before progressing to the next duty cycle.

    The end_delay set in nrf_pwm_sequence_t decides how long the PWM should maintain the final duty cycle of the sequence before moving on to the next sequence.

    Do note however, that if you configure the PWM with the NRF_DRV_PWM_FLAG_STOP flag, it will stop 1 period after the last value of the final sequence is loaded from RAM. This will override the repeats and end_delay set in nrf_pwm_sequence_t and can end the sequence earlier than you want. If you use the NRF_DRV_PWM_FLAG_LOOP flag it will complete all the repeats for the last duty cycle, but not the end_delay.

    With this, it might be possible to do what you want.

    With one sequence you could set the first duty cycle to 50%, and the second to 0%, and run it with nrf_drv_pwm_simple_playback, and the loop flag. The number of repeats in nrf_pwm_sequence_t would then decide how long you would hold each duty cycle. Holding one longer than the other could be achieved by adding additional lines of said duty cycle to the sequence. In this case your timings would be limited to a multiple of the PWM periods X repeats.

    With two sequences you could define one sequence with 50% Duty cycle and one with 0% and use the nrf_drv_pwm_complex_playback, with the loop flag. In this case you would be able to choose number of repeats for each sequence independently.

    Best regards

    Jørn Frøysa

  • Thanks Jørn for the clarification. The documentation is not very clear about the different parameters. I will try what you suggested when I am back in the office and I will report here how it goes.

  • Hi Jørn,

    I implemented your solution with one sequence and I put the code here as reference. In my application I don't need to change the data transmitted so the sequence is not modified after initialisation.

    I have two questions:

    1. at the beginning I was using the NRF_DRV_PWM_FLAG_LOOP flag and I was stopping the pwm in the handler upon reception of NRF_DRV_PWM_EVT_FINISHED event. However, I noticed that it works fine also if I use the NRF_DRV_PWM_FLAG_STOP flag and no handler to stop the pwm (as in the code below). Why is this the case? It shouldn't override the repeats field?

    2. Does the base_clock affect power consumption significantly? At the moment I am using 4MHz but I could lower it if this allows me to save power.

    Thanks!

    #define MARK_DUTY_CYLE 0x5A        
    #define SPACE_DUTY_CYLE 0x8000
    static nrf_pwm_sequence_t m_seq;
    void ir_transmitter_init(uint16_t code, uint8_t nbits)
    {
        uint32_t err_code;
        nrf_drv_pwm_config_t const config0 =
        {
            .output_pins =
            {
                IR_TX_PIN, // channel 0
                NRF_DRV_PWM_PIN_NOT_USED, // channel 1
                NRF_DRV_PWM_PIN_NOT_USED, // channel 2
                NRF_DRV_PWM_PIN_NOT_USED  // channel 3
            },
            .irq_priority = APP_IRQ_PRIORITY_LOW,
            .base_clock   = NRF_PWM_CLK_4MHz,
            .count_mode   = NRF_PWM_MODE_UP,		//edge-aligned PWM duty cycle
            .top_value    = m_top,
            .load_mode    = NRF_PWM_LOAD_COMMON,
            .step_mode    = NRF_PWM_STEP_AUTO
        };
        err_code = nrf_drv_pwm_init(&m_pwm0, &config0, NULL);
        APP_ERROR_CHECK(err_code);
    
        static uint16_t seq_values[DUTY_CYCLES_MAX_LENGTH] = 
        {
            MARK_DUTY_CYLE,  
            MARK_DUTY_CYLE,
            MARK_DUTY_CYLE,
            MARK_DUTY_CYLE,
            SPACE_DUTY_CYLE,
        };
    
        uint8_t index = 5;  // We start after the header
        for (unsigned long  mask = 1UL << (nbits - 1);  mask;  mask >>= 1) {
            if (code & mask) {
                // Mark 1 and HDR space
                seq_values[index++] = MARK_DUTY_CYLE;
                seq_values[index++] = MARK_DUTY_CYLE;
                seq_values[index++] = SPACE_DUTY_CYLE;
            } else {
                // Mark 0 and HDR space
                seq_values[index++] = MARK_DUTY_CYLE;
                seq_values[index++] = SPACE_DUTY_CYLE;
            }
        }
    
        m_seq.repeats = 22, // Each duty cycle is kept for 600us
        m_seq.end_delay = 0
        m_seq.values.p_common = seq_values;
        m_seq.length = index; // At the end of the loop the index variable has the actual number of duty cycles used
    }
    
    void ir_transmit(void)
    {
        nrf_drv_pwm_simple_playback(&m_pwm0, &m_seq, 1,  NRF_DRV_PWM_FLAG_STOP);
    }
    

    Your repeats are set to make each duty cycle hold for 600us. This should then be the case for your MARK_DUTY_CYCLE instances, but when it reaches the SPACE_DUTY_CYCLE it should only be executed for one PWM period ((1/(base_clock)*top_value before the pin is set to its default value (in your case high). If the SPACE_DUTY_CYCLE registers as a high, due to its high duty cycle, it might be hard to tell where it ends, and the default state of the pin begins.

    According to the product specification page 512, here, the current consumption of the PWM is the same for 8MHz and 125kHz. I therefore believe there is no change in power consumption with clock rates below 8MHz, though I have no actual measurements on that at this time.

  • Hi Alessandra,

    are you able to achieve what you were trying using the code provided in the comments?

    I want to implement something similar.

Related