Reklama

SPI u AVR 3. Díl – Praktické příklady II.

Úvod

V tomto posledním díle seriálu věnovaném SPI u mikrokontroléru AVR si ukážeme příklad, jak ovládat digitální potenciometr MCP4251.

Popis

Většina vašich aplikací bude SPI využívat k ovládání nějakého integrovaného obvodu a ne ke komunikaci mikrokontrolér – mikrokontrolér, jak jsme si předváděli v příkladech v minulém dílu seriálu (SPI u AVR 2. Díl – Praktické příklady I.). Proto jsem mezi příklady zařadil i ukázku ovládání digitálního potenciometru. Volba padla na MCP4251 z několika důvodů. Pro ukázky jsem potřeboval relativně jednoduché zařízení, abyste se neztráceli v jeho struktuře. Zároveň jsem také chtěl, aby to pro vás bylo užitečné a aby byl obvod k sehnání. Kromě mnoha dalších možností využití může digitální potenciometr sloužit jako DA převodník, který 8bitovým Atmelům chybí. Příklad bude trochu rozsáhlejší, než bývá obvyklé. Nejprve se budu ve zkratce věnovat možnostem digitálního potenciometru a poté stručně proberu způsob ovládání. Nakonec vyberu jednu z připravené knihovny funkcí a popíši, co dělá. I když se příklad bude točit kolem potenciometru, tak mějte na zřeteli, že se chcete naučit používat SPI na mikrokontroléru. Příště totiž budete psát ovladač pro něco úplně jiného.

Digitálních potenciometrů existuje celá řada a jejich popis a ukázky by vydaly na několik seriálů. Proto se pokusím být stručný. Pokud se budete dívat na digitální potenciometr jako na “black-box” (což ve článku budu), tak je potřeba si uvědomovat, že oproti běžnému potenciometru potřebuje jeho digitální verze napájení. A na žádný jeho vývod nesmíte přivést napětí mimo rozsah toho napájecího. To je tvrdý limit. Znemožňuje vám to vzít libovolné zapojení a vyměnit v něm běžné potenciometry za digitální. Běžné digitální potenciometry mají maximální provozní napětí 1.8 – 5.5 V, takže systémy pracující s vyšším napětím musí být navrženy chytře, tak aby potenciometry mohly pracovat s nižším napětím. Naštěstí ale narazíte i na potenciometry na 36 V (respektive +/- 18V). Což je napětí, které umožňuje implementovat potenciometry do valné většiny aplikací s běžnými operačními zesilovači (ty mívají provozní napětí podobná). Kromě provozního napětí existují ještě další kritéria, podle kterých je potenciometry možné rozdělit. Prvním z nich je paměť. Existují volatilní a nevolatilní verze. Volatilní verze ztrácejí po odpojení napájení svoji hodnotu a startují vždy do základního nastavení, kde je jezdec buď na kraji odporové dráhy, nebo uprostřed. Nevolatilní verze mají paměť, ve které je možné uchovat polohu potenciometru a po připojení napájení je pak jezdec na hodnotě jakou si určíte. Dalším kritériem je počet kroků. Existují v podstatě pouze varianty s 64, 128, 256, 512 a 1024 kroky. Na jemnější potenciometry jsem nenarazil. Dále je potenciometry možné dělit podle komunikačního rozhraní. Nejčastěji se setkáte s I2C, SPI a UD protokoly. UD protokol je volen, tak aby bylo možné potenciometr připojit přímo k rotačnímu enkodéru. Posledním kritériem, které stojí za zmínku je “topologie”. Vyrábí se potenciometry se třemi vývody, nebo dvouvývodové verze sloužící jako reostat. Nejlevnější potenciometry začínají přibližně na ceně 15 kč za kus. Vyrábí se ve variantách s jedním nebo dvěma potenciometry v jednom pouzdře. Hodnoty odporu bývají od 1 kOhm do 1 MOhm a přesnost není vysoká. Typicky se hodnoty pohybují v toleranci +/- 10 %. Situace je podobná jako u trimrů, kde většina zapojení je ale navržena tak, aby tyto nejistoty nevadily. Stálost hodnoty je naštěstí vysoká. Pokud vás budou možnosti digitálních potenciometrů zajímat více, zkuste si pobrouzdat obchody jako TME nebo Farnell (Farnell) a podívat se na různé typy.

