Ramp/soak heater control

Hi all,

I am trying to implement a ramp/soak controller based on a resistive element as the heater and a type-k thermocouple as the temperature monitor.

So I want to be able to ramp from ambient to 200C, then hold for 100 seconds.
Then ramp to 400C and hold for 100 seconds.
Then ramp to 600C and hold for 100 seconds.

I can load temperature and time information from a datalog.txt file on SD card into an array, no problem.
The text file looks like this...

200, 100
400, 100
600, 100

// open the SD file.
		file = SD.open("datalog.TXT", FILE_WRITE);
		if (!file) {
			errorHalt("open failed");
		}

		// Fill array
		file.seek(0);
		for (i = 0; i < ROW_DIM; i++) {
			for (j = 0; j < COL_DIM; j++) {
				n = readField(&file, str, sizeof(str), ",\n");
				if (n == 0) {
					errorHalt("Too few lines");
				}
				array[i][j] = strtol(str, &ptr, 10);
				if (ptr == str) {
					errorHalt("bad number");
				}
				while (*ptr == ' ') {
					ptr++;
				}
				if (*ptr != ',' && *ptr != '\n' && *ptr != '\0') {
					errorHalt("extra characters in field");
				}
				if (j < (COL_DIM - 1) && str[n - 1] != ',') {
					errorHalt("line with too few fields");
				}
			}
			// Allow missing endl at eof.
			if (str[n - 1] != '\n' && file.available()) {
				errorHalt("missing endl");
			}
		}

See my graphic which illustrates what I would like the control code to do.

Questions:

How do I assign integers stored in the array to double type variables needed by the PID V1 library?

Is a for loop the way to cycle through the array assigning different values to variables?

Thank you.

negativ3:
How do I assign integers stored in the array to double type variables needed by the PID V1 library?

Is a for loop the way to cycle through the array assigning different values to variables?

In an assignment the value is converted according to the type of the target variable.
A for loop is okay.

You'll have to learn more about your device, how temperature changes over time depending on some heating power. E.g. find out how much energy (current...) is required for a temperature ramp of a certain slope.

DrDiettrich:
In an assignment the value is converted according to the type of the target variable.
A for loop is okay.

You'll have to learn more about your device, how temperature changes over time depending on some heating power. E.g. find out how much energy (current...) is required for a temperature ramp of a certain slope.

Thanks. I am building a PLA burnout furnace for aluminium casting so the ramp parts are just the heating element "full-on". Its the setpoints at 200, 400 and 600 which will be under PID control.

When I try to compile, I get an error for these two lines:

Setpoint = (array*);*
setTime = (array[j]);
error: cannot convert 'int [3]' to 'double' in assignment Setpoint = (array*)*
error: invalid conversion from 'int*' to 'int' [-fpermissive] setTime = (array[j])
Setpoint is double type, setTime is an integer.

Your declaration for "array" is in some way wrong but since you didn't post your code, only you can see it.

Please read the How to use this forum and post your code as described in topic #7.

avr_fred:
Your declaration for "array" is in some way wrong but since you didn't post your code, only you can see it.

Please read the How to use this forum and post your code as described in topic #7.

Ok...

for (i = 0; i < ROW_DIM; i++) {
				for (j = 0; j < COL_DIM; j++) {
					Setpoint = (array[i]);
					setTime = (array[j]);
					Serial.println(array[i]);
					Serial.println(array[j]);
				}
			}

'Where is your declaration of Setpoint and setTime?

DrDiettrich:
'Where is your declaration of Setpoint and setTime?

Sorry, I don't mean for this to be like dragging teeth for you gurus.

/*PID working variables*/
double Setpoint, Input, Output;

//not a PID var
int setTime = 0; //initial on time

Why don't you do this:

Open your IDE.
Press Ctrl-A.
Create a reply to this thread.
Enter the opening code tag, press Ctrl-C, then enter the closing code tag.
Click Post.

In other words: post your complete sketch, no snippets. Because, believe it or not, the error is almost always in the parts you didn't post.

/*Libraries*/
#include <Adafruit_GFX.h>
#include <Adafruit_ST7735.h>
#include <SPI.h>
#include <SD.h>
#include <PID_v1.h>
#include <Adafruit_MAX31855.h>

