C to Assembly timer help

Hello everyone. I have some working C code that takes input from the serial monitor and sends it to the Arduino which is wired to a seven segment display. The inputs are the number 9 → 0, and the corresponding digit will be displayed on the hex display.

I would like to transpose this code to pure Assembly, but I am having some difficulties. The logic seems right, but being new to assembly, I could use some help.

The C-code is:

#define BAUD_PRESCALER 0x0067 //9600 baud
#include <avr/io.h>

int i;

int seven_seg_display[10][7] =
{
  { 1, 1, 1, 1, 1, 1, 0 }, // = 0
  { 0, 1, 1, 0, 0, 0, 0 }, // = 1
  { 1, 1, 0, 1, 1, 0, 1 }, // = 2
  { 1, 1, 1, 1, 0, 0, 1 }, // = 3
  { 0, 1, 1, 0, 0, 1, 1 }, // = 4
  { 1, 0, 1, 1, 0, 1, 1 }, // = 5
  { 1, 0, 1, 1, 1, 1, 1 }, // = 6
  { 1, 1, 1, 0, 0, 0, 0 }, // = 7
  { 1, 1, 1, 1, 1, 1, 1 }, // = 8
  { 1, 1, 1, 0, 0, 1, 1 } // = 9
};

void setup()
{
  pinMode(2, INPUT);
  pinMode(3, OUTPUT);
  pinMode(4, OUTPUT);
  pinMode(5, OUTPUT);
  pinMode(6, OUTPUT);
  pinMode(7, OUTPUT);
  pinMode(8, OUTPUT);
  pinMode(9, OUTPUT);
  pinMode(10, OUTPUT);
  cli(); // Disable global interrupts
  USART_interrupt_init();
  sei(); // Enable global interrupts
  
}

void seven_seg_hex(int digit)
{
  int pin = 3;
  for (int segCount = 0; segCount < 7; ++segCount) {
    digitalWrite(pin, seven_seg_display[digit][segCount]);
    ++pin;
  }
}

void loop() {
  if (i == 1) 
  seven_seg_hex(1);
  else if (i == 2) 
  seven_seg_hex(2);
  else if (i == 3) 
  seven_seg_hex(3);
  else if (i == 4) 
  seven_seg_hex(4);
  else if (i == 5) 
  seven_seg_hex(5);
  else if (i == 6) 
  seven_seg_hex(6);
  else if (i == 7) 
  seven_seg_hex(7);
  else if (i == 8) 
  seven_seg_hex(8);
  else if (i == 9) 
  seven_seg_hex(9);
  else if (i == 0) 
  seven_seg_hex(0);
}

unsigned char USART_Receive(void) {
      /* Wait for data to be received */
      while (!(UCSR0A & (1<<RXC0)));
      /* Get and return received data from buffer */
      return UDR0;
}

ISR(USART_RX_vect)
{
    i = UDR0-48;  //Read USART data register
}

void USART_interrupt_init(void)
{
  cli();
  UBRR0 = BAUD_PRESCALER; 
  UCSR0B = (1<<RXEN0)|(1 << RXCIE0); 
  UCSR0C = (0<<USBS0)|(1 << UCSZ01)|(1<<UCSZ00);
  sei(); 
}

And my (non-working) assembly code is:

#include "avr/io.h"
#include "avr/interrupt.h"
            
            .global setup
            .global loop
            .global USART_receive
            .global USART_interrupt_init
            .global USART_RX_vect

setup:
            ldi r19, 0b11111000    // Initilizing parameters for 7 segment display
            ldi r20, 0b00000111
            out 0x0A, r19
            out 0x04, r20
            
            cli // Disable global interrupts
            rcall USART_interrupt_init
            sei // Enable interrupt
            reti

USART_interrupt_init:
            cli
            ldi r16, 0b10011000        // (1<<RXEN0)|(1 << RXCIE0)
            sts UCSR0B, r16           //  Enable receiver and interrupt
            ldi r16, 0b00000110      //   UCSR0C = (0<<USBS0)|(1 << UCSZ01)|(1<<UCSZ00)
            sts UCSR0C, r16         //    1 stop bit | 8-bit CHAR SIZE
                                   //     Could have skipped UCSR0C since these are all default values
            
            
            ldi r20, 0x0067                //  UBRR0 = BAUD_PRESCALER
            sts UBRR0, r20                //   Set the baud rate prescale rate register
            sei
            reti

