summaryrefslogtreecommitdiffstats
path: root/boiler-monster
diff options
context:
space:
mode:
authorfishsoupisgood <github@madingley.org>2020-09-09 11:53:37 +0100
committerfishsoupisgood <github@madingley.org>2020-09-09 11:53:37 +0100
commit9d87c925a9eaa4fc256be3173c14a20d1469472d (patch)
tree50d63f87a47a0eac3f5b8058850184bcd4e6ee95 /boiler-monster
parentdafd8cf2fdcdd637cc06f760d318cf8391b1a294 (diff)
downloadheating-9d87c925a9eaa4fc256be3173c14a20d1469472d.tar.gz
heating-9d87c925a9eaa4fc256be3173c14a20d1469472d.tar.bz2
heating-9d87c925a9eaa4fc256be3173c14a20d1469472d.zip
everything, mostly, working
Diffstat (limited to 'boiler-monster')
-rw-r--r--boiler-monster/DOCS/IMG_20200514_1758578.jpgbin0 -> 3034508 bytes
-rw-r--r--boiler-monster/DOCS/Opentherm Protocol v2-2.pdfbin0 -> 285534 bytes
-rw-r--r--boiler-monster/mr3020/.gitignore1
-rw-r--r--boiler-monster/mr3020/Makefile5
-rwxr-xr-xboiler-monster/mr3020/etc/init.d/stm327
l---------boiler-monster/mr3020/etc/rc.d/S99stm321
-rwxr-xr-xboiler-monster/mr3020/etc/stm32/startup50
-rw-r--r--boiler-monster/mr3020/stamp0
-rwxr-xr-xboiler-monster/mr3020/usr/bin/thermostat70
-rw-r--r--boiler-monster/original-pic-4.2.5/.gitignore6
-rw-r--r--boiler-monster/original-pic-4.2.5/16f88.lkr76
-rw-r--r--boiler-monster/original-pic-4.2.5/Makefile22
-rw-r--r--boiler-monster/original-pic-4.2.5/build.asm2
-rw-r--r--boiler-monster/original-pic-4.2.5/build.vbs77
-rw-r--r--boiler-monster/original-pic-4.2.5/ds1820.asm198
-rw-r--r--boiler-monster/original-pic-4.2.5/gateway.asm4524
-rw-r--r--boiler-monster/original-pic-4.2.5/license.txt58
-rw-r--r--boiler-monster/original-pic-4.2.5/ot-gateway.brdbin0 -> 37424 bytes
-rw-r--r--boiler-monster/original-pic-4.2.5/ot-gateway.schbin0 -> 210692 bytes
-rw-r--r--boiler-monster/original-pic-4.2.5/readme.txt43
-rw-r--r--boiler-monster/original-pic-4.2.5/selfprog.asm391
-rw-r--r--boiler-monster/pic/.gitignore8
-rw-r--r--boiler-monster/pic/16f88.lkr76
-rw-r--r--boiler-monster/pic/Makefile22
-rw-r--r--boiler-monster/pic/build.asm2
-rw-r--r--boiler-monster/pic/docs/otgw-big-sch.pngbin0 -> 77271 bytes
-rw-r--r--boiler-monster/pic/docs/pinout.txt33
-rw-r--r--boiler-monster/pic/gateway.asm296
-rw-r--r--boiler-monster/pic/selfprog.asm391
-rw-r--r--boiler-monster/stm32/.gitignore15
-rw-r--r--boiler-monster/stm32/Makefile8
-rw-r--r--boiler-monster/stm32/Makefile.include44
-rw-r--r--boiler-monster/stm32/Makefile.rules261
-rw-r--r--boiler-monster/stm32/app/1wire.c392
-rw-r--r--boiler-monster/stm32/app/1wire.h10
-rw-r--r--boiler-monster/stm32/app/Makefile113
-rw-r--r--boiler-monster/stm32/app/adc.c154
-rw-r--r--boiler-monster/stm32/app/boiler.ld31
-rw-r--r--boiler-monster/stm32/app/cmd.c55
-rw-r--r--boiler-monster/stm32/app/commit.c3
-rw-r--r--boiler-monster/stm32/app/ds1820.c93
-rw-r--r--boiler-monster/stm32/app/gdb.script2
-rw-r--r--boiler-monster/stm32/app/led.c101
-rw-r--r--boiler-monster/stm32/app/main.c134
-rw-r--r--boiler-monster/stm32/app/ot.c452
-rw-r--r--boiler-monster/stm32/app/ot_phy_rx.c177
-rw-r--r--boiler-monster/stm32/app/ot_phy_tx.c121
-rw-r--r--boiler-monster/stm32/app/pic.c52
-rw-r--r--boiler-monster/stm32/app/pins.h53
-rw-r--r--boiler-monster/stm32/app/pressure.c164
-rw-r--r--boiler-monster/stm32/app/project.h30
-rw-r--r--boiler-monster/stm32/app/prototypes.h106
-rw-r--r--boiler-monster/stm32/app/ring.c77
-rw-r--r--boiler-monster/stm32/app/ring.h13
-rw-r--r--boiler-monster/stm32/app/stdio.c77
-rw-r--r--boiler-monster/stm32/app/temp.c76
-rw-r--r--boiler-monster/stm32/app/ticker.c105
-rw-r--r--boiler-monster/stm32/app/usart.c175
-rw-r--r--boiler-monster/stm32/app/util.c14
-rw-r--r--boiler-monster/stm32/docs/pinout.txt24
-rw-r--r--boiler-monster/stm32/docs/pm0056.pdfbin0 -> 2098835 bytes
-rw-r--r--boiler-monster/stm32/docs/rm0008.pdfbin0 -> 13016697 bytes
-rw-r--r--boiler-monster/stm32/docs/stm32f103c8.pdfbin0 -> 1697666 bytes
-rw-r--r--boiler-monster/stm32/docs/stm32f103c8t6_pinout_voltage01.pngbin0 -> 679565 bytes
m---------boiler-monster/stm32/libopencm30
65 files changed, 9491 insertions, 0 deletions
diff --git a/boiler-monster/DOCS/IMG_20200514_1758578.jpg b/boiler-monster/DOCS/IMG_20200514_1758578.jpg
new file mode 100644
index 0000000..5f159c5
--- /dev/null
+++ b/boiler-monster/DOCS/IMG_20200514_1758578.jpg
Binary files differ
diff --git a/boiler-monster/DOCS/Opentherm Protocol v2-2.pdf b/boiler-monster/DOCS/Opentherm Protocol v2-2.pdf
new file mode 100644
index 0000000..47a7f59
--- /dev/null
+++ b/boiler-monster/DOCS/Opentherm Protocol v2-2.pdf
Binary files differ
diff --git a/boiler-monster/mr3020/.gitignore b/boiler-monster/mr3020/.gitignore
new file mode 100644
index 0000000..859afb1
--- /dev/null
+++ b/boiler-monster/mr3020/.gitignore
@@ -0,0 +1 @@
+stamp
diff --git a/boiler-monster/mr3020/Makefile b/boiler-monster/mr3020/Makefile
new file mode 100644
index 0000000..26efbc3
--- /dev/null
+++ b/boiler-monster/mr3020/Makefile
@@ -0,0 +1,5 @@
+STUFF=$(shell find etc usr \! -type d -print)
+
+stamp: ${STUFF}
+ tar cf - ${STUFF} | ssh boiler-monster "cd / && tar xvpf -"
+ touch $@
diff --git a/boiler-monster/mr3020/etc/init.d/stm32 b/boiler-monster/mr3020/etc/init.d/stm32
new file mode 100755
index 0000000..6dce9bf
--- /dev/null
+++ b/boiler-monster/mr3020/etc/init.d/stm32
@@ -0,0 +1,7 @@
+#!/bin/sh /etc/rc.common
+# Copyright (C) 2006 OpenWrt.org
+
+START=99
+boot() {
+ /etc/stm32/startup > /var/log/stm32.log
+}
diff --git a/boiler-monster/mr3020/etc/rc.d/S99stm32 b/boiler-monster/mr3020/etc/rc.d/S99stm32
new file mode 120000
index 0000000..a3b0774
--- /dev/null
+++ b/boiler-monster/mr3020/etc/rc.d/S99stm32
@@ -0,0 +1 @@
+../init.d/stm32 \ No newline at end of file
diff --git a/boiler-monster/mr3020/etc/stm32/startup b/boiler-monster/mr3020/etc/stm32/startup
new file mode 100755
index 0000000..9422745
--- /dev/null
+++ b/boiler-monster/mr3020/etc/stm32/startup
@@ -0,0 +1,50 @@
+#!/bin/sh
+
+logger -t stm32 "startup"
+
+LOCK=/var/lock/LCK..ttyATH0
+echo $$ > ${LOCK}.tmp
+
+#killall -9 ser2net
+
+FW="/etc/stm32/boiler.fw"
+TFW="/tmp/stm32.fw"
+
+PORT="/dev/ttyATH0"
+
+echo 5 > /sys/class/gpio/export
+echo out > /sys/class/gpio/gpio5/direction
+
+for i in $(seq 0 20); do
+ echo 1 > /sys/class/leds/tp-link\:green\:wps/brightness
+ echo 0 > /sys/class/leds/tp-link\:green\:wps/brightness
+
+ if stm32flash "${PORT}"; then
+ break
+ fi
+ sleep 1
+done
+
+if [ -z "$1" ]; then
+ logger -t stm32 "reading ROM"
+ if ! stm32flash -r "${TFW}" "${PORT}" ; then
+ exit 1
+ fi
+
+ if diff -q "${TFW}" "${FW}"; then
+ logger -t stm32 "Code in ROM matches file"
+ echo "Code in ROM matches file"
+ else
+ logger -t stm32 "Flashing"
+ echo "Code in ROM doesn't match file, flashing"
+ stm32flash -w "${FW}" "${PORT}"
+ fi
+else
+ echo "Flashing..."
+ stm32flash -w "$1" "${PORT}"
+fi
+
+logger -t stm32 "Booting"
+stm32flash -g 0 "${PORT}"
+
+rm -f ${LOCK}
diff --git a/boiler-monster/mr3020/stamp b/boiler-monster/mr3020/stamp
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/boiler-monster/mr3020/stamp
diff --git a/boiler-monster/mr3020/usr/bin/thermostat b/boiler-monster/mr3020/usr/bin/thermostat
new file mode 100755
index 0000000..1689964
--- /dev/null
+++ b/boiler-monster/mr3020/usr/bin/thermostat
@@ -0,0 +1,70 @@
+#!/bin/sh
+
+LOCK=/var/lock/LCK..ttyATH0
+M=10.32.139.1
+
+WATERS=0
+MAX=70
+
+# conventions
+# POWER is power to valve coil
+# OPEN is valve state
+# DELTA is temp difference
+# var1 is low set point
+# var2 is high set point
+# var3 is manual override
+
+# towel radiators
+
+for i in laundry_radiator bathroom_radiator; do
+ O="$(mosquitto_sub -t stat/$i/OPEN -h ${M} -W 1 -C 1)"
+
+ W=0
+ if [ "$O" == "1" ]; then
+ W=$MAX
+ fi
+ WATERS="$WATERS $W"
+
+ logger -t thermostat " $i O=$O W=$W"
+done
+
+#... others where we care about the delta
+
+for i in kstudy_radiator bedroom_radiator spare_bedroom_radiator dd_radiator1 dd_radiator2 dd_radiator3 hall_radiator kitchen_radiator music_room_radiator; do
+ O="$(mosquitto_sub -t stat/$i/OPEN -h ${M} -W 1 -C 1)"
+ D="$(mosquitto_sub -t stat/$i/DELTA -h ${M} -W 1 -C 1 | sed -e 's/\..*$//g') "
+
+ W=0
+ if [ "$O" == "1" ]; then
+ W=$[ $D * 3 + 40]
+ fi
+
+ logger -t thermostat " $i O=$O D=$D W=$W"
+
+ WATERS="$WATERS $W"
+done
+
+
+R=0
+for w in ${WATERS}; do
+ if [ $w -gt $R ]; then
+ R=$w
+ fi
+done
+
+if [ $R -gt $MAX ]; then
+ R=$MAX
+fi
+
+
+
+
+if [ ! -f "${LOCK}" ]; then
+ logger -t thermostat "Requesting water temp of $R"
+ printf "\nCH=%d\n" $R > /dev/ttyATH0
+else
+ logger -t thermostat "Interface is locked, so cant request water temp of $R"
+fi
+
+
+
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
--- /dev/null
+++ b/boiler-monster/original-pic-4.2.5/ot-gateway.brd
Binary files 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
--- /dev/null
+++ b/boiler-monster/original-pic-4.2.5/ot-gateway.sch
Binary files 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 <STX> 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 <ETX> character and waits up to 1 second for
+; a <STX> 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 <ETX>)
+ 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 <STX>
+ 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 <ETX>
+ 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
diff --git a/boiler-monster/pic/.gitignore b/boiler-monster/pic/.gitignore
new file mode 100644
index 0000000..f8ab154
--- /dev/null
+++ b/boiler-monster/pic/.gitignore
@@ -0,0 +1,8 @@
+*.err
+*.swp
+*.swo
+*.lst
+*.o
+*.hex
+*.map
+*.cod
diff --git a/boiler-monster/pic/16f88.lkr b/boiler-monster/pic/16f88.lkr
new file mode 100644
index 0000000..b2b3e95
--- /dev/null
+++ b/boiler-monster/pic/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/pic/Makefile b/boiler-monster/pic/Makefile
new file mode 100644
index 0000000..31ab11c
--- /dev/null
+++ b/boiler-monster/pic/Makefile
@@ -0,0 +1,22 @@
+AFLAGS=-k -p 16F88 -k
+LDFLAGS=-m -s 16f88.lkr
+
+PROG=gateway
+ASRCS=${PROG}.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/pic/build.asm b/boiler-monster/pic/build.asm
new file mode 100644
index 0000000..67d0747
--- /dev/null
+++ b/boiler-monster/pic/build.asm
@@ -0,0 +1,2 @@
+#define build "1"
+#define tstamp "17:59 20-10-2015"
diff --git a/boiler-monster/pic/docs/otgw-big-sch.png b/boiler-monster/pic/docs/otgw-big-sch.png
new file mode 100644
index 0000000..114e65b
--- /dev/null
+++ b/boiler-monster/pic/docs/otgw-big-sch.png
Binary files differ
diff --git a/boiler-monster/pic/docs/pinout.txt b/boiler-monster/pic/docs/pinout.txt
new file mode 100644
index 0000000..a3db674
--- /dev/null
+++ b/boiler-monster/pic/docs/pinout.txt
@@ -0,0 +1,33 @@
+
+headers:
+
+left
+
+GND - STM32
+GPIO_B <- STM32 B9
+GPIO_A <- STM32 B8
+5V -> STM32
+LED_D -> STM32 B7
+LED_C -> STM32 B6
+LED_B -> NC
+LED_A -> NC
+
+
+middle
+
+GND - NC
+NC - NC
+NC -
+PIC RX <- STM32 B10
+PIC TX -> STM32 B11
+*RESET <- STM32 B1
+
+
+right
+
+5V -> MR3020
+NC
+GND - MR3020
+
+
+
diff --git a/boiler-monster/pic/gateway.asm b/boiler-monster/pic/gateway.asm
new file mode 100644
index 0000000..b1c6324
--- /dev/null
+++ b/boiler-monster/pic/gateway.asm
@@ -0,0 +1,296 @@
+ 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
+
+;#######################################################################
+;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.
+
+;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
+
+ extern SelfProg
+
+;Variables accessible from all RAM banks
+ UDATA_SHR
+loopcounter res 1
+
+Bank0data UDATA 0x20
+rxbuffer res 16 ;Serial receive buffer
+
+;Variables for longer lasting storage
+rxpointer res 1
+txpointer res 1
+
+temp res 1
+
+Bank1data UDATA 0xA0
+
+Bank1values UDATA 0xE0
+
+Bank2data UDATA 0x110
+Bank2values UDATA 0x160
+
+Bank3data UDATA 0x190
+;Use all available RAM in bank 3 for the transmit buffer
+ constant TXBUFSIZE=80
+txbuffer res TXBUFSIZE
+Bank3values UDATA 0x1E0
+
+;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
+
+InterruptVector code 0x0004
+ retfie ;End of interrupt routine
+
+ package Interrupt
+
+;########################################################################
+; Main program
+;########################################################################
+
+ package Main
+Break tstf RCREG ;Clear the RCIF interrupt flag
+ bcf STATUS,RP1
+ bcf STATUS,RP0 ;bank 0
+ movlw 128
+ call Pause
+ pcall SelfProg ;Jump to the self-programming code
+Start bcf STATUS,RP1
+ bcf STATUS,RP0 ;bank 0
+ 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
+
+ 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 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
+ movlw b'00000011' ;Common reference mode
+ 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
+ bsf RCSTA,CREN ;Enable receive
+
+ ;Configure A/D converter
+ movlw b'01000001' ;A/D on, channel 0, Fosc/8
+ movwf ADCON0
+
+ ;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 b'11100110'
+ bsf STATUS,RP0
+ movwf CVRCON ;Set the reference voltage
+
+
+ clrf STATUS
+ bcf PIR2,CMIF ;Clear any comparator interrupt
+
+ movlw 0x41
+ movwf TXREG
+
+
+
+
+MainLoop clrwdt
+
+ ; Copy CMCON bits 7 and 6 from to PORTB bits 7 and 6 (comparitor outputs to LED_C and LED_D pins)
+ bsf STATUS,RP0
+ movfw CMCON
+ bcf STATUS,RP0
+
+ andlw b'11000000'
+ iorwf PORTB,F
+ iorlw b'00111111'
+ andwf PORTB,F
+
+ ; Copy PORTA bits 7 and 6 to PORTA bits 4 and 3 (GPIO inputs to transmitter outputs)
+ rrf PORTA,W
+ movwf temp
+ rrf temp,F
+ rrf temp,W
+
+ andlw b'00011000'
+ iorwf PORTA,F
+ iorlw b'11100111'
+ andwf PORTA,F
+
+ ; And port B bits 4 and 3 (LED_A and LED_B)
+
+ andlw b'00011000'
+ iorwf PORTB,F
+ iorlw b'11100111'
+ andwf PORTB,F
+
+
+ btfss PIR1,RCIF ;Activity on the serial receiver?
+ goto MainLoop
+ tstf RCREG
+
+ ;FERR is only relevant if RCIF is set
+ btfsc RCSTA,FERR ;Check for framing error (break)
+ goto Break
+ btfss RCSTA,OERR ;Check for overrun error
+ goto serial_recv
+ bcf RCSTA,CREN ;Procedure to clear an overrun error
+ bsf RCSTA,CREN ;Re-enable the serial interface
+serial_recv movfw RCREG
+ ;movwf TXREG
+ xorlw 0x4
+ skpnz
+ goto Break
+ goto MainLoop
+
+Pause clrwdt
+ btfsc PIR1,RCIF
+ return
+ btfss INTCON,TMR0IF
+ goto Pause
+ bcf INTCON,TMR0IF
+ addlw -1
+ skpz
+ goto Pause
+ return
+
+
+ end
diff --git a/boiler-monster/pic/selfprog.asm b/boiler-monster/pic/selfprog.asm
new file mode 100644
index 0000000..9e18769
--- /dev/null
+++ b/boiler-monster/pic/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 <STX> 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 <ETX> character and waits up to 1 second for
+; a <STX> 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 <ETX>)
+ 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 <STX>
+ 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 <ETX>
+ 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
diff --git a/boiler-monster/stm32/.gitignore b/boiler-monster/stm32/.gitignore
new file mode 100644
index 0000000..64532f4
--- /dev/null
+++ b/boiler-monster/stm32/.gitignore
@@ -0,0 +1,15 @@
+*.o
+*.d
+*.map
+*.hex
+*.elf
+*.swp
+*~
+*.dfu
+plot/data
+plot/script
+*.bin
+scmversion
+commit.h
+opencm3.build.stamp
+*.orig
diff --git a/boiler-monster/stm32/Makefile b/boiler-monster/stm32/Makefile
new file mode 100644
index 0000000..e1cd497
--- /dev/null
+++ b/boiler-monster/stm32/Makefile
@@ -0,0 +1,8 @@
+
+default:
+ make -C libopencm3
+ make -C app
+
+clean:
+ make -C libopencm3 clean
+ make -C app clean
diff --git a/boiler-monster/stm32/Makefile.include b/boiler-monster/stm32/Makefile.include
new file mode 100644
index 0000000..4f5cbd9
--- /dev/null
+++ b/boiler-monster/stm32/Makefile.include
@@ -0,0 +1,44 @@
+##
+## This file is part of the libopencm3 project.
+##
+## Copyright (C) 2009 Uwe Hermann <uwe@hermann-uwe.de>
+## Copyright (C) 2010 Piotr Esden-Tempski <piotr@esden.net>
+##
+## This library is free software: you can redistribute it and/or modify
+## it under the terms of the GNU Lesser General Public License as published by
+## the Free Software Foundation, either version 3 of the License, or
+## (at your option) any later version.
+##
+## This library is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+## GNU Lesser General Public License for more details.
+##
+## You should have received a copy of the GNU Lesser General Public License
+## along with this library. If not, see <http://www.gnu.org/licenses/>.
+##
+
+LIBNAME = opencm3_stm32f1
+DEFS = -DSTM32F1
+
+FP_FLAGS ?= -msoft-float
+ARCH_FLAGS = -mthumb -mcpu=cortex-m3 $(FP_FLAGS) -mfix-cortex-m3-ldrd
+
+################################################################################
+# OpenOCD specific variables
+
+OOCD ?= openocd
+OOCD_INTERFACE ?= interface/stlink-v2.cfg
+OOCD_BOARD ?= target/stm32f1x.cfg
+
+################################################################################
+# Black Magic Probe specific variables
+# Set the BMP_PORT to a serial port and then BMP is used for flashing
+BMP_PORT ?=
+
+################################################################################
+# texane/stlink specific variables
+#STLINK_PORT ?= :4242
+
+
+include ../Makefile.rules
diff --git a/boiler-monster/stm32/Makefile.rules b/boiler-monster/stm32/Makefile.rules
new file mode 100644
index 0000000..a723e6f
--- /dev/null
+++ b/boiler-monster/stm32/Makefile.rules
@@ -0,0 +1,261 @@
+#
+## This file is part of the libopencm3 project.
+##
+## Copyright (C) 2009 Uwe Hermann <uwe@hermann-uwe.de>
+## Copyright (C) 2010 Piotr Esden-Tempski <piotr@esden.net>
+## Copyright (C) 2013 Frantisek Burian <BuFran@seznam.cz>
+##
+## This library is free software: you can redistribute it and/or modify
+## it under the terms of the GNU Lesser General Public License as published by
+## the Free Software Foundation, either version 3 of the License, or
+## (at your option) any later version.
+##
+## This library is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+## GNU Lesser General Public License for more details.
+##
+## You should have received a copy of the GNU Lesser General Public License
+## along with this library. If not, see <http://www.gnu.org/licenses/>.
+##
+
+# Be silent per default, but 'make V=1' will show all compiler calls.
+ifneq ($(V),1)
+Q := @
+NULL := 2>/dev/null
+endif
+
+###############################################################################
+# Executables
+
+PREFIX ?= arm-none-eabi
+
+CC := $(PREFIX)-gcc
+CXX := $(PREFIX)-g++
+LD := $(PREFIX)-gcc
+AR := $(PREFIX)-ar
+AS := $(PREFIX)-as
+OBJCOPY := $(PREFIX)-objcopy
+OBJDUMP := $(PREFIX)-objdump
+GDB := $(PREFIX)-gdb
+STFLASH = $(shell which st-flash)
+STYLECHECK := /checkpatch.pl
+STYLECHECKFLAGS := --no-tree -f --terse --mailback
+STYLECHECKFILES := $(shell find . -name '*.[ch]')
+
+
+###############################################################################
+# Source files
+
+LDSCRIPT ?= $(BINARY).ld
+
+#OBJS += $(BINARY).o
+
+
+ifeq ($(strip $(OPENCM3_DIR)),)
+# user has not specified the library path, so we try to detect it
+
+# where we search for the library
+LIBPATHS := ./libopencm3 ../libopencm3
+
+OPENCM3_DIR := $(wildcard $(LIBPATHS:=/locm3.sublime-project))
+OPENCM3_DIR := $(firstword $(dir $(OPENCM3_DIR)))
+
+ifeq ($(strip $(OPENCM3_DIR)),)
+$(warning Cannot find libopencm3 library in the standard search paths.)
+$(error Please specify it through OPENCM3_DIR variable!)
+endif
+endif
+
+ifeq ($(V),1)
+$(info Using $(OPENCM3_DIR) path to library)
+endif
+
+INCLUDE_DIR = $(OPENCM3_DIR)/include
+LIB_DIR = $(OPENCM3_DIR)/lib
+SCRIPT_DIR = $(OPENCM3_DIR)/scripts
+
+###############################################################################
+# C flags
+
+CFLAGS += -Os -g
+CFLAGS += -Wextra -Wimplicit-function-declaration
+CFLAGS += -Wmissing-prototypes -Wstrict-prototypes
+CFLAGS += -fno-common -ffunction-sections -fdata-sections
+
+###############################################################################
+# C++ flags
+
+CXXFLAGS += -Os -g
+CXXFLAGS += -Wextra -Wshadow -Wredundant-decls -Weffc++
+CXXFLAGS += -fno-common -ffunction-sections -fdata-sections
+
+###############################################################################
+# C & C++ preprocessor common flags
+
+CPPFLAGS += -MD
+CPPFLAGS += -Wall -Wundef
+
+INCLUDES = -I$(INCLUDE_DIR)
+DEFINES = $(DEFS)
+
+CPPFLAGS += $(INCLUDES) $(DEFINES)
+
+###############################################################################
+# Linker flags
+
+LDFLAGS += --static -nostartfiles
+LDFLAGS += -L$(LIB_DIR)
+LDFLAGS += -T$(LDSCRIPT)
+LDFLAGS += -Wl,-Map=$(*).map
+LDFLAGS += -Wl,--gc-sections
+ifeq ($(V),99)
+LDFLAGS += -Wl,--print-gc-sections
+endif
+
+###############################################################################
+# Used libraries
+
+LDLIBS += -l$(LIBNAME)
+LDLIBS += -Wl,--start-group -lc -lgcc -lnosys -Wl,--end-group
+
+###############################################################################
+###############################################################################
+###############################################################################
+
+.SUFFIXES: .elf .bin .hex .srec .list .map .images .dfu
+.SECONDEXPANSION:
+.SECONDARY:
+
+all: elf
+
+
+elf: $(BINARY).elf
+bin: $(BINARY).bin
+hex: $(BINARY).hex
+srec: $(BINARY).srec
+list: $(BINARY).list
+
+images: $(BINARY).images
+flash: $(BINARY).flash
+
+%.images: %.bin %.hex %.srec %.list %.map %.dfu
+ @#printf "*** $* images generated ***\n"
+
+%.bin: %.elf
+ @#printf " OBJCOPY $(*).bin\n"
+ $(Q)$(OBJCOPY) -Obinary $(*).elf $(*).bin
+
+%.hex: %.elf
+ @#printf " OBJCOPY $(*).hex\n"
+ $(Q)$(OBJCOPY) -Oihex $(*).elf $(*).hex
+
+%.dfu: %.elf
+ @#printf " OBJCOPY $(*).dfu\n"
+ $(Q)$(OBJCOPY) -Obinary $(*).elf $(*).dfu
+
+%.srec: %.elf
+ @#printf " OBJCOPY $(*).srec\n"
+ $(Q)$(OBJCOPY) -Osrec $(*).elf $(*).srec
+
+%.list: %.elf
+ @#printf " OBJDUMP $(*).list\n"
+ $(Q)$(OBJDUMP) -S $(*).elf > $(*).list
+
+fish:
+ echo %.elf %.map: $(OBJS) $(LDSCRIPT) $(LIB_DIR)/lib$(LIBNAME).a
+ echo $(BINARY).elf
+
+%.elf %.map: $(OBJS) $(LDSCRIPT) $(LIB_DIR)/lib$(LIBNAME).a
+ @#printf " LD $(*).elf\n"
+ $(Q)$(LD) $(LDFLAGS) $(ARCH_FLAGS) $(OBJS) $(LDLIBS) -o $(*).elf
+
+%.o: %.c $(OPENCM3_DIR)/../opencm3.build.stamp
+ @#printf " CC $(*).c\n"
+ $(Q)$(CC) $(CFLAGS) $(CPPFLAGS) $(ARCH_FLAGS) -o $(*).o -c $(*).c
+
+%.o: %.cxx $(OPENCM3_DIR)/../opencm3.build.stamp
+ @#printf " CXX $(*).cxx\n"
+ $(Q)$(CXX) $(CXXFLAGS) $(CPPFLAGS) $(ARCH_FLAGS) -o $(*).o -c $(*).cxx
+
+%.o: %.cpp $(OPENCM3_DIR)/../opencm3.build.stamp
+ @#printf " CXX $(*).cpp\n"
+ $(Q)$(CXX) $(CXXFLAGS) $(CPPFLAGS) $(ARCH_FLAGS) -o $(*).o -c $(*).cpp
+
+clean:
+ @#printf " CLEAN\n"
+ $(Q)$(RM) *.o *.d *.elf *.bin *.hex *.srec *.list *.map *~ *.dfu ${EXTRACLEANS}
+
+stylecheck: $(STYLECHECKFILES:=.stylecheck)
+styleclean: $(STYLECHECKFILES:=.styleclean)
+
+# the cat is due to multithreaded nature - we like to have consistent chunks of text on the output
+%.stylecheck: %
+ $(Q)$(SCRIPT_DIR)$(STYLECHECK) $(STYLECHECKFLAGS) $* > $*.stylecheck; \
+ if [ -s $*.stylecheck ]; then \
+ cat $*.stylecheck; \
+ else \
+ rm -f $*.stylecheck; \
+ fi;
+
+%.styleclean:
+ $(Q)rm -f $*.stylecheck;
+
+
+%.stlink-flash: %.bin
+ @printf " FLASH $<\n"
+ $(Q)$(STFLASH) write $(*).bin 0x8000000
+
+ifeq ($(STLINK_PORT),)
+ifeq ($(BMP_PORT),)
+ifeq ($(OOCD_SERIAL),)
+%.flash: %.hex
+ @printf " FLASH $<\n"
+ @# IMPORTANT: Don't use "resume", only "reset" will work correctly!
+ $(Q)$(OOCD) -f $(OOCD_INTERFACE) \
+ -f $(OOCD_BOARD) \
+ -c "init" -c "reset init" \
+ -c "flash write_image erase $(*).hex" \
+ -c "reset" \
+ -c "shutdown" $(NULL)
+else
+%.flash: %.hex
+ @printf " FLASH $<\n"
+ @# IMPORTANT: Don't use "resume", only "reset" will work correctly!
+ $(Q)$(OOCD) -f $(OOCD_INTERFACE) \
+ -f $(OOCD_BOARD) \
+ -c "ft2232_serial $(OOCD_SERIAL)" \
+ -c "init" -c "reset init" \
+ -c "flash write_image erase $(*).hex" \
+ -c "reset" \
+ -c "shutdown" $(NULL)
+endif
+else
+%.flash: %.elf
+ @printf " GDB $(*).elf (flash)\n"
+ $(Q)$(GDB) --batch \
+ -ex 'target extended-remote $(BMP_PORT)' \
+ -x $(SCRIPT_DIR)/black_magic_probe_flash.scr \
+ $(*).elf
+endif
+else
+%.flash: %.elf
+ @printf " GDB $(*).elf (flash)\n"
+ $(Q)$(GDB) --batch \
+ -ex 'target extended-remote $(STLINK_PORT)' \
+ -x $(SCRIPT_DIR)/stlink_flash.scr \
+ $(*).elf
+endif
+
+.PHONY: images clean stylecheck styleclean elf bin hex srec list
+
+-include $(OBJS:.o=.d)
+
+
+$(LIB_DIR)/lib$(LIBNAME).a: $(OPENCM3_DIR)/../opencm3.build.stamp
+
+
+$(OPENCM3_DIR)/../opencm3.build.stamp:
+ ${MAKE} -C ${OPENCM3_DIR}
+ touch $@
+
diff --git a/boiler-monster/stm32/app/1wire.c b/boiler-monster/stm32/app/1wire.c
new file mode 100644
index 0000000..c374c5c
--- /dev/null
+++ b/boiler-monster/stm32/app/1wire.c
@@ -0,0 +1,392 @@
+#include "project.h"
+
+#define USART2_TX GPIO_USART2_TX
+#define USART2_TX_PORT GPIOA
+
+#define BIT_ZERO '0'
+#define BIT_ONE '1'
+#define BIT_UNKNOWN 'U'
+#define BIT_CONFLICT 'C'
+
+
+static int poke;
+static volatile unsigned exchange_timeout;
+
+void onewire_tick (void)
+{
+ static unsigned ticker;
+
+ if (exchange_timeout)
+ exchange_timeout--;
+
+
+ ticker++;
+
+ if (ticker < MS_TO_TICKS (5000))
+ return;
+
+ ticker = 0;
+ poke = 1;
+
+
+}
+
+
+static unsigned usart_send_ready (uint32_t usart)
+{
+ return (USART_SR (usart) & USART_SR_TXE);
+}
+
+
+static unsigned usart_recv_ready (uint32_t usart)
+{
+ return (USART_SR (usart) & USART_SR_RXNE);
+}
+
+
+
+static int onewire_exchange (int tx, unsigned timeout)
+{
+ int rx;
+ (void) USART_SR (USART2);
+ (void) usart_recv (USART2);
+
+
+ exchange_timeout = MS_TO_TICKS (timeout);
+
+ while (!usart_send_ready (USART2))
+ if (!exchange_timeout)
+ return -1;
+
+ usart_send (USART2, tx);
+
+ exchange_timeout = MS_TO_TICKS (timeout);
+
+ while (!usart_recv_ready (USART2))
+ if (!exchange_timeout)
+ return -1;
+
+
+ rx = usart_recv (USART2);
+
+ return rx;
+
+}
+
+/* returns 1 if nothing on bus or error */
+int
+onewire_reset (void)
+{
+ int ret;
+
+ usart_set_baudrate (USART2, 9600);
+ delay_ms (1);
+ ret = onewire_exchange (0xf0, 60);
+ usart_set_baudrate (USART2, 115200);
+ delay_ms (1);
+
+ return (ret == 0xf0);
+}
+
+static void
+onewire_one (void)
+{
+ onewire_exchange (0xff, 3);
+}
+
+static void
+onewire_zero (void)
+{
+ onewire_exchange (0x00, 3);
+}
+
+static int
+onewire_read (void)
+{
+ uint8_t rx;
+
+ rx = onewire_exchange (0xff, 3);
+
+ return (rx == 0xff);
+}
+
+
+void
+onewire_write_byte (uint8_t v)
+{
+ int c;
+
+
+ for (c = 0; c < 8; ++c) {
+ if (v & 1)
+ onewire_one();
+ else
+ onewire_zero();
+
+ v >>= 1;
+ }
+}
+
+uint8_t
+onewire_read_byte (void)
+{
+ uint8_t v = 0;
+ int c;
+
+
+ for (c = 0; c < 8; ++c) {
+ v >>= 1;
+
+ if (onewire_read())
+ v |= 0x80;
+ }
+
+
+ return v;
+}
+
+
+
+void
+onewire_read_bytes (uint8_t *buf, int n)
+{
+ while (n--)
+ * (buf++) = onewire_read_byte();
+}
+
+
+void
+onewire_write_bytes (const uint8_t *buf, int n)
+{
+ while (n--)
+ onewire_write_byte (* (buf++));
+}
+
+
+int onewire_select (const Onewire_addr *a)
+{
+ if (!a)
+ onewire_write_byte (ONEWIRE_SKIP_ROM);
+ else {
+ onewire_write_byte (ONEWIRE_MATCH_ROM);
+ onewire_write_bytes (a->a, 8);
+ }
+
+ return 0;
+}
+
+
+int onewire_reset_and_select (const Onewire_addr *a)
+{
+ if (onewire_reset())
+ return 1;
+
+ if (onewire_select (a))
+ return 1;
+
+ return 0;
+}
+
+
+
+int onewire_wait_complete (unsigned timeout)
+{
+ while (! (onewire_read())) {
+ delay_ms (10);
+
+ if (! (timeout--))
+ return 1;
+ }
+
+ return 0;
+}
+
+
+
+int
+onewire_check_crc (uint8_t *buf, int n, uint8_t v)
+{
+ uint8_t crc = 0;
+ int i;
+
+ while (n--) {
+ uint8_t v = * (buf++);
+
+ for (i = 0; i < 8; ++i) {
+ uint8_t mix = (crc ^ v) & 0x01;
+ crc >>= 1;
+
+ if (mix)
+ crc ^= 0x8C;
+
+ v >>= 1;
+ }
+ }
+
+ return ! (crc == v);
+
+}
+
+static int onewire_conduct_search (uint8_t *bits)
+{
+ unsigned i, ir, r;
+
+ if (onewire_reset())
+ return -1;
+
+ onewire_write_byte (ONEWIRE_SEARCH_ROM);
+
+
+ for (i = 0; i < 64; ++i) {
+
+ r = onewire_read();
+ ir = onewire_read();
+
+#if 0
+
+ if ((i == 27) || (i == 24) || (i == 39))
+ ir = r = 0;
+
+#endif
+
+ switch (bits[i]) {
+ case BIT_UNKNOWN:
+
+ if (!r && ir) { /* Zero */
+ bits[i] = BIT_ZERO;
+ onewire_zero();
+ } else if (r && !ir) { /*One */
+ bits[i] = BIT_ONE;
+ onewire_one();
+ } else if (r && ir) { /*Nothing here */
+ //MEH;
+ return -1;
+ } else if (!r && !ir) { /*Both */
+ bits[i] = BIT_CONFLICT;
+ onewire_zero();
+ }
+
+ break;
+
+ case BIT_CONFLICT:
+ if (!r && !ir) /*Both */
+ onewire_zero();
+ else {
+ //MEH;
+ return -1;
+ }
+
+ break;
+
+ case BIT_ZERO:
+ if (!r)
+ onewire_zero();
+ else {
+ //MEH;
+ return -1;
+ }
+
+ break;
+
+ case BIT_ONE:
+ if (!ir)
+ onewire_one();
+ else {
+ //MEH;
+ return -1;
+ }
+
+ break;
+ }
+ }
+
+ return 0;
+}
+
+static int onewire_next (uint8_t *bits)
+{
+ unsigned i;
+
+ for (i = 0; i < 64; ++i) {
+
+ if (bits[63 - i] == BIT_CONFLICT) {
+ bits[63 - i] = BIT_ONE;
+ return 1;
+ }
+
+ bits[63 - i] = BIT_UNKNOWN;
+ }
+
+ return 0;
+}
+
+static void onewire_bits_to_a (uint8_t *bits, Onewire_addr *a)
+{
+ unsigned i, j, c;
+
+ for (i = 0, j = 0; i < 8; ++i) {
+
+ a->a[i] = 0;
+
+ for (c = 1; c < 0x100; c <<= 1, ++j) {
+ if (bits[j] == BIT_ONE)
+ a->a[i] |= c;
+ }
+ }
+}
+
+int onewire_search (void)
+{
+ uint8_t bits[64];
+ Onewire_addr a;
+ int r, c;
+
+ delay_ms (2000);
+
+ memset (bits, BIT_UNKNOWN, sizeof (bits));
+
+ do {
+ r = onewire_conduct_search (bits);
+
+ if (!r) {
+ onewire_bits_to_a (bits, &a);
+ c = onewire_check_crc (&a.a[0], 7, a.a[7]);
+
+ if (!c) {
+ printf ("QO: {0x%02x,0x%02x,0x%02x,0x%02x,0x%02x,0x%02x,0x%02x,0x%02x}\r\n",
+ a.a[0],
+ a.a[1],
+ a.a[2],
+ a.a[3],
+ a.a[4],
+ a.a[5],
+ a.a[6],
+ a.a[7]);
+ }
+
+ }
+
+ } while (onewire_next (bits));
+
+
+ return 0;
+}
+
+
+
+void
+onewire_init (void)
+{
+ MAP_AF_OD (USART2_TX);
+
+ usart_set_baudrate (USART2, 115200);
+ usart_set_databits (USART2, 8);
+ usart_set_stopbits (USART2, USART_STOPBITS_1);
+ usart_set_parity (USART2, USART_PARITY_NONE);
+ usart_set_flow_control (USART2, USART_FLOWCONTROL_NONE);
+ usart_set_mode (USART2, USART_MODE_TX_RX);
+
+ USART_CR3 (USART2) |= USART_CR3_HDSEL;
+
+ usart_enable (USART2);
+}
diff --git a/boiler-monster/stm32/app/1wire.h b/boiler-monster/stm32/app/1wire.h
new file mode 100644
index 0000000..4a31418
--- /dev/null
+++ b/boiler-monster/stm32/app/1wire.h
@@ -0,0 +1,10 @@
+#define ONEWIRE_SKIP_ROM 0xcc
+#define ONEWIRE_SEARCH_ROM 0xf0
+#define ONEWIRE_MATCH_ROM 0x55
+
+#define ONEWIRE_ADDR_LEN 8
+typedef struct {
+ uint8_t a[ONEWIRE_ADDR_LEN];
+} Onewire_addr;
+
+
diff --git a/boiler-monster/stm32/app/Makefile b/boiler-monster/stm32/app/Makefile
new file mode 100644
index 0000000..23b4a74
--- /dev/null
+++ b/boiler-monster/stm32/app/Makefile
@@ -0,0 +1,113 @@
+##
+## This file is part of the libopencm3 project.
+##
+## Copyright (C) 2009 Uwe Hermann <uwe@hermann-uwe.de>
+##
+## This library is free software: you can redistribute it and/or modify
+## it under the terms of the GNU Lesser General Public License as published by
+## the Free Software Foundation, either version 3 of the License, or
+## (at your option) any later version.
+##
+## This library is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+## GNU Lesser General Public License for more details.
+##
+## You should have received a copy of the GNU Lesser General Public License
+## along with this library. If not, see <http://www.gnu.org/licenses/>.
+##
+
+CPROTO=cproto
+PROG=boiler
+
+CSRCS=main.c ot.c ot_phy_rx.c ot_phy_tx.c led.c ticker.c usart.c ring.c stdio.c util.c commit.c cmd.c pic.c 1wire.c temp.c ds1820.c pressure.c adc.c
+HSRCS=pins.h project.h ring.h
+
+EXTRACLEANS=*.orig scmversion commit.h
+
+TARGET=boiler-monster
+
+V=1
+default: ${PROG}.fw.bin
+ rsync -vaP ${PROG}.fw.bin ${TARGET}:/tmp/boiler.fw
+ ssh ${TARGET} /etc/stm32/startup /tmp/boiler.fw
+
+
+install: ${PROG}.fw.bin
+ rsync -vaP ${PROG}.fw.bin ${TARGET}:/etc/stm32/boiler.fw
+
+
+128k_of_ff.bin:
+ dd if=/dev/zero bs=128k count=1 | tr '\0' '\377' > $@
+
+
+${PROG}.fw.bin:${PROG}.bin 128k_of_ff.bin
+ cat ${PROG}.bin 128k_of_ff.bin | dd of=$@ bs=128k iflag=fullblock count=1
+
+
+program: ${PROG}.elf
+ echo halt | nc -t localhost 4444
+ echo flash write_image erase ${PWD}/$< 0x2000 | nc -t localhost 4444
+ echo reset halt | nc -t localhost 4444
+ RV=$$(arm-none-eabi-objdump --disassemble=reset_handler $< | awk '/^0/ { print "0x"$$1 }') && \
+ echo resume $${RV} | nc -t localhost 4444 && \
+ echo reset vector at $${RV}
+
+run:${PROG}.run
+
+%.run: %.hex
+ RV=$$(arm-none-eabi-objdump --disassemble=reset_handler $(*).elf | awk '/^0/ { print "0x"$$1 }') && \
+ $(Q)$(OOCD) -f $(OOCD_INTERFACE) \
+ -f $(OOCD_BOARD) \
+ -c "init" -c "reset init" \
+ -c "flash write_image erase $(*).hex" \
+ -c "reset halt" \
+ -c "resume $${RV}" \
+ -c "shutdown" $(NULL) && \
+ echo reset_handler is at $${RV}
+
+
+
+
+BINARY = ${PROG}
+OBJS = ${CSRCS:%.c=%.o}
+
+include ../Makefile.include
+
+CFLAGS+=-Wno-redundant-decls -Wno-unused-parameter -DDEBUG
+
+INCLUDES += -I..
+
+protos:
+ echo -n > prototypes.h
+ ${CPROTO} $(INCLUDES) $(DEFINES) -e -v ${CSRCS} > prototypes.h.tmp
+ mv -f prototypes.h.tmp prototypes.h
+
+server:
+ $(OOCD) -f $(OOCD_INTERFACE) \
+ -f $(OOCD_BOARD)
+
+debug: ${PROG}.elf
+ ${PREFIX}-gdb -x gdb.script ${PROG}.elf
+
+tidy:
+ astyle -A3 -s2 --attach-extern-c -L -c -w -Y -m0 -f -p -H -U -k3 -xj -xd ${CSRCS} ${HSRCS}
+
+HEAD_REF = $(shell git rev-parse --verify --short HEAD)
+SCM_DIRTY = $(shell if ! git diff-index --quiet HEAD --; then echo "+dirty"; fi)
+SCMVERSION = ${HEAD_REF}${SCM_DIRTY}
+
+scmversion:
+ echo -n ${SCMVERSION} > $@
+
+.PHONY: scmversion
+
+commit.o: commit.h
+
+commit.h: scmversion
+ echo -n '#define COMMIT_ID "' > $@
+ echo -n `cat scmversion` >> $@
+ echo '"' >> $@
+
+
+
diff --git a/boiler-monster/stm32/app/adc.c b/boiler-monster/stm32/app/adc.c
new file mode 100644
index 0000000..2cfc4e3
--- /dev/null
+++ b/boiler-monster/stm32/app/adc.c
@@ -0,0 +1,154 @@
+#include "project.h"
+
+#define T do { printf("%s:%d\r\n",__FILE__,__LINE__); } while (0)
+
+void adc_dump (void)
+{
+ printf ("ADC_SR %x ADC_CR1 %x ADC_CR2 %x\r\n", (unsigned) ADC_SR (ADC1),
+ (unsigned) ADC_CR1 (ADC1),
+ (unsigned) ADC_CR2 (ADC1));
+
+}
+
+
+volatile unsigned timeout;
+
+
+void adc_tick (void)
+{
+ if (timeout) timeout--;
+}
+
+
+static int adc_wait (volatile uint32_t *reg, uint32_t wait_while_set, uint32_t wait_while_clear, unsigned ms)
+{
+ timeout = MS_TO_TICKS (ms);
+
+
+ while ((*reg) & wait_while_set)
+ if (!timeout) {
+ printf ("QADC timeout\r\n");
+ return -1;
+ }
+
+
+ while ((~ (*reg)) & wait_while_clear)
+ if (!timeout) {
+ printf ("QADC timeout\r\n");
+ return -1;
+ }
+
+
+ return 0;
+}
+
+
+
+int adc_calibrate (void)
+{
+ adc_off (ADC1);
+ adc_power_on (ADC1);
+ delay_ms (5);
+
+ ADC_CR2 (ADC1) |= ADC_CR2_RSTCAL;
+
+ if (adc_wait (&ADC_CR2 (ADC1), ADC_CR2_CAL, 0, 2)) return -1;
+
+ ADC_CR2 (ADC1) |= ADC_CR2_CAL;
+
+ if (adc_wait (&ADC_CR2 (ADC1), ADC_CR2_CAL, 0, 2)) return -1;
+
+ return 0;
+}
+
+
+
+int adc_convert_start (unsigned channel)
+{
+ uint8_t ch = channel;
+
+ adc_set_regular_sequence (ADC1, 1, &ch);
+
+ /* Start conversion on regular channels. */
+ ADC_CR2 (ADC1) |= ADC_CR2_SWSTART;
+
+#if 0
+ if (adc_wait (&ADC_CR2 (ADC1), ADC_CR2_SWSTART, 0, 2))
+ return -1;
+#endif
+
+ return 0;
+}
+
+
+
+int adc_convert_done(void)
+{
+
+ if (ADC_SR(ADC1) & ADC_SR_EOC)
+ return 1;
+
+ return 0;
+}
+
+unsigned adc_convert_get(void)
+{
+ return adc_read_regular (ADC1);
+}
+
+
+unsigned adc_convert (unsigned channel)
+{
+ uint8_t ch = channel;
+
+ adc_calibrate();
+
+ adc_set_regular_sequence (ADC1, 1, &ch);
+
+ /* Start conversion on regular channels. */
+ ADC_CR2 (ADC1) |= ADC_CR2_SWSTART;
+
+ if (adc_wait (&ADC_CR2 (ADC1), ADC_CR2_SWSTART, 0, 2))
+ return 0;
+
+ if (adc_wait (&ADC_SR (ADC1), 0, ADC_SR_EOC, 2))
+ return 0;
+
+ return adc_read_regular (ADC1);
+}
+
+void adc_init (void)
+{
+ /* main set ADC clock is 9Mhz */
+
+ adc_dump();
+
+ adc_off (ADC1);
+ rcc_periph_clock_enable (RCC_ADC1);
+#if 0
+ rcc_peripheral_reset (&RCC_APB2RSTR, RCC_APB2RSTR_ADC1RST);
+ rcc_peripheral_clear_reset (&RCC_APB2RSTR, RCC_APB2RSTR_ADC1RST);
+#endif
+
+ adc_set_dual_mode (ADC_CR1_DUALMOD_IND);
+ adc_disable_scan_mode (ADC1);
+ adc_enable_temperature_sensor (ADC1);
+ adc_set_single_conversion_mode (ADC1);
+
+ adc_set_sample_time (ADC1, ADC_CHANNEL0, ADC_SMPR_SMP_239DOT5CYC);
+ adc_set_sample_time (ADC1, ADC_CHANNEL17, ADC_SMPR_SMP_239DOT5CYC); /*Want 17.1uS , which is 154 cycles */
+ adc_enable_external_trigger_regular (ADC1, ADC_CR2_EXTSEL_SWSTART);
+
+ adc_set_right_aligned (ADC1);
+
+
+ adc_dump();
+
+ adc_calibrate();
+
+ adc_dump();
+
+
+
+}
+
diff --git a/boiler-monster/stm32/app/boiler.ld b/boiler-monster/stm32/app/boiler.ld
new file mode 100644
index 0000000..e15beca
--- /dev/null
+++ b/boiler-monster/stm32/app/boiler.ld
@@ -0,0 +1,31 @@
+/*
+ * This file is part of the libopencm3 project.
+ *
+ * Copyright (C) 2009 Uwe Hermann <uwe@hermann-uwe.de>
+ *
+ * This library is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/* Linker script for Olimex STM32-H103 (STM32F103RBT6, 128K flash, 20K RAM). */
+
+/* Define memory regions. */
+MEMORY
+{
+ rom (rx) : ORIGIN = 0x08000000, LENGTH = 128K
+ ram (rwx) : ORIGIN = 0x20000000, LENGTH = 20K
+}
+
+/* Include the common ld script. */
+INCLUDE libopencm3_stm32f1.ld
+
diff --git a/boiler-monster/stm32/app/cmd.c b/boiler-monster/stm32/app/cmd.c
new file mode 100644
index 0000000..7e1975a
--- /dev/null
+++ b/boiler-monster/stm32/app/cmd.c
@@ -0,0 +1,55 @@
+#include "project.h"
+
+
+static void cmd_dispatch (char *cmd)
+{
+
+ printf ("Q received cmd %s\r\n", cmd);
+
+ if (!strncmp (cmd, "CH=", 3)) {
+ ot_override_ch = atoi (cmd + 3);
+ printf ("Q CH override set to %d\r\n", ot_override_ch);
+ }
+
+
+ if (!strncmp (cmd, "DHW=", 4)) {
+ ot_override_dhw = atoi (cmd + 4);
+ printf ("Q DHW override set to %d\r\n", ot_override_dhw);
+ }
+
+ if (!strcmp (cmd, "PIC")) {
+ ot_override_dhw = atoi (cmd + 4);
+ printf ("Q Entering PIC mode, reset to leave\r\n");
+ pic_passthru();
+ }
+
+}
+
+
+void cmd_usart_dispatch (void)
+{
+ static char cmd[16];
+ static unsigned cmd_ptr;
+
+ uint8_t c;
+
+ if (ring_read_byte (&rx1_ring, &c))
+ return;
+
+
+ if ((c == '\n') || (c == '\r')) {
+ if (cmd_ptr)
+ cmd_dispatch (cmd);
+
+ cmd_ptr = 0;
+ return;
+ }
+
+
+ if (cmd_ptr < (sizeof (cmd) - 1)) {
+ cmd[cmd_ptr++] = c;
+ cmd[cmd_ptr] = 0;
+ }
+}
+
+
diff --git a/boiler-monster/stm32/app/commit.c b/boiler-monster/stm32/app/commit.c
new file mode 100644
index 0000000..207448f
--- /dev/null
+++ b/boiler-monster/stm32/app/commit.c
@@ -0,0 +1,3 @@
+#include "commit.h"
+
+char scm_version[] = COMMIT_ID;
diff --git a/boiler-monster/stm32/app/ds1820.c b/boiler-monster/stm32/app/ds1820.c
new file mode 100644
index 0000000..200f4b7
--- /dev/null
+++ b/boiler-monster/stm32/app/ds1820.c
@@ -0,0 +1,93 @@
+#include "project.h"
+
+#define DS1820_READ_SCRATCHPAD 0xbe
+#define DS1820_CONVERT_T 0x44
+
+
+#define TIMEOUT 150
+
+
+
+static unsigned extract_leu16 (uint8_t *d)
+{
+ uint32_t u;
+
+ u = (uint32_t) d[0] | (((uint32_t) d[1]) << 8);
+ return u;
+}
+
+
+
+static int extract_les16 (uint8_t *d)
+{
+ uint32_t u;
+ u = extract_leu16 (d);
+
+ if (u & 0x8000) u |= 0xffff0000;
+
+ return (int) u;
+}
+
+
+
+
+
+static int
+ds1820_read_sp (const Onewire_addr *a, unsigned page, uint8_t *buf, unsigned len)
+{
+ if (onewire_reset_and_select (a))
+ return ~0U;
+
+ onewire_write_byte (DS1820_READ_SCRATCHPAD);
+ onewire_read_bytes (buf, len);
+
+ if ((len == 9) && onewire_check_crc (buf, 8, buf[8]))
+ return ~0U;
+
+ return 0;
+}
+
+
+
+static int ds1820_convert_t (const Onewire_addr *a)
+{
+
+ if (onewire_reset_and_select (a))
+ return ~0U;
+
+ onewire_write_byte (DS1820_CONVERT_T);
+
+ if (onewire_wait_complete (TIMEOUT))
+ return ~0U;
+
+ return 0;
+}
+
+int
+ds1820_read (const Onewire_addr *a, int *temp)
+{
+ uint8_t buf[9];
+ int t;
+
+ if (ds1820_read_sp (a, 0, buf, 9))
+ return 1;
+
+ if (ds1820_convert_t (a))
+ return 1;
+
+ if (ds1820_read_sp (a, 0, buf, 9))
+ return 1;
+
+ t = extract_les16 (&buf[0]);
+
+ t <<= 4 ;
+
+ if (temp)
+ *temp = t;
+
+
+ return 0;
+}
+
+
+
diff --git a/boiler-monster/stm32/app/gdb.script b/boiler-monster/stm32/app/gdb.script
new file mode 100644
index 0000000..7cf9d09
--- /dev/null
+++ b/boiler-monster/stm32/app/gdb.script
@@ -0,0 +1,2 @@
+target remote localhost:3333
+cont
diff --git a/boiler-monster/stm32/app/led.c b/boiler-monster/stm32/app/led.c
new file mode 100644
index 0000000..bdfd084
--- /dev/null
+++ b/boiler-monster/stm32/app/led.c
@@ -0,0 +1,101 @@
+#include "project.h"
+
+#define LED_BOARD GPIO13
+#define LED_BOARD_PORT GPIOC
+
+#define LED_YELLOW GPIO11
+#define LED_YELLOW_PORT GPIOA
+
+#define LED_GREEN1 GPIO12
+#define LED_GREEN1_PORT GPIOA
+
+#define LED_GREEN2 GPIO15
+#define LED_GREEN2_PORT GPIOA
+
+#define LED_RED GPIO3
+#define LED_RED_PORT GPIOB
+
+static unsigned led, yellow;
+
+
+void led_red_set (int i)
+{
+ if (i)
+ CLEAR (LED_RED);
+ else
+ SET (LED_RED);
+}
+
+void led_green1_set (int i)
+{
+ if (i)
+ CLEAR (LED_GREEN1);
+ else
+ SET (LED_GREEN1);
+}
+
+void led_green2_set (int i)
+{
+ if (i)
+ CLEAR (LED_GREEN2);
+ else
+ SET (LED_GREEN2);
+}
+
+static void _led_yellow_set (int i)
+{
+ if (i)
+ CLEAR (LED_YELLOW);
+ else
+ SET (LED_YELLOW);
+}
+
+static void led_board_set (int i)
+{
+ if (i)
+ CLEAR (LED_BOARD);
+ else
+ SET (LED_BOARD);
+}
+
+void led_blink (void)
+{
+ led = MS_TO_TICKS (25);
+}
+
+
+void led_yellow_set (int i)
+{
+ yellow = !!i;
+}
+
+
+void
+led_tick (void)
+{
+ if (led) {
+ led--;
+
+ led_board_set (1);
+ _led_yellow_set (!yellow);
+ } else {
+
+ led_board_set (0);
+ _led_yellow_set (yellow);
+ }
+}
+
+
+void
+led_init (void)
+{
+ MAP_OUTPUT_PP (LED_BOARD);
+ MAP_OUTPUT_PP (LED_RED);
+ MAP_OUTPUT_PP (LED_GREEN1);
+ MAP_OUTPUT_PP (LED_GREEN2);
+ MAP_OUTPUT_PP (LED_YELLOW);
+
+ CLEAR (LED_BOARD);
+}
+
+
diff --git a/boiler-monster/stm32/app/main.c b/boiler-monster/stm32/app/main.c
new file mode 100644
index 0000000..5ceb4af
--- /dev/null
+++ b/boiler-monster/stm32/app/main.c
@@ -0,0 +1,134 @@
+#include "project.h"
+extern uint32_t dfu_flag;
+
+extern volatile uint32_t vector_table[];
+
+
+void reset_hardware (void)
+{
+ int i;
+
+ rcc_periph_reset_pulse (RST_OTGFS);
+ rcc_periph_reset_pulse (RST_ETHMAC);
+ rcc_periph_reset_pulse (RST_AFIO);
+ rcc_periph_reset_pulse (RST_GPIOA);
+ rcc_periph_reset_pulse (RST_GPIOB);
+ rcc_periph_reset_pulse (RST_GPIOC);
+ rcc_periph_reset_pulse (RST_GPIOD);
+ rcc_periph_reset_pulse (RST_GPIOE);
+ rcc_periph_reset_pulse (RST_GPIOF);
+ rcc_periph_reset_pulse (RST_GPIOG);
+ rcc_periph_reset_pulse (RST_ADC1);
+ rcc_periph_reset_pulse (RST_ADC2);
+ rcc_periph_reset_pulse (RST_TIM1);
+ rcc_periph_reset_pulse (RST_SPI1);
+ rcc_periph_reset_pulse (RST_TIM8);
+ rcc_periph_reset_pulse (RST_USART1);
+ rcc_periph_reset_pulse (RST_ADC3);
+ rcc_periph_reset_pulse (RST_TIM15);
+ rcc_periph_reset_pulse (RST_TIM16);
+ rcc_periph_reset_pulse (RST_TIM17);
+ rcc_periph_reset_pulse (RST_TIM9);
+ rcc_periph_reset_pulse (RST_TIM10);
+ rcc_periph_reset_pulse (RST_TIM11);
+ rcc_periph_reset_pulse (RST_TIM2);
+ rcc_periph_reset_pulse (RST_TIM3);
+ rcc_periph_reset_pulse (RST_TIM4);
+ rcc_periph_reset_pulse (RST_TIM5);
+ rcc_periph_reset_pulse (RST_TIM6);
+ rcc_periph_reset_pulse (RST_TIM7);
+ rcc_periph_reset_pulse (RST_TIM12);
+ rcc_periph_reset_pulse (RST_TIM13);
+ rcc_periph_reset_pulse (RST_TIM14);
+ rcc_periph_reset_pulse (RST_WWDG);
+ rcc_periph_reset_pulse (RST_SPI2);
+ rcc_periph_reset_pulse (RST_SPI3);
+ rcc_periph_reset_pulse (RST_USART2);
+ rcc_periph_reset_pulse (RST_USART3);
+ rcc_periph_reset_pulse (RST_UART4);
+ rcc_periph_reset_pulse (RST_UART5);
+ rcc_periph_reset_pulse (RST_I2C1);
+ rcc_periph_reset_pulse (RST_I2C2);
+ rcc_periph_reset_pulse (RST_USB);
+ rcc_periph_reset_pulse (RST_CAN);
+ rcc_periph_reset_pulse (RST_CAN1);
+ rcc_periph_reset_pulse (RST_CAN2);
+ rcc_periph_reset_pulse (RST_BKP);
+ rcc_periph_reset_pulse (RST_PWR);
+ rcc_periph_reset_pulse (RST_DAC);
+ rcc_periph_reset_pulse (RST_CEC);
+
+
+ for (i = 0; i < NVIC_IRQ_COUNT; ++i)
+ nvic_disable_irq (i);
+
+}
+
+
+int
+main (void)
+{
+
+ SCB_VTOR = (uint32_t) &vector_table;
+ asm volatile ("msr msp, %0"::"g" (vector_table[0]));
+
+ reset_hardware();
+
+
+
+ // rcc_clock_setup_in_hsi_out_48mhz ();
+ //nvic_set_priority_grouping(NVIC_PriorityGroup_4);
+
+ /*set up pll */
+ rcc_clock_setup_in_hse_8mhz_out_72mhz();
+
+ /*turn on clocks to periferals */
+ rcc_periph_clock_enable (RCC_GPIOA);
+ rcc_periph_clock_enable (RCC_GPIOB);
+ rcc_periph_clock_enable (RCC_GPIOC);
+ rcc_periph_clock_enable (RCC_AFIO);
+ rcc_periph_clock_enable (RCC_USART1);
+ rcc_periph_clock_enable (RCC_USART2);
+ rcc_periph_clock_enable (RCC_USART3);
+ rcc_periph_clock_enable (RCC_ADC1);
+
+ dwt_enable_cycle_counter();
+
+ /*Change interrupt priorities so that USART trumps Timer trumps ATKBD */
+ nvic_set_priority (NVIC_USART1_IRQ, 0x40);
+ nvic_set_priority (NVIC_USART2_IRQ, 0x40);
+ nvic_set_priority (NVIC_USART3_IRQ, 0x40);
+ nvic_set_priority (NVIC_SYSTICK_IRQ, 0x80);
+ nvic_set_priority (NVIC_EXTI9_5_IRQ, 0xc0);
+
+ gpio_primary_remap (AFIO_MAPR_SWJ_CFG_JTAG_OFF_SW_ON, 0);
+
+ led_init();
+
+ usart_init();
+ ticker_init();
+
+ pic_init();
+ ot_init();
+
+ onewire_init();
+
+ pressure_init();
+ adc_init();
+
+
+ printf ("STARTUP\r\n");
+ printf ("%s\r\n", scm_version);
+
+
+ for (;;) {
+ if (!ring_empty (&rx1_ring))
+ cmd_usart_dispatch();
+
+ temp_dispatch();
+
+ }
+
+
+ return 0;
+}
diff --git a/boiler-monster/stm32/app/ot.c b/boiler-monster/stm32/app/ot.c
new file mode 100644
index 0000000..2a8ad36
--- /dev/null
+++ b/boiler-monster/stm32/app/ot.c
@@ -0,0 +1,452 @@
+#include "project.h"
+
+
+typedef struct {
+ uint8_t flags: 5;
+ uint8_t type: 3;
+ uint8_t data[2];
+} OT_Msg;
+
+
+#define FLAGS_PENDING_THM 1U << 0
+#define FLAGS_PROCESSED_THM 1U << 1
+#define FLAGS_PENDING_USR 1U << 2
+#define FLAGS_PROCESSED_USR 1U << 3
+#define FLAGS_PENDING_OVR 1U << 4
+
+#define FLAGS_PENDING (FLAGS_PENDING_THM|FLAGS_PENDING_USR | FLAGS_PENDING_OVR)
+
+#define IDX_MAX 0x300
+
+static OT_Msg ot_thm_req[IDX_MAX];
+static OT_Msg ot_blr_rsp[IDX_MAX];
+
+#define OT_NEXT(a) (((a) < 0x2ff) ? ((a)+1):0)
+#define OT_INDEX(t,id) ((((unsigned) (t) ) <<8 ) | ((unsigned) (id)))
+#define OT_IDX_TO_ID(idx) ((idx) & 0xff)
+#define OT_IDX_TO_TYPE(idx) ((idx) >> 8)
+
+static unsigned ot_req_idx;
+static unsigned in_flight_req_type;
+static unsigned blr_backoff;
+static unsigned ot_status_wdt;
+
+
+#define OT_READ_DATA 0x0
+#define OT_WRITE_DATA 0x1
+#define OT_INVALID_DATA 0x2
+#define OT_READ_ACK 0x4
+#define OT_WRITE_ACK 0x5
+#define OT_DATA_INVALID 0x6
+#define OT_UNKNOWN_DATAID 0x7
+
+
+#define OT_IDX_STATUS 0
+/*TX*/
+#define OT_IDX_STATUS_BIT_ENABLE_CH (1U << 0)
+#define OT_IDX_STATUS_BIT_ENABLE_DHW (1U << 1)
+
+/*RX*/
+#define OT_IDX_STATUS_BIT_FAULT (1U << 0)
+#define OT_IDX_STATUS_BIT_CH_MODE (1U << 1)
+#define OT_IDX_STATUS_BIT_DHW_MODE (1U << 2)
+#define OT_IDX_STATUS_BIT_FLAME (1U << 3)
+
+#define OT_IDX_CONTROL_SETPOINT 1
+#define OT_IDX_DHW_SETPOINT 56
+
+
+#define OT_IDX_CH_WATER_PRESSURE 18
+#define OT_IDX_RETURN_WATER_TEMP 28
+#define OT_IDX_SUPPLY_INLET_TEMP 80
+
+
+
+unsigned ot_override_ch = 0;
+unsigned ot_override_dhw = 0;
+
+
+static inline int parity (uint8_t v)
+{
+ return (0x6996u >> ((v ^ (v >> 4)) & 0xf)) & 1;
+}
+
+static void ot_parity (uint8_t *data)
+{
+ int p;
+ p = parity (data[0]);
+ p ^= parity (data[1]);
+ p ^= parity (data[2]);
+ p ^= parity (data[3]);
+
+ if (p) data[0] ^= 0x80;
+}
+
+
+static const char *type_str[8] = {
+ ">Read Data",
+ ">Write Data",
+ ">Invalid Data",
+ ">Reserved",
+ "<Read ACK",
+ "<Write ACK",
+ "<Data Invalid",
+ "<Unknown DataID"
+};
+
+static void ot_debug (char *who, uint8_t *msg, char *what)
+{
+ unsigned type = (msg[0] >> 4) & 7;
+
+
+ printf ("%s%02x%02x%02x%02x %s %s\r\n",
+ who,
+ msg[0],
+ msg[1],
+ msg[2],
+ msg[3],
+ type_str[type],
+ what
+ );
+}
+
+static void send_reply_to_thm (unsigned idx)
+{
+ uint8_t reply[4];
+
+ if (ot_tx_thm (NULL)) return;
+
+ reply[0] = ot_blr_rsp[idx].type << 4;
+ reply[1] = OT_IDX_TO_ID (idx);
+ reply[2] = ot_blr_rsp[idx].data[0];
+ reply[3] = ot_blr_rsp[idx].data[1];
+
+ ot_debug ("B", reply, "");
+
+ ot_parity (reply);
+ ot_tx_thm (reply);
+
+ ot_blr_rsp[idx].flags &= ~FLAGS_PROCESSED_THM;
+
+}
+
+
+static int send_req_to_blr (unsigned idx)
+{
+ uint8_t req[4];
+
+ if (ot_tx_blr (NULL)) return -1;
+
+
+
+ req[0] = ot_thm_req[ot_req_idx].type << 4;
+ req[1] = OT_IDX_TO_ID (ot_req_idx);
+ req[2] = ot_thm_req[ot_req_idx].data[0];
+ req[3] = ot_thm_req[ot_req_idx].data[1];
+
+ ot_parity (req);
+ ot_debug (" S", req, "");
+
+ return ot_tx_blr (req);
+}
+
+
+
+
+void ot_rx_thm (uint8_t *msg, int error)
+{
+ unsigned type = (msg[0] >> 4) & 7;
+ unsigned id = msg[1];
+
+ unsigned idx;
+
+ if (error) return;
+
+ if (type > 2) {
+ ot_debug ("T", msg, "message type invalid for thermostat");
+ return;
+ }
+
+ if (ot_override_ch) {
+ if ((id == OT_IDX_STATUS) && (type == OT_READ_DATA)) /* Turn on heating */
+ msg[2] |= OT_IDX_STATUS_BIT_ENABLE_CH;
+
+ if ((id == OT_IDX_CONTROL_SETPOINT) && (type == OT_WRITE_DATA)) /* set water temp */
+ msg[2] = ot_override_ch;
+
+ }
+
+ if (ot_override_dhw) {
+ if ((id == OT_IDX_STATUS) && (type == OT_READ_DATA)) /* Turn on hotwater */
+ msg[2] |= OT_IDX_STATUS_BIT_ENABLE_DHW;
+
+ if ((id == OT_IDX_DHW_SETPOINT) && (type == OT_WRITE_DATA)) /* set water temp */
+ msg[2] = ot_override_dhw;
+
+ }
+
+ if ((id == OT_IDX_STATUS) && (type == OT_READ_DATA))
+ ot_status_wdt = 0;
+
+ ot_debug ("T", msg, "");
+
+ idx = OT_INDEX (type, id);
+
+ if (ot_blr_rsp[idx].flags & FLAGS_PROCESSED_THM)
+ send_reply_to_thm (idx);
+
+ else {
+ ot_thm_req[idx].type = type;
+ ot_thm_req[idx].data[0] = msg[2];;
+ ot_thm_req[idx].data[1] = msg[3];;
+ ot_thm_req[idx].flags |= FLAGS_PENDING_THM;
+ }
+}
+
+static int ot_fake_read_ack (unsigned id, uint8_t *msg)
+{
+ unsigned t;
+
+ switch (id) {
+ case OT_IDX_CH_WATER_PRESSURE:
+ t = pressure_ch();
+
+ if (!t) return -1;
+
+ break;
+
+ case OT_IDX_RETURN_WATER_TEMP:
+ t = temp_ch_return();
+
+ if (!t) return -1;
+
+ break;
+
+ case OT_IDX_SUPPLY_INLET_TEMP:
+ t = temp_supply_inlet();
+
+ if (!t) return -1;
+
+ break;
+
+ default:
+ return -1;
+ }
+
+
+
+ msg[0] = OT_READ_ACK << 4;
+ msg[1] = id;
+ msg[2] = t >> 8;
+ msg[3] = t & 0xff;
+
+ return 0;
+}
+
+
+void ot_rx_blr (uint8_t *msg, int error)
+{
+ unsigned type = (msg[0] >> 4) & 7;
+ unsigned id = msg[1];
+
+ unsigned idx;
+
+ if (error) return;
+
+ ot_debug (" A", msg, "");
+
+ idx = OT_INDEX (in_flight_req_type, id);
+
+ if ((in_flight_req_type == OT_READ_DATA) && (type != OT_READ_ACK)) {
+ if (!ot_fake_read_ack (id, msg)) {
+ ot_debug (" A", msg, " (faked)");
+ type = (msg[0] >> 4) & 7;
+ }
+ }
+
+ ot_blr_rsp[idx].type = type;
+ ot_blr_rsp[idx].data[0] = msg[2];;
+ ot_blr_rsp[idx].data[1] = msg[3];;
+ ot_blr_rsp[idx].flags |= FLAGS_PROCESSED_THM | FLAGS_PROCESSED_USR;
+
+ if (ot_thm_req[idx].flags & FLAGS_PENDING_THM) {
+ ot_thm_req[idx].flags &= ~FLAGS_PENDING_THM;
+ send_reply_to_thm (idx);
+ }
+
+ if (ot_thm_req[idx].flags & FLAGS_PENDING_USR) {
+ ot_thm_req[idx].flags &= ~FLAGS_PENDING_USR;
+ //send_reply_to_usr (idx);
+ }
+
+ if (ot_thm_req[idx].flags & FLAGS_PENDING_OVR)
+ ot_thm_req[idx].flags &= ~FLAGS_PENDING_OVR;
+
+ blr_backoff = 0;
+
+}
+
+
+
+static void ot_boiler_worker (void)
+{
+ unsigned i;
+
+ if (blr_backoff) {
+ blr_backoff--;
+ return;
+ }
+
+ if (ot_tx_blr (NULL)) return;
+
+
+ for (i = 0; i < IDX_MAX; ++i, ot_req_idx = OT_NEXT (ot_req_idx)) {
+
+ if (ot_thm_req[ot_req_idx].flags & FLAGS_PENDING) {
+
+ if (!send_req_to_blr (ot_req_idx)) {
+ ot_thm_req[ot_req_idx].flags &= ~FLAGS_PENDING;
+ in_flight_req_type = OT_IDX_TO_TYPE (ot_req_idx);
+ blr_backoff = 10;
+ ot_req_idx = OT_NEXT (ot_req_idx);
+ return;
+ }
+ }
+
+ }
+
+}
+
+
+static void ot_request (unsigned flags, unsigned type, unsigned id, uint8_t *data)
+{
+ unsigned idx = OT_INDEX (type, id);
+
+ ot_thm_req[idx].type = type;
+
+ if (data) {
+ ot_thm_req[idx].data[0] = data[0];
+ ot_thm_req[idx].data[1] = data[1];
+ } else {
+ ot_thm_req[idx].data[0] = 0;
+ ot_thm_req[idx].data[1] = 0;
+ }
+
+ ot_thm_req[idx].flags |= FLAGS_PENDING_USR;
+
+}
+
+void ot_request_usr (unsigned type, unsigned id, uint8_t *data)
+{
+ ot_request (FLAGS_PENDING_USR, type, id, data);
+}
+static void ot_request_ovr (unsigned type, unsigned id, uint8_t *data)
+{
+ ot_request (FLAGS_PENDING_OVR, type, id, data);
+}
+
+
+
+static void ot_force_status (void)
+{
+ uint8_t data[2] = { (ot_override_ch ? OT_IDX_STATUS_BIT_ENABLE_CH : 0) | (ot_override_dhw ? OT_IDX_STATUS_BIT_ENABLE_DHW : 0), 0};
+
+ ot_request_ovr (OT_READ_DATA, OT_IDX_STATUS, data);
+
+}
+
+static void ot_30s_ticker (void)
+{
+ uint8_t data[2];
+
+ printf ("Q ot ticker - push control flags\r\n");
+
+ if (ot_override_ch) {
+ data[0] = ot_override_ch;
+ data[1] = 0;
+ ot_request_ovr (OT_WRITE_DATA, OT_IDX_CONTROL_SETPOINT, data);
+ }
+
+ if (ot_override_dhw) {
+
+ data[0] = ot_override_dhw;
+ data[1] = 0;
+ ot_request_ovr (OT_WRITE_DATA, OT_IDX_DHW_SETPOINT, data);
+ }
+
+}
+
+static void ot_2s_ticker (void)
+{
+ uint8_t msg[4];
+
+ if (!ot_fake_read_ack (OT_IDX_CH_WATER_PRESSURE, msg))
+ ot_debug ("B", msg, " (fake request and reply)");
+
+ if (!ot_fake_read_ack (OT_IDX_RETURN_WATER_TEMP, msg))
+ ot_debug ("B", msg, " (fake request and reply)");
+
+ if (!ot_fake_read_ack (OT_IDX_SUPPLY_INLET_TEMP, msg))
+ ot_debug ("B", msg, " (fake request and reply)");
+}
+
+
+
+
+
+static void ot_4hz_ticker (void)
+{
+ static unsigned thirty, two;
+ thirty++;
+ two++;
+
+ ot_boiler_worker();
+
+ ot_status_wdt++;
+
+ if (ot_status_wdt > 120) {
+ printf ("Q forcing status packet\r\n");
+ ot_force_status();
+ ot_status_wdt = 0;
+ }
+
+
+ if (two >= 8) {
+ ot_2s_ticker();
+ two = 0;
+ }
+
+
+ if (thirty >= 120) {
+ ot_30s_ticker();
+ thirty = 0;
+ }
+
+
+ led_yellow_set (ot_blr_rsp[OT_INDEX (OT_READ_DATA, OT_IDX_STATUS)].data[1] & OT_IDX_STATUS_BIT_FAULT);
+ led_green1_set (ot_blr_rsp[OT_INDEX (OT_READ_DATA, OT_IDX_STATUS)].data[1] & OT_IDX_STATUS_BIT_CH_MODE);
+ led_green2_set (ot_blr_rsp[OT_INDEX (OT_READ_DATA, OT_IDX_STATUS)].data[1] & OT_IDX_STATUS_BIT_DHW_MODE);
+ led_red_set (ot_blr_rsp[OT_INDEX (OT_READ_DATA, OT_IDX_STATUS)].data[1] & OT_IDX_STATUS_BIT_FLAME);
+
+}
+
+
+void ot_tick (void)
+{
+ static unsigned i;
+ i++;
+
+ if (i >= 500) {
+ ot_4hz_ticker();
+ i = 0;
+ }
+
+
+}
+
+
+void ot_init (void)
+{
+ ot_phy_rx_init();
+ ot_phy_tx_init();
+}
diff --git a/boiler-monster/stm32/app/ot_phy_rx.c b/boiler-monster/stm32/app/ot_phy_rx.c
new file mode 100644
index 0000000..9348d24
--- /dev/null
+++ b/boiler-monster/stm32/app/ot_phy_rx.c
@@ -0,0 +1,177 @@
+#include "project.h"
+
+#define OT_THM_IN GPIO6
+#define OT_THM_IN_PORT GPIOB
+
+#define OT_BLR_IN GPIO7
+#define OT_BLR_IN_PORT GPIOB
+
+
+#define OT_RX_IRQ NVIC_EXTI9_5_IRQ
+
+
+typedef struct rx_phy {
+ uint32_t last_cycle;
+ int last_v;
+ unsigned half_bits;
+ unsigned data_bits;
+ int frame_error;
+ uint8_t data[10];
+ int parity;
+} RX_Phy;
+
+
+static RX_Phy p_thm, p_blr;
+
+
+static uint32_t
+cycle_diff (uint32_t a, uint32_t b)
+{
+ return b - a;
+}
+
+
+
+
+static void ot_phy_rx_bit (RX_Phy *p)
+{
+
+
+ if (p->half_bits & 1)
+ p->frame_error = 1;
+
+ if (!p->half_bits) {
+ if (!p->last_v)
+ p->frame_error = 1;
+
+ return;
+ }
+
+ if (p->data_bits < 32) {
+ if (p->last_v) p->data[p->data_bits >> 3] |= 0x80 >> (p->data_bits & 7);
+
+ p->parity ^= p->last_v;
+ } else if (p->data_bits == 32) {
+ if ((!p->last_v) || (p->parity))
+ p->frame_error = 1;
+
+ led_blink();
+
+ if (p == &p_thm)
+ ot_rx_thm (p->data, p->frame_error);
+ else
+ ot_rx_blr (p->data, p->frame_error);
+ }
+
+
+ p->data_bits++;
+
+}
+
+
+static void ot_phy_rx_worker (RX_Phy *p, int v)
+{
+ uint32_t now, diff;
+
+ if (v == p->last_v) return;
+
+
+ now = dwt_read_cycle_counter();
+ diff = cycle_diff (p->last_cycle, now);
+
+
+ if (diff < 10000) return;
+
+ if (diff < 50000) {
+ if (! (p->half_bits & 1)) ot_phy_rx_bit (p);
+
+ p->half_bits++;
+ } else if (diff < 85000) {
+ p->half_bits++;
+ ot_phy_rx_bit (p);
+ p->half_bits++;
+ } else {
+ p->parity = 0;
+ p->half_bits = 0;
+ p->frame_error = 0;
+ p->data_bits = 0;
+ memset (p->data, 0, sizeof (p->data));
+ }
+
+ p->last_cycle = now;
+ p->last_v = v;
+}
+
+
+
+void
+exti9_5_isr (void)
+{
+ int v;
+
+ if (EXTI_PR & OT_THM_IN) {
+ EXTI_PR = OT_THM_IN;
+ v = !GET (OT_THM_IN);
+
+#if 0
+
+ if (v)
+ CLEAR (OT_BLR_OUT);
+ else
+ SET (OT_BLR_OUT);
+
+#endif
+
+ ot_phy_rx_worker (&p_thm, v);
+
+
+ }
+
+ if (EXTI_PR & OT_BLR_IN) {
+ EXTI_PR = OT_BLR_IN;
+ v = !GET (OT_BLR_IN);
+
+#if 0
+
+ if (v)
+ CLEAR (OT_THM_OUT);
+ else
+ SET (OT_THM_OUT);
+
+#endif
+
+ ot_phy_rx_worker (&p_blr, v);
+ }
+
+ return;
+}
+
+
+void ot_phy_rx_tick (void)
+{
+}
+
+
+
+void ot_phy_rx_init (void)
+{
+ MAP_INPUT_PU (OT_THM_IN);
+ MAP_INPUT_PU (OT_BLR_IN);
+
+
+ exti_select_source (OT_THM_IN, OT_THM_IN_PORT);
+ exti_set_trigger (OT_THM_IN, EXTI_TRIGGER_BOTH);
+ exti_enable_request (OT_THM_IN);
+ exti_reset_request (OT_THM_IN);
+
+ exti_select_source (OT_BLR_IN, OT_BLR_IN_PORT);
+ exti_set_trigger (OT_BLR_IN, EXTI_TRIGGER_BOTH);
+ exti_enable_request (OT_BLR_IN);
+ exti_reset_request (OT_BLR_IN);
+
+ nvic_enable_irq (OT_RX_IRQ);
+
+}
+
+
+
diff --git a/boiler-monster/stm32/app/ot_phy_tx.c b/boiler-monster/stm32/app/ot_phy_tx.c
new file mode 100644
index 0000000..36fbcc5
--- /dev/null
+++ b/boiler-monster/stm32/app/ot_phy_tx.c
@@ -0,0 +1,121 @@
+#include "project.h"
+
+#define OT_THM_OUT GPIO9
+#define OT_THM_OUT_PORT GPIOB
+
+#define OT_BLR_OUT GPIO8
+#define OT_BLR_OUT_PORT GPIOB
+
+
+typedef struct tx_phy {
+ int busy;
+ unsigned half_bit;
+ uint8_t data[4];
+} TX_Phy;
+
+
+static TX_Phy p_thm, p_blr;
+
+
+
+#if 0
+static uint32_t
+cycle_diff (uint32_t a, uint32_t b)
+{
+ return b - a;
+}
+#endif
+
+
+static int ot_phy_tx_worker (TX_Phy *p)
+{
+ int ret = p->half_bit & 1;
+ unsigned bit;
+
+
+ if (p->half_bit < 2)
+ ret ^= 1;
+
+ else if (p->half_bit < 66) {
+ bit = (p->half_bit >> 1) - 1;
+
+ if (p->data[bit >> 3] & (0x80 >> (bit & 7)))
+ ret ^= 1;
+ } else if (p->half_bit < 68)
+
+ ret ^= 1;
+
+ p->half_bit++;
+
+ if (p->half_bit == 68) {
+ p->half_bit = 0;
+ p->busy = 0;
+ }
+
+ return ret;
+}
+
+
+void ot_phy_tx_tick (void)
+{
+ int v;
+
+ if (p_thm.busy) {
+ v = ot_phy_tx_worker (&p_thm);
+
+ if (v)
+ CLEAR (OT_THM_OUT);
+ else
+ SET (OT_THM_OUT);
+ }
+
+
+ if (p_blr.busy) {
+ v = ot_phy_tx_worker (&p_blr);
+
+ if (v)
+ CLEAR (OT_BLR_OUT);
+ else
+ SET (OT_BLR_OUT);
+
+ }
+}
+
+
+int ot_tx_thm (uint8_t *data)
+{
+ if (p_thm.busy) return -1;
+
+ if (!data) return 0;
+
+ led_blink();
+
+ memcpy (p_thm.data, data, sizeof (p_thm.data));
+ p_thm.busy = 1;
+
+ return 0;
+}
+
+
+int ot_tx_blr (uint8_t *data)
+{
+
+ if (p_blr.busy) return -1;
+
+ if (!data) return 0;
+
+ led_blink();
+
+ memcpy (p_blr.data, data, sizeof (p_blr.data));
+ p_blr.busy = 1;
+
+ return 0;
+}
+
+
+
+void ot_phy_tx_init (void)
+{
+ MAP_OUTPUT_PP (OT_THM_OUT);
+ MAP_OUTPUT_PP (OT_BLR_OUT);
+}
diff --git a/boiler-monster/stm32/app/pic.c b/boiler-monster/stm32/app/pic.c
new file mode 100644
index 0000000..b478766
--- /dev/null
+++ b/boiler-monster/stm32/app/pic.c
@@ -0,0 +1,52 @@
+#include "project.h"
+
+#define PIC_RESET GPIO1
+#define PIC_RESET_PORT GPIOB
+
+
+static void pic_reset (void)
+{
+ SET (PIC_RESET);
+ delay_ms (1);
+ CLEAR (PIC_RESET);
+}
+
+
+void pic_passthru (void)
+{
+ uint8_t c;
+
+ printf ("\r\nPIC MODE\r\n");
+ usart1_drain();
+ block_stdio = 1;
+
+ while (!ring_read_byte (&rx3_ring, &c));
+
+ pic_reset();
+
+
+ for (;;) {
+ if (!ring_read_byte (&rx1_ring, &c))
+ usart3_queue (c);
+
+ if (!ring_read_byte (&rx3_ring, &c))
+ usart1_queue (c);
+
+ }
+}
+
+
+
+
+
+
+
+void pic_init (void)
+{
+ MAP_OUTPUT_PP (PIC_RESET);
+
+ pic_reset();
+
+}
+
+
diff --git a/boiler-monster/stm32/app/pins.h b/boiler-monster/stm32/app/pins.h
new file mode 100644
index 0000000..9ea01cf
--- /dev/null
+++ b/boiler-monster/stm32/app/pins.h
@@ -0,0 +1,53 @@
+#ifndef _PINS_H_
+#define _PINS_H_
+
+/* st seem to change these with every chip revision */
+
+#define MAP_AF(a) do { \
+ gpio_set_mode( a ## _PORT, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_ALTFN_PUSHPULL, a ); \
+ } while (0)
+
+/* STM32F1 doesn't have AF pull up, but also doesn't disconnect af inputs so just use regular pull up */
+#define MAP_AF_PU(a) do { \
+ gpio_set_mode( a ## _PORT, GPIO_MODE_INPUT, GPIO_CNF_INPUT_PULL_UPDOWN, a); \
+ gpio_set( a ## _PORT, a); \
+ } while (0)
+
+#define MAP_AF_OD(a) do { \
+ gpio_set_mode( a ## _PORT, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_ALTFN_OPENDRAIN, a ); \
+ } while (0)
+
+
+#define MAP_OUTPUT_PP(a) do { \
+ gpio_set_mode( a ## _PORT, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_PUSHPULL, a ); \
+ } while (0)
+
+
+#define MAP_OUTPUT_OD(a) do { \
+ gpio_set_mode( a ## _PORT, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_OPENDRAIN, a ); \
+ } while (0)
+
+
+/* STM32F1 madly uses the output register to drive the other end of the resistor, so pull up */
+/* requires us to write a 1 there */
+
+#define MAP_INPUT_PU(a) do { \
+ gpio_set_mode( a ## _PORT, GPIO_MODE_INPUT, GPIO_CNF_INPUT_PULL_UPDOWN, a); \
+ gpio_set( a ## _PORT, a); \
+ } while (0)
+
+
+#define MAP_INPUT(a) do { \
+ gpio_set_mode( a ## _PORT, GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT, a); \
+ } while (0)
+
+#define MAP_ANALOG(a) do { \
+ gpio_set_mode( a ## _PORT, GPIO_MODE_INPUT, GPIO_CNF_INPUT_ANALOG, a); \
+ } while (0)
+
+
+#define CLEAR(a) gpio_clear( a ## _PORT, a)
+#define SET(a) gpio_set( a ## _PORT, a)
+#define GET(a) gpio_get( a ## _PORT, a)
+
+#endif
diff --git a/boiler-monster/stm32/app/pressure.c b/boiler-monster/stm32/app/pressure.c
new file mode 100644
index 0000000..f719e4d
--- /dev/null
+++ b/boiler-monster/stm32/app/pressure.c
@@ -0,0 +1,164 @@
+#include "project.h"
+
+#define PRESSURE GPIO0
+#define PRESSURE_PORT GPIOA
+#define PRESSURE_CHANNEL ADC_CHANNEL0
+#define VREF_CHANNEL ADC_CHANNEL17
+
+#define N_SAMPLES 1000
+
+#define T do { printf("%s:%d\r\n",__FILE__,__LINE__); } while (0)
+
+static int pressure;
+
+struct avg {
+ int acc;
+ unsigned n;
+};
+
+static void avg_reset(struct avg *a)
+{
+a->acc=0;
+a->n=0;
+return;
+}
+static void avg_add(struct avg *a, int v)
+{
+a->acc+=v;
+a->n++;
+}
+inline static unsigned avg_n(struct avg *a)
+{
+return a->n;
+}
+
+static int avg_get(struct avg *a)
+{
+if (!a->n) return 0;
+return (a->acc) / (a->n);
+}
+
+
+uint16_t pressure_ch (void)
+{
+ return pressure;
+}
+
+
+static void pressure_calculate (int v,int r)
+{
+ /* r is 1.25 volts, transducer is 0.5V -> 0 psi 4.5V -> 100psi */
+ /* 100psi is 6.8947573 bar, and we want 256ths of bar */
+
+
+ if (!r) {
+ pressure = 0;
+ // return;
+ } else {
+ pressure = ((v * 552) / r) - 221;
+
+ if (pressure < 0) pressure = 0;
+ }
+
+ printf ("QP: %d %d %d\r\n", v, r, (pressure * 100) / 256);
+}
+
+
+
+void pressure_tick(void)
+{
+static unsigned state;
+static unsigned backoff=MS_TO_TICKS(1000);
+static unsigned timeout;
+
+static struct avg vref_avg;
+static struct avg pressure_avg;
+
+
+timeout++;
+
+if (backoff) {
+ backoff--;
+ return;
+}
+
+
+switch (state) {
+case 0:
+ adc_off (ADC1);
+ adc_power_on (ADC1);
+ backoff=MS_TO_TICKS(5);
+ state++;
+ break;
+case 1:
+ ADC_CR2 (ADC1) |= ADC_CR2_RSTCAL;
+ backoff=MS_TO_TICKS(2);
+ state++;
+ break;
+case 2:
+ if (ADC_CR2(ADC1) & ADC_CR2_CAL)
+ state=0;
+ ADC_CR2 (ADC1) |= ADC_CR2_CAL;
+ backoff=MS_TO_TICKS(2);
+ state++;
+ break;
+case 3:
+ if (ADC_CR2(ADC1) & ADC_CR2_CAL)
+ state=0;
+ state++;
+
+ avg_reset(&vref_avg);
+ avg_reset(&pressure_avg);
+ break;
+case 4:
+ if (adc_convert_start(VREF_CHANNEL))
+ state=0;
+ else {
+ timeout=0;
+ state++;
+ }
+ break;
+case 5:
+ if (adc_convert_done()) {
+ avg_add(&vref_avg,adc_convert_get());
+ state++;
+ } else if (timeout > MS_TO_TICKS(5))
+ state=0;
+ break;
+case 6:
+ if (adc_convert_start(PRESSURE_CHANNEL))
+ state=0;
+ else {
+ timeout=0;
+ state++;
+ }
+ break;
+case 7:
+ if (adc_convert_done()) {
+ avg_add(&pressure_avg,adc_convert_get());
+
+ if (avg_n(&pressure_avg)<N_SAMPLES)
+ state=4;
+ else
+ state++;
+ } else if (timeout > MS_TO_TICKS(5))
+ state=0;
+ break;
+case 8:
+ pressure_calculate(avg_get(&pressure_avg),avg_get(&vref_avg));
+ state=0;
+ break;
+}
+
+}
+
+
+
+void pressure_init (void)
+{
+ MAP_ANALOG (PRESSURE);
+}
+
+
+
+
diff --git a/boiler-monster/stm32/app/project.h b/boiler-monster/stm32/app/project.h
new file mode 100644
index 0000000..d1bfa52
--- /dev/null
+++ b/boiler-monster/stm32/app/project.h
@@ -0,0 +1,30 @@
+#include <stdlib.h>
+#include <libopencm3/stm32/rcc.h>
+#include <libopencm3/stm32/gpio.h>
+#include <libopencm3/stm32/usart.h>
+#include <libopencm3/stm32/usb.h>
+#include <libopencm3/stm32/exti.h>
+#include <libopencm3/stm32/adc.h>
+#include <libopencm3/cm3/systick.h>
+#include <libopencm3/cm3/nvic.h>
+#include <libopencm3/cm3/cortex.h>
+#include <libopencm3/cm3/scb.h>
+#include <libopencm3/cm3/dwt.h>
+
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+
+#include "ring.h"
+#include "pins.h"
+#include "1wire.h"
+
+
+#define US (72)
+#define MS (US * 1000)
+#define HZ (MS * 1000)
+
+#define MS_TO_TICKS(a) ((a) *2)
+
+
+#include "prototypes.h"
diff --git a/boiler-monster/stm32/app/prototypes.h b/boiler-monster/stm32/app/prototypes.h
new file mode 100644
index 0000000..5206734
--- /dev/null
+++ b/boiler-monster/stm32/app/prototypes.h
@@ -0,0 +1,106 @@
+/* main.c */
+extern void reset_hardware(void);
+extern int main(void);
+/* ot.c */
+extern unsigned ot_override_ch;
+extern unsigned ot_override_dhw;
+extern void ot_rx_thm(uint8_t *msg, int error);
+extern void ot_rx_blr(uint8_t *msg, int error);
+extern void ot_request_usr(unsigned type, unsigned id, uint8_t *data);
+extern void ot_tick(void);
+extern void ot_init(void);
+/* ot_phy_rx.c */
+extern void exti9_5_isr(void);
+extern void ot_phy_rx_tick(void);
+extern void ot_phy_rx_init(void);
+/* ot_phy_tx.c */
+extern void ot_phy_tx_tick(void);
+extern int ot_tx_thm(uint8_t *data);
+extern int ot_tx_blr(uint8_t *data);
+extern void ot_phy_tx_init(void);
+/* led.c */
+extern void led_red_set(int i);
+extern void led_green1_set(int i);
+extern void led_green2_set(int i);
+extern void led_blink(void);
+extern void led_yellow_set(int i);
+extern void led_tick(void);
+extern void led_init(void);
+/* ticker.c */
+extern void delay_us(uint32_t d);
+extern int tick_dispatch;
+extern void sys_tick_handler(void);
+extern void delay_ms(uint32_t d);
+extern void ticker_init(void);
+/* usart.c */
+extern ring_t rx1_ring;
+extern ring_t tx1_ring;
+extern ring_t rx3_ring;
+extern ring_t tx3_ring;
+extern void usart1_isr(void);
+extern void usart1_queue(uint8_t d);
+extern void usart1_drain(void);
+extern int usart1_write(char *ptr, int len, int blocking);
+extern void usart3_isr(void);
+extern void usart3_queue(uint8_t d);
+extern void usart3_drain(void);
+extern int usart3_write(char *ptr, int len, int blocking);
+extern void usart_init(void);
+/* ring.c */
+extern void ring_init(volatile ring_t *r, uint8_t *buf, size_t len);
+extern int ring_write_byte(volatile ring_t *r, uint8_t c);
+extern int ring_read_byte(volatile ring_t *r, uint8_t *c);
+extern int ring_write(volatile ring_t *r, uint8_t *buf, size_t len, int blocking);
+extern int ring_empty(volatile ring_t *r);
+/* stdio.c */
+extern unsigned block_stdio;
+extern int _open(const char *name, int flags, int mode);
+extern int _close(int file);
+extern int _write(int file, char *buf, int nbytes);
+extern int _read(int file, char *buf, int nbytes);
+extern int _lseek(int file, int offset, int whence);
+extern int isatty(int file);
+extern void stdio_drain(void);
+/* util.c */
+extern char int_to_hex_char(int a);
+/* commit.c */
+extern char scm_version[];
+/* cmd.c */
+extern void cmd_usart_dispatch(void);
+/* pic.c */
+extern void pic_passthru(void);
+extern void pic_init(void);
+/* 1wire.c */
+extern void onewire_tick(void);
+extern int onewire_reset(void);
+extern void onewire_write_byte(uint8_t v);
+extern uint8_t onewire_read_byte(void);
+extern void onewire_read_bytes(uint8_t *buf, int n);
+extern void onewire_write_bytes(const uint8_t *buf, int n);
+extern int onewire_select(const Onewire_addr *a);
+extern int onewire_reset_and_select(const Onewire_addr *a);
+extern int onewire_wait_complete(unsigned timeout);
+extern int onewire_check_crc(uint8_t *buf, int n, uint8_t v);
+extern int onewire_search(void);
+extern void onewire_init(void);
+/* temp.c */
+extern void temp_tick(void);
+extern void temp_dispatch(void);
+extern uint16_t temp_ch_return(void);
+extern uint16_t temp_supply_inlet(void);
+/* ds1820.c */
+extern int ds1820_read(const Onewire_addr *a, int *temp);
+/* pressure.c */
+extern uint16_t pressure_ch(void);
+extern void pressure_tick(void);
+extern void pressure_init(void);
+/* adc.c */
+extern void adc_dump(void);
+extern volatile unsigned timeout;
+extern void adc_tick(void);
+extern int adc_calibrate(void);
+extern int adc_convert_start(unsigned channel);
+extern int adc_convert_done(void);
+extern unsigned adc_convert_get(void);
+extern unsigned adc_convert(unsigned channel);
+extern void adc_init(void);
diff --git a/boiler-monster/stm32/app/ring.c b/boiler-monster/stm32/app/ring.c
new file mode 100644
index 0000000..973f345
--- /dev/null
+++ b/boiler-monster/stm32/app/ring.c
@@ -0,0 +1,77 @@
+#include "project.h"
+
+
+static inline size_t
+ring_next (volatile ring_t *r, size_t p)
+{
+ p++;
+
+ if (p >= r->size)
+ p -= r->size;
+
+ return p;
+}
+
+void
+ring_init (volatile ring_t *r, uint8_t *buf, size_t len)
+{
+ r->data = buf;
+ r->size = len;
+ r->write = 0;
+ r->read = 0;
+}
+
+int
+ring_write_byte (volatile ring_t *r, uint8_t c)
+{
+ size_t n = ring_next (r, r->write);
+
+ if (n == r->read)
+ return -EAGAIN;
+
+ r->data[r->write] = c;
+
+ r->write = n;
+
+ return 0;
+}
+
+
+int
+ring_read_byte (volatile ring_t *r, uint8_t *c)
+{
+ size_t n = ring_next (r, r->read);
+
+ if (r->read == r->write)
+ return -EAGAIN;
+
+ *c = r->data[r->read];
+ r->read = n;
+
+ return 0;
+}
+
+int
+ring_write (volatile ring_t *r, uint8_t *buf, size_t len, int blocking)
+{
+ while (len--) {
+ if (blocking) {
+ while (ring_write_byte (r, *buf));
+
+ buf++;
+ } else {
+ if (ring_write_byte (r, * (buf++)))
+ return -EAGAIN;
+ }
+ }
+
+ return 0;
+}
+
+
+
+int
+ring_empty (volatile ring_t *r)
+{
+ return (r->read == r->write) ? 1 : 0;
+}
diff --git a/boiler-monster/stm32/app/ring.h b/boiler-monster/stm32/app/ring.h
new file mode 100644
index 0000000..a329354
--- /dev/null
+++ b/boiler-monster/stm32/app/ring.h
@@ -0,0 +1,13 @@
+#ifndef _RING_H_
+#define _RING_H_
+
+typedef struct ring {
+ uint8_t *data;
+ size_t size;
+ size_t write;
+ size_t read;
+} ring_t;
+
+#endif
+
+
diff --git a/boiler-monster/stm32/app/stdio.c b/boiler-monster/stm32/app/stdio.c
new file mode 100644
index 0000000..bcdd5c7
--- /dev/null
+++ b/boiler-monster/stm32/app/stdio.c
@@ -0,0 +1,77 @@
+#include "project.h"
+
+unsigned block_stdio;
+
+int
+_open (const char *name, int flags, int mode)
+{
+ errno = ENOSYS;
+ return -1; /* Always fails */
+
+} /* _open () */
+
+int
+_close (int file)
+{
+ errno = EBADF;
+ return -1; /* Always fails */
+
+} /* _close () */
+
+int
+_write (int file, char *buf, int nbytes)
+{
+
+ int ret = nbytes;
+
+ if (!block_stdio)
+ ret = usart1_write (buf, nbytes, 1);
+
+ if (ret < 0) {
+ errno = -ret;
+ return -1;
+ }
+
+ return ret;
+} /* _write () */
+
+
+int
+_read (int file, char *buf, int nbytes)
+{
+
+ errno = -EAGAIN;
+ return -1; /* EOF */
+
+} /* _read () */
+
+#if 0
+int
+_fstat (int file, struct stat *st)
+{
+ st->st_mode = S_IFCHR;
+ return 0;
+
+} /* _fstat () */
+#endif
+
+int
+_lseek (int file, int offset, int whence)
+{
+ return 0;
+
+} /* _lseek () */
+
+int
+isatty (int file)
+{
+ return 1;
+
+} /* _isatty () */
+
+
+void
+stdio_drain (void)
+{
+ usart1_drain();
+}
diff --git a/boiler-monster/stm32/app/temp.c b/boiler-monster/stm32/app/temp.c
new file mode 100644
index 0000000..4548d01
--- /dev/null
+++ b/boiler-monster/stm32/app/temp.c
@@ -0,0 +1,76 @@
+#include "project.h"
+
+
+#define N_SENSORS 2
+
+#define SENSOR_INDEX_CH_RETURN 0
+#define SENSOR_INDEX_SUPPLY_INLET 1
+
+static const Onewire_addr s_addr[N_SENSORS] = {
+ [0] = {{0x28, 0x60, 0x06, 0x53, 0x03, 0x00, 0x00, 0xf5}},
+ [1] = {{0x28, 0xa4, 0x08, 0x53, 0x03, 0x00, 0x00, 0x14}},
+};
+
+
+static int s_temp[N_SENSORS];
+
+static unsigned poke;
+
+
+void temp_tick (void)
+{
+ static unsigned ticker;
+
+ ticker++;
+
+ if (ticker < 3000)
+ return;
+
+ ticker = 0;
+ poke = 1;
+
+}
+
+
+
+
+
+void temp_dispatch (void)
+{
+ static unsigned sensor;
+
+ if (!poke) return;
+
+ poke = 0;
+
+ if (sensor < N_SENSORS) {
+ if (ds1820_read (&s_addr[sensor], &s_temp[sensor]))
+ s_temp[sensor] = 0;
+
+
+ printf ("Q1W: sensor %d temp %d\r\n", sensor, (s_temp[sensor] * 100) / 256);
+
+
+ sensor++;
+ } else {
+#if 0
+ onewire_search();
+#endif
+ }
+
+ if (sensor == N_SENSORS)
+ sensor = 0;
+
+
+}
+
+
+
+uint16_t temp_ch_return (void)
+{
+ return s_temp[SENSOR_INDEX_CH_RETURN];
+}
+uint16_t temp_supply_inlet (void)
+{
+ return s_temp[SENSOR_INDEX_SUPPLY_INLET];
+}
diff --git a/boiler-monster/stm32/app/ticker.c b/boiler-monster/stm32/app/ticker.c
new file mode 100644
index 0000000..9e268ca
--- /dev/null
+++ b/boiler-monster/stm32/app/ticker.c
@@ -0,0 +1,105 @@
+#include "project.h"
+
+
+static volatile uint32_t delay_hms_count;
+static volatile uint32_t ticks;
+static uint32_t scale = 7;
+
+void
+delay_us (uint32_t d)
+{
+ d *= scale;
+
+ while (d--)
+ __asm__ ("nop");
+}
+
+
+void
+sys_tick_handler (void)
+{
+ if (delay_hms_count)
+ delay_hms_count--;
+
+
+ led_tick();
+ ot_phy_tx_tick();
+ ot_tick();
+ onewire_tick();
+ temp_tick();
+ adc_tick();
+ pressure_tick();
+
+
+ ticks++;
+}
+
+
+
+void
+delay_ms (uint32_t d)
+{
+ delay_hms_count = d << 1;
+
+ while (delay_hms_count);
+}
+
+#if 0
+int
+timed_out (uint32_t then, unsigned int ms)
+{
+ then = ticks - then;
+
+ if (then > ms)
+ return 1;
+
+ return 0;
+}
+
+int
+timed_out_cycles (uint32_t then, unsigned int cycles)
+{
+ then = dwt_read_cycle_counter() - then;
+
+ if (then > cycles)
+ return 1;
+
+ return 0;
+}
+#endif
+
+
+
+void
+ticker_init (void)
+{
+ uint32_t v, w;
+
+ /*Start periodic timer */
+
+ systick_set_clocksource (STK_CSR_CLKSOURCE_AHB_DIV8);
+ /* 72MHz / 8 = > 9Mhz */
+ systick_set_reload (4500);
+ /* 9MHz / 4500 => 2kHz */
+ systick_interrupt_enable();
+ systick_counter_enable();
+
+ /*Calibrate the delay loop */
+
+
+
+ do {
+ scale--;
+ v = ticks;
+
+ while (v == ticks);
+
+ delay_us (500);
+ w = ticks;
+ v++;
+ w -= v;
+ } while (w);
+
+
+
+}
diff --git a/boiler-monster/stm32/app/usart.c b/boiler-monster/stm32/app/usart.c
new file mode 100644
index 0000000..28676a0
--- /dev/null
+++ b/boiler-monster/stm32/app/usart.c
@@ -0,0 +1,175 @@
+#include "project.h"
+
+#define BUFFER_SIZE 256
+
+
+#define USART1_TX GPIO_USART1_TX
+#define USART1_TX_PORT GPIOA
+
+#define USART1_RX GPIO_USART1_RX
+#define USART1_RX_PORT GPIOA
+
+#define USART3_TX GPIO_USART3_TX
+#define USART3_TX_PORT GPIOB
+
+#define USART3_RX GPIO_USART3_RX
+#define USART3_RX_PORT GPIOB
+
+
+
+ring_t rx1_ring;
+static uint8_t rx1_ring_buf[BUFFER_SIZE];
+
+ring_t tx1_ring;
+static uint8_t tx1_ring_buf[BUFFER_SIZE];
+
+ring_t rx3_ring;
+static uint8_t rx3_ring_buf[BUFFER_SIZE];
+
+ring_t tx3_ring;
+static uint8_t tx3_ring_buf[BUFFER_SIZE];
+
+void
+usart1_isr (void)
+{
+ uint8_t data;
+
+ /* Check if we were called because of RXNE. */
+ if (((USART_CR1 (USART1) & USART_CR1_RXNEIE) != 0) &&
+ ((USART_SR (USART1) & USART_SR_RXNE) != 0)) {
+ data = usart_recv (USART1);
+ ring_write_byte (&rx1_ring, data);
+ }
+
+ /* Check if we were called because of TXE. */
+ if (((USART_CR1 (USART1) & USART_CR1_TXEIE) != 0) &&
+ ((USART_SR (USART1) & USART_SR_TXE) != 0)) {
+
+ if (ring_read_byte (&tx1_ring, &data)) {
+ /*No more data, Disable the TXE interrupt, it's no longer needed. */
+ usart_disable_tx_interrupt (USART1);
+ } else
+ usart_send_blocking (USART1, data);
+ }
+
+}
+
+void
+usart1_queue (uint8_t d)
+{
+ ring_write_byte (&tx1_ring, d);
+ usart_enable_tx_interrupt (USART1);
+}
+
+void
+usart1_drain (void)
+{
+ while (!ring_empty (&tx1_ring));
+}
+
+
+int
+usart1_write (char *ptr, int len, int blocking)
+{
+ int ret;
+
+ ret = ring_write (&tx1_ring, (uint8_t *) ptr, len, blocking);
+ usart_enable_tx_interrupt (USART1);
+ return ret;
+}
+
+
+void
+usart3_isr (void)
+{
+ uint8_t data;
+
+ /* Check if we were called because of RXNE. */
+ if (((USART_CR1 (USART3) & USART_CR1_RXNEIE) != 0) &&
+ ((USART_SR (USART3) & USART_SR_RXNE) != 0)) {
+ data = usart_recv (USART3);
+ ring_write_byte (&rx3_ring, data);
+ }
+
+ /* Check if we were called because of TXE. */
+ if (((USART_CR1 (USART3) & USART_CR1_TXEIE) != 0) &&
+ ((USART_SR (USART3) & USART_SR_TXE) != 0)) {
+
+ if (ring_read_byte (&tx3_ring, &data)) {
+ /*No more data, Disable the TXE interrupt, it's no longer needed. */
+ usart_disable_tx_interrupt (USART3);
+ } else
+ usart_send_blocking (USART3, data);
+ }
+
+}
+
+void
+usart3_queue (uint8_t d)
+{
+ ring_write_byte (&tx3_ring, d);
+ usart_enable_tx_interrupt (USART3);
+}
+
+void
+usart3_drain (void)
+{
+ while (!ring_empty (&tx3_ring));
+}
+
+
+int
+usart3_write (char *ptr, int len, int blocking)
+{
+ int ret;
+
+ ret = ring_write (&tx3_ring, (uint8_t *) ptr, len, blocking);
+ usart_enable_tx_interrupt (USART3);
+ return ret;
+}
+
+
+void
+usart_init (void)
+{
+ ring_init (&rx1_ring, rx1_ring_buf, sizeof (rx1_ring_buf));
+ ring_init (&tx1_ring, tx1_ring_buf, sizeof (tx1_ring_buf));
+
+ nvic_enable_irq (NVIC_USART1_IRQ);
+
+ MAP_AF (USART1_TX);
+ MAP_AF_PU (USART1_RX);
+
+
+ usart_set_baudrate (USART1, 57600);
+ usart_set_databits (USART1, 8);
+ usart_set_stopbits (USART1, USART_STOPBITS_1);
+ usart_set_parity (USART1, USART_PARITY_NONE);
+ usart_set_flow_control (USART1, USART_FLOWCONTROL_NONE);
+ usart_set_mode (USART1, USART_MODE_TX_RX);
+
+ USART_CR1 (USART1) |= USART_CR1_RXNEIE;
+
+ usart_enable (USART1);
+
+
+ ring_init (&rx3_ring, rx3_ring_buf, sizeof (rx3_ring_buf));
+ ring_init (&tx3_ring, tx3_ring_buf, sizeof (tx3_ring_buf));
+
+
+ nvic_enable_irq (NVIC_USART3_IRQ);
+
+ MAP_AF (USART3_TX);
+ MAP_AF_PU (USART3_RX);
+
+ usart_set_baudrate (USART3, 9600);
+ usart_set_databits (USART3, 8);
+ usart_set_stopbits (USART3, USART_STOPBITS_1);
+ usart_set_parity (USART3, USART_PARITY_NONE);
+ usart_set_flow_control (USART3, USART_FLOWCONTROL_NONE);
+ usart_set_mode (USART3, USART_MODE_TX_RX);
+
+ USART_CR1 (USART3) |= USART_CR1_RXNEIE;
+
+ usart_enable (USART3);
+}
diff --git a/boiler-monster/stm32/app/util.c b/boiler-monster/stm32/app/util.c
new file mode 100644
index 0000000..bf65510
--- /dev/null
+++ b/boiler-monster/stm32/app/util.c
@@ -0,0 +1,14 @@
+#include "project.h"
+
+char int_to_hex_char (int a)
+{
+ if (a < 0) return '?';
+
+ if (a < 0xa) return '0' + a;
+
+ if (a < 0x10) return '7' + a;
+
+ return '?';
+}
+
+
diff --git a/boiler-monster/stm32/docs/pinout.txt b/boiler-monster/stm32/docs/pinout.txt
new file mode 100644
index 0000000..03abe35
--- /dev/null
+++ b/boiler-monster/stm32/docs/pinout.txt
@@ -0,0 +1,24 @@
+
+A0 <- pressure sensor
+A2 <-> one wire bus
+A11 -> yellow LED (OD)
+A12 -> green LED 1 (OD)
+A15 -> green LED 2 (OD)
+
+B1 -> PIC RESET
+B3 -> red LED (OD)
+B6 <- PIC B6 LED_C (from A0, OT from thermostat)
+B7 <- PIC B7 LED_D (from A1, OT from boiler)
+B8 -> PIC A6 GPIO_A (to A3, OT to boiler)
+B9 -> PIC A7 GPIO_B (to A4, OT to thermostat)
+B10 -> PIC RX
+B11 <- PIX TX
+
+C13 -> onboard LED (OD)
+
+A2 -> MR3020 TX
+A3 <- MR4020 RX
+RST <- MR3020 GPIO5
+
+
+
diff --git a/boiler-monster/stm32/docs/pm0056.pdf b/boiler-monster/stm32/docs/pm0056.pdf
new file mode 100644
index 0000000..659c9de
--- /dev/null
+++ b/boiler-monster/stm32/docs/pm0056.pdf
Binary files differ
diff --git a/boiler-monster/stm32/docs/rm0008.pdf b/boiler-monster/stm32/docs/rm0008.pdf
new file mode 100644
index 0000000..8f3d0e1
--- /dev/null
+++ b/boiler-monster/stm32/docs/rm0008.pdf
Binary files differ
diff --git a/boiler-monster/stm32/docs/stm32f103c8.pdf b/boiler-monster/stm32/docs/stm32f103c8.pdf
new file mode 100644
index 0000000..2723cee
--- /dev/null
+++ b/boiler-monster/stm32/docs/stm32f103c8.pdf
Binary files differ
diff --git a/boiler-monster/stm32/docs/stm32f103c8t6_pinout_voltage01.png b/boiler-monster/stm32/docs/stm32f103c8t6_pinout_voltage01.png
new file mode 100644
index 0000000..2c22a74
--- /dev/null
+++ b/boiler-monster/stm32/docs/stm32f103c8t6_pinout_voltage01.png
Binary files differ
diff --git a/boiler-monster/stm32/libopencm3 b/boiler-monster/stm32/libopencm3
new file mode 160000
+Subproject 5c73d601763dd140bff020be8d6d06f03c2bea7