Summation in AVR Assembly - algorithm

I want to implement a routine that calculates the sum of all natural numbers from 1 to n. n is a variable stored in RAM. The result has to be stored in a two-byte variable in RAM, too. I'm very new in assembly programming so I'm having a hard time trying to figure out the algorithm to achieve this. So far, I've done this:
.DSEG
.ORG 0x100
n: .BYTE l_n
result: .BYTE l_result
.CSEG
.ORG 0x100
SUM:
LDI XL, n ;the direction of n is stored in XL
LD R16, X ;now r16=n
LDI XL, LOW(result)
LDI XH, HIGH(result) ;X points to result
CLC ;in case C is full with trash
LDI R17, 0x0 ;R17 = 0
LDI R18, 0x1 ;R18 = 1
CALL LOOP
LDI R16,0
LDI R17,0
ADC R16, R17 ;if C is on when the loop finishes, then it has to be summed as well
ST X, R16
RET ;returns to the program that called the routine
I did the initialization of R17 and R18 because I thought that the subroutine LOOP should do something like increasing this numbers one by one until doing it n times. The thing that is complicating me the most is the fact that the result has two bytes, while each number being summed consists of just one byte. I don't know how to deal with this. Any help will be appreciated.

what you need is
ADD R18,R24 //sumL += nL
ADC R19,R25 //sumH += nH + Carry
and for 2 bytes variable the max sum will be 65535 so for
1+2+3+...+n=n*(n+1)/2 <= 65535 then N <= 361 = 0x0169
1+2+3+...+361=361*362/2=65341
and code will looks like this:
//CPU: ATmega128A
.include "m128Adef.inc"
.DSEG
//.ORG 0x100
n: .BYTE 2 // define 2 bytes var
result: .BYTE 2 // define 2 bytes var
.CSEG
.ORG 0
RJMP boot
n0: .DW 0x0169 //init value for n=361 (max value for 2 byte result)
//in: N=R24:R25
//out: Sum=R18:R19
//calc sum 1 to n (n >=1 and n <=361)
//1+2+3+...+n=n*(n+1)/2 <= 65535 => n<=361= 0x0169
Sum1toN:
LDI R18,0x00 //sumL=0
LDI R19,0x00 //sumH=0
Lsum:
ADD R18,R24 //sumL + = nL
ADC R19,R25 //sumH += nH + C
SBIW R24,0x01 //n--
BRNE Lsum // n >0 ?
RET
boot:
CLR R1
OUT SREG,R1 //Clear all
//init stack pointer
LDI R28,LOW(RAMEND) //LDI R28,0xFF
LDI R29,HIGH(RAMEND) //LDI R28,0x10
OUT SPH,R29
OUT SPL,R28
//init
LDI ZL,LOW(n0<<1)
LDI ZH,HIGH(n0<<1)
LDI XL,LOW(n)
LDI XH,HIGH(n)
LDI R24,2
LDI R25,0
init:
LPM R0,Z+
ST X+,R0
SBIW R24,1
BRNE init
//calc:
LDS R24,n // LDS R24,0x0100
LDS R25,n+1 // LDS R25,0x0101
RCALL Sum1toN
STS result,R18
STS result+1,R19
main:
RJMP main

Related

Multiplication table in VMLab / AVR

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

Assembly safes and keys- why it won't work?

So we have like this safes challenge in assembly, you need to create safes and keys that will break them and end the infinite loop.
Here's an example for a safe:
loopy:
mov ax, [1900]
cmp ax,1234
jne loopy
and a key:
loopy2:
mov ax, 1234
mov [1900],ax
jmp loopy2
So I have a safe and a key, and I don't understand why it doesn't work:
here's my safe:
org 100h
mySafe:
mov dx,5
mov ax, [5768h]
mov bx,7
mov word [180h],2
mul word [180h]
mov [180h],bx
push ax
dec bx
mov cx,dx
mov ax,dx
loopy1:
add bx,ax
loop loopy1
dec bx
pop ax
add ax,bx
mul word [180h]
cmp ax,350
jne mySafe
And here's my key:
org 100h
loopy:
mov word [5768h],10
jmp loopy
ret
The right answer to break the loop should be 10 and it works when I put in on the safe, somehow with the key it doesn't work and I can't figure out why..
(the "word" is needed for nasm)
The value in dx used as the counter for the loop instruction comes from the first mul instruction.
This multiplication is just doubling the key, so dx is either 0 or 1 (an easy way to see this is to think of the multiplication as a left shift by one or by remembering that the sum of two n-bit numbers has at most n+1 bits)
If dx is zero, the whole loopy1 block does nothing (as dx also sets ax) and the value in ax at the end of the safe is 7*(5 +2k) where k is the key (see the commented code below).
It is then easy to see that 350 = 7*(5+2k) => 2k = 45 has no solution. Therefore no key for which dx is zero can unlock the safe.
A key has dx 0 iif its value is less than 32768 (again, this is easy to see when thinking of the multiplication as a left shift by one).
Corollary: 10 cannot be a solution.
safe:
mov dx,5
mov ax, [k] ;ax = k (key)
mov bx,7
mov word [aux],2
mul word [aux] ;dx = 0 ax = 2k
mov [aux],bx ;aux = 7
push ax ;ax = 2k
dec bx ;bx = 6
dec bx ;bx = 5
pop ax ;ax = 2k
add ax,bx ;ax = 5 + 2k
mul word [aux] ;ax = 7*(5 +2k)
cmp ax,350
ret
If there is a key that unlocks the safe then it must be greater or equal to 32768 so that dx is 1 after the first multiplication.
With this condition, the value in ax at the end of the safe can be written as 7*(6 + (2k & 0xffff)) => k & 0x7fff = 22.
Adding the condition stated at the very beginning of this section, the final value for k is 32768 + 22 = 32790 or 0x8016 in hex.
I've leaped quite a few logical steps in manipulating the equation and forming the result but, again, thinking of 2k as a shift may help visualize them.
Corollary: Due to the algebraic structure involved, this is the only solution.
safe:
mov dx,5
mov ax, [k] ;ax = k
mov bx,7
mov word [aux],2
mul word [aux] ;dx:ax = 2k
mov [aux],bx ;[aux] = 7
push ax ;dx = 1 ax = 2k & 0xffff
dec bx ;bx = 6
mov cx,dx ;cx = 1
mov ax,dx ;ax = 1
loopy1:
add bx,ax ;bx = 6 + 1
dec cx
jnz loopy1
dec bx ;bx = 6
pop ax ;ax = 2k & 0xffff
add ax,bx ;ax = 6 + (2k & 0xffff)
mul word [aux] ;ax = 7*(6 + (2k & 0xffff))
cmp ax,350
ret
Considering that you have a mov dx, 5 before the first multiplication, did you (or the author of the safe) forget that mul affects dx?
If you wrap the first mul in push dx / pop dx (or just move mov dx, 5 after it), you would get, at the end of the safe, a value in ax equals to 7*(30 +2k) which implies k = 10 indeed.

How to setup two different led delays for a specific sequence?

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

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).
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.
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
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

Is this a hardware or Software issue?

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.

Resources