Dialog DA14531 — UART

Universal Asynchronous Receiver-Transmitter is a peripheral used for asynchronous serial communication. Most modern microcontrollers have 1 or more UART peripherals. UART can also be found as standalone ICs.

As the name says, UART handles the sending and receiving of data serially. On the transmit side, a UART must create the data packet — appending sync and parity bits — and send that packet out the TX line with precise timing (according to the set baud rate). On the receive end, the UART has to sample the RX line at rates according to the expected baud rate, pick out the sync bits, and spit out the data

Internal block diagram of the ST16C550 UART IC

The DA14531 has 2 UART peripherals — UART and UART2. Both UART peripherals have the following features:

  • 16-byte transmit and receive FIFOs
  • Shadow registers to reduce software overhead and a software programmable reset is included
  • Transmitter Holding Register Empty (THRE) interrupt mode
  • Functionality based on the 16550 industry standard
  • Programmable serial data baud rate

Hardware Flow control(RTS/CTS) is available only on UART and not on the UART2.

Project setup

The project with the code for this tutorial is available on Github at https://github.com/vicara-hq/da14531-tutorials

Download the project and copy it. The project has to be placed inside the Dialog SDK6 folder. Navigate to <SDK6_ROOT>/projects/target_apps/template and paste it in this folder. The project is a modified version of the empty_peripheral_template project provided by Dialog. But to keep this tutorial series as open source as possible, all the following steps will use the SmartSnippets Studio.

Hardware overview

We will use a DA145xx Pro motherboard with a DA14531 Tiny modules daughterboard. Ensure that the URX and UTX pins on J1 has jumpers which connect it to their respective adjacent pins. This allow us to receive and send the UART data over USB.

Code Overview

The aim of this tutorial is to write code to receive and send data. We will first print a string, read 5 bytes and then print it back.

You will need a serial monitor application for this tutorial. You can use RealTerm on windows.

First step is to configure the pins we will use for RX and TX in the user_periph_setup.c file.

void GPIO_reservations(void) {
 RESERVE_GPIO(UART_TX, GPIO_PORT_0, GPIO_PIN_6, PID_UART2_TX);
 RESERVE_GPIO(UART_RX, GPIO_PORT_0, GPIO_PIN_5, PID_UART2_RX);
}void set_pad_functions(void) {
 GPIO_ConfigurePin(GPIO_PORT_0, GPIO_PIN_6, OUTPUT, PID_UART2_TX, false);
 GPIO_ConfigurePin(GPIO_PORT_0, GPIO_PIN_5, INPUT, PID_UART2_RX, false);
}

You can observe that UART is configured and initialized in the the periph_init function.

static const uart_cfg_t uart_cfg = {
     .baud_rate = UART_BAUDRATE_115200,
     .data_bits = UART_DATABITS_8,
     .parity = UART_PARITY_NONE,
     .stop_bits = UART_STOPBITS_1,
     .auto_flow_control = UART_AFCE_DIS,
     .use_fifo = UART_FIFO_DIS,
     .tx_fifo_tr_lvl = UART_TX_FIFO_LEVEL_0,
     .rx_fifo_tr_lvl = UART_RX_FIFO_LEVEL_0,
     .intr_priority = 2,
   };uart_initialize(UART2, & uart_cfg);

We set the baud rate to 115200, use 8 data bits per transmission, 1 stop bit and disabled parity. Since we are using UART2, flow control needs to be disabled.

We start the transmission/reception of data once the MCU has completed all other initialization. We use a custom callback to implement this. For more details, take a look at my tutorial on GPIOs, buttons and LEDs.

#define RX_BUFFER_LENGTH 5
uint8_t uart_rx_buffer[RX_BUFFER_LENGTH] = {0}; static void uart_rx_handler (uint16_t length) {
 char print_str[] = "\r\nChar received = ";
 uart_send(UART2, print_str, strlen(print_str), UART_OP_BLOCKING);
 uart_send(UART2, uart_rx_buffer, RX_BUFFER_LENGTH, UART_OP_BLOCKING);
}void user_app_on_init() {
 char print_str[] = "Enter 5 characters: ";
 default_app_on_init();
 uart_send(UART2, print_str, strlen(print_str), UART_OP_BLOCKING);
 uart_register_rx_cb(UART2, uart_rx_handler);
 uart_receive(UART2, uart_rx_buffer, RX_BUFFER_LENGTH, UART_OP_INTR);
}

UART data can be transmitted and received using 3 mechanisms:

  • Blocking: The microcontroller waits for the transmission or reception to complete before the next instruction is executed. This is a simple implementation but is inefficient and could be stuck for a long time.
  • Interrupt: An interrupt is fired when the reception or transmission is completed and then the processor can step in and handle the data. This requires the MCU to know the number of bytes it’s sending or receiving.
  • DMA: DMA is a complicated topic which is out of the scope of this tutorial. It is very efficient but a bit difficult to use.

We use blocking mode to send data and interrupt mode to receive the data. uart_send takes the UART peripheral, buffer, length and transmission mode as parameters. A string is created and then is passed to it.

To start a receive, uart_receive needs to be called. The interrupt mode is used and we inform the UART peripheral to trigger the interrupt once the buffer is filled.

uart_rx_handler is a callback function which is registered by uart_register_rx_cb. The callback function transmits a string and the data stored in the rx buffer.

Testing

Build the project and start the debugger. Use the following configuration in your serial monitor application.

Setting on RealTerm

When the dev kit is connected, you will notice 2 COM ports with the names “VCP2” and “VCP3”. Use the port associated with “VCP2”.

Sample output