NerdKits - electronics education for a digital generation

You are not logged in. [log in]

NEW: Learning electronics? Ask your questions on the new Electronics Questions & Answers site hosted by CircuitLab.

Project Help and Ideas » Beer Kettle Cooker

June 07, 2011
by marekmjoe
marekmjoe's Avatar

I bought a nerd kit to control the temperature of my beer kettle, since I am moving my brewing equipment to the garage and out of the kitchen. I am using a thermistor (resistance changes with change in temperature) in a voltage divider circuit as the temperature sensor, a 2000W 120V AC hot water heating element as the heat source, 20 Amp solid state relay to bridge the gap between AC and DC circuits, and a 10k Ohm potentiometer to hopefully adjust the temperature. I am looking to heat my wort to a given temperature, hold that temperature constant, and use the potentiometer to adjust the constant temperature.

Doing the wiring is not bad, but I am almost completely new to programming. I used LabVIEW and MatLab on a very limited basis in school, but programming the nerdkits micro controller is a daunting task for me. I have tried working my way through the sample code for the temperature sensor tutorial, but to be perfectly honest even with the nerdkit guide I cannot make sense of it. I am really rethinking my decision to make my own controller instead of buying a temperature controller.

If someone out there is board enough and has the time, I would greatly appreciate them helping me out and explaining how to make this work. I know I need to set up an analog input for the temperature sensor and one for the potentiometer, then probably use a some 'if' statements to relate the temperature to the position of the potentiometer. Then I know I also need to have an output of a few volts to switch the relay to turn on the heating element. The problem is that I can't figure out how to set up the code to make this happen.

Thanks for any and all help.

June 07, 2011
by bretm
bretm's Avatar

A top-down approach would be something like this. Start with the main loop:

int main()
{
    initialize();

    while(1)
    {
        adjust();
    }

    return 0;
}

Then you have to define initialize() and adjust(). They have to come before main() in order for the compiler to understand them later.

void initialize()
{
    // This is where you set the ADC frequency and 
    // enable it, as per the Nerdkit guide.

    // Also enable the relay control pin as an output pin.
}

void adjust()
{
    float desiredTemperature = read_potentiometer();
    float actualTemperature = read_temperature();
    float hysteresis = 0.5;

    if (actualTemperature < desiredTemperature - hysteresis)
    {
        // set relay control pin to HIGH
    }
    else if (actualTemperature > desiredTemperature + hysteresis)
    {
        // set relay control pin to LOW
    }
    // otherwise leave the relay as-is
}

Reading the potentiometer will involve choosing a temperature range and mapping the ADC voltage reading to that range. For example, if you want the range to be 40 to 80 degrees celcius, and the potentiometer is hooked up to provide a voltage between 0V and 5V, the function would look something like this:

float read_potentiometer()
{
    int adc = read_adc(1);
    return 40.0 + (80.0 - 40.0) * adc / 1023.0;
}

I'll leave "read_adc", "read_temperature", "set pin to HIGH", and other bits like that up to you.

June 21, 2011
by marekmjoe
marekmjoe's Avatar

That helps a bit, but no where in the nerdkits pdf does it say how to read from an adc input. So in your code above:

float read_potentiometer()
{
    int adc = read_adc(1);
    return 40.0 + (80.0 - 40.0) * adc / 1023.0;
}

How would you program/initialize a function like read_adc(1) to work?

June 21, 2011
by Rick_S
Rick_S's Avatar

Read up on the tempsensor project in the NK guide. That is a full representation of using the ADC input on the micro.

June 21, 2011
by missle3944
missle3944's Avatar

Hey marekmjoe,

Its ok if you dont understand each line of the tempsensor code. I barely understand half of it. All you need to know is just use the standard tempsensor code to do your ADC readings. I do that and I think alot of the other nerdkitters do that too. For the Pot:

plug one lead into the positive and the other lead into the negative , then just plug the middle lead into the adc pin that they use. Once you turn the pot you should start getting changing data on the LCD. Its that simple. Just keep asking questions I guess. Thats my slice of the cake...

-missle3944

June 22, 2011
by bretm
bretm's Avatar

The tempsensor project will mislead slightly, as it is designed to read only a single ADC channel and its ADC-reading function is structured in such a way as to make multi-channel a little difficult.

But the tempsensor project will show you how to initialize the ADC (enable it and set the sampling frequency). Then you need to learn how to set the channel, start it, wait until its done, and then read the result. Each of those steps is in the tempsensor code except for setting the channel, which is done with three of the bits in the ADMUX register. Search the forums for ADMUX or venture into the datasheet.

July 12, 2011
by marekmjoe
marekmjoe's Avatar

