[Getting started with STM32CubeIDE] (3) Configuration and use of USART (DMA)

Table of contents

1. Basic configuration

2. USART configuration

 1, general layout

 2. DMA settings

Three, printf redirection

4. Familiar with USART common functions

 1. Question about Timeout setting

 2. HAL_UART_GetState() function

 3. HAL_UART_Transmit_IT() function

 4. HAL_UART_TxCpltCallback() function

 5. HAL_UART_Receive_IT() function & HAL_UART_RxCpltCallback() function

 6. HAL_UART_Receive_DMA() function

4. Ordinary transceiver mode

 1. Send example

 2. Receive example

5. DMA transceiver mode


        If you don't know how to create a project file, you can refer to an article I wrote before: [Getting Started with STM32CubeIDE] (1) Project Creation & Project Configuration

1. Basic configuration

2. USART configuration

 1, general layout

1. Turn on USARTx (depending on individual needs) and configure it in asynchronous communication mode, and turn on interrupts .

 2. Here you can set the interrupt priority.

 3. The ide has configured the corresponding TX port and RX port, so there is no need to configure it by yourself.

 4. Configure the baud rate, data bits, parity bits, and stop bits according to individual needs . The default is baud rate: 115200, data bit: 8 bits, parity bit: 0 bit, stop bit: 1 bit.

 5. After the relevant code is automatically generated, proceed to the next step of configuration. Project-"Properties, set to allow the serial port to output floating-point numbers.

 2. DMA settings

Three, printf redirection

Add the following lines of code to main.c, and remember to         add #include <  stdio.h> too.

/* USER CODE BEGIN Includes */
#include <stdio.h>
/* USER CODE END Includes */

/* USER CODE BEGIN 4 */
#ifdef __GNUC__
#define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
#else
#define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
#endif

PUTCHAR_PROTOTYPE
{
	HAL_UART_Transmit(&huart1, (uint8_t*)&ch,1,HAL_MAX_DELAY);
    return ch;
}
/* USER CODE END 4 */

4. Familiar with USART common functions

HAL_StatusTypeDef HAL_UART_Transmit(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout)       // 串口发送数据,失败允许阻塞
HAL_StatusTypeDef HAL_UART_Receive(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout)        // 串口接收数据,失败允许阻塞
HAL_StatusTypeDef HAL_UART_Transmit_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)       // 串口中断模式发送,失败不允许阻塞
HAL_StatusTypeDef HAL_UART_Receive_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)        // 串口中断模式接收,失败不允许阻塞
HAL_StatusTypeDef HAL_UART_Transmit_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)      // 串口DMA模式发送
HAL_StatusTypeDef HAL_UART_Receive_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)       // 串口DMA模式接收
HAL_UART_StateTypeDef HAL_UART_GetState(UART_HandleTypeDef *huart)    // 查询串口状态
void HAL_UART_IRQHandler(UART_HandleTypeDef *huart)        // 串口中断处理函数
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)    // 串口发送中断回调函数
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)    // 串口接收中断回调函数
void HAL_UART_ErrorCallback()                              // 串口接收错误函数

typedef enum
{
  HAL_OK       = 0x00U,
  HAL_ERROR    = 0x01U,
  HAL_BUSY     = 0x02U,
  HAL_TIMEOUT  = 0x03U
} HAL_StatusTypeDef;

 1. Question about Timeout setting

        If the baud rate is 9600, it takes 1/9600s = 0.0001042s = 0.1042ms to send a bit. Here, the data bits are 8 bits and the stop bits are 2 bits. The sum is 10 bits. The time required to send 10 bits is : 0.1042 *  10ms = 1.042ms, if I want to send 10 bytes of data, the time required to send these 10 bytes of data to the receiver is: 1.04210ms = 10.42ms.

        This is the time required to actually send 10 bytes of data. We can widen the time when the receiver receives the data, so that it has a little margin . To allow the receiver to take over the data from the sender stably, you can add 5ms, or a wider 10ms, and add the time it takes to send 10 bytes, which is 15ms or 20ms.

        Why should Timeout be set to a suitable time? Because if the setting is too long, if the sending fails, it will be blocked for a long time, which will seriously affect the speed of the system; if the setting time is too short, the sending will be suspended because the data has not been sent, resulting in data loss.

 2. HAL_UART_GetState () function

        Query the status of the serial port.

