STM32 cubemx+串口空闲中断+DMA双缓冲

这篇具有很好参考价值的文章主要介绍了STM32 cubemx+串口空闲中断+DMA双缓冲。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

        写这篇文章是为了记录下之前做过的项目中用到的一部分关键技术,之前做过的项目中涉及到采用最小开销来实时接收遥控器数据、能够准确验证传输过来数据的准确性,减小误差率,要求能稳定适用于不同的环境。

目录

1、为什么要用到串口空闲中断?

2、为什么要用到DMA双缓冲?

3、具体代码流程。

        (1)cubemx配置stm32串口DMA双缓冲。

        (2)添加串口中断处理函数。

        (3)根据手册处理遥控器数据


1、为什么要用到串口空闲中断?

        在stm32 中,uart是最为常见的通信方式——它每次接收一个字节,我们可以使用轮询的方式,轮询就是不断去访问一个信号的端口,看看有没有信号进入,有则进行处理,但是对于某些数据不固定时间发送的数据轮询的方式过多消耗系统资源了。

       使用中断的方式,中断方式则是当输入产生的时候,产生一个触发信号告诉 STM32 有输入信号进入,需要进行处理。如每一个字节都中断一次,比较消耗系统资源。特别是HAL库中,从中断到回调函数运行了不少的程序,频繁的中断很可能造成数据溢出。

        为了避免这个问题,我们使用指定接收一定长度的数据,再调用回调函数,这会让我们可以接收大数据,但是这种情况则造成了,要求每次的包是固定长度

        为了解决以上一些问题,网上最常用的办法是使用空闲中断,即在串口空闲的时候,触发一次中断,通知内核,本次运输完成了。串口空闲中断的判定是:当串口开始接收数据后,检测到1字节数据的时间内没有数据发送,则认为串口空闲了。由于我们的内核在串口接收数据到空闲这段时间,是不受理串口数据的,所以我们还需要使用DMA来协助我们把数据传送到指定的地方,当数据传输完成后,通知内核去处理。

        由于在项目中要用到遥控器发送数据给stm32,并且外界环境的干扰,可能会发生丢帧的情况。这时遥控器发送数据是不定时的,因此我在项目中采用的是不定时、不定长接收数据。

        如果有一次数据被干扰,遥控器的18帧数据只有17个被串口接受,那么在下一次遥控器发送数据时,stm32会把下一次的第一个帧和上一次的17帧拼接在一次保存在缓存里,导致之后的数据全部接收错误。

2、为什么要用到DMA双缓冲?

        DMA全称Direct Memory Access,即直接内存访问。
普通DMA的使用是在DMA的数据流中进行的,设置好DMA的起点和终点以及传输的数据量即可开启DMA传输。

        DMA开启后就开始从起点将数据搬运至终点,每搬一次,传输数据量大小就减1,当传输数据量为0时,DMA停止搬运。

普通模式:当传输数据量为0时,需要程序中手动将传输数据量重新设置满才能开启下一次的DMA数据传输。
循环模式:则当传输数据量为0时,会自动将传输数据量设置为满的,这样数据就能不断的传输。

普通DMA模式下,普通DMA的目标数据储存区域只有一个,也就是如果当数据存满后,新的数据又传输过来了,那么旧的数据会被新的数据覆盖。

双缓冲模式下,我们DMA的目标数据储存区域有两个,也就是双缓冲,
当一次完整的数据传输结束后(即Counter值从初始值变为0),会自动指向另一个内存区域。

        但有时为了安全,当我们设置的DMA缓冲区大小大于一次完整的数据时,当接收完一次数据后,Counter值还没有变为0,这时不会自动指向另一个缓冲区,所以这时候我们就需要手动更改指向下一个缓冲区。(也就是当第一帧数据传输完成后,Counter值不会自动填满,且内存区域还是指向Memory0,然后我们将剩余数据量保存下来,再将Counter值填满,接着把DMA指向Memory1,最后通过判断剩余数据量来决定是否对数据进行处理)

//设定缓冲区0
DMA1_Stream1->CR &= ~(DMA_SxCR_CT);
//设定缓冲区1
hdma_usart1_rx.Instance->CR |= DMA_SxCR_CT;

3、具体代码流程。

