Source file: /~heha/basteln/Haus/Frostwächter/frost.zip/xc8-cc/frost.c

#include <xc.h>
#include <string.h>
#include <stdbool.h>
#include "onewire.h"

/* Compiler: xc8-cc 2.0
   ~ Generiert endlich ELF-Dateien und ist viel angenehmer als der alte XC8!
   ~ Kann C99: Variablendeklaration mittendrin wie C++, stdbool usw.
   ~ Mit SDCC komme ich partout nicht zurecht.
Hardware
1	Ucc		5 V von USB oder 3 V von Stützbatterie über Dioden
2	RA5	T1OSI	32-KHz-Uhrenquarz
3	RA4	T1OSO	32-KHz-Uhrenquarz
4	RA3	!MCLR	Reset
5	RC5		f
6	RC4		e
7	RC3		d
8	RC6		g
9	RC7		h (Dezimalpunkt)
10	RB7		A0
11	RB6		A1
12	RB5	RxD	OneWire-Bus zum Temperatursensor DS18B20
13	RB4		Relais-Ausgang, L = angezogen, gegen USB-Spannung
14	RC2		c
15	RC1	INT	b; Detektion USB-Spannung (H), Batteriebetrieb (L)
16	RC0		a
17	Uusb		Kondensator nach Masse
18	RA1	D-	USB
19	RA0	D+	USB
20	GND		Masse

Betriebsarten:
* Low-Power-Modus: Batterieversorgung 3 V, Stromaufnahme < 1 µA, RA3 = Low
  - Temperaturmessung alle 1 Minute, Speichern im RAM
  - Mittelwertbildung alle 1 Stunde, Speichern im Flash
  - Anzeige: Dezimalpunkt-LED blitzt bei Temperaturmessung
* High-Power-Modus: USB-Versorgung 5 V, Stromaufnahme < 100 mA, RA3 = High
  - Temperaturanzeige Multiplex (letzter Messwert) auf LED-Anzeige
  - Temperaturmessung alle 2 Sekunden, Mittelung über 30 Werte
  - Frostschutz-Relais (Heizungssteuerung) zieht bei unter 1 °C an;
    Anzeige durch invertiert blitzenden Dezimalpunkt
* High-Power-Modus mit verbundenen D+ und D-, Stromaufnahme ≤ 500 mA
  - Anzeige durch etwas länger blitzenden Dezimalpunkt;
    USB-Netzteil darf Heizung mit 2,5 W mitversorgen  
* High-Power-Modus mit aktivem USB, Stromaufnahme ≤ 100 mA
  - High-Speed-PLL und USB-Devicecontroller aktiv
  - HID-Gerät oder serielle Schnittstelle noch unklar, evtl. beides
  - USB-Funktionen:
    ~ 32-KHz-Echtzeituhr setzen + auslesen
    ~ Minuten-Log (60 Werte) und Stunden-Log (4K Werte) auslesen
    ~ Aktuelle Temperaturen auslesen
    ~ Temperatur-Offsets abfragen + setzen

Timer:
* Timer0 = Anzeige-Multiplex, Vorteiler 32 ergibt 12 MHz/32/256/16 = 91 Hz
* Timer1 = Echtzeituhr mit asynchronem Quarzoszillator
* Timer2 = Mikrosekunden-Messung für OneWire-Bus?, sonst frei

Flash-Speicher für's Logging:
32 Temperaturwerte (14 Bit) pro Page
Beim stundenweisen Logging in 4 KWords (Hälfte des Flash)
wären das 170 Tage, ein halbes Jahr.
Beim Speichern von Min+Max 85 Tage: Kann knapp sein.
Im RAM kann minutenweise geloggt werden, 1h = 60 Samples = 120 Bytes,
24h = 1440 Samples (1K RAM reicht nicht)
Durch die Batteriestütze genügt es, alle 32 Stunden den Flash-Page-Puffer
in den Flash zu schreiben.
Stündliches Speichern überlastet den Flash aber auch nicht.
Temperaturwerte von 0x3FFF zeigen das Ende des Log-Bereiches an.
*/

// Konfigurationsbits wie beim USB-Bootloader
#pragma config FOSC = INTOSC
#pragma config WDTE = SWDTEN
#pragma config PWRTE = ON
#pragma config IESO = OFF
#pragma config FCMEN = OFF
#pragma config WRT = BOOT
#pragma config CPUDIV = NOCLKDIV

