4. I/O Porty

Jednotlivé vývody mikrokontroleru jsou řízeny pomocí portů. Každý port může mít až osm vstupně-výstupních pinů (vývodů). Podle počtu vývodů, kterými MCU disponuje, jsou jednotlivé porty značeny písmeny A, B, C atd., například PORTA, PORTB. Jednotlivé vývody daných portů pak PA1, PB2, PC6 atd. V případě ATtiny3217 máme k dispozici 20 (pouzdro SOIC) nebo 22 (pouzdro VQFN) vstupně-výstupních pinů.

soic
Obrázek 12. Vývody pouzdra VQFN
vqfn
Obrázek 13. Vývody pouzdra SOIC

Všechny vývody MCU (kromě napájecích) mohou fungovat jako vstup nebo jako výstup. Dokážou tedy zpracovávat logické úrovně v rozsahu napájecího napětí 0 až 5 V. Kromě zpracování digitálních hodnot plní téměř všechny vývody další alternativní funkci. Slouží tedy jako vstupy nebo výstupy různých periferií, jako jsou USART, PWM a další. O těchto alternativních funkcích si povíme v některé z dalších kapitol. Nyní se zaměříme pouze na zpracování digitálních hodnot.

Protože v tomto seriálu používám desku Curiosity, zajímá nás dostupnost jednotlivých pinů na této desce. Rozmístění vývodů desky Curiosity můžete vidět na následujícím obrázku.

curiosity
Obrázek 14. Vývody Curiosity Nano

Pojďme se nyní podívat na zapojení jednotlivých vývodů. Každý I/O pin obsahuje vstupní a výstupní driver, které jsou nezávislé na nastavení ostatních pinů. Taktéž obsahuje obvod pro nastavení pull-up rezistorů. Další velice užitečnou funkcionalitou je možnost generovat přerušení při určitém stavu na daném vývodu.

pin
Obrázek 15. Blokové schéma jednoho vývodu

4.1. Nastavení vývodů

Po resetu obvodu jsou I/O vývody MCU nastaveny jako vstupy, přerušení a pull-up rezistory jsou zakázány. Je tedy na programátorovi, aby jednotlivé vývody nastavil do požadované konfigurace. Na začátku programu je tedy potřeba provést tuto sekvenci:

  • nastavit jednotlivé piny jako vstup nebo výstup; registr DIR, DIRSET, DIRCLR, DIRTGL

  • zajistit na vývodech výchozí logické úrovně (v případě výstupů); registr OUT, OUTSET, OUTCLR, OUTTGL;

  • připojit pull-up rezistory, pokud je potřeba (v případě vstupů); registr PINnCTRL;

  • povolit přerušení, bude-li využíváno; registr PINnCTRL;

Tato nastavení lze provádět pomocí registrů daného PORTu. Každý PORTx (x zastupuje A, B, C, atd.) disponuje svou vlastní skupinou registrů, pomocí kterých ovládá svou skupinu pinů. PORTx je instancí struktury typu PORT_t, která obsahuje všechny registry a má následující definici.

/* I/O Ports */
typedef struct PORT_struct
{
    register8_t DIR;                /* Data Direction */
    register8_t DIRSET;             /* Data Direction Set */
    register8_t DIRCLR;             /* Data Direction Clear */
    register8_t DIRTGL;             /* Data Direction Toggle */
    register8_t OUT;                /* Output Value */
    register8_t OUTSET;             /* Output Value Set */
    register8_t OUTCLR;             /* Output Value Clear */
    register8_t OUTTGL;             /* Output Value Toggle */
    register8_t IN;                 /* Input Value */
    register8_t INTFLAGS;           /* Interrupt Flags */
    register8_t reserved_1[6];
    register8_t PIN0CTRL;           /* Pin 0 Control */
    register8_t PIN1CTRL;           /* Pin 1 Control */
    register8_t PIN2CTRL;           /* Pin 2 Control */
    register8_t PIN3CTRL;           /* Pin 3 Control */
    register8_t PIN4CTRL;           /* Pin 4 Control */
    register8_t PIN5CTRL;           /* Pin 5 Control */
    register8_t PIN6CTRL;           /* Pin 6 Control */
    register8_t PIN7CTRL;           /* Pin 7 Control */
    register8_t reserved_2[8];
} PORT_t;

