0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

rt-thread 心法系列(二) 使用寶典

出出 ? 來源:出出 ? 作者:出出 ? 2022-07-08 09:41 ? 次閱讀

前言

接觸 rt-thread 已有半年,混論壇也5個(gè)半月了,期間遇到過各種奇奇怪怪的棘手問題,有過尷尬,也自信曾經(jīng)提供過比較妙的應(yīng)對方案。所以產(chǎn)生了將一些典型的使用技巧匯總分享出來的想法,遂有此篇。

PS: 接觸 rt-thread一年多了,這篇文章也經(jīng)歷了多次增補(bǔ)

入門篇

Q1. 剛下載SDK 啥也沒干,編譯沒錯(cuò),為啥程序跑不起來?

如果使用 keil + env 環(huán)境,下載源碼后的**第一件事就是 `menuconfig`** ;
如果使用 RT-Studio ,創(chuàng)建項(xiàng)目后的**第一件事就是打開 Settings** ;
把其中所有配置頁面所有配置項(xiàng)全瀏覽一遍,取消掉所有不相干的配置,最后只留一個(gè)內(nèi)核。

先保證最小系統(tǒng)跑起來,用點(diǎn)燈程序驗(yàn)證最小系統(tǒng)運(yùn)行正常。然后再添加自己需要用到的功能和底層外設(shè)等等。

Q2. 剛下載的 SDK 啥也沒干,編譯沒錯(cuò),為啥程序跑起來 hard fault on thread?

**同上**

Q3. 剛下載的 SDK 啥也沒干,編譯為啥報(bào)錯(cuò)了?

**同上**

內(nèi)核篇

Q1. RT_NAME_MAX 定義多少合適

原則上越少越省內(nèi)存,以內(nèi)核對象 100 個(gè)為例,一個(gè)對象名占用 8 字節(jié),總共是 800 字節(jié)。但是考慮到 `struct rt_object` 結(jié)構(gòu)體定義,后面跟了兩個(gè) rt_uint8_t 型變量。

RT_NAME_MAX 可以定義成 2n + 2

Q2. RT_DEBUG

如非必要,不要開啟內(nèi)核調(diào)試。除非,你真的想學(xué)習(xí)內(nèi)核,或者調(diào)試內(nèi)核的問題。

Q3. 線程棧大小定義多少合適?

這個(gè)問題和應(yīng)用有很大關(guān)系,如果僅僅是一個(gè)最小內(nèi)核系統(tǒng),除了 idle 線程,沒有使用其它中斷和應(yīng)用,256 也將將夠。如果添加了應(yīng)用代碼,還有中斷和消息機(jī)制。建議 1024 起步。

Q4. 怎么快速計(jì)算 GET_PIN 返回的編號?

我們知道,芯片的 GPIO 分組往往是從 PA 開始,往后依次是 PB PC PD PE ... PZ。往往的,每組端口或者是 16bit 或者是 8bit (分別對應(yīng) 16 個(gè) IO 和 8 個(gè) 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`

這個(gè)公式別忘啊,別忘了!

PS: 有種,他們的引腳號編碼很奇特,比如 RA6M4 ,見[【開發(fā)板評測】Renesas RA6M4開發(fā)板之GPIO、IIC模擬)]( https://club.rt-thread.org/ask/article/36fe553196532ddd.html ) 第二節(jié)部分。

Q5. 硬定時(shí)器、軟定時(shí)器、硬件定時(shí)器,傻傻分不清楚

rt-thread 內(nèi)核定義了軟件定時(shí)器,和硬件定時(shí)器不同,硬件定時(shí)器需要占用一個(gè)定時(shí)器外設(shè),還有各種比較、捕獲等功能。軟件定時(shí)器僅僅是簡單的設(shè)定一個(gè)時(shí)間,時(shí)間 timeout 的時(shí)候執(zhí)行我們設(shè)定的回調(diào)函數(shù)。

rt-thread 定義的軟件定時(shí)器還細(xì)分兩種,“硬定時(shí)器” “軟定時(shí)器”,前一種是在 SysTick 中斷中執(zhí)行回調(diào)函數(shù)的,多數(shù)用于線程內(nèi)置定時(shí)器,應(yīng)用層也可以用,但是要時(shí)刻謹(jǐn)記它的回調(diào)函數(shù)是在中斷中執(zhí)行的。
后一種,是在一個(gè)線程中運(yùn)行的,應(yīng)用層對定時(shí)精度要求不是很高的可以用這種,但是也要注意“定義定時(shí)器和執(zhí)行定時(shí)器回調(diào)函數(shù)的線程是兩個(gè)不同的線程!”

Q6. 消息隊(duì)列池申請多少內(nèi)存合適?

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` 創(chuàng)建消息隊(duì)列,消息隊(duì)列池自動(dòng)根據(jù)消息體大小 `msg_size` 和消息隊(duì)列最多容納的消息數(shù)量 `max_msgs` 計(jì)算。

但如果使用 `rt_mq_init` 初始化消息隊(duì)列,消息隊(duì)列池的內(nèi)存 `msgpool` 需要用戶提供,這個(gè)時(shí)候,需要注意消息池內(nèi)存大小 `pool_size`。根據(jù)下面的公式計(jì)算得出:

`(RT_ALIGN(msg_size, RT_ALIGN_SIZE) + sizeof(struct rt_mq_message*)) * max_msgs`

其中,`msg_size` 是消息體大小,`max_msgs` 是消息隊(duì)列中最多消息容量。

Q7. 使用消息隊(duì)列注意

雖然 `rt_mq_send` `rt_mq_send_wait` `rt_mq_urgent` `rt_mq_recv` 幾個(gè) api 有 size 參數(shù),但是請嚴(yán)格按照 `rt_mq_init` `rt_mq_create` 中的 msg_size 參數(shù)值傳遞相等的實(shí)參值。***千萬不要隨意改變 size 參數(shù)的數(shù)值***。

換種說法,別用消息隊(duì)列直接發(fā)變長數(shù)據(jù)。

Q8. INIT_xxx_EXPORT 宏詳解

當(dāng)初接觸 rt-thread 第一個(gè)讓我感觸的地方就是,main 函數(shù)里沒有初始化配置,上來直接就是一個(gè)單獨(dú)的線程。而,其它線程都通過 INIT_APP_EXPORT 自動(dòng)啟動(dòng)了。
rt-thread 一共定義了 6 個(gè)啟動(dòng)階段,

/* 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 運(yùn)行在任務(wù)調(diào)度器啟動(dòng)前,也是唯一任務(wù)調(diào)度器運(yùn)行前被執(zhí)行的。這里是外設(shè)初始化配置階段。
其余幾個(gè)階段都是任務(wù)調(diào)度器啟動(dòng)以后,由 main 線程(標(biāo)準(zhǔn)版,如果使用了 main 線程)負(fù)責(zé)執(zhí)行。

這些階段并不是完全固定,有些是可以調(diào)整的,例如,我曾經(jīng)把 lcd 的初始化從 DEVICE 提前到 BOARD ,而把 emwin 的初始化放到 PREV 。還在 ENV 階段初始化了一些消息隊(duì)列等等。

大部分情況下,以上幾個(gè)階段可以完成所有定義的初始化工作。但是,也難免出現(xiàn)沖突的可能。

例如,github [#5194]( https://github.com/RT-Thread/rt-thread/pull/5194 ) 上的這個(gè) pr。里面還提供了很多反應(yīng)這個(gè)問題的鏈接。以及很多人提出的解決方案。

個(gè)人認(rèn)為,啟動(dòng)順序在同一級的,而且之間有依賴/互斥關(guān)系的兩個(gè)部分。這種情況,應(yīng)要求開發(fā)者自己注意調(diào)整代碼執(zhí)行順序,把兩個(gè)部分初始化過程寫到同一個(gè)函數(shù)里,由開發(fā)者自己維護(hù)依賴關(guān)系。

Q9. 怎么通過 rt_thread_suspend rt_thread_resume 掛起喚醒某線程

盡量不要這么做,在 rt-thread 里,一個(gè)線程進(jìn)入 suspend 態(tài)有兩種情況,一種是時(shí)間片耗盡自動(dòng)讓出 cpu;一種是等待資源阻塞讓出 cpu。兩個(gè)線程之間并沒有完整透明的了解對方當(dāng)前狀態(tài)的途徑。
假如某線程 A 想顯式掛起線程 B,但是,A 并不知道 B 當(dāng)前是運(yùn)行中讓出 cpu,還是等待資源中已經(jīng)處于掛起狀態(tài),還是資源可用正在從掛起態(tài)被喚醒過程中。所以,不明就里地掛起其它線程的做法是危險(xiǎn)的。

筆者唯一能想到的,就是 B 線程執(zhí)行任務(wù)比較多,自己不會(huì)主動(dòng)出讓 cpu。而且,它的線程優(yōu)先級比較低,某高優(yōu)先級線程 A 在某種條件下使得 B 掛起。但是這樣線程 B 勢必會(huì)影響到 idle 線程。
其實(shí),這種場景,完全可以使用線程間同步機(jī)制實(shí)現(xiàn),線程 B 通過發(fā)信號給 A 而掛起自己;線程 A 再通過另外一個(gè)信號喚醒線程 B。

曾經(jīng)以為自己能找到直接使用這倆 api 的方式,有一天,突然想到 rt-thread 的 ipc 都是針對性的,因信號量掛起的,不可能因?yàn)猷]箱被喚醒。因?yàn)闀r(shí)間片耗盡掛起的線程也別想著會(huì)被什么資源喚醒。掛起和喚醒具有唯一性。

Q10. list_thread 或 ps 查看線程狀態(tài)不對?

1. error 列的線程錯(cuò)誤沒有多少參考價(jià)值,0 是正常,-2 表示超時(shí),執(zhí)行一個(gè) `rt_thread_mdelay` 就變 -2 了。但并不表示有錯(cuò)誤。目前還沒有看到賦值有其它錯(cuò)誤值的代碼。
2. status 列代表當(dāng)前線程狀態(tài)。但是呢,因?yàn)?list_thread 或 ps 兩條命令是在 tshell 線程執(zhí)行的,所以 tshell 線程肯定是 running ;idle 線程不可能被掛起,肯定顯示的是 ready;其它線程可能會(huì)出現(xiàn) ready,但是多數(shù)時(shí)候是 suspend。但是這并不表示其它線程一直是 suspend 不被調(diào)度了。

Q11. 定時(shí)器可以執(zhí)行長時(shí)間操作?

如上所說,rt-thread 中有三種定時(shí)器,每種定時(shí)器有各自的特點(diǎn)
硬件定時(shí)器:回調(diào)函數(shù)在中斷里,不建議直接執(zhí)行長時(shí)間操作。
硬定時(shí)器:同樣也是在中斷執(zhí)行回調(diào)函數(shù),不建議直接執(zhí)行長時(shí)間操作。
軟定時(shí)器:由定時(shí)器線程執(zhí)行調(diào)用回調(diào)函數(shù)的軟定時(shí)器,是具有執(zhí)行長時(shí)間操作的理論基礎(chǔ)的。定時(shí)器線程同樣是一個(gè)線程,它也有自己的線程棧,優(yōu)先級等。如果某些操作是獨(dú)立的,把它們放到某特定線程里和在定時(shí)器線程運(yùn)行是沒區(qū)別的。

但是,目前定時(shí)器線程處理軟定時(shí)器的方式不適合執(zhí)行長時(shí)間操作。需要進(jìn)行修改后才能做到。具體修改方法見 rt-thread優(yōu)化系列(三)軟定時(shí)器的定時(shí)漂移問題分析

注意:定時(shí)器線程的優(yōu)先級需要根據(jù)需要進(jìn)行調(diào)整;若有多個(gè)軟定時(shí)器,回調(diào)函數(shù)執(zhí)行都比較長,必然存在某回調(diào)被延遲執(zhí)行的可能性,這個(gè)是無法避免的。

Q12. "Function[xxx] shall not be used in ISR" 錯(cuò)誤是怎么回事兒?

以及類似的錯(cuò)誤 "Function[xxx] shall not be used before scheduler start"
詳見 rt-thread心法系列(一)那些你必須知道的幾類 api

開發(fā)環(huán)境篇

Q1. 改變 env 或者 RT studio 下載源

rt studio 內(nèi)置了 env 環(huán)境,studio 可能也是借助 env 實(shí)現(xiàn)下載更新組件的。有些第三方組件的主倉庫在 github 上,這樣就難為了很多小伙伴,經(jīng)常因?yàn)樵L問不了 github 而出現(xiàn)下載更新失敗。其實(shí)官方提供了鏡像下載的方式,鏡像倉庫在 gitee 上,我們需要切換 env 下載方式為鏡像下載。見此文章 RT-Studio 切換鏡像服務(wù)器下載

mirrorserver.png

文章中 **RTT_逍遙** 大佬提供了個(gè)命令 `menuconfig -s` ,這個(gè)命令也可以,查看 menuconfig 的幫助信息可以得到詳細(xì)說明。

Q2. 生成 MDK5 項(xiàng)目,配置變了怎么辦?

當(dāng)執(zhí)行 `scons --target=mdk5` 的時(shí)候,scons 從當(dāng)前目錄下的 "template.uvprojx" 文件為模版生成 "project.uvprojx" 項(xiàng)目配置文件。
我們修改項(xiàng)目配置,啟用了 "Use MicroLIB" "Browse Information" "GNU extensions" 等等之后,重新生成可能導(dǎo)致之前的修改丟失,可以通過修改 "template.uvprojx" 文件

"0" 為 "1"
"0" 為 "1"
"0" 為 "1"

還比如,有人問,“[用Scons 生成keil工程時(shí), 如何導(dǎo)入sct文件?]( https://club.rt-thread.org/ask/question/433781.html )”
打開 "template.uvprojx" 文件找到 “ScatterFile” 的位置,修改里面的文件路徑及文件名就可以了。
`.boardlinker_scriptslink.sct`

其它可類比。

Q3. 修改 scons 使用的編譯器

找到 rtconfig.py 文件,一般和 rtconfig.h 文件同目錄,文件開頭有幾個(gè)變量

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

分別定義了,cpu 核架構(gòu),版本,以及使用的交叉編譯工具鏈平臺。目前支持 gcc keil iar 三種平臺。

接下來,針對每一種平臺,使用不同的交叉編譯工具鏈及其安裝路徑

最后是每種交叉編譯工具鏈編譯選項(xiàng)。

通過修改 `CROSS_TOOL='gcc'` 的定義可以修改編譯器。

Q4. env 下的兩種界面配置姿勢

他人都說 studio 好,我卻獨(dú)衷心 env。
以前只知道 menuconfig,從此還有一個(gè) `scons --pyconfig`。

論壇 mysterywolf 大佬發(fā)現(xiàn)的這個(gè)命令,像撿到一個(gè)寶,不習(xí)慣 menuconfig 的童鞋,喜歡 studio 配置可以點(diǎn)點(diǎn)點(diǎn)的童鞋,你們可以回來繼續(xù)使用 env 啦!

pyconfig.png

修改完,點(diǎn) SAVE -》關(guān)閉。

還支持搜索跳轉(zhuǎn),點(diǎn) Jump to...,彈出界面里輸入搜索內(nèi)容,Search。

pyconfigsearch.png

選項(xiàng)里可以直接配置,或者雙擊跳回原菜單位置。

比 studio 或 menuconfig 里的功能一點(diǎn)兒不少,還更方便操作啦。

Q5. menuconfig 找不到需要的在線包怎么辦?

執(zhí)行 `pkgs --upgrade` 命令,注意!不是 `pkgs --update` !
前一條命令用于更新 env 自帶的 RT-Thread online packages 包列表信息。后者用于下載、更新、刪除選擇的包。

Q6. RT-Studio 怎么修改編譯選項(xiàng)?

在 env 環(huán)境下,每個(gè) bsp 根目錄下都有個(gè) rtconfig.py 文件,里面是各種開發(fā)環(huán)境下交差編譯工具鏈配置(上文有提及過)。
修改了 rtconfig.py 文件后,使用 scons 編譯直接使用的修改后的配置;使用 keil 開發(fā)需要執(zhí)行 `scons --target=mdk5`,把新修改同步更新到 keil 項(xiàng)目配置文件里。

如果使用 RT-Studio 呢?。

1. 修改 RT-Studio 里的編譯配置,需要按照 eclipse 的方式來。右鍵項(xiàng)目》》屬性》》C/C++ 構(gòu)建》》設(shè)置》》工具設(shè)置,在這里可以修改的有 c 編譯器選項(xiàng)、c++ 編譯器選項(xiàng)、鏈接器選項(xiàng),blablabla 眼花繚亂的不一定能改對,小心謹(jǐn)慎,多加練習(xí)吧。
2. 使用 `scons --target=eclipse`,更新 RT-Studio 項(xiàng)目配置文件。需要注意的是,執(zhí)行這個(gè)命令前先關(guān)掉 RT-Studio ,然后打開 env 切換到項(xiàng)目目錄下,刪掉 ".cproject" 項(xiàng)目文件,最后修改 rtconfig.py 文件后執(zhí)行 `scons --target=eclipse`。

Q7. 添加第三方 lib 及其搜索路徑

添加 lib 和路徑也需要在 rtconfig.py 文件里修改,修改 LFLAGS 變量,增加庫 `-lxxx` 。修改 LPATH 添加庫搜索路徑。
修改 rtconfig.py 之后按照上一小節(jié)的操作步驟刷新一下 IDE 工程文件。

還有一些 lib 是跟隨軟件包組件添加的,修改軟件包目錄下的 Scronscript 文件,

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

`pathlib` 用于添加 lib 文件路徑。
“DefineGroup” 定義組時(shí),增加兩個(gè)參數(shù) `LIBS=['STemWin532_CM4_OS_Keil_ot'], LIBPATH = pathlib` 分別指定庫名稱和庫路徑。

Q8. 添加頭文件包含路徑

因?yàn)?rt-thread 源碼默認(rèn)是 scons 自動(dòng)化開發(fā)環(huán)境。源碼中有大量的 Scronscript 腳本文件,這些文件控制著源碼文件是否參與編譯,增加哪些頭文件搜索路徑
`src += ['xxxx.c']` 添加源碼文件。
`path += [cwd + '/ports']` 添加頭文件路徑。

外設(shè)驅(qū)動(dòng)篇

Q1. USB Host 不識別 U 盤等設(shè)備

詳見 [rt-thread STM32F4 usbhost 調(diào)試筆記](https://club.rt-thread.org/ask/article/2878.html)

這里還有另外兩位大佬提供的修改方案,可以都嘗試一下?;蛘呒娂抑L,前一段時(shí)間我按照兩位大佬的也修改了一下,感覺都是可以兼容的,暫未發(fā)現(xiàn)問題。

PS: STM32 系列的芯片,可能要求 USBHOST 時(shí)鐘頻率是 48MHz ,這個(gè)要注意。

Q2. NAND Flash 驅(qū)動(dòng)

[gitee](https://gitee.com/thewon/rt_thread_repo) 有完整代碼

Q3. 移植 yaffs2 文件系統(tǒng)

配合上面的 nand flash 驅(qū)動(dòng)使用。
[gitee](https://gitee.com/thewon/rt_thread_repo) 有完整代碼

Q4. 更高效的串口驅(qū)動(dòng)框架 serialX

請移步系列文章,從理論提出到實(shí)現(xiàn),到實(shí)踐驗(yàn)證,測試程序,用法demo,全套的。

rt-thread 驅(qū)動(dòng)篇(一) serialX 框架理論
rt-thread 驅(qū)動(dòng)篇(二) serialX 理論實(shí)現(xiàn)
rt-thread 驅(qū)動(dòng)篇(三) serialX 壓力測試
rt-thread 驅(qū)動(dòng)篇(四)serialX 多架構(gòu)適配
rt-thread 驅(qū)動(dòng)篇(五)serialX 小試牛刀

使用篇

Q1. 串口通訊數(shù)據(jù)被分多次接收了,怎么辦?

首先說明,串口是一種流設(shè)備,無協(xié)議接口。它收到一個(gè)字節(jié)給你一個(gè)字節(jié),收到兩個(gè)字節(jié)給你兩個(gè)字節(jié)。如果你的數(shù)據(jù)是整齊的 16 個(gè)字節(jié),而且想每收 16 個(gè)字節(jié)串口驅(qū)動(dòng)給你個(gè)信號,這就難為人了。還有一種情況是,前后兩次不同的數(shù)據(jù)被拼接在一起了。

這個(gè)時(shí)候,需要我們在應(yīng)用層進(jìn)行處理?;蛘呤嵌ㄩL包,或者定義包頭包尾,包長度等等。下面給出我在論壇上多次分享過的代碼,這個(gè)是帶包頭包尾的,在這個(gè)基礎(chǔ)上可以修改成其它各種形式包協(xié)議的。

   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: %sn", "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: %dn", 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: %dn", adc_val.type);
                   continue;
               break;
               }
               rt_memcpy(datbuf, recvbuf+4, 7);                            // change str 2 float
               datbuf[7] = 0;
               rt_kprintf("%sn", datbuf);        // 數(shù)據(jù)域 ,可以是字符串,可以是十六進(jìn)制數(shù)據(jù)
               // 其它數(shù)據(jù)處理
               ....
               // prepare next package 準(zhǔn)備下一包
               if (tmp > 0) {
                   rt_memcpy(recvbuf, &recvbuf[dat_len + 3], tmp);
                   rcv_off = tmp;
               } else {
                   rcv_off = 0;
               }
               dat_len = 0;
           }
       }
   }

項(xiàng)目代碼,神明保佑,別被老板看到

Q2. 線程間傳輸不定長數(shù)據(jù)

有兩種消息機(jī)制可以傳輸數(shù)據(jù),郵箱和消息隊(duì)列。以下是一些使用建議:

  • 郵箱傳輸?shù)氖嵌ㄩL 32bit 數(shù)據(jù),或者是一個(gè)整型值,或者是一個(gè)地址;
  • 消息隊(duì)列的可伸縮性更強(qiáng),而且有隊(duì)列,消息體大小由用戶決定,但是,一經(jīng)初始化,消息體大小也是固定長度的了。
  1. 對于某些不同類型數(shù)據(jù),每種數(shù)據(jù)長度固定,而且各種類型數(shù)據(jù)長度差別不是很多的情況,我們可以使用聯(lián)合體代替結(jié)構(gòu)體。這樣消息體的長度也是固定的,以最長長度為準(zhǔn)。
  2. 用郵箱傳遞內(nèi)存地址,這樣不限定數(shù)據(jù)長度,但是要求每一次郵箱必須被接收方接收。發(fā)送方申請內(nèi)存,接收方釋放內(nèi)存。如果出現(xiàn)郵箱發(fā)送失敗,由發(fā)送方釋放內(nèi)存。
  3. 用消息隊(duì)列傳遞內(nèi)存地址,比郵箱的優(yōu)勢就在于它能緩存多個(gè)地址,降低發(fā)送失敗的風(fēng)險(xiǎn)。
  4. pipe 管道或 ringbuffer。pipe 內(nèi)部數(shù)據(jù)結(jié)構(gòu)也是 ringbuffer。雖然可以讀寫任意長度數(shù)據(jù),但是,這樣又將數(shù)據(jù)變成流了。需要讀取方根據(jù)事先約定的協(xié)議進(jìn)行解析拆分。還有個(gè)缺陷是它沒有消息機(jī)制,寫方需要單獨(dú)發(fā)消息通知接收方,或者,接收方死等這個(gè)數(shù)據(jù)。鑒于這種方式必須用鎖,不適合中斷和線程之間的數(shù)據(jù)傳輸。

Q3. 插上 U 盤怎么通知應(yīng)用程序?

[gitee](https://gitee.com/thewon/rt_thread_repo) 有完整代碼,主要修改在 hub.c 和 udisk.c 兩個(gè)文件

Q4. 怎么優(yōu)雅的掛載多種存儲(chǔ)設(shè)備?

內(nèi)存、片上 flash 、片外 spi flash、sd 卡、U盤... 各式各樣的的設(shè)備,掛載的文件系統(tǒng)也可能不一而足。怎么優(yōu)雅的把多種設(shè)備掛載到文件系統(tǒng)就是個(gè)需要考慮的問題了。

1. 掛載 rom 根文件系統(tǒng),同時(shí)創(chuàng)建其它可讀寫文件系統(tǒng)掛載點(diǎn)。
2. 其它設(shè)備分別掛載到 rom 文件系統(tǒng)的掛載點(diǎn)上。

Q5. rom 文件系統(tǒng)

rt-thread 源碼目錄下 “components/dfs/filesystems/romfs” 有個(gè) romfs.c 文件,是 rom 文件系統(tǒng)配置模板文件,拷貝它到你的應(yīng)用目錄下,修改 `_root_dirent` 定義??梢詣?chuàng)建只讀文件。

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},
};

有了只讀文件系統(tǒng),可以很方便擴(kuò)展掛載很多其它文件系統(tǒng)。

Q6. 絲滑掛載設(shè)備

嵌入式里很多存儲(chǔ)設(shè)備是焊接到電路板上的存儲(chǔ)芯片。如果我們有在存儲(chǔ)芯片上掛載文件系統(tǒng)的需求,出廠生產(chǎn)必須有方式對存儲(chǔ)設(shè)備進(jìn)行格式化。為此,可能難倒一大批流水線工人??梢允褂孟旅娴牧鞒踢M(jìn)行掛載。

   result = dfs_mount(mtd_dev->parent.parent.name, "/usr", "yaffs", 0, 0);
   if (result == RT_EOK)
   {
       rt_kprintf("Mount YAFFS2 on NAND successfullyn");
   }
   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 failedn");
           return -RT_ERROR;
       }
       rt_kprintf("Mount YAFFS2 on NAND successfullyn");
   }

掛載失敗,直接格式化,有些比較暴力,但是不需要人工格式化存儲(chǔ)設(shè)備了。

Q7. 如何自動(dòng)掛載文件系統(tǒng)

兩種方式:一種是通過配置,啟用 RT_USING_DFS_MNTTABLE 。這種方式需要使用者自己實(shí)現(xiàn)一個(gè)結(jié)構(gòu)體數(shù)組 `const struct dfs_mount_tbl mount_table[]` 。

另一種就是自己寫代碼掛載不同設(shè)備。我喜歡這一種,因?yàn)檫@樣我可以使用上一小節(jié)提到的設(shè)計(jì)。

Q8. assertion failed at function:rt_xxxxx

問題是我沒調(diào)用 `rt_xxxxx` 函數(shù)???!

這種問題分兩種:
一種是,確定這個(gè)函數(shù)在運(yùn)行中正常調(diào)用的,例如:(tid != RT_NULL) assertion failed at function:rt_applilcation_init,可以確定的是 `rt_applilcation_init` 函數(shù)運(yùn)作于線程調(diào)度器啟動(dòng)前,這個(gè)時(shí)候肯定不會(huì)是多線程非法寫了內(nèi)存引起的。可以確定是因?yàn)?`rt_thread_create` 函數(shù)調(diào)用返回了空指針。那么,問題來了,堆初始化成功了嗎?內(nèi)存有多大?
另外一種是,沒有調(diào)用那個(gè)函數(shù)的地方,但是提示這個(gè)函數(shù)參數(shù)檢測出錯(cuò)。這種情況大概率是 PC 指針飛了。走到了不應(yīng)該走到的位置。

定位問題方法請見下節(jié)。

Q9. hard fault on thread: xxx

考慮了很久,要不要把這個(gè)加進(jìn)來。出現(xiàn)這個(gè)錯(cuò)誤提示的可能性太多了。從現(xiàn)象上看也分兩類,一類比較確定的,程序走到這個(gè)位置必然出現(xiàn);一類不太確定,每次運(yùn)行可能現(xiàn)象不一樣。

  • 環(huán)境搭建問題,系統(tǒng)移植有缺陷引起的。
  • 線程棧太小,線程棧爆棧。
  • 數(shù)組越界、野指針、函數(shù)參數(shù)傳參錯(cuò)誤...
  • 邏輯性錯(cuò)誤,內(nèi)存釋放后還有可能被使用。多發(fā)生在外設(shè)的接收緩存上。

但是,上面這些只是扯淡,并不能定位到錯(cuò)誤位置。定位問題是個(gè)方法論范疇的概念。每個(gè)人都應(yīng)該有自己熟悉的一套做法,我的想法請見 rt-thread 工具講解系列(二) 之 如何排查系統(tǒng) bug

Q10. 怎么定義變量到指定內(nèi)存位置?

非 gcc 版
- 定義一個(gè)宏

#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
{
 ... /* 忽略其它內(nèi)容 */

 .sdram : 
 {
  KEEP(*(.sdram_section))
 } >SDRAM
}

