Firmware
========

The project's fitmware is split in three parts: the bootloader, the
main firmware and the test firmware.


PMBus command infrastructure
----------------------------

A common command handling infrastructure has been put in place, such
that both the main firmware and the bootloader can easily implement
different subsets of PMBus commands. The basic construct
of this implementation is the :code:`cmd_t` structure:

.. doxygenstruct:: cmd_t
   :project: common
   :members:

An array of these structs makes up a command space:

.. doxygenstruct:: cmd_space_t
   :project: common
   :members:

From the user's point of view, these structures are defined and used
just once, in the function

.. doxygenfunction:: setup_I2C_slave
   :project: common
   :outline:

This function will configure the inturrupt handlers, below, with the
command spaces defined in the specific user implementation (main
firmware or bootloader). From that point on, the only interaction will
be through the user-defined callbacks.

.. doxygengroup:: I2C_int_handlers
   :project: common
   :outline:
   :content-only:



Bootloader
----------

The bootloader, after bringing up the device, will check for the
special word :code:`0xBEC0ABCD` in the flash storage (see struct
below) and, depending on the value, will either hand control to the
main FW, or enter remote programming (bootloader) mode.

.. doxygenstruct:: user_flash_t
   :project: common
   :members:



PMBus commands overview
-----------------------

The full list of PMBus commands implemented by the
MoniMod can be found in :numref:`Table %s <pmbus_cmds>` and
:numref:`Table %s <pmbus_cmds_mfn_spec>`.

All physical quantities (except output voltage, which is discussed
below) are expressed in the 16-bit PMBus Linear data format (LINEAR11,
:numref:`Linear`), instead of the (somewhat more complex) Direct
format PMBus also supports. An 11-bit mantissa (Y) and a 5-bit
exponent (N), expressed in 2's complement, form a floating-point
number X according to :math:`X = Y \cdot 2^N`.

.. _Linear:

.. figure:: figures/latex/linear.png
   :align: center
   :scale: 50%

   The PMBus Linear data format

The output voltages use a different, 21-bit format, called LINEAR16
(in contrast to the 16-bit LINEAR11, the names are derived from the
mantissa width). This format comprises a 5-bit 2's complement
exponent, reported by the 5 MSBs of the VOUT_MODE_ command; and a
16-bit unsigned mantissa, reported by READ_VOUT_. Many COTS PSUs use
LINEAR11 for everything, and this behavior is also possible using a
compile-time switch. In the MoniMod, the exponential factor for the
LINEAR16 format is fixed to -10, so the voltages reported can be
obtained with :math:`X = Y \cdot 2^-10`, where Y is the 16-bit value
returned by READ_VOUT_.

.. _pmbus_cmds:

