2022. szeptember 5., hétfő

Szabályozható áramkorlát 230V-ra

       Tápegységek javításakor vagy hálózati feszültségen működő egyéb elektromos készülékek élesztésekor szükség lehet egy korlátozó eszközre, ami lekapcsolja a fogyasztót, ha annak áramfelvétele meghaladja az előírt határértéket. Ez megvédheti a fogyasztót a végső meghibásodástól, illetve megelőzheti a kismegszakító leoldását szélsőséges túláram esetén. Olyan esetben is hasznos, amikor nem áll rendelkezésre áramkorláttal ellátott nagyfeszültségű labortápegység, a javítandó készülék áramfelvétele pedig nem elég nagy a kismegszakító lekapcsolásához, viszont elég nagy ahhoz, hogy a készülékben nagyobb károkat okozzon.
 
      A feladat tehát egy olyan konnektor készítése, ami méri az fogyasztást és a beállított határértéknél (max 16A) lekapcsolja a fogyasztót. Nem szükséges nagy pontosságú legyen (legfeljebb 10mA felbontás – hogy minimum 2.3W-os lépésekkel mérjen), de oldja le a fogyasztót, ha túl nagy az áramfelvétel. Végső soron izzót is sorba lehet kapcsolni, vagy lehet szabályozható toroid transzformátorral (Variac) csökkenteni a feszültséget, de valami hordozható (kicsi), praktikusabb, olcsó és viszonylag egyszerű megoldásra lenne szükség. A legkisebb eszköz, ami fogyasztást mér, az a fogyasztásmérő konnektor:


Ez a 10 – 20 dolláros készülék feszültségszintet és áramerősséget mér, amiből kiszámítja a fogyasztást. Az ötlet az (lett volna), hogy a készülék által mért áramerősséget összehasonlítva egy határértékkel, egy logikai áramkör lekapcsolja a konnektort a hálózatról.

A gyári fogyasztásmérő működése


      A gyári készülék a kínai BL0937 egypólusú energiamérő IC-t használja. Mindkét irányban képes mérni és kimenetén az átlag, illetve a valós idejű aktív teljesítményről szolgáltat információt. Továbbá képes az áramerősség és a feszültség effektív értékét is jelezni. Az IC lábkiosztása és blokkválzlata a következő:
Rámérve a CF lábra, ami az aktív teljesítménnyel arányos frekvenciájú impulzust generál,  a következő eredmények születtek:

100W-os terhelés, Impulzusfrekvencia CF-en 60Hz

50W-os terhelés, CF=30Hz
10W-os terhelés, CF=2Hz

1W-os fogyasztó esetén 0.6Hz-en jönnek az impulzusok, a frekvencia tehát nagyon alacsony. Hogy ezt mérni lehessen, szükség van legalább 2 impulzusra, vagyis 1,6 másodpercre. Ha csak ennyi idő után old le konnektor, akkor könnyen tönkre mehet a rácsatlakoztatott készülék. A fogyasztásmérő is kb. 4-5 másodperc után jelzi ki a valós fogyasztást.
Hasonló a helyzet a CF1 lábakon is, melyek áramerősséget és feszültséget jeleznek impulzusok formájában. Az energiamérő IC kimenetei tehát nem alkalmasak arra, hogy az áramkorlátozó konnektor vezérlőjelei legyenek.
 
Egy másik lehetőség lenne az energiamérő IC analóg bemeneteinek használata.  Az adatlapból kivett, alkalmazási blokkdiagram a következő:

Hasonlóképp van kivitelezve az áramkör a gyári panelen is. A feszültségmérés egy egyszerű feszültségosztóval történik, ami a 230V-os amplitúdót 100mV-os nagyságrendre csökkenti. Az árammérés az 1mohm-os söntön eső feszültség mérésével történik, ami igen csekély (pl. 100W-os terheléssel 434uV), ezért az IC-ben egy PGA felerősíti az ADC-nek megfelelő szintre. Ha ezt az utat választom, akkor a mikrovezérlő ADC-je elé szintén szükség lesz egy PGA-ra és az ADC felbontása is legalább 16-bites kell, hogy legyen. Egy ilyen felszerelés már nem olcsó, de a fő gond, hogy nincs galvanikus izolálás a processzor és a 230V-os hálózati vezeték között, vagyis a feszültségtüskék tönkre tehetik az eszközt. Ami fogyasztásmérő IC-t illeti, így egyszerűbb a mérés, mert a galvanikus elválasztók fázistolást vinnének a jelbe, ami hibás teljesítménytényezőt eredményez és elrontja a számításokat. Jelen esetben viszont nincs szükség a teljesítménytényezőre, mert nem a teljesítmény, hanem az áramfelvétel mérése a cél, tehát nem probléma, ha a mérőjel fázistolást szenved a galvanikus elválasztás során.

Miután a fogyasztásmérő érzékelői nem használhatóak, így a kijelző adatai sem lesznek aktuálisak, mert nem a kijelzett értékek alapján fogja lekapcsolni a fogyasztót a műszer. A kijelző tehát új vezérlést igényel, ami egyrészt rossz, mert csupaszon kell meghajtani (rengeteg I/O lábra lesz szükség), másrészt viszont jó, mert a gombokkal beállítható egy határérték és kijelezhető a túláram is. Ez viszont korlátot szab a szenzornak, ugyanis a folyadékkristály meghibásodhat, ha 5V-tal van polarizálva, így 3.3V-os tápegység szükséges, vagyis a szenzor jelszintje is legfeljebb 3.3V lehet az ADC bemeneten. Alkalmazható feszültségosztó, vagy LLC a kijelző jelszintjének illesztésére, azonban 20+ kimenet esetén ez rengeteg áramköri komponenst jelent.

Áramszenzor


A söntellenálláson kívül még van két módszer, amivel áramerősséget lehet mérni:
  1. Áramtranszformátorral (CT – Current Transformer): egy toroid tekercs, aminek a közepén halad át a mérendő tápvezeték. Ez képezi a primer ágat, és a rajta átfolyó árammal arányos áramerősség mérhető a szekunder ágon – ami maga a tekercs. A tekercsre párhuzamosan kapcsolt ellenálláson már biztonságosan mérhető a feszültségesés, mert a tekercs nincs galvanikus kapcsolatban a hálózattal.
  2. Hall szenzorral: egy megszakított mágneses gyűrű, aminek a közepén halad át a mérendő tápvezeték. A vezetéken átfolyó áram mágneses mezőt gerjeszt, amit a gyűrű a légrésben lévő hall szenzorhoz összpontosít. A szenzoron az áramerősséggel arányos feszültség mérhető.
A szenzor feszültségét egy 3.3V-ról üzemelő mikrovezérlő 10 bites ADC-je méri majd. Ez azt jelenti, 1024 feszültségszintet tud megkülönböztetni 0 és 3.3V között, vagyis 3.22mV a felbontása.

 A helyigény miatt a második opció lenne előnyösebb, ugyanis a Hall effektusos áramérzékelők általában IC-be vannak tokozva, ami tartalmazza az erősítőfokozatot is. Ilyen az ACS712 integrált áramkör, melyből a 20A-es változat kimenete 100mV/A, azaz 2V a mérési tartomány. Ez 1mV/10mA felbontást jelent, ami túl apró a 10 bites ADC-nek. Másik probléma, hogy szükség van jelszint illesztésre, mert az IC 5V-os tápot igényel, kimenete pedig 2.5V-ra van emelve, vagyis innen oszcillál 0 és 5V irányába.

 Végül maradt a CT, ami bár nagyobb, de sokkal jobban kezelhető a kimenete.
A kiválasztott CT trafó 20A/20mA vagyis, ha 20A folyik a primeren, akkor 20mA folyik a szekunderen. Olyan ellenállást kell tenni a szekunderre, amin 16mA mellett 3.3V esik. Ez durván 206ohm, ha a csúcsáramot nézzük. Az áramfogyasztás tehát 0 és 3.3V közé van skálázva, a felbontás pedig 2,06mV/0,01A, ami már közelebb áll az ADC felbontásához.

 A CT teljes szigetelést nyújt a 230V-os hálózattól, és ha valamiért a műszer nem kapcsolná le a konnektort túláram (>16A vagy rövidzár) esetén, akkor a CT telítődik, a mágneses gyűrű már nem reagál a primer oldal mágneses fluxusának további növekedésére, mert a ferromágneses anyag mindenik mezővektora már beállt a mágneses tér irányába. Ilyenkor a szekunder áram tele van felharmonikusokkal és erősen torz, ami hibás mérést eredményez, azonban a műszert nem teszi tönkre.

A CT fázistolást visz a primer és szekunder árama közé, amely hatással van a teljesítménytényezőre. Az induktív és kapacitív terhelések során ez mérési hibához vezet, aminek kiküszöbölése eléggé körülményes hardver és szoftver szempontjából is. Most azonban nem a teljesítmény, hanem az áramerősség mérése a cél (nem túl nagy pontossággal), ezért a fázistolás elhanyagolható.
 

Árammérés


      Az áramszenzor kimeneti feszültsége egy 50Hz-es szinuszos jel, aminek amplitúdója az áramerősséggel arányos. A jelalak tengelye 0V-on van, így a jelszint -3.3V +3.3V között oszcillál. Ahhoz, hogy az ADC helyes mintát tudjon venni, a jel tengelyét 1.65V-ra lehet emelni, ám ekkor a mérési tartomány 1.65V-ra csökken, ami nem jó. Másik lehetőségként le lehet vágni, vagy megfordítani a negatív félhullámokat egy-egy Schottky diódával. A diódák használata ront a mérés linearitásán, de ugyanakkor javít a minták pontosságán, és megfelezi a mintavételezéshez szükséges időt. A 206 ohmos söntön mért jel 100W terhelés mellett a következő:


Legkevesebb negyed periódusig, vagyis a félhullám 0-tól maximumig ívelő alakjának időtartamáig is elég mintavételezni, ami 50Hz esetén 5ms.


Ez alatt minél több mintát kell venni, ám a sűrű mintavételezés ront a pontosságon, ugyanis gyorsabban kell dolgozzon az ADC. 150 minta esetén, ha egy mintavétel 13 óraciklusba telik, 1950 óraciklusra van szükség, ami ha 5ms alatt kell lejárjon, akkor 1 óraciklus 2.56us-ig tart, azaz a mintavételezési frekvencia 390kHz. A mintavételezést érdemes automatikus megszakításra állítani, hogy míg a háttérben fut, addig egyéb műveleteket lehessen végezni, mint a kijelző frissítése és a gombok ellenőrzése. Semmi esetre sem szabad tárolni a mintákat, hanem rögtön fel kell dolgozni, hogy minél kevesebb legyen a fölösleges műveletet.

1. Lépés: minta feszültséggé alakítása: 
\[V_{ADC}=\frac{ADC \cdot 1024}{VCC}\]
2. Lépés: négyzetes középérték kiszámítása N darab mintára:
\[V_{TRMS}=\sqrt{\frac{1}{N}\sum_{i=0} ^{N} V_{ADC_i} ^{2}}\]
C-ben ez a következőképp néz ki:

if(i<N && converted) //N darab minta osszegzese
{
      adc_read(); converted=0;              //1 ADC minta
      adc_voltage = (adc_value * VCC)/1024; //minta feszultsegge alakitasa
      avg_voltage += pow(adc_voltage,2);    //feszultsegek negyzetosszege
      i++;
}
if (i==N)
{
      i=0;
      avg_voltage = avg_voltage/N;        //kozepertek
      rms_voltage = sqrt(avg_voltage);    //negyzetes kozep
      rms_current = rms_voltage / Shunt;  //I=U/R
      rms_current = rms_current*CT_ratio; //mA -> A
      meas_value  = rms_current*100;      //kijelzohoz valo igazitas (2 tizedes)
}

Mindezt addig kell ismételni, míg N-szer le nem fut, így nem kell tárolni és külön „for” ciklussal kiolvasni a memóriából.


Az LCD vezérlése


      A fogyasztásmérő kijelzője egy egyedi kialakítású szegmentált folyadékkristályos kijelző, ahol a szegmensek vezérléséhez nem áll rendelkezésre közismert vezérlő IC. Az ilyen esetekben, ahol az LCD egy bizonyos készülék számára volt gyártva, a szegmenseket egy célprocesszor közvetlenül vezéreli, és legtöbb esetben valamilyen multiplex meghajtás alapján vannak a szegmensek összekötve, hogy kevesebb kivezetése legyen a kijelzőnek. A szétbontott fogyasztásmérő kijelzőjének 30 darab kivezetése van.


A kivezetések azonosítása


      Első lépésként azonosítani kell a lábakat. Ehhez tudni kell, hogy a folyadékkristály szegmens bekapcsolása a LED-es szegmenssel ellentétben váltakozó feszültséggel, leginkább négyszögjellel történik. Míg a LED folyamatosan aktív, ha feszültséget kap, addig a folyadékkristály néhány másodperc után kifehéredik és károsodik, ha egyenfeszültséggel van meghajtva. Ez azért van, mert a változatlan elektromos tér hatására mindenféle szennyeződés beágyazódhat a szegmens felületébe, ami a feszültség eltávolítása után is fenntarja az elektromos mezőt (a felfogott töltések miatt) és ez tönkreteheti az LCD szegmenst. Fontos tehát, hogy a kivezetéseken váltóárammal keresgéljük a szegmensekhez tartozó kivezetéseket.

      Az LCD szegmens bekapcsolási feszültsége általában 3-5V, kikapcsolása pedig 0-1.5V váltakozó feszültséggel történik, 50Hz vagy nagyobb frekvencián, hogy a villogás ne legyen szembetűnő. Az LCD kivezetései az üvegre felvitt indium-ón-oxid fémkeverék bevonatból állnak, amik egy zebra gumicsík segítségével létesítenek elektromos kapcsolatot a meghajtó áramkörrel. Az üvegen lévő bevonat alig látható és nagyon sérülékeny, ezért nem ajánlott ezeken közvetlenül mérőszondával vagy egyéb csatlakozóval mérni. Érdemes az LCD eredeti meghajtó panelét használni, vagy egy új panelt készíteni, melyet a zebra gumi kapcsol össze az LCD-vel. Az utóbbi esetben pontosan olyan távolságra kell a panel rézkivezetéseit tervezni, ahogyan az üveglapon is szerepelnek, és összeszerelésnél is pontosan egymás fölé kell a kivezetéseket igazítani. A gyári panel birtokában a méretek adottak.


Mind a 30 kivezetés külön vezetékhez tartozik, aminek lecsupaszított vége megfelel a breadboard-on való kísérletezéshez. Mivel nagyon kevés áram is elegendő a szegmensek bekapcsolásához, előfordul, hogy a szegmensek bekapcsolnak a vezetékek érintésekor. Ennek elkerülése érdekében minden vezetéket a földre kell kapcsolni és egyszerre egy vezetéket szabad táplálni.


A tápfeszültséget egy mikrovezérlő I/O lába adja, amit ki- és bekapcsolgat egy program.
Az itt előállított 3.3V/50Hz négyszögjel talán nem egyezik az eredeti panelen alkalmazott jelalakkal, viszont a tesztelésnek éppen megfelel, és nem teszi tönkre ezt a kijelzőt. A kivezetések próbálgatása során legtöbb esetben 3 vagy 4 szegmens illetve szimbólum kapcsol be a kijelzőn – ezek a szegmens kivezetések, 4 esetben pedig 20-nál is több bekapcsol– ezek a közös vagy COM kivezetések. A kivezetéseket 1-től 30-ig számozva azonosíthatók a szegmenscsoportok. Az alábbi ábrán a 6-os szegmenscsoport van bekapcsolva és mindenik szegmens a hozzá tartozó kivezetés számával van ellátva.


