Problém s přerušením (?) blikání LED časovačem, občas nejde

Zdravím, mám tu něco, s čím si nevím rady. V přerušení od časovače (TOVF2) mám úsek programu, co mi bliká LEDkami. Ten úsek programu funguje naprosto bezchybně. POdívejte se do zdrojáku na “mode no 2”. Tam je test blikání těch “blinkrů”. Tam to bez potíží funguje. Ovšem v “mode no. 6” mi to blikat za žádnou cenu nechce. Nepřišel jsem na to, proč. Nechápu to. Dík za vysvětlení/radu.

Druhý můj dotaz se týká tohoto: Co mám napsat v C, aby mi překladač vložil instrukci “nop” ? Třeba potřebuji pozdržet impulz na pinu jen pár desítek nanosekund, a dávat tam kvůli tomu celý “delay…()” se mi nezdá ani trochu vhodné. Nyní tam mám ty “_delay_us(1)”. Raději bych tam viděl cca 3x NOP instrukci. Ale jak donutit překladač?

Zde je zdroják. Pro zvědavce doplním, že je to rozpracovaný software pro moje první robotické vozítko (ehm, pokus o něj).

[code]//************************************************************
//* ROBOT SWARE
//*
//*
//*
//*
//************************************************************

#include <avr/io.h>

#include <avr/interrupt.h>

#define F_CPU 16000000U
#include <util/delay.h>

//inicializace promennych

char mode; //rezim prace

char light; //aktualni stav svetel
// bit 0 - dalkove
// bit 1, 2 - blinkry
// bit 3 - brzdovky
char blinkr; //bit 0(1) spousti blikani leveho(praveho) bl.
char blincnt; //casovac blinkru (promena softwarove delicky freqence preruseni)

//************************** INTERRUPT **************************

ISR(TIMER2_OVF_vect)
{
//Preruseni 62.5Hz

blincnt++;
if (blincnt == 8) //62.5Hz / 8 = 7.8Hz
{
blincnt = 0; //vynuluj sw delici promennou
light = light ^ (blinkr << 1); //tohle neguje bity podle promenne blinkr
light = light & ((blinkr << 1) | 0b11111001); //zhasnuti blinkru, pokud bylo ukoncene blikani pri rozsvicenem. Proste vymaskovani nulou.
svetla(light);
}

}

//**************************** MAIN ******************************

