CH552 (CH55x) alaplap Arduino környezetben

Tartalom:

  • WeAct Studio CH552 alaplap felhasználásának indokai
  • Telepítés Arduino IDE környezetben
  • Példa programok a legfontosabb tulajdonságok kipróbálásához
  • Szoftveres I2C, DS3231 RTC óra illesztése
  • C források a C++ helyett, nehézségek

2021-22 gyászév volt az elektronikai alkatrészek beszerzése szempontjából. Nagyot romlott a forint, bevezették a Kínából rendelt árukra az ÁFA fizetési kötelezettséget és már távolkeleti barátaink sem szeretik kifizetni helyettünk a szállítási költséget. És ki tudja miért, de az Arduino termékek ára ezen indokokon túlmenően is növekedett! Kedvencem az Arduino Nano alaplap immár 800Ft helyett 2000Ft összegért szerezhető be. Bár volt egy átmeneti időszak, amíg még ennél is drágább volt. Kétségbeesve próbáltam hát olcsóbb alkatrészeket keresni. Így találtam rá a CH552 alaplapra, ami 600Ft/db áron érkezett, bár még 400Ft szállítási költség is terhelte a pénztárcámat ehhez kapcsolódóan. Mivel kapásból hármat vettem, nagyon kedvező ár alakult ki.

Így néz ki:

Világítás vezérlést szerettem volna megvalósítani, amire tökéletesen alkalmas lenne. Azonban a világításvezérlés lakásunkban kapott egy plusz tulajdonságot, miszerint bármely kapcsoló dupla megnyomásával lekapcsolható minden lámpa akárhol is felejtettük égve. Vannak a lakásnak olyan részei, ahová nem tudtam kábeleket vinni, így a központi lekapcsolást olcsó 433Mhz-s adó-vevő párral oldottam meg. Ezt az adó-vevő párt a RadioHead programkönyvtárral hajtom meg, ami viszont nem működik a CH552-vel. Így végül úgy tűnik, az alaplapok fiókba kerülnek, hátha később még találok hozzá funkciót!

A fenti hiányosságokat már csak akkor sikerült megértenem, amikor az alaplap legfontosabb tulajdonságait megfejtettem, így most közzé teszem amit már tudok, hátha valaki más is hasznát veszi (illetve magam is saját leírásaimat olvasgatom hosszabb idő eltelte után, amikor már elfelejtettem mindent)!

Alig van elérhető dokumentáció a net-en. A legrészletesebb doksi a github-on található itt: https://github.com/NoNamedCat/ch55xduino

Mire is használható ez az alaplap?! Jóval szerényebb mint a nano, de jobb mint egy ATtiny85, valahol a kettő között áll! A legfontosabb tulajdonságok:

  • Belső 5V -> 3.3V konverter, mert az USB kivezetések 3.3V-os feszültséggel működnek. Egyébként 2.8V-5.5V között bármilyen tápfesz lehetséges.
  • A flash 16Kb (valójában csak 14Kb használható),
  • 1Kb RAM, plusz 256byte RAM, aminek a szerepe nem teljesen világos, de utóbbi a gyors írásokra szolgál, míg az 1Kbyte a tömeges adattárolásra.
  • Három időzítő, ebből az egyik külső kivezetésekről indítható, leállítható és két kivezetés jeleinek időtartam mérésére is használható.
  • Két 8 bites PWM kimenet.
  • Egy SPI.
  • Két UART.
  • Négy 8 bites ADC bemenet.
  • Hat érintésre érzékeny (touch) bemenet.

Sajnos beépített I2C nincs, de szoftveres megoldással lehet helyettesíteni. Láthatóan mindenben egy kicsivel több, mint egy ATtiny alaplap, aminek alig van kivezetése, és csak 8Kb belső flash memóriája van. Egészen egyszerű dolgokra jól használhatónak tűnik. Korlátozottan még az Arduino IDE környezetből is programozható. Sajnos nincs hozzá C++ fordító, csak a C nyelvet használhatjuk. A C nyelv még nem objektum orientált nyelv, így a legtöbb programkönyvtár nem lesz használható, de kisebb nagyobb munkával a programok átalakíthatók. Csábítónak tűnik a 6db érintésre érzékeny bemenet. Már kipróbáltam, valóban elegendő megérinteni a kivezetést! Így nem kell a kütyünkre (bármi legyen is az) nyomógombot szerelni. Elég egy kicsike fémfelület, amit az újunkkal elérünk, pl. egy kiálló csavarfej stb.

Csak azért, hogy nagyon tudományosnak érezzem magam, bemásolom még a chip belső felépítésének vázlatát:

Valamint egy képet a lábkiosztásról:

Valahol a net-en találtam egy angol nyelvű adatlapot. Ezt letöltheted magadnak innen! Egyébként mindenütt (pl. datesheet.com-on is) csak kínai van, ez így nagy szó. Megtudtam az adatlapból, hogy a chip 2,8V-tól 5,5V-ig működőképes.
Ha alapból 5V-on használjuk, akkor a bemenetek 0-5V feszültségeket fogadnak, kivéve a P3.6 és a P3.7 (a kivezetések számozása kicsit szokatlan). Ezek a kivezetések ugyanis a belső USB meghajtó kivezetései, és ezek valamiért 3.3V-al működnek.
A kivezetés bemenetnek állítása esetén 15Kohm-os felhúzó ellenállásból, illetve 1,5Kohm-os lehúzó ellenállásból lehet választani. A lehúzó ellenállás bekapcsolásának módját még nem tudom. Eddig még nem kellett. Az is lehetséges, hogy csak félreértés részemről!
Ha a kivezetést kimenetként használjuk, akkor a 0V és tápfesz között kapcsolgat (kivéve az említett P3.6 és P3.7), ha pl. 5V-on használjuk, akkor a magas szint 5V. A kimenet terhelhetősége 8mA, de a teljes chip összes terhelése nem lehet több mint 12mA. A belső 24Mhz-s órajel valószínűleg egy RC oszcillátor, mert az egyik táblázatban a frekvencia értékét kb. 0.5Mhz pontossággal adták meg. Feltehetőleg ezért is került az alaplapra külső alkatrészként egy 24Mhz-s kristály. Így ezzel nem kell foglalkoznunk, és nem kell aggódnunk, az órajel kvarcz pontos! Érdemes észben tartani még, hogy a flash memória újra írásainak száma 200!!! az alaplap szerint. Mire ezeket a sorokat írom, már vagy ötvenet elhasználtam!

A kivezetésekhez tartozó sok-sok funkció részletes magyarázatát az adatlapból sikerült megfejteni. A fentebb található lábkiosztás ábrán aláhúzással jelzett kivezetések inverz módon működnek (a funkciót a 0 jelszint aktíválja). Pl. a RST (reset) kivezetés jelzése nincs aláhúzással jelölve, vagyis magas jelszinttel lehet reset-elni a chip-et. Ez elég szokatlan, mert az Arduino alaplapokon a reset mindig 0-val történik. Egyébként az adatlap szerint a reset kivezetésnek beépített és fix felhúzó ellenállása van. A többi kivezetés felhúzó ellenállását ki lehet kapcsolni, de alapértelmezetten bekapcsolásra kerülnek a chip indulásakor.

RSTKülső reset bemenet
T0, T1TIMER0,1 külső bemenetek
T2TIMER2 Külső órajel bemenet
T2EXTIMER2 újraindító/leállító bemenet
CAP1, CAP2TIMER2 capture mód bementetek
TIN0 ~ TIN5Érintőgombos kapacitásérzékelő bemenetek
AIN0 ~ AIN3ADC analóg jelbemenetek (0v-Vcc között 256 jelszint)
INT0, INT1Külső megszakítás bemenetek.
UDM és UDPUSB D- és D+ csatlakozási pontok
UCC1, UCC2C típusú USB kétirányú konfigurációs csatorna
VBUS1, VBUS2USB C típusú busz feszültségészlelés bemenet
XI, XOKülső kristály ki és bemenet
RXD, TXDUART0 soros adat be és kimenet
RXD1, TXD1UART1 soros be és kimenet
SCS, MOSI, MISO, SCKSPI interfész, SCS chip kiválasztási bemenet, a MOSI a master kimenet / slave bemenet, A MISO a master bemenet / slave kimenet, az SCK órajel
PWM1, PWM2PWM1 kimenet, PWM2 kimenet

Sajnos a fent említett github leírás számomra nem volt teljesen érthető, ezért napokat töltöttem azzal, hogy megfejtsem, hogyan is lehet ezt az alaplapot az Arduino IDE-vel együtt használni. Első lépésben le kell tölteni a megfelelő alaplapkezelő könyvtárat. Ez még egyszerű! Az Arduino IDE programban a file/beállítások menüpontban a további alaplapkezelő URL-ek közé illesszük be a következő sort:

https://raw.githubusercontent.com/DeqingSun/ch55xduino/ch55xduino/package_ch55xduino_mcs51_index.json

Ezt követően az eszközök/alaplap/alaplap kezelő menüpontot indítsuk el, és a kereső mezőbe írjuk be „CH55x”:

Telepítsük a megtalált alaplapkezelőt! Mint a nevében is látható, nem C++ képes a programkönyvtár. Ha jól értettem meg a leírtakat, ennek oka, hogy a CH55X mikrovezérlő család alapját a 8051-es CPU képezi, melynek utasítás készlete olyan, amire nagyon nehéz lett volna (esetleg nem is lehetséges) a C++ nyelv fordítójának átalakítása. Így maradt a C nyelv, ami sokkal szerényebb, de legalább a megszokott Arduino IDE környezetben dolgozhatunk tovább. Egyébként a C++ programok visszabutítása C-re nem tűnik nagyon bonyolultnak, a fenti github oldalon erre is találunk instrukciókat. Jómagam kezdetben az objektum orientált programozást nem is értettem, ezért nem használtam ki az abban rejlő lehetőségeket. Valamint elég sok program könyvtár tartalmát „hobbyból” átemeltem a saját programsoraim közé, megismertem az adott modul működését és újra megírtam a kezelő függvényeket. Pl. ledmatrix és Ds3231 RTC óra stb. Így ez most hasznomra is válhat!

