Dies ist eine alte Version des Dokuments!
Inhaltsverzeichnis
TPS/SPS Assembler
Die Idee kam mir im Zusammenhang mit der Arduino SPS. Es ist zwar toll, mal eben an der Hardware das Programm ändern zu können. Das funktioniert im Wohnzimmer oder am Schreibtisch auch sehr gut. Ich setzt die SPS aber eben auch in meinen RC Modellen ein und da habe ich selten die Möglichkeit, nach Einbau, an die Tasten zu gelangen. Geschweige denn in aller Geduld die Codes einzuhacken. Ein smartes Handy hat ja mittlerweile jeder dabei. Also dachte ich mir eine Möglichkeit aus, wie ich mit Handy, etwas Hardware und einem Internetdienst meine Programme in die SPS bekomme.
Dazu braucht es
- eine SPS der neuesten Firmware (>.10)
- ein Smartphone mit Internet und USB OTG
- ein USB OTG Kabel
- einen FTDI
- und ein Stück Kabel
Der tatsächliche Download des Programms auf die SPS erfolgt über ein serielles Protokoll. Das kann man sogar mit einem einfachen Terminalprogramm (oder dem eingebauten seriellen Monitor in der ArduinoIDE) machen. Gestartet wird dieser neue Programmiermodus, wenn nach einem Reset der SPS die SEL (und zwar nur Diese) Taste gedrückt bleibt. Das Protokoll kennt nur 3 Befehle R(ead), W(rite) und E(xit). Mit Read wird das aktuelle Programm ausgegeben und mit Write ein neues eingelesen. Format ist bei beiden Optionen Intel HEX. Mit Exit oder einem erneuten Reset verlässt man diesen Modus wieder.
Um mit dem Handy mit der SPS zu kommunizieren, braucht man zusätzlich einen USB/Seriell-Konverter. Die Gängigsten sind findet man unter der Bezeichnung FTDI. Das ist ein einfacher USB Seriell Konverter, der sowohl mit dem PC wie auch mit einem Handy funktioniert. Weiterhin muss das Handy USB OTG unterstützen. (Einfach mal bei seinem Handy googeln.) Damit haben wir eine einfache serielle Schnittstelle am Handy. Jetzte fehlt nur noch ein Terminalprogramm auf dem Handy, fertig.
Zu allerletzt muss man seine Programmidee in das IntelHEX Format konvertieren. Dazu dient dieser kleine Service hier auf der Webseite.
Der Assembler übersetzt, wie jeder andere Assembler, handgeschriebenen Mnemonic Code in ein maschinenlesbares Programm. Hier eben in ein Programm für die TPS/SPS.
Dabei werden folgende Features unterstützt:
- Ausgabeformate: Intel HEX Code, SPS Emulator Source und HEX Text
- Eingabeformate: TPS Assembler
- Unterstützung von folgenden TPS Systemen: HOLTEK, ATmega8, Arduino SPS, ATtiny84 SPS
- automatische Berechnung von Sprüngen und Unterprogrammen
- Unterstützung von Labels für Sprünge
- Unterstützung von Kommentaren
- Unterstützung von Makros
- Unterstützung verschiedener Assemblerdirektriven
Die Online-Version findest du hier: Online TPS Assembler public BETA
Dabei werden folgende Mnemonics unterstützt:
Programmsteuerung
Mnemonic | short description | Beschreibung |
---|---|---|
NOP | No Operation | es wird nichts gemacht, aber die Ausführung kostet trotzdem etwas Zeit und Prozessorzyklen |
WAIT #x | wait <time> | Verzögerung des Programms, 1⇐ x ⇐ 15, nach der vorher stehenden Tabelle . Wahlweise kann auch ein String verwendet werden. Mögliche Werte dazu: 1ms, 2ms … 60s. |
RJMP #x | Jump back | Sprung zurück um x Befehle, oder zu :label |
PAGE #x | page #x | Setzen des Page Registers 0 ⇐ x < 16 |
JMP #x | jump to #x + 16 * page | Springe nach Adresse #x + (16 * page) 0 ⇐ x < 16 |
LOOPC #x | loop c | Ist C>0: C=C-1; Springe nach #x + (16*page), sonst gehe zum nächsten Befehl |
LOOPD #x | loop d | Ist D>0: D=D-1; Springe nach #x + (16*page), sonst gehe zum nächsten Befehl |
SKIP0 | Skip if A = 0 | Überspringe nächsten Befehl wenn A = 0 |
AGTB | Skip if A > B | Überspringe nächsten Befehl wenn A > B |
ALTB | Skip if A < B | Überspringe nächsten Befehl wenn A < B |
AEQB | Skip if A = B | Überspringe nächsten Befehl wenn A = B |
DEQ#y #x | Skip if Din.X = Y | Überspringe nächsten Befehl wenn der Eingang #y 0 oder 1 ist. x=0,1 |
PRG#x | Skip if PRG = x | Überspringe nächsten Befehl wenn der PRG TAster gedrückt(0) oder nicht gedrückt (1) ist. x=0,1 |
SEL#x | Skip if SEL = x | „ nur für SEL |
CALL #x | Call #x + (16 * page) | |
RTR | Return | Zurück springen nach einem Call-Befehl oder am Ende einer Subroutine |
CASB #x | Call sub #x | Aufruf der Subroutine x, 1 ⇐ x ⇐ 6 |
DFSB #x | Define sub #x | Definierung der Subroutine x, 1 ⇐ x ⇐ 6 |
REST | Restart program | |
PEND | Program end |
Laden und Speichern
Mnemonic | short description | Beschreibung |
---|---|---|
LDA ##x | A = #x | Register A wird mit dem festen Wert #x geladen |
SWAP | Swap A & B | Tauschen der Register A und B |
MOV X,Y | X = Y | kopiert ein Register in ein anderes X und Y dürfen folgende Werte annehmen: A, B, C, D, E, F |
PUSH | push A on stack | Das A Register wird auf dem Stack gelegt |
POP | Pop value from stack to A | Wert aus dem Stack in das A Register übertragen |
Mathematik
Mnemonic | short description | Beschreibung |
---|---|---|
INC | A = A + 1 | Increment A |
DEC | A = A - 1 | Decrement A |
ADD | A = A + B | |
SUB | A = A - B | |
MUL | A = A * B | |
DIV | A = A / B | |
AND | A = A and B | |
OR | A = A or B | |
XOR | A = A xor B | |
NOT | A = not A | |
MOD | A = A % B | |
BYTE | A = A + 16 * B | |
BSUBA | A = B - A |
Ein/Ausgabe
Mnemonic | short description | Beschreibung |
---|---|---|
LDA DIN | A = Din | Register A wird mit dem Wert vom digitalen Eingang geladen. Alle 4 Bit. |
LDA DINx | A = Din.x | Register A wird mit dem Wert vom digitalen Eingang #x geladen. x = 1..4 |
LDA ADCx | A = ADC.x | Register A wird mit dem Wert vom analogen Eingang #x geladen. x = 1..2 |
LDA RCx | A = RC.x | Register A wird mit dem Wert vom RC Empfängereingang #x geladen. x = 1..2 |
PORT #x | Dout = #x | Direkte Ausgabe von Wert #x auf den Ausgängen. 0 ⇐ x ⇐ 15. |
STA DOUT | Dout = A | Ausgabe von A auf den Ausgängen |
STA DOUTx | Dout.x = A | Ausgabe von A.x auf den Ausgang x = 1..4 |
STA PWMx | PWM.x = A | Der Wert aus dem A Register wird als PWM.x ausgegeben. x = 1,2 |
STA SRVx | Servo.x = A | Der Wert aus dem A Register wird als Servo.x ausgegeben. x = 1,2 |
Byte Befehle
Mnemonic | short description | Beschreibung |
---|---|---|
BLDA ADCx | A = ADC.x | Bytewert auslesen eines Analogeinganges x = 1,2 |
BLDA RCx | A = RC.x | Bytewert auslesen eines Fernsteuerungskanals x = 1,2 |
BSTA PWMx | PWM.x = A | Der Byte Wert aus dem A Register wird als PWM.x ausgegeben. x = 1,2 |
BSTA SRVx | Servo.x = A | Der Byte Wert aus dem A Register wird als Servo.x ausgegeben. x = 1,2 |
TONE | Tone A | Ausgabe eines Tones nach Midi 32 ⇐ A ⇐ 108 |
Labels
Für die Jump, Loop und Call Befehle kann auch ein Label verwendet werden. Der zugehörige Page Befehl muss aber selber implementiert werden. Die Berechnung der Sprungadresse erfolgt dabei automatisch, wenn das Argument des Page Befehles als :? angegebenen wird. Beispiel:
:Loop PORT #4 WAIT 200ms PORT #0 WAIT 200ms PAGE :? JMP :Loop
Labels sind bei den Mnemonics RJMP, JMP, LOOPC, LOOPD und CALL erlaubt. Auch die beiden erweiterten Befehle DFSB und CASB zur Unterprogrammausführung unterstützen Labels.
Somit funktioniert auch sowas:
CASB :Blink ... DFSB :Blink PORT #4 WAIT 200ms PORT #0 WAIT 200ms RTR
Kommentare
Kommentare sind erlaubt. Es gibt 2 Arten von Kommentaren. Blockkommentare gehen über mehrere Zeilen, Starten mit „\*“ und Enden mit „*/„
Zeilenkommentare werden mit ; gestartet, stehen hinter dem Argument und der Assembler ignoriert den Rest der Zeile.
Beispiel:
/* Kommentar über mehrere Zeilen */ :loop PORT #0x0F ; Zeilenkommentar WAIT 200ms PORT #0x00 WAIT 200ms RJMP :loop
JMP, CALL, LOOPx und PAGE
Für die absoluten Sprünge werden immer das Argument des Mnemonics und zusätzlich der Inhalt des PAGE Registers verwendet. Die Adresse berechnet sich dann aus: adr = #x + (16 * PAGE). Nun muss man sich vorher die PAGE berechnen und den entsprechenden PAGE Befehl vor den Sprung setzten. Hier kann einem der Assembler bei der Berechnung helfen. Steht direkt vor dem Sprung mit Label ein PAGE mit Argument :? berechnet der Assembler selber die PAGE Nummer. Beispiel:
PAGE :? JMP :loop2 :loop PORT #0x0F WAIT 200ms :loop2 PORT #0x00 WAIT 200ms RJMP :loop
Hier wird automatisch die richtige Seite in den ersten PAGE Befehl eingetragen.
Makros
Es können auch zur Vereinfachung von Programmen Makros verwendet werden. Zunächst muss man ein Makro definieren. Beispiel hier mal die Blink Funktion:
PORT #0B1111 WAIT 200ms PORT #0B0000 WAIT 200ms
Um diese als Makro an verschiedenen Stellen einsetzen zu können, muss man zunächst die Stelle als Makro markieren. Das macht man, indem man ein mit .macro und .endmacro Anfang und Ende markiert. Hinter .macro muss man dann den Namen des Makros schreiben, unter dem man das Makro dann verwenden möchte. Ergebnis:
.macro blink PORT #0B1111 WAIT 200ms PORT #0B0000 WAIT 200ms .endmacro
Jetzt kann man bestimmte Werte auch noch variabel gestalten. Also hier z.B. welche LEDs blinken sollen und wie lange die Wartezeit sein soll.
.macro blink output delay PORT output WAIT delay PORT #0B0000 WAIT delay .endmacro
Im eigentlichen Programm kann man das Makro mit z.B.
.blink #0B1010 200ms
aufrufen.
Makros müssen vor dem ersten Aufruf definiert sein. Das gesamte Programm lautet also nun:
.macro blink output delay PORT output WAIT delay PORT #0B0000 WAIT delay .endmacro :loop .blink #0B1111 200ms RJMP :loop
Direktriven
Der Assembler unterstützt verschiedene Direktriven. Einige haben wir schon kennengelernt, die Macros, Loops…
Weitere:
- .include
- .arduinosps: damit wird die Hardware auf ArduinoSPS festgelegt. Diese Directrive sollte vor dem eigentlichen Code erscheinen.
- .tinysps: legt die Hardware auf die TinySPS fest.
- .atmega8: legt die Hardware auf die ATMega8 fest.
- .holtek: legt die Hardware auf Holtek fest.
.include
Mit dieser Direktrive kann man ein Unterprogramm aus einer anderen Datei einbinden. (Dieses Feature wird im Webmodus nicht unterstützt) mit ``.include Blink `` wird eine Datei Blink.tps an dieser Stelle mit ins Programm eingebunden. Damit steht der gesamte Code dieser Datei an dieser Stelle als wäre er dort in der originalen Datei hingeschrieben worden.
Programmgrößen
Jede Hardware hat andere max. Programmgrößen.
- HOLTEK: 128 Byte
- ATmega8: 256 Byte
- ArduinoSPS: 1024 Byte*
- TinySPS: 512 Byte*
* Alles über 256Byte kann nur durch die Verwendung von Unterroutinen (DFSB und CASB) benutzt werden.
Upload der Daten
Mit dem Online SPSAssembler kannst du deinen Quellcode eingeben und kompilieren lassen. Als Ausgabeformat gibt es ein einfaches Textfile mit den entsprechenden Darstellung zur direkten Eingabe in die TPS.
Oder aber du kannst dir das ganze auch als Intel HEX File ausgeben lassen. Meine ArduinoSPS und die TinySPS können beide das Format über die serielle Schnittstelle empfangen. Dazu sind folgende Schritte nötig:
- HEX Datei erzeugen und herunter laden.
- Handy mit FTDI (über ein USB OTG Kabel) verbinden.
- Den FTDI mit der seriellen Schnittstelle der Hardware verbinden.
- Terminalprogramm auf dem Handy starten und mit dem FTDI verbinden. 9600Baud.
- Auf der TPS Reset und SEL gleichzeitig drücken. SEL muss man gedrückt lassen, bis sich die TPS im Terminalprogramm meldet.
- Mit „w“ in den Programmsendemodus wechseln und schauen, ob sich die TPS im Programmiermodus befindet.
- Dann die HEX Datei mit dem Terminalprogramm auf die TPS laden.
- Jetzt noch ein Reset oder „E“ und fertig.
Serieller Anschluss
Beim ATTiny84 werden dazu die Pins 11 (RX) und Pin 12 (TX) verwendet.
Benutzt wird ein Protokoll mit 9600Baud, 0 Parität, 1 Stoppbit.
Der Anschluss erfolgt bei der TinySPS Platine an der Input Leiste.
Pin | Signal | FTDI |
---|---|---|
2 | Din.2, TX | RX |
3 | Din.3, RX | TX |
5 | GND | GND |