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

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

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

rt-thread 驅(qū)動(dòng)篇(六)serialX弊端及解決方法

出出 ? 來(lái)源:出出 ? 作者:出出 ? 2022-06-20 11:43 ? 次閱讀

前言

控制臺(tái),做為一種人機(jī)交互接口,相較于其他接口(顯示器、網(wǎng)絡(luò)終端),可能是最簡(jiǎn)單的。它耗用資源少,容易配置,幾乎是任何芯片會(huì)自帶的外設(shè)。而且可以很容易和計(jì)算機(jī)建立連接。因而,串口控制臺(tái)可能是程序員進(jìn)行人機(jī)交互的首選。

啟用控制臺(tái),可以幫我們?cè)谙到y(tǒng)全速運(yùn)行時(shí)窺探系統(tǒng)運(yùn)行狀況??梢员O(jiān)測(cè)其它外設(shè)或者組件初始化過(guò)程。

一月份,筆者在論壇發(fā)的 serialX 串口驅(qū)動(dòng)反響很大,應(yīng)該會(huì)讓很多人眼前一亮(老王賣瓜)。當(dāng)初,決定費(fèi)力研究它的初衷很簡(jiǎn)單 —— 應(yīng)用和驅(qū)動(dòng)弱耦合、真阻塞非阻塞特性。這兩個(gè)月來(lái),筆者一直在實(shí)際項(xiàng)目中使用它,而且應(yīng)用到了控制臺(tái)上,同時(shí)發(fā)現(xiàn)了一些問(wèn)題。

控制臺(tái)串口問(wèn)題匯總

問(wèn)題一、任務(wù)調(diào)度器啟動(dòng)前 `rt_kprintf` 死循環(huán)到 tx 函數(shù)

問(wèn)題原因是,筆者打開(kāi)控制臺(tái)串口設(shè)定的 flag 是阻塞寫方式,無(wú)論是中斷發(fā)送還是 DMA 發(fā)送都依賴中斷。但是任務(wù)調(diào)度器啟動(dòng)前是關(guān)全局中斷的,這樣導(dǎo)致觸發(fā)發(fā)送失敗,發(fā)送緩沖區(qū)滿了以后再有寫動(dòng)作就會(huì)永久死到 tx 函數(shù)里。

> 應(yīng)對(duì)之策:任務(wù)調(diào)度器啟動(dòng)前只能使用 poll 發(fā)送模式。任務(wù)調(diào)度器啟動(dòng)后或臨啟動(dòng)時(shí)切換到中斷或 DMA 模式。

問(wèn)題二、出現(xiàn)異常后,`rt_kprintf` 無(wú)輸出

這個(gè)現(xiàn)象和上一條有點(diǎn)兒類似。不同的是,雖然全局中斷是開(kāi)著的,但是串口中斷優(yōu)先級(jí)不足,導(dǎo)致控制臺(tái)設(shè)備停止工作。

arm9 架構(gòu)上出現(xiàn) Undef SWI PAbt DBbt 等等 trap 后,串口外設(shè)中斷級(jí)別不足,導(dǎo)致這些 trap 中的 printf 輸出失效。

> 應(yīng)對(duì)之策:unset 控制臺(tái)串口設(shè)備,或者將控制臺(tái)串口切換到 poll 發(fā)送模式。

問(wèn)題三、`rt_hw_interrupt_disable` 之后 `rt_kprintf` 無(wú)輸出

這個(gè)和“問(wèn)題一”是一樣的,根本原因是,非 poll 模式必須有中斷它才能工作,關(guān)中斷以后設(shè)備停止工作。

> 應(yīng)對(duì)之策:盡量減少關(guān)中斷時(shí)間;避免在關(guān)中斷之后寫串口設(shè)備。

問(wèn)題四、遇到 `_rt_scheduler_stack_check` 也會(huì)停止輸出

因?yàn)?`_rt_scheduler_stack_check` 函數(shù)最后先關(guān)全局中斷,然后進(jìn)入 while 死循環(huán)。這個(gè)時(shí)候串口中斷肯定也失效了。

> 應(yīng)對(duì)之策:關(guān)全局中斷前,先 flush 串口設(shè)備。讓串口把 “stack overflow” 的提示信息輸出完。