Z výše uvedeného výpisu je vidět, že se vývojáři z Microchipu celkem rozvášnili a nabízejí nám vcelku široké možnosti řízení vstupů a výstupů mikrokontroleru. Bez některých registrů bychom se asi v mnoha případech obešli, protože ve finále vykonávají tu samou činnost, ale když už je máme k dispozici, tak si v následující sekci popíšeme, co jednotlivé registry dělají a kdy je použít. Jen pro úplnost ještě doplním, že každý pin portu koresponduje s odpovídajícím bitem v registru, například vývod PA2 bude řízen portem PORTA a bitem 2, vývod PB5 bude řízen portem PORTB a bitem 5, pin PC0 je ovládán portem PORTC a bitem 0 atd.

Při práci s registry (nejen s registry portů) můžete využít předdefinovaná makra nebo výčty (enum). Názvy těchto položek mají příponu _bm, _bp, _gm, _gc nebo _gp a jejich význam naleznete níže. Úplný výpis pro obvod ATtiny3217 pak naleznete v hlavičkovém souboru iotn3217.h. Pokud nevíte, kde se tento soubor ve vašem počítači nachází, vložte do svého zdrojového kódu dočasně tento řádek PORTB.DIRSET = PIN5_bm;. Poté klikněte pravým tlačítkem myši na PIN5_bm a zvolte Navigate → Go to Declaration/Definition.
  • _bm (Bit Mask) Maskování konkrétního bitu, například:

    #define PORT_INT_4_bm  (1<<4)  /* Pin Interrupt bit 4 mask. */
    #define PIN3_bm 0x08
  • _bp (Bit Position) Pozice bitu v registru, například:

    #define PORT_INT_7_bp  7
    #define PORT_PULLUPEN_bp  3  /* Pullup enable bit position. */
  • _gm (Group Mask) Maska pro skupinu bitů, například:

    #define RTC_PRESCALER_gm  0x78  /* Prescaling Factor group mask. */
    #define TCB_CNTMODE_gm  0x07  /* Timer Mode group mask. */
  • _gp (Group Position) Pozice skupiny bitů, například:

    #define RTC_PERIOD_gp  3  /* Period group position. */
    #define SLPCTRL_SMODE_gp  1  /* Sleep mode group position. */
  • _gc (Group Config) Nastavení skupiny bitů, například:

    /* Interrupt Mode select */
    typedef enum AC_INTMODE_enum
    {
        AC_INTMODE_BOTHEDGE_gc = (0x00<<4),  /* Any Edge */
        AC_INTMODE_NEGEDGE_gc = (0x02<<4),  /* Negative Edge */
        AC_INTMODE_POSEDGE_gc = (0x03<<4)  /* Positive Edge */
    } AC_INTMODE_t;

4.2. Registry pro I/O

Registr DIR - Data Direction

Detail

bit_7

bit_6

bit_5

bit_4

bit_3

bit_2

bit_1

bit_0

DIR7

DIR6

DIR5

DIR4

DIR3

DIR2

DIR1

DIR0

R/W - 0

R/W - 0

R/W - 0

R/W - 0

R/W - 0

R/W - 0

R/W - 0

R/W - 0

Tabulka 12. Význam jednotlivých bitů registru DIR
bit Symbol Význam bitu Popis

b7 až b0

DIR[7:0]

Řízení směru vývodu

0:   nastaví Pxn jako vstup; výstupní driver je zakázán
1:   nastaví Pxn jako výstup; výstupní driver je povolen;

PORTA.DIR = 0x03;               //PA0, PA1 výstup; zbytek vstup
PORTB.DIR |= PIN7_bm;           //PB7 výstup; zbytek nezměněn
PORTB.DIR = PIN7_bm;            //PB7 výstup; zbytek vstup
PORTA.DIR |= PIN6_bm | PIN4_bm; //PA6, PA4 výstup; zbytek nezměněn

Registr DIRSET - Data Direction Set

Detail

bit_7

bit_6

bit_5

bit_4

bit_3

bit_2

bit_1

bit_0

DIRSET7

DIRSET6

DIRSET5

DIRSET4

DIRSET3

DIRSET2

DIRSET1

DIRSET0

R/W - 0

R/W - 0

R/W - 0

R/W - 0

R/W - 0

R/W - 0

R/W - 0

R/W - 0

Tabulka 13. Význam jednotlivých bitů registru DIRSET
bit Symbol Význam bitu Popis

b7 až b0

DIRSET[7:0]

