Total rework of simulator for dev branch. Create separate thread for interrupt processes. Tick-accurate simulation of timers. Non-blocking character input for running in realtime mode. Decouple hardware sim from grbl code as much as possible. Expanded command line options. Provisions for cross-platform solution.

This commit is contained in:
ashelly
2014-07-04 11:14:54 -04:00
parent 92d6c2bca5
commit 8c9f3bca65
33 changed files with 1062 additions and 437 deletions

1
sim/.gitignore vendored
View File

@ -1 +1,2 @@
grbl_sim.exe
*.dat

View File

@ -15,11 +15,13 @@
# You should have received a copy of the GNU General Public License
# along with Grbl. If not, see <http://www.gnu.org/licenses/>.
OBJECTS = main.o simulator.o runtime.o ../protocol.o ../planner.o ../settings.o ../print.o ../nuts_bolts.o eeprom.o serial.o avr/pgmspace.o avr/interrupt.o util/delay.o util/floatunsisf.o ../stepper.o ../gcode.o ../spindle_control.o ../motion_control.o ../limits.o ../report.o ../coolant_control.o
PLATFORM = LINUX
OBJECTS = main.o simulator.o serial.o ../main.o ../protocol.o ../planner.o ../settings.o ../print.o ../nuts_bolts.o eeprom.o ../serial.o avr/pgmspace.o avr/interrupt.o avr/io.o util/delay.o util/floatunsisf.o ../stepper.o ../gcode.o ../spindle_control.o ../motion_control.o ../limits.o ../report.o ../coolant_control.o ../probe.o ../system.o kbhit.o platform_$(PLATFORM).o
CLOCK = 16000000
EXE_NAME = grbl_sim.exe
COMPILE = $(CC) -Wall -Os -DF_CPU=$(CLOCK) -include config.h -I.
COMPILE = $(CC) -Wall -g -DF_CPU=$(CLOCK) -include config.h -I. -DPLAT_$(PLATFORM)
LINUX_LIBRARIES = -lrt -pthread
# symbolic targets:
all: main
@ -30,15 +32,18 @@ clean:
# file targets:
main: $(OBJECTS)
$(COMPILE) -o $(EXE_NAME) $(OBJECTS) -lm
$(COMPILE) -o $(EXE_NAME) $(OBJECTS) -lm -lrt $($(PLATFORM)_LIBRARIES)
%.o: %.c
$(COMPILE) -c $< -o $@
../protocol.o: ../protocol.c
$(COMPILE) -include rename_execute_runtime.h -c $< -o $@
$(COMPILE) -c $< -o $@
../planner.o: ../planner.c
$(COMPILE) -include planner_inject_accessors.c -c $< -o $@
../serial.o: ../serial.c
$(COMPILE) -include serial_hooks.h -c $< -o $@
../main.o: ../main.c
$(COMPILE) -include rename_main.h -c $< -o $@

View File

@ -21,23 +21,115 @@
*/
#include "interrupt.h"
#include "io.h"
//pseudo-Interrupt vector table
isr_fp compa_vect[6]={0};
isr_fp compb_vect[6]={0};
isr_fp ovf_vect[6]={0};
void sei() {io.sreg|=SEI;}
void cli() {io.sreg&=~SEI;}
int16_t sim_scaling[8]={0,1,8,64,256,1024,1,1}; //clock scalars
//Timer/Counter modes: these are incomplete, but enough for this application
enum sim_wgm_mode {
wgm_NORMAL,
wgm_CTC,
wgm_FAST_PWM,
wgm_PHASE_PWM,
wgm_PH_F_PWM,
wgm_RESERVED
};
enum sim_wgm_mode sim_wgm0[4] = {wgm_NORMAL,wgm_PHASE_PWM,wgm_CTC,wgm_FAST_PWM};
enum sim_wgm_mode sim_wgmN[8] = {wgm_NORMAL,wgm_PHASE_PWM,wgm_PHASE_PWM,wgm_PH_F_PWM,
wgm_CTC, wgm_FAST_PWM, wgm_FAST_PWM, wgm_FAST_PWM};
void timer_interrupts() {
int i;
uint8_t ien = io.sreg&SEI; //interrupts enabled?
io.prescaler++;
//all clocks
for (i=0;i<2;i++){
uint8_t cs = io.tccrb[i]&7; //clock select bits
int16_t increment = sim_scaling[cs];
//check scaling to see if timer fires
if (increment && (io.prescaler&(increment-1))==0) {
//select waveform generation mode
enum sim_wgm_mode mode;
if (i==0 || i==2) { //(T0 and T2 are different from rest)
uint8_t wgm = io.tccra[i]&3; //look at low 2 bits
mode = sim_wgm0[wgm];
}
else {
uint8_t wgm = ((io.tccrb[i]&8)>>1) | (io.tccra[i]&3); //only using 3 bits for now
mode = sim_wgmN[wgm];
}
//tick
io.tcnt[i]++;
//comparators
if ((io.timsk[i]&(1<<SIM_OCA)) && io.tcnt[i]==io.ocra[i]) io.tifr[i]|=(1<<SIM_OCA);
if ((io.timsk[i]&(1<<SIM_OCB)) && io.tcnt[i]==io.ocrb[i]) io.tifr[i]|=(1<<SIM_OCB);
if ((io.timsk[i]&(1<<SIM_OCC)) && io.tcnt[i]==io.ocrc[i]) io.tifr[i]|=(1<<SIM_OCC);
switch (mode) {
case wgm_NORMAL: //Normal mode
if (i==0) io.tcnt[i]&=0xFF; //timer0 is 8 bit;
if (i==2) io.tcnt[i]&=0xFF; //timer2 is 8 bit;
if (io.tcnt[i]==0) io.tifr[i]|=(1<<SIM_TOV);
break;
case wgm_CTC: //CTC mode
if (io.tcnt[i]==io.ocra[i]) io.tcnt[i]=0;
break;
default: //unsupported
break;
}
//call any triggered interupts
if (ien && io.tifr[i]) {
if (compa_vect[i] && (io.tifr[i]&(1<<SIM_OCA))) {
compa_vect[i]();
io.tifr[i]&=~(1<<SIM_OCA);
//TODO: insert port_monitor call here
}
if (compb_vect[i] && (io.tifr[i]&(1<<SIM_OCB))) {
compb_vect[i]();
io.tifr[i]&=~(1<<SIM_OCB);
}
if (ovf_vect[i] && (io.tifr[i]&(1<<SIM_TOV))) {
ovf_vect[i]();
io.tifr[i]&=~(1<<SIM_TOV);
}
}
}
}
//// TODO for more complete timer sim.
// pwm modes. (only used for variable spindle, I think).
// -- would require fixing wgm mode for Timers1..5
// -- phase correct modes need updown counter.
// output pins (also only for variable spindle, I think).
//// Other chip features not needed yet for grbl:
// writes to TCNT0 prevent compare match (need write detector.)
// force output compare (unused)
// input capture (unused and how would we signal it?)
// define the other output compare registers.
// usercode can clear unhandled interrupt flags by writing 1.
// --(this may be impossible, since bit was 1 before the write.)
// prescaler reset.
// maybe need to cli on interrupt entry
}
// dummy register variables
uint16_t timsk0;
uint16_t timsk1;
uint16_t timsk2;
uint16_t ocr1a;
uint16_t ocr2a;
uint16_t tcnt0;
uint16_t tcnt2;
uint16_t tccr0b;
uint16_t tccr2b;
uint16_t tccr1b;
uint16_t tccr0a;
uint16_t tccr1a;
uint16_t tccr2a;
uint16_t pcmsk0;
uint16_t pcicr;
void sei() {};
void cli() {};

