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

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

3天内不再提示

RT-Thread学习笔记(入门篇/内核篇/开发环境篇/外设驱动篇/使用篇)

RTThread物联网操作系统 来源:RTThread物联网操作系统 作者:出出啊 2022-05-17 16:22 次阅读

前言

接触 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

这个公式别忘啊,别忘了!

Q5. 硬定时器、软定时器、硬件定时器,傻傻分不清楚

rt-thread 内核定义了软件定时器,和硬件定时器不同,硬件定时器需要占用一个定时器外设,还有各种比较、捕获等功能。软件定时器仅仅是简单的设定一个时间,时间 timeout 的时候执行我们设定的回调函数。

rt-thread 定义的软件定时器还细分两种,“硬定时器” “软定时器”,前一种是在 SysTick 中断中执行回调函数的,多数用于线程内置定时器,应用层也可以用,但是要时刻谨记它的回调函数是在中断中执行的。

后一种,是在一个线程中运行的,应用层对定时精度要求不是很高的可以用这种,但是也要注意“定义定时器和执行定时器回调函数的线程是两个不同的线程!”

Q6. 消息队列池申请多少内存合适?

1rt_err_trt_mq_init(rt_mq_tmq,constchar*name,void*msgpool,
2rt_size_tmsg_size,rt_size_tpool_size,rt_uint8_tflag);
3rt_mq_trt_mq_create(constchar*name,rt_size_tmsg_size,rt_size_tmax_msgs,rt_uint8_tflag)

如果使用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_sendrt_mq_send_waitrt_mq_urgentrt_mq_recv几个 api 有 size 参数,但是请严格按照rt_mq_initrt_mq_create中的 msg_size 参数值传递相等的实参值。千万不要随意改变 size 参数的数值

换种说法,别用消息队列直接发变长数据。

Q8. INIT_xxx_EXPORT 宏详解

当初接触 rt-thread 第一个让我感触的地方就是,main 函数里没有初始化配置,上来直接就是一个单独的线程。而,其它线程都通过 INIT_APP_EXPORT 自动启动了。

rt-thread 一共定义了 6 个启动阶段,

 1/*boardinitroutineswillbecalledinboard_init()function*/
 2#defineINIT_BOARD_EXPORT(fn)INIT_EXPORT(fn,"1")
 3
 4/*pre/device/component/env/appinitroutineswillbecalledininit_thread*/
 5/*componentspre-initialization(puresoftwareinitilization)*/
 6#defineINIT_PREV_EXPORT(fn)INIT_EXPORT(fn,"2")
 7/*deviceinitialization*/
 8#defineINIT_DEVICE_EXPORT(fn)INIT_EXPORT(fn,"3")
 9/*componentsinitialization(dfs,lwip,...)*/
10#defineINIT_COMPONENT_EXPORT(fn)INIT_EXPORT(fn,"4")
11/*environmentinitialization(mountdisk,...)*/
12#defineINIT_ENV_EXPORT(fn)INIT_EXPORT(fn,"5")
13/*appliationinitialization(rtguiapplicationetc...)*/
14#defineINIT_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 切换镜像服务器下载

0191b576-d50a-11ec-bce3-dac502259ad0.png

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

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

当执行scons --target=mdk5的时候,scons 从当前目录下的 "template.uvprojx" 文件为模版生成 "project.uvprojx" 项目配置文件。

我们修改项目配置,启用了 "Use MicroLIB" "Browse Information" "GNU extensions" 等等之后,重新生成可能导致之前的修改丢失,可以通过修改 "template.uvprojx" 文件

1"0""1"
2"0""1"
3"0""1"

还比如,有人问,“用Scons 生成keil工程时, 如何导入sct文件?”

打开 "template.uvprojx" 文件找到 “ScatterFile” 的位置,修改里面的文件路径及文件名就可以了。

.oardlinker_scriptslink.sct

其它可类比。

Q3. 修改 scons 使用的编译器

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

1#toolchainsoptions
2ARCH='arm'
3CPU='cortex-m4'
4CROSS_TOOL='gcc'

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

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

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

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

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

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

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

01b432f4-d50a-11ec-bce3-dac502259ad0.jpg

修改完,点 SAVE -》关闭。

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

020ee960-d50a-11ec-bce3-dac502259ad0.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 文件,

