ATMEGA328P Freezing when PWM changes

I'm currently using a custom PCB, but this also happened when testing with an Arduino Uno R3 on a breadboard.

I'm making a voice activated soldering iron for a project I'm working on, everything works fine except when it gets close to temperature, or close enough that the PID control would change the PWM on the output pin. Once it gets within 50 degrees C the display resets, and stops responding. I'm not sure what could be causing this.

// https://learn.adafruit.com/thermocouple/
#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include "max6675.h"
#include <PID_v1.h>
#include "DFRobot_DF2301Q.h"

#define PIN_INPUT 0
#define PIN_OUTPUT 11

//Define Variables we'll be connecting to
//I2C communication
DFRobot_DF2301Q_I2C DF2301Q;
char status = 'o';
double Setpoint, Input;

//Define the aggressive and conservative Tuning Parameters
double aggKp=4, aggKi=0.2, aggKd=1;
double consKp=1, consKi=0.05, consKd=0.25;

double Output = 0.00;

//Specify the links and initial tuning parameters
PID myPID(&Input, &Output, &Setpoint, consKp, consKi, consKd, DIRECT);

int thermoDO = 2;
int thermoCS = 3;
int thermoCLK = 4;

MAX6675 thermocouple(thermoCLK, thermoCS, thermoDO);

// make a cute degree symbol
uint8_t degree[8]  = {140,146,146,140,128,128,128,128};

#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels

// Declaration for an SSD1306 display connected to I2C (SDA, SCL pins)
// The pins for I2C are defined by the Wire-library. 
// On an arduino UNO:       A4(SDA), A5(SCL)
// On an arduino MEGA 2560: 20(SDA), 21(SCL)
// On an arduino LEONARDO:   2(SDA),  3(SCL), ...
#define OLED_RESET     -1 // Reset pin # (or -1 if sharing Arduino reset pin)
#define SCREEN_ADDRESS 0x3C ///< See datasheet for Address; 0x3D for 128x64, 0x3C for 128x32
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

#define NUMFLAKES     10 // Number of snowflakes in the animation example

#define LOGO_HEIGHT   16
#define LOGO_WIDTH    16
static const unsigned char PROGMEM logo_bmp[] =
{ 0b00000000, 0b11000000,
  0b00000001, 0b11000000,
  0b00000001, 0b11000000,
  0b00000011, 0b11100000,
  0b11110011, 0b11100000,
  0b11111110, 0b11111000,
  0b01111110, 0b11111111,
  0b00110011, 0b10011111,
  0b00011111, 0b11111100,
  0b00001101, 0b01110000,
  0b00011011, 0b10100000,
  0b00111111, 0b11100000,
  0b00111111, 0b11110000,
  0b01111100, 0b11110000,
  0b01110000, 0b01110000,
  0b00000000, 0b00110000 }; //displays the Adafruit logo

  int tempF = thermocouple.readFahrenheit(); //sets tempF to the thermocouple's reported fahrenheit temperature
  int tempC = thermocouple.readCelsius();//sets tempC to the thermocouple's reported celsius temperature
  int Output2 = 0; //sets output 2 to zero at startup for PID to function correctly
  int increment = 1;
  int set = 0;






void setup() {
  Serial.begin(9600);

  // Init the sensor
  while( !( DF2301Q.begin() ) ) {
    Serial.println("Communication with device failed, please check connection");
    delay(3000);
  }
  Serial.println("Begin ok!");

  /**
   * @brief Set voice volume
   * @param voc - Volume value(1~7)
   */
  DF2301Q.setVolume(3);

  /**
   * @brief Set mute mode
   * @param mode - Mute mode; set value 1: mute, 0: unmute
   */
  DF2301Q.setMuteMode(0);

  /**
   * @brief Set wake-up duration
   * @param wakeTime - Wake-up duration (0-255)
   */
  DF2301Q.setWakeTime(15);

  /**
   * @brief Get wake-up duration
   * @return The currently-set wake-up period
   */
  uint8_t wakeTime = 0;
  wakeTime = DF2301Q.getWakeTime();
  Serial.print("wakeTime = ");
  Serial.println(wakeTime);

  /**
   * @brief Play the corresponding reply audio according to the command word ID
   * @param CMDID - Command word ID
   * @note Can enter wake-up state through ID-1 in I2C mode
   */
  // DF2301Q.playByCMDID(1);   // Wake-up command
  DF2301Q.playByCMDID(23);   // Common word ID

  Input = tempC;
  Setpoint = 100;

  //turn the PID on
  myPID.SetMode(AUTOMATIC);

  Serial.println("MAX6675 test");
  // wait for MAX chip to stabilize
  delay(500);

  
  // SSD1306_SWITCHCAPVCC = generate display voltage from 3.3V internally
  if(!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) {
    Serial.println(F("SSD1306 allocation failed"));
    for(;;); // Don't proceed, loop forever
  }

  // Show initial display buffer contents on the screen --
  // the library initializes this with an Adafruit splash screen.
  display.display();
  delay(2000); // Pause for 2 seconds

  // Clear the buffer
  display.clearDisplay();

  display.setTextSize(1);             // Normal 1:1 pixel scale
  display.setTextColor(SSD1306_WHITE);        // Draw white text
  display.setCursor(0,0);  
  // Draw a single pixel in white
  display.print(tempF);

  // Show the display buffer on the screen. You MUST call display() after
  // drawing commands to make them visible on screen!
  display.display();

}