A következő nagy kihívás az alaplap használatához a megfelelő USB driver telepítése. Ezt ugyanis a Windows10 nem telepíti! Nagyon nehezen értettem meg, hogy kétféle állapot kell az alaplap használatához, és az egyikhez kell csak drivert telepíteni:

  1. Programozás. Ahhoz, hogy programot töltsünk az alaplapra, úgy kell az USB porthoz csatlakoztatni, hogy közben nyomva tartjuk a boot nevű nyomógombot (az alaplapon P36 felirat látható). Ez nem csinál mást, mint egy felhúzó ellenállással magasra húzza az USB port D+ kivezetését. Nagyon macerás a programfeltöltés, mert az a műveletet megfelelő ritmusban kell elvégezni. Azt tapasztaltam ugyanis, hogy csatlakoztatás után elindul a CH552 chip-ben a bootloader program, ami csak néhány másodpercig várakozik az adatok érkezésére, aztán újraindítja chip-et „nem” bootloader üzemmódban. Ha az Arduino IDE nem végez a program fordítással, és nem kezdi meg a feltöltést, akkor meghiúsul a feltöltési kísérlet. Az én Arduino IDE példányom hosszú ideig fordít, így csak akkor dugom be lenyomott nyomógombbal az alaplapot, amikor éppen megjelenik a fordítás végén az a kb. két soros fehér betűs üzenet, ami a lefordított program méretéről és a felhasznált memória méretéről tájékoztat. Ekkor a feltöltés egy másodperc alatt megtörténik.
  2. USB soros kommunikáció PC-vel. Amikor a programból adatokat küldünk soros portra, a soros monitornak meg kell adnunk egy sorosport számot (pl. COM20 stb.). Ehhez kell egy USB-Serial illesztő driver. Ezt a drivert akkor fogja használni a PC, amikor a boot gomb megnyomása nélkül dugjuk be az USB portba az alaplapot. Program feltöltés után természetesen nem kell kihúzni és visszadugni, elegendő megnyomni a reset gombot. Sajnos a soros monitort reset után tapasztalatom szerint újra kell indítani.

Mielőtt a driver telepítés menetét megismerjük, érdemes egy pillantást vetni az alaplap kapcsolási rajzának a részletére. Rögvest kiderül belőle, hogy mit is csinál a boot nyomógomb, ami az alaplapon P36 felitattal van ellátva.

A rajzon a CH552P változat szerepel, aminek kicsit kevesebb a kivezetése, de ez most szempontunkból mindegy. Az ismertetett alaplapon egyébként a CH552T változat található, aminek összesen 20 kivezetése van.

És ha már kapcsolási rajzokat nézegetünk, érdemes rápillantani az USB Type-C típusú csatlakozó környezetére is. Ez a kapcsolási rajz egy Adafruit alaplaphoz készült (WeAct-ra nem találtam), de feltételezem, hogy ugyanígy lett bekötve. Az USB-C csatlakozóról annyit érdemes tudni, hogy 24 csatlakozási pontja van, és lefelé kompatibilis az USB-A és USB-B csatlakozókkal, és ezt úgy biztosítja, hogy megtalálható benne a D+ és D- jelzésű érpár. Ha csak ezeket használjuk, akkor az USB-C kábel másik végén az USB-A csatlakozó összesen négy kivezetésébe ezt a kettőt, illetve a földet és a tápfeszt kell bekötni.

Itt azonban van még két felhasznált kivezetés a 24-ből, a CC1 és a CC2. Ezeket a csatlakozási pontokat az USB-C akkor használná, ha a másik oldalon is USB-C csatlakozó található. Ekkor ugyanis ezeknek a kivezetési pontoknak a feszültség állapotaiból állapítja meg a kábel csatlakoztatásának pozícióját (kétféle képpen lehet csatlakoztatni), valamint ha tápfeszt is visz a kábel, akkor a maximális áramot, Vbus feszültséget stb. Ezek most nem kellenek nekünk. Viszont ezeknek a pontoknak a feszültségét képes az alaplapra szerelt CH552 megmérni, mert pont olyan kivezetésekhez csatlakoznak (UCC1 és UCC2), ami beállítható analóg bemenetnek.

A driver telepítéshez érdemes rendszergazda felhasználóval belépni a Windows-ba, és elindítani az eszközkezelőt. Ha bedugjuk a nyomógomb megnyomása nélkül az alaplapot, akkor az eszközkezelő megjeleníti a Windows beépített driver-el működő USB eszközt és a kapott portszámot.

Ehhez tehát elvben nem kell driver! Azonban nekem ez nem ment ilyen könnyen. Minden bizonnyal már előtte össze-vissza telepítgettem és próbálkoztam, így ismeretlen eszközt kaptam csatlakoztatáskor. Az a gyanúm, hogy ez csak nálam, és csak akkor előforduló eset lehetett, mert van egy másik laptopom is, azon kapásból a fenti kép jelent meg az eszközkezelőben, csak más lett a portszám! Egyébként az ismeretlen eszköz esetén az alább ismertetett ZADIG programmal az „USB Serial CDC” drivert kell telepíteni, ha valakinél ez előfordulna.