/*TFT*/
#define TFT_SCLK 13
#define TFT_MOSI 11
#define TFT_CS 10
#define TFT_RST 9
#define TFT_DC     8
Adafruit_ST7735 tft = Adafruit_ST7735(TFT_CS, TFT_DC, TFT_RST);

/*SD card*, clk 13, miso 12, mosi 11*/
const int chipSelect = A0;
/* 6 X 3 array for SD card read data */
#define ROW_DIM 6
#define COL_DIM 3
int array[ROW_DIM][COL_DIM];
int i = 0;     // First array index.
int j = 0;     // Second array index
size_t n;      // Length of returned field with delimiter.
char str[20];  // Must hold longest field with delimiter and zero byte.
char *ptr;     // Test for valid field.
File file;
size_t readField(File* file, char* str, size_t size, char* delim) {
	char ch;
	size_t n = 0;
	while ((n + 1) < size && file->read(&ch, 1) == 1) {
		// Delete CR.
		if (ch == '\r') {
		continue;	}
		str[n++] = ch;
		if (strchr(delim, ch)) {
		break;	}	}
		str[n] = '\0';
	return n;	}
	#define errorHalt(msg) {Serial.println(F(msg)); while(1);}

	/*time*/
	#define SECS_PER_MIN  (60UL)
	#define numberOfMinutes(_time_) ((_time_ / SECS_PER_MIN) % SECS_PER_MIN)

	/*PID working variables*/
	double Setpoint, Input, Output;
	//Specify the links and initial tuning parameters
	PID myPID(&Input, &Output, &Setpoint, 2, 5, 1, DIRECT);
	int WindowSize = 1000;
	unsigned long windowStartTime;

	/*Thermocouple MAX31855*/
	#define maxSO   5
	#define maxCS   6
	#define maxSCK  7
	Adafruit_MAX31855 kTC(maxSCK, maxCS, maxSO);
	const int numReadings = 10; //average number of readings from thermocouple
	double v = 0;
	int readings[numReadings];
	int readIndex = 0;
	double total;
	double vavg;
	double Ctemp, Ftemp;
	double offset = 0; //temp offset for calibration

	/*rotary encoder*/
	int sw1 = 2; //rotary encoder pushbutton
	const int encoderPinB = 3; //rotary encoder B
	const int encoderPinA = 4; //rotary encoder A
	boolean encoderALast = LOW; //remembers the previous rotary encoder pin state
	//edit setpoint time, sptime
	//edit setpoint temperature, sptemp

	/*opto-isolator*/
	int relay1 = A4;
	unsigned long relayStartTime = 0; //time the relay is switched on

	/*calculations and status*/
	String seconds; //used in setTimer function to display strings on LCD
	int setTime = 0; //initial on time selected in milliseconds

	void setup(void) {
		Serial.begin(9600);
		pinMode(relay1, OUTPUT); digitalWrite(relay1, LOW);
		pinMode(encoderPinA, INPUT); digitalWrite(encoderPinA, HIGH);
		pinMode(encoderPinB, INPUT); digitalWrite(encoderPinB, HIGH);
		pinMode(sw1, INPUT); digitalWrite(sw1, HIGH);
		
		for (int thisReading = 0; thisReading < numReadings; thisReading++) {
			readings[thisReading] = 0;
		}

		//PID
		windowStartTime = millis();
		//initialize the variables we're linked to
		Setpoint = 30;
		//tell the PID to range between 0 and the full window size
		myPID.SetOutputLimits(0, WindowSize);
		//turn the PID on
		myPID.SetMode(AUTOMATIC);
		tft.initR(INITR_BLACKTAB);   // initialize a ST7735S chip

		//splash screen
		tft.fillScreen(ST7735_BLACK);
		tft.setTextWrap(false);
		tft.setCursor(20, 0);
		tft.setTextColor(ST7735_YELLOW, ST7735_BLACK);
		tft.setTextSize(1);
		tft.println("FURNACE CONTROL");
		tft.println("");
		tft.println("V0.9");
		tft.println("October 2017");
		delay(3000);

		// close the SD card
		if (!SD.begin(chipSelect)) {
			Serial.println("no sd");
			errorHalt("begin failed");
		}

		// open the SD file.
		file = SD.open("datalog.TXT", FILE_WRITE);
		if (!file) {
			errorHalt("open failed");
		}

		// Fill array
		file.seek(0);
		for (i = 0; i < ROW_DIM; i++) {
			for (j = 0; j < COL_DIM; j++) {
				n = readField(&file, str, sizeof(str), ",\n");
				if (n == 0) {
					errorHalt("Too few lines");
				}
				array[i][j] = strtol(str, &ptr, 10);
				if (ptr == str) {
					errorHalt("bad number");
				}
				while (*ptr == ' ') {
					ptr++;
				}
				if (*ptr != ',' && *ptr != '\n' && *ptr != '\0') {
					errorHalt("extra characters in field");
				}
				if (j < (COL_DIM - 1) && str[n - 1] != ',') {
					errorHalt("line with too few fields");
				}
			}
			// Allow missing endl at eof.
			if (str[n - 1] != '\n' && file.available()) {
				errorHalt("missing endl");
			}
		}
		
		setMenu();
	}

	void loop() {
		setMenu();
	}

	void setMenu() {
		int encoderPos = 1;
		int x = 0;
		tft.fillScreen(ST7735_BLACK);
		tft.setTextWrap(false);
		tft.setCursor(20, 0);
		tft.setTextColor(ST7735_YELLOW, ST7735_BLACK);
		tft.setTextSize(1);
		tft.println("START OR LEARN");
		tft.println("");
		tft.setCursor(25, 50);
		tft.setTextColor(ST7735_YELLOW, ST7735_RED);
		tft.setTextSize(2);
		tft.print("START");
		tft.setCursor(25, 70);
		tft.setTextColor(ST7735_YELLOW, ST7735_BLACK);
		tft.setTextSize(2);
		tft.print("LEARN");
		while (digitalRead(sw1) == HIGH) {
			boolean encoderA = digitalRead(encoderPinA);
			if ((encoderALast == HIGH) && (encoderA == LOW)) {
				if (digitalRead(encoderPinB) == LOW) {
					encoderPos--;
				encoderPos = constrain(encoderPos, 1, 2);	}
				else {
					encoderPos++;
				encoderPos = constrain(encoderPos, 1, 2);	}
				switch (encoderPos) {
					case 1:
					Serial.println("START");
					tft.setTextWrap(false);
					tft.setTextSize(2);
					tft.setCursor(25, 70);
					tft.setTextColor(ST7735_YELLOW, ST7735_BLACK);
					tft.print("LEARN");
					tft.setCursor(25, 50);
					tft.setTextColor(ST7735_YELLOW, ST7735_RED);
					tft.print("START");
					x = 1;
					break;
					case 2:
					Serial.println("LEARN");
					tft.setTextWrap(false);
					tft.setTextSize(2);
					tft.setCursor(25, 50);
					tft.setTextColor(ST7735_YELLOW, ST7735_BLACK);
					tft.print("START");
					tft.setCursor(25, 70);
					tft.setTextColor(ST7735_YELLOW, ST7735_RED);
					tft.print("LEARN");
					x = 2;
				break;	}
			}
		encoderALast = encoderA;	}
		if (x <= 1) {
			Serial.println("ramp");
			run_ramp();
		return;	}
		else {
			Serial.println("learn");
			learn();
		return;	}
	return;	}