View File

@ -24,60 +24,33 @@
#ifndef interrupt_h
#define interrupt_h
#include <inttypes.h>
// dummy register variables
extern uint16_t timsk0;
extern uint16_t timsk1;
extern uint16_t timsk2;
extern uint16_t tcnt0;
extern uint16_t tcnt2;
extern uint16_t tccr0b;
extern uint16_t tccr0a;
extern uint16_t tccr2a;
extern uint16_t tccr2b;
extern uint16_t tccr1b;
extern uint16_t tccr1a;
extern uint16_t ocr1a;
extern uint16_t ocr2a;
extern uint16_t pcmsk0;
extern uint16_t pcicr;
// macros to turn avr interrupts into regular functions
#define TIMER1_COMPA_vect
//#define TIMER1_COMPA_vect
#define ISR(a) void interrupt_ ## a ()
// enable interrupts does nothing in the simulation environment
// Stub of the timer interrupt functions we need
void interrupt_TIMER0_COMPA_vect();
void interrupt_TIMER1_COMPA_vect();
void interrupt_TIMER0_OVF_vect();
void interrupt_SERIAL_UDRE();
void interrupt_SERIAL_RX();
//pseudo-Interrupt vector table
typedef void(*isr_fp)(void);
extern isr_fp compa_vect[6];
extern isr_fp compb_vect[6];
extern isr_fp ovf_vect[6];
// enable interrupts now does something in the simulation environment
#define SEI 0x80
void sei();
void cli();
// dummy macros for interrupt related registers
#define TIMSK0 timsk0
#define TIMSK1 timsk1
#define TIMSK2 timsk2
#define OCR1A ocr1a
#define OCR2A ocr2a
#define OCIE1A 0
#define OCIE2A 0
#define TCNT0 tcnt0
#define TCNT2 tcnt2
#define TCCR0B tccr0b
#define TCCR0A tccr0a
#define TCCR1A tccr1a
#define TCCR1B tccr1b
#define TCCR2A tccr2a
#define TCCR2B tccr2b
#define CS21 0
#define CS10 0
#define WGM13 0
#define WGM12 0
#define WGM11 0
#define WGM10 0
#define WGM21 0
#define COM1A0 0
#define COM1B0 0
#define TOIE0 0
#define TOIE2 0
#define PCICR pcicr
//simulate timer operation
void timer_interrupts();
#endif

4
sim/avr/io.c Normal file
View File

@ -0,0 +1,4 @@
#include "io.h"
// dummy register variables
volatile io_sim_t io={{0}};

View File

