Programm-Struktur für Anfänger

dino03

Aktives Mitglied
27. Okt. 2008
6.760
20
38
Sprachen
  1. BascomAVR
  2. Assembler
Hallo zusammen,

ich hab mal wieder nix anderes zu tun als ne neue FAQ zu schreiben :D
Also diesmal geht es darum, wie man ein Programm strukturiert und
systematisch aufbaut. Ich hab ja sxhon ein paar Programmiersprachen
hinter mir ;) Aber die richtige Struktur von einem Programm hab ich damals
in Pascal gelernt. Das zwingt einen zur sauberen Struktur weil man sonst
mit fehlermeldungen erschlagen wird :p

Also fangen wir mal an ...

Wie bei einem guten Buch sollte man ganz an den Anfang das Vorwort setzen.
Das sind einige Bemerkungszeilen in denen man schreibt was das Programm
eigentlich machen soll, wer es wann geschrieben hat und auf welchem
Prozessor es mit welchen Einstellungen (Fuses), Takt, ... laufen soll. Den
Namen des Programms (wenn es denn einen hat) sollte man auch nicht
vergessen. Das sieht in Assembler zB so aus ...


CodeBox ASM

; -----------------------------------------
; ASCII-Keyboard mit LCD-Display (Terminal)
; LCD-Display Powertip PC1602-E (Pollin)
; mit ST7066U Controller an ATmega168-20PU
; mit 12MHz Quarz
; -----------------------------------------
;
;
;
; ===============================
; ========== ATtiny2313 =========
; ===============================
;
.include "tn2313def.inc" ;Definitionsdatei laden
;
;
;
; ----- FUSES -----
;
; * SUT1 und SUT0 (Zustand=11): Start-up Time 65ms nach Reset,
; Einstellung für Quarzoszillator und langsam ansteigende
; Betriebsspannung (Tabelle 5 des Datenblattes)
; * CKSEL3-CKSEL0 (Zustand=1111): Quarzoszillator im Bereich 3-8MHz
; (Tabelle 4 des Datenblattes)
; * CKOPT (Zustand=1): schneller Quarzoszillator (Tabelle 4 des Datenblattes)
; * BODEN (Zustand=0): Brown-out einschalten
; * BODLEVEL (Zustand=1): Brown-out Schwelle auf 2,7V setzen
;
; Unter Beachtung der invertierten Logik der Fuse-Bits sollte man
; also die Fuses so setzen wie im folgenden Bild:
;
; ( )7 ( )6 ( )5 ( )4 ( )3 ( )2 [ ]Lock2 [ ]Lock1
;
; ( )7 ( )6 ( )5 ( )4 ( )3 ( )2 ( )1 [ ]SPMEN
;
; [ ]DWEN [ ]EESAVE (X)SPIEN [ ]WDTON [ ]BODLEVEL2 [ ]BODLEVEL1 [ ]BODLEVEL0 ( )RSTDISBL
;
; [X]CKDIV8 [ ]CKOUT [ ]SUT1 [X]SUT0 [X]CKSEL3 [ ]CKSEL2 [X]CKSEL1 [X]CKSEL0
; ______________________
; | |
; | [X] Bit=0 [ ] Bit=1 | ( ) -> Nicht anwaehlbar [ ] -> Anwaehlbar
; | progr. unprogr. |
; |______________________|
;
;
;
; ----- PINS -----
;
; ATMEL AVR ATtiny2313
; 2kByte Flash (1kx16)
; 128Byte EEPROM
; 32 Register + 128Byte RAM
;
; ATtiny2313
; +------------------------------------------+
; (ISP) RESET --| 1 PA2(Reset/dW) VCC 20|-- +Vcc
; | 2 PD0(RXD) (UCSK/SCL/PCINT7)PB7 19|-- SCK (ISP)
; | 3 PD1(TXD) (MISO/DO/PCINT6)PB6 18|-- MISO (ISP)
; XTAL --| 4 PA1(XTAL2) (MOSI/DI/SDA/PCINT5)PB5 17|-- MOSI (ISP)
; XTAL --| 5 PA0(XTAL1) (OC1B/PCINT4)PB4 16|
; | 6 PD2(CKOUT/XCK/INT0) (OC1A/PCINT3)PB3 15|
; | 7 PD3(INT1) (OC0A/PCINT2)PB2 14|
; | 8 PD4(T0) (AIN1/PCINT1)PB1 13|
; | 9 PD5(T1/OC0B) (AIN0/PCINT0)PB0 12|
; GND --|10 GND (ICP)PD6 11|
; +------------------------------------------+
; (*) 100nF nach GND
;

