本文由RT-Thread论坛用户@出出啊原创发布:https://club.rt-thread.org/ask/article/2460fcd7db4821ae.html
前言
接触 rt-thread 已有半年,混论坛也5个半月了,期间遇到过各种奇奇怪怪的棘手问题,有过尴尬,也自信曾经提供过比较妙的应对方案。所以产生了将一些典型的使用技巧汇总分享出来的想法,遂有此篇。
入门篇
Q1. 刚下载的 SDK 啥也没干,编译没错,为啥程序跑不起来?
如果使用 keil + env 环境,下载源码后的第一件事就是 menuconfig
;
如果使用 RT-Studio ,创建项目后的第一件事就是打开 Settings ;
把其中所有配置页面所有配置项全浏览一遍,取消掉所有不相干的配置,最后只留一个内核。
先保证最小系统跑起来,用点灯程序验证最小系统运行正常。然后再添加自己需要用到的功能和底层外设等等。
Q2. 刚下载的 SDK 啥也没干,编译没错,为啥程序跑起来 hard fault on thread?
同上
Q3. 刚下载的 SDK 啥也没干,编译为啥报错了?
同上
内核篇
Q1. RT_NAME_MAX 定义多少合适
原则上越少越省内存,以内核对象 100 个为例,一个对象名占用 8 字节,总共是 800 字节。但是考虑到 struct rt_object
结构体定义,后面跟了两个 rt_uint8_t 型变量。
RT_NAME_MAX 可以定义成 2n + 2
Q2. RT_DEBUG
如非必要,不要开启内核调试。除非,你真的想学习内核,或者调试内核的问题。
Q3. 线程栈大小定义多少合适?
这个问题和应用有很大关系,如果仅仅是一个最小内核系统,除了 idle 线程,没有使用其它中断和应用,256 也将将够。如果添加了应用代码,还有中断和消息机制。建议 1024 起步。
Q4. 怎么快速计算 GET_PIN 返回的编号?
我们知道,芯片的 GPIO 分组往往是从 PA 开始,往后依次是 PB PC PD PE … PZ。往往的,每组端口或者是 16bit 或者是 8bit (分别对应 16 个 IO 和 8 个 IO)。下面给出 GET_PIN
的简化公式:
16bit 是 (X - A) * 16 + n
A10 就是 10.
C9 就是 2*16+9=41.
H1 就是 7*16+1=113.
8bit 是 (X - A) * 8 + n
这个公式别忘啊,别忘了!
PS: 有种,他们的引脚号编码很奇特,比如 RA6M4 ,见【开发板评测】Renesas RA6M4开发板之GPIO、IIC(模拟) 第二节部分。
Q5. 硬定时器、软定时器、硬件定时器,傻傻分不清楚
rt-thread 内核定义了软件定时器,和硬件定时器不同,硬件定时器需要占用一个定时器外设,还有各种比较、捕获等功能。软件定时器仅仅是简单的设定一个时间,时间 timeout 的时候执行我们设定的回调函数。
rt-thread 定义的软件定时器还细分两种,“硬定时器” “软定时器”,前一种是在 SysTick 中断中执行回调函数的,多数用于线程内置定时器,应用层也可以用,但是要时刻谨记它的回调函数是在中断中执行的。
后一种,是在一个线程中运行的,应用层对定时精度要求不是很高的可以用这种,但是也要注意“定义定时器和执行定时器回调函数的线程是两个不同的线程!”
Q6. 消息队列池申请多少内存合适?
rt_err_t rt_mq_init(rt_mq_t mq, const char *name, void *msgpool, rt_size_t msg_size, rt_size_t pool_size, rt_uint8_t flag);
rt_mq_t rt_mq_create(const char *name, rt_size_t msg_size, rt_size_t max_msgs, rt_uint8_t flag)
如果使用 rt_mq_create
创建消息队列,消息队列池自动根据消息体大小 msg_size
和消息队列最多容纳的消息数量 max_msgs
计算。
但如果使用 rt_mq_init
初始化消息队列,消息队列池的内存 msgpool
需要用户提供,这个时候,需要注意消息池内存大小 pool_size
。根据下面的公式计算得出:
(RT_ALIGN(msg_size, RT_ALIGN_SIZE) + sizeof(struct rt_mq_message*)) * max_msgs
其中,msg_size
是消息体大小,max_msgs
是消息队列中最多消息容量。
Q7. 使用消息队列注意
虽然 rt_mq_send
rt_mq_send_wait
rt_mq_urgent
rt_mq_recv
几个 api 有 size 参数,但是请严格按照 rt_mq_init
rt_mq_create
中的 msg_size 参数值传递相等的实参值。千万不要随意改变 size 参数的数值。
换种说法,别用消息队列直接发变长数据。
Q8. INIT_xxx_EXPORT 宏详解
当初接触 rt-thread 第一个让我感触的地方就是,main 函数里没有初始化配置,上来直接就是一个单独的线程。而,其它线程都通过 INIT_APP_EXPORT 自动启动了。
rt-thread 一共定义了 6 个启动阶段,
/* board init routines will be called in board_init() function */
#define INIT_BOARD_EXPORT(fn) INIT_EXPORT(fn, "1")
/* pre/device/component/env/app init routines will be called in init_thread */
/* components pre-initialization (pure software initilization) */
#define INIT_PREV_EXPORT(fn) INIT_EXPORT(fn, "2")
/* device initialization */
#define INIT_DEVICE_EXPORT(fn) INIT_EXPORT(fn, "3")
/* components initialization (dfs, lwip, ...) */
#define INIT_COMPONENT_EXPORT(fn) INIT_EXPORT(fn, "4")
/* environment initialization (mount disk, ...) */
#define INIT_ENV_EXPORT(fn) INIT_EXPORT(fn, "5")
/* appliation initialization (rtgui application etc ...) */
#define INIT_APP_EXPORT(fn) INIT_EXPORT(fn, "6")
其中, INIT_BOARD_EXPORT 运行在任务调度器启动前,也是唯一任务调度器运行前被执行的。这里是外设初始化配置阶段。
其余几个阶段都是任务调度器启动以后,由 main 线程(标准版,如果使用了 main 线程)负责执行。
这些阶段并不是完全固定,有些是可以调整的,例如,我曾经把 lcd 的初始化从 DEVICE 提前到 BOARD ,而把 emwin 的初始化放到 PREV 。还在 ENV 阶段初始化了一些消息队列等等。
大部分情况下,以上几个阶段可以完成所有定义的初始化工作。但是,也难免出现冲突的可能。
例如,github