Ursprünglich wollte ich dieses USI ja ganz links liegenlassen und garnicht weiter beachten, aber nachdem die bit-banging-I2C-Funktionen hier ganz gut funktioniert haben, wollte ich mal rausfinden, ob das unter Einbeziehung vorhandener Hardware nicht noch einfacher und kleiner geht.
tl;dr: nö
Um nun nicht wieder bei Adam und Eva anzufangen, habe ich das mal nach dem zur AppNote 310* gehörigen C-Beispiel in Assembler nachgebaut.
Sieht vielleicht noch ein wenig unaufgeräumt aus und man kann sicher noch vieles verbessern. Ich habe bisher noch kein größeres Assemblerprogramm für AVRs geschrieben und kenne mich auch mit dem Zusammenspiel mit C nicht so gut aus. Die IO-Registerbezeichnungen aus der C-Headerdatei funktionieren in Assembler nicht problemlos, zumindest nicht für cbi, sbi, sbis, sbic, in und out. Außerdem habe ich wohl nicht ganz verstanden, warum es zwei um 0x20 verschiedene Adressen für ein und das selbe Register gibt und wann und warum und wofür der Compiler und der Assembler welche benutzt. Um nicht lange nach den Ursachen zu forschen, habe ich die problematischen Register einfach selbst (unter anderem Namen) definiert.
Es gibt zwei grundsätzlich unterschiedliche Herangehensweisen. Die eine aus der bit-banging-Version benutzt viele Funktionen. Neben init, stop und start gibt es noch repeatedStart, startWait, write, readAck und readNack. Die meisten davon müssen dann vom aufrufenden Programm benutzt werden.
Die andere aus der AN310 geht, ähnlich wie die xmega-Version von Atmel, über einen message-buffer, sodaß es neben init nur noch eine master-transceive-Funktion gibt. Das aufrufende Programm muß hier nur den buffer füllen und die transceive-Funktion aufrufen.
Möglicherweise liegt es an diesem etwas umständlicheren Weg über den buffer, daß diese Version mit ca. 200 Bytes (wenn man die Fehlerbehandlung ganz wegläßt) trotz benutzter Hardware noch größer als die bit-banging-Version mit 160 Bytes ist.
Da für 100 kHz ein delay von 4 – 5 µs gebraucht wird und bei F_CPU = 1 MHz ein Unterprogrammaufruf incl. return schon allein 7 µs braucht, ist das delay inline.
Bei einer höheren Taktfrequenz (min. 2 MHz, und für fast-I2C min. 8 MHz) könnte man durch Verwendung eines Unterprogramms für das delay nochmal gut 15 Bytes einsparen.
Im Beispiel kann während der Übertragung kein Interrupt auftreten. Ich habe nicht getestet, ob es mit Interrupts funktioniert. Sicherheitshalber würde ich sie – auch bei der bit-banging-Version – während der Übertragung lieber sperren.
Vielleicht hat ja mal jemand eine Idee eine kürzere USI-Version zu schreiben. Für mich vorerst das Fazit: Wenn der Controller echte I2C-Hardware hat, benutze ich sie. Wenn nicht, benutze ich die bit-banging-Version. Da kann man außerdem gegenüber der USI-Version die Pins völlig frei wählen.
*(jetzt: Atmel-2561-Using-the-USI-Module-as-a-I2C-Master_AP-Note_AVR310)
tl;dr: nö
Um nun nicht wieder bei Adam und Eva anzufangen, habe ich das mal nach dem zur AppNote 310* gehörigen C-Beispiel in Assembler nachgebaut.
Sieht vielleicht noch ein wenig unaufgeräumt aus und man kann sicher noch vieles verbessern. Ich habe bisher noch kein größeres Assemblerprogramm für AVRs geschrieben und kenne mich auch mit dem Zusammenspiel mit C nicht so gut aus. Die IO-Registerbezeichnungen aus der C-Headerdatei funktionieren in Assembler nicht problemlos, zumindest nicht für cbi, sbi, sbis, sbic, in und out. Außerdem habe ich wohl nicht ganz verstanden, warum es zwei um 0x20 verschiedene Adressen für ein und das selbe Register gibt und wann und warum und wofür der Compiler und der Assembler welche benutzt. Um nicht lange nach den Ursachen zu forschen, habe ich die problematischen Register einfach selbst (unter anderem Namen) definiert.
Es gibt zwei grundsätzlich unterschiedliche Herangehensweisen. Die eine aus der bit-banging-Version benutzt viele Funktionen. Neben init, stop und start gibt es noch repeatedStart, startWait, write, readAck und readNack. Die meisten davon müssen dann vom aufrufenden Programm benutzt werden.
Die andere aus der AN310 geht, ähnlich wie die xmega-Version von Atmel, über einen message-buffer, sodaß es neben init nur noch eine master-transceive-Funktion gibt. Das aufrufende Programm muß hier nur den buffer füllen und die transceive-Funktion aufrufen.
Möglicherweise liegt es an diesem etwas umständlicheren Weg über den buffer, daß diese Version mit ca. 200 Bytes (wenn man die Fehlerbehandlung ganz wegläßt) trotz benutzter Hardware noch größer als die bit-banging-Version mit 160 Bytes ist.
Da für 100 kHz ein delay von 4 – 5 µs gebraucht wird und bei F_CPU = 1 MHz ein Unterprogrammaufruf incl. return schon allein 7 µs braucht, ist das delay inline.
Bei einer höheren Taktfrequenz (min. 2 MHz, und für fast-I2C min. 8 MHz) könnte man durch Verwendung eines Unterprogramms für das delay nochmal gut 15 Bytes einsparen.
Im Beispiel kann während der Übertragung kein Interrupt auftreten. Ich habe nicht getestet, ob es mit Interrupts funktioniert. Sicherheitshalber würde ich sie – auch bei der bit-banging-Version – während der Übertragung lieber sperren.
Vielleicht hat ja mal jemand eine Idee eine kürzere USI-Version zu schreiben. Für mich vorerst das Fazit: Wenn der Controller echte I2C-Hardware hat, benutze ich sie. Wenn nicht, benutze ich die bit-banging-Version. Da kann man außerdem gegenüber der USI-Version die Pins völlig frei wählen.
*(jetzt: Atmel-2561-Using-the-USI-Module-as-a-I2C-Master_AP-Note_AVR310)