@ -1,5 +1,6 @@
/*
io.h - dummy replacement for the avr include of the same name
interrupt.h - replacement for the avr include of the same name to provide
dummy register variables and macros
Part of Grbl Simulator
@ -19,3 +20,182 @@
along with Grbl. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef io_h
#define io_h
#include <inttypes.h>
union hilo16 {
uint16_t w;
struct {
uint8_t l; //TODO: check that these are right order on x86. Doesn't matter for current usage, but might someday
uint8_t h;
};
};
enum {
SIM_A, SIM_B, SIM_C, SIM_D, SIM_E,
SIM_F, SIM_G, SIM_H, SIM_J, SIM_K, SIM_L,
SIM_PORT_COUNT
};
#define SIM_N_TIMERS 6
// dummy register variables
typedef struct io_sim {
uint8_t ddr[SIM_PORT_COUNT];
uint8_t port[SIM_PORT_COUNT];
uint8_t pin[SIM_PORT_COUNT];
uint8_t timsk[SIM_N_TIMERS];
uint16_t ocra[SIM_N_TIMERS];
uint16_t ocrb[SIM_N_TIMERS];
uint16_t ocrc[SIM_N_TIMERS];
uint16_t tcnt[SIM_N_TIMERS]; //tcint0 is really only 8bit
uint8_t tccra[SIM_N_TIMERS];
uint8_t tccrb[SIM_N_TIMERS];
uint8_t tifr[SIM_N_TIMERS];
uint8_t pcicr;
uint8_t pcmsk[3];
uint8_t ucsr0[3];
uint8_t udr[3];
union hilo16 ubrr0;
uint16_t prescaler; //continuously running
uint8_t sreg;
} io_sim_t;
volatile extern io_sim_t io;
// dummy macros for interrupt related registers
#define PORTA io.port[SIM_A]
#define PORTB io.port[SIM_B]
#define PORTC io.port[SIM_C]
#define PORTD io.port[SIM_D]
#define PORTE io.port[SIM_E]
#define PORTF io.port[SIM_F]
#define PORTG io.port[SIM_G]
#define PORTH io.port[SIM_H]
#define PORTJ io.port[SIM_J]
#define PORTK io.port[SIM_K]
#define PORTL io.port[SIM_L]
#define DDRA io.ddr[SIM_A]
#define DDRB io.ddr[SIM_B]
#define DDRC io.ddr[SIM_C]
#define DDRD io.ddr[SIM_D]
#define DDRE io.ddr[SIM_E]
#define DDRF io.ddr[SIM_F]
#define DDRG io.ddr[SIM_G]
#define DDRH io.ddr[SIM_H]
#define DDRJ io.ddr[SIM_J]
#define PINA io.pin[SIM_A]
#define PINB io.pin[SIM_B]
#define PINC io.pin[SIM_C]
#define PIND io.pin[SIM_D]
#define PINE io.pin[SIM_E]
#define PINF io.pin[SIM_F]
#define PING io.pin[SIM_G]
#define PINH io.pin[SIM_H]
#define PINJ io.pin[SIM_J]
#define PINK io.pin[SIM_K]
#define PINL io.pin[SIM_L]
#define TIMSK0 io.timsk[0]
#define TIMSK1 io.timsk[1]
#define TIMSK2 io.timsk[2]
#define TIMSK3 io.timsk[3]
#define TIMSK4 io.timsk[4]
#define TIMSK5 io.timsk[5]
#define SIM_TOV 0
#define SIM_OCA 1
#define SIM_OCB 2
#define SIM_OCC 3
#define SIM_ICI 5
#define OCIE0A SIM_OCA
#define OCIE0B SIM_OCB
#define TOIE0 SIM_TOV
#define ICIE1 SIM_ICI
#define OCIE1C SIM_OCC
#define OCIE1B SIM_OCB
#define OCIE1A SIM_OCA
#define TOIE1 SIM_ICI
#define ICIE2 SIM_ICI
#define OCIE2C SIM_OCC
#define OCIE2B SIM_OCB
#define OCIE2A SIM_OCA
#define TOIE2 SIM_TOV
#define OCR0A io.ocra[0]
#define OCR1A io.ocra[1]
#define OCR2A io.ocra[2]
//There are more..
#define TCNT0 io.tcnt[0]
#define TCNT1 io.tcnt[1]
#define TCNT2 io.tcnt[2]
#define TCCR0B io.tccra[0]
#define TCCR0A io.tccrb[0]
#define TCCR1A io.tccra[1]
#define TCCR1B io.tccrb[1]
#define TCCR2A io.tccra[2]
#define TCCR2B io.tccrb[2]
#define CS00 0
#define CS01 1
#define CS12 2
#define CS11 1
#define CS10 0
#define CS21 1
#define WGM13 4
#define WGM12 3
#define WGM11 1
#define WGM10 0
#define WGM21 1
#define COM1A1 7
#define COM1A0 6
#define COM1B1 5
#define COM1B0 4
#define COM1C1 3
#define COM1C0 2
#define PCICR io.pcicr
#define PCIE0 0
#define PCIE1 1
//serial channel
#define UCSR0A io.ucsr0[SIM_A]
#define UCSR0B io.ucsr0[SIM_B]
#define UDR0 io.udr[0]
#define UDRIE0 0
#define RXCIE0 1
#define RXEN0 2
#define TXEN0 3
#define U2X0 4
#define UBRR0H io.ubrr0.h
#define UBRR0L io.ubrr0.l
#define PCMSK0 io.pcmsk[0]
#define PCMSK1 io.pcmsk[1]
#define PCMSK2 io.pcmsk[2]
#endif

1
sim/avr/wdt.h Normal file
View File

@ -0,0 +1 @@
uint16_t wdt;

View File

@ -24,94 +24,5 @@
#include "../config.h"
#include <inttypes.h>
// dummy register variables implemented in simulator.c
extern uint8_t stepping_ddr;
extern uint8_t stepping_port;
extern uint8_t spindle_ddr;
extern uint8_t spindle_port;
extern uint8_t limit_ddr;
extern uint8_t limit_port;
extern uint8_t limit_int_reg;
extern uint8_t pinout_ddr;
extern uint8_t pinout_port;
extern uint8_t pinout_int_reg;
extern uint8_t coolant_flood_ddr;
extern uint8_t coolant_flood_port;
// ReDefine pin-assignments
#undef STEPPING_DDR
#define STEPPING_DDR stepping_ddr
#undef STEPPING_PORT
#define STEPPING_PORT stepping_port
#undef X_STEP_BIT
#define X_STEP_BIT 2 // Uno Digital Pin 2
#undef Y_STEP_BIT
#define Y_STEP_BIT 3 // Uno Digital Pin 3
#undef Z_STEP_BIT
#define Z_STEP_BIT 4 // Uno Digital Pin 4
#undef X_DIRECTION_BIT
#define X_DIRECTION_BIT 5 // Uno Digital Pin 5
#undef Y_DIRECTION_BIT
#define Y_DIRECTION_BIT 6 // Uno Digital Pin 6
#undef Z_DIRECTION_BIT
#define Z_DIRECTION_BIT 7 // Uno Digital Pin 7
#undef STEPPERS_DISABLE_DDR
#define STEPPERS_DISABLE_DDR stepping_ddr
#undef STEPPERS_DISABLE_PORT
#define STEPPERS_DISABLE_PORT stepping_port
#undef STEPPERS_DISABLE_BIT
#define STEPPERS_DISABLE_BIT 0 // Uno Digital Pin 8
#undef LIMIT_DDR
#define LIMIT_DDR limit_ddr
#undef LIMIT_PORT
#define LIMIT_PORT limit_port
#undef LIMIT_PIN
#define LIMIT_PIN limit_port
#undef X_LIMIT_BIT
#define X_LIMIT_BIT 1 // Uno Digital Pin 9
#undef Y_LIMIT_BIT
#define Y_LIMIT_BIT 2 // Uno Digital Pin 10
#undef Z_LIMIT_BIT
#define Z_LIMIT_BIT 3 // Uno Digital Pin 11
#undef LIMIT_INT
#define LIMIT_INT 0
#undef LIMIT_PCMSK
#define LIMIT_PCMSK limit_int_reg
#undef SPINDLE_ENABLE_DDR
#define SPINDLE_ENABLE_DDR spindle_ddr
#undef SPINDLE_ENABLE_PORT
#define SPINDLE_ENABLE_PORT spindle_port
#undef SPINDLE_ENABLE_BIT
#define SPINDLE_ENABLE_BIT 4 // Uno Digital Pin 12
#undef SPINDLE_DIRECTION_DDR
#define SPINDLE_DIRECTION_DDR spindle_ddr
#undef SPINDLE_DIRECTION_PORT
#define SPINDLE_DIRECTION_PORT spindle_port
#undef SPINDLE_DIRECTION_BIT
#define SPINDLE_DIRECTION_BIT 5 // Uno Digital Pin 13
#undef PINOUT_DDR
#define PINOUT_DDR pinout_ddr
#undef PINOUT_PORT
#define PINOUT_PORT pinout_port
#undef PINOUT_PIN
#define PINOUT_PIN pinout_port
#undef PINOUT_PCMSK
#define PINOUT_PCMSK pinout_int_reg
#undef PINOUT_INT
#define PINOUT_INT 0
#undef COOLANT_FLOOD_DDR
#define COOLANT_FLOOD_DDR coolant_flood_ddr
#undef COOLANT_FLOOD_PORT
#define COOLANT_FLOOD_PORT coolant_flood_port
#undef COOLANT_FLOOD_BIT
#define COOLANT_FLOOD_BIT 0
#define AUTO_REPORT_MOVE_DONE
#endif

View File

@ -21,18 +21,75 @@
*/
// These are never called in the simulator
#include <stdio.h>
#define MAX_EEPROM_SIZE 4096 //4KB EEPROM in Mega
FILE* eeprom_create_empty_file(){
FILE* fp = fopen("EEPROM.DAT","w+b");
int i;
if (fp){
for(i=0;i<MAX_EEPROM_SIZE;i++){
fputc(0,fp);
}
fseek(fp,0,SEEK_SET);
}
return fp;
}
FILE* eeprom_fp(){
static FILE* EEPROM_FP = NULL;
static int tried= 0;
if (!EEPROM_FP && !tried){
tried = 1;
EEPROM_FP = fopen("EEPROM.DAT","r+b");
if (!EEPROM_FP) {
EEPROM_FP = eeprom_create_empty_file();
}
}
return EEPROM_FP;
}
void eeprom_close(){
FILE* fp = eeprom_fp();
fclose(fp);
}
unsigned char eeprom_get_char( unsigned int addr ) {
return 0;
FILE* fp = eeprom_fp();
if (fseek(fp,addr,SEEK_SET)) { return 0; } //no such address
return fgetc(fp);
}
void eeprom_put_char( unsigned int addr, unsigned char new_value ) {
FILE* fp = eeprom_fp();
if (fseek(fp,addr,SEEK_SET)) { return; } //no such address
fputc(new_value, fp);
}
// Extensions added as part of Grbl
// KEEP IN SYNC WITH ../eeprom.c
void memcpy_to_eeprom_with_checksum(unsigned int destination, char *source, unsigned int size) {
unsigned char checksum = 0;
for(; size > 0; size--) {
checksum = (checksum << 1) || (checksum >> 7);
checksum += *source;
eeprom_put_char(destination++, *(source++));
}
eeprom_put_char(destination, checksum);
}
int memcpy_from_eeprom_with_checksum(char *destination, unsigned int source, unsigned int size) {
return 0;
unsigned char data, checksum = 0;
for(; size > 0; size--) {
data = eeprom_get_char(source++);
checksum = (checksum << 1) || (checksum >> 7);
checksum += data;
*(destination++) = data;
}
return(checksum == eeprom_get_char(source));
}
// end of file