Pro můj příklad jsem vybral typ
MCP4251. Volatilní relativně levný dvojitý potenciometr s rozsahem 256 kroků s SPI rozhraním a možností interního odpojení vývodů. Díky tomu, že jde o volatilní typ, tak je jeho ovládání jednodušší. Doporučuji prostudovat si datasheet (už jen proto, abyste si udělali představu o tom, jaké informace v něm jsou a jaké ne). Hned v úvodu vás upozorním na jistý nedostatek v datasheetu. Na nákresu rozmístění vývodů vidíte pin označený WP (asi jako Write Protect), dále v textu, ale nenajdete popis a význam tohoto vývodu. Na obrázku je totiž “překlep” a vývod má být označen NC (“Not Connected”). Zkoušel jsem na něj přivést log. 0, ale zápis to neblokovalo. Pin SHDN slouží k “vypnutí” potenciometru a obsahuje “pull-up” rezistor, takže ho nemusíte zapojovat, pokud funkci vypínání nepotřebujete. Během vypnutí jsou všechny jeho vývody odpojeny. Vývody P0B, P0W a P0A tvoří potenciometr 0 a vývody P1B, P1W a P1A tvoří potenciometr 1. Dále už na čipu najdete jen komunikační linky pro SPI. Odpor jezdce je přibližně 70 Ohmů a potenciometr umožňuje nastavit jezdec k oběma krajům, tedy defakto spojit P0W s P0B nebo P0A (pouze s odporem jezdce). Komunikace po SPI může probíhat v režimu 0 nebo v reřimu 3 a kmitočet hodin může dosahovat až 10 MHz (takže žádné zdržování). Připojení k mikrokontroléru můžete vidět na Obr. 1.

 
Obr. 1: Zapojení MCP4251. R4 zajišťuje, že během programování mikrokontrolér nepřijímá potenciometr data. Není nutné použít oddělovací odpory R1,R2 a R3.

Obr. 1: Zapojení MCP4251. R4 zajišťuje, že během programování mikrokontrolér nepřijímá potenciometr data. Není nutné použít oddělovací odpory R1,R2 a R3.

 

Používání jakéhokoli integrovaného obvodu začíná vždycky čtením datasheetu. Přirozeně nikomu se ho nechce číst celý, tak je tedy potřeba zaměřit se jen na klíčové informace. V tomto případě jde o to, co potenciometru posílat a jak mu to posílat. Co lze potenciometru posílat je v Tab. 2 (v datasheetu tabulka 7-2). Jak můžete vidět, tak lze každému ze dvou potenciometrů nastavovat pozici jezdce, číst ji a dávat pokyn k inkrementaci nebo dekrementaci pozice jezdce. Mimo to můžete zapisovat nebo číst z TCON registru. Také ještě můžete číst tzv. “Status register” (datasheet 4-1), který obsahuje pouze informaci o tom, jestli je potenciometr vypnut pinem SHDN. Pomocí TCON (datasheet 4-2) registru můžete odpojovat / připojovat kterýkoli z vývodů potenciometru. Příkazy inkrementace a dekrementace jsou osmibitové. Všechny ostatní příkazy jsou 16 bitové. Formát 16 bitového příkazu je znázorněn v Tab. 1. V horním řádku jsou data, která vysílá Master, v dolním řádku je pak odpověď od potenciometru. Nejvyšších 6 bitů zprávy (C5 až C0) obsahuje kód příkazu (viz Tab. 2). Bitem ERR potenciometr signalizuje chybu v příkazu. Pokud je příkaz neplatný (pošlete něco, co není v tab. 2.), tak potenciometr pošle v bitu ERR nulu. Pokud je příkaz v pořádku, tak potenciometr posílá jedničku. Vy si pak můžete odpověď přečíst a případně na to reagovat. Zbylých devět bitů zprávy (D8 až D0) jsou odesílaná nebo přijímaná data (podle toho jestli zapisujete nebo čtete). Všechna data jsou devítibitová, ať už jde o hodnotu jezdce, TCON registr nebo Status registr. Vždy se čte nebo zapisuje devítibitová hodnota. Poloha jezdce smí nabývat hodnot 0 až 256 (proto je na ni potřeba devět bitů). Výsledný odpor dráhy od jezdce k terminálu B je:

 

Rwb = Rab * N/256 + Rw

Kde Rab je odpor celého potenciometru (mezi vývody R0A a R0B), Rw je odpor jezdce a N je zapsaná hodnota (0 až 256). U osmibitových příkazů (inkrementace a dekrementace) nemají žádné bity kromě příkazu (C5 až C0) a ERR žádný význam. Potenciometr navíc umožňuje “kontinuální zápis”, není tedy nutné po každém příkazu vracet CS pin do log. 1. Což je vlastnost, kterou asi neoceníte. Pokud se pokusíme inkrementovat nebo dekrementovat jezdec, který je na konci dráhy a daným směrem se už nemůže “pohnout”, tak se jeho poloha přirozeně nezmění.

Tab. 1: Formát zprávy pro MCP4251

