Avtor: dr. Simon Vavpotič
V preteklem nadaljevanju smo lotili programiranja krmilnikov dronov. Začeli smo z osnovnimi funkcijami, kot sta krmiljenje motorjev s pomočjo pulzno-širinske modulacije (PWM) in zajemanje vrednosti iz merilnika pospeškov ter nadaljevali s programskim upravljanjem drona.
Ta vsebina je samo za naročnike
Za preverjanje pravilnosti delovanja programov smo uporabili tudi simulator drona, s katerim lahko predhodno preverimo odzivanje programske kode v Pythonu v različnih situacijah, ne da bi, zaradi pogosto trivialnih programerskih napak, tvegali poškodovanje ali uničenje pravega drona. Tokrat se lotimo programiranja pravega drona na osnovi ESP32 modula in MicroPythona.



Dron z MikroPython krmilnikom
ESP32 modul je zaradi majhne teže in ugnezdene Wi-Fi/Bluetooth komunikacije kot nalašč za gradnjo manjših dronov. Na spletu najdemo množico majhnih gotovih odprtokodnih dronov na osnovi ESP32 pa tudi takih za sestavljanje. Programiramo jih lahko v različnih razvojnih okoljih, kot so: Arduino, Espressif Systems IDF, Microsoft Visual Studio Code in Thonny (MicroPython). Ker se pričujoča serija člankov osredotoča na programiranje v Pythonu in MicroPythonu, se bomo v nadaljevanju lotili izdelave krmilnega programa v MicroPythonu za upravljanje pravega drona.
Ko v spletni iskalnik vnesemo geslo »ESP32 drone MicroPython«, odkrijemo kar nekaj zanimivih projektov. Podrobnejši odgovor dobimo, če isto geslo vnesemo v Google AI ali Chat GPT. Dron za osnovno delovanje in poganjanje interpreterja za MicroPython sicer potrebuje osnovno programsko opremo v obliki nizkonivojskih programskih knjižnic, katerih izvorna koda je največkrat v programskem jeziku C++, vendar ta del programja navadno podpira zgolj osnovne funkcije, medtem kot je logika za usmerjanje in odločanje med poletom prepuščena programski kodi v katerem od enostavnejših programskih jezikov, denimo MicroPythonu.
PyDrone [1] je odličen za učenje programiranja dronov v MicroPythonu, saj njegova ugnezdena programska oprema podpira upravljanje z visokonivojskimi ukazi, kot sta: drone.take_off() in drone.landing(), ki samodejno poskrbita za varen vzlet in pristanek drona. Vodenje drona zato izvedemo s (skoraj) enako programsko opremo kot vodenje njegovega modela v simulatorju.
Izvorno kodo projekta PyDrone najdemo na [2], kjer je tudi obsežna dokumentacija, a žal samo v kitajščini. Dron uporablja MPU6050 merilnik pospeška in druge senzorje, med katerimi je tudi kamera.
Flix [3] je podoben projekt, ki prav tako temelji na ESP32-S3 modulu, a je k sreči vsa dokumentacija v angleščini. Priloženi so tudi videi poletov z dronom. Ugnezdena programska oprema vsebuje vse glavne Python knjižnice, priložen pa je tudi simulator, ki deluje v okolju Gazebo 11, ki ga lahko poganjamo v operacijskem sistemu Ubuntu 20.04. Za druge Linux OS so potrebne prilagoditve. Tako lahko programsko opremo preizkušamo tudi brez pravega drona. Navodila za uporabo sicer niso za začetnike, a so podrobna in natančna.
Ugnezdena programska oprema
V programsko kodo MicroPythonskih knjižnic se ne gre spuščati, zato pa je toliko zanimivejša koda, ki z visoko nivojskimi ukazi usmerja in nadzira delovanje drona. Na spletu najdemo kar nekaj primerov za pyDrona, programsko kodo pa lahko, tako kot za simulacijo drona, izdela tudi Google AI. Poglejmo program, ki iz daljinskega upravljalnika sprejema osnovne ukaze in upravlja drona.
Prvi deli programa izvede pripravo drona za letenje, ki obsega kalibracijo in zagon Wi-Fi dostopne točke, na katero se povežemo z Wi-Fi postajo (daljinskim upravljalnikom) in ga upravljamo.
import network,socket,time
from machine import Timer
import drone
izgradnja objekta drona in izbira načina delovanja
d = drone.DRONE(flightmode = 0)
Kalibracija delovanja dona
while True:
#izpis kalibraciskih podatkov
print(d.read_cal_data())
#Preverjanje, ali je dron že skalibriran
if d.read_calibrated():
print(d.read_cal_data())
break # prekinitev neskončne zanke in nadaljevanje glavnega programa po
uspešni kalibraciji
time.sleep_ms(100) # čakanja 100 ms
za ponovitev kalibracije
Podprogram za vzpostavitev dostopne točke, na katero se poveže daljinski upravljalnik
def startAP():
wlan_ap = network.WLAN(network.AP_IF)
print(‘Connect pyDrone AP to Config WiFi.’)
wlan_ap.active(True)
wlan_ap.config(essid=’pyDrone’,authmode=0)
while not wlan_ap.isconnected(): #čakanje na povezavo klienta
pass
Nadaljevanje glavnega programa za zagonom
dostopne točke, s katero se poveže
daljinski upravljalnik
startAP()
Drugi del programa se izvede, ko se daljinski upravljalnik poveže z dronom. Krmilnik slednjega (ESP32 modul) vzpostavi programske strukture za komunikacijo (t.i. žep), v katere se prenašajo sporočila z daljinskega upravljalnika, določi tudi svoj IP naslov za komunikacijo in IP naslov daljinskega upravljalnika. Komunikacija poteka prek vrat 2390. Vse omenjeno poteka prek protokola UDP in lahko steče šele, ko je vzpostavljena osnova povezava med doronom in daljinskim upravljalnikom na fizičnem nivoju. V nadaljevanju vidimo ukaz connect iz objekta žep, s katerim se vzpostavi komunikacijska povezava tudi na logičnem nivoju – med aplikacijo drona in aplikacijo daljinskega upravljalnika.
UDP socket izgradnja objekta (žepa) komunikacijo z daljinskim upravljalnikom
s=socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.bind((‘0.0.0.0’, 2390)) #določitev komunikacijskih vrat strežnika (oz. drona): 2390
določitev naslova strežnika IP naslov strežnika (oz. drona):192.168.4.1
data,addr = s.recvfrom(128)
print(addr)
vzpostavitev povezave z daljinskim upravljalnikom
s.connect(addr)
s.setblocking(False) #ni čakanja na odziv
pri pošiljanju in sprejemanju podatkov
Tretji del programa predstavlja funkcija komunikacijskega žepka, ki so jo kitajski avtorji poimenovali Socket_fun, pri čemer vsekakor niso mislili na zabavo, ampak na funkcijo. Funkcija s prav takim imenom je tudi v programu daljinskega upravljalnika, vendar opozorimo, da je v njej povsem druga koda. Namen funkcije Socket_fun drona je zajem krmilnih ukazov in njihova prevedba na osnovne krmilne funkcije.
Prvi del podprograma zajame 128 bajtov podatkov, iz njih prebere želene vrednosti zasuka, nagiba, vrtenja in potiska, ki jih nato prek ukaza control posreduje objektu drona d, v katerega so ugnezdene nizkonivojske funkcije za upravljanje elektronike. Pri tem je obseg vsake od vrednosti med -100 in 100 (predznačeno 1-bajtno število), kar zahteva prilagoditev iz nepredznačenega 1-bajtnega števila, pri čemer se vrednosti med 100 in 155 preslikajo v vrednost 0, vrednosti med 0 in 100, v negativno vrednost med -100 in 0, vrednosti nad 155 do 255 pa vrednosti od 0 do 100. Tako se hkrati izognemo vplivom zelo majhnih odmikov krmilne palice, ki jih pri vodenju drona morda nehote naredimo s prsti, ali pa so stalno prisotni, če krmilna palica ni dobro kalibrirana.
podprogram za krmiljenje dronove elektronike
def Socket_fun(tim):
try:
text=s.recv(128) #sprejem bloka 128 podatkov
control_data = [None]*4
#analiza zajetnih podatkov za usmerjanje drona
for i in range(4):
if 100 < text[i+1] < 155 :
control_data[i] = 0
elif text[i+1] <= 100 :
control_data[i] = text[i+1] – 100
else:
control_data[i] = text[i+1] – 155
print(‘control:’,control_data)
#rol:[-100:100],rol:[-100:100],yaw:[-100:100],thr:[-100:100]
d.control(rol = control_data[0], pit
= control_data[1], yaw = control_data[2],
thr = control_data[3])
V nadaljevanju funkcije žepa so podprte še funkcije tipke standardne igralne tablice A, B, X in Y. Tipka Y sproži funkcijo take_off, ki drona samodejno dvigne na višino 120 cm nad zemljo. Konstanto v programu lahko seveda prilagodimo svojim željam. Tipka A sproži samodejni pristanek drona s pomočjo ukaza landing, tipka X pa ugasne motorje drona. Iz programa vidimo, da tipka B zaenkrat še nima določene funkcije, razen tega, da na standardno konzolo za razhroščevanje izpiše črko B.
#izvajanje funkcij, ki so vezane na
funkcijske tipke upravljavske konzole oz. igralne palice
if text[5] == 24: #Tipka Y na igralni
tablici sproži vzlet drona na višino 120 cm od tal.
print(‘Y’)
#vzlet drona, ki se dvigne 120 cm od tal
d.take_off(distance = 120)
if text[5] == 72: #Tipka A na igralni tablici sproži samodejno pristajanje.
print('A')
#dron samodejno mehko pristane
d.landing()
if text[5] == 40: #Tipka B na igralni tablici je še brez funkcije.
print('B')
if text[5] == 136: # Tipke X na igralni tablici ustavi motorje drona.
print('X')
#zaustavitev motorjev drona
d.stop()
Zadnji del funkcije žepa ima nalogo sporočanja stanja drona, ki ga vrne daljinskemu upravljalniku v polju devetih 2-bajtnih vrednostih, ki vsebujejo vrednosti nagiba, zasuka,
states = d.read_states()
print('states: ',states)
state_buf = [None]*18
for i in range(9):
for j in range(2):
if j == 0:
state_buf[i*2+j] = int((states[i]+32768)/256)
else:
state_buf[i*2+j] = int((states[i]+32768)%256)
s.send(bytes(state_buf)) #posrednovanje stanja drona prek povezave Wi-Fi
except OSError:
pass
Nastavitev periodičnega preverjanje zajema podatkov prek Wi-Fi na vsakih 50 ms.
tim = Timer(1)
tim.init(period=50, mode=Timer.PERIODIC,
callback=Socket_fun)
Kot vidimo je program sorazmerno enostaven za razumevanje. Lahko ga realiziramo celo zgolj z ESP32 modulom, na katerega poprej naložimo programsko opremo pyDrone. Brez motorjev in zmogljive baterija sicer modul ne bo mogel leteti, a lahko s štirimi LED (namesto motorjev) vseeno preverimo osnovno delovaje programske kode, če imamo tudi daljinski upravljalnik.
MicroPython program za daljinski upravljalnik
Daljinski upravljalnik sestavljajo: standardna igralna ploščica s dvema igralnima palicama in številnimi tipkami (lahko takšna kot jo uporabljamo za igranje računalniških igric), ESP32-S3 modul, ki rabi tudi za komunikacijo Wi-Fi/Bluetooth, majhen barvni prikazovalnik in baterijski modul za napajanje. ESP32-S3 modul je z igralno ploščico povezan prek svojega USB priključka, s prikazovalnikom pa prek GPIO priključkov. Za povezljivost skrbijo standardne programske knjižnice, ki so tudi del projekta pyDrone.
Prvi del programske kode daljinskega upravljalnika poskrbi za povezavo z dronom, ki deluje kot dostopna točka. Pri tem je IP naslov 192.168.4.1 strežniške aplikacije v prejšnjem poglavju opisane programske kode drona že nekaka stalnica za majhne projekte, v kateri smo obenem določili tudi komunikacijska vrata 2390. Lahko bi tudi druga, a vsekakor taka, ki še niso uporabljena za druge funkcionalnosti.
import network,usocket,time, controller
from machine import Pin,Timer
import tftlcd
Wi-Fi prijavni parametri za dostopno točko (dron)
SSID = ‘pyDrone’
PASSWORD = ”
addr=(‘192.168.4.1’,2390) #IP naslov in vrata dostopne točke
lokalni IP določi dostopna točka
ip_local = ”
za krmiljenje drona uporabljamo kar igralno ploščico ali igralko palic
gamepad = controller.CONTROLLER()
Drugi del programske kode daljinskega upravljalnika se ukvarja z zagonom TFT LCD prikazovalnika (ustvari objekt l), pri katerem najprej definira 3-bajtne konstante za posamezne barve, nato pa pobriše zaslon z belo barvo (l.fill(WHITE)).
definicija LCD prikazovalnika za prikaz osnovnih podatkov
l = tftlcd.LCD15()
določitev barv za barvni prikaz
RED = (255,0,0)
GREEN = (0,255,0)
BLUE = (0,0,255)
BLACK = (0,0,0)
WHITE = (255,255,255)
DEEPGREEN = (0,139,0)
izbriši prikazovalnik – da je v celoti bel
l.fill(WHITE)
Tretji del programske kode vzpostavi povezavo Wi-Fi z dronon, pri čemer uporablja GPIO priključek 46 za krmiljenje LED, ki sproti ponazarja stanje povezave Wi-Fi. Utripanje LED pomeni, da je vzpostavljanje povezave v teku, vzpostavljena pa je, ko LED stalno sveti.
Vzpostavitev Wi-Fi povezave
def WIFI_Connect():
global ip_local
WIFI_LED=Pin(46, Pin.OUT) #določitev priključka za Wi-Fi LED
wlan = network.WLAN(network.STA_IF) #izbira načina delovanje – postaja Wi-Fi
wlan.active(True)
vzpostavitev odjemalske Wi-Fi povezave z
dostopno točko
start_time=time.time()
shranitev začetnega časa
if not wlan.isconnected():
print('Connecting to network...')
l.printStr('Connecting WiFi...',10,10,color=BLUE,size=2)
l.printStr('SSID: pyDrone',10,60,color=BLACK,size=2)
l.printStr('KEY: None',10,100,color=BLACK,size=2)
wlan.connect(SSID,PASSWORD) #povezava za dronom (dostopno točko)
while not wlan.isconnected():
#Utripanje LED med zpostavljanjem Wi-Fi povezave
WIFI_LED.value(1)
time.sleep_ms(300)
WIFI_LED.value(0)
time.sleep_ms(300)
#Če povezava ni vzpostavljena v 15 sekundah, prekini vzpostavljanje
if time.time()-start_time > 15 :
print('WIFI Connected Timeout!')
wlan.active(False)
break
if wlan.isconnected():
#LED za Wi-Fi sveti, če je povezava vzpostavljena
WIFI_LED.value(1)
#Izpis informacij o nastavitvah Wi-Fi omrežja na prikazovalnik
print('network information:', wlan.ifconfig())
ip_local = wlan.ifconfig()[0]
return True
else:
return False
Ponavljaja klic zgoraj definirane funkcije
WIFI_Connect, dokler povezava ni vzpostavljena
while not WIFI_Connect():
pass
Četrti del programske kode poskrbi še za vzpostavitev logične UDP povezave z aplikacijo drona. Začne se z brisanjem vsebine prikazovalnika TFT LCD, nakar sledi povezava žepa z vrati 2390.
brisanje vsebine prikazovalnika – prebarvanje z belo
l.fill(WHITE)
Vzpostavitev UDP povezave s strežniško aplikacijo drona
s=usocket.socket(usocket.AF_INET, usocket.SOCK_DGRAM)
s.bind((ip_local, 2390)) # povezava z vrati
2390
s.setblocking(False) #brez čakanja na
vzpostavitev pri izvedbi ukaza connect
s.connect(addr)
Peti del programske predstavlja funkcija žepka daljinskega upravljalnika Socket_fun, ki najprej poskuša prebrati 128 bajtov podatkov iz drona s klicem funkcije recv(128), nakar iz prvih 18 bajtov prebere devet 2-bajtnih števil, ki opisujejo stanje drona v obliki vrednosti preteklih in trenutnih vrednosti vrtenja, nagiba in zasuka ter trenutne povprečne vrednosti potiska motorjev (večji potisk pomeni hitrejše premikanje). Prebere tudi napetost baterije, ki je izpisana zeleno, dokler je nad 3,1 V in z rdečo, ko je manjša ali enaka 3,1 V, kar pomeni, da je baterija drona že skoraj izpraznjena.
obdelava podatkov iz drona in krmiljenje
def Socket_fun(tim):
try:
text = s.recv(128) #sprejem bloka 128 bajtov podatkov iz drona
print(text) # izpis podatkov je zakomentiran,
state_buf = [None]*9
#zajem stanja drona iz prvih 9 2-bajtnih besed v strukturo state_buf
for i in range(9):
state_buf[i] = text[i*2]*256+text[i*2+1] - 32768
print(state_buf)
#izpis preteklih parametrov letenja: ROL(vrtenje), PIT(nagib), YAW(zasuk)
l.printStr('ROL: '+str('%.2f'%(state_
buf[0]/100))+’ ‘,10,10,color=BLACK,size=2)
l.printStr(‘PIT: ‘+str(‘%.2f’%(state_buf[1]/100))+’ ‘,10,40,color=BLACK,
size=2)
l.printStr(‘YAW: ‘+str(‘%.2f’%(state_
buf[2]/100))+’ ‘,10,70,color=BLACK,size=2)
#izpis trenutnih parametrov letenja:
ROL(vrtenje), PIT(nagib), YAW(zasuk), THRUST (potisk)
l.printStr(‘ROL: ‘+str(int(state_buf[3]/10))+’ ‘,10,110,color=BLUE,size=2)
l.printStr(‘PIT: ‘+str(int(state_
buf[4]/10))+’ ‘,130,110,color=BLUE,size=2)
l.printStr(‘YAW: ‘+str(int(state_
buf[5]/200))+’ ‘,10,140,color=BLUE,size=2)
l.printStr(‘THR: ‘+str(state_buf[6]*2-100)+’ ‘,130,140,color=BLUE,size=2)
#izpis višine trenutne letenja
l.printStr(‘ALT: ‘ + str(‘%.2f’%(state_buf[8]/100))+’ M ‘,10,180,color=
DEEPGREEN,
size=2)
#Izpis napetosti baterije z zeleno barvo, ki je ta večja od 3.1V
if state_buf[7] > 310 :
l.printStr(‘BAT: ‘+str(‘%.2f’%(state_
buf[7]/100))+’ V ‘,10,210,color=DEEPGREEN,size=2)
else: # Izpis napetosti baterije z
rdečo barvo, ki je ta enaka ali manjša od 3.1V
l.printStr(‘BAT: ‘+str(‘%.2f’%(state_
buf[7]/100))+’ V (LOW)’,10,210,color=RED,size=2)
except OSError:
pass
Zadnji del programske kode s funkcijo časovnika najprej vzpostavi samodejno periodično zaganjanje funkcije Socket_fun na vsakih 50 ms, nato pa na vsakih 50 ms v neskončni zanki posreduje stanje igralne plošče dronu.
Periodično izvajanje funkcije Socket_fun
za prikaz parametrov na prikazovalniku vzporedno z neskončno zanko
tim = Timer(1)
tim.init(period=50, mode=Timer.PERIODIC,callback=Socket_fun)
while True: # neskončna zanka za kermiljenje
v = gamepad.read() #branje stanja igralne plošče
s.send(bytes(v)) #posredovanje stanja igralne plošče dronu
time.sleep_ms(50) #počakaj 50 ms pred
ponovitvijo neskončne zanke
Kot vidimo, ima daljinski upravljalnik barvni prikazovalnik predvsem za lepši izgled. Strojno opremo lahko poenostavimo tako, da namesto prikaza na prikazovalniku uporabimo zaporedno povezavo module ESP32 in prikaz izvedemo na prenosnem računalniku. To zahteva le spremembo podprograma Socket_fun, ne pa tudi neskončne zanke, ki posreduje krmilne ukaze z igralne plošče dronu.
Prihodnjič
Tokrat nam je zmanjkalo prostora, da bi preverili tudi možnosti za gradnjo pametnega drona z Raspberry Pi 5 s 16 GB RAM in Raspberry Pi AI HAT+ 2 modulom z 8 GB RAM, ki omogoča hitro prepoznavanje predmetov v prostoru in uporabo velikih jezikovnih modelov (LLM) za pomoč pri odločanju. Zato se tega lotimo prihodnjič, ko si bomo ogledali še nekatere druge primere.
Viri:
1: cnx-software.com/2025/07/02/pydrone-esp32-s3-drone-programmable-with-micropython
2: github.com/01studio-lab/pyDrone
3: github.com/okalachev/flix