.. table:: PMBus commands implemented by the MoniMod

   +----------+---------------------+------------------------+----------+------------------------------+
   | Cmd code | Command name        | Transaction type       | Data len | Description                  |
   +==========+=====================+========================+==========+==============================+
   | 00 +     | PAGE_               | Byte write / read      |        1 | set get page                 |
   +----------+---------------------+------------------------+----------+------------------------------+
   | 19 +     | CAPABILITY_         | Byte read              |        1 | capabilities of the device   |
   +----------+---------------------+------------------------+----------+------------------------------+
   | 1A +*    | QUERY_              | Block w / r proc. call |        1 | query cmd props              |
   +----------+---------------------+------------------------+----------+------------------------------+
   | 20 +     | VOUT_MODE_          | Byte read              |        1 | read voltage format          |
   +----------+---------------------+------------------------+----------+------------------------------+
   | 3A +     | FAN_CONFIG_1_2_     | Byte write / read      |        1 | config fans 1&2              |
   +----------+---------------------+------------------------+----------+------------------------------+
   | 3B +     | FAN_COMMAND_1_      | Word write / read      |        2 | set fan 1 speed              |
   +----------+---------------------+------------------------+----------+------------------------------+
   | 3C +     | FAN_COMMAND_2_      | Word write / read      |        2 | set fan 2 speed              |
   +----------+---------------------+------------------------+----------+------------------------------+
   | 3D +     | FAN_CONFIG_3_4_     | Byte write / read      |        1 | config fan 3                 |
   +----------+---------------------+------------------------+----------+------------------------------+
   | 3E +     | FAN_COMMAND_3_      | Word write / read      |        2 | set fan 3 speed              |
   +----------+---------------------+------------------------+----------+------------------------------+
   | 78 +     | STATUS_BYTE_        | Byte read              |        1 | status byte                  |
   +----------+---------------------+------------------------+----------+------------------------------+
   | 7E +     | STATUS_CML_         | Byte read              |        1 | status CML                   |
   +----------+---------------------+------------------------+----------+------------------------------+
   | 8B +     | READ_VOUT_          | Word read              |        2 | read voltage                 |
   +----------+---------------------+------------------------+----------+------------------------------+
   | 8C +     | READ_IOUT_          | Word read              |        2 | read current                 |
   +----------+---------------------+------------------------+----------+------------------------------+
   | 8D +     | READ_TEMPERATURE_1_ | Word read              |        2 | read temp. sensor 1          |
   +----------+---------------------+------------------------+----------+------------------------------+
   | 8E +     | READ_TEMPERATURE_2_ | Word read              |        2 | read temp. sensor 2          |
   +----------+---------------------+------------------------+----------+------------------------------+
   | 8F +     | READ_TEMPERATURE_3_ | Word read              |        2 | read temp. sensor 3          |
   +----------+---------------------+------------------------+----------+------------------------------+
   | 90 +     | READ_FAN_SPEED_1_   | Word read              |        2 | read fan 1 speed             |
   +----------+---------------------+------------------------+----------+------------------------------+
   | 91 +     | READ_FAN_SPEED_2_   | Word read              |        2 | read fan 2 speed             |
   +----------+---------------------+------------------------+----------+------------------------------+
   | 92 +     | READ_FAN_SPEED_3_   | Word read              |        2 | read fan 3 speed             |
   +----------+---------------------+------------------------+----------+------------------------------+
   | 96 +     | READ_POUT_          | Word read              |        2 | read power                   |
   +----------+---------------------+------------------------+----------+------------------------------+
   | 99 +*    | MFR_ID_             | Block read             |      var | manufacturer ID              |
   +----------+---------------------+------------------------+----------+------------------------------+
   | 9A +*    | MFR_MODEL_          | Block read             |      var | model                        |
   +----------+---------------------+------------------------+----------+------------------------------+
   | 9B +*    | MFR_REVISION_       | Block read             |      var | revision                     |
   +----------+---------------------+------------------------+----------+------------------------------+
   | 9C +*    | MFR_LOCATION_       | Block read             |      var | location                     |
   +----------+---------------------+------------------------+----------+------------------------------+
   | 9D +*    | MFR_DATE_           | Block read             |      var | date                         |
   +----------+---------------------+------------------------+----------+------------------------------+
   | 9E +*    | MFR_SERIAL_         | Block read             |      var | serial number                |
   +----------+---------------------+------------------------+----------+------------------------------+
   | AE +*    | IC_DEVICE_REV_      | Block read             |      var | git commit id                |
   +----------+---------------------+------------------------+----------+------------------------------+

Commands marked with \+ or \* are:

\+ - supported by main fw

\* - supported by bootloader

.. _pmbus_cmds_mfn_spec:

.. table:: Manufacturer specific PMBus commands implemented by the MoniMod

   +----------+---------------------+------------------------+----------+-------------------------------------------------------------------------+
   | Cmd code | Command name        | Transaction type       | Data len | Description                                                             |
   +==========+=====================+========================+==========+=========================================================================+
   | D1  *    | WRITTEN_FW_SIZE_    | Word write / read      |        2 | size of the FW to be written                                            |
   +----------+---------------------+------------------------+----------+-------------------------------------------------------------------------+
   | D2  *    | WRITTEN_FW_BLOCK_   | MultiByte write / read |        8 | FW block to be written                                                  |
   +----------+---------------------+------------------------+----------+-------------------------------------------------------------------------+
   | D3  *    | WRITTEN_FW_CHKSUM_  | Word write / read      |        2 | checksum of the written FW                                              |
   +----------+---------------------+------------------------+----------+-------------------------------------------------------------------------+
   | D4  *    | LOCAL_FW_CHKSUM_    | Word write             |        2 | calculated checksum of the written FW                                   |
   +----------+---------------------+------------------------+----------+-------------------------------------------------------------------------+
   | D5 +*    | BOOT_NEW_FW_        | Byte write / read      |        1 | on write turn on btldr pgm mode, reset; on read get which fw is running |
   +----------+---------------------+------------------------+----------+-------------------------------------------------------------------------+
   | D6 +*    | UC_RESET_           | Byte write             |        1 | reset the uC                                                            |
   +----------+---------------------+------------------------+----------+-------------------------------------------------------------------------+
   | D7 +     | UPTIME_SECS_        | Block read             | var(1+4) | get the uptime in seconds                                               |
   +----------+---------------------+------------------------+----------+-------------------------------------------------------------------------+
   | D8 +     | TMR_ERROR_CNT_      | Block read             | var(1+4) | get TMR error count                                                     |
   +----------+---------------------+------------------------+----------+-------------------------------------------------------------------------+
   | D9 +     | USE_PEC_            | Byte write / read      |        1 | turn PEC on / off                                                       |
   +----------+---------------------+------------------------+----------+-------------------------------------------------------------------------+
   | E0 +     | TEMP_CURVE_POINTS_  | Block write / read     | var(1+13)| set / get temp. curve points                                            |
   +----------+---------------------+------------------------+----------+-------------------------------------------------------------------------+
   | E1 +     | TEMP_MATRIX_ROW_    | Block write / read     | var(1+7) | set / get temp. matrix points                                           |
   +----------+---------------------+------------------------+----------+-------------------------------------------------------------------------+
   | E2 +     | TC_ONOFF_           | Byte write / read      |        1 | turn temp. control on / off                                             |
   +----------+---------------------+------------------------+----------+-------------------------------------------------------------------------+

Commands marked with \+ or \* are:

\+ - supported by main fw

\* - supported by bootloader

Block registers contain in the first byte the size of data that follows.
This also applies to the registers with the fixed size like UPTIME_SECS_.

Detailed list of PMBus commands
-------------------------------

.. _PAGE:

PAGE
~~~~

| Command code: **00**
| Transaction type: **Byte write / read**
| Data length: **1**

The PAGE command is used to select a power rail for the READ_VOUT_,
READ_IOUT_ and READ_POUT_ commands. Allowed values for the page
parameter are :math:`0 \leq N \leq 3` for the DI/OT Rad-Tol System
Board revision and :math:`0 \leq N \leq 2` for the fan-tray, RaToPUS
and generic prototype revisions.

.. _CAPABILITY:

CAPABILITY
~~~~~~~~~~

| Command code: **19**
| Transaction type: **Byte read**
| Data length: **1**

The CAPABILITY command returns one byte of information with some key
capabilities of a PMBus device.

.. table:: CAPABILITY Data byte format

   +--------+------------------------------------------------------+
   | Bit(s) | Meaning                                              |
   +--------+------------------------------------------------------+
   |      7 | Packet Error Checking is supported                   |
   +--------+------------------------------------------------------+
   |    6:0 | Unused                                               |
   +--------+------------------------------------------------------+

Bit 7 of the CAPABILITY register reflects the use of PEC set by the USE_PEC_ register.

.. _QUERY:

QUERY
~~~~~

| Command code: **1A**
| Transaction type: **Block w / r proc. call**
| Data length: **1**

The QUERY command takes a command code as an argument and replies with
information on the command: whether it is supported, if read or write
is supported, and what data format it works with.

.. _VOUT_MODE:

VOUT_MODE
~~~~~~~~~

| Command code: **20**
| Transaction type: **Byte read**
| Data length: **1**

The VOUT_MODE command reports the format the device uses for measured
voltage related data. The 3 MSBs indicate whether that's Linear_
(0b000), VID (0b001) or Direct (0b010), and in the MoniMod's case it's
always 0b000 for Linear format. The 5 MSBs return either 0x16, for
LINEAR16 format used (fully PMBus-compliant operation, fixed
:math:`2^-10` exponential); or 0x00, for LINEAR11 format used (common
for COTS PSUs). See Linear_ for more details on the specifics of these
formats.

.. _FAN_CONFIG_1_2:
.. _FAN_CONFIG_3_4:

FAN_CONFIG_n_m
~~~~~~~~~~~~~~

| Command codes: **3A, 3D**
| Transaction type: **Byte write / read**
| Data length: **1**

The FAN_CONFIG_1_2 and FAN_CONFIG_3_4 commands are used to configure
the fans at positions 1, 2, and 3. The format of the configuration
byte can be seen in :numref:`fan_config_cmd`. The two bits that set
the tachometer pulses / revolution, which take the values 0--3,
correspond to 1--4 pulses per revolution.

.. _fan_config_cmd:

.. table:: FAN_CONFIG_1_2 and FAN_CONFIG_3_4 data byte format

   +--------+-----------+-----------------------------------+
   | Bit(s) |     Value | Meaning                           |
   +--------+-----------+-----------------------------------+
   |      7 |         1 | Fan 1 / 3 installed               |
   +        +-----------+-----------------------------------+
   |        |         0 | Fan 1 / 3 not installed           |
   +--------+-----------+-----------------------------------+
   |      6 |         1 | Fan 1 / 3 commanded in RPM        |
   +        +-----------+-----------------------------------+
   |        |         0 | Fan 1 / 3 commanded in duty cycle |
   +--------+-----------+-----------------------------------+
   |    5:4 |      0--3 | Fan 1 / 3 tachometer pulses / rev |
   +--------+-----------+-----------------------------------+
   |      3 |         1 | Fan 2 installed                   |
   +        +-----------+-----------------------------------+
   |        |         0 | Fan 2 not installed               |
   +--------+-----------+-----------------------------------+
   |      2 |         1 | Fan 2 commanded in RPM            |
   +        +-----------+-----------------------------------+
   |        |         0 | Fan 2 commanded in duty cycle     |
   +--------+-----------+-----------------------------------+
   |    1:0 |      0--3 | Fan 2 tachometer pulses / rev     |
   +--------+-----------+-----------------------------------+


.. _FAN_COMMAND_1:
.. _FAN_COMMAND_2:
.. _FAN_COMMAND_3:
   
FAN_COMMAND_n
~~~~~~~~~~~~~

| Command code: **3B, 3C, 3E**
| Transaction type: **Word write / read**
| Data length: **2**

The FAN_COMMAND_n commands set the desired speed of the attached
fans. The value set is either in RPMs (when the fan is configured to
be controlled like that) or duty cycle, in the range 0--1000.


.. _STATUS_BYTE:

STATUS_BYTE
~~~~~~~~~~~

| Command code: **78**
| Transaction type: **Byte read**
| Data length: **1**

The STATUS_BYTE command returns one byte of information with a summary of the
most critical faults.

.. table:: STATUS_BYTE Data byte format

   +--------+------------------------------------------------------+
   | Bit(s) | Meaning                                              |
   +--------+------------------------------------------------------+
   |    7:2 | Unused                                               |
   +--------+------------------------------------------------------+
   |      1 | A communications, memory or logic fault has occurred |
   +--------+------------------------------------------------------+
   |      0 | Unused                                               |
   +--------+------------------------------------------------------+

The value of STATUS_BYTE is calculated based on other status registers (for now only STATUS_CML).
To clear value of STATUS_BYTE, please clear information in other status registers.

.. _STATUS_CML:

STATUS_CML
~~~~~~~~~~

| Command code: **7E**
| Transaction type: **Byte write / read**
| Data length: **1**

The STATUS_CML command returns one data byte with contents as follows:

.. table:: STATUS_CML Data byte format

   +--------+---------------------------+
   | Bit(s) | Meaning                   |
   +--------+---------------------------+
   |    7:6 | Unused                    |
   +--------+---------------------------+
   |      5 | Packet Error Check Failed |
   +--------+---------------------------+
   |      4 | TMR Error                 |
   +--------+---------------------------+
   |    3:2 | Unused                    |
   +--------+---------------------------+
   |      1 | A communication fault     |
   +--------+---------------------------+
   |      0 | Unused                    |
   +--------+---------------------------+

Write 1's in the desired bits positions to clear corresponding errors in the register.

Clearing TMR Error flag sets TMR_ERROR_CNT_ register to 0.

.. _READ_VOUT:

READ_VOUT
~~~~~~~~~

| Command code: **8B**
| Transaction type: **Word read**
| Data length: **2**

The READ_VOUT command is used to get the measured voltage of the rail
indicated by the last PAGE command (by default that would be the first
one).

