跟着韦东山老师FreeRTOS教学资料的学习记录
FreeRTOS全部项目代码链接(更新中)
https://gitee.com/chenshao777/free-rtos_-study
本文章一共分为一下几个部分
1. 创建队列
2. 写队列
3. 读队列
4. 队列阻塞访问
5. 分辨数据源头
6. 传输大块数据
1. 创建队列
步骤: 包含队列头文件 -> 定义队列句柄全局变量 -> 在main函数中创建队列
#include "queue.h"
QueueHandle_t xQueue;
int main()
{
xQueue = xQueueCreate( 5, sizeof( int32_t ) );
}
重点:
xQueueCreate 函数的第一个参数是 栈的长度,第二个参数是栈内每个数据的长度
该函数会返回一个队列句柄,写队列和读队列就是通过这个句柄来进行操作
--------主函数代码(对应 2. 写队列 和 3. 读队列 )--------
int main()
{
SysTick_Init(72);
USART1_Init(115200);
printf("串口初始化成功!\r\n");
xQueue = xQueueCreate( 5, sizeof( int32_t ) );
xTaskCreate(TaskSend, "taskSend1", 200, (void*)100, 1, NULL);
xTaskCreate(TaskSend, "taskSend2", 200, (void*)110, 1, NULL);
xTaskCreate(TaskSend, "taskSend3", 200, (void*)120, 1, NULL);
xTaskCreate(TaskRead, "TaskRead", 200, NULL, 2, NULL);
vTaskStartScheduler();
}
2. 写队列
写队列任务代码,使用的 xQueueSendToBack 函数,数据写入队列尾
void TaskSend(void *pvParameters)
{
int param;
BaseType_t status;
param = (int)pvParameters;
for(;;)
{
status = xQueueSendToBack(Queue_t, ¶m, 0);
if(status == pdPASS){
printf("write success!\r\n");
}
taskYIELD();
}
}
3. 读队列
读队列代码,使用的 xQueueReceive 函数,从队头读取一个数据,并将其删除
void TaskRead(void *pvParameters)
{
int read_data;
BaseType_t result;
BaseType_t count;
for(;;)
{
result = xQueueReceive(Queue_t, &read_data, 50);
if(result == pdPASS){
printf("Read: %d\r\n", read_data);
}
else
printf("Read NULL\r\n");
}
}
4. 队列阻塞访问
队列阻塞访问有两种情况
一、写队列时发现队列已经满了
二、读队列时发现队列是空的
我们发现 写队列函数 和 读队列函数 的最后一个参数都可以传入一个 超时时间
情况一:
写队列时发现队列满了,如果设置了超时时间,则任务进入阻塞状态
-------->>>
在超时时间内,如果有 其他任务读取了队列,则会空出位置,那么会第一时间 唤醒正在阻塞的写队列任务
-------->>>
那么唤醒哪一个呢(唤醒优先级最高的、同优先级内唤醒等待时间最长的)
情况二:
读队列时发现队列空,如果设置了超时时间,则任务进入阻塞状态
-------->>>
在超时时间内,如果有 其他任务写队列了,则队列里有数据了,那么会第一时间 唤醒正在阻塞的读任务
-------->>>
那么唤醒哪一个呢(唤醒优先级最高的、同优先级内唤醒等待时间最长的)
情况三:
如果超时时间设置为0,则读不到数据(队列为空)或者写入失败(队列满),则立即返回不等待。
情况四:
如果把 xTicksToWait 设置为 portMAX_DELAY ,并且在FreeRTOSConig.h 中设定INCLUDE_vTaskSuspend 为 1,那么阻塞等待将没有超时限制,一直等。
5. 分辨数据源头
分辨数据源的方法: 使用结构体
typedef struct{
char id;
uint32_t data;
}SendStruct;
定义一个结构体,结构体成员包括 id 和 数据变量, 其中 id 用于标识数据来源
全部代码
#include "stm32f10x.h"
#include "led.h"
#include "usart.h"
#include "FreeRTOS.h"
#include "task.h"
#include "SysTick.h"
#include "queue.h"
#include "string.h"
typedef struct{
char id;
uint32_t data;
}SendStruct;
#define task1_id 1
#define task2_id 2
#define task1_data 10
#define task2_data 20
const SendStruct xSend_Buff[2] =
{
{task1_id, task1_data},
{task2_id, task2_data}
};
QueueHandle_t Queue_t;
void TaskSend(void *pvParameters)
{
BaseType_t status;
for(;;)
{
status = xQueueSendToBack(Queue_t, pvParameters, 10);
if(status != pdPASS){
printf("send fail %d \r\n", ((SendStruct *)pvParameters)->id);
}
}
}
void TaskRead(void *pvParameters)
{
SendStruct read_data;
BaseType_t result;
for(;;)
{
result = xQueueReceive(Queue_t, &read_data, 10);
if(result == pdPASS){
printf("ID: %d DATA: %d\r\n", read_data.id, read_data.data);
}else
printf("Read NULL\r\n");
}
}
int main()
{
SysTick_Init(72);
USART1_Init(115200);
printf("串口初始化成功!\r\n");
Queue_t = xQueueCreate(5, sizeof(SendStruct));
if(Queue_t != NULL)
printf("队列创建成功!\r\n");
xTaskCreate(TaskSend, "taskSend1", 200, (void *)&(xSend_Buff[0]), 3, NULL);
xTaskCreate(TaskSend, "taskSend2", 200, (void *)&(xSend_Buff[1]), 3, NULL);
xTaskCreate(TaskRead, "TaskRead", 200, NULL, 4, NULL);
vTaskStartScheduler();
}
6. 传输大块数据
传输大块数据的方法: 传输指针
定义一个大容量数组
写队列时只需写入一个指向该数组的指针的地址即可
这里注意,写入的是指向该数组的指针的地址
如果直接传入数组名(也就是数组的首地址)是不行的
举个例子
定义一个 sendBuff 数组,然后将该数组首地址写入队列,读队列时读取到的就是数组首地址了
QueueHandle_t xQueue;
char sendBuff[200];
......
......
void taskSend(void *pvParameters)
{
static int count = 0;
char *buff;
for(;;)
{
sprintf(sendBuff, "My name is ChenShao %d\r\n", count++);
buff = sendBuff;
xQueueSendToBack(xQueue, &buff, 0);
}
}
......
......
以下这三种写法都不行
- xQueueSendToBack(xQueue, sendBuff, 0); //错误写法
- xQueueSendToBack(xQueue, &sendBuff, 0); //错误写法
- xQueueSendToBack(xQueue, buff, 0); //错误写法
只能这样写
- xQueueSendToBack(xQueue, &buff, 0); //正确写法
个人理解:
因为 xQueueSendToBack 函数的第二个参数需要传入一个指针
并且这个指针本身不存储数据,它只是指向一片存储数据的内存
从而可以从那片内存中拷贝数据到队列里
所以,如果按照前三种写法,实际上传入的是 sendBuff数组的 首地址,但是这个首地址 并不指向任何内存,它只是用来存储我们传输的数据的。
将这四个传输的地址打印出来分析
可以看到 图中1 2 3 打印出的都是 sendBuff 的首地址
此时给 xQueueSendToBack 函数的第二个参数是 &sendBuff ,可以看到程序只打印出 “read data :” 就没了
左边的报错是 “*** error 65: access violation at 0x6E20794D : no ‘read’ permission”
表示内存地址访问没有权限
但是如果 将第二个参数改为 &buff , 则可以顺利运行,如下图所示
&buff 是一个地址,这个地址上存储的是 0x20000164 这个值,也就是 sendBuff 数组的首地址
所以传入一个 指向数组首地址的指针的地址 才是正确的
============== 传输大块数据全部代码 ==============
#include "stm32f10x.h"
#include "led.h"
#include "usart.h"
#include "FreeRTOS.h"
#include "task.h"
#include "SysTick.h"
#include "queue.h"
#include "string.h"
#include "stdlib.h"
QueueHandle_t xQueue;
char sendBuff[200];
void taskSend(void *pvParameters)
{
static int count = 0;
BaseType_t res;
char *buff;
for(;;)
{
sprintf(sendBuff, "My name is ChenShao %d\r\n", count++);
buff = sendBuff;
printf("sendBuff = 0x%016lx\r\n", (long unsigned int)sendBuff);
printf("&sendBuff = 0x%016lx\r\n", (long unsigned int)&sendBuff);
printf("buff = 0x%016lx\r\n", (long unsigned int)buff);
printf("&buff = 0x%016lx\r\n", (long unsigned int)&buff);
res = xQueueSendToBack(xQueue, &buff, 0);
if(res != pdPASS){
printf("write fail \r\n");
}
}
}
void taskRead(void *pvParameters)
{
const TickType_t delay = 100 / portTICK_PERIOD_MS;
char *str;
BaseType_t res;
for(;;)
{
res = xQueueReceive(xQueue, &str, delay);
if(res == pdPASS){
printf("read data : %s\r\n", str);
}else{
printf("read null\r\n");
}
}
}
int main()
{
SysTick_Init(72);
USART1_Init(115200);
printf("串口初始化成功!\r\n");
xQueue = xQueueCreate(1, sizeof(char *));
xTaskCreate(taskSend, "taskSend", 1000, NULL, 1, NULL);
xTaskCreate(taskRead, "taskRead", 1000, NULL, 2, NULL);
vTaskStartScheduler();
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)