Introduces a nasty hack to forcefully write into the constexpr SPI descriptor's registers
(which is the correct way in ASM, but kind of cumbersome in C++ now)
Please note Windows CRLF sequence is not supported,
but a separate CR xor a separate LF works now.
This is a workaround for stupid terminals for debugging purposes,
and it is not necessary for the protocol to work on its own.
It may be removed in the future.
- Move unit/step conversion to modules/axisunit.h
- Unify motion::unitToAxisUnit<> and motion::unitToSteps<>,
making conversion in other modules just as easy as motion.
- Improve the documentation
Introduces:
- config::Unit: base class for physical quantities
- motion::AxisUnit: type-checked steps type
"config/unit.h" defines basic physical quantities, which are not
normally used elsewhere besides config.h.
"modules/axisunit.h" extends the modules::motion namespace with
Axis-aware units, with one type per axis per unit.
P_pos_t defines step positions for the pulley, I_pos_t for the idler,
etc. These are defined through the literar operators which are
similarly named and automatically convert a physical quantity to an
AxisUnit at compile time:
P_pos_t pulley_pos = 10.0_P_mm;
Besides type-checking, AxisUnit are otherwise identical to raw step
counts and are intended to be used along with the updated Motion API.
PlanMove/PlanMoveTo has been extended to support moves using these units
or physical quantities. Again, conversion is performed at compile time.
At least for min/max this will ensure types for both arguments are the
same.
This should be a little bit closer to the <cmath> definition that
simply overloads these functions instead.
This matches PulseGen::Position() and avoids confusion around the term
"current": Position() returns the head position in the queue, not the
"live" axis position.
We have PulseGen::CurPosition() now for this purpose, although we don't
expose it to Motion yet.
CurPosition() returns the live axis position, which in this
implementation is inherently expensive to compute.
This shouldn't be required for the MMU, but it /will/ come in handy to
check for the axis position/s in Motion tests.
Make ReadPin return the last value set by WritePin for proper testing.
Add a slow-path to TogglePin that goes through a read-write cycle. This
is useful both for testing and for platforms that don't have an
efficient toggle like AVR.
The cast to Level is incorrect, since the expression returns either 0 or
a positive value if the pin is set. The value is directly assigned to
the underlying uint8_t, meaning that Level::high won't match for levels
greater than 0.
Return a boolean and cast that to Level instead.
Instead of adding #ifdefs for missing headers, create a shim that can be
included everywhere.
"limits.h" includes a bare-bone implementation of numeric_limits (to be
extended as needed).
Motion::Full() (without a specific axis) is counter-productive.
When planning new moves the axis needs to be known beforehand, so it
might be as well be given to Full() to check the proper queue.
Implement Motion::SetEnabled (for symmetry with TMC2130::SetEnabled).
Rename DisableAxis to Disable and use the new SetEnabled. This makes the
member names more consistent.
Make ReadPin return the last value set by WritePin for proper testing.
Add a slow-path to TogglePin that goes through a read-write cycle. This
is useful both for testing and for platforms that don't have an
efficient toggle like AVR.
The cast to Level is incorrect, since the expression returns either 0 or
a positive value if the pin is set. The value is directly assigned to
the underlying uint8_t, meaning that Level::high won't match for levels
greater than 0.
Return a boolean and cast that to Level instead.
We might want to schedule new moves while a single motor is moving.
Allow to do that by introducing per-axis query functions.
The main QueueEmpty() and Full() still function as before:
- Call QueueEmpty() to wait for all moves to finish.
- Use !Full() to know that a Plan() move will never be discarded.
Since scheduling a move on a block which is being executed will jolt the
motors, be extra-safe and perform an extra lower-level check before
committing even if the caller is responsible.
Return the status, which can be useful to build a simple busy loop.
- Remove the combined PlanMove(a,b,c,rate) call. If we allow the units
of the various motors to be changed at compile time, the unit of
rate can vary between axes.
- Build PlanMove on top of the absolute PlanMoveTo.
- Add required stubs for TMC2130.
- Allow each axis mode to be set independently, since we have this
feature for free anyway.
- Rework internals to use PulseGen data types and structs.
To generate optimal code, size itself needs to be of the same type as
the index to avoid promotion to the largest type.
In full(), wrap the subtraction explicity in another type cast to avoid
another automatic type promotion.
The empty/full distinction fails to work if size matches the number of
representable positions for the index type.
Add a static check to ensure this invariant is met where possible.
Comparing head/tail indexes cannot distinguish between empty/full cases,
so we end up wasting one item in the circular buffer. This also limits
the smallest and efficient size choice to be 4.
If the circular buffer is large, there's no issue, however for the
motion planner the block size is significant, and I was planning to use
exactly a ring buffer of two: one busy block, plus one planned.
Modify the indexer to store the internal "index" (aka cursor) pointer to
be one extra bit deeper than the final index. Comparing the underlying
cursor allow to distinguish the empty/full case due to the extra bit,
while producing the final index requires simple masking.
This is just as efficient if the size is a power of two with
2-complement wrap-around logic, which is the optimized case. However
the implementation also works for non-power-of-two sizes.
Add tests for more failure cases in the CircularBuffer which is built on
top.
(tecnique described in the Art of Computer Programming by Knuth)
Allow the lower-level index to be used without an actual container by
splitting off the index management into CircularIndex.
Rebuild CircularBuffer using CircularIndex itself.
Remove the constructor from GPIO_pin so that we can use brace
initialization at compile time.
Rewrite the contents of pins.h to construct GPIO_pin types directly by
the use of a simple preprocessor macro.
Makes the code type-check and easier to read/extend.
To keep these methods efficient no member should be accessed away.
That's the reason MotorParams is passed explicitly for each.
Mark all stepping methods as static.
Change the TMC2130 class after starting to review the Motion API.
Move motor mode handling and stepping to this module.
Create a new MotorParams class for compile-time settings (currently
pins, direction, microstepping).
Keep other settings separate, so that we can pass constexpr expressions
while stepping and still get 1 instruction without overhead.
+ start shaping up main.cpp
+ make the usage of namespaces and class names more consistent throughout the whole project
+ refactor related unit tests accordingly