rt-thread 使用宝典(2022-0516更新)

RT-Thread实时操作系统简介 阅读详情

本文由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 都是针对性的,因信号量挂起的,不可能因为邮箱被唤醒。因为时间片耗尽挂起的线程也别想着会被什么资源唤醒。挂起和唤醒具有唯一性。

Q10. list_thread 或 ps 查看线程状态不对?
  1. error 列的线程错误没有多少参考价值,0 是正常,-2 表示超时,执行一个 rt_thread_mdelay 就变 -2 了。但并不表示有错误。目前还没有看到赋值有其它错误值的代码。
  2. status 列代表当前线程状态。但是呢,因为 list_thread 或 ps 两条命令是在 tshell 线程执行的,所以 tshell 线程肯定是 running ;idle 线程不可能被挂起,肯定显示的是 ready;其它线程可能会出现 ready,但是多数时候是 suspend。但是这并不表示其它线程一直是 suspend 不被调度了。
Q11. 定时器可以执行长时间操作?

如上所说,rt-thread 中有三种定时器,每种定时器有各自的特点
硬件定时器:回调函数在中断里,不建议直接执行长时间操作。
硬定时器:同样也是在中断执行回调函数,不建议直接执行长时间操作。
软定时器:由定时器线程执行调用回调函数的软定时器,是具有执行长时间操作的理论基础的。定时器线程同样是一个线程,它也有自己的线程栈,优先级等。如果某些操作是独立的,把它们放到某特定线程里和在定时器线程运行是没区别的。

但是,目前定时器线程处理软定时器的方式不适合执行长时间操作。需要进行修改后才能做到。具体修改方法见 rt-thread 系统优化系列(三) 之 软定时器的定时漂移误差分析

注意:定时器线程的优先级需要根据需要进行调整;若有多个软定时器,回调函数执行都比较长,必然存在某回调被延迟执行的可能性,这个是无法避免的。

Q12. “Function[xxx] shall not be used in ISR” 错误是怎么回事儿?

以及类似的错误 “Function[xxx] shall not be used before scheduler start”
详见rt-thread 那些你必须知道的几类 api

开发环境篇

Q1. 改变 env 或者 RT studio 下载源

rt studio 内置了 env 环境,studio 可能也是借助 env 实现下载更新组件的。有些第三方组件的主仓库在 github 上,这样就难为了很多小伙伴,经常因为访问不了 github 而出现下载更新失败。其实官方提供了镜像下载的方式,镜像仓库在 gitee 上,我们需要切换 env 下载方式为镜像下载。见此文章RT-Studio 切换镜像服务器下载

mirrorserver.png

文章中 RTT_逍遥 大佬提供了个命令 menuconfig -s ,这个命令也可以,查看 menuconfig 的帮助信息可以得到详细说明。

Q2. 生成 MDK5 项目,配置变了怎么办?

当执行 scons --target=mdk5 的时候,scons 从当前目录下的 “template.uvprojx” 文件为模版生成 “project.uvprojx” 项目配置文件。
我们修改项目配置,启用了 “Use MicroLIB” “Browse Information” “GNU extensions” 等等之后,重新生成可能导致之前的修改丢失,可以通过修改 “template.uvprojx” 文件

"<useUlib>0</useUlib>" 为 "<useUlib>1</useUlib>"
"<BrowseInformation>0</BrowseInformation>" 为 "<BrowseInformation>1</BrowseInformation>"
"<uGnu>0</uGnu>" 为 "<uGnu>1</uGnu>"

还比如,有人问,“用Scons 生成keil工程时, 如何导入sct文件?”
打开 “template.uvprojx” 文件找到 “ScatterFile” 的位置,修改里面的文件路径及文件名就可以了。
<ScatterFile>.\board\linker_scripts\link.sct</ScatterFile>

其它可类比。

Q3. 修改 scons 使用的编译器

找到 rtconfig.py 文件,一般和 rtconfig.h 文件同目录,文件开头有几个变量

# toolchains options
ARCH='arm'
CPU='cortex-m4'
CROSS_TOOL='gcc'

分别定义了,cpu 核架构,版本,以及使用的交叉编译工具链平台。目前支持 gcc keil iar 三种平台。

接下来,针对每一种平台,使用不同的交叉编译工具链及其安装路径

最后是每种交叉编译工具链编译选项。

通过修改 CROSS_TOOL='gcc' 的定义可以修改编译器。

Q4. env 下的两种界面配置姿势

他人都说 studio 好,我却独衷心 env。
以前只知道 menuconfig,从此还有一个 scons --pyconfig。

论坛 mysterywolf 大佬发现的这个命令,像捡到一个宝,不习惯 menuconfig 的童鞋,喜欢 studio 配置可以点点点的童鞋,你们可以回来继续使用 env 啦!

pyconfig.png

修改完,点 SAVE -》关闭。

