0

I am trying to implement a program in assembly code for an Arduino UNO. A potentiometer is used on an ADC pin for variable time (t). LED1 flashes for t seconds, then stays on for t seconds, then goes off and another LED(LED2) goes on for t seconds and then the cycle repeats (t is variable time that comes from potentiometer). I have a working program in c++ but I want to convert it to assembly. I have just started learning assembly and am not very good at it.

The part I am really stuck on is the analogRead() function. I do not know how to implement this on assembly. Anyone know how to do this?

#define led 3
#define led2 2

void setup()

{

Serial.begin(9600);

//initializing pins
pinMode(led,OUTPUT);

pinMode(led2,OUTPUT);

}
void loop()

{

// read the input on analog pin 0:
int sensorValue = analogRead(A0);

// Convert the analog reading(which goes from 0 - 1023) to a voltage(0-5V):
int voltage = sensorValue * (5.0 / 1023.0);

// print out the value you read:
Serial.println(voltage);

for(int i=0;i<2*voltage;i++)

{

digitalWrite(led,LOW);

delay(100);

digitalWrite(led,HIGH);

delay(100);

}


//red stays on
digitalWrite(led,HIGH);

delay(voltage*500); 

digitalWrite(led,LOW);

//green stays on
digitalWrite(led2,HIGH);

delay(voltage*500);

digitalWrite(led2,LOW);

}
  • are you able to run simple blink code, that does not use analog input, in assembly? – jsotola Sep 4 at 20:25
  • It sounds like you want to forsake all the work people have put into the Arduino environment and do what is called bare metal programming. If that is the case, say so and I will respond with a new answer to your question. Be aware, this will immerse you into firmware programming where you will need to do everything the Arduino environment is doing for you. This can get so technical that some companies hire special embedded programmers who specialize in Board Support Packages or BSPs. – st2000 Sep 4 at 21:14
  • @jsotola Yes I am able to do that – user58745 Sep 4 at 22:07
  • @st2000 Yes please – user58745 Sep 4 at 22:08
  • 1
    Well, @CrossRoads basically told you what to do. You will need to read and understand the parts of the datasheet which describes the peripheral you are interested in using as well as the core of the processor. You don't need to bother with the electrical or physical packaging details. Yes, I know the PDF is 600 pages. (This is what the Arduino is abstracting you from.) Welcome to firmware and bare metal programming! – st2000 Sep 5 at 2:32
1

The obvious solution for converting any program to assembly is... to compile it! You can disassemble the resulting binary with a command like:

avr-objdump -S -C the_program.elf > the_program.lss

This will output a listing of the program, with the original source interspersed with an hex dump of the binary and the corresponding disassembly.

The part I am really stuck on is the analogRead() function.

Here it goes:

000003a6 <analogRead>:
 3a6:   8e 30           cpi r24, 0x0E   ; 14
 3a8:   08 f0           brcs    .+2         ; 0x3ac <analogRead+0x6>
 3aa:   8e 50           subi    r24, 0x0E   ; 14
 3ac:   87 70           andi    r24, 0x07   ; 7
 3ae:   20 91 00 01     lds r18, 0x0100 ; 0x800100 <__data_start>
 3b2:   90 e4           ldi r25, 0x40   ; 64
 3b4:   29 9f           mul r18, r25
 3b6:   90 01           movw    r18, r0
 3b8:   11 24           eor r1, r1
 3ba:   82 2b           or  r24, r18
 3bc:   80 93 7c 00     sts 0x007C, r24 ; 0x80007c <__TEXT_REGION_LENGTH__+0x7e007c>
 3c0:   80 91 7a 00     lds r24, 0x007A ; 0x80007a <__TEXT_REGION_LENGTH__+0x7e007a>
 3c4:   80 64           ori r24, 0x40   ; 64
 3c6:   80 93 7a 00     sts 0x007A, r24 ; 0x80007a <__TEXT_REGION_LENGTH__+0x7e007a>
 3ca:   80 91 7a 00     lds r24, 0x007A ; 0x80007a <__TEXT_REGION_LENGTH__+0x7e007a>
 3ce:   86 fd           sbrc    r24, 6
 3d0:   fc cf           rjmp    .-8         ; 0x3ca <analogRead+0x24>
 3d2:   80 91 78 00     lds r24, 0x0078 ; 0x800078 <__TEXT_REGION_LENGTH__+0x7e0078>
 3d6:   20 91 79 00     lds r18, 0x0079 ; 0x800079 <__TEXT_REGION_LENGTH__+0x7e0079>
 3da:   90 e0           ldi r25, 0x00   ; 0
 3dc:   92 2b           or  r25, r18
 3de:   08 95           ret

