// This #include statement was automatically added by the Spark IDE.
#include "LiquidCrystal_I2C.h"
// This #include statement was automatically added by the Spark IDE.
#include "PID_v1.h"
#include "math.h"
#define PWM_FREQ 20000 // in Hertz (SET YOUR FREQUENCY) for analogWrite2
#define pitPin A6
#define meatPin A7
#define downPin D2
#define upPin D3
#define motorPin A0
uint16_t TIM_ARR = (uint16_t)(24000000 / PWM_FREQ) - 1; // Don't change! Calc's period.
char resultstr[64];
int val = 0; // variable to store the read ADC value
double Setpoint, Output, Input;
int meatTemp, pitTemp, P, I, D,motor,set;
unsigned long downTime=0;
unsigned long upTime=0;
unsigned long Time;
LiquidCrystal_I2C *lcd;
PID myPID(&Input, &Output, &Setpoint,10,0.05,2, DIRECT);
Servo fanServo;
void setup() {
Spark.variable("Input",&pitTemp,INT);
Spark.variable("Setpoint",&set,INT);
Spark.variable("Output",&motor,INT);
Spark.variable("meatTemp",&meatTemp,INT);
Spark.variable("P",&P,INT);
Spark.variable("I",&I,INT);
Spark.variable("D",&D,INT);
Spark.function("webControl",webControl);
Spark.variable("result", &resultstr, STRING);
pinMode(downPin, INPUT_PULLDOWN);
pinMode(upPin, INPUT_PULLDOWN);
pinMode(A0,OUTPUT);
Input = thermistor_temp(analogRead(pitPin));
fanServo.attach(A4);
Setpoint = 225;
myPID.SetMode(AUTOMATIC);
myPID.SetOutputLimits(75,255); //80 is the lowest value that the fan would kick on. 79 will be off
myPID.SetSampleTime(1000); //slows pid sample time due to slow moving system
lcd = new LiquidCrystal_I2C(0x27, 16, 2); // set the LCD address to 0x20 for a 16x2 //SparkCore bug, address is actually 27 but shifted (0x27*2)
lcd->init(); // initialize the lcd
lcd->backlight();
lcd->clear();
lcd->setCursor(0,0);
lcd->print("Pit:");
lcd->setCursor(0,1);
lcd->print("Meat:");
lcd->setCursor(8,0);
lcd->print("Set:");
lcd->setCursor(4,0);
// lcd->print(thermister_temp(analogRead(6)));
}
void loop() {
Input = thermistor_temp(analogRead(pitPin));
myPID.Compute();
pitTemp = Input;
meatTemp = thermistor_temp(analogRead(meatPin));
motor = map(Output,75,255,0,100);
if ((Setpoint-Input > 10) or (Input-Setpoint > 10)){
myPID.SetTunings(myPID.GetKp(),0,myPID.GetKd());
}
else myPID.SetTunings(myPID.GetKp(),0.05,myPID.GetKd());
P = myPID.GetKp();
I = myPID.GetKi();
D = myPID.GetKd();
set = Setpoint;
if (Output <= 79) analogWrite2(motorPin,0); //Turn fan off at low point of range
else analogWrite2(A0,Output);
if (Output == 75) fanServo.write(10);
else fanServo.write(85);
if (digitalRead (D2) == HIGH){ //Listen for button press
Time = millis();
if (Time-upTime > 150){ //debounce and slow increment
Setpoint++;
upTime = millis();
}
}
if (digitalRead(D3) == HIGH){
Time = millis();
if (Time-downTime>150){
Setpoint--;
downTime = millis();
}
}
lcdPrint(); //calls print function
sprintf(resultstr, "{\"pitTemp\":%d,\"set\":%d, \"motor\":%d}", pitTemp, set, motor);
}
int webControl(String command){ //allow app to interface through API
if (command == "setUp") Setpoint++;
if (command == "setDown") Setpoint--;
if (command.startsWith("S") == true){ // change paramaters via web/app
Setpoint = double(command.substring(1,4).toInt());
}
if (command.startsWith("P") == true){ // change paramaters via web/app
String holder = command.substring(1,4);
myPID.SetTunings(double(holder.toInt()),myPID.GetKi(),myPID.GetKd());
P = myPID.GetKp();
}
if (command.startsWith("I") == true){
String holder = command.substring(1,4);
myPID.SetTunings(myPID.GetKp(),double(holder.toInt()),myPID.GetKd());
I = myPID.GetKi();
}
if (command.startsWith("D") == true){
String holder = command.substring(1,4);
myPID.SetTunings(myPID.GetKp(),myPID.GetKi(),double(holder.toInt()));
D = myPID.GetKd();
}
return 1;
}
void lcdPrint(){ // update lcd
if (Input < 0){
lcd->setCursor(4,0);
lcd->print("XXX");
}
else{
lcd->setCursor(4,0);
lcd->print((int)(Input));
lcd->print((char)223);
if (Input < 100){
lcd->setCursor(8,1);
lcd->print(" ");
}
}
if (meatTemp < 0){
lcd->setCursor(5,1);
lcd->print("XXX");
}
else{
lcd->setCursor(5,1);
lcd->print(meatTemp);
lcd->print((char)223);
if (meatTemp < 100){
lcd->setCursor(7,0);
lcd->print(" ");
}
}
lcd->setCursor(12,0);
lcd->print((int)(Setpoint));
lcd->print((char)223);
if(Setpoint < 100){
lcd->setCursor(15,0);
lcd->print(" ");
}
lcd->setCursor(12,1);
lcd->print((int)(map(Output,75,255,0,100)));
// lcd->print("%");
if (map(Output,79,255,0,100) == 100) {
// lcd->setCursor(14,1);
lcd->print("%");
}
else if (map(Output,79,255,0,100) < 100){
lcd->print("% ");
}
else if (map(Output,79,255,0,100) < 10){
//lcd->setCursor(13,1);
lcd->print("% ");
}
}
int thermistor_temp(int aval) { //Function to do theristor calculations
double R, T;
R = log((1 / ((4095 / (double) aval) - 1)) * (double) 20800);
T = (1 / ((2.3067434E-4) + (2.3696596E-4) * R + (1.2636414E-7) * R * R * R)) - 273.25;
// return degrees F
return ((int) ((T * 9.0) / 5.0 + 32.0));
}
void analogWrite2(uint16_t pin, uint8_t value) { //analogWrite function allowing 20khz pwm credit bdub from Sark forums
TIM_OCInitTypeDef TIM_OCInitStructure;
if (pin >= TOTAL_PINS || PIN_MAP[pin].timer_peripheral == NULL) {
return;
}
// SPI safety check
if (SPI.isEnabled() == true && (pin == SCK || pin == MOSI || pin == MISO)) {
return;
}
// I2C safety check
if (Wire.isEnabled() == true && (pin == SCL || pin == SDA)) {
return;
}
// Serial1 safety check
if (Serial1.isEnabled() == true && (pin == RX || pin == TX)) {
return;
}
if (PIN_MAP[pin].pin_mode != OUTPUT && PIN_MAP[pin].pin_mode != AF_OUTPUT_PUSHPULL) {
return;
}
// Don't re-init PWM and cause a glitch if already setup, just update duty cycle and return.
if (PIN_MAP[pin].pin_mode == AF_OUTPUT_PUSHPULL) {
TIM_OCInitStructure.TIM_Pulse = (uint16_t)(value * (TIM_ARR + 1) / 255);
if (PIN_MAP[pin].timer_ch == TIM_Channel_1) {
PIN_MAP[pin].timer_peripheral-> CCR1 = TIM_OCInitStructure.TIM_Pulse;
} else if (PIN_MAP[pin].timer_ch == TIM_Channel_2) {
PIN_MAP[pin].timer_peripheral-> CCR2 = TIM_OCInitStructure.TIM_Pulse;
} else if (PIN_MAP[pin].timer_ch == TIM_Channel_3) {
PIN_MAP[pin].timer_peripheral-> CCR3 = TIM_OCInitStructure.TIM_Pulse;
} else if (PIN_MAP[pin].timer_ch == TIM_Channel_4) {
PIN_MAP[pin].timer_peripheral-> CCR4 = TIM_OCInitStructure.TIM_Pulse;
}
return;
}
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
//PWM Frequency : PWM_FREQ (Hz)
uint16_t TIM_Prescaler = (uint16_t)(SystemCoreClock / 24000000) - 1; //TIM Counter clock = 24MHz
// TIM Channel Duty Cycle(%) = (TIM_CCR / TIM_ARR + 1) * 100
uint16_t TIM_CCR = (uint16_t)(value * (TIM_ARR + 1) / 255);
// AFIO clock enable
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
pinMode(pin, AF_OUTPUT_PUSHPULL);
// TIM clock enable
if (PIN_MAP[pin].timer_peripheral == TIM2)
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
else if (PIN_MAP[pin].timer_peripheral == TIM3)
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
else if (PIN_MAP[pin].timer_peripheral == TIM4)
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE);
// Time base configuration
TIM_TimeBaseStructure.TIM_Period = TIM_ARR;
TIM_TimeBaseStructure.TIM_Prescaler = TIM_Prescaler;
TIM_TimeBaseStructure.TIM_ClockDivision = 0;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(PIN_MAP[pin].timer_peripheral, & TIM_TimeBaseStructure);
// PWM1 Mode configuration
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
TIM_OCInitStructure.TIM_Pulse = TIM_CCR;
if (PIN_MAP[pin].timer_ch == TIM_Channel_1) {
// PWM1 Mode configuration: Channel1
TIM_OC1Init(PIN_MAP[pin].timer_peripheral, & TIM_OCInitStructure);
TIM_OC1PreloadConfig(PIN_MAP[pin].timer_peripheral, TIM_OCPreload_Enable);
} else if (PIN_MAP[pin].timer_ch == TIM_Channel_2) {
// PWM1 Mode configuration: Channel2
TIM_OC2Init(PIN_MAP[pin].timer_peripheral, & TIM_OCInitStructure);
TIM_OC2PreloadConfig(PIN_MAP[pin].timer_peripheral, TIM_OCPreload_Enable);
} else if (PIN_MAP[pin].timer_ch == TIM_Channel_3) {
// PWM1 Mode configuration: Channel3
TIM_OC3Init(PIN_MAP[pin].timer_peripheral, & TIM_OCInitStructure);
TIM_OC3PreloadConfig(PIN_MAP[pin].timer_peripheral, TIM_OCPreload_Enable);
} else if (PIN_MAP[pin].timer_ch == TIM_Channel_4) {
// PWM1 Mode configuration: Channel4
TIM_OC4Init(PIN_MAP[pin].timer_peripheral, & TIM_OCInitStructure);
TIM_OC4PreloadConfig(PIN_MAP[pin].timer_peripheral, TIM_OCPreload_Enable);
}
TIM_ARRPreloadConfig(PIN_MAP[pin].timer_peripheral, ENABLE);
// TIM enable counter
TIM_Cmd(PIN_MAP[pin].timer_peripheral, ENABLE);
}
The code is pretty messy so I would welcome any suggestions. I will post the schematic as soon as I finish I’m pretty new to fritzing.
David