Structured Arduino programming for thyristor regulated heating of pottery kiln

Some reflections on PID-programming.

Sofar, I have anticipated a setpoint curve with several houndreds of temperature data, supposed to be read with intervals of 30 sec. But look at the curve below (and forget the lack of minus on the right side). Its fairly simple, and could be represented by this :

Slope time temp
3.1 200 620
0 10 620
-0.92 240 400
-1.35 148 200
-3.3 60 0

Where temp=620 is the setpoint after 200 minutes with slope=3.1

As PID algorithm uses slope to calculate output, a lot of readings from the memory (micro-SD) can be avoided. Is this possible?

Yes. At it's simplest, you could write a hard coded function that takes a time in minutes parameter and returns the required temperature by calculating it using the slope data above.

Since you need different heating/cooling profiles, you could store the data for each one in an array of structs containing time and slope. Since that data is constant, the arrays could be stored in progmem if necessary to save RAM. You would need some input mechanism to select the required profile.

Better again would be to have an array of profiles. You might consider separating the profiles into heating and cooling and being able to choose them individually for each run of the kiln.

For more flexibility, the profile data could be stored on the SD card and then you could create new ones without needing to recompile your code.

wildbill:
Better again would be to have an array of profiles...

For more flexibility, the profile data could be stored on the SD card and then you could create new ones without needing to recompile your code.

I think this might be the best option for me. Thanks wildbill!

Yes, you can store the profile as a few points, with lines between the points. However, the points will be defined as (time, temp), not (time,slope). For the curve above, you would do something like:

struct point_t { int time; int temp; };

point_t profile[] = {
  {   0,  22 },
  { 200, 625 },
  { 210, 625 },
  { 450, 400 },
  { 600, 200 },
  { 660,  22 }
};

Then you will do "linear interpolation" between the points.

Cheers,
/dev

Thanks /dev

Another maybe newbie question. Some of the shields I plan to use are based on SPI protocol, but the DAC (MCP4725) is based on I2C. I read somewhere that "combining I2C and SPI on one microcontroller is like running a car with diesel and oil at the same time - it won't work." (perhaps meant diesel and petrol).

Is it possible to combine the 2 protocols in the same sketch? How to go around this?
/Kaj

I read somewhere that "combining I2C and SPI on one microcontrollerwon't work."

Hmmm... if you could find that, we could probably tell you why it does or does not apply. I have seen problems with Ethernet shields coexisting with other devices, but I really think most of those problems have to do with (1) inappropriate ISRs or (2) multiple devices coexisting on the same SPI (or I2C).

In your case, the devices are on different interfaces, and the devices are not interrupting the UNO for attention. The UNO is outputting batches of data to each one, individually. You can completely control that timing, so I wouldn't expect a problem.

As you begin developing, you should get each interface working by itself, then try to make them both work in the same sketch. If they don't work together, you'll know they must be interfering, because they worked separately. Again, I wouldn't expect a problem.

I think you will have SD and LCD coexisting on the SPI interface, though. The newest SPI libraries have resolved one of the problems with that configuration. Seems like I read that you might have to be more careful with the pull-ups required for those two devices... or it might work first time. :slight_smile: It probably depends on the specific modules you buy. Maybe somebody can suggest a pair that is known to work together?

Cheers,
/dev

/dev:
Hmmm... if you could find that, we could probably tell you why it does or does not apply.

The conclusion made is about LED and a realtime clock, and You'll find it here.

LED and a realtime clock here.

