一.串口中断通信
串口中断方式的特点
- 发送数据时,将一字节数据放入数据寄存器DR;接收数据时,将DR的内容存放到用户存储区;
- 中断方式不必等待数据的传输过程,只需要在每字节数据收发完成后,由中断标志位触发中断,在中断服务程序中放入新的一字数据或者读取接收到 的一字节数据;
- 在传输数据量较大,且通信波特率较高(大于38400)时,如果采用中断方式,每收发一个字节的数据,CPU都会被打断,造成CPU无法处理其他事务。因此在批量数据传输,通信波特率较高时,建议采用DMA方式。
中断代码如下
void USART1_IRQHandler(void)
{
/* USER CODE BEGIN USART1_IRQn 0 */
if (__HAL_UART_GET_FLAG(&huart1, UART_FLAG_RXNE) != RESET)
{
if (rxReceiveCount1 < 20)
{
rxBuffer1[rxReceiveCount1]=(uint8_t)(huart1.Instance->DR);
++rxReceiveCount1;
}
else
{
rxReceiveCount1 = 0;
}
__HAL_UART_CLEAR_FLAG(&huart1, UART_FLAG_RXNE);
}
else if (__HAL_UART_GET_FLAG(&huart1, UART_FLAG_IDLE) != RESET)
{
__HAL_UART_CLEAR_IDLEFLAG(&huart1);
uint32_t tmp1;
tmp1 = huart1.Instance->SR;
tmp1 = huart1.Instance->DR;
rxFrameFlag1 = 1;
}
/* USER CODE END USART1_IRQn 0 */
/* USER CODE BEGIN USART1_IRQn 1 */
/* USER CODE END USART1_IRQn 1 */
}
/**
HAL_UART_Receive_IT(&huart1, (uint8_t *)&rc_data, 1)
的作用是:
-
启动UART1的中断接收模式:该代码将启动UART1的中断接收模式,使UART1开始等待接收数据。
-
指定接收缓冲区:将rc_data
变量的地址作为接收数据的缓冲区地址。这意味着当UART1接收到数据时,数据将存储在rc_data
变量中。
-
指定缓冲区大小:1
表示接收数据的大小为1个字节。这意味着当UART1接收到1个字节的数据时,将触发一个接收中断,并将数据存储在rc_data
变量中。
需要注意的是,在初始化UART1时,需要先调用一次该函数,来启动UART1的中断接收模式;再接受完一个数据后,也需要再调用该函数来重新启动UART1的中断接收模式,以等待接收下一个字节的数据。
二.串口空闲中断+DMA
1.空闲中断接收
空闲中断接收,当一帧数据接收完成之后,串口会进入到空闲中断中去,然后在空闲中断中处理收到的数据。这种模式对处理不定长数据帧带来很大的便利,我们不必频繁的进入接收中断处理数据,但是弊端也是明显的,由于每次都要接收完一个完整的数据帧后才空闲中断,所以当一帧数据出错时,我们也不得不接收这帧错误的数据。在通讯可靠的场合,使用空闲中断接收模式接收串口数据,将会大大提高系统的性能。
2.DMA
1.简介
DMA(Direct Memory Access)—直接存储器存取,是单片机的一个外设,它的主要功能是用来搬数
据,但是不需要占用
CPU
,即在传输数据的时候,
CPU
可以干其他的事情,好像是多线程一样。
2、使用场景
DMA用在只需要传输数据,不需要处理数据的地方,有三种传输方式:
-
外设→存储器(例:从串口RDR寄存器写入某数据buf)
-
存储器→外设(例:从某数据buf写入串口TDR寄存器)
-
存储器→存储器(例:复制某特别大的数据buf)
3、指针递增
在传输数据时,可以配置指向传输双方数据的指针是否自动向后递增。通过单个寄存器访问外设源或目标数据时,禁止递增模式十分有用。
通常如下图配置:
Cubemx配置
串口设置
DMA设置
开启全局中断
最后直接生成文件
代码编写
1.在usart.c文件中添加
定义变量
/* USER CODE BEGIN 0 */
volatile uint8_t rx_len=0; //接收到的数据长度
volatile uint8_t recv_end_flag=0;//接收完成标志
uint8_t rx_buffer[200];//缓存数组
/* USER CODE END 0 */
开启空闲中断和dma接收
/* USER CODE BEGIN USART1_Init 2 */
__HAL_UART_ENABLE_IT(&huart1,UART_IT_IDLE); //使能空闲中断
HAL_UARTEx_ReceiveToIdle_DMA(&huart1,rx_buffer,200); //开启DMA接收
/* USER CODE END USART1_Init 2 */
重定义fputc,是为了能使用printf函数
/* USER CODE BEGIN 1 */
//重定向fputc函数
int fputc(int ch, FILE *f)
{
HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 0xFFFF);
return ch;
}
/* USER CODE END 1 */
2.在usart.h文件中添加
#include <stdio.h>
/* USER CODE BEGIN Includes */
extern volatile uint8_t rx_len ; //接收一帧数据的长度
extern volatile uint8_t recv_end_flag; //一帧数据接收完成标志
extern uint8_t rx_buffer[200]; //接收数据缓存数组
注意这里的#include <stdio.h>,不添加的话重定向fputc函数那里会报错
3.在中断函数中添加
void USART1_IRQHandler(void)
{
/* USER CODE BEGIN USART1_IRQn 0 */
uint32_t tmp_flag = 0;
uint32_t temp;
tmp_flag =__HAL_UART_GET_FLAG(&huart1,UART_FLAG_IDLE); //获取IDLE标志位
if((tmp_flag != RESET))
{
__HAL_UART_CLEAR_IDLEFLAG(&huart1);//
temp = huart1.Instance->SR;
temp = huart1.Instance->DR;
HAL_UART_DMAStop(&huart1);
temp = hdma_usart1_rx.Instance->CNDTR;//获取此时DMA中未传输的数据个数
rx_len = 200 - temp; //总计数减去未传输的数据个数,得到此次接收到的数据个数
recv_end_flag = 1;
}
HAL_UART_IRQHandler(&huart1);
/* USER CODE END USART1_IRQn 0 */
/* USER CODE BEGIN USART1_IRQn 1 */
/* USER CODE END USART1_IRQn 1 */
}
这里不使用回调函数
4.在main.c中添加
while (1)
{
/* USER CODE END WHILE */
if(recv_end_flag ==1)
{
printf("接收到的数据长度为%d\r\n",rx_len);
HAL_UART_Transmit(&huart1,rx_buffer, rx_len,200);
for(uint8_t i=0;i<rx_len;i++)
{
rx_buffer[i]=0;
}
printf("\r\n");
rx_len=0;
recv_end_flag=0;
}
HAL_UARTEx_ReceiveToIdle_DMA(&huart1,rx_buffer,200);//再次开启DMA接收
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
这里只是测试代码,将接收到的数据再利用串口输出到串口助手
5.结果
当数据接收间隔时间很短的情况下,这里是1ms,数据接收会出错;在10ms后就正常
注意事项
1.程序下载到板子上没反应,只有在一步一步调试时才有输出。最后勾选了Use MicoLIB后程序就正常运行了
2.输出中文时串口助手上显示乱码,代码格式要改为GB 2312,但直接在keil上修改没什么用,我这里是在vscode上修改格式后保存,在keil上运行就行了。