STM32串口空闲中断+DMA不定长收发数据(HAL库)

目录

一、STM32 IDLE和RXNE中断

1.1.STM32的IDLE功能:

1.2.STM32 的RXNE功能:

二、USART+DMA接收数据

2.1.DMA计算实际接收数据字节数

2.2.串口IDLE空闲中断的方式接收一帧数据原理

三、软件设计

3.1.接收数据的流程:

3.2数据接收完成的流程:

3.3.while循环主程序流程:

四、注意事项


一、STM32 IDLE和RXNE中断

1.1.STM32的IDLE功能:

在使用串口接受字符串时,可以使用空闲中断(IDLEIE置1,即可使能空闲中断),这样在接收完一个字符串,进入空闲状态时(IDLE置1)便会激发一个空闲中断。在中断处理函数,我们可以解析这个字符串。

中断产生条件:在串口无数据接收的情况下,不会产生,当清除IDLE标志位后,必须有接收到第一个数据后,才开始触发,一但接收的数据断流,没有接收到数据,即产生IDLE中断。

1.2.STM32 的RXNE功能:

当串口接收到一个bit的数据时,(读取到一个停止位) 便会触发 RXNE接收数据中断,接受到一个字节的数据,触发中断。比如给上位机给单片机一次性发送了8个字节,就会产生8次RXNE中断,1次IDLE中断。当串口接收到数据时,bit5 RXNE就会自动变成1,当接收完一帧数据后,bit4就会变成1。

图1.串口CR1寄存器

二、USART+DMA接收数据

2.1.DMA计算实际接收数据字节数

图2.1.DMA返回接收空间剩余字节宏定义

DMA接收时该宏将返回当前接收空间剩余字节。其本质就是读取NTDR寄存器,DMA通道结构体中定义了NDTR寄存器,读取该寄存器即可得到未传输的数据数。

实际接收的字节= 预先定义的接收总字节 - __HAL_DMA_GET_COUNTER()

图2.2 NTDR寄存器

2.2.串口IDLE空闲中断的方式接收一帧数据原理

采用STM32F103的串口1,并配置成空闲中断IDLE模式且使能DMA接收,并同时设置接收缓冲区和初始化DMA。那么初始化完成之后,当外部给单片机发送数据的时候,假设这次接受的数据长度是100个字节,那么在单片机接收到一个字节的时候并不会产生串口中断,而是DMA在后台把数据默默地搬运到你指定的缓冲区数组里面。当整帧数据发送完毕之后串口才会产生一次中断,此时可以利用__HAL_DMA_GET_COUNTER(&hdma_usart1_rx);函数计算出当前DMA接收存储空间剩余字节。

例如,本次串口接收100个字节,通过上位机发送了 Usart_Test 6个字节的数据长度,那么此时 GET_COUNTER函数读出来接收存储空间剩余字节就是94个字节,即:

实际接收的字节(6) = 预先定义的接收总字节(100) -__HAL_DMA_GET_COUNTER()(94)

9 = 100-94

图2.3.串口+DMA接收数据的变量


volatile uint8_t Array_number = 100;          //暂存数组个数         
uint8_t Rx_date[100]={0};                     //数据接收暂存数组
HAL_UART_Receive_DMA(&huart1,Rx_date,Array_number);//重启DM

三、软件设计

本例程将使用DMA+串口接受空闲中断,实现不定长数据完整接收,并将接收到的数据发送至上位机的功能。

图3.1.例程逻辑图

3.1.接收数据的流程:

首先在初始化的时候打开DMA接收,当MCU通过USART接收外部发来的数据时,在进行第①②③步的时候,DMA直接将接收到的数据写入缓存rx_buffer[100] //接收数据缓存数组,程序此时也不会进入接收中断,在软件上无需做任何事情,要在初始化配置的时候设置好配置就可以了。

3.2数据接收完成的流程:

当一帧数据接收完成之后IDLE置1,产生接收空闲中断④,在中断服务函数中做这几件事:

  • 判断是否为IDLE接收空闲中断。

②在中断服务函数中将接收完成标志位置1。

③关闭DMA防止在处理数据时候接收数据,产生干扰。

④计算出接收缓存中的数据长度,清除中断标志位。

图3.2.中断服务函数代码

void USART1_IRQHandler(void)
{
  /* USER CODE BEGIN USART1_IRQn 0 */
	uint32_t temp;                                           //未传输数据的个数
	uint8_t IDLE_flag = 0;                                   //空闲中断标志位
	IDLE_flag =__HAL_UART_GET_FLAG(&huart1,UART_FLAG_IDLE);  //获取IDLE标志位
	if(IDLE_flag == 1)                                       //判断是否进入空闲中断
	{ 
		__HAL_UART_CLEAR_IDLEFLAG(&huart1);                  //清除标志位
		HAL_UART_DMAStop(&huart1);                           //停止DMA传输,防止搬错
		temp = __HAL_DMA_GET_COUNTER(&hdma_usart1_rx);       //获取DMA中未传输的数据个数   
		Rx_length = Array_number - temp;                     //总计数减去未传输的数据个数,得到已经接收的数据个数
		Rx_end_flag = 1;	                                 //接收完成标志位置1	
	 }

  /* USER CODE END USART1_IRQn 0 */
	
  /* USER CODE BEGIN USART1_IRQn 1 */
  /* USER CODE END USART1_IRQn 1 */
	 
	HAL_UART_IRQHandler(&huart1);
	 
}

