A Flicker of Hope!

I noticed a marked flicker in the Newhaven Display. It turns out, it’s a glitch in the firmware based on any active communication with the display. Luckily, Newhaven Display acknowledges this, and is willing to exchange / upgrade the firmware. Thank goodness!

I’m at a point where things are really starting to come together piece-wise. I have a few things left to test, including the MID400 line sensors to detect the rocker-switch status’. So far, I have the ADS1247 analog-to-digital converter, LCD display, DS3231M real-time clock, and solid-state relay integrated together. Theoretically, it could operate as an effective PID. But of course, I’m looking to implement much more before I put it to practice.

I’m also fussing around a bit with the code – trying to get everything neat and tidy as opposed to the rats nest that I’ve been using. I’m toying with different looks on the display and how to implement a lean yet effective visual menu.

You can see on the display, the first line is dynamic and displays the real-time clock, boiler status and current temperature (I’ve got a resistor in place to simulate the PT100). The second line of the display shows the custom characters that I’ve designed so far – the thermometer, “Brew”, “Hot Water”, and “Steam” icons. I intend for the latter three to activate as appropriate depending on the switch status.

I’m imagining two modes within the system – Automatic or Manual. In automatic mode, the system would wait for the brew switch to be thrown and then activate the pump. The system would then toggle the pump to control pre-infusion and extraction as programmed. In this mode, the three-way solenoid valve would remain engaged during the pre-infusion to prevent the pre-infusion from back-flushing through the solenoid valve. The solenoid valve disengages when the brew switch is turned off. In manual mode, the system will wait for the brew switch and engage the pump until the switch is turned off. Throwing the hot-water switch has much the same effect in both modes, except that the solenoid valve is not engaged. Switching temperature on the controller to a “steam” temperature (greater than 120°C) and throwing the steam switch will automatically pulse the pump to fill up the boiler. Of course there will be a number of programmable presets so that you can tailor pre-infusions and brew temperatures (or even tea temperatures!). These presets will be able to be set by “learning” how you operate the controls.

It might be a bit before I get the code worked out, but I suppose a few extra espressos might keep me on the ball.

Time and the DS3231M

Because the Arduino Pro Mini lacks the ability to accurately maintain time over extended periods of time, and because of the complexities of time-keeping, I decided to implement an external real-time clock (RTC). Ultimately I want Silvia to turn herself on, and warm up to temperature long before I rise and shine in the morning. I decided to go with an RTC chip produced by Maxim / Dallas Semiconductors – the DS3231M. This is a tiny 8-pin SOIC chip with onboard resonator that can communicate via I2C. As the display also uses I2C, the addition of this little chip on the I2C bus requires minimal effort. Of importance, this chip automatically performs carryover for seconds, minutes, hours, days, weeks, months, years, and century, and supports 24 and 12-hour timekeeping. Literally, you simply have to set it, and forget it. I was able to obtain four of these chips through the Dallas / Maxim sample program – plenty of chips to experiment with and place in the final prototype.

I’ve got both the display and RTC wired up to the Pro Mini – so far, everything is great!

To get the devices up and running together, I use the following skit:


#include <Wire.h>
#include <LCDNHDI2C.h>

