Stepdance Software Library
Loading...
Searching...
No Matches
core.hpp
1#include "WString.h"
2#include <sys/_stdint.h>
3#include <cstddef>
4#include <stdint.h>
5#include <functional>
6#include "arm_math.h"
7#include "Arduino.h"
8/*
9Core Module of the StepDance Control System
10
11This contains core system functions such as the frame interrupt timer.
12
13A part of the Mixing Metaphors Project
14(c) 2025 Ilan Moyer, Jennifer Jacobs, Devon Frost, Emilie Yu
15*/
16#ifndef core_h //prevent importing twice
17#define core_h
18
19class RPC; //forward declaration of RPC from rpc.hpp
20
21typedef volatile float64_t DecimalPosition; //used to store positions across the system. We are using double-precision to allow incremental moves with acceptable error (~0.05 steps/day at 25khz)
22typedef volatile int32_t IntegerPosition; //previously used to store positions
23typedef volatile float32_t ControlParameter; //controls plugin parameters, typically from an analog input value
24
25typedef void (*frame_function_pointer)(); //defines function pointers that can be called at each frame
26
27#define CORE_FRAME_PERIOD_US 40 //microseconds. This yields a max output step rate of 25k steps/sec.
28#define CORE_FRAME_PERIOD_S CORE_FRAME_PERIOD_US / 1000000 //duration of each frame in seconds
29#define CORE_FRAME_FREQ_HZ 1000000 / CORE_FRAME_PERIOD_US //framerate in Hz. This is 25k
30#define MAX_NUM_FRAME_FUNCTIONS 10 //maximum number of functions that can be called on the frame interrupt
31
32#define KILOHERTZ_PLUGIN_PERIOD_US 1000 //microseconds, for the kilohertz plugin timer
33
34// Signal Indices
35// This is ordered by pulse length.
36#define SIGNAL_X 0 //index of the X signal in the active_signal and signal_directions arrrays
37#define SIGNAL_Y 1
38#define SIGNAL_R 2 // Polar Radial Axis
39#define SIGNAL_T 3 // Polar Theta Axis
40#define SIGNAL_Z 4
41#define SIGNAL_E 5 // Extruder
42
43// Standard Step Ratio
44#define STANDARD_RATIO_MM 0.01 // world mm / steps. At 25KHz this provides a max velocity of 250mm/sec.
45#define STANDARD_RATIO_IN 0.0003937 // world inches / step
46
47// Plugin Execution Context
48#define PLUGIN_FRAME_PRE_CHANNEL 0 //runs on the frame, before channels are evaluated
49#define PLUGIN_FRAME_POST_CHANNEL 1 //runs on the frame, after channels are evaluated
50#define PLUGIN_KILOHERTZ 2 //runs in an independent 1khz context
51#define PLUGIN_LOOP 3 //runs in the main loop
52#define PLUGIN_INPUT_PORT 4 //runs on the frame, at the start before all other plugins
53
54// Position Mode
55// Throughout stepdance, there is a question of whether to operate incrementally or in absolute coordinates.
56// By default, we operate incrementally. But some modules require data to be in absolute values (e.g. non-linear functions)
57
58enum{
59 INCREMENTAL, //indicates a position is being provided incrementally
60 ABSOLUTE, //position should be interpreted as an absolute position, relative to the local state of the component
61 GLOBAL //position should be interpreted as absolute, relative to the global (e.g. channel) state of the machine
62};
63// #define INCREMENTAL 0 //data is handled incrementally
64// #define ABSOLUTE 1 //data is handled in absolute values
65
66#define MIN 0
67#define MAX 1
68
69enum{
70 BLOCKPORT_INPUT, //blockport is an input
71 BLOCKPORT_OUTPUT, //blockport is an output
72 BLOCKPORT_UNDEFINED //blockport is undefined
73};
74
75void add_function_to_frame(frame_function_pointer target_function);
76void dance_start();
77
78void stepdance_metrics_reset(); //resets the CPU usage metrics
79float stepdance_get_cpu_usage(); //returns a value from 0-1 indicating the maximum CPU usage.
80static volatile float stepdance_max_cpu_usage = 0; //stores a running count of the maximum CPU usage, in the range 0-1;
81static volatile uint32_t stepdance_interrupt_entry_cycle_count = 0; //stores the entry value of ARM_DWT_CYCCNT
82
83// -- Plug-In Base Class --
84//
85// This provides a common interface for any filters, synthesizers, kinematics, etc that need access to the core frame.
86#define MAX_NUM_INPUT_PORT_FRAME_PLUGINS 10 //plugins that execute at the start of the frame. This is reserved for input ports
87#define MAX_NUM_PRE_CHANNEL_FRAME_PLUGINS 10 //plugins that execute in the frame, before the channels are evaluated
88#define MAX_NUM_POST_CHANNEL_FRAME_PLUGINS 10 //plugins that execute in the frame, after the channels are evaluated
89#define MAX_NUM_KILOHERTZ_PLUGINS 10 //plugins that execute at a 1khz rate, independent of the frame, and with a lower priority
90#define MAX_NUM_LOOP_PLUGINS 10 //plugins that execute in the main loop.
95class Plugin{
96 // Base class for all plugins that need to run in the core frame.
97 public:
98 Plugin();
99 static void run_input_port_frame_plugins(); //runs all input port frame plugins.
100 static void run_pre_channel_frame_plugins(); //runs all pre-channel frame plugins, in the order they appear in the registered_plugins list
101 static void run_post_channel_frame_plugins(); //runs all post-channel frame plugins, in the order they appear in the registered_plugins list
102 static void run_kilohertz_plugins(); //runs all post-channel frame plugins, in the order they appear in the registered_plugins list
103 static void run_loop_plugins(); //runs all loop plugins, in the order they appear in the registered_plugins list
104
105 virtual void enable();
106 virtual void disable();
107 virtual void enroll(RPC *rpc, const String& instance_name); //enrolls the plugin in an RPC. This should be overridden by the derived class, and is responsible for enrolling any members.
108 virtual void push_deep(); //deep push across the plugin (e.g. from input to output blockports) for state sync.
109 virtual void pull_deep(); //performs a deep pull across the plugin (e.g. from output to input blockports) for state sync
110
111 private:
112 static Plugin* registered_input_port_frame_plugins[MAX_NUM_INPUT_PORT_FRAME_PLUGINS]; //stores all registered input port plugins
113 static Plugin* registered_pre_channel_frame_plugins[MAX_NUM_PRE_CHANNEL_FRAME_PLUGINS]; //stores all registered pre-channel frame plugins
114 static Plugin* registered_post_channel_frame_plugins[MAX_NUM_POST_CHANNEL_FRAME_PLUGINS]; //stores all registered post-channel frame plugins
115 static Plugin* registered_kilohertz_plugins[MAX_NUM_KILOHERTZ_PLUGINS]; //stores all registered kilohertz plugins
116 static Plugin* registered_loop_plugins[MAX_NUM_LOOP_PLUGINS]; //stores all registered loop plugins
117 static uint8_t num_registered_input_port_frame_plugins; //tracks the number of registered input port frame plugins
118 static uint8_t num_registered_pre_channel_frame_plugins; //tracks the number of registered pre-channel frame plugins
119 static uint8_t num_registered_post_channel_frame_plugins; //tracks the number of registered post-channel frame plugins
120 static uint8_t num_registered_kilohertz_plugins; //tracks the number of registered kilohertz plugins
121 static uint8_t num_registered_loop_plugins; //tracks the number of registered loop plugins
122
123 protected: //these need to be accessed from derived classes
124 void register_plugin(); //registers the plugin
125 void register_plugin(uint8_t execution_target); //registers the plugin
126 virtual void run(); //this should be overridden in the derived class. Runs each frame.
127 virtual void loop(); //this can be overridden in the derived class. Runs in the main loop context.
128};
130
131// -- BlockPort --
132// BlockPorts provide a unified interface into and out of component blocks (i.e. "blocks")
133// These are designed to be flexibly used depending on the component to which they belong.
134
146
147// Main BlockPort Class
149 public:
154
155 // -- User Functions -- these are called within user code, not (just) the library
161 void set_ratio(float world_units, float block_units = 1.0); // sets the ratio between world and block units, for automatic conversion. Default is 1.
162 // conversion always happens within the write/read functions when data enters and exits the BlockPort
168 void map(BlockPort *map_target, uint8_t mode); //maps this BlockPort's pipe to a target BlockPort
169 inline void map(BlockPort *map_target){
170 map(map_target, INCREMENTAL); //default internal mode is INCREMENTAL
171 }
172
173 // -- External Functions -- these are called outside the block that contains this BlockPort
178 float64_t read(uint8_t mode); // externally reads the BlockPort's target via the BlockPort buffers.
179 // an incremental read will reflect the change to the target, either pending or after the block has run.
180 // an absolute read will reflect the upcoming or last state of the target, depending on whether the block has run.
181 // In some cases this function can be overridden by a custom read function.
185 float64_t read_absolute(); // returns the absolute value of the BlockPort's target in world units.
186 // This is provided for simplified access via RPC.
187
192 void write(float64_t value, uint8_t mode); // writes to the BlockPort's absolute or incremental buffers
193
194 void write_now(float64_t); //writes directly to the target. REMEMBER TO UPDATE ABSOLUTE_BUFFER AT SAME TIME.
195 float64_t read_target(); //reads directly from the target
197
198 // -- Internal Functions -- called by the block containing this BlockPort
203 void begin(volatile float64_t *target, uint8_t direction = BLOCKPORT_UNDEFINED, Plugin *parent = nullptr); //initializes the BlockPort
204 void set_target(volatile float64_t *target); //sets a target variable for the BlockPort
205 void update(); //called by the block, to update the target and the buffers. Note that this does not handle pulling or pushing, which must be done first or after update.
206 void reverse_update(); //updates the buffers based on changes made by direct writes to the target. Used by input_ports, which run before all other blocks.
207 void set(float64_t value, uint8_t mode); //sets a new value for the target.
208 inline void set(float64_t value){ //default for set is ABSOLUTE
209 set(value, ABSOLUTE);
210 };
211 void reset(float64_t value, bool raw = false); //resets the target, and updates buffers to reflect new value WITHOUT an incremental update.
212
213 void push(uint8_t mode); // pushes this BlockPort's buffer state to a target.
214 inline void push(){
215 push(this->mode); //uses internal mode
216 }
217
218 void pull(uint8_t mode); // pulls a target BlockPort's buffer state into this BlockPort's buffers.
219 inline void pull(){
220 pull(this->mode); //uses internal mode
221 }
222
223 // State Synchronization Functions
224 // Internal
225 void push_deep(DecimalPosition abs_value); //Pushes an ABSOLUTE value across a mapping chain.
226 DecimalPosition pull_deep(); //pulls an ABSOLUTE value thru from the terminal of the mapping chain.
227
228 // User Facing
229 inline void reset_deep(DecimalPosition abs_value){
230 push_deep(abs_value);
231 }
232 inline DecimalPosition read_deep(){
233 return pull_deep();
234 }
235
236 void enable(); // enables push/pull on blockport
237 void disable(); // disables push/pull
238
239 volatile float64_t incremental_buffer = 0;
240 volatile float64_t absolute_buffer = 0; //contains a new value if absolute_buffer_is_written, otherwise the last value of the associated variable.
241
242 inline float64_t convert_block_to_world_units(float64_t block_units){
243 return block_units * world_to_block_ratio;
244 }
245 inline float64_t convert_world_to_block_units(float64_t world_units){
246 return world_units / world_to_block_ratio;
247 }
248
249 volatile float64_t* target = nullptr;
250
251 void enroll(RPC *rpc, const String& instance_name); //used to enroll the blockport in an RPC
253 private:
254 volatile bool update_has_run = false; //set to true when an update has run, and false when write() is called.
255 uint8_t mode = INCREMENTAL; //default mode used by push and pull, unless specified in that function call. This is set by the map function.
256 volatile uint8_t push_pull_enabled = true; //controlled by enable() and disable(). This enables/disables push and pull. NOTE: We could optimize by removing volatile,
257 // but then this couldn't be operated inside any interrupts incl. the kilohertz interrupt, which could be confusing.
258 // Can re-examine if we start running out of compute overhead.
259 float64_t world_to_block_ratio = 1;
260
261 BlockPort* target_BlockPort = nullptr;
262 Plugin* parent_Plugin = nullptr; //This should only be set on INPUTS.
263 uint8_t blockport_direction = BLOCKPORT_UNDEFINED; //direction is not explicitly set by the parent Plugin
264};
265
266
267// -- LOOP FUNCTION AND CLASSES --
268// These allow non-blocking functions to be called within the loop, at an approximately given frequency.
269
274void dance_loop();
275
276static volatile float stepdance_loop_time_ms; //tracks the time spent in the last loop
277static volatile uint32_t stepdance_loop_entry_cycle_count = 0; //cycle count when dance_loop was last called
278
279class LoopDelay{
280 public:
281 LoopDelay();
282 void periodic_call(void (*callback_function)(), float interval_ms);
283
284 private:
285 float time_since_last_call_ms; //stores time since the function was last called
286};
287
289#endif
float64_t read_absolute()
Returns the absolute position value of the BlockPort in world units.
float64_t read(uint8_t mode)
Returns the position value of the BlockPort in world units. Reading in INCREMENTAL mode returns the c...
void map(BlockPort *map_target, uint8_t mode)
Maps this BlockPort to a target BlockPort with a specified mode (INCREMENTAL or ABSOLUTE).
BlockPort()
Default constructor for BlockPort. Initializes a BlockPort instance. You do not need to call this dir...
void set_ratio(float world_units, float block_units=1.0)
Sets the ratio between world units and block units for this BlockPort for automatic conversion....
RPC class for handling remote procedure calls over serial streams.
Definition rpc.hpp:35