内核的许多方面都是可配置的,并在适用的情况下提及配置选项。
系统启动
由于 main 不再是一个线程,RTX5 在到达 main 之前不会干扰系统启动。一旦执行到达 main(),建议初始化硬件并启动内核。这也反映在随 RTX5 组件提供的用户代码模板文件 “CMSIS-RTOS2” 主 “功能” 中。
你的应用程序的 main()应该按给定的顺序至少实现以下内容:
- 硬件的初始化和配置,包括外设,存储器,引脚,时钟和中断系统。
- 使用相应的 CMSIS-Core(Cortex-M)或 CMSIS-Core(Cortex-A)功能更新系统内核时钟。
- 使用 osKernelInitialize 初始化 CMSIS-RTOS 内核。
- 或者,创建一个新线程 app_main,该线程将作为使用 osThreadNew 的主线程使用。或者,可以直接在 main()中创建线程。
- 使用 osKernelStart 启动 RTOS 调度程序。如果执行成功,该函数不会返回。除非 osKernelStart 失败,否则 osKernelStart 之后的任何应用程序代码都不会执行。
注意
-
内核使用的中断(如 SVC)在 osKernelInitialize 中初始化。如果在上述顺序之后 NVIC 中的优先级和分组被更改,则可能需要再次调用osKernelInitialize。您可能会观察到可能由 osRtxErrorNotify 捕获或导致硬故障的奇怪的不当行为。
-
滴答定时器在 osKernelStart 期间配置。滴答间隔是基于 SystemCoreClock 变量计算的。
调度
RTX5 实现了一个低延迟预先调度器。RTX5 的主要部分在处理器模式下执行,例如
为了在 ISR 执行方面具有低延迟,这些系统例外被配置为使用可用的最低优先级组。优先级被配置为使得它们之间不发生抢占。因此,不需要中断关键部分(即中断锁)来保护调度器。
线程调度和中断执行
调度程序结合了基于优先级和循环的上下文切换。上图中描述的示例包含四个线程(1, 2, 3 和 4)。 线程 1 和 2 共享相同的优先级,线程 3 具有较高的优先级,线程 4 最高(osThreadAttr_t :: priority)。只要线程 3 和 4 被阻塞,调度程序就会按时间片(循环)在线程 1 和线程 2 之间切换。可以配置循环调度的时间片,请参阅系统配置中的循环超时。
线程 2 在时间索引 2 时通过任意 RTOS 调用(在 SVC 处理程序模式下执行)解除对线程 3 的阻塞。由于线程 3 具有高优先级,因此调度程序立即切换到线程 3。线程 4 仍然被阻塞。
在索引 4 时间发生中断(ISR)并抢占 SysTick_Handler 。 RTX 不会为中断服务执行添加任何延迟。ISR 例程使用解除线程 4 的RTOS调用,而不是立即切换到线程 4,PendSV 标志被设置为推迟上下文切换。PendSV_Handler 在 SysTick_Handler 返回之后立即执行,并且将被拒绝的上下文切换到线程 4。一旦最高优先级线程 4 通过使用阻塞 RTOS 调用执行再次阻塞,在时间索引 5 期间立即切换回线程 3 。
在索引 5 时,线程 3 也使用阻塞 RTOS 调用。因此,调度程序切换回线程 2 以获得时间索引 6 。在索引 7 时,调度程序使用循环机制切换到线程1,依此类推。
内存分配
RTX5 对象(线程,互斥锁,信号量,定时器,消息队列,线程和事件标志以及内存池)需要专用的 RAM 存储器。可以使用 osObjectNew()调用创建对象,并使用 osObjectDelete()调用删除对象。相关的对象内存需要在对象的生命周期中可用。
RTX5 为对象提供了三种不同的内存分配方法:
- 全局内存池为所有对象使用单个全局内存池。它很容易配置,但是当创建并销毁具有不同大小的对象时,可能存在内存碎片的缺点。
- 特定于对象的内存池为每个对象类型使用固定大小的内存池。该方法是时间确定性的,避免了内存碎片。
- 静态对象内存在编译期间保留内存并完全避免系统可能内存不足。这对于一些安全关键系统通常是必需的。
有可能在同一个应用程序中混合所有内存分配方法。
全局内存池
全局内存池分配内存区域中的所有对象。这种内存分配方法是 RTX5 的默认配置设置。
全局内存池用于所有对象
当内存池没有提供足够的内存时,对象的创建失败,并且相关的 osObjectNew()函数返回 NULL 。
在系统配置中启用。
特定于对象的内存池
特定于对象的内存池可避免内存碎片,并为每种对象类型提供专用固定大小的内存管理。这种类型的内存池是完全时间确定性的,这意味着对象创建和销毁总是需要相同的固定时间。由于固定大小的内存池是特定于对象类型的,因此可以简化对内存不足情况的处理。
每个对象类型一个内存池
特定于对象的内存池可针对每种对象类型启用,例如:使用 RTX 配置文件的互斥锁或线程:
- 在线程对象的线程配置中启用。
- 在定时器对象的定时器配置中启用。
- 在事件标志配置中为事件对象启用。
- 在互斥对象的互斥体配置中启用。
- 在信号量的信号量配置中启用。
- 在内存池的内存池配置中启用。
- 在消息队列的消息队列配置中启用。
当内存池没有提供足够的内存时,对象的创建失败,并且相关的 osObjectNew()函数返回 NULL 。
静态对象内存
与动态内存分配相比,静态内存分配需要编译时分配对象内存。
为所有对象静态分配内存
静态内存分配可以通过在对象创建时使用属性提供用户定义的内存来实现,请参见手动用户定义的分配。请特别注意以下限制:
为了允许 RTX5 知道调试,即组件查看器识别控制块,这些块需要放置在单独的存储器部分中,即使用__attribute __((section(...)))。
RTX 对象 | 链接器部分 |
---|
线程 | .bss.os.thread.cb |
定时器 | .bss.os.timer.cb |
事件标志 | .bss.os.evflags.cb |
互斥锁 | .bss.os.mutex.cb |
信号量 | .bss.os.semaphore.cb |
内存池 | .bss.os.mempool.cb |
消息队列 | .bss.os.msgqueue.cb |
以下代码示例显示如何使用静态内存创建 OS 对象。
代码示例:
#include "RTE_Components.h"
#include CMSIS_device_header
void worker(
void *arg)
{
while(1)
{
}
}
__attribute__((section(
".bss.os.thread.cb")))
uint64_t worker_thread_stk_1[64];
"wrk1",
&worker_thread_tcb_1,
sizeof(worker_thread_tcb_1),
&worker_thread_stk_1[0],
sizeof(worker_thread_stk_1),
0
};
void app_main (
void *argument) {
uint32_t param = NULL;
for (;;) {}
}
int main (
void) {
SystemCoreClockUpdate();
for (;;) {}
}
线程堆栈管理
对于没有浮点单元的 Cortex-M 处理器,线程上下文在本地堆栈上需要 64 个字节。
注意
-
对于带有FPU 的 Cortex-M4 / M7,线程上下文在本地堆栈上需要 200 个字节。 对于这些设备,默认堆栈空间应该增加到最小 300 字节。
每个线程都有一个单独的堆栈,为自动变量和函数调用嵌套返回地址保存线程上下文和堆栈空间。RTX 线程的堆栈大小可以灵活配置,如线程配置部分所述。RTX 提供了一个可配置的检查堆栈溢出和堆栈利用率。
低功耗操作
系统线程 osRtxIdleThread 可用于将系统切换至低功耗模式。进入低功耗模式的最简单形式是执行 __WFE 功能,使处理器进入休眠模式,等待事件。
代码示例:
#include "RTE_Components.h"
#include CMSIS_device_header
for (;;) {
__WFE();
}
}
注意
-
__WFE()不是在每个 Cortex-M 实现中都是可用的。检查设备手册的可用性。
RTX 内核定时器节拍
RTX 使用通用 OS Tick API 来配置和控制其周期性内核刻度。
要使用替代定时器作为内核刻度定时器,只需实现 OS Tick API 的自定义版本即可。
注意
-
所提供的 OS Tick 实现必须确保所使用的定时器中断使用与服务中断相同(低)的优先级组,即 RTX 使用的中断不能相互抢占。 有关更多详细信息,请参阅计划程序部分。
无滴答低功耗操作
RTX5 为无滴答操作提供了扩展,对于使用 SysTick 定时器也被禁用的广泛低功耗模式的应用非常有用。为了在这种省电模式下提供时间标记,使用唤醒定时器来导出定时器间隔。CMSIS-RTOS2 函数 osKernelSuspend 和 osKernelResume 控制无滴答的操作。
使用这个函数允许 RTX5 线程调度器停止周期性内核刻度中断。当所有活动线程挂起时,系统进入掉电模式,并计算在这个掉电模式下可以停留多长时间。在省电模式下,处理器和外设可以关闭。只有唤醒定时器必须保持上电状态,因为该定时器负责在断电周期到期后唤醒系统。
无滴答操作由 osRtxIdleThread 线程控制。唤醒超时值在系统进入掉电模式之前设置。函数 osKernelSuspend 计算在 RTX Timer Ticks 中测量的唤醒超时; 该值用于设置在系统掉电模式下运行的唤醒定时器。
一旦系统恢复运行(通过唤醒超时或其他中断),RTX5 线程调度程序将以函数 osKernelResume 启动。参数 sleep_time 指定系统处于掉电模式的时间(在 RTX Timer Ticks 中)。
代码示例:
#include "msp.h"
static
void MSP432_LP_Entry(
void) {
PCM->CTL1 = PCM_CTL1_KEY_VAL | PCM_CTL1_FORCE_LPM_ENTRY;
SYSCTL->SRAM_BANKRET |= SYSCTL_SRAM_BANKRET_BNK7_RET;
__enable_interrupt();
NVIC_EnableIRQ(RTC_C_IRQn);
SCB->SCR |= SCB_SCR_SLEEPONEXIT_Msk;
SCB->SCR |= (SCB_SCR_SLEEPDEEP_Msk);
}
static
volatile
unsigned
int tc;
static
volatile
unsigned
int tc_wakeup;
void RTC_C_IRQHandler(
void)
{
if (tc++ > tc_wakeup)
{
SCB->SCR &= ~SCB_SCR_SLEEPONEXIT_Msk;
NVIC_DisableIRQ(RTC_C_IRQn);
NVIC_ClearPendingIRQ(RTC_C_IRQn);
return;
}
if (RTC_C->PS0CTL & RTC_C_PS0CTL_RT0PSIFG)
{
RTC_C->CTL0 = RTC_C_KEY_VAL;
RTC_C->PS0CTL &= ~RTC_C_PS0CTL_RT0PSIFG;
RTC_C->CTL0 = 0;
SCB->SCR |= (SCB_SCR_SLEEPDEEP_Msk);
}
}
uint32_t g_enable_sleep = 0;
for (;;) {
if (tc_wakeup > 0) {
tc = 0;
MSP432_LP_Entry();
__WFE();
}
}
}
注意
-
__WFE()不是在每个 ARM Cortex-M 实现中都是可用的。检查设备手册的可用性。使用 __WFI()的替代方案还有其他问题,请注意http://www.keil.com/support/docs/3591.htm 。
RTX5 头文件
CMSIS-RTOS2 API 的每个实现都可以带来自己的附加功能。RTX5 增加了一些空闲功能,用于错误通知和特殊的系统定时器功能。 它也使用宏来控制块和内存大小。
如果您的应用程序代码中需要一些 RTX 特定功能,请包括头文件 rtx_os.h:
#ifndef RTX_OS_H_
#define RTX_OS_H_
#include <stdint.h>
#include <stddef.h>
#ifdef __cplusplus
extern
"C"
{
#endif
#define osRtxVersionAPI 20010002
#define osRtxVersionKernel 50030000
#define osRtxKernelId "RTX V5.3.0"
#define osRtxIdInvalid 0x00U
#define osRtxIdThread 0x01U
#define osRtxIdTimer 0x02U
#define osRtxIdEventFlags 0x03U
#define osRtxIdMutex 0x04U
#define osRtxIdSemaphore 0x05U
#define osRtxIdMemoryPool 0x06U
#define osRtxIdMessage 0x07U
#define osRtxIdMessageQueue 0x08U
#define osRtxObjectInactive 0x00U
#define osRtxObjectActive 0x01U
#define osRtxFlagSystemObject 0x01U
#define osRtxFlagSystemMemory 0x02U
#define osRtxKernelInactive ((uint8_t)osKernelInactive)
#define osRtxKernelReady ((uint8_t)osKernelReady)
#define osRtxKernelRunning ((uint8_t)osKernelRunning)
#define osRtxKernelLocked ((uint8_t)osKernelLocked)
#define osRtxKernelSuspended ((uint8_t)osKernelSuspended)
#define osRtxThreadStateMask 0x0FU
#define osRtxThreadInactive ((uint8_t)osThreadInactive)
#define osRtxThreadReady ((uint8_t)osThreadReady)
#define osRtxThreadRunning ((uint8_t)osThreadRunning)
#define osRtxThreadBlocked ((uint8_t)osThreadBlocked)
#define osRtxThreadTerminated ((uint8_t)osThreadTerminated)
#define osRtxThreadWaitingDelay ((uint8_t)(osRtxThreadBlocked | 0x10U))
#define osRtxThreadWaitingJoin ((uint8_t)(osRtxThreadBlocked | 0x20U))
#define osRtxThreadWaitingThreadFlags ((uint8_t)(osRtxThreadBlocked | 0x30U))
#define osRtxThreadWaitingEventFlags ((uint8_t)(osRtxThreadBlocked | 0x40U))
#define osRtxThreadWaitingMutex ((uint8_t)(osRtxThreadBlocked | 0x50U))
#define osRtxThreadWaitingSemaphore ((uint8_t)(osRtxThreadBlocked | 0x60U))
#define osRtxThreadWaitingMemoryPool ((uint8_t)(osRtxThreadBlocked | 0x70U))
#define osRtxThreadWaitingMessageGet ((uint8_t)(osRtxThreadBlocked | 0x80U))
#define osRtxThreadWaitingMessagePut ((uint8_t)(osRtxThreadBlocked | 0x90U))
#define osRtxThreadFlagDefStack 0x10U
#define osRtxStackMagicWord 0xE25A2EA5U
#define osRtxStackFillPattern 0xCCCCCCCCU
typedef
struct osRtxThread_s {
uint8_t id;
uint8_t state;
uint8_t flags;
uint8_t attr;
const
char *name;
struct osRtxThread_s *thread_next;
struct osRtxThread_s *thread_prev;
struct osRtxThread_s *delay_next;
struct osRtxThread_s *delay_prev;
struct osRtxThread_s *thread_join;
uint32_t delay;
int8_t priority;
int8_t priority_base;
uint8_t stack_frame;
uint8_t flags_options;
uint32_t wait_flags;
uint32_t thread_flags;
struct osRtxMutex_s *mutex_list;
void *stack_mem;
uint32_t stack_size;
uint32_t sp;
uint32_t thread_addr;
uint32_t tz_memory;
#define osRtxTimerInactive 0x00U
#define osRtxTimerStopped 0x01U
#define osRtxTimerRunning 0x02U
#define osRtxTimerPeriodic ((uint8_t)osTimerPeriodic)
typedef
struct {
void *arg;
typedef
struct osRtxTimer_s {
uint8_t id;
uint8_t state;
uint8_t flags;
uint8_t type;
const
char *name;
struct osRtxTimer_s *prev;
struct osRtxTimer_s *next;
uint32_t tick;
uint32_t load;
typedef
struct {
uint8_t id;
uint8_t state;
uint8_t flags;
uint8_t reserved;
const
char *name;
uint32_t event_flags;
typedef
struct osRtxMutex_s {
uint8_t id;
uint8_t state;
uint8_t flags;
uint8_t attr;
const
char *name;
struct osRtxMutex_s *owner_prev;
struct osRtxMutex_s *owner_next;
uint8_t lock;
uint8_t padding[3];
typedef
struct {
uint8_t id;
uint8_t state;
uint8_t flags;
uint8_t reserved;
const
char *name;
uint16_t tokens;
uint16_t max_tokens;
typedef
struct {
uint32_t max_blocks;
uint32_t used_blocks;
uint32_t block_size;
void *block_base;
void *block_lim;
void *block_free;
typedef
struct {
uint8_t id;
uint8_t state;
uint8_t flags;
uint8_t reserved;
const
char *name;
typedef
struct osRtxMessage_s {
uint8_t id;
uint8_t state;
uint8_t flags;
uint8_t priority;
struct osRtxMessage_s *prev;
struct osRtxMessage_s *next;
typedef
struct {
uint8_t id;
uint8_t state;
uint8_t flags;
uint8_t reserved;
const
char *name;
uint32_t msg_size;
uint32_t msg_count;
typedef
struct {
uint8_t id;
uint8_t state;
uint8_t flags;
uint8_t reserved;
const
char *name;
typedef
struct {
const
char *os_id;
uint32_t version;
struct {
uint8_t state;
volatile uint8_t blocked;
uint8_t pendSV;
uint8_t reserved;
uint32_t tick;
} kernel;
int32_t tick_irqn;
struct {
struct {
} run;
struct {
uint32_t tick;
uint32_t timeout;
} robin;
} thread;
struct {
void (*tick)(void);
} timer;
struct {
uint16_t max;
uint16_t cnt;
uint16_t in;
uint16_t out;
void **data;
} isr_queue;
struct {
} post_process;
struct {
void *stack;
void *mp_data;
void *mq_data;
void *common;
} mem;
struct {
} mpi;
typedef
struct {
uint32_t cnt_alloc;
uint32_t cnt_free;
uint32_t max_used;
#define osRtxThreadFlagsLimit 31U
#define osRtxEventFlagsLimit 31U
#define osRtxMutexLockLimit 255U
#define osRtxSemaphoreTokenLimit 65535U
#define osRtxThreadCbSize sizeof(osRtxThread_t)
#define osRtxTimerCbSize sizeof(osRtxTimer_t)
#define osRtxEventFlagsCbSize sizeof(osRtxEventFlags_t)
#define osRtxMutexCbSize sizeof(osRtxMutex_t)
#define osRtxSemaphoreCbSize sizeof(osRtxSemaphore_t)
#define osRtxMemoryPoolCbSize sizeof(osRtxMemoryPool_t)
#define osRtxMessageQueueCbSize sizeof(osRtxMessageQueue_t)
#define osRtxMemoryPoolMemSize(block_count, block_size) \
(4*(block_count)*(((block_size)+3)/4))
#define osRtxMessageQueueMemSize(msg_count, msg_size) \
(4*(msg_count)*(3+(((msg_size)+3)/4)))
#define osRtxErrorStackUnderflow 1U
#define osRtxErrorISRQueueOverflow 2U
#define osRtxErrorTimerQueueOverflow 3U
#define osRtxErrorClibSpace 4U
#define osRtxErrorClibMutex 5U
#define osRtxConfigPrivilegedMode (1UL<<0)
#define osRtxConfigStackCheck (1UL<<1)
#define osRtxConfigStackWatermark (1UL<<2)
typedef
struct {
uint32_t flags;
uint32_t tick_freq;
uint32_t robin_timeout;
struct {
void **data;
uint16_t max;
uint16_t padding;
} isr_queue;
struct {
void *stack_addr;
uint32_t stack_size;
void *mp_data_addr;
uint32_t mp_data_size;
void *mq_data_addr;
uint32_t mq_data_size;
void *common_addr;
uint32_t common_size;
} mem;
struct {
} mpi;
uint32_t thread_stack_size;
const
const
const
uint32_t timer_mq_mcnt;
#ifdef __cplusplus
}
#endif
#endif // RTX_OS_H_