Forum: Mikrocontroller und Digitale Elektronik avr C asm -- brauche hilfe


von chris (Gast)


Lesenswert?

Hallo allerseits,
ich bräuchte Hilfe bei einer C funktion.
Folgendes:
 Ich muss mit 2Mhz bei 16Mhz F_CLK samplen.
 dazu habe ich folgenden C code.
 dise generiert folgenden ASM code.
 Wie kann ich diesen ASM code umändern und in C oder extern in ASM 
einbetten.
 Sollte von C aufgerufen werden.
 aus dem Mapfile ist ersichtlich, dass captured_data 0x600 ist.
 Auch verstehe ich den Code zum incrementieren der Variable nicht ganz,
 ist aber irrelevant.
 Wie komme ich zum gewünschten Code dass dieser functioniert.

Danke.


C code:
1
void capture_()
2
  {
3
    byte i,j; word x;
4
  x=0; i=0; x=data_size-1;
5
    j=data_size - ((data_size / 256) * 256); 
6
  #if data_size >= 256
7
    while(--i)
8
      {
9
        captured_data[x--] = PINC;
10
      }
11
  #endif
12
  #if data_size >= 256*2
13
    for(;--i;)
14
      {
15
        captured_data[x--] = PINC;
16
      }
17
  #endif
18
  #if data_size >= 256*3
19
    while(++i)
20
      {
21
        captured_data[x--] = PINC;
22
      }
23
  #endif
24
  #if data_size >= 256*4
25
    for(;--i;)
26
      {
27
        captured_data[x--] = PINC;
28
      }
29
  #endif
30
  #if data_size >= 256*5
31
    while(i--)
32
      {
33
        captured_data[x--] = PINC;
34
      }
35
  #endif
36
  #if data_size >= 256*6
37
    while(i--)
38
      {
39
        captured_data[x--] = PINC;
40
      }
41
  #endif
42
  #if data_size - ((data_size / 256) * 256)> 0
43
    while(j--)
44
      {
45
        captured_data[x--] = PINC;
46
      }
47
  #endif
48
    flag|=(captured);
49
  }

ASM code:
1
/* frame size = 0 */
2
/* stack size = 0 */
3
.L__stack_usage = 0
4
        ldi r30,lo8(captured_data+1536)
5
        ldi r31,hi8(captured_data+1536)
6
        mov r25,r30
7
        com r25
8
.L63:
9
        mov r24,r25
10
        add r24,r30
11
        breq .L73
12
        in r24,0x6
13
        st -Z,r24
14
        rjmp .L63
15
.L73:
16
        ldi r30,lo8(captured_data+1281)
17
        ldi r31,hi8(captured_data+1281)
18
        mov r25,r30
19
        com r25
20
.L65:
21
        mov r24,r25
22
        add r24,r30
23
        breq .L74
24
        in r24,0x6
25
        st -Z,r24
26
        rjmp .L65
27
.L74:
28
        ...

gewünschter Code, r?? ist eine word variable und folgendermassen 
definiert
    word i = -data_size ; // data_size ist eine preprozessor constante.
1
.L63:
2
        in r24,0x6
3
        st -Z,r24
4
        adiw r??:r??,1
5
        brcs .L73
6
        rjmp .L63
7
.L73:

von Curby23523 N. (Gast)


Lesenswert?

Wenn du C-Code hast, warum schreibst du dann nicht alles in C?
Zur Sache: im Netz findest du etliche Tutorials, wie du ASM in C und 
umgekehrt einfügen kannst. Das erklärt man nicht nebenbei, sonder sollte 
man sich aneignen - sofern man es braucht.

von Chris S. (aaa)


Lesenswert?

Eben ist das nicht, was man auf die schnelle lernt.
Weiters ist dies eher eine Ausnahme, normalerweise mache ich nichts
mit avr asm. Wieso ich es nicht in C machen kann, weil ich da auf 9 
cyclen
komme, und ich nur 8 brauchen darf, ich brauche 2Mhz, und nicht 1.77Mhz 
bei
16Mhz OSC.
Ich bin auf sowas gekommen, aber dies geht nicht.
1
asm volatile ( "\n"
2
    "L_loop%=: "
3
        "in r24,0x6  \n\t"
4
        "st -Z,r24  \n\t"
5
        "adiw %[loop],1  \n\t"
6
        "brcs L_done%=\n\t"
7
        "rjmp L_loop%=\n"
8
    "L_done%=:  \n"
9
  : [loop] "=&w" (-data_size)
10
  : "z" (&captured_data[data_size])
11
  : "r24"
12
  );

