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_tADC_read(void)
9
{
10
uint16_tdata=0;
11
uint8_tlowb=0;
12
uint8_thighb=0;
13
RC_PORT&=~(1<<RC_PIN);//ADC-Conversion einleiten
14
RC_PORT|=(1<<RC_PIN);
15
asmvolatile(
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
returndata;
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
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.
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_tADC_read(void)
2
{
3
uint16_tdata=0;
4
uint16_tmask=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
returndata;
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
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
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?
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.
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?
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_tmask=0x80;
2
uint8_tdataHigh=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.
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