小熊派实践——实现串口2数据DMA通道接收

举报
ttking 发表于 2020/10/31 22:46:08 2020/10/31
【摘要】 物联网设备在收到数据后,为了及时的对数据进行处理以及防止数据的丢失,本文通过使用Stm32的DMA实现数据接收,同时使用回调函数实现数据缓存,能够减小数据的丢失率。

一、实验准备

1.实验环境

  • 一块stm32开发板(推荐使用小熊派),以及数据线

  • 已经安装STM32CubeMX

  • 已经安装KeilMDK,并导入stm32开发板对应的芯片包(小熊派使用的是STM32L431RCT6)

  • 准备一个串口调试助手,我使用的是UartAssist


image.png

2.目标效果

  • 通过CubeMX创建工程并配置参数

  • 实现串口2空闲中断

  • 串口回调函数实现数据缓存

二、通过CubeMX生产MDK工程

A.芯片选择

  • 打开CubeMX,进入芯片选择:

image.png

  • 选择自己的stm32芯片(即STM32L431RCT6):

image.png


B.时钟源RCC设置

  • 更改系统时钟源

系统时钟默认使用内部的高速时钟(HSI),选择使用HSE,时钟更精确

  • 设置外部时钟对应的端口

image.png

  • 配置时钟树

STM32L431RCT6系统时钟最大可以为80MHz,我们配置到最大即可

image.png


C.参数配置(对应端口设置)

1)配置USART1

使用USART,模式为异步,波特率为115200,无硬件流控制


image.png


2)配置串口2

DMA知识补充:DMA传输将数据从一个地址空间复制到另一个地址空间,提供在外设和存储器之间或者存储器和存储器之间的高速数据传输。当CPU初始化这个传输动作,传输动作本身是由DMA控制器来实现和完成的。DMA传输方式无需CPU直接控制传输,也没有中断处理方式那样保留现场和恢复现场过程,通过硬件为RAM和IO设备开辟一条直接传输数据的通道,使得CPU的效率大大提高。

打开串口2

因为我使用的模组是M5310A,默认波特率为9600.


image.png

---
打开串口2中断

image.png


打开串口2 接收的DMA
image.png

D.工程设置

一些基础的设置,包括工程名、存储位置、工程环境、工程中各个文件的组成


image.png


image.png


E.生成代码

image.png


三、代码补充

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


image.png

此时,我们已经完成了基本的配置,之后如果大家需要和通信模组通信,只需要加入相应的AT指令就能够完成AT指令数据发送


【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。