A szegmenseket lehet vezérelni a szegmensvezetékeket váltogatva, illetve a COM kivezetéseket váltogatva. Amikor az egyik COM kivezetés kap feszültséget, miközben a többi szegmens a földre van kötve, akkor az összes szegmens bekapcsol, ami a COM kivezetéshez tartozik. A következő ábrán a 29-es COM szegmensei vannak bekapcsolva (bal oldalt), valamint a négy COM kivezetéshez tartozó szegmensek vannak különböző színekkel jelölve (jobb oldalt).


Legyenek a kivezetések nevei COM0 (30), COM1 (29), COM2 (28) és COM3 (27). Mind a 4 COM kivezetésre jelet kapcsolva az összes szegmens bekapcsol.


A fentiek ismeretében most már a szegmensek külön is felkapcsolhatók, például a 6. szegmenscsoport felső szegmense a  COM0 kivezetéssel aktiválható:


A gyári vezérlés


      Ennek a szegmensnek a gyári vezérlése ellenőrizhető, ha oszcilloszkóppal rámérünk a 6. és a COM0 kivezetésre a gyári meghajtópanelen. A szegmens a fogyasztásmérő első bekapcsolásakor is aktív, amikor 0.0W fogyasztást mutat.


A fenti ábrán a pirossal a 6-os szegmenscsoport, sárgával pedig a COM0 jelalak látható. A szegmens akkor kapcsol be, amikor a vezérlőjele magas és COM0 alacsony, illetve amikor a vezérlőjel alacsony, COM0 pedig magas. Ez a vezérlő négyszögjel félhullámainak első 1ms idejére igaz, a maradék 3ms-os időablak COM1, COM2 és COM3-nak van fenntartva.
Ezalatt COM0 köztes feszültségen van (2.3V vagy 1.2V), és a szegmens nincs polarizálva. A gyári panel tehát a szegmenseket 1kHz-en és 3.5V feszültségen hajtja. A COM kivezetések pedig szerre kapnak időrést a szegmensek kapcsolgatására, amit szaknyelven négyszeres időmultiplexelt vezérlésnek neveznek. A fenti ábra a 6-os szegmenscsoport összes szegmensének felkapcsolását szemlélteti (a „0” kijelzéséhez mindre szükség van), ezért magas illetve alacsony a szegmenscsoport vezérlőjele mind a négy COM intervallumában. Ha azonban az 5-ös szegmenscsoportot vizsgáljuk (ahol „0” középső szegmensét nem szabad bekapcsolni, különben „8” lesz belőle), akkor ott a COM1 időablakában a szegmenscsoport vezérlőjele a bekapcsolási feszültségtől eltérő szinten lesz. A következő ábrán az 5-ös szegmenscsoport négyszögjele és a COM1 kivezetés vezérlőjele látható:


Tulajdonképpen a szegmens kikapcsolásához elegendő a szegmens és a COM feszültségét azonos szintre emelni, itt azonban a kikapcsolási érték 1/3-dal eltér a középfeszültség irányába. Ez azért fontos, mert egy maximum vagy minimum COM feszültség, ami pillanatnyilag kikapcsol egy szegmenset, az bekapcsolhat egy másikat egy másik csoportból, hiszen a szegmensek csoportosan vannak összekötve – vagyis arra még külön figyelni kéne, hogy a nem kívánt szegmensek feszültségei a COM szintjén legyenek. Egyszerűbb tehát olyan feszültségszint alkalmazása, ami biztosan nem polarizálja a folyadékkristályt. Ez lehet 1/4, 1/3 vagy akár 1/2 is. Minél több lépcsőből áll a vezérlés, annál összetettebb meghajtást  igényel, hiszen analóg feszültségszintekről van szó.
 

Saját vezérlő


      Ebben a kísérletben 1/2 vezérlőfeszültséget használok a szegmensek kikapcsolásához. Marad a négyszeres időmultiplexelt vezérlés, azaz a félperiódus 1/4 idejéig tartanak a COM jelek, melyeket a mikrovezérlő általános I/O lábainak vezérlésével kapunk. Az 1/2 feszültségszintet a COM kivezetésre kapcsolt feszültségosztó ellenállásokkal lehet elérni.


A mikrovezérlő portjait a következőképp lehet definiálni:

#define COM0_GND DDRD |=  (1<<PD0); PORTD &= ~(1<<PD0); //COM0 - GND
#define COM0_1P2 DDRD &= ~(1<<PD0); PORTD &= ~(1<<PD0); //COM0 - 1/2 VCC
#define COM0_VCC DDRD |=  (1<<PD0); PORTD |=  (1<<PD0); //COM0 - VCC
#define COM1_GND DDRD |=  (1<<PD1); PORTD &= ~(1<<PD1);
#define COM1_1P2 DDRD &= ~(1<<PD1); PORTD &= ~(1<<PD1);
#define COM1_VCC DDRD |=  (1<<PD1); PORTD |=  (1<<PD1);
#define COM2_GND DDRD |=  (1<<PD2); PORTD &= ~(1<<PD2);
#define COM2_1P2 DDRD &= ~(1<<PD2); PORTD &= ~(1<<PD2);
#define COM2_VCC DDRD |=  (1<<PD2); PORTD |=  (1<<PD2);
#define COM3_GND DDRD |=  (1<<PD3); PORTD &= ~(1<<PD3);
#define COM3_1P2 DDRD &= ~(1<<PD3); PORTD &= ~(1<<PD3);
#define COM3_VCC DDRD |=  (1<<PD3); PORTD |=  (1<<PD3);
#define delay 2

Ezek után már csak a megfelelő sorrendben kell őket alkalmazni:

void LCD_print(uint8_t m1, uint8_t m2, uint8_t m3, uint8_t m4)
{
      //pozitiv felperiodus
      COM3_1P2; COM0_GND;
      //ToDo: COM0-hoz tartozo szegmensek BE
      _delay_ms(delay);
      COM0_1P2; COM1_GND;
      //ToDo: COM1-hoz tartozo szegmensek BE
      _delay_ms(delay);
      COM1_1P2; COM2_GND;
      //ToDo: COM2-hoz tartozo szegmensek BE
      _delay_ms(delay);
      COM2_1P2; COM3_GND;
      //ToDo: COM3-hoz tartozo szegmensek BE
      _delay_ms(delay);
     
      //negativ felperiodus
      COM3_1P2; COM0_VCC;
      //ToDo: COM0-hoz tartozo szegmensek KI
      _delay_ms(delay);
      COM0_1P2; COM1_VCC;
      //ToDo: COM1-hoz tartozo szegmensek KI
      _delay_ms(delay);
      COM1_1P2; COM2_VCC;
      //ToDo: COM2-hoz tartozo szegmensek KI
      _delay_ms(delay);
      COM2_1P2; COM3_VCC;
      //ToDo: COM3-hoz tartozo szegmensek KI
      _delay_ms(delay);
}

Egymás után hívogatva ezeket az utasításokat, a következő jelalak mérhető két egymás utáni COM lábon:


A szegmenscsoportok ki és be kapcsolgatása a VCC és GND jelszintekkel történik, szintén az I/O lábak segítségével. Ezeket közvetlenül az LCD kivezetéseihez lehet kapcsolni. Nincs szükség középfeszültségre, ugyanis 3.3V esetén, a bekapcsolt szegmens kialszik, amint a COM0 középfeszültségre áll (1.65V-ra esik a potenciálkülönbség).
A kijelezni szánt számjegyekhez tartozó szegmenseket a kivezetések beazonosításnál készített rajzról lehet leolvasni, például az „1” számjegy esetén csupán a COM1 és COM2 jeleknél kell a 2. szegmenscsoportot bekapcsolni. A következő ábra a 2. szegmenscsoport és a COM1 jelalakokat mutatja:


Minden időablak a 0-1-1-0, majd 1-0-0-1 sorozatokból áll, miközben COM0-COM1-COM2-COM3 vezérlőjelek szerre kapcsolnak be és ki. Mivel egy számjegyet az LCD két szegmenscsoporttal jelez, alkalmazható egy kétdimenziós tömb a szegmensjelek tárolására, ami mindig az éppen kijelzett számjegy adatait tartalmazza:

int Segments[4][4] = {{0,0,0,0},{0,0,0,0}};
 
void setSegments (int s00, int s01, int s02, int s03, int s10, int s11, int s12, int s13)
{   
  Segments[0][0] = s00; Segments[0][1] = s01; Segments[0][2] = s02; Segments[0][3] = s03;
  Segments[1][0] = s10; Segments[1][1] = s11; Segments[1][2] = s12; Segments[1][3] = s13; 
}
 
void SEGs0() { setSegments(1,0,1,1, 1,1,1,0); } //A "0" szegmensei COM0..3 fuggvenyeben
void SEGs1() { setSegments(0,0,0,0, 0,1,1,0); }
void SEGs2() { setSegments(0,1,1,1, 1,1,0,0); }
void SEGs3() { setSegments(0,1,0,1, 1,1,1,0); }
void SEGs4() { setSegments(1,1,0,0, 0,1,1,0); }
void SEGs5() { setSegments(1,1,0,1, 1,0,1,0); }
void SEGs6() { setSegments(1,1,1,1, 1,0,1,0); }
void SEGs7() { setSegments(0,0,0,0, 1,1,1,0); }
void SEGs8() { setSegments(1,1,1,1, 1,1,1,0); }
void SEGs9() { setSegments(1,1,0,1, 1,1,1,0); }

A fenti adatokkal az LCD_print(2,1,4,8); függvény végtelen ciklusban a következőt jeleníti meg a kijelzőn:


Ezek az adatok bármelyik számjegy-szegmens meghajtására alkalmazhatók, azonban most nincs szükség mindre, csupán a zölddel jelöltekre:


A középső nagy szegmensek a mért értéket jelzik A-ban 1-től 16-ig 0,01 lépésekkel, az alsó szegmensek pedig a beállított határértéket. Az OVERLOAD felirat akkor kapcsol be, amikor a határértéket elérte a fogyasztó és lekapcsol a relé. A négy COM kivezetésen kívül szükség lesz tehát az 1, 2, 3, 4, 5, 6, 7, 8, 19, 20, 21, 22, 23, 24, 25, 26 szegmenscsoportokra is, tehát összesen 20 I/O lábra lesz szüksége a szegmenseknek. Ezen kívül még lesz két gomb a határérték beállítására, egy RESET, valamint kell egy ADC bemenet a söntellenálláson eső feszültség mérésre is. Erre megfelel az ATmega324PA.
 

A teljesítménykorlát beállítása


      A fogyasztásmérő „UP” és „DOWN” jelzésű gombjai felhasználhatók a teljesítménykorlát beállítására. Ez az érték az alsó szegmenseken jelenik meg. Ha a mért érték meghaladja az itt jelzett határértéket, akkor az OVERLOAD felirat bekapcsol.
A mért és beállított érték is egész szám 1 és 1600 között. A beállítás egyszerűsítésére a nyomógombokon alkalmazható egy időzítő, ami méri a gomb nyomva tartott idejét és felgyorsítja a számolást. A mikrovezérlő gomboknak szánt bemenetei alapból fel vannak húzva a tápfeszültségre. A gomb lenyomásakor a földre záródnak. A gombok egyszerre történő megnyomását egy logikai változó (button_action) segítségével figyelmen kívül lehet hagyni.

if (UP_pressed() && !button_action)
{
      button_action=1;
      if (set_value<1600) {set_value++;}
      start_timer0(); 
}
           
if (DOWN_pressed() && !button_action)
{
      button_action=1;
      if (set_value>0) {set_value--;}
      start_timer0(); 
}
 
if (RESET_pressed() && !button_action && meas_value < set_value)
{
      button_action=1;
      overload=0;
      RELAY_ON;
      _delay_ms(100); //A rele bekapcsolasa utani varakozas az elso meres elott
}
 
if (!DOWN_pressed() && !UP_pressed() && button_action)
{
      button_action=0;
      overflows=0;
      stop_timer0();
}

Ha a gomb lenyomva marad, akkor a timer0 számolni kezd, többször is telítődik és újraindul. Ezeket az overflows változó számolja a megszakítás függvényben, ami minden telítődésnél lefut.

ISR (TIMER0_OVF_vect) // timer0 overflow interrupt
{    
      if (UP_pressed())
      {
            if (set_value<1600 && overflows == 0) {overflows++; return;}
            if (set_value<=1599 && overflows >= 120) {set_value+=1; overflows++; return;}
      }
     
      if (DOWN_pressed())
      {
            if (set_value>0 && overflows == 0) {overflows++; return;}
            if (set_value>=1 && overflows >= 120) {set_value-=1; overflows++; return;}
      }
     
      overflows++;
}

Az első 120 megszakításig nem történik semmi, hogy legyen idő felemelni az ujjat a gombról, ha csak 1 egészet szeretnénk növelni vagy csökkenteni. Ha 120 megszakításnál tovább van nyomva a gomb, akkor a számlálás nagy sebességgel elindul 1-es lépéssel. 

A feladatok ütemezése


      Három alapvető feladata van a processzornak: mintavételezés, kijelző kapcsolgatása, gombok ellenőrzése. Az egyetlen processzormaggal ellátott mikrovezérlő nem képes ezeket párhuzamosan végrehajtani, csakis egymás után:
 
while(1)
{
      ADC_conversion();
      LCD_Update();
      Cehck_buttons();
}

Ennek egyik hátránya, hogy a mintavételezés alatt a kijelző meghajtása szünetel, tehát villoghat a kép. A másik hátrány, hogy míg a kijelző üzemel, addig nincs mintavételezés, vagyis nem ellenőrizhető a túláram. A gombok állapota pedig figyelmen kívül van hagyva, míg a kijelzés és a mintavételezés történik. Ha mindez elég gyorsan történik, akkor emberi érzékszervekkel nem észlelhető a késés, vagyis a kijelző nem villog, a kijelzett érték látszólag valós idejű és a relé is rögtön kiold túláram esetén. A feladatok gyorsasága nem is annyira a képernyőnél, hanem a relé lekapcsolásánál kritikus. A relének egymagában is szüksége van 15ms-ra, míg a földre húzott vezérlőjel lemágnesezi a tekercs vasmagját és kiold a mechanikus szerkezet. Ehhez hozzá kell adni a mintavételezési időt és azokat az óraciklusokat, amik során a program feszültséggé alakítja, négyzetes középértéket számol, áramerősséget számol,  összehasonlítja a vett mintát a határértékkel és lekapcsolja a relét irányító I/O lábat. A következő ábrán a relé vezérlőjele (piros) és a söntön mért feszültségesés (sárga) látható 3000W-os terhelés esetén. A túláram kezdetétől a fogyasztó lekapcsolásáig 136ms idő telik el.


Hacsak nem használunk három külön mikrovezérlőt mindenik feladatra, akkor ezt az időt csak ütemezéssel lehet csökkenteni. Erre képes a mikrovezérlőknek készült FreeRTOS nyílt forrású valós idejű operációs rendszer, melynek van egy kernelje és egy rakás könyvtára, köztük a feladatok ütemezésére szolgáló könyvtár. Ez a mikrokontrollerekben fellelhető megszakítási rutinokat (ISR) használja, melyek bármely feladatot félbe szakíthatnak valamilyen más kód végrehajtásáért. A FreeRTOS időréseket foglal minden feladatnak, ami alatt a feladat futhat. Ha a feladat nem ért véget a neki szánt időrésben, akkor folytatódik a következőben. Az ütemező dolga, hogy meghatározza, melyik feladat fusson a következő időrésben. A nagyobb prioritású feladatok elsőbbséget élveznek, ha viszont egyforma prioritásúak, akkor azonos frekvenciával fut mindenik a maga idejében.