void loop() {

   /**
   * @brief Get the ID corresponding to the command word 
   * @return Return the obtained command word ID, returning 0 means no valid ID is obtained
   */
  uint8_t CMDID = 0;
  CMDID = DF2301Q.getCMDID();

  switch (CMDID) {
    case 5:
    Setpoint = 315;
    break;

    case 6:
    Setpoint = 380;
    break;

    case 7:
    Setpoint = 420;
    break;

    case 8:
    Setpoint = 450;
    break;

    case 9:
    if(increment < 15)
    {
      increment++;
    }
    break;

    case 10:
    if(increment > 1){
      increment--;
    }
    break;

    case 129:
    status = 'i';
    Serial.print("\n");
    Serial.print(status); 
    Serial.print("\n");
    Serial.print(CMDID);
    break;

    case 128:
    status = 'o';
    Serial.print("\n");
    Serial.print(status);    
    Serial.print("\n");
    Serial.print(CMDID);
    break;

    case 126:
    if(Setpoint < 480){
    Setpoint = Setpoint + increment;
    }
    break;

    case 127:
    if(Setpoint > 300){
      Setpoint = Setpoint - increment;
    }
  }
  delay(250);
  // basic readout test, just print the current temp
  tempC = thermocouple.readCelsius();
 if (status == 'i'){ 
  Input = tempC;

  double gap = abs(Setpoint-Input); //distance away from setpoint
  if (gap < 10)
  {  //we're close to setpoint, use conservative tuning parameters
    myPID.SetTunings(consKp, consKi, consKd);
  }
  else
  {
     //we're far from setpoint, use aggressive tuning parameters
     myPID.SetTunings(aggKp, aggKi, aggKd);
  }

  myPID.Compute();
  Output2 = map(Output, 0, 255, 255, 0); //reverses output value for transistor to process correctly
  analogWrite(PIN_OUTPUT, Output2); // writes the output value given by the PID to pin 11 on the arduino
 }
else{
  analogWrite(PIN_OUTPUT, 255);
}
  Serial.print(tempC); // prints the temperature celsius to the serial monitor to check for errors

  set = Setpoint;
  
  display.clearDisplay(); //clears OLED display to reset image
  display.setCursor(0, 0); //sets cursor to top left
  display.print("C"); // prints the temperature celsius on the screen
  display.setCursor(0, 55);//sets the cursor to just above the bottom left corner
  if(status == 'i'){
    display.print("Heat: ON");//displays the output from the PID control
    }
  if(status == 'o'){
    display.print("Heat: OFF");//displays the output from the PID control
    }
  display.setCursor(40,27);
  display.print("Temp: ");
  display.print(tempC);
  display.setCursor(46, 37);
  display.print("Set: ");
  display.print(set);
  display.setCursor(85, 55);
  display.print("incr: ");
  display.print(increment);
  display.display();//tells the display to show all the information
 
   // For the MAX6675 to update, you must delay AT LEAST 250ms between reads!
   delay(250);
}

You may be running out of dynamic memory. The display buffer alone takes half of it.

To save dynamic memory, use the F macro to put .print() strings in program memory. For example, replace all statements like this:
Serial.println("MAX6675 test");
with this
Serial.println(F("MAX6675 test"));

Do that with display.print() statements too.