还支持搜索跳转,点 Jump to…,弹出界面里输入搜索内容,Search。

pyconfigsearch.png

选项里可以直接配置,或者双击跳回原菜单位置。

比 studio 或 menuconfig 里的功能一点儿不少,还更方便操作啦。

Q5. menuconfig 找不到需要的在线包怎么办?

执行 pkgs --upgrade 命令,注意!不是 pkgs --update !
前一条命令用于更新 env 自带的 RT-Thread online packages 包列表信息。后者用于下载、更新、删除选择的包。

Q6. RT-Studio 怎么修改编译选项?

在 env 环境下,每个 bsp 根目录下都有个 rtconfig.py 文件,里面是各种开发环境下交差编译工具链配置(上文有提及过)。
修改了 rtconfig.py 文件后,使用 scons 编译直接使用的修改后的配置;使用 keil 开发需要执行 scons --target=mdk5,把新修改同步更新到 keil 项目配置文件里。

如果使用 RT-Studio 呢?。

  1. 修改 RT-Studio 里的编译配置,需要按照 eclipse 的方式来。右键项目》》属性》》C/C++ 构建》》设置》》工具设置,在这里可以修改的有 c 编译器选项、c++ 编译器选项、链接器选项,blablabla 眼花缭乱的不一定能改对,小心谨慎,多加练习吧。
  2. 使用 scons --target=eclipse,更新 RT-Studio 项目配置文件。需要注意的是,执行这个命令前先关掉 RT-Studio ,然后打开 env 切换到项目目录下,删掉 “.cproject” 项目文件,最后修改 rtconfig.py 文件后执行 scons --target=eclipse。
Q7. 添加第三方 lib 及其搜索路径

添加 lib 和路径也需要在 rtconfig.py 文件里修改,修改 LFLAGS 变量,增加库 -lxxx 。修改 LPATH 添加库搜索路径。
修改 rtconfig.py 之后按照上一小节的操作步骤刷新一下 IDE 工程文件。

还有一些 lib 是跟随软件包组件添加的,修改软件包目录下的 Scronscript 文件,

pathlib = [cwdlib + '/Lib']

group = DefineGroup('STemWin2RTT', src, depend = [''], CPPPATH = path, LIBS=['STemWin532_CM4_OS_Keil_ot'], LIBPATH = pathlib)

pathlib 用于添加 lib 文件路径。
“DefineGroup” 定义组时,增加两个参数 LIBS=['STemWin532_CM4_OS_Keil_ot'], LIBPATH = pathlib 分别指定库名称和库路径。

Q8. 添加头文件包含路径

因为 rt-thread 源码默认是 scons 自动化开发环境。源码中有大量的 Scronscript 脚本文件,这些文件控制着源码文件是否参与编译,增加哪些头文件搜索路径
src += ['xxxx.c'] 添加源码文件。
path += [cwd + '/ports'] 添加头文件路径。

外设驱动篇

Q1. USB Host 不识别 U 盘等设备

详见 rt-thread STM32F4 usbhost 调试笔记

这里还有另外两位大佬提供的修改方案,可以都尝试一下。或者集众家之长,前一段时间我按照两位大佬的也修改了一下,感觉都是可以兼容的,暂未发现问题。

PS: STM32 系列的芯片,可能要求 USBHOST 时钟频率是 48MHz ,这个要注意。

Q2. NAND Flash 驱动

gitee 有完整代码

Q3. 移植 yaffs2 文件系统

配合上面的 nand flash 驱动使用。
gitee 有完整代码

使用篇

Q1. 串口通讯数据被分多次接收了,怎么办?

首先说明,串口是一种流设备,无协议接口。它收到一个字节给你一个字节,收到两个字节给你两个字节。如果你的数据是整齐的 16 个字节,而且想每收 16 个字节串口驱动给你个信号,这就难为人了。还有一种情况是,前后两次不同的数据被拼接在一起了。

