Watchdog ISR in assembly language

I want to have an ISR for the watchdog timer. I've got working code that has been running in an assembly project for some time and I now want to use the same code within the Arduino environment.

Now of course I can use asm("") but the function is about 150 lines long so that would be a pain plus very difficult to maintain/modify.

There's asm volatile () but that's not much better.

If I was in my normal AVR environment I would figure out how to link the object file or whatever.

But is there a clean way to do this in Arduino. For example

ISR (WDT_vect) {
#include "my_asm_file.s"
}

would be about perfect if my_asm_file was straight assembly code.

Why not just rewite in C? I don't think that's an option because I need to have direct access to registers and the stack, but maybe.

For now if I can do it in ASM I would prefer that.


Rob

I can't help with embedding the assembly or linking to the assembled object file but...

Why not just rewite in C? I don't think that's an option because I need to have direct access to registers and the stack, but maybe.

GCC-AVR (and C in general) does a nice job of providing direct access to those things. For example, the stack pointer can be accessed with "SP". If you decide to try the C route, I can't promise you'll like the journey but I will try to help get you to your destination.

Truth is I only really wanted SP access because that's where some regs are stored in the ASM version. In C nobody cares about registers (this is a monitor program) so maybe that doesn't matter.


Rob

But is there a clean way to do this in Arduino. For example

ISR (WDT_vect) {
#include "my_asm_file.s"
}

Thinking out loud.

You can include a .h file in any project as a local file. So maybe a rename to the my_asm_file.h could do the trick? such as below? Only thing you need is a small script to convert the .s file into an appropiate .h file.

void setup() {}

void loop()
{
  #include "asm.h"
}

asm.h contains:

// do nothing;
asm("nop");

comments in the asm.h file could contain C code as sort of reference - a bit like: ...\avr-objdump -D -S file.cpp.elf | more

I already tried something similar and a couple of variations, it does seem to work but you still have to put all the lines inside an asm("") or you get compiler errors.


Rob

Rob,
Can you post a piece of your asm file, I can look if I can do a quick python asm2include.py script

Here's the code

#define r_temp1        r16 
#define r_temp2        r17
#define r_command    r18
#define r_addr_lo    r19
#define r_addr_hi    r20

#define    CMD_POLL     1
#define    CMD_B         2
#define    CMD_W         3

#define    ACK        6
#define    NAK        15

WDT_vect:
    push    r16
    in    r16,SREG
    push    r16
    push    r17
    push    r18
    push    r19
    push    r20
    push    r28
    push    r29
    push    r30
    push    r31
    
    // kick the watch dog 
    wdr

    SHIFTIN r_command

    cpi    r_command,CMD_POLL
    brne    not_poll
    ldi    r_temp1, ACK
    rjmp    transmit_answer_byte

not_poll:
    DELAY     r_temp1,30

    SHIFTIN r_addr_lo

    DELAY     r_temp1,30

    SHIFTIN r_addr_hi

    tst    r_command
    brne    try_B
    rjmp    cmd_null_func

try_B:
    cpi    r_command,CMD_B
    brne    try_W    
    rjmp    cmd_b_func
try_W:
    cpi    r_command,CMD_W
    brne    cmd_is_bad    
    rjmp    cmd_w_func

cmd_is_bad:
    rjmp    cmd_bad_func

    
//*************************************************************
// We return here from all commands 
// with the command result in r_temp1
//
transmit_answer_byte:
    SHIFTOUT    r_temp1

    pop    r31
    pop    r30
    pop    r29
    pop    r28
    pop    r20
    pop    r19
    pop    r18
    pop    r17
    pop    r16
    out    SREG,r16
    pop    r16
    reti

//************************ CMD_NULL ************************
cmd_null_func:
    ldi    r_temp1,ACK 
    rjmp    transmit_answer_byte

//************************ CMD_BAD *************************
cmd_bad_func:
    ldi    r_temp1,NAK
    rjmp    transmit_answer_byte

//************************ CMD_B ************************
cmd_b_func:
    mov     ZH,r_addr_hi
    mov     ZL,r_addr_lo
    ld    r_temp1,z
    rjmp    transmit_answer_byte

//************************ CMD_W ************************
cmd_w_func:
    DELAY     r_temp1,5

    SHIFTIN r_temp1        // get value to write

    mov     ZH,r_addr_hi
    mov     ZL,r_addr_lo
    st    z,r_temp1
    ldi    r_temp1,ACK
    rjmp    transmit_answer_byte

Note that there are a couple of macros that will have to be expanded I would assume.

No problem ditching all the comments if that makes it easier.

So you plan to wrap every line in an asm("") ?

Meanwhile I've written a rough C version, it's a bit slow but I'm still using digitalWrite() et al so there's room for improvement.


Rob

This snippet of python is called:

asm2include.py filein fileout

import sys, os

