I´m using Atmega2560 and 24c16A EEPROM for testing an I2C code. For every state transition, controller responds by changing the status register TWSR. After start condition is transmitted, EEPROM responds and then also for Device address and write instruction (SLA+R/W), status register updates fine. But when I transmit next 8 bits, instead of ACK (Data byte sent ACK), status changes to repeated start. The code is given below. I never could find the possible solution to make it work.
.include "./m2560def.inc"
.list
.cseg
.org 0x00
jmp inicio ; PC = 0x0000 RESET
inicio:
LDI R21, HIGH(RAMEND) ;Set Up Stack
OUT SPH, R21
LDI R21, LOW(RAMEND)
OUT SPL, R21
CALL I2C_INIT ;Initialize TWI(I2C)
CALL I2C_START ;Transmit START condition
LDI R27, 0b11010000 ;SLA(0b1001100) + W(0)
CALL I2C_WRITE ;Write R27 ato the I2C bus
LDI R27, 0b11110000 ;Data to be transmitted
CALL I2C_WRITE ;Write R27 ato the I2C bus
CALL I2C_STOP ;Transmit STOP condition
HERE: RJMP HERE
;----------------------------I2C_INIT-----------------------------
I2C_INIT:
LDI R21, 0
OUT TWSR, R21 ;Set prescaler bits to 0
LDI R21, 0x47 ;R21 = 0x47
OUT TWBR, R21 ;Fclk = 50 KHz (8 MHz Xtal)
LDI R21, (1<<TWEN) ;R21 = 0x04
OUT TWCR, R21 ;HEnable TWI (I2C)
RET
;----------------------------I2C_START-----------------------------
I2C_START:
LDI R21, (1<<TWINT)|1<<(TWSTA)|(1<<TWEN)
OUT TWCR, R21 ;Transmit START condition
WAIT1:
IN R21, TWCR ;Read Control Register TWCR into R21
SBRS R21, TWINT ;Skip the next line if TWINT is 1
RJMP WAIT1 ;Jump a WAIT1 if TWINT is 1
RET
;----------------------------I2C_WRITE -----------------------------
I2C_WRITE:
OUT TWDR, R27 ;Move the byte into TWRD
LDI R21, (1<<TWINT)|(1<<TWEN)
OUT TWCR, R21 ;Configure TWCR to send TWDR
WAIT3:
IN R21, TWCR ;Read Control Register TWCR into R21
SBRS R21, TWINT ;Skip the next line if TWINT is 1
RJMP WAIT3 ;Jump a WAIT3 if TWINT is 1
RET
;----------------------------I2C_STOP------------------------------
I2C_STOP:
LDI R21, (1<<TWINT)|1<<(TWSTO)|(1<<TWEN)
OUT TWCR, R21 ;Transmit STOP condition
RET
;----------------------------I2C_READ------------------------------
I2C_READ:
LDI R21, (1<<TWINT)|(1<<TWEN)
OUT TWCR, R21
WAIT2:
IN R21, TWCR ;Read Control Register TWCR into R21
SBRS R21, TWINT ;Skip the next line if TWINT is 1
RJMP WAIT2 ;Jump a WAIT2 if TWINT is 1
IN R27, TWCR ;Read received data into R21
RET
The output is
D:\ATMEL_AVR\I2C_TWI\I2C_TWI.asm(41,0): error: Operand 1 out of range: 0xb9
D:\ATMEL_AVR\I2C_TWI\I2C_TWI.asm(43,0): error: Operand 1 out of range: 0xb8
D:\ATMEL_AVR\I2C_TWI\I2C_TWI.asm(45,0): error: Operand 1 out of range: 0xbc
D:\ATMEL_AVR\I2C_TWI\I2C_TWI.asm(51,0): error: Operand 1 out of range: 0xbc
D:\ATMEL_AVR\I2C_TWI\I2C_TWI.asm(53,0): error: Operand 2 out of range: 0xbc
D:\ATMEL_AVR\I2C_TWI\I2C_TWI.asm(60,0): error: Operand 1 out of range: 0xbb
D:\ATMEL_AVR\I2C_TWI\I2C_TWI.asm(62,0): error: Operand 1 out of range: 0xbc
D:\ATMEL_AVR\I2C_TWI\I2C_TWI.asm(64,0): error: Operand 2 out of range: 0xbc
D:\ATMEL_AVR\I2C_TWI\I2C_TWI.asm(72,0): error: Operand 1 out of range: 0xbc
D:\ATMEL_AVR\I2C_TWI\I2C_TWI.asm(78,0): error: Operand 1 out of range: 0xbc
D:\ATMEL_AVR\I2C_TWI\I2C_TWI.asm(80,0): error: Operand 2 out of range: 0xbc
D:\ATMEL_AVR\I2C_TWI\I2C_TWI.asm(83,0): error: Operand 2 out of range: 0xbc
Assembly failed, 12 errors, 0 warnings
The code lines involved in these errors are:
OUT TWSR, R21
OUT TWBR, R21
OUT TWCR, R21
OUT TWCR, R21
IN R21, TWCR
OUT TWDR, R27
OUT TWCR, R21
IN R21, TWCR
OUT TWCR, R21
OUT TWCR, R21
IN R21, TWCR
IN R27, TWCR
In AVR normally all I/O registers are mapped into the memory space, starting from address 0x0020
in and out instructions could be used ONLY with the first 64 of them (with 0x20...0x5F memory addresses)
sbi, cbi, sbis and sbic has further limitation and can be used only with first 32 I/O registres (with memory address 0x20...0x3F). Please refer to the AVR Instruction Set Manual
In the datasheet to ATmega2560, section 33 "Register Summary", page 401, you can see the TWI registers have addresses 0xB8 thru 0xBD, i.e. they cannot be accessed using in and out instructions. You have to use STS and LDS.
I.e. LDS R21, TWCR instead of IN R21, TWCR etc.
Also, always be careful when accessing the first 64 registers. Make sure the corresponding names are defining their I/O and not RAM addresses. I.e. PORTA should be equal to 0x02 (IO address for in / out instruction) and not 0x22 (RAM address or PORTA, which, when accessed using in or out, equals to EEARH register).
Related
I am trying to figure out this question. I posted my code below. It doesn't work properly. it seems like its not multiplying the 2 least significant nibbles. I don't know AVR very well.
Write AVR that generates a multiplication table for SRAM addresses 0x0100 to 0x01FF. The value at each address is the product of the two least significant nibbles of the address. For example, at address 0x0123, the multiplicand is 3 and the multiplier is 2. calculate the product (6 in this case) and store it at address 0x0123. The answer should be about 10-12 lines of code with a loop
.include "C:\VMLAB\include\m168def.inc"
ldi r27, 0x01
ldi r26, 0x00
ldi r30, 0xff
main:
mov r16, r26
andi r16,0x0f
mov r17,r27
andi r17,0xf0
swap r17
mul r17, r16
st x+, r16
dec r30
brne main
According to the manual, the MUL instruction stores its result in R0 and R1. So you need to read R0 after MUL, not R16.
You have three errors in your code:
mul stores results to R1:R0 pair registers
if use X as the index register and content mark symbolically as ABCD then CD is located in XL register, not in XH (r27). AB is constant (01)
you loops only 255 times not 256
ldi xh, 0x01
ldi xl, 0x00
ldi r30, 0x00
main:
mov r16, xl ;put D to R16
andi r16,0x0f
mov r17, xl ;put C to R17
andi r17,0xf0
swap r17
mul r17, r16 ;multiply C x D
st x+, r0 ;store result low byte to memory
dec r30 ;repeat 256 times
brne main
I have a program that's supposed to read input values from a dip switch connected to PORTA and when button on PC0 is pressed, then it outputs the bits to the LEDs on PORTB, or if PC7 is pressed, then it shows up in PORTD. When PC3 is pressed, it's supposed to multiply both bytes for a 16-bit value stored as a high and low bytes.
The issue is that once I press the button once, the result does show up in the LEDs. However, after the first button press, the logic stops working. For example, when switching input on PORTA, it still shows up in the LEDs regardless of whether the button is pushed or not.
I cannot pinpoint where the issue is exactly and I really need help debugging this.
I'm using an atmega32, programming in Atmel studio, and simulating in Proteus.
I appreciate any help.
.cseg
.org 0x0000
; set stack pointer
ldi r29, low(ramend)
out spl, r29
ldi r29, high(ramend)
out sph, r29
start:
ser r16
out ddrb, r16 ; portb output
out ddrd, r16 ; portd output
clr r16
out ddra, r16 ;porta input
out ddrc, r16 ;portc input
ser r16
out portc, r16 ;pull-up resistor on PORTC
rjmp main
main:
sbic pinc, 0 ;skip if button is not pressed on PC0
call Load_Low ;call subroutine function to load the lower bit
sbic pinc, 7
call Load_High ;call subroutine function to load the higher bit
sbic pinc, 3
call Multiply ;call subroutine function to multiply both stored values
rjmp main
Load_High:
in r20, pina ;read bits in PINA to R20
mov r30, r20 ;store copy
out portb, r30 ;output to LEDs on PORTB
cbi portc, 0 ;clear bit
ret
Load_High:
in r20, pina
mov r31, r20
out portd, r31
cbi portc, 7
ret
Multiply:
mul r31, r30
out portd, r0
out portb, r1
cbi portc, 3
ret
In the schematics there is no pull-down resistor which could make a low logical level when the button is released. Therefore voltage is undefined, in real life voltage will be floating causing unexpected logical level change on the inputs.
Instead of adding external pull-down resistors you can connect buttons to the "GND" and engage internal pull-up resistors (setting corresponding bits of PORTC to 1 while in DDRC those bits are 0)
Also in all your routines there are strange lines without explanation:
cbi portc, 0 ;clear bit
cbi portc, 7
cbi portc, 3
those are the button inputs, why you're clearing PORTC bits each time?
I am working on a program that is suppose to create a certain blink sequence on my arduino board (atmega328p). The pattern that I am trying to create is,
ON for 1/2 second
OFF for 1/2 second
ON for 1/2 second
Off for one full second
Repeat this sequence.
I approached the problem by creating two different delays one for the 1/2 sec and other for the 1 sec, and then I call them.
If I only have one delay the light will work with that pattern but once I put both delays in the loop together the light does not even follow the pattern. I apologize if this is a easy question, I don't know if I am approaching this right.
Here is my code:
#include "config.h"
.section .data
dummy: .byte 0 ; dummy global variable
.section .text
.global main
.extern delay
.org 0x0000
main:
; clear the SREG register
eor r1, r1 ; cheap zero
out _(SREG), r1 ; clear flag register
; set up the stack
ldi r28, (RAMEND & 0x00ff)
ldi r29, (RAMEND >> 8)
out _(SPH), r29
out _(SPL), r28
; initialize the CPU clock to run at full speed
ldi r24, 0x80
sts CLKPR, r24 ; allow access to clock setup
sts CLKPR, r1 ; run at full speed
; set up the LED port
sbi LED_DIR, LED_PIN ; set LED pin to output
cbi LED_PORT, LED_PIN ; start with the LED off
; enter the blink loop
1: rcall toggle
rcall delay
rcall delay2
rjmp 1b
toggle:
in r24, LED_PORT ; get current bits
ldi r25, (1 << LED_PIN) ; LED is pin 5
eor r24, r25 ; flip the bit
out LED_PORT, r24 ; write the bits back
ret
delay: ; 1/2 sec delay loop
ldi r21, 41
ldi r22, 150
ldi r23, 127
1: dec r23
brne 1b
dec r22
brne 1b
dec r21
brne 1b
ret
delay2: ; 1 sec delay loop
ldi r18, 82
ldi r19, 43
ldi r20, 0
2: dec r20
brne 2b
dec r19
brne 2b
dec r18
brne 2b
ret
I'm using Atmel Studio 6.1 and an Arduino Uno board with an Atmega328P microcontroller. Included below is my code and a picture of my hardware. I can't figure out if this is a hardware or software issue... All LEDs start off. When the button is pressed, it is supposed to change to a determined pattern. Pushing the button again results in a different pattern being displayed. I can't use C and I haven't been able to use real time debugging since I don't have JTAG or some other debugging supported interface. What ends up happening is that the button is pressed and I'm shorting the circuit and the power to the board gets reset. Oddly enough, the pattern changes once but then never again.
.def counter = R23
.def TimeLoopMax = R24
.def AllOnes = R16
.def DisplayPattern = R17
.def AllZeros = R18
MAIN:
LDI AllOnes, 0xFF ; assign 1 - make an output
LDI DisplayPattern, 0x00 ; start with all the LEDS ON; Holds the Light Pattern
LDI AllZeros, 0x00 ; assign 0 - make an input
LDI r19, 0x00 ; to hold the value read from PORTB0
LDI counter, 0x00 ; value for counter
LDI TimeLoopMax, 0x70
;According to the breakout board, PORTB5 is connected on spot 13 on the board
OUT DDRD, AllOnes ;set PORT D as an output
;make PORTB an input
OUT DDRB, AllZeros ;set PORT B as an input
;Start by turning all LEDS OFF
OUT PORTD, DisplayPattern
LOOP:
;read in the value from PORTB0 (ie the push button)
IN r19, PORTB0
;CPI r19, 0x05 ; might need to do a compare; compare with 5 since we're using 5V
CP r19, AllZeros
BRNE LOOP
JMP IncreasePatternCounter
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;; Blinking Pattern Definitions ;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;This defines a blinking sequence for Pattern 0
P0:
;P0, start at a value of 1, and shift the bit to the MSB
LDI DisplayPattern, 0x01
P0_LOOP:
OUT PORTD, DisplayPattern
LSL DisplayPattern ;Logic shift Left
;need to check butotn press every time the light switches
; since we are not using interrupts
/*IN r19, PORTB0
SBRC r19, 0 ;skip if Bit in Reg. is clear
JMP IncreasePatternCounter*/
JMP TimeWastingLoop
;This defines a blinking sequence for Pattern 1
P1:
;P1, start at a value of AA, and shift the bit to the MSB
LDI DisplayPattern, 0xAA
P1_LOOP:
OUT PORTD, DisplayPattern
LSL DisplayPattern
/*IN r19, PORTB0
SBRC r19, 0
JMP IncreasePatternCounter*/
JMP TimeWastingLoop
;This defines a blinking sequence for Pattern 1
P2:
;P1, start at a value of AA, and shift the bit to the MSB
LDI DisplayPattern, 0xFF
P2_LOOP:
OUT PORTD, DisplayPattern
LSR DisplayPattern
/*IN r19, PORTB0
SBRC r19, 0
JMP IncreasePatternCounter*/
JMP TimeWastingLoop
;This defines a blinking sequence for Pattern 1
P3:
;P1, start at a value of AA, and shift the bit to the MSB
LDI DisplayPattern, 0x00
P3_LOOP:
OUT PORTD, DisplayPattern
INC DisplayPattern
/*IN r19, PORTB0
SBRC r19, 0
JMP IncreasePatternCounter*/
JMP TimeWastingLoop
IncreasePatternCounter:
INC counter
SequenceSelect:
CPI counter, 0x01
BREQ P0;
CPI counter, 0x02
BREQ P1;
CPI counter, 0x03
BREQ P2
CPI counter, 0x04
BREQ P3
JMP P0
; Time wasting loop registers: r20,21,22
; R16 is all 1's
TimeWastingLoop:
LDI R20, 0x05
LDI R21, 0x05
LDI R22, 0x04
OutMostLoop:
CP TimeLoopMax, R20;(TLM - R20)
BREQ EndLoop
FirstInnerLoop:
/*IN r19, PORTB0
SBRC r19, 0
JMP IncreasePatternCounter*/
CP TimeLoopMax, R21; (TLM - R21)
BREQ EndOutMostLoop
SecondInnerLoop:
IN r19, PORTB0
SBRC r19, 0
JMP IncreasePatternCounter
CP TimeLoopMax, R22; (TLM - R22)
BREQ EndSecondInnerLoop
INC R22
JMP SecondInnerLoop
EndSecondInnerLoop:
CLR R22 ;reset the register for the next pass
INC R21 ; increment 1st inner loop counter
JMP FirstInnerLoop
EndOutMostLoop:
CLR R21
INC R20
JMP OutMostLoop
EndLoop:
;this is where we do the compare statements with counter
;Need to perform a compare here to see which loop we bounce back in
CPI counter, 0x01
BREQ P0_TWL ; Pattern0 Time Wasting Loop
CPI counter, 0x02
BREQ P1_TWL
CPI counter, 0x03
BREQ P2_TWL
;OUT PORTD, counter
P0_TWL:
CPI DisplayPattern, 0b00000000
BREQ BtnPressCheckP0
BRNE P0_LOOP
P1_TWL:
CPI DisplayPattern, 0b00000000
BREQ P1
BRNE P1_LOOP
P2_TWL:
CPI DisplayPattern, 0b00000000
BREQ P2
BRNE P2_LOOP
BtnPressCheckP0:
IN r19, PORTB0
SBRC r19, 0
JMP IncreasePatternCounter
JMP LOOP
Can't comment on the code portion, but the circuit around the button looks like the cause of your short circuit. This tutorial shows a good button circuit.
i'm pretty new to assembly language and microblaze cpus and i have a problem getting interrupt handling working.
i'm forced to write my own startup and init routines. (stuff that is usually contained in crt0) and i guess i'm doing something wrong with branching to the interrupt handler at the specified address. in the documentation it is said, to branch from code address 0x10 to the interrupt handler, however in my test program it doesn't work.
here you can find the microblaze doc. i'm referring to p. 102 (Interrupt and Exception Handling)
here you can see the code for 'my_crt0_intc.asm'
bri _start
nop
bri _exception_handler
nop
bri _interrupt_handler
nop
bri _hw_exception_handler
nop
/* Starting Point */
.global _start
_start:
brlid r15, _init_routine
nop
/* Exception Handler *
.global _exception_handler
_exception_handler:
/**/
/* Interrupt Handler */
.global _interrupt_handler
_interrupt_handler:
/* set led 0010 */
ori r20, r20, 0x00000002
swi r20, r0, 0x81400000
rtid r14, 8
nop
/* HW Exception Handler *
.global _hw_exception_handler
_hw_exception_handler:
/**/
/* Init Routine */
.global _init_routine
_init_routine:
/*** Initialize Stack Pointer ***/
addi r1, r0, 0x00004000
/*** ***/
/*** Initialize Interrupts ***/
/** GPIO **/
/* enable interrupts in GIE */
addi r19, r0, 0x80000000
swi r19, r0, 0x8146011C
/* enable interrupts in channel 1 (and 2, not) in IER */
addi r19, r0, 0x00000003
swi r19, r0, 0x81460128
/** INTC **/
/* enable HW interrupts in INTC */
addi r19, r0, 0x00000003
swi r19, r0, 0x8180001C
/* enable interrupts in INTC IER */
addi r19, r0, 0xffffffff
swi r19, r0, 0x81800008
/** CPU, enable interrupts in MSR **/
msrset r19, 0x00000002
/*** ***/
/* Initialize Constants */
/* r11 = word size, for increment and decrement stack pointer */
/* addi r11, r0, 4 */
/*** call main function ***/
brlid r15, main
nop
/*** ***/
/*** halting loop ***/
_halting:
bri _halting
/*** ***/
/* Return */
rtsd r15, 8
nop
my test program 'test_interrupt_cpu_intc_gpio.asm' is intended to signal interrupts via leds at address 0x81400000
/* Main */
.global main
main:
addi r20, r0, 0x0
_loop:
/* set/unset alive led 0001 */
andi r21, r20, 0x00000001
bnei r21, _unset_alive
_set_alive:
ori r20, r20, 0x1
swi r20, r0, 0x81400000
bri _no_alive
_unset_alive:
andi r20, r20, 0xfffffffe
swi r20, r0, 0x81400000
_no_alive:
/* if gpio isr is set, set led 0100 */
lwi r21, r0, 0x81460120
beqi r21, _unset_gpio
_set_gpio:
ori r20, r20, 0x4
swi r20, r0, 0x81400000
bri _noset_gpio
_unset_gpio:
andi r20, r20, 0xfffffffb
swi r20, r0, 0x81400000
_noset_gpio:
/* if intc isr is set, set led 1000 */
lwi r21, r0, 0x81800000
beqi r21, _unset_intc
_set_intc:
ori r20, r20, 0x8
swi r20, r0, 0x81400000
bri _noset_intc
_unset_intc:
andi r20, r20, 0xfffffff7
swi r20, r0, 0x81400000
_noset_intc:
/* begin time loop */
addi r21, r0, 0x004af080
_loop_time:
addi r21, r21, -1
bnei r21, _loop_time
/* end time loop*/
bri _loop
/* return 0*/
addi r3, r0, 0
rtsd r15, 8
nop
the error symptoms are: if i do not enable interrupts in the MSR, led 0001 blinks and leds 1000 and 0100 are active if trigger the interrupt by pushing the appropriate button. however, in this situation of course the cpu doesn't handle interrupts and doesn't branch to the interrupt handler routine.
BUT: if i enable interrupts in the MSR to enable interrupt handling, led 0001 blinks in the beginning. after triggering the interrupt led 0001 is permanently set (or resp. unset if led 0001 is already unset when the interrupt occurs). led 1000 and 0100 stay inactive, which seems like the cpu stops working.
i compiled the code as follows with the mb-gcc tools:
mb-as test_interrupt_cpu_intc_gpio.o -o test_interrupt_cpu_intc_gpio.o
mb-as my_crt0_intc.asm -o my_crt0_intc.o
mb-ld my_crt0_intc.o test_interrupt_cpu_intc_gpio.o -o ../executable.elf -N
any help would be great. this problem is really annoying and i'm working on this for days. i'm sure i miss something essentially in the code. if you need any additional information, please let me know.
UPDATE:
as far as i can trust mb-gdb my program starts at address 0x50. if this is true, this explains why my branch operations are never executed.
i tried several things to ensure placing the essential instructions at the correct positions
(1)
.org 0x0
brai _start
.org 0x8
brai _exception_handler
.org 0x10
brai _interrupt_handler
.org 0x20
brai _hw_exception_handler
(2) i tried linking with mb-gcc and option x1-mode-xmdstub (as it is said in the documentation, that the standard crt0.o is not linked using this option) -> however, in this case i get the error, mutliple defintions of _start
(3) i tried
mb-ld test_interrupt_cpu_intc_gpio.o my_crt0_intc.o -o ../executable.elf -N
this way, the behavior changes, but is still uncorrect since now test_interrupt_cpu_intc_gpio.asm starts at address 0x50.
THANKS,
sema
just for record:
the problem was that the program was linked with start address 0x50. however, as specified in the microblaze documentation, the branch (vector) to the interrupt handler must be located at address 0x10.
'mb-ld --verbose ' gave a hint to this problem
ENTRY(_start) _TEXT_START_ADDR = DEFINED(_TEXT_START_ADDR) ? _TEXT_START_ADDR : 0x50;
'mb-objdump -d executable.elf' shows
../executable.elf: file format elf32-microblaze
Disassembly of section .text:
00000050 <_start-0x28>:
50: b0000000 imm 0
54: b8080078 brai 120 // 78 <_start>
58: b0000000 imm 0
5c: b80800cc brai 204 // cc <_exception_handler>
60: b0000000 imm 0
64: b80800d4 brai 212 // d4 <_interrupt_handler>
68: b0000000 imm 0
the problem is solved by option -Ttest 0x0:
mb-ld $CRT_OBJ $OBJ -o ../executable.elf -Ttext 0x0
i also missed the clearing the interrupt status registers in the interrupt handler routine which should be as follows (note rtid r14, 0 instead of rtid r14,8):
.global _interrupt_handler
_interrupt_handler:
/* set led 0010 */
ori r20, r20, 0x00000002
swi r23, r0, 0x81400000
/**/
/* clear channel 1 in gpio isr */
addi r22, r0, 0x00000001
swi r22, r0, 0x81460120
/* acknowledge interrupts in intc from channel 1 of gpio 3b (bit 2) */
addi r22, r0, 0x00000002
swi r22, r0, 0x8180000C
/* return from interrupt */
rtid r14, 0
nop
SUMMARY:
OBJDUMP is very useful tool! (it was new to me)