1
0
mirror of https://github.com/bdring/Grbl_Esp32.git synced 2025-09-07 04:40:43 +02:00
This commit is contained in:
Jens Hauser
2020-08-13 17:14:54 +02:00
11 changed files with 567 additions and 192 deletions

48
.gitignore vendored
View File

@@ -314,51 +314,3 @@ paket-files/
# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc
# Cake - Uncomment if you are using it
# tools/**
# !tools/packages.config
# Tabs Studio
*.tss
# Telerik's JustMock configuration file
*.jmconfig
# BizTalk build output
*.btp.cs
*.btm.cs
*.odx.cs
*.xsd.cs
# OpenCover UI analysis results
OpenCover/
# Azure Stream Analytics local run output
ASALocalRun/
# MSBuild Binary and Structured Log
*.binlog
# NVidia Nsight GPU debugger configuration file
*.nvuser
# MFractors (Xamarin productivity tool) working folder
.mfractor/
# Local History for Visual Studio
.localhistory/
# BeatPulse healthcheck temp database
healthchecksdb
# Backup folder for Package Reference Convert tool in Visual Studio 2017
MigrationBackup/
# Ionide (cross platform F# VS Code tools) working folder
.ionide/
# Fody - auto-generated XML schema
FodyWeavers.xsd
.vs/slnx.sqlite
.vs/Grbl_Esp32_JH/v16/.suo

31
Grbl_Esp32.sln Normal file
View File

@@ -0,0 +1,31 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.29306.81
MinimumVisualStudioVersion = 10.0.40219.1
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Grbl_Esp32", "Grbl_Esp32.vcxproj", "{11C8A44F-A303-4885-B5AD-5B65F7FE41C0}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|x64 = Debug|x64
Debug|x86 = Debug|x86
Release|x64 = Release|x64
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{11C8A44F-A303-4885-B5AD-5B65F7FE41C0}.Debug|x64.ActiveCfg = Debug|x64
{11C8A44F-A303-4885-B5AD-5B65F7FE41C0}.Debug|x64.Build.0 = Debug|x64
{11C8A44F-A303-4885-B5AD-5B65F7FE41C0}.Debug|x86.ActiveCfg = Debug|Win32
{11C8A44F-A303-4885-B5AD-5B65F7FE41C0}.Debug|x86.Build.0 = Debug|Win32
{11C8A44F-A303-4885-B5AD-5B65F7FE41C0}.Release|x64.ActiveCfg = Release|x64
{11C8A44F-A303-4885-B5AD-5B65F7FE41C0}.Release|x64.Build.0 = Release|x64
{11C8A44F-A303-4885-B5AD-5B65F7FE41C0}.Release|x86.ActiveCfg = Release|Win32
{11C8A44F-A303-4885-B5AD-5B65F7FE41C0}.Release|x86.Build.0 = Release|Win32
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {EEC94F4B-059C-4596-94B8-1C4C9CE5E0DD}
EndGlobalSection
EndGlobal

View File

@@ -83,8 +83,8 @@
#define Y_LIMIT_PIN GPIO_NUM_32
#define Z_LIMIT_PIN GPIO_NUM_35
#define A_LIMIT_PIN GPIO_NUM_34
#define B_LIMIT_PIN GPIO_NUM_39
#define C_LIMIT_PIN GPIO_NUM_36
//#define B_LIMIT_PIN GPIO_NUM_39
//#define C_LIMIT_PIN GPIO_NUM_36
#define PROBE_PIN GPIO_NUM_25
@@ -117,13 +117,13 @@
#define COOLANT_FLOOD_PIN I2SO(27)
*/
/*
// RS485 In socket #3
#define SPINDLE_TYPE SPINDLE_TYPE_HUANYANG // only one spindle at a time
#define HUANYANG_TXD_PIN GPIO_NUM_26
#define HUANYANG_RTS_PIN GPIO_NUM_4
#define HUANYANG_RXD_PIN GPIO_NUM_16
*/
// === Default settings

View File