: Bearbeitet durch User
von Peter D. (peda)


Lesenswert?

chris schrieb:
> Ich muss mit 2Mhz bei 16Mhz F_CLK samplen.

Das geht nicht.

von Purzel H. (hacky)


Lesenswert?

Ich denke, dass 2MSample auch mit 16MHz Clock nicht gehen. Auch wenn's 
gehen wuerde, was willst mit so vielen Daten ? Abspeichern ? Verarbeiten 
? Das Lesen von 2 Byte dauert 2 Takte, bleiben noch 6...

Auch mit externem ADC. Nimm eine schnellere Maschine, keinen 8 bitter, 
sondern vielleicht einen 32 bitter, der mit 40..80MHz laeuft.

von Chris S. (aaa)


Lesenswert?

@peda, habe ich wirklich falsch recherchiert, laut online doc rechne ich 
8 cyclen.
Funktion ist von decompilierten C code und wurde von mir verändert.

.L63:
        in r24,0x6          ; 1 cyclen
        st -Z,r24           ; 2 cyclen
        adiw r27:r26,1      ; 2 cyclen
        brcs .L73           ; 1 cyclen bei skip, 2 bei jmp
        rjmp .L63           ; 2 cyclen
.L73:

Ja, es genügt mir, wenn ich ca 800uS nach der Triggerung abspeichere und
dann über rs232 in 140ms hochlade.

von S. R. (svenska)


Lesenswert?

Ich würde dir empfehlen, die gesamte Funktion in Assembler zu speichern 
und als Modul danebenzulegen.

von Michael U. (amiga)


Lesenswert?

Hallo,

Peter D. schrieb:
> chris schrieb:
>> Ich muss mit 2Mhz bei 16Mhz F_CLK samplen.
>
> Das geht nicht.

naja...

loop:
in r16,PORTB        1 Takt
st Z+,r16           2 Takte
cp ZH,Ende          1 Takt
brne loop           2 Takte bei Rücksprung

sind 6 Takte, bei 16MHz also 2,667MHz Samplerate.
Bei 8 Takten kann mit CP/CPC/BRCC auch auf ein beliebeiges Ende 
gestestet werden. Vorbelegungen müssen vorher passieren, wenn er extern 
triggern will, muß eben direkt vor dem Loop noch eine Schleife, die nur 
auf den Pin testet. Der Samplestart ist dann maximal 3 Takte nach dem 
externen Ereignis.

Sind auf einem Mega1284 mit seinen 16k Ram sinnvoll vermutlich 15k 
Daten.

Ob es für den gewünschten Zweck sinnvoll ist, muß er wissen.
Wie man es aus C aufruft? Keine Ahnung...

Gruß aus Berlin
Michael

: Bearbeitet durch User
von S. Landolt (Gast)


Lesenswert?

> Das geht nicht.

Wenn es absolut sein muss, geht es noch schneller, indem man die 
Schleife ausschreibt (ein ATmega1284 z.B. hat ja 128 KiB flash):
in  r16,PINa
sts data+0,r16
in  r16,PINa
sts data+1,r16
...
...

Sind 3 Takte pro Byte.

von S. Landolt (Gast)


Lesenswert?

Ist man, z.B. als Assemblerprogrammierer, Herr über Stack & 
Stackpointer, geht es mit Vervielfachung eines Macros auch einfacher und 
kürzer, wenn auch (natürlich) nicht schneller:
1
.macro dump
2
  in   r16,PINa
3
  push r16
4
.endmacro

 dump
 dump
 dump
 ...

von avr (Gast)


Lesenswert?

S. Landolt schrieb:
> in  r16,PINa
> sts data+0,r16
> in  r16,PINa
> sts data+1,r16

mit einem Pointer braucht man auch nur 4 Byte/gesampeltes Byte. Nach der 
Masche hatte ich tatsächlich mal einen kleinen Logikanalyer gebaut, 
finde das Projekt aber nicht mehr. Sah aber ungefähr so aus:
1
;X auf ramstart setzen
2
;auf Trigger warten
3
4
.rep 1024
5
in r16, PINx
6
st X+, r16
7
.endr
8
9
;Daten übertragen

von Yalu X. (yalu) (Moderator)


Lesenswert?

AVR-GCC 7.3.0 macht mit -Os aus diesem

1
#include <stdint.h>
2
#include <avr/io.h>
3
4
#define data_size 300
5
#define captured 1
6
7
uint8_t captured_data[data_size];
8
uint8_t flag;
9
10
void capture(void) {
11
  for (uint16_t i=data_size; i; i--)
12
    captured_data[i-1] = PINC;
13
  flag |= captured;
14
}

dieses:

1
  .file  "avrtest.c"
2
__SP_H__ = 0x3e
3
__SP_L__ = 0x3d
4
__SREG__ = 0x3f
5
__tmp_reg__ = 0
6
__zero_reg__ = 1
7
  .text
8
.global  capture
9
  .type  capture, @function
10
capture:
11
/* prologue: function */
12
/* frame size = 0 */
13
/* stack size = 0 */
14
.L__stack_usage = 0
15
  ldi r30,lo8(captured_data+300)
16
  ldi r31,hi8(captured_data+300)
17
.L2:
18
  in r24,0x6
19
  st -Z,r24
20
  ldi r24,hi8(captured_data)
21
  cpi r30,lo8(captured_data)
22
  cpc r31,r24
23
  brne .L2
24
  lds r24,flag
25
  ori r24,lo8(1)
26
  sts flag,r24
27
/* epilogue start */
28
  ret
29
  .size  capture, .-capture
30
  .comm  flag,1,1
31
  .comm  captured_data,300,1
32
  .ident  "GCC: (GNU) 7.3.0"
33
.global __do_clear_bss

Ein Schleifendurchlauf dauert wie gewünscht 8 Zyklen.

Du kannst den Schleifenzähler auch aufwärts laufen lassen, bspw. so:

1
  for (uint16_t i=1; i<=data_size; i++)
2
    captured_data[data_size-i] = PINC;

oder auch so:

1
  for (uint16_t i=0; i<data_size; i++)
2
    captured_data[data_size-i-1] = PINC;

Der erzeugte Assemblercode ist in allen drei Fällen gleich.

: Bearbeitet durch Moderator
von S. Landolt (Gast)


Lesenswert?

an avr:

Stimmt, Ihre Lösung ist einfacher. Und wir kommen beide auf 3 
Takte/Byte.

von Chris S. (aaa)


Lesenswert?

@yalu, die Lösung ist gut, danke für den Hinweis.

Mittlerweile konnte ich es auch mit inline assembling machen,
bekam das mit der Variablenübergabe nicht hin, aber so geht es auch,
auf die harte tour.
1
asm volatile ( "\n\t"
2
        "ldi r30,lo8(captured_data+1536) \n\t"
3
        "ldi r31,hi8(captured_data+1536) \n\t"
4
        "ldi r24,hi8(-data_size) \n\t"
5
        "ldi r25,lo8(-data_size) \n"
6
    "L_loop%=: \n\t"
7
        "in __tmp_reg__,0x6  \n\t"
8
        "st -Z,__tmp_reg__  \n\t"
9
        "adiw r24,1  \n\t"
10
        "brcs L_done%=\n\t"
11
        "rjmp L_loop%=\n"
12
    "L_done%=:  \n\t"
13
    ::: "r31","r30","r24","r25" 
14
  );

von Chris S. (aaa)


Lesenswert?

Wieso ich warscheinlich selbst nicht draufgekommen bin.
Vorher hatte ich von die Daten von 0 bis data_size raufgezählt.
Bin dann beim optimieren des Assemblercodes dann draufgekommen dass
ein Abspeicher vom Ende zum Beginn eine Instruction spart, hatte da aber
nicht die anfänglichen Tests wiederholt und so hatte ich die saubere 
Lösung
übersehen.
Danke nochmals.

von avr (Gast)


Lesenswert?

S. Landolt schrieb:
> Stimmt, Ihre Lösung ist einfacher. Und wir kommen beide auf 3
> Takte/Byte.

Gerne auch "du". Auf die Idee mit push bin ich damals aber auch nicht 
gekommen. Finde ich auch nicht schlecht. Evtl. kann man aber auch den 
Stackpointer in den Registerbereich legen (z.B. auf R14:R15). Dann 
könnte man nach wie vor Funktionen und Interrupts nutzen.

von Jim M. (turboj)


Lesenswert?

avr schrieb:
> Dann
> könnte man nach wie vor Funktionen und Interrupts nutzen.

Interrupts in einem Programm wo Zyklen von Hand gezählt werden? Das will 
man an der Stelle nicht haben.

Und weil eh' ein cli; davor steht kann man auch den Stack Pointer 
sichern und vor dem Einschalten der Interupts wiederherstellen.

von avr (Gast)


Lesenswert?

Jim M. schrieb:
> Interrupts in einem Programm wo Zyklen von Hand gezählt werden? Das will
> man an der Stelle nicht haben.