(1)cubemx配置stm32串口DMA双缓冲。

        网上教程很多,这里我就不具体说明了,这里可参考别人写的这篇文章。

        虽然我们使用的CubeMx来配置DMA,当然只是配置DMA模式为串口到内存(DMA初始化),但还需要在程序中进一步制定,DMA具体搬运到哪一个内存中,建立两个数组用以存放DMA搬运的串口数据,并将数组的地址传给化函数,具体代码如下所示:文章来源地址https://www.toymoban.com/news/detail-698919.html

//初始化代码
#define SBUS_RX_BUF_NUM 16u  //定义的DMA缓冲区的大小,为了安全我定义了16(定义8以上就行了)
#define RC_FRAME_LENGTH 8u   //遥控器发送的数据长度

extern UART_HandleTypeDef huart1;
extern DMA_HandleTypeDef hdma_usart1_rx;

//接收原始数据,为8个字节,给了16个字节长度,防止DMA传输越界
static uint8_t sbus_rx_buf[2][SBUS_RX_BUF_NUM];

void RC_Init(uint8_t *rx1_buf, uint8_t *rx2_buf, uint16_t dma_buf_num)
{
    //使能DMA串口接收
    SET_BIT(huart1.Instance->CR3, USART_CR3_DMAR);
    //使能串口空闲中断
    __HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE);
    //失效DMA
    __HAL_DMA_DISABLE(&hdma_usart1_rx);
    while(hdma_usart1_rx.Instance->CR & DMA_SxCR_EN)
    {
        __HAL_DMA_DISABLE(&hdma_usart1_rx);
    }
    hdma_usart1_rx.Instance->PAR = (uint32_t) & (USART1->DR);
    //内存缓冲区1
    hdma_usart1_rx.Instance->M0AR = (uint32_t)(rx1_buf);
    //内存缓冲区2
    hdma_usart1_rx.Instance->M1AR = (uint32_t)(rx2_buf);
    //数据长度
    hdma_usart1_rx.Instance->NDTR = dma_buf_num;
    //使能双缓冲区
    SET_BIT(hdma_usart1_rx.Instance->CR, DMA_SxCR_DBM);
    //使能DMA
    __HAL_DMA_ENABLE(&hdma_usart1_rx);	
}

void RC_unable(void)
{
    __HAL_UART_DISABLE(&huart1);
}
void RC_restart(uint16_t dma_buf_num)
{
    __HAL_UART_DISABLE(&huart1);
    __HAL_DMA_DISABLE(&hdma_usart1_rx);

    hdma_usart1_rx.Instance->NDTR = dma_buf_num;

    __HAL_DMA_ENABLE(&hdma_usart1_rx);
    __HAL_UART_ENABLE(&huart1);

}
//遥控器初始化
void remote_control_init(void)
{
    RC_Init(sbus_rx_buf[0], sbus_rx_buf[1], SBUS_RX_BUF_NUM);
}

(2)添加串口中断处理函数。

void USART1_IRQHandler(void)
{
    //HAL_GPIO_TogglePin(GPIOA, led2_Pin);
    if(huart1.Instance->SR & UART_FLAG_RXNE)//接收到数据
    {
        __HAL_UART_CLEAR_PEFLAG(&huart1);//清除空闲中断标志(否则会一直不断进入中断)
    }
    else if(USART1->SR & UART_FLAG_IDLE)//串口空闲时执行
    {
        static uint16_t this_time_rx_len = 0;

        __HAL_UART_CLEAR_PEFLAG(&huart1);

        if ((hdma_usart1_rx.Instance->CR & DMA_SxCR_CT) == RESET)//现在是缓冲区1
        {
            /* Current memory buffer used is Memory 0 */

            //失效DMA或者停止DMA传输
            __HAL_DMA_DISABLE(&hdma_usart1_rx); //HAL_UART_DMAStop(&huart1);
            //获取接收数据长度,长度 = 设定长度 - 剩余长度
            this_time_rx_len = SBUS_RX_BUF_NUM - hdma_usart1_rx.Instance->NDTR;
            //重新设定数据长度
            hdma_usart1_rx.Instance->NDTR = SBUS_RX_BUF_NUM;
            //设定缓冲区1
            hdma_usart1_rx.Instance->CR |= DMA_SxCR_CT;
            //使能DMA
            __HAL_DMA_ENABLE(&hdma_usart1_rx);
            if(this_time_rx_len == RC_FRAME_LENGTH)
            {
                sbus_to_rc(sbus_rx_buf[0], &rc_ctrl);
                //判断遥控器数据是否出错
				RC_data_is_error();
                //sbus_to_usart1(sbus_rx_buf[0]);//这里可以把接收的数据发送给出去
            }
        }
        else
        {
            /* Current memory buffer used is Memory 1 */
          
            //失效DMA
            __HAL_DMA_DISABLE(&hdma_usart1_rx);
            //获取接收数据长度,长度 = 设定长度 - 剩余长度
            this_time_rx_len = SBUS_RX_BUF_NUM - hdma_usart1_rx.Instance->NDTR;
            //重新设定数据长度
            hdma_usart1_rx.Instance->NDTR = SBUS_RX_BUF_NUM;
            //设定缓冲区0
            DMA1_Stream1->CR &= ~(DMA_SxCR_CT);
            //使能DMA
            __HAL_DMA_ENABLE(&hdma_usart1_rx);
            if(this_time_rx_len == RC_FRAME_LENGTH)
            {
                //处理遥控器数据
                sbus_to_rc(sbus_rx_buf[1], &rc_ctrl);
                //判断遥控器数据是否出错
				RC_data_is_error();
				//sbus_to_usart1(sbus_rx_buf[1]);//这里可以把接收的数据发送给出去
            }
        }
		//RC_data_is_error();
    }
}