//typedef unsigned char byte;

#if 0
const byte seg7[]={
 0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F,	// 0 1 2 3 4 5 6 7 8 9
 0x77,0x7C,0x58,0x5E,0x79,0x71,0x6D,0x76,0x10,0x1F,	// A b c d E F G H i J
 0x74,0x68,0x67,0x54,0x5C,0x73,0x50,0x78,0x4C,0x6E};	// K L M n o P r t u U
// Statt einer Divisionsroutine Tabelle Sechzehntel- auf Zehntelgrad mit Rundung
const byte map10[16]={
   10/32, 30/32, 50/32, 70/32, 90/32,110/32,130/32,150/32,
  170/32,190/32,210/32,230/32,250/32,270/32,290/32,310/32};

byte mux[2]={0xFF,0xFF};
#define GK	// nicht definiert: Gemeinsame Anode
// Multiplex segment-, nicht digitweise, um Widerstände zu sparen (2 statt 8)
// Bei gemeinsamer Anode (bevorzugt) können die Widerstände entfallen,
// da PIC-Ausgänge ohnehin viel weniger nach High treiberfähig sind als nach Low.

static void powerUp() {
 INTCON=0xC0;		// externer Interrupt (Echtzeituhr) und 
}

static void powerDown() {
 INTCON=0x50;		// externer Interrupt (Echtzeituhr) und Pegelwechsel-Interrupt (für PowerUp)
}

static void detectPowerDown() {
 TRISC|=2;		// auf Eingang
 __delay_us(1);
 if (!(PORTC&2)) powerDown();
 else TRISC&=~2;
}

// LED-Multiplex: Nächstes Segment helltasten
static void muxSegment() {
#ifdef GK
 LATB |= 0xC0;		// Katoden aus (1)
 byte a=LATC<<1;
 if (LATC&0x80) a|=1;	// Anoden-Bit (1) rotieren
 LATC=a;
// if (a==1) detectPowerDown();
 if (!(INTCON&1<<INTE)) {
  if (mux[0]&a) LATB &=~0x80;
  if (mux[1]&a) LATB &=~0x40;
 }
#else
 LATB &=~0xC0;		// Anoden aus (0)
 byte k=LATC<<1;
 if (LATC&0x80) k|=1;	// Katoden-Bit (0) rotieren
 LATC=k;
// if (k==0xFD) detectPowerDown();	// wenn gerade Low ausgegeben wird
 if (!(INTCON&1<<INTE)) {
  k=~k;
  if (mux[0]&k) LATB |= 0x80;
  if (mux[1]&k) LATB |= 0x40;
 }
#endif
}

// ISR zum Multiplexen des Displays mit Timer0
// Nur im High-Power-Modus aktiv
void __interrupt() muxer() {
 T0IF=0;
 muxSegment();
}

// Temperatur in 1/16 °C (vom DS20B18) ausgeben
// Auf eine 1½-stellige Anzeige VQB21 o.ä.
// Der für einen Frostwächter interessante Temperaturbereich ±19 °C
// wird sofort lesbar ausgegeben, mit 1 Kommastelle im Bereich ±1,9 °C
// Tiefer: Nicht anzeigbar	--
// Im Bereich -39..20		-1U .. -1A
// Im Bereich -19..-2		-19 .. -2
// Im Bereich -1.9..-0.1	-1.9 .. -.1
// Wert 0			.0 sowie +.0 und -.0 bei ±1/16 °C
// Im Bereich 0.1..1.9		+.1 .. +1.9
// Im Bereich 2..19		+2 .. +19
// Im Bereich 20..39		+1A,+1b,+1c,+1d,+1E,+1F,+1G,+1H,+1i,+1J,+1k,+1L .. +1U
// höher: Nicht anzeigbar	+-
static void otemp(int t) {
 signed char sgn=t>>8;
 byte th;
 if (sgn<0) t=-t;
 th=t>>4;			// ganze °C
 mux[0]=sgn<0?0x40:0x70;	// "+" oder "-" (vorbereiten)
 if (th<2) {
  byte tl=t&15;			// Sechzehntelgrad
  mux[0]|=0x80;			// Dezimalpunkt dazu
  if (th) mux[0]|=0x06;		// "1" dazu"
  else if (!tl) mux[0]=0x80;	// kein Vorzeichen bei exakt Null
  mux[1]=seg7[map10[tl]];	// Zehntelgrad
 }else if (th<10) {
  mux[1]=seg7[th];
 }else{
  mux[0]|=0x06;			// "1" dazu
  th-=10;
  mux[1]=th<30?seg7[th]:0x07;	// "-" bei Anzeige-Überlauf
 }
}

