PWM Sequence from HAL to NRFX

Hello

I am trying to change the code from the HAL code up to NRFX in order to make it event-based instead of using infinite while loops. 

I am using 52840 on a custom PCA with goal in mind to create a half-duplex-half-UART communication.

Working with HAL drivers, the code works as intended, but it is not optimized and is very power hungry for our needs. 

The HAL Setup and sending of the message (Byte by byte, each byte 10 bits at a time (Start - 8xData - Stop)): 

TxSetup()
{
    NRF_P0->DIRSET = ((1UL) << PIN_NEG); // PIN_NEG as output
    NRF_P0->OUTCLR = ((1UL) << PIN_NEG); // initial LOW on PIN_NEG

    NRF_P0->DIRSET = ((1UL) << PIN_POS); // PIN_POS as output
    NRF_P0->OUTSET = ((1UL) << PIN_POS); // initial HIGH on PIN_POS

    CounterTopRegister = F_CLK / F_PWM; // F_CLK = 16000000
    cyclesPerBit = F_PWM / LL_BAUDRATE; // F_PWM = 32400 ; LL_BAUDRATE = 1200
    P_PWM_HIGH = CounterTopRegister;
    P_PWM_LOW = (int)(0.5 * CounterTopRegister);
    N_PWM_HIGH = P_PWM_HIGH + 0x8000;
    N_PWM_LOW = P_PWM_LOW + 0x8000;

    NRF_PWM2->PSEL.OUT[0] = (PIN_POS << PWM_PSEL_OUT_PIN_Pos) | (PIN_POS << PWM_PSEL_OUT_PORT_Pos) | (PWM_PSEL_OUT_CONNECT_Connected << PWM_PSEL_OUT_CONNECT_Pos);
    NRF_PWM2->PSEL.OUT[1] = (PIN_NEG << PWM_PSEL_OUT_PIN_Pos) | (PIN_NEG << PWM_PSEL_OUT_PORT_Pos) | (PWM_PSEL_OUT_CONNECT_Connected << PWM_PSEL_OUT_CONNECT_Pos);
    NRF_PWM2->MODE = (PWM_MODE_UPDOWN_Up << PWM_MODE_UPDOWN_Pos);
    NRF_PWM2->PRESCALER = (PWM_PRESCALER_PRESCALER_DIV_1 << PWM_PRESCALER_PRESCALER_Pos);
    NRF_PWM2->COUNTERTOP = (CounterTopRegister << PWM_COUNTERTOP_COUNTERTOP_Pos);
    NRF_PWM2->LOOP = (0 << PWM_LOOP_CNT_Pos);
    NRF_PWM2->DECODER = (PWM_DECODER_LOAD_Individual << PWM_DECODER_LOAD_Pos) | (PWM_DECODER_MODE_RefreshCount << PWM_DECODER_MODE_Pos);
    NRF_PWM2->SEQ[0].PTR = ((uint32_t)(initialSequence) << PWM_SEQ_PTR_PTR_Pos);

    NRF_PWM2->SEQ[0].CNT = 44;
    NRF_PWM2->SEQ[0].REFRESH = cyclesPerBit - 1;
    NRF_PWM2->SEQ[0].ENDDELAY = 0;
}

void TxWrite(uint8_t txVal)
{
    int i;
    // Generate the Startbit (LOW)
    initialSequence[0] = P_PWM_LOW;
    initialSequence[1] = N_PWM_LOW;
    initialSequence[2] = 0;
    initialSequence[3] = 0;
    // Generate the 8 databits
    for (i = 1; i < 9; i++)
    {
        if (txVal & 1)
        {
            // Bit is HIGH
            initialSequence[4 * i + 0] = P_PWM_HIGH;
            initialSequence[4 * i + 1] = N_PWM_HIGH;
        }
        else
        {
            // Bit is LOW
            initialSequence[4 * i + 0] = P_PWM_LOW;
            initialSequence[4 * i + 1] = N_PWM_LOW;
        }
        initialSequence[4 * i + 2] = 0;
        initialSequence[4 * i + 3] = 0;
        txVal = txVal >> 1;
    }

    // Generate the Stopbit (HIGH)
    initialSequence[36] = P_PWM_HIGH;
    initialSequence[37] = N_PWM_HIGH;
    initialSequence[38] = 0;
    initialSequence[39] = 0;
    initialSequence[40] = P_PWM_HIGH;
    initialSequence[41] = N_PWM_HIGH;
    initialSequence[42] = 0;
    initialSequence[43] = 0;

    NRF_PWM2->ENABLE = (PWM_ENABLE_ENABLE_Enabled << PWM_ENABLE_ENABLE_Pos);

    NRF_PWM2->TASKS_SEQSTART[0] = 1;
}

