串口DMA接收不定长数据+双缓存接收方案,移植非常方便,只需要9个字节就可以管理

2019-07-21 02:18发布

学习原子哥的板子已经两年了,现在也如愿以偿的做相关开发工作。在工作中也学习不少软件方案,上次看论坛里有个网友采用串口空闲中断及DMA接收串口不定长数据,感觉这个非常好用。特意花了点时间写好库,方便与大家交流学习。
一、结构体
[mw_shl_code=c,true]__packed typedef struct
{
        u32                              USARTX_Bound;          //波特率
        uint32_t                         USART_RCC      ;              //串口时钟时能
  USART_TypeDef *                  USARTX ;               //串口
        uint8_t                          USART_IRQChannel;      //串口中断线
        uint32_t                         GPIO_RCC_APB2;         //GPIO时钟时能
  GPIO_TypeDef  *                  GPIO_RX;               //GPIO RX端口
  uint16_t                         GPIO_RX_Pin;           //引脚
  GPIO_TypeDef  *                  GPIO_TX;               
  uint16_t                         GPIO_TX_Pin;
  DMA_Channel_TypeDef *            USART_RX_DMA_Channel;  //串口DMA接收通道
        uint8_t                          DMA_IRQChannel            ;      //DMA中断线
        uint32_t                         DMA_FLAG_TC               ;      //DMA传输完成标记      
}
USERT_Irregular_DMA_typ ;    //串口DMA接收不定长数据结构体[/mw_shl_code]

此结构体用于记录此方案所采用的IO、串口、DMA通道等,此结构体的数据一旦确定完成,建议不要改变,此结构体可以直接定义到FLASH中(用关键字 const)


[mw_shl_code=c,true]#define  USART_IrrG_DMA_BUFF_LEN        40   //双缓存大小   最大 65535
__packed typedef struct  
{
u8     RX_BUFF_Comple  : 2 ;   //接收完成标记 0 都没有接收完成   bit0 缓存0接收完成  bit1 缓存1接收完成
u8     RX_BUFF0_STA    : 1 ;   //0 空闲  1 接收完成      
u8     RX_BUFF1_STA    : 1 ;   //0 空闲  1 接收完成  
u8     RX_BUFF_Err     : 2 ;   //0 正常  1 缓存都接收满 未处理   
u8     RX_BUFF_Inx     : 2 ;          //当前正在接收的缓存索引
u16    RX_BUFF0_Size ;         //接收到的数据长度
u8     RX_BUFF0[USART_IrrG_DMA_BUFF_LEN] ;  //数据缓存
u16    RX_BUFF1_Size ;
u8     RX_BUFF1[USART_IrrG_DMA_BUFF_LEN] ;
const  USERT_Irregular_DMA_typ * USERT_Irregular_DMA  ;
}
USART_RX_BUFF;        [/mw_shl_code]

此结构体用于管理双缓存,并向上层提供双缓存当前状态。
宏 USART_IrrG_DMA_BUFF_LEN   用于设置双缓存长度