int main(void)
{
      init();
      vSemaphoreCreateBinary(binaris);
      xTaskCreate(ADC_conversion,(signed char*)"ADC",80,NULL,1,NULL);
      xTaskCreate(LCD_Update,(signed char*)"LCD",80,NULL,1,NULL);
      xTaskCreate(Cehck_buttons,(signed char*)"Button",80,NULL,1,NULL);
      vTaskStartScheduler();
      return 0;
}

A feladatok egy-egy végtelenciklust tartalmaznak, ezért a főprogramot nem kell végtelen ciklusba tenni. A feladatoknak van néhány közös változójuk, mint például a mért érték, amit az ADC állít elő és az LCD ír ki. Ha mindkét függvény egyszerre próbálja ezt használni (pl. az ADC írja, miközben az LCD olvassa), akkor hibás adatok születnek. Ez elkerülhető szemaforok alkalmazásával, melyek a mutex-hez hasonlóan megakadályozzák ez erőforrások elérését más feladatok számára, míg egy feladat be nem fejeződött. Jelen esetben elegendő a bináris szemafor (mely csak a piros vagy zöld jelzést ismeri), mert nincs bufferben tárolt adat, aminek készenlétét külön jelezni kellene.

xSemaphoreHandle binaris;

void ADC_conversion()
{
      while(1)
      {
            xSemaphoreTake(binaris, (portTickType) 1 );
            //kód
            xSemaphoreGive(binaris);
      }
}
 
void LCD_Update()
{
      while(1)
      {
            xSemaphoreTake(binaris, (portTickType) 1 );
            //kód
            xSemaphoreGive(binaris);
      }
} 

void Cehck_buttons()
{
      while(1)
      {
            xSemaphoreTake(binaris, (portTickType) 1 );
            //kód
            xSemaphoreGive(binaris);
      }
}
 
Az így megírt program reakció ideje sokat javult: 136ms-ról 17ms-re, amiből 15ms a relé kikapcsolási ideje.



A teljes kapcsolás


 A kapcsolás a lehető legegyszerűbb, mert minden el kell férjen a kijelző alatt. A kijelző nem használt kivezetéseit egy feszültségosztóval a középfeszöltségre kapcsoltam, így semmiképp sem kapcsolhatnak be.

A legnagyobb helyet a CT, a relé és a fogyasztóval sorba kapcsolt 16A-es biztosítékon kívül a tápegység foglalja. Mivel a relé 5V/100mA, ezért egy telefontöltő 5V-os tápegységére került a választás, ahol a mikrovezérlőnek szánt 3.3V feszültséget egy LT1086-os pozitív feszültségstabilizátor állítja elő. A fogyasztásmérő eredeti panelje kisebb volt ugyan, de az ott használt 3.3V és 5V ág nem volt elég erős a relé és a mikrovezérlő számára. Pár fénykép a saját készítésű áramkörről:


A teljes forráskód

 
A forráskód nincs letisztítva, sok mindent lehetne még finomítani, másképp írni vagy rendezni, de a reakcióidőn vagy a mérési pontosságon nem valószínű, hogy lehet javítani. Az LCD_Update() függvényben bekerült egy középértékszámítás, ami az azonnali kijelzés helyett összegez 16 darab áramerősség értéket, és ennek átlagát jelzi ki. Ha minden mérést azonnal kijelezne, akkor nagyon gyorsan ugrálna az utolsó tizedes és nem lehetne kivenni, hogy pontosan mennyi. Emiatt úgy tűnik, hogy a műszer lassabban reagál az áramváltozásra, de valójában a relé továbbra is a pillanatnyi érték alapján kapcsol. A freeFRTOS állományait külön be kell állítani a makefile-ban, valamint a mikrovezérlőhöz tartozó port.c forrásfájlt is módosítani kell a kívánt timer regiszterek alkalmazásához, továbbá a freeRTOSConfig.h-ban definiálni kell az órajelet, a feladatoknak szánt óraciklusok számát, illetve az alkalmazott feladatfüggvényeket.

#include <avr/io.h>
#define F_CPU 16000000UL //16MHz - a delay fuggvenyeknek
#include <util/delay.h>
#include <avr/interrupt.h>
#include "FreeRTOS.h"
#include "task.h"
#include "semphr.h"
#include <stddef.h> //NULL
 
#define COM0_GND DDRD |=  (1<<PD0); PORTD &= ~(1<<PD0); //COM0 - GND
#define COM0_1P2 DDRD &= ~(1<<PD0); PORTD &= ~(1<<PD0); //COM0 - 1/2 VCC
#define COM0_VCC DDRD |=  (1<<PD0); PORTD |=  (1<<PD0); //COM0 - VCC
#define COM1_GND DDRD |=  (1<<PD1); PORTD &= ~(1<<PD1);
#define COM1_1P2 DDRD &= ~(1<<PD1); PORTD &= ~(1<<PD1);
#define COM1_VCC DDRD |=  (1<<PD1); PORTD |=  (1<<PD1);
#define COM2_GND DDRD |=  (1<<PD2); PORTD &= ~(1<<PD2);
#define COM2_1P2 DDRD &= ~(1<<PD2); PORTD &= ~(1<<PD2);
#define COM2_VCC DDRD |=  (1<<PD2); PORTD |=  (1<<PD2);
#define COM3_GND DDRD |=  (1<<PD3); PORTD &= ~(1<<PD3);
#define COM3_1P2 DDRD &= ~(1<<PD3); PORTD &= ~(1<<PD3);
#define COM3_VCC DDRD |=  (1<<PD3); PORTD |=  (1<<PD3);
 
#define SEG26_ON  PORTD |= (1<<PD4); //1. kis szegmenscsoport VCC
#define SEG25_ON  PORTD |= (1<<PD5); 
#define SEG24_ON  PORTD |= (1<<PD6);
#define SEG23_ON  PORTD |= (1<<PD7); 
#define SEG22_ON  PORTC |= (1<<PC0);
#define SEG21_ON  PORTC |= (1<<PC1);
#define SEG20_ON  PORTC |= (1<<PC2);
#define SEG19_ON  PORTC |= (1<<PC3);
 
#define SEG26_OFF PORTD &= ~(1<<PD4); //1. kis szegmenscsoport GND
#define SEG25_OFF PORTD &= ~(1<<PD5);
#define SEG24_OFF PORTD &= ~(1<<PD6);
#define SEG23_OFF PORTD &= ~(1<<PD7);
#define SEG22_OFF PORTC &= ~(1<<PC0);
#define SEG21_OFF PORTC &= ~(1<<PC1);
#define SEG20_OFF PORTC &= ~(1<<PC2);
#define SEG19_OFF PORTC &= ~(1<<PC3);
 
#define SEG1_ON  PORTA |= (1<<PA4); //26. nagy szegmenscsoport VCC
#define SEG2_ON  PORTA |= (1<<PA5); 
#define SEG3_ON  PORTA |= (1<<PA6);
#define SEG4_ON  PORTA |= (1<<PA7); 
#define SEG5_ON  PORTC |= (1<<PC7);
#define SEG6_ON  PORTC |= (1<<PC6);
#define SEG7_ON  PORTC |= (1<<PC5);
#define SEG8_ON  PORTC |= (1<<PC4);
 
#define SEG1_OFF PORTA &= ~(1<<PA4); //26. nagy szegmenscsoport GND
#define SEG2_OFF PORTA &= ~(1<<PA5); 
#define SEG3_OFF PORTA &= ~(1<<PA6);
#define SEG4_OFF PORTA &= ~(1<<PA7); 
#define SEG5_OFF PORTC &= ~(1<<PC7);
#define SEG6_OFF PORTC &= ~(1<<PC6);
#define SEG7_OFF PORTC &= ~(1<<PC5);
#define SEG8_OFF PORTC &= ~(1<<PC4);
 