//Custom Display Characters
const uint8_t custChar0[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; //Blank Char

uint8_t timeH, timeM, timeS;

LCDNHDI2C LCD = LCDNHDI2C(2, 16, 0x50>>1);
uint8_t lcdBuffer[2][17], lcdBufferCount;
unsigned long dispUpdateInterval; //Number of millis that must pass before the display is updated

void setup()
{
//Turn on I2C Bus
Wire.begin();
LCD.init();
LCD.setCharacter(0, custChar0);
LCD.setBacklight(0x0);
LCD.setContrast(0x2D);

Wire.beginTransmission(0xD0>>1);
Wire.write(0x00); //Point to register 0x00
Wire.write(0x00); //Set Seconds
Wire.write(0x28); //Set Minutes
Wire.write(1<<6|1<<5|0x11); //Set 12hr bit and PM bit with Hours
Wire.endTransmission(true);
}

void loop()
{
if ((millis() - dispUpdateInterval) > 50)
{
dispUpdateInterval = millis();
Wire.beginTransmission(0xD0>>1);
Wire.write(0x00);
Wire.endTransmission(false);
Wire.requestFrom(0xD0>>1, 3);
timeS = Wire.read();
timeM = Wire.read();
timeH = Wire.read();
//Line 0
LCD.setCursor(0,0);
if (1<<5 & timeH) { lcdBufferCount = sprintf((char*)lcdBuffer[0], "%X:%02Xp", timeH & 0x1F, timeM, timeS); } //If the PM bit is set, format the display string with a 'p'
else { lcdBufferCount = sprintf((char*)lcdBuffer[0], "%X:%02Xa", timeH & 0x1F, timeM, timeS); } //If the PM bit is not set, format the display string with an 'a'
Wire.beginTransmission(0x50<<1);
Wire.write(lcdBuffer[0],16);
Wire.endTransmission(0x00);
}
}

 

You will also need my revision of the Newhaven Display LCD library.

 

Sprintf and the Arduino IDE

I’ve had to recompile the Arduino IDE to change the linking process in order to enable true “float” or “double” compatibility when using sprintf() or printf(). These functions are fantastically useful when looking to create a final string that can be sent to a display for example. Unfortunately the main Arduino IDE builds available link against a pared down version of the function that replaces floats with a nasty ‘?’. The recompiled build increases the final sketch build size for all sketches but it is worth it if you need float-related string parsing. For example, I am using the float-enabled sprintf function to place dynamic text (such as temperature or a raw value) in a fixed (predetermined) statement. Because my display is limited in character space, I need to manage display real-estate and can set fixed widths for things such as strings or temperatures etc.

If you wish to use my compiled version of the Arduino IDE, it can be downloaded here: http://josh.to/E2nS (OSX Only).

She’s Got a Pulse!

So I’ve been tinkering with some code to establish a true PWM signal from the Arduino in order to drive the boiler. Rather than implement a software based “Is it time yet?” approach, I thought I’d take advantage of the 16-bit Timer1 on the Atmega328P (the Pro Mini’s brain). It turns out, you can pre-scale the system clock that drives this timer to as low as about 30Hz, set the number of ticks that must occur before the timer resets, and finally set the number of ticks that must occur before the PWM pin is toggled. All of this requires accessing the system registers on the chip, but ultimately allows me to simply set a value and forget about. Moreover, this value can be updated every single program loop based on a simple PID calculation without glitching out any PWM cycles. All in all, it becomes an efficient and effective use of the underlying PWM hardware to manage a fairly passive process.

Here’s the code that I’m using in setup():


//Define Boiler PWM
DDRB |= 1<<PORTB1; //Sets Pin 9 As Output
TCCR1A = 0; //Clear Timer Counter Control Register A.
TCCR1B = 1<<WGM13; //Set WGM13 - Mode 8 Uses ICR1 as max and OCR1 as width. Mode is symmetrical!
ICR1 = 0x7FFF; //Defines the number of ticks (15 625Hz) per half PWM period (due to PWM symmetry).
OCR1A = 0x4000; //Match Value - Change this from 0x0000 to ICR1 value to adjust PWM.
TCCR1A |= 1<<COM1A1; //OC1A (Pin 9) Clear On Match Up, Set on Match Down.
TCCR1B |= 1<<CS12 | 1<<CS10; //Start Timer With 1024 Prescaler: 16MHz / 1024

All you have to do is change the value of register OCR1A and it will change the PWM value. Note that this value should never exceed the value stored in ICR1 otherwise the PWM will never toggle. ICR1 can be has high as 0xFFFF giving a period of ~8.1s.

This is now easily implemented in a PID control loop. My current algorithm will keep the temperature within 1°C. I’m working to refine it a bit more though :).

Hearing Noises – Silvia’s Craniotomy and the ADS1247

This specific update is going to be brief, but it’s to discuss the mild learning curve that’s required to implement sensitive devices in an electrically noisy environment. It turns out that both Silvia’s pump, as well as the three-way solenoid valve generate a significant amount of inductive noise when they’re switched on or off. So much so that it’s sufficient to overload the SPI bus between the Arduino and the ADS1247. The ADS1247 has inherent 50/60Hz filtering – but the SPI bus does not. With my initial testing, every time I energized the pump or solenoid valve, the ADS1247 would effectively go “dead” due to communication problems. It would require a complete power cycle of the setup to restore the system.