int main ()
{

//----------- inicializace i/o portu --------------

DDRD = 0xFF;
DDRB = 0b00111111;
DDRC = 0x00;

PORTD = 0x00;
PORTB = 0x00;
PORTC = 0x00;

//----------- inicializace latch registru ------------

light = 0; //zadna svetla nesviti
svetla(light); //odesleme to na latch register
displej(255); //odeslani na jiny latch ktery budi displej

//----------- inicializace timeru, INT, PWM, ---------------

//Nastaveni timeru1:
//Fast PWM mode, prescaler 256, 8bit resolution.
//OCR1A/OCR1B pins active, non-inverting mode
//(0 - zero duty cycle, 255 - max duty cycle)
TCCR1A = (1<<COM1A1)|(1<<COM1B1)|(1<<WGM10);
TCCR1B = (1<<WGM12)|(1<<CS12);
OCR1A = 0;
OCR1B = 0;

//Nastaveni timeru2:
//Prescaler 1024, Fast PWM mode, non-inverting. OVF INT enabled
TCCR2 = (1<<WGM21)|(1<<WGM20)|(1<<COM21)|(1<<CS22)|(1<<CS21)|(1<<CS20);
OCR2 = 22;

blincnt = 0; //vynulovani sw delicky frekvence

//Povoleni preruseni
TIMSK = (1<<TOIE2); //povoleni TCCR2 OVF INT
sei();

//----------------- test veskereho HW -----------------

LED_R_on(); //rozsvitime cervenou LED
LED_Y_on(); //zlutou LED
svetla(0b1111); //Rozsvitime vsechna svetla
displej(0b10000000); //cely displej

_delay_ms(50);

svetla(0); //vsecko pozhasiname
displej(128);
LED_R_off();
LED_Y_off();

//----------------- vyberove menu --------------------

while (1) //zacatek hlavni programove smycky
{
mode = 0; //vychozi mod je c. 0
preved_na_dis(); //aktualni “mode” se zobrazi na displeji, !!nic víc!!!

do {  //smycka vyberu podprogramu

  if ((PINC & 0b00100000) == 0) //jeli stisknuto tlacitko NEXT, zvys mode o 1
  {
    mode++;
    if (mode == 8) mode = 0;
    preved_na_dis(); //mod o 1 vyssi zas zobraz na displej
    _delay_ms(200);
  }      

} while ((PINC & 0b00010000) != 0); //stisknuto OK, delej patricny (pod)program

_delay_ms(200);
switch (mode)  //podle aktualne zvoleneho "mode" delej:
{
  case 0: 							//podprogram 0- Mereni napeti baterie
    
    break;
  case 1: 							//podprogram 1- Sviceni svetel
    light = light ^ 0b00000001; //neguje stav dalkovych svetel
    svetla(light); //posle ven na latch
    break;
  case 2: 							//podprogram 2- Test ostatnich svetel
    blinkr = 0b00000001; //rozblikame levy blinkr
	displej(0b01000111); //na displej zobrazime "L"
	while ((PINC & 0b00010000) != 0) ; //pockame na stisk OK
	blinkr = 0b00000010; //rozblikame pravy blinkr
	displej(0b00001100); //na displej "P"
	_delay_ms(200);
    while ((PINC & 0b00010000) != 0) ; //cekame na OK
	blinkr = 0; //vypneme blinkry
	light |= 0b00001000; //rozsvitime brzdova svetla
	svetla(light); //odesleme na latch ovladajici budice LED
	displej(0b00000011); //na displej zobrazime "b"
	_delay_ms(200);
    while ((PINC & 0b00010000) != 0) ; //cekame na OK
	light &= 0b11110111; //Zhasneme brzdovky
	svetla(light); //odesleme na latch
	_delay_ms(200);	    
    break;
  case 3:							//podprogram 3- rucni ovladani PWM motoru
    while (1)  //slozite na komentovani :-/ Tlacitky je mozne menit otacky pohonneho motru, vcetne reverzace
	{
	  if ((PINC & 0b00100000) == 0) //up
	  {
	    if (OCR1A > 0) OCR1A -= 10;
		  else if (OCR1B < 250) OCR1B += 10;
        test_smeru(); 
		_delay_ms(200);
	  }
	  if ((PINC & 0b00010000) == 0) //down
	  {
		if (OCR1B >= 10) OCR1B -= 10;
		  else if (OCR1A < 250) OCR1A += 10;
		  test_smeru();
		_delay_ms(200);
	  }
	}   
    break;
  case 4:   //podprogram 4  rozjede robota pár metrů vpřed, a pak zas zpět.
  	_delay_ms(1000);
	OCR1B = 64;
	_delay_ms(500);
	OCR1B = 128;
	_delay_ms(500);
	OCR1B = 255;
	_delay_ms(2000);
	OCR1B = 0;

	_delay_ms(2000);
	OCR1A = 64;
	_delay_ms(500);
	OCR1A = 129;
	_delay_ms(500);
	OCR1A = 255;
	_delay_ms(2000);
	OCR1A = 0;           
    break;
 case 5: //podprogram 5 otestuje servo - otoceni vpravo, vlevo, stred
   LED_Y_on();
   OCR2 = 5;
   _delay_ms(2000);
   OCR2 = 30;
   _delay_ms(2000);
   OCR2 = 22; 
   LED_Y_off();      
   break;
 case 6: //podprogram 6 - Jizda do "zakruty S"
   displej(0b01100001); //na displej zobrazime "J"
   _delay_ms(500);
   LED_Y_on();
   light |= 0b00001001; //rozsvitime brzdovky, predni svetla
   svetla(light);

   OCR1B = 64; //rozjedeme vpred
   _delay_ms(1000);
   OCR2 = 16; //servo vlevo
   blinkr = 1; //zapni blikr vlevo
   _delay_ms(1000);
   OCR2 = 22; //servo stred
   blinkr = 0; //vypni blinkry
   OCR1B = 128; //pridej rychlost
   _delay_ms(1000);
   OCR1B = 64; //sniz rychlost
   _delay_ms(500);
   OCR2 = 27; //servo vpravo
   blinkr = 2; //blinkr vpravo
   _delay_ms(1000);
   OCR2 = 22; //servo stred
   blinkr = 0; //vypni blinkr
   OCR1B = 128; //rychle vpred
   _delay_ms(1000);
   OCR1B = 0; //zastavit stat!
   _delay_ms(1000);

   light &= 0b11110110;  //Bliknuti prednimi svetly (zhasnout)
   svetla(light); //na latch
   _delay_ms(200);
   light |= 0b00001001; //rozsvitit
   svetla(light); //na latch
   _delay_ms(200);
   light &= 0b11110110; //zhasnout
   svetla(light); //na latch
   LED_Y_off(); //zhasnout LED	   
   break;

 case 7: // Nedokoncene jezdeni do ctverce, zatim nefunkcni
   displej(0b01100001);
   _delay_ms(500);
   LED_Y_on();
   light |= 0b00001001;
   svetla(light);

   OCR1B = 64;
   _delay_ms(1000);
   OCR2 = 16;
   _delay_ms(1000);
   OCR2 = 22;
   _delay_ms(1000);
   OCR2 = 16;
   _delay_ms(1000);
   OCR2 = 22;
   _delay_ms(1000);
   OCR2 = 16;
   _delay_ms(1000);
   OCR2 = 22;
   _delay_ms(1000);
   OCR2 = 16;
   _delay_ms(1000);
   OCR2 = 22;
   _delay_ms(1000);       

   light &= 0b11110110; //bliknuti prednimi na zaver
   svetla(light);
   _delay_ms(200);
   light |= 0b00001001;
   svetla(light);
   _delay_ms(200);
   light &= 0b11110110;
   svetla(light);
   LED_Y_off(); 
   break;
} //konec switch-e

} //Zpet nazacatek menu

while (1) ; //pro jistotu :slight_smile:
}

