This discussion has been locked.
You can no longer post new replies to this discussion. If you have a question you can start a new discussion

nRF52 Software Serial library similar to Arduino software serial

I have made a software serial library for nRF52840 similar to Arduino software serial. It is working fine. nRF52840 has two UARTE instance and one UART instance, As for my application requirement I need 3 UART or UARTE instances. 

Working baud rates: 1200, 2400, 4800, 9600, 14400, 19200, 28800, 31250, 38400, 56000, 57600, 76800,

Not working rates : 115200 

Count  of UART instances:  4. You can increase the number of instances by doing some minor modification in the software.

Test: I have only tested one SF_UART. It is working fine.

Can you help me to improve this library? I want to use 115200 baud rate, which is not working yet. 

All community members can use this library if they are lacking hardware UART. Please help me to improve it more. keep sharing more updates.

//#include "includes.h"
#include <stdint.h>
#include "gpio.h"
#include "sf_uart.h"

void (*sf_uart0_appRxEventHandler)(uint16_t rx);
void (*sf_uart1_appRxEventHandler)(uint16_t rx);
void (*sf_uart2_appRxEventHandler)(uint16_t rx);
void (*sf_uart3_appRxEventHandler)(uint16_t rx);


sf_uart_config sf_uart0_config =
{
    .rxPin = SF_RX0_PIN_NUMBER,
    .txPin = SF_TX0_PIN_NUMBER,
    .baudrate = 9600,
    .delay =
        {
            .rx_centering = 0,
            .rx_intrabit = 0,
            .rx_stopbit = 0,
            .tx = 0,
        }
};

sf_uart_config sf_uart1_config =
{
    .rxPin = SF_RX1_PIN_NUMBER,
    .txPin = SF_TX1_PIN_NUMBER,
    .baudrate = 9600,
    .delay =
        {
            .rx_centering = 0,
            .rx_intrabit = 0,
            .rx_stopbit = 0,
            .tx = 0,
        }
};

sf_uart_config sf_uart2_config =
{
    .rxPin = SF_RX2_PIN_NUMBER,
    .txPin = SF_TX2_PIN_NUMBER,
    .baudrate = 9600,
    .delay =
        {
            .rx_centering = 0,
            .rx_intrabit = 0,
            .rx_stopbit = 0,
            .tx = 0,
        }
};

sf_uart_config sf_uart3_config =
{
    .rxPin = SF_RX3_PIN_NUMBER,
    .txPin = SF_TX3_PIN_NUMBER,
    .baudrate = 9600,
    .delay =
        {
            .rx_centering = 0,
            .rx_intrabit = 0,
            .rx_stopbit = 0,
            .tx = 0,
        }
};

static sf_uart_config *configs[] =
{
    &sf_uart0_config, &sf_uart1_config,
    &sf_uart2_config, &sf_uart3_config,
};

void sf_uart0_drvRxEventHandler()
{
    uint8_t rx = 0;
    rx = sf_uart_drv_recv(configs[APP_SF_UART0]);
    sf_uart0_appRxEventHandler(rx);
}

void sf_uart1_drvRxEventHandler()
{
    uint8_t rx = 0;
    rx = sf_uart_drv_recv(configs[APP_SF_UART1]);
    sf_uart0_appRxEventHandler(rx);
}

void sf_uart2_drvRxEventHandler()
{
    uint8_t rx = 0;
    rx = sf_uart_drv_recv(configs[APP_SF_UART2]);
    sf_uart0_appRxEventHandler(rx);
}

void sf_uart3_drvRxEventHandler()
{
    uint8_t rx = 0;
    rx = sf_uart_drv_recv(configs[APP_SF_UART3]);
    sf_uart0_appRxEventHandler(rx);
}

static sf_uart_rxEventHandler sf_uart_drvRxEventHandlers[] =
{
    &sf_uart0_drvRxEventHandler, &sf_uart1_drvRxEventHandler,
    &sf_uart2_drvRxEventHandler, &sf_uart3_drvRxEventHandler,
};

static EventHandler *sf_uart_appRxEventHandlers[] =
{
    &sf_uart0_appRxEventHandler, &sf_uart1_appRxEventHandler,
    &sf_uart2_appRxEventHandler, &sf_uart3_appRxEventHandler,
};

void delay_loop(uint32_t cycles)
{
  volatile uint32_t cycle = cycles;
  for(; cycle>0;cycle--)
  {
      __NOP();
  }
}


uint16_t subtract_cap(uint16_t num, uint16_t sub) 
{
  volatile uint16_t diff = 0;
  if (num > sub)
  {
    diff = num - sub; //- 6;
    return diff;
  }
  else
    return 1;
}