In my original design, I had the slave-select (SS) line of the ADS1247 tied permanently to ground as it’s the only device on the bus and will always serve as a slave. But it turns out, this pin can also be used pull the SPI bus into a high-impedance mode that effectively resets the SPI bus (but not the rest of the ADS1247) and effectively makes it “deaf” to this inductive noise. I tied this pin to the Arduino’s SS line (pin 10) and have now implemented toggling of the SS line in the code. According to the Atmega328P datasheet, the SS line can be safely used as a GPIO without forcing the Atmega328P into slave mode as long as it’s set to output and never to input. Note that the code accesses the registers directly. Since I’ve made this change, I’ve not had a single problem.

Note that the code accesses the registers directly. For anyone using this code, make sure to pull SS low when you want to communicate with the ADS1247 and high once you’re done. Out of good practice, you should also write a NOP (0xFF) to the device before pulling the SS high – this will ensure that the last few bits have been read in / out before the SS line goes high.


//A2D Setup
DDRB |= (1<<DDB5) | (1<<DDB3) | (1<<DDB2); //Set SCK, MOSI, SS Pins as Output
PORTB |= 1<<PORTB2; //Pull SS High to Disable Communications with ADS1247
SPCR = (1<<SPE) | (1<<MSTR) | (1<<SPR0) | (1<<CPHA); //Set SPI and Enable
PORTB &= ~(1<<PORTB2); //Pull SS Low to Enable Communications with ADS1247
SPI.transfer(0x06); //Reset
delay(210);
SPI.transfer(0x16); //SDATAC
SPI.transfer(0x4B); //Set IDAC1 Register (0Bh) Write 01h - Output reference current on ANIN0,1
SPI.transfer(0x00);
SPI.transfer(0x01);
SPI.transfer(0x4A); //Set IDAC0 Register (0Ah) Write 07h - Select 1.5mA reference current for RTD
SPI.transfer(0x00);
SPI.transfer(0x07);
SPI.transfer(0x43); //Set SYS0 Register (03h) Write 52h - PGA:32, Sample at 20sps
SPI.transfer(0x00);
SPI.transfer(0x52);
SPI.transfer(0x42); //Set MUX1 Register (02h) Write 30h - Select internal reference always on, internal ref connected to REF0 pins. Use 33h if wanting an on chip temp read.
SPI.transfer(0x00);
SPI.transfer(0x30);
SPI.transfer(0x40); //Set MUX0 Register (00h) Write 01h
SPI.transfer(0x00);
SPI.transfer(0x08);
SPI.transfer(0xFF); //NOP to prevent CS High too soon
PORTB |= 1<<PORTB2; //CS High

Measuring Temperature and Resistance on the Auber Instruments RTD using the ADS1247 and Arduino

Just wanted to see if I could get some real readings from the prototype design that I’ll use in the Arduino / Silvia Duetto Project. It’s alive!

I should say that I’m using the Callendar-Van Dusen equation and not a linear approximation to estimate temperature. The A2DResRatio value must be set and calculated as appropriate given the RREF, RBIAS and PGA setting used with the ADS1247.

For best results, view this in HD and with fullscreen!

Code:

#include <SPI.h>

//A2D Values
const double A2DConstA = 3.9083E-3;
const double A2DConstB = -5.775E-7;
const double A2DResRatio = 5.587936114E-6;
const double A2DResOffset = 150;

unsigned long A2DVal = 0x0;

void setup()
{
//A2D Setup
//Turn on SPI Bus
SPI.setClockDivider(SPI_CLOCK_DIV16);
SPI.setDataMode(0x04);
delay(2000);
SPI.begin();
SPI.transfer(0x06); //Reset
delay(3);
SPI.transfer(0x16); //SDATAC
SPI.transfer(0x4B); //Set IDAC1 Register (0Bh) Write 01h - Output reference current on ANIN0,1
SPI.transfer(0x00);
SPI.transfer(0x01);
SPI.transfer(0x4A); //Set IDAC0 Register (0Ah) Write 07h - Select 1.5mA reference current for RTD
SPI.transfer(0x00);
SPI.transfer(0x07);
SPI.transfer(0x43); //Set SYS0 Register (03h) Write 52h - PGA:32, Sample at 20sps
SPI.transfer(0x00);
SPI.transfer(0x52);
SPI.transfer(0x42); //Set MUX1 Register (02h) Write 30h - Select internal reference always on, internal ref connected to REF0 pins. Use 33h if wanting an on chip temp read.
SPI.transfer(0x00);
SPI.transfer(0x30);
SPI.transfer(0x40); //Set MUX0 Register (00h) Write 01h
SPI.transfer(0x00);
SPI.transfer(0x08);

//Enable Serial
Serial.begin(115200);
}

