Forum: Mikrocontroller und Digitale Elektronik längerer ASM-Inline-Abschnitt - wie?


von Nikolas B. (physikant)


Lesenswert?

Hallo,

ich habe das Problem, dass ein Atmega168 in einem engen Zeitfenster per 
Software-SPI Daten auslesen muss. Dabei muss ich eine Rate von 800kbit/s 
- 1Mbit/s erreichen. Der Atmega läuft auf 18,432MHz.
Da ein maximal vereinfachter Code in C nach wie vor zu langsam ist, 
wollte ich einen ASM-Abschnitt inlinen. Leider habe ich das noch nie 
gemacht.
Hier ist, was ich bislang habe:
1
#define DCLK_PORT       PORTD
2
#define DCLK_PIN  6
3
#define DATASIG_PORT  PINB
4
#define DATASIG_PIN  0
5
#define RC_PORT   PORTD
6
#define RC_PIN    5
7
8
uint16_t ADC_read(void)
9
{
10
  uint16_t data = 0;
11
  uint8_t lowb = 0;
12
  uint8_t highb = 0;
13
  RC_PORT &= ~(1<<RC_PIN);  //ADC-Conversion einleiten
14
  RC_PORT |= (1<<RC_PIN);
15
  asm volatile (
16
  "LDI r27, 8\n\t"
17
  "SBI %2, DCLK_PIN\n\t"
18
  "CBI %2, DCLK_PIN\n\t"
19
  "SBI %2, DCLK_PIN\n\t"
20
  "CBI %2, DCLK_PIN\n\t"
21
  "read_byte1_loop:\n\t"
22
  "SBI %2, DCLK_PIN\n\t"
23
  "SBIC %3, DATASIG_PIN\n\t"
24
  "SEC\n\t"
25
  "SBIS %3, DATASIG_PIN\n\t"
26
  "CLC\n\t"
27
  "ROL %0\n\t" 
28
  "CBI %2, DCLK_PIN\n\t"
29
  "DEC r27\n\t"
30
  "BRNE read_byte1_loop\n\t"
31
  "LDI r27, 8\n\t"
32
  "read_byte2_loop:\n\t"
33
  "SBI %2, DCLK_PIN\n\t"
34
  "SBIC %3, DATASIG_PIN\n\t"
35
  "SEC\n\t"
36
  "SBIS %3, DATASIG_PIN\n\t"
37
  "CLC\n\t"
38
  "ROL %1 \n\t"
39
  "CBI %2, DCLK_PIN\n\t"
40
  "DEC r27\n\t"
41
  "BRNE read_byte2_loop\n\t"
42
  :"=r"(highb),"=r"(lowb),"=r"(DCLK_PORT)
43
  :"r"(DATASIG_PORT)
44
  : "r27"
45
  );
46
  data = lowb + (highb*256);
47
  return data;            
48
}
Mir ist klar, dass das lowb + (highb*256) weder guter Stil noch schnell 
ist, aber darauf kommt es gerade nicht an.

