Hallo,
ich versuch mich gerade mit einem Tiny zu arrangieren, aber irgendwie
will es nicht so recht klappen.
Ich muss zugeben, dass ich mit Assembler nicht viel am Hut habe, aber
mit C kann man wohl keine genauen Timings erreichen.
Wollte mir also eine Funktion basteln, die einen String übergeben
bekommt und diesen über die serielle Schnittstelle verschickt.
1
voidusiWrite(uint8_t*p,uint8_ts){
2
uint8_ti=0;
3
uint8_tdata;
4
5
do{
6
USIDR=p[i];
7
asmvolatile("ldi r16, (1 << 5) \n\t"
8
"ldi r17, (1 << 5) | (1 << 1) \n\t"
9
"sts USICR, r16\n\t"
10
...
11
"sts USICR, r17\n\t");
12
}while(++i<s);
13
}
Der Linker kann den Registernamen USICR nicht auflösen.
Habe versucht, mit dem Tutorial von Roboternetz mein Problem zu lösen -
allerdings scheitert es wohl daran, dass mir viele Begrifflichkeiten
nicht klar sind.
Könnte ich p[i] auch als Parameter an das inline-construct übergeben?
Wenn ja, wie wären da die korrekten asm-Befehle?
Bin für jeden Tip dankbar.
> Ich muss zugeben, dass ich mit Assembler nicht viel am Hut habe, aber> mit C kann man wohl keine genauen Timings erreichen.
Es gibt aber in der avr-libc Funktionen, die das anbieten.
> asm volatile ("ldi r16, (1 << 5) \n\t"> "ldi r17, (1 << 5) | (1 << 1) \n\t"> "sts USICR, r16\n\t">...> "sts USICR, r17\n\t");> Der Linker kann den Registernamen USICR nicht auflösen.
Du mußt ihn als Parameter übergeben. Wie das geht, ist in der
avr-libc-Doku beschrieben. Da gibt es eine Seite über inline-Assembler.
> Könnte ich p[i] auch als Parameter an das inline-construct übergeben?
Ja.
> Wenn ja, wie wären da die korrekten asm-Befehle?
Da würde ich dich auch an die avr-libc-Doku verweisen. Da ist das mit
Beispielen gut beschrieben.
Hallo Rolf,
danke für Deine Antwort.
Der Hinweis auf die Doku der avr-libc bringt mich nicht wirklich weiter.
Das Tutorial von Roboternetz scheint ja eine Übersetzung der avr-libc
Seiten zu sein.
Wenn ich es schon auf Deutsch nicht verstehe, dann geht es mit Englisch
auch nicht besser.
> Es gibt aber in der avr-libc Funktionen, die das anbieten.
Was anbieten? - das genaue timing? Wie soll die Funktion wissen, was ich
tun will?
Ich habe alle Optimierungsstufen im AVR-Emulator durchdebuggt - bei
keiner hat das timing gestimmt. Deshalb wollte ich assembler einsetzen.
Die ganze Funktion in Assembler bekomme ich nicht hin, deshalb dachte
ich an inline ...
Hm - hättest Du nicht nen kleinen Tip?
Vielleicht die Rubrik in der ich die Funktionen finden könnte?
Vermutlich muss ich das Register irgendwie deklarieren.
Nur weiß ich nicht, wo liegt das Register der seriellen Schnittstelle?
Die Ports liegen ja wohl im IO-Bereich, mehr konnte ich bislang nicht
rausfinden.
... und was ist ein SFR?
Der Inline Assembler ist wirklich ein bisschen haarig, aber warum sagst
DU nicht erstmal was du willst, mit welchem AVR du kämpfst, etc. DANN
können wir (Leser/Forenbesucher) dir evtl. Tipps geben.
hth. Jörg
ps.: SFR heißt 'Special Function Register'
Fred S. wrote:
> Gemeint war vermutlich, dass Du die SFR innerhalb des Inline Assembler> Codes z.B. so ansprechen kannst: _SFR_IO_ADDR(USICR) .
Nein, das geht nur für eine reguläre Assemblerdatei (und dann auch
nur, wenn sie sich selbst <avr/io.h> reinzieht und durch den
Präprozessor geschickt wird). Für den Inline-Assembler hat Rolf
schon richtig bemerkt, dass man sie Portadressen als Parameter
übergeben muss.
Der Inline-Assembler ist ein Werkzeug für Bibliotheksentwickler und
erfahrene Anwender. Er greift ganz tief in den Compiler rein, bietet
damit maximale Flexibilität und bestmögliche Integration in den
Optimierungsprozess, aber für die Benutzung durch eher Gelegenheits-
anwender oder gar Anfänger ist er denkbar ungeeignet.
Wenn es schon Assembler sein muss (wovon ich mit den paar Codeschnipseln
da keineswegs überzeugt bin), dann ist eine separate Assemblerdatei in
der Regel sehr viel besser zu pflegen und zu verstehen.
Ich habe neulich SPI über eine USI machen müssen. Habe auch erstmal
das Beispiel aus dem Datenblatt in eine .S-Datei gehämmert. Irgendwann
kam ein Freund mit diesem C-Code daher:
1
uint8_t
2
SPITransfer(uint8_td)
3
{
4
USIDR=d;
5
USISR=_BV(USIOIF);
6
do{
7
USICR=_BV(USIWM0)|_BV(USICS1)|
8
_BV(USICLK)|_BV(USITC);
9
}while((USISR&_BV(USIOIF))==0);
10
returnUSIDR;
11
}
und was soll ich sagen? Der generierte Assemblercode daraus
unterscheidet sich nur minimal von meinem handgefrickelten.
Hallo,
herzlichen Dank für Eure Unterstützung.
@Jörg X.
Ich will eine serielle Kommunikation über eine USI-Schnittstelle eines
Tiny2x machen, bei dem beide Partner unterschiedlichen Bitreihenfolgen
erwarten.
Eine Idee war, im AVR mit "falscher" Bitreihenfolge zu arbeiten, um die
dann richtig zu übertragen - aber die Daten werden teilweise aus Zahlen
zusammengesetzt - das war mir dann etwas zu heftig.
Inzwischen habe ich die statischen Protokollfetzen in falscher
Bitreihenfolge abgelegt und tausche nur noch die Berechnungs-Ergebnisse.
Mein erster Ansatz sah ungefär so aus:
1
voidtransfer(uint8_t*p,uint8_ts){
2
uint8_tn=0;
3
uint8_ti=0;
4
uint8_tj=7;
5
uint8_tdata;
6
7
do{
8
data=p[n];
9
10
i=0;
11
j=7;
12
do{
13
if(data&1<<i)PORTx|=1<<j;
14
elsePORTx&=(uint8_t)~(1<<j);
15
j--;
16
}while(++i<8);
17
}while(++n<s);
18
}
Im Emulator stellte sich dann heraus, dass es die benötigten Takte davon
abhingen, ob das Bit im Port tatsächlich verändert werden musste.
Je nach Optimierungseinstellung kamen völlig andere Zeiten raus.
Ich hatte kurz zuvor ein Update des WinAVR durchgeführt und kam bei -Os
auf 480 Systemtakte pro Schleifendurchlauf um 1 Bit zu übertragen.
Nachdem ich wieder zurückgewexelt hatte, war ich bei 48 Takten - was mir
immer noch zu viel ist.
@Fred S.
Danke für die Links, aber die liegen schon bei mir auf der Platte.
Es ist nicht so, dass ich nix zu lesen hätte, mir fehlt irgendwo ein
kleines Stück (Verständnis), sodass ich das Puzzle nicht zusammen
bekomme.
@Jörg Wunsch
> Nein, das geht nur für eine reguläre Assemblerdatei (und dann auch> nur, wenn sie sich selbst <avr/io.h> reinzieht und durch den> Präprozessor geschickt wird).
An dem Punkt war ich auch angekommen.
> Für den Inline-Assembler hat Rolf schon richtig bemerkt, dass man sie> Portadressen als Parameter übergeben muss.
Da wußte ich nimmer weiter. Zählt das USICR Register jetzt zu den
IO-Registern, die man mit dem IO-Makro übergeben muss?
Ich habe dann nochmal intensiver das Datenblatt angeschaut und (endlich)
die Adressen der Register bei den Beschreibungen entdeckt.
Beim USICR Register steht Adresse 0x0D und in Klammern 0x2D - welche
davon muss ich verwenden? Was ist die Bedeutung der unterschiedlichen
Adressen.
Zu dem C-Schnipsel:
Ich betreibe die USI im Modus 1 (2 Leitungsmodus), bei dem man den Clock
per Software erzeugen muss.
> Wenn es schon Assembler sein muss (wovon ich mit den paar Codeschnipseln> da keineswegs überzeugt bin), dann ist eine separate Assemblerdatei in> der Regel sehr viel besser zu pflegen und zu verstehen.
Hm - das mag wohl stimmen.
Nur da ich Assembler gerade mal aussprechen kann, tu ich mich doch sehr
schwer mit Zeiger-Arithmetik u.ä., was mich in C gerade mal ein Lächeln
kostet.
Den Aufruf einer Assembler-Funktion aus der C-Funktion heraus wollte ich
mir wegen der Call- und Ret-Timings ersparen.
Für einen Tiny2X mag die Programmierung dieser Teile in Assembler in
der Tat die effektivste Methode sein. Wenn du noch 256 Bytes frei
hast im ROM kannst du die Bitinvertierung auch mit einer lookup table
machen.
Ansonsten: schreib mir den C-Code mal auf, den du für die USI nehmen
würdest, und ich schreibe ihn dir so um, dass er funktioniert
einschließlich timing, ohne dass du auf den kryptischen Inline-
Assembler ausweichen musst. Wird aber erst nach Ostern.
> Ich will eine serielle Kommunikation über eine USI-Schnittstelle eines> Tiny2x machen, bei dem beide Partner unterschiedlichen Bitreihenfolgen> erwarten.
Warum gibst du nicht endlich an, welcher genau? Tiny2313, Tiny24,
Tiny25, Tiny26, ...?
> Zählt das USICR Register jetzt zu den IO-Registern, die man mit dem> IO-Makro übergeben muss?
Prinzipell gehört er zu den I/O-Registern (SFRs). Aber je nach Prozessor
muß man die unterschiedlich übergeben, da nur die ersten 64 I/O-Register
auch als solche angesprochen werden können. Die anderen müssen "memory
mapped" angesprochen werden, also so, wie man aufs RAM zugreift. Diese
Variante geht für alle Register, ist aber langsamer, vor allem wenn man
nur einzelne Bits verändern oder abfragen möchte.
> Beim USICR Register steht Adresse 0x0D und in Klammern 0x2D - welche> davon muss ich verwenden? Was ist die Bedeutung der unterschiedlichen> Adressen.
Das erste ist I/O-Adresse, über die man per in/out/sbi/cbi/sbic/sbis auf
das Register zugreifen kann (_SFR_IO_ADDR). Das zweite ist die Adresse,
über die man "memory mappped" darauf zugreifen kann (_SFR_MEM_ADDR).
Diese beiden Adressen liegen immer genau um 0x20 auseinander, weil auch
die ALU-Register r0 bis r31 in den Adressraum gemapt sind und noch vor
den I/O-Registern liegen.
Schauen wir uns deinen Code an:
> asm volatile ("ldi r16, (1 << 5) \n\t"> "ldi r17, (1 << 5) | (1 << 1) \n\t"
Du darfst nicht einfach irgendwelche Register verändern, ohne das dem
Compiler mitzuteilen, denn es kann sein, daß er gerade darin eine lokale
Variable liegen hat, die du damit einfach überschreibst. Du mußt immer
daran denken, daß der Compiler absolut keine Ahnung hat, was du in einem
Inline-Assembler-Schnipsel tust.
Es gibt beim Inline-Assembler drei Listen, und zwar die Liste der
Augangsoperanden, über die du Werte aus dem Inline-Assembler ans
C-Programm übergeben kannst, dann die Liste aus Eingangsoperanden, und
als letztes noch die Clobber-Liste, in die du einträgst, was deine
asm-Anweisung intern verwendet. Da gehören diese beiden Register rein.
Allerdings würde ich hier keine hartcodierten Register verwenden,
sondern dem Compiler die Wahl überlassen.
> "sts USICR, r16\n\t"> ...> "sts USICR, r17\n\t");
Hier greifst du über das memory-mapped-Interface auf USICR zu, also mußt
du einen Eingabeparameter mit _SFR_MEM_ADDR dafür machen. Allerdings
könntet du eigentlich besser mit sbi und cbi arbeiten, dann könntest du
dir das ldi ganz sparen, je nachdem, was in "..." noch steht.
Also kommt irgendwas in der Art raus:
asm volatile("sbi %[usicr], 1\n\t"
...
"cbi %[usicr], 1\n\t"
: : [usicr] "I" (_SFR_IO_ADDR(USICR)));
Hallo Jörg,
> Nein, das geht nur für eine reguläre Assemblerdatei ....
Natürlich hast Du recht -- tut mir leid, dass ich hier Unsinn verbreitet
habe. Eigentlich sollte ich mich nicht zum Inline Assembler äußern, denn
den nehme ich höchstens mal für einen Einzeiler, da ich bei Bedarf für
Assembler in C Programmen immer "*.S" Dateien erzeuge.
Viele Grüße
Fred
Hallo,
> Warum gibst du nicht endlich an, welcher genau? Tiny2313, Tiny24, Tiny25,> Tiny26, ...?
sorry, wollte kein Geheimnis draus machen - ich dachte nicht, dass es
wichtig wäre.
Also das Baby soll ein Tiny261 werden.
Im Augenblick treibe ich mich nur im Emulator rum.
@Rolf Magnus
Herzlichen Dank für Deine ausführliche Antwort!
Schätzte jetzt habbichs kappiert :)
Fühlt sich auf jeden Fall so an, als ob ich jetzt weiter kommen würde.
Danke auch für das Beispiel. Das gibt doch neuen Mut, die
Inline-Funktion nochmal anzugehen.
Das mit dem Register überschreiben leuchtet mir ein - die Namensvergabe
des Beispiels gefällt mir sehr gut. So werde ich es machen.
@Jörg Wunsch
Danke für das Angebot.
Denke, dass ich mit dem Beispiel von Rolf klar komme.
Für eine Lookup-Table hat der Winzling keinen Platz.
@all
Habe versuchsweise mal den asm-Aufruf auskommentiert. Bin inzwischen bei
75% Speicherauslastung - schätze da geht nimmer viel. Muss aber auch
nicht. Habe den Rest fast fertig.
Nochmals Danke für Eure Geduld und Unterstützung.
Santiago wrote:
> Danke für das Angebot.> Denke, dass ich mit dem Beispiel von Rolf klar komme.
Du hast mich falsch verstanden: ich wollte dir das in C so schreiben,
dass du damit vernünftigen Code erhälst.
Hallo,
kleine Erfolgsmeldung:
Habe jetzt die gleiche Funktion in C und inline-Assembler ausgetestet.
Der Simulator zeigt für die C-Funktion 501 Systemtakte, für die
Assemblerfunktion 370 Systemtakte - zur Übertragung eines Strings von 8
Byte.
Da bin ich doch sehr zufrieden.
Danke Euch allen.
P.S. es ist mein erstes µC-Proggy :)
Santiago wrote:
> Im Emulator stellte sich dann heraus, dass es die benötigten Takte davon> abhingen, ob das Bit im Port tatsächlich verändert werden musste.> Je nach Optimierungseinstellung kamen völlig andere Zeiten raus.
Natürlich, du hast einen branch mit unterschiedlichen
Berechnungszweigen. Bit-reversal macht man eigentlich gerne so:
1
uint8_trev(uint8_tx)
2
{
3
x=(x>>4)|((x<<4)&0xf);
4
x=((x>>2)&0x33)|((x<<2)&0xcc);
5
x=((x>>1)&0x55)|((x<<1)&0xaa);
6
returnx;
7
}
Der Compiler macht bei mir daraus 23 Instruktionen um ein byte
umzudrehen.
Ich habe so in Erinnerung, dass die einfache Schleife beim Umdrehen
von 8 Bits in etwa gleich großen Code erzeugt wie der Trick mit
den Bitmasken. Müsste es aber mal wieder nachzählen gehen.
Bei größeren Zahlen lohnt sich der Trick auf jeden Fall.
Ich weiß nicht ganz genau, ob ich dich richtig verstanden habe.
Aber folgendes Schnipsel ,,manuelles SPI'':
1
#include<avr/io.h>
2
3
void
4
manual_spi(uint8_ti)
5
{
6
uint8_tj=8;
7
8
PORTB&=~4;// slave select
9
do{
10
if(i&1)
11
PORTB|=1;// 1-bit
12
else
13
PORTB&=~1;// 0-bit
14
i>>=1;
15
PORTB|=2;// SCK
16
PORTB&=~2;
17
j--;
18
}while(j);
19
PORTB|=4;
20
}
müsste doch das sein, was du machen willst, oder?
Das compiliert bei mir (-Os) für den ATtiny261 in 15 Befehle (26
Bytes) Code, einschließlich des RET am Ende. Die Abarbeitungszeit
beträgt worst case 103 Takte, wobei der Unterschied zwischen einem
Schleifendurchlauf mit 1-Bit 12 Takte zu einem Durchlauf mit 0-Bit
11 Takte beträgt. Wenn dich diese leichte Asymmetrie noch stört,
kannst du im else-Zweig der if-Anweisung noch einen NOP einbauen.
Die Werte entsprechen dann einer effektiven Bitrate von ca f[CPU] / 5
für die USI-basierte Implementierung. Wenn du statt desn ATtiny261
einen ATmega48 nehmen würdest, hättest du eine Hardware-SPI, die das
alles (auch das Vertauschen der Bitreihenfolge) in Hardware macht und
würdest auf fast f[CPU] / 2 kommen.
Hallo Jörg,
herzlichen Dank für Deine Mühen.
Habe erst heute entdeckt, was Du noch alles geschrieben hast.
Inzwischen ist das Baby eine Tina2313 geworden - also immer noch USI
Schnittstelle. Bei derartigen Spitzfindigkeiten merke ich, dass mein
Englisch noch nicht perfekt ist.
Was (für mich) erschwerend hinzukommt:
Ich habe garkeinen Zugriff auf die Hardware - ich programmiere nur für
einen Freund und muss anhand seiner Rückmeldungen meine Fehler finden.
Inzwischen habe ich auch gemerkt, dass eine Bitänderung in C teuerer ist
als eine Zuweisung. Das Ändern der (Bit-)Reihenfolge mache ich in der
Protokollschicht, sodass ich inzwischen bei folgender
Übertragungsfunktion angelangt bin (ziemlich ähnlich Deiner letzten
Variante):
(die defines liegen natürlich in einer separaten h-Datei. Habe sie nur
der Vollständigkeit halber dazu kopiert)
.
Santiago wrote:
> ..., sodass ich inzwischen bei folgender> Übertragungsfunktion angelangt bin (ziemlich ähnlich Deiner letzten> Variante):
Außer dass du zum masochistischen Inline-Assembler gegriffen hast,
obwohl man das genauso gut in C schreiben könnte. ;-)
> "mov %[usicr], %0\n\t"
Muss das nicht so heißen?
>>"OUT %[usicr], %0\n\t"
^^^
Bei den AVRs kopiert mov doch nur zwischen den 'General Purpose'
Register herum...
hth. Jörg
ps.: bei
> "a" (COMM_CLOCK_LOW)
bin ich mir gerade nicht sicher was daraus wird, wird das zu einem
"simple upper register" mit
- dem Inhalt COMMLOCK_LOW, oder
- der Nummer COMMLOCK_LOW
?
...der inline-Asm ist eben kompliziert ;)
Jörg Wunsch wrote:
> Außer dass du zum masochistischen Inline-Assembler gegriffen hast,> obwohl man das genauso gut in C schreiben könnte. ;-)
Ich hatte es auch erst in C (ähnlich Deinem Code), aber der Simulator
zeigte mir einen Bedarf von 5 Systemtakten pro Zuweisung (also pro Bit).
Mit der Assembler-Variante sind es 18 Takte pro Byte.
Sonst hätte ich mir dat auch nicht angetan :/
Jörg X. wrote:
> ps.: bei> > "a" (COMM_CLOCK_LOW)> bin ich mir gerade nicht sicher was daraus wird, wird das zu einem> "simple upper register" mit> - dem Inhalt COMMLOCK_LOW, oder> - der Nummer COMMLOCK_LOW ?
wäre dann ein "i" statt dem "a" richtiger?
>bin ich mir gerade nicht sicher was daraus wird, wird das zu einem>"simple upper register" mit> - dem Inhalt COMMLOCK_LOW, oder> - der Nummer COMMLOCK_LOW>?
Ok, ein kurzer Test hat nr 1 - Inhalt - bestätigt, da hab ich also auch
wieder was gelernt :D
>wäre dann ein "i" statt dem "a" richtiger?
MMh. ein "i" finde ich in der doku gar nicht, aber um das Programm kurz
zu halten, solltest du "a" oder "d" nehmen (dann kann der Compiler
"COMMLOCK" mit "ldi" direkt laden).
Ich sehe aber bei einer Inline-Asm-Variante keine Vorteile gegenüber
reinem C-Code.
hth. Jörg
Santiago wrote:
> Ich hatte es auch erst in C (ähnlich Deinem Code), aber der Simulator> zeigte mir einen Bedarf von 5 Systemtakten pro Zuweisung (also pro Bit).> Mit der Assembler-Variante sind es 18 Takte pro Byte.
Kann nicht sein. ;-)
Ein Zugriff auf ein IO-Register dauert einen Takt, ein Zugriff über
Speicheradressen dauert zwei Takte. Jedes Bit braucht zwei
Zugriffe.
Die einzige (rein ästhetische) Unschönheit an der C-Variante ist, dass
das erste Bit einen Takt länger dauert: das Laden der beiden
Konstanten in Register erfolgt dort so, dass zuerst eine Konstante
geladen und ausgegeben wird, danach wird die zweite in ein Register
geladen und ausgegeben. Ab diesem Zeitpunkt werden die in den
Registern stehenden Konstanten dann immer wechselseitig ausgegeben.
Deine Variante dagegen würde zuerst beide Konstanten in Register
laden.
Die Frage nach dem MOV ist aber berechtigt.
Hallo Jörg und Jörg,
herzlichen Dank für Eure Aufmerksamkeit, Nachsicht und Geduld.
Jörg X. wrote:
> Ok, ein kurzer Test hat nr 1 - Inhalt - bestätigt, da hab ich also auch> wieder was gelernt :D
Danke für den Test!
Habbich zumindest in einem Punkt mal Glück gehabt.
"i", bzw. "I" wurde im Beitrag von Rolf Magnus verwendet - und laut
Roboternetz-Tutorial würde das eine Konstante bezeichnen.
Wenn das mit "a" geklappt hat - ist es auch gut :)
Jörg Wunsch wrote:
> Kann nicht sein. ;-)
Du hast Recht - ich muss Abbitte leisten.
Weil es mit der USI-Schnittstelle nicht klappen will, habe ich mehrere
Testprogramme nebeneinander erstellt und bei dem letzten vergessen, die
Optimierung einzuschalten. Ich hatte also Äpfel mit Birnen verglichen.
Sorry.
Gestern abend haben wir dann noch Deinen Code ausprobiert (ich im
Simulator, mein Freund in HW) - die Takte im Simulator stimmen.
Leider tut sich an der Hardware nix.
Eine Idee, was ich übergesehen haben könnte?
Die Schnittstelle ist eine Mischung aus TWI und SPI - es gibt eine
Taktleitung, eine Datenleitung (Lese- und Schreibdaten auf gleicher
Leitung)
und eine Registerselect-Leitung (ähnlich der RS-Leitung bei
Standard-LCDs).
In der Initialisierung habe ich alle 3 Leitungen als Ausgang geschaltet,
denn im Gegensatz zu TWI ändert der Slave keine Pegel.
Wenn die USI-Übertragung läuft, tut sich an den Pins überhaupt nix.
Ich bin radlos.
Ich habe USIWM1 auf 1 gesetzt, USIWM0 auf 0. Beide auf 1 hatte ich nicht
verwendet, weil ein idle clock high sein soll.
> Die einzige (rein ästhetische) Unschönheit an der C-Variante ist, dass> das erste Bit einen Takt länger dauert
Ich denke, das ist nicht von Bedeutung. Wichtig ist, dass die 8 Bit
eines Bytes alle gleiches Timing und einen symmetrischen Takt haben.
Zwischen den Bytes muss der Takt nicht weiterlaufen.
Ach ja, die Tina läuft auf 3.2V mit internem Takt und zum Testen haben
wir den Vorteiler von 8 noch drin gelassen (soll mal raus wenn alles
funzt).
Santiago wrote:
> Ich habe USIWM1 auf 1 gesetzt, USIWM0 auf 0. Beide auf 1 hatte ich nicht> verwendet, weil ein idle clock high sein soll.
Hast du denn externe Pullups dran? Du hast two-wire mode eingeschaltet,
da sind die Ausgänge im Datenblatt als `open collector' bezeichnet
(vermutlich sind sie in der Tat aber open drain :-). Wenn du dann
keine externen Pullups dran hängst (wie halt bei richtigem I²C), dann
wirst du da kaum einen Pegel messen können...
Mein Beispiel war für SPI via USI, nicht TWI.