C a flagy

Pro ostřílené Cčkaře asi nebudu psát nic nového, ale jsou mezi námi i takoví, kteří s Cčkem teprve začínají a těm by tohle povídání mohlo usnadnit práci. Při použití timeru nebo přerušení, případně i při jiných činnostech prováděných mikrokontrolerem je někdy šikovné používat flagy. Vzhledem k tomu, že flagy (neboli příznaky) jsou jednobitové informace, může být použití celého bytu pro jeden příznak celkem plýtvání RAMkou. Obzvlášť u menších procesorů. Ale konec s teorií, ukažme si, jak na ně.


První možnost je tato :

unsigned char ProgramStatus;

#define Bit0 0
#define Bit1 1
#define Bit2 2
#define Bit3 3
#define Bit4 4
#define Bit5 5
#define Bit6 6
#define Bit7 7

Použití je pak následující :

ProgramStatus |= (1<<Bit6); // Nastavení bitu 6
ProgramStatus &= ~(1<<Bit4); // Vynulování bitu 4

if ((ProgramStatus & (1<<Bit6)) != 0) ; // Pokud bit 6 = 1
if ((ProgramStatus & (1<<Bit6)) == (1<<Bit6)) ; // Pokud bit 6 = 1

if ((ProgramStatus & (1<<Bit2)) == 0) ; // Pokud bit 2 = 0

Jak vidíte, použití je trošku komplikované, ale naštěstí se dá celkem výrazně zjednodušit.
Bohužel se tento způsob nedá použít pro PORTx, DDRx apod. - tedy alespoň ne přímo (viz. dále) - tuto informaci beru zpět - viz následující příspěvek od Radiuse.

struct
{
	unsigned char Bit0 : 1;
	unsigned char Bit1 : 1;
	unsigned char Bit2 : 1;
	unsigned char Bit3 : 1;
	unsigned char Bit4 : 1;
	unsigned char Bit5 : 1;
	unsigned char Bit6 : 1;
	unsigned char Bit7 : 1;
}ProgramStatus;

nebo varianta s definicí typu:

typedef struct
{
	unsigned char Bit0 : 1;
	unsigned char Bit1 : 1;
	unsigned char Bit2 : 1;
	unsigned char Bit3 : 1;
	unsigned char Bit4 : 1;
	unsigned char Bit5 : 1;
	unsigned char Bit6 : 1;
	unsigned char Bit7 : 1;
}ProgramStatusTyp; // 1 byte

typedef struct
{
	unsigned char Bit0 : 1;
	unsigned char Bit1 : 1;
	unsigned char Bit2 : 1;
	unsigned char Bit3 : 1;
	unsigned char Bit4 : 1;
	unsigned char Bit5 : 1;
	unsigned char Bit6 : 1;
	unsigned char Bit7 : 1;
	unsigned char Bit8 : 1;
	unsigned char Bit9 : 1;
	unsigned char Bit10 : 1;
}ProgramStatusTyp; // 2 byty

ProgramStatusTyp ProgramStatus;



Takto nadefinované flagy se pak používají takto :

ProgramStatus.Bit6 = 1; // Nastavení bitu 6
ProgramStatus.Bit4 = 0; // Vynulování bitu 4

if (ProgramStatus.Bit6 == 1) ; // Pokud bit 6 = 1
if (ProgramStatus.Bit2 == 0) ; // Pokud bit 2 = 0

Výsledný překlad je pro unsigned char i použití struktury shodný.
Výhodou je, že program pracuje pouze s tím bytem, ve kterém se nalézá bit, se kterým se pracuje.

Nulování celého statusu najednou se dělá následnovně :

memset((void*)&ProgramStatus, 0, sizeof(ProgramStatus));

Tohle byl nejčistší způsob a funguje na jakékoliv množství flagů ve statusu. Překladač totiž alokuje pro status proměnou tolik bytů, aby se vešly všechny bity. Lze jej však vynulovat i takto :

*(unsigned char *)&BitStatus = 0; // pro 1 byte
*(unsigned int *)&BitStatus = 0; // pro 2 byty

Nevýhodou je, že je potřeba znát, kolik bytů status vlastně zabírá (i když nepředpokládám, že by programátor potřeboval více, než 2 byty).

