【STM32CubeIDE入门】(三)USART的配置及使用(DMA)

目录

一、基础配置

二、USART 配置

 1、通用配置

 2、DMA 设置

三、printf 重定向

四、熟悉 USART 常用函数

 1、有关 Timeout 设置问题

 2、HAL_UART_GetState() 函数

 3、HAL_UART_Transmit_IT() 函数

 4、HAL_UART_TxCpltCallback() 函数

 5、HAL_UART_Receive_IT() 函数 & HAL_UART_RxCpltCallback() 函数

 6、HAL_UART_Receive_DMA() 函数

四、普通收发模式

 1、发送示例

 2、接收示例

五、DMA 收发模式


        如果不知道如何创建工程文件的可以参考我之前写的一篇文章:【STM32CubeIDE入门】(一)工程创建&工程配置_谢老板不用蟹的博客-CSDN博客

一、基础配置

二、USART 配置

 1、通用配置

1、打开USARTx(具体看个人需求)并配置成异步通信模式,并打开中断

 2、这里可以设置中断优先级。

 3、ide已将对应的 TX口、RX口 配置完毕,不用自行配置。

 4、根据个人需求配置波特率、数据位、校验位、停止位。默认为波特率:115200、数据位:8位、校验位:0位、停止位:1位。

 5、自动生成相关代码后,接着进行下一步的配置。项目-》属性,设置允许串口输出浮点数。

 2、DMA 设置

三、printf 重定向

        在 main.c 中加入下面几行代码,记得将 #include <stdio.h> 也加入进去。

/* 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 */

四、熟悉 USART 常用函数

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、有关 Timeout 设置问题

        如果波特率为 9600,发送一个位需要 1/9600s = 0.0001042s = 0.1042ms,这里按数据位为 8 位,停止位为 2 位,加起来就是 10 位,10 个位发送所需的时间为:0.1042 * 10ms = 1.042ms,如果我要发送 10 个字节的数据,那发送这 10 个字节数据给接收方需要的时间为:1.042 10ms = 10.42ms。

        这是算实际的发送 10 个字节的数据所需要的时间。我们在接收方接收数据时可以把时间再加宽一些,让它有一点余量。让接收方能稳定的把数据从发送方接手过来,可以加个 5ms,或更宽一点 10ms,加上发送 10 个字节所花的时间,就是 15ms 或 20ms。

        为什么要将 Timeout 设置成合适时间呢?因为如果设置太长时间,如果真的发送失败后就会堵塞过长时间,严重影响系统的速度;如果设置的时间过短,会因为数据尚未发送完毕就中止发送了,造成数据的丢失。

 2、HAL_UART_GetState() 函数

        查询串口的状态。

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() 函数

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

        每当执行完成一次 HAL_UART_Transmit_IT() 函数之后都会调用 HAL_UART_TxCpltCallback() 发送中断回调函数。

        HAL_UART_Transmit_IT() 函数主要用来打开 TXE 中断

        注意事项:如果要连续使用该函数进行发送数据的话,要使用一个判断语句判断串口是否处于准备状态(不连续发送也建议这样做,比较保险)(详细请看 四的发送示例)

 4、HAL_UART_TxCpltCallback() 函数

void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart) 

        串口发送中断回调函数,只有 HAL_UART_Transmit_IT() 函数执行成功之后才会调用,使用 HAL_UART_Transmit() 函数或者使用重定向的 printf() 函数均无法调用该函数

        注意:在该函数中不要执行占用时间过长或者不确定的代码(在连续发送的时候会导致连续发送失败,即使加了判断语句)

 5、HAL_UART_Receive_IT() 函数 & HAL_UART_RxCpltCallback() 函数

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

        HAL_UART_Receive_IT() 函数常被用于在 HAL_UART_RxCpltCallback() 串口接收中断回调函数中,因为中断接收函数 HAL_UART_Receive_IT() 只能触发一次接收中断,所以我们需要在中断回调函数 HAL_UART_Receive_IT() 中再调用一次中断接收函数 HAL_UART_Receive_IT()。(详细请看 四的接收示例)

        但在外部还是需要执行一次 HAL_UART_Receive_IT() 函数的用来打开 PE、ERR、RXNE 中断(详细请看 四的接收示例)

函数流程图

  1. HAL_UART_Receive_IT (中断接收函数
  2. USART1_IRQHandler(void) (中断服务函数)
  3. HAL_UART_IRQHandler(UART_HandleTypeDef *huart) (中断处理函数)
  4. UART_Receive_IT(UART_HandleTypeDef *huart) (接收函数)
  5. HAL_UART_RxCpltCallback(huart) (中断回调函数)

 6、HAL_UART_Receive_DMA() 函数

        该函数调用了 UART_Start_Receive_DMA() 函数,以下是该函数所做的一些事情。

四、普通收发模式

 1、发送示例

        注:以下的代码都是自动生成的代码中没有的,需要全部添加进去!!!

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、接收示例

        注:以下的代码都是自动生成的代码中没有的,需要全部添加进去!!!

 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 */
}

五、DMA 收发模式

        DMA是什么,简单来说就是数据可以不通过CPU就可以达到发送和接收数据的目的,从而提高CPU的效率。

        在找如何使用串口+DMA收发的示例的时候,发现虽然网上有非常多的教程,但我发现很多不是不能用就是有很多瑕疵,调试很久心态爆炸,中途也不断实验,终于让我弄出了一个可以使用的代码。

        不过我觉得实际使用的时候感觉 DMA+串口发送 和 串口中断接收 会比较常用一点,因为一般使用串口接收的数据都不会太长,也占用不了太多的 CPU,但这里还是贴上 DMA 发送和接收的代码供参考。

        DMA的配置请看 二、USART配置。

        注:以下的代码都是自动生成的代码中没有的,需要全部添加进去!!!

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 */
}

猜你喜欢

转载自blog.csdn.net/weixin_48896613/article/details/127426478