void loop()
{
//Reset A2D Storage Value
A2DVal = 0x0;

SPI.transfer(0x12); //Issue RDATA
A2DVal |= SPI.transfer(0xFF);
A2DVal <<= 8;
A2DVal |= SPI.transfer(0xFF);
A2DVal <<= 8;
A2DVal |= SPI.transfer(0xFF);

Serial.println(DtoT(A2DtoD(A2DVal)),2);

delay(1000);
}

double A2DtoD (long A2D)
{
if (A2D & 0x800000) {A2D |= ~0xFFFFFF;}
return A2D;
}

double DtoT(double A2D)
{
return -1 * ( A2DConstA / ( 2 * A2DConstB ) - sqrt( 25 * A2DConstA * A2DConstA + A2DConstB * ( A2D * A2DResRatio + A2DResOffset ) - 100 * A2DConstB ) / ( 10 * A2DConstB ) );
}

double DtoR(double A2D)
{
return A2D*A2DResRatio+A2DResOffset;
}

Time for an Espresso

So I’ve decided that I’d like the ability for the Arduino / Silvia Duetto to be able to maintain the time and consequently warm her belly up at specific times during the day. As any Silvia owner knows, it is extremely important to have both Silvia’s boiler as well as the group-head and portafilter up to temperature prior to pulling a shot.

I selected the Maxim / Dallas Semiconductors DS3232 as a suitable real-time clock (RTC). This handy little device has a built-in crystal (XTAL) oscillator and most importantly operates over an I2C bus. As pin space is limited on the Arduino Pro Mini, both the display I’ve selected and now this RTC have the ability to share the same two data pins. Moreover this RTC is rated for industrial temperatures at -40°C to 85°C and contains a built in temperature sensor to provide temperature compensation to the device (as the heat will fluctuate inside Silvia, so will the resonant frequency of the XTAL).

Maxim / Dallas Semiconductors also have a great sample program which means I got 4 of these chips for free! I’m off to wire up a test circuit. More to come soon!

 

On Simplicity and Sensitivity – The Texas Instruments ADS1247

I’m still very much in the prototyping and design phase of this project – trying out component circuits with a hodge-podge of jumper wires and breakout boards to ultimately create a working prototype.

An analog to digital converter (A2D) takes an analog signal within a specific voltage range and estimates the numerical value that best represents that signal. Because the Resistance-Temperature-Device (RTD) used in this project changes resistance as temperature changes, we need a way to accurately and reliably measure the subtle changes in resistance that will occur. I’m aiming for an accuracy of 0.1°C or better for this project which translates to a change of only 0.0379Ω per 0.1°C. So here’s the rationale: if we pass a fixed current across the RTD, then the voltage will always be proportional to the resistance according to Ohm’s law. If we can find a suitably sensitive device to measure the small changes in voltage, we have a way to measure the PT100 resistance and thus estimate the temperature. Moreover, the RTD is only expected to have ~80Ω worth of change in the temperature range that Silvia will be operating. At the low currents that we’ll be using on the RTD, we’re looking at only 120mV of voltage change. That’s a fraction of the total 0V-5V range that normal A2Ds use as a reference (including the A2D on the Arduino Pro Mini). So, we need a way to scale up that 120mV to something that’s easier to quantify. Most people would turn to an Op Amp based circuit. This type of device effectively feeds back on itself to scale an analog signal in amplitude. However, this requires a bit of external circuitry and can be subject to electrical noise if the circuit isn’t clean.

Enter the ADS1247 – an all-in-one device that: has 24 bits of resolution (21 effective bits, or 2.1 x 106 possible values), generates a fixed current on its analog pins, and can scale input voltages using a Programmable Gate Array (PGA) (similar to an op-amp circuit). This chip can communicate with the Arduino via 3 pins dedicated to the SPI communication bus.