part 2

	void run_ramp() {
		delay(500);
		tft.fillScreen(ST7735_BLACK);
		//display set points from array
		tft.setTextSize(1);
		tft.println("");
		tft.setTextColor(ST7735_CYAN, ST7735_BLACK);
		tft.println("Setpoint, Deg C, Min");
		tft.println("");
		for (i = 0; i < ROW_DIM; i++) {
			for (j = 0; j < COL_DIM; j++) {
				if (j) {
				tft.print(' ');	}
			tft.print(array[i][j]);	}
		tft.println();	}
		while (digitalRead(sw1) == HIGH) {
			//this is where we handle ramp and PID parts
			for (i = 0; i < ROW_DIM; i++) {
				for (j = 0; j < COL_DIM; j++) {
					Setpoint = (array[i]);
					setTime = (array[j]);
					Serial.println(array[i]);
					Serial.println(array[j]);
				}
			}
			
			double c = kTC.readCelsius();
			total = total - readings[readIndex];
			readings[readIndex] = (c);
			total = total + readings[readIndex];
			readIndex = readIndex + 1;
			if (readings[readIndex] == -1)   {
			Serial.print("No sensor \n");	}
			else   {
				if (readIndex >= numReadings) {
				readIndex = 0;	}
			vavg = total / numReadings;	}
			Ftemp = ((vavg * 9 / 5) + 32);
			tft.setTextWrap(false);
			tft.setCursor(10, 50);
			tft.setTextColor(ST7735_GREEN, ST7735_BLACK);
			tft.setTextSize(2);
			tft.print(vavg);
			tft.println(" C  ");
			tft.setCursor(10, 70);
			tft.setTextColor(ST7735_RED, ST7735_BLACK);
			tft.print(Ftemp);
			tft.println(" F  ");
			tft.setTextColor(ST7735_WHITE, ST7735_BLACK);
			tft.println("");
			tft.setTextColor(ST7735_RED, ST7735_BLACK);
			tft.setTextSize(1);
			tft.print("Time Elapsed ");
			int mins = (millis() / 1000 / 60);
			tft.print(mins);
			tft.print(" Min");
		}
		setMenu();
	}

	void learn() { 	//log.txt is time in seconds followed by TempC then TempF
		delay(500);
		tft.fillScreen(ST7735_BLACK);
		tft.setTextWrap(false);
		tft.setCursor(0, 60);
		tft.setTextColor(ST7735_YELLOW, ST7735_BLACK);
		tft.setTextSize(2);
		tft.println(" LEARNING");
		Serial.println("Creating learn_log.txt...");
		while (digitalRead(sw1) == HIGH) {
			delay(220);
			// make a string for assembling the data to log:
			String dataString = "";
			double timer = millis();
			double secs = timer/1000;
			dataString += secs;
			dataString += (",");
			double sensor = kTC.readCelsius();
			double sensorcalC = (sensor - offset);
			dataString += sensor;
			dataString += (",");
			Ftemp = (sensor * 9 / 5) + 32;
			dataString += Ftemp;
			// open the file
			File dataFile = SD.open("log.TXT", FILE_WRITE);
			// if the file is available, write to it:
			if (dataFile) {
				dataFile.println(dataString);
				dataFile.close();
			}
			// if the file isn't open, pop up an error:
			else {
				Serial.println("error opening learn_log.txt");
			}
			digitalWrite(relay1, HIGH);
		}
		digitalWrite(relay1, LOW);
		setMenu();
	}

	void user_stop() {

	} //user interrupt ramp
	
	void PID_USER() {
		//define PID input
		Input = kTC.readCelsius();
		//run PID routine
		myPID.Compute();
		//turn the output pin on/off based on pid output
		unsigned long now = millis();
		Serial.println(Input);
		Serial.println(Output);
		if (now - windowStartTime > WindowSize)
		{ //time to shift the Relay Window
			windowStartTime += WindowSize;
		}
		if (Output > now - windowStartTime) {digitalWrite(relay1, HIGH); Serial.println("ON");}
		else {digitalWrite(relay1, LOW);
			Serial.println("OFF");
		}
	}

Your array is 2D, you have to supply 2 indices to access a single element.

Hi, Thanx for the post.
I just joined. Have been trying to get a kiln controller going for some time.
negativ3. what sort of mold material are you using? i run a foundry and your burnout cycle doesn't make sense to me. when i use ceramic shall molds its an aggressive burnout because the mods handle the shock well. room temp. to 700c as fast as possibly. hod for an hour and cast hot as possibly.
on investment the molds are fragile. ramp at 200c/hr max. firs soak at 200c for several hours depending on the mold diameter. then 200c/hr up to 700 and soak for long .min 5 to 6hrs. then bring down to 500c and soak for one hr to stabilize casting temp through the mod.

I dont beleave that pid is that important. small enough increments temp raise and small steps hold before next increment will work. a kiln has huge thermal mass. so every thing happens slow.
if you heat for 5 sec it takes the thermocouple on my kiln 10 to 15 sec to register.
'

ill post update on my build soon. going to see a friend this weekend who knows his way about code to help me resolve the "grammer"of my code.....

Hi. i just got this build done in prototype form and it works well.

i used a nano and 20 x4 i2c lcd. max 6675. and a home made ssr(to be upgraded) cheap micro sd card reader and a good quality micro sd card.

powered a soldering iron as heat source to test.