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

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

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

什么樣的幀頭是足夠特殊的數(shù)據(jù)呢?

lhl545545 ? 來源:玩轉(zhuǎn)單片機 ? 作者:玩轉(zhuǎn)單片機 ? 2020-06-09 09:34 ? 次閱讀

本篇文章包含以下內(nèi)容,很長,但干貨滿滿,就看你能吸收多少了(這將是魚鷹本階段公眾號技術(shù)分享的最后一篇收尾文章):

傳入參數(shù)指針

互斥鎖釋放順序

數(shù)據(jù)幀檢查

串口空閑

通信吞吐量

內(nèi)容很多,魚鷹慢慢寫,道友您也請慢慢看。

為了更好的理解接下來的知識點,魚鷹將設計一個串口框架,讓道友心中有一個參考方向。

本篇重點在于解決如何寫一個健壯、高效的串口接收數(shù)據(jù),發(fā)送與接收處理過程略講。

幀格式

先聊聊幀格式,一般來說,一個數(shù)據(jù)幀有以下幾部分內(nèi)容:

幀頭

幀頭用于分辨一個數(shù)據(jù)幀的起始,這個幀頭必須足夠特殊才行,因為它是分辨一個幀的起始,那么什么樣的幀頭是足夠特殊的數(shù)據(jù)呢?保證這個數(shù)據(jù)在一個幀內(nèi)最好只出現(xiàn)一次的數(shù)據(jù),那就是幀頭,比如0x55、0xAA之類的。而且最好有兩個字節(jié)以上,這樣幀頭才更加獨一無二。

但是數(shù)據(jù)域內(nèi)的數(shù)據(jù)你是沒辦法保障不包含和幀頭一樣的數(shù)據(jù)。

那么如果不湊巧,除了幀頭外其他部分也有這樣的兩個字節(jié)的幀頭,那會出現(xiàn)什么問題?

幾乎不會出現(xiàn)問題。因為一般來說數(shù)據(jù)都是一幀一幀發(fā)送的,只要你前面的數(shù)據(jù)幀傳輸正確,那么即使下一幀的數(shù)據(jù)中有和幀頭一樣的數(shù)據(jù)(包括幀頭)也沒有問題,因為幀頭判斷已經(jīng)在開始就判斷成功了,就不會繼續(xù)判斷后面的數(shù)據(jù)是否是幀頭了。

那么為什么說是幾乎,因為如果上一幀數(shù)據(jù)接收錯誤,那么程序必須再找一次幀頭才行(單字節(jié)接收時是如此,采用空閑中斷的話就不需要這么麻煩),這就導致找?guī)^的時候在幀頭數(shù)據(jù)之外尋找了,很可能這些數(shù)據(jù)就有幀頭。

但是即使幀頭數(shù)據(jù)之外的假幀頭真的存在,也沒關(guān)系,還有第二重保障,那就是校驗,即使找到了一個錯誤的幀頭,那么數(shù)據(jù)校驗這一關(guān)也很難過去,所以放寬心。

如果校驗也湊巧通過了,那還有第三重保障:幀尾。應該到不了這里吧,畢竟這比中彩票還難。

又要上一幀數(shù)據(jù)接收錯誤,還要當前幀除了幀頭之外還有幀頭,另外你還能跳過校驗的檢查(還有功能字、長度信息的檢查),太難了。所以只要通過了這些檢查,你就可以認為這個數(shù)據(jù)幀是可用的了。所以一幀數(shù)據(jù)接收錯誤,導致的問題最多只是丟失了這幀數(shù)據(jù),對后續(xù)接收是不會有影響的(前提是你這個接收程序設計的足夠好),發(fā)送端在發(fā)送超時后再發(fā)送一次即可,所以重發(fā)機制很重要。

事實上,如果你采用串口空閑中斷,幀頭、幀尾都可以不用,但一般來說,幀頭都會保留,幀尾可以不需要,這是為了當單片機沒有串口空閑中斷時考慮,當然也可能有其他考慮,所以幀頭得保留。

功能字

功能字主要用于說明該數(shù)據(jù)幀的功能,當然也可以作為函數(shù)指針的索引,一個索引值代表了一個具體功能,據(jù)此可找到對應的功能函數(shù)。

比如,設計一個函數(shù)指針數(shù)組,通過功能字進行索引,進而跳轉(zhuǎn)到對應的功能函數(shù)中處理。

特別注意的是,設計功能字的時候,要考慮兼容性,對數(shù)據(jù)幀的功能進行劃分,不要想到一個算一個,功能字也不要隨便安排,不然在以后增加數(shù)據(jù)幀的時候會很麻煩。

比如說,只有一個字節(jié)的功能字,前四位作為一個大類,后四位作為大類中具體類。這樣就可以將系統(tǒng)數(shù)據(jù)通信幀分為16個大類,每個大類下有16個可用的具體類,當你增加功能字的時候,就可以根據(jù)你的設計來確定屬于哪個大類了,然后再插入進去。這樣在管理、維護這些通信數(shù)據(jù)時你會發(fā)現(xiàn)很方便。

這個思想其實在ARM內(nèi)核的中斷系統(tǒng)和設計 uCOS II 任務優(yōu)先級的時候都有,而魚鷹在設計項目的通信協(xié)議的時候就是運用了這些思想。

什么樣的幀頭是足夠特殊的數(shù)據(jù)呢?

(圖片來源于《權(quán)威指南》)

長度

長度信息也是一個非常關(guān)鍵的數(shù)據(jù),別小看了它,因為它,魚鷹用了將近一個星期的時間才把一個HardFaul問題解決了,雖然這個程序bug不是我寫的(魚鷹一直用的是串口空閑接收方式,這個bug自然而然就跳過了),但確實很容易出錯。

因為它是決定了你這個數(shù)據(jù)域長度的關(guān)鍵信息(一般長度信息代表數(shù)據(jù)域的長度,而不包含其它部分長度),也是這個數(shù)據(jù)幀的長度信息(加上固定字節(jié)長度就是幀長度了),更是接收程序還要接收多少數(shù)據(jù)的關(guān)鍵信息(對于空閑中斷接收方式不算關(guān)鍵,這里的不關(guān)鍵是指不會造成程序異常問題)。

比如說你的程序剛好將幀頭、幀尾、功能字判斷完畢,然后中斷程序因為種種原因?qū)е聸]有及時接收串口數(shù)據(jù),那么你可能得到的就是錯誤的數(shù)據(jù),然后這個錯誤的長度數(shù)據(jù)就可能導致你的棧幀或者全局變量被破壞(單字節(jié)接收情況下就可能出現(xiàn),因為魚鷹碰到過),這是很嚴重的事情。所以在接收數(shù)據(jù)域的數(shù)據(jù)之前一定一定要判斷這個長度信息(空閑中斷除外)是否合法,不合法的話及時扔掉這幀數(shù)據(jù),開始下一幀的數(shù)據(jù)檢查。

所以為了保證及時接收數(shù)據(jù),最好采用DMA傳輸。

