Source file: /~heha/enas/STS Multiplex/firmware.zip/sts.cpp

/* Firmware für den ATmega16U4 für die STS Multiplex Trockenätzmaschine
Gaskasten-Steuerung
Henrik Haftmann, 160512
Aufgaben:
1. Durchlassen der Daten der seriellen Schnittstelle (Echo)
   TODO: Baudrate herausfinden; vermutlich 9600 Baud
2. Mitschnitt der seriellen Daten nach USB (= Sniffer)
3. Abfrage der Istwerte von 5-6 Analogeingängen
4. Ausgabe der Sollwerte an 5-6 PWM-Ausgänge
5. Ansteuerung der bis zu 8 Digitalausgänge

Alles via USB-HID

Hardware (nach Ports sortiert; 44 Pins):
8	PB0	-	D1	Digitalausgang 1
9	PB1	-	D2	Digitalausgang 2
10	PB2	-	D3	Digitalausgang 3
11	PB3	-	D4	Digitalausgang 4
28	PB4	(ADC11)	D5/I8	Digitalausgang 5 (Istwert 8)
29	PB5	OC1A	O1	PWM-Ausgang 1
30	PB6	OC1B	O2	PWM-Ausgang 2
12	PB7	OC1C	O3	PWM-Ausgang 3 (!RTS nicht nutzbar)

31	PC6	OC3A	O4	PWM-Ausgang 4
32	PC7	OC4A	O5	PWM-Ausgang 5

18	PD0	-	D6	Digitalausgang 6
19	PD1	-	D7	Digitalausgang 7
20	PD2	RxD	RxD	Empfangsdaten
21	PD3	TxD	TxD	Sendedaten
25	PD4	(ADC8)	D8/I7	Digitalausgang 8 (Istwert 7 / SoftPWM 8)
22	PD5	!CTS	!CTS	Clear-To-Send-Eingang
26	PD6	-	!RTS	Request-To-Send-Ausgang (in Software)
27	PD7	OC4D	O6	PWM-Ausgang 6

33	PE2	!HWB	-	DIL-Schalter für Bootloader-Zwang (SoftPWM 7)
1	PE6	AIN0	-	Überstromdetektor für Digitalausgänge

41	PF0	ADC0	I1	Istwert 1
40	PF1	ADC1	I2	Istwert 2
39	PF4	ADC4	I3	Istwert 3
38	PF5	ADC5	I4	Istwert 4
37	PF6	ADC6	I5	Istwert 5
36	PF7	ADC7	I6	Istwert 6

3	-	D-	D-	USB Data-
4	-	D+	D+	USB Data+
7	-	Ubus	Ubus	USB-Busspannung
13	-	!Reset	-	DIL-Schalter für Reset
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

Verwendung der Timer:
Timer0	8 Bit	Timeout für USB-Übertragung von der seriellen Schnittstelle
Timer1	16 bit	OC1A, OC1B, OC1C
Timer2	gibt es gar nicht
Timer3	16 bit	OC3A; OC3B, OC3C mit Software-Pintoggle
Timer4	10 bit	OC4A, OC4D

*/

#include "USB.h"
#include <avr/io.h>
#include <avr/wdt.h>
#include <avr/power.h>
#include <avr/sleep.h>
#include <avr/interrupt.h>
#include <avr/eeprom.h>
#include <avr/pgmspace.h>

#ifdef MYSTARTUP
extern "C" void __do_copy_data() __attribute__((naked));
	   void __do_copy_data() {};
extern "C" void __do_clear_bss() __attribute__((naked));
	   void __do_clear_bss() {}
#define NOBLOCK		// Interruptroutinen, wo SEI in der Interrupttabelle steht
#else
#define NOBLOCK ,ISR_NOBLOCK
#endif

/**********************
 * Alle 6 HID-Reports *
 **********************/
static struct{
 char reportId;		// = 1
 uint8_t flags;		// 1-Bit für neuen Messwert; Byte wird beim Abholen gelöscht
 uint16_t values[8];	// Per ISR akkumulierte Messwerte
}AdcReport;

static struct{
 char reportId;		// = 2
 uint8_t flags;		// Bit 6 aktiviert O7, Bit 7 aktiviert O8
 uint16_t values[8];
}DacReport;

struct _DioReport{	// nicht „static“ wegen Zugriff aus startup.S
 char reportId;		// = 3
 uint8_t DigitalOut;
}DioReport;

static struct _SerialCfgReport{
 char reportId;		// = 4
 uint32_t baudRate;	// kompatibel zu HE2325U, wenn auch unausgerichtet
 uint8_t lcr;		// 8259+3W: 0,Break,Par,Par,Par,Stop,Dlen,Dlen
 uint8_t fcr;		// 8250+2W: OUTXX,INXX,1,0,0,⌂ClrTxFifo,⌂ClrRxFifo,0
 uint8_t mcr;		// 8259+4W: PIPE,SNIFF,RtsCtsHs,Loopback,0,0,RTS,DTR
 uint8_t lsr;		// 8259+5R: PIPEOVER,SNIFFOVER,0,Break,Framing,Parity,Overrun,0
 uint8_t msr;		// 8259+6R: DCD,Ring,DSR,CTS,↕DCD,↕Ring,↕DSR,↕CTS
}SerialCfgReport	// wird vom EEPROM initialisiert bzw. dort gespeichert
// Par: 0=No, 1=Odd, 2=Even, 5=Mark, 7=Space
// Stop: 0=1, 1=2
// Dlen: 0=5, 1=6, 2=7, 3=8 Datenbits
// OUTXX, INXX: Xon/Xoff-Protokoll ausgehend bzw. eingehend (nicht bei 8259)
={
 4,
 9600,	// Baudrate
 0x03,	// 8 Datenbits, 1 Stoppbit, keine Parität
 0x20,	// nichts
 0xC0,	// Pipe+Sniff
};

#define TO_IN 0.01	// s Timeout vom Eintreffen des Zeichens zum Abschicken eines USB-Pakets

static struct{
 char reportId;		// = 5
 char dlen;
 char data[62];
}SerialInReport;	// zum Schnüffeln

static struct{
 char reportId;		// = 6
 char dlen;
 char data[62];
}SerialOutReport;	// zum Einfügen (sofern zeitlich Platz ist)

/*************************
 * A/D-Wandler: Istwerte *
 *************************/
static uint16_t AdcVal[8];	// zur 16-Bit-Mittelwertbildung (die Istwerte der MFCs sind langsam)
// Zeitbetrachtung:
// ADC-Takt: 16 MHz/128 = 125 kHz; Wandlungstakte: 13 -> 9,6 kHz
// Anzahl Kanäle: 8 -> 1,2 kHz
// Mittelung über 64 Abtastwerte: 18,8 Hz: Schnell genug für MFCs und HID

// Der durchlaufende A/D-Wandler akkumuliert hier seine Ergebnisse
// ISR-Aufruffrequenz: 9,6 kHz
ISR(ADC_vect NOBLOCK) {
 static const uint8_t PROGMEM mux[8]={0x40,0x41,0x44,0x45,0x46,0x47,0x60,0x63};
 static char chIdx;	// 0..7
 static char counter;	// zählt 0..63
 int i=chIdx;
// Zurzeit steht in ADC das Ergebnis zu chIdx,
// der Wandler wandelt gerade (chIdx+1)&7,
// der nächste zu setzende Kanal ist deshalb (chIdx+2)&7
 uint16_t v=AdcVal[i]+=ADC;		// summieren und aufheben
 ADMUX=pgm_read_byte(mux+(i+2&7));	// Nächsten Eingangskanal setzen
 if (counter==63) {
  AdcReport.values[i]=v;		// Wert ist fertig
  AdcReport.flags|=1<<i;
#ifdef DEBUG
  if (!i) PINB|=0x01;		// D1 toggeln (DEBUG) - sollten 9 Hz sein
#endif
 }
 i=i+1&7;			// vorrücken
 if (!i) counter=counter+1&63;	// vorrücken
 chIdx=(char)i;
};

/**************************
 * D/A-Wandler: Sollwerte *
 **************************/
/*
Die Analogausgänge sind folgenden Kanälen zugeordnet:
O1	OC1A	16	PB5
O2	OC1B	16	PB6
O3	OC1C	16	PB7
O4	OC3A	16	PC6
O5	OC4A	10	PC7
O6	OC4D	10	PD7
O7	(OC3B)	~16	PE2	Soft-PWM
O8	(OC3C)	~16	PD4	Soft-PWM
Das Register GPIOR0 dient zur flinken Steuerung der Software-PWM
*/

#ifndef MYSTARTUP	// Die ISRs sind in startup.S verpackt

// Diese ISRs sollten extrem schnell reagieren,
// und Interrupts sollten in anderen ISRs freigegeben bleiben
// Wäre der Compiler intelligent, passt das in die Interrupttabelle ohne Sprungbefehl.
ISR(TIMER3_COMPB_vect) {
 PORTE|=0x04;	// Hier: Inverse Ausgabe, weil Überlauf-Interrupt geringer priorisiert ist
}		// und besonders die kurzen Pulse ohne Überlaufeffekte ausgegeben werden müssen
ISR(TIMER3_COMPC_vect) {
 PORTD|=0x10;
}
ISR(TIMER3_OVF_vect) {
 if (GPIOR0&0x40) PORTE&=~0x04;
 if (GPIOR0&0x80) PORTD&=~0x10;
}

#endif

//Aufzurufen sobald DacReport verändert wurde
static void DacChange() {
 uint16_t v;
 OCR1A=DacReport.values[0];
 OCR1B=DacReport.values[1];
 OCR1C=DacReport.values[2];
 OCR3A=DacReport.values[3];
 v=DacReport.values[4]>>6;	// nur 10 Bit
 TC4H=v>>8; OCR4A=v&0xFF;
 v=DacReport.values[5]>>6;
 TC4H=v>>8; OCR4D=v&0xFF;
 OCR3B=~DacReport.values[6];	// invers (siehe oben)
 OCR3C=~DacReport.values[7];
 GPIOR0=DacReport.flags;
 if (GPIOR0&0x40) {
  DDRE|=0x04;
  if (DacReport.values[6]>=8) TIMSK3|=0x04;	// Compare aktivieren
 }else{
  DDRE&=~0x04;
  TIMSK3&=~0x04;
 }
 if (GPIOR0&0x80) {
  DDRD|=0x10;
  if (DacReport.values[7]>=8) TIMSK3|=0x08;
 }else{
  DDRD&=~0x10;
  TIMSK3&=~0x08;
 }
 if (GPIOR0&0xC0) TIMSK3|=0x01; else TIMSK3&=~0x01;	// Überlauf-Interrupt wahlweise
}

static void DacInit() {
 ICR1=ICR3=0xFFFF;	// 16 Bit bei 16 MHz: 244 Hz Trägerfrequenz
 TCCR1A=TCCR3A=0xAA;	// alles schnelle PWM (OC3B und OC3C gibt's nicht als Pin)
 TCCR1B=TCCR3B=0x19;	// ohne Vorteiler, TOP=ICRx
 TC4H=0x03;
 OCR4C=0xFF;		// 10 Bit laden
 TCCR4A=0x82;		// OC4A aktivieren
 TCCR4B=0x07;		// Vorteiler 64 (gleiche Trägerfrequenz, vorerst)
 TCCR4C=0x89;		// OC4D aktivieren
 TCCR4E=0x40;		// 10 Bit aktivieren
}

/************************
 * Digitale Ein/Ausgabe *
 ************************/

void DioChange(void) {
 uint8_t c=DioReport.DigitalOut;
 PORTB=PORTB&0xE0|c&0x1F;
 PORTD=PORTD&0xEC|c>>3&0x10|c>>5&0x03;
}

#ifndef MYSTARTUP
// Löst aus bei Überstrom: Schaltet die Ausgänge ab, um die Transistoren zu schützen
ISR(ANALOG_COMP_vect) {
 PORTB&=0xE0;
 PORTD&=0xEC;
 DioReport.DigitalOut=0;	// auf diese Weise der Steuerung melden
}
#endif

/**************************************************************
 * Serielle Schnittstelle: Althergebrachte Maschinensteuerung *
 **************************************************************/
struct _SerialCfgReport EEMEM eeSerialCfgReport;	// Kopie im EEPROM

// Jedes eingehende Zeichen wird auf den Ausgang kopiert
// Die USB-Daten werden hingegen nur bei leerem Sendeschieberegister kopiert
// und sind dadurch automatisch niedriger priorisiert
ISR(USART1_RX_vect) {
 char c=UDR1;			// Zeichen lesen
 sei();				// Verschachtelung? Sollte nicht passieren, ISR ist schnell genug
 char f=SerialCfgReport.mcr;	// Über Registervariable gehen
 char i=0;
 if (f&0x80) {			// PIPE
  if (f&0x20 && PIND&0x20) SerialCfgReport.lsr|=0x80;// PIPEOVER: Verwerfen wegen RTS-Handshake
  else UDR1=c;			// durchstellen
 }
 if (f&0x40) {			// SNIFF
  i=SerialInReport.dlen;	// Über Registervariable gehen
  if (TCCR0B&4) {		// Uhr läuft nicht?
   TIFR0|=1;			// Überlaufinterruptflag löschen
   TCNT0=byte(-F_CPU*TO_IN/1024/TO_IN);	// 99
   TCCR0B=5;			// Vorteiler 1024: Zählfrequenz 15625 Hz, Überlauffrequenz 61 Hz (16 ms)
  }
  if (i<62) {
   SerialInReport.data[i]=c;
   SerialInReport.dlen=++i;
  }else SerialCfgReport.lsr|=0x40;	// SNIFFOVER: Puffer voll (wurde nicht abgeholt)
 }
 if (f&0x20) {			// RTS/CTS-Handshake?
  if (i<60 && (f&0x40 || !(PIND&0x20)))
       PORTD&=~0x40;		// AVR:RTS/RS232:CTS: Gegenseite darf senden
  else PORTD|=0x40;		// AVR:RTS/RS232:CTS: Gegenseite sollte Datenausstoß einstellen
 }
 c=SerialCfgReport.msr;
 if ((PIND>>1^c)&0x10) {	// Pegelwechsel?
  c^=0x10;			// Neuen Pegel merken
  c|=0x01;			// Pegelwechsel vermerken
  SerialCfgReport.msr=c;	// rückspeichern
 }
}