Mint említettem az előbb, szükségünk lesz egy ZADIG nevű programra. Ez telepíti ugyanis a megfelelő drivert arra az esetre, amikor úgy dugjuk az alaplapot az USB portba, hogy nyomva tartjuk a boot gombot!

Töltsük le tehát a ZADIG programot innen: https://zadig.akeo.ie/ (vagy máshonnan), és indítsuk el. Ez a program fogja elvégezni a megfelelő USB driver telepítését. Ha nem rendszergazda felhasználóval vagyunk bejelentkezve a Windows-ba, akkor kelleni fog a rendszergazdai jelszó. A ZADIG működéséről annyit kell tudni, hogy nem kell újraindítani a programot ha új eszközt csatlakoztatunk. Folyamatosan frissíti az eszköz listát. Azonban előtte az „options” menüben be kell kapcsolni az eszközök megjelenítését:

Ha tehát csatlakoztattunk egy új eszközt, várjuk meg amíg megjelenik az eszközkezelőben, és nyissuk le a ZADIG eszközlistát. Az alábbi képek azt ábrázolják, amint csatlakoztattam a CH552 alaplapot benyomott boot nyomógombbal.

Fontos még megjegyeznem, hogy jómagam a boot gombot folyamatosan nyomva tartottam attól való félelmemben, hogy amikor a bootloader már nem vár tovább, akkor el fog tűnni az ismeretlen eszköz. Úgy emlékszem, ez meg is történik, de igyekeztem villámgyorsan csinálni mindent. A boot gomb folyamatos nyomva tartása mindazonáltal feleslegesnek tűnik. Amikor kiválasztottuk az ismeretlen eszközt, keressük meg alatta az alábbi képen látható drivert, és nyomjuk meg az „Install driver” gombot. Ennek felirata „Replace driver” is lehet, ha esetleg a gépen található valamilyen régebbi telepítés:

Ha esetleg nem lenne jól látható a képen, az említett github leírás instrukciói alapján a „libusb-win32 (v1.2.6.0)” drivert kell telepíteni.

Két laptopom van, mindkettőn telepítettem a drivereket, és mindkettő másként viselkedett. Ennek oka, hogy ez egyiken már járhatott a ZADIG program emlékeim szerint, és valamit telepítettem vele. Megmutatnám, hogy azon mit tapasztaltam, amikor csatlakoztattam benyomott boot gombbal a CH552-t:

Ezzel készen is vagyunk a modul első programjának feltöltésére. Sajnos az alaplapkezelővel telepített példa programok nem teljesen pontosak. Nyilván a github-on található alaplapkezelő esetében nem az általam beszerzett Wemos Studio CH552 szolgált alapmodulként (ez a képeken látszik is a github leírásban), és ezért más kivezetésen van az beépített led. A többi programban is lehetnek egyéb pontatlanságok. Az első program az alap blink program lett, amit azzal spékeltem meg, hogy az USB portra is küldök szöveget, amit az USB soros driver szolgai módon átfordít nálam a COM20-ra az egyik laptopomon, a másikon COM8-ra. Mint említettem már, ehhez a program feltöltés után kell egy reset az alaplapon, és a soros monitort is újra kell indítani. Az alábbi forrásban a setup()-ban található „INDUL…” szöveg nem is tud megjelenni, ehhez kellene előtte egy jó nagy időzítés, ami alatt a reset után újra indítom a soros monitort.  A példaprogram megértéséhez még annyit kell tudni, hogy a CH552 kivezetésein a 1-es és 3-as port bitjei jelennek meg (lásd feljebb a belső felépítés blokkvázlata). Ezért a kivezetések jelentése kicsit szokatlan az Arduino világhoz képest. pl. a 3-as port 0. bitjének kivezetés száma P3.0. Erre a kivezetésre kötötték a beépített led-et, és ennek száma a programban „30”!

void setup() {
  pinMode(30, OUTPUT);           //a beépített led a 30-as kivezetésen található
  USBSerial_println("INDUL..."); //Szöveget küldünk az USB portra
}

void loop() {
  digitalWrite(30, HIGH);        //led világít
  USBSerial_println("LED BE");
  delay(1000);                      
  digitalWrite(30, LOW);         //led nem világít
  USBSerial_println("LED KI");
  delay(1000);                      
}

Az USB portra történő írásban már látható, hogy mit is jelent az, hogy a C++ helyett csak C nyelvben írhatunk programokat. Az eddig „C++”-ban megszokott módszer az volt, hogy „.”-al választottuk el egy objektum metódusait. Emlékeztetőül a „Serial” objektum „println” metódusának használatával így nézne ki a fenti programsor: „Serial.println()”. Mivel az objektum orientált programozás nem áll rendelkezésre, az objektumok osztály definicióiban található függvényeket mind-mind külön meg kellett írnia az alkotóknak. Így lett C-ben USBSerial_println() függvény! A Serial helyett az USBSerial azért született, mert az a vezérlő ténylegesen a saját beépített USB portjára ír. Van neki igazi soros portja is (UART0 és UART2). Ha pl. megnézed a példák között szállított UsbSerialAdaptor nevű mintapéldát, ott láthatod, hogy a 10-es kivezetésen megvalósított soros TX kimenetre a Serial10_println() függvénnyel tudsz írni! Azért „Serial10”, mert ennek a soros portnak a kimenete a P1.0 azaz „10”-es kivezetésre lett kivezetve. Emlékeztetőül az Arduino nano egyetlen soros portjára a Serial.println() programsorral tudnunk információt küldeni! Ha Te kedves kezdő a fenti szavakból nem értesz mindent, akkor ne keseredj el, rengeteg oktatóanyagot találsz a net-en a témáról. Én is most tanulom!

