rtthread編程及軟件包使用經驗

 

     本篇博文主要用於記錄在使用rt-thread系統中遇到的一些問題,經驗,軟件bug及使用技巧,長期更新。

     rt-thread操作系統版本:3.1.2或3.1.3

 

1、2019.11.25 modbus軟件包,mbrtu_m.c函數238行中/*RT_ASSERT(( eSndState == STATE_M_TX_IDLE ) || ( eSndState == STATE_M_TX_XFWR ));*/,此斷言刪除,由於Modbus主機在從機斷開時,主機eSndState可能等於STATE_M_TX_XMIT,觸發斷言,導致程序死機。

 

2、特別 注意rt thread默認工程中,rtconfig.h中開啓了RT_DEBUG宏,此宏會對程序中的斷言進行使用,在發佈程序中請關閉斷言,一旦發佈程序中發生了斷言,程序的某個功能模塊或任務將會死機。

3、軟件定時的最大定時時間一定要注意,查看rt_timer_start()函數中代碼,可以發現,RT_ASSERT(timer->init_tick < RT_TICK_MAX / 2);這條斷言已經限制了最大的定時時間爲0xffffffff/2個系統時鐘tick,如果創建定時器時,超過此值,啓動定時器時,程序在停止在這條斷言中,爲什麼tick值只能達到一半呢,因爲tick定義爲有符號數,負值表示永久延時。

4、pipe.c中第35行的pipe_fops_open函數中,當申請到fifo內存失敗時,要返回錯誤。同理rt_pipe_open函數也做同樣的修改。

     

if (device->ref_count == 0)
    {
        pipe->fifo = rt_ringbuffer_create(pipe->bufsz);

        /*申請內存失敗時要返回錯誤 zhaoshimin 20191203*/
        if(pipe->fifo == RT_NULL)
        {
            rt_mutex_release(&(pipe->lock));
            return -1;
        }
    }

5、timer.c函數中在459行的rt_timer_control函數中增加一個查詢定時器運行狀態的功能。如下圖所做的修改。當應用程序在使用定時器,有些情況需要查詢定時器的運行狀態,如果停止了就要啓動,此時就需要增加的這個功能

6、STM32F103系列芯片的IWG獨立看門狗,使用芯片內部的單獨的40KHz時鐘源,IWG功能在連接JLINK仿真器調試時,默認關閉不起作用,請注意。程序中增加看門狗設備後,餵狗程序建議創建一個次低優先級的線程用於餵狗,如果當應用程序中有任務掛掉後,餵狗線程無法運行,導致看門狗發生復位。空閒進程中也可以餵狗,使用鉤子函數的方式,但是調用的餵狗程序太過頻繁,所以不建議使用。

 

7、非阻塞socket通信 tcpclient.c函數中,原來官方提供的代碼,存在着內存泄露問題,使用前請仔細的閱讀代碼,搞清楚邏輯,最後再商用。否則程序長時間運行後就無法聯網了,我在實際應用中踩過坑。rtthread的其他的軟件包,使用時也請好好閱讀源碼,並且做詳細的測試,軟件包由大家供現,每個使用者的用法又不太完全一樣,難免有bug存在,使用時要特別注意。

 

8、物聯網設備的無線通信測試方法,編寫一個測試軟件,通過設備的接入平臺向設備發送數據。對於通信的測試方法:測試大量數據發送給設備,比如每隔10ms發送一包數據;測試發送大包數據,比如一包1000字節的數據;測試在當接入平臺與設備間通信異常,比如無線信號弱時,發送數據在平臺產生積壓後,無線信號變好後,大量收到平臺積壓數據。以上三種極端測試都要求設備軟件不死機,當異常數據消失後,還能正常接收數據並做出相應的處理。

 

9、sscanf函數的高級應用,sscanf函數可以實現從一個字符串中解析出指定的子字符串或數字,是做字符串處理時一個非常有用的函數,同時sscanf還支持正則表達式。sscanf最基本的功能可以查閱相關資料,這裏重點介紹他支持的正則表達式。

     %[a-z]表示選擇小寫字母, %[^a-z]表示選擇非小寫字母,^表示“非”, %*[0-9]表示跳過數字, *表示跳過。

    sscanf("Hello word", "%s", str);   str=Hello

    sscanf("Hello word zsm","%*s%s", str );  str= word

    sscanf("Hello word zSm", "%*[A-Z]%[a-z ]", str )  str=ello word z

    sscanf("123 Entering Passive Mode (3489,456,678,90,45,87)", "%d%*[^0-9]%d,%d,%d,%d,%d,%d",&a,&b,&c,&d,&e,&f,&g);    提取這個字符串中的數字,結果爲a=123, b=3489,c=456,d=678,e=90,f=45,g=87

    特別注意sscanf("123 Entering Passive Mode (489,456,678,90,45,87)", "%d%*[^0-9]%d,%d,%d,%d,%d,%d",&a,&b,&c,&d,&e,&f,&g);這個函數在keil 5中運行的出來的結果是錯誤的,即其中b只取到了9。經過修改代碼測試分析,keil中sscanf函數在執行這種%*[^0-9]時能成功的過濾了這個字符串中的空格,字母,括號,但是它會多過濾掉'48'這兩個字符,導致提取出來的b值錯誤。如下圖,我分析可能是keil中自帶的sscanf有問題。

    在keil中要正確提取這個字符串中數字的方法是使用:sscanf("123 Entering Passive Mode (489,456,678,90,45,87)", "%d%*[^(](%d,%d,%d,%d,%d,%d",&a,&b,&c,&d,&e,&f,&g);

 

