一、介紹
??雖然如今或者將來(lái),5G網(wǎng)絡(luò)的建設(shè)帶來(lái)人工智能和工業(yè)自動(dòng)化的全面升級(jí),生產(chǎn)活動(dòng)中勞動(dòng)力的需求大大減少,大量的勞動(dòng)力將向內(nèi)容生產(chǎn)行業(yè)和服務(wù)行業(yè)轉(zhuǎn)移。教育、醫(yī)療、娛樂(lè)、公共管理等諸多領(lǐng)域,乃至整個(gè)社會(huì)都將迎來(lái)巨大變革??蓞㈤單业囊黄x書(shū)筆記5G社會(huì):萬(wàn)物互聯(lián)新時(shí)代。
??但是,使用傳統(tǒng)無(wú)線電通信設(shè)備通信仍然是非常重要的通信方式,比如無(wú)線電臺(tái)、對(duì)講機(jī),航模、車(chē)模、船模遙控等等。與手機(jī)移動(dòng)網(wǎng)絡(luò)、WIFI連接相比,無(wú)線電連接有它獨(dú)特的優(yōu)勢(shì)。
??在樹(shù)莓派基礎(chǔ)實(shí)驗(yàn)38:邏輯分析儀分析PWM、UART信號(hào)中使用邏輯分析儀,對(duì)樹(shù)莓派的PWM信號(hào)和UART信號(hào)進(jìn)行分析,從中可以詳細(xì)了解邏輯分析儀分析的使用方法及PWM信號(hào)和UART信號(hào)。
??本實(shí)驗(yàn)中將使用邏輯分析儀、樹(shù)莓派,對(duì)航模無(wú)線電接收機(jī)輸出的PWM信號(hào)、SBUS信號(hào)進(jìn)行采集分析,以便樹(shù)莓派能夠接收無(wú)線電控制信號(hào),進(jìn)而可以開(kāi)發(fā)基于無(wú)線電控制的樹(shù)莓派航模飛行控制系統(tǒng)、或者智能小車(chē)的無(wú)人駕駛系統(tǒng)。
二、組件
★Raspberry Pi 3 B+全套*1
★睿思凱Frsky X8R 接收機(jī)*1
★電平反向器模塊*1
★睿思凱Frsky Taranis X9D PLUS SE2019遙控器*1
★國(guó)產(chǎn)夢(mèng)源DSLogic Plus邏輯分析儀*1
★面包板*1(可選)
★40P軟排線*1
★跳線若干
三、實(shí)驗(yàn)原理
(一)航模無(wú)線電遙控系統(tǒng)
本實(shí)驗(yàn)中使用的遙控系統(tǒng)可以自行選擇其它品牌的產(chǎn)品,如國(guó)產(chǎn)的天地飛還不錯(cuò)。
航模的遙控器就是像電視機(jī)遙控器、空調(diào)遙控器一樣可以不用接觸到被控設(shè)備,而通過(guò)一個(gè)手持器件,使用無(wú)線電與被控設(shè)備進(jìn)行通信,從而達(dá)到對(duì)設(shè)備的控制。
遙控器想到達(dá)到與無(wú)人機(jī)通信的功能需要有兩部分配合完成。即:發(fā)射器(遙控器)與接收機(jī)。遙控器上的控制桿轉(zhuǎn)為無(wú)線電波發(fā)送給接收機(jī),而接收機(jī)通過(guò)接收無(wú)線電波,讀取遙控器上控制桿的讀數(shù),并轉(zhuǎn)為數(shù)字信號(hào)發(fā)送到航模的控制器中。

目前用于無(wú)人機(jī)遙控器主流的無(wú)線電頻率是2.4G,這樣的無(wú)線電波的波長(zhǎng)更長(zhǎng),可以通信的距離較遠(yuǎn),普通2.4G遙控器與接收機(jī)的通信距離在空曠的地方大概在1km以?xún)?nèi)。2.4GHz無(wú)線技術(shù)如今已經(jīng)成為了無(wú)線產(chǎn)品的主流傳輸技術(shù)。所謂的2.4GHz所指的是一個(gè)工作頻段2400M-2483M范圍,這個(gè)頻段是全世界免申請(qǐng)使用。
常見(jiàn)的Wifi、藍(lán)牙、ZigBee都是使用的2.4G頻率段,只不過(guò)他們采用的協(xié)議不同,導(dǎo)致其傳輸速率不同,所以運(yùn)用的范圍就不同。同樣是采用2.4G頻率作為載波,但不同的通訊協(xié)議衍生出的通訊方式會(huì)有著天壤之別;僅僅在傳輸數(shù)據(jù)量上,就有著從1M每秒到100M每秒的差別。
關(guān)于遙控器與無(wú)人機(jī)的通信協(xié)議也有很多種,常見(jiàn)的數(shù)據(jù)協(xié)議如下:
1.pwm:需要在接收機(jī)上接上全部pwm輸出通道,每一個(gè)通道就要接一組線,解析程序需要根據(jù)每一個(gè)通道的pwm高電平時(shí)長(zhǎng)(即占空比)計(jì)算通道數(shù)值。
2.ppm:按固定周期發(fā)送所有通道pwm脈寬的數(shù)據(jù)格式,一組接線,一個(gè)周期內(nèi)發(fā)送所有通道的pwm值,解析程序需要自行區(qū)分每一個(gè)通道的pwm時(shí)長(zhǎng)。PPM的頻率通常是50Hz,周期長(zhǎng)度20ms,每一個(gè)周期中可以存放最多10路PWM信號(hào),每一路PWM的周期為2ms。
3.sbus:每11個(gè)bit位表示一個(gè)通道數(shù)值的協(xié)議,串口通信,但是sbus的接收機(jī)通常是反向電平,連接到航模時(shí)需要接電平反向器,大部分支持sbus的飛行控制板已經(jīng)集成了反向器。
4.xbus:常規(guī)通信協(xié)議,支持18個(gè)通道,數(shù)據(jù)包較大,串口通信有兩種模式,可以在遙控器的配置選項(xiàng)中配置。接收機(jī)無(wú)需做特殊配置。


