实时操作系统
本节介绍如何在STM32CubeMX中配置不同的Real Time Operating System(实时操作系统),与TouchGFX Generator生成的代码配合使用,实现能正常运行的TouchGFX OSAL。
通常,通过调用函数MX_TouchGFX_Process()
启动TouchGFX主循环。 对该函数的调用应位于开发人员希望运行TouchGFX的任务处理程序内。
无OS
当TouchGFX在无操作系统的情况下运行时,TouchGFX主循环在main()
中的无限while循环中运行,MCU通过调用MX_TouchGFX_Process()
来等待VSYNC
信号。
main.c
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
MX_TouchGFX_Process();
/* USER CODE BEGIN 3 */
}
MX_TouchGFX_Process()
调用TouchGFX_taskEntry()
,当检测到VSYNC
时,TouchGFX开始处理下一帧。
TouchGFXConfiguration.cpp
void touchgfx_taskEntry()
{
/*
* Main event loop will check for VSYNC signal, and then process next frame.
*
* 注意,如果无VSYNC信号,该函数将立即返回。
*/
if (OSWrappers::isVSyncAvailable())
{
hal.backPorchExited();
}
}
FreeRTOS (CMSIS V1 & CMSIS V2)
以下示例显示了FreeRTOS (CMSIS V2)的配置方法,以便与TouchGFX配合使用。 如运行CMSIS V1,适用同样配置。
Having enabled FreeRTOS in a project, navigate to the FREERTOS menu found under Middleware in the "Categories" list on the left side of the project menu in STM32CubeMX. 将TICK_RATE_HZ
设置为1000,以获取每毫秒的计时 配置Memory Management(内存管理)方案
和TOTAL_HEAP_SIZE
。 本例中,我们使用heap_4和70000字节的堆大小来为touchgfx应用程序提供足够大的堆。
The required heap size depends on the application, as all software modules can allocate memory in the FreeRTOS heap.
A standard TouchGFX application generated with CubeMX allocates the following objects in the FreeRTOS heap:
Object | Size |
---|---|
Frame buffer semaphore | 80 bytes |
VSync message queue | 84 bytes |
TouchGFX task control block | 96 bytes |
TouchGFX task stack | 16,384 bytes |
Default task control block | 96 bytes |
Default task stack | 512 bytes |
Total | 17,252 bytes |
This can be reduced by lowering the stack size (see below).
The Default task is not used by TouchGFX, but is created by default by CubeMX. It can be deleted.
If your application is using the LIBJPEG middleware or software based MJPEG video with TouchGFX you need more heap space. LIBJPEG is allocating many objects in the heap during decompression. The LIBJPEG memory usage depends on the video or images decompressed (mostly resolution). For one of the videos available in the TouchGFX Designer of resolution 480*272, the memory used by LIBJPEG is 42,600 bytes.
Adding this number to the heap requirements gives:
Object | Size |
---|---|
Standard objects | 17,252 bytes |
LIBJPEG objects | 42,600 bytes |
Total | 59,852 bytes |
FreeRTOS aligns the heap array on 8-bytes in both ends (loosing a few bytes) and allocates a linked list of free blocks in the heap. So it is necessary to round up the calculated number a bit, for example to 60*1024 bytes.
Main loop
调用以下函数时,将进入TouchGFX主循环。
void MX_TouchGFX_Process(void);
开发人员可以在STM32CubeMX中配置自定义任务来运行TouchGFX主循环,也可以使用TouchGFX Generator生成的TouchGFX_task()
句柄。
自定义任务
The function void touchgfx_taskEntry(void)
is called by MX_TouchGFX_Process()
.
void MX_TouchGFX_Process(void)
{
// Calling forward to touchgfx_taskEntry in C++ domain
touchgfx_taskEntry();
}
开发人员需要在运行TouchGFX主循环的任务中调用MX_TouchGFX_Process()
。 If the developer has configured a FreeRTOS task in STM32CubeMX called MyTask
for instance, then the following example shows how MX_TouchGFX_Process()
should be called in the custom task handler to start TouchGFX.
void StartMyTask(void *argument)
{
/* USER CODE BEGIN 5 */
MX_TouchGFX_Process();
/* USER CODE END 5 */
}
TouchGFX任务
TouchGFX主循环可在TouchGFX任务中运行。 TouchGFX Generator 定义了一个任务处理程序,在app_touchgfx.c
中调用名为TouchGFX_Task()
的函数,在此函数中调用touchgfx_taskEntry()
。
app_touchgfx.c
void TouchGFX_Task(void *argument)
{
// Calling forward to touchgfx_taskEntry in C++ domain
touchgfx_taskEntry();
}
要让FreeRTOS调度程序调度该任务,必须在STM32CubeMX的FREERTOS菜单中创建该任务。 在“Tasks and Queues(任务和队列)”选项卡中,添加一个任务,将“TouchGFX_Task”作为入口函数,并将一个足够大的堆栈和Code Generation Option(代码生成选项)作为外部函数。
Here we have configured a stack size of 4,096 words (equal to 16,384 bytes). The required stack size depends on the application (mostly on the number of widgets updated at the same time). A stack size of 2048 words is enough for most applications, and many can go even further down in stack size.
Tip
STM32CubeMX将在生成代码时在main.c
中创建TouchGFX任务,从而启动TouchGFX主循环。
为确保SYS Timebase Source始终接收到用于某些系统函数(包括HAL_Delay()
)的计时,指定计时器的NVIC优先级必须高于(即具有较低的数值)最高中断优先级,该优先级可用于调用中断安全FreeRTOS API函数。 在FreeRTOS中可配置为configMAX_SYSCALL_INTERRUPT_PRIORITY
,默认设置为5
。 大多数情况下,用于SYS时基源的计时器为TIM6
。
ThreadX
本节展示了如何使用ThreadX作为X-CUBE软件包或本机ThreadX中间件来配置TouchGFX项目。
本机中间件配置
以下示例显示如何为STM32U5 MCU配置ThreadX,该MCU将ThreadX用作本机中间件。
在项目中启用线程本机中间件后,导航至STM32CubeMX项目菜单左侧“Categories(类别)”列表中间件下的THREADX菜单。 从Mode(模式)
列表启用Core。 将TX_TIMER_TICKS_PER_SECOND
设置为1000以获得每毫秒一次的计时,并将“Memory Pool Allocation(内存池分配)”
设置为“Use Static Allocation(使用静态分配)”。
Caution
- 当Memory Pool Allocation(内存池分配)设置为使用Use Dynamic Allocation(动态分配)时:
- 用户必须在生成的app_azure_rtos.c文件的USER CODE BEGIN DYNAMIC_MEM_ALLOC部分中添加缺失的代码。
- 用户还需要根据生成的app_azure_rtos.c文件中代码注释所描述的建议更新链接器文件。
- 根据STM32CubeMX版本,Dynamic Allocation(动态分配)可能无法正常工作。
在使用ThreadX时,STM32CubeMX不负责生成分配和调度任务的代码。 为此,STM32CubeMX生成对函数MX_TouchGFX_PreOSInit()
的调用,该函数将对TouchGFX 框架进行初始化。 之后,生成对函数MX_ThreadX_Init()
的另一个调用,初始化并启动ThreadX内核。
main.c
int main(void)
{
...
/* Call PreOsInit function */
MX_TouchGFX_PreOSInit();
...
MX_ThreadX_Init();
...
}
TouchGFX Generator将生成MX_TouchGFX_Init()
函数,该函数将创建TouchGFX线程。
app_touchgfx.c
UINT MX_TouchGFX_Init(VOID *memory_ptr)
{
UINT ret = TX_SUCCESS;
CHAR *pointer = 0;
/* Allocate the stack for TouchGFX Thread. */
if (tx_byte_allocate((TX_BYTE_POOL*)memory_ptr, (VOID **) &pointer,
TOUCHGFX_STACK_SIZE, TX_NO_WAIT) != TX_SUCCESS)
{
ret = TX_POOL_ERROR;
}
/* Create TouchGFX Thread */
else if (tx_thread_create(&TouchGFXThread, (CHAR *)"TouchGFX", TouchGFX_Task, 0,
pointer, TOUCHGFX_STACK_SIZE,
5, 5,
TX_NO_TIME_SLICE, TX_AUTO_START) != TX_SUCCESS)
{
ret = TX_THREAD_ERROR;
}
return ret;
}
然后,TouchGFX Generator将生成创建TouchGFX任务字节池的代码,并调用MX_TouchGFX_Init()
。 此代码插入app_azure_rtos.c
文件中函数tx_application_define()的适当位置,对TouchGFX任务字节池进行定义。
tx_application_define()
函数在初始化时由ThreadX内核调用。 TouchGFX线程将在稍后启动ThreadX内核时启动。
app_azure_rtos.c
#include "app_touchgfx.h"
...
__ALIGN_BEGIN static UCHAR touchgfx_byte_pool_buffer[TOUCHGFX_APP_MEM_POOL_SIZE] __ALIGN_END;
static TX_BYTE_POOL touchgfx_app_byte_pool;
...
VOID tx_application_define(VOID *first_unused_memory)
{
...
if (tx_byte_pool_create(&touchgfx_app_byte_pool, "TouchGFX App memory pool", touchgfx_byte_pool_buffer, TOUCHGFX_APP_MEM_POOL_SIZE) != TX_SUCCESS)
{
/* USER CODE BEGIN TouchGFX_Byte_Pool_Error */
/* USER CODE END TouchGFX_Byte_Pool_Error */
}
else
{
/* USER CODE BEGIN TouchGFX_Byte_Pool_Success */
/* USER CODE END TouchGFX_Byte_Pool_Success */
memory_ptr = (VOID *)&touchgfx_app_byte_pool;
if (MX_TouchGFX_Init(memory_ptr) != TX_SUCCESS)
{
/* USER CODE BEGIN MX_X-CUBE-TOUCHGFX_Init_Error */
/* USER CODE END MX_X-CUBE-TOUCHGFX_Init_Error */
}
/* USER CODE BEGIN MX_X-CUBE-TOUCHGFX_Init_Success */
/* USER CODE END MX_X-CUBE-TOUCHGFX_Init_Success */
}
...
}
X-CUBE-AZRTOS配置
以下示例显示如何为STM32H7 MCU配置ThreadX,该MCU的ThreadX作为X-CUBE软件包提供。
在项目中将ThreadX作为X-CUBE软件包启用后,导航至STM32CubeMX中项目菜单左侧“Categories(类别)”列表软件包下的X-CUBE-AZRTOS-XX菜单,并将Memory Allocation(内存分配)
设置为使用Use Static MemPool Allocation(静态内存池分配)。
选择ThreadX设置选项卡,并将TX_TIMER_TICKS_PER_SECOND
设置为1000,以获得每毫秒一次的计时。
在X-CUBEAZRTOS-XX菜单中完成配置后,导航至TouchGFX Generator,选择ThreadX作为Real Time Operating System(实时操作系统),并定义Memory Pool Size(内存池大小
)和Memory Stack Size(内存堆栈大小)