二、用户调用函数
此方案有三个函数需要用户调用,但不需要更改。
1、初始化
[mw_shl_code=c,true]void USART_DMA_Init(USART_RX_BUFF *USERT_RX_DMA)
{
  //GPIO端口设置
  GPIO_InitTypeDef      GPIO_InitStructure;
        USART_InitTypeDef     USART_InitStructure;
        NVIC_InitTypeDef      NVIC_InitStructure;

        RCC_APB2PeriphClockCmd(USERT_RX_DMA->USERT_Irregular_DMA->GPIO_RCC_APB2, ENABLE);        //使能GPIO时钟
        if(USERT_RX_DMA->USERT_Irregular_DMA->USARTX == USART1)
        {
                RCC_APB2PeriphClockCmd(USERT_RX_DMA->USERT_Irregular_DMA->USART_RCC, ENABLE);          //使能串口1时钟
        }
        else
        {
                RCC_APB1PeriphClockCmd(USERT_RX_DMA->USERT_Irregular_DMA->USART_RCC, ENABLE);         //使能串口时钟
        }
  //USART_TX  
  GPIO_InitStructure.GPIO_Pin = USERT_RX_DMA->USERT_Irregular_DMA->GPIO_TX_Pin ;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;                                   //复用推挽输出
  GPIO_Init(USERT_RX_DMA->USERT_Irregular_DMA->GPIO_TX, &GPIO_InitStructure);
   
//USART_RX         
  GPIO_InitStructure.GPIO_Pin = USERT_RX_DMA->USERT_Irregular_DMA->GPIO_RX_Pin;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;              //浮空输入
  GPIO_Init(USERT_RX_DMA->USERT_Irregular_DMA->GPIO_RX, &GPIO_InitStructure);  
       
        //Usart NVIC 配置
  NVIC_InitStructure.NVIC_IRQChannel = USERT_RX_DMA->USERT_Irregular_DMA->USART_IRQChannel;
        NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3 ; //抢占优先级3
        NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3      ; //子优先级3
        NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE         ; //IRQ通道使能
        NVIC_Init(&NVIC_InitStructure);                          //根据指定的参数初始化VIC寄存器
  
   //USART 初始化设置
        USART_DeInit(USERT_RX_DMA->USERT_Irregular_DMA->USARTX);     //先复位串口
        USART_InitStructure.USART_BaudRate = USERT_RX_DMA->USERT_Irregular_DMA->USARTX_Bound;      //波特率
        USART_InitStructure.USART_WordLength = USART_WordLength_8b;      //字长为8位数据格式
        USART_InitStructure.USART_StopBits = USART_StopBits_1;           //一个停止位
        USART_InitStructure.USART_Parity = USART_Parity_No;              //无奇偶校验位
        USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//无硬件数据流控制
        USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;         //收发模式

  USART_Init(USERT_RX_DMA->USERT_Irregular_DMA->USARTX, &USART_InitStructure);           //初始化串口
        USART_ClearITPendingBit(USERT_RX_DMA->USERT_Irregular_DMA->USARTX, USART_IT_IDLE) ;    //清除空闲中断
  USART_ITConfig(USERT_RX_DMA->USERT_Irregular_DMA->USARTX, USART_IT_IDLE, ENABLE);      //开启空闲中断

//DMA 配置--------------------------------------------------------------------------------------------------
        USART_DMACmd(USERT_RX_DMA->USERT_Irregular_DMA->USARTX,USART_DMAReq_Rx,ENABLE);  //开启DMA接收       
        MY_USART_Iglr_DMA_CONFIG(USERT_RX_DMA);                                          //DMA配置    先由缓存0 接收
        USART_RX_DMA_START(USERT_RX_DMA);                                                //开启DMA
  USART_Cmd(USERT_RX_DMA->USERT_Irregular_DMA->USARTX, ENABLE);                                                                                                                               //使能串口
}[/mw_shl_code]

此函数是根据结构体指针变量  USERT_RX_DMA   的值 来初始化,其中包括时钟、串口、IO、DMA、中断等配置,无需修改,直接调用


2、串口中断函数
[mw_shl_code=c,true]void  USART_RX_DMA_USART_IRQ(USART_RX_BUFF * USERT_RX_DMA)   //必须在串口中断函数中调用
{
if(USART_GetITStatus(USERT_RX_DMA->USERT_Irregular_DMA->USARTX, USART_IT_IDLE) != RESET)   //如果空闲中断
{
         USERT_RX_DMA->USERT_Irregular_DMA->USARTX->DR ;  //空读一次
   USART_ClearFlag(USART1, USART_IT_IDLE);          //清除空闲中断标记
         
         //标记缓存接收完成--------------------------------------------------
   USERT_RX_DMA->RX_BUFF_Comple |= (0X01<<USERT_RX_DMA->RX_BUFF_Inx );  
         
         if( USERT_RX_DMA->RX_BUFF_Inx == 0)  //如果当前是第0个缓存接收
         {
                //得到缓存长度--------------------------
                USERT_RX_DMA->RX_BUFF0_Size = USART_IrrG_DMA_BUFF_LEN - DMA_GetCurrDataCounter(USERT_RX_DMA->USERT_Irregular_DMA->USART_RX_DMA_Channel);
                USERT_RX_DMA->RX_BUFF_Inx = 1 ;   //设置缓存1 接收数据
         }
         else if( USERT_RX_DMA->RX_BUFF_Inx == 1)
         {
                //得到缓存长度--------------------------
                USERT_RX_DMA->RX_BUFF1_Size = USART_IrrG_DMA_BUFF_LEN - DMA_GetCurrDataCounter(USERT_RX_DMA->USERT_Irregular_DMA->USART_RX_DMA_Channel);
                USERT_RX_DMA->RX_BUFF_Inx = 0 ;   //设置缓存1 接收数据       
         }
         
         if( USERT_RX_DMA->RX_BUFF_Comple == 0x03 )  //如果缓存0 缓存1 都接收满
         {
          USERT_RX_DMA->RX_BUFF_Err = 1 ;  //错误 缓存满标记
         }
         else   //正常   则开启 DMA接收
         {
                USART_RX_DMA_START(USERT_RX_DMA);  //开启DMA
         }
}   
}[/mw_shl_code]