然后,就是電調(diào)通過(guò)接收接收機(jī)輸出的這些信號(hào),來(lái)將輸入的電源轉(zhuǎn)為不同的電壓,并輸出到電機(jī),從而達(dá)到使電機(jī)產(chǎn)生不同的轉(zhuǎn)速的目的。有刷電調(diào)可以改變電流方向,從而可以改變電機(jī)轉(zhuǎn)動(dòng)方向。而無(wú)刷電調(diào)卻不能改變電機(jī)的轉(zhuǎn)動(dòng)方向,但是可以將直流電轉(zhuǎn)為三相交流電,從而輸出到無(wú)刷電機(jī)上。
所謂電調(diào)就是電壓調(diào)節(jié)器,也可以通俗的說(shuō)成是電機(jī)調(diào)節(jié)器,這里不做過(guò)多講解。
(二)接收機(jī)的PWM信號(hào)
PWM英文全稱(chēng)為(Pulse-width modulation)。也稱(chēng)占空比信號(hào),它表示高電平時(shí)長(zhǎng)占整個(gè)信號(hào)周期的比例。

PWM信號(hào)的頻率是通常是沒(méi)有規(guī)定的,可以是50hz、100hz、200hz或500hz等等??刂祁l率越高,其周期越短,控制間隔也就越短,電調(diào)和電機(jī)響應(yīng)速度也就越快。反之,控制頻率越低,其周期就越長(zhǎng),控制間隔就越長(zhǎng),電調(diào)和電機(jī)的響應(yīng)速度就越慢。早期電調(diào)響應(yīng)PWM信號(hào)的頻率是50hz,但隨著科技的發(fā)展和對(duì)控制流暢度的要求,現(xiàn)在多數(shù)電調(diào)都支持500hz以上的PWM信號(hào),并且電調(diào)內(nèi)部自帶濾波器,可以很好的響應(yīng)并控制電機(jī)的轉(zhuǎn)動(dòng)。
傳統(tǒng)的遙控器接收機(jī)是采用多路PWM的方式進(jìn)行輸出的,遙控器中有多少個(gè)通道,接收機(jī)中就有多少路PWM輸出,睿思凱Frsky X8R接收機(jī)的1-8個(gè)PWM輸出通道,都是以PWM的形式輸出的,這就需要飛控能夠采集并解析這些PWM信號(hào),并為飛控所用。
那么,睿思凱Frsky X8R接收機(jī)的PWM信號(hào)到底是怎樣的呢?我們使用邏輯分析儀看看吧,連接好遙控器、接收機(jī)、連接邏輯分析儀。

這里我只采集了1、3、5號(hào)通道的PWM信號(hào)。1號(hào)通道是右手油門(mén)搖桿左右晃動(dòng),會(huì)自動(dòng)回中;3號(hào)通道是右手油門(mén)搖桿油門(mén)控制,由低到高表示油門(mén)由小到大,不會(huì)回中;5號(hào)通道是SA開(kāi)關(guān),有上中下3個(gè)檔位。
首先來(lái)看1號(hào)通道,當(dāng)搖桿往左搖到底時(shí),占空比約為5.5%,高電平時(shí)長(zhǎng)為0.99ms,信號(hào)周期為18ms。

1號(hào)通道,當(dāng)搖桿往右搖到底時(shí),占空比約為11.2%,高電平時(shí)長(zhǎng)為2.01ms,信號(hào)周期為18ms。

再看3號(hào)通道,當(dāng)搖桿往下?lián)u到底時(shí),油門(mén)為0,占空比約為5.5%,高電平時(shí)長(zhǎng)為0.99ms,信號(hào)周期為18ms。

3號(hào)通道,當(dāng)搖桿往上搖到底時(shí),油門(mén)為最大,占空比約為11.2%,高電平時(shí)長(zhǎng)為2.01ms,信號(hào)周期為18ms。

那當(dāng)他們居中時(shí)呢?占空比約為8.3%,高電平時(shí)長(zhǎng)為1.50ms,信號(hào)周期還是為18ms。

5號(hào)通道為開(kāi)關(guān),上中下三檔,與1/3通道的高中低三檔時(shí)的數(shù)值一樣,占空比依次約為11.2%、8.3%、5.5%,高電平時(shí)長(zhǎng)依次約為2ms、1.5ms、1ms,信號(hào)周期一直是穩(wěn)定的18ms。



