SMT160 a AVR-GCC

29.08.2007

SMT160 je poměrně známé a často využívané čidlo na měření teploty. Na internetu lze nalézt mnoho popisů jak s tímto čidlem pracovat a jak jeho výstup více či méně přesně převést na číselnou hodnotu pomocí nějakého jednočipu. Důvodem, proč jsem se rozhodl sepsat několik svých poznámek byl fakt, že jsem chtěl udělat převodník pomocí Atmela a jazyka C, konkrétně AVR-GCC.

Jak pracuje SMT160

Jedná se v principu o obyčejný kalibrovaný převodník teplota střída. Výstupem čidla je binární signál (log.1 a log.0) o frekvenci 1 až 4kHz. Tato frekvence není nikterak důležitá, já měl většinou čidla, které produkovaly signál s výstupním kmitočtem 3.2kHz v širokém rozsahu teplot. Důležitá je právě ta střída, tedy poměr mezi log.1 a log.0. Následující obrázek ukazuje, jak takový výstupní signál vypadá:

T je perioda signálu a může být od 0.25 do 1 ms. TH je doba signálu v log.1 a TL doba signálu v log.0.

Střídu zjistíme takto: DC = TH / ( TH + TL ) a výsledkem je hodnota od 0 do 1

Vlastní vztah pro výpočet teploty je DC = 0.32 + 0.0047 * t

Pokud vyjádříme teplotu, je to: t = ( DC - 0.32 ) / 0.0047

Teoretické maximální hodnoty jsou pro DC=0:-68°C a pro DC=1:144°C. Celý výpočet vypadá poměrně snadno, pokud však máme multimetr nebo osciloskop, který umí změřit střídu a kalkulačka, která umí sčítat a dělit desetinná čísla. U jednočipů jsou tyto věci problémovější a je nutno sáhnout ve většině případů k různým kompromisům a to co se přesnosti nebo časové náročnosti týče.

Měření střídy

Hlavním uskalím při měření střídy je fakt, že se musí hodně přesně poznat začátek a konec impulzu, resp. změny stavu signálu. Pokud se tento problém podcení, tak jsou výsledky značně zkresleny. Čili je potřeba buď velmi rychlý jednočip, který dovede v dané smyčce testovat změnu hodnoty na nějakém pinu a přitom si počítat nějaky timeout (tj. celkem dost instrukcí) pro případ, že by čidlo nebylo připojeno a nebo je nutné, z mého pohledu nevyhnutelné, použít přerušení a vazbu na nějaký časovač/čítač v jednočipu. Ideální varianta by byla, kdyby jednočip obsahoval 2 čítače a změnou signálu na pinu je dokázal sám spustit nebo zastavit. Tímto způsobem by bylo možné velmi elegantně změřit střídu s přesností na jeden takt procesoru. Důležité je ovšam rychlost plnění integrovaného čítače zvolit tak, aby došlo k jejich významějšímu naplnění jak pro frekvenci čidla 4kHz a nemohlo dojít k přetečení při nejnižší frekvenci 1kHz. Rozdíl je 4-násobný a tak při 4 kHz je při celé délce periody vyuřije z 16-ti bitů (65536) jen 16384. Ale i tato hodnota svou přesností dostačuje k uspokojivým výsledkům. Výhoda tohoto přístupu je tak, žev výsledná teplota se dostane během jedné nebo maximálně 2 period vstupního signálu, tedy prakticky hned. Nevýhodou náročnost na rychlost nebo HW vybavenost jednočipu.

Měření střídy tak trochu jinak

