este jedna dlazdicska...

Jan Waclawek konfera na efton.sk
Neděle Leden 8 01:15:06 CET 2017


>je tu nejak ticho tak pridam blbu otazku...
>
>
>majme porty na procesore Atmega. Chcel by som niektore piny nadratovat 
>do boolean premennej, tak aby som po precitani dostal 0/1. Takisto by 
>som chcel zase niektore ine boolean premenne chcel nadratovat na 
>vystupne piny. Ako sa to v C spravne robi?

Nijako.

_Bool v C nie je typ, ktory by sa dal namapovat na bit. Presnejsie, v C
nemoze existovat samostatny objekt (a tym ani typ, ktory by ten objekt
popisoval), ktory by nepozostaval z jedneho alebo viacerych bytov (C99,
6.2.6.1#2).

V C vsak existuje prostriedok na manipulaciu jednotlivych bitov, bitove
polia (bitfields, resp. v norme sa to pise so spojovnikom bit-fields) v
strukturach (struct). Pouzitie je trocha ine ako so samostatnou premennou,
t.j. nemozes zapisat nieco ako PORTC_1 = 1; a musis zapisat nieco ako
PORTC.b1 = 1; - ale tipujem, ze to by Ti az tak velmi nevadilo.

Zacnime vsak s tymi portami (resp. vseobecnejsie k SFR) u AVR. Tie
samozrejme su (mozu) byt bitovo manipulovatelne, rovnako ako u '51. V
povodnom AVR tak ako ho A&V vymysleli, je pre SFR vyhradena oblast 64
byte, kde samotne SFR su adresovane 0x00-0x3F - to su adresy P pouzite v
instrukciach pre SFR. Su dve instrukcie pre nastavenie/nulovanie SFR bitu
- SBI P,b a CBI P,b (Set resp. Clear Bit in I/O) - a dve instrukcie na
testovanie bitu a obskocenie nasledujucej instrukcie - SBIS P,b a SBIC P,b
(Skip if Bit in I/O Set resp. Cleared). Toto sa prenieslo aj do
programovania v C s prekladacom avr-gcc - avr-gcc spociatku nevedel
zohladnit tuto specialitu, a tak sa porty efektivne nastavovali pomocou
makier sbi()/cbi() generujucich inline assembler - toto spominam preto,
lebo pozostatky tych makier sa daju najst v starsich programoch ako aj v
dokumentacii k avr-libc, a pre spatnu kompatibilitu su stale obsiahnute v
<compat/deprecated.h>
http://www.nongnu.org/avr-libc/user-manual/group__deprecated__items.html .

Sucasne vsak je cela SFR oblast namapovana aj v "unifikovanej" pamati,
ktora u klasickych AVR obsahuje na adresach 0x00 az 0x1F registre
procesora (uzitocnost tohoto je na velmi dlhu diskusiu a tyka sa vlastne
len tych najmensich a najstarsich Tiny), na adresach 0x20 az 0x5F klasicke
bitovo adresovatelne SFR registre ako boli popisane hore (takze kazdy taky
register ma vlastne dve adresy, jednu v priestore SFR (od adresy 0x00) a
druhu v unifikovanej pamati (posunutu o 0x20). Unifikovana pamat znamena,
ze k vsetkym prostriedkom sa da pristupovat jednou sadou instrukcii v
jednom adresnom priestore. Unifikovana pamat je prave jedna z vlastnosti
AVR ktora bola zamerne pritomna uz od pociatku prave kvoli pouzitiu vo
vyssich jazykoch (na cele s C), kedze tie maju notoricky problem s
rozmanitostou pamati (prave preto vravi pan kolega Andel, ze C pre '51 je
Keil - C pre '51 musi totiz obsahovat rozsirenia pre viacero roznych
pamatovych oblasti, ktorych ma '51 tak zhruba 5, inak je takmer
nepouzitelne a urcite nie efektivne pouzitelne). 

Klasicky bola v AVR nad SFR oblastou umiestnena SRAM (t.j. od adresy 0x60).
FLASH nebola v unifikovanej pamati (to prinasa dalsiu sadu problemov s
konstantnymi premennymi umiestnenymi vo FLASH, toto sa vo svete avr-gcc
prejavovalo skupinou makier a funkcii v <pgmspace.h> a s nimi spojenymi
zvlastnostami, od urciteho casu sa to prejavuje skupinou pomenovanych
pamatovych priestorov typu _flash) a ani EEPROM (tam sa to vsak obvykle
ani necaka a pristupuje sa k nej dost prirodzene zvlastnymi funkciami).
Toto sa zmenilo v xMEGA kde vznikla skutocne unifikovana pamat zahrnajuca
vsetky prostriedky, avsak za cenu strankovania, kedze prirodzeny adresny
priestor AVR je dost pochopitelne 16-bitovy (ta potreba vsak u FLASH >64kB
tak ci tak existuje).

Vratme sa vsak k SFR. Pochopitelne, v urcitom bode prestalo 64 byte pre
vsetky SFR stacit, a v novsich, vacsich modeloch boli dodatocne SFR
umiestnene uz len do unifikovanej pamate, nad oblast povodnych SFR t.j. od
adresy 0x60 (a SRAM bola posunuta nad nove SFR, na adresu 0x100 a neskor
aj vyssie). Tieto nove SFR uz teda neboli bitovo manipulovatelne, pre
unifikovanu pamat take instrukcie nie su. (Mimochodom, aj v '51 je len
pomerne mala cast SFR priestoru bitovo manipulovatelna, vsakze). Pre ne
teda zmena bitu znamena precitat cely SFR do registra, zmodifikovat ten
jeden bit, a zapisat naspat. 

Pisatelom v C/avr-gcc teda vznikli dve moznosti: bud si pamatat, pre ktore
SFR sa da pouzit cbi()/sbi() a pre ktore nie, alebo prosto pouzivat pre
vsetky SFR pristup ako k normalnej pamati (to je to PORTx |= 1 << 5 apod.)
aj za cenu, ze to pre tie klasicke SFR nie je optimalne. Medzicasom sa
vsak nasiel aj dobrodinec ktory do avr-gcc doplnil optimalizaciu, ktora
skuma, ci bitovo-manipulovana adresa je v oblasti klasickych SFR, a ak
ano, tak pouzije cbi/sbi, co umoznilo standardizovat pristup k SFR ako k
pamati a zachovat optimalitu. Tym padom sa stali makra cbi()/sbi()
zastarale (deprecated) a oficialne doporuceny pristup je popisany tu:
http://www.nongnu.org/avr-libc/user-manual/group__avr__sfr.html

Ono to PORTB |= _BV(PB1); nie je omnoho zrozumitelnejsie ci citatelnejsie
ako PORTB |= (1 << PB1), takze si to mnohi aj tak spestruju svojimi
makrami. My vo firme napriklad mame taky postup, ze si na zaciatku
projektu porobime makra typu
#define SetLED1() PORTB |= (1 << 3) 
#define ClrLED1() PORTB &= ~(1 << 3)
#define GetButton1() ((PINB & (1 << 5)) == 0)
a podobne - je to mozno trocha tukania, ale potom zvysok programu je
relativne dobre citatelny.

Ale aby som sa po tejto obrovskej okluke vratil uplne na zaciatok, je mozne
urobit nieco taketo (http://bit.ly/2i5mMXr ):

#include <stdint.h>
#include <avr/io.h>

typedef union __attribute__((packed)) {
  uint8_t all;
  struct __attribute__((packed)) { uint8_t nibH : 4, nibL : 4; };
  struct __attribute__((packed)) { _Bool lsb: 1; uint8_t : 6; _Bool msb: 1;
};
  struct __attribute__((packed)) { _Bool b0: 1, b1: 1, b2: 1, b3: 1, b4: 1,
b5: 1, b6: 1, b7: 1; };
} bit8;


#define SFR2BITFIELD8(sfr) (*(volatile bit8 *)(&(sfr)))

#define pinC     SFR2BITFIELD8(PINC)
#define portC    SFR2BITFIELD8(PORTC)
#define pinF     SFR2BITFIELD8(PINF)
#define portF    SFR2BITFIELD8(PORTF)


int main(void) {
  if (PINC & (1 << 4)) {
    PORTC |= (1 << 5);
  }

  if (pinC.b4) {
    portC.b5 = 1;
  }


  if (PINF & (1 << 4)) {
    PORTF |= (1 << 5);
  }
  if (pinF.b4) {
    portF.b5 = 1;
  }

  portC.b5 = !pinC.b4;

  portC.b5 = ~pinC.b4;
  
}

z coho relevantna cast sa prelozi ako


int main(void) {
  if (PINC & (1 << 4)) {
  be:	9c 99       	sbic	0x13, 4	; 19
    PORTC |= (1 << 5);
  c0:	ad 9a       	sbi	0x15, 5	; 21
  }

  if (pinC.b4) {
  c2:	9c 99       	sbic	0x13, 4	; 19
    portC.b5 = 1;
  c4:	ad 9a       	sbi	0x15, 5	; 21
  }


  if (PINF & (1 << 4)) {
  c6:	04 9b       	sbis	0x00, 4	; 0
  c8:	05 c0       	rjmp	.+10     	; 0xd4 <main+0x16>
    PORTF |= (1 << 5);
  ca:	80 91 62 00 	lds	r24, 0x0062
  ce:	80 62       	ori	r24, 0x20	; 32
  d0:	80 93 62 00 	sts	0x0062, r24
  }
  if (pinF.b4) {
  d4:	04 9b       	sbis	0x00, 4	; 0
  d6:	05 c0       	rjmp	.+10     	; 0xe2 <main+0x24>
    portF.b5 = 1;
  d8:	80 91 62 00 	lds	r24, 0x0062
  dc:	80 62       	ori	r24, 0x20	; 32
  de:	80 93 62 00 	sts	0x0062, r24
  }

  portC.b5 = !pinC.b4;
  e2:	83 b3       	in	r24, 0x13	; 19
  e4:	82 95       	swap	r24
  e6:	8f 70       	andi	r24, 0x0F	; 15
  e8:	80 95       	com	r24
  ea:	81 70       	andi	r24, 0x01	; 1
  ec:	82 95       	swap	r24
  ee:	88 0f       	add	r24, r24
  f0:	80 7e       	andi	r24, 0xE0	; 224
  f2:	95 b3       	in	r25, 0x15	; 21
  f4:	9f 7d       	andi	r25, 0xDF	; 223
  f6:	98 2b       	or	r25, r24
  f8:	95 bb       	out	0x15, r25	; 21

  portC.b5 = ~pinC.b4;
  fa:	83 b3       	in	r24, 0x13	; 19
  fc:	ad 9a       	sbi	0x15, 5	; 21


(domaca uloha: vysvetlit preklad posledneho riadku)

wek



Další informace o konferenci Hw-list