Nastavý vývod jako výstup bez použití read-modify-write operace

0:   žádný efekt;
1:   nastaví odpovídající bit v registru PORTx.DIR; vývod bude výstup;

PORTA.DIRSET = 0x03;        //PA0, PA1 výstup; zbytek nežměněn
PORTA.DIRSET = PIN6_bm;     //PA6 výstup; zbytek nezměněn
PORTA.DIRSET |= PIN6_bm;    //PA6 výstup; zbytek nezměněn
PORTB.DIRSET = PIN6_bp;     //PB2, PB1 výstup; zbytek nezměněn
                            //POZOR na možnou chybu, PIN6_bp odpovídá
                            //číslu 6, tedy 0000 0110 binárně

Registr DIRCLR - Data Direction Clear

Detail

bit_7

bit_6

bit_5

bit_4

bit_3

bit_2

bit_1

bit_0

DIRCLR7

DIRCLR6

DIRCLR5

DIRCLR4

DIRCLR3

DIRCLR2

DIRCLR1

DIRCLR0

R/W - 0

R/W - 0

R/W - 0

R/W - 0

R/W - 0

R/W - 0

R/W - 0

R/W - 0

Tabulka 14. Význam jednotlivých bitů registru DIRCLR
bit Symbol Význam bitu Popis

b7 až b0

DIRCLR[7:0]

Nastavý vývod jako vstup bez použití read-modify-write operace

0:   žádný efekt;
1:   vynuluje odpovídající bit v registru PORTx.DIR; vývod bude vstup;

PORTC.DIRCLR = 0x00;        //žádný efekt
PORTB.DIRCLR = 0b00010010;  //PB4, PB1 vstup; zbytek nezměněn
PORTA.DIRCLR = PIN2_bm;     //PA2 vstup; zbytek nezměněn

Registr DIRTGL - Data Direction Toggle

Detail

bit_7

bit_6

bit_5

bit_4

bit_3

bit_2

bit_1

bit_0

DIRTGL7

DIRTGL6

DIRTGL5

DIRTGL4

DIRTGL3

DIRTGL2

DIRTGL1

DIRTGL0

R/W - 0

R/W - 0

R/W - 0

R/W - 0

R/W - 0

R/W - 0

R/W - 0

R/W - 0

Tabulka 15. Význam jednotlivých bitů registru DIRTGL
bit Symbol Význam bitu Popis

b7 až b0

DIRTGL[7:0]

Přepne výstupní driver vývod bez použití read-modify-write operace

0:   žádný efekt;
1:   zneguje odpovídající bit v registru PORTx.DIR; pokud byl vývod konfigurován jako vstup, bude nyný konfigurován jako výstup a naopak;

PORTB.DIRTGL = PIN3_bm;     //PB3 přepne na opačný směr
PORTB.DIRTGL = 0xFF;        //změna směru celého PORTB
PORTB.DIRTGL |= PIN2_bm;    //změna směru PB2

Registr OUT - Output Value

Detail

bit_7

bit_6

bit_5

bit_4

bit_3

bit_2

bit_1

bit_0

OUT7

OUT6

OUT5

OUT4

OUT3

OUT2

OUT1

OUT0

R/W - 0

R/W - 0

R/W - 0

R/W - 0

R/W - 0

R/W - 0

R/W - 0

R/W - 0

Tabulka 16. Význam jednotlivých bitů registru OUT
bit Symbol Význam bitu Popis

b7 až b0

OUT[7:0]

Nastaví logickou úroveň na výstupním pinu

0:   výstup bude mít úroveň log. 0;
1:   výstup bude mít úrpveň log. 1;
zápis do tohoto registr bude mít efekt pouze pokud bude registr PORTx.DIR konfigurován jako výstup;

PORTA.OUT = 0xFE;           //PA0 == 1; zbytek == 0
PORTB.OUT |= PIN3_bm;       //PB3 == 1; zbytek nezměněn
PORTB.OUT &= 0b00001100;    //PB3, PB2 nezměněn; zbytek == 0
PORTA.OUT é PIN2_bm;        //PA2 == 1; zbytek == 0

Registr OUTSET - Output Value Set

Detail

bit_7

bit_6

bit_5

bit_4

bit_3

bit_2

bit_1

bit_0

OUTSET7

OUTSET

OUTSET5

OUTSET4

OUTSET3

OUTSET2

OUTSET1

OUTSET0

