mikrocontroller.net

Forum: Compiler & IDEs Inline Assembler und Register Zugriff


Autor: Santiago (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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.
void usiWrite(uint8_t *p, uint8_t s) {
   uint8_t i=0;
   uint8_t data;

   do {
      USIDR = p[i];
      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");
   } while (++i < s);
}

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.

Autor: Rolf Magnus (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: Santiago (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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?

Autor: Jörg X. (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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'

Autor: Fred S. (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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/aut20...

Gruß

Fred

Autor: Jörg Wunsch (dl8dtl) (Moderator) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht 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:
uint8_t
SPITransfer(uint8_t d)
{
        USIDR = d;
        USISR = _BV(USIOIF);
        do {
                USICR = _BV(USIWM0) | _BV(USICS1) |
                        _BV(USICLK) | _BV(USITC);
        } while ((USISR & _BV(USIOIF)) == 0);
        return USIDR;
}

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

Autor: Santiago (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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:
void transfer(uint8_t * p, uint8_t s) {
   uint8_t n=0;
   uint8_t i=0;
   uint8_t j=7;
   uint8_t data;

   do {
      data = p[n];

      i=0;
      j=7;
      do {
         if (data & 1 << i) PORTx |= 1 << j;
         else               PORTx &= (uint8_t) ~(1 << j);
         j--;
      } while (++i < 8);
   } while (++n < s);
}
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.

Autor: Jörg Wunsch (dl8dtl) (Moderator) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: Rolf Magnus (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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)));

Autor: Fred S. (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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

Autor: Santiago (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: Jörg Wunsch (dl8dtl) (Moderator) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: Santiago (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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 :)

Autor: Luther Blissett (luther-blissett)
Datum:

Bewertung
0 lesenswert
nicht 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:
uint8_t rev(uint8_t x) 
{
  x=(x>>4) | ((x<<4) & 0xf);
  x=((x>>2) & 0x33) | ((x<<2) & 0xcc);  
  x=((x>>1) & 0x55) | ((x<<1) & 0xaa);
  return x; 
}

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

Autor: Jörg Wunsch (dl8dtl) (Moderator) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: Jörg Wunsch (dl8dtl) (Moderator) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Ich weiß nicht ganz genau, ob ich dich richtig verstanden habe.
Aber folgendes Schnipsel ,,manuelles SPI'':
#include <avr/io.h>

void
manual_spi(uint8_t i)
{
        uint8_t j = 8;

        PORTB &= ~4;  // slave select
        do {
                if (i & 1)
                        PORTB |= 1;  // 1-bit
                else
                        PORTB &= ~1; // 0-bit
                i >>= 1;
                PORTB |= 2;   // SCK
                PORTB &= ~2;
                j--;
        } while (j);
        PORTB |= 4;
}

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.

Autor: Jörg Wunsch (dl8dtl) (Moderator) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hier noch eine Variante mit USI und entrollter USI-Schleife für
maximale Geschwindigkeit:
#include <avr/io.h>
#include <avr/pgmspace.h>

static const uint8_t bitswap[] PROGMEM = {
        0b0000, 0b1000, 0b0100, 0b1100,
        0b0010, 0b1010, 0b0110, 0b1110,
        0b0001, 0b1001, 0b0101, 0b1101,
        0b0011, 0b1011, 0b0111, 0b1111
};

static inline uint8_t
swapbits(uint8_t i) {
        uint8_t rv, j;

        rv = pgm_read_byte(bitswap + (i & 0x0f));
        j = pgm_read_byte(bitswap + ((i >> 4) & 0x0f));
        return rv | (j << 4);
}

void
usisend(uint8_t i)
{
        USIDR = swapbits(i);
        USICR = _BV(USIWM0) | _BV(USITC);
        USICR = _BV(USIWM0) | _BV(USITC) | _BV(USICLK);
        USICR = _BV(USIWM0) | _BV(USITC);
        USICR = _BV(USIWM0) | _BV(USITC) | _BV(USICLK);
        USICR = _BV(USIWM0) | _BV(USITC);
        USICR = _BV(USIWM0) | _BV(USITC) | _BV(USICLK);
        USICR = _BV(USIWM0) | _BV(USITC);
        USICR = _BV(USIWM0) | _BV(USITC) | _BV(USICLK);
        USICR = _BV(USIWM0) | _BV(USITC);
        USICR = _BV(USIWM0) | _BV(USITC) | _BV(USICLK);
        USICR = _BV(USIWM0) | _BV(USITC);
        USICR = _BV(USIWM0) | _BV(USITC) | _BV(USICLK);
        USICR = _BV(USIWM0) | _BV(USITC);
        USICR = _BV(USIWM0) | _BV(USITC) | _BV(USICLK);
        USICR = _BV(USIWM0) | _BV(USITC);
        USICR = _BV(USIWM0) | _BV(USITC) | _BV(USICLK);
}

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

Autor: Jörg Wunsch (dl8dtl) (Moderator) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: Santiago (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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)
.
#define COMM_DATA       USIDR
#define COMM_BUFFER     USIBR
#define COMM_CTRL       USICR
#define COMM_STATUS     USISR
#define COMM_CLOCK_LOW  (1 << USIWM1 | 1 << USITC)
#define COMM_CLOCK_HIGH (1 << USIWM1 | 1 << USITC | 1 << USICLK)


void usiWrite(uint8_t *p, uint8_t s) {
   uint8_t n=0;

   do {
      COMM_DATA = p[n];
      asm volatile("mov %[usicr], %1\n\t"                // toggle Control register which in turn 
                   "mov %[usicr], %0\n\t"                // shifts the data out
                   "mov %[usicr], %1\n\t"
                   "mov %[usicr], %0\n\t"
                   "mov %[usicr], %1\n\t"
                   "mov %[usicr], %0\n\t"
                   "mov %[usicr], %1\n\t"
                   "mov %[usicr], %0\n\t"
                   "mov %[usicr], %1\n\t"
                   "mov %[usicr], %0\n\t"
                   "mov %[usicr], %1\n\t"
                   "mov %[usicr], %0\n\t"
                   "mov %[usicr], %1\n\t"
                   "mov %[usicr], %0\n\t"
                   "mov %[usicr], %1\n\t"
                   "mov %[usicr], %0\n\t"
                   : : [usicr] "I" (_SFR_IO_ADDR(USICR)), "a" (COMM_CLOCK_LOW), "a" (COMM_CLOCK_HIGH));      
   } while (++n < s); 
}

Autor: Jörg Wunsch (dl8dtl) (Moderator) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht 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. ;-)

Autor: Jörg X. (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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 ;)

Autor: Santiago (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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?

Autor: Jörg X. (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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

Autor: Jörg Wunsch (dl8dtl) (Moderator) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: Santiago (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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).

Autor: Jörg Wunsch (dl8dtl) (Moderator) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht 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.

Antwort schreiben

Die Angabe einer E-Mail-Adresse ist freiwillig. Wenn Sie automatisch per E-Mail über Antworten auf Ihren Beitrag informiert werden möchten, melden Sie sich bitte an.

Wichtige Regeln - erst lesen, dann posten!

  • Groß- und Kleinschreibung verwenden
  • Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang

Formatierung (mehr Informationen...)

  • [c]C-Code[/c]
  • [avrasm]AVR-Assembler-Code[/avrasm]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] - Link zu Artikel
  • Verweis auf anderen Beitrag einfügen: Rechtsklick auf Beitragstitel,
    "Adresse kopieren", und in den Text einfügen




Bild automatisch verkleinern, falls nötig
Bitte das JPG-Format nur für Fotos und Scans verwenden!
Zeichnungen und Screenshots im PNG- oder
GIF-Format hochladen. Siehe Bildformate.
Hinweis: der ursprüngliche Beitrag ist mehr als 6 Monate alt.
Bitte hier nur auf die ursprüngliche Frage antworten,
für neue Fragen einen neuen Beitrag erstellen.

Mit dem Abschicken bestätigst du, die Nutzungsbedingungen anzuerkennen.