To do something useful, these function calls have to be inside a function like setup() or loop():

 int tempF = thermocouple.readFahrenheit(); //sets tempF to the thermocouple's reported fahrenheit temperature
  int tempC = thermocouple.readCelsius();//sets tempC to the thermocouple's reported celsius temperature

Can you please hit ctrl-t and repost your code?
Could you explain what status 'i' means?
And also what the other statuses mean?

the status is an indicator for on and off, I used i and o for on and off symbols, they're just used as status indicators so the heating element is off when the unit is first turned on, there are only two statuses, on and off

I edited the code to reflect these changes, I'll test and see if it works

Where does the last else belong to? It seems it has no if. Hit ctrl-t to properly outlune your code....

The last else is used to turn off the heating element if it shouldn't be heating, that part works as it should. As for reposting my code using ctrl-t, all ctrl-t does on my browser is open a new tab.

You should use ctrl-t in the Arduino IDE, not in the web browser.
Otherwise: go to ' tools' and then click ' auto format' .

that makes sense, thank you, I decided to try a different approach to the PID control to see if it would stop crashing, now it at least fully restarts when it crashes, but still crashes, when compiled it shows 28% memory usage and 57% program storage space, could it be a memory issue?

//This example is public domain. enjoy!
// https://learn.adafruit.com/thermocouple/
#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include "max6675.h"
#include "DFRobot_DF2301Q.h"


//Define Variables we'll be connecting to
//I2C communication
DFRobot_DF2301Q_I2C DF2301Q;

// make a cute degree symbol
uint8_t degree[8] = { 140, 146, 146, 140, 128, 128, 128, 128 };

#define SCREEN_WIDTH 128  // OLED display width, in pixels
#define SCREEN_HEIGHT 64  // OLED display height, in pixels

// Declaration for an SSD1306 display connected to I2C (SDA, SCL pins)
// The pins for I2C are defined by the Wire-library.
// On an arduino UNO:       A4(SDA), A5(SCL)
// On an arduino MEGA 2560: 20(SDA), 21(SCL)
// On an arduino LEONARDO:   2(SDA),  3(SCL), ...
#define OLED_RESET -1        // Reset pin # (or -1 if sharing Arduino reset pin)
#define SCREEN_ADDRESS 0x3C  ///< See datasheet for Address; 0x3D for 128x64, 0x3C for 128x32
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);


int PWM_pin = 11;     //Sets the output pin for PWM for the element
double tempC;         //initializes the input temperature
float increment = 1;  //Sets increment to 1 at the beginning of the program
char status = 'o';
int thermoDO = 2;  //sets the pins for the MAX6675 IC
int thermoCS = 3;
int thermoCLK = 4;
float set_temperature = 315;  //sets the set temp at startup
float PID_error = 0;          //checks the error value
float previous_error = 0;     //is used for comparison
float elapsedTime, Time, timePrev;
float PID_value = 0;

int kp = 90;
int ki = 30;
int kd = 80;

int PID_p = 0;
int PID_i = 0;
int PID_d = 0;
float last_kp = 0;
float last_ki = 0;
float last_kd = 0;

MAX6675 thermocouple(thermoCLK, thermoCS, thermoDO);


void setup() {
  Serial.begin(9600);

  pinMode(PWM_pin, OUTPUT);

  analogWrite(PWM_pin, 255);

  // Init the sensor
  while (!(DF2301Q.begin())) {
    delay(3000);
  }

  /**
   * @brief Set voice volume
   * @param voc - Volume value(1~7)
   */
  DF2301Q.setVolume(3);

  /**
   * @brief Set mute mode
   * @param mode - Mute mode; set value 1: mute, 0: unmute
   */
  DF2301Q.setMuteMode(0);

  /**
   * @brief Set wake-up duration
   * @param wakeTime - Wake-up duration (0-255)
   */
  DF2301Q.setWakeTime(15);

  /**
   * @brief Get wake-up duration
   * @return The currently-set wake-up period
   */
  uint8_t wakeTime = 0;
  wakeTime = DF2301Q.getWakeTime();

  /**
   * @brief Play the corresponding reply audio according to the command word ID
   * @param CMDID - Command word ID
   * @note Can enter wake-up state through ID-1 in I2C mode
   */
  // DF2301Q.playByCMDID(1);   // Wake-up command
  DF2301Q.playByCMDID(23);  // Common word ID



  // wait for MAX chip to stabilize
  delay(500);


  // SSD1306_SWITCHCAPVCC = generate display voltage from 3.3V internally
  if (!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) {
    for (;;)
      ;  // Don't proceed, loop forever
  }

  // Show initial display buffer contents on the screen --
  // the library initializes this with an Adafruit splash screen.
  display.setTextSize(1);               // Normal 1:1 pixel scale
  display.setTextColor(SSD1306_WHITE);  // Draw white text
  display.setCursor(0, 0);
  // Draw a single pixel in white
  display.print(F("Starting"));
  display.display();
  delay(2000);  // Pause for 2 seconds

  // Clear the buffer
  display.clearDisplay();
}

