Flytande medelvärde i avr
- Swech
- EF Sponsor
- Inlägg: 4750
- Blev medlem: 6 november 2006, 21:43:35
- Ort: Munkedal, Sverige (Sweden)
- Kontakt:
Oönskade interrupt
Det stämmer bra det Sodjan skriver att interrupts bör vara maskbara även på AVR.
Det jag menar är att vara på den säkra sidan. Om systemet hickar till, om du får en brownout eller något annat fenomen så kan det hända, jag säger inte att det händer. Det är lite som att jag har säkerhetsbälte på mig när jag kör trots att jag inte planerar att krocka..
Anledning nr 2 är att kod går mycket fort att skriva... men att underhålla, modifiera kod 6 månader - 1 år efter man skrev den tar MYCKET längre tid
För att slippa oroa sig.. t.ex. "stängde jag av UART interrupten?"
så är det lättast att vara konsekvent. Definiera upp alla interrupt,
tar 20-30 bytes som man lätt kan avvara och du har ett problem mindre att
tänka på.
Anledning nr 3. Klipp och klistra stadiet. När man skapat sig en hög med
bra rutiner man vill återanvända... ju mindre av "måste ligga på adress 0100h" för att funka destå bättre går det.
Obs. endast goda råd... vill inte "knäppa någon på näsan"...
är bara glad att kunna dela med mig av AVR tips
Jonas / Swech
Det jag menar är att vara på den säkra sidan. Om systemet hickar till, om du får en brownout eller något annat fenomen så kan det hända, jag säger inte att det händer. Det är lite som att jag har säkerhetsbälte på mig när jag kör trots att jag inte planerar att krocka..
Anledning nr 2 är att kod går mycket fort att skriva... men att underhålla, modifiera kod 6 månader - 1 år efter man skrev den tar MYCKET längre tid
För att slippa oroa sig.. t.ex. "stängde jag av UART interrupten?"
så är det lättast att vara konsekvent. Definiera upp alla interrupt,
tar 20-30 bytes som man lätt kan avvara och du har ett problem mindre att
tänka på.
Anledning nr 3. Klipp och klistra stadiet. När man skapat sig en hög med
bra rutiner man vill återanvända... ju mindre av "måste ligga på adress 0100h" för att funka destå bättre går det.
Obs. endast goda råd... vill inte "knäppa någon på näsan"...
är bara glad att kunna dela med mig av AVR tips

