Source file: /~heha/Mikrocontroller/SLB0587Ersatz.zip/slb0587.a16

;Ansteuerung von Triacs, möglichst pinkompatibel zu SLB0587
;(Nur!) Wegen der günstigen Anordnung der Betriebsspannungsanschlüsse
;wurde hier Microchip PIC12 gegenüber Atmel ATtiny favorisiert.

;Hier gelistete Typen waren gerade zum Test verfügbar.
;Der geeignetste Typ ist 12C508A (OTP: einmal programmierbar, 1,00€)
;oder 12F508 (Flash: wiederprogrammierbar, 1,25€),
;weil billig und keine überflüssige Peripherie eingebaut.
;Für die anderen 8poligen Typen PIC12F629, PIC16F675, PIC12CE673
;sowie den 18poligen „Wald- und Wiesen-PIC“ PIC16F84A
;ist ein Kommandozeilenschalter /p (Prozessor) zu übergeben.

;MPLAB IDE (hierfür überflüssig) schluckt die Endung .A16 nicht.
;Eine Mini-Include-Datei oder Umbenennung zu *.asm löst das Problem.

;Für die Einbindung des PIC-Assemblers MPASM in CR-Edit, UltraEdit
;oder Programmers Notepad (unter Verzicht auf MPLAB IDE und
;damit des Simulators) steht eine PICASM.CMD zur Verfügung,
;die neben MPASM.EXE kopiert werden muss.
;PN2-Einbindung: Parameter "%f /p%?", Prozessortyp angeben!
;MPASM schluckt anstandslos UTF-8 (im Gegensatz zu MPLAB IDE).

;haftmann#software, henrik.haftmann@e-technik.tu-chemnitz.de,
;http://www.tu-chemnitz.de/~heha/Mikrocontroller/SLB0587Ersatz.htm

;061021	erste Ausgabe, ohne "Safety-Cutout", aber schon mit PLL
;	Funktion der Modusauswahl OK, Diode von Pin4 nach Pin1
;061104	Test bestanden, Verbindung Pin3 über Widerstand 100 Ohm
;	zum Gate des Triac
;071219	Korrektur der Initialisierung für PIC12F629/675

#ifdef __16F84A		;übliches, nicht pinkompatibles Testmuster
	list	p=16f84a,r=dec
	include	"p16f84a.inc"
	__config _XT_OSC & _WDT_OFF & _PWRTE_ON
RAMSTART equ 10h	;ab hier freie RAM-Arbeitszellen
#define GPIO PORTA
 messg	"8 Warnungen bitte ignorieren!"
extrainit macro
 endm
 
#else	;dummwerweise kennt MPASM.EXE kein #elseif(def) -> Schachtelung!
 #ifdef __12F675	;Flash, Reichelt: 1,75€
	list	p=12f675,r=dec
	include	"p12f675.inc"
	__config _INTRC_OSC_NOCLKOUT & _WDT_OFF & _MCLRE_OFF & _BODEN_ON &_PWRTE_ON
RAMSTART equ 20h	;ab hier freie RAM-Arbeitszellen
 messg	"8 Warnungen bitte ignorieren!"
extrainit macro
	movlw	ANSEL
	movwf	FSR
	clrf	INDF	;A/D-Wandler totlegen - sonst chaotische Funktion!
	movlw	7
	movwf	CMCON	;Analogvergleicher totlegen
 endm
 
 #else
  #ifdef __12F629	;Flash, Reichelt: 1,45€
	list	p=12f629,r=dec
	include	"p12f629.inc"
	__config _INTRC_OSC_NOCLKOUT & _WDT_OFF & _MCLRE_OFF & _BODEN_ON & _PWRTE_ON
RAMSTART equ 20h	;ab hier freie RAM-Arbeitszellen
 messg	"8 Warnungen bitte ignorieren!"
extrainit macro
	movlw	7
	movwf	CMCON	;Analogvergleicher totlegen
 endm
 
  #else
   #ifdef __12CE673	;OTP, Farnell
	list	p=12ce673,r=dec
	include	"p12ce673.inc"
	__config _INTRC_OSC_NOCLKOUT & _WDT_OFF & _MCLRE_OFF & _PWRTE_ON
RAMSTART equ 20h	;ab hier freie RAM-Arbeitszellen
 messg	"Warnungen bitte ignorieren! Ungetesteter Zielprozessor!"
extrainit macro
	movlw	ADCON1
	movwf	FSR
	movlw	7
	movwf	INDF
 endm
 
   #else	; alles andere:
