STM32 - Using DMA for Serial Receive
Jump to navigation
Jump to search
USART - Using DMA for Serial Receive
With high speed serial data transfer, especially reception, it is critical to get the data out of the receive shift register before another byte is received, else the earlier data will be lost. Early serial modules included FIFOs in the design, allowing data to flow from receive shift register into a FIFO for storage. These STM32 parts we are using DO NOT have FIFOs for data storage. Instead, they rely on DMA to move data out of the receive shift register directly into the client's receive buffer. This document was originally created for the NUCLEO-F103RB, which connects USART2 to the on-board USB-Serial JTAG part.
Configure your project to use DMA with USART2:
1) Open up "Pinout & Configuration" tab (accessible by opening your project's .ioc file) 2) Open up Connectivity -> USART2
3) Select the "DMA Settings" tab, and click "Add" at the bottom of the dialog. 4) Select "USART2_RX" from the pull-down menu 5) Select "Circular" for Mode, Byte Data Width, and "Memory" for Increment Address
Add the following code to main.c, inside USER CODE BEGIN Includes:
/* USER CODE BEGIN Includes */ #include <stdio.h> /* USER CODE END Includes */
Add the following code to main.c, inside USER CODE BEGIN 0
// DMA Buffer for usart2 RX #define USART2_RX_DMA_BUFFER_SIZE 128 uint8_t usart2_rx_dma_buffer[USART2_RX_DMA_BUFFER_SIZE]; #define HAL_SMALL_DELAY 40 // Define serial input and output functions using UART2 int __io_putchar(int ch) { HAL_UART_Transmit(&huart2, (uint8_t *)&ch, 1, HAL_SMALL_DELAY); // infinite delay value return 1; } // Implement a get_byte(void) function that utilizes a circular DMA RX buffer that: // 1) Tests if data is available in the DMA RX buffer // 2) Pulls and returns the data // 3) Manages an index_out value for next removal of data // 4) As a non-blocking function, return EOF when no bytes are available, else returns data byte int __io_getchar(void) { static uint16_t index_out = 0; uint16_t index_in = USART2_RX_DMA_BUFFER_SIZE - huart2.hdmarx->Instance->CNDTR; if(index_in == index_out) return EOF; // Else, we have a byte in buffer to return uint8_t data = usart2_rx_dma_buffer[index_out]; // advance index index_out++; // wrap index when necessary if(index_out >= USART2_RX_DMA_BUFFER_SIZE) index_out = 0; return data; }
Add the following code to main.c, following the MX_USART2_UART_Init() call, inside USER CODE BEGIN 2
/* USER CODE BEGIN 2 */ /* Put UART peripheral in DMA reception mode */ if (HAL_UART_Receive_DMA(&huart2, usart2_rx_dma_buffer, USART2_RX_DMA_BUFFER_SIZE) != HAL_OK) { /* Transfer error in reception process */ printf("HAL_UART_Receive_DMA() error!\n"); }
You can now test your serial receive with the following code in main's while-loop:
/* USER CODE BEGIN WHILE */ while (1) { // Using TeraTerm, paste large text blocks into the terminal. // The DMA assisted receive function won't miss a single character, // and will display all text received. int mybyte = __io_getchar(); if(mybyte < 0) continue; __io_putchar(mybyte); /* USER CODE END WHILE */ /* USER CODE BEGIN 3 */ } /* USER CODE END 3 */ }
Command Line - X-Modem
Once DMA receive was implemented, I returned to a previous project, X-Modem. Now, X-Modem Just Worked! I started with the X-Modem package, xmodem-1k-master.zip, from here: https://github.com/kelvinlawson/xmodem-1k/blob/master/xmodem.c I added code to use STM32 serial interfaces and LittleFS: xmodem.c crc16.c crc16.h