Jonas / Swech
Nja, ska man tänka så är nog en hopp till reset vektorn bättre efter som något troligt viss är någe galet. Dess utom bör allt oanvänd mine oxå hoppa till rest, om man ska vara riktigt "petig"... ^^
Men om du har några fler goda råd så får du gärna komma med dem, då det är roligt veta hur andra resonerar ^^
Men om du har några fler goda råd så får du gärna komma med dem, då det är roligt veta hur andra resonerar ^^
Du måste först spar vanligt register först, vilket du sedan använder för att läsa in SREG, som du sedan push:ar på stacken.
typ
Sedan är det bara att spara unda de register som du använder i din rutin... ^^
typ
Kod: Markera allt
push r16
in r16,SREG
push r16
massa kod
pop r16
out SREG, r16
pop r16
Sådär nu har jag rotat runt lite i koden igen.
Vad det gället att spara undan register på stacken har jag en fundering. Det finns väl inget som hindrar att man skapar två subrutiner, en för att spara ut på stacken och en för att hämta från stacken? Dessa anropar man sedan först i rutinen, respektive precis innan RETI. Skulle underlätta en del om man gör så. Har interuptrutiner som kan sluta på flera ställen och skulle man vilja ändra vilka register man sparar undan så kan man lätt missa på något ställe. Får prova helt enkelt.
Nu har det uppstått en annan fråga. Det gäller programmets presentation på RS232. När jag skrev ihop de bitar som skickar ut text via seriedata så läste jag ett exempel och använde det mer eller mindre rakt av. Därför uppstår nu en lite fråga kring följande
ldi ZL, LOW(text_seconds*2) ;set Low memory pointer
ldi ZH, HIGH(text_seconds*2) ;set High memory pointer
Varför skall det vara *2 med?
Passar återigen på att rikta ett stort tack till alla som tar sig tid att komma med förslag, ideér, synpunkter, osv. Det är helt ovärderligt. TACK!
Mvh
Nisse
Koden i sin helhet:
Vad det gället att spara undan register på stacken har jag en fundering. Det finns väl inget som hindrar att man skapar två subrutiner, en för att spara ut på stacken och en för att hämta från stacken? Dessa anropar man sedan först i rutinen, respektive precis innan RETI. Skulle underlätta en del om man gör så. Har interuptrutiner som kan sluta på flera ställen och skulle man vilja ändra vilka register man sparar undan så kan man lätt missa på något ställe. Får prova helt enkelt.
Nu har det uppstått en annan fråga. Det gäller programmets presentation på RS232. När jag skrev ihop de bitar som skickar ut text via seriedata så läste jag ett exempel och använde det mer eller mindre rakt av. Därför uppstår nu en lite fråga kring följande
ldi ZL, LOW(text_seconds*2) ;set Low memory pointer
ldi ZH, HIGH(text_seconds*2) ;set High memory pointer
Varför skall det vara *2 med?
Passar återigen på att rikta ett stort tack till alla som tar sig tid att komma med förslag, ideér, synpunkter, osv. Det är helt ovärderligt. TACK!
Mvh
Nisse
Koden i sin helhet:
Kod: Markera allt
.include "m88def.inc"
; 8 MHz external chrystal
.org 0x000
rjmp Reset ; Reser Vector
.org INT0addr ; Interuptvector external INT0
rjmp startcalib
.org OC1Aaddr ; Interuptvektor timer 1 match A
rjmp timer1
;--------------------------
; initialise Stack Pointer
;--------------------------
reset:
ldi r16, low(RAMEND)
out SPL, r16
ldi r16, high(RAMEND)
out SPH, r16
;--------------------------
; Set definitions
;--------------------------
.def data = r0 ; Used when Data is read from Flash
.def calibtime = r14 ; Counts down calibration time > 0 means calibration running
.def switch = r15 ; CPS switchpoint
.def temp = r16 ; Temp
.def temp2 = r17 ; Temp
.def cps = r18 ; Counts per second
.def avg = r19 ; Avrage counts per second over 8 seconds
.def hex = r20 ; hex value to convert to ascii (could be substiuted for temp)
.def dig1 = r21 ; Digit 1 of ascii value
.def dig2 = r22 ; Digit 2 of ascii value
.def dig3 = r23 ; Digit 3 of ascii value
.def sum = r24 ; The sum of all numbers in buffer. Used to calculate avrage
.equ buff_length = 8 ; Length of buffer
;--------------------------
; Set buffer pointers and zero buffer
;--------------------------
ldi XH, high(avg_buffer) ; Set buffer pointers
ldi XL, low(avg_buffer)
ldi temp, 0 ; Set temp to Zero
zero_buffer:
st X+, temp ; Load 0 into buffer and increase pointer
cpi XL, buff_length ; Check if buffer end
brne zero_buffer ; Repeat if not end
ldi XH, high(avg_buffer) ; Set buffer pointers
ldi XL, low(avg_buffer)
ldi sum, 0 ; Set sum to zero
;--------------------------
; USART init
;--------------------------
;set baudrate
ldi temp, (0<<U2X0)
sts UCSR0A, temp
ldi temp, 51 ; 51 - 9600 Baud @ 8 MHz
sts UBRR0L, temp
;enable transmitter
ldi temp, (1<<TXEN0)
sts UCSR0B, temp
;set frame format 8n1
ldi temp, (1<<UCSZ00)|(1<<UCSZ01)
sts UCSR0C, temp
;--------------------------
; Set timer 0 to count CPS via pin T0
;--------------------------
ldi temp, (1<<CS02)|(1<<CS01)|(1<<CS00) ;Pin T0 on rising edge
out TCCR0B, temp
ldi temp, 0
out TCNT0, temp
;--------------------------
; Set interupts
;--------------------------
; Extarnal interupt 0 used to start calibration
ldi temp, (1<<INT0) ; Extern interupt INT0
out EIMSK, temp
ldi temp, (1<<ISC01)|(1<<ISC00) ; Rising edge
sts EICRA, temp
; Timer 1 compare interupt used for seconds
ldi temp, 30 ; Set timer compare
ldi temp2, 255 ; 30x255 gives about 1 second @ 8 MHz
sts OCR1AH, temp
sts OCR1AL, temp2
ldi temp, (1<<OCIE1A) ; Timer match A interupt
sts TIMSK1, temp
ldi temp, (1<<CS12)|(1<<CS10)|(1<<WGM12) ; Timer1 prescaler 1024 & CTC
sts TCCR1B, temp
sei ; Global interupt on
;--------------------------
; Main program
;--------------------------
main:
rjmp main
;--------------------------
; Calilbration routine
;--------------------------
; During 32 seconds counts are read each second from counter 0
; and added to register YL, YH that serves as a 16 bit register together.
; After 32 seconds the sum in YL, YH is divided in 32 to get an avrage
; and then divided in 2 to get the switchpoint
calibrate:
in cps, TCNT0 ;Get pulse count from counter 0
ldi temp, 0
out TCNT0, temp ;Clear counter 0
add YL, cps
adc YH, temp
ldi ZL, LOW(text_cps*2) ;set Low memory pointer
ldi ZH, HIGH(text_cps*2) ;set High memory pointer
rcall transmitt
mov hex, cps
rcall bcd
ldi ZL, LOW(text_seconds*2) ;set Low memory pointer
ldi ZH, HIGH(text_seconds*2) ;set High memory pointer
rcall transmitt
mov hex, calibtime
rcall bcd
ldi ZL, LOW(text_crlf*2) ;set Low memory pointer
ldi ZH, HIGH(text_crlf*2) ;set High memory pointer
rcall transmitt
dec calibtime
ldi temp, 0
cp calibtime, temp
breq calibrate_1
pop temp ; Get vital registers back from stack
out SREG, temp
pop temp2
pop temp
reti
calibrate_1: ;Calculate average and divide by 2 to get switchpoint
clc
ror YH
ror YL
clc
ror YH
ror YL
clc
ror YH
ror YL
clc
ror YH
ror YL
clc
ror YH
ror YL ; Avrage after divide by 2,5 times (2^5=32)
clc
ror YH ; Divide by 2 to get switchpoint
ror YL
mov switch, YL
pop temp ; Get vital registers back from stack
out SREG, temp
pop temp2
pop temp
reti
;--------------------------
; Timer interupt 1
;--------------------------
; In this interupt most of the action is.
; The counter is read and zeroed, the rotating buffer is keept updated and avrage CPS is calculated
; Presentation och RS232.
; And if Calibflag is < 0 the calibration is done
timer1:
push temp ; Push vital registers to stack
push temp2
in temp, SREG
push temp
ldi temp, 0
cp calibtime, temp
brne calibrate
in cps, TCNT0 ;Get pulse count from counter 0
ldi temp, 0
out TCNT0, temp ;Clear counter 0
; Avrage using rotating buffer
ld temp, X ;fetch old value from buffer
sub sum, temp ;subtract from sum
st X+, cps ;store new value in buffer and increase pointer
add sum, cps ;add new value to sum
andi XL, buff_length - 1 ;check if pointer reached buffer end
mov temp, sum
; Divide by 8
lsr temp
lsr temp
lsr temp
mov avg, temp
; Present via RS232
ldi ZL, LOW(text_cps*2) ;set Low memory pointer
ldi ZH, HIGH(text_cps*2) ;set High memory pointer
rcall transmitt
mov hex, cps
rcall bcd
ldi ZL, LOW(text_cpsavg*2) ;set Low memory pointer
ldi ZH, HIGH(text_cpsavg*2) ;set High memory pointer
rcall transmitt
mov hex, avg
rcall bcd
ldi ZL, LOW(text_cpsswitch*2) ;set Low memory pointer
ldi ZH, HIGH(text_cpsswitch*2) ;set High memory pointer
rcall transmitt
mov hex, switch
rcall bcd
ldi ZL, LOW(text_crlf*2) ;set Low memory pointer
ldi ZH, HIGH(text_crlf*2) ;set High memory pointer
rcall transmitt
pop temp ; Get vital registers back from stack
out SREG, temp
pop temp2
pop temp
reti
;--------------------------
; Transmitt via RS232
;--------------------------
transmitt:
rcall ready_tx
lpm temp2, Z+
cpi temp2, 0
breq transmitt1
sts UDR0, temp2
rjmp transmitt
transmitt1:
ret
;--------------------------
; waits for tx ready
;--------------------------
ready_tx:
lds temp, UCSR0A
sbrs temp, UDRE0
rjmp ready_tx
ret
;--------------------------
; Hex to ascii and out rs232
;--------------------------
;input: hex = 8 bit value 0 ... 255
;output: dig1, dig2, dig3 = digits
bcd:
ldi dig1, -1 + '0'
_bcd1:
inc dig1
subi hex, 100
brcc _bcd1
ldi dig2, 10 + '0'
_bcd2:
dec dig2
subi hex, -10
brcs _bcd2
sbci hex, -'0'
mov dig3, hex
rcall ready_tx
cpi dig1, '0'
brne _bcd3
ldi dig1, ' '
_bcd3:
sts UDR0, dig1
rcall ready_tx
cpi dig2, '0'
brne _bcd4
cpi dig1, ' '
brne _bcd4
ldi dig2, ' '
_bcd4:
sts UDR0, dig2
rcall ready_tx
sts UDR0, dig3
ret
;--------------------------
; Calibration start
;--------------------------
; Set the flag calibtime to start the calibration routine.
startcalib:
push temp ; Store the register on the stack
ldi temp, 32 ; Count pulses during 32 seconds
mov calibtime, temp
pop temp ; Get the register from stack
reti
;--------------------------
; RAM address
;--------------------------
.DSEG
avg_buffer: .BYTE buff_length
;--------------------------
; Dialog text
;--------------------------
.CSEG
text_cps:
.db "cps: ",0
text_cpsavg:
.db " cps avg: ",0
text_cpsswitch:
.db " cps switch: ",0
text_seconds:
.db " seconds: ",0
text_crlf:
.db 10,13,0
Att göra en subrutin för att förändra stacken brukar inte vara någon bra idé. På stacken ligger returadressen för återhoppet från din subrutin och färändrar du stackpekaren så krachar programmet när den skall hoppa tillbaka från subrutinen. Detta _går_ att komma runt om man verkligen vill göra på det sättet men det är varken snyggt eller överskådligt.
Däremot är det väl inget som hindrar att du stackar undan registerna innan du testar dom olika avbrottsflaggorna och det grenar ut sig i olika avbrottsrutiner. Samma sak vid återhopet. Du kan ha en gemensam återhoppspunkt där all återställning av registerna sker för att sedan returnera.
Det blir nästan samma sak fast utan subrutinerna.
Däremot är det väl inget som hindrar att du stackar undan registerna innan du testar dom olika avbrottsflaggorna och det grenar ut sig i olika avbrottsrutiner. Samma sak vid återhopet. Du kan ha en gemensam återhoppspunkt där all återställning av registerna sker för att sedan returnera.
Det blir nästan samma sak fast utan subrutinerna.
Ahaa, visst i tusan, återhoppspekaren för subrutinen läggs ju också på stacken.... Hade nästan på känn att det var något jag missat.
Men att ha en gemensam "återhoppspunkt" är ju en enkel och bra lösning. Istället för att ha Reti på flera ställen i rutinen så har man ett hopp till slutet av rutinen där man hämtar tillbaka register från stacken och sedan kör Reti.
Det här är bra, jag lär mig massor... Fortsätt komma med ideér och synpunkter.
Mvh
Nisse
Men att ha en gemensam "återhoppspunkt" är ju en enkel och bra lösning. Istället för att ha Reti på flera ställen i rutinen så har man ett hopp till slutet av rutinen där man hämtar tillbaka register från stacken och sedan kör Reti.
Det här är bra, jag lär mig massor... Fortsätt komma med ideér och synpunkter.
Mvh
Nisse
> Men att ha en gemensam "återhoppspunkt" är ju en enkel och bra lösning.
Gäller även subrutiner, inte bara interruptrutiner.
Det blir mycket enklare att förstå och modifiera koden senare om
man håller den "ren" där varje rutin (sub- eller interrupt-) har en enda
returpunkt.
Däremot är det ibland praktiskt att ha flera olika "enter" punkter på
samma subrutin...
Gäller även subrutiner, inte bara interruptrutiner.
Det blir mycket enklare att förstå och modifiera koden senare om
man håller den "ren" där varje rutin (sub- eller interrupt-) har en enda
returpunkt.
Däremot är det ibland praktiskt att ha flera olika "enter" punkter på
samma subrutin...
Sodjan -> Jo jag insåg fördelen med att använda det även i subrutiner. Man liksom "knyter ihop" hela rutinen till ett mer lättförståligt paket.
Ingen som har någon bra förklaring till min andra fråga:
Nils
Ingen som har någon bra förklaring till min andra fråga:
MvhNu har det uppstått en annan fråga. Det gäller programmets presentation på RS232. När jag skrev ihop de bitar som skickar ut text via seriedata så läste jag ett exempel och använde det mer eller mindre rakt av. Därför uppstår nu en lite fråga kring följande
ldi ZL, LOW(text_seconds*2) ;set Low memory pointer
ldi ZH, HIGH(text_seconds*2) ;set High memory pointer
Varför skall det vara *2 med?
Nils
Svaret är enklare än du tror, instruktionerna på avr är 16/32bitar, vilket gör att man inte ha behöver ha addresserna till varje byte, så vid hopp skulle sista biten vara onödig och efter som man normalt endast använder addreserna till att hoppa med så är sista biten onödig, undantaget är när man använder program minet som rom då man läser varje byte för sej, vilket kräver den sista address biten.
Jag hoppas att de rättar ut några fråge tecken... ^^
Angånde pop och push kan man göra makron som man sedan anväder
typ push_all, pop_all
Sedan ser jag att du fortfarande använder
clc
ror YH
ror YL
om du användere
lsr YH
ror YL
så sparar du en instruktion ^^
Jag hoppas att de rättar ut några fråge tecken... ^^
Angånde pop och push kan man göra makron som man sedan anväder
typ push_all, pop_all
Sedan ser jag att du fortfarande använder
clc
ror YH
ror YL
om du användere
lsr YH
ror YL
så sparar du en instruktion ^^
exile -> Tror det var en av de längsta meningar jag läst på länge
Tyvärr så rätade den inte riktigt ut frågetecknet. Jag greppar nästan vad du menar med adresseringen, men vad innebär just skrivsättet med *2?
Och så var det CLC och ROR
Det var helt enkelt så att jag missförstått funktionen på LSR lite. Jag trodde inte den använde Carry-flaggan alls. Men efter att ha läst lite nogrannare i databladet så ser jag ju att det stämmer inte.
Mvh
Nils