R/W - 0

R/W - 0

R/W - 0

R/W - 0

R/W - 0

R/W - 0

R/W - 0

R/W - 0

Tabulka 17. Význam jednotlivých bitů registru OUTSET
bit Symbol Význam bitu Popis

b7 až b0

OUTSET[7:0]

Nastaví logickou úroveň na výstupním pinu bez použití read-modify-write operace

0:   žádný efekt;
1:   výstup bude mít úrpveň log. 1;
zápis do tohoto registr bude mít efekt pouze pokud bude registr PORTx.DIR konfigurován jako výstup;
čtením bitů tohoto registru obdržíma hodnoty registru PORTx.OUT;

PORTA.OUTSET = PIN4_bm | PIN3_bm;   //PA4, PA3 == 1; zbytek nezměněn
PORTA.OUTSET = 3;                   //PA0, PA1 == 1; zbytek nezměněn
PORTA.OUTSET |= 0xA6;               //PA7, PA5, PA2, PA1 == 1; zbytek nezměněn

Registr OUTCLR - Output Value Clear

Detail

bit_7

bit_6

bit_5

bit_4

bit_3

bit_2

bit_1

bit_0

OUTCLR7

OUTCLR

OUTCLR5

OUTCLR4

OUTCLR3

OUTCLR2

OUTCLR1

OUTCLR0

R/W - 0

R/W - 0

R/W - 0

R/W - 0

R/W - 0

R/W - 0

R/W - 0

R/W - 0

Tabulka 18. Význam jednotlivých bitů registru OUTCLR
bit Symbol Význam bitu Popis

b7 až b0

OUTCLR[7:0]

Resetuje logickou úroveň na výstupním pinu bez použití read-modify-write operace

0:   žádný efekt;
1:   výstup bude mít úrpveň log. 0;
zápis do tohoto registr bude mít efekt pouze pokud bude registr PORTx.DIR konfigurován jako výstup;
čtením bitů tohoto registru obdržíma hodnoty registru PORTx.OUT;

PORTB.OUTCLR = 0x25;    //PB5, PB2, PB0 == 0; zbytek nezměněn
PORTB.OUTCLR = PIN3_bm; //PB3 == 0; zbytek nezměněn
PORTB.OUTCLR = 25;      //PB4, PB3, PB0 == 0; zbytek nezměněn

Registr OUTTGL - Output Value Toggle

Detail

bit_7

bit_6

bit_5

bit_4

bit_3

bit_2

bit_1

bit_0

OUTTGL7

OUTTGL6

OUTTGL5

OUTTGL4

OUTTGL3

OUTTGL2

OUTTGL1

OUTTGL0

R/W - 0

R/W - 0

R/W - 0

R/W - 0

R/W - 0

R/W - 0

R/W - 0

R/W - 0

Tabulka 19. Význam jednotlivých bitů registru OUTTGL
bit Symbol Význam bitu Popis

b7 až b0

OUTTGL[7:0]

Přepne logickou úroveň na výstupním pinu bez použití read-modify-write operace

0:   žádný efekt;
1:   výstup bude mít negovanou log. úroveň;
zápis do tohoto registr bude mít efekt pouze pokud bude registr PORTx.DIR konfigurován jako výstup;
čtením bitů tohoto registru obdržíma hodnoty registru PORTx.OUT;

PORTA.OUTTGL = 0xFF;        //negace celého PORTA
PORTA.OUTTGL = PIN6_bm;     //negace PA6
PORTA.OUTTGL |= PIN3_bm;    //negace PA3

Registr IN - Input Value

Detail

bit_7

bit_6

bit_5

bit_4

bit_3

bit_2

bit_1

bit_0

IN7

IN6

IN5

IN4

IN3

IN2

IN1

IN0

R/W - 0

R/W - 0

R/W - 0

R/W - 0

R/W - 0

R/W - 0

R/W - 0

R/W - 0

Tabulka 20. Význam jednotlivých bitů registru IN
bit Symbol Význam bitu Popis

b7 až b0

OUTTGL[7:0]

Obsahuje logickou úroveň odpivídající hodnotě na fyzickém pinu

zápis:
0:   žádný efekt;
1:   pokud bude pin konfigurován jako vstup provede negaci bitu v registru; PORTx.OUT
čtení:
0:   hodnota vstupního pinu je log. 0;
1:   hodnota vstupního pinu je log. 1;