.. _READ_IOUT:

READ_IOUT
~~~~~~~~~

| Command code: **8C**
| Transaction type: **Word read**
| Data length: **2**

The READ_IOUT command is used to get the measured current of the rail
indicated by the last PAGE command (by default that would be the first
one).

.. _READ_TEMPERATURE_1:
.. _READ_TEMPERATURE_2:
.. _READ_TEMPERATURE_3:

READ_TEMPERATURE_N
~~~~~~~~~~~~~~~~~~

| Command code: **8D, 8E, 8F**
| Transaction type: **Word read**
| Data length: **2**

The READ_TEMPERATURE_n commands return the measured temperature from
the three installed temperature sensors.

.. _READ_FAN_SPEED_1:
.. _READ_FAN_SPEED_2:
.. _READ_FAN_SPEED_3:

READ_FAN_SPEED_N
~~~~~~~~~~~~~~~~

| Command code: **90, 91, 92**
| Transaction type: **Word read**
| Data length: **2**

The READ_FAN_SPEED_n return the fan speed of an installed fan, or 0 in
case no fan is installed in the pertinent location.

.. _READ_POUT:

READ_POUT
~~~~~~~~~

| Command code: **96**
| Transaction type: **Word read**
| Data length: **2**

The READ_POUT command is used to get the measured power of the rail
indicated by the last PAGE command (by default that would be the first
one).

.. _MFR_ID:

MFR_ID
~~~~~~

| Command code: **99**
| Transaction type: **Block read**
| Data length: **var**

This returns the manufacturer ID string, "CERN (BE/CO)".

.. _MFR_MODEL:

MFR_MODEL
~~~~~~~~~

| Command code: **9A**
| Transaction type: **Block read**
| Data length: **var**

This returns the manufacturer model string, "DI/OT MoniMod".

.. _MFR_REVISION:

MFR_REVISION
~~~~~~~~~~~~

| Command code: **9B**
| Transaction type: **Block read**
| Data length: **var**

This returns the manufacturer revision string.

.. _MFR_LOCATION:

MFR_LOCATION
~~~~~~~~~~~~

| Command code: **9C**
| Transaction type: **Block read**
| Data length: **var**

This returns the manufacturer ID string, "Geneva".

.. _MFR_DATE:

MFR_DATE
~~~~~~~~

| Command code: **9D**
| Transaction type: **Block read**
| Data length: **var**

This returns the manufacturer date string, which currently corresponds
to the date of the last release (and not the build used, for example).

.. _MFR_SERIAL:

MFR_SERIAL
~~~~~~~~~~

| Command code: **9E**
| Transaction type: **Block read**
| Data length: **var**

This returns a manufacturer serial string (currently unused, returns
"123456789").

.. _IC_DEVICE_REV:

IC_DEVICE_REV
~~~~~~~~~~~~~

| Command code: **AE**
| Transaction type: **Block read**
| Data length: **var**

This returns a git commit id of the used firmware.

.. _WRITTEN_FW_SIZE:

WRITTEN_FW_SIZE
~~~~~~~~~~~~~~~

| Command code: **D1**
| Transaction type: **Word write / read**
| Data length: **2**

Before writing a new FW binary through the bootloader, its size in bytes
divided by 8 has to be given using this command.
The written value can be read.

.. _WRITTEN_FW_BLOCK:

WRITTEN_FW_BLOCK
~~~~~~~~~~~~~~~~

| Command code: **D2**
| Transaction type: **MultiByte write / read**
| Data length: **8**

A new binary is written to the bootloader in consecutive chunks of 8
bytes, using this command.
The written data can be read.

.. _WRITTEN_FW_CHKSUM:

WRITTEN_FW_CHKSUM
~~~~~~~~~~~~~~~~~

| Command code: **D3**
| Transaction type: **Word write / read**
| Data length: **2**

After setting the size of the FW binary with WRITTEN_FW_SIZE and
writing it with the WRITTEN_FW_BLOCK command, its SYS-V checksum
should be written with this command. This command also resets the
write pointers.
The written checksum can be read. Please note that this is not
the calculated checksum. The calculated checksum is stored in
LOCAL_FW_CHKSUM (:numref:`LOCAL_FW_CHKSUM`).