Och så var det CLC och ROR
Det var helt enkelt så att jag missförstått funktionen på LSR lite. Jag trodde inte den använde Carry-flaggan alls. Men efter att ha läst lite nogrannare i databladet så ser jag ju att det stämmer inte.
Mvh
Nils
- Swech
- EF Sponsor
- Inlägg: 4750
- Blev medlem: 6 november 2006, 21:43:35
- Ort: Munkedal, Sverige (Sweden)
- Kontakt:
*2
Varfär *2 .. JO....
AVR processorerna läser in 16 bitar för varje instruktion.
Varje instruktion är alltid minst 16 bitar (2 bytes) lång. Vissa är längre.
Är de längre är de multiplar av 2 bytes.. 2..4..6..8 osv..
det innebär att programräknaren.. dvs det "register" som håller reda på vilken instruktion som skall utföras alltid pekar på en grupp av 2 bytes..
så programräknaren räknar 0.1.2.3.4.5.6.7 - i programminnet pekar
programräknaren på 0.2.4.6.8.10.12.14 .....
Så ett program..
i exemplet ovan ligger label Loop1 på instruktionsposition 3..
i programminnet är den på adress 6
kompilatorn översätter alltid loop1 till 3
vill man ladda t.ex ZL med adressen till Loop1 så måste man alltså
tänka till.
skriver du ldi zl,loop1 så får du 3 i ZL men man vill ju ha byte adressen som är 6
därför..... skriv ldi zl, 2*loop1 och voilla du får 6.. som är byte adressen till loop1
Vad gäller interrupt har Exile redan svarat alldeles förträffligt..
Koda på nisse... ^^
M.v.h
Jonas /Swech trading
AVR processorerna läser in 16 bitar för varje instruktion.
Varje instruktion är alltid minst 16 bitar (2 bytes) lång. Vissa är längre.
Är de längre är de multiplar av 2 bytes.. 2..4..6..8 osv..
det innebär att programräknaren.. dvs det "register" som håller reda på vilken instruktion som skall utföras alltid pekar på en grupp av 2 bytes..
så programräknaren räknar 0.1.2.3.4.5.6.7 - i programminnet pekar
programräknaren på 0.2.4.6.8.10.12.14 .....
Så ett program..
Kod: Markera allt
ldi r16,0 instr 0 minne 0,1
lpm instr 1 minne 2,3
adiw #4,zl instr 2 minne 4,5
Loop1: instr 3 minne 6,7 osv.
i programminnet är den på adress 6
kompilatorn översätter alltid loop1 till 3
vill man ladda t.ex ZL med adressen till Loop1 så måste man alltså
tänka till.
skriver du ldi zl,loop1 så får du 3 i ZL men man vill ju ha byte adressen som är 6
därför..... skriv ldi zl, 2*loop1 och voilla du får 6.. som är byte adressen till loop1
Vad gäller interrupt har Exile redan svarat alldeles förträffligt..
Koda på nisse... ^^
M.v.h
Jonas /Swech trading
- Swech
- EF Sponsor
- Inlägg: 4750
- Blev medlem: 6 november 2006, 21:43:35
- Ort: Munkedal, Sverige (Sweden)
- Kontakt:
ytterligare ett tips
Ett generellt tips bara..
Snart börjar ditt program bli stort nisse... ju större program destå fler
spännande problem...
När jag började med AVR skrev jag på samma sätt som du. Jag definierade
upp fler och fler register .
Använd dig istället av RAM minnet... t.ex i ditt exempel skulle dig1,dig2 och
dig3 läggas där istället för att låsa upp 3 register.
Och snart inser man hur värefulla R16 till R31 är och hur värdelösa R1.. R15
är...
Kör man med Ram får man istället register över.. som man då kan låsa upp
till bättre grejer. Jag nämde detta tidigare.. att ha ett register som alltid är 0
och ett som är -1 är mycket hjälpsamt...
Jonas / Swech
Snart börjar ditt program bli stort nisse... ju större program destå fler
spännande problem...
När jag började med AVR skrev jag på samma sätt som du. Jag definierade
upp fler och fler register .
Det problem jag stötte på var att jag aldrig kunde komma ihåg om t.ex. hex var register R20 eller R19.. (vad jag menar var attt jag inte visste villka som var upptagna utan att kolla i definitionen) och att registrena mycket snart tog slut..def data = r0 ; Used when Data is read from Flash
.def calibtime = r14 ; Counts down calibration time > 0 means calibration running
.def switch = r15 ; CPS switchpoint
.def temp = r16 ; Temp
.def temp2 = r17 ; Temp
.def cps = r18 ; Counts per second
.def avg = r19 ; Avrage counts per second over 8 seconds
.def hex = r20 ; hex value to convert to ascii (could be substiuted for temp)
.def dig1 = r21 ; Digit 1 of ascii value
.def dig2 = r22 ; Digit 2 of ascii value
.def dig3 = r23 ; Digit 3 of ascii value
.def sum = r24 ; The sum of all numbers in buffer. Used to calculate
Använd dig istället av RAM minnet... t.ex i ditt exempel skulle dig1,dig2 och
dig3 läggas där istället för att låsa upp 3 register.
Och snart inser man hur värefulla R16 till R31 är och hur värdelösa R1.. R15
är...
Kör man med Ram får man istället register över.. som man då kan låsa upp
till bättre grejer. Jag nämde detta tidigare.. att ha ett register som alltid är 0
och ett som är -1 är mycket hjälpsamt...
Jonas / Swech