Avtor: dr. Simon Vavpotič
Python in MikroPython sta popularna programska jezika med nadobudnimi mladimi programerji. Kako se lotiti kompleksnejšega projekta?
V preteklem nadaljevanju smo nadaljevali z opisi kompleksnejših programov v Pythonu in MicroPythonu, ki so lahko zelo uporabni pri praktičnem delu z majhnimi krmilnimi računalniki, kot je Raspberry Pi Pico 2, in načrtovanju zahtevnejše strojne opreme z vgrajeno umetno inteligenco, ki kot krmilnik uporablja računalnik na eni ploščici tiskanega vezja, kot je Raspberry Pi 5 z nameščenim Raspberry Pi HAT+ 2 (glej SE348). Videli smo, kako upravljamo GPIO priključke, sprogramirali pa smo tudi prenos podatkov po zaporedni povezavi RS232 s 3,3-voltnimi nivoji.
Ta vsebina je samo za naročnike
Tokrat se lotimo že obljubljenega programiranja krmilnikov dronov: Začeli bomo 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.


Za preverjanje pravilnosti delovanja programov bomo 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 – vrednega tudi več tisoč evrov.
Simulatorji omogočajo posnemanje različnih tipov dronov, boljši pa omogočajo tudi letenje v različnih navideznih prostorih pod različnim vremenskimi pogoji. V navidezne prostore lahko namestimo tudi realistične virtualne ovire, ki se jim mora dron izogibati. Najnaprednejši simulatorji lahko posnemajo celo sliko s kamere drona, ki jo procesiramo z v Pythonu z umetno-inteligenčnimi ali matematičnimi metodami in nato rezultate uporabimo pri navigaciji in izvajanju načrtovanih nalog.
PWM v Pythonu za osnovno krmiljenje motorjev
Program v Pythonu ali MicroPythonu omogoča enostavno spreminjanje funkcionalnosti splošno-namenskih vhodni-izhodnih priključkov (GPIO), če imamo na voljo ustrezne programske knjižnice. Poglejmo primer krmiljenja v Pythonu na Raspberry Pi (modeli 1 do 5). V prvem koraku uvozimo potrebno programsko knjižnico:
import RPi.GPIO as GPIO
Nato v skladu s standardnim označevanjem in možnostmi priključkov izberemo enega izmed GPIO priključkov, ki omogočajo strojno PWM modulacijo. Raspberry Pi 5 ima poleg BCM2712 (način označevanja BCM) sistema v enem čipu še R1 mikrokontroler (način označevanja BOARD). Zato najprej izberemo način označevanja GPIO priključkov (BCM), nakar izberemo priključek (priključek 18 navadno uporabljamo za PWM) in ga nastavimo kot digitalni izhod:
GPIO.setmode(GPIO.BCM)
pwm_pin = 18
GPIO.setup(pwm_pin, GPIO.OUT)
V naslednjem koraku nastavimo nosilno frekvenco PWM (v našem primeru 1000 Hz) in določimo trajanje logične enice (angl. duty cycle) na 33 odstotkov trajanja periode:
pwm_output = GPIO.PWM(pwm_pin, 1000)
pwm_output.start(33.33)
Ostane le še odgovor na vprašanje, ali lahko PWM funkcionalnost tudi prekinemo in vrnemo GPIO priključek v izhodiščno stanje. Poglejmo:
pwm_output.stop()
GPIO.cleanup()
Kot vidimo, za prekinitev PWM modulacije poskrbi prvi ukaz, ukaz cleanup pa vrne vse GPIO priključke v izhodiščna stanja in ponastavi način označevanja priključkov pred zagonom aplikacije v Pythonu.
PWM v MicroPythonu za osnovno krmiljenje motorjev
Raspberry Pi Pico (2) uporablja MicroPython, kjer za dostop do GPIO funkcionalnosti uporabljamo programsko knjižnico machine, iz katere uvozimo razreda Pin in PWM. Nato ustvarimo objekt pwm_pin, s katerim krmilimo pin 15:
from machine import Pin, PWM
pwm_pin = Pin(15)
Nato ustvarimo PWM objekt, s katerim na pinu 15 upravljamo PWM modulacijo:
pwm = PWM(pwm_pin)
pwm.freq(1000)
pwm.duty_u16(21845)
Kot vidimo, smo nastavili osnovno frekvenco 1000 Hz in trajanje enice na 33 odstotkov. Ker uporabljamo 16-bitni zapis, vrednost trajanja izračunamo kot 65536 / 3 = 21845 (zaokroženo na celo število).
Merjenje pospeškov v MicroPythonu
MPU6050 je eden najpopularnejših merilnikov pospeškov med samograditelji večjih dronov ter avtonomnih robotskih vozil in plovil. Z računalnikom komunicira prek vodila I2C s frekvenco do 400 kHz, kar s pridom izkoriščamo v MikroPythonu na Raspberry Pi Pico 2 W. MPU6050 povežemo prek priključkov Vcc (napajanje +3,3 V), GND (masa), GPIO0 (SDA – priključek 0) in GPIO1 (SCL – priključek 1). Poglejmo program:
from machine import Pin, I2C
i2c = I2C(0, sda=Pin(0), scl=Pin(1),
freq=400000)
Tudi tokrat uporabljamo programsko knjižnico machine, iz katere uvozimo razreda Pin in I2C. S pomočjo slednjega ustvarimo objekt i2c (poimenovan z malimi črkami – Python loči med velikim in malimi črkami), tako da uporabimo I2C vodilo 0, ter I2C krmilnik logično povežemo s priključkoma 0 (SDA) in 1 (SCL). Zadnji parameter je hitrost I2C vodila.



