Recent

Author Topic: How to read function parameter in assembler code for AVR/Arduino delay function  (Read 6159 times)

ccrause

  • Full Member
  • ***
  • Posts: 222
I've had a stab at alternative code for an AVR delay function, http://forum.lazarus.freepascal.org/index.php/topic,30960.msg201014.html#msg201014

At the moment I have a delay_ms function that produces a delay of 1 millisecond, which can be used in a busy loop as follows:

Code: Pascal  [Select]
  1. program blink;
  2.  
  3. {$IFNDEF F_CPU}
  4.   {$WARNING F_CPU not defined, setting default value of 16 MHz}
  5.   {$MACRO ON}
  6.   {$define F_CPU:=16000000}
  7. {$ENDIF}
  8.  
  9. const
  10.   PB5 = (1 shl 5);
  11.  
  12. // Cycle count = 2 + 4*(x-1) + 3 + 4
  13. // = 5 + 4*x
  14. procedure delay_ms(); assembler;
  15. const
  16.   x = ((F_CPU div 1000) - 5) div 4;
  17.   x_l = x and 255;
  18.   x_h = x shr 8;
  19.  
  20. label
  21.   Loop;
  22.  
  23. asm  
  24.   // XH: XL register is same as R26: R27 register combination
  25.   LDI XH, x_h //Set high byte   1 cycle
  26.   LDI XL, x_l //Set low byte    1 cycle
  27.  
  28. Loop:
  29.   SBIW XL,1 //subtract one from word 2 cycles
  30.   BRNE Loop //Take branch = 2 cycles / No branch = 1 cycle (last iteration of loop)
  31.   // ret is implicitly inserted here by compiler  4 cycles
  32. end;
  33.  
  34. var
  35.   i: uint16;
  36.  
  37. begin
  38.   DDRB := DDRB or PB5; // Set pin 5 of port B to output
  39.  
  40.   while true do
  41.   begin
  42.     i := 500;
  43.     repeat
  44.       delay_ms();
  45.       dec(i);
  46.     until i = 0;
  47.    
  48.     PORTB := PORTB XOR PB5;  // toggle pin 5 of port B on/off
  49.   end;
  50. end.

The delay_ms function above gives a constant delay.  I would like to pass a parameter specifying the delay required to this function and then build a countdown loop in assembly.  My problem is that I don't know how to access a procedure/function parameter in assembler.  Can anyone help in fixing the following pseudocode so that it loads the variable t into two 8 bit registers please?
Code: Pascal  [Select]
  1. procedure delay_ms(t: uint16); assembler;
  2. label
  3.   Loop;
  4.  
  5. asm  
  6.   mov XH, hi8(t)
  7.   mov XL, lo8(t)
  8.  
  9. Loop:
  10.   SBIW XL,1
  11.   BRNE Loop
  12. end;
  13.  

When I try to compile a program with this code I get the following error:
Code: Pascal  [Select]
  1. Error: Cannot use local variable or parameters here

Thaddy

  • Hero Member
  • *****
  • Posts: 9419
This may be enough:
Code: Pascal  [Select]
  1. procedure delay_ms(const t: uint16); assembler;
  2. label
  3.   Loop;
  4.  
  5. asm  
  6.   mov XH, hi8(t)
  7.   mov XL, lo8(t)
  8.  
  9. Loop:
  10.   SBIW XL,1
  11.   BRNE Loop
  12. end;
also related to equus asinus.

ccrause

  • Full Member
  • ***
  • Posts: 222
This may be enough:
Code: Pascal  [Select]
  1. procedure delay_ms(const t: uint16); assembler;
  2. label
  3.   Loop;
  4.  
  5. asm  
  6.   mov XH, hi8(t)
  7.   mov XL, lo8(t)
  8.  
  9. Loop:
  10.   SBIW XL,1
  11.   BRNE Loop
  12. end;

Thanks Thaddy.  I did try that but then I get a compiler error: Cannot use local variable or parameters here at the hi8(t) and lo8(t) instructions.

I guess I have to figure out in which registers the value is passed, then I can proceed from there?

Thaddy

  • Hero Member
  • *****
  • Posts: 9419
Can't you use a byte record? instead of the UINT16? That should work and you can also cast it.
Note I assume little endian. If big endian change l and h. This way there aren't any locals for sure.
Code: Pascal  [Select]
  1. Type
  2.   TMyUINT16 = packed record
  3.     l:byte;
  4.     h:byte;
  5.   end;
  6.  
  7.    procedure delay_ms(const t: TMyUint16); assembler;
  8.    label
  9.      Loop;
  10.      asm  
  11.        mov XH, t.h
  12.        mov XL, t.l
  13.      
  14.     Loop:
  15.       SBIW XL,1
  16.       BRNE Loop
  17.     end;
  18.  

