STM32:串口轮询模式、中断模式、DMA模式和接收不定长数据

一.串口轮询模式底层机制:

在STM32每个串口的内部都有两个寄存器:发送数据寄存器(TDR)/发送移位寄存器,当我们调用HAL_UART_Transmit 把数据发送出去时,CPU会将数据依次将数据发送到数据寄存器中,移位寄存器中的数据会根据我们设置的比特率传化成高低电平从TX引脚输出。待发送移位寄存器中发数据发送出去后,CPU就会将下一个数据进行相同的发送。

当我们调用HAL_UART_Receive把数据接收过来时,数据会通过RX引脚收到的电平信号进行转化后,会将数据存进接收移位寄存器。接收移位寄存器每接收完1帧就会将数据放到接收数据寄存器。而后CPU会将接收数据寄存器中的数据存到变量中

而在轮询模式下。在发送整个数据的过程中,CPU都要不断地轮询"发送数据寄存器"中的数据是否移动到"发送移位寄存器"下,直到把本次要发送的数据全部发完,或者用时超过设置的超时时间才算结束。

因此,采用轮询模式,在数据接收和发送过程中,CPU不会去做其他事情,主程序中的代码会进行阻塞直到IO结束。

具体的案例在下面链接:

STM32:TTL串口调试-CSDN博客

二.串口的中断模式

(1).中断模式机制

采用中断模式便可以解决在IO过程中主程序阻塞问题。原理是接收和发送数据时,CPU并不会轮询发送/接收数据寄存器是否有数据。而是发送/接收数据寄存器当每数据时会发送一个中断主动通知CPU。因此CPU在将数据寄存器中的数据移动到移位寄存器后,就可以去执行其他任务了。当发送移位寄存器中的数据发送出去后就会触发"发送移位寄存器空"中断再把CPU叫回来。如此反复完成IO。

(2).中断模式案例

STM32:TTL串口调试-CSDN博客这个案例下,改造成中断函数形式。

  1. 打开CubeIDE,开启USART2中断,生成代码

2.查看stm32f1xx_it文件中 USART2_IRQHandler() 中断处理函数的定义。由于每个USART中只有一个中断向量,并且这个中断向量是USART中断请求共用的,所以中断处理函数也是被USART共用的。因此,为了单独写发送数据的逻辑写在中断处理函数中就不太合适。因此需要判断哪些原因触发了这个中断处理函数,分别实现逻辑,而这个判断HAL_UART_IRQHandler(&huart2) 函数中已经帮我们准备好了。

转到HAL_UART_IRQHandler(&huart2)的定义。可以看见经过一系列判断等逻辑后就会根据判断的结果执行Callback函数。因此当某个事件发生时,就会调用回调函数。而数据接收完成后执行的回调函数就是:

__weak void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)

注:Cplt 指 完成 。 __weak 关键字作为前缀代表是一个弱定义,我们可以在其他地方重新定义此函数

因此我们可以实现这个回调函数来实现传输数据又不阻塞主程序。

(3).示例代码

main.c关键代码如下:

注: HAL_UART_Receive_IT(&huart2, &message, size);

HAL_UART_Transmit_IT(&huart2,&message, size);

是中断形式的UART接收/发送数据的函数,由于不阻塞主程序因此不需要设置超时时间。

cpp 复制代码
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include <string.h>
/* USER CODE END Includes */

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */

/* USER CODE END PTD */

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */

/* USER CODE END PD */

/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */

/* USER CODE END PM */

/* Private variables ---------------------------------------------------------*/
UART_HandleTypeDef huart2;
uint8_t  recvDate[2];

/* USER CODE BEGIN PV */

/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_USART2_UART_Init(void);
/* USER CODE BEGIN PFP */