Většina autorů, jejichž popisy měřících rutin jsem na internetu našel, využívá trošku jiného přístupu. Neměří střídu jedné periody signálu, ale periodicky signál vzorkují - tzn. testují jestli je signál v log.1 nebo log.0 a to přes mnoho po sobě následujících period signálu. Počet takových vzorků, kde se zjistí signál v log.1 a v log.0 pak celkem uspokojivě určuje střídu vlastního signálu. Zásadní výhodou této metody je, že jednočip nemusí být zase tak moc rychlý, ale nutnou podmínkou je, aby vzorkování probíhalo v naprosto stejných intervalech a vyšší rychlostí než je vlastní signál z čidla. Důležitá vlastnost pro vyšší přesnost je také velký počet vzorků. Celkem zajímavou vlastností přepočetního vztahu je, že pokud zvolíme vhodný počet vzorků, lze teplotu dostat bez nějakých koplikovaných přepočtů. O tom se lze přesvědčit například zde. Následující část popisuje, jak lze tuto metodu "nabastlit" v jednočipu Atmel AT-Mega8 a jazyce C a to bez použití časovačů, nebo přerušení.

Měření Atmelem v AVR-GCC

V prvé řadě musím podotknout, že Cčko a jakýkoliv jiný jazyk není na vyrobení rychlých vzorkovacích intervalů přiliš vhodný? Proč? Protože při překladu do ASM dochází k určitým optimalizacím, překladač si námi dobře myšlené kontrukce přežvejká jinak a výsledek je třebas ten, že při testování log.0 provádí cyklus o jednu nebo dvě instrukce méně a celé měření se tím zkreslí. Čili pokud mohu doporučit hned na začátku, pište tyto věci v assembleru, je to lepší. No a pokud nechcete, tak jako já, zde je nějaký ten výsledek, který kupodivu chodí :-)

Nejprve kousek kódu, který produkuje špatné hodnoty díky rozdílným intervalům vzorkování:

unsigned int mert2(unsigned char pin) {
 unsigned int sh=0, sl=0, samples=0x0FFFF;
 while(samples--) {
  if(PINC&pin) {
   sh++; 
  } else {
   sl++;
  }
 }
}

Jedná se o primitivní funkci, která cyklicky testuje stav na portu C (lze samozřejmě změnit) a pinu daném bitovou maskou pin. Bitová maska je číslo, kde binární jednička je na místě pinu, který chceme testovat, to jen tak pro snadnější porozumění. Například 0x20h je test pro pin 5, v tomto případě PC5. Princip spočívá v tom, že provedeme 0x0FFFF, tedy 65535 vzorků a do proměnných sh a sl uložíme počet log.1 a počet log.0. Další zpracování už je principielně nevýznamné.

.L30:
   in r24,51-0x20
   and r24,r25
   breq .L31
   subi r22,lo8(-(1))
   sbci r23,hi8(-(1))
   rjmp .L33
.L31:
   subi r20,lo8(-(1))
   sbci r21,hi8(-(1))
.L33:
   daší společný kód realizující while a skok na .L30

Zde je kousek ASM, které vygeneroval překladač. Je to část toho Cčkovského IFu. Registry 20,21 a 22,23 jsou registry proměnných čítačů sh a sl. V registru r25 je uložena proměnná pin, tedy zmíněná bitová maska a do r24 se instrukcí in načítá stav portu C. Včechny instrukce trvají jeden strojový cyklus. V čem je tedy zrada? V počtu instrukcí, které se vykonají v případě, že je signál z čidla v log.1 nebo v log.0. Počet vzorků jedné úrovně pak neodpovídá poměrově druhé úrovni, získáme špatnou střídu a nelineární vztah mezi skutečnou teplotou a naměřenou.

Prováděné instrukce pro jednotlivé logické stavy:

log.1: in, and, breq, subi, sbci, dalsi kod
log.0: in, and, breq, subi, sbci, rjmp, dalsi kod

Grafické znázornění ukazuje delší vzorkovací periodu u log.0 díky instrukci navíc.

Eliminace je z pohledu assembleru velmi jednoduché a to přidat instrukci nop do bloku v návěstí .L31. Tím by se počet instrukcí srovnal a výsledek vzorkování by byl správný. At jsem vlozil asm("nop"); kamkoliv, ať jsem měnil konstrukce IFu, tak překladač stále generoval kód, který byl takto postižený. Nepomohla ani změna optimalizace překladace a do gcc in-line assembleru se mi příliš pouštět nechtělo.

