IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 嵌入式 -> 【STM32】STM32CubeMX HAL库 DMA[2]环形FIFO缓冲区 -> 正文阅读

[嵌入式]【STM32】STM32CubeMX HAL库 DMA[2]环形FIFO缓冲区

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.hroot/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

/* Code is added by eric,reference from FreeRTOS kernel source code.This code need to change. CODE BEGIN*/
#include "stm32f1xx_hal.h"
#include "gpio.h"

#define __USE__CLZ              1  //是否采用 STM32 硬件提供的计算前导零指令 CLZ

#define configPRIO_BITS         2 // 此宏用来设置 MCU 使用几位优先级(抢占优先级)
																	// 比如FreeRTOS中优先级选组4,4 bits for pre-emption priority,0 bits for subpriority,此时设置为4
																	// 在本程序中,因为配置为2 bits for pre-emption priority,2 bits for subpriority,故设置为2
#define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 1   // 此宏用来设置系统可管理的最大优先级,也就是高于此优先级的不能被管理(屏蔽)。
																												 // 根据手册可知,无论把BASEPRI设置为多少,都无法屏蔽主优先级为0的中断。
																												 // 根据自己需求设置,此处设置为1
/* Code is added by eric,reference from FreeRTOS kernel source code.This code need to change. CODE END*/

/* Code is added by eric,reference from FreeRTOS kernel source code.This code don't need to change. CODE BEGIN*/
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)) //为2的次幂,值为1

#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" ))
/* Code is added by eric,reference from FreeRTOS kernel source code.This code don't need to change. CODE END*/
struct kfifo {
	unsigned char *buffer;	/* the buffer holding the data */
	unsigned int size;	/* the size of the allocated buffer */
	unsigned int in;	/* data is added at offset (in % size) */
	unsigned int out;	/* data is extracted from off. (out % size) */
//	spinlock_t *lock;	/* protects concurrent modifications */ //lock主要保证任何时刻读写互斥访问,stm32只有一个核心,所以不需要
};


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);

/**
 * __kfifo_reset - removes the entire FIFO contents, no locking version
 * @fifo: the fifo to be emptied.
 */
static inline void __kfifo_reset(struct kfifo *fifo)
{
	fifo->in = fifo->out = 0;
}

/**
 * __kfifo_len - returns the number of bytes available in the FIFO, no locking version
 * @fifo: the fifo to be used.
 */
static inline unsigned int __kfifo_len(struct kfifo *fifo)
{
	return fifo->in - fifo->out;
}

/* Code is added by eric,reference from FreeRTOS kernel source code.This code don't need to change. CODE BEGIN*/

#define portFORCE_INLINE    __forceinline

/* !!!! configMAX_SYSCALL_INTERRUPT_PRIORITY must not be set to zero !!!!*/
#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
	{
		/* Barrier instructions are not used as this function is only used to
		lower the BASEPRI value. */
		msr basepri, ulBASEPRI
	}
}

static portFORCE_INLINE void vPortRaiseBASEPRI( void )
{
uint32_t ulNewBASEPRI = configMAX_SYSCALL_INTERRUPT_PRIORITY;

	__asm
	{
		/* Set BASEPRI to the max syscall priority to effect a critical
		section. */
		msr basepri, ulNewBASEPRI
		dsb
		isb
	}
}

#define lock_irqsave()           portDISABLE_INTERRUPTS()
#define unlock_irqrestore()      portENABLE_INTERRUPTS()

/* Code is added by eric,reference from FreeRTOS kernel source code.This code don't need to change. CODE END*/


/**
 * kfifo_reset - removes the entire FIFO contents
 * @fifo: the fifo to be emptied.
 */
static inline void kfifo_reset(struct kfifo *fifo)
{
	lock_irqsave();

	__kfifo_reset(fifo);

	unlock_irqrestore();
}

/**
 * kfifo_put - puts some data into the FIFO
 * @fifo: the fifo to be used.
 * @buffer: the data to be added.
 * @len: the length of the data to be added.
 *
 * This function copies at most @len bytes from the @buffer into
 * the FIFO depending on the free space, and returns the number of
 * bytes copied.
 */
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;
}

/**
 * kfifo_get - gets some data from the FIFO
 * @fifo: the fifo to be used.
 * @buffer: where the data must be copied.
 * @len: the size of the destination buffer.
 *
 * This function copies at most @len bytes from the FIFO into the
 * @buffer and returns the number of copied bytes.
 */
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);

	/*
	 * optimization: if the FIFO is empty, set the indices to 0
	 * so we don't wrap the next time
	 */
	if (fifo->in == fifo->out)
		fifo->in = fifo->out = 0;

	unlock_irqrestore();

	return ret;
}

