Beste,
Voor school hebben wij de opdracht een wekker te maken. De wekker moet met dmv UART gegevens sturen naar een zeven segment klok op de computer.
Nu heb ik een basis opzetje gemaakt en vraag ik mij af of ik uberhaupt wel op de goede weg ben. Wat ik eerst wil bereiken is een standaard klok te maken.
Het gaat hierbij om een ATmega32 icm een STK500. Ik heb zelf het idee dat de code nu al neigt naar spagetti code wat ik dus absoluut niet wil.
Is deze code dus wel een goeie aanpak? Of hoe kan ik het beter en overzichtelijker maken?
(Het einddoel is een wekker die je zelf kan instellen met behulp van twee knopjes op het bordje. Hierbij moet je eerste de uren instellen dan de minuten en dan de seconden. Als deze 3 ingesteld kan je de "wektijd" instellen dit gebeurd op dezelfde manier. Als dit eenmaal gebeurd is kan je wekker nog aan of uit zetten met 1 van de knopjes.)
/*
* wekker.asm
*
* Created: 4/6/2013 20:42:06
* Author: Marc
*/
; leds met portB om output te zien
.include "m32def.inc"
.def saveSR = r17
.def temp = r18
.def highSecond = r19
.def lowSecond = r20
.def highMinute = r20
.def lowMinute = r21
.def highHour = r22
.def lowHour = r23
.org 0x0000
rjmp init ; bij opstarten & reset naar init
.org OC1Aaddr
rjmp increaseSeconds ; adres ISR (Timer1 Output Compare Match)
init:
; init stack pointer
ldi R16, high(RAMEND)
out SPH, R16
ldi R16, low(RAMEND)
out SPL, R16
rcall initTimer
rcall initOutputCompare
rcall initPorts
loop:
rjmp loop
increaseSeconds: ; ISR wordt elke seconde aangeroepen
in saveSR, SREG ; save SREG
in r16, PORTB ; schrijf de inhoud van PORTB naar r16
rcall addSecond
calcIsDone:
com r16 ; inverteer alle bits $00 <-> $FF
out PORTB, r16 ; schrijf waarde r16 naar PORTB
out SREG, saveSR; restore SREG
reti ; return from interrupt
addSecond:
inc lowSecond
cpi lowSecond,10
breq addHighSecond
ret
addHighSecond:
ldi lowSecond,0x00
inc highSecond
inc r16
rjmp calcIsDone
GetNumberValue:
/*
0 = 0124652
1 = 2 5
2 = 02346
3 = 0 2356
4 = 1235
5 = 01356
6 = 13564
7 = 0235
8 = 0123456
9 = 012356
*/
ret
/*
init subroutines
*/
initTimer:
ldi lowSecond,0x00;
ldi highSecond,0x00
ldi highMinute,0x00
ldi lowMinute,0x00
ldi highHour,0x00
ldi lowHour,0x00
ret
initOutputCompare:
; init Output Compare Register
; f kristal = 11059200 en 1 sec = (256/11059200) * 43200
; to do a 16-bit write, the high byte must be written before the low byte !
; for a 16-bit read, the low byte must be read before the high byte !
; (p 89 datasheet)
ldi temp, high(43200)
out OCR1AH, temp
ldi temp, low(43200)
out OCR1AL, temp
; zet prescaler op 256 & zet timer in CTC-mode
ldi temp, (1 << CS12) | (1 << WGM12)
out TCCR1B, temp
; enable interrupt
ldi temp, (1 << OCIE1A)
out TIMSK, temp
ret
initPorts:
; init port
ser temp ; tmp = $FF
out DDRB, temp ; Port B is output port (via LEDs)
out PORTB, temp ; LEDs uitzetten
sei ; enable alle interrupts
; wacht in lus op interrupt
ret
Wat ik persoonlijk lelijk vind is de calcIsDone methode. Waar ik eigenlijk naar op zoek ben is een branch die een subroutine called. Maar volgens mij bestaat dit niet?
Graag hoor ik van jullie hou jullie dit persoonlijk zouden aanpakken en wat de elegantste oplossing hiervoor is 
klein is fijn
Moderator
Ik denk dat niet veel mensen werken met assembly in een ATmega. AVR's hebben een behoorlijke instructieset en met het formaat programma's waar je een ATmega voor pakt ben je honderden regels assembly aan het kloppen.
Waarbij dan ook de vraag is, zit je vast aan assembly of mag je ook een hogere taal gebruiken?
Ik zie dat je een RCALL doet in de ISR routine. Zelf zou ik (vooral in assembly) de ISR zo kort mogelijk houden. Je kan bijvoorbeeld een flagbit zetten in een zelf gedeclarede flag-byte. Als je meer dan dat gaat doen heb je veel kans dat je een control register trashed waar je weer last van hebt in de rest van je code. Het setten van 1 bit in een byte doet volgens mij niets met een control register.
De bits van die flag-byte kan je dan in een main loop checken en een actie laten uitvoeren als ze geset (of gereset) zijn. Op die manier ben je meteen van het probleem af dat een interrupt een actie langduriger onderbreekt, of dat de ene interrupt de andere langdurig onderbreekt.
Ik zie daar ook nogal wat init spul waar eenmalig met een RCALL wordt aangeroepen. Mijn idee zou zijn om dat gewoon bovenaan in de code te zetten. Scheelt jumps en plaats in de stack.
pros
Prosper, yop la boum, c'est le roi du macadam (aldus Maurice Chevalier)
Ik zou de code in C schrijven. Van assembly heb ik m'n buik vol... 
Een paar opmerkingen:
.def lowSecond = r20
.def highMinute = r20
Hier moet ik geen uitleg bij geven, veronderstel ik.
; f kristal = 11059200 en 1 sec = (256/11059200) * 43200
ldi temp, high(43200)
out OCR1AH, temp
In CTC-mode gaat Timer1 nu tellen van 0 tot en met 43200. Vervolgens springt hij terug op 0. Dat zijn 43201 stappen!
Wil je 43200 stappen, dan moet je 43199 in OCR1A plaatsen.
Ja we zitten vast aan de Assembly taal helaas
Heb het aangepast aan jullie input 
de lowSecond en highMinute moeten natuurlijk een ander register zijn. Van die timer wist ik niet en is goed om te weten!
Ook heb ik een registeer gedeclareed als "TimerFlags" en de rcalls uit de init gehaald.
Nieuwe code:
loop:
cpi TimerFlags,0x01
breq newLowSecond
rjmp loop; wacht in lus op interrupt
secondCounter: ; ISR wordt elke seconde aangeroepen
in saveSR, SREG ; save SREG
ldi TimerFlags,0x01 ; nieuwe seconde
out SREG, saveSR; restore SREG
reti
newLowSecond:
clr TimerFlags
cpi lowSecond,0x09
breq newHighSecond
inc lowSecond
jmp loop
newHighSecond:
clr lowSecond
cpi highSecond,0x5
breq newLowMinute
inc highSecond
jmp loop
newLowMinute:
clr highSecond
cpi lowMinute,0x09
breq newHighMinute
inc lowMinute
in r16, PORTB ; schrijf de inhoud van PORTB naar r16
com r16 ; inverteer alle bits $00 <-> $FF
out PORTB, r16 ; schrijf waarde r16 naar PORTB
jmp loop
newHighMinute:
clr lowMinute
cpi highMinute,0x05
breq newHour
inc highMinute
jmp loop
newHour:
clr highMinute
cpi hours,0x17
breq newDay
inc hours
jmp loop
newDay:
clr hours
jmp loop
[Bericht gewijzigd door Henry S. op (2%)]
Ben al een stuk verder dan dat ik was. Nu zit ik alleen met het volgende. Ik heb een register r18 die ik TimerFlags noem. Hierin zet ik het volgende:
/*
0x0b0000 0001 seconde flag
0x0b0000 0010 uren nog niet ingesteld flag
0x0b0000 0100 minuten nog niet ingesteld flag
0x0b1000 0000 teller is klaar
*/
Wat ik hiermee graag wil en wat nu fout gaat is:
ldi TimerFlags,0xb0000 0001
ldi TimerFlags,(1 << 0x01)
En dat dan het resultaat: 0000 0011 is ipv 0000 0010.
Hier is geloof ik geen instructie voor.. Hoe dit op te lossen?
Ja normaliter wel geloof ik. Alleen gebruik ik nu mijn eigen status register voor de timer.
SBI en CBI gaan over IO registers wat mijn register dus niet is.
Het is toch gewoon een byte in het ram-geheugen. Lijkt me sterk dat die instructies daarop niet werken. Zelfs bij Microchip kan dat 
Tsja, het is geen IO register en daarom werkt het niet. Als ik het wel met een IO register doe werkt ie wel.
Zie nu net in de datasheet de SBR en CBR instructie misschien dat die het klusje klaren 
BSET en BCLR denk ik.
Edit; nee het zullen inderdaad wel SBR enCBR zijn.
[Bericht gewijzigd door Zonnepaneeltje op (56%)]
Druiloor
Opgepast: MaartenBakker citaat: "ruik ik een troll die per ongeluk met de arreslee meegelift is" :
Bij de defs staat 2 keer r20
Bij de increment seconde doe je dat met een rcall, je springt echter uit de subroutine als je 10 passeert, dat wil zeggen je slaat de ret over. Dat gaat dus fout.
Het is overzichtelijker :
addSecond:
inc lowSecond
cpi lowSecond,10
breq addHighSecond
ret
addHighSecond:
clr lowSecond
inc highSecond
inc r16
ret
Je hebt cbr en sbr mnemonics sbr r16,2 set het tweede bit
Je kunt altijd bst r16,2 doen, dan zit het tweede bit in T van SREG en daar heb je branch instructies voor brts brtc
immidiateinstructies werken bij (sommige?) Atmels niet in r0 t/m r15
Ik heb de rest verder niet gelezen, succes
Dus eigenlijk als ik de BST gebruik hoef ik mijn eigen register niet te gebruiken en kan ik gewoon met het status register werken?
De bovenstaande code is alweer erg verouderd en staan inderdaad de fouten in die je zei, heb het aangepast inmiddels.
Anoniemp? Toevallig hanze student met het zelfde blok
?
Wat ik doe is in de seconde interrupt een Flag zetten die ik later weer check in de loop. In deze check kan ik dus zien of er een nieuwe seconde bij is gekomen en als je seconde opgeteld is kan ik dus mijn output laten zien. Of bedoel je dat niet?
Druiloor
Opgepast: MaartenBakker citaat: "ruik ik een troll die per ongeluk met de arreslee meegelift is" :
Op 12 april 2013 14:29:27 schreef Anononiemp:
Hoe heb je het nou opgelost dan?
Misschien prettig als je verder converseert in je eigen gecreeerde topic. Daar heeft een zekere pinky-t een vraag gesteld aan je, waarvan hij het antwoord door mij gegeven niet wil horen, hij wil horen "is verplicht voorgeschreven op school" ; dan kan hij vervolgens de school waar je opzit afkatten; dat geeft hem vermoedelijk een goed gevoel. Gun hem dat dan ook.
Het beste wat je kan doen, is een flowchart tekenen, en die omzetten naar assembler.
Zoiets van (in woorden):
wacht in "main" op een vlag die in de interrupt routine wordt geset, als een seconde of deel van een seconde wordt bereikt door een tellerstandoverflow. Reset in main die vlag.
Verhoog daarna in main de "deelvan een seconde" teller
Indien volle seconde bereikt, dan reset die "deel van een secondeteller" en verhoog de secondeteller. Indien die 60 wordt, dan reset en verhoog de minuten teller, indien 60 wordt reset en verhoog de urenteller indien 24 reset en verhoog de dagvandemaandteller
indien etc etc. Vervolgens de waarden eruit naar de uart, daarna terug naar de wachten op de vlag
Tellers dus binair. BCD zou kunnen als je een decimal adjust instructie hebt voor optellen, die heb je wellicht niet. Binair spaart bytes en conversatie via de uart.
Omzetten van binair naar BCD doe je in je PC door te delen door 10 waardoor je de tientallen=quotient krijgt en de eenheden = rest van de deling.
PC programmeren in Delphi, in elektuur van een aantal jaren terug staat een cursus Delphi, er is een gratis Delphi die heet Lazarus.
Ik herinner me uit Elektuur, tegenwoordig Elektor geheten, dat je in les 2 al een 6 cijferige klok op je scherm van de PC zette.
Omdat de meeste interruptoverflows geen seconde halen aan de onderkant (maximale herhalingstijd) kun je met een breuk 1/n werken, met n een natuurlijk getal>1
Je zult merken dat een count gemaakt moet worden die je niet kunt maken om de klok redelijk gelijk te laten lopen, omdat die presetcount geen geheel getal is. De afwijking in de tijd is dus gemiddeld een halve seconde op [preset] seconde.
Stel dat die preset m,n moet zijn met n[00,99], dan kun je n keer uit de 100 interrupts de ene preset m nemen en 100-n keer de andere preset m+1. Over 100 interrupts is je klok dan 100 maal nauwkeuriger dan je met een enkele preset haalt. En dat is nodig want die 9600*96*12 Hz kristallen hebben een zeer ruime tolerantie. Je kunt wel met trimmers beginnen maar wat je met software kunt oplossen valt te prefereren.
Is dat duidelijk Anoniemp ?
@Workerbee
Wat zijn je overwegingen geweest, gelet op de omschrijving, om een 24 uurs klok te gaan maken met alle cijfers in aparte bytes, als dat niet is voorgeschreven?
Met andere woorden je kunt ook besluiten elke secondetik in een tikkentotaalteller binair te zetten (zoals in Unix) en die via een UART door te sturen naar een PC, of je kunt zelfs tik voor tik doorsturen, een nietszeggend karakter per seconde. Tot slot zou je aan de andere kant van het ontwerpspectrum een complete plaat in jpg format of mp3 kunnen maken in assembler en die elke seconde doorsturen naar je PC. Je moet er niet aan denken, maar waar je wel aan moet denken zijn de overwegingen om iets te doen zoals je nu doet, en een slechte overweging is: Ik zag een en slechts een gaatje aan het einde van de tunnel zonder dat ik hoefde na te denken, dus ik ga als een vlinder op het licht af .
Dit is wel voorgeschreven in het project. Ik moet dus 7 bytes in 1x sturen met daarin de zevensegment display waardes.
Druiloor
Opgepast: MaartenBakker citaat: "ruik ik een troll die per ongeluk met de arreslee meegelift is" :
Dan kun je nog beslissen:
Een cijfer per byte, in ASCII (printable, makkelijk voor foutcontrole tijdens ontwikkeling van de firmware)
of
7 bits 1 of 0 voor segment aan of uit en een bit voor decimale punt of voor JanJoker.
of
BCD danwel binary, voor 0 tot 9 maakt dat geen verschil en heb je 4 redundante bits per byte.
Wat ik nu al doe is 1 cijfer per byte elke seconde:
showOutput:
cbr TimerFlags,0x80
mov temp,highHour
rcall GetNumberValue
rcall sendOutputByte
mov temp,lowHour
rcall GetNumberValue
rcall sendOutputByte
mov temp,highMinute
rcall GetNumberValue
rcall sendOutputByte
mov temp,lowMinute
rcall GetNumberValue
rcall sendOutputByte
mov temp,highSecond
rcall GetNumberValue
rcall sendOutputByte
mov temp, lowSecond
rcall GetNumberValue
rcall sendOutputByte
ldi temp,0x00
rcall GetNumberValue
rcall sendOutputByte
jmp loop
Druiloor
Opgepast: MaartenBakker citaat: "ruik ik een troll die per ongeluk met de arreslee meegelift is" :
Ik zou dat niet zo omslachtig doen, je bent per slot van rekening geen optimaliserende C compiler.
Ik zou ervoor zorgen dat die bytes in de goede volgorde in je werkregisters staan bijvoorbeeld r1 t/m r7 (r0 kun je beter afblijven, die is gereserveerd voor lpm)
en dan
showOutput: ldi XL,LOW(r1)
ldi XH,HIGH(r1)
ldi r17,7 ; bytes to do
showOutput1: ld r16,X+
rcall SentOutputByte ;outputs r16 . r17 not damaged
dec r17
brne showOutput1
ret
Als je een cijfer per byte zendt, dan kun je overwegen de communicatie te beperken door alleen de cijfers die wijzigen over te zenden. Niet dat dat hier nodig is, zoals bij mpeg codering van videobeelden, maar een cijfer kost 4 bits dus je houdt er 4 over om aan te geven welk cijfer het is. Spaart je globaal 90% van de communicatie en je toont ermee aan dat je zuinig met resources omgaat, noodzakelijk of niet.