Nakonec se ukázalo, že řešení tohoto problému je v C poměrně snadné a jednoduché. Principielně jde o to, že v jednom cyklu budeme v signálu sledovat a počítat log.1, v druhém cyklu pak log.0. Rozdílné počty vzorků při průchodu IFem se takto vzájemně eliminují, protože protože použijeme jen a pouze je. Je pak celkem zřejmé, že pokud IF zpomalí vzorkování, nebude celkový součet vzorků jedniček a nul roven 65535, ale průměrně číslu 62000, ale to v konečném výpočtu vůbec nevadí. Následující část kódu ukazuje, o co jde:

...
sh=0;
samples=0x0FFFF;
while(samples--) {
 if(PINC&pin) { 
  sh++;
 }
}
sl=0;
samples=0x0FFFF;
while(samples--) {
 if(!(PINC&pin)) {
  sl++;
 }
}
...

První cyklus provede 65535 testů, zda-li je vstupní signál roven log.1 a pokud ano, přičte k proměnné sh jedničku. Druhý cyklus provede to samé, jen s tím rozdílem, že přičte jedničku k sl pokud je signál v log.0.

...
.L8:
   in r24,51-0x20
   and r24,r7
   breq .L9
   subi r16,lo8(-(1))
   sbci r17,hi8(-(1))
.L9:
   daší společný kód prvního while a skok na .L8

.L12:
   in r24,51-0x20
   and r24,r7
   brne .L13
   adiw r28,1 <- trvá 2 strojové takty
.L13:
   daší společný kód druhého while a skok na .L12
...

Zde je opět kousek assembleru, kde je zřejmé, že pri splnění podmínky dochází k ovlivnění proměnných sh a sl. V obou případech však procesor provádí stejný počet strojových taktů. Instrukce adiw potřebuje 2. Graficky pak situace vypadá následovně:

Z takto získaných hodnot sh a sl uz lze vypočítat střídu a teplotu. Vhodné je ovšem měření několikrát zopakovat a použít až výsledný průměr, tak lze získat celkem slušné hodnoty teploty. Nevýhodou tohoto způsobu měření je to, že trvá déle a nelze čekat výsledek během několika ms. Pro běžné použití to však nepředstavuje žádný problém a kdo potřebuje něco rychlého a přesného, tak stejně raději sáhne po pt100 a převodníku :-)

Následující funkce 5x změří teplotu a vrátí její průměr jako usigned int viz. komentář funkce. Takt procesotru ATmega8L je 8MHz, ale mělo by to fungovat i při nižším. Během měření musí být zakázané přerušení, mohlo by výsledek negativně ovlivnit. Taktéž je vhodné nulovat watchdog, pokud je aktivní.

// bitové masky snímačů
#define TH0 0x02
#define TH1 0x04
#define TH2 0x08

// zmeri teplotu pomoci cidla SMT160
// navrat je cislo od 700 do 2200, kde stred je 1000 a teplota *10
// priklad: 25.6°C je 1000+256=1256;  -7.5°C je 1000-75=925
// neni tedy pro nutne pro dalsi zpracovani pouzivat 4 bytes float
unsigned int merto(unsigned char sensor) {
 unsigned int sh, sl, samples, tepout;
 unsigned char i=5, th=0;
 float teplota=0;
 switch (sensor) {
  case 1: th=TH0; break;
  case 2: th=TH1; break;
  case 3: th=TH2; break;
 }
 cli();
 while(i--) {
  sh=0;
  samples=0x0FFFF;
  while(samples--) if(PINC&th) sh++;
  sl=0;
  samples=0x0FFFF;
  while(samples--) if(!(PINC&th)) sl++;
  teplota+=(int)((((float)sh/(float)(sh+sl))-0.32)/0.00047);
  asm("wdr");
  }
  sei();
 tepout=(int)(teplota/5)+1000;
 if((tepout<700)||(tepout>2200)) tepout=0;
 return tepout;
}

Závěr:

Výše uvedené řešní nepředstavuje žádný zázrak (tzn. nevyužívá přerušení na vstupní signál, časovače apod), ale může pomoci těm, kdo umí jen C a diví se, že výsledky měřících nebo zpožďovacích cyklů jsou nějaké divné a netuší proč :-)