Source file: /~heha/basteln/Konsumgüter/Durchschlagprüfer/hv.zip/hv.cpp

#include <avr/io.h>
#include <avr/pgmspace.h>
#include <avr/interrupt.h>
#include <avr/sleep.h>
#include <avr/eeprom.h>
#include <util/delay.h>
#include <avr/fuse.h>
#include <avr/signature.h>	// gepatcht: "const" weg!

FUSES={
 0b11100010,	// kein Taktteiler, normaler Startup, RC-Oszillator 8 MHz
 0b01111101,	// kein RESET, EEPROM-Inhalt behalten, Brownout bei 2,7 V
 0b11111111,	// Keine Selbstprogrammierung
};

/*
Hardware: ATtiny44
1	Ucc	5P
2  PB0		K	Tasten, H-aktiv
3  PB1		K2	Spaltendekoder Katode2
4  PB3		K1	Spaltendekoder Katode1
5  PB2		K0	Spaltendekoder Katode0
6  PA7	OC0B		Transistor Hochspannungsgenerator
7  PA6	OC1A		Lautsprecher-Ausgang
8  PA5			Strommess-Bereichsumschaltung, nur L oder Z
9  PA4	ADC4		Strommessung
10 PA3	ADC3		Spannungsmessung
11 PA2	AIN1		Schnelle Strommessung Transistor-Emitter
12 PA1		C	Schieberegister-Takt '164 auf Anzeige-Platine
13 PA0		D	Schieberegister-Daten, H-aktiv
14	GND	00
Schieberegister-Anschluss (Schieberichtung MSBfirst)
3  0	QA	a	„1/s“	S5
4  1	QB	b	-	S6
5  2	QC	c	„1/min“	S4 = P/R
6  3	QD	d	-	S3 = PfeilRechts
10 4	QE	e	„1/h“	S1 = PfeilLinks
11 5	QF	f	-	S2 = PfeilHoch
12 6	QG	g	-	-
13 7	QH	dp	-	-

Bedienkonzept: ↑ = drücken (L-H), ↓ = Loslassen (H-L),  [p = vorhergehender Zustand]
		Weiter:	Links	Hoch	Rechts	P/R	Timeout
[0] Intro/Aus
xxxoFF	Sollspannung anzeigen, aber ausgeschaltet, "off" blinkend
xx oFF	(wenn dritte Kommastelle Null, bspw. "4.0 off" bei Prüfprogramm 4 kV)
		[1]	↓	-	↓	-	-	(ODER)
		sonst wie [1]
[1] Normalmodus (beides anzeigen wie eine Laborspannungsquelle):
xxxyyy	Anzeige Spannung (in kV) und Strom (in mA)
	Blinkender Punkt dazwischen
		[0]	-	↑	-	-	-
		[2]	↓	-	-	-	-	(UND)
		[3]	-	-	↓	-	-
		[4]	H	-	↓	-	-
		[5]	↓	-	H	-	-
		[6]	-	H	-	-	0,5 s
		[7]	L	-	L	↑	-
		[8]	H	-	L	↑	-
		[9]	L	-	H	↑	-
		[A]	H	-	H	↑	-
[2] Nur Spannung anzeigen (PfeilLinks)
u xxxx	(aktuelle) Spannung in Volt
		[1]	↓	-	-	-	-	(UND)
		sonst wie bei [1]
[3] Nur Strom anzeigen (PfeilRechts)
i xxxx	(aktueller) Strom in mA/µA
		[1]	-	-	↓	-	-	(UND)
		sonst wie bei [1]
[4] Nur Spannung ODER Strom anzeigen, je nach Begrenzung
u xxxx	(aktuelle) Spannung in Volt
i xxxx	(aktueller) Strom in mA/µA
		[1]	H	-	↓	-	-	(UND)
		[1]	↓	-	H	-	-
		sonst wie bei [1]
[5] Widerstand / Leitwert / Leistung anzeigen
r xxxx	(aktueller) Widerstand in kΩ, MΩ
G xxxx	(aktueller) Leitwert in µS, nS
P xxxx	(aktuelle) Leistung in mW, W
		[r-G-P]	-	-	↓	-	-	(UND)
		sonst wie bei [1]
[6] Prüfmodus (PfeilHoch)
yyy tt	Anzeige Strom (in mA/µA) und Restzeit (in s)
	Ende durch Timeout (Okaypiep), Überstrom (Fehlerpiep) oder beliebige Taste
		[p]	↑	↑	↑	↑	-	(ODER)
PASS							t*s
FAIL								Überstrom
	Während dieser Text dasteht, ist die Spannungsquelle abgeschaltet,
	sofern der vorhergehende Zustand [0] ist.
		[p]	-	-	-	-	15 s
[7] Einstellmodus (Taste P/R)
U xxxx	(maximale) Spannung in Volt
I xxxx	(maximaler) Strom in mA/µA (mit verschieblicher Kommastelle)
	bspw. "01.23" mit blinkender "3", dann PfeilHoch: "1.234" mit blinkender "4".
	Jedoch nicht wenn vorn keine Null steht!
	bspw. "9.234" mit blinkender "9", dann PfeilRechts: "10.23" mit blinkender "0".
t   xx	Prüfzeit in s
	Hell/dunkel (nicht ein/aus!) blinkende Ziffer
	Stellenauswahl mit PfeilHoch
	Zahl hoch/runter mit PfeilLinks, PfeilRechts
	Beenden mit P/R (1 s) oder Timeout 15 s,  immer mit Speichern
		[z-]	↑	-	-	-	-
		[d+]	-	↑	-	-	-
		[z+]	-	-	↑	-	-
		[U-I-t]	-	-	-	↑	-
		[p]	-	-	-	H	0,5 s
		[p]	-	-	-	-	15 s
[8] Einstellen PID-Reglerparameter Spannungsreglung (PfeilLinks und P/R)
P xxxx	hexadezimaler vzl. P-Parameter
I xxxx	hexadezimaler vzl. I-Parameter (I hier linksbündig)
d xxxx	hexadezimaler vzl. D-Parameter
		Bedientasten wie im Einstellmodus

[9] Einstellen PID-Reglerparameter Stromreglung (PfeilRechts und P/R)
	wie [8]

[A] dritte Hintertür (PfeilLinks+PfeilRechts und P/R)
yymmdd	Firmware-Versionsanzeige
hhmmss	Betriebsstundenzähler
RES  x	Auswahl Betriebsart nach Reset (0, 1, 2, 3, 4 oder 5)

LEDs:	1/s	kV (sonst V)
	1/min	mA
	1/h	µA
*/
// 4 Tasten
#define KL 0x10	// Key Left
#define KU 0x04
#define KR 0x08
#define KP 0x20
// 3 LEDs
#define LS 0x01
#define LM 0x04
#define LH 0x10

