From 9d87c925a9eaa4fc256be3173c14a20d1469472d Mon Sep 17 00:00:00 2001 From: fishsoupisgood Date: Wed, 9 Sep 2020 11:53:37 +0100 Subject: everything, mostly, working --- boiler-monster/original-pic-4.2.5/.gitignore | 6 + boiler-monster/original-pic-4.2.5/16f88.lkr | 76 + boiler-monster/original-pic-4.2.5/Makefile | 22 + boiler-monster/original-pic-4.2.5/build.asm | 2 + boiler-monster/original-pic-4.2.5/build.vbs | 77 + boiler-monster/original-pic-4.2.5/ds1820.asm | 198 + boiler-monster/original-pic-4.2.5/gateway.asm | 4524 ++++++++++++++++++++++ boiler-monster/original-pic-4.2.5/license.txt | 58 + boiler-monster/original-pic-4.2.5/ot-gateway.brd | Bin 0 -> 37424 bytes boiler-monster/original-pic-4.2.5/ot-gateway.sch | Bin 0 -> 210692 bytes boiler-monster/original-pic-4.2.5/readme.txt | 43 + boiler-monster/original-pic-4.2.5/selfprog.asm | 391 ++ 12 files changed, 5397 insertions(+) create mode 100644 boiler-monster/original-pic-4.2.5/.gitignore create mode 100644 boiler-monster/original-pic-4.2.5/16f88.lkr create mode 100644 boiler-monster/original-pic-4.2.5/Makefile create mode 100644 boiler-monster/original-pic-4.2.5/build.asm create mode 100644 boiler-monster/original-pic-4.2.5/build.vbs create mode 100644 boiler-monster/original-pic-4.2.5/ds1820.asm create mode 100644 boiler-monster/original-pic-4.2.5/gateway.asm create mode 100644 boiler-monster/original-pic-4.2.5/license.txt create mode 100644 boiler-monster/original-pic-4.2.5/ot-gateway.brd create mode 100644 boiler-monster/original-pic-4.2.5/ot-gateway.sch create mode 100644 boiler-monster/original-pic-4.2.5/readme.txt create mode 100644 boiler-monster/original-pic-4.2.5/selfprog.asm (limited to 'boiler-monster/original-pic-4.2.5') diff --git a/boiler-monster/original-pic-4.2.5/.gitignore b/boiler-monster/original-pic-4.2.5/.gitignore new file mode 100644 index 0000000..86351a7 --- /dev/null +++ b/boiler-monster/original-pic-4.2.5/.gitignore @@ -0,0 +1,6 @@ +*.err +*.lst +*.o +*.hex +*.map +*.cod diff --git a/boiler-monster/original-pic-4.2.5/16f88.lkr b/boiler-monster/original-pic-4.2.5/16f88.lkr new file mode 100644 index 0000000..b2b3e95 --- /dev/null +++ b/boiler-monster/original-pic-4.2.5/16f88.lkr @@ -0,0 +1,76 @@ +// File: 16F88_g.lkr +// Generic linker script for the PIC16F88 processor + +#IFDEF _DEBUG + +LIBPATH . + +CODEPAGE NAME=page0 START=0x0 END=0x7FF +CODEPAGE NAME=page1 START=0x800 END=0xEFF +CODEPAGE NAME=debug START=0xF00 END=0xFFF PROTECTED +CODEPAGE NAME=.idlocs START=0x2000 END=0x2003 PROTECTED +CODEPAGE NAME=.device_id START=0x2006 END=0x2006 PROTECTED +CODEPAGE NAME=.config START=0x2007 END=0x2009 PROTECTED +CODEPAGE NAME=eedata START=0x2100 END=0x21FF PROTECTED + +DATABANK NAME=sfr0 START=0x0 END=0x1F PROTECTED +DATABANK NAME=sfr1 START=0x80 END=0x9F PROTECTED +DATABANK NAME=sfr2 START=0x100 END=0x10F PROTECTED +DATABANK NAME=sfr3 START=0x180 END=0x18F PROTECTED + +SHAREBANK NAME=dbgnobnk START=0x70 END=0x70 PROTECTED +SHAREBANK NAME=dbgnobnk START=0xF0 END=0xF0 PROTECTED +SHAREBANK NAME=dbgnobnk START=0x170 END=0x170 PROTECTED +SHAREBANK NAME=dbgnobnk START=0x1F0 END=0x1F0 PROTECTED + +SHAREBANK NAME=sfrnobnk START=0x71 END=0x7F +SHAREBANK NAME=sfrnobnk START=0xF1 END=0xFF PROTECTED +SHAREBANK NAME=sfrnobnk START=0x171 END=0x17F PROTECTED +SHAREBANK NAME=sfrnobnk START=0x1F1 END=0x1FF PROTECTED + +DATABANK NAME=gpr0 START=0x20 END=0x6F +DATABANK NAME=gpr1 START=0xA0 END=0xEF +DATABANK NAME=gpr2 START=0x110 END=0x16F +DATABANK NAME=gpr3 START=0x190 END=0x1E4 +DATABANK NAME=dbg3 START=0x1E5 END=0x1EF PROTECTED + +SECTION NAME=PROG0 ROM=page0 // ROM code space +SECTION NAME=PROG1 ROM=page1 // ROM code space +SECTION NAME=DEBUG ROM=debug // ICD debug executive +SECTION NAME=IDLOCS ROM=.idlocs // ID locations +SECTION NAME=DEVICEID ROM=.device_id // Device ID +SECTION NAME=DEEPROM ROM=eedata // Data EEPROM + +#ELSE + +LIBPATH . + +CODEPAGE NAME=page0 START=0x0 END=0x7FF +CODEPAGE NAME=page1 START=0x800 END=0xFFF +CODEPAGE NAME=.idlocs START=0x2000 END=0x2003 PROTECTED +CODEPAGE NAME=.device_id START=0x2006 END=0x2006 PROTECTED +CODEPAGE NAME=.config START=0x2007 END=0x2009 PROTECTED +CODEPAGE NAME=eedata START=0x2100 END=0x21FF PROTECTED + +DATABANK NAME=sfr0 START=0x0 END=0x1F PROTECTED +DATABANK NAME=sfr1 START=0x80 END=0x9F PROTECTED +DATABANK NAME=sfr2 START=0x100 END=0x10F PROTECTED +DATABANK NAME=sfr3 START=0x180 END=0x18F PROTECTED + +SHAREBANK NAME=sfrnobnk START=0x70 END=0x7F +SHAREBANK NAME=sfrnobnk START=0xF0 END=0xFF PROTECTED +SHAREBANK NAME=sfrnobnk START=0x170 END=0x17F PROTECTED +SHAREBANK NAME=sfrnobnk START=0x1F0 END=0x1FF PROTECTED + +DATABANK NAME=gpr0 START=0x20 END=0x6F +DATABANK NAME=gpr1 START=0xA0 END=0xEF +DATABANK NAME=gpr2 START=0x110 END=0x16F +DATABANK NAME=gpr3 START=0x190 END=0x1EF + +SECTION NAME=PROG0 ROM=page0 // ROM code space +SECTION NAME=PROG1 ROM=page1 // ROM code space +SECTION NAME=IDLOCS ROM=.idlocs // ID locations +SECTION NAME=DEVICEID ROM=.device_id // Device ID +SECTION NAME=DEEPROM ROM=eedata // Data EEPROM + +#FI diff --git a/boiler-monster/original-pic-4.2.5/Makefile b/boiler-monster/original-pic-4.2.5/Makefile new file mode 100644 index 0000000..ec282b1 --- /dev/null +++ b/boiler-monster/original-pic-4.2.5/Makefile @@ -0,0 +1,22 @@ +AFLAGS=-k -p 16F88 -k +LDFLAGS=-m -s 16f88.lkr + +PROG=gateway +ASRCS=${PROG}.asm ds1820.asm selfprog.asm +OBJS=${ASRCS:%.asm=%.o} +LSTS=${ASRCS:%.asm=%.err} +ERRS=${ASRCS:%.asm=%.lst} + +default:${PROG}.hex + +%.o:%.asm + gpasm ${AFLAGS} -c -o $@ $< + +${PROG}.hex:${OBJS} + gplink ${LDFLAGS} -o $@ ${OBJS} + + +clean: + /bin/rm -f ${PROG}.hex ${PROG}.map ${PROG}.cod ${OBJS} ${LSTS} ${ERRS} + + diff --git a/boiler-monster/original-pic-4.2.5/build.asm b/boiler-monster/original-pic-4.2.5/build.asm new file mode 100644 index 0000000..67d0747 --- /dev/null +++ b/boiler-monster/original-pic-4.2.5/build.asm @@ -0,0 +1,2 @@ +#define build "1" +#define tstamp "17:59 20-10-2015" diff --git a/boiler-monster/original-pic-4.2.5/build.vbs b/boiler-monster/original-pic-4.2.5/build.vbs new file mode 100644 index 0000000..989fbf1 --- /dev/null +++ b/boiler-monster/original-pic-4.2.5/build.vbs @@ -0,0 +1,77 @@ +'Build.vbs +'Generate MPLAB include script +'----------------------------- +' +Option Explicit + +Dim sh, fso, src, fd +Dim ts, name, timestr +Dim proj, regpath, build + +Const incfile = "build.asm" + +Set sh = CreateObject("WScript.Shell") +Set fso = CreateObject("Scripting.FileSystemObject") + +'Make sure to be working in the project directory +sh.CurrentDirectory = fso.GetParentFolderName(Wscript.ScriptFullName) + +'Determine the last modification time of the sources +ts = DateValue("1/1/1970") +For Each name In fso.GetFolder(".").Files + If LCase(fso.GetExtensionName(name)) = "asm" Then + If fso.GetFileName(name) <> incfile Then + Set src = fso.GetFile(name) + If DateDiff("S", ts, src.DateLastModified) > 0 Then + ts = src.DateLastModified + End If + End If + End If +Next + +'Check if the include file needs to be regenerated +If fso.FileExists(incfile) Then + Set src = fso.GetFile(incfile) + If DateDiff("S", ts, src.DateLastModified) >= 0 Then + Wscript.StdOut.WriteLine "Source was not changed" + Wscript.Quit + End If +End If + +'Format the last modification time +timestr = right("0" & hour(ts), 2) & ":" & _ + right("0" & minute(ts), 2) & " " & _ + right("0" & day(ts), 2) & "-" & _ + right("0" & month(ts), 2) & "-" & year(ts) + +'Determine the project name +If WScript.Arguments.Count > 0 Then + 'The project name was specified on the command line + proj = WScript.Arguments.item(0) +Else + 'Derive the project name from the current directory + proj = fso.GetFileName(fso.GetAbsolutePathName(".")) +End If + +'Get the build revision from the registry +regpath = "HKLM\SOFTWARE\Microchip\MPLAB IDE\Projects\" & proj & "\Revision" +On Error Resume Next +build = sh.RegRead(regpath) +If Hex(Err.number) = "80070002" Then + build = 1 +Else + build = build + 1 +End If +On Error GoTo 0 +'Update the registry +sh.RegWrite regpath, build, "REG_DWORD" + +'Create the build.asm file +Set fd = fso.CreateTextFile(incfile) +fd.WriteLine "#define build """ & build & """" +fd.WriteLine "#define tstamp """ & timestr & """" +fd.Close + +'Report the build revision to standard output as well +Wscript.StdOut.WriteLine "Build revision: " & build +Wscript.StdOut.WriteLine "Build time & date: " & timestr diff --git a/boiler-monster/original-pic-4.2.5/ds1820.asm b/boiler-monster/original-pic-4.2.5/ds1820.asm new file mode 100644 index 0000000..b9b06c1 --- /dev/null +++ b/boiler-monster/original-pic-4.2.5/ds1820.asm @@ -0,0 +1,198 @@ + title "Outside sensor" + list p=16F88, b=8, r=dec + +#include "p16f88.inc" + errorlevel -302, -306 + +#define IOPORT PORTA,7 ;I/O port used for 1-wire line + +;1-wire ROM commands +#define READROM 0x33 +#define SKIPROM 0xCC +;1-wire FUNCTION commands +#define CONVERTT 0x44 +#define READSCRATCHPAD 0xBE +;1-wire family codes for supported temperature sensors +#define DS_FAM_DS18S20 0x10 +#define DS_FAM_DS18B20 0x28 +;Return codes, controlling when to perform the next step +#define DONE 0 ;Next step after one second +#define CONTINUE 1 ;Next step as soon as possible + + extern float1, float2, flags, temp, StoreOutTemp + + UDATA +sensorstage res 1 +#define ds18b20 sensorstage,7 +#define restart sensorstage,6 +lsbstorage res 1 + + +#define OutsideTemp flags,0 ;Keep in sync with gateway.asm +#define OutsideInvalid flags,1 ;Keep in sync with gateway.asm + + global Temperature +Temperature CODE +Temperature skpz ;Zero bit indicates no message in prog + goto Restart ;Sequence interrupted by OT message + btfsc restart + clrf sensorstage + ;Split up the temperature measurement into several short steps, + ;each less than 1ms. That way the main loop is allowed to run + ;frequently enough for it to be able to do all the necessary + ;processing in time, like clearing out the serial receive + ;register. + movlw high StageTable + movwf PCLATH + movfw sensorstage + andlw b'11111' + addlw low StageTable + skpnc + incf PCLATH,F + incf sensorstage,F ;Determine next step of the process + movwf PCL ;Jump to the current step +StageTable goto PresencePulse ;Step 0 - Transmit a reset pulse + goto SkipRom ;Step 1 - Address all devices on the bus + goto Measure ;Step 2 - Start a measurement + retlw DONE ;Step 3 - Allow time for measurement + goto PresencePulse ;Step 4 - Transmit a reset pulse + goto ReadRom ;Step 5 - Send Read ROM command + goto ReadFamily ;Step 6 - Read device family code + goto PresencePulse ;Step 7 - Transmit a reset pulse + goto SkipRom ;Step 8 - Address all devices + goto ReadCommand ;Step 9 - Send Read Scratchpad command + goto ReadLSB ;Step 10 - Read temperature value, LSB + goto ReadMSB ;Step 11 - Read temperature value, MSB + goto Restart ;Step 12 - Start over (never reached) + +PresencePulse bcf IOPORT ;Prepare the output latch + bsf STATUS,RP0 + bcf IOPORT ;Drive the output port low + call Delay480 ;Wait for the minimum reset duration + bsf STATUS,RP0 + bsf IOPORT ;Release the I/O port + bcf STATUS,RP0 + call Delay60 ;Allow the device time to respond + btfsc IOPORT ;Check the 1-wire line + goto NoDevice + call Delay240 ;Wait for the presence pulse to end + retlw CONTINUE +NoDevice bsf OutsideInvalid ;Outside temperature is not valid + goto Restart ;Start the sequence from the top + +Measure movlw CONVERTT ;Initiate temperature conversion + call WriteByte ;Send the command on the 1-wire bus + retlw DONE ;Give devices time to do the conversion + +ReadFamily call ReadByte ;Read family code from the 1-wire bus + movfw temp + sublw DS_FAM_DS18S20 ;Check if the sensor is a DS18S20 + skpnz + retlw CONTINUE ;Found a DS18S20 + movfw temp + sublw DS_FAM_DS18B20 ;Check if the sensor is a DS18B20 + skpz + goto NoDevice ;Not a supported temperature sensor + bsf ds18b20 ;Found a DS18B20 + retlw CONTINUE + +ReadCommand movlw READSCRATCHPAD ;The scratchpad contains the temerature + goto WriteByte ;Send the command on the 1-wire bus + +ReadLSB call ReadByte ;Read the first scratchpad byte + movfw temp + movwf lsbstorage ;Save the byte for later use + retlw CONTINUE + +ReadMSB call ReadByte ;Read the second scratchpad byte + btfsc ds18b20 ;Check which sensor type was found + goto HighPrecision ;A DS18B20 stores the value differently + rrf temp,W ;Sign of the temperature value + rrf lsbstorage,W ;Determine the full degrees + movwf float1 + clrf float2 + rrf float2,F ;Determine the half degrees + goto Success +HighPrecision swapf lsbstorage,W ;Get the 1/16 degrees in the high nibble + andlw b'11110000' + movwf float2 ;Store as outside temperture low byte + swapf temp,W ;Get the upper nibble of the degrees + andlw b'11110000' + movwf temp ;Save for later use + swapf lsbstorage,W + andlw b'1111' ;Get the lower nibble of the degrees + iorwf temp,W ;Combine with the saved upper nibble + movwf float1 ;Store as outside temperature high byte +Success bsf OutsideTemp ;Temperature measurement completed + bsf OutsideInvalid ;Invalid until validity is checked + xorlw 85 ;Test against powerup value + skpnz + goto Restart ;Not a valid measurement + lcall StoreOutTemp ;Store the measurement + retlw CONTINUE + +Restart bsf restart ;Don't clear sensorstage for debugging + retlw DONE + +Delay480 call Delay240 ;480 = 2 * 240 +Delay240 movlw 240 - 8 ;Value for 240uS delay (compensated) + goto Delay ;Wait for the specified amount of time +Delay60 movlw 60 - 6 ;Value for 60uS delay (compensated) +Delay bsf STATUS,RP0 ;Bank 1 + bcf PIE1,TMR2IE ;Disable interrupt from timer 2 + subwf PR2,W ;Calculate the start value + bcf STATUS,RP0 ;Bank 0 + addlw 10 ;Overhead for starting and stopping + movwf TMR2 ;Load the timer + bsf T2CON,TMR2ON ;Start timer 2 +WaitTimer2 btfss PIR1,TMR2IF ;Check if timer 2 expired + goto WaitTimer2 ;Wait some more + bcf T2CON,TMR2ON ;Stop timer 2 + bcf PIR1,TMR2IF ;Prevent unwanted interrupt + return + +Delay15 call Delay5 ;15 = 3 * 5 +Delay10 call Delay5 ;10 = 2 * 5 +Delay5 nop ;Wait 1 us + return ;Subroutine Call + Return = 2us + 2us + +ReadByte call ReadNibble ;Read 4 bits from 1-wire bus +ReadNibble call ReadBit ;Read 1 bit from 1-wire bus + call ReadBit ;Read 1 bit from 1-wire bus + call ReadBit ;Read 1 bit from 1-wire bus +ReadBit bcf IOPORT ;Prepare output latch + bsf STATUS,RP0 ;TRIS register is in Bank 1 + bcf IOPORT ;Drive the 1-wire line low + clrc ;Prepare carry while waiting + bsf IOPORT ;Release the 1-wire bus + bcf STATUS,RP0 ;Back to bank 0 + call Delay10 ;Allow device time to write the bit + btfsc IOPORT ;Read the 1-wire bus + setc ;Device wrote a "1" + rrf temp,F ;Shift bit into the result varaible + movlw 45 ;Maximum time a device drives the bus + call Delay ;Wait for the device to release the bus + return + +ReadRom movlw READROM ;Command to read a device's ROM code + goto WriteByte ;Send the command on the 1-wire bus +SkipRom movlw SKIPROM ;Address all devices on the 1-wire bus +WriteByte movwf temp ;Temporarily store the 1-wire command + call WriteNibble ;Write 4 bits to the 1-wire bus +WriteNibble call WriteBit ;Write 1 bit to the 1-wire bus + call WriteBit ;Write 1 bit to the 1-wire bus + call WriteBit ;Write 1 bit to the 1-wire bus +WriteBit rrf temp,F ;Set carry to least significant bit +WriteSlot bcf IOPORT ;Prepare the output latch + bsf STATUS,RP0 + bcf IOPORT ;Drive the 1-wire line low + skpnc ;Carry contains the bit to send + bsf IOPORT ;Release the 1-wire line + movlw 60 - 10 ;Value for 60uS delay (compensated) + call Delay ;Bit duration + bsf STATUS,RP0 ;Bank 1 + bsf IOPORT ;Float the 1-wire line, if necessary + bcf STATUS,RP0 ;Bank 0 + retlw CONTINUE + + end diff --git a/boiler-monster/original-pic-4.2.5/gateway.asm b/boiler-monster/original-pic-4.2.5/gateway.asm new file mode 100644 index 0000000..da214b1 --- /dev/null +++ b/boiler-monster/original-pic-4.2.5/gateway.asm @@ -0,0 +1,4524 @@ + title "OpenTherm Gateway" + list p=16F88, b=8, r=dec + +;Copyright (c) 2009 Schelte Bron + +#define version "4.2" +#define phase "." ;a=alpha, b=beta, .=production +#define patch "5" ;Comment out when not applicable +;#define bugfix "1" ;Comment out when not applicable +#include build.asm + +;See the file "license.txt" for information on usage and redistribution of +;this file, and for a DISCLAIMER OF ALL WARRANTIES. + +#ifndef LVP + __config H'2007', B'10111101110100' +#else + __config H'2007', B'10111111110100' +#endif + __config H'2008', B'11111111111111' + + errorlevel -302 + +;The OpenTherm Gateway is located between the thermostat and the boiler of a +;central heating system. In monitor mode it passes its inputs unmodified to +;the outputs using only hardware functions. In back to back mode the messages +;from the thermostat are passed to the boiler under firmware control. In some +;cases the responses from the boiler may be overridden by the gateway and +;external data is injected into the messages. If no thermostat, or a simple +;on/off thermostat is connected, the gateway will generate its own opentherm +;messages to communicate with the Opentherm boiler. +; +;The gateway starts in back to back mode. It switches to monitor mode if for +;some reason the firmware locks up, resulting in a watchdog timeout. +; +;Interaction with the gateway takes place via a serial interface. The interface +;uses 9600 baud, 8 bits, no parity. +; +;Messages on the OpenTherm link are reported as a letter follwed by 8 hex +;digits. The letter indicates if the message was received from the boiler (B), +;or from the thermostat (T). If the gateway modifies the message to the boiler +;in any way, the modified request is also reported (R), as is a modified answer +;back to the thermostat (A). The hex digits represent the message contents. +; +;The serial interface also allows commands to be sent to the gateway. Commands +;consist of two letters, an equals-sign, and an argument. A command must be +;terminated by a carriage-return character (13, 0xd). The gateway understands +;the following commands: +;OT=+15.4 Specify the outside temperature +;TT=18.5 Change the temperature setpoint (temporary). Use 0 (+0.0) to +; return to the thermostat's normal programming. +;TC=+16.0 Change the temperature setpoint (continuously). Use +0.0 to +; return to the thermostat's normal programming. +;SB=15.5 Configure the setback temperature to use in combination with +; GPIO functions HOME (5) and AWAY (6). +;CS=10.0 Override the control setpoint from the thermostat +;VS=25 Override the ventilation setpoint from the thermostat +;MM=40 Override the maximum relative modulation from the thermostat +;SH=72.0 Set the maximum central heating setpoint. +;SW=60.0 Set the domestic hot water setpoint. +;SC=14:45/6 Set the clock and the day of the week, 1 = Monday, 7 = Sunday +;VR=3 Adjust the reference voltage between 0.6V and 2.5V +;HW=1 Control DHW. Use T or A to let the thermostat control DHW. +;LA=F Configure the function for LED A, defaults to Flame on +;LB=X Configure the function for LED B, defaults to Transmitting +;LC=O Configure the function for LED C, defaults to Setpoint Override +;LD=M Configure the function for LED D, defaults to Maintenance +;LE=P Configure the function for LED E, defaults to Raised Power +;LF=C Configure the function for LED F, defaults to Comfort Mode +; Possible function codes to use for the LEDs are: +; R: Receiving an Opentherm message from the thermostat or boiler +; X: Transmitting an Opentherm message to the thermostat or boiler +; T: Transmitting or receiving a message on the master interface +; B: Transmitting or receiving a message on the slave interface +; O: Remote setpoint override is active +; F: Flame is on +; H: Flame is on for central heating +; W: Flame is on for hot water +; C: Comfort mode (Domestic Hot Water Enable) is on +; E: Communication error has been detected +; M: Boiler requires maintenance +; P: Raised power mode +;GA=3 Configure the function for GPIO pin A, defaults to No Function +;GB=4 Configure the function for GPIO pin B, defaults to No Function +; Possible GPIO functions are: +; 0 (input): NONE: No GPIO function. +; 1 (output): GND: Port is constantly pulled low. +; 2 (output): VCC: Port is constantly pulled high. +; 3 (output): LEDE: Port outputs the LED E function. +; 4 (output): LEDF: Port outputs the LED F function. +; 5 (input): HOME: High - Cancel remote room setpoint override +; Low - Sets thermostat to setback temperature +; 6 (input): AWAY: High - Sets thermostat to setback temperature +; Low - Cancel remote room setpoint override +; 7 (in/out): DS1820: Drive DS18[SB]20 for outside temperature +;PR=L Print selected information. Supported information sources are: +; A: About opentherm gateway (prints the welcome message) +; B: Build date and time +; C: Clock speed of the PIC +; G: Report the GPIO pin configuration +; I: Report the GPIO pin input states +; L: Configured LED functions. This prints a string like FXOMPC +; M: Report the gateway mode +; O: Setpoint override +; P: Current Smart-Power mode. +; R: The state of the automatic Remeha thermostat detection. +; S: Print the configured setback temperature +; T: Tweak settings: Ignore Transition and Override in High Byte +; V: Report the reference voltage setting +; W: Domestic hot water setting +;PS=1 Select whether to only print a summary line (1) on request, or +; every received/transmitted opentherm message (0). +;AA=42 Add alternative message to send to the boiler instead of a +; message that is known not to be supported by the boiler. +;DA=42 Delete a single occurrence from the list of alternative messages +; to send to the boiler +;SR=18:1,230 Set the response for a DataID. Either one or two data bytes +; must be provided. +;CR=18 Clear the response for a DataID. +;UI=9 Tell the gateway which DataID's are not supported by the boiler, +; so the gateway can inject its own message. +;KI=9 Remove a DataID from the list of ID's that have been configured +; as unsupported by the boiler. +;RS=HBS Reset boiler counter. Supported counters are: +; HBS: Central heating burner starts +; HBH: Central heating burner operation hours +; HPS: Central heating pump starts +; HPH: Central heating pump operation hours +; WBS: Domestic hot water burner starts +; WBH: Domestic hot water burner operation hours +; WPS: Domestic hot water pump starts +; WPH: Domestic hot water pump operation hours +;GW=1 Switch between monitor (0) and gateway (1) mode +;IT=1 Control whether multiple mid-bit line transitions are ignored. +; Normally more than one such transition results in Error 01. +;OH=0 Copy the low byte of MsgID 100 also into the high byte to work +; with thermostats that look at the wrong byte (Honeywell Vision) +;FT=D Force thermostat model to Remeha Celcia 20 (C) or iSense (I). +;DP=1F Set the debug pointer to the specified file register + +;####################################################################### +; Peripheral use +;####################################################################### +;Comparator 1 is used for requests. The input is connected to the thermostat. +;In monitor mode, the output is connected to the boiler. +;Comparator 2 is used for responses. The input is connected to the Boiler. +;In monitor mode, the output is connected to the thermostat. + +;Timer 0 uses a 1:64 prescaler. It is used to measure the 10ms line idle-time +;and is the time base for lighting up the error LED for 2 seconds +;Timer 1 has a 1:8 prescaler, which gives it a period of 524288us, or roughly +;half a second. It is used for flashing the override LED. +;Timer 2 is used to create a 250uS time base. When the first low to high +;transition on one of the comparators is detected the timer is started with +;a 100us head start. A variable is used to count timer 2 roll-overs. Any +;transition of the comparator while the lowest two bits of the rollover +;counter are binary 10 marks a real bit. These bits are shifted into a 32 bit +;message buffer. Also timer 2 is resynchronized. When 34 bits have been +;received, the gateway will send a report to the serial interface. The report +;consists of either a 'T' or a 'B' and eight hex digits. The first letter +;indicates whether the message was received from the thermostat or the +;boiler. The hex digits represent the value of the 32-bits of data in the +;OpenTherm message. + +;The AUSART is configured for full duplex asynchronous serial communication at +;9600 baud, 8 bits, no parity. It is used for reporting about opentherm messages +;and receiving commands. + +;Analog input 0 is used to measure the voltage level on the opentherm line to +;the thermostat. This way the code can distinguish between a real opentherm +;thermostat and a simple on/off thermostat. An opentherm thermostat will keep +;the line at a few volts (low) or between 15 and 18 volts (high). An on/off +;thermostat will either short-circuit the line (0 volts) or leave the line open +;(20+ volts). + +#include "p16f88.inc" + +;Define the speed of the PIC clock +#define mhzstr "4" + +;BAUD contains the SPBRG value for 9600 baud + constant BAUD=25 + +;PERIOD is the value to be loaded into PR2 to obtain a 250uS period for TMR2 + constant PERIOD=249 + +;PRELOADT0 gives TMR0 a head start so the next rollover (with a 1:64 prescaler) +;will happen after 10 ms. This is the time the line must be stable to consider +;it idle after an error was detected. + constant PRELOADT0=100 + +;ONESEC contains the number of timer 0 overflows (with a 1:64 prescaler) +;that happen in roughly 1 second. This controls the frequency for generating +;Opentherm requests to the boiler when no Opentherm thermostat is connected. + constant ONESEC=61 ;61 * 64 * 256 = 999424 +;TWOSEC contains the number of timer 0 overflows (with a 1:64 prescaler) +;that happen in roughly 2 seconds. This is the time the error LED will stay +;lit after an error has been detected. + constant TWOSEC=2*ONESEC + +;Opentherm message types + constant T_READ=0x00 + constant T_WRITE=0x10 + constant T_INV=0x20 + constant B_RACK=0x40 + constant B_WACK=0x50 + constant B_INV=0x60 + constant B_UNKN=0x70 + +;Opentherm Data IDs + constant MSG_STATUS=0 + constant MSG_CTRLSETPT=1 + constant MSG_REMOTECMD=4 + constant MSG_FAULTCODE=5 + constant MSG_REMOTEPARAM=6 + constant MSG_SETPTOVERRIDE=9 + constant MSG_MAXMODULATION=14 + constant MSG_ROOMSETPOINT=16 + constant MSG_MODULATION=17 + constant MSG_CHPRESSURE=18 + constant MSG_DAYTIME=20 + constant MSG_BOILERTEMP=25 + constant MSG_OUTSIDETEMP=27 + constant MSG_RETURNTEMP=28 + constant MSG_DHWBOUNDS=48 + constant MSG_MAXCHBOUNDS=49 + constant MSG_DHWSETPOINT=56 + constant MSG_MAXCHSETPOINT=57 + constant MSG_FUNCOVERRIDE=100 + +;Opentherm remote requests + constant RCMD_TELEFOON=170 + +;Some default values to use if the boiler hasn't specified its actual limits. + constant MIN_DHWSETPOINT=20 + constant MAX_DHWSETPOINT=80 + constant MIN_CHSETPOINT=10 + constant MAX_CHSETPOINT=90 + +;Line voltage thresholds + constant V_SHORT=6 ;6 / 256 * 5 * 37.7 / 4.7 = 0.94V + constant V_LOW=58 ;58 / 256 * 5 * 37.7 / 4.7 = 9.09V + constant V_OPEN=122 ;122 / 256 * 5 * 37.7 / 4.7 = 19.11V + + extern SelfProg, Temperature + +;Variables accessible from all RAM banks + UDATA_SHR +s_wreg res 1 ;Used in interrupt routine +s_status res 1 ;Used in interrupt routine +s_fsr res 1 ;Used in interrupt routine + +temp res 1 +temp2 res 1 + +byte1 res 1 +byte2 res 1 +byte3 res 1 +byte4 res 1 +#define MsgParity byte1,7 +#define MsgResponse byte1,6 + +originaltype res 1 +originalreq res 1 +databyte1 res 1 +databyte2 res 1 +loopcounter res 1 + +mode res 1 +#define MonitorMode mode,0 ;The code expects this to be bit 0 +#define ChangeMode mode,1 +#define Overrun mode,2 +#define FailSafeMode mode,3 + +Bank0data UDATA 0x20 +rxbuffer res 16 ;Serial receive buffer + +;Variables for temporary storage +s_pclath res 1 ;Used in interrupt routine +tempvar0 res 1 +tempvar1 res 1 +tempvar2 res 1 +float1 res 1 +float2 res 1 + +;Variables for longer lasting storage +rxpointer res 1 +txpointer res 1 +bitcount res 1 ;Count of bits remaining in a message +celciasetpoint res 1 ;Setpoint times 5 as needed for a Celcia 20 +setpoint1 res 1 +setpoint2 res 1 +outside1 res 1 +outside2 res 1 +clock1 res 1 +clock2 res 1 +dhwsetpoint1 res 1 +dhwsetpoint2 res 1 +maxchsetpoint1 res 1 +maxchsetpoint2 res 1 +controlsetpt1 res 1 +controlsetpt2 res 1 +ventsetpoint res 1 +debug res 1 +outputmask res 1 +messagetype res 1 +alternativecmd res 1 +valueindex res 1 +txavailable res 1 +resetflags res 1 +prioritymsgid res 1 +dhwsetpointmin res 1 +dhwsetpointmax res 1 +chsetpointmin res 1 +chsetpointmax res 1 +minutetimer res 1 +#define ChangeTime minutetimer,7 + +SecCounter res 1 +MessagePtr res 1 +RemOverrideFunc res 1 +#define ManChangePrio RemOverrideFunc,0 +#define ProgChangePrio RemOverrideFunc,1 +#define TempChangePrio RemOverrideFunc,5 + +LineVoltage res 1 +MaxModLevel res 1 +GPIOFunction res 1 ;Funtions assigned to GPIO pins + +flags res 1 ;General bit flags +#define OutsideTemp flags,0 ;Keep in sync with ds1820.asm +#define OutsideInvalid flags,1 ;Keep in sync with ds1820.asm +#define SummaryRequest flags,2 +#define BoilerAlive flags,3 +#define BoilerFault flags,4 +#define NegativeTemp flags,6 +#define LeadingZero flags,7 + +stateflags res 1 ;More bit flags +#define MessageRcvd stateflags,0 +#define Unsolicited stateflags,1 +#define HideReports stateflags,2 +#define CommandComplete stateflags,3 +#define NoAlternative stateflags,4 +#define AlternativeUsed stateflags,5 +#define OverrideUsed stateflags,6 +#define BoilerResponse stateflags,7 + +msgflags res 1 ;Used in interrupt routine +#define NextBit msgflags,0 +#define Transition msgflags,1 +#define Comparator1 msgflags,2 ;Must be bit 2 +#define Comparator2 msgflags,3 ;Must be bit 3 +#define Transmit msgflags,4 +#define Intermission msgflags,5 +#define MidBit msgflags,6 +#define Parity msgflags,7 +#define NextBitMask 1 << 0 + +quarter res 1 ;Count of 1/4 ms, used in interrupt routine +#define LineStable quarter,0 +#define RealBit quarter,1 +#define BitClkCarry quarter,3 + +tstatflags res 1 ;Used in- and outside interrupt routine +#define SmartPower tstatflags,7 ;Thermostat supports Smart Power +#define DisconnectEvent tstatflags,5 ;Long high without Smart Power +#define OneSecond tstatflags,4 ;Time to send the next message +#define Request tstatflags,3 ;Event on the thermostat line +#define PowerReport tstatflags,2 +#define HighPower tstatflags,1 +#define RaisedPower tstatflags,0 + +override res 1 +#define OverrideBlink override,7 ;Keep track of blinking LED +#define OverrideWait override,6 ;Override has not yet been sent +#define OverrideFunc override,5 ;ID100 has not yet been sent +#define OverrideClr override,4 ;Cancel override request +#define OverrideReq override,3 ;Override temp was requested +#define OverrideAct override,2 ;Override accepted by thermostat +;Bits 0&1 are used as a counter to allow 3 attempts for the override to stick + +initflags res 1 +#define InitParameter initflags,0 +#define InitHotWater initflags,1 +#define InitHeating initflags,2 +#define WaterSetpoint initflags,3 +#define MaxHeatSetpoint initflags,4 +#define NoResetUnknown initflags,6 +#define PriorityMsg initflags,7 + +onoffflags res 1 +#define dhwupdate onoffflags,0 ;Must be bit 0 +#define maxchupdate onoffflags,1 ;Must be bit 1 +#define NoThermostat onoffflags,2 +#define HeatDemand onoffflags,3 +#define HotWaterSwitch onoffflags,4 +#define HotWaterEnable onoffflags,5 +#define UserMaxModLevel onoffflags,6 +#define CHModeOff onoffflags,7 + +gpioflags res 1 +#define gpioaway gpioflags,0 +#define VirtualLedE gpioflags,1 +#define VirtualLedF gpioflags,2 +#define gpio_port1 gpioflags,6 +#define gpio_port2 gpioflags,7 +#define gpio_mask gpioflags + +;Flags to work around quirks of remeha thermostats +remehaflags res 1 +#define TStatRemeha remehaflags,7 ;Auto-detected Remeha Thermostat +#define TStatISense remehaflags,6 ;Thermostat is Remeha iSense +#define TStatCelcia remehaflags,5 ;Thermostat is Remeha Celcia 20 +#define TStatManual remehaflags,4 ;Thermostat model set manually + +s_timer2 res 1 ;Store timer 2 value, used in interrupt routine + +errornum res 1 +errortimer res 1 + +repeatcount res 1 ;Count same message from the thermostat + +TSPCount res 1 +TSPIndex res 1 + +;If the serial receive pin is disconnected and it picks up some 50Hz noise, the +;AUSART would detect a BREAK every 20ms. To prevent the gateway from repeatedly +;resetting in this situation, wait for 32 TMR0 overflows (slightly over 0.5s) +;without a BREAK before acting on a BREAK again. +BreakTimer res 1 +#define NoBreak BreakTimer,5 ;Set after 32 TMR0 overflows + +settings res 1 ;Bits 0-4: voltage reference +#define IgnoreErr1 settings,5 ;For bouncing high<->low changes +#define OverrideHigh settings,6 ;Copy MsgID100 data to high byte + +resetreason res 1 +boilercom res 1 + + global float1, float2, flags, temp, StoreOutTemp + +Bank1data UDATA 0xA0 +;The functions that can be assigned to a LED are referred to by an uppercase +;letter. One byte is reserved for each possible function. Bits 3, 4, 6, and 7 +;indicate if the function is assigned to one or more of the four LED outputs. +;Bit 0 is used to keep track of the current state of the function. That makes +;it possible to immediately set a LED to the correct state upon assignment of +;the function. +functions res 26 + +;The gateway keeps track of which messages are not supported by the boiler. +;The code counts the times the boiler responds with an Unknown-DataID in two +;bits per DataID. If the counter for a specific DataID reaches 3, the gateway +;stops sending the DataID to the boiler. The next time the thermostat requests +;that information again, the gateway will actually send a different request to +;the boiler. This allows gathering of information that the thermostat does not +;normally request. +unknownmap res 32 ;2 bits per DataID for 128 DataID's + +; 6 bytes remaining in Bank 1 + +Bank1values UDATA 0xE0 +ResponseValues1 res 16 ;DataID's of fake responses + + +Bank2data UDATA 0x110 +;Keep track of the data values of up to 40 DataID's +valuestorage res 80 ;2 bytes per DataID for up to 40 DataID's +Bank2values UDATA 0x160 +ResponseValues2 res 16 ;High byte of fake responses + +Bank3data UDATA 0x190 +;Use all available RAM in bank 3 for the transmit buffer + constant TXBUFSIZE=80 +txbuffer res TXBUFSIZE +Bank3values UDATA 0x1E0 +ResponseValues3 res 16 ;Low byte of fake responses + +;I/O map +#define MasterIn CMCON,C1OUT +#define SlaveIn CMCON,C2OUT +#define SlaveOut PORTA,3 +#define MasterOut PORTA,4 +#define RXD PORTB,2 +#define TXD PORTB,5 +#define CCP1 PORTB,0 +#define LED_A PORTB,3 +#define LED_B PORTB,4 +#define LED_C PORTB,6 +#define LED_D PORTB,7 +#define RSET PORTB,1 ;Used by self-programming + +#define SlaveMask b'00001000' +#define MasterMask b'00010000' + +package macro pkg +pkg code +pkg + endm + +pcall macro rtn + lcall rtn + pagesel $ + endm + +;Skip the next instruction (bit 7 of PCLATH is always 0) +skip macro + btfsc PCLATH,7 + endm + +;Get the output of the active comparator into the carry bit +getcompout macro + bsf STATUS,RP0 + rlf CMCON,W ;Get the output of comparator 2 + bcf STATUS,RP0 + btfsc Request ;A request goes through comparator 1 + addlw b'10000000' ;Cause a carry if C1OUT is high + endm + +;The first thing to do upon a reset is to allow the firmware to be updated. +;So no matter how buggy freshly loaded firmware is, if the first two command +;have not been messed up, the device can always be recovered without the need +;for a PIC programmer. The self-programming code times out and returns after +;a second when no firmware update is performed. +; + +ResetVector code 0x0000 + lcall SelfProg ;Always allow a firmware update on boot + lgoto Start ;Start the opentherm gateway program + +;Handle interrupts. This piece of code saves the important registers and finds +;out which interrupt fired. It then calls a subroutine to handle that specific +;interrupt. Upon return from the subroutine, it restores the saved registers. +; +InterruptVector code 0x0004 + movwf s_wreg ;Save the work register + swapf STATUS,W ;Don't change any status flags + clrf STATUS ;Start with a known state + movwf s_status ;Save the (swapped) status register + movfw FSR + movwf s_fsr ;Save the FSR register + movfw PCLATH + movwf s_pclath + pagesel Interrupt + movfw TMR2 + movwf s_timer2 ;Save the current value of timer2 + ;Process the timer 2 interrupt before the comparator + ;interrupt to get the correct overflow count + btfsc PIR1,TMR2IF ;Check if timer 2 matched + call timer2int + btfsc PIR2,CMIF ;Check if an input changed + call inputint + ;Restore the registers and return from the interrupt + movfw s_pclath + movwf PCLATH + movfw s_fsr ;Get the old value of the FSR register + movwf FSR ;Restore the indirect addressing pointer + swapf s_status,W ;Swap the saved status register back + movwf STATUS ;Restore the status register + swapf s_wreg,F ;Don't change any status flags + swapf s_wreg,W ;Restore the work register + retfie ;End of interrupt routine + +;######################################################################## +;Interrupt routines +;######################################################################## +; +;The meaning of events on the opentherm interface depends on the logical levels +;and timing. The state is tracked via a number of bits and variables. +; +;TMR2ON==0 && CXOUT==1: Line idle +;TMR2ON==0 && CXOUT:=0: Start T2 +;bitcount==0 && quarter<=3 && CXOUT:=1: Start of opentherm message. Restart T2 +;bitcount>0 && quarter<=3 && CXOUT:=0: Midbit transition, ignore. +;bitcount>0 && quarter<=3 && CXOUT:=1: Midbit transition, ignore. +;bitcount>0 && quarter>3 && quarter<=6 && CXOUT:=0: Bit: 0. Restart T2 +;bitcount>0 && quarter>3 && quarter<=6 && CXOUT:=1: Bit: 1. Restart T2 +;quarter:=20 && C1OUT==0 && C1INV==0: Smartpower on. +;quarter>20 && quarter<80 && + + package Interrupt + +;Comparator interrupt routine +inputint bsf STATUS,RP0 + swapf CMCON,W ;Read to end the mismatch condition + bcf STATUS,RP0 + bcf PIR2,CMIF ;Clear the interrupt + xorwf msgflags,W ;Determine which comparator has changed + andlw b'00001100' ;Filter out the comparator bits + skpnz ;Make sure there actually was a change + return ;False trigger + xorwf msgflags,F ;Update the saved state + andlw b'00000100' ;Check for a change from the thermostat + skpnz ;Thermostat wants attention + btfss Request ;Or not receiving a request + goto inputmark ;then continue + movlw -1 ;Suppress errors for a while + movwf errornum + return + +;The thermostat may try to change power levels while the gateway is in the +;process of transmitting a message, or receiving a message from the boiler. +;The same timer is used for all opentherm communication, so the gateway can't +;handle the two things at the same time. Ignoring the thermostat may result in +;incorrect power levels leading to user-visible artefacts (like a flashing +;backlight of the thermostat screen). So, for a smoother user experience, we +;abort the current operation when the thermostat asks for attention. +inputmark skpz ;Z bit is cleared if comparator1 changed + btfsc Request ;Already listening to the thermostat + goto inputboiler ;Not a new event from the thermostat + bsf Request ;Receiving from the thermostat + tstf bitcount ;Message in progress? + skpnz + btfsc Transmit ;Transmission in progress? + bcf Request + goto activity ;End of the interrupt routine +inputboiler tstf bitcount ;Is a message in progress? + skpnz + goto InputEvent ;Line state change outside message + movlw 3 ;3 overflows of timer 2 = 750 us + subwf quarter,W ;Check the time since the last bit + skpc ;More than 750 us + goto midbitchange + getcompout ;Get the comparator out + decfsz bitcount,F ;Is this the stop bit? + goto storebit ;If not, store the bit in the buffer + skpc ;Carry is set when the line is low + goto error02 ;A stop bit must be logical 1 + btfsc Parity ;Check for even parity + goto error04 ;Parity error + bsf MessageRcvd ;Indicate that a message was received + pagesel Message + movlw 'R' ;Receive function + call SwitchOffLED ;Switch off the receive LED + movlw 'T' ;Thermostat function + call SwitchOffLED ;Switch off the thermostat LED + movlw 'B' ;Boiler function + call SwitchOffLED ;Switch off the boiler LED + pagesel Interrupt + bcf Request ;Thermostat is done +inputspike bcf T2CON,TMR2ON ;Stop timer 2 + goto activity ;End of the interrupt routine + +inputabort clrf bitcount ;Abort message + bcf Transmit ;Clear flag + movlw b'00011000' ;Default state of the outputs + btfsc RaisedPower + movlw b'00001000' ;Idle state of port 4 is high + xorwf PORTA,W ;Check against the current state + andlw b'00011000' ;Only consider port 3 and 4 + xorwf PORTA,F ;Update the ports + pagesel Message + movlw 'X' + call SwitchOffLED ;Switch off transmit LED + movlw 'B' + call SwitchOffLED ;Switch off boiler LED + pagesel Interrupt + goto restarttimer ;Reset T2 and 1/4 ms counter + +storebit ;Store the current bit in the message buffer + movlw 1 << 7 ;Mask for the bit flag tracking parity + skpnc + xorwf msgflags,F ;Update the parity + rlf byte4,F ;Shift the 32 bits receive buffer + rlf byte3,F ;Shift bits 8 through 15 + rlf byte2,F ;Shift bits 16 through 23 + rlf byte1,F ;Shift bits 24 through 31 + bcf MidBit ;Mid-bit transitions are allowed again +restarttimer clrf quarter ;Restart 1/4 ms counter + movfw s_timer2 ;Timer 2 value just after the interrupt + subwf TMR2,F ;Adjust the timer value + movlw 6 ;Difference between 255 and PR2 + skpc + subwf TMR2,F ;Compensate for early reset of T2 + bcf PIR1,TMR2IF ;Interrupt routine is less than 250us. +activity movlw PRELOADT0 ;Idle time preload value + movwf TMR0 ;Restart idle timer + return ;End of the interrupt routine + +InputEvent ;The line state changed while not receiving a message. Need to + ;figure out what is going on. + btfsc T2CON,TMR2ON ;Timer2 is stopped when the line is idle + goto InputProcess + ;If an unreported error has happened, we're still waiting for + ;the line to become idle again. + tstf errornum ;Is there an unreported error? + skpz + goto activity ;Restart the non-idle timer + ;Change of a previously idle line + movlw 42 ;Time since start of interrupt routine + movwf TMR2 ;Initialize timer 2 + bsf T2CON,TMR2ON ;Start timer2 + clrf quarter ;Reset the 1/4 ms counter + bsf STATUS,RP0 + bsf PIE1,TMR2IE ;Enable timer2 interrupts + bcf STATUS,RP0 + goto activity ;The change has no meaning + +InputProcess ;A second line change happened shortly after the previous one. + movlw 10 + subwf quarter,W + skpnc ;Less than 2.5 ms since the last change + goto restarttimer +InputFast getcompout ;Get the comparator output + skpc ;Carry is set when the line is low + goto activity ;Not a start bit + bsf STATUS,RP0 + btfss CMCON,C1INV ;Carry is guaranteed to be set here + clrc ;Clear carry if not inverting + bcf STATUS,RP0 + skpnc ;Thermostat line not inverted? + btfsc HighPower ;High power mode not yet established? + goto StartBit + bsf HighPower ;Start of message on inverted line + bsf PowerReport ;Report high power mode +StartBit tstf quarter ;Check if pulse was less than 250us + skpnz + goto inputspike ;Ignore spikes + pagesel Message + movlw 'R' ;Receive function + call SwitchOnLED ;Turn on the receive LED + movlw 'B' ;Boiler function + btfsc Request + movlw 'T' ;Thermostat function + call SwitchOnLED ;Switch on the boiler or thermostat LED + pagesel Interrupt + movlw 33 ;Message length: 32 bits + stop bit - 1 + movwf bitcount ;Initialize the counter + bcf Parity ;Initialize the parity check flag + bcf MidBit ;No mid-bit transitions yet + goto restarttimer ;Significant transition of the start bit + +;Timer 2 interrupt routine + +timer2int bcf PIR1,TMR2IF ;Clear the interrupt flag + incf quarter,F ;Update the 1/4 ms counter + btfsc Transmit ;Transmitting a message? + goto timer2xmit + tstf bitcount ;Message in progress? + skpnz + goto timer2idle + btfsc BitClkCarry ;Bit transition happened on time? + goto error03 ;Report missing transition + return + +timer2xmit btfsc LineStable ;Nothing to do in a stable interval + return + tstf bitcount + skpnz + goto timer2xmitend + clrw ;Initialize work register + btfss NextBit ;Find out the desired output level + movlw b'00011000' ;Set bits matching the opentherm outputs + btfsc RaisedPower ;Using raised power to the thermostat? + xorlw b'00010000' ;Invert the bit for the thermostat line + xorwf PORTA,W ;Compare with the current port levels + andwf outputmask,W ;Only apply for the relevant output + xorwf PORTA,F ;Update the output port + btfsc RealBit ;Check if this was the start of a bit + goto InvertBit ;Next time send the inverted value + bcf NextBit ;Start with assumption next bit is 0 + setc ;Prepare to shift in a stop bit + rlf byte4,F ;Shift the full 32 bit message buffer + rlf byte3,F ;Shift bits 8 through 15 + rlf byte2,F ;Shift bits 16 through 23 + rlf byte1,F ;Shift bits 24 through 31 + skpnc ;Test the bit shifted out of the buffer + bsf NextBit ;Next time, send a logical 1 + clrf quarter + decf bitcount,F ;Test for the end of the message + return ;Return from the interrupt routine + +timer2xmitend bcf T2CON,TMR2ON ;Stop timer 2 + bcf Transmit ;Finished transmitting + btfsc outputmask,4 ;Transmission to the thermostat? + bsf Intermission ;Message exchange is finished + pagesel Message + movlw 'X' ;Transmit function + call SwitchOffLED ;Switch off the transmit LED + movlw 'T' ;Thermostat function + call SwitchOffLED ;Switch off the thermostat LED + movlw 'B' ;Boiler function + call SwitchOffLED ;Switch off the boiler LED + pagesel Interrupt + return ;Return from the interrupt routine + +InvertBit movlw NextBitMask ;Load a mask for the NextBit flag + xorwf msgflags,F ;Invert the bit + return ;Return from the interrupt routine + +timer2idle btfss Request ;Event was on the thermostat line? + return ;Other events only apply to thermostat + movfw quarter + sublw 20 ;New state persisted for 5ms? + skpnz + goto timer2invert + movlw 80 + subwf quarter,W ;20ms since last line event? + skpc + return + bcf T2CON,TMR2ON ;No line event for 20ms + bcf Request ;Thermostat is done + bsf STATUS,RP0 + btfss CMCON,C1INV ;Carry is guaranteed to be set here + clrc ;Clear carry if not inverting + bcf STATUS,RP0 + skpc ;Thermostat line inverted? + return + bsf HighPower ;low/medium => high power transition + bsf PowerReport ;Power levels have changed + return + +timer2invert btfsc Comparator1 ;Line high (according to current mode)? + return ;Not interresting + btfss SmartPower ;Thermostat supports Smart Power? + goto ExtendedHigh ;Long high level without Smart Power + bsf STATUS,RP0 ;Switch to bank 1 + movlw 1 << C1INV ;Bit mask for the comparator1 invert bit + xorwf CMCON,F ;Toggle the invert bit + btfss CMCON,C1INV ;Carry is guaranteed to be set here + clrc ;Clear carry if not inverting + swapf CMCON,W ;Read the new comparator states + bcf STATUS,RP0 + bcf PIR2,CMIF ;Clear the interrupt caused by inverting + xorwf msgflags,W ;Get the saved comparator state + andlw b'00001100' ;Filter out the comparator bits + ;Always expect W to be 0x04 here. + xorwf msgflags,F ;Update the saved state + skpnc ;Inverting + goto SmartPowerOn + bsf PowerReport ;Power levels have changed + btfss HighPower + return ;Medium power + bcf RaisedPower ;High/Medium => low transition + bcf HighPower ;Clear bit for next round + bsf MasterOut ;Make the thermostat line low + movlw 'P' + pcall SwitchOffLED ;Switch off the raised power mode LED + return +ExtendedHigh bsf DisconnectEvent ;Simulated disconnect + return +SmartPowerOn btfsc RaisedPower ;Already in raised power mode? + goto MaximumPower + bsf RaisedPower ;In raised power mode + bcf MasterOut ;Make the thermostat line high + movlw 'P' + pcall SwitchOnLED ;Switch on the raised power mode LED + return +MaximumPower bsf HighPower ;This will produce the desired behavior + return + +midbitfirst bsf MidBit ;Remember a mid-bit transition happened + goto activity +midbitchange btfsc MidBit ;First mid-bit transition? + btfsc IgnoreErr1 ;Or multiple transitions are ignored? + goto midbitfirst ;Register mid-bit transition + +;Error 01: More than one mid-bit transition +error01 movlw 1 + goto errorcleanup + +;Error 02: Wrong stop bit +error02 movlw 2 + goto errorcleanup + +;Error 03: Bit not received in time +error03 movlw 3 + goto errorcleanup + +;Error 04: Parity error +error04 movlw 4 + +errorcleanup tstf errornum + skpnz + movwf errornum + bcf T2CON,TMR2ON ;Stop timer 2 + clrf bitcount + clrf quarter + bcf Transmit + pagesel Message + movlw 'X' + call SwitchOffLED ;Switch off the transmit LED + movlw 'R' + call SwitchOffLED ;Switch off the receive LED + movlw 'B' + call SwitchOffLED ;Switch off the boiler LED + movlw 'T' + call SwitchOffLED ;Switch off the thermostat LED + comf errornum,W + skpnz + goto errorskip ;Not a real error + movlw 'E' + call SwitchOnLED ;Switch on the error LED + movlw TWOSEC ;Leave the error LED on for 2 seconds + movwf errortimer +errorskip lgoto activity ;Wait for the line to go idle + +;######################################################################## +; Main program +;######################################################################## + + package Main +Break tstf RCREG ;Clear the RCIF interrupt flag + btfsc NoBreak + goto Restart2 + clrf BreakTimer ;Reset break-free timer + goto MainLoop ;continue running the mainloop +Restart1 bsf resetreason,0 ;Reset because we're stuck in a loop +Restart2 bsf resetreason,1 ;Reset due to BREAK on serial line + pcall SelfProg ;Jump to the self-programming code +Start bcf STATUS,RP1 + bcf STATUS,RP0 + clrf PORTB ;Flash the LED's on startup + bsf STATUS,RP0 + movlw b'01100000' ;Internal oscillator set to 4MHz + movwf OSCCON ;Configure the oscillator + + ;Configure I/O pins + ;Power on defaults all pins to inputs + ;Port A + ;Pins 0 and 1 are used as comparator inputs + ;Pin 2 is used as VREF (must be configured as input!) + ;Pins 3 and 4 are (comparator) ouputs + ;Pin 5 is master reset input + ;Pins 6 and 7 are GPIO + movlw b'11100111' + movwf TRISA + + ;Port B + ;Pins 2 and 5 are used by the USART and don't need to be configured + ;Pins 3, 4, 6, and 7 are used to indicate events for debugging +#ifndef LVP + movlw b'00100111' +#else + ;Can't use RB3 if LVP is enabled + movlw b'00101111' +#endif + movwf TRISB + + ;Check for reset reason + movlw b'11' + xorwf PCON,W + movlw 4 << 4 + btfsc PCON,NOT_POR + movlw 5 << 4 + bcf STATUS,RP0 + skpnz + swapf resetreason,W + movwf mode + btfss STATUS,NOT_TO + bsf FailSafeMode + bsf STATUS,RP0 + movlw b'11' + movwf PCON + + ;Configure Timer 0 & Watchdog Timer + ;Bit 7 RBPU = 1 - Port B pull up disabled + ;Bit 6 INTEDG = 1 - Interrupt on rising edge + ;Bit 5 T0CS = 0 - Internal clock + ;Bit 4 T0SE = 1 - High to low transition + ;Bit 3 PSA = 0 - Prescaler assigned to Timer 0 + ;Bit 2-0 PS = 5 - 1:64 Prescaler + movlw b'11010101' + movwf OPTION_REG + + ;Configure Timer 2 + ;Generate an interrupt every 250uS (one fourth of a bit) + ;Bit 6-3 TOUTPS = 0 - 1:1 Postscaler + ;Bit 2 TMR2ON = 0 - Timer disabled + ;Bit 1-0 T2CKPS = 0 - 1:1 Prescaler + movlw PERIOD ;Fosc / 16000 - 1 + movwf PR2 + bcf STATUS,RP0 +#ifndef T2PRESCALER + movlw b'00000000' +#else + movlw b'00000001' +#endif + movwf T2CON + + clrf TMR1L + clrf TMR1H + ;Configure Timer 1 + ;Used to blink LEDs approximately once per second + ;Bit 5-4 T1CKP = 3 - 1:8 Prescale value + ;Bit 3 T1OSCEN = 0 - Oscillator is shut off + ;Bit 2 T1SYNC = 0 - Bit is ignored because TMR1CS=0 + ;Bit 1 TMR1CS = 0 - Internal clock (Fosc/4) + ;Bit 0 TMR1ON = 1 - Enable Timer1 + movlw b'110001' + movwf T1CON + + ;Delay a few milliseconds to show the lit LEDs + clrf TMR0 + bcf INTCON,TMR0IF +WaitTimer0 clrwdt + btfss INTCON,TMR0IF + goto WaitTimer0 + + ;Configure comparator module + ;Bit 7 C2OUT = R/O + ;Bit 6 C1OUT = R/O + ;Bit 5 C2INV = 0 - Not inverted + ;Bit 4 C1INV = 0 - Not inverted + ;Bit 3 CIS = 0 - Irrelevant + ;Bit 2-0 = 3 - Common reference / 6 - with outputs + bsf STATUS,RP0 + movlw b'00000011' ;Common reference mode + btfsc FailSafeMode + movlw b'00000110' ;Common reference with outputs + movwf CMCON + movlw b'00000111' ;A0 through A2 are used for analog I/O + movwf ANSEL + + ;Configure the serial interface + movlw BAUD + movwf SPBRG + bsf TXSTA,BRGH ;9600 baud + bcf TXSTA,SYNC ;Asynchronous + bsf TXSTA,TXEN ;Enable transmit + bcf STATUS,RP0 + bsf RCSTA,SPEN ;Enable serial port + btfss FailSafeMode + bsf RCSTA,CREN ;Enable receive + + ;Configure A/D converter + movlw b'01000001' ;A/D on, channel 0, Fosc/8 + movwf ADCON0 + + ;Clear RAM in bank 0 & 1 + movlw 0x20 + movwf FSR + movlw 80 + movwf loopcounter + movlw 0x80 +InitLoop0 clrf INDF + xorwf FSR,F + clrf INDF + incf FSR,F + decfsz loopcounter,F + goto InitLoop0 + ;Clear RAM in bank 2 + movlw low 0x110 + bsf STATUS,IRP + movwf FSR + movlw 96 + movwf loopcounter +InitLoop1 clrf INDF + incf FSR,F + decfsz loopcounter,F + goto InitLoop1 + + ;Configure the voltage reference module + ;The reference voltage must be set to 1.3V + ;Bit 7 VREN = 1 - VREF Enabled + ;Bit 6 VROE = 1 - Output on pin RA2 + ;Bit 5 VRR = 1 - Low range + ;Bit 3-0 VR = 6 - 1.25V (7 - 1.46V) + movlw SavedSettings + call ReadEpromData ;Get the setting from EEPROM + movwf settings ;Store a copy in RAM + iorlw b'11100000' + bsf STATUS,RP0 + movwf CVRCON ;Set the reference voltage + + ;Setup interrupt sources + bsf PIE2,CMIE ;Enable comparator interrupt + bsf PIE1,TMR2IE ;Enable timer 2 interrupts + + swapf CMCON,W + clrf STATUS + bcf PIR2,CMIF ;Clear any comparator interrupt + andlw b'00001100' + movwf msgflags + rlf msgflags,W + iorlw b'11100111' + movwf PORTA + movlw b'11111111' + movwf PORTB + bsf INTCON,PEIE + btfss FailSafeMode + bsf INTCON,GIE + comf alternativecmd,F;Compensate for initial increment + movlw b'111' + movwf initflags ;Nothing has been initialized yet + movlw MIN_DHWSETPOINT + movwf dhwsetpointmin + movlw MAX_DHWSETPOINT + movwf dhwsetpointmax + movlw MIN_CHSETPOINT + movwf chsetpointmin + movlw MAX_CHSETPOINT + movwf chsetpointmax + + movlw 100 + movwf MaxModLevel ;Initialize Max Modulation Level at 100% + + movlw ONESEC + movwf SecCounter ;Initialize second counter + + movlw TStatModel + call ReadEpromData + movwf remehaflags + + ;Reinstall manually configured unsupported MsgID's + movlw 16 ;Size of flag data: 16 * 8 = 128 msgs + movwf loopcounter ;Initialize loop counter + clrf float1 ;Message number + movlw UnknownFlags ;Start of bitmap in data EEPROM + movwf tempvar0 ;Pointer to byte of bitmap +UnknownLoop1 movfw tempvar0 ;Get the bitmap pointer + call ReadEpromData ;Read the flags for 8 MsgID's + movwf float2 ;Temporary storage of bit flags + movfw float1 ;Number of the first of 8 MsgID's +UnknownLoop2 tstf float2 ;Check if any remaining flag is set + skpnz + goto UnknownJump2 ;Skip over the rest + movwf float1 ;Update the message number + clrc + rrf float2,F ;Shift a flag into the carry bit + skpc + goto UnknownJump1 ;Current message is not flagged + pcall UnknownMask2 ;Get mask and pointer into unknownmap + iorwf INDF,F ;Mark message as unsupported +UnknownJump1 incf float1,W ;Next message + goto UnknownLoop2 ;Repeat the loop +UnknownJump2 movlw b'111' + iorwf float1,F ;The last of the current 8 MsgID's + incf float1,F ;The first of the next 8 MsgID's + incf tempvar0,F ;Also advance the bitmap pointer + decfsz loopcounter,F + goto UnknownLoop1 ;Repeat the loop + + movlw GreetingStr + pcall PrintStringNL + + ;Initialize the LED functions + clrf temp + movlw FunctionLED1 + call ReadEpromData + pcall SetLEDFunction + incf temp,F + movlw FunctionLED2 + call ReadEpromData + pcall SetLEDFunction + incf temp,F + movlw FunctionLED3 + call ReadEpromData + pcall SetLEDFunction + incf temp,F + movlw FunctionLED4 + call ReadEpromData + pcall SetLEDFunction + incf temp,F + movlw FunctionLED5 + call ReadEpromData + pcall SetLEDFunction + incf temp,F + movlw FunctionLED6 + call ReadEpromData + pcall SetLEDFunction + + ;Initialize the GPIO ports + movlw FunctionGPIO + call ReadEpromData + movwf GPIOFunction + pcall gpio_init + + btfss FailSafeMode + goto MainLoop + movlw 'M' + lcall SwitchOnLED + movlw TimeoutStr + pcall PrintStringNL + +;************************************************************************ + +MainLoop clrwdt + + tstf txavailable ;Check if there is data to transmit + skpz + call ProcessOutput + + btfsc FailSafeMode + goto MainLoop + + ;Check if an OpenTherm message was received + btfsc MessageRcvd + call OpenThermMsg + + btfsc PowerReport + call ReportPower ;Report a power level change + + btfsc INTCON,TMR0IF + call IdleTimer + + btfsc PIR1,TMR1IF + call FlashTimer + + btfsc PIR1,ADIF + call CheckThermostat + + pcall gpio_common + + btfsc SummaryRequest ;Summary report requested/in progress? + call SummaryReport + + btfsc repeatcount,6 ;Reset after same message 64 times + goto Restart1 + + btfss PIR1,RCIF ;Activity on the serial receiver? + goto MainLoop + + ;FERR is only relevant if RCIF is set + btfsc RCSTA,FERR ;Check for framing error (break) + goto Break + + call SerialEvent + goto MainLoop + +;Callers depend on this function never setting the carry bit +SerialReceive movfw rxpointer + addlw rxbuffer + movwf FSR + movfw RCREG + btfsc CommandComplete + return ;Not ready to accept a command now + movwf INDF + xorlw 0x0a ;Ignore linefeed characters + skpnz + return + xorlw 0x0a ^ 0x0d ;Check for end of command + skpnz + goto SerialCmdEnd + movfw rxpointer + addlw 1 + skpdc ;All commands are less than 15 chars + movwf rxpointer + return ;User is still typing the command +SerialCmdEnd tstf rxpointer ;Check command length + skpnz + return ;Ignore empty commands + clrf INDF ;Indicate end of command + bsf CommandComplete + return + +SerialEvent btfss RCSTA,OERR ;Check for overrun error + goto SerialEventJmp1 + bcf RCSTA,CREN ;Procedure to clear an overrun error + bsf RCSTA,CREN ;Re-enable the serial interface + bsf Overrun ;Remember for later reporting +SerialEventJmp1 call SerialReceive + btfss CommandComplete + return + lcall SerialCommand + clrf rxpointer ;Prepare for a new command + bcf CommandComplete + pagesel Print + iorlw 0 + skpz + call PrintString ;Print the result + call NewLine + pagesel $ + goto PrtDebugPointer + +OpenThermMsg pcall PrintRcvdMsg ;Report the received message + bcf MessageRcvd ;Message has been reported + btfsc MonitorMode ;In monitor mode there is no need ... + goto MonModeCommOK ;... to explicitly transmit the message + pcall HandleMessage ;Apply special treatment + btfss NoThermostat + goto SendMessage + bsf Intermission ;Message exchange is finished + movfw byte2 + xorwf originalreq,W + skpz + goto PrtDebugPointer ;Didn't get the expected answer + incf MessagePtr,F + bcf MessagePtr,4 +MonModeCommOK clrf repeatcount ;Communication is working + goto PrtDebugPointer ;No need to send the message +SendMessage movlw 1 + movwf quarter ;Initialize the state counter + bsf NextBit ;Start bit is 1 + bsf Transmit ;Starting to transmit a message + movlw 'X' ;Transmit function + pagesel Message + call SwitchOnLED ;Switch on the transmit LED + movlw 'B' ;Boiler function + btfsc MsgResponse + movlw 'T' ;Thermostat function + ; In package Message + call SwitchOnLED ;Switch on the boiler or thermostat LED + movlw SlaveMask ;Transmitting to the boiler + btfsc MsgResponse ;Check message direction + movlw MasterMask ;Transmitting to the thermostat + movwf outputmask + movlw 34 ;A message is 32 bits + start & stop bit + movwf bitcount ;Load the counter + bsf STATUS,RP0 + bsf PIE1,TMR2IE ;Enable timer 2 interrupts + bcf STATUS,RP0 + movlw PERIOD + movwf TMR2 ;Prepare timer 2 to overflow asap + bsf T2CON,TMR2ON ;Start timer 2 +PrtDebugPointer pcall PrintDebug ;Report the debug value, if necessary + return + +ReportPower bcf PowerReport ;Print the report only once + movlw NormalPowerStr ;Normal Power + btfss RaisedPower + goto ReportPowerJ2 + movlw MediumPowerStr ;Medium Power + btfss HighPower + goto ReportPowerJ1 + movfw LineVoltage ;Check the line voltage + sublw V_OPEN ;Thermostat disconnected? + skpc + return ;Don't report the power change + movlw HighPowerStr ;High Power +ReportPowerJ1 lcall PrintString + movlw PowerStr +ReportPowerJ2 pcall PrintStringNL ;Print the new power setting + return + +; IdleTimer is called whenever timer 0 overflows +IdleTimer bcf INTCON,TMR0IF ;Clear Timer 0 overflow flag + decfsz SecCounter,F ;Decrease second counter + goto IdleTimerJ1 + btfsc BoilerAlive ;Ever received a message from boiler? + incf repeatcount,F ;Keep track of repeated messages + bsf OneSecond ;Trigger new message in stand-alone mode + movlw ONESEC + movwf SecCounter ;Reload second counter + incfsz boilercom,W ;Increment variable without rolling over + movwf boilercom + movlw 5 + subwf boilercom,W ;Check for 5 seconds without a message + skpnc + bcf SlaveOut ;Prevent unwanted heating +IdleTimerJ1 btfss NoBreak + incf BreakTimer,F + btfss T2CON,TMR2ON + bsf ADCON0,GO ;Start A/D conversion + pagesel Serial + btfsc ChangeMode ;Is a request to change mode pending? + call SetMonitorMode + pagesel Print + comf errornum,W ;Suppressing errors? + skpnz + clrf errornum ;Re-enable errordetection + tstf errornum ;Check if any errors were detected + skpz + call PrintError ;Report the error on the serial intf + pagesel Main + tstf errortimer + skpz + decfsz errortimer,F + return + movlw 'E' + pcall SwitchOffLED ;Switch off the error LED + return + +FlashTimer bcf PIR1,TMR1IF ;Clear timer overrun flag + btfsc ChangeTime + decf minutetimer,F + btfsc OverrideReq + btfsc OverrideAct + return ;No override request pending + ;Override requested, but not active. Flash the override LED + movlw 1 << 7 ;Mask for the OverrideBlink bit + xorwf override,F + andwf override,W + movlw 'O' + pcall SwitchLED + return + +SummaryReport btfsc txavailable,6 ;Buffer filled less than 80%? + return ;Not enough space in the transmit buffer + pcall PrintStoredVal + movlw SUMMARYFIELDS + subwf valueindex,W + skpnc ;Check if the summary is complete + goto SummaryDone + movlw ',' ;Separator + pcall PrintChar + return +SummaryDone bcf SummaryRequest + pcall NewLine + return + +ProcessOutput btfss PIR1,TXIF ;Check if the transmit buffer is empty + return + movfw txpointer ;Get the offset of the next character + addlw txbuffer ;Add the start of the buffer + movwf FSR ;Setup indirect access + bsf STATUS,IRP ;The buffer is in bank 2 + movfw INDF ;Get the character from the buffer + bcf STATUS,IRP ;Restore the status register to normal + movwf TXREG ;Load the serial transmitter + incf txpointer,F ;Move to the next character + movlw TXBUFSIZE + xorwf txpointer,W ;Check for the end of the buffer + skpnz + clrf txpointer ;Wrap around to the start + decf txavailable,F ;Adjust character count + return + +;This code should not set the carry bit, it may clear the carry +EepromWait bsf STATUS,RP1 ;EEPROM registers are in bank 2&3 + bsf STATUS,RP0 + btfss EECON1,WR ;Check if a write is in progress + return ;Safe to modify the EEPROM registers + bcf STATUS,RP0 ;Switch back to bank 0 + bcf STATUS,RP1 + btfss PIR1,RCIF ;Check for serial receiver activity + goto EepromWait ;Wait some more + movwf temp2 ;Preserve value in W register + call SerialReceive ;Prevent receiver overrun + movfw temp2 ;Restore original W register value + goto EepromWait ;Wait some more + +ReadEpromData call EepromWait ;Wait for any pending EEPROM activity + clrf EECON1 ;Read from DATA EEPROM + bcf STATUS,RP0 + movwf EEADR ;Setup the EEPROM data address + bsf STATUS,RP0 + bsf EECON1,RD ;Start the EEPROM read action + bcf STATUS,RP0 + movfw EEDATA ;Read the data from EEPROM + bcf STATUS,RP1 ;Switch back to bank 0 + return + +;The Opentherm specification mentions the following special circumstance: +;"The boiler unit must support an important installation feature which allows +;the terminals at the boiler to be short-circuited to simulate a heat demand +;such as can be done with existing on/off boilers. The boiler unit should +;interpret the short-circuit as a heat demand within15 secs of the short- +;circuit being applied. This must be supported by both OT/+ and OT/- boilers. +; +;It is allowable that this can implemented by a software-detection method. The +;software short-circuit condition is defined as a low-voltage state (Vlow) with +;no valid communications frame for at least 5 seconds." +; +;Without special precautions, this situation would occur when the thermostat is +;disconnected while the gateway is still powered up. Without a thermostat, the +;gateway does not generate any messages towards the boiler and the default idle +;level of the Opentherm signal is the mentioned low-voltage state. +; +;To prevent an unintended heat demand when the thermostat is disconnected, the +;thermostat interface is monitored periodically between messages. If the master +;interface is found to be at a high-voltage state, the slave interface is also +;put to the high-voltage state. + +;Measure the voltage on the thermostat line. A value between 10 and 45 is the +;normal idle line value. Between 95 and 115 is the opentherm high level. We'll +;check for a value below 6 (short circuit) or above 122 (open) to detect an +;on/off thermostat (or no thermostat at all). + +CheckThermostat bcf PIR1,ADIF ;Clear flag for next round + movfw ADRESH ;Get the A/D result + xorwf LineVoltage,W ;Swap values of W and LineVoltage + xorwf LineVoltage,F + xorwf LineVoltage,W ;-- + subwf LineVoltage,W ;Calculate the difference + skpc + sublw 0 ;Turn into absolute value + andlw b'11111100' ;Check for limited deviation + skpz + goto Unstable + movfw LineVoltage ;Get the A/D result again + sublw V_OPEN ;Line open? + skpnc + btfsc DisconnectEvent ;Interrupt routine detected disconnect + goto OpenCircuit ;No opentherm thermostat connected + sublw V_OPEN - V_SHORT ;Line shorted? + skpc + goto ShortCircuit ;Heat demand from on/off thermostat + btfss NoThermostat + return +;Any opentherm thermostat will always start in low power mode, so we want to +;see a low line voltage before considering the thermostat to be reconnected. + sublw V_LOW - V_SHORT ;No short, but logical low level? + skpc + goto ThermostatEnd ;Not a logical low opentherm level +Connect bcf NoThermostat ;Thermostat was reconnected + movlw TConnect + pcall PrintStringNL + return +ShortCircuit bsf HeatDemand + goto DumbThermostat +OpenCircuit bcf HeatDemand +DumbThermostat btfsc NoThermostat + goto ThermostatEnd +Disconnect bsf NoThermostat ;Thermostat has been disconnected + movlw TDisconnect ;Report the thermostat was disconnected + pcall PrintStringNL + bcf T2CON,TMR2ON ;Stop timer 2 + bcf INTCON,PEIE ;Prevent peripheral interrupts + bsf STATUS,RP0 + bcf CMCON,C1INV ;Make sure the line is in low power mode + swapf CMCON,W ;Read the new comparator states + bcf STATUS,RP0 + bcf PIR2,CMIF ;Clear the interrupt caused by inverting + bsf INTCON,PEIE ;Re-enable peripheral interrupts + xorwf msgflags,W ;Get the saved comparator state + andlw b'00001100' ;Filter out the comparator bits + xorwf msgflags,F ;Update the saved state + clrf tstatflags ;Forget all thermostat related info + btfss TStatManual ;Manually configure thermostat model + clrf remehaflags + clrf override + clrf setpoint1 + clrf setpoint2 + movlw 'P' + pcall SwitchOffLED ;Switch off Raised Power LED + return +Unstable btfss NoThermostat + return +ThermostatEnd btfss MonitorMode ;Can't send messages in monitor mode + btfss OneSecond ;Time to send the next message? + return + movlw T_READ + movwf byte1 + clrf byte3 + clrf byte4 + call SelectMessage ;Get a message to send from the table + movwf byte2 + movwf originalreq + pcall UnknownMask + skpz ;Check if the boiler supports the msg + goto NoAltMessage + call UserMsg ;Get a msg from the list of alternatives + movwf byte2 + movwf originalreq +NoAltMessage pcall SetParity ;Calculate the parity + bcf AlternativeUsed ;Prevent unnecessary processing + call SendMessage ;Start sending the message + clrf tstatflags ;Message queued - clear all flags + clrf boilercom ;Sent a message to the boiler + return +SelectMessage movlw high MessageTable + movwf PCLATH + movfw MessagePtr + addlw low MessageTable + skpnc + incf PCLATH,F + movwf PCL +MessageTable goto StatusMsg + retlw MSG_BOILERTEMP + goto CtrlSetptMsg + goto UserMsg + retlw MSG_MODULATION + retlw MSG_OUTSIDETEMP + retlw MSG_RETURNTEMP + goto MaxModulation + goto UserMsg + goto StatusMsg + retlw MSG_BOILERTEMP + goto CtrlSetptMsg + retlw MSG_DHWSETPOINT + retlw MSG_MAXCHSETPOINT + goto UserMsg + retlw MSG_CHPRESSURE + +UserMsg pcall Alternative ;Find a user defined message ID + btfss NoAlternative ;Check if any message was found + return + clrf byte1 ;Clear out the fall-back message + clrf byte3 + clrf byte4 + ;Fall through so a status message is used as filler +StatusMsg tstf controlsetpt1 ;User specified setpoint? + skpz + btfsc CHModeOff + goto StatusMsgJmp1 + bsf byte3,0 ;Controlsetpt set and CHModeOff == 0 +StatusMsgJmp1 skpnz + btfss HeatDemand + goto StatusMsgJmp2 + bsf byte3,0 ;Controlsetpt clear and HeatDemand == 1 +StatusMsgJmp2 btfsc HotWaterEnable ;Check hot water user setting + bsf byte3,1 ;Enable domestic hot water + retlw MSG_STATUS ;Send a status message + +CtrlSetptMsg bsf byte1,4 ;This is a write-data request + movfw controlsetpt2 + movwf byte4 + movfw controlsetpt1 ;Check the user defined ctrl setpoint + skpz ;No user defined control setpoint + goto CtrlSetptMsgRet + movlw 10 ;Default control setpoint + btfss HeatDemand ;Check if lines are short-circuited + goto CtrlSetptSimple ;Send control setpoint for heating off + movfw maxchsetpoint2 + movwf byte4 + movfw maxchsetpoint1 ;Did user specify a max ch setpoint? + skpz + goto CtrlSetptMsgRet + movfw chsetpointmax ;Use the maximum control setpoint +CtrlSetptSimple clrf byte4 +CtrlSetptMsgRet movwf byte3 ;Specify the desired control setpoint + retlw MSG_CTRLSETPT ;Send a control setpoint message + +MaxModulation bsf byte1,4 ;This is a write-data request + movfw MaxModLevel ;Get user specified level (default 100%) + movwf byte3 + retlw MSG_MAXMODULATION + +;************************************************************************ + + package Print +PrintString pcall EepromWait ;Wait for any pending EEPROM activity + bcf STATUS,RP0 + movwf EEADR + goto PrintJump +PrintLoop call PrintChar + bsf STATUS,RP1 + incf EEADR,F +PrintJump bsf STATUS,RP0 + bsf EECON1,RD + bcf STATUS,RP0 + movfw EEDATA + bcf STATUS,RP1 + skpz + goto PrintLoop + return + +PrintStringNL call PrintString +NewLine movlw 0x0d + call PrintChar + movlw 0x0a + goto PrintChar + +PrintHex movwf temp2 ;Temporarily store the byte + swapf temp2,W ;Get the high nibble + call PrintXChar ;And print it + movf temp2,W ;Next, get the low nibble + ;Fall through to the subroutine to print a hex character +PrintXChar andlw B'00001111' ;Extract the low nible + addlw -10 ;Check if is a digit or alpha char + skpnc + addlw 'A' - '9' - 1 ;Bridge the gap in the ASCII table + addlw 10 ;Re-add the amount subtracted earlier +PrintDigit bsf LeadingZero ;Don't skip any future zeroes + addlw '0' ;Convert to ASCII digit +PrintChar tstf txavailable ;Check if some data is already waiting + skpnz + btfss PIR1,TXIF ;Check the transmit register + goto PrintBuffer ;Need to put the byte into the buffer + movwf TXREG ;Can transmit immediately + retlw 0 +PrintBuffer movwf FSR ;Abuse FSR for temporary storage + movfw txpointer ;Current position in the transmit buffer + addwf txavailable,W ;Add number of pending characters + addlw -TXBUFSIZE ;Check for wrap-around + skpc + addlw TXBUFSIZE ;No wrap, recalculate original value + addlw txbuffer ;Next free position in transmit buffer + xorwf FSR,W ;Exchange FSR and W + xorwf FSR,F + xorwf FSR,W ;-- + bsf STATUS,IRP ;Buffer is in bank 2 + movwf INDF ;Store byte in the buffer + bcf STATUS,IRP ;Restore status register to normal + movfw txavailable ;Get number of pending characters + xorlw TXBUFSIZE ;Compare against buffer size + skpz ;Buffer full? + incf txavailable,F ;One more character in the buffer + retlw 0 + +PrintRcvdMsg movlw 'T' + btfsc MsgResponse + movlw 'B' +PrintMessage btfsc HideReports + return ;Don't print the message + call PrintChar + movfw byte1 + call PrintHex + movfw byte2 + call PrintHex + movfw byte3 + call PrintHex + movfw byte4 + call PrintHex + goto NewLine + +PrintError btfsc PowerReport ;Pending power change report? + return ;Error probably due to the power change + movlw ErrorStr + call PrintString + movfw errornum + call PrintHex + call NewLine + movlw 'E' + btfss errornum,0 + call PrintMessage ;Print message for error 2 and 4. + clrf errornum +PrintDebug movfw debug + skpnz + return + movwf FSR + movfw INDF + movwf temp + movfw debug + call PrintHex + movlw '=' + call PrintChar + movfw temp + call PrintHex + goto NewLine + +PrintStoredVal movfw valueindex + addwf valueindex,W + addlw valuestorage + movwf FSR + bsf STATUS,IRP + movfw INDF + movwf float1 + incf FSR,F + movfw INDF + movwf float2 + bcf STATUS,IRP + movlw high StoreProcTable + movwf PCLATH + movfw valueindex + incf valueindex,F + addlw low StoreProcTable + skpnc + incf PCLATH,F + movwf PCL +StoreProcTable goto PrintStatus ;Message ID 0 + goto PrintFloat ;Message ID 1 + goto PrintStatus ;Message ID 6 + goto PrintFloat ;Message ID 14 + goto PrintBytes ;Message ID 15 + goto PrintFloat ;Message ID 16 + goto PrintFloat ;Message ID 17 + goto PrintFloat ;Message ID 18 + goto PrintFloat ;Message ID 24 + goto PrintFloat ;Message ID 25 + goto PrintFloat ;Message ID 26 + goto PrintSigned ;Message ID 27 + goto PrintFloat ;Message ID 28 + goto PrintBytes ;Message ID 48 + goto PrintBytes ;Message ID 49 + goto PrintFloat ;Message ID 56 + goto PrintFloat ;Message ID 57 + goto PrintShort ;Message ID 116 + goto PrintShort ;Message ID 117 + goto PrintShort ;Message ID 118 + goto PrintShort ;Message ID 119 + goto PrintShort ;Message ID 120 + goto PrintShort ;Message ID 121 + goto PrintShort ;Message ID 122 + goto PrintShort ;Message ID 123 + +PrintStatus movfw float1 + call PrintBinary + movlw '/' + call PrintChar + movfw float2 +PrintBinary movwf temp + movlw 8 + movwf loopcounter +PrintBinaryLoop rlf temp,F + call PrintBoolean + decfsz loopcounter,F + goto PrintBinaryLoop + return + + errorlevel -219 ;Ignore warning about invalid RAM addr +PrintBoolean rlf 7,W ;Trick: Address 7 reads as all 0's + errorlevel +219 ;Turn warning back on + goto PrintDigit + +PrintSigned btfss float1,7 ;Check if value is negative + goto PrintFloat + comf float1,F ;Negate the value + comf float2,F + incf float2,F + skpnz + incf float1,F ;-- + movlw '-' + call PrintChar ;Print a minus sign + ;Fall through to print the absolute value +;If the low byte of the floating point value is 0xff, it would translate to a +;fraction of .996, which should be rounded up to the next full number. This +;needs to be checked before the integer part of the value can be printed. +PrintFloat incfsz float2,W ;Check if the 2nd byte is 0xff + goto PrintFloatJ1 + incf float1,F ;Round up to the next integral number + clrf float2 ;-- +PrintFloatJ1 movfw float1 + call PrintByte + movlw 100 + movwf temp + movfw float2 + call Multiply + btfsc float2,7 + incf float1,F ;Rounding + movfw float1 + movwf temp + movlw '.' + goto PrintFraction + +PrintBytes movfw float1 + call PrintByte + movlw '/' + call PrintChar + movfw float2 +PrintByte bcf LeadingZero + movwf temp + addlw -100 + skpc + goto PrintByteJ1 + movwf temp + addlw -100 + skpc + goto Print100 + movwf temp + movlw '2' + goto PrintFraction +Print100 movlw '1' +PrintFraction call PrintChar +PrintDecimal bsf LeadingZero +PrintByteJ1 clrf loopcounter + movfw temp +PrintByteL1 movwf temp ;BCD + addlw -10 + skpc + goto PrintByteJ2 + incf loopcounter,F + goto PrintByteL1 +PrintByteJ2 movfw loopcounter + skpnz + btfsc LeadingZero + call PrintLDigit + movfw temp +PrintLDigit bsf LeadingZero + goto PrintDigit + +PrintShort movlw 16 + movwf loopcounter + clrf tempvar0 + clrf tempvar1 + clrf tempvar2 + goto PrintShortJump +PrintShortLoop movfw tempvar2 + call AdjustBCD + movwf tempvar2 + movfw tempvar1 + call AdjustBCD + movwf tempvar1 +PrintShortJump rlf float2,F + rlf float1,F + rlf tempvar2,F + rlf tempvar1,F + rlf tempvar0,F + decfsz loopcounter,F + goto PrintShortLoop + bcf LeadingZero + movfw tempvar0 + call PrintBCD + movfw tempvar1 + call PrintBCD + movfw tempvar2 + call PrintBCD + btfsc LeadingZero + return + goto PrintDigit ;W is always 0 here +AdjustBCD addlw 0x33 + movwf temp + btfss temp,3 + addlw -0x03 + btfss temp,7 + addlw -0x30 + return +PrintBCD movwf temp + swapf temp,W + call PrintBCDDigit + movfw temp +PrintBCDDigit andlw b'1111' + skpnz + btfsc LeadingZero + goto PrintDigit + return + +PrintSetting movfw rxpointer ;Check that the command is 4 chars long + sublw 4 + skpz + retlw SyntaxError + movlw high PrintTable + movwf PCLATH + movfw rxbuffer + 3 + sublw 'Z' + sublw 'Z' - 'A' + skpc + retlw BadValue + addlw low PrintTable + skpnc + incf PCLATH,F + movwf PCL +PrintTable retlw PrintSettingA ;PR=A + retlw PrintSettingB ;PR=B + retlw PrintSettingC ;PR=C + retlw BadValue ;PR=D + retlw BadValue ;PR=E + retlw BadValue ;PR=F + goto PrintSettingG ;PR=G + retlw BadValue ;PR=H + goto PrintSettingI ;PR=I + retlw BadValue ;PR=J + retlw BadValue ;PR=K + retlw PrintSettingL ;PR=L + goto PrintSettingM ;PR=M + retlw BadValue ;PR=N + goto PrintSettingO ;PR=O + goto PrintSettingP ;PR=P + goto PrintSettingQ ;PR=Q + goto PrintSettingR ;PR=R + goto PrintSettingS ;PR=S + goto PrintSettingT ;PR=T + retlw BadValue ;PR=U + goto PrintSettingV ;PR=V + goto PrintSettingW ;PR=W + retlw BadValue ;PR=X + retlw BadValue ;PR=Y + retlw BadValue ;PR=Z + +PrintSettingID movfw rxbuffer + 3 + call PrintChar + movlw '=' + goto PrintChar + +PrintSettingV call PrintSettingID +PrintRefVoltage movfw settings + addlw -3 + goto PrintXChar + +PrintSettingO call PrintSettingID +PrintOverride tstf override ;Any override flags set? + movlw 'N' + skpnz + goto PrintChar ;No override + movfw setpoint1 ;Override setpoint set? + xorwf setpoint2,W + movlw 'n' + skpnz + goto PrintChar ;Waiting to clear the override +PrintOvrSetPt swapf RemOverrideFunc,W ;Get the current bits in lower nibble + xorwf RemOverrideFunc,W ;Determine the changed bits + andlw b'1111' ;Only consider the lower nibble + btfss OverrideFunc ;Lower nibble contains pending request + xorwf RemOverrideFunc,F ;Harmless to overwrite lower nibble + movlw 'T' + btfss ProgChangePrio ;Either pending or current state + movlw 'C' + btfss OverrideAct ;Override accepted by the thermostat? + iorlw 0x20 ;Switch to lowercase + call PrintChar ;Print the override type + movfw setpoint1 ;Copy the setpoint + movwf float1 + movfw setpoint2 + movwf float2 ;-- + goto PrintFloat ;Print as a floating point value + +PrintSettingM call PrintSettingID +PrintGateway movlw 'G' + btfsc MonitorMode + movlw 'M' + goto PrintChar + +PrintSettingQ call PrintSettingID + swapf mode,W + andlw b'111' + addlw ResetReasonStr + pcall ReadEpromData + goto PrintChar + +PrintSettingW call PrintSettingID +PrintHotWater movlw '0' + btfsc HotWaterEnable + movlw '1' + btfss HotWaterSwitch + movlw 'A' + goto PrintChar + +PrintSettingP call PrintSettingID +PrintPowerlevel btfss RaisedPower + retlw NormalPowerStr + movlw MediumPowerStr + btfsc HighPower + movlw HighPowerStr + call PrintString + retlw PowerStr + +PrintSettingR call PrintSettingID +PrintRemeha movlw 'D' + btfsc TStatRemeha + movlw 'R' + btfsc TStatCelcia + movlw 'C' + btfsc TStatISense + movlw 'I' + goto PrintChar + +PrintSettingT call PrintSettingID +PrintTweaks swapf settings,W + movwf temp + rrf temp,F + rrf temp,F + call PrintBoolean + rrf temp,F + goto PrintBoolean + +PrintSettingI call PrintSettingID + rlf PORTA,W + addlw b'10000000' + call PrintBoolean + rlf PORTA,W + goto PrintBoolean + +PrintSettingG call PrintSettingID +PrintGPIO swapf GPIOFunction,W + goto PrintHex + +PrintSettingS call PrintSettingID +PrintSetback movlw AwaySetpoint1 + pcall ReadEpromData + movwf float1 + movlw AwaySetpoint2 + pcall ReadEpromData + movwf float2 + goto PrintFloat + +PrintSummary movfw rxpointer + sublw 4 + skpz + retlw SyntaxError + movfw rxbuffer + 3 + sublw '1' + sublw '1' - '0' + skpc + retlw BadValue + skpnz + goto PrintSummaryOff + clrf valueindex + bsf SummaryRequest + bsf HideReports + goto PrintSummaryOn +PrintSummaryOff bcf SummaryRequest + bcf HideReports +PrintSummaryOn lgoto PrintDigit + +;Multiply W by temp - result in float1:float2 +Multiply clrf float1 + clrf float2 + xorlw 8 + movwf loopcounter + xorlw 8 + xorwf loopcounter,F +MultiplyLoop clrc + rlf float2,F + rlf float1,F + rlf temp,F + skpnc + addwf float2,F + skpnc + incf float1,F + decfsz loopcounter,F + goto MultiplyLoop + return + +;************************************************************************ +;Process an incoming OpenTherm message +;************************************************************************ + package Message +HandleMessage btfsc MsgResponse ;Treat requests and responses differently + goto HandleResponse + +HandleRequest btfsc byte1,4 ;For Write-Data messages, ... + call StoreValue ;... store the value from the thermostat + movfw byte1 + movwf originaltype ;Save the original message type + movfw byte2 + xorwf originalreq,W ;Compare against previous message + skpz + clrf repeatcount ;Message is different -> reset counter + xorwf originalreq,F ;Save the original message ID + movfw byte3 + movwf databyte1 ;Save original data byte #1 + movfw byte4 + movwf databyte2 ;Save original data byte #2 + bcf AlternativeUsed + bsf OverrideUsed ;Assume only data values will be changed + call TreatMessage ;Process the request from the thermostat + clrf boilercom ;Going to send a message to the boiler + call UnknownMask ;Check if ID is unsupported by boiler + skpnz ;Data ID is not known to be unsupported + goto SendAltRequest + btfss AlternativeUsed ;Was the request modified in some way? + return ;Send message unmodified to the boiler + goto SetParity ;Calculate the parity of the new message +SendAltRequest bsf AlternativeUsed ;Probably going to send a different msg + bcf OverrideUsed ;Changes will be more drastic + call Alternative ;Get the alternative message to send + btfss AlternativeUsed + return ;There were no other candidates + movwf byte2 ;Fill in the alternative Message ID + goto SetParity ;Calculate the parity of the new message + +;Vendor specific ID's can go through the code below without causing any trouble +;because the mask returned by UnknownMask will be 0. +HandleResponse bsf BoilerAlive ;Received a message from the boiler + movfw byte2 + xorwf prioritymsgid,W ;Response matches ID of priority msg? + skpnz + bcf PriorityMsg ;Priority message answered + btfss byte1,4 ;For Read-Ack messages, ... + call StoreValue ;... store the value from the boiler + call UnknownMask ;Check boiler support for the DataID + btfsc byte1,5 + btfss byte1,4 + goto MsgSupported ;No Unknown-DataID response + skpnz + goto MsgUnsupported ;Already found 3 consecutive failures + andlw b'01010101' ;Get lower bit of the mask + addwf INDF,F ;Increase failure count + goto HandleRespJump +MsgUnsupported movfw byte2 ;Get the message ID + pcall DeleteAlt ;Remove from the list of alternatives + goto HandleRespJump +MsgSupported xorlw -1 ;Invert the mask + btfss NoResetUnknown ;Do nothing for administrative requests + andwf INDF,F ;Reset the failure count +HandleRespJump bcf NoResetUnknown + bsf BoilerResponse + btfsc Unsolicited + goto RemoteCommand + btfsc AlternativeUsed + goto SendAltResponse + call TreatMessage ;Process the received response + btfsc AlternativeUsed ;Was the response modified in some way? + btfsc NoThermostat ;And a thermostat is connected? + return + goto SetParity +SendAltResponse call TreatMessage ;Process the received response + movlw b'01000000' ;Response bit + btfss OverrideUsed + movlw b'01110000' ;Boiler does not support the message + call CreateMessage ;Turn the request into a response + bcf BoilerResponse + btfss OverrideUsed ;Does the replacement need processing? + call TreatMessage ;Process the message to return +SetParity movfw byte4 ;Get the last byte + xorwf byte3,W ;Calculate the sum of the last 2 bytes + xorwf byte2,W ;Calculate the sum of the last 3 bytes + bcf MsgParity ;Make sure the parity bit is cleared + xorwf byte1,W ;Calculate the sum of all four bytes + movwf temp ;Save in temporary storage space + swapf temp,F ;Swap the nibbles of the checksum + xorwf temp,W ;Calculate the sum of the two nibbles + movwf temp ;Save the result again + rrf temp,F ;Shift the checksum 2 bits + rrf temp,F + xorwf temp,F ;Combine two sets of two bits + movlw 1 ;Setup a mask to be used later + btfsc temp,1 ;Check if bit 1 is set + xorwf temp,F ;If so, invert bit 0 + btfsc temp,0 ;Check the calculated parity + bsf MsgParity ;Fill in the parity bit for even parity + movlw 'R' + btfsc MsgResponse + movlw 'A' + lgoto PrintMessage + +RemoteCommand call TreatMessage ;Process the received message + movlw B_WACK + movwf byte1 + movlw MSG_REMOTECMD + movwf byte2 + movlw RCMD_TELEFOON + movwf byte3 + movfw celciasetpoint + movwf byte4 + bcf Unsolicited + goto SetParity + +;Define special treatment based on the message ID. This code will be called in +;three situations: +;1. A request was received from the thermostat. +;2. A response was received from the boiler. +;3. An alternative message has been sent to the boiler and a fake response has +; been created to be sent back to the thermostat. +;Which of the three situations applies can be determined by checking the +;MsgResponse and BoilerResponse flags. The flags will have the following states: +;1. MsgResponse = 0, BoilerResponse = X +;2. MsgResponse = 1, BoilerResponse = 1 +;3. MsgResponse = 1, BoilerResponse = 0 +;If the code modifies the message, it must set the AlternativeUsed flag. This +;will cause the parity to be recalculated and the modified message is reported +;on the serial interface. + +TreatMessage btfsc byte2,7 + goto FakeResponse ;Limited support for Vender specific ID + movlw high messagetable + movwf PCLATH + movfw byte2 + addlw low messagetable + skpnc + incf PCLATH,F + movwf PCL +messagetable goto MessageID0 ;Data ID 0 + goto MessageID1 ;Data ID 1 + goto MessageID2 ;Data ID 2 + goto MandatoryWord ;Data ID 3 + goto MessageID4 ;Data ID 4 + goto MessageID5 ;Data ID 5 + goto MessageID6 ;Data ID 6 + goto FakeResponse ;Data ID 7 + goto FakeResponse ;Data ID 8 + goto MessageID9 ;Data ID 9 + goto TSPBufferSize ;Data ID 10 + goto TSPReadEntry ;Data ID 11 + goto TSPBufferSize ;Data ID 12 + goto TSPReadEntry ;Data ID 13 + goto MessageID14 ;Data ID 14 + goto WordResponse ;Data ID 15 + goto MessageID16 ;Data ID 16 + goto MandatoryWord ;Data ID 17 + goto WordResponse ;Data ID 18 + goto WordResponse ;Data ID 19 + goto MessageID20 ;Data ID 20 + goto WordResponse ;Data ID 21 + goto WordResponse ;Data ID 22 + goto FakeResponse ;Data ID 23 + goto WordResponse ;Data ID 24 + goto MandatoryWord ;Data ID 25 + goto WordResponse ;Data ID 26 + goto MessageID27 ;Data ID 27 + goto WordResponse ;Data ID 28 + goto WordResponse ;Data ID 29 + goto WordResponse ;Data ID 30 + goto WordResponse ;Data ID 31 + goto WordResponse ;Data ID 32 + goto WordResponse ;Data ID 33 + goto WordResponse ;Data ID 34 + goto WordResponse ;Data ID 35 + goto FakeResponse ;Data ID 36 + goto FakeResponse ;Data ID 37 + goto FakeResponse ;Data ID 38 + goto FakeResponse ;Data ID 39 + goto FakeResponse ;Data ID 40 + goto FakeResponse ;Data ID 41 + goto FakeResponse ;Data ID 42 + goto FakeResponse ;Data ID 43 + goto FakeResponse ;Data ID 44 + goto FakeResponse ;Data ID 45 + goto FakeResponse ;Data ID 46 + goto FakeResponse ;Data ID 47 + goto MessageID48 ;Data ID 48 + goto MessageID49 ;Data ID 49 + goto WordResponse ;Data ID 50 + goto WordResponse ;Data ID 51 + goto WordResponse ;Data ID 52 + goto WordResponse ;Data ID 53 + goto WordResponse ;Data ID 54 + goto WordResponse ;Data ID 55 + goto MessageID56 ;Data ID 56 + goto MessageID57 ;Data ID 57 + goto WordResponse ;Data ID 58 + goto WordResponse ;Data ID 59 + goto WordResponse ;Data ID 60 + goto WordResponse ;Data ID 61 + goto WordResponse ;Data ID 62 + goto WordResponse ;Data ID 63 + goto FakeResponse ;Data ID 64 + goto FakeResponse ;Data ID 65 + goto FakeResponse ;Data ID 66 + goto FakeResponse ;Data ID 67 + goto FakeResponse ;Data ID 68 + goto FakeResponse ;Data ID 69 + goto ByteResponse ;Data ID 70 + goto MessageID71 ;Data ID 71 + goto WordResponse ;Data ID 72 + goto WordResponse ;Data ID 73 + goto WordResponse ;Data ID 74 + goto WordResponse ;Data ID 75 + goto WordResponse ;Data ID 76 + goto WordResponse ;Data ID 77 + goto WordResponse ;Data ID 78 + goto WordResponse ;Data ID 79 + goto WordResponse ;Data ID 80 + goto WordResponse ;Data ID 81 + goto WordResponse ;Data ID 82 + goto WordResponse ;Data ID 83 + goto WordResponse ;Data ID 84 + goto WordResponse ;Data ID 85 + goto WordResponse ;Data ID 86 + goto WordResponse ;Data ID 87 + goto TSPBufferSize ;Data ID 88 + goto TSPReadEntry ;Data ID 89 + goto TSPBufferSize ;Data ID 90 + goto TSPReadEntry ;Data ID 91 + goto FakeResponse ;Data ID 92 + goto FakeResponse ;Data ID 93 + goto FakeResponse ;Data ID 94 + goto FakeResponse ;Data ID 95 + goto FakeResponse ;Data ID 96 + goto FakeResponse ;Data ID 97 + goto FakeResponse ;Data ID 98 + goto FakeResponse ;Data ID 99 + goto MessageID100 ;Data ID 100 + goto ByteResponse ;Data ID 101 + goto WordResponse ;Data ID 102 + goto WordResponse ;Data ID 103 + goto WordResponse ;Data ID 104 + goto TSPBufferSize ;Data ID 105 + goto TSPReadEntry ;Data ID 106 + goto TSPBufferSize ;Data ID 107 + goto TSPReadEntry ;Data ID 108 + goto FakeResponse ;Data ID 109 + goto FakeResponse ;Data ID 110 + goto FakeResponse ;Data ID 111 + goto FakeResponse ;Data ID 112 + goto WordResponse ;Data ID 113 + goto WordResponse ;Data ID 114 + goto WordResponse ;Data ID 115 + goto WordResponse ;Data ID 116 + goto WordResponse ;Data ID 117 + goto WordResponse ;Data ID 118 + goto WordResponse ;Data ID 119 + goto WordResponse ;Data ID 120 + goto WordResponse ;Data ID 121 + goto WordResponse ;Data ID 122 + goto WordResponse ;Data ID 123 + goto FakeResponse ;Data ID 124 + goto MessageID125 ;Data ID 125 + goto MessageID126 ;Data ID 126 + goto WordResponse ;Data ID 127 + +messageinv movlw B_INV + goto setbyte1 +messageack movfw originaltype ;Get original request type + iorlw b'11000000' ;Turn it into a matching acknowledgement + btfss byte1,7 + andlw b'01111111' ;Match parity bit of the received msg +setbyte1 xorwf byte1,W ;Check if the byte is different + skpz + bsf AlternativeUsed ;Going to modify the message + xorwf byte1,F ;Set the byte + retlw 0 + +setbyte3 xorwf byte3,W ;Check if the byte is different + skpz + bsf AlternativeUsed ;Going to modify the message + xorwf byte3,F ;Set the byte + retlw 0 + +datarestore movfw databyte1 + call setbyte3 + movfw databyte2 +setbyte4 xorwf byte4,W ;Check if the byte is different + skpz + bsf AlternativeUsed ;Going to modify the message + xorwf byte4,F ;Set the byte + retlw 0 + +;DataID 0: Status +;The gateway may want to manipulate the status message generated by the +;thermostat for two reasons: The domestic hot water enable option specified +;via a serial command may differ from what the thermostat sent. And if a +;control setpoint has been specified via a serial command, central heating +;mode must be enabled for the setpoint to have any effect. +;When a status response comes back from the boiler, several LED states are +;updated. Also if the master status byte was changed before sending it to the +;boiler, the original master status must be restored before the message is +;returned to the thermostat, so it doesn't get confused +MessageID0 btfsc MsgResponse ;Status request or response? + goto statusreturn +readstatus call MandatoryID ;Remove any blacklisting for the DataID + movfw byte3 + tstf controlsetpt1 + skpnz + goto normalstatus + iorlw b'1' ;Enable central heating mode + btfsc CHModeOff + andlw ~b'1' ;Disable central heating mode +normalstatus btfss HotWaterSwitch + goto setbyte3 ;Don't manipulate hot water enable + andlw ~b'10' ;Domestic hot water enable off + btfsc HotWaterEnable + iorlw b'10' ;Domestic hot water enable on + goto setbyte3 + +statusreturn btfsc byte1,5 + return ;Not a valid response from the boiler + movfw byte4 + movwf databyte2 ;Copy slave status to response message + andlw b'01000001' ;Check fault bit and diagnostic bit + skpnz + bcf BoilerFault ;Clear any previous boiler faults + skpz + btfsc BoilerFault ;New boiler fault? + goto statusmaint + bsf BoilerFault ;Remember a fault was reported + movlw MSG_FAULTCODE ;ASF-flags/OEM-fault-code message + movwf prioritymsgid ;Schedule request for more information + bsf PriorityMsg +statusmaint movlw 'M' + call SwitchLED ;Update the maintenance LED + movfw byte4 + andlw b'00001000' ;Check the flame status bit + movlw 'F' + call SwitchLED ;Update the flame LED + movfw byte4 + andlw b'00000100' ;Check the DHW mode bit + movlw 'W' + call SwitchLED ;Update the hot water LED + movfw byte4 + andlw b'00000010' ;Check the CH mode bit + movlw 'H' + call SwitchLED ;Update the central heating LED + movfw byte3 + andlw b'00000010' ;Check the DHW enable bit + movlw 'C' + goto SwitchLED ;Update the comfort mode LED + +;DataID 1: Control Setpoint +;If a control setpoint has been specified via a serial command, the specified +;value is put into the message before sending it to the boiler. +;If the boiler indicates that the control setpoint is invalid, the value +;specified by the user is cleared. +MessageID1 btfsc MsgResponse ;Request or response? + goto ctrlsetptreturn + call MandatoryID ;Prevent the DataID getting blacklisted + movfw controlsetpt1 ;Check if a setpoint has been specified + xorwf controlsetpt2,W + skpz + btfss byte1,4 ;WriteData request? + return ;Continue with normal processing + movfw controlsetpt1 ;Fill in the specified setpoint + call setbyte3 + movfw controlsetpt2 + goto setbyte4 + +ctrlsetptreturn btfss byte1,5 + return ;Setpoint acknowledged by boiler + btfsc byte1,4 + return + tstf controlsetpt1 + skpnz + return + clrf controlsetpt1 ;Zap the invalid value + clrf controlsetpt2 + return + +;DataID 2: Master Configuration +;The gateway will provide Smart Power support, even if the boiler doesn't. +MessageID2 btfss MsgResponse + goto BrandDetect ;Detect thermostat manufacturer + movlw b'1' ;Don't know what the other bits are + andwf databyte1,W ;... so don't acknowledge those. + call setbyte3 ;Acknowledge Smart Power support + movfw databyte2 ;Make sure the master MemberID in ... + call setbyte4 ;... the response matches the request + goto messageack ;Turn request into acknowledgement +BrandDetect btfsc byte3,0 ;Smart power bit + bsf SmartPower ;Thermostat supports Smart Power + btfsc TStatManual + return ;Don't auto-detect the thermostat model + movfw byte4 ;MemberID code of the master + sublw 11 ;Remeha + skpnz + btfsc TStatRemeha + return + bsf TStatRemeha ;The master is some Remeha thermostat + btfsc SmartPower ;No Smart Power Support? + bsf TStatISense ;Probably an iSense (or qSense) + ;Without Smart Power support, the thermostat can be a Celcia20 + ;or a c-Mix. The Celcia20 periodically sends a Master Product + ;Version message (MsgID 126). So assume the thermostat is not + ;a Celcia20 until it informs us otherwise. + return + +;DataID 4: Remote Request +;The remote request message with a request code of 170 is used by the Celcia +;20 to specify a remote setpoint override. +MessageID4 btfss MsgResponse + return ;Don't modify requests + movlw RCMD_TELEFOON ;Check for the celcia override request + subwf byte3,W + skpz + return ;Todo: Turn UnkDataID into DataInvalid? + call messageack ;Turn request into acknowledgement + movfw celciasetpoint + goto setbyte4 + +;DataID 5: Application-specific flags +;The Remeha iSense RF doesn't always report a room setpoint, but it does ask for +;the Application-specific flags quite regularly. If such a message is received +;after the remote setpoint clear command has been sent, we're going to assume +;the setpoint clear has been accepted. +MessageID5 btfsc MsgResponse ;Only interested in requests + goto WordResponse ;Default handling for responses + btfsc OverrideClr ;No pending clear ... + btfsc OverrideWait ;... or clear command has not been sent? + return ;Do nothing + goto roomsetptclear ;Consider setpoint clear accepted + +;DataID 6: Remote Boiler Parameter flags +;If the boiler doesn't support the DHW Setpoint or Max CH Setpoint parameters, +;the gateway will simulate read access. +MessageID6 btfss MsgResponse + return + bcf InitParameter + clrw ;Assume no support for remote-parameters + btfss byte1,5 + btfsc byte1,4 + goto parameterupdate + movfw byte4 ;Get the remote-parameter r/w flags +parameterupdate xorwf onoffflags,W + andlw b'11' + xorwf onoffflags,F + call messageack ;Turn request into acknowledgement + btfss dhwupdate + bcf InitHotWater ;Boiler doesn't allow DHW setpt update + btfss maxchupdate + bcf InitHeating ;Boiler doesn't allow MaxCH setpt update + movfw byte3 + iorlw b'11' ;The gateway will simulate read access + goto setbyte3 + +;If a remote override setpoint has been specified, return it to the thermostat +MessageID9 btfss MsgResponse + return + call messageack ;Turn request into acknowledgement + btfss OverrideClr + movfw setpoint1 + call setbyte3 ;Data byte 1 has integer part + btfss OverrideClr + movfw setpoint2 + call setbyte4 ;Data byte 2 has the fraction + bcf OverrideWait ;Override has been requested + btfsc TStatCelcia ;Remeha Celcia thermostat? + btfss OverrideReq ;And override requested? + return + btfss OverrideAct ;And override not yet active? + bsf Unsolicited ;Then resend the request on next msg + return + +;Maximum relative modulation level +MessageID14 btfsc MsgResponse ;Status request or response? + return + call MandatoryID ;Prevent the DataID getting blacklisted + btfss UserMaxModLevel + return + movfw MaxModLevel ;Get the user specified max modulation + call setbyte3 ;Set the max modulation level + goto setbyte4 ;Clear the second data byte + +;When the thermostat reports a room setpoint, compare it against the requested +;override setpoint. When it matches, the remote setpoint has been accepted. If +;at some later point it no longer matches, the thermostat has overriden the +;remote setpoint and it must be released. +MessageID16 btfsc MsgResponse + ;We don't want an überclever thermostat to stop sending these + ;messages, so make sure we always return an acknowledgement + goto messageack ;Turn request into acknowledgement + btfsc OverrideReq ;Check for pending override request + btfsc OverrideWait ;Must actually send the request first + return + btfsc OverrideClr + goto roomsetptclear + ;Another "nice" feature of the Remeha iSense thermostat: It + ;doesn't set the setpoint to the exact value requested. So we + ;have to allow for some variation. The delta chosen is 0.125 + ;as that should be enough and is easy to do with bit operations + movfw setpoint2 ;Calcualte the difference between ... + subwf byte4,W ;... the received value and the ... + movwf temp ;... desired setpoint + movfw setpoint1 ;Get the units of the requested setpoint + skpc ;Check for borrow from fractional part + addlw 1 ;Adjust the integer part + subwf byte3,W ;Calculate the difference + skpc + xorlw -1 ;Make the result positive + skpz + goto roomsetptdiff ;Values differ more than 1 degree + skpc ;Carry is set if setpoint <= value + comf temp,F ;Invert the fraction + movlw b'11100000' ;Only the lower 5 bits may be set + andwf temp,W ;Reset the lower 5 bits + skpz ;Result should be 0 for a match + goto roomsetptdiff ;Values differ more than 0.125 degrees + ;Setpoint matches the request + btfsc OverrideAct + return ;Override remains in effect + bsf OverrideAct ;Override was picked up by thermostat + bcf OverrideBlink + movlw 'O' + goto SwitchOnLED ;Switch on the override LED +roomsetptclear bcf OverrideClr ;Override clear data has been sent + movfw setpoint1 + iorwf setpoint2,W ;Check if there is a new setpoint + skpnz + goto roomsetptreset + bsf OverrideWait ;The new setpoint must be sent next + return +roomsetptdiff btfss OverrideAct ;Override currently active? + incf override,F ;Make 3 attempts to set the override + btfss OverrideAct ;Time to abandon override? + return +roomsetptcancel btfsc TStatISense ;Special treatment for iSense thermostat + goto roomsetptreset + clrf setpoint1 ;Reset the setpoint variables + clrf setpoint2 +roomsetptreset clrf override ;Stop requesting a setpoint override + movlw 'O' + goto SwitchOffLED ;Switch off the override LED + +;Return the current date and time, if specified by the user +MessageID20 btfss MsgResponse + return ;No need to process a request + call messageack ;Turn request into acknowledgement + btfss ChangeTime ;Is a new clock value available? + goto datarestore ;Restore the old data bytes and return + movfw clock1 + call setbyte3 + movfw clock2 + goto setbyte4 + +;Return the outside temperature, if specified via the serial interface +MessageID27 btfsc MsgResponse ;Do not modify a request + btfss OutsideTemp ;Do nothing if no outside temp available + return + btfsc OutsideInvalid + goto messageinv + call messageack ;Turn request into acknowledgement + movfw outside1 + call setbyte3 + movfw outside2 + goto setbyte4 + +;Keep track of the boundaries for domestic hot water reported by the boiler +MessageID48 btfss MsgResponse + return + bcf InitHotWater ;Received Hot Water Boundaries response + btfsc byte1,5 ;DataInvalid Or UnknownDataID? + goto WordResponse ;Ignore bad responses + movfw byte3 + movwf dhwsetpointmax ;Remember upper boundary + movfw byte4 + movwf dhwsetpointmin ;Remember lower boundary + goto WordResponse + +;Keep track of the boundaries for central heating reported by the boiler +MessageID49 btfss MsgResponse + return + bcf InitHeating ;Received Heating Boundaries response + btfsc byte1,5 ;DataInvalid Or UnknownDataID? + goto WordResponse ;Ignore bad responses + movfw byte3 + movwf chsetpointmax ;Remember upper boundary + movfw byte4 + movwf chsetpointmin ;Remember lower boundary + goto WordResponse + +;Pass on user defined boundaries for domestic hot water to the boiler +MessageID56 btfsc MsgResponse + goto hotwaterreturn + tstf dhwsetpoint1 ;Check if a setpoint has been specified + btfss byte1,4 ;Change on WriteData + btfsc WaterSetpoint ;Or new setpoint + skpnz ;And a setpoint exists + return ;Continue with normal processing +SetWaterSet movlw T_WRITE + call setbyte1 ;Turn into a write request, if necessary + movfw dhwsetpoint1 + call setbyte3 ;Load the data value high byte + movfw dhwsetpoint2 + call setbyte4 ;Load the data value low byte + retlw MSG_DHWSETPOINT ;Return MsgID when used as alternative +hotwaterreturn bcf WaterSetpoint + btfss byte1,5 + return + btfss byte1,4 + goto InvalidHotWater ;Boiler didn't respond with DataInvalid + ;Boiler does not support the message + call messageack ;Turn request into acknowledgement + movfw dhwsetpoint1 + skpnz + movfw dhwsetpointmax + call setbyte3 + movfw dhwsetpoint2 + goto setbyte4 +InvalidHotWater tstf dhwsetpoint1 + skpnz + return + clrf dhwsetpoint1 ;Zap the invalid value + clrf dhwsetpoint2 + return + +;Pass on user defined boundaries for central heating to the boiler +MessageID57 btfsc MsgResponse + goto maxchsetptret + tstf maxchsetpoint1 ;Check if a setpoint has been specified + btfss byte1,4 ;Change on WriteData + btfsc MaxHeatSetpoint ;Or new setpoint + skpnz ;And a setpoint exists + return ;Continue with normal processing +SetHeatingSet movlw T_WRITE + call setbyte1 ;Turn into a write request, if necessary + movfw maxchsetpoint1 + call setbyte3 ;Load the data value high byte + movfw maxchsetpoint2 + call setbyte4 ;Load the data value low byte + retlw MSG_MAXCHSETPOINT ;Return MsgID when used as alternative +maxchsetptret bcf MaxHeatSetpoint + btfss byte1,5 + return ;Boiler was happy with the message + btfss byte1,4 + goto InvalidHeating + ;Boiler does not support the message + call messageack ;Turn request into acknowledgement + movfw maxchsetpoint1 + skpnz + movfw chsetpointmax + call setbyte3 + movfw maxchsetpoint2 + goto setbyte4 +InvalidHeating tstf maxchsetpoint1 + skpnz + return + clrf maxchsetpoint1 ;Zap the invalid value + clrf maxchsetpoint2 + return + +; Control setpoint ventilation / heat-recovery +MessageID71 clrc ;Prepare for shift operation + rrf ventsetpoint,W ;Get the user defined setpoint + skpnc ;There is no user defined setpoint + btfsc MsgResponse ;Nothing to do for a response + return ;Leave message unmodified + call setbyte4 ;Put the setpoint in the low byte + goto setbyte3 ;Clear the high byte + +;Indicate under which conditions the remote override setpoint can be overridden +;by the thermostat. The opentherm specs are quite confusing regarding which byte +;the flags must be in. So the gateway has an option to put them in both bytes. +MessageID100 btfss MsgResponse + return + btfsc OverrideFunc ;There is a new set of function bits + swapf RemOverrideFunc,F ;Start using the new function bits + bcf AlternativeUsed ;Allow user to override the response + call WordResponse + btfsc AlternativeUsed + goto UserOverride ;--- + call messageack ;Turn request into acknowledgement + swapf RemOverrideFunc,W + andlw b'11' ;Get the remote override flags + call setbyte4 + btfsc OverrideHigh + movfw byte4 ;Fix for confused manufacturers + call setbyte3 ;Reserved, must be 0? +UserOverride btfsc TStatISense ;Special treatment for iSense thermostat + btfss OverrideFunc ;First time after setpoint change? + goto OverrideFuncEnd + swapf RemOverrideFunc,W + xorwf RemOverrideFunc,W ;Compare the upper and lower nibbles + skpz + btfsc OverrideWait ;Func before setpoint + goto OverrideFuncEnd ;Previously sent fucntion bits are OK + bsf OverrideClr ;Clear the remote override setpoint + bsf OverrideReq ;Then set it again + bsf OverrideWait +OverrideFuncEnd bcf OverrideFunc ;Override func has been sent + return + +;The gateway implements version 3.0 of the opentherm protocol specification +MessageID125 btfss MsgResponse + return + call messageack ;Turn request into acknowledgement + movlw 3 + call setbyte3 ;Major version number + goto setbyte4 ;Minor version number + +;Check if work-arounds are needed for bugs in certain thermostats +MessageID126 btfss MsgResponse ;Only interested in requests + btfss TStatRemeha ;Only Remeha thermostats have known bugs + return + ;c-Mix: 17/17 + ;Celcia20: 20/3 + ;iSense: 30/19, 32/23 + bcf TStatISense ;Clear guess based on ID2 information + movfw byte3 ;Master device product version number + sublw 20 ;Celcia 20 + skpnz + bsf TStatCelcia + skpc + bsf TStatISense ;At least 30 and 32 are iSenses + return + +;Messages to read transparent slave parameters (TSPs) and fault history buffer +;(FHB) entries. +TSPBufferSize btfss MsgResponse ;Only interested in responses + return + btfsc AlternativeUsed ;Info was not requested by the gateway + btfss BoilerResponse ;Response from the boiler + goto WordResponse ;Response going back to the thermostat + movfw byte3 ;Get the number of entries + btfss byte1,5 ;Check for valid response + skpnz + return ;No (valid) data available + movwf TSPCount ;Store the number of entries + incf byte2,W ;Reading entries is the next MsgID + movwf prioritymsgid ;Start reading entries + bsf PriorityMsg + return + +TSPReadEntry btfsc MsgResponse ;Only interested in responses ... + btfss AlternativeUsed ;... not requested by the thermostat + goto ByteResponse + incf TSPIndex,F ;Index of next entry + movfw TSPCount + subwf TSPIndex,W ;Check if more entries exist + skpc + bsf PriorityMsg ;Prepare to read next entry + return + +;Remove blacklisting for special DataIDs. Do this when receiving a request, so +;even (accidental) manual action cannot blacklist the DataID. +MandatoryID btfsc MsgResponse ;Only interested in requests + return + call UnknownMask ;Get mask and pointer into unknownmap + xorlw -1 ;Invert the mask + andwf INDF,F ;Unmark the ID as unsupported + return + +Alternative movlw T_READ ;Alternative is probably a read command + movwf byte1 + clrf byte3 ;And a data value of 0 + clrf byte4 + tstf initflags + skpnz + goto GotConfigData + ;Obtain some parameters from the boiler that are useful for the + ;internal operation of the gateway. + bsf NoResetUnknown ;Don't whitelist if command succeeds + btfsc InitParameter + retlw MSG_REMOTEPARAM + btfsc InitHotWater + retlw MSG_DHWBOUNDS + btfsc InitHeating + retlw MSG_MAXCHBOUNDS + btfsc WaterSetpoint + goto SetWaterSet + btfsc MaxHeatSetpoint + goto SetHeatingSet + btfss PriorityMsg + goto InitDone + movfw TSPIndex + movwf byte3 + movfw prioritymsgid + return +InitDone clrf initflags +GotConfigData movfw resetflags ;Check for pending reset requests + skpz + goto ResetCommand ;Send a reset command + btfsc NoAlternative + goto RestoreRequest ;No alternatives available, W = 0 + movlw 32 ;Size of the list of alternatives + movwf loopcounter ;Counter to prevent infinite loop +AltLoop incf alternativecmd,W + andlw b'11111' + movwf alternativecmd ;Next entry in the list of alternatives + addlw AlternativeCmd ;Add the starting address of the list + pcall EepromWait ;Wait for any pending EEPROM activity + bcf STATUS,RP0 + movwf EEADR ;Load the list pointer + bsf STATUS,RP0 + bsf EECON1,RD ;Prepare for reading EEPROM data + bcf STATUS,RP0 + movfw EEDATA ;Get the alternative message ID + bcf STATUS,RP1 ;Bank 0 + skpz ;Check if the slot is empty + return ;Found an entry + decfsz loopcounter,F + goto AltLoop ;Continue searching + bsf NoAlternative ;List of alternatives is empty +RestoreRequest bcf AlternativeUsed ;Not providing an alternative after all + ;At this point W = 0 +CreateMessage iorwf originaltype,W ;Combine W and the original message type + movwf byte1 ;Set the message type for the response + movfw databyte1 + movwf byte3 ;Restore data byte #1 + movfw databyte2 + movwf byte4 ;Restore data byte #2 + movfw originalreq + movwf byte2 ;Restore the message ID + return + +ResetCommand movlw T_WRITE + movwf byte1 ;Initialize a Write-Data message + movlw 116 ;MsgID of first counter + movwf loopcounter ;Initialize loopcounter + clrf temp ;Use for mask + setc ;Make sure to shift in a '1' + goto ResetCmdJump +ResetCmdLoop incf loopcounter,F +ResetCmdJump rlf temp,F ;Shift the mask one position + movfw temp ;Get the mask + andwf resetflags,W ;Check if the matching resetflag is set + skpnz + goto ResetCmdLoop ;Find the first set bit + xorwf resetflags,F ;Clear the resetflag + movfw loopcounter ;Get the Message ID + return + +UnknownMask movfw byte2 +UnknownMask2 movwf temp2 + rrf temp2,W + movwf temp + rrf temp,W + andlw b'00011111' ;Get bit 2-7 + addlw unknownmap + movwf FSR ;Pointer into the map of unknown ID's + btfsc temp2,7 ;Check for msg ID's in the range 0..127 + retlw 0 ;Don't blacklist vendor specific ID's + movlw b'11' + btfsc temp2,0 + movlw b'1100' + movwf temp + btfsc temp2,1 + swapf temp,F ;Calculate mask for the desired bit + comf INDF,W + andwf temp,W ;Check if ID is unsupported by boiler + swapf temp,F + swapf temp,W ;Get the mask without changing the Z bit + return ;Z = 1 if message is not supported + +;************************************************************************ +;Values to store +;Byte 0 : (00) 0 1 6 01000011 +;Byte 1 : (03) 14 15 11000000 +;Byte 2 : (05) 16 17 18 00000111 +;Byte 3 : (08) 24 25 26 27 28 00011111 +;Byte 4 : 00000000 +;Byte 5 : 00000000 +;Byte 6 : (0D) 48 49 00000011 +;Byte 7 : (0F) 56 57 00000011 +;Byte 8 : 00000000 +;Byte 9 : 00000000 +;Byte 10: 00000000 +;Byte 11: 00000000 +;Byte 12: 00000000 +;Byte 13: 00000000 +;Byte 14: (11) 116 117 118 119 11110000 +;Byte 15: (15) 120 121 122 123 00001111 +;Keep the three tables in sync! + +#define outsideval1 valuestorage + 22 +#define outsideval2 valuestorage + 23 + +StoreValue rlf byte2,W + skpc ;Ignore manufacturer specific ID's + btfsc byte1,5 ;Only store valid data + return + movwf temp + swapf temp,W + andlw b'00001111' ;Calculate table entry + movwf temp + movfw byte2 + andlw b'00000111' + movwf loopcounter + incf loopcounter,F + call LookupStoreFlag + xorwf temp,W ;Exchange W and temp + xorwf temp,F + xorwf temp,W ;-- + clrf tempvar0 + clrc ;Carry may be set in LookupStoreFlag +StoreValueLoop skpnc + incf tempvar0,F + rrf temp,F + decfsz loopcounter,F + goto StoreValueLoop + skpc + return ;Not storing this ID + call LookupStoreOffs + addwf tempvar0,F + rlf tempvar0,W + addlw valuestorage + movwf FSR + bsf STATUS,IRP + movfw byte3 + movwf INDF + incf FSR,F + movfw byte4 + movwf INDF + bcf STATUS,IRP + return + +LookupStoreFlag movlw high StoreFlagTable + movwf PCLATH + movfw temp + addlw low StoreFlagTable + skpnc + incf PCLATH,F + movwf PCL +StoreFlagTable dt b'01000011' + dt b'11000000' + dt b'00000111' + dt b'00011111' + dt b'00000000' + dt b'00000000' + dt b'00000011' + dt b'00000011' + dt b'00000000' + dt b'00000000' + dt b'00000000' + dt b'00000000' + dt b'00000000' + dt b'00000000' + dt b'11110000' + dt b'00001111' + +LookupStoreOffs movwf temp + movlw high StoreOffsTable + movwf PCLATH + movfw temp + addlw low StoreOffsTable + skpnc + incf PCLATH,F + movwf PCL +StoreOffsTable dt 0 + dt 3 + dt 5 + dt 8 + dt 13 + dt 13 + dt 13 + dt 15 + dt 17 + dt 17 + dt 17 + dt 17 + dt 17 + dt 17 + dt 17 + dt 21 + constant SUMMARYFIELDS=25 ;Maximum: 48 + +;The following code should only run for responses going back to the thermostat. +;A response to an alternative message coming back from the boiler (which will +;not be sent to the thermostat) can be recognized by both BoilerResponse and +;AlternativeUsed being set. +;The clever combination of bit tests in the following code will end up at a +;return command for requests or alternative responses. +FakeResponse btfsc BoilerResponse + btfss AlternativeUsed + btfss MsgResponse + return ;Not a message towards the thermostat + goto ClearNoneResp +ByteResponse btfsc BoilerResponse + btfss AlternativeUsed + btfss MsgResponse + return ;Not a message towards the thermostat + goto ClearByteResp +MandatoryWord call MandatoryID ;Prevent the DataID getting blacklisted +WordResponse btfsc BoilerResponse + btfss AlternativeUsed + btfss MsgResponse + return ;Not a message towards the thermostat +ClearWordResp clrf databyte1 +ClearByteResp clrf databyte2 +ClearNoneResp movfw byte2 ;Get the DataID + pcall FindResponse + skpz + return ;No entry found for the DataID + call messageack + bsf STATUS,IRP ;Access memory bank 2&3 + movfw INDF ;Get databyte 2 + iorwf databyte2,W + call setbyte4 + bcf FSR,7 + movfw INDF ;Get databyte 1 + bcf STATUS,IRP ;Access memory bank 0&1 + iorwf databyte1,W + goto setbyte3 + +;************************************************************************ +; Switch LEDs on and off depending on their configured function +;************************************************************************ + +SwitchLED skpnz ;Zero bit indicates LED state + goto SwitchOffLED + +SwitchOnLED addlw functions - 'A' ;Point into the table of functions + movwf FSR ;Setup indirect addressing + bsf INDF,0 ;Remember that this function is on + movlw b'11111110' ;Mask off the state bit + andwf INDF,W ;Get the LED(s) for the function + skpnz + return ;No LED is assigned to this function + xorlw -1 ;Invert the bitmask + andwf PORTB,F ;Switch on the LED + iorlw ~b'110' + andwf gpioflags,F ;Switch on virtual LEDs E/F + retlw 0 + +SwitchOffLED addlw functions - 'A' ;Point into the table of functions + movwf FSR ;Setup indirect addressing + bcf INDF,0 ;Remember that this function is off + movfw INDF ;Get the LED for the function + skpnz + return ;No LED is assigned to this function + iorwf PORTB,F ;Switch off the LED + andlw b'110' + iorwf gpioflags,F ;Switch off virtual LEDs E/F + retlw 0 + +;************************************************************************ +; Parse commands received on the serial interface +;************************************************************************ + + package Serial +SerialCommand btfsc Overrun + goto SerialOverrun +SerialCmdSub movlw 4 + subwf rxpointer,W ;Check the length of the command >= 4 + skpc + retlw SyntaxError ;Invalid command + movfw rxbuffer + 2 ;Third character of every command ... + sublw '=' ;... must be '=' + skpz + retlw SyntaxError + movlw high SerialCmdTable + movwf PCLATH + movfw rxbuffer ;Get the first character of the command + xorwf rxbuffer + 1,W ;Combine with the second character + movwf temp ;Save for later use + andlw ~b'11111' ;First quick check for a valid command + skpz + retlw CommandNG ;Command is not valid + ;Report the command back to the caller + movfw rxbuffer + lcall PrintChar + movfw rxbuffer + 1 + call PrintChar + movlw ':' + call PrintChar + movlw ' ' + call PrintChar + ;Indexed jump on the exclusive or of the 2 command characters + movlw high SerialCmdTable + movwf PCLATH + movfw temp + addlw low SerialCmdTable + skpnc + incf PCLATH,F + movwf PCL ;Make a quick initial selection +; Calculate table index with Tcl: tcl::mathop::^ {*}[scan SB %c%c] +SerialCmdTable goto SerialCmd00 ; AA, MM, TT commands + goto SerialCmd01 ; RS, SR commands + goto SerialCmd02 ; PR, KI commands + goto SerialCmd03 ; PS command + goto SerialCmd04 ; VR, SW commands + goto SerialCmd05 ; DA, GB, VS commands + goto SerialCmdGPIO ; GA command + goto SerialCmd07 ; OH command + goto SerialCmdLED ; LD command + goto SerialCmdLED ; LE command + goto SerialCmdLED ; LF command + goto SerialCmd0B ; CH command + retlw CommandNG +#ifndef LVP + goto SerialCmdLED ; LA command +#else + retlw CommandNG +#endif + goto SerialCmdLED ; LB command + goto SerialCmdLED ; LC command + goto SerialCmd10 ; GW, SC, CS commands + goto SerialCmd11 ; CR, SB commands + goto SerialCmd12 ; FT command + retlw CommandNG + goto SerialCmd14 ; DP command + retlw CommandNG + retlw CommandNG + goto SerialCmd17 ; TC command + retlw CommandNG + retlw CommandNG + retlw CommandNG + goto SerialCmd1B ; OT, SH commands + goto SerialCmd1C ; UI command + goto SerialCmd1D ; IT, PM commands + retlw CommandNG + goto SerialCmd1F ; HW command + +SerialOverrun bcf Overrun + retlw OverrunError + +SerialCmd00 movfw rxbuffer + xorlw 'T' + skpnz + goto SetTempSetPoint + xorlw 'T' ^ 'A' + skpnz + goto SetAlternative + xorlw 'A' ^ 'M' + skpnz + goto SetMaxModLevel + retlw CommandNG + +SerialCmd01 movfw rxbuffer + xorlw 'R' + skpnz + goto ResetCounter + xorlw 'R' ^ 'S' + skpnz + goto SetResponse + retlw CommandNG + +SerialCmd02 movfw rxbuffer + xorlw 'P' + skpnz + goto ReportSetting + xorlw 'P' ^ 'K' + skpnz + goto KnownDataID + retlw CommandNG + +SerialCmd03 movfw rxbuffer + xorlw 'P' + skpnz + goto ReportSummary + retlw CommandNG + +SerialCmd04 movfw rxbuffer + xorlw 'V' + skpnz + goto SetVoltageRef + xorlw 'V' ^ 'S' + skpnz + goto SetHotWaterTemp + retlw CommandNG + +SerialCmd05 movfw rxbuffer + xorlw 'D' + skpnz + goto DelAlternative + xorlw 'D' ^ 'V' + skpnz + goto SetVentSetpoint + goto SerialCmdGPIO + +SerialCmd07 movfw rxbuffer + xorlw 'O' + skpnz + goto SetOverrideHigh + retlw CommandNG + +SerialCmd0B movfw rxbuffer + xorlw 'C' + skpnz + goto SetCentralHeat + retlw CommandNG + +SerialCmd10 movfw rxbuffer + xorlw 'S' + skpnz + goto SetClock + xorlw 'S' ^ 'G' + skpnz + goto SetGatewayMode + xorlw 'G' ^ 'C' + skpnz + goto SetCtrlSetpoint + retlw CommandNG + +SerialCmd11 movfw rxbuffer + xorlw 'C' + skpnz + goto ClearResponse + xorlw 'C' ^ 'S' + skpnz + goto SetSetBack + retlw CommandNG + +SerialCmd12 movfw rxbuffer + xorlw 'F' + skpnz + goto SetTStatModel + retlw CommandNG + +SerialCmd14 movfw rxbuffer + xorlw 'D' + skpnz + goto SetDebugPtr + retlw CommandNG + +SerialCmd17 movfw rxbuffer + xorlw 'T' + skpnz + goto SetContSetPoint + retlw CommandNG + +SerialCmd1B movfw rxbuffer + xorlw 'O' + skpnz + goto SetOutsideT + xorlw 'O' ^ 'S' + skpnz + goto SetHeatingTemp + retlw CommandNG + +SerialCmd1C movfw rxbuffer + xorlw 'U' + skpnz + goto UnknownDataID + retlw CommandNG + +SerialCmd1D movfw rxbuffer + xorlw 'I' + skpnz + goto IgnoreError1 + xorlw 'I' ^ 'P' + skpnz + goto SetPrioMessage + retlw CommandNG + +SerialCmd1F movfw rxbuffer + xorlw 'H' + skpnz + goto SetHotWater + retlw CommandNG + +SerialCmdLED movfw rxbuffer + xorlw 'L' + skpz + retlw CommandNG ;LED commands must start with 'L' + movfw rxpointer + sublw 4 + skpz + retlw SyntaxError ;Command length must be 4 + movfw rxbuffer + 3 + movwf temp ;Keep func code for storing in EEPROM + sublw 'Z' + sublw 'Z' - 'A' ;Check for valid function code + skpc + retlw BadValue ;Valid functions are 'A' - 'Z' + movlw 'A' + subwf rxbuffer + 1,W ;Get LED number + addlw FunctionLED1 ;Calculate EEPROM address + call WriteEpromData ;Save the LED configuration in EEPROM + movfw rxbuffer + 3 + pcall PrintChar + movlw 'A' + subwf rxbuffer + 1,W ;Get LED number again + movwf temp + movfw rxbuffer + 3 ;Get the function code +SetLEDFunction movwf float1 ;Save temporarily + movlw b'00001000' ;Bit mask for LED 0 + btfsc temp,1 + movlw b'01000000' ;Bit mask for LED 2 + btfsc temp,2 + movlw b'00000010' ;Bit mask for LED 4 + movwf tempvar0 ;Save the bit mask + clrc + btfsc temp,0 + rlf tempvar0,F ;Shift the bit mask for odd LEDs + ;Remove the old function for the LED + movlw functions ;Start of the function table + movwf FSR ;Setup indirect adressing + movlw 26 ;Size of the table + movwf loopcounter + comf tempvar0,W ;Invert the bit mask +SetLEDLoop andwf INDF,F ;Remove the LED from the function flags + incf FSR,F ;Point to the next function + decfsz loopcounter,F + goto SetLEDLoop ;Repeat the loop + ;Setup the new function for the LED + movfw float1 ;Name of the new function + addlw functions - 'A' ;Pointer into the function table + movwf FSR ;Setup indirect addressing + movfw tempvar0 ;Reload the bit mask for the LED + iorwf INDF,F ;Link the LED to the selected function + movfw float1 ;Name of the new function, clears Z-bit + btfss INDF,0 ;Check the function state + setz ;Function state is off + lgoto SwitchLED ;Set the initial state of the LED + +SerialCmdGPIO movfw rxbuffer + sublw 'G' + skpz + retlw CommandNG ;GPIO commands must start with 'G' + movfw rxpointer + sublw 4 + skpz + retlw SyntaxError ;Command length must be 4 + movlw '0' + subwf rxbuffer + 3,W + movwf temp ;Keep func code for storing in EEPROM + andlw b'11111000' ;Currently there are 7 GPIO functions + skpz + retlw SyntaxError ;Invalid GPIO function + movlw b'11110000' ;Mask to clear old GPIO A function + btfsc rxbuffer + 1,0 ;Configuring GPIO B? + goto SetGPIOFunction + andwf GPIOFunction,W ;GPIO B function code in high nibble + sublw 0x70 ;Old GPIO B function was DS1820? + skpnz + bcf OutsideTemp ;Forget outside temperature + swapf temp,F ;Move new function code to upper nibble + movlw b'1111' ;Mask to clear old GPIO B function +SetGPIOFunction andwf GPIOFunction,W ;Clear the old function code + iorwf temp,W ;Insert the new function code + movwf GPIOFunction ;Store in RAM + movwf temp ;Value to write to EEPROM + movlw FunctionGPIO ;EEPROM address to write to + call WriteEpromData ;Save in EEPROM + bsf gpio_port1 + bcf gpio_port2 + movlw b'11000000' + btfss rxbuffer + 1,0 ;Configuring GPIO A? + xorwf gpio_mask,F + lcall gpio_initport + movfw GPIOFunction + btfss rxbuffer + 1,0 ;Configuring GPIO A? + swapf GPIOFunction,W + andlw b'111' +GoPrintDigit lgoto PrintDigit + +CheckBoolean clrc ;Carry indicates success + movfw rxpointer + xorlw 4 ;Check the command is 4 characters long + skpz + retlw SyntaxError ;Wrong length + movfw rxbuffer + 3 ;Get the flag state + sublw '1' ;It must be either '0' or '1' + sublw '1' - '0' ;Only the two allowed values ... + skpnc ;... result in a carry + return ;Success + retlw BadValue ;Illegal boolean value + +SetContSetPoint call GetFloatArg ;Parse floating point value + skpnc + return + movlw b'01' ;Only allow manual override + goto SetSerSetPoint +SetTempSetPoint call GetFloatArg ;Parse floating point value + skpnc + return + movlw b'11' ;Allow manual and program override +SetSerSetPoint btfsc NegativeTemp ;Check for a negative value + retlw SyntaxError ;Cannot set the thermostat below zero + call SetSetPoint ;Change the setpoint + goto CommandFloat ;Confirm the new setpoint + +SetSetPoint xorwf RemOverrideFunc,W ;Store function bits in lower nibble + andlw b'1111' + xorwf RemOverrideFunc,F ;-- + movlw 0 ;Clear all old state information + btfss TStatISense ;Special treatment for iSense thermostat + goto SetSetPointJ1 + movfw setpoint1 ;Check old setpoint + iorwf setpoint2,W + skpz ;There is no old setpoint + bsf OverrideClr ;Clear old setpoint before setting new + movlw 1 << 4 ;Leave OverrideClr bit intact +SetSetPointJ1 andwf override,F ;Clear old state information + movfw float2 ;Copy the value to the setpoint vars + movwf setpoint2 + movfw float1 + movwf setpoint1 ;-- + bsf OverrideReq ;Start blinking the override LED + bsf OverrideWait + bsf OverrideFunc + clrf celciasetpoint + btfsc TStatCelcia + bsf Unsolicited ;Send an unsolicited 04-frame + iorwf setpoint2,W ;Check if both bytes are 0 + skpnz + goto ClearSetPoint + btfss TStatCelcia ;Special treatment for Celcia thermostat + return +;The Remeha Celcia 20 thermostat does not react propperly to the value provided +;in a remote room setpoint override (MsgID=09). It needs to receive a remote +;command response (MsgID=04) to manipulate the room setpoint. +;Thanks to Rien Groot for figuring out this feature and providing the necessary +;information to add this option to the opentherm gateway firmware. +CelciaValue movfw setpoint1 + call Multiply5 ;Multiply the units by 5 + movfw float2 + movwf celciasetpoint + movfw setpoint2 + call Multiply5 ;Multiply the fraction by 5 + movfw float1 + addwf celciasetpoint,F;Add the two together + btfsc float2,7 + incf celciasetpoint,F;Round up + movfw setpoint1 + movwf float1 + movfw setpoint2 + movwf float2 + return +ClearSetPoint bsf OverrideClr ;Override request to be cancelled + bcf ManChangePrio + bcf ProgChangePrio + return + +SetSetBack call GetFloatArg ;Parse floating point value + skpnc + return + iorwf float2,W + skpz ;Zero has special meaning in the code + btfsc NegativeTemp ;Check for a negative value + retlw SyntaxError ;Setpoint cannot be zero or below + movlw AwaySetpoint1 ;EEPROM address for units + call WriteEpromData ;Save in EEPROM + movfw float2 + movwf temp + movlw AwaySetpoint2 ;EEPROM address for fraction + call WriteEpromData ;Save in EEPROM + goto CommandFloat + +SetHotWaterTemp call GetFloatArg + skpnc + return + iorwf float2,W + skpnz + goto HotWaterReset + movfw dhwsetpointmin + call Compare + skpc + retlw BadValue + movfw dhwsetpointmax + call Compare + skpz + skpc + goto HotWaterTempOK + retlw BadValue +HotWaterReset bcf WaterSetpoint + goto HotWaterFinish +HotWaterTempOK btfsc dhwupdate ;Check if boiler supports write access + bsf WaterSetpoint ;Schedule to send command to the boiler +HotWaterFinish movfw float2 + movwf dhwsetpoint2 + movfw float1 + movwf dhwsetpoint1 + goto CommandFloat + +SetHeatingTemp call GetFloatArg + skpnc + return + iorwf float2,W + skpnz + goto HeatingReset + movfw chsetpointmin + call Compare + skpc + retlw BadValue + movfw chsetpointmax + call Compare + skpz + skpc + goto HeatingTempOK + retlw BadValue +HeatingReset bcf MaxHeatSetpoint + goto HeatingFinish +HeatingTempOK btfsc maxchupdate ;Check if boiler supports write access + bsf MaxHeatSetpoint ;Schedule to send command to the boiler +HeatingFinish movfw float2 + movwf maxchsetpoint2 + movfw float1 + movwf maxchsetpoint1 + goto CommandFloat + +SetCtrlSetpoint call GetFloatArg + skpnc + return + btfsc NegativeTemp ;Check for a negative value + retlw SyntaxError ;Negative temperatures are not allowed + movwf controlsetpt1 + movfw float2 + movwf controlsetpt2 + goto CommandFloat + +SetVentSetpoint call GetDecimalArg ;Get the ventilation setpoint + skpnc + goto ClrVentSetpoint ;Non-numeric value + tstf INDF + skpz + retlw SyntaxError ;Characters following the value + sublw 100 + skpc + retlw OutOfRange ;Value must be in the range 0-100 + rlf temp,W ;Shift a 1 into bit 0, value in bit 1-7 + movwf ventsetpoint + movfw temp + lgoto PrintByte ;Confirm the command +ClrVentSetpoint clrf ventsetpoint + goto ValueCleared + +SetOutsideT call GetFloatArg + skpnc + return + btfss float1,7 ;Temperature value negative? + btfss float1,6 ;Temperature higher than 64 degrees? + goto StoreOutsideT + bcf OutsideTemp ;No (reasonable) outside temperature +ValueCleared movlw '-' + lgoto PrintChar +StoreOutsideT call StoreOutTemp +CommandFloat lgoto PrintSigned + +StoreOutTemp movfw float1 + movwf outside1 + bsf STATUS,RP1 + movwf outsideval1 + bcf STATUS,RP1 + movfw float2 + movwf outside2 + bsf STATUS,RP1 + movwf outsideval2 + bcf STATUS,RP1 + bsf OutsideTemp + bcf OutsideInvalid ;Outside temperature is valid + return + +SetDebugPtr movfw rxpointer + sublw 5 + skpz + retlw SyntaxError + call GetHexArg + skpnc + return + movwf debug + lgoto PrintHex + +SetClock call GetDecimalArg ;Get the hours + skpnc + return + movwf clock1 + sublw 23 + skpc + retlw OutOfRange + + movfw INDF + sublw ':' + skpz + retlw SyntaxError + incf FSR,F + + call GetDecimal ;Get the minutes + skpnc + retlw SyntaxError + movwf clock2 + sublw 59 + skpc + retlw OutOfRange + + movfw INDF + sublw '/' + skpz + retlw SyntaxError + incf FSR,F + + movlw '0' ;Get the day of the week + subwf INDF,W + skpz + skpc + retlw BadValue + movwf temp + andlw b'11111000' + skpz + retlw BadValue + incf FSR,F + tstf INDF + skpz + retlw SyntaxError + swapf temp,F + clrc + rlf temp,W + iorwf clock1,F + movlw 120 | 1 << 7 + movwf minutetimer + movfw clock1 + andlw b'11111' + movwf temp + lcall PrintDecimal + movfw clock2 + movwf temp + movlw ':' + call PrintFraction + movlw '/' + call PrintChar + swapf clock1,W + movwf temp + rrf temp,W + andlw b'111' + goto PrintDigit + +SetHotWater movfw rxpointer + sublw 4 + skpz + retlw SyntaxError + bcf HotWaterSwitch + bcf HotWaterEnable + movfw rxbuffer + 3 + sublw '1' + skpnz + bsf HotWaterEnable + andlw ~1 + skpnz + bsf HotWaterSwitch + lgoto PrintHotWater + +SetCentralHeat call CheckBoolean + skpc + return + skpz + bcf CHModeOff + skpnz + bsf CHModeOff + goto GoPrintDigit + +;Set the reference voltage: +;0 = 0.625V 1 = 0.833V 2 = 1.042V 3 = 1.250V 4 = 1.458V +;5 = 1.667V 6 = 1.875V 7 = 2.083V 8 = 2.292V 9 = 2.500V +SetVoltageRef movfw rxpointer + sublw 4 + skpz + retlw SyntaxError + movfw rxbuffer + 3 ;Get the reference voltage setting + sublw '9' ;Valid values are '0' through '9' + sublw '9' - '0' ;Convert the value, while at the ... + skpc ;... same time checking for valid input + retlw BadValue + addlw 3 + movwf temp + iorlw b'11100000' + bsf STATUS,RP0 + movwf CVRCON + bcf STATUS,RP0 + movlw b'11100000' + andwf settings,W + iorwf temp,W + movwf settings + call SaveSettings + lgoto PrintRefVoltage + +SaveSettings movwf temp + movlw SavedSettings +;Write to data EEPROM. Data must be in temp, address in W +WriteEpromData pcall EepromWait ;Wait for any pending EEPROM activity + bcf STATUS,RP0 + movwf EEADR ;Setup the EEPROM data address + movfw temp ;Get the value to store in EEPROM +StoreEpromData bsf STATUS,RP1 + bsf STATUS,RP0 + bsf EECON1,RD ;Read the current value + bcf STATUS,RP0 + xorwf EEDATA,W ;Check if the write can be skipped + skpnz + goto StoreEpromSkip ;Prevent unnecessary EEPROM writes + xorwf EEDATA,F ;Load the data value + bsf STATUS,RP0 + bsf EECON1,WREN ;Enable EEPROM writes + bcf INTCON,GIE ;The sequence should not be interrupted + ;Required sequence to write EEPROM + movlw 0x55 + movwf EECON2 + movlw 0xAA + movwf EECON2 + bsf EECON1,WR ;Start the actual write + ;-- + bsf INTCON,GIE ;Interrupts are allowed again + bcf EECON1,WREN ;Prevent accidental writes + bcf STATUS,RP0 +StoreEpromSkip bcf STATUS,RP1 ;Switch back to bank 0 + movfw temp ;Return the byte that was written + return + +SetAlternative call GetDecimalArg ;Get the message number + skpnc ;Valid decimal value? + return ;Return the error code from GetDecimal + skpnz ;Don't allow adding message ID=0 ... + retlw OutOfRange ;... because 0 indicates an empty slot + tstf INDF ;Is this the end of the command? + skpz + retlw SyntaxError + bcf NoAlternative ;There now is at least one alternative + pcall EepromWait ;Wait for any pending EEPROM activity + bcf STATUS,RP0 + movlw AlternativeCmd + movwf EEADR ;Setup access to list of alternatives + movfw temp ;Get DataID specified by the user +SetAltLoop bsf STATUS,RP0 + bsf EECON1,RD + bcf STATUS,RP0 + tstf EEDATA ;Check for an empty slot + skpnz + goto StoreAlt ;Slot is empty, put the DataID in it + incf EEADR,F ;Slot not empty, try next position + skpz ;End of list reached? + goto SetAltLoop + bcf STATUS,RP1 + retlw NoSpaceLeft ;No empty slot available +StoreAlt call StoreEpromData + lgoto PrintByte + +DelAlternative call GetDecimalArg ;Get the message number + skpnc ;Valid decimal value? + return ;Return the error code from GetDecimal + skpnz ;Don't allow deleting message ID=0 ... + retlw OutOfRange ;... because 0 indicates an empty slot + tstf INDF ;Is this the end of the command? + skpz + retlw SyntaxError + call DeleteAlt + skpnc ;MessageID was actually deleted? + return ;Return the error code + lgoto PrintByte ;Print the message ID + +DeleteAlt movwf temp + pcall EepromWait ;Wait for any pending EEPROM activity + bcf STATUS,RP0 + movlw AlternativeCmd ;Start of list of alternatives + movwf EEADR +DelAltLoop bsf STATUS,RP0 + bsf EECON1,RD ;Read a byte from EEPROM + bcf STATUS,RP0 + movfw temp + xorwf EEDATA,W ;Compare to the message ID to delete + skpnz + goto StoreEpromData ;Release the slot by writing 0 to it + incf EEADR,F ;Proceed to the next slot + skpz ;Check if we're done + goto DelAltLoop ;Repeat the loop + bcf STATUS,RP1 ;Switch back to bank 0 + setc ;Nothing was found + retlw NotFound ;The requested message ID was not found + +SetTStatModel movfw rxpointer + sublw 4 ;Command takes a single char argument + skpz + retlw SyntaxError + clrf remehaflags ;Assume auto-detect + movfw rxbuffer + 3 + xorlw 'C' ;C=Celcia 20 + skpnz + bsf TStatCelcia + xorlw 'C' ^ 'I' ;I=ISense + skpnz + bsf TStatISense + tstf remehaflags ;No set bits means auto-detection + skpz + bsf TStatManual ;Thermostat model set manually + movfw remehaflags + movwf temp + movlw TStatModel ;EEPROM address for thermostat model + call WriteEpromData ;Save in EEPROM + lgoto PrintRemeha ;Report the selected model + +SetGatewayMode movfw rxpointer ;Check the command is 4 characters long + sublw 4 + skpz + retlw SyntaxError + call CheckBoolean + skpc + goto ResetGateway + xorwf mode,W ;Compare against the current setting + andlw 1 << 0 + skpz + goto SetModeDone ;Not really changing the mode + bsf ChangeMode ;Remember to change the gateway mode + call SetMonitorMode + btfss ChangeMode ;Mode change still pending? +SetModeDone movfw rxbuffer + 3 + lgoto PrintChar + +SetMonitorMode btfsc T2CON,TMR2ON ;Check if timer 2 is running + retlw 'P' ;Don't change while communicating + movlw 1 << 0 ;Mask for the MonitorMode flag + xorwf mode,F ;Toggle gateway/monitor mode + movlw b'00000011' ;Comparator setting for gateway mode + btfsc MonitorMode ;Check which mode to switch to + movlw b'00000110' ;Comparator setting for monitor mode + bsf STATUS,RP0 + movwf CMCON ;Configure the comparators + bcf STATUS,RP0 + bcf ChangeMode ;Change has been applied + return + +ResetGateway movfw rxbuffer + 3 + sublw 'R' ;GW=R resets the gateway + skpz + retlw BadValue + bsf resetreason,0 ;Resetting due to a serial command + bcf RSET ;Prepare the output latch + bsf STATUS,RP0 + bcf RSET ;Switch the pin to output + lgoto 0 ;Simulate reset + +SetMaxModLevel call GetDecimalArg ;Get the modulation level + skpnc + goto ClrMaxModLevel + tstf INDF + skpz + retlw SyntaxError + sublw 100 + skpc + retlw OutOfRange + bsf UserMaxModLevel + sublw 100 + movwf MaxModLevel + lgoto PrintByte +ClrMaxModLevel bcf UserMaxModLevel + movlw 100 + movwf MaxModLevel + goto ValueCleared + +ReportSetting lgoto PrintSetting + +ReportSummary lgoto PrintSummary + +SetResponse call GetDecimalArg ;Get the DataID + skpnc + return ;No decimal value specified + skpnz ;DataID should not be 0 + retlw BadValue + movwf temp2 + movlw ':' + subwf INDF,W + skpz + retlw SyntaxError + incf FSR,F + clrf float1 + call GetDecimal ;Get byte value + skpnc + return ;Missing byte value + tstf INDF + skpnz + goto OneByteResponse + movwf float1 ;Save as upper byte + movlw ',' + subwf INDF,W + skpz + retlw SyntaxError + incf FSR,F + call GetDecimal ;Get the lower byte value + skpnc + return ;Missing lower byte value + tstf INDF ;Command should be finished here + skpz + retlw SyntaxError +OneByteResponse movwf float2 ;Save lower byte + movfw temp2 ;Get specified DataID + call FindResponse ;Check if it is already in the list + skpnz + goto UpdateResponse ;DataID found in the list + clrw + call FindResponse ;Find an empty slot + skpz + retlw NoSpaceLeft ;There is no empty slot +UpdateResponse bsf STATUS,IRP + movfw float2 ;Get the lower byte + movwf INDF ;Save in the slot matching the DataID + bcf FSR,7 + movfw float1 ;Get the upper byte + movwf INDF ;Save in the slot matching the DataID + bcf STATUS,IRP + bsf FSR,7 + movfw temp2 + movwf INDF ;Save the DataID + lcall PrintByte + movlw ':' + call PrintChar + goto PrintBytes + +ClearResponse call GetDecimalArg ;Get the DataID + skpnc + return ;No decimal value specified + skpnz ;DataID should not be 0 + retlw BadValue + tstf INDF + skpz + retlw SyntaxError + call FindResponse ;Find the slot for the DataID + skpz + retlw NotFound ;The DataID was not in the list + clrf INDF ;Release the slot + movfw temp + lgoto PrintByte + +FindResponse movwf temp ;Save the DataID to search for + movlw ResponseValues1 + movwf FSR +FindResLoop movfw INDF ;Get the DataID for the response + xorwf temp,W + skpnz ;Check if the slot matches + return ;DataID found (carry is cleared) + movlw 1 + addwf FSR,F + skpdc + goto FindResLoop + retlw 0 ;DataID not found (carry is set) + +UnknownDataID call GetDecimalArg ;Get the DataID + skpnc + return ;No decimal value specified + skpz ;DataID should not be 0 + movwf float1 + btfsc float1,7 ;Only allow MsgID's in range 1..127 + retlw BadValue + tstf INDF ;Is this the end of the command? + skpz + retlw SyntaxError + pcall UnknownMask2 ;Get mask and pointer into unknownmap + iorwf INDF,F ;Mark message as unsupported + movfw float1 + call BitMask + addlw UnknownFlags + pcall ReadEpromData + iorwf temp,W +StoreDataIdMask call StoreEpromData ;Update non-volatile storage + movfw float1 + lgoto PrintByte + +KnownDataID call GetDecimalArg ;Get the DataID + skpnc + return ;No decimal value specified + skpz ;DataID should not be 0 + movwf float1 + btfsc float1,7 ;Only allow MsgID's in range 1..127 + retlw BadValue + tstf INDF ;Is this the end of the command? + skpz + retlw SyntaxError + pcall UnknownMask2 ;Get mask and pointer into unknownmap + xorlw -1 ;Invert the mask + andwf INDF,F ;Mark message as supported + movfw float1 + call BitMask + addlw UnknownFlags + pcall ReadEpromData + comf temp,F + andwf temp,W + goto StoreDataIdMask + +BitMask clrc + movwf temp2 + movlw b'1' + btfsc temp2,1 + movlw b'100' + movwf temp + btfsc temp2,0 + rlf temp,F + btfsc temp2,2 + swapf temp,F + swapf temp2,F + rlf temp2,W + rlf temp2,W + andlw b'11111' + return + +;There have been reports that some equipment does not produce clean transitions +;between the two logical Opentherm signal levels. As a result the gateway may +;produce frequent Error 01 reports. By setting the IT flag, the requirement for +;a maximum of one mid-bit transition is no longer checked. +;When this flag is set, Error 01 will never occur. +IgnoreError1 call CheckBoolean + skpc + return + movlw 1 << 5 ;Mask for IgnoreErr1 +SetSetting btfsc rxbuffer + 3,0 + iorwf settings,F ;Set the option + xorlw -1 ;Invert the mas + btfss rxbuffer + 3,0 + andwf settings,F ;Clear the option + movfw settings + call SaveSettings ;Store the setting in EEPROM + movfw rxbuffer + 3 + lgoto PrintChar + +SetOverrideHigh call CheckBoolean + skpc + return + movlw 1 << 6 ;Mask for the OverrideHigh bit + goto SetSetting + +;Reset boiler counters +;Algorithm: +;First char: H = 0, W = 2 +;Second char: B = 0, P = 1 +;Third char: S = 0, H = 4 +;If bit 1 is set, xor bit 0 + +ResetCounter movfw rxpointer + sublw 6 + skpz + retlw SyntaxError + movlw 1 + movwf temp + movfw rxbuffer + 3 + xorlw 'H' + skpnz + goto ResetCntJump1 + xorlw 'H' ^ 'W' + skpz + retlw SyntaxError + movlw 4 + movwf temp + movlw 'B' ^ 'P' +ResetCntJump1 xorwf rxbuffer + 4,W + xorlw 'B' + skpnz + goto ResetCntJump2 + xorlw 'B' ^ 'P' + skpz + retlw SyntaxError + clrc + rlf temp,F +ResetCntJump2 movfw rxbuffer + 5 + xorlw 'S' + skpnz + goto ResetCntJump3 + xorlw 'S' ^ 'H' + skpz + retlw SyntaxError + swapf temp,F +ResetCntJump3 movfw temp + iorwf resetflags,F + movfw rxbuffer + 3 + lcall PrintChar + movfw rxbuffer + 4 + call PrintChar + movfw rxbuffer + 5 + goto PrintChar + +SetPrioMessage call GetDecimalArg ;Get the DataID + skpnc + return ;No decimal value specified + tstf INDF ;Is this the end of the command? + skpz + retlw SyntaxError + movwf prioritymsgid + bsf PriorityMsg + clrf TSPCount + clrf TSPIndex + lgoto PrintByte + +CmdArgPointer movlw rxbuffer + 3 + movwf FSR + return + +;Returns the decimal value found. Carry is set on error +GetDecimalArg call CmdArgPointer +GetDecimal movfw INDF + sublw '9' + sublw '9' - '0' + skpc + goto CarrySyntaxErr +GetDecLoop movwf temp + incf FSR,F + movfw INDF + sublw '9' + sublw '9' - '0' + skpc + goto GetDecReturn + movwf tempvar0 + movlw 26 + subwf temp,W + skpnc + retlw OutOfRange + movfw temp + call Multiply10 + addwf tempvar0,W + skpc + goto GetDecLoop + retlw OutOfRange +GetDecReturn movfw temp + return +CarrySyntaxErr setc + retlw SyntaxError + +GetHexArg call CmdArgPointer +GetHexadecimal call GetHexDigit + skpnc + return + movwf temp + swapf temp,F + call GetHexDigit + skpc + iorwf temp,W + return + +GetHexDigit movlw '0' + subwf INDF,W + skpc + goto CarrySyntaxErr + addlw -10 + skpc + goto GetHexDigitNum + andlw ~h'20' + addlw -7 + skpc + goto CarrySyntaxErr + addlw -6 + skpnc + retlw SyntaxError + addlw 6 +GetHexDigitNum addlw 10 + incf FSR,F + clrc + return + +GetFloatArg call CmdArgPointer +GetFloat bsf NegativeTemp ;Assume a negative value + movfw INDF + sublw '-' ;Check if the assumption is right + skpnz + goto GetSignedTemp + bcf NegativeTemp ;No minus sign. Must be positive then. + sublw '-' - '+' ;Check for a plus sign + skpnz ;No sign specified +GetSignedTemp incf FSR,F ;Skip the sign + call GetDecimal ;Get the integer part + skpnc ;Carry is set on error + return ;Return the error code from GetDecimal + clrf float1 ;Assume no fractional part + movfw INDF ;Check for end of string + skpnz + goto GetFloatValue ;No fractional part specified + sublw '.' + skpz + goto CarrySyntaxErr ;The only allowed character is a '.' + movlw b'000000111' ;Action encoding + movwf loopcounter +FloatFracLoop incf FSR,F + movfw INDF ;Check for end of string + skpnz + goto GetFloatValue ;End of fractional part + sublw '9' + sublw '9' - '0' ;Only digits are allowed at this point + skpc + goto CarrySyntaxErr ;Found something other than a digit + btfsc loopcounter,2 + call Multiply10 ;Multiply the digit by 10 + btfsc loopcounter,1 + addwf float1,F + clrc + rrf loopcounter,F + tstf loopcounter + skpnz + skpc + goto FloatFracLoop + addlw -5 + skpnc + incf float1,F + goto FloatFracLoop +GetFloatValue btfss NegativeTemp ;Check for negative values + goto GetFloatWrapUp + comf temp,F ;Invert the integer part + tstf float1 + skpnz + goto GetFloatNegInt + movfw float1 + sublw 100 + movwf float1 +GetFloatWrapUp clrf float2 + call Divide100 + tstf tempvar1 ;The honneywell thermostat always ... + skpz ;... rounds down, so if the result ... + addlw 1 ;... is not exact, round up + movwf float2 + movfw temp + movwf float1 + return ;Carry is guaranteed to be clear +GetFloatNegInt incf temp,F ;Add 1 to get negative value + goto GetFloatWrapUp + +Multiply5 clrf float1 + movwf float2 + call Multiply2 + call Multiply2 + addwf float2,F + skpnc + incf float1,F + return + +Multiply2 clrc + rlf float2,F + rlf float1,F + return + +Multiply10 movwf tempvar1 ;Store a copy in a temporary variable + addwf tempvar1,F ;Add again to get: value * 2(clears C) + rlf tempvar1,F ;Shift left gives: value * 4 + addwf tempvar1,F ;Add original value: value * 5 + rlf tempvar1,W ;Shift left for: value * 10 + return ;Return the result + +;Divide float1:float2 by 100 - result in W +Divide100 movlw 16 + movwf loopcounter + clrf tempvar0 + clrf tempvar1 +DivideLoop rlf float2,F + rlf float1,F + rlf tempvar1,F + movlw 100 + subwf tempvar1,W + skpnc + movwf tempvar1 + rlf tempvar0,F + decfsz loopcounter,F + goto DivideLoop + movfw tempvar0 + return + +;Compare the value in float1:float2 to W, sets C and Z bit. C=1 if float >= W +Compare subwf float1,W + skpz + return + tstf float2 + return + +;************************************************************************ +; Implement the GPIO port functions +;************************************************************************ + + package gpio + constant GPIO_NONE=0 + constant GPIO_GND=1 + constant GPIO_VCC=2 + constant GPIO_LEDE=3 + constant GPIO_LEDF=4 + constant GPIO_HOME=5 + constant GPIO_AWAY=6 + constant GPIO_DS1820=7 + +gpio_init bsf gpio_port1 ;Set up the port mask for gpio port 1 + bcf gpio_port2 + call gpio_initport ;Initialize the port funtion + bcf gpio_port1 ;Set up the port mask for gpio port 2 + bsf gpio_port2 +gpio_initport movlw high gpio_inittab ;Standard indexed jump code + movwf PCLATH + movfw GPIOFunction + btfsc gpio_port2 + swapf GPIOFunction,W + andlw b'111' ;Limit to 0-7 + addlw low gpio_inittab + skpnc + incf PCLATH,F + movwf PCL ;Jump into the jump table +gpio_inittab goto gpio_input ;GPIO_NONE + goto gpio_initgnd ;GPIO_GND + goto gpio_initvcc ;GPIO_VCC + goto gpio_initvcc ;GPIO_LEDE + goto gpio_initvcc ;GPIO_LEDF + goto gpio_input ;GPIO_HOME + goto gpio_input ;GPIO_AWAY + goto gpio_onewire ;GPIO_DS1820 +gpio_onewire movlw b'11110000' + btfss gpio_port2 ;Function is only supported on port 2 + andwf GPIOFunction,F ;Disable function on port 1 +gpio_input movfw gpio_mask ;Get the port mask + andlw b'11000000' ;Mask of the other bits + bsf STATUS,RP0 + iorwf TRISA,F ;Set the port to input + bcf STATUS,RP0 + return +gpio_output comf gpio_mask,W ;Get the inverted port mask + iorlw b'00111111' ;Set all other bits + bsf STATUS,RP0 + andwf TRISA,F ;Set the port to output + bcf STATUS,RP0 + return +gpio_initgnd call gpio_output ;Make the port an output + andwf PORTA,F ;Pull the output low + return +gpio_initvcc call gpio_output ;Make the port an output + xorlw -1 ;Invert the mask + iorwf PORTA,F ;Pull the output high + return + +gpio_common bsf gpio_port1 ;Set up the port mask for gpio port 1 + bcf gpio_port2 + call gpio_apply ;Perform the port function + bcf gpio_port1 ;Set up the port mask for gpio port 2 + bsf gpio_port2 +gpio_apply movlw high gpio_table ;Standard indexed jump code + movwf PCLATH + movfw GPIOFunction + btfsc gpio_port2 + swapf GPIOFunction,W + andlw b'111' ;Limit to 0-7 + addlw low gpio_table + skpnc + incf PCLATH,F + movwf PCL ;Jump into the jump table +gpio_table return ;GPIO_NONE + return ;GPIO_GND + return ;GPIO_VCC + goto gpio_lede ;GPIO_LEDE + goto gpio_ledf ;GPIO_LEDF + goto gpio_home ;GPIO_HOME + goto gpio_away ;GPIO_AWAY + goto gpio_temp ;GPIO_DS1820 +gpio_lede movfw PORTA ;Get current state of the GPIO pin + btfsc VirtualLedE ;Check the shadow LED state + comf PORTA,W ;Get inverted state of the GPIO pin + andwf gpio_mask,W ;Mask off the other GPIO pin + andlw b'11000000' ;Mask off the remaining bits + xorwf PORTA,F ;Update the GPIO port + return +gpio_ledf movfw PORTA ;Get current state of the GPIO pin + btfsc VirtualLedF ;Check the shadow LED state + comf PORTA,W ;Get inverted state of the GPIO pin + andwf gpio_mask,W ;Mask off the other GPIO pin + andlw b'11000000' ;Mask off the remaining bits + xorwf PORTA,F ;Update the GPIO port + return +gpio_temp btfss Intermission ;Wait for the gap between messages + return + tstf bitcount ;Check that no line events have happened + pcall Temperature ;Do one temperature measurement step + iorlw 0 ;Check if measurement is complete + skpnz + bcf Intermission ;No more steps to be done + return +gpio_home comf PORTA,W ;Get the inverted state of PORT A + goto gpio_security +gpio_away movfw PORTA ;Get the normal state of PORT A +gpio_security andwf gpio_mask,W ;Apply the GPIO port mask + andlw b'11000000' ;Clear the irrelevant bits + skpz + goto gpio_armed ;Security system is armed + btfss gpioaway ;Did the port change since last check? + return ;Nothing to do + bcf gpioaway ;Security system is disarmed + clrf float1 ;Cancel the remote setpoint + clrf float2 + goto gpio_setpoint ;W is 0 +gpio_armed btfsc gpioaway ;Did the port change since last check? + return ;Nothing to do + bsf gpioaway ;Security system is armed + movlw AwaySetpoint1 ;EEPROM address of the away setpoint + lcall ReadEpromData ;Get the setpoint from EEPROM + movwf float1 ;Configure the remote setpoint + movlw AwaySetpoint2 ;EEPROM address of the away setpoint + call ReadEpromData ;Get the setpoint from EEPROM + movwf float2 ;Configure the remote setpoint + movlw b'01' ;Temperature override mode: Continue +gpio_setpoint pcall SetSetPoint ;Set setpoint + return + +;######################################################################## + +;Initialize EEPROM data memory + + org 0x2100 +SavedSettings de 6 | 1 << 5 | 1 << 6 ;Set IgnoreErr1 and OverrideHigh +FunctionGPIO de GPIO_NONE | GPIO_NONE << 4 +AwaySetpoint1 de 16 +AwaySetpoint2 de 0 +PrintSettingL de "L=" +FunctionLED1 de 'M' +FunctionLED2 de 'X' +FunctionLED3 de 'O' +FunctionLED4 de 'F' +FunctionLED5 de 'P' +FunctionLED6 de 'C' +NullString de 0 +TStatModel de 0 + +PrintSettingA de "A=" +GreetingStr de "OpenTherm Gateway ", version +#ifdef __DEBUG + de phase, patch, ".", build +#else + #ifdef patch + de phase, patch + #ifdef bugfix + de '.', bugfix + #endif + #endif +#endif + de 0 +TimeoutStr de "WDT reset!", 0 +TDisconnect de "Thermostat disconnected", 0 +TConnect de "Thermostat connected", 0 +CommandNG de "NG", 0 +SyntaxError de "SE", 0 +NoSpaceLeft de "NS", 0 +BadValue de "BV", 0 +NotFound de "NF", 0 +OutOfRange de "OR", 0 +OverrunError de "OE", 0 +MediumPowerStr de "Medium", 0 +HighPowerStr de "High", 0 +NormalPowerStr de "Low" +PowerStr de " power", 0 +ErrorStr de "Error ", 0 +PrintSettingB de "B=" +TimeStamp de tstamp, 0 +PrintSettingC de "C=" +SpeedString de mhzstr, " MHz", 0 +ResetReasonStr de "ECSLPB" + + org 0x21D0 +UnknownFlags de 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 + org 0x21E0 +AlternativeCmd de 116,117,118,119,120,121,122,123 + de 0,0,0,0,0,0,0,0 + de 0,0,0,0,0,0,0,0 + de 0,0,0,0,0,0,0,0 + end diff --git a/boiler-monster/original-pic-4.2.5/license.txt b/boiler-monster/original-pic-4.2.5/license.txt new file mode 100644 index 0000000..6d576d3 --- /dev/null +++ b/boiler-monster/original-pic-4.2.5/license.txt @@ -0,0 +1,58 @@ +Copyright (c) 2009, Schelte Bron + +PREAMBLE + +This license establishes the terms under which a given software Package may +be copied, modified, distributed, and/or redistributed. The intent is that +the Copyright Holder maintains control over the development and distribution +of the Package, while allowing the users of the Package to use the Package +in a variety of ways. + +You are always permitted to make arrangements wholly outside of this license +directly with the Copyright Holder of a given Package. If the terms of this +license do not permit the full use that you propose to make of the Package, +you should contact the Copyright Holder and seek a different licensing +arrangement. + +DEFINITIONS + +"Copyright Holder" means the individual(s) or organization(s) named in the +copyright notice for the entire Package. + +"You" and "your" means any person who would like to copy, distribute, or +modify the Package. + +"Package" means the collection of files distributed by the Copyright Holder, +and derivatives of that collection and/or of those files. A given Package +may consist of either the Standard Version, or a Modified Version. + +"Distribute" means providing a copy of the Package or making it accessible +to anyone else, or in the case of a company or organization, to others +outside of your company or organization. + +"Standard Version" refers to the Package if it has not been modified, or has +been modified only in ways explicitly requested by the Copyright Holder. + +"Modified Version" means the Package, if it has been changed, and such +changes were not explicitly requested by the Copyright Holder. + +TERMS + +1. You may use this Package for non-commercial purposes without charge. + +2. You may make verbatim copies of this Package for personal use, provided +that you duplicate all of the original copyright notices and associated +disclaimers. You may not distribute copies of this Package, or copies of +packages derived from this Package, to others without specific prior written +permission from the Copyright Holder (although you are encouraged to direct +them to sources from which they may obtain it for themselves). + +3. You may apply bug fixes, portability fixes, and other modifications. A +Package modified in such a way shall still be covered by the terms of this +license. + +4. THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED +WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT WILL +THE AUTHORS BE HELD LIABLE FOR ANY DAMAGES ARISING FROM THE USE OF THIS +SOFTWARE. diff --git a/boiler-monster/original-pic-4.2.5/ot-gateway.brd b/boiler-monster/original-pic-4.2.5/ot-gateway.brd new file mode 100644 index 0000000..301dede Binary files /dev/null and b/boiler-monster/original-pic-4.2.5/ot-gateway.brd differ diff --git a/boiler-monster/original-pic-4.2.5/ot-gateway.sch b/boiler-monster/original-pic-4.2.5/ot-gateway.sch new file mode 100644 index 0000000..580fd38 Binary files /dev/null and b/boiler-monster/original-pic-4.2.5/ot-gateway.sch differ diff --git a/boiler-monster/original-pic-4.2.5/readme.txt b/boiler-monster/original-pic-4.2.5/readme.txt new file mode 100644 index 0000000..fd5d117 --- /dev/null +++ b/boiler-monster/original-pic-4.2.5/readme.txt @@ -0,0 +1,43 @@ +Requirements +============ + +The provided files are intended to be used together with the MPLAB tools that +can be downloaded for free form Microchip (www.microchip.com). The firmware +has been developed on MPLAB v8.53. + + +Compiling +========= + +The build.cmd script can be used to build the Opentherm Gateway firmware. If +you installed the MPLAB tools in a non-standard location, you will have to +modify the path on the second line of the script. + + +MPLAB Project +============= + +If you want to create a MPLAB IDE project for the Opentherm Gateway firmare, +you can follow these steps: +- Go to Project -> Project Wizard. +- Click Next +- Select the "PIC16F88" device and click Next +- Select the "Microchip MPASM Toolsuite" and click Next +- Browse to the otgw-4.2.5 directory and enter gateway.mcp as the filename. +- Click Save, then click Next +- Add the files "gateway.asm", "selfprog.asm", "ds1820.asm" and "16f88.lkr" + to the project and click Next +- Click Finish +- Select Project -> Build Configuration -> Release + +The "build.asm" file is supposed to be auto-generated at the start of every +build. The included "build.vbs" script can take care of that. To automatically +invoke the script at the start of each build, do the following: +- Go to Project -> Build options -> Project +- Select the "Custom Build" tab +- Enable the "Pre-Build Step" option +- Specify the command line: WScript "path\to\project\dir\build.vbs" +- Optionally add a project name as an argument on the command line +- Click OK + +Now press F10 to build the project. diff --git a/boiler-monster/original-pic-4.2.5/selfprog.asm b/boiler-monster/original-pic-4.2.5/selfprog.asm new file mode 100644 index 0000000..9e18769 --- /dev/null +++ b/boiler-monster/original-pic-4.2.5/selfprog.asm @@ -0,0 +1,391 @@ + title "Self Programming" + list b=8, r=dec + +;######################################################################## +; Self programming, based on Microchip AN851 +;######################################################################## +; This code has been optimized for size so it would fit in 0x100 code words. +; To achieve this, some special tricks had to be used. As a result the code +; may be a little hard to read +; +; The functionality differs from that described in AN851 in the following +; respects: +; 1. No auto baud detection. Communication must take place at 9600 baud. +; 2. Dropped the first that was used for auto baud detection. +; 3. Dropped the third address byte as it would always be 0. +; 4. The code does not check the last byte of EEPROM data memory to enter boot +; mode. Instead it transmits a character and waits up to 1 second for +; a character. +; 5. The code is designed to be placed in the highest part of memory. That way +; it doesn't interfere with the interrupt code address. It does rely on the +; main program to call the self-program code at an appropriate time. +; 6. Due to the location of the boot loader code, it cannot be protected using +; the code protection bits in the configuration word. This means that the +; boot loader code can also be updated, if necessary. +; 7. The Erase Flash command has been implemented for the PIC16F device. +; 8. The device reset command is 0x08, not any command with a 0 length. +; 9. The version command can be called with a data length of 1-3 words. It will +; report the following information: +; 1. Version +; 2. Boot loader start address +; 3. Boot loader end address +; The software used to reflash the device can use the last two pieces of +; information to determine which part of memory should not be touched. + +#include "p16f88.inc" + + errorlevel -302, -306 + +#define MAJOR_VERSION 1 +#define MINOR_VERSION 1 + +#define STX 0x0F +#define ETX 0x04 +#define DLE 0x05 + +CHKSUM equ 0x70 +COUNTER equ 0x71 +RXDATA equ 0x72 +TXDATA equ 0x73 +TEMP equ 0x74 + +DATA_BUFF equ 0x10 ;Start of receive buffer +COMMAND equ 0x10 ;Data mapped in receive buffer +DATA_COUNT equ 0x11 +ADDRESS_L equ 0x12 +ADDRESS_H equ 0x13 +PACKET_DATA equ 0x14 + +#define SELFRESET PORTB,1 +#define RX PORTB,2 +#define TX PORTB,5 + +#ifdef SELFPROGNEW + #define SELFPROG SelfProgNew + #define STARTADDRESS 0x0700 +#else + #define SELFPROG SelfProg + #define STARTADDRESS 0x0f00 +#endif + + global SELFPROG +SELFPROG code STARTADDRESS + ;Do not go into selfprog mode after a watchdog timeout reset +SELFPROG btfss STATUS,NOT_TO + return ;Let the main code handle a timeout + + bcf INTCON,GIE ;Ignore interrupts + + banksel OSCCON + movlw b'01100000' ;Internal oscillator set to 4MHz + movwf OSCCON ;Configure the oscillator + + ;Set the LEDS as outputs +#ifndef LVP + movlw b'00100111' +#else + movlw b'00101111' +#endif + movwf TRISB + + ;Configure the comparators and voltage reference + ;Allow communication between thermostat and boiler to continue + ;while reprogramming the device + movlw b'11100111' ;Pins 3 and 4 are digital ouputs + movwf TRISA + movlw b'00000111' ;A0 through A2 are used for analog I/O + movwf ANSEL + movlw b'11100110' ;Voltage reference at 1.25V + movwf CVRCON + movlw b'00000110' ;Two common reference comps with output + movwf CMCON + + ;Configure Timer 0 & Watchdog Timer + ;Bit 7 RBPU = 1 - Port B pull up disabled + ;Bit 6 INTEDG = 1 - Interrupt on rising edge + ;Bit 5 T0CS = 0 - Internal clock + ;Bit 4 T0SE = 1 - High to low transition + ;Bit 3 PSA = 0 - Prescaler assigned to Timer 0 + ;Bit 2-0 PS = 7 - 1:256 Prescaler + movlw b'11010111' + movwf OPTION_REG + + movlw 25 ;9600 baud + movwf SPBRG + movlw b'00100110' + movwf TXSTA ;High speed, Asynchronous, Enabled + bcf STATUS,RP0 + bsf RCSTA,SPEN ;Enable serial port + bsf RCSTA,CREN ;Enable receive + + ;Clear the LEDS + movlw b'11111111' + movwf PORTB + + clrf TMR0 + movlw 1 + call Pause +;Notify the external programming software (Pause returns ) + call WrRS232 +;Programmer should react within 1 second + movlw 16 + call Pause + btfss PIR1,RCIF +;No data received, resume the normal program + return +;Check that the received character is + call RdRS232 + skpz +;Other serial data received, resume normal program + return + + bsf STATUS,IRP +ReSync movlw DATA_BUFF + movwf FSR + clrf CHKSUM +GetNextDat call RdRS232 ;Get the data + skpnz + goto ReSync ;Found unprotected STX + xorlw STX ^ ETX ;Check for ETX + skpnz + goto CheckSum ;Yes, examine checksum + xorlw ETX ^ DLE ;Check for DLE + skpnz + call RdRS232 ;Yes, get the next byte + movfw RXDATA + movwf INDF ;Store the data + addwf CHKSUM,F ;Get sum + incf FSR,F + goto GetNextDat + +CheckSum tstf CHKSUM + skpz + goto StartOfLine + bsf STATUS,RP1 + movfw ADDRESS_L + movwf EEADR + movfw ADDRESS_H + movwf EEADRH + movlw PACKET_DATA + movwf FSR + movfw DATA_COUNT + movwf COUNTER +CheckCommand +#ifdef SELFPROGNEW + movlw high ($ + 0x800) +#else + movlw high $ +#endif + movwf PCLATH + movfw COMMAND + sublw 8 + sublw 8 + skpc + goto StartOfLine + addwf PCL,F + goto ReadVersion ;0 Read Version Information + goto ReadProgMem ;1 Read Program Memory + goto WriteProgMem ;2 Write Program Memory + goto EraseProgMem ;3 Erase Program Memory + goto ReadEE ;4 Read EEDATA Memory + goto WriteEE ;5 Write EEDATA Memory + goto StartOfLine ;6 Read Config Memory + goto StartOfLine ;7 Write Config Memory + goto VReset ;8 Reset + +VersionData data (MAJOR_VERSION << 8) | MINOR_VERSION +#ifdef SELFPROGNEW + data SELFPROG + 0x800, SelfProgEnd + 0x800 +#else + data SELFPROG, SelfProgEnd +#endif + +ReadVersion movlw low VersionData + movwf EEADR +#ifdef SELFPROGNEW + movlw high (VersionData + 0x800) +#else + movlw high VersionData +#endif + movwf EEADRH + movlw DATA_BUFF + 2 + movwf FSR +ReadProgMem bsf STATUS,RP0 + bsf EECON1,EEPGD + bsf EECON1,RD + nop + nop + bcf STATUS,RP0 + movfw EEDATA + movwf INDF + incf FSR,F + movfw EEDATH + movwf INDF + incf FSR,F + incf EEADR,F + skpnz + incf EEADRH,F + decfsz COUNTER,F + goto ReadProgMem +WritePacket movlw DATA_BUFF + subwf FSR,W +WritePacketJ1 movwf COUNTER + movlw STX + call WrRS232 + clrf CHKSUM + movlw DATA_BUFF + movwf FSR +SendNext movfw INDF + addwf CHKSUM,F + incf FSR,F + call WrData + decfsz COUNTER,F + goto SendNext + comf CHKSUM,W + addlw 1 + call WrData ;WrData returns + call WrRS232 +StartOfLine bcf STATUS,RP1 + call RdRS232 ;Look for a start of line + skpnz + goto ReSync + goto StartOfLine + +WriteProgMem movlw b'10000100' + call LoadEECon1 + movlw b'11111100' + andwf EEADR,F + movlw 4 + movwf TEMP +Lp1 movfw INDF + movwf EEDATA + incf FSR,F + movfw INDF + movwf EEDATH + incf FSR,F + call StartWrite + decfsz TEMP,F + goto Lp1 + decfsz COUNTER,F + goto WriteProgMem + goto WritePacketJ1 + +EraseProgMem movlw b'10010100' + call LoadEECon1 + movlw 0x1F + iorwf EEADR,F + call StartWrite + decfsz COUNTER,F + goto EraseProgMem + goto WritePacketJ1 + +ReadEE bsf STATUS,RP0 + clrf EECON1 + bsf EECON1,RD + bcf STATUS,RP0 + movfw EEDATA + movwf INDF + incf FSR,F + incf EEADR,F + decfsz COUNTER,F + goto ReadEE + goto WritePacket + +WriteEE movlw b'00000100' + call LoadEECon1 + movfw INDF + movwf EEDATA + incf FSR,F + call StartWrite + decfsz COUNTER,F + goto WriteEE + goto WritePacketJ1 + +WrData movwf TXDATA + xorlw STX + skpnz + goto WrDLE + xorlw STX ^ ETX + skpnz + goto WrDLE + xorlw ETX ^ DLE + skpz + goto WrNext +WrDLE movlw DLE + call WrRS232 +WrNext movfw TXDATA +WrRS232 clrwdt + bcf STATUS,RP1 +WaitTxEmpty btfss PIR1,TXIF + goto WaitTxEmpty + movwf TXREG + retlw ETX + +RdRS232 btfsc RCSTA,OERR + goto VReset +RdLp1 clrwdt + btfss PIR1,RCIF + goto RdLp1 + movfw RCREG + movwf RXDATA + xorlw STX + return + +LoadEECon1 bsf STATUS,RP0 + movwf EECON1 + bcf STATUS,RP0 + return + +StartWrite clrwdt + bsf STATUS,RP0 + movlw 0x55 + movwf EECON2 + movlw 0xAA + movwf EECON2 + bsf EECON1,WR +WaitEEWrite btfsc EECON1,WR ;Skipped when writing program memory + goto WaitEEWrite ;Skipped when writing program memory + bcf STATUS,RP0 + incf EEADR,F + skpnz + incf EEADRH,F + retlw 1 ;Return length of acknowledgement + +Pause clrwdt + btfsc PIR1,RCIF + return + btfss INTCON,TMR0IF + goto Pause + bcf INTCON,TMR0IF + addlw -1 + skpz + goto Pause + retlw ETX + +;Reset the device via an external connection from an I/O pin to the reset pin. +VReset bcf STATUS,RP1 + bcf SELFRESET ;Prepare the output latch + bsf STATUS,RP0 + bcf SELFRESET ;Switch the pin to output + +;If resetting via the output pin doesn't work, restore all used registers to +;their power-on defaults and jump to address 0. +;Do not simply return because the code that called this routine may have been +;wiped and reprogrammed to something completely different. + movlw b'11111111' + movwf TRISA + movwf TRISB + movwf OPTION_REG + movwf ANSEL + movlw b'00000111' + movwf CMCON + clrf TXSTA + clrf SPBRG + clrf STATUS + clrf RCSTA + clrf INTCON + clrf PCLATH + clrwdt +SelfProgEnd goto 0 + + end -- cgit v1.2.3