/**
 * kfifo_len - returns the number of bytes available in the FIFO
 * @fifo: the fifo to be used.
 */
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"

/** linux-2.6.24.y **/

#ifndef __USE__CLZ
	// https://stackoverflow.com/questions/4398711/round-to-the-nearest-power-of-two
	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;
	}
	
	// https://blog.csdn.net/dreamispossible/article/details/91162847
	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
	// http://blog.csdn.net/zhzht19861011/article/details/51418383
	static inline unsigned int roundup_pow_of_two(unsigned int date_roundup_pow_of_two )
	{            
			/* 这里采用 STM32 硬件提供的计算前导零指令 CLZ
			 * 举个例子,假如变量date_roundup_pow_of_two 0x09
			 *(二进制为:0000 0000 0000 0000 0000 0000 0000 1001), 即bit3和bit0为1
			 * 则__clz( (date_roundup_pow_of_two)的值为28,即最高位1 前面有28个0,32-28 =3 代表最高位1 的 位置
			 * 31UL 表示 无符号 int 数字 31,否则默认为 有符号 int 数字 31
			 */

			return ( 1UL << ( 32UL - ( unsigned int ) __clz( (date_roundup_pow_of_two) ) ) );
	}
#endif

/**
 * kfifo_init - allocates a new FIFO using a preallocated buffer
 * @buffer: the preallocated buffer to be used.
 * @size: the size of the internal buffer, this have to be a power of 2.
 *
 * Do NOT pass the kfifo to kfifo_free() after use! Simply free the
 * &struct kfifo with free().
 */
struct kfifo *kfifo_init(unsigned char *buffer, unsigned int size)
{
	struct kfifo *fifo;

	/* size must be a power of 2 */
	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;
}

/**
 * kfifo_alloc - allocates a new FIFO and its internal buffer
 * @size: the size of the internal buffer to be allocated.
 *
 * The size will be rounded-up to a power of 2.
 */
struct kfifo *kfifo_alloc(unsigned int size)
{
	unsigned char *buffer;
	struct kfifo *ret;

	/*
	 * round up to the next power of 2, since our 'let the indices
	 * wrap' tachnique works only in this case.
	 */
	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;
}

/**
 * kfifo_free - frees the FIFO
 * @fifo: the fifo to be freed.
 */
void kfifo_free(struct kfifo *fifo)
{
	free(fifo->buffer);
	free(fifo);
}

/**
 * __kfifo_put - puts some data into the FIFO, no locking version
 * @fifo: the fifo to be used.
 * @buffer: the data to be added.
 * @len: the length of the data to be added.
 *
 * This function copies at most @len bytes from the @buffer into
 * the FIFO depending on the free space, and returns the number of
 * bytes copied.
 *
 * Note that with only one concurrent reader and one concurrent
 * writer, you don't need extra locking to use these functions.
 */
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);

	/*
	 * Ensure that we sample the fifo->out index -before- we
	 * start putting bytes into the kfifo.
	 */

//	smp_mb();

	/* first put the data starting from fifo->in to buffer end */
	l = min(len, fifo->size - (fifo->in & (fifo->size - 1)));
	memcpy(fifo->buffer + (fifo->in & (fifo->size - 1)), buffer, l);

	/* then put the rest (if any) at the beginning of the buffer */
	memcpy(fifo->buffer, buffer + l, len - l);

	/*
	 * Ensure that we add the bytes to the kfifo -before-
	 * we update the fifo->in index.
	 */

//	smp_wmb();

	fifo->in += len;

	return len;
}

/**
 * __kfifo_get - gets some data from the FIFO, no locking version
 * @fifo: the fifo to be used.
 * @buffer: where the data must be copied.
 * @len: the size of the destination buffer.
 *
 * This function copies at most @len bytes from the FIFO into the
 * @buffer and returns the number of copied bytes.
 *
 * Note that with only one concurrent reader and one concurrent
 * writer, you don't need extra locking to use these functions.
 */
unsigned int __kfifo_get(struct kfifo *fifo,
			 unsigned char *buffer, unsigned int len)
{
	unsigned int l;

	len = min(len, fifo->in - fifo->out);

	/*
	 * Ensure that we sample the fifo->in index -before- we
	 * start removing bytes from the kfifo.
	 */

//	smp_rmb();

	/* first get the data from fifo->out until the end of the buffer */
	l = min(len, fifo->size - (fifo->out & (fifo->size - 1)));
	memcpy(buffer, fifo->buffer + (fifo->out & (fifo->size - 1)), l);

	/* then get the rest (if any) from the beginning of the buffer */
	memcpy(buffer + l, fifo->buffer, len - l);

	/*
	 * Ensure that we remove the bytes from the kfifo -before-
	 * we update the fifo->out index.
	 */

//	smp_mb();

	fifo->out += len;

	return len;
}

