Firmware: Dedicated Thread For CAN Transmission#

../../../../../../../_images/fsm-can-thread.svg

Plan#

“Physical” Separation

  • In main(), create a message queue

  • In main() create a medium priority thread, and pass it

  • 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, &notify_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;
}