A következő mintapélda a bemenet használata. A P3.3 bemenetre (azaz a 33-as kivezetésre) egy nyomógombot kötöttem. A program lekérdezi a bemenet állapotát, és fel vagy lekapcsolja a beépített led-et az állapottól függően:

void setup() {
  pinMode(30, OUTPUT);          //A led a P3.0 kivezetésen van
  pinMode(33, INPUT_PULLUP);    //Egy külső nyomógombot tettem a 33-as kivezetésre, és bekapcsoltam a belső felhúzó ellenállást
                                //Ha nem kell a belső felhúzó ellenállás, akkor simán "INPUT"-ot kell írni.
}

void loop() {
  if (digitalRead(33)) {        //Kiolvassuk a 33-as bemenet állapotát
    digitalWrite(30, HIGH);     //led világít
  } else {
    digitalWrite(30, LOW);      //led nem világít
  }
}

A következő mintapélda az touch bemenetek használatát mutatja be. A „Touchkey.h”-t kell includ-al beemelni, és kész is. A konkrét működést (a chip belső technikai működését) nem értem, de feltételezem, hogy valamilyen kapacitív hatást érzékel, és vélelmezem, hogy a leolvasáshoz idő szükséges. Kíváncsi voltam, hogy mekkora vezetéket köthetek a bemenetre ahhoz, hogy jól működjön. 50cm-es vezetékkel még működött, azaz a bemenetre kötött vezeték másik végét érintettem. A fiókban egy 2m-es riasztó vezetéket is találtam. Annak a csatlakoztatásakor már bizonytalan volt a bemenet. Hol 0, hol 1 jelent meg a programban. Általában inkább 1-et mutatott, illetve egy érintés után hosszú ideig nem váltott vissza 0-ra. Tehát a kivezetésre csak néhány cm-es vezetéket célszerű kapcsolni!

#include <TouchKey.h>

byte touch_on[]={0,0,0,0,0,0};  //a tömb elemei 0 és 1-el jelzik, ha az adott bemenetet megérintették

void setup() {
  pinMode(30, OUTPUT);          //A led a P3.0 kivezetésen van
  TouchKey_begin(1|2|4|8|0|32); //Engedélyezzük az egyes bemeneteket: TIN0(P1.0), TIN1(P1.1), TIN2(P1.4), TIN3(P1.5), TIN4(P1.6), TIN5(P1.7)
                                 //Sajnos a fordítónak nem lehet binárisan megadni a paramétert (pl.: B00110011)
                                 //Ezért kellett vagy kapcsolattal összemaszkolni az eredményt. Ha egy bemenetet nem akarunk 
                                 //érintő bemenetnek használni, akkor oda 0-át kell írni. Pl. ha P1.6-ot másra használjuk, akkor 
                                 //így kell megadni a bemenő paramétert 1|2|4|8|0|32 (ez ennek felel meg: B00111101)
}

void loop() {
  touch_lekerdez();
  //a TIN1.0 bemenetttel vezéreljük a beépített led-et.
  if (touch_on[0]) {
    digitalWrite(30, HIGH);
  } else {
    digitalWrite(30, LOW);
  }
  //Megjelenítjük a tömb elemeit, hogy lássuk a soros monitoron is az eredményt
  USBSerial_print(touch_on[0]);
  USBSerial_print(" ");
  USBSerial_print(touch_on[1]);
  USBSerial_print(" ");
  USBSerial_print(touch_on[2]);
  USBSerial_print(" ");
  USBSerial_print(touch_on[3]);
  USBSerial_print(" ");
  USBSerial_print(touch_on[4]);
  USBSerial_print(" ");
  USBSerial_println(touch_on[5]);
}

void touch_lekerdez() {
  TouchKey_Process();                    //bemenet ellenőrzésének előkészítése
  byte touch_eredmeny=TouchKey_Get();    //bemenet állapotának lekérdezése. A változóban bitenként adja vissza
                                         //a bemenetek állapotát (amelyik bemenet engedélyezve van). Pl. ha csak
                                         //TIN1.0 lett megérintve, akkor az eredmény 1. Ha csak TIN1.1 lett megérintve
                                         //akkor az eredmény 2. Ha mindkettő, akkor 3.
  //A lekérdezés eredményéből kimaszkoljuk a biteket és feltöltjük a tousc_on[] tömb elemeit
  if (touch_eredmeny&1) {touch_on[0]=1;} else  {touch_on[0]=0;}
  if (touch_eredmeny&2) {touch_on[1]=1;} else  {touch_on[1]=0;}
  if (touch_eredmeny&4) {touch_on[2]=1;} else  {touch_on[2]=0;}
  if (touch_eredmeny&8) {touch_on[3]=1;} else  {touch_on[3]=0;}
  if (touch_eredmeny&16) {touch_on[4]=1;} else  {touch_on[4]=0;}
  if (touch_eredmeny&32) {touch_on[5]=1;} else  {touch_on[5]=0;}
}