在采集接收機(jī)PWM信號(hào)時(shí)發(fā)現(xiàn),當(dāng)接收機(jī)剛通電時(shí),接收機(jī)不輸出PWM信號(hào),當(dāng)遙控器連接成功接收機(jī)后,接收機(jī)就立馬輸出遙控器的即時(shí)狀態(tài)信號(hào),所以請(qǐng)注意,連接之前請(qǐng)注意將油門(mén)調(diào)至0,否則如果電調(diào)沒(méi)有保護(hù)機(jī)制,螺旋槳會(huì)立馬飛起來(lái)。
無(wú)線電波在傳輸過(guò)程中可能受到干擾或是數(shù)據(jù)丟失等等問(wèn)題,當(dāng)接收機(jī)無(wú)法接收到發(fā)射器的數(shù)據(jù)時(shí),通常會(huì)進(jìn)入保護(hù)狀態(tài),也就是仍舊向無(wú)人機(jī)發(fā)送控制信號(hào),此時(shí)的信號(hào)就是接收機(jī)收到遙控器發(fā)射器最后一次的有效數(shù)據(jù)。這樣因?yàn)樾盘?hào)丟失而發(fā)送的保護(hù)數(shù)數(shù)據(jù)通常叫做failsafe數(shù)據(jù)。
如果遙控器沒(méi)有設(shè)置failsafe mode,X8R接收機(jī)默認(rèn)HOLD模式,即保持?jǐn)嗦?lián)之前的信號(hào)一直輸出;可以在遙控器上設(shè)置No pulses模式,指斷聯(lián)后接收機(jī)不輸出信號(hào);可以在遙控器上設(shè)置Custom模式,定制斷聯(lián)后接收機(jī)要輸出的控制信號(hào),比如降低油門(mén)到比較低的程度,以便飛機(jī)自動(dòng)降落。
樹(shù)莓派輸出PWM信號(hào)很簡(jiǎn)單,但是如果我們需要使用樹(shù)莓派來(lái)讀取接收機(jī)輸出的PWM信號(hào)值怎么辦呢?
我們以第一個(gè)通道的PWM為例,講述樹(shù)莓派對(duì)其處理的具體方法:
(1)檢測(cè)引腳由低點(diǎn)平變?yōu)楦唠娖降臅r(shí)刻,并記錄當(dāng)前時(shí)間t0,表示高電平開(kāi)始;
(2)檢測(cè)引腳由高電平變?yōu)榈忘c(diǎn)平的時(shí)刻,并記錄當(dāng)前時(shí)間t1,表示高電平結(jié)束;
(3)繼續(xù)檢測(cè)引腳由低點(diǎn)平變?yōu)楦唠娖降臅r(shí)刻,并記錄當(dāng)前時(shí)間t2,表示一個(gè)PWM周期結(jié)束;
(4)計(jì)算高電平時(shí)長(zhǎng) = t1 - t0;
(5)計(jì)算整個(gè)PWM周期 = t2 - t0;
(6)計(jì)算PWM占空比 = 高電平時(shí)長(zhǎng) / PWM周期
每一個(gè)遙控器通道都需要一個(gè)PWM采集器進(jìn)行采集,但是對(duì)于樹(shù)莓派來(lái)說(shuō)不可能使用多個(gè)定時(shí)器來(lái)采集多個(gè)通道的PWM,這對(duì)于樹(shù)莓派的資源來(lái)說(shuō)十分浪費(fèi),因此我優(yōu)先采用的就是SBUS編碼,可以在一個(gè)管腳中傳輸多路控制信號(hào)。
(三)SBUS信號(hào)
1.介紹
S.BUS是FUTABA提出的舵機(jī)控制總線,全稱(chēng)Serial Bus,別名S-BUS或SBUS,也稱(chēng) Futaba S.BUS。
S-BUS其實(shí)是一種串口通信協(xié)議,采用100000的波特率,數(shù)據(jù)位點(diǎn)8bits,停止位點(diǎn)2bits,偶效驗(yàn),即8E2的串口通信。但是S-BUS采用的是反向電平傳輸,也就是說(shuō),在S-BUS的發(fā)送端高低電平是反向的,協(xié)議中的所有高電平都被轉(zhuǎn)換成低電平,協(xié)議中的所有低電平都被轉(zhuǎn)換成高電平。所以在S-BUS的接收端需要增加一個(gè)高低電平反向器來(lái)進(jìn)行電平反轉(zhuǎn)。

