1 引言
队列的基础知识和介绍参考求职笔记之数据结构与算法04_栈和队列、STM32进阶之串口环形缓冲区实现。
顺序FIFO缓冲区存在“假上溢”现象,由于入队和出队操作使头尾指针只增不减导致被删元素的空间无法利用,队尾指针超过缓冲区空间的上界而不能入队为克服“假上溢”现象。如果将缓冲区空间的首尾相连,当指针访问到内存最后一个位置时,再折回第一个内存位置,此时就形成了一个环形FIFO缓存区。其中,头指针指向环形缓冲区中可读的数据,尾指针指向环形缓冲区中可写的缓冲空间。
环形FIFO缓冲区本质是“生产者消费者”模型,生产者以某一速度存入数据,消费者以某一速度取出数据,为了保证两者的同步,需要使用环形缓冲区。比如在stm32串口接收数据的速度大于数据的处理速度,来不及处理的数据会被覆盖,造成数据丢包现象,此时可以引入环形缓冲区缓存未来得及处理的数据。此外,当需要接收数据量较大时,不能存储所有数据,也可以采用环形缓冲区。
注意:生产者和消费者的速度不用时刻一致,但总体的平均速度必须相等;如果消费(数据处理)效率过低,导致生产(数据接收)过剩,从而覆盖未读出的数据,导致出错,此时需增加环形缓冲区内存大小或者优化代码效率;在多线程访问时,需要采用互斥机制保证数据安全。
2 KFIFO
Linux中的kfifo “其形也,翩若惊鸿,婉若游龙”,简洁、优雅、高效。
2.1 KFIFO移植
本文kfifo.c和kfifo.h复制于linux-2.6.24.y内核,对应路径分别为root/include/linux/kfifo.h、root/kernel/kfifo.c,经过了以下几个修改:
- 删除或注释掉无关的或不必要的代码,如对内核头文件的引用,smp_rmb()函数等;
- 重新实现了某些宏或函数,比如函数min,宏is_power_of_2(n),BUG_ON(x)等。此外,将函数spin_lock_irqsave和spin_unlock_irqrestore进行改写lock_irqsave和unlock_irqrestore,实现STM32的开中断、关中断,用以保护FIFO的读写索引。
移植后的kfifo.c和kfifo.h如下,代码的分析请看注释和后面的补充。
kfifo.h
#ifndef _LINUX_KFIFO_H
#define _LINUX_KFIFO_H
#include "stm32f1xx_hal.h"
#include "gpio.h"
#define __USE__CLZ 1
#define configPRIO_BITS 2
#define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 1
static inline unsigned int min(unsigned int x,unsigned int y)
{
unsigned int __min1=x,__min2 = y;
return __min1 < __min2 ? __min1: __min2;
}
#define is_power_of_2(n) (n != 0 && ((n & (n - 1)) == 0))
#define STR(x) VAL(x)
#define VAL(x) #x
#define kBUG_ON(char) printf("BUG_ON Error:%s\r\n",char)
#define BUG_ON(x) ((x)? 0 :kBUG_ON(__FILE__ ":" STR(__LINE__) " " #x"\n" ))
struct kfifo {
unsigned char *buffer;
unsigned int size;
unsigned int in;
unsigned int out;
};
extern struct kfifo *kfifo_init(unsigned char *buffer, unsigned int size);
extern struct kfifo *kfifo_alloc(unsigned int size);
extern void kfifo_free(struct kfifo *fifo);
extern unsigned int __kfifo_put(struct kfifo *fifo,
unsigned char *buffer, unsigned int len);
extern unsigned int __kfifo_get(struct kfifo *fifo,
unsigned char *buffer, unsigned int len);
static inline void __kfifo_reset(struct kfifo *fifo)
{
fifo->in = fifo->out = 0;
}
static inline unsigned int __kfifo_len(struct kfifo *fifo)
{
return fifo->in - fifo->out;
}
#define portFORCE_INLINE __forceinline
#define configMAX_SYSCALL_INTERRUPT_PRIORITY ( configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) )
#define portDISABLE_INTERRUPTS() vPortRaiseBASEPRI()
#define portENABLE_INTERRUPTS() vPortSetBASEPRI( 0 )
static portFORCE_INLINE void vPortSetBASEPRI( uint32_t ulBASEPRI )
{
__asm
{
msr basepri, ulBASEPRI
}
}
static portFORCE_INLINE void vPortRaiseBASEPRI( void )
{
uint32_t ulNewBASEPRI = configMAX_SYSCALL_INTERRUPT_PRIORITY;
__asm
{
msr basepri, ulNewBASEPRI
dsb
isb
}
}
#define lock_irqsave() portDISABLE_INTERRUPTS()
#define unlock_irqrestore() portENABLE_INTERRUPTS()
static inline void kfifo_reset(struct kfifo *fifo)
{
lock_irqsave();
__kfifo_reset(fifo);
unlock_irqrestore();
}
static inline unsigned int kfifo_put(struct kfifo *fifo,
unsigned char *buffer, unsigned int len)
{
unsigned int ret;
lock_irqsave();
ret = __kfifo_put(fifo, buffer, len);
unlock_irqrestore();
return ret;
}
static inline unsigned int kfifo_get(struct kfifo *fifo,
unsigned char *buffer, unsigned int len)
{
unsigned int ret;
lock_irqsave();
ret = __kfifo_get(fifo, buffer, len);
if (fifo->in == fifo->out)
fifo->in = fifo->out = 0;
unlock_irqrestore();
return ret;
}
static inline unsigned int kfifo_len(struct kfifo *fifo)
{
unsigned int ret;
lock_irqsave();
ret = __kfifo_len(fifo);
unlock_irqrestore();
return ret;
}
#endif
kfifo.c
#include "kfifo.h"
#include <string.h>
#include <stdlib.h>
#include "stdio.h"
#ifndef __USE__CLZ
static inline unsigned int roundup_pow_of_two(unsigned int v) {
v--;
v |= v >> 1;
v |= v >> 2;
v |= v >> 4;
v |= v >> 8;
v |= v >> 16;
v++;
return v;
}
static inline unsigned int rounddown_pow_of_two(unsigned int n) {
n|=n>>1; n|=n>>2; n|=n>>4; n|=n>>8; n|=n>>16;
return (n+1) >> 1;
}
#else
static inline unsigned int roundup_pow_of_two(unsigned int date_roundup_pow_of_two )
{
return ( 1UL << ( 32UL - ( unsigned int ) __clz( (date_roundup_pow_of_two) ) ) );
}
#endif
struct kfifo *kfifo_init(unsigned char *buffer, unsigned int size)
{
struct kfifo *fifo;
BUG_ON(is_power_of_2(size));
fifo=(struct kfifo *) malloc(sizeof (struct kfifo));
if(!fifo)
return NULL;
fifo->buffer = buffer;
fifo->size = size;
fifo->in = fifo->out = 0;
return fifo;
}
struct kfifo *kfifo_alloc(unsigned int size)
{
unsigned char *buffer;
struct kfifo *ret;
if (!is_power_of_2(size)) {
BUG_ON(!(size > 0x80000000));
size = roundup_pow_of_two(size);
}
buffer = (unsigned char*) malloc(size);
if (!buffer)
return NULL;
ret=(struct kfifo *) malloc(sizeof (struct kfifo));
ret = kfifo_init(buffer, size);
if (!ret)
free(buffer);
return ret;
}
void kfifo_free(struct kfifo *fifo)
{
free(fifo->buffer);
free(fifo);
}
unsigned int __kfifo_put(struct kfifo *fifo,
unsigned char *buffer, unsigned int len)
{
unsigned int l;
len = min(len, fifo->size - fifo->in + fifo->out);
l = min(len, fifo->size - (fifo->in & (fifo->size - 1)));
memcpy(fifo->buffer + (fifo->in & (fifo->size - 1)), buffer, l);
memcpy(fifo->buffer, buffer + l, len - l);
fifo->in += len;
return len;
}
unsigned int __kfifo_get(struct kfifo *fifo,
unsigned char *buffer, unsigned int len)
{
unsigned int l;
len = min(len, fifo->in - fifo->out);
l = min(len, fifo->size - (fifo->out & (fifo->size - 1)));
memcpy(buffer, fifo->buffer + (fifo->out & (fifo->size - 1)), l);
memcpy(buffer + l, fifo->buffer, len - l);
fifo->out += len;
return len;
}
API接口函数测试:
- 创建。创建 一个 128字节的 fifo buff
struct kfifo *test_kifo_buffer=NULL;
uint8_t test_fifo_write_buff[20]="He is sbGGBER!!!!!!";
#if 1
uint8_t test_f[128];
test_kifo_buffer=kfifo_init(test_f,sizeof(test_f));
#else
test_kifo_buffer=kfifo_alloc(100);
#endif
if( !test_kifo_buffer )
printf("KFIFO 结构体 test_kifo_buffer 没有创建成功!\r\n");
- 存取操作。
#if 1
write_counter=__kfifo_put(test_kifo_buffer, test_fifo_write_buff, 20 );
if(write_counter!=20)
printf("\r\n发送10个字节写入到KFIFO结构体test_kifo_buffer失败,写入个数是:%d.\r\n",write_counter);
read_counter=__kfifo_get(test_kifo_buffer, test_fifo_read_buff,14);
#else
write_counter=kfifo_put(test_kifo_buffer, test_fifo_write_buff, 20 );
if(write_counter!=20)
printf("\r\n发送10个字节写入到KFIFO结构体test_kifo_buffer失败,写入个数是:%d.\r\n",write_counter);
read_counter=kfifo_get(test_kifo_buffer, test_fifo_read_buff,14);
#endif
printf("从test_kifo_buffer缓冲区读取的数据是 %s ,读出的个数是 %d .\r\n",test_fifo_read_buff,read_counter);
printf("test_kifo_buffer->size:%d,test_kifo_buffer->out:%d,test_kifo_buffer->in:%d.\r\n",test_kifo_buffer->size,test_kifo_buffer->out,test_kifo_buffer->in);
2.2 KFIFO代码的一些理解及补充
2.2.1 整数的环回特性
这部分内容参考自【利用整数的环回特性打造高效计时器、补码反码、负数的内存布局】。其实,看到在标题中的“整数环回”,我的脑海中第一时间浮现的时这张图,来自于中文版《C++ Primer Plus(第六版)》,这幅图也是对这一特性很直观的解释。 对于上图,假设有类型int16_t的变量sam和uint16_t类型的变量sue都设置为32767=0x7fff,然后都加1,即0x7fff+0x1=0x8000,sam为-32768,sue为32768;将sam和sue都设置为0,然后都减1,即0x0000-0x1=0xffff,sam为-1(补码的补码为原码,0xffff的补码为0x8001),sue为65535。
负数在计算机中以补码的形式存储,补码是在其原码的基础上, 符号位不变, 其余各位取反后+1,即在反码的基础上+1(补码的计算过程看求职笔记之嵌入式知识点01_C&Cpp(1)–C语言知识点 2.22 整数、小数与二进制间转换 ),且满足补码的补码为原码。
根据上面分析,假设有uint16_t类型的变量sue,则有
- ①
-sue=(~sue)+1 - ②
sue+(-sue)=0=0x10000=65536 - ③
sue+(~sue)=0-1=0x10000-1=0xffff=65535
下面做进一步讨论。假设有两个uint16_t的变量A、B,需要证明有向区间长度运算uint16_t len=B-A 在任何时候都成立。
- 当B>=A时,len=B-A,即如下图中,A的位置处于A1=16384,B的位置在B1=24576时,len=B-A=B1-A1=8192,成立;
- 当A、B变化使得B<A时(图中为了清晰表达,A1保持值为16384不变,B从B1变为B2=8192),由图可知此时的有向区间长度=A1O’+OB2=AO’+OB=65536-A+B=65536-(A-B)=0-(A-B)=B-A,得证。
2.2.2 min宏或内联函数的讨论
这部分内容参考自【求最小值的宏:#define min(x,y) x > y? y: x 中的陷阱,慎用】和【min宏定义探究】。
下面是比较常见求最小值的宏,大多数条件下正常,在某些特殊条件或者应用场景下会发生bug,所以使用时要考虑清楚它的应用场景。
- ①
#define min(x,y) x > y? y: x - ②
#define min(x,y) (x) > (y)? (y): (x) - ③
#define min(x,y) ((x) > (y)? (y): (x))
问题1:define 只是做简单的字符替换,使用时为了安全需要在适当的地方添加括号。比如,对于①,int a = min(1 , 2+3); 等效于int a=1>2+3?2+3:1; ,编译会报错;对于②,int a=2*min(1,3); 等效于int a=2*(1)>(3)?(3):(1); ,明显偏离了我们的意愿;③时对①和②的改进。
问题2:①、②和③都存在这浪费CPU的情况。以③为例,int a=min(b++, c++); 等效于int =((b++) < (c++) ?(c++):(b++)); ,变量b、c都自加了2次,偏离了我们的意愿。
问题3:对于③,考虑下面代码:
uint16_t a = 1, b = 2, c = 3;
uint16_t t = min(a, b - c);
上面不论是返回值,还是实参,都是uint16_t 类型。第一个形参a=1,第二个形参y只要不为0,min(1, y)就应该返回1才是对的,实际返回t=65535,因为这里y=b-c=-1=65535。
int的位数在不同的编译器中是不同的,但不会少于16位。 在16位编译器中,一个int是16位的,在32位的编译器中,一个int是32位的。下面以int为16位为前提背景,即此时unsigned int 等价与uint16_t ,分析fifo.c中的unsigned int __kfifo_get(struct kfifo *fifo, unsigned char *buffer, unsigned int len) 函数:
unsigned int __kfifo_get(struct kfifo *fifo,
unsigned char *buffer, unsigned int len)
{
unsigned int l;
len = min(len, fifo->in - fifo->out);
..........
}
考虑有一个环形FIFO缓冲区char buff[256] ,写索引uint16_t in ,读索引uint16_t out 。考虑环回特性,fifo中已使用的空间大小总是等于in-out。例如in = 8,out=3时,已用空间=8-3 = 5;即使in发生了环回,这个等式依然成立,例如:in = 1,out = 65535时,已用空间为2,而uint16_t 的运算时:1 - 65535 = 2。根据这一环回逻辑,已使用的空间大小in-out总是介于0~size之间。可见,当len=1时,计算min(1, in - out),只要(in - out)不是0,min的结果就应该返回1。 使用③ #define min(x,y) ((x) > (y)? (y): (x)) 时,相当于直接写入1-65535 ,此时计算1-65535结果并不为2,而是-65534,它比1小,所以返回-65534。又因为len类型为uint16_t,即返回的结果len= -65534 = 2(不是我们想要的)。
改进方法1:对于上述kfifo.c中的min宏可以改进为#define min(x,y) ((unsigned int)(x) > (unsigned int)(y)? (unsigned int)(y): (unsigned int)(x)) ,这样可以避免环回特性存在的问题,但是当参数为表达式时,仍然会被计算两次,浪费了cpu。
改进方法2:linux内核中定义的min宏如下,路径为root/include/linux/kernel.h
#define min(x, y) ({ \
typeof(x) _min1 = (x); \
typeof(y) _min2 = (y); \
(void) (&_min1 == &_min2); \
_min1 < _min2 ? _min1 : _min2; })
首先,将_min1和__min2来存参数x和y,这样避免了参数为表达式时计算两次的弊端。如果形参不是表达式而就是个单变量的话,除非你指定编译器使用O(0)优化,否则,编译器会优化为最高效的代码的(看编译出来的汇编代码),所以也不会降低效率。 (void) (&_min1 == &_min2); 这句话对于执行程序来说是一句废话,但是它可以检查x和y的参数类型是否一致(C语言并没有类型检查功能,所以这里巧妙地用取地址,对比指针的方式来产生警告)。当x和y的参数类型不同时,两个不一样的指针类型进行比较操作,编译器会提示:warning: comparison of distinct pointer types lacks a cast 。此外,(void) 的作用是强制执行本行代码,否则这行代码在编译器看来确实是一句废话,会被优化掉. 注意:typeof关键字只在GUN编译器支持,标准C89、C99都不支持。 改进方法3:最稳妥的方法使用内联函数。针对上述kfifo.c中的min宏可以改进为:
static inline unsigned int min(unsigned int x,unsigned int y)
{
unsigned int __min1=x, __min2 = y;
return __min1 < __min2 ? __min1: __min2;
}
2.2.3 开关全局中断、BUG_ON宏
开关全局中断的内容代码移植于FreeRTOS内核代码,具体内容请看FreeRTOS之03-中断配置与临界段1.1.4 用于中断屏蔽的特殊寄存器(PRIMASK、FAULTMASK 和 BASEPRI) 和1.3 FreeRTOS 开关中断 。
BUG_ON宏实现代码和之前在FreeRTOS之02-编程规范及系统配置一文中configASSERT 相同,详细代码分析请自行跳转。
2.2.4 kfifo源码分析
这部分参考下面文章: 眉目传情之匠心独运的kfifo、 巧夺天工的kfifo(修订版)、 Linux kfifo 源码分析
2.3 移植stm32测试
所选开发环境及硬件配置
- 德飞莱开发板。芯片为stm32f103zet6;LED2、LED3的引脚为PE5、PB5,低电平亮;WK_UP、KEY1、KEY2、KEY3的引脚为PA0、PE4、PE3、PE2,除WK_UP外其余按键按下为低电平;USART1 TX和RX引脚分别为PA9、PA10;外部晶振为8MHZ和32.768KHZ。
- 软件:STM32CUBMX、KEIL、串口调试助手(sscom33.exe)
具体配置
-
步骤1,配置时钟、下载方式。 -
步骤2,配置LED、按键的GPIO,用于调试,配置信息如下图。 -
步骤3,配置串口USART1。 -
步骤4,配置NVIC参数。注意:优先级分组选2位抢占优先级、2位亚优先级,且UASRT1的抢占优先级为1、亚优先级为0。因为这里的配置需要和kfifo.h中的参数configPRIO_BITS和configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 对应,如果为其他设置,需要修改它们的值。 -
步骤5,在Project Manager中设置工程名字、路径以及代码生成方式等。 以上步骤在STM32CUBMX软件中完成,而下面步骤主要在MDK5中进行。约定: 添加的所有个人代码均在/* USER CODE xxx BEGIN xxx*/ 和/* USER CODE xxx END xxx*/ 之间,可以通过确定代码所在位置。 -
main.h
#include <stdio.h>
#include <string.h>
#include "usart.h"
#include "gpio.h"
#include "kfifo.h"
-
main.c
int i=0;
int len;
uint8_t read_data = 0;
uint8_t data_len = 0;
kifo_buffer=kfifo_alloc(256);
if( !kifo_buffer )
printf("KFIFO 结构体 test_kifo_buffer 没有创建成功!\r\n");
HAL_UART_Receive_IT(&huart1, (uint8_t*)&recv_data, sizeof(recv_data));
while((len = kfifo_get(kifo_buffer, (uint8_t*)&read_data, sizeof(read_data))) > 0)
{
if(read_data == 0x3F)
{
printf("your data: length=");
if((len = kfifo_get(kifo_buffer, (uint8_t*)&read_data, sizeof(read_data))) > 0)
{
data_len = read_data;
printf("%03d,data=",data_len);
}
for(i = 0; i < data_len; i++)
{
if((len = kfifo_get(kifo_buffer, (uint8_t*)&read_data, sizeof(read_data))) > 0)
{
printf("[0x%02x] ", read_data);
}
}
}
}
HAL_Delay(200);
}
-
usart.h
extern uint8_t recv_data;
extern struct kfifo *kifo_buffer;
-
usart.c
uint8_t recv_data = 0;
struct kfifo *kifo_buffer;
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if(huart ->Instance == USART1)
{
kfifo_put(kifo_buffer, &recv_data, sizeof(recv_data));
HAL_UART_Receive_IT(&huart1, (uint8_t*)&recv_data, sizeof(recv_data));
}
}
#if 1
#pragma import(__use_no_semihosting)
struct __FILE
{
int handle;
};
FILE __stdout;
void _sys_exit(int x)
{
x = x;
}
int fputc(int ch, FILE *f)
{
while((USART1->SR&0X40)==0);
USART1->DR=(uint8_t)ch;
return ch;
}
#endif
-
gpio.h
#define LED2(n) (n?HAL_GPIO_WritePin(GPIOE,GPIO_PIN_5,GPIO_PIN_SET):HAL_GPIO_WritePin(GPIOE,GPIO_PIN_5,GPIO_PIN_RESET))
#define LED2_Toggle() (HAL_GPIO_TogglePin(GPIOE, GPIO_PIN_5))
#define LED3(n) (n?HAL_GPIO_WritePin(GPIOB,GPIO_PIN_5,GPIO_PIN_SET):HAL_GPIO_WritePin(GPIOB,GPIO_PIN_5,GPIO_PIN_RESET))
#define LED3_Toggle() (HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_5))
#define KEY1() HAL_GPIO_ReadPin(GPIOE,GPIO_PIN_4)
#define KEY2() HAL_GPIO_ReadPin(GPIOE,GPIO_PIN_3)
#define KEY3() HAL_GPIO_ReadPin(GPIOE,GPIO_PIN_2)
#define WK_UP() HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0)
运行结果 MCU下载程序,连接串口,设置发送参数和数据,选择定时发送模式,定时10ms发送“3F 05 01 02 03 04 05”。下面两图分别为数据帧发送一次和多次测试的结果。 注意:虽然上面简单测试时,62/7=373426/42161=8.857142857142857,没有测试出来丢帧的情况,但是时存在这个隐患的,因为while((len = kfifo_get(kifo_buffer, (uint8_t*)&read_data, sizeof(read_data))) > 0) 这种直接获取帧头的方式,会丢弃帧头前的数据。
对应工程文件:dma/UART_KFIFO ,仓库地址为xxx (后面补充)。
3 LWRB
LWRB是是一种轻量级FIFO环形缓冲区实现的开源库,遵循 MIT 开源许可协议。具有如下特点:
- 用ANSI C99编写,与大小数据类型兼容size_t;
- 独立于平台,无特定于架构的代码;
- 先进先出(先进先出)缓冲器实现;
- 无动态内存分配,数据为静态数组;
- 使用优化的内存拷贝而不是循环从内存读/写数据到内存;
- 线程安全,当用作具有单写入和单个读取条目的管道时;
- 当用作具有单读和单读条目的管道时,中断安全;
- 适用于 DMA 在内存之间传输和与内存之间的传输,在缓冲区和应用程序内存之间具有零拷贝开销;
- 支持数据透视、跳过读取和前进写入;
- 实现对事件通知的支持。
详情介绍和使用,请阅读官网文档:LwRB 最新开发文档
3.1 移植stm32及测试
所选开发环境及硬件配置
- 德飞莱开发板。芯片为stm32f103zet6;LED2、LED3的引脚为PE5、PB5,低电平亮;WK_UP、KEY1、KEY2、KEY3的引脚为PA0、PE4、PE3、PE2,除WK_UP外其余按键按下为低电平;USART1 TX和RX引脚分别为PA9、PA10;外部晶振为8MHZ和32.768KHZ。
- 软件:STM32CUBMX、KEIL、串口调试助手(sscom33.exe)
具体配置 在STM32CUBMX软件中完成的工作和本文2.3 节相同,而下面步骤主要在MDK5中进行。 首先从lwrb Github下载lwrb源码,将lwrb/src/lwrb/lwrb.c 和lwrb/src/include/lwrb/lwrb.h 文件添加工程,配置lwrb.h中LWRB_VOLATILE 为#define LWRB_VOLATILE volatile ,OK ,编译以下会发现没有错没有警告,就可以实现自己想要功能了。
约定: 添加的所有个人代码均在/* USER CODE xxx BEGIN xxx*/ 和/* USER CODE xxx END xxx*/ 之间,可以通过确定代码所在位置。
-
main.h
#include <stdio.h>
#include <string.h>
#include "lwrb.h"
#include "usart.h"
#include "gpio.h"
-
main.c
int i=0;
int len;
uint8_t read_data = 0;
uint8_t data_len = 0;
lwrb_init(&usart1_ringbuff, (uint8_t*)usart1_buffdata, USART1_BUFFDATA_SIZE);
lwrb_is_ready(&usart1_ringbuff);
HAL_UART_Receive_IT(&huart1, (uint8_t*)&recv_data, sizeof(recv_data));
while((len = lwrb_read(&usart1_ringbuff, (uint8_t*)&read_data, sizeof(read_data))) > 0)
{
if(read_data == 0x3F)
{
printf("your data: length=");
if((len = lwrb_read(&usart1_ringbuff, (uint8_t*)&read_data, sizeof(read_data))) > 0)
{
data_len = read_data;
printf("%03d,data=",data_len);
}
for(i = 0; i < data_len; i++)
{
if((len = lwrb_read(&usart1_ringbuff, (uint8_t*)&read_data, sizeof(read_data))) > 0)
{
printf("[0x%02x] ", read_data);
}
}
}
}
HAL_Delay(200);
}
-
usart.h
extern uint8_t recv_data;
extern lwrb_t usart1_ringbuff;
#define USART1_BUFFDATA_SIZE 150
extern uint8_t usart1_buffdata[USART1_BUFFDATA_SIZE];
-
usart.c
uint8_t recv_data = 0;
lwrb_t usart1_ringbuff;
uint8_t usart1_buffdata[USART1_BUFFDATA_SIZE];
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if(huart ->Instance == USART1)
{
lwrb_write(&usart1_ringbuff, &recv_data, sizeof(recv_data));
HAL_UART_Receive_IT(&huart1, (uint8_t*)&recv_data, sizeof(recv_data));
}
}
#if 1
#pragma import(__use_no_semihosting)
struct __FILE
{
int handle;
};
FILE __stdout;
void _sys_exit(int x)
{
x = x;
}
int fputc(int ch, FILE *f)
{
while((USART1->SR&0X40)==0);
USART1->DR=(uint8_t)ch;
return ch;
}
#endif
-
gpio.h
#define LED2(n) (n?HAL_GPIO_WritePin(GPIOE,GPIO_PIN_5,GPIO_PIN_SET):HAL_GPIO_WritePin(GPIOE,GPIO_PIN_5,GPIO_PIN_RESET))
#define LED2_Toggle() (HAL_GPIO_TogglePin(GPIOE, GPIO_PIN_5))
#define LED3(n) (n?HAL_GPIO_WritePin(GPIOB,GPIO_PIN_5,GPIO_PIN_SET):HAL_GPIO_WritePin(GPIOB,GPIO_PIN_5,GPIO_PIN_RESET))
#define LED3_Toggle() (HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_5))
#define KEY1() HAL_GPIO_ReadPin(GPIOE,GPIO_PIN_4)
#define KEY2() HAL_GPIO_ReadPin(GPIOE,GPIO_PIN_3)
#define KEY3() HAL_GPIO_ReadPin(GPIOE,GPIO_PIN_2)
#define WK_UP() HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0)
运行结果 这部分和本文2.3 节相同。
对应工程文件:dma/UART_LWRB ,仓库地址为xxx (后面补充)。
相关参考
1、无锁环形缓冲区队列 kfifo 2、STM32 & FreeRTOS & KFIFO (巧夺天工) 3、ringbuff | 通用FIFO环形缓冲区实现库 4、STM32HAL 移植一款通用FIFO轻量级环形缓冲管理器开源库lwrb(裸机开发)
That’s all.Check it.
|