// Ist das Senderegister leer, kam wohl nichts von RxD hinein.
// Dann USB-Daten einfügen.
// Liegen keine USB-Daten mehr vor, wird der Interrupt deaktiviert.
static char UsbTxdSendIdx;
ISR(USART1_UDRE_vect) {
 UCSR1B=0x98;	// Bit 5 löschen: Keine Interruptverschachtelung!
 sei();
 char i=UsbTxdSendIdx;
 if (i<SerialOutReport.dlen) {
  if (SerialCfgReport.mcr&0x04 && PIND&0x40) {
   SerialCfgReport.mcr|=0x80;	// verwerfen wegen RTS-Handshake (Overflow)
  }else{
   UDR1=SerialOutReport.data[i];	// ausgeben
   UsbTxdSendIdx=++i;
   UCSR1B=0xB8;	// Bit 5 wieder setzen
  }
 }
}

// Aufzurufen nach Änderung durch einen USB-Schreibbefehl
static void SerialCfgChange() {
 UBRR1=(F_CPU/16+(SerialCfgReport.baudRate>>1))/SerialCfgReport.baudRate;
// UCSR1D=SerialCfgReport.mcr&0x20?0x02:0x00;	// Automatisches CTS einstellen
}
// Aufzurufen vor einem USB-Lesebefehl
static void SerialCfgUpdate() {
 SerialCfgReport.msr=SerialCfgReport.msr&0xF0|PIND>>1&0x10;	// CTS einbauen
}
// Aufzurufen nach einem USB-Lesebefehl
static void SerialCfgReset() {
 SerialCfgReport.lsr=0;		// Fehler löschen
 SerialCfgReport.msr=0;		// Statuswechsel löschen
}

static void SerialOutChange() {
 if (SerialOutReport.dlen) {
  UsbTxdSendIdx=0;
  UCSR1B=0xB8;			// Bit 5 setzen: Daten ausgeben lassen
 }
}

static void SerialPoll() {
 if (!(UCSR1B&0x20)) {		// Serielle Ausgabe „arbeitslos“?
  if (usbEp2Recv(&SerialOutReport)) SerialOutChange();
 }
 if (SerialInReport.dlen>40	// Puffer erreicht Hochwasserstand (nur bei hoher Baudrate möglich)
 || TIFR0&1) {			// oder Timeout
  byte ucsr1b=UCSR1B;
  UCSR1B=ucsr1b&~0x80;		// Empfängerinterrupt deaktivieren
  if (usbEp1Send(&SerialInReport))
    SerialInReport.dlen=0;	// Daten abgeschickt, Platz für neue Daten
  TCCR0B=0;			// Uhr anhalten
  UCSR1B=ucsr1b;		// Empfängerinterrupt freigeben
 }
}

/***********************************************
 * Generelle Initialisierung und Hauptprogramm *
 ***********************************************/

