写这篇文章是为了记录下之前做过的项目中用到的一部分关键技术,之前做过的项目中涉及到采用最小开销来实时接收遥控器数据、能够准确验证传输过来数据的准确性,减小误差率,要求能稳定适用于不同的环境。
目录
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双缓冲。
网上教程很多,这里我就不具体说明了,这里可参考别人写的这篇文章。文章来源:https://www.toymoban.com/news/detail-698919.html
虽然我们使用的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模板网!