Da hat aber jeder so seine Eigenheiten und eigenen Stil. Aber so im Großen
und Ganzen sollten diese Angaben vorhanden sein.


Danach kommen die Einstellungen für den Compiler. Also zB die Include-Datei
für den Prozessortyp (wenn man sie denn nicht schon im Vorwort eingebaut
hat). Auch die Clockrate gehört hier jetzt hin und auch alle weiteren
Einstellungen die nur für den Compiler da sind. Das ist bei Bascom zB dieser
Bereich ...


CodeBox BASCOM
' #############################################################################
' ##### Definitionen ##########################################################
' #############################################################################

' Prozessor ATmega8535 (lag grade so rum)
' 8k Flash, 512Byte SRAM, 512Byte EEPROM
$regfile = "m8535.dat"

' 16MHz Quarztakt
$crystal = 16000000

' Hab ich abgekupfert ;-)
$hwstack = 32
$swstack = 10
$framesize = 40


Nun weiß der Mensch und auch der Compiler was das alles soll und womit
man es zu tun hat. ;)

Bei der weiteren Vorgehensweise kann man sich jetzt etwas streiten was
man zuerst und was danach gemacht werden soll. Ich benutze da folgendes
Vorgehen ...

Jetzt kommen die Variablendeklarationen mit den Initial-Werten. Weil man die
evtl schon sehr früh im Programm benötigt. Wie schon vorher ein kleines
Beispiel (Bascom) ...


CodeBox BASCOM
' #############################################################################
' ##### Variablen #############################################################
' #############################################################################

' Was Byte-mässiges
Dim A As Byte
A = 1

Dim B As Integer
B = 0

Dim B_alt As Integer
B_alt = B

Die Initialwerte sollte man auf keinen Fall vergessen weil die Variablen sonst
irgendwelche Zufallswerte beinhalten können. Man darf also bei der ersten
Benutzung einer Variablen nicht davon ausgehen das sie leer ist oder Null
enthält.

Jetzt würde ich im Anschluß die Portkonfiguration machen. Zuerst mal die
normalen Ports ...


CodeBox BASCOM
' #############################################################################
' ##### Initialisierung #######################################################
' #############################################################################


' ==============================================
' Die Ports auf dem Eval-Board und ihre Belegung
' ==============================================

' DDRx => Datenrichtung des Portpins
' 0=Eingang , 1=Ausgang

' PORTx => Ausgang oder PullUp
' 0=GND/PullUp aus , 1=Vcc/PullUp an

' PINx => Eingang (Pin abfragen)

' === PortA ============================
' PA7 PA6 PA5 PA4 PA3 PA2 PA1 PA0
' - - - - - - - -
' Alle Portbits können verwendet werden
Ddra = &B1111_1111
Porta = &B0000_0000
' ======================================

Zu dieser Portkonfiguration gehört natürlich auch die Beschreibung, was sich
an welchem Pin befindet. Also zB 1Wire-Bus, LCD, Taster, LED, ... und wie
der angeschlossene Pin der Pheripherie heißt (zB RS beim LCD).

Wenn die "normalen" Ports konfiguriert sind und wissen was sie zu tun
haben und ob sie Eingang oder Ausgang sind kann man an die "spezielleren"
Ports gehen. Das wären zB die LCD-Definitionen, 1Wire-Bus, RS232, ...