// Asynchrones Update: Wartet nicht sondern stößt EEPROM-Schreibvorgang nur an
static void eeprom_update(const void*ram, void*eeprom, size_t len) {
 if (EECR&0x02) return;
 const byte*src=(const byte*)ram;
 byte*dst=(byte*)eeprom;
 for (;len;src++,dst++,len--) if (eeprom_read_byte(dst)!=*src) {
  EEDR=*src;	// Adresse steht noch in EEAR
  cli();
  EECR|=0x04;
  sei();
  EECR|=0x02;	// immer noch mit gesperrten Interrupts (wird zu SBI compiliert)
  return;
 }
}

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;
 WDTCSR= 0x00;	// Anscheinend aktiviert der AVR-Urlader den Watchdog: dekativieren!
 CLKPR = 0x80;
 CLKPR = 0x00;	// CLKDIV8-Fuse ist ab Werk gesetzt (beim Arduino Leonardo gelöscht)
 SMCR  = 0x01;	// Sleep aktivieren
 PRR0  = 0x84;	// I²C und SPI aus, wird nicht benötigt
 DDRB  = 0xFF;	// Ausgänge aktivieren
 DDRC  = 0xC0;
 DDRD  = 0xBB;
 PORTE = 0x44;	// Pullups für Übertromdetektor-Eingang und !HWB aktivieren
 DIDR0 = 0xF3;	// PF0..PF7 deaktivieren für A/D-Wandler
 DIDR1 = 0x01;	// PE6 deaktivieren für Analogvergleicher (Überstromdetektor Digitalausgänge)
 if (PINE&0x04) {	// Der Schalter an !HWB schließt nicht nach Masse kurz?
  DDRE|= 0x04;	// !HWB als PWM-Ausgang S7 umfunktionieren
  DacReport.flags=0x40;	// Analogausgang = SoftPWM S7 aktiv melden
 }
 PORTE&=~0x04;	// An !HWB 0 V ausgeben oder nur den Pull-Up deaktivieren

 ADMUX = 0x40;	// für 1. Wandlung
 ADCSRA= 0xFF;	// A/D-Wandler: Stetige Messung mit Vorteiler 128 und Interrupt
 ADMUX = 0x41;	// für 2. Wandlung
 ACSR  = 0x1B;	// Analogvergleicher aktivieren: Interrupt bei fallender Flanke

// Ansonsten mit Null (im BSS) initialisierte Reports füllen,
// spart Flash-Speicher zur Vorhaltung der Initialisierungsdaten
 AdcReport.reportId=1;
 DacReport.reportId=2;
 DioReport.reportId=3;
 if (eeprom_read_byte((uint8_t*)&eeSerialCfgReport.reportId)==4)
   eeprom_read_block(&SerialCfgReport,&eeSerialCfgReport,sizeof SerialCfgReport);
 SerialInReport.reportId=5;
 SerialOutReport.reportId=6;
 UCSR1B=0x98;	// Sender + Empfänger aktiveren, Empfänger mit Interrupt, 8 Bit, 1 Stoppbit
 SerialCfgChange();	// Baudrate setzen
 DacInit();
 USBCON=0x31;	// OTG-Pad überwachen mit Interrupt — um den Rest kümmert sich usbPoll()
}

int main() {
 setupHardware();
 for (;;) {
#ifdef DEBUG
  PORTB&=~0x10;	// Digitalausgang 5
#endif
  byte b=UDINT&1?0x30:0x21;	// Reset-Ende und Wakeup bzw. Suspend
  cli();
// USB-Interrupts freigeben
  UDIEN=b;
  UENUM=2; UEIENX=0x04;		// OUT
  UENUM=0; UEIENX=0x08;		// SETUP
  sei();
  sleep_cpu();	// spart ein paar mA
  cli();
// USB-Interrupts wieder sperren — außer OTG-Pad (selten; wird eh' zuerst behandelt)
  UEIENX=0;
  UENUM=2; UEIENX=0;
  sei();
  UDIEN=0;	// dieser Befehl noch mit gesperrten Interrupts
#ifdef DEBUG
  PORTB|= 0x10;	// Digitalausgang 5
#endif
  usbPoll();	// Aktionen
  SerialPoll();
  eeprom_update(&SerialCfgReport,&eeSerialCfgReport,sizeof SerialCfgReport);
 }
}

void onEp0GetReport(int wValue) {
 switch (wValue) {
  case 0x0301: usbEp0Send(&AdcReport, sizeof AdcReport); AdcReport.flags=0; break;
  case 0x0302: usbEp0Send(&DacReport, sizeof DacReport); break;
  case 0x0303: usbEp0Send(&DioReport, sizeof DioReport); break;
  case 0x0304: SerialCfgUpdate(); usbEp0Send(&SerialCfgReport, sizeof SerialCfgReport); SerialCfgReset(); break;
  case 0x0105: usbEp0Send(&SerialInReport, sizeof SerialInReport); SerialInReport.dlen=0; break;
 }
}

void onEp0SetReport(int wValue) {
 switch (wValue) {
  case 0x0302: usbEp0Recv(&DacReport,sizeof DacReport); DacChange(); break;
  case 0x0303: usbEp0Recv(&DioReport,sizeof DioReport); DioChange(); break;
  case 0x0304: usbEp0Recv(&SerialCfgReport,sizeof SerialCfgReport); SerialCfgChange(); break;
  case 0x0206: usbEp0Recv(&SerialOutReport,sizeof SerialOutReport); SerialOutChange(); break;
 }
}
Detected encoding: UTF-80