此函数必须在对应的串口中断中调用,起作用是用于判断及设置双缓存状态及切换缓存。无需修改,直接调用。

3、DMA中断函数
[mw_shl_code=c,true]
void  USART_RX_DMA_DMA_IRQ(USART_RX_BUFF * USERT_RX_DMA)  //必须在DMA中断函数中调用
{
if( DMA_GetFlagStatus(USERT_RX_DMA->USERT_Irregular_DMA->DMA_FLAG_TC) != RESET)
{
         DMA_ClearFlag(USERT_RX_DMA->USERT_Irregular_DMA->DMA_FLAG_TC);  //清楚接收完成标记
         
         //标记缓存接收完成--------------------------------------------------         
   USERT_RX_DMA->RX_BUFF_Comple |= (0X01<<USERT_RX_DMA->RX_BUFF_Inx );  //标记缓存接收完成

         if( USERT_RX_DMA->RX_BUFF_Inx == 0)  //如果当前是第0个缓存接收
         {
                //得到缓存长度--------------------------
                USERT_RX_DMA->RX_BUFF0_Size = USART_IrrG_DMA_BUFF_LEN - DMA_GetCurrDataCounter(USERT_RX_DMA->USERT_Irregular_DMA->USART_RX_DMA_Channel);
                USERT_RX_DMA->RX_BUFF_Inx = 1 ;   //设置缓存1 接收数据
         }
         else if( USERT_RX_DMA->RX_BUFF_Inx == 1)
         {
                //得到缓存长度--------------------------
                USERT_RX_DMA->RX_BUFF1_Size = USART_IrrG_DMA_BUFF_LEN - DMA_GetCurrDataCounter(USERT_RX_DMA->USERT_Irregular_DMA->USART_RX_DMA_Channel);
                USERT_RX_DMA->RX_BUFF_Inx = 0 ;   //设置缓存1 接收数据       
         }
         
         if( USERT_RX_DMA->RX_BUFF_Comple == 0x03 )  //如果缓存0 缓存1 都接收满
         {
          USERT_RX_DMA->RX_BUFF_Err = 1 ;  //错误 缓存满标记
         }
         else   //正常   则开启 DMA接收
         {
                USART_RX_DMA_START(USERT_RX_DMA);  //开启DMA
         }
}
}
[/mw_shl_code]

必须在串口中断中调用!此函数与串口中断函数功能一致,用于设置双缓存当前状态及切换缓存。无需修改直接调用。


三、应用例子
先定义一个 USERT_Irregular_DMA_typ 结构体 ,用于加载此双缓存的硬件信息。本例子此结构体定义如下:
[mw_shl_code=c,true]const  USERT_Irregular_DMA_typ  USERT_Irregular_DMA  =
{
   115200,
         RCC_APB2Periph_USART1,
         USART1,
         USART1_IRQn,
         RCC_APB2Periph_GPIOA,
         GPIOA,
         GPIO_Pin_10,
         GPIOA,
         GPIO_Pin_9,
         DMA1_Channel5,
   DMA1_Channel5_IRQn,
         DMA1_FLAG_TC5,
};[/mw_shl_code]

声明保存在FLASH中,用于节约SRAM


再定义一个 USART_RX_BUFF 结构体 ,并把刚才定义的 USERT_Irregular_DMA 变量 加载到结构体中(如果一个个赋值太麻烦),本例子定义如下:
[mw_shl_code=c,true]USART_RX_BUFF   USRT_RX_buff = {        //串口 DMA接收不定长数据 对象
   0,
         0,
         0,
         0,
         0,
         0,
        {0},
         0,
        {0},
         &USERT_Irregular_DMA ,
};[/mw_shl_code]


前5个成员变量用于表示双缓存状态,这里全设为0,接下来4个成员分别为对应缓存接收到的数据长度及接收缓存。最后加载双缓存硬件数据结构体。


