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

2
.gitignore vendored
View File

@ -3,3 +3,5 @@
*.elf
*.DS_Store
*.d
*~
\#*\#

View File

@ -84,7 +84,7 @@
// parser state depending on user preferences.
#define N_STARTUP_LINE 2 // Integer (1-3)
// Allows GRBL to tranck and report gcode line numbers. Enabling this means that the planning buffer
// Allows GRBL to track and report gcode line numbers. Enabling this means that the planning buffer
// goes from 18 or 16 to make room for the additional line number data in the plan_block_t struct
// #define USE_LINE_NUMBERS // Disabled by default. Uncomment to enable.

View File

@ -47,6 +47,7 @@ void coolant_run(uint8_t mode)
{
if (sys.state == STATE_CHECK_MODE) { return; }
protocol_auto_cycle_start();
protocol_buffer_synchronize(); // Ensure coolant turns on when specified in program.
if (mode == COOLANT_FLOOD_ENABLE) {
COOLANT_FLOOD_PORT |= (1 << COOLANT_FLOOD_BIT);

View File

@ -178,6 +178,7 @@ ISR(SERIAL_RX)
#endif
}
//TODO: some sort of alarm on overflow?
}
}

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 $@
../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,12 +1,12 @@
/*
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.
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) 2012 Jens Geisler
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
@ -22,17 +22,18 @@
along with Grbl. If not, see <http://www.gnu.org/licenses/>.
*/
#include "simulator.h"
//if linux
#include <stdio.h>
#include <termios.h>
#include <unistd.h>
void orig_protocol_execute_runtime(void);
int kbhit(void);
void enable_kbhit(int);
//else
//#include <conio.h>
//#define enable_kbhit(e)
//#define kbhit _kbhit
//endif
// 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();
}

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);
//( Files are now closed cleanly when sim gets EOF or CTRL-F.)
platform_init();
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
init_simulator(tick_rate);
// Main loop of command processing until EOF is encountered
//printf("protocol_process():\n");
protocol_process();
//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);
}
// flush the block buffer and print stepper info on any pending blocks
plan_synchronize();
//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;
platform_sleep(0);
return orig_serial_read();
}
return SERIAL_NO_DATA;
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);
//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.
}
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;
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]);
}
// 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++;
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"
//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;
} sim_vars_t;
extern sim_vars_t sim;
typedef struct arg_vars {
// Output file handles
extern FILE *block_out_file;
extern FILE *step_out_file;
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;
// 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;
} 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

View File

@ -60,6 +60,7 @@ void spindle_run(uint8_t direction, float rpm)
if (sys.state == STATE_CHECK_MODE) { return; }
// Empty planner buffer to ensure spindle is set when programmed.
protocol_auto_cycle_start();
protocol_buffer_synchronize();
// Halt or set spindle direction and rpm.

View File

@ -76,6 +76,9 @@ typedef struct {
#else
uint8_t prescaler; // Without AMASS, a prescaler is required to adjust for slow timing.
#endif
#ifdef AUTO_REPORT_MOVE_DONE
uint8_t block_end; //true for last segment of a block
#endif
} segment_t;
static segment_t segment_buffer[SEGMENT_BUFFER_SIZE];
@ -242,7 +245,7 @@ void st_go_idle()
is usually not a physical problem at higher frequencies, although audible.
To improve Bresenham multi-axis performance, Grbl uses what we call an Adaptive Multi-Axis
Step Smoothing (AMASS) algorithm, which does what the name implies. At lower step frequencies,
AMASS artificially increases the Bresenham resolution without effecting the algorithm's
AMASS artificially increases the Bresenham resolution without affecting the algorithm's
innate exactness. AMASS adapts its resolution levels automatically depending on the step
frequency to be executed, meaning that for even lower step frequencies the step smoothing
level increases. Algorithmically, AMASS is acheived by a simple bit-shifting of the Bresenham
@ -281,7 +284,7 @@ void st_go_idle()
// with probing and homing cycles that require true real-time positions.
ISR(TIMER1_COMPA_vect)
{
// SPINDLE_ENABLE_PORT ^= 1<<SPINDLE_ENABLE_BIT; // Debug: Used to time ISR
// TIMING_ENABLE_PORT ^= 1<<TIMING_ENABLE_BIT; // Debug: Used to time ISR
if (busy) { return; } // The busy-flag is used to avoid reentering this interrupt
// Set the direction pins a couple of nanoseconds before we step the steppers
@ -395,6 +398,9 @@ ISR(TIMER1_COMPA_vect)
st.step_count--; // Decrement step events count
if (st.step_count == 0) {
// Segment is complete. Discard current segment and advance segment indexing.
#ifdef AUTO_REPORT_MOVE_DONE
sys.execute |= st.exec_segment->block_end;
#endif
st.exec_segment = NULL;
if ( ++segment_buffer_tail == SEGMENT_BUFFER_SIZE) { segment_buffer_tail = 0; }
}
@ -793,8 +799,14 @@ void st_prep_buffer()
// Normal operation. Block incomplete. Distance remaining in block to be executed.
pl_block->millimeters = mm_remaining;
prep.steps_remaining = steps_remaining;
#ifdef AUTO_REPORT_MOVE_DONE
prep_segment->block_end = 0;
#endif
} else {
// End of planner block or forced-termination. No more distance to be executed.
#ifdef AUTO_REPORT_MOVE_DONE
prep_segment->block_end = EXEC_STATUS_REPORT; // force move-done report
#endif
if (mm_remaining > 0.0) { // At end of forced-termination.
// Reset prep parameters for resuming and then bail.
// NOTE: Currently only feed holds qualify for this scenario. May change with overrides.