Microcontroller projects

Radiator thermostat hacks

last updated: 06/10/19

Introduction

Back in 2010 we used HR20 thermostats from Honeywell (Rondostat) for a school project. They contained an ATmega169 controller from Atmel, and a team from microcontroller.net hacked the thermostat and published the results (in German). Especially their pdf on the hardware revealed many interesting information. The thermostat has a 10 pin header with JTAG for programming, a GPIO Pin (PE2) and a serial interface.

We reprogrammed the thermostat with Bascom (Basic for AVR) and used the serial interface to communicate with the thermostat.

With new techniques like LoRaWan and ESP chips I think re-hacking would be interesting :).

Homeexpert by Honeywell Rondostat style

I have some old thermostats, but thought to look if the thermostat still exists, and for sure I found the same model with a rounder design. It is called homeexpert by Honeywell Rondostat style. To hack something the first challenge is always to open it without damaging the housing. First the big wheel can be removed with a screwdriver and by pulling hard. Than the two pieces of the axes are pulled apart (picture bottom). Now two of the fittings can be pushed with a screwdriver to pull off the display with the buttons.


Rondostat style

And for sure, the circuit seems to be the same as 9 years ago with an ATmega169 :)).


Rondostat style PCB front Rondostat style PCB back

How to program with Arduino

To program we need a JTAG programmer. I still have my old Dragon board from Atmel (now microchip), that is supported by avrdude used by Arduino. Another AVR JTAG programmer should do the same thing.

Her is the circuit of a little adapter board to connect the Dragon board to the thermostat.


avr dragon jtag adapter

Now we need an hardware core to use with Arduino. Fortunately MCUdude did already some work and we can download the core (.zip) at: https://github.com/MCUdude/ButterflyCore

Make a folder called hardware in your sketchbook folder. In the hardware folder you have a vendor folder with the vendor name and in this folder the architecture folder (here avr). So we get a portable/sketchbook/hardware/weigu/avr/ and uncompress the zip file here. Now we find the ATmega169 under Butterfly boards. We add the following lines to programmers.txt:

    dragon.name=Atmel AVR Dragon in JTAG mode
    dragon.communication=usb
    dragon.program.tool=avrdude
    dragon.protocol=dragon_jtag

I also had to exchange in line 222 of boards.txt the atmega169p with atmega169. I changed also the high_fuses=0xD6 to 0x96. This prevents the bootlader to destroy the JTAG programming access. Then I copied a more recent avrdude.conf to the folder. After this we have to restart Arduino.

The file pins_arduino.h in /portable/sketchbook/hardware/weigu/avr/variants/standard/ tells us what numbers Arduino uses for the pins:

AVR pin 0 1 2 3 4 5 6 7
PA 44 43 42 41 40 39 38 37
PB  8  9 10 11 12 13 14 15
PC 28 29 30 31 32 33 34 35
PD 18 19 20 21 22 23 24 25
PE  0  1  2  3  4  5  6  7
PF 45 46 47 48 49 50 51 52
PG 26 27 36 16 17 - - -

We get 53 digital pins and 8 analog pins (PF0-PF7 as A0-A7).

With avrdude we can read the lock and fuse bits:

    cd arduino/arduino-1.8.10/hardware/tools/avr/bin
    ./avrdude -C../etc/avrdude.conf -v -patmega169 -cdragon_jtag -U lock:r:"lock.out":r
    ./avrdude -C../etc/avrdude.conf -v -patmega169 -cdragon_jtag

We must read the file lock.out with an hex editor and see that the lock bits are 0xFC or 0b1111101, so LB1 and LB2 are programmed: Further programming and reading of the Flash and EEPROM are disabled.

And we get: lfuse 0x62, hfuse 0x91 and efuse 0xFD.

lfuse 0x01100010 means:

hfuse 0x10010001 means:

efuse 0x11111101 means:

So we choose 1 MHz internal clock and the right brown out level (1.8 V) to program the board.


Arduino tools screen

Let's test it with a blink sketch for the external GPIO Arduino digital pin 2 (PE2):

    void setup() {
      init_gpio();
    }

    void loop() {
      gpio_low();
      delay(500);
      gpio_high();
      delay(500);
    }

    void init_gpio() {
      pinMode(2, OUTPUT);
    }

    void gpio_low() {
      digitalWrite(2, LOW);
    }

    void gpio_high() {
      digitalWrite(2, HIGH);
    }

Don't forget to press shift to use the external programmer to upload the sketch.

Yes! it works!

To change fuses you can use (e.g. no clock divide by 8), but pay attention to not brick the device (JTAG disabled):

./avrdude -C../etc/avrdude.conf -v -patmega169 -cdragon_jtag -U lfuse:w:0xE2:m

Testing the Hardware

Motor

Now lets turn the motors. We need the following Arduino pins: 12 (PB4), 15 (PB7), 16 (PG3) and 17 PG4:

  12 15 16 17