typedef unsigned char byte;
typedef unsigned short word;

// Ziffern
#define _0 0x3F
#define _1 0x06
#define _2 0x5B
#define _3 0x4F
#define _4 0x66
#define _5 0x6D
#define _6 0x7D
#define _7 0x07	// Alternative 0x27
#define _8 0x7F
#define _9 0x6F
#define DP 0x80	// Dezimalpunkt (zum VerODERn)
// Buchstaben
#define _A 0x77
#define _b 0x7C	// kann mit Z6 verwechselt werden
#define _C 0x39
#define _c 0x58
#define _d 0x5E
#define _E 0x79
#define _F 0x71
#define _G 0x3D
#define _H 0x76
#define _h 0x74
#define _I 0x06	// wie Z1
#define _i 0x04
#define _J 0x1E
#define _L 0x38
#define _n 0x54
#define _O 0x3F	// wie Z0
#define _o 0x5C
#define _P 0x73
#define _q 0x67	// kann mit Z9 verwechselt werden
#define _r 0x50
#define _S 0x6D	// wie Z5
#define _t 0x78	// umgekehrtes F
#define _U 0x3E
#define _u 0x1C
#define _Y 0x6E
// Sonstiges
#define MI 0x40	// Minus
#define __ 0x08	// Unterstrich