// ************************* FCE ****************************
//prace s HW:
void svetla(char d) //odeslani na latch svetel
{
PORTD = d;
PORTB |= (1<< PB0);
_delay_us(1); //taj chci ten NOP!
PORTB &= ~(1<< PB0);
_delay_us(1);
}

void displej(char d) //odeslani na displej
{
PORTD = d;
PORTD |= 0b10000000; //nastavit bit 7
_delay_us(1);
PORTD &= 0b01111111; //vynulovat bit 7
_delay_us(1);
}

void LED_R_on() //rozsvitime LED
{
PORTB |= (1<<PB5);
}

void LED_R_off() //zhasneme LED
{
PORTB &= ~(1<<PB5);
}

void LED_Y_on()//rozsvitime LED
{
PORTB |= (1<<PB4);
}

void LED_Y_off() //zhasneme LED
{
PORTB &= ~(1<<PB4);
}

//Prace s vyberovym menu …

void preved_na_dis(void) // obsah cisla promenne mode prevede na cislo na displej
{
char x;
switch (mode)
{
case 0:
x = 0b01000000; //0
break;
case 1:
x = 0b01111001; //1
break;
case 2:
x = 0b00100100; //2
break;
case 3:
x = 0b00110000; //3
break;
case 4:
x = 0b00011001; //4
break;
case 5:
x = 0b00010010; //5
break;
case 6:
x = 0b00000010; //6
break;
case 7:
x = 0b01111000; //7
break;
}

displej(x); //na latch displeje
}

//Pomocne fce …

void test_smeru() //pomocna funkce podprogramu 3
//Podle toho, jestli se motor toci vpred/vzad/stoji zobrazuje paznaky na displej
{
if ((OCR1A | OCR1B) == 0) displej(0b00111111); // “-”
else if (OCR1A > 0) displej(0b01100011); “u”
else if (OCR1B > 0) displej(0b01011100); “n”
}
[/code]

Všem moc dík za veškerou pomoc!

EDIT: doplneny komentare do zdrojaku, vymyzany davno nefunkcni a nepouzivane FCE.

Cau , k nopu , ja v CCS pouzivam
#asm
nop
#endasm
nevim jestli ti to tvuj prekladac skousne, chce to kouknout do helpu k prekladaci

použít instrukci cycle - jde o obdobu nop

Co je instrukce “cycle”? Takovou slyším prvně.

asm(“nop”);

V “avr-libc-user-manual.pdf” najdeš pár příkladů i nějaký text o kombinaci C s ASM. Je tam spousta uzitečných informací :wink:

Místo těch jednořádkových funkcí by bylo vhodnější využít makra :wink: Překladač sice neni blbej a volání takové funkce provádět nebude, ale neni to moc hezké.
#define SET_LEDA PORTA |= 1<<PA0
Dají se tvořit i víceřádková makra
#define MOTOR_VPRED PORTB |= (1<<PB1);
PORTB &= ~(1<<PB2)
Nezapomeň u víceřádkových maker dávat středníky za jednotlivé příkazy.

Program je dost nepřehledný díky všem těm kouzelným číslům. Když nevíme, kde co je, těžko to rozluštíme.

jestli nemel na mysli neco jako delay_cycles();
castecne ma pravdu , pokud pouzijes delay_cycles(1); tak to skutecne udela NOP , ale pokud pouzijes uz vic jak 2 cykly tak je s toho obycejna cekaci smycka s pouzitim goto a promenych, samo zalezi na typu prekladace jak to prelouska do asm

delay_cycles mylsim znám, a nezkoumal jsme, jak se to přeloží. Umí to fakt když tam dám 1čku napsat jen ten “NOP” ? Respektive umí to vygenerovat takl krátký čekací cosi? Aby to místo třeba 2taktů nevynehcalo 50, protože se to kdoví jak přelouská.

piityy: Upravil jsem zdroják, vymyzal zbytečné kraviny, co jsou tam na prd. Nyní by ot mělo být víc než srozumitelné. Pokud by pomohlo schéma zapojení, můžu ho sem dát.

EDIT - jen ještě jedna věc: Daný problém jsem vyřešil napsáním proměnné light jako “volatile”. Vůbec nevim co to klíčové slovo dělá, jen vim že jsem ho někde četl ve spojitosti s přerušením. TO volatile je co, a proč to čirou náhodou začlo fungovat?
Tuším zradu v tom, že funkci svetla volam z hlavni smycky programu i v preruseni. Jestli to nejak nesouvisi.
Díkes H.

to je ono opravdu 1 znamená jeden cyklus procesoru takže nop !
hledal jsem manuál ke kompileru gcc ale marně ?! asi to neumím - určitě ale existuje a snad obsahuje i popis jak daný povel překládá ! psát nop v c je opravdu hloupost - běžně taky generuji hrany pomocí 3x nop , a žádná rozumná instrukce v této délce neexistuje !

Klíčové slovo volatile dává překladači na jevo, že proměnná může být ovlivněna externí událostí (v našem případě nejčastěji přerušení, ale může být i paralelní vlákno, jiná funkce…). Bez použití volatile překladač optimalizuje přístup k proměnné. To může mít za následek například úplné vyhození proměnné z programu (obsah vyhodnocen jako konstanta), její čtení pouze 1x na začátku cyklu místo v každém průchodu (v daném cyklu se proměnná nemění) a podobné vychytávky. Oproti tomu s volatile se program chová dle očekávání. Když totiž překladač ví, že se může proměnná měnit externě, ví také že jí musí číst/zapisovat v každém průchodu cyklem nebo že jí dokonce nemůže vyhodit kdyby měl takové choutky.
Na druhou stranu má použití i nevýhody. Jsou omezeny optimalizace týkající se kódu kolem takové proměnné. Proměnná je vždy umístěna v ram (lokální ne-volatile bývají v registrech pokud se vejdou). Z toho plyne o něco pomalejší přístup.

Pokud pracuješ s globální proměnnou nejen v přerušení tak musí být vždy volatile abys měl jistotu že se změna projeví na všech místech. To platí ikdyž bude proměnná lokální v main a globální bude jen pointer na ni.

Doporučuji stejně ošetřit i “blinkr”.
Naopak “blincnt” využíváš POUZE v přerušení (a nuluješ v main na začátku). Bylo by vhodnější ji deklarovat jako “static” uvnitř obsluhy přerušení. Statická proměnná je vždy inicializována na “0” a po opuštění funkce zůstává v paměti. Po opětovném zavolání jsou v ní obsažená data k dispozici.

Doplněno:
Obecně je lepší minimalizovat počet globálních proměnných, zhoršují čitelnost kódu a hůř se hledá chyba když ti ji něco někde změní. Například s “mode” pracuješ v mainu a pak v jedné funkci. Klidně by mohla být lokální a do funkce předávána jako parametr. V tomto případě by to bylo i rychlejší.

