AneesAhmed777
AneesAhmed777

Reputation: 2695

How to do analogRead() in AVR assembly language?

If I need to be specific: I'm asking about ATmega328P chip. The analog pins are under PortC on this chip.

I have learnt that digitalWrite can be done using out, and digitalRead using in.
But how can I do analogRead ?? Please explain. I'm new to this.

EXTRA: It would be helpful if you show analogWrite too (In the sense of PWM).

Upvotes: 1

Views: 8556

Answers (3)

bsliao
bsliao

Reputation: 1

After a long time struggle with me, I survey the datasheet of ATmega 328P and many google surfing articles, the simple and workable code is completed as below.

; UNO_asmADCapp.asm
; revised by bsliao: 2020/5/12 下午 03:39:20, TEST OK 2020/05/13, 11:33
; Reference: 
; https://stackoverflow.com/questions/38972805/
; [1] how-to-code-an-adc-for-an-atmega328p-in-assembly
;    Author : Dario, Created: 8/14/2016 7:34:43 AM
; [2] https://robotics.ee.uwa.edu.au/courses/des/labprep/
; LabPrep%205%20-%20Timers%20and%20ADC%20in%20ATMEL.pdf
; [3] https://www.avrfreaks.net/forum/adc-converter-assembly-using-atmega328p-mcu
;  AD0 --- uno A0
;  value ADCH (b9 b8) ADCL (b7- b0) <Internal> --- PB1(uno d9) PB0 (d8), PD7-PD0 (uno D7 -D0)
#define  F_CPU 16000000UL
.def  temp =r16

; Replace with your application code
.include "./m328Pdef.inc"
    .org 0x000
    rjmp start

;   .org 0x002A
;   rjmp ADC_conversion_complete_Handler
start:
    eor r1, r1
    out SREG, r1
    ldi temp, HIGH(RAMEND)
    out SPH, r16
    ldi temp, LOW(RAMEND)
    out SPL, r16
setup:
    ldi temp, 0xFF ; set r16 = 1111 1111
    out ddrb, temp ; set all d pins as output
    out ddrd, temp ; set all b pins as output

configADC0:
;------initialize ADC0 -------  Set ADMUX and ADCSRA: 
;REF1 REFS0 ALLAR - (MUX3 MUX2 MUX1 MUX0 )=(0000)  
;Aref=5.0 V is used, default right-adjust result,  analog in at AIN0 (ADC0)  
    LDI temp, 0x00
    STS ADMUX, temp

;ADcENable, (ADPS2 ADPS1 ADPS0 )=(000) : division factor=128 16Mhz/128: ADC0 is applied.

    LDI temp, (1<<ADEN)|(1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0)
    STS ADCSRA, temp 
    andi temp, 0b11011111 
    STS ADCSRA, temp 

; the first conversion  
    LDS temp,ADCSRA
    ori temp, (1<<ADSC);
    STS ADCSRA, temp

LOOP:
; start the next single conversion on ADCn, here n=0
    LDS temp,ADCSRA
    ori temp, (1<<ADSC);
    STS ADCSRA, temp

adc_read_loop:
// while (bit_is_set(ADCSRA, ADSC));
    lds temp,ADCSRA
    sbrc temp,ADSC   ;after ADC0 conversion over, the bit ADSC in the ADCSRA is set to zero and the bit ADIF is set to one.
    rjmp adc_read_loop

read_ADC_value: 
    lds r24,ADCL
    lds r25,ADCH 

display_ADC_value:
    andi r25, 0x03
    out PORTB, r25    ; LEDs active high, PORTB most significant byte
    com r24           ; LEDs active low
    out PORTD, r24    ; PORTD less significant byte
    call one_sec_delay
    rjmp LOOP

one_sec_delay:
        ldi     r20, 20
        ldi     r21, 255
        ldi     r22, 255
delay:  dec     r22
        brne    delay
        dec     r21
        brne    delay
        dec     r20
        brne    delay
        ret

Upvotes: 0

AneesAhmed777
AneesAhmed777

Reputation: 2695

This is for the future visitors who stumble upon here...


As mentioned by Rev1.0, Arduino C does make things too easy for you. A lot of complicated things are going on under the hood when you write a simple statement analogRead(). But it's not that complicated once you understand it. You should definitely read up on ADCs.

As mentioned by David Grayson, you should definitely take a look at the source code of analogRead(). Here is the datasheet of ATmega328P and the instruction set manual for ATmega328P to help you understand what is going on.

You can read this and this to get some idea on how to exactly write the code.


Now, here is what I came up with for my use-case in my project.
The bold-face words are there to tell you that this code was NOT written for a general use-case. Copy-Pasting this will most probably not work.
You see the amuont of links in this post? Read all of them. Below is only for using as a reference in case you get stuck and it might help.

adcInit:
    ldi r16, 0b01100000   ; Voltage Reference: AVcc with external capacitor at AREF pin
    sts ADMUX, r16        ; Enable ADC Left Adjust Result
                          ; Analog Channel: ADC0

    ldi r16, 0b10000101   ; Enable ADC
    sts ADCSRA, r16       ; ADC Prescaling Factor: 32

    ret

adcRead:
    ldi r16, 0b01000000   ; Set ADSC flag to Trigger ADC Conversion process
    lds r17, ADCSRA       ;
    or  r17, r16          ;
    sts  ADCSRA, r17      ;
    ret

adcWait:
    lds r17, ADCSRA       ; Observe the ADIF flag, it gets set by hardware when ADC conversion completes
    sbrs r17, 4           ;

    jmp adcWait           ; Keep checking until the flag is set by hardware

    ldi r16, 0b00010000   ; Set the flag again to signal 'ready-to-be-cleared' by hardware
    lds r17, ADCSRA       ;
    or  r17, r16          ;
    sts  ADCSRA, r17      ;
    ret

It is used like this:

call adcInit

mainLoop:
    call adcRead
    call adcWait
    lds r18, ADCL  ; Must read ADCL first, and ADCH after that
    lds r19, ADCH

Upvotes: 3

David Grayson
David Grayson

Reputation: 87416

You can read the source code of analogRead from the Arduino environment:

https://github.com/arduino/ArduinoCore-avr/blob/master/cores/arduino/wiring_analog.c

The important thing is to find all the places where it reads or writes from a special function register (SFR) like ADMUX, and then make sure you do the same thing in your assembly code.

You should also look at the ATmega328P datasheet, which defines all of those SFRs, as a way to double check that you are doing the correct thing.

If you have further trouble, I recommend asking a new question where you show some code and get specific about exactly what part of analogRead is confusing to you.

Upvotes: 5

Related Questions