CodeBox BASCOM
' ======================================
' === LCD-Initialisierung ==============
' ======================================
' Umschaltbyte für Anzeige : 0 = Chip 1 / 1 = Chip 2
Dim ___lcdno As Byte
' with the config lcdpin statement you can override the compiler settings
Config Lcd = 40 * 4

' LCD an PORT-A läuft auch !
Config Lcdpin = Pin , Db4 = Porta.0 , Db5 = Porta.1 , Db6 = Porta.2 , Db7 = Porta.3 , E = Porta.4 , E2 = Porta.5 , Rs = Porta.6

Config Lcdbus = 4

' === oberen Chip 1 einschalten und initialisieren ===
___lcdno = 0
Initlcd
Cls
' === unteren Chip 2 einschalten und initialisieren ===
___lcdno = 1
Initlcd
Cls
' ======================================
' ======================================
' ======================================


Damit ist das gesamte System jetzt hardwaremäßig in einem Zustand das
jeder Pin weiß was er zu tun hat. Die Interrupts sollte man bei den speziellen
Ports allerdings erst kurz vor der Hauptschleife einschalten. Sonst hat man
in der Initialisierungsphase noch zuviel Wildwuchs mit den Unterbrechungen.
...
..
.
 
und weiter gehts. Die Ports sind fertig. Nun kommen die Systemstrukturen.
Also zB irgendwelche Daten die Aus dem EEPROM als Kallibrierwerte geladen
werden müssen usw. Nun muß die Software ihren stabilen Zustand erreichen.
Was man hier alles zu tun hat ist natürlich von dem entsprechenden Programm
abhängig. Es könnte zB eine Zeit sein die man aus einer RTC lesen muß um die
Softwareuhr zu initialisieren oder zB ein Test ob die angeschlossene Hardware
auch wirklich vorhanden ist (1Wire-Bus mit soundsoviel Sensoren, ...) Diese
Aufgaben sind jetzt dran.

Danach ist das gesamte System von der Hardware und von der Software
in einem Zustand das die Arbeit losgehen kann.

Was jetzt folgt kennen die meisten Mikrocontroller-Einsteiger aus ihrer evtl
vorhanden Programmiererfahrung auf dem PC überhaupt nicht. Es folgt eine
ENDLOSSCHLEIFE :p Die nennt man auch Hauptschleife oder Mainloop oder
wie auch immer. das ist sozusagen unser DOS oder Windows. Diese Schleife
fängt den Prozessor wieder auf wenn er miit einem Unterprogramm fertig ist.
Wenn man die vergißt dann rennt der Prozessor bis zum Ende seines Flash
und darüber hinaus. Er fängt also von Adresse 0000 wieder an weil der
Programmzähler an Ende des Flash einmal übergelaufen ist. In dieser Schleife
baut man die Dinge ein, die so im normalen Betrieb geschehen sollen. Aber
dieser Inhalt wird sich schon von selber ergeben wenn man tippt und denkt
und tippt und denkt :stupido3: :stupido2: :fie: ;) Ganz kurz sieht das dann
so aus ...


CodeBox BASCOM
' #############################################################################
' ##### Hauptschleife #########################################################
' #############################################################################

Do

Waitms 100

' Da es in Bascom keinen SHIFT-Befehl gibt der mit dem in Assembler
' vergleichbar ist mußte ich ein wenig improvisieren ...

' Also Variable um 1 Bit nach links schieben ( X<--B7<B6<B5<B4<B3<B2<B1<B0<--0 )
' Bit 7 fällt aus der Variablen raus und in Bit 0 wird eine Null geschoben
Shift A , Left , 1

' Wenn PinD.2 gesetzt ist (Taster 1 gedrückt) dann
If Pind.2 = 1 Then
' Setze Bit 0 in Byte-Variable A
Set A.0
End If

' Variable binär auf PortC ausgeben
Portc = A