;PIC12C508A (PIC12C508, PIC12C509A, PIC12C509): OTP, Reichelt: 1,00€
;PIC12F508 (PIC12F509):	Flash, Reichelt: 1,25€
	list	p=12c508a,r=dec
	include	"p12c508a.inc"
	__config _IntRC_OSC & _WDT_OFF & _MCLRE_OFF
RAMSTART equ 08h	;ab hier freie RAM-Arbeitszellen
extrainit macro
 endm
   #endif
  #endif
 #endif
#endif
	
	__idlocs 1207h	;Monat + Jahr der Firmware (BCD)

;#define SIMULATE	;Warteschleifen abkürzen (für neueres MPLAB unnötig)
;#define DEBUG		;EXT (SENSOR2) ist Debug-Ausgang: Marker für Oszi
;#define HELLER		;minimaler Phasenwinkel Null (geht nicht trotz C4!)

;****************
;** Konstanten **
;****************

;Belegung GPIO (Pin-Nummer in Klammern, nicht gültig für 16F84)
TRIAC2	equ	0	;(7) Triac-Steuerausgang, 0=EIN, Z=AUS
SENSOR2	equ	1	;(6) EXTerner Sensor, H=EIN
SENSOR	equ	2	;(5) SENsor, L=EIN (im Abtastzeitpunkt)
USENSE	equ	3	;(4) SYNC, Spannungsüberwachung am Triac
TRIAC	equ	4	;(3) Triac-Steuerausgang, 0=EIN, 1=AUS
MODE	equ	5	;(2) PROG an - (A), offen (B), an + (C)
;Der Triac kann WAHLWEISE an TRIAC oder TRIAC2 angeschlossen werden,
;je nach geeigneterer Modifikation der Leiterplatte oder Schaltung.
;Masseschluss des Ausgangs TRIAC2 ist ohne Folgen.


;************************
;** RAM-Speicherzellen **
;************************
tmp0	equ	RAMSTART+0	;Zwischenwert (Berechnung)
KeyBits	equ	RAMSTART+1	;Diverse Bits zur Tastensteuerung
;0 = Sensor-Drückzähler Bit 0 (Schieberegister-Nachbildung)
;1 = Sensor-Drückzähler Bit 1 (beide =1 gilt als "gedrückt" (>50ms))
;2 = Sensor-Langzeitdruck (>400ms)
;3 = Richtungsbit, 1=hoch, 0=runter (Helligkeit)
;4 = MODE klebt an - (Modus "A" lt. Datenblatt)
;5 = MODE klebt an + (Modus "C" lt. Datenblatt)
;6 = Licht EIN (ansonsten ist "Hell" der Speicherwert)
;7 = Sensor-Zustand (0,9 ms nach dem Nulldurchgang ermittelt), 1 = berührt
KeyCnt	equ	RAMSTART+2	;Zähler für Autorepeat
Hell	equ	RAMSTART+3	;Soll-Helligkeit (Zündwinkel)
Real	equ	RAMSTART+4	;Ist-Helligkeit wegen Softstart (Zündwinkel)
HellMin	equ	RAMSTART+5	;Mindest-Helligkeit (25%)
HellMax	equ	RAMSTART+6	;Höchst-Helligkeit (95%)
T2	equ	RAMSTART+7	;Halbe Periodendauer

CPUCLK	equ	1000000	;Hz (etwa; exakter Wert unkritisch wegen Zeitmessung)
DEFGPIO	equ	3Eh	;Normalzustand für GPIO-Ausgaberegister
#ifdef DEBUG
DEFTRIS	equ	2Dh	;Normalzustand für TRIS-Ausgaberegister: TRIAC+EXT=Ausgang
#else
DEFTRIS	equ	2Fh	;Normalzustand für TRIS-Ausgaberegister: TRIAC=Ausgang
#endif
DEFOPT	equ	0C6h	;keine Pull-Ups(!!), Vorteiler=128, Zählerperiode > 32ms
;Zur Begrenzung der Nulldurchgangs-Suche (15 und 22 sind Millisekunden)
MINCT0	equ	15*CPUCLK/1000/128	;Minimaler "erlaubter" Zählerstand (117)
MAXCT0	equ	22*CPUCLK/1000/128	;Maximaler "erlaubter" Zählerstand (171)
SAMCT0	equ	1*CPUCLK/1000/128	;Zählerstand zum Abtasten von SENSOR (7)

;*********************
;** Programm im ROM **
;*********************
	org	0
	goto	startup