int prom = PORTB.IN;       //prom bude obsahovat hodnotu celého PORTB
if(PORTB.IN & PIN3_bm)     //TRUE pokud PB3 == 1
if(PORTB.IN & 0x80)        //TRUE pokud PB7 == 1

Registr INTFLAGS - Interrupt Flags

Detail

bit_7

bit_6

bit_5

bit_4

bit_3

bit_2

bit_1

bit_0

INT7

INT6

INT5

INT4

INT3

INT2

INT1

INT0

R/W - 0

R/W - 0

R/W - 0

R/W - 0

R/W - 0

R/W - 0

R/W - 0

R/W - 0

Tabulka 21. Význam jednotlivých bitů registru INTFLAGS
bit Symbol Význam bitu Popis

b7 až b0

INT[7:0]

Obsahuje příznak přerušení, který vznkl na odpovídajícím pinu

zápis:
0:   žádný efekt;
1:   vynuluje příznak přerušení;
čtení:
0:   událost pro přerušení nenastala;
1:   nastala údálost umožňující přerušení;

if(PORTB.INTFLAGS & PORT_INT_7_bm)  //TRUE pokud nastalo přerušení na PB7
if(PORTB.INTFLAGS & 0x02)           //TRUE pokud nastalo přerušení na PB1
PORTB.INTFLAGS = PORT_INT_gm;       //smaže všechny příznaky přerušení,
                                    //které nastaly na PORTB

Registr PINnCTRL - Pin n Control

(Tento registr nastavuje jeden pin portu)

Detail

bit_7

bit_6

bit_5

bit_4

bit_3

bit_2

bit_1

bit_0

INVEN

PULLUPEN

ISC2

ISC1

ISC0

R/W - 0

R/W - 0

R/W - 0

R/W - 0

R/W - 0

Tabulka 22. Význam jednotlivých bitů registru PINnCTRL
bit Symbol Význam bitu Popis

b7

INVEN

Povoluje invertování vstupní nebo výstupní log. úroveň pinu

0:   hodnoty nebudou invertovány;
1:   hodnoty budou invertovány;

b3

PULLUPEN

Nastavení pull-up rezistorů

0:   pull-up zakázány;
1: &nbsp: pull-up povoleny;

b2 až b0

ISC[2:0]

Nastavení události způsobující přerušení

000:   přerušení zakázáno; vstupní buffer povolen (INTDISABLE);
001:   přerušení povoleno při obou hranách vstupního signálu (BOTHEDGES);
010: &nbsp: přerušení povoleno při náběžné hraně vstupního signálu (RISING);
011:   přerušení povoleno při sestupné hraně vstupního signálu (FALLING);
100:   přerušení a vstupní buffer zakázán (INPUT_DISABLE);
101:   přerušení při log. 0 vstupního signálu (LEVEL);

PORTA.PIN3CTRL = PORT_PULLUPEN_bm;      //pull-up na PA3 povolen
PORTB.PIN2CTRL = PORT_INVEN_bm;         //hodnoty na PB2 budou invertovány
PORTB.PIN5CTRL = 0x0A;                  //pull-up a přerušení při náběžné hraně
                                        //na PB5 povoleno

4.3. Virtuální porty pro I/O

Virtuální porty VPORTx plní stejnou funkci jako klasické porty PORTx. Jejich výhodou je, že provádění instrukcí nad těmito porty je rychlejší. Virtuální porty obsahují pouze čtyři registry: DIR, OUT, IN, INTFLAGS. Význam jednotlivých bitů registrů je naprosto stejný a nemá cenu je zde popisovat. Ukážeme si jen několik příkladů, ale i práce s virtuálními porty je stejná jako s klasickými porty, jen u názvu portu přibude písmeno "V".

VPORTB.DIR = 0xF0;          ekvivalent      PORTB.DIR = 0xF0;
VPORTA.OUT |= (1 << 5);     ekvivalent      PORTA.OUT |= (1 << 5);
VPORTB.OUT &= ~(1 << 4);    ekvivalent      PORTB.OUT &= ~(1 << 4);
if(!(VPORTA.IN & 0x04))     ekvivalent      if(!(PORTA.IN & 0x04))

4.4. Příklad: LED displej

V tomto příkladu si ukážeme ovládání vývodů MCU. Program bude jednoduchý, každým stisknutím tlačítka SW0 inkrementujeme proměnnou. Počet stisků, tedy obsah proměnné, budeme zobrazovat na čtyřmístném sedmisegmentovém LED displeji CC56-12EWA.