Ok, I have run into a new problem with my project. I used the code from someone else's project because they were using the same concept I am...I just changed the equation for the temperature sensor and degrees F to degrees C. When I use the following equation the LCD displays "inf" as the temperature output as soon as my relay switches on.

double t = (log(v/638.7))/-0.033;

This log equation is derived from an exponential decay equation found through experimentation and is specific to the thermistors I am using. I am NOT using the temperature sensor supplied in the NerdKit because it will not hold up to the environment I am placing it in. If I use a linear equation in place of exponential decay equation the LCD will display a temperature instead of "inf", but my temperature will not be right (because the thermistors are not linear). What is going on? Is the adc_read() function giving out a 0 for a reading, and sending it to the log equation the only problem? (ln of 0=-infinity) I tried to throw out the zero's by adding the following statement at the end of the adc_read() function at line 56, but that only output "inf" on the LCD:

If( result = 0)
  adc_read();
else
  return result;

I am completely stumped. Please help. Here is the code I am using to test my equation:

// tempsensor.c
// for NerdKits with ATmega168
// mrobbins@mit.edu

//modified for refrigerator warmer project

#define F_CPU 14745600

#include <stdio.h>
#include <math.h>

#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>
#include <inttypes.h>

#include "../libnerdkits/delay.h"
#include "../libnerdkits/lcd.h"
#include "../libnerdkits/uart.h"

// PIN DEFINITIONS:
//
// PC0 -- temperature sensor analog input

void adc_init() {
  // set analog to digital converter
  // for external reference (5v), single ended input ADC0
  ADMUX = 0;

  // set analog to digital converter
  // to be enabled, with a clock prescale of 1/128
  // so that the ADC clock runs at 115.2kHz.
  ADCSRA = (1<<ADEN) | (1<<ADPS2) | (1<<ADPS1) | (1<<ADPS0);

  // fire a conversion just to get the ADC warmed up
  ADCSRA |= (1<<ADSC);
}

uint16_t adc_read() {
  // read from ADC, waiting for conversion to finish
  // (assumes someone else asked for a conversion.)
  // wait for it to be cleared
  while(ADCSRA & (1<<ADSC)) {
    // do nothing... just hold your breath.
  }
  // bit is cleared, so we have a result.

  // read from the ADCL/ADCH registers, and combine the result
  // Note: ADCL must be read first (datasheet pp. 259)
  uint16_t result = ADCL;
  uint16_t temp = ADCH;
  result = result + (temp<<8);

  // set ADSC bit to get the *next* conversion started
  ADCSRA |= (1<<ADSC);
  return result;
}

double sampleToCelsius(uint16_t sample) {
  // conversion ratio in DEGREES/STEP:
  // (5000 mV / 1024 steps) * (1 degree / 10mV)
  //    ^^^^^^^^^^^      ^^^^^^^^^^
  //     from ADC         from LM34
  double v = sample*(4937/1023);
  double t = (log(v/638.7))/-0.033;
  return t;
}

int main() {
  // start up the LCD
  lcd_init();
  FILE lcd_stream = FDEV_SETUP_STREAM(lcd_putchar, 0, _FDEV_SETUP_WRITE);
  lcd_home();

  // start up the Analog to Digital Converter
  adc_init();

  // start up the serial port
  uart_init();
  FILE uart_stream = FDEV_SETUP_STREAM(uart_putchar, uart_getchar, _FDEV_SETUP_RW);
  stdin = stdout = &uart_stream;

  // holder variables for temperature data
  uint16_t last_sample = 0;
  double this_temp;
  double temp_avg;
  uint16_t i;
  int heat;

  while(1) {
    // take 100 samples and average them!

    temp_avg = 0.0;
    for(i=0; i<100; i++) {
      last_sample = adc_read();
      this_temp = sampleToCelsius(last_sample);

      // add this contribution to the average
      temp_avg = temp_avg + this_temp/100.0;
      }

 ///////////////////////////////////////////////
 //code added to make the heater turn on/off  //
 ///////////////////////////////////////////////

 // pin28 for output to heater led
 DDRC |= (1<<PC5);

 //logic to turn on off heat
 if (temp_avg>40.0){heat=0;}  //sets info for LCD
 if (temp_avg>40.0) {PORTC &= ~(1<<PC5);}

 //logic to turn on off heat
 if (temp_avg<40.1){heat=1;}  //sets info for LCD
 if (temp_avg<40.1){PORTC |= (1<<PC5);}

 ///////////////////////////////////////////////
 //        End code for heater on/off         //
 ///////////////////////////////////////////////

    // write message to LCD
    lcd_home();
    lcd_write_string(PSTR("ADC: "));
    lcd_write_int16(last_sample);
    lcd_write_string(PSTR(" of 1024   "));
    lcd_line_two();
    fprintf_P(&lcd_stream, PSTR("Current Temp:%.1f"), temp_avg);
    lcd_write_data(0xdf);
    lcd_write_string(PSTR("C      "));
    lcd_line_three();
    lcd_write_string(PSTR("Heater - "));
    lcd_write_int16(heat);
    lcd_line_four();
    lcd_write_string(PSTR("Milk is - OK "));

    // write message to serial port
    printf_P(PSTR("%.2f degrees F\r\n"), temp_avg);
  }

  return 0;
}
July 13, 2011
by hevans
(NerdKits Staff)