;====================
;== Unterprogramme ==
;====================

AdjustLimits:
;Anhand des gespeicherten Wertes für T2 die Werte HellMin(30%), HellMax(95%)
;berechnen und ggf. "Hell" eingrenzen
	clrc
	rrf	T2,w		;viertel Periodendauer (50%) laden
	movwf	tmp0
	clrc
	rrf	tmp0,w		;achtel Periodendauer (75%)
	movwf	tmp0
	subwf	T2,w		;3/8 Periodendauer (25%)
	movwf	HellMin
	subwf	Hell,w		;w=Hell-HellMin, C=0 wenn Hell<HellMin (OK)
	movfw	HellMin
	skpnc
	 movwf	Hell
#ifdef HELLER
	clrf	HellMax		;immer 1
	incf	HellMax,f
#else
	clrc
	rrf	tmp0,f		;1/16 Periodendauer (88%)
;	clrc
;	rrf	tmp0,f		;dann 1/64 Periodendauer (97%)
	clrc
	rrf	tmp0,w		;1/32 Periodendauer (94%)
	movwf	HellMax
	subwf	Hell,w		;w=Hell-HellMax, C=0 wenn Hell<HellMax (nicht OK)
	movfw	HellMax
	skpc
	 movwf	Hell
#endif
	retlw	0

CheckSensor:
;Bei (etwa) 0,9 ms nach Nulldurchgang STROBE_SEN ausführen
#ifdef DEBUG
	bsf	GPIO,SENSOR2
#endif
	movlw	SAMCT0
	;ggf. addwf T2,w
	xorwf	TMR0,w		;w=TMR0^SAMCT0
	skpz
	 retlw	0
	bcf	KeyBits,7
;SENSOR ist L, wenn eine externe Kapazität (Mensch) den H-L-Wechsel verzögert
	btfss	GPIO,SENSOR
	 bsf	KeyBits,7	;EIN wenn LOW
#ifdef DEBUG
	btfsc	KeyBits,7
	 bcf	GPIO,SENSOR2	;Markierung im Oszibild (wenn berührt)
#endif
	retlw	0

ZeroCross:	;Sucht Nulldurchgang (des Stroms) so dass Spannung negativ wird
;Bei >22ms Periodendauer wird von einem Fehler ausgegangen
;Warten bis USENSE=H, dabei Zählerüberlauf detektieren
zc_1:
	call	CheckSensor
	movlw	MAXCT0
	subwf	TMR0,w		;w=TMR0-MAXCT0, C=0 wenn TMR0<MAXCT0
	skpnc
	 retlw	0		;C=1: Fehler
	btfss	GPIO,USENSE
	 goto	zc_1		;warte solange USENSE=L
zc_2:
	call	CheckSensor
	movlw	MAXCT0
	subwf	TMR0,w
	skpnc
	 retlw	0
	btfsc	GPIO,USENSE
	 goto	zc_2		;warte solange USENSE=H
	movlw	MINCT0
	subwf	TMR0,w		;w=TMR0-MINCT0, C=0 wenn TMR0<MINCT0
	bnc	zc_1		;warte solange zu wenig Zeit vergangen ist
	clrc
	rrf	TMR0,w		;Zählerwert laden und halbieren
	clrf	TMR0		;Zähler neu starten
	movwf	T2		;neue halbe Periodendauer
	clrc
	retlw	0

HandleSensor:	;Aufruf alle 20ms, nachdem USENSE auf L gegangen ist
	btfsc	KeyBits,7	;Bit gesetzt wenn Sensor berührt wird
	 goto	hs_touched
#ifndef DEBUG
	btfsc	GPIO,SENSOR2	;SENSOR2 ist H, wenn externer Taster betätigt
	 goto	hs_touched
#endif
;Sensor nicht berührt: letzten Zustand prüfen und Zählbits nullsetzen
	btfss	KeyBits,0
	 goto	hs_release_0	;Zähler <>3: svw. Schieberegister leeren
	btfss	KeyBits,1
	 goto	hs_release_0
	btfss	KeyBits,2
	 goto	hs_release_short;Licht schalten
	movlw	1<<3		;Richtungsbit
	btfss	KeyBits,4	;Wenn MODE an + klebt: Richtung NICHT ändern
	 xorwf	KeyBits,f
	goto	hs_release_0	;Licht nicht schalten
hs_release_short:
	movlw	1<<6
	xorwf	KeyBits,f	;Licht ein- bzw. ausschalten