實(shí)際上,有的飛控板上已經(jīng)集成了反向器,所以對(duì)于使用這種飛控的用戶來(lái)說(shuō),可以忽略掉S-BUS的反向機(jī)制,但是對(duì)于其它沒(méi)有集成S-BUS反向器的硬件平臺(tái)上,就需要使用者增加一個(gè)反向器來(lái)處理數(shù)據(jù),否則將無(wú)法讀取協(xié)議數(shù)據(jù)。
另外,100000的波特率并不是標(biāo)準(zhǔn)的波特率,這在一些只支持標(biāo)準(zhǔn)波特率的系統(tǒng)上無(wú)法實(shí)現(xiàn),我們可以通過(guò)對(duì)設(shè)備節(jié)點(diǎn)的配置實(shí)現(xiàn)波特率的設(shè)定。
通信接口:USART(TTL)
通信參數(shù):1個(gè)起始位+8個(gè)數(shù)據(jù)位+偶校驗(yàn)位+2個(gè)停止位,無(wú)控流,25個(gè)字節(jié),波特率=100000bit/s,電平邏輯反轉(zhuǎn)。
X6R的SBUS通信速率:每6ms間隔發(fā)送數(shù)據(jù),每數(shù)據(jù)幀時(shí)長(zhǎng)為3ms。
數(shù)據(jù)幀格式:
需要注意的是S-BUS中用11bits來(lái)表示一個(gè)遙控器通道的數(shù)值,22個(gè)字節(jié)就可以表示16通道(8 × 22 = 11 ×16)。11個(gè)bit可以表示的數(shù)值范圍為0~2047。
每幀25個(gè)字節(jié),排列如下:
[start byte] [data1] [data2] [data3] ... [data22] [flag] [end byte]

簡(jiǎn)單來(lái)說(shuō)就是,通道1數(shù)據(jù)在前,通道16數(shù)據(jù)最后;每通道的數(shù)據(jù),低位在前面的字節(jié)中,高位在后面的字節(jié)中;每8bit數(shù)據(jù)中,低位是上一通道的數(shù)據(jù),高位是下一通道的數(shù)據(jù)。
start byte = 0x0F
CH1 = [data2]的低3位 + [data1]的8位
????(678?+?12345678 = 678,12345678)
CH2 = [data3]的低6位 + [data2]的高5位
????(345678?+?12345 = 345678,12345 )
CH3 = [data5]的低1位 + [data4]的8位 + [data3]的高2位
????(8 ? ?+? ?12345678 ?? +? ?12 = 8,12345678,12)
... ...
flag(由高位到低位:N/A N/A N/A N/A 故障保護(hù)激活位 幀丟失位 數(shù)字通道CH18 數(shù)字通道CH17 )
end byte = 0x00
2.未做電平反向時(shí)的SBUS信號(hào)

可以看出字節(jié)數(shù)不對(duì),只解析出23字節(jié),起始字節(jié)不是正確的0x0F,而是0xF8,還有紅色的PE(Frame error)幀錯(cuò)誤,即是亂碼。


3.電平反向后的SBUS信號(hào)

可以看出一幀數(shù)據(jù)為25字節(jié),起始字節(jié)是正確的0x0F,結(jié)束字節(jié)為0x00。
再詳細(xì)分析起始字節(jié),要搞清楚每個(gè)字節(jié)的含義,先弄清UART的數(shù)據(jù)通信的字節(jié)格式:

其中各位的意義如下:
起始位:先發(fā)出一個(gè)邏輯”0”信號(hào),表示傳輸字符的開(kāi)始。
數(shù)據(jù)位:可以是5~8位邏輯”0”或”1”。如ASCII碼(7位),擴(kuò)展BCD碼(8位),小端傳輸。
校驗(yàn)位:數(shù)據(jù)位加上這一位后,使得“1”的位數(shù)應(yīng)為偶數(shù)(偶校驗(yàn))或奇數(shù)(奇校驗(yàn))
停止位:它是一個(gè)字符數(shù)據(jù)的結(jié)束標(biāo)志??梢允?位、1.5位、2位的高電平。
空閑位:處于邏輯“1”狀態(tài),表示當(dāng)前線路上沒(méi)有資料傳送。
傳輸方向:即數(shù)據(jù)是從高位(MSB)開(kāi)始傳輸還是從低位(LSB)開(kāi)始傳輸,X8R是從低位開(kāi)始傳輸?shù)摹?/p>

波特率:上圖中可以看出每位的時(shí)長(zhǎng)是10us,意思就是每秒傳輸100000比特位數(shù)(bit),即波特率為100000。
起始位:先發(fā)出一個(gè)邏輯”0”的信號(hào),即低電平,表示傳輸數(shù)據(jù)的開(kāi)始。
數(shù)據(jù)位:SBUS信號(hào)明顯為8位。
校驗(yàn)位:數(shù)據(jù)位加上這一位后,使得“1”的位數(shù)應(yīng)為偶數(shù)(偶校驗(yàn))或奇數(shù)(奇校驗(yàn)),以此來(lái)校驗(yàn)數(shù)據(jù)傳送的正確性。SBUS為偶校驗(yàn),起始字節(jié)數(shù)據(jù)位中已有4個(gè)“1”,所以偶校驗(yàn)位為0。
停止位:它是一幀數(shù)據(jù)的結(jié)束標(biāo)志??梢允?bit、1.5bit、2bit的空閑電平。SBUS信號(hào)是2位停止位,即2位高電平。
空閑位:沒(méi)有數(shù)據(jù)傳輸時(shí)線路上的電平狀態(tài)。為邏輯1。
傳輸方向:uart傳輸數(shù)據(jù)的順序就是:剛開(kāi)始傳輸一個(gè)起始位,接著傳輸數(shù)據(jù)位,接著傳輸校驗(yàn)位(可不需要此位),最后傳輸停止位。這樣一幀的數(shù)據(jù)就傳輸完了。所以上圖中Bits顯示的11110000,是從左到右是由低到高位顯示的,其值實(shí)際上是B00001111=0x0F。
幀間隔:即傳送數(shù)據(jù)的幀與幀之間的間隔大小,這里的間隔為6ms,每幀的周期可以以位為計(jì)量也可以用時(shí)間,(起始1位+數(shù)據(jù)8位+校驗(yàn)1位+中止2位=12位) x 25字節(jié)=300位,每位時(shí)長(zhǎng)為10us x 300位=3000us=3ms。

