/*本文的所有源代码以及其他一切形式的知识转述(文字、图表)均出自百问网,传送门:【FreeRTOS入门与工程实践 --由浅入深带你学习FreeRTOS(FreeRTOS教程 基于STM32,以实际项目为导向)】 https://www.bilibili.com/video/BV1Jw411i7Fz/?p=29&share_source=copy_web&vd_source=bab35cd72a6b7a3ffd3c77e664d802f1*/
第29讲 数据传输的方法_环形buffer
本讲主要介绍对比三种多线程之间的通信方式:1.全局变量 2.环形数据缓冲区 3.队列
使用全局变量作为多线程之间的通信方式主要有两个弊病:
1.降低了CPU运行效率(缺乏阻塞与唤醒机制);
2.通信过程中传输的数据可能会出错。
环形缓冲区的本质也是一个数组,假设我们定义了一个如下表所示的数组:
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
假设我们要对这个缓冲区进行读写操作,(由于缓冲区本质也是线性表)肯定也要从0位置开始。这时就需要两个变量作为读写标志位,r代表读,w代表写。
对应的伪代码:
int buf[8];
int r =0;
int w =0;
首先我们学习如何判断这个缓冲区是否为空,初始状态下r=w=0,缓冲区为空,当r!=w时,缓冲区非空。那我们如何判断一个缓冲区是否为满呢?这个问题要从读写操作开始讲起,首先利用if语句判断,然后从buf[x]处开始赋值,然后下标x自加直到溢出。
对应的伪代码:
Write:
if(未满)
{
buf[w]=val;
w++;
if(w%8==0)
{
w=0;
}
}
Read:
if(r!=w)
{
val=buf[w];
r++;
if(r%8==0)
{
r=0;
}
}
我们由此可以得出读写标志位(r/w)的数值代表了下一次读或者写的位置。那什么时候缓冲区被填满了呢?假设我们让r=0,w自增进行写操作,w会从0开始递增到7,但如果7位置也被写入数据,会出现r==w(缓冲区为空的判断条件),这与事实并不符合。因此我们可以把下一个w位置等于r位置作为缓冲区为满的条件,同时位置7做一个保留不写入数据。
if((w+1)%8==0)
{
w=0;
}
上文的未满条件:(w+1)!=r
总结:如果你的进程之中只存在两个线程需要进行数据交换,且不需要进行唤醒于阻塞,那么可以使用环形buffer。
我自己写了点C代码,阐述我自己有关于环形缓冲区的理解。
#include <iostream>
#include "RingBuffer.h"
using namespace std;
RingBuffer_Type g_rb;
/*初始化环形缓冲区的方法是让
读写标记指针都指向同一个线性表元素*/
void RingBuffer_Init(RingBuffer_Type *rb)
{
rb->w = 0;
rb->r = 0;
for (int i = 0; i < RingBuffer_Size; i++)
rb->buffer[i] = 0;
}
/*判断buffer是否填满*/
int RingBuffer_Is_Full(RingBuffer_Type* rb)
{
return (((rb->w) + 1) % RingBuffer_Size == (rb->r));
}
/*判断buffer是否为空*/
int RingBuffer_Is_Empty(RingBuffer_Type* rb)
{
return(rb->r==rb->w);
}
/*写环形Buffer*/
int RingBuffer_Write(RingBuffer_Type* rb, int rb_data)
{
if (RingBuffer_Is_Full(rb))
return -1;
rb->buffer[rb->w] = rb_data;
rb->w = ((rb->w) + 1) % RingBuffer_Size;
return 0;
}
/*读环形Buffer*/
int RingBuffer_Read(RingBuffer_Type *rb, int *rb_data)
{
if (RingBuffer_Is_Empty(rb))
return -1;
*rb_data = rb->buffer[rb->r];
rb->r = (rb->r + 1) % RingBuffer_Size;
return 0;
}
/*在控制台打印环形Buffer*/
void RingBuffer_Printf(RingBuffer_Type* rb)
{
int rb_data;
int pbuffer[RingBuffer_Size] = {};
if (!RingBuffer_Read(rb,&rb_data))/* C/C++函数的形参列表中不能含有类型名 */
{
for (int x = 0; x < RingBuffer_Size - 1; x++)
{
pbuffer[x] = (rb->buffer[x]);
printf("%d\r\n", pbuffer[x]);
}
}
}
int main()
{
int Prime_Number[] = {2,3,5,6,11,13,17};//环形buffer需要自己保留一位
int size;
size = sizeof(Prime_Number) / sizeof(Prime_Number[0]);
/*printf("size=%d",size);*/
RingBuffer_Init(&g_rb);
for(int j=0;j<size;j++)
RingBuffer_Write(&g_rb, Prime_Number[j]);
//for (int x = 0; x < RingBuffer_Size-1; x++)
// printf("%d\r\n", g_rb.buffer[x]);
RingBuffer_Printf(&g_rb);
system("pause");
return 0;
}
头文件"RingBuffer.h"如下:
#ifndef __RingBuffer_H
#define __RingBuffer_H
#include <stdio.h>
#define RingBuffer_Size 8
typedef struct {
int w;
int r;
int buffer[RingBuffer_Size];
}RingBuffer_Type;
extern RingBuffer_Type g_rb;
/*初始化环形缓冲区的方法是让
读写标记指针都指向同一个线性表元素*/
void RingBuffer_Init(RingBuffer_Type* rb);
/*判断buffer是否填满*/
int RingBuffer_Is_Full(RingBuffer_Type* rb);
/*判断buffer是否为空*/
int RingBuffer_Is_Empty(RingBuffer_Type* rb);
/*写环形Buffer*/
int RingBuffer_Write(RingBuffer_Type* rb, int rb_data);
/*读环形Buffer*/
int RingBuffer_Read(RingBuffer_Type* rb, int* rb_data);
/*在控制台打印环形Buffer*/
void RingBuffer_Printf(RingBuffer_Type* rb);
#endif
第30讲 数据传输的方法_队列的本质
队列的读写本质也是环形缓冲区,在它的基础上增加了互斥措施、阻塞-唤醒机制。如果这个队列不传输数据,只调整数据个数,它就是信号量。在信号量中限定数据个数最大值为1,那么它就是互斥量。
使用队列机制分三步走:创建队列、写队列、读队列。
队列的简化操做如入下图所示,从此图可知:
一个队列至少包含了这两样东西:1.环形Buffer 2.两个链表(Sender List、Receiver List)。
第32讲 队列实验_多设备玩游戏(编程)
memset
是一个常用于C和C++编程中的库函数,用于设置内存区域的值。其原型在<string.h>
头文件中定义。这个函数的目的是将一块内存区域的内容设置为指定的值。
函数原型如下:
void *memset(void *str, int c, size_t n);
参数说明:
str
:指向要填充的内存块的指针。c
:要设置的值,以int形式提供。通常,此值被转换为unsigned char,因此它通常应该在0到255的范围内。n
:要填充的字节数。
返回值:
返回一个指向填充后的内存区域的指针。
这是一个简单的memset
使用示例,将内存区域的前10个字节设置为0:
#include <string.h>
#include <stdio.h>
int main() {
char str[20] = "Hello, World!";
printf("Original string: %s\n", str);
// 将str的前10个字节设置为0
memset(str, 0, 10);
printf("Modified string: %s\n", str);
return 0;
}
![](https://img-blog.csdnimg.cn/direct/9bcb1c7e8e614f97937b56c50acd5b8e.png)
静态:xQueueCreateStatic,队列的内存要事先分配好。
参数 | 说明 |
uxQueueLength
|
队列长度,最多能存放多少个数据(item)
|
uxItemSize
|
每个数据(item)的大小:以字节为单位
|
pucQueueStorageBuffer
|
如果 uxItemSize 非 0,pucQueueStorageBuffer 必须
指向一个 uint8_t 数组,
此数组大小至少为"uxQueueLength * uxItemSize"
|
pxQueueBuffer
|
必须执行一个 StaticQueue_t 结构体,用来保存队列
的数据结构
|
返回值
|
非 0:成功,返回句柄,以后使用句柄来操作队列
NULL:失败,因为 pxQueueBuffer 为 NULL
|
这节课我们编写的程序要实现的功能就是挡球板任务与小球任务之间进行交互通信,步骤如下:
1.创建对列
2.红外中断写队列
3.挡球板任务读队列(由裸机程序的读环形buffer改为读队列)
Attention:从中断里写队列需要用xQueueSendToBackFromISR!
第33讲 队列实验_多设备玩游戏(旋转编码器)
这节课我们的目标是实现用螺旋编码器玩挡球板游戏。首先我们要修改螺旋编码器驱动代码里的中断回调函数void RotaryEncoder_IRQ_Callback(void),并且创建一个任务用来解析传递过来的数据,然后写队列。
第34讲 队列集实验_改进程序框架(思路介绍)
老师介绍了实际开发中的代码解耦问题,实现业务功能的应用程序不应该和底层驱动程序耦合。
第35讲 队列集实验_改进实验框架(编程)
Keil里的这个按键可以进行全局搜索。