下载 FreeRTOS
 

出色的 RTOS & 嵌入式软件

内核
最新资讯
简化任何设备的身份验证云连接。
利用 CoAP 设计节能型云连接 IoT 解决方案。
11.0.0 版 FreeRTOS 内核简介:
FreeRTOS 路线图和代码贡献流程。
使用 FreeRTOS 实现 OPC-UA over TSN。

简单多核内核间通信
使用 FreeRTOS 消息缓冲区


【FreeRTOS 下载包中的 STM32H745I 演示 提供了下面所描述的控制缓冲区方案的工作示例。】


在这篇文章中,我描述了如何实现基础、轻量级内核到内核通信的方案, 方案使用了 FreeRTOS 消息缓冲区, 消息缓冲区是无锁循环缓冲区, 可以将不同大小的数据包从单个发送器传递到单个接收器。 消息缓冲区只传输数据,不强加任何 数据必须符合的格式或更高级别的协议。

下述用例中,发送和接收任务是在 非对称多处理器 (AMP) 配置中的多核微控制器 (MCU) 的不同内核 进行——这说明着每个内核只运行自己的 FreeRTOS 实例。 唯一的 硬件要求(除了有一个以上的内核)是具有 一个内核可以在另一个内核中产生中断的能力,同时需具有 两个内核都可以访问的内存区域(共享内存)。 消息缓冲区 放在上面所述共享内存中, 在每个内核中的应用程序知道该共享内存地址。 见图 1。 理想情况下,还将有一个内存保护单元 (MPU) ,以确保消息缓冲区只能 可通过内核的消息 缓冲区 API 访问,并且最好将共享内存标记为不可缓存。

AMP 多核配置中两个内核上的 RTOS
图 1:硬件拓扑。 点击放大。


以下两个伪代码列表显示 API 函数的结构 用于从消息缓冲区发送和接收信息。 可以看出,在这两种情况下, 调用任务可以选择性进入阻塞状态(因此不消耗 任何 CPU 周期)等待操作完成。


xMessageBufferSend()
{
    /* If a time out is specified and there isn't enough
    space in the message buffer to send the data, then
    enter the blocked state to wait for more space. */
    if( time out != 0 )
    {
        while( there is insufficient space in the buffer &&
               not timed out waiting )
        {
            Enter the blocked state to wait for space in the buffer
        }
    }

    if( there is enough space in the buffer )
    {
        write data to buffer
        sbSEND_COMPLETED()
    }
}
				
用于将数据发送到流缓冲区的简化伪代码


xMessageBufferReceive()
{
    /* If a time out is specified and the buffer doesn't
    contain any data that can be read, then enter the
    blocked state to wait for the buffer to contain data. */
    if( time out != 0 )
    {
        while( there is no data in the buffer &&
               not timed out waiting )
        {
            Enter the blocked state to wait for data
        }
    }

    if( there is data in the buffer )
    {
        read data from buffer
        sbRECEIVE_COMPLETED()
    }
}
				
用于从流缓冲区读取数据的简化伪代码


如果任务在 xMessageBufferReceive() 中进入阻塞状态以等待缓冲区 装入数据,则将数据发送到缓冲区必须取消阻塞任务,以便 完成其操作。 取消阻塞任务通过 xMessageBufferSend() 调用 sbSEND_COMPLETED() 来完成 ,这是一个预处理宏。

默认的 sbSEND_COMPLETED 实现方式是假设发送任务 (或中断任务)和接收任务是处于相同的 FreeRTOS 内核实例控制之下,并在同一 MCU 内核上运行。 在这个 AMP 例子中 所述发送任务和所述接收任务处于 FreeRTOS 内核不同实例的控制之下,并在不同的 MCU 核心上运行, 因此,默认的 sbSEND_COMPLETED 实现方式不起作用(每个 FreeRTOS 内核实例仅了解其控制下的任务)。 因此,AMP 方案需要覆盖 sbSEND_COMPLETED 宏(并且可能 是 sbRECEIVE_COMPLETED 宏,请参阅下文),这 只需向项目的 FreeRTOSConfig.h 文件中提供您自己 的实现。 重新实现的 sbSEND_COMPLETED() 宏可以简单地触发 在另一个 MCU 核心中的任务中断。 中断处理程序(ISR 在一个内核中触发,在另外一个内核中执行)必须完成任务,否则 由 sbSEND_COMPLETE 的默认实现完成的任务,即 如果任务正等待从包含数据的消息缓冲区接收数据,则取消阻塞任务。 ISR 取消任务阻塞是通过将消息缓冲区的句柄作为参数传递 到 xMessageBufferSendCompletedFromISR() 函数来完成的。 此序列由 图 2 中的编号箭头所示,其中发送和接收任务在不同的 MCU 核心上:

  1. 接收任务尝试从空消息缓冲区读取,并且 进入阻塞状态以等待数据到达。

  2. 发送任务将数据写入消息缓冲区。

  3. sbSEND_COMPLETED() 在 正在执行接收任务的核心中触发中断。

  4. 中断服务程序调用 xMessageBufferSendCompletedFromISR() 以取消阻塞接收任务,该任务现在可以从消息缓冲区读取数据因为 缓冲区不再为空。

AMP 多核配置中两个内核上的 RTOS
图 2:编号箭头对应于上面的编号列表,描述通过消息缓冲区传输一个数据项。 点击放大