STOP 0 0 0 0
OPEN 1 0 1 0
CLOSE 0 1 0 1
    void setup() {
      init_motor();
    }

    void loop() {
      motor_open();
      delay(3000);
      motor_close();
      delay(3000);
      motor_stop();
      delay(13000);
    }

    void init_motor() {
      pinMode(12,OUTPUT);
      pinMode(15,OUTPUT);
      pinMode(16,OUTPUT);
      pinMode(17,OUTPUT);
    }

    void motor_close() {
      digitalWrite(12,LOW);
      digitalWrite(15,HIGH);
      digitalWrite(16,LOW);
      digitalWrite(17,HIGH);
    }

    void motor_open() {
      digitalWrite(12,HIGH);
      digitalWrite(15,LOW);
      digitalWrite(16,HIGH);
      digitalWrite(17,LOW);
    }

    void motor_stop() {
      digitalWrite(12,LOW);
      digitalWrite(15,LOW);
      digitalWrite(16,LOW);
      digitalWrite(17,LOW);
    }
Switch "thermostat mounted"

The switch "thermostat mounted" 8 (PBO) is closed if thermostat is not mounted! We use an internal pull up resistor.

    void setup() {
      init_switch_tm();
      init_motor();
    }

    void loop() {
      if (switch_tm()) {
        motor_open();
      }
      else {
        motor_stop();
      }
    }

    void init_switch() {
      pinMode(8, INPUT_PULLUP);
    }

    bool switch_tm() {
      return digitalRead(8);
    }
User commands

We get three push-buttons and 2 inputs from the our rotary pulse encoder.

    void setup() {
      init_rotary();
      init_motor();
    }

    void loop() {
      if (rotary() == 0x01) {
        motor_open();
        delay(100);
      }
      if (rotary() == 0x10) {
        motor_close();
        delay(100);
      }
      motor_stop();
    }

    void init_rotary() {
      pinMode(13, INPUT_PULLUP);
      pinMode(14, INPUT_PULLUP);
    }

    byte rotary() {  // 0-3: AUTO|MANU = 4 °C = 2 PROG = 1
      return (digitalRead(13)*2+digitalRead(14));
    }
Temperature with NTC
Display

The display pins are named Com0-Com2 and Seg0-Seg21. All these pins are connected with the µC pins, set as output.

Com 0 1 2
  44 43 42
  PA0 PA1 PA2
Seg 0 1 2 3 4 5 6 7 8 9 10 11 12
  40 39 38 37 36 28 29 30 31 32 33 34 35
  PA4 PA5 PA6 PA7 PG2 PC7 PC6 PC5 PC4 PC3 PC2 PC1 PC0
Seg 13 14 15 16 17 18 19 20 21
  27 26 25 24 23 22 21 20 19
  PG1 PG0 PD7 PD6 PD5 PD4 PD3 PD2 PD1

The display uses the 1/3 duty und 1/3 bias mode and also the low power waveform mode. The mode is asynchronous (bit LCDCS = 1) and uses the crystal frequency of 32.768 kHz. This value is divided by (KND) to get frame rate. We choose K=6 (1/3 duty), N=16 and D=6 which gives us a frame rate of 56 Hz

In our lcd_init() function we mask the direction registers (DDR) to set the pins driving the display to output. The 4 LCD register LCDCRA, LCDCRB, LCDFRR, and LCDCCR are set accordingly (see data sheet or pdf from microcontroller.net). Finally the 9 LCDDR (0-2, 5-7, 10-12) register are used to write to the display (look at the same pdf).

    void setup() {
      init_lcd();
    }

    void init_lcd() {
      DDRA = DDRA | 0b11110111; // Com0-COM2 + Seg0-Seg3 (PA0-PA2, PA4-PA7)
      DDRC = DDRC | 0b11111111; // Seg5-Seg12  (PC7-PC0)
      DDRD = DDRD | 0b11111110; // Seg15-Seg21 (PD7-PD1)
      DDRG = DDRG | 0b00000111; // Seg4 (PG2) + Seg13-Seg14 (PG1,PG0)
      LCDCRA = 0b11011000; // initialise 4 LCD register (data sheet)
      LCDCRB = 0b10100101;
      LCDFRR = 0b00000101;
      LCDCCR = 0b00001111;
      LCDDR0 = 0b00000000; // init screen with Auto, sun and text "CO22"
      LCDDR1 = 0b00000000;
      LCDDR2 = 0b00000000;
      LCDDR5 = 0b10101001;
      LCDDR6 = 0b11100110;
      LCDDR7 = 0b00101110;
      LCDDR10 = 0b10111000;
      LCDDR11 = 0b00110101;
      LCDDR12 = 0b00000111;
    }
Reflection sensor

Honeywell Home evohome THR092HRT

I thought it possible that Honeywell uses Atmel chips in newer devices and bought a Honeywell Home evohome THR092HRT and jackpot! We get USB and an ATmega644. Hopefully we can hack the thing and program our own RF thermostat.