' Nur 1-Bits eingeschoben => Taster vollständig gedrückt, Prellen hat aufgehört
If A = 255 Then
' LED 1 anschalten
Portd.5 = 1
End If

' Nur 0-Bits eingeschoben => Taster vollständig geöffnet, Prellen hat aufgehört
If A = 0 Then
' LED 1 ausschalten
Portd.5 = 0
End If

' Es wird nur auf das LCD geschrieben wenn sich B in der Zwischenzeit
' durch die Timer0-ISR geändert hat. Dann wird der neue Wert ausgegeben
' und für die nächste Überprüfung auf Änderung in B_alt gespeichert.
' Das entlastet das System von unnötigen LCD-Ausgaben
If B_alt <> B Then
Locate 2 , 20
Lcd B ; " "
B_alt = B
Locate 1 , 20
Lcd A ; " "
End If

Loop


Damit hat man bei kleinen Aufgaben eigentlich schon alles zusammen. Man
könnte aufhören und es funktioniert alles ;) Aber wir wären schlechte
Programmierer wenn wir alles in diese Hauptschleife klatschen würden.
Außerdem gibt es noch ein paar Kleinigkeiten die die meisten Umsteiger
von ihren PC-Programmen auch nicht kennen ... und das kommt jetzt ...
...
..
.
 
und weiter gehts. Nun kommt Modularität in das Programm. Damit man das
Rad nicht jedesmal neu erfinden muß erstellt man für bestimmte Aufgaben
eigentlich Subroutinen (Unterprogramme, Prozeduren, Funktionen). Die
versucht man natürlich so universell wie möglich zu gestalten. Man muß
das mit den Libraries in Bascom vergleichen. So wie es andere Programmierer
gemacht haben wie sie die Bibliotheken geschrieben haben so muß man jetzt
auch vorgehen. Man definiert sich "Softwareschnittstellen" mit denen diese
Unterprogramme mit den anderen Teilen des Gesamtprogramms sprechen
sollen. Das können in Bascom zB bestimme Variablennamen sein oder in
Assembler bestimmte Register oder bestimmte Speicherbereiche in denen
Daten übergeben werden. Bei dieser Definition wie man seine Daten von einem
Teil des Programms in die Subroutine bekommt sollte man sich etwas Zeit
nehmen und auch für die Zukunft planen. Wer weiß wofür dieser kleine Teil
nochmal gut ist. Und wenn mann sich jetzt etwas mehr Zeit nimmt und etwas
mehr Arbeit macht, dann kann man evtl in der Zukunft dadurch sehr viel
Zeit sparen.

Bei den Subroutinen gibt es beim Mikrocontroller zwei verschiedene Arten.
1. Die normalen Subroutinen
2. Die Interrupt-Service-Routinen

Die normalen Subroutinen sind ganz normale Teile des Programms die man
über einen Call (Asm) oder Gosub (Bascom) anspringt, mit Daten versorgt,
die dann irgendwas machen und danach wieder zum Ausgangspunkt von dem
sie angesprungen wurden zurückkehren (mit Return).

Die Interrupt-Service-Routinen (kurz ISR) sind spezielle Subroutinen die man
eigentlich nicht selber anspringt. Die weden direkt von der Hardware
angesprungen. Wenn ein Ereignis im System passiert dann werden sie direkt
und meist auch sofort vom Prozessor aufgerufen. Wenn man zB Daten im
Eingangspuffer des USART hat oder wenn eine AD-Wandlung abgeschlossen
wurde oder ein Timer einen Zählerüberlauf oder einen bestimmten Wert
erreicht hat, oder, oder, oder. Diese ISRs sind also die Reaktionen des
Prozessors auf einen bestimmten Zustand der Hardware. Man kann diese
Reaktionen auf Zustände entweder einzeln abschalten (zB Timer0, ADC, ...)
oder die gesamte Interrupt-Reaktionen deaktivieren (global). Man muß auf
jeden Fall beachten das man durch einen Interrupt SOFORT aus der normalen
Abarbeitung des Programms herausgerissen wird. So wie zuhause ...

