Overzichtelijk programmeren in MicroChip Assembles

Hoi, kan iemand zich in onderstaande zelfopgestelde basisregels vinden? Eventuele ideën of verbeteringen zijn altijd welkom. Tis omdat het redelijk overzichtelijk werkt in assembler ;)

Groeten, Wiebo


Tips bij het Programmeren van PIC's in ASSEMBLER:

;**************************************************************************
; 1) Om een overzichtelijk programma te schrijven:
;**************************************************************************
- Vermeld labels, File registers, Macro's en bestemmingen in Hoofdletters en enkel en
  alleen de Instructies in kleine letters => HOOFD klein HOOFD, H
  Zet ook niets meer achter een label, maar ga verder op de regel er vlak onder.
  vb.	MAIN_PROG				; Main Program
		movfw	REGISTER_1		; Commentaar
		addwf	TIMER,		W	; Commentaar
		movwf	COUNTER			; Commentaar
	LOOP					; Looping
		decfsz	COUNTER,	F	; Commentaar
		goto	LOOP			; Commentaar
		...

- Teveel labels is ook weer onoverzichtelijk. In voorgaande voorbeeld kan het Label 'LOOP'
  weggelaten worden mbv het 'ThisLine'-Teken => $
  Dit teken staat voor de waarde die in de program counter staat voor toegang tot de op
  dat moment uitgevoerde programmaregel. Er kan een waarde aan toegevoegd worden of
  afgetrokken om zo regels terug of vooruit te springen, maar wordt best gebruikt voor
  uitsluitend sprongen van max. enkele regels. Bovenstaand voorbeeld wordt
  dan als volgt:
  vb.	MAIN_PROG				; Main Program
		movfw	REGISTER_1		; Commentaar
		addwf	TIMER,		W	; Commentaar
		movwf	COUNTER			; Commentaar
		decfsz	COUNTER,	F	; Commentaar
		goto	$ - 1			; Spring 1 regel terug
		...

- Gebruik een <TAB> tussen ieder statement in een lijn, best dus geen spaties. Hierdoor komt
  alles netjes in een kolom staan en blijft alles in een oogopslag leesbaar.
  (Zie bovenstaand voorbeeld)

- Gebruik als bestemming voor 'Wreg' of 'File' niet waarden '1' en '0', maar de effectieve
  namen 'W' en 'F'. Vermeld steeds de bestemming, reken niet op de default waarden.
  bv.	addwf FILE, 0 => addwf FILE, W   (Zie bovenstaand voorbeeld)

- Vervang de instructie "movf FILE, W" door "movfw FILE" Dit is veel overzichtelijker omdat
  de instructie movwf ook bestaat en werkt ook op de 16F-serie instructieset.

- Plaats in een Label of Registernaam die uit 2 woorden bestaat een underscore '_'
  bv.	PICINIT = PIC_INIT (Pic Initialization)
	MAINPROG = MAIN_PROG (Main Program)
  (Zie bovenstaand voorbeeld)

- Plaats tussen iedere blok met een functie een hoofding met de naam van die functie
  bv.	;***************************************************************
	; PIC_INIT
	;***************************************************************

- Bij lookup tabellen die tekst bevatten kan je best het 'dt'-statement gebruiken in
  combinatie met NULL. Dit verkort je programma aanzienlijk. De HEX-file die gegenereerd
  wordt blijft onveranderd. De hele reeks van retlw-instructies valt wel weg, wat de
  duidelijkheid weer ten goede komt.
  vb.	retlw A'V'	wordt:	dt "Versie 1.00"
	retlw A'e'		retlw NULL
	retlw A'r'
	retlw A's'
	retlw A'i'
	retlw A'e'
	retlw A' '
	retlw A'1'
	retlw A'.'
	retlw A'0'
	retlw A'0'
	retlw NULL
  De NULL heeft de absolute of wiskundige '0'-waarde. Deze wordt bij iedere return getest.
  Keerde de lookup tabel NULL terug, dan betekend dat het einde van die tekst. De tekst
  kan dus zonder problemen aangepast worden, en op deze manier speelt zelfs de lengte in
  de meeste gevallen geen rol meer.

- En last but nog least: Voorzie je programma van genoeg commentaar! Beter te veel dan
  te weinig!

;**************************************************************************
; 2) Om program memory te sparen:
;**************************************************************************

- Bij timingsgevoelige applicaties die meer dan 1 'nop' vereisen, gebruik per 2 'nop's
  de instructie "goto $ + X" Deze vereist ook 2 cycli en gebruikt minder program memory.
  bv.	nop  	wordt:	goto $ + 1
	nop		goto $ + 1
	nop
	nop