HAL_UART_StateTypeDef HAL_UART_GetState(UART_HandleTypeDef *huart)

typedef enum
{
  HAL_UART_STATE_RESET             = 0x00U,    /*!< Peripheral is not yet Initialized
                                                   Value is allowed for gState and RxState */
  HAL_UART_STATE_READY             = 0x20U,    /*!< Peripheral Initialized and ready for use
                                                   Value is allowed for gState and RxState */
  HAL_UART_STATE_BUSY              = 0x24U,    /*!< an internal process is ongoing
                                                   Value is allowed for gState only */
  HAL_UART_STATE_BUSY_TX           = 0x21U,    /*!< Data Transmission process is ongoing
                                                   Value is allowed for gState only */
  HAL_UART_STATE_BUSY_RX           = 0x22U,    /*!< Data Reception process is ongoing
                                                   Value is allowed for RxState only */
  HAL_UART_STATE_BUSY_TX_RX        = 0x23U,    /*!< Data Transmission and Reception process is ongoing
                                                   Not to be used for neither gState nor RxState.
                                                   Value is result of combination (Or) between gState and RxState values */
  HAL_UART_STATE_TIMEOUT           = 0xA0U,    /*!< Timeout state
                                                   Value is allowed for gState only */
  HAL_UART_STATE_ERROR             = 0xE0U     /*!< Error
                                                   Value is allowed for gState only */
} HAL_UART_StateTypeDef;

 3. HAL_UART_Transmit_IT() function

HAL_StatusTypeDef HAL_UART_Transmit_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)

        The HAL_UART_TxCpltCallback() send interrupt callback function will be called every time the HAL_UART_Transmit_IT() function is executed.

        The HAL_UART_Transmit_IT() function is mainly used to enable the TXE interrupt .

        Note: If you want to use this function to send data continuously, you need to use a judgment statement to judge whether the serial port is in the ready state (this is also recommended for discontinuous sending, which is safer) (see the sending example of 4 for details )

 4. HAL_UART_TxCpltCallback() function

void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart) 

        The serial port sending interrupt callback function will be called only after the HAL_UART_Transmit_IT() function is executed successfully , and this function cannot be called by using the HAL_UART_Transmit() function or the redirected printf() function .

        Note: Do not execute code that takes too long or is uncertain in this function (continuous sending will cause continuous sending failure, even if a judgment statement is added)

 5. HAL_UART_Receive_IT() function & HAL_UART_RxCpltCallback() function

HAL_StatusTypeDef HAL_UART_Receive_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)

        The HAL_UART_Receive_IT() function is often used in the HAL_UART_RxCpltCallback() serial port receive interrupt callback function, because the interrupt receive function HAL_UART_Receive_IT() can only trigger a receive interrupt once, so we need to call the interrupt receive function again in the interrupt callback function HAL_UART_Receive_IT() HAL_UART_Receive_IT(). (For details, please see the reception example of 4)

        However, it is still necessary to execute the HAL_UART_Receive_IT() function once externally to enable PE, ERR, and RXNE interrupts . (For details, please see the reception example of 4)

