Stepdance Software Library
Loading...
Searching...
No Matches
interfaces.hpp
1#include "WString.h"
2#include <sys/_stdint.h>
3#include "arm_math.h"
4#include <stdint.h>
5#include "Stream.h"
6#include "Arduino.h"
7#include "core.hpp"
8#include "interpolators.hpp"
9#include <map>
10#include <string>
11
12#ifndef interfaces_h //prevent importing twice
13#define interfaces_h
14
15// Null Serial Port
16class NullSerial : public Stream {
17public:
18 void begin(unsigned long) {}
19 void end() {}
20
21 int available() override { return 0; }
22 int read() override { return -1; }
23 int peek() override { return -1; }
24
25 // Print/Stream writes go through Print::write(uint8_t)
26 size_t write(uint8_t) override { return 1; }
27 using Print::write; // enables write(buffer, size)
28
29 void flush() override {}
30
31 operator bool() const { return true; }
32};
33
34/*
35EiBotBoard Interface Module of the StepDance Control System
36
37This module provides an input interface that emulates the EiBotBoard, which allows standard AxiDraw workflows
38to provide direct input. It is anticipated that this interface will feed a standard downstream motion generation synthesizer.
39
40[More Details to be Added]
41
42A part of the Mixing Metaphors Project
43(c) 2025 Ilan Moyer, Jennifer Jacobs, Devon Frost
44*/
45#define EBB_COMMAND_SIZE 2 //the command portion of the input block is up to two characters long
46#define EBB_MAX_NUM_INPUT_PARAMETERS 10 //maximum allowable number of parameters in an input string
47#define EBB_EXECUTE_IMMEDIATE 0 //command to be executed on receipt, if no other command is pending
48#define EBB_EXECUTE_EMERGENCY 1 //command to be executed on receipt, regardless of whether a command is pending
49#define EBB_EXECUTE_TO_QUEUE 2 //command to be added to the queue
50#define EBB_BLOCK_PENDING 1 //block is pending
51
52#define EBB_SERVO_MAX_POSITION_STEPS 500 // +500us from neutral position (1500us pulse width)
53#define EBB_SERVO_MIN_POSITION_STEPS -500 // -500us from neutral position (1500us pulse width)
54#define EBB_SERVO_MIDPOINT_PULSE_DURATION_US 1500 //pulse duration for the servo midpoint.
55
65class Eibotboard : public Plugin{
66 public:
67 Eibotboard();
71 void begin();
76 void begin(usb_serial_class *target_usb_serial);
82 void set_ratio_xy(float output_units_mm, float input_units_steps); //sets the xy conversion between steps and mm
88 void set_ratio_z(float output_units_mm, float input_units_steps); //sets the z conversion between steps and mm
89
93 BlockPort& output_x = target_interpolator.output_x;
97 BlockPort& output_y = target_interpolator.output_y;
101 BlockPort& output_z = target_interpolator.output_z;
105 BlockPort& output_e = target_interpolator.output_e;
106 BlockPort& output_r = target_interpolator.output_r;
107 BlockPort& output_t = target_interpolator.output_t;
109
114 BlockPort& output_parameter = target_interpolator.output_parameter;
115
121 BlockPort& output_duration = target_interpolator.output_duration;
122
123
124
125 protected:
126 void loop(); // should be run inside loop
127
128 private:
129 // Interpolator
130 TimeBasedInterpolator target_interpolator;
131
132 // Serial Debug State
133 uint8_t debug_port_identified; //1 if debug port has been ID'd, otherwise 0
134 Stream *ebb_serial_port; //stores a pointer to the ebb serial port
135 Stream *debug_serial_port; //pointer to the debug port
136 NullSerial SerialNone; //can be used by interfaces to silence a debug port.
137
138 float32_t xy_conversion_mm_per_step = 25.4 / 2874.0; //AxiDraw standard conversion
139 float32_t z_conversion_mm_per_step = 1.0 / 50.0; //50 steps per mm of travel
140
141 // Command Processing
142 void process_character(uint8_t character);
143 void reset_input_buffer(); //resets the input buffer state
144 void process_command(uint16_t command_value);
145 void process_string_int32(); //processes the block string (after the command word) into the input_parameters array.
146 static void initialize_all_commands_struct(); //initializes the all_commands struct by pre-calculating the command values.
147 struct command{
148 char command_string[EBB_COMMAND_SIZE + 1]; //two-character string
149 uint16_t command_value; //the command string, converted into a command value during initialization.
150 void (Eibotboard::*command_function)(); //pointer to the command function to execute when this command value shows up.
151 uint8_t execution; //0 -- immediate execution, 1 -- emergency execution, 2 -- add to queue
152 };
153 char input_buffer[255]; //pre-allocate a string buffer to store the serial input stream.
154 uint8_t input_buffer_write_index; //stores the current index of the write buffer
155 uint8_t input_state; //tracks the current state of the input process
156 uint16_t input_command_value; //tracks the value of the current command
157 int32_t input_parameters[EBB_MAX_NUM_INPUT_PARAMETERS]; // parameters that have been parsed from the input string
158 uint8_t num_input_parameters; // number of parameters in the string
159
160 // Block Generation
161 uint16_t block_id = 0; //stores the current block ID, which simply increments each time a new motion-containing block is received
162 TimeBasedInterpolator::motion_block pending_block; //stores motion block information that is pending being added to the queue
163 uint8_t block_pending_flag = 0; //1 if a block is pending addition to the queue
164 uint8_t debug_buffer_full_flag = 0; //1 if already sent a debug message
165 void (Eibotboard::*pending_block_function)(); //pointer to the command function whose block is pending
166
167 // Servo State
168 int32_t servo_position_steps = 0; //tracks the current servo position. Unlike other moves, this one is provided in absolute coordinates.
169 int32_t servo_pen_up_position_steps = 0; //absolute position of servo when the pen is up
170 int32_t servo_pen_down_position_steps = 0; //absolute position of the servo when the pen is down
171 float servo_rate_up_steps_per_sec = 100; //slew rate of the pen up in steps/sec
172 float servo_rate_down_steps_per_sec = 100; //slew rate of the pen down in steps/sec
173
174 // -- COMMANDS --
175 static struct command all_commands[]; //stores all available commands
176 void command_query_current(); //'QC' returns the supply voltage and motor currents
177 void command_query_button(); //'QB' returns whether the PRG button has been pressed
178 void command_query_variable(); //'QL' returns the value of a variable stored in memory
179 void command_query_pen(); //'QL' returns the pen state
180 void command_stepper_servo_configure(); //'SC' configures the stepper and servo motors
181 void command_stepper_move(); //'SM' moves the stepper motors
182 void command_set_pen(); //'SP' sets pen position
183 void command_version(); //'V' returns the version of the EBB Board
184 void command_generic(); //simply returns an OK
185
186 // -- UTILITY FUNCTIONS --
187 static void set_servo_position(uint16_t pulse_duration_83_3_ns, int32_t *servo_position_register); //sets a servo position register based on the pulse duration in increments of 83.3nS
188 static void set_servo_rate(uint16_t pulse_rate_us_per_ms, float *servo_rate_register); //sets a servo rate register, see S2 command for units
189 void debug_report_pending_block(bool waiting_for_slot); // outputs a report on the pending block to the debug port
190
191};
192
201class GCodeInterface : public Plugin{
202 public:
203 GCodeInterface();
208 void begin(); //defaults to Serial as the input stream
209 void begin(Stream *target_stream);
224 void begin(usb_serial_class *target_usb_serial);
225 void begin(HardwareSerialIMXRT *target_serial, uint32_t baud, uint16_t format = 0); //hardware serial
227 //BlockPorts
231 BlockPort& output_x = target_interpolator.output_x;
235 BlockPort& output_y = target_interpolator.output_y;
239 BlockPort& output_z = target_interpolator.output_z;
243 BlockPort& output_e = target_interpolator.output_e;
248 BlockPort& output_r = target_interpolator.output_r;
249 BlockPort& output_t = target_interpolator.output_t;
251
256 BlockPort& output_parameter = target_interpolator.output_parameter;
257
263 BlockPort& output_duration = target_interpolator.output_duration;
264
265
266 protected:
267 void loop();
268
269 private:
270
271 // ---- COMMUNICATIONS ---
272 Stream *gcode_stream; //serial port etc
273
274 enum {
275 RECEIVER_READY, //waiting for a new line to read
276 RECEIVER_READING, //reading a line
277 RECEIVER_PROCESSING, //processing a line
278 RECEIVER_BLOCKED //a block is stuck in the inbound block buffer. Only process asynchronous control commands (e.g. ?)
279 };
280
281 uint8_t receiver_state = RECEIVER_READY;
282
283 // 1. Receiving Block Lines
284 // We assume that each line contains at most one block, and that a newline represents the end of a block
285 char input_line_buffer[255]; //pre-allocate a string buffer to store the serial input stream.
286 uint8_t input_line_buffer_index; //stores the next position to write to in the input_line_buffer.
287 void reset_input_line_buffer(); //resets the input line buffer.
288 bool process_character(uint8_t character);
289
290 // 2. Tokenize Block
291 const char *REALTIME_LETTERS = "\x18?~!";
292 const char *TOKEN_LETTERS = "GMXYZEABCSTHDFPN$="; //a string containing all token letters. Most are G-code except for $ and =.
293 static const uint8_t MAX_NUM_TOKENS = 10; //support up to 10 phrases in the incoming block
294 static const uint8_t MAX_TOKEN_SIZE = 15; //max characters in a given token. For example, "E110292.6186" is 12 characters.
295 struct token{ //stores a gcode phrase in the incoming block
296 char token_string[MAX_TOKEN_SIZE + 1]; //up to 15 characters.
297 };
298
299
300 struct block{
301 struct token tokens[MAX_NUM_TOKENS]; //all tokens in the block
302 int num_tokens = 0;
303 int key_token_index = -1; //index of the "key" token (i.e. G or M) for the block.
304 uint8_t execution; //context for execution (e.g. immediate, queue, interpolator)
305 void (GCodeInterface::*code_function)(); //pointer to the command function to execute when this command value shows up.
306 };
307
308 struct block inbound_block; //stores an inbound block
309 bool tokenize_block(); //places the input line buffer into inbound_block. Returns true if block has tokens and at least one is a key token.
310
311 // 3. Pre-Process Block
312 enum{
313 EXECUTE_NOW, //runs the target function on receipt
314 EXECUTE_QUEUE, //runs the target function in the block queue, when the interpolator is idle
315 EXECUTE_INTERPOLATOR, //runs the target function on the interpolator
316 };
317
318 struct code{
319 char code_string[MAX_TOKEN_SIZE + 1]; //e.g. G0, G1, M82
320 void (GCodeInterface::*code_function)(); //pointer to the command function to execute when this code shows up.
321 uint8_t execution;
322 };
323
324 static struct code all_codes[]; //stores all available codes. This is defined in the .cpp file
325
326 bool preprocess_block(); //determines the target function for the block, and the execution scope. Returns true if the block matches to an existant code
327 int8_t find_code(char *code_string); //returns the index of the code in all_codes, or -1 if not found
328
329 // 4. Dispatch
330 bool dispatch_block(); //dispatches the inbound_block
331
332 // 5. Queue
333 static const uint8_t BLOCK_QUEUE_SIZE = 6;
334 uint8_t block_queue_read_index = 0; //next block to read
335 uint8_t block_queue_write_index = 0; //next block to write
336 uint8_t block_queue_slots_remaining = BLOCK_QUEUE_SIZE;
337 bool queue_block(); //queues the inbound block. Returns true if queue was successful
338 bool queue_is_empty();
339 struct block* pull_block();
340 struct block block_queue[BLOCK_QUEUE_SIZE];
341 void advance_head(uint8_t* target_head);
342 void reset_block_queue();
343 uint8_t next_block_execution();
344
345 // 6. Execution
346 std::map<String, DecimalPosition> execution_tokens;
347 void execute_block(block *target_block);
348 void load_tokens(block *target_block); //loads tokens into the execution_tokens map.
349
350 // 7. Responses
351 enum{
352 ERROR_NO_KEY, //GRBL 1
353 ERROR_BAD_FORMAT, //GRBL 2
354 ERROR_UNSUPPORTED_CODE, //GRBL 20
355 ERROR_NO_FEED_RATE //GRBL 22
356 };
357
358 void send_ok();
359 void send_error(uint8_t error_type);
360
361 // GCode Commands
362 void g0_rapid();
363 void g1_move();
364 void g4_dwell();
365
366 // system commands
367 void _help();
368 void _report_parameters();
369 void _soft_reset();
370 void _status_report();
371 void _cycle_start();
372 void _feed_hold();
373 void _parser_state();
374
375 // 8. "Realtime Commands"
376 void execute_realtime(char command);
377
378 // Interpolator
379 TimeBasedInterpolator target_interpolator;
380
381 // Execution State
382 struct TimeBasedInterpolator::position machine_position; //interpreter machine positional state
383 float64_t modal_feedrate_mm_per_min = 0; // sets the current feedrate, which persists across blocks
384};
385
386#endif
BlockPorts provide a unified interface for mapping inputs and outputs of different StepDance componen...
Definition core.hpp:148
Enables using Axidraw commands from over serial as motion stream input.
Definition interfaces.hpp:65
BlockPort & output_parameter
Output BlockPort for the generated position signal of a parameter in [0, 1] range....
Definition interfaces.hpp:114
BlockPort & output_z
BlockPort for Z axis output. Use this to map to downstream components to drive position based on the ...
Definition interfaces.hpp:101
void begin()
Initialize the EiBotBoard interface. This must be called to set up the interface. Default communicati...
void begin(usb_serial_class *target_usb_serial)
Initialize the EiBotBoard interface with a specified communication interface.
BlockPort & output_x
BlockPort for X axis output. Use this to map to downstream components to drive position based on the ...
Definition interfaces.hpp:93
BlockPort & output_y
BlockPort for Y axis output. Use this to map to downstream components to drive position based on the ...
Definition interfaces.hpp:97
void set_ratio_xy(float output_units_mm, float input_units_steps)
Set the conversion ratio between XY steps and millimeters.
BlockPort & output_duration
Output BlockPort for the duration of the active move. Indicates the time the entire move is scheduled...
Definition interfaces.hpp:121
void set_ratio_z(float output_units_mm, float input_units_steps)
Set the conversion ratio between Z steps and millimeters.
BlockPort & output_parameter
Output BlockPort for the generated position signal of a parameter in [0, 1] range....
Definition interfaces.hpp:256
BlockPort & output_e
BlockPort for extruder output. Use this to map to downstream components to drive position based on G-...
Definition interfaces.hpp:243
BlockPort & output_z
BlockPort for Z axis output. Use this to map to downstream components to drive position based on G-co...
Definition interfaces.hpp:239
BlockPort & output_duration
Output BlockPort for the duration of the active move. Indicates the time the entire move is scheduled...
Definition interfaces.hpp:263
BlockPort & output_y
BlockPort for Y axis output. Use this to map to downstream components to drive position based on G-co...
Definition interfaces.hpp:235
BlockPort & output_x
Initialize the G-code interface with a USB serial port.
Definition interfaces.hpp:231
Definition interfaces.hpp:16
Enables scheduling a sequence of pre-planned motions towards given coordinate points,...
Definition interpolators.hpp:41