每幀數(shù)據(jù)時(shí)長(zhǎng)為2.990ms,有10us的誤差,應(yīng)該是3ms:

4. 關(guān)閉遙控器后接收機(jī)的反應(yīng)
為模擬接收機(jī)與遙控器失聯(lián)后的狀態(tài),關(guān)閉遙控器的過(guò)程中,用邏輯分析儀分析了第24個(gè)字節(jié)的變化情況,在斷開(kāi)連接的前900ms內(nèi),幀丟失位由0變?yōu)?,即第24個(gè)字節(jié)值為0x04。

之后,故障保護(hù)激活位由0變?yōu)?,幀丟失位仍為1,即第24個(gè)字節(jié)值為0x0C,此時(shí)如果設(shè)置了failsafe數(shù)據(jù),接收機(jī)就按照f(shuō)ailsafe數(shù)據(jù)輸出信號(hào)。

比如我設(shè)置的為No pulses(無(wú)脈沖),所有的通道值變?yōu)?。

四、實(shí)驗(yàn)步驟
(一) 樹(shù)莓派解析接收機(jī)PWM信號(hào)
- 連接線路。將接收機(jī)的1/3/5通道分別連接到樹(shù)莓派面包板上的G17、G18、G19上,接收機(jī)的電源+、-接5V和GND。
| 樹(shù)莓派(name) | T型轉(zhuǎn)接板(BCM) | 接收機(jī) |
|---|---|---|
| GPIO.0 | G17 | Channel 1(SIG) |
| GPIO.1 | G18 | Channel 3(SIG) |
| GPIO.24 | G19 | Channel 5(SIG) |
| 5V | 5V | + |
| GND | GND | - |
連線很簡(jiǎn)單,電路圖就沒(méi)畫(huà)了,接收機(jī)上端接出的兩個(gè)黑色細(xì)長(zhǎng)薄片是天線。

- 編寫(xiě)樹(shù)莓派解析PWM信號(hào)的程序。為了不至于結(jié)果刷新太快,為了便于觀察,我設(shè)置了每次采集信號(hào)0.5秒的延遲,在實(shí)際信號(hào)使用過(guò)程中,顯然是不用的。
#!/usr/bin/env python
import RPi.GPIO as GPIO
import time
channel_1 = 17 #接收機(jī)1通道連接樹(shù)莓派G17針腳
channel_3 = 18 #接收機(jī)3通道連接樹(shù)莓派G18針腳
channel_5 = 19 #接收機(jī)5通道連接樹(shù)莓派G19針腳
def setup():
GPIO.setmode(GPIO.BCM)
GPIO.setup(channel_1, GPIO.IN)
GPIO.setup(channel_3, GPIO.IN)
GPIO.setup(channel_5, GPIO.IN)
def duty_cycle_collect(pin):
#等待低電平結(jié)束,然后記錄時(shí)間
while GPIO.input(pin) == 0: #捕捉信號(hào)端輸出上升沿
pass
time1 = time.time()
#等待高電平結(jié)束,然后記錄時(shí)間
while GPIO.input(pin) == 1: #捕捉信號(hào)端輸出下降沿
pass
time2 = time.time()
#等待低電平結(jié)束,然后記錄時(shí)間
while GPIO.input(pin) == 0: #捕捉信號(hào)端輸出上升沿
pass
time3 = time.time()
period = time3 - time1
high_time = time2 - time1
low_time = time3 - time2
duty_cycle = high_time * 100 / period
#print period
return duty_cycle
def loop():
while True:
#調(diào)用占空比采集函數(shù)duty_cycle_collect()獲得各通道的信號(hào)占空比
duty_cycle_channel_1 = duty_cycle_collect(channel_1)
print 'duty_cycle_channel_1 =',duty_cycle_channel_1
duty_cycle_channel_3 = duty_cycle_collect(channel_3)
print 'duty_cycle_channel_3 =',duty_cycle_channel_3
duty_cycle_channel_5 = duty_cycle_collect(channel_5)
print 'duty_cycle_channel_5 =',duty_cycle_channel_5
print ''
time.sleep(0.5) #為了便于觀察結(jié)果設(shè)置了延遲
def destroy():
GPIO.cleanup()
if __name__ == "__main__":
setup()
try:
loop()
except KeyboardInterrupt:
destroy()
-
測(cè)試成功獲取接收機(jī)PWM信號(hào)的占空比。1/5通道的遙控均在中位,所以占空比約為8.3%,與邏輯分析儀的結(jié)果一致;3通道是油門(mén),由大到小滑動(dòng)時(shí),得到的占空比結(jié)果11.2%降至5.5%,與邏輯分析儀的結(jié)果一致。但是少數(shù)測(cè)量結(jié)果會(huì)有偏差,極少數(shù)情況偏差較大。
pwm_analyse - 當(dāng)遙控器與接收機(jī)失聯(lián)時(shí),我定制了failsafe數(shù)據(jù),油門(mén)降低。3號(hào)通道的占空比在開(kāi)始失聯(lián)的時(shí)候有抖動(dòng),約3秒鐘后穩(wěn)定在設(shè)置的6.3%左右。