这个时候,需要我们在应用层进行处理。或者是定长包,或者定义包头包尾,包长度等等。下面给出我在论坛上多次分享过的代码,这个是带包头包尾的,在这个基础上可以修改成其它各种形式包协议的。

    rt_uint8_t *recvbuf = RT_NULL;
    static struct serial_configure uart_conf = RT_SERIAL_CONFIG_USER;
    rt_uint8_t *datbuf = RT_NULL;
    rt_size_t rcv_off = 0, recv_sz = 1024, tmp = 0;
    rt_size_t dat_off = 0, dat_len = 0, i;
    rt_tick_t _speed_ctrl = 0;

    recvbuf = rt_malloc(128);
    rt_memset(recvbuf, 0, 128);
    datbuf = rt_malloc(32);
    rt_memset(datbuf, 0, 32);

    busif_speed_ctrl = rt_tick_get();

    rt_sem_init(&rx_sem, "bifrx", 0, 0);
    dev_busif = rt_device_find("uart1");
    if (dev_busif == RT_NULL)
    {
        rt_kprintf("Can not find device: %s\n", "uart1");
        return;
    }
    if (rt_device_open(dev_busif, RT_DEVICE_OFLAG_RDWR | RT_DEVICE_FLAG_INT_RX | \
                       RT_DEVICE_FLAG_STREAM) == RT_EOK)
    {
        rt_device_set_rx_indicate(dev_busif, busif_rx_ind);
    }

    while(1) {
        rt_sem_take(&rx_sem, RT_WAITING_FOREVER);
        recv_sz = rt_device_read(dev_busif, -1, &recvbuf[rcv_off], 128-rcv_off);
        if (recv_sz > 0) {
            rt_kprintf("data: %d\n", recv_sz);
            if (rcv_off == 0) {
                i = 0;
                while ((recvbuf[i] != 0x1A) && (i < recv_sz)) i++;          // find header
                if (i == 0) {
                    rcv_off = recv_sz;
                } else if (i < recv_sz) {
                    rcv_off = recv_sz-i;
                    rt_memcpy(recvbuf, &recvbuf[i], recv_sz-i);
                } else {                                                    // no header
                    rcv_off = 0;
                    continue;
                }
            } else {
                rcv_off += recv_sz;
            }
            if (rcv_off < 2) {                                              // data not enough
                continue;
            }
            dat_len = recvbuf[1];
            if (dat_len > 16) {                                             // error length
                rcv_off = 0;
                dat_len = 0;
                continue;
            }
            if (rcv_off >= (dat_len + 3)) {                                 // len enough
                float val = 0;
                AdcVal adc_val;

                if (recvbuf[9+2] != 0x1B) {                                 // find tailer error
                    dat_len = 0;
                    rcv_off = 0;
                    continue;
                }
                tmp = rcv_off-(dat_len + 3);
                _speed_ctrl = rt_tick_get();
                if (/*busif_busy > 0 || */(_speed_ctrl - busif_speed_ctrl < 100)) {
                    busif_busy--;
                    if (tmp > 0) {
                        rt_memcpy(recvbuf, &recvbuf[dat_len + 3], tmp);
                        rcv_off = tmp;
                    } else {
                        rcv_off = 0;
                    }
                    dat_len = 0;
                    continue;
                }
                switch(recvbuf[2]){                                         // map function type id
                case 1:
                    adc_val.type = FUNC_DCV;
                break;
                case 2:
                    adc_val.type = FUNC_DCI;
                break;
                default:                                                    // unsupport type id
                    if (tmp > 0) {
                        rt_memcpy(recvbuf, &recvbuf[dat_len + 3], tmp);
                        rcv_off = tmp;
                    } else {
                        rcv_off = 0;
                    }
                    dat_len = 0;
                    rt_kprintf("error type: %d\n", adc_val.type);
                    continue;
                break;
                }
                rt_memcpy(datbuf, recvbuf+4, 7);                            // change str 2 float
                datbuf[7] = 0;
                rt_kprintf("%s\n", datbuf);        // 数据域 ,可以是字符串,可以是十六进制数据
                // 其它数据处理
                ....
                // prepare next package 准备下一包
                if (tmp > 0) {
                    rt_memcpy(recvbuf, &recvbuf[dat_len + 3], tmp);
                    rcv_off = tmp;
                } else {
                    rcv_off = 0;
                }
                dat_len = 0;
            }
        }
    }

项目代码,神明保佑,别被老板看到

Q2. 线程间传输不定长数据

有两种消息机制可以传输数据,邮箱和消息队列。以下是一些使用建议:

  • 邮箱传输的是定长 32bit 数据,或者是一个整型值,或者是一个地址;
  • 消息队列的可伸缩性更强,而且有队列,消息体大小由用户决定,但是,一经初始化,消息体大小也是固定长度的了。
  1. 对于某些不同类型数据,每种数据长度固定,而且各种类型数据长度差别不是很多的情况,我们可以使用联合体代替结构体。这样消息体的长度也是固定的,以最长长度为准。
  2. 用邮箱传递内存地址,这样不限定数据长度,但是要求每一次邮箱必须被接收方接收。发送方申请内存,接收方释放内存。如果出现邮箱发送失败,由发送方释放内存。
  3. 用消息队列传递内存地址,比邮箱的优势就在于它能缓存多个地址,降低发送失败的风险。
  4. pipe 管道或 ringbuffer。pipe 内部数据结构也是 ringbuffer。虽然可以读写任意长度数据,但是,这样又将数据变成流了。需要读取方根据事先约定的协议进行解析拆分。还有个缺陷是它没有消息机制,写方需要单独发消息通知接收方,或者,接收方死等这个数据。鉴于这种方式必须用锁,不适合中断和线程之间的数据传输。
Q3. 插上 U 盘怎么通知应用程序?

gitee 有完整代码,主要修改在 hub.c 和 udisk.c 两个文件