Allternatively, but should also work:
Code: Pascal  [Select]
  1.   procedure delay_ms(const t: Uint16); assembler;
  2.    label
  3.      Loop;
  4.      asm  
  5.        mov XH, TMyUint16(t).h
  6.        mov XL, TMyUint16(t).l
  7.   ...
  8.  
« Last Edit: February 28, 2016, 09:07:25 am by Thaddy »
also related to equus asinus.

ccrause

  • Full Member
  • ***
  • Posts: 222
Thanks Thaddy, your example compiles fine, but the GNU assembler chokes on the generated assembler code. Compiling the following snippet:
Code: Pascal  [Select]
  1. Type
  2.   TMyUINT16 = packed record
  3.     l:byte;
  4.     h:byte;
  5.   end;
  6.  
  7. procedure delay_ms2(const t: uint16); assembler;
  8. label
  9.   Loop;
  10. asm  
  11.   mov XH, TMyUint16(t).h
  12.   mov XL, TMyUint16(t).l
  13. Loop:
  14.   SBIW XL,1
  15.   BRNE Loop
  16. end;

generates the following assembler snippet:
Code: ASM  [Select]
  1. PsBLINK_ss_DELAY_MS2sWORD:
  2.         mov     r27,r24+1
  3.         mov     r26,r24
  4. .Lj8:
  5.         sbiw    r26,1
  6.         brne    .Lj8
  7.         ret
  8. .Le1:
  9.         .size   PsBLINK_ss_DELAY_MS2sWORD, .Le1 - PsBLINK_ss_DELAY_MS2sWORD
  10.  

The AVR assembler generates the following error:
blink_test.s: Assembler messages:
blink_test.s:19: Error: garbage at end of line


It seems as if the fpc compiler translated the line mov XH, TMyUint16(t).h  into mov r27,r24+1.  From the rest of the generated assembler code it seems as if the value passed to the procedure is loaded into r24:r25, so I suspected that the compiler was trying to generate "the next register following r24" and ended up writing r24+1 instead of r25.

If so I guess this is a bug?

Thaddy

  • Hero Member
  • *****
  • Posts: 9419
Well, we tried, Doesn't mean it is a bug but maybe you should file one.
I am out of ideas. The code looks good by now.
also related to equus asinus.

ccrause

  • Full Member
  • ***
  • Posts: 222
Thanks for your help Thaddy.  Bug report

avra

  • Hero Member
  • *****
  • Posts: 1757
    • Additional info
A little off topic, but once the bug is solved you can take a look at several ways to generate cycle exact delays on AVR:
http://forum.e-lab.de/topic.php?t=1822&page=1#post8
ct2laz - Conversion between Lazarus and CodeTyphon
bithelpers - Bit manipulation for standard types
pasettimino - Siemens S7 PLC lib

Laksen

  • Hero Member
  • *****
  • Posts: 637
    • J-Software
There's no way to do that currently.

You just have to know the calling convention.

ccrause

  • Full Member
  • ***
  • Posts: 222
A little off topic, but once the bug is solved you can take a look at several ways to generate cycle exact delays on AVR:
http://forum.e-lab.de/topic.php?t=1822&page=1#post8

Looks useful, thanks avra

ccrause

  • Full Member
  • ***
  • Posts: 222
There's no way to do that currently.

You just have to know the calling convention.

Right, so according to the wiki I should follow the avr-libc convention, so I should assume the following:
Code: Pascal  [Select]
  1. procedure delay_ms2(const t: uint16); assembler;
  2. asm  
  3.   // First 16 bit parameter passed as lo(t) -> R24 and hi(t) -> R25
  4.   mov XH, R25
  5.   mov XL, R24
  6. ...

Is this right?

I would still prefer it if the compiler could substitute the appropriate registers from the parameter name reference though, I'm sure I will confuse myself at some point with the order of registers, little/big endianness etc...

ccrause

  • Full Member
  • ***
  • Posts: 222
There's no way to do that currently.

You just have to know the calling convention.
Thanks to Jeppe for fixing the compiler to accept the type cast suggested by Thaddy.  The following code now works correctly:

Allternatively, but should also work:
Code: Pascal  [Select]
  1.   procedure delay_ms(const t: Uint16); assembler;
  2.    label
  3.      Loop;
  4.      asm  
  5.        mov XH, TMyUint16(t).h
  6.        mov XL, TMyUint16(t).l
  7.   ...
  8.