Tür geht auf, Mutter stürmt rein, ... "Du bringst jetzt SOFORT den Müll runter" :D

Da gibt es keine Ausreden. Man kann nur noch schnell den Game-Status
speichern und dann heißt es laufen ;) Diesen Game-Status - also der aktuelle
Zustand bei der Unterbrechung durch die ISR - muß man als erstes in der
ISR abspeichern. Dazu gehören die aktuellen Registerwerte und ganz wichtig
das Statusregister. In Assembler muß man sich darum selber kümmern.
Bascom macht das für einen. Also alle Zustände auf den Stack speichern.
Nach der Abarbeitung der ISR werden sie wieder vom Stack geholt und die
normale Programmabarbeitung geht an der Stelle weiter wo man vorher
unterbrochen wurde. Da es meist weniger ISRs als andere subroutinen gibt
würde ich sie direkt hinter die Hauptschleife setzen. Das sieht zB so aus ...


CodeBox BASCOM
' #############################################################################
' ##### Interrupt-Service-Routinen ############################################
' #############################################################################

' ======================================
' === ISR für Timer0-Overflow ==========
' ======================================
'the following code is executed when the timer rolls over
Tim0_isr:
Incr B
If B > 1023 Then B = 0
Return
' ======================================
' ======================================
' ======================================


Ganz wichtig! Die ISRs sollten so kurz wie möglich gehalten werden da
innerhalb einer ISR die normale Interrupt-Bearbeitung global deaktiviert
ist. Also kurz die Daten irgendwo sichern die angefallen sind, noch ne
Variable oder nen Register als Flag setzen das man da was neues hat
und den Rest der Bearbeitung in die Hauptroutine oder eine andere
Subroutine verlagern die dann aus der Hauptschleife angesprungen wird.

Nach den ISRs kommen dann in ähnlicher Weise die normalen Subroutinen.
Das können zB Ausgaberoutinen für Werte auf dem LCD sein oder Routinen
für Temperatorberechnungen usw.

Damit ist der Bereich mit Programmcode auch "schon" abgeschlossen. Aber
wir sind immer noch nicht am Ende ;)
...
..
.
 
Nun zum Schluß des ganzen Geschreibsels ...
Die Programmzeilen sind fertig, man hat ne Menge Gehirnschmalz in seine
Routinen investiert aber die Routinen alleine machen kein Programm. Was
ist zB mit Zeichensätzen für ein Grafik-LCD ? Oder mit Umwandlungstabellen ?
oder ...
Die kommen jetzt dran. Man sollte sich dabei eines angewöhnen :
Ein Programm wächst vom Anfang des Flash nach hinten. Die Datentabellen
wachsen vom Ende des Flash zum Anfang. Damit muß man nicht auf einmal
seine gesamte Adresstruktur über den Haufen werfen wenn es eng wird.
Aber wie immer keine Vorgehensweise ohne Ausnahme ... Wenn man was
mit Bootloadern machen will dann muß der hintere Flashbereich je nach
Einstellung in den Fuses mehr oder weniger weit für den Bootloader frei
bleiben. Also die Datentabellen etwas nach vorne schieben. Es sieht im
Flash also ungefähr so aus ...

Anfang --- Programm --- frei --- Daten/Tabellen --- Bootloader --- Ende

Zum Abschluß noch ein paar Tips :

- Nicht mit Bemerkungen sparen! Wenn man sich ein Programm nach einem,
zwei oder drei Monaten wieder ansieht um ein paar Änderungen zu machen
dann muß es entweder sehr klein sein oder gut dokumentiert (viele Infos).
Wenn beides nicht zutrifft dann ist es meist einfacher den gesamten Code
in die Tonne zu hauen und alles neu zu machen. Das ist eine Erfahrung !
Denn bevor man da wieder durchfindet hat man es auch neu geschrieben.