Function flow chart

  1. HAL_UART_Receive_IT ( interrupt receive function
  2. USART1_IRQHandler(void) ( interrupt service function )
  3. HAL_UART_IRQHandler(UART_HandleTypeDef *huart) ( interrupt handler )
  4. UART_Receive_IT(UART_HandleTypeDef *huart) ( receive function )
  5. HAL_UART_RxCpltCallback(huart) ( interrupt callback function )

 6. HAL_UART_Receive_DMA() function

        This function calls the UART_Start_Receive_DMA() function, and the following are some things that this function does.

4. Ordinary transceiver mode

 1. Send example

        Note: The following codes are not included in the automatically generated codes, and all of them need to be added! ! !

main.c

int main(void)
{
  // 省略。。。

  /* USER CODE BEGIN 2 */
  uint8_t data[] = "123\n";
  /* USER CODE END 2 */

  while (1)
  {
	  for(int i = 0; i < strlen(data); i++)
	  {
          // 二选一
          // while(HAL_UART_Transmit_IT(&huart1, &data[i], 1) != HAL_OK);
		  while(huart1.gState != HAL_UART_STATE_READY);
	      HAL_UART_Transmit_IT(&huart1, &data[i], 1);
		  HAL_Delay(500);    // 为了让回调函数执行效果更明显
	  }
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

// 串口发送中断回调函数
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)
{
	HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin);
}

 2. Receive example

        Note: The following codes are not included in the automatically generated codes, and all of them need to be added! ! !

 usart.h

/* USER CODE BEGIN Private defines */
typedef struct __USART_RX{
	uint8_t rxbuf[128];    // 数据缓冲区
	uint8_t ch;            // 中断接收缓冲区
	bool finishFlag;       // 接收完成标志
	int rx_buf_idx;        // 缓冲区索引
	UART_HandleTypeDef *huart;    // 串口指针
} stu_usart_rx;
/* USER CODE END Private defines */

usart.c

/* USER CODE BEGIN 0 */
stu_usart_rx usart1_rx;
/* USER CODE END 0 */

void MX_USART1_UART_Init(void)
{
  // 省略。。。

  /* USER CODE BEGIN USART1_Init 2 */
  usart1_rx.huart = &huart1;
  usart1_rx.finishFlag = false;
  usart1_rx.ch = 0;
  usart1_rx.rx_buf_idx = 0;
  /* USER CODE END USART1_Init 2 */
}

// 串口接收中断回调函数
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
    if (huart->Instance == USART1)
    {
    	usart1_rx.rxbuf[usart1_rx.rx_buf_idx++] = usart1_rx.ch;

        if(usart1_rx.rxbuf[usart1_rx.rx_buf_idx - 1] == '\n')    // 接收完毕标志判断
        {
        	usart1_rx.finishFlag = true;
        }
        HAL_UART_Receive_IT(usart1_rx.huart, &usart1_rx.ch, 1);    // 接收成功后还要重新打开中断
    }
}

main.c

/* USER CODE BEGIN PV */
extern stu_usart_rx usart1_rx;
/* USER CODE END PV */

int main(void)
{
  // 省略。。。

  /* USER CODE BEGIN 2 */
  HAL_UART_Receive_IT(usart1_rx.huart, &usart1_rx.ch, 1);    // 打开接收等相关中断
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
	if(usart1_rx.finishFlag == true)    // 将接收好的数据重新发送出去
	{
		HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin);    // 灯闪烁(可删)
		while(usart1_rx.huart->gState != HAL_UART_STATE_READY);  // 等待串口处于准备发送状态
		HAL_UART_Transmit_IT(usart1_rx.huart, (uint8_t *)&usart1_rx.rxbuf, usart1_rx.rx_buf_idx);
		usart1_rx.rx_buf_idx = 0;
		usart1_rx.finishFlag = false;
	}

    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

5. DMA transceiver mode

        What is DMA? Simply put, it means that data can be sent and received without passing through the CPU, thereby improving the efficiency of the CPU.

        When I was looking for an example of how to use serial port + DMA to send and receive, I found that although there are many tutorials on the Internet, I found that many of them were either unusable or had many flaws. After debugging for a long time, my mentality exploded, and I kept experimenting halfway through. Finally, I figured out A working code.

       However, I think that DMA+serial port sending and serial port interrupt receiving will be more commonly used in actual use , because generally the data received by using the serial port will not be too long, and it will not take up too much CPU, but here is still posted DMA sending and receiving code for reference.

        For DMA configuration, please refer to 2. USART configuration.

        Note: The following codes are not included in the automatically generated codes, and all of them need to be added! ! !

usart.h

/* USER CODE BEGIN Includes */
#include <stdarg.h>
#include <stdio.h>
#include <stdbool.h>
/* USER CODE END Includes */

/* USER CODE BEGIN Private defines */
#define BUFFERSIZE 		255				// 缓冲区大小

typedef struct _USART_DMA_ {
	bool 	 recv_end_flag;				// 接收完成标志
	uint8_t  send_buf[BUFFERSIZE];		// 发送缓冲区
	uint8_t  recv_buf[BUFFERSIZE];		// 接收缓冲区
	uint8_t  dma_buf[BUFFERSIZE];		// dma缓冲区
	uint16_t recv_len;					// 接收数据的长度
} stu_usart_DMA;

