Laboratorul 6: I2C (Inter-Integrated Circuit)

Acest laborator acoperă noțiunea de I2C. Pentru aprofundarea acestui topic, consultați Datasheet ATmega324P și Inter-Integrated Circuit.

1. I2C (Inter-Integrated Circuit)

Protocolul I2C (sau IIC - Inter-Integrated Circuit / TWI - Two-Wire Interface) este un protocol de comunicație serială sincron, multi-master - multi-slave, dezvoltat de către Phillips în anul 1982. O magistrală I2C utilizează următoarele semnale:

  • SDA - linia de date
  • SCL - semnalul de clock

Semnalul de ceas este generat de către master, însă linia de date este controlată ori de către master, ori de către slave, în funcție de faza protocolului, cu restricția ca, la un moment dat, un singur dispozitiv să transmită. Din acestă cauză protocolul I2C este half-duplex.

I2C mai este cunoscut și cu numele de TWI (Two-Wire Interface)

2. Modul de funcționare

Față de SPI unde master-ul activa, prin intermediul semnalului de Slave Select, dispozitivul cu care dorea să comunice, I2C nu necesită un asemenea semnal adițional. Protocolul I2C introduce noțiunea de Slave Address. Adresa unui dispozitiv de tip slave este un număr pe 7 biți (cel mai comun), pe 8 biți sau 10 biți. Comunicația dintre un master și un slave se face prin mesaje și este tot timpul inițiată de către master. Aceste mesaje pot fi sparte în două tipuri de cadre:

  • un cadru de adresă
  • unul sau mai multe cadre de date

Aceste cadre sunt interschimbate numai după ce master-ul a trimis condiția de start. Sfârșitul unui mesaj este identificat prin condiția de stop.

1. Condiția de start

Înainte ca master-ul să trimită pe linia de date adresa slave-ului cu care dorește să comunice, acesta trebuie sa genereze o condiție de start. Condiția de start determină toate dispozitivele slave să ”asculte” linia de date pentru că va urma o adresă. Pentru a genera această condiție, master-ul lasă linia SCL in HIGH și pune linia SDA pe LOW.

2. Cadrul de adresă

După ce masterul a generat condiția de start, acesta trimite pe linia de date (SDA) adresa dispozitivului slave cu care dorește să comunice. Adresa este (de cele mai multe ori) un număr pe 7 biți (biții A6-A0). Bitul 0 menționează dacă master-ul inițiază o operație de Citire (bitul 0 este 1) sau o operație de Scriere (bitul 0 este 0).

Slave-ul care își recunoaște adresa trimite un ACK master-ului prin punerea liniei SDA pe LOW în al nouălea ciclu de ceas. Starea default a liniilor SDA/SCL este HIGH datorită rezistențelor de pull-up. Master-ul/Slave-ul doar ”trag” liniile pe LOW.

Master-ul identifică dacă a primit ACK (SDA pus pe LOW) sau NACK (SDA a rămas HIGH pe durata celui de-al nouălea ciclu de ceas).

3. Cadrele de date