// Ziffern-zu-Siebensegment-Tabelle
PROGMEM const byte CodeTab[16]={
/* 0..7 */	_0,_1,_2,_3,_4,_5,_6,_7,
/* 8..F */	_8,_9,_A,_b,_C,_d,_E,_F};

static byte digits[7];	// Index 6 = zusätzliche LEDs
static volatile byte keystate;
static volatile byte keychange;
static byte digit;	// 0..7, 7 = Tastenabfrage, in den oberen 3 Bits
static byte dim;	// Bit 7:5 = gedimmte Stelle, Bit 4:0 = Helligkeit
static volatile byte t122;	// Sub-Teiler zur Zeitmessung (Tonlänge, Timeout)

// Interruptfrequenz 8 MHz / 256 = 31,25 kHz
// Software-Teiler /256 = 122 Hz (LED-Bildwiederholfrequenz)
ISR(TIM0_OVF_vect) {
 byte d=++digit;
 if (d==dim) PORTB=0x0E; // Aus nach Zeit
 if (d&0x1F) return;	// Software-Teiler /32
 PORTB=0x0E;		// Nicht vorhandene Katode ansteuern: Alle LEDs aus
 if (d==0xE0) {
  PORTA&=~0x01;		// Schieberegister leeren
  byte b=6;
  do{
   PORTA|= 0x02;
   PORTA&=~0x02;
  }while(--b);
  PORTA|=0x01;		// Einzelnes gesetztes Bit durchschieben
  b=0x01;		// Endemarker
  do{
   PORTA|= 0x02;
   PORTA&=~0x01;
   b<<=1;
   PORTA&=~0x02;
   if (PINB&0x01) ++b;	// Taste einlesen
  }while(!(b&0x40));	// bis 6 Bits drin sind
  b&=~0x40;
  keychange|=keystate^b;// geänderte Bits der Hauptschleife melden
  keystate=b;		// Tastenzustand
  ++t122;
  return;		// Display bleibt dunkel
 }
 d>>=5;
 byte b=digits[d];
 byte i=8;
 do{
  if (b&0x80) PORTA|=0x01; else PORTA&=~0x01;
  PORTA|= 0x02;		// Takt
  b<<=1;
  PORTA&=~0x02;
 }while(--i);
 b=0;
 if (d&1) b|=0x04;
 if (d&2) b|=0x08;
 if (d&4) b|=0x02;
 PORTB=b;		// Gemeinsame Katode ansteuern
}

// Bei Überstrom OC0B deaktivieren
ISR(ANA_COMP_vect) {
 TCCR0A=0x03;	// Fast-PWM belassen
}

static word akku[2];	// nur für ISR
static volatile word voltage,current;
static byte adc_cnt;	// nur für ISR
static volatile bool adc_int;
static volatile bool i_lim;	// im Strombegrenzungsmodus

ISR(ADC_vect,ISR_NOBLOCK) {
 if ((ADMUX^=0x07)&0x04) akku[0]+=ADC;
 else akku[1]+=ADC;
 if (!(adc_cnt+=2)) {
  voltage=akku[0];
  current=akku[1];	// mit 37 Hz ein neuer 16-Bit-Wert
  akku[0]=akku[1]=0;
  adc_int=true;
 }
}

EMPTY_INTERRUPT(PCINT0_vect)

static void setstr_P(const byte str[6]) {
 memcpy_P(digits,str,6);
}

union regler_t{
 word pid[3];
 struct{
  word p,i,d;
 };
};

EEMEM struct config_t{
 word max_u; 	// U in V	(0 .. 9.999 kV)	4.000 kV
 word max_i;	// I in 1/10 µA	(0 .. 999.9 µA)	100.0 µA
 word max_p;	// P in 1/10 mW	(0 .. 999.9 mW)	10.00 mW
 word max_t;	// T in s	(0 .. 999 s)	60 s
 byte startstate;
 byte rsv;
 regler_t reg_u,reg_i;
}config_e={
 4000,1000,100,60,0,0,
 {{0x100,0,0}},
 {{0x100,0,0}},
};

config_t config;

