Original posts including reverse engineering here:
http://hackcorrelation.blogspot.com/2013/07/building-new-firmware-for-senseo-coffee.html
http://hackcorrelation.blogspot.com/2013/11/senseo-custom-firmware-update.html
State of the project: pending deployment, some important details are missing:
- SAFETY!
- what's the water temperature used by the original system?
- I don't have a functioning heater/boiler anymore - used the working one for a repair
Hardware - mostly stock, I'm just putting in the MSP430 chip and a regulator. I kept the original board and removed the uC and EEPROM chips.
- three buttons: power, 1 coffee, 2 coffees
- the power button has a small red LED circle around it
- heater controlled via an SCR
- NTC thermistor inside the heater is sampled through a voltage divider
- water sensor is a hall type sensor that detects the floater position inside the water tank
- water pump is controlled via a small transistor
Operation:
- while line voltage is on but the machine is powered off the user can enter service modes via button combinations
- upon powering on the machine starts heating the water, displaying on the LED how hot the water is (PWM)
- if the user requests a coffee, the machine changes is blinking pattern and starts making a coffee once the water is heated
- if there is no water in the tank there is a faster blinking pattern
- if no user input is detected for 30 minutes the machine goes to power off state
- a lot of other safety stuff and under the hood stuff that you kind of expect to work
Why?
The original machine annoyed me (and my work mates) for various reasons:
- once plugged in, it stayed off, so if you used a remote outlet you have to physically move yourself to power on the machine, making the remote outlet kind of useless
- heating the water takes quite a lot of time (90s) and you cannot request a coffee in advance
- that 90s seems like forever because you have no indication how far you are from the target temperature; the led just blinks and then settles to an ON state
- if left unused for a while, the boiler temperature goes down quite a lot; this makes the next coffee colder than normal
Things to do after real-world testing is complete:
- the 2 coffee button is not implemented yet, not sure it is needed. Two presses of the other button achieve exactly the same thing and the water does not cool so much
- LPM3/4. Probably not needed, but I will test initially with a separate battery supply.
- water quantity calibration via a service mode. You should be able to increase/decrease the pump duration in 1 second increments via a service mode and the value saved in flash. The original chip did this
- add a jumper or some kind of configuration mode where you can have the machine prepare a coffee once it's powered on, for remote controlled outlet usage
Code and 'schematic', written for the Energia framework:
/***************************************************************************************
* // LEGEND: < from pin = digital output
* // > to pin = digital input
* // ^ pullup
* // $ analog
* // % pwm
* //
* // MSP430G2231 - on launchpad
* // ----------------- ^
* // |VCC GND| /|\
* // | | |
* // PWRLED %<|P1.0/A0/LED XIN|<^P2.6 CAFBTN |
* // | | |
* // |P1.1/A1 TXD XOUT|>P2.7 HEATER |
* // | | |
* // |P1.2/A2 RXD TEST| |
* // | | |
* // CAF2BTN ^>|P1.3/A3 S2 nRST|------------- /
* // | |
* // NTC/1.8k $>|P1.4/A4 A7/P1.7|< WATERSNS
* // | |
* // PWRBTN ^>|P1.5/A5 A6/P1.6|> PUMP (LED2)
* // -----------------
***************************************************************************************/
// NTC is 0.77k@93C in divider configuration with 1.8k so boiling voltage is less than Vcc*1.8/(1.8+0.77)=0.7*Vcc
// this means that for 3.3V going into divider the boiling point value is > 0.7*1024 = 717;
// 87C is about 0.643*Vcc (658) so cca 58 ADC points hysteresis
// pump debit is marked 2L/min but this does not seem right
//#define DEBUG_ON 1
//#define TEMPERATURE_DEBUG
#include <Energia.h>
static const uint8_t PWRLED = P1_0;
static const uint8_t CAF2BTN = P1_3;
#ifdef TEMPERATURE_DEBUG
static const uint8_t NTC = A10; //internal temperature sensor
#else
static const uint8_t NTC = P1_4;
#endif
static const uint8_t PWRBTN = P1_5;
static const uint8_t CAFBTN = P2_6;
static const uint8_t HEATER = P2_7;
static const uint8_t WATERSNS = P1_7;
static const uint8_t PUMP = P1_6;
static const uint32_t COFFEE_TIME = (uint32_t)25*1000;
static const uint32_t IDLE_TIME = (uint32_t)30*60*1000;
#ifdef TEMPERATURE_DEBUG
static const uint16_t BOILING_POINT = 307;
static const uint16_t TEMP_HYSTER = 3;
static const uint16_t HIGH_TEMP_SCALE = 315;
static const uint16_t LOW_TEMP_SCALE = 300;
#else
static const uint16_t BOILING_POINT = 717;
static const uint16_t TEMP_HYSTER = 3;
static const uint16_t HIGH_TEMP_SCALE = 700;
static const uint16_t LOW_TEMP_SCALE = 400;
#endif
// coffee state
static const uint16_t NOT_REQUESTED = 0;
static const uint16_t REQUESTED = 1;
static const uint16_t IN_PROGRESS = 2;
static const uint16_t _TEMP_SCALE = HIGH_TEMP_SCALE - LOW_TEMP_SCALE;
static const uint16_t _PWM_SCALE = 16;
unsigned long lastUserTime;
void setup() {
#if 0
BCSCTL1 = 0x8E; // 14.76MHz
DCOCTL = 0xE0; // 14.76MHz
BCSCTL1 = 0x8F; // 15.92MHz
DCOCTL = 0x90; // 15.92MHz
BCSCTL1 = 0x8F; // 15.95MHz
DCOCTL = 0x91; // 15.95MHz
BCSCTL1 = 0x8F; // 15.99MHz
DCOCTL = 0x92; // 15.99MHz
BCSCTL1 = 0x8F; // 16.03MHz
DCOCTL = 0x93; // 16.03MHz
P1DIR |= BIT4; // output dco clock on P1.4
P1SEL |= BIT4; // measure with scope or frequency counter
#endif
P2SEL &= ~(BIT6|BIT7);
// DCOCTL = 0; // avoid glitches
// BCSCTL1 = 0x8F; // 15.92MHz
// DCOCTL = 0x90; // 15.92MHz
pinMode(PWRLED, OUTPUT);
pinMode(CAF2BTN, INPUT_PULLUP);
pinMode(NTC, INPUT);
pinMode(PWRBTN, INPUT_PULLUP);
pinMode(CAFBTN, INPUT_PULLUP);
pinMode(HEATER, OUTPUT);
pinMode(WATERSNS, INPUT);
pinMode(PUMP, OUTPUT);
digitalWrite(HEATER, 0);
digitalWrite(PUMP, 0);
#ifdef DEBUG_ON
Serial.begin(4800); // msp430g2231 must use 4800
#endif
lastUserTime = millis();
}
boolean isCooling = false;
uint16_t analogPowerPWM;
boolean poweredOff = 0;
uint16_t coffeeState = 0;
unsigned long coffeeStartTime;
void loop() {
// for safety
if (poweredOff){
#ifndef DEBUG_ON
digitalWrite(HEATER, 0);
#endif
digitalWrite(PUMP, 0);
digitalWrite(PWRLED, 0);
coffeeState = NOT_REQUESTED;
}
volatile uint16_t currentCycle = (uint16_t)millis();
uint16_t temperatureValue = analogRead(NTC);
if (!poweredOff){
digitalWrite(PWRLED, analogPowerPWM>(currentCycle%_PWM_SCALE));
// TODO: water sensor
// heater management
if (currentCycle % (1<<4)){
// check water level
if (digitalRead(WATERSNS)){
if (temperatureValue>BOILING_POINT){
digitalWrite(HEATER, 0);
isCooling = true;
}
else if (temperatureValue>(BOILING_POINT-TEMP_HYSTER) && isCooling){
digitalWrite(HEATER, 0);
}
else{
digitalWrite(HEATER, 1);
isCooling = false;
}
//check water and coffee requested
if (coffeeState == REQUESTED && (temperatureValue>(BOILING_POINT-TEMP_HYSTER))){
coffeeState = IN_PROGRESS;
coffeeStartTime = millis();
// force heater on if needed
isCooling = false;
}
//handle coffee progress
if (coffeeState == IN_PROGRESS){
if (((uint32_t)(millis() - coffeeStartTime) >= COFFEE_TIME)){
// we linger for one more loop cycle but it's ok
coffeeState = NOT_REQUESTED;
lastUserTime = millis();
}
digitalWrite(PUMP, 1);
}
else{
digitalWrite(PUMP, 0);
}
}
else{
// no water
digitalWrite(PWRLED, ((currentCycle >> 6 ) % 2));
digitalWrite(PUMP, 0);
digitalWrite(HEATER, 0);
// we should reset the coffee state but then the pending request will be lost
}
}
if (currentCycle % (1<<9)){
// alternate between odd/even codes at same frequency
if ((currentCycle >> 9 ) % 2){
analogPowerPWM = coffeeState != NOT_REQUESTED ? 0 : 255;
}
else{
analogPowerPWM = (temperatureValue-LOW_TEMP_SCALE)*_PWM_SCALE/_TEMP_SCALE;
}
// check timeout
if (((uint32_t)(millis() - lastUserTime) >= IDLE_TIME)){
poweredOff = 1;
}
}
#ifdef DEBUG_ON
if (currentCycle % (1<<8)){
Serial.println(coffeeState);
}
#endif
if ((currentCycle % (1<<7)) && !digitalRead(CAF2BTN) && (coffeeState != IN_PROGRESS)){
lastUserTime = millis();
coffeeState = REQUESTED;
}
}//end if !poweredOff
// everything below happens even when powered off
if (currentCycle % (1<<7)){
if (!digitalRead(PWRBTN)){
delay(200);
// DEBUG MODE 1 - PWR + 1 COFEE button pressed
if (!digitalRead(CAF2BTN)){
// TODO
}
lastUserTime = millis();
poweredOff = !poweredOff;
}
if (poweredOff){
// DEBUG MODE 2 - 1 COFFEE + 2 COFFEE buttons pressed while off -> purge water
if (!digitalRead(CAF2BTN) && !digitalRead(CAFBTN)){
while (!digitalRead(CAF2BTN) && !digitalRead(CAFBTN)){
digitalWrite(PUMP, 1);
}
digitalWrite(PUMP, 0);
}
}
}
//TODO: add wake on buttons and go to LPM in between
//_BIS_SR(LPM4_bits | GIE);
}