Ahogy arra a legutóbbi bejegyzésben már utaltam, a mágnesek számát növelni kellett ahhoz, hogy normális enkóderünk legyen. Normális alatt közel 50%-os pulzusszélességet értek, azaz az érzékelők ugyanannyi fokos elforgatáson keresztül érzékeljenek alacsony és magas állapotot. Mint kiderült, ehhez a mágnesek számát legalább háromszorozni kell, azonban így sem lett tökéletes az eredmény, így végül 24 mágnest építettem be, de itt már nagy hangsúlyt fektettem a pontos elhelyezésükre, valamint mérni kezdtem a jeleket, hogy láthassam, hogy min kell változtatni.
Mágnestartó, a nagy újítás
Az előző kialakításhoz képest az igazán nagy újítás, hogy a mágneseket nem egyesével ragasztottam be a kerékbe, hanem készítettem egy mágnestartó sablont. Ehhez TinkerCAD-et ragadtam és felfűtöttem a 3D nyomtatót, majd a ragasztópisztolyt. Az eredmény a képeken látható:
Ennyi mágnesnél már muszáj volt ezt meglépni, de nem csak a beszerelés egyszerűsödött, hanem sokkal pontosabb is lett az eredmény. Ráadásul a bepattintható tartók könnyen eltávolíthatók, ha változtatásra lesz szükség (kiegészítés: lett).
Geometria
24 db mágnes elhelyezése a körön azt jelenti, hogy azok 15°-onként vannak. A szenzorokat így 7.5°-ra tettem egymástól egy újabb tartóra átszerelve. Ez a 7.5° végre kellőképp kicsi ahhoz, hogy egy mágnes úgy ússzon át az egyik szenzor hatósugarából a másikéba, hogy előbb kezdi érzékelni az új szenzor, utána "veszíti szem elől" a régi – tehát a szenzorok átfedésben érzékelnek. Eddig ez nem volt meg, mert bőven 50% alatti volt a mágnesesen aktív szakaszok aránya a körön. Mint kiderült, a mágnesek azonban így túl közel kerültek egymáshoz. A táblázat mutatja a szenzorok által érzékelt jelek szekvenciáját:
Előre: |
|
Hátra: |
|
Ahogy látható, hiányzik a 00 állapot, ami nélkül nem lehet megbízhatóan érzékelni az irányváltást, tehát ugyanabba az elméleti problémába futottam bele, mint amikor túl kevés mágnes volt, csak inverz módon. A két jelsorozat igazából megegyezik.
Miután logikai analizátorral is felmértem a helyzetet (lásd a következő fejezetet), visszatértem a 18 mágneses elrendezéshez, mert az közelebb áll az 50%-hoz, viszont a 7.5°-os szenzortartót meghagytam, úgy gondolkodva, hogy – bár egy picit eltolódik a két jel fázisa az ideális 90°-tól (ez a 90° nem összekeverendő a kerék kerületén mért szögekkel!), fontosabb, hogy a szenzorok átfedésben maradjanak.
Az átalakítás után bíztató eredményeket kaptam:
Előre: |
|
Hátra: |
|
Ez már egy olyan kimenet, amit egy kvadratúra-enkódertől várunk: az állapotátmenetekből minden esetben eldönthető a forgásirány (érdemes gondolatban kipróbálni a fenti táblázat segítségével).
Logikai analizátor - út a XXI. századba
Miután szert tettem erre az eszközre, úgy éreztem, korábban egy sötét szobában tapogatóztam csupán. A logikai analizátor minden digitális technikával kicsit is komolyabban foglalkozó számára egy kötelező műszer! Szerencsére nem szükséges venni egyet, hála az Arduinok népszerűségének.
Éppen volt nálam kölcsönben egy Arduino Mega, a neten pedig találtam egy logikai analizátor firmware-t rá, amivel egy 8 csatornás, akár 4 MHz mintavételezésű logikai analizátorrá alakítható a kártya. Ráadásul a firmware a szabványos SUMP protokollt is támogatja, így a nyílt forráskódú Sigrok PulseView programmal is használható.
A PulseView nagyon jó felhasználói felülettel rendelkezik és rengeteg extra funkciója van. Egyetlen szépséghibája az Arduino + Sigrok kombinációnak, hogy a beérkező jeleket jobbról balra mutatja az időtengelyen, de ezzel együtt lehet élni, ha nem akarjuk használni a rengeteg protokoldekóder egyikét sem. A hiba egyébként ismert, így a nyílt forráskódnak köszönhetően várhatóan hamarosan javításra kerül. (FRISSÍTÉS: Végül én adtam ki rá egy javítást először. :) )
A dekóderek közül én csak az impulzusok hosszát mérő "Timing" eszközt használtam, ami így is tökéletesen működik, és számomra most ez volt fontos:
A jelek értelmezéséhez fontos tudni, hogy az A3144-es hall-szenzor inverz logikai jelet ad, tehát akkor húzza földre a kimenetét, amikor mágneses teret érzékel. Így már látható is a fenti ábrán, hogy 24 mágnes esetén kb. 2/3-os kitöltési tényezővel jócskán sikerült túllépni az ideális 50%-ot.
A következő mérést már a 18 mágneses és 7.5°-os szenzortávolságos verzióval készítettem:
A képen szemmel látható, hogy elég jó pontossággal közelítik a jelek az 50%-os kitöltést (a sűrűbb és ritkább szakaszok a sebességváltozásból, a kerék felgyorsulásából és lelassulásából adódnak). A középső hosszabb konstans szakasztól jobbra egy előre, balra egy hátrafelé forgás jeleit láthatjuk. Elhelyeztem két kurzort is a képen, amik jól mutatják, hogy előreforgásnál a 6-os jel lefutó élénél (ne feledjük, hogy továbbra is visszafelé, jobbról balra futnak a jelek) magas a 4-es jel állapota, míg a bal oldali hátrafelé forgás esetén pont a 6-os jel felfutó élénél magas a 4-es jel szintje (az ellenkező éleknél pedig alacsony).
Szoftver
A forgásirány eldöntéséhez minden állapotátmenetet figyelni kell, tehát az előző részben bemutatott implementációhoz képest mindkét szenzor fel- és lefutó jeleihez is megszakításkezelőt kötöttem. Ez azt is jelenti, hogy egy mágnes elmozdulása egy körülfordulás során nem kétszer, hanem négyszer detektálható, ami az enkóderünk felbontását egészen vállalható 18*4=72 pulzus/körülfordulás (pulse per rotation - PPR) magasságokba röpíti.
Nézzük a kódot:
void Odometry::hallAriseISR(void) {
if(hallB->read() == 0) {
// 00 -> 10
dir = BW;
} else {
// 01 -> 11
dir = FW;
}
if(dir == FW)
odoPos++;
else
odoPos--;
odoCount++;
calcSpeed();
}
void Odometry::hallAfallISR(void) {
if(hallB->read() == 0) {
// 10 -> 00
dir = FW;
} else {
// 11 -> 01
dir = BW;
}
if(dir == FW)
odoPos++;
else
odoPos--;
odoCount++;
calcSpeed();
}
void Odometry::hallBriseISR(void) {
if(hallA->read() == 0) {
// 00 -> 01
dir = FW;
} else {
// 10 -> 11
dir = BW;
}
if(dir == FW)
odoPos++;
else
odoPos--;
odoCount++;
calcSpeed();
}
void Odometry::hallBfallISR(void) {
if(hallA->read() == 0) {
// 01 -> 00
dir = BW;
} else {
// 11 -> 10
dir = FW;
}
if(dir == FW)
odoPos++;
else
odoPos--;
odoCount++;
calcSpeed();
}
void Odometry::calcSpeed() {
// Prevent division by zero -> side effect: measuring limit under 833 RPM (@72 PPR)
if(timer.read_ms() == previousTime)
return;
// Current speed calculation
currentSpeed = (WHEELCIRCUMFERENCE / PPR) * 100 / (timer.read_ms() - previousTime);
// Highest speed calculation
if((previousSpeed + currentSpeed)/2 > highestSpeed)
highestSpeed = (previousSpeed + currentSpeed)/2;
// Average speed calculation
speedSum += currentSpeed; // TODO: this will overflow once, far in the future
avgSpeed = speedSum / odoCount;
previousTime = timer.read_ms();
previousSpeed = currentSpeed;
hallTimeout.detach();
hallTimeout.attach(callback(this, &Odometry::clearSpeed), 1);
}
void Odometry::clearSpeed() {
currentSpeed = 0;
}
A megszakításkezelők magukért beszélnek (ezért érdemes kommentezni a kódot). A hallTimeout és a clearSpeed() függvény szúrhat szemet a szemlélőnek. Erre a sebességnullázásra azért van szükség, mert az ISR-ek csak akkor frissítik a sebesség értékét, amikor új impulzus érkezik. Ha megáll az autó, akkor nem fog ilyesmi történni, így egy idő után más módon kell nullára állítani a sebességet. Ehhez a timeout értékét 1 másodpercnek választottam, ami azt jelenti, hogy ha olyan lassan forog a kerék, hogy két érzékelt pozíció között pont ennyi idő telik el, az a leglassabb érzékelhető sebesség. 72 pozícióval egy 1 fordulat / 72 másodperc alatt történik ekkor, azaz 1/72-ed fordulat per másodpercre, átváltva 4.79 mm / másodpercre jön ki a leglassabb érzékelhető sebesség. Ez a 4.79 mm egyébként a legkisebb érzékelhető elmozdulásnak is a mértéke.
Ezekkel az értékekkel már elégedett vagyok, a forgásirányváltás is minden esetben detektálható, erre már lehet építeni. A következő bejegyzésben arról írok majd, hogy mihez is kezdtem az odometriából kinyert adatokkal.