i.MX RT1170:VGLite移植RT-Thread Nano过程讲解(下)

上篇介绍了如何移植 rt-thread nano 内核与 finsh 控制台到 rt1170。本篇继续介绍如何将 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 的一些区别,以加深读者理解,帮助后续用 rt-thread api 改写 freertos api 。 1. 任务与线程
freertos 称线程为 “任务”(task), 而 rt-thread 直接称为 “线程”(thread),这一术语尚未达成共识,两者只是同一事物的不同表述。
2. 任务(线程)优先级
freertos 中,优先级范围为 0 到 configmax_priorities - 1,该宏在 freertosconfig.h 中定义,数字越低则该任务优先级越低。
rt-thread 中,优先级范围为 0 到 rt_thread_priority_max - 1,该宏在 rtconfig.h 中定义,数字越低线程优先级却越高,这点与 freertos 相反。
3. 任务(线程)调度 freertos 中,需手动调用 vtaskstartscheduler() 开启任务调度器。任务一旦创建便直接参与调度运行。时间片轮转调度时,各相同优先级线程的单次运行时间片统一为 1,即 1 个 tick 便调度一次。
rt-thread 中,系统初始化时就已调用了 rt_system_scheduler_start() ,无需再手动开启。但创建后的线程尚位于初始状态,初始状态的线程均需调用 rt_thread_startup() 才会参与调度运行。各线程的单次运行时间片在创建时可指定为不同 tick。
4. 中断适用函数
freertos 中,涉及上下文切换的函数存在两个版本:一种是在任务中的常规版本;另一种则用于中断内调用,通常以 fromisr() 结尾。若中断调用的 api 唤醒了更高优先级的线程,需手动调用 portyield_from_isr(xhigherprioritytaskwoken) 以在中断退出时唤醒高优先级线程。
rt-thread 中,线程与中断可使用同一 api。但中断内不能使用挂起当前线程的操作,若使用则会打印 function[xxx_func] shall not used in isr 的提示信息。若中断内的函数唤醒了更高优先级的线程,则中断退出时会自动切换到高优先级线程,无需手动切换。
5. 线程本地数据
freertos 中,线程的本地数据为一个数组,长度由 freertosconfig.h 中的 confignum_thread_local_storage_pointers 宏设置。
rt-thread 中,线程的本地数据为一个 uint32 格式的 user_data 变量,而非数组。若要在线程本地存储数组、结构体等数据,可手动创建后将地址存入该变量。
6. 信号量
freertos 中,分为二值信号量与计数信号量。二值信号量最大值为 1 ,初值为 0 。计数信号量的最大值和初值均可在创建时分别指定。
rt-thread 中,未区分二值或计数信号量,且仅能指定信号量初值,最大值无法指定,统一为 65535 。此外,信号量在创建时可选先入先出模式(rt_ipc_flag_fifo)或优先级模式(rt_ipc_flag_prio),通常选用优先级模式以保证线程实时性。
7. 互斥量
freertos 中,除创建的 api 以外,互斥量的结构体与持有、释放与删除所使用的 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() 替换。注意两者优先级数字代表的高低相反,需转换。rt_thread_create() 中可指定线程单次运行的时间片,若 rt-thread 已设置 tick 频率与原 freertos 相等,则时间片可全部设置为1。vtaskdelete(null) 为删除当前线程,rt-thread 可用 rt_thread_delete(rt_thread_self()) 代替。taskhandle_t 结构体由 rt_thread_t 代替。
而原有的 vtaskstartscheduler() 应删除,改用 rt_thread_startup() 启动指定线程。此外,命名中的 task 可替换为thread 以符合 rt-thread 规范。
/source/clock_rtthread.c的线程相关代码对比主要如下:(篇幅所限,仅以有代表性的文件与函数为例,其他未列出的文件和函数可根据例子参考,对编译错误信息位置改写,下同)
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 更改的线程相关代码主要如下:
#define queue_task_prio (configmax_priorities - 1)^^^^^^^#define queue_thread_prio 0vtaskdelete(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 结构体换为 rt_sem_t 。
xsemaphoretake() 可替换为 rt_sem_take(),freertos 中 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() 替换。有关信号量的中断内函数 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 结构体替换为 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 结构体替换为 rt_mq_t 。
rt-thread 无类似 uxqueuemessageswaiting() 的函数用于确认队列是否不为空,但 rt_mq_t 结构体中的 entry 变量表示队列的消息数,故可用 if (xxx->entry) 代替。
xqueuereceive(), xqueuesend() 更换为 rt_mq_recv() ,  rt_mq_send_wait() ,需额外指定发送与接收消息的大小,同时也需注意过期 tick 的转换。
/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. 中断内 api 改写
freertos 中断内采用 xsemaphoregivefromisr() 信号量释放函数保证以中断安全,且根据 xhigherprioritytaskwoken 变量,需使用 portyield_from_isr() 手动切换上下文;而 rt-thread 仍使用通用的 rt_sem_release() ,且可自动切换上下文。
/vglite/vglite/rtos/vg_lite_os.c中,中断内代码对比主要如下:
portbase_type xhigherprioritytaskwoken = pdfalse;xsemaphoregivefromisr(int_queue, &xhigherprioritytaskwoken);if(xhigherprioritytaskwoken != pdfalse ) portyield_from_isr(xhigherprioritytaskwoken);^^^^^^^rt_sem_release(int_queue);7. 内存管理 api 改写  
pvportmalloc() 代替为 rt_malloc() ,vportfree() 代替为 rt_free() 即可。
8. 临界区资源 api 改写
portenter_critical() 与 portexit_critical() 需替换为 rt_enter_critical() 与 rt_exit_critical() 。
9. 时间相关 api 改写
vtaskdelay() 可替换为 rt_thread_delay() 。若用于延时指定毫秒,也可直接使用 rt_thread_mdelay() 代替,无需再计算毫秒对应的 tick。xtaskgettickcount() 用于得到 tick 的计数值,可用 rt_tick_get() 代替。
/source/clock_rtthread.c的时间相关代码对比如下:
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. 线程本地数据 api 改写  
freertos 中各线程的本地数据为一个数组,而 rt-thread 中线程本地数据仅有一个名为 user_data 的变量。
原 vglite 仅使用了 freertos 本地数组中第一个元素,若用 rt-thread 仅改用 vglite api,则可直接用 user_data 变量保存原数组中第一个元素。但若也使用了 elementary (即本工程),elementary 也需要存放一个线程本地变量,此时本地数据便需要存放两个变量。这是使用 rt-thread nano 需要先开辟一个数组空间以存放两个变量,再将数组地址存放于本地的 user_data 变量中。
在工程 /vglite/vglite/rtos/vg_lite_os.c 中新建数组 tls_array 并添加 vg_lite_os_init_tls_array() 、 vg_lite_os_deinit_tls_array() 函数,再在对应的 .h 文件中声明。vg_lite_os_init_tls_array() 将数组地址存入当前线程的 user_data 变量;vg_lite_os_deinit_tls_array() 则用于删除当前线程 user_data 中的数组地址。
#define tls_array_length 2rt_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() 修改。原例程调用了 freertos 函数获取与修改线程本地数据,而新代码需手动实现,以获取当前线程本地数据并读写数组第一位元素: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() ,在调用 vg_lite_os_get_tls() 、vg_lite_os_malloc() 、vg_lite_os_set_tls() 等函数之前,添加上文定义的 vg_lite_os_init_tls_array() 进行线程本地数据初始化。同样,该文件有 vg_lite_close() ,在其调用 vg_lite_os_reset_tls() 等函数的最后,也需添加上文定义的 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() ,以读写线程本地数据中数组的第二个元素,与上文 vglite 的三个线程本地数据 api 改写方法基本一致,主要区别为将使用 *(tls_ptr + 1) 而非 *tls_ptr 。 12. 数据类型改写
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() 已自动在字符串末尾添加 ,无需再手动添加。
四结果验证
编译并运行,若与上篇的原工程结果相同,即屏幕出现指针不断旋转的时钟,且串口打印帧数信息。恭喜, vglite 与 elementary 已成功移植到 rt-thread nano 上啦!
五总结
vglite 移植到 rt-thread nano 的过程还是有些繁琐的,需要更改的 freertos api 较多,但两个 rtos 的大部分特性相似度高,难度不大。经过两篇文章的学习,相信您对于 freertos、rt-thread 以及 vglite 的细节有了更深入的了解。
此外,若追求移植效率,并不关心 rtos 细节,也可以使用 rt-thread 的 freertos-wrapper 兼容层替换 freertos,读者可以自行进行研究。


新能源汽车高压线束设计指南
2020对于人工智能有什么机会
这9种工作不会被人工智能取代 目前非常难以实现自动化
全球首个AI宇宙三维模拟器,可在几毫秒内完成模拟
华为Mate X2再爆新消息
i.MX RT1170:VGLite移植RT-Thread Nano过程讲解(下)
KNX总线和RS485总线的区别是什么
针对手机应用的DCA系列天线
iOS10.3 Beta4发布:耗电、流畅性如何?
传“Switch Pro"可能被称为SuperSwitch
专为纯电动汽车平台设计的800V碳化硅高压逆变器
ADX3208驱动基于RT1052的方法
小米将占据印度智能手机市场,代替三星“一哥”地位
光时域反射仪(OTDR)的基础知识
TI MSP430F22xx系列的主要特性及无线RFID开发方案
如何有效的监管数字货币
温度传感器的分类,它的种类都有哪些
一种新型OLED能够让光线进行偏振从而绕过这些防眩光滤光片
国内外磁悬浮智能输送系统盘点
e络盟进一步加大投入拓展Traco Power产品阵容