diff --git a/targets/TARGET_NORDIC/TARGET_NRF5x/PeripheralPinsDefault.c b/targets/TARGET_NORDIC/TARGET_NRF5x/PeripheralPinsDefault.c index 6336c47617c..fcd47977975 100644 --- a/targets/TARGET_NORDIC/TARGET_NRF5x/PeripheralPinsDefault.c +++ b/targets/TARGET_NORDIC/TARGET_NRF5x/PeripheralPinsDefault.c @@ -21,19 +21,26 @@ * Can be overwritten by user. */ MBED_WEAK const PinMapI2C PinMap_I2C[1] = { - {NC, NC, NC} + { NC, NC, NC } }; /* Default mapping between SPI pins and SPI instance. * Can be overwritten by user. */ MBED_WEAK const PinMapSPI PinMap_SPI[1] = { - {NC, NC, NC, NC} + { NC, NC, NC, NC } }; /* Default mapping between PWM pins and PWM instance. * Can be overwritten by user. */ MBED_WEAK const PinMapPWM PinMap_PWM[1] = { - {NC, NC} + { NC, NC } +}; + +/* Default mapping between UART pins and UART instance. + * Can be overwritten by user. + */ +MBED_WEAK const PinMapUART PinMap_UART[1] = { + { NC, NC, NC } }; diff --git a/targets/TARGET_NORDIC/TARGET_NRF5x/README.md b/targets/TARGET_NORDIC/TARGET_NRF5x/README.md index 7e0de3d51e4..1eba09ca18f 100644 --- a/targets/TARGET_NORDIC/TARGET_NRF5x/README.md +++ b/targets/TARGET_NORDIC/TARGET_NRF5x/README.md @@ -40,3 +40,65 @@ The tables must be placed in a C compilation file. 1. When called from the same thread, it is safe to assign I2C and SPI objects to the same instance. 1. If an instance is being used exclusively for either I2C or SPI, the objects can safely be called from multiple threads. 1. If an instance is being used for both I2C and SPI, the user must provide thread safety between the objects. + + +### Serial + +The serial implementation uses the UARTE module which works exclusively through EasyDMA and RAM buffers. For optimal performance, each configured instance (NRF52832 has 1, NRF52840 has 2) has three buffers statically assigned: + +1. Rx DMA buffer, which EasyDMA is currently writing to. +1. Rx DMA buffer, pre-loaded in EasyDMA for automatic switchover. +1. Rx FIFO buffer, for serving data to the application. + +When the first DMA buffer is full or flushed the interrupt handler will automatically copy the DMA buffer to the FIFO buffer. This happens in interrupt context to avoid data loss and with UARTE interrupts set at the highest priority. The FIFO buffer is backed by the Nordic atomic fifo, which can be read and written to safely without disabling interrupts. + +#### Customization + +All buffers can be resized to fit the application: + +``` + "name": "nordic", + "config": { + "uart-dma-size": { + "help": "UART DMA buffer. 2 buffers per instance. DMA buffer is filled by UARTE", + "value": 8 + }, + "uart-0-fifo-size": { + "help": "UART0 FIFO buffer. FIFO buffer is filled from DMA buffer.", + "value": 32 + }, + "uart-1-fifo-size": { + "help": "UART1 FIFO buffer. FIFO buffer is filled from DMA buffer.", + "value": 32 + } + } +``` + +All DMA buffers are the same size and must be at least 5 bytes due to hardware restrictions. DMA buffers should be sized to handle the worst expected interrupt latency. FIFO buffers can be configured per instance and the size should reflect the largest expected burst data. For example, a serial debug port might receive a line of data at a time, so an 80 byte FIFO buffer would be adequate. A serial port connected to a wifi radio should have a FIFO buffer in the kilo byte range. + +For the NRF52840, UARTE instances are assigned based on pins and calling order. Serial objects with the same pin configurations will go to the same instance. A custom configuration table can be provided by overriding the weakly defined default empty table. In the example below, serial objects using pins `p1` and `p2` for `Tx` and `Rx` will always be assigned to `Instance 1` and serial objects using pins `p3` and `p4` for `Tx` and `Rx` will be assigned to `Instance 0` regardless of calling order. The custom configuration table must always be terminated with a row of `NC`. + +``` +const PinMapI2C PinMap_UART[] = { + {p1, p2, 1}, + {p3, p4, 0}, + {NC, NC, NC} +}; +``` + +The table must be placed in a C compilation file. + + +#### RTC2 + +Because each DMA buffer must be at least 5 bytes deep, each buffer is automatically flushed after a certain idle period to ensure low latency and correctness. This idle timeout is implemented using 2 of the 4 channels on RTC instance 2. This leaves RTC0 for the SoftDevice and RTC1 for Mbed tickers. + +The RTC2 ISR is set at the lowest interrupt priority to ensure that UARTE interrupts take precedence. The last 2 of the 4 RTC channels are used for decoupling UARTE ISR context from Mbed IRQ events. This ensures that any user code will only delay other user callbacks and idle flushing and puts an upper bound on the interrupt handling time for the UARTE ISR. + +#### Limitations + + * The UARTE hardware only supports 8-bit, None/Even parity, and 1 stop bit. + * The asynchronous read and write implementation currently only support 255 byte transfers. + * The EasyDMA hardware can only read from RAM, which means all Tx buffers must reside in RAM. If a Tx buffer residing in flash is passed to the asynchronous write function, the function will try to copy the Tx buffer to a temporary internal buffer and transmit the data from there. + * It is not possible to do an asynchronous write from flash and receive non-asynchronously at the same time since the non-asynchronous receive buffer is being used as the temporary transmission buffer. + diff --git a/targets/TARGET_NORDIC/TARGET_NRF5x/TARGET_NRF52/TARGET_MCU_NRF52832/PeripheralNames.h b/targets/TARGET_NORDIC/TARGET_NRF5x/TARGET_NRF52/TARGET_MCU_NRF52832/PeripheralNames.h index 0fe6da592e8..7e9274d7c7b 100644 --- a/targets/TARGET_NORDIC/TARGET_NRF5x/TARGET_NRF52/TARGET_MCU_NRF52832/PeripheralNames.h +++ b/targets/TARGET_NORDIC/TARGET_NRF5x/TARGET_NRF52/TARGET_MCU_NRF52832/PeripheralNames.h @@ -45,10 +45,6 @@ extern "C" { #endif -#define STDIO_UART_TX TX_PIN_NUMBER -#define STDIO_UART_RX RX_PIN_NUMBER -#define STDIO_UART UART_0 - typedef enum { UART_0 = (int)NRF_UART0_BASE } UARTName; diff --git a/targets/TARGET_NORDIC/TARGET_NRF5x/TARGET_NRF52/TARGET_MCU_NRF52832/TARGET_DELTA_DFBM_NQ620/PinNames.h b/targets/TARGET_NORDIC/TARGET_NRF5x/TARGET_NRF52/TARGET_MCU_NRF52832/TARGET_DELTA_DFBM_NQ620/PinNames.h index c9cb9317078..18d89777bf7 100644 --- a/targets/TARGET_NORDIC/TARGET_NRF5x/TARGET_NRF52/TARGET_MCU_NRF52832/TARGET_DELTA_DFBM_NQ620/PinNames.h +++ b/targets/TARGET_NORDIC/TARGET_NRF5x/TARGET_NRF52/TARGET_MCU_NRF52832/TARGET_DELTA_DFBM_NQ620/PinNames.h @@ -139,6 +139,8 @@ typedef enum { // mBed interface Pins USBTX = TX_PIN_NUMBER, USBRX = RX_PIN_NUMBER, + STDIO_UART_TX = TX_PIN_NUMBER, + STDIO_UART_RX = RX_PIN_NUMBER, SPI_PSELMOSI0 = p23, SPI_PSELMISO0 = p24, diff --git a/targets/TARGET_NORDIC/TARGET_NRF5x/TARGET_NRF52/TARGET_MCU_NRF52832/TARGET_MTB_UBLOX_NINA_B1/PinNames.h b/targets/TARGET_NORDIC/TARGET_NRF5x/TARGET_NRF52/TARGET_MCU_NRF52832/TARGET_MTB_UBLOX_NINA_B1/PinNames.h index 0c99221c3ed..87b07172c7e 100644 --- a/targets/TARGET_NORDIC/TARGET_NRF5x/TARGET_NRF52/TARGET_MCU_NRF52832/TARGET_MTB_UBLOX_NINA_B1/PinNames.h +++ b/targets/TARGET_NORDIC/TARGET_NRF5x/TARGET_NRF52/TARGET_MCU_NRF52832/TARGET_MTB_UBLOX_NINA_B1/PinNames.h @@ -119,6 +119,8 @@ typedef enum { TX_PIN_NUMBER = p6, CTS_PIN_NUMBER = p7, RTS_PIN_NUMBER = p31, + STDIO_UART_TX = TX_PIN_NUMBER, + STDIO_UART_RX = RX_PIN_NUMBER, I2C_SDA = p2, I2C_SCL = p3, diff --git a/targets/TARGET_NORDIC/TARGET_NRF5x/TARGET_NRF52/TARGET_MCU_NRF52832/TARGET_NRF52_DK/PinNames.h b/targets/TARGET_NORDIC/TARGET_NRF5x/TARGET_NRF52/TARGET_MCU_NRF52832/TARGET_NRF52_DK/PinNames.h index 2b86eca4857..b5bb17f8ac5 100644 --- a/targets/TARGET_NORDIC/TARGET_NRF5x/TARGET_NRF52/TARGET_MCU_NRF52832/TARGET_NRF52_DK/PinNames.h +++ b/targets/TARGET_NORDIC/TARGET_NRF5x/TARGET_NRF52/TARGET_MCU_NRF52832/TARGET_NRF52_DK/PinNames.h @@ -140,6 +140,8 @@ typedef enum { // mBed interface Pins USBTX = TX_PIN_NUMBER, USBRX = RX_PIN_NUMBER, + STDIO_UART_TX = TX_PIN_NUMBER, + STDIO_UART_RX = RX_PIN_NUMBER, SPI_PSELMOSI0 = p23, SPI_PSELMISO0 = p24, diff --git a/targets/TARGET_NORDIC/TARGET_NRF5x/TARGET_NRF52/TARGET_MCU_NRF52832/TARGET_RBLAB_BLENANO2/PinNames.h b/targets/TARGET_NORDIC/TARGET_NRF5x/TARGET_NRF52/TARGET_MCU_NRF52832/TARGET_RBLAB_BLENANO2/PinNames.h index 0798e678c1e..dd61f6f2068 100644 --- a/targets/TARGET_NORDIC/TARGET_NRF5x/TARGET_NRF52/TARGET_MCU_NRF52832/TARGET_RBLAB_BLENANO2/PinNames.h +++ b/targets/TARGET_NORDIC/TARGET_NRF5x/TARGET_NRF52/TARGET_MCU_NRF52832/TARGET_RBLAB_BLENANO2/PinNames.h @@ -130,6 +130,8 @@ typedef enum { TX_PIN_NUMBER = p29, CTS_PIN_NUMBER = p28, RTS_PIN_NUMBER = p2, + STDIO_UART_TX = TX_PIN_NUMBER, + STDIO_UART_RX = RX_PIN_NUMBER, // mBed interface Pins USBTX = TX_PIN_NUMBER, diff --git a/targets/TARGET_NORDIC/TARGET_NRF5x/TARGET_NRF52/TARGET_MCU_NRF52832/TARGET_UBLOX_EVA_NINA/PinNames.h b/targets/TARGET_NORDIC/TARGET_NRF5x/TARGET_NRF52/TARGET_MCU_NRF52832/TARGET_UBLOX_EVA_NINA/PinNames.h index e29473888fc..cfb9917ac4f 100644 --- a/targets/TARGET_NORDIC/TARGET_NRF5x/TARGET_NRF52/TARGET_MCU_NRF52832/TARGET_UBLOX_EVA_NINA/PinNames.h +++ b/targets/TARGET_NORDIC/TARGET_NRF5x/TARGET_NRF52/TARGET_MCU_NRF52832/TARGET_UBLOX_EVA_NINA/PinNames.h @@ -84,6 +84,9 @@ typedef enum { TX_PIN_NUMBER = p6, CTS_PIN_NUMBER = p7, RTS_PIN_NUMBER = p31, + STDIO_UART_TX = TX_PIN_NUMBER, + STDIO_UART_RX = RX_PIN_NUMBER, + I2C_SDA0 = p2, I2C_SCL0 = p3, diff --git a/targets/TARGET_NORDIC/TARGET_NRF5x/TARGET_NRF52/TARGET_MCU_NRF52832/TARGET_UBLOX_EVK_NINA_B1/PinNames.h b/targets/TARGET_NORDIC/TARGET_NRF5x/TARGET_NRF52/TARGET_MCU_NRF52832/TARGET_UBLOX_EVK_NINA_B1/PinNames.h index 7268459eb28..cee9e689dac 100644 --- a/targets/TARGET_NORDIC/TARGET_NRF5x/TARGET_NRF52/TARGET_MCU_NRF52832/TARGET_UBLOX_EVK_NINA_B1/PinNames.h +++ b/targets/TARGET_NORDIC/TARGET_NRF5x/TARGET_NRF52/TARGET_MCU_NRF52832/TARGET_UBLOX_EVK_NINA_B1/PinNames.h @@ -107,6 +107,8 @@ typedef enum { TX_PIN_NUMBER = p6, CTS_PIN_NUMBER = p7, RTS_PIN_NUMBER = p31, + STDIO_UART_TX = TX_PIN_NUMBER, + STDIO_UART_RX = RX_PIN_NUMBER, I2C_SDA0 = p2, I2C_SCL0 = p3, // mBed interface pins diff --git a/targets/TARGET_NORDIC/TARGET_NRF5x/TARGET_NRF52/TARGET_MCU_NRF52832/TARGET_VBLUNO52/PinNames.h b/targets/TARGET_NORDIC/TARGET_NRF5x/TARGET_NRF52/TARGET_MCU_NRF52832/TARGET_VBLUNO52/PinNames.h index 401cd344a90..1b6ccc502bf 100644 --- a/targets/TARGET_NORDIC/TARGET_NRF5x/TARGET_NRF52/TARGET_MCU_NRF52832/TARGET_VBLUNO52/PinNames.h +++ b/targets/TARGET_NORDIC/TARGET_NRF5x/TARGET_NRF52/TARGET_MCU_NRF52832/TARGET_VBLUNO52/PinNames.h @@ -141,6 +141,8 @@ typedef enum { TX_PIN_NUMBER = p6, CTS_PIN_NUMBER = p7, //not on Header RTS_PIN_NUMBER = p5, //not on Header + STDIO_UART_TX = TX_PIN_NUMBER, + STDIO_UART_RX = RX_PIN_NUMBER, // mBed interface Pins USBTX = TX_PIN_NUMBER, diff --git a/targets/TARGET_NORDIC/TARGET_NRF5x/TARGET_NRF52/TARGET_MCU_NRF52832/config/sdk_config.h b/targets/TARGET_NORDIC/TARGET_NRF5x/TARGET_NRF52/TARGET_MCU_NRF52832/config/sdk_config.h index 5487f5a226a..89a3a962efe 100644 --- a/targets/TARGET_NORDIC/TARGET_NRF5x/TARGET_NRF52/TARGET_MCU_NRF52832/config/sdk_config.h +++ b/targets/TARGET_NORDIC/TARGET_NRF5x/TARGET_NRF52/TARGET_MCU_NRF52832/config/sdk_config.h @@ -3004,7 +3004,7 @@ // UART_ENABLED - nrf_drv_uart - UART/UARTE peripheral driver //========================================================== #ifndef UART_ENABLED -#define UART_ENABLED 0 +#define UART_ENABLED 1 #endif // UART_DEFAULT_CONFIG_HWFC - Hardware Flow Control @@ -3044,7 +3044,7 @@ // <268435456=> 1000000 baud #ifndef UART_DEFAULT_CONFIG_BAUDRATE -#define UART_DEFAULT_CONFIG_BAUDRATE 30801920 +#define UART_DEFAULT_CONFIG_BAUDRATE 2576384 #endif // UART_DEFAULT_CONFIG_IRQ_PRIORITY - Interrupt priority diff --git a/targets/TARGET_NORDIC/TARGET_NRF5x/TARGET_NRF52/TARGET_MCU_NRF52840/PeripheralNames.h b/targets/TARGET_NORDIC/TARGET_NRF5x/TARGET_NRF52/TARGET_MCU_NRF52840/PeripheralNames.h index 87d1983478f..bcfd647d1a1 100644 --- a/targets/TARGET_NORDIC/TARGET_NRF5x/TARGET_NRF52/TARGET_MCU_NRF52840/PeripheralNames.h +++ b/targets/TARGET_NORDIC/TARGET_NRF5x/TARGET_NRF52/TARGET_MCU_NRF52840/PeripheralNames.h @@ -45,10 +45,6 @@ extern "C" { #endif -#define STDIO_UART_TX TX_PIN_NUMBER -#define STDIO_UART_RX RX_PIN_NUMBER -#define STDIO_UART UART_0 - typedef enum { UART_0 = (int)NRF_UART0_BASE diff --git a/targets/TARGET_NORDIC/TARGET_NRF5x/TARGET_NRF52/TARGET_MCU_NRF52840/TARGET_NRF52840_DK/PinNames.h b/targets/TARGET_NORDIC/TARGET_NRF5x/TARGET_NRF52/TARGET_MCU_NRF52840/TARGET_NRF52840_DK/PinNames.h index 5d860570c07..a89be795974 100644 --- a/targets/TARGET_NORDIC/TARGET_NRF5x/TARGET_NRF52/TARGET_MCU_NRF52840/TARGET_NRF52840_DK/PinNames.h +++ b/targets/TARGET_NORDIC/TARGET_NRF5x/TARGET_NRF52/TARGET_MCU_NRF52840/TARGET_NRF52840_DK/PinNames.h @@ -178,6 +178,8 @@ typedef enum { // mBed interface Pins USBTX = TX_PIN_NUMBER, USBRX = RX_PIN_NUMBER, + STDIO_UART_TX = TX_PIN_NUMBER, + STDIO_UART_RX = RX_PIN_NUMBER, SPI_PSELMOSI0 = P1_13, SPI_PSELMISO0 = P1_14, diff --git a/targets/TARGET_NORDIC/TARGET_NRF5x/TARGET_NRF52/TARGET_MCU_NRF52840/config/sdk_config.h b/targets/TARGET_NORDIC/TARGET_NRF5x/TARGET_NRF52/TARGET_MCU_NRF52840/config/sdk_config.h index e2bbb51e6f1..393d9be522f 100644 --- a/targets/TARGET_NORDIC/TARGET_NRF5x/TARGET_NRF52/TARGET_MCU_NRF52840/config/sdk_config.h +++ b/targets/TARGET_NORDIC/TARGET_NRF5x/TARGET_NRF52/TARGET_MCU_NRF52840/config/sdk_config.h @@ -3004,7 +3004,7 @@ // UART_ENABLED - nrf_drv_uart - UART/UARTE peripheral driver //========================================================== #ifndef UART_ENABLED -#define UART_ENABLED 0 +#define UART_ENABLED 1 #endif // UART_DEFAULT_CONFIG_HWFC - Hardware Flow Control @@ -3044,7 +3044,7 @@ // <268435456=> 1000000 baud #ifndef UART_DEFAULT_CONFIG_BAUDRATE -#define UART_DEFAULT_CONFIG_BAUDRATE 30801920 +#define UART_DEFAULT_CONFIG_BAUDRATE 2576384 #endif // UART_DEFAULT_CONFIG_IRQ_PRIORITY - Interrupt priority @@ -3095,7 +3095,7 @@ // UART1_ENABLED - Enable UART1 instance //========================================================== #ifndef UART1_ENABLED -#define UART1_ENABLED 0 +#define UART1_ENABLED 1 #endif // UART1_CONFIG_USE_EASY_DMA - Default setting for using EasyDMA diff --git a/targets/TARGET_NORDIC/TARGET_NRF5x/mbed_lib.json b/targets/TARGET_NORDIC/TARGET_NRF5x/mbed_lib.json new file mode 100644 index 00000000000..34730e6fa8d --- /dev/null +++ b/targets/TARGET_NORDIC/TARGET_NRF5x/mbed_lib.json @@ -0,0 +1,29 @@ +{ + "name": "nordic", + "config": { + "uart-hwfc": { + "help": "Enable hardware flow control for STDIO.", + "value": 1 + }, + "lf-clock-src": { + "help": "Select Low Frequency clock source.", + "value": "NRF_LF_SRC_XTAL" + }, + "uart-timeout-us": { + "help": "Idle time in micro seconds between characters before buffer is flushed.", + "value": 2000 + }, + "uart-0-fifo-size": { + "help": "UART0 FIFO buffer. FIFO buffer is filled from DMA buffer.", + "value": 32 + }, + "uart-1-fifo-size": { + "help": "UART1 FIFO buffer. FIFO buffer is filled from DMA buffer.", + "value": 32 + }, + "uart-dma-size": { + "help": "UART DMA buffer. 2 buffers per instance. DMA buffer is filled by UARTE", + "value": 8 + } + } +} diff --git a/targets/TARGET_NORDIC/TARGET_NRF5x/objects.h b/targets/TARGET_NORDIC/TARGET_NRF5x/objects.h index 22c2bbdea37..760674b3ce1 100644 --- a/targets/TARGET_NORDIC/TARGET_NRF5x/objects.h +++ b/targets/TARGET_NORDIC/TARGET_NRF5x/objects.h @@ -52,9 +52,32 @@ extern "C" { #endif +#include "nrf_uart.h" + struct serial_s { - uint32_t placeholder; // struct is unused by nRF5x API implementation -}; // but it must be not empty (required by strict compiler - IAR) + int instance; + uint32_t tx; + uint32_t rx; + uint32_t cts; + uint32_t rts; + nrf_uart_hwfc_t hwfc; + nrf_uart_parity_t parity; + nrf_uart_baudrate_t baudrate; + uint32_t context; + uint32_t handler; + uint32_t mask; + uint32_t event; + bool update; +#if DEVICE_SERIAL_ASYNCH + bool rx_asynch; + uint32_t tx_handler; + uint32_t tx_mask; + uint32_t tx_event; + uint32_t rx_handler; + uint32_t rx_mask; + uint32_t rx_event; +#endif +}; struct spi_s { int instance; diff --git a/targets/TARGET_NORDIC/TARGET_NRF5x/pinmap_ex.c b/targets/TARGET_NORDIC/TARGET_NRF5x/pinmap_ex.c index b68fc411472..bae81853311 100644 --- a/targets/TARGET_NORDIC/TARGET_NRF5x/pinmap_ex.c +++ b/targets/TARGET_NORDIC/TARGET_NRF5x/pinmap_ex.c @@ -32,6 +32,7 @@ #define NORDIC_TWI_COUNT TWI_COUNT #define NORDIC_SPI_COUNT SPI_COUNT #define NORDIC_PWM_COUNT PWM_COUNT +#define NORDIC_UART_COUNT UARTE_COUNT /* Define which instance to return when there are no free instances left. * The Mbed HAL API doesn't provide a way for signaling initialization errors @@ -41,6 +42,7 @@ #define DEFAULT_I2C_INSTANCE 0 // SPI counts down, choose instance furthest away #define DEFAULT_SPI_INSTANCE (NORDIC_SPI_COUNT - 1) // I2C counts up, choose instance furthers away #define DEFAULT_PWM_INSTANCE (NORDIC_PWM_COUNT - 1) +#define DEFAULT_UART_INSTANCE (NORDIC_UART_COUNT - 1) /*** @@ -365,3 +367,115 @@ int pin_instance_pwm(PinName pwm) return instance; } + + +/*** + * _ _ _____ _______ + * | | | | /\ | __ \__ __| + * | | | | / \ | |__) | | | + * | | | |/ /\ \ | _ / | | + * | |__| / ____ \| | \ \ | | + * \____/_/ \_\_| \_\ |_| + * + * + */ + +/* Internal data structure to keep track of allocated instances. + */ +typedef struct { + PinName tx; + PinName rx; +} uart_t; + +static uart_t nordic_internal_uart[NORDIC_UART_COUNT] = { + { NC, NC }, +#if (NORDIC_UART_COUNT == 2) + { NC, NC } +#endif +}; + +/** + * Brief Find hardware instance for the provided UART pin. + * + * The function will search the PeripheralPin map for a pre-allocated + * assignment. If none is found the allocation map will be searched + * to see if the same pins have been assigned an instance before. + * + * If no assignement is found and there is an empty slot left in the + * map, the pins are stored in the map and the hardware instance is + * returned. + * + * If no free instances are available, the default instance is returned. + * + * Parameter tx tx pin. + * Parameter rx rx pin. + * + * Return Hardware instance associated with provided pins. + */ +int pin_instance_uart(PinName tx, PinName rx) +{ + int instance = NC; + + /* Search pin map for pre-allocated instance */ + for (size_t index = 0; ((PinMap_UART[index].tx != NC) && (PinMap_UART[index].rx != NC)); index++) { + + /* Compare pins to entry. */ + if ((PinMap_UART[index].tx == tx) && (PinMap_UART[index].rx == rx)) { + + DEBUG_PRINTF("found: %d %d %d\r\n", tx, rx, PinMap_UART[index].instance); + + /* Instance found, save result. */ + instance = PinMap_UART[index].instance; + + /* Lock out entry in map. */ + nordic_internal_uart[instance].tx = tx; + nordic_internal_uart[instance].rx = rx; + break; + } + } + + /* No instance was found in static map. */ + if (instance == NC) { + + /* Search dynamic map for entry. */ + for (size_t index = 0; index < NORDIC_UART_COUNT; index++) { + + /* Pins match previous dynamic allocation, return instance. */ + if ((nordic_internal_uart[index].tx == tx) && (nordic_internal_uart[index].rx == rx)) { + + instance = index; + break; + } + } + } + + /* No instance was found in dynamic map. */ + if (instance == NC) { + + /* Search dynamic map for empty slot. */ + for (size_t index = 0; index < NORDIC_UART_COUNT; index++) { + + /* Empty slot found, reserve slot by storing pins. */ + if ((nordic_internal_uart[index].tx == NC) && (nordic_internal_uart[index].rx == NC)) { + + nordic_internal_uart[index].tx = tx; + nordic_internal_uart[index].rx = rx; + + instance = index; + break; + } + } + } + +#if defined(DEFAULT_UART_INSTANCE) + /* Exhausted all options. Return default value. */ + if (instance == NC) { + instance = DEFAULT_UART_INSTANCE; + } +#endif + + DEBUG_PRINTF("UART: %d %d %d\r\n", tx, rx, instance); + + return instance; +} + diff --git a/targets/TARGET_NORDIC/TARGET_NRF5x/pinmap_ex.h b/targets/TARGET_NORDIC/TARGET_NRF5x/pinmap_ex.h index 21250bae305..5738248a3c1 100644 --- a/targets/TARGET_NORDIC/TARGET_NRF5x/pinmap_ex.h +++ b/targets/TARGET_NORDIC/TARGET_NRF5x/pinmap_ex.h @@ -50,6 +50,15 @@ typedef struct { extern const PinMapPWM PinMap_PWM[]; +/* Data structure for pre-allocated UART instances. */ +typedef struct { + PinName tx; + PinName rx; + int instance; +} PinMapUART; + +extern const PinMapUART PinMap_UART[]; + /** * @brief Find hardware instance for the provided I2C pins. * @@ -110,6 +119,26 @@ int pin_instance_spi(PinName mosi, PinName miso, PinName clk); */ int pin_instance_pwm(PinName pwm); +/** + * @brief Find hardware instance for the provided UART pins. + * + * The function will search the PeripheralPin map for a pre-allocated + * assignment. If none is found the allocation map will be searched + * to see if the same pins have been assigned an instance before. + * + * If no assignement is found and there is an empty slot left in the + * map, the pins are stored in the map and the hardware instance is + * returned. + * + * If no free instances are available, the default instance is returned. + * + * @param[in] tx tx pin. + * @param[in] rx rx pin. + * + * @return Hardware instance associated with provided pins. + */ +int pin_instance_uart(PinName tx, PinName rx); + #ifdef __cplusplus } #endif diff --git a/targets/TARGET_NORDIC/TARGET_NRF5x/serial_api.c b/targets/TARGET_NORDIC/TARGET_NRF5x/serial_api.c index 0835c517652..0b27ab937a0 100644 --- a/targets/TARGET_NORDIC/TARGET_NRF5x/serial_api.c +++ b/targets/TARGET_NORDIC/TARGET_NRF5x/serial_api.c @@ -1,28 +1,28 @@ -/* +/* * Copyright (c) 2013 Nordic Semiconductor ASA * All rights reserved. - * + * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this list + * + * 1. Redistributions of source code must retain the above copyright notice, this list * of conditions and the following disclaimer. * - * 2. Redistributions in binary form, except as embedded into a Nordic Semiconductor ASA - * integrated circuit in a product or a software update for such product, must reproduce - * the above copyright notice, this list of conditions and the following disclaimer in + * 2. Redistributions in binary form, except as embedded into a Nordic Semiconductor ASA + * integrated circuit in a product or a software update for such product, must reproduce + * the above copyright notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the distribution. * - * 3. Neither the name of Nordic Semiconductor ASA nor the names of its contributors may be - * used to endorse or promote products derived from this software without specific prior + * 3. Neither the name of Nordic Semiconductor ASA nor the names of its contributors may be + * used to endorse or promote products derived from this software without specific prior * written permission. * - * 4. This software, with or without modification, must only be used with a + * 4. This software, with or without modification, must only be used with a * Nordic Semiconductor ASA integrated circuit. * - * 5. Any software provided in binary or object form under this license must not be reverse - * engineered, decompiled, modified and/or disassembled. - * + * 5. Any software provided in binary or object form under this license must not be reverse + * engineered, decompiled, modified and/or disassembled. + * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE @@ -33,631 +33,1720 @@ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * + * */ -#include "serial_api.h" - #if DEVICE_SERIAL -#include -#include "mbed_assert.h" -#include "mbed_error.h" -#include "nrf_uart.h" +#include "hal/serial_api.h" + +#include "nrf_uarte.h" +#include "nrf_drv_uart.h" #include "nrf_drv_common.h" +#include "nrf_atfifo.h" #include "app_util_platform.h" -#include "nrf_gpio.h" -#include "sdk_config.h" +#include "nrf_rtc.h" +#include "pinmap_ex.h" + +#include "platform/mbed_critical.h" + +#if UART0_ENABLED == 0 +#error UART0 is disabled. DEVICE_SERIAL must also be disabled to continue. +#endif -#define UART_INSTANCE_COUNT 1 -#define UART_INSTANCE NRF_UART0 -#define UART_IRQn UART0_IRQn -#define UART_IRQ_HANDLER UART0_IRQHandler -#define UART_INSTANCE_ID 0 -#define UART_CB uart_cb[UART_INSTANCE_ID] -#define UART_DEFAULT_BAUDRATE UART_DEFAULT_CONFIG_BAUDRATE -#define UART_DEFAULT_PARITY UART_DEFAULT_CONFIG_PARITY +/*** + * _____ __ _ _ _ + * / ____| / _(_) | | (_) + * | | ___ _ __ | |_ _ __ _ _ _ _ __ __ _| |_ _ ___ _ __ + * | | / _ \| '_ \| _| |/ _` | | | | '__/ _` | __| |/ _ \| '_ \ + * | |___| (_) | | | | | | | (_| | |_| | | | (_| | |_| | (_) | | | | + * \_____\___/|_| |_|_| |_|\__, |\__,_|_| \__,_|\__|_|\___/|_| |_| + * __/ | + * |___/ + */ -// expected the macro from mbed configuration system +/** + * Legacy hardware flow control setting. Enables flow control for STDOUT. + */ #ifndef MBED_CONF_NORDIC_UART_HWFC - #define MBED_CONF_NORDIC_UART_HWFC 1 - #warning None of UART flow control configuration (expected macro MBED_CONF_NORDIC_UART_HWFC). The RTSCTS flow control is used by default . +#define MBED_CONF_NORDIC_UART_HWFC 0 +#endif + +/** + * Idle timeout between characters before DMA buffer is flushed. + */ +#ifndef MBED_CONF_NORDIC_UART_TIMEOUT_US +#define MBED_CONF_NORDIC_UART_TIMEOUT_US 2000 +#endif + +/** + * Default FIFO buffer size for UARTE0. + */ +#ifndef MBED_CONF_NORDIC_UART_0_FIFO_SIZE +#define MBED_CONF_NORDIC_UART_0_FIFO_SIZE 32 #endif -#if MBED_CONF_NORDIC_UART_HWFC == 1 - #define UART_DEFAULT_HWFC UART_DEFAULT_CONFIG_HWFC +/** + * Default FIFO buffer size for UARTE1. + */ +#ifndef MBED_CONF_NORDIC_UART_1_FIFO_SIZE +#define MBED_CONF_NORDIC_UART_1_FIFO_SIZE 32 +#endif + +/** + * Default DMA buffer size. Each instance has two DMA buffers. + */ +#ifndef MBED_CONF_NORDIC_UART_DMA_SIZE +#define MBED_CONF_NORDIC_UART_DMA_SIZE 8 #else - #define UART_DEFAULT_HWFC NRF_UART_HWFC_DISABLED +#if MBED_CONF_NORDIC_UART_DMA_SIZE < 5 +#error MBED_CONF_NORDIC_UART_DMA_SIZE must be at least 5 bytes +#endif +#if MBED_CONF_NORDIC_UART_DMA_SIZE > 255 +#error MBED_CONF_NORDIC_UART_DMA_SIZE must be less than 256 bytes +#endif +#endif + +/** + * Internal short names. + */ +#define IDLE_TIMEOUT_US MBED_CONF_NORDIC_UART_TIMEOUT_US +#define UART0_FIFO_BUFFER_SIZE MBED_CONF_NORDIC_UART_0_FIFO_SIZE +#define UART1_FIFO_BUFFER_SIZE MBED_CONF_NORDIC_UART_1_FIFO_SIZE +#define DMA_BUFFER_SIZE MBED_CONF_NORDIC_UART_DMA_SIZE +#define NUMBER_OF_BANKS 2 + +/** + * Default timer delay for callbacks. + */ +#define CALLBACK_DELAY_US 100 + +/** + * Use RTC2 for idle timeouts and deferred callbacks. + * Each channel is dedicated to one particular task. + */ +#define UARTE0_RTC_TIMEOUT_CHANNEL 0 +#define UARTE0_RTC_CALLBACK_CHANNEL 1 +#define UARTE1_RTC_TIMEOUT_CHANNEL 2 +#define UARTE1_RTC_CALLBACK_CHANNEL 3 + +/** + * RTC frequency. + */ +#define RTC_FREQUENCY 32768 + + +/*** + * _______ _ __ + * |__ __| | | / _| + * | |_ _ _ __ ___ __| | ___| |_ ___ + * | | | | | '_ \ / _ \/ _` |/ _ \ _/ __| + * | | |_| | |_) | __/ (_| | __/ | \__ \ + * |_|\__, | .__/ \___|\__,_|\___|_| |___/ + * __/ | | + * |___/|_| + */ + +/** + * Missing event typedefs. + */ +typedef enum +{ + NRF_UARTE_EVENT_RXDRDY = offsetof(NRF_UARTE_Type, EVENTS_RXDRDY), + NRF_UARTE_EVENT_TXDRDY = offsetof(NRF_UARTE_Type, EVENTS_TXDRDY), +} nrf_uarte_event_extra_t; + +/** + * Missing interrupt masks. + */ +typedef enum +{ + NRF_UARTE_INT_RXDRDY_MASK = UARTE_INTENSET_RXDRDY_Msk, + NRF_UARTE_INT_TXDRDY_MASK = UARTE_INTENSET_TXDRDY_Msk, +} nrf_uarte_int_mask_extra_t; + +/** + * Internal struct for storing each UARTE instance's state: + * + * owner: pointer to serial object currently using instance. + * buffer: buffers assigned to EasyDMA. + * rxdrdy_counter: count received characters for idle detection. + * endrx_counter: count filled DMA buffers for idle detection. + * tx_data: 1 byte Tx buffer for blocking putc. + * tx_in_progress: mutex for atomic Tx. + * rx_in_progress: mutex for atomic Rx when using async API. + * tx_asynch: set synch or asynch mode for Tx. + * rx_asynch: set synch or asynch mode for Rx. + * ticker_is_running: flag for enabling/disabling idle timer. + * callback_posted: flag for posting only one callback. + * active_bank: flag for buffer swapping. + * fifo: pointer to the FIFO buffer. + */ +typedef struct { + struct serial_s *owner; + uint8_t buffer[NUMBER_OF_BANKS][DMA_BUFFER_SIZE]; + uint32_t rxdrdy_counter; + uint32_t endrx_counter; + uint8_t tx_data; + volatile uint8_t tx_in_progress; + volatile uint8_t rx_in_progress; + bool tx_asynch; + bool rx_asynch; + bool ticker_is_running; + bool callback_posted; + uint8_t active_bank; + nrf_atfifo_t *fifo; +} nordic_uart_state_t; + +/** + * Turn Mbed HAL IRQ flags into maskable bit masks. + */ +typedef enum { + NORDIC_TX_IRQ = (1 << 0), + NORDIC_RX_IRQ = (1 << 1), +} nordic_irq_t; + + +/*** + * _____ _ _ _ __ __ _ _ _ + * / ____| | | | | | \ \ / / (_) | | | | + * | | __| | ___ | |__ __ _| | \ \ / /_ _ _ __ _ __ _| |__ | | ___ ___ + * | | |_ | |/ _ \| '_ \ / _` | | \ \/ / _` | '__| |/ _` | '_ \| |/ _ \/ __| + * | |__| | | (_) | |_) | (_| | | \ / (_| | | | | (_| | |_) | | __/\__ \ + * \_____|_|\___/|_.__/ \__,_|_| \/ \__,_|_| |_|\__,_|_.__/|_|\___||___/ + * + * + */ + +/** + * UARTE state. One for each instance. + */ +static nordic_uart_state_t nordic_nrf5_uart_state[UART_ENABLED_COUNT] = { 0 }; + +/** + * Array with UARTE register pointers for easy access. + */ +static NRF_UARTE_Type *nordic_nrf5_uart_register[UART_ENABLED_COUNT] = { + NRF_UARTE0, +#if UART1_ENABLED + NRF_UARTE1, #endif +}; -#define UART_DEFAULT_CTS CTS_PIN_NUMBER -#define UART_DEFAULT_RTS RTS_PIN_NUMBER +/** + * @brief Create atomic fifo using macro. Macro defines static arrays + * for buffer and internal state. + */ +NRF_ATFIFO_DEF(nordic_nrf5_uart_fifo_0, uint8_t, UART0_FIFO_BUFFER_SIZE); -#ifdef NRF51 - #define NRFx_MBED_UART_IRQ_PRIORITY APP_IRQ_PRIORITY_LOW -#elif defined(NRF52) || defined(NRF52840_XXAA) - #define NRFx_MBED_UART_IRQ_PRIORITY APP_IRQ_PRIORITY_LOWEST +#if UART1_ENABLED +NRF_ATFIFO_DEF(nordic_nrf5_uart_fifo_1, uint8_t, UART1_FIFO_BUFFER_SIZE); #endif -// Required by "retarget.cpp". +/** + * Global variables expected by mbed_retarget.cpp for STDOUT. + */ int stdio_uart_inited = 0; -serial_t stdio_uart; +serial_t stdio_uart = { 0 }; + + +/*** + * _____ _ _______ _ + * / ____| | | |__ __(_) + * | | _ _ ___| |_ ___ _ __ ___ | | _ _ __ ___ ___ _ __ + * | | | | | / __| __/ _ \| '_ ` _ \ | | | | '_ ` _ \ / _ \ '__| + * | |___| |_| \__ \ || (_) | | | | | | | | | | | | | | | __/ | + * \_____\__,_|___/\__\___/|_| |_| |_| |_| |_|_| |_| |_|\___|_| + * + * + */ -typedef struct { - bool initialized; - uint32_t irq_context; - uart_irq_handler irq_handler; - - uint32_t pselrxd; - uint32_t pseltxd; - uint32_t pselcts; - uint32_t pselrts; - nrf_uart_hwfc_t hwfc; - nrf_uart_parity_t parity; - nrf_uart_baudrate_t baudrate; +/** + * @brief Set timout for a particular channel in RTC2. + * + * @param[in] timeout The timeout + * @param[in] channel The channel + */ +static void nordic_custom_ticker_set(uint32_t timeout, int channel) +{ + /** + * Add timeout to current time and set as compare value for channel. + */ + uint32_t now = nrf_rtc_counter_get(NRF_RTC2); + uint32_t ticksout = (timeout * RTC_FREQUENCY) / (1000 * 1000); + nrf_rtc_cc_set(NRF_RTC2, channel, ticksout + now); + + /** + * Enable interrupt for channel. + */ + uint32_t mask = nrf_rtc_int_get(NRF_RTC2); + nrf_rtc_int_enable(NRF_RTC2, mask | RTC_CHANNEL_INT_MASK(channel)); +} -#if DEVICE_SERIAL_ASYNCH - bool volatile rx_active; - uint8_t *rx_buffer; - size_t rx_length; - size_t rx_pos; - void (*rx_asynch_handler)(); - uint8_t char_match; - - bool volatile tx_active; - const uint8_t *tx_buffer; - size_t tx_length; - size_t tx_pos; - void (*tx_asynch_handler)(); - - uint32_t events_wanted; - uint32_t events_occured; - - #define UART_IRQ_TX 1 - #define UART_IRQ_RX 2 - uint8_t irq_enabled; -#endif // DEVICE_SERIAL_ASYNCH -} uart_ctlblock_t; +/** + * @brief Set idle timeout for particular instance. + * This function translates instance number to RTC channel. + * + * @param[in] instance The instance + */ +static void nordic_custom_ticker_set_timeout(int instance) +{ + if (instance == 0) { -static uart_ctlblock_t uart_cb[UART_INSTANCE_COUNT]; + nordic_custom_ticker_set(IDLE_TIMEOUT_US, UARTE0_RTC_TIMEOUT_CHANNEL); + } + else if (instance == 1) { -static void internal_set_hwfc(FlowControl type, - PinName rxflow, PinName txflow); + nordic_custom_ticker_set(IDLE_TIMEOUT_US, UARTE1_RTC_TIMEOUT_CHANNEL); + } +} +/** + * @brief Schedule callback for particular instance. + * This function translates instance number to RTC channel. + * + * @param[in] instance The instance + */ +static void nordic_custom_ticker_set_callback(int instance) +{ + if (instance == 0) { -#if DEVICE_SERIAL_ASYNCH -static void end_asynch_rx(void) + nordic_custom_ticker_set(CALLBACK_DELAY_US, UARTE0_RTC_CALLBACK_CHANNEL); + } + else if (instance == 1) { + + nordic_custom_ticker_set(CALLBACK_DELAY_US, UARTE1_RTC_CALLBACK_CHANNEL); + } +} + + +/*** + * _______ _ _ _ _ _ + * |__ __(_) | | | | | | | + * | | _ _ __ ___ ___ _ __ | |__| | __ _ _ __ __| | | ___ _ __ + * | | | | '_ ` _ \ / _ \ '__| | __ |/ _` | '_ \ / _` | |/ _ \ '__| + * | | | | | | | | | __/ | | | | | (_| | | | | (_| | | __/ | + * |_| |_|_| |_| |_|\___|_| |_| |_|\__,_|_| |_|\__,_|_|\___|_| + * + * + */ + +/** + * @brief Interrupt handler for idle timeouts. + * This function fans out interrupts from ISR and + * translates channel to instance. + * + * @param[in] instance The instance + */ +static void nordic_nrf5_uart_timeout_handler(uint32_t instance) { - // If RX interrupt is activated for synchronous operations, - // don't disable it, just stop handling it here. - if (!(UART_CB.irq_enabled & UART_IRQ_RX)) { - nrf_uart_int_disable(UART_INSTANCE, NRF_UART_INT_MASK_RXDRDY); + /** + * Check if any characters have been received or buffers been flushed + * since the last idle timeout. + */ + if ((nordic_nrf5_uart_state[instance].rxdrdy_counter > 0) || + (nordic_nrf5_uart_state[instance].endrx_counter > 0)) { + + /* Activity detected, reset timeout. */ + nordic_custom_ticker_set_timeout(instance); + + } else { + + /* No activity detected, no timeout set. */ + nordic_nrf5_uart_state[instance].ticker_is_running = false; + + /** + * Stop Rx, this triggers a buffer swap and copies data from + * DMA buffer to FIFO buffer. + */ + nrf_uarte_task_trigger(nordic_nrf5_uart_register[instance], + NRF_UARTE_TASK_STOPRX); } - UART_CB.rx_active = false; + + /* reset activity counters. */ + nordic_nrf5_uart_state[instance].rxdrdy_counter = 0; + nordic_nrf5_uart_state[instance].endrx_counter = 0; } -static void end_asynch_tx(void) + +/** + * @brief Interrupt handler for scheduled callbacks. + * This function fans out interrupts from ISR and + * translates channel to instance. + * + * @param[in] instance The instance + */ +static void nordic_nrf5_uart_callback_handler(uint32_t instance) { - // If TX interrupt is activated for synchronous operations, - // don't disable it, just stop handling it here. - if (!(UART_CB.irq_enabled & UART_IRQ_TX)) { - nrf_uart_int_disable(UART_INSTANCE, NRF_UART_INT_MASK_TXDRDY); + /* Flag that no callback is posted. */ + nordic_nrf5_uart_state[instance].callback_posted = false; + + /* Check if callback handler is set and if event mask match. */ + uart_irq_handler callback = (uart_irq_handler) nordic_nrf5_uart_state[instance].owner->handler; + uint32_t mask = nordic_nrf5_uart_state[instance].owner->mask; + + if (callback && (mask & NORDIC_RX_IRQ)) { + + /* Invoke callback function. */ + uint32_t context = nordic_nrf5_uart_state[instance].owner->context; + callback(context, RxIrq); } - UART_CB.tx_active = false; } -#endif // DEVICE_SERIAL_ASYNCH -void UART_IRQ_HANDLER(void) +/** + * @brief RTC2 ISR. Used for timeouts and scheduled callbacks. + */ +static void nordic_nrf5_rtc2_handler(void) { - if (nrf_uart_int_enable_check(UART_INSTANCE, NRF_UART_INT_MASK_RXDRDY) && - nrf_uart_event_check(UART_INSTANCE, NRF_UART_EVENT_RXDRDY)) { + /* Channel 0 */ + if (nrf_rtc_event_pending(NRF_RTC2, NRF_RTC_EVENT_COMPARE_0)) { - #if DEVICE_SERIAL_ASYNCH - if (UART_CB.rx_active) { - nrf_uart_event_clear(UART_INSTANCE, NRF_UART_EVENT_RXDRDY); + /* Clear event and disable interrupt for channel. */ + nrf_rtc_event_clear(NRF_RTC2, NRF_RTC_EVENT_COMPARE_0); - uint8_t rx_data = nrf_uart_rxd_get(UART_INSTANCE); - UART_CB.rx_buffer[UART_CB.rx_pos] = rx_data; + uint32_t mask = nrf_rtc_int_get(NRF_RTC2); + nrf_rtc_int_enable(NRF_RTC2, mask & ~NRF_RTC_INT_COMPARE0_MASK); - bool end_rx = false; - // If character matching should be performed, check if the current - // data matches the given one. - if (UART_CB.char_match != SERIAL_RESERVED_CHAR_MATCH && - rx_data == UART_CB.char_match) { - // If it does, report the match and abort further receiving. - UART_CB.events_occured |= SERIAL_EVENT_RX_CHARACTER_MATCH; - if (UART_CB.events_wanted & SERIAL_EVENT_RX_CHARACTER_MATCH) { - end_rx = true; - } - } - if (++UART_CB.rx_pos >= UART_CB.rx_length) { - UART_CB.events_occured |= SERIAL_EVENT_RX_COMPLETE; - end_rx = true; - } - if (end_rx) { - end_asynch_rx(); - - if (UART_CB.rx_asynch_handler) { - // Use local variable to make it possible to start a next - // transfer from callback routine. - void (*handler)() = UART_CB.rx_asynch_handler; - UART_CB.rx_asynch_handler = NULL; - handler(); - } - } - } - else - #endif + /* Call timeout handler with instance ID. */ + nordic_nrf5_uart_timeout_handler(0); + } - if (UART_CB.irq_handler) { - UART_CB.irq_handler(UART_CB.irq_context, RxIrq); - } + /* Channel 1 */ + if (nrf_rtc_event_pending(NRF_RTC2, NRF_RTC_EVENT_COMPARE_1)) { + + /* Clear event and disable interrupt for channel. */ + nrf_rtc_event_clear(NRF_RTC2, NRF_RTC_EVENT_COMPARE_1); + + uint32_t mask = nrf_rtc_int_get(NRF_RTC2); + nrf_rtc_int_enable(NRF_RTC2, mask & ~NRF_RTC_INT_COMPARE1_MASK); + + /* Call callback handler with instance ID. */ + nordic_nrf5_uart_callback_handler(0); } - if (nrf_uart_int_enable_check(UART_INSTANCE, NRF_UART_INT_MASK_TXDRDY) && - nrf_uart_event_check(UART_INSTANCE, NRF_UART_EVENT_TXDRDY)) { - - #if DEVICE_SERIAL_ASYNCH - if (UART_CB.tx_active) { - if (UART_CB.tx_pos < UART_CB.tx_length) { - // When there is still something to send, clear the TXDRDY event - // and put next byte to transmitter. - nrf_uart_event_clear(UART_INSTANCE, NRF_UART_EVENT_TXDRDY); - nrf_uart_txd_set(UART_INSTANCE, - UART_CB.tx_buffer[UART_CB.tx_pos]); - UART_CB.tx_pos++; - } - else { - // When the TXDRDY event is set after the last byte to be sent - // has been passed to the transmitter, the job is done and TX - // complete can be indicated. - // Don't clear the TXDRDY event, it needs to remain set for the - // 'serial_writable' function to work properly. - end_asynch_tx(); - - UART_CB.events_occured |= SERIAL_EVENT_TX_COMPLETE; - if (UART_CB.tx_asynch_handler) { - // Use local variable to make it possible to start a next - // transfer from callback routine. - void (*handler)() = UART_CB.tx_asynch_handler; - UART_CB.tx_asynch_handler = NULL; - handler(); - } +#if UART1_ENABLED + /* Channel 2 */ + if (nrf_rtc_event_pending(NRF_RTC2, NRF_RTC_EVENT_COMPARE_2)) { + + /* Clear event and disable interrupt for channel. */ + nrf_rtc_event_clear(NRF_RTC2, NRF_RTC_EVENT_COMPARE_2); + + uint32_t mask = nrf_rtc_int_get(NRF_RTC2); + nrf_rtc_int_enable(NRF_RTC2, mask & ~NRF_RTC_INT_COMPARE2_MASK); + + /* Call timeout handler with instance ID. */ + nordic_nrf5_uart_timeout_handler(1); + } + + /* Channel 3 */ + if (nrf_rtc_event_pending(NRF_RTC2, NRF_RTC_EVENT_COMPARE_3)) { + + /* Clear event and disable interrupt for channel. */ + nrf_rtc_event_clear(NRF_RTC2, NRF_RTC_EVENT_COMPARE_3); + + uint32_t mask = nrf_rtc_int_get(NRF_RTC2); + nrf_rtc_int_enable(NRF_RTC2, mask & ~NRF_RTC_INT_COMPARE3_MASK); + + /* Call callback handler with instance ID. */ + nordic_nrf5_uart_callback_handler(1); + } +#endif + +} + + +/*** + * _ _ _____ _______ ______ _ _ _ _ _ + * | | | | /\ | __ \__ __| | ____| | | | | | | | | | + * | | | | / \ | |__) | | | | |____ _____ _ __ | |_ | |__| | __ _ _ __ __| | | ___ _ __ + * | | | |/ /\ \ | _ / | | | __\ \ / / _ \ '_ \| __| | __ |/ _` | '_ \ / _` | |/ _ \ '__| + * | |__| / ____ \| | \ \ | | | |___\ V / __/ | | | |_ | | | | (_| | | | | (_| | | __/ | + * \____/_/ \_\_| \_\ |_| |______\_/ \___|_| |_|\__| |_| |_|\__,_|_| |_|\__,_|_|\___|_| + * + * + */ + +/** + * @brief Event handler for when Rx buffer is full or buffer swap has been + * triggered by idle task. + * + * Copy data from DMA buffer to FIFO buffer. + * Post callback if not already posted. + * + * @param[in] instance The instance + */ +static void nordic_nrf5_uart_event_handler_endrx(int instance) +{ + /* Increment idle counter. */ + core_util_atomic_incr_u32(&nordic_nrf5_uart_state[instance].endrx_counter, 1); + + /* Read out active bank flag and swap DMA buffers. */ + uint8_t active_bank = nordic_nrf5_uart_state[instance].active_bank; + nordic_nrf5_uart_state[instance].active_bank = active_bank ^ 0x01; + + /* Get number of bytes in DMA buffer. */ + uint32_t available = nrf_uarte_rx_amount_get(nordic_nrf5_uart_register[instance]); + + if (available > 0) { + + /* Copy data from DMA buffer to FIFO buffer. */ + for (size_t index = 0; index < available; index++) { + + /* Atomic FIFO can be used safely without disabling interrutps. */ + nrf_atfifo_item_put_t fifo_context; + + /* Get pointer to available space. */ + uint8_t *byte = (uint8_t *) nrf_atfifo_item_alloc(nordic_nrf5_uart_state[instance].fifo, &fifo_context); + + if (byte != NULL) { + + /* Copy 1 byte from DMA buffer and commit to FIFO buffer. */ + *byte = nordic_nrf5_uart_state[instance].buffer[active_bank][index]; + nrf_atfifo_item_put(nordic_nrf5_uart_state[instance].fifo, &fifo_context); + + } else { + + /* Buffer overflow. */ + break; } } - else - #endif - if (UART_CB.irq_handler) { - UART_CB.irq_handler(UART_CB.irq_context, TxIrq); + /* Schedule callback to signal data is available if not already posted. */ + if (nordic_nrf5_uart_state[instance].callback_posted == false) { + + nordic_nrf5_uart_state[instance].callback_posted = true; + nordic_custom_ticker_set_callback(instance); } + } +} + +/** + * @brief Event handler for when DMA has been armed with Rx buffer. + * + * Arm Rx buffer with second buffer for optimal reception. + * + * @param[in] instance The instance + */ +static void nordic_nrf5_uart_event_handler_rxstarted(int instance) +{ + uint8_t next_bank = nordic_nrf5_uart_state[instance].active_bank ^ 0x01; + + nrf_uarte_rx_buffer_set(nordic_nrf5_uart_register[instance], nordic_nrf5_uart_state[instance].buffer[next_bank], DMA_BUFFER_SIZE); +} + +/** + * @brief Event handler for when a character has been received in DMA buffer. + * + * Increment idle counter and set idle timeout if not already set. + * + * @param[in] instance The instance + */ +static void nordic_nrf5_uart_event_handler_rxdrdy(int instance) +{ + /* Increment idle counter. */ + core_util_atomic_incr_u32(&nordic_nrf5_uart_state[instance].rxdrdy_counter, 1); + + /* Set idle timeout if not already set. */ + if (nordic_nrf5_uart_state[instance].ticker_is_running == false) { + + nordic_nrf5_uart_state[instance].ticker_is_running = true; + + nordic_custom_ticker_set_timeout(instance); + } +} + +/** + * @brief Event handler for when the Tx buffer has been transmitted. + * + * @param[in] instance The instance + */ +static void nordic_nrf5_uart_event_handler_endtx(int instance) +{ + /* Set Tx done. */ + nordic_nrf5_uart_state[instance].tx_in_progress = 0; + + /* Check if callback handler and Tx event mask is set. */ + uart_irq_handler callback = (uart_irq_handler) nordic_nrf5_uart_state[instance].owner->handler; + uint32_t mask = nordic_nrf5_uart_state[instance].owner->mask; + + if (callback && (mask & NORDIC_TX_IRQ)) { + + /* Invoke callback function. */ + uint32_t context = nordic_nrf5_uart_state[instance].owner->context; + callback(context, TxIrq); } +} #if DEVICE_SERIAL_ASYNCH - if (nrf_uart_event_check(UART_INSTANCE, NRF_UART_EVENT_ERROR)) { - nrf_uart_event_clear(UART_INSTANCE, NRF_UART_EVENT_ERROR); - - uint8_t errorsrc = nrf_uart_errorsrc_get_and_clear(UART_INSTANCE); - if (UART_CB.rx_asynch_handler) { - UART_CB.events_occured |= SERIAL_EVENT_ERROR; - if (errorsrc & NRF_UART_ERROR_PARITY_MASK) { - UART_CB.events_occured |= SERIAL_EVENT_RX_PARITY_ERROR; - } - if (errorsrc & NRF_UART_ERROR_FRAMING_MASK) { - UART_CB.events_occured |= SERIAL_EVENT_RX_FRAMING_ERROR; - } - if (errorsrc & NRF_UART_ERROR_OVERRUN_MASK) { - UART_CB.events_occured |= SERIAL_EVENT_RX_OVERRUN_ERROR; - } - UART_CB.rx_asynch_handler(); - } +/** + * @brief Asynchronous event handler for when Rx DMA buffer is full. + * + * @param[in] instance The instance + */ +static void nordic_nrf5_uart_event_handler_endrx_asynch(int instance) +{ + /* Set Rx done and reset Rx mode to be not asynchronous. */ + nordic_nrf5_uart_state[instance].rx_in_progress = 0; + nordic_nrf5_uart_state[instance].rx_asynch = false; + + /* Cast handler to callback function pointer. */ + void (*callback)(void) = (void (*)(void)) nordic_nrf5_uart_state[instance].owner->rx_handler; + uint32_t mask = nordic_nrf5_uart_state[instance].owner->rx_mask; + + /* Signal error if event mask matches and event handler is set. */ + if (callback && (mask & SERIAL_EVENT_RX_COMPLETE)) { + + /* Store event value so it can be read back. */ + nordic_nrf5_uart_state[instance].owner->rx_event = SERIAL_EVENT_RX_COMPLETE; + + /* Signal callback handler. */ + callback(); } -#endif // DEVICE_SERIAL_ASYNCH } -void serial_init(serial_t *obj, PinName tx, PinName rx) { +/** + * @brief Asynchronous event handler for when Tx DMA buffer has been sent. + * + * @param[in] instance The instance + */ +static void nordic_nrf5_uart_event_handler_endtx_asynch(int instance) +{ + /* Set Tx done and reset Tx mode to be not asynchronous. */ + nordic_nrf5_uart_state[instance].tx_in_progress = 0; + nordic_nrf5_uart_state[instance].tx_asynch = false; + + /* Cast handler to callback function pointer. */ + void (*callback)(void) = (void (*)(void)) nordic_nrf5_uart_state[instance].owner->tx_handler; + uint32_t mask = nordic_nrf5_uart_state[instance].owner->tx_mask; - NVIC_SetVector(UART0_IRQn, (uint32_t) UART0_IRQHandler); + /* Signal error if event mask matches and event handler is set. */ + if (callback && (mask & SERIAL_EVENT_TX_COMPLETE)) { - - UART_CB.pseltxd = - (tx == NC) ? NRF_UART_PSEL_DISCONNECTED : (uint32_t)tx; - UART_CB.pselrxd = - (rx == NC) ? NRF_UART_PSEL_DISCONNECTED : (uint32_t)rx; - if (UART_CB.pseltxd != NRF_UART_PSEL_DISCONNECTED) { - nrf_gpio_pin_set(UART_CB.pseltxd); - nrf_gpio_cfg_output(UART_CB.pseltxd); - } - if (UART_CB.pselrxd != NRF_UART_PSEL_DISCONNECTED) { - nrf_gpio_cfg_input(UART_CB.pselrxd, NRF_GPIO_PIN_NOPULL); + /* Store event value so it can be read back. */ + nordic_nrf5_uart_state[instance].owner->tx_event = SERIAL_EVENT_TX_COMPLETE; + + /* Signal callback handler. */ + callback(); } +} +#endif - if (UART_CB.initialized) { - // For already initialized peripheral it is sufficient to reconfigure - // RX/TX pins only. +/** + * @brief UARTE event handler. + * + * Collect signals from UARTE0 and UARTE1 ISR and translate to instance. + * + * @param[in] instance The instance + */ +static void nordic_nrf5_uart_event_handler(int instance) +{ + /* DMA buffer is full or has been swapped out by idle timeout. */ + if (nrf_uarte_event_check(nordic_nrf5_uart_register[instance], NRF_UARTE_EVENT_ENDRX)) + { + nrf_uarte_event_clear(nordic_nrf5_uart_register[instance], NRF_UARTE_EVENT_ENDRX); - // Ensure that there is no unfinished TX transfer. - while (!serial_writable(obj)) { +#if DEVICE_SERIAL_ASYNCH + /* Call appropriate event handler based on current mode. */ + if (nordic_nrf5_uart_state[instance].rx_asynch) { + + nordic_nrf5_uart_event_handler_endrx_asynch(instance); + } else +#endif + { + nordic_nrf5_uart_event_handler_endrx(instance); } - // UART pins can be configured only when the peripheral is disabled. - nrf_uart_disable(UART_INSTANCE); - nrf_uart_txrx_pins_set(UART_INSTANCE, UART_CB.pseltxd, UART_CB.pselrxd); - nrf_uart_enable(UART_INSTANCE); } - else { - UART_CB.baudrate = UART_DEFAULT_BAUDRATE; - UART_CB.parity = UART_DEFAULT_PARITY; - UART_CB.hwfc = UART_DEFAULT_HWFC; - UART_CB.pselcts = UART_DEFAULT_CTS; - UART_CB.pselrts = UART_DEFAULT_RTS; - - nrf_uart_event_clear(UART_INSTANCE, NRF_UART_EVENT_RXDRDY); - nrf_uart_event_clear(UART_INSTANCE, NRF_UART_EVENT_TXDRDY); - nrf_uart_task_trigger(UART_INSTANCE, NRF_UART_TASK_STARTRX); - nrf_uart_task_trigger(UART_INSTANCE, NRF_UART_TASK_STARTTX); - - nrf_uart_int_disable(UART_INSTANCE, NRF_UART_INT_MASK_RXDRDY | - NRF_UART_INT_MASK_TXDRDY); - #if DEVICE_SERIAL_ASYNCH - nrf_uart_int_enable(UART_INSTANCE, NRF_UART_INT_MASK_ERROR); - #endif - nrf_drv_common_irq_enable(UART_IRQn, NRFx_MBED_UART_IRQ_PRIORITY); - - // TX interrupt needs to be signaled when transmitter buffer is empty, - // so a dummy transmission is needed to get the TXDRDY event initially - // set. - nrf_uart_configure(UART_INSTANCE, - NRF_UART_PARITY_EXCLUDED, NRF_UART_HWFC_DISABLED); - // Use maximum baud rate, so this dummy transmission takes as little - // time as possible. - nrf_uart_baudrate_set(UART_INSTANCE, NRF_UART_BAUDRATE_1000000); - // Perform it with disconnected TX pin, so nothing actually comes out - // of the device. - nrf_uart_txrx_pins_disconnect(UART_INSTANCE); - nrf_uart_hwfc_pins_disconnect(UART_INSTANCE); - nrf_uart_enable(UART_INSTANCE); - nrf_uart_txd_set(UART_INSTANCE, 0); - while (!nrf_uart_event_check(UART_INSTANCE, NRF_UART_EVENT_TXDRDY)) { - } - nrf_uart_disable(UART_INSTANCE); - - // Now everything is prepared to set the default configuration and - // connect the peripheral to actual pins. - nrf_uart_txrx_pins_set(UART_INSTANCE, UART_CB.pseltxd, UART_CB.pselrxd); - nrf_uart_baudrate_set(UART_INSTANCE, UART_CB.baudrate); - nrf_uart_configure(UART_INSTANCE, UART_CB.parity, UART_CB.hwfc); - if (UART_CB.hwfc == NRF_UART_HWFC_ENABLED) { - internal_set_hwfc(FlowControlRTSCTS, - (PinName) UART_CB.pselrts, (PinName) UART_CB.pselcts); - } - - nrf_uart_enable(UART_INSTANCE); - UART_CB.initialized = true; + /* Rx DMA buffer has been armed. */ + if (nrf_uarte_event_check(nordic_nrf5_uart_register[instance], NRF_UARTE_EVENT_RXSTARTED)) + { + nrf_uarte_event_clear(nordic_nrf5_uart_register[instance], NRF_UARTE_EVENT_RXSTARTED); + + nordic_nrf5_uart_event_handler_rxstarted(instance); } - if (tx == STDIO_UART_TX && rx == STDIO_UART_RX) { - stdio_uart_inited = 1; - memcpy(&stdio_uart, obj, sizeof(serial_t)); + /* Single character has been put in DMA buffer. */ + if (nrf_uarte_event_check(nordic_nrf5_uart_register[instance], NRF_UARTE_EVENT_RXDRDY)) + { + nrf_uarte_event_clear(nordic_nrf5_uart_register[instance], NRF_UARTE_EVENT_RXDRDY); + + nordic_nrf5_uart_event_handler_rxdrdy(instance); } - else { - stdio_uart_inited = 0; + + /* Tx DMA buffer has been sent. */ + if (nrf_uarte_event_check(nordic_nrf5_uart_register[instance], NRF_UARTE_EVENT_ENDTX)) + { + nrf_uarte_event_clear(nordic_nrf5_uart_register[instance], NRF_UARTE_EVENT_ENDTX); + +#if DEVICE_SERIAL_ASYNCH + /* Call appropriate event handler based on current mode. */ + if (nordic_nrf5_uart_state[instance].tx_asynch) { + + nordic_nrf5_uart_event_handler_endtx_asynch(instance); + } else +#endif + { + nordic_nrf5_uart_event_handler_endtx(instance); + } } } -void serial_free(serial_t *obj) +/** + * @brief UARTE0 ISR. + */ +static void nordic_nrf5_uart0_handler(void) +{ + /* Call event handler with instance ID. */ + nordic_nrf5_uart_event_handler(0); +} + +#if UART1_ENABLED +/** + * @brief UARTE1 ISR. + */ +static void nordic_nrf5_uart1_handler(void) +{ + /* Call event handler with instance ID. */ + nordic_nrf5_uart_event_handler(1); +} +#endif + + +/*** + * _____ __ _ _ _ + * / ____| / _(_) | | (_) + * | | ___ _ __ | |_ _ __ _ _ _ _ __ __ _| |_ _ ___ _ __ + * | | / _ \| '_ \| _| |/ _` | | | | '__/ _` | __| |/ _ \| '_ \ + * | |___| (_) | | | | | | | (_| | |_| | | | (_| | |_| | (_) | | | | + * \_____\___/|_| |_|_| |_|\__, |\__,_|_| \__,_|\__|_|\___/|_| |_| + * __/ | + * |___/ + */ + +/** + * @brief Enable UARTE interrupts. + * + * Translates instance to UARTE register. + * Set IRQ priority to highest to avoid Rx overflow. + * + * @param[in] instance The instance + */ +static void nordic_nrf5_uart_irq_enable(int instance) { - (void)obj; + if (instance == 0) { - if (UART_CB.initialized) { - nrf_uart_disable(UART_INSTANCE); - nrf_uart_int_disable(UART_INSTANCE, NRF_UART_INT_MASK_RXDRDY | - NRF_UART_INT_MASK_TXDRDY | - NRF_UART_INT_MASK_ERROR); - nrf_drv_common_irq_disable(UART_IRQn); - UART_CB.initialized = false; + nrf_drv_common_irq_enable(UARTE0_UART0_IRQn, APP_IRQ_PRIORITY_HIGHEST); + } - // There is only one UART instance, thus at this point the stdio UART - // can no longer be initialized. - stdio_uart_inited = 0; +#if UART1_ENABLED + else if (instance == 1) { + + nrf_drv_common_irq_enable(UARTE1_IRQn, APP_IRQ_PRIORITY_HIGHEST); } +#endif } -void serial_baud(serial_t *obj, int baudrate) +/** + * @brief Configure UARTE based on serial object settings. + * + * Common for both Rx and Tx. + * + * @param obj The object + */ +static void nordic_nrf5_uart_configure_object(serial_t *obj) { - // nrf_uart_baudrate_set() is not used here (registers are accessed - // directly) to make it possible to set special baud rates like 56000 - // or 31250. - - static uint32_t const acceptedSpeeds[][2] = { - { 1200, UART_BAUDRATE_BAUDRATE_Baud1200 }, - { 2400, UART_BAUDRATE_BAUDRATE_Baud2400 }, - { 4800, UART_BAUDRATE_BAUDRATE_Baud4800 }, - { 9600, UART_BAUDRATE_BAUDRATE_Baud9600 }, - { 14400, UART_BAUDRATE_BAUDRATE_Baud14400 }, - { 19200, UART_BAUDRATE_BAUDRATE_Baud19200 }, - { 28800, UART_BAUDRATE_BAUDRATE_Baud28800 }, - { 31250, (0x00800000UL) /* 31250 baud */ }, - { 38400, UART_BAUDRATE_BAUDRATE_Baud38400 }, - { 56000, (0x00E51000UL) /* 56000 baud */ }, - { 57600, UART_BAUDRATE_BAUDRATE_Baud57600 }, - { 76800, UART_BAUDRATE_BAUDRATE_Baud76800 }, - { 115200, UART_BAUDRATE_BAUDRATE_Baud115200 }, - { 230400, UART_BAUDRATE_BAUDRATE_Baud230400 }, - { 250000, UART_BAUDRATE_BAUDRATE_Baud250000 }, - { 460800, UART_BAUDRATE_BAUDRATE_Baud460800 }, - { 921600, UART_BAUDRATE_BAUDRATE_Baud921600 }, - { 1000000, UART_BAUDRATE_BAUDRATE_Baud1M } - }; - - if (baudrate <= 1200) { - UART_INSTANCE->BAUDRATE = UART_BAUDRATE_BAUDRATE_Baud1200; - return; - } + MBED_ASSERT(obj); + +#if DEVICE_SERIAL_ASYNCH + struct serial_s *uart_object = &obj->serial; +#else + struct serial_s *uart_object = obj; +#endif + + /* Configure Tx and Rx pins. */ + nrf_gpio_pin_set(uart_object->tx); + nrf_gpio_cfg_output(uart_object->tx); + nrf_gpio_cfg_input(uart_object->rx, NRF_GPIO_PIN_NOPULL); - int const item_cnt = sizeof(acceptedSpeeds)/sizeof(acceptedSpeeds[0]); - for (int i = 1; i < item_cnt; i++) { - if ((uint32_t)baudrate < acceptedSpeeds[i][0]) { - UART_INSTANCE->BAUDRATE = acceptedSpeeds[i - 1][1]; - return; + nrf_uarte_txrx_pins_set(nordic_nrf5_uart_register[uart_object->instance], + uart_object->tx, + uart_object->rx); + + /* Set hardware flow control pins. */ + if (uart_object->hwfc == NRF_UART_HWFC_ENABLED) { + + /* Check if pin is set before configuring it. */ + if (uart_object->rts != NRF_UART_PSEL_DISCONNECTED) { + + nrf_gpio_cfg_output(uart_object->rts); + } + + /* Check if pin is set before configuring it. */ + if (uart_object->cts != NRF_UART_PSEL_DISCONNECTED) { + + nrf_gpio_cfg_input(uart_object->cts, NRF_GPIO_PIN_NOPULL); } + + nrf_uarte_hwfc_pins_set(nordic_nrf5_uart_register[uart_object->instance], + uart_object->rts, + uart_object->cts); } - UART_INSTANCE->BAUDRATE = UART_BAUDRATE_BAUDRATE_Baud1M; + /* Enable flow control and parity. */ + nrf_uarte_configure(nordic_nrf5_uart_register[uart_object->instance], + uart_object->parity, + uart_object->hwfc); + + /* Set baudrate. */ + nrf_uarte_baudrate_set(nordic_nrf5_uart_register[uart_object->instance], + uart_object->baudrate); } -void serial_format(serial_t *obj, - int data_bits, SerialParity parity, int stop_bits) +/** + * @brief Setup non-asynchronous reception. + * + * @param[in] instance The instance + */ +static void nordic_nrf5_uart_configure_rx(int instance) { - (void)obj; + /* Disable interrupts during confiration. */ + nordic_nrf5_uart_register[instance]->INTEN &= ~(NRF_UARTE_INT_RXSTARTED_MASK | + NRF_UARTE_INT_ENDRX_MASK | + NRF_UARTE_INT_RXDRDY_MASK); - if (data_bits != 8) { - error("UART supports only 8 data bits.\r\n"); - } - if (stop_bits != 1) { - error("UART supports only 1 stop bits.\r\n"); - } - if (parity == ParityNone) { - UART_CB.parity = NRF_UART_PARITY_EXCLUDED; - } else if (parity == ParityEven) { - UART_CB.parity = NRF_UART_PARITY_INCLUDED; - } else { - error("UART supports only even parity.\r\n"); - } + /* Clear FIFO buffer. */ + nrf_atfifo_clear(nordic_nrf5_uart_state[instance].fifo); + + /* Clear Rx related events. */ + nrf_uarte_event_clear(nordic_nrf5_uart_register[instance], NRF_UARTE_EVENT_RXSTARTED); + nrf_uarte_event_clear(nordic_nrf5_uart_register[instance], NRF_UARTE_EVENT_ENDRX); + nrf_uarte_event_clear(nordic_nrf5_uart_register[instance], NRF_UARTE_EVENT_RXDRDY); + + /* Enable shortcut between buffer full and begin reception on next buffer armed. */ + nrf_uarte_shorts_enable(nordic_nrf5_uart_register[instance], NRF_UARTE_SHORT_ENDRX_STARTRX); - // Reconfigure UART peripheral. - nrf_uart_configure(UART_INSTANCE, UART_CB.parity, UART_CB.hwfc); + /* Reset bank flag. */ + nordic_nrf5_uart_state[instance].active_bank = 0; + + /* Arm first DMA buffer. */ + nrf_uarte_rx_buffer_set(nordic_nrf5_uart_register[instance], + nordic_nrf5_uart_state[instance].buffer[0], + DMA_BUFFER_SIZE); + + /* Set non-asynchronous mode. */ + nordic_nrf5_uart_state[instance].rx_asynch = false; + + /* Enable interrupts again. */ + nordic_nrf5_uart_register[instance]->INTEN |= (NRF_UARTE_INT_RXSTARTED_MASK | + NRF_UARTE_INT_ENDRX_MASK | + NRF_UARTE_INT_RXDRDY_MASK); } -void serial_irq_handler(serial_t *obj, uart_irq_handler handler, uint32_t id) +#if DEVICE_SERIAL_ASYNCH +/** + * @brief Setup asynchronous reception. + * + * @param[in] instance The instance + */ +static void nordic_nrf5_uart_configure_rx_asynch(int instance) { - (void)obj; - UART_CB.irq_handler = handler; - UART_CB.irq_context = id; + /* Disable Rx related interrupts. */ + nordic_nrf5_uart_register[instance]->INTEN &= ~(NRF_UARTE_INT_RXSTARTED_MASK | + NRF_UARTE_INT_ENDRX_MASK | + NRF_UARTE_INT_RXDRDY_MASK); + + /* Clear Rx related events. */ + nrf_uarte_event_clear(nordic_nrf5_uart_register[instance], NRF_UARTE_EVENT_RXSTARTED); + nrf_uarte_event_clear(nordic_nrf5_uart_register[instance], NRF_UARTE_EVENT_ENDRX); + nrf_uarte_event_clear(nordic_nrf5_uart_register[instance], NRF_UARTE_EVENT_RXDRDY); + + /* Disable shortcut. Next Rx buffer must be manually started. */ + nrf_uarte_shorts_disable(nordic_nrf5_uart_register[instance], NRF_UARTE_SHORT_ENDRX_STARTRX); + + /* Set asynchronous mode. */ + nordic_nrf5_uart_state[instance].rx_asynch = true; + + /* Enable Rx interrupt. */ + nordic_nrf5_uart_register[instance]->INTEN |= NRF_UARTE_INT_ENDRX_MASK; } +#endif -void serial_irq_set(serial_t *obj, SerialIrq irq, uint32_t enable) +/** + * @brief Main configuration function. + * + * @param obj The serial object + */ +static void nordic_nrf5_serial_configure(serial_t *obj) { - (void)obj; - if (enable) { - switch (irq) { - case RxIrq: - #if DEVICE_SERIAL_ASYNCH - UART_CB.irq_enabled |= UART_IRQ_RX; - #endif - nrf_uart_int_enable(UART_INSTANCE, NRF_UART_INT_MASK_RXDRDY); - break; + MBED_ASSERT(obj); - case TxIrq: - #if DEVICE_SERIAL_ASYNCH - UART_CB.irq_enabled |= UART_IRQ_TX; - #endif - nrf_uart_int_enable(UART_INSTANCE, NRF_UART_INT_MASK_TXDRDY); - break; - } - } else { - switch (irq) { - case RxIrq: - #if DEVICE_SERIAL_ASYNCH - UART_CB.irq_enabled &= ~UART_IRQ_RX; - if (!UART_CB.rx_active) - #endif - { - nrf_uart_int_disable(UART_INSTANCE, - NRF_UART_INT_MASK_RXDRDY); - } - break; +#if DEVICE_SERIAL_ASYNCH + struct serial_s *uart_object = &obj->serial; +#else + struct serial_s *uart_object = obj; +#endif - case TxIrq: - #if DEVICE_SERIAL_ASYNCH - UART_CB.irq_enabled &= ~UART_IRQ_TX; - if (!UART_CB.tx_active) - #endif - { - nrf_uart_int_disable(UART_INSTANCE, - NRF_UART_INT_MASK_TXDRDY); - } - break; + /* Get object instance. */ + int instance = uart_object->instance; + + /* Only configure if instance owner has changed or an update is forced. */ + if ((uart_object != nordic_nrf5_uart_state[instance].owner) || (uart_object->update)) { + + /* Configure common setting. */ + nordic_nrf5_uart_configure_object(obj); + + /* Clear Tx event and enable Tx interrupts. */ + nrf_uarte_event_clear(nordic_nrf5_uart_register[instance], NRF_UARTE_EVENT_ENDTX); + nordic_nrf5_uart_register[instance]->INTEN |= NRF_UARTE_INT_ENDTX_MASK; + + /* Set new owner. */ + nordic_nrf5_uart_state[instance].owner = uart_object; + uart_object->update = false; + +#if DEVICE_SERIAL_ASYNCH + /* Set asynchronous mode. */ + if (uart_object->rx_asynch == true) { + + nordic_nrf5_uart_configure_rx_asynch(instance); + } else +#endif + { + /* Set non-asynchronous mode. */ + nordic_nrf5_uart_configure_rx(instance); + nrf_uarte_task_trigger(nordic_nrf5_uart_register[instance], + NRF_UARTE_TASK_STARTRX); } + } +#if DEVICE_SERIAL_ASYNCH + /* Owner hasn't changed but mode has. Reconfigure. */ + else if ((uart_object->rx_asynch == false) && (nordic_nrf5_uart_state[instance].rx_asynch == true)) { + + nordic_nrf5_uart_configure_rx(instance); + nrf_uarte_task_trigger(nordic_nrf5_uart_register[instance], + NRF_UARTE_TASK_STARTRX); + + /* Owner hasn't changed but mode has. Reconfigure. */ + } else if ((uart_object->rx_asynch == true) && (nordic_nrf5_uart_state[instance].rx_asynch == false)) { + + nordic_nrf5_uart_configure_rx_asynch(instance); } +#endif } -int serial_getc(serial_t *obj) +/*** + * __ __ _ _ _ _ _ _____ _____ + * | \/ | | | | | | | | /\ | | /\ | __ \_ _| + * | \ / | |__ ___ __| | | |__| | / \ | | / \ | |__) || | + * | |\/| | '_ \ / _ \/ _` | | __ | / /\ \ | | / /\ \ | ___/ | | + * | | | | |_) | __/ (_| | | | | |/ ____ \| |____ / ____ \| | _| |_ + * |_| |_|_.__/ \___|\__,_| |_| |_/_/ \_\______| /_/ \_\_| |_____| + * + * + */ + +/** Initialize the serial peripheral. It sets the default parameters for serial + * peripheral, and configures its specifieds pins. + * + * Param obj The serial object + * Param tx The TX pin name + * Param rx The RX pin name + */ +void serial_init(serial_t *obj, PinName tx, PinName rx) { - while (!serial_readable(obj)) { + MBED_ASSERT(obj); + +#if DEVICE_SERIAL_ASYNCH + struct serial_s *uart_object = &obj->serial; +#else + struct serial_s *uart_object = obj; +#endif + + /* Only initialize on first call. */ + static bool first_init = true; + if (first_init) { + first_init = false; + + /* Register RTC2 ISR. */ + NVIC_SetVector(RTC2_IRQn, (uint32_t) nordic_nrf5_rtc2_handler); + + /* Clear RTC2 channel events. */ + nrf_rtc_event_clear(NRF_RTC2, NRF_RTC_EVENT_COMPARE_0); + nrf_rtc_event_clear(NRF_RTC2, NRF_RTC_EVENT_COMPARE_1); + nrf_rtc_event_clear(NRF_RTC2, NRF_RTC_EVENT_COMPARE_2); + nrf_rtc_event_clear(NRF_RTC2, NRF_RTC_EVENT_COMPARE_3); + + /* Enable interrupts for all four RTC2 channels. */ + nrf_rtc_event_enable(NRF_RTC2, + NRF_RTC_INT_COMPARE0_MASK | + NRF_RTC_INT_COMPARE1_MASK | + NRF_RTC_INT_COMPARE2_MASK | + NRF_RTC_INT_COMPARE3_MASK); + + /* Enable RTC2 IRQ. Priority is set to lowest so that the UARTE ISR can interrupt it. */ + nrf_drv_common_irq_enable(RTC2_IRQn, APP_IRQ_PRIORITY_LOWEST); + + /* Start RTC2. According to the datasheet the added power consumption is neglible so + * the RTC2 will run forever. + */ + nrf_rtc_task_trigger(NRF_RTC2, NRF_RTC_TASK_START); + + /* Initialize FIFO buffer for UARTE0. */ + NRF_ATFIFO_INIT(nordic_nrf5_uart_fifo_0); + nordic_nrf5_uart_state[0].fifo = nordic_nrf5_uart_fifo_0; + + /* Initialize owner to NULL. */ + nordic_nrf5_uart_state[0].owner = NULL; + + /* Enable interrupts for UARTE0. */ + NVIC_SetVector(UARTE0_UART0_IRQn, (uint32_t) nordic_nrf5_uart0_handler); + nordic_nrf5_uart_irq_enable(0); + nrf_uarte_enable(nordic_nrf5_uart_register[0]); + +#if UART1_ENABLED + /* Initialize FIFO buffer for UARTE1. */ + NRF_ATFIFO_INIT(nordic_nrf5_uart_fifo_1); + nordic_nrf5_uart_state[1].fifo = nordic_nrf5_uart_fifo_1; + + /* Initialize owner to NULL. */ + nordic_nrf5_uart_state[1].owner = NULL; + + /* Enable interrupts for UARTE1. */ + NVIC_SetVector(UARTE1_IRQn, (uint32_t) nordic_nrf5_uart1_handler); + nordic_nrf5_uart_irq_enable(1); + nrf_uarte_enable(nordic_nrf5_uart_register[1]); +#endif } - nrf_uart_event_clear(UART_INSTANCE, NRF_UART_EVENT_RXDRDY); - return nrf_uart_rxd_get(UART_INSTANCE); + /* Get instance ID based on provided pins. */ + int instance = pin_instance_uart(tx, rx); + + uart_object->instance = instance; + + /* Store pins in serial object. */ + if (tx == NC) { + + uart_object->tx = NRF_UART_PSEL_DISCONNECTED; + } else { + + uart_object->tx = tx; + } + + if (rx == NC) { + + uart_object->rx = NRF_UART_PSEL_DISCONNECTED; + } else { + + uart_object->rx = rx; + } + + /* Set default parity and baud rate. */ + uart_object->parity = NRF_UART_PARITY_EXCLUDED; + uart_object->baudrate = NRF_UART_BAUDRATE_9600; + + /** + * If the provided pins match STDOUT and the default flow control is enabled, + * enable flow control using STDOUT's flow control pins. + */ + if (MBED_CONF_NORDIC_UART_HWFC && (tx == STDIO_UART_TX) && (rx == STDIO_UART_RX)) { + + uart_object->cts = CTS_PIN_NUMBER; + uart_object->rts = RTS_PIN_NUMBER; + uart_object->hwfc = NRF_UART_HWFC_ENABLED; + + } else { + + uart_object->cts = NRF_UART_PSEL_DISCONNECTED; + uart_object->rts = NRF_UART_PSEL_DISCONNECTED; + uart_object->hwfc = NRF_UART_HWFC_DISABLED; + } + + /* Initializing the serial object does not make it the owner of an instance. + * Only when the serial object is being used will the object take ownership + * over the instance. + */ + uart_object->update = true; + uart_object->handler = 0; +} + +/** Release the serial peripheral, not currently invoked. It requires further + * resource management. + * + * Param obj The serial object + */ +void serial_free(serial_t *obj) +{ + } -void serial_putc(serial_t *obj, int c) +/** Configure the baud rate + * + * Param obj The serial object + * Param baudrate The baud rate to be configured + */ +void serial_baud(serial_t *obj, int baudrate) { - while (!serial_writable(obj)) { + MBED_ASSERT(obj); + +#if DEVICE_SERIAL_ASYNCH + struct serial_s *uart_object = &obj->serial; +#else + struct serial_s *uart_object = obj; +#endif + + nrf_uart_baudrate_t new_rate = NRF_UART_BAUDRATE_9600; + + /* Round down to nearest supported baud rate. */ + if (baudrate < 2400) { + new_rate = NRF_UARTE_BAUDRATE_1200; + } else if (baudrate < 4800) { + new_rate = NRF_UARTE_BAUDRATE_2400; + } else if (baudrate < 9600) { + new_rate = NRF_UARTE_BAUDRATE_4800; + } else if (baudrate < 14400) { + new_rate = NRF_UARTE_BAUDRATE_9600; + } else if (baudrate < 19200) { + new_rate = NRF_UARTE_BAUDRATE_14400; + } else if (baudrate < 28800) { + new_rate = NRF_UARTE_BAUDRATE_19200; + } else if (baudrate < 38400) { + new_rate = NRF_UARTE_BAUDRATE_28800; + } else if (baudrate < 57600) { + new_rate = NRF_UARTE_BAUDRATE_38400; + } else if (baudrate < 76800) { + new_rate = NRF_UARTE_BAUDRATE_57600; + } else if (baudrate < 115200) { + new_rate = NRF_UARTE_BAUDRATE_76800; + } else if (baudrate < 230400) { + new_rate = NRF_UARTE_BAUDRATE_115200; + } else if (baudrate < 250000) { + new_rate = NRF_UARTE_BAUDRATE_230400; + } else if (baudrate < 460800) { + new_rate = NRF_UARTE_BAUDRATE_250000; + } else if (baudrate < 921600) { + new_rate = NRF_UARTE_BAUDRATE_460800; + } else if (baudrate < 1000000) { + new_rate = NRF_UARTE_BAUDRATE_921600; + } else { + new_rate = NRF_UARTE_BAUDRATE_1000000; } - nrf_uart_event_clear(UART_INSTANCE, NRF_UART_EVENT_TXDRDY); - nrf_uart_txd_set(UART_INSTANCE, (uint8_t)c); + /* Force reconfiguration next time serial object is owner if baud rate has changed. */ + if (uart_object->baudrate != new_rate) { + + uart_object->baudrate = new_rate; + uart_object->update = true; + } } -int serial_readable(serial_t *obj) +/** Configure the format. Set the number of bits, parity and the number of stop bits + * + * Param obj The serial object + * Param data_bits The number of data bits + * Param parity The parity + * Param stop_bits The number of stop bits + */ +void serial_format(serial_t *obj, int data_bits, SerialParity parity, int stop_bits) { - (void)obj; + MBED_ASSERT(obj); + + /** + * Only 8-bit mode, None/Even parity, and 1 stop bit supported by hardware. + */ + MBED_ASSERT(data_bits == 8); + MBED_ASSERT((parity == ParityNone) || (parity == ParityEven)); + MBED_ASSERT(stop_bits == 1); + #if DEVICE_SERIAL_ASYNCH - if (UART_CB.rx_active) { - return 0; + struct serial_s *uart_object = &obj->serial; +#else + struct serial_s *uart_object = obj; +#endif + + /** + * Only force change if parity has changed. + */ + if ((uart_object->parity != NRF_UART_PARITY_EXCLUDED) && (parity == ParityNone)) { + + uart_object->parity = NRF_UART_PARITY_EXCLUDED; + uart_object->update = true; + + } else if ((uart_object->parity != NRF_UART_PARITY_INCLUDED) && (parity == ParityEven)) { + + uart_object->parity = NRF_UART_PARITY_INCLUDED; + uart_object->update = true; } +} + +/** Configure the serial for the flow control. It sets flow control in the hardware + * if a serial peripheral supports it, otherwise software emulation is used. + * + * Param obj The serial object + * Param type The type of the flow control. Look at the available FlowControl types. + * Param rxflow The TX pin name + * Param txflow The RX pin name + */ +void serial_set_flow_control(serial_t *obj, FlowControl type, PinName rxflow, PinName txflow) +{ + MBED_ASSERT(obj); + +#if DEVICE_SERIAL_ASYNCH + struct serial_s *uart_object = &obj->serial; +#else + struct serial_s *uart_object = obj; #endif - return (nrf_uart_event_check(UART_INSTANCE, NRF_UART_EVENT_RXDRDY)); + + /** + * Convert Mbed pin names to Nordic pin names. + */ + uart_object->cts = ((txflow == NC) || (type == FlowControlRTS)) ? NRF_UART_PSEL_DISCONNECTED : (uint32_t) txflow; + uart_object->rts = ((rxflow == NC) || (type == FlowControlCTS)) ? NRF_UART_PSEL_DISCONNECTED : (uint32_t) rxflow; + uart_object->hwfc = (type == FlowControlNone) ? NRF_UART_HWFC_DISABLED : NRF_UART_HWFC_ENABLED; + + /* Force reconfiguration next time object is owner. */ + uart_object->update = true; } -int serial_writable(serial_t *obj) +/** Clear the serial peripheral + * + * Param obj The serial object + */ +void serial_clear(serial_t *obj) { - (void)obj; + MBED_ASSERT(obj); + #if DEVICE_SERIAL_ASYNCH - if (UART_CB.tx_active) { - return 0; - } + struct serial_s *uart_object = &obj->serial; +#else + struct serial_s *uart_object = obj; #endif - return (nrf_uart_event_check(UART_INSTANCE, NRF_UART_EVENT_TXDRDY)); + + /** + * Reconfigure UART. + */ + uart_object->update = true; + nordic_nrf5_uart_configure_object(obj); } +/** Set the break + * + * Param obj The serial object + */ void serial_break_set(serial_t *obj) { - (void)obj; - nrf_uart_task_trigger(UART_INSTANCE, NRF_UART_TASK_SUSPEND); - nrf_uart_txrx_pins_disconnect(UART_INSTANCE); - nrf_gpio_pin_clear(UART_CB.pseltxd); + MBED_ASSERT(obj); + +#if DEVICE_SERIAL_ASYNCH + struct serial_s *uart_object = &obj->serial; +#else + struct serial_s *uart_object = obj; +#endif + + /* Set Tx pin low. */ + nrf_gpio_pin_clear(uart_object->tx); } +/** Clear the break + * + * Param obj The serial object + */ void serial_break_clear(serial_t *obj) { - (void)obj; - nrf_gpio_pin_set(UART_CB.pseltxd); - nrf_uart_txrx_pins_set(UART_INSTANCE, UART_CB.pseltxd, UART_CB.pselrxd); - nrf_uart_task_trigger(UART_INSTANCE, NRF_UART_TASK_STARTRX); - nrf_uart_task_trigger(UART_INSTANCE, NRF_UART_TASK_STARTTX); + MBED_ASSERT(obj); + +#if DEVICE_SERIAL_ASYNCH + struct serial_s *uart_object = &obj->serial; +#else + struct serial_s *uart_object = obj; +#endif + + /* Set Tx pin high (default). */ + nrf_gpio_pin_set(uart_object->tx); +} + +/** Configure the TX pin for UART function. + * + * Param tx The pin name used for TX + */ +void serial_pinout_tx(PinName tx) +{ + /** + * Legacy API. Not used by Mbed. + */ + MBED_ASSERT(0); } +/*** + * _____ _ _ _____ _____ + * / ____(_) | | /\ | __ \_ _| + * | (___ _ _ __ ___ _ __ | | ___ / \ | |__) || | + * \___ \| | '_ ` _ \| '_ \| |/ _ \ / /\ \ | ___/ | | + * ____) | | | | | | | |_) | | __/ / ____ \| | _| |_ + * |_____/|_|_| |_| |_| .__/|_|\___| /_/ \_\_| |_____| + * | | + * |_| + */ -static void internal_set_hwfc(FlowControl type, - PinName rxflow, PinName txflow) +/** The serial interrupt handler registration + * + * Param obj The serial object + * Param handler The interrupt handler which will be invoked when the interrupt fires + * Param id The SerialBase object + */ +void serial_irq_handler(serial_t *obj, uart_irq_handler handler, uint32_t id) { - UART_CB.pselrts = - ((rxflow == NC) || (type == FlowControlCTS)) ? NRF_UART_PSEL_DISCONNECTED : (uint32_t)rxflow; - UART_CB.pselcts = - ((txflow == NC) || (type == FlowControlRTS)) ? NRF_UART_PSEL_DISCONNECTED : (uint32_t)txflow; + MBED_ASSERT(obj); - if (UART_CB.pselrts != NRF_UART_PSEL_DISCONNECTED) { - nrf_gpio_pin_set(UART_CB.pselrts); - nrf_gpio_cfg_output(UART_CB.pselrts); - } - if (UART_CB.pselcts != NRF_UART_PSEL_DISCONNECTED) { - nrf_gpio_cfg_input(UART_CB.pselcts, NRF_GPIO_PIN_NOPULL); +#if DEVICE_SERIAL_ASYNCH + struct serial_s *uart_object = &obj->serial; +#else + struct serial_s *uart_object = obj; +#endif + + /* Store handler and ID in serial object. */ + uart_object->handler = (uint32_t) handler; + uart_object->context = id; +} + +/** Configure serial interrupt. This function is used for word-approach + * + * Param obj The serial object + * Param irq The serial IRQ type (RX or TX) + * Param enable Set to non-zero to enable events, or zero to disable them + */ +void serial_irq_set(serial_t *obj, SerialIrq irq, uint32_t enable) +{ + MBED_ASSERT(obj); + +#if DEVICE_SERIAL_ASYNCH + struct serial_s *uart_object = &obj->serial; +#else + struct serial_s *uart_object = obj; +#endif + + /* Convert Mbed type to Nordic IRQ mask. */ + uint32_t type = (irq == TxIrq) ? NORDIC_TX_IRQ : NORDIC_RX_IRQ; + + /* Enable/disable interrupt bit mask. */ + if (enable) { + + uart_object->mask |= type; + + } else { + + uart_object->mask &= ~type; } - - UART_CB.hwfc = (nrf_uart_hwfc_t)((type == FlowControlNone)? NRF_UART_HWFC_DISABLED : UART_DEFAULT_CONFIG_HWFC); - - nrf_uart_configure(UART_INSTANCE, UART_CB.parity, UART_CB.hwfc); - nrf_uart_hwfc_pins_set(UART_INSTANCE, UART_CB.pselrts, UART_CB.pselcts); } -void serial_set_flow_control(serial_t *obj, FlowControl type, - PinName rxflow, PinName txflow) +/** Get character. This is a blocking call, waiting for a character + * + * Param obj The serial object + */ +int serial_getc(serial_t *obj) { - (void)obj; - - nrf_uart_disable(UART_INSTANCE); - internal_set_hwfc(type, rxflow, txflow); - nrf_uart_enable(UART_INSTANCE); + MBED_ASSERT(obj); + +#if DEVICE_SERIAL_ASYNCH + struct serial_s *uart_object = &obj->serial; + uart_object->rx_asynch = false; +#else + struct serial_s *uart_object = obj; +#endif + + int instance = uart_object->instance; + + /* Take ownership and configure UART if necessary. */ + nordic_nrf5_serial_configure(obj); + + /** + * Use head and tail pointer in FIFO to determine whether there is data available. + */ + nrf_atfifo_t *fifo = nordic_nrf5_uart_state[instance].fifo; + + volatile uint16_t *head = &fifo->head.pos.rd; + volatile uint16_t *tail = &fifo->tail.pos.rd; + + /* serial_getc is a blocking call. */ + while (*head == *tail); + + /* Get 1 byte from FIFO buffer. The buffer is atomic + * and doesn't need to be protected in a critical section. + */ + nrf_atfifo_item_get_t context; + uint8_t *byte = (uint8_t *) nrf_atfifo_item_get(fifo, &context); + nrf_atfifo_item_free(fifo, &context); + + return *byte; } +/** Send a character. This is a blocking call, waiting for a peripheral to be available + * for writing + * + * Param obj The serial object + * Param c The character to be sent + */ +void serial_putc(serial_t *obj, int character) +{ + MBED_ASSERT(obj); + +#if DEVICE_SERIAL_ASYNCH + struct serial_s *uart_object = &obj->serial; +#else + struct serial_s *uart_object = obj; +#endif + + int instance = uart_object->instance; + + /** + * tx_in_progress acts like a mutex to ensure only one transmission can be active at a time. + * The flag is modified using the atomic compare-and-set function. + */ + bool mutex = false; + + do { + uint8_t expected = 0; + uint8_t desired = 1; + + mutex = core_util_atomic_cas_u8((uint8_t *) &nordic_nrf5_uart_state[instance].tx_in_progress, &expected, desired); + } while (mutex == false); + + /* Take ownership and configure UART if necessary. */ + nordic_nrf5_serial_configure(obj); -void serial_clear(serial_t *obj) { - (void)obj; + /* Arm Tx DMA buffer. */ + nordic_nrf5_uart_state[instance].tx_data = character; + nrf_uarte_tx_buffer_set(nordic_nrf5_uart_register[instance], + &nordic_nrf5_uart_state[instance].tx_data, + 1); + + /* Trigger DMA transfer. */ + nrf_uarte_task_trigger(nordic_nrf5_uart_register[instance], + NRF_UARTE_TASK_STARTTX); } +/** Check if the serial peripheral is readable + * + * Param obj The serial object + * Return Non-zero value if a character can be read, 0 if nothing to read + */ +int serial_readable(serial_t *obj) +{ + MBED_ASSERT(obj); + #if DEVICE_SERIAL_ASYNCH + struct serial_s *uart_object = &obj->serial; + uart_object->rx_asynch = false; +#else + struct serial_s *uart_object = obj; +#endif -int serial_tx_asynch(serial_t *obj, const void *tx, size_t tx_length, - uint8_t tx_width, uint32_t handler, uint32_t event, - DMAUsage hint) + int instance = uart_object->instance; + + /* Take ownership and configure UART if necessary. */ + nordic_nrf5_serial_configure(obj); + + /** + * Use head and tail pointer in FIFO to determine whether there is data available. + */ + nrf_atfifo_t *fifo = nordic_nrf5_uart_state[instance].fifo; + + return (fifo->head.pos.rd != fifo->tail.pos.rd); +} + +/** Check if the serial peripheral is writable + * + * Param obj The serial object + * Return Non-zero value if a character can be written, 0 otherwise. + */ +int serial_writable(serial_t *obj) { - (void)obj; - (void)tx_width; - (void)hint; - if (UART_CB.tx_active || !tx_length) { - return 0; - } + MBED_ASSERT(obj); - UART_CB.tx_buffer = tx; - UART_CB.tx_length = tx_length; - UART_CB.tx_pos = 0; - UART_CB.tx_asynch_handler = (void(*)())handler; - UART_CB.events_wanted &= ~SERIAL_EVENT_TX_ALL; - UART_CB.events_wanted |= event; +#if DEVICE_SERIAL_ASYNCH + struct serial_s *uart_object = &obj->serial; +#else + struct serial_s *uart_object = obj; +#endif - UART_CB.tx_active = true; - nrf_uart_int_enable(UART_INSTANCE, NRF_UART_INT_MASK_TXDRDY); + int instance = uart_object->instance; - return 0; + return (nordic_nrf5_uart_state[instance].tx_in_progress == 0); } -void serial_rx_asynch(serial_t *obj, void *rx, size_t rx_length, - uint8_t rx_width, uint32_t handler, uint32_t event, - uint8_t char_match, DMAUsage hint) +/*** + * _ _____ _____ + * /\ | | /\ | __ \_ _| + * / \ ___ _ _ _ __ ___| |__ _ __ ___ _ __ ___ _ _ ___ / \ | |__) || | + * / /\ \ / __| | | | '_ \ / __| '_ \| '__/ _ \| '_ \ / _ \| | | / __| / /\ \ | ___/ | | + * / ____ \\__ \ |_| | | | | (__| | | | | | (_) | | | | (_) | |_| \__ \ / ____ \| | _| |_ + * /_/ \_\___/\__, |_| |_|\___|_| |_|_| \___/|_| |_|\___/ \__,_|___/ /_/ \_\_| |_____| + * __/ | + * |___/ + */ + +#if DEVICE_SERIAL_ASYNCH + +/** Begin asynchronous TX transfer. The used buffer is specified in the serial object, + * tx_buff + * + * Param obj The serial object + * Param tx The transmit buffer + * Param tx_length The number of bytes to transmit + * Param tx_width Deprecated argument + * Param handler The serial handler + * Param event The logical OR of events to be registered + * Param hint A suggestion for how to use DMA with this transfer + * Return Returns number of data transfered, otherwise returns 0 + */ +int serial_tx_asynch(serial_t *obj, const void *tx, size_t tx_length, uint8_t tx_width, uint32_t handler, uint32_t mask, DMAUsage hint) { - (void)obj; - (void)rx_width; - (void)hint; - if (UART_CB.rx_active || !rx_length) { - return; + MBED_ASSERT(obj); + MBED_ASSERT(tx_width == 8); + MBED_ASSERT(tx_length < 256); + + int instance = obj->serial.instance; + + /** + * tx_in_progress acts like a mutex to ensure only one transmission can be active at a time. + * The flag is modified using the atomic compare-and-set function. + */ + bool mutex = false; + + do { + uint8_t expected = 0; + uint8_t desired = 1; + + mutex = core_util_atomic_cas_u8((uint8_t *) &nordic_nrf5_uart_state[instance].tx_in_progress, &expected, desired); + } while (mutex == false); + + /* State variables. */ + int result = 0; + bool valid = false; + + /** + * EasyDMA can only access RAM. Check if provided buffer is in RAM or flash. + * If the buffer is in flash, check if the FIFO buffer is large enough to store + * the Tx data. + */ + if (instance == 0) { + + if (nrf_drv_is_in_RAM(tx) || (tx_length <= UART0_FIFO_BUFFER_SIZE)) { + valid = true; + } + } +#if UART1_ENABLED + else { + if (nrf_drv_is_in_RAM(tx) || (tx_length <= UART1_FIFO_BUFFER_SIZE)) { + valid = true; + } } +#endif + + if (valid) { + + /* Setup buffers for transfer. */ + uint8_t *buffer = NULL; + + /* Tx buffer is in RAM. */ + if (nrf_drv_is_in_RAM(tx)) { + + buffer = (uint8_t *) tx; + } else { + + /** + * Tx buffer is in flash. Copy Tx buffer to FIFO buffer. + * NOTE: this prevents simultaneous Rx using non-asynchronous API. + */ + const uint8_t *pointer = (const uint8_t *) tx; + + for (size_t index = 0; index < tx_length; index++) { + nordic_nrf5_uart_fifo_0_data[index] = pointer[index]; + } + + buffer = (uint8_t *) nordic_nrf5_uart_fifo_0_data; + } + + /* Store callback handler, mask and reset event value. */ + obj->serial.tx_handler = handler; + obj->serial.tx_mask = mask; + obj->serial.tx_event = 0; - UART_CB.rx_buffer = rx; - UART_CB.rx_length = rx_length; - UART_CB.rx_pos = 0; - UART_CB.rx_asynch_handler = (void(*)())handler; - UART_CB.events_wanted &= ~SERIAL_EVENT_RX_ALL; - UART_CB.events_wanted |= event; - UART_CB.char_match = char_match; + /* Enable asynchronous mode and configure UART. */ + nordic_nrf5_uart_state[instance].tx_asynch = true; + nordic_nrf5_serial_configure(obj); - UART_CB.rx_active = true; - nrf_uart_int_enable(UART_INSTANCE, NRF_UART_INT_MASK_RXDRDY); + /* Set Tx DMA buffer. */ + nrf_uarte_tx_buffer_set(nordic_nrf5_uart_register[obj->serial.instance], + buffer, + tx_length); + + /* Trigger DMA transfer. */ + nrf_uarte_task_trigger(nordic_nrf5_uart_register[obj->serial.instance], + NRF_UARTE_TASK_STARTTX); + + /* Setup complete, return length as sign of success. */ + result = tx_length; + + } else { + + /* Signal error if event mask matches and event handler is set. */ + if ((mask & SERIAL_EVENT_ERROR) && handler) { + + /* Cast handler to callback function pointer. */ + void (*callback)(void) = (void (*)(void)) handler; + + /* Store event value so it can be read back. */ + obj->serial.event = SERIAL_EVENT_ERROR; + + /* Signal callback handler. */ + callback(); + } + } + + return result; } +/** Begin asynchronous RX transfer (enable interrupt for data collecting) + * The used buffer is specified in the serial object - rx_buff + * + * Param obj The serial object + * Param rx The receive buffer + * Param rx_length The number of bytes to receive + * Param rx_width Deprecated argument + * Param handler The serial handler + * Param event The logical OR of events to be registered + * Param handler The serial handler + * Param char_match A character in range 0-254 to be matched + * Param hint A suggestion for how to use DMA with this transfer + */ +void serial_rx_asynch(serial_t *obj, void *rx, size_t rx_length, uint8_t rx_width, uint32_t handler, uint32_t mask, uint8_t char_match, DMAUsage hint) +{ + MBED_ASSERT(obj); + MBED_ASSERT(rx_width == 8); + MBED_ASSERT(rx_length < 256); + MBED_ASSERT(char_match == SERIAL_RESERVED_CHAR_MATCH); + + int instance = obj->serial.instance; + + /** + * rx_in_progress acts like a mutex to ensure only one asynchronous reception can be active at a time. + * The flag is modified using the atomic compare-and-set function. + */ + bool mutex = false; + + do { + uint8_t expected = 0; + uint8_t desired = 1; + + mutex = core_util_atomic_cas_u8((uint8_t *) &nordic_nrf5_uart_state[instance].rx_in_progress, &expected, desired); + } while (mutex == false); + + /* Store callback handler, mask and reset event value. */ + obj->serial.rx_handler = handler; + obj->serial.rx_mask = mask; + obj->serial.rx_event = 0; + + /* Enable asynchronous mode and configure UART. */ + obj->serial.rx_asynch = true; + nordic_nrf5_serial_configure(obj); + + /* Set Rx DMA buffer. */ + nrf_uarte_rx_buffer_set(nordic_nrf5_uart_register[instance], + (uint8_t *) rx, + rx_length); + + /* Enable reception. */ + nrf_uarte_task_trigger(nordic_nrf5_uart_register[instance], + NRF_UARTE_TASK_STARTRX); + +} + +/** Attempts to determine if the serial peripheral is already in use for TX + * + * Param obj The serial object + * Return Non-zero if the RX transaction is ongoing, 0 otherwise + */ uint8_t serial_tx_active(serial_t *obj) { - (void)obj; - return UART_CB.tx_active; + MBED_ASSERT(obj); + + return nordic_nrf5_uart_state[obj->serial.instance].tx_asynch; } +/** Attempts to determine if the serial peripheral is already in use for RX + * + * Param obj The serial object + * Return Non-zero if the RX transaction is ongoing, 0 otherwise + */ uint8_t serial_rx_active(serial_t *obj) { - (void)obj; - return UART_CB.rx_active; + MBED_ASSERT(obj); + + return nordic_nrf5_uart_state[obj->serial.instance].rx_asynch; } +/** The asynchronous TX and RX handler. + * + * Param obj The serial object + * Return Returns event flags if an RX transfer termination condition was met; otherwise returns 0 + */ int serial_irq_handler_asynch(serial_t *obj) { - (void)obj; - uint32_t events_to_report = UART_CB.events_wanted & UART_CB.events_occured; - UART_CB.events_occured &= (~events_to_report); - return events_to_report; + MBED_ASSERT(obj); + + return (obj->serial.tx_event | obj->serial.rx_event); } +/** Abort the ongoing TX transaction. It disables the enabled interupt for TX and + * flushes the TX hardware buffer if TX FIFO is used + * + * Param obj The serial object + */ void serial_tx_abort_asynch(serial_t *obj) { - (void)obj; - end_asynch_tx(); - UART_CB.tx_asynch_handler = NULL; + MBED_ASSERT(obj); + + /* Transmission might be in progress. Disable interrupts to prevent ISR from firing. */ + core_util_critical_section_enter(); + + /* Reset Tx flags. */ + nordic_nrf5_uart_state[obj->serial.instance].tx_in_progress = 0; + nordic_nrf5_uart_state[obj->serial.instance].tx_asynch = false; + + /* Force reconfiguration. */ + obj->serial.update = true; + nordic_nrf5_serial_configure(obj); + + /* Trigger STOP task. */ + nrf_uarte_task_trigger(nordic_nrf5_uart_register[obj->serial.instance], + NRF_UARTE_TASK_STOPTX); + + /* Enable interrupts again. */ + core_util_critical_section_exit(); } +/** Abort the ongoing RX transaction. It disables the enabled interrupt for RX and + * flushes the RX hardware buffer if RX FIFO is used + * + * Param obj The serial object + */ void serial_rx_abort_asynch(serial_t *obj) { - (void)obj; - end_asynch_rx(); - UART_CB.rx_asynch_handler = NULL; + MBED_ASSERT(obj); + + /* Transmission might be in progress. Disable interrupts to prevent ISR from firing. */ + core_util_critical_section_enter(); + + /* Reset Rx flags. */ + nordic_nrf5_uart_state[obj->serial.instance].rx_in_progress = 0; + nordic_nrf5_uart_state[obj->serial.instance].rx_asynch = false; + obj->serial.rx_asynch = false; + + /* Force reconfiguration. */ + obj->serial.update = true; + nordic_nrf5_serial_configure(obj); + + /* Enable interrupts again. */ + core_util_critical_section_exit(); } #endif // DEVICE_SERIAL_ASYNCH