問(wèn)題五、打斷點(diǎn)后 `rt_kprintf` 輸出不完整,部分?jǐn)?shù)據(jù)沒(méi)輸出到控制臺(tái)

因?yàn)?debug 斷點(diǎn)停止的時(shí)候,前邊 printf 緩存的數(shù)據(jù)可能還沒(méi)來(lái)得及送到串口移位寄存器,cpu時(shí)鐘被斷點(diǎn)打斷停止運(yùn)行了,導(dǎo)致部分?jǐn)?shù)據(jù)沒(méi)輸出。繼續(xù)運(yùn)行程序就可以出現(xiàn)剩余信息輸出。這是非阻塞設(shè)備的特性。

以上這些問(wèn)題是所有非 poll 非阻塞設(shè)備輸出都會(huì)遇到的現(xiàn)象。在 RTOS 系統(tǒng)里,應(yīng)用程序不可避免地要和中斷打交道,了解中斷對(duì)我們編程思想的影響很重要。


完整解決方案

rt_device 增加 flush 接口

flush 接口對(duì)帶緩存設(shè)備是極其有用的,無(wú)論是阻塞還是非阻塞模式,我們總有需求要求*在某個(gè)代碼節(jié)點(diǎn)設(shè)備的緩存已經(jīng)是空的*,*或者要求實(shí)現(xiàn)通信同步*。

`struct rt_device` 增加 `flush` 接口

struct rt_device
{
...
   rt_err_t  (*flush)  (rt_device_t dev);
...
};

serialX.c 添加 `flush` 回調(diào)函數(shù)實(shí)現(xiàn) `static rt_err_t rt_serial_flush(struct rt_device *dev)` ,用于等待串口驅(qū)動(dòng)層發(fā)送緩存發(fā)完數(shù)據(jù)。另外底層外設(shè)也增加 flush 接口,用于等待串口發(fā)送寄存器中的*最后一個(gè)字節(jié)數(shù)據(jù)*被搬到了移位發(fā)送寄存器中。

console 添加 unset flush 控制臺(tái)設(shè)備接口

void rt_console_unset_device()
{
   if (_console_device != RT_NULL)
   {
       /* close old console device */
       rt_device_close(_console_device);
       _console_device = RT_NULL;
   }
}
RT_WEAK void rt_hw_console_flush()
{
   /* empty console output */
}
void rt_console_flush()
{
#ifdef RT_USING_DEVICE
   if (_console_device == RT_NULL)
   {
       rt_hw_console_flush();
   }
   else
   {
       rt_device_flush(_console_device);
   }
#else
   rt_hw_console_flush();
#endif /* RT_USING_DEVICE */
}

有 set 也有 unset, 不是嗎? unset 是為了調(diào)用 `rt_hw_console_output` 而不是 `rt_device_write` 輸出打印信息。

`rt_console_flush` 既考慮啟用設(shè)備框架也考慮未啟用設(shè)備框架兩種情況。`rt_device_flush(_console_device)` 會(huì)調(diào)用上文的 `rt_serial_flush` ;`rt_hw_console_flush` 和 `rt_hw_console_output` 類似用于不使用設(shè)備框架,自定義 `rt_kprintf` 底層接口時(shí)要實(shí)現(xiàn)的。視實(shí)際情況實(shí)現(xiàn) `rt_hw_console_flush` 。例如 NUC970 UART 自帶了 FIFO ,需要實(shí)現(xiàn) `rt_hw_console_flush`

> 如果使用了 DMA 模式,底層實(shí)現(xiàn) flush 還是有點(diǎn)兒難度的。需要花點(diǎn)兒心思。

延遲 `rt_console_set_device` 調(diào)用

挪到任務(wù)調(diào)度器啟動(dòng)前,那么之前的控制臺(tái)輸出怎么實(shí)現(xiàn)?答案是使用 `rt_hw_console_output`。如上所說(shuō),第一次使用 poll 模式打開(kāi)控制臺(tái)串口,到這里臨啟動(dòng)任務(wù)調(diào)度器的時(shí)候再次用 中斷/DMA 模式打開(kāi)控制臺(tái)串口也可以。但是,多次用不同模式打開(kāi)同一個(gè)設(shè)備會(huì)引入另外的問(wèn)題,要不要先關(guān)閉上次的 open 呢?假如之前沒(méi)有打開(kāi)過(guò)呢?

   /* Set the shell console output device */