.. _LOCAL_FW_CHKSUM:

LOCAL_FW_CHKSUM
~~~~~~~~~~~~~~~

| Command code: **D4**
| Transaction type: **Word read**
| Data length: **2**

After writing the new firmware, this command can be used to get
the checksum calculated by the MoniMod. Please note that it is
up to the writer to verify (compare) the checksum.

.. _BOOT_NEW_FW:

BOOT_NEW_FW
~~~~~~~~~~~

| Command code: **D5**
| Transaction type: **Byte write / read**
| Data length: **1**

The BOOT_NEW_FW command changes the execution mode of the Firmware.
When the byte 0xAD is written to this register the running firmware is switched
between bootloader and main mode.
When the proper byte is received, a special code is written to or removed form
the flash memory to let the bootloader to stay in bootloader mode or proceed to
the main firmware. More information is available in the section Bootloader_.

The read gives the information which firmware is actually running.

   +--------+---------------------------+
   | Value  | Meaning                   |
   +--------+---------------------------+
   |    1   | Bootloader                |
   +--------+---------------------------+
   |    2   | Main firmware             |
   +--------+---------------------------+

.. _UC_RESET:

UC_RESET
~~~~~~~~

| Command code: **D6**
| Transaction type: **Byte write**
| Data length: **1**

Writing 0x5A byte to this register triggers a uC reset.
Other writes are silently ignored.

.. _UPTIME_SECS:

UPTIME_SECS
~~~~~~~~~~~

| Command code: **D7**
| Transaction type: **Block read**
| Data length: **var(1+4)**

Get the uptime of the MoniMod (in seconds).

The fist byte of the register contains the number of bytes of data.
For this register, its value is fixed to 4.

.. _TMR_ERROR_CNT:

TMR_ERROR_CNT
~~~~~~~~~~~~~

| Command code: **D8**
| Transaction type: **Block read**
| Data length: **var(1+4)**

When software mitigation through COAST is enabled (see
:ref:`coast_sec`), one can access the TMR_ERROR_CNT counter using
this command. Setting `TMR Error` bit in STATUS_CML_ clears the value of this
register.

The fist byte of the register contains the number of bytes of data.
For this register, its value is fixed to 4.

.. _USE_PEC:

USE_PEC
~~~~~~~

| Command code: **D9**
| Transaction type: **Byte write / read**
| Data length: **1**

The SMBus specification indicates that a device's PEC support could be
enabled or disabled at will. Using this command with a zero byte
disables PEC; any non-zero value enables it. The command itself is
used without a PEC byte appended, no matter whether the function is
enabled or not.

.. _TEMP_CURVE_POINTS:

TEMP_CURVE_POINTS
~~~~~~~~~~~~~~~~~

| Command code: **E0**
| Transaction type: **Block write / read**
| Data length: **var(1+13)**

As described in the :ref:`temp_control_sec` section, the temperature
curve can be set separately for each fan. To do this, the format in
:numref:`temp_curve_fmt` has to be used.

.. _temp_curve_fmt:

.. figure:: figures/latex/temp_curve_format.png
   :align: center
   :scale: 33%

   Temperature curve data frame

The first byte specifies the fan number (0-2), following bytes
contains data specific for temperature curve.

The first read after the write to this register allows to get just stored data
for the particular fan.
The following reads of this register iterates over all fans and allow to get
their data.

The fist byte of the register contains the number of bytes of data.
For this register, its value is fixed to 13.

.. _TEMP_MATRIX_ROW:
   
TEMP_MATRIX_ROW
~~~~~~~~~~~~~~~

| Command code: **E1**
| Transaction type: **Block write / read**
| Data length: **var(1+7)**

As described in the :ref:`temp_control_sec` section, the temperature
matrix can be set separately for each fan. The data format for the
operation is illustrated in :numref:`temp_matrix_fmt`.

.. _temp_matrix_fmt:

.. figure:: figures/latex/temp_matrix_format.png
   :align: center
   :scale: 33%

   Temperature matrix data frame

The first byte specifies the fan number (0-2), following bytes
contains data specific for temperature control.

The first read after the write to this register allows to get just stored data
for the particular fan.
The following reads of this register iterates over all fans and allow to get
their data.

