[FreeRTOS]Day8-Part1队列+红外遥控器玩游戏+旋转编码器玩游戏

队列

任务之间如何传输数据

数据个数 互斥措施 阻塞-唤醒
全局变量 1
环形缓冲区 多个
队列 多个

使用全局变量:对于TaskATaskB,如果使用全局变量来进行数据传输,一个全局变量只能传递一个数据,并且没有互斥措施,没有阻塞与唤醒

环形缓冲区用于在两个 任务之间传递数据。环形缓冲区是一个循环数组 ,例如定义一个数组int buf[8]表示环形缓冲区,两个指针r = 0w = 0分别指示下一次读和写的位置

缓冲区为空 的条件:r == w

缓冲区满 的条件:(w + 1) % BufSize == r,当w指向环形数组中r的前一个位置时表示数组满,数组利用率为BufSize - 1

读操作和写操作

c 复制代码
const int BufSize = 8;

int BufRead(void) {
    int val = -1;
    if(r != w) {		// 如果缓冲区不空
        val = buf[r];
        r = (r + 1) % BufSize;
    }
    return val;
}

int BufWrite(int val) {
    int flag = -1;
    if((w + 1) % BufSize != r) {				// 如果缓冲区不满
        buf[w] = val;
        w = (w + 1) % BufSize;
        flag = 1;
    }
    return flag;
}

使用全局变量num记录缓冲区元素个数,以此判断缓冲区空/满的缺陷:多个任务同时访问全局变量,可能导致出错

队列的本质

队列中,数据的读写本质就是唤醒缓冲区,在这个基础上增加了互斥措施、阻塞-唤醒机制

和环形缓冲区一样,队列有读位置r和写位置w,另外队列增加了保护措施,所以可以有元素个数num

如果这个队列不传输数据,只调整"数据个数num",它就是信号量(semaphore)

如果信号量中 限制"数据个数"的最大值为1,它就是互斥量(mutal exclusion, mutex)

队列相关操作:创建、写队列、读队列

TaskA写队列,TaskB读队列,则TaskA可能的操作有

  • 写队列
  • 若队列满,阻塞
  • 队列不满时被唤醒/超时时间到被唤醒

TaskB可能的操作有

  • 读队列
  • 若队列空,阻塞
  • 队列不空时被唤醒/超时时间到被唤醒

队列组成部分

  • 环形缓冲区
  • 两个链表:SenderList存放队列满时希望写队列的任务,用于队列不满时将这些任务唤醒;ReceiverList存放队列空时希望读队列的任务,用于队列不空时将这些任务唤醒

尝试读队列进程所在链表切换:

一个处于就绪链表的任务尝试读队列,但此时队列为空,该任务被从就绪链表删除,删除操作由自己执行,加入到阻塞链表,同时被加入到队列接收链表QueueReciverList,当以下情况发生时,该任务重新回到就绪链表:

  • 队列不空,任务进行读队列操作
  • 超时时间到,读队列函数返回错误值

队列实验

在02_nwatch_game_freertos.7z的基础上,改出8-1QueueGame,实现使用红外遥控器,旋转编码器玩游戏

实验方案:

  • 游戏任务:读取队列A获得控制信息,用来控制游戏
  • 红外遥控器驱动:在中断函数里解析出按键后,写队列A
  • 旋转编码器:中断函数里解析出旋转编码器的状态,写队列B 任务函数里,读队列B,构造好数据后写队列A

队列相关函数

创建队列

与任务创建函数一样,队列创建函数也分动态分配内存和静态分配内存

c 复制代码
// 动态分配内存队列创建函数
QueueHandle_t xQueueCreate(			// 成功返回:句柄	失败返回:NULL
    UBaseType_t uxQueueLength, 		// 队列长度
    UBaseType_t uxItemSize			// 每个数据(item)大小,以字节为单位
	);

// 静态分配内存队列创建函数
QueueHandle_t xQueueCreateStatic(	// 成功返回:句柄	失败返回:NULL
    UBaseType_t uxQueueLength,		// 队列长度
    UBaseType_t uxItemSize,			// 每个数据(item)大小,以字节为单位
    uint8_t *pucQueueStorageBuffer,	// 如果uxItemSize非零,pucQueueStorageBuffer必须指向一个uint8_t数组,数组大小至少为uxQueueLength * uxItemSize
    StaticQueue_t *pxQueueBuffer	// 执行一个 StaticQueue_t 结构体,用来保存队列的数据结构
    )
读队列