Nepřemýšlel jsi o náhradě funkce “void preved_na_dis(void)” jednorozměrným polem? :wink: Jendolivá čísla, která dáváš do “x” by byla prvky pole a “mode” pak index v poli.
Volání by pak vypadalo takto průhledně: “displej(pole[mode]);”
Klidně by v tom poli mohla být uložena i abeceda (tu tam taky využíváš). Pro abecedu by sis pak vytvořil výčtový typ enum. Zápis písmena by pak mohl vypadat jako “displej(pole[X]);” Ostatně abeceda by nemusela být v poli, ten enum by pak neobsahoval u abecedy indexy do pole ale přímo konstanty pro displej. Potom by to bylo “displej(X);”.
Pokud bys chtěl jít ještě dál a šetřit paměť, lze tabulku umístit do FLASH. To pak vypadá nějak následovně:
unsigned char dataRC[2][3] PROGMEM =
{
{128 & 63, 160 & 63, 192 & 63},
{128 >> 6, 160 >> 6, 192 >> 6,},
};
temp = pgm_read_byte(&(dataRC[0][1]));

eljaro:
manuál pro gcc je ve složce instalace. U mne je to konkrétně “C:\Program Files\WinAVR-20090313\doc\avr-libc\avr-libc-user-manual.pdf”

piityy: vůbec nevím cos to na mě vyrchlil. Cčko sotva chápu, a ty mě tu bombarduješ enumem. Kdo ví jak se ot ještě pak přeloží, a 10x zpomalí. Funknce preved_na_dis() jako pole? SRAM mám sice dostatečě prázdnou, ale proč jí ještě přidávat, když jsou přístupy do SRAM zpomalující? I když, s přístupem do FLASH to bude asi na stejno. Uznávám, že SWITCH není nejšťastnější volba, ale aspoň si ho procvičim.

Můžeš mi ještě jendou prosím vysvětlit, co dělá to volatile konkrétně? Nějak jsem to příliš nepochopil.

Nyní poučen: NOP budu dělat tím delay_cycle(n). Dík za radu.
Může se ještě někdo ochotný nyní podívat prosím na ten program, jestli tam někde nenarazí na to, proč blinkry po vstupouu do modu 6 neblikají a v podprogramu 2 ano? Respektive, proč to začlo fungovat normálně, když jsem bezhlavě napsal volatile proměnnou light ? Nějak ještě nemám příliš jasno.

K tomu enum - to je pouze záležitost preprocesoru, na výsledný kód to nemá vliv. Zlepšuje to ovšem čitelnost kódu a usnadňuje údržbu. Pokud by se stalo, že budeš muset předrátovat displej, stačí provést změnu v tom enumu a zbytek kódu zůstává nedotčen.

Co se týká pole - přístup do pole v SRAM dle indexu je jednoznačně rychlejší než proskákání celého switch :wink:.

volatile - zjednodušeně zabrání provedení optimalizací, které by vedly k nefunkčnosti kódu v programu, kde se k proměnné přistupuje z více míst než jen lokálně v 1 funkci.
Ve tvém případě se stalo to, že v modu 6 překladač znefunkčnil kód, protože nevěděl, že s proměnnou pracuješ i mimo funkci main.
On si řekl: zapíše tam něco poprvé, nikde to nečte, pak tam zapíše podruhé. To je zbytečná ztráta výkonu a tak první zápis vyhodím. To, že ty to potřebuješ v přerušení on neví. Musíš mu to právě říct tím volatile. Že ti to v módu 2 funguje je dáno tím, že jsou tam vždy 1 operace stejného typu (třeba and). V módu 6 prostě ty 2xAND a 2xOR sloučí za účelem optimalizace a už to nefunguje. Tím volatile mu řekneš, že takovou optimalizaci nesmí provést.

Jo, konečně to chápu. To je ten překladač až toliko drzej, že mi normálně maže příkazy? (na tohle odpovídat nemusíte…)
No, ale nechápu to zas tolik úplně.
V modu dva mám to samé jak v modu 6. Promennou blinkr tam taky nikde nečtu, stejně tomu je tak i v mode 6. Vždycky do toho jen přiřadím číslo. Tak proč to tedy ve dvojce chodí, a v šestce ne?

