中级会员
 
- 积分
- 441
- 金钱
- 441
- 注册时间
- 2014-8-11
- 在线时间
- 84 小时
|
本帖最后由 战舰水手 于 2020-5-9 02:47 编辑
之前在论坛上分享了一篇串口DMA双缓冲数据传输的帖子(http://www.openedv.com/forum.php?mod=viewthread&tid=64008&extra=),近期发现上传的源码附件没法下载了,该帖是我在早期项目需要的情况下基于F1学习状态下做的测试源码,并在之后的项目中不断进行了优化和实测。因为更换过几次电脑所以该贴的源码也找不到了。故打算重新制作一篇分享贴,将我在项目中的一些案例分享给大家。
因为时间不是太充裕,故该贴会不定时更新,如果该贴中的原理和代码出现低级错误望大家积极指出,我会定时纠正。
源码基于STM32F1/4HAL库函数,部分内容基于正点原子相关示例完成,悉知。
更新日志:
1、2020/1/16
开贴开篇
2、2020/05/09
上传FIFO接口函数
上传基于MiniSTM32开发板的串口+IDLE+DMA+FIFO例程
3、待更新(DMA介入的数据接收方式及软双缓冲)
目录
一、简单的串口数据接收方式:
1、IT+尾字节判断
2、IT+定时器超时判断
3、IDEL(空闲中断)
二、数据队列管理FIFO(接口函数已上传附件)
- #define FIFO_SIZE 256 //定义接受串口数据的队列数组大小
- typedef enum
- {
- FIFO_FREE = 0 , //空闲
- FIFO_OK = 0 , //读取或写入成功
- FIFO_BUSY = 0x01 , //忙
- FIFO_OVER = 0x02 , //溢出错误
- FIFO_NULL = 0X03 , //缓存空,或者无新的数据需要处理
- }
- FIFO_STA_ENUM; //FIFO状态枚举
- __packed typedef struct
- {
- u8 FIFO_STA; //FIFO当前状态 忙 空闲 溢出错误
- u16 FIFO_BUF_SIZE; //FIFO缓存中有效数据个数
- u16 W_ADDR_NUM; //写指针
- u16 R_ADDR_NUM; //读指针
- u16 W_CIRCLE_CNT; //写指针循环次数
- u16 R_CIRCLE_CNT; //读指针循环次数
- <font color="Red">//以下错误字节数计数我主要是用来测试通讯协议和解析效率</font>
- u16 W_ERR_NUM; //写错误字节计数
- u16 R_ERR_NUM; //读错误字节计数
- u8 FIFO_BUF[FIFO_SIZE] ; //FIFO缓存
- }
- FIFO_typ; //FIFO结构体
- //------------------------ User Function ------------------------------
- //队列申请内存
- u8 SRAM_DATA_Init(void);
- //清空FIFO
- void FIFO_CLR_BUF(FIFO_typ * FIFO);
- //返回可写入队列的数组长度最大值
- u16 FIFO_WRITE_VALID_NUM(FIFO_typ * FIFO);
- //返回从队列读出数组的最大长度
- u16 FIFO_READ_VALID_NUM(FIFO_typ * FIFO);
- //[底层]向队列缓存写入一个字节
- void FIFO_WRITE_BYTE_NO(FIFO_typ *FIFO,u8 data);
- //[底层]读取队列缓存一个字节
- u8 FIFO_READ_BYTE_NO(FIFO_typ *FIFO);
- //向队列缓存写入一个数组
- //返回 0 成功 其它失败
- u8 FIFO_WRITE_BUF(FIFO_typ *FIFO,u8 *buf,u16 len);
- //向队列缓存写入一个字节
- u8 FIFO_WRITE_BYTE(FIFO_typ *FIFO,u8 data);
复制代码
- //FIFO初始赋值
- void FIFO_CLR_BUF(FIFO_typ * FIFO) //清空FIFO
- {
- FIFO->FIFO_STA = FIFO_FREE; //状态为空闲
- FIFO->FIFO_BUF_SIZE = FIFO_SIZE; //有效数据清零
- FIFO->W_ADDR_NUM = 0; //写指针清零
- FIFO->R_CIRCLE_CNT = 0;//读指针循环次数清零
- FIFO->W_CIRCLE_CNT = 0;//写指针循环次数清零
- FIFO->R_ADDR_NUM = 0; //读指针清零
- FIFO->W_ERR_NUM = 0;
- FIFO->R_ERR_NUM = 0;
- }
- //返回可写入队列的数组长度最大值
- u16 FIFO_WRITE_VALID_NUM(FIFO_typ * FIFO)
- {
- if(FIFO->W_CIRCLE_CNT == FIFO->R_CIRCLE_CNT)
- return FIFO->FIFO_BUF_SIZE+FIFO->R_ADDR_NUM-FIFO->W_ADDR_NUM;
- else
- return FIFO->R_ADDR_NUM-FIFO->W_ADDR_NUM;
- }
- //返回从队列读出数组的最大长度
- u16 FIFO_READ_VALID_NUM(FIFO_typ * FIFO)
- {
- if(FIFO->W_CIRCLE_CNT == FIFO->R_CIRCLE_CNT)
- return FIFO->W_ADDR_NUM-FIFO->R_ADDR_NUM;
- else
- return FIFO->FIFO_BUF_SIZE+FIFO->W_ADDR_NUM-FIFO->R_ADDR_NUM;
- }
- //[底层]向队列缓存写入一个字节
- void FIFO_WRITE_BYTE_NO(FIFO_typ *FIFO,u8 data)
- {
- FIFO->FIFO_BUF[FIFO->W_ADDR_NUM] = data ; //写入数据
-
- FIFO->W_ADDR_NUM++; //一定要先写完 再地址变更
-
- if(FIFO->W_ADDR_NUM >= FIFO->FIFO_BUF_SIZE) //如果到了缓存尾
- {
- FIFO->W_ADDR_NUM = 0 ; //回归到缓存头
- FIFO->W_CIRCLE_CNT++;
- }
- }
- //[底层]读取队列缓存一个字节
- u8 FIFO_READ_BYTE_NO(FIFO_typ *FIFO)
- {
- u8 data;
- data = FIFO->FIFO_BUF[FIFO->R_ADDR_NUM] ; //读取数据
- FIFO->R_ADDR_NUM++; //一定要先读完 再地址变更
-
- if(FIFO->R_ADDR_NUM >= FIFO->FIFO_BUF_SIZE) //如果到了缓存尾
- {
- FIFO->R_ADDR_NUM = 0 ; //回归到缓存头
- FIFO->R_CIRCLE_CNT++;
- }
- return data;
- }
- //向队列缓存写入一个数组
- //返回 0 成功 其它失败
- u8 FIFO_WRITE_BUF(FIFO_typ *FIFO,u8 *buf,u16 len)
- {
- u16 i=0;
- if((FIFO_WRITE_VALID_NUM(FIFO))<len) //如果缓存有效数据数量与写入总和 超过最大有效数据
- {
- return FIFO_OVER; //返回满
- }
- for(i=0;i<len;i++)
- {
- FIFO_WRITE_BYTE_NO(FIFO,buf[i]);
- }
- return FIFO_OK;
- }
复制代码 上述接口函数中队列缓存的大小是由宏定义#define FIFO_SIZE 256决定,但我们在实际应用中可能涉及到多个外设数据通道需要用到该FIFO接口函数,这些通道对应的
队列缓存大小可能会不一致,这种情况下我们将结构体FIFO_typ中的数组做指针链表,即结构体中的变量为队列缓存的头地址,实现如下
- 背景:实现串口1队列缓存大小为256、串口2队列缓存大小为1024
- __packed typedef struct
- {
- u8 FIFO_STA; //FIFO当前状态 忙 空闲 溢出错误
- u16 FIFO_BUF_SIZE; //FIFO缓存中有效数据个数
- u16 W_ADDR_NUM; //写指针
- u16 R_ADDR_NUM; //读指针
- u32 W_CIRCLE_CNT; //写指针循环次数
- u32 R_CIRCLE_CNT; //读指针循环次数
- u16 W_ERR_NUM; //写错误字节计数
- u16 R_ERR_NUM; //读错误字节计数
- //u8 FIFO_BUF[FIFO_SIZE] ; //FIFO缓存
- <b><font color="Red">u8 *FIFO_BUF;//这里的变量用来指向队列缓存的头地址</font></b>
- }
- FIFO_typ; //FIFO结构体
- extern FIFO_typ *U1_RX_FIFO;
- extern FIFO_typ *U2_RX_FIFO;
- #define U1_RX_FIFO_BUF_SIZE 256
- #define U2_RX_FIFO_BUF_SIZE 1024
- //以下为结构体链表实现方法
- U1_RX_FIFO_BUF= (u8*)mymalloc(U1_RX_FIFO_BUF_SIZE) ;//队列缓存申请内存
- U2_RX_FIFO_BUF= (u8*)mymalloc(U2_RX_FIFO_BUF_SIZE) ;//队列缓存申请内存
- U1_RX_FIFO= (FIFO_typ*)mymalloc(sizeof(FIFO_typ)) ;//结构体申请内存
- U2_RX_FIFO= (FIFO_typ*)mymalloc(sizeof(FIFO_typ)) ;//结构体申请内存
- U1_RX_FIFO->FIFO_BUF_SIZE=U1_RX_FIFO_BUF_SIZE;
- U2_RX_FIFO->FIFO_BUF_SIZE=U2_RX_FIFO_BUF_SIZE;
- U1_RX_FIFO->FIFO_BUF=U1_RX_FIFO_BUF;//将申请到的队列缓存地址赋值给结构体中存储队列缓存地址的变量
- U2_RX_FIFO->FIFO_BUF=U2_RX_FIFO_BUF;//将申请到的队列缓存地址赋值给结构体中存储队列缓存地址的变量
复制代码
以上的实现的接口函数中并没有涉及从队列中读取一个数组,这是由于数据读取后需要根据相关的通讯协议做解析,故此处只讲述接口函数的应用方法,如下:
- FIFO_STA_ENUM U1_RxData_Polling(void)
- {
- u16 len,i;
- //记录当前读写指针位置
- u16 read_addr_num=U1_RX_FIFO->R_ADDR_NUM;
- u16 read_circle_num=U1_RX_FIFO->R_CIRCLE_CNT;
- u8 *CLC_BUF;//数据
- u6 clc_byte_num;
- //获取当前队列缓存中未处理的数据长度
- len=FIFO_READ_VALID_NUM(U1_RX_FIFO);
- if(len==0)
- {
- return FIFO_NULL;//返回无数据需处理
- }
- else
- {
- CLC_BUF=(u8*)mymalloc(len+1);
- CLC_BUF[len]=0;
- for(i=0;i<len;i++)
- {
- CLC_BUF[i]=FIFO_READ_BYTE_NO(U1_RX_FIFO);//将未处理的数据赋值给*CLC_BUF
- }
- //接下来就是根据具体通讯协议解析数据,如果是串口配置命令的话单次polling队列我都是只解析一个命令就退出
- //此处要注意的是我们逐字节校验是否符合协议的话要把错误的字节数也记录下来形成真实的*CLC_BUF中已处理的数据(clc_byte_num),因为我们是直接将未处理的所有数据全部提取出来的,提取的时候读指针
- //也就被移动到了未处理数据的最后,若单次polling我们解析出一条命令后退出polling那需要在退出前将读指针(U1_RX_FIFO->R_ADDR_NUM)放到我们实际停止的位置
- 。。。。。。。。
- //以下实现将读指针放置在解析停止处
- U1_RX_FIFO->R_ADDR_NUM = read_addr_num;
- U1_RX_FIFO->R_CIRCLE_CNT = read_circle_num; //读指针归位
- for(i=0;i<clc_byte_num;i++)
- {
- FIFO_READ_BYTE_NO(U1_RX_FIFO); //将读指针放置在有效帧尾或写指针处
- }
- }
-
复制代码
三、DMA介入的数据接收方式
四、DMA双缓冲数据接收
1、软双缓冲
2、硬双缓冲
五、IDLE+DMA+FIFO应用示例
背景:基于HAL库实现STM32F103RCT6串口1的接收IDLE+DMA+FIFO
技术路线:
1:DMA配置
- //DMA1的各通道配置
- //这里的传输形式是固定的,这点要根据不同的情况来修改
- //从存储器->外设模式/8位数据宽度/存储器增量模式
- //chx:DMA通道选择,DMA1_Channel1~DMA1_Channel7
- //串口1初始化时调用
- void U1_RX_DMA_Config(void)
- {
- __HAL_RCC_DMA1_CLK_ENABLE(); //DMA1时钟使能
- __HAL_LINKDMA(&UART1_Handler,hdmarx,UART1RxDMA_Handler); //将DMA与USART1联系起来(发送DMA)
-
- //Rx DMA配置
- UART1RxDMA_Handler.Instance=DMA1_Channel5; //通道选择
- UART1RxDMA_Handler.Init.Direction=DMA_PERIPH_TO_MEMORY; //外设到存储器
- UART1RxDMA_Handler.Init.PeriphInc=DMA_PINC_DISABLE; //外设非增量模式
- UART1RxDMA_Handler.Init.MemInc=DMA_MINC_ENABLE; //存储器增量模式
- UART1RxDMA_Handler.Init.PeriphDataAlignment=DMA_PDATAALIGN_BYTE; //外设数据长度:8位
- UART1RxDMA_Handler.Init.MemDataAlignment=DMA_MDATAALIGN_BYTE; //存储器数据长度:8位
- UART1RxDMA_Handler.Init.Mode=DMA_NORMAL; //外设普通模式
- UART1RxDMA_Handler.Init.Priority=DMA_PRIORITY_MEDIUM; //中等优先级
-
- HAL_DMA_DeInit(&UART1RxDMA_Handler);
- HAL_DMA_Init(&UART1RxDMA_Handler);
-
- //HAL_NVIC_DisableIRQ(DMA1_Channel5_IRQn); //使能USART1_RX_DMA中断通道
- HAL_NVIC_SetPriority(DMA1_Channel5_IRQn,1,0); //抢占优先级1,子优先级0
- }
- void DMA1_Channel5_IRQHandler(void)
- {
- #if SYSTEM_SUPPORT_OS //使用OS
- OSIntEnter();
- #endif
- // HAL_UART_DMAStop(&UART1_Handler); //先停止DMA,暂停接收
- if(DMA1->ISR&(1<<17))//传输完成
- {
- DMA1->IFCR|=(1<<17);
- USART1RxDMA_callback(); //执行回调函数,读取数据等操作在这里面处理
- }
- if(DMA1->ISR&(1<<18))//传输半完成
- {
- DMA1->IFCR|=(1<<18);
- }
- #if SYSTEM_SUPPORT_OS //使用OS
- OSIntExit();
- #endif
- }
- void USART1RxDMA_callback(void)
- {
- if(USART1_DMA_Status.USART_IS_IDLE!=1)
- {
- USART1_DMA_Status.DMA_ReceiveOK=1;
- FIFO_WRITE_BUF(U1_RX_FIFO,USART1_RX_DMA_BUF,USART1_RX_DMA_BUF_SIZE);
- }
- USART1_DMA_Status.USART_IS_IDLE=0;
- }
复制代码 2:串口1空闲中断接收函数
- //串口接收空闲中断
- void UsartReceive_IDLE(UART_HandleTypeDef *huart)
- {
- //当触发了串口空闲中断
- __IO uint32_t tmpreg;
- if((__HAL_UART_GET_FLAG(huart,UART_FLAG_IDLE) != RESET))
- {
- if(huart->Instance == USART1)
- {
- /* 1.清除标志 */
- __HAL_UART_CLEAR_IDLEFLAG(huart); //清除空闲标志
- tmpreg = huart->Instance->SR;
- tmpreg = huart->Instance->DR;
-
- /* 2.读取DMA */
- HAL_UART_DMAStop(huart); //先停止DMA,暂停接收
- /* 3.搬移数据进行其他处理 */
- U1_RX_FIFO->FIFO_STA=FIFO_BUSY; //队列忙
- FIFO_WRITE_BUF(U1_RX_FIFO,USART1_RX_DMA_BUF,USART1_RX_DMA_BUF_SIZE - (__HAL_DMA_GET_COUNTER(&UART1RxDMA_Handler)));
- U1_RX_FIFO->FIFO_STA=FIFO_FREE; //队列空闲
- USART1_DMA_Status.DMA_ReceiveOK=1;
- USART1_DMA_Status.USART_IS_IDLE=1;
- /* 4.开启新的一次DMA接收 */
- HAL_UART_Receive_DMA(huart,(u8*)&USART1_RX_DMA_BUF, USART1_RX_DMA_BUF_SIZE); //重新DMA接收
- __HAL_DMA_ENABLE_IT(&UART1RxDMA_Handler, DMA_IT_TC);
- }
- }
- UNUSED(tmpreg);
- }
- //串口1中断服务程序
- void USART1_IRQHandler(void)
- {
- #if SYSTEM_SUPPORT_OS //使用OS
- OSIntEnter();
- #endif
- UsartReceive_IDLE(&UART1_Handler);//串口空闲中断处理
- HAL_UART_IRQHandler(&UART1_Handler);//HAL库串口处理
-
- #if SYSTEM_SUPPORT_OS //使用OS
- OSIntExit();
- #endif
- }
-
- void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart)
- {
- if(huart->ErrorCode&HAL_UART_ERROR_ORE)
- {
- __HAL_UART_CLEAR_OREFLAG(huart);
- }
- }
复制代码
|
|