Za dostop do MPU6050 registrov potrebujemo ustrezne podprograme, še prej pa moramo MPU6050 zbuditi iz spanja in nastaviti merilnik pospeška na območje +/- 2 g, ki nastopi takoj po priklopu napajanje. Slednje naredimo z ukazoma:
i2c.writeto_mem(MPU6050_ADDR, PWR_MGMT_1,
b’\x00′)
i2c.writeto_mem(MPU6050_ADDR, DEVICE_ADDR,
ACCEL_CONFIG, b’\x00′)
s katerima v registra PWR_MGMT_1 in ACCEL_CONFIG zapišemo vrednosti 0. Nato lahko preberemo 16-bitne vrednosti pospeškov po vsaki od treh geometrijskih osi (X, Y, Z) z naslednjo funkcijo:
def read_raw_data(addr):
# Read two bytes and combine them into a 16-bit integer
high = i2c.readfrom_mem(MPU6050_ADDR, addr, 1)[0]
low = i2c.readfrom_mem(MPU6050_ADDR, addr + 1, 1)[0]
val = (high << 8) | low if val > 32767:
val -= 65536
return val
Za branje vseh treh osi tako potrebujemo v glavnem programu tri klice zgornje funkcije:
raw_x = read_raw_data(ACCEL_XOUT_H)
raw_y = read_raw_data(ACCEL_XOUT_H + 2)
raw_z = read_raw_data(ACCEL_XOUT_H + 4)
Povejmo še, da pretvorbo pospeškov v enoto g (g je težnostni pospešek 9.81 m/s2) izvedemo tako, da vsako od zgornjih vrednosti delimo s številom 16384. Tako dobimo obseg od -2 g do +2 g.
Merjenje pospeškov v Pythonu samo z uporabo gonilnika I2C
Tudi v Pythonu lahko MPU6050 uporabljamo neposredno prek I2C gonilnika, lahko pa namesto tega uporabimo namensko programsko knjižnico, v kateri so že definiranje višje-nivojske funkcije. Raspberry Pi 5 ima na voljo dve I2C vodili, smbus in smbus 2. Program je sicer po zgradbi zelo podoben tistemu v MicroPythonu:
import smbus
bus = smbus.SMBus(1)
Po uvozu programske knjižnice smbus izberemo vodilo in ustvarimo njegov objekt. Podprogram za komunikacijo prek I2C protokola je nato zelo podoben tistemu v MicroPythonu:
def read_word_2c(reg):
»««Read two bytes and combine them into a signed 16-bit integer.««« high = bus.read_byte_data(DEVICE_ADDR, reg) low = bus.read_byte_data(DEVICE_ADDR, reg + 1) val = (high << 8) + low if val >= 0x8000: return -((65535 – val) + 1) else: return val
Potrebujemo še podprogram za zagon in nastavitev merilnika pospeška MPU6050 na +/- 2 g:
bus.write_byte_data(DEVICE_ADDR,
PWR_MGMT_1, 0)
bus.write_byte_data(DEVICE_ADDR, ACCEL_
CONFIG, 0)
Zdaj lahko v glavnem programu beremo neobdelane vrednosti merilnika pospeška z ukazi:
raw_x = read_word_2c(0x3B)
raw_y = read_word_2c(0x3D)
raw_z = read_word_2c(0x3F)
Če dobljene vrednosti delimo z 16384, dobimo vrednosti pospeška.
Poenostavitev merjenja pospeškov v Pythonu
Poglejmo še, kako si programiranje v Pythonu lahko poenostavimo s programsko knjižnico mpu6050, ki jo v našem primeru namestimo z ukazom pip install mpu6050-raspberrypi:
from mpu6050 import mpu6050
sensor = mpu6050(DEVICE_ADDR)
Inicializacijo merilnika pospeška lahko zdaj naredimo z enim samim klicem funkcije mpu6050. Nato lahko podatke o pospešku beremo v obliki podatkovnega polja s tremi realnimi vrednostmi:
accel_data = sensor.get_accel_data()
print(f«X: {accel_data[‘x’]:.2f}, Y: {accel_
data[‘y’]:.2f}, Z: {accel_data[‘z’]:.2f}«)
Z ukazom print nato vse tri vrednosti tudi izpišemo.
Osnove programiranja leta drona v Pythonu
Zdaj, ko smo spoznali, kako enostavno je v Pythonu in MicroPythonu programirati osnovne funkcionalnosti, je čas da se lotimo tudi navigacije in zahtevnejših funkcionalnosti. Na spletni strani pysimverse.com najdemo sorazmerno zmogljiv simulator drona, ki ga lahko povežemo z interpreterjem za Python in s programom v tem programskem jeziku upravljamo izbranega navideznega drona.
Naslednji enostaven primer, s katerim dvignemo navideznega drona od tal je prikazan kar na spletni strani pysimverse.com:
from pysimverse import Drone
drone = Drone()
drone.connect()
drone.take_off()
V resnici pritisk na tipko Run Code & Fly zažene zgolj video na desni strani. Če želimo zares upravljati navideznega drona iz programske kode v Pythonu, moramo prej namesti simulator. Za to zadošča že klik na tipko Download in izbor različice simulatorja za Windows ali macOS. Nato zaženemo namestitveni program in počakamo, da se nekaj manj kot 1 GB programske kode in podatkov iz spleta pretoči v naš računalnik, nakar začne simulator delovati.
Manjka še Python, katerega zadnjo produkcijsko različico (3.14) za Windows ali macOS lahko prenesemo iz spletne strani python.org. Ker ima veliko sprememb, je pogosto bolje namestiti nekoliko starejšo različico (3.13). Sledi namestitev knjižnice pysimverse z ukazom: py –m pip install pysimverse, ki na klasičnem PC steče brez težav, na računalnikih Windows 11 za ARM arhitekturo pa moramo paziti, da namestimo Python 3.13.13 za arhitekturo x64 in ne tistega za ARM64, saj v nasprotnem že prevedenih t.i. koles (angl. wheels, oz. python knjižnic) ni na voljo.
Simulator Pysimverse 0.14 in Python 3.13.13 nam je tako uspelo naložiti na klasični PC in Orange Pi 6 Plus ter z navideznim dronom uspešno preleteti krog v navideznem prostoru.
Zataknilo se je le pri poskusu prevajanja knjižnice Pysimverse za arhitekturo ARM64, za katero zaenkrat ni uradne že prevedene različice. Za prevajanje knjižnice Pysimverse v Windows 11 potrebujemo Microsoft Visual Studio 2022 ali katero do starejših različic, od 2015 naprej. Zanimivo pa je, da zadnji Visual Studio 2026 za ARM arhitekturo ni deloval. Morda je napaka v skriptah za prevajanje izvorne kode programske knjižnice Pysimverse, ki ga enostavno ni zaznala.
Programsko knjižnico pysimverse lahko sicer z ukazom pip install pysimverse namestimo tudi v Raspberry Pi 5 z operacijskim sistemom Raspberry Pi OS Bookworm ali Trixie, a le v navidezno Pythonsko okolje (glej SE349, Python in MicroPython – 2. nadaljevanje). Druga težava je povezava s simulatorjem, ki mora teči v računalniku z Windows 11 ali macOS. Ker programska knjižnica Pysimverse pri ukazu connect ne dopušča definiranja IP-naslova, ampak uporablja prednastavljen naslov 127.0.0.1, je mogoča samo komunikacija z lokalno nameščenim simulatorjem.
Programiranje krožne poti
Pysimverse simulator odlikuje odlična grafika 3D navideznega prostora, vendar je pogoj za njeno tekočo uporabo zmogljiv grafični procesor, oziroma grafična kartica. Igričarski klasični PC je zato kot nalašč za testiranje programov v Pythonu. A brez umetno-inteligenčnih funkcij, ki med drugim izkoriščajo tudi kamero navideznega drona, je programiranje poti drona deterministično in nezanimivo.
Vseeno poglejmo program z nekaj matematičnimi funkcijami, s katerim dron v navideznem prostoru poleti v krogu in obenem spreminja svojo orientacijo. Prvi del programa je znan, le da tokrat naložimo še knjižnici time za časovne funkcije in merjenje časa ter math za matematične funkcije:
from pysimverse import Drone
import math
import time
drone = Drone()
drone.connect()
Sledi matematični izračun parametrov krožne poti, pri katerem določimo tudi premer kroga in število odsekov poti, ki jih dron preleti naravnost. Več odsekov pomeni natančnejše potovanje v krogu:
diameter_cm = 400 # 4 meters in centimeters
radius_cm = diameter_cm / 2
segments = 36 # Number of steps to
complete the circle
angle_per_step = 360/segmentsdistance_per_
step=2 * radius_cm * math.sin(math.
radians(angle_per_step/2))
distance_per_step = 2 * radius_cm * math.
sin(math.radians(angle_per_step / 2))
Zdaj je na vrsti vzlet: Pri ukazu take_off smo kot parameter določili tudi začetno višino (170 cm), na katero se dron dvigne in jo vzdržuje po izvedbi ukaza:
drone.take_off(170)
print(»Starting circular flight…«)
Naslednje faze leta dron zvede tako, da naravnost preleti vnaprej določeno število segmentov dolžine distance_per_step, pri čemer se po vsakem preletu zasuka za kot angle_per_step:
for i in range(segments):
drone.move_forward(int(distance_per_step))
drone.rotate(int(angle_per_step))
Ker dronu ne želimo v celoti izprazniti navidezne baterije, mu po preleti krožnice ukažemo naj pristane.
print(»Circle complete. Landing…«)
drone.land()
Umetno-inteligenčne (UI) funkcije
Dron lahko med letenjem opravlja številne naloga. V našem primeru želimo, da poišče stol in pristane na njem. Zato mora znati sam poiskati predmet v (navideznem) prostoru, pri čemer si lahko pomaga s sliko s svoje kamere. Čeprav bi lahko govorili o navidezni kameri, je to vseeno, saj potrebujemo zgolj sliko navideznega prostora, kot ga zaznava navidezni dron. Za razpoznavanje predmetov v navideznem prostoru, tako kot v realnem, potrebujemo metode umetne inteligence.
Najprej poglejmo, katere programske knjižnice moramo vključiti ter kako zaženemo drona in njegove UI funkcije:
from pysimverse import Drone
import cv2
from ultralytics import YOLO
drone = Drone()
drone.connect()
model = YOLO(‘yolov8n.pt’) # Small, fast model for real-time detection
drone.take_off(170)
Za detekcijo predmetov v prostoru v realnem času uporabljamo prednaučen UI model YOLO, do katerega dostopamo s pomočjo programske knjižnice ultralytics, ki jo moramo predhodno namestiti z ukazom pip install ultralytics.
Nadaljevanje programske kode s pomočjo slike s kamere poišče stol in pristane na njem:
while True:
img = drone.get_image()
results = model(img, stream=True)
chair_detected = False
for r in results:
for box in r.boxes:
if int(box.cls[0]) == 56: # Class 56 is ‘chair’ in COCO dataset used by YOLO
chair_detected = True
x1, y1, x2, y2 = box.xyxy[0]
cx, cy = int((x1 + x2) / 2), int((y1 + y2) / 2)
if cx > 350:
drone.rotate(5)
elif cx < 290: drone.rotate(-5) else: # Move forward if aligned drone.forward(1) if (x2 – x1) > 200: # If the chair is »large« in the frame
drone.land()
break
if not chair_detected:
drone.rotate(10) # Scan for chair
if cv2.waitKey(1) & 0xFF == ord(‘q’):
break
drone.land()
Program najprej z ukazom img = drone.get_image() v objekt img zajame sliko s kamere, ki jo nato z objektom model obdela in v objekt results vrne množico prepoznanih predmetov. V notranji for zanki poišče vse okvire z oznako razreda 56, ki ustreza obliki stola. Pri tem moramo upoštevati, da je lahko stolov v prostoru tudi več. Algoritem se odloči za prvega. Nato izračuna pot leta tako, da se dron ustavi natanko nad stolom in pristane na njem.
Če stola ne najde, se dron nekoliko zavrti in na sliki s svoje kamere ponovno poskuša najti stol. Izvajanje programa lahko tudi ročno prekinemo, če pritisnemo tipko q, nakar dron pristane.
Prihodnjič
Prihodnjič se lotimo še programiranja pravega drona na osnovi ESP32 modula in MicroPythona. Preverili bomo 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.
