V predhodnih treh nadaljevanjih smo pokazali, kako napisati Bascom-AVR ali Arduino IDE program za mikrokontroler iz ATtiny25/45/85 družine in ga pretvoriti v I2C slave čip.
Takšen slave lahko pomaga masterju tako, da na sebe prevzame izvršitev neke kompleksne softverske naloge ali pa lahko master izkoristi vezja znotraj slave-a za povečanje lastne hardverske “infrastrukture” (kot v naših primerih).
Avtor: mag. Vladimir Mitrović
E-pošta: vmitrovic12@gmail.com
2022-306-40
Slabost malih 8-pin ATtiny mikrokontrolerjev je prav majhno število priključkov. Tudi takrat, ko ne potrebujemo več kot 2 ali 3 priključke za povezovanje dodatnih komponent, malo število priključkov potegne za sabo tudi to, da so posameznemu priključku dodeljene različne funkcionalnosti, ki se včasih lahko tudi “nerodno” pokrijejo; to je problem, s katerim smo se tudi mi srečali v našem RGB modulu. Vendar pa so opisane procedure uporabne na vseh ATtiny mikrokontrolerjih z USI vezjem: 14-pin ATtiny24/44/84 in 20-pin ATtiny261/461/861, ATtiny87/167 ali ATtiny2313/4313. Softverska emulacija je uporabna tudi na mikrokontrolerjih, ki nimajo USI, vključno tudi mikrokontrolerje iz ATmega družine. Seveda pri ATmega mikrokontrolerjih je boljša rešitev to, da izkoristimo njihovo TWI vezje, čemur se bomo posvetili v enem od naslednjih člankov.
Ta vsebina je samo za naročnike
Bascom-AVR programi so temeljito preverjeni na ATtiny45 in ATtiny85 mikrokontrolerjih na delovnih frekvencah 4, 8 in 16 MHz in pri I2C hitrosti komunikacije od 100 kHz. Če uporabimo USI vezje in pripadajočo knjižnico, zanesljiva in in zelo pogosta komunikacija, brez opaženih napak, se doseže na delovni frekvenci 8 MHz. Če imamo manj pogosto komunikacijo in če lahko toleriramo posamezne izpade (katere master lahko zazna s preverjanjem Err bita in po potrebi ponovi sporočilo), bo slave delal tudi na nižjih frekvencah, vendar na 4 MHz postane neuporaben; za nižje delovne frekvence moramo znižati hitrost komunikacije. Če uporabimo softversko emulacijo in želimo doseči hitrost I2C komunikacije od 100 kHz, mora delovna frekvenca mikrokontrolerja biti 16 MHz.
Kako je napisana Bascom-AVR knjižnica
Za tiste, ki želijo vedeti več, bomo opisali kako je napisana Bascom-AVR knjižnica za I2C slave čip, ATtiny_I2Cslave_USI_bascom.sub. Knjižnica uporablja univerzalno serijsko vezje – USI, ki ga ima večina ATtiny mikrokontrolerjev (razen najenostavnejši, kot ATtiny13 ali ATtiny15). Vezju so pridruženi registri USIDR, USICR in USISR (slika 15), od katerih prvi vsebuje podatek, ki je sprejet ali katerega je potrebno poslati preko komunikacijskega vodila. Ostala dva vsebujeta konfiguracijske in statusne bite. Večina mikrokontrolerjev ima tudi buffer register USIBR, v katerem se nahaja kopija sprejetega podatka; zaradi kompatibilnosti programa s čim večjim številom ATtiny mikrokontrolerjev ga nismo uporabili. USI vezju je dodan tudi Start Condition Detector, vezje ki detektira START signal na komunikacijskem vodilu.
Prvi štirje biti statusnega registra USISR vsebujejo zastavice, ki signalizirajo sledeče:
- USISF: detektiran je START (lahko se sproži prekinitev)
- USIOIF: sprejeto je pričakovano število bitov (lahko se sproži prekinitev)
- USIPF: detektiran je STOP
- USIDC: prišlo je do kolizije na komunikacijskem vodilu
Preostali štirje biti, USICNT3:0 tvorijo števec impulzov. V naših primerih števec šteje impulze na SCL liniji komunikacijskega vodila in takrat, ko “preskoči” s 15 na 0, postavi USIOIF. Ker števec šteje tudi rastoče in padajoče fronte SCL signala, največje število impulzov, ki jih lahko prebere, znaša 8. Če želimo npr, sprejeti ali poslati 8 bitov, bomo USICNT postavili v stanje “0000”; če želimo sprejeti ali poslati samo en bit, bomo USICNT postavili v stanje “1110” (= 14).
Prva dva bita USICR registra, USISIE in USIOIE, omogočata prekinitve kadar je detektiran START, oziroma kadar je sprejeto pričakovano število bitov. Biti USIWM1:0 določajo način komunikacije; za I2C protokol nam bosta ustrezali naslednji postavki:
- USIWM1:0 = “10”: uporablja dve komunikacijski liniji, SDA in SCL; ustrezna priključka mikrokontrolerja sta vrste open collector in sta dvosmerna
- USIWM1:0 = “11”: ko prejme pričakovano število bitov zadrži SCL linijo v stanju “0”; ostalo je isto kot za USIWM1:0 = “10”
Bita USICS1:0 in USICLK določata izvor in aktivno fronto clock signala. I2C slave uporablja clock ki ga generira master, zato nam ustreza konfiguracija Clock Source = External, Positive edge:
- USICS1:0 = “10” in USICLK =”0″
Zadnji bit USICR registra, USITC, bomo postavili v stanje “0”.
Sedaj ko smo spoznali namen registrov pridruženih USI vezju lahko napišemo komunikacijski program. Analizirali bomo kako je napisan program iz Attiny_I2Cslave_USI_bascom.sub knjižnice, katere diagram poteka je prikazan na sliki 16. Modro obarvane procedure so podprogrami, ki pripravljajo USI vezje, da bo naredilo korak v komunikacijski proceduri. Rumeno obarvane procedure so podprogrami, katere mora uporabnik pripraviti v svojem delu programa, na način na katerega smo to pokazali v primerih iz predhodnih nadaljevanj. Uporabljena imena v diagramu poteka so enaki imenom podprogramov, label in konstant v programu.
Na začetku programa definiramo naslednje spremenljivke:
Dim I2c_address As Byte Dim I2c_rcv_byte(i2c_bytes_to_receive) As Byte Dim I2c_snd_byte(i2c_bytes_to_send) As Byte Dim I2c_i As Byte
Namen nizov I2c_rcv_byte() i I2c_snd_byte() smo že spoznali v analizi slave programa. V spremenljivko I2c_address knjižnica shrani zadnji uporabljen I2C naslov, I2C_i je lokalni števec. Še bomo definirali makro ukaz Wait_for_counter_overflow:
Macro Wait_for_counter_overflow Do If USISR.USIPF = 1 Then Goto Wait_for_start If USISR.USISIF = 1 Then Goto Start_detected Loop Until USISR.USIOIF = 1 End Macro
V tej zanki program čaka da USI postavi USIOIF zastavico, s čemer signalizira, da je prejel ali poslal dano število bitov. Če se vmes na I2C vodilu pojavi STOP ali START stanje (kar se med normalnim delovanjem komunikacije ne bi smelo zgoditi), se bo odvijanje programa nadaljevalo od labele Wait_for_start ali Start_detected na začetku glavne zanke. Navedeno proceduro smo definirali kot makro, ker jo potrebujemo na več mestih v programu.
Konfigurirali bomo tudi Watchdog timer, da bi preprečili da se program zavrti v neskončni zanki v primeru napake v komunikaciji:
Config Watchdog = 1024
in nato bomo poklicali podprogram Init_slave, v katerem mora uporabnik inicializirati svoj del programa. Sledi klic podprograma I2c_init; v njemu bomo SCL in SDA priključka mikrokontrolerja definirali kot vhodne
I2c_init: I2c_port.scl = 1 I2c_port.sda = 1 Config I2c_port.scl = Input Config I2c_port.sda = Input
USI pa postavili v stanje v katerem pričakuje START signal. V ta namen bomo USICR konfigurirali na prej opisan način; pri tem bomo onemogočili prekinitve ker jih naša knjižnica ne uporablja:
USICR = &B00101000 ' (0<<USISIE)|(0<<USIOIE)| (1<<USIWM1)|(0<<USIWM0) ' (1<<USICS1)|(0<<USICS0)| (0<<USICLK)|(0<<USICLK)
Zbrisali bomo vse zastavice in števec postavili v stanje 0, ker po START-u pričakujemo sprejem 8-bitnega naslova:
USISR = &B11110000 ' (1<<USISIF)|(1<<USIOIF)|(1<<USIPF)| (1<<USIDC) ' (&H00<<USICNT) Return
Nato program vstopi v glavno zanko, zaženeWatchdog in čaka START signal:
Main_loop: Start Watchdog Wait_for_start: While USISR.USISIF = 0 Reset Watchdog Wend
Ko Start Condition Detector prepozna START signal na I2C vodilu, bo postavil USISIF in tako omogočil da program izstopi iz While-Wend zanke. START signal lahko čakamo nedoločeno dolgo in zato v zanki večkrat resetiramo Watchdog. Preden spoznamo kako so rešene posamezne procedure, bomo pojasnili kaj se dogaja znotraj glavne zanke. Po START signalu program bere naslednji bajt (I2c_receive_byte), ga shrani v spremenljivko I2c_address in preverja ali gre za lasten slave naslov.
Če je uporabljen lastniread naslov (v našem primeru, &B11110001), se bo izvršitev programa nadaljevala od labele Send_data_bytes. Program bo poslal ACK in nato poklical uporabniški podprogram za pripravo podatkov, Prepare_data_to_be_sent. Uporabnik mora podatke shraniti v niz I2c_snd_byte(), slave jih bo začel pošiljati masterju bajt po bajt takoj po zaključku uporabniškega podprograma. Postopek se zaključi z vrnitvijo na začetek zanke, ko se pošljejo vsi podatki ali če master nekega podatka ne potrdi z ACK-om.
Če je sprejet lastni write naslov (v našem primeru, &B11110000) ali nek splošen naslov (general call, &B00000000), bo to program potrdil z ACK-om in nadaljeval izvrševanje programa od labele Receive_data_bytes. Tukaj bo bajt po bajt sprejemal podatke, ki mu jih pošilja master in jih shranjeval v niz I2c_rcv_byte(). Ko sprejme zadnji podatek, bo program poklical uporabniški podprogram za obdelavo podatkov, Execute_i2c_command in nato se bo vrnil na začetek glavne zanke.
Če je bil sprejet kakšen drug naslov, ga bo program ignoriral in se vrnil na začetek glavne zanke.
Opombe:
zanke za sprejem in pošiljanje podatkov se izvršujejo samo, če sta konstanti I2c_bytes_to_receive in I2c_bytes_to_send večji od 0.
Master mora predvideti dovolj časa za izvrševanje uporabniških podprogramov. Če trajajo dalj kot 2-3 µs, je treba v master programu predvideti ustrezne Wait ukaze, kar smo komentirali v analizi naših primerov master programa.
Poglejmo sedaj, kako so rešeni posamezni komunikacijski podprogrami!
* I2c_receive_byte
Podprogram I2c_receive_byte pripravlja USI za sprejem 8 bitov z I2C vodila. SDA pin mora biti konfiguriran kot vhodni, nakar bomo čakali, da se SCL vrne v stanje “0” in konfiguriramo USI:
I2c_receive_byte: Config I2c_pin.sda = Input Bitwait I2c_pin.scl , Reset USICR = &B00101000 '(0<<USISIE)|(0<<USIOIE)|(1<<USIWM1)|(1<<USIWM0) '(1<<USICS1)|(0<<USICS0)|(0<<USICLK)|(0<<USICLK) USISR = &B11110000 '(1<<USISIF)|(1<<USIOIF)|(1<<USIPF)|(1<<USIDC) '(&H00<<USICNT) Return
Konfiguracija bitov USIWM1:0 = “11” postavlja USI v hold SCL način: ko prejme pričakovano število bitov, USI bo zadržal SCL linijo v stanju “0” dokler se ne zbriše USIOIF bit. To je legitimna situacija v I2C protokolu, ki daje slave-u malo dodatnega časa za obdelavo sprejete informacije; seveda ne sme trajati predolgo, ker blokira ves promet na vodilu.
* I2c_send_ack
Podprogram I2c_send_ack pripravlja USI za pošiljanje potrdila masterju (ACK), ko je od njega prejel lasten naslov ali neki podatek. V ta namen moramo SDA priključek konfigurirati kot izhod, v USIDR register pa vpisati 0 (= ACK):
I2c_send_ack: USIDR = 0 Config I2c_pin.sda = Output
V USISR registru brišemo vse zastavice razen USISIF, v USICNT pa vpisujemo 14, da bi ga konfigurirali za pošiljanje samo enega bita:
' (0<<USISIF)|(1<USIOIF)|(1<<USIPF)|(1<<USIDC) ' (&H0E<<USICNT) USISR = &B01111110 Return
* I2c_send_byte
Podprogram I2c_send_byte pripravlja USI za pošiljanje 8 bitov preko I2C vodila. SDA pin mora biti konfiguriran kot izhod, nakar čakamo, da se SCL vrne v stanje “0” in konfiguriramo USI:
I2c_send_byte: Config I2c_pin.sda = Output Bitwait I2c_pin.scl , Reset ' (0<<USISIE)|(0<<USIOIE)|(1<<USIWM1)|(0<<USIWM0) ' (1<<USICS1)|(0<<USICS0)|(0<<USICLK)|(0<<USITC) USICR = &B00101000
Konfiguracija bitov USIWM1:0 = “10” zagotavlja, da USI ne zadržuje SCL, ko pošlje vseh 8 bitov. V USISR registru brišemo vse zastavice, v USICNT pa vpišemo 0, da bi ga konfigurirali za pošiljanje 8 bitov:
'(1<<USISIF)|(1<<USIOIF)|(1<<USIPF)|(1<<USIDC) ' (&H00<<USICNT) USISR = &B11110000 Return
* I2c_wait_for_ack
Podprogram I2c_wait_for_ack pripravlja USI za prevzem potrditve (ACK) od masterja, ko mu je poslal neki podatek. S tem namenom moramo SDA priključek konfigurirati kot vhod, v USIDR register pa vpisati 0:
I2c_wait_for_ack: USIDR = 0 Config I2c_pin.sda = Input
V USISR registru brišemo vse zastavice razen USISIF, v USICNT pa vpišemo 14, da bi ga konfigurirali za sprejem samo enega bita:
' (0<<USISIF)|(1<USIOIF)|(1<<USIPF)| (1<<USIDC) ' (&H0E<<USICNT) USISR = &B01111110 Return
Če je master poslal ACK, bo vsebina USISR registra ostala 0 tudi po tem, ko USI sprejme en bit z I2C vodila.
Opombe:
po konfiguraciji USI vezja bo sprejel ali poslal zahtevano število bitov in nato postavil USIOIF bit. Ker ne uporabljamo prekinitev vezanih na USI, bomo stanje USIOIF bita preverjali v programski zanki znotraj makro instrukcije Wait_for_counter_overflow, katero kličemo neposredno po klicu komunikacijske rutine. Makro instrukcija preverja, če se je na I2C vodilu pojavil začasni START ali STOP, in v tem primeru vrača kontrolo na začetek programa.
če med izvrševanjem naloge, za katero smo konfigurirali USI vezje, pride do nepredvidenega zastoja, to je če master preneha generirati clock ali če nek drugi slave blokira SCL linijo, USI ne bo postavil USIOIF bita in program se bo zavrtel znotraj Wait_for_counter_overflow makro instrukcije. V tem primeru bo Watchdog po izteku ene sekunde resetiral mikrokontroler in tako vrnil program v izvršno stanje.
za popolno razumevanje je potrebno pogledati celoten program v knjižnici ATtiny_I2Cslave_USI_bascom.sub, v katerem je smisel posameznih postavk USICR in USISR registrov bolj izdatno komentiran, kot smo lahko napravili v članku.
knjižnica ATtiny_I2Cslave_USI.sub je identična opisani, samo da so vse rutine pisane v asemblerju. Vse spremenljivke in ključni podprogrami imajo ista imena.
knjižnica ATtiny_I2Cslave_noUSI.sub je funkcionalno identična opisani, vendar je komunikacijski protokol rešen na softverskem nivoju. Vse spremenljivke in ključni podprogrami imajo enaka imena.
Opomba za Arduino uporabnike
V Arduino IDE rešitvi smo uporabili projekt ATTinyCore avtorja Space Konde, ki uspešno podpira I2C protokol s pomočjo knjižnice z imenom “Wire”. Prav tako uporablja USI vezje mikrokontrolerja, vendar je koncept popolnoma drugačen od Bascom-AVR rešitve: dogodki na I2C vodilu aktivirajo vektorje prekinitev USI_START in USI_OVF, ki so zaradi lažje berljivosti dobili “vzdevke” USI_START_VECTOR in USI_OVERFLOW_VECTOR. Izvorni program se nahaja v direktoriju “packages/ATTinyCore/hardware/avr/1.5.2/libraries/wire”; tukaj ga ne bomo analizirali, ker gre za tuje avtorsko delo. Sam direktorij “packages” se nahaja na različnih mestih, odvisno od operacijskega sistema on od tega, ali uporabimo prenosno verzijo Arduino IDE.
Knjižnice ATtiny_I2Cslave_USI_bascom.sub., ATtiny_I2Cslave_USI.sub in ATtiny_I2Cslave_noUSI.sub lahko brezplačno dobite v uredništvu revije Svet elektronike.
ATtiny_I2Cslave_noUSI