I need to write assembly code for my ATmega8 microcontroller that will generate 12-bit triangular voltage with a frequency ranging from 10 Hz to 1 kHz. Additionally, the code should allow controlling the frequency using a potentiometer.
I've designed a schematic in Proteus: I connected a potentiometer to the PC0/ADC0 port of the ATmega8 microcontroller and linked a power source and ground to it. Then, I connected power to the AVCC and AREF ports of the microcontroller. To interface the MCP4921 with the ATmega8 microcontroller, I used the PB5/SCK port for SCK, PB3/MOSI/OC2 port for SDI from the MCP4921, and PB2/SS/OC1B port for CS from the MCP4921. After that, I connected the MCP4921 via the LDAC port to ground. I also provided power to the MCP4921 through the VREFA port and connected a voltmeter to the VOUTA port, tied to ground, along with an oscilloscope.
My code:
; Assembly code for ATmega8, which generates a 12-bit triangular voltage ranging from 10 Hz to 1 kHz
; and controls the frequency using a potentiometer connected to the PC0/ADC0 port.
; Connection scheme of MCP4921 to ATmega8:
; SCK - PB5
; SDI - PB3
; CS - PB2
; LDAC - GND
; VREFA - VCC
; VOUTA - voltmeter and oscilloscope
.include "m8def.inc" ; Definitions for ATmega8
.equ F_CPU = 8000000 ; Clock frequency
.equ SPI_DDR = DDRB ; Port B data direction register
.equ SPI_PORT = PORTB ; Port B data register
.equ SPI_SCK = PB5 ; SCK pin
.equ SPI_MOSI = PB3 ; MOSI pin
.equ SPI_CS = PB2 ; CS pin
.def temp = r16 ; Temporary register
.def value = r17 ; Register to store potentiometer value
.def step = r18 ; Register to store voltage change step
.def dac = r19 ; Register to store value for MCP4921
.def flag = r20 ; Register to store direction flag for voltage change
.org 0x0000 ; Program start
rjmp start ; Jump to start label
.org 0x0012 ; Timer 2 comparison interrupt vector
rjmp timer2_isr ; Jump to interrupt handler
start:
; SPI initialization
ldi temp, (1\<\<SPI_SCK)|(1\<\<SPI_MOSI)|(1\<\<SPI_CS) ; Set SCK, MOSI, and CS pins as outputs
out SPI_DDR, temp ; Write to Port B data direction register
ldi temp, (1\<\<SPE)|(1\<\<MSTR)|(1\<\<SPR0) ; Enable SPI, set master mode, set clock rate divider to 16
out SPCR, temp ; Write to SPI control register
; ADC initialization
ldi temp, (1\<\<REFS0) ; Select internal reference voltage source
out ADMUX, temp ; Write to ADC channel selection register
ldi temp, (1\<\<ADEN)|(1\<\<ADPS2)|(1\<\<ADPS1)|(1\<\<ADPS0) ; Enable ADC, set clock rate divider to 128
out ADCSRA, temp ; Write to ADC control and status register A
; Timer 2 initialization
ldi temp, (1\<\<WGM21) ; Set CTC mode (clear timer on compare match)
out TCCR2, temp ; Write to Timer 2 control register
ldi temp, 249 ; Set comparison value for 1 kHz interrupt frequency
out OCR2, temp ; Write to Timer 2 compare register
ldi temp, (1\<\<OCIE2) ; Enable Timer 2 comparison interrupt
out TIMSK, temp ; Write to Timer interrupt mask register
ldi temp, (1\<\<CS21)|(1\<\<CS20) ; Start Timer 2 with a frequency divider of 32
out TCCR2, temp ; Write to Timer 2 control register
sei ; Enable global interrupts
ldi step, 1 ; Initialize voltage change step
ldi flag, 0 ; Initialize voltage change direction flag
main:
rcall read_adc ; Read potentiometer value
rcall write_dac ; Write value to MCP4921
rjmp main ; Infinite loop
read_adc:
sbi ADCSRA, ADSC ; Start ADC conversion
adc_wait: ; Wait for conversion to complete
in temp, ADCSRA ; Read ADC control and status register
sbrs temp, ADSC ; Check ADSC bit
rjmp adc_wait ; Continue waiting if bit is not cleared
in value, ADCH ; Read high byte of ADC result
ret ; Return from subroutine
write_dac:
ldi temp, 0b00110000 ; Set configuration bits for MCP4921
or temp, dac ; Add high 4 bits of value for MCP4921
cbi SPI_PORT, SPI_CS ; Set CS pin low
rcall spi_send ; Send byte over SPI
swap dac ; Swap high and low nibbles of value for MCP4921
andi dac, 0b00001111 ; Clear high 4 bits of value for MCP4921
rcall spi_send ; Send byte over SPI
sbi SPI_PORT, SPI_CS ; Set CS pin high
ret ; Return from subroutine
spi_send:
out SPDR, temp ; Write byte to SPI data register
spi_wait: ; Wait for transmission to complete
in temp, SPSR ; Read SPI status register
sbrs temp, SPIF ; Check SPIF bit
rjmp spi_wait ; Continue waiting if bit is not set
ret ; Return from subroutine
timer2_isr:
; Timer 2 comparison interrupt handler
; Changes the value for MCP4921 based on potentiometer value and direction flag
push temp ; Save temp register to stack
push value ; Save value register to stack
push step ; Save step register to stack
push dac ; Save dac register to stack
push flag ; Save flag register to stack
rcall read_adc ; Read potentiometer value
lsr value ; Right shift value by 1 bit
lsr value ; Right shift value by another bit
mov step, value ; Copy value to step register
tst step ; Check if value is zero
breq timer2_exit ; Exit handler if value is zero
tst flag ; Check direction flag
breq timer2_up ; Jump to timer2_up if flag is zero
timer2_down: ; Label for decreasing voltage
sub dac, step ; Subtract step from value for MCP4921
brcc timer2_exit ; Exit handler if no carry
ldi dac, 0xFF ; Set value for MCP4921 to maximum if carry
timer2_up: ; Label for increasing voltage
add dac, step ; Add step to value for MCP4921
brcc timer2_exit ; Exit handler if no carry
ldi dac, 0x00 ; Set value for MCP4921 to minimum if carry
ldi flag, 1 ; Change direction flag to 1
rjmp timer2_exit ; Jump to exit label
timer2_exit: ; Exit label for handler
pop flag ; Restore flag register from stack
pop dac ; Restore dac register from stack
pop step ; Restore step register from stack
pop value ; Restore value register from stack
pop temp ; Restore temp register from stack
reti ; Return from interrupt
Message:
PROSPICE 8.13.00 (Build 32709) (C) Labcenter Electronics 1993-2023. Loaded netlist 'C:\\Users\\maxim\\AppData\\Local\\Temp\\LISA7525.SDF' for design 'Kursach.pdsprj' AVR Release 8.3SP0 build 33337 for ATMEGA8. \[U1\] QPainter::begin: Paint device returned engine == 0, type: 1 Invalid opcode 0xFFFF at PC=0x0008 @0.016216000s
I found this (Why am I getting this error in proteus: " Invalid opcode 0xFFFF at PC=0x002A") but i don't know how to use it
The compiler is telling you that there is no valid instruction in the interrupt vector table for TIMER2
. It tells you this because of these two lines:
.org 0x0012 ; Timer 2 comparison interrupt vector
rjmp timer2_isr ; Jump to interrupt handler
The address 0x0012
is not the correct address for TIMER2
compare match interrupt, it belongs to USART RXC
(page 46 in the datasheet lists the addresses of interrupt vectors). What you need to do in order to fix the compile error is to change it to the correct address: 0x0003
.