Now, in order to more easily make sense of it, you need to know what all those magic numbers mean. The command

avr-nm -nC the_program.elf

will dump the symbols associated with every RAM and flash address. In order to tell those apart, 0x800000 is added to every RAM address. This will tell you that, in this specific program, the RAM address 0x0100 is actually the address of the variable analog_reference from the Arduino core library. Thus, you can replace the line

 3ae:   20 91 00 01     lds r18, 0x0100 ; 0x800100 <__data_start>

by

    lds  r18, analog_reference

Then you can use the datasheet to get the names of the memory-mapped IO registers, and the names of the bits from those registers. I have done all this for you, so here is the cleaned-up disassembly of analogRead():

analogRead:
    cpi  r24, 14
    brcs 1f
    subi r24, 14
1:  andi r24, 0x07
    lds  r18, analog_reference
    ldi  r25, 1<<REFS0
    mul  r18, r25
    movw r18, r0
    clr  r1
    or   r24, r18
    sts  _SFR_MEM_ADDR(ADMUX), r24
    lds  r24, _SFR_MEM_ADDR(ADCSRA)
    ori  r24, 1<<ADSC
    sts  _SFR_MEM_ADDR(ADCSRA), r24
2:  lds  r24, _SFR_MEM_ADDR(ADCSRA)
    sbrc r24, ADSC
    rjmp 2b
    lds  r24, _SFR_MEM_ADDR(ADCL)
    lds  r18, _SFR_MEM_ADDR(ADCH)
    ldi  r25, 0
    or   r25, r18
    ret

If you compare this with the source code of analogRead(), it should now make perfect sense.

I am trying to implement a program in assembly

Now, if you want to write the assembly yourself, that's a whole different story. If you allow yourself the use of the Arduino core, then you can simply

    ldi r24, 14  ; pin A0 is pin 14
    call analogRead

If you don't want to rely on external libraries, and really write everything yourself, it's going to be a lot harder. I suggest you do this as a two-step process:

Step 1: Learn how to program the ATmega328P in plain C, at the register level, using no library other than the avr-libc. You can start by reading this short tutorial about direct port access. But then you should quickly move on to studying the datasheet and the manual of the avr-libc. Yes, that's a lot to digest. I hope you didn't expect this to be quick and easy. But note that, on those documents, you can skip all the chapters related to functions you are not going to use.

I am really stuck on is the analogRead() function.

In the specific case of your example program, you don't need a version of analogRead() that can read any pin with any reference. You can instead write a simpler version that just reads the currently selected pin using the currently selected reference:

int simple_analog_read(void)
{
    ADCSRA |= 1<<ADSC;                    // start the converion
    while (ADCSRA & (1<<ADSC)) continue;  // wait till it's done
    return ADC;                           // return the reading
}

Of course, this assumes you have appropriately initialized the ADC.

int voltage = sensorValue * (5.0 / 1023.0);

You really, really don't want to implement something like this in assembly. The AVR hardware supports neither floating point nor divisions. All this is handled by the compiler through supporting libraries, and they are not simple. What you really want is something like

int voltage = (sensorValue * 5) >> 10;

which is incidentally more correct, as the scaling factor is 5/1024, not 5/1023 (see the datasheet).

Step 2: Learn assembly, and rewrite your low-level C code in assembly. If your C code is low-level enough this should not be too hard. As a learning aid, you can ask the compiler to do it for you and then try to make sense of what it did. If you just try to compile the simple_analog_read() function I wrote above you will see the translation to assembly is almost trivial.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.