API接口函数测试:

  • 创建。创建 一个 128字节的 fifo buff
    struct kfifo *test_kifo_buffer=NULL;//创建一个 KFIFO 的结构体 指针
    uint8_t test_fifo_write_buff[20]="He is sbGGBER!!!!!!";//这个字符串加上`\0`刚好存满
    #if 1    
    	//方式1:allocates a new FIFO using a preallocated buffer
    	uint8_t test_f[128];
    	test_kifo_buffer=kfifo_init(test_f,sizeof(test_f));
    #else
    	//方式2:allocates a new FIFO and its internal buffer
    	test_kifo_buffer=kfifo_alloc(100); //如果分配100,为了方便计算,实际会分配128字节的空间。
    #endif				
    	if( !test_kifo_buffer )
    					printf("KFIFO 结构体 test_kifo_buffer 没有创建成功!\r\n");
    
  • 存取操作。
    #if 1
    	// 方式1:no locking version
    	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
    	// 方式2:locking version
    	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

/*
 * min()/max()/clamp() macros that also do
 * strict type-checking.. See the
 * "unnecessary" pointer comparison.
 */
#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; //注意这里没必要加改进方法2中的括号
	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

    /* USER CODE BEGIN Includes */
    #include <stdio.h>
    #include <string.h>
    #include "usart.h"
    #include "gpio.h"
    #include "kfifo.h"
    /* USER CODE END Includes */
    
  • main.c

     /* USER CODE BEGIN Init */
    int i=0;
    int len;
    uint8_t read_data = 0;
    uint8_t data_len = 0;
     /* USER CODE END Init */
    
     /* USER CODE BEGIN 2 */
    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));
     /* USER CODE END 2 */
    
    /* USER CODE BEGIN 3 */	
    //		//这种直接获取帧头会存在数据丢弃的情况,比如上一次获取时处理了数据帧的3F,剩余数据在下一次被获取出来时,通过丢弃数据的方式重新找新帧头,原来的那帧数据就会被丢失
    		while((len = kfifo_get(kifo_buffer, (uint8_t*)&read_data, sizeof(read_data))) > 0)
    		{
    			/* 捕获起始标志 */
    			if(read_data == 0x3F)
    			{
    				printf("your data: length="); 
    				//读取数据字节数,最大支持0xFF
    				if((len = kfifo_get(kifo_buffer, (uint8_t*)&read_data, sizeof(read_data))) > 0)
    				{
    					data_len = read_data;
    					printf("%03d,data=",data_len); 
    				}
    				
    				//提取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);
      }
      /* USER CODE END 3 */
    
  • usart.h

    /* USER CODE BEGIN Private defines */
    extern uint8_t recv_data;
    extern struct kfifo *kifo_buffer;
    /* USER CODE END Private defines */
    
  • usart.c

    /* USER CODE BEGIN 0 */
    uint8_t recv_data  = 0;
    struct kfifo *kifo_buffer;
    /* USER CODE END 0 */
    
    /* USER CODE BEGIN 1 */
    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;       
    	//定义_sys_exit()以避免使用半主机模式    
    	void _sys_exit(int x) 
    	{ 
    		x = x; 
    	} 
    	//重定义fputc函数 
    	int fputc(int ch, FILE *f)
    	{ 	
    		while((USART1->SR&0X40)==0);//循环发送,直到发送完毕   
    		USART1->DR=(uint8_t)ch;      
    		return ch;
    	}
    #endif 
    /* USER CODE END 1 */
    
  • gpio.h

    /* USER CODE BEGIN Private defines */
    #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)) //LED0输出电平翻转
    #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)) //LED1输出电平翻转
    
    #define KEY1()        HAL_GPIO_ReadPin(GPIOE,GPIO_PIN_4) //KEY1按键
    #define KEY2()        HAL_GPIO_ReadPin(GPIOE,GPIO_PIN_3) //KEY2按键
    #define KEY3()        HAL_GPIO_ReadPin(GPIOE,GPIO_PIN_2) //KEY3按键
    #define WK_UP()       HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0) //WKUP按键
    
    /* USER CODE END Private defines */
    

运行结果
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.clwrb/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

    /* USER CODE BEGIN Includes */
    #include <stdio.h>
    #include <string.h>
    #include "lwrb.h"
    #include "usart.h"
    #include "gpio.h"
    /* USER CODE END Includes */
    
  • main.c

     /* USER CODE BEGIN Init */
    int i=0;
    int len;
    uint8_t read_data = 0;
    uint8_t data_len = 0;
     /* USER CODE END Init */
    
     /* USER CODE BEGIN 2 */
    lwrb_init(&usart1_ringbuff, (uint8_t*)usart1_buffdata, USART1_BUFFDATA_SIZE);  //大小32。 xom每个10ms发送3F 05 01 02 03 04 05,, 20ms/10ms*7=14,取1.5~2倍后在取最近的2的幂次即32
    lwrb_is_ready(&usart1_ringbuff);
    HAL_UART_Receive_IT(&huart1, (uint8_t*)&recv_data, sizeof(recv_data));
     /* USER CODE END 2 */
    
    /* USER CODE BEGIN 3 */	
    /* USER CODE BEGIN 3 */	
    	//这种直接获取帧头会存在数据丢弃的情况,比如上一次获取时处理了数据帧的3F,剩余数据在下一次被获取出来时,通过丢弃数据的方式重新找新帧头,原来的那帧数据就会被丢失
    	while((len = lwrb_read(&usart1_ringbuff, (uint8_t*)&read_data, sizeof(read_data))) > 0)
    	{
    		/* 捕获起始标志 */
    		if(read_data == 0x3F)
    		{
    			printf("your data: length="); 
    			//读取数据字节数,最大支持0xFF
    			if((len = lwrb_read(&usart1_ringbuff, (uint8_t*)&read_data, sizeof(read_data))) > 0)
    			{
    				data_len = read_data;
    				printf("%03d,data=",data_len); 
    			}
    			
    			//提取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);
    }
    /* USER CODE END 3 */
    
  • usart.h

    /* USER CODE BEGIN Private defines */
    extern uint8_t recv_data;
    extern lwrb_t	usart1_ringbuff;
    #define USART1_BUFFDATA_SIZE	150
    extern uint8_t usart1_buffdata[USART1_BUFFDATA_SIZE]; //开辟一块内存用于缓冲区
    /* USER CODE END Private defines */
    
  • usart.c

    /* USER CODE BEGIN 0 */
    uint8_t recv_data  = 0;
    lwrb_t	usart1_ringbuff;
    uint8_t usart1_buffdata[USART1_BUFFDATA_SIZE];
    /* USER CODE END 0 */
    
    /* USER CODE BEGIN 1 */
    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;       
    	//定义_sys_exit()以避免使用半主机模式    
    	void _sys_exit(int x) 
    	{ 
    		x = x; 
    	} 
    	//重定义fputc函数 
    	int fputc(int ch, FILE *f)
    	{ 	
    		while((USART1->SR&0X40)==0);//循环发送,直到发送完毕   
    		USART1->DR=(uint8_t)ch;      
    		return ch;
    	}
    #endif 
    /* USER CODE END 1 */
    
  • gpio.h

    /* USER CODE BEGIN Private defines */
    #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)) //LED0输出电平翻转
    #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)) //LED1输出电平翻转
    
    #define KEY1()        HAL_GPIO_ReadPin(GPIOE,GPIO_PIN_4) //KEY1按键
    #define KEY2()        HAL_GPIO_ReadPin(GPIOE,GPIO_PIN_3) //KEY2按键
    #define KEY3()        HAL_GPIO_ReadPin(GPIOE,GPIO_PIN_2) //KEY3按键
    #define WK_UP()       HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0) //WKUP按键
    
    /* USER CODE END Private defines */
    

运行结果
这部分和本文2.3节相同。

对应工程文件:dma/UART_LWRB,仓库地址为xxx(后面补充)。

相关参考

1、无锁环形缓冲区队列 kfifo
2、STM32 & FreeRTOS & KFIFO (巧夺天工)
3、ringbuff | 通用FIFO环形缓冲区实现库
4、STM32HAL 移植一款通用FIFO轻量级环形缓冲管理器开源库lwrb(裸机开发)

That’s all.Check it.

  嵌入式 最新文章
基于高精度单片机开发红外测温仪方案
89C51单片机与DAC0832
基于51单片机宠物自动投料喂食器控制系统仿
《痞子衡嵌入式半月刊》 第 68 期
多思计组实验实验七 简单模型机实验
CSC7720
启明智显分享| ESP32学习笔记参考--PWM(脉冲
STM32初探
STM32 总结
【STM32】CubeMX例程四---定时器中断(附工
上一篇文章      下一篇文章      查看所有文章
加:2021-12-10 11:13:10  更:2021-12-10 11:13:58 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2025年1日历 -2025/1/9 1:56:51-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码