@@ -2,6 +2,8 @@
HuanyangSpindle.cpp
This is for a Huanyang VFD based spindle via RS485 Modbus.
Sorry for the lengthy comments, but finding the details on this
VFD was a PITA. I am just trying to help the next person.
Part of Grbl_ESP32
2020 - Bart Dring
@@ -21,6 +23,21 @@
VFDs are very dangerous. They have high voltages and are very powerful
Remove power before changing bits.
==============================================================================
If a user changes state or RPM level, the command to do that is sent. If
the command is not responded to a message is sent to serial that there was
a timeout. If the Grbl is in a critical state, an alarm will be generated and
the machine stopped.
If there are no commands to execute, various status items will be polled. If there
is no response, it will behave as described above. It will stop any running jobs with
an alarm.
===============================================================================
Protocol Details
VFD frequencies are in Hz. Multiply by 60 for RPM
before using spindle, VFD must be setup for RS485 and match your spindle
@@ -32,20 +49,65 @@
PD015 10 Deceleration time (Test to optimize)
PD023 1 Reverse run enabled
PD142 3.7 Max current Amps (0.8kw=3.7 1.5kw=7.0, 2.2kw=??)
PD143 2 Poles most are 2 (I think this is only used for RPM calc from Hz)
PD163 1 RS485 Address: 1 (Typical. OK to change...see below)
PD164 1 RS485 Baud rate: 9600 (Typical. OK to change...see below)
PD165 3 RS485 Mode: RTU, 8N1
Some references....
Manual: http://www.hy-electrical.com/bf/inverter.pdf
The official documentation of the RS485 is horrible. I had to piece it together from
a lot of different sources
Manuals: https://github.com/RobertOlechowski/Huanyang_VFD/tree/master/Documentations/pdf
Reference: https://github.com/Smoothieware/Smoothieware/blob/edge/src/modules/tools/spindle/HuanyangSpindleControl.cpp
Refernece: https://gist.github.com/Bouni/803492ed0aab3f944066
VFD settings: https://www.hobbytronics.co.za/Content/external/1159/Spindle_Settings.pdf
Spindle Talker 2 https://github.com/GilchristT/SpindleTalker2/releases
Python https://github.com/RobertOlechowski/Huanyang_VFD
=========================================================================
Commands
ADDR CMD LEN DATA CRC
0x01 0x03 0x01 0x01 0x31 0x88 Start spindle clockwise
0x01 0x03 0x01 0x08 0xF1 0x8E Stop spindle
0x01 0x03 0x01 0x11 0x30 0x44 Start spindle counter-clockwise
Return values are
0 = run
1 = jog
2 = r/f
3 = running
4 = jogging
5 = r/f
6 = Braking
7 = Track start
==========================================================================
Setting RPM
ADDR CMD LEN DATA CRC
0x01 0x05 0x02 0x09 0xC4 0xBF 0x0F Write Frequency (0x9C4 = 2500 = 25.00HZ)
Response is same as data sent
==========================================================================
Status registers
Addr Read Len Reg DataH DataL CRC CRC
0x01 0x04 0x03 0x00 0x00 0x00 CRC CRC // Set Frequency * 100 (25Hz = 2500)
0x01 0x04 0x03 0x01 0x00 0x00 CRC CRC // Ouput Frequency * 100
0x01 0x04 0x03 0x02 0x00 0x00 CRC CRC // Ouput Amps * 10
0x01 0x04 0x03 0x03 0x00 0x00 0xF0 0x4E // Read RPM (example CRC shown)
0x01 0x04 0x03 0x0 0x00 0x00 CRC CRC // DC voltage
0x01 0x04 0x03 0x05 0x00 0x00 CRC CRC // AC voltage
0x01 0x04 0x03 0x06 0x00 0x00 CRC CRC // Cont
0x01 0x04 0x03 0x07 0x00 0x00 CRC CRC // VFD Temp
Message is returned with requested value = (DataH * 16) + DataL (see decimal offset above)
TODO:
Move CRC Calc to task to free up main task
TODO
Returning errors to Grbl and handling them in Grbl.
What happens if the VFD does not respond
Add periodic pinging of VFD in run mode to see if it is still at correct RPM
*/
#include "SpindleClass.h"
@@ -54,8 +116,10 @@
#define HUANYANG_UART_PORT UART_NUM_2 // hard coded for this port right now
#define ECHO_TEST_CTS UART_PIN_NO_CHANGE // CTS pin is not used
#define HUANYANG_BUF_SIZE 127
#define HUANYANG_QUEUE_SIZE 10 // numv\ber of commands that can be queued up.
#define RESPONSE_WAIT_TICKS 50 // how long to wait for a response
#define HUANYANG_MAX_MSG_SIZE 16 // more than enough for a modbus message
#define HUANYANG_POLL_RATE 200 // in milliseconds betwwen commands
// OK to change these
// #define them in your machine definition file if you want different values
@@ -75,12 +139,27 @@ typedef struct {
char msg[HUANYANG_MAX_MSG_SIZE];
} hy_command_t;
typedef enum : uint8_t {
READ_SET_FREQ = 0, // The set frequency
READ_OUTPUT_FREQ = 1, // The current operating frequency
READ_OUTPUT_AMPS = 2, //
READ_SET_RPM = 3, // This is the last requested freq even in off mode
READ_DC_VOLTAGE = 4, //
READ_AC_VOLTAGE = 5, //
READ_CONT = 6, // counting value???
READ_TEMP = 7, //
} read_register_t;
QueueHandle_t hy_cmd_queue;
static TaskHandle_t vfd_cmdTaskHandle = 0;
bool hy_spindle_ok = true;
// The communications task
void vfd_cmd_task(void* pvParameters) {
static bool unresponsive = false; // to pop off a message once each time it becomes unresponsive
uint8_t reg_item = 0x00;
hy_command_t next_cmd;
uint8_t rx_message[HUANYANG_MAX_MSG_SIZE];
@@ -88,33 +167,55 @@ void vfd_cmd_task(void* pvParameters) {
if (xQueueReceive(hy_cmd_queue, &next_cmd, 0) == pdTRUE) {
uart_flush(HUANYANG_UART_PORT);
//report_hex_msg(next_cmd.msg, "Tx: ", next_cmd.tx_length);
uart_write_bytes(HUANYANG_UART_PORT, next_cmd.msg, next_cmd.tx_length);
uint16_t read_length = uart_read_bytes(HUANYANG_UART_PORT, rx_message, next_cmd.rx_length, RESPONSE_WAIT_TICKS);
if (read_length < next_cmd.rx_length) {
grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "Spindle RS485 Unresponsive");
if (next_cmd.critical)
grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "Critical Spindle RS485 Unresponsive");
// TODO Do something with this error
system_set_exec_alarm(EXEC_ALARM_SPINDLE_CONTROL);
if (!unresponsive) {
grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "Spindle RS485 Unresponsive %d", read_length);
if (next_cmd.critical) {
grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "Critical Spindle RS485 Unresponsive");
system_set_exec_alarm(EXEC_ALARM_SPINDLE_CONTROL);
}
unresponsive = true;
}
} else {
// success
unresponsive = false;
//report_hex_msg(rx_message, "Rx: ", read_length);
uint32_t ret_value = ((uint32_t)rx_message[4] << 8) + rx_message[5];
//grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "Item:%d value:%05d ", rx_message[3], ret_value);
}
} else {
// TODO: Should we ping the spindle here to make sure it does not go off line?
// But there is virtually no real documentation on how to do this.
HuanyangSpindle :: read_value(reg_item); // only this appears to work all the time. Other registers are flakey.
if (reg_item < 0x03)
reg_item++;
else
{
reg_item = 0x00;
}
}
vTaskDelay(100); // TODO: What is the best value here?
vTaskDelay(HUANYANG_POLL_RATE); // TODO: What is the best value here?
}
}
// ================== Class methods ==================================
void HuanyangSpindle :: init() {
hy_spindle_ok = true; // initialize
// fail if required items are not defined
if (!get_pins_and_settings()) {
grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "Huanyang spindle errors");
return;
}
if (! _task_running) { // init can happen many times, we only want to start one task
hy_cmd_queue = xQueueCreate(5, sizeof(hy_command_t));
hy_cmd_queue = xQueueCreate(HUANYANG_QUEUE_SIZE, sizeof(hy_command_t));
xTaskCreatePinnedToCore(vfd_cmd_task, // task
"vfd_cmdTaskHandle", // name for task
2048, // size of task stack
@@ -124,13 +225,7 @@ void HuanyangSpindle :: init() {
0 // core
);
_task_running = true;
}
// fail if required items are not defined
if (!get_pins_and_settings()) {
grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "Huanyang spindle errors");
return;
}
}
// this allows us to init() again later.
// If you change certain settings, init() gets called agian
@@ -176,30 +271,38 @@ void HuanyangSpindle :: init() {
// It returns a message for each missing pin
// Returns true if all pins are defined.
bool HuanyangSpindle :: get_pins_and_settings() {
bool pins_ok = true;
#ifdef HUANYANG_TXD_PIN
_txd_pin = HUANYANG_TXD_PIN;
#else
grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "Missing HUANYANG_TXD_PIN");
pins_ok = false;
grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "Undefined HUANYANG_TXD_PIN");
hy_spindle_ok = false;
#endif
#ifdef HUANYANG_RXD_PIN
_rxd_pin = HUANYANG_RXD_PIN;
#else
grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "No HUANYANG_RXD_PIN");
pins_ok = false;
grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "Undefined HUANYANG_RXD_PIN");
hy_spindle_ok = false;
#endif
#ifdef HUANYANG_RTS_PIN
_rts_pin = HUANYANG_RTS_PIN;
#else
grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "No HUANYANG_RTS_PIN");
pins_ok = false;
grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "Undefined HUANYANG_RTS_PIN");
hy_spindle_ok = false;
#endif
return pins_ok;
if (laser_mode->get()) {
grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "Huanyang spindle disabled in laser mode. Set $GCode/LaserMode=Off and restart");
hy_spindle_ok = false;
}
_min_rpm = rpm_min->get();
_max_rpm = rpm_max->get();
return hy_spindle_ok;
}
void HuanyangSpindle :: config_message() {
@@ -209,24 +312,15 @@ void HuanyangSpindle :: config_message() {
pinName(_txd_pin).c_str(),
pinName(_rxd_pin).c_str(),
pinName(_rts_pin).c_str());
}
/*
ADDR CMD LEN DATA CRC
0x01 0x03 0x01 0x01 0x31 0x88 Start spindle clockwise
0x01 0x03 0x01 0x08 0xF1 0x8E Stop spindle
0x01 0x03 0x01 0x11 0x30 0x44 Start spindle counter-clockwise
0x01 0x04 0x03 0x00 0x00 0x00 0xF0 0x4E Read Frequency
*/
void HuanyangSpindle :: set_state(uint8_t state, uint32_t rpm) {
if (sys.abort)
return; // Block during abort.
bool critical = (sys.state == STATE_CYCLE || state != SPINDLE_DISABLE);
if (_current_state != state) { // already at the desired state. This function gets called a lot.
set_mode(state, critical); // critical if we are in a job
set_rpm(rpm);
@@ -251,6 +345,9 @@ void HuanyangSpindle :: set_state(uint8_t state, uint32_t rpm) {
}
bool HuanyangSpindle :: set_mode(uint8_t mode, bool critical) {
if (!hy_spindle_ok) return false;
hy_command_t mode_cmd;
mode_cmd.tx_length = 6;
@@ -260,32 +357,47 @@ bool HuanyangSpindle :: set_mode(uint8_t mode, bool critical) {
mode_cmd.msg[1] = 0x03;
mode_cmd.msg[2] = 0x01;
if (mode == SPINDLE_ENABLE_CW)
if (mode == SPINDLE_ENABLE_CW)
mode_cmd.msg[3] = 0x01;
else if (mode == SPINDLE_ENABLE_CCW)
mode_cmd.msg[3] = 0x11;
else //SPINDLE_DISABLE
mode_cmd.msg[3] = 0x08;
else { //SPINDLE_DISABLE
mode_cmd.msg[3] = 0x08;
if (! xQueueReset(hy_cmd_queue)) {
grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "VFD spindle off, queue could not be reset");
}
}
add_ModRTU_CRC(mode_cmd.msg, mode_cmd.rx_length);
mode_cmd.critical = critical;
;
if (xQueueSend(hy_cmd_queue, &mode_cmd, 0) != pdTRUE)
grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "VFD Queue Full");
return true;
}
/*
ADDR CMD LEN DATA CRC
0x01 0x05 0x02 0x09 0xC4 0xBF 0x0F Write Frequency (0x9C4 = 2500 = 25.00HZ)
*/
uint32_t HuanyangSpindle :: set_rpm(uint32_t rpm) {
if (!hy_spindle_ok) return 0;
hy_command_t rpm_cmd;
// apply override
rpm = rpm * sys.spindle_speed_ovr / 100; // Scale by spindle speed override value (uint8_t percent)
// apply limits
if ((_min_rpm >= _max_rpm) || (rpm >= _max_rpm))
rpm = _max_rpm;
else if (rpm != 0 && rpm <= _min_rpm)
rpm = _min_rpm;
sys.spindle_speed = rpm;
if (rpm == _current_rpm) // prevent setting same RPM twice
return rpm;
return rpm;
_current_rpm = rpm;
@@ -305,7 +417,7 @@ uint32_t HuanyangSpindle :: set_rpm(uint32_t rpm) {
add_ModRTU_CRC(rpm_cmd.msg, rpm_cmd.tx_length);
rpm_cmd.critical = (sys.state == STATE_CYCLE);
rpm_cmd.critical = false;
if (xQueueSend(hy_cmd_queue, &rpm_cmd, 0) != pdTRUE)
grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "VFD Queue Full");
@@ -313,44 +425,34 @@ uint32_t HuanyangSpindle :: set_rpm(uint32_t rpm) {
return rpm;
}
// 0x01 0x04 0x03 0x00 0x00 0x00 0xF0 0x4E Read Frequency
// This appears to read the control register and will return an RPM running or not.
uint16_t HuanyangSpindle :: read_rpm() {
void HuanyangSpindle :: read_value(uint8_t reg) {
uint16_t ret_value = 0;
hy_command_t read_cmd;
uint8_t rx_message[HUANYANG_MAX_MSG_SIZE];
read_cmd.tx_length = 8;
read_cmd.rx_length = 6;
read_cmd.rx_length = 8;
read_cmd.msg[0] = HUANYANG_ADDR;
read_cmd.msg[1] = 0x04;
read_cmd.msg[2] = 0x03;
read_cmd.msg[3] = 0x00;
read_cmd.msg[3] = reg;
read_cmd.msg[4] = 0x00;
read_cmd.msg[5] = 0x00;
read_cmd.critical = (sys.state == STATE_CYCLE); // only critical if running a job TBD.... maybe spindle on?
add_ModRTU_CRC(read_cmd.msg, read_cmd.tx_length);
uart_flush(HUANYANG_UART_PORT);
uart_write_bytes(HUANYANG_UART_PORT, read_cmd.msg, read_cmd.tx_length);
uint16_t read_length = uart_read_bytes(HUANYANG_UART_PORT, rx_message, read_cmd.rx_length, RESPONSE_WAIT_TICKS);
if (read_length < read_cmd.rx_length)
grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "Read RPM Spindle RS485 Unresponsive");
else {
uint32_t hz = ((uint32_t)rx_message[4] << 8) + rx_message[5];
grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "speed %d", (hz * 60) / 100);
report_hex_msg(rx_message, "Rx:", read_length);
}
return 0;
if (xQueueSend(hy_cmd_queue, &read_cmd, 0) != pdTRUE)
grbl_msg_sendf(CLIENT_SERIAL, MSG_LEVEL_INFO, "VFD Queue Full");
}
void HuanyangSpindle ::stop() {
void HuanyangSpindle ::stop() {
set_mode(SPINDLE_DISABLE, false);
}

View File

@@ -173,8 +173,12 @@ class HuanyangSpindle : public Spindle {
uint8_t get_state();
uint32_t set_rpm(uint32_t rpm);
void stop();
static uint16_t read_rpm();
static void add_ModRTU_CRC(char* buf, int full_msg_len);
static void read_value(uint8_t reg);
static void add_ModRTU_CRC(char* buf, int full_msg_len);
protected:
uint32_t _min_rpm;
uint32_t _max_rpm;

View File

@@ -22,7 +22,7 @@
// Grbl versioning system
#define GRBL_VERSION "1.3a"
#define GRBL_VERSION_BUILD "20200725"
#define GRBL_VERSION_BUILD "20200727"
//#include <sdkconfig.h>

View File

@@ -39,21 +39,17 @@ typedef struct {
} client_line_t;
client_line_t client_lines[CLIENT_COUNT];
static void empty_line(uint8_t client)
{
static void empty_line(uint8_t client) {
client_line_t* cl = &client_lines[client];
cl->len = 0;
cl->buffer[0] = '\0';
}
static void empty_lines() {
for (uint8_t client = 0; client < CLIENT_COUNT; client++) {
for (uint8_t client = 0; client < CLIENT_COUNT; client++)
empty_line(client);
}
}
err_t add_char_to_line(char c, uint8_t client)
{
err_t add_char_to_line(char c, uint8_t client) {
client_line_t* cl = &client_lines[client];
// Simple editing for interactive input
if (c == '\b') {
@@ -64,9 +60,8 @@ err_t add_char_to_line(char c, uint8_t client)
}
return STATUS_OK;
}
if (cl->len == (LINE_BUFFER_SIZE - 1)) {
if (cl->len == (LINE_BUFFER_SIZE - 1))
return STATUS_OVERFLOW;
}
if (c == '\r' || c == '\n') {
cl->len = 0;
cl->line_number++;
@@ -77,21 +72,17 @@ err_t add_char_to_line(char c, uint8_t client)
return STATUS_OK;
}
err_t execute_line(char* line, uint8_t client, auth_t auth_level)
{
err_t execute_line(char* line, uint8_t client, auth_t auth_level) {
err_t result = STATUS_OK;
// Empty or comment line. For syncing purposes.
if (line[0] == 0) {
if (line[0] == 0)
return STATUS_OK;
}
// Grbl '$' or WebUI '[ESPxxx]' system command
if (line[0] == '$' || line[0] == '[') {
if (line[0] == '$' || line[0] == '[')
return system_execute_line(line, client, auth_level);
}
// Everything else is gcode. Block if in alarm or jog mode.
if (sys.state & (STATE_ALARM | STATE_JOG)) {
if (sys.state & (STATE_ALARM | STATE_JOG))
return STATUS_SYSTEM_GC_LOCK;
}
return gc_execute_line(line, client);
}
@@ -99,7 +90,7 @@ err_t execute_line(char* line, uint8_t client, auth_t auth_level)
GRBL PRIMARY LOOP:
*/
void protocol_main_loop() {
empty_client_buffers();
serial_reset_read_buffer(CLIENT_ALL);
empty_lines();
//uint8_t client = CLIENT_SERIAL; // default client
// Perform some machine checks to make sure everything is good to go.
@@ -156,25 +147,25 @@ void protocol_main_loop() {
while ((c = serial_read(client)) != SERIAL_NO_DATA) {
err_t res = add_char_to_line(c, client);
switch (res) {
case STATUS_OK:
break;
case STATUS_EOL:
protocol_execute_realtime(); // Runtime command check point.
if (sys.abort) {
return; // Bail to calling function upon system abort
}
line = client_lines[client].buffer;
case STATUS_OK:
break;
case STATUS_EOL:
protocol_execute_realtime(); // Runtime command check point.
if (sys.abort) {
return; // Bail to calling function upon system abort
}
line = client_lines[client].buffer;
#ifdef REPORT_ECHO_RAW_LINE_RECEIVED
report_echo_line_received(line, client);
report_echo_line_received(line, client);
#endif
// auth_level can be upgraded by supplying a password on the command line
report_status_message(execute_line(line, client, LEVEL_GUEST), client);
empty_line(client);
break;
case STATUS_OVERFLOW:
report_status_message(STATUS_OVERFLOW, client);
empty_line(client);
break;
// auth_level can be upgraded by supplying a password on the command line
report_status_message(execute_line(line, client, LEVEL_GUEST), client);
empty_line(client);
break;
case STATUS_OVERFLOW:
report_status_message(STATUS_OVERFLOW, client);
empty_line(client);
break;
}
} // while serial read
} // for clients
@@ -188,9 +179,8 @@ void protocol_main_loop() {
}
// check to see if we should disable the stepper drivers ... esp32 work around for disable in main loop.
if (stepper_idle) {
if (esp_timer_get_time() > stepper_idle_counter) {
if (esp_timer_get_time() > stepper_idle_counter)
motors_set_disable(true);
}
}
}
return; /* Never reached */
@@ -456,6 +446,9 @@ void protocol_exec_rt_system() {
bit_true(sys.step_control, STEP_CONTROL_UPDATE_SPINDLE_RPM);
sys.spindle_speed_ovr = last_s_override;
sys.report_ovr_counter = 0; // Set to report change immediately
// If spinlde is on, tell it the rpm has been overridden
if (gc_state.modal.spindle != SPINDLE_DISABLE)
spindle->set_rpm(gc_state.spindle_speed);
}
if (rt_exec & EXEC_SPINDLE_OVR_STOP) {
// Spindle stop override allowed only while in HOLD state.
@@ -564,13 +557,13 @@ static void protocol_exec_rt_suspend() {
// NOTE: State is will remain DOOR, until the de-energizing and retract is complete.
#ifdef ENABLE_PARKING_OVERRIDE_CONTROL
if (homing_enable->get() &&
(parking_target[PARKING_AXIS] < PARKING_TARGET) &&
laser_mode->get() &&
(sys.override_ctrl == OVERRIDE_PARKING_MOTION)) {
#else
if (homing_enable->get() &&
(parking_target[PARKING_AXIS] < PARKING_TARGET) &&
laser_mode->get()) {
!laser_mode->get() &&
(sys.override_ctrl == OVERRIDE_PARKING_MOTION)) {
#else
if (homing_enable->get() &&
(parking_target[PARKING_AXIS] < PARKING_TARGET) &&
!laser_mode->get()) {
#endif
// Retract spindle by pullout distance. Ensure retraction motion moves away from
// the workpiece and waypoint motion doesn't exceed the parking target location.
@@ -584,9 +577,9 @@ static void protocol_exec_rt_suspend() {
// NOTE: Clear accessory state after retract and after an aborted restore motion.
pl_data->condition = (PL_COND_FLAG_SYSTEM_MOTION | PL_COND_FLAG_NO_FEED_OVERRIDE);
pl_data->spindle_speed = 0.0;
spindle->set_state((SPINDLE_DISABLE, 0); // De-energize
coolant_set_state(COOLANT_DISABLE); // De-energize
// Execute fast parking retract motion to parking target location.
spindle->set_state(SPINDLE_DISABLE, 0); // De-energize
coolant_set_state(COOLANT_DISABLE); // De-energize
// Execute fast parking retract motion to parking target location.
if (parking_target[PARKING_AXIS] < PARKING_TARGET) {
parking_target[PARKING_AXIS] = PARKING_TARGET;
pl_data->feed_rate = PARKING_RATE;
@@ -595,9 +588,9 @@ static void protocol_exec_rt_suspend() {
} else {
// Parking motion not possible. Just disable the spindle and coolant.
// NOTE: Laser mode does not start a parking motion to ensure the laser stops immediately.
->set_state((SPINDLE_DISABLE, 0.0); // De-energize
coolant_set_state(COOLANT_DISABLE); // De-energize
}
spindle->set_state(SPINDLE_DISABLE, 0); // De-energize
coolant_set_state(COOLANT_DISABLE); // De-energize
}
#endif
sys.suspend &= ~(SUSPEND_RESTART_RETRACT);
sys.suspend |= SUSPEND_RETRACT_COMPLETE;
@@ -624,7 +617,7 @@ static void protocol_exec_rt_suspend() {
// NOTE: State is will remain DOOR, until the de-energizing and retract is complete.
#ifdef ENABLE_PARKING_OVERRIDE_CONTROL
if (homing_enable->get() && !laser_mode->get()) &&
(sys.override_ctrl == OVERRIDE_PARKING_MOTION)) {
(sys.override_ctrl == OVERRIDE_PARKING_MOTION)) {
#else
if (homing_enable->get() && !laser_mode->get()) {
#endif
@@ -638,8 +631,8 @@ static void protocol_exec_rt_suspend() {
#endif
// Delayed Tasks: Restart spindle and coolant, delay to power-up, then resume cycle.
if (gc_state.modal.spindle != SPINDLE_DISABLE) {
// Block if safety door re-opened during prior restore actions.
if (bit_isfalse(sys.suspend, SUSPEND_RESTART_RETRACT)) {
// Block if safety door re-opened during prior restore actions.
if (bit_isfalse(sys.suspend, SUSPEND_RESTART_RETRACT)) {
if (laser_mode->get()) {
// When in laser mode, ignore spindle spin-up delay. Set to turn on laser when cycle starts.
bit_true(sys.step_control, STEP_CONTROL_UPDATE_SPINDLE_RPM);
@@ -650,8 +643,8 @@ static void protocol_exec_rt_suspend() {
}
}
if (gc_state.modal.coolant != COOLANT_DISABLE) {
// Block if safety door re-opened during prior restore actions.
if (bit_isfalse(sys.suspend, SUSPEND_RESTART_RETRACT)) {
// Block if safety door re-opened during prior restore actions.
if (bit_isfalse(sys.suspend, SUSPEND_RESTART_RETRACT)) {
// NOTE: Laser mode will honor this delay. An exhaust system is often controlled by this pin.
coolant_set_state((restore_condition & (PL_COND_FLAG_COOLANT_FLOOD | PL_COND_FLAG_COOLANT_FLOOD)));
delay_sec(SAFETY_DOOR_COOLANT_DELAY, DELAY_MODE_SYS_SUSPEND);
@@ -661,7 +654,7 @@ static void protocol_exec_rt_suspend() {
// Execute slow plunge motion from pull-out position to resume position.
#ifdef ENABLE_PARKING_OVERRIDE_CONTROL
if (homing_enable->get() && !laser_mode->get()) &&
(sys.override_ctrl == OVERRIDE_PARKING_MOTION)) {
(sys.override_ctrl == OVERRIDE_PARKING_MOTION)) {
#else
if (homing_enable->get() && !laser_mode->get()) {
#endif
@@ -678,8 +671,8 @@ static void protocol_exec_rt_suspend() {
}
#endif
if (bit_isfalse(sys.suspend, SUSPEND_RESTART_RETRACT)) {
sys.suspend |= SUSPEND_RESTORE_COMPLETE;
system_set_exec_state_flag(EXEC_CYCLE_START); // Set to resume program.
sys.suspend |= SUSPEND_RESTORE_COMPLETE;
system_set_exec_state_flag(EXEC_CYCLE_START); // Set to resume program.
}
}
}

View File

@@ -63,12 +63,6 @@ static TaskHandle_t serialCheckTaskHandle = 0;
InputBuffer client_buffer[CLIENT_COUNT]; // create a buffer for each client
void empty_client_buffers() {
for (uint8_t i = 0; i < CLIENT_COUNT; i++) {
client_buffer[i].end();
}
}
// Returns the number of bytes available in a client buffer.
uint8_t serial_get_rx_buffer_available(uint8_t client) {
return client_buffer[client].availableforwrite();
@@ -160,7 +154,7 @@ void serialCheckTask(void* pvParameters) {
void serial_reset_read_buffer(uint8_t client) {
for (uint8_t client_num = 0; client_num < CLIENT_COUNT; client_num++) {
if (client == client_num || client == CLIENT_ALL)
client_buffer[client].begin();
client_buffer[client_num].begin();
}
}

View File

@@ -56,6 +56,4 @@ void execute_realtime_command(uint8_t command, uint8_t client);
bool any_client_has_data();
bool is_realtime_command(uint8_t data);
void empty_client_buffers();
#endif

26
VisualStudio.md Normal file
View File

@@ -0,0 +1,26 @@
# Getting started with GRBL_ESP32 in Visual Studio
**!! Important !! There's a huge difference between Visual Studio and
Visual Studio Code. This document is about Visual Studio, not Visual
Studio Code!**
First, get PlatformIO to work with Visual studio. The steps that
need to be taken for this are the following:
1. Install python. This is needed for both PlatformIO and for generating
the vcxproj file.
2. From https://docs.platformio.org/en/latest/core/index.html#piocore
you should install the PlatformIO Core (CLI). Make sure you update
the command line search path.
3. Use python to generate a vcxproj file: `python generate_vcxproj.py`.
4. Start Grbl_Esp32.sln
## Building
Building is as easy as building your solution.
## Uploading
Uploading can be done from the command line using platformio. For
example, run `platformio run --target upload --upload-port COM7`.
For more details, see [the documentation of pio](https://dokk.org/documentation/platformio/v3.6.1/platforms/espressif32/).

275
generate_vcxproj.py Normal file
View File

@@ -0,0 +1,275 @@
'''
Visual studio project file generator
Grbl_ESP32 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_ESP32. If not, see <http://www.gnu.org/licenses/>.
@authors: atlaste [github.com/atlaste]
'''
PATHS_TO_SEARCH = ['Grbl_Esp32']
HEADER_EXT = ['.h', '.inl']
SOURCE_EXT = ['.c', '.cpp']
OTHER_EXT = ['.ino', '.md']
import os, uuid
def UUID(name):
return str(uuid.uuid3(uuid.NAMESPACE_OID, name)).upper()
def FilterFromPath(path):
(head, tail) = os.path.split(path)
head = head.replace('/', '\\').replace('..\\', '').replace('.\\', '')
if head == '.':
return ''
h = head[0:10];
if h == 'Grbl_Esp32':
h = head[11:]
return h
class Vcxproj:
# configuration, platform
ConfigurationFmt = '\n'.join([
' <ProjectConfiguration Include="Grbl_Esp32\{0}|{1}">',
' <Configuration>{0}</Configuration>',
' <Platform>{1}</Platform>',
' </ProjectConfiguration>'])
# item name, item file
ItemFmt = '\n'.join([
' <{0} Include="{1}" />'])
# configuration, platform
ConfigTypePropertyGroupFmt = '\n'.join([
' <PropertyGroup Condition="\'$(Configuration)|$(Platform)\'==\'{0}|{1}\'" Label="Configuration">',
' <ConfigurationType>Makefile</ConfigurationType>',
' <UseDebugLibraries>true</UseDebugLibraries>',
' <PlatformToolset>v142</PlatformToolset>',
' </PropertyGroup>'])
# configuration, platform
ImportGroupFmt = '\n'.join([
' <ImportGroup Label="PropertySheets" Condition="\'$(Configuration)|$(Platform)\'==\'{0}|{1}\'">',
' <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists(\'$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props\')" Label="LocalAppDataPlatform" />',
' </ImportGroup>'
])
# configuration, platform
PIOPropertyGroupFmt = '\n'.join([
' <PropertyGroup Condition="\'$(Configuration)|$(Platform)\'==\'{0}|{1}\'">',
' <NMakeBuildCommandLine>platformio --force run</NMakeBuildCommandLine>',
' <NMakeCleanCommandLine>platformio --force run -t clean</NMakeCleanCommandLine>',
' <NMakePreprocessorDefinitions>WIN32;_DEBUG;$(NMakePreprocessorDefinitions)</NMakePreprocessorDefinitions>',
' <NMakeIncludeSearchPath>$(HOMEDRIVE)$(HOMEPATH)\\.platformio\\packages\\toolchain-xtensa32\\xtensa-esp32-elf\\include;$(HOMEDRIVE)$(HOMEPATH)\\.platformio\\packages\\framework-arduinoespressif32\\cores\\esp32;$(NMakeIncludeSearchPath)</NMakeIncludeSearchPath>',
' </PropertyGroup>'
])
@staticmethod
def Configuration(configuration, platform):
return Vcxproj.ConfigurationFmt.format(configuration, platform)
@staticmethod
def Item(name, file):
return Vcxproj.ItemFmt.format(name, file)
@staticmethod
def ConfigTypePropertyGroup(configuration, platform):
return Vcxproj.ConfigTypePropertyGroupFmt.format(configuration, platform)
@staticmethod
def ImportGroup(configuration, platform):
return Vcxproj.ImportGroupFmt.format(configuration, platform)
@staticmethod
def PIOPropertyGroup(configuration, platform):
return Vcxproj.PIOPropertyGroupFmt.format(configuration, platform)
class Filters:
# itemtype, path, folder
ItemFmt = '\n'.join([
' <{0} Include="{1}">',
' <Filter>{2}</Filter>',
' </{0}>'])
# folder, uuid
FilterFmt = '\n'.join([
' <Filter Include="{0}">',
' <UniqueIdentifier>{{{1}}}</UniqueIdentifier>',
' </Filter>'])
@staticmethod
def Item(itemtype, path):
folder = FilterFromPath(path)
return Filters.ItemFmt.format(itemtype, path, folder)
@staticmethod
def Filter(folder):
uid = UUID(folder)
return Filters.FilterFmt.format(folder, uid)
class Generator:
Folders = set()
# Files
Headers = set()
Sources = set()
Others = set()
# Stuffs
Platforms = set(['Win32','x64'])
Configurations = set(['Debug','Release'])
Name = 'Grbl_Esp32'
def AddFolder(self, path):
filt = FilterFromPath(path)
if filt == '':
return
if filt not in self.Folders:
self.Folders.add(filt)
filters = ''
for f in os.path.split(filt):
filters = os.path.join(filters, f)
if filters != '':
self.Folders.add(filters)
def AddFile(self, path):
(root, ext) = os.path.splitext(path)
if ext in HEADER_EXT:
self.Headers.add(path)
elif ext in SOURCE_EXT:
self.Sources.add(path)
elif ext in OTHER_EXT:
self.Others.add(path)
else:
return
self.AddFolder(path)
def Walk(self, path):
if path == 'Grbl_Esp32\\Custom' or path == 'Grbl_Esp32/Custom':
return
if os.path.isfile(path):
self.AddFile(path)
else:
for subPath in os.listdir(path):
self.Walk(os.path.join(path, subPath))
def CreateProject(self):
project = []
project.append('<?xml version="1.0" encoding="utf-8"?>')
project.append('<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">')
project.append('<ItemGroup Label="ProjectConfigurations">')
for p in self.Platforms:
for c in self.Configurations:
project.append(Vcxproj.Configuration(c, p))
project.append('</ItemGroup>')
# cpp header files
project.append('<ItemGroup>')
for f in self.Headers:
project.append(Vcxproj.Item('ClInclude', f))
project.append('</ItemGroup>')
# cpp source files
project.append('<ItemGroup>')
for f in self.Sources:
project.append(Vcxproj.Item('ClCompile', f))
project.append('</ItemGroup>')
# md files and ino file
project.append('<ItemGroup>')
for f in self.Others:
project.append(Vcxproj.Item('None', f))
project.append('</ItemGroup>')
# Bookkeeping, compilation, etc.
project.append('<PropertyGroup Label="Globals">')
project.append(' <VCProjectVersion>16.0</VCProjectVersion>')
project.append(' <ProjectGuid>{11C8A44F-A303-4885-B5AD-5B65F7FE41C0}</ProjectGuid>')
project.append(' <Keyword>Win32Proj</Keyword>')
project.append('</PropertyGroup>')
project.append('<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />')
for p in self.Platforms:
for c in self.Configurations:
project.append(Vcxproj.ConfigTypePropertyGroup(c, p))
project.append('<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />')
project.append('<ImportGroup Label="ExtensionSettings">')
project.append('</ImportGroup>')
project.append(' <ImportGroup Label="Shared">')
project.append('</ImportGroup>')
for p in self.Platforms:
for c in self.Configurations:
project.append(Vcxproj.ImportGroup(c, p))
project.append('<PropertyGroup Label="UserMacros" />')
for p in self.Platforms:
for c in self.Configurations:
project.append(Vcxproj.PIOPropertyGroup(c, p))
project.append('<ItemDefinitionGroup>')
project.append('</ItemDefinitionGroup>')
project.append('<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />')
project.append(' <ImportGroup Label="ExtensionTargets">')
project.append('</ImportGroup>')
project.append('<ProjectExtensions>')
project.append(' <VisualStudio>')
project.append(' <UserProperties config.Debug.customdebug_esp32_esp32_debugger_type="universal" />')
project.append(' </VisualStudio>')
project.append(' </ProjectExtensions>')
project.append('</Project>')
return '\n'.join(project)
def CreateFilters(self):
project = []
project.append('<?xml version="1.0" encoding="utf-8"?>')
project.append('<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">')
project.append('<ItemGroup>')
for f in self.Folders:
project.append(Filters.Filter(f))
project.append('</ItemGroup>')
project.append('<ItemGroup>')
for f in self.Headers:
project.append(Filters.Item('ClInclude', f))
project.append('</ItemGroup>')
project.append('<ItemGroup>')
for f in self.Sources:
project.append(Filters.Item('ClCompile', f))
project.append('</ItemGroup>')
project.append('<ItemGroup>')
for f in self.Others:
project.append(Filters.Item('None', f))
project.append('</ItemGroup>')
project.append('</Project>')
return '\n'.join(project)
def Generate(self):
f = open(self.Name + '.vcxproj', 'w')
f.write(self.CreateProject())
f.close()
f = open(self.Name + '.vcxproj.filters', 'w')
f.write(self.CreateFilters())
f.close()
def main(paths):
generator = Generator()
for path in paths:
generator.Walk(path)
generator.Generate()
main(PATHS_TO_SEARCH)