Wer sagt denn dass die Interrupts auftreten wenn die kritische Routine 
läuft? Natürlich deaktiviert man sie davor. Stackpointer sichern macht 
aber ganz bestimmt keinen Sinn, wenn man ihn einfach auf ein paar 
CPU-Register legen kann.

von Peter D. (peda)


Lesenswert?

Chris S. schrieb:
> @peda, habe ich wirklich falsch recherchiert, laut online doc rechne ich
> 8 cyclen.

Was ich damit meinte, daß dabei der MC nichts sinnvolles mehr machen 
kann.
Typisch haben MCs bei mir immer mehrere Tasks gleichzeitg auszuführen.

Mich würde daher brennend interessieren, wozu braucht man denn so ein 
merkwürdiges Programm?

von Stefan S. (chiefeinherjar)


Lesenswert?

Peter D. schrieb:
> Mich würde daher brennend interessieren, wozu braucht man denn so ein
> merkwürdiges Programm?

Ich könnte mir vorstellen, dass das eine Art Low Cost Logic Analyser 
wird.
Aber ich bin auch mal ehrlich neugierig gespannt, was das werden soll!

von Karl (Gast)


Lesenswert?

Peter D. schrieb:
> Was ich damit meinte, daß dabei der MC nichts sinnvolles mehr machen
> kann.

Stimmt schon. Aber bei 2MHz und 15k ist der Speicher eh in 7.5msec voll. 
Da kann man noch eine Menge anderen Kram abhandeln.

von Yalu X. (yalu) (Moderator)


Lesenswert?

Peter D. schrieb:
> Chris S. schrieb:
>> @peda, habe ich wirklich falsch recherchiert, laut online doc rechne ich
>> 8 cyclen.
>
> Was ich damit meinte, daß dabei der MC nichts sinnvolles mehr machen
> kann.

Chris S. schrieb:
> Ja, es genügt mir, wenn ich ca 800uS nach der Triggerung abspeichere und
> dann über rs232 in 140ms hochlade.

D.h. der Controller ist für weniger als 1ms blockiert. Alles, was keine
Reaktionszeit von weniger als 1ms braucht, kann also problemlos nebenher
erledigt werden.

von chris (Gast)


Lesenswert?

Gefordert wurde prinzipiell ein i2c tool incl Logic analyzer.
Die hatten mein pickit2 mit LA SW gesehen, als ich das Problem beseitigt 
hatte,
und wollen nun auch sowas.

Txtzyme, petit_fat, i2c/rs232/lin/... wobei petit_fat nur für 
Datenlogger
von interesse ist.

Da sich das Project als Datenlogger inkl protocol decoder hergibt, will 
ich
es in diesem Sinne erweitern.
Da die RLE LA mit max 1Mhz funktioniert, wollte ich die ohne RLE mit max
2Mhz und weil es den anschein hatte dass es gehen müsste.
Ein reines Abspeichern von Port braucht mir zuviel Flash.

Gruß
Chris

von chris (Gast)


Lesenswert?

@peda
Hallo Peter, da ich deinen Code für i2c benutze, ist mir aufgefallen,
dass clock stretching nur teilweise implementiert wurde, fehlt bei start 
und stop, und auch die
Filterung der I2C Leitung fehlt. Ich habe mal was versucht den Code 
abzuändern.
was hälst du davon, braucht aber auch etwas mehr Zeit.
1
#define CONCAT__(a,b) a##b
2
#define CONCAT_(a,b)  CONCAT__(a,b)
3
#define CONCAT(a,b)   CONCAT_(a,b)
4
5
;******----- Adapt these SCA and SCL port and pin definition to your target !!
6
;
7
#ifndef I2C_ID
8
#error "I2C ID must be defined"
9
#endif
10
#undef SDA
11
#undef SCL
12
#undef SDA_PORT
13
#undef SCL_PORT
14
#undef SDA_DDR
15
#undef SCL_DDR
16
#undef SDA_OUT
17
#undef SDA_IN
18
#undef SCL_IN
19
#undef func
20
#define func(x)  CONCAT(i2c,CONCAT(I2C_ID,CONCAT(_,x)))
21
22
#define SDA             CONCAT(SDA_BIT_,I2C_ID)
23
#define SCL             CONCAT(SCL_BIT_,I2C_ID)
24
#define SDA_PORT        CONCAT(SDA_PORT_,I2C_ID)
25
#define SCL_PORT        CONCAT(SCL_PORT_,I2C_ID)
26
27
...
28
29
  .func  func(stretch)
