890
社区成员
本文由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 #5194 上的这个 pr。里面还提供了很多反应这个问题的链接。以及很多人提出的解决方案。
个人认为,启动顺序在同一级的,而且之间有依赖/互斥关系的两个部分。这种情况,应要求开发者自己注意调整代码执行顺序,把两个部分初始化过程写到同一个函数里,由开发者自己维护依赖关系。
Q9. 怎么通过 rt_thread_suspend rt_thread_resume 挂起唤醒某线程
尽量不要这么做,在 rt-thread 里,一个线程进入 suspend 态有两种情况,一种是时间片耗尽自动让出 cpu;一种是等待资源阻塞让出 cpu。两个线程之间并没有完整透明的了解对方当前状态的途径。
假如某线程 A 想显式挂起线程 B,但是,A 并不知道 B 当前是运行中让出 cpu,还是等待资源中已经处于挂起状态,还是资源可用正在从挂起态被唤醒过程中。所以,不明就里地挂起其它线程的做法是危险的。
笔者唯一能想到的,就是 B 线程执行任务比较多,自己不会主动出让 cpu。而且,它的线程优先级比较低,某高优先级线程 A 在某种条件下使得 B 挂起。但是这样线程 B 势必会影响到 idle 线程。
其实,这种场景,完全可以使用线程间同步机制实现,线程 B 通过发信号给 A 而挂起自己;线程 A 再通过另外一个信号唤醒线程 B。
曾经以为自己能找到直接使用这俩 api 的方式,有一天,突然想到 rt-thread 的 ipc 都是针对性的,因信号量挂起的,不可能因为邮箱被唤醒。因为时间片耗尽挂起的线程也别想着会被什么资源唤醒。挂起和唤醒具有唯一性。
..................................
原文链接:https://blog.csdn.net/rtthreadiotos/article/details/125177962