Arduino BBQ Fan Temperature Controller

Commercially sourced BBQ temperature controllers are expensive. They start at about US $200 and average well above $300. Most include WIFI interfaces too. So I thought I could do better. This is the first step in making a BBQ temperature controller. The next step will be to add a simple flue control. Eventually, I want to add WIFI control to the device.

This project uses the following components. Most of my parts were sourced off ebay and came from China.

  • Arduino
  • 12v fan
  • 12v power supply
  • L298N motor control module
  • MAX6675 k-type thermocouple module
  • TM-1637 4-digit 7-segment display
  • 2 switches

Obviously, the fan is one of the most important items in how well the system operates. Fans are rated by their airflow, typically given in Cubic Feet per Minute (CFM). Here is an example of a nominally-sized, fan with a decent rating of 40 CFM at 12 volts.

So far, the system simply allows the user to select a “set-point” temperature and stores this value in EEPROM. An attached MAX6675 k-type thermocouple (0-500 degree C range) is used to check the temperature inside the BBQ. The variable fan motor speed is adjusted depending upon the temperature difference between the actual and set-point values. Signals sent from the Arduino to the L298N module are the commands to set a specific fan speed. An additional benefit to using an L298N module is it has a 5V output capable of supplying the other components. The two buttons are used to display and adjust the “set-point” temperature. A TM-1637 4-digit, 7-segment display is incorporated to show either temperatures.

Finer temperature control will be achieved through use of a flue door. This will help to prevent temperature overshoots and allow for the full range of temperature throttling. I envision using a flue door operated via a servo and possibly incorporating software PID control if necessary.

The complete system can be viewed below:

Here is a matrix showing all of the interconnections between modules. It’s really not as difficult as it looks:

The project consumes 10 digital pins on the Arduino. And while I used an old Atmega168 based Arduino, a 328, Uno or Leonardo would work equally as well. The Arduino firmware is very simple:

/*
 * Temperature Controlled BBQ Variable Fan
 * James M. Eli
 * 8/20/2019
 * Version 1.0
 * 
 * Components:
 *   Arduino (Atmega168)
 *   MAX6675 K-Type Thermocouple
 *   TM1637 7-segment 4-digit Display
 *   L298N Motor Contorller Module
 *   12V Fan
 *   12V Power Supply
 *   
 * Sketch uses 4096 bytes (28%) of program storage space. 
 * Maximum is 14336 bytes.
 * Global variables use 47 bytes (4%) of dynamic memory, 
 * leaving 977 bytes for local variables. 
 * Maximum is 1024 bytes.
 */
#include "TM1637Display.h" // display module
#include "max6675.h"       // temperature module
#include "EEPROM.h"        // eeprom access

// Program states.
constexpr uint8_t NO_STATE{ 0 };
constexpr uint8_t INC_TEMP{ 1 };
constexpr uint8_t DEC_TEMP{ 2 };
constexpr uint8_t SET_TEMP{ 3 };

// TM1637 display module connection pins.
constexpr uint8_t TM1637_CLK { 2 };   // PD2
constexpr uint8_t TM1637_DIO { 3 };   // PD3
// MAX6675 temperature module connection pins.
constexpr uint8_t MAX6675_SO { 8 };   // PB0
constexpr uint8_t MAX6675_CS { 9 };   // PB1
constexpr uint8_t MAX6675_CLK { 10 }; // PB2
// L298N module connection pins.
constexpr uint8_t L298N_ENA { 5 };    // PD5, l298n pin #7 (motor 1 enable, remove jumper)
constexpr uint8_t L298N_IN1 { 7 };    // PD7, l298n pin #8 (IN1)
constexpr uint8_t L298N_IN2 { 6 };    // PD6, l298n pin #9 (IN2)
// Buttons (2).
constexpr uint8_t INC_BUTTON { 11 };  // PB3
constexpr uint8_t DEC_BUTTON { 12 };  // PB4

// Fan speed temperature thresholds (degrees F).
constexpr int16_t TEMP_THRESHOLD[4] = { 7, 16, 33, 65 };
// Fan speeds voltages (0-255, where 255=100%).
constexpr uint8_t FAN_SPEED[4] = { 63, 127, 191, 255 };

// Temperature value change per each increase/decrease.
constexpr int16_t TEMP_STEP { 10 };
// Min, max and default temperature settings.
constexpr int16_t MIN_TEMP { 200 };
constexpr int16_t MAX_TEMP { 500 };
constexpr int16_t DEFAULT_TEMP { 350 };

// Eeprom address (stores set point temperature).
#define EEPROM_ADDRESS 0 

// Display timer.
uint32_t timer;
// Set point temperature & actual temperature (as read from MAX6675).
int16_t setTemp;
int16_t actTemp;
// Pointer to select temperature for display.
int16_t *temp = &actTemp;

// Instantiate display & temperature module objects.
TM1637Display disp(TM1637_CLK, TM1637_DIO);
MAX6675 tc(MAX6675_CLK, MAX6675_CS, MAX6675_SO);

// Read/write word to/from EEPROM.
int16_t eepromRead16(uint16_t address) 
{
  int16_t value = word(EEPROM.read(address), EEPROM.read(address + 1));
  return value;
} 
void eepromWrite16(uint16_t address, int16_t value) 
{
  EEPROM.write(address, highByte(value));
  EEPROM.write(address + 1, lowByte(value));
}