使用xQueueReceive()函数读队列,读到一个数据后,队列中该数据会被移除。这个函数有两个版本:在任务中使用,在ISR中使用

c 复制代码
BaseType_t xQueueReceive( 		// 返回值:pdPASS->成功读出数据,errQUEUE_EMPTY->队列空
    QueueHandle_t xQueue,		// 确定读哪个队列
    void * const pvBuffer,		// 队列的数据会被复制到这个buffer
    TickType_t xTicksToWait 	// 设置超时时间
    );
BaseType_t xQueueReceiveFromISR(
    QueueHandle_t xQueue,
    void *pvBuffer,
    BaseType_t *pxTaskWoken
    );
写队列

可以把数据写到队列头部,也可以写到队列尾部;可以在任务中使用,在中断服务程序ISR(Iterrupt Service Routine)中使用

c 复制代码
BaseType_t xQueueSend(
    QueueHandle_t xQueue,
    const void *pvItemToQueue,		// 要写入的数据
    TickType_t xTicksToWait
    );

红外遥控器玩游戏

当前逻辑:红外遥控中断写环形缓冲区,挡球板任务读环形缓冲区,无阻塞与唤醒,CPU利用率低

改造步骤:

  • 创建队列
  • 红外遥控中断写队列
  • 挡球板任务读队列
c 复制代码
// 定义队列item
struct input_data {
	uint32_t dev;
	uint32_t val;
};

QueueHandle_t g_xQueuePlatform;		// 挡球板队列

/* 创建队列 */
g_xQueuePlatform = xQueueCreate(10, sizeof(struct input_data));

struct input_data idata;		// 存放读出的数据

// 读队列
/* 读取红外遥控器 */
// if (0 == IRReceiver_Read(&dev, &data))
/* 读取队列:知道队列不空,成功读取数据 */
if(xQueueReceive(g_xQueuePlatform, &idata, portMAX_DELAY) == pdPASS)
    data = idata.val;

// 写队列

修改红外接收器的中断回调函数,写环形缓冲区 -> 写队列

c 复制代码
// 声明外部变量
extern QueueHandle_t g_xQueuePlatform;		// 挡球板队列

// 存放写入的数据
struct input_data idata;

/* device: 0, val: 0, 表示重复码 */
// 写环形缓冲区
// PutKeyToBuf(0);
// PutKeyToBuf(0);
// 写队列
idata.dev = 0;
idata.val = 0;
xQueueSendToBackFromISR(g_xQueuePlatform, &idata, 0);		// 等待时间设置为0,如果队列满,直接返回错误

// 修改其他位置
// PutKeyToBuf(datas[0]);
// PutKeyToBuf(datas[2]);
idata.dev = datas[0];
idata.val = datas[2];
xQueueSend(g_xQueuePlatform, &idata, 0);

// driver_irr_receiver.c
#include "FreeRTOS.h"
#include "queue.h"
#include "typedefs.h"

旋转编码器玩游戏

实验目的:增加使用旋转编码器玩游戏功能

实现方案:

  • 游戏任务:读取队列A,控制游戏
  • 红外遥控器:写队列A
  • 旋转编码器:中断函数中解析出旋转编码器的状态,写队列B;任务函数中,读取队列B,构造好之后写队列A
c 复制代码
// game1.c
QueueHandle_t g_xQueueRotaryEncoder;		// 旋转编码器队列

// 使用静态内存分配函数创建旋转编码器的队列
// typedefs.h
// 旋转编码器队列存放的item
struct rotaryEncoder_data {
	int32_t cnt;
	int32_t speed;
};
// game1.h
// 静态分配内存队列创建函数创建RotaryEncoder队列需要的参数
static uint8_t g_ucQueueRotaryEncoderBuf[10 * sizeof(struct rotaryEncoder_data)];
static StaticQueue_t g_xQueueRotaryStaticStruct;
g_xQueueRotaryEncoder = xQueueCreateStatic(10, sizeof(struct rotaryEncoder_data), g_ucQueueRotaryEncoderBuf, &g_xQueueRotaryStaticStruct);

// 修改RotaryEncoder中断函数
// driver_rotary_encoder.c
#include "FreeRTOS.h"
#include "queue.h"
#include "typedefs.h"
extern  QueueHandle_t g_xQueueRotaryEncoder;		// 旋转编码器队列

struct rotaryEncoder_data rdata;
// 写队列
rdata.cnt = g_count;
rdata.speed = g_speed;
xQueueSendToBackFromISR(g_xQueueRotaryEncoder, &rdata, NULL);