3.3.while循环主程序流程:

  • 主程序中检测到接收完成标志被置1。
  • 进入数据处理程序,将接收到的数据重新发送到上位机。
  • 将接收完成标志位置0,并清除计数。
  • 重新设置DMA下次要接收的数据字节数,使能DMA进入接收数据状态。

图3.1.while循环代码


  while (1)
  {
    /* USER CODE END WHILE */
	 if(Rx_end_flag == 1)                                                                 //判断数据是否接受完成
		{			
		    HAL_UART_Transmit_DMA(&huart1,Rx_date,Rx_length);                                 //将接收的数据返回上位机			
			Rx_length = 0;                                                                    //清除计数
			Rx_end_flag = 0;                                                                  //清除接收标志位			
        }
	 HAL_UART_Receive_DMA(&huart1,Rx_date,Array_number);                                 //重启DMA
    /* USER CODE BEGIN 3 */
  }

四、注意事项

1、一定要初始化空闲中断和DMA,空闲中断未初始化会导致无法进入空闲中断,DMA未初始化会导致第一次搬运数据为空数据。

图4.1.空闲中断和DMA初始化函数

2、while每次循环结束时,要重启DMA(HAL_UART_Receive      _DMA();),否则DMA搬运的数据不会更新,即DMA只搬运一次,Rx_date[ ]中的数据永远为第一次存进去的数据。

图4.2

3、接收数据时,DMA搬运来的数据将暂存至Rx_date[ ]数组中,但是将接收到的数据处理后,用HAL_UART_Transmit_DMA(&huart1,Rx_date,Rx_length)发回上位机时,数据的长度应该为Rx_length,如果用Array_number,会出现下图情况。

图4.3

4、__HAL_DMA_GET_COUNTER();函数获取的是DMA中未传输的数据个数,若要获取实际接收数据的长度需进一步处理。

图4.4 实际接收数据长度的处理