pin_status sf_uart_drv_init(sf_uart_config *config, sf_uart_rxEventHandler event_handler)
{
    // Precalculate the various delays, in number of 6-cycle delays
    uint16_t bit_delay = (F_XTAL / config->baudrate)/6;

    // 12 (gcc 4.8.2) or 13 (gcc 4.3.2) cycles from start bit to first bit,
    // 15 (gcc 4.8.2) or 16 (gcc 4.3.2) cycles between bits,
    // 12 (gcc 4.8.2) or 14 (gcc 4.3.2) cycles from last bit to stop bit
    // These are all close enough to just use 15 cycles, since the inter-bit
    // timings are the most critical (deviations stack 8 times)
    config->delay.tx = subtract_cap(bit_delay, 15/6);
    // Timings counted from gcc 4.8.2 output. This works up to 115200 on
    // 16Mhz and 57600 on 8Mhz.
    //
    // When the start bit occurs, there are 3 or 4 cycles before the
    // interrupt flag is set, 4 cycles before the PC is set to the right
    // interrupt vector address and the old PC is pushed on the stack,
    // and then 75 cycles of instructions (including the RJMP in the
    // ISR vector table) until the first delay. After the delay, there
    // are 17 more cycles until the pin value is read (excluding the
    // delay in the loop).
    // We want to have a total delay of 1.5 bit time. Inside the loop,
    // we already wait for 1 bit time - 23 cycles, so here we wait for
    // 0.5 bit time - (71 + 18 - 22) cycles.
    config->delay.rx_centering = subtract_cap(bit_delay / 2, (4 + 4 + 75 + 17 - 23)/6) - 7;//corrections for nRF;

    // There are 23 cycles in each loop iteration (excluding the delay)
    config->delay.rx_intrabit = subtract_cap(bit_delay, 23/6) - 6;//corrections for nRF;

    // There are 37 cycles from the last bit read to the start of
    // stopbit delay and 11 cycles from the delay until the interrupt
    // mask is enabled again (which _must_ happen during the stopbit).
    // This delay aims at 3/4 of a bit time, meaning the end of the
    // delay will be at 1/4th of the stopbit. This allows some extra
    // time for ISR cleanup, which makes 115200 baud at 16Mhz work more
    // reliably
    config->delay.rx_stopbit = subtract_cap(bit_delay*3/4, (37 + 11)/6) - 15;//corrections for nRF;
    
    pin_status p_status = PIN_VALID;
    p_status = gpiote_pinMode(config->rxPin, INPUT, GPIOTE_CONFIG_POLARITY_HiToLo, event_handler);
    while(p_status != PIN_VALID);
    p_status = gpio_pinMode(config->txPin, OUTPUT);
    while(p_status != PIN_VALID);
    gpio_write(config->txPin, HIGH);
}


bool sf_uart_drv_write(sf_uart_config *config, uint8_t x)
{
    if (config->delay.tx == 0) 
    {
        return 0;
    }
  
    //Write the start bit
    gpio_write(config->txPin, LOW);
    delay_loop(config->delay.tx);

    // Write each of the 8 bits
    for (uint8_t i = 8; i > 0; --i)
    {
      if (x & 1) // choose bit
        gpio_write(config->txPin, HIGH);
      else
        gpio_write(config->txPin, LOW);

      delay_loop(config->delay.tx);
      x >>= 1;
    }

    //restore pin to natural state
    gpio_write(config->txPin, HIGH);
    delay_loop(config->delay.tx);

    return 1;
}

uint8_t sf_uart_drv_recv(sf_uart_config *config)
{
  uint8_t d = 0;

  // If RX line is high, then we don't see any start bit
  // so interrupt is probably not for us
  if (!gpio_read(config->rxPin))
  {
    // Disable further interrupts during reception, this prevents
    // triggering another interrupt directly after we return, which can
    // cause problems at higher baudrates.
    gpiote_eventDisable(config->rxPin);
        
    // Wait approximately 1/2 of a bit width to "center" the sample
    delay_loop(config->delay.rx_centering);

    // Read each of the 8 bits
    for(uint8_t i=8; i > 0; --i)
    {
      delay_loop(config->delay.rx_intrabit);       
      d >>= 1;
      if (gpio_read(config->rxPin))
        d |= 0x80;
    }
  
    // skip the stop bit
    delay_loop(config->delay.rx_stopbit);

    // Re-enable interrupts when we're sure to be inside the stop bit
    gpiote_eventEnable(config->rxPin);
    return d;
  }
}

