From 20ca99befd9f2d9be3c3011888d7f39d88f3b84d Mon Sep 17 00:00:00 2001 From: "D.R.racer" Date: Sun, 31 Jul 2022 13:36:54 +0200 Subject: [PATCH] Add additional registers It looks like the units' conversion linked some floating point routines ... C++ units are a PITA :( --- src/logic/cut_filament.cpp | 4 +- src/logic/feed_to_bondtech.cpp | 12 +-- src/logic/retract_from_finda.cpp | 2 +- src/logic/unload_to_finda.cpp | 4 +- src/modules/globals.cpp | 20 ++--- src/modules/globals.h | 45 ++++++++-- src/modules/idler.cpp | 8 +- src/modules/selector.cpp | 8 +- src/registers.cpp | 145 ++++++++++++++++++++++++++----- src/version.c | 3 +- src/version.h | 4 + 11 files changed, 197 insertions(+), 58 deletions(-) diff --git a/src/logic/cut_filament.cpp b/src/logic/cut_filament.cpp index b45ef94..59edd92 100644 --- a/src/logic/cut_filament.cpp +++ b/src/logic/cut_filament.cpp @@ -62,7 +62,7 @@ bool CutFilament::StepInner() { } else { // unload back to the pulley state = ProgressCode::UnloadingToPulley; - mpu::pulley.PlanMove(-config::cutLength, config::pulleyUnloadFeedrate); + mpu::pulley.PlanMove(-config::cutLength, mg::globals.PulleyUnloadFeedrate_mm_s()); } } break; @@ -76,7 +76,7 @@ bool CutFilament::StepInner() { case ProgressCode::PreparingBlade: if (ms::selector.Slot() == cutSlot + 1) { state = ProgressCode::PushingFilament; - mpu::pulley.PlanMove(config::cutLength, config::pulleyUnloadFeedrate); // + mpu::pulley.PlanMove(config::cutLength, mg::globals.PulleyUnloadFeedrate_mm_s()); // } break; case ProgressCode::PushingFilament: diff --git a/src/logic/feed_to_bondtech.cpp b/src/logic/feed_to_bondtech.cpp index ac5eb19..fc740ab 100644 --- a/src/logic/feed_to_bondtech.cpp +++ b/src/logic/feed_to_bondtech.cpp @@ -24,9 +24,7 @@ void logic::FeedToBondtech::GoToPushToNozzle() { mg::globals.SetFilamentLoaded(mg::globals.ActiveSlot(), mg::FilamentLoadState::InFSensor); // plan a slow move to help push filament into the nozzle //@@TODO the speed in mm/s must correspond to printer's feeding speed! - mpu::pulley.PlanMove( - config::U_mm({ (long double)mg::globals.FSensorToNozzleMM() }), - config::U_mm_s({ (long double)mg::globals.FSensorToNozzleFeedrate() })); + mpu::pulley.PlanMove(mg::globals.FSensorToNozzle_mm(), mg::globals.PulleySlowFeedrate_mm_s()); state = PushingFilamentIntoNozzle; } @@ -39,9 +37,13 @@ bool FeedToBondtech::Step() { state = PushingFilamentFast; mpu::pulley.InitAxis(); // plan a fast move while in the safe minimal length - mpu::pulley.PlanMove(config::minimumBowdenLength, config::pulleyLoadFeedrate, config::pulleySlowFeedrate); + mpu::pulley.PlanMove(config::minimumBowdenLength, + mg::globals.PulleyLoadFeedrate_mm_s(), + mg::globals.PulleySlowFeedrate_mm_s()); // plan additional slow move while waiting for fsensor to trigger - mpu::pulley.PlanMove(config::maximumBowdenLength - config::minimumBowdenLength, config::pulleySlowFeedrate, config::pulleySlowFeedrate); + mpu::pulley.PlanMove(config::maximumBowdenLength - config::minimumBowdenLength, + mg::globals.PulleySlowFeedrate_mm_s(), + mg::globals.PulleySlowFeedrate_mm_s()); } return false; case PushingFilamentFast: diff --git a/src/logic/retract_from_finda.cpp b/src/logic/retract_from_finda.cpp index 65fa94a..ef1c8dd 100644 --- a/src/logic/retract_from_finda.cpp +++ b/src/logic/retract_from_finda.cpp @@ -23,7 +23,7 @@ bool RetractFromFinda::Step() { if (mi::idler.Engaged()) { dbg_logic_fP(PSTR("Pulley start steps %u"), mpu::pulley.CurrentPosition_mm()); state = UnloadBackToPTFE; - mpu::pulley.PlanMove(-(config::cuttingEdgeToFindaMidpoint + config::cuttingEdgeRetract), config::pulleyUnloadFeedrate); + mpu::pulley.PlanMove(-(config::cuttingEdgeToFindaMidpoint + config::cuttingEdgeRetract), mg::globals.PulleyUnloadFeedrate_mm_s()); } return false; case UnloadBackToPTFE: diff --git a/src/logic/unload_to_finda.cpp b/src/logic/unload_to_finda.cpp index 3b058e0..f24754b 100644 --- a/src/logic/unload_to_finda.cpp +++ b/src/logic/unload_to_finda.cpp @@ -39,12 +39,12 @@ bool UnloadToFinda::Step() { state = WaitingForFINDA; mg::globals.SetFilamentLoaded(mg::globals.ActiveSlot(), mg::FilamentLoadState::InSelector); unloadStart_mm = mpu::pulley.CurrentPosition_mm(); - mpu::pulley.PlanMove(-config::defaultBowdenLength - config::feedToFinda - config::filamentMinLoadedToMMU, config::pulleyUnloadFeedrate); + mpu::pulley.PlanMove(-config::defaultBowdenLength - config::feedToFinda - config::filamentMinLoadedToMMU, mg::globals.PulleyUnloadFeedrate_mm_s()); } return false; case WaitingForFINDA: { int32_t currentPulley_mm = mpu::pulley.CurrentPosition_mm(); - if ((abs(unloadStart_mm - currentPulley_mm) > mm::truncatedUnit(config::fsensorUnloadCheckDistance)) && mfs::fsensor.Pressed()) { + if ((abs(unloadStart_mm - currentPulley_mm) > mm::truncatedUnit(mg::globals.FSensorUnloadCheck_mm())) && mfs::fsensor.Pressed()) { // fsensor didn't trigger within the first fsensorUnloadCheckDistance mm -> stop pulling, something failed, report an error // This scenario should not be tried again - repeating it may cause more damage to filament + potentially more collateral damage state = FailedFSensor; diff --git a/src/modules/globals.cpp b/src/modules/globals.cpp index 7cb0eb9..9360467 100644 --- a/src/modules/globals.cpp +++ b/src/modules/globals.cpp @@ -21,8 +21,16 @@ void Globals::Init() { filamentLoaded = FilamentLoadState::AtPulley; } - ResetFSensorToNozzleMM(); - ResetFSensorToNozzleFeedrate(); + ResetFSensorToNozzle(); + ResetFSensorUnloadCheck(); + + ResetPulleyLoadFeedrate(); + ResetPulleySlowFeedrate(); + ResetPulleyUnloadFeedrate(); + + ResetSelectorFeedrate(); + + ResetIdlerFeedrate(); } uint8_t Globals::ActiveSlot() const { @@ -68,13 +76,5 @@ void Globals::SetMotorsMode(bool stealth) { // @@TODO store into EEPROM } -void Globals::ResetFSensorToNozzleMM() { - fsensorToNozzleMM = config::fsensorToNozzle.v; -} - -void Globals::ResetFSensorToNozzleFeedrate() { - fsensorToNozzleFeedrate = config::pulleySlowFeedrate.v; -} - } // namespace globals } // namespace modules diff --git a/src/modules/globals.h b/src/modules/globals.h index 7fbfb4a..1062a27 100644 --- a/src/modules/globals.h +++ b/src/modules/globals.h @@ -1,6 +1,7 @@ /// @file globals.h #pragma once #include +#include "../config/config.h" namespace modules { @@ -61,13 +62,33 @@ public: /// @returns true if the motors are to be operated in stealth mode bool MotorsStealth() const { return stealthMode; } - uint8_t FSensorToNozzleMM() const { return fsensorToNozzleMM; } - void ResetFSensorToNozzleMM(); - void SetFSensorToNozzleMM(uint8_t fss2NozzleMM) { fsensorToNozzleMM = fss2NozzleMM; } + config::U_mm FSensorToNozzle_mm() const { return config::U_mm({ (long double)fsensorToNozzle_mm }); } + void ResetFSensorToNozzle() { fsensorToNozzle_mm = config::fsensorToNozzle.v; } + void SetFSensorToNozzle_mm(uint8_t fss2Nozzle_mm) { fsensorToNozzle_mm = fss2Nozzle_mm; } - uint8_t FSensorToNozzleFeedrate() const { return fsensorToNozzleFeedrate; } - void ResetFSensorToNozzleFeedrate(); - void SetFSensorToNozzleFeedrate(uint8_t fs2NozzleFeedrate) { fsensorToNozzleFeedrate = fs2NozzleFeedrate; } + config::U_mm FSensorUnloadCheck_mm() const { return config::U_mm({ (long double)fsensorUnloadCheck_mm }); } + void ResetFSensorUnloadCheck() { fsensorUnloadCheck_mm = config::fsensorUnloadCheckDistance.v; } + void SetFSensorUnloadCheck_mm(uint8_t fs2UnlCheck_mm) { fsensorUnloadCheck_mm = fs2UnlCheck_mm; } + + config::U_mm_s PulleyLoadFeedrate_mm_s() const { return config::U_mm_s({ (long double)pulleyLoadFeedrate_mm_s }); } + void ResetPulleyLoadFeedrate() { pulleyLoadFeedrate_mm_s = config::pulleyLoadFeedrate.v; } + void SetPulleyLoadFeedrate_mm_s(uint16_t pulleyLoadFR_mm_s) { pulleyLoadFeedrate_mm_s = pulleyLoadFR_mm_s; } + + config::U_mm_s PulleySlowFeedrate_mm_s() const { return config::U_mm_s({ (long double)pulleySlowFeedrate_mm_s }); } + void ResetPulleySlowFeedrate() { pulleySlowFeedrate_mm_s = config::pulleySlowFeedrate.v; } + void SetPulleySlowFeedrate_mm_s(uint16_t pulleySlowFR_mm_s) { pulleySlowFeedrate_mm_s = pulleySlowFR_mm_s; } + + config::U_mm_s PulleyUnloadFeedrate_mm_s() const { return config::U_mm_s({ (long double)pulleyUnloadFeedrate_mm_s }); } + void ResetPulleyUnloadFeedrate() { pulleyUnloadFeedrate_mm_s = config::pulleyUnloadFeedrate.v; } + void SetPulleyUnloadFeedrate_mm_s(uint16_t pulleyUnloadFR_mm_s) { pulleyUnloadFeedrate_mm_s = pulleyUnloadFR_mm_s; } + + config::U_mm_s SelectorFeedrate_mm_s() const { return config::U_mm_s({ (long double)selectorFeedrate_mm_s }); } + void ResetSelectorFeedrate() { selectorFeedrate_mm_s = config::selectorFeedrate.v; } + void SetSelectorFeedrate_mm_s(uint16_t selectorFR_mm_s) { selectorFeedrate_mm_s = selectorFR_mm_s; } + + config::U_deg_s IdlerFeedrate_deg_s() const { return config::U_deg_s({ (long double)idlerFeedrate_deg_s }); } + void ResetIdlerFeedrate() { idlerFeedrate_deg_s = config::idlerFeedrate.v; } + void SetIdlerFeedrate_deg_s(uint16_t idlerFR_deg_s) { idlerFeedrate_deg_s = idlerFR_deg_s; } private: /// Sets the active slot, usually after some command/operation. @@ -78,8 +99,16 @@ private: uint8_t activeSlot; FilamentLoadState filamentLoaded; bool stealthMode; - uint8_t fsensorToNozzleMM; - uint8_t fsensorToNozzleFeedrate; + uint8_t fsensorToNozzle_mm; + uint8_t fsensorUnloadCheck_mm; + + uint16_t pulleyLoadFeedrate_mm_s; + uint16_t pulleySlowFeedrate_mm_s; + uint16_t pulleyUnloadFeedrate_mm_s; + + uint16_t selectorFeedrate_mm_s; + + uint16_t idlerFeedrate_deg_s; }; /// The one and only instance of global state variables diff --git a/src/modules/idler.cpp b/src/modules/idler.cpp index 29be930..dab4cb3 100644 --- a/src/modules/idler.cpp +++ b/src/modules/idler.cpp @@ -15,12 +15,13 @@ Idler idler; void Idler::PrepareMoveToPlannedSlot() { mm::motion.PlanMoveTo( plannedMove == Operation::engage ? SlotPosition(plannedSlot) : IntermediateSlotPosition(plannedSlot), - mm::unitToAxisUnit(config::idlerFeedrate)); + mm::unitToAxisUnit(mg::globals.IdlerFeedrate_deg_s())); dbg_logic_fP(PSTR("Prepare Move Idler slot %d"), plannedSlot); } void Idler::PlanHomingMoveForward() { - mm::motion.PlanMove(mm::unitToAxisUnit(config::idlerLimits.lenght * 2), mm::unitToAxisUnit(config::idlerHomingFeedrate)); + mm::motion.PlanMove(mm::unitToAxisUnit(config::idlerLimits.lenght * 2), + mm::unitToAxisUnit(config::idlerHomingFeedrate)); dbg_logic_P(PSTR("Plan Homing Idler Forward")); } @@ -28,7 +29,8 @@ void Idler::PlanHomingMoveBack() { // we expect that we are at the front end of the axis, set the expected axis' position mm::motion.SetPosition(mm::Idler, mm::unitToSteps(config::idlerLimits.lenght)); axisStart = mm::axisUnitToTruncatedUnit(mm::motion.CurPosition()); - mm::motion.PlanMove(mm::unitToAxisUnit(-config::idlerLimits.lenght * 2), mm::unitToAxisUnit(config::idlerHomingFeedrate)); + mm::motion.PlanMove(mm::unitToAxisUnit(-config::idlerLimits.lenght * 2), + mm::unitToAxisUnit(config::idlerHomingFeedrate)); dbg_logic_P(PSTR("Plan Homing Idler Back")); } diff --git a/src/modules/selector.cpp b/src/modules/selector.cpp index cceb713..79a6a60 100644 --- a/src/modules/selector.cpp +++ b/src/modules/selector.cpp @@ -15,7 +15,7 @@ namespace selector { Selector selector; void Selector::PrepareMoveToPlannedSlot() { - mm::motion.PlanMoveTo(SlotPosition(plannedSlot), mm::unitToAxisUnit(config::selectorFeedrate)); + mm::motion.PlanMoveTo(SlotPosition(plannedSlot), mm::unitToAxisUnit(mg::globals.SelectorFeedrate_mm_s())); dbg_logic_fP(PSTR("Prepare Move Selector slot %d"), plannedSlot); } @@ -28,7 +28,8 @@ void Selector::PlanHomingMoveBack() { // we expect that we are at the front end of the axis, set the expected axis' position mm::motion.SetPosition(mm::Selector, 0); axisStart = mm::axisUnitToTruncatedUnit(mm::motion.CurPosition()); - mm::motion.PlanMove(mm::unitToAxisUnit(config::selectorLimits.lenght * 2), mm::unitToAxisUnit(config::selectorHomingFeedrate)); + mm::motion.PlanMove(mm::unitToAxisUnit(config::selectorLimits.lenght * 2), + mm::unitToAxisUnit(mg::globals.SelectorFeedrate_mm_s())); dbg_logic_P(PSTR("Plan Homing Selector Back")); } @@ -101,7 +102,8 @@ bool Selector::Step() { if (mi::idler.HomingValid()) { // idler is ok, we can start homing the selector state = HomeForward; - mm::motion.PlanMove(mm::unitToAxisUnit(-config::selectorLimits.lenght * 2), mm::unitToAxisUnit(config::selectorHomingFeedrate)); + mm::motion.PlanMove(mm::unitToAxisUnit(-config::selectorLimits.lenght * 2), + mm::unitToAxisUnit(mg::globals.SelectorFeedrate_mm_s())); } return false; case HomeForward: diff --git a/src/registers.cpp b/src/registers.cpp index 88ac1c8..22fff3e 100644 --- a/src/registers.cpp +++ b/src/registers.cpp @@ -11,6 +11,8 @@ #include "modules/finda.h" #include "modules/fsensor.h" #include "modules/globals.h" +#include "modules/idler.h" +#include "modules/selector.h" struct RegisterFlags { uint8_t writable : 1; @@ -29,6 +31,9 @@ struct RegisterFlags { using TReadFunc = uint16_t (*)(); using TWriteFunc = void (*)(uint16_t); +// dummy zero register common to all empty registers +static constexpr uint16_t dummyZero = 0; + struct RegisterRec { RegisterFlags flags; union U1 { @@ -63,6 +68,11 @@ struct RegisterRec { : flags(RegisterFlags(true, true, bytes)) , A1(readFunc) , A2(writeFunc) {} + + constexpr RegisterRec() + : flags(RegisterFlags(false, false, 1)) + , A1((void *)&dummyZero) + , A2((void *)nullptr) {} }; // @@TODO it is nice to see all the supported registers at one spot, @@ -71,51 +81,140 @@ struct RegisterRec { // @@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. +// +// Note: +// The lambas seem to be pretty cheap: +// void SetFSensorToNozzleFeedrate(uint8_t fs2NozzleFeedrate) { fsensorToNozzleFeedrate = fs2NozzleFeedrate; } +// compiles to: +// sts , r24 +// ret static const RegisterRec registers[] PROGMEM = { + // 0x00 RegisterRec(false, &project_major), - + // 0x01 RegisterRec(false, &project_minor), - + // 0x02 + RegisterRec(false, &project_revision), + // 0x03 RegisterRec(false, &project_build_number), - + // 0x04 RegisterRec( // MMU errors []() -> uint16_t { return mg::globals.DriveErrors(); }, [](uint16_t) {}, // @@TODO think about setting/clearing the error counter from the outside 2), - + // 0x05 RegisterRec([]() -> uint16_t { return application.CurrentProgressCode(); }, 1), - + // 0x06 RegisterRec([]() -> uint16_t { return application.CurrentErrorCode(); }, 2), - - RegisterRec( // filamentState + // 0x07 filamentState + RegisterRec( []() -> uint16_t { return mg::globals.FilamentLoaded(); }, [](uint16_t v) { return mg::globals.SetFilamentLoaded(mg::globals.ActiveSlot(), static_cast(v)); }, 1), - - RegisterRec( // FINDA + // 0x08 FINDA + RegisterRec( []() -> uint16_t { return static_cast(mf::finda.Pressed()); }, 1), - - RegisterRec( // fsensor + // 09 fsensor + RegisterRec( []() -> uint16_t { return static_cast(mfs::fsensor.Pressed()); }, [](uint16_t v) { return mfs::fsensor.ProcessMessage(v != 0); }, 1), + // 0xa motor mode (stealth = 1/normal = 0) + RegisterRec([]() -> uint16_t { return static_cast(mg::globals.MotorsStealth()); }, 1), + // 0xb extra load distance after fsensor triggered (30mm default) + RegisterRec( + []() -> uint16_t { return mg::globals.FSensorToNozzle_mm().v; }, + [](uint16_t d) { mg::globals.SetFSensorToNozzle_mm(d); }, + 1), + // 0x0c fsensor unload check distance (40mm default) + RegisterRec( + []() -> uint16_t { return mg::globals.FSensorUnloadCheck_mm().v; }, + [](uint16_t d) { mg::globals.SetFSensorUnloadCheck_mm(d); }, + 1), + // 0xd empty register - one empty register takes 5 bytes of code + RegisterRec(), + // 0xe + RegisterRec(), + // 0xf + RegisterRec(), - RegisterRec([]() -> uint16_t { return static_cast(mg::globals.MotorsStealth()); }, 1), // mode (stealth = 1/normal = 0) + // Pulley + // 0x10 Pulley acceleration RW + RegisterRec( + []() -> uint16_t { return config::pulleyLimits.accel.v; }, + //@@TODO + 1), + // 0x11 2 Pulley fast load feedrate RW + RegisterRec( + []() -> uint16_t { return mg::globals.PulleyLoadFeedrate_mm_s().v; }, + [](uint16_t d) { mg::globals.SetPulleyLoadFeedrate_mm_s(d); }, + 2), + // 0x12 2 Pulley slow load to fsensor feedrate RW + RegisterRec( + []() -> uint16_t { return mg::globals.PulleySlowFeedrate_mm_s().v; }, + [](uint16_t d) { mg::globals.SetPulleySlowFeedrate_mm_s(d); }, + 2), + // 0x13 2 Pulley unload feedrate RW + RegisterRec( + []() -> uint16_t { return mg::globals.PulleyUnloadFeedrate_mm_s().v; }, + [](uint16_t d) { mg::globals.SetPulleyUnloadFeedrate_mm_s(d); }, + 2), + RegisterRec(), // 14 + RegisterRec(), // 15 + RegisterRec(), // 16 + RegisterRec(), // 17 + RegisterRec(), // 18 + RegisterRec(), // 19 + RegisterRec(), // 1a + RegisterRec(), // 1b + RegisterRec(), // 1c + RegisterRec(), // 1d + RegisterRec(), // 1e + RegisterRec(), // 1f - RegisterRec( // extra load distance after fsensor triggered (30mm default) - []() -> uint16_t { return mg::globals.FSensorToNozzleMM(); }, - [](uint16_t d) { mg::globals.SetFSensorToNozzleMM(d); }, + // Selector + //20 2 Selector acceleration RW + RegisterRec( + []() -> uint16_t { return config::selectorLimits.accel.v; }, + //@@TODO + 1), + //21 2 Selector nominal speed RW + RegisterRec( + []() -> uint16_t { return mg::globals.SelectorFeedrate_mm_s().v; }, + [](uint16_t d) { mg::globals.SetSelectorFeedrate_mm_s(d); }, + 2), + //22 2 Selector sg_thrs RW + RegisterRec( + []() -> uint16_t { return config::selector.sg_thrs; }, + //@@TODO + 1), + //23 1 Set/Get Selector slot RW + RegisterRec( + []() -> uint16_t { return ms::selector.Slot(); }, + [](uint16_t d) { ms::selector.MoveToSlot(d); }, 1), - // The lambas seem to be pretty cheap: - // void SetFSensorToNozzleFeedrate(uint8_t fs2NozzleFeedrate) { fsensorToNozzleFeedrate = fs2NozzleFeedrate; } - // compiles to: - // sts , 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); }, + // Idler + //30 2 Idler acceleration RW + RegisterRec( + []() -> uint16_t { return config::idlerLimits.accel.v; }, + //@@TODO + 1), + //31 2 Idler nominal speed RW + RegisterRec( + []() -> uint16_t { return mg::globals.IdlerFeedrate_deg_s().v; }, + [](uint16_t d) { mg::globals.SetIdlerFeedrate_deg_s(d); }, + 1), + //32 2 Idler sg_thrs RW + RegisterRec( + []() -> uint16_t { return config::idler.sg_thrs; }, + //@@TODO + 1), + //33 1 Set/Get Idler slot RW + RegisterRec( + []() -> uint16_t { return mi::idler.Slot(); }, + // [](uint16_t d) { mi::idler.MoveToSlot(d); }, // @@TODO can be theoretically done as well 1), }; diff --git a/src/version.c b/src/version.c index 5c44d65..d73f7ce 100644 --- a/src/version.c +++ b/src/version.c @@ -14,4 +14,5 @@ const char project_version_suffix_short[] = STR(FW_VERSION_SUFFIX_SHORT); const uint8_t project_major = project_version_major; const uint8_t project_minor = project_version_minor; -const uint16_t project_build_number = FW_BUILD_NUMBER; +const uint16_t project_revision = project_version_revision; +const uint16_t project_build_number = project_version_build; diff --git a/src/version.h b/src/version.h index 8be11c3..429d7e1 100644 --- a/src/version.h +++ b/src/version.h @@ -9,6 +9,7 @@ extern "C" { #define project_version_major 2 #define project_version_minor 0 #define project_version_revision 19 +#define project_version_build 634 #define FW_BUILD_NUMBER 0 /// Project's version (2.0.0) @@ -29,6 +30,9 @@ extern const uint8_t project_major; /// Project's minor version extern const uint8_t project_minor; +/// Project's revision number +extern const uint16_t project_revision; + /// Project's build number (number of commits in a branch) extern const uint16_t project_build_number;