├── .gitattributes ├── .gitignore ├── COPYING ├── README.md ├── arm-driver ├── README.md ├── driver.c ├── eeprom.c ├── eeprom.h ├── serial.c └── serial.h ├── mcodes.c ├── mcodes.h └── my_plugin ├── MCU_load ├── CMakeLists.txt └── my_plugin.c ├── MQTT_example └── my_plugin.c ├── Macros_bound_to_aux_inputs ├── CMakeLists.txt └── my_plugin.c ├── Motor_power_monitor ├── CMakeLists.txt └── my_plugin.c ├── Pause_on_SD_file_run ├── CMakeLists.txt └── my_plugin.c ├── Persistent_tool ├── CMakeLists.txt └── my_plugin.c ├── Probe_select ├── CMakeLists.txt └── my_plugin.c ├── Probe_select2 ├── CMakeLists.txt └── my_plugin.c ├── README.md ├── Realtime_report_timestamp ├── CMakeLists.txt └── my_plugin.c ├── STM32F411_blinky └── my_plugin.c ├── Set_output_on_feed_hold ├── CMakeLists.txt └── my_plugin.c ├── Solenoid_spindle ├── CMakeLists.txt └── my_plugin.c ├── Stepper_enable_control ├── CMakeLists.txt └── my_plugin.c ├── hpgl ├── CMakeLists.txt ├── README.md ├── arc.c ├── arc.h ├── charset0.c ├── clip.c ├── font173.c ├── hpgl.c ├── hpgl.h ├── htext.c ├── htext.h ├── motori.c ├── motori.h ├── scale.c └── scale.h └── settings └── my_plugin.c /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto eol=lf 3 | 4 | 5 | # Custom for Visual Studio 6 | *.cs diff=csharp 7 | 8 | # Standard to msysgit 9 | *.doc diff=astextplain 10 | *.DOC diff=astextplain 11 | *.docx diff=astextplain 12 | *.DOCX diff=astextplain 13 | *.dot diff=astextplain 14 | *.DOT diff=astextplain 15 | *.pdf diff=astextplain 16 | *.PDF diff=astextplain 17 | *.rtf diff=astextplain 18 | *.RTF diff=astextplain 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Windows thumbnail cache files 2 | Thumbs.db 3 | ehthumbs.db 4 | ehthumbs_vista.db 5 | 6 | # Folder config file 7 | Desktop.ini 8 | 9 | # Recycle Bin used on file shares 10 | $RECYCLE.BIN/ 11 | 12 | # Windows Installer files 13 | *.cab 14 | *.msi 15 | *.msm 16 | *.msp 17 | 18 | # Windows shortcuts 19 | *.lnk 20 | 21 | # Subversion folders 22 | 23 | .svn 24 | 25 | # Build folder 26 | 27 | build 28 | 29 | # 30 | # ========================= 31 | # Operating System Files 32 | # ========================= 33 | 34 | .vscode/settings.json 35 | .vscode/c_cpp_properties.json 36 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## grblHAL templates 2 | 3 | Template code to aid writing custom code for extensions such as additional IO-pins, M-codes or plugins by publishing entry points in the HAL function pointer structs. 4 | 5 | * [ioports.c](./ioports.c) - for additional digital and/or analog outputs. For enabling M62 - M68 M-codes. 6 | 7 | * [mcodes.c](./mcodes.c) - for additional M-codes, includes example code. 8 | 9 | * [my_plugin](my_plugin) folder - contains some plugin examples. 10 | 11 | --- 12 | 2021-07-29 13 | -------------------------------------------------------------------------------- /arm-driver/README.md: -------------------------------------------------------------------------------- 1 | ## grblHAL templates, arm-driver 2 | 3 | This is a "bare bones" driver template. 4 | 5 | Function calls that are commented out and in upper case are pseudo code that needs to be replaced, possibly by reading/writing directly from/to registers. 6 | 7 | To make a functional driver MCU documentation has to be consulted and quite a bit of code added, mostly for peripheral configuration. It may also be helpful to peek at the existing drivers to see how they are implemented. 8 | 9 | Tip: I like to use the bit-band region for flexibility, and sometimes speed of execution, for GPIO pin access. 10 | 11 | --- 12 | 2019-12-29 13 | -------------------------------------------------------------------------------- /arm-driver/driver.c: -------------------------------------------------------------------------------- 1 | /* 2 | driver.c - An embedded CNC Controller with rs274/ngc (g-code) support 3 | 4 | Template driver code for ARM processors 5 | 6 | Part of grblHAL 7 | 8 | By Terje Io, public domain 9 | 10 | */ 11 | 12 | #include "eeprom.h" 13 | #include "serial.h" 14 | 15 | #include "grbl/hal.h" 16 | 17 | static bool pwmEnabled = false, IOInitDone = false; 18 | static axes_signals_t next_step_outbits; 19 | static spindle_pwm_t spindle_pwm; 20 | static delay_t delay = { .ms = 1, .callback = NULL }; // NOTE: initial ms set to 1 for "resetting" systick timer on startup 21 | 22 | // Inverts the probe pin state depending on user settings and probing cycle mode. 23 | static uint8_t probe_invert; 24 | 25 | static void spindle_set_speed (uint_fast16_t pwm_value); 26 | 27 | // Interrupt handler prototypes 28 | // Interrupt handlers needs to be registered, possibly by modifying a system specific startup file. 29 | // It is possible to relocate the interrupt dispatch table from flash to RAM and programatically attach handlers. 30 | // See the driver for SAMD21 for an example, relocation is done in the driver_init() function. 31 | // Also, if a MCU specific driver library is used this might have functions to programatically attach handlers. 32 | 33 | static void stepper_driver_isr (void); 34 | static void stepper_pulse_isr (void); 35 | static void stepper_pulse_isr_delayed (void); 36 | static void limit_isr (void); 37 | static void control_isr (void); 38 | static void systick_isr (void); 39 | 40 | 41 | // Millisecond resolution delay function 42 | // Will return immediately if a callback function is provided 43 | static void driver_delay_ms (uint32_t ms, delay_callback_ptr callback) 44 | { 45 | if(ms) { 46 | delay.ms = ms; 47 | // SYSTICK_ENABLE(); // Enable systick timer 48 | if(!(delay.callback = callback)) 49 | while(delay.ms); 50 | } else { 51 | if(delay.ms) { 52 | delay.callback = NULL; 53 | delay.ms = 1; 54 | } 55 | if(callback) 56 | callback(); 57 | } 58 | } 59 | 60 | 61 | // Set stepper pulse output pins. 62 | // step_outbits.value (or step_outbits.mask) are: bit0 -> X, bit1 -> Y... 63 | // Individual step bits can be accessed by step_outbits.x, step_outbits.y, ... 64 | inline static void set_step_outputs (axes_signals_t step_outbits) 65 | { 66 | step_outbits.value ^= settings.steppers.step_invert.mask; 67 | 68 | // GPIO_WRITE(STEP_PORT, step_outbits.value); // Output step bits. 69 | } 70 | 71 | 72 | // Set stepper direction ouput pins. 73 | // dir_outbits.value (or dir_outbits.mask) are: bit0 -> X, bit1 -> Y... 74 | // Individual direction bits can be accessed by dir_outbits.x, dir_outbits.y, ... 75 | inline static void set_dir_outputs (axes_signals_t dir_outbits) 76 | { 77 | dir_outbits.value ^= settings.steppers.dir_invert.mask; 78 | 79 | // GPIO_WRITE(STEP_PORT, dir_outbits.value); // Output direction bits. 80 | } 81 | 82 | 83 | // Enable steppers. 84 | // enable.value (or enable.mask) are: bit0 -> X, bit1 -> Y... 85 | // Individual enable bits can be accessed by enable.x, enable.y, ... 86 | // NOTE: if a common signal is used to enable all drivers enable.x should be used to set the signal. 87 | static void stepperEnable (axes_signals_t enable) 88 | { 89 | enable.value ^= settings.steppers.enable_invert.mask; 90 | 91 | // NOTE: many drivers are enabled by a active low signal 92 | // GPIO_WRITE(ENABLE_PORT, enable.value); // Output stepper enable bits. 93 | } 94 | 95 | // Starts stepper driver timer and forces a stepper driver interrupt callback. 96 | static void stepperWakeUp (void) 97 | { 98 | // Enable stepper drivers. 99 | stepperEnable((axes_signals_t){AXES_BITMASK}); 100 | 101 | // STEPPERTIMER_LOAD(500); // Load stepper timer with a sensible timeout value allowing stepper drivers time to wake up. 102 | // NOTE: timeout should be long enough to allow the IRQ handler time to update the load 103 | // value before it fires again. 104 | // STEPPERTIMER_IRQ_ENABLE(); // Enable stepper timer IRQ. 105 | // STEPPERTIMER_START(); // Start stepper timer. 106 | } 107 | 108 | // Disables stepper driver interrupts and reset outputs. 109 | static void stepperGoIdle (bool clear_signals) 110 | { 111 | // STEPPERTIMER_IRQ_DISABLE(); // Disable stepper timer IRQ. 112 | // STEPPERTIMER_STOP(); // Stop stepper timer. 113 | 114 | if(clear_signals) { 115 | set_step_outputs((axes_signals_t){0}); 116 | set_dir_outputs((axes_signals_t){0}); 117 | } 118 | } 119 | 120 | // Sets up stepper driver interrupt timeout. 121 | // Called at the start of each segment. 122 | // NOTE: If a 32-bit timer is used it is advisable to limit max step time to about 2 seconds 123 | // in order to avoid excessive delays on completion of motions 124 | // NOTE: If a 16 bit timer is used it may be neccesary to adjust the timer clock frequency (prescaler) 125 | // to cover the needed range. Refer to actual drivers for code examples. 126 | static void stepperCyclesPerTick (uint32_t cycles_per_tick) 127 | { 128 | // STEPPERTIMER_LOAD(cycles_per_tick); // Set the stepper timer timeout time. 129 | } 130 | 131 | 132 | // Start a stepper pulse, no delay version 133 | // stepper_t struct is defined in grbl/stepper.h 134 | static void stepperPulseStart (stepper_t *stepper) 135 | { 136 | if(stepper->new_block) { 137 | stepper->new_block = false; 138 | set_dir_outputs(stepper->dir_outbits); 139 | } 140 | 141 | if(stepper->step_outbits.value) { 142 | set_step_outputs(stepper->step_outbits); 143 | // STEPPULSETIMER_START(); // Start step pulse timer. 144 | } 145 | } 146 | 147 | 148 | // Start a stepper pulse, delay version 149 | // stepper_t struct is defined in grbl/stepper.h 150 | static void stepperPulseStartDelayed (stepper_t *stepper) 151 | { 152 | if(stepper->new_block) { 153 | stepper->new_block = false; 154 | set_dir_outputs(stepper->dir_outbits); 155 | } 156 | 157 | if(stepper->step_outbits.value) { 158 | next_step_outbits = stepper->step_outbits; // Store out_bits 159 | // STEPPULSETIMER_START(); // Start step pulse timer. 160 | } 161 | } 162 | 163 | 164 | // Enable/disable limit pins interrupt. 165 | // NOTE: the homing parameter is indended for configuring advanced 166 | // stepper drivers for sensorless homing. 167 | static void limitsEnable (bool on, bool homing) 168 | { 169 | // if (on && settings.limits.flags.hard_enabled) 170 | // GPIO_IRQ_ENABLE(LIMITS_PORT); // Enable limit pins change interrupts. 171 | // else 172 | // GPIO_IRQ_DISABLE(LIMITS_PORT); // Disable limit pins change interrupts. 173 | } 174 | 175 | // Returns limit state as an axes_signals_t bitmap variable. 176 | // signals.value (or signals.mask) are: bit0 -> X, bit1 -> Y... 177 | // Individual signals bits can be accessed by signals.x, signals.y, ... 178 | // Each bit indicates a limit signal, where triggered is 1 and not triggered is 0. 179 | // axes_signals_t is defined in grbl/nuts_bolts.h. 180 | inline static limit_signals_t limitsGetState() 181 | { 182 | limit_signals_t signals = {0}; 183 | 184 | signals.min.mask = settings.limits.invert.mask; 185 | 186 | // Read limits pins, either to a temp variable or directly to signals.vaue if no remapping is required. 187 | // Single bits may be also assigned directly by reading them via the bit band region. 188 | // signals.min.value = GPIO_READ(LIMITS_PORT); 189 | 190 | if (settings.limits.invert.mask) 191 | signals.min.value ^= settings.limits.invert.mask; 192 | 193 | return signals; 194 | } 195 | 196 | // Returns system state as a control_signals_t bitmap variable. 197 | // signals.value (or signals.mask) are: bit0 -> reset, bit1 -> feed_hold, ... 198 | // Individual enable bits can be accessed by signals.reset, signals.feed_hold, ... 199 | // Each bit indicates a control signal, where triggered is 1 and not triggered is 0. 200 | // axes_signals_t is defined in grbl/system.h. 201 | inline static control_signals_t systemGetState (void) 202 | { 203 | control_signals_t signals = {settings.control_invert.value}; 204 | 205 | // Read control signal pins, either to a temp variable or directly to signals.vaue if no remapping is required. 206 | // Single bits may be also assigned directly by reading them via the bit band region. 207 | // signals.value = GPIO_READ(CONTROL_PORT); 208 | 209 | if(settings.control_invert.value) 210 | signals.value ^= settings.control_invert.value; 211 | 212 | return signals; 213 | } 214 | 215 | // Sets up the probe pin invert mask to 216 | // appropriately set the pin logic according to setting for normal-high/normal-low operation 217 | // and the probing cycle modes for toward-workpiece/away-from-workpiece. 218 | static void probeConfigure (bool is_probe_away, bool probing) 219 | { 220 | probe_invert = settings.flags.invert_probe_pin ? 0 : PROBE_PIN; 221 | 222 | if (is_probe_away) 223 | probe_invert ^= PROBE_PIN; 224 | } 225 | 226 | // Returns the probe connected and triggered pin states. 227 | probe_state_t probeGetState (void) 228 | { 229 | probe_state_t state = { 230 | .connected = On 231 | }; 232 | 233 | // state.triggered = (GPIO_READ(PROBE_PORT, PROBE_PIN) ^ probe_invert) != 0; 234 | 235 | return state; 236 | } 237 | 238 | // Static spindle (off, on cw & on ccw) 239 | 240 | inline static void spindle_off (void) 241 | { 242 | // GPIO_WRITE(SPINDLE_PORT, SPINDLE_ENABLE_PIN) = settings.spindle.invert.on ? SPINDLE_ENABLE_PIN : 0; 243 | } 244 | 245 | inline static void spindle_on (void) 246 | { 247 | // GPIO_WRITE(SPINDLE_PORT, SPINDLE_ENABLE_PIN) = settings.spindle.invert.on ? 0 : SPINDLE_ENABLE_PIN); 248 | } 249 | 250 | inline static void spindle_dir (bool ccw) 251 | { 252 | // GPIO_WRITE(SPINDLE_PORT, SPINDLE_DIRECTION_PIN) = (ccw ^ settings.spindle.invert.ccw) ? SPINDLE_DIRECTION_PIN : 0); 253 | } 254 | 255 | 256 | // Start or stop spindle. 257 | static void spindleSetState (spindle_state_t state, float rpm) 258 | { 259 | if (!state.on) 260 | spindle_off(); 261 | else { 262 | if(hal.driver_cap.spindle_dir) 263 | spindle_dir(state.ccw); 264 | spindle_on(); 265 | } 266 | } 267 | 268 | // Variable spindle control functions 269 | 270 | // Set spindle speed. 271 | static void spindle_set_speed (uint_fast16_t pwm_value) 272 | { 273 | if (pwm_value == spindle_pwm.off_value) { 274 | if(settings.spindle.flags.pwm_action == SpindleAction_DisableWithZeroSPeed) 275 | spindle_off(); 276 | pwmEnabled = false; 277 | if(spindle_pwm.always_on) { 278 | // SPINDLEPWM_TIMER_SET(spindle_pwm.off_value); 279 | // SPINDLEPWM_TIMER_START(); 280 | } else { 281 | // SPINDLEPWM_TIMER_STOP(); 282 | // NOTE: code may be added to ensure spindle output are at the correct level. 283 | } 284 | } else { 285 | if(!pwmEnabled) { 286 | spindle_on(); 287 | pwmEnabled = true; 288 | } 289 | // SPINDLEPWM_TIMER_SET(pwm_value); 290 | // SPINDLEPWM_TIMER_START(); 291 | } 292 | } 293 | 294 | #ifdef SPINDLE_PWM_DIRECT 295 | 296 | // Convert spindle speed to PWM value. 297 | static uint_fast16_t spindleGetPWM (float rpm) 298 | { 299 | return spindle_compute_pwm_value(&spindle_pwm, rpm, false); 300 | } 301 | 302 | #else 303 | 304 | // Update spindle speed. 305 | static void spindleUpdateRPM (float rpm) 306 | { 307 | spindle_set_speed(spindle_compute_pwm_value(&spindle_pwm, rpm, false)); 308 | } 309 | 310 | #endif 311 | 312 | // Start or stop spindle. 313 | static void spindleSetStateVariable (spindle_state_t state, float rpm) 314 | { 315 | if (!state.on || rpm == 0.0f) { 316 | spindle_set_speed(spindle_pwm.off_value); 317 | spindle_off(); 318 | } else { 319 | if(hal.driver_cap.spindle_dir) 320 | spindle_dir(state.ccw); 321 | spindle_set_speed(spindle_compute_pwm_value(&spindle_pwm, rpm, false)); 322 | } 323 | } 324 | 325 | // Returns spindle state in a spindle_state_t variable. 326 | // spindle_state_t is defined in grbl/spindle_control.h 327 | static spindle_state_t spindleGetState (void) 328 | { 329 | spindle_state_t state = {0}; 330 | 331 | // state.on = GPIO_READ(SPINDLE_PORT, SPINDLE_ENABLE_PIN) != 0; 332 | // if(hal.driver_cap.spindle_dir) 333 | // state.ccw = GPIO_READ((SPINDLE_PORT, SPINDLE_DIRECTION_PIN) != 0; 334 | state.value ^= settings.spindle.invert.mask; 335 | if(pwmEnabled) 336 | state.on |= pwmEnabled; 337 | state.value ^= settings.spindle.invert.mask; 338 | 339 | return state; 340 | } 341 | 342 | // end spindle code 343 | 344 | // Start/stop coolant (and mist if enabled). 345 | // coolant_state_t is defined in grbl/coolant_control.h. 346 | static void coolantSetState (coolant_state_t mode) 347 | { 348 | mode.value ^= settings.coolant_invert.mask; 349 | 350 | // GPIO_WRITE(COOLANT_PORT, FLOOD_PIN) = mode.flood; // Set flood output according to mode.flood value: 0 is off, 1 is on 351 | // GPIO_WRITE(COOLANT_PORT, MIST_PIN) = mode.mist; // Set mist output according to mode.mist value: 0 is off, 1 is on 352 | } 353 | 354 | // Returns coolant state in a coolant_state_t variable. 355 | // coolant_state_t is defined in grbl/coolant_control.h. 356 | static coolant_state_t coolantGetState (void) 357 | { 358 | coolant_state_t state = {0}; 359 | 360 | // Read state back from GPIO pins 361 | // mode.flood = GPIO_READ(COOLANT_PORT, FLOOD_PIN); 362 | // mode.mist = GPIO_READ(COOLANT_PORT, MIST_PIN); 363 | 364 | state.value ^= settings.coolant_invert.mask; 365 | 366 | return state; 367 | } 368 | 369 | // Helper functions for setting/clearing/inverting individual bits atomically (uninterruptable) 370 | static void bitsSetAtomic (volatile uint_fast16_t *ptr, uint_fast16_t bits) 371 | { 372 | __disable_irq(); 373 | *ptr |= bits; 374 | __enable_irq(); 375 | } 376 | 377 | static uint_fast16_t bitsClearAtomic (volatile uint_fast16_t *ptr, uint_fast16_t bits) 378 | { 379 | __disable_irq(); 380 | uint_fast16_t prev = *ptr; 381 | *ptr &= ~bits; 382 | __enable_irq(); 383 | return prev; 384 | } 385 | 386 | static uint_fast16_t valueSetAtomic (volatile uint_fast16_t *ptr, uint_fast16_t value) 387 | { 388 | __disable_irq(); 389 | uint_fast16_t prev = *ptr; 390 | *ptr = value; 391 | __enable_irq(); 392 | return prev; 393 | } 394 | 395 | // Configures perhipherals when settings are initialized or changed 396 | static void settings_changed (settings_t *settings) 397 | { 398 | // 399 | hal.driver_cap.variable_spindle = spindle_precompute_pwm_values(&spindle_pwm, 120000000UL); 400 | 401 | if(IOInitDone) { 402 | 403 | stepperEnable(settings->steppers.deenergize); 404 | 405 | if(hal.driver_cap.variable_spindle) { 406 | // Set spindle PWM timer timeout value to spindle_pwm.period here 407 | hal.spindle.set_state = spindleSetStateVariable; 408 | } else 409 | hal.spindle.set_state = spindleSetState; 410 | 411 | // Stepper pulse timeout setup. 412 | // When the stepper pulse is delayed either two timers or a timer that supports multiple 413 | // compare registers is required. 414 | if(settings->steppers.pulse_delay_microseconds) { 415 | // Configure step pulse timer(s) for delayed pulse here. 416 | hal.stepper.pulse_start = stepperPulseStartDelayed; 417 | } else { 418 | // Configure step pulse timer for pulse off here. 419 | hal.stepper.pulse_start = stepperPulseStart; 420 | } 421 | 422 | /************************* 423 | * Control pins config * 424 | ************************/ 425 | 426 | // Configure pullup/pulldown and interrupt type (falling/rising edge) here. 427 | 428 | 429 | /*********************** 430 | * Limit pins config * 431 | ***********************/ 432 | 433 | // Configure pullup/pulldown and interrupt type (falling/rising edge) here. 434 | 435 | 436 | /******************** 437 | * Probe pin init * 438 | ********************/ 439 | 440 | // Configure pullup/pulldown here. 441 | } 442 | } 443 | 444 | // Initializes MCU peripherals for Grbl use 445 | static bool driver_setup (settings_t *settings) 446 | { 447 | // System init 448 | 449 | // Enable peripherals to use here (if required). 450 | 451 | /****************** 452 | * Stepper init * 453 | ******************/ 454 | 455 | // Configure stepper driver timer here. 456 | 457 | // Configure step pulse timer here. 458 | 459 | /**************************** 460 | * Software debounce init * 461 | ****************************/ 462 | 463 | if(hal.driver_cap.software_debounce) { 464 | // Configure software debounce here. Additional code in GPIO IRQ handlers is required. 465 | } 466 | 467 | /*********************** 468 | * Control pins init * 469 | ***********************/ 470 | 471 | // Configure control pins here. Pullup/pulldown and interrupt type is set up in the settings_changed() function. 472 | 473 | /********************* 474 | * Limit pins init * 475 | *********************/ 476 | 477 | // Configure limit pins here. Pullup/pulldown and interrupt type is set up in the settings_changed() function. 478 | 479 | /******************** 480 | * Probe pin init * 481 | ********************/ 482 | 483 | // Configure probe pin here. Pullup/pulldown is set up in the settings_changed() function. 484 | 485 | /*********************** 486 | * Coolant pins init * 487 | ***********************/ 488 | 489 | // Configure coolant pin(s) here. 490 | 491 | /****************** 492 | * Spindle init * 493 | ******************/ 494 | 495 | // Configure spindle pin(s) and spindle PWM timer here. 496 | 497 | // Set defaults 498 | 499 | IOInitDone = settings->version == 19; 500 | 501 | settings_changed(settings); 502 | 503 | hal.stepper.go_idle(true); 504 | hal.spindle.set_state((spindle_state_t){0}, 0.0f); 505 | hal.coolant.set_state((coolant_state_t){0}); 506 | 507 | return IOInitDone; 508 | } 509 | 510 | // Initialize HAL pointers, setup serial comms and enable EEPROM. 511 | // NOTE: the core is not yet configured (from EEPROM data), driver_setup() will be called when done. 512 | bool driver_init (void) 513 | { 514 | // If CMSIS is used this may be a good place to initialize the system. Comment out or remove if not available or done already. 515 | SystemInit(); 516 | 517 | // Set up systick timer with a 1ms period 518 | // and set SysTick IRQ to lowest priority. 519 | // NOTE: the following code assumes CMSIS is used, if not so this has to be changed. 520 | NVIC_SetPriority(SysTick_IRQn, (1 << __NVIC_PRIO_BITS) - 1); 521 | 522 | SysTick->LOAD = (SystemCoreClock / 1000) - 1; 523 | SysTick->VAL = 0; 524 | SysTick->CTRL |= SysTick_CTRL_CLKSOURCE_Msk|SysTick_CTRL_TICKINT_Msk; 525 | 526 | // end systick timer setup. 527 | 528 | // Enable EEPROM peripheral here if available. 529 | 530 | // Enable lazy stacking of FPU registers here if a FPU is available. 531 | 532 | hal.info = "my driver name"; // Typically set to MCU or board name 533 | hal.driver_version = "YYMMDD"; // Set to build date 534 | #ifdef BOARD_NAME 535 | hal.board = BOARD_NAME; 536 | #endif 537 | hal.driver_setup = driver_setup; 538 | hal.f_step_timer = SystemCoreClock; // NOTE: SystemCoreClock is a CMSIS definition, ... 539 | hal.rx_buffer_size = RX_BUFFER_SIZE; 540 | hal.delay_ms = driver_delay_ms; 541 | hal.settings_changed = settings_changed; 542 | 543 | hal.stepper.wake_up = stepperWakeUp; 544 | hal.stepper.go_idle = stepperGoIdle; 545 | hal.stepper.enable = stepperEnable; 546 | hal.stepper.cycles_per_tick = stepperCyclesPerTick; 547 | hal.stepper.pulse_start = stepperPulseStart; 548 | 549 | hal.limits.enable = limitsEnable; 550 | hal.limits.get_state = limitsGetState; 551 | 552 | hal.coolant.set_state = coolantSetState; 553 | hal.coolant.get_state = coolantGetState; 554 | 555 | hal.probe.get_state = probeGetState; 556 | hal.probe.configure = probeConfigure; 557 | 558 | hal.spindle.set_state = spindleSetState; 559 | hal.spindle.get_state = spindleGetState; 560 | #ifdef SPINDLE_PWM_DIRECT 561 | hal.spindle.get_pwm = spindleGetPWM; 562 | hal.spindle.update_pwm = spindle_set_speed; 563 | #else 564 | hal.spindle._update_rpm = spindleUpdateRPM; 565 | #endif 566 | 567 | hal.control.get_state = systemGetState; 568 | 569 | // Enable serial communication. 570 | const io_stream_t *serial_stream = serialInit(115200); 571 | 572 | memcpy(&hal.stream, serial_stream, offsetof(io_stream_t, enqueue_realtime_command)); 573 | 574 | // If EEPROM is available for settings storage uncomment the following line: 575 | 576 | // eeprom_init(); 577 | 578 | // end EEPROM available 579 | 580 | // Settings may also be stored in flash. 581 | // Quite a few drivers has code for that: SAMD21, SAM3X8E, ESP32, STM32F1xx and LPC1769 582 | // Check out the source for these for howto examples. 583 | // Note: many drivers has code examples for using external EEPROM, typically via I2C interface. 584 | 585 | hal.set_bits_atomic = bitsSetAtomic; 586 | hal.clear_bits_atomic = bitsClearAtomic; 587 | hal.set_value_atomic = valueSetAtomic; 588 | 589 | // Driver capabilities, used for announcing and negotiating (with the core) driver functionality. 590 | // See control_signals_t and driver_cap_t union i grbl/hal.h for available flags. 591 | 592 | #if SAFETY_DOOR_ENABLE 593 | hal.signals_cap.safety_door_ajar = On; 594 | #endif 595 | 596 | hal.driver_cap.spindle_dir = On; 597 | hal.driver_cap.variable_spindle = On; 598 | hal.driver_cap.mist_control = On; 599 | // hal.driver_cap.software_debounce = On; 600 | hal.driver_cap.step_pulse_delay = On; 601 | hal.driver_cap.amass_level = 3; 602 | hal.driver_cap.control_pull_up = On; 603 | hal.driver_cap.limits_pull_up = On; 604 | hal.driver_cap.probe_pull_up = On; 605 | 606 | my_plugin_init(); // Init any user supplied plugin (it defaults to a weak implementation). 607 | 608 | // No need to move version check before init. 609 | // Compiler will fail any signature mismatch for existing entries. 610 | return hal.version == 8; 611 | } 612 | 613 | /* interrupt handlers */ 614 | 615 | // Main stepper driver. 616 | static void stepper_driver_isr (void) 617 | { 618 | // STEPPERTIMER_IRQ_CLEAR(); // Clear stepper timer interrupt. 619 | hal.stepper.interrupt_callback(); 620 | } 621 | 622 | /* The Stepper Port Reset Interrupt: This interrupt handles the falling edge of the step 623 | pulse. This should always trigger before the next general stepper driver interrupt and independently 624 | finish, if stepper driver interrupts is disabled after completing a move. 625 | NOTE: Interrupt collisions between the serial and stepper interrupts can cause delays by 626 | a few microseconds, if they execute right before one another. Not a big deal, but can 627 | cause issues at high step rates if another high frequency asynchronous interrupt is 628 | added to Grbl. 629 | */ 630 | // This interrupt is enabled when Grbl sets the motor port bits to execute 631 | // a step. This ISR resets the motor port after a short period (settings.pulse_microseconds) 632 | // completing one step cycle. 633 | static void stepper_pulse_isr (void) 634 | { 635 | // STEPPULSETIMER_STOP(); // Stop step pulse timer. 636 | set_step_outputs((axes_signals_t){0}); 637 | } 638 | 639 | static void stepper_pulse_isr_delayed (void) 640 | { 641 | if(STEP_PULSE_ON) 642 | set_step_outputs(next_step_outbits); 643 | else { 644 | // STEPPULSETIMER_STOP(); // Stop step pulse timer. 645 | set_step_outputs((axes_signals_t){0}); 646 | } 647 | } 648 | 649 | // Limit pins ISR. 650 | static void limit_isr (void) 651 | { 652 | // GPIO_IRQ_CLEAR(LIMIT_PORT); 653 | hal.limits.interrupt_callback(limitsGetState()); 654 | } 655 | 656 | // Control pins ISR. 657 | static void control_isr (void) 658 | { 659 | // GPIO_IRQ_CLEAR(CONTROL_PORT); 660 | hal.control.interrupt_callback(systemGetState()); 661 | } 662 | 663 | // Interrupt handler for 1 ms interval timer 664 | static void systick_isr (void) 665 | { 666 | if(delay.ms && !(--delay.ms)) { 667 | // SYSTICK_DISABLE; // Disable systick timer 668 | if(delay.callback) { 669 | delay.callback(); 670 | delay.callback = NULL; 671 | } 672 | } 673 | } 674 | -------------------------------------------------------------------------------- /arm-driver/eeprom.c: -------------------------------------------------------------------------------- 1 | /* 2 | eeprom.c - An embedded CNC Controller with rs274/ngc (g-code) support 3 | 4 | Template driver code for ARM processors 5 | 6 | Part of GrblHAL 7 | 8 | By Terje Io, public domain 9 | 10 | */ 11 | 12 | #include "grbl/grbl.h" 13 | 14 | // NOTE: See driver for TM4C123 for additional code if EEPROM peripheral only support word (32-bit) access. 15 | // Also, many drivers has code for external EEPROM access, typically via I2C interface. 16 | 17 | // Read single byte from EEPROM. 18 | // addr is 0-based offset from start. 19 | static uint8_t getByte (uint32_t addr) 20 | { 21 | uint32_t data; 22 | 23 | // EEPROM_READ(addr); 24 | 25 | return data; 26 | 27 | } 28 | 29 | // Write single byte to EEPROM. 30 | // addr is 0-based offset from start. 31 | static void putByte (uint32_t addr, uint8_t new_value) 32 | { 33 | // EEPROM_WRITE(new_value); 34 | } 35 | 36 | // Read block of data from EEPROM, return true if checksum matches or no checksum used. 37 | // Checksum is stored in the byte following the last byte read from the block. 38 | static nvs_transfer_result_t readBlock (uint8_t *destination, uint32_t source, uint32_t size, bool with_checksum) 39 | { 40 | uint8_t *data = destination; 41 | 42 | for(; size > 0; size--) 43 | *data++ = getByte(source++); 44 | 45 | return with_checksum ? (calc_checksum(destination, size) == getByte(source + size) ? NVS_TransferResult_OK : NVS_TransferResult_Failed) : NVS_TransferResult_OK; 46 | } 47 | 48 | // Write block of data to EEPROM followed by an optional checksum byte. 49 | static nvs_transfer_result_t writeBlock (uint32_t destination, uint8_t *source, uint32_t size, bool with_checksum) 50 | { 51 | 52 | uint8_t *data = source; 53 | uint32_t remaining = size; 54 | 55 | for(; remaining > 0; remaining--) 56 | putByte(destination++, *data++); 57 | 58 | if(with_checksum) 59 | putByte(destination + size, calc_checksum(source, size)); 60 | } 61 | 62 | void eeprom_init (void) 63 | { 64 | hal.nvs.type = NVS_EEPROM; 65 | hal.nvs.get_byte = getByte; 66 | hal.nvs.put_byte = putByte; 67 | hal.nvs.memcpy_to_nvs = writeBlock; 68 | hal.nvs.memcpy_from_nvs = readBlock; 69 | } 70 | -------------------------------------------------------------------------------- /arm-driver/eeprom.h: -------------------------------------------------------------------------------- 1 | /* 2 | eeprom.h - An embedded CNC Controller with rs274/ngc (g-code) support 3 | 4 | Template driver code for ARM processors 5 | 6 | Part of GrblHAL 7 | 8 | By Terje Io, public domain 9 | 10 | */ 11 | 12 | #ifndef __EEPROM_H__ 13 | #define __EEPROM_H__ 14 | 15 | void eeprom_init (void); 16 | 17 | #endif 18 | -------------------------------------------------------------------------------- /arm-driver/serial.c: -------------------------------------------------------------------------------- 1 | /* 2 | serial.c - An embedded CNC Controller with rs274/ngc (g-code) support 3 | 4 | Template driver code for ARM processors 5 | 6 | Part of grblHAL 7 | 8 | By Terje Io, public domain 9 | 10 | */ 11 | 12 | #include "grbl/grbl.h" 13 | 14 | static stream_tx_buffer_t txbuffer = {0}; 15 | static stream_rx_buffer_t rxbuffer = {0}, rxbackup; 16 | 17 | // 18 | // serialGetC - returns -1 if no data available 19 | // 20 | static int16_t serialGetC (void) 21 | { 22 | int16_t data; 23 | uint_fast16_t bptr = rxbuffer.tail; 24 | 25 | if(bptr == rxbuffer.head) 26 | return -1; // no data available else EOF 27 | 28 | data = rxbuffer.data[bptr++]; // Get next character, increment tmp pointer 29 | rxbuffer.tail = bptr & (RX_BUFFER_SIZE - 1); // and update pointer 30 | 31 | return data; 32 | } 33 | 34 | // 35 | // Returns number of characters in serial input buffer 36 | // 37 | uint16_t serialRxFree (void) 38 | { 39 | return (RX_BUFFER_SIZE - 1) - serialRxCount(); 40 | } 41 | 42 | // 43 | // Flushes the serial input buffer. 44 | // NOTE: If the peripheral has an output FIFO that should be flushed as well. 45 | // 46 | static void serialRxFlush (void) 47 | { 48 | rxbuffer.tail = rxbuffer.head; 49 | rxbuffer.overflow = false; 50 | } 51 | // 52 | // Flushes and adds a CAN character to the serial input buffer 53 | // 54 | static void serialRxCancel (void) 55 | { 56 | serialRxFlush(); 57 | rxbuffer.data[rxbuffer.head] = ASCII_CAN; 58 | rxbuffer.head = (rxbuffer.tail + 1) & (RX_BUFFER_SIZE - 1); 59 | } 60 | 61 | // 62 | // Writes a character to the serial output stream. 63 | // 64 | static bool serialPutC (const char c) 65 | { 66 | uint_fast16_t next_head; 67 | 68 | // NOTE: If buffer and transmit register are empty buffering may be bypassed. 69 | // See actual drivers for examples. 70 | 71 | next_head = (txbuffer.head + 1) & (TX_BUFFER_SIZE - 1); // Get and update head pointer 72 | 73 | while(txbuffer.tail == next_head) { // Buffer full, block until space is available... 74 | if(!hal.stream_blocking_callback()) 75 | return false; 76 | } 77 | 78 | txbuffer.data[txbuffer.head] = c; // Add data to buffer 79 | txbuffer.head = next_head; // and update head pointer 80 | 81 | UART_TX_IRQ_ENABLE(); // Enable TX interrupts 82 | 83 | return true; 84 | } 85 | 86 | // 87 | // Writes a null terminated string to the serial output stream, blocks if buffer full 88 | // 89 | static void serialWriteS (const char *data) 90 | { 91 | char c, *ptr = (char *)data; 92 | 93 | while((c = *ptr++) != '\0') 94 | serialPutC(c); 95 | } 96 | 97 | // ******************************************** 98 | // Optional functions, not required by the core 99 | // ******************************************** 100 | 101 | // Some plugins will refuse to activate if not implemented. 102 | 103 | // 104 | // Writes a number of characters from a buffer to the serial output stream, blocks if buffer full 105 | // 106 | void serialWrite(const char *s, uint16_t length) 107 | { 108 | char *ptr = (char *)s; 109 | 110 | while(length--) 111 | serial2PutC(*ptr++); 112 | } 113 | 114 | // 115 | // Suspend or reading from the input buffer or restore backup copy of it. 116 | // Used by the manual tool change protocol. 117 | // 118 | static bool serialSuspendInput (bool suspend) 119 | { 120 | return stream_rx_suspend(&rxbuffer, suspend); 121 | } 122 | 123 | // 124 | // Returns number of characters in the input buffer. 125 | // 126 | static inline uint16_t serialRxCount (void) 127 | { 128 | uint_fast16_t head = rxbuffer.head, tail = rxbuffer.tail; 129 | 130 | return BUFCOUNT(head, tail, RX_BUFFER_SIZE); 131 | } 132 | 133 | // 134 | // Returns number of characters pending transmission. 135 | // 136 | uint16_t serialTxCount (void) { 137 | 138 | uint_fast16_t head = txbuffer.head, tail = txbuffer.tail; 139 | 140 | return BUFCOUNT(head, tail, TX_BUFFER_SIZE) /* + remaining bytes in any FIFO and/or transmit register */; 141 | } 142 | 143 | // 144 | // Flush the serial output buffer. 145 | // 146 | void serial2TxFlush (void) 147 | { 148 | // Disable TX interrupts here. 149 | // Flush caracters in any transmit FIFO too. 150 | txbuf2.tail = txbuf2.head; 151 | } 152 | 153 | // 154 | // Disable/enable reception. 155 | // 156 | static bool serialDisable (bool disable) 157 | { 158 | if(disable) 159 | // Disable RX interrupt. 160 | else 161 | // Enable RX interrupt. 162 | 163 | return true; 164 | } 165 | 166 | // 167 | // Sets the baud rate. 168 | // 169 | static bool serialSetBaudRate (uint32_t baud_rate) 170 | { 171 | 172 | return true; 173 | } 174 | 175 | // ********************** 176 | // End optional functions 177 | // ********************** 178 | 179 | // 180 | // Configure serial peripheral 181 | // 182 | const io_stream_t *serialInit (uint32_t baud_rate) 183 | { 184 | // Optional functions can be commented out, deleted or set to NULL if not implemented. 185 | static const io_stream_t stream = { 186 | .type = StreamType_Serial, 187 | .connected = true, 188 | .read = serialGetC, 189 | .write = serialWriteS, 190 | .write_all = serialWriteS, 191 | .get_rx_buffer_free = serialRxFree, 192 | .write_char = serialPutC, 193 | .reset_read_buffer = serialRxFlush, 194 | .cancel_read_buffer = serialRxCancel, 195 | .write_n = serialWrite, // Optional, required for Modbus plugin support. 196 | .get_rx_buffer_count = serialRxCount, // Optional, required for Modbus plugin support. 197 | .get_tx_buffer_count = serialTxCount, // Optional, required for Modbus plugin support. 198 | .reset_write_buffer = serialTxFlush, // Optional, required for Modbus plugin support. 199 | .suspend_read = serialSuspendInput, // Optional, required for manual tool change support. 200 | .disable = serialDisable, // Optional, recommended for some plugins. 201 | .set_baud_rate = serialSetBaudRate // Optional, required for Modbus and Bluetooth plugin support. 202 | }; 203 | 204 | // Add code to initialize peripheral here, including enabling RX interrupts. 205 | 206 | return &stream; 207 | } 208 | 209 | static void uart_interrupt_handler (void) 210 | { 211 | uint_fast16_t bptr; 212 | int32_t data; 213 | uint32_t iflags; 214 | 215 | // iflags = UART_GET_IRQSSTATE(); // Get UART interrupt flags. 216 | 217 | if(iflags & UART_IRQ_TX) { 218 | 219 | bptr = txbuffer.tail; 220 | 221 | if(txbuffer.head != bptr) { 222 | 223 | // UART_TX_WRITE(UARTCH, txbuffer.data[bptr++]); // Put character in TXT register 224 | bptr &= (TX_BUFFER_SIZE - 1); // and update tmp tail pointer. 225 | 226 | txbuffer.tail = bptr; // Update tail pointer. 227 | 228 | if(bptr == txbuffer.head) // Disable TX interrups 229 | // UART_TX_IRQ_DISABLE(); // when TX buffer empty. 230 | } 231 | } 232 | 233 | if(iflags & (UART_IRQ_RX)) { 234 | 235 | bptr = (rxbuffer.head + 1) & (RX_BUFFER_SIZE - 1); // Get next head pointer. 236 | 237 | if(bptr == rxbuffer.tail) { // If buffer full 238 | rxbuffer.overflow = 1; // flag overflow. 239 | // UART_RX_IRQ_DISABLE(); Clear RX interrupt, may be done by a dummy read of the RX register 240 | } else { 241 | // data = UART_GET(); Read received character to data varable, clear RX interrupt if not done automatically by read. 242 | if(data == CMD_TOOL_ACK && !rxbuffer.backup) { // If tool change acknowledged 243 | stream_rx_backup(&rxbuf); // save current RX buffer 244 | hal.stream.read = serialGetC; // and restore normal input. 245 | } else if(!hal.stream.enqueue_realtime_command((char)data)) { 246 | rxbuffer.data[rxbuffer.head] = (char)data; // Add data to buffer. 247 | rxbuffer.head = bptr; // and update pointer. 248 | } 249 | } 250 | } 251 | } 252 | -------------------------------------------------------------------------------- /arm-driver/serial.h: -------------------------------------------------------------------------------- 1 | /* 2 | serial.h - An embedded CNC Controller with rs274/ngc (g-code) support 3 | 4 | Template driver code for ARM processors 5 | 6 | Part of grblHAL 7 | 8 | By Terje Io, public domain 9 | 10 | */ 11 | 12 | #ifndef _HAL_SERIAL_H_ 13 | #define _HAL_SERIAL_H_ 14 | 15 | #include "grbl/hal.h" 16 | 17 | const io_stream_t *serialInit (uint32_t baud_rate); 18 | 19 | // If baud rate selection is not inplemented implement this signature instead: 20 | // const io_stream_t *serialInit (void); 21 | 22 | #endif 23 | -------------------------------------------------------------------------------- /mcodes.c: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | mcode.c - user defined M-codes template 4 | 5 | Part of grblHAL 6 | 7 | Copyright (c) 2019-2024 Terje Io 8 | 9 | grblHAL is free software: you can redistribute it and/or modify 10 | it under the terms of the GNU General Public License as published by 11 | the Free Software Foundation, either version 3 of the License, or 12 | (at your option) any later version. 13 | 14 | grblHAL is distributed in the hope that it will be useful, 15 | but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | GNU General Public License for more details. 18 | 19 | You should have received a copy of the GNU General Public License 20 | along with grblHAL. If not, see . 21 | 22 | */ 23 | 24 | /* 25 | * NOTE: this template is also a bare bones example for adding M100 with two parameters: P and Q 26 | */ 27 | 28 | #include 29 | #include 30 | 31 | #ifdef ARDUINO 32 | #include "../grbl/hal.h" 33 | #else 34 | #include "grbl/hal.h" 35 | #endif 36 | 37 | static user_mcode_ptrs_t user_mcode; 38 | 39 | // check - check if M-code is handled here. 40 | // parameters: mcode - M-code to check for (some are predefined in user_mcode_t in grbl/gcode.h), use a cast if not. 41 | // returns: mcode if handled, UserMCode_Ignore otherwise (UserMCode_Ignore is defined in grbl/gcode.h). 42 | static user_mcode_type_t check (user_mcode_t mcode) 43 | { 44 | return mcode == UserMCode_Generic0 45 | ? UserMCode_Normal // Handled by us. Set to UserMCode_NoValueWords if there are any parameter words (letters) without an accompanying value. 46 | : (user_mcode.check ? user_mcode.check(mcode) : UserMCode_Unsupported); // If another handler present then call it or return ignore. 47 | } 48 | 49 | // validate - validate parameters 50 | // NOTE: the isnanf() checks below are not needed when check() returns UserMCode_Generic0, it has already been performed by the parser. 51 | // parameters: gc_block - pointer to parser_block_t struct (defined in grbl/gcode.h). 52 | // gc_block->words - holds a bitfield of parameter words available. 53 | // If float values are NAN (Not A Number) this means they are not available. 54 | // If integer values has all bits set to 1 this means they are not available. 55 | // returns: status_code_t enum (defined in grbl/gcode.h): Status_OK if validated ok, appropriate status from enum if not. 56 | static status_code_t validate (parser_block_t *gc_block) 57 | { 58 | status_code_t state = Status_GcodeValueWordMissing; 59 | 60 | switch(gc_block->user_mcode) { 61 | 62 | case UserMCode_Generic0: 63 | if(gc_block->words.p && !isnanf(gc_block->values.p)) // Check if P parameter value is supplied. 64 | state = Status_BadNumberFormat; // Return error if so. 65 | 66 | if(gc_block->words.q && isnanf(gc_block->values.q)) // Check if Q parameter value is supplied. 67 | state = Status_BadNumberFormat; // Return error if not. 68 | 69 | if(state != Status_BadNumberFormat && gc_block->words.q) { // Are required parameters provided? 70 | if(gc_block->values.q > 0.0f && gc_block->values.q <= 5.0f) // Yes, is Q parameter value in range (1-5)? 71 | state = Status_OK; // Yes - return ok status. 72 | else 73 | state = Status_GcodeValueOutOfRange; // No - return error status. 74 | if(gc_block->words.q) // If P parameter is present set 75 | gc_block->values.p = 1.0f; // value to 1 for execution. 76 | gc_block->words.p = gc_block->words.q = Off; // Claim parameters. 77 | gc_block->user_mcode_sync = true; // Optional: execute command synchronized 78 | } 79 | break; 80 | 81 | default: 82 | state = Status_Unhandled; 83 | break; 84 | } 85 | 86 | // If not handled by us and another handler present then call it. 87 | return state == Status_Unhandled && user_mcode.validate ? user_mcode.validate(gc_block) : state; 88 | } 89 | 90 | // execute - execute M-code 91 | // parameters: state - sys.state (bitmap, defined in system.h) 92 | // gc_block - pointer to parser_block_t struct (defined in grbl/gcode.h). 93 | // returns: - 94 | static void execute (sys_state_t state, parser_block_t *gc_block) 95 | { 96 | bool handled = true; 97 | 98 | switch(gc_block->user_mcode) { 99 | 100 | case UserMCode_Generic0: 101 | // do something: Q parameter value can be found in gc_block->values.q. 102 | // P parameter has its value in gc_block->values.p set to 1 if present, NAN if not. 103 | break; 104 | 105 | default: 106 | handled = false; 107 | break; 108 | } 109 | 110 | 111 | if(!handled && user_mcode.execute) // If not handled by us and another handler present 112 | user_mcode.execute(state, gc_block); // then call it. 113 | } 114 | 115 | // Set up HAL pointers for handling additional M-codes. 116 | // Call this function on driver setup. 117 | void mcodes_init (void) 118 | { 119 | // Save away current HAL pointers so that we can use them to keep 120 | // any chain of M-code handlers intact. 121 | memcpy(&user_mcode, &grbl.user_mcode, sizeof(user_mcode_ptrs_t)); 122 | 123 | // Redirect HAL pointers to our code. 124 | grbl.user_mcode.check = check; 125 | grbl.user_mcode.validate = validate; 126 | grbl.user_mcode.execute = execute; 127 | } 128 | -------------------------------------------------------------------------------- /mcodes.h: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | mcode.h - user defined M-codes template 4 | 5 | Part of grblHAL 6 | 7 | Copyright (c) 2019 Terje Io 8 | 9 | Grbl is free software: you can redistribute it and/or modify 10 | it under the terms of the GNU General Public License as published by 11 | the Free Software Foundation, either version 3 of the License, or 12 | (at your option) any later version. 13 | 14 | Grbl is distributed in the hope that it will be useful, 15 | but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | GNU General Public License for more details. 18 | 19 | You should have received a copy of the GNU General Public License 20 | along with Grbl. If not, see . 21 | 22 | */ 23 | 24 | // Set up HAL pointers for handling addtional M-codes. 25 | // Call this function on driver setup. 26 | void mcodes_init (void); 27 | -------------------------------------------------------------------------------- /my_plugin/MCU_load/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(my_plugin INTERFACE) 2 | 3 | target_sources(my_plugin INTERFACE 4 | ${CMAKE_CURRENT_LIST_DIR}/my_plugin.c 5 | ) 6 | 7 | target_include_directories(my_plugin INTERFACE ${CMAKE_CURRENT_LIST_DIR}) 8 | -------------------------------------------------------------------------------- /my_plugin/MCU_load/my_plugin.c: -------------------------------------------------------------------------------- 1 | /* 2 | my_plugin.c.c - MCU load estimator 3 | 4 | Counts number of iterations of protocol idle loop per 10ms and add count to real time report. 5 | 6 | Higher number is better. E.g. the iMXRT1062 reports > 20000 when idle -> less than 500ns per iteration. 7 | 8 | Part of grblHAL 9 | 10 | Public domain. 11 | This code is is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 14 | */ 15 | 16 | #include 17 | 18 | #include "grbl/hal.h" 19 | 20 | static uint32_t count = 0; 21 | static bool add_report = false; 22 | static on_execute_realtime_ptr on_execute_realtime; 23 | static on_realtime_report_ptr on_realtime_report; 24 | static on_report_options_ptr on_report_options; 25 | 26 | void onExecuteRealtime (uint_fast16_t state) 27 | { 28 | static uint32_t last_ms, last_count; 29 | 30 | uint32_t ms = hal.get_elapsed_ticks(); 31 | 32 | if(ms - last_ms >= 10) { 33 | last_ms = ms; 34 | count = last_count; 35 | last_count = 0; 36 | add_report = true; 37 | } else 38 | last_count++; 39 | 40 | on_execute_realtime(state); 41 | } 42 | 43 | static void onRealtimeReport (stream_write_ptr stream_write, report_tracking_flags_t report) 44 | { 45 | static char buf[20] = {0}; 46 | 47 | if (add_report) { 48 | 49 | add_report = false; 50 | 51 | strcpy(buf, "|LOAD:"); 52 | strcat(buf, uitoa(count)); 53 | 54 | stream_write(buf); 55 | } 56 | 57 | if(on_realtime_report) 58 | on_realtime_report(stream_write, report); 59 | } 60 | 61 | static void onReportOptions (bool newopt) 62 | { 63 | on_report_options(newopt); 64 | 65 | if(!newopt) 66 | report_plugin("MCU Load", "v0.01"); 67 | } 68 | 69 | void my_plugin_init (void) 70 | { 71 | on_report_options = grbl.on_report_options; 72 | grbl.on_report_options = onReportOptions; 73 | 74 | on_execute_realtime = grbl.on_execute_realtime; 75 | grbl.on_execute_realtime = onExecuteRealtime; 76 | 77 | on_realtime_report = grbl.on_realtime_report; 78 | grbl.on_realtime_report = onRealtimeReport; 79 | } 80 | -------------------------------------------------------------------------------- /my_plugin/MQTT_example/my_plugin.c: -------------------------------------------------------------------------------- 1 | /* 2 | my_plugin.c - some MQTT fun 3 | 4 | Part of grblHAL 5 | 6 | Public domain. 7 | This code is is distributed in the hope that it will be useful, 8 | but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 10 | */ 11 | 12 | #include "driver.h" 13 | 14 | #if MQTT_ENABLE 15 | 16 | #include 17 | 18 | #include "networking/networking.h" 19 | 20 | static bool mqtt_connected = false; 21 | static const char *client_id = NULL; 22 | static coolant_set_state_ptr coolant_set_state_; 23 | static on_state_change_ptr on_state_change; 24 | static on_report_options_ptr on_report_options; 25 | static on_program_completed_ptr on_program_completed; 26 | static on_mqtt_client_connected_ptr on_client_connected; 27 | static on_mqtt_message_received_ptr on_message_received; 28 | 29 | static const char *msg_alarm = "Alarm %d! - %s"; 30 | static const char *msg_job_complete = "job completed!"; 31 | static const char *msg_coolant_on = "turn on water cooler!"; 32 | static const char *msg_coolant_off = "turn off water cooler!"; 33 | 34 | static void onStateChanged (sys_state_t state) 35 | { 36 | static sys_state_t last_state = STATE_IDLE; 37 | 38 | if(state != last_state) { 39 | 40 | last_state = state; 41 | 42 | if((state & STATE_ALARM) && mqtt_connected) { 43 | 44 | char *msg; 45 | const char *alarm; 46 | if((msg = malloc(strlen((alarm = alarms_get_description(sys.alarm)) + strlen(msg_alarm) + 3)))) { 47 | sprintf(msg, msg_alarm, sys.alarm, alarm); 48 | mqtt_publish_message("grblHALxx", msg, strlen(msg), 1, false); 49 | free(msg); 50 | } 51 | } 52 | } 53 | 54 | if(on_state_change) 55 | on_state_change(state); 56 | } 57 | 58 | void onProgramCompleted (program_flow_t program_flow, bool check_mode) 59 | { 60 | if(!check_mode && mqtt_connected) 61 | mqtt_publish_message("grblHALxx", msg_job_complete, strlen(msg_job_complete), 1, false); 62 | 63 | if(on_program_completed) 64 | on_program_completed(program_flow, check_mode); 65 | } 66 | 67 | static void onCoolantSetState (coolant_state_t state) 68 | { 69 | static coolant_state_t last_state = {0}; 70 | 71 | coolant_set_state_(state); 72 | 73 | if(state.flood != last_state.flood && mqtt_connected) { 74 | if(state.flood) 75 | mqtt_publish_message("grblHALxx", msg_coolant_on, strlen(msg_coolant_on), 1, false); 76 | else 77 | mqtt_publish_message("grblHALxx", msg_coolant_off, strlen(msg_coolant_off), 1, false); 78 | } 79 | 80 | last_state = state; 81 | } 82 | 83 | static void onMQTTconnected (bool connected) 84 | { 85 | if(on_client_connected) 86 | on_client_connected(connected); 87 | 88 | if((mqtt_connected = connected)) { 89 | mqtt_subscribe_topic("grblHAL", 1, NULL); 90 | client_id = networking_get_info()->mqtt_client_id; 91 | } else 92 | client_id = NULL; 93 | } 94 | 95 | static bool onMQTTmessage (const char *topic, const void *payload, size_t payload_length) 96 | { 97 | /* 98 | hal.stream.write(topic); 99 | hal.stream.write(ASCII_EOL); 100 | hal.stream.write((char *)payload); 101 | hal.stream.write(ASCII_EOL); 102 | */ 103 | if(!strcmp((char *)payload, "stop job")) 104 | grbl.enqueue_realtime_command(CMD_STOP); 105 | 106 | return on_message_received == NULL || on_message_received(topic, payload, payload_length); 107 | } 108 | 109 | static void onReportOptions (bool newopt) 110 | { 111 | on_report_options(newopt); 112 | 113 | if(!newopt) 114 | report_plugin("MQTT Demo", "v0.01"); 115 | } 116 | 117 | void my_plugin_init (void) 118 | { 119 | on_report_options = grbl.on_report_options; 120 | grbl.on_report_options = onReportOptions; 121 | 122 | on_state_change = grbl.on_state_change; 123 | grbl.on_state_change = onStateChanged; 124 | 125 | coolant_set_state_ = hal.coolant.set_state; 126 | hal.coolant.set_state = onCoolantSetState; 127 | 128 | on_program_completed = grbl.on_program_completed; 129 | grbl.on_program_completed = onProgramCompleted; 130 | 131 | on_client_connected = mqtt_events.on_client_connected; 132 | mqtt_events.on_client_connected = onMQTTconnected; 133 | 134 | on_message_received = mqtt_events.on_message_received; 135 | mqtt_events.on_message_received = onMQTTmessage; 136 | } 137 | 138 | #endif 139 | -------------------------------------------------------------------------------- /my_plugin/Macros_bound_to_aux_inputs/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(my_plugin INTERFACE) 2 | 3 | target_sources(my_plugin INTERFACE 4 | ${CMAKE_CURRENT_LIST_DIR}/my_plugin.c 5 | ) 6 | 7 | target_include_directories(my_plugin INTERFACE ${CMAKE_CURRENT_LIST_DIR}) 8 | -------------------------------------------------------------------------------- /my_plugin/Macros_bound_to_aux_inputs/my_plugin.c: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | my_plugin.c - plugin for binding macros to aux input pins 4 | 5 | Part of grblHAL 6 | 7 | Public domain. 8 | This code is is distributed in the hope that it will be useful, 9 | but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 11 | 12 | Up to 4 macros can be bound to input pins by changing the N_MACROS symbol below. 13 | Each macro can be up to 127 characters long, blocks (lines) are separated by a vertical bar character: | 14 | Setting numbers $450 - $453 are for defining the macro content. 15 | Setting numbers $454 - $457 are for configuring which aux input port to assign to each macro. 16 | NOTES: If the driver does not support mapping of port numbers settings $454 - $457 will not be available. 17 | The mapped pins has to be interrupt capable and support falling interrupt mode. 18 | The controller must be in Idle mode when starting macros. 19 | 20 | Examples: 21 | $450=G0Y5|G1X0F50 22 | $451=G0x0Y0Z0 23 | 24 | Tip: use the $pins command to check the port mapping. 25 | */ 26 | 27 | #define N_MACROS 2 // MAX 4 28 | 29 | #include 30 | #include 31 | 32 | #include "grbl/hal.h" 33 | #include "grbl/nvs_buffer.h" 34 | #include "grbl/nuts_bolts.h" 35 | #include "grbl/protocol.h" 36 | #include "grbl/state_machine.h" 37 | #include "grbl/platform.h" 38 | #include "grbl/task.h" 39 | 40 | #if N_MACROS > 4 41 | #undef N_MACROS 42 | #define N_MACROS 4 43 | #endif 44 | 45 | typedef struct { 46 | uint8_t port; 47 | char data[128]; 48 | } macro_setting_t; 49 | 50 | typedef struct { 51 | macro_setting_t macro[N_MACROS]; 52 | } macro_settings_t; 53 | 54 | static bool is_executing = false; 55 | static uint8_t n_ports; 56 | uint8_t port[N_MACROS]; 57 | static char max_port[4], *command; 58 | static nvs_address_t nvs_address; 59 | static on_report_options_ptr on_report_options; 60 | static macro_settings_t plugin_settings; 61 | static stream_read_ptr stream_read; 62 | static driver_reset_ptr driver_reset; 63 | 64 | static int16_t get_macro_char (void); 65 | 66 | // Ends macro execution if currently running 67 | // and restores normal operation. 68 | static void end_macro (void) 69 | { 70 | is_executing = false; 71 | if(hal.stream.read == get_macro_char) { 72 | hal.stream.read = stream_read; 73 | report_init_fns(); 74 | } 75 | } 76 | 77 | // Called on a soft reset so that normal operation can be restored. 78 | static void plugin_reset (void) 79 | { 80 | end_macro(); // End macro if currently running. 81 | driver_reset(); // Call the next reset handler in the chain. 82 | } 83 | 84 | // Macro stream input function. 85 | // Reads character by character from the macro and returns them when 86 | // requested by the foreground process. 87 | static int16_t get_macro_char (void) 88 | { 89 | static bool eol_ok = false; 90 | 91 | if(*command == '\0') { // End of macro? 92 | end_macro(); // If end reading from it 93 | return eol_ok ? SERIAL_NO_DATA : ASCII_LF; // and return a linefeed if the last character was not a linefeed. 94 | } 95 | 96 | char c = *command++; // Get next character. 97 | 98 | if((eol_ok = c == '|')) // If character is vertical bar 99 | c = ASCII_LF; // return a linefeed character. 100 | 101 | return (uint16_t)c; 102 | } 103 | 104 | // This code will be executed after each command is sent to the parser, 105 | // If an error is detected macro execution will be stopped and the status_code reported. 106 | static status_code_t trap_status_report (status_code_t status_code) 107 | { 108 | if(status_code != Status_OK) { 109 | char msg[30]; 110 | sprintf(msg, "error %d in macro", (uint8_t)status_code); 111 | report_message(msg, Message_Warning); 112 | end_macro(); 113 | } 114 | 115 | return status_code; 116 | } 117 | 118 | // Actual start of macro execution. 119 | static void run_macro (void *data) 120 | { 121 | if(state_get() == STATE_IDLE && hal.stream.read != get_macro_char) { 122 | stream_read = hal.stream.read; // Redirect input stream to read from the macro instead of 123 | hal.stream.read = get_macro_char; // the active stream. This ensures that input streams are not mingled. 124 | grbl.report.status_message = trap_status_report; // Add trap for status messages so we can terminate on errors. 125 | } 126 | } 127 | 128 | // On falling interrupt run macro if machine is in Idle state. 129 | // Since this function runs in an interrupt context actual start of execution 130 | // is registered as a single run task to be started from the foreground process. 131 | ISR_CODE static void execute_macro (uint8_t irq_port, bool is_high) 132 | { 133 | if(!is_high && !is_executing && state_get() == STATE_IDLE) { 134 | 135 | // Determine macro to run from port number 136 | uint_fast8_t idx = N_MACROS; 137 | do { 138 | idx--; 139 | } while(idx && port[idx] != irq_port); 140 | 141 | command = plugin_settings.macro[idx].data; 142 | if(!(*command == '\0' || *command == 0xFF)) { // If valid command 143 | is_executing = true; 144 | task_add_immediate(run_macro, NULL); // register run_macro function to be called from foreground process. 145 | } 146 | } 147 | } 148 | 149 | // Add info about our settings for $help and enumerations. 150 | // Potentially used by senders for settings UI. 151 | 152 | static const setting_group_detail_t macro_groups [] = { 153 | { Group_Root, Group_UserSettings, "Macros"} 154 | }; 155 | 156 | static status_code_t set_port (setting_id_t setting, float value) 157 | { 158 | status_code_t status; 159 | uint_fast8_t idx = setting - Setting_UserDefined_4; 160 | 161 | if((status = isintf(value) ? Status_OK : Status_BadNumberFormat) == Status_OK) 162 | plugin_settings.macro[idx].port = value < 0.0f ? 255 : (uint8_t)value; 163 | 164 | return status; 165 | } 166 | 167 | static float get_port (setting_id_t setting) 168 | { 169 | uint_fast8_t idx = setting - Setting_UserDefined_4; 170 | 171 | return plugin_settings.macro[idx].port; 172 | } 173 | 174 | static const setting_detail_t macro_settings[] = { 175 | { Setting_UserDefined_0, Group_UserSettings, "Macro 1", NULL, Format_String, "x(127)", "0", "127", Setting_NonCore, &plugin_settings.macro[0].data, NULL, NULL }, 176 | #if N_MACROS > 1 177 | { Setting_UserDefined_1, Group_UserSettings, "Macro 2", NULL, Format_String, "x(127)", "0", "127", Setting_NonCore, &plugin_settings.macro[1].data, NULL, NULL }, 178 | #endif 179 | #if N_MACROS > 2 180 | { Setting_UserDefined_2, Group_UserSettings, "Macro 3", NULL, Format_String, "x(127)", "0", "127", Setting_NonCore, &plugin_settings.macro[2].data, NULL, NULL }, 181 | #endif 182 | #if N_MACROS > 3 183 | { Setting_UserDefined_3, Group_UserSettings, "Macro 4", NULL, Format_String, "x(127)", "0", "127", Setting_NonCore, &plugin_settings.macro[3].data, NULL, NULL }, 184 | #endif 185 | { Setting_UserDefined_4, Group_AuxPorts, "Macro 1 port", NULL, Format_Decimal, "-#0", "-1", max_port, Setting_NonCoreFn, set_port, get_port, NULL, { .reboot_required = On } }, 186 | #if N_MACROS > 1 187 | { Setting_UserDefined_5, Group_AuxPorts, "Macro 2 port", NULL, Format_Decimal, "-#0", "-1", max_port, Setting_NonCoreFn, set_port, get_port, NULL, { .reboot_required = On } }, 188 | #endif 189 | #if N_MACROS > 2 190 | { Setting_UserDefined_6, Group_AuxPorts, "Macro 3 port", NULL, Format_Decimal, "-#0", "-1", max_port, Setting_NonCoreFn, set_port, get_port, NULL, { .reboot_required = On } }, 191 | #endif 192 | #if N_MACROS > 3 193 | { Setting_UserDefined_7, Group_AuxPorts, "Macro 4 port", NULL, Format_Decimal, "-#0", "-1", max_port, Setting_NonCoreFn, set_port, get_port, NULL, { .reboot_required = On } }, 194 | #endif 195 | }; 196 | 197 | #ifndef NO_SETTINGS_DESCRIPTIONS 198 | 199 | static const setting_descr_t macro_settings_descr[] = { 200 | { Setting_UserDefined_0, "Macro content for macro 1, separate blocks (lines) with the vertical bar character |." }, 201 | #if N_MACROS > 1 202 | { Setting_UserDefined_1, "Macro content for macro 2, separate blocks (lines) with the vertical bar character |." }, 203 | #endif 204 | #if N_MACROS > 2 205 | { Setting_UserDefined_2, "Macro content for macro 3, separate blocks (lines) with the vertical bar character |." }, 206 | #endif 207 | #if N_MACROS > 3 208 | { Setting_UserDefined_3, "Macro content for macro 4, separate blocks (lines) with the vertical bar character |." }, 209 | #endif 210 | { Setting_UserDefined_4, "Aux port number to use for the Macro 1 start pin input. Set to -1 to disable." }, 211 | #if N_MACROS > 1 212 | { Setting_UserDefined_5, "Aux port number to use for the Macro 2 start pin input. Set to -1 to disable." }, 213 | #endif 214 | #if N_MACROS > 2 215 | { Setting_UserDefined_6, "Aux port number to use for the Macro 3 start pin input. Set to -1 to disable." }, 216 | #endif 217 | #if N_MACROS > 3 218 | { Setting_UserDefined_7, "Aux port number to use for the Macro 4 start pin input. Set to -1 to disable." }, 219 | #endif 220 | }; 221 | 222 | #endif 223 | 224 | // Write settings to non volatile storage (NVS). 225 | static void macro_settings_save (void) 226 | { 227 | hal.nvs.memcpy_to_nvs(nvs_address, (uint8_t *)&plugin_settings, sizeof(macro_settings_t), true); 228 | } 229 | 230 | // Restore default settings and write to non volatile storage (NVS). 231 | static void macro_settings_restore (void) 232 | { 233 | uint_fast8_t idx = N_MACROS, port = n_ports > N_MACROS ? n_ports - N_MACROS : 0; 234 | 235 | // Register empty macro strings and set default port numbers if mapping is available. 236 | for(idx = 0; idx < N_MACROS; idx++) { 237 | plugin_settings.macro[idx].port = port++; 238 | *plugin_settings.macro[idx].data = '\0'; 239 | }; 240 | 241 | hal.nvs.memcpy_to_nvs(nvs_address, (uint8_t *)&plugin_settings, sizeof(macro_settings_t), true); 242 | } 243 | 244 | // Load our settings from non volatile storage (NVS). 245 | // If load fails restore to default values. 246 | static void macro_settings_load (void) 247 | { 248 | uint_fast8_t idx = N_MACROS, n_ok = 0, n_enabled = 0; 249 | xbar_t *pin = NULL; 250 | 251 | if(hal.nvs.memcpy_from_nvs((uint8_t *)&plugin_settings, nvs_address, sizeof(macro_settings_t), true) != NVS_TransferResult_OK) 252 | macro_settings_restore(); 253 | 254 | do { 255 | idx--; 256 | if((port[idx] = plugin_settings.macro[idx].port) == 0xFF) 257 | continue; 258 | n_enabled++; 259 | if((pin = ioport_get_info(Port_Digital, Port_Input, port[idx])) && !(pin->cap.irq_mode & IRQ_Mode_Falling)) // Is port interrupt capable? 260 | port[idx] = 0xFF; // No, flag it as not claimed. 261 | else if(ioport_claim(Port_Digital, Port_Input, &port[idx], "Macro pin")) { // Try to claim the port. 262 | if(pin->cap.debounce) { 263 | gpio_in_config_t config = { 264 | .debounce = On, 265 | .pull_mode = PullMode_Up 266 | }; 267 | pin->config(pin, &config, false); 268 | } 269 | } else 270 | port[idx] = 0xFF; 271 | } while(idx); 272 | 273 | // Then try to register the interrupt handler. 274 | idx = N_MACROS; 275 | do { 276 | idx--; 277 | if(port[idx] != 0xFF && hal.port.register_interrupt_handler(port[idx], IRQ_Mode_Falling, execute_macro)) 278 | n_ok++; 279 | } while(idx); 280 | 281 | if(n_ok < n_enabled) 282 | task_run_on_startup(report_warning, "Macro plugin failed to claim all needed ports!"); 283 | } 284 | 285 | // Add info about our plugin to the $I report. 286 | static void report_options (bool newopt) 287 | { 288 | on_report_options(newopt); 289 | 290 | if(!newopt) 291 | report_plugin("Macro plugin (PD)", "0.04); 292 | } 293 | 294 | // A call my_plugin_init will be issued automatically at startup. 295 | // There is no need to change any source code elsewhere. 296 | void my_plugin_init (void) 297 | { 298 | // Settings descriptor used by the core when interacting with this plugin. 299 | static setting_details_t setting_details = { 300 | .groups = macro_groups, 301 | .n_groups = sizeof(macro_groups) / sizeof(setting_group_detail_t), 302 | .settings = macro_settings, 303 | .n_settings = sizeof(macro_settings) / sizeof(setting_detail_t), 304 | #ifndef NO_SETTINGS_DESCRIPTIONS 305 | .descriptions = macro_settings_descr, 306 | .n_descriptions = sizeof(macro_settings_descr) / sizeof(setting_descr_t), 307 | #endif 308 | .save = macro_settings_save, 309 | .load = macro_settings_load, 310 | .restore = macro_settings_restore 311 | }; 312 | 313 | // If resources available register our plugin with the core. 314 | if(ioport_can_claim_explicit() && 315 | (n_ports = ioports_available(Port_Digital, Port_Input)) && 316 | (nvs_address = nvs_alloc(sizeof(macro_settings_t)))) { 317 | 318 | // Used for setting value validation. 319 | strcpy(max_port, uitoa(n_ports - 1)); 320 | 321 | // Register settings. 322 | settings_register(&setting_details); 323 | 324 | // Add our plugin to the $I options report. 325 | on_report_options = grbl.on_report_options; 326 | grbl.on_report_options = report_options; 327 | 328 | // Hook into the driver reset chain so we 329 | // can restore normal operation if a reset happens 330 | // when a macro is running. 331 | driver_reset = hal.driver_reset; 332 | hal.driver_reset = plugin_reset; 333 | } else 334 | task_run_on_startup(report_warning, "Macro plugin failed to initialize!"); 335 | } 336 | -------------------------------------------------------------------------------- /my_plugin/Motor_power_monitor/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(my_plugin INTERFACE) 2 | 3 | target_sources(my_plugin INTERFACE 4 | ${CMAKE_CURRENT_LIST_DIR}/my_plugin.c 5 | ) 6 | 7 | target_include_directories(my_plugin INTERFACE ${CMAKE_CURRENT_LIST_DIR}) 8 | -------------------------------------------------------------------------------- /my_plugin/Motor_power_monitor/my_plugin.c: -------------------------------------------------------------------------------- 1 | /* 2 | my_plugin.c - plugin for monitoring motor power 3 | 4 | Part of grblHAL 5 | 6 | Public domain. 7 | This code is is distributed in the hope that it will be useful, 8 | but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 10 | 11 | On power loss alarm 17 is raised (Motor fault). 12 | On alarm cleared or soft reset a M122I command is issued to reinit Trinamic drivers if power is back on. 13 | 14 | Setting $450 is for configuring which aux input port to assign for monitoring. 15 | NOTE: If the driver does not support mapping of port number settings $450 will not be available. 16 | The mapped pin has to be interrupt capable and support change (falling and rising) interrupt mode. 17 | 18 | Tip: use the $pins command to check the port mapping. 19 | */ 20 | 21 | #include 22 | #include 23 | 24 | #include "grbl/hal.h" 25 | #include "grbl/nvs_buffer.h" 26 | #include "grbl/protocol.h" 27 | #include "grbl/state_machine.h" 28 | #include "grbl/task.h" 29 | 30 | typedef enum { 31 | Power_On = 0, 32 | Power_Alarm, 33 | Power_Lost 34 | } power_state_t; 35 | 36 | typedef struct { 37 | uint8_t port; 38 | } power_settings_t; 39 | 40 | static uint8_t port, n_ports; 41 | static char max_port[4]; 42 | static power_state_t power_state = Power_On; 43 | static nvs_address_t nvs_address; 44 | static power_settings_t plugin_settings; 45 | static on_report_options_ptr on_report_options; 46 | 47 | static status_code_t set_port (setting_id_t setting, float value) 48 | { 49 | if(!isintf(value)) 50 | return Status_BadNumberFormat; 51 | 52 | plugin_settings.port = value < 0.0f ? 0xFF : (uint8_t)value; 53 | 54 | return Status_OK; 55 | } 56 | 57 | static float get_port (setting_id_t setting) 58 | { 59 | return plugin_settings.port >= n_ports ? -1.0f : (float) plugin_settings.port; 60 | } 61 | 62 | // Use a float (decimal) setting with getter/setter functions so -1 can be used to disable the plugin. 63 | static const setting_detail_t power_settings[] = { 64 | { Setting_UserDefined_0, Group_AuxPorts, "Power monitor port", NULL, Format_Decimal, "-#0", "-1", max_port, Setting_NonCoreFn, set_port, get_port, NULL, { .reboot_required = On } } 65 | }; 66 | 67 | #ifndef NO_SETTINGS_DESCRIPTIONS 68 | 69 | static const setting_descr_t power_settings_descr[] = { 70 | { Setting_UserDefined_0, "Auxiliary port to use for stepper power monitoring. Set to -1 to disable." } 71 | }; 72 | 73 | #endif 74 | 75 | // Write settings to non volatile storage (NVS). 76 | static void power_settings_save (void) 77 | { 78 | hal.nvs.memcpy_to_nvs(nvs_address, (uint8_t *)&plugin_settings, sizeof(power_settings_t), true); 79 | } 80 | 81 | static void check_power_restored (void *data) 82 | { 83 | if(hal.port.wait_on_input(Port_Digital, port, WaitMode_Immediate, 0.0f) == 1) { 84 | 85 | power_state = Power_On; 86 | 87 | report_message("Motor power restored", Message_Info); 88 | // Reset stepper drivers 89 | if(hal.stepper.stepper_status) 90 | hal.stepper.stepper_status(true); 91 | 92 | } else 93 | task_add_delayed(check_power_restored, NULL, 250); 94 | } 95 | 96 | // Raise motor fault alarm. 97 | static void raise_power_alarm (void *data) 98 | { 99 | if(power_state == Power_Alarm) { 100 | system_raise_alarm(Alarm_MotorFault); 101 | task_add_delayed(check_power_restored, NULL, 250); 102 | } 103 | 104 | power_state = Power_Lost; 105 | } 106 | 107 | // Called when power sensing pin changes state. 108 | // Raises alarm or sets up wait for alarm reset before taking action. 109 | static void on_power_change (uint8_t port, bool state) 110 | { 111 | if(!state) { 112 | if(power_state == Power_On) { 113 | protocol_enqueue_foreground_task(raise_power_alarm, NULL); 114 | power_state = Power_Alarm; 115 | } 116 | } 117 | } 118 | 119 | // Restore default settings and write to non volatile storage (NVS). 120 | static void power_settings_restore (void) 121 | { 122 | // Find highest numbered port that supports change interrupt, or keep the current one if found. 123 | plugin_settings.port = ioport_find_free(Port_Digital, Port_Input, (pin_cap_t){ .irq_mode = IRQ_Mode_Change, .claimable = On }, "Motor supply monitor"); 124 | 125 | hal.nvs.memcpy_to_nvs(nvs_address, (uint8_t *)&plugin_settings, sizeof(power_settings_t), true); 126 | } 127 | 128 | // Load our settings from non volatile storage (NVS). 129 | // If load fails restore to default values. 130 | static void power_settings_load (void) 131 | { 132 | if(hal.nvs.memcpy_from_nvs((uint8_t *)&plugin_settings, nvs_address, sizeof(power_settings_t), true) != NVS_TransferResult_OK) 133 | power_settings_restore(); 134 | 135 | // Sanity check 136 | if(plugin_settings.port >= n_ports) 137 | plugin_settings.port = 0xFF; 138 | 139 | // If port is valid try claiming it, if successful register an interrupt handler. 140 | if((port = plugin_settings.port) != 0xFF) { 141 | 142 | xbar_t *portinfo = ioport_get_info(Port_Digital, Port_Input, port); 143 | if(portinfo && (portinfo->cap.irq_mode & IRQ_Mode_Change) && ioport_claim(Port_Digital, Port_Input, &port, "Motor supply monitor")) { 144 | hal.port.register_interrupt_handler(port, IRQ_Mode_Change, on_power_change); 145 | // TODO add check for power present and raise alarm if not? 146 | } else { 147 | port = 0xFF; 148 | protocol_enqueue_foreground_task(report_warning, "Motor supply monitor plugin failed to claim needed port!"); 149 | } 150 | } 151 | } 152 | 153 | // Add info about our plugin to the $I report. 154 | static void onReportOptions (bool newopt) 155 | { 156 | on_report_options(newopt); 157 | 158 | if(!newopt) 159 | report_plugin("Motor supply monitor", "0.03"); 160 | } 161 | 162 | // A call my_plugin_init will be issued automatically at startup. 163 | // There is no need to change any source code elsewhere. 164 | void my_plugin_init (void) 165 | { 166 | // Settings descriptor used by the core when interacting with this plugin. 167 | static setting_details_t setting_details = { 168 | .settings = power_settings, 169 | .n_settings = sizeof(power_settings) / sizeof(setting_detail_t), 170 | #ifndef NO_SETTINGS_DESCRIPTIONS 171 | .descriptions = power_settings_descr, 172 | .n_descriptions = sizeof(power_settings_descr) / sizeof(setting_descr_t), 173 | #endif 174 | .save = power_settings_save, 175 | .load = power_settings_load, 176 | .restore = power_settings_restore 177 | }; 178 | 179 | // If auxiliary input available and enough free non volatile memory register our plugin with the core. 180 | if(ioport_can_claim_explicit() && 181 | (n_ports = ioports_available(Port_Digital, Port_Input)) && 182 | (nvs_address = nvs_alloc(sizeof(power_settings_t)))) { 183 | 184 | // Create a string of the highest port number allowed, used for $-setting input validation. 185 | strcpy(max_port, uitoa(n_ports - 1)); 186 | 187 | // Register settings. 188 | settings_register(&setting_details); 189 | 190 | // Add our plugin to the $I options report. 191 | on_report_options = grbl.on_report_options; 192 | grbl.on_report_options = onReportOptions; 193 | } else 194 | protocol_enqueue_foreground_task(report_warning, "Motor supply monitor plugin failed to claim needed port!"); 195 | } 196 | -------------------------------------------------------------------------------- /my_plugin/Pause_on_SD_file_run/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(my_plugin INTERFACE) 2 | 3 | target_sources(my_plugin INTERFACE 4 | ${CMAKE_CURRENT_LIST_DIR}/my_plugin.c 5 | ) 6 | 7 | target_include_directories(my_plugin INTERFACE ${CMAKE_CURRENT_LIST_DIR}) 8 | -------------------------------------------------------------------------------- /my_plugin/Pause_on_SD_file_run/my_plugin.c: -------------------------------------------------------------------------------- 1 | /* 2 | my_plugin.c - plugin for pausing (enter feed hold) when a SD file is run. 3 | 4 | A cycle start command has to be issued to start execution. 5 | 6 | Part of grblHAL 7 | 8 | Public domain. 9 | This code is is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | */ 13 | 14 | #include "grbl/hal.h" 15 | #include "grbl/gcode.h" 16 | 17 | static on_stream_changed_ptr on_stream_changed; 18 | static on_report_options_ptr on_report_options; 19 | 20 | static void stream_changed (stream_type_t type) 21 | { 22 | 23 | if(on_stream_changed) 24 | on_stream_changed(type); 25 | 26 | if(type == StreamType_SDCard) { 27 | char m1[5]; 28 | strcpy(m1, "M1"); 29 | gc_execute_block(m1); 30 | } 31 | } 32 | 33 | static void report_options (bool newopt) 34 | { 35 | on_report_options(newopt); 36 | 37 | if(!newopt) 38 | report_plugin("SD Pause", "0.02"); 39 | } 40 | 41 | void my_plugin_init (void) 42 | { 43 | on_stream_changed = grbl.on_stream_changed; 44 | grbl.on_stream_changed = stream_changed; 45 | 46 | on_report_options = grbl.on_report_options; 47 | grbl.on_report_options = report_options; 48 | } 49 | -------------------------------------------------------------------------------- /my_plugin/Persistent_tool/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(my_plugin INTERFACE) 2 | 3 | target_sources(my_plugin INTERFACE 4 | ${CMAKE_CURRENT_LIST_DIR}/my_plugin.c 5 | ) 6 | 7 | target_include_directories(my_plugin INTERFACE ${CMAKE_CURRENT_LIST_DIR}) 8 | -------------------------------------------------------------------------------- /my_plugin/Persistent_tool/my_plugin.c: -------------------------------------------------------------------------------- 1 | /* 2 | my_plugin.c - user defined plugin for keeping current number tool over reboot 3 | 4 | Set $485=1 to enable, $485=0 to disable. 5 | 6 | Part of grblHAL 7 | 8 | Public domain. 9 | This code is is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | */ 13 | 14 | #include "grbl/hal.h" 15 | #include "grbl/report.h" 16 | #include "grbl/nvs_buffer.h" 17 | #include "grbl/nuts_bolts.h" 18 | #include "grbl/protocol.h" 19 | 20 | #if GRBL_BUILD < 20231210 21 | #error Persistent tool plugin requires build 20231210 or later! 22 | #endif 23 | 24 | typedef struct { 25 | bool keep_tool; 26 | tool_id_t tool_id; 27 | } plugin_settings_t; 28 | 29 | static nvs_address_t nvs_address; 30 | static plugin_settings_t my_settings; 31 | static on_report_options_ptr on_report_options; 32 | static on_tool_changed_ptr on_tool_changed; 33 | static on_parser_init_ptr on_parser_init; 34 | static const setting_detail_t user_settings[] = { 35 | { Setting_EnableToolPersistence, Group_Toolchange, "Keep tool number over reboot", NULL, Format_Bool, NULL, NULL, NULL, Setting_IsExtended, &my_settings.keep_tool, NULL, NULL } 36 | }; 37 | 38 | // Write settings to non volatile storage (NVS). 39 | static void plugin_settings_save (void) 40 | { 41 | hal.nvs.memcpy_to_nvs(nvs_address, (uint8_t *)&my_settings, sizeof(plugin_settings_t), true); 42 | } 43 | 44 | // Restore default settings and write to non volatile storage (NVS). 45 | static void plugin_settings_restore (void) 46 | { 47 | my_settings.keep_tool = false; 48 | my_settings.tool_id = 0; 49 | 50 | hal.nvs.memcpy_to_nvs(nvs_address, (uint8_t *)&my_settings, sizeof(plugin_settings_t), true); 51 | } 52 | 53 | // Load settings from non volatile storage (NVS). 54 | // If load fails restore to default values. 55 | static void plugin_settings_load (void) 56 | { 57 | if(hal.nvs.memcpy_from_nvs((uint8_t *)&my_settings, nvs_address, sizeof(plugin_settings_t), true) != NVS_TransferResult_OK) 58 | plugin_settings_restore(); 59 | } 60 | 61 | // Add info about our plugin to the $I report. 62 | static void onReportOptions (bool newopt) 63 | { 64 | on_report_options(newopt); 65 | 66 | if(!newopt) 67 | report_plugin("Persistent tool", "0.03"); 68 | } 69 | 70 | static void onToolChanged (tool_data_t *tool) 71 | { 72 | if(on_tool_changed) 73 | on_tool_changed(tool); 74 | 75 | if(my_settings.keep_tool) { 76 | my_settings.tool_id = tool->tool_id; 77 | plugin_settings_save(); 78 | } 79 | } 80 | 81 | static void onParserInit (parser_state_t *gc_state) 82 | { 83 | if(on_parser_init) 84 | on_parser_init(gc_state); 85 | 86 | if(sys.cold_start && my_settings.keep_tool) { 87 | #if N_TOOLS 88 | if(my_settings.tool_id <= N_TOOLS) 89 | gc_state->tool = &grbl.tool_table.tool[my_settings.tool_id]; 90 | #else 91 | gc_state->tool->tool_id = gc_state->tool_pending = my_settings.tool_id; 92 | #endif 93 | } 94 | } 95 | 96 | // A call my_plugin_init will be issued automatically at startup. 97 | // There is no need to change any source code elsewhere. 98 | void my_plugin_init (void) 99 | { 100 | // Settings descriptor used by the core when interacting with this plugin. 101 | static setting_details_t setting_details = { 102 | .settings = user_settings, 103 | .n_settings = sizeof(user_settings) / sizeof(setting_detail_t), 104 | .save = plugin_settings_save, 105 | .load = plugin_settings_load, 106 | .restore = plugin_settings_restore 107 | }; 108 | 109 | // Try to allocate space for our settings in non volatile storage (NVS). 110 | if((nvs_address = nvs_alloc(sizeof(plugin_settings_t)))) { 111 | 112 | // Add info about our plugin to the $I report. 113 | on_report_options = grbl.on_report_options; 114 | grbl.on_report_options = onReportOptions; 115 | 116 | // Subscribe to parser init event, sets current tool number to stored value on a cold start. 117 | on_parser_init = grbl.on_parser_init; 118 | grbl.on_parser_init = onParserInit; 119 | 120 | // Subscribe to tool changed event, write tool number (tool_id) to NVS when triggered. 121 | on_tool_changed = grbl.on_tool_changed; 122 | grbl.on_tool_changed = onToolChanged; 123 | 124 | // Register our settings with the core. 125 | settings_register(&setting_details); 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /my_plugin/Probe_select/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(my_plugin INTERFACE) 2 | 3 | target_sources(my_plugin INTERFACE 4 | ${CMAKE_CURRENT_LIST_DIR}/my_plugin.c 5 | ) 6 | 7 | target_include_directories(my_plugin INTERFACE ${CMAKE_CURRENT_LIST_DIR}) 8 | -------------------------------------------------------------------------------- /my_plugin/Probe_select/my_plugin.c: -------------------------------------------------------------------------------- 1 | /* 2 | my_plugin.c - plugin template for using an auxiliary output to control a probe selection relay. 3 | 4 | Part of grblHAL 5 | 6 | Public domain. 7 | This code is is distributed in the hope that it will be useful, 8 | but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 10 | 11 | Use the $pins command to find out which output port/pin is used, it will be labeled "Probe relay". 12 | If driver supports aux port remapping a setting for selecting which port to use will be 13 | available, see below. 14 | 15 | NOTE: If no auxiliary output is available it will not install itself. 16 | 17 | M401 - switch on relay immediately. 18 | M401Q0 - set mode to switch on relay when probing @ G59.3 (default). 19 | M401Q1 - set mode to switch on relay when probing @ G59.3 while changing tool (executing M6 when $341 tool change mode is 1, 2 or 3). 20 | M401Q2 - set mode to switch on relay when probing while changing tool (executing M6). 21 | M401Q3 - set mode to always switch on relay when probing. 22 | M401Q4 - set mode to never switch on relay when probing. 23 | M401Q5 - set mode to leave relay in current state when probing. 24 | M402 - switch off relay immediately. 25 | 26 | NOTES: The symbol TOOLSETTER_RADIUS (defined in grbl/config.h, default 5.0mm) is the tolerance for checking "@ G59.3". 27 | When $341 tool change mode 1 or 2 is active it is possible to jog to/from the G59.3 position. 28 | Automatic relay switching when probing at the G59.3 position requires the machine to be homed (X and Y). 29 | 30 | Tip: Set default mode at startup by adding M401Qx to a startup script ($N0 or $N1) 31 | 32 | */ 33 | 34 | #include "grbl/hal.h" 35 | #include "grbl/protocol.h" 36 | 37 | #include 38 | #include 39 | 40 | #define RELAY_DEBOUNCE 50 // ms - increase if relay is slow and/or bouncy 41 | 42 | typedef enum { 43 | ProbeMode_AtG59_3 = 0, 44 | ProbeMode_ToolChangeAtG59_3, 45 | ProbeMode_ToolChange, 46 | ProbeMode_Always, 47 | ProbeMode_Never, 48 | ProbeMode_Manual, 49 | ProbeMode_MaxValue = ProbeMode_Manual, 50 | } probe_mode_t; 51 | 52 | static uint8_t relay_port; 53 | static bool relay_on = false; 54 | static probe_mode_t probe_mode = ProbeMode_AtG59_3; // Default mode 55 | static driver_reset_ptr driver_reset; 56 | static on_probe_toolsetter_ptr on_probe_toolsetter; 57 | static user_mcode_ptrs_t user_mcode; 58 | static on_report_options_ptr on_report_options; 59 | 60 | 61 | // Later versions of grblHAL and the driver may allow configuring which aux port to use for relay control. 62 | // If possible the plugin adds a $setting and delay claiming the port until settings has been loaded. 63 | // The default setting number is Setting_UserDefined_0 ($450), this can be changed by 64 | // modifying the RELAY_PLUGIN_SETTING symbol below. 65 | 66 | #include "grbl/nvs_buffer.h" 67 | 68 | #define RELAY_PLUGIN_SETTING Setting_UserDefined_0 69 | 70 | static uint8_t n_ports; 71 | static char max_port[4]; 72 | 73 | typedef struct { 74 | uint8_t port; 75 | } relay_settings_t; 76 | 77 | static nvs_address_t nvs_address; 78 | static on_report_options_ptr on_report_options; 79 | static relay_settings_t relay_settings; 80 | 81 | static user_mcode_type_t mcode_check (user_mcode_t mcode) 82 | { 83 | return mcode == (user_mcode_t)401 || mcode == (user_mcode_t)402 84 | ? UserMCode_Normal 85 | : (user_mcode.check ? user_mcode.check(mcode) : UserMCode_Unsupported); 86 | } 87 | 88 | static status_code_t mcode_validate (parser_block_t *gc_block) 89 | { 90 | status_code_t state = Status_OK; 91 | 92 | switch((uint16_t)gc_block->user_mcode) { 93 | 94 | case 401: 95 | if(gc_block->words.q) { 96 | if(isnanf(gc_block->values.q)) 97 | state = Status_BadNumberFormat; 98 | else { 99 | if(!isintf(gc_block->values.q) || gc_block->values.q < 0.0f || (probe_mode_t)gc_block->values.q > ProbeMode_MaxValue) 100 | state = Status_GcodeValueOutOfRange; 101 | gc_block->words.q = Off; 102 | } 103 | } 104 | break; 105 | 106 | case 402: 107 | break; 108 | 109 | default: 110 | state = Status_Unhandled; 111 | break; 112 | } 113 | 114 | return state == Status_Unhandled && user_mcode.validate ? user_mcode.validate(gc_block) : state; 115 | } 116 | 117 | static void mcode_execute (uint_fast16_t state, parser_block_t *gc_block) 118 | { 119 | bool handled = true; 120 | 121 | if (state != STATE_CHECK_MODE) 122 | switch((uint16_t)gc_block->user_mcode) { 123 | 124 | case 401: 125 | if(gc_block->words.q) 126 | probe_mode = (probe_mode_t)gc_block->values.q; 127 | else { 128 | relay_on = true; 129 | hal.port.digital_out(relay_port, 1); 130 | hal.delay_ms(RELAY_DEBOUNCE, NULL); // Delay a bit to let any contact bounce settle. 131 | } 132 | break; 133 | 134 | case 402: 135 | relay_on = false; 136 | hal.port.digital_out(relay_port, 0); 137 | hal.delay_ms(RELAY_DEBOUNCE, NULL); // Delay a bit to let any contact bounce settle. 138 | break; 139 | 140 | default: 141 | handled = false; 142 | break; 143 | } 144 | 145 | if(!handled && user_mcode.execute) 146 | user_mcode.execute(state, gc_block); 147 | } 148 | 149 | // When called from "normal" probing tool is always NULL, when called from within 150 | // a tool change sequence (M6) then tool is a pointer to the selected tool. 151 | bool probeToolSetter (tool_data_t *tool, coord_data_t *position, bool at_g59_3, bool on) 152 | { 153 | if(on_probe_toolsetter) 154 | on_probe_toolsetter(tool, position, at_g59_3, on); 155 | 156 | if(on) switch(probe_mode) { 157 | 158 | case ProbeMode_AtG59_3: 159 | relay_on = at_g59_3; 160 | break; 161 | 162 | case ProbeMode_ToolChangeAtG59_3: 163 | relay_on = tool != NULL && at_g59_3; 164 | break; 165 | 166 | case ProbeMode_ToolChange: 167 | relay_on = tool != NULL; 168 | break; 169 | 170 | case ProbeMode_Never: 171 | relay_on = false; 172 | break; 173 | 174 | case ProbeMode_Always: 175 | relay_on = true; 176 | break; 177 | 178 | default: 179 | break; 180 | 181 | } else if(probe_mode != ProbeMode_Manual) 182 | relay_on = false; 183 | 184 | hal.port.digital_out(relay_port, relay_on); 185 | hal.delay_ms(RELAY_DEBOUNCE, NULL); // Delay a bit to let any contact bounce settle. 186 | 187 | return relay_on; 188 | } 189 | 190 | static void probe_reset (void) 191 | { 192 | driver_reset(); 193 | 194 | relay_on = false; 195 | hal.port.digital_out(relay_port, false); 196 | } 197 | 198 | static status_code_t set_port (setting_id_t setting, float value) 199 | { 200 | if(!isintf(value)) 201 | return Status_BadNumberFormat; 202 | 203 | relay_settings.port = value < 0.0f ? 0xFF : (uint8_t)value; 204 | 205 | return Status_OK; 206 | } 207 | 208 | static float get_port (setting_id_t setting) 209 | { 210 | return relay_settings.port >= n_ports ? -1.0f : (float) relay_settings.port; 211 | } 212 | 213 | static const setting_detail_t user_settings[] = { 214 | { RELAY_PLUGIN_SETTING, Group_AuxPorts, "Relay aux port", NULL, Format_Decimal, "-#0", "-1", max_port, Setting_NonCoreFn, set_port, get_port, NULL, { .reboot_required = On } } 215 | }; 216 | 217 | #ifndef NO_SETTINGS_DESCRIPTIONS 218 | 219 | static const setting_descr_t relay_settings_descr[] = { 220 | { RELAY_PLUGIN_SETTING, "Aux port number to use for probe relay control. Set to -1 to disable." } 221 | }; 222 | 223 | #endif 224 | 225 | // Write settings to non volatile storage (NVS). 226 | static void plugin_settings_save (void) 227 | { 228 | hal.nvs.memcpy_to_nvs(nvs_address, (uint8_t *)&relay_settings, sizeof(relay_settings_t), true); 229 | } 230 | 231 | // Restore default settings and write to non volatile storage (NVS). 232 | // Default is highest numbered free port. 233 | static void plugin_settings_restore (void) 234 | { 235 | // Find highest numbered port that supports change interrupt, or keep the current one if found. 236 | relay_settings.port = ioport_find_free(Port_Digital, Port_Output, (pin_cap_t){ .claimable = On }, "Probe relay"); 237 | 238 | hal.nvs.memcpy_to_nvs(nvs_address, (uint8_t *)&relay_settings, sizeof(relay_settings_t), true); 239 | } 240 | 241 | // Load our settings from non volatile storage (NVS). 242 | // If load fails restore to default values. 243 | static void plugin_settings_load (void) 244 | { 245 | if(hal.nvs.memcpy_from_nvs((uint8_t *)&relay_settings, nvs_address, sizeof(relay_settings_t), true) != NVS_TransferResult_OK) 246 | plugin_settings_restore(); 247 | 248 | // Sanity check 249 | if(relay_settings.port >= n_ports) 250 | relay_settings.port = 0xFF; 251 | 252 | if((relay_port = relay_settings.port) != 0xFF) { 253 | 254 | if(ioport_claim(Port_Digital, Port_Output, &relay_port, "Probe relay")) { 255 | 256 | memcpy(&user_mcode, &grbl.user_mcode, sizeof(user_mcode_ptrs_t)); 257 | 258 | grbl.user_mcode.check = mcode_check; 259 | grbl.user_mcode.validate = mcode_validate; 260 | grbl.user_mcode.execute = mcode_execute; 261 | 262 | driver_reset = hal.driver_reset; 263 | hal.driver_reset = probe_reset; 264 | 265 | on_probe_toolsetter = grbl.on_probe_toolsetter; 266 | grbl.on_probe_toolsetter = probeToolSetter; 267 | 268 | } else 269 | protocol_enqueue_foreground_task(report_warning, "Relay plugin: configured port number is not available"); 270 | } 271 | } 272 | 273 | static void onReportOptions (bool newopt) 274 | { 275 | on_report_options(newopt); 276 | 277 | if(!newopt) 278 | report_plugin("Probe select", "0.08"); 279 | } 280 | 281 | void my_plugin_init (void) 282 | { 283 | // Settings descriptor used by the core when interacting with this plugin. 284 | static setting_details_t setting_details = { 285 | .settings = user_settings, 286 | .n_settings = sizeof(user_settings) / sizeof(setting_detail_t), 287 | #ifndef NO_SETTINGS_DESCRIPTIONS 288 | .descriptions = relay_settings_descr, 289 | .n_descriptions = sizeof(relay_settings_descr) / sizeof(setting_descr_t), 290 | #endif 291 | .save = plugin_settings_save, 292 | .load = plugin_settings_load, 293 | .restore = plugin_settings_restore 294 | }; 295 | 296 | if(ioport_can_claim_explicit() && 297 | (n_ports = ioports_available(Port_Digital, Port_Output)) && 298 | (nvs_address = nvs_alloc(sizeof(relay_settings_t)))) { 299 | 300 | strcpy(max_port, uitoa(n_ports - 1)); 301 | 302 | on_report_options = grbl.on_report_options; 303 | grbl.on_report_options = onReportOptions; 304 | 305 | settings_register(&setting_details); 306 | } else 307 | protocol_enqueue_foreground_task(report_warning, "Probe select plugin failed to initialize!"); 308 | } 309 | -------------------------------------------------------------------------------- /my_plugin/Probe_select2/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(my_plugin INTERFACE) 2 | 3 | target_sources(my_plugin INTERFACE 4 | ${CMAKE_CURRENT_LIST_DIR}/my_plugin.c 5 | ) 6 | 7 | target_include_directories(my_plugin INTERFACE ${CMAKE_CURRENT_LIST_DIR}) 8 | -------------------------------------------------------------------------------- /my_plugin/Probe_select2/my_plugin.c: -------------------------------------------------------------------------------- 1 | /* 2 | my_plugin.c - plugin template for using an auxiliary input for a second probe input. 3 | optionally a second input can be assigned for an overtravel input (by compile time option). 4 | 5 | Part of grblHAL 6 | 7 | Public domain. 8 | This code is is distributed in the hope that it will be useful, 9 | but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 11 | 12 | Use the $pins command to find out which input port/pin is used, it will be labeled "Probe 2". 13 | 14 | NOTE: If no auxiliary input is available it will not install itself. 15 | 16 | M401 - switch to probe2 immediately. 17 | M401Q0 - set mode to switch to probe2 when probing @ G59.3 (default). 18 | M401Q1 - set mode to switch to probe2 when probing @ G59.3 while changing tool (executing M6 when $341 tool change mode is 1, 2 or 3). 19 | M401Q2 - set mode to switch to probe2 when probing while changing tool (executing M6). 20 | M401Q3 - set mode to always use probe2 when probing. 21 | M401Q4 - set mode to never use probe2 when probing. 22 | M401Q5 - set mode to leave probe2 in current state when probing. 23 | M402 - switch off probe2 use immediately. 24 | 25 | NOTES: The symbol TOOLSETTER_RADIUS (defined in grbl/config.h, default 5.0mm) is the tolerance for checking "@ G59.3". 26 | When $341 tool change mode 1 or 2 is active it is possible to jog to/from the G59.3 position. 27 | Automatic relay switching when probing at the G59.3 position requires the machine to be homed (X and Y). 28 | 29 | Tip: Set default mode at startup by adding M401Qx to a startup script ($N0 or $N1) 30 | 31 | */ 32 | 33 | #include "grbl/hal.h" 34 | #include "grbl/protocol.h" 35 | #include "grbl/task.h" 36 | 37 | #include 38 | #include 39 | 40 | typedef enum { 41 | ProbeMode_AtG59_3 = 0, 42 | ProbeMode_ToolChangeAtG59_3, 43 | ProbeMode_ToolChange, 44 | ProbeMode_Always, 45 | ProbeMode_Never, 46 | ProbeMode_Manual, 47 | ProbeMode_MaxValue = ProbeMode_Manual, 48 | } probe_mode_t; 49 | 50 | static uint8_t probe_port; 51 | static bool use_probe2 = false; 52 | static probe_state_t probe = { 53 | .connected = On 54 | }; 55 | static probe_mode_t probe_mode = ProbeMode_AtG59_3; // Default mode 56 | static driver_reset_ptr driver_reset; 57 | static probe_configure_ptr probe_configure; 58 | static probe_get_state_ptr probe_get_state; 59 | static on_probe_toolsetter_ptr on_probe_toolsetter; 60 | static user_mcode_ptrs_t user_mcode; 61 | static on_report_options_ptr on_report_options; 62 | static driver_reset_ptr driver_reset; 63 | static uint8_t overtravel_port; 64 | static control_signals_get_state_ptr control_signals_get_state; 65 | 66 | #include "grbl/nvs_buffer.h" 67 | 68 | #define PROBE_PLUGIN_SETTING Setting_UserDefined_0 69 | #define PROBE_PLUGIN_SETTING1 Setting_UserDefined_1 70 | 71 | static uint8_t n_ports; 72 | static char max_port[4]; 73 | 74 | typedef struct { 75 | uint8_t probe_port; 76 | uint8_t overtravel_port; 77 | } probe2_settings_t; 78 | 79 | static nvs_address_t nvs_address; 80 | static on_report_options_ptr on_report_options; 81 | static probe2_settings_t probe2_settings; 82 | 83 | static user_mcode_type_t mcode_check (user_mcode_t mcode) 84 | { 85 | return mcode == (user_mcode_t)401 || mcode == (user_mcode_t)402 86 | ? UserMCode_Normal 87 | : (user_mcode.check ? user_mcode.check(mcode) : UserMCode_Unsupported); 88 | } 89 | 90 | static status_code_t mcode_validate (parser_block_t *gc_block) 91 | { 92 | status_code_t state = Status_OK; 93 | 94 | switch((uint16_t)gc_block->user_mcode) { 95 | 96 | case 401: 97 | if(gc_block->words.q) { 98 | if(isnanf(gc_block->values.q)) 99 | state = Status_BadNumberFormat; 100 | else { 101 | if(!isintf(gc_block->values.q) || gc_block->values.q < 0.0f || (probe_mode_t)gc_block->values.q > ProbeMode_MaxValue) 102 | state = Status_GcodeValueOutOfRange; 103 | gc_block->words.q = Off; 104 | } 105 | } 106 | break; 107 | 108 | case 402: 109 | break; 110 | 111 | default: 112 | state = Status_Unhandled; 113 | break; 114 | } 115 | 116 | return state == Status_Unhandled && user_mcode.validate ? user_mcode.validate(gc_block) : state; 117 | } 118 | 119 | static void mcode_execute (uint_fast16_t state, parser_block_t *gc_block) 120 | { 121 | bool handled = true; 122 | 123 | if (state != STATE_CHECK_MODE) 124 | switch((uint16_t)gc_block->user_mcode) { 125 | 126 | case 401: 127 | if(gc_block->words.q) 128 | probe_mode = (probe_mode_t)gc_block->values.q; 129 | else 130 | use_probe2 = true; 131 | break; 132 | 133 | case 402: 134 | use_probe2 = false; 135 | break; 136 | 137 | default: 138 | handled = false; 139 | break; 140 | } 141 | 142 | if(!handled && user_mcode.execute) 143 | user_mcode.execute(state, gc_block); 144 | } 145 | 146 | // When called from "normal" probing tool is always NULL, when called from within 147 | // a tool change sequence (M6) then tool is a pointer to the selected tool. 148 | bool probeToolSetter (tool_data_t *tool, coord_data_t *position, bool at_g59_3, bool on) 149 | { 150 | if(on_probe_toolsetter) 151 | on_probe_toolsetter(tool, position, at_g59_3, on); 152 | 153 | if(on) switch(probe_mode) { 154 | 155 | case ProbeMode_AtG59_3: 156 | use_probe2 = at_g59_3; 157 | break; 158 | 159 | case ProbeMode_ToolChangeAtG59_3: 160 | use_probe2 = tool != NULL && at_g59_3; 161 | break; 162 | 163 | case ProbeMode_ToolChange: 164 | use_probe2 = tool != NULL; 165 | break; 166 | 167 | case ProbeMode_Never: 168 | use_probe2 = false; 169 | break; 170 | 171 | case ProbeMode_Always: 172 | use_probe2 = true; 173 | break; 174 | 175 | default: 176 | break; 177 | 178 | } else if(probe_mode != ProbeMode_Manual) 179 | use_probe2 = false; 180 | 181 | return use_probe2; 182 | } 183 | 184 | static control_signals_t signalsGetState (void) 185 | { 186 | control_signals_t signals = control_signals_get_state(); 187 | 188 | signals.probe_overtravel = hal.port.wait_on_input(Port_Digital, overtravel_port, WaitMode_Immediate, 0.0f); 189 | 190 | return signals; 191 | } 192 | 193 | static void on_overtravel (uint8_t port, bool state) 194 | { 195 | hal.control.interrupt_callback(signalsGetState()); 196 | } 197 | 198 | // Sets up the probe pin invert mask to 199 | // appropriately set the pin logic according to setting for normal-high/normal-low operation 200 | // and the probing cycle modes for toward-workpiece/away-from-workpiece. 201 | static void probeConfigure (bool is_probe_away, bool probing) 202 | { 203 | xbar_t *portinfo = hal.port.get_pin_info(Port_Digital, Port_Input, probe_port); 204 | 205 | probe.inverted = is_probe_away ? !portinfo->mode.inverted : portinfo->mode.inverted; 206 | probe.triggered = Off; 207 | probe.is_probing = probing; 208 | 209 | probe_configure(is_probe_away, probing); 210 | } 211 | 212 | // Returns the probe connected and triggered pin states. 213 | static probe_state_t probeGetState (void) 214 | { 215 | probe_state_t state = probe_get_state(); 216 | 217 | if(use_probe2) { 218 | state.triggered = hal.port.wait_on_input(Port_Digital, probe_port, WaitMode_Immediate, 0.0f) ^ probe.inverted; 219 | state.connected = probe.connected; 220 | } 221 | 222 | return state; 223 | } 224 | 225 | static void probeReset (void) 226 | { 227 | driver_reset(); 228 | 229 | use_probe2 = false; 230 | } 231 | 232 | static status_code_t set_port (setting_id_t setting, float value) 233 | { 234 | status_code_t status; 235 | 236 | if((status = isintf(value) ? Status_OK : Status_BadNumberFormat) == Status_OK) 237 | switch(setting) { 238 | 239 | case PROBE_PLUGIN_SETTING: 240 | probe2_settings.probe_port = value < 0.0f ? 255 : (uint8_t)value; 241 | break; 242 | 243 | case PROBE_PLUGIN_SETTING1: 244 | probe2_settings.overtravel_port = value < 0.0f ? 255 : (uint8_t)value; 245 | break; 246 | 247 | default: 248 | break; 249 | } 250 | 251 | return status; 252 | } 253 | 254 | static float get_port (setting_id_t setting) 255 | { 256 | float value = 0.0f; 257 | 258 | switch(setting) { 259 | 260 | case PROBE_PLUGIN_SETTING: 261 | value = probe2_settings.probe_port >= n_ports ? -1.0f : (float)probe2_settings.probe_port; 262 | break; 263 | 264 | case PROBE_PLUGIN_SETTING1: 265 | value = probe2_settings.overtravel_port >= n_ports ? -1.0f : (float)probe2_settings.overtravel_port; 266 | break; 267 | 268 | default: 269 | break; 270 | } 271 | 272 | return value; 273 | } 274 | 275 | // Add info about our settings for $help and enumerations. 276 | // Potentially used by senders for settings UI. 277 | static const setting_detail_t user_settings[] = { 278 | { PROBE_PLUGIN_SETTING, Group_Probing, "Probe 2 aux port", NULL, Format_Decimal, "-#0", "-1", max_port, Setting_NonCoreFn, set_port, get_port, NULL, { .reboot_required = On } }, 279 | { PROBE_PLUGIN_SETTING1, Group_Probing, "Probe 2 overtravel aux port", NULL, Format_Decimal, "-#0", "-1", max_port, Setting_NonCoreFn, set_port, get_port, NULL, { .reboot_required = On } } 280 | }; 281 | 282 | #ifndef NO_SETTINGS_DESCRIPTIONS 283 | 284 | static const setting_descr_t probe2_settings_descr[] = { 285 | { PROBE_PLUGIN_SETTING, "Aux port number to use for second probe input. Set to -1 to disable." }, 286 | { PROBE_PLUGIN_SETTING1, "Aux port number to use for second probe overtravel input. Set to -1 to disable.\\n" 287 | "If asserted Z hard limit alarm will raised.\\n\\n" 288 | "NOTE: if input inversion is changed with $370 a hard reset is required to reconfigure the port!" 289 | } 290 | }; 291 | 292 | #endif 293 | 294 | // Write settings to non volatile storage (NVS). 295 | static void plugin_settings_save (void) 296 | { 297 | hal.nvs.memcpy_to_nvs(nvs_address, (uint8_t *)&probe2_settings, sizeof(probe2_settings_t), true); 298 | } 299 | 300 | // Restore default settings and write to non volatile storage (NVS). 301 | // Default is highest numbered free port. 302 | static void plugin_settings_restore (void) 303 | { 304 | probe2_settings.overtravel_port = 0xFF; 305 | 306 | // Find highest numbered port, or keep the current one if found. 307 | probe2_settings.probe_port = ioport_find_free(Port_Digital, Port_Input, (pin_cap_t){ .claimable = On }, "Probe 2"); 308 | 309 | hal.nvs.memcpy_to_nvs(nvs_address, (uint8_t *)&probe2_settings, sizeof(probe2_settings_t), true); 310 | } 311 | 312 | // Load our settings from non volatile storage (NVS). 313 | // If load fails restore to default values. 314 | static void plugin_settings_load (void) 315 | { 316 | if(hal.nvs.memcpy_from_nvs((uint8_t *)&probe2_settings, nvs_address, sizeof(probe2_settings_t), true) != NVS_TransferResult_OK) 317 | plugin_settings_restore(); 318 | 319 | // Sanity checks 320 | if(probe2_settings.probe_port >= n_ports) 321 | probe2_settings.probe_port = 0xFF; 322 | if(probe2_settings.overtravel_port >= n_ports) 323 | probe2_settings.overtravel_port = 0xFF; 324 | 325 | if((probe_port = probe2_settings.probe_port) != 0xFF) { 326 | 327 | xbar_t *portinfo = ioport_get_info(Port_Digital, Port_Input, probe_port); 328 | 329 | if(portinfo && ioport_claim(Port_Digital, Port_Input, &probe_port, "Probe 2")) { 330 | 331 | memcpy(&user_mcode, &grbl.user_mcode, sizeof(user_mcode_ptrs_t)); 332 | 333 | grbl.user_mcode.check = mcode_check; 334 | grbl.user_mcode.validate = mcode_validate; 335 | grbl.user_mcode.execute = mcode_execute; 336 | 337 | driver_reset = hal.driver_reset; 338 | hal.driver_reset = probeReset; 339 | 340 | probe_configure = hal.probe.configure; 341 | hal.probe.configure = probeConfigure; 342 | 343 | probe_get_state = hal.probe.get_state; 344 | hal.probe.get_state = probeGetState; 345 | 346 | on_probe_toolsetter = grbl.on_probe_toolsetter; 347 | grbl.on_probe_toolsetter = probeToolSetter; 348 | 349 | } else 350 | task_run_on_startup(report_warning, "Probe select plugin: probe port is not available"); 351 | } 352 | 353 | if((overtravel_port = probe2_settings.overtravel_port) != 0xFF) { 354 | 355 | xbar_t *portinfo = ioport_get_info(Port_Digital, Port_Input, overtravel_port); 356 | if(portinfo && (portinfo->cap.irq_mode & IRQ_Mode_Change) && ioport_claim(Port_Digital, Port_Input, &overtravel_port, "Probe 2 overtravel")) { 357 | 358 | hal.port.register_interrupt_handler(overtravel_port, portinfo->mode.inverted ? IRQ_Mode_Falling : IRQ_Mode_Rising, on_overtravel); 359 | 360 | control_signals_get_state = hal.control.get_state; 361 | hal.control.get_state = signalsGetState; 362 | } else 363 | task_run_on_startup(report_warning, "Probe select plugin: overtravel port is not available"); 364 | } 365 | } 366 | 367 | static void report_options (bool newopt) 368 | { 369 | on_report_options(newopt); 370 | 371 | if(!newopt) 372 | report_plugin("Probe select 2", "0.05"); 373 | } 374 | 375 | void my_plugin_init (void) 376 | { 377 | // Settings descriptor used by the core when interacting with this plugin. 378 | static setting_details_t setting_details = { 379 | .settings = user_settings, 380 | .n_settings = sizeof(user_settings) / sizeof(setting_detail_t), 381 | #ifndef NO_SETTINGS_DESCRIPTIONS 382 | .descriptions = probe2_settings_descr, 383 | .n_descriptions = sizeof(probe2_settings_descr) / sizeof(setting_descr_t), 384 | #endif 385 | .save = plugin_settings_save, 386 | .load = plugin_settings_load, 387 | .restore = plugin_settings_restore 388 | }; 389 | 390 | if(ioport_can_claim_explicit() && 391 | (n_ports = ioports_available(Port_Digital, Port_Input)) && 392 | (nvs_address = nvs_alloc(sizeof(probe2_settings_t)))) { 393 | 394 | // Used for setting value validation 395 | strcpy(max_port, uitoa(n_ports - 1)); 396 | 397 | on_report_options = grbl.on_report_options; 398 | grbl.on_report_options = report_options; 399 | 400 | settings_register(&setting_details); 401 | 402 | } else 403 | task_run_on_startup(report_warning, "Probe select plugin failed to initialize!"); 404 | } 405 | -------------------------------------------------------------------------------- /my_plugin/README.md: -------------------------------------------------------------------------------- 1 | ## grblHAL my_plugin templates and utilities 2 | 3 | To add a plugin to a build copy _my_plugin.c_ to the folder where _driver.c_ is found. 4 | Do NOT copy _CMakeLists.txt_ as this is used exclusively by the [Web Builder](http://svn.io-engineering.com:8080/) and may interfere with local builds. 5 | 6 | __Note:__ To add a plugin to the firmware for RP2040 and ESP32 builds enable the _AddMyPlugin_ option in _CMakeLists.txt_. 7 | 8 | --- 9 | 2023-08-09 10 | -------------------------------------------------------------------------------- /my_plugin/Realtime_report_timestamp/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(my_plugin INTERFACE) 2 | 3 | target_sources(my_plugin INTERFACE 4 | ${CMAKE_CURRENT_LIST_DIR}/my_plugin.c 5 | ) 6 | 7 | target_include_directories(my_plugin INTERFACE ${CMAKE_CURRENT_LIST_DIR}) 8 | -------------------------------------------------------------------------------- /my_plugin/Realtime_report_timestamp/my_plugin.c: -------------------------------------------------------------------------------- 1 | /* 2 | my_plugin.c.c - Real time report timestamp 3 | 4 | Part of grblHAL 5 | 6 | Public domain. 7 | This code is is distributed in the hope that it will be useful, 8 | but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 10 | 11 | Adds timestamp in |TS element to real time report, millisecond resolution. 12 | 13 | Use: 14 | M101 to reset to 0 and keep current mode. 15 | M101P0 to reset to zero and exit synchronous mode. 16 | M101P1 to reset to zero and enter synchronous mode (default). 17 | M101P2 switch to use RTC in non synchronous mode when RTC is available. 18 | M101P3 switch to use RTC in synchronous mode when RTC is available. 19 | 20 | NOTE: be sure to set the RTC before switching to RTC output. 21 | 22 | When synchrounous mode is active delay reset until buffered motions has been completed. 23 | */ 24 | 25 | #include 26 | #include 27 | #include 28 | 29 | #include "grbl/hal.h" 30 | 31 | static uint32_t offset = 0; 32 | static bool mcode_sync = true, use_rtc = false; 33 | static on_realtime_report_ptr on_realtime_report; 34 | static on_report_options_ptr on_report_options; 35 | static user_mcode_ptrs_t user_mcode; 36 | 37 | static user_mcode_type_t check (user_mcode_t mcode) 38 | { 39 | return mcode == UserMCode_Generic1 40 | ? UserMCode_Normal 41 | : (user_mcode.check ? user_mcode.check(mcode) : UserMCode_Unsupported); 42 | } 43 | 44 | static status_code_t validate (parser_block_t *gc_block) 45 | { 46 | status_code_t state = Status_OK; 47 | 48 | switch(gc_block->user_mcode) { 49 | 50 | case UserMCode_Generic1: 51 | if(gc_block->words.p) { 52 | if(!isintf(gc_block->values.p)) 53 | state = Status_BadNumberFormat; 54 | else { 55 | mcode_sync = (uint32_t)gc_block->values.p & 0x01; 56 | if((uint32_t)gc_block->values.p & 0x02) { 57 | struct tm time; 58 | if(!(use_rtc = hal.rtc.get_datetime != NULL && !!hal.rtc.get_datetime(&time))) 59 | state = Status_InvalidStatement; 60 | } else 61 | use_rtc = false; 62 | } 63 | gc_block->words.p = Off; 64 | } 65 | gc_block->user_mcode_sync = mcode_sync; 66 | break; 67 | 68 | default: 69 | state = Status_Unhandled; 70 | break; 71 | } 72 | 73 | return state == Status_Unhandled && user_mcode.validate ? user_mcode.validate(gc_block) : state; 74 | } 75 | 76 | static void execute (sys_state_t state, parser_block_t *gc_block) { 77 | 78 | bool handled = true; 79 | 80 | switch(gc_block->user_mcode) { 81 | 82 | case UserMCode_Generic1: 83 | offset = hal.get_elapsed_ticks(); 84 | break; 85 | 86 | default: 87 | handled = false; 88 | break; 89 | } 90 | 91 | 92 | if(!handled && user_mcode.execute) 93 | user_mcode.execute(state, gc_block); 94 | } 95 | 96 | static void onRealtimeReport (stream_write_ptr stream_write, report_tracking_flags_t report) 97 | { 98 | static char buf[30] = {0}; 99 | 100 | if(use_rtc) { 101 | 102 | struct tm time; 103 | if(!!hal.rtc.get_datetime(&time)) 104 | sprintf(buf, "|TS:%d:%02d:%02d", time.tm_hour, time.tm_min, time.tm_sec); 105 | else 106 | *buf = '\0'; 107 | 108 | } else { 109 | 110 | uint32_t ts = hal.get_elapsed_ticks() - offset; 111 | uint32_t ms, s; 112 | 113 | ms = ts % 1000; 114 | ts -= ms; 115 | s = (ts % 60000); 116 | ts -= s; 117 | 118 | sprintf(buf, "|TS:%lu:%02lu,%lu", ts / 60000, s / 1000, ms); 119 | } 120 | 121 | if(*buf) 122 | stream_write(buf); 123 | 124 | if(on_realtime_report) 125 | on_realtime_report(stream_write, report); 126 | } 127 | 128 | static void onReportOptions (bool newopt) 129 | { 130 | on_report_options(newopt); 131 | 132 | if(!newopt) 133 | report_plugin("RT timestamp", "0.04"); 134 | } 135 | 136 | void my_plugin_init (void) 137 | { 138 | on_report_options = grbl.on_report_options; 139 | grbl.on_report_options = onReportOptions; 140 | 141 | on_realtime_report = grbl.on_realtime_report; 142 | grbl.on_realtime_report = onRealtimeReport; 143 | 144 | memcpy(&user_mcode, &grbl.user_mcode, sizeof(user_mcode_ptrs_t)); 145 | 146 | grbl.user_mcode.check = check; 147 | grbl.user_mcode.validate = validate; 148 | grbl.user_mcode.execute = execute; 149 | } 150 | -------------------------------------------------------------------------------- /my_plugin/STM32F411_blinky/my_plugin.c: -------------------------------------------------------------------------------- 1 | /* 2 | my_plugin.c - user defined plugin that blinks the LED on a STM32F411 Blackpill 3 | 4 | Part of grblHAL 5 | 6 | Public domain 7 | This code is is distributed in the hope that it will be useful, 8 | but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 10 | */ 11 | 12 | #include "driver.h" 13 | #include "grbl/task.h" 14 | 15 | static on_report_options_ptr on_report_options; 16 | 17 | // Add info about our plugin to the $I report. 18 | static void on_report_my_options (bool newopt) 19 | { 20 | on_report_options(newopt); 21 | 22 | if(!newopt) 23 | hal.stream.write("[PLUGIN:Blink LED v2.00]" ASCII_EOL); 24 | } 25 | 26 | static void blink_led (void *data) 27 | { 28 | static bool led_on = false; 29 | 30 | if((led_on = !led_on)) 31 | GPIOC->ODR |= GPIO_PIN_13; 32 | else 33 | GPIOC->ODR &= ~GPIO_PIN_13; 34 | 35 | // Reschedule the blink LED function 36 | task_add_delayed(blink_led, NULL, 500); 37 | } 38 | 39 | void my_plugin_init (void) 40 | { 41 | // Add info about our plugin to the $I report. 42 | on_report_options = grbl.on_report_options; 43 | grbl.on_report_options = on_report_my_options; 44 | 45 | // Schedule blink LED function to be run from the grblHAL foreground process 46 | task_add_delayed(blink_led, NULL, 500); // 500 ms 47 | 48 | // Enable PC13 as output 49 | GPIO_InitTypeDef GPIO_InitStructure = { 50 | .Mode = GPIO_MODE_OUTPUT_PP, 51 | .Speed = GPIO_SPEED_FREQ_VERY_HIGH, 52 | .Pin = GPIO_PIN_13, 53 | }; 54 | HAL_GPIO_Init(GPIOC, &GPIO_InitStructure); 55 | } 56 | -------------------------------------------------------------------------------- /my_plugin/Set_output_on_feed_hold/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(my_plugin INTERFACE) 2 | 3 | target_sources(my_plugin INTERFACE 4 | ${CMAKE_CURRENT_LIST_DIR}/my_plugin.c 5 | ) 6 | 7 | target_include_directories(my_plugin INTERFACE ${CMAKE_CURRENT_LIST_DIR}) 8 | -------------------------------------------------------------------------------- /my_plugin/Set_output_on_feed_hold/my_plugin.c: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | my_plugin.c - plugin template for setting auxiliary output on feed hold 4 | 5 | Part of grblHAL 6 | 7 | Public domain. 8 | This code is is distributed in the hope that it will be useful, 9 | but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 11 | */ 12 | 13 | #include 14 | 15 | #include "driver.h" 16 | #include "grbl/task.h" 17 | #include "grbl/nvs_buffer.h" 18 | 19 | #define PLUGIN_SETTING Setting_UserDefined_9 20 | 21 | static uint8_t port; 22 | static uint8_t n_ports; 23 | static char max_port[4]; 24 | 25 | typedef struct { 26 | uint8_t port; 27 | } plugin_settings_t; 28 | 29 | static nvs_address_t nvs_address; 30 | static plugin_settings_t plugin_settings; 31 | static on_state_change_ptr on_state_change; 32 | static on_report_options_ptr on_report_options; 33 | 34 | static void onStateChanged (sys_state_t state) 35 | { 36 | static sys_state_t last_state = STATE_IDLE; 37 | 38 | if(state != last_state) { 39 | last_state = state; 40 | hal.port.digital_out(port, state == STATE_HOLD); 41 | } 42 | 43 | if(on_state_change) // Call previous function in the chain. 44 | on_state_change(state); 45 | } 46 | 47 | static status_code_t set_port (setting_id_t setting, float value) 48 | { 49 | if(!isintf(value)) 50 | return Status_BadNumberFormat; 51 | 52 | plugin_settings.port = value < 0.0f ? 0xFF : (uint8_t)value; 53 | 54 | return Status_OK; 55 | } 56 | 57 | static float get_port (setting_id_t setting) 58 | { 59 | return plugin_settings.port >= n_ports ? -1.0f : (float) plugin_settings.port; 60 | } 61 | 62 | static const setting_detail_t user_settings[] = { 63 | { PLUGIN_SETTING, Group_AuxPorts, "Feed hold aux port", NULL, Format_Decimal, "-#0", "-1", max_port, Setting_NonCoreFn, set_port, get_port, NULL, { .reboot_required = On } } 64 | }; 65 | 66 | #ifndef NO_SETTINGS_DESCRIPTIONS 67 | 68 | static const setting_descr_t plugin_settings_descr[] = { 69 | { PLUGIN_SETTING, "Aux port number to use for feed hold output. Set to -1 to disable." } 70 | }; 71 | 72 | #endif 73 | 74 | // Write settings to non volatile storage (NVS). 75 | static void plugin_settings_save (void) 76 | { 77 | hal.nvs.memcpy_to_nvs(nvs_address, (uint8_t *)&plugin_settings, sizeof(plugin_settings_t), true); 78 | } 79 | 80 | // Restore default settings and write to non volatile storage (NVS). 81 | static void plugin_settings_restore (void) 82 | { 83 | // Find highest numbered port, or keep the current one if found. 84 | plugin_settings.port = ioport_find_free(Port_Digital, Port_Output, (pin_cap_t){ .claimable = On }, "Feed hold out"); 85 | 86 | hal.nvs.memcpy_to_nvs(nvs_address, (uint8_t *)&plugin_settings, sizeof(plugin_settings_t), true); 87 | } 88 | 89 | // Load our settings from non volatile storage (NVS). 90 | // If load fails restore to default values. 91 | static void plugin_settings_load (void) 92 | { 93 | if(hal.nvs.memcpy_from_nvs((uint8_t *)&plugin_settings, nvs_address, sizeof(plugin_settings_t), true) != NVS_TransferResult_OK) 94 | plugin_settings_restore(); 95 | 96 | // Sanity check 97 | if(plugin_settings.port >= n_ports) 98 | plugin_settings.port = 0xFF; 99 | 100 | if((port = plugin_settings.port) != 0xFF) { 101 | if(ioport_claim(Port_Digital, Port_Output, &port, "Feed hold out")) { 102 | on_state_change = grbl.on_state_change; // Subscribe to the state changed event by saving away the original 103 | grbl.on_state_change = onStateChanged; // function pointer and adding ours to the chain. 104 | } else 105 | task_run_on_startup(report_warning, "Feed hold plugin: configured port number is not available"); 106 | } 107 | } 108 | 109 | static void onReportOptions (bool newopt) 110 | { 111 | on_report_options(newopt); // Call previous function in the chain. 112 | 113 | if(!newopt) // Add info about us to the $I report. 114 | report_plugin("PLUGIN Template 2", "v0.03"); 115 | } 116 | 117 | void my_plugin_init (void) 118 | { 119 | // Settings descriptor used by the core when interacting with this plugin. 120 | static setting_details_t setting_details = { 121 | .settings = user_settings, 122 | .n_settings = sizeof(user_settings) / sizeof(setting_detail_t), 123 | #ifndef NO_SETTINGS_DESCRIPTIONS 124 | .descriptions = plugin_settings_descr, 125 | .n_descriptions = sizeof(plugin_settings_descr) / sizeof(setting_descr_t), 126 | #endif 127 | .save = plugin_settings_save, 128 | .load = plugin_settings_load, 129 | .restore = plugin_settings_restore 130 | }; 131 | 132 | if(ioport_can_claim_explicit() && 133 | (n_ports = ioports_available(Port_Digital, Port_Output)) && 134 | (nvs_address = nvs_alloc(sizeof(plugin_settings_t)))) { 135 | 136 | strcpy(max_port, uitoa(n_ports - 1)); 137 | 138 | settings_register(&setting_details); 139 | 140 | on_report_options = grbl.on_report_options; // Add our plugin to to the options report chain 141 | grbl.on_report_options = onReportOptions; // to tell the user we are active. 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /my_plugin/Solenoid_spindle/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(my_plugin INTERFACE) 2 | 3 | target_sources(my_plugin INTERFACE 4 | ${CMAKE_CURRENT_LIST_DIR}/my_plugin.c 5 | ) 6 | 7 | target_include_directories(my_plugin INTERFACE ${CMAKE_CURRENT_LIST_DIR}) 8 | -------------------------------------------------------------------------------- /my_plugin/Solenoid_spindle/my_plugin.c: -------------------------------------------------------------------------------- 1 | /* 2 | my_plugin.c - sets PWM output to full power on spindle on, reduces it after a short delay. 3 | 4 | Part of grblHAL 5 | 6 | Public domain 7 | This code is is distributed in the hope that it will be useful, 8 | but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 10 | */ 11 | 12 | #include "driver.h" 13 | 14 | #include 15 | 16 | #define SOLENOID_HOLD_DELAY 50 // ms 17 | #define SOLENOID_HOLD_FACTOR 0.25f 18 | 19 | static uint32_t power_down = 0; 20 | static spindle_ptrs_t pwm_spindle; 21 | static on_report_options_ptr on_report_options; 22 | static on_execute_realtime_ptr on_execute_realtime; 23 | static on_spindle_select_ptr on_spindle_select; 24 | 25 | static void solenoid_reduce_current (sys_state_t state) 26 | { 27 | if(power_down && hal.get_elapsed_ticks() - power_down >= SOLENOID_HOLD_DELAY) 28 | pwm_spindle.set_state(&pwm_spindle, pwm_spindle.get_state(&pwm_spindle), pwm_spindle.rpm_max * SOLENOID_HOLD_FACTOR); 29 | 30 | on_execute_realtime(state); 31 | } 32 | 33 | static void solenoid_set_state (spindle_ptrs_t *spindle, spindle_state_t state, float rpm) 34 | { 35 | power_down = state.on && rpm > 0.0f ? hal.get_elapsed_ticks() : 0; 36 | 37 | pwm_spindle.set_state(&pwm_spindle, state, power_down ? pwm_spindle.rpm_max : rpm); 38 | } 39 | 40 | static bool solenoid_spindle_select (spindle_ptrs_t *spindle) 41 | { 42 | if(spindle->type == SpindleType_PWM) { 43 | memcpy(&pwm_spindle, spindle, sizeof(spindle_ptrs_t)); 44 | spindle->set_state = solenoid_set_state; 45 | spindle->cap.laser = Off; 46 | } 47 | 48 | return on_spindle_select == NULL || on_spindle_select(spindle); 49 | } 50 | 51 | static void onReportOptions (bool newopt) 52 | { 53 | on_report_options(newopt); 54 | 55 | if(!newopt) 56 | report_plugin("Solenoid spindle", "1.04"); 57 | } 58 | 59 | 60 | void my_plugin_init (void) 61 | { 62 | on_report_options = grbl.on_report_options; 63 | grbl.on_report_options = onReportOptions; 64 | 65 | on_execute_realtime = grbl.on_execute_realtime; 66 | grbl.on_execute_realtime = solenoid_reduce_current; 67 | 68 | on_spindle_select = grbl.on_spindle_select; 69 | grbl.on_spindle_select = solenoid_spindle_select; 70 | } 71 | -------------------------------------------------------------------------------- /my_plugin/Stepper_enable_control/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(my_plugin INTERFACE) 2 | 3 | target_sources(my_plugin INTERFACE 4 | ${CMAKE_CURRENT_LIST_DIR}/my_plugin.c 5 | ) 6 | 7 | target_include_directories(my_plugin INTERFACE ${CMAKE_CURRENT_LIST_DIR}) 8 | -------------------------------------------------------------------------------- /my_plugin/Stepper_enable_control/my_plugin.c: -------------------------------------------------------------------------------- 1 | /* 2 | my_plugin.c - plugin for M17 & M18 (M84) Marlin style stepper enable/disable commands. 3 | 4 | Part of grblHAL 5 | 6 | Public domain. 7 | This code is is distributed in the hope that it will be useful, 8 | but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 10 | 11 | Usage: 12 | M17[X][Y][Z] - enable steppers 13 | M18[X][Y][Z][S] - disable steppers 14 | M84[X][Y][Z][S] - disable steppers 15 | 16 | If no axis words are specified all axes are enabled/disabled 17 | If no delay is specified disable is immediate, else delay is number of seconds. 18 | 19 | https://marlinfw.org/docs/gcode/M017.html 20 | https://marlinfw.org/docs/gcode/M018.html 21 | */ 22 | 23 | #include 24 | #include 25 | 26 | #include "grbl/hal.h" 27 | #include "grbl/protocol.h" 28 | 29 | static bool await_disable = false; 30 | static user_mcode_ptrs_t user_mcode; 31 | static on_report_options_ptr on_report_options; 32 | static stepper_enable_ptr stepper_enable; 33 | static axes_signals_t stepper_enabled = {0}; 34 | 35 | static void disable_steppers (void *data); 36 | 37 | static void stepperEnable (axes_signals_t enable, bool hold) 38 | { 39 | if(await_disable) { 40 | await_disable = false; 41 | task_delete(disable_steppers, NULL); 42 | } 43 | 44 | stepper_enabled.mask = enable.mask; 45 | 46 | stepper_enable(enable, hold); 47 | } 48 | 49 | static void disable_steppers (void *data) 50 | { 51 | await_disable = false; 52 | stepperEnable(stepper_enabled, false); 53 | } 54 | 55 | static user_mcode_type_t mcode_check (user_mcode_t mcode) 56 | { 57 | return mcode == (user_mcode_t)17 || mcode == (user_mcode_t)18 || mcode == (user_mcode_t)84 58 | ? UserMCode_NoValueWords 59 | : (user_mcode.check ? user_mcode.check(mcode) : UserMCode_Unsupported); 60 | } 61 | 62 | static status_code_t mcode_validate (parser_block_t *gc_block) 63 | { 64 | status_code_t state = Status_OK; 65 | 66 | switch((uint16_t)gc_block->user_mcode) { 67 | 68 | case 17: 69 | gc_block->words.x = gc_block->words.y = gc_block->words.z = Off; 70 | #ifdef A_AXIS 71 | gc_block->words.a = Off; 72 | #endif 73 | #ifdef B_AXIS 74 | gc_block->words.b = Off; 75 | #endif 76 | #ifdef C_AXIS 77 | gc_block->words.c = Off; 78 | #endif 79 | break; 80 | 81 | case 18: 82 | case 84: 83 | if(gc_block->words.s && isnanf(gc_block->values.s)) 84 | state = Status_BadNumberFormat; 85 | gc_block->words.s = gc_block->words.x = gc_block->words.y = gc_block->words.z = Off; 86 | #ifdef A_AXIS 87 | gc_block->words.a = Off; 88 | #endif 89 | #ifdef B_AXIS 90 | gc_block->words.b = Off; 91 | #endif 92 | #ifdef C_AXIS 93 | gc_block->words.c = Off; 94 | #endif 95 | break; 96 | 97 | default: 98 | state = Status_Unhandled; 99 | break; 100 | } 101 | 102 | return state == Status_Unhandled && user_mcode.validate ? user_mcode.validate(gc_block) : state; 103 | } 104 | 105 | static void mcode_execute (uint_fast16_t state, parser_block_t *gc_block) 106 | { 107 | bool handled = true; 108 | 109 | static const parameter_words_t axis_words = { 110 | .x = On, 111 | .y = On, 112 | .z = On 113 | #ifdef A_AXIS 114 | , .a = On 115 | #endif 116 | #ifdef B_AXIS 117 | , .b = On 118 | #endif 119 | #ifdef C_AXIS 120 | , .c = On 121 | #endif 122 | }; 123 | 124 | if (state != STATE_CHECK_MODE) 125 | switch((uint16_t)gc_block->user_mcode) { 126 | 127 | case 17: // stepper enable 128 | { 129 | if(gc_block->words.mask & axis_words.mask) { 130 | if(gc_block->words.x) 131 | stepper_enabled.x = On; 132 | if(gc_block->words.y) 133 | stepper_enabled.y = On; 134 | if(gc_block->words.z) 135 | stepper_enabled.z = On; 136 | #ifdef A_AXIS 137 | if(gc_block->words.a) 138 | stepper_enabled.a = On; 139 | #endif 140 | #ifdef B_AXIS 141 | if(gc_block->words.b) 142 | stepper_enabled.b = On; 143 | #endif 144 | #ifdef C_AXIS 145 | if(gc_block->words.c) 146 | stepper_enabled.c = On; 147 | #endif 148 | } else 149 | stepper_enabled.mask = AXES_BITMASK; 150 | 151 | stepperEnable(stepper_enabled, false); 152 | } 153 | break; 154 | 155 | case 18: // stepper disable 156 | case 84: 157 | { 158 | if(gc_block->words.mask & axis_words.mask) { 159 | if(gc_block->words.x) 160 | stepper_enabled.x = Off; 161 | if(gc_block->words.y) 162 | stepper_enabled.y = Off; 163 | if(gc_block->words.z) 164 | stepper_enabled.z = Off; 165 | #ifdef A_AXIS 166 | if(gc_block->words.a) 167 | stepper_enabled.a = Off; 168 | #endif 169 | #ifdef B_AXIS 170 | if(gc_block->words.b) 171 | stepper_enabled.b = Off; 172 | #endif 173 | #ifdef C_AXIS 174 | if(gc_block->words.c) 175 | stepper_enabled.c = Off; 176 | #endif 177 | } else 178 | stepper_enabled.mask = 0; 179 | 180 | if(gc_block->words.s && gc_block->values.s > 0.0f) { 181 | if(!await_disable) 182 | await_disable = task_add_delayed(disable_steppers, NULL, (uint32_t)gc_block->values.s * 1000); 183 | } else 184 | stepperEnable(stepper_enabled, false); 185 | } 186 | break; 187 | 188 | default: 189 | handled = false; 190 | break; 191 | } 192 | 193 | if(!handled && user_mcode.execute) 194 | user_mcode.execute(state, gc_block); 195 | } 196 | 197 | static void reportOptions (bool newopt) 198 | { 199 | on_report_options(newopt); 200 | 201 | if(!newopt) 202 | report_plugin("Stepper enable", "0.03"); 203 | } 204 | 205 | void my_plugin_init (void) 206 | { 207 | memcpy(&user_mcode, &grbl.user_mcode, sizeof(user_mcode_ptrs_t)); 208 | 209 | grbl.user_mcode.check = mcode_check; 210 | grbl.user_mcode.validate = mcode_validate; 211 | grbl.user_mcode.execute = mcode_execute; 212 | 213 | stepper_enable = hal.stepper.enable; 214 | hal.stepper.enable = stepperEnable; 215 | 216 | on_report_options = grbl.on_report_options; 217 | grbl.on_report_options = reportOptions; 218 | } 219 | -------------------------------------------------------------------------------- /my_plugin/hpgl/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(hpgl INTERFACE) 2 | 3 | target_sources(hpgl INTERFACE 4 | ${CMAKE_CURRENT_LIST_DIR}/motori.c 5 | ${CMAKE_CURRENT_LIST_DIR}/hpgl.c 6 | ${CMAKE_CURRENT_LIST_DIR}/arc.c 7 | ${CMAKE_CURRENT_LIST_DIR}/clip.c 8 | ${CMAKE_CURRENT_LIST_DIR}/charset0.c 9 | ${CMAKE_CURRENT_LIST_DIR}/font173.c 10 | ${CMAKE_CURRENT_LIST_DIR}/htext.c 11 | ${CMAKE_CURRENT_LIST_DIR}/scale.c 12 | ) 13 | 14 | target_include_directories(hpgl INTERFACE ${CMAKE_CURRENT_LIST_DIR}) 15 | -------------------------------------------------------------------------------- /my_plugin/hpgl/README.md: -------------------------------------------------------------------------------- 1 | ## Motöri the Plotter as a grblHAL plugin 2 | 3 | Original code by [Viacheslav Slavinsky](http://sensi.org/~svo/motori/), modified and refactored for grblHAL. 4 | 5 | This plugin adds a HPGL interpreter to grblHAL making grblHAL multilingual. 6 | No changes to the grblHAL code was needed to add this plugin! 7 | 8 | The `$HPGL` command disables the grblHAL gcode interpreter and activates the HPGL interpreter lifted from Motöri. 9 | Use `+` to exit back to normal operation. 10 | 11 | I made the plugin for my [C.ITOH CX-600 plotter](https://hackaday.io/project/183600-citoh-cx-6000-plotter-upgrade) and as an example for how the grblHAL APIs can be used. 12 | 13 | --- 14 | 2022-01-15 15 | -------------------------------------------------------------------------------- /my_plugin/hpgl/arc.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "hpgl.h" 4 | #include "arc.h" 5 | #include "scale.h" 6 | 7 | static int16_t arc_step; 8 | static uint_fast8_t is_wedge = 0; 9 | static float arc_stepangle, arc_xc, arc_yc, arc_a0, arc_r, arc_phi; 10 | 11 | static bool arc_cfg (user_point_t user_loc) 12 | { 13 | arc_phi = hpgl_state.numpad[2] * M_PI / 180.0f; 14 | 15 | arc_stepangle = fabsf(hpgl_state.numpad[3]); 16 | if (arc_phi < -0.0f) 17 | arc_stepangle = -arc_stepangle; 18 | arc_stepangle *= M_PI / 180.0f; 19 | 20 | arc_step = 0; 21 | arc_xc = hpgl_state.numpad[0]; 22 | arc_yc = hpgl_state.numpad[1]; 23 | arc_a0 = atan2f(hpgl_state.user_loc.y - arc_yc, hpgl_state.user_loc.x - arc_xc); 24 | if (arc_a0 < -0.0f) 25 | arc_a0 += 2.0f * M_PI; 26 | 27 | arc_r = hypotf(hpgl_state.user_loc.x - arc_xc, hpgl_state.user_loc.y - arc_yc); 28 | 29 | //printf_P(PSTR("AA: (%f,%f) r=%f stepangle=%f a0=%f aend=%f\n"), 30 | // arc_xc, arc_yc, arc_r, arc_stepangle, arc_a0*180/M_PI, (arc_a0+arc_phi)*180/M_PI); 31 | 32 | return arc_phi != 0.0f && arc_r != 0.0f; 33 | } 34 | 35 | 36 | bool arc_init (void) 37 | { 38 | is_wedge = 0; 39 | return arc_cfg(hpgl_state.user_loc); 40 | } 41 | 42 | bool circle_init (hpgl_point_t *target) 43 | { 44 | user_point_t d; 45 | 46 | is_wedge = 0; 47 | d.x = hpgl_state.user_loc.x + hpgl_state.numpad[0]; 48 | d.y = hpgl_state.user_loc.y; 49 | 50 | hpgl_state.numpad[2] = 360.0f; 51 | hpgl_state.numpad[3] = hpgl_state.numpad[1]; 52 | hpgl_state.numpad[0] = hpgl_state.user_loc.x; 53 | hpgl_state.numpad[1] = hpgl_state.user_loc.y; 54 | 55 | userscale(d, target, &hpgl_state.user_loc); 56 | 57 | return arc_cfg(d); 58 | } 59 | 60 | bool wedge_init (void) 61 | { 62 | is_wedge = 2; 63 | float r = hpgl_state.numpad[0]; 64 | arc_phi = hpgl_state.numpad[1] * M_PI / 180.0f; 65 | 66 | hpgl_state.numpad[0] = hpgl_state.user_loc.x; 67 | hpgl_state.numpad[1] = hpgl_state.user_loc.y; 68 | hpgl_state.user_loc.y += r * sinf(arc_phi); 69 | hpgl_state.user_loc.x += r * cosf(arc_phi); 70 | 71 | return arc_cfg(hpgl_state.user_loc); 72 | } 73 | 74 | bool arc_next (hpgl_point_t *target) 75 | { 76 | bool cont = true; 77 | user_point_t d; 78 | 79 | if(is_wedge == 2) { 80 | 81 | is_wedge = 1; 82 | d.x = hpgl_state.numpad[0]; 83 | d.y = hpgl_state.numpad[1]; 84 | 85 | } else { 86 | 87 | arc_step++; 88 | float alpha = arc_step * arc_stepangle; 89 | 90 | if (fabsf(alpha) > fabsf(arc_phi)) { 91 | alpha = arc_phi; 92 | cont = false; 93 | } 94 | 95 | alpha += arc_a0; 96 | d.x = arc_xc + arc_r * cosf(alpha); 97 | d.y = arc_yc + arc_r * sinf(alpha); 98 | } 99 | 100 | if(!cont && is_wedge == 1) { 101 | cont = true; 102 | is_wedge = 3; 103 | } 104 | 105 | if(is_wedge == 3) { 106 | cont = false; 107 | d.x = hpgl_state.numpad[0]; 108 | d.y = hpgl_state.numpad[1]; 109 | } 110 | 111 | //printf_P(PSTR("ARC SPAN: (%6.1f %6.1f)-(%6.1f %6.1f)\n"), user_loc.x, user_loc.y, xd, yd); 112 | 113 | userscale(d, target, &hpgl_state.user_loc); 114 | 115 | return cont;//arc_step < arc_steps ? 1 : 0; 116 | } 117 | -------------------------------------------------------------------------------- /my_plugin/hpgl/arc.h: -------------------------------------------------------------------------------- 1 | #ifndef _ARC_H 2 | #define _ARC_H 3 | 4 | #include "hpgl.h" 5 | 6 | /// Initialize the arc based on current location and scratchpad data. 7 | /// @see numpad 8 | /// @see user_loc 9 | /// @returns 0 if the arc is degenerate (0 degrees or R=0) 10 | bool arc_init (void); 11 | 12 | bool wedge_init (void); 13 | 14 | bool circle_init (hpgl_point_t *target); 15 | 16 | /// Calculate the next chord. 17 | /// @param x next x in absolute stepper coordinates 18 | /// @param y next y in absolute stepper coordinates 19 | /// @returns 0 if this is the last chord 20 | bool arc_next (hpgl_point_t *target); 21 | 22 | #endif 23 | -------------------------------------------------------------------------------- /my_plugin/hpgl/charset0.c: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 1992 - 1994 Heinz W. Werntges. All rights reserved. 3 | Parts Copyright (c) 1999 Martin Kroeker All rights reserved. 4 | 5 | Distributed by Free Software Foundation, Inc. 6 | 7 | This file is part of HP2xx. 8 | 9 | HP2xx is distributed in the hope that it will be useful, but 10 | WITHOUT ANY WARRANTY. No author or distributor accepts responsibility 11 | to anyone for the consequences of using it or for whether it serves any 12 | particular purpose or works at all, unless he says so in writing. Refer 13 | to the GNU General Public License, Version 2 or later, for full details. 14 | 15 | Everyone is granted permission to copy, modify and redistribute 16 | HP2xx, but only under the conditions described in the GNU General Public 17 | License. A copy of this license is supposed to have been 18 | given to you along with HP2xx so you can know your rights and 19 | responsibilities. It should be in a file named COPYING. Among other 20 | things, the copyright notice and this notice must be preserved on all 21 | copies. 22 | 23 | - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 24 | */ 25 | 26 | /** 27 | ** This file defines a standard character set by elementary 28 | ** "draw" & "move" commands. The format is a very compact one from 29 | ** the old days where every byte was still appreciated. 30 | ** 31 | ** A font or character set is an array of strings. Each character 32 | ** corresponds to one of these strings, which is addressed by its ASCII code. 33 | ** 34 | ** A character is a (NULL-terminated) string of bytes. Each byte 35 | ** codes for a draw or move action according to the code below: 36 | ** 37 | ** Bit: 7 6 5 4 3 2 1 0 38 | ** p x x x y y y y 39 | ** 40 | ** p: Plot flag. If set, "draw to" new point, else "move to" it. 41 | ** xxx: 3-bit unsigned integer (0...7). X coordinate of new point. 42 | ** yyyy: 4-bit unsigned integer (0..15). Y coordinate of new point. 43 | ** 44 | ** The baseline is y = 4 instead of y = 0, so characters with parts 45 | ** below it can be drawn properly without a need for sign bits. 46 | ** Function "code_to_ucoord()" transforms these coordinates into 47 | ** actual user coordinates. 48 | ** 49 | ** Example: code for character 'L': "\032\224\324" translates to: 50 | ** moveto(1,10); drawto(1,4); drawto(5,4); 51 | ** 52 | ** From the example you can conclude that the font below essentially is 53 | ** defined on a 5x7 grid: 54 | ** 55 | ** 0 1 2 3 4 5 6 7 56 | ** 15 . . . . . . . . . : unused 57 | ** 14 . . . . . . . . * : always used 58 | ** 13 . . . . . . . . o : sometimes used 59 | ** 12 . . . . . . . . 60 | ** 11 . . . . . . . . 61 | ** 10 o * * * * * . . 62 | ** 9 o * * * * * . . 63 | ** 8 o * * * * * . . 64 | ** 7 o * * * * * . . 65 | ** 6 o * * * * * . . 66 | ** 5 o * * * * * . . 67 | ** 4 o * * * * * . . 68 | ** 3 o o o o o o . . 69 | ** 2 o o o o o o . . 70 | ** 1 o o o o o o . . 71 | ** 0 o o o o o o . . 72 | **/ 73 | 74 | 75 | /** 76 | ** The following array of strings contains the basic character set (set 0). 77 | ** 78 | ** NOTE: A nice way to add a new charset would be, e. g., to introduce a 79 | ** ``charset1[]'' as the "alternate" charset and implement the HP-GL 80 | ** commands needed for switching from one to the other. 81 | **/ 82 | 83 | const char *const charset0[256] = { 84 | /* 0x00 ... 0x1f */ 85 | 86 | /** 87 | ** Some control codes are valid in HPGL. These are handled elsewhere 88 | ** in a font-independent manner, so following codes are dummies: 89 | **/ 90 | "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", 91 | "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", 92 | 93 | /** 94 | ** Unfortunately, some compilers do not process \xNN properly, 95 | ** so I changed all hex codes (\xNN) into octal codes (\NNN), 96 | ** thereby losing readability but gaining portability. 97 | **/ 98 | 99 | /* 0x20 ... 0x2f */ 100 | "", 101 | "\064\265\066\272", 102 | "\051\252\111\312", 103 | "\044\252\104\312\026\326\030\330", 104 | "\064\272\131\251\230\247\307\326\305\225", 105 | "\024\332\051\250\270\271\251\066\265\305\306\266", 106 | "\124\230\231\252\271\270\226\225\244\264\326", 107 | "\071\312", 108 | "\132\270\266\324", 109 | "\024\266\270\232", 110 | "\005\351\145\211\072\264", 111 | "\065\271\027\327", 112 | "\064\244\245\265\263\242", 113 | "\027\327", 114 | "\064\244\245\265\264", 115 | "\352", 116 | 117 | /* 0x30 ... 0x3f */ 118 | /* 119 | "\025\244\304\325\331\312\252\231\225\331", ** Zero including `/' ** 120 | */ 121 | "\025\244\304\325\331\312\252\231\225", 122 | "\044\304\064\272\251", 123 | "\031\252\312\331\330\225\224\324", 124 | "\025\244\304\325\326\307\267\332\232", 125 | "\112\227\226\326\107\304", 126 | "\132\232\230\310\327\325\304\244\225", 127 | "\132\272\230\225\244\304\325\326\307\227", 128 | "\032\332\331\226\224", 129 | "\107\330\331\312\252\231\230\247\307\326\325\304\244\225\226\247", 130 | "\044\264\326\331\312\252\231\230\247\327", 131 | "\047\250\270\267\247\045\265\264\244\245", 132 | "\046\247\267\266\246\064\244\245\265\263\242", 133 | "\112\227\304", 134 | "\030\330\026\326", 135 | "\032\307\224", 136 | "\031\252\312\331\330\307\267\266\065\264", 137 | 138 | /* 0x40 ... 0x4f */ 139 | "\103\243\224\230\252\312\331\326\305\266\267\310\330", 140 | "\024\231\252\312\331\324\026\326", 141 | "\024\232\312\331\330\307\227\024\304\325\326\307", 142 | "\125\304\244\225\231\252\312\331", 143 | "\024\232\312\331\325\304\224", 144 | "\124\224\232\332\027\307", 145 | "\024\232\332\027\307", 146 | "\131\312\252\231\225\244\304\325\327\247", 147 | "\024\232\124\332\027\327", 148 | "\024\324\064\272\032\332", 149 | "\025\244\304\325\332\232", 150 | "\024\232\027\247\324\047\332", 151 | "\032\224\324", 152 | "\024\232\270\332\324", 153 | "\024\232\324\332", 154 | "\044\225\231\252\312\331\325\304\244", 155 | 156 | /* 0x50 ... 0x5f */ 157 | "\024\232\312\331\330\307\227", 158 | "\044\225\231\252\312\331\326\264\244\066\324", 159 | "\024\232\312\331\330\307\227\247\324", 160 | "\025\244\304\325\326\307\247\230\231\252\312\331", 161 | "\064\272\232\332", 162 | "\032\225\244\304\325\332", 163 | "\032\230\264\330\332", 164 | "\032\224\267\324\332", 165 | "\024\332\124\232", 166 | "\032\231\266\264\066\331\332", 167 | "\032\332\224\324", 168 | "\124\264\272\332", 169 | "\032\324", 170 | "\024\264\272\232", 171 | "\030\272\330", 172 | "\023\323", 173 | 174 | /* 0x60 ... 0x6f */ 175 | "\053\310", 176 | "\124\244\225\227\250\310\304", 177 | "\024\304\325\327\310\250\052\244", 178 | "\125\304\264\245\247\270\310\327", 179 | "\112\304\244\225\227\250\310\104\324", 180 | "\026\306\327\310\250\227\225\244\324", 181 | "\064\271\312\332\047\307", 182 | "\022\262\303\310\250\227\225\244\304", 183 | "\032\224\030\270\307\304", 184 | "\072\271\050\270\264\044\304", 185 | "\072\271\050\270\263\242\222", 186 | "\024\232\104\226\310", 187 | "\052\272\264\044\304", 188 | "\024\230\027\250\267\264\067\310\327\324", 189 | "\024\230\027\250\270\307\304", 190 | "\044\225\227\250\270\307\305\264\244", 191 | 192 | /* 0x70 ... 0x7f */ 193 | "\022\230\270\307\305\264\224", 194 | "\104\244\225\227\250\310\302", 195 | "\030\224\026\270\310", 196 | "\110\250\227\246\266\305\264\224", 197 | "\052\244\304\030\310", 198 | "\030\225\244\304\310", 199 | "\030\226\264\326\330", 200 | "\030\225\244\265\267\065\304\325\330", 201 | "\030\324\024\330", 202 | "\022\326\330\030\226\264", 203 | "\030\310\224\304", 204 | "\113\273\252\250\227\246\244\263\303", 205 | "\073\263", 206 | "\053\273\312\310\327\306\304\263\243", 207 | "\031\252\310\331", 208 | "" 209 | }; 210 | -------------------------------------------------------------------------------- /my_plugin/hpgl/clip.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | // https://educatech.in/c-program-to-implement-sutherland-hodgemann-polygon-clipping-algorithm/ 5 | 6 | enum { 7 | TOP = 0x1, 8 | BOTTOM = 0x2, 9 | RIGHT = 0x4, 10 | LEFT = 0x8 11 | }; 12 | 13 | typedef uint_fast8_t outcode; 14 | 15 | float xwmin,xwmax,ywmin,ywmax; 16 | 17 | outcode CompOutCode(float x, float y) 18 | { 19 | outcode code = 0; 20 | if(y > ywmax) 21 | code |=TOP; 22 | else if(y < ywmin) 23 | code |= BOTTOM; 24 | 25 | if(x > xwmax) 26 | code |= RIGHT; 27 | else if(x < xwmin) 28 | code |= LEFT; 29 | 30 | return code; 31 | } 32 | 33 | void clip (float x0, float y0, float x1, float y1) 34 | { 35 | outcode outcode0,outcode1,outcodeOut; 36 | bool accept = false, done = false; 37 | outcode0 = CompOutCode(x0, y0); 38 | outcode1 = CompOutCode(x1, y1); 39 | 40 | do { 41 | if(!(outcode0 | outcode1)) { 42 | accept = true; 43 | done = true; 44 | } 45 | else if(!(done = !!(outcode0 & outcode1))) { 46 | 47 | float x, y; 48 | 49 | outcodeOut = outcode0 ? outcode0 : outcode1; 50 | 51 | if(outcodeOut & TOP) { 52 | x = x0 + (x1 - x0) * (ywmax - y0) / (y1 - y0); 53 | y = ywmax; 54 | } else if(outcodeOut & BOTTOM) { 55 | x = x0 + (x1 - x0) * (ywmin - y0) / (y1 - y0); 56 | y = ywmin; 57 | } else if(outcodeOut & RIGHT) { 58 | y = y0 + (y1 - y0) * (xwmax - x0) / (x1 - x0); 59 | x = xwmax; 60 | } else { 61 | y = y0 + (y1 - y0) * (xwmin -x0) / (x1 - x0); 62 | x = xwmin; 63 | } 64 | if(outcodeOut == outcode0) { 65 | x0 = x; 66 | y0 = y; 67 | outcode0 = CompOutCode(x0,y0); 68 | } else { 69 | x1 = x; 70 | y1 = y; 71 | outcode1 = CompOutCode(x1,y1); 72 | } 73 | } 74 | } while(!done); 75 | /* 76 | if(accept) 77 | line(x0,y0,x1,y1); 78 | outtextxy(150,20,"POLYGON AFTER CLIPPING"); 79 | rectangle(xwmin,ywmin,xwmax,ywmax); 80 | */ 81 | } 82 | -------------------------------------------------------------------------------- /my_plugin/hpgl/font173.c: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 1992 - 1994 Heinz W. Werntges. All rights reserved. 3 | Parts Copyright (c) 1999 Martin Kroeker All rights reserved. 4 | 5 | Distributed by Free Software Foundation, Inc. 6 | 7 | This file is part of HP2xx. 8 | 9 | HP2xx is distributed in the hope that it will be useful, but 10 | WITHOUT ANY WARRANTY. No author or distributor accepts responsibility 11 | to anyone for the consequences of using it or for whether it serves any 12 | particular purpose or works at all, unless he says so in writing. Refer 13 | to the GNU General Public License, Version 2 or later, for full details. 14 | 15 | Everyone is granted permission to copy, modify and redistribute 16 | HP2xx, but only under the conditions described in the GNU General Public 17 | License. A copy of this license is supposed to have been 18 | given to you along with HP2xx so you can know your rights and 19 | responsibilities. It should be in a file named COPYING. Among other 20 | things, the copyright notice and this notice must be preserved on all 21 | copies. 22 | 23 | - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 24 | */ 25 | 26 | /** 27 | ** This file defines a standard character set by elementary 28 | ** "draw" & "move" commands. The format is a very compact one from 29 | ** the old days where every byte was still appreciated. 30 | ** 31 | ** A font or character set is an array of strings. Each character 32 | ** corresponds to one of these strings, which is addressed by its ASCII code. 33 | ** 34 | ** A character is a (NULL-terminated) string of bytes. Each byte 35 | ** codes for a draw or move action according to the code below: 36 | ** 37 | ** Bit: 7 6 5 4 3 2 1 0 38 | ** p x x x y y y y 39 | ** 40 | ** p: Plot flag. If set, "draw to" new point, else "move to" it. 41 | ** xxx: 3-bit unsigned integer (0...7). X coordinate of new point. 42 | ** yyyy: 4-bit unsigned integer (0..15). Y coordinate of new point. 43 | ** 44 | ** The baseline is y = 4 instead of y = 0, so characters with parts 45 | ** below it can be drawn properly without a need for sign bits. 46 | ** Function "code_to_ucoord()" transforms these coordinates into 47 | ** actual user coordinates. 48 | ** 49 | ** Example: code for character 'L': "\032\224\324" translates to: 50 | ** moveto(1,10); drawto(1,4); drawto(5,4); 51 | ** 52 | ** From the example you can conclude that the font below essentially is 53 | ** defined on a 5x7 grid: 54 | ** 55 | ** 0 1 2 3 4 5 6 7 56 | ** 15 . . . . . . . . . : unused 57 | ** 14 . . . . . . . . * : always used 58 | ** 13 . . . . . . . . o : sometimes used 59 | ** 12 . . . . . . . . 60 | ** 11 . . . . . . . . 61 | ** 10 o * * * * * . . 62 | ** 9 o * * * * * . . 63 | ** 8 o * * * * * . . 64 | ** 7 o * * * * * . . 65 | ** 6 o * * * * * . . 66 | ** 5 o * * * * * . . 67 | ** 4 o * * * * * . . 68 | ** 3 o o o o o o . . 69 | ** 2 o o o o o o . . 70 | ** 1 o o o o o o . . 71 | ** 0 o o o o o o . . 72 | **/ 73 | 74 | 75 | /** 76 | ** The following array of strings contains the ps math font (173), 77 | ** which differs from ventura math (205) only in the sequence of 78 | ** characters in the upper half of the font 79 | **/ 80 | 81 | const char *const charset173[256] = { 82 | /* 0x00 ... 0x1f */ 83 | 84 | /** 85 | ** Some control codes are valid in HPGL. These are handled elsewhere 86 | ** in a font-independent manner, so following codes are dummies: 87 | **/ 88 | "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", 89 | "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", 90 | 91 | /** 92 | ** Unfortunately, some compilers do not process \xNN properly, 93 | ** so I changed all hex codes (\xNN) into octal codes (\NNN), 94 | ** thereby losing readability but gaining portability. 95 | **/ 96 | /* 0x20 ... 0x2f */ 97 | "", /*space */ 98 | "\064\265\066\272", /*exclam */ 99 | "\031\264\331\047\307", /*forall */ 100 | "\044\252\104\312\026\326\030\330", /*hash */ 101 | "\031\271\265\225\067\227", /*exists */ 102 | "\024\332\051\250\270\271\251\066\265\305\306\266", /* percent */ 103 | "\124\230\231\252\271\270\226\225\244\264\326", /*ampersand */ 104 | "\031\251\270\267\266\225\067\227", /*ni */ 105 | "\132\270\266\324", /* opening brace */ 106 | "\024\266\270\232", /* closing brace */ 107 | "\005\351\145\211\072\264", /* asterisk */ 108 | "\065\271\027\327", /* plus */ 109 | "\064\244\245\265\263\242", /* comma */ 110 | "\027\327", /* minus */ 111 | "\064\244\245\265\264", /* dot */ 112 | "\352", /* slash */ 113 | 114 | /* 0x30 ... 0x3f */ 115 | "\025\244\304\325\331\312\252\231\225", 116 | "\044\304\064\272\251", 117 | "\031\252\312\331\330\225\224\324", 118 | "\025\244\304\325\326\307\267\332\232", 119 | "\112\227\226\326\107\304", 120 | "\132\232\230\310\327\325\304\244\225", 121 | "\132\272\230\225\244\304\325\326\307\227", 122 | "\032\332\331\226\224", 123 | "\107\330\331\312\252\231\230\247\307\326\325\304\244\225\226\247", 124 | "\044\264\326\331\312\252\231\230\247\327", 125 | "\047\250\270\267\247\045\265\264\244\245", 126 | "\046\247\267\266\246\064\244\245\265\263\242", 127 | "\112\227\304", 128 | "\030\330\026\326", 129 | "\032\307\224", 130 | "\031\252\312\331\330\307\267\266\065\264", 131 | 132 | /* 0x40 ... 0x4f */ 133 | "\025\325\027\327\030\251\270\310\331", /*congruent */ 134 | "\024\231\252\312\331\324\026\326", /* A */ 135 | "\024\232\312\331\330\307\227\024\304\325\326\307", /*B*/ "\024\332\124\232", /* Chi (X) */ 136 | "\024\272\324\224", /*Delta */ 137 | "\124\224\232\332\027\307", /*E*/ "\052\312\072\264\044\304\027\251\311\327\305\245\227", /*Phi */ 138 | "\052\332\330\072\264\044\304", /*Gamma */ 139 | "\024\232\124\332\027\327", /*H*/ "\024\324\064\272\032\332", /*I*/ "\027\304\330\331\271\270\326", /*vartheta */ 140 | "\024\232\027\247\324\047\332", /*K*/ "\024\272\324", /*Lambda */ 141 | "\024\232\270\332\324", /*M*/ 142 | "\024\232\324\332", /*N*/ 143 | "\044\225\231\252\312\331\325\304\244", /*O*/ 144 | /* 0x50 ... 0x5f */ 145 | "\044\252\032\332\112\304", /*Pi */ 146 | "\044\225\231\252\312\331\325\304\244\027\327", /*Theta */ 147 | "\024\232\312\331\330\307\227", /*Rho (P) */ 148 | "\044\252\032\332\112\304", /*Sigma */ 149 | "\064\272\232\332", /*T*/ "\032\231\266\264\066\331\332", /*Y*/ "\042\262\303\304\225\226\247\307", /*varsigma */ 150 | "\024\244\226\231\252\312\331\326\304\324", /*Omega */ 151 | "\031\232\332\331\050\246\047\307\110\306\025\224\324\325", /*Xi */ 152 | "\030\250\246\306\310\330\052\312\072\264\044\304", /*Psi */ 153 | "\032\332\224\324", /*Z*/ "\124\264\272\332", /*opening bracket */ 154 | "\004\224\225\205\204\104\324\325\305\304\051\271\272\252\251", /*point triangle */ 155 | "\024\264\272\232", /* closing bracket */ 156 | "\024\324\064\270", /*bottom */ 157 | "\023\323", /*underline */ 158 | 159 | /* 0x60 ... 0x6f */ 160 | 161 | "\034\334", /*overline */ 162 | "\025\227\250\270\307\305\264\244\225\107\330\105\324", /* alpha */ 163 | "\044\251\272\311\310\267\306\304\264\245", /* beta */ 164 | "\027\250\267\265\304\325\024\330", /* chi */ 165 | "\045\246\267\307\326\325\304\264\245\107\271\312\331", /*delta */ 166 | "\127\310\250\227\246\266\046\225\244\304\325", /* epsilon */ 167 | "\026\247\307\326\305\245\226\064\270", /*phi */ 168 | "\027\226\303\262\243\326\327", /*gamma */ 169 | "\030\247\270\310\327\322\047\245", /* eta */ 170 | "067\264\304\305", /* iota */ 171 | "\030\227\246\306\327\310\270\264\244", /*varphi */ 172 | "\050\244\046\266\330\066\305\304\324", /* kappa */ 173 | "\031\251\250\324\024\266", /* lambda */ 174 | "\024\250\246\265\306\310\106\325", /* mu */ 175 | "\030\226\264\326\327\310", /* nu */ 176 | "\044\225\227\250\270\307\305\264\244", /*o */ 177 | 178 | /* 0x70 ... 0x7f */ 179 | "\044\247\104\307\027\250\267\307\330", /* pi */ 180 | "\045\264\305\311\272\251\245\047\307", /* theta */ 181 | "\023\246\267\307\326\325\304\264\245", /*rho */ 182 | "\045\246\267\307\326\325\304\264\245\107\330", /*sigma */ 183 | "\026\267\327\067\264\324", /*tau */ 184 | "\030\226\264\326\330", /*v */ 185 | "\050\227\225\244\265\266\065\304\325\327\310\047\307", /*var pi */ 186 | "\050\227\225\244\265\266\065\304\325\327\310", /*omega */ 187 | "\051\250\267\307\330\270\246\265\305\326\266\244\243\262\322\321\301", /*xi */ 188 | "\027\247\265\305\327\110\264", /*psi */ 189 | "\051\250\267\307\330\270\246\244\243\262\322\321\301", /*zeta */ 190 | "\113\273\252\250\227\246\244\263\303", /* opening curly brace */ 191 | "\073\263", /* vertical bar */ 192 | "\053\273\312\310\327\306\304\263\243", /*closing curly brace */ 193 | "\027\250\267\307\330", /* similar */ 194 | "", /*blank */ 195 | 196 | /* 0x80 ... 0x9f */ 197 | "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", 198 | "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", 199 | 200 | /* 0xa0 ... 0xaf */ 201 | 202 | "", /* space */ 203 | "\030\251\270\311\330\070\264\044\304", /* Upsilon */ 204 | "\071\312", /* prime */ 205 | "\025\325\227\331", /*lessorequal */ 206 | "\352", /* slash */ 207 | "\026\226\247\247\266\266\245\245\226\66\266\307\326\305\266", /*infinity */ 208 | "\131\332\312\270\265\244\224\225\050\310", /* math function */ 209 | "\026\247\266\245\226\066\307\326\305\266\067\250\271\310\267\067\264", /*clubsuit */ 210 | "\026\271\326\263\226", /* diamondsuit */ 211 | "\030\252\271\312\330\265\230", /* heartsuit */ 212 | "\026\271\326\305\267\245\226\067\264", /* spadesuit */ 213 | "\027\327\106\327\310\046\227\250", /* leftrightarrow */ 214 | "\027\327\046\227\250", /* leftarrow */ 215 | "\072\264\030\272\330", /*uparrow */ 216 | "\027\327\106\327\310", /*rightarrow */ 217 | "\072\264\026\264\326", /* downarrow */ 218 | 219 | 220 | 221 | /* 0xb0 ... 0xbf */ 222 | "\050\251\272\311\310\267\250", /* degree */ 223 | "\065\271\027\327\025\325", /* plusminus */ 224 | "\051\252\111\312", /* two primes */ 225 | "\125\225\327\231", /*greaterorequal */ 226 | "\045\310\050\305", /* times */ 227 | "\026\226\247\247\266\266\245\245\226\66\266\307\066\305", /* propto */ 228 | "\050\271\311\305\264\244\225\226\267\306", /* partial */ 229 | "\047\270\307\266\247", /* circle */ 230 | "\027\327\065\266\071\272", /*divide */ 231 | "\030\330\026\326\045\311", /*notequal */ 232 | "\025\325\027\327\031\331", /* identity */ 233 | "\026\247\266\306\327\030\251\270\310\331", /* approx */ 234 | "\024\225\064\265\124\325", /* ellipsis */ 235 | "\072\264", /* center part of braces and brackets */ 236 | "\027\327", /* horiz line */ 237 | "\027\327\046\227\250\127\330", /* leftanglearrow */ 238 | 239 | /* 0xc0 ... 0xcf */ 240 | 241 | "\024\245\247\130\307\305\030\324", /* aleph */ 242 | "\050\231\252\272\330\132\310\305\264\244\225", /* Im */ 243 | "\050\231\252\312\330\307\305\324\107\267\072\265\244\225", /* Re */ 244 | "\030\227\245\243\222\224\246\247\270\310\327\325\304\264\245", /*wp */ 245 | "\027\230\251\311\330\326\305\245\226\227\045\310\050\305", /* otimes */ 246 | "\027\230\251\311\330\326\305\245\226\227\071\265\027\327", /*oplus */ 247 | "\027\251\311\327\305\245\227\024\332", /* oslash */ 248 | "\025\230\251\311\330\325", /* cap */ 249 | "\031\226\245\305\326\331", /* cup */ 250 | "\025\305\326\330\311\231", /* superset */ 251 | "\026\306\327\310\230\024\324", /* supsetequal */ 252 | "\125\245\226\230\251\331\045\311", /* not subset */ 253 | "\125\245\226\230\251\331", /* subset */ 254 | "\126\246\227\250\330\024\324", /*subsetequal */ 255 | "\125\245\226\230\251\331\027\327", /* in */ 256 | "\125\245\226\230\251\331\027\327\045\311", /* not in */ 257 | 258 | /* 0xd0 ... 0xdf */ 259 | 260 | "\132\224\324", /* angle */ 261 | "\032\332\264\232", /* nabla */ 262 | "\027\230\251\311\330\326\305\245\226\227\046\250\270\267\247\266", /* registered */ 263 | "\027\230\251\311\330\326\305\245\226\227\110\270\247\266\306", /* copyright */ 264 | "\032\272\052\250\070\272\311\332\330", /* TM */ 265 | "\024\231\124\331\031\331", /* prod */ 266 | "\026\246\264\311\331", /* root */ 267 | "\066\267", /* centered dot */ 268 | "\026\326\325", /* neg */ 269 | "\024\267\324", /*wedge */ 270 | "\030\264\330", /* vee */ 271 | "\045\305\047\307\070\226\264\070\326\264", /* leftright doublearrow */ 272 | "\045\325\047\327\070\226\264", /* left doublearrow */ 273 | "\044\250\104\310\027\272\327", /* up doublearrow */ 274 | "\025\305\027\307\070\326\264", /* right doublearrow */ 275 | "\052\245\112\305\026\264\326", /* down doublearrow */ 276 | 277 | /* 0xe0 ... 0xef */ 278 | 279 | "\026\270\326\264\226", /* diamond */ 280 | "\112\247\304", /* opening angle bracket */ 281 | "\027\230\251\311\330\326\305\245\226\227\046\250\270\267\247\266", /*registered */ 282 | "\027\230\251\311\330\326\305\245\226\227\110\270\247\266\306", /* copyright */ 283 | "\032\272\052\250\070\272\311\332\330", /* TM */ 284 | "\044\252\032\332\112\304", /*Sigma */ 285 | "\112\270\264", /* top third of opening brace */ 286 | "\072\264", /* center part of braces and brackets */ 287 | "\072\266\304", /* lower third of opening brace */ 288 | "\112\272\264", /* upper third of opening bracket */ 289 | "\072\264", /* center part of braces and brackets */ 290 | "\072\264\304", /* lower third of opening bracket */ 291 | "\112\270\264", /* top third of opening brace */ 292 | "\072\270\247\266\264", /* center part of opening curly brace */ 293 | "\072\266\324", /* lower third of opening round brace */ 294 | "\072\264", /* center part of braces and brackets */ 295 | 296 | /* 0xf0 ... 0xff */ 297 | 298 | "", /* empty */ 299 | "\052\307\244", /* closing angle bracket */ 300 | "\024\243\264\272\313\332", /* integral */ 301 | "\112\270\264", /* top third of opening brace */ 302 | "\072\264", /* center part of braces */ 303 | "\072\266\224", /* lower third of closing brace */ 304 | "\052\270\264", /* top third of closing brace */ 305 | "\072\264", /* center part of braces */ 306 | "\072\266\224", /* lower third of closing brace */ 307 | "\052\272\264", /* upper third of closing bracket */ 308 | "\072\264", /* center part of braces */ 309 | "\072\264\244", /* lower third of closing bracket */ 310 | "\052\270\264", /* top third of closing brace */ 311 | "\072\270\307\266\264", /* center part of closing curly brace */ 312 | "\072\266\224", /* lower third of closing brace */ 313 | "" /* blank */ 314 | }; 315 | -------------------------------------------------------------------------------- /my_plugin/hpgl/hpgl.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "hpgl.h" 6 | #include "scale.h" 7 | 8 | #include "grbl/hal.h" 9 | #include "grbl/nuts_bolts.h" 10 | 11 | extern const char *const charset0[256]; 12 | extern const char *const charset173[256]; 13 | 14 | static const hpgl_state_t defaults = { 15 | .chord_angle = 5.0f, 16 | .pen_thickness = .3f, 17 | .plot_relative = false, 18 | .etxchar = ASCII_ETX, // ^C 19 | .term = ASCII_EOL, 20 | .pattern_type = 0, 21 | .pattern_length = 4.0f, 22 | #ifdef HPGL_A3 23 | .character_size.width = 0.187f, 24 | .character_size.height = 0.269f, 25 | #else 26 | .character_size.width = 0.285f, 27 | .character_size.height = 0.375f, 28 | #endif 29 | .use_alt_charset = false, 30 | .text_vertical = false, 31 | .charset = &charset0[0], 32 | .charset_std = &charset0[0], 33 | .charset_alt = &charset173[0], 34 | .first_error = ERR_None, 35 | .last_error = ERR_None, 36 | .errmask = 0, 37 | .alertmask = 223, 38 | .ip_pad[0] = 0, 39 | .ip_pad[1] = 0, 40 | .ip_pad[2] = MAX_X, 41 | .ip_pad[3] = MAX_Y, 42 | .sc_pad[0] = 0, 43 | .sc_pad[1] = MAX_X, 44 | .sc_pad[2] = 0, 45 | .sc_pad[3] = MAX_Y, 46 | .user_loc.x = 0.0f, 47 | .user_loc.y = 0.0f, 48 | .flags.initialized = On, 49 | .flags.ready = On 50 | }; 51 | 52 | extern pen_status_t get_pen_status (void); 53 | 54 | hpgl_state_t hpgl_state; 55 | hpgl_char_ptr hpgl_char; 56 | 57 | static hpgl_command_t hpgl_char_inp (char c, hpgl_point_t *target, uint8_t *lb); 58 | 59 | static char scratchpad[SCRATCHPAD_SIZE]; 60 | 61 | __attribute__((weak)) void alert_led (bool on) 62 | { 63 | } 64 | 65 | void hpgl_init (void) 66 | { 67 | // pstate = STATE_EXP1; 68 | hpgl_char = hpgl_char_inp; 69 | 70 | memcpy(&hpgl_state, &defaults, sizeof(hpgl_state_t)); 71 | 72 | hpgl_set_error(hpgl_state.last_error); 73 | 74 | translate_init_sc(); 75 | 76 | hpgl_state.comm.enable_dtr = stream_get_flags(hal.stream).rts_handshake; 77 | } 78 | 79 | static void hpgl_defaults (void) 80 | { 81 | memcpy(&hpgl_state, &defaults, offsetof(hpgl_state_t, ip_pad)); 82 | 83 | hpgl_set_error(hpgl_state.last_error); 84 | } 85 | 86 | void hpgl_set_error (hpgl_error_t errnum) 87 | { 88 | if(errnum == ERR_None) 89 | hpgl_state.errmask = 0; 90 | else 91 | hpgl_state.errmask |= (1 << errnum); 92 | 93 | if(hpgl_state.first_error == ERR_None) 94 | hpgl_state.first_error = errnum; 95 | 96 | hpgl_state.last_error = errnum; 97 | hpgl_state.flags.error = hpgl_state.errmask != 0; // ?? 98 | 99 | alert_led(!!(hpgl_state.errmask & hpgl_state.alertmask)); 100 | } 101 | 102 | hpgl_error_t hpgl_get_error (void) 103 | { 104 | return hpgl_state.last_error; 105 | } 106 | 107 | bool is_numeric (char c, uint_fast8_t idx) 108 | { 109 | // TODO: add check for single decimal point in input? 110 | return idx < sizeof(scratchpad) - 2 && ((c >= '0' && c <= '9') || c == '.' || (idx == 0 && (c == '+' || c == '-'))); 111 | } 112 | 113 | bool is_whitespace (char c) 114 | { 115 | return c == ' ' || c == '\n' || c == '\r' || c == 't'; 116 | } 117 | 118 | bool is_command_char (char c) 119 | { 120 | return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z'); 121 | } 122 | 123 | bool is_separator (char *c) 124 | { 125 | bool ok; 126 | 127 | if((ok = *c == ',' || *c == ' ' || *c == '+' || *c == '-')) 128 | *c = '\0'; 129 | 130 | return ok; 131 | } 132 | 133 | bool is_terminator (char *c) 134 | { 135 | bool ok; 136 | 137 | if((ok = *c == ';')) 138 | *c = '\0'; 139 | 140 | return ok || is_command_char(*c); 141 | } 142 | 143 | bool get_parameter (char *c, uint_fast8_t *idx) 144 | { 145 | if(is_numeric(*c, *idx)) { 146 | scratchpad[*idx] = *c; 147 | (*idx)++; 148 | *c = '\0'; 149 | } 150 | 151 | return *idx > 0 && *c != '\0' && ((is_terminator(c)) || is_separator(c)); 152 | } 153 | 154 | hpgl_command_t get_instruction (char c) 155 | { 156 | static hpgl_command_t command = 0; 157 | 158 | hpgl_command_t instruction = 0; 159 | 160 | if(is_command_char(c)) { 161 | command = command << 8 | CAPS(c); 162 | } else if(!is_whitespace(c)) 163 | command = 0; 164 | 165 | if(command > 255) { 166 | instruction = command; 167 | command = 0; 168 | } 169 | 170 | return instruction; 171 | } 172 | 173 | hpgl_command_t hpgl_char_inp (char c, hpgl_point_t *target, uint8_t *lb) 174 | { 175 | static uint_fast8_t si, numpad_idx; 176 | static hpgl_command_t command = CMD_CONT; 177 | 178 | static bool is_plotting = false, is_labeling = false, is_labelterminator = false; 179 | 180 | *lb = 0; 181 | target->x = target->y = -1; 182 | 183 | if(c == ASCII_ESC) // should never arrive here... 184 | return CMD_CONT; 185 | 186 | if(is_labeling) { 187 | if((is_labeling = c != hpgl_state.etxchar)) { 188 | 189 | *lb = c; 190 | 191 | if(hpgl_state.comm.monitor_on && hpgl_state.comm.monitor_on) 192 | hal.stream.write_char(c); 193 | 194 | return command; 195 | } 196 | return command = CMD_CONT; 197 | } 198 | 199 | if(is_labelterminator) { // DT: Set text terminator 200 | hpgl_state.etxchar = c; 201 | is_labelterminator = false; 202 | return command = CMD_CONT; 203 | } 204 | 205 | if(is_whitespace(c)) 206 | return CMD_CONT; 207 | 208 | char t = c; 209 | bool terminated = false; 210 | hpgl_command_t cmd = CMD_CONT; 211 | 212 | if(hpgl_state.comm.monitor_on && hpgl_state.comm.monitor_on) 213 | hal.stream.write_char(c); 214 | 215 | if(command == CMD_CONT) { 216 | t = '\0'; 217 | if((command = get_instruction(c)) != CMD_CONT) { 218 | 219 | si = 0; 220 | is_plotting = command == CMD_PA || command == CMD_PD || command == CMD_PR || command == CMD_PU; 221 | is_labelterminator = command == CMD_DT; 222 | 223 | if(command == CMD_PA || command == CMD_PR) 224 | hpgl_state.plot_relative = command == CMD_PR; 225 | 226 | if((is_labeling = command == CMD_LB)) 227 | cmd = CMD_LB0; 228 | 229 | numpad_idx = 4; 230 | do { 231 | hpgl_state.numpad[--numpad_idx] = 0.0f; 232 | } while(numpad_idx); 233 | } 234 | } else if(get_parameter(&c, &si)) { 235 | 236 | if((terminated = is_command_char(c))) 237 | get_instruction(c); 238 | 239 | scratchpad[si] = '\0'; 240 | si = 0; 241 | 242 | if(numpad_idx < sizeof(hpgl_state.numpad)) { 243 | read_float(scratchpad, &si, &hpgl_state.numpad[numpad_idx++]); 244 | si = 0; 245 | } 246 | 247 | if(is_plotting && numpad_idx == 2) 248 | t = ';'; 249 | 250 | } else if((terminated = is_command_char(c))) 251 | get_instruction(c); 252 | 253 | if(t && is_terminator(&t)) { 254 | 255 | switch(command) { 256 | 257 | case CMD_AA: 258 | cmd = CMD_AA; 259 | if (numpad_idx == 3) 260 | hpgl_state.numpad[3] = hpgl_state.chord_angle; 261 | cmd = command; 262 | break; 263 | 264 | case CMD_AR: // AR: Arc Relative - xc, yc, CentralAngle [,Degrees per step] 265 | if(numpad_idx < 3) { 266 | cmd = CMD_ERR; 267 | hpgl_set_error(ERR_WrongParams); 268 | } else { 269 | if (numpad_idx == 3) 270 | hpgl_state.numpad[3] = hpgl_state.chord_angle; 271 | user_point_t f, offset = { 0.0f, 0.0f }; 272 | f.x = hpgl_state.numpad[0]; 273 | f.y = hpgl_state.numpad[1]; 274 | userscalerelative(f, target, &offset); 275 | hpgl_state.numpad[0] = hpgl_state.user_loc.x + offset.x; 276 | hpgl_state.numpad[1] = hpgl_state.user_loc.y + offset.y; 277 | target->x = target->y = -1; 278 | cmd = command; 279 | } 280 | break; 281 | 282 | case CMD_CA: // CA: Designate alternate character set 283 | if(numpad_idx == 0) { 284 | hpgl_state.numpad[0] = 0.0f; 285 | numpad_idx = 1; 286 | } 287 | if(numpad_idx == 1) { 288 | hpgl_state.charset_alt = hpgl_state.numpad[0] == 0.0f ? &charset0[0] : &charset173[0]; 289 | if(hpgl_state.use_alt_charset) 290 | hpgl_state.charset = hpgl_state.charset_alt; 291 | } else { 292 | cmd = CMD_ERR; 293 | hpgl_set_error(ERR_UnknownCharset); 294 | } 295 | break; 296 | 297 | case CMD_CI: // CI: Circle - radius [,Degrees per step] 298 | if(numpad_idx > 0) { 299 | if(numpad_idx == 1) 300 | hpgl_state.numpad[1] = hpgl_state.chord_angle; 301 | userscale(hpgl_state.user_loc, target, &hpgl_state.user_loc); 302 | cmd = command; 303 | } else { 304 | cmd = CMD_ERR; 305 | hpgl_set_error(ERR_WrongParams); 306 | } 307 | break; 308 | 309 | case CMD_CP: // CP: Character Plot 310 | if(numpad_idx == 0) { 311 | hpgl_state.numpad[0] = NAN; 312 | hpgl_state.numpad[1] = NAN; 313 | } 314 | if(numpad_idx == 1) 315 | hpgl_state.numpad[1] = 0.0f; 316 | cmd = command; 317 | break; 318 | 319 | case CMD_CS: // CA: Designate standard character set 320 | if(numpad_idx == 0) { 321 | hpgl_state.numpad[0] = 0.0f; 322 | numpad_idx = 1; 323 | } 324 | if(numpad_idx == 1) { 325 | hpgl_state.charset_std = hpgl_state.numpad[0] == 0.0f ? &charset0[0] : &charset173[0]; 326 | if(!hpgl_state.use_alt_charset) 327 | hpgl_state.charset = hpgl_state.charset_std; 328 | } else { 329 | cmd = CMD_ERR; 330 | hpgl_set_error(ERR_UnknownCharset); 331 | } 332 | break; 333 | 334 | case CMD_DF: // DF: Set default values 335 | hpgl_defaults(); 336 | cmd = command; 337 | break; 338 | 339 | case CMD_DI: // DI: Absolute text direction // numpad contains sin(theta), cos(theta) 340 | if(numpad_idx == 0) { 341 | hpgl_state.numpad[0] = 1.0f; 342 | hpgl_state.numpad[1] = 0.0f; 343 | numpad_idx = 2; 344 | } 345 | if(numpad_idx >= 2) { 346 | // fabs(hpgl_state.numpad[0] + hpgl_state.numpad[1]) > 0.0004f 347 | } 348 | cmd = command; 349 | break; 350 | 351 | case CMD_DV: // DV: Vertical label direction 352 | if(numpad_idx == 1) { 353 | hpgl_state.text_vertical = truncf(hpgl_state.numpad[0]) != 0.0f; 354 | } else { 355 | cmd = CMD_ERR; 356 | hpgl_set_error(ERR_WrongParams); 357 | } 358 | break; 359 | 360 | case CMD_EA: // EA: Edge Absolute 361 | if(numpad_idx < 2) { 362 | cmd = CMD_ERR; 363 | hpgl_set_error(ERR_WrongParams); 364 | } else { 365 | user_point_t f; 366 | f.x = hpgl_state.numpad[0]; 367 | f.y = hpgl_state.numpad[1]; 368 | userscale(f, target, NULL); 369 | if(numpad_idx > 2) 370 | hpgl_set_error(ERR_WrongParams); 371 | cmd = command; 372 | } 373 | break; 374 | 375 | case CMD_ER: // ER: Edge Relative 376 | if(numpad_idx < 2) { 377 | cmd = CMD_ERR; 378 | hpgl_set_error(ERR_WrongParams); 379 | } else { 380 | user_point_t f; 381 | f.x = hpgl_state.user_loc.x + hpgl_state.numpad[0]; 382 | f.y = hpgl_state.user_loc.y + hpgl_state.numpad[1]; 383 | userscale(f, target, NULL); 384 | if(numpad_idx > 2) 385 | hpgl_set_error(ERR_WrongParams); 386 | cmd = command; 387 | } 388 | break; 389 | 390 | case CMD_ES: // ES: Extra Space 391 | if(numpad_idx == 0) { 392 | hpgl_state.numpad[0] = 0.0f; 393 | hpgl_state.numpad[1] = 0.0f; 394 | numpad_idx = 2; 395 | } else { 396 | // TODO: 397 | if(numpad_idx > 2) 398 | hpgl_set_error(ERR_WrongParams); 399 | cmd = command; 400 | } 401 | break; 402 | 403 | case CMD_EW: // EW: Edge Wedge 404 | if(numpad_idx > 0) { 405 | if(numpad_idx < 3) { 406 | cmd = CMD_ERR; 407 | hpgl_set_error(ERR_WrongParams); 408 | } else { 409 | if (numpad_idx == 3) 410 | hpgl_state.numpad[3] = hpgl_state.chord_angle; 411 | /* user_point_t f, offset = { 0.0f, 0.0f }; 412 | f.x = hpgl_state.numpad[0]; 413 | f.y = hpgl_state.numpad[1]; 414 | userscalerelative(f, target, &offset); 415 | hpgl_state.numpad[0] = hpgl_state.user_loc.x + offset.x; 416 | hpgl_state.numpad[1] = hpgl_state.user_loc.y + offset.y; 417 | target->x = target->y = -1; */ 418 | cmd = command; 419 | } 420 | } 421 | break; 422 | 423 | case CMD_IM: // IM: Input Mask 424 | if(numpad_idx > 0 && hpgl_state.numpad[0] < 256.0f) { 425 | hpgl_state.alertmask = truncf(hpgl_state.numpad[0]); 426 | alert_led(!!(hpgl_state.errmask & hpgl_state.alertmask)); 427 | } 428 | break; 429 | 430 | case CMD_IN: // IN: Initialize plotter 431 | hpgl_set_error(ERR_None); 432 | cmd = command; 433 | break; 434 | 435 | case CMD_IP: // IP: Initialize plotter 436 | if(numpad_idx == 0) 437 | translate_init_ip(); 438 | else if(numpad_idx == 2 || numpad_idx == 4) { 439 | bool valid; 440 | uint_fast8_t i = numpad_idx; 441 | do { 442 | i--; 443 | valid = hpgl_state.numpad[i] >= 0.0f && (int32_t)truncf(hpgl_state.numpad[i]) <= (i & 0b1 ? MAX_Y : MAX_X); 444 | } while(i && valid); 445 | 446 | if(valid) { 447 | 448 | user_point_t ip = range_P1P2(); 449 | 450 | for (i = 0; i < numpad_idx; i++) 451 | hpgl_state.ip_pad[i] = (int32_t)truncf(hpgl_state.numpad[i]); 452 | 453 | if(numpad_idx == 2) { 454 | hpgl_state.ip_pad[2] = hpgl_state.ip_pad[0] + ip.x; // clip! 455 | hpgl_state.ip_pad[3] = hpgl_state.ip_pad[1] + ip.y; 456 | } 457 | } 458 | } 459 | cmd = command; 460 | break; 461 | 462 | case CMD_LB: // LB: Label character 463 | // scanning text 464 | cmd = CMD_LB; 465 | if (!(is_labeling = c != hpgl_state.etxchar)) { 466 | *lb = 0; 467 | command = CMD_CONT; 468 | } else { 469 | target->x = target->y = -1; 470 | *lb = c; 471 | } 472 | cmd = command; 473 | break; 474 | 475 | case CMD_LT: // LT: Line Type 476 | if(numpad_idx == 0) { 477 | hpgl_state.pattern_type = 0; 478 | hpgl_state.pattern_length = 4.0f; 479 | cmd = command; 480 | } else if((cmd = fabsf(hpgl_state.numpad[0]) >= 128.0f ? CMD_ERR : command) == command) { 481 | hpgl_state.numpad[0] = truncf(hpgl_state.numpad[0]); 482 | if(hpgl_state.numpad[0] <= 6.0f) 483 | hpgl_state.pattern_type = hpgl_state.numpad[0] <= 0.0f ? 0 : (uint8_t)hpgl_state.numpad[0]; 484 | if(numpad_idx > 1) { 485 | if(hpgl_state.numpad[1] >= 0.0f && hpgl_state.numpad[1] < 128.0f) 486 | hpgl_state.pattern_length = hpgl_state.numpad[1]; 487 | else 488 | cmd = CMD_ERR; 489 | } else 490 | hpgl_state.pattern_length = 4.0f; 491 | } 492 | if(cmd == CMD_ERR) 493 | hpgl_set_error(ERR_BadParam); 494 | break; 495 | 496 | case CMD_OA: // OA: Output Actual Position and Pen Status 497 | { 498 | hpgl_point_t position; 499 | 500 | usertohpgl(hpgl_state.user_loc, &position); 501 | 502 | hal.stream.write(uitoa(position.x)); 503 | hal.stream.write(","); 504 | hal.stream.write(uitoa(position.y)); 505 | hal.stream.write(","); 506 | hal.stream.write(uitoa(get_pen_status() == Pen_Down)); 507 | hal.stream.write(hpgl_state.term); 508 | } 509 | break; 510 | 511 | case CMD_OE: // OE: Output Error 512 | hal.stream.write(uitoa(hpgl_state.first_error)); 513 | hal.stream.write(hpgl_state.term); 514 | hpgl_state.flags.error = Off; 515 | break; 516 | 517 | case CMD_OF: // OF: Output Factors 518 | hal.stream.write("40,40"); 519 | hal.stream.write(hpgl_state.term); 520 | break; 521 | 522 | case CMD_OI: // OI: Output Identification 523 | hal.stream.write(HPGL_DEVICE_IDENTIFICATION); 524 | hal.stream.write(hpgl_state.term); 525 | break; 526 | 527 | case CMD_OO: // OO: Output Options 528 | hal.stream.write("0,0,0,0,1,0,0,0"); // TODO: set second parameter to 1 if pen change is supported 529 | hal.stream.write(hpgl_state.term); 530 | break; 531 | 532 | case CMD_OP: // OP: Output Parameters P1 & P2 533 | output_P1P2(); 534 | hpgl_state.flags.p1p2_changed = Off; 535 | break; 536 | 537 | case CMD_OS: // OE: Output Status 538 | hpgl_state.flags.pen_down = get_pen_status() == Pen_Down; 539 | hal.stream.write(uitoa(hpgl_state.flags.value)); 540 | hal.stream.write(hpgl_state.term); 541 | hpgl_state.flags.initialized = Off; 542 | break; 543 | 544 | case CMD_OW: // OW: Output Window 545 | case CMD_OH: // OH: Output Hard-clip Limits TODO: implement clip 546 | hal.stream.write("0,0,"); 547 | hal.stream.write(uitoa(MAX_X)); 548 | hal.stream.write(","); 549 | hal.stream.write(uitoa(MAX_Y)); 550 | hal.stream.write(hpgl_state.term); 551 | break; 552 | 553 | case CMD_PA: 554 | case CMD_PD: 555 | case CMD_PR: 556 | case CMD_PU: 557 | switch(numpad_idx) { 558 | case 0: 559 | cmd = command; 560 | command = CMD_CONT; 561 | break; 562 | case 2: 563 | { 564 | user_point_t f; 565 | f.x = hpgl_state.numpad[0]; 566 | f.y = hpgl_state.numpad[1]; 567 | if(hpgl_state.plot_relative) 568 | userscalerelative(f, target, &hpgl_state.user_loc); 569 | else 570 | userscale(f, target, &hpgl_state.user_loc); 571 | cmd = command; 572 | } 573 | break; 574 | default: 575 | cmd = CMD_ERR; 576 | command = CMD_CONT; 577 | hpgl_set_error(ERR_BadParam); 578 | break; 579 | } 580 | break; 581 | 582 | case CMD_PT: // PT: pen thickness 583 | cmd = command; 584 | if(numpad_idx == 0) 585 | hpgl_state.pen_thickness = 0.3f; 586 | else { 587 | 588 | if(hpgl_state.numpad[0] < 0.1f || hpgl_state.numpad[0] > 5.0f) { 589 | cmd = CMD_ERR; 590 | hpgl_set_error(ERR_BadParam); 591 | } else 592 | hpgl_state.pen_thickness = hpgl_state.numpad[0]; 593 | 594 | if(cmd != CMD_ERR && numpad_idx > 1) { 595 | cmd = CMD_ERR; 596 | hpgl_set_error(ERR_WrongParams); 597 | } 598 | } 599 | break; 600 | 601 | case CMD_SA: // SA: select alternate charset 602 | hpgl_state.use_alt_charset = true; 603 | hpgl_state.charset = hpgl_state.charset_alt; 604 | break; 605 | 606 | case CMD_SC: // Scale 607 | if(numpad_idx == 0) 608 | translate_init_sc(); 609 | else if(numpad_idx == 4) { 610 | for (uint_fast8_t i = 0; i < 4; i++) 611 | hpgl_state.sc_pad[i] = (int32_t)truncf(hpgl_state.numpad[i]); 612 | translate_scale(); 613 | } 614 | cmd = numpad_idx == 0 || numpad_idx == 4 ? command : CMD_ERR; 615 | break; 616 | 617 | case CMD_SI: // SI: Absolute character size 618 | if(numpad_idx == 0) { 619 | hpgl_state.numpad[0] = 0.187f; 620 | hpgl_state.numpad[1] = 0.269f; 621 | numpad_idx = 2; 622 | } 623 | if((cmd = (numpad_idx == 1 ? CMD_ERR : command)) == CMD_ERR) 624 | hpgl_set_error(ERR_WrongParams); 625 | else { 626 | hpgl_state.character_size.width = hpgl_state.numpad[0]; 627 | hpgl_state.character_size.height = hpgl_state.numpad[1]; 628 | if(numpad_idx > 2) 629 | hpgl_set_error(ERR_WrongParams); 630 | } 631 | break; 632 | 633 | case CMD_SP: // SP: Select Pen 634 | if(numpad_idx == 0) 635 | hpgl_state.numpad[0] = 0.0f; 636 | hpgl_state.pen_thickness = 0.3; // only if pen changed... 637 | cmd = command; 638 | break; 639 | 640 | case CMD_SR: // SR: Relative character size 641 | if(numpad_idx == 0) { 642 | hpgl_state.numpad[0] = 0.75f; 643 | hpgl_state.numpad[1] = 1.5f; 644 | numpad_idx = 2; 645 | } 646 | if((cmd = (numpad_idx == 1 ? CMD_ERR : command)) == CMD_ERR) 647 | hpgl_set_error(ERR_WrongParams); 648 | else { 649 | user_point_t range = range_P1P2(); 650 | hpgl_state.character_size.width = range.x * hpgl_state.numpad[0] / 100.0f; 651 | hpgl_state.character_size.height = range.y * hpgl_state.numpad[1] / 100.0f; 652 | if(numpad_idx > 2) 653 | hpgl_set_error(ERR_WrongParams); 654 | } 655 | break; 656 | 657 | case CMD_SS: // SA: select standard charset 658 | hpgl_state.use_alt_charset = false; 659 | hpgl_state.charset = hpgl_state.charset_std; 660 | break; 661 | 662 | case CMD_VS: // VS: Velocity Set 663 | cmd = numpad_idx ? command : CMD_ERR; 664 | break; 665 | 666 | default: 667 | cmd = CMD_ERR; 668 | hpgl_set_error(ERR_UnknownCommand); 669 | break; 670 | } 671 | 672 | numpad_idx = si = 0; 673 | if(terminated || !is_plotting || cmd == CMD_ERR) 674 | command = CMD_CONT; 675 | } 676 | 677 | return cmd; 678 | } 679 | -------------------------------------------------------------------------------- /my_plugin/hpgl/hpgl.h: -------------------------------------------------------------------------------- 1 | #ifndef _HPGL_H 2 | #define _HPGL_H 3 | 4 | #include 5 | #include 6 | 7 | //#define HPGL_DEBUG 8 | 9 | #ifndef HPGL_DEVICE_IDENTIFICATION 10 | #define HPGL_DEVICE_IDENTIFICATION "HP7574A" 11 | #endif 12 | 13 | #define SCRATCHPAD_SIZE 64 14 | 15 | #define MAX_X_A4 11040 16 | #define MAX_Y_A4 7721 17 | #define P1X_A4 603 18 | #define P1Y_A4 521 19 | #define P2X_A4 10603 20 | #define P2Y_A4 7721 21 | 22 | #define MAX_X_A3 16158 23 | #define MAX_Y_A3 11040 24 | #define P1X_A3 170 25 | #define P1Y_A3 602 26 | #define P2X_A3 15370 27 | #define P2Y_A3 10602 28 | 29 | #ifdef HPGL_A3 30 | #define MAX_X MAX_X_A3 31 | #define MAX_Y MAX_Y_A3 32 | #define P1X P1X_A3 33 | #define P1Y P1Y_A3 34 | #define P2Y P2Y_A3 35 | #define P2X P2X_A3 36 | #else 37 | #define MAX_X MAX_X_A4 38 | #define MAX_Y MAX_Y_A4 39 | #define P1X P1X_A4 40 | #define P1Y P1Y_A4 41 | #define P2Y P2Y_A4 42 | #define P2X P2X_A4 43 | #endif 44 | 45 | typedef enum { 46 | ERR_None = 0, 47 | ERR_UnknownCommand, 48 | ERR_WrongParams, 49 | ERR_BadParam, 50 | ERR_Unused1, 51 | ERR_UnknownCharset, 52 | ERR_PosOverflow, 53 | ERR_Unused2, 54 | Err_WheelsUp 55 | } hpgl_error_t; 56 | 57 | /// HPGL commands. Returned by hpgl_char() when there is data and handled by do_stuff() in motori.c 58 | /// @see do_stuff() 59 | /// @see hpgl_char() 60 | typedef enum { 61 | CMD_CONT = 0, ///< Continue, do nothing (data coming) 62 | CMD_ERR, ///< Error 63 | CMD_LB0, ///< Mark label start!! 64 | CMD_AA = 'A' << 8 | 'A', ///< AA: Arc absolute 65 | CMD_AR = 'A' << 8 | 'R', ///< AR: Arc relative 66 | CMD_AS = 'A' << 8 | 'S', ///< AS: Acceleration Select: 0 = no acceleration (nonstandard) 67 | CMD_CA = 'C' << 8 | 'A', ///< CA: Designate alternate character set 68 | CMD_CI = 'C' << 8 | 'I', ///< CI: Circle 69 | CMD_CP = 'C' << 8 | 'P', ///< CP: Character plot 70 | CMD_CS = 'C' << 8 | 'S', ///< CS: Designate standard character set 71 | CMD_DI = 'D' << 8 | 'I', ///< DI: Label direction: numpad[0]=sin(theta), numpad[1]=cos(theta) 72 | CMD_DF = 'D' << 8 | 'F', ///< DF: Set default values 73 | CMD_DT = 'D' << 8 | 'T', ///< DT: Set text terminator 74 | CMD_DV = 'D' << 8 | 'V', ///< DV: Vertical label direction 75 | CMD_EA = 'E' << 8 | 'A', ///< EA: Rectangle absolute 76 | CMD_ER = 'E' << 8 | 'R', ///< EV: Rectangle relative 77 | CMD_ES = 'E' << 8 | 'S', ///< ES: Extra Space 78 | CMD_EW = 'E' << 8 | 'W', ///< EW: Edge Wedge 79 | CMD_IN = 'I' << 8 | 'N', ///< IN: Initialize 80 | CMD_IM = 'I' << 8 | 'M', ///< IM: Input Mask 81 | CMD_IP = 'I' << 8 | 'P', ///< IP: Initialize plotter 82 | CMD_LB = 'L' << 8 | 'B', ///< LB: Label text 83 | CMD_LT = 'L' << 8 | 'T', ///< LT: Line type 84 | 85 | CMD_OA = 'O' << 8 | 'A', ///< OA: Output Actual Position and Pen Status 86 | CMD_OC = 'O' << 8 | 'C', ///< OC: Output Commanded Position and Pen Status 87 | CMD_OD = 'O' << 8 | 'D', ///< OD: Output Digitized Point and Pen Status 88 | CMD_OE = 'O' << 8 | 'E', ///< OE: Output Error 89 | CMD_OF = 'O' << 8 | 'F', ///< OF: Output Factors 90 | CMD_OH = 'O' << 8 | 'H', ///< OH: Output Hard-clip Limits 91 | CMD_OI = 'O' << 8 | 'I', ///< OI: Output Identification 92 | CMD_OO = 'O' << 8 | 'O', ///< OO: Output Options 93 | 94 | CMD_OP = 'O' << 8 | 'P', ///< OP: Output Parameters P1 & P2 95 | 96 | CMD_OS = 'O' << 8 | 'S', ///< OS: Output Status 97 | CMD_OW = 'O' << 8 | 'W', ///< OW: Output Window 98 | 99 | CMD_PA = 'P' << 8 | 'A', ///< PA: Move to returned coordinates 100 | CMD_PD = 'P' << 8 | 'D', ///< PD: Pen down 101 | CMD_PR = 'P' << 8 | 'R', ///< PR: Nove to relative position 102 | CMD_PT = 'P' << 8 | 'T', ///< PT: Pen thickness 103 | CMD_PU = 'P' << 8 | 'U', ///< PU: Pen up 104 | CMD_SA = 'S' << 8 | 'A', ///< SA: Select alternate character set 105 | CMD_SC = 'S' << 8 | 'C', ///< SC: Scale 106 | CMD_SI = 'S' << 8 | 'I', ///< SI: Absolute character size 107 | CMD_SP = 'S' << 8 | 'P', ///< SP: Select pen 108 | CMD_SR = 'S' << 8 | 'R', ///< SR: Relative character size 109 | CMD_SS = 'S' << 8 | 'S', ///< SS: Select standard character set 110 | CMD_VS = 'V' << 8 | 'S', ///< VS: Velocity Select: 0 = fastest (nonstandard) 111 | CMD_SEEK0 = 'H' << 8 | 'S' ///< Locate home position 112 | } hpgl_command_t; 113 | 114 | typedef enum { 115 | Pen_NoAction = 0, 116 | Pen_Up, 117 | Pen_Down, 118 | Pen_Timeout, 119 | Pen_Unknown = 255 120 | } pen_status_t; 121 | 122 | ///< Absolute coordinates used for stepper motion. 123 | ///< Negative means invalid. 124 | typedef int16_t hpgl_coord_t; 125 | 126 | ///< User coordinates used in input, arc calculation etc. 127 | typedef float user_coord_t; 128 | 129 | typedef struct { 130 | hpgl_coord_t x; 131 | hpgl_coord_t y; 132 | } hpgl_point_t; 133 | 134 | typedef struct { 135 | user_coord_t x; 136 | user_coord_t y; 137 | } user_point_t; 138 | 139 | typedef struct { 140 | float width; 141 | float height; 142 | } char_size_t; 143 | 144 | typedef union { 145 | uint8_t value; 146 | struct { 147 | uint8_t pen_down :1, 148 | p1p2_changed :1, 149 | point_available :1, 150 | initialized :1, 151 | ready :1, 152 | error :1, 153 | service :1, 154 | unused :1; 155 | }; 156 | } hpgl_status_flags; 157 | 158 | typedef union { 159 | uint8_t value; 160 | struct { 161 | uint8_t enable_dtr :1, 162 | unused :1, 163 | monitor_mode :1, 164 | monitor_on :1, 165 | block_mode :1, 166 | unassigned :3; 167 | }; 168 | } hpgl_comm_flags; 169 | 170 | typedef struct { 171 | float chord_angle; 172 | float pen_thickness; 173 | bool plot_relative; 174 | uint8_t etxchar; 175 | char term[3]; 176 | uint8_t pattern_type; 177 | float pattern_length; 178 | bool use_alt_charset; 179 | bool text_vertical; 180 | char_size_t character_size; 181 | const char *const *charset; 182 | const char *const *charset_std; 183 | const char *const *charset_alt; 184 | user_point_t cr_loc; 185 | hpgl_error_t first_error; 186 | hpgl_error_t last_error; 187 | uint8_t errmask; 188 | uint8_t alertmask; 189 | float numpad[4]; 190 | // The following values are not changed on a reset to default values, ip_pad must be first! 191 | int32_t ip_pad[4]; 192 | int32_t sc_pad[4]; 193 | int32_t esc_pad[8]; 194 | user_point_t user_loc; 195 | hpgl_status_flags flags; 196 | hpgl_comm_flags comm; 197 | } hpgl_state_t; 198 | 199 | extern hpgl_state_t hpgl_state; 200 | typedef hpgl_command_t (*hpgl_char_ptr)(char c, hpgl_point_t *target, uint8_t *lb); 201 | 202 | /// Initialize the scanner. 203 | void hpgl_init(); 204 | 205 | /// Handle next character from the input. When action is determined, return one of the _hpgl_command values. 206 | /// Pass target coordinates in x and y. 207 | /// @param c input char 208 | /// @param x output: destination x (returns -1 if no data) 209 | /// @param y output: destination y (returns -1 if no data) 210 | /// @param lb output: next label character (see CMD_LB) 211 | /// @returns _hpgl_command 212 | extern hpgl_char_ptr hpgl_char; 213 | 214 | hpgl_error_t hpgl_get_error (void); 215 | void hpgl_set_error (hpgl_error_t errnum); 216 | 217 | #endif 218 | -------------------------------------------------------------------------------- /my_plugin/hpgl/htext.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "hpgl.h" 5 | #include "scale.h" 6 | #include "htext.h" 7 | 8 | #ifdef HPGL_DEBUG 9 | #include "../grbl/hal.h" 10 | #endif 11 | 12 | user_point_t fontscale; 13 | user_point_t charorigin; 14 | user_point_t labelorigin; 15 | 16 | user_coord_t sintheta, costheta; 17 | 18 | static const char *coffs; 19 | 20 | void text_init (void) 21 | { 22 | // user_point_t ip = range_P1P2(); 23 | 24 | // text_setscale(ip.x * 0.75f / 100.0f, ip.y * 1.5f / 100.0f); 25 | text_setscale(10.0f, 10.0f); 26 | text_direction(1.0f, 0.0f); 27 | } 28 | 29 | void text_setscale (float sx, float sy) 30 | { 31 | fontscale.x = sx; 32 | fontscale.y = sy; 33 | 34 | printf_P(PSTR("Text scale is (%f, %f)\n"), fontscale.x, fontscale.y); 35 | } 36 | 37 | void text_scale_cm (float cx, float cy) 38 | { 39 | user_point_t s; 40 | 41 | s.x = cx * 10.0f / 7.0f / 0.025f; 42 | s.y = cy * 10.0f / 10.0f / 0.025f; 43 | 44 | userprescale(s, &fontscale); 45 | text_setscale(fontscale.x, fontscale.y); 46 | } 47 | 48 | void text_scale_rel (float rx, float ry) 49 | { 50 | user_point_t prect = range_P1P2(); 51 | float sx = rx / 100.0f * prect.x / 7.0f; // character size in plotter units 52 | float sy = ry / 100.0f * prect.y / 10.0f; 53 | 54 | text_setscale(sx, sy); 55 | } 56 | 57 | void text_direction (float cost, float sint) 58 | { 59 | sintheta = -sint; 60 | costheta = cost; 61 | 62 | printf_P(PSTR("Label rotation: sin=%f cos=%f\n"), sintheta, costheta); 63 | } 64 | 65 | static void rotate (float* x, float* y) 66 | { 67 | float xc = *x - charorigin.x; 68 | float yc = *y - charorigin.y; 69 | float xrot = xc * costheta + yc * sintheta; 70 | float yrot = -xc * sintheta + yc * costheta; 71 | 72 | *x = xrot + charorigin.x; 73 | *y = yrot + charorigin.y; 74 | } 75 | 76 | void text_beginlabel (void) 77 | { 78 | labelorigin = hpgl_state.user_loc; 79 | printf_P(PSTR("Label origin: (%f,%f)\n"), labelorigin.x, labelorigin.y); 80 | } 81 | 82 | bool text_pos (float x, float y, hpgl_point_t *target) 83 | { 84 | user_point_t d; 85 | 86 | if(isnanf(x)) { 87 | d.x = labelorigin.x; 88 | d.y = labelorigin.y - fontscale.y * 10.0f; 89 | rotate(&d.x, &d.y); 90 | userscale(d, target, &hpgl_state.user_loc); 91 | labelorigin = hpgl_state.user_loc; 92 | } else { 93 | d.x = hpgl_state.user_loc.x; 94 | d.y = hpgl_state.user_loc.y; 95 | if(x != 0.0f) 96 | d.x += fontscale.x * 5.0f * x; 97 | if(y != 0.0f) 98 | d.y += fontscale.y * 10.0f * y; 99 | rotate(&d.x, &d.y); 100 | userscale(d, target, &hpgl_state.user_loc); 101 | } 102 | 103 | charorigin = hpgl_state.user_loc; 104 | 105 | return true; 106 | } 107 | 108 | bool text_char (uint8_t c, hpgl_point_t *target, pen_status_t *pen) 109 | { 110 | user_point_t d; 111 | uint8_t encoded; 112 | static bool noadvance = false; 113 | 114 | if (c != 0) { 115 | 116 | encoded = 1; 117 | *pen = Pen_Up; 118 | 119 | switch (c) { 120 | case '\r': 121 | printf_P(PSTR("CR")); 122 | d.x = labelorigin.x; 123 | d.y = labelorigin.y; 124 | //rotate(&xd, &yd); 125 | userscale(d, target, &hpgl_state.user_loc); 126 | coffs = hpgl_state.charset[0]; 127 | charorigin = hpgl_state.user_loc; 128 | noadvance = true; 129 | break; 130 | case '\n': 131 | printf_P(PSTR("LF")); 132 | d.x = hpgl_state.user_loc.x; 133 | d.y = hpgl_state.user_loc.y - fontscale.y * 10.0f; 134 | rotate(&d.x, &d.y); 135 | userscale(d, target, &hpgl_state.user_loc); 136 | coffs = hpgl_state.charset[0]; 137 | charorigin = hpgl_state.user_loc; 138 | labelorigin = hpgl_state.user_loc; 139 | noadvance = true; 140 | break; 141 | default: 142 | coffs = hpgl_state.charset[c]; 143 | charorigin = hpgl_state.user_loc; 144 | noadvance = false; 145 | //printf_P(PSTR("coffs=%x first=%o"), charset0[c], *charset0[c]); 146 | break; 147 | } 148 | 149 | } else { 150 | 151 | encoded = *coffs++; 152 | *pen = encoded & 0b10000000 ? Pen_Down : Pen_Up; 153 | 154 | if (encoded) { 155 | d.x = charorigin.x + fontscale.x * ((encoded >> 4) & 0b111); 156 | d.y = charorigin.y + fontscale.y * ((encoded & 0b1111) - 4); 157 | } else if (!noadvance) { 158 | d.x = charorigin.x + fontscale.x * 5.0f; 159 | d.y = charorigin.y; 160 | } 161 | 162 | #ifdef HPGL_DEBUG 163 | hal.stream.write("CH:"); 164 | hal.stream.write(ftoa(d.x, 3)); 165 | hal.stream.write(","); 166 | hal.stream.write(ftoa(d.y, 3)); 167 | hal.stream.write(ASCII_EOL); 168 | #endif 169 | 170 | if (!noadvance) { 171 | rotate(&d.x, &d.y); 172 | userscale(d, target, &hpgl_state.user_loc); 173 | } 174 | 175 | #ifdef HPGL_DEBUG 176 | hal.stream.write("CT:"); 177 | hal.stream.write(uitoa(target->x)); 178 | hal.stream.write(","); 179 | hal.stream.write(uitoa(target->y)); 180 | hal.stream.write(ASCII_EOL); 181 | #endif 182 | 183 | } 184 | 185 | return encoded != 0; 186 | } 187 | -------------------------------------------------------------------------------- /my_plugin/hpgl/htext.h: -------------------------------------------------------------------------------- 1 | #ifndef _HTEXT_H 2 | #define _HTEXT_H 3 | 4 | #include 5 | #include 6 | 7 | #include "motori.h" 8 | 9 | void text_init(void); 10 | void text_setscale(float sx, float sy); 11 | void text_scale_cm(float cx, float cy); 12 | void text_scale_rel(float rx, float ry); 13 | void text_direction(float cost, float sint); 14 | 15 | void text_beginlabel(); 16 | bool text_char (uint8_t c, hpgl_point_t *target, pen_status_t *pen); 17 | bool text_pos (float x, float y, hpgl_point_t *target); 18 | 19 | #endif 20 | -------------------------------------------------------------------------------- /my_plugin/hpgl/motori.h: -------------------------------------------------------------------------------- 1 | #ifndef _MOTORI_H 2 | #define _MOTORI_H 3 | 4 | #include "hpgl.h" 5 | #include "grbl/hal.h" 6 | 7 | #define PEN_DOWN_DELAY 20 8 | #define PEN_LIFT_DELAY 50 9 | 10 | #define GO_HOME_ON_IN // Uncomment to disable 11 | 12 | #define PSTR(s) s 13 | #define printf_P printf 14 | #define DELAY_MS(ms) hal.delay_ms(ms, NULL) 15 | 16 | /// Controls the pen position. Both servo and solenoid actuators. 17 | /// Causes immediate delay to allow for the pen to settle. 18 | /// 19 | /// @param down true if pen should be down, 0 if raised 20 | void pen_control (pen_status_t state); 21 | 22 | pen_status_t get_pen_status (void); 23 | 24 | /// Initialize plotter state. Move to home position, then reset everything, including motors and timers. 25 | /// Reset user scale and translation, raise the pen. 26 | void plotter_init(); 27 | 28 | coord_data_t *get_origin (void); 29 | 30 | #endif 31 | -------------------------------------------------------------------------------- /my_plugin/hpgl/scale.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "scale.h" 5 | 6 | #include "grbl/hal.h" 7 | 8 | static user_point_t user_scale, user_translate; 9 | 10 | void translate_init_ip (void) 11 | { 12 | hpgl_state.ip_pad[0] = 0; 13 | hpgl_state.ip_pad[1] = 0; 14 | hpgl_state.ip_pad[2] = MAX_X; 15 | hpgl_state.ip_pad[3] = MAX_Y; 16 | } 17 | 18 | void translate_init_sc (void) 19 | { 20 | user_scale.x = user_scale.y = 1.0f; 21 | user_translate.x = user_translate.y = 0.0f; 22 | 23 | hpgl_state.sc_pad[0] = 0; 24 | hpgl_state.sc_pad[1] = MAX_X; 25 | hpgl_state.sc_pad[2] = 0; 26 | hpgl_state.sc_pad[3] = MAX_Y; 27 | } 28 | 29 | // use IP and SC data to calculate the scale 30 | void translate_scale (void) 31 | { 32 | user_point_t iprange = range_P1P2(); 33 | 34 | int32_t scxrange = hpgl_state.sc_pad[1] - hpgl_state.sc_pad[0]; // xmax - xmin 35 | int32_t scyrange = hpgl_state.sc_pad[3] - hpgl_state.sc_pad[2]; // ymax - ymin 36 | 37 | user_scale.x = iprange.x / (float)scxrange; 38 | user_scale.y = iprange.y / (float)scyrange; 39 | //user_xscale = ((float)scxrange)/((float)ipxrange); 40 | //user_yscale = ((float)scyrange)/((float)ipyrange); 41 | user_translate.x = -hpgl_state.sc_pad[0] * user_scale.x; 42 | user_translate.y = -hpgl_state.sc_pad[2] * user_scale.y; 43 | 44 | // printf_P(PSTR("Scale set to: (%f,%f) translate (%f,%f)"), user_xscale, user_yscale, user_translate_x, user_translate_y); 45 | } 46 | 47 | void userprescale (user_point_t abs, user_point_t *out) 48 | { 49 | out->x = abs.x / user_scale.x; 50 | out->y = abs.y / user_scale.y; 51 | } 52 | 53 | void usertohpgl (user_point_t src, hpgl_point_t *target) 54 | { 55 | target->x = (int16_t)roundf(src.x * user_scale.x); 56 | target->y = (int16_t)roundf(src.y * user_scale.y); 57 | } 58 | 59 | void userscalerelative (user_point_t src, hpgl_point_t *target, user_point_t *out) 60 | { 61 | target->x = (int16_t)roundf(out->x + src.x * user_scale.x); 62 | target->y = (int16_t)roundf(out->y + src.y * user_scale.y); 63 | 64 | if(out) { 65 | out->x = (float)target->x / user_scale.x; 66 | out->y = (float)target->y / user_scale.y; 67 | } 68 | } 69 | 70 | void userscale (user_point_t src, hpgl_point_t *target, user_point_t *out) 71 | { 72 | target->x = (int16_t)roundf(src.x * user_scale.x); 73 | target->y = (int16_t)roundf(src.y * user_scale.y); 74 | 75 | if(out) { 76 | out->x = (float)target->x / user_scale.x; 77 | out->y = (float)target->y / user_scale.y; 78 | } 79 | } 80 | 81 | user_point_t range_P1P2 (void) 82 | { 83 | user_point_t p; 84 | 85 | p.x = (float)(hpgl_state.ip_pad[2] - hpgl_state.ip_pad[0]); 86 | p.y = (float)(hpgl_state.ip_pad[3] - hpgl_state.ip_pad[1]); 87 | 88 | return p; 89 | } 90 | 91 | void output_P1P2 (void) 92 | { 93 | hal.stream.write(uitoa(hpgl_state.ip_pad[0])); 94 | hal.stream.write(","); 95 | hal.stream.write(uitoa(hpgl_state.ip_pad[1])); 96 | hal.stream.write(","); 97 | hal.stream.write(uitoa(hpgl_state.ip_pad[2])); 98 | hal.stream.write(","); 99 | hal.stream.write(uitoa(hpgl_state.ip_pad[3])); 100 | hal.stream.write(hpgl_state.term); 101 | } 102 | -------------------------------------------------------------------------------- /my_plugin/hpgl/scale.h: -------------------------------------------------------------------------------- 1 | #ifndef _SCALE_H 2 | #define _SCALE_H 3 | 4 | #include "hpgl.h" 5 | 6 | /// Initialize translation and scale. 7 | /// 8 | /// Translation is not implemented. 9 | void translate_init_ip (void); 10 | void translate_init_sc (void); 11 | 12 | /// Use IP and SC data to calculate the scale and translation. 13 | /// 14 | /// Translation is not implemented. 15 | void translate_scale (void); 16 | 17 | /// Transform user coordinates (fx,fy) into plotter coordinates (x,y) according to 18 | /// the transform defined by IP/SC. Then do reverse transform, round and assign 19 | /// resulting values to (ox,oy). 20 | /// @param fx user coordinates x 21 | /// @param fy user coordinates y 22 | /// @param *x (output) absolute stepper x 23 | /// @param *y (output) absolute stepper y 24 | /// @param *ox (output) corrected fx 25 | /// @param *oy (output) corrected fy 26 | /// 27 | void userscale (user_point_t src, hpgl_point_t *target, user_point_t *out); 28 | 29 | void userscalerelative (user_point_t src, hpgl_point_t *target, user_point_t *out); 30 | 31 | void usertohpgl (user_point_t src, hpgl_point_t *target); 32 | 33 | /// Something that shouldn't be used 34 | void userprescale (user_point_t abs, user_point_t *out); 35 | 36 | /// Something else that should not be used 37 | user_point_t range_P1P2 (void); 38 | 39 | void output_P1P2 (void); 40 | 41 | #endif 42 | -------------------------------------------------------------------------------- /my_plugin/settings/my_plugin.c: -------------------------------------------------------------------------------- 1 | /* 2 | my_plugin.c - user defined plugin template with settings handling 3 | 4 | Part of grblHAL 5 | 6 | Public domain. 7 | This code is is distributed in the hope that it will be useful, 8 | but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 10 | */ 11 | 12 | /* 13 | * NOTE: this plugin does not add any other fuctionality than settings handling, attach to other HAL entry points to provide that. 14 | * See the mcode.c template or standard/template plugins for how to do this. 15 | */ 16 | 17 | #include "grbl/hal.h" 18 | #include "grbl/report.h" 19 | #include "grbl/nvs_buffer.h" 20 | #include "grbl/nuts_bolts.h" 21 | 22 | typedef struct { 23 | float fvalue; 24 | uint16_t ivalue; 25 | } plugin_settings_t; 26 | 27 | static nvs_address_t nvs_address; 28 | static on_report_options_ptr on_report_options; 29 | static plugin_settings_t my_settings; 30 | 31 | static status_code_t set_my_setting1 (setting_id_t id, uint16_t value) 32 | { 33 | my_settings.ivalue = value; 34 | 35 | // do some stuff related to changes in the setting value 36 | 37 | return Status_OK; 38 | } 39 | 40 | static uint16_t get_my_setting1 (setting_id_t setting) 41 | { 42 | return my_settings.ivalue; 43 | } 44 | 45 | // Add info about our settings for $help and enumerations. 46 | // Potentially used by senders for settings UI. 47 | static const setting_group_detail_t user_groups [] = { 48 | { Group_Root, Group_UserSettings, "My settings"} 49 | }; 50 | 51 | // Setting_UserDefined_0 is read and written by the core via the &my_settings.fvalue pointer. 52 | // Setting_UserDefined_1 is read and written via calls to get_my_setting1() and set_my_setting1(). 53 | // - this is useful for settings that requires immediate action on a change and/or value transformation. 54 | static const setting_detail_t user_settings[] = { 55 | { Setting_UserDefined_0, Group_UserSettings, "My setting 1", NULL, Format_Decimal, "#0.0", "0", "15", Setting_NonCore, &my_settings.fvalue, NULL, NULL }, 56 | { Setting_UserDefined_1, Group_UserSettings, "My setting 2", "milliseconds", Format_Int16, "####0", "50", "250", Setting_NonCoreFn, set_my_setting1, get_my_setting1, NULL } 57 | }; 58 | 59 | // Write settings to non volatile storage (NVS). 60 | static void plugin_settings_save (void) 61 | { 62 | hal.nvs.memcpy_to_nvs(nvs_address, (uint8_t *)&my_settings, sizeof(plugin_settings_t), true); 63 | } 64 | 65 | // Restore default settings and write to non volatile storage (NVS). 66 | static void plugin_settings_restore (void) 67 | { 68 | my_settings.fvalue = 3.1f; 69 | my_settings.ivalue = 2; 70 | 71 | hal.nvs.memcpy_to_nvs(nvs_address, (uint8_t *)&my_settings, sizeof(plugin_settings_t), true); 72 | } 73 | 74 | // Load our settings from non volatile storage (NVS). 75 | // If load fails restore to default values. 76 | static void plugin_settings_load (void) 77 | { 78 | if(hal.nvs.memcpy_from_nvs((uint8_t *)&my_settings, nvs_address, sizeof(plugin_settings_t), true) != NVS_TransferResult_OK) 79 | plugin_settings_restore(); 80 | } 81 | 82 | // Add info about our plugin to the $I report. 83 | static void on_report_my_options (bool newopt) 84 | { 85 | on_report_options(newopt); 86 | 87 | if(!newopt) 88 | report_plugin("Settings template plugin", "1.03"); 89 | } 90 | 91 | // A call my_plugin_init will be issued automatically at startup. 92 | // There is no need to change any source code elsewhere. 93 | void my_plugin_init (void) 94 | { 95 | // Settings descriptor used by the core when interacting with this plugin. 96 | static setting_details_t setting_details = { 97 | .groups = user_groups, 98 | .n_groups = sizeof(user_groups) / sizeof(setting_group_detail_t), 99 | .settings = user_settings, 100 | .n_settings = sizeof(user_settings) / sizeof(setting_detail_t), 101 | .save = plugin_settings_save, 102 | .load = plugin_settings_load, 103 | .restore = plugin_settings_restore 104 | }; 105 | 106 | // Try to allocate space for our settings in non volatile storage (NVS). 107 | if((nvs_address = nvs_alloc(sizeof(plugin_settings_t)))) { 108 | 109 | // Add info about our plugin to the $I report. 110 | on_report_options = grbl.on_report_options; 111 | grbl.on_report_options = on_report_my_options; 112 | 113 | // Register our settings with the core. 114 | settings_register(&setting_details); 115 | 116 | // "Hook" into other HAL pointers here to provide functionality. 117 | } 118 | } 119 | --------------------------------------------------------------------------------