PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : In der Kürze liegt die Würze. Oder: weniger ist mehr.



SprinterSB
04.04.2006, 13:53
Hallo AVR-Freunde.

Eine beliebte Aufgabenstellung in Programmier-Kursen ist es (oder war es immer ;-)) ein Problem mit möglichst wenig Code zu lösen.

Die Aufgabe hier ist: Ein AVR soll eine LED im Sekundentakt blinken, und zwar mit möglichst wenig Maschinen-Befehlen!

Welche Programmiersprache ihr dazu verwendet ist egal, aber das Maß der Dinge sind die Assembler-Befehle bzw. Maschinen-Instruktionen, die "hinten" rauskommen.

Am besten geht so was natürlich direkt in Assembler. Mit Sprachen wie BASIC oder C hat man da eh keine Chance...

Lange Rede, kurzer Sinn: Wer schafft es, das kürzeste Assembler-Programm zu schreiben, um einen AVR im Sekundentakt einen Port zwischen LOW und HIGH wechseln zu lassen?

Nebenbei kann man durch das kleine Programm natürlich seinen AVR besser kennen lernen.

Die Aufgabe gilt als gelöst, wenn es jemand mit weniger als den magischen 10 Instruktionen schafft.

Noch ein Tip: Wenn man einen Lösungsvorschlag hat, kann man den so hinschreiben :





Lösung: ...

Dadurch sehen andere Leder beim Durchblättern nicht den Lösungsansatz und verlieren nicht die Lust am Mittüfteln.

Viel Spaß!

Rofo88
04.04.2006, 14:09
Frage : solls ne exakte Sekunde sein oder reichts in so etwa, also von 0,7 bis 1,3 oder so???


MfG

SprinterSB
04.04.2006, 14:18
So gaaanz genau muss es nicht sein, aber bis auf 2-3% sollte es schon stimmen.

+/- 30% Fehler ist aber entschieden zu viel.

Rofo88
04.04.2006, 15:46
Nagut,

is ein 3-Zeiler und hat nen fehler von 4,8% laut Simulator.








.include "m8def.inc" ;gedacht für nen Mega-8
sbi DDRB,7 ;Port als Ausgang für die LED
inc R16 ;R16 um eins erhöhen
out PORTB,R16 ;R16 ausgeben

habs auch noch nicht in Wirklichkeit ausprobiert, aber geht im Simulator

Taktfrequenz 1 Mhz
der Portbin muß immer die DDR*,7 sein

kleiner gehts wohl nicht, genauer aber :idea: :idea: :idea:

MfG

SprinterSB
04.04.2006, 16:12
@Rofo88

Und was pasiert nach dem letzten Befehl??? Wir gehen in den Wald...


Zur Taktfrequenz: Die kann als bekannte Konstante angenommen werden. Der Code sollte also für 1MHz ebenso gehen wie für 12MHz (evtl mit neuem Übersetzen nach Anpassung der Konstanten in der asm-Quelle).

Rofo88
04.04.2006, 16:23
@SprinterSB

Jeb da gehts ab ins nichts... das ist richtig, da steht dann FFFF womit der AVR nicht anfangen kann und das macht er bis zum ende des Speichers ( also ne pause ohne eine Codezeile, stant ja nirgendwo das ich den ganzen Flash nicht nutzen darf sollte nur wenig Code sein) :lol: :lol: :lol:

is nicht sehr elegant aber funzt...

zur Taktfrequenz : da brauche ich dann wohl mehr Flash \:D/

Fazit : nicht ganz sauber aber unheimlich klein...

MfG

Mehto
04.04.2006, 18:50
Hi,

super Idee, muss natürlich mitmachen....

Und zwar bring ich einfach mal die Standard Methode....








.include "m16def.inc"

sbi DDRB, 2

main:
ldi r16, 1
ldi r17, 145
ldi r18, 3


loop:

dec r16
brne loop

dec r17
brne loop

dec r18
brne loop

com r20
out PortB, r20

rjmp main


Sind 13 Befehle...

Damits nicht bei der Unglückszahl bleibt hier nur 12:









.include "m16def.inc"

sbi DDRB, 2