(二) 分析接收機(jī)SBUS信號(hào)
- 連接電路。與樹(shù)莓派基礎(chǔ)實(shí)驗(yàn)36:通用串口通信實(shí)驗(yàn)一樣設(shè)置樹(shù)莓派的串口為通用串口,恢復(fù)硬件串口(/dev/ttyAMA0)與GPIO 14/15的映射關(guān)系,使得我們能夠通過(guò)GPIO使用高性能的硬件串口來(lái)連接我們的SBUS信號(hào)輸入。
| T型轉(zhuǎn)接板(BCM) | 接收機(jī) | 電平反向模塊 | DSlogic邏輯分析儀 |
|---|---|---|---|
| - | SBUS | A6 | - |
| - | - | B6 | Channel 1(SIG) |
| 3.3V | - | 3.3V | - |
| 5V | 5V | - | - |
| GND | GND | GND | Channel 0(GND) |
電平反相模塊很便宜,某寶5元一個(gè)能買(mǎi)到6路的電平反相器。注意反向后的高電平是幾伏,反相器的VCC就接幾伏的電源,樹(shù)莓派GPIO接收3.3V高電平,不能接收5V高電平,所以這里電平反向模塊的VCC只能接3.3V電源。


- 這里樹(shù)莓派要使用pyserial模塊編程接收SBUS信號(hào),有關(guān)基礎(chǔ)可以參考樹(shù)莓派基礎(chǔ)實(shí)驗(yàn)37:pyserial模塊通信實(shí)驗(yàn)。下面的程序只能在Python2.7的環(huán)境下運(yùn)行,Python3上會(huì)出現(xiàn)問(wèn)題。如果有更快更好的代碼,請(qǐng)留言。
#!/usr/bin/env python
#-*- coding: utf-8 -*-
import array #array模塊是python中實(shí)現(xiàn)的一種高效的數(shù)組存儲(chǔ)類(lèi)型
import serial #serial模塊封裝了對(duì)串行端口的訪問(wèn)
import codecs #Python中專(zhuān)門(mén)用作編碼轉(zhuǎn)換的模塊
import time
class SBUSReceiver():
def __init__(self, _uart_port='/dev/ttyAMA0'):
#初始化樹(shù)莓派串口參數(shù)
self.ser = serial.Serial(
port=_uart_port, #樹(shù)莓派的硬件串口/dev/ttyAMA0
baudrate = 100000, #波特率為100k
parity=serial.PARITY_EVEN, #偶校驗(yàn)
stopbits=serial.STOPBITS_TWO,#2個(gè)停止位
bytesize=serial.EIGHTBITS, #8個(gè)數(shù)據(jù)位
timeout = 0,
)
# 常數(shù)
self.START_BYTE = b'\x0f' #起始字節(jié)為0x0f
self.END_BYTE = b'\x00' #結(jié)束字節(jié)為0x00
self.SBUS_FRAME_LEN = 25 #SBUS幀有25個(gè)字節(jié)
self.SBUS_NUM_CHAN = 18 #18個(gè)通道
self.OUT_OF_SYNC_THD = 10
self.SBUS_NUM_CHANNELS = 18 #18個(gè)通道
self.SBUS_SIGNAL_OK = 0 #信號(hào)正常為0
self.SBUS_SIGNAL_LOST = 1 #信號(hào)丟失為1
self.SBUS_SIGNAL_FAILSAFE = 2 #輸出failsafe信號(hào)時(shí)為2
# 堆棧變量初始化
self.isReady = True
self.lastFrameTime = 0
self.sbusBuff = bytearray(1) # 用于同步的單個(gè)字節(jié)
#bytearray(n) 方法返回一個(gè)長(zhǎng)度為n的初始化數(shù)組;
self.sbusFrame = bytearray(25) # 單個(gè)SBUS數(shù)據(jù)幀,25個(gè)字節(jié)
self.sbusChannels = array.array('H', [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) # 接收到的各頻道值
#array.array(typecode,[initializer]) --typecode:元素類(lèi)型代碼;initializer:初始化器,若數(shù)組為空,則省略初始化器
self.failSafeStatus = self.SBUS_SIGNAL_FAILSAFE
def get_rx_channels(self):
"""
用于讀取最后的SBUS通道值
返回:由18個(gè)無(wú)符號(hào)短元素組成的數(shù)組,包含16個(gè)標(biāo)準(zhǔn)通道值+ 2個(gè)數(shù)字(ch17和18)
"""
return self.sbusChannels
def get_rx_channel(self, num_ch):
"""
用于讀取最后的SBUS某一特定通道的值
num_ch: 要讀取的某個(gè)通道的通道序號(hào)
返回:某一通道的值
"""
return self.sbusChannels[num_ch]
def get_failsafe_status(self):
"""
用于獲取最后的FAILSAFE狀態(tài)
返回: FAILSAFE狀態(tài)值
"""
return self.failSafeStatus
def decode_frame(self):
"""
對(duì)每幀數(shù)據(jù)進(jìn)行解碼,每個(gè)通道的值在兩個(gè)或三個(gè)不同的字節(jié)之間,要讀取出來(lái)很麻煩
不過(guò)futaba已經(jīng)發(fā)布了下面的解碼代碼
"""
def toInt(_from):
#encode() 方法以指定的編碼格式編碼字符串。
#int() 函數(shù)用于將一個(gè)字符串或數(shù)字轉(zhuǎn)換為整型。
return int(codecs.encode(_from, 'hex'), 16)
#CH1 = [data2]的低3位 + [data1]的8位(678+12345678 = 678,12345678)
self.sbusChannels[0] = ((toInt(self.sbusFrame[1]) |toInt(self.sbusFrame[2])<<8) & 0x07FF);
#CH2 = [data3]的低6位 + [data2]的高5位(345678+12345 = 345678,12345 )
self.sbusChannels[1] = ((toInt(self.sbusFrame[2])>>3 |toInt(self.sbusFrame[3])<<5) & 0x07FF);
#CH3 = [data5]的低1位 + [data4]的8位 + [data3]的高2位(8+12345678+12 = 8,12345678,12)
self.sbusChannels[2] = ((toInt(self.sbusFrame[3])>>6 |toInt(self.sbusFrame[4])<<2 |toInt(self.sbusFrame[5])<<10) & 0x07FF);
self.sbusChannels[3] = ((toInt(self.sbusFrame[5])>>1 |toInt(self.sbusFrame[6])<<7) & 0x07FF);
self.sbusChannels[4] = ((toInt(self.sbusFrame[6])>>4 |toInt(self.sbusFrame[7])<<4) & 0x07FF);
self.sbusChannels[5] = ((toInt(self.sbusFrame[7])>>7 |toInt(self.sbusFrame[8])<<1 |toInt(self.sbusFrame[9])<<9) & 0x07FF);
self.sbusChannels[6] = ((toInt(self.sbusFrame[9])>>2 |toInt(self.sbusFrame[10])<<6) & 0x07FF);
self.sbusChannels[7] = ((toInt(self.sbusFrame[10])>>5 |toInt(self.sbusFrame[11])<<3) & 0x07FF);
self.sbusChannels[8] = ((toInt(self.sbusFrame[12]) |toInt(self.sbusFrame[13])<<8) & 0x07FF);
self.sbusChannels[9] = ((toInt(self.sbusFrame[13])>>3 |toInt(self.sbusFrame[14])<<5) & 0x07FF);
self.sbusChannels[10] = ((toInt(self.sbusFrame[14])>>6 |toInt(self.sbusFrame[15])<<2|toInt(self.sbusFrame[16])<<10) & 0x07FF);
self.sbusChannels[11] = ((toInt(self.sbusFrame[16])>>1 |toInt(self.sbusFrame[17])<<7) & 0x07FF);
self.sbusChannels[12] = ((toInt(self.sbusFrame[17])>>4 |toInt(self.sbusFrame[18])<<4) & 0x07FF);
self.sbusChannels[13] = ((toInt(self.sbusFrame[18])>>7 |toInt(self.sbusFrame[19])<<1|toInt(self.sbusFrame[20])<<9) & 0x07FF);
self.sbusChannels[14] = ((toInt(self.sbusFrame[20])>>2 |toInt(self.sbusFrame[21])<<6) & 0x07FF);
self.sbusChannels[15] = ((toInt(self.sbusFrame[21])>>5 |toInt(self.sbusFrame[22])<<3) & 0x07FF);
#17頻道,第24字節(jié)的最低一位
if toInt(self.sbusFrame[23]) & 0x0001 :
self.sbusChannels[16] = 2047
else:
self.sbusChannels[16] = 0
#18頻道,第24字節(jié)的低第二位,所以要右移一位
if (toInt(self.sbusFrame[23]) >> 1) & 0x0001 :
self.sbusChannels[17] = 2047
else:
self.sbusChannels[17] = 0
#幀丟失位為1時(shí),第24字節(jié)的低第三位,與0x04進(jìn)行與運(yùn)算
self.failSafeStatus = self.SBUS_SIGNAL_OK
if toInt(self.sbusFrame[23]) & (1 << 2):
self.failSafeStatus = self.SBUS_SIGNAL_LOST
#故障保護(hù)激活位為1時(shí),第24字節(jié)的低第四位,與0x08進(jìn)行與運(yùn)算
if toInt(self.sbusFrame[23]) & (1 << 3):
self.failSafeStatus = self.SBUS_SIGNAL_FAILSAFE
def update(self):
"""
我們需要至少2幀大小,以確保找到一個(gè)完整的幀
所以我們?nèi)〕鏊械木彺妫ㄇ蹇账?,讀取全部數(shù)據(jù),直到捕獲新的數(shù)據(jù)
首先找到END BYTE并向后查找SBUS_FRAME_LEN,看看它是否是START BYTE
"""
#我們是否有足夠的數(shù)據(jù)在緩沖區(qū)和有沒(méi)有線程在后臺(tái)?
if self.ser.inWaiting() >= self.SBUS_FRAME_LEN*2 and self.isReady: #inWaiting()返回接收緩存中的字節(jié)數(shù)
self.isReady = False #表明有線程在運(yùn)行,isReady = False
# 讀取所有臨時(shí)幀數(shù)據(jù)
tempFrame = self.ser.read(self.ser.inWaiting())
# 在緩沖區(qū)幀的每個(gè)字符中,我們尋找結(jié)束字節(jié)
for end in range(0, self.SBUS_FRAME_LEN):
#尋找結(jié)束字節(jié),從后向前查找
if tempFrame[len(tempFrame)-1-end] == self.END_BYTE :
#從最后的命中點(diǎn)減去SBUS_FRAME_LEN尋找起始字節(jié)
if tempFrame[len(tempFrame)-end-self.SBUS_FRAME_LEN] == self.START_BYTE :
# 如果相等,則幀數(shù)據(jù)正確,數(shù)據(jù)以8E2包到達(dá),因此它已經(jīng)被校驗(yàn)過(guò)
# 從臨時(shí)幀數(shù)據(jù)中取出剛驗(yàn)證正確的一段正確幀數(shù)據(jù)
lastUpdate = tempFrame[len(tempFrame)-end-self.SBUS_FRAME_LEN:len(tempFrame)-1-end]
if not self.sbusFrame == lastUpdate: #相等即表示沒(méi)有操作,不用再次解碼
self.sbusFrame = lastUpdate
self.decode_frame() #調(diào)用解碼函數(shù)
self.lastFrameTime = time.time() # 跟蹤最近的更新時(shí)間
self.isReady = True
break
if __name__ == '__main__':
sbus = SBUSReceiver('/dev/ttyAMA0')
while True:
time.sleep(0.005)
# X8R的SBUS信號(hào)是間隔6ms發(fā)送一次,一次持續(xù)發(fā)送3ms;
# 不要調(diào)用sbus.update()太快,如果sbus.ser.inWaiting()>50,且增長(zhǎng)很多,可以調(diào)用sbus.update()快點(diǎn),即time.sleep()延遲短點(diǎn);
# 如果sbus.ser.inWaiting()<50,可以調(diào)用sbus.update()慢點(diǎn),即time.sleep()延遲長(zhǎng)點(diǎn);
sbus.update()
#在您的代碼中,您可以調(diào)用sbus.get_rx_channels()來(lái)獲取所有數(shù)據(jù),或者調(diào)用sbus.get_rx_channels()[n]來(lái)獲取第n個(gè)通道的值;
#或get_rx_channel(self, num_ch)來(lái)獲得第num_ch個(gè)通道的值;
print sbus.get_failsafe_status(), sbus.get_rx_channels(), str(sbus.ser.inWaiting()).zfill(4) , (time.time()-sbus.lastFrameTime)
#str() 函數(shù)將對(duì)象轉(zhuǎn)化為適于人閱讀的形式,將指定的值轉(zhuǎn)換為字符串。
#zfill() 方法返回指定長(zhǎng)度的字符串,原字符串右對(duì)齊,前面填充0。
#(time.time()-sbus.lastFrameTime)用于展示得到最近這次數(shù)據(jù)的延遲
-
運(yùn)行程序后,依次打印出了failsafe狀態(tài)值、所有通道的10進(jìn)制數(shù)組、讀取緩存中的字節(jié)數(shù)、當(dāng)次數(shù)據(jù)更新的延遲時(shí)間??刂七b控器搖桿晃動(dòng),能夠及時(shí)得到該通道的數(shù)值變化。
從實(shí)驗(yàn)數(shù)據(jù)中可以看出,三個(gè)檔位的通道的下檔值為172,中間檔位時(shí)值為992,上檔位時(shí)值為1811;2個(gè)檔位的下檔值為172,上檔值為1811,搖桿在中間位置時(shí)值為992,向其它方向搖動(dòng)時(shí),數(shù)值向172或1811變化。
使用上面的數(shù)值,通過(guò)函數(shù)轉(zhuǎn)換,就可以輸出相應(yīng)通道的PWM控制信號(hào),或者其它開(kāi)關(guān)控制信號(hào)了!為什么不直接使用PWM輸出呢?因?yàn)檫@樣可以通過(guò)無(wú)線電遠(yuǎn)距離控制樹(shù)莓派了,再通過(guò)樹(shù)莓派編程,控制其他設(shè)備,比如樹(shù)莓派無(wú)人機(jī)或者樹(shù)莓派智能小車(chē),特別是在沒(méi)有移動(dòng)網(wǎng)絡(luò)信號(hào)的時(shí)候。
遙控器的數(shù)字通道17/18沒(méi)有搞明白怎么用,所以這里沒(méi)有能夠測(cè)試,有知道的同學(xué)可以留言。

我的博客即將同步至騰訊云+社區(qū),邀請(qǐng)大家一同入駐:https://cloud.tencent.com/developer/support-plan?invite_code=3pjcxw02g6o04

