A Freedom Boardon van 6 db analóg bemenet, az akkumulátor monitorozását mindenképp terveztem, így adta magát, hogy feszültségmonitorozó legyen az első szenzor.
A legegyszerűbb szenzor egy analóg vagy digitális bemenet állapotának lekérdezése, azonban a szenzorkezelést nem érdemes ad-hoc módon megoldani. Mivel maga a szenzor egyszerű, a szenzorkezelő keretrendszer kialakítására fektettem nagyobb hangsúlyt.
Hardver
A Freedom board 3.3 V-os rendszerfeszültséggel működik, ami azt jelenti, hogy az analóg bemenetei a 0 és 3.3 V közötti feszültségtartományt tudják mérni. Ha ennél nagyobb feszültséget szeretnénk monitorozni, akkor azt ebbe a tartományba kell skáláznunk, amit egy egyszerű feszültségosztóval megtehetünk:
2 db egyforma értékű ellenállással felezzük a feszültséget, így pl. egy 5 V-os tápvonal mérhetővé válik, mivel 0 - 2.5 V tartományba fog esni a mérendő feszültség.
Az akkumulátor 12 V körüli feszültségét pedig különböző méretű ellenállásokkal tudjuk a 2 V körüli tartományba hozni, az Umért = Umérendő / (R1 + R2) * R2 képlet szerint.
Fontos, hogy az ellenállásoknak ne csak az arányát, de a méretét is helyesen válasszuk meg:
- Ha túl kicsi ellenállásokat választunk, akkor nagyon nagy lesz a feszültségosztónkon eső áramerősség, fölöslegesen terheljük az áramforrásunkat, hamar lemerítjük az akkumulátorunkat.
- Ha ellenben túl nagy (pl. MΩ) nagyságrendű ellenállásokat választunk, túl pici lesz az áramerősség, ami az analóg-digitális átalakító (ADC) sample and hold kondenzátorát tölteni fogja, az onnan szükségszerűen elszivárgó áram pedig pontatlanná, sőt értékelhetetlenné teszi a mérést.
- Jó tehát, ha tudjuk az ADC-nk bemeneti impedanciáját (10 kΩ körüli érték az esetünkben) és ehhez igazítjuk a feszültségosztó ellenállásainak értékét.
Az akku feszültségének méréséhez egy 100 kΩ és egy 15 kΩ ellenállásból készítettem feszültségosztót, ami nem disszipál el túl nagy teljesítményt (P = U² / R), ugyanakkor elegendő áramot szolgáltat a méréshez.
A mérés stabilabbá tételéhez egy 100 nF értékű kondenzátort is elhelyeztem az "alsó" ellenállással párhuzamosan kötve, amire azért is szükség volt, mert egyelőre a breadboardos kialakítás miatt csak egy nemkívánatosan hosszú vezetékkel tudtam az ADC bemeneti csatlakozóját bekötni. A kondenzátor megléte ebben az esetben szemmel láthatóan stabilabbá tette a méréseket, de az igazi majd egy megfelelően kialakított forrasztott megoldás lesz. Ezzel a trükkel akár 1 MΩ körüli ellenállásokkal is stabil és pontos mérési eredményekhez jutunk, feltéve, ha a mérések között van elég ideje a kondenzátornak a feltöltődésre.
Keretrendszer
Szoftveres oldalról nézve minden szenzornak van:
- neve, azonosítója
- egy mérést végző algoritmusa
- egy mért értéke
- a mért értéknek mértékegysége
A tervem az, hogy egy központi objektumtól lehessen lekérdezni az elérhető szenzorok listáját, valamint hozzáférést kapni az egyes szenzorok paramétereihez.
A fenti listában szereplő 4 tulajdonság köré egy Sensor osztályt építettem:
class Sensor {
public:
Sensor(char *sId, char *name, char *metric);
~Sensor();
...
void setFunction(float (*function)(void) = 0);
char *getId();
char *getName();
char * getMetric();
float readValue();
private:
char sId[10];
char name[30];
char metric[4]; // max 3 characters + terminating 0
float (*function)(void);
};
Mivel C++ környezetben vagyunk, lehetne ez egy ősosztály, amiből származtatnám a konkrét szenzorokat implementáló osztályokat, felüldefiniálva a readValue() függvényt és a többi paramétert, de aktuálisan más utat választottam.
Mivel a mérést végző függvényt leszámítva nagy különbség (egyelőre) nem lesz a szenzorok között és nem akartam, hogy nagyon terjedelmes legyen a kód, ellenben kíváncsi voltam, hogy hogyan működnek a függvénypointerek a mbed OS környezetben, ebből az egy osztályból példányosítottam az összes szenzoromat, majd állítottam a függvénypointerüket a megfelelő függvényekre:
float readVBatt() {
float factor = 3.3 * 8.25; // Adjusted value of theoretical: 3.3V * 115k / 15k
return sensors->getAnalogIn(0)->read()*factor;
}
void Sensors::createVBatt() {
Sensor *s = &sensArr[sensNum++];
s->setId("vBatt");
s->setName("battery voltage");
s->setMetric("V");
s->setFunction(readVBatt);
}
Ebből a kódrészletből már az is látható, hogy van egy Sensors osztály (amiből egy példányosított objektum létezhet az egész programon belül), ami nyilvántartja a szenzorainkat, valamint tartalmazza azokat az erőforrásokat (pl. AnalogIn objektumok), amiket a szenzor objektumok használhatnak.
class Sensors {
public:
Sensors();
~Sensors();
Sensor* getSensor(char *sId);
AnalogIn* getAnalogIn(int aiId);
private:
void createVBatt();
...
Sensor sensArr[MAX_NUM_SENS];
int sensNum;
AnalogIn* analogInputs[6];
};
Sensors::Sensors() {
// create peripheral objects
analogInputs[0] = new AnalogIn(PTB2);
...
sensNum = 0;
// create sensor objects
createVBatt();
...
}
Sensor* Sensors::getSensor(char *sId) {
if(!strcmp(sId, "vBatt"))
return &sensArr[SENS_VBATT];
...
}
Az így megtervezett keretrendszer szépen és jól működik, de visszatekintve rá úgy néz ki, mintha az objektumorientáltság hőskorából származna, amikor class-ok híján struct-okba próbálták összefogni a fejlesztők az összetartozó adatokat, függvényeket. Egyszer valószínűleg korszerűbbre fogom tervezni, bár ez csupán esztétikai kérdés, hiszen a háttérben mindkét megoldás körülbelül ugyanarra a gépi kódra fordul le.
Szenzor olvasása
A szenzorok olvasására kibővítettem a Bluetooth-on keresztül várt parancsok listáját egy readSensor paranccsal:
void execCommand(char *cmd, int argc, simplestr *args) {
...
else if(!strcmp(cmd, "readSensor")) {
Sensor *s = sensors->getSensor(args[0]);
if(s != NULL) {
BT.printf("%s: %f %s\n", s->getName(), s->readValue(), s->getMetric());
}
}
...
}
Így az autó irányítására is használt Bluetooth Serial Controller programból lekérdezhetővé váltak a szenzorok:
Forráskód
Korábban az os.mbed.com-on keresztül már megosztottam a kódot, mostantól azonban a kényelmesebb hozzáférés, olvasás érdekében a GitHubon is elérhetővé tettem: https://github.com/dralisz82/robotkocsi_OS