main:
ldi r17, 145
ldi r18, 3


loop:

dec r16
brne loop

dec r17
brne loop

dec r18
brne loop

com r20
out PortB, r20

rjmp main



Kann sein das die Zeit nicht 100% stimmt, müste aber halbwegs in der Toleranz sein.
Gruß,
Mehto

SprinterSB
04.04.2006, 19:32
@Mehto: Das sieht doch schon mal gut aus! Hart an den "weniger als 10" :-)







Kannst du noch 3 Befehle sparen? Bei den Werten für die Zählschleife komm ich auf ca. 335000 Zyklen, was auf einen Takt von 1/3 MHz passen würde.


@Rofo88: [-X Deine Lösung verwendet neben den 3 Befehlen noch 8186 Bytes des Flash (ATmega8) also insgesamt 8192 Bytes. Nicht gerade kurz die Lösung...

Rofo88
04.04.2006, 20:34
erfüllt aber voll deine Aufgabenstellung


und zwar mit möglichst wenig Maschinen-Befehlen!

mit weniger als den magischen 10 Instruktionen


ok gebs ja zu, es ist ein Hintertürchen :cheesy: :cheesy: :cheesy:

Mehto
04.04.2006, 21:27
ok, hab noch eins, zwar nicht kleiner, sondern mit 13 Befehlen, dafür aber mit Timer :-)
Was mich wirklich wundert ist, ist das es funktioniert, Simulieren klappt bei mir nicht.

Allerding ist die Sekunde doch ein wenig zu schnell (0,9536743 Sekunden, bei einem Quarz von 1,048576MHZ wären es genau 1sek) und nicht für einenen anderen Quarz anpasspar...
(erst wieder bei einem Quarz von 8,388608MHZ wieder genau, oder bei einem mit 33,554432MHZ oder 134,21773MHZ *g*)








.include "m16def.inc"

.org 0x01E
rjmp TIM1_OVF ;Timer1 Overflow Handler


start:

sbi ddrb, 2 ;PinB 2 als Ausgang

ldi r16, (1<<INT0) ;Int0 aktivieren
out GICR, r16


ldi r16,0b00000010 ;Vorteiler auf 8
out tccr1b,r16

ldi r16,(1<<toie1) ;auf overflow stellen
out timsk,r16
sei ;Interupts aktivieren


ende:
rjmp ende


TIM1_OVF:

com r18
out PortB, r18
reti




Wüste sonst leider gerade nicht wie man das ganze kleiner machen könnte, die Idee von Rofo88 war eigentlich schon recht gut, man sollte aber noch schauen ob das überhaupt richtig funktioniert.

Kommt halt drauf an wie man es sieht, eigentlich stehen im Code ja noch über 8000 Nop's anderseits kann man sich die bei einem neuen µC sparen.

Aber wie ist das bei einem gebrauchten?
Der Flash wird ja nicht jedesmal komplett gelöscht, oder?
Sobald also ein älteres größeres Programm vorher drin stand dürfte der Code nicht meher funktionieren.

Aber ich denke mit nur drei Befehlen kann man nur irgendwie tricksen, anderst ist das gar nicht möglich...

Gruß,
Mehto

SprinterSB
04.04.2006, 21:44
erfüllt aber voll deine Aufgabenstellung

und zwar mit möglichst wenig Maschinen-Befehlen!


Eigentlich nicht. Du verwendest mehr Maschinen-Befehle. Im Assembler-Code stehen zwar nur 3 Zeilen Code, aber es geht eben um die verwendeten Maschinen-Befehle. Und das sind bei die _recht_ viele ;-)

Wo deine Masch-Codes herkommen ist ja egal. Ob sie vom Himmel fallen oder explitit in der Assembler-Quelle stehen, du brauchst sie und kommst nicht ohne sie aus.
Übrigens: würde das Programm z.B. durch einen Bootloader gesaugt und gestartet, würde es rummsen. Mehtos Programm funktioniert auch dann.