// game1.c
// 创建任务
xTaskCreate(RotaryEncoderTask, "RotaryEncoderTask", 128, NULL, osPriorityNormal, NULL);

// game1.c
// 旋转编码器任务函数
// RotaryEncoder任务函数
static void RotaryEncoderTask(void *params)
{
	struct rotaryEncoder_data rdata;
	struct input_data idata;
	uint8_t isLeft;
	int cnt;
	
	// 读RotaryEncoder队列
	xQueueReceive(g_xQueueRotaryEncoder, &rdata, portMAX_DELAY);
	
	// 处理数据
	// 判断速度:速度 < 0 -> 左移,否则右移
	if(rdata.speed < 0) {
		isLeft = 1;
		rdata.speed = 0 - rdata.speed;
	} else {
		isLeft = 0;
	}
	
	cnt = rdata.speed / 10;
	if(!cnt) {
		cnt = 1;
	}
	idata.dev = 1;
	idata.val = isLeft ? 0xe0 : 0x90;
	for(int i = 0; i < cnt; i ++) {
		// 写Platform队列
		xQueueSend(g_xQueuePlatform, &idata, 0);
	}
	
}

遇到问题:任务函数RotaryEncoderTask没有死循环,导致旋转编码器旋转后整个系统卡死

c 复制代码
// RotaryEncoder任务函数
static void RotaryEncoderTask(void *params)
{
	struct rotaryEncoder_data rdata;
	struct input_data idata;
	uint8_t isLeft;
	int i, cnt;
	
	while(1) {
		// 读RotaryEncoder队列
		xQueueReceive(g_xQueueRotaryEncoder, &rdata, portMAX_DELAY);
		
		// 处理数据
		// 判断速度:速度 < 0 -> 左移,否则右移
		if(rdata.speed < 0) {
			isLeft = 1;
			rdata.speed = 0 - rdata.speed;
		} else {
			isLeft = 0;
		}
		
		cnt = rdata.speed / 10;
		if(!cnt) {
			cnt = 1;
		}
		idata.dev = 1;
		idata.val = isLeft ? 0xe0 : 0x90;
		for(i = 0; i < cnt; i ++) {
			// 写Platform队列
			xQueueSend(g_xQueuePlatform, &idata, 0);
		}
	}
	
}

优化

修改挡球板队列g_xQueuePlatform中的数据格式

c 复制代码
#define UPT_MOVE_NONE	0
#define UPT_MOVE_RIGHT	1
#define UPT_MOVE_LEFT	2

struct input_data {
	uint32_t dev;
	uint32_t val;		// 存放左或右
};

红外遥控器写队列时,val要为动作

c 复制代码
idata.dev = datas[0];
	if(datas[2] == 0xe0) {			// 左移
		idata.val = UPT_MOVE_LEFT;
	} else if(datas[2] == 0x90) {
		idata.val = UPT_MOVE_RIGHT;		// 右移
	} else {
		idata.val = UPT_MOVE_NONE;		// 不动
	}
	xQueueSendToBackFromISR(g_xQueuePlatform, &idata, NULL);

旋转编码器写队列时,写入动作

c 复制代码
while(1) {
		// 读RotaryEncoder队列
		xQueueReceive(g_xQueueRotaryEncoder, &rdata, portMAX_DELAY);
		
		// 处理数据
		// 判断速度:速度 < 0 -> 左移,否则右移
		if(rdata.speed < 0) {
			isLeft = 1;
			rdata.speed = 0 - rdata.speed;
		} else {
			isLeft = 0;
		}
		
		cnt = rdata.speed / 10;
		if(!cnt) {
			cnt = 1;
		}
		idata.dev = 1;
		idata.val = isLeft ? UPT_MOVE_LEFT : UPT_MOVE_RIGHT;
		for(i = 0; i < cnt; i ++) {
			// 写Platform队列
			xQueueSend(g_xQueuePlatform, &idata, 0);
		}
	}

挡球板任务

