0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

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

恩智浦MCU加油站 来源:恩智浦MCU加油站 2023-11-09 11:22 次阅读

上篇介绍了如何移植 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  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 结构体换为 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_TYPExHigherPriorityTaskWoken=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    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()修改。原例程调用了 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,读者可以自行进行研究。

审核编辑:汤梓红

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 移植
    +关注

    关注

    1

    文章

    367

    浏览量

    27854
  • RTOS
    +关注

    关注

    20

    文章

    776

    浏览量

    118795
  • 开发板
    +关注

    关注

    25

    文章

    4429

    浏览量

    93991
  • FreeRTOS
    +关注

    关注

    12

    文章

    473

    浏览量

    61347
  • RT-Thread
    +关注

    关注

    31

    文章

    1148

    浏览量

    38869

原文标题:泄密了! i.MX RT1170:VGLite移植RT-Thread Nano全过程图解,这也太详细了吧!(下)

文章出处:【微信号:NXP_SMART_HARDWARE,微信公众号:恩智浦MCU加油站】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    i.MX RT1170VGLite移植RT-Thread Nano过程讲解(上)

    RT-Thread 是国人自主研发的开源实时操作系统(RTOS),RT-Thread Nano 是极简版的硬实时内核,内存占用小,移植简单。VGLi
    的头像 发表于 11-09 11:20 1240次阅读
    <b class='flag-5'>i.MX</b> <b class='flag-5'>RT1170</b>:<b class='flag-5'>VGLite</b><b class='flag-5'>移植</b><b class='flag-5'>RT-Thread</b> <b class='flag-5'>Nano</b><b class='flag-5'>过程</b><b class='flag-5'>讲解</b>(上)

    如何基于CubeMX移植RT-Thread Nano

    档介绍了如何使用 CubeMX 移植 RT-Thread Nano,并以一个 stm32f103 的基础工程作为示例进行讲解
    发表于 03-29 06:56

    基于 CubeMX 移植 RT-Thread Nano

    档介绍了如何使用 CubeMX 移植 RT-Thread Nano,并以一个 stm32f103 的基础工程作为示例进行讲解移植
    发表于 05-14 16:09

    i.MX RT1170车规级产品有AEC-Q100认证吗?

    大家好如题,i.MX RT1170车规级产品有AEC-Q100认证吗?如果是,能否提供相关文件?
    发表于 03-15 08:24

    i.MX RT1170自定义引导加载程序,如何在i.MX RT处理器上完成?

    我将在我的自定义 i.MX RT1170 板上开发自定义引导加载程序。在我的例子中,闪存驱动程序将通过 USB 读取以更新主要应用程序。 我正在研究
    发表于 05-17 08:13

    野火电子基于RT-Threadi.MX RT1052 EVK Pro板卡特点介绍

    RT-Thread合作伙伴野火电子携RT-Thread Inside板卡i.MX RT1052 EVK Pro参展NXP技术日,现场展示了多个基于R
    的头像 发表于 07-16 09:44 8630次阅读

    恩智浦i.MX RT1170开创GHz MCU时代

    自2017年上市以来,i.MX RT系列取得了优秀的市场表现。i.MX RT1170跨界MCU是恩智浦i.MX
    的头像 发表于 03-22 11:14 3149次阅读

    恩智浦i.MX RT1170在将该系列带上了更高的层面

    自2017年上市以来,i.MX RT系列取得了优秀的市场表现。i.MX RT1170跨界MCU是恩智浦i.MX
    的头像 发表于 05-18 11:15 3443次阅读

    基于 Keil MDK 移植 RT-Thread Nano

    本文介绍如何基于 Keil MDK 移植 RT-Thread Nano ,并以一个 stm32f103 的基础工程作为示例进行讲解RT-Thre
    发表于 01-26 17:04 16次下载
    基于 Keil MDK <b class='flag-5'>移植</b> <b class='flag-5'>RT-Thread</b> <b class='flag-5'>Nano</b>

    如何创建RT-Thread Nano工程

    简单(比freeRTOS移植还简单)等,本文将讲解如何将RT-Thread Nano移植到GD32L233C。
    的头像 发表于 03-19 12:13 3287次阅读

    RT-Thread文档_野火 I.MX RT1052上手指南

    RT-Thread文档_野火 I.MX RT1052 上手指南
    发表于 02-22 18:25 2次下载
    <b class='flag-5'>RT-Thread</b>文档_野火 <b class='flag-5'>I.MX</b> <b class='flag-5'>RT</b>1052上手指南

    RT-Thread文档_正点原子 I.MX RT1052号令者上手指南

    RT-Thread文档_正点原子 I.MX RT1052 号令者上手指南
    发表于 02-22 18:26 3次下载
    <b class='flag-5'>RT-Thread</b>文档_正点原子 <b class='flag-5'>I.MX</b> <b class='flag-5'>RT</b>1052号令者上手指南

    RT-Thread文档_RT-Thread SMP 介绍与移植

    RT-Thread文档_RT-Thread SMP 介绍与移植
    发表于 02-22 18:31 7次下载
    <b class='flag-5'>RT-Thread</b>文档_<b class='flag-5'>RT-Thread</b> SMP 介绍与<b class='flag-5'>移植</b>

    i.MX RT1170评估套件快速入门:这份保姆级教程,请收藏!

    恩智浦官网精彩导览 i.MX RT1170评估套件快速入门 i.MX RT1170 是恩智浦推出的首款主频超过1GHz的跨界MCU,结合了强悍的计算能力、多种媒体功能、实时功能,以及一
    的头像 发表于 05-12 11:55 1614次阅读
    <b class='flag-5'>i.MX</b> <b class='flag-5'>RT1170</b>评估套件快速入门:这份保姆级教程,请收藏!

    基于NXP微控制器i.MX RT1170的多人体实时检测算法和系统

    基于NXP微控制器i.MX RT1170的多人体实时检测算法和系统
    的头像 发表于 10-26 16:27 701次阅读
    基于NXP微控制器<b class='flag-5'>i.MX</b> <b class='flag-5'>RT1170</b>的多人体实时检测算法和系统