A negyedik mintapéldám azt mutatja be, hogyan lehet az analóg bemeneteteket használni. Egyébként pont úgy mint C++-ban, csak vannak apróságok, amik másként működnek. Speciel ennél a példaprogramnál jöttem rá, hogy egy változó deklarálásakor az azonnali értékadásban nem használhatok függvényt! Profi C-s programozóknak ez biztosan egyértelmű, nekem rá kellett jönnöm! A példa programban ugyanis arra is kerestem a választ, hogy hány mintavételt tudnék csinálni az ADC-vel egy másodperc alatt, és ehhez kellett egy long változó, amit a millis() értékével szerettem volna feltölteni a deklarációkor. Egyébként így is sikerült megmérnem, hogy ~73.000 mérés történt. Természetesen csalóka az adat, mert más programsorok is bekerültek a loop()-ba, amikkel szintén megy az idő, de nekem ez az adat is beszédes. Arduino nano esetén úgy emlékszem kb. 10.000 mintavételt tapasztaltam. Persze ne feledjük, ez az ADC csak 8 bites! A programban kipróbáltam a PWM kimenetet is, a led fényerejét rögvest be is állítom az ADC bemenetére adott feszültséggel. A bemenetre a feszültség egy potméterről érkezik, amit a GND és a chip tápfesz közé kötöttem. A referencia feszültség a tápfesz a chip-ben!

byte poti_fesz=0;  //Ebbe a változóba olvasom be a bemenet feszültségét. Elég a byte típus, mert csak 8 bites az ADC
long idozites;     //Másodpercenként kiírom, hogy mennyi mérést végzett az ADC, ennek időzítéséhez segédváltozó
long meres_db=0;   //A mérések darabszáma, másodpercenként nullázom, mérésenként növelem.

void setup() {
  pinMode(30, OUTPUT);   //A led a P3.0 kivezetésen van
  pinMode(32, INPUT);    //Egy külső potmétert tettem 32-es kivezetésre, és ezt tekergetem. Belső felhúzó ellenállás nem kell.
  idozites=millis();     //a változó deklarálásakor az értékadásba nem lehet függvényt írni (gondolom ez C tulajdonság)
}

void loop() {
  poti_fesz=analogRead(32);       //kiolvassuk a bemenet feszültséget (0V=0 ... 5V=255);
  meres_db++;
  if (idozites+1000<millis()) {
    USBSerial_print("Meresek száma /sec:");   //Eredményt kiírjuk a soros monitoron 
    USBSerial_println(meres_db);   //Eredményt kiírjuk a soros monitoron 
    meres_db=0;
    idozites=millis();
    analogWrite(30, poti_fesz);     //A poti feszültsége alapján beállítjuk a PWM kimenet kitöltési arányát (fényerőt)
  }
}

Mivel a DS3231 RTC órához már régebben saját függvényeket írtam, és ezekben nem használtam ki a C++ összes lehetőségét (azaz C-ban írtam meg mindent), lehetőségem volt, hogy kipróbáljak egy valós működő dolgot. Ehhez persze szükség van az I2C kapcsolatra, ami alapból nincs a chip-be építve. Tehát első lépés a szoftveres I2C kipróbálása. Természetesen működik a példaprogram. Az I2C portscenner programot csináltam meg elsőnek, és rádugdostam az egyik Ds3231 modulomat. Íme a működő program forrása:

#include <SoftI2C.h>
byte valasz;

void setup() {
  scl_pin = 30; //SoftI2C külső változója az SCL kivezetés beállítására
  sda_pin = 31; //SoftI2C külső változója az SCL kivezetés beállítására
  I2CInit();    //I2C inicializálása
}

void loop() {
  USBSerial_println("I2C port scennelés start:");
  for (byte i = 0; i < 128; i++) {    //végigmegyünk az összes lehetséges címen
    USBSerial_print("Cím: 0x");
    USBSerial_print(i, HEX);
    I2CStart();                       //I2C átvitel indítása 
    valasz = I2CSend(i << 1 | 1);     //adott című slave megcímzése. Az utolsó bítben állítjuk be, hogy olvasni akarunk (1)
                                      //vagy írni (0) a slave címére.
    USBSerial_print(" Válasz:");      //Ha a válasz 0, akkor találtunk egy eszközt az adott címen
    USBSerial_println(valasz);
    I2CStop();
    delay(1);
  }
  delay(2000);
}

Kicsit átalakítottam az eredetit, mert látni szerettem volna, hogy milyen hibakódot ad vissza stb. Meglehetősen szokatlan a szoftveres I2C függvények használata, de végül is könnyedén megfejthető a működés. Nem is részletezném, akinek kell, ismerje meg alaposabban. Itt található az eredeti Arduino változat!

