Debugovací přípravek pro ATtiny


Když dělám nějaké prográmky pro ATtiny, stává se mi, že občas potřebuji při ladění zobrazit nějaký mezivýsledek výpočtu, nebo nějkou změřenou hodnotu.
Problém je v tom, že Attiny má omezenou paměť, takže knihovny pro sériovou komunikaci nebo dokonce I2C komunikaci se tam už obvykle nevejdou. Omezení bývá i v počtu volných I/O pinů.

Většinou jsem to řešil nějakým blikáním jedné LED (na internetu jsem našel třeba kus kódu, který postupně "vybliká" i několikaciferné číslo).
Takovéto kódy už jsou ale zase docela náročné na paměť ATtiny. Navíc existuje značné riziko, že se při ručním počítání bliknutí spletu.
Je to i pomalé (vyblikání jednoho trojciferného čísla může trvat třeba i víc než 20 sekund).


Napadlo mě, že bych místo blikání použil jen rozsvícení LED na nějakou dobu, která by odpovídala číslu.
Například číslo 0 by bylo signalizováno impulzem o délce 50us, číslo 1 impulzem 100us, 2...150us , a tak dále až k číslu 255, které by přenesl impulz délky 12,8ms.
Bylo by samozřejmě nutné pro měření délky impulzu použít nějaké přesné stopky (druhé Arduino).

Vysílání by bylo jednoduché a zabralo by jen pár bajtů v paměti ATtiny:
void debug(byte hodnota)
{
unsigned int pauza = (1 + hodnota) * 50; // 0=50us; 1=100us; 2=150us ........ 255=12800us
digitalWrite(vystupni_pin, HIGH)
delayMicroseconds(pauza);
digitalWrite(vystupni_pin, LOW)
delay(10); // nutna pauza pro prijimac, aby stacil zobrazit prijate cislo treba na displeji
  }

 


Kód pro příjem v druhém Arduinu by byl taky jednoduchý.
Předpokládal jsem, že použiju funkci pulseIn(), která je přímo stavěná na měření délky impulzů.
Získané číslo by se pak někde zobrazilo (sériový terminál, displej ...)
void loop(void)
{
sirka_pulzu = pulseIn(pin_vstup , HIGH); // mereni sirky impulzu
if (sirka_impulzu > 0) // kdyz se nejaky impulz zaznamena, tak se zpracuje
{
cislo = (sirka_pulzu - 25) / 50; // prevod casu na cislo vcetne zaokrouhleni
Serial.println(cislo);
  }
}


Jenže takhle jednoduché to není!





První důvod, proč to nefunguje, je v tom, že ATtiny nemá přesný vnitřní generátor, takže šířka impulzu nemusí úplně přesně odpovídat požadovanému času.
Tahle chyba se projevuje více na delších intervalech.

Tento problém jsem v prvním kroku vyřešil kalibračním impulzem, který se odesílá před každým číslem.
Kalibrační impulz byl dlouhý třeba 1ms (z pohledu vysílacího ATtiny) a podle toho, jak dlouhý impulz ve skutečnosti změřilo přijímací Arduino se pak druhý impulz, který už přenášel číslo, korigoval.

Větší problém ale byl v tom, že když jsem zkusil generovat v ATtiny řadu stejně dlouhých 1ms impulzů, tak byl každý jinak dlouhý.
Odchylka mezi minimální a maximální naměřenou hodnotou byla třeba i přes půl procenta.
Tato nepřesnost způsobovala, že se výsledné číslo mohlo lišit od vysílaného čísla až o +/- 1.

Při dlouhodobém testování (několik hodin) se občas objevila odchylka, která byla ještě větší. Chyba by pak občas mohla být i +/- 2 čísla.

V dalším kroku jsem tedy přidal do kalibračního impulzu ještě informaci, která jednoznačně upřesní hodnotu odesílaného čísla.

U odesílaného čísla zjistím, jaký je jeho zbytek po dělení 5.

(zbytek = cislo % 5)