// Warte t/122 Sekunden (also max. 2 Sekunden)
static void pause(byte t) {
 t+=t122;
 do sleep_cpu(); while(t!=t122);
}

static void hexout(word value,byte start, byte len) {
 do{
  digits[start++]=pgm_read_byte(CodeTab+(value>>12))|(len==1?DP:0);
  value<<=4;
 }while(--len);
}

// vzl. Zahl mit Komma rechtsbündig ausgeben ohne (oder mit) führenden Nullen
// zlen = Ziffernlänge inkl. führender Nullen, sollte >= dp sein
// (die führenden Nullen werden zum Editieren benötigt)
// glen = Displaylänge, Digits nach vorn werden abgeschaltet
static void numout(word value, char dp=0, byte d0pos=5, char zlen=0, char glen=3, byte base=10) {
 do{
  byte z=value%base;
  value/=base;
  z=pgm_read_byte(CodeTab+z);
  if (!dp) z|=DP;
  digits[d0pos]=z;
  dp--;
  d0pos--;	// nach links
  glen--;
 }while (--zlen>=0 || value);
 for(;glen>0;glen--) {digits[d0pos]=0; d0pos--;}
}

static byte state,prev;

// vzl. Gleitkommazahl dreistellig ausgeben
// Mit dp<0 und value<1000 ohne Dezimalpunkt
// Bei value>1000 automatische Anpassung des Dezimalpunktes und
// Weglassen nachfolgender Stellen
static void dezout(word value,char dp=0,byte d0pos=2) {
 for (;value>=1000;value/=(byte)10) if (--dp<0) {
  dp+=3;
  digits[6]|=d0pos==5?LM:LS;	// "kV" oder "mA" ein
 }
 if (!state && d0pos==2 && dp && !value%(byte)10) {
  value/=(byte)10;
  digits[2]=0;
  dp--;
  d0pos--;
 }
 numout(value,dp,d0pos,dp);
}

// Tastenbehandlung; serialisiert Tastenereignisse
// Returnwert:
// Bit 7 = KeyUp
// Bit 6 = KeyDown
// Bit 3:0 = Tastennummer (hier nur 0..3)
static byte keyEventToState() {
 byte kc=keychange;
 byte ks=keystate;
 keychange=0;
 if (kc&~ks) return 0xFF;	// Loslass-Ereignisse ignorieren
 if (~ks&kc&KL) return 2;
 if (~ks&kc&KR) return 3;
 if (ks&KL && ~ks&kc&KR) return 4;
 if (ks&KR && ~ks&kc&KL) return 5;
 if (ks&kc&KU) return 6;
 if (ks&kc&KP) return 7+(ks&KL?1:0)+(ks&KR?2:0);
 return 1;
}

static void waitEvent() {
 do sleep_cpu();
 while (!keychange);
}

static void waitEvent(byte t) {
 t+=t122;
 do sleep_cpu();
 while (!keychange && t122!=t);
}

