#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-8 | 0
|