1pathlib=[cwdlib+'/Lib']
2
3group=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 个字节串口驱动给你个信号,这就难为人了。还有一种情况是,前后两次不同的数据被拼接在一起了。

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

  1rt_uint8_t*recvbuf=RT_NULL;
  2staticstructserial_configureuart_conf=RT_SERIAL_CONFIG_USER;
  3rt_uint8_t*datbuf=RT_NULL;
  4rt_size_trcv_off=0,recv_sz=1024,tmp=0;
  5rt_size_tdat_off=0,dat_len=0,i;
  6rt_tick_t_speed_ctrl=0;
  7
  8recvbuf=rt_malloc(128);
  9rt_memset(recvbuf,0,128);
 10datbuf=rt_malloc(32);
 11rt_memset(datbuf,0,32);
 12
 13busif_speed_ctrl=rt_tick_get();
 14
 15rt_sem_init(&rx_sem,"bifrx",0,0);
 16dev_busif=rt_device_find("uart1");
 17if(dev_busif==RT_NULL)
 18{
 19rt_kprintf("Cannotfinddevice:%s
","uart1");
 20return;
 21}
 22if(rt_device_open(dev_busif,RT_DEVICE_OFLAG_RDWR|RT_DEVICE_FLAG_INT_RX|
 23RT_DEVICE_FLAG_STREAM)==RT_EOK)
 24{
 25rt_device_set_rx_indicate(dev_busif,busif_rx_ind);
 26}
 27
 28while(1){
 29rt_sem_take(&rx_sem,RT_WAITING_FOREVER);
 30recv_sz=rt_device_read(dev_busif,-1,&recvbuf[rcv_off],128-rcv_off);
 31if(recv_sz>0){
 32rt_kprintf("data:%d
",recv_sz);
 33if(rcv_off==0){
 34i=0;
 35while((recvbuf[i]!=0x1A)&&(i< recv_sz)) i++;          //findheader
 36if(i==0){
 37rcv_off=recv_sz;
 38}elseif(i< recv_sz) {
 39rcv_off=recv_sz-i;
 40rt_memcpy(recvbuf,&recvbuf[i],recv_sz-i);
 41}else{//noheader
 42rcv_off=0;
 43continue;
 44}
 45}else{
 46rcv_off+=recv_sz;
 47}
 48if(rcv_off< 2){//datanotenough
 49continue;
 50}
 51dat_len=recvbuf[1];
 52if(dat_len>16){//errorlength
 53rcv_off=0;
 54dat_len=0;
 55continue;
 56}
 57if(rcv_off>=(dat_len+3)){//lenenough
 58floatval=0;
 59AdcValadc_val;
 60
 61if(recvbuf[9+2]!=0x1B){//findtailererror
 62dat_len=0;
 63rcv_off=0;
 64continue;
 65}
 66tmp=rcv_off-(dat_len+3);
 67_speed_ctrl=rt_tick_get();
 68if(/*busif_busy>0||*/(_speed_ctrl-busif_speed_ctrl< 100)){
 69busif_busy--;
 70if(tmp>0){
 71rt_memcpy(recvbuf,&recvbuf[dat_len+3],tmp);
 72rcv_off=tmp;
 73}else{
 74rcv_off=0;
 75}
 76dat_len=0;
 77continue;
 78}
 79switch(recvbuf[2]){//mapfunctiontypeid
 80case1:
 81adc_val.type=FUNC_DCV;
 82break;
 83case2:
 84adc_val.type=FUNC_DCI;
 85break;
 86default://unsupporttypeid
 87if(tmp>0){
 88rt_memcpy(recvbuf,&recvbuf[dat_len+3],tmp);
 89rcv_off=tmp;
 90}else{
 91rcv_off=0;
 92}
 93dat_len=0;
 94rt_kprintf("errortype:%d
",adc_val.type);
 95continue;
 96break;
 97}
 98rt_memcpy(datbuf,recvbuf+4,7);//changestr2float
 99datbuf[7]=0;
100rt_kprintf("%s
",datbuf);//数据域,可以是字符串,可以是十六进制数据
101//其它数据处理
102....
103//preparenextpackage准备下一包
104if(tmp>0){
105rt_memcpy(recvbuf,&recvbuf[dat_len+3],tmp);
106rcv_off=tmp;
107}else{
108rcv_off=0;
109}
110dat_len=0;
111}
112}
113}

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

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定义。可以创建只读文件。

1RT_WEAKconststructromfs_dirent_root_dirent[]=
2{
3{ROMFS_DIRENT_DIR,"dummy",(rt_uint8_t*)_dummy,sizeof(_dummy)/sizeof(_dummy[0])},
4{ROMFS_DIRENT_FILE,"dummy.txt",_dummy_txt,sizeof(_dummy_txt)},
5};

或者,只有目录

1RT_WEAKconststructromfs_dirent_root_dirent[]=
2{
3{ROMFS_DIRENT_DIR,"mnt",RT_NULL,0},
4{ROMFS_DIRENT_DIR,"usr",RT_NULL,0},
5{ROMFS_DIRENT_DIR,"var",RT_NULL,0},
6};

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

Q6. 丝滑挂载设备

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

 1result=dfs_mount(mtd_dev->parent.parent.name,"/usr","yaffs",0,0);
 2if(result==RT_EOK)
 3{
 4rt_kprintf("MountYAFFS2onNANDsuccessfully
");
 5}
 6else
 7{
 8result=dfs_mkfs("yaffs",mtd_dev->parent.parent.name);
 9if(result==RT_EOK)
10{
11result=dfs_mount(mtd_dev->parent.parent.name,"/usr","yaffs",0,0);
12}
13else
14{
15rt_kprintf("MountYAFFS2onNANDfailed
");
16return-RT_ERROR;
17}
18rt_kprintf("MountYAFFS2onNANDsuccessfully
");
19}

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

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 版

  • 定义一个宏


	
 1#ifndef__MEMORY_AT
 2#if(defined(__CC_ARM))
 3#define__MEMORY_AT(x)__attribute__((at(x)))
 4#elif(defined(__ARMCC_VERSION)&&(__ARMCC_VERSION>=6010050))
 5#define__MEMORY_AT__(x)__attribute__((section(".ARM.__AT_"#x)))
 6#define__MEMORY_AT(x)__MEMORY_AT__(x)
 7#else
 8#define__MEMORY_AT(x)
 9#warningPositionmemorycontaining__MEMORY_ATmacroatabsoluteaddress!
10#endif
11#endif

  • 使用uint8_t blended_address_buffer[480*272*2] __MEMORY_AT(0xC0000000);

gcc 版

  • 修改链接文件

 1/*ProgramEntry,settomarkitas"used"andavoidgc*/
 2MEMORY
 3{
 4CODE(rx):ORIGIN=0x08000000,LENGTH=1024k/*1024KBflash*/
 5RAM1(rw):ORIGIN=0x20000000,LENGTH=192k/*192Ksram*/
 6RAM2(rw):ORIGIN=0x10000000,LENGTH=64k/*64Ksram*/
 7SDRAM(rw):ORIGIN=0xC0000000,LENGTH=8092k/*1024KBsdram*/
 8}
 9
10SECTIONS
11{
12.../*忽略其它内容*/
13
14.sdram:
15{
16KEEP(*(.sdram_section))
17}>SDRAM
18}

  • 定义一个宏

1#ifndef__MEMORY_AT
2#define__MEMORY_AT(x)__attribute__((section(".#x")))
3#endif

  • 使用uint8_t blended_address_buffer[480*272*2] __MEMORY_AT(sdram_section);

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

Q11. 结构体字节对齐

 1__packedstruct__packed_struct{
 2...
 3};
 4struct__attribute__((packed))__packed_struct{
 5...
 6};
 7struct__packed_struct{
 8...
 9}__attribute__((packed));
10
11#pragmapack(push,n)
12struct__packed_struct{
13...
14};
15#pragmapack(pop)
keil 里可以这么写,其它开发环境下有差异。gcc 不支持__packed的写法
最后的写法,可以指定按照 n 个字节对齐,n 是一个具体是常数,常用的有 1 2 4 8 ...

Q12. unaligned access 是怎么出现的?

据我所知,当取指令的时候要求比较严格,编译器也往往把指令做了对齐处理。如果出现非对齐访问,多半是 pc 指针异常了。

还有一种情况,有人说 ARMv7-M 架构设计的时候,0xC0000000-0xDFFFFFFF 这个地址段默认要求必须 4字节(数据总线宽度)对齐访问。如果这片内存有个压缩的结构体变量,对此变量读写也可能出现 unaligned access 错误。解决方法如下:

1、修改 MPU 让这个区域变成正常储存器

1/*ConfiguretheMPUattributesasWTforSRAM*/
2LL_MPU_ConfigRegion(LL_MPU_REGION_NUMBER1,0x00,0xC0000000UL,
3LL_MPU_REGION_SIZE_16MB|LL_MPU_REGION_FULL_ACCESS|LL_MPU_ACCESS_NOT_BUFFERABLE|
4LL_MPU_ACCESS_NOT_CACHEABLE|LL_MPU_ACCESS_NOT_SHAREABLE|LL_MPU_TEX_LEVEL1|
5LL_MPU_INSTRUCTION_ACCESS_DISABLE);

2、映射 SDRAM 到别的地址.(0x60000000, 如果你外挂 NOR Flash,这个方法行不通)

1RCC->APB2ENR|=RCC_APB2ENR_SYSCFGEN;
2SYSCFG->MEMRMP|=SYSCFG_MEMRMP_SWP_FMC_0;

3、取消未对齐访问检测

编译器添加–no_unaligned_access编译选项。
有人介绍这个选项的时候说“强制编译对齐”或者“禁用未对齐访问支持”,个人认为这种说法不正确,因为,结构体还是按照咱们的想法按照字节对齐的,只是在访问这个数据的时候换了种方式,不用4字节(数据总线宽度)对齐的方式访问了。添加这个选项,恰恰是支持了未对齐数据访问。

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

  • 基于 ARMv6 体系结构的所有处理器

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

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

  • 基于 ARMv6 以前版本的体系结构的所有处理器

  • 基于 ARMv7-M 体系结构的处理器。

Q13. STM32F767 怎么使用 PersimmonUI?

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

结束语

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

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

原文标题:RT-Thread 使用宝典(持续更新中)

文章出处:【微信公众号:RTThread物联网操作系统】欢迎添加关注!文章转载请注明出处。

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

    关注

    3

    文章

    1309

    浏览量

    39833
  • 开发环境
    +关注

    关注

    1

    文章

    202

    浏览量

    16457
  • RT-Thread
    +关注

    关注

    31

    文章

    1147

    浏览量

    38857

原文标题:RT-Thread 使用宝典(持续更新中)

文章出处:【微信号:RTThread,微信公众号:RTThread物联网操作系统】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    单片机学习入门篇)pdf

    单片机学习入门篇)
    发表于 10-10 10:26

    PCB设计技巧之入门篇

    PCB设计技巧之入门篇
    发表于 08-05 21:44

    单片机 (入门篇

    单片机 (入门篇
    发表于 08-20 16:42

    单片机(入门篇

    单片机(入门篇
    发表于 04-01 15:22

    【OK210试用体验】入门篇(4)编程入门(NFS登录、驱动入门

    如下所示,表明成功。三 下阶段安排这样驱动模块的基本环境驱动开发的基本流程介绍完毕,阶段二的介绍,参考附录部分。自此【OK210试用体验】入门篇
    发表于 08-01 22:39

    CSR8670开发板--菜鸟入门篇

    CSR8670开发板--菜鸟入门篇
    发表于 09-30 08:45

    电子工程师自学速成 入门篇

    、毫伏表、示波器、频率计和扫频仪等内容。  《电子工程师自学速成入门篇》具有基础起点低、内容由浅入深、语言通俗易懂、结构安排符合学习认知规律的特点。《电子工程师自学速成入门篇》适合作为电子工程师
    发表于 11-09 12:50

    电子工程师自学速成 设计

    `“电子工程师自学速成”丛书分为“入门篇”、“提高”和“设计”共3本。《电子工程师自学速成(设计)》为“设计”,包括单片机技术和Pr
    发表于 11-09 12:53

    电子工程师自学速成入门篇 pdf 带书签!!

    发生器、毫伏表、示波器、频率计和扫频仪等内容。 《电子工程师自学速成——入门篇》具有基础起点低、内容由浅入深、语言通俗易懂、结构安排符合学习认知规律的特点。《电子工程师自学速成——入门篇》适合作为电子工程师
    发表于 10-27 15:37

    【转】STM32学习之路入门篇之指令集

    STM32学习之路入门篇之指令集
    发表于 12-26 22:45

    电子工程师自学速成 入门篇

    电子工程师自学速成 入门篇
    发表于 05-08 08:29

    机器学习入门篇:一个完整的机器学习项目

    机器学习项目入门篇:一个完整的机器学习项目
    发表于 05-11 14:47

    RK3399(内核入门篇)通过sysfs清楚了解设备的系统状况

    RK3399平台开发系列讲解(内核入门篇)1.1、通过sysfs清楚了解设备的系统状况 sys目录
    发表于 12-16 08:00

    【原创精选】RT-Thread征文精选技术文章合集

    、I2C通讯)STM32L051上使用RT-Thread (四、串口通讯)STM32L051上使用RT-Thread (五、完结)2. RT-Thread
    发表于 07-26 14:56

    RT-Thread设备驱动开发指南》读书测评

    驱动开发指南》这本书主要分了三个部分,由基础,进阶,高级组成,适用不同层次的开发者,对于不
    发表于 03-26 21:04