// Retrieve temperature from eeprom.
int16_t getEepromTemp(void) 
{
  int16_t temp;

  // Get temp from eeprom memory.
  temp = eepromRead16((uint16_t)EEPROM_ADDRESS);

  // Validate temp in range & multiple of step value.
  temp = (temp / TEMP_STEP) * TEMP_STEP;
  if (temp < MIN_TEMP || temp > MAX_TEMP)
    temp = DEFAULT_TEMP;
  
  // Save it.
  eepromWrite16((uint16_t)EEPROM_ADDRESS, (int16_t)temp);
  return temp;
}

// Store temperature in eeprom.
void setEepromTemp(int16_t temp) 
{
  // Validate temp in range & multiple of step value.
  temp = (temp / TEMP_STEP) * TEMP_STEP;
  if (temp < MIN_TEMP || temp > MAX_TEMP)
    temp = DEFAULT_TEMP; 
  
  // Save it.
  eepromWrite16((uint16_t)EEPROM_ADDRESS, (int16_t)temp);  
}

// Poll buttons.
uint8_t checkButtons() 
{ 
  // Both buttons down?
  if ( !(PINB&(1<<PORTB3)) && !(PINB&(1<<PORTB4)) ) 
    return SET_TEMP;
  
  // Increment button down?
  else if ( !(PINB&(1<<PORTB3)) ) 
    return INC_TEMP; 
  
  // Decrement button down?
  else if ( !(PINB&(1<<PORTB4)) ) 
    return DEC_TEMP; 
  
  return NO_STATE;
}

void setup() 
{
  // L298N motor controller pins.
  pinMode(L298N_ENA, OUTPUT);
  pinMode(L298N_IN1, OUTPUT);
  pinMode(L298N_IN2, OUTPUT);
  
  // TM1637 display module connection pins.
  pinMode(TM1637_CLK, OUTPUT);
  pinMode(TM1637_DIO, OUTPUT);
  
  // MAX6675 temperature module connection pins.
  pinMode(MAX6675_SO, OUTPUT);
  pinMode(MAX6675_CS, OUTPUT);
  pinMode(MAX6675_CLK, OUTPUT);
  
  // Activate internal pull-ups on button pins.
  pinMode(INC_BUTTON, INPUT);
  pinMode(INC_BUTTON, INPUT_PULLUP);
  pinMode(DEC_BUTTON, INPUT);
  pinMode(DEC_BUTTON, INPUT_PULLUP);

  setFan(0);                 // Turn fan motor off.
  disp.setBrightness(0);     // Dim down.
  timer = millis();          // Initialize timer.
  setTemp = getEepromTemp(); // Retrieve stored set point temperature.
  delay(500);                // Allow everything to settle.
}

// Update L298N fan motor.
void setFan(uint8_t index) 
{
  if (index == 0) 
  {
    // Turn fan motor off.
    digitalWrite(L298N_IN1, LOW);
    digitalWrite(L298N_IN2, LOW);  
  } 
  else 
  {
    // Turn motor on and adjust speed.
    digitalWrite(L298N_IN1, HIGH);
    digitalWrite(L298N_IN2, LOW);
    analogWrite(L298N_ENA, FAN_SPEED[index]);
  }
}

// Handle TM1637 display details.
void displayTemp(int16_t t) 
{
  disp.setBrightness(0x0f);
  //disp.clear();
  disp.showNumberDec(t, false);  // Show decimal numbers without leading zeros
}

void loop() 
{
  actTemp = (int16_t)tc.readFahrenheit();
  uint8_t button = checkButtons();

  if (button) 
  {
    // Reset display timer.
    timer = millis(); 
    if (temp != &setTemp)
      // Display set point temperature.
      temp = &setTemp;
    else if (button == INC_TEMP)
    {
      // Increment temperature by step amount.
      setTemp += TEMP_STEP;
      if (setTemp > MAX_TEMP)
        setTemp = MIN_TEMP;
      setEepromTemp(setTemp);
    }
    else if (button == DEC_TEMP)
    {
      // decrement temperature by step amount.
      setTemp -= TEMP_STEP;
      if (setTemp < MIN_TEMP)
        setTemp = MAX_TEMP;
      setEepromTemp(setTemp);
    }
  }
  else
  {
    uint8_t speed { 0 };
    // Get temperature difference.
    int16_t dTemp = (int16_t)(setTemp - actTemp);

    // Iterate through temperature thresholds, incrementing fan speed.
    for (int i=0; i<4; i++)
      if (dTemp >= TEMP_THRESHOLD[i])
        speed++;

    setFan(speed);
  }
    
  // Display actual or set point temperature.
  displayTemp((int16_t)*temp);
  
  // A delay is useful for switch debounce.
  if (temp == &setTemp) {
    // Time to change display temperature to actual?
    if (millis() - timer > 5000)
      temp = &actTemp;
    else
    delay(500);
  }
  else
    delay(1000);
}

About Jim Eli

µC experimenter
This entry was posted in arduino and tagged . Bookmark the permalink.

3 Responses to Arduino BBQ Fan Temperature Controller

  1. thedengo says:

    Awesome work! Been looking all over the internet for someone that has built one of these. Most are overworked and expensive. Have you used it on your grill yet? Any WiFi updates?

    • Jim Eli says:

      I’m currently transferring the project to an esp8266 and experimenting with different fan sizes. For full temperature throttling it will need a flue. I’m thinking about a circular cover with a servo to open/close slots on the top of my BGE.

      • thedengo says:

        Nice, check out the PitmasterIQ with Kamado adapter. Seems like most of the controllers get away with just controlling the airflow from the bottom vent leaving the top one open a bit. Looking forward to the evolution of this project.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s