Ah, great! readreadread... Ok. I have used both interfaces, but never at the same time. I was hoping someone who has would chime in. Until then...

  • That project was on a Due, which is a 32-bit processpr. Its software did have some differences from the 8-bit products. Perhaps there is/was a conflict on the Due.
  • That user only made those 3 posts, and (suprisingly) no one ever answered his question. He did include his code, so I have to give him some credit, if not credence. :slight_smile:
  • The post is 5 years old, and I know the SPI interface has been updated since then. I would guess the Wire interface has also been updated.
  • The RGBMatrix code he downloaded from Adafruit has its own implementation of SPI. It didn't use the official SPI library.
  • After reviewing the documents for the ATmega328 processor, it appears that the SPI and TWI (aka I2C) interfaces are completely independent modules. I thought that was the case, but I checked to be sure. One problem with SPI has been that devices with different transfer rates may not peacefully coexist, but that has been fixed.
  • The default TWI and SPI configuration on a Leonardo does have a pin conflict, but you can use different pins to resolve that. You are using an UNO instead.
  • After a bit of googling, I can't find anything that says there is a conflict. Adafruit recently responded to a similar question here.
  • Although the SPI library does not generate interrupts by default, they can be enabled. You shouldn't need them. Similarly, the TWI library handles its own interrupts by default. You can configure it to call your code for some of the interrupts, but you shouldn't need them.
    Unless somebody has something else to offer, I'm sticking by my original claim: most SPI/I2C conflicts are caused by naive interrupt handling or device conflicts on the same interface. You should be fine.

Cheers,
/dev

/dev:
most SPI/I2C conflicts are caused by naive interrupt handling or device conflicts on the same interface. You should be fine.

Thanks for your review /dev !

Started to struggle w code. To start with, I want to read setpoint data into 2 arrays, TempArray and TimeArray, from mSD. Here's my code:

#include <SD.h>

//Declarations
int Temp = 0;
int Time = 0;
int TempArray[10];
int TimeArray[10];
int i;

void setup()
{
Serial.begin(9600);
while (!Serial)
{
}
pinMode(10,OUTPUT);
if(!SD.begin(8))
{
Serial.println("initialization failed!");
return;
}

for (i=0; i<10; i++);
{
// Read from file in mSD
File SPFile = SD.open("RawGlaz.txt");
    if (SPFile) 
{
	Serial.println("RawGlaz.txt");
	while (SPFile.available()) 
{
  Temp = SPFile.parseInt();
  Time = SPFile.parseInt();
  TempArray[i] = Temp;
  TimeArray[i] = Time;

    if (Temp == -1)
{
    break;
}
}
// close file:
SPFile.close();
Serial.println("done.");
for(i=0; i<10; i++)
{
  Serial.println(TempArray[i]);
}
}
else
{
// if the file didn't open, print an error:
Serial.println("error opening RawGlaz.txt");
}
}
}
void loop()
{
  //Reading emf from thermocouple and transform to temperature
}

RawGlaz contains this: 0,20,240,300,280,790,300,790,-1,20

The result should be a printout like this:
RawGlaz.txt
done.
20
300
790
790
20
0
0
0
0
0

But all values in the array is 0. What is wrong?

You don't need two loops to do this - in particular you don't need to open/close the file ten times with your for loop. Open the file and then loop through it once getting your data items.

Before you make any logic changes though, I'd suggest that you add a couple of serial prints just after where you read temp and time to show you their values at that point and it will let you see what's happening.

Just as a guess, Time might be used elsewhere by Arduino so you can't have a global variable called Time. Try time or declare it as a local variable inside setup() - because you don't use it anywhere else.

The SPI and I2C interfaces are used in lots of applications. I use both in several of my Arduino projects and I've never seen a problem.

wildbill:
You don't need two loops ..don't need to open/close the file ten times

..add a couple of serial prints just after where you read temp and time to show..

wildbill, right now Im using a lot of serial prints just to check out. The second for loop was put there to double check on the time- and tempArrays. The resulting output isnt quite satisfying, and I cant see why.

MorganS:
Try time or declare it as a local variable inside setup() - because you don't use it anywhere else.

The SPI and I2C ... I use both in several of my Arduino projects and I've never seen a problem.

Changed to time, ok. And actually, there seems to be no SPI/I2C problems for me either
.

This is the content of the [RawGlaz] file: 0,20,240,300,280,790,300,790,300,-1

Here is the output:

RawGlaz.txt
0 ; 0
240 ; 300
280 ; 790
300 ; 790
300 ; -1
done.
10 ; 300

I cant understand why 0 ; 0 on the first row, should be 0 ; 20. And I certainly cant understand the output 10 ; 300 from the second loop. I expected:

0 ; 20
240 ; 300
280 ; 790
300 ; 790
300 ; -1
0 ; 0
0 ; 0
0 ; 0
0 ; 0
0 ; 0

Here's the code:

#include <SD.h>