#define RELAY_ON  PORTB |= (1<<PB0);
#define RELAY_OFF PORTB &= ~(1<<PB0);
 
#define DOWN PA1
#define UP PA2
#define RESET PA3
 
#define N 150 //mintak szama
#define Shunt 206 //sont ellenallas erteke (ohm)
#define VCC 3.3  //tapfeszultseg (V)
#define CT_ratio 1000 //CT szekunder menetszam
#define delay 2
 
uint8_t Segments[4][4] = {{0,0,0,0},{0,0,0,0}};
 
void setSegments (uint8_t s00, uint8_t s01, uint8_t s02, uint8_t s03, uint8_t s10, uint8_t s11, uint8_t s12, uint8_t s13)
{   
      Segments[0][0] = s00; Segments[0][1] = s01; Segments[0][2] = s02; Segments[0][3] = s03;
    Segments[1][0] = s10; Segments[1][1] = s11; Segments[1][2] = s12; Segments[1][3] = s13;
     
}
 
void SEGs0() { setSegments(1,0,1,1, 1,1,1,0); } //A "0" szegmensei COM0..3 fuggvenyeben
void SEGs1() { setSegments(0,0,0,0, 0,1,1,0); }
void SEGs2() { setSegments(0,1,1,1, 1,1,0,0); }
void SEGs3() { setSegments(0,1,0,1, 1,1,1,0); }
void SEGs4() { setSegments(1,1,0,0, 0,1,1,0); }
void SEGs5() { setSegments(1,1,0,1, 1,0,1,0); }
void SEGs6() { setSegments(1,1,1,1, 1,0,1,0); }
void SEGs7() { setSegments(0,0,0,0, 1,1,1,0); }
void SEGs8() { setSegments(1,1,1,1, 1,1,1,0); }
void SEGs9() { setSegments(1,1,0,1, 1,1,1,0); }
void OVERLOAD() { setSegments(0,0,0,0, 0,0,0,1); }
void decimal() { setSegments(0,0,0,0, 0,0,0,1); }
 
uint16_t set_value=100;//kezdeti aramkorlat
uint16_t overflows=0;  //idozito lejarasanak szama, a hosszan nyomvatartott UP es DOWN gombokbak
uint16_t display=0;    //idozito lejarasanak szama, a kijelzo frissitesi rataja
uint16_t meas_value=0, adc_value, meas_avg=0;
uint8_t converted;
float avg_voltage = 0;
float rms_voltage = 0;
float rms_current = 0;
float adc_voltage=0;
uint8_t button_action = 0, overload=0, i=0; //flag, hogy egyszerre csak egy gomb mukodjon
uint8_t (*(LoadSEGs[10]))() = {SEGs0, SEGs1, SEGs2, SEGs3, SEGs4, SEGs5, SEGs6, SEGs7, SEGs8, SEGs9}; //index = fuggvenynev
xSemaphoreHandle binaris;
uint16_t set1, set2, set3, set4, meas1, meas2, meas3, meas4;
 
void LCD_print(uint8_t m1, uint8_t m2, uint8_t m3, uint8_t m4, uint8_t c1, uint8_t c2, uint8_t c3, uint8_t c4, uint8_t overload); //m - measured, c - configured
 
uint8_t UP_pressed()
{
      if (!(PINA & (1<<UP))) //ha le van nyomva
      {
            return 1;
      }
      return 0;
}
 
uint8_t DOWN_pressed()
{
      if (!(PINA & (1<<DOWN))) //ha le van nyomva
      {
            return 1;
      }
      return 0;
}
 
uint8_t RESET_pressed()
{
      if (!(PINA & (1<<RESET))) //ha le van nyomva
      {
            return 1;
      }
      return 0;
}
 
void start_timer0()
{
      TCCR0B |= (1<< CS02); //CLK/256, start
      TIMSK0= (1<<TOIE0);  //enable overflow interrupt
      sei(); //set the I-bit in SREG
}
 
void stop_timer0()
{
      TCCR0B &= ~(1<< CS02); //nincs orajel => az idozito megall
}
 
void initPORTs()
{
      DDRA |= (1<<PA4) | (1<<PA5) | (1<<PA6) | (1<<PA7); //kimenet szegmenscsoportoknak
      DDRA &= ~((1<<UP) | (1<<DOWN) | (1<<RESET));//bemenet a gomboknak
      PORTA |= (1<<UP)|(1<<DOWN)|(1<<RESET); //felhuzo ellenallasok a gomboknak
      DDRB |= (1<<PB0); //kimenet a relenek
      DDRC = 0xff; //kimenet szegmenscsoportoknak
      DDRD |= (1<<PD4) | (1<<PD5) | (1<<PD6) | (1<<PD7);  //kimenet szegmens-csoportoknak
}
 
void initADC()
{
      ADCSRA |= (1 << ADPS2) | (1 << ADPS0); //16MHz/32=500kHz)
      ADMUX |= (1 << REFS0); //ADC referencia = AVCC
      //Mux4..0=0 - ADC0 csatorna
      ADCSRA |= (1<<ADIE); //Enable ADC interrupt
      ADCSRA |= (1<<ADATE); //Enable auto-triggering
      ADCSRA |= (1<<ADEN); //Enable ADC
      sei();
      ADCSRA |= (1<<ADSC); //start first ADC conversion
      converted = 1;
}
 
void adc_read()
{
      ADCSRA |= (1<<ADSC); //start ADC
      ADCSRA |= (1<<ADIE); //Enable ADC interrupt
}
 
void ADC_conversion()
{
      while(1)
      {
            xSemaphoreTake(binaris, (portTickType) 1 );
            if(i<N && converted) //N darab minta osszegzese
            {
                  adc_read(); converted=0;                     //1 ADC minta
                  adc_voltage = (adc_value * VCC)/1024; //minta feszultsegge alakitasa
                  avg_voltage += pow(adc_voltage,2);    //feszultsegek negyzetosszege
                  i++;
            }
            if (i==N)
            {
                  i=0;
                  avg_voltage = avg_voltage/N;        //kozepertek
                  rms_voltage = sqrt(avg_voltage);    //negyzetes kozep
                  rms_current = rms_voltage / Shunt;  //I=U/R
                  rms_current = rms_current*CT_ratio; //mA -> A
                  meas_value = rms_current*100;       //kijelzohoz valo igazitas
            }
            if(meas_value > set_value) {overload=1; RELAY_OFF;}
            xSemaphoreGive(binaris);
      }
}
 
void LCD_Update()
{
      while(1)
      {
            xSemaphoreTake(binaris, (portTickType) 1 );
            if(meas_value > set_value) {overload=1; RELAY_OFF;}
           
            //a beallitott ertek szamjegyekre bontasa
            set4 = (set_value / 1) % 10;
            set3 = (set_value / 10) % 10;
            set2 = (set_value / 100) % 10;
            set1 = (set_value / 1000) % 10;
           
            //szamtani kozepertek
            if(display<16){meas_avg+=meas_value;}
            if (display==15)
            {    
                  meas_avg=meas_avg>>4; //=osztva 16-tal
                  if(meas_avg > set_value) {overload=1; RELAY_OFF;}
                  //szamjegyekre bontas
                  meas4 = (meas_avg / 1) % 10;
                  meas3 = (meas_avg / 10) % 10;
                  meas2 = (meas_avg / 100) % 10;
                  meas1 = (meas_avg / 1000) % 10;
                  display=0;
            } else {display++;}
           
            LCD_print(meas1,meas2,meas3,meas4, set1,set2,set3,set4, overload);
            xSemaphoreGive(binaris);
      }
}
void Cehck_buttons()
{
      while(1)
      {
            xSemaphoreTake(binaris, (portTickType) 1 );
            if(meas_value > set_value) {overload=1; RELAY_OFF;}
           
            if (UP_pressed() && !button_action)
            {
                  button_action=1;
                  if (set_value<1600) {set_value++;}
                  start_timer0(); 
            }
                       
            if (DOWN_pressed() && !button_action)
            {
                  button_action=1;
                  if (set_value>0) {set_value--;}
                  start_timer0(); 
            }
           
            if (RESET_pressed() && !button_action && meas_value < set_value)
            {
                  button_action=1;
                  overload=0;
                  RELAY_ON;
                  _delay_ms(100); // bekapcsolas utani varakozas az elso meres elott
            }
           
            if (!DOWN_pressed() && !UP_pressed() && button_action)
            {
                  button_action=0;
                  overflows=0;
                  stop_timer0();
            }    
           
            xSemaphoreGive(binaris);
      }
}
 
