小熊派实践——实现串口2数据DMA通道接收
一、实验准备
1.实验环境
一块stm32开发板(推荐使用小熊派),以及数据线
已经安装STM32CubeMX
已经安装KeilMDK,并导入stm32开发板对应的芯片包(小熊派使用的是STM32L431RCT6)
准备一个串口调试助手,我使用的是UartAssist
2.目标效果
通过CubeMX创建工程并配置参数
实现串口2空闲中断
串口回调函数实现数据缓存
二、通过CubeMX生产MDK工程
A.芯片选择
打开CubeMX,进入芯片选择:
选择自己的stm32芯片(即STM32L431RCT6):
B.时钟源RCC设置
更改系统时钟源
系统时钟默认使用内部的高速时钟(HSI),选择使用HSE,时钟更精确
设置外部时钟对应的端口
配置时钟树
STM32L431RCT6系统时钟最大可以为80MHz,我们配置到最大即可
C.参数配置(对应端口设置)
1)配置USART1
使用USART,模式为异步,波特率为115200,无硬件流控制
2)配置串口2
DMA知识补充:DMA传输将数据从一个地址空间复制到另一个地址空间,提供在外设和存储器之间或者存储器和存储器之间的高速数据传输。当CPU初始化这个传输动作,传输动作本身是由DMA控制器来实现和完成的。DMA传输方式无需CPU直接控制传输,也没有中断处理方式那样保留现场和恢复现场过程,通过硬件为RAM和IO设备开辟一条直接传输数据的通道,使得CPU的效率大大提高。
打开串口2
因为我使用的模组是M5310A,默认波特率为9600.
---
打开串口2中断
打开串口2 接收的DMA
D.工程设置
一些基础的设置,包括工程名、存储位置、工程环境、工程中各个文件的组成
E.生成代码
三、代码补充
1. 串口1输出重定向
我们知道printf是打印函数,原理是根据传入的字符串参数格式化打印输出到stdout中。我们需要让printf打印到串口之中,只需要在usart.c文件中模仿printf写一个输出函数即可
在添加头文件
/* USER CODE BEGIN 0 */#include <stdarg.h>#include <string.h>#include <stdio.h>/* USER CODE END 0 */
写输出函数
/* USER CODE BEGIN 1 */void UsartPrintf(UART_HandleTypeDef *huart, char *fmt,...){ unsigned char UsartPrintfBuf[296]; va_list ap; unsigned char *pStr = UsartPrintfBuf; va_start(ap, fmt); vsprintf((char *)UsartPrintfBuf, fmt, ap); //格式化 va_end(ap); while(*pStr != 0) { USART1->TDR = *pStr++; while((USART1->ISR & 0x40) == 0); }}//使用方法:UsartPrintf(&huart1,"hello world\r\n");/* USER CODE END 1 */
注意:自己添加的代码,需要在begin和end之间
2. 写串口2回调函数
STM32在获得串口2的数据后,可能会因为数据处理的速度较慢,导致某些数据丢失,虽然这种几率极低,但是为了保险起见,我们使用串口回调函数。因为数据量较少,故没有使用队列进行数据缓存。
1.串口回调函数如下
//串口回调函数/** 参考华清远见的NB-IoT课程 **/void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart){ if(huart->Instance == USART2) { if(Usart2type.UsartRecLen>0) //证明还有未完成数据 { memcpy(&Usart2type.Usart2RecBuffer[Usart2type.UsartRecLen],Usart2type.Usart2DMARecBuffer,Usart2type.UsartDMARecLen); //转存到待处理区域 Usart2type.UsartRecLen += Usart2type.UsartDMARecLen; } else { memcpy(Usart2type.Usart2RecBuffer,Usart2type.Usart2DMARecBuffer,Usart2type.UsartDMARecLen); //转存到待处理区域 Usart2type.UsartRecLen = Usart2type.UsartDMARecLen; } memset(Usart2type.Usart2DMARecBuffer, 0x00, sizeof(Usart2type.Usart2DMARecBuffer)); //先清空DMA缓冲区 Usart2type.UsartRecFlag = 1; }}
2.串口2中断函数如下:
void USART2_IRQHandler(void){ /* USER CODE BEGIN USART2_IRQn 0 */ if(__HAL_UART_GET_FLAG(&huart2,UART_FLAG_IDLE) ==SET) //触发空闲中断 { uint16_t temp = 0; __HAL_UART_CLEAR_IDLEFLAG(&huart2); //清除串口2空闲中断标志位 HAL_UART_DMAStop(&huart2); //关闭DMA temp = huart2.Instance->ISR; //清除SR状态寄存器 F0 ISR temp = huart2.Instance->RDR; //读取DR数据寄存器 F0 RDR 用来清除中断 temp = hdma_usart2_rx.Instance->CNDTR; //获取DMA中未传输的数据个数 Usart2type.UsartDMARecLen = USART2_DMA_REC_SIE - temp; //总计数减去未传输的数据个数,得到已经接收的数据个数 HAL_UART_RxCpltCallback(&huart2); //串口接收回调函数 } /* USER CODE END USART2_IRQn 0 */ HAL_UART_IRQHandler(&huart2); /* USER CODE BEGIN USART2_IRQn 1 */ HAL_UART_Receive_DMA(&huart2,Usart2type.Usart2DMARecBuffer,USART2_DMA_REC_SIE); //重新打开DMA接收 /* USER CODE END USART2_IRQn 1 */}
3.代码分析!!
打开串口2空闲中断以及使能DMA接收
````__HAL_UART_ENABLE_IT(&huart2, UART_IT_RXNE);//开启串口接收中断 __HAL_UART_ENABLE_IT(&huart2, UART_IT_IDLE);//开启串口2空闲接收中断 __HAL_UART_CLEAR_IDLEFLAG(&huart2); //清除串口2空闲中断标志位 HAL_UART_Receive_DMA(&huart2,Usart2type.Usart2DMARecBuffer,USART2_DMA_REC_SIE); //使能DMA接收
我们设置一个全局的结构体,用于存放串口信息
#define USART2_DMA_REC_SIE 256#define USART2_REC_SIE 512typedef struct{ uint8_t UsartRecFlag; //数据接收到标志位 uint16_t UsartDMARecLen; //DMA接受长度 uint16_t UsartRecLen; //处理数据长度 uint8_t Usart2DMARecBuffer[USART2_DMA_REC_SIE]; //DMAbuffer uint8_t Usart2RecBuffer[USART2_REC_SIE]; //数据处理buffer} teUsart2type;extern teUsart2type Usart2type; //结构体全局定义
串口中断,即串口在完成一组数据接收之后,会进入此中断。进入中断后,我们进行相应的DMA操作。同时调用串口回调函数。
四、使用方式
在接收到串口2 的数据之后,会进入此中断,并将 UsartRecFlag 标志位置1.
我们在进行数据处理时,首先判断 UsartRecFlag 标志位,然后进行数据操作 ,最后记得清空标志位、清空接收长度。
比如,我们对AT指令的返回进行数据操作,则可以:
void ATRec(void){ if(Usart2type.UsartRecFlag == 1) //串口收到数据 { if(strstr((const char*)Usart2type.Usart2RecBuffer,ATCmds[ATRecCmdNum].ATRecStr) != NULL) //如果包含对应校验符 { ATCmds[ATRecCmdNum].ATStatus = SUCCESS_REC; //设置为成功接收 } printf("Have Rev:%s\r\n",Usart2type.Usart2RecBuffer); Usart2type.UsartRecFlag = 0; //清空接收标志位 Usart2type.UsartRecLen = 0; //清空接收长度 }}
五、编译+下载
点击编译后,0 error,0 warning
此时,我们已经完成了基本的配置,之后如果大家需要和通信模组通信,只需要加入相应的AT指令就能够完成AT指令数据发送
- 点赞
- 收藏
- 关注作者
评论(0)