Forum: Projekte & Code AVR TWI Master ausschließlich mit Interrupt in C


Announcement: there is an English version of this forum on EmbDev.net. Posts you create there will be displayed on Mikrocontroller.net and EmbDev.net.
von Jochen D. (joe_d1)


Angehängte Dateien:

Lesenswert?

Bei der Programmierung eines Atmega328P als Modbus-Slave (yaMBSiavr) und 
der  Abfrage eines I2C-Sensors alle 200ms mit der 
I2C-Software-Assembler-Bibliothek von Peter Fleury bekam ich immer 
wieder Timeouts auf dem Modbus.

Also probierte ich die TWI-Bibliothek von Peter Fleury, aber auch die 
"glänzt" mit Waits ;(

TWI-Bibliotheken findet man ja an jeder Ecke, nur eine die 100% und 
ausschließlich mit Interrupts funktioniert (ähnlich wie die uartlibrary 
von Peter Fleury) ist mir nicht untergekommen.

Als Basis habe ich https://github.com/scttnlsn/avr-twi genommen, eine 
als "nonblocking" beworbene Interrupt-Version die gleichmal solche 
lustige Sachen enthielt:
1
uint8_t *twi_wait() {
2
  while (busy);
3
  return &transmission.buffer[1];
4
}
5
6
void twi_write(uint8_t address, uint8_t* data, uint8_t length, void (*callback)(uint8_t, uint8_t *)) {
7
  twi_wait();
8
  [...]
9
}

Soviel zu "nonblocking" ;)

Hab' den ganzen Delay- und Wait-Mist rausgeschmissen, herausgekommen 
sind nun die angehängten Dateien die es auch unter 
https://github.com/joed74/avr-twi gibt.

Was nicht implementiert ist, ist repeated Start - bislang nutze ich also 
nur Write / Read jeweils mit Start/Stop.

Verwendet werden kann die Bibliothek z.B. so:
1
if (readtemp==1) {
2
    twi_write(0x44,0xFD);
3
    uint8_t length=6;
4
    uint8_t *buffer=NULL;
5
    if (twi_read(0x44,&length,&buffer)==TWI_OK) {
6
       if (length==6) {
7
            uint16_t t=buffer[0]*256+buffer[1];
8
            uint16_t h=buffer[3]*256+buffer[4];
9
            reg.temperature=-45.0f+175.0f*((float) t/65535.0f);
10
            reg.humidity=-6.0f+125.0f*((float) h/65535.0f);
11
            if (reg.humidity<0) reg.humidity=0;
12
            if (reg.humidity>100) reg.humidity=100;
13
       }
14
       readtemp++;
15
    }
16
}

readtemp wird von einem Timer alle 200ms auf 1 gesetzt. An den 
I2C-Sensor an Adresse 0x44 wird dann das Kommando 0xFD (Messung starten) 
gesendet. Der twi_write wird intern solange wiederholt bis ein ACK 
empfangen wird.

Der Sensor benötigt 10ms bis das Ergebnis abrufbar ist. In dieser Zeit 
wird auf eine Leseanfrage mit NACK geantwortet. Der twi_read wird 
dementsprechend solange wiederholt bis ein ACK empfangen wird. Wenn ein 
ACK empfangen wurde wird readtemp um 1 erhöht, somit wird dieser 
Programmteil erst wieder beim nächsten Setzen von 1 vom Timer 
ausgeführt.

Die Bibliothek ist so aufgebaut das nach einem twi_write intern ein 
twi_read "erwartet" wird. Wenn dieses Verhalten nicht gewünscht ist muss 
nach einem erfolgreichen twi_write ein twi_resetstate aufgerufen werden:
1
switch (operation) {
2
   case 1:
3
     if (twi_write(address, &data, 2)==TWI_OK) {
4
        twi_resetstate();
5
        operation++;
6
     }
7
     break;
8
9
   case 2:
10
     if (twi_write(address, &data, 2)==TWI_OK) {
11
        operation++;
12
     }
13
     break;
14
15
   case 3:
16
     if (twi_read(address, &length, &data)==TWI_OK) {
17
        operation++;
18
     }
19
     break;
20
}

Hier bietet sich eine "State-Machine" an, wenn der Rückgabewert des 
ersten twi_write TWI_OK (=ACK) ist kann mit twi_resetstate() der interne 
Status zurückgesetzt werden und die nächste Operation (ein weiteres 
twi_write) aufgerufen werden.

Insgesamt gibt es bis jetzt fünf Funktionen:
1
void twi_init()
Initialisiert das TWI(0) Interface
1
void twi_resetstate()
Setzt den internen Status zurück (siehe oben)
1
TWI_STATUS twi_write(uint8_t address, uint8_t* data, uint8_t length)
Schreibt den Buffer data mit der Länge length an die Adresse address, 
Rückgabe als TWI_STATUS: TWI_OK, TWI_BUSY oder TWI_NOK
1
TWI_STATUS twi_write1(uint8_t address, uint8_t data)
Schreibt 1 Byte an die angegebene Adresse, shortcut damit man keinen 
Buffer braucht
1
TWI_STATUS twi_read(uint8_t address, uint8_t *length, uint8_t **data)
Empfängt Daten von Adresse address. In data wird ein Pointer auf die 
empfangenen Daten zurückgegeben. Der Längenpointer length muss vor dem 
Aufruf mit der erwarteten Anzahl Bytes belegt werden, bei TWI_OK steht 
dort dann die Länge der empfangenen Daten drin.

Getestet wurde die Bibliothek mit Atmega324PB (twi0) und einem 
Atmega328P (twi). Ein Test mit Atmega328PB (twi0) steht noch aus..

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.