A legutóbbi poszban említettem, hogy lett egy kis gond a kormánymű vezérlésével. A kiégett tranzisztort cseréltem egy hasonlóra, majd futottam néhány kört, de nem lett már igazán jó. Ami azt illeti eleve nem volt egy jó konstrukció a H-híddal meghajtott kis motor, pozícióérzékelés nélkül. Így megfelelőnek találtam az alkalmat egy modellszervó beépítésére.
Miért is jobb a szervomotor?
Eddig 3 állása volt a kormánynak: teljesen balra, teljesen jobbra, valamint egyenesen. Utóbbi helyzetbe egy rugó húzta be a kormányművet. Ez azt is jelentette, hogy kanyarodáskor tartósan táplálni kell a motort, miközben az állóra fékeződik a végállás miatt. Ez se a motornak, se a meghajó áramkörnek nem tesz jót, az ilyen konstrukció alkalomszerű rövid kanyarodásokat feltételez.
Szervomotorral sokkal finomabban, pontosabban lehet kormányozni, a pozíció tartásáért ráadásul a motorba épített (általában potméteres) visszacsatolás és egy PWM-es szabályzó felel, aminek az az előnye, hogy nincs folyamatosan terhelve, erőltetve a motor.
A megfelelő motor kiválasztása
Az általánosan elterjedt modellszervók közül a legkisebbek az SG/MG-90-esek. Ezeket követi az MG-945/995 méretosztály. Az előbbiek erre a célra érzésre gyengék lettek volna (az MG, "metal gearing", tehát fém fogaskerekes 90-es is inkább csak terhelés nélkül tudta volna pozícionálni a kerekeket. Az MG-945/995 viszont már kellően erős ahhoz, hogy akár durva terepen, megterhelt robotot is tudjon terelgetni, ráadásul méretben is tökéletesen illik a gyári motor helyére:
Beépítés
Pusztán a fülek számára kellett bevágásokat készíteni a műanyagban, valamint picit szélesíteni a résen, ahol a kábelt vezetem el:
A szervo precízen rögzül a kialakított helyen, pusztán az oldalirányú rögzítésről kellett pluszban gondoskodni. Ezt egy régi, feslett óraszíjból vágott darabokkal oldottam meg, amikkel precízen kiékeltem a szervót a helyén. A szervohoz mellékelt kétágú karból valamint M3-as csavarból és ráhúzott szilikon csőből készítettem kilincset:
Látszanak a rugalmas távtartók, valamint az, hogy minden kényelmesen elfért a gyári burkolat alá:
Kalibrálás
A hobbyszervók, mint az MG995-ös is, adott szélességű impulzusokkal irányíthatók, amihez egy PWM kimenet szükséges. Általában 1.5 ms széles impulzus viszi középállásba a szervót és ±0.5 ms szükséges a két szélső álláshoz. A szervók azonban sajnos nem mind egyformák, így érdemes egy függvénygenerátorral, vagy egy PWM-vezérlővel megvizsgálni őket:
Esetemben 0.6 ms és 2 ms széles impulzusokkal (6% és 20% kitöltés egy 100 Hz-es jel esetén) értem el a két szélső állást, a középálláshoz pedig 1.3 ms-es impulzusra van szükségem. A kormányműben persze nem használom ki a teljes, 180°-os tartományt, empirikus úton állapítottam meg, hogy 1.1 és 1.5 ms közötti tartományban lehet a két szélső állás között kormányozni a kerekeket.
Szoftver
A szervó vezérlését egy PwmOut objektumon keresztül tudjuk megoldani, viszont miért kezdenénk nulláról, ha egy olyan platformot használunk, amire már sokan sokmindent kifejlesztettek.
Tehát kerestem, és találtam egy library-t, pont amire szükségem van: https://os.mbed.com/cookbook/Servo
A library-t importáltam a projektembe, és már adhattam is hozzá a Servo objektumot a Drive osztályomhoz a korábbi két PwmOut helyett. Persze ez a Servo osztály 1.5 ms-os középállást feltételez, csak az impulzustartomány szélességét lehet változtatni a calibrate() függvény segítségével, így számomra nem használható. Át lehetne írni a kódot, de nem használnám ki a plusz szolgáltatásait, így maradtam egy pőre PwmOut objektum használatánál:
Drive::Drive(PinName pF, PinName pB, PinName pS, Lights* lights) {
...
po_steering = new PwmOut(pS);
po_steering->period(0.01); // 100Hz
...
}
void Drive::controlThread_main(void const *argument) {
...
// 20 Hz control loop
while (true)
...
if(self->f_steerLeft) {
steeringDebug = true;
steeringPosition = 0.11f;
} else if(self->f_steerRight) {
steeringDebug = true;
steeringPosition = 0.15f;
} else
steeringPosition = 0.13f;
self->po_steering->write(steeringPosition);
if(steeringDebug)
printf("Steering position: %d, %d, %f\n", self->f_steerLeft, self->f_steerRight, steeringPosition);
if(!(self->f_steerLeft) && !(self->f_steerRight))
steeringDebug = false;
Thread::wait(50);
}
}
Ez a minimum kódváltoztatás, ami szükséges volt a működő állapot visszaállításához, valamint a szervós megoldás teszteléséhez, azonban ez még mindig csak a korábbi 3 állású kormányzást teszi lehetővé.
A fent látható kontroll ciklust azért hoztam létre, hogy a meghajtómotor vezérlése és a kormányzás szabályozhatóan történjen. Itt most csak a kormányzást érintő részt mutatom be.
Első körben a "csapkodó" kormánymozdulatokat szerettem volna finomítani, így bevezettem két változót: a steeringServoPosition a szervó aktuális állapotát tárolja, a steeringTargetPosition pedig a kívánt kormányálláshoz tartozó értéket. Utóbbit változtatom a Drive::steerLeft() és ...Right() függvényekből, a szabályozó ciklusban lévő logika pedig gondoskodik arról, hogy fokozatosan érjük el a kívánt állapotot:
Drive::Drive(PinName pF, PinName pB, PinName pS, Lights* lights) {
...
po_steering = new PwmOut(pS);
po_steering->period(0.01); // 100Hz
steeringTargetPosition = 0.13f;
...
}
void Drive::steerLeft(float target = 0.11f) {
if(target >= 0.13f)
return;
if(lights && autoIndex)
lights->indexLeft();
steeringTargetPosition = target;
}
void Drive::steerRight(float target = 0.15f) {
if(target <= 0.13f)
return;
if(lights && autoIndex)
lights->indexRight();
steeringTargetPosition = target;
}
void Drive::steerStraight() {
if(lights && autoIndex)
lights->indexOff();
steeringTargetPosition = 0.13f;
}
void Drive::controlThread_main(void const *argument) {
...
// 20 Hz control loop
while (true) {
...
// steering control
if(fabs(self->steeringTargetPosition - steeringServoPosition) > 0.001f) {
steeringDebug = true;
if(self->steeringTargetPosition > steeringServoPosition)
steeringServoPosition += 0.005f;
else
steeringServoPosition -= 0.005f;
self->po_steering->write(steeringServoPosition);
if(steeringDebug)
printf("Steering position (target and actual): %f, %f\n", self->steeringTargetPosition, steeringServoPosition);
}
Thread::wait(50); // 20 Hz
}
}
Látható, hogy a steeringServoPosition fokozatosan, több apró lépésben követi a mindenkori "irányadó" steeringTargetPosition értékét.
Külön érdekességként kiemelem, hogy miért nem "self->steeringTargetPosition != steeringServoPosition" szerepel a szabályzó ciklusban lévő első feltételben: A float egy érdekes adattípus ami a tárolásához használt bitek véges száma miatt általában csak közelíti azt az értéket, amit ábrázolni szeretnénk. A 0.13f például 0.129999995232-ként tárolódik, így nem feltétlenül lesz egyenlő például a 0.125f + 0.005f művelet eredményével. Ezért float típusú értékeket sosem hasonlítunk az == és != operátorokkal, hanem például a fenti kódban is látható módon megnézzük, hogy a kettejük különbségének az abszolútértéke kisebb-e egy általunk jól megválasztott epszilon értéknél. Ezt a bizonyos hibahatárt gondosan kell megválasztani. Esetemben 0.005f lépésekben változtatom a steeringServoPosition értékét, így 0.001f-et választottam, ami kisebb a lépésköznél, de nem annyira kicsi, hogy az adatábrázolás véges pontossága miatt bizonytalanná tegye a művelet eredményét.
Bekötés
Ez a szervó terhelés alatt 1 A-nél nagyobb áramot is felvehet, terhelés nélküli mozgás közben azonban 300 mA körül alakul az áramfelvétele, a nyugalmi áramfelvétele pedig egészen minimális.
A fenti szoftveres megoldás kipróbálásához a Freedom Boardnak is 5 V-os tápot biztosító 7805-ös stabilizátor IC kimenetére kötöttem rá a szervót, azonban annak legkisebb mozdulata is a vezérlő újraindulását okozta.
Multiméterrel kimérve azt tapasztaltam, hogy a szervó mozgása, a beépített kondenzátorok ellenére, akár 4.5 V közelébe húzta le egy pillanatra a tápfeszültséget. Ezt kiküszöbölendő külön tápellátást építettem a szervónak egy ugyanolyan 3 A-es DC/DC konverterből, amit korábban már bemutattam.
Egy ilyen DC/DC konverter kimenetére minden esetben szükséges, de legalábbis nagyon ajánlott pufferkondenzárort tenni, de kíváncsiságból kipróbáltam, hogy mi történik, ha anélkül táplálom meg a szervót vele.
A konverter kimnenetét 5 V-ra állítottam be, majd folyamatosan mértem rajta a feszültséget. Amikor a szervó nyugalomban volt, nem is történt semmi váratlan, azonban amikor a szervónak terhelést adtam (megpróbáltam kézzel kimozdítani a tartott pozícióból, ill. terhelve irányítottam másik pozícióba, meglepően nagy feszültségcsúcsokat mutatott a műszer. 8 V fölötti értékeket is mértem, ami annak köszönhető, hogy a szervó által hirtelen felvett áram feszültségcsökkenést okozott volna a konverter kimenetén, amit annak szabályzója azonnal megpróbált korrigálni, több áramot pumpálva át magán, majd amikor az áramfelvételi csúcs megszűnt, a feszültség ugrott meg. Nem lepett meg a jelenség, bár a 8 V fölötti értékek igen.
A nemkívánatos jelenség kiküszöbölésére egy 1000 uF-os kondenzátort kötöttem a szervóval párhuzamosan a konverter kimenetére, majd újra teszteltem. Ezúttal a feszültség stabilan a beállított 5 V közelében maradt.
A konvertert az elkóval a breakout kártyára építettem rá, miután azon helyet szabadítottam fel a korábbi kormánymű csatlakozásainak eltávolításával:
Nem várt problémák
Összeraktam az autót, nagyjából minden a várakozások szerint működött, viszont a szervó gyakran "darált", remegett, valamint úgy tűnt, mintha a hajtás jelei is zavarnák.
A zavarokat kiszűrendő három fejlesztést hajtottam végre:
- A szervó hosszú zsinórját lerövidítettem, ne kelljen feltekerni, így csökken a zavarérzékenysége.
- A Freedom Board 3.3 V-os PWM jelét felerősítettem 5 V-ra egy egyszerű kapcsolás segítségével:
https://electronics.stackexchange.com/questions/81580/step-up-3-3v-to-5v-for-digital-i-o - Végül, a breakout kártya és a breadboard között futó kábelköteg párhuzamosan futó szálai közötti esetleges áthallást csökkentendő/megszüntetendő sodrott érpáros kivitelűvé alakítottam a jelvezetékeket, mindegyiket külön-külön megsodorva egy földvezetékkel:
Végül
Miután minden tökéletesen működött, összeszereltem az autót, hogy újra bevethető állapotban várja az idei Kutatók Éjszakáját (HURBA stand az Ericsson Házban). Addig még talán belefér 1-2 apróbb fejlesztés is.