樹(shù)莓派基礎(chǔ)實(shí)驗(yàn)39:解析無(wú)線電接收機(jī)PWM、SBUS信號(hào)

一、介紹

??雖然如今或者將來(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ā)送到航模的控制器中。

發(fā)射器與接收機(jī)

目前用于無(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ú)需做特殊配置。

睿思凱Frsky Taranis X9D PLUS SE2019遙控器
睿思凱Frsky X8R 接收機(jī)

然后,就是電調(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周期

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ī)、連接邏輯分析儀。

接收機(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)通道搖桿往左搖到底時(shí)

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


1號(hào)通道搖桿往右搖到底時(shí)

再看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)通道油門(mén)為0,搖桿往下?lián)u到底時(shí)

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


pwm_acc_100.jpg

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


pwm_center.jpg

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。

開(kāi)關(guān)為下檔時(shí)

開(kāi)關(guān)為中檔時(shí)

開(kāi)關(guān)為上檔時(shí)

在采集接收機(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]


SBUS幀格式

簡(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)
未做電平反向時(shí)的SBUS信號(hào)

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


未做電平反向時(shí)的起始字節(jié)
未做電平反向時(shí)的結(jié)束字節(jié)
3.電平反向后的SBUS信號(hào)
電平反向后的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>

電平反向后的起始字節(jié)

波特率:上圖中可以看出每位的時(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。

幀與幀之間的間隔為6ms

每幀數(shù)據(jù)時(shí)長(zhǎng)為2.990ms,有10us的誤差,應(yīng)該是3ms:
每幀數(shù)據(jù)時(shí)長(zhǎ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。


幀丟失位由0變?yōu)?

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

故障保護(hù)激活位也變?yōu)?

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


無(wú)脈沖

四、實(shí)驗(yàn)步驟

(一) 樹(shù)莓派解析接收機(jī)PWM信號(hào)

  1. 連接線路。將接收機(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)薄片是天線。

樹(shù)莓派解析接收機(jī)PWM信號(hào)接線圖
  1. 編寫(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()
  1. 測(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
  2. 當(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%左右。
pwm_analyse_mistake

(二) 分析接收機(jī)SBUS信號(hào)

  1. 連接電路。與樹(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電源。

電平反相器
解析SBUS信號(hào)接線圖
  1. 這里樹(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ù)的延遲
  1. 運(yùn)行程序后,依次打印出了failsafe狀態(tài)值、所有通道的10進(jìn)制數(shù)組、讀取緩存中的字節(jié)數(shù)、當(dāng)次數(shù)據(jù)更新的延遲時(shí)間??刂七b控器搖桿晃動(dòng),能夠及時(shí)得到該通道的數(shù)值變化。


  2. 從實(shí)驗(yàn)數(shù)據(jù)中可以看出,三個(gè)檔位的通道的下檔值為172,中間檔位時(shí)值為992,上檔位時(shí)值為1811;2個(gè)檔位的下檔值為172,上檔值為1811,搖桿在中間位置時(shí)值為992,向其它方向搖動(dòng)時(shí),數(shù)值向172或1811變化。

  3. 使用上面的數(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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容