The fist byte of the register contains the number of bytes of data.
For this register, its value is fixed to 7.

.. _TC_ONOFF:
   
TC_ONOFF
~~~~~~~~

| Command code: **E2**
| Transaction type: **Byte write / read**
| Data length: **1**

Using the TC_ONOFF command with a zero argument disables Temperature
Control, while any non-zero value enables it.


Fan control PID
---------------

When the fans connected provide a tachometer output, fan speed control
can be enabled. This is implemented using PID controllers, with each
fan having its own instance. The main data structure of the PID
implementation is

.. doxygenstruct:: pid_cntrl_t
   :members:

This is used by the main software to set the PID setpoint, and by the
PID controller to hold integration data. The main function that has to
be called every timestep is described below:

.. doxygenfunction:: pid_compute


.. _temp_control_sec:

Temperature control
-------------------

The MoniMod implements a very flexible temperature control
scheme. Each fan can be assigned its own 3-point temperature--speed
curve, as in :numref:`temp_curve_fig`. Temperatures outside the set
range will adopt the speed of the minimum and maximum temperature,
accordingly.

.. _temp_curve_fig:

.. figure:: figures/latex/temp_curve.png
   :width: 50%
   :align: center

   Temperature curve

Moreover, the temperature each fan considers for its curve is a
weighted product of all three monitored temperatures, as in
:numref:`temp_matrix_fig`. This allows one to easily configure the
MoniMod to match a wide variety of fan / sensor setups, e.g.:

* each fan is assigned its own temperature sensor
* all three temperatures are averaged to give a more precise system
  temperature
* one fan blows directly on a sensitive component which is monitored,
  the other two fans handle the rest of the system

.. _temp_matrix_fig:

.. figure:: figures/latex/temp_matrix.png
   :width: 50%
   :align: center

   Temperature mixing matrix


Test firmware
-------------

To help with development, a test firmware has been written for a
`Feather M0 Basic <https://www.adafruit.com/product/2772>`_ minimal
development board.


Mitigation measures
-------------------

The MoniMod will be used in radiation environments. Although its
function is not critical and it can be remotely reset upon loss of
communication, some measures have been taken to minimize interruptions
and data corruption, leading to an improved QoS.


.. _coast_sec:

TMR using COAST
~~~~~~~~~~~~~~~

The `COAST <https://github.com/byuccl/coast>`_ LLVM passes can be
optionally used to automatically implement TMR (Triple Modular
Redundancy) in important and long-lived variables. This can
particularly benefit the integrity of dynamic configuration data that
gets set in the memory once and then gets read periodically, or state
machines such as the one in the I2C interrupt handlers which is
critical for stable communications.


NOPs and trampolines
~~~~~~~~~~~~~~~~~~~~

The Program Counter is also sensitive to SEUs; in fact, execution can
sometimes jump to an invalid address. To help mitigate failures owed
to this mechanism, any region of unused memory space has been filled
with :code:`NOP` instructions, and a small trampoline function as an
epilogue that will reset the stack pointer and jump to the device
initialization code. Furthermore, the instruction that comprises the
main loop has been placed at a "strategic" location, aligned by
:code:`0x8000`: that way, a bit-flip in any of the lower bits will
send execution to the upper memory region, filled with the NOPs and
concluding at the trampoline.


Watchdog
~~~~~~~~

The uC integrates a watchdog peripheral: this is fed every time the
main timer callback runs, i.e. every 10ms. The watchdog is set to
trigger if it doesn't get fed for 20ms -- as soon as the main loop
skips a beat. That ensures a quick revival of the uC and should lead
to minimal downtime.


Bling scrubbing
~~~~~~~~~~~~~~~

Blindly scrubbing the configuration of peripherals can be used to
reduce gradual corruption of their configuration during operation. The
frequency has to be carefully selected to minimize downtime.

.. note:: This hasn't been implemented yet, this is a reminder to do
          it.


Stack protection
~~~~~~~~~~~~~~~~

The compilers' stack protection feature is enabled to catch the corner
case that some loop goes awry and corrupts the stack due to some SEU.
In case that happens, the uC quickly gets reset.


Toolchains
----------

The project can be built with GCC and Clang / LLVM compilers; one can
switch between the two simply by setting a Makefile variable. Note,
however, that TMR only works with Clang.
