PID rework - use watts
This commit is contained in:
@@ -116,9 +116,8 @@ enum TipType {
|
||||
#endif
|
||||
|
||||
uint16_t getHandleTemperature();
|
||||
uint16_t getTipRawTemp(uint8_t instant);
|
||||
uint16_t getTipRawTemp(uint8_t refresh);
|
||||
uint16_t getInputVoltageX10(uint16_t divisor);
|
||||
uint16_t getTipInstantTemperature();
|
||||
uint8_t getTipPWM();
|
||||
void setTipPWM(uint8_t pulse);
|
||||
uint16_t ctoTipMeasurement(uint16_t temp);
|
||||
@@ -128,8 +127,8 @@ uint16_t tipMeasurementToF(uint16_t raw);
|
||||
void seekQC(int16_t Vx10,uint16_t divisor);
|
||||
void setCalibrationOffset(int16_t offSet);
|
||||
void setTipType(enum TipType tipType, uint8_t manualCalGain);
|
||||
uint32_t calculateTipR(uint8_t useFilter);
|
||||
int16_t calculateMaxVoltage(uint8_t useFilter, uint8_t useHP);
|
||||
uint32_t calculateTipR();
|
||||
int16_t calculateMaxVoltage(uint8_t useHP);
|
||||
void startQC(uint16_t divisor); // Tries to negotiate QC for highest voltage, must be run after
|
||||
// RToS
|
||||
// This will try for 12V, failing that 9V, failing that 5V
|
||||
|
||||
41
workspace/TS100/inc/history.hpp
Normal file
41
workspace/TS100/inc/history.hpp
Normal file
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* history.hpp
|
||||
*
|
||||
* Created on: 28 Oct, 2018
|
||||
* Authors: Ben V. Brown, David Hilton
|
||||
*/
|
||||
|
||||
#ifndef HISTORY_HPP_
|
||||
#define HISTORY_HPP_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
// max size = 127
|
||||
template <class T=uint16_t, uint8_t SIZE=15>
|
||||
struct history {
|
||||
static const uint8_t size = SIZE;
|
||||
T buf[size];
|
||||
int32_t sum;
|
||||
uint8_t loc;
|
||||
|
||||
void update(T const val) {
|
||||
// step backwards so i+1 is the previous value.
|
||||
loc = (size+loc-1) % size;
|
||||
|
||||
sum -= buf[loc];
|
||||
sum += val;
|
||||
buf[loc] = val;
|
||||
}
|
||||
|
||||
T operator[] (uint8_t i) const {
|
||||
// 0 = newest, size-1 = oldest.
|
||||
i = (i+loc) % size;
|
||||
return buf[i];
|
||||
}
|
||||
|
||||
T average() const {
|
||||
return sum / size;
|
||||
}
|
||||
};
|
||||
|
||||
#endif /* HISTORY_HPP_ */
|
||||
21
workspace/TS100/inc/power.hpp
Normal file
21
workspace/TS100/inc/power.hpp
Normal file
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* Power.hpp
|
||||
*
|
||||
* Created on: 28 Oct, 2018
|
||||
* Authors: Ben V. Brown, David Hilton
|
||||
*/
|
||||
|
||||
#include "stdint.h"
|
||||
#include <history.hpp>
|
||||
|
||||
#ifndef POWER_HPP_
|
||||
#define POWER_HPP_
|
||||
|
||||
extern history<uint16_t, 75> milliWattHistory;
|
||||
|
||||
int32_t tempToMilliWatts(int32_t rawTemp, uint16_t mass, uint8_t rawC);
|
||||
void setTipMilliWatts(int32_t mw);
|
||||
uint8_t milliWattsToPWM(int32_t milliWatts, uint8_t divisor);
|
||||
int32_t PWMToMilliWatts(uint8_t pwm, uint8_t divisor);
|
||||
|
||||
#endif /* POWER_HPP_ */
|
||||
@@ -664,20 +664,8 @@ static void settings_displayTipModel(void) {
|
||||
#endif
|
||||
}
|
||||
static void calibration_displaySimpleCal(void) { printShortDescription(18, 5); }
|
||||
static void dotDelay() {
|
||||
for (uint8_t i = 0; i < 20; i++) {
|
||||
getTipRawTemp(
|
||||
1); // cycle through the filter a fair bit to ensure we're stable.
|
||||
OLED::clearScreen();
|
||||
OLED::setCursor(0, 0);
|
||||
for (uint8_t x = 0; x < i / 4; x++) OLED::print(".");
|
||||
OLED::refresh();
|
||||
osDelay(50);
|
||||
}
|
||||
}
|
||||
static void setTipOffset() {
|
||||
setCalibrationOffset(0); // turn off the current offset
|
||||
dotDelay();
|
||||
|
||||
// If the thermocouple at the end of the tip, and the handle are at
|
||||
// equalibrium, then the output should be zero, as there is no temperature
|
||||
@@ -685,8 +673,7 @@ static void setTipOffset() {
|
||||
|
||||
int32_t offset = 0;
|
||||
for (uint8_t i = 0; i < 15; i++) {
|
||||
offset += getTipRawTemp(
|
||||
1); // cycle through the filter a fair bit to ensure we're stable.
|
||||
offset += getTipRawTemp(1);
|
||||
|
||||
OLED::clearScreen();
|
||||
OLED::setCursor(0, 0);
|
||||
@@ -719,7 +706,6 @@ static void calibration_enterSimpleCal(void) {
|
||||
OLED::refresh();
|
||||
osDelay(200);
|
||||
waitForButtonPress();
|
||||
dotDelay(); // cycle the filter a bit
|
||||
// Now take the three hot measurements
|
||||
// Assume water is boiling at 100C
|
||||
uint32_t RawTipHot = getTipRawTemp(0) * 10;
|
||||
|
||||
@@ -118,28 +118,16 @@ uint16_t lookupTipDefaultCalValue(enum TipType tipID) {
|
||||
#endif
|
||||
}
|
||||
|
||||
uint16_t getTipRawTemp(uint8_t instant) {
|
||||
static int64_t filterFP = 0;
|
||||
uint16_t getTipRawTemp(uint8_t refresh) {
|
||||
static uint16_t lastSample = 0;
|
||||
const uint8_t filterBeta = 7; // higher values smooth out more, but reduce responsiveness
|
||||
|
||||
if (instant == 1) {
|
||||
uint16_t itemp = getTipInstantTemperature();
|
||||
filterFP = (filterFP << filterBeta) - filterFP;
|
||||
filterFP += (itemp << 9);
|
||||
filterFP = filterFP >> filterBeta;
|
||||
uint16_t temp = itemp;
|
||||
itemp += lastSample;
|
||||
itemp /= 2;
|
||||
lastSample = temp;
|
||||
return itemp;
|
||||
} else if (instant == 2) {
|
||||
filterFP = (getTipInstantTemperature() << 8);
|
||||
return filterFP >> 9;
|
||||
} else {
|
||||
return filterFP >> 9;
|
||||
if (refresh) {
|
||||
lastSample = getTipInstantTemperature();
|
||||
}
|
||||
|
||||
return lastSample;
|
||||
}
|
||||
|
||||
uint16_t getInputVoltageX10(uint16_t divisor) {
|
||||
// ADC maximum is 32767 == 3.3V at input == 28.05V at VIN
|
||||
// Therefore we can divide down from there
|
||||
@@ -333,7 +321,7 @@ void startQC(uint16_t divisor) {
|
||||
QCMode = 0;
|
||||
}
|
||||
// Get tip resistance in milliohms
|
||||
uint32_t calculateTipR(uint8_t useFilter) {
|
||||
uint32_t calculateTipR() {
|
||||
// We inject a small current into the front end of the iron,
|
||||
// By measuring the Vdrop over the tip we can calculate the resistance
|
||||
// Turn PA0 into an output and drive high to inject (3.3V-0.6)/(6K8+Rtip)
|
||||
@@ -347,27 +335,19 @@ uint32_t calculateTipR(uint8_t useFilter) {
|
||||
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_0, GPIO_PIN_RESET); // Set low first
|
||||
setTipPWM(0);
|
||||
vTaskDelay(1);
|
||||
uint32_t offReading = getTipInstantTemperature();
|
||||
uint32_t offReading = getTipRawTemp(1);
|
||||
for (uint8_t i = 0; i < 24; i++) {
|
||||
if (useFilter == 0) {
|
||||
vTaskDelay(1); // delay to allow it too stabilize
|
||||
offReading += getTipInstantTemperature();
|
||||
} else {
|
||||
offReading += getTipRawTemp(0);
|
||||
}
|
||||
vTaskDelay(1); // delay to allow it to stabilize
|
||||
offReading += getTipRawTemp(1);
|
||||
}
|
||||
|
||||
// Turn on
|
||||
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_0, GPIO_PIN_SET); // Set low first
|
||||
vTaskDelay(1); // delay to allow it too stabilize
|
||||
vTaskDelay(1); // delay to allow it to stabilize
|
||||
uint32_t onReading = getTipInstantTemperature();
|
||||
for (uint8_t i = 0; i < 24; i++) {
|
||||
if (useFilter == 0) {
|
||||
vTaskDelay(1); // delay to allow it too stabilize
|
||||
onReading += getTipInstantTemperature();
|
||||
} else {
|
||||
onReading += getTipRawTemp(0);
|
||||
}
|
||||
vTaskDelay(1); // delay to allow it to stabilize
|
||||
onReading += getTipRawTemp(1);
|
||||
}
|
||||
|
||||
uint32_t difference = onReading - offReading;
|
||||
@@ -392,11 +372,11 @@ static unsigned int sqrt32(unsigned long n) {
|
||||
g |= c;
|
||||
}
|
||||
}
|
||||
int16_t calculateMaxVoltage(uint8_t useFilter, uint8_t useHP) {
|
||||
int16_t calculateMaxVoltage(uint8_t useHP) {
|
||||
// This measures the tip resistance, then it calculates the appropriate
|
||||
// voltage To stay under ~18W. Mosfet is "9A", so no issues there
|
||||
// QC3.0 supports up to 18W, which is 2A @9V and 1.5A @12V
|
||||
uint32_t milliOhms = calculateTipR(useFilter);
|
||||
uint32_t milliOhms = calculateTipR();
|
||||
// Check no tip
|
||||
if (milliOhms > 10000)
|
||||
return -1;
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
#include <gui.hpp>
|
||||
#include <main.hpp>
|
||||
#include "LIS2DH12.hpp"
|
||||
#include <history.hpp>
|
||||
#include <power.hpp>
|
||||
#include "Settings.h"
|
||||
#include "Translation.h"
|
||||
#include "cmsis_os.h"
|
||||
@@ -36,7 +38,7 @@ int main(void) {
|
||||
HAL_Init();
|
||||
Setup_HAL(); // Setup all the HAL objects
|
||||
HAL_IWDG_Refresh(&hiwdg);
|
||||
setTipPWM(0); // force tip off
|
||||
setTipMilliWatts(0); // force tip off
|
||||
FRToSI2C::init(&hi2c1);
|
||||
OLED::initialize(); // start up the LCD
|
||||
OLED::setFont(0); // default to bigger font
|
||||
@@ -87,6 +89,21 @@ int main(void) {
|
||||
while (1) {
|
||||
}
|
||||
}
|
||||
|
||||
void debugNumber(int32_t val) {
|
||||
if (abs(val) > 99999) {
|
||||
OLED::print(" OoB"); // out of bounds
|
||||
return;
|
||||
}
|
||||
if (val >= 0) {
|
||||
OLED::drawChar(' ');
|
||||
OLED::printNumber(val, 5);
|
||||
} else {
|
||||
OLED::drawChar('-');
|
||||
OLED::printNumber(-val, 5);
|
||||
}
|
||||
}
|
||||
|
||||
void printVoltage() {
|
||||
OLED::printNumber(getInputVoltageX10(systemSettings.voltageDiv) / 10, 2);
|
||||
OLED::drawChar('.');
|
||||
@@ -535,8 +552,10 @@ static void gui_solderingMode(uint8_t jumpToSleep) {
|
||||
if (systemSettings.detailedSoldering) {
|
||||
OLED::setFont(1);
|
||||
OLED::print(SolderingAdvancedPowerPrompt); // Power:
|
||||
OLED::printNumber(getTipPWM(), 3);
|
||||
OLED::print("%");
|
||||
OLED::printNumber(milliWattHistory[0] / 1000, 2);
|
||||
OLED::drawChar('.');
|
||||
OLED::printNumber(milliWattHistory[0] / 100 % 10, 1);
|
||||
OLED::drawChar('W');
|
||||
|
||||
if (systemSettings.sensitivity && systemSettings.SleepTime) {
|
||||
OLED::print(" ");
|
||||
@@ -723,7 +742,7 @@ void startGUITask(void const *argument __unused) {
|
||||
uint8_t tempWarningState = 0;
|
||||
bool buttonLockout = false;
|
||||
bool tempOnDisplay = false;
|
||||
getTipRawTemp(2); // reset filter
|
||||
getTipRawTemp(1); // reset filter
|
||||
OLED::setRotation(systemSettings.OrientationMode & 1);
|
||||
uint32_t ticks = xTaskGetTickCount();
|
||||
ticks += 400; // 4 seconds from now
|
||||
@@ -786,8 +805,7 @@ void startGUITask(void const *argument __unused) {
|
||||
#ifdef MODEL_TS80
|
||||
//Here we re-check for tip presence
|
||||
if (idealQCVoltage < 90)
|
||||
idealQCVoltage = calculateMaxVoltage(1,
|
||||
systemSettings.cutoutSetting); // 1 means use filtered values rather than do its own
|
||||
idealQCVoltage = calculateMaxVoltage(systemSettings.cutoutSetting);
|
||||
seekQC(idealQCVoltage,systemSettings.voltageDiv);
|
||||
#endif
|
||||
gui_solderingMode(0); // enter soldering mode
|
||||
@@ -807,7 +825,7 @@ void startGUITask(void const *argument __unused) {
|
||||
|
||||
currentlyActiveTemperatureTarget = 0; // ensure tip is off
|
||||
|
||||
uint16_t tipTemp = tipMeasurementToC(getTipRawTemp(1)); // This forces a faster update rate on the filtering
|
||||
uint16_t tipTemp = tipMeasurementToC(getTipRawTemp(0));
|
||||
|
||||
if (tipTemp < 50) {
|
||||
if (systemSettings.sensitivity) {
|
||||
@@ -906,37 +924,15 @@ void startPIDTask(void const *argument __unused) {
|
||||
* struct
|
||||
*
|
||||
*/
|
||||
setTipPWM(0); // disable the output driver if the output is set to be off
|
||||
#ifdef MODEL_TS100
|
||||
for (uint8_t i = 0; i < 50; i++) {
|
||||
osDelay(10);
|
||||
getTipRawTemp(1); // cycle up the tip temp filter
|
||||
HAL_IWDG_Refresh(&hiwdg);
|
||||
}
|
||||
#else
|
||||
// On the TS80 we can measure the tip resistance before cycling the filter a
|
||||
// bit
|
||||
idealQCVoltage = 0;
|
||||
idealQCVoltage = calculateMaxVoltage(0, systemSettings.cutoutSetting);
|
||||
// Rapidly cycle the filter to help converge
|
||||
HAL_IWDG_Refresh(&hiwdg);
|
||||
for (uint8_t i = 0; i < 50; i++) {
|
||||
osDelay(11);
|
||||
getTipRawTemp(1); // cycle up the tip temp filter
|
||||
}
|
||||
HAL_IWDG_Refresh(&hiwdg);
|
||||
|
||||
setTipMilliWatts(0); // disable the output driver if the output is set to be off
|
||||
#ifdef MODEL_TS80
|
||||
idealQCVoltage = calculateMaxVoltage(systemSettings.cutoutSetting);
|
||||
#endif
|
||||
int32_t rawC = ctoTipMeasurement(100) - ctoTipMeasurement(101); // 1*C change in raw.
|
||||
currentlyActiveTemperatureTarget = 0; // Force start with no output (off). If in sleep / soldering this will
|
||||
// be over-ridded rapidly
|
||||
int32_t integralCount = 0;
|
||||
int32_t derivativeLastValue = 0;
|
||||
// be over-ridden rapidly
|
||||
history<int16_t> tempError = {{0}, 0, 0};
|
||||
|
||||
// REMEBER ^^^^ These constants are backwards
|
||||
// They act as dividers, so to 'increase' a P term, you make the number
|
||||
// smaller.
|
||||
|
||||
const int32_t itermMax = 100;
|
||||
pidTaskNotification = xTaskGetCurrentTaskHandle();
|
||||
for (;;) {
|
||||
if (ulTaskNotifyTake(pdTRUE, 50)) {
|
||||
@@ -944,69 +940,54 @@ void startPIDTask(void const *argument __unused) {
|
||||
// This is a call to block this thread until the ADC does its samples
|
||||
uint16_t rawTemp = getTipRawTemp(1); // get instantaneous reading
|
||||
if (currentlyActiveTemperatureTarget) {
|
||||
// Compute the PID loop in here
|
||||
// Because our values here are quite large for all measurements (0-32k
|
||||
// ~= 66 counts per C) P I & D are divisors, so inverse logic applies
|
||||
// (beware)
|
||||
|
||||
// Cap the max set point to 450C
|
||||
if (currentlyActiveTemperatureTarget > ctoTipMeasurement(450)) {
|
||||
currentlyActiveTemperatureTarget = ctoTipMeasurement(450);
|
||||
}
|
||||
|
||||
int32_t rawTempError = currentlyActiveTemperatureTarget
|
||||
- rawTemp;
|
||||
// As we get close to our target, temp noise causes the system
|
||||
// to be unstable. Use a rolling average to dampen it.
|
||||
// We overshoot by roughly 1/2 of 1 degree Fahrenheit.
|
||||
// This helps stabilize the display.
|
||||
tempError.update(currentlyActiveTemperatureTarget - rawTemp + rawC/4);
|
||||
|
||||
int32_t ierror = (rawTempError
|
||||
/ ((int32_t) systemSettings.PID_I));
|
||||
// Now for the PID!
|
||||
int32_t milliWattsOut = 0;
|
||||
|
||||
integralCount += ierror;
|
||||
// P term - total power needed to hit target temp next cycle.
|
||||
// thermal mass = 1690 milliJ/*C for my tip.
|
||||
// = Watts*Seconds to raise Temp from room temp to +100*C, divided by 100*C.
|
||||
// divided by 4 to let I term dominate near set point.
|
||||
const uint16_t mass = 1690 / 4;
|
||||
int32_t milliWattsNeeded = tempToMilliWatts(tempError.average(), mass, rawC);
|
||||
milliWattsOut += milliWattsNeeded;
|
||||
|
||||
if (integralCount > (itermMax / 2))
|
||||
integralCount = itermMax / 2; // prevent too much lead
|
||||
else if (integralCount < -itermMax)
|
||||
integralCount = itermMax;
|
||||
// I term - energy needed to compensate for heat loss.
|
||||
// We track energy put into the system over some window.
|
||||
// Assuming the temp is stable, energy in = energy transfered.
|
||||
// (If it isn't, P will dominate).
|
||||
milliWattsOut += milliWattHistory.average();
|
||||
|
||||
int32_t dInput = (rawTemp - derivativeLastValue);
|
||||
|
||||
/*Compute PID Output*/
|
||||
int32_t output = (rawTempError
|
||||
/ ((int32_t) systemSettings.PID_P));
|
||||
if (((int32_t) systemSettings.PID_I))
|
||||
output += integralCount;
|
||||
if (((int32_t) systemSettings.PID_D))
|
||||
output -= (dInput / ((int32_t) systemSettings.PID_D));
|
||||
|
||||
if (output > 100) {
|
||||
output = 100; // saturate
|
||||
} else if (output < 0) {
|
||||
output = 0;
|
||||
}
|
||||
|
||||
if (currentlyActiveTemperatureTarget < rawTemp) {
|
||||
output = 0;
|
||||
integralCount = 0;
|
||||
derivativeLastValue = 0;
|
||||
}
|
||||
setTipPWM(output);
|
||||
derivativeLastValue = rawTemp; // store for next loop
|
||||
// D term - use sudden temp change to counter fast cooling/heating.
|
||||
// In practice, this provides an early boost if temp is dropping
|
||||
// and counters extra power if the iron is no longer losing temp.
|
||||
// basically: temp - lastTemp
|
||||
// Unfortunately, our temp signal is too noisy to really help.
|
||||
|
||||
setTipMilliWatts(milliWattsOut);
|
||||
} else {
|
||||
setTipPWM(0); // disable the output driver if the output is set to be off
|
||||
integralCount = 0;
|
||||
derivativeLastValue = 0;
|
||||
setTipMilliWatts(0);
|
||||
}
|
||||
|
||||
HAL_IWDG_Refresh(&hiwdg);
|
||||
} else {
|
||||
if (currentlyActiveTemperatureTarget == 0) {
|
||||
setTipPWM(0); // disable the output driver if the output is set to be off
|
||||
integralCount = 0;
|
||||
derivativeLastValue = 0;
|
||||
setTipMilliWatts(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#define MOVFilter 8
|
||||
void startMOVTask(void const *argument __unused) {
|
||||
OLED::setRotation(false);
|
||||
|
||||
48
workspace/TS100/src/power.cpp
Normal file
48
workspace/TS100/src/power.cpp
Normal file
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* power.cpp
|
||||
*
|
||||
* Created on: 28 Oct, 2018
|
||||
* Authors: Ben V. Brown, David Hilton
|
||||
*/
|
||||
|
||||
#include <power.hpp>
|
||||
#include <Settings.h>
|
||||
#include <hardware.h>
|
||||
|
||||
history<uint16_t, 75> milliWattHistory = {{0}, 0, 0};
|
||||
|
||||
const uint8_t tipResistance = 87;
|
||||
const uint8_t hz = 33;
|
||||
const uint8_t maxPWM = 100;
|
||||
|
||||
int32_t tempToMilliWatts(int32_t rawTemp, uint16_t mass, uint8_t rawC) {
|
||||
// mass is in milliJ/*C, rawC is raw per degree C
|
||||
int32_t milliJoules = mass * rawTemp / rawC;
|
||||
return milliJoules * hz;
|
||||
}
|
||||
|
||||
void setTipMilliWatts(int32_t mw) {
|
||||
int32_t output = milliWattsToPWM(mw, systemSettings.voltageDiv / 10);
|
||||
setTipPWM(output);
|
||||
uint16_t actualMilliWatts = PWMToMilliWatts(output, systemSettings.voltageDiv / 10);
|
||||
|
||||
milliWattHistory.update(actualMilliWatts);
|
||||
}
|
||||
|
||||
uint8_t milliWattsToPWM(int32_t milliWatts, uint8_t divisor) {
|
||||
int32_t v = getInputVoltageX10(divisor); // 1000 = 10v
|
||||
int32_t availableMilliWatts = v*v / tipResistance;
|
||||
int32_t pwm = maxPWM * milliWatts / availableMilliWatts;
|
||||
|
||||
if (pwm > maxPWM) {
|
||||
pwm = maxPWM;
|
||||
} else if (pwm < 0) {
|
||||
pwm = 0;
|
||||
}
|
||||
return pwm;
|
||||
}
|
||||
|
||||
int32_t PWMToMilliWatts(uint8_t pwm, uint8_t divisor) {
|
||||
int32_t v = getInputVoltageX10(divisor);
|
||||
return pwm * (v*v / tipResistance) / maxPWM;
|
||||
}
|
||||
Reference in New Issue
Block a user