//Declarations
int temp = 0;
int time = 0;
int tempArray[10];
int timeArray[10];
int i;

void setup()
{
Serial.begin(9600);
while (!Serial)
{
}
pinMode(10,OUTPUT);
if(!SD.begin(8))
{
Serial.println("initialization failed!");
return;
}
File SPFile = SD.open("RawGlaz.txt");
for (i=0; i<10; i++);
{
// Read from file in mSD
    if (SPFile) 
{
	Serial.println("RawGlaz.txt");
	while (SPFile.available()) 
{
  time = SPFile.parseInt();
  temp = SPFile.parseInt();
  tempArray[i] = temp;
  timeArray[i] = time;
  Serial.print(timeArray[i]);
  Serial.print(" ; ");
  Serial.println(tempArray[i]);
    if (temp == -1)
{
    break;
}
}
}
else
{
// if the file didn't open, print an error:
Serial.println("error opening RawGlaz.txt");
}
}
// close file:
SPFile.close();
Serial.println("done.");
for (i=0; i<10; i++);
{
Serial.print(timeArray[i]);
Serial.print(" ; ");
Serial.println(tempArray[i]);
}
}
void loop()
{
  //Reading emf from thermocouple and transform to temperature
  
}

You still don't need both loops, but the final semicolon on this line is doing the damage that prevents you seeing it:
for (i=0; i<10; i++);

Get rid of it & things will improve - somewhat.

wildbill:
...things will improve - somewhat.

So they did. Within the important loop (not the control loop) there are correct values of the arrays. But when the "reading loop" is left, the print command results in mismatch. First value is the last value, rest is just
0 ; 0. This implies that the arrays dont keep correct values for the calculations supposed to be made within
void loop().

#include <SD.h>

//Declarations

int temp, time, tempArray[5], timeArray[5], i;

void setup()
{
Serial.begin(9600);
while (!Serial)
{
}
pinMode(10,OUTPUT);
if(!SD.begin(8))
{
Serial.println("initialization failed!");
return;
}
File SPFile = SD.open("RawGlaz.txt");
Serial.println("RawGlaz.txt");
for (int i=0; i<5; i++)
{
// Read from file in mSD
    if (SPFile) 
{
 while (SPFile.available()) 
{
  time = SPFile.parseInt();
  temp = SPFile.parseInt();
 
  tempArray[i] = temp;
  timeArray[i] = time;
  Serial.print(timeArray[i]);
  Serial.print(" ; ");
  Serial.println(tempArray[i]);
//    if (temp == -1)
//{
//  i=0;  
//  break;
//}
}
}
else
{
// if the file didn't open, print an error:
Serial.println("error opening RawGlaz.txt");
}
}
// close file:
SPFile.close();
Serial.println("done.");
for (int i=0; i<5; i=i+1)
{
  Serial.println(i);
  Serial.print(timeArray[i]);
  Serial.print(" ; ");
  Serial.println(tempArray[i]);
}
}
void loop()
{
  //Reading emf from thermocouple and transform to temperature
  
}

This:

 while (SPFile.available())

Consumes all the data in the file while i=0. The other iterations of your for loop get nothing, as you see.

Try this:

#include <SD.h>

//Declarations

int temp, time, tempArray[5], timeArray[5], i;

void setup()
{
  Serial.begin(9600);
  while (!Serial)
  {
  }
  pinMode(10,OUTPUT);
  if(!SD.begin(8))
  {
    Serial.println("initialization failed!");
    return;
  }
  File SPFile = SD.open("RawGlaz.txt");
  Serial.println("RawGlaz.txt");
  if (SPFile) 
  {
    for (int i=0; i<5; i++)
    {
        time = SPFile.parseInt();
        temp = SPFile.parseInt();

        tempArray[i] = temp;
        timeArray[i] = time;
        Serial.print(timeArray[i]);
        Serial.print(" ; ");
        Serial.println(tempArray[i]);
    }
  }
  else
  {
    // if the file didn't open, print an error:
    Serial.println("error opening RawGlaz.txt");
  }
  // close file:
  SPFile.close();
  Serial.println("done.");
  for (int i=0; i<5; i=i+1)
  {
    Serial.println(i);
    Serial.print(timeArray[i]);
    Serial.print(" ; ");
    Serial.println(tempArray[i]);
  }
}
void loop()
{
  //Reading emf from thermocouple and transform to temperature

}

