Firmware: Dedicated Thread For CAN Transmission#
Plan#
“Physical” Separation
In
main(), create a message queueIn
main()create a medium priority thread, and pass itThe queue’s read end
A CAN controller from Device Driver: CAN Controller JF0815
Pass the write end to the FSM
Remove CAN ISR from FSM
In
FSM::_state_change(), write into the message queue
Decouple FSM From CAN
All we want is state change notifications
From FSM point of view, CAN is irrelevant
⟶ Message queue carries plain FSM state
Frame created in CAN-thread
main(): CAN Thread#
#include "sensor.h"
#include "switch.h"
#include "fsm.h"
#include <can-jf0815.h>
#include <why-timer.h>
#include <why-irq.h>
#include <why-time.h>
#include <why-thread.h>
#include <print>
#include <cstring>
int main()
{
Why::init();
Sensor sensor("/tmp/plant/temperature");
Switch cooling_switch("/tmp/plant/cooling");
Switch soft_alarm("/tmp/plant/soft-alarm");
Switch hard_alarm("/tmp/plant/hard-alarm");
std::string can_iface("my-can"); // <-- Linux socketcan interface
const int can_id = 37; // <-- frame ID for CAN state change notifications
CAN_JF0815 can_controller(can_iface); // <-- CAN controller
Why::MessageQueue<FSM::State> state_change_queue; // <-- state change notifications
const long LOW_TEMP = 20;
const long HIGH_TEMP = 500'000;
const long SOFT_ALARM_TEMP = 1'000'000;
const int SENSOR_BURNING_IRQ = 7;
const int OPERATOR_BUTTON_IRQ = 42;
std::println("* Thermostat lo: {}", LOW_TEMP);
std::println("* Thermostat hi: {}", HIGH_TEMP);
std::println("* Soft critical: {}", SOFT_ALARM_TEMP);
std::println("* Cooling switch: {}", cooling_switch.path());
std::println("* Soft alarm: {}", soft_alarm.path());
std::println("* Hard alarm: {}", hard_alarm.path());
std::println("* Sensor burning IRQ: {}", SENSOR_BURNING_IRQ);
std::println("* Operator button IRQ: {}", OPERATOR_BUTTON_IRQ);
FSM fsm(
Why::TimeSpec(0, 1'000'000),
SENSOR_BURNING_IRQ, hard_alarm,
SOFT_ALARM_TEMP, soft_alarm,
OPERATOR_BUTTON_IRQ,
state_change_queue, // <-- state changes sent here
sensor, cooling_switch,
LOW_TEMP, HIGH_TEMP
);
fsm.start();
Why::Thread can_thread( // <-- send out state change notifications
[&can_controller, &state_change_queue](){
while (true) {
FSM::State state =
state_change_queue.get(); // <-- block until state changes
uint32_t notify_state;
switch (state) {
case FSM::State::INIT:
notify_state = 0;
break;
case FSM::State::RUNNING:
notify_state = 1;
break;
case FSM::State::OVERTEMP:
notify_state = 2;
break;
case FSM::State::SENSORBURNING:
notify_state = 3;
break;
}
CANFrame frame;
frame.id = can_id;
frame.dlc = sizeof(notify_state);
memcpy(frame.data, ¬ify_state, sizeof(notify_state));
can_controller.xmit(frame); // <-- block until frame is out
}
}
);
Why::pause();
return 0;
}
FSM: Send State On Message Queue#
#pragma once
#include "sensor.h"
#include "switch.h"
#include <why-timer.h>
#include <why-spinlock.h>
#include <why-messagequeue.h>
class FSM
{
public:
enum class State {
INIT,
RUNNING,
OVERTEMP,
SENSORBURNING,
};
public:
FSM(Why::TimeSpec interval,
int sensor_burning_irq, Switch& hard_alarm,
long soft_critical_temp, Switch& soft_alarm,
int operator_button_irq,
Why::MessageQueue<State>& state_change_queue,
Sensor& sensor, Switch& cooling,
long low_temp, long high_temp);
void start();
private:
void _timer_tick();
void _sensor_burning();
void _operator_button_pressed();
void _state_change(State state); // <-- instead of _state = ...
private:
Why::Spinlock _lock;
Why::TimeSpec _interval;
Why::Timer _interval_timer;
int _sensor_burning_irq;
Switch& _hard_alarm;
long _soft_critical_temp;
Switch& _soft_alarm;
int _operator_button_irq;
Sensor& _sensor;
Switch& _cooling;
long _low_temp;
long _high_temp;
Why::MessageQueue<State>& _state_change_queue; // <-- replaces CAN logic
State _state;
};
#include "fsm.h"
#include <why-irq.h>
#include <cassert>
#include <cstring>
FSM::FSM(
Why::TimeSpec interval,
int sensor_burning_irq, Switch& hard_alarm,
long soft_critical_temp, Switch& soft_alarm,
int operator_button_irq,
Why::MessageQueue<State>& state_change_queue,
Sensor& sensor, Switch& cooling,
long low_temp, long high_temp)
: _interval(interval),
_interval_timer([this](){_timer_tick();}),
_sensor_burning_irq(sensor_burning_irq),
_hard_alarm(hard_alarm),
_soft_critical_temp(soft_critical_temp),
_soft_alarm(soft_alarm),
_operator_button_irq(operator_button_irq),
_sensor(sensor),
_cooling(cooling),
_low_temp(low_temp),
_high_temp(high_temp),
_state_change_queue(state_change_queue),
_state(State::INIT)
{}
void FSM::start()
{
_lock.lock();
switch (_state) {
case State::INIT:
_interval_timer.start(Why::TimeSpec(0,1), _interval);
Why::IRQ::connect(_sensor_burning_irq, [this](int irqnum){_sensor_burning();});
Why::IRQ::connect(_operator_button_irq, [this](int irqnum){_operator_button_pressed();});
_sensor.turn_on();
_state_change(State::RUNNING);
break;
case State::RUNNING:
assert(!"RUNNING");
break;
case State::OVERTEMP:
assert(!"OVERTEMP");
break;
case State::SENSORBURNING:
assert(!"SENSORBURNING");
break;
}
_lock.unlock();
}
void FSM::_timer_tick()
{
_lock.lock();
switch (_state) {
case State::INIT:
assert(!"INIT");
break;
case State::RUNNING: {
double temp = _sensor.get_temperature();
if (temp > _soft_critical_temp) {
_cooling.set_state(true);
_soft_alarm.set_state(true);
_state_change(State::OVERTEMP);
}
else if (temp > _high_temp)
_cooling.set_state(true);
else if (temp < _low_temp)
_cooling.set_state(false);
break;
}
case State::OVERTEMP: {
double temp = _sensor.get_temperature();
if (temp > _high_temp)
_cooling.set_state(true);
else if (temp < _low_temp)
_cooling.set_state(false);
break;
}
case State::SENSORBURNING:
break;
}
_lock.unlock();
}
void FSM::_sensor_burning()
{
_lock.lock();
switch (_state) {
case State::INIT:
assert(!"INIT");
break;
case State::RUNNING:
_sensor.turn_off();
_cooling.set_state(false);
_hard_alarm.set_state(true);
_state_change(State::SENSORBURNING);
break;
case State::OVERTEMP:
_sensor.turn_off();
_cooling.set_state(false);
_soft_alarm.set_state(false);
_hard_alarm.set_state(true);
_state_change(State::SENSORBURNING);
break;
case State::SENSORBURNING:
break;
}
_lock.unlock();
}
void FSM::_operator_button_pressed()
{
_lock.lock();
switch (_state) {
case State::INIT:
assert(!"INIT");
break;
case State::RUNNING:
break;
case State::OVERTEMP:
_soft_alarm.set_state(false);
_state_change(State::RUNNING);
break;
case State::SENSORBURNING:
break;
}
_lock.unlock();
}
void FSM::_state_change(State state)
{
auto written =
_state_change_queue.put_noblock(state); // <-- noblock: we're still in interrupt context
if (!written)
std::println("state changes too fast?!"); // <-- missing one notification!
_state = state;
}