USART_receive:
            lds r24, UCSR0A
            sbrs r24, RXC0
            rjmp USART_receive
            lds r22, UDR0
            reti

USART_RX_vect:
            subi r22, 0x30 // Subtract 48 from r22
            mov r26, r22
            reti


loop: 
          cpi r26, 7
          rcall seven
          
          cpi r26, 8
          rcall eight
          
          cpi r26, 9
          rcall nine
            
          cpi r26, 0
          rcall zero
          
          cpi r26, 1
          rcall one
          
          cpi r26, 2
          rcall two
          
          cpi r26, 3
          rcall three
          
          cpi r26, 4
          rcall four
          
          cpi r26, 5
          rcall five
          
          cpi r26, 6
          rcall six
          reti
          
nine:  
        ldi r19, 0b00111000
        ldi r20, 0b00000011
        out 0x0B, r19
        out 0x05, r20
        reti
        
eight:  
        ldi r19, 0b11111000
        ldi r20, 0b00000011
        out 0x0B, r19
        out 0x05, r20
        reti

seven:          
        ldi r19, 0b00111000
        ldi r20, 0b00000000
        out 0x0B, r19
        out 0x05, r20
        reti

six:  
        ldi r19, 0b11101000
        ldi r20, 0b00000011
        out 0x0B, r19
        out 0x05, r20
        reti

five:          
        ldi r19, 0b01101000
        ldi r20, 0b00000011
        out 0x0B, r19
        out 0x05, r20
        reti

four:       
        ldi r19, 0b00110000
        ldi r20, 0b00000011
        out 0x0B, r19
        out 0x05, r20
        reti

three:        
        ldi r19, 0b01111000
        ldi r20, 0b00000010
        out 0x0B, r19
        out 0x05, r20
        reti

two:          
        ldi r19, 0b11011000
        ldi r20, 0b00000010
        out 0x0B, r19
        out 0x05, r20
        reti

one:            
        ldi r19, 0b00110000
        ldi r20, 0b00000000
        out 0x0B, r19
        out 0x05, r20
        reti

zero:           
        ldi r19, 0b11111000
        ldi r20, 0b00000001
        out 0x0B, r19
        out 0x05, r20
        reti

Any guidance, hints, suggestions would be much appreciated.

What in the world do you hope to gain by this? Modern C compilers are incredibly efficient, and you will save very little, or nothing, by translating that very simple code to assembler.

Regards, Ray L.

I would like to transpose this code to pure Assembly

Why? The compiler is pretty darned good at generating optimal code. There is rarely a reason to resort to assembly language programming.

There is NEVER a reason to just say "it doesn't work". It does something. You expect it to do something. YOU must tell us what those two somethings are.

It's for educational purposes. I am trying to learn Assembly.

With the above code, nothing happens until I remove the line: .global USART_RX_vect (I don't think I need it anyway because it's a predefined function, right?) When I remove this line, then when I go to input a number in the serial monitor, no matter what number I enter, it will display a number '6' on the hex display, at which point I can do nothing else.

            .global setup
            .global loop
            .global USART_receive
            .global USART_interrupt_init
            .global USART_RX_vect

The C global namespace generally includes a hidden leading underscore (e.g. _USART_RX_vect).

The C++ global namespace generally includes mangling.

The vectors are named by number; "USART_RX_vect" in any form is not the actual name of the function.

As far as I know all of those are your responsibility when working in assembly (which is why I always inline instead).

Hi davlovsky.

I'm also keen to try some asm code when my hardware arrives, so I'm interested in how you go at sorting this out.

Just wondering if you've tried some really simple stuff without any comms or interrupts, I mean just to test that you at least have the "setup" and "loop" stuff happened as expected from the "S" file code.

Have you tested it with something ultra simple like flashing the LED on and off, just to make sure those basic things (setup and loop) are operating as expected?

If you turn on verbose compile logging you will see the command the IDE uses to compile your sketch. You can execute that command at the command line and add the argument to generate an assembler listing of the generated code: '-S'. That will create a .s file with the assembler listing.

Consider starting with inline assembly? A wealth of information is located here.

Thank you for all your suggestions. There were a few syntactical errors.