Q4. 怎么优雅的挂载多种存储设备?

内存、片上 flash 、片外 spi flash、sd 卡、U盘… 各式各样的的设备,挂载的文件系统也可能不一而足。怎么优雅的把多种设备挂载到文件系统就是个需要考虑的问题了。

  1. 挂载 rom 根文件系统,同时创建其它可读写文件系统挂载点。
  2. 其它设备分别挂载到 rom 文件系统的挂载点上。
Q5. rom 文件系统

rt-thread 源码目录下 “components/dfs/filesystems/romfs” 有个 romfs.c 文件,是 rom 文件系统配置模板文件,拷贝它到你的应用目录下,修改 _root_dirent 定义。可以创建只读文件。

RT_WEAK const struct romfs_dirent _root_dirent[] =
{
    {ROMFS_DIRENT_DIR, "dummy", (rt_uint8_t *)_dummy, sizeof(_dummy) / sizeof(_dummy[0])},
    {ROMFS_DIRENT_FILE, "dummy.txt", _dummy_txt, sizeof(_dummy_txt)},
};

或者,只有目录

RT_WEAK const struct romfs_dirent _root_dirent[] =
{
    {ROMFS_DIRENT_DIR, "mnt", RT_NULL, 0},
    {ROMFS_DIRENT_DIR, "usr", RT_NULL, 0},
    {ROMFS_DIRENT_DIR, "var", RT_NULL, 0},
};

有了只读文件系统,可以很方便扩展挂载很多其它文件系统。

Q6. 丝滑挂载设备

在嵌入式里很多存储设备是焊接到电路板上的存储芯片。如果我们有在存储芯片上挂载文件系统的需求,出厂生产必须有方式对存储设备进行格式化。为此,可能难倒一大批流水线工人。可以使用下面的流程进行挂载。

    result = dfs_mount(mtd_dev->parent.parent.name, "/usr", "yaffs", 0, 0);
    if (result == RT_EOK)
    {
        rt_kprintf("Mount YAFFS2 on NAND successfully\n");
    }
    else
    {
        result = dfs_mkfs("yaffs", mtd_dev->parent.parent.name);
        if (result == RT_EOK)
        {
            result = dfs_mount(mtd_dev->parent.parent.name, "/usr", "yaffs", 0, 0);
        }
        else
        {
            rt_kprintf("Mount YAFFS2 on NAND failed\n");
            return -RT_ERROR;
        }
        rt_kprintf("Mount YAFFS2 on NAND successfully\n");
    }

挂载失败,直接格式化,有些比较暴力,但是不需要人工格式化存储设备了。

Q7. 如何自动挂载文件系统

两种方式:一种是通过配置,启用 RT_USING_DFS_MNTTABLE 。这种方式需要使用者自己实现一个结构体数组 const struct dfs_mount_tbl mount_table[] 。

另一种就是自己写代码挂载不同设备。我喜欢这一种,因为这样我可以使用上一小节提到的设计。

Q8. assertion failed at function:rt_xxxxx

问题是我没调用 rt_xxxxx 函数啊?!

这种问题分两种:
一种是,确定这个函数在运行中正常调用的,例如:(tid != RT_NULL) assertion failed at function:rt_applilcation_init,可以确定的是 rt_applilcation_init 函数运作于线程调度器启动前,这个时候肯定不会是多线程非法写了内存引起的。可以确定是因为 rt_thread_create 函数调用返回了空指针。那么,问题来了,堆初始化成功了吗?内存有多大?
另外一种是,没有调用那个函数的地方,但是提示这个函数参数检测出错。这种情况大概率是 PC 指针飞了。走到了不应该走到的位置。

定位问题方法请见下节。

Q9. hard fault on thread: xxx

考虑了很久,要不要把这个加进来。出现这个错误提示的可能性太多了。从现象上看也分两类,一类比较确定的,程序走到这个位置必然出现;一类不太确定,每次运行可能现象不一样。

  • 环境搭建问题,系统移植有缺陷引起的。
  • 线程栈太小,线程栈爆栈。
  • 数组越界、野指针、函数参数传参错误…
  • 逻辑性错误,内存释放后还有可能被使用。多发生在外设的接收缓存上。

但是,上面这些只是扯淡,并不能定位到错误位置。定位问题是个方法论范畴的概念。每个人都应该有自己熟悉的一套做法,我的想法请见rt-thread 工具讲解系列(二) 之 如何排查系统 bug

Q10. 怎么定义变量到指定内存位置?

非 gcc 版

  • 定义一个宏
#ifndef      __MEMORY_AT
  #if     (defined (__CC_ARM))
    #define  __MEMORY_AT(x)     __attribute__((at(x)))
  #elif   (defined (__ARMCC_VERSION) && (__ARMCC_VERSION >= 6010050))
    #define  __MEMORY_AT__(x)   __attribute__((section(".ARM.__AT_"#x)))
    #define  __MEMORY_AT(x)     __MEMORY_AT__(x)
  #else
    #define  __MEMORY_AT(x)
    #warning Position memory containing __MEMORY_AT macro at absolute address!
  #endif