Der Compiler meldet einige Male Error: constant value required sowie 
Error: symbol `read_byte1_loop' is already defined (auch für die andere 
Schleife).
Was mache ich falsch?
Ist mein Code so prinzipiell sinnvoll?

Danke schonmal,

Nikolas

von Mw E. (Firma: fritzler-avr.de) (fritzler)


Lesenswert?

Also längeres ASM nicht inline machen, sondern auslagern und dann noch 
an die GCC Registerkonventionen halten.
Zudem bei asm immer schön kommentieren, sonst blickt man nach ner Woche 
selbst nicht mehr durch.

von Nikolas B. (physikant)


Lesenswert?

Eigentlich hätte ich es lieber so im Code. Es müsste doch so auch gehen, 
oder?
Falls es hilft, das war die Funktion vor der ASM-Ersetzung:
1
uint16_t ADC_read(void)
2
{
3
  uint16_t data = 0;
4
  uint16_t mask = 0x8000; 
5
  RC_PORT &= ~(1<<RC_PIN);
6
  RC_PORT |= (1<<RC_PIN);
7
8
  DCLK_PORT |= (1<<DCLK_PIN);
9
  DCLK_PORT &= ~(1<<DCLK_PIN);
10
  DCLK_PORT |=  (1<<DCLK_PIN);
11
  DCLK_PORT &= ~(1<<DCLK_PIN);
12
   do{
13
    DCLK_PORT |=  (1<<DCLK_PIN);
14
    if(DATASIG_PORT&(1<<DATASIG_PIN))data|=mask;
15
    DCLK_PORT &= ~(1<<DCLK_PIN);    
16
      }while((mask>>=1));
17
  return data;
18
}
Im Prinzip wird also ein CLK-Signal generiert (mit zwei leeren 
CLK-Pulsen vorneweg) und dann Bit für Bit ein Signal eingelesen zwischen 
den Pulsen. In ASM ist das dann in zwei Schleifen aufgeteilt für high- 
und lowbyte.
r27 dient lediglich als Schleifenzähler.

Grüße
  Nikolas

von Stefan E. (sternst)


Lesenswert?

Nikolas B. schrieb:
> Was mache ich falsch?

Unter anderem das hier:
1
#define DCLK_PIN  6
2
...
3
  "SBI %2, DCLK_PIN\n\t"
Innerhalb von String-Literalen werden keine Makros ersetzt.

Mache es als separate Assembler-Datei, das erleichtert einiges.

von Stefan (Gast)


Lesenswert?

Lohnt sich der AUfwand, an dieser Stelle Assembler einzusetzen? Würde 
mich wundern.

von Nikolas B. (physikant)


Lesenswert?

Auch durch Ersetzen mit den richtigen Zahlen meldet er: error:constant 
value required. Seltsamerweise in den Zeilen, in denen zuvor die Makros 
waren.
So sieht der Code in der .s -Datei aus:
1
LDI r27, 8
2
SBI r25, 6
3
CBI r25, 6
4
SBI r25, 6
5
CBI r25, 6
6
read_byte1_loop:
7
SBI r25, 6  
8
SBIC r24, 0
9
SEC
10
SBIS r24, 0
11
CLC
12
ROL r24
13
CBI r25, 6
14
DEC r27
15
BRNE read_byte1_loop
16
LDI r27, 8
17
read_byte2_loop:
18
SBI r25, 6
19
SBIC r24, 0
20
SEC
21
SBIS r24, 0
22
CLC
23
ROL r30 
24
CBI r25, 6
25
DEC r27
26
BRNE read_byte2_loop
Seltsamerweise taucht der Code in der .s-Datei mehrfach auf, wodurch er 
read_byte1_loop und read_byte2_loop als already defined bemerkt.

Und ja, der Aufwand lohnt sich (hoffentlich), da die maximale Rate, die 
ich mit dem C-Code erreiche, rund 400kbit/s ist, ich jedoch mindestens 
800 brauche.
Grüße
  Nikolas

von Oliver J. (skriptkiddy)


Lesenswert?

Nikolas B. schrieb:
> Und ja, der Aufwand lohnt sich (hoffentlich), da die maximale Rate, die
> ich mit dem C-Code erreiche, rund 400kbit/s ist, ich jedoch mindestens
> 800 brauche.
> Grüße
>   Nikolas

Warum nimmst du nicht HW-SPI?

von Nikolas B. (physikant)


Lesenswert?

Weil es nicht funktioniert mit dem ADC.
Diskussion dazu:
Beitrag "SPI mit ADS7825"
Grüße
  Nikolas

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


Lesenswert?

Nikolas B. schrieb:
> Und ja, der Aufwand lohnt sich (hoffentlich), da die maximale Rate, die
> ich mit dem C-Code erreiche, rund 400kbit/s ist, ich jedoch mindestens
> 800 brauche.

Verstehe ich nicht.  Was GCC (4.7.2) mit -Os draus macht, sieht schon
recht kompakt aus:
1
.global ADC_read
2
        .type   ADC_read, @function
3
ADC_read:
4
/* prologue: function */
5
/* frame size = 0 */
6
/* stack size = 0 */
7
.L__stack_usage = 0
8
        cbi 0xb,5
9
        sbi 0xb,5
10
        sbi 0xb,6
11
        cbi 0xb,6
12
        sbi 0xb,6
13
        cbi 0xb,6
14
        ldi r24,lo8(16)
15
        ldi r25,0
16
        ldi r20,0
17
        ldi r21,lo8(-128)
18
        ldi r18,0
19
        ldi r19,0
20
.L3:
21
        sbi 0xb,6
22
        sbis 0x3,0
23
        rjmp .L2
24
        or r18,r20
25
        or r19,r21
26
.L2:
27
        cbi 0xb,6
28
        lsr r21
29
        ror r20
30
        sbiw r24,1
31
        brne .L3
32
        movw r24,r18
33
        ret

Mit -O3 wird der Code viel größer und "irgendwie entrollt", wobei
ich meine Zweifel habe, dass es wirklich dadurch noch schneller wird.

von Stefan E. (sternst)


Lesenswert?

1
SBI r25, 6
2
CBI r25, 6
3
SBI r25, 6
4
CBI r25, 6
Die sbi/cbi auf ein Register sind völlig sinnlos und das Resultat eines 
falschen Constraints.

Nikolas B. schrieb:
> Seltsamerweise taucht der Code in der .s-Datei mehrfach auf, wodurch er
> read_byte1_loop und read_byte2_loop als already defined bemerkt.

Dann hast du oder der Compiler die Funktion ge-inlined.


Und vom zweifelhaften Vorteil des Ganzen mal abgesehen:
Du hast offenbar größere Probleme mit Inline-Assembler, und die (schon 
mehrfach empfohlene) Alternative hätte keine Nachteile. Warum zum Teufel 
beharrst du also auf Inline-Assembler?

von Nikolas B. (physikant)


Lesenswert?

Es hat sich erübrigt. Ich will garnicht wissen wieso, aber ohne 
ersichtlichen Grund läuft das Teil jetzt.
Danke für eure Mühe!
Nikolas

von Karl H. (kbuchegg)


Lesenswert?

Nikolas B. schrieb:

> In ASM ist das dann in zwei Schleifen aufgeteilt für high-
> und lowbyte.

... damit man nicht mit 16 Bit rummachen muss. Schon klar.

Aber: Warum kannst du das nicht in C genauso machen?

Ich seh jetzt ehrlich gesagt nicht, wie man in Assembler ein
1
   uint8_t mask = 0x80;
2
   uint8_t dataHigh = 0;
3
4
   do {
5
    DCLK_PORT |=  (1<<DCLK_PIN);
6
    if(DATASIG_PORT&(1<<DATASIG_PIN))
7
      dataHigh |= mask;
8
    DCLK_PORT &= ~(1<<DCLK_PIN);    
9
   }while ( mask >>= 1 );
noch wesentlich beschleunigen könnte. Ich seh aber enormes Potential, 
wie man sich in Assembler ins eigene Knie schiessen kann.

von Oliver J. (skriptkiddy)


Lesenswert?

Nikolas B. schrieb:
> Weil es nicht funktioniert mit dem ADC.
> Diskussion dazu:
> Beitrag "SPI mit ADS7825"
> Grüße
>   Nikolas

Kann mir nicht vorstellen, warm das das nicht funktionieren sollte.

Siehe FIGURE 4. Serial Data Timing Using Internal Data Clock (TAG LOW).


Konvertierung Starten. (CS low puls)
Kurz warten. (1,4 µs nach start des CS-Pulses)
Und dann zwei Bytes per SPI abholen (mit HW-SPI).

Gruß Oliver

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.