上篇介紹了如何移植 RT-Thread Nano 內(nèi)核與 Finsh 控制臺到 RT1170。本篇繼續(xù)介紹如何將 NXP 官方的 VGLite API 移植到 RT-Thread Nano 上。
一RT-Thread配置
rtconfig.h 可對 RT-Thread 配置,因 VGLite 會使用互斥量、消息隊列等,故取消以下注釋:
#define RT_USING_MUTEX #define RT_USING_MESSAGEQUEUE #define RT_USING_HEAP
郵箱機制在本工程并不使用,可以注釋掉:
// #define RT_USING_MAILBOX默認最大名稱長度為 8,可以更改為 16 以容納更長名稱:
#define RT_NAME_MAX 16原 FreeRTOS 工程 tick 頻率為 200,RT-Thread Nano 默認 tick 頻率為 1000,可與原工程保持一致:
#define RT_TICK_PER_SECOND 200
二FreeRTOS與RT-Thread對比
首先分析 FreeRTOS 與 RT-Thread 的一些區(qū)別,以加深讀者理解,幫助后續(xù)用 RT-Thread API 改寫 FreeRTOS API 。 1. 任務與線程
FreeRTOS 稱線程為 “任務”(task), 而 RT-Thread 直接稱為 “線程”(Thread),這一術(shù)語尚未達成共識,兩者只是同一事物的不同表述。
2. 任務(線程)優(yōu)先級
FreeRTOS 中,優(yōu)先級范圍為 0 到 configMAX_PRIORITIES - 1,該宏在 FreeRTOSConfig.h 中定義,數(shù)字越低則該任務優(yōu)先級越低。
RT-Thread 中,優(yōu)先級范圍為 0 到 RT_THREAD_PRIORITY_MAX - 1,該宏在 rtconfig.h 中定義,數(shù)字越低線程優(yōu)先級卻越高,這點與 FreeRTOS 相反。
3. 任務(線程)調(diào)度 FreeRTOS 中,需手動調(diào)用 vTaskStartScheduler()開啟任務調(diào)度器。任務一旦創(chuàng)建便直接參與調(diào)度運行。時間片輪轉(zhuǎn)調(diào)度時,各相同優(yōu)先級線程的單次運行時間片統(tǒng)一為 1,即 1 個 tick 便調(diào)度一次。
RT-Thread 中,系統(tǒng)初始化時就已調(diào)用了 rt_system_scheduler_start(),無需再手動開啟。但創(chuàng)建后的線程尚位于初始狀態(tài),初始狀態(tài)的線程均需調(diào)用 rt_thread_startup()才會參與調(diào)度運行。各線程的單次運行時間片在創(chuàng)建時可指定為不同 tick。
4. 中斷適用函數(shù)
FreeRTOS 中,涉及上下文切換的函數(shù)存在兩個版本:一種是在任務中的常規(guī)版本;另一種則用于中斷內(nèi)調(diào)用,通常以 FromISR()結(jié)尾。若中斷調(diào)用的 API 喚醒了更高優(yōu)先級的線程,需手動調(diào)用 portYIELD_FROM_ISR(xHigherPriorityTaskWoken)以在中斷退出時喚醒高優(yōu)先級線程。
RT-Thread 中,線程與中斷可使用同一 API。但中斷內(nèi)不能使用掛起當前線程的操作,若使用則會打印 "Function[xxx_func] shall not used in ISR" 的提示信息。若中斷內(nèi)的函數(shù)喚醒了更高優(yōu)先級的線程,則中斷退出時會自動切換到高優(yōu)先級線程,無需手動切換。
5. 線程本地數(shù)據(jù)
FreeRTOS 中,線程的本地數(shù)據(jù)為一個數(shù)組,長度由 FreeRTOSConfig.h 中的 configNUM_THREAD_LOCAL_STORAGE_POINTERS 宏設置。
RT-Thread 中,線程的本地數(shù)據(jù)為一個 uint32 格式的 user_data 變量,而非數(shù)組。若要在線程本地存儲數(shù)組、結(jié)構(gòu)體等數(shù)據(jù),可手動創(chuàng)建后將地址存入該變量。
6. 信號量
FreeRTOS 中,分為二值信號量與計數(shù)信號量。二值信號量最大值為 1 ,初值為 0 。計數(shù)信號量的最大值和初值均可在創(chuàng)建時分別指定。
RT-Thread 中,未區(qū)分二值或計數(shù)信號量,且僅能指定信號量初值,最大值無法指定,統(tǒng)一為 65535 。此外,信號量在創(chuàng)建時可選先入先出模式(RT_IPC_FLAG_FIFO)或優(yōu)先級模式(RT_IPC_FLAG_PRIO),通常選用優(yōu)先級模式以保證線程實時性。
7. 互斥量
FreeRTOS 中,除創(chuàng)建的 API 以外,互斥量的結(jié)構(gòu)體與持有、釋放與刪除所使用的 API ,與信號量的相同。
RT-Thread 中,互斥量擁有一套獨立的 API ,而非與信號量共用 API 。
8. 頭文件
FreeRTOS 中,當使用信號量、互斥量、隊列等,除 FreeRTOS.h 外,需額外包含其他對應的頭文件。
RT-Thread 中,通常僅需包含 rtthread.h 即可使用信號量、互斥量、隊列等。
三VGLite 代碼改寫
首先,可將上篇工程中排除編譯的組與文件恢復回去,并恢復之前備份的 /source/clock_rtthread.c 代碼。
1. 頭文件更改
工程在以下文件中會使用到 RT-Thread ,需包含頭文件:#include "rtthread.h":
/source/clock_rtthread.c
/vglite/VGLite/rtos/vg_lite_os.c(較多改動)
/vglite/VGLiteKernel/rtos/vg_lite_hal.c
/vglite/font/vft_draw.c
/elementary/src/elm_os.c
/elementary/src/velm.h
/video/fsl_fbdev.h
/video/fsl_dc_fb_lcdifv2.c
/video/fsl_video_common.c
/utilities/fsl_debug_console.c
2. 線程 API 改寫
FreeRTOS 中的 xTaskCreate()可由 RT-Thread 中的 rt_thread_create()替換。注意兩者優(yōu)先級數(shù)字代表的高低相反,需轉(zhuǎn)換。rt_thread_create()中可指定線程單次運行的時間片,若 RT-Thread 已設置 tick 頻率與原 FreeRTOS 相等,則時間片可全部設置為1。vTaskDelete(NULL)為刪除當前線程,RT-Thread 可用 rt_thread_delete(rt_thread_self())代替。TaskHandle_t 結(jié)構(gòu)體由 rt_thread_t 代替。
而原有的 vTaskStartScheduler()應刪除,改用 rt_thread_startup()啟動指定線程。此外,命名中的 "task" 可替換為"thread" 以符合 RT-Thread 規(guī)范。
/source/clock_rtthread.c的線程相關(guān)代碼對比主要如下:(篇幅所限,僅以有代表性的文件與函數(shù)為例,其他未列出的文件和函數(shù)可根據(jù)例子參考,對編譯錯誤信息位置改寫,下同)
xTaskCreate(vglite_task, "vglite_task", configMINIMAL_STACK_SIZE + 200, NULL, configMAX_PRIORITIES - 1, NULL) ^^^^^^ rt_thread_t vglite_thread_handle = rt_thread_create("vglite_thread", vglite_thread, RT_NULL, 1024, 0, 1); vTaskStartScheduler(); ^^^^^^ if (vglite_thread_handle != RT_NULL) rt_thread_startup(vglite_thread_handle);
/vglite/VGLite/rtos/vg_lite_os.c 更改的線程相關(guān)代碼主要如下:
#define QUEUE_TASK_PRIO (configMAX_PRIORITIES - 1) ^^^^^^^ #define QUEUE_THREAD_PRIO 0 vTaskDelete(NULL); ^^^^^^^ rt_thread_delete(rt_thread_self()); ret = xTaskCreate(command_queue, QUEUE_TASK_NAME, QUEUE_TASK_SIZE, NULL, QUEUE_TASK_PRIO, &os_obj.task_hanlde); ^^^^^^^ os_obj.task_hanlde = rt_thread_create(QUEUE_THREAD_NAME, command_queue, NULL, QUEUE_THREAD_SIZE, QUEUE_THREAD_PRIO, 1); if (os_obj.task_hanlde != RT_NULL) rt_thread_startup(os_obj.task_hanlde);
3. 信號量 API 改寫
原xSemaphoreCreateCounting()與 xSemaphoreCreateBinary()均可使用 rt_sem_create()代替,rt_sem_create()需設置名稱,無需設置最大值,初值通常為 0,排隊方式一般采用 RT_IPC_FLAG_PRIO ,下同。SemaphoreHandle_t 結(jié)構(gòu)體換為 rt_sem_t 。
xSemaphoreTake()可替換為 rt_sem_take(),F(xiàn)reeRTOS 中 portMAX_DELAY 代表無限等待,可換為 RT-Thread 的 RT_WAITING_FOREVER 。若原 FreeRTOS 中指定過期 tick 形如 timeout / portTICK_PERIOD_MS,應使用(rt_int32)((rt_int64)timeout * RT_TICK_PER_SECOND / 1000)替換。
判斷返回值由 pdTRUE 替換為 RT_EOK 。xSemaphoreGive()換為 rt_sem_release()。vSemaphoreDelete()使用 rt_sem_delete()替換。有關(guān)信號量的中斷內(nèi)函數(shù) xSemaphoreGiveFromISR()在下文再詳細講解。
以 /vglite/VGLite/rtos/vg_lite_os.c 為例,信號量代碼對比主要如下:
command_semaphore = xSemaphoreCreateCounting(30,0); ^^^^^^^ command_semaphore = rt_sem_create("cs", 0, RT_IPC_FLAG_PRIO); int_queue = xSemaphoreCreateBinary(); ^^^^^^^ int_queue = rt_sem_create("iq", 0, RT_IPC_FLAG_PRIO); if (xSemaphoreTake(int_queue, timeout / portTICK_PERIOD_MS) == pdTRUE) ^^^^^^^ if (rt_sem_take(int_queue, (rt_int32_t) ((rt_int64_t)timeout * RT_TICK_PER_SECOND / 1000)) == RT_EOK)
4. 互斥量 API 改寫
xSemaphoreCreateMutex()替換為 rt_mutex_create(),需指定名稱與排隊方式。SemaphoreHandle_t 結(jié)構(gòu)體替換為 rt_mutex_t 。
其他互斥量 API 的改寫與信號量基本一致。xSemaphoreTake()、xSemaphoreGive()、vSemaphoreDelete()替換為 rt_mutex_take()、rt_mutex_release()、rt_mutex_delete()。
/vglite/VGLite/rtos/vg_lite_os.c中,互斥量代碼對比主要如下:
mutex = xSemaphoreCreateMutex(); ^^^^^^^ mutex = rt_mutex_create("mut", RT_IPC_FLAG_PRIO); if(xSemaphoreTake(mutex, TASK_WAIT_TIME/portTICK_PERIOD_MS) == pdTRUE) ^^^^^^^ if(rt_mutex_take(mutex, (rt_int32_t) ((rt_int64_t)MAX_MUTEX_TIME * RT_TICK_PER_SECOND / 1000)) != RT_EOK)5. 消息隊列 API 改寫
xQueueCreate()替換為 rt_mq_create(),需指定名稱與排隊方式。QueueHandle_t 結(jié)構(gòu)體替換為 rt_mq_t 。
RT-Thread 無類似 uxQueueMessagesWaiting()的函數(shù)用于確認隊列是否不為空,但 rt_mq_t 結(jié)構(gòu)體中的 entry 變量表示隊列的消息數(shù),故可用 if (xxx->entry) 代替。
xQueueReceive(), xQueueSend()更換為 rt_mq_recv() ,rt_mq_send_wait(),需額外指定發(fā)送與接收消息的大小,同時也需注意過期 tick 的轉(zhuǎn)換。
/vglite/VGLite/rtos/vg_lite_os.c中,消息隊列代碼對比主要如下:
os_obj.queue_handle = xQueueCreate(QUEUE_LENGTH, sizeof(vg_lite_queue_t * )); ^^^^^^^ os_obj.queue_handle = rt_mq_create("queue_vglite", sizeof(vg_lite_queue_t * ), QUEUE_LENGTH, RT_IPC_FLAG_PRIO); if(uxQueueMessagesWaiting(os_obj.queue_handle)) ^^^^^^^ if(os_obj.queue_handle->entry) ret = xQueueReceive(os_obj.queue_handle, (void*) &peek_queue, TASK_WAIT_TIME/portTICK_PERIOD_MS); ^^^^^^^ ret = rt_mq_recv(os_obj.queue_handle, (void*) &peek_queue, os_obj.queue_handle->msg_size, (rt_int32_t) ((rt_int64_t)TASK_WAIT_TIME * RT_TICK_PER_SECOND / 1000)); if(xQueueSend(os_obj.queue_handle, (void *) &queue_node, ISR_WAIT_TIME/portTICK_PERIOD_MS) != pdTRUE) ^^^^^^^ if(rt_mq_send_wait(os_obj.queue_handle, (void *) &queue_node, os_obj.queue_handle->msg_size, (rt_int32_t) ((rt_int64_t)ISR_WAIT_TIME * RT_TICK_PER_SECOND / 1000)) != RT_EOK)
6. 中斷內(nèi) API 改寫
FreeRTOS 中斷內(nèi)采用 xSemaphoreGiveFromISR()信號量釋放函數(shù)保證以中斷安全,且根據(jù) xHigherPriorityTaskWoken 變量,需使用 portYIELD_FROM_ISR()手動切換上下文;而 RT-Thread 仍使用通用的 rt_sem_release(),且可自動切換上下文。
/vglite/VGLite/rtos/vg_lite_os.c中,中斷內(nèi)代碼對比主要如下:
portBASE_TYPExHigherPriorityTaskWoken=pdFALSE; xSemaphoreGiveFromISR(int_queue,&xHigherPriorityTaskWoken); if(xHigherPriorityTaskWoken!=pdFALSE) portYIELD_FROM_ISR(xHigherPriorityTaskWoken); ^^^^^^^ rt_sem_release(int_queue);7. 內(nèi)存管理 API 改寫
pvPortMalloc()代替為 rt_malloc(),vPortFree()代替為 rt_free()即可。
8. 臨界區(qū)資源 API 改寫
portENTER_CRITICAL()與 portEXIT_CRITICAL()需替換為 rt_enter_critical()與 rt_exit_critical()。
9. 時間相關(guān) API 改寫
vTaskDelay()可替換為 rt_thread_delay()。若用于延時指定毫秒,也可直接使用 rt_thread_mdelay()代替,無需再計算毫秒對應的 tick。xTaskGetTickCount()用于得到 tick 的計數(shù)值,可用 rt_tick_get()代替。
/source/clock_rtthread.c的時間相關(guān)代碼對比如下:
return(uint32_t)(xTaskGetTickCount()*portTICK_PERIOD_MS); ^^^^^^^ return(rt_tick_t)((rt_uint64_t)rt_tick_get()*1000/RT_TICK_PER_SECOND);/vglite/VGLite/rtos/vg_lite_os.c中,延時代碼對比如下,無需再計算毫秒對應的 tick:
vTaskDelay((configTICK_RATE_HZ*msec+999)/1000); ^^^^^^^ rt_thread_mdelay(msec);10. 線程本地數(shù)據(jù) API 改寫
FreeRTOS 中各線程的本地數(shù)據(jù)為一個數(shù)組,而 RT-Thread 中線程本地數(shù)據(jù)僅有一個名為 user_data 的變量。
原 VGLite 僅使用了 FreeRTOS 本地數(shù)組中第一個元素,若用 RT-Thread 僅改用 VGLite API,則可直接用 user_data 變量保存原數(shù)組中第一個元素。但若也使用了 Elementary (即本工程),Elementary 也需要存放一個線程本地變量,此時本地數(shù)據(jù)便需要存放兩個變量。這是使用 RT-Thread Nano 需要先開辟一個數(shù)組空間以存放兩個變量,再將數(shù)組地址存放于本地的 user_data 變量中。
在工程 /vglite/VGLite/rtos/vg_lite_os.c 中新建數(shù)組 tls_array 并添加 vg_lite_os_init_tls_array()、 vg_lite_os_deinit_tls_array()函數(shù),再在對應的 .h 文件中聲明。vg_lite_os_init_tls_array()將數(shù)組地址存入當前線程的 user_data 變量;vg_lite_os_deinit_tls_array()則用于刪除當前線程 user_data 中的數(shù)組地址。
#define TLS_ARRAY_LENGTH 2 rt_uint32_t tls_array[TLS_ARRAY_LENGTH] = {NULL}; int32_t vg_lite_os_init_tls_array(void) { rt_thread_t rt_TCB = rt_thread_self(); RT_ASSERT( rt_TCB != NULL ); rt_TCB->user_data=(rt_uint32_t)tls_array; return VG_LITE_SUCCESS; } void vg_lite_os_deinit_tls_array(void) { rt_thread_t rt_TCB = rt_thread_self(); RT_ASSERT( rt_TCB != NULL ); rt_TCB->user_data = NULL; }再對 /vglite/VGLite/rtos/vg_lite_os.c 中 vg_lite_os_get_tls()、vg_lite_os_set_tls()、vg_lite_os_reset_tls()修改。原例程調(diào)用了 FreeRTOS 函數(shù)獲取與修改線程本地數(shù)據(jù),而新代碼需手動實現(xiàn),以獲取當前線程本地數(shù)據(jù)并讀寫數(shù)組第一位元素:
void * vg_lite_os_get_tls() { rt_thread_t rt_TCB = rt_thread_self(); rt_uint32_t * tls_ptr = (rt_uint32_t *) rt_TCB->user_data; void * pvReturn = (void *) (*tls_ptr); return pvReturn; } int32_t vg_lite_os_set_tls(void* tls) { rt_thread_t rt_TCB; rt_TCB = rt_thread_self(); RT_ASSERT( rt_TCB != NULL ); rt_uint32_t * tls_ptr = (rt_uint32_t *) rt_TCB->user_data; *tls_ptr = (rt_uint32_t) tls; } void vg_lite_os_reset_tls() { rt_thread_t rt_TCB = rt_thread_self(); RT_ASSERT( rt_TCB != NULL ); rt_uint32_t * tls_ptr = (rt_uint32_t *) rt_TCB->user_data; *tls_ptr = NULL; }
隨后更改工程 /vglite/VGLite/vg_lite.c 中 vg_lite_init(),在調(diào)用 vg_lite_os_get_tls()、vg_lite_os_malloc()、vg_lite_os_set_tls()等函數(shù)之前,添加上文定義的 vg_lite_os_init_tls_array()進行線程本地數(shù)據(jù)初始化。同樣,該文件有 vg_lite_close(),在其調(diào)用 vg_lite_os_reset_tls()等函數(shù)的最后,也需添加上文定義的 vg_lite_os_deinit_tls_array()。
11. Elementary API改寫
使用 Elementary 時,同樣也需修改工程/elementary/src/elm_os.c 中的 elm_os_get_tls()、 elm_os_set_tls()、elm_os_reset_tls(),以讀寫線程本地數(shù)據(jù)中數(shù)組的第二個元素,與上文 VGLite 的三個線程本地數(shù)據(jù) API 改寫方法基本一致,主要區(qū)別為將使用*(tls_ptr + 1)而非*tls_ptr 。 12. 數(shù)據(jù)類型改寫
FreeRTOS 定義了 TickType_t 與 BaseType_t 類型,在 RT1170 中可分別用 rt_uint32_t 與 rt_err_t 代替。同時,可用 RT_NULL 代替 NULL 判斷 RT-Thread 對象是否為空。
13.輸出 API 改寫
若在上篇移植了 Finsh 控制臺組件,則可用 rt_kprintf()代替 PRINTF 宏。rt_kprintf()已自動在字符串末尾添加" " ,無需再手動添加。
四結(jié)果驗證
編譯并運行,若與上篇的原工程結(jié)果相同,即屏幕出現(xiàn)指針不斷旋轉(zhuǎn)的時鐘,且串口打印幀數(shù)信息。恭喜, VGLite 與 Elementary 已成功移植到 RT-Thread Nano 上啦!
五總結(jié)
VGLite 移植到 RT-Thread Nano 的過程還是有些繁瑣的,需要更改的 FreeRTOS API 較多,但兩個 RTOS 的大部分特性相似度高,難度不大。經(jīng)過兩篇文章的學習,相信您對于 FreeRTOS、RT-Thread 以及 VGLite 的細節(jié)有了更深入的了解。
此外,若追求移植效率,并不關(guān)心 RTOS 細節(jié),也可以使用 RT-Thread 的FreeRTOS-Wrapper兼容層替換 FreeRTOS,讀者可以自行進行研究。
審核編輯:湯梓紅
-
移植
+關(guān)注
關(guān)注
1文章
379瀏覽量
28124 -
RTOS
+關(guān)注
關(guān)注
22文章
811瀏覽量
119592 -
開發(fā)板
+關(guān)注
關(guān)注
25文章
5032瀏覽量
97371 -
FreeRTOS
+關(guān)注
關(guān)注
12文章
484瀏覽量
62138 -
RT-Thread
+關(guān)注
關(guān)注
31文章
1285瀏覽量
40079
原文標題:泄密了! i.MX RT1170:VGLite移植RT-Thread Nano全過程圖解,這也太詳細了吧?。ㄏ拢?/p>
文章出處:【微信號:NXP_SMART_HARDWARE,微信公眾號:恩智浦MCU加油站】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
相關(guān)推薦
評論