void TxWaitDone()
{

    while (NRF_PWM2->EVENTS_SEQEND[0] == 0)
        ;
    NRF_PWM2->EVENTS_SEQEND[0] = 0;
}

void TxStop()
{

    NRF_PWM2->TASKS_STOP = 1;

    while (NRF_PWM2->EVENTS_STOPPED == 0)
        ;
    NRF_PWM2->EVENTS_STOPPED = 0;

    NRF_PWM2->ENABLE = (PWM_ENABLE_ENABLE_Disabled << PWM_ENABLE_ENABLE_Pos);
}


WriteMessage(uint8_t *msg, uint8_t len)
{
    nrfx_lpcomp_disable();
    NRFX_IRQ_DISABLE(SAADC_IRQn);
    for (int i = 0; i < len; i++)
    {
        TxWrite(msg[i]);
        TxWaitDone();
        TxStop();
    }
    lpcomp_event_count = 0;
    is_carrier_found = 0;
    NRFX_IRQ_ENABLE(COMP_LPCOMP_IRQn);
    nrfx_lpcomp_enable();
    return 0;
}

The issue with this approach, again, is the while loops that, among other issues, can sometimes get stuck and hang the rest of the process. 

The actual issue rewriting this with NRFX drivers is the fact that I cannot, for the life of me, figure out how to write out my "initialSequence." I can get the PWM to toggle on or off, but not actually make it do that in sequence as I need it to. I tried following a few examples I found on Devzone, but nothing concrete enough to help me with my issue. 

