logo_elektromys.eu

/ GPIO I |

/ Možnosti GPIO |

GPIO (General Purpose Input Output), jinak též PORTy nebo piny jsou končetiny mikrokontrolérů. Je to prakticky jediný způsob jak mohou interagovat se světem a je tedy určitě na místě podívat se jak se na moderních AVR ovládají. Zdá se že návrháři Atmelu převzali porty z řady Xmega a rozhodně to byl krok dobrý směrem. Většina z vás si jistě pamatuje že na starých AVR nebylo možné porty ovládat atomickými operacemi. Jinak řečeno když jste chtěli změnit stav jednoho pinu, museli jste přečíst stav celého portu, upravit ho a zase na celý port upravenou hodnotu zapsat. Při tom všem vám hrozilo riziko, že to špatně dopadne pokud během operace přijde přerušení, které bude také měnit něco na portu. Celá tato komplikace u nových AVR odpadá. Nastavování, nulování a přepínání stavu libovolného pinu je atomické. Navíc je každý pin vybaven schopností "input sense", tedy schopností sledovat vybranou událost (sestupnou hranu, vzestupnou hranu atd.) a případně volat přerušení (výrazně dokonalejší obdoba funkce PCINT, kterou znáte ze starých AVR). Krom klasické možnosti zapínat pull-up rezistory, můžete také libovolné piny invertovat a to jak v roli vstupů tak v roli výstupů. Pojďme se tedy podívat pomocí jakých registrů se porty řídí.

/ Ovládání GPIO |

K ovládání slouží 18 registrů. Neděste se toho čísla, registry mají jasný a zřetelný účel a s jejich používáním nebudete mít problémy. Organizace je podobná jako u starých AVR s tím rozdílem, že přibyly registry pro atomické operace jako (SET - nastavit do log.1, CLR - vynulovat do log.0, TGL - přepnout hodnotu). Myslím, že krátký komentář ke každému registru vše osvětlí:

Pro jistotu ještě zrekapituluji, že k registrům lze přistupovat dvěma způsoby a to buď pomocí struktury například: PORTB.DIRSET = PIN3_bm; nebo pomocí makra PORTB_DIRSET =PIN3_bm;. Nejprve jde vždy jméno portu (tedy název periferie) a pak název registru. Makra PINx_bm můžete "ořit" a tvořit tak masky pro více pinů (například PIN3_bm | PIN4_bm ). Registry SET,CLR a TGL slouží jen k zápisu. Nelze z nich nic rozumného číst a jejich jediným úkolem je manipulovat s obsahem registrů DIR a OUT. No a teď vystoupíme z nudné teorie a zkusíme něco prakticky.

/ Příklad |

V demonstračním příkladu (schema níže) si zkusíme tři užitečné manipulace s porty. Rozebereme si je popořadě:

Zapojení demonstračního příkladu
/* tutorial TinyAVR 1-Series 
* GPIO 1 - LED, tlačítko a otevřený kolektor
* zapojení:
* LED na PB3 připojená proti GND
* Na PA6 pullup rezistor proti 2.5V (napětí 2.5V realizováno obvodem TL431)
* Tlačítko proti GND na PB4
*/


/* Ve fuses zvolen 20MHz oscilátor (lze volit ještě 16MHz) */
#define F_CPU 20000000UL
#include <avr/io.h>

// "běžné" ovládání výstupu (PB3) - LEDka připojená proti GND
#define LED_ON  PORTB.OUTSET = PIN3_bm
#define LED_OFF PORTB.OUTCLR = PIN3_bm

// ovládání výstupu PA6 s otevřeným kolektorem (externí pull-up na vybrané napětí)
#define OUT_HIGH PORTA.DIRCLR = PIN6_bm // PA6 vstup (externí pullup vytváří log.1)
#define OUT_LOW  PORTA.DIRSET = PIN6_bm // PA6 výstup (na portu log.0)

// čtení stavu tlačítka (PB4)
#define TLACITKO_STISKNUTO (!(PORTB.IN & PIN4_bm))


void clock_20MHz(void);
void gpio_init(void);

int main(void){
 clock_20MHz(); // taktujeme na 20MHz
 gpio_init(); // konfigurace pinů
 
 while (1){
  // při stisku tlačítka rozsvítíme LED
  // a vytvoříme log.1 na výstupu s otevřeným kolektorem
  // jinak zhasneme LED a na výstupu uděláme log.0
  if(TLACITKO_STISKNUTO){
   LED_ON;
   OUT_HIGH;
  }else{
   LED_OFF;
   OUT_LOW;
  }
 }
}

void gpio_init(void){
 // LEDka (PB3)
 PORTB.DIRSET = PIN3_bm; // PB3 výstup
 
 // výstup s otevřeným kolektorem (PA6)
 PORTA.DIRCLR = PIN6_bm; // PA6 vstup (bude se měnit)
 PORTA.OUTCLR = PIN6_bm; // Na PA6 log.0 (nebude se měnit)
 
 // Vstup tlačítka (PB4)
 PORTB.DIRCLR = PIN4_bm; // PB4 je vstup
 PORTB.PIN4CTRL = PORT_PULLUPEN_bm; // Zapíná vnitřní pull-up rezistor na PB4
}



// Nastaví clock 20MHz (z interního 20MHz bez děličky)
void clock_20MHz(void){
 // v případě potřeby zde dočasně vypněte přerušení
 CCP = CCP_IOREG_gc; // odemyká zápis do chráněného registru
 CLKCTRL.MCLKCTRLA =  CLKCTRL_CLKSEL_OSC20M_gc; // vypouští clock na PB5 (CLKOUT), vybírá 20MHz oscilátor
 CCP = CCP_IOREG_gc; // odemyká zápis do chráněného registru
 CLKCTRL.MCLKCTRLB = 0; // vypne prescaler (děličku)
}

Závěrem bych rád ještě okomentoval takovou drobnost. Podíváte-li se na oscilogramy, uvidíte že vzestupná i sestupná hrana na PB3 (tedy na LEDce a tedy na výstupu Atmelu) je ostrá. Kdežto na výstupu s otevřeným kolektorem je vzestupná hrana "tupá". Je to dáno tím že log.1 se na linku přivádí skrze pull-up rezistor, který nabíjí parazitní kapacity. Čím je tedy odpor menší, tím rychleji k nabíjení dochází a tím je hrana ostřejší. Žlutý průběh odpovídá pull-up rezistoru 2k2 a zelený průběh rezistoru 10k. V tomto případě jde o zlomky us, takže vás to nemusí pálit, ale mějte to na paměti až budete signál posílat skrze dlouhý kabel s nezanedbatelnou kapacitou.

Ihned po stisknutí tlačítka jdou oba výstupy do log.1. Modrý průběh je napětí na výstupu s LED. Žlutý průběh je výstup s otevřeným kolektorem. Log.1 je tam opravdu 2.5V, všimněte si také pomalejší doby náběhu. Zelený průběh odpovídá pullup rezistoru 10k, žlutý 2k2.
Ihned po uvolnění tlačítka jdou oba výstupy do log.0. Modrý průběh je napětí na výstupu s LED. Žlutý průběh je výstup s otevřeným kolektorem.

| Závěr /

Chápu že tenhle tutoriál nebyl zrovna nejšťavnatější sousto ale bez portů by to nešlo. Doufám že budu mít dost času abychom se mohli setkat u dalších dílů. Určitě se portům ještě minimálně jednou vrátíme v souvislostech s externím přerušením nebo event systémem.

| Odkazy /

Home
| V1.0 19.12.2018 /
| By Michal Dudka (m.dudka@seznam.cz) /