static void heat(bool on) {
 if (on) {TRISB&=~0x10; mux[1]|=0x80;}	// Low und Dezimalpunkt ein
 else TRISB|= 0x10;		// hochohmig
}

/**************************************
 * DS18B20 digitaler Temperatursensor *
 **************************************/
#define MAXT 4

struct{
 byte reportId;
 byte n;		// Anzahl gefundener Chips
 rom_t rom[MAXT];	// max. 4 Temperatursensoren; das Minimum zählt
}romReport;

struct{
 byte reportId;
 char intern;		// Für interne Chip-Temperatur
 char o[MAXT];		// Korrekturwerte
}offsetReport;

struct{
 byte reportId;
 byte i;		// Anzahl gültiger Antworten von DS18B20
 int intern;		// Gemessene Chip-Temperatur
 int t[MAXT];		// Gemessene Temperaturen
}tempReport;

static int readTemp() {
 mux[1]^=0x80;		// Aufleuchtender (oder verlöschender) Dezimalpunkt zeigt Lesevorgang an
 byte k;
 int mintemp=(int)0xF800;	// 12-bit-NaN
 for (k=0; k<romReport.n; k++) {
  int t=(int)0x8000;		// NaN
  if (owReset()) {
   owSelect(romReport.rom[k]);	// Diesen auswählen
   owWriteByte(0x44);
   __delay_ms(750);	// für volle 12 Bit Auflösung
   if (owReset()) {
    owSelect(romReport.rom[k]);
    owWriteByte(0xBE);	// Schmierzettel lesen
    t=owReadByte();
    t|=owReadByte()<<8;
    t+=offsetReport.o[k];	// Korrektur um maximal ±15,9 °C
    if (mintemp<t) mintemp=t;
   }
  }
  tempReport.t[k]=t;
  if (INTCON&1<<INTE) break;	// Nur bei 1 Sensor messen bei Low-Power!
 }
 tempReport.i=k;
 otemp(mintemp);
 heat(!(INTCON&1<<INTE) && mintemp<16);	// Bei High-Power unter 1 °C heizen
 return mintemp;
}

/***********
 * Logging *
 ***********/
struct{
 byte reportId;		// = 3
 byte minute;		// 0..59
 unsigned hour;		// 0..4K-1 für den Flash-Speicher
 unsigned hourbuffer[60];
}rep3;			// 122 Byte Report

unsigned flashpage[32];

static void flashUnlock() {
 di();
 PMCON2=0x55;
 PMCON2=0xAA;
 PMCON1|=1<<WR;
 NOP();
 NOP();
 ei();
}

static void flashWrite() {
 PMCON1=1<<FREE|1<<WREN;
 flashUnlock();		// Prozessor blockiert 2 ms
 for(;;){
  PMCON1=1<<WREN|1<<LWLO;
  PMDAT=flashpage[PMADRL&0x1F];
  flashUnlock();	// Prozessor blockiert noch nicht
  if (!(~PMADRL&0x1F)) break;
  ++PMADRL;
 }
 PMCON1&=~(1<<LWLO);
 PMADRL&=~0x1F;		// sicherheitshalber
 flashUnlock();		// Prozessor blockiert
 PMCON1&=~(1<<WREN);
}

static void logTemp(unsigned t) {
 rep3.hourbuffer[rep3.minute]=t;
 if (++rep3.minute==60) {
  rep3.minute=0;
  flashpage[rep3.hour&0x1F]=t;
  unsigned newh=rep3.hour+1&0x0FFF;
  if (!(newh&0x1F)) {	// alle 32 Stunden
   PMADR=0x1000+(rep3.hour&~0x1F);
   flashWrite();
  }
  rep3.hour=newh;
 }
}