#endif
  • 使用 uint8_t blended_address_buffer[480*272*2] __MEMORY_AT(0xC0000000);

gcc 版

  • 修改链接文件
/* Program Entry, set to mark it as "used" and avoid gc */
MEMORY
{
    CODE (rx) : ORIGIN = 0x08000000, LENGTH = 1024k /* 1024KB flash */
    RAM1 (rw) : ORIGIN = 0x20000000, LENGTH =  192k /* 192K sram */
    RAM2 (rw) : ORIGIN = 0x10000000, LENGTH =   64k /* 64K sram */
    SDRAM (rw): ORIGIN = 0xC0000000, LENGTH = 8092k /* 1024KB sdram */
}

SECTIONS
{
    ... /* 忽略其它内容 */

    .sdram : 
    {
        KEEP(*(.sdram_section))
    } >SDRAM
}
  • 定义一个宏
#ifndef      __MEMORY_AT
  #define  __MEMORY_AT(x)     __attribute__((section(".#x")))
#endif
  • 使用 uint8_t blended_address_buffer[480*272*2] __MEMORY_AT(sdram_section);

gcc 版本是把变量分配到某 section ,距离地址还有查一点儿。当多个变量放到同一个 section 的时候,它们的顺序就不保证了。这种情况只能多定义一些 section。

Q11. 结构体字节对齐
__packed struct __packed_struct{
    ...
};
struct __attribute__((packed)) __packed_struct{
    ...
};
struct __packed_struct{
    ...
} __attribute__((packed));

#pragma pack(push, n)
struct __packed_struct{
    ...
};
#pragma pack(pop)

keil 里可以这么写,其它开发环境下有差异。gcc 不支持 __packed 的写法
最后的写法,可以指定按照 n 个字节对齐,n 是一个具体是常数,常用的有 1 2 4 8 …

Q12. unaligned access 是怎么出现的?

据我所知,当取指令的时候要求比较严格,编译器也往往把指令做了对齐处理。如果出现非对齐访问,多半是 pc 指针异常了。
还有一种情况,有人说 ARMv7-M 架构设计的时候,0xC0000000-0xDFFFFFFF 这个地址段默认要求必须 4字节(数据总线宽度)对齐访问。如果这片内存有个压缩的结构体变量,对此变量读写也可能出现 unaligned access 错误。解决方法如下:

  1. 修改 MPU 让这个区域变成正常储存器
    /* Configure the MPU attributes as WT for SRAM */
    LL_MPU_ConfigRegion(LL_MPU_REGION_NUMBER1, 0x00, 0xC0000000UL,
                        LL_MPU_REGION_SIZE_16MB | LL_MPU_REGION_FULL_ACCESS | LL_MPU_ACCESS_NOT_BUFFERABLE |
                        LL_MPU_ACCESS_NOT_CACHEABLE | LL_MPU_ACCESS_NOT_SHAREABLE | LL_MPU_TEX_LEVEL1 |
                        LL_MPU_INSTRUCTION_ACCESS_DISABLE);
  1. 映射 SDRAM 到别的地址.(0x60000000, 如果你外挂 NOR Flash,这个方法行不通)
RCC-> APB2ENR | = RCC_APB2ENR_SYSCFGEN;
SYSCFG-> MEMRMP | = SYSCFG_MEMRMP_SWP_FMC_0;
  1. 取消未对齐访问检测
    编译器添加 –no_unaligned_access 编译选项。
    有人介绍这个选项的时候说“强制编译对齐”或者“禁用未对齐访问支持”,个人认为这种说法不正确,因为,结构体还是按照咱们的想法按照字节对齐的,只是在访问这个数据的时候换了种方式,不用4字节(数据总线宽度)对齐的方式访问了。添加这个选项,恰恰是支持了未对齐数据访问。

支持未对齐数据访问的基于 ARM 体系结构的处理器,包括:

  • 基于 ARMv6 体系结构的所有处理器
  • 基于 ARMv7-A 和 ARMv7-R 体系结构的处理器。

不支持未对齐数据访问的基于 ARM 体系结构的处理器,包括:

  • 基于 ARMv6 以前版本的体系结构的所有处理器
  • 基于 ARMv7-M 体系结构的处理器。
Q13. STM32F767 怎么使用 PersimmonUI?

不止 STM32F767,可以使用 PersimmonUI 芯片可以很多,详情见独立文章STM32F767 使用 PersimmonUI 及其它芯片使用可行性分析

结束语

本人能力有限,文中难免有错误,或者方法错误。望各位同仁不吝赐教。
拜谢拜谢

此宝典不定期更新,希望有朝一日能破万条。