數(shù)據(jù)域

這個沒啥好說的,就是整個幀你真正需要發(fā)送的數(shù)據(jù)。而為了讓你的發(fā)送函數(shù)能接收各種類型的數(shù)據(jù),那么把參數(shù)類型設置為 void * 會是不錯的選擇。

校驗

一個數(shù)據(jù)在接收過程中可能會被干擾,導致接收到錯誤的數(shù)據(jù),那么如何保證這幀數(shù)據(jù)的完整與準確性呢,就在校驗這一關(guān)了。

校驗有很多方式,和校驗、CRC校驗等(奇偶校驗是針對一個字節(jié)的,不是數(shù)據(jù)幀)。

和校驗算法簡單,CPU運算量小,累加最后只取最低字節(jié)即可(注意不是高字節(jié),想想為什么),或者保存累加和的變量就是一個字節(jié)空間,這樣就不需要額外操作了。

CRC校驗,這個算法復雜,理解起來比較困難,但一般來說可以直接拿來用,因為它是對每一位(bit)進行校驗,所以糾錯率很高,幾乎不存在發(fā)現(xiàn)不了的數(shù)據(jù)錯誤,但正因為對每一位進行檢查,所以CPU運算量較大,但是有的單片機是可以硬件計算CRC校驗值的(比如stm32)。不過現(xiàn)在CPU運算速度都挺快的,軟件運算也是可以接受的。

那么該怎么校驗呢?是從幀頭開始到數(shù)據(jù)域部分,還是說直接校驗數(shù)據(jù)部分?其實都可以,區(qū)別就是運算量問題,不過問題不大(最好是從頭開始校驗,以保證整幀數(shù)據(jù)的準確性)。

幀尾

前面說了,幀尾在空閑中斷中可以不用,RXNE中斷接收時其實也可以不用,當然也可以加上,好處就是當你用串口助手查看數(shù)據(jù)流時,可以觀察出一幀數(shù)據(jù)是否發(fā)送完整了。

最后再說說為什么在數(shù)據(jù)域前面設計四個字節(jié)大小,除了協(xié)議本身需要外,還有一個原因就是強制類型轉(zhuǎn)化需要,我們知道,一般來說,賦值時都有字節(jié)對齊的限制(實際上有的CPU可以不對齊進行賦值),stm32是32位的,那么四字節(jié)對齊是最合適的,這樣就可以直接將我們收到的數(shù)據(jù)轉(zhuǎn)化為需要的數(shù)據(jù)類型了。

傳輸過程

聊完了幀格式,再從大的方向看串口的傳輸過程:

當發(fā)送端發(fā)送第一幀數(shù)據(jù)包時,接收端通過某種方式接收(串口接收非空RXNE中斷、串口空閑IDLE中斷),為了讓串口能夠觸發(fā)空閑中斷,必須在發(fā)送端兩個發(fā)送幀之間插入一段空閑時間(就是在此時間內(nèi)不發(fā)數(shù)據(jù),紅色部分),保證空閑中斷的準確觸發(fā)。

同理,為了讓發(fā)送端也能正常接收接收端的數(shù)據(jù),也需要控制接收端的發(fā)送,不能在返回一幀數(shù)據(jù)時立馬發(fā)送下一幀數(shù)據(jù),不然觸發(fā)不了發(fā)送端的空閑中斷。

事實上,有些程序員設計的發(fā)送、接收過程比這個簡單一些。即只有當接收端接收到一幀數(shù)據(jù)并返回一幀數(shù)據(jù)之后,發(fā)送端才能繼續(xù)發(fā)送數(shù)據(jù),這樣一來,我們只需要控制好接收端的頻率,就可以控制整個通信過程,也能控制通信頻率。

但為什么還要設計成第一種傳輸情況呢?這是為了充分利用串口,增大數(shù)據(jù)吞吐率(這個后面再說)。

另外,不知道你是否觀察到圖中的每個數(shù)據(jù)幀占用的時間是不一樣的,這是因為每個數(shù)據(jù)幀不可能都是一樣長的,它們是不定長的數(shù)據(jù)包,所以你的定時不能從發(fā)送開始定時,而是從發(fā)送完成后開始定時控制空閑時間。

軟件設計

上面所有的內(nèi)容都是設定一些條件、需求,那么該如何實現(xiàn)軟件設計呢?畢竟說的再多,如果不能實現(xiàn)這些,又有什么用呢?talk is cheap, show me the code。

下圖設計了三個數(shù)據(jù)幀:GetVision(),GetSN(),GetMsg()。

什么樣的幀頭是足夠特殊的數(shù)據(jù)呢?

GetVision()用于獲取硬件版本號、軟件版本號。

GetSN()用于獲取產(chǎn)品序列號,用于識別唯一設備。

GetMsg()用于獲取消息,可以獲取各種傳感器數(shù)據(jù),事實上,如果數(shù)據(jù)量多的話,根據(jù)傳感器的不同,會根據(jù)需要設計各種不同的數(shù)據(jù)幀(功能字不同)。

在軟件設計上一般都會對這些函數(shù)設計一個統(tǒng)一的函數(shù)類型,用函數(shù)指針數(shù)組統(tǒng)一管理。

既要統(tǒng)一,又要體現(xiàn)差異性,函數(shù)參數(shù)就顯得很有必要了。

這里設計了兩個參數(shù),一個是void* (無類型指針),一個是length(長度)。

無類型指針主要是用于傳遞數(shù)據(jù)域的數(shù)據(jù)地址,而數(shù)據(jù)域的數(shù)據(jù)可能是整型、浮點型、結(jié)構(gòu)體、枚舉、聯(lián)合體等,為了保證傳入的各種數(shù)據(jù)類型在不通過強制轉(zhuǎn)化情況下都能兼容,設計為 void * 就顯得很有必要了。

實際上為了顯得更專業(yè)性,加上 const 修飾會是不錯的選擇,因為這可以保證緩存數(shù)據(jù)不被修改(事實上只能保證不被程序員修改,而不能保證程序本身,這個后面會解釋)。

長度,長度參數(shù)是一個很關(guān)鍵的參數(shù),為了保證長度的準確性,建議使用sizeof 獲取。

有人覺得sizeof 好像一個函數(shù),會不會導致效率低下啊,畢竟每次通信都要計算一次長度,那你就大特大錯了。事實上,只要你的類型定義定義好了(不管是內(nèi)置的類型定義還是自定義的結(jié)構(gòu)體、枚舉、聯(lián)合體),編譯器都能確定 sizeof 最終的數(shù)據(jù)長度,根本不存在計算過程。

用 sizeof 的兩個好處:

1、可以忽略字節(jié)對齊問題(不同平臺不能忽略,比如window和單片機通信)。因為編譯器為了數(shù)據(jù)讀寫效率更高,一般會對數(shù)據(jù)進行地址對齊,這樣一來手工計算一個數(shù)據(jù)類型的長度變得麻煩(當然你可以說使用某些手段讓數(shù)據(jù)不進行對齊,這個另說),而 sizeof 將智能且準確計算數(shù)據(jù)大小。