bit 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 Poznámka
MOSI C5 C4 C3 C2 C1 C0 - D8 D7 D6 D5 D4 D3 D2 D1 D0 mikrokontrolér => pontenciometr
MISO - - - - - - ERR D8 D7 D6 D5 D4 D3 D2 D1 D0 potenciometr => mikrokontrolér

 

Pomlčka značí bity jejichž hodnota nemá význam.

Tab. 2: Seznam příkazů pro MCP4251

Příkaz Kód příkazu Vzor zprávy (MOSI) Odpověď potenciometru (MISO)
Zapiš polohu jezdce 0 000000 0000 000d dddd dddd 1111 1111 1111 1111
Přečti polohu jezdce 0 000011 0000 1100 0000 0000 1111 111d dddd dddd
Inkrementuj jezdec 0 000001 0000 0100 1111 1111
Dekrementuj jezdec 0 000010 0000 1000 1111 1111
Zapiš polohu jezdce 1 000100 0001 000d dddd dddd 1111 1111 1111 1111
Přečti polohu jezdce 1 000111 0001 1100 0000 0000 1111 111d dddd dddd
Inkrementuj jezdec 1 000101 0001 0100 1111 1111
Dekrementuj jezdec 1 000110 0001 1000 1111 1111
Zapiš do TCON 010000 0100 000d dddd dddd 1111 1111 1111 1111
Přečti TCON 010011 0100 110d dddd dddd 1111 111d dddd dddd
Přečti Status Registr 010111 0101 110d dddd dddd 1111 111d dddd dddd

 

Celý zdrojový kód naleznete ke stažení ZDE. Je rozsáhlejší a nemá smysl ho prezentovat v celku. Příklad je testován na Atmega16A s taktovacím kmitočtem 8 MHz, ale bez úprav půjde nejspíš přenést na libovolný Atmel s SPI rozhraním. Z tab. 1 a tab. 2 by vám mělo být jasné, jakou zprávu chceme potenciometru posílat a případně, jakou odpověď od něj očekávat. Tyto informace by vám obecně měly stačit proto, abyste mohli napsat sadu funkcí, které odesílají vybrané příkazy. Což je úkol, který budete provádět často. Ať už budete psát program k ovládání ADC, nějakého čidla nebo motorového driveru, tak skoro vždycky napíšete sadu funkcí, kterou pak budete v rámci programu volat. Většinou nebudete využívat všechny možnosti obvodu, takže se nebudete obtěžovat s programováním všech funkcí. Kdybych chtěl potenciometr ovládat tlačítky, stačili by mi funkce mcp4251_increment(), mcp4251_decrement() a mcp4251_write_tcon() a se zbytkem bych se nemusel zdržovat. Já jsem připravil takovou sadu základních funkcí. Přirozeně hlavně proto, abyste si prohlédli, jak se zachází s SPI a jak se takový “ovladač” může programovat.

  • mcp4251_set() – nastaví hodnotu jezdce. První argument vybere jeden ze dvou potenciometrů, druhý argument je poloha jezdce, funkce vrací 0 při úspěšném provedení
  • mcp4251_read() – přečte polohy obou jezdců. Dva argumenty jsou ukazatele na proměnné, kam se mají polohy uložit, funkce vrací 0 při úspěšném provedení
  • mcp4251_increment() – inkrementuje jezdec. Argumentem se vybírá jeden ze dvou potenciometrů, funkce vrací 0 při úspěšném provedení
  • mcp4251_decrement() – dekrementuje jezdec. Argumentem se vybírá jeden ze dvou potenciometrů, funkce vrací 0 při úspěšném provedení
  • mcp4251_status() – přečte hodnotu status registru a funkce vrátí 1, pokud je MCP z vnějšku vypnuté (pinem SHDN)
  • mcp4251_write_tcon() – zapisuje hodnotu do TCON registru, funkce vrací 0 při úspěšném provedení
  • mcp4251_read_tcon() – přečte a funkce vrátí hodnotu TCON registru

Komentovat všechny funkce by bylo zbytečné. Podíváme se pouze na mcp4251_set(). Zbylé funkce si můžete projít a zkusit pochopit jak pracují.

 

