基于飞凌嵌入式i.MX9352嵌入式开发板M核的FreeRTOS设计例程
在嵌入式系统领域,嵌入式实时操作系统(RTOS) 的应用正日益广泛,采用RTOS能够更合理、更高效地利用CPU资源,FreeRTOS作为一款轻量级且成熟的实时操作系统内核,其核心功能完备,包括任务管理、时间管理(如延时、定时器)、同步机制(信号量、互斥锁)、进程间通信(消息队列)等等。这些特性使其能够很好地满足资源相对有限的中小型嵌入式系统的需求。
i.MX 9352作为NXP 推出的新一代轻量级边缘AI处理器,集成2个Cortex-A55核和1个Cortex-M33实时核,其架构设计充分体现了对实时性与复杂任务处理能力的兼顾。为了帮助开发者充分利用i.MX 9352 M33核的实时能力,其配套的M核SDK包提供的FreeRTOS例程分为两类,一类介绍FreeRTOS系统组件特性,如信号量、互斥量、队列等,另一类是介绍外设接口如何在FreeRTOS使用,我们分别挑选这两类下的例程进行演示。
演示平台:飞凌嵌入式OK-MX9352-C开发板
1、FreeRTOS-generic
飞凌嵌入式OK-MX9352-C开发板支持FreeRTOS功能特性示例代码如下:
-
freertos_event:任务事件演示例程
-
freertos_queue:队列消息实现任务间通信的演示例程
-
freertos_mutex:互斥锁使用例程
-
freertos_sem:信号量使用例程
-
freertos_swtimer:软件计数器及其回调的用法。
-
freertos_tickless:使用 LPTMR 延时唤醒或者硬件中断唤醒例程
-
freertos_generic:task、queue、swtimer、tick hook 、semaphore 组合利用演示例程。
因FreeRTOS_generic例程使用的FreeRTOS特性较多,我们重点分析此例程。
(1)软件实现
示例程序内容包括:任务创建、队列、软定时器、系统节拍时钟、信号量、异常处理。具体如下:
任务创建:
主函数创建了队列发送、接收,信号量三个任务。
// 创建队列接收任务
if(xTaskCreate(prvQueueReceiveTask,"Rx",configMINIMAL_STACK_SIZE+166,NULL,mainQUEUE_RECEIVE_TASK_PRIORITY,NULL)!=pdPASS)// 创建队列发送任务 if(xTaskCreate(prvQueueSendTask,"TX",configMINIMAL_STACK_SIZE+166, NULL, mainQUEUE_SEND_TASK_PRIORITY, NULL) !=pdPASS)// 创建信号量任务 if(xTaskCreate(prvEventSemaphoreTask,"Sem",configMINIMAL_STACK_SIZE+166,NULL,mainEVENT_SEMAPHORE_TASK_PRIORITY, NULL) != pdPASS)
队列:
队列发送任务,阻塞200ms后向队列发送数据;队列接收任务,任务阻塞读取队列,数据读取正确,则打印此时的队列接收数量。
// 队列发送任务,阻塞200ms后 向队列发送数据
static void prvQueueSendTask(void *pvParameters)
{
TickType_t xNextWakeTime;
const uint32_t ulValueToSend = 100UL;
xNextWakeTime = xTaskGetTickCount();
for (;;)
{
// 任务阻塞,直至200ms延时结束
vTaskDelayUntil(&xNextWakeTime, mainQUEUE_SEND_PERIOD_MS);
// 向队列发送数据,阻塞时间为0表示当队列满的时候就立即返回
xQueueSend(xQueue, &ulValueToSend, 0);
}
}
// 队列接收任务,任务阻塞读取队列,数据读取正确,则打印此时的队列接收数量。
static void prvQueueReceiveTask(void *pvParameters)
{
uint32_t ulReceivedValue;
for (;;)
{
// 任务一直阻塞,知道队列内读取到数据
xQueueReceive(xQueue, &ulReceivedValue, portMAX_DELAY);
// 队列数据和发送一致,队列接收数量+1 输出此时的队列接收数量
if (ulReceivedValue == 100UL)
{
ulCountOfItemsReceivedOnQueue++;
PRINTF("Receive message counter: %d.\r\n", ulCountOfItemsReceivedOnQueue); } } }
软定时器:
设置软定时器周期1s,时间到后,调用回调函数,记录次数并串口打印。
// 创建软件定时器任务 时间为1s,周期循环
xExampleSoftwareTimer = xTimerCreate(
"LEDTimer",
mainSOFTWARE_TIMER_PERIOD_MS,
pdTRUE,
(void *)0,
vExampleTimerCallback);
// 启动软件定时器
xTimerStart(xExampleSoftwareTimer, 0);
// 回调函数
static void vExampleTimerCallback(TimerHandle_t xTimer)
{
// 每1s进入一次回调函数,计数增加
ulCountOfTimerCallbackExecutions++;
PRINTF("Soft timer: %d s.\r\n", ulCountOfTimerCallbackExecutions);
}
系统节拍时钟:
通过设置文件 FreeRTOSConfig.h 中 configTICK_RATE_HZ 设置任务节拍中断频率, 在启动任务调度器时,系统会根据另一个变量CPU的频率configCPU_CLOCK_HZ计算对应写入节拍计数器的值,启动定时器中断。
// 设置系统时钟节拍为 1000/200=5ms
#define configTICK_RATE_HZ ((TickType_t)200)
信号量:
每个系统节拍时钟中断中,调用函数vApplicationTickHook,累积500次即500*5ms=2.5s后,发送信号量。信号量任务获取信号后,计数并打印累积次数。
// 系统节拍为5ms,每个500*5ms=2.5s 释放事件信号量
void vApplicationTickHook(void)
{
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
static uint32_t ulCount = 0;
ulCount++;
if (ulCount >= 500UL)
{
// 在中断中释放事件信号量
xSemaphoreGiveFromISR(xEventSemaphore, &xHigherPriorityTaskWoken);
ulCount = 0UL;
}
}
// 任务阻塞等待信号量,收到后,接收次数增加,并通过串口打印
static void prvEventSemaphoreTask(void *pvParameters)
{
for (;;)
{
// 任务阻塞,直到能获取信号量
if (xSemaphoreTake(xEventSemaphore, portMAX_DELAY) != pdTRUE)
{
PRINTF("Failed to take semaphore.\r\n");
}
// 接收到信号量的次数累加
ulCountOfReceivedSemaphores++;
PRINTF("Event task is running. Get semaphore :%d \r\n",ulCountOfReceivedSemaphores);
}
}
异常处理:
当内存分配失败、堆栈发生错误或任务空闲时,进入相应的函数,用户可添加相应的处理函数。
// 内存分配失败函数,当内存分配失败时,进入此函数
void vApplicationMallocFailedHook(void)
{
for (;;)
;
}
// 堆栈错误检查函数,当堆栈发生溢出时,进入此函数
void vApplicationStackOverflowHook(TaskHandle_t xTask, char *pcTaskName)
{
(void)pcTaskName;
(void)xTask;
for (;;)
;
}
// 空闲任务,优先级最低,没有实际意义,只是让CPU有事情做,用户可以自己添加自己的函数
void vApplicationIdleHook(void)
{
volatile size_t xFreeStackSpace;
xFreeStackSpace = xPortGetFreeHeapSize();
if (xFreeStackSpace > 100)
{
}
}
(2)实验现象
① 编译程序:在uboot手动加载M核程序。
② 队列:每隔200ms,队列发送任务发送数据,队列接收任务获取数据,从阻塞态到运行态,打印计数。
③ 软定时器:每隔1s,时间到达,调用回调函数,打印计数。
④ 信号量:每隔5ms,系统时钟节拍中断调用函数,超过500次后,释放信号量。信号量任务获的信号量,从阻塞态到运行态,打印计数。
2、FreeRTOS-外设
飞凌嵌入式OK-MX9352-C开发板支持外设使用FreeRTOS完成相应功能,示例代码如下:
-
freertos_uart:freertos串口演示例程
-
freertos_lpi2c_b2b:freertos I2C演示例程
-
freertos_lpspi_b2b:freertos SPI演示例程
因freertos_uart例程使用的FreeRTOS特性比较典型,我们重点分析此例程。
(1)软件实现
示例程序内容包括:串口初始化任务、串口发送任务、串口接收任务。具体如下:
串口初始化任务:
主要包含串口外设初始化,发送、接收互斥量,发送和接收事件组。串口外设初始化在裸跑串口例程中已展现,此处不再详述。
// 创建串口发送互斥量
handle->txSemaphore = xSemaphoreCreateMutex();
// 创建串口接收互斥量
handle->rxSemaphore = xSemaphoreCreateMutex();
// 创建发送事件标志组
handle->txEvent = xEventGroupCreate();
// 创建接收事件标志组
handle->rxEvent = xEventGroupCreate();
串口发送:
发送前获取信号量,启动发送流程,在中断中置位发送完成事件标志。发送任务获取到事件后,释放发送信号量。
// 1 获取发送信号量
if (pdFALSE == xSemaphoreTake(handle->txSemaphore, 0))
{
return kStatus_Fail;
}
handle->txTransfer.data = (uint8_t *)buffer;
handle->txTransfer.dataSize = (uint32_t)length;
// 2 阻塞式发送
status = UART_TransferSendNonBlocking(handle->base, handle->t_state, &handle->txTransfer);
if (status != kStatus_Success)
{
(void)xSemaphoreGive(handle->txSemaphore);
return kStatus_Fail;
}
// 3 等待发送完成的事件
ev = xEventGroupWaitBits(handle->txEvent, RTOS_UART_COMPLETE, pdTRUE, pdFALSE, portMAX_DELAY);// 等待并判断多个事件位
if ((ev & RTOS_UART_COMPLETE) == 0U)
{
retval = kStatus_Fail;
}
// 4 发送完成,释放发送信号量
if (pdFALSE == xSemaphoreGive(handle->txSemaphore)) // 释放信号量
{
retval = kStatus_Fail;
}
串口接收:
接收前获取信号量,调用串口接收函数,在中断中置位获取事件标志。接收任务获取到事件后,释放接收信号量。
// 1获取接收信号量
if (pdFALSE == xSemaphoreTake(handle->rxSemaphore, portMAX_DELAY))
{
return kStatus_Fail;
}
handle->rxTransfer.data = buffer;
handle->rxTransfer.dataSize = (uint32_t)length;
// 2 串口接收函数
status = UART_TransferReceiveNonBlocking(handle->base, handle->t_state, &handle->rxTransfer, &n);
if (status != kStatus_Success)
{
(void)xSemaphoreGive(handle->rxSemaphore);
return kStatus_Fail;
}
// 3 获取接收事件
ev = xEventGroupWaitBits(handle->rxEvent,RTOS_UART_COMPLETE | RTOS_UART_RING_BUFFER_OVERRUN | RTOS_UART_HARDWARE_BUFFER_OVERRUN, pdTRUE, pdFALSE, portMAX_DELAY); // 等待并判断接收完成事件位
// 3.1 硬件接收错误
if ((ev & RTOS_UART_HARDWARE_BUFFER_OVERRUN) != 0U)
{
UART_TransferAbortReceive(handle->base, handle->t_state);
(void)xEventGroupClearBits(handle->rxEvent, RTOS_UART_COMPLETE); // 将接收完成的事件位清零,
retval = kStatus_UART_RxHardwareOverrun;
local_received = 0;
}
// 3.2 接收缓冲区过载错误
else if ((ev & RTOS_UART_RING_BUFFER_OVERRUN) != 0U)
{
UART_TransferAbortReceive(handle->base, handle->t_state);
(void)xEventGroupClearBits(handle->rxEvent, RTOS_UART_COMPLETE); // 将接收完成的事件位清零,
retval = kStatus_UART_RxRingBufferOverrun;
local_received = 0;
}
// 3.3 接收完成
else if ((ev & RTOS_UART_COMPLETE) != 0U)
{
retval = kStatus_Success;
local_received = length;
}
else
{
retval = kStatus_UART_Error;
local_received = 0;
}
// 4 释放接收信号量
if (pdFALSE == xSemaphoreGive(handle->rxSemaphore))
{
retval = kStatus_Fail;
}
(2)实验现象
① 编译程序,在uboot手动加载M核程序。
② 装置上电后,串口打印程序信息,此时通过键盘输入4个字符,M核调试串口将回显,重复输入和回显字符,证明程序运行成功。
以上就是在飞凌嵌入式i.MX 9352开发板M核上软件设计FreeRTOS的例程演示,希望能够对各位工程师朋友有所帮助。