The HAL Implementation repeats every 100 milliseconds, so the NRFX one should be able to match that. If anyone has an idea of how to write this using the NRFX drivers, or to point me in the direction of a set-up example, I'd be very grateful. 
To add to this: The idea is for this code to run along with a Zephyr based system that runs the rest of the functionality of the board (Thus why we're trying to make it event based as opposed to current implementation).

Thank you in advanced for response. If you need any more information, please ask. 

EDIT: Added the missing code that was calling the TX sequence in order. (Write, Wait, Stop)

Parents
  • I should add the current (latest) attempt at implementing, so maybe that could be corrected in the right way:

    nrfx_pwm_t pwm = NRFX_PWM_INSTANCE(2);
    static nrf_pwm_values_individual_t pwm_values[2];
    nrf_pwm_sequence_t pwm_sequence;
    
    void pwm_tx_init()
    {
        nrfx_pwm_config_t pwm_config;
    
        pwm_config.output_pins[0] = PIN_POS;
        pwm_config.output_pins[1] = PIN_NEG;
        pwm_config.output_pins[2] = NRFX_PWM_PIN_NOT_USED;
        pwm_config.output_pins[3] = NRFX_PWM_PIN_NOT_USED;
    
        pwm_config.base_clock = NRF_PWM_CLK_16MHz;
        pwm_config.count_mode = NRF_PWM_MODE_UP;
        pwm_config.top_value = (F_CLK / F_PWM);
        pwm_config.load_mode = NRF_PWM_LOAD_WAVE_FORM;
        pwm_config.step_mode = NRF_PWM_STEP_AUTO;
        pwm_config.skip_gpio_cfg = false;
        pwm_config.skip_psel_cfg = false;
        pwm_config.irq_priority = 5;
    
        nrfx_pwm_init(&pwm, &pwm_config, pwm_transmit_evt_handler, 0);
        IRQ_DIRECT_CONNECT(COMP_LPCOMP_IRQn, 5, nrfx_pwm_2_irq_handler, 0);
    }
    
    
    void pwm_tx_transmit(uint8_t *msg)
    {
        set_sequence(0x80);
        nrf_pwm_values_t sequence_values;
        sequence_values.p_raw = initialSequence;
        nrf_pwm_sequence_t sequence1;
        sequence1.repeats = (F_PWM / LL_BAUDRATE) - 1;
        sequence1.length = 44;
        sequence1.end_delay = 0;
        sequence1.values = sequence_values;
       
    }
    void make_sequence(uint16_t duty_p_p, uint16_t duty_p_n, uint16_t duty_n_p, uint16_t duty_n_n)
    {
    
        pwm_sequence.values.p_individual = pwm_values;
        pwm_sequence.length = NRF_PWM_VALUES_LENGTH(pwm_values);
        uint32_t repeats = (F_PWM / LL_BAUDRATE) - 1;
        pwm_sequence.repeats = repeats;
        pwm_sequence.end_delay = 0;
    
        pwm_values[0].channel_0 = duty_p_p;
        pwm_values[0].channel_1 = duty_p_n;
    
        pwm_values[1].channel_0 = duty_n_p;
        pwm_values[1].channel_1 = duty_n_n;
        nrfx_pwm_simple_playback(&pwm, &pwm_sequence, 1, NRFX_PWM_FLAG_STOP);
    }

    This is just a few itterations after, at this point I'm unsure if it has any relation to my initial sequence anyway. 
    The "initialSequence" array is filled in the same way as in the HAL implementation.

  • I can't get this reply to line up, but anyway I see your issue. The PWM continues to play the last (maybe only)  cycle indefinitely until you issue a STOP TASK and wait for the STOPPED Event. This can be automated by setting SHORTS:

       NRF_PWM_Type * const pNRF_PWM = NRF_PWM2;
       // PWM_SHORTS_SEQEND0_STOP_Msk         Shortcut between SEQEND[0] event and STOP task.
       // PWM_SHORTS_SEQEND1_STOP_Msk         Shortcut between SEQEND[1] event and STOP task.
       // PWM_SHORTS_LOOPSDONE_SEQSTART0_Msk  Shortcut between LOOPSDONE event and SEQSTART[0] task.
       // PWM_SHORTS_LOOPSDONE_SEQSTART1_Msk  Shortcut between LOOPSDONE event and SEQSTART[1] task.
       // PWM_SHORTS_LOOPSDONE_STOP_Msk        Shortcut between LOOPSDONE event and STOP task.
       pNRF_PWM->SHORTS = NRF_PWM_SHORT_LOOPSDONE_STOP_MASK;

    "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 last loaded value."

  • Replies seem to have issues for some reason. 

    But regarding the issue, I think you misunderstood me, I have issue actually setting up the PWM ( I Think).

    I call "nrfx_pwm_simple_playback(&pwm, &pwm_sequence, 1, NRFX_PWM_FLAG_STOP);"
    And this is what drives my sequence. I no longer want to have direct registry access, unless absolutely needed. I wonder what, from the provided code, is wrong with the way I'm using NRFX API. And what do I need to modify in the code for it to execute the same function that my HAL code does. 
    The best I can achieve with NRFX is making PWM output a loop of repeating peak values at a defined duty cycle. 
    And it keeps being stuck on 1khz, where the HAL code works on 32Khz. 

  • Oh, maybe try changing the LOOPS (disguised as repeats):

        uint32_t repeats = (F_PWM / LL_BAUDRATE) - 1;
        uint32_t repeats = 0;

Reply Children
  • Still no Cigar. 

    I thought that LOOPS are actually the "playback Count" in the arguments of simple_playback. 
    And the Repeats was the same as SEQ[n].REFRESH? 

    My assumption is that something in the API is not properly configured, or I am not doign something in the right order. 

  • 1, not 0, for the repeats - sorry my typo. Maybe post the current code you are using, the earlier code is a little hard to follow with some unused code. Also include the main sequence if possible. I sometimes use the same api without any issues, though often use the bare metal drivers. A screen shot of the PWM registers might help in debug to confirm actual values loaded are not being obfuscated by the library code.

    REFRESH is a way of using more clock cycles per PWM value step, so typically set to 0.

  • Here's the current code in NRFX: 

    void pwm_transmit_evt_handler(nrfx_pwm_evt_type_t event_type, void *p_context)
    {
        switch (event_type)
        {
        case NRFX_PWM_EVT_FINISHED:
            LOG_INF("PWM EVENT! NRFX_PWM_EVT_FINISHED");
            break;
        case NRFX_PWM_EVT_END_SEQ0:
            LOG_INF("PWM EVENT! NRFX_PWM_EVT_END_SEQ0");
            break;
        case NRFX_PWM_EVT_END_SEQ1:
            LOG_INF("PWM EVENT! NRFX_PWM_EVT_END_SEQ1");
            break;
        case NRFX_PWM_EVT_STOPPED:
            LOG_INF("PWM EVENT! NRFX_PWM_EVT_STOPPED");
            break;
        }
    }
    
    void pwm_tx_init()
    {
        uint16_t CounterTopRegister;
        CounterTopRegister = F_CLK / F_PWM;
        nrfx_pwm_config_t pwm_config;
    
        pwm_config.output_pins[0] = PIN_POS;
        pwm_config.output_pins[1] = PIN_NEG;
        pwm_config.output_pins[2] = NRFX_PWM_PIN_NOT_USED;
        pwm_config.output_pins[3] = NRFX_PWM_PIN_NOT_USED;
    
        pwm_config.base_clock = NRF_PWM_CLK_16MHz ;
        pwm_config.count_mode = NRF_PWM_MODE_UP;
        pwm_config.top_value = CounterTopRegister;
        pwm_config.load_mode = NRF_PWM_LOAD_INDIVIDUAL;
        pwm_config.step_mode = NRF_PWM_STEP_AUTO;
        pwm_config.skip_gpio_cfg = false;
        pwm_config.skip_psel_cfg = false;
        pwm_config.irq_priority = 5;
    
        nrfx_pwm_init(&pwm, &pwm_config, pwm_transmit_evt_handler, 0);
        IRQ_DIRECT_CONNECT(COMP_LPCOMP_IRQn, 5, nrfx_pwm_2_irq_handler, 0);
    }
    
    void set_sequence(uint8_t txVal)
    {
        uint16_t P_PWM_HIGH;
        uint16_t P_PWM_LOW;
        uint16_t N_PWM_HIGH;
        uint16_t N_PWM_LOW;
        P_PWM_HIGH = (F_CLK / F_PWM);
        P_PWM_LOW = (int)(0.5 * (F_CLK / F_PWM));
        N_PWM_HIGH = P_PWM_HIGH + 0x8000;
        N_PWM_LOW = P_PWM_LOW + 0x8000;
    
        int i;
    
        // Generate the Startbit (LOW)
        initialSequence[0] = P_PWM_LOW;
        initialSequence[1] = N_PWM_LOW;
        initialSequence[2] = 0;
        initialSequence[3] = 0;
        //  LOG_INF("S");
    
        // Generate the 8 databits
        for (i = 1; i < 9; i++)
        {
            if (txVal & 1)
            {
                // Bit is HIGH
                //  LOG_INF("1");
                initialSequence[4 * i + 0] = P_PWM_HIGH;
                initialSequence[4 * i + 1] = N_PWM_HIGH;
            }
            else
            {
                // Bit is LOW
                //  LOG_INF("0");
                initialSequence[4 * i + 0] = P_PWM_LOW;
                initialSequence[4 * i + 1] = N_PWM_LOW;
            }
            initialSequence[4 * i + 2] = 0;
            initialSequence[4 * i + 3] = 0;
            txVal = txVal >> 1;
        }
    
        // Generate the Stopbit (HIGH)
        initialSequence[36] = P_PWM_HIGH;
        initialSequence[37] = N_PWM_HIGH;
        initialSequence[38] = 0;
        initialSequence[39] = 0;
        initialSequence[40] = P_PWM_HIGH;
        initialSequence[41] = N_PWM_HIGH;
        initialSequence[42] = 0;
        initialSequence[43] = 0;
    }
    
    
    nrf_pwm_sequence_t pwm_sequence;
    
    void make_sequence()
    {
        pwm_sequence.values.p_raw = initialSequence;
        pwm_sequence.length = 44;
        pwm_sequence.repeats = 18;
        pwm_sequence.end_delay = 0;
    
        nrfx_pwm_simple_playback(&pwm, &pwm_sequence, 0, 0);
    }
    
    void pwm_tx_transmit(uint8_t msg)
    {
        set_sequence(msg);
        make_sequence();
    }
    
    int TxMessage(uint8_t *msg, uint8_t len)
    {
        nrfx_lpcomp_disable();
    
        NRFX_IRQ_DISABLE(SAADC_IRQn);
        for (int i = 0; i < len; i++)
        {
            pwm_tx_transmit(msg[i]);
        }
        lpcomp_event_count = 0;
        is_carrier_found = 0;
        NRFX_IRQ_ENABLE(COMP_LPCOMP_IRQn);
        nrfx_lpcomp_enable();
        return 0;
    }
    
    
    // Elsewhere in Code:
    void Init()
    {
        pwm_tx_init();
        initLpComp();
    }
    
    request_data()
    {
        uint8_t STX = 0x82;
        uint8_t signal_id = 0x64 | 0x80;
        uint8_t data_byte_0 = 0x03 | 0x80;
    
        uint8_t checksum = (signal_id) + (data_byte_0);
        checksum &= 0x7F;
    
        uint8_t message[4] = {STX, signal_id, data_byte_0, checksum};
        TxMessage(message, 4);
    }

    After trying and implementing your method of constructing the message, the result was no different. Following your advice on pins being set/cleared. I've specifically set the pin, but no cigar. 

    Here is the Screengrab of the registers. On the left side is the current code (NOT WORKING) 
    On the right side is the Bare-metal/HAL Code (WORKING). 

    One thing I've noticed, that keeps being obvious, is that no matter what I do  to the top_value, the countertop doesn't change in any way. Likewise, the DECODER register keeps being missmatched, no matter which one I try. Also for some reason with the NRFX code there is value on PTR_1, CNT_1, REFRESH_1. Unlike the Bare Metal approach. 

    Here is the Scope screen grab of the Current NRFX approach. The bare metal approach still looks like in the image provided earlier. 

    If you have any clue as to what is causing this issue, that'd be amazing. 

    I have edited the code according to the bare-metal code trying to match as many registers as possible. But it does not seem to work for me in any approach.


    Thank you so much for taking your time to try and help me out! 

  • So I have solutions for you, but rather than edit the code just posted I just wrote the simplest which does the job (hopefully). There are a few requirements (my interpretation):

    • 10 bit bytes (Start - 8xData - Stop)
    • single char or burst (packet) transmission
    • Differential output using 2 pins
    • No level change on pins outside transmitted data to avoid spurious character detection

    First example is low-level, just to show the register requirements (I tested this code, transmits a 2-byte packet):

    // PWM Half-Duplex UART
    // ====================
    #include "nrf_delay.h"
    #include "nrf_pwm.h"
    
    #define LL_BAUDRATE      1200
    #define F_CLK        16000000              // nRF52832/nRF52840 fixed at 16MHz
    #define F_PRESCALER  (PWM_PRESCALER_PRESCALER_DIV_1)  // Divide by 1, 16MHz clock
    #define COUNTER_TOP  ((F_CLK+(LL_BAUDRATE/2)) / LL_BAUDRATE)
    STATIC_ASSERT(COUNTER_TOP < 32768, "COUNTER_TOP value too large for 15-bit register");
    STATIC_ASSERT(COUNTER_TOP >= 3, "COUNTER_TOP value too small for correct operation");
    #define BIT_LOW               0           // Normal encoding
    #define BIT_HIGH    (COUNTER_TOP)         // Normal encoding
    #define PWM_NEG (PIN_FEATHER_D6)          // Low level outside transmission
    #define PWM_POS (PIN_FEATHER_D9)          // High level outside transmission
    #define STX_BYTE 0x02 // Example start character: STX (^B)
    #define ETX_BYTE 0x03 // Example end character:   ETX (^C)
    
    // Example STX byte in little-endian format
    #define STX_BIT_0 (((STX_BYTE>>0) & 0x01) ? BIT_HIGH : BIT_LOW)
    #define STX_BIT_1 (((STX_BYTE>>1) & 0x01) ? BIT_HIGH : BIT_LOW)
    #define STX_BIT_2 (((STX_BYTE>>2) & 0x01) ? BIT_HIGH : BIT_LOW)
    #define STX_BIT_3 (((STX_BYTE>>3) & 0x01) ? BIT_HIGH : BIT_LOW)
    #define STX_BIT_4 (((STX_BYTE>>4) & 0x01) ? BIT_HIGH : BIT_LOW)
    #define STX_BIT_5 (((STX_BYTE>>5) & 0x01) ? BIT_HIGH : BIT_LOW)
    #define STX_BIT_6 (((STX_BYTE>>6) & 0x01) ? BIT_HIGH : BIT_LOW)
    #define STX_BIT_7 (((STX_BYTE>>7) & 0x01) ? BIT_HIGH : BIT_LOW)
    // Example ETX byte in little-endian format
    #define ETX_BIT_0 (((ETX_BYTE>>0) & 0x01) ? BIT_HIGH : BIT_LOW)
    #define ETX_BIT_1 (((ETX_BYTE>>1) & 0x01) ? BIT_HIGH : BIT_LOW)
    #define ETX_BIT_2 (((ETX_BYTE>>2) & 0x01) ? BIT_HIGH : BIT_LOW)
    #define ETX_BIT_3 (((ETX_BYTE>>3) & 0x01) ? BIT_HIGH : BIT_LOW)
    #define ETX_BIT_4 (((ETX_BYTE>>4) & 0x01) ? BIT_HIGH : BIT_LOW)
    #define ETX_BIT_5 (((ETX_BYTE>>5) & 0x01) ? BIT_HIGH : BIT_LOW)
    #define ETX_BIT_6 (((ETX_BYTE>>6) & 0x01) ? BIT_HIGH : BIT_LOW)
    #define ETX_BIT_7 (((ETX_BYTE>>7) & 0x01) ? BIT_HIGH : BIT_LOW)
    
    // Example using grouped, note uses pin channels 0 and 2 not 0 and 1
    nrf_pwm_values_grouped_t halfDuplexUartMsg[] = {
      //   Index   Normal pin 0        Inverted pin 2
      //   =====   =================== ==============
      { /* 0:0  */ 0x8000|(BIT_LOW),   (BIT_LOW),  }, // Start bit
      { /* 0:1  */ 0x8000|(STX_BIT_0), (STX_BIT_0) },
      { /* 0:2  */ 0x8000|(STX_BIT_1), (STX_BIT_1) },
      { /* 0:3  */ 0x8000|(STX_BIT_2), (STX_BIT_2) },
      { /* 0:4  */ 0x8000|(STX_BIT_3), (STX_BIT_3) },
      { /* 0:5  */ 0x8000|(STX_BIT_4), (STX_BIT_4) },
      { /* 0:6  */ 0x8000|(STX_BIT_5), (STX_BIT_5) },
      { /* 0:7  */ 0x8000|(STX_BIT_6), (STX_BIT_6) },
      { /* 0:8  */ 0x8000|(STX_BIT_7), (STX_BIT_7) },
      { /* 0:9  */ 0x8000|(BIT_HIGH),  (BIT_HIGH), }, // Stop bit 1
    
      { /* 1:0  */ 0x8000|(BIT_LOW),   (BIT_LOW),  }, // Start bit
      { /* 1:1  */ 0x8000|(ETX_BIT_0), (ETX_BIT_0) },
      { /* 1:2  */ 0x8000|(ETX_BIT_1), (ETX_BIT_1) },
      { /* 1:3  */ 0x8000|(ETX_BIT_2), (ETX_BIT_2) },
      { /* 1:4  */ 0x8000|(ETX_BIT_3), (ETX_BIT_3) },
      { /* 1:5  */ 0x8000|(ETX_BIT_4), (ETX_BIT_4) },
      { /* 1:6  */ 0x8000|(ETX_BIT_5), (ETX_BIT_5) },
      { /* 1:7  */ 0x8000|(ETX_BIT_6), (ETX_BIT_6) },
      { /* 1:8  */ 0x8000|(ETX_BIT_7), (ETX_BIT_7) },
      { /* 1:9  */ 0x8000|(BIT_HIGH),  (BIT_HIGH), }, // Stop bit 1
    };
    #define NUM_PWM_ITERATIONS  ( sizeof(halfDuplexUartMsg)/sizeof(halfDuplexUartMsg[0].group_0) )
    #define NUM_PWM_TABLE_LINES ( sizeof(halfDuplexUartMsg)/sizeof(halfDuplexUartMsg[0]) )
    STATIC_ASSERT(sizeof(nrf_pwm_values_grouped_t) == sizeof(halfDuplexUartMsg[0]), "halfDuplexUart line size");
    STATIC_ASSERT((NUM_PWM_ITERATIONS) == 40, "iterations");
    
    void TestPWM_Uart(void)
    {
        // Start accurate HFCLK (XOSC)
        NRF_CLOCK->TASKS_HFCLKSTART = 1;
        while (NRF_CLOCK->EVENTS_HFCLKSTARTED == 0)
            ;
        NRF_CLOCK->EVENTS_HFCLKSTARTED = 0;
        // Configure PWM pins as outputs, drive low or high
        NRF_GPIO->DIRSET = (1 << PWM_NEG);
        NRF_GPIO->OUTSET = (1 << PWM_NEG);  // Normal, stays low outside message
        NRF_GPIO->DIRSET = (1 << PWM_POS);
        NRF_GPIO->OUTCLR = (1 << PWM_POS);  // Inverted, stays high outside message
        nrf_delay_ms(10);                   // Pause so can see initial condition on 'scope single=shot
        NRF_PWM0->PRESCALER = F_PRESCALER;
        NRF_PWM0->PSEL.OUT[0] = PWM_NEG;
        NRF_PWM0->PSEL.OUT[1] = 0x80000000; // Disconnected
        NRF_PWM0->PSEL.OUT[2] = PWM_POS;
        NRF_PWM0->PSEL.OUT[3] = 0x80000000; // Disconnected
        NRF_PWM0->MODE = (PWM_MODE_UPDOWN_Up << PWM_MODE_UPDOWN_Pos);
        NRF_PWM0->DECODER = (PWM_DECODER_LOAD_Grouped << PWM_DECODER_LOAD_Pos) | (PWM_DECODER_MODE_RefreshCount << PWM_DECODER_MODE_Pos);
        NRF_PWM0->LOOP = 0;
        NRF_PWM0->COUNTERTOP = COUNTER_TOP;
        NRF_PWM0->SEQ[0].CNT = (NUM_PWM_ITERATIONS) << PWM_SEQ_CNT_CNT_Pos;
        NRF_PWM0->SEQ[0].PTR = (uint32_t)halfDuplexUartMsg;
        NRF_PWM0->SEQ[0].ENDDELAY = 0;
        NRF_PWM0->SEQ[0].REFRESH = 0;
        NRF_PWM0->SEQ[1].CNT = (NUM_PWM_ITERATIONS) << PWM_SEQ_CNT_CNT_Pos;
        NRF_PWM0->SEQ[1].PTR = (uint32_t)halfDuplexUartMsg;
        NRF_PWM0->SEQ[1].ENDDELAY = 0;
        NRF_PWM0->SEQ[1].REFRESH = 0;
        NRF_PWM0->SHORTS = NRF_PWM_SHORT_LOOPSDONE_STOP_MASK;
        NRF_PWM0->ENABLE = 1;
        // Stop after burst
        NRF_PWM0->SHORTS = NRF_PWM_SHORT_SEQEND0_STOP_MASK;
        NRF_PWM0->TASKS_SEQSTART[0] = 1;
        while (1)
        {
            __WFE();
        }
    }

    Second example provides an identical waveform on the 'scope, which is actually what you are looking for. It addresses a number of issues in the code you posted (I tested this code also, same 2-byte packet):

    // PWM Half-Duplex UART
    // ====================
    #include "nrf_pwm.h"
    
    #define LL_BAUDRATE      1200
    #define F_CLK        16000000              // nRF52832/nRF52840 fixed at 16MHz
    #define F_PRESCALER  (PWM_PRESCALER_PRESCALER_DIV_1)  // Divide by 1, 16MHz clock
    #define COUNTER_TOP  ((F_CLK+(LL_BAUDRATE/2)) / LL_BAUDRATE)
    STATIC_ASSERT(COUNTER_TOP < 32768, "COUNTER_TOP value too large for 15-bit register");
    STATIC_ASSERT(COUNTER_TOP >= 3, "COUNTER_TOP value too small for correct operation");
    #define BIT_LOW               0           // Normal encoding
    #define BIT_HIGH    (COUNTER_TOP)         // Normal encoding
    #define PWM_NEG (PIN_FEATHER_D6)          // Low level outside transmission
    #define PWM_POS (PIN_FEATHER_D9)          // High level outside transmission
    #define STX_BYTE 0x02 // Example start character: STX (^B)
    #define ETX_BYTE 0x03 // Example end character:   ETX (^C)
    
    // Example STX byte in little-endian format
    #define STX_BIT_0 (((STX_BYTE>>0) & 0x01) ? BIT_HIGH : BIT_LOW)
    #define STX_BIT_1 (((STX_BYTE>>1) & 0x01) ? BIT_HIGH : BIT_LOW)
    #define STX_BIT_2 (((STX_BYTE>>2) & 0x01) ? BIT_HIGH : BIT_LOW)
    #define STX_BIT_3 (((STX_BYTE>>3) & 0x01) ? BIT_HIGH : BIT_LOW)
    #define STX_BIT_4 (((STX_BYTE>>4) & 0x01) ? BIT_HIGH : BIT_LOW)
    #define STX_BIT_5 (((STX_BYTE>>5) & 0x01) ? BIT_HIGH : BIT_LOW)
    #define STX_BIT_6 (((STX_BYTE>>6) & 0x01) ? BIT_HIGH : BIT_LOW)
    #define STX_BIT_7 (((STX_BYTE>>7) & 0x01) ? BIT_HIGH : BIT_LOW)
    // Example ETX byte in little-endian format
    #define ETX_BIT_0 (((ETX_BYTE>>0) & 0x01) ? BIT_HIGH : BIT_LOW)
    #define ETX_BIT_1 (((ETX_BYTE>>1) & 0x01) ? BIT_HIGH : BIT_LOW)
    #define ETX_BIT_2 (((ETX_BYTE>>2) & 0x01) ? BIT_HIGH : BIT_LOW)
    #define ETX_BIT_3 (((ETX_BYTE>>3) & 0x01) ? BIT_HIGH : BIT_LOW)
    #define ETX_BIT_4 (((ETX_BYTE>>4) & 0x01) ? BIT_HIGH : BIT_LOW)
    #define ETX_BIT_5 (((ETX_BYTE>>5) & 0x01) ? BIT_HIGH : BIT_LOW)
    #define ETX_BIT_6 (((ETX_BYTE>>6) & 0x01) ? BIT_HIGH : BIT_LOW)
    #define ETX_BIT_7 (((ETX_BYTE>>7) & 0x01) ? BIT_HIGH : BIT_LOW)
    
    // Example using grouped, note uses pin channels 0 and 2 not 0 and 1
    nrf_pwm_values_grouped_t halfDuplexUartMsg[] = {
      //   Index   Normal pin 0        Inverted pin 2
      //   =====   =================== ==============
      { /* 0:0  */ 0x8000|(BIT_LOW),   (BIT_LOW),  }, // Start bit
      { /* 0:1  */ 0x8000|(STX_BIT_0), (STX_BIT_0) },
      { /* 0:2  */ 0x8000|(STX_BIT_1), (STX_BIT_1) },
      { /* 0:3  */ 0x8000|(STX_BIT_2), (STX_BIT_2) },
      { /* 0:4  */ 0x8000|(STX_BIT_3), (STX_BIT_3) },
      { /* 0:5  */ 0x8000|(STX_BIT_4), (STX_BIT_4) },
      { /* 0:6  */ 0x8000|(STX_BIT_5), (STX_BIT_5) },
      { /* 0:7  */ 0x8000|(STX_BIT_6), (STX_BIT_6) },
      { /* 0:8  */ 0x8000|(STX_BIT_7), (STX_BIT_7) },
      { /* 0:9  */ 0x8000|(BIT_HIGH),  (BIT_HIGH), }, // Stop bit 1
    
      { /* 1:0  */ 0x8000|(BIT_LOW),   (BIT_LOW),  }, // Start bit
      { /* 1:1  */ 0x8000|(ETX_BIT_0), (ETX_BIT_0) },
      { /* 1:2  */ 0x8000|(ETX_BIT_1), (ETX_BIT_1) },
      { /* 1:3  */ 0x8000|(ETX_BIT_2), (ETX_BIT_2) },
      { /* 1:4  */ 0x8000|(ETX_BIT_3), (ETX_BIT_3) },
      { /* 1:5  */ 0x8000|(ETX_BIT_4), (ETX_BIT_4) },
      { /* 1:6  */ 0x8000|(ETX_BIT_5), (ETX_BIT_5) },
      { /* 1:7  */ 0x8000|(ETX_BIT_6), (ETX_BIT_6) },
      { /* 1:8  */ 0x8000|(ETX_BIT_7), (ETX_BIT_7) },
      { /* 1:9  */ 0x8000|(BIT_HIGH),  (BIT_HIGH), }, // Stop bit 1
    };
    #define NUM_PWM_ITERATIONS  ( sizeof(halfDuplexUartMsg)/sizeof(halfDuplexUartMsg[0].group_0) )
    #define NUM_PWM_TABLE_LINES ( sizeof(halfDuplexUartMsg)/sizeof(halfDuplexUartMsg[0]) )
    STATIC_ASSERT(sizeof(nrf_pwm_values_grouped_t) == sizeof(halfDuplexUartMsg[0]), "halfDuplexUart line size");
    STATIC_ASSERT((NUM_PWM_ITERATIONS) == 40, "iterations");
    
    void TestPWM_Uart(void)
    {
        // Start accurate HFCLK (XOSC)
        NRF_CLOCK->TASKS_HFCLKSTART = 1;
        while (NRF_CLOCK->EVENTS_HFCLKSTARTED == 0)
            ;
        NRF_CLOCK->EVENTS_HFCLKSTARTED = 0;
        static nrfx_pwm_t m_pwm0 = NRFX_PWM_INSTANCE(0);
    
        uint32_t err_code;
        // Declare a configuration structure and use a macro to instantiate it with default parameters.
        nrfx_pwm_config_t pwm_config = NRFX_PWM_DEFAULT_CONFIG;
        //    .irq_priority = NRFX_PWM_DEFAULT_CONFIG_IRQ_PRIORITY,                  \
        //    .base_clock   = (nrf_pwm_clk_t)NRFX_PWM_DEFAULT_CONFIG_BASE_CLOCK,     \
        //    .count_mode   = (nrf_pwm_mode_t)NRFX_PWM_DEFAULT_CONFIG_COUNT_MODE,    \
        //    .top_value    = NRFX_PWM_DEFAULT_CONFIG_TOP_VALUE,                     \
        //    .load_mode    = (nrf_pwm_dec_load_t)NRFX_PWM_DEFAULT_CONFIG_LOAD_MODE, \
        //    .step_mode    = (nrf_pwm_dec_step_t)NRFX_PWM_DEFAULT_CONFIG_STEP_MODE
       static nrf_pwm_sequence_t pwm_sequence;
    
        // Override some of the default parameters:
        pwm_config.output_pins[0] = PWM_NEG | NRFX_PWM_PIN_INVERTED;
        pwm_config.output_pins[1] = NRFX_PWM_PIN_NOT_USED;
        pwm_config.output_pins[2] = PWM_POS;
        pwm_config.output_pins[3] = NRFX_PWM_PIN_NOT_USED;
        pwm_config.load_mode      = NRF_PWM_LOAD_GROUPED;   // 1 == 1st half word (16-bit) used in channels 0 and 1; 2nd word in channels 2 and 3
        pwm_config.base_clock     = F_PRESCALER;            // 0 == divide by 1 for 16MHz clock
        pwm_config.step_mode      = NRFX_PWM_DEFAULT_CONFIG_STEP_MODE; // 0 == Count Up
        pwm_config.top_value      = COUNTER_TOP;
    
        // Pass config structure into driver init() function 
        err_code = nrfx_pwm_init(&m_pwm0, &pwm_config, NULL);
        APP_ERROR_CHECK(err_code);
        pwm_sequence.values.p_grouped = (uint32_t)halfDuplexUartMsg;
        pwm_sequence.length           = NUM_PWM_ITERATIONS;
        pwm_sequence.repeats          = 0;
        nrfx_pwm_simple_playback(&m_pwm0, &pwm_sequence, 1, NRFX_PWM_FLAG_STOP);
        while (1)
        {
            __WFE();
        }
    }

    Note the second example relies on nrfx to set the initial pins but to get the default 'parking' high level before any transmission needs to take place usually the same port init code would be run at power-up (maybe not an issue ..)

    Edit: Max baud rate is 16MHz/3, or 5.33MHz, using the minimum allowed value of TOPCOUNT of 3; pretty nice. Finally worked out that by shrinking screen with ctrl-minus a refresh then shows all the Reply buttons

Related