;**************************************************************************
; 3) Om toekenningen overzichtelijker te maken:
;**************************************************************************

- Ga niet 2x registers een naam toekennen, maar gebruik steeds de headerfile die Mirochip
  levert voor ieder type processor en voeg deze toe voor het begin van het programma:
  bv. '#include P16F628A.INC'
  De gebruikte registers nog eens in het begin van het programma een naam toekennen is
  zinloos.

- Om bits een naam toe te kennen, gebruik steeds het '#DEFINE'-statement, en stel dus
  niet een bitnaam gelijk aan een registernaam, zoals onderstaand voorbeeld:
  bv. Op portB pin 5 is een LED aangesloten. Soms wordt dit genoteerd als 'LED equ 0x06'
  of 'LED equ PORTB' en moet men dus in programma volgende regel gebruiken wil men deze
  bit testen of van waarde veranderen:
  => bsf LED, 5
  Dit is nogal vervelend bij grote programma's, men moet dus permanent onthouden op welke 
  port-pin de LED is aangesloten. Wil men de LED achteraf op een andere pin gebruiken
  moet men het hele programma afzoeken om de regels te wijzigen waarin dit voorkomt.
  Veel beter is: #DEFINE LED PORTB, 5
  => bsf LED
  Wil men de LED nu op een andere pin gebruiken, zoals PORTB,2 dan moet men alleen de
  waarde veranderen in de definitie van de LED => #DEFINE LED PORTB, 2
  De compiler doet de rest ;o)

tja.. .. ik heb het uitgeprint...

Ik ga me binnekort echt verdiepen in PIC programmeren,(eerst verhuizen)
en vond dit wel super handige tips en uitleg...

Had dit idd nog niet gezien.

Goeie stuff man
* Gebruik van CBLOCK ENDC met daartussen al je variabelen

	CBLOCK 0x20
	REGISTER_1
	TIMER
	COUNTER
	ENDC

* mee programmeren van de fuses in begin programma

	; je kan hiermee de juiste instellingen meegeven aan de programmer
	__CONFIG _CP_OFF & _WDT_OFF & _PWRTE_ON & _INTRC_OSC_NOCLKOUT & _LVP_OFF & _BODEN_OFF

* macro's voor veel gebruikte functies


;===============================================================;
; IRLEQVT (IF_REG_LESS_EQUAL_VAL_THEN)		 			;
;===============================================================;

IRLEQVT	MACRO	register1, value, label1
	movf	register1, w		; move contents of register1 into register W
	sublw	value 			; subtract literal 'value' from register W
	btfsc	STATUS, C	 	; check the C bit in the status register
	goto	label1			; if register1 <=  value 
	ENDM		

gebruik in code:


	IRLEQVT	COUNTER, D'10', SKIP
	;Dit wordt alleen uitgevoerd als COUNTER > 10
SKIP	;Dit wordt altijd uitgevoerd

Je kan de macro's in een include file laden. De code wordt pas gebruikt indien er een macro wordt gebruikt.

* nog een paar macro voorbeelden


BANK0	MACRO
	BCF	STATUS, RPO
	BCF	STATUS, RP1
	ENDM		

BANK1	MACRO
	BSF	STATUS, RPO
	BCF	STATUS, RP1
	ENDM		

BANK2	MACRO
	BCF	STATUS, RPO
	BSF	STATUS, RP1
	ENDM		

BANK3	MACRO
	BSF	STATUS, RPO
	BSF	STATUS, RP1
	ENDM		

maar volgens mij is er ook zoiets als banksel
* verder zijn er nog


	HIGH	label
	LOW	label

met HIGH label kan je achterhalen waar je PCLATH moet staan om corect naar een label te springen.
Dit heb je nodig als je een goto boven de 2K code wil uitvoeren of een tabel groter als 256 bytes hebt.

Wat is de toegevoegde waarde van het programmeren in Assembler boven C ? Wat levert het extra werk dat dit kost op ?

snelheid man, snelheid... :)

bvb: kan je in een hogere programmeertaal een 4-kanaals dimmer programmeren van volledig onafhankelijke kanalen, waarvan de resolutie 8 bits per kanaal is, en PIC draait op 4MHz (1 MIPS) wetende dat de netspannings nuldoorgang 100 x per seconde optreed?

1 MIPS / 4 kanalen = 0.25 MIPS of 250.000 instrukties
250.000 / 100 nuldoorgangen = 2.500 instructies
2.500 / 8-bit (256) = 39,... zoveel.

Je hebt dus 39 instructies om je volledige interrupt routine uit te voeren, en dit kan enkel door heeeeel intensief met instrukties om te gaan.