int main(void)
{
      initPORTs();
      initADC();
      vSemaphoreCreateBinary(binaris);
      RELAY_ON;
      _delay_ms(100); //bekapcsolas utani varakozas az elso meres elott
      xTaskCreate(ADC_conversion,(signed char*)"ADC",80,NULL,1,NULL);
      xTaskCreate(LCD_Update,(signed char*)"LCD",80,NULL,1,NULL);
      xTaskCreate(Cehck_buttons,(signed char*)"Button",80,NULL,1,NULL);
      vTaskStartScheduler();
      return 0;
 
}
 
ISR (TIMER0_OVF_vect) // timer0 overflow interrupt
{    
      if (UP_pressed())
      {
            if (set_value<1600 && overflows == 0) {overflows++; return;}
            if (set_value<=1599 && overflows >= 120) {set_value+=1; overflows++; return;}
      }
     
      if (DOWN_pressed())
      {
            if (set_value>0 && overflows == 0) {overflows++; return;}
            if (set_value>=1 && overflows >= 120) {set_value-=1; overflows++; return;}
      }
     
      overflows++;
}
 
ISR(ADC_vect)
{
  adc_value = ADC;
  converted = 1;
}
 
//A COM-ok egymas utan kapcsolnak be es ki. A koztes idoben 1/2 feszultsegen pihennek.
//Mindenik COM allapotnal bekapcsolnak a szamjegyhez tartozo szegmenscsoportok
//A ket felperiodus egymas ellentete
void LCD_print(uint8_t m1, uint8_t m2, uint8_t m3, uint8_t m4, uint8_t c1, uint8_t c2, uint8_t c3, uint8_t c4, uint8_t overload)
{    
      //pozitiv felperiodus
      COM3_1P2; COM0_GND; //COM0-hoz tartozo szegmensek: a Segments tomb 0. oszlopa
      LoadSEGs[m1](); if (Segments[0][0]) {SEG1_ON} else {SEG1_OFF} if (Segments[1][0]) {SEG2_ON} else {SEG2_OFF}
      LoadSEGs[m2](); if (Segments[0][0]) {SEG3_ON} else {SEG3_OFF} if (Segments[1][0]) {SEG4_ON} else {SEG4_OFF}
      LoadSEGs[m3](); if (Segments[0][0]) {SEG5_ON} else {SEG5_OFF} if (Segments[1][0]) {SEG6_ON} else {SEG6_OFF}
      LoadSEGs[m4](); if (Segments[0][0]) {SEG7_ON} else {SEG7_OFF} if (Segments[1][0]) {SEG8_ON} else {SEG8_OFF}
      LoadSEGs[c1](); if (Segments[0][0]) {SEG26_ON} else {SEG26_OFF} if (Segments[1][0]) {SEG25_ON} else {SEG25_OFF}
      LoadSEGs[c2](); if (Segments[0][0]) {SEG24_ON} else {SEG24_OFF} if (Segments[1][0]) {SEG23_ON} else {SEG23_OFF}
      LoadSEGs[c3](); if (Segments[0][0]) {SEG22_ON} else {SEG22_OFF} if (Segments[1][0]) {SEG21_ON} else {SEG21_OFF}
      LoadSEGs[c4](); if (Segments[0][0]) {SEG20_ON} else {SEG20_OFF} if (Segments[1][0]) {SEG19_ON} else {SEG19_OFF}
      _delay_ms(delay);
     
      COM0_1P2; COM1_GND;
      LoadSEGs[m1](); if (Segments[0][1]) {SEG1_ON} else {SEG1_OFF} if (Segments[1][1]) {SEG2_ON} else {SEG2_OFF}
      LoadSEGs[m2](); if (Segments[0][1]) {SEG3_ON} else {SEG3_OFF} if (Segments[1][1]) {SEG4_ON} else {SEG4_OFF}
      LoadSEGs[m3](); if (Segments[0][1]) {SEG5_ON} else {SEG5_OFF} if (Segments[1][1]) {SEG6_ON} else {SEG6_OFF}
      LoadSEGs[m4](); if (Segments[0][1]) {SEG7_ON} else {SEG7_OFF} if (Segments[1][1]) {SEG8_ON} else {SEG8_OFF}
      LoadSEGs[c1](); if (Segments[0][1]) {SEG26_ON} else {SEG26_OFF} if (Segments[1][1]) {SEG25_ON} else {SEG25_OFF}
      LoadSEGs[c2](); if (Segments[0][1]) {SEG24_ON} else {SEG24_OFF} if (Segments[1][1]) {SEG23_ON} else {SEG23_OFF}
      LoadSEGs[c3](); if (Segments[0][1]) {SEG22_ON} else {SEG22_OFF} if (Segments[1][1]) {SEG21_ON} else {SEG21_OFF}
      LoadSEGs[c4](); if (Segments[0][1]) {SEG20_ON} else {SEG20_OFF} if (Segments[1][1]) {SEG19_ON} else {SEG19_OFF}
      _delay_ms(delay);
     
      COM1_1P2; COM2_GND;
      LoadSEGs[m1](); if (Segments[0][2]) {SEG1_ON} else {SEG1_OFF} if (Segments[1][2]) {SEG2_ON} else {SEG2_OFF}
      LoadSEGs[m2](); if (Segments[0][2]) {SEG3_ON} else {SEG3_OFF} if (Segments[1][2]) {SEG4_ON} else {SEG4_OFF} 
      LoadSEGs[m3](); if (Segments[0][2]) {SEG5_ON} else {SEG5_OFF} if (Segments[1][2]) {SEG6_ON} else {SEG6_OFF}
      LoadSEGs[m4](); if (Segments[0][2]) {SEG7_ON} else {SEG7_OFF} if (Segments[1][2]) {SEG8_ON} else {SEG8_OFF}
      LoadSEGs[c1](); if (Segments[0][2]) {SEG26_ON} else {SEG26_OFF} if (Segments[1][2]) {SEG25_ON} else {SEG25_OFF}
      LoadSEGs[c2](); if (Segments[0][2]) {SEG24_ON} else {SEG24_OFF} if (Segments[1][2]) {SEG23_ON} else {SEG23_OFF}
      LoadSEGs[c3](); if (Segments[0][2]) {SEG22_ON} else {SEG22_OFF} if (Segments[1][2]) {SEG21_ON} else {SEG21_OFF}
      LoadSEGs[c4](); if (Segments[0][2]) {SEG20_ON} else {SEG20_OFF} if (Segments[1][2]) {SEG19_ON} else {SEG19_OFF}
      _delay_ms(delay);
     
      COM2_1P2; COM3_GND;
      LoadSEGs[m1](); if (Segments[0][3]) {SEG1_ON} else {SEG1_OFF} if (Segments[1][3]) {SEG2_ON} else {SEG2_OFF}
      LoadSEGs[m2](); if (Segments[0][3]) {SEG3_ON} else {SEG3_OFF} if (Segments[1][3]) {SEG4_ON} else {SEG4_OFF}
      LoadSEGs[m3](); if (Segments[0][3]) {SEG5_ON} else {SEG5_OFF} if (Segments[1][3]) {SEG6_ON} else {SEG6_OFF}
      LoadSEGs[m4](); if (Segments[0][3]) {SEG7_ON} else {SEG7_OFF} if (Segments[1][3]) {SEG8_ON} else {SEG8_OFF}
      LoadSEGs[c1](); if (Segments[0][3]) {SEG26_ON} else {SEG26_OFF} if (Segments[1][3]) {SEG25_ON} else {SEG25_OFF}
      LoadSEGs[c2](); if (Segments[0][3]) {SEG24_ON} else {SEG24_OFF} if (Segments[1][3]) {SEG23_ON} else {SEG23_OFF}
      LoadSEGs[c3](); if (Segments[0][3]) {SEG22_ON} else {SEG22_OFF} if (Segments[1][3]) {SEG21_ON} else {SEG21_OFF}
      LoadSEGs[c4](); if (Segments[0][3]) {SEG20_ON} else {SEG20_OFF} if (Segments[1][3]) {SEG19_ON} else {SEG19_OFF}
      if (overload) {OVERLOAD(); if (Segments[1][3]) {SEG8_ON} else {SEG8_OFF}}
      decimal(); if (Segments[1][3]) {SEG4_ON; SEG23_ON} else {SEG4_OFF; SEG23_OFF}
      _delay_ms(delay);
     
      //negativ felperiodus
      COM3_1P2; COM0_VCC;
      LoadSEGs[m1](); if (Segments[0][0]) {SEG1_OFF} else {SEG1_ON} if (Segments[1][0]) {SEG2_OFF} else {SEG2_ON}
      LoadSEGs[m2](); if (Segments[0][0]) {SEG3_OFF} else {SEG3_ON} if (Segments[1][0]) {SEG4_OFF} else {SEG4_ON}
      LoadSEGs[m3](); if (Segments[0][0]) {SEG5_OFF} else {SEG5_ON} if (Segments[1][0]) {SEG6_OFF} else {SEG6_ON}
      LoadSEGs[m4](); if (Segments[0][0]) {SEG7_OFF} else {SEG7_ON} if (Segments[1][0]) {SEG8_OFF} else {SEG8_ON}
      LoadSEGs[c1](); if (Segments[0][0]) {SEG26_OFF} else {SEG26_ON} if (Segments[1][0]) {SEG25_OFF} else {SEG25_ON}
      LoadSEGs[c2](); if (Segments[0][0]) {SEG24_OFF} else {SEG24_ON} if (Segments[1][0]) {SEG23_OFF} else {SEG23_ON}
      LoadSEGs[c3](); if (Segments[0][0]) {SEG22_OFF} else {SEG22_ON} if (Segments[1][0]) {SEG21_OFF} else {SEG21_ON}
      LoadSEGs[c4](); if (Segments[0][0]) {SEG20_OFF} else {SEG20_ON} if (Segments[1][0]) {SEG19_OFF} else {SEG19_ON}
      _delay_ms(delay);
     
      COM0_1P2; COM1_VCC;
      LoadSEGs[m1](); if (Segments[0][1]) {SEG1_OFF} else {SEG1_ON} if (Segments[1][1]) {SEG2_OFF} else {SEG2_ON}
      LoadSEGs[m2](); if (Segments[0][1]) {SEG3_OFF} else {SEG3_ON} if (Segments[1][1]) {SEG4_OFF} else {SEG4_ON} 
      LoadSEGs[m3](); if (Segments[0][1]) {SEG5_OFF} else {SEG5_ON} if (Segments[1][1]) {SEG6_OFF} else {SEG6_ON} 
      LoadSEGs[m4](); if (Segments[0][1]) {SEG7_OFF} else {SEG7_ON} if (Segments[1][1]) {SEG8_OFF} else {SEG8_ON} 
      LoadSEGs[c1](); if (Segments[0][1]) {SEG26_OFF} else {SEG26_ON} if (Segments[1][1]) {SEG25_OFF} else {SEG25_ON}
      LoadSEGs[c2](); if (Segments[0][1]) {SEG24_OFF} else {SEG24_ON} if (Segments[1][1]) {SEG23_OFF} else {SEG23_ON}
      LoadSEGs[c3](); if (Segments[0][1]) {SEG22_OFF} else {SEG22_ON} if (Segments[1][1]) {SEG21_OFF} else {SEG21_ON}
      LoadSEGs[c4](); if (Segments[0][1]) {SEG20_OFF} else {SEG20_ON} if (Segments[1][1]) {SEG19_OFF} else {SEG19_ON}
      _delay_ms(delay);
     
      COM1_1P2; COM2_VCC;
      LoadSEGs[m1](); if (Segments[0][2]) {SEG1_OFF} else {SEG1_ON} if (Segments[1][2]) {SEG2_OFF} else {SEG2_ON}
      LoadSEGs[m2](); if (Segments[0][2]) {SEG3_OFF} else {SEG3_ON} if (Segments[1][2]) {SEG4_OFF} else {SEG4_ON} 
      LoadSEGs[m3](); if (Segments[0][2]) {SEG5_OFF} else {SEG5_ON} if (Segments[1][2]) {SEG6_OFF} else {SEG6_ON}
      LoadSEGs[m4](); if (Segments[0][2]) {SEG7_OFF} else {SEG7_ON} if (Segments[1][2]) {SEG8_OFF} else {SEG8_ON}
      LoadSEGs[c1](); if (Segments[0][2]) {SEG26_OFF} else {SEG26_ON} if (Segments[1][2]) {SEG25_OFF} else {SEG25_ON}
      LoadSEGs[c2](); if (Segments[0][2]) {SEG24_OFF} else {SEG24_ON} if (Segments[1][2]) {SEG23_OFF} else {SEG23_ON}
      LoadSEGs[c3](); if (Segments[0][2]) {SEG22_OFF} else {SEG22_ON} if (Segments[1][2]) {SEG21_OFF} else {SEG21_ON}
      LoadSEGs[c4](); if (Segments[0][2]) {SEG20_OFF} else {SEG20_ON} if (Segments[1][2]) {SEG19_OFF} else {SEG19_ON}
      _delay_ms(delay);
     
      COM2_1P2; COM3_VCC;
      LoadSEGs[m1](); if (Segments[0][3]) {SEG1_OFF} else {SEG1_ON} if (Segments[1][3]) {SEG2_OFF} else {SEG2_ON}
      LoadSEGs[m2](); if (Segments[0][3]) {SEG3_OFF} else {SEG3_ON} if (Segments[1][3]) {SEG4_OFF} else {SEG4_ON}  
      LoadSEGs[m3](); if (Segments[0][3]) {SEG5_OFF} else {SEG5_ON} if (Segments[1][3]) {SEG6_OFF} else {SEG6_ON}
      LoadSEGs[m4](); if (Segments[0][3]) {SEG7_OFF} else {SEG7_ON} if (Segments[1][3]) {SEG8_OFF} else {SEG8_ON}
      LoadSEGs[c1](); if (Segments[0][3]) {SEG26_OFF} else {SEG26_ON} if (Segments[1][3]) {SEG25_OFF} else {SEG25_ON}
      LoadSEGs[c2](); if (Segments[0][3]) {SEG24_OFF} else {SEG24_ON} if (Segments[1][3]) {SEG23_OFF} else {SEG23_ON}
      LoadSEGs[c3](); if (Segments[0][3]) {SEG22_OFF} else {SEG22_ON} if (Segments[1][3]) {SEG21_OFF} else {SEG21_ON}
      LoadSEGs[c4](); if (Segments[0][3]) {SEG20_OFF} else {SEG20_ON} if (Segments[1][3]) {SEG19_OFF} else {SEG19_ON}
      if (overload) {OVERLOAD(); if (Segments[1][3]) {SEG8_OFF} else {SEG8_ON}}
      decimal(); if (Segments[1][3]) {SEG4_OFF; SEG23_OFF} else {SEG4_ON; SEG23_ON}
      _delay_ms(delay);
}

Az eszköz tesztelése


   Bár az elvárások nem voltak túl magasak a mérési pontosságot illetően, mégis elég pontosan mér egy TrueRMS műszerhez viszonyítva.


A túláram teszteléséhez egy fényerőszabályzót és egy 100W-os izzót használtam: