百问网FreeRTOS复习笔记第29到35讲

/*本文的所有源代码以及其他一切形式的知识转述(文字、图表)均出自百问网,传送门:【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.通信过程中传输的数据可能会出错。

环形缓冲区的本质也是一个数组,假设我们定义了一个如下表所示的数组:

01234567

假设我们要对这个缓冲区进行读写操作,(由于缓冲区本质也是线性表)肯定也要从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,那么它就是互斥量。

使用队列机制分三步走:创建队列、写队列、读队列。

队列的简化操做如入下图所示,从此图可知:

队列可以包含若干个数据:队列中有若干项,这被称为 " 长度 "(length)
每个数据大小固定
创建队列时就要指定长度、数据大小
数据的操作采用先进先出的方法 (FIFO First In First Out) :写数据时放到尾
部,读数据时从头部读
也可以强制写队列头部:覆盖头部数据

一个队列至少包含了这两样东西:1.环形Buffer 2.两个链表(Sender List、Receiver List)。

只要知道队列的句柄,谁都可以读、写该队列。任务、ISR 都可读、写队列。可以多个
任务读写队列。
任务读写队列时,简单地说:如果读写不成功,则阻塞;可以指定超时时间。口语化地
说,就是可以定个闹钟:如果能读写了就马上进入就绪态,否则就阻塞直到超时。
某个任务读队列时,如果队列没有数据,则该任务可以进入阻塞状态:还可以指定阻塞
的时间。如果队列有数据了,则该阻塞的任务会变为就绪态。如果一直都没有数据,则时间
到之后它也会进入就绪态。
既然读取队列的任务个数没有限制,那么当多个任务读取空队列时,这些任务都会进入
阻塞状态:有多个任务在等待同一个队列的数据。当队列中有数据时,哪个任务会进入就绪
态?
优先级最高的任务
如果大家的优先级相同,那等待时间最久的任务会进入就绪态
跟读队列类似,一个任务要写队列时,如果队列满了,该任务也可以进入阻塞状态:还
可以指定阻塞的时间。如果队列有空间了,则该阻塞的任务会变为就绪态。如果一直都没有
空间,则时间到之后它也会进入就绪态。
既然写队列的任务个数没有限制,那么当多个任务写 " 满队列 " 时,这些任务都会进入阻
塞状态:有多个任务在等待同一个队列的空间。当队列中有空间时,哪个任务会进入就绪态?
优先级最高的任务
如果大家的优先级相同,那等待时间最久的任务会进入就绪态

第32讲 队列实验_多设备玩游戏(编程)

例程里面memset函数报warning直接添加string.h头文件可以解决。

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

这节课的目的是使用队列来传递红外遥控器的接收信号。
使用队列的流程是:创建队列、写队列、读队列、删除队列。
1.创建队列
方式有两种:动态分配内存、静态分配内存
动态:

静态: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讲 队列集实验_改进程序框架(思路介绍)

老师介绍了实际开发中的代码解耦问题,实现业务功能的应用程序不应该和底层驱动程序耦合。

要支持多个输入设备时,我们需要实现一个“InputTask”,它读取各个设备的队列,得
到数据后再分别转换为游戏的控制键。
InputTask 如何及时读取到多个队列的数据?要使用队列集。
队列集的本质也是队列,只不过里面存放的是“队列句柄”。使用过程如下:
a. 创建队列 A,它的长度是 n1
b. 创建队列 B,它的长度是 n2
c. 创建队列集 S,它的长度是“n1+n2”
d. 把队列 A、B 加入队列集 S
e. 这样,写队列 A 的时候,会顺便把队列 A 的句柄写入队列集 S
f. 这样,写队列 B 的时候,会顺便把队列 B 的句柄写入队列集 S
g. InputTask 先读取队列集 S,它的返回值是一个队列句柄,这样就可以知道哪个队列有
有数据了;然后 InputTask 再读取这个队列句柄得到数据。

第35讲 队列集实验_改进实验框架(编程)

Keil里的这个按键可以进行全局搜索。

  • 7
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值