c 复制代码
/* 挡球板任务 */
static void platform_task(void *params)
{
    byte platformXtmp = platformX;    
    uint8_t dev, data, last_data;
	struct input_data idata;

    // Draw platform
    draw_bitmap(platformXtmp, g_yres - 8, platform, 12, 8, NOINVERT, 0);
    draw_flushArea(platformXtmp, g_yres - 8, 12, 8);
    
    while (1)
    {
        /* 读取红外遥控器 */
		// if (0 == IRReceiver_Read(&dev, &data))
		/* 读取队列,直到读取成功 */
		xQueueReceive(g_xQueuePlatform, &idata, portMAX_DELAY);
		
		uptMove = idata.val;
		// Hide platform
		draw_bitmap(platformXtmp, g_yres - 8, clearImg, 12, 8, NOINVERT, 0);
		draw_flushArea(platformXtmp, g_yres - 8, 12, 8);
		
		// Move platform
		if(uptMove == UPT_MOVE_RIGHT)
			platformXtmp += 3;
		else if(uptMove == UPT_MOVE_LEFT)
			platformXtmp -= 3;
		uptMove = UPT_MOVE_NONE;
		
		// Make sure platform stays on screen
		if(platformXtmp > 250)
			platformXtmp = 0;
		else if(platformXtmp > g_xres - PLATFORM_WIDTH)
			platformXtmp = g_xres - PLATFORM_WIDTH;
		
		// Draw platform
		draw_bitmap(platformXtmp, g_yres - 8, platform, 12, 8, NOINVERT, 0);
		draw_flushArea(platformXtmp, g_yres - 8, 12, 8);
		
		platformX = platformXtmp;
    }
}

红外遥控器重复码的处理

c 复制代码
static uint32_t g_lastVal;
// drevier_ir_receiver.c  IRReceiver_IRQTimes_Parse() 
g_lastVal = idata.val;			// 记录上一次的值

// drevier_ir_receiver.c	IRReceiver_IRQ_Callback()
/* 是否重复码 */
if (isRepeatedKey())
{
    /* device: 0, val: 0, 表示重复码 */
    // 写环形缓冲区
    // PutKeyToBuf(0);
    // PutKeyToBuf(0);
    // 写队列
    idata.dev = 0;
    idata.val = g_lastVal;;
    xQueueSendToBackFromISR(g_xQueuePlatform, &idata, NULL);
    g_IRReceiverIRQ_Cnt = 0;
}

优化旋转编码器速度

c 复制代码
// game1.c	RotaryEncoderTask()
cnt = rdata.speed / 10;
if(!cnt) {
    cnt = 1;
}
// 修改为
if(rdata.speed > 100) {
    cnt = 4;
} else if(rdata.speed > 50) {
    cnt = 2; 
} else {
    cnt = 1;
}

优化旋转编码器控制

c 复制代码
// driver_rotary_encoder.c
// mdelay(2);
if(time - pre_time < 2000000) {			// 当前中断时间与上次中断时间间隔小于2ms,不处理
    pre_time = time;
    return ;
}

if(g_speed == 0) {
    g_speed = 1;
}

ata, NULL);

g_IRReceiverIRQ_Cnt = 0;

}

复制代码
优化旋转编码器速度

```c
// game1.c	RotaryEncoderTask()
cnt = rdata.speed / 10;
if(!cnt) {
    cnt = 1;
}
// 修改为
if(rdata.speed > 100) {
    cnt = 4;
} else if(rdata.speed > 50) {
    cnt = 2; 
} else {
    cnt = 1;
}

优化旋转编码器控制

c 复制代码
// driver_rotary_encoder.c
// mdelay(2);
if(time - pre_time < 2000000) {			// 当前中断时间与上次中断时间间隔小于2ms,不处理
    pre_time = time;
    return ;
}

if(g_speed == 0) {
    g_speed = 1;
}
相关推荐
十月的皮皮1 小时前
STM32从零到量产开发:四路继电器工业控制模块开发— Modbus RTU 通讯协议设计
stm32·stm32cubemx·hal库
JSMSEMI112 小时前
JSM4430 30V N 沟道增强型 MOSFET
单片机·嵌入式硬件
安妮细水长流16 小时前
汽车电子面试问题记录
单片机
GeekArch16 小时前
第28讲:避坑——AI堆栈分配错误、栈溢出BUG
c语言·人工智能·stm32·mcu·学习·bug
wuyk55519 小时前
59.嵌入式C语言高级宏定义实战:多行宏、字符串化与符号拼接
c语言·开发语言·stm32·单片机·嵌入式硬件·物联网·51单片机
CPETW19 小时前
企业版配套软件介绍 ---- USB TO SPI 3.0-Slave
网络·科技·单片机·嵌入式硬件·电子
piaoyiren19 小时前
STM32 综合温控风扇控制系统
stm32·单片机·嵌入式硬件·oled·lcd1602·hc-05蓝牙
亿道电子Emdoor1 天前
【Arm】解决Keil MDK无法查看外设寄存器的问题
stm32·单片机·嵌入式硬件
BW.SU1 天前
瑞佑RUI--工业UI,拖拽即成
stm32·单片机·51单片机·gui·人机界面·单片机可视化设计