- 定義一個(gè)宏

#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 ,距離地址還有查一點(diǎn)兒。當(dāng)多個(gè)變量放到同一個(gè) section 的時(shí)候,它們的順序就不保證了。這種情況只能多定義一些 section。

Q11. 結(jié)構(gòu)體字節(jié)對齊

__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 里可以這么寫,其它開發(fā)環(huán)境下有差異。gcc 不支持 `__packed` 的寫法
最后的寫法,可以指定按照 n 個(gè)字節(jié)對齊,n 是一個(gè)具體是常數(shù),常用的有 1 2 4 8 ...

Q12. unaligned access 是怎么出現(xiàn)的?

據(jù)我所知,當(dāng)取指令的時(shí)候要求比較嚴(yán)格,編譯器也往往把指令做了對齊處理。如果出現(xiàn)非對齊訪問,多半是 pc 指針異常了。
還有一種情況,有人說 ARMv7-M 架構(gòu)設(shè)計(jì)的時(shí)候,0xC0000000-0xDFFFFFFF 這個(gè)地址段默認(rèn)要求必須 4字節(jié)(數(shù)據(jù)總線寬度)對齊訪問。如果這片內(nèi)存有個(gè)壓縮的結(jié)構(gòu)體變量,對此變量讀寫也可能出現(xiàn) unaligned access 錯(cuò)誤。解決方法如下:
1. 修改 MPU 讓這個(gè)區(qū)域變成正常儲(chǔ)存器

   /* 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,這個(gè)方法行不通)

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


3. 取消未對齊訪問檢測
編譯器添加 `–no_unaligned_access` 編譯選項(xiàng)。
有人介紹這個(gè)選項(xiàng)的時(shí)候說“強(qiáng)制編譯對齊”或者“禁用未對齊訪問支持”,個(gè)人認(rèn)為這種說法不正確,因?yàn)?,結(jié)構(gòu)體還是按照咱們的想法按照字節(jié)對齊的,只是在訪問這個(gè)數(shù)據(jù)的時(shí)候換了種方式,不用4字節(jié)(數(shù)據(jù)總線寬度)對齊的方式訪問了。添加這個(gè)選項(xiàng),恰恰是支持了未對齊數(shù)據(jù)訪問。

支持未對齊數(shù)據(jù)訪問的基于 ARM 體系結(jié)構(gòu)的處理器,包括:

  • 基于 ARMv6 體系結(jié)構(gòu)的所有處理器
  • 基于 ARMv7-A 和 ARMv7-R 體系結(jié)構(gòu)的處理器。

不支持未對齊數(shù)據(jù)訪問的基于 ARM 體系結(jié)構(gòu)的處理器,包括:

  • 基于 ARMv6 以前版本的體系結(jié)構(gòu)的所有處理器
  • 基于 ARMv7-M 體系結(jié)構(gòu)的處理器。

Q13. STM32F767 怎么使用 PersimmonUI?

不止 STM32F767,可以使用 PersimmonUI 芯片可以很多,詳情見獨(dú)立文章 STM32F767 使用 PersimmonUI 及其它芯片使用可行性分析

結(jié)束語

本人能力有限,文中難免有錯(cuò)誤,或者方法錯(cuò)誤。望各位同仁不吝賜教。
拜謝拜謝

此寶典不定期更新,希望有朝一日能破萬條。

審核編輯:湯梓紅

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報(bào)投訴
  • 內(nèi)核
    +關(guān)注

    關(guān)注

    3

    文章

    1361

    瀏覽量

    40185
  • SDK
    SDK
    +關(guān)注

    關(guān)注

    3

    文章

    1020

    瀏覽量

    45699
  • RT-Thread
    +關(guān)注

    關(guān)注

    31

    文章

    1261

    瀏覽量

    39840
收藏 人收藏

    評論

    相關(guān)推薦

    RT-Thread記錄(、RT-Thread內(nèi)核啟動(dòng)流程)

    在前面我們RT-Thread Studio工程基礎(chǔ)之上講一講RT-Thread內(nèi)核啟動(dòng)流程.
    的頭像 發(fā)表于 06-20 00:30 ?4937次閱讀
    <b class='flag-5'>RT-Thread</b>記錄(<b class='flag-5'>二</b>、<b class='flag-5'>RT-Thread</b>內(nèi)核啟動(dòng)流程)

    【原創(chuàng)精選】RT-Thread征文精選技術(shù)文章合集

    rt-thread心法系列(一)那些你必須知道的幾類apirt-thread 心法系列) 使用寶典
    發(fā)表于 07-26 14:56

    RT-Thread編程指南

    RT-Thread編程指南——RT-Thread開發(fā)組(2015-03-31)。RT-Thread做為國內(nèi)有較大影響力的開源實(shí)時(shí)操作系統(tǒng),本文是RT-Thread實(shí)時(shí)操作系統(tǒng)的編程指南
    發(fā)表于 11-26 16:06 ?0次下載

    RT-Thread用戶手冊

    RT-Thread用戶手冊——本書是RT-Thread的編程手冊,用于指導(dǎo)在RT-Thread實(shí)時(shí)操作系統(tǒng)環(huán)境下如何進(jìn)行編 程。
    發(fā)表于 11-26 16:16 ?0次下載

    RT-Thread Smart 上手指南

    RT-Thread Smart(簡稱rt-smart)是基于RT-Thread操作系統(tǒng)衍生的新分支,面向帶MMU,中高端應(yīng)用的芯片,例如ARM Cortex-A系列芯片,MIPS...
    發(fā)表于 01-25 20:09 ?12次下載
    <b class='flag-5'>RT-Thread</b> Smart 上手指南

    RT-Thread全球技術(shù)大會(huì):螢石研發(fā)團(tuán)隊(duì)使用RT-Thread的技術(shù)挑戰(zhàn)

    RT-Thread全球技術(shù)大會(huì):研發(fā)團(tuán)隊(duì)使用RT-Thread的技術(shù)挑戰(zhàn) ? ? ? ? 審核編輯:彭靜
    的頭像 發(fā)表于 05-27 11:36 ?1269次閱讀
    <b class='flag-5'>RT-Thread</b>全球技術(shù)大會(huì):螢石研發(fā)團(tuán)隊(duì)使用<b class='flag-5'>RT-Thread</b>的技術(shù)挑戰(zhàn)

    RT-Thread全球技術(shù)大會(huì):Kconfig在RT-Thread中的工作機(jī)制

    RT-Thread全球技術(shù)大會(huì):Kconfig在RT-Thread中的工作機(jī)制 ? ? ? ? ? ? ? 審核編輯:彭靜
    的頭像 發(fā)表于 05-27 14:49 ?1493次閱讀
    <b class='flag-5'>RT-Thread</b>全球技術(shù)大會(huì):Kconfig在<b class='flag-5'>RT-Thread</b>中的工作機(jī)制

    RT-Thread全球技術(shù)大會(huì):在RT-Thread上編寫測試用例

    RT-Thread全球技術(shù)大會(huì):在RT-Thread上編寫測試用例 ? ? ? ? ? 審核編輯:彭靜
    的頭像 發(fā)表于 05-27 16:28 ?1439次閱讀
    <b class='flag-5'>RT-Thread</b>全球技術(shù)大會(huì):在<b class='flag-5'>RT-Thread</b>上編寫測試用例

    RT-Thread全球技術(shù)大會(huì):RT-Thread測試用例集合案例

    RT-Thread全球技術(shù)大會(huì):RT-Thread測試用例集合案例 ? ? ? ? ? 審核編輯:彭靜
    的頭像 發(fā)表于 05-27 16:34 ?2050次閱讀
    <b class='flag-5'>RT-Thread</b>全球技術(shù)大會(huì):<b class='flag-5'>RT-Thread</b>測試用例集合案例

    RT-Thread學(xué)習(xí)筆記 RT-Thread的架構(gòu)概述

    RT-Thread 簡介 作為一名 RTOS 的初學(xué)者,也許你對 RT-Thread 還比較陌生。然而,隨著你的深入接觸,你會(huì)逐漸發(fā)現(xiàn) RT-Thread 的魅力和它相較于其他同類型 RTOS
    的頭像 發(fā)表于 07-09 11:27 ?4460次閱讀
    <b class='flag-5'>RT-Thread</b>學(xué)習(xí)筆記 <b class='flag-5'>RT-Thread</b>的架構(gòu)概述

    RT-Thread文檔_RT-Thread 簡介

    RT-Thread文檔_RT-Thread 簡介
    發(fā)表于 02-22 18:22 ?5次下載
    <b class='flag-5'>RT-Thread</b>文檔_<b class='flag-5'>RT-Thread</b> 簡介

    RT-Thread文檔_RT-Thread 潘多拉 STM32L475 上手指南

    RT-Thread文檔_RT-Thread 潘多拉 STM32L475 上手指南
    發(fā)表于 02-22 18:23 ?9次下載
    <b class='flag-5'>RT-Thread</b>文檔_<b class='flag-5'>RT-Thread</b> 潘多拉 STM32L475 上手指南

    RT-Thread文檔_RT-Thread SMP 介紹與移植

    RT-Thread文檔_RT-Thread SMP 介紹與移植
    發(fā)表于 02-22 18:31 ?9次下載
    <b class='flag-5'>RT-Thread</b>文檔_<b class='flag-5'>RT-Thread</b> SMP 介紹與移植

    基于RT-Thread Studio學(xué)習(xí)

    前期準(zhǔn)備:從官網(wǎng)下載 RT-Thread Studio,弄個(gè)賬號登陸,開啟rt-thread學(xué)習(xí)之旅。
    的頭像 發(fā)表于 05-15 11:00 ?3776次閱讀
    基于<b class='flag-5'>RT-Thread</b> Studio學(xué)習(xí)

    RT-Thread v5.0.2 發(fā)布

    RT-Thread 代碼倉庫地址: ●? https://github.com/RT-Thread/rt-thread RT-Thread 5.0.2 版本發(fā)布日志詳情: ●? htt
    的頭像 發(fā)表于 10-10 18:45 ?1356次閱讀
    <b class='flag-5'>RT-Thread</b> v5.0.2 發(fā)布