10、有符號數和無符號數的比較大小時,編譯器會默認把有符號數當成無符號數處理,這在比較一個負數和一個正數的大小時就出錯了。在比較一個有符號數和無符號數時請對無符號做強制類型轉換。      

unsigned long  a = 234;      
signed    char b = -2;      
if(b < a)
{
    printf("b < a");
}
else
{
    printf("b > a"); 
}

/*以上的運行結果是 b < a   有符號數char b的值是-2,在32位處理器進行比較時中會擴展成32位有符號數據,即0xFFFFFFFFE,默認按無符號數進行比較 就是 b(0xFFFF FFFE) > a(234)*/


      

unsigned long  a = 300;      
signed    char b = -2;      
if(b < (signed long)a)
{
    printf("b < a");
}
else
{
    printf("b > a"); 
}

/*以上的運行結果是 b < a   有符號數char b的值是-2,在32位CPU中進行比較運算時的數據會自動擴展成32位有符號數據0xFFFFFFFE,對應的有符號數還是-2 ,按有符號數進行比較 就是 b(-2) < a(300)*/

11、memcpy函數,此函數根據輸入的參數,輸入的參數簡單時,編譯器會把這個函數直接優化成類似內聯函數的方式,直接生成彙編不進行函數調用操作,如代碼的示例。

unsigned char arr[10] = {1,2,3,4,5,6,8,9,10};
memcpy(&arr[2], arr, 5);

      在KEIL5 ARM編譯器下優化的結果如下圖,可以看出,memcpy這個函數直接優化成4句彙編代碼,彙編代碼對內存的複製操作是按字(4字節)來複制的,所以memcpy函數運行後數組中的內容是{1,2,1,2,3,4,3,8,9,10}

12、rt thread 3.1.3  lwip協議的操作系統接口部分驅動,  調用netdev_dhcp_enabled(netdev, RT_FALSE)關閉dhcp功能後,再調用netdev_dhcp_enabled(netdev, RT_TRUE)打開DHCP功能時,無法再進行ip地址的獲取的,解決辦法在ethernetif.c程序的168行,底層調用dhcp功能處增加dhcp功能打開與關閉的功能。代碼如下:

       13、rt-thread 3.1.3 的select功能中poll.c, 由於poll調用底層的設備驅動poll函數,此函數返回錯誤時,會導致select返回1,產生錯誤的結果,需要修改poll中的相關函數來處理,一共修改2處,如下

       

14、sal_socket.c文件中的sal_accept函數沒有處理netdev設備down的情況,增加上相關代碼,如下

15、dhcp功能在打開的情況下無法關閉,修改ethernetif.c程序中的底層lwip_netdev_set_dhcp函數,增加對dhcp功能的操作。

16、 lwip 2.0.2協議在斷開網線的情況下,執行關閉TCP連接的操作,停留在狀態FIN_WAIT_1狀態,

       連接建立時拔下網線,在msh接口,執行命令tcpserver --stop 關閉開發板上的服務器程序連接。再輸入netstate 查看lwip鏈接的狀態,發現鏈接並不能馬上關閉,如下圖,鏈接處於FIN_WAIT_1狀態,要過很長的時間才能關閉釋放。經過查看lwip的源程序,tcp.c程序中的981行 tcp_slowtmr函數完成了tcp連接的超時釋放處理,函數處理FIN_WAIT_1狀態的鏈接,根據tcp連接關閉的4次握手操作,服務器(開發板)發送FIN_WAIT_1後,等待客戶端(電腦)應答ACK進而轉入FIN_WAIT_2,由於網線斷開,服務器(開發板)程序收到不ack而處於FIN_WAIT_1狀態。tcp_slowtmr函數中處理這個狀態的超時是按照tcp數據重發邏輯進行的,即採用超時後指數級時間退讓再進行下一次數據發送,這個時間總的算下來,可達10多個分鐘或更久。

   
       經過查看lwip網站,此問題已經相關開發者在去年就提出過,bug的網址:http://savannah.nongnu.org/bugs/ ... m_id=56161#comment3
     
     開發者提出的修改建議並沒有得到lwip作者的贊同,原因是作者認爲這樣修改會違背tcpip協議的標準,但是這種bug確實在實際應用中容易出現,並且不能允許這個tcp連接長時間不釋放而無法建立新的連接。
     我個人建議採用討論中修改辦法,即在 tcp_slowtmr函數中增加對FIN_WAIT_1狀態的超時處理。如下圖標示的修改方法。

 

17、lwip的應用層線程的堆棧要設置大一些,在正常網絡連接建立的情況的堆棧的消耗達小於2048字節,當網線斷開時,線程不斷的嘗試建議連接的時,堆棧的消耗更大,會稍稍大於2048字節,並且產生了堆棧溢出,導致這個應用線程死機,並且阻塞住了lwip協議內部使用的信號量,引起lwip協議卡死,網絡不通了。所以建議堆棧設置爲3072,即3KB的大小。

 

 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章