hevans's Avatar

Hi marekmjoe,

I think the issue might definitely be infinity related. In the snippet you showed above you used = for comparison, which would not work correctly. You need to do == to check for equality, I'm not sure if that was just a typo.

What I would do is stick some print statements (either to the LCD or out the serial port) to see what kind of numbers you are getting for v, if those are small enough (especially after divided by 600) your numbers might be trending towards negative infinity pretty quick.

I would also run a small controlled test and just print out the result of some known log() computations on start up, just to make sure there isn't something wonky with the math includes.

Humberto

July 13, 2011
by bretm
bretm's Avatar

sample*(4937/1023) probably doesn't do what you want. (4937/1023) is just 4, because it's integer division. You may want (4937.0/1024) because each increment of sample is 1/1024th of the maximum range, and the ".0" at the end of either number makes it floating-point.

If you keep it 1023, the two equations are equivalent to 148.043 - 30.303 * log(sample). If you use 1024 it's equivalent to 148.073 - 30.303 * log(sample). In other words it just adds 0.03.

But to your main question I have nothing helpful. Sorry!

July 13, 2011
by Noter
Noter's Avatar

I think you are on the right track but if the ADC returns 0 then reading it again could just give another 0 so why not just force it to be a value of 1 and go on. 1 out of 1024 is just about 0 anyway but seems like something else may be going wrong if you're getting a 0 from the ADC. Give this a try in front of line 56 - it will get rid of the "inf".

  if(result==0) result=1;
July 14, 2011
by marekmjoe
marekmjoe's Avatar

Thank you Noter. The code works prefect now! Adding that if statement made the difference. Here is my final code that will maintain any temperature set within the bounds I specify with the ability to change the set temperature using a potentiometer. If anyone can use it for a project of theirs, all you need to do is change the upper and lower temperature bounds to what you want on line 81 of the code.

// tempsensor.c
// for NerdKits with ATmega168
// mrobbins@mit.edu

//modified for Beer Kettle Cooker Project

#define F_CPU 14745600

#include <stdio.h>
#include <math.h>

#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>
#include <inttypes.h>

#include "../libnerdkits/delay.h"
#include "../libnerdkits/lcd.h"
#include "../libnerdkits/uart.h"

// PIN DEFINITIONS:
//
// PC0 -- temperature sensor analog input

void adc_init() {
  // set analog to digital converter
  // for external reference (5v), single ended input ADC0
  ADMUX = 0;

  // set analog to digital converter
  // to be enabled, with a clock prescale of 1/128
  // so that the ADC clock runs at 115.2kHz.
  ADCSRA = (1<<ADEN) | (1<<ADPS2) | (1<<ADPS1) | (1<<ADPS0);

  // fire a conversion just to get the ADC warmed up
  ADCSRA |= (1<<ADSC);
}

uint16_t adc_read() {
  // read from ADC, waiting for conversion to finish
  // (assumes someone else asked for a conversion.)
  // wait for it to be cleared
  while(ADCSRA & (1<<ADSC)) {
    // do nothing... just hold your breath.
  }
  // bit is cleared, so we have a result.

  // read from the ADCL/ADCH registers, and combine the result
  // Note: ADCL must be read first (datasheet pp. 259)
  uint16_t result = ADCL;
  uint16_t temp = ADCH;
  result = result + (temp<<8);

  // set ADSC bit to get the *next* conversion started
  ADCSRA |= (1<<ADSC);
  if(result==0) result=1;

  return result;
}

double sampleToCelsius(uint16_t sample) {
  // conversion ratio in DEGREES/STEP:
  // (5000 mV / 1024 steps) * (1 degree / 10mV)
  //    ^^^^^^^^^^^      ^^^^^^^^^^
  //     from ADC         from LM34
  double v = sample*(4940/1024);
  //double t = (v - 4844.1)/(-31.7);
  double t = (log(v/690.57)/-.035);
  return t;  
/*if (result = 0)
  adc_read();
else
  return result;*/}

float read_potentiometer(double vin)
{
    //int adc = read_adc(1);
    return 40 + (60 - 40) * vin / 1024.0;
}