主程序如下:
[mw_shl_code=c,true]int main(void)
{
volatile u16  i = sizeof(USART_RX_BUFF) ;          //用于查看变量所用SRAM 大小
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);        //设置NVIC中断分组2:2位抢占优先级,2位响应优先级
delay_init();
USART_DMA_Init(&USRT_RX_buff) ;
while(1)
{
  if(USRT_RX_buff.RX_BUFF_Comple > 0)
        {
          if(USRT_RX_buff.RX_BUFF_Comple&0x01)
                {
           //数据处理-------------------------------------------------------------------
                  printf("缓存0已接收完毕 ") ;
                        printf("接收数据长度为 :%d ",USRT_RX_buff.RX_BUFF0_Size);
                        printf("数据为: %s ",USRT_RX_buff.RX_BUFF0) ;
                //----------------------------------------------------------------------------
                        USART_DMA_CLR_BUFF(&USRT_RX_buff.RX_BUFF0[0],0,USART_IrrG_DMA_BUFF_LEN) ;   //清空缓存
                        USRT_RX_buff.RX_BUFF_Comple &=~0x01 ;   //清楚接收完成标记
                }
               
                if(USRT_RX_buff.RX_BUFF_Comple&0x02)
                {
                 //数据处理-----------------------------------------------------
                  printf("缓存1已接收完毕 ") ;
                        printf("接收数据长度为 :%d ",USRT_RX_buff.RX_BUFF1_Size);
                        printf("数据为: %s ",USRT_RX_buff.RX_BUFF1) ;
                //--------------------------------------------------------------
                        USART_DMA_CLR_BUFF(&USRT_RX_buff.RX_BUFF1[0],0,USART_IrrG_DMA_BUFF_LEN) ;   //清空缓存               
                        USRT_RX_buff.RX_BUFF_Comple &=~0x02 ;   //清楚接收完成标记
                }
                if(USRT_RX_buff.RX_BUFF_Err > 0 )   //如果双缓存 溢出了    接收数据过长   缓存开小了
                {
                  USRT_RX_buff.RX_BUFF_Err  = 0 ;    //清楚错误标记
                        printf(" 错误!双缓存溢出!    接收数据过长或者双缓存太小 ") ;
                         //开启DMA  -----------------
                  USART_RX_DMA_START(&USRT_RX_buff);   //必须要手动开启   否则无法运行
                }
        }
}
}

void DMA1_Channel5_IRQHandler(void)    //DMA 中断函数
{
USART_RX_DMA_DMA_IRQ(&USRT_RX_buff) ;
}


void USART1_IRQHandler(void)                        //串口1中断服务程序
{
  USART_RX_DMA_USART_IRQ(&USRT_RX_buff) ;
}
[/mw_shl_code]


这里 变量i 用于指示 变量 USART_RX_BUFF 占用 SAM大小。
串口中断函数及DMA中断函数写在 main.c 文件中 是用于方便提示两个函数的调用
注意!
  如果接收到的数据长度大于单个缓存长度,则双缓存会关闭,必须要手动开启才能继续运行。这里只把单个缓存长度设为40字节也是为了方便测试出这个情况。


工程代码:
串口DMA接收不定长数据.zip (341.84 KB, 下载次数: 993) 2016-7-6 09:43 上传 点击文件名下载附件
串口DMA接收不定长数据方案


谢谢论坛中的各位大神的分享,让我受益匪浅!




友情提示: 此问题已得到解决,问题已经关闭,关闭后问题禁止继续编辑,回答。
该问题目前已经被作者或者管理员关闭, 无法添加新回复
22条回答
不吃鱼的老鼠
1楼-- · 2019-07-21 07:17
 精彩回答 2  元偷偷看……
龙之谷
2楼-- · 2019-07-21 11:47
谢谢分享,稍后有时间学习一下...
yyx112358
3楼-- · 2019-07-21 16:58
漂亮!论坛一代更比一代强
513393302@qq.co
4楼-- · 2019-07-21 17:03
 精彩回答 2  元偷偷看……
513393302@qq.co
5楼-- · 2019-07-21 18:29
龙之谷 发表于 2016-7-6 10:04
谢谢分享,稍后有时间学习一下...

大神,谢谢。多多交流
513393302@qq.co
6楼-- · 2019-07-21 19:41
 精彩回答 2  元偷偷看……

一周热门 更多>