Hi all -
After weeks of searching and tearing my hair out and my fiftieth read through of the ADC and Interrupt portions of the datasheet, I finally resorted to opening a thread. It is as the title says - the ADC refuses to trigger conversion complete interrupts or, for that matter, even convert once! Old versions of this code and a (working!) C version are at https://github.com/npiscitello/adc_led.
What I Want It To Do:
There are LEDs on PB0-PB5 - I'm trying to implement a variable speed chase sequence. I'm doing this by using a potentiometer and an analog input to adjust the length of the delay between steps in the chase. The chase is definitely working - the full code has a timer implemented that drives the chase at a fixed speed using a compare interrupt (see the github link above). Basically, I was planning on writing the left adjusted ADC value in ADCH to the OCR0A register every time a conversion finished (aka, in the ISR). This would allow the timer to just do it's thing independently. I'm sure there are easier ways to do this, but I am using this exercise to learn specifically how to make multiple interrupts play nicely together.
What It's Doing:
Instead of chasing, the first LED turns on (as expected) and stays on (not as expected), exhibiting a complete failure to chooch. I can only assume that this is because the ADC conversion ISR is not being executed. In fact, due to other tests on similar versions of the code (all in the commit history), I don't think the ADC even converts once. I know that you need to kick off the first conversion for the ADC to start free-running by setting `ADSC` in `ADCSRA`. I have checked the prescalers to ensure the ADC clock is faster than the requisite 50 kHz. I've compared my ADC setup to the working C version and it seems to be identical (except for enabling the interrupt in the asm code below) - I've also checked the register settings against the datasheet more times than I can count. As mentioned above, I've verified my chase sequence using a known-working timer setup. I've verified that the ISR is being loaded into the correct location to the best of my ability - the internals of WinAVR are still mysterious to me, but both external interrupts and all the timer interrupts I've tried work and the ADC interrupt has the correct index (vector number 21, if the reset vector is at index 0). If there's any other checks I should do, I'd very much appreciate to hear about it! I've obviously missed a pretty crucial one...
Processor: Bare ATMEGA328P-PU on a breadboard
Fuses: default (lfuse: 0x62, hfuse: 0xD9, efuse: 0x07)
Clock: internal 8 MHz
Simulation: none (I tried to load a couple simulation packages but none of them seemed to work on my laptop)
Compiler: avr-gcc 4.3.3, provided by WinAVR 20100110
My Experience: I'm fairly experienced with C and C++ in the Arduino IDE and with embedded code (c++11) for other platforms, but this is my first time with assembler for any platform and my first time using a bare AVR chip with an ISP. I haven't quite read the 328P datasheet cover to cover, but I'm familiar with most sections and intimate with a few.
Available Equipment: I have an Adafruit TinyISP, Windows 10 laptop with an ArchLinux VM, and a DMM on my bench and I have access to a digital oscilloscope.
Thank you guys in advance for the help. This is my first actual post asking for code help - ususally I can figure it out on my own (aka Google can figure it out for me) but this one has me completely stumped. Please help me get my pixies dancing again!
; Nick P ; January 2017 ; Atmel ATMEGA328P-PU ; avr-gcc 4.3.3 (WinAVR 20100110) #include <avr/io.h> #include <avr/interrupt.h> ;=== REGISTER MAP ===; ; r16: temporary working reg #define TEMP r16 ; r17: minimum LED for chase sequence #define MIN r17 ; r18: maximimum LED for chase sequence #define MAX r18 ; r19: current LED lit #define LOC r19 ; r20: LED direction (1 for ascending, 0 for descending) #define DIR r20 #define ASC 0x01 #define DSC 0x00 ; r21: ADC value to be written to output compare #define ADC r21 ;=== UTILITY MACROS ===; #define low(x) ((x) & 0xFF) #define high(x) (((x) >> 8) & 0xFF) ; run when the chip resets .global main main: ; divide system clock by 64 (I would prescale it down by 256 but the ADC needs at least 100kHz) ; this gives a system clock speed of 8 MHz / 64 = 125 kHz ldi TEMP,0x00 | _BV(CLKPCE) sts CLKPR,TEMP ldi TEMP, 0x00 | _BV(CLKPS2) | _BV(CLKPS1) sts CLKPR,TEMP ; enable output pins ldi TEMP,0x00 | _BV(DDB5) | _BV(DDB4) | _BV(DDB3) | _BV(DDB2) | _BV(DDB1) | _BV(DDB0) out _SFR_IO_ADDR(DDRB),TEMP ; set min and max for LED chase and initialize location to min LED clr MIN clr MAX clr LOC clr DIR ldi MIN,_BV(PORTB0) ldi MAX,_BV(PORTB5) mov LOC,MIN ldi DIR,ASC ; power on the features we are using lds TEMP,PRR cbr TEMP,PRADC sts PRR,TEMP ; set stack pointer to the end of RAM (required for returning from interrupts) ldi TEMP,low(RAMEND) sts SPL,TEMP ldi TEMP,high(RAMEND) sts SPH,TEMP ; set up ADC voltage reference and input pin ; ADMUX [ REFS1 | REFS0 | ADLAR | - | MUX3 | MUX2 | MUX1 | MUX0 ] lds TEMP,ADMUX ; load ADMUX into TEMP cbr TEMP,REFS1 ; enable internal voltage ref sbr TEMP,REFS0 ; enable internal voltage ref cbr TEMP,MUX3 ; set ADC0 as the ADC pin cbr TEMP,MUX2 ; set ADC0 as the ADC pin cbr TEMP,MUX1 ; set ADC0 as the ADC pin cbr TEMP,MUX0 ; set ADC0 as the ADC pin sbr TEMP,ADLAR ; left adjust result for 8-bit precision sts ADMUX,TEMP ; set ADMUX to TEMP ; set up ADC trigger source ; ADCSRB [ - | ACME | - | - | - | ADTS2 | ADTS1 | ADTS0 ] lds TEMP,ADCSRB ; load ADCSRB into TEMP cbr TEMP,ADTS2 ; select ADC interrupt as trigger source cbr TEMP,ADTS1 ; select ADC interrupt as trigger source cbr TEMP,ADTS0 ; select ADC interrupt as trigger source sts ADCSRB,TEMP ; set ADCSRB to TEMP ; set up ADC clocking and triggering, enable ADC ; ADCSRA [ ADEN | ADSC | ADATE | ADIF | ADIE | ADPS2 | ADPS1 | ADPS0 ] lds TEMP,ADCSRA ; load ADCSRA into TEMP sbr TEMP,ADEN ; enable ADC cbr TEMP,ADPS2 ; divide system clock by 2 (62.5 kHz) cbr TEMP,ADPS1 ; divide system clock by 2 (62.5 kHz) cbr TEMP,ADPS0 ; divide system clock by 2 (62.5 kHz) sbr TEMP,ADATE ; enable auto trigger sbr TEMP,ADIE ; enable ADC interrupts sbr TEMP,ADIF ; clear pending ADC interrupts sbr TEMP,ADSC ; trigger first read sts ADCSRA,TEMP ; set ADCSRA to TEMP sei ; globally enable interrupts out _SFR_IO_ADDR(PORTB),LOC ; start at minimum LED rjmp loop ; jump into infinite loop ; infinite loop. I guess this could be a sleep maybe? loop: lds ADC,ADCH ; read the ADC value rjmp loop ; loop infinitely ; run when the ADC finishes a conversion .global ADC_vect ADC_vect: cpi DIR,ASC ; compare the direction reg with the ascend direction constant... breq ascend ; ...and skip to the ascend code if they're equal... rjmp descend ; ...or to the descend code if they're not. ascend: lsl LOC ; increment LED location (originally used rol, but it screwed up the compare out _SFR_IO_ADDR(PORTB),LOC ; write LED location out cpse LOC,MAX ; compare the location to the max location... reti ; ...and skip this instruction if they're the same. ldi DIR,DSC ; reverse direction reti ; return from ISR descend: lsr LOC ; decrement LED location (originally used ror, but it screwed up the compare) out _SFR_IO_ADDR(PORTB),LOC ; write LED location out cpse LOC,MIN ; compare the current location with the min location... reti ; ...and skip this instruction if they're the same. ldi DIR,ASC ; reverse direction reti ; return from ISR