Ganz nebenbei sind es illegal Opcodes. Insbesondere sind es keine NOPs.
Bisher hab ich in keiner Spezifikation was gefunden, welchen Effekt FF:FF auf die Maschine hat. Es gab sogar mal einen Thread dazu, der ergebnislos blieb. Und Quellen wie die von avr-objdump sagen nur, daß FF ein Loch in den Opcodes ist und nichts implementiert.

teslanikola
04.04.2006, 21:56
.include "2313DEF.inc"
sbi DDRB,7 ;Port als Ausgang für die LED
Main:
inc R16 ;R16 um eins erhöhen
out PORTB,R16
rjmp Main

und den AVR mit 3Hz Tackten und du hast deine eine Sekunde ( laut Sim ).

Mehto
04.04.2006, 22:01
mhh, da könnte man jetzt aber diskutieren... :-k

im Grunde brauchen alle Programme gleich viel Maschienen-Befehle, bei Rofo werden sie ohne zu springen nacheinander verarbeitet, und bei mir halt immer wiederholt.

Ich hab seine Version jetzt mal getestet, funktioniert wunderbar, und das sozusagen mt drei Befehlen. Nur etwas zu langsam, etwas zwei Sekunden in der Minute zu wenig.

Aber genauso wie meine Timerversion ist die leider nicht anpassbar geschweige denn auf einen anderen µC aanpassbar (aber trotzdem genial).

Was für ein Hexcode sind eigentlich Nop's? Ich bin mir nicht sicher, aber ich glaube entweder 0x00 oder 0xff.

Was mich aber wirklich interessiert ist, was mit dem schon beschriebenen Inhalt passiert wenn man einen neueren, kleineren Code einspielt. Wird der automatisch auf 0xff gesetzt und das geht so schnell das man es nicht bemerkt, oder wie ist das?

Gruß,
Mehto

Mehto
04.04.2006, 22:09
@teslanikola

*g* schonmal einen AVR mit 3HZ getaktet?
Ich glaub das niedrigste sind mit speziell gesetzten Fusebits ca. 33KHZ.

Dein Code stimmt aber glaube ich nicht, nim statt inc com dann stimmts.
Sonst brauchst du einen Takt von ca. 768 (wenn ich retzt richtig gedacht habe). Aber ist ja wie gesagt nicht machbar, nur im Simu.

Gruß,
Mehto

dennisstrehl
04.04.2006, 22:26
*g* schonmal einen AVR mit 3HZ getaktet?
Ich glaub das niedrigste sind mit speziell gesetzten Fusebits ca. 33KHZ.

Man kan ein Uhrenquarz benutzen, dann ist man doch schon bei 32,768 kHz. Und über das Clock Prescale Register (gibt es zumindest beim Mega48) kann man die 32 kHz noch durch 256 teilen, macht 128 Hz ^^

Oder per externer Taktquelle mit nem Funktionsgenerator dran, dann geht's noch weiter.

SprinterSB
04.04.2006, 22:31
und den AVR mit 3Hz Tackten und du hast deine eine Sekunde ( laut Sim ).

Klopp mal den Simulator in die Tonne. Eine Schleife dauert 4 Takte (1+1+2 laut Brain 1.0).

Mit externem Oszillator geht das schon, nen Takt von 4Hz zu machen. AVRs arbeiten ja voll statisch.

Wie passt du deine Quelle auf andere Taktraten an?


Was für ein Hexcode sind eigentlich Nop's?

Ein NOP ist 00:00. FF:FF ist wie gesagt Illegal Opcode.
Vor dem Flashen wird z.B. mit "erase device" der Flash gelöscht, also auf FF gesetzt. Ansonsten steht da Müll rum bzw das, was vorher da stand und man kommt nen Fehler beim Proggen. Die meisten Progger machen das ohne das man was davon merkt.

dennisstrehl
04.04.2006, 22:45
Mal so als Anregung: Hat schon einer mal geschaut ob man den 16bit-Timer mit 9 Takten so initialisieren kann dass er ne 1Hz-PWM erzeugt? (bzw. 0,5 Hz wenn das mit "im Sekundentakt blinken" gemeint ist)?
Mit Clear Timer on Compare Match könnte man den an den Takt anpassen.

SprinterSB
04.04.2006, 22:58
Noch eins:

Falls Unklarheit über den Flashverbrauch eures Programms bestehen, macht einfach ein Disassemble eurer "Spielwiese".

Das Programm darf natürlich auch Konstanten enthalten, die im Flash gespeichert sind.

SprinterSB
05.04.2006, 17:24
Tipp: Bei der Lösung, die ich gefunden habe, verwende ich einen Interrupt.

Welchen, verrat ich jetzt mal nicht. Sonst wisst ihr direkt, wie es geht ;-)

uwegw
05.04.2006, 19:19
Kann es sein, dass deine Lösung auf nem 90S8515 nicht funktioniert? Den hätt ich grad auf meinem Steckbrett drauf und wollte grad anfangen zu proggen, aber so wie ich denke das du es gemacht hast gehts mit ihm nicht...
Lösungansatz per PN...

[EDIT: Lösungansatz durch SprinterSB bestätigt, aber das mit den 90S8515 ist Quatsch]

tobimc
05.04.2006, 19:36
Hi,

Lösungsansatz ebenfalls per PN, und wir denken das Gleiche, uwegw... ;D
ich werd mal versuchen, in gebrochenem ASM zusammenzustückeln...

VLG...

SprinterSB
05.04.2006, 19:55
Kann es sein, dass deine Lösung auf nem 90S8515 nicht funktioniert?

Für die abgekündigten AVRs hab ichs noch nicht durchgeschaut, da müsste ich mir erst alle Datenblätter saugen (und das bei 56k).

Mit den aktuellen AVRs geht's aber.

*Birne-rauch*

Mit einem AT90S8515 hab ich ne Lösung mit 14 Bytes Code (7 kurze Instruktionen). Zumindest nach dem Datenblatt sollte es gehen.

tobimc
05.04.2006, 20:05
Hi

Also hier meine lösung, hierbei gilt:
I've just checked it correctly, not tried it.

-> ich glaube auch nicht, dass der Code funzt... :D












.include "m16def.inc"

;system-deklaration (gehört nicht zum Code.... :D)
ldi R17 , 0b111111
out DDRb , R17
out PORTb , R17

;hier beginnt der Code

sbis portb , 0 ;... ... ... portb.1 invertieren... umständlich
sbi portb , 0

sbic portb , 0
cbi portb , 0


ldi R16 , 0b00011110 ; Watchdog-Controllregister laden
out WDTCR , R16

ldi R16 , 0b00000000 ;Sleepmode setzen, sucht euch einen raus
out MCUCR , R16

sleep ;schlafen

Naja. Wie gesagt, das setzen der IOs als output gilt nicht als Code... :D

Ohne das ist der code 9 zeilen lang, mit 12 (eig. 11) und ergibt 12 Bytes.

-> ich bin überzeugter Basic-Progger, der warscheinlcih bald auf C umsteigt, und in ASM bin ich nicht so gut (miserabel)... ;D

VLG Tobi

SprinterSB
05.04.2006, 20:39
@tobimic, nö, das funzt net. Die LED wird immer leuchten.

uwegw
06.04.2006, 14:54
Das Problem bei der Sache ist ja, dass man irgendwie ein Bit über den Reset hinaus retten muss, um zu wissen, wo man weiterblinken muss...
Die Register werden plattgemacht und das EEPROM würde das nicht lange mitmachen...










...dachte ich zumindestens bis jetzt immer, dass ein Reset die Register killt!
Informationen dazu waren nicht wirklich zu finden, aber Versuch macht klug:


.INCLUDE "8515def.inc" ;Prozessordefinition laden

;Register zum Speichern des alten Zustandes
.DEF altwert= R17

.cseg
.org $0000

;Watchdog einstellen und starten
ldi r16, 0b00001110 ;nach 1 sec Reset
out WDTCR, r16

;Alten Wert invertiern und ausgeben
COM altwert
OUT DDRb, altwert
OUT PORTb, altwert

loop:
rjmp loop


Funktioniert und macht nur 6 Words...

[EDIT: erst hatte ich ne Version mit Speicherung im SRAM hochgeladen, aber es scheint ja auch mit nem Register zu gehen...]

