how are you connecting the sensors to the beer line? I assume 1/2" NPT to 1/4" barb. Do you have any problems with foaming with that?
how are you connecting the sensors to the beer line? I assume 1/2" NPT to 1/4" barb. Do you have any problems with foaming with that?
The LadyAda example code is a little rough as I recall. At a serving rate of 6 pints per minute you're only getting 24 pulses per second and if you're sampling every second you might get 23 or 25 pulses per second which can add 10% error just in the sampling. I went with using TIMER1 and adding up the timer ticks and interrupt count, then dividing by the count to get a 1/62000th of a second resolution. Again, this may not be an issue because flow rate doesn't matter as much as pulse count does.
If you don't mind me spewing advice, here's what I found:
-- Orient the flow sensors in the exact orientation you will use them in before calibrating. They show a large sensitivity to being rotated on any axis so make sure they're firmly mounted so they won't go out of calibration because they're flopping around.
-- They are off by quite a bit as you reach the low end of their spec'ed flow range. This probably won't be a problem because you're calibrating at more or less the exact flow rate you'll be measuring.
-- The LadyAda example code is a little rough as I recall. At a serving rate of 6 pints per minute you're only getting 24 pulses per second and if you're sampling every second you might get 23 or 25 pulses per second which can add 10% error just in the sampling. I went with using TIMER1 and adding up the timer ticks and interrupt count, then dividing by the count to get a 1/62000th of a second resolution. Again, this may not be an issue because flow rate doesn't matter as much as pulse count does.
I'd like to see where this goes though, I hope you don't mind if I steal some ideas for my keezerstat as well. Mine already has an ATmega in it running the power relay, and draws a little graph, and changes colors, but I don't have any sort of volume measurement. I'd really like to see a CO2 level measurement interface so I won't run out in the middle of a party!
citywok said:I used weather stripping and then installed the collar. The LCD's are wired and working, 80mm dc fan installed. The LCD's took a lot longer than I thought they would, doh.
The lid stays put. I actual bought a replacement set of hinges for my model of freezer. That wat i had no need to modify the freezer or lid. I just screwed them into the collar. The hinges are strong enough to hold up the collar and lid. I thought it wasnt but it turned out i had a gas line pulling it down.
The high pressure gauges on CO2 tanks are virtually meaningless. They can only tell you the pressure of the gas inside the tank, but the majority of the CO2 is in liquid form. The gauge reads the same number until all the liquid has been converted to gas and then drop rather rapidly. At 70F the gauge should read somewhere around 850 psi, at 30F it should read below 500 psi. The only way to know how much CO2 is left is by weight.So I filled my 10lb co2, connected everything, and the regulator said the tank only had 700 psi (60% on the outer gauge). I ran a keg of water + cleaner through everything, and then a keg of just water, and it was down to 53%, then I turned the freezer on and this morning it's down to 35%, is that drop normal for going from 65f to 38f?
// Solder-free method of detecting the ROM of a DS18B20 - spread the 3 legs of the sensor wide enough to fit into GND, 13, and 12
// and place the sensor in these pins with the flat side facing the LED on the Uno and the round side facing away from the Uno.
// Then place a 4.7 KΩ resistor in pin 13 and 12. Unload and open a serial monitor with a baud rate of 9600.
//#include "OneWire.h" // An extremely space-efficient implementation of selected portions of the OneWire protocol
//#include "simpleTx.h" // An extremely space-efficient serial debug tool
#include <util/delay.h>
// ====================================================== Pre-Compiler Definitions ======================================================
#define PowerPin PB4 // Pin 12 - we will be using this pin to supply Vcc to the DS18B20
#define POWER_TEMP_PROBE PORTB |= _BV(PowerPin) // Define method for powering the DB18B20
#define DEPOWER_TEMP_PROBE PORTB &= ~_BV(PowerPin) // Define method for depowering the DB18B20
#define Pin PB5 // Set up pin 13 as the data pin
#define DIRECT_MODE_OUTPUT DDRB |= _BV(Pin)
#define DIRECT_MODE_INPUT DDRB &= ~_BV(Pin)
#define DIRECT_WRITE_HIGH PORTB |= _BV(Pin)
#define DIRECT_WRITE_LOW PORTB &= ~_BV(Pin)
#define DIRECT_READ PINB & _BV(Pin) ? 1 : 0
#define READROM 0x33 // Read the ROM of a OneWire device; there must only be one OneWire device on the bus!
#define STARTCONVO 0x44 // Tells device to take a temperature reading and put it on the scratchpad
#define READSCRATCH 0xBE // Read EEPROM
#define SKIPROM 0xCC
#define MATCHROM 0x55
#define TEMP_LSB 0
#define TEMP_MSB 1
#define clk_div 1
#define DELAY_A 6/clk_div
#define DELAY_B 64/clk_div
#define DELAY_C 60/clk_div
#define DELAY_D 10/clk_div
#define DELAY_E 9/clk_div
#define DELAY_F 55/clk_div
#define DELAY_G 0/clk_div
#define DELAY_H 480/clk_div
#define DELAY_I 72/clk_div
#define DELAY_J 410/clk_div
#define myubbr (16000000/16/9600-1)
int main() {
uint8_t ROM[8];
uint8_t scratchPad[2];
DDRB = B00010000;
for(;;) { // Loop forever
// Perform a OneWire reset pulse
POWER_TEMP_PROBE; // This isn't neccesary but I show it to demonstrate how to make the project conserve energy
simpletx("Presence pulse: ");
if(reset()) {
simpletx("Detected\t");
} else {
simpletx("Not Detected\t");
}
// Attempt to read the ROM, if nothing is present this will return 0x00 for each of the eight bytes
simpletx("ROM is: ");
write(READROM);
for(uint8_t i=0;i<8;i++) {
ROM[i]=read();
simpletx("0x");
txByteAsHex(ROM[i]);
if (i!=7) simpletx(",");
}
simpletx("\t\t");
DEPOWER_TEMP_PROBE; // This isn't neccesary but I show it to demonstrate how to make the project conserve energy
// If we detected a Dallas family sensor, let's go ahead and take a temperature reading
if (ROM[0]=0x28) { // The first byte of all dallas sensors is always 0x28
POWER_TEMP_PROBE;
reset();
write(SKIPROM);
write(STARTCONVO);
_delay_ms(750);
reset();
write(MATCHROM);
for(uint8_t i = 0; i < 8; i++) write(ROM[i]);
write(READSCRATCH);
scratchPad[TEMP_LSB] = read();
scratchPad[TEMP_MSB] = read();
reset();
DEPOWER_TEMP_PROBE;
uint16_t rawTemperature = (((int16_t)scratchPad[TEMP_MSB]) << 8) | scratchPad[TEMP_LSB];
simpletx("Temperature: ");
txRawTempAsFloat(rawTemperature);
simpletx("F\n");
}
_delay_ms(5000);
} // End for
} // End main
static inline uint8_t read() {
uint8_t r=0;
noInterrupts();
for (uint8_t bitMask = 0x01; bitMask; bitMask <<= 1) {
DIRECT_MODE_OUTPUT;
DIRECT_WRITE_LOW;
_delay_us(DELAY_A);
DIRECT_MODE_INPUT;
_delay_us(DELAY_E);
if (DIRECT_READ) r |= bitMask;
_delay_us(DELAY_F);
}
interrupts();
return r;
}
static inline void write(uint8_t v) {
noInterrupts();
for (uint8_t bitMask = 0x01; bitMask; bitMask <<= 1) {
DIRECT_WRITE_LOW;
DIRECT_MODE_OUTPUT;
if (bitMask & v) {
_delay_us(DELAY_A);
DIRECT_WRITE_HIGH;
_delay_us(DELAY_B);
} else {
_delay_us(DELAY_C);
DIRECT_WRITE_HIGH;
_delay_us(DELAY_D);
}
}
DIRECT_MODE_INPUT;
DIRECT_WRITE_LOW;
interrupts();
}
static inline uint8_t reset(void) {
noInterrupts();
DIRECT_MODE_INPUT;
DIRECT_WRITE_LOW;
DIRECT_MODE_OUTPUT;
_delay_us(DELAY_H);
DIRECT_MODE_INPUT;
_delay_us(DELAY_I);
uint8_t ret = !(DIRECT_READ);
interrupts();
_delay_us(DELAY_J);
return ret;
}
static inline void simpletx( char * string ) {
if (UCSR0B != (1<<TXEN0)) { //do we need to init the uart?
UBRR0H = (unsigned char)(myubbr>>8);
UBRR0L = (unsigned char)myubbr;
UCSR0A = 0;//Disable U2X mode
UCSR0B = (1<<TXEN0);//Enable transmitter
UCSR0C = (3<<UCSZ00);//N81
_delay_ms(30);
}
while (*string) {
while ( !( UCSR0A & (1<<UDRE0)) );
UDR0 = *string++; //send the data
}
}
static inline void txByteAsHex(uint8_t inp) {
char snd[3];
uint8_t tmp = inp>>4;
if (tmp<10) {
snd[0]=48+tmp;
} else {
snd[0]=55+tmp;
}
tmp=inp%16;
if (tmp<10) {
snd[1]=48+tmp;
} else {
snd[1]=55+tmp;
}
snd[2]='\0';
simpletx(snd);
}
// Converts the raw temperature from a DS18B20 directly to a string containing the temperature in °F with 1 decimal place
// avoids unnecessary floating point math, float variables, and casts, and 32-bit math
// TODO: May not work properly with temperatures below 32°F
static inline void txRawTempAsFloat(uint16_t raw) {
char buffer[5];
//uint32_t temp = (raw*1125ul+320000ul)/1000ul; // Keeps all 4 decimal places but uses 32-bit math
uint16_t temp = (9*raw)/8+320; // Keeps only 1 decimal place but uses 16-bit math
uint8_t decimalPos = 2; // default case of a temp between 0 and 99.9
uint8_t nullPos = 4; // default case of a temp between 0 and 99.9
if(temp>1000) { // We're looking at a positive number with three digits
decimalPos = 3;
nullPos = 5;
}
for(int8_t i=nullPos;i>-1;i--) {
if (i==decimalPos) {
buffer[i] = '.';
}
else if (i==nullPos) {
buffer[i] = '\0';
}
else {
buffer[i]= temp % 10 + '0';
temp /= 10;
}
}
simpletx(buffer);
}
static inline void txInt(long inp) {
long temp = inp;
uint8_t numChars=0;
boolean isNegative=false;
// Check to see if there is a negative sign
if(temp<0){
isNegative=true;
numChars++;
temp*=-1;
}
do {
numChars++;
temp /= 10;
} while ( temp );
char buf[numChars];
// Write the negative sign if present and the terminating null character
temp=inp;
buf[numChars]=0;
if(isNegative) {
temp*=-1;
buf[0]='-';
}
int i = numChars - 1;
do {
buf[i--] = temp%10 + '0';
temp /= 10;
} while (temp);
simpletx(buf);
}
This whole project is awesome! I have been thinking about doing something similar. I don't have any experience with Arduino, but I was initially thinking this might be a simple place to start. After seeing your frustrations, I'm rethinking doing it from scratch...might just end up copying your code/process if you don't mind.
My initial idea was to use load sensors rather than flow. I was thinking I would hack into an ebay postal scale and tare the weight of the keg out. Then I would display remaining beer as a percentage rather than a number of pints (since I don't always pour a pint). Any reason other than space that wouldn't work?
I like the web interface a lot! I was thinking about creating a twitter account for each keg and tweeting commands to the displays, but your solution is a lot cleaner.
Can't wait to see the finished product!!!
EDIT: Is it possible the flow sensors would get gummed up with sugar or trub over time?
Ok, I've decided to pull the trigger based on your build. This is just too awesome to pass up. A couple of questions.
- Is your Arduino sketch available somewhere? I am a total Arduino beginner, so it would be great to have something to work off of.
- Is an Arduino Mega R3 necessary? I found this one on Amazon, but an Uno is half the price (like here). I'm guessing I definitely need the Ethernet Shield and a breadboard (looks like you used a 170 point like this one). The ethernet shield is hardwired to your home network at all times, right?
- One adjustment I'm interested in making eventually is to put a weight sensor under the CO2 tank to actually have an estimate of how much is left. Is that something I can add on later, once I get the other stuff working?
- I was thinking I might use 4-line LCDs (these) with the beer name on line 1, style on line 2, ABV and IBUs on line 3, and amount (or percent) remaining on line 4. It seems like this would be a pretty easy adjustment to the webserver code, right?
This whole build is so awesome!!! :rockin:
citywok, first thanks for the great write up. I hope you're serious about helping a beginner. My aduino, flowmeters and 1 wire temp sensors and touch screen display arrived today. I have never even seen an arduino before much less know any code (other than html). I hope to begin my journey sometime over the next few days. Questions to follow.
Does the flow sensor affect the beer at all?
I can see something that big bit of plastic giving a plastic taste to the beer in the line for hours or days that can happen between pours ...
Enter your email address to join: