egy mobil robot építésének története

Robotkocsi

Robotkocsi

Szervokormány

avagy kormányszervó

2018. augusztus 30. - dralisz82

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:

robotkocsi_063_eredeti_kormanymu.jpg robotkocsi_064_kormanyszervo.jpg

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:

robotkocsi_065_szervonak_bevagas.jpgrobotkocsi_066_szervonak_bevagas.jpg

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:

robotkocsi_067_kormanyszervo_kilincs.jpg

Látszanak a rugalmas távtartók, valamint az, hogy minden kényelmesen elfért a gyári burkolat alá:

robotkocsi_068_kormanyszervo_beepites.jpgrobotkocsi_069_kormanyszervo_beepites.jpg

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:

robotkocsi_070_kormanyszervo_kalibralas.jpgrobotkocsi_071_kormanyszervo_kalibralas.jpg

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:

robotkocsi_074_kormanyszervo_bekotes.jpg

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:

  1. A szervó hosszú zsinórját lerövidítettem, ne kelljen feltekerni, így csökken a zavarérzékenysége.

  2. A Freedom Board 3.3 V-os PWM jelét felerősítettem 5 V-ra egy egyszerű kapcsolás segítségével:
    robotkocsi_075_kormanyszervo_stepup.pnghttps://electronics.stackexchange.com/questions/81580/step-up-3-3v-to-5v-for-digital-i-o
  3. 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:
    robotkocsi_076_breakout_sodrott_erpar.jpg

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.

 

A bejegyzés trackback címe:

https://robotkocsi.blog.hu/api/trackback/id/tr3114167669

Kommentek:

A hozzászólások a vonatkozó jogszabályok  értelmében felhasználói tartalomnak minősülnek, értük a szolgáltatás technikai  üzemeltetője semmilyen felelősséget nem vállal, azokat nem ellenőrzi. Kifogás esetén forduljon a blog szerkesztőjéhez. Részletek a  Felhasználási feltételekben és az adatvédelmi tájékoztatóban.

Nincsenek hozzászólások.
süti beállítások módosítása