关注V公众号,有更多精彩,第一时间收到消息,每天带你学习,一天一点,一年一大变
有关FreeRTOS中的函数后面都会讲到,前面章节部分主要是说一下原理,看的时候比较乏味,了解即可,没必要较真
FreeRTOS流和缓冲区是任务和任务之间、中断和任务之间的通信原语。和其他多数FreeRTOS通信原语不同,他们针对读和写的单一性进行了方案的优化工作。例如将数据从中断服务例程传递到任务,或在双核cpu上从一个微控制器内核传递到另一个微控制器内核。数据通过拷贝传递——发送端将数据复制到缓冲区,读取端将数据复制出缓冲区。
流缓冲区传递连续的字节流。消息缓冲区传递大小可变但离散的消息。消息缓冲区使用流缓冲区进行数据传输。
在FreeRTOS对象是惟一的,流缓冲区的实现(也是消息缓冲区的实现,因为消息缓冲区是建立在流缓冲区之上的)假设只有一个任务或中断将写入缓冲区(写入器),并且仅有一个任务或中断将从缓冲区读取(读取器)。对不同的任务或者中断写入或者读取是安全的,但是有多个不同的写入或者读取是不安全的。若有多个不同的写入器,那么程序写入必须每次对写入的API函数(例如xStreamBufferSend())的调用都放在临界区中,并将发送阻塞时间设置为0。同样,如果有多个不同的读取器,那么应用程序编写器必须将每次对读取API函数(如xStreamBufferReceive())的调用都放在临界区中,并使用0的接收块时间。
流缓冲区允许一个字节流从中断服务程序传递到一个任务,或者从一个任务传递到另一个任务。字节流可以是任意长度的,而且不一定有开头或结尾。一次可以写入任意数量的字节,一次可以读取任意数量的字节。数据通过复制传递——发送端将数据复制到缓冲区,读取端将数据复制出缓冲区。
与大多数其他FreeRTOS通信原语不同,流缓冲区是针对单读单写场景进行优化的,例如将数据从中断服务程序传递到任务,或在双核CPU上从一个微控制器内核传递到另一个微控制器内核。
在FreeRTOS/source/stream_buffer.c源文件来启用流缓冲区功能。可自行查阅。
流缓冲区的实现使用了任务的通知。因此,调用将调用任务置于阻塞状态的流缓冲区API函数可以更改调用任务的通知状态和值。
在reeRTOS/Demo/Common/Minimal/StreamBufferInterrupt.c源文件 提供了一个示例,说明从中断服务程序到任务使用流缓冲区传递数据的例程。在这里我给大家复制过来了。
/*
* FreeRTOS V202112.00
* Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
* http://www.FreeRTOS.org
* http://aws.amazon.com/freertos
*
* 1 tab == 4 spaces!
*/
/*
* A simple example that shows a stream buffer being used to pass data from an
* interrupt to a task.
*
* There are two strings, pcStringToSend and pcStringToReceive, where
* pcStringToReceive is a substring of pcStringToSend. The interrupt sends
* a few bytes of pcStringToSend to a stream buffer ever few times that it
* executes. A task reads the bytes from the stream buffer, looking for the
* substring, and flagging an error if the received data is invalid.
*/
/* Standard includes. */
#include "stdio.h"
#include "string.h"
/* FreeRTOS includes. */
#include "FreeRTOS.h"
#include "task.h"
#include "stream_buffer.h"
/* Demo app includes. */
#include "StreamBufferInterrupt.h"
#define sbiSTREAM_BUFFER_LENGTH_BYTES ( ( size_t ) 100 )
#define sbiSTREAM_BUFFER_TRIGGER_LEVEL_10 ( ( BaseType_t ) 10 )
/*-----------------------------------------------------------*/
/* Implements the task that receives a stream of bytes from the interrupt. */
static void prvReceivingTask( void * pvParameters );
/*-----------------------------------------------------------*/
/* The stream buffer that is used to send data from an interrupt to the task. */
static StreamBufferHandle_t xStreamBuffer = NULL;
/* The string that is sent from the interrupt to the task four bytes at a
* time. Must be multiple of 4 bytes long as the ISR sends 4 bytes at a time*/
static const char * pcStringToSend = "_____Hello FreeRTOS_____";
/* The string to task is looking for, which must be a substring of
* pcStringToSend. */
static const char * pcStringToReceive = "Hello FreeRTOS";
/* Set to pdFAIL if anything unexpected happens. */
static BaseType_t xDemoStatus = pdPASS;
/* Incremented each time pcStringToReceive is correctly received, provided no
* errors have occurred. Used so the check task can check this task is still
* running as expected. */
static uint32_t ulCycleCount = 0;
/*-----------------------------------------------------------*/
void vStartStreamBufferInterruptDemo( void )
{
/* Create the stream buffer that sends data from the interrupt to the
* task, and create the task. */
xStreamBuffer = xStreamBufferCreate( /* The buffer length in bytes. */
sbiSTREAM_BUFFER_LENGTH_BYTES,
/* The stream buffer's trigger level. */
sbiSTREAM_BUFFER_TRIGGER_LEVEL_10 );
xTaskCreate( prvReceivingTask, /* The function that implements the task. */
"StrIntRx", /* Human readable name for the task. */
configMINIMAL_STACK_SIZE, /* Stack size (in words!). */
NULL, /* Task parameter is not used. */
tskIDLE_PRIORITY + 2, /* The priority at which the task is created. */
NULL ); /* No use for the task handle. */
}
/*-----------------------------------------------------------*/
static void prvReceivingTask( void * pvParameters )
{
char cRxBuffer[ 20 ];
BaseType_t xNextByte = 0;
/* Remove warning about unused parameters. */
( void ) pvParameters;
/* Make sure the string will fit in the Rx buffer, including the NULL
* terminator. */
configASSERT( sizeof( cRxBuffer ) > strlen( pcStringToReceive ) );
/* Make sure the stream buffer has been created. */
configASSERT( xStreamBuffer != NULL );
/* Start with the Rx buffer in a known state. */
memset( cRxBuffer, 0x00, sizeof( cRxBuffer ) );
for( ; ; )
{
/* Keep receiving characters until the end of the string is received.
* Note: An infinite block time is used to simplify the example. Infinite
* block times are not recommended in production code as they do not allow
* for error recovery. */
xStreamBufferReceive( /* The stream buffer data is being received from. */
xStreamBuffer,
/* Where to place received data. */
( void * ) &( cRxBuffer[ xNextByte ] ),
/* The number of bytes to receive. */
sizeof( char ),
/* The time to wait for the next data if the buffer
* is empty. */
portMAX_DELAY );
/* If xNextByte is 0 then this task is looking for the start of the
* string, which is 'H'. */
if( xNextByte == 0 )
{
if( cRxBuffer[ xNextByte ] == 'H' )
{
/* The start of the string has been found. Now receive
* characters until the end of the string is found. */
xNextByte++;
}
}
else
{
/* Receiving characters while looking for the end of the string,
* which is an 'S'. */
if( cRxBuffer[ xNextByte ] == 'S' )
{
/* The string has now been received. Check its validity. */
if( strcmp( cRxBuffer, pcStringToReceive ) != 0 )
{
xDemoStatus = pdFAIL;
}
/* Return to start looking for the beginning of the string
* again. */
memset( cRxBuffer, 0x00, sizeof( cRxBuffer ) );
xNextByte = 0;
/* Increment the cycle count as an indication to the check task
* that this demo is still running. */
if( xDemoStatus == pdPASS )
{
ulCycleCount++;
}
}
else
{
/* Receive the next character the next time around, while
* continuing to look for the end of the string. */
xNextByte++;
configASSERT( ( size_t ) xNextByte < sizeof( cRxBuffer ) );
}
}
}
}
/*-----------------------------------------------------------*/
void vBasicStreamBufferSendFromISR( void )
{
static size_t xNextByteToSend = 0;
const BaseType_t xCallsBetweenSends = 100, xBytesToSend = 4;
static BaseType_t xCallCount = 0;
/* Is it time to write to the stream buffer again? */
xCallCount++;
if( xCallCount > xCallsBetweenSends )
{
xCallCount = 0;
/* Send the next four bytes to the stream buffer. */
xStreamBufferSendFromISR( xStreamBuffer,
( const void * ) ( pcStringToSend + xNextByteToSend ),
xBytesToSend,
NULL );
/* Send the next four bytes the next time around, wrapping to the start
* of the string if necessary. */
xNextByteToSend += xBytesToSend;
if( xNextByteToSend >= strlen( pcStringToSend ) )
{
xNextByteToSend = 0;
}
}
}
/*-----------------------------------------------------------*/
BaseType_t xIsInterruptStreamBufferDemoStillRunning( void )
{
uint32_t ulLastCycleCount = 0;
/* Check the demo is still running. */
if( ulLastCycleCount == ulCycleCount )
{
xDemoStatus = pdFAIL;
}
else
{
ulLastCycleCount = ulCycleCount;
}
return xDemoStatus;
}
xStreamBufferReceive()用于读取来自 RTOS 任务的流缓冲区的数据。xStreamBufferReceiveFromISR())是 用于从中断服务例程 (ISR) 从流缓冲区中读取数据。
下面是这两个函数的用法举例:
//1-xStreamBufferReceive()
void vAFunction( StreamBuffer_t xStreamBuffer )
{
uint8_t ucRxData[ 20 ];
size_t xReceivedBytes;
const TickType_t xBlockTime = pdMS_TO_TICKS( 20 );
/* Receive up to another sizeof( ucRxData ) bytes from the stream buffer.
Wait in the Blocked state (so not using any CPU processing time) for a
maximum of 100ms for the full sizeof( ucRxData ) number of bytes to be
available. */
xReceivedBytes = xStreamBufferReceive( xStreamBuffer,
( void * ) ucRxData,
sizeof( ucRxData ),
xBlockTime );
if( xReceivedBytes > 0 )
{
/* A ucRxData contains another xRecievedBytes bytes of data, which can
be processed here.... */
}
}
2--xStreamBufferReceiveFromISR()
/* A stream buffer that has already been created. */
StreamBuffer_t xStreamBuffer;
void vAnInterruptServiceRoutine( void )
{
uint8_t ucRxData[ 20 ];
size_t xReceivedBytes;
BaseType_t xHigherPriorityTaskWoken = pdFALSE; /* Initialised to pdFALSE. */
/* Receive the next stream from the stream buffer. */
xReceivedBytes = xStreamBufferReceiveFromISR( xStreamBuffer,
( void * ) ucRxData,
sizeof( ucRxData ),
&xHigherPriorityTaskWoken );
if( xReceivedBytes > 0 )
{
/* ucRxData contains xReceivedBytes read from the stream buffer.
Process the stream here.... */
}
/* If xHigherPriorityTaskWoken was set to pdTRUE inside
xStreamBufferReceiveFromISR() then a task that has a priority above the
priority of the currently executing task was unblocked and a context
switch should be performed to ensure the ISR returns to the unblocked
task. In most FreeRTOS ports this is done by simply passing
xHigherPriorityTaskWoken into taskYIELD_FROM_ISR(), which will test the
variables value, and perform the context switch if necessary. Check the
documentation for the port in use for port specific instructions. */
taskYIELD_FROM_ISR( xHigherPriorityTaskWoken );
}
xStreamBufferReceive()允许指定阻塞时间如果任务使用xStreamBufferReceive()从一个空的流缓冲区中读取数据时指定了一个非零的阻塞时间,那么任务将被放置到阻塞状态(因此它不会消耗任何CPU时间,其他任务可以运行),直到流缓冲区中有指定数量的数据可用,或者块时间到期。在等待数据的任务从阻塞状态移除之前,流缓冲区中必须存在的数据量称为流缓冲区的触发级别。例如:
如果一个任务在读取触发级别为1的空流缓冲区时被阻塞,那么当向缓冲区写入一个字节或任务的阻塞时间到期时,该任务将被解除阻塞。
如果一个任务在读取触发级别为10的空流缓冲区时被阻塞,那么该任务将不会被解除阻塞,直到流缓冲区包含至少10个字节或任务的阻塞时间到期。
如果读取任务的阻塞时间在触发级别到达之前超时,那么无论实际可用的字节数是多少,该任务仍然会接收到。
xStreamBufferSend())用于从RTOS任务向流缓冲区发送数据。xStreamBufferSendFromISR())用于从中断服务例程(ISR)向流缓冲区发送数据。
如果任务使用xStreamBufferSend()向流缓冲区写入数据时指定了一个非零的阻塞时间,那么该任务将被放置到阻塞状态(因此它不会消耗任何CPU时间,其他任务可以运行),直到流缓冲区中有空间可用,或者阻塞时间到达。
流和消息缓冲区在每次发送和接收操作完成后都会执行一个回调:
使用xStreamBufferCreate()和xMessageBufferCreate() API函数(以及它们静态分配的等价函数)创建的流和消息缓冲区共享相同的回调函数,这些回调函数是使用sbSEND_COMPLETED()和sbRECEIVE_COMPLETED()宏定义的。
使用xStreamBufferCreateWithCallback()和xMessageBufferCreateWithCallback() API函数(以及它们静态分配的等价函数)创建的流缓冲区和消息缓冲区都可以有自己唯一的回调函数。
sbSEND_COMPLETED() (and sbSEND_COMPLETED_FROM_ISR())
sbSEND_COMPLETED()是一个宏,当数据写入使用xStreamBufferCreate()或xStreamBufferCreateStatic() API创建的流缓冲区时,会调用该宏(在FreeRTOS API函数内部)。它接受一个参数,即更新后的流缓冲区句柄。
默认情况下(如果应用程序编写者没有提供自己的宏实现),sbSEND_COMPLETED()会检查是否有任务阻塞在流缓冲区上等待数据,如果有,则从阻塞状态中删除该任务。
应用程序编写人员可以通过在FreeRTOSConfig.h中提供他们自己的sbSEND_COMPLETED()实现来改变这种默认行为。当使用流缓冲区在多核处理器的核之间传递数据时,这很有用。在这种情况下,可以实现sbSEND_COMPLETED()在另一个CPU核心中生成一个中断,然后中断的服务例程可以使用xStreamBufferSendCompletedFromISR() API函数来检查(如果必要的话)等待数据的任务。
在FreeRTOS/Demo/Common/Minimal/MessageBufferAMP.c文件中有更详细的应用过程。有时间到时候会单独的把FreeRTOS所有函数给介绍一下以及应用。
sbRECEIVE_COMPLETED() (and sbRECEIVE_COMPLETED_FROM_ISR())
sbRECEIVE_COMPLETED()是与sbSEND_COMPLETED()等价的接收方。当从流缓冲区读取数据时,它会被调用(在FreeRTOS API函数内部)。默认情况下(如果应用程序编写人员没有提供自己的宏实现),宏会检查流缓冲区上是否有任务阻塞,以等待缓冲区内的空间可用,如果有,则将该任务从阻塞状态中移除。
与bSEND_COMPLETED()一样,应用程序编写人员可以通过在FreeRTOSConfig.h(后续会单独讲述这个文件)中提供替代实现来更改sbRECEIVE_COMPLETED()的默认行为。如果你需要每个流缓冲区都有自己的“接收完成”行为,可以使用xStreamBufferCreateWithCallback()或xStreamBufferCreateStaticWithCallback() API函数来创建流缓冲区。
消息缓冲区允许不同字节的离散消息从中断服务程序传递到一个进程,或从一个进程传递到另一个进程。例如,长度为10、20和123字节的消息都可以写入和读取到同一个消息缓冲区。与使用流缓冲区不同的是,10字节的消息只能作为10字节消息读取,而不能作为单个字节读取。消息缓冲区建立在流缓冲区之上(也就是说,它们使用流缓冲区的实现)。
数据通过复制的方式通过消息缓冲区——发送方将数据复制到缓冲区中,读取方将数据复制出缓冲区。
在文件目录FreeRTOS/Demo/Common/Minimal/MessageBufferAMP.c下提供了一个从一个MCU到另一个MCU的数据,可以看看。
为了能让消息缓冲区能够处理不同字节的消息,每个消息的字节大小在消息之前写入消息缓冲区(这在FreeRTOS API函数内部发生)。字节长度保存在一个变量中,其类型由FreeRTOSConfig.h中的configMESSAGE_BUFFER_LENGTH_TYPE常数设置。
如果没有额外的定义,configMESSAGE_BUFFER_LENGTH_TYPE默认为size_t类型,,size_t在32位体系结构上通常是4字节。所以呢,当configMESSAGE_BUFFER_LENGTH_TYPE为4字节时,向缓冲区写入一个10字节的消息,但是实际上占用的字节是超过10个字节的,实际上是占用了14个字节。同样,把100个字节的消息写入到缓冲区,实际上占用104个字节空间。
xMessageBufferReceive())是用来读取任务消息缓冲区的数据的,xMessageBufferReceiveFromISR())是 用于从中断服务程序 (ISR) 的消息缓冲区读取数据。
用于从RTOS任务的消息缓冲区中读取数据。用于从中断服务例程(ISR)的消息缓冲区中读取数据xMessageBufferSend()用于发送 数据从 RTOS 任务发送到消息缓冲区。xMessageBufferSendFromISR()是用于将数据从中断服务程序 (ISR) 发送到消息缓冲区。
如果在任务中使用xMessageBufferReceive()说明了阻塞时间,从消息缓冲区,而这个缓冲区刚好是空的,则这个函数置于阻塞状态(因此它不会消耗任何CPU时间,其他任务可以运行),直到数据在消息缓冲区中可用,或阻塞时间到达。)
如果在任务中使用xMessageBufferSend()函数写入到消息缓冲区,这个缓冲区已经满了,则任务将会被置位到阻塞状态(因此它不消耗任何 CPU 时间,并且可以运行其他任务) 直到消息缓冲区中有空间可用或阻塞时间结束。)