2、當你使用 sizeof 時,兼容性更強,也顯得更專業(yè)。程序修修改改很正常,一個數(shù)據(jù)結(jié)構(gòu)改來改去也很正常,特別是開發(fā)初期更是如此。但是不管你怎么改,只要在編譯器看來是固定長度的數(shù)據(jù)類型,那么 sizeof 就能在鏈接程序前計算出來;并且即使你后來加了數(shù)據(jù)不對齊的限制(加了這個限制后,很可能數(shù)據(jù)大小變?。材軠蚀_計算。別問為什么,就是這么任性。

所以為了減小出錯的可能性、減少勞動量,sizeof 是不錯的選擇。

當接收到數(shù)據(jù)地址和長度信息后,就可以進行發(fā)送了。

因為只有數(shù)據(jù)域的數(shù)據(jù),為了組成一幀完整的數(shù)據(jù),就必須加入打包過程。加上數(shù)據(jù)幀頭、功能字、數(shù)據(jù)長度、校驗等數(shù)據(jù)。

當一幀數(shù)據(jù)打包好之后,就可以進行發(fā)送了,發(fā)送可以采用循環(huán)查詢發(fā)送,也可使用發(fā)送空TXE中斷,當然還是建議使用DMA發(fā)送,這樣你可以還沒等它發(fā)送完就可處理其它事情了。

以上就是發(fā)送過程,接收過程也是同理,根據(jù)功能字來調(diào)用相應的函數(shù)進行回復。

事實上,如果只是數(shù)據(jù)的傳輸過程,完全可以使用一個發(fā)送函數(shù)實現(xiàn)數(shù)據(jù)的特異性傳輸,這樣就可以減少一層數(shù)據(jù)傳遞,但是有些通信幀不只是數(shù)據(jù)的傳輸,可能在接收、發(fā)送時作一些其他處理(比如清除、設置某些標志位),所以需要再增加一層,用于進行差異性處理。

以上就是本篇內(nèi)容的基礎(chǔ)內(nèi)容了,你以為快完了?你錯了,現(xiàn)在只是剛開始而已,魚鷹寫本篇筆記的最終目的還在后面。

這只是前菜,正文才剛開始。

串口接收遇到的那些問題

以下內(nèi)容不會用太多的筆墨描述如何寫發(fā)送、接收函數(shù),而是重點關(guān)注串口接收過程中可能遇到的一些問題,如果說描述到了發(fā)送、接收函數(shù),別會錯意,順帶的。

以下大部分問題都是因為采用RXNE(接收不為空)中斷方式導致的問題,只有一個問題是魚鷹從前沒有考慮到,也是IDLE + DMA方式不可忽略的問題。

這就是為什么魚鷹建議采用IDLE + DMA 的原因,不僅是因為效率問題,更因為它能避免很多問題,當然水平足夠高的話,采用RXNE也是完全(“完全”就未必,里面有一個問題是RXNE方式難以避免的問題)沒有問題。

事實上,即使魚鷹采用RXNE方式接收數(shù)據(jù),也能避免以下大部分的問題,因為魚鷹的基礎(chǔ)足夠扎實,會在一開始編寫代碼的時候自然而然避免一些問題的出現(xiàn)。

但是看完以下內(nèi)容后,相信各位道友寫出一個高效且健壯的串口接收程序根本不是問題,因為這就是所謂的經(jīng)驗啊。

傳入?yún)?shù)指針

前面魚鷹已經(jīng)提到了需要一個指針作為函數(shù)的參數(shù),這里說說這個指針問題。

我們知道,為了維護方便,也是為了節(jié)省空間,一般都會將類似的功能整合成一個函數(shù),比如串口經(jīng)常要用的發(fā)送、接收功能,但是所發(fā)送的數(shù)據(jù)內(nèi)存空間可能就處于五湖四海了,他們通過指針來指向?qū)⒁l(fā)送的數(shù)據(jù)。

為了節(jié)省 RAM 空間或者其它不為人知的原因,傳遞給發(fā)送函數(shù)的指針就是實際發(fā)送數(shù)據(jù)的地址,并且在計算校驗值的時候也是直接使用這個地址進行校驗計算,然后采用循環(huán)查詢的方式發(fā)送數(shù)據(jù),這樣一來,就不必拷貝一個數(shù)據(jù)的副本進行發(fā)送,而是直接從數(shù)據(jù)源的地址進行發(fā)送,節(jié)省了部分 RAM 空間。

但是這樣真的好嗎?

你是否考慮過在計算數(shù)據(jù)幀校驗值的時候,數(shù)據(jù)源改變了的問題呢?

比如說你采用和校驗,數(shù)據(jù)一開始是0x55,計算數(shù)據(jù)幀的校驗和值為tx_sum,然后被中斷程序或者DMA修改了這個數(shù)據(jù)源,變成了 0xAA,此時你再使用這個數(shù)據(jù)地址進行發(fā)送,接收端接收到了0xAA,接收端計算校驗和的時候是 rx_sum,那么rx_sum 必然不等于 tx_sum,然后接收端就認為該數(shù)據(jù)幀是錯誤的,然后丟失這幀數(shù)據(jù),而這種情況是比較少見的,但確實是會偶爾出現(xiàn)接收錯誤的情況(當時發(fā)現(xiàn)這個問題時始終不得其解,明明我發(fā)送的是這個校驗值,為什么你計算的校驗值是另一個?開始懷疑是校驗函數(shù)的問題,但其他數(shù)據(jù)幀計算時沒有問題,只有一種數(shù)據(jù)幀會出現(xiàn)問題,然后魚鷹懷疑是數(shù)據(jù)源的問題,是的,魚鷹很快就懷疑數(shù)據(jù)源的問題,但當時驗證時,只改了校驗那部分地址,發(fā)送時的地址還是使用源地址,導致問題還是沒解決,過了好久之后才發(fā)現(xiàn)這個發(fā)送地址沒改,囧。所以說,即使你的思路是對的,但如果你解決時錯了,問題也很嚴重)。

如果說接收端(從機)具有重發(fā)機制,那么問題不是很大,丟失一幀數(shù)據(jù)而已,再重發(fā)就是,但事實是,一般串口設計成主從模式,主機會在沒有接收到從機的應答數(shù)據(jù)時會進行重發(fā),但是從機一般不會主動重發(fā)數(shù)據(jù)的,它無法判斷主機是否成功接收,而從機一般會在成功發(fā)送完數(shù)據(jù)后開始清除一些標志位(比如鍵盤按鍵數(shù)據(jù)清空,不然主機下次獲取按鍵信息時還是同一個按鍵數(shù)據(jù)),事實上這個動作必須在對方成功接收才能進行(否則這次按鍵信息就丟失了),從這個角度來看,我們必須設計一個機制用于判斷主機是否成功接收。

