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ó 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.
Áramszenzor
A söntellenálláson kívül még van két módszer, amivel áramerősséget lehet mérni:
- Á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.
- 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 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
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.
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)
}
adc_read(); converted=0; //1 ADC minta
}
if (i==N)
i=0;
avg_voltage = avg_voltage/N; //kozepertek
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.
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).
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
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ő
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);
}
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:
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
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();
}
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);
}
}
A teljes kapcsolás
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
#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);
}