/* 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-8 | 0
|