#ifdef RT_USING_CONSOLE
   rt_console_flush();
rt_console_set_device(RT_CONSOLE_DEVICE_NAME);
#endif
   /* start scheduler */
   rt_system_scheduler_start();

這時(shí)候,我們的 `rt_console_set_device` 可以用任何模式打開(kāi)控制臺(tái)串口設(shè)備

   if (rt_device_open(new_device, RT_DEVICE_OFLAG_RDWR | RT_DEVICE_FLAG_STREAM
                       | RT_DEVICE_FLAG_INT_RX
                       | RT_DEVICE_FLAG_INT_TX
                       ) == RT_EOK) {
       _console_device = new_device;
   }

或者,先用 poll 模式 set console device

       /* set new console device */
       if (rt_device_open(new_device, RT_DEVICE_OFLAG_RDWR | RT_DEVICE_FLAG_STREAM
                       ) == RT_EOK) {
           _console_device = new_device;
       }

當(dāng)?shù)诙?reset 的時(shí)候,需要先 unset (用到了上面提到的 `rt_console_unset_device`),因?yàn)?`rt_console_set_device` 不允許重復(fù) set 同一個(gè)設(shè)備,也沒(méi)法修改打開(kāi)設(shè)備的參數(shù)。寫另外一個(gè) set api 也就變的必要了

       if (rt_device_open(new_device, RT_DEVICE_OFLAG_RDWR | RT_DEVICE_FLAG_STREAM
                       | RT_DEVICE_FLAG_INT_RX
                       | RT_DEVICE_FLAG_INT_TX
                       ) == RT_EOK) {
           _console_device = new_device;
       }

但是,我為什么不喜歡這種方式呢?

1. board 初始化階段需要初始化系統(tǒng)時(shí)鐘、倍頻 cpu 時(shí)鐘、 `rt_hw_systick_init`、 `rt_system_heap_init`、 `rt_hw_pin_init`、還有 `rt_hw_usart_init` 設(shè)備,可能還有 `rt_console_set_device`。為了能第一時(shí)間使用上控制臺(tái)串口,`rt_hw_usart_init` 必須盡早執(zhí)行,然后是 `rt_console_set_device` 。
2. 但是 uart 設(shè)備可能用到動(dòng)態(tài)申請(qǐng)內(nèi)存,這樣就必然要求 `rt_system_heap_init` 先于 `rt_hw_usart_init` 。
3. 初始化系統(tǒng)時(shí)鐘、倍頻 cpu 時(shí)鐘、 `rt_hw_systick_init`、 `rt_system_heap_init`、 `rt_hw_usart_init` 。也只能這樣了,前邊幾步的串口打印需求就忽略了吧,`rt_system_heap_init`->`rt_memheap_init` 里的 `RT_DEBUG_LOG` 調(diào)試信息就忽略了吧。

如果不著急用串口設(shè)備,先簡(jiǎn)單初始化串口外設(shè),讓 `rt_hw_console_output` 以最快的速度工作起來(lái)。如此一來(lái),初始化流程可能就可以變成,初始化系統(tǒng)時(shí)鐘、倍頻 cpu 時(shí)鐘、 **`rt_hw_console_init`**、 `rt_hw_systick_init`、... 。后面是初始化順序都無(wú)關(guān)緊要了,而且所有的打印信息需求都可以滿足。最后在任務(wù)調(diào)度器啟動(dòng)前選擇某個(gè)串口設(shè)備做控制臺(tái)串口,將會(huì)避免前文說(shuō)到的*問(wèn)題一*。

進(jìn)入不可恢復(fù)狀態(tài)的處理

- 以 `_rt_scheduler_stack_check` 為例,關(guān)中斷前先 flush 控制臺(tái)。

   rt_console_flush();
   level = rt_hw_interrupt_disable();
   while (level);

- 還比如 SWI 異常,`rt_hw_cpu_shutdown` 也會(huì)關(guān)中斷,進(jìn)入 while 死循環(huán)。先 unset 控制臺(tái),使用 `rt_hw_console_output` 進(jìn)行 poll 輸出之后的輸出需求。

