/* Firmware für den ATmega32U4 eines Arduino Micro
für den Convac-Ätzer
Henrik Haftmann, 161011
Aufgaben:
1. Ansteuerung des LC-Displays, Tastenabfrage, Tonerzeugung
2. Benutzerschnittstelle
3. Maschinenschnittstelle
4. Programmier-Interface für den Anwender
Mit USB, dient zur Programmierung sowie zum Backup/Restore des EEPROM-Inhalts)
Hardware (nach Ports sortiert; 44 Pins):
SS 8 PB0 i0 SS D0 Bus:D0, LED RX (high-aktiv)
SC 9 PB1 i1 SCL D1 Bus:D1
MO 10 PB2 i2 MOSI D2 Bus:D2
MI 11 PB3 i3 MISO D3 Bus:D3
8 28 PB4 i4 ADC11 D4 Bus:D4
9 29 PB5 i5 OC1A !OC4B ADC12 D5 Bus:D5
10 30 PB6 i6 OC1B OC4B ADC13 D6 Bus:D6
11 12 PB7 i7 OC1C OC0A !RTS D7 Bus:D7
5 31 PC6 OC3A !OC4A (PWM-Ausgang)
13 32 PC7 ICP3 OC4A CLK0 frei
3 18 PD0 I0 SCL OC0B A0 Bus:A0
2 19 PD1 I1 SDA A1 Bus:A1
RX 20 PD2 I2 RxD A2 Bus:A2
TX 21 PD3 I3 TxD A3 Bus:Select
4 25 PD4 ICP1 ADC8 A4 Bus:Select
-- 22 PD5 XCK !CTS - LED TX (high-aktiv), nicht herausgeführt
12 26 PD6 T1 !OC4D ADC9 A5 Bus:Select
6 27 PD7 T0 OC4D ADC10 TON Display:Ton (getauscht mit PD6 170403)
Der Piezo ist dummerweise selbstschwingend.
-- 33 PE2 !HWB - nicht herausgeführt, via 10 kΩ auf Masse gelegt
7 1 PE6 I6 AIN0 A6 Bus:Select Enable
A5 41 PF0 ADC0 !WR Bus: Schreiben
A4 40 PF1 ADC1 !IOSTB Bus: I/O-Zyklus
A3 39 PF4 TCK ADC4 X0 Bus:X0
A2 38 PF5 TMS ADC5 X1 Bus:X1
A1 37 PF6 TDO ADC6 X2 Bus:X2
A0 36 PF7 TDI ADC7 !OE Bus:!OUT/HALT (High = alle Ausgänge inaktiv)
3 - D- D- USB Data-
4 - D+ D+ USB Data+
7 - Ubus Ubus USB-Busspannung
RS 13 - !Reset - Reset-Taste
16 - XTAL2 - 16-MHz-Quarz
17 - XTAL1 - 16-MHz-Quarz
42 - AREF - Kondensator
6 - UCAP - Kondensator
2,34,14,44,24 UUcc,Ucc,AUcc 5P
5,15,23,35,43 UGND,GND GND
Die Buszuordnung ist wegen der Notwendigkeit der Subadressen
und kaum noch freien Pins fraglich geworden.
Beim nächsten Mal ein zusätzliches Adresslatch (74HC574) spart Pins,
erlaubt damit die Verwendung des billigeren Boards „Pro Micro“.
bisher künftig
X0 A0 via '574 auf Bus
X1 A1 via '574 auf Bus
X2 A2 via '574 auf Bus
A0 A3 via '574 auf Bus
A1 A4 via '574 auf Bus
A2 A5 via '574 auf Bus
A3 A8 via '574 an Adressdekoder A0
A4 A9 via '574 an Adressdekoder A1
A5 A10 Portpin an Takteingang des '574 sowie an Adressdekoder A2
!OE !OE Portpin an !OE-Eingang des '574, an !CE des Adressdekoders
sowie an !OUT/HALT auf Bus
Statt 11 werden nur 2 Portpins benötigt.
Der Adressbuss ist vom Arduino vollständig entkoppelt.
Verwendung der Timer:
0
1 Uhr 100 Hz VT256 CTC 625
3
4 Tonausgabe
Aufteilung des Displays:
Die linke obere Ecke (52×11) ist für die Seitenbezeichnung vorgesehen.
Die rechte obere Ecke (52×11) ist für eine Uhr (36×11)
und Aktionsanzeige (16×11) vorgesehen.
Der untere Bereich (240×12) dient zur Anzeige der Belegung der F-Tasten,
mit 39×11 Pixeln für jedes der 6 Felder.
Die obere Mitte (136×11) dient zur Kontexthilfe
Verbleiben 240×41 Pixel für das restliche Bild.
Das genügt für 3 Zeilen Text und etwas unterstützende Grafik.
Das ursprüngliche Design verwendete 40×6 Zeichen.
Im Zoom-Modus entfallen alle Header und die F-Tastenbelegung,
und der mittlere Bereich dehnt sich auf die gesamte Displayfläche aus.
Menüsystem (F1..F6):
Die Taste „Set“ ist zum Setzen und Übernahme von Werten bestimmt (wie ENTER)
Die Taste „↲“ ist zum Verwerfen von Eingaben und zum Zurückgehen (wie ESC)
Hauptmenü
├─F1 System: Testseite und Backup/Restore; SET = Zeichenkode-Test
│ ├─F1 Backup: Ausgabe 1 KByte auf USB CDC
│ ├─F2 Restore: Emppfang 1 KByte von USB CDC, nach 2 s Timeout
│ ├─F3 Beispiel-Rezeptliste aus Flash in EEPROM kopieren
│ ├─F4 (frei)
│ ├─F5 Debug: Bestimmte Eingangskanäle invertieren,
│ │ damit die Steuerung im ausgebauten Zustand nicht massiv Warnungen produziert
│ ├─F6 Urlader starten für Firmware-Update
│ ├─0..9 Ziffer ausgeben
│ └─SET Zeichenkode-Test, jeder Tastendruck gibt das nächste Zeichen aus
├─F2 Start: Aktives Rezept starten (wie Startknopf am Gerät) oder stoppen
│ Wechselt zu Stopp, solange ein Rezept abgearbeitet wird
├─F3 Alarm-Editor: Listen-Anzeige
│ ├─F1 ▲ hoch
│ ├─F2 ▼ runter
│ ├─F3 Ausgewählten Alarm löschen
│ ├─F4 Alle Alarme löschen
│ ├─F5 Refresh: Alarmlistenanzeige aktualisieren
│ ├─F6 Zoom ein/aus
│ ├─0..9 ohne Funktion
│ └─SET ohne Funktion
├─F4 Rezept-Editor: Baum-Anzeige (= Derivat der Listen-Anzeige)
│ ├─F1 ▲ hoch
│ ├─F2 ▼ runter
│ ├─F3 +/- Unteräste ein/ausblenden
│ ├─F4 Edit: Weitere Funktionen (keine vertikale Bewegung möglich!)
│ │ ├─F1 (nur Befehl): Klausel (Bedingung) einstellen
│ │ ├─F2 (nur Befehl): Tätigkeit (Abbruch, Log) einstellen
│ │ ├─F3 (nur Befehl): Kanal auswählen (mit Zahleneingabe)
│ │ │ ├─F1 ◄ Zahleneingabe: Cursor links
│ │ │ ├─F2 ► Zahleneingabe: Cursor rechts
│ │ │ ├─F3 ▲ hoch: Vorhergehende Kanalnummer auswählen
│ │ │ ├─F4 ▼ runter: Nächste Kanalnummer auswählen
│ │ │ ├─F5 Umschalten Analog- oder Digitalkanal
│ │ │ └─F6 Zahleneingabe: Letzte Ziffer löschen
│ │ ├─F1 (Rezept,Schritt): ▲▲ Tauschen mit vorher
│ │ ├─F2 (Rezept,Schritt): ▼▼ Tauschen mit danach
│ │ ├─F3 (Rezept,Schritt): ×2 Duplizieren
│ │ ├─F4 Rezept-ID, Schritt-Dauer oder Befehls-Wert ändern (Zahleneingabe)
│ │ ├─F5 Neu…
│ │ │ ├─F1 Neues Rezept (erscheint nur wenn kontextbezogen erlaubt)
│ │ │ ├─F2 Neuer Schritt (erscheint nur wenn kontextbezogen erlaubt)
│ │ │ └─F3 Neuer Befehl (erscheint nur wenn kontextbezogen erlaubt)
│ │ ├─F6 ⌫ Rezept, Schritt oder Aktion löschen
│ │ ├─0..9 Erste Ziffer für Rezept-ID, Schritt-Zeit oder Befehls-Wert
│ │ └─SET leitet Zahleneingabe für Rezept-ID, Schritt-Zeit oder Befehls-Wert ein
│ ├─F5 Sicherheitsschritte anzeigen/verbergen
│ ├─F6 Zoom ein/aus
│ ├─0..9 Erste Ziffer für Rezept-ID, Schritt-Zeit oder Befehls-Wert
│ └─SET wählt oder startet aktives Rezept
├─F5 Digital-Editor („Ports“)
│ ├─F1 ◄ links
│ ├─F2 ► rechts
│ ├─F3 ▲ Seite (= I/O-Karte) zurück
│ ├─F4 ▼ Seite (= I/O-Karte) vorwärts
│ ├─F5 Alles 0: Setzt Ausgänge auf Null und schaltet Eingangsinvertierungen ab
│ ├─F6 Anzeige Uhr oder Richtung
│ ├─0..9 0 = Bit löschen, 1 = Bit setzen, 2..9 = ohne Funktion
│ └─SET Bit kippen
├─F6 Analog-Editor: Listen-Anzeige
│ ├─F1 ▲ hoch
│ ├─F2 ▼ runter
│ ├─F3 ◄ Fokus nach links
│ ├─F4 ► Fokus nach rechts
│ ├─F5 Alles 0
│ ├─F6 Zoom ein/aus
│ ├─0..9 Erste Ziffer einer Zahleneingabe
│ └─SET Leitet Zahleneingabe ein
├─0..9 ohne Funktion
├─SET ohne Funktion
└─↲ ohne Funktion
*/
#include "Convac.h"
#include <avr/eeprom.h> // eeprom_read_block()
#include <avr/wdt.h> // wdt_reset()
#include <avr/power.h>
#include <avr/interrupt.h> // sei()
#include <avr/signature.h>
#include <avr/fuse.h>
FUSES={
0xDE, // USB-Urlader können diese Fuses nur überprüfen, nicht ändern
0xDF,
0xC3
};
// EEPROM-Vorgabewerte sind hier nicht enthalten, um mit Urladern
// kompatibel zu sein, die keinen EEPROM schreiben können. „ubaboot“ kann's.
/*======================================================================*
* System (Grafik-Testseite sowie manuelles Backup/Restore via USB) *
*======================================================================*/
#define W(x) (x)&0xFF,(x)>>8
#define PL(n) (n)|0x80 // gefalteter Eintrag (zeigt PL=Plus an)
const PROGMEM byte Beispiel[]={
// Globale Sicherheit und externe Start/Halt-Schalter
W(1000),PL(9), // (implizit stets nur 1 „Schritt“)
OP_D1|OP_CNT,0x00,// Eingang 0.0.0 abfragen und starten/nächster Schritt bei Flanke 0->1 (Taste Start)
OP_D1|OP_BRK,0x04, // Eingang 0.0.4 abfragen und Abbruch generieren solange 1 (Taste Stopp)
OP_D0|OP_DNT,0x06, // Eingang 0.0.6 (000 00 110) beim Start abfragen, muss 1 sein (Tür offen)
OP_D0|OP_DNT,0x0A, // Eingang 0.1.2 (000 01 010) beim Start abfragen, muss 1 sein (Absaugung fehlt)
OP_D0|OP_DNT,0x0E, // Eingang 0.1.6 (000 01 110) beim Start abfragen, muss 1 sein (Tankeinschub offen)
OP_D0|OP_BRK|OP_LOG,0x1D, // Eingang 0.3.5 (000 11 101), Abbruch solange 0 (Motortemperatur)
OP_D0|OP_BRK|OP_LOG,0x1F, // Eingang 0.3.7 (000 11 111), Abbruch solange 0 (Motorstörung)
OP_D1|OP_BRK|OP_LOG,0x3C, // Eingang 1.3.4 (001 11 100), Abbruch solange 1 (Überstrom an Ausgängen)
OP_D1|OP_BRK|OP_LOG,0x5C, // Eingang 2.3.4 (010 11 100) für die zweite Ausgabekarte
// Rezeptliste
2, // Anzahl Rezepte Zeilenummer
PL(5), // Anzahl Schritte 1
W(300),0, // ID 2
// keine „lokale Sicherheit“
W(100),PL(1), // 1 s 3
OP_D1|OP_OUT,0x38, // Relais einschalten 4
W(300),PL(1), // 5
OP_D1|OP_OUT,0x39, // noch ein Relais 6
W(200),PL(1), // 7
OP_D0|OP_OUT,0x39, // wieder aus 8
W(0),PL(2), // Aufräum-Schritt Zeit = 0 9
OP_D0|OP_OUT,0x39, // 10
OP_D0|OP_OUT,0x38, // 11
PL(11), // Sequenz Ätzen (hypothetisch)
W(200),PL(1), // ID
OP_D0|OP_DNT,0x14, // Eingang 0.2.4 (000 10 100) Ätzer leer
W(50),PL(1), // 0,5 s
OP_D1|OP_OUT,0x46, // Ausgang 2.0.6 (010 00 110): Vakuum Chuck Ein
W(20),PL(2), // 0,2 s
OP_D0|OP_BRK,0x08, // Eingang 0.1.0 (000 01 000): Vakuum Chuck fehlt
OP_D1|OP_OUT,0x38, // Ausgang 1.3.0 (001 11 000): Spindelmotor (Relais)
W(500),PL(1), // 5 s
OP_A16|OP_OUT,9,W(0x1000),// zum halben Maximum des D/A-Wandlers
W(50),PL(1), // 0,5 s
OP_D1|OP_OUT,0x26, // Ausgang 1.0.6 (001 00 110): Ätzer (laden??)
W(40),PL(2),
OP_D0|OP_OUT,0x26, // Ausgang 1.0.6 (001 00 110): Ätzer Ende
OP_D1|OP_OUT,0x27, // Ausgang 1.0.7 (001 00 111): Ätzer zerstäuben
W(1000),PL(1), // 10 s auf Wafer verteilen
OP_D0|OP_OUT,0x27, // Ausgang 1.0.7 (001 00 111): Ätzer zerstäuben Ende
W(100),PL(1), // 1 s
OP_D1|OP_OUT,0x28, // Ausgang 1.0.7 (001 01 000): Ätzer spülen
W(500),PL(2), // 5 s
OP_D0|OP_OUT,0x28, // Ausgang 1.0.7 (001 01 000): Ätzer spülen Ende
OP_A8|OP_OUT,9,0, // runterfahren
W(10),PL(3), // 0,1 s
OP_D0|OP_OUT,0x46, // Ausgang 2.0.6 (010 00 110): Vakuum Chuck Ein Ende
OP_D1|OP_OUT,0x47, // Ausgang 2.0.7 (010 00 111): Vakuum Chuck Aus (belüften?)
OP_D0|OP_OUT,0x38, // Ausgang 1.3.0 (001 11 000): Spindelmotor Ende
W(0),PL(1),
OP_D0|OP_OUT,0x47, // Ausgang 2.0.7 (010 00 111): Vakuum Chuck Aus Ende
#if 1
0xFF, // Debug-Endemarkierung
0xFF,
#endif
};
#undef PL
#undef W
void scroll() {
d.L=0;d.T=12;d.R=d.getXMax();d.B=50;
d.scroll(0,d.getFontHeight()); // Vertikal scrollen
d.T=d.B-(d.getFontHeight()-1);
d.fillRect();
d.X=0;
}
void melde(bool ok) {
d.print(ok?F(" fertig."):F(" Zeitüberschreitung!"));
beep(ok?80:160,10);
beep(ok?70:180,10);
}
static void debugSetInv() {
DIGITAL::setIo(0,0x40); // DEBUG: Eingänge manipulieren dass es nicht
DIGITAL::setIo(1,0x44); // (ohne angeschlossene Hardware) andauernd piept
DIGITAL::setIo(3,0xA0);
}
static void dlgSystem() {
d.clear();
d.print(F("Test Grafik + Tasten"));
idle();
d.fillRect(144,24,161,39); // Grafikdemoseite und Tastentest
d.drawRect(84,16,101,39);
static const PROGMEM byte xhatch[8]={0x82,0x44,0x28,0x10,0x28,0x44,0x82,0x01};
memcpy_P(d.brush,xhatch,8);
d.patRect (24,14,51,39);
d.flags=d.COLOR|d.R2_XORPEN;
d.drawLine(17,24,229,38);
d.flags=d.COOKED|d.COLOR|d.R2_COPYPEN;
[](){}(); // Eine λ-Funktion, die nichts tut, aufrufen (Geigel! Nur um zu testen ob der C++-Compiler C++11 kann.)
/*
d.L=0;
d.T=0;
d.R=d.PIXEL_X-1;
d.B=10;
d.bitblt(ArialP12+4+0x80,1183,0);
idle();
d.T=12;
d.B=22;
d.bitblt(ArialP12+4+0x80,1183,d.PIXEL_X);
*/
static byte cc=20;
menu:
byte _usbCfg=usbCfg;
byte mask=0x3F;
if (!(_usbCfg&1)) mask=0x1C; // Kein Backup, Restore und Urlader ohne USB.
if (run.aktion) mask&=~2; // Kein Restore während Prozesslauf
paintMenu(F("Backup\0Restore\0Beispiel\0\0Debug\0Urlader"),mask);
d.gotoXY(0,39);
d.flags=d.R2_COPYPEN;
for (;;) {
byte c=getkey(50);
switch (c) {
case 0: if (_usbCfg!=usbCfg) goto menu; continue;
case '\r': return;
case '\n': c=cc++; break; // Zeichen der Reihe nach (im Code) ausgeben
case 0xF1: {
scroll();
d.print(F("copy com8 Datei"));
melde(usbBackup()); // EEROM-Inhalt auf serielle Schnittstelle herausdonnern
}continue;
case 0xF2: {
usbPurge();
scroll();
d.print(F("copy Datei com8"));
melde(usbRestore());// Was von der Schnittstelle kommt geht zum RAM und schleichend zum EEPROM
}continue;
case 0xF3: { // Beispielrezeptliste laden
scroll();
d.print(F("Beispielrezeptliste laden?"));
if (yesno()) {
memcpy_P(&eedata.gs,Beispiel,sizeof Beispiel);
#ifdef DEBUG
if (eedata.gs.calcsizes()!=sizeof Beispiel-2) exit(-4); // DEBUG: Fehler im Beispiel
#endif
}
}goto menu;
case 0xF4: beep(10,10); continue; // funktionslos
case 0xF5: {
scroll();
d.print(F("Eingangsinvertierungen aktivieren?"));
if (yesno()) debugSetInv();
}goto menu;
case 0xF6: {
scroll();
d.print(F("Zum Atmel-Urlader springen?"));
if (yesno()) {
d.clear();
bootstart();
}
}goto menu;
}
if (d.X+d.getTextExtent(c)>d.getXMax()) scroll();
d.print(c);
}
}
/*===========*
* Hauptmenü *
*===========*/
ITER giter;
static void mainscreen() __attribute__((noreturn));
static void mainscreen() {
vorn:
d.clear();
idle();
d.print(F("EEPROM-Füllstand: "));
d.print(floatstr((unsigned long)(sizeof(CONFIG)+gslen)*(1000U<<6)>>16,1)); // EEPROM-Länge: 1024 Bytes (2**6)
d.print(F(" %"));
idle();
d.gotoXY(8,17);
giter.ire=C.runrez;
giter.re=*rl+giter.ire; // Achtung! operator+
giter.re->print(giter.ire);
ALARM*a=ALARM::newest();
if (a) {
d.print(F("Letzter Fehler: "),0,29);
d.Y=40; a->print();
}
paintMenu(F("System\0\0Alarme\0Rezepte\0Digital\0Analog"));
start:
paintMenuItem(1*40,run.aktion?F("Stopp"):F("Start"),run.aktion?25:24);
for(;;) {
// Fehler hier anzeigen!
switch (getkey(255)) {
case 0: goto start;
case 0xF1: dlgSystem(); goto vorn;
case 0xF2: if (run.aktion) run.next(run.re->n.c-1); else run.start(C.runrez); goto start;
case 0xF3: dlgAlarm(); goto vorn;
case 0xF4: dlgRezept(); goto vorn;
case 0xF5: dlgDigital(); goto vorn;
case 0xF6: dlgAnalog(); goto vorn;
default: beep(80,20);
}
}
}
/***********************************************
* Generelle Initialisierung und Hauptprogramm *
***********************************************/
static void setupHardware(void) {
MCUCR = 0x80; // JTAG deaktivieren (sonst gehen PF4..PF7 = ADC4..ADC7 nicht)
MCUSR = 0; // WDRF löschen, damit das nachfolgende Deaktivieren des Watchdogs klappt
WDTCSR|=0x18; // Watchdog aktivieren, legt u.a. !OUT/HALT auf High
WDTCSR= 0x45; // auf 500 ms
wdt_reset();
CLKPR = 0x80;
CLKPR = 0x00; // CLKDIV8-Fuse ist ab Werk gesetzt, beim Arduino Micro gelöscht
SMCR = 0x01; // Sleep aktivieren
PRR0 = 0x84; // I²C und SPI aus, wird nicht benötigt
PORTB = 0xFF; // Pullups am Datenbus
DDRC = 0xC0; // Ausgänge aktivieren
DDRD = 0xFF;
PORTE = 0x40;
DDRE = 0x40;
PORTF = 0x83;
DDRF = 0xF3;
ACSR = 0x80; // Analogvergleicher deaktivieren
ICR1 = 624; // Zählumfang 0..624
TCCR1B= 0x1C; // CTC via ICR, Vorteiler 256, ergibt 100 Hz
TIMSK1= 0x20; // mit Interrupt
TCCR4D= 0x01; // Frequenz einstellbar am Timer4
sei();
}
int main() {
EEAR=0; EECR|=1; if (EEDR!=0xFF) {
eeprom_read_block(&eedata,&eesave,sizeof(eedata)); // alles in einem Rutsch
}
setupHardware();
usbInit();
d.init();
d.setFont(FP(ArialP12));
if (eedata.gs.calcsizes() // ausrechnen, sollte zunächst 1 ergeben
> sizeof eedata-sizeof(CONFIG)) {
memcpy_P(&eedata.gs,Beispiel,sizeof Beispiel);
while (eeprom_update(&eedata.gs,&eesave.gs,sizeof Beispiel));
return gslen; // sterben mit Länge als Fehlerkode
}
DIGITAL::initOutputs();
ANALOG::initOutputs();
RUN::init();
PORTF&=~0x80; // !OUT/HALT aktivieren
#ifdef DEBUG
debugSetInv();
#endif
mainscreen();
}
Detected encoding: UTF-8 | 0
|