RT-Thread简介及启动流程分析 RT-Thread是一个来自中国的开源、中英文双语的实时操作系统(RTOS),它适用于各种资源受限的嵌入式系统。自2006年由熊谱翔(Bernard Xiong)创建以来,RT-Thread已经发展成为一个功能丰富、高度可伸缩、完全开源的实时操作系统。实时性能:RT-Thread提供了高实时性能,能够满足嵌入式系统对实时性的需求。可伸缩性:系统设计为可伸缩,可以运行在从几百字节内存的简单嵌入式设备到拥有大量内存的复杂系统。组件丰富。 阅读详情

相关推荐

[RTT] RT-Thread Hard Fault 死机问题定位方法

RT-Thread操作系统中Hard Fault是比较常见的死机问题,造成这个问题的原因多种多样,但排查方式大同小异。本文以执行空函数死机和操作不可写内存死机两种情况为例,和大家分享下这类问题的排查定位方法。

_ 1万+

RT-Thread简介

RT-Thread是一款完全由国内团队开发维护的嵌入式实时操作系统(RTOS),具有完全的自主知识产权。经过16个年头的沉淀,伴随着物联网的兴起,它正演变成一个功能强大、组件丰富的物联网操作系统。RT-Thread,全称是Real Time-Thread,顾名思义,它是一个嵌入式实时多线程操作系统,基本属性之一是支持多任务,允许多个任务同时运行并不意味着处理器在同一时刻真地执行了多个任务。事实上,一个处理器核心在某一时刻只能运行一个任务。

Caramel_biscuit的博客 4040

RTThread操作系统深度学习指南

RTThread 是一个具有高可靠性和卓越性能的开源实时操作系统(RTOS),专为资源受限的嵌入式设备设计。它支持广泛的硬件平台和丰富的中间件组件,非常适合物联网(IoT)、嵌入式和消费电子设备的开发。在本章中,我们将从 RTThread 的起源和核心理念出发,探讨其作为物联网基石的关键作用。接着,我们将分析 RTThread 的设计哲学,特别是其微内核架构如何满足现代嵌入式系统对性能和可扩展性的需求。

weixin_35995661的博客 2183

rtthread学习笔记(一)

RT-Thread(Real Time-Thread),是一个嵌入式实时操作系统,基本属性之一是支持多任务,在rtt系统中,任务通过线程实现名词解释——rttread特点:是一个高度可伸缩组件完整丰富、简易开发、超低功耗、高安全性的物联网操作系统高度可伸缩——可以根据MCU资源情况裁剪或添加组件,最小仅需(NANO版本——极简版内核)。实时内核的实现包括:☐ 对象管理(Object Management)

weixin_43231779的博客 7253

高效网络流量回放:使用tcpreplay实现多目标IP的pcap重放

本文详细介绍了如何使用tcpreplay工具高效回放网络流量pcap文件,并实现多目标IP发送。通过tcprewrite编辑报文和并行回放策略,可满足安全测试、负载测试等场景需求。文章涵盖安装、基础回放、核心技巧及性能优化,帮助读者构建真实流量压力测试环境。

weixin_29297127的博客 479

从‘裸纤’到成品:手把手拆解光纤阵列(FA)的装配、点胶与端面研磨全流程

本文详细拆解了光纤阵列(FA)从裸纤到成品的全流程,包括装配、点胶与端面研磨等关键工艺。通过实战经验和数据对比,揭示了5G基站、数据中心等场景中FA的精密制造要点与常见故障解决方案,为光通信器件生产提供专业指导。

weixin_42652674的博客 744

RT-Thread 简介及架构

RT-Thread,全称是 Real Time-Thread,顾名思义,它是一个嵌入式实时多线程操作系统,基本属性之一是支持多任务,允许多个任务同时运行并不意味着处理器在同一时刻真地执行了多个任务。事实上,一个处理器核心在某一时刻只能运行一个任务,由于每次对一个任务的执行时间很短、任务与任务之间通过任务调度器进行非常快速地切换(调度器根据优先级决定此刻该执行的任务),给人造成多个任务在一个时刻同时运行的错觉。在 RT-Thread 系统中,任务通过线程实现的,RT-Thread 中的线程调度器也就是以上提到

weixin_43885532的博客 3132

SolidWorks的GB库(钢型材库,包括铝型材、铝管等结构)安装及使用教程(生成铝型材为例)

1、GB库的下载 2、GB库的安装教程 3、GB库的使用教程

树下等苹果的博客 12万+

RTThread(一) - 概念及简介

文章目录前置说明RTThread框架线程管理及调度器线程间通信管理时钟管理内存分布内存管理动态内存堆管理静态内存池管理RTT启动过程自动初始化机制内核对象管理架构内核配置&剪裁 前置说明 一般单片机一般只有一个核心,做多线程实际上是 分时复用 CPU,是并发的。线程通常是指操作系统上的概念,而本文介绍的实时操作系统 RTThread 的线程个人感觉应该称之为 任务 更合理,但为了和官方说法一致,以下仍叫线程。 主要参考: RTThread 内核基础 RTThread框架 与其他 RTOS 不同,R