static void hardwareInit() {
 IOCAF=0;	// Pegelwechsel-Interrupts löschen
 if (!(INTCON&1<<INTE)) {	// High-Power-Modus
#ifdef GK
  LATC  = 0x01;
#else
  LATC  = 0xFE;
#endif
  IOCAP=0;
  IOCAN=0x08;		// Fallende Flanke an RA3 auswerten
// Der Übergang von "Low Power" zu "High-Power"
// ist IMHO der richtige Moment um die angeschlossenen DS18B20 zu detektieren.
  owsReset();
  owsTarget(0x28);	// Nur DS18B20 suchen / erkennen
  byte i;
  for (i=0; i<4; i++) {
   if (!owsSearch(romReport.rom[i])) break;	// keine Antwort
   if (owCrc8(romReport.rom[i],7)!=romReport.rom[i][7]) break;	// falsche Antwort
  }
  romReport.n=i;
 }else{			// Low-Power-Modus
  IOCAN=0;
  IOCAP=0x08;		// Steigende Flanke an RA3 auswerten
 }
}

// Ein 48-Bit-Zähler
static unsigned counter[3];
static byte sec2;	// zählt 0..29, dann Temperaturmessung

static void incCounter() {
 PIR1&=~(1<<TMR1IF);
 asm(
"	banksel	_counter	\n"
"	movlw	1		\n"
"	addwf	_counter,f	\n"
"	movlw	0		\n"
"	addwfc	_counter+1,f	\n"
"	addwfc	_counter+2,f	\n"
"	addwfc	_counter+3,f	\n"
"	addwfc	_counter+4,f	\n"
"	addwfc	_counter+5,f	\n"
 );
 ++sec2;
// Im High-Power-Modus alle 2 Sekunden Temperatur messen und anzeigen
 if (!(INTCON&1<<INTE)) {
  int t=readTemp();
  if (++sec2==30) {
   sec2=0;
   logTemp(t);
  }
 }
}

// Liest 64-Bit-Wert aus (= Zeit in 32 KHz)
void queryTime(unsigned buf[4]) {
 di();	// Die Leseroutine läuft wesentlich schneller als der 32-KHz-Oszillator
 unsigned t=TMR1;
 if ((t&0xFF)==0xFF) {
  if (!TMR1L) t=TMR1;	// gerade Überlauf vom Low- zum High-Byte gewesen: Nochmal einlesen
 }
 ei();
 buf[0]=t;
 if (PIR1&1<<TMR1IF && !(t>>8)) incCounter();	// gerade Überlauf vom High-Byte zum Flag
 memcpy(buf+2,counter,6);
}

// Nimmt 64-Bit-Wert entgegen
void setTime(const unsigned buf[4]) {
 T1CON&=~1;		// Timer (nicht den Oszillator) mal kurz anhalten
 TMR1=buf[0];		// Zählerstand setzen
 T1CON|=1;		// Timer weiterlaufen lassen
 memcpy(counter,buf+1,6);
}

static struct{
 byte reportId;
 byte valid;
 unsigned timebuf[4];
}timeReport;
#endif

void main(){
 OPTION_REG=0x44;	// Timer0 mit Vorteiler 32
 LATA  = 0;
 LATB  = 0xF0;
 LATC  = 1;
// detectPowerDown();
// WPUA  = 0;		// Kein Pullup
// WPUB  = 0x20;		// Pullup für DS18B20 (reicht nicht; extern bestücken!)
 TRISA = 0x20;		// T1OSO = Ausgang
 TRISB = 0x30;		// Gemeinsame Anode = Ausgang
 TRISC = 0;		// alles Ausgänge
 ANSELA= 0;
 ANSELB= 0;		// digital
 ANSELC= 0;
 LATB  = 0x30;
 for(;;) {
  asm("banksel LATC");
  asm("clrw");
  asm("lslf LATC,f");
  asm("addwfc LATC,f");
  CLRWDT();	// STOP
 }
#if 0
 hardwareInit();
 T1CON = 0x8D;		// Timer1 asynchron mit Vorteiler 1
 PIE1  = 1<<TMR1IE;	// mit Interrupt bei Überlauf von 0xFFFF nach 0x0000
 INTCON=1<<PEIE|1<<INTE;	// Auf Pegelwechsel und Interrupt-Pin reagieren
 ei();
 otemp((int)(1.9*16));	// probeweise
 queryTime(timeReport.timebuf);
 setTime(timeReport.timebuf);
 for(;;){
  SLEEP();		// Ohne Interrupt
  if (IOCAF) hardwareInit();
  if (PIR1&1<<TMR1IF) incCounter();
// Im High-Power-Modus läuft der Timer0 mit einigen kHz.
// Im Low-Power-Modus läuft die CPU nur alle 2 s um die Uhr weiterzustellen.
 }
#endif
}
Detected encoding: UTF-80