Most érkezik el a nagyobb feladat. Elővettem a saját magam által barkácsolt DS3231 kezelő függvényeket és elkezdtem soronként átírni. Az eredeti program a DS3231 leírás legvégén a “Wire.h” használatával megismerhető. AZ új program a szoftveres I2C használatával pedig ez lett:

#include <SoftI2C.h>

int ev;             //év tárolása
byte ho;            //hó tárolása 
byte nap;           //nap tárolása 
byte het_napja;     //hét napja (1-hétfő....7-vasárnap)
byte ora;           //óra tárolása (24 órás üzemmódot fogunk használni)
byte perc;          //perc tárolása 
byte masodperc;     //másodperc tárolása 
bool pontos;        //ez a változó fogja megadni az idő kiolvasását követően, hogy pontos-e a kiolvasott időpont 
                    //(ha elemes működés közben leáll a DS3231 oszcillátora, akkor az "OSF" flag bebillen, és ezt olvassuk ki

byte bcd_Dec(byte val) {  // Convertál bcd számból decimálisba
  return ( (val/16*10) + (val%16) );
}
byte dec_bcd(byte val) {  // Convertál decimális számból bcd-be
  return ( (val/10*16) + (val%10) );
}


void setTime24() {
  //status regiszter beállítása
  I2CStart();
  I2CSend(0x68 << 1 | 0);         //Írni fogunk a DS3231-re.
  I2CSend(0x0E);                  //A control regiszterbe fogunk írni, ez annak a címe a chip-ben
  I2CSend(0x63);                  //Ez a control regiszter új tartalma (ez az érték binárisan 0b01100011)
                                  //BBSQW bit 1, vagyis elemes táplálásnál is működik az oszcillátor
                                  //ha 1-re állítanánk, elemes táplálásnál nem járna az óra, csak őrizni az utolsó időpontot
                                  //A RS2 és RS1 bit 0-val beállítja a négyszögjel kimenetet 1Hz-ra, ezen lehet változtatni:
                                  // 0b01101011 esetén 1024Khz, 0b01110011 esetén 4096Khz, 0b01111011 esetén 8192Khz.
                                  //A INTCN bit 0-val beállítja, hogy az INT/SQW kimenet beállított frekvenciájú jelet ad ki.
  //címben a státus regiszter következik, nem kell megcímezni csak írni, és az adat oda kerül
  I2CSend(0x00);                  //A status regiszterben töröljük az OSF fleg-et, ami 1-el jelzi majd, ha elemes táplálásnál 
                                  //nem működött az oszcillátor, vagyis az óra valószínűleg nem pontos.
                                  //Az EN32Khz bit-et is 0-ra írjuk, azaz letiltjuk a 32Khz-s kimenetet
  I2CRestart();
  I2CSend(0x68 << 1 | 0);         //Írni fogunk a DS3231-re.
  I2CSend(0x00);                  //Most az adatregiszterekbe írjü1k a pontos időt, ez a legelső adatregiszter címe a chip-ben
  I2CSend(dec_bcd(masodperc));
  I2CSend(dec_bcd(perc));
  I2CSend(dec_bcd(ora) & 0b10111111);
  I2CSend(dec_bcd(het_napja)); 
  I2CSend(dec_bcd(nap));
  I2CSend(dec_bcd(ho));
  I2CSend(dec_bcd(ev-2000)); 
  I2CStop();
}

void getTime24() {
  //kiolvassuk a dátumot és az időpontot 
  I2CStart();
  I2CSend(0x68 << 1 | 0);     //Írni fogunk a DS3231-re.
  I2CSend(0x00);              //az adatregisztereket fogjuk kiolvasni, ez a kezdőcím a chip-ben
  I2CRestart();
  I2CSend(0x68 << 1 | 1);     //Olvasni fogunk a DS3231-ből.
  masodperc = bcd_Dec(I2CRead());
  I2CAck();
  perc = bcd_Dec(I2CRead());
  I2CAck();
  ora = bcd_Dec(I2CRead());
  I2CAck();
  het_napja = bcd_Dec(I2CRead());
  I2CAck();
  nap = bcd_Dec(I2CRead());
  I2CAck();
  ho = bcd_Dec(I2CRead() & 0b01111111);
  I2CAck();
  ev = bcd_Dec(I2CRead())+2000;
  I2CNak();
  I2CRestart();
  //Kiolvassuk a status regiszterből a osf flag-et, ami megmondja, ha elemes táplálásnál leállt az oszcillátor
  I2CSend(0x68 << 1 | 0);     //Írni fogunk a DS3231-re.
  I2CSend(0x0F);              //ez a státus regiszter címe
  I2CRestart();
  I2CSend(0x68 << 1 | 1);     //Olvasni fogunk a DS3231-ből.
  if (I2CRead() & 0b10000000) {pontos=0;} else {pontos=1;}  //Kiolvassuk a status regisztert, kimaszkoljuk az OSF bitet.
                                                            //Ez alapján 1-el jelezzük, ha az óra valószínűleg pontos
  I2CStop();
}

