一、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 实际接收数据长度的处理