//int main() __attribute__((noreturn));
int main() {
 PORTA  = 0x00;
 DDRA   = 0xC3;
 PORTB  = 0x0E;
 DDRB   = 0x0E;
 TIMSK0 = 0x01;		// Überlauf-Interrupt für LED-Multiplex
 OCR0B  = 64;
 TCCR0A = 0x23;		// Schnelle PWM mit 256 Schritten
 TCCR0B = 0x01;		// maximale Taktfrequenz (F_CPU)
 ADMUX  = 0x83;		// mit Kanal 3 beginnen
 ADCSRA = 0xEF;		// A/D-Wandler mit Vorteiler 64, Abtastrate = 4,8 kSa/s
 ADMUX  = 0x84;		// für nächste Wandlung Kanal 4
 DIDR0  = 0xFF;		// Kein digitaler Eingang auf Port A
 PRR    = 0x0A;		// Timer1 und USI deaktivieren
 MCUCR  = 0x20;		// Sleep: Timer muss weiter laufen
 eeprom_read_block(&config,&config_e,sizeof config);
 if (config.startstate<=5) state=config.startstate;
 sei();			// Interrupts aktivieren
// static PROGMEM const byte strHello[6]={_H,_E,_L,_L,_O,0};
// setstr_P(strHello);
 word time,*ref,max,udv=1;
 byte blinkdigit=5;
 for(;;) {
  byte st=0xFF;
  switch (state) {
   case 0x00: {
    dezout(config.max_u);
    digits[3]=_o;
    digits[4]=_F;
    digits[5]=_F;
    waitEvent();
/*    if (keystate&keychange&(KL|KR)) st=1;
    else*/ st=keyEventToState();
   }break;
   case 0x01: {
    dezout(keystate/*voltage*/);
    dezout(keychange/*current*/,0,5);
    waitEvent(122/4);	// warte mit Timeout
    if (keychange) {	// jede Taste bewirkt Statusänderung??
     st=keyEventToState();
     if (st==1) st=0;	// Umschalten 1-0 mit derselben Taste
    }
   }break;
   case 0x02: {
    digits[0]=_u;
    numout(voltage,0,5,0,5);
    waitEvent(122/4);
    if (keychange) {
     st=keyEventToState();
     if (st==state&0x0F) st=1;
    }
   }break;
   case 0x03: {
    digits[0]=_i;
    numout(current,0,5,0,5);
    waitEvent(122/4);
    if (keychange) {
     st=keyEventToState();
     if (st==state&0x0F) st=1;
    }
   }
   case 0x04: {
    bool b=i_lim;
    digits[0]=b?_u:_i;
    numout(b?voltage:current,0,5,0,5);
    waitEvent(122/4);
    if (keychange) {
     st=keyEventToState();
     if (st==state&0x0F) st=1;
    }
   }break;
   case 0x05: digits[0]=_r; numout(voltage/current,0,5,0,5); goto state5;
   case 0x15: digits[0]=_G; numout(current/voltage,0,5,0,5); goto state5;
   case 0x25: digits[0]=_P; numout(voltage*current,0,5,0,5); state5: {
    waitEvent(122/4);
    if (keychange) {
     st=keyEventToState();
     if (st==state&0x0F) st=1;
    }
   }break;
   case 0x06: time=config.max_t<<1; state=0x16;	// nobreak
   case 0x16: {
    dezout(current);
    numout(time>>1,-1);
    waitEvent(122/2);
    if (i_lim) {
     state=0x26;
     static PROGMEM const byte strFail[6]={_F,_A,_I,_L};
     setstr_P(strFail);
    }else if(keychange) {
     st=keyEventToState();
     if (st==6) st=prev;
    }else if (!--time) {
     state=0x26;
     static PROGMEM const byte strPass[6]={_P,_A,_S,_S};
     setstr_P(strPass);
    }
   }break;
   case 0x26: {
    waitEvent();
    st=keyEventToState();
    if (st==6) st=prev;
   }break;
   case 0x07: digits[0]=_U; ref=&config.max_u; max=9999; goto state7;
   case 0x17: digits[0]=_I; ref=&config.max_i; max=9999; goto state7;
   case 0x27: digits[0]=_P; ref=&config.max_p; max=9999; goto state7;
   case 0x37: digits[0]=_t; ref=&config.max_t; max=999; state7: {
    if (udv>max) {udv=1; blinkdigit=5;}
    numout(*ref,0,5,0,5);
    waitEvent();
    byte kc=keychange;
    byte ks=keystate;
    keychange=0;
    if (ks&kc&KP) {
     state=state+0x10&~0x40;
    }else if (ks&kc&KL) {
     *ref-=udv;
     if (*ref>max) *ref=0;
    }else if (ks&kc&KR) {
     *ref+=udv;
     if (*ref>max) *ref=max;
    }else if (ks&kc&KU) {
     udv*=10; blinkdigit--;
     if (udv>max) {udv=1; blinkdigit=5;}
    }
   }break;
  }
  if (st!=0xFF) {
   prev=state;		// Anzeige wechseln
   state=st;
  }
  digits[6]^=LM;
//  if (adc_int) {
//   hexout(voltage,0,3);
//   hexout(current,3,3);
//   adc_int=false;
//  }
//  pause(30);
 }
}
Detected encoding: UTF-80