V 6 tam přiřazuješ 2x stejnou operací. Je tam 2x AND a 2xOR. Je možné, že to prostě sloučí dohromady a provede každou operaci jen 1x. Z pohledu překladače je to totiž jedno když mezi těmi operacemi tu proměnnou nepotřebuješ (bez volatile neví že ji využíváš jinde) a optimalizátor si udělá čárku za ušetřenou instrukci.

nevím, možná se nechápem. Nikde tam and nebo or nevidím :open_mouth:
Nechodí toto
blinkr = 1; //zapni blikr vlevo
A vyřešilo to přidání volatile k proměnné light. A já se snažím dopátrat, jakou to má souvislost.

Pardon, ja koukal na light :unamused:
Tady mě nenapadá proč to ve 2 chodí. Podle stejného pravidla by to nemělo frčet ani tam. Asi se tam vyskytlo něco, díky čemu to překladač nevyhodil. Netuším ale co. Ony jsou ty optimalizace poměrně nevyzpytatelný. Dokonce se mi stalo, že po jednom překladu podobná věc chodila a po druhym ne :wink: V takovémhle případě máš ale víceméně stěstí když to funguje dobře. Lepší je když to nejde, aspoň to vyrešíš hned a nejsou s tím pak problémy.

Takže nikdo neví, proč to nejde. To se dalo předpokládat. Zas to jen podporuju můj odpor vůči programování MCU v Cčku. v ASM je to pracné, ale máte jistotu, že to bude fungovat.

Takže jestli jsem správně pochopil to volatile, tak bych ho měl dát před všecky proměnné, kromě té mode. Ach jo, já to C tak nesnáším! Sice některé věci jsou v něm tak jednoduché k naprogramování, ale na druhou stranu vám to vyrobí takovéhle problémy nemající řešení :smiley:
A kdybych náhodou nepřišel na to, jak to vyřešit, tak protože tu nikdo netuší kde je problém, asi by to nikdy nefungovalo. Jinak to byla čirá náhoda, že mě napadlo napsat tam slovo “volatile”, a to jsem ani nevěděl co tohle slovo znamená. Jen náhoda. Člověk je občas schopný dělat pěkné blbosti, především po hodině čumění do správně napsaného zdrojáku, který nefunguje, protože předkladač vám z něj vyrobí něco jiného, co jste vůbec nenapsal.

A to se mi to Cčko docela začalo líbit. Ale jak jsem začal používat přerušení, už se mi to zas pomalu líbit přestává.
No, kdyžtak mi promiňte ty výlevy mimo mísu :slight_smile:

Před všechny né, to by sis to zbytečně zpomalil. Jen před globální které využíváš v přerušení. A taky když ti občas něco nefunguje a přitom by mělo, tak je tohle dobrá cesta při hledání chyby :slight_smile:.
Neměj strach, to volatile bysme ti určitě doporučili :wink: Kde je problém víme, to je specifická věc C-čka. Jen není jasné proč to ve 2 funguje - tam jsi měl štěstí :smiley:

Řeknu svů j skromný názor na věc, proč to funguje tam, a jinde ne.
Další blbý bug v AVR studiu. Neni to poprvé (už po třetí) kdy jsem v AVR studiu narazil na věc stejného charakteru. 2krát v ASM kompileru ( bohužel si nepamatuju první případ) -druhý případ si matně vzpomínám - když se dají za sebe nějaké dvě instrukce, tak se vůbec nepřeloží, nebo blbě. enhle problém mohu přesně dohledat - dobře si pamatuju, jak jsem byl nehorázně nas* že mi kvůli chybě překladače nejde program. Přišel jsem na to až po hodinách strávených v hexa editoru, a pitvání přeloženého kodu). Třetí případ je tento. A to do toho nepočítám tu chybu v AVR simulatoru, kde jsou špatně interpretovány ADCSRx registry u ATmega8. Je to ve verzi 4.16, kterou stále používám. Ale po simulaci šáhnu málokdy, takžýe mě to příliš netrápí. :slight_smile:
Už těch výlevů radši nechám :slight_smile:

Mám tu učebnici Cčka, ale jako programování pro PC, možná tam půjde o tom volatile něco najít.

Mimochodem, abychom tu furt nekecali jen nad nepovedeným zdrojákem, tak vám ještě předvedu, k čemu že ten program vlastně je :slight_smile:

jan16.czela.net/Robot.jpg