(3)根据手册处理遥控器数据。

/**
  * @brief          遥控器协议解析
  * @param[in]      sbus_buf: 原生数据指针
  * @param[out]     rc_ctrl: 遥控器数据指
*/
static void sbus_to_rc(volatile const uint8_t *sbus_buf, RC_ctrl_t *rc_ctrl)
{
    if (sbus_buf == NULL || rc_ctrl == NULL)
    {
        return;
    }
    rc_ctrl->header = sbus_buf[0]; 
    rc_ctrl->alt    = sbus_buf[1]; 
    rc_ctrl->ele    = sbus_buf[2]; 
    rc_ctrl->thr	= sbus_buf[3];
    rc_ctrl->rudd   = sbus_buf[4]; 
    rc_ctrl->flag   = sbus_buf[5]; //标志位
    rc_ctrl->verify = sbus_buf[6]; //(BYTE[1]^BYTE[2]^BYTE[3]^BYTE[4]^BYTE[5])&0xff;
    rc_ctrl->tail   = sbus_buf[7]; //数据尾,固定为 0x99            
}

//判断遥控器数据是否出错,
uint8_t RC_data_is_error(void)
{
    //使用了go to语句 方便出错统一处理遥控器变量数据归零
    if (rc_ctrl.header != 0x66)
    {
        goto error;
    }
    //if (RC_abs(rc_ctrl.alt) > RC_CHANNAL_ERROR_VALUE)
    //{
    //    goto error;
    //}

	if (rc_ctrl.verify != ((rc_ctrl.alt ^ rc_ctrl.ele ^ rc_ctrl.thr ^ rc_ctrl.rudd ^ rc_ctrl.flag)&0xff))
    {
        goto error;
    }
    if (rc_ctrl.tail != 0x99)
    {	
        goto error;
    }
    return 0;

error:
    rc_ctrl.header = 0;
    rc_ctrl.alt = 128;
    rc_ctrl.ele = 128;
    rc_ctrl.thr = 0;
    rc_ctrl.rudd = 128;
    rc_ctrl.flag = 0;
    rc_ctrl.verify = 0;
	rc_ctrl.tail = 0;
    return 1;
}

//获取遥控器数据指针
RC_ctrl_t *get_remote_control_point(void)
{
    return &rc_ctrl;
}