30
func(stretch):
31
  rcall  i2c_delay_T2  ;delay T/2
32
  cbi  SCL_DDR,SCL  ;release SCL
33
  rcall   i2c_delay_T2  ;delay  T/2
34
func(ack_wait):
35
  clr   r18    ; implement scl line filtering
36
        sbis   SCL_IN, SCL        ;loop until SCL is high (allow slave to stretch SCL)
37
  inc  r18
38
        sbis   SCL_IN, SCL        ;loop until SCL is high (allow slave to stretch SCL)
39
  inc  r18
40
        sbis   SCL_IN, SCL        ;loop until SCL is high (allow slave to stretch SCL)
41
  inc  r18
42
  sbrs  r18,1
43
  rjmp  func(ack_wait)
44
  ret
45
func(sda):  // value of SCL
46
  clc      ;clear carry flag
47
        sbic   SDA_IN, SDA        ;loop until SCL is high (allow slave to stretch SCL)
48
  inc  r18
49
        sbic   SDA_IN, SDA        ;loop until SCL is high (allow slave to stretch SCL)
50
  inc  r18
51
        sbic   SDA_IN, SDA        ;loop until SCL is high (allow slave to stretch SCL)
52
  inc  r18
53
  lsr  r18
54
  sbrc  r18,0
55
  sec      ;  set carry flag
56
  ret
57
  .endfunc

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Chris S. schrieb:
> bekam das mit der Variablenübergabe nicht hin

In welchem Kontext denn?  Ohne Kontext kann ich das asm nicht 
compilieren, und da konkret zu zeigen wie's geht ist dann nicht 
drin... Z.B: weil die Variablen nicht deklariert sind etc.

von Chris S. (aaa)


Angehängte Dateien:

Lesenswert?

Hier eine sehr frühe Version mit nur der LA Funktionalität welche 
compilierbar ist.

Sei es in la8.h sowie in la.c war ich nicht imstande auf das Array 
captured_data sowie data_size, was eine konstante hier ist, habe es aber
auch als veriable versucht, im inline ASM zu übergeben.
Die Lösung was ich geschafft habe ist die Register as used temp register
zu deklarieren und die Variable in ASM selbs zu laden, eigentlich 
sollten
die variablen aber z.B. als %[data] oder auch mit %0 ansprechbar sein 
und irgendwie im
inline assembler übergeben werden, was ich nicht geschafft hatte.

       "ldi r30,lo8(captured_data+1536) \n\t"
        "ldi r31,hi8(captured_data+1536) \n\t"
        "ldi r24,hi8(-data_size) \n\t"
        "ldi r25,lo8(-data_size) \n"
    "L_loop%=: \n\t"
        "in _tmp_reg_,0x6  \n\t"
        "st -Z,__tmp_reg__  \n\t"
        "adiw r24,1  \n\t"
        "brcs L_done%=\n\t"
        "rjmp L_loop%=\n"
    "L_done%=:  \n\t"
    ::: "r31","r30","r24","r25"

Beitrag #5342545 wurde von einem Moderator gelöscht.
von Johann L. (gjlayde) Benutzerseite


Lesenswert?

So?
1
#include <stdint.h>
2
#include <stdbool.h>
3
#include <avr/io.h>
4
5
typedef uint8_t byte;
6
typedef uint16_t word;
7
8
extern bool captured, backward, flag;
9
10
#define DATA_SIZE 1536
11
// array to contain sampled data
12
byte captured_data[DATA_SIZE];
13
14
void capture (void)
15
{
16
    for (word i = DATA_SIZE; i; i--)
17
        captured_data[i-1] = PINC;
18
    flag |= captured | backward;
19
}
20
21
void capture_with_asm (void)
22
{
23
    byte *addr = captured_data + DATA_SIZE;
24
    word n_bytes = DATA_SIZE;
25
26
    __asm volatile (
27
        "0:"                        "\n\t"
28
        // Beware: %i needs v4.7+
29
        "in __tmp_reg__, %i[port]"  "\n\t"
30
        "st -Z, __tmp_reg__"        "\n\t"
31
        "sbiw %[n], 1"              "\n\t"
32
        "brne 0b"
33
        : [addr] "+z" (addr), [n] "+w" (n_bytes)
34
        : [port] "n" (&PINC)
35
        : "memory");
36
    flag |= captured | backward;
37
}

von Chris S. (aaa)


Lesenswert?

Vielen Dank, das Beispiel ist sehr Hilfreich. Nun habe ich es verstanden 
wie
es geht.
Danke nochmals.
Chris

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.