display
Obrázek 16. Čtyřmístný LED displej CC56-12EWA

Displej budeme řídit v tzv. multiplexním režimu. Schéma zapojení je na níže uvedeném obrázku.

led display
Obrázek 17. Schéma zapojení obvodu s LED displejem

Anody jednotlivých segmentů jsou kromě segmentu c připojeny na PORTA a katody pak na spodní bity portu C. Ze schématu je vidět, jak jsou anody zapojeny. Vždy je lepší, když jsou anody zapojeny na jeden port, ale v tomto případě máme jeden segment připojen na jiný port, s čímž si poradíme. Na pořadí jednotlivých segmentů příliš nezáleží. Vytvoříme si totiž převodní tabulku, kterou poté aplikujeme v programu.

Tabulka 23. Převodní tabulka LED displeje

g

f

e

d

a

b

c

segment

pozice

PA7

PA6

PA5

PA4

PA3

PA2

PA1

PC4

pin

0

0

1

1

1

1

1

1

1

0x7F

1

0

0

0

0

1

0

1

1

0x0B

2

1

0

1

1

1

1

1

0

0xBE

3

1

0

0

1

1

1

1

1

0x9F

4

1

1

0

0

1

0

1

1

0xCB

5

1

1

0

1

1

1

0

1

0xDD

6

1

1

1

1

1

1

0

1

0xFD

7

0

0

0

0

1

1

1

1

0x0F

8

1

1

1

1

1

1

1

1

0xFF

9

1

1

0

1

1

1

1

1

0xDF

Jelikož se jedná o displej se společnou katodou, musíme pro rozsvícení cifry na odpovídajících vývodech PORTA nastavovat logické jedničky. Vývod PA3 není využit pro řízení displeje, ale je na něj připojena interní LED0 desky Curiosity. Je tedy vždy nastaven na logickou jedničku, aby zbytečně nesvítila (LED0 je rozsvěcována logickou nulou, viz schéma zapojení desky Curiosity).

#define F_CPU 3333333UL                         //20MHz/6
#include <avr/io.h>
#include <util/delay.h>

void zobraz(unsigned int cislo);
const uint8_t kod[10] = {0x7f,0x0b,0xbe,0x9f,0xcb,0xdd,0xfd,0x0f,0xff,0xdf};

int main(void) {
    PORTA.DIR = 0xFF;                        //PORTA výstup
    PORTB.PIN7CTRL |= PORT_PULLUPEN_bm;      //pull-up pro tlačítko
    PORTC.DIR = 0x1F;
    PORTC.OUT = 0x1F;

    unsigned int pocitadlo = 0;

    while (1) {
        if(!(PORTB.IN & PIN7_bm)) {          //tlačítko stisknuto
            if(++pocitadlo > 9999){
                pocitadlo = 0;
            }
            _delay_ms(2);
            while(!(PORTB.IN & PIN7_bm)){    //čeká na uvolnění tlačítka
                zobraz(pocitadlo);
            }
        }
        zobraz(pocitadlo);
    }
}

//zobrazí 4místné číslo na LED displeji
void zobraz(unsigned int cislo){
    uint8_t hodnota = 0;
    for(int i = 0; i < 4; i++){
        hodnota = kod[cislo%10];             //získání jednotek
        PORTA.OUT = hodnota;                 //nastavení PORTA
        if(hodnota%2) PORTC.OUT |= PIN4_bm;  //nastavení PC4
        PORTC.OUT &= ~(1 << i);              //rozsviť digit
        _delay_ms(4);
        PORTC.OUT = 0x0F;                    //zhasni digit
        cislo /= 10;                         //posun o řád vpravo
    }
}

Program je velice jednoduchý. V hlavní smyčce pouze testujeme stisk tlačítka a následně inkrementujeme proměnnou pocitadlo. Poté voláme funkci zobraz(). Tato funkce už je o něco zajímavější než hlavní smyčka. Jelikož pracujeme s multiplexním řízením displeje, zobrazujeme v jeden okamžik pouze jednu cifru z čísla. Proto dělením modulo deset získáváme číselnou hodnotu jednotek a tu pak pomocí pole kod[] převedeme na kombinaci pro 7segment (položky pole musí být seřazeny, jak je uvedeno. Využíváme totiž skutečnosti, že index prvku pole se rovná zobrazovanému číslu). Zároveň musíme zkontrolovat nultý bit této binární kombinace a podle něj nastavit vývod PC4. Poté musíme přivést na odpovídající katodu logickou nulu tak, aby došlo k rozsvícení požadované 7segmentovky. V prvním cyklu pro jednotky, v druhém pro desítky, ve třetím pro stovky a v posledním pro tisíce. V každém cyklu též musíme posunout zobrazované číslo o jeden řád doprava tak, aby na pozici jednotek bylo číslo odpovídající právě zobrazovanému řádu. To provedeme jednoduše celočíselným dělením deseti.