3
sim/eeprom.h Normal file
View File

@ -0,0 +1,3 @@
#include "../eeprom.h"
void eeprom_close();

6
sim/kbhit.c Normal file
View File

@ -0,0 +1,6 @@
#include <sys/types.h>
#include <sys/time.h>
#include "kbhit.h"

View File

@ -1,38 +1,39 @@
/*
runtime.c - replacement for the modul of the same name in grbl
Run time commands are not processed in the simulator.
Instead, the execute_runtime() is used as a hook to handle stepper simulation
and printing of simulation results.
Part of Grbl Simulator
Copyright (c) 2012 Jens Geisler
Grbl is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Grbl is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Grbl. If not, see <http://www.gnu.org/licenses/>.
*/
#include "simulator.h"
#include <stdio.h>
void orig_protocol_execute_runtime(void);
// replacement for original execute_runtime as a hook to print blocks as they are generated
// and to control simulation of buffered blocks
void protocol_execute_runtime(void) {
orig_protocol_execute_runtime();
//printf("printBlock():\n");
printBlock();
//printf("handle_buffer():\n");
handle_buffer();
}
/*
kbhit.h - keyboard hit detection - used in serial port replacement for grbl sim
linux kbhit taken from http://cboard.cprogramming.com/c-programming/63166-kbhit-linux.html.
By 'thanatos':http://cboard.cprogramming.com/member.php?u=380
Part of Grbl Simulator
Copyright (c) 2014 Adam Shelly
Grbl is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Grbl is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Grbl. If not, see <http://www.gnu.org/licenses/>.
*/
//if linux
#include <stdio.h>
#include <termios.h>
#include <unistd.h>
int kbhit(void);
void enable_kbhit(int);
//else
//#include <conio.h>
//#define enable_kbhit(e)
//#define kbhit _kbhit
//endif

View File

