If anyone is interested, here is the program c file for V1 hardware (header files not shown). Still not complete, only current limiting omitted until I play with the hardware.
Code:
/*
Project: Solar fire-n-ice: An advanced bipolar PID software controller for Peltier devices.
File: main.c - Main Program.
Hardware platform: Atmel ATTiny84 MCU
Hardware version: 1.xx
Hardware developers:
Frank Hill (Solarfire) VooDooI@online.de
Wayne Osborn (dnar) wayne.osborn@dnaresearch.com.au
Credits: This is a Photonlexicon community project http://photonlexicon.com
@author Wayne Osborn wayne.osborn@dnaresearch.com.au, Copyright (C) 2011.
@see The GNU Public License (GPL)
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
// ***************************************************************
// Includes
// ***************************************************************
#include "main.h"
#include "stdlib.h"
#include "avr/eeprom.h"
#include "avr/io.h"
#include "avr/interrupt.h"
#include "avr/wdt.h"
#define _VERSION 0x0100
// ***************************************************************
// Global variables
// ***************************************************************
float P_Term; // (P) Proportional term
float I_State; // (I) Integral running state
float I_Term; // (I) Integral term
float D_Term; // (D) Derivitive term
float P_Gain; // Proportional gain
float I_Gain; // Integral gain
float D_Gain; // Deriviative gain
float PID_Gain; // Master PID Gain (pot)
int16_t iError; // Error value
uint16_t uiLastInput; // Previous error value
uint16_t uiNTC; // NTC raw ADC value
uint16_t uiSetPoint; // Setpoint as ADC value
int16_t iPV; // Process variable (PID output)
uint16_t uiISetPoint; // Current Limit Set Point
uint16_t uiSparePot; // Spare pot value
uint16_t uiCurrent; // Bridge current
uint16_t uiTimerPID; // PID Timer value
uint16_t uiTimerInhibit; // Inhibit Timer value
uint16_t uiTimerReadPots; // Read physical potentiometers timer
uint16_t uiTimerISense; // Current sense timer
uint16_t uiTimerStatus; // Status LED update timer
uint8_t uiTimerPIDInt; // PID Timer timeout flag
uint8_t uiTimerInhibitInt; // Inhibit timer timeout flag
uint8_t uiTimerPotsInt; // Read pots timer timeout flag
uint8_t uiTimerISenseInt; // Current sense timer timeout flag
uint8_t uiTimerStatusInt; // Status LED timer timout flag
// ***************************************************************
// Timer 1 overflow interrupt service routine (1002Hz) creating several synthesised timers
// ***************************************************************
ISR(TIM1_OVF_vect)
{
TCNT1 = _TIMER1_LOAD;
// ***************************************************************
// PID Timer (20mS)
// ***************************************************************
if (uiTimerPID) uiTimerPID--;
else
{
uiTimerPID = TIMER_PID;
uiTimerPIDInt = TRUE;
}
// ***************************************************************
// Read Potentiometers Timer (1000mS)
// ***************************************************************
if (uiTimerReadPots) uiTimerReadPots--;
else
{
uiTimerReadPots = TIMER_POTS;
uiTimerPotsInt = TRUE;
}
// ***************************************************************
// Current Sense Timer (40mS)
// ***************************************************************
if (uiTimerISense) uiTimerISense--;
else
{
uiTimerISense = TIMER_ISENSE;
uiTimerISenseInt = TRUE;
}
// ***************************************************************
// Inhibit Timer (1000mS)
// ***************************************************************
if (uiTimerInhibit) uiTimerInhibit--;
else
{
uiTimerInhibit = TIMER_INHIBIT;
uiTimerInhibitInt = TRUE;
}
// ***************************************************************
// Status Timer (250mS)
// ***************************************************************
if (uiTimerStatus) uiTimerStatus--;
else
{
uiTimerStatus = TIMER_STATUS;
uiTimerStatusInt = TRUE;
}
}
// ***************************************************************
// Program and MCU initialisation
// ***************************************************************
void init(void)
{
// Setup physical port direction
DDRA = PORT_A_DIR;
DDRB = PORT_B_DIR;
// Crystal Oscillator division factor
CLKPR = 0x00; // No divisor, run at 20MHz
//Analog Comparator Control and Status Register
ACSR = 0b10000000; // No comparator
// ADC Control and Status Register A
ADCSRA = 0b10000111; // ADC Prescaler = 128
// ADC Control and Status Register B
ADCSRB = 0b00000000;
// Disable digital input buffers on utilised ADC channels
DIDR0 = 0b10001111;
// Timer/Counter Control Register A (PWM control)
TCCR0A = 0b10000111; // Fast PWM on OC0A, Set OC0A on Compare Match, count down from OCR0A to 0
OCR0A = 0x00 ; // Initialise to 0% PWM
// Timer/Counter Control Register B (Timer with interrupts)
TCCR0B = 0b00000100; // CPU Clock = 20MHz, prescaler = 256;
TCNT0 = _TIMER1_LOAD; // Counter load value
// General Interrupt Mask Registe
GIMSK = 0x00; // No external interrupts
// MCU Control Register
MCUCR = 0x00;
// Timer/Counter 0 Interrupt Mask Register
TIMSK0 = 0x01; // Timer/Counter0 Overflow Interrupt Enable
// Watchdog Timer initialization
wdt_enable(WDTO_1S);
// Initialise program variables
iError = 0;
uiLastInput = 0;
iPV = 0;
// Enable interrupts
sei();
}
// ***************************************************************
// Initiate an ADC sample for a specified ADC channel and return the ADC result
// ***************************************************************
uint16_t Read_ADC_Sensor(uint8_t Channel)
{
ADMUX = Channel;
SETBIT(ADCSRA,ADSC);
//Wair for ADC to be ready
while (bit_is_clear(ADCSRA,ADIF));
//Read the ADC
SETBIT(ADCSRA,ADIF);
return ADCW;
}
// ***************************************************************
// PID loop update processing
// ***************************************************************
float UpdatePID(uint16_t uiTarget, uint16_t uiInput)
{
float WindupGaurd;
// Calculate the error
iError = uiInput - uiTarget;
// Update the Proportional term
P_Term = P_Gain * iError;
// Update the Integral term with windup protection
I_State += iError;
WindupGaurd = WINDUP_GUARD_GAIN / I_Gain;
if (I_State > WindupGaurd) I_State = WindupGaurd;
else if (I_State < -WindupGaurd) I_State = -WindupGaurd;
I_Term = I_Gain * I_State;
// Update the Derivative term
D_Term = (D_Gain * (uiInput - uiLastInput));
uiLastInput = uiInput;
// Now return the PID result
return P_Term + I_Term - D_Term;
}
// ***************************************************************
// The main loop. All processing is timer based
// ***************************************************************
int main(void)
{
init(); // Program initialisation
while (TRUE)
{
// ***************************************************************
// PID Update Timer timeout
// ***************************************************************
if (uiTimerPIDInt)
{
uiTimerPIDInt = FALSE;
// Read the Sensor ADC
uiNTC = 1023 - Read_ADC_Sensor(ADC_TSENSE); // Convert the temperature sample from 0..1023 to 1023.00 as the sensor slope is inverted.
// Update the PID loop
iPV = UpdatePID(uiSetPoint, uiNTC);
// **** Current Limiting to go Here ****
// Removed until hardware performance is terermined
// *************************************
if (iPV > 0) SETBIT(PORT_OUT, TEC_DIR); // Positive PV, set direction control
else CLRBIT(PORT_OUT, TEC_DIR); // Negative PV, clear direction control
OCR0A = abs(iPV); // Set the PWM to the absolute Process Value
}
// ***************************************************************
// LD-Inhibit Timer timeout
// ***************************************************************
if (uiTimerInhibitInt)
{
//Reset watchdog
wdt_reset();
uiTimerInhibitInt = FALSE;
// Update the Inhibit output if out of range
if ((uiNTC >= (uiSetPoint - INHIBIT_RANGE)) && (uiNTC <= (uiSetPoint + INHIBIT_RANGE)))
{
// Within +/- 2.5c enable laser modulation
SETBIT(PORT_OUT, LD_INHIBIT);
}
else
{
CLRBIT(PORT_OUT, LD_INHIBIT);
}
}
// ***************************************************************
// Read Set Points Timer timeout
// ***************************************************************
if (uiTimerPotsInt)
{
uiTimerPotsInt = FALSE;
// Read the Tuning Pot ADC
PID_Gain = Read_ADC_Sensor(ADC_TUNE);
// Read the I Set Pot ADC
uiSetPoint = Read_ADC_Sensor(ADC_ISET);
// Read the T Set Pot ADC
uiISetPoint = Read_ADC_Sensor(ADC_TSET);
}
// ***************************************************************
// Read Bridge Current Timer timeout
// ***************************************************************
if (uiTimerISenseInt)
{
uiTimerISenseInt = FALSE;
// Read the Bridge Sense ADC
uiCurrent = Read_ADC_Sensor(ADC_TSENSE);
}
// ***************************************************************
// Update Status Timer timeout
// ***************************************************************
if (uiTimerStatusInt)
{
uiTimerStatusInt = FALSE;
// Update the under/over temperature LED's.
if (uiNTC - ADCPERC < uiSetPoint)
{
SETBIT(PORT_LED, LED_UNDER); // Under Temp LED ON
CLRBIT(PORT_LED, LED_OVER); // Over Temp LED OFF
}
else if (uiNTC + ADCPERC > uiSetPoint)
{
CLRBIT(PORT_LED, LED_UNDER); // Under Temp LED OFF
SETBIT(PORT_LED, LED_OVER); // Over Temp LED ON
}
else
{
CLRBIT(PORT_LED, LED_UNDER); // Under Temp LED OFF
CLRBIT(PORT_LED, LED_OVER); // Over Temp LED OFF
}
}
}
}