hs_release_0:	;svw. Schieberegister leeren
	bcf	KeyBits,0
	bcf	KeyBits,1
	retlw	0

hs_count_on:
	incf	KeyBits,f	;Schieberegister-Nachbildung mit Einsen füllen
	btfss	KeyBits,0
	 retlw	0
	btfss	KeyBits,1
	 retlw	0
;Jetzt geht der Tastendruck los: Wenn Licht aus, dann ggf. Maximum laden
	btfsc	KeyBits,6	;Licht aus?
	 goto	hs_noloadmax
	movfw	HellMax
	btfsc	KeyBits,4	;Klebt an -? Maximum laden!
	 movwf	Hell
	btfsc	KeyBits,5	;Klebt an +? Maximum laden!
	 movwf	Hell
hs_noloadmax:
	bcf	KeyBits,2	;"Kurzer Tastendruck" melden
	movlw	8		;Zähler für 400ms
hs_keycntx:
	movwf	KeyCnt
	retlw	0

hs_touched:	;Sensor für diese Vollwelle berührt
	btfss	KeyBits,0
	 goto	hs_count_on	;Schieberegister-Nachbildung noch nicht gefüllt
	btfss	KeyBits,1
	 goto	hs_count_on
	decf	KeyCnt,f	;Autorepeat-Zähler
	skpz
	 retlw	0		;nichts tun wenn <>0
	btfsc	KeyBits,2	;"Lange" Berührung?
	 goto	hs_longtouch
	btfsc	KeyBits,6	;Hier: Übergang von kurzer zu langer Berührung
	 goto	hs_long_on	;Wenn Licht bereits EIN, kein Minimum laden
	movfw	HellMin
	btfsc	KeyBits,4	;Klebt an -? Minimum laden!
	 movwf	Hell
	btfsc	KeyBits,5	;Klebt an +? Minimum laden!
	 movwf	Hell
	bsf	KeyBits,6	;Licht AN
hs_long_on:
	bsf	KeyBits,2	;auf "lange Berührung" umschalten
	movlw	5		;erster Repeat länger als 3
	goto	hs_keycntx	;Zähler laden
hs_longtouch:
	movfw	HellMin		;besser mit "subwf" arbeiten!
	xorwf	Hell,w		;Minimum ist erreicht?
	skpnz
	 bsf	KeyBits,3	;Richtung RAUF
	movfw	HellMax
	xorwf	Hell,w		;Maximum ist erreicht?
	skpnz
	 bcf	KeyBits,3	;Richtung RUNTER
	btfsc	KeyBits,3
	 decf	Hell,f		;RAUF (Anschnittwinkel verkleinern)
	btfss	KeyBits,3
	 incf	Hell,f		;RUNTER (Anschnittwinkel vergrößern)
	movlw	3
	goto	hs_keycntx

HandleSoftstart:	;Aufruf nach HandleSensor (alle 20ms)
;Nachführung von "Real" an "Hell" - oder AUS
	movfw	HellMin
	btfss	KeyBits,6	;Licht EIN?
	 goto	hs_set		;wenn AUS dann nächsten Softstart vorbereiten
;auf Verdacht Zündwinkel verkleinern, dann begrenzen
	decf	Real,f
	decf	Real,f		;in 2 Schritten
	movfw	Real
	subwf	Hell,w		;W=Hell-Real, C=0 wenn Hell<Real
	movfw	Hell
	skpnc			;überspringe wenn Real>Hell
hs_set:
	 movwf	Real		;wenn Real<=Hell Zündwinkel halten oder vergrößern
	retlw	0

ReadMode:	;Verbindung (nach + oder -) am MODE-Pin prüfen
	bcf	KeyBits,4
	bcf	KeyBits,5
#ifdef DEBUG	;weil mit bcf und bsf herumgefuhrwerkt wird...
	movlw	DEFGPIO&~(1<<SENSOR2)
	movwf	GPIO		;H vorbereiten
#endif
	movlw	DEFTRIS&~(1<<MODE)
	tris	GPIO		;H ausgeben (statt Z vorher)
	movlw	DEFTRIS		;1 µs warten
	btfsc	GPIO,MODE	;wenns an Minus (Uss) klebt
	 goto	rm_nominus
	tris	GPIO		;Treiber sofort entlasten (Kurzschluss!)
	bsf	KeyBits,4
	retlw	0		;Nulldurchgangsmarker kurz (Mode A)
rm_nominus:
#ifdef DEBUG
	movlw	DEFGPIO&~((1<<MODE)|(1<<SENSOR2))
