PIC16F145x-Urlader - mehr als nur das!

Die Mikrocontroller PIC16F1454, PIC16F1455 und PIC16F1459 sind gerade für Bastler äußerst interessante Chips, da es diese sehr preiswert im Durchsteck-Gehäuse zu kaufen gibt und diese einen quarzlosen USB-Full-Speed-Devicecontroller enthalten. Mit gerade mal 2 externen Kondensatoren kann damit ein USB-Seriell-Konverter USB-standardkonform realisiert werden. Mit 8 KWort Flash kann man auch komlexere Aufgaben in C ausprogrammieren. Für 14-Bit-Core-PICs bieten diese Chips ziemlich umfangreiche Peripherie, etwa einen 16-Bit-Zeitgeber, serielle Schnittstellen (UART, SPI und I²C) sowie (nicht beim '1454) einen 10-Bit-A/D-Wandler. Außerdem einen asynchronen Quarzoszillator, gemacht für autonome Datenlogger. Wie bei allen vierstelligen PICs mit 14-Bit-Core gibt es (endlich!) eine lineare RAM-Adressierung, die die jeweils 80 RAM-Bytes in jeder einzelnen Bank ab der Adresse 0x2000 spiegelt. Und auch der Flash-Speicher ist ohne goto-Gewurstel ansprechbar und ab Adresse 0x8000 eingeblendet. (D.h. nur die Low-Bytes.)

Der Pferdefuß mit diesem Chips ist nur, dass diese nicht mit einem USB-Urlader ausgeliefert werden, sodass deren Erstkontakt mit einem Programmierinterface erfolgen muss, etwa dem PICKIT2 oder einer einfachen Bastelschaltung am Parallelport.

Deshalb gibt es im Web eine Reihe USB-Urlader, mit eher mauer Funktionalität und mieser Komatibilität, oftmals viel zu groß. Die Idealgröße des Urladers für diese Chips ist 512 Programmworte, weil diese Größe via Konfigurationsbits gesondert vor Überschreiben abgesichert werden kann. USB bekommt man da nur mit Assembler­programmierung hinein, bei Verzicht auf Interrupts sogar bequem.

Der übrig bleibende Platz in der Firmware dient schließlich dazu, einfache Aufgaben der Labormesstechnik ganz ohne eigene Firmware lösen zu können, wie das Einlesen eines Analogwertes oder die Steuerung einer digitalen Leitung. Damit erleichtert sich der Einstieg in diese Mikrocontroller ungemein: Kein Assembler, kein C-Compiler wird benötigt, um Portpins analog oder digital abzufragen oder zu setzen.

Der Einsatz eines USB-Urladers macht das Programmieren rasend schnell! Die 8 KWort sind innerhalb von 1,3 s gelöscht und neu beschrieben. Die Gesamtzeit setzt sich aus 1,1 s Flash-Zeit und 0,2 s Datentransferzeit zusammen.

Eingesetzt wird bei mir ein solcher Mikrocontroller in folgenden Projekten:

Der erweiterte Urlader

Hier ist er: Der Urlader Er läuft nun auch unter Windows. Und erzeugt nicht permanent USB-Verkehr wie die Vorlage: Diese generierte ständig 0-Byte-Pakete, anstatt die Sache mit dem Data-Toggle richtig zu imlementieren und nur dann etwas zu senden, wenn er gefragt wird.

Die beigefügte INF-Datei ist derart frisiert, dass fortan alle USB-Seriell-Konverter (u.a. alle Arduinos) von Windows ohne Treiberanfrage installert werden. Eine Bindung an USB-VID&PID erfolgt nicht.

Der Urlader ist trotzdem kleiner als das Original von der Softwaremüllhalde. Die Art der Funktion wird von der Länge des OUT-Transfers bestimmt. Das obligatorische IN-Byte (Antwort) macht zwar die Kommunikation etwas langsamer, verhindert jedoch zuverlässig, dass Softwareschichten (bspw. NI-VISA) Bytes zusammenfassen, was hier unzulässig wäre.

Liste der Funktionen des Urladers, grün die neuen Funktionen
Länge in BytesFunktionDaten (Länge Bytes)Antwort (1 Byte)
0wird ignoriertkeinekeine
1Reset: Sprung zum Anwenderprogramm = Adresse 0x0200'R'
  • 1 = okay
  • 2 = kein 'R'
2Byte lesenAdresseDatenbyte
3Byte schreiben
  • Adresse (2 Bytes)
  • Datenbyte
  • 1 = okay
4Flash-Schreiben vorbereiten und ggf. Flash-Speicherseite löschen; Flash lesen
  • Wortadresse (2 Bytes)
  • 1 Byte Daten-Prüfsumme
  • 1 Flagbyte:
    • 'E' = löschen
    • 'R' = lesen
  • 1 = okay
  • 2 = Wortadresse nicht durch 32 teilbar
  • ausnahmsweise 64 Byte Flash-Daten bei Flagbyte = 'R'
64Flash beschreiben 32 14-Bit-Worte (High-Bits 0)
  • 1 = okay
  • 3 = falsche Prüfsumme
  • 4 = Vergleichs-Fehler

Mit der Funktion Byte lesen kann man ein Byte von jedem I/O-Port, vom RAM und vom Flash lesen. Um Bytes vom Flash zu lesen, ist das Bit 15 der Adresse zu setzen. Es kann so nur das Low-Byte gelesen werden.
Achtung! Einige Portadressen haben beim Lesen Seiteneffekte!

Mit der Funktion Byte schreiben kann man ein Byte auf jedes I/O-Port und in den RAM schreiben.
Achtung! Alle Portadressen haben (logischerweise) Seiteneffekte! Aber auch das Beschreiben des RAM und von Sonderfunktionsregistern kann den Urlader durcheinanderbringen. Dann hilft nur noch ein Reset = Ab- und Anstecken des USB-Kabels. Die Urlader-Firmware im Flash-Speicher ist unzerstörbar, durch Fuses gesichert.

Die entsprechenden Portadressen und Bitbelegungen holt man sich vom Datenblatt des Mikrocontrollers. Die wichtigsten:

Einige Portadressen der PIC16F145x
NameAdresseBedeutungVorgabe
PORTA00C
  • Schreiben: Ausgangstreiber-Latch LATA
  • Lesen: Pinzustand
TRISA08C
  • 0 = Ausgabe
  • 1 = Eingabe
3B
LATA10CAusgangstreiber-Latch00
ANSELA18CAnalog-Auswahl: 0 = analog+digital, 1 = analog10
WPUA20CPullup-Enable: 0 = kein Pullup, 1 = Pullup38
OPTION_REG095Bit 7: 0 = globale Pullup-Freigabe7F

Um sich ein PIC-Programmiergerät zu ersparen, habe ich einen PC mit Parallelport aufgesucht und die Firmware mit dem Low-Voltage-Programmierinterface (also keine hohe Reset-Spannung) draufgetan. Das ist so ähnlich wie beim AVR ISP mit MOSI und MISO. Braucht man unbedingt den RESET-Anschluss als Eingang, muss man die Konfigurationsbits (AVR-Deutsch: Fuses) entsprechend ändern.

Wie kommt der Urlader in die PIC?

Am einfachsten nehme man sich einen PC mit Parallelport. Man benötigt:

Der mir überlassene GALEP-III geht dafür nicht, die Software GALEP32 kennt diesen Mikrocontroller nicht. Blöd.

Wie verwendet man den Urlader?

Da diese Mikrocontroller verhältnismäßig viel Flash-Speicher haben, wird man sie in aller Regel in C programmieren. Will man den Urlader für Anwendungen benutzen, ist folgendes zu beachten:

Änderungsprotokoll

Anwendungsprogramm

Hier ist es: Die Portpin-Fummel-Äpp mit Flash-Funktion de/en.

Das Windows-Programm ermöglicht es, mit allen Portadessen und Speicherzellen herumzuspielen sowie Firmware hochzuladen. Es läuft von Windows 98 bis mindestens Windows 10 und benötigt keine besonderen DLLs. Es ist zweisprachig.

Die Möglichkeit der Manipulation ist eher als Demo-Programm zu sehen, wie man ohne einen Strich eigene Firmware schreiben zu müssen einige alltägliche Probleme der Laborautomatisierung sehr preiswert lösen kann. So genügt es bspw. in LabVIEW, mit dem vorhandenen Knoten zum Senden und Empfangen von der seriellen Schnittstelle (seltsamerweise unter VISA zu finden) digitale Ausgänge zu steuern, einzulesen und in der Richtung umzuschalten. Ein NI USB-6008 und ein Arduino (jener leider nur mit Firmware) kann das auch, aber ein Durchsteck-Chip ohne Quarz am USB und mit einem winzigen USB-Urlader, das ist doch schon fetzig.

Außerdem kann man damit ohne Steckerstöpseln und ohne sich über die USB-Vendor- und Produkt-ID (VID&PID) einen Kopf machen zu müssen seine Firmware drauftun. Der mit den Fuses gesperrte Urlader-Bereich, der nur 116 des gesamten Flashs abzwackt, ist vor dem Überschreiben sicher. Zurück zum Urlader geht es mit dem RESET-Eingang, falls sich die Firmware mal verhakt. (Dieser Eingang steht fürs Anwendungsprogramm erst mal nicht zur Verfügung.)

Beim Drücken auf „Programmieren“ wird die angegebene HEX-Datei stets neu eingelesen. Damit kann man das Fenster immer offen stehen lassen. Emuliert das Anwendungsprogramm keine serielle Schnittstelle via USB, wird bei „Anschluss“ umgehend nichts angezeigt. Hervorholen lässt sich das COM-Port durch Reset des Mikrocontrollers:

Die aktuelle Version kann auch mit Kommandozeile (Hex-Dateiname) gestartet werden, dann wird diese Datei geflasht. Die angeschlossene PIC wird automatisch erkannt. Zudem zeigt sie 16-Bit-Werte 16-bittrig an, etwa FSR. Nicht vom Lesezugriff abhängige Register (d.h. die meisten) werden permanent ausgelesen. So kann man beim Beobachten von PORTC den Pegel der Eingänge verfolgen. Nicht vergessen, ANSELC muss dazu vorher auf 0 gesetzt werden.

Änderungsprotokoll

SDCC zügeln

Der freie C-Compiler ist für PICs mit 14 Bit Wortbreite wie dem PIC16F1459 nicht allzu eingängig verwendbar. Es müssen eine Reihe von Hürden genommen werden, um ein brauchbares Kompilat sowie die Kontrollmöglichkeit eines gut lesbaren Disassemblerlistings zu bekommen, was mit avr-gcc kein Problem darstellt. Zunächst geht es um die Kontrolle des Startups. Ist dieser Teil verstanden, geht es um die Verschiebung des Kodes um die Größe des Urladers von 512 Words.

Gemeinsam mit SDCC benötigt man GPUTILS. Möchte man keine PIC18 bearbeiten, kann man getrost alle Dateien mit dem Muster "p18*" und "pic18*" löschen, um den genutzten Festplattenplatz zu halbieren.

Ich habe Umgebungsvariablen so gesetzt.

Beim Umstieg von acr-gcc auf SDCC sind folgende Programmier-Eigenheiten zu beachten:

Unterschiede beim avr-gcc- und SDCC-Quelltext
avr-gccSDCCAnmerkung
PORTB = 42;
LATB = 42;	//oder
PORTB = 42;
Byte-Ausgabe: Andere, verwirrende Namen für dasselbe Ding
byte x = PINB;
byte x = PORTB;
Byte-Eingabe: Andere, verwirrende Namen für dasselbe Ding;
SDCC kann kein C99, keine Variablendeklaration mittendrin
DDRB |= 1<<bitnum;
TRISB &= ~bitmask;
Bit-Definitionen bei PIC als Maske, nicht als Bitnummer
DDRB |= 1<<bitnum;
TRISB7 = 0;
Einzelbit-Zugriff; Tristate-Bits andersherum
DIDR0 = 0;
ANSELA = 0;
Digitaleingänge sind bei PIC nach Reset abgeschaltet
sleep_cpu()
__asm__("sleep");
Bei PIC wird stets die Peripherie mit abgeschaltet!

Eigener Startup-Kode

Um den Startup-Kode zu minimieren oder zu entfernen, muss man an der Zwischen-Assemblerdatei herumfummeln. Es geht wirklich nicht besser! Dort habe ich die Info her. Im Beispiel:

sdcc -mpic14 -p16f1459 --use-non-free -S main.c
sed -e "/^STARTUP/./goto/ d" main.asm > main.a14

Die erste Zeile compiliert die C-Datei zu einer Assemblerdatei (Option "-S"). Diese enthält die folgenden Zeilen:

STARTUP	code 0x0000
	nop
	pagesel __sdcc_gsinit_startup
	goto	__sdcc_gsinit_startup

Tatsächlich enthält also die relozierbare Assemblerdatei "main.asm" einen nicht-relozierbaren Bereich. Dieser muss weg und kann durch einen eigene Startup-Datei ersetzt werden. Das erledigt das sed-Kommando. Dabei ermöglicht die Endung "a14" das Einrichten von Text-Editoren für spezifische Architekturen. sed und awk sind bereits auf dem Computer, wenn man winavr installiert hat: Kleine Utilities (= EXE-Dateien), die keiner Installation bedürfen. Angenehm!

Das nop am Anfang ist für den Debugger reserviert. Benötigt man es nicht, wird an der Adresse 0x0002 Platz für einen weiteren Einsprung, was in Verbindung mit dem obigen Urlader interessant wird. Dazu siehe unten.

Das Programm ohne Startup-Kode kann immer noch lauffähig sein! Allerdings nur wenn es ausschließlich aus main() besteht. Initialisierungsaufgaben werden nicht ausgeführt: Statische Variablen werden nicht initialisiert oder auf Null gesetzt. Ein Unterprogramm würde vor main() aufgerufen und beim anschließenden return abstürzen. Daher ist ein eigener Startup-Kode günstig:

STARTUP code 0
	extern	_main
	pagesel _main
	goto	_main
	end

So wird an Adresse Null ein Sprung zu main() realisiert, egal wo die Funktion im Flash liegt. (pagesel ist einer der neuen Befehle der Enhanced-PICs: Compiliert zu movlp adr>>8, setzt PCLATH.) Das ist schon sehr minimalistisch.

Beide Assemblerdateien werden wie folgt zu einer HEX-Datei zusammengeführt:

Die Option "-C" unterdrückt _cinit-Warnungen. Die Prozessorangabe wird bei "main.a14" nicht benötigt, dort steht sie im Quelltext. Umgebungsvariablen müssen folgendermaßen gesetzt sein:

set SDCC_HOME=c:/programs/pic/sdcc
set GPUTILS_HEADER_PATH=%SDCC_HOME%/header
set GPUTILS_LIB_PATH=%SDCC_HOME%/non-free/lib/pic14
set GPUTILS_LKR_PATH=%SDCC_HOME%/lkr

Disassembler-Listing

Jetzt wäre eine brauchbare Listing-Datei zur Kontrolle hilfreich. gpdasm muss man etwas auf die Sprünge helfen, wenn auffindbare Labels eingefügt werden sollen. Dazu muss eine Label-Datei generiert werden. Aus der COD-Datei, die beim Linken entsteht, mittels gpvc. Damit füttert man dann gpdasm, und bearbeitet den Output von gpdasm nochmals, um Leerzeilen und schwachsinnige Kommentarzeilen zu entfernen.

gpvc -s main.cod | awk -- "BEGIN{print \"[CODE]\"} /address$/ {print $1=\"0x\"$3}" > main.ulist
gpdasm -p16f1459 -nt -k main.ulist main.hex | sed -e "/^$/ d" -e "/^;/ d" > main.d14

awk ist um einiges leistungsfähiger als sed. Hier geht es um das Davorsetzen von [CODE] vor die Liste.

Alternativ wäre es möglich, eine MAP-Datei als Ausgangspunkt zu verwenden. Hier stehen auch lokale (statische) Adressen. Ist aber um einiges schwieriger zu filtern.

Eine vernünftige Beschreibung dieser ulist-Datei gibt es nicht, nur eine Beispiel-Datei.

Urlader-Verschiebung

Dazu muss an 2 Stellen angesetzt werden:

Das Linkerskript, das normalerweise so aussieht:

…
CODEPAGE   NAME=page0      START=0x0     END=0x7FF
CODEPAGE   NAME=page1      START=0x800   END=0xFFF
CODEPAGE   NAME=page2      START=0x1000  END=0x17FF
CODEPAGE   NAME=page3      START=0x1800  END=0x1FFF
CODEPAGE   NAME=.idlocs    START=0x8000  END=0x8003  PROTECTED 
CODEPAGE   NAME=.devid     START=0x8006  END=0x8006  PROTECTED 
CODEPAGE   NAME=.config    START=0x8007  END=0x8008  PROTECTED 
…

wird an der ersten Zeile verdoppelt:

CODEPAGE   NAME=boot       START=0x0     END=0x1FF   PROTECTED
CODEPAGE   NAME=page0      START=0x200   END=0x7FF

Ich habe es kurzerhand im Verzeichnis der Linkerskripte geändert.

Wie man sieht, ist der Kodespeicher in vier Abschnitte (Pages) so aufgeteilt, dass in jedem ein goto oder call lokal funktioniert. Diese Befehle haben 11-Bit-Absolutadressen. Um von einer Page zur anderen zu springen wird movlp benötigt. Nur bei sequenzieller Kode-Abarbeitung sowie beim neuen bra-Befehl (relativer Sprung) wandert man problemlos zwischen den Pages; die Pages werden bedeutungslos.

Die Verschiebung der beiden festen Adressen erfordert das Patchen der Zwischen-Assemblerdateien, wie oben angegeben. Besser geht's leider nicht!

sdcc -mpic14 -p16f1459 --use-non-free -S main.c
sed -e "s/0x0000$/0x0200/" -e "s/0x0004$/0x0204/" main.asm > main.a14

Platz an Adresse 0x0202

Um einen Einsprung an Adresse 0x0202 für den Urlader-Kode zu ermöglichen, muss der Startup-Kode ersetzt werden. Ungefähr so:

STARTUP code 0x0200
	extern	_main,_usbRx
	pagesel _main
	goto	_main
	pagesel	_usbRx
	goto	_usbRx
	end

Die main.asm wird dann wie folgt bearbeitet:

sdcc -mpic14 -p16f1459 --use-non-free -S main.c
sed -e "/^STARTUP/./goto/ d" -e "/0x0004$/0x0204/" main.asm > main.a14

Das entfernt den Startupkode und verschiebt den Interruptkode. Beides wird wie oben beschrieben zusammengefügt. Das Hauptprogramm muss irgendwo die Funktion usbRx() definieren.

Wiederverwendung der VCP-Routinen

Der Urlader ist so gemacht, dass man die USB-Routinen samt Deskriptoren und Puffer im Anwendungsprogramm wiederverwenden kann. Dazu sind folgende Vorkehrungen zu treffen:

Zu beachten ist, dass ein Datenpaket von usbTx(len) erst dann bei Windows/Linux ankommt, wenn es kürzer als 64 Byte lang ist. 64-Byte-Pakete werden hingegend von der USB-Treiberschicht zu einem längeren Block zusammengesetzt; ein ReadFile() bzw. read() bleibt blockiert, selbst wenn nur 64 Byte oder weniger gelesen werden sollen, denn das Zerstückeln übernimmt eine andere, höher liegende Treiberschicht. Daher merke: Endet das letzte Paket eines Blocks mit genau 64 Byte, muss ein 0-Byte-Paket zum Abschluss abgesendet werden.

Da (die gesichteten) C-Compiler ein einzelnes Byte-Argument in W übergeben, können usbTx(byte len) und usbRx(byte len) direkt als eine solche Funktion deklariert werden; es wird kein Assembler-Stub benötigt.

Alle Einsprungpunkte liefern nichts, also void.

Dasselbe für andere PICs?

Für PIC18F2550 und PIC18F25K50 wäre das gleiche zu implementieren. Wegen ihrer 16-Bit-Struktur und der fehlenden Von-Neumannisierung ist das Assemblerprogramm erheblich umzustrukturieren. Andererseits ist deren Urlader-Bereich mit 1 KWord (2 KByte) doppelt so groß, was das zusätzliche Anbieten von HID und/oder WinUSB/WebUSB ermöglicht.

Alptraum python-serial

Unter Linux könnte man ja das Python-Skript verwenden, wenn da nicht jedesmal das Problem der fehlenden serial-Bibliothek wäre! Denn üblicherweise geht die Installation nur für Python3, und die gängigen Skripte sind Python2.7. Und Python3 ist zu Python2.7 so gut wie niemals kompatibel! Weniger noch als C und C++!

Unter den vielen vermeintlichen Lösungen fand ich (unter Ubuntu 2022) nur diese funktionierend:

cd /usr/local/lib/python2.7/dist-packages
sudo ln -s ../../python3.8/dist-packages/serial serial

Klar, der Vorschlag war Kopieren, aber ich will immer noch Platz sparen und nicht nur kopieren, daher ln -s = symbolischen Link erstellen.