Just a little embellishment.
Actually / sadly its more like knew it, forgot it, knew it, forgot it.
That's what happens when we forget to take our medication.
Tom....
![]()
Medication ? ![]()
Thank you all so much, really appreciate the support!!
My program hasn’t been finished yet and I need to revise it following your advice here. I’ll look up how I can post code correctly and show where I’m at later. Lots to think about
Cheers, Rene
#include <Servo.h>
#include <FastPwmPin.h>
#include <LibPrintf.h>
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <OneWire.h>
#include <DallasTemperature.h>
// Initialize the LCD (address 0x27, 20 columns, 4 rows)
LiquidCrystal_I2C lcd(0x27, 20, 4);
// Pin connected to the DS18B20 data line
#define ONE_WIRE_BUS 7 // Input pin from the DS18B20 temp sensors; recommended to connect with a 4.7kΩ pull-up resistor between DQ and VDD.
OneWire oneWire(ONE_WIRE_BUS);
DallasTemperature sensors(&oneWire);
const int voltPin = A0; //Used for voltage reading from voltage-devider
const int ampPin = A1; //Used for current reading and alarm/protection setting, signal from WCS1700 module
const int irSensorPin = A2; //Used for part detection
const int glowPin = A3; //Used for detection of light emission when part is at temperature
const int annealRelayPin = 9; //ZVS operation servo, connected to SSR
const int servoPin = 11; //Trapdoor operation servo
// Entries related to averaging
const int numReadings = 10;
float readings[numReadings];
int readIndex = 0;
float total = 0;
float average = 0;
unsigned long previousMillis = 0;
const long interval = 100;
Servo myServo;
enum State { Waiting, Annealing, Soaking, Trapdoor };
State currentState = Waiting;
unsigned long annealStartTime = 0;
unsigned long soakStartTime = 0;
unsigned long trapdoorStartTime = 0;
const unsigned long annealTime = 1000; // 8 seconds ; now 1 sec for testing
const unsigned long soakDuration = 1100; // 1.1 seconds
const unsigned long trapdoorDuration = 500; // 0.5 seconds
const int dutyCycle = 64; // 25% of 255
void setup() {
Serial.begin(115200);
sensors.begin();
myServo.attach(servoPin);
myServo.write(90); // Trapdoor closed in correct position
digitalRead(irSensorPin) == HIGH;
pinMode(LED_BUILTIN, OUTPUT);
lcd.init(); // Initialize the LCD
lcd.backlight(); // Turn on the backlight
lcd.setCursor(0, 0); // Set cursor to the first row, first column
lcd.print("Dashboard"); // Display static text
//Start up fan (need pwm tied in with temp), cooling water pump, radiator fan (needs pwm to be tied in with temp) - All separate functions called from Setup?
Serial.print("Setup completed.\n");
delay(2000); //Delay entered to have the system settled, will be removed later once proven. Need to understand how long it takes for the machine to get into steady state
}
void loop() {
unsigned long currentMillis = millis();
switch (currentState) {
case Waiting:
Serial.print("Current State: Waiting\n");
//Waiting for a part to be entered (detected) for the machine to start
if (digitalRead(irSensorPin) == LOW) {
previousMillis = currentMillis;
currentState = Annealing;
}
break;
case Annealing:
Serial.print("Current State: Annealing\n");
//After part detected to have been placed in the coil switch on machine for pre-determined time or until glow value reached
Serial.print("Relay: High\n");
while (millis() - previousMillis <= annealTime || analogRead(glowPin) < 50) {
digitalWrite(annealRelayPin, HIGH);
digitalWrite(LED_BUILTIN, HIGH);
}
digitalWrite(annealRelayPin, LOW);
Serial.print("Relay: Low\n");
digitalWrite(LED_BUILTIN, LOW);
previousMillis = millis();
currentState = Soaking;
break;
case Soaking:
Serial.print("Current State: Soaking\n");
//Set up to allow the part to remain at the temperature reached during annealing
soakStartTime = millis();
while (millis() - soakStartTime <= soakDuration) {
FastPwmPin::enablePwmPin(3, 1000L, 25); // Pin 3, 1 KHz, 25% duty cycle //switching on PWM for relay pin
digitalWrite(LED_BUILTIN, HIGH);
}
FastPwmPin::disablePwmPin(3); //switching off PWM for relay pin
digitalWrite(LED_BUILTIN, LOW);
currentState = Trapdoor;
break;
case Trapdoor:
Serial.print("Current State: Trapdoor\n");
//Leave trapdoor open to clear the part long enough
trapdoorStartTime = millis();
while (millis() - trapdoorStartTime <= trapdoorDuration);{
myServo.write(135);
delay(1000); //Delay to be replaced by millis once proven.
}
myServo.write(90);
currentState = Waiting;
break;
default:
break;
}
}
void Current() {
for (int i = 0; i < numReadings; i++) {
readings[i] = 0;
}
// Needs max amp alarm/shutdown
unsigned long currentMillis = millis();
if (currentMillis - previousMillis >= interval) {
previousMillis = currentMillis;
total -= readings[readIndex];
readings[readIndex] = analogRead(ampPin) * (5.0 / 1023.0);
total += readings[readIndex];
readIndex = (readIndex + 1) % numReadings;
float averageAmp = total / numReadings;
Serial.println(averageAmp);
}
}
void Voltage() {
float readVoltage();
const int numReadings = 10;
float readings[numReadings];
float total = 0;
int index = 0;
float averageVolt = 0;
for (int i = 0; i < numReadings; i++) {
readings[i] = analogRead(A2) * (5.0 / 1023.0); // Convert to voltage
total += readings[i];
delay(100); // Delay for stability //Delay to be removed later once proven.
}
averageVolt = total / numReadings;
return averageVolt;
}
void Glow() {
int glowValue = analogRead(glowPin); // Read potentiometer value
float glow = glowValue * (5.0 / 1023.0); //
// Needs min glow alarm/shutdown
lcd.setCursor(0, 1); // Set cursor to the second row
lcd.print("Glow: ");
lcd.print(glow, 0); // Display voltage without decimal places
lcd.print(" G "); // Add unit and clear extra characters
delay(500); // Update every 500ms //Delay to be removed later once proven.
}
void Temperature() {
sensors.requestTemperatures(); // Request temperature readings
float tempC = sensors.getTempCByIndex(0); // Get temperature in Celsius
// Needs max temp alarm/shutdown
Serial.print("Temp: ");
Serial.print(tempC);
Serial.print(" °C");
delay(1000); //Delay to be removed later once proven.
}
void Display() {
// Set initial 'template' for dashboard with text and sensor values updating once every 0.1 second
//lcd.setCursor(0, 0); // top left
//lcd.setCursor(19, 0); // top right
//lcd.setCursor(0, 3); // bottom left
//lcd.setCursor(19, 3); // bottom right
lcd.setCursor(0,3);
Serial.print("Temp: ");
Serial.print(" °C");
}
Please note I have still a lot to include to finalise the code and set-up. Hope it is clear what I’m trying to do and appreciate your comments. BTW Took me a long time to find out how to post code - searching for “how to post code” doesn’t get you there ![]()
Thanks, Rene
Prepare a separate sketch for each Input (read sensor, I2C, etc) task to start. Those should be pretty small. When you get those done, they can be added together as the final sketch. The small ones will debug quicker.
I and others here can convert delay code to non-blocking at least for some parts but for you to learn, you got to do it too once you’ve seen examples to go by.
Hello Rene
If you need help designing tasks, here is a link that shows you the first steps.
This allows you to program the tasks as functions that can be tested individually and then linked to the overall project using control structures with a state machine.
here's a table driven approach to implementing a state machine. Overkill for the often trivial state machines discussed on this forum, but a sanity saver for ones more complicated.
for complex state machines, the challenge is verifying the transitions and actions, and is easier just looking at the data, not logic
there are separate tables for the transitions to the next state and the actions for that transition
sm.cpp
#include <Arduino.h>
#include "sm.h"
extern int turnOff (void *arg);
extern int turnOn (void *arg);
// -----------------------------------------------------------------------------
typedef int (*Action_t)(void*) ;
// ---------------------------------------------------------
int __ (void *arg)
{
return OK;
}
// ---------------------------------------------------------
State_t smTransitionTbl [S_Last] [St_Last] = {
// St_Null St_Sensor1 St_ThrshHi St_ThrsLo St_Tmr
{ S_Idle, S_On, S_Idle, S_Idle, S_Idle, }, // S_Idle
{ S_Off, S_Off, S_On, S_Off, S_Idle, }, // S_Off
{ S_On, S_On, S_On, S_Off, S_Idle, }, // S_On
};
Action_t smActionTbl [S_Last] [St_Last] = {
// St_Null St_Sensor1 St_ThrshHi St_ThrsLo St_Tmr
{ __, __, turnOff, __, turnOff }, // S_Idle
{ __, __, turnOn, __, turnOff }, // S_Off
{ __, __, __, turnOff, turnOff }, // S_On
};
// ---------------------------------------------------------
static State_t _smState = S_Idle;
int
smEngine (Stim_t stim)
{
if (St_Last <= stim)
return ERROR;
State_t last = _smState;
Action_t func = smActionTbl [_smState] [stim];
_smState = smTransitionTbl [_smState] [stim];
char s [90];
sprintf (s, " stim %d, state %d -> %d, %s",
stim, last, _smState, __ == func ? "" : "exec func");
Serial.println (s);
return (*func) (NULL);
}
sm.h
#ifndef _SM_H_
# define _SM_H_
enum Stim_t { St_Nul, St_Sensor1, St_ThrshHi, St_ThrshLo, St_Tmr, St_Last };
enum State_t { S_Idle, S_Off, S_On, S_Last };
enum { ERROR = -1, OK };
int smEngine (Stim_t stim);
#endif
#include "Arduino.h"
#include "sm.h"
const int PinSensor1 = A1;
const int PinSensor2 = A0;
const int PinOutput = 10;
int butState;
const int ThreshHigh = 100;
const int ThreshLow = 90;
bool sensor1;
int sensor2;
// -----------------------------------------------------------------------------
unsigned long msecPeriod;
unsigned long msec0;
unsigned long msec;
bool tmr;
// -------------------------------------
void timerSet (
int _msecPeriod )
{
msecPeriod = _msecPeriod;
msec0 = msec;
tmr = true;
}
// ---------------------------------------------------------
int turnOff (void *arg)
{
Serial.println (__func__);
digitalWrite (PinOutput, LOW);
return OK;
}
// -------------------------------------
int turnOn (void *arg)
{
Serial.println (__func__);
digitalWrite (PinOutput, HIGH);
timerSet (4000);
return OK;
}
// -----------------------------------------------------------------------------
void loop ()
{
msec = millis ();
if (tmr && msec - msec0 >= msecPeriod) {
tmr = false;
smEngine (St_Tmr);
}
sensor2 = analogRead (PinSensor2);
if (ThreshLow > sensor2)
smEngine (St_ThrshLo);
else if (ThreshHigh < sensor2)
smEngine (St_ThrshHi);
char but = digitalRead (PinSensor1);
if (butState != but) {
butState = but;
delay (30);
if (LOW == but)
smEngine (St_Sensor1);
}
#if (1)
Serial.println (sensor2);
delay (500);
#endif
}
// -----------------------------------------------------------------------------
void setup ()
{
Serial.begin (9600);
pinMode (PinOutput, OUTPUT);
pinMode (PinSensor1, INPUT_PULLUP);
butState = digitalRead (PinSensor1);
}
Thanks but tasks are not the best solution. If the user already implements a state machine for one task, she can implement another state machine for the sensors, and both machines are called in loop().
EDIT see post 35 for correction.
Just FYI, at IBM we had software that took a table as input and created the state machine.
as you can see, there's not much to do once you have the tables correct
would be more intiutive to take a graphviz file as the input to create the tables
digraph G {
graph [
label="Finite State Machine"
labelloc="t"
fontname="sans-serif"
]
node [
style=filled
fillcolor=gray95
fontname="sans-serif"
]
idle -> short [label="25-499" fontsize=10];
idle -> medium [label="500-1999" fontsize=10];
idle -> long [label="2000+" fontsize=10];
short -> idle;
medium -> idle;
long -> idle;
}
Actually now that I think more about it the output was not a state machine, it was just complex if then else.
Which in non-blocking code I call Tasks since they run virtually parallel.
When I use that word for functions containing not just a state machine (or multiple) but a timeout at the start to let a single timer cover all wait periods of the state machine… you didn’t object.
but Task in computer science has a very specific meaning.
why not say several functions are executed within loop()
You go to trouble to write a state machine, but compromise it with case code that blocks:
soakStartTime = millis();
while (millis() - soakStartTime <= soakDuration) {
FastPwmPin::enablePwmPin(3, 1000L, 25); // Pin 3, 1 KHz, 25% duty cycle //switching on PWM for relay pin
digitalWrite(LED_BUILTIN, HIGH);
}
which may be fine. But if you are OK with blocking code, you could just write the entire thing as a series of tests, actions and delays.
In a bob-blocking sketch, the expectation is that loop() runs free, cycling very rapidly and often doing nothing but checking that time has (not yet) elapsed or inputs have changed.
millis() can be called once at the beginning. This is an early thing I look at: calls to millis(), to get an idea of what is happening flow-wise and as far as blocking is concerned.
This would allow other functionality to be added, which itself would politely not block and not take much time each loop pass, again maybe mostly not doing anything. Yet.
a7
Good idea. Next comes the state switch with only non-blocking code. Other task related/managing functions are not required in such state machines.
There are as many ways to handle a timed state as there are to skin a cat.
Here I added an additional state which intiates soaking, and changed the soaking state to just wait out the period of soaking then shut it down.
/// in annealing
currentState = StartSoaking;
break;
case StartSoaking :
FastPwmPin::enablePwmPin(3, 1000L, 25); // Pin 3, 1 KHz, 25% duty cycle //switching on PWM for relay pin
digitalWrite(LED_BUILTIN, HIGH);
soakStartTime = now; // unsigned long now = millis(); at top of loop!
currentState = Soaking;
break;
case Soaking :
if (now - soakStartTime > soakDuration) {
FastPwmPin::disablePwmPin(3); //switching off PWM for relay pin
digitalWrite(LED_BUILTIN, LOW);
currentState = Trapdoor;
}
break;
I could not test this. But the idea is sound, and the same thing can be done to de-block any other cases that are currently blocking whilst marking time.
a7
Because I’m not part of the priesthood who don’t own the dictionary and I can change lightbulbs.
Find yourself a more general definition for the word Task in computing, it’s not that obscure nor pwned.
“The Functions run as Tasks” is a descriptive phrase, not a pedant’s perfect phrase.