Podle výsledku se nastaví délka kalibračního impulzu:
Pokud je odesílané číslo přesně dělitelné 5, je kalibrační impulz dlouhý 5ms.
Pokud je zbytek po dělení 1, je kalibrační impulz dlouhý 6ms.
Pokud je zbytek po dělení 2, je kalibrační impulz dlouhý 7ms.
Pokud je zbytek po dělení 3, je kalibrační impulz dlouhý 8ms.
Pokud je zbytek po dělení 4, je kalibrační impulz dlouhý 9ms.

Takto velké rozdíly není problém v přijímacím Arduinu rozlišit.


Když pak zbytek po dělení vypočteného čísla odpovídá délce kalibračního pulzu, předpokládám, že bylo měření v pořádku.
Když délka kalibračního pulzu neodpovídá zbytku po dělení vypočteného čísla, je provedena celočíselná korekce výsledného čísla na takovou hodnotu, aby délka kalibračního impulzu souhlasila.



Příklad:
Má se odeslat číslo 103. Zbytek po dělení pěti je tedy 3.

ATtiny odešle první impulz, který by měl být dlouhý 8ms.

Přijímací Arduino tento impulz přijme a změří, že ve skutečnosti trvá 7.93ms.
Tím je téměř s jistotou dáno, že zbytek po dělení výsledného čísla musí být 3.
(I když ten impulz netrvá přesně 8ms, je jasné, že k 8ms má výrazně blíž, než k 7ms.)


Druhý impulz, který vyšle ATtiny by měl být dlouhý ((103 + 1) * 50us)  = 5200us.

Protože přijímací Arduino nemá seřízený krystal na úplně stejnou frekvenci, jako má vnitřní oscilátor ATtiny, změří Arduino délku
 druhého impulzu třeba jen 5097us.

Na základě prvního kalibračního pulzu, který měl být 8ms, ale byl jen 7.93ms se určí, že skutečná jednotková délka času je
     7.93ms / 160 =  49.5625us.
(160 jednotek by mělo teoreticky trvat 8ms)

Změřená délka druhého impulzu se vydělí touto jednotkovou konstantou, zaokrouhlí se a převede se na celé číslo:
int( (5097 / 49.5625) - 0.5) = 102

Číslo 102 má ale zbytek po dělení pěti roven 2. To neodpovídá požadovanému zbytku po dělení, který by měl být 3.
Proto se pomocí korekční tabulky najde takové nejbližší číslo ke 102, pro které platí ten požadovaný zbytek po dělení pěti.

V tomto případě je tedy nejbližší nižší číslo 98 (to by ale znamenalo korekci -4).
Nejbližší vyšší číslo je 103 (to je na korekci +1).
103 je tedy nejblíž a je to správný výsledek.
 
 



Příklady skutečných signálů:


detail jednoho přenášeného čísla


přenos dvou čísel (130 a 213)

Schéma:



verze pro Eagle: tiny_debug.sch

Programy:


Vysílací část pro ATtiny se skládá ze 3 částí, které se přidají k původnímu programu, který se nachází v ATtiny.

1) V globální části je nadefinovaný vysílací pin. Volí se odkomentováním jedné ze 6 řádek.
  (V případě, že se má pro odesílání čisel použít resetovací pin (PB5), je nutné změnit v ATtiny FUSE.)

2)  Samostatný podprogram, který převádí číslo na dva impulzy.

3) V sekci setup() je třeba přenastavit vysílací pin na výstup

Požadované číslo se pak do displeje odesílá v libovolné části původního programu pomocí volání podprogramu:
   debug(číslo);

Kromě běžných jednobajtových čísel (0 až 255) je možné odeslat i několik řídících kódů, které upravují zobrazení na displeji:

256 nastavení pozice pro zobrazení čísla na levý horní roh displeje [0,0]
257 nastavení pozice pro zobrazení čísla souřadnice  [4,0]
258 nastavení pozice pro zobrazení čísla souřadnice  [8,0]
259 nastavení pozice pro zobrazení čísla souřadnice  [12,0] (pravý horní roh displeje)
260 nastavení pozice pro zobrazení čísla na levý dolní roh displeje [0,1]
261 nastavení pozice pro zobrazení čísla souřadnice  [4,1]
262 nastavení pozice pro zobrazení čísla souřadnice  [8,1]
263 nastavení pozice pro zobrazení čísla souřadnice  [12,1] (pravý dolní roh displeje)
264 přepnout na režim desítkového zobrazení čísel
265 přepnout na režim hexadecimálního zobrazení čísel
266 přepnout na režim znakového zobrazení
267 přepnout na režim binárního zobrazení čísel
268 smazat aktuální pozici na displeji (blok 4 znaků pro DEC, HEX a CHAR, nebo blok 8 znaků pro BIN
269 smazat celý displej

Přepínač na přípravku umožňuje přepsat nastavení režimu zobrazení.
V poloze "Tiny" respektuje režim zobrazení, který odesílá Attiny.
V polohách "DEC", "HEX", "CHAR" a "BIN" ale zobrazuje všechna přijatá čísla ve zvoleném režimu -
  nezávisle na tom, jaký režim chce zobrazovat  ATtiny.


režim Tiny (ATtiny si určuje, jak se mají čísla zobrazovat)

režim DEC (všechna čísla jsou zobrazana desítkově)

režim HEX (všechna přijatá čísla jsou zobrazena hexadecimálně)

režim BIN (čísla jsou zobrazena binárně - bez mezer)



// Pred kompilaci odkomentovat radku s vysilacim pinem
 #define vysilaci_pin 0b00000001 // pouzit pro PB0 (noha 5)
//#define vysilaci_pin 0b00000010 // pouzit pro PB1 (noha 6)
//#define vysilaci_pin 0b00000100 // pouzit pro PB2 (noha 7)
//#define vysilaci_pin 0b00001000 // pouzit pro PB3 (noha 2)
//#define vysilaci_pin 0b00010000 // pouzit pro PB4 (noha 3)
//#define vysilaci_pin 0b00100000 // pouzit pro PB5 (noha 1) (pin RESET vyzduje jeste zmenu FUSE)





//------------- podprogram pro prevod cisla na dva impulzy ---------------------------------
void debug(unsigned int hodnota)
{
// prvni impulz je pro kalibraci a zaroven slouzi pro kontrolu, jestli je cislo /5, /5 +1, /5 +2 , /5 +3 nebo /5 +4
 unsigned int pauza = 5000 + (1000 * (hodnota % 5)) ; // kalibracni impulz bude dlouhy 5, 6, 7, 8 nebo 9 ms
PORTB |= vysilaci_pin; // vysilaci pin do HIGH
delayMicroseconds(pauza);
  PORTB &= ~vysilaci_pin; // vysilaci pin zpatky do LOW
delay(20); // nutna pauza pro prijimac

// druhy impulz uz ma delku odpovidajici odesilanemu cislu
pauza = (1+hodnota) * 50;
PORTB |= vysilaci_pin; // vysilaci pin do HIGH
delayMicroseconds(pauza);
  PORTB &= ~vysilaci_pin; // vysilaci pin zpatky do LOW
delay(200); // nutna pauza pro prijimac
 }
//------------------------------------------------------------------------------------------





void setup(void)
{
// nastaveni pinu v puvodnim programu pro ATtiny
// *****


// prenastaveni vysilaciho pinu na vystup
DDRB |= vysilaci_pin; // nastaveni smeru signalu na vysilacim pinu na vystup (ostatni piny beze zmeny)
PORTB &= ~vysilaci_pin; // tento vystup nastavit do LOW (ostatni piny beze zmeny)


debug(156); // odeslani cisla 156 na displej (defaultne se zobrazi v levem hornim rohu v desitkove soustave)

}




Ukázkový příklad do ATtiny s odesíláním čísel do různých poloh displeje s různými režimy zobrazení:


Program pro přijímač (Arduino NANO):




úvodní strana webu AstroMiK.org

poslední úprava stránky 24.10.2019