int main() {
  // start up the LCD
  lcd_init();
  FILE lcd_stream = FDEV_SETUP_STREAM(lcd_putchar, 0, _FDEV_SETUP_WRITE);
  lcd_home();

  // start up the Analog to Digital Converter
  adc_init();

  // start up the serial port
  uart_init();
  FILE uart_stream = FDEV_SETUP_STREAM(uart_putchar, uart_getchar, _FDEV_SETUP_RW);
  stdin = stdout = &uart_stream;

  // holder variables for temperature data
  uint16_t last_sample = 0;
  double this_temp;
  double temp_avg;
  double target_temp = 40.0;
  double this_volt;
  double volt_avg;
  uint16_t i;
  int heat;

  while(1) {
    // take 100 samples and average them!
     temp_avg = 0.0;
    ADMUX = 0;
    for(i=0; i<100; i++) {
      last_sample = adc_read();
      this_temp = sampleToCelsius(last_sample);

      // add this contribution to the average
      temp_avg = temp_avg + this_temp/100.0;
       }

 ///////////////////////////////////////////////
 //code added to make the heater turn on/off  //
 ///////////////////////////////////////////////

 // pin28 for output to heater led
 DDRC |= (1<<PC5);
 // pin27 for input tempture
 DDRC &= ~(1<<PC4);
 // volatge to tempurature conversion

 ADMUX = 4;
 volt_avg = 0.0;
 this_volt = 0;
 for(i=0; i<500; i++) {
       last_sample = adc_read();
      this_volt = last_sample;

      // add this contribution to the average
      volt_avg = volt_avg + this_volt/500.0;
      }

  target_temp = read_potentiometer(volt_avg);

 //logic to turn on off heat
 if (temp_avg>=target_temp){heat=0;}  //sets info for LCD
 if (temp_avg>=target_temp) {PORTC &= ~(1<<PC5);}

 //logic to turn on off heat
 if (temp_avg<target_temp){heat=1;}  //sets info for LCD
 if (temp_avg<target_temp){PORTC |= (1<<PC5);}

 ///////////////////////////////////////////////
 //        End code for heater on/off         //
 ///////////////////////////////////////////////

    // write message to LCD
    lcd_home();
    lcd_write_string(PSTR("ADC: "));
    //lcd_write_int16(last_sample);
    lcd_write_string(PSTR(" of 1024   "));
    lcd_line_two();
    fprintf_P(&lcd_stream, PSTR("Current Temp:%.1f"), temp_avg);
    lcd_write_data(0xdf);
    lcd_write_string(PSTR("C      "));
    lcd_line_three();
    lcd_write_string(PSTR("Heater - "));
    lcd_write_int16(heat);
    lcd_line_four();
    fprintf_P(&lcd_stream, PSTR("Set Temp:%.1f"), target_temp);

    // write message to serial port
    printf_P(PSTR("%.2f degrees F\r\n"), temp_avg);
  }

  return 0;
}

The only problem that I am having now is that my pot switch is drawing enough power off the breadboard that it changes the voltage of my source (4937mV) to some lower voltage. The result of this changes the temperature reading of the thermistor. I'll put my pot its own exclusive voltage source and I believe this problem will go away. Thanks for the help everyone.

November 28, 2011
by huzbum
huzbum's Avatar

Hey marekmjoe, glad to see I'm not the only brewer on here. I wish I had noticed your post earlier. I had a similar idea in mind around the same time (great minds think alike, eh?) I started gathering parts, but then I got distracted by silly things like getting married and moving... but now I'm thinking about it again.

How did yours turn out? Do you just turn the heater on till you're up to temp then turn it off till it falls to a certain temp? I'm thinking of using a SSR (solid state relay) to do pulse width modulation to control the heat, in an attempt to match the supplied heat to heat lost so there's not as much fluctuation in temps.

How accurate is the thermistor? I thought about making a temp probe for the temp sensor, but the thermistor sounds like a potentially simpler/better idea.

Also, how well does your water heater element work? Does it heat faster? How did you attach the element?

As for your pot drawing the voltage down, how did you wire it? Did you use all 3 terminals? If you check it with an ohm meter it should be 10k from one end to the other regardless of position and those ends should be hooked up to the + and - bus's and the wiper terminal should be hooked up to the ADC so it's reading the voltage drop of the resistance. There should be a very small current running through it, and it shouldn't change significantly.

December 12, 2011
by pfullen
pfullen's Avatar

I am also a home brewer Thanks for for this post. Very interesting and informative

Post a Reply

Please log in to post a reply.

Did you know that you can read diagnostic data from some cars with a NerdKit? Learn more...