Hsy的博客 3万+

别再只飞不记了!用CsvView分析你的大疆飞行日志,优化航拍与排查炸机原因

本文详细介绍了如何利用CsvView工具分析大疆无人机的飞行日志,帮助用户优化航拍效率和飞行安全。通过解读飞行日志中的关键数据,如电池性能、飞行轨迹等,用户可以及时发现潜在问题并改进飞行习惯。文章还提供了事故排查方法和进阶分析技巧,适合无人机爱好者和专业飞手。

weixin_42535461的博客 547

LabVIEW内存泄漏诊断与优化:从报错到高效运行

本文深入探讨LabVIEW内存泄漏的诊断与优化方法,从理解内存泄漏本质到使用专业工具链进行问题定位,再到高频场景的解决方案和高级优化策略。通过实战案例展示如何解决工业数据采集系统中的内存不足问题,帮助开发者提升LabVIEW应用的稳定性和效率。

moon9的博客 197

RT-Thread 操作系统介绍、移植及内核介绍

做世界级的 OS,让万物互联,信息畅通无阻,成为未来 AIoT 领域最为主流的操作系统平台。RT-Thread 是一个集实时操作系统(RTOS)内核、中间件组件和开发者社区于一体的技术平台,由熊谱翔先生带领并集合开源社区力量开发而成,RT-Thread 也是一个组件完整丰富、高度可伸缩、简易开发、超低功耗、高安全性的物联网操作系统。RT-Thread 具备一个 IoT OS 平台所需的所有关键组件,例如GUI、网络协议栈、安全传输、低功耗组件等等。

sinat_30943509的博客 8696

DCA决策曲线的解读和代码实现

0.决策曲线 决策曲线分析法(Decision Curve Analysis,DCA)是个与ROC曲线相提并论的相对比较新的模型评价方法。 关于它的原理,长篇大论的医学统计学知识解读实在不是我的强项,放上两个链接偷偷懒: https://www.plob.org/article/12455.html https://www.iikx.com/news/statistics/1622.html 如果想看文章,这篇里的fig7就是:https://www.ncbi.nlm.nih.gov/pmc/artic

weixin_42960896的博客 1万+

RT-Thread——快速入门

以RT-Thread 源码(V3.1.5)为例,它的目录如下:主要涉及3个目录:以理解、使用RT-Thread 为目标的话,最核心的文件只有5个:这些文件的作用列表如下:移植RT-Thread,包含CPU 部分移植和BSP 部分移植。CPU 部分移植就是移植libcpu 目录,主要涉及 和。比如: 和 ,这表示 Cortex-M3 架构在 RVDS 或Keil 工具上的移植文件如果RT-Thread 还没支持你使用的CPU,你才需要实现这两个文件。一般来说,对于大部分的CPU, RT-Thread都已经支持

2301_78772787的博客 7406

信贷模型域——清收阶段模型(贷后模型)

本文深入探讨了信贷模型域中的清收阶段模型(贷后模型),涵盖早期催收模型(C卡)、晚期催收模型、Best Time to Contact模型和回收模型。这些模型通过多维度数据输入,运用分类、回归和生存分析等建模方法,输出关键指标如还款概率、回收率等,广泛应用于银行、资产管理公司和催收外包公司,助力提升回款率、优化资源分配、降低催收成本,并融入贷后风控体系。

庄小焱 651

UGUI合批杀手:Mask/RectMask2D为何必断合批?

颜色缓冲负责记录像素颜色;模板缓冲负责记录“这个像素的模板值”(一个小整数);渲染时,你可以设定:只有模板值为 X 的地方才允许画;或者往某些区域写特定的模板值。类似于:先拿一支隐形笔在纸上画出一块区域,给它记一个编号“1”,后面的涂色动作只允许涂在“编号为1”的区域上。合批的前提:渲染状态不变,连续元素能一口气画完。材质、贴图、混合模式、Stencil、裁剪区域……都得一致。Mask / RectMask2D 的本质:在渲染状态上加规则——“只在某个区域内允许写像素”。

本博客聚焦游戏开发技巧、创业实战心得及大学热门课程干货。这里既有技术深度,也有思维广度,无论你是开发者、创业者还是高校学子,都能在这里找到适合自己的成长之路! 117

[嵌入式系统-54]:RT-Thread-内核:内核基础与核心概念

本章介绍 RT-Thread内核基础与核心概念,包括:内核简介系统的启动流程及内核配置的部分内容,为后面的章节奠定基础。RT-Thread 内核的简单介绍,从软件架构入手讲解实时内核的组成与实现,这部分给初学者引入一些 RT-Thread 内核相关的概念与基础知识,让初学者对内核有初步的了解,知道内核的组成部分、系统如何启动、内存分布情况以及内核配置方法。一、RT-Thread 内核介绍内核是一个操作系统的核心,是操作系统最基础也是最重要的部分。它负责管理系统的。