float getTemperature() {
  // Kiolvassa a hőmérséklet értékét a 0x11h és 0x12h regiszterekből
  float temp;
  I2CStart();
  I2CSend(0x68 << 1 | 0);                //Írni fogunk a DS3231-re.
  I2CSend(0x11);                         //a DS3231 chip 0x11 és 0x12-es regisztere a hőmérséklet regiszter
  I2CRestart();
  I2CSend(0x68 << 1 | 1);                //Olvasni fogunk a DS3231-ből.
  temp=I2CRead()+(0.25*(float)(I2CRead()>>6)); //hőmérséklet MSB és LSB regisztere kiolvasás, hőmérséklet számítás
  I2CStop();
  return temp;                           //visszatérési érték
}

void setup() {
  scl_pin = 30; //SoftI2C külső változója az SCL kivezetés beállítására
  sda_pin = 31; //SoftI2C külső változója az SCL kivezetés beállítására
  I2CInit();    //I2C inicializálása
  //az óra beállítása (csak egyszer kell megcsinálni, aztán ki kell kommentezni
  masodperc=0;
  perc=50;
  ora=5;
  het_napja=7;
  nap=13;
  ho=11;
  ev=2022; 
  setTime24();   //óra beállítása: négyszög kimenő jelet kikapcsolja, 
                 //32Khz-s kimenetet kikapcsolja és 24 órás üzemmódot állít be.
                 //Beállítja a pontos időt a feti változók értékével 
  
}

void loop() {
  USBSerial_println("Pontos idő:");
  getTime24();                   //kiolvassuk az időt a változóba
  USBSerial_print(ev,DEC);USBSerial_print("-");
  USBSerial_print(ho,DEC);USBSerial_print("-");
  USBSerial_print(nap,DEC);USBSerial_print("  ");
  USBSerial_print(het_napja);
  USBSerial_print("  ");USBSerial_print(ora,DEC);
  USBSerial_print(":");USBSerial_print(perc,DEC);
  USBSerial_print(":");USBSerial_println(masodperc,DEC);
  if (pontos==0) {USBSerial_println("Az óra valószínűleg nem pontos");}
  USBSerial_print("Hőmérséklet:");USBSerial_println(getTemperature());
  delay(1000);
}

Az eredeti programból csak a DS3231 parancskészletének tudását használtam fel, a programból szinte egy betű sem maradt változatlanul. Elég nagy szívás volt a C++ program átírása C-re. Nem igazán tudom, hogy az alábbi tapasztalatokból mik a nyelvi sajátosságok, és melyek azok amelyek az Arduino IDE C programnyelv kialakításának következményei, de roppant kényelmetlen a programozás ebben a környezetben.

  • A program forrásban fontos a függvények deklarálásának sorrendje. Ebből következően a setup() illetve a loop() szinte biztosan a legutolsó függvény a forráskódban. Ha egy függvényre a forrásban feljebb hivatkozunk, mint ahogy deklaráltuk, akkor azt feljebb kell másolni.
  • A fenti körülményből következően nem használhatjuk a forráskód füleket, minden forráskódnak egyetlen állományba kell kerülnie (legalább is így tapasztaltam, nem küzdöttem sokat vele)
  • Nem tudtam változó struktúrát deklarálni! A struktúrát még létre tudta a forrás hozni, de amikor az adott struktúrával akartam változót deklarálni, az már hibára futott. Ezzel sem küzdöttem sokat.
  • Változó értékadásban nem lehet olyan függvény, amit még csak a forrásban helyileg később írunk le.
  • Az USBSerial_print() függvény nem tud bool típusú változót írni. (Lehet, hogy a Serial.println() sem tud, csak most jut eszembe, miközben írok, hogy meg kellene nézni).
  • Változó értékadásnál nem tudtam a bitenkénti bináris értéket megadni. Pl. 0x63 helyett nem írhattam ezt: 0b01100011. Ezzel sem küzdöttem sokat, így ez is lehet félreértés részemről.

További példa programokat egyelőre nem készítettem. Már nézegettem az LCD kijelzők meghajtó chip-ek működését és parancskészletét. Egyszerűbb újra megírni néhány parancsot, mint átemelni a rendelkezésre álló könyvtárakból. Ha minden a terveim szerint alakul (…és soha nem úgy alakul), akkor néhány hét múlva felrakom ide a leírás végére az LCD kijelző, Dallas DS18B20 hőmérő példa programjait. I2C-vel működő EEPROM-ra van is mintapélda. FRAM illesztése annyira egyszerű, hogy szoftveres I2C-val azonnal mehet elméletileg. A 6 digites LCD számkijelzőhöz meg csak SPI kommunikáció kell, és ehhez is vannak saját függvényeim, tehát elméletileg ez sem lehet gond. Ennyi alkatrészből már össze lehet rakni az Apolló űrhajót! Több energiát egyelőre nem akarok belefektetni!

Mennyire volt hasznos amit olvastál? Értékelés után szövegesen is leírhatod megjegyzéseidet és véleményedet!

Kattints egy csillagra az értékeléshez!

Szövegesen is leírhatod véleményedet! Ha kérdésed van, ne felejtsd el megadni az email címedet!