New circuit board arrived, firmware written, all wiring complete, and it’s working! I posted some info in an instructable here: https://www.instructables.com/id/Revive-a-Vacuum-Former/
The rest of this post is for future-me if I need to troubleshoot my work and can’t find it on my hard drive:
//Replacement controller for Clarke Vacuum Former 1820
//Elliotmade 4/27/2020
//Manual for the machine: https://www.abbeon.com/ItemFiles/Manual/1820.pdf
/*
* Components attached to the arduino:
* 4 heater relays (12v, using an IRFZ44 n-channel mosfet to switch these
* 1 buzzer relay
* 4 heater potentiometers
* 2 7-segment displays each using a TM1637 driver
* 1 Rotary encoder with button
* 2 existing switches from the machine for the heater location
*
* Operation should be similar to this:
* Power on the machine, switch heaters on and off slowly according to power level set on the knobs
* Timer set value is stored in eeprom, displayed on the first 7-segment display
* Changing the timer set value is done with the rotary encoder
* Extending the heater triggers a switch and the timer should count down on the second display
* When the timer reaches zero the buzzer should be turned on
* Retracting the heater triggers a switch and the timer should be reset and buzzer turned off
*
* Pay attention to the heater position switches and which order they get pushed in
* States for the timer:
* Not running, buzzer not triggered (normal idle status, assume we started from here)
* Running, buzzer not triggered (heater extended)
* Running, buzzer triggered (heater extended, but time has run out)
* Not running, buzzer not triggered (heater retracted, timer is reset and buzzer is canceled)
*/
//////////////////////////////////////////////libraries////////////////////////////////////////////////////////////
#include <RotaryEncoder.h>
//http://www.mathertel.de/Arduino/RotaryEncoderLibrary.aspx
//https://github.com/mathertel/RotaryEncoder
#include "OneButton.h"
//https://github.com/mathertel/OneButton
#include <SimpleTimer.h>
//https://github.com/jfturcot/SimpleTimer
#include <TM1637Display.h>
//https://github.com/avishorp/TM1637
#include <EEPROMex.h>
//https://github.com/thijse/Arduino-EEPROMEx
//////////////////////////////////////////Pins////////////////////////////////////////////////////////////////
const int encoderButton = A1;
const int encoderA = A2;
const int encoderB = A3;
const int timerStart = 7; //D7
const int timerReset = 6; //D6
const int buzzer = 8; //D8
const int dispClock1 = 2; //D2
const int dispClock2 = 4; //D4
const int dispData1 = 3; //D3
const int dispData2 = 5; //D5
const int heatRelay[4] = {12,11,10,9}; //D9-D12
const int heatKnob[4] = {A4,A5,A6,A7};
///////////////////////////////////////////Constants///////////////////////////////////////////////////////////////
//This will impact the duration of the on/off cycles for the heaters. Milliseconds. Heaters should stay on for the duty cycle percent, then off for 100-duty cycle
const unsigned long heatIntervalMult = 1000;
//read heater knobs and update heaters every (milliseconds)
const int updateHeatInterval = 500;
//minimum duty cycle for heaters (old controls were approx. 55% according to the manual "power reduction is 45% on setting 1"
const int minDutyPct = 55;
//thresholds for knob to identify zero and max heat (0-1023)
const int minThreshold = 30;
const int maxThreshold = 1000;
const byte heaterCount = 4;
//eeprom memory locations
const int minAddress = 10;
const int secAddress = 20;
const int memBase = 350;
////////////////////////////////////////////Variables//////////////////////////////////////////////////////////////
bool heatStatus[4] = {false,false,false,false}; //current on/off status of a heater
int heatSetting[4] = {0,0,0,0}; //current heat setting 0-100 percent, updated when the knobs are read
unsigned long heatLastChange[4] = {0,0,0,0}; //last time in milliseconds that the heater was turned on
bool timerRunning = false; //state of the timer
bool buzzerOn = false; //state of the buzzer
bool minutesMode = false; //change behavior of encoder based on this
int minutes; //timer set time
int seconds; //timer set time
int elapsedMinutes = 0; //timer current countdown time
int elapsedSeconds = 0; //timer current countdown time
bool rollover = false; //used to help display the countdown
//Initialize some things
SimpleTimer countdownTimer;
RotaryEncoder encoder(encoderA, encoderB);
OneButton startSwitch(timerStart, true);
OneButton resetSwitch(timerReset, true);
OneButton encoderButt(encoderButton, true);
TM1637Display display1(dispClock1, dispData1);
TM1637Display display2(dispClock2, dispData2);
void setup() { ////////////////////////////////////////Setup///////////////////////////////////////////////////
//Serial for debugging
Serial.begin(9600);
Serial.println("Setup Start");
//interrupts for encoder
PCICR |= (1 << PCIE1); // This enables Pin Change Interrupt 1 that covers the Analog input pins or Port C.
PCMSK1 |= (1 << PCINT10) | (1 << PCINT11); // This enables the interrupt for pin 2 and 3 of Port C.
// Set Up EEPROM
EEPROM.setMemPool(memBase, EEPROMSizeNano);
//pins
pinMode(buzzer, OUTPUT);
for (byte i = 0; i <= heaterCount - 1; i++) {
pinMode(heatRelay[i], OUTPUT);
pinMode(heatKnob[i], INPUT);
}
//button functions
startSwitch.attachClick(startTime);
resetSwitch.attachClick(resetTime);
encoderButt.attachClick(encoderClick);
encoderButt.attachLongPressStart(saveTime);
//Load the stored timer value
minutes = EEPROM.readInt(minAddress);
seconds = EEPROM.readInt(secAddress);
//displays
display1.setBrightness(2);
display2.setBrightness(2);
display1.showNumberDec(8008);
display2.showNumberDecEx(8008,0x40,true);
delay(50);
updateDisplay1();
countdownTimer.setInterval(1000,incrimentTimer); //make timer count every second
countdownTimer.setInterval(updateHeatInterval,readKnobs);
countdownTimer.setInterval(250,updateHeatStatus);
countdownTimer.setInterval(250,updateHeatRelays);
Serial.println("Setup Complete");
} //////////////////////////////////////////////End Setup/////////////////////////////////////////////////////
void loop() { ////////////////////////////////////Loop/////////////////////////////////////////////////////
//monitor buttons
startSwitch.tick();
resetSwitch.tick();
encoderButt.tick();
readEncoder();
countdownTimer.run();
checkTimer();
updateDisplay1();
updateDisplay2();
updateBuzzer();
} //////////////////////////////////////////////End Loop/////////////////////////////////////////////////////
void encoderClick() { //When the encoder button is clicked, change from minutes to seconds for timer adjustment
minutesMode = !minutesMode; //toggle time setting modes
Serial.print("Mode: ");
Serial.println(minutesMode);
}
void readEncoder() {
static int pos = 0;
int newPos = encoder.getPosition();
if (!timerRunning) { //only let the set time be changed when the timer is not counting down
if (pos != newPos) {
if (minutesMode) {
minutes = minutes + (newPos - pos);
}
else {
seconds = seconds + (newPos - pos);
if (seconds == 60) {
seconds = 0;
minutes++;
}
if (seconds < 0) {
seconds = 59;
minutes--;
}
}
pos = newPos;
minutes = constrain(minutes,0,59);
seconds = constrain(seconds,0,59);
Serial.print("Minutes: ");
Serial.print(minutes);
Serial.print(" Seconds: ");
Serial.println(seconds);
}
}
else {
encoder.setPosition(pos); //reset the encoder, as if it didn't move while the timer was counting
}
}
void startTime() { //When the heater is extended, start counting down
if (!timerRunning) {
timerRunning = true;
elapsedSeconds = 0;
elapsedMinutes = 0;
Serial.println("Timer Started");
}
}
void resetTime() { //When the heater is retracted, stop counting down, rest the timer, and shut off the buzzer
if (timerRunning) {
timerRunning = false;
buzzerOn = false;
elapsedMinutes = 0;
elapsedSeconds = 0;
}
updateBuzzer();
}
void saveTime() { //When the encoder knob is held down, save the set time to EEPROM
EEPROM.writeInt(minAddress, minutes);
EEPROM.writeInt(secAddress, seconds);
Serial.println("Timer setting saved to EEPROM");
}
void incrimentTimer() { //update the elapsed time variables after each second passes
if (timerRunning) {
elapsedSeconds++;
if (elapsedSeconds == seconds && rollover == false) { //used to help display differently if the original number of seconds have passed
rollover = true;
elapsedSeconds = 60;
elapsedMinutes++;
}
if (elapsedSeconds == 60) {
elapsedSeconds = 0;
elapsedMinutes++;
}
}
}
void checkTimer() { //If the timer is running, sound the buzzer once it has reached zero
if (elapsedMinutes >= minutes && (elapsedSeconds % 60) >= seconds && !buzzerOn) {
Serial.println("Timer has reached zero");
buzzerOn = true; //turn on the buzzer
elapsedSeconds = 0; //reset the elapsed time so it can be displayed counting up easily
elapsedMinutes = 0;
}
}
void updateBuzzer() { //Turn buzzer on/off based on the variable
if (buzzerOn) {
digitalWrite(buzzer, LOW);
//Serial.println("Buzzer On");
}
else {
digitalWrite(buzzer, HIGH);
//Serial.println("Buzzer Off");
}
}
void updateDisplay1() {
display1.showNumberDecEx(minutes * 100 + seconds, 0x40, true); //Set time should always show on display 1
}
void updateDisplay2() {
if (timerRunning) {
if (buzzerOn) { //count up if the buzzer is on (timer has reached zero)
display2.showNumberDecEx((elapsedMinutes) * 100 + (elapsedSeconds), 0x40, true); //count up. These were reset to 0 when the timer ran out
}
else if (rollover == true) { //count down from 60 seconds
display2.showNumberDecEx((minutes - elapsedMinutes) * 100 + (60 - elapsedSeconds), 0x40, true); //count down
}
else { //count down from the original number of seconds
display2.showNumberDecEx((minutes - elapsedMinutes) * 100 + (seconds - elapsedSeconds), 0x40, true); //count down
}
}
else {
display2.showNumberDecEx(minutes * 100 + seconds, 0x40, true); //show the same thing on 2 as 1
}
}
void readKnobs() { //read knob position and update the heat setting array
for (byte j = 0; j <= heaterCount - 1; j++) {
if (analogRead(heatKnob[j]) < minThreshold) {
heatSetting[j] = 0;
}
else if (analogRead(heatKnob[j]) > maxThreshold) {
heatSetting[j] = 100;
}
else {
heatSetting[j] = map(analogRead(heatKnob[j]),0,1023,minDutyPct * 10,maxThreshold)/10;
}
}
//Serial.println(" ");
}
void updateHeatStatus() { //update the on/off array for each heater based on the knob position
//calculate the time to the next change
unsigned long curMillis = millis();
for (byte m = 0; m <= heaterCount - 1; m++) {
if (heatSetting == 0 && heatStatus[m] == true) { //fully off if the knob is at the minimum position
heatStatus[m] = false;
heatLastChange[m] = curMillis;
}
else if (heatSetting == 100 && heatStatus[m] == false) { //fully on if the knob is at the max position
heatStatus[m] = true;
heatLastChange[m] = curMillis;
}
else if (heatStatus[m] == true) {
if (heatLastChange[m] + heatSetting[m] * heatIntervalMult < curMillis) { //if the heater is currently on, wait until last change time + (duty cycle * multiplier) seconds have gone by then turn it off
heatStatus[m] = false;
heatLastChange[m] = curMillis;
}
}
else { //if the heater is currently off, wait until last change time + (100 - duty cycle * multiplier) seconds have gone by then turn it on
if (heatLastChange[m] + (100 - heatSetting[m]) * heatIntervalMult < curMillis) { //if the heater is currently off, wait until last change time + (100 - duty cycle) * multiplier seconds have gone by then turn it off
heatStatus[m] = true;
heatLastChange[m] = curMillis;
}
}
}
}
void updateHeatRelays() { //Turn heaters relays on or off
for (byte k = 0; k <= heaterCount - 1; k++) {
if(heatStatus[k] == true) {
digitalWrite(heatRelay[k], HIGH);
}
else {
digitalWrite(heatRelay[k], LOW);
}
}
}
ISR(PCINT1_vect) { // The Interrupt Service Routine for Pin Change Interrupt 1
encoder.tick(); // just call tick() to check the state.
}