def asm2include(infile, outfile):

    line = ""
    with open(infile, 'r') as IN:
        for line in IN:
            line = line.strip(' ')
            if (line != ""):
                line = line.replace('  ',' ')
                OUT = open(outfile, "a")
                if (line.startswith("//") == False):
                    OUT.write("asm(\"")
                    OUT.write(line[:-1])
                    OUT.write("\");\n")
                else:
                    OUT.write(line)
                    OUT.write("\n")
                OUT.close()
            print '.',

""" --------------------------------------------------------------
MAIN APPLICATION
"""  

print "ASM2include 0.1"
asm2include(sys.argv[1], sys.argv[2])
print "Done"

produces this : (not all pasted)

asm("");
asm("#define r_temp1    r16 ");
asm("#define r_temp2    r17");
asm("#define r_command  r18");
asm("#define r_addr_lo  r19");
asm("#define r_addr_hi  r20");
asm("");
asm("#define  CMD_POLL   1");
asm("#define  CMD_B     2");
asm("#define  CMD_W     3");
asm("");
asm("#define  ACK    6");
asm("#define  NAK    15");
asm("");
asm("WDT_vect:");
asm("push  r16");
asm("in  r16,SREG");
asm("push  r16");
asm("push  r17");
asm("push  r18");
asm("push  r19");
asm("push  r20");
asm("push  r28");
asm("push  r29");
asm("push  r30");
asm("push  r31");
asm("");
// kick the watch dog

asm("wdr");
asm("");
asm("SHIFTIN r_command");
asm("");
asm("cpi  r_command,CMD_POLL");
asm("brne  not_poll");
asm("ldi  r_temp1, ACK");
asm("rjmp  transmit_answer_byte");
asm("");

I know this is not what you want, but what should be improved?

  • update
  • recognize the // commentlines in code above

Thanks Rob.

The assembler throws errors like

register name or number from 0 to 31 required

on lines that don't make sense (comments) and I can't find the compiler asm output.


Rob

Bit of an update.

Comments in original code break things, ie

//asm(" brne del1");

is OK but

asm("// brne del1");

is not. In AVR assembler the // is valid for a comment.

No #defines or .equs work so I've had to replace everything with hex numbers.

Macros cause an EOF error so I've put the code in line.

In short the code now looks like a dog's breakfast and it will be totally unmaintainable, but is is running, although not correctly. It looks like the chip is constanly resetting so I'd say the WD is not being reset or something.

The C option looks better all the time. :slight_smile:

@Rob
I just noticed you've edited the last post. How do I run that python script?


Rob

I think current IDE will assemble .S files. That leaves merging C's treatment of the vector table with the ASM code.
You ought to be able to do something like:

ISR (WDT_vect, ISR_NAKED)
{
  asm("jmp asmwdtfnc\n");
}

and make everything pretty happy...

Thanks westfw, using ISR_NAKED fixed my resetting problem with the #included file in the ISR. Presumably because I did my own reti the stack crashed after a while because the postamble never got executed.

Now how do I get the IDE to link in the .S file? The only thing I can see is Sketch>Add file which appears to do nothing.

EDIT: This works

#include "atmon_isr.s"
ISR (WDT_vect, ISR_NAKED)
{
asm("jmp WDT_vect\n");
}

but is no different to

ISR (WDT_vect, ISR_NAKED)
{
#include "atmon_isr.s"
}

And still needs a .S with all the asm("") stuff.


Rob

I just noticed you've edited the last post. How do I run that python script?

I have windows 7 and installed python 2.7 from - Download Python | Python.org -

I created a text file named asm2include.py (.py being the python extention) with the contents shown before (and slightly adapted)
I copied your asm code to test.asm
I open a command window; changed dir to my python directory; and called:

...\apps> asm2include.py test.asm test.h

and it generates the output as shown

asm("");
asm("#define r_temp1 r16 ");
asm("#define r_temp2 r17");
...

I think these need "\n" (not an embedded line-feed but the characters slash followed by "n") at the end...

asm("\n");
asm("#define r_temp1 r16 \n");
asm("#define r_temp2 r17\n");
...

@Rob,

new version of asm2include.py, still in beta^2 but a small step made

added - #defines are handled
added - empty lines are removed

Python code:

import sys

def asm2include(infile, outfile):

    line = ""
    # empty dictonary for defines
    defines = {}
    OUT = open(outfile, "a")
    with open(infile, 'r') as IN:
        for line in IN:
            line = line.strip(' ')  # strip leading and trailing spaces
            if (len(line) > 1):
                line = line.replace('  ',' ')
                # put all defines in a dictionary
                if (line.startswith("#define")): 
                    x = line.split()
                    defines[x[1]] = x[2]
                    continue  # goto next line
                if (line.startswith("//") == False):
                    # iterate through dictionary to replace #defines
                    for k in defines.keys():
                        line = line.replace( k, defines[k] )
                    OUT.write("asm(\"")
                    OUT.write(line[:-1])
                    OUT.write("\");\n")
                else:
                    OUT.write(line)
            print '.',
    OUT.close()

