(Sort of) Running an Arduino Program Stored in Memory

flash

Ever wonder if you could call a program stored as machine code inside of an array? It’s possible, however, there are some hurdles to overcome.

First, the arduino (an ATMEL AVR based μC) is based upon the modified Harvard architecture. Why is that important? Because in the Harvard architecture, data and program instructions are stored in different memory. These separate pathways are primarily implemented to enhance performance, but it also prohibits executing program instructions from data memory. Bummer!

Fortunately, there are provisions for storing “data” inside program memory (see this information on the use of the PROGMEM attribute). Our first task is to write a simple test program in assembly and store the machine code inside program memory. Easy!

Here is our version of a simple “blink” program :

.section .text

  sbi 0x04, 5         ;set D13(LED) as output

1:
  sbi 0x05, 5         ;turn LED on

  ldi r20, 80         ;delay ~1 second
  ldi r21, 255
  ldi r22, 255
2:
  dec r22
  brne 2b
  dec r21
  brne 2b
  dec r20
  brne 2b

  cbi 0x05, 5         ;turn LED off

  ldi r20, 80         ;delay ~1 second
  ldi r21, 255
  ldi r22, 255
3:
  dec r22
  brne 3b
  dec r21
  brne 3b
  dec r20
  brne 3b

  rjmp 1b             ;repeat

We compile (or assemble) this using the avr-as program like so:

avr-as blink.S –o blink.out
avr-objcopy –O binary blink.out blink.bin

Using a hex-editor, we open the blink.bin file and copy the machine code of our program:

hexed

This hexidecimal data is then inserted into our 44-byte MachineCode array inside the following arduino program (notice the use of the PROGMEM attribute which forces the data array to be stored inside flash memory):

const uint8_t MachineCode[44] PROGMEM = {
  0x25, 0x9A, 0x2D, 0x9A, 0x40, 0xE5, 0x5F, 0xEF,
  0x6F, 0xEF, 0x6A, 0x95, 0xF1, 0xF7, 0x5A, 0x95,
  0xE1, 0xF7, 0x4A, 0x95, 0xD1, 0xF7, 0x2D, 0x98,
  0x40, 0xE5, 0x5F, 0xEF, 0x6F, 0xEF, 0x6A, 0x95,
  0xF1, 0xF7, 0x5A, 0x95, 0xE1, 0xF7, 0x4A, 0x95,
  0xD1, 0xF7, 0xEB, 0xCF
};

const uint8_t *ptr = MachineCode;

void setup() {
  //get address of code and call it
  asm(
    "lds r30, ptr   \n\t"
    "lds r31, ptr+1 \n\t"
    "lsr r30        \n\t" //convert byte address (data) to word address (flash)
    "icall          \n\t"
  );
}

void loop() {
  //never reach here
}

That’s our entire program. It doesn’t look like it does much does it?

Finally, we define a pointer and set it to point at the MachineCode array:

const uint8_t *ptr = MachineCode;

Inside the arduino setup() function, we run a very short inline assembler routine. Here we simply convert the data (SRAM) address into a program (flash) address. This is required, because in an arduino (an ATMEL AVR based μC) the data memory is aligned by bytes (8-bits), and program memory is aligned via words (16-bits). This conversion is a simple process of dividing the address by 2 (note, we assume our MachineCode array is stored in low memory, immediately after the IVT, which makes our division easier):

  asm(
    "lds r30, ptr   \n\t"
    "lds r31, ptr+1 \n\t"
    "lsr r30        \n\t" //convert byte address (data) to word address (flash)
    "icall          \n\t"
  );

If we run this program on an arduino, you will notice it blinks the D13 LED at approximatley 1-second intervals. Next we will add provisions to transfer our array from data (SRAM) to program (flash) memory.

Advertisements

About Jim Eli

µC experimenter
This entry was posted in Uncategorized and tagged , , , , , , , , . Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s