void sf_uart_init(int uartNumb, sf_uart_baud_rate baud, EventHandler rx_handler)
{
    configs[uartNumb]->baudrate = baud;
    sf_uart_drv_init(configs[uartNumb], sf_uart_drvRxEventHandlers[uartNumb]);
    *sf_uart_appRxEventHandlers[uartNumb] = rx_handler;
}

void sf_uart_write(int uartNumb,  uint8_t x)
{
   sf_uart_drv_write(configs[uartNumb], x);
}

void sf_uart_writeStr(int uartNumb, char* str)
{
    uint16_t i = 0;
    while (str[i] != NULL)
    {
        sf_uart_write(uartNumb, str[i]);
        i++;
    }
}

void sf_uart_writeBuf(int uartNumb, uint8_t *data, int size)
{
    for(int i = 0; i < size; i++ )
    {
        sf_uart_write(uartNumb, *data++);
    }
}

void sf_uart_rxEnable(int uartNumb)
{
    gpiote_eventEnable(configs[uartNumb]->rxPin);
}

void sf_uart_rxDisable(int uartNumb)
{
    gpiote_eventDisable(configs[uartNumb]->rxPin);
}
sf_uart.h
//#include "includes.h"
#include <stdint.h>
#include "nrf_drv_gpiote.h"
#include "gpio.h"

void (*gpiote0_appInEventHandler)();
void (*gpiote1_appInEventHandler)();
void (*gpiote2_appInEventHandler)();
void (*gpiote3_appInEventHandler)();
void (*gpiote4_appInEventHandler)();
void (*gpiote5_appInEventHandler)();
void (*gpiote6_appInEventHandler)();
void (*gpiote7_appInEventHandler)();
void (*gpiote8_appInEventHandler)();
void (*gpiote9_appInEventHandler)();
void (*gpiote10_appInEventHandler)();
void (*gpiote11_appInEventHandler)();
void (*gpiote12_appInEventHandler)();
void (*gpiote13_appInEventHandler)();
void (*gpiote14_appInEventHandler)();
void (*gpiote15_appInEventHandler)();
void (*gpiote16_appInEventHandler)();
void (*gpiote17_appInEventHandler)();
void (*gpiote18_appInEventHandler)();
void (*gpiote19_appInEventHandler)();
void (*gpiote20_appInEventHandler)();
void (*gpiote21_appInEventHandler)();
void (*gpiote22_appInEventHandler)();
void (*gpiote23_appInEventHandler)();
void (*gpiote24_appInEventHandler)();
void (*gpiote25_appInEventHandler)();
void (*gpiote26_appInEventHandler)();
void (*gpiote27_appInEventHandler)();
void (*gpiote28_appInEventHandler)();
void (*gpiote29_appInEventHandler)();
void (*gpiote30_appInEventHandler)();
void (*gpiote31_appInEventHandler)();
void (*gpiote32_appInEventHandler)();
void (*gpiote33_appInEventHandler)();
void (*gpiote34_appInEventHandler)();
void (*gpiote35_appInEventHandler)();
void (*gpiote36_appInEventHandler)();
void (*gpiote37_appInEventHandler)();
void (*gpiote38_appInEventHandler)();
void (*gpiote39_appInEventHandler)();
void (*gpiote40_appInEventHandler)();
void (*gpiote41_appInEventHandler)();
void (*gpiote42_appInEventHandler)();
void (*gpiote43_appInEventHandler)();
void (*gpiote44_appInEventHandler)();
void (*gpiote45_appInEventHandler)();
void (*gpiote46_appInEventHandler)();
void (*gpiote47_appInEventHandler)();