@ -34,18 +34,106 @@
#include "simulator.h"
arg_vars_t args;
const char* progname;
int usage(const char* badarg){
if (badarg){
printf("Unrecognized option %s\n",badarg);
}
printf("Usage: \n"
"%s [options] [time_step] [block_file]\n"
" Options:\n"
" -r <report time> : minimum time step for printing stepper values. Default=0=no print.\n"
" -t <time factor> : multiplier to realtime clock. Default=1. (needs work)\n"
" -g <response file> : file to report responses from grbl. default = stdout\n"
" -b <block file> : file to report each block executed. default = stdout\n"
" -s <step file> : file to report each step executed. default = stderr\n"
" -c<comment_char> : character to print before each line from grbl. default = '#'\n"
" -n : no comments before grbl response lines.\n"
" -h : this help.\n"
"\n <time_step> and <block_file> can be specifed with option flags or positional parameters\n"
"\n ^-F to shutdown cleanly\n\n",
progname);
return -1;
}
//prototype for renamed original main function
int avr_main(void);
//wrapper for thread interface
PLAT_THREAD_FUNC(avr_main_thread,exit){
avr_main();
return NULL;
}
int main(int argc, char *argv[]) {
uint32_t tick_rate=1;
int positional_args=0;
//defaults
args.step_out_file = stderr;
args.block_out_file = stdout;
args.grbl_out_file = stdout;
args.comment_char = '#';
args.step_time = 0.0;
// Get the minimum time step for printing stepper values.
// If not given or the command line cannot be parsed to a float than
// step_time= 0.0; This means to not print stepper values at all
if(argc>1) {
argv++;
step_time= atof(*argv);
progname = argv[0];
while (argc>1) {
argv++;argc--;
if (argv[0][0] == '-'){
switch(argv[0][1]){
case 'c': //set Comment char
args.comment_char = argv[0][2];
break;
case 'n': //No comment char on grbl responses
args.comment_char = 0;
break;
case 't': //Tick rate
argv++;argc--;
tick_rate = atof(*argv);
break;
case 'b': //Block file
argv++;argc--;
args.block_out_file = fopen(*argv,"w");
break;
case 's': //Step out file.
argv++;argc--;
args.step_out_file = fopen(*argv,"w");
break;
case 'g': //Grbl output
argv++;argc--;
args.grbl_out_file = fopen(*argv,"w");
break;
case 'r': //step_time for Reporting
argv++;argc--;
args.step_time= atof(*argv);
break;
case 'h':
return usage(NULL);
default:
return usage(*argv);
}
}
else { //handle old positional argument interface
positional_args++;
switch(positional_args){
case 1:
args.step_time= atof(*argv);
break;
case 2: //block out and grbl out to same file, like before.
args.block_out_file = fopen(*argv,"w");
args.grbl_out_file = args.block_out_file;
break;
default:
return usage(*argv);
}
}
}
// Setup output file handles. Block info goes to stdout. Stepper values go to stderr.
block_out_file= stdout;
step_out_file= stderr;
// Make sure the output streams are flushed immediately.
// This is important when using the simulator inside another application in parallel
// to the real grbl.
@ -53,36 +141,26 @@ int main(int argc, char *argv[]) {
// does not know line buffered streams. So for now we stick to flushing every character.
//setvbuf(stdout, NULL, _IONBF, 1);
//setvbuf(stderr, NULL, _IONBF, 1);
st_init(); // Setup stepper pins and interrupt timers
memset(&sys, 0, sizeof(sys)); // Clear all system variables
//settings_reset(); TODO: implement read_settings from eeprom
settings_init();
protocol_init(); // Clear incoming line data
//printf("plan_init():\n");
plan_init(); // Clear block buffer and planner variables
//printf("gc_init():\n");
gc_init(); // Set g-code parser to default state
//printf("spindle_init():\n");
spindle_init();
//printf("limits_init():\n");
limits_init();
//printf("coolant_init():\n");
coolant_init();
//printf("st_reset():\n");
st_reset(); // Clear stepper subsystem variables.
sys.auto_start = true; // runtime commands are not processed. We start to simulate immediately
// Main loop of command processing until EOF is encountered
//printf("protocol_process():\n");
protocol_process();
// flush the block buffer and print stepper info on any pending blocks
plan_synchronize();
//( Files are now closed cleanly when sim gets EOF or CTRL-F.)
platform_init();
init_simulator(tick_rate);
//launch a thread with the original grbl code.
plat_thread_t*th = platform_start_thread(avr_main_thread);
if (!th){
printf("Fatal: Unable to start hardware thread.\n");
exit(-5);
}
//All the stream io and interrupt happen in this thread.
sim_loop();
platform_kill_thread(th); //need force kill since original main has no return.
// Graceful exit
fclose(block_out_file);
fclose(step_out_file);
shutdown_simulator(0);
platform_terminate();
exit(EXIT_SUCCESS);
}

View File

@ -1,10 +1,10 @@
#include "../planner.h"
static block_t block_buffer[BLOCK_BUFFER_SIZE]; // A ring buffer for motion instructions
block_t *get_block_buffer() { return block_buffer; }
static plan_block_t block_buffer[BLOCK_BUFFER_SIZE]; // A ring buffer for motion instructions
plan_block_t *get_block_buffer() { return block_buffer; }
static volatile uint8_t block_buffer_head; // Index of the next block to be pushed
static uint8_t block_buffer_head; // Index of the next block to be pushed
uint8_t get_block_buffer_head() { return block_buffer_head; }
static volatile uint8_t block_buffer_tail; // Index of the next block to be pushed
static uint8_t block_buffer_tail; // Index of the next block to be pushed
uint8_t get_block_buffer_tail() { return block_buffer_tail; }

23
sim/platform.h Normal file
View File

@ -0,0 +1,23 @@
#ifndef platform_h
#ifdef PLAT_LINUX
#include "platform_linux.h"
#else
#include "platform_windows.h"
#endif
#define platform_h
void platform_init();
void platform_terminate();
plat_thread_t* platform_start_thread(plat_threadfunc_t func);
void platform_stop_thread(plat_thread_t* thread);
void platform_kill_thread(plat_thread_t* thread);
uint32_t platform_ns(); //monotonically increasing nanoseconds since program start.
void platform_sleep(long microsec); //sleep for suggested time in microsec.
uint8_t platform_poll_stdin(); //non-blocking stdin read - returns 0 if no char present, 0xFF for EOF
#endif

116
sim/platform_LINUX.c Normal file
View File

@ -0,0 +1,116 @@
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <termios.h>
#include <time.h>
#include <sys/time.h>
#include "platform.h"
#define MS_PER_SEC 1000000
//any platform-specific setup that must be done before sim starts here
void platform_init()
{
}
//cleanup int here;
void platform_terminate()
{
}
//returns a free-running 32 bit nanosecond counter which rolls over
uint32_t platform_ns()
{
static uint32_t gTimeBase = 0;
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC,&ts);
if (gTimeBase== 0){gTimeBase=ts.tv_nsec;}
return ts.tv_nsec-gTimeBase;
}
//sleep in microseconds
void platform_sleep(long microsec)
{
struct timespec ts={0};
while (microsec >= MS_PER_SEC){
ts.tv_sec++;
microsec-=MS_PER_SEC;
}
ts.tv_nsec = microsec*1000;
nanosleep(&ts,NULL);
}
#define SIM_ECHO_TERMINAL 1 //use this to make grbl_sim act like a serial terminal with local echo on.
//set terminal to allow kbhit detection
void enable_kbhit(int dir)
{
static struct termios oldt, newt;
if ( dir == 1 )
{
tcgetattr( STDIN_FILENO, &oldt);
newt = oldt;
newt.c_lflag &= ~( ICANON );
if (!SIM_ECHO_TERMINAL) {
newt.c_lflag &= ~( ECHO );
}
tcsetattr( STDIN_FILENO, TCSANOW, &newt);
}
else
tcsetattr( STDIN_FILENO, TCSANOW, &oldt);
}
//detect key pressed
int kbhit (void)
{
struct timeval tv={0};
fd_set rdfs={{0}};
int retval;
/* tv.tv_sec = 0; */
/* tv.tv_usec = 0; */
/* FD_ZERO(&rdfs); */
FD_SET (STDIN_FILENO, &rdfs);
select(STDIN_FILENO+1, &rdfs, NULL, NULL, &tv);
retval = FD_ISSET(STDIN_FILENO, &rdfs);
return retval;
}
plat_thread_t* platform_start_thread(plat_threadfunc_t threadfunc) {
plat_thread_t* th = malloc(sizeof(plat_thread_t));
if (pthread_create(&th->tid, NULL, threadfunc, &th->exit)){
free(th);
return NULL;
}
return th;
}
//ask thread to exit nicely, wait
void platform_stop_thread(plat_thread_t* th){
th->exit = 1;
pthread_join(th->tid,NULL);
}
//force-kill thread
void platform_kill_thread(plat_thread_t* th){
th->exit = 1;
pthread_cancel(th->tid);
}
//return char if one available.
uint8_t platform_poll_stdin() {
uint8_t char_in=0;
enable_kbhit(1);
if ( kbhit()) {
char_in = getchar();
}
enable_kbhit(0);
return char_in;
}

72
sim/platform_WINDOWS.c Normal file
View File

@ -0,0 +1,72 @@
#include <stdio.h>
#include <stdlib.h>
#include <conio.h>
#include <windows.h>
#include "platform.h"
#define NS_PER_SEC 1000000000
#define MICRO_PER_MILLI 1000
double ns_per_perfcount;
//any platform-specific setup that must be done before sim starts here
void platform_init()
{
__int64 counts_per_sec;
QueryPerformanceFrequency((LARGE_INTEGER*)&countsPerSec);
ns_per_perfcount = (float)NS_PER_SEC / counts_per_sec;
}
//cleanup int here;
void platform_terminate()
{
}
//returns a free-running 32 bit nanosecond counter which rolls over
uint32_t platform_ns()
{
static uint32_t gTimeBase = 0;
__int64 counts;
uint32_t ns;
QueryPerformanceCounter((LARGE_INTEGER*)&counts);
ns = (counts*ns_per_perfcount);
if (gTimeBase== 0){gTimeBase=ns;}
return ns-gTimeBase;
}
//sleep in microseconds
void platform_sleep(long microsec)
{
Sleep(microsec/MICRO_PER_MILLI);
}
//create a thread
plat_thread_t* platform_start_thread(plat_threadfunc_t threadfunc) {
plat_thread_t* th = malloc(sizeof(plat_thread_t));
th->tid = CreateThread(NULL,0,threadfunc,&th->exit,0);
if (!th->tid){
free(th);
return NULL;
}
return th;
}
//ask thread to exit nicely, wait
void platform_stop_thread(plat_thread_t* th){
th->exit = 1;
WaitForSingleObject(th->tid,INFINITE);
}
//force-kill thread
void platform_kill_thread(plat_thread_t* th){
th->exit = 1;
TerminateThread(th->tid);
}
//return char if one available.
uint8_t platform_poll_stdin() {
return _kbhit();
}

16
sim/platform_linux.h Normal file
View File

@ -0,0 +1,16 @@
#ifdef platform_h
#error "platform implementation already defined"
#else
#include <pthread.h>
typedef struct {
pthread_t tid;
int exit;
} plat_thread_t;
typedef void*(*plat_threadfunc_t)(void*);
#define PLAT_THREAD_FUNC(name,arg) void* name(void* arg)
#endif

16
sim/platform_windows.h Normal file
View File

@ -0,0 +1,16 @@
#ifdef platform_h
#error "platform implementation already defined"
#else
#include <windows.h>
typedef struct {
HANDLE tid;
int exit;
} plat_thread_t;
typedef DWORD WINAPI(*plat_threadfunc_t)(LPVOID);
#define PLAT_THREAD_FUNC(name,arg) DWORD WINAPI name(LPVOID arg)
#endif

View File

@ -1,2 +0,0 @@
#define protocol_execute_runtime orig_protocol_execute_runtime
//void __attribute__((weak)) protocol_execute_runtime(void);

1
sim/rename_main.h Normal file
View File

@ -0,0 +1 @@
#define main avr_main

View File

@ -26,28 +26,51 @@
#include "simulator.h"
#include <stdio.h>
void serial_write(uint8_t data) {
printBlock();
if(print_comment && data!='\n' && data!='\r') {
fprintf(block_out_file, "# ");
print_comment= 0;
}
if(data=='\n' || data=='\r')
print_comment= 1;
fprintf(block_out_file, "%c", data);
// Indicate the end of processing a command. See simulator.c for details
runtime_second_call= 0;
}
#include "kbhit.h"
//prototypes for overridden functions
uint8_t orig_serial_read();
//used to inject a sleep in grbl main loop,
// ensures hardware simulator gets some cycles in "parallel"
uint8_t serial_read() {
int c;
if((c = fgetc(stdin)) != EOF) {
serial_write(c);
return c;
}
return SERIAL_NO_DATA;
platform_sleep(0);
return orig_serial_read();
}
void simulate_write_interrupt(){
while (UCSR0B & (1<<UDRIE0)){
interrupt_SERIAL_UDRE();
grbl_out(UDR0);
}
}
void simulate_read_interrupt(){
uint8_t char_in = platform_poll_stdin();
if (char_in) {
UDR0 = char_in;
//EOF or CTRL-F to exit
if (UDR0 == EOF || UDR0 == 0xFF || UDR0 == 0x06 ) {
sim.exit = 1;
}
//debugging
if (UDR0 == '%') {
printf("%ld %f\n",sim.masterclock,(double)sim.sim_time);
}
interrupt_SERIAL_RX();
}
}
extern volatile uint8_t rx_buffer_head;
extern volatile uint8_t rx_buffer_tail;
void simulate_serial(){
simulate_write_interrupt();
uint8_t head = rx_buffer_head+1;
if (head==RX_BUFFER_SIZE) { head = 0; }
if (head!=rx_buffer_tail) {
simulate_read_interrupt();
}
}

2
sim/serial_hooks.h Normal file
View File

@ -0,0 +1,2 @@
#define serial_read orig_serial_read

2
sim/sim.sh Executable file
View File

@ -0,0 +1,2 @@
./grbl_sim.exe 0.01 <HelloWorld.nc >HelloWorld.dat 2> HelloWorldSteps.dat
gnuplot -persist gnuplot.plt

View File

@ -28,93 +28,146 @@
#include "../planner.h"
#include "../nuts_bolts.h"
#include "simulator.h"
// This variable is needed to determine if execute_runtime() is called in a loop
// waiting for the buffer to empty, as in plan_synchronize()
// it is reset in serial_write() because this is certainly called at the end of
// every command processing
int runtime_second_call= 0;
#include "avr/interrupt.h" //for registers and isr declarations.
#include "eeprom.h"
// Current time of the stepper simulation
double sim_time= 0.0;
// Next time the status of stepper values should be printed
double next_print_time= 0.0;
// Minimum time step for printing stepper values. Given by user via command line
double step_time= 0.0;
// global system variable structure for position etc.
system_t sys;
int block_position[]= {0,0,0};
uint8_t print_comment= 1;
uint8_t end_of_block= 1;
int block_position[]= {0,0,0}; //step count after most recently planned block
uint32_t block_number= 0;
// Output file handles set by main program
FILE *block_out_file;
FILE *step_out_file;
sim_vars_t sim={0};
// dummy port variables
uint8_t stepping_ddr;
uint8_t stepping_port;
uint8_t spindle_ddr;
uint8_t spindle_port;
uint8_t limit_ddr;
uint8_t limit_port;
uint8_t limit_int_reg;
uint8_t pinout_ddr;
uint8_t pinout_port;
uint8_t pinout_int_reg;
uint8_t coolant_flood_ddr;
uint8_t coolant_flood_port;
extern block_t block_buffer[BLOCK_BUFFER_SIZE]; // A ring buffer for motion instructions
extern uint8_t block_buffer_head; // Index of the next block to be pushed
extern uint8_t block_buffer_tail; // Index of the block to process now
//local prototypes
void print_steps(bool force);
// Stub of the timer interrupt function from stepper.c
void interrupt_TIMER2_COMPA_vect();
//setup
void init_simulator(float time_multiplier) {
// Call the stepper interrupt until one block is finished
void sim_stepper() {
//printf("sim_stepper()\n");
block_t *current_block= plan_get_current_block();
//register the interrupt handlers we actually use.
compa_vect[1] = interrupt_TIMER1_COMPA_vect;
ovf_vect[0] = interrupt_TIMER0_OVF_vect;
#ifdef STEP_PULSE_DELAY
compa_vect[0] = interrupt_TIMER0_COMPA_vect;
#endif
// If the block buffer is empty, call the stepper interrupt one last time
// to let it handle sys.cycle_start etc.
if(current_block==NULL) {
interrupt_TIMER2_COMPA_vect();
return;
}
sim.next_print_time = args.step_time;
sim.speedup = time_multiplier;
sim.baud_ticks = (int)((double)F_CPU*8/BAUD_RATE); //ticks per byte
}
while(current_block==plan_get_current_block()) {
sim_time+= get_step_time();
interrupt_TIMER2_COMPA_vect();
// Check to see if we should print some info
if(step_time>0.0) {
if(sim_time>=next_print_time) {
if(end_of_block) {
end_of_block= 0;
fprintf(step_out_file, "# block number %d\n", block_number);
}
fprintf(step_out_file, "%20.15f, %d, %d, %d\n", sim_time, sys.position[X_AXIS], sys.position[Y_AXIS], sys.position[Z_AXIS]);
// Make sure the simulation time doesn't get ahead of next_print_time
while(next_print_time<sim_time) next_print_time+= step_time;
}
}
//shutdown simulator - close open files
int shutdown_simulator(uint8_t exitflag) {
fclose(args.block_out_file);
print_steps(1);
fclose(args.step_out_file);
fclose(args.grbl_out_file);
eeprom_close();
return 1/(!exitflag); //force exception, since avr_main() has no returns.
}
}
// always print stepper values at the end of a block
if(step_time>0.0) {
fprintf(step_out_file, "%20.15f, %d, %d, %d\n", sim_time, sys.position[X_AXIS], sys.position[Y_AXIS], sys.position[Z_AXIS]);
end_of_block= 1;
block_number++;
void simulate_hardware(bool do_serial){
//do one tick
sim.masterclock++;
sim.sim_time = (float)sim.masterclock/F_CPU;
timer_interrupts();
if (do_serial) simulate_serial();
//TODO:
// check limit pins, call pinchange interrupt if enabled
// can ignore pinout int vect - hw start/hold not supported
}
//runs the hardware simulator at the desired rate until sim.exit is set
void sim_loop(){
uint64_t simulated_ticks=0;
uint32_t ns_prev = platform_ns();
uint64_t next_byte_tick = F_CPU; //wait 1 sec before reading IO.
while (!sim.exit || sys.state>2 ) { //don't quit until idle
if (sim.speedup) {
//calculate how many ticks to do.
uint32_t ns_now = platform_ns();
uint32_t ns_elapsed = (ns_now-ns_prev)*sim.speedup; //todo: try multipling nsnow
simulated_ticks += F_CPU/1e9*ns_elapsed;
ns_prev = ns_now;
}
else {
simulated_ticks++; //as fast as possible
}
while (sim.masterclock < simulated_ticks){
//only read serial port as fast as the baud rate allows
bool read_serial = (sim.masterclock >= next_byte_tick);
//do low level hardware
simulate_hardware(read_serial);
//print the steps.
//For further decoupling, could maintain own counter of STEP_PORT pulses,
// print that instead of sys.position.
print_steps(0);
if (read_serial){
next_byte_tick+=sim.baud_ticks;
//recent block can only change after input, so check here.
printBlock();
}
//TODO:
// set limit pins based on position,
// set probe pin when probing.
// if VARIABLE_SPINDLE, measure pwm pin to report speed?
}
platform_sleep(0); //yield
}
}
//show current position in steps
void print_steps(bool force)
{
static plan_block_t* printed_block = NULL;
plan_block_t* current_block = plan_get_current_block();
if (sim.next_print_time == 0.0) { return; } //no printing
if (current_block != printed_block ) {
//new block.
if (block_number) { //print values from the end of prev block
fprintf(args.step_out_file, "%20.15f %d, %d, %d\n", sim.sim_time, sys.position[X_AXIS], sys.position[Y_AXIS], sys.position[Z_AXIS]);
}
printed_block = current_block;
if (current_block == NULL) { return; }
// print header
fprintf(args.step_out_file, "# block number %d\n", block_number++);
}
//print at correct interval while executing block
else if ((current_block && sim.sim_time>=sim.next_print_time) || force ) {
fprintf(args.step_out_file, "%20.15f %d, %d, %d\n", sim.sim_time, sys.position[X_AXIS], sys.position[Y_AXIS], sys.position[Z_AXIS]);
fflush(args.step_out_file);
//make sure the simulation time doesn't get ahead of next_print_time
while (sim.next_print_time<=sim.sim_time) sim.next_print_time += args.step_time;
}
}
//Functions for peeking inside planner state:
plan_block_t *get_block_buffer();
uint8_t get_block_buffer_head();
uint8_t get_block_buffer_tail();
// Returns the index of the previous block in the ring buffer
uint8_t prev_block_index(uint8_t block_index)
{
@ -123,95 +176,49 @@ uint8_t prev_block_index(uint8_t block_index)
return(block_index);
}
block_t *get_block_buffer();
uint8_t get_block_buffer_head();
uint8_t get_block_buffer_tail();
block_t *plan_get_recent_block() {
plan_block_t *plan_get_recent_block() {
if (get_block_buffer_head() == get_block_buffer_tail()) { return(NULL); }
return(get_block_buffer()+prev_block_index(get_block_buffer_head()));
}
// Print information about the most recently inserted block
// but only once!
void printBlock() {
block_t *b;
static block_t *last_block;
//printf("printBlock()\n");
plan_block_t *b;
static plan_block_t *last_block;
b= plan_get_recent_block();
if(b!=last_block && b!=NULL) {
//fprintf(block_out_file,"%s\n", line);
//fprintf(block_out_file," block: ");
if(b->direction_bits & (1<<X_DIRECTION_BIT)) block_position[0]-= b->steps_x;
else block_position[0]+= b->steps_x;
fprintf(block_out_file,"%d, ", block_position[0]);
if(b->direction_bits & (1<<Y_DIRECTION_BIT)) block_position[1]-= b->steps_y;
else block_position[1]+= b->steps_y;
fprintf(block_out_file,"%d, ", block_position[1]);
if(b->direction_bits & (1<<Z_DIRECTION_BIT)) block_position[2]-= b->steps_z;
else block_position[2]+= b->steps_z;
fprintf(block_out_file,"%d, ", block_position[2]);
fprintf(block_out_file,"%f", b->entry_speed_sqr);
fprintf(block_out_file,"\n");
int i;
for (i=0;i<N_AXIS;i++){
if(b->direction_bits & get_direction_mask(i)) block_position[i]-= b->steps[i];
else block_position[i]+= b->steps[i];
fprintf(args.block_out_file,"%d, ", block_position[i]);
}
fprintf(args.block_out_file,"%f", b->entry_speed_sqr);
fprintf(args.block_out_file,"\n");
fflush(args.block_out_file); //TODO: needed?
last_block= b;
}
}
// The simulator assumes that grbl is fast enough to keep the buffer full.
// Thus, the stepper interrupt is only called when the buffer is full and then only to
// finish one block.
// Only when plan_synchronize() wait for the whole buffer to clear, the stepper interrupt
// to finish all pending moves.
void handle_buffer() {
// runtime_second_call is reset by serial_write() after every command.
// Only when execute_runtime() is called repeatedly by plan_synchronize()
// runtime_second_call will be incremented above 2
//printf("handle_buffer()\n");
if(plan_check_full_buffer() || runtime_second_call>2) {
sim_stepper(step_out_file);
} else {
runtime_second_call++;
//printer for grbl serial port output
void grbl_out(uint8_t data){
static uint8_t buf[128]={0};
static uint8_t len=0;
static bool continuation = 0;
buf[len++]=data;
if(data=='\n' || data=='\r' || len>=127) {
if (args.comment_char && !continuation){
fprintf(args.grbl_out_file,"%c ",args.comment_char);
}
buf[len]=0;
fprintf(args.grbl_out_file,"%s",buf);
continuation = (len>=128); //print comment on next line unless we are only printing to avoid buffer overflow)
len=0;
}
}
double get_step_time() {
/* code for the old stepper algorithm
uint16_t ceiling;
uint16_t prescaler;
uint32_t actual_cycles;
uint8_t invalid_prescaler= 0;
prescaler= ((TCCR1B>>CS10) & 0x07) - 1;
ceiling= OCR1A;
switch(prescaler) {
case 0:
actual_cycles= ceiling;
break;
case 1:
actual_cycles= ceiling * 8L;
break;
case 2:
actual_cycles = ceiling * 64L;
break;
case 3:
actual_cycles = ceiling * 256L;
break;
case 4:
actual_cycles = ceiling * 1024L;
break;
default:
invalid_prescaler= 1;
}
if(invalid_prescaler) return 12345.0;
else return (double)actual_cycles/F_CPU;*/
return (double)((ocr2a+1)*8)/(double)(F_CPU);
}

View File

@ -25,36 +25,56 @@
#include <stdio.h>
#include "../nuts_bolts.h"
#include "../system.h"
#include "platform.h"
// Output file handles
extern FILE *block_out_file;
extern FILE *step_out_file;
//simulation globals
typedef struct sim_vars {
uint64_t masterclock;
double sim_time; //current time of the simulation
uint8_t started; //don't start timers until first char recieved.
uint8_t exit;
float speedup;
int32_t baud_ticks;
double next_print_time;
// This variable is needed to determine if execute_runtime() is called in a loop
// waiting for the buffer to empty, as in plan_synchronize()
extern int runtime_second_call;
} sim_vars_t;
extern sim_vars_t sim;
typedef struct arg_vars {
// Output file handles
FILE *block_out_file;
FILE *step_out_file;
FILE *grbl_out_file;
// Minimum time step for printing stepper values. //Given by user via command line
double step_time;
//char to prefix comments; default '#'
uint8_t comment_char;
} arg_vars_t;
extern arg_vars_t args;
// Minimum time step for printing stepper values. Given by user via command line
extern double step_time;
// global system variable structure for position etc.
extern system_t sys;
extern uint8_t print_comment;
// setup avr simulation
void init_simulator(float time_multiplier);
//shutdown simulator - close open files
int shutdown_simulator(uint8_t exitflag);
//simulates the hardware until sim.exit is set.
void sim_loop();
// Call the stepper interrupt until one block is finished
void sim_stepper();
// Check if buffer is full or if plan_synchronize() wants to clear the buffer
void handle_buffer();
void simulate_serial();
// Print information about the most recently inserted block
void printBlock();
// Calculate the time between stepper interrupt calls from TCCR1B and OCR1A AVR registers
// which are set in config_step_timer in stepper.c
// This reconstructs the stepper-internal value of variable st.cycles_per_step_event
// The reconstruction is done to truely decouple the simulator from the actual grbl code
double get_step_time();
//printer for grbl serial port output
void grbl_out(uint8_t char_out);
#endif