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 Assemblerprogrammierung 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:
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.
Länge in Bytes | Funktion | Daten (Länge Bytes) | Antwort (1 Byte) |
---|---|---|---|
0 | wird ignoriert | keine | keine |
1 | Reset: Sprung zum Anwenderprogramm = Adresse 0x0200 | 'R' |
|
2 | Byte lesen | Adresse | Datenbyte |
3 | Byte schreiben |
|
|
4 | Flash-Schreiben vorbereiten und ggf. Flash-Speicherseite löschen; Flash lesen |
|
|
64 | Flash beschreiben | 32 14-Bit-Worte (High-Bits 0) |
|
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:
Name | Adresse | Bedeutung | Vorgabe |
---|---|---|---|
PORTA | 00C |
| |
TRISA | 08C |
| 3B |
LATA | 10C | Ausgangstreiber-Latch | 00 |
ANSELA | 18C | Analog-Auswahl: 0 = analog+digital, 1 = analog | 10 |
WPUA | 20C | Pullup-Enable: 0 = kein Pullup, 1 = Pullup | 38 |
OPTION_REG | 095 | Bit 7: 0 = globale Pullup-Freigabe | 7F |
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
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.
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:
#pragma
-Anweisungen sind wirkungslos.
Hier ist es: Die Portpin-Fummel-Äpp mit Flash-Funktion .
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 1∕16 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.
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:
avr-gcc | SDCC | Anmerkung | |||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
PORTB = 42; |
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:
gpasm -c main.a14 gpasm -p 16f1459 startup.a14 gplink -o main.hex -C startup.o main.o pic16f1459.lib
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
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.
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
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.
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
.
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.
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.