STM32F103系列微控制器是基于ARM Cortex-M3内核的高性能微处理器,广泛应用于嵌入式系统设计。在本示例中,我们关注的是如何利用串口(USART)的空闲中断DMA(Direct Memory Access)功能来实现Modbus通信,这是一种在工业自动化领域广泛应用的通信协议。 我们要理解串口通信的基本概念。串口通信是一种点对点的数据传输方式,通常使用UART(通用异步收发传输器)或USART(通用同步/异步收发传输器)接口。在STM32F103中,USART模块提供了串行数据的发送接收功能,包括数据帧的起始位、数据位、奇偶校验位停止位。 串口空闲中断是在串口接收线路上检测到一段无数据传输的空闲时间后触发的中断,这对于检测完整的Modbus报文非常有用。在传统实现中,可能需要设置定时器中断来判断一帧数据是否接收完成,但这种方法可能会增加系统的复杂性功耗。使用串口空闲中断则更为高效,一旦接收到的数据流停止,中断立即被触发,可以立即处理接收到的数据,无需等待额外的定时器中断。 接下来,我们讨论DMADMA是一种允许外部设备直接访问内存的技术,减少了CPU的干预,提高了数据传输效率。在STM32中,我们可以配置DMA通道来处理串口接收数据,这样当数据到达时,DMA会自动将数据串口接收缓冲区转移到指定的内存位置,而CPU可以继续执行其他任务,提升了系统性能。 在实现Modbus通信时,我们需要设置USART的相关参数,如波特率、数据位、停止位奇偶校验,并开启串口接收中断。同时,要配置DMA,选择合适的通道,设置传输方向(从外设到内存),并指定数据缓冲区地址。当串口进入空闲状态并触发中断时,通过DMA已经将完整的一帧数据传输到内存,此时可以进行Modbus报文解析响应。 在提供的文件列表中,`keilkilll.bat`可能是Keil的工程
### STM32 UART Idle Interrupt Data Reception Example and Configuration In applications where the length of incoming data packets is not fixed, using an idle line detection interrupt can be beneficial to handle such scenarios efficiently on STM32 microcontrollers. The following sections provide a detailed explanation along with code examples. #### Configuring UART for Idle Line Detection To enable receiving data via the idle line state detection feature: - Initialize the UART peripheral. - Enable the IDLE interrupt within the UART configuration structure by setting `InterruptPriority` appropriately so that it does not conflict with other peripherals like DMA[^2]. ```c static void MX_USART2_UART_Init(void) { huart2.Instance = USART2; huart2.Init.BaudRate = 115200; huart2.Init.WordLength = UART_WORDLENGTH_8B; huart2.Init.StopBits = UART_STOPBITS_1; huart2.Init.Parity = UART_PARITY_NONE; huart2.Init.Mode = UART_MODE_TX_RX; huart2.Init.HwFlowCtl = UART_HWCONTROL_NONE; huart2.Init.OverSampling = UART_OVERSAMPLING_16; huart2.Init.OneBitSampling = UART_ONE_BIT_SAMPLE_DISABLE; huart2.AdvancedInit.AdvFeatureInit = UART_ADVFEATURE_NO_INIT; if (HAL_UART_Init(&huart2) != HAL_OK) { Error_Handler(); } /* Ensure that UART interrupts are enabled */ __HAL_UART_ENABLE_IT(&huart2, UART_IT_IDLE); } ``` Ensure that the serial communication interface's settings match those expected by external devices communicating over this link. #### Setting Up DMA for Receiving Data For efficient handling of received bytes without CPU intervention until a complete packet arrives or timeout occurs due to idleness between characters: - Configure DMA channel parameters including source/destination addresses and buffer size according to application needs. - Clear any pending flags before starting transfers. - Enable relevant events as required but disable half-transfer notifications when continuous blocks need processing together instead of being split into smaller chunks during transfer completion[^1]. ```c void HAL_UART_MspInit(UART_HandleTypeDef* uartHandle) { GPIO_InitTypeDef GPIO_InitStruct = {0}; if(uartHandle->Instance==USART2) { /* Peripheral clock enable */ __HAL_RCC_USART2_CLK_ENABLE(); /**USART2 GPIO Configuration PA2 ------> USART2_TX PA3 ------> USART2_RX */ GPIO_InitStruct.Pin = GPIO_PIN_2|GPIO_PIN_3; GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; GPIO_InitStruct.Pull = GPIO_NOPULL; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH; GPIO_InitStruct.Alternate = GPIO_AF7_USART2; HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); hdma_usart2_rx.Instance = DMA1_Channel6; hdma_usart2_rx.Init.Direction = DMA_PERIPH_TO_MEMORY; hdma_usart2_rx.Init.PeriphInc = DMA_PINC_DISABLE; hdma_usart2_rx.Init.MemInc = DMA_MINC_ENABLE; hdma_usart2_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE; hdma_usart2_rx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE; hdma_usart2_rx.Init.Mode = DMA_CIRCULAR; hdma_usart2_rx.Init.Priority = DMA_PRIORITY_LOW; if (HAL_DMA_Init(&hdma_usart2_rx) != HAL_OK) { Error_Handler(); } __HAL_LINKDMA(uartHandle,hdmarx,hdma_usart2_rx); /* USART2 interrupt Init */ HAL_NVIC_SetPriority(USART2_IRQn, 5, 0); // Lower than DMA priority HAL_NVIC_EnableIRQ(USART2_IRQn); } } // Before initiating reception... __HAL_DMA_CLEAR_FLAG(&hdma_usart2_rx, DMA_FLAG_TCIF6 | DMA_FLAG_HTIF6 | DMA_FLAG_TEIF6); __HAL_DMA_DISABLE_IT(&hdma_usart2_rx, DMA_IT_HT); // Disable Half Transfer ITs if(HAL_OK != HAL_UART_Receive_DMA(&huart2, RxBuffer, RXBUFFERSIZE)) { Error_Handler(); } ``` This setup ensures seamless operation even under varying load conditions while maintaining low power consumption through minimal processor involvement in routine tasks related to character-by-character I/O operations. #### Handling Received Packets Using ISR Finally, implement the interrupt service routine responsible for collecting completed messages once detected based upon periods of silence exceeding specified thresholds defined internally within hardware registers associated with each instance of Universal Synchronous Asynchronous Receiver Transmitter present inside these chips. ```c void USART2_IRQHandler(void) { uint16_t temp; HAL_UART_IRQHandler(&huart2); if (__HAL_UART_GET_FLAG(&huart2, UART_FLAG_IDLE) && __HAL_UART_GET_IT_SOURCE(&huart2, UART_IT_IDLE)) { // Stop ongoing DMA activity after detecting end-of-message condition signaled by prolonged absence of further input symbols arriving at receiver pin(s). HAL_UART_DMAStop(&huart2); // Read one byte from RDR register which also clears the IDLE flag automatically post-read action. temp = READ_REG(huart2.Instance->RDR); // Process your message here... // Restart listening process immediately afterwards readying system back up again awaiting next transmission event occurrence anytime soon thereafter. if(HAL_OK != HAL_UART_Receive_DMA(&huart2, RxBuffer, RXBUFFERSIZE)){ Error_Handler(); } } } ``` --related questions-- 1. How do you configure UART baud rates correctly? 2. What considerations should be taken when choosing DMA channels for different peripherals? 3. Can multiple UART instances share the same DMA resource safely? If yes, how would synchronization look like? 4. In what situations might disabling half-transfer interrupts improve performance? 5. Are there alternative methods besides using idle interrupts for managing variable-length data frames effectively?
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值