Hilfe - mein (alter) Assembler-Code lässt sich nicht kompilieren

Das Problem

Microchip hatte 2020 entschieden, den (alten) MPASM nicht mehr zu unterstützen bzw. auch nicht weiterzuentwickeln. 

The MPASM™ assembler toolchain is a 32-bit application and is no longer installed with MPLAB® X IDE starting with v5.40. This means no toolchain will be available by default when MPLAB X IDE is installed for the first time.

Aktuell habe ich die Version „MPLAB X IDE v6.25“ installiert. Diese IDE bringt einen XC8 Compiler mit, der mit dem im XC8 enthaltenden Assembler (pic-as) auch Assembler-Code übersetzt. Diese bzw. die zukünftigen Versionen mögen neuere Controller unterstützen, jedoch haben viele Hobby-Elektroniker zumeist kleinere Programme für noch ältere Controller geschrieben, die sich nun nicht mehr kompilieren lassen. Man hat nun zwei Möglichkeiten:

  1. Installation von MPLABx 5.35, wenn man dort den (alten) MPASM nutzen möchte.
  2. Den (alten) Source-Code anpassen, so dass dieser vom XC8 in MPLAB X IDE v6.25 übersetzt wird.

Für beide Wege gibt es in Foren Lösungsansätze. Auch sind Tools vorgeschlagen, die den alten in neuen Assemblercode portieren, mit unterschiedlichen Erfolg. Geht man jedoch den ersten Weg und nutzt weiterhin die alten Tools, verpasst man vielleicht die Chance sich in neuere Tools einzuarbeiten und die Option neuere Controllern einsetzen zu können. Ich halte den Aufwand für die Portierung des alten MPASM Codes insbesondere für Hobby-Elektroniker für vertretbar und habe mich auch entschieden, den alten Code für den neuen Assembler XC8 (pic-as) zu portieren. Sämtlichen Code, den ich auf diesen Seiten veröffentliche, ist auch mit der Entwicklungsumgebung „MPLAB X IDE v6.25“ übersetzbar. 

Portierung des alten MPASM Assemblercodes für XC8 (pic-as)

Hier ein paar wesentliche Beispiele von notwendigen Anpassungen des alten Assemblercodes. Dazu das folgende Listing, welches angepasst werden sollte. Das abgebildete Programm erfüllt keinen (technischen) Zweck und dient nur der Veranschaulichung, was man im alten Listing verändern muss.

;******************************************************************************
	LIST		P=PIC16F1826   	;directive to define processor
	#include	<p16f1826.inc> 	;processor specific variable definitions
;******************************************************************************
;     configuration bits
    __CONFIG    _CONFIG1, _FOSC_INTOSC & _CLKOUTEN_OFF & _WDTE_OFF & _MCLRE_ON
;******************************************************************************
#define	THRESHOLD_VALUE		D'292'
Timer0_500_ms		equ	(.255-.243)
Time_02Minute		equ	.30
AD_CONVERSION_DONE	equ 0
;******************************************************************************
RAM_Bank0	UDATA	0x20	; 80 Bytes RAM in Bank0 
GN_GSR0_L		RES	1
GN_GSR0_H		RES	1
;******************************************************************************
RAM_Bank1	UDATA	0xA0	; 80 Bytes RAM in Bank1 
CONTROL_xx		RES	1
;******************************************************************************
RAM_Bank2	UDATA	0x120	; 80 Bytes RAM in Bank2
CONTROL_xxx		RES	1
;******************************************************************************
SharedBank	UDATA_SHR	; 16 Bytes RAM that are shared across all RAM banks
CONTROL_FLAGS		RES	1
FOTOWID_H		RES	1
FOTOWID_L		RES	1
TMR0_500MS		RES	1
TMR1_4SEC		RES	1
;******************************************************************************
;EEPROM data
; Data to be programmed into the Data EEPROM is defined here
DATA_EEPROM	CODE    0xf00000
        DE	"Test Data", 0, 1, 2, 3, 4, 5
;******************************************************************************
;Reset vector
; This code will start executing when a reset occurs.
RESET_VECTOR	CODE	0x0000
		goto 		Start
		nop
;******************************************************************************
; interrupt vector
; This code will start executing when a interrupt occurs 
INT_VECTOR	CODE	0x0004
CheckIntTMR0
		btfss		INTCON, TMR0IF
		bra		CheckIntTMR1		; jump to the next routine
		; interrupt for timer0 occurred
		bcf		INTCON, TMR0IF
		incf		TMR0_500MS, F
		movlw		Timer0_500_ms
		banksel		TMR0				; bank0
		movwf		TMR0
		bra		EnableINTandReturn
CheckIntTMR1
		banksel 	PIR1				; bank0
		btfss		PIR1, TMR1IF
		bra		EnableINTandReturn
		; interrupt for Timer1 occurred
		bcf		PIR1, TMR1IF
		;  ....
EnableINTandReturn
		bsf		INTCON, GIE	
 		retfie

;******************************************************************************
;Start of main program
Start
		; Configure oscillator
		movlw   	b'00111010'
		movwf   	OSCCON 
		banksel		OPTION_REG		; bank1
		movlw		B'00000111'
		movwf		OPTION_REG
		;  ....
SwitchValve
		clrf		TMR1_4SEC
WaitLongTime
		btfsc		CONTROL_FLAGS, AD_CONVERSION_DONE
		call        	SubRoutine
		movf		TMR1_4SEC, W
		sublw		Time_02Minute
		btfsc		STATUS, C
		bra		WaitLongTime
		bra		SwitchValve

;******************************************************************************
SubRoutine
		bcf		CONTROL_FLAGS, AD_CONVERSION_DONE
		movlw   	low(THRESHOLD_VALUE)
		subwf		FOTOWID_L,W
		movlw		high(THRESHOLD_VALUE)
		subwfb		FOTOWID_H, W
		return

Für die gelb hinterlegten Zeilen wird der XC8 (pic-as) Assembler Fehler ausgeben. Auch wenn dieses Programm-Listing bzw. die fehlerhaften Zeilen einen enormen Aufwand für die Portierung suggerieren, ist der Aufwand relativ überschaubar. Aber der Reihe nach hier einige wichtige Anpassungen:
  • LIST        P=PIC16F1826
    Der Controller wird in den Projekteinstellungen der MPLAB X IDE angegeben, daher kann diese Directive hier entfallen.
  • #include    <p16f1826.inc>
    In der MPLAB X IDE gibt es keine solche Datei. Die Controller-spezifischen Definitionen befinden sich (unter Linux) im Verzeichnis /opt/Microchip/v6.25/packs/Microchip/PIC12-16F1xxx_DFP/1.7.242/xc8/pic/include/proc/.. In diesem Verzeichnis nach der richtigen Include-Datei (pic16f1826.inc) suchen und entsprechend im Listing ändern. Diese include-Anweisung nach der Include-Anweisung für xc.inc einfügen. Siehe dazu auch den nächsten Punkt!
  • __CONFIG    _CONFIG1, _FOSC_INTOSC & _CLKOUTEN_OFF & _WDTE_OFF & _MCLRE_ON
    In der MPLAB X IDE werden die Configurations-Bits mit Hilfe eines Tools generiert. Dazu im Menü unter Windows->Target Memory Views -> Configuration Bits dieses Tool aufrufen, die Configuration definieren und danach die Schaltfläche „Generate Source Code to Output“ drücken. Den so genierten Code kopieren und in das Listing einfügen (mit der include-Anweisung xc.inc).
  • (Dezimale) Zahlenangaben sind falsch wie .30 oder D'292'
    Standardmäßig interpretiert MPASM numerische Zahlen als hexadezimale Zahlen. XC8 dagegen als dezimale Zahlen. Daher diese Definition ohne Zusatz definieren also 30 oder 292
  • (Binäre) Zahlenangaben sind falsch wie b'00111010'
    Die Notation lautet jetzt 0b00111010
  • RAM_Bank0    UDATA    0x20 erzeugt einen Syntax-Fehler
    Im MPLAB X IDE kann man Code-Segmente definieren. Anstelle obiger Anweisung ist hier psect udata_bank0, class=BANK0, space=SPACE_DATA, noexec, size=80 zu schreiben. XC8 definiert einige Code-Segmente, die am Ende der Controller-spezifischen Datei pic16f1826.inc auch definiert worden sind. Man kann in dieser Datei nachschauen und sich die richtige Anweisung von dort herauskopieren. Zum Beispiel finden sich in der Include-Datei pic16f1826.inc für den PIC16F1826 folgende Zeilen:
    psect udata_shr,class=COMMON,space=SPACE_DATA,noexec
    psect udata,class=RAM,space=SPACE_DATA,noexec
    psect udata_bank0,class=BANK0,space=SPACE_DATA,noexec
    psect udata_bank1,class=BANK1,space=SPACE_DATA,noexec
    psect udata_bank2,class=BANK2,space=SPACE_DATA,noexec
    psect code,class=CODE,space=SPACE_CODE,delta=2
    psect data,class=STRCODE,space=SPACE_CODE,delta=2,noexec
    psect edata,class=EEDATA,space=SPACE_EEPROM,delta=2,noexec
    Zusätzlich gebe ich mit size=80 die maximale Größe (wenn sinnvoll) für dieses Segment an. Der Compiler warnt mich, sollte ich diese Größe überschreiten.
  • Alle Labels wie Start oder SubRoutine müssen jetzt am Ende einen Doppelpunkt haben, also Start: oder SubRoutine:
  • ORG-Anweisungen beziehen ich beim XC8 immer auf den Anfang eines Code-Segments und nicht auf die Adresse 000h. Daher sind, wenn diese notwendig sein sollten, entsprechend zu korrigieren. Oft können diese durch die Definition der Segmente entfallen. Hat ein Code-Segment einen Namen bekommen, so muss dem Linker mitgeteilt werden, wo dieses Segement platziert werden soll. Zum Beispiel werden zwei Segment folgendermaßen definiert:
    PSECT resetVec, class=CODE, delta=2, size=4h
    PSECT interruptVector, class=CODE, space=SPACE_CODE, delta=2
    Das Segment mit dem Namen resetVec hat eine Größe von 4 (Programmspeicherplätze), bevor das nächste Segment interruptVector auf der Adresse 04h beginnen muss (siehe Spezifikation PIC16F1824). Dem Linker werden mit folgender Option diese Adressen mitgeteilt:
    -presetVec=0h
    -pinterruptVector=04h
    Dies erfolgt in der Entwicklungsumgebung unter Project-Properties -> Conf:[default] -> pic-as Global Option -> pic-as Linker -> Custom linker options
  • Zeilen in der Form GN_GSR0_L        RES    1 haben zwei Fehler
    Zum einen fehlt der Doppelpunkt am Ende des Labels und zum zweiten muss RES mit DS ersetzt werden. Die Zeile lautet also richtig: GN_GSR0_L:        DS    1
  • Zeichenketten "Test Data" lassen sich im XC8 nicht definieren. Die Zeichen müssen einzeln definiert werden 'T','e','s','t',' ','D','a','t','a'.
  • Entsprechend der Definitionen in pic16f1826.inc ist in der Zeile btfss INTCON, TMR0IF mit TMR0IF nicht mehr als das 2. Bit (vom Register INTCON) definiert. 
    In pic16f1826.inc sind folgende (relevante) Definition vorhanden:
    INTCON_TMR0IF_POSN  equ 0002h
    INTCON_TMR0IF_POSITION  equ 0002h
    #define TMR0IF  BANKMASK(INTCON), 2
    Entsprechend muss die Zeile nun lauten: Entweder btfss    TMR0IF oder btfss INTCON, INTCON_TMR0IF_POSITION
    Hier versteckt sich eine gewisser Aufwand, da an vielen Stellen die Definitionen in der pic16f1826.inc nicht mehr zum alten MPASM passen. Mit Suchen und Ersetzen ist der Aufwand aber wieder vertretbar.
  • Eigentlich behauptet Microchip im MPASMTM to MPLAB® XC8 PIC® Assembler Migration Guide dass Assembler-Source-Dateien die Endung .s oder .S haben sollten. Es scheint aber auch mit der Erweiterung .asm zu funktionieren. Der Dateiname bzw. die Erweiterung muss also nicht umbenannt werden.

Wenn man all diese Stellen im alten Source-Code ersetzt, dann ergibt sich für das oben genannte Listing folgender Code (der vom XC8 (pic-as) auch übersetzt wird):

;******************************************************************************
;     configuration bits
; CONFIG1
  CONFIG  FOSC = INTOSC         ; Oscillator Selection (INTOSC oscillator: I/O function on CLKIN pin)
  CONFIG  WDTE = OFF            ; Watchdog Timer Enable (WDT disabled)
  CONFIG  PWRTE = OFF           ; Power-up Timer Enable (PWRT disabled)
  CONFIG  MCLRE = ON            ; MCLR Pin Function Select (MCLR/VPP pin function is MCLR)
  CONFIG  CP = OFF              ; Flash Program Memory Code Protection (Program memory code protection is disabled)
  CONFIG  CPD = OFF             ; Data Memory Code Protection (Data memory code protection is disabled)
  CONFIG  BOREN = OFF           ; Brown-out Reset Enable (Brown-out Reset disabled)
  CONFIG  CLKOUTEN = OFF        ; Clock Out Enable (CLKOUT function is disabled. I/O or oscillator function on the CLKOUT pin)
  CONFIG  IESO = OFF            ; Internal/External Switchover (Internal/External Switchover mode is disabled)
  CONFIG  FCMEN = OFF           ; Fail-Safe Clock Monitor Enable (Fail-Safe Clock Monitor is disabled)

; CONFIG2
  CONFIG  WRT = OFF             ; Flash Memory Self-Write Protection (Write protection off)
  CONFIG  PLLEN = ON            ; PLL Enable (4x PLL enabled)
  CONFIG  STVREN = ON           ; Stack Overflow/Underflow Reset Enable (Stack Overflow or Underflow will cause a Reset)
  CONFIG  BORV = LO             ; Brown-out Reset Voltage Selection (Brown-out Reset Voltage (Vbor), low trip point selected.)
  CONFIG  DEBUG = OFF           ; In-Circuit Debugger Mode (In-Circuit Debugger disabled, ICSPCLK and ICSPDAT are general purpose I/O pins)
  CONFIG  LVP = OFF             ; Low-Voltage Programming Enable (High-voltage on MCLR/VPP must be used for programming)

// config statements should precede project file includes.
#include <xc.inc>
#include <p16f1826.inc>
  
;******************************************************************************
#define	THRESHOLD_VALUE		292
Timer0_500_ms		equ	255-243
Time_02Minute		equ	30
AD_CONVERSION_DONE	equ	0		
;******************************************************************************
psect udata_bank0, class=BANK0, space=SPACE_DATA, noexec, size=80
GN_GSR0_L:		DS	1
GN_GSR0_H:		DS	1
;******************************************************************************
psect udata_bank1, class=BANK1, space=SPACE_DATA, noexec, size=80
CONTROL_xx:		DS	1
;******************************************************************************
psect udata_bank2, class=BANK2, space=SPACE_DATA, noexec, size=80
CONTROL_xxx:		DS	1
;******************************************************************************
psect udata_shr, class=COMMON, space=SPACE_DATA, noexec, size=16
CONTROL_FLAGS:		DS	1
FOTOWID_H:		DS	1
FOTOWID_L:		DS	1
TMR0_500MS:		DS	1
TMR1_4SEC:		DS	1
;******************************************************************************
;EEPROM data
; Data to be programmed into the Data EEPROM is defined here
PSECT eEPROM, class=EEDATA, delta=2, space=3, noexec, size=256
        DB	'T','e','s','t',' ','D','a','t','a', 0, 1, 2, 3, 4, 5
;******************************************************************************
;Reset vector
; This code will start executing when a reset occurs.
PSECT resetVec, class=CODE, delta=2, size=4h 
		goto 		Start
		nop
;******************************************************************************
; interrupt vector
; This code will start executing when a interrupt occurs 
PSECT interruptVector, class=CODE, space=SPACE_CODE, delta=2
CheckIntTMR0:
		btfss		TMR0IF
		bra		CheckIntTMR1		; jump to the next routine
		; interrupt for timer0 occurred
		bcf		TMR0IF
		incf		TMR0_500MS, F
		movlw		Timer0_500_ms
		banksel		TMR0				; bank0
		movwf		TMR0
		bra		EnableINTandReturn
CheckIntTMR1:
		banksel 	PIR1				; bank0
		btfss		TMR1IF
		bra		EnableINTandReturn
		; interrupt for Timer1 occurred
		bcf		TMR1IF
		;  ....
EnableINTandReturn:
		bsf		GIE	
 		retfie

;******************************************************************************
;Start of main program
Start:
		; Configure oscillator
		movlw   	0b00111010
		movwf   	OSCCON 
		banksel		OPTION_REG		; bank1
		movlw		0b00000111
		movwf		OPTION_REG
		;  ....
SwitchValve:
		clrf		TMR1_4SEC
WaitLongTime:
		btfsc		CONTROL_FLAGS, AD_CONVERSION_DONE
		call        	SubRoutine
		movf		TMR1_4SEC, W
		sublw		Time_02Minute
		btfsc		CARRY
		bra		WaitLongTime
		bra		SwitchValve

;******************************************************************************
SubRoutine:
		bcf		CONTROL_FLAGS, AD_CONVERSION_DONE
		movlw   	low(THRESHOLD_VALUE)
		subwf		FOTOWID_L,W
		movlw		high(THRESHOLD_VALUE)
		subwfb		FOTOWID_H, W
		return