Source file: /~heha/mb-iwp/Punktschweißzange/Firmware.zip/psz1.c

/* Programm für Punktschweißzange „TZ 5.0“ (VEB Elektroschweißtechnik Dresden)
 * für ATtiny13
 * Hardware:
 * Die Zange hat zwei Einstellpotenziometer:
 * - Schweißstrom, 1 % bis 100 % (neu; per Phasenanschnitt)
 * - Schweißdauer, 10 ms bis 1200 ms (½ bis 60 Netzperioden, bei 60 Hz geringere Zeiten!)
 * einen Schalter in der Zündstromleitung sowie einen Taster zum Auslösen
 * am Griff. Eine Piezokapsel wurde zusätzlich eingebaut (Fehlermeldung).
 * Die Phasenmessung erfolgt per Spannungsteiler und A/D-Wandler!
 * Betrieben wird die Schweißzange mit 400 V einphasiger Wechselspannung.
 * Die Schaltung und Firmware ist auch für 230 V Wechselspannung
 * (dann natürlich mit bedeutend reduzierter Schweißleistung)
 * sowie 50 bis 60 Hz ausgelegt.
 * „h#s“ Henrik Haftmann, TU Chemnitz, 12. Januar 2009
 *
 * tabsize = 8, encoding = utf-8 (ohne Windows-Header BOM)
 */

#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/eeprom.h>
#include <util/delay_basic.h>

#define DEBUG

/************
 * Hardware *
 ************/
/*
Verwendung der Ports:
PB0	(5)	OC0A	Taster (über Widerstand, low-aktiv); Piezokapsel
PB1	(6)	-	Triac-Zündimpuls low-aktiv
PB2	(7)	ADC1	Potenziometer (Spannungsteiler) Schweißdauer
PB3	(2)	ADC3	Triac-Anodenspannung (250tel), mittensymmetrisch
PB4	(3)	ADC2	Potenziometer (Spannungsteiler) Schweißstrom
PB5	(1)	-	Reset (frei für In-System-Programmierung)
*/
#define CPUCLK 1200000	// Hz: 1,2 MHz (Standard)

#define MAXCT0 (12*CPUCLK/1000/64)	// Halbperiodendauer max. 12 ms,
					// sonst Trafo im Leerlauf?

typedef unsigned char uchar, bool;
typedef unsigned short ushort;
typedef unsigned long ulong;
enum {false, true};

typedef struct{
 signed char Remanenz;	// Remanenz des Trafokerns für knallfreies Einschalten*
 uchar SWinkel;		// Mindeststartwinkel (Trafokenngröße)
 uchar MaxZeit;		// Maximale Schweißzeit in Netzperioden
 uchar MinKuehl;	// Minimale Abkühlzeit in Netzperioden (sonst piept's)
 uchar LastError;	// Letzter Fehler (Piepcode)
 uchar unused[3];
 ulong usage;		// Anzahl Tastenbetätigungen
 ulong welds;		// Anzahl erfolgreiche Schweißungen
}persistent_t;

//* vorerst nur als Vorzeichen gespeichert
 
EEMEM persistent_t eeState;

persistent_t ramState;

register uchar PosPegel	asm("r7");	// Darüber ist „positive Spannung“
register uchar NegPegel	asm("r6");	// Darunter ist „negative Spannung“
register bool NegWelle	asm("r5");	// 0 wenn positiv, ~0 wenn negativ
register uchar T2	asm("r4");	// halbe Periodendauer in Timerticks
register uchar ZWinkel	asm("r3");	// tatsächlicher Zündwinkel, in Timerticks

/**************************
 * ATtiny-interner EEPROM *
 **************************/
// entnommen aus h#s USB2LPT

// Updates an ATmega-internal EEPROM location when different
static void ActualizeEepromByte(uchar *ee_addr, uchar b) {
 if (eeprom_read_byte(ee_addr)==b) return;
 EEDR =b;
 cli();
 EECR |= 1<<EEMPE;
 EECR |= 1<<EEPE;	// don't wait here!
 sei();
}

// Updates EEPROM out of RAM area, must be called periodically, does not block
static void ActualizeEepromB(const uchar *pRam, uchar *pEe, uchar n) {
 if (!n) return;
 do{
  if (!eeprom_is_ready()) return;
  ActualizeEepromByte(pEe++,*pRam++);
 }while(--n);
}
#define ActualizeEeprom(r,e,l) ActualizeEepromB((const uchar*)r,(uchar*)e,l)

/***********
 * Piepser *
 ***********/

