FreeRTOS 10以后引入了一个新的数据类型就是 Stream Buffer。它和Queue最大的不同就是,Stream Buffer读写的大小没有限制,而Queue是预设值好固定的值。Stream Buffer 流媒体缓存顾名思义它的受众对象就是 流媒体 比如MP3,视频,在线电台等。
需要设置最小“一帧”的大小,播放一个声音最少需要8个字节。设置xTruggerLevel = 8
流媒体的优点:输入输出数据的大小不受限制
程序: 使用Stream Buffer 对流媒体数据,在任务间进行传输
流媒体,读和写的大小都没有任何的限制
读和写的大小可以不一致, 比如写入100 bytes, 可以分成两次每次50 bytes读取出来
注意: 适合于一个任务写,另外一个任务读
不适合多任务读写
如果必须要用在多任务的读写,请将内容放入CRITICAL SECTION
可以使用 MUTEX 或者 TASK Notification
/*
程序: 使用Stream Buffer 对流媒体数据,在任务间进行传输
流媒体,读和写的大小都没有任何的限制
读和写的大小可以不一致, 比如写入100 bytes, 可以分成两次每次50 bytes读取出来
注意: 适合于一个任务写,另外一个任务读
不适合多任务读写
如果必须要用在多任务的读写,请将内容放入CRITICAL SECTION
可以使用 MUTEX 或者 TASK Notification
公众号:孤独的二进制
API:
*/
//不要忘记include stream_buffer.h
#include
StreamBufferHandle_t xStreamMusic = NULL; //创建一个 Stream Buffer 的 handler
void downloadTask(void *pvParam) { //下载音乐
String music;
size_t xBytesSent; //The number of bytes written to the stream buffer.
while (1) {
//从网络下载音乐,放一些随机的延迟
for (int i = 0; i < random(20, 40); i++) vTaskDelay(1);
music = randomMusic(); //随机生成一些数据
xBytesSent = xStreamBufferSend( xStreamMusic,
(void *)&music,
sizeof(music),
portMAX_DELAY);
if ( xBytesSent != sizeof( music ) ) {
Serial.println("警告: xStreamBufferSend 写入数据出错"); //Optional
}
vTaskDelay(100);
}
}
void playBackTask(void *pvParam) { //解码并且播放
size_t xReceivedBytes; //The number of bytes read from the stream buffer.
size_t xReadBytes = 8*10-1;
String music;
while (1) {
xReceivedBytes = xStreamBufferReceive( xStreamMusic,
( void * ) &music,
xReadBytes,
portMAX_DELAY );
if ( xReceivedBytes > 0 )
{
decode(music);
}
}
}
void monitorTask (void * pvParam) { //对 streamBuffer进行监控
size_t xAvailable, xUsed;
bool isFull;
while (1) {
//Queries a stream buffer to see if it is full.
if (xStreamBufferIsFull(xStreamMusic) == pdTRUE) Serial.println("xStreamMusic 已满");
//Queries a stream buffer to see how much data it contains
xUsed = xStreamBufferBytesAvailable(xStreamMusic);
//Queries a stream buffer to see how much free space it contains
xAvailable = xStreamBufferSpacesAvailable(xStreamMusic);
char msg[40];
sprintf(msg, "xStreamBuffer已使用 %d 字节", xUsed);
Serial.println(msg);
sprintf(msg, "xStreamBuffer可用空间为 %d 字节", xAvailable);
Serial.println(msg);
vTaskDelay(2000);
}
}
void setup() {
Serial.begin(115200);
//Stream Buffer的最大尺寸,如果超出可能内存空间,那么创建Stream Buffer就会失败
const size_t xStreamBufferSizeBytes = 540;
//Trigger Level - Stream Buffer内数据超过这个数值,才会被读取
const size_t xTriggerLevel = 8;
xStreamMusic = xStreamBufferCreate(xStreamBufferSizeBytes, xTriggerLevel);
if ( xStreamMusic == NULL )
{ //内存不过,无法创建Stream Buffer
Serial.println("UNABLE TO CREATE STREAM BUFFER");
}
else
{
xTaskCreate(downloadTask, "Download Music", 1024 * 8, NULL, 1, NULL); //下载音乐
xTaskCreate(playBackTask, "Playback Music", 1024 * 8, NULL, 1, NULL); //解码播放音乐
xTaskCreate(monitorTask, "Monitor Stream Buffer", 1024 * 8, NULL, 1, NULL); //对Stream Buffer进行监控
}
vTaskDelete(NULL); //setup 和 loop 这个loopBack任务没用了,自宫了
}
void loop() {
}
Message Buffer是基于Stream Buffer的。只是在每一次发送数据的时候多了四个字节的空间用于存放消息的大小。根据这个消息大小,读取放就可以一次读取出全部消息的内容。
在每一次的数据里面都会加4个字节,用于存放数据大小
用处:消息的传输。任务1对GPS数据进行读取,任务2进行LCD显示。
很适合 串口 接收和发送数据,每次的大小不定,但是接受和发送的数据量需要相同
注意:在每次发送数据都会自动加4个字节,因此读的数据和写的数据相同。
/*
程序: Message Buffer
基于Stream Buffer上实现的, 在传输的时候用4个字节记录了sent的内容大小
这样子读取的话,也可以一次读取对应大小的数据
所以很适合 串口 接收和发送数据,每次的大小不定,但是接受和发送的数据量需要相同
公众号:孤独的二进制
*/
//不要忘记include message_buffer.h
#include
#include
LiquidCrystal_I2C lcd(0x27, 20, 4);
MessageBufferHandle_t xMessageBuffer = NULL;
void readGPS(void *pvParam)
{
size_t xBytesSent; // The number of bytes written to the message buffer.
String gpsInfo;
while (1)
{
gpsInfo = randomGPS(); //随机发送不同长度的信息
xBytesSent = xMessageBufferSend(xMessageBuffer,
(void *)&gpsInfo,
sizeof(gpsInfo),
portMAX_DELAY);// 如果内容已经满了,则处于堵塞状态,此时不占用CPU内存
if (xBytesSent != sizeof(gpsInfo))
{
Serial.println("危险: xMessageBufferSend 发送数据不完整");
}
vTaskDelay(3000);
}
}
void showGPS(void *pvParam)
{
size_t xReceivedBytes;
String gpsInfo;
const size_t xMessageSizeMax = 100;
lcd.init();
lcd.backlight();
lcd.setCursor(0, 0);
lcd.print(" GPS INFO"); // clear this line
while (1)
{
xReceivedBytes = xMessageBufferReceive(xMessageBuffer,
(void *)&gpsInfo,
xMessageSizeMax, // 设置最大读取的熟练多少,跟显示屏可以显示多少内容有关
portMAX_DELAY);
if (xReceivedBytes > 0)
{
gpsDecoder(gpsInfo); //解码,并且显示到屏幕上
}
vTaskDelay(1000);
}
}
void monitorTask(void *pvParam)
{ //对 streamBuffer进行监控
size_t xAvailable, xUsed;
bool isFull;
while (1)
{
// Queries a stream buffer to see if it is full.
if (xMessageBufferIsFull(xMessageBuffer) == pdTRUE)
Serial.println("xMessageBuffer 已满");
// Queries a stream buffer to see how much free space it contains
xAvailable = xMessageBufferSpacesAvailable(xMessageBuffer);
char msg[40];
sprintf(msg, "xMessageBuffer可用空间为 %d 字节", xAvailable);
Serial.println(msg);
vTaskDelay(1000);
}
}
void setup()
{
Serial.begin(115200);
const size_t xMessageBufferSizeBytes = 100; // 创建消息缓存大小,因为每次发送数据自动加4个字节,所以不需要加****
xMessageBuffer = xMessageBufferCreate(xMessageBufferSizeBytes);
if (xMessageBuffer == NULL)
{
Serial.println("Unable to Create Message Buffer");
}
else
{
xTaskCreate(readGPS, "Read GPX", 1024 * 4, NULL, 1, NULL);
xTaskCreate(showGPS, "Show GPX", 1024 * 4, NULL, 1, NULL);
xTaskCreate(monitorTask, "Monitor Message Buffer", 1024 * 8, NULL, 1, NULL); //对Stream Buffer进行监控
}
}
void loop()
{
// String gpsinfo = randomGPS();
// Serial.println(gpsinfo);
// gpsDecoder(gpsinfo);
// delay(1000);
}
Direct Task Notification是FreeRTOS 10版本以后的最重要的一个功能。他可以实现大部分二进制信号量,计数信号量,事件组,邮箱等等的功能。而且速度快45%,占用更少的内存。
采用CPU分时的方法,1s内执行一千次ticks,达到实时的效果
任务传单参数/多参数,只能用指针的方式,用指针不用进行数据的复制,节省内存空间,采用结构体进行多参数的传递
创建任务注意事项:core0运行蓝牙和WIFI
内存管理:分配内存空间尽量大一点,1024* N 字节
看门狗与任务优先级结合使用,避免一直工作在某一个任务上
任务的绝对频率,应用在周期性的任务上,时间更加准确
软件定时器:分为一次性的和周期性的定时器
保护数据安全(线程安全,还有多任务全局变量Mutex):队列单数据、流媒体缓存和消息缓存(后两者是freertos最新的版本10.0才有的)
队列单数据:在创建队列时,已经规定队列的大小,放数据和取数据,数据结构必须一样
流媒体缓存:放的数据和取的数据大小可以不一样,比如MP3、MP4
消息缓存:基于流媒体缓存,第一次放的X,则第一次读出的也为X,每一次的放入可以不一样大,但是每一次的读出一样大(适合于串口的数据读取,可以采用Mutex对数据进行保护)
当一个资源被N(≥2)个资源访问,必须采用Mutex对线程进行保护