Thanks a lot!

Coding continues...
First part - to get setpoint values - is almost completed. But there's one big drawback. I would like to put all code concerning input of setpoint values in the setup part of the sketch. Tried different ways without success. Here are the premises:

I have 7 files on mSD, with different burning data.
One example file: RawGlaz.txt: 0,20,240,300,280,790,300,790,315,20,0,0
Would like to choose one of these files, to get the right time/temp relations for the actual burning.
By Serial.read I can choose file 1-7 and thus read the correct data in this file.
But I dont realize how to get "if (Serial.available())" working as expected in "void setup". I realise that one should find a method to delay and wait for an input, but I cant figure out how.

Right now this procedure is placed in the "void loop"-part of the sketch, and it works ok. But I would seriously avoid to open/close mSD-files everytime the process moves through the loop. Any clues?

Heres the code. Dont care for all prints, right now they're only checkpoint for me.

#include <SD.h>
//Declarations
char* BurnCurve[]={
  "GLABEND.TXT", "RAWGLAZ.TXT", "DRYING_.TXT", "STOWARE.TXT", "STOGLAZ.TXT", "CLAGLAZ.TXT", "BISCUIT.TXT"};
int i, temp, time, tempArray[5], timeArray[5];
int incomingByte;

void setup()
//Open serial comm. & set comm. channels
{
  Serial.begin(9600);
  while (!Serial)
  {
  }
  pinMode(10,OUTPUT);
  if(!SD.begin(8))
  {
    Serial.println("initialization failed!");
    return;
  }
  // Instructions to choose right Burn curve
  for (i=0; i<7; i++) 
  {     
    Serial.print(BurnCurve[i]);
    Serial.print(" - Choose ");
    Serial.print(i + 1);
    Serial.println(" for this curve");       
  }    
}
void loop()
{
  // Input number of Burn curve
  if (Serial.available())
  {
    // read the incoming byte:
    incomingByte = Serial.read();
    incomingByte = incomingByte - 49;               
    Serial.println("");
    Serial.print("Burn Curve is:  ");
    Serial.println(BurnCurve[incomingByte]);

    File SPFile = SD.open(BurnCurve[incomingByte]);
    Serial.println("");
    if (SPFile) 
    {
      for (int i=0; i<6; i++)
      {
        time = SPFile.parseInt();
        temp = SPFile.parseInt();

        tempArray[i] = temp;
        timeArray[i] = time;
        Serial.println("");
        Serial.print(timeArray[i]);
        Serial.print("  ");
        Serial.println(tempArray[i]);
      }
    }
else
  {
    // if the file didn't open, print an error:
    Serial.println("error opening RawGlaz.txt");
  }
  // close file:
  SPFile.close();
  Serial.println("done.");    
  }
}

/*
{
 Calculate SetPoint values from temp/time Arrays
}
{
 Catch data from the thermocouples and transform to degrees centrigrade
}
{
 PDI calculations
}
{
 Output to thyristor stack
}
EOF
*/

Here's the output when choosing file GlaBend.txt through sending "1" as input:
GLABEND.TXT - Choose 1 for this curve
RAWGLAZ.TXT - Choose 2 for this curve
DRYING_.TXT - Choose 3 for this curve
STOWARE.TXT - Choose 4 for this curve
STOGLAZ.TXT - Choose 5 for this curve
CLAGLAZ.TXT - Choose 6 for this curve
BISCUIT.TXT - Choose 7 for this curve

Burn Curve is: GLABEND.TXT

0 20

200 625

210 625

450 400

600 200

615 20
done.

Move the setpoint code back to setup and add code before it to wait for serial input. i.e.

while(Serial.available()==0)
  ; // Do nothing

while(Serial.available()==0)

Thanks wildbill, that works, although a bit slow.

I'm trying with another solution as well. A variable "int readfile" set to 0 at global declaration, then set to 1 after first loop w Serial.available and testing status with "if" before Serial.available works well within void loop, and much faster than "while ()". But yet not when moved to void setup. Perhaps did some mismatch when moved.