I2CCAN總線都有應答信號,但這是這些是總線自帶的特性,我們不可能在接收到一個字節(jié)后發(fā)送一個應答信號給主機,那么是否有其他辦法呢。

人們很容易想到的一個辦法就是在主機收到正確數(shù)據(jù)后,主動發(fā)送一幀專用數(shù)據(jù)幀用于清除這個標志(這個幀和普通幀一樣,所以可以確保主機數(shù)據(jù)能準確送達從機,因為如果超時沒有送達,會觸發(fā)重發(fā)機制)。這樣只要在獲取完這幀數(shù)據(jù)后,再額外的發(fā)送一幀數(shù)據(jù)用于對方確認即可,從機接收到后,即可開始著手清除一些標志位。

但這樣會有一個問題,因為這種特殊的需要從機確認的數(shù)據(jù)包(其他類型數(shù)據(jù)不需要確認是因為如果主機沒有正確收到數(shù)據(jù)還可以繼續(xù)獲取,獲取的數(shù)據(jù)是一樣并沒有關(guān)系,但這種需要從機確認,一旦從機認為發(fā)送成功了,數(shù)據(jù)就被清除了這種情況就需要確認,典型的就是按鍵信息了),我們需要額外處理并占用發(fā)送帶寬。這是魚鷹不愿忍受的。

那么是否有更好的辦法?

或許我們可以從 USB 協(xié)議中獲得啟發(fā)(這是在寫這篇筆記的時候想到的,當時寫按鍵板代碼的時候沒有想到過,但因為當時測試時傳輸成功率100%,所以就放棄處理這種情況了)。

USB協(xié)議是典型的主從機制,主機不主動獲取數(shù)據(jù),從機是無法主動發(fā)送數(shù)據(jù)的。那么從機是如何確定對方成功接收數(shù)據(jù)了呢?

一個bit的翻轉(zhuǎn)位。

每當主機成功發(fā)送一幀數(shù)據(jù)后再發(fā)送下一幀數(shù)據(jù)時,就會翻轉(zhuǎn)這個位,從機就可以根據(jù)這個位判斷主機是屬于重發(fā)數(shù)據(jù)(重發(fā)數(shù)據(jù)表示主機接收失敗了)還是新數(shù)據(jù)了,這樣從機就能從下一幀數(shù)據(jù)確定上一幀數(shù)據(jù)是否成功發(fā)送了。

而主機發(fā)送的數(shù)據(jù)是由從機發(fā)送應答包確定的,和上面的串口協(xié)議類似,所以這個方向的數(shù)據(jù)是沒有問題的。

那么我們該如何重新設計這個協(xié)議呢?可以盡可能的不改變原來協(xié)議的情況下實現(xiàn)嗎?

或許可以從功能字出發(fā)。

為了保證對功能字的原有定義保持基本不變,使用最高 bit 作為這個特殊的位,這個 bit 開始是 0,之后主機每接收一個從機應答數(shù)據(jù)就進行翻轉(zhuǎn),如果因為沒有接收到從機的應答數(shù)據(jù),就會使用相同的翻轉(zhuǎn)位重復發(fā)送;而從機也根據(jù)這個bit來確定自己的上一幀數(shù)據(jù)對方是否接收(對比上一幀數(shù)據(jù)的翻轉(zhuǎn)位),如果主機沒有成功接收,就不清除標志位(之后主機會重發(fā)數(shù)據(jù)再次獲取),否則清除標志位,。

因為是魚鷹剛想到的,就不多說了,僅提供一個思路。

現(xiàn)在回到指針那塊的問題。

現(xiàn)在已經(jīng)知道,如果你在計算校驗和與發(fā)送的過程中出現(xiàn)源數(shù)據(jù)改變的情況,就會導致數(shù)據(jù)幀校驗失敗,那么有什么解決辦法?

如果說你堅持使用查詢方式發(fā)送來節(jié)省部分空間,那么只要在計算校驗值之前拷貝一份源數(shù)據(jù),然后用這份數(shù)據(jù)計算并發(fā)送即可。

另一種方法就是,直接把整幀數(shù)據(jù)拷貝到一個數(shù)據(jù)緩存中,使用DMA發(fā)送。

現(xiàn)在還有一個問題,那就是如果我想發(fā)送一個數(shù)據(jù)域為空的數(shù)據(jù)該怎么發(fā)送?

一般來說,在使用指針的時候,不會使用 NULL 空指針,但是在數(shù)據(jù)為空的情況下,就需要使用 NULL 指針了,并長度設置為 0,這個時候在檢查指針的時候,不能看到空指針就退出函數(shù),還要判斷長度信息,當長度為0時在打包時就不拷貝源數(shù)據(jù),但最終還是要發(fā)送數(shù)據(jù)幀的(當時別人寫的代碼將指針和長度判斷同時放到了 for 循環(huán)的條件里面,魚鷹覺得效率太低,導致修改代碼是沒考慮指針為空的情況,所以導致了一個小bug)。

互斥鎖釋放順序

現(xiàn)在考慮第二個問題:互斥鎖釋放順序問題。

如果沒有采用隊列方式接收數(shù)據(jù),而是主機發(fā)送完成后等待接收從機數(shù)據(jù)后再發(fā)送下一幀數(shù)據(jù),那么該如何處理互斥鎖的問題?

我們知道為了保證數(shù)據(jù)的同步,保證在接收到一幀數(shù)據(jù)進行處理時,不能被新的數(shù)據(jù)幀沖掉,這時就要加入一個互斥鎖,表示我正在處理數(shù)據(jù),下面的數(shù)據(jù)我接收不了,這樣就能保證你正在處理的數(shù)據(jù)不會被新來的數(shù)據(jù)修改掉,從而進行正確的處理。

那么這個標志位(互斥鎖)該什么時候清掉(釋放掉)呢?

一般來說標志位,一般越早清掉越好,比如外部中斷標志位,進入中斷后,一般首先會清理標志位,這樣即使你正在處理本次中斷的程序,那么即使這時再來了中斷,也不會丟失中斷信息(有懸起標志位),這樣就可以在處理完這次中斷后,立馬進行下一次中斷的處理了(前提是優(yōu)先級足夠高)。

但是如果你清理太早或者清理太晚會怎樣?

比如說你在接收到一幀數(shù)據(jù)后(數(shù)據(jù)幀所有檢查完成),開始設置標志位,當主程序查詢到這個標志時(一般數(shù)據(jù)處理不會放在中斷中),如果馬上開始清除這個標志……嗯,一般來說不會有問題。

那么什么時候會出現(xiàn)問題?當你的主程序查詢到這這個標志時開始清除標志,然后處理、返回數(shù)據(jù)給主機,如果此時主機超時重發(fā)數(shù)據(jù)時,,因為這個時候你雖然在處理數(shù)據(jù),但是因為你的標志位已經(jīng)被清除了,所以接收程序就會開始往接收緩存區(qū)存數(shù)據(jù)了,當你存完之后再回到數(shù)據(jù)處理那里,你的緩存區(qū)可能就不是你想要的數(shù)據(jù)了。