#else
	movlw	DEFGPIO&~(1<<MODE)
#endif
	movwf	GPIO		;L ausgeben
	movlw	DEFTRIS		;1 µs warten (reicht offenbar)
	btfss	GPIO,MODE	;wenns an Plus (Udd) klebt
	 goto	rm_noplus
	tris	GPIO		;Treiber sofort entlasten (Kurzschluss!)
	bsf	KeyBits,5
#ifdef DEBUG
	bsf	GPIO,SENSOR2	;zusätzliche High-Nadel (Mode C: 2 Nadeln)
	bcf	GPIO,SENSOR2
#endif
rm_noplus:
#ifdef DEBUG
	bsf	GPIO,SENSOR2	;zusätzliche High-Nadel (Mode B: 1 Nadel)
	bcf	GPIO,SENSOR2
#endif
	tris	GPIO		;Z ausgeben
#ifdef DEBUG
	movlw	DEFGPIO&~(1<<SENSOR2)
#else
	movlw	DEFGPIO
#endif
	movwf	GPIO
	retlw	0		;Nulldurchgangsmarker mittel (Mode B)

FireNow:	;10 Zündimpulse (je 10µs) ausgeben
	movlw	10
	movwf	tmp0
fn_loop:
	movlw	DEFGPIO&~(1<<TRIAC)
	movwf	GPIO		;L ausgeben
	movlw	DEFTRIS&~(1<<TRIAC2)
	tris	GPIO		;L ausgeben - Zündung
	goto	$+1
	goto	$+1
	goto	$+1
	goto	$+1
	movlw	DEFGPIO
	movwf	GPIO		;H ausgeben
	movlw	DEFTRIS
	tris	GPIO		;Z ausgeben
	decf	tmp0,f
	bnz	fn_loop
	retlw	0

FireNeg:	;Zündung Triac zur negativen (ersten) Halbwelle
fn_wait:
	call	CheckSensor
	movfw	Real
	xorwf	TMR0,w
	bnz	fn_wait
	goto	FireNow		;Maximale Stacktiefe erreicht!
;Bei der negativen Halbwelle wäre die Messung des Zünderfolgs möglich.
;Die dazu notwendige Zeit könnte zur positiven Halbwelle wiederholt werden,
;da hierbei nicht messbar.
	;retlw	0
	
FirePos:	;Zündung Triac zur positiven (zweiten) Halbwelle
fp_wait:
	call	CheckSensor
	movfw	Real
	addwf	T2,w
	xorwf	TMR0,w
	bnz	fp_wait
	goto	FireNow
	;retlw	0
	
;===================
;== Hauptprogramm ==
;===================
startup:
;* Aktion 1:
;  Ports einrichten, Option setzen
	movlw	DEFOPT
	option
	movlw	DEFGPIO
	movwf	GPIO
	movlw	DEFTRIS
	tris	GPIO
	extrainit
;Viertelsekunde warten (nur aus Angst!)
	clrf	KeyCnt
	clrf	tmp0
s_wait:	decf	tmp0,f
	bnz	s_wait
	decf	KeyCnt,f
	bnz	s_wait
	clrf	KeyBits
;* Aktion 2:
;  H-L-Flanke abwarten, Timer starten (Timer zählt aufwärts)
fail:
#ifdef DEBUG
	bcf	GPIO,SENSOR2
#endif
f1:	btfss	GPIO,USENSE
	 goto	f1
f2:	btfsc	GPIO,USENSE
	 goto	f2
	clrf	TMR0
;* Aktion 3:
;  Variablen vorbelegen
	call	ReadMode
	call	ZeroCross	;Periodendauer messen
	call	AdjustLimits
	movfw	HellMax		;damit es im Mode B mit vollem Licht losgeht
	movwf	Hell		;(ansonsten andere Vorgabewerte)
#ifdef DEBUG
	bsf	GPIO,SENSOR2
#endif
;Hauptschleife
mainloop:
	call	ZeroCross
	bc	fail
#ifdef DEBUG
	bcf	GPIO,SENSOR2	;Markierung im Oszibild
#endif
	call	ReadMode
#ifdef DEBUG
	bsf	GPIO,SENSOR2
#endif
	call	HandleSensor	;Etwa nach ZeroCross SENSOR2 einlesen
	call	AdjustLimits
	call	HandleSoftstart
	btfss	KeyBits,6	;weiter nur wenn EIN
	 goto	mainloop
	call	FireNeg
	call	FirePos
	goto	mainloop

	END
Detected encoding: ASCII (7 bit)8