地面站開發了一段時間了,由于沒有硬件,所以一直在 APM 模擬器中驗證。我們已經實現了 MAVLink 消息接收和解析,顯示無人機狀態,給無人機發送消息,實現一鍵起飛,飛往指定地點,降落,返航等功能,本期我們來看看如何在模擬器中實現對無人機的遙控。
【注意】
- 本期所說的遙控無人機都是指遙控無人機模擬器,不涉及硬件在環。
- 前方有墜機風險,請謹慎駕駛。
遙控的基本知識
遙控分為發送端和接收端,發送端自然是遙控器,接收方自然就是無人機。遙控信號一般是一系列數值,通過無線電傳輸。每個數值被稱為一個通道(channel),所以在無人機中,遙控信號一般也稱為 Radio Channel,簡稱 RC。通道個數以及每個通道的作用取決于軟件實現,比如 APM 共有18個通道。
無人機收到 RC 控制信號后,會通過飛控將其轉換為 PWM 信號,然后輸出給電調來控制電機轉速。飛控就是飛行控制模塊,是一個集成了多種傳感器的嵌入式系統,配有飛控固件,可以通過地面站設置固件參數,簡化了無人機的開發流程。
RC 信號可以理解為一個整數數組,其中有些來自搖桿,有些來自按鈕,通過遙控器發送給無人機。當然,除了遙控,地面站也能發送這個數組來模擬遙控控制,所以 RC 信號的來源是可以不唯一的。此外,遙控和地面站是不同的東西,雖然在物理上可以把它們封裝在一個盒子里。因為我用的是 AMP 模擬器,所以本期所講的內容也都是基于 APM 飛控平臺。
通過mavproxy遙控無人機
由于沒有無人機硬件,我首先想到的是能不能通過 mavproxy 來模擬遙控控制。mavproxy 的確提供了 rc
命令來手動控制無人機,但是官網上這部分資料不夠詳細,對新手不太友好。簡單來說就是我們可以通過 rc <channel> <value>
命令來修改某個 RC 通道的值,APM 共有 18 個通道,每個通道的范圍都是 0~65535 (2字節無符號整數),有效范圍是 1000~2000,對于搖桿,1500 表示中立位置,而 0 和 65536 具有特殊含義。
- 0 表示將該通道的值釋放回遙控器。什么意思呢?也就是采用遙控器上對應通道的值,或者說將該通道的控制權交還給遙控器,為什么呢?因為 RC 輸入的來源可以不止是遙控器。
- 65535 表示忽略該通道的值,通過地面站模擬遙控時,如果不想修改某些通道的值,就可以將這些通道的值都設置為 65535。
在 mavproxy 的控制臺,我們可以通過以下命令來查看各個通道的最小值,最大值和中立值,其中 x
是通道編號,取值為 1,2,3…等。
param fetch RCx_MIN
:最小值。param fetch RCx_MAX
:最大值。param fetch RCx_TRIM
:中立值。
對于我現在的需求,用不到所有的通道,主要用到的是前4個通道,分別控制無人機的姿態和油門。至于為什么俯仰和偏航不是連著的,我也表示很好奇。
通道 | 值 | 含義 |
---|---|---|
RC1 | 1000~2000 | 滾轉(roll) |
RC2 | 1000~2000 | 俯仰(pitch) |
RC3 | 1000~2000 | 油門(throttle) |
RC4 | 1000~2000 | 偏航(yaw) |
首先我們還是通過一鍵起飛的方式讓無人機先飛起來,用 mavproxy 命令行或者 QGC 都可以。一鍵起飛之后,無人機會處于 guided
模式,這個模式下無人機會飛往地圖上點擊的位置或者指定的某個經緯度。 guided
模式不支持手動控制,手動模式是 manual
,在 APM 中,它等價于 stabilize
模式,也就是模擬器啟動時的默認模式。
不過在使用 mode
切換模式之前,我們需要先使用 rc 3 1500
命令將油門置于中立位置。因為 APM 模擬器啟動時,油門默認是處于最低位置的,也就是1000。如果此時直接切換到手動模式,無人機就會因失去動力而墜機,再現經典的“黑鷹墜落”。
Mayday, Mayday! Black Hawk is going down!
現在我們可以使用 mode stabilize
切換到手動模式了。然后我們可以通過 rc 3 1600
讓無人機上升, rc 3 1400
下降,或者通過 rc 1 1800
轉向。RC1 到 RC4 你可以都試試,如果你還開著 QGroundControl 地面站,可以通過姿態儀和指南針查看無人機狀態,因為 mavproxy 的地圖沒有姿態儀和指南針,所以 QGC 上會看得比較直觀。唯一需要注意的就是,小心墜機!
mode guided
arm throttle
takeoff 40rc 3 1500
mode stabilizerc 3 1600
rc 1 1800
... ...
我們已經學會了手動控制無人機飛行,那么能否直接手動起飛呢?
答案是肯定的。因為 APM 模擬器啟動時默認就出于 stabilize
模式,因此我們可以直接解鎖,然后轟油門起飛。
arm throttle
rc 3 1600
throttle
就是油門的意思,arm
是武裝的意思,arm throttle
就是解鎖油門。注意在無人機領域,系統狀態不是用”已上鎖“和”未上鎖“來描述,而是用”武裝“和”解除武裝“來描述。”鎖“表示安全,”武裝“表示危險,因此他們對狀態的表述是相反的,“武裝”的含義是解鎖,“解除武裝”是上鎖。
但是在實操上有個需要注意的點,那就是解鎖后需要馬上轟油門才能成功起飛,否則無人機會重新上鎖,導致起飛失敗。這個間隔時間很短,這可能跟我調快了仿真速度有關。這其實是因為無人機的自動鎖定機制,當無人機落地,電機停轉之后,會自動解除武裝,是無人機的一種安全保護機制。
如果來不及輸入命令,我們還可以先將油門設置為 1500,然后使用 arm throttle force
強制解鎖。
rc 3 1500
arm throttle force
rc 3 1600
如果不強制解鎖,而是使用 arm throttle
,模擬器就會報錯 Arm: Throttle (RC3) is not neutral
,意思是油門處于不正常位置,這也是出于對無人機的保護,防止彈射起步。如果在連續的操作過程中導致油門沒有處于最低位,在一鍵起飛的時候也會看到這個報錯,只需要用 rc 3 1000
將油門置于最低位即可。有時候報的錯可能不是 RC3,而是其他通道,也是通過 rc
命令將其設置到中立位置(1500)即可。曾經在網上看到過某些無人機在起飛之前要先將油門擋桿扒到最左,其實也是這個原因。
如果想查看 RC 輸入和電機輸出,可以點擊 mavproxy 圖形命令窗口(Console)菜單欄的 Tools 菜單。
然后在彈出菜單中選擇 RC Inputs 或 Servo Outputs 就能看到每個 RC 通道的值和電機輸出了。
上圖左邊就是初始時各 RC 通道的值。
通過代碼遙控無人機
知道如何手動控制無人機之后,我們還要知道是怎么實現的。我想知道 rc
命令是如何發送給無人機的呢?首先想到的是抓包,如果我們觀察 mavproxy 的啟動命令,會發現它設置了三個通信地址:
--sitl 127.0.0.1:5501
--master tcp:127.0.0.1:5760
--out 127.0.0.1:14550
14550
我們已經很熟悉了,這是 mavproxy 轉發 mavlink 消息的地址,也是其他地面站連接的地址。 5760
是與模擬器通信的地址, 5501
是與仿真環境通信的地址,也是 UDP 協議。
為了弄清 rc
命令背后的原理,我對 5501
和 5760
兩個端口進行了抓包,結果顯示 rc
命令是通過 5501
端口發送出去的。但問題是發送的數據并不是 mavlink 格式,而是下面這么一串數據。
為了一探究竟,也只好去看看 mavproxy 的源碼了。 rc
命令的源碼在 MAVProxy/modules/mavproxy_rc.py
文件中,我們找到 cmd_cr
這個函數。
def cmd_rc(self, args):'''handle RC value override'''if len(args) > 0 and args[0] == 'set':self.rc_settings.command(args[1:])returnif len(args) == 1 and args[0] == 'clear':channels = self.overridefor i in range(self.count):channels[i] = 0self.set_override(channels)returnif len(args) == 1 and args[0] == "status":self.cmd_rc_status()returnif len(args) == 1 and args[0] == "guiin":if not mp_util.has_wxpython:print("No wxpython detected. Cannot show GUI")elif sys.version_info >= (3, 10) and sys.modules['wx'].__version__ < '4.2.1':print("wxpython needs to be >=4.2.1 on Python >=3.10. Cannot show GUI")elif not self.rcin_gui:from MAVProxy.modules.lib import wxrcself.rcin_gui = wxrc.RCStatus(panelType=wxrc.PanelType.RC_IN)returnif len(args) == 1 and args[0] == "guiout":if not mp_util.has_wxpython:print("No wxpython detected. Cannot show GUI")elif sys.version_info >= (3, 10) and sys.modules['wx'].__version__ < '4.2.1':print("wxpython needs to be >=4.2.1 on Python >=3.10. Cannot show GUI")elif not self.servoout_gui:from MAVProxy.modules.lib import wxrcself.servoout_gui = wxrc.RCStatus(panelType=wxrc.PanelType.SERVO_OUT)returnif len(args) != 2:print("Usage: rc <set|channel|all|clear|status|guiin|guiout> <pwmvalue>")returnvalue = int(args[1])if value > 65535 or value < -1:raise ValueError("PWM value must be a positive integer between 0 and 65535")if value == -1:value = 65535channels = self.overrideif args[0] == 'all':for i in range(self.count):channels[i] = valueelse:channel = int(args[0])if channel < 1 or channel > self.count:print("Channel must be between 1 and %u or 'all'" % self.count)returnchannels[channel - 1] = valueself.set_override(channels)
從這個函數中,能看到 rc
命令有很多子命令, channel
就是我們上一節提到的用法, all
可以用來設置所有通道的值。 rc status
可以打印出每個通道的值,但是注意它不是打印的真實通道值,而是本地的副本,默認都是0,如果通道沒有修改過,那么打印出來就會是0。而 rc guiin
和 rc guiout
對應的其實就是上一節中 Tools 菜單下的 RC Inputs 和 Servo Outputs 兩個子菜單,用這兩個命令也能彈出對應的窗口。
回到函數的后半段,函數將命令中設置的 value
首先記錄到本地 channels
副本,然后調用了 set_override
函數設置通道值,該函數定義如下。
def set_override(self, newchannels):'''this is a public method for use by drone API or other scripting'''self.override = newchannelsself.override_counter = 10self.send_rc()def send_rc(self):'''send RC override packet'''if self.sitl_output:chan16 = self.override[:16]buf = struct.pack('<HHHHHHHHHHHHHHHH', *chan16)self.sitl_output.write(buf)else:chan18 = self.override[:18]self.master.mav.rc_channels_override_send(self.target_system,self.target_component,*chan18)
set_override
最終調用了 send_rc
函數,而在 send_rc
函數中,將 channels
發送了出去。因為我們啟動 mavproxy 時設置了 sitl
參數,所以這里會進入 if
分支,也就是將 RC 通道的值發送到了 5501
端口。而且這里對數據的序列化方式也是直接使用了 struct.pack
, '<HHHHHHHHHHHHHHHH'
是序列化方式, <
表示采用小端序,每個 H
表示兩個字節,也就是 chan16
數組里每個整數用兩字節表示,所以我們抓包時抓到 32 字節也就不足為奇了,將它們按每兩個字節拆開如下。
dc05 dc05 dd05 dc05 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000
0x05dc
剛好就是十進制的 1500
,第三個 0x05dd
,而我測試用的命令正是 rc 3 1501
。
下一步我們需要在地面站中通過 mavlink 協議發送遙控信號實現手動控制無人機,這里有兩個消息比較重要。一是 RC_CHANNELS (65)
,他是 無人機向地面站發送的當前 RC 通道值。二是 RC_CHANNELS_OVERRIDE (70)
,它是地面站發送給無人機的 RC 控制消息。
一是 RC_CHANNELS (65)
,他是 無人機向地面站發送的當前 RC 通道值。
二是 RC_CHANNELS_OVERRIDE (70)
,它是地面站發送給無人機的 RC 控制消息。要實現手動控制無人機,發送這個消息就行了。
這里給出兩篇官方參考文章:
- RC Input (aka Pilot Input) — Dev documentation
- https://mavlink.io/zh/messages/common.html#RC_CHANNELS
看吧,一點都不難。