# --------------------------------------------------------------
# MAIN APPLICATION

print "ASM2include 0.1.1"
asm2include(sys.argv[1], sys.argv[2])
print "Done"

from your asm file it now generates:

asm("WDT_vect:");
asm("push  r16");
asm("in  r16,SREG");
asm("push  r16");
asm("push  r17");
asm("push  r18");
asm("push  r19");
asm("push  r20");
asm("push  r28");
asm("push  r29");
asm("push  r30");
asm("push  r31");
// kick the watch dog 
asm("wdr");
asm("SHIFTIN r18");
asm("cpi  r18,1");
asm("brne  not_poll");
asm("ldi  r16, 6");
asm("rjmp  transmit_answer_byte");
asm("not_poll:");
asm("DELAY   r16,30");
asm("SHIFTIN r19");
asm("DELAY   r16,30");
asm("SHIFTIN r20");
asm("tst  r18");
asm("brne  try_B");
asm("rjmp  cmd_null_func");
asm("try_B:");
asm("cpi  r18,2");
asm("brne  try_W  ");
asm("rjmp  cmd_b_func");
asm("try_W:");
asm("cpi  r18,3");
asm("brne  cmd_is_bad  ");
asm("rjmp  cmd_w_func");
asm("cmd_is_bad:");
asm("rjmp  cmd_bad_func");
//*************************************************************
// We return here from all commands 
// with the command result in r_temp1
//
asm("transmit_answer_byte:");
asm("SHIFTOUT  r16");
asm("pop  r31");
asm("pop  r30");
asm("pop  r29");
asm("pop  r28");
asm("pop  r20");
asm("pop  r19");
asm("pop  r18");
asm("pop  r17");
asm("pop  r16");
asm("out  SREG,r16");
asm("pop  r16");
asm("reti");
//************************ CMD_NULL ************************
asm("cmd_null_func:");
asm("ldi  r16,6 ");
asm("rjmp  transmit_answer_byte");
//************************ CMD_BAD *************************
asm("cmd_bad_func:");
asm("ldi  r16,15");
asm("rjmp  transmit_answer_byte");
//************************ CMD_B ************************
asm("cmd_b_func:");
asm("mov   ZH,r20");
asm("mov   ZL,r19");
asm("ld  r16,z");
asm("rjmp  transmit_answer_byte");
//************************ CMD_W ************************
asm("cmd_w_func:");
asm("DELAY   r16,5");
asm("SHIFTIN r16    // get value to write");
asm("mov   ZH,r20");
asm("mov   ZL,r19");
asm("st  z,r16");
asm("ldi  r16,6");
asm("rjmp  transmit_answer_byt");

Can't you use the assembler .include directive? This works for me in Linux (I don't know about Windows).

asm (".include \"/full/path/to/sketch/asm.h\" \n");

extern "C" int func();

void setup()
{
  Serial.begin(9600);
}

void loop()
{
  Serial.println(func());
}

asm.h:

.section .text

.equ elo, 105
.equ ehi, 122

.global func
func:
  ldi r24,elo
  ldi r25,ehi
  ret

I use a .h file so the IDE doesn't complain, it's a bit of a hack. You seem to have to use an absolute path to .include, and you have to remember to save it in the IDE each time you change it before compiling.

asm("#define SREG 0x3f\n");
asm(" in r16,SREG\n"); // fails with "Error: constant value required"
asm(" in r16,0x3f\n"); // works

@CB
I tried with and without \n, no difference. I often wondered about the \n in these assembler examples, it's the sort of thing you do when generating HTML code that has to be read by a human but you wouldn't expect it to be required here.

@Rob
I did the same with the small exception that I don't have python installed :), I'll go and get it.

@stimmer
That approach produced 964

at com.oroinc.text.regex.Perl5Matcher._match(Perl5Matcher.java)

errors.

However I noticed your .equ syntax is different

asm(".equ SREG,0x3f\n");
asm(" in r16,SREG\n"); // this works

but

asm(".equ r_temp1,r16\n");
asm(" push r_temp\n"); // this doesn't work, Error: register name or number from 0 to 31 required
asm(" in r_temp,SREG\n"); // same error

Is this the ASM equivelant of "lvalue required"?

Anyway that's where I left it at 2AM last night. I'm back on deck now so will continue with this.


Rob

I don't know if you can properly use .equ or = to rename registers.

But it just happens to work under certain circumstances. In particular, don't make the first letter of the symbol 'r' and omit the 'r' from the register. ie:

.equ q_temp1,16

which is the same as

q_temp1 = 16

should 'work', at least for now.

I'll have to check the mechanics of having assembler included in your sketch when I'm at home with the source code.
I thought you could add a .S tab to your sketch just the way you can add .h, .c, or .cpp modules. It may have to be capitalized...

Huh. It looks like the "Compiler" part of the IDE understands .S files, but the editor refuses to add such files to the sketch, or pass them to the compiler. Hmmph.