The heading of the code was changed:

#include "avr/io.h"
#include "avr/interrupt.h"
#define USART_RX_vect    _VECTOR(18)
            
            .global setup
            .global loop
            .global USART_interrupt_init
            .global USART_RX_vect

I also added ‘rcall USART_receive’ at the beginning of the loop, which I had originally and not sure why it wasn’t in the code that I originally posted.

The big thing was my use of the repeated ‘rcall’ in the loop. I originally had ‘breq’ but was getting this weird error: relocation truncated to fit: R_AVR_7_PRCEL against ‘no symbol’, which was pointing to my ‘breq’ statements; this is why I had changed them all to ‘rcall’.

But ‘rcall’ will call a function regardless of the previous compare statements, so ‘breq’ was correct. I did a little more searching in regards to the error and I found the solution (http://stackoverflow.com/questions/8188849/avr-linker-error-relocation-truncated-to-fit). So, I changed ‘rcall USART_receive’ to ‘call USART_receive’ and voila, it works.

#include "avr/io.h"
#include "avr/interrupt.h"
#define USART_RX_vect    _VECTOR(18)
            
            .global setup
            .global loop
            .global USART_interrupt_init
            .global USART_RX_vect

setup:
            ldi r19, 0b11111000    // Initilizing parameters for 7 segment display
            ldi r20, 0b00000111
            out 0x0A, r19
            out 0x04, r20
            
            cli // Disable global interrupts
            rcall USART_interrupt_init
            sei // Enable interrupt
            ret
            
loop:     call USART_receive
          cpi r26, 0
          breq zero

          cpi r26, 1
          breq one

          cpi r26, 2
          breq two 

          cpi r26, 3
          breq three

          cpi r26, 4
          breq four

          cpi r26, 5
          breq five

          cpi r26, 6
          breq six

          cpi r26, 7
          breq seven

          cpi r26, 8
          breq eight
          
          cpi r26, 9
          breq nine
        
nine:   ldi r19, 0b00111000
        ldi r20, 0b00000011
        out 0x0B, r19
        out 0x05, r20
        reti
        
eight:  ldi r19, 0b11111000
        ldi r20, 0b00000011
        out 0x0B, r19
        out 0x05, r20
        reti

seven:  ldi r19, 0b00111000
        ldi r20, 0b00000000
        out 0x0B, r19
        out 0x05, r20
        reti

six:    ldi r19, 0b11101000
        ldi r20, 0b00000011
        out 0x0B, r19
        out 0x05, r20
        reti

five:   ldi r19, 0b01101000
        ldi r20, 0b00000011
        out 0x0B, r19
        out 0x05, r20
        reti

four:   ldi r19, 0b00110000
        ldi r20, 0b00000011
        out 0x0B, r19
        out 0x05, r20
        reti
        
three:  ldi r19, 0b01111000
        ldi r20, 0b00000010
        out 0x0B, r19
        out 0x05, r20
        reti
        
two:    ldi r19, 0b11011000
        ldi r20, 0b00000010
        out 0x0B, r19
        out 0x05, r20
        reti

one:    ldi r19, 0b00110000
        ldi r20, 0b00000000
        out 0x0B, r19
        out 0x05, r20
        reti

zero:   ldi r19, 0b11111000
        ldi r20, 0b00000001
        out 0x0B, r19
        out 0x05, r20
        reti
        
USART_interrupt_init:
            cli
            ldi r16, 0b10010000        // (1<<RXEN0)|(1 << RXCIE0)
            sts UCSR0B, r16           //  Enable receiver and interrupt
            ldi r16, 0b00000110      //   UCSR0C = (0<<USBS0)|(1 << UCSZ01)|(1<<UCSZ00)
            sts UCSR0C, r16         //    1 stop bit | 8-bit CHAR SIZE
                                   //     Could have skipped UCSR0C since these are all default values
             
            ldi r20, 0x0067                //  UBRR0 = BAUD_PRESCALER
            sts UBRR0, r20                //   Set the baud rate prescale rate register
            sei
            ret

USART_receive:
            lds r24, UCSR0A
            sbrs r24, RXC0
            rjmp USART_receive
            lds r22, UDR0
            ret

USART_RX_vect:
            mov r26, r22
            subi r26, 48 // decrement by 48
            reti