I want to program the ADC module of arduino Mega2560 using Assembly language (for simulation and learning), I can initialise the ADC Module using Assembly and my program converts in the first iteration, but since the ATmega2560 has an exteded I/O registers, I did not find a way to reset the ADC Flag bit (ADIF) once the first conversion is done. If I use the SBI instruction, I got a error message that says: "operand out of range: 122"
#define __SFR_OFFSET 0x00
#include <avr/io.h>
.global init_ADC
.global init_PORT
.global read_ADC
;----PORT Initilasation--------
init_PORT:
LDI R23,0xFF;
OUT DDRB,R23 ;Configure PORTB as OUTPUT
OUT DDRC,R23 ;Configure PORTC as OUTPUT
RET
;----Initializing ADC Module----------
init_ADC:
LDI R19,0x00 ;
OUT DDRF,R19 ;Configure ADC0 as INPUT for analog conversion
LDI R16,0x40 ;Load R16 with 0x40
STS ADMUX,R16 ;Configure ADMUX with 5V vref and channel 0 for conversion
LDI R17,0x00
STS ADCSRB,R17 ;Set MUX5=0
LDI R18,0x85
STS ADCSRA,R18 ;Set Enable bit and prescaler to 1:32
RET
read_ADC:
LDI R20,0x40 ;
LDS R21,ADCSRA;
ADD R20,R21;
STS ADCSRA,R20 ;Set ADSC bit to start conversion
RCALL WAIT ;
LDS R21,ADCH;
OUT PORTC,R21
LDS R22,ADCL
OUT PORTB,R22
;==CLEAR FLAG BIT===+
SBI ADCSRA,ADIF ;Set ADC flag bit to clear it
RET
WAIT:
LDS R16,ADCSRA ;Load ADCSRA to R16 register
ANDI R16,0x10;
BREQ WAIT
RET
How could I clear or set any bit of a register that is out of range of these instructions (In my case is the ADCSRA) ?
2 Answers 2
IN
/OUT
/SBI
/CBI
are just shortcuts for ST
/LD
family instructions (plus additional operations in the case of the latter), for addresses within the IO range. The IO range is very limited; registers outside that range, must use regular SRAM access. This is a common oversight, so this seems relevant here.
But you're already using LDS
and STS
to access registers outside that range; it seems you do know this. So I'm not sure precisely what you're asking. Then, you know how to read-modify-write, right?:
For example, avr-gcc 13 compiles
ADCSRA |= 1 << ADIF;
into
ldi r30,lo8(122)
ldi r31,0
ld r24,Z
ori r24,lo8(16)
st Z,r24
Of course if you're already using Z
, you can use LDS
/STS
directly; same code size (and, cycle count I think?).
Finally, as mentioned in the other answer, a read-modify-write is not actually required for this register; the bit is already set and you can simply read then write. Convenient!
Indeed, if you already know the value of the other bits of ADCSRA
, you don't need to read it at all (and, generally settings aren't changing, so this can indeed be used in practice).
Or if the values are changing (say, stopping ADC after a series of acquisitions, to save power), I suppose you can write them back at the same time; unless there's errata that says not to.
(Semantics: note the above instructions are not a direct substitute for SBI
/CBI
, as they are not atomic; a truer emulation would wrap the passage in CLI
...SEI
(depending on initial I
flag state), but this only accounts for software atomicity. Nothing can be done to account for register atomicity -- but AVR peripherals are at least reasonably well behaved, and we're generally fine with this.)
In general, read a non-bit-accessible IO register into a general purpose register, modify the bits you want, and write it back.
But actually, you don't need to modify any bits.
The ADIF bit will be set when ready, and to clear it, you need to write '1' to it, so just write back what you wrote to clear ADIF.
Explore related questions
See similar questions with these tags.
lds r24,ADCSRA ori r24, (1 << ADIF) sts ADCSRA,r24
in CADCSRA |= (1 << ADIF);
And why do you want to clear this flag? This flag is cleared by hardware. Also, do not forget that ADIF is not equal to 0x10 but 0x04 instead. This is why I usedori r24,(1 << ADIF)
whit is equivalent toori r24,0x10
\$\endgroup\$ldi r16,ADIF
will load 4 to r16. \$\endgroup\$