001: // wiper 0 nebo 1, value 0–256, vrací 0
002: // při úspěšném zápisu
003: char mcp4251_set(char wiper, unsigned int value){
004: char tmp;
005: // aktivuj slave obvod
006: CS_L;
007: if(wiper == 0){
008: // příkaz zápisu jezdce 0 (s 9.bitem hodnoty)
009: SPDR = 0x00 | (char)((value>>8) & 0b1);
010: }
011: else{
012: // příkaz zápisu jezdce 1 (s 9.bitem hodnoty)
013: SPDR = 0x10 | (char)((value>>8) & 0b1);
014: }
015: // počkej na dokončení přenosu 1.byte
016: while(!(SPSR & (1<<SPIF)));
017: // přečti odpověď potenciometru
018: tmp = SPDR;
019: // pokud je příkaz chybný,
020: // (CMDERR v nule), ukonči vysílání
021: if(!(tmp & 0b10)){CS_H; return 1;}
022: // odešli dolní byte hodnoty
023: SPDR = (char)(value);
024: // počkej na dokončení přenosu 2.byte
025: while(!(SPSR & (1<<SPIF)));
026: // deaktivuj slave obvod
027: CS_H;
028: return 0; // vše proběhlo v pořádku
029: }
Vzorové zdrojové kódy slouží pouze k jednoduché demonstraci funkce, proto často obsahují globální proměnné, neobsahují většinou ukazatele, dále také neobsahují kontroly proměnných, definování nevyužitých pinů mikrokontroléru a podobné správné programátorské návyky. Proto je nutné tyto kódy brát s patřičnou rezervou.
 

První nedostatek, který vás může napadnout je to, že funkce nemá ošetřené vstupy, ale ona je ošetřeny v podstatě má. V prvním argumentu byste měli funkci sdělit, kterému ze dvou potenciometrů chcete nastavovat polohu jezdce. Předhodíte-li jí nulu, zapíše hodnotu pro jezdec nula, při jakékoli jiné hodnotě zapíše hodnotu do jezdce jedna. V druhém argumentu máte funkci předat hodnotu jezdce z rozsahu 0 – 256. Na to přirozeně nestačí typ char a musí se použít integer. Teoreticky si můžete dovolit vložit do funkce hodnotu větší jak 256, protože se před odesláním ořízne, ale pak asi budete mít guláš v tom, co odeslala. Ihned po vstupu do funkce se pomocí CS pinu aktivuje potenciometr. Celý přenos bude šestnáctibitový, takže bude potřeba odeslat dvakrát osm bitů. Podmínka rozhodne o tom, který kód povelu se má odeslat. Po přenesení horního bytu si přečteme bit ERR v odpovědi z potenciometru. Ten by nás mohl informovat o chybném příkazu. Pokud by tento bit byl nulový, tak nastala někde chyba a ukončíme komunikaci. Pokud je vše v pořádku, tak odešleme zbytek zprávy a pak už jen deaktivujeme komunikaci s potenciometrem (CS nastavíme do log. 1). Pominu-li maskování a bitové operace při sestavování prvního bytu zprávy, tak je funkce naprosto jasná.

Makra v úvodu programu využijete při ovládání TCON registru. Ale to už bych zbytečně odbočoval od tématu SPI. Doufám, že jste si udělali hrubou představu o tom, co vás čeká, až si budete psát vlastní ovladač. V závěru přidám ještě jednu radu. Nesnažte se celý kód psát najednou. Najděte si vždy nejjednodušší možný příkaz, pomocí kterého dokážete poznat, že vám zařízení rozumí. Protože na 90% uděláte na poprvé někde chybu. Pošlete do zařízení nějaký nesmysl, ono nic neudělá a vy budete muset znovu pročíst datasheet a zjistit co jste si špatně vyložili. Taky vám doporučuji každou funkci trpělivě otestovat. Protože pak se můžete spolehnout na to, že fungují a můžete s klidem zapomenout všechny detaily. Bohužel ne vždycky budete mít dost klidu a času to dělat poctivě :D

 
Obr. 2: Ukázka komunikace s MCP4251. Potenciometr přijal příkaz nastavit jezdec 0 na hodnotu 0xB4. Odpověděl samými jedničkami (takže v ERR bitu nehlásil chybu). Na žlutém průběhu vidíte, že okamžitě po přijetí příkazu potenciometr zareagoval a posunul hodnotu jezdce.

Obr. 2: Ukázka komunikace s MCP4251. Potenciometr přijal příkaz nastavit jezdec 0 na hodnotu 0xB4. Odpověděl samými jedničkami (takže v ERR bitu nehlásil chybu). Na žlutém průběhu vidíte, že okamžitě po přijetí příkazu potenciometr zareagoval a posunul hodnotu jezdce.

 
 
Autor: Michal Dudka
 
 

Jiné příspěvky v kategorii:

 
SPI u AVR 1. Díl – Teorie
SPI u AVR 2. Díl – Praktické příklady I.

Tajned facebook
 

Za případné chyby v textu, ve zdrojovém kódě, nebo ve schématickém zapojení se omlouváme.
AUTOŘI NEBEROU ŽÁDNOU ODPOVĚDNOST ZA PŘÍPADNÉ ÚJMY NA ZDRAVÍ ČI MAJETKU.