Archive for Mods
Gaggia Espresso PID Arduino Mod
Modding the Gaggia Espresso:
Adding a PID temperature controller and computer interface.

Goal
The goal of this project is to create a computer interface for my Gaggia Espresso machine replacing the factory thermal switches with a PID controller and the “steam” and “pump” toggle switches with a LCD/button menu system.
Motivation
After seeing the various instructions and kits geared toward modding a home espresso machine with a PID, and their costs and features compared to something like the PID PIC MOD, I decided I would start from scratch and build my own PID. Then I could easily extend it to control the rest of the espresso machine, such as adding a timer to monitor or control the shot time and adding temperature presets.
Hardware
Parts list:
- 1x Arduino Decimillia
- 1x 25A zero-cross SSR (for pump, could be much smaller, > 5A)
- 1x 40A zero-cross SSR (for heating element, could be 25A)
- 1x AD595 thermocouple conditioner/interface IC
- 1x Type-K thermocouple (could use type T or possibly other)
- 1x 16×2 character LCD with HD44780 parallel interface
- 3x SPST normally-open momentary push-button switch
- 10AWG stranded wire for 125V wiring (12AWG is probably good enough)
- 22AWG solid wire for 5V wiring
- Power supply for Arduino
- Various resistors, capacitors, quick-disconnect connectors for 10AWG wire, etc.
I’m using an Arduino Decimillia micro-controller which utilizes the AVR AtMega168. The hookup of the components is pretty straight-forward; The LCD uses 6 digital IO pins - 4 for a 4 bit parallel data bus, one for Register Select and one for Operation (data read/write) enable signal. The SSRs take one digital IO pin each. The AD595 takes one 10 bit AD converter pin. The three buttons share one external interrupt pin and use two digital IO pins to indicate which button has been pressed; with this scheme, one interrupt can be used with 2^n buttons taking only n+1 pins. Various resistors, capacitors and other components are used, but the details are rather tedious and boring.
Software
The software was written in the Arduino Programming Language, which is based on Processing and is similar to C++ with a few java-like syntactical niceties thrown in.
The main loop is executed at about 100Hz. During each iteration of the loop the AD595 is read and summed 100 times and after 100 iterations the average is taken and the current temp is updated. I started by oversampling 100x but found that that resulted in poor accuracy and provided many more readings than I was using. After some experimentation I found that oversampling 10,000 times gave great accuracy (about +/- 0.1 deg. Farenheit) and didn’t introduce any negative side effects due to excessive smoothing of the temperature curve. Also, since the PID period is one second, oversampling 10,000 times, giving me one reading per second, is just enough for the PID to have an updated reading at each iteration.
The PID algorithm is based off of this article: PID without a PHD. One of the bigger problems I had was tuning the PID, as seems to be the case for many people. What gave me such a hard time was all the instructions on the internet about how to tune a PID. Most say to start with Ki and Kd set to 0 and adjust Kp first. They go on to say that Ki can then be set to remove any static offset from the setpoint. Kd is said to be tricky to set and unnecessary for most applications.
After having trouble with that tuning technique and observing the system in operation a bit I realized that those instuctions wouldn’t work for me. The problem with relying on Kp to correct most of the error is that you have to be below the setpoint before any correction occurs (unless you add a constant positive error term to each reading). Because the system takes a few seconds to respond, the temp dips pretty low before it starts to reverse. Likewise, as the temp is climbing towards the setpoint, proportional error correction is applied right up until hitting the setpoint, at which point too much heat has been added and the temp overshoots the setpoint considerably. Adding ANY Ki just makes things worse. It dawned on me that the system was responding way too late, so the PID needed to anticipate the future state of the system in order to be effective. The future temp depends on two things, the current temp and the rate of change of temp. After observing that the temp would climb about 15 deg. after shutting off the boiler at maximum rate of change and doing some simple math to figure out what some reasonable values for Kd and Kp should be, I came up with settings which hold the temp to within +/- 1 deg. farenheit of the setpoint, and not more than 4 or 5 deg. of over/undershoot when turning on or recovering from steam mode. Intra-shot stability is also quite good usually dipping no more than a couple of deg. farenhiet and ending back at setpoint by the end of a 30 sec. 1.75 - 2 oz. shot. This is with Kd set to an aggressive 5x Kp and Ki set to 0. I believe this can be improved with further tuning of the PID parameters.
Because the boiler is either off or on, I use a PWM (pulse width modulation) technique to control the boiler. I treat the PID output as a percentage and keep the boiler on for that percent of a second, updating every second. Anything < 0% is treated as 0% and anything > 100% is treated as 100%. I round the output to the tens so that I limit the minimum on-off cycle time of the boiler to 0.1 seconds. This is for several reasons; anything faster is really unnecessary, it wears the SSR less and it limits the amount of EM interference caused by switching 1300 watts on and off that quickly.
Results
The machine works great! I get better stability and control than I was expecting, and it came together pretty quickly with no major problems. Additionally, I still need to finish fine-tuning the PID parameters to maximize stability.
As for the Espresso, that is a whole other subject that I will dedicate a whole post to, but for now I will say that I’m getting some great results with my local San Francisco favorites — Blue Bottle Espresso Temescal, Blue Bottle Roman Espresso and Coffee to the People’s Beatnik Espresso. I’ll be picking up some Blue Bottle Hayes Valley Espresso and some Blue Bottle Retrofit Espresso shortly.
What I have left to do:
- Fine-tune the PID parameters
- Improve the accuracy of the temperature reading by using an equation to adjust for the slight non-linearity of the AD595
- Fix the sometimes flaky button circuit
- Save settings to EEPROM instead of reverting to defaults upon power cycle.
- Add persistent settings profiles that can be chosen via the setup menu.
- Calibrate the temp circuit
- Correlate the boiler temp to group head temp or add secondary thermometer to group head
- Show average error in +/- deg. F/C
- ???
The program is still pretty beta, with some features requiring cleaning up and others needing to be implemented. When it’s a bit more finished I will post the GPL’d source code, if there is any interest.
If you thought this was interesting, check out the PID PIC MOD. It’s a surprisingly similar project with a Rancilio Sylvia and gave me some high-level ideas for my project.
Nash Lincoln
iPod to BS2 update
I’ve made a good bit of progress since last posting. I have some pictures I will post, and some code for the BasicStamp2 and the uClinux that runs on my 3rd Gen. iPod.
BS2 Code
' {$STAMP BS2}
' {$PBASIC 2.5}
' IR navigation with PID control via ipod/linux serial link
' Nash Lincoln, 2007
' --- vars ---
freqSelect VAR Nib
irFrequency VAR Word
irDetectLeft VAR Bit
rDetectRight VAR Bit
distanceLeft VAR Nib
distanceRight VAR Nib
speedLeft VAR Byte
peedRight VAR Byte
dirLeft VAR Bit
dirRight VAR Bit
' --- cons ---
iPodOut CON 11
iPodIn CON 10
rightTP CON 1
leftTP CON 2
baudMode CON 84
motorPin CON 5
' --- init --
HIGH 14
PAUSE 1000
LOW 14
' -----[ Subroutine - NoData ]-----------------------------------------
NoData:
HIGH 14
PAUSE 50
LOW 14
speedLeft = 0
speedRight = 0
GOSUB Drive_Motors
' -----[ Subroutine - Main ]-----------------------------------------
Main:
DO
' collect input
GOSUB Get_Ir_Distances
' output sensor data to ipod for processing/logging
SEROUT iPodOut, baudMode, [distanceLeft,distanceRight]
' input control data from ipod
SERIN iPodIn, baudMode,2000,NoData,[speedLeft]
SERIN iPodIn, baudMode,2000,NoData,[speedRight]
' send control signals
GOSUB Drive_Motors
LOOP
' -----[ Subroutine - Get_Distances ]-----------------------------------------
Get_Ir_Distances:
distanceLeft = 5
distanceRight = 5
FOR freqSelect = 0 TO 4
LOOKUP freqSelect,[37500,38250,39500,40500,41500], irFrequency
FREQOUT leftTP,1,irFrequency
irDetectLeft = IN3
distanceLeft = distanceLeft - irDetectLeft
FREQOUT rightTP,1,irFrequency
irDetectRight = IN0
distanceRight = distanceRight - irDetectRight
NEXT
RETURN
' -----[ Subroutine - Drive_ Motors ]-----------------------------------------
Drive_Motors:
dirRight = speedRight.HIGHBIT
IF dirRight = 1 THEN
speedRight = speedRight & $7f
ENDIF
dirRight = ~dirRight
dirLeft = speedLeft.HIGHBIT
IF dirLeft = 1 THEN
speedLeft = speedLeft & $7f
ENDIF
'left motor
SEROUT motorPin,84,[$80,0,4+dirLeft,speedLeft]
'right motor
SEROUT motorPin,84,[$80,0,6+dirRight,speedRight]
RETURN
C Code for the iPod running iPodLinux
#include#include #include #include #include #define MODEMDEVICE “/dev/ttyUSB0″ #define _POSIX_SOURCE 1 /* POSIX compliant source */ main(int argc, char *argv[]) { char out1,out2; int fd, c, res; struct termios oldtio,newtio; char buf[255]; fd = open(MODEMDEVICE, O_RDWR | O_NOCTTY ); if (fd < 0) {perror(MODEMDEVICE); exit(-1); } tcgetattr(fd,&oldtio); /* save current port settings */ bzero(&newtio, sizeof(newtio)); newtio = oldtio; newtio.c_iflag &= ~(ICRNL | INPCK | ISTRIP | IXON); // I'm not sure how many of these newtio.c_iflag |= (BRKINT); newtio.c_lflag &= ~(ICANON | ECHO | ISIG | IEXTEN); // settings actually need setting. newtio.c_oflag &= ~(OPOST); newtio.c_oflag |= (ONOCR | ONLRET); newtio.c_cc[VTIME]= 10; /* inter-character timer */ newtio.c_cc[VMIN] = 2; /* blocking read until n chars received */ tcflush(fd, TCIFLUSH); tcsetattr(fd,TCSANOW,&newtio); ///// PID controller variables int fp = 10, fi = 1, df = 4; // PID weights int anti_windup = 15; // max val of errorI to prevent integral windup int errorPl, errorIl, errorDl, errorPpl = 0, totall, speedl = 0; int errorPr, errorIr, errorDr, errorPpr = 0, totalr, speedr = 0; int errorPoint = 5; int max_speed = 50; // can be up to 127 ///// // if you want, pass in some args // carefull, not much error checking here if(argc == 6){ sscanf(argv[1],"%d", &fp); sscanf(argv[3],"%d", &df); sscanf(argv[2],"%d", &fi); sscanf(argv[4],"%d", &anti_windup); sscanf(argv[5],"%d", &max_speed); } c = 0; while (c < 200) { /* loop */ res = read(fd,&buf,255); /* returns after n chars have been input */ buf[res] = 0; /* terminate string... */ /////////// PID for right wheel errorPl = errorPoint - (2 * buf[0]); errorDl = (errorPpl - errorPl); errorIl += errorPl; if(errorIl < -anti_windup) errorIl = -anti_windup; else if(errorIl > anti_windup) errorIl = anti_windup; errorPpl = errorPl; totall = errorPl * fp + errorIl * fi + errorDl * df; // make output a signed byte speedl += totall; if(speedl > max_speed) speedl = max_speed; else if(speedl < -max_speed) speedl = -max_speed; if(speedl < 0){ out2 = -speedl; out2 |= 0x80; } else out2 = speedl; /////////// /////////// PID for left wheel errorPr = errorPoint - (2 * buf[1]); errorDr = (errorPpr - errorPr); errorIr += errorPr; if(errorIr < -anti_windup) errorIr = -anti_windup; else if(errorIr > anti_windup) errorIr = anti_windup; errorPpr = errorPr; totalr = errorPr * fp + errorIr * fi + errorDr * df; // scale output and make output a signed byte speedr += totalr; if(speedr > max_speed) speedr = max_speed; else if(speedr < -max_speed) speedr = -max_speed; if(speedr < 0){ out1 = -speedr; out1 |= 0x80; } else out1 = speedr; /////////// if(c % 20 == 0){ // debug/display every nth iteration printf("Input: l:%d r:%dnOutput: l:%d r:%dn", buf[0],buf[1],speedl,speedr); } write(fd, &out1, 1); usleep(1000); // haven't tested smaller delay value yet. should write(fd, &out2, 1); c++; } tcsetattr(fd,TCSANOW,&oldtio); }
The code isn’t commented very well, and is a bit rough, so I’ll explain a bit of what’s going on.
The BS2 is connected to the iPod via the serial IO port on the iPod and pins 10 and 11 on the BS2.
I use a Pololu dual serial motor controller, so if you are using the continuous rotational servos you will have to modify the BS2 code appropriately. The main loop of the BS2 program writes 2 bytes of data — one byte for each distance reading IR emitter/detector pair, connected to BS2 pins 0 and 3 — and reads 2 bytes of data — speed settings, 1 byte for each wheel as a signed byte, from -127 to 127.
The c program is pretty straightforward, it reads 2 bytes of data and then runs through an iteration of a PID algorithm then outputs two values that correspond to left and right motor speed (-127 to 127 max, signed byte). You can specify weights via the command prompt or just use the defaults, which, again, are a little rough. The algorithm also features anti-windup. This program is hard-coded to point to /dev/ttyUSB0, you will probably have to change that or soft-link to /dev/ttyS0.
If you have any questions at all, please feel free to comment here or send email to nashira_lincoln @AT yahoo .dot com.