extern stu_usart_DMA usart1_dma;
/* USER CODE END Private defines */

/* USER CODE BEGIN Prototypes */
void USART1_DMA_Send(uint8_t *buffer, uint16_t length);
void Debug_printf(const char *format, ...);
/* USER CODE END Prototypes */

 usart.c

/* USER CODE BEGIN 0 */
stu_usart_DMA usart1_dma;
/* USER CODE END 0 */

/* USART1 init function */

void MX_USART1_UART_Init(void)
{
  // 省略。。。

  /* USER CODE BEGIN USART1_Init 2 */
  // 开启空闲中断
  __HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE);
  // 打开DMA接收中断
  HAL_UART_Receive_DMA(&huart1, usart1_dma.dma_buf, BUFFERSIZE);
  /* USER CODE END USART1_Init 2 */
}

/* USER CODE BEGIN 1 */
void USART1_DMA_Send(uint8_t *buffer, uint16_t length)
{
    //等待DMA发送通道处于准备状态
	while(HAL_DMA_GetState(&hdma_usart1_tx) != HAL_DMA_STATE_READY);

    //关闭DMA
    //__HAL_DMA_DISABLE(&hdma_usart1_tx);

    //发送数据
    HAL_UART_Transmit_DMA(&huart1, buffer, length);
}

// 重写printf函数
void Debug_printf(const char *format, ...)
{
	uint32_t length = 0;
	va_list args;

	va_start(args, format);
	length = vsnprintf((char*)usart1_dma.send_buf, sizeof(usart1_dma.send_buf), (char*)format, args);
	USART1_DMA_Send(usart1_dma.send_buf, length);
}
/* USER CODE END 1 */

stm32f1xxx_it.c

/* USER CODE BEGIN Includes */
#include "usart.h"
#include <string.h>
/* USER CODE END Includes */

void USART1_IRQHandler(void)
{
  /* USER CODE BEGIN USART1_IRQn 0 */
	uint16_t temp;
	if(huart1.Instance == USART1)
	{
		// 如果串口接收完一帧数据,处于空闲状态(IDLE 中断已置位)
		if(__HAL_UART_GET_FLAG(&huart1, UART_FLAG_IDLE) != RESET)
		{
			// 重置 IDLE 位(读取 SR 和 DR 寄存器后即可重置)
			__HAL_UART_CLEAR_IDLEFLAG(&huart1);
			// 停止 DMA 传输,因为不停止的话拷贝数据起来就会容易造成数据缺失
			HAL_UART_DMAStop(&huart1);
			// 读取 CNDTR 寄存器,获取 DMA 中未传输的数据个数
			temp = __HAL_DMA_GET_COUNTER(&hdma_usart1_rx);
			// 获得接收数据的长度(缓冲区总长度 - 未传输的数据个数)
			usart1_dma.recv_len = BUFFERSIZE - temp;
			// 将已接收到的数据进行拷贝,防止数据覆盖造成丢失
			memcpy(usart1_dma.recv_buf, usart1_dma.dma_buf, usart1_dma.recv_len);
			// 接收完成标志置位
			usart1_dma.recv_end_flag = 1;
			// 因为前面停止了 DMA 传输,现在要重新打开(这个视个人需求而定要不要重新打开)
			while (HAL_UART_Receive_DMA(&huart1, usart1_dma.dma_buf, BUFFERSIZE) != HAL_OK);
		}
	}
  /* USER CODE END USART1_IRQn 0 */
  HAL_UART_IRQHandler(&huart1);
  /* USER CODE BEGIN USART1_IRQn 1 */

  /* USER CODE END USART1_IRQn 1 */
}

main.c

int main(void)
{
  // 省略。。。
  while (1)
  {
	  if(usart1_dma.recv_end_flag == 1)
	  {
		  Debug_printf("接收数据:%s\n", usart1_dma.recv_buf);
		  usart1_dma.recv_end_flag = 0;
		  usart1_dma.recv_len = 0;
		  HAL_GPIO_TogglePin(LED2_GPIO_Port, LED2_Pin);
	  }
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

Guess you like

Origin blog.csdn.net/weixin_48896613/article/details/127426478