/* USER CODE END PFP */

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart){
	//	  HAL_UART_Receive(huart, pData, Size, Timeout)
//		  HAL_UART_Receive(&huart2, recvDate, 2, HAL_MAX_DELAY);
		  HAL_UART_Transmit_IT(&huart2,recvDate,2);
		  GPIO_PinState pinstate= GPIO_PIN_RESET;
		  if(recvDate[1] == '1'){
			  pinstate = GPIO_PIN_SET;
		  }
		  if(recvDate[0] == 'R'){
			  HAL_GPIO_WritePin(redLED_GPIO_Port, redLED_Pin, pinstate);
		  }else if(recvDate[0] == 'B'){
			  HAL_GPIO_WritePin(blueLED_GPIO_Port,blueLED_Pin, pinstate);
		  }else if(recvDate[0] == 'G'){
			  HAL_GPIO_WritePin(greenLED_GPIO_Port,greenLED_Pin, pinstate);
		  }
		  HAL_UART_Receive_IT(&huart2, recvDate, 2);

}
/* USER CODE END 0 */

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{
  /* USER CODE BEGIN 1 */

  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_USART2_UART_Init();
  /* USER CODE BEGIN 2 */
  HAL_UART_Receive_IT(&huart2, recvDate, 2);

  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {

    /* USER CODE END WHILE */

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

三.DMA 模式

虽然采用中断模式便可以解决在IO过程中主程序阻塞问题,但是CPU切换过于频繁。而直接内存访问(DMA,Direct Memory Access)是一些计算机总线架构提供的功能,它能使数据从附加设备(如磁盘驱动器)直接发送到计算机主板的内存上。

CPU和寄存器就像老师与学生。轮询模式就像老师每讲完一段知识点,老师都会不断地问学生学好了没,直到学会才会讲下一个知识点。中断模式就像老师每讲完一段知识点后就开始干自己的事,等待学生举手示意自己学习完后才开始讲下一个知识点。而DMA就像一名助教,负责提前学习老师要讲给学生的知识,助教再将所学知识讲给学生。直到学生把助教所学的知识都学完后,助教再让教师再传授一部分知识。

再CubeIDE设计界面中,connective ->USART2-> DMA Settings 可以配置DMA通道(如下图).

想要发送数据(TX),即内存向外设传输数据,默认通道为Channal7,而接收为Channal6。目前采用默认配置就行。

配置完后,传输和发送数据的函数就变成了

注: HAL_UART_Receive_DMA(&huart2, &message, size);

HAL_UART_Transmit_DMA(&huart2,&message, size);

当然,还可以利用中断来通知CPU传输/发送数据,只不过就不是原先的串口中断,而是DMA传输完成中断了。

四.接收不定长数据

(1) 接收不定长数据的原理

接收不定长数据主要关心的是"串口空闲(Idle)中断",即接收串口(RX引脚)上无后续数据进入便会触发。通常这个场景代表一帧数据包接收完成

而数据接收关键函数就变成了:

HAL_UARTEx_ReceiveToIdle(&huart2, pData, Size, RxLen, Timeout)

//size为允许装入的最大数据长度。

HAL_UARTEx_ReceiveToIdle_IT(&huart2, &message,maxsize);

HAL_UARTEx_ReceiveToIdle_DMA(&huart2, &message, maxsize);

回调函数就变成了

// Size参数传入数值为本次接收的数据长度

__weak void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)

对于该回调函数,除了"串口空闲中断"会调用以外,DMA传输过半中断也会调用。因此需要根据业务要求决定无关中断是否要屏蔽。

(2).采用DMA方式接收不定长数据的示例代码

cpp 复制代码
/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */

/* USER CODE END PM */
uint8_t  recvDate[20];
/* Private variables ---------------------------------------------------------*/
UART_HandleTypeDef huart2;
DMA_HandleTypeDef hdma_usart2_tx;
DMA_HandleTypeDef hdma_usart2_rx;

/* USER CODE BEGIN PV */

/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_DMA_Init(void);
static void MX_USART2_UART_Init(void);
/* USER CODE BEGIN PFP */

/* USER CODE END PFP */

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size){
	if(huart == &huart2){
		 //把接收到的数据,发给终端进行打印
		 HAL_UART_Transmit_DMA(&huart2,recvDate,Size);
		 GPIO_PinState pinstate= GPIO_PIN_RESET;
		 if(recvDate[1] == '1'){
			 pinstate = GPIO_PIN_SET;
		 }
		 if(recvDate[0] == 'R'){
			 HAL_GPIO_WritePin(redLED_GPIO_Port, redLED_Pin, pinstate);
		 }else if(recvDate[0] == 'B'){
			 HAL_GPIO_WritePin(blueLED_GPIO_Port,blueLED_Pin, pinstate);
		 }else if(recvDate[0] == 'G'){
			 HAL_GPIO_WritePin(greenLED_GPIO_Port,greenLED_Pin, pinstate);
		 }
		 //继续接收即将要接收的数据
		  HAL_UARTEx_ReceiveToIdle_DMA(&huart2, recvDate, sizeof(recvDate));
		  //关闭DMA传输过半中断
		  __HAL_DMA_DISABLE_IT(&hdma_usart2_rx,DMA_IT_HT);
	}
}
/* USER CODE END 0 */

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{
  /* USER CODE BEGIN 1 */

  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_DMA_Init();
  MX_USART2_UART_Init();
  /* USER CODE BEGIN 2 */
  //接收数据,并屏蔽DMA传输过半中断
  HAL_UARTEx_ReceiveToIdle_DMA(&huart2, recvDate, sizeof(recvDate));
  __HAL_DMA_DISABLE_IT(&hdma_usart2_rx,DMA_IT_HT);
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {

    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}
相关推荐
点灯小铭1 天前
基于单片机的多路热电偶温度监测与报警器
单片机·嵌入式硬件·毕业设计·课程设计·期末大作业
tianyue1001 天前
STM32G431 ADC 多个channel 采集
stm32·单片机·嵌入式硬件
清风6666661 天前
基于单片机的水泵效率温差法测量与报警系统设计
单片机·嵌入式硬件·毕业设计·课程设计·期末大作业
z20348315201 天前
定时器练习报告
单片机·嵌入式硬件
zk001 天前
内容分类目录
单片机·嵌入式硬件
安生生申1 天前
STM32 ESP8266连接ONENET
c语言·stm32·单片机·嵌入式硬件·esp8266
广药门徒1 天前
电子器件烧毁的底层逻辑与避坑指南
单片机·嵌入式硬件
点灯小铭2 天前
基于单片机的社区医院小型高压蒸汽灭菌自动控制器设计
单片机·嵌入式硬件·毕业设计·课程设计·期末大作业
youcans_2 天前
【动手学STM32G4】(3)STM32G431之定时器
stm32·单片机·嵌入式硬件·定时器
悠哉悠哉愿意2 天前
【嵌入式学习笔记】AD/DA
笔记·单片机·嵌入式硬件·学习