Met C kan dit zeker niet, dan moet je de processor al op minstens 10 MHz laten draaien om het teveel aan overbodige voorgekauwde routines weggewerkt te krijgen.

DAT is het grote verschil. Kan je beetje met assembler overweg, dan gaat het ook redelijk vlotjes...

Worteltje: Kende daar ook nog nie alles van, thanx!!

Groeten!

toegevoegde waarde van assembler

- je weet waar je mee bezig bent
- je processor leert kennen
- je niet afhankelijk bent van runtime libraries waarvan je niet weet hoe ze in elkaar zitten
- je niet afhankelijk bent van een crappy compiler die niks kan optimaliseren. ze gedragen zich allemaal anders die compilers. code die op de ene compiler compileert naar 10 instructies en 2 byte ram compileert met de andere naar 20 instructies en 5 byte ram.
- je zelf timing kritische code kan schrijven en op voorhand exact weet hoeveel machine cycle ze gaat duren
- je niet moet programmeren in grootste onleesbare klotesyntax die ooit bedacht is . 't zijn precies een nest wiskundig formules.
- er geen ambiguiteit is , geen ge-overloade operatoren en geen andere geinigheid die code onverstaanbaar maken. assembler is echt WHAT YOU SEE IS WHAT YOU GET.

-je programma zal kleiner zijn , minder ram nodig hebben en sneller lopen dan in C. en het gedrag is perfect voorspelbaar.

als dat geen argumenten zijn om in assembler te gaan programmeren weet ik het ook niet meer hoor.

opgelet ik spreek hier dus wel op microcontrollertjes he. Ik ga wel akkoord dat om programmas te ontwikkelen op computers die veel ram hebben , harde schijven , grafische interfaces etc, je daar een high level language gebruikt.

maar als je op een controllertje 2 k rom hebt en 64 byte ram moet je niet in C liggen klooien. T'gaat er nooit in geraken.

En een belangrijke: Gebruik bij JUMP tables altijd een test of je tabel niet over een 256 byte grens heen wrapt.
Het voorbeeldje hieronder komt uit een rotary encoder.


_jt1b		addwf	PCL,f		; bounce in jump table below
		goto	_iDone		; 00 -> 00 = no change
		goto	inc_freq	; 00 -> 01 = increment
		goto	dec_freq	; 00 -> 10 = decrement
		goto	_iDone		; 00 -> 11 = illegal, ignore
		goto	dec_freq	; 01 -> 00 = decrement
		goto	_iDone		; 01 -> 01 = no change
		goto	_iDone		; 01 -> 10 = illegal, ignore
		goto	inc_freq	; 01 -> 11 = increment
		goto	inc_freq	; 10 -> 00 = increment
		goto	_iDone		; 10 -> 01 = illegal, ignore
		goto	_iDone		; 10 -> 10 = no change
		goto	dec_freq	; 10 -> 11 = decrement
		goto	_iDone		; 11 -> 00 = illegal, ignore
		goto	dec_freq	; 11 -> 01 = decrement
		goto	inc_freq	; 11 -> 10 = increment
		goto	_iDone		; 11 -> 11 = no change
_jt1e
	IF ( (_jt1b & 0x0FF) >= (_jt1e & 0x0FF) )
		MESSG   "Warning - User Definded: JumpTable1 crosses page boundary in computed jump"
	ENDIF

Ik gebruik voor variabelen trouwens bijna altijd een Hoofdletter/Kleine letter combinatie, zoals


	CBLOCK	0x20
	OldKeyState			; Previous state of keys (used to fire events)
	Freq:  4			; FREQ updated by ISR
	FreqR: 4			; Current Receive frequency
	FreqT: 4			; Current Transmit frequency
	VfoIndex			; VFO Index (0/1)
	DdsInc: 4			; DDS Phase increment
	DdsCal: 4			; Oscillator calibration factor

Hiermee kan je de Hardware registers (SFR) makkelijk onderscheiden van je variabelen

Lokale labels (binnen een functie) prefix ik dan weer met een underscore, om ze te onderscheiden van globale labels.

Pas trouwens op met een GOTO $-1, werkt niet op een processor uit de 18F reeks (daar moet het $-2 zijn)

En zo zijn er nog veel zaken.....

@free_elektron: op een PIC doe ik bijna alles in assembly, maar de nieuwe types hebben een paar K RAM en tot 64K ROM. Dus voor een GPS-applicatie is het gebruik van 'C' wel handig ivm al het floating point rekenwerk. Dat is niet leuk is assembly (ook al heb ik het wel gedaan). Je moet dus gewoon weten WAT je in assembly moet doen (tijdkritisch) en WAT in een hogere taal kan.

Op 19 april 2005 11:05:55 schreef Wiebo:
snelheid man, snelheid... :)

Mwah... op een simpel uCtje is het idd nog wel te doen om net zo goede code te maken als een compiler, op iets complexers lukt je dat niet meer.

bvb: kan je in een hogere programmeertaal een 4-kanaals dimmer programmeren van volledig onafhankelijke kanalen, waarvan de resolutie 8 bits per kanaal is, en PIC draait op 4MHz (1 MIPS) wetende dat de netspannings nuldoorgang 100 x per seconde optreed?

Ten eerste gebruik ik geen PIC's maar AVR's , (4 Mhz = 4 MIPS) maar ook op zo'n trage PIC moet 't lukken hoor.

1 MIPS / 4 kanalen = 0.25 MIPS of 250.000 instrukties
250.000 / 100 nuldoorgangen = 2.500 instructies
2.500 / 8-bit (256) = 39,... zoveel.

Kwestie van efficient programmeren, moet zat kunnen in C.

Je hebt dus 39 instructies om je volledige interrupt routine uit te voeren, en dit kan enkel door heeeeel intensief met instrukties om te gaan.

denk dat het zat te doen is.

Met C kan dit zeker niet, dan moet je de processor al op minstens 10 MHz laten draaien om het teveel aan overbodige voorgekauwde routines te laten wegwerken.

Een 10Mhz crystal is ook zoveel duurder dan een 4Mhz crystal ... :P , dat is het echt waard om extra tijd in te steken.

DAT is het grote verschil. Kan je beetje met assembler overweg, dan gaat het ook redelijk vlotjes...

Zelfde geld voor C, ik heb in C wel software geprogged die (op een 16Mhz Atmel ARV) op de uSec nauwkeurig getimede signalen stuurt.

Op 19 april 2005 11:12:20 schreef free_electron:
- je niet moet programmeren in grootste onleesbare klotesyntax die ooit bedacht is . 't zijn precies een nest wiskundig formules.

Je bent volgens mij de eerste die het met me eens is wat dat betreft. Het enige wat me tegenhoudt om C te leren is de waanzinnig rommelige, onleesbare, en ook nog eens hypergevoelige syntax. Niet te verstaan!

Assembler daarentegen.... de vrijheid! Heerlijk!

zoiets bepaal je zelf hè, maar het lijkt me niet meer dan logisch dat je programma's die floting point getallen en dergelijke best met een high-level programmeertaal schrijft. Persoonlijk heb ik dat nog niet nodig gehad, 'k reken bijna zelf binair :)

Op 19 april 2005 11:31:52 schreef Fuzzbass:
[...]
Je bent volgens mij de eerste die het met me eens is wat dat betreft. Het enige wat me tegenhoudt om C te leren is de waanzinnig rommelige, onleesbare, en ook nog eens hypergevoelige syntax. Niet te verstaan!
Assembler daarentegen.... de vrijheid! Heerlijk!

In de C syntax worden trouwens bijna geen beperkingen aangegeven wat betreft de formattering van je code.
Dat mensen daardoor de vrijheid hebben om er een onleesbare zooi van te maken is hun verantwoording.
Trouwens wel eens code geport van assembler merk A naar assembler merk B ?, daar heb je meestal meer werk aan dan bij het switchen van compiler.

Op 19 april 2005 11:12:20 schreef free_electron:
toegevoegde waarde van assembler

- je weet waar je mee bezig bent

Weet ik prima

- je processor leert kennen

Geld ook voor low-level C proggen

- je niet afhankelijk bent van runtime libraries waarvan je niet weet hoe ze in elkaar zitten

Het gebruik hiervan is zeer minimaal.

- je niet afhankelijk bent van een crappy compiler die niks kan optimaliseren. ze gedragen zich allemaal anders die compilers. code die op de ene compiler compileert naar 10 instructies en 2 byte ram compileert met de andere naar 20 instructies en 5 byte ram.

Een compiler kan beter optimaliseren dan je met de hand kan, zeker op complexere (superscalar) architecturen met lange pipelines.

- je zelf timing kritische code kan schrijven en op voorhand exact weet hoeveel machine cycle ze gaat duren

Ik heb erg timing-kritische code geschreven in C (op de uSec nauwkeurig), geen probleem

- je niet moet programmeren in grootste onleesbare klotesyntax die ooit bedacht is . 't zijn precies een nest wiskundig formules.

Daarover verschillen we dan van mening, C code is heel erg leesbaar, je ziet aan variabelen en functies aan de naam wat ze doen, als je assembler code onder de neus krijgt kan je wel zien wat de individuele instructies doen, maar afleiden wat de bedoeling van het geheel is is een stuk lastiger.

Maar goed, aangezien het zo leesbaar is, wat doet deze code dan: http://130.89.164.246/~aaargh/assembler ?

- er geen ambiguiteit is , geen ge-overloade operatoren en geen andere geinigheid die code onverstaanbaar maken. assembler is echt WHAT YOU SEE IS WHAT YOU GET.

Ja, je zit idd bare-metal, maar daar wordt het niet leesbaarder of handiger op.

-je programma zal kleiner zijn , minder ram nodig hebben en sneller lopen dan in C. en het gedrag is perfect voorspelbaar.

Mijn C code gedraagt zich ook perfect voorspelbaar, bovendien kan je gewoon de assembler code die uit de compiler komt bekijken (en die is erg efficient)

maar als je op een controllertje 2 k rom hebt en 64 byte ram moet je niet in C liggen klooien. T'gaat er nooit in geraken.

Hmm.. waarom is mij dat dan zat gelukt ?

Op 19 april 2005 11:31:52 schreef Fuzzbass:
[...]
Je bent volgens mij de eerste die het met me eens is wat dat betreft. Het enige wat me tegenhoudt om C te leren is de waanzinnig rommelige, onleesbare, en ook nog eens hypergevoelige syntax. Niet te verstaan!

Assembler daarentegen.... de vrijheid! Heerlijk!

De syntax is juist heerlijk strak en duidelijk.

Als je iemand die gewoon goed kan programmeren C code laat schrijven is het uiterst leesbaar.

Op 19 april 2005 11:48:07 schreef JoWi:
Trouwens wel eens code geport van assembler merk A naar assembler merk B ?, daar heb je meestal meer werk aan dan bij het switchen van compiler.

Wat dacht je van architectuur A naar architectuur B, kan je al je code herschrijven.

Het nadeel van assembler is dat het meestal niet compatible is met andere typen microcontrollers. Als je iets in een hogere programmeertaal schrijft zoals Taal C en VHDL kan je gemakkelijk een ander component programmeren.

Het voordeel van in assembler programmeren is dat je precies weet wanneer iets gebeurt en hoelang dat gebeurt. Als je een hogere programmeertaal iets laat omzetten, krijg je meestal een grotere programmacode dan met assembler.

Let wel, de grafische effecten in computergames (vooral vroeger) werden hoofdzakelijk in assembler geschreven omdat de compiler van een hogere programeertaal een te langzaam programma leverde.

Op 19 april 2005 11:51:38 schreef Aaargh!:
[...]
Wat dacht je van architectuur A naar architectuur B, kan je al je code herschrijven.

Been there, done that :-)
Was een port van 68000 code (16 universele 32bit registers) naar 80286 (4 dedicated 16 bit registers). Maar aangezien PC hardware een kwart van de prijs was van Multibus hardware (DMA controllers, GPIB controllers en noem maar op wat borden we gebruikten) toch wel (economisch gezien) handig.
Gelukkig was alles op wat drivers en high-performance stuff na in C geschreven. Helaas was een int van 32 bit, 16 bit geworden, dus je kon wel even alles nalopen. (En vanaf dat moment maar wat vaker de longs en shorts gebruikt)

Oops, dubbelpost

[Bericht gewijzigd door JoWi op ]

Op 19 april 2005 12:12:53 schreef JoWi:
Helaas was een int van 32 bit, 16 bit geworden, dus je kon wel even alles nalopen. (En vanaf dat moment maar wat vaker de longs en shorts gebruikt)

Vandaar ook specifieke types gebruiken, dus geen int maar uint8_t, uint16_t, uint32_t, etc.

Op 19 april 2005 12:02:06 schreef Onbekend:
Let wel, de grafische effecten in computergames (vooral vroeger) werden hoofdzakelijk in assembler geschreven omdat de compiler van een hogere programeertaal een te langzaam programma leverde.

Tegenwoordig is de compiler beter dan de mens, zeker op de complexe cpu's die in PC's en dergelijke zitten.

Er zijn voorbeelden van assembler code die er stukken efficienter en korter uitziet dan de equivalente code uit een compiler, maar toch ordes van grootte trager draait. Assembler is niet meer zo eenvoudig als het ooit was (op een simpele uC zoals een AVR of PIC nog wel trouwens). Je moet rekening houden met pipelining, meerdere execution units (compile-time scheduling), caching, etc. Kijk b.v. naar de pipeline op een Pentium 4, dat ding is achterlijk lang, als je die invalidate is dat ontzettend duur.