Dacă master-ul a primit ACK (dacă există un slave pe magistrală cu adresa respectivă), el poate continua cu transmiterea datelor (operație de scriere), sau cu recepția datelor (operație de citire). Numărul de cadre de date este arbitrar, pot fi interschimbate oricâte. Fiecare cadru trimis/recepționat este ACK'd sau NACK'd. În funcție de operație (citire sau scriere), ACK-ul/NACK-ul este trimis fie de master fie de slave.

  • Daca master-ul a inițiat o operație de scriere, fiecare cadru trimis este confirmat (ACK'd) de către slave.
  • Daca master-ul a inițiat o operatie de citire, fiecare cadru recepționat este confirmat de (ACK'd) de master. Când master-ul dorește să oprească tranzacția după ce un anumit număr de cadre a fost recepționat, în loc să trimită ACK trimite NACK. Astfel slave-ul se va opri din transmitere.

4. Condiția de stop

După ce toate cadrele de date au fost interschimbate, master-ul generează condiția de stop. Aceasta este realizată prin eliberarea liniei SDA (trecere din LOW în HIGH) după eliberarea liniei SCL (trecere din LOW în HIGH).

Magistrala este aceeași pentru toți senzorii iar fiecare slave este referit printr-o adresa. Astfel, dacă avem mai mulți slave conectați pe magistrala, masterul alege cu care dintre ei dorește sa initieze comunicatia, precizand adresa specificata în datasheetul senzorului. Cel mai adesea, se folosesc adrese reprezentate pe 7 biti, iar cel mai nesemnificativ bit din octetul adresei ne arata daca urmeaza o operatie de scriere sau citire. După identificarea dispozitivului cu care se comunică, se poate începe transmiterea datelor.

3. Registre configurare I2C

ATMega324P poate funcționa atât în modul I2C Master cât și I2C Slave.

TWI Bit Rate Register

  • TWBR selectează divizorul de frecvență care generează frecvența ceasului SCL în modurile master.

TWI Control Register

  • TWCR este folosit pentru a activa interfața I2C, inițiază accesul la master prin aplicarea condiției START, generează confirmarea receptorului, generează condiția de oprire și control al acesteia în timpul scrierii în registrul de date TWDR. De asemenea, indică coliziuni la scriere.
  • TWINT : TWI Interrupt Flag
    • 1 - Clear, setat din software, in rest este setat de hardware.
  • TWEA: TWI Enable Acknowledge Bit
    • 1 - ACK este trimis
    • 0 - NACK, deconectare virtuală a dispozitivului de pe interfață
  • TWEN: TWI Enable Bit
    • 1 - Permite comunicarea
    • 0 - Încheierea transmisiunii indiferent de stare

TWI Status Register

  • TWSR descrie statusul si prescalerul interfeței.

TWI Data Register

  • TWDR conține în modul de transmisie următorul octet de transmis iar în modul de recepție ultimul octet primit.

4. Utilizarea modulului I2C pe AVR

Procesul de configurare al modulului I2C pe microcontrollerele AVR este relativ simplu (trebuie setată doar frecvența ceasului):

I2C Setup:

void twi_init(void)
{
  // Initialize I2C to a 100KHz clock
  // TWI Status Register: initialize prescaler to 1
  TWSR = (0b00 << TWPS0);
  // TWI Bitrate Register: set bitrate
  // SCL_Freq = CPU_Freq / (16 + 2*TWBR * TWSR_Prescaler)
  // so: TWBR = (CPU_Freq / SCL_Freq  - 16) / (TWSR_Prescaler * 2)
  TWBR = 52; // (12000000/100000 - 16) / (1 * 2)
}

Însă pașii pentru a efectua o tranzacție completă pe I2C sunt complecși:

  • configurare TWCR pentru a transmite condiția de start;
  • așteptarea completării operațiunii (prin busy waiting pe TWSR);
  • scrierea adresei dispozitivului slave în registrul TWDR + așteptarea;
  • verificarea condiției de acknowledgement (tot prin TWSR);
  • transmiterea sau primirea a zero sau mai mulți bytes prin intermediul registrului de date TWDR (de asemenea, trebuie incluși pașii intermediari de așteptare a finalizării operațiilor);
  • în final, transmiterea condiție de stop printr-un alt flag din TWCR.

5. Senzor de presiune MPL3115A2

Plăcuța de laborator este dotată cu un senzor de presiune atmosferică și temperatură care poate comunica cu microprocesorul nostru prin protocolul I2C, acesta fiind și usecase-ul la care vom lucra astăzi. Pinout-ul pentru I2C este:

I2C GPIO Pin
I2C Enable PA6
SCL PC0
SDA PC1

Comunicarea cu senzorul se va face prin citirea/scrierea registrelor senzorului, folosind I2C. Pentru accesarea unui registru, atât pentru scriere cât și pentru citire, este necesară transmiterea adresei acestuia înainte de operație. Tabela cu registrele si adresele acestora, se găsește în Datasheet, în secțiunea 14.

6. Exerciții

Descărcați arhiva cu scheletul de cod. Urmăriți indicațiile din TODO-uri.

Task 0. Completați corpul funcțiilor twi_init, twi_start și twi_stop din fișierul twi.c.

Definițiile constantelor le găsiți în avr-libc util/twi.h!

Task 1. Completați corpul funcțiilor twi_read_ack, twi_read_nack și twi_write din fișierul twi.c.

HINT: Exemplul de cod C din tabelul 23-2, pagina 268 din Datasheet ATmega324P vă arată pașii compleți pe care trebuie să îi urmați pentru a efectua o scriere.

Task 2. Completați corpul funcției twi_discover din fișierul twi.c. Funcția trebuie să trimită un SLA_R (slave read) către toate adresele posibile pentru I2C slaves (0-127), pentru a determina ce dispozitive sunt conectate pe magistrala de I2C. Afișați pe serial adresele dispozitivelor conectate și nu uitați să apelați funcția în main.

HINT: Registrul TWSR va indica dacă dispozitivul a trimis un ACK. Tabelul 23-4, pagina 275 din Datasheet ATmega324P vă descrie valorile de status.

Pentru a indica faptul că urmează să se facă o citire de tipul Master Receiver, trebuie trimisă prima dată adresa dispozitivului de pe magistrala I2C de la care dorim să primim date. Dacă dispozitivul “își recunoaște” adresa, ne va trimite un ACK. Pentru că dorim doar să descoperim dispozitivele, este suficient doar un “ping” de acest fel, fără o citire de date ulterioară.

  • Adresă pentru citire:
     (DEVICE_ADDRESS << 1) | 1 
  • Adresă pentru scriere:
     DEVICE_ADDRESS << 1 

Conform documentației, adresa senzorului nostru este 0x60 (deci, adresa pentru citire este 0xC1, iar cea pentru scriere este 0xC0).

Task 3. În următoarele task-uri, vom folosi senzorul MPL3115A2, prezentat anterior. Pentru a putea configura și citi valorile registrelor senzorului, vom urmări pașii din diagrama de stări de mai jos, preluată din Datasheet MPL3115A2.

HINT: Exemplul oferit setează registrul CTRL_REG1 astfel încât senzorul să se comporte ca un altimetru. Pentru că dorim să calculăm presiunea, setați corespunzător bit-ul ALT.

Task 3.1. Pentru început, vrem să inițializăm senzorul. Completați corpul funcției mpl3115a2_init din fișierul mpl3115a2.c. Aceasta va trebui apelată în cadrul funcției main, înainte de loop.

Task 3.2. Completați corpul funcțiilor mpl3115a2_read_pressure și mpl3115a2_read_temperature din fișierul mpl3115a2.c. Acestea vor trebui apelate în cadrul funcției main, în interiorul loop-ului. Afișați valorile citite pe serial.

HINT 1: În secțiunea 14 a Datasheet-ului găsiți semnificația registrelor.

HINT 2: Formula pentru a calcula presiunea în Pa este:

/* Q18.2 fixed point format where there are 18 integer bits and two fractional bits */
PRESSURE = ((OUT_P_MSB << 12) | (OUT_P_CSB << 4) | (OUT_P_LSB >> 4)) >> 2

Pașii pentru a citi dintr-un registru, folosind funcțiile din task-urile 0 și 2, sunt:

/* Send START condition */
twi_start();
 
/* Write SLA_W on the I2C bus because we're going to send the register address next */
twi_write(DEVICE_ADDRESS << 1);
 
/* Write the register address */
twi_write(REGISTER_ADDRESS);
 
/* Send REPEATING START condition */
twi_start();
 
/* Write SLA_R on the I2C bus because we're going to read */
twi_write((DEVICE_ADDRESS << 1) | 1);
 
/* Read from the sensor. The last read must be sent with NACK */
uint8_t data = 0x00;
twi_read_nack(&data);
 
/* If we're not going to do anything next, send STOP condition */
twi_stop();

7. Linkuri utile

8. Responsabili laborator

pm/lab/lab6-2023-2024.txt · Last modified: 2024/04/15 18:35 by victor.stoica0203
CC Attribution-Share Alike 3.0 Unported
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0