可能你會說,既然是重發(fā),那么數(shù)據(jù)應該是相同的吧?好吧,你贏了,魚鷹編不下去了,這種情況(有重發(fā)機制)下清理太早好像是不會出現(xiàn)問題,但你怎么知道對方是采用副本進行重發(fā)數(shù)據(jù)的呢,如果重發(fā)時它又從源數(shù)據(jù)中拷貝后再進行重發(fā)會出現(xiàn)什么問題?比如時間信息,開始第一幀數(shù)據(jù)是11:59,CPU剛把11拷貝到用戶空間,被串口中斷程序打斷,導致下一幀接收的數(shù)據(jù)是12:00,此時回到主程序繼續(xù)拷貝,拷貝00,數(shù)據(jù)的完整性被破壞,這樣導致的結(jié)果就是11:00,而實際上時間是12:00,這就是你打斷數(shù)據(jù)處理過程的后果。

現(xiàn)在再說說清理太晚會怎么樣。

當你的主程序查詢到這個標志后,暫時不清除,而是等到從機發(fā)送完應答數(shù)據(jù)之后再清除標志,此時因為從機采用查詢方式(查詢方式表明從機發(fā)送完最后一個字節(jié)后后開始清除標志位,也就代表了主機就差最后一個字節(jié)沒有接收了,這樣發(fā)送和清除之間間隔時間較短,而采用 DMA 方式的話,發(fā)送和清除的間隔時間更短,因為可能DMA還沒開始發(fā)送第一幀數(shù)據(jù),清除工作就已經(jīng)完成了),或者因為其他原因(比如中斷處理)導致發(fā)送和清除之間的時間很長這種特殊情況,這樣可能主機已經(jīng)開始下發(fā)下一幀數(shù)據(jù)了,但是因為此時標志還沒有清除,不能接收數(shù)據(jù),所以主機這一幀數(shù)據(jù)就這樣丟失了。

什么樣的幀頭是足夠特殊的數(shù)據(jù)呢?

那么這個清除標志位最合適的時機是什么時候?

你鎖定資源利用完的時候。

現(xiàn)在來看看,這個互斥鎖鎖定的是什么資源?對,就是接收緩存,那么接收緩存什么時候用完?當然是在數(shù)據(jù)處理完成之時。也就是說在數(shù)據(jù)處理完之前、發(fā)送數(shù)據(jù)之前清除最合適。

這樣就不會因為處理其他事情而導致清除操作過晚而丟失下一幀數(shù)據(jù)了,因為此時主機還沒收到從機上傳的數(shù)據(jù),也就不會馬上開始下一幀數(shù)據(jù)的傳輸了。

說到清除,你覺得,需要把整個緩沖區(qū)進行清零操作嗎?這個問題在以往的文章解釋過,就看你是否看完了。

數(shù)據(jù)幀檢查

你是否會對接收的數(shù)據(jù)進行檢查?如果不進行檢查會發(fā)生什么?

我們知道一幀數(shù)據(jù)中,每個部分都有各自的含義,甚至有些部分可能在某些數(shù)據(jù)幀中不存在,比如數(shù)據(jù)域部分,我們需要根據(jù)長度信息來判斷數(shù)據(jù)域部分是否存在。

但是你能保證你所接收的數(shù)據(jù)都是準確的嗎?你能確保在工作環(huán)境下不會因為各種干擾導致數(shù)據(jù)長度信息由0x05變成0x85(最高位翻轉(zhuǎn))嗎?,如果出現(xiàn)了會導致什么后果?

假設你采用RXNE中斷方式來一個、一個字節(jié)的接收數(shù)據(jù),分析如下:

因為是單字節(jié)接收數(shù)據(jù),所以你需要把所有接收的數(shù)據(jù)當成數(shù)據(jù)流,根據(jù)幀頭信息來確定幀的開始,一旦確定幀頭信息之后,你就可以根據(jù)接下來的一系列數(shù)據(jù)保證一幀數(shù)據(jù)的結(jié)束,同時開始新幀的接收……

什么樣的幀頭是足夠特殊的數(shù)據(jù)呢?

初看這個接收流程沒有問題,但是真的如此嗎?

但是就像前面所說,你能保證你的數(shù)據(jù)沒有問題嗎?如果說你接收到一個長度信息,本來是0x05,但是最終接收的數(shù)據(jù)是0x85,這就意味著你接下來的數(shù)據(jù)域的長度是0x85,根據(jù)你的接收流程,你需要再接收0x85個字節(jié)之后,才能判斷校驗字節(jié)是否正確。

可能你會說,雖然你的長度信息由0x05變成了0x85,之后接收校驗過程肯定是失敗的,那么這幀數(shù)據(jù)就會被接收程序丟棄,從而導致接收程序進入重新尋找?guī)^的流程,這個過程不是挺正常的嗎?按理說上述異常情況是能被接收流程處理掉的。

那么首先確認一點,上述異常能被接收流程處理嗎?

答案是能!

既然上述異常是能被接收狀態(tài)機處理的,那么還會有什么問題?

問題就出在這個錯誤數(shù)據(jù)本身!

因為你是根據(jù)錯誤數(shù)據(jù)來決定接下來需要接收多少數(shù)據(jù),而一般來說,接收緩存大小設置為最大幀的長度,那么就出現(xiàn)一個問題,你的緩存夠你接收0x85個字節(jié)嗎?

如果說你開辟的接收緩存空間很大,足夠接收這么多數(shù)據(jù),那么就算遇上以上情況,也是沒有任何問題,但是萬一你比較節(jié)省資源,緩存不夠大會出現(xiàn)什么情況?

這就涉及到內(nèi)存分配問題:

什么樣的幀頭是足夠特殊的數(shù)據(jù)呢?

你的串口緩存一般在 Data區(qū)域,一旦你接收的數(shù)據(jù)超出了你開辟的空間,那么必然導致緩存空間溢出!

那么緩存空間溢出會導致什么危害?

我們通過上圖可以知道,一旦緩存溢出,必然導致該緩存周圍的數(shù)據(jù)出現(xiàn)異常(數(shù)據(jù)被篡改),如果你的其它代碼剛好需要這個數(shù)據(jù)作為重要參考,而你在使用的時候又沒有對這個數(shù)據(jù)的有效性進行檢查,那么可能導致另一個災難性后果,而這個后果又導致了其他后果,從而導致雪崩效應。

而你修復這個bug時,你以為修復了,但你只修復了表面,真正內(nèi)在bug還存在!

所以,千萬別太相信內(nèi)存中的數(shù)據(jù),每一個數(shù)據(jù)的輸入都要進行嚴格檢查,這個數(shù)據(jù)可以錯誤,但是不能導致程序崩潰!

所以千萬別寫能篡改別人數(shù)據(jù)的代碼,這是很危險的事情,也是很難解決的bug,因為你不知道它會在什么時候篡改哪里的數(shù)據(jù)!