void (**gpiote_appInEventHandler[])(void) = 
{
    &gpiote0_appInEventHandler,
    &gpiote1_appInEventHandler,
    &gpiote2_appInEventHandler,
    &gpiote3_appInEventHandler,
    &gpiote4_appInEventHandler,
    &gpiote5_appInEventHandler,
    &gpiote6_appInEventHandler,
    &gpiote7_appInEventHandler,
    &gpiote8_appInEventHandler,
    &gpiote9_appInEventHandler,
    &gpiote10_appInEventHandler,
    &gpiote11_appInEventHandler,
    &gpiote12_appInEventHandler,
    &gpiote13_appInEventHandler,
    &gpiote14_appInEventHandler,
    &gpiote15_appInEventHandler,
    &gpiote16_appInEventHandler,
    &gpiote17_appInEventHandler,
    &gpiote18_appInEventHandler,
    &gpiote19_appInEventHandler,
    &gpiote20_appInEventHandler,
    &gpiote21_appInEventHandler,
    &gpiote22_appInEventHandler,
    &gpiote23_appInEventHandler,
    &gpiote24_appInEventHandler,
    &gpiote25_appInEventHandler,
    &gpiote26_appInEventHandler,
    &gpiote27_appInEventHandler,
    &gpiote28_appInEventHandler,
    &gpiote29_appInEventHandler,
    &gpiote30_appInEventHandler,
    &gpiote31_appInEventHandler,
    &gpiote32_appInEventHandler,
    &gpiote33_appInEventHandler,
    &gpiote34_appInEventHandler,
    &gpiote35_appInEventHandler,
    &gpiote36_appInEventHandler,
    &gpiote37_appInEventHandler,
    &gpiote38_appInEventHandler,
    &gpiote39_appInEventHandler,
    &gpiote40_appInEventHandler,
    &gpiote41_appInEventHandler,
    &gpiote42_appInEventHandler,
    &gpiote43_appInEventHandler,
    &gpiote44_appInEventHandler,
    &gpiote45_appInEventHandler,
    &gpiote46_appInEventHandler,
    &gpiote47_appInEventHandler,
};

pin_status pins_status[64] =
{
    PIN_VALID,     PIN_VALID,     PIN_VALID,     PIN_VALID,    
    PIN_VALID,     PIN_VALID,     PIN_VALID,     PIN_VALID, 
    PIN_VALID,     PIN_VALID,     PIN_VALID,     PIN_VALID, 
    PIN_VALID,     PIN_VALID,     PIN_VALID,     PIN_VALID,    
    PIN_VALID,     PIN_VALID,     PIN_VALID,     PIN_VALID, 
    PIN_VALID,     PIN_VALID,     PIN_VALID,     PIN_VALID,
    PIN_VALID,     PIN_VALID,     PIN_VALID,     PIN_VALID,    
    PIN_VALID,     PIN_VALID,     PIN_VALID,     PIN_VALID,    
    PIN_VALID,     PIN_VALID,     PIN_VALID,     PIN_VALID, 
    PIN_VALID,     PIN_VALID,     PIN_VALID,     PIN_VALID,
    PIN_VALID,     PIN_VALID,     PIN_VALID,     PIN_VALID, 
    PIN_VALID,     PIN_VALID,     PIN_VALID,     PIN_VALID,
    PIN_NOT_VALID, PIN_NOT_VALID, PIN_NOT_VALID, PIN_NOT_VALID, 
    PIN_NOT_VALID, PIN_NOT_VALID, PIN_NOT_VALID, PIN_NOT_VALID,
    PIN_NOT_VALID, PIN_NOT_VALID, PIN_NOT_VALID, PIN_NOT_VALID, 
    PIN_NOT_VALID, PIN_NOT_VALID, PIN_NOT_VALID, PIN_NOT_VALID,
};

void gpiote_drvInEventhandler(nrf_drv_gpiote_pin_t pinNumber, nrf_gpiote_polarity_t action)
{
    //**(gpiote_appInEventHandler[pinNumber]);
    (*gpiote_appInEventHandler[pinNumber])();
}

void gpiote_init(void)
{
    ret_code_t err_code;

    err_code = nrf_drv_gpiote_init();
    APP_ERROR_CHECK(err_code);
}

/*  valid values of senseTrnsiton:
    GPIOTE_CONFIG_POLARITY_LoToHi,       ///<  Low to high.
    GPIOTE_CONFIG_POLARITY_HiToLo,       ///<  High to low.
    GPIOTE_CONFIG_POLARITY_Toggle        ///<  Toggle.
 */
pin_status gpiote_pinMode(uint32_t pinNumber, uint8_t state,
                      nrf_gpiote_polarity_t senseTrnsiton, 
                      void (*actionEvent)(void))
{
    ret_code_t err_code;

    if(pins_status[pinNumber] == PIN_VALID)
    {
        pins_status[pinNumber] = PIN_OCCUPIED;
    }
    else if(pins_status[pinNumber] == PIN_NOT_VALID)
    {
        return PIN_NOT_VALID;
    }
    else
    {
        return PIN_OCCUPIED;
    }

    if(state == OUTPUT)
    {
        nrf_drv_gpiote_out_config_t gpiote_outConfig = 
        {
            .action     = NRF_GPIOTE_POLARITY_LOTOHI,
            .init_state = NRF_GPIOTE_INITIAL_VALUE_LOW,               \
            .task_pin   = false,                   
        };
        err_code = nrf_drv_gpiote_out_init(pinNumber, &gpiote_outConfig);
        APP_ERROR_CHECK(err_code);
    }
    else if(state == INPUT)
    {
        nrf_drv_gpiote_in_config_t gpiote_inConfig =
        {
            .sense           = senseTrnsiton,                         \
            .pull            = GPIO_PIN_CNF_PULL_Pullup,              \
            .is_watcher      = false,                                 \
            .hi_accuracy     = true,                                  \
            .skip_gpio_setup = false,                                 \
        };
        err_code = nrf_drv_gpiote_in_init(pinNumber, &gpiote_inConfig, gpiote_drvInEventhandler);
        *gpiote_appInEventHandler[pinNumber] = actionEvent;
        APP_ERROR_CHECK(err_code);
        nrf_drv_gpiote_in_event_enable(pinNumber, true);
    }
    return PIN_VALID;
}