// Haltebedingung (nur) für Beep
// Die zusätzlichen Umladevorgänge sorgen für einen schrillen Ton
// (es ginge ja auch eleganter)
static bool Taste(void) {
 DDRB &= ~0x01;
 _delay_loop_1(100);
 if (PINB&0x01) return false;
 DDRB |= 0x01;
 return true;
}

// Piep ausgeben (Anzahl Perioden (0=∞) oder bis Taster losgelassen)
static void Beep(uchar perilen, uchar periods, bool KeyReleased) {
 DDRB |= 0x01;		// als Ausgang setzen (keine Tastenabfrage)
 OCR0A = perilen;
 TCCR0A = 0x42;		// Timer0: CTC-Modus, OC0A toggelt bei Überlauf
 do{
  do; while (!TIFR0);	// warten bis Interrupt-Flag
  TIFR0 = 0xFF;		// Bit löschen
  if (!KeyReleased && !Taste()) break;
  do; while (!TIFR0);	// noch einmal (ganze Periode)
  TIFR0 = 0xFF;
 }while (!periods || --periods);
 TCCR0A = 0;		// Timer0: normaler Modus
 DDRB &= ~0x01;
 _delay_loop_1(100);
}

/***********************************
 * Nulldurchgang und Zündzeitpunkt *
 ***********************************/

static uchar GetADC(void) {
 ADCSRA |= 0x40;	// A/D-Wandler starten
 do; while (ADCSRA&0x40);	// warten bis fertig (ca. 166 µs)
 return ADCH;		// Wert liefern
}

// Sucht Minimal- und Maximalwert der Spannung und „schätzt“ daraus den
// Mittelwert (also den Nullpegel bei nicht gezündetem Triac)
static void SuchNullpegel(void) {
 uchar z = 0, min = 255, max = 0, val, n, b;
 do{	// 256 Runden genügen, sind mindestens 42 ms
  val = GetADC()>>1;	// 7 Bit reichen
  if (min>val) min = val;
  if (max<val) max = val;
 }while(--z);
 n = max+min;		// sollte ca. 80h sein
 b = (max-min)>>6;	// 1/64 der Gesamtspannung
 if (!b) b=1;		// Nullbereich mindestens 2
 PosPegel = n+b;
 NegPegel = n-b;
}

// Wartet Nulldurchgang ab; liefert FALSE bei Zählerüberlauf
// Außerdem wird eine EEPROM-Aktualisierung angeschubst
static bool ZeroCross(void) {
 static uchar tp;
 ActualizeEeprom(&ramState,&eeState,sizeof(persistent_t));
 do{
  if (TCNT0 >= MAXCT0) return false;
 }while ((NegWelle && (GetADC()<=PosPegel)) || (!NegWelle && (GetADC()>=NegPegel)));
 if (NegWelle) {
  T2 = ((ushort)tp+TCNT0)>>1;
 }else{
  tp = TCNT0;
 }
 TCNT0 = 0;
#ifdef DEBUG
 DDRB |= 0x08;		// in Oszibild sichtbar machen
 _delay_loop_1(8);
 DDRB &= ~0x08;
#endif
 NegWelle = ~NegWelle;
 return true;
}

// Triac zünden: 5 Zündimpulse (je 1,6 µs, alle 6 µs) ausgeben
static void FireNow(void) {
 uchar z = 5;
 PORTB &= ~0x02;	// Low vorbereiten
#ifdef DEBUG
 DDRB |= 0x08;		// Marker setzen
 PORTB |= 0x08;
#endif
 do {
  DDRB |= 0x02;		// als Ausgang (Low)
  asm volatile ("nop");
  DDRB &= ~0x02;	// als Eingang (nicht unnötig Kapazitäten umladen)
  _delay_loop_1(4);
 }while (--z);
#ifdef DEBUG
 DDRB &= ~0x08;
 PORTB &= ~0x08;
#endif
 PORTB |= 0x02;		// als Eingang mit Pull-Up
}

// warten per Timer und dann Triac zünden
// liefert FALSE wenn keine Zündung (i.d.R. Hauptschalter ausgeschaltet)
// oder bei unzureichender/falscher Triac-Spannung vor der Zündung
static uchar Fire(void) {
 uchar val;
 do; while (TCNT0<ZWinkel);
 val = GetADC();
 if (NegWelle && val>NegPegel) return 5;
 if (!NegWelle && val<PosPegel) return 5;
 FireNow();
 val = GetADC();
 if (val>PosPegel || val<NegPegel) return 4;
 ramState.Remanenz = NegWelle;
 return 0;
}

/********************
 * Analoge Eingaben *
 ********************/