void loop() {

  /**
   * @brief Get the ID corresponding to the command word 
   * @return Return the obtained command word ID, returning 0 means no valid ID is obtained
   */
  uint8_t CMDID = 0;
  CMDID = DF2301Q.getCMDID();

  switch (CMDID) {
    case 5:
      set_temperature = 315.00;
      break;

    case 6:
      set_temperature = 380.00;
      break;

    case 7:
      set_temperature = 420.00;
      break;

    case 8:
      set_temperature = 450.00;
      break;

    case 9:
      if (increment < 15) {
        increment++;
      }
      break;

    case 10:
      if (increment > 1) {
        increment--;
      }
      break;

    case 129:
      status = 'i';
      break;

    case 128:
      status = 'o';
      break;

    case 126:
      if (set_temperature < 480) {
        set_temperature = set_temperature + increment;
      }
      break;

    case 127:
      if (set_temperature > 190) {
        set_temperature = set_temperature - increment;
      }
  }


  delay(250);
  // basic readout test, just print the current temp
  tempC = thermocouple.readCelsius();
  if (status == 'i') {
    // First we read the real value of temperature
    //Next we calculate the error between the setpoint and the real value
    PID_error = set_temperature - tempC + 3;
    //Calculate the P value
    PID_p = 0.01 * kp * PID_error;
    //Calculate the I value in a range on +-3
    PID_i = 0.01 * PID_i + (ki * PID_error);


    //For derivative we need real time to calculate speed change rate
    timePrev = Time;  // the previous time is stored before the actual time read
    Time = millis();  // actual time read
    elapsedTime = (Time - timePrev) / 1000;
    //Now we can calculate the D calue
    PID_d = 0.01 * kd * ((PID_error - previous_error) / elapsedTime);
    //Final total PID value is the sum of P + I + D
    PID_value = PID_p + PID_i + PID_d;

    //We define PWM range between 0 and 255
    if (PID_value < 0) { PID_value = 0; }
    if (PID_value > 255) { PID_value = 255; }
    //Now we can write the PWM signal to the mosfet on digital pin D3
    //Since we activate the MOSFET with a 0 to the base of the BJT, we write 255-PID value (inverted)
    analogWrite(PWM_pin, 255 - PID_value);
    previous_error = PID_error;  //Remember to store the previous error for next loop.
  } else {
    analogWrite(PWM_pin, 255);
  }


  display.clearDisplay();    //clears OLED display to reset image
  display.setCursor(0, 0);   //sets cursor to top left
  display.print(F("C"));     // prints the temperature celsius on the screen
  display.setCursor(0, 55);  //sets the cursor to just above the bottom left corner
  if (status == 'i') {
    display.print(F("Heat: ON"));  //displays the output from the PID control
  }
  if (status == 'o') {
    display.print(F("Heat: OFF"));  //displays the output from the PID control
  }
  display.setCursor(40, 27);
  display.print(F("Temp: "));
  display.print(tempC);
  display.setCursor(46, 37);
  display.print(F("Set: "));
  display.print(set_temperature);
  display.setCursor(85, 55);
  display.print(F("incr: "));
  display.print(increment);
  display.display();  //tells the display to show all the information

  // For the MAX6675 to update, you must delay AT LEAST 250ms between reads!
  delay(300);
}

to give more specifics, at the moment it seems to be crashing when the temperature is about 10-20 degrees Celsius from the set point.

Can you have a delay if you use PID control?
It is not a good idea to divide an integer by 1000.
Probably nothing is left.
999/1000 = 0!
I see now you have defined those as float. That should be uint32_t or unsigned long.
Otherwise you will loose essential precision.

would either of those cause it to crash like this though?

20240117_115828.mp4 - Google Drive Here's a video of what's going on

Division by zero will occur if elapsedTime is equal to zero...
So it is well possible.

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.