I will eventually publish the full schematic, however the basic design for a circuit incorporating the ADS1247 chip is simple and follows a similar design to the TI published example (see Example 7). Effectively I will use a low tolerance 140Ω (0.05% tolerance) resistor as RREF to centre the 80Ω resistance swing at the half-way point (~140Ω at 100°C) and a 750Ω (0.05% tolerance) resistor as RBIAS to create a reference voltage (±2.25V). I’ll set the analog input pins to output a fixed 1500μA current and the PGA to multiply the ±60mV voltage developed across the analog input pins by 32 to ±1.92V (and well within the ±2.25V reference range). With this setup, I expect to obtain a maximum resolution of ~8.4 x 10-9 V per bit. Fantastic!

While this design is fine in theory, I wanted to try it in practice. More importantly, I wanted to see if I could even get it working smoothly with the Arduino. So I wired up a prototype circuit based on TI Example 7 using a couple of resistors as RREF and RBIAS, as well as a trim-pot to simulate the RTD. The only other component required was a 10μF capacitor between VREFCOM and VREFOUT to stabilize the internal voltage reference.

I coded up the Arduino as follows:

#include <SPI.h>

unsigned long A2DVal=0x0;

void setup()
{
SPI.setClockDivider(SPI_CLOCK_DIV16); //1MHz Bus Speed
SPI.setDataMode(0x04); //SPI_MODE1
delay(2000); //Give you time to open up the Serial monitor as it will restart the Arduino but not ADS1247
SPI.begin(); //Turn on the SPI Bus
SPI.transfer(0x06); //Reset the ADS1247
delay(2); //Minimum 0.6ms required for Reset to finish.
SPI.transfer(0x16); //Issue SDATAC
SPI.transfer(0x40); //Set MUX0 Register (00h) Write 01h
SPI.transfer(0x00);
SPI.transfer(0x01);
SPI.transfer(0x42); //Set MUX1 Register (02h) Write 38h - Select internal reference always on, internal ref connected to REF0 pins. Use 33h if wanting an on chip temp read.
SPI.transfer(0x00);
SPI.transfer(0x33);
SPI.transfer(0x43); //Set SYS0 Register (03h) Write 52h - PGA:32, Sample at 20sps
SPI.transfer(0x00);
SPI.transfer(0x52);
SPI.transfer(0x4A); //Set IDAC0 Register (0Ah) Write 07h - Select 1.5mA reference current for RTD
SPI.transfer(0x00);
SPI.transfer(0x07);
SPI.transfer(0x4B); //Set IDAC1 Register (0Bh) Write 01h - Output reference current on ANIN0,1
SPI.transfer(0x00);
SPI.transfer(0x01);

//Enable Serial
Serial.begin(115200);
}

void loop()
{
//Reset A2D Storage Value
A2DVal = 0x0;

SPI.transfer(0x12); //Issue RDATA
A2DVal |= SPI.transfer(0xFF); //Write NOP, Read First Byte and Mask to A2DVal
A2DVal <<= 8; //Left Bit-Shift A2DVal by 8 bits
A2DVal |= SPI.transfer(0xFF); //Write NOP, Read Second Byte and Mask to A2DVal
A2DVal <<= 8;
A2DVal |= SPI.transfer(0xFF); //Write NOP, Read Third Byte and Mask to A2DVal

//SPI.transfer(0x22); //Read Register 0x2
//SPI.transfer(0x00); //N - 1 Bytes To Be Read
//A2DVal |= SPI.transfer(0xFF); //Mask to A2DVal

Serial.println(A2DVal,HEX);
delay(1000);
}

The ADS1247 also has an onboard temperature sensor that can be read instead of the analog inputs by writing to MUX1 register with 33h. When doing this, the value returned by the Arduino was 0764XXh where XX (the least significant byte) fluctuated. I could push this up to 0771XXh when I blew on it with my breath or touched the chip with my finger! I also tried out the actual analog inputs (MUX1 should be set to 38h) and was able to get the value to change according to the trim pot.

It’s now just a matter of sourcing some low tolerance resistors for the final circuit – let me know if anyone knows of any vendors!

More to come soon.