|
使用FreeRTOS消息缓冲区,实现简单的非对称多处理(AMP)核心到核心通信(STM32H7 双核处理器)。 一、概述 实现STM32H7双核之间通信是FreeRTOS官方提供的一个方案,是基于FreeRTOS消息缓冲区,该消息缓冲区是无锁循环缓冲区,可以将大小不同的数据包从单个发送方传递到单个接收方。 说明,该消息缓冲区仅提供数据的传输,不提供通信相关协议处理。 二、基本原理 实现双核之间通信基本原理:发送和接收任务位于非对称多处理器(AMP)配置中的多核微控制器(MCU)的不同内核上,这意味着每个内核都运行自己的FreeRTOS程序。 同时,一个内核在另一个内核中具有生成中断的能力,以及两个内核都有访问的内存区域(共享内存)。消息缓冲区以每个内核上运行在应用程序已知的地址置在共享内存中,如下图: 
理想情况下,还将有一个内存保护单元(MPU),以确保只能通过内核的消息缓冲区API来访问消息缓冲区,并最好将共享内存标记为不可被其他程序占用。 三、单消息代码描述 这里官方提供了实现该方案的基础代码(仅供参考)。 将数据发送到流缓冲区的代码: - xMessageBufferSend()2 h3 K1 {' C# F1 n
- {; O* c# t+ I, r
- /* If a time out is specified and there isn't enough
% A7 R D, R8 l- q2 Q( i* A" E - space in the message buffer to send the data, then" F: r+ `9 R5 O5 {
- enter the blocked state to wait for more space. */
9 S' N* l' M0 c/ b3 b7 H - if( time out != 0 )- D# |4 B, v' ^- S
- {
, Z) p7 Z+ m/ u1 x6 l - while( there is insufficient space in the buffer &&( j, V- S* k) S0 w# k' s
- not timed out waiting ), V4 V& ~2 U$ E3 g
- {- }" q4 N3 ]! Y$ `$ q7 H
- Enter the blocked state to wait for space in the buffer( u" A- `9 y" B; C! K* v
- }
9 o/ @$ e+ @% J J. s3 i - }' {3 j6 G3 b2 ^6 y8 C
- : x( o1 M0 @1 M* J4 a
复制代码- if( there is enough space in the buffer )
; H1 R' Z( S H2 [2 ?& y5 P; W2 x: A - {/ s' _# {. g/ L* `# g4 Y5 ~3 h
- write data to buffer. N; ` ^/ h( w1 E& D7 f
- sbSEND_COMPLETED()" r' u! l4 H% M( i- N" O# a
- }0 W* G1 [2 }1 o- | l' Z7 C8 a% w Y) l
- }
复制代码 7 C# m# y' `. Y, J% k8 l( [
从流缓冲区读取数据的代码: - xMessageBufferReceive()2 F+ `* O1 t% h* E# v* I
- {& f* N3 [$ ]7 q
- /* If a time out is specified and the buffer doesn't2 C8 S @# g2 w4 _
- contain any data that can be read, then enter the
9 @! c: N( \- S/ Y: P2 [ - blocked state to wait for the buffer to contain data. */
8 f$ r; s$ ]) W4 n8 i' V- V - if( time out != 0 )4 O1 Q i( E4 S
- { q8 }3 n& r0 ?0 B0 ~# b$ y/ _
- while( there is no data in the buffer &&' @1 } V/ r* F% C# G: p
- not timed out waiting )
1 P: o/ P: U9 r8 G - {9 B% j ^ @$ }
- Enter the blocked state to wait for data
v1 W% m9 B( d( c) L6 S+ f4 y- b9 k - }6 Y' J) v" A0 v" j. z
- } P; {* K" C8 E3 j: q. J3 H: m" Y3 H) V
5 |' r& E1 y* F, p) w7 H" B. o
复制代码- if( there is data in the buffer )
3 a2 j* J3 f0 K# q - {4 n( d! j4 v- H
- read data from buffer5 A6 M& e' K/ q, ]8 Y
- sbRECEIVE_COMPLETED()
# X6 C1 ~& p0 G: v8 w - }
9 S% R6 c' ]% R) ?2 q - }
复制代码
& A/ e$ P ~" W( ~2 J如果任务在xMessageBufferReceive()中进入阻塞状态以等待缓冲区包含数据,则将数据发送到缓冲区必须取消阻塞该任务,以便它可以完成其操作。 当xMessageBufferSend()调用sbSEND_COMPLETED()时,任务将不受阻碍。 
; Z& q. |9 P1 a0 uISR通过将消息缓冲区的句柄作为参数传递给xMessageBufferSendCompletedFromISR()函数来解除对任务的阻塞。
4 M/ G5 K* R" V% i如图箭头所示,其中发送和接收任务位于不同的MCU内核上: 1.接收任务尝试从空的消息缓冲区中读取数据,并进入阻止状态以等待数据到达。 2.发送任务将数据写入消息缓冲区。 3.sbSEND_COMPLETED()在正在执行接收任务的内核中触发一个中断。 4.中断服务例程调用xMessageBufferSendCompletedFromISR()来解除阻止接收任务,该任务现在可以从缓冲区读取,因为缓冲区不再为空。 4 Y6 D) }! I2 b5 O' |
四、多消息代码描述 当只有一个消息缓冲区时,很容易将消息缓冲区的句柄传递到xMessageBufferSendCompletedFromISR()中。
3 a2 Q& h1 h( g, l但是要考虑有两个或更多消息缓冲区的情况,ISR必须首先确定哪个消息缓冲区包含数据。如果消息缓冲区的数量很少,则有几种方法可以实现: 如果硬件允许,则每个消息缓冲区可以使用不同的中断线,从而使中断服务程序和消息缓冲区之间保持一对一的映射。 中断服务例程可以简单地查询每个消息缓冲区以查看其是否包含数据。 可以通过传递元数据(消息是什么,消息的预期接收者是什么等等)以及实际数据的单个消息缓冲区来代替多个消息缓冲区。
- y; r. h' b- G4 d& C& l
0 @6 E. E6 H* M' v: g% X7 d5 G7 H % G1 V8 `& G/ [ ?1 y$ N4 S% o
但是,如果存在大量或未知的消息缓冲区,则这些技术效率不高。
0 O; r! f) [7 Y5 N在这种情况下,可伸缩的解决方案是引入单独的控制消息缓冲区。如下面的代码所示,sbSEND_COMPLETED()使用控制消息缓冲区将包含数据的消息缓冲区的句柄传递到中断服务例程中。 & X, M9 ?2 a0 t O4 S
使用sbSEND_COMPLETED()的实现: - /* Added to FreeRTOSConfig.h to override the default implementation. */- \) W# \% K$ ~" c9 F6 j0 t: H: z
- #define sbSEND_COMPLETED( pxStreamBuffer ) vGenerateCoreToCoreInterrupt( pxStreamBuffer )' Z8 V/ S0 i0 }+ }: h+ R6 c
复制代码- /* Implemented in a C file. */
( K/ j4 L Y9 c4 A" c! e - void vGenerateCoreToCoreInterrupt( MessageBufferHandle_t xUpdatedBuffer )
D; d, K+ c) Z; c. K - {5 T3 }% R3 j: W# ^8 ]
- size_t BytesWritten.
- q! ~0 S2 x# Z
复制代码- /* Called by the implementation of sbSEND_COMPLETED() in FreeRTOSConfig.h.
; ^ H6 z. m2 j4 @ - If this function was called because data was written to any message buffer4 Y0 y" y) s/ w6 }3 E
- other than the control message buffer then write the handle of the message4 X5 G" A1 H; a& ^
- buffer that contains data to the control message buffer, then raise an e0 U7 z& ~! F1 L" r6 L, H. O5 i
- interrupt in the other core. If this function was called because data was* o' }( m0 x x$ [% T0 t) P" s
- written to the control message buffer then do nothing. */) U3 O7 M. U! f. U( p5 u2 J0 R
- if( xUpdatedBuffer != xControlMessageBuffer )
) a3 P6 p3 f# ?2 q - {% w6 }3 y9 @: e, J4 z
- BytesWritten = xMessageBufferSend( xControlMessageBuffer, ?! t# ]$ q! ~8 y Z
- &xUpdatedBuffer,; \$ T' h# g" s* J! O
- sizeof( xUpdatedBuffer ),
9 q7 h% \9 p$ s7 L) \ - 0 );
8 @, @8 G r( p- @
复制代码- /* If the bytes could not be written then the control message buffer
4 z/ Q1 i$ m- q( Y, `6 F" i+ P - is too small! */" l+ x5 h( I" G- i3 v6 t5 q
- configASSERT( BytesWritten == sizeof( xUpdatedBuffer );
0 }: g2 e5 J/ a! o1 n
复制代码- /* Generate interrupt in the other core (pseudocode). */
! ?2 P5 v( d1 o$ _* w7 p - GenerateInterrupt();
9 Y: }$ M7 Q& z' l' v/ E9 M - }
5 A. h" r. T: B. r; W - }
复制代码然后,ISR读取控制消息缓冲区以获得句柄,将句柄作为参数传递到xMessageBufferSendCompletedFromISR()中: - void InterruptServiceRoutine( void )- c: c3 q8 Y6 b: w# q
- {
) ~( Q' E6 x; {4 v* s5 Z E+ | - MessageBufferHandle_t xUpdatedMessageBuffer;
8 n6 a4 i2 a" p1 i - BaseType_t xHigherPriorityTaskWoken = pdFALSE; h' D7 H9 B+ \$ J& L# m
复制代码- /* Receive the handle of the message buffer that contains data from the, A& O1 a1 `6 ^
- control message buffer. Ensure to drain the buffer before returning. */1 P" o3 W8 ^- H1 K# \
- while( xMessageBufferReceiveFromISR( xControlMessageBuffer,2 S" [0 e) c) n9 { f
- &xUpdatedMessageBuffer,+ u1 h) z" Q; E
- sizeof( xUpdatedMessageBuffer ),
! @9 V5 A8 G' ^, r3 m - &xHigherPriorityTaskWoken )
6 l$ ~+ ]% r: W( I+ U |3 n - == sizeof( xUpdatedMessageBuffer ) )( S5 ]# y% L1 o- X9 S
- {5 e4 E* f" L( n- c, W# Z# o# H
- /* Call the API function that sends a notification to any task that is
" z, X0 c; N1 K9 @ - blocked on the xUpdatedMessageBuffer message buffer waiting for data to
9 F$ k, A. U) f% X - arrive. */
( z+ ]2 r8 r( T0 F - xMessageBufferSendCompletedFromISR( xUpdatedMessageBuffer,* D+ C) z7 C* R; |! C; k) F' |
- &xHigherPriorityTaskWoken );/ r" f" V) F8 o( Q8 ?, y$ i) |
- }
7 S4 t4 |) B; T( i: d2 y) b: b# M
复制代码- /* Normal FreeRTOS "yield from interrupt" semantics, where
: t1 S5 b1 O" `* C8 } \% o4 B5 I - xHigherPriorityTaskWoken is initialised to pdFALSE and will then get set to
4 S) q2 O8 ~' K, L5 c" Y% i- P - pdTRUE if the interrupt unblocks a task that has a priority above that of
" q) m9 Z4 t- o- N! F& d; P - the currently executing task. */ l d8 I8 w+ d/ H. W9 p% v
- portYIELD_FROM_ISR( xHigherPriorityTaskWoken ); @" Q3 H" t m" c
- }
复制代码
& S. t! s. A& n% g" J' f- l5 G: L/ x: w+ \' {

如图,使用控制消息缓冲区时的顺序: 1.接收任务尝试从空的消息缓冲区中读取数据,并进入阻止状态以等待数据到达。 2.发送任务将数据写入消息缓冲区。 3.sbSEND_COMPLETED()将现在包含数据的消息缓冲区的句柄发送到控制消息缓冲区。 4.sbSEND_COMPLETED()在正在执行接收任务的内核中触发一个中断。 5.中断服务例程从控制消息缓冲区中读取包含数据的消息缓冲区的句柄,然后将该句柄传递给xMessageBufferSendCompletedFromISR()API函数以取消阻止接收任务,该任务现在可以从缓冲区读取,因为缓冲区不再存在空的。
' b6 X- L" |# X6 H. C当然,以上仅提供基础原理和方法,具体实现需结合项目实际情况。更多相关内容,请参看官方信息。 6 a m9 ] c7 f G- r/ N4 |, J
, v/ e3 ]6 E) ~ |
好好学习