Forum: Compiler & IDEs Inline Assembler und Register Zugriff


von Santiago (Gast)


Lesenswert?

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
void usiWrite(uint8_t *p, uint8_t s) {
2
   uint8_t i=0;
3
   uint8_t data;
4
5
   do {
6
      USIDR = p[i];
7
      asm volatile ("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.

von Rolf Magnus (Gast)


Lesenswert?

> 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.

von Santiago (Gast)


Lesenswert?

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?

von Jörg X. (Gast)


Lesenswert?

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'

von Fred S. (Gast)


Lesenswert?

Hi Santiago,
>> 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?
Gemeint war vermutlich, dass Du die SFR innerhalb des Inline Assembler 
Codes z.B. so ansprechen kannst: _SFR_IO_ADDR(USICR) .

Viele hilfreiche Tipps stehen hier: 
http://www.nongnu.org/avr-libc/user-manual/FAQ.html

Mein Lieblingstext ist dieser: 
http://www.stanford.edu/class/ee281/projects/aut2002/yingzong-mouse/media/GCCAVRInlAsmCB.pdf

Gruß

Fred

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

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_t d)
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
        return USIDR;
11
}

und was soll ich sagen?  Der generierte Assemblercode daraus
unterscheidet sich nur minimal von meinem handgefrickelten.

von Santiago (Gast)


Lesenswert?

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
void transfer(uint8_t * p, uint8_t s) {
2
   uint8_t n=0;
3
   uint8_t i=0;
4
   uint8_t j=7;
5
   uint8_t data;
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
         else               PORTx &= (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.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

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.

von Rolf Magnus (Gast)


Lesenswert?

> 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)));

von Fred S. (Gast)


Lesenswert?

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

von Santiago (Gast)


Lesenswert?

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.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

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.

von Santiago (Gast)


Lesenswert?

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 :)

von Luther B. (luther-blissett)


Lesenswert?

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_t rev(uint8_t x) 
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
  return x; 
7
}

Der Compiler macht bei mir daraus 23 Instruktionen um ein byte 
umzudrehen.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

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.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

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_t i)
5
{
6
        uint8_t j = 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.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Hier noch eine Variante mit USI und entrollter USI-Schleife für
maximale Geschwindigkeit:
1
#include <avr/io.h>
2
#include <avr/pgmspace.h>
3
4
static const uint8_t bitswap[] PROGMEM = {
5
        0b0000, 0b1000, 0b0100, 0b1100,
6
        0b0010, 0b1010, 0b0110, 0b1110,
7
        0b0001, 0b1001, 0b0101, 0b1101,
8
        0b0011, 0b1011, 0b0111, 0b1111
9
};
10
11
static inline uint8_t
12
swapbits(uint8_t i) {
13
        uint8_t rv, j;
14
15
        rv = pgm_read_byte(bitswap + (i & 0x0f));
16
        j = pgm_read_byte(bitswap + ((i >> 4) & 0x0f));
17
        return rv | (j << 4);
18
}
19
20
void
21
usisend(uint8_t i)
22
{
23
        USIDR = swapbits(i);
24
        USICR = _BV(USIWM0) | _BV(USITC);
25
        USICR = _BV(USIWM0) | _BV(USITC) | _BV(USICLK);
26
        USICR = _BV(USIWM0) | _BV(USITC);
27
        USICR = _BV(USIWM0) | _BV(USITC) | _BV(USICLK);
28
        USICR = _BV(USIWM0) | _BV(USITC);
29
        USICR = _BV(USIWM0) | _BV(USITC) | _BV(USICLK);
30
        USICR = _BV(USIWM0) | _BV(USITC);
31
        USICR = _BV(USIWM0) | _BV(USITC) | _BV(USICLK);
32
        USICR = _BV(USIWM0) | _BV(USITC);
33
        USICR = _BV(USIWM0) | _BV(USITC) | _BV(USICLK);
34
        USICR = _BV(USIWM0) | _BV(USITC);
35
        USICR = _BV(USIWM0) | _BV(USITC) | _BV(USICLK);
36
        USICR = _BV(USIWM0) | _BV(USITC);
37
        USICR = _BV(USIWM0) | _BV(USITC) | _BV(USICLK);
38
        USICR = _BV(USIWM0) | _BV(USITC);
39
        USICR = _BV(USIWM0) | _BV(USITC) | _BV(USICLK);
40
}

Compiliert zu 92 Bytes an Code (einschließlich der Tabelle), 45
Takte, wenn ich richtig gezählt habe (einschließlich der 4 Takte
für das RET).

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

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.

von Santiago (Gast)


Lesenswert?

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)
.
1
#define COMM_DATA       USIDR
2
#define COMM_BUFFER     USIBR
3
#define COMM_CTRL       USICR
4
#define COMM_STATUS     USISR
5
#define COMM_CLOCK_LOW  (1 << USIWM1 | 1 << USITC)
6
#define COMM_CLOCK_HIGH (1 << USIWM1 | 1 << USITC | 1 << USICLK)
7
8
9
void usiWrite(uint8_t *p, uint8_t s) {
10
   uint8_t n=0;
11
12
   do {
13
      COMM_DATA = p[n];
14
      asm volatile("mov %[usicr], %1\n\t"                // toggle Control register which in turn 
15
                   "mov %[usicr], %0\n\t"                // shifts the data out
16
                   "mov %[usicr], %1\n\t"
17
                   "mov %[usicr], %0\n\t"
18
                   "mov %[usicr], %1\n\t"
19
                   "mov %[usicr], %0\n\t"
20
                   "mov %[usicr], %1\n\t"
21
                   "mov %[usicr], %0\n\t"
22
                   "mov %[usicr], %1\n\t"
23
                   "mov %[usicr], %0\n\t"
24
                   "mov %[usicr], %1\n\t"
25
                   "mov %[usicr], %0\n\t"
26
                   "mov %[usicr], %1\n\t"
27
                   "mov %[usicr], %0\n\t"
28
                   "mov %[usicr], %1\n\t"
29
                   "mov %[usicr], %0\n\t"
30
                   : : [usicr] "I" (_SFR_IO_ADDR(USICR)), "a" (COMM_CLOCK_LOW), "a" (COMM_CLOCK_HIGH));      
31
   } while (++n < s); 
32
}

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

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. ;-)

von Jörg X. (Gast)


Lesenswert?

> "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 ;)

von Santiago (Gast)


Lesenswert?

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?

von Jörg X. (Gast)


Lesenswert?

>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

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

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.

von Santiago (Gast)


Lesenswert?

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).

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

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.

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.