Nesmíme taktéž zapomenout na časovou prodlevu po rozsvícení segmentovky tak, abychom něco viděli. Dobu rozsvícení určíme experimentálně nebo můžeme vycházet z úvahy, že lidské oko vnímá blikání světla o frekvenci vyšší než cca 40 Hz už jako trvalý svit. V programu jsem sice použil 4 ms, ale můžete si sami vyzkoušet, co se bude dít, když tento čas hodně prodloužíte, nebo naopak zkrátíte.

4.5. Příklad: Klávesnice

Abychom si procvičili práci se vstupy mikrokontroléru, rozšíříme předcházející příklad o maticovou klávesnici. Maticová klávesnice funguje na principu propojení všech řádků a sloupců tlačítek na společný vývod. Při stisku klávesy (tlačítka) dojde k propojení odpovídajícího sloupce a řádku. Například pokud stiskneme klávesu s číslem "7" dojde k propojení druhého sloupce a třetího řádku. Pomocí této konfigurace můžeme připojit na osm vývodů MCU až 16 tlačítek.

keyboard
Obrázek 18. Maticová klávesnice 4x4
keyboard
Obrázek 19. Schéma zapojení obvodu s maticovou klávesnicí a LED displejem

Pro jednotlivé klávesy opět vytvoříme převodní tabulku, která bude obsahovat binární kombinace pro rozsvícení příslušných segmentů zobrazovače. Tuto tabulku použijeme ve zdrojovém kódu implementovanou jako pole kod[]. Indexy prvků pole budou odpovídat pozici klávesy, která je počítána po řádcích klávesnice.

Tabulka 24. Převodní tabulka LED displeje a klávesnice

g

f

e

d

a

b

c

segment

pozice

klávesa

PA7

PA6

PA5

PA4

PA3

PA2

PA1

PC4

pin

0

1

0

0

0

0

1

0

1

1

0x0B

1

2

1

0

1

1

1

1

1

0

0xBE

2

3

1

0

0

1

1

1

1

1

0x9F

3

A

1

1

1

0

1

1

1

1

0xEF

4

4

1

1

0

0

1

0

1

1

0xCB

5

5

1

1

0

1

1

1

0

1

0xDD

6

6

1

1

1

1

1

1

0

1

0xFD

7

B

1

1

1

1

1

0

0

1

0xF9

8

7

0

0

0

0

1

1

1

1

0x0F

9

8

1

1

1

1

1

1

1

1

0xFF

10

9

1

1

0

1

1

1

1

1

0xDF

11

C

0

1

1

1

1

1

0

0

0x7C

12

*

1

1

1

0

1

1

0

0

0x7E

13

0

0

1

1

1

1

1

1

1

0x7F

14

#

1

1

1

1

1

1

0

0

0xFC

15

D

1

0

1

1

1

0

1

1

0xBB

Nyní zbývá vyřešit, jak zjistit, že došlo k propojení řádku a sloupce při stisknutí klávesy. Řešení je vcelku jednoduché. Sloupce a řádky klávesnice připojíme na piny mikrokontroléru a postupně budeme přivádět na jednotlivé řádky logickou nulu. Poté budeme kontrolovat, zda-li se tato logická úroveň objeví i na některém ze sloupců. Pokud ano, je klávesa v daném řádku a sloupci stisknuta.

Aby vše fungovalo, musí být piny, na které jsou připojeny řádky, konfigurovány jako výstupy a sloupce naopak jako vstupy, abychom mohli číst logické úrovně. Z toho též vyplývá, že na vstupech, na kterých jsou připojeny sloupce klávesnice, musí být definována hodnota pokud není tlačítko stisknuto. To zařídíme pomocí pull-up rezistorů, které nastaví vstupy na logickou jedničku.