Status se dá i vyslat na nějaký port. Nevýhodou je ale to, že na port se vyšle vždy jenom prvních 8/16 bitů.

PORTA = *(unsigned char *)&BitStatus;
TCNT1 = *(unsigned int *)&BitStatus;

Použití flagů jako pojmenování LED nebo signálů na portu je tedy možné, jen je to naopak vyváženo trošku menším komfortem zápisu.

Lze nadefinovat ale i čítač (v tomto případě 2-bitový) :

typedef struct
{
	unsigned char Bit0 : 1;
	unsigned char Bit1 : 1;
	unsigned char Bit2 : 1;
	unsigned char Bit3 : 1;
	unsigned char Bit4 : 1;
	unsigned char Bit5 : 1;
	unsigned char Citac : 2;
}ProgramStatusTyp; // 1 byte

ProgramStatusTyp ProgramStatus;

Když by se pak provádělo :

ProgramStatus.Citac++; // čítá 0-1-2-3-0-1-...

V případě zápisu :

ProgramStatus.Citac = 5; // nepřeteče, ale zapíše jen hodnotu%4.

V případě definování vícebitových částí ale doporučuju je umístit tak, aby celá vícebitová proměnná byla obsažena jen v jednom bytu, ideálně od bitu 0.




Veškeré zde uvedené střípky kódu jsou vyzkoušené na překlad v AVR Studiu 4.19 + WinAVR a zkontrolované pomocí disassembleru.

1 Like

Vidíš a přesně tenhle způsob já používám pro práci s jednotlivými porty u AVR.

typedef struct
	{
    unsigned B0: 1;
    unsigned B1: 1;
    unsigned B2: 1;
    unsigned B3: 1;
    unsigned B4: 1;
    unsigned B5: 1;
    unsigned B6: 1;
    unsigned B7: 1;
    } bits;
#define BITS(p) (*(volatile bits*)&p)
#define LED1        BITS(PORTA).B0
#define DIR_LED1    BITS(DDRA).B0

DIR_LED1=1; // Vystup

LED1=1; // Sviti
.
.
.
LED1=0; // Nesviti

Super - díky za rozšíření. Zase jsem o něco moudřejší. A navíc je to přeloženo jako jedna jediná instrukce procesoru (CBI/SBI). To se mi moc líbí.

Beru tedy zpět informaci o tom, že se to nedá použít přímo pro PORTx, DDRx apod.

Doplním ještě Radiusovo info :

typedef struct
{
    unsigned B0: 1;
    unsigned B1: 1;
    unsigned B2: 1;
    unsigned B3: 1;
    unsigned B4: 1;
    unsigned B5: 1;
    unsigned B6: 1;
    unsigned B7: 1;
} bits; 

#define BITS(p) (*(volatile bits*)&p) 

#define LED1        BITS(PORTA).B0
#define DIR_LED1    BITS(DDRA).B0

Když funguje tohle :

DIR_LED1=1; // Vystup

LED1=1; // Sviti
.
.
.
LED1=0; // Nesviti

Vyzkoušel jsem i následující :

#define KEY1        BITS(PINA).B2
#define DIR_KEY1        BITS(DDRA).B2
#define PORT_KEY1        BITS(PORTA).B2

DIR_KEY=0; // Vstup
PORT_KEY=1; // Zapnout Pull-up

if (KEY1==0) LED1=1; else LED1=0;

Funguje to také.

Tohle funguje taky :wink:

LED1=KEY1;

nebo

LED1=!KEY1;

Vidíš, to jsem zapomněl napsat.

Doplním tedy, co jsem včera ještě zjistil :

if (KEY1==0) LED1=1; else LED1=0;

zabere jenom 10 bytů (5 instrukcí), kdežto

LED1 = KEY1;
LED1 = (KEY1==1) ? 1 : 0;

LED1 = !KEY1;
LED1 = (KEY1==0) ? 1 : 0;

zabere paměti více a záleží i na vzájemné poloze bitů v bytu o kolik.

Čekal bych, že zápis LED1 = (KEY1==0) ? 1 : 0; bude mít stejný překlad jako if (KEY1==0) LED1=1; else LED1=0;, ale nemá, překládá se stejně jako LED1 = !KEY1;.