- Einzüge in den Programmzeilen! Sie erhöhen die Übersicht im Programm
ungemein. Einfach ab und zu mal die Tab-Taste benutzen. Schon weiß
man welches NEXT zu welchem FOR gehört und welches ENDIF zu welchem
IF. Ich mach da mal zwei Beispiele (der Code interessiert dabei nicht)....


CodeBox BASCOM

' ===== UNUEBERSICHTLICH =====
For D = 1 To One_wire_bc
1wreset
Waitms 200
Print Err
1wwrite &H33
$one_wire_sn_string(1) = " "
For C = 1 To 8
One_wire_sn(c) = 1wread()
Mid($one_wire_sn_string(1) , C , 1) = Chr(one_wire_sn(c))
Next
Next


' ===== SO SOLLTE ES SEIN =====
For D = 1 To One_wire_bc
1wreset
Waitms 200
Print Err
1wwrite &H33
$one_wire_sn_string(1) = " "
For C = 1 To 8
One_wire_sn(c) = 1wread()
Mid($one_wire_sn_string(1) , C , 1) = Chr(one_wire_sn(c))
Next
Next

Auch wenn der Code in beiden Beispielen identisch ist sieht man im zweiten
Beispiel wesentlich besser was in welcher Schleife ausgeführt wird und wie
es ineinandergeschachtelt ist. Da sollte man sich dran halten.

- Versionen anlegen! Wenn man mal was ausprobiert und auf einmal läuft
garnix mehr. Wie war es denn vorher wie es noch lief ? Tja, zu spät. Alles
nur noch Programm-Müll. Also müssen Versionen her. Entweder man nimmt
einfach das Datum in umgedrehter Form .... 100125 zB für den 25.10.2010 ...
das kann dann zB so aussehen .... Testprogramm_100125.bas
Wenn das nicht ausreicht kann man da ja noch Buchstaben hintersetzen.
zB so ... Testprogramm_100125a.bas Wenn einem 26 Versionen
am Tag reichen. Oder man macht es mit den Versionen so wie die Großen ...
Testprogramm_v17.3.1a.bas oder so.

Das war jetzt mal eine kleine Einführung von mir zur Programmierung. Das
mag für manche evtl nicht der Weißheit letzter Schluß gewesen sein. Man
kann auch bestimmt noch das eine oder andere verbessern, verändern oder
an andere Situationen anpassen. Aber diese Vorgehensweise ist aus meinen
Erfahrungen so entstanden und im Großen und Ganzen bin ich damit eigentlich
immer recht gut gefahren.

Also viel Spaß beim coden und proggen :D :cool:

Gruß
Dino
 

Über uns

  • Makerconnect ist ein Forum, welches wir ausschließlich für einen Gedankenaustausch und als Diskussionsplattform für Interessierte bereitstellen, welche sich privat, durch das Studium oder beruflich mit Mikrocontroller- und Kleinstrechnersystemen beschäftigen wollen oder müssen ;-)
  • Dirk
  • Du bist noch kein Mitglied in unserer freundlichen Community? Werde Teil von uns und registriere dich in unserem Forum.
  •  Registriere dich

User Menu

 Kaffeezeit

  • Wir arbeiten hart daran sicherzustellen, dass unser Forum permanent online und schnell erreichbar ist, unsere Forensoftware auf dem aktuellsten Stand ist und der Server regelmäßig gewartet wird. Auch die Themen Datensicherheit und Datenschutz sind uns wichtig und hier sind wir auch ständig aktiv. Alles in allem, sorgen wir uns darum, dass alles Drumherum stimmt :-)

    Dir gefällt das Forum und unsere Arbeit und du möchtest uns unterstützen? Unterstütze uns durch deine Premium-Mitgliedschaft!
    Wir freuen uns auch über eine Spende für unsere Kaffeekasse :-)
    Vielen Dank! :ciao:


     Spende uns! (Paypal)