Prusa-Firmware-MMU/src/registers.cpp

189 lines
6.0 KiB
C++

#ifndef UNITTEST
#include <avr/pgmspace.h>
#else
#define PROGMEM /* */
#endif
#include "registers.h"
#include "application.h"
#include "version.h"
#include "modules/finda.h"
#include "modules/fsensor.h"
#include "modules/globals.h"
struct RegisterFlags {
uint8_t writable : 1;
uint8_t rwfuncs : 1; // 1: register needs special read and write functions
uint8_t size : 2; // 0: 1 bit, 1: 1 byte, 2: 2 bytes
constexpr RegisterFlags(bool writable, uint8_t size)
: writable(writable)
, rwfuncs(0)
, size(size) {}
constexpr RegisterFlags(bool writable, bool rwfuncs, uint8_t size)
: writable(writable)
, rwfuncs(rwfuncs)
, size(size) {}
};
using TReadFunc = uint16_t (*)();
using TWriteFunc = void (*)(uint16_t);
struct RegisterRec {
RegisterFlags flags;
union U1 {
void *addr;
TReadFunc readFunc;
constexpr explicit U1(const TReadFunc &r)
: readFunc(r) {}
constexpr explicit U1(void *a)
: addr(a) {}
} A1;
union U2 {
void *addr;
TWriteFunc writeFunc;
constexpr explicit U2(const TWriteFunc &w)
: writeFunc(w) {}
constexpr explicit U2(void *a)
: addr(a) {}
} A2;
template <typename T>
constexpr RegisterRec(bool writable, T *address)
: flags(RegisterFlags(writable, sizeof(T)))
, A1((void *)address)
, A2((void *)nullptr) {}
constexpr RegisterRec(const TReadFunc &readFunc, uint8_t bytes)
: flags(RegisterFlags(false, true, bytes))
, A1(readFunc)
, A2((void *)nullptr) {}
constexpr RegisterRec(const TReadFunc &readFunc, const TWriteFunc &writeFunc, uint8_t bytes)
: flags(RegisterFlags(true, true, bytes))
, A1(readFunc)
, A2(writeFunc) {}
};
// @@TODO it is nice to see all the supported registers at one spot,
// however it requires including all bunch of dependencies
// which makes unit testing and separation of modules much harder.
// @@TODO clang complains that we are initializing this array with an uninitialized referenced variables (e.g. mg::globals)
// Imo that should be safe as long as we don't call anything from this array before the FW init is completed (which we don't).
// Otherwise all the addresses of global variables should be known at compile time and the registers array should be consistent.
static const RegisterRec registers[] PROGMEM = {
RegisterRec(false, &project_major),
RegisterRec(false, &project_minor),
RegisterRec(false, &project_build_number),
RegisterRec( // MMU errors
[]() -> uint16_t { return mg::globals.DriveErrors(); },
[](uint16_t) {}, // @@TODO think about setting/clearing the error counter from the outside
2),
RegisterRec([]() -> uint16_t { return application.CurrentProgressCode(); }, 1),
RegisterRec([]() -> uint16_t { return application.CurrentErrorCode(); }, 2),
RegisterRec( // filamentState
[]() -> uint16_t { return mg::globals.FilamentLoaded(); },
[](uint16_t v) { return mg::globals.SetFilamentLoaded(mg::globals.ActiveSlot(), static_cast<mg::FilamentLoadState>(v)); },
1),
RegisterRec( // FINDA
[]() -> uint16_t { return static_cast<uint16_t>(mf::finda.Pressed()); },
1),
RegisterRec( // fsensor
[]() -> uint16_t { return static_cast<uint16_t>(mfs::fsensor.Pressed()); },
[](uint16_t v) { return mfs::fsensor.ProcessMessage(v != 0); },
1),
RegisterRec([]() -> uint16_t { return static_cast<uint16_t>(mg::globals.MotorsStealth()); }, 1), // mode (stealth = 1/normal = 0)
RegisterRec( // extra load distance after fsensor triggered (30mm default)
[]() -> uint16_t { return mg::globals.FSensorToNozzleMM(); },
[](uint16_t d) { mg::globals.SetFSensorToNozzleMM(d); },
1),
// The lambas seem to be pretty cheap:
// void SetFSensorToNozzleFeedrate(uint8_t fs2NozzleFeedrate) { fsensorToNozzleFeedrate = fs2NozzleFeedrate; }
// compiles to:
// sts <modules::globals::globals+0x4>, r24
// ret
RegisterRec( // extra load distance after fsensor triggered - feedrate (20mm/s default)
[]() -> uint16_t { return mg::globals.FSensorToNozzleFeedrate(); },
[](uint16_t d) { mg::globals.SetFSensorToNozzleFeedrate(d); },
1),
};
static constexpr uint8_t registersSize = sizeof(registers) / sizeof(RegisterRec);
bool ReadRegister(uint8_t address, uint16_t &value) {
if (address >= registersSize) {
return false;
}
value = 0;
if (!registers[address].flags.rwfuncs) {
switch (registers[address].flags.size) {
case 0:
case 1:
value = *static_cast<uint8_t *>(registers[address].A1.addr);
break;
case 2:
value = *static_cast<uint16_t *>(registers[address].A1.addr);
break;
default:
return false;
}
return true;
} else {
switch (registers[address].flags.size) {
case 0:
case 1:
case 2:
value = registers[address].A1.readFunc();
break;
default:
return false;
}
return true;
}
}
bool WriteRegister(uint8_t address, uint16_t value) {
if (address >= registersSize) {
return false;
}
if (!registers[address].flags.writable) {
return false;
}
if (!registers[address].flags.rwfuncs) {
switch (registers[address].flags.size) {
case 0:
case 1:
*static_cast<uint8_t *>(registers[address].A1.addr) = value;
break;
case 2:
*static_cast<uint16_t *>(registers[address].A1.addr) = value;
break;
default:
return false;
}
return true;
} else {
switch (registers[address].flags.size) {
case 0:
case 1:
case 2:
registers[address].A2.writeFunc(value);
break;
default:
return false;
}
return true;
}
}