乐趣区

关于人工智能:rtthread-使用宝典20220516更新

本文由 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 切换镜像服务器下载

文章中 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 啦!

批改完,点 SAVE -》敞开。

还反对搜寻跳转,点 Jump to…,弹出界面里输出搜寻内容,Search。

选项里能够间接配置,或者双击跳回原菜单地位。

比 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);
  2. 映射 SDRAM 到别的地址.(0x60000000, 如果你外挂 NOR Flash, 这个办法行不通)

    RCC-> APB2ENR | = RCC_APB2ENR_SYSCFGEN;
    SYSCFG-> 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 及其它芯片应用可行性剖析

结束语

自己能力无限,文中不免有谬误,或者办法谬误。望各位同仁不吝赐教。
拜谢拜谢

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

退出移动版