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搬运的串口数据,并将数组的地址传给化函数,具体代码如下所示:

//初始化代码
#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;
}

  • 6
    点赞
  • 52
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

洲洲不是州州

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值