Merge grbl-sim updates for v0.9.
- Removed some conflicting code in the main Grbl firmware source. - Temporary patch for coolant and spindle control with streaming applied.
This commit is contained in:
commit
21850ce985
@ -46,7 +46,8 @@ void coolant_stop()
|
||||
void coolant_run(uint8_t mode)
|
||||
{
|
||||
if (sys.state == STATE_CHECK_MODE) { return; }
|
||||
|
||||
|
||||
protocol_auto_cycle_start(); //temp fix for M8 lockup
|
||||
protocol_buffer_synchronize(); // Ensure coolant turns on when specified in program.
|
||||
if (mode == COOLANT_FLOOD_ENABLE) {
|
||||
COOLANT_FLOOD_PORT |= (1 << COOLANT_FLOOD_BIT);
|
||||
|
1
sim/.gitignore
vendored
1
sim/.gitignore
vendored
@ -1 +1,2 @@
|
||||
grbl_sim.exe
|
||||
*.dat
|
||||
|
22
sim/Makefile
22
sim/Makefile
@ -15,11 +15,14 @@
|
||||
# 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 = WINDOWS
|
||||
|
||||
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 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
|
||||
WINDOWS_LIBRARIES =
|
||||
# symbolic targets:
|
||||
all: main
|
||||
|
||||
@ -30,15 +33,18 @@ clean:
|
||||
|
||||
# file targets:
|
||||
main: $(OBJECTS)
|
||||
$(COMPILE) -o $(EXE_NAME) $(OBJECTS) -lm
|
||||
$(COMPILE) -o $(EXE_NAME) $(OBJECTS) -lm $($(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 $@
|
||||
|
||||
|
||||
|
@ -9,3 +9,10 @@ What can you do with Grbl Sim?
|
||||
- Visualize a g-code program by having the simulator parse and execute to a GUI. Fluctuations in feed rates by the acceleration planner can be viewed as well.
|
||||
- A powerful debugging tool for development.
|
||||
- Each of the AVR functions are replaced with dummy functions, like the stepper ISR. These could be written to whatever you need. For example, output simulated step pulses over time and examine its performance.
|
||||
|
||||
Realtime modifications by Adam Shelly:
|
||||
|
||||
Simulates Atmel hardware in separate thread. Runs in aproximate realtime.
|
||||
|
||||
On Linux, use `socat PTY,raw,link=/dev/ttyFAKE,echo=0 "EXEC:'./grbl_sim.exe -n -s step.out -b block.out',pty,raw,echo=0"` to create a fake serial port connected to the simulator. This is useful for testing grbl interface software.
|
||||
|
||||
|
@ -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() {};
|
@ -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
4
sim/avr/io.c
Normal file
@ -0,0 +1,4 @@
|
||||
#include "io.h"
|
||||
|
||||
// dummy register variables
|
||||
volatile io_sim_t io={{0}};
|
182
sim/avr/io.h
182
sim/avr/io.h
@ -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
1
sim/avr/wdt.h
Normal file
@ -0,0 +1 @@
|
||||
uint16_t wdt;
|
91
sim/config.h
91
sim/config.h
@ -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
|
||||
|
61
sim/eeprom.c
61
sim/eeprom.c
@ -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
3
sim/eeprom.h
Normal file
@ -0,0 +1,3 @@
|
||||
|
||||
#include "../eeprom.h"
|
||||
void eeprom_close();
|
148
sim/main.c
148
sim/main.c
@ -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);
|
||||
}
|
||||
|
@ -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
23
sim/platform.h
Normal 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
116
sim/platform_LINUX.c
Normal 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;
|
||||
}
|
75
sim/platform_WINDOWS.c
Normal file
75
sim/platform_WINDOWS.c
Normal file
@ -0,0 +1,75 @@
|
||||
#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*)&counts_per_sec);
|
||||
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,NULL);
|
||||
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, 0);
|
||||
}
|
||||
|
||||
//return char if one available.
|
||||
uint8_t platform_poll_stdin() {
|
||||
if (_kbhit()) {
|
||||
return getch();
|
||||
}
|
||||
return 0;
|
||||
}
|
16
sim/platform_linux.h
Normal file
16
sim/platform_linux.h
Normal 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
16
sim/platform_windows.h
Normal 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
|
@ -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
1
sim/rename_main.h
Normal file
@ -0,0 +1 @@
|
||||
#define main avr_main
|
@ -1,38 +0,0 @@
|
||||
/*
|
||||
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();
|
||||
}
|
63
sim/serial.c
63
sim/serial.c
@ -26,28 +26,49 @@
|
||||
#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;
|
||||
}
|
||||
|
||||
//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
2
sim/serial_hooks.h
Normal file
@ -0,0 +1,2 @@
|
||||
#define serial_read orig_serial_read
|
||||
|
2
sim/sim.sh
Executable file
2
sim/sim.sh
Executable file
@ -0,0 +1,2 @@
|
||||
./grbl_sim.exe 0.01 <HelloWorld.nc >HelloWorld.dat 2> HelloWorldSteps.dat
|
||||
gnuplot -persist gnuplot.plt
|
297
sim/simulator.c
297
sim/simulator.c
@ -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);
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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(); //temp fix for M3 lockup
|
||||
protocol_buffer_synchronize();
|
||||
|
||||
// Halt or set spindle direction and rpm.
|
||||
|
Loading…
Reference in New Issue
Block a user