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– jsotola2019年09月04日 20:25:44 +00:00Commented Sep 4, 2019 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– st20002019年09月04日 21:14:33 +00:00Commented Sep 4, 2019 at 21:14
-
@jsotola Yes I am able to do thatuser58745– user587452019年09月04日 22:07:10 +00:00Commented Sep 4, 2019 at 22:07
-
@st2000 Yes pleaseuser58745– user587452019年09月04日 22:08:48 +00:00Commented Sep 4, 2019 at 22:08
-
2Well, @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– st20002019年09月05日 02:32:30 +00:00Commented Sep 5, 2019 at 2:32
1 Answer 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.
Explore related questions
See similar questions with these tags.