void rt_hw_trap_swi(struct rt_hw_register *regs)
{
   rt_console_unset_device();
   rt_hw_show_register(regs);   rt_kprintf("software interrupt\n");
   rt_hw_cpu_shutdown();
}

**注:為避免因中斷優(yōu)先級(jí),引起串口設(shè)備中斷得不到響應(yīng),在中斷響應(yīng)里切忌調(diào)用 `rt_console_flush` 函數(shù)**

結(jié)束

控制臺(tái)串口在系統(tǒng)中扮演著極其重要的角色,對(duì)其處理不當(dāng),會(huì)引起各種依賴問(wèn)題。有人就有疑慮了,做其它通信用時(shí) serialX 會(huì)不會(huì)存在同樣的隱患?筆者保證,您不在中斷響應(yīng)里調(diào)用 `rt_device_flush` 就不會(huì)出現(xiàn)以上所有列出來(lái)的問(wèn)題。

筆者下一篇計(jì)劃聊聊內(nèi)核啟動(dòng)流程的問(wèn)題,雖然之前發(fā)過(guò)一篇文章 [rt-thread 系統(tǒng)啟動(dòng)及 SysTick 初始化流程優(yōu)化可行性分析]( https://club.rt-thread.org/ask/article/2881.html ),里面提了一種可能的系統(tǒng)啟動(dòng)流程,當(dāng)時(shí)只是一種想法,并不系統(tǒng)。再寫一篇,筆者希望把需要考慮的問(wèn)題以及優(yōu)缺點(diǎn)系統(tǒng)化地說(shuō)明白,可能還會(huì)提及控制臺(tái)串口設(shè)備,以及控制臺(tái)對(duì)內(nèi)核啟動(dòng)流程的影響。


相關(guān)文章:

rt-thread 驅(qū)動(dòng)篇(一) serialX 框架理論

rt-thread 驅(qū)動(dòng)篇(二) serialX 理論實(shí)現(xiàn)

rt-thread 驅(qū)動(dòng)篇(三) serialX 壓力測(cè)試

rt-thread 驅(qū)動(dòng)篇(四)serialX 多架構(gòu)適配

rt-thread 驅(qū)動(dòng)篇(五)serialX 小試牛刀

審核編輯:湯梓紅

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

    關(guān)注

    12

    文章

    1199

    瀏覽量

    55269
  • 控制臺(tái)
    +關(guān)注

    關(guān)注

    0

    文章

    85

    瀏覽量

    10328
  • 串口
    +關(guān)注

    關(guān)注

    14

    文章

    1540

    瀏覽量

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

    關(guān)注

    31

    文章

    1261

    瀏覽量

    39840
  • serialX
    +關(guān)注

    關(guān)注

    0

    文章

    7

    瀏覽量

    800
收藏 人收藏

    評(píng)論

    相關(guān)推薦

    RT-Thread記錄(一、版本開(kāi)發(fā)環(huán)境及配合CubeMX)

    RT-Thread 學(xué)習(xí)記錄的第一文章,RT-Thread記錄(一、RT-Thread 版本、RT-Thread Studio開(kāi)發(fā)環(huán)境 及
    的頭像 發(fā)表于 06-20 00:28 ?5102次閱讀
    <b class='flag-5'>RT-Thread</b>記錄(一、版本開(kāi)發(fā)環(huán)境及配合CubeMX)

    RT-Thread NUC97x 移植 LVGL

    不涉及 rt-thread 驅(qū)動(dòng),但是它是 LVGL 和 rt-thread 的接口。LVGL 在 rt-thread 上運(yùn)行的基石。
    發(fā)表于 07-08 09:37 ?1460次閱讀

    基于RT-Thread的SPI通訊

    驅(qū)動(dòng)層的驅(qū)動(dòng)。(rt-thread的設(shè)備 I/O 模型有設(shè)備管理層、設(shè)備驅(qū)動(dòng)框架層、設(shè)備驅(qū)動(dòng)層),我寫過(guò)一
    的頭像 發(fā)表于 08-22 09:28 ?1632次閱讀

    RT-Thread ssd1306驅(qū)動(dòng)

    RT-Thread 驅(qū)動(dòng)ssd1306
    的頭像 發(fā)表于 04-21 10:08 ?26.4w次閱讀
    <b class='flag-5'>RT-Thread</b> ssd1306<b class='flag-5'>驅(qū)動(dòng)</b>

    RT-Thread設(shè)備驅(qū)動(dòng)開(kāi)發(fā)指南基礎(chǔ)—以先楫bsp的hwtimer設(shè)備為例

    RT-Thread設(shè)備驅(qū)動(dòng)開(kāi)發(fā)指南》書籍是RT-thread官方出品撰寫,系統(tǒng)講解RT-thread IO設(shè)備驅(qū)動(dòng)開(kāi)發(fā)
    的頭像 發(fā)表于 02-20 16:01 ?1564次閱讀
    <b class='flag-5'>RT-Thread</b>設(shè)備<b class='flag-5'>驅(qū)動(dòng)</b>開(kāi)發(fā)指南基礎(chǔ)<b class='flag-5'>篇</b>—以先楫bsp的hwtimer設(shè)備為例

    RT-Thread驅(qū)動(dòng)開(kāi)發(fā)指南進(jìn)階-動(dòng)手驅(qū)動(dòng)先楫未適配的外設(shè)LCD

    經(jīng)過(guò)上一的《《RT-Thread設(shè)備驅(qū)動(dòng)開(kāi)發(fā)指南》基礎(chǔ)--以先楫bsp的hwtimer設(shè)備為例》闡述,可以大致了解到RT-thread設(shè)
    的頭像 發(fā)表于 02-25 11:04 ?2238次閱讀
    <b class='flag-5'>RT-Thread</b><b class='flag-5'>驅(qū)動(dòng)</b>開(kāi)發(fā)指南進(jìn)階<b class='flag-5'>篇</b>-動(dòng)手<b class='flag-5'>驅(qū)動(dòng)</b>先楫未適配的外設(shè)LCD

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

    rt-thread 驅(qū)動(dòng)serialX弊端解決方法
    發(fā)表于 07-26 14:56

    RT-Thread編程指南

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

    RT-Thread Studio驅(qū)動(dòng)SD卡

    RT-Thread Studio驅(qū)動(dòng)SD卡前言一、創(chuàng)建基本工程1、創(chuàng)建Bootloader2、創(chuàng)建項(xiàng)目工程二、配置RT-Thread Settings三、代碼分析1.引入庫(kù)2.讀入數(shù)據(jù)四、效果驗(yàn)證
    發(fā)表于 12-27 19:13 ?20次下載
    <b class='flag-5'>RT-Thread</b> Studio<b class='flag-5'>驅(qū)動(dòng)</b>SD卡

    rt-thread 驅(qū)動(dòng)(五)serialX 小試牛刀

    終于來(lái)到了 serialX 的實(shí)踐,期待很久了。
    的頭像 發(fā)表于 06-16 11:29 ?4434次閱讀
    <b class='flag-5'>rt-thread</b> <b class='flag-5'>驅(qū)動(dòng)</b><b class='flag-5'>篇</b>(五)<b class='flag-5'>serialX</b> 小試牛刀

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

    RT-Thread 簡(jiǎn)介 作為一名 RTOS 的初學(xué)者,也許你對(duì) 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 簡(jiǎn)介

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

    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è)賬號(hào)登陸,開(kāi)啟rt-thread學(xué)習(xí)之旅。
    的頭像 發(fā)表于 05-15 11:00 ?3776次閱讀
    基于<b class='flag-5'>RT-Thread</b> Studio學(xué)習(xí)

    RT-Thread設(shè)備驅(qū)動(dòng)開(kāi)發(fā)指南》基礎(chǔ)--以先楫bsp的hwtimer設(shè)備為例

    一、概述(一)RT-Thread設(shè)備驅(qū)動(dòng)RT-Thread設(shè)備驅(qū)動(dòng)開(kāi)發(fā)指南》書籍是RT-thread官方出品撰寫,系統(tǒng)講解
    的頭像 發(fā)表于 02-24 08:16 ?1317次閱讀
    《<b class='flag-5'>RT-Thread</b>設(shè)備<b class='flag-5'>驅(qū)動(dòng)</b>開(kāi)發(fā)指南》基礎(chǔ)<b class='flag-5'>篇</b>--以先楫bsp的hwtimer設(shè)備為例