void gpiote_eventEnable(uint32_t pinNumber)
{
    nrf_drv_gpiote_in_event_enable(pinNumber, true);
}

void gpiote_eventDisable(uint32_t pinNumber)
{
    nrf_drv_gpiote_in_event_disable(pinNumber);
}

pin_status gpio_pinMode(uint32_t pinNumber, uint8_t state)
{
    if(pins_status[pinNumber] == PIN_VALID)
    {
        pins_status[pinNumber] = PIN_OCCUPIED;
    }
    else if(pins_status[pinNumber] == PIN_NOT_VALID)
    {
        return PIN_NOT_VALID;
    }
    else
    {
        return PIN_OCCUPIED;
    }

    if(state == OUTPUT)
    {
        nrf_gpio_cfg_output(pinNumber);
    }
    else if(state == INPUT)
    {
        nrf_gpio_cfg_input(pinNumber, GPIO_PIN_CNF_PULL_Pullup);
    }
    return PIN_VALID;
}

void gpio_write(uint32_t pinNumber, uint8_t value)
{
    nrf_gpio_pin_write(pinNumber, value);
}

bool gpio_read(uint32_t pinNumber)
{
    return nrf_gpio_pin_read(pinNumber);
}


gpio.h
#include <stdint.h>
#include <string.h>
#include "nrf_delay.h"
#include "sf_uart.h"


char rxBuffer[100] = {0};
uint8_t rxCount = 0;
void event_handler(uint16_t rx)
{
    rxBuffer[rxCount++] = rx;
    if(rxCount == 100)
    {
      rxCount = 0;
      memset(rxBuffer, 0, 100);
    }
}

int main()
{
    char str[27] =  "abcdefghijklmnopqrstuvwxyz";
    gpiote_init(); 
    sf_uart_init(APP_SF_UART0, APP_SF_UART_BAUDRATE_9600, event_handler);
    while(1)
    {
       sf_uart_writeStr(APP_SF_UART0, str);
       nrf_delay_ms(100);
    }
}

  • Hi,

    What is not working with 115200 baudrate? I tried your code, but I got hardfault in gpiote_drvInEventhandler(), is there some code missing?

    Handling 115200 baudrate in software will be quite work-intensive for the CPU to handle all the interrupts. You may need to consider writing your own GPIOTE interrupt handler for this specific purpose, to reduce the overhead of the generic checks that is performed in the GPIOTE driver. An optimized interrupt handler that is doing only what is required, may help resolve your issues.

    Note that SW/bit-banging UART will not work well together with BLE or other high-priority interrupt tasks, as this will block you from keeping the timing requirements of the UART reading/writing.

    Best regards,
    Jørgen

  • Used your exact code/files, connected P1.10 to P1.11. It HardFaults at gpio.c:130.

    Built using SES for pca10056/nRF52840 target.

  • Okay. Jorgen, it will not work with this connection. Please use hardware UART or UARTE with it for testing. 

  • Ok, seems it is working when connected with the J-Link VCOM on the DK at 9600 baudrate.

    Looks like the optimization level affects the timing enough to break it at higher baudrate levels though. 57600/76800 is unstable or not working correctly with optimization set to anything below level 3 during my testing. Once I was actually able to receive data correctly with application baudrate set to 76800 and terminal set to 57600.

    115200 baudrate is also working for TX with optimization level set to 3 on my end, but RX is not working correctly. However, it seems is just 1 bit off the expected, so you may have to check if there is some way you can resolve it:

    Expected:"H": 0x48 -> 0xC8, "E": 0x45 -> 0xC5, "L": 0x4C -> 0xCC, "O": 0x4F -> 0xCF, "h": 0x68 -> 0xE8, "e": 0x65 -> 0xE5, "l": 0x6C -> 0xEC, "o": 0x6F -> 0xEF.

Related