很容易将消息缓冲区的句柄传递到 xMessageBufferSendCompletedFromISR() 在只有一个消息缓冲区时,请考虑有两个 或更多的消息缓冲区的情况-然后 ISR 必须首先确定哪个消息 缓冲区包含数据。 有几种方法可以做到这一点,如果 消息缓冲区数量很小。 例如:

  • 如果硬件允许,那么每个消息缓冲区可以使用不同的中断线, 它保持中断服务例程和 消息缓冲区之间的一对一映射。

  • 中断服务例程可以简单查询每个消息缓冲区, 查看其中是否包含数据。

  • 多个消息缓冲区可以被单个消息缓冲区替换,该消息缓冲区 传递两个元数据(消息是什么,其预期接受方是什么 等)以及实际数据。

然而,这些技术不足以处理存在大量或未知数量的 消息缓冲区---在这种情况下,可扩展的解决方案是引入一个单独的控件 消息缓冲区。 如下面的代码所示, sbSEND_COMPLETED() 使用 控制消息缓冲区以传递包含数据的消息缓冲区的句柄 进入中断服务例程。


/* Added to FreeRTOSConfig.h to override the default implementation. */
#define sbSEND_COMPLETED( pxStreamBuffer ) vGenerateCoreToCoreInterrupt( pxStreamBuffer )

/* Implemented in a C file. */
void vGenerateCoreToCoreInterrupt( MessageBufferHandle_t xUpdatedBuffer )
{
size_t BytesWritten.

    /* Called by the implementation of sbSEND_COMPLETED() in FreeRTOSConfig.h.
    If this function was called because data was written to any message buffer
    other than the control message buffer then write the handle of the message
    buffer that contains data to the control message buffer, then raise an
    interrupt in the other core.  If this function was called because data was
    written to the control message buffer then do nothing. */
    if( xUpdatedBuffer != xControlMessageBuffer )
    {
        BytesWritten = xMessageBufferSend(  xControlMessageBuffer,
                                            &xUpdatedBuffer,
                                            sizeof( xUpdatedBuffer ),
                                            0 );

        /* If the bytes could not be written then the control message buffer
        is too small! */
        configASSERT( BytesWritten == sizeof( xUpdatedBuffer );

        /* Generate interrupt in the other core (pseudocode). */
        GenerateInterrupt();
    }
}

				
当使用控制消息缓冲区时实现 sbSEND_COMPLETED()。


然后 ISR 读取控制消息缓冲区以获得句柄,之后通过 将句柄作为参数传入 xMessageBufferSendCompletedFromISR()。 请参阅 下面的代码列表。


void InterruptServiceRoutine( void )
{
MessageBufferHandle_t xUpdatedMessageBuffer;
BaseType_t xHigherPriorityTaskWoken = pdFALSE;

    /* Receive the handle of the message buffer that contains data from the
    control message buffer.  Ensure to drain the buffer before returning. */
    while( xMessageBufferReceiveFromISR( xControlMessageBuffer,
                                         &xUpdatedMessageBuffer,
                                         sizeof( xUpdatedMessageBuffer ),
                                         &xHigherPriorityTaskWoken )
                                           == sizeof( xUpdatedMessageBuffer ) )
    {
        /* Call the API function that sends a notification to any task that is
        blocked on the xUpdatedMessageBuffer message buffer waiting for data to
        arrive. */
        xMessageBufferSendCompletedFromISR( xUpdatedMessageBuffer,
                                            &xHigherPriorityTaskWoken );
    }

    /* Normal FreeRTOS "yield from interrupt" semantics, where
    xHigherPriorityTaskWoken is initialised to pdFALSE and will then get set to
    pdTRUE if the interrupt unblocks a task that has a priority above that of
    the currently executing task. */
    portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
}
                
使用控制消息缓冲区时 ISR 的实现。


图 3 显示了使用控制消息缓冲区时的序列。 同样, 图中编号箭头与项目编号相关:

  1. 接收任务尝试从空消息缓冲区读取,并且 进入阻塞状态以等待数据到达。

  2. 发送任务将数据写入消息缓冲区。

  3. sbSEND_COMPLETED() 发送消息缓冲区的句柄,该句柄现在 包含发向控制消息缓冲区的数据。

  4. sbSEND_COMPLETED() 在 正在执行接收任务的内核中触发中断。

  5. 中断服务程序读取消息缓冲区的句柄,该句柄 包含来自控制消息缓冲区的数据,然后传递句柄 到 xMessageBufferSendCompletedFromISR() API 函数中以解锁 任务接收功能,现在可以从缓冲区读取消息,因为缓冲区 不再为空。

AMP 多核配置中两个内核上的 RTOS
图 3 :编号箭头对应于上面的编号列表, 它描述了一个数据项如何通过众多消息缓冲区的一个, 它使用控制消息缓冲区来允许 ISR 了解哪个消息缓冲区包含数据。 点击放大


到目前为止,我们只考虑了发送任务必须取消阻塞 接收任务的情况。 如果用于内核到内核之间的通信的消息缓冲区已充满了消息, 导致发送任务被阻塞,然后它也 必须考虑接收任务如何解除阻塞发送任务。 那么可以 通过覆盖 sbRECEIVE_COMPLETED() 的默认实现, 与已经描述的 sbSEND_COMPLETED() 完全相同。

在所有这些情况下,确保任务不会在 一个消息队列中无限期阻塞(以防出现中断丢失的情况)是一种不错的防御性编程实践,并应总是将消息队列完全排空, 而不是假定每个中断有一条消息。



Copyright (C) Amazon Web Services, Inc. or its affiliates. All rights reserved.