到了这里,关于STM32 cubemx+串口空闲中断+DMA双缓冲的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • STM32学习笔记(五)串口空闲中断+DMA实现不定长收发(stm32c8t6)

    记录一下学习过程 DMA DMA,全称为: Direct Memory Access,即直接存储器访问, DMA 传输将数据从一个 地址空间复制到另外一个地址空间。 这一过程无需cpu的参与,从而提高cpu使用的效率 DMA相关的参数:1 数据的源地址、2 数据传输的目标地址 、3 传输宽度,4 传输多少字节,5 传

    2024年02月14日
    浏览(39)
  • 【STM32 CubeMX】串口编程DMA+IDLE中断

    在嵌入式系统中,串口通信是一项关键的任务,而使用DMA(直接内存访问)结合IDLE中断进行串口编程,尤其是在STM32 CubeMX环境中,能够提高系统的效率和性能。STM32 CubeMX为STM32微控制器提供了图形化的配置工具,可以简化初始化代码的生成过程,使得串口编程变得更加容易。

    2024年02月20日
    浏览(50)
  • STM32-HAL库串口DMA空闲中断的正确使用方式+解析SBUS信号

    能够点进这篇文章的小伙伴肯定是对STM32串口DMA空闲中断接收数据感兴趣的啦,今天用这一功能实现串口解析航模遥控器sbus信号时,查阅了很多网友发布的文章(勤劳的搬运工~),包括自己之前写过一篇博客 STM32_HAL库_CubeMx串口DMA通信(DMA发送+DMA空闲接收不定长数据)。本文

    2024年02月09日
    浏览(56)
  • STM32使用串口空闲中断(IDLE)和 DMA接收一串数据流

    方法一、使用宏定义判断IDLE标志位 空闲的定义是总线上在一个字节的时间内没有再接收到数据,USART_IT_IDLE空闲中断是检测到有数据被接收后,总线上在一个字节的时间内没有再接收到数据的时候发生的。 串口空闲中断(UART_IT_IDLE):STM32的IDLE的中断在串口无数据接收的情况

    2024年01月23日
    浏览(49)
  • stm32串口空闲中断+DMA传输接受不定长数据+letter shell 实现命令行

    空闲中断(IDLE),俗称帧中断,即第一帧数据接收完毕到第二帧数据开始接收期间存在一个空闲状态(每接收一帧数据后空闲标志位置1),检测到此空闲状态后即执行中断程序。 空闲中断的优点在于省去了帧头帧尾的检测 ,进入中断程序即意味着已经接收到一组完整数据,仅需

    2024年02月03日
    浏览(51)
  • Stm32407串口1空闲中断+DMA收发(基于标准库实现)

    stm32串口的配置很简单,这里就不赘述了,使用 USART_SendData() 阻塞模式发送数据,或是接收中断配置 “接收缓冲区非空” USART_IT_RXNE ,这种做法效率很低,而且来一个数据中断一次数据处理起来也麻烦。 这里基于STM32F407提供一种串口空闲中断+DMA接收的方式,通过库函数编程

    2024年02月16日
    浏览(41)
  • 【STM32】HAL库 STM32CubeMX——DMA (串口DMA发送接收)

    软件: STM32CubeMX KEIL5 mcuisp 串口通信助手 硬件: STM32F103C8Tx 杜邦线,面包板,USB转TTL DMA,全称Direct Memory Access,即直接存储器访问。 DMA传输将数据从一个地址空间复制到另一个地址空间,提供在外设和存储器之间或者存储器和存储器之间的高速数据传输。 我们知道系统的运

    2024年02月12日
    浏览(57)
  • stm32串口+DMA环形缓冲收发保姆级

    首先在此感谢开源项目,以及大佬们的无私奉献,让每一个逐梦人能够免费学习,再次感谢! 发布只为记录,记性不够,笔记来凑。记得点赞哦 具体实现原理讲起来确实挺复杂,不过用起来还是很NICE的!可以直接移植! 1.1、选择单片机型号 2、配置时钟和串口 或者直接在

    2024年02月10日
    浏览(31)
  • 【STM32】CUBEMX之串口:串口三种模式(轮询模式、中断模式、DMA模式)的配置与使用示例 + 串口重定向 + 使用HAL扩展函数实现不定长数据接收

    目录   总览 使用CUBEMX创建工程的基本配置 CUBEMX中的配置 Keil中的配置 实物连接 串口轮询模式 轮询模式HAL库函数 特点 实验一:发送数据给单片机并让其返回相同值 串口重定向 串口中断模式 在CUBEMX中打开串口中断 中断模式HAL库函数 特点 实验二:使用中断回调完成实验一

    2024年04月10日
    浏览(44)
  • 【STM32 CubeMX】串口编程DMA

    在嵌入式系统中,串口通信是一项至关重要的功能,它允许单片机与外部设备进行数据交换,如传感器、显示器或其他设备。然而,在高速数据传输的场景下,传统的串口通信方式可能会使CPU过于繁忙,从而影响系统的性能。为了解决这一问题,STM32系列微控制器提供了DMA(

    2024年02月20日
    浏览(48)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包