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
110 protected:
111 void loop(); // should be run inside loop
112
113 private:
114 // Interpolator
115 TimeBasedInterpolator target_interpolator;
116
117 // Serial Debug State
118 uint8_t debug_port_identified; //1 if debug port has been ID'd, otherwise 0
119 Stream *ebb_serial_port; //stores a pointer to the ebb serial port
120 Stream *debug_serial_port; //pointer to the debug port
121 NullSerial SerialNone; //can be used by interfaces to silence a debug port.
122
123 float32_t xy_conversion_mm_per_step = 25.4 / 2874.0; //AxiDraw standard conversion
124 float32_t z_conversion_mm_per_step = 1.0 / 50.0; //50 steps per mm of travel
125
126 // Command Processing
127 void process_character(uint8_t character);
128 void reset_input_buffer(); //resets the input buffer state
129 void process_command(uint16_t command_value);
130 void process_string_int32(); //processes the block string (after the command word) into the input_parameters array.
131 static void initialize_all_commands_struct(); //initializes the all_commands struct by pre-calculating the command values.
132 struct command{
133 char command_string[EBB_COMMAND_SIZE + 1]; //two-character string
134 uint16_t command_value; //the command string, converted into a command value during initialization.
135 void (Eibotboard::*command_function)(); //pointer to the command function to execute when this command value shows up.
136 uint8_t execution; //0 -- immediate execution, 1 -- emergency execution, 2 -- add to queue
137 };
138 char input_buffer[255]; //pre-allocate a string buffer to store the serial input stream.
139 uint8_t input_buffer_write_index; //stores the current index of the write buffer
140 uint8_t input_state; //tracks the current state of the input process
141 uint16_t input_command_value; //tracks the value of the current command
142 int32_t input_parameters[EBB_MAX_NUM_INPUT_PARAMETERS]; // parameters that have been parsed from the input string
143 uint8_t num_input_parameters; // number of parameters in the string
144
145 // Block Generation
146 uint16_t block_id = 0; //stores the current block ID, which simply increments each time a new motion-containing block is received
147 TimeBasedInterpolator::motion_block pending_block; //stores motion block information that is pending being added to the queue
148 uint8_t block_pending_flag = 0; //1 if a block is pending addition to the queue
149 uint8_t debug_buffer_full_flag = 0; //1 if already sent a debug message
150 void (Eibotboard::*pending_block_function)(); //pointer to the command function whose block is pending
151
152 // Servo State
153 int32_t servo_position_steps = 0; //tracks the current servo position. Unlike other moves, this one is provided in absolute coordinates.
154 int32_t servo_pen_up_position_steps = 0; //absolute position of servo when the pen is up
155 int32_t servo_pen_down_position_steps = 0; //absolute position of the servo when the pen is down
156 float servo_rate_up_steps_per_sec = 100; //slew rate of the pen up in steps/sec
157 float servo_rate_down_steps_per_sec = 100; //slew rate of the pen down in steps/sec
158
159 // -- COMMANDS --
160 static struct command all_commands[]; //stores all available commands
161 void command_query_current(); //'QC' returns the supply voltage and motor currents
162 void command_query_button(); //'QB' returns whether the PRG button has been pressed
163 void command_query_variable(); //'QL' returns the value of a variable stored in memory
164 void command_query_pen(); //'QL' returns the pen state
165 void command_stepper_servo_configure(); //'SC' configures the stepper and servo motors
166 void command_stepper_move(); //'SM' moves the stepper motors
167 void command_set_pen(); //'SP' sets pen position
168 void command_version(); //'V' returns the version of the EBB Board
169 void command_generic(); //simply returns an OK
170
171 // -- UTILITY FUNCTIONS --
172 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
173 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
174 void debug_report_pending_block(bool waiting_for_slot); // outputs a report on the pending block to the debug port
175
176};
177
186class GCodeInterface : public Plugin{
187 public:
188 GCodeInterface();
193 void begin(); //defaults to Serial as the input stream
194 void begin(Stream *target_stream);
209 void begin(usb_serial_class *target_usb_serial);
210 void begin(HardwareSerialIMXRT *target_serial, uint32_t baud, uint16_t format = 0); //hardware serial
212 //BlockPorts
216 BlockPort& output_x = target_interpolator.output_x;
220 BlockPort& output_y = target_interpolator.output_y;
224 BlockPort& output_z = target_interpolator.output_z;
228 BlockPort& output_e = target_interpolator.output_e;
233 BlockPort& output_r = target_interpolator.output_r;
234 BlockPort& output_t = target_interpolator.output_t;
236 protected:
237 void loop();
238
239 private:
240
241 // ---- COMMUNICATIONS ---
242 Stream *gcode_stream; //serial port etc
243
244 enum {
245 RECEIVER_READY, //waiting for a new line to read
246 RECEIVER_READING, //reading a line
247 RECEIVER_PROCESSING, //processing a line
248 RECEIVER_BLOCKED //a block is stuck in the inbound block buffer. Only process asynchronous control commands (e.g. ?)
249 };
250
251 uint8_t receiver_state = RECEIVER_READY;
252
253 // 1. Receiving Block Lines
254 // We assume that each line contains at most one block, and that a newline represents the end of a block
255 char input_line_buffer[255]; //pre-allocate a string buffer to store the serial input stream.
256 uint8_t input_line_buffer_index; //stores the next position to write to in the input_line_buffer.
257 void reset_input_line_buffer(); //resets the input line buffer.
258 bool process_character(uint8_t character);
259
260 // 2. Tokenize Block
261 const char *REALTIME_LETTERS = "\x18?~!";
262 const char *TOKEN_LETTERS = "GMXYZEABCSTHDFPN$="; //a string containing all token letters. Most are G-code except for $ and =.
263 static const uint8_t MAX_NUM_TOKENS = 10; //support up to 10 phrases in the incoming block
264 static const uint8_t MAX_TOKEN_SIZE = 15; //max characters in a given token. For example, "E110292.6186" is 12 characters.
265 struct token{ //stores a gcode phrase in the incoming block
266 char token_string[MAX_TOKEN_SIZE + 1]; //up to 15 characters.
267 };
268
269
270 struct block{
271 struct token tokens[MAX_NUM_TOKENS]; //all tokens in the block
272 int num_tokens = 0;
273 int key_token_index = -1; //index of the "key" token (i.e. G or M) for the block.
274 uint8_t execution; //context for execution (e.g. immediate, queue, interpolator)
275 void (GCodeInterface::*code_function)(); //pointer to the command function to execute when this command value shows up.
276 };
277
278 struct block inbound_block; //stores an inbound block
279 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.
280
281 // 3. Pre-Process Block
282 enum{
283 EXECUTE_NOW, //runs the target function on receipt
284 EXECUTE_QUEUE, //runs the target function in the block queue, when the interpolator is idle
285 EXECUTE_INTERPOLATOR, //runs the target function on the interpolator
286 };
287
288 struct code{
289 char code_string[MAX_TOKEN_SIZE + 1]; //e.g. G0, G1, M82
290 void (GCodeInterface::*code_function)(); //pointer to the command function to execute when this code shows up.
291 uint8_t execution;
292 };
293
294 static struct code all_codes[]; //stores all available codes. This is defined in the .cpp file
295
296 bool preprocess_block(); //determines the target function for the block, and the execution scope. Returns true if the block matches to an existant code
297 int8_t find_code(char *code_string); //returns the index of the code in all_codes, or -1 if not found
298
299 // 4. Dispatch
300 bool dispatch_block(); //dispatches the inbound_block
301
302 // 5. Queue
303 static const uint8_t BLOCK_QUEUE_SIZE = 6;
304 uint8_t block_queue_read_index = 0; //next block to read
305 uint8_t block_queue_write_index = 0; //next block to write
306 uint8_t block_queue_slots_remaining = BLOCK_QUEUE_SIZE;
307 bool queue_block(); //queues the inbound block. Returns true if queue was successful
308 bool queue_is_empty();
309 struct block* pull_block();
310 struct block block_queue[BLOCK_QUEUE_SIZE];
311 void advance_head(uint8_t* target_head);
312 void reset_block_queue();
313 uint8_t next_block_execution();
314
315 // 6. Execution
316 std::map<String, DecimalPosition> execution_tokens;
317 void execute_block(block *target_block);
318 void load_tokens(block *target_block); //loads tokens into the execution_tokens map.
319
320 // 7. Responses
321 enum{
322 ERROR_NO_KEY, //GRBL 1
323 ERROR_BAD_FORMAT, //GRBL 2
324 ERROR_UNSUPPORTED_CODE, //GRBL 20
325 ERROR_NO_FEED_RATE //GRBL 22
326 };
327
328 void send_ok();
329 void send_error(uint8_t error_type);
330
331 // GCode Commands
332 void g0_rapid();
333 void g1_move();
334 void g4_dwell();
335
336 // system commands
337 void _help();
338 void _report_parameters();
339 void _soft_reset();
340 void _status_report();
341 void _cycle_start();
342 void _feed_hold();
343 void _parser_state();
344
345 // 8. "Realtime Commands"
346 void execute_realtime(char command);
347
348 // Interpolator
349 TimeBasedInterpolator target_interpolator;
350
351 // Execution State
352 struct TimeBasedInterpolator::position machine_position; //interpreter machine positional state
353 float64_t modal_feedrate_mm_per_min = 0; // sets the current feedrate, which persists across blocks
354};
355
356#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_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.
void set_ratio_z(float output_units_mm, float input_units_steps)
Set the conversion ratio between Z steps and millimeters.
BlockPort & output_e
BlockPort for extruder output. Use this to map to downstream components to drive position based on G-...
Definition interfaces.hpp:228
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:224
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:220
BlockPort & output_x
Initialize the G-code interface with a USB serial port.
Definition interfaces.hpp:216
Definition interfaces.hpp:16
Definition interpolators.hpp:34
Definition interpolators.hpp:47