SprinterSB
06.04.2006, 15:43
Wo wird denn der Port auf OUT geschaltet?

Ok, so blinkt die LED, wenn man sie gegen GND hängt. Sie wird dann über den PullUp versorgt. Für superhelle LEDs recht das.

uwegw
06.04.2006, 15:46
Ich hab grad ne bessere Version nachgeschoben ... andere Methode aufgrund von neuen experimentalen Erkentnissen, jetzt mit 6 Words... inklusive Port als Ausgang einstellen...
(den einen Befehl gegenüber deiner Vorgabe von 7 spart man, wenn man mit dem Programm sofort an .ORG =$0000 beginnt und nicht erst noch nen Sprung einbaut, der sonst wegen Interruptvektoren nötig wäre)

SprinterSB
06.04.2006, 16:57
Jepp, fast so hatte ich es auch gelöst, nur daß bei mir nix ausser einem Port-Pin wackelt:







; Hier für AT90S8515, AT90S1200, ...

/*
Die Register (GPRs) werden durch einen RESET nicht zurückgesetzt, sondern behalten ihre Inhalte!

Wirklich schwer wird es, wenn man mit VCC runter geht, weil sich dann die WDT-Zeiten ändern. Mit 0.19s (VCC=3V) käme man auf 5% Fehler, aber den Vergleich auf 5 (bzw. modulo 5) hab ich nicht in weniger als 10 Instruktionen geschafft.

Noch nicht... ;-)
*/

#include <avr/io.h>

#define _IO(x) _SFR_IO_ADDR(x)

.text

; #1: PortB.2 als Ausgang
sbi _IO (DDRB), 2

; #2-#4: Toggle PortB.2
inc r0
sbrs r0, 1
sbi _IO (PORTB), 2

; #5-#6: WatchDog auf 0.49 Sekunden scharf machen (genauer als 0.97s)
ldi r16, (1 << WDE) | (1 << WDP2) | (1 << WDP0)
out _IO (WDTCR), r1

; #7: Warten, bis es wieder los geht (WatchDog-Reset)
0:
rjmp 0b

Mehto
06.04.2006, 17:51
Hier ist noch meiner Version mit Watchdog von gestern Abend.
Ich war mir zuerst nicht sicher ob ihr auch den Watchdog gelten läst, immerhin ist der doch recht ungenau (bei mir über 4 sek. in der Minute).


Leider blinkt die LED nicht, sondern wird immer kurz gepulst (man sieht es aber) und der Komplette Flash wird durchlaufen...

5 Maschienenbefehle:





.include "m16def.inc"


ldi r16, (1<<wde|1<<WDP2|1<<WDP1)
out WDTCR, r16

sbi ddrB, 2

com r20
out PortB, r20


Beim genauerem betrachten ist der Code aber absuluter Quatsch, die LED wird in wirklichkeit andauernd gepulst (allerdings zu schnell fürs menschliche Auge) da der Code mehrmals durchlaufen wird, nur wenn der Watchdog einsprignt ist die Pause ein wenig länger und man sieht jede sekunde ein kurzes flashen...

Besser und nur eine Zeile länger, ist es den Controller einfach zu beschäftigen wie man es ja auch normalerweise macht #-o






.include "m16def.inc"

ldi r16, (1<<wde|1<<WDP2|1<<WDP0)
out WDTCR, r16

sbi ddrB, 2

com r20
out PortB, r20

ende:
rjmp ende



Aber das ist dann ja schon die Lösung von uwegw.

Ich fand das Rätsel auf jeden Fall sehr gut, sollten wir mit einer anderen (komplexeren?) Aufgabe wiederholen.

Gruß,
Mehto

SprinterSB
06.04.2006, 21:32
Freut mich, wenn das Knobeln Spaß gemacht hat :-)

Als erstes hat uwegw die 10 Instruktionen unterschritten.

@Mehto: Kannst ja was überlegen. Jedoch find ich für solche Knobeleien gerade einfache Aufgaben interessant, da gibt es schon trickreiche Lösungsansätze und Ideen genug. Und zu oft sollte ein Themenbereich auch nicht beackert werden, find ich...