再假如你的接收緩存放在棧中了呢(稍微有C語言常識的程序員都不會把串口接收緩存放在棧中,但魚鷹偏偏遇到過這種代碼,而為了解決這個bug整整花了一星期,這還是在bug復現(xiàn)率高的情況下)?

根據(jù)前面的圖可知,棧一般存放在高地址,并且一般棧生長方向為向低地址生長。如果出現(xiàn)上述情況(接收的數(shù)據(jù)大于開辟的棧緩存空間)會發(fā)生什么?

棧幀被破壞!

灰色部分因為接收的數(shù)據(jù)太多,導致原本存在的棧數(shù)據(jù)被串口的接收的數(shù)據(jù)修改了(注意篡改的數(shù)據(jù)可能不是連續(xù)的,因為每一次進入時,開辟的那部分棧空間可能都不在同一個地址),假如這個數(shù)據(jù)是保存返回寄存器LR的,那么必然導致返回錯誤,極可能觸發(fā)HardFault中斷!

那么有什么辦法解決棧被破壞的問題?

最有效的方式魚鷹覺得是使用ITM,如果無法在線調(diào)試,可以嘗試DMA循環(huán)傳輸PC指針值(但是如何得到這個值?畢竟這個寄存器本身是沒有地址概念的)到一塊內(nèi)存中,這樣就可以得到最后正常執(zhí)行的代碼地址,從而定位錯誤代碼的位置。

如果單片機不支持這些功能呢?魚鷹現(xiàn)有的知識體系好像無法解決,只能佛系調(diào)bug了(看和bug之間的緣分),囧。

前面說了由于外部工作環(huán)境導致數(shù)據(jù)長度信息錯誤從而出現(xiàn)數(shù)組溢出這種情況,如果說你保證工作環(huán)境非常好,不可能出現(xiàn)這種干擾,是否還會出現(xiàn)問題?

當然會!

前面分析了外在原因,現(xiàn)在分析內(nèi)在原因,你的接收程序能保證及時接收發(fā)送端發(fā)送過來的數(shù)據(jù)嗎?如果不及時接收數(shù)據(jù)會出現(xiàn)什么問題?

我們知道,一個系統(tǒng)一般都有很多中斷需要處理,如果說你的接收程序的中斷優(yōu)先級不是最高的,那么很可能出現(xiàn)接收程序無法及時接收的情況,即RXNE中斷來臨時,因更高優(yōu)先級中斷需要處理,而且處理時間較長,那么就會出現(xiàn)當前接收的字節(jié)因為沒有接收完成而被后續(xù)的數(shù)據(jù)沖掉,即出現(xiàn)ORE(溢出錯誤)。

這樣會導致什么問題?

數(shù)據(jù)域信息(也可能是校驗值等數(shù)據(jù))當成了長度信息(為什么只討論長度,而不討論功能字之類的數(shù)據(jù),難道他們不會出現(xiàn)ORE的情況嗎?),這樣一來,如果這個數(shù)據(jù)很大,接收程序就會以為接下來還需要接收很多數(shù)據(jù)才能完成一幀的接收,導致后果和前面分析的數(shù)據(jù)干擾一樣嚴重。

那么采用RXNE接收方式時該怎么解決這種問題?

檢查長度信息的合理性,只要長度信息不會導致緩存溢出即可。

但是上面的解決方案會導致什么問題?

因為你的程序設計問題(采用RXNE接收導致不能及時處理),使得原本能接收的數(shù)據(jù)無法及時處理(DMA可以及時處理),最終使得當前這一幀數(shù)據(jù)無法正常接收(如果錯誤的長度信息夠大的話,還有可能接下來很多幀數(shù)據(jù)都無法接收),這你能接受嗎?

但是采用DMA為什么就不會有上述問題,除了DMA能自動接收數(shù)據(jù)提高效率之外,還有一點就是它不根據(jù)接收的數(shù)據(jù)來判斷接下來還需要接收多少數(shù)據(jù),而是根據(jù)設定的接收數(shù)據(jù)長度來接收的(如果加入IDLE中斷,可以提前結(jié)束接收工作),這就避免了上述的緩存溢出和接收不及時問題。

最后再分析上述接收的另一個問題,那就是一幀數(shù)據(jù)中可能出現(xiàn)沒有數(shù)據(jù)域的情況,這種情況該怎么處理?

只要根據(jù)長度信息分開處理即可。如果不對沒有數(shù)據(jù)域的情況分開處理,那么你接收的下一個數(shù)據(jù)直接就是校驗值,而你的接收流程卻認為這是數(shù)據(jù)域的數(shù)據(jù),必然導致校驗失敗。

現(xiàn)在總結(jié)使用RXNE方式接收的幾個問題:

1、緩存溢出。

緩存溢出有兩種可能,第一種就是環(huán)境干擾導致長度信息出錯,從而出現(xiàn)緩存溢出情況;第二種情況就是因為接收不及時,導致數(shù)據(jù)錯位,如果剛好是長度信息錯誤,并且這個長度信息太大,而你的代碼未對長度進行檢查,那么也會出現(xiàn)緩存溢出bug,而這種bug一旦出現(xiàn),很難發(fā)現(xiàn)。所以在代碼中對數(shù)據(jù)的合理性檢查是非常有必要的一件事。

2、中斷及時處理。

如果中斷不及時處理,會導致數(shù)據(jù)錯位,輕則丟失至少一幀數(shù)據(jù),重則緩存溢出!

3、狀態(tài)機是否需要接收數(shù)據(jù)部分。

由于數(shù)據(jù)幀有可能沒有數(shù)據(jù)域的情況,所以必須區(qū)別處理,保證代碼接收的準確性,否則有可能把校驗值當成數(shù)據(jù)了,這樣必然無法通過校驗,這一幀數(shù)據(jù)必然會丟失!

串口空閑

前面一直提到串口空閑,也大概明白串口的作用,但是一些細節(jié)問題還是需要好好說一下的。

第一個問題,如何清除串口空閑中斷標志位?

很多人會使用USART_ClearFlag標準庫函數(shù)進行清除,但是當你跳轉(zhuǎn)到該函數(shù)原型時,你會看到如下說明:

什么樣的幀頭是足夠特殊的數(shù)據(jù)呢?

你會看到很多標志位是無法通過該函數(shù)清除的。

那么該如何清除IDLE標志呢?其實上面的注釋已經(jīng)進行了說明。

PE、FE、NE、ORE、IDLE標志位的清除是通過一個軟件序列進行清除的:首先通過USART_GetFlagStatus讀取USART_SR寄存器的值,然后通過USART_ReceiveData函數(shù)讀取USART_DR的值即可。

那么這里就有一個問題,是否這些標志問題的清除都要單獨編寫清除序列呢?

答案是否定的。

因為這些標志位都是由同一種序列進行清除的,所以只要一個清除序列就會把所有的標志位都進行清除了(同樣一旦執(zhí)行了這個序列,也就意味著你無法再通過USART_SR寄存器獲得標志位了)。