static uchar LeseStrom(void) {
 uchar val;
 ADMUX &= 0xFE;		// Potenziometerspannung „Strom“ messen
 val = ~GetADC();	// 0 = 10% (Zündwinkel 90%), 255 = 100% (Zündwinkel 0)
 ADMUX |= 0x01;		// zurück: Anodenspannung messen
 return (ushort)T2*val/283;	// 283 = 255/0.9
}	// 10 % Leistung (nicht Strom) ist bei 75 % Anschnittwinkel!
	// Bei 90 % Anschnittwinkel ist die Leistung 0,6 %!

static uchar LeseDauer(void) {
 uchar val;
 ADMUX &= 0xFD;		// Potenziometerspannung „Dauer“ messen
 val = GetADC();
 ADMUX |= 0x02;		// zurück: Anodenspannung messen
 return 1+((ushort)(ramState.MaxZeit-1)*(ushort)val)/255;
}

/*************************************
 * Initialisierung und Hauptschleife *
 *************************************/

static void hardwareInit(void) {
 ACSR |= 0x80;		// Analogvergleicher ausschalten
 PORTB = 0x03;		// Port-Pullups EIN
 ADMUX = 0x23;		// Anodenspannung messen, 8 bit linksbündig
 ADCSRA = 0x83;		// A/D-Wandler aktivieren (noch nicht starten)
 DIDR0 = 0x3C;		// digitale Eingänge nicht nutzen
 eeprom_read_block(&ramState,&eeState,sizeof(persistent_t));
 if (ramState.SWinkel==0xFF) ramState.SWinkel=0;	// unprogrammiert
 if (ramState.MaxZeit==0xFF) ramState.MaxZeit=120;	// 1,2 Sekunden
 if (ramState.MinKuehl==0xFF) ramState.MinKuehl=50;	// ½ Sekunde
 TCCR0B = 0x03;		// Vorteiler 64; Arbeitsfrequenz ab 37 Hz
}

int __attribute__((noreturn)) main(void) {
 uchar t, NWinkel;	// nächster Zündwinkel
 hardwareInit();
mainloop:		// Hauptschleife: hier als Sprungziel
// Warten bis Taste losgelassen (sonst geht's nicht weiter)
 do{
  ActualizeEeprom(&ramState,&eeState,sizeof(persistent_t));
 }while (!(PINB&0x01));
// Nullpegel und Unsicherheitsbereich abschätzen
 SuchNullpegel();
// warte positive Halbwelle ab und starte Timer
 TCNT0 = 0;
 NegWelle = 0;
 ZeroCross();		// könnte T2=0 liefern
 ZeroCross();		// könnte T2 zu klein liefern
// messe Periodendauer
 ZeroCross();
 ZeroCross();		// jetzt sollte T2 stimmen
// Warte auf Taster
 do ZeroCross(); while (PINB&0x01);
 ramState.usage++;	// Anzahl Tastenbetätigungen
 t = LeseDauer();
 ZWinkel = ramState.SWinkel;	// mit Startwinkel beginnen
 if (ramState.Remanenz!=NegWelle) ZeroCross();
 do{
  NWinkel = LeseStrom();
  if (ZWinkel<NWinkel) ZWinkel = NWinkel;	// Größtwert
  if (!ZeroCross()) {
   Beep(10,80,false);	// Unregelmäßigkeiten der Netzspannung oder Trafo-Leerlauf?
   ramState.LastError = 3;	// Phasenwinkel-Problem (meist: Leerlauf)
   goto mainloop;
  }
  if (PINB&0x01) {	// Taste vorzeitig losgelassen (im Schweißvorgang)
   Beep(15,250,true);
   ramState.LastError = 1;
   goto mainloop;
  }
  switch (Fire()) {
   case 5: Beep(40,80,false);	// Zündung ohne Spannung (Logikfehler)
   ramState.LastError = 5;
   goto mainloop;
   case 4: Beep(20,0,false);	// Zündung erfolglos (Hauptschalter)
   ramState.LastError = 4;
   goto mainloop;
  }
  ZWinkel = NWinkel;  
 }while (--t);
 t = ramState.MinKuehl;		// ½ Sekunde
 do{
  ZeroCross();
  if (PINB&0x01) {	// Taste vorzeitig losgelassen (im Abkühlvorgang)
   Beep(15,100,true);
   ramState.LastError = 2;
   goto mainloop;
  }
 }while (--t);
 ramState.welds++;	// Anzahl „ordentliche“ Schweißungen
 goto mainloop;		// Ruhe wenn Schweißvorgang OK
}
Detected encoding: UTF-80