文火冰糖(王文兵)的博客 5146

精雕软件(JDpaint) 5.5 绿色版

精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版

【RT-Thread基础教程】线程的基本使用

在嵌入式系统开发中,RTOS(Real-Time Operating System)扮演着至关重要的角色,而RT-Thread作为一款开源的实时操作系统,在嵌入式领域中备受欢迎。线程是RTOS中的基本执行单元,其良好的多任务处理能力使得嵌入式系统能够更有效地响应各种事件和任务。本文将着重介绍在RT-Thread中线程的基本使用方法,包括线程的创建、调度、挂起和删除等方面。通过深入理解线程的基本概念和操作,读者将能更好地利用RT-Thread构建稳定而高效的嵌入式系统。

m0_62599305的博客 5114

温度PID控制PLC程序

温度PID稳定控制程序,适用于欧姆龙PLC,对其他PLC也具有很大参考价值

SAP B1中文培訓教材_SDK

公司内部培训资料,对开发工具 ,DIAPI, UIAPI,Add-ON有详细解说

RT-Thread——概述与体验

RT-Thread启动代码统一入口为rtthread_startup(),芯片启动文件在完成必要工作(初始化时钟、配置中断向量表、初始化堆栈、初始化PC指针、调用C库函数-main最终去到C的世界)后,最终会在程序跳转市,跳转至RT-Thread的启动入口中,RT-Thread的启动流程如下;可以分析看到,在stm32的启动代码中,先调用SystemInit:系统初始化、比如初始化时钟,调用__main函数:重定位,比如把代码段从Flash复制到内存,然后执行$Sub$$main。

qq_43460068的博客 2573

RT-Thread STM32系列驱动介绍①

在 RT-Thread 实时操作系统中,各种各样的设备驱动是通过一套 I/O 设备管理框架来管理的。设备管理框架给上层应用提供了一套标准的设备操作 API,开发者通过调用这些标准设备操作 API,可以高效地完成和底层硬件外设的交互。设备管理框架的结构如下图所示:使用同一套标准的 API 开发应用程序,使应用程序具有更好的移植性底层驱动的升级和修改不会影响到上层代码驱动和应用程序相互独立,方便多个开发者协同开发板载外设驱动:指 MCU 之外,开发板上外设,例如 TF 卡、以太网和 LCD 等。

华为奋斗者精神 2055

RT-Thread学习

一、入门 RT-Thread官网  RT-Thread官方bilibili视频号   GD32官网 教你动手移植RT-Thread到国产MCU    如何移植RT-Thread到GD32单片机上(非studio版) 东方青讲RT-Thread

pzs0221的专栏 4655

RT-Thread之线程管理(线程的基础概念和使用)

本篇文章来给大家讲解RT-Thread中的线程管理,线程管理是属于最基础的部分,这篇文章将带大家学习如何在RT-Thread使用线程。本篇文章我们主要给大家讲解了RT-Thread中的线程的一些基础概念和基础使用方法。

m0_49476241的博客 2351

[嵌入式系统-33]:RT-Thread -18- 新手指南:三种不同的版本、三阶段学习路径

RT-Thread,全称是 Real Time-Thread,顾名思义,它是一个嵌入式实时多线程操作系统,基本属性之一是支持多任务,但允许多个任务同时运行并不意味着处理器在同一时刻真的执行了多个任务。事实上,一个处理器核心在某一时刻只能运行一个任务由于每次对一个任务的执行时间很短、任务与任务之间通过任务调度器进行非常快速地切换(调度器根据优先级决定此刻该执行的任务)所以给人造成多个任务在一个时刻同时运行的错觉。

文火冰糖(王文兵)的博客 3441

RTThread 线程管理

Rtthread线程管理学习

qq_40074911的博客 1348

[RTOS]uCOS、FreeRTOS、RTThread、RTX等RTOS的对比之特点

最近正好又重新回顾了一下这几款OS,心里一直有个疑问,明明这几款RTOS是这么像,为什么还要搞出这么多个来呢,最后的结论就是,管他呢,反正哪个用的顺手用哪个。   本篇博客就来细数这几个RTOS的特点。   以下内容均来自官方网站或者官方手册Feature的Google翻译的加了我的一些调整,没有任何主观成分。 未经同意禁止转载,尊重别人的劳动成果!!!https://blog.youkuaiyun.com/wangyijieonline/article/details/77584726 1. FreeRT..

weixin_43684318的博客 3409
上一篇: 专访李路昌: 从普通打工人到在深圳安家的嵌入式攻城狮
下一篇: 【先楫HPM6750测评】RT-Thread开发环境搭建和Hello World
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值