Poté už je to jen hraní si s bity, v našem případě registru PORTB.IN`a `PORTB.OUT. V nekonečné smyčce přivádíme postupně na spodní čtyři bity portu logickou úroveň nula a následně čteme horní polovinu bitů portu. Pokud zjistíme logickou nulu na některém z bitů horní poloviny jednoduše určíme, která klávesa byla stisknu a zavoláme funkci dekoduj(), která pomocí proměnné kod[] převede pozici klávesy na binární hodnotu potřebnou pro rozsvícení správných segmentů zobrazovače a uloží ji do pole znaky[]. Displej LED opět pracuje v multiplexním režimu, což zajišťuje funkce zobraz().

A to je vše. Popis programu je trochu delší, ale funkcionalita je opravdu jednoduchá. Prozkoumejte níže uvedený kód a zjistíte, jak jednoduché to je. Ještě doplním informaci, že program funguje tak, že zobrazuje čtyři naposledy stisknuté klávesy. Poslední stisknutá klávesa je zobrazena na pozici vpravo a předcházející tři jsou posunuty o jednu pozici vlevo. Klávesa se symbolem '#' je zobrazována jako znak 'E' a klávesa '*' jako písmeno 'F'.

#define F_CPU 3333333UL                         //20MHz/6
#include <avr/io.h>
#include <util/delay.h>

#define COL1_MASK   239     //1110 1111
#define COL2_MASK   223     //1101 1111
#define COL3_MASK   191     //1011 1111
#define COL4_MASK   127     //0111 1111

void zobraz();
void dekoduj(unsigned char pozice);
const uint8_t kod[16] = {0x0b,0xbe,0x9f,0xef,0xcb,0xdd,0xfd,0xf9,
                         0x0f,0xff,0xdf,0x7c,0xec,0x7f,0xfc,0xbb};
uint8_t znaky[4] = {0x80,0x80,0x80,0x80};

int main(void) {
    PORTA.DIR = 0xFF;                       //PORTA výstup
    PORTB.DIR = 0x0F;                       //PB0-3 výstup; PB4-7 vstup
    PORTB.PIN4CTRL |= PORT_PULLUPEN_bm;     //pull-up sloupec1
    PORTB.PIN5CTRL |= PORT_PULLUPEN_bm;     //pull-up sloupec2
    PORTB.PIN6CTRL |= PORT_PULLUPEN_bm;     //pull-up sloupec3
    PORTB.PIN7CTRL |= PORT_PULLUPEN_bm;     //pull-up sloupec4
    PORTC.DIR = 0x1F;                       //PC0-4 výstupy
    PORTC.OUT = 0x1F;

    while (1) {
        for(unsigned char i = 0; i < 4; i++){   //projdi všechny řádky
            PORTB.OUT = ~(1 << i);              //nastav i-tý řádek na 0
            _delay_us(100);                     //ustálení hodnot
            switch(PORTB.IN | 0x0F){            //čti sloupce
                case COL1_MASK: dekoduj(4*i+0); break; //sloupec1 == 0
                case COL2_MASK: dekoduj(4*i+1); break; //sloupec2 == 0
                case COL3_MASK: dekoduj(4*i+2); break; //sloupec3 == 0
                case COL4_MASK: dekoduj(4*i+3); break; //sloupec4 == 0
            }
        }
        zobraz();
    }
}

//převede pozici na bin. kód a přidá nový znak do pole znaky[]
void dekoduj(unsigned char pozice){
    for(unsigned char i = 3; i > 0; i--){
        znaky[i] = znaky[i-1];                  //posune znaky o 1 vlevo
    }
    znaky[0] = kod[pozice];                     //nový znak na pozici vpravo
    //while((PORTB.IN >> 4 != 0x0f)){zobraz();} //čeká na uvolnění tlačítka
    while(PORTB.IN < 0xF0){zobraz();}           //čeká na uvolnění tlačítka
}

//zobrazí pole znaků - znaky[]
void zobraz(){
    for(unsigned char i = 0; i < 4; i++){
        PORTA.OUT = znaky[i];
        if(znaky[i]%2) PORTC.OUT |= PIN4_bm;
        PORTC.OUT &= ~(1 << i);
        _delay_ms(4);
        PORTC.OUT = 0x0F;
    }
}

V příštím díle se podíváme na alternativní funkce vývodů MCU a přerušení, abychom mohli využívat všechny funkce mikrokontroléru.