小熊派实践——实现串口2数据DMA通道接收
【摘要】 物联网设备在收到数据后,为了及时的对数据进行处理以及防止数据的丢失,本文通过使用Stm32的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指令数据发送
【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱:
cloudbbs@huaweicloud.com
- 点赞
- 收藏
- 关注作者
作者其他文章
评论(0)