為了保證獲取標志位,我們可以在清除序列之前把USART_SR寄存器的值保存到副本中,然后再讀取USART_DR寄存器的值保存到副本來實現(xiàn)清除功能,注意該序列應該無條件執(zhí)行(不在某個判斷語句中)。這樣后續(xù)我們就可以使用這個 USART_SR 的副本判斷哪一個標志置位了,同樣也可以使用 USART_DR 的副本獲取串口數(shù)據(jù),而為了實現(xiàn)以上效果,USART_GetFlagStatus這個函數(shù)就不合適了,只能直接操作寄存器去實現(xiàn)。

第二個問題,在線調(diào)試時對空閑中斷會有影響嗎?

我們知道,KEIL能夠?qū)⒁粋€結(jié)構(gòu)體的數(shù)據(jù)全部讀取出來,而庫函數(shù)將串口模塊的所有寄存器都封裝在一個結(jié)構(gòu)體中,這樣就會出現(xiàn)一個問題,如果你的窗口是實時刷新的,當你使用KEIL讀取串口模塊寄存器的時候(不管是使用peripheral窗口還是Watch窗口),就會出現(xiàn)先讀取SR再讀取DR的情況, 這樣就有可能出現(xiàn)KEIL和單片機CPU讀取這兩個寄存器沖突的情況。

如果全速運行時,KEIL 先執(zhí)行了這個序列(通過調(diào)試器讀取這兩個寄存器的值),單片機CPU再讀取SR寄存值,必然是無法讀取到正確標志位的,因為這些標志位已經(jīng)被KEIL的讀取序列清除了(這個情況魚鷹確實碰到過,當時明明下發(fā)了數(shù)據(jù),但是單片機無法獲取標志位),所以在調(diào)試串口時,注意不要讓 KEIL 去讀取這些寄存器(即關(guān)閉這些窗口,只有在必須的情況下才開啟),防止出現(xiàn)莫名其妙的情況。

第三個問題,空閑中斷能準確觸發(fā)嗎?

如果從接收端考慮的話,如果觸發(fā)了空閑中斷,那么必然滿足了條件才觸發(fā)的,而不是意外觸發(fā)的(嗯,我們要相信STM32),但從發(fā)送端考慮的話,有可能出現(xiàn)一幀數(shù)據(jù)斷續(xù)發(fā)送,導致一幀數(shù)據(jù)觸發(fā)多次空閑中斷,所以如果是簡單的DMA+空閑中斷方式接收是很有問題的(空閑出現(xiàn)就認為一幀結(jié)束了,就會把一幀數(shù)據(jù)當成兩幀處理,這樣肯定無法通過數(shù)據(jù)檢查的)。

那么先來分析為什么會出現(xiàn)一幀數(shù)據(jù)多次觸發(fā)空閑中斷情況。我們知道linux、windows系統(tǒng)并不是實時系統(tǒng),當應用程序需要發(fā)送一幀數(shù)據(jù)時,可能并沒有連續(xù)發(fā)送,而是發(fā)送完一個字節(jié)后去處理其他事情后才發(fā)送下一個字節(jié),這樣一來,如果耽誤的時間夠長,就會觸發(fā)串口的空閑中斷,從而一幀數(shù)據(jù)當成兩幀處理了。

那有什么方法可以解決呢?魚鷹提供兩種解決思路。

第一種,使用兩個緩存空間,一個緩存空間專門用于接收串口數(shù)據(jù),將接收到的數(shù)據(jù)存放到另一個緩存,這個緩存采用字節(jié)隊列的方式進行管理,應用程序從緩存隊列中一個字節(jié)一個字節(jié)的取出數(shù)據(jù)進行處理(注意檢查數(shù)據(jù)有效性),這樣就能保證及時處理。但是因為空閑中斷不再可靠,所以空閑中斷不再作為判斷一幀數(shù)據(jù)結(jié)束的依據(jù)(根據(jù)長度信息判斷),而是只在空閑中斷中將已接收數(shù)據(jù)復制到字節(jié)隊列緩存中,這樣就可以處理意外的空閑中斷。

第二種,還是一個緩存空間,還是DMA+空閑方式處理,但是需要增加額外的條件。就是當進入空閑中斷后,不再直接處理,而是獲取當前接收時刻,然后在處理數(shù)據(jù)的時候根據(jù)這個時刻來判斷是否達到足夠的空閑時間,只有在進入空閑中斷后并達到一定延時之后才認為一幀數(shù)據(jù)結(jié)束了,這樣可以避免一些非常短的空閑時間(魚鷹公眾號提供過的代碼使用這種方式)。

以上問題是就是魚鷹以前使用空閑中斷從未考慮的問題,魚鷹并不知道使用空閑中斷還可能出現(xiàn)誤觸發(fā)的情況,但是既然知道了,就要想辦法解決。但是為什么以前使用空閑中斷時沒有出現(xiàn)通信問題呢?

事實上不是沒有問題,而是有可能把分散的一幀數(shù)據(jù)的兩部分直接丟棄了而已,因為有重發(fā)機制,所以即使丟棄一幀數(shù)據(jù),也能通信正常,而且這種一幀數(shù)據(jù)分散成兩部分的概率還是挺低的,ubuntu(linux系統(tǒng))下大概千分之三左右的樣子。

第四個問題,如果單片機沒有空閑中斷又該如何做?

當我們使用 RXNE 的同時其實我們也可以使用空閑中斷,這樣也能確定一幀數(shù)據(jù)的結(jié)束(但是要注意前面的誤觸發(fā)問題)。但是如果有些低端單片機(如 51 )沒有空閑中斷又該怎么辦?

其實我們可以從 stm32 的空閑中斷得到相應的啟發(fā)。

所謂空閑中斷,就是當串口接收到數(shù)據(jù)后,在應該接收數(shù)據(jù)的時刻,發(fā)送方并沒有發(fā)送數(shù)據(jù),所以串口模塊置位空閑標志位,從而引起空閑中斷。

那么我們是否可以軟件模擬串口模塊的這個功能,從而確定一幀數(shù)據(jù)的結(jié)束呢?

答案是肯定的(前提是每一幀數(shù)據(jù)之間有空閑時間)。

我們可以使用一個定時器,定時器向上計數(shù)。當接收到一個字節(jié)數(shù)據(jù)后,初始化計數(shù)器并啟動定時器,這樣一旦有一段時間沒有接收到串口數(shù)據(jù)(也就不再初始化計數(shù)器),那么定時器溢出,進入溢出中斷,而這個溢出中斷就類似于串口的空閑中斷(在溢出中斷中關(guān)閉定時器以達到清除空閑中斷標志的作用),這樣就達到了串口空閑中斷的效果(和前面問題的第二種解決方案類似)。

通信吞吐量

在以上分析過程中,都是采用主機發(fā)送,從機接收后再回復主機的方式進行通信,雖然通信正常,但實際上效率比較低下,單位時間傳輸?shù)臄?shù)據(jù)量較少,如下圖所示:

紅色部分就是必要的空閑時間,可以看到左右兩張圖的通信頻率是有差異的,右圖中從機必須等待前一幀數(shù)據(jù)發(fā)送完畢才能處理數(shù)據(jù),而左圖可以在接收當前幀時處理上一幀數(shù)據(jù),類似CPU的指令執(zhí)行流水線。
責任編輯:pj

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

    關(guān)注

    0

    文章

    2

    瀏覽量

    6140
  • 幀格式
    +關(guān)注

    關(guān)注

    0

    文章

    7

    瀏覽量

    5480
收藏 人收藏

    評論

    相關(guān)推薦

    請問TLV320AIC3268的ADC轉(zhuǎn)換后的數(shù)據(jù)格式是什么樣的?

    我在使用TLV320AIC3268,但是不知道ADC轉(zhuǎn)換后的數(shù)據(jù)格式是什么樣的?比如是二進制補碼嗎?轉(zhuǎn)后的數(shù)據(jù)與輸入的模擬信號的電壓值有什么關(guān)系?
    發(fā)表于 10-14 07:48

    代碼整潔之道-大師眼中的整潔代碼是什么樣

    幾個月前寫了一篇文章“如何寫出難以維護的代碼”,從中能大概了解到不好維護的代碼是什么樣,有哪些壞味道,那肯定有人會反問,難以維護的代碼見的太多了,也知道長什么樣,但是對于好維護的代碼是什么樣的比較
    的頭像 發(fā)表于 09-09 16:30 ?216次閱讀
    代碼整潔之道-大師眼中的整潔代碼是<b class='flag-5'>什么樣</b>

    CAN數(shù)據(jù)的各個域及其作用

    CAN數(shù)據(jù)由多個域組成,包括起始、仲裁域、控制域、數(shù)據(jù)域和結(jié)束。每個域都有其特定的作用,共同構(gòu)成了一個完整的
    的頭像 發(fā)表于 07-24 15:10 ?647次閱讀

    FPGA能實現(xiàn)什么樣的算法?

    FPGA功能如此強大,請問用FPGA能實現(xiàn)或者比較適合實現(xiàn)什么樣的算法?
    發(fā)表于 05-26 20:18

    快充充電哪個品牌好?什么樣的充電用著“香”?

    在探討快充充電哪個品牌好,以及什么樣的充電使用起來更令人滿意的議題時,我們不僅要考慮充電速度,還要關(guān)注充電的散熱性能、兼容性和便攜性。 充電
    的頭像 發(fā)表于 05-20 17:27 ?894次閱讀

    基于HTTP/3構(gòu)建SSH協(xié)議會是什么樣?

    來自UCLouvain的Fran?ois Michel 和Olivier Bonaventure在研究中思考了一個問題:如果使用最新的網(wǎng)絡技術(shù)來重新設計SSH協(xié)議,那新協(xié)議會是什么樣
    的頭像 發(fā)表于 02-20 17:07 ?570次閱讀
    基于HTTP/3構(gòu)建SSH協(xié)議會是<b class='flag-5'>什么樣</b><b class='flag-5'>呢</b>?

    請問用CAN存儲器存儲的CAN總線數(shù)據(jù)格式有哪些

    請問用CAN存儲器存儲的CAN總線數(shù)據(jù)格式有哪些? CAN總線是一種常用于汽車、工業(yè)控制和其他應用領(lǐng)域的串行通信協(xié)議。在CAN總線中,數(shù)據(jù)通過數(shù)據(jù)
    的頭像 發(fā)表于 01-31 13:46 ?1107次閱讀

    工業(yè)控制通信協(xié)議的報文分別是什么樣的?

    那樣先存儲起來,不知道我對同步異步通信的理解是否正確? 2、這些協(xié)議下的通信設備發(fā)送數(shù)據(jù)的格式多種多樣,請問每種協(xié)議的通信發(fā)送的結(jié)構(gòu)是怎么的,想總結(jié)一下這些通信協(xié)議的發(fā)送類型結(jié)構(gòu)
    發(fā)表于 01-19 14:46

    什么樣的電子元件才是車規(guī)級的器件?車規(guī)級芯片到底有哪些要求?

    汽車電子產(chǎn)品的價格普遍比較貴,其中的主要原因之一就是使用了車規(guī)級的電子元件,但什么樣的電子元件才是車規(guī)級的器件?我們先來看看電子元件在汽車上的應用和一般的消費電子在應用有什么差異。
    的頭像 發(fā)表于 01-13 16:24 ?2135次閱讀
    <b class='flag-5'>什么樣</b>的電子元件才是車規(guī)級的器件<b class='flag-5'>呢</b>?車規(guī)級芯片到底有哪些要求?

    請問AD2S1200的串行數(shù)據(jù)輸出波形及處理的角度波形是什么樣的?

    AD2S1200的串行數(shù)據(jù)輸出波形及處理的角度波形是什么樣的?
    發(fā)表于 12-20 06:07

    變頻器一般是控制什么樣的電機,是不是需要特殊的電機才能與之匹配?

    變頻器一般是控制什么樣的電機,是不是需要特殊的電機才能與之匹配?普通的電機行不?直流電機行不?是不是非得是某種類型的電機?這個東東還沒有過!請教!
    發(fā)表于 12-19 06:57

    什么樣的變壓器可以做升壓器?

    什么樣的變壓器可以做升壓器? 變壓器可以通過改變輸入電壓和輸出電壓的繞組匝數(shù)比來實現(xiàn)升壓操作。根據(jù)變壓器的類型和設計,有多種變壓器可以用作升壓器。下面將詳細介紹幾種常見的變壓器類型及其應用。 1.
    的頭像 發(fā)表于 11-20 14:44 ?887次閱讀

    環(huán)路增益是什么?什么樣的環(huán)路增益特性是理想的?

    環(huán)路增益是什么?什么樣的環(huán)路增益特性是理想的? 環(huán)路增益(loop gain)是同相反饋系統(tǒng)中的一個重要概念,用于描述反饋回路的閉環(huán)增益。在電子電路和控制系統(tǒng)中,環(huán)路增益對系統(tǒng)的穩(wěn)定性、幅頻特性
    的頭像 發(fā)表于 11-08 17:46 ?2965次閱讀

    倒裝芯片封裝選擇什么樣的錫膏?

    介紹倒裝芯片封裝選擇什么樣的錫膏?
    的頭像 發(fā)表于 10-31 13:16 ?759次閱讀
    倒裝芯片封裝選擇<b class='flag-5'>什么樣</b>的錫膏?

    環(huán)路增益是什么?什么樣的環(huán)路增益特性是理想的?

    環(huán)路增益是什么?什么樣的環(huán)路增益特性是理想的? 環(huán)路增益是指信號在一個系統(tǒng)中形成閉環(huán)后,從輸入到輸出經(jīng)過一圈的增益。具體來說,環(huán)路增益是指信號在經(jīng)過系統(tǒng)的反饋回路后,被加強了多少倍。這個概念
    的頭像 發(fā)表于 10-25 11:39 ?2298次閱讀