-
Notifications
You must be signed in to change notification settings - Fork 90
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Custom HMI Communication with grblHAL #186
Comments
Why? The current protocol cannot be used?
There are a number of example plugins that hooks into the core in order to provide custom functionality, take a look at them. |
In #include "main.h"
#include "driver.h"
#include "grbl/grbllib.h"
#include "Modbus.h"
static void SystemClock_Config (void);
static void MX_GPIO_Init (void);
/* EDITED */
UART_HandleTypeDef huart2;
/* USER CODE BEGIN PV */
uint8_t RxData[256];
uint8_t TxData[256];
extern uint16_t Holding_Registers_Database[50];
extern Coils_Database[25];
/* USER CODE END PV */
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_USART2_UART_Init(void);
void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart,uint16_t Size){
if (RxData[0]==SLAVE_ID){
switch (RxData[1]){
case 0x01:
readCoils ();
break;
case 0x03:
readHoldingRegs();
break;
case 0x04:
readHoldingRegs();
break;
case 0x05:
writeSingleCoil();
break;
case 0x06:
writeSingleReg();
break;
case 15:
writeMultiCoils();
break;
case 16:
writeHoldingRegs();
break;
default:
break;
}
}
HAL_UARTEx_ReceiveToIdle_IT(&huart2, RxData, 256);
}
int main (void)
{
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* Configure the system clock */
SystemClock_Config();
/* Initialize all configured peripherals */
MX_GPIO_Init();
HAL_UARTEx_ReceiveToIdle_IT(&huart2, RxData, 256);
if(!(CoreDebug->DEMCR & CoreDebug_DEMCR_TRCENA_Msk)) {
CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk;
DWT->CYCCNT = 0;
DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk;
}
grbl_enter();
} In #include "driver.h"
static on_report_options_ptr on_report_options;
static on_execute_realtime_ptr on_execute_realtime;
// Define the register address and values
#define REGISTER_ADDRESS 0x3
#define START_COMMAND 0x05
// Define the base address and offset for the register
#define PERIPHERAL_BASE_ADDRESS 0x40000000 // FIX_ME
#define REGISTER_OFFSET 0x00000010 // FIX_ME
// Function to read register value
static uint8_t read_register(uint8_t reg_address) {
volatile uint8_t* register_address;
// Calculate the register address
register_address = (volatile uint8_t*)(PERIPHERAL_BASE_ADDRESS + REGISTER_OFFSET + reg_address);
// Read and return the value from the register
return *register_address;
}
// Function to send a sequence of G-code commands
static void send_gcode_sequence(void) {
hal.stream.write("G1 X10 Y10 F1000\n"); // Example G-code command
hal.stream.write("G2 X20 Y20 I10 J10\n"); // Another example G-code command
}
// Check the register value and send G-code commands if needed
static void check_register_and_execute_gcode(sys_state_t state) {
// Check the register value
uint8_t reg_value = read_register(REGISTER_ADDRESS);
if (reg_value == START_COMMAND) {
send_gcode_sequence();
}
// Continue with any other real-time execution tasks
on_execute_realtime(state);
}
// Add info about our plugin to the $I report.
static void on_report_my_options(bool newopt) {
if (on_report_options) {
on_report_options(newopt);
}
if (!newopt) {
hal.stream.write("[PLUGIN:Register Monitor v1.00]" ASCII_EOL);
}
}
void my_plugin_init(void) {
// Add info about our plugin to the $I report.
on_report_options = grbl.on_report_options;
grbl.on_report_options = on_report_my_options;
// Add check register and execute G-code function to grblHAL foreground process
on_execute_realtime = grbl.on_execute_realtime;
grbl.on_execute_realtime = check_register_and_execute_gcode;
} I haven’t tested it yet, but it built successfully. I’m not sure if it’s correct. I am trying to implement a while loop to check the register for a signal to start drawing based on HMI commands that change the STM32 register value. This |
Better to move your code in main.c to my_plugin.c.
Not really, FYI |
I wonder if I can block the default UART2 connection of GRBL defined in I have a question. I tried this and it didn’t work. What is the purpose of using #define SERIAL_PORT 1 // 2 // GPIOA: TX = 2, RX = 3
#define SERIAL1_PORT 2 // GPIOA: TX = 2, RX = 3 When debugging the variable |
This is my latest modified #include "driver.h"
#include "Modbus.h"
#include <string.h>
#include <stdio.h>
static on_report_options_ptr on_report_options;
static on_execute_realtime_ptr on_execute_realtime;
// UART variables
UART_HandleTypeDef huart2;
uint8_t RxData[256];
uint8_t TxData[256];
extern uint16_t Holding_Registers_Database[50];
extern uint8_t Coils_Database[25];
// Buffer for storing G-code commands
#define GCODE_BUFFER_SIZE 256
static char gcode_buffer[GCODE_BUFFER_SIZE];
static uint16_t gcode_buffer_index = 0;
static uint16_t gcode_buffer_length = 0;
// G-code file strings
const char* gcode_file_1 =
"G21 ; Set units to millimeters\n"
"G90 ; Absolute positioning\n"
"G1 F1500 ; Set feed rate\n"
"G1 X10 Y10 ; Move to position\n"
"G1 X20 Y10 ; Draw line\n"
"G1 X20 Y20 ; Draw line\n"
"G1 X10 Y20 ; Draw line\n"
"G1 X10 Y10 ; Draw line\n"
"M2 ; End of program\n";
const char* gcode_file_2 =
"G21 ; Set units to millimeters\n"
"G90 ; Absolute positioning\n"
"G1 F1500 ; Set feed rate\n"
"G1 X10 Y10 ; Move to position\n"
"G2 X20 Y20 I10 J0 ; Draw clockwise arc\n"
"G2 X10 Y10 I-10 J0 ; Complete the circle\n"
"M2 ; End of program\n";
const char* gcode_file_3 =
"G21 ; Set units to millimeters\n"
"G90 ; Absolute positioning\n"
"G1 F1500 ; Set feed rate\n"
"G1 X10 Y10 ; Move to position\n"
"G1 X20 Y10 ; Draw line\n"
"G1 X10 Y20 ; Draw line\n"
"G1 X20 Y20 ; Draw line\n"
"G1 X10 Y30 ; Draw line\n"
"G1 X20 Y30 ; Draw line\n"
"M2 ; End of program\n";
// Function prototypes
static void send_gcode_sequence(int selected_index);
static int16_t get_macro_char(void);
void SystemClock_Config(void);
void MX_GPIO_Init(void);
void MX_USART2_UART_Init(void);
// UART callback function
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) {
if (huart->Instance == USART2) {
if (RxData[0] == SLAVE_ID) {
switch (RxData[1]) {
case 0x01:
readCoils();
break;
case 0x03:
readHoldingRegs();
break;
case 0x04:
readHoldingRegs();
break;
case 0x05:
writeSingleCoil();
break;
case 0x06:
writeSingleReg();
break;
case 15:
writeMultiCoils();
break;
case 16:
writeHoldingRegs();
break;
default:
break;
}
}
HAL_UART_Receive_IT(&huart2, RxData, 256);
}
}
// Function to send a sequence of G-code commands to the buffer
static void send_gcode_sequence(int selected_index) {
const char* selected_gcode;
// Select the appropriate G-code file
switch (selected_index) {
case 0:
selected_gcode = gcode_file_1;
break;
case 1:
selected_gcode = gcode_file_2;
break;
case 2:
selected_gcode = gcode_file_3;
break;
default:
return; // Invalid index, do nothing
}
// Copy the selected G-code into the buffer
strncpy(gcode_buffer, selected_gcode, GCODE_BUFFER_SIZE - 1);
gcode_buffer[GCODE_BUFFER_SIZE - 1] = '\0'; // Ensure null-termination
gcode_buffer_length = strlen(gcode_buffer);
gcode_buffer_index = 0;
// Redirect hal.stream.read to our buffer reader function
hal.stream.read = get_macro_char;
hal.stream.file = NULL; // Input stream is not file based
}
// Function to read a character from the G-code buffer
static int16_t get_macro_char(void) {
if (gcode_buffer_index < gcode_buffer_length) {
return gcode_buffer[gcode_buffer_index++];
} else {
return 0; // No more data
}
}
// Check the Holding_Registers_Database and send G-code commands if needed
static void check_register_and_execute_gcode(sys_state_t state) {
// Get data from Holding_Registers_Database
uint16_t number_to_write = Holding_Registers_Database[10];
uint16_t text_size = Holding_Registers_Database[12];
// Check if the values are within the required range
if (number_to_write < 0 || number_to_write > 9 || text_size < 1 || text_size > 10) {
return; // Values out of range, do nothing
}
// Select which G-code file to use based on some criteria
int selected_index = 0; // Example: you might have a function to determine this
send_gcode_sequence(selected_index);
// Continue with any other real-time execution tasks
on_execute_realtime(state);
}
// Add info about our plugin to the $I report
static void on_report_my_options(bool newopt) {
if (on_report_options) {
on_report_options(newopt);
}
if (!newopt) {
hal.stream.write("[PLUGIN:Register Monitor v1.00]" ASCII_EOL);
}
}
void my_plugin_init(void) {
// Add info about our plugin to the $I report
on_report_options = grbl.on_report_options;
grbl.on_report_options = on_report_my_options;
// Initialize UART2
MX_USART2_UART_Init();
// Start UART receive interrupt
HAL_UART_Receive_IT(&huart2, RxData, 256);
// Add check register and execute G-code function to grblHAL foreground process
on_execute_realtime = grbl.on_execute_realtime;
grbl.on_execute_realtime = check_register_and_execute_gcode;
}
void MX_USART2_UART_Init(void)
{
huart2.Instance = USART2;
huart2.Init.BaudRate = 115200;
huart2.Init.WordLength = UART_WORDLENGTH_8B;
huart2.Init.StopBits = UART_STOPBITS_1;
huart2.Init.Parity = UART_PARITY_NONE;
huart2.Init.Mode = UART_MODE_TX_RX;
huart2.Init.HwFlowCtl = UART_HWCONTROL_NONE;
huart2.Init.OverSampling = UART_OVERSAMPLING_16;
if (HAL_UART_Init(&huart2) != HAL_OK)
{
// Initialization Error
Error_Handler();
}
// Configure GPIOs for USART2
MX_GPIO_Init();
}
void MX_GPIO_Init(void)
{
__HAL_RCC_GPIOA_CLK_ENABLE();
GPIO_InitTypeDef GPIO_InitStruct = {0};
// Configure GPIO pins for USART2 TX and RX
GPIO_InitStruct.Pin = GPIO_PIN_2 | GPIO_PIN_3;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF7_USART2;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
}
//void Error_Handler(void)
//{
// // User can add their own implementation to report the HAL error return state
// while (1)
// {
// }
//} |
To clarify my current task, I aim to use the HMI as the host via UART2, but not for sending GRBL-based commands or parameter configurations. Instead, the HMI will modify specific registers inside the MCU based on button presses on the touch screen. My plan is to reassign the My first step is to implement If successful, I'll modify the I would greatly appreciate your guidance on this. Also, if you have time, please check out my forked GRBLHAL project here. Thank you! |
I am not familiar with the UART HAL, to me it seems the callback is not entered before the specified amount of data is received. Are your modbus messages 256 bytes long? FYI you may use the grblHAL serial API instead of the ST UART HAL - see the client modbus code. |
For my test project, I initially created the project in STM32CubeIDE with the selected STM32F407VET6 MCU. I copied these two Modbus.c and Modbus.h files for Modbus RTU communication, then initialized UART2 for interrupts. I used these files in For the grblHAL serial API, could you write me an example for doing this kind of task? The code you linked seems quite overwhelming for using another Modbus library for the spindle.
|
Odd, the callback is only called when all bytes has been received if I read the code correctly.
You only need the parts that claims the stream. Poll for input calling Note that if using the STM HAL you should typically not submit received messages for processing in the callback since this is called from an interrupt context. To avoid that post it for processing via |
I’ve implemented a basic data transmission and reception system with a 256-byte buffer, as you suggested. The idea is that data received via UART should be stored sequentially starting from the beginning of #include "driver.h"
#include <string.h>
#include <stdio.h>
static on_report_options_ptr on_report_options;
static on_execute_realtime_ptr on_execute_realtime;
#define RX_DATA_SIZE 256
static uint8_t RxData[RX_DATA_SIZE];
static uint16_t rx_index = 0;
// Function prototypes
static void poll_for_received_data(void);
// Function to poll for received data, store it in RxData, and write it back
static void poll_for_received_data(void) {
int16_t c;
// Read data from the stream
while ((c = hal.stream.read()) != -1) {
if (rx_index < RX_DATA_SIZE) {
RxData[rx_index++] = (uint8_t)c; // Store received character in RxData
} else {
rx_index = 0;
}
hal.stream.write((uint8_t)c); // Echo the received character
hal.stream.write(ASCII_EOL); // Write ASCII end-of-line character
}
}
// Check the Holding_Registers_Database and send G-code commands if needed
static void check_register_and_execute_gcode(sys_state_t state) {
// Poll for and process received data
poll_for_received_data();
// Continue with any other real-time execution tasks
on_execute_realtime(state);
}
// Add info about our plugin to the $I report
static void on_report_my_options(bool newopt) {
if (on_report_options) {
on_report_options(newopt);
}
if (!newopt) {
// Add ASCII_EOL after the plugin information string
const char plugin_info[] = "[PLUGIN:Register Monitor v1.00]" ASCII_EOL;
hal.stream.write((uint8_t*)plugin_info);
}
}
void my_plugin_init(void) {
// Add info about our plugin to the $I report
on_report_options = grbl.on_report_options;
grbl.on_report_options = on_report_my_options;
// Add check register and execute G-code function to grblHAL foreground process
on_execute_realtime = grbl.on_execute_realtime;
grbl.on_execute_realtime = check_register_and_execute_gcode;
} |
You should wait until a complete message is received before reading:
If variable length messages then read enough to parse the length then wait for the rest. Note that the default serial stream is also read by the main protocol loop, it will be random who gets the data. You need to read from another stream by claiming one like how it is done in the modbus_rtu code. Or if you know which stream instance to use you can claim it explicitly by calling Looking at the modbus code I see that there is no CRC check of the received data, only range checks. If reception goes out of sync you should flush the input buffer before reading the next message - possibly after a short delay. |
After trying for a while without success, I tested the method you suggested, and while it works, it still doesn't meet my needs for a custom receive interrupt. Please note that the project involves a single communication stream with the HMI. To clarify, the simple program in main.c uses I want to implement this feature and am unsure if it's acceptable or possible to write it in |
What are your needs that is not met?
All your code can be placed in my_plugin.c (and by adding other files as needed), you can then easily upgrade to newer releases without applying modifications to the ones from the repo. |
You mentioned that it was possible to place all the code from the three files I provided above, similar to what I attempted a week ago as referenced here. However, despite numerous attempts, I have not achieved the desired results. You also advised against using the ST HAL library for UART, but suggested that all my code could be placed in |
I think the code I'm developing is receiving characters correctly, but not as hex values. I suspect that The current code#include "driver.h"
#include <string.h>
#include <stdio.h>
#include "Modbus.h"
static on_report_options_ptr on_report_options;
static on_execute_realtime_ptr on_execute_realtime;
#define RX_DATA_SIZE 16
uint8_t RxData[RX_DATA_SIZE];
static uint16_t rx_index = 0;
uint8_t TxData[RX_DATA_SIZE];
// Function prototypes
static void poll_for_received_data(void);
static void flush_input_buffer(void);
static void modbus_proces(void);
static void modbus_process(void) {
if (RxData[0]==SLAVE_ID) {
if (RxData[1] == 0x03 || RxData[1] == 0x04) {
readHoldingRegs();
}
}
}
// Function to flush the input buffer
static void flush_input_buffer(void) {
int16_t c;
// Read and discard all data from the stream until the buffer is empty
while ((c = hal.stream.read()) != -1) {
// Discard data
}
}
// Function to poll for received data, store it in RxData, and write it back
static void poll_for_received_data(void) {
int16_t c;
uint16_t rx_buffer_count;
uint32_t idle_start_time;
const uint32_t IDLE_TIMEOUT_MS = 1000; // Timeout for idle detection in milliseconds
// Initialize the start time for idle detection
idle_start_time = HAL_GetTick(); // Assuming HAL_GetTick() returns milliseconds since system start
while (1) {
rx_buffer_count = hal.stream.get_rx_buffer_count();
// Check if there is data in the buffer
if (rx_buffer_count > 0) {
// Reset the idle timer
idle_start_time = HAL_GetTick();
// Reset index for new message
rx_index = 0;
// Read data from the stream
while (rx_buffer_count > 0 && rx_index < RX_DATA_SIZE) {
if ((c = hal.stream.read()) != -1) {
RxData[rx_index++] = c; // Store received character in RxData
rx_buffer_count--;
}
}
while (rx_index < RX_DATA_SIZE) {
RxData[rx_index++] = 0x00;
}
modbus_process();
} else {
// Check for idle timeout
if ((HAL_GetTick() - idle_start_time) > IDLE_TIMEOUT_MS) {
break;
}
}
// Delay before next check
HAL_Delay(10); // Adjust delay as necessary
}
}
// Check the Holding_Registers_Database and send G-code commands if needed
static void check_register_and_execute_gcode(sys_state_t state) {
// Poll for and process received data
poll_for_received_data();
// Continue with any other real-time execution tasks
on_execute_realtime(state);
}
// Add info about our plugin to the $I report
static void on_report_my_options(bool newopt) {
if (on_report_options) {
on_report_options(newopt);
}
if (!newopt) {
// Add ASCII_EOL after the plugin information string
const char plugin_info[] = "[PLUGIN:Register Monitor v1.00]" ASCII_EOL;
hal.stream.write((uint8_t*)plugin_info);
}
}
void my_plugin_init(void) {
// Add info about our plugin to the $I report
on_report_options = grbl.on_report_options;
grbl.on_report_options = on_report_my_options;
// Add check register and execute G-code function to grblHAL foreground process
on_execute_realtime = grbl.on_execute_realtime;
grbl.on_execute_realtime = check_register_and_execute_gcode;
} |
hal.stream.read() returns int16,
|
Oh, it is working well. However, when transmitting back those same received hex bytes using |
If you need each hex data (byte) to be encoded by sending multiple ASCII characters per byte then yes.
|
I am not converting those hex values to ASCII before sending; I want to send the raw hex values back to the host (in this case, the HMI is the master and the microcontroller is the slave). This is the code that processes void sendData (uint8_t *data, int size)
{
// we will calculate the CRC in this function itself
uint16_t crc = crc16(data, size);
data[size] = crc&0xFF; // CRC LOW
data[size+1] = (crc>>8)&0xFF; // CRC HIGH
// HAL_UART_Transmit(&huart2, data, size+2, 1000);
uint8_t *buff = (uint8_t *)malloc(size + 2);
if (buff == NULL) {
// Handle memory allocation failure
return;
}
// Copy data to buffer
memcpy(buff, data, size+2);
// Send the buffer
hal.stream.write(buff);
// Free the allocated buffer
free(buff);
} |
|
Thank you, this works pretty well. I have one more question: How can I put a sequence of commands into the read buffer and then send them to the machine for processing? For example, I have a string of G-code like this: const char* gcode_file_1 =
"G21 ; Set units to millimeters\n"
"G90 ; Absolute positioning\n"
"G1 F1500 ; Set feed rate\n"
"G1 X10 Y10 ; Move to position\n"
"G1 X20 Y10 ; Draw line\n"
"G1 X20 Y20 ; Draw line\n"
"G1 X10 Y20 ; Draw line\n"
"G1 X10 Y10 ; Draw line\n"
"M2 ; End of program\n"; |
My idea is to poll for data via idle line detection and then check whether to start or stop the G-code execution based on the status. However, I am unsure why the The current code of `my_plugin.c`#include "driver.h"
#include <string.h>
#include <stdio.h>
#include "Modbus.h"
static on_report_options_ptr on_report_options;
static on_execute_realtime_ptr on_execute_realtime;
static int16_t* org_read_ptr;
#define RX_DATA_SIZE 16
#define TX_DATA_SIZE 16
uint8_t RxData[RX_DATA_SIZE];
static uint16_t rx_index = 0;
uint8_t TxData[TX_DATA_SIZE];
extern uint16_t Holding_Registers_Database[50];
extern uint8_t Coils_Database[25];
const char* gcode_file_1 =
"G21 ; Set units to millimeters\n"
"G90 ; Absolute positioning\n"
"G1 F1500 ; Set feed rate\n"
"G1 X10 Y10 ; Move to position\n"
"G1 X20 Y10 ; Draw line\n"
"G1 X20 Y20 ; Draw line\n"
"G1 X10 Y20 ; Draw line\n"
"G1 X10 Y10 ; Draw line\n"
"M2 ; End of program\n";
const char* gcode_file_2 =
"G21 ; Set units to millimeters\n"
"G90 ; Absolute positioning\n"
"G1 F1500 ; Set feed rate\n"
"G1 X10 Y10 ; Move to position\n"
"G2 X20 Y20 I10 J0 ; Draw clockwise arc\n"
"G2 X10 Y10 I-10 J0 ; Complete the circle\n"
"M2 ; End of program\n";
const char* gcode_file_3 =
"G21 ; Set units to millimeters\n"
"G90 ; Absolute positioning\n"
"G1 F1500 ; Set feed rate\n"
"G1 X10 Y10 ; Move to position\n"
"G1 X20 Y10 ; Draw line\n"
"G1 X10 Y20 ; Draw line\n"
"G1 X20 Y20 ; Draw line\n"
"G1 X10 Y30 ; Draw line\n"
"G1 X20 Y30 ; Draw line\n"
"M2 ; End of program\n";
// Function prototypes
static void poll_for_received_data(void);
static void status_checker(void);
static void flush_input_buffer(void);
static void modbus_proces(void);
static void modbus_process(void) {
if (RxData[0]==SLAVE_ID) {
if (RxData[1] == 0x03 || RxData[1] == 0x04) {
readHoldingRegs();
} else if (RxData[1] == 16) {
writeHoldingRegs();
} else if (RxData[1] == 0x01) {
readCoils ();
} else if (RxData[1] == 0x05) {
writeSingleCoil();
}
}
}
// Function to flush the input buffer
static void flush_input_buffer(void) {
int16_t c;
// Read and discard all data from the stream until the buffer is empty
while ((c = hal.stream.read()) != -1) {
// Discard data
}
}
static const char* gcode = NULL;
// Function to send G-code commands via the communication stream
static int16_t send_gcode(void) {
static bool eol_ok = false;
if (gcode == NULL) {
return SERIAL_NO_DATA;
}
if (*gcode == '\0') { // End of the G-code string?
if (eol_ok) {
gcode = NULL; // Reset the G-code pointer
return SERIAL_NO_DATA; // No more data to send
}
eol_ok = true;
return ASCII_LF; // Send a linefeed character
}
char c = *gcode++; // Get the next character
if ((eol_ok = (c == '|'))) { // If the character is a vertical bar '|'
c = ASCII_LF; // Replace it with a linefeed character
}
return (int16_t)c;
}
// Function to poll for received data, store it in RxData, and write it back
static void poll_for_received_data(void) {
int16_t c;
uint16_t rx_buffer_count;
uint32_t idle_start_time;
const uint32_t IDLE_TIMEOUT_MS = 500; // Adjusted based on T3.5
const uint32_t POLLING_INTERVAL_MS = 200; // Faster than T1.5
// Initialize the start time for idle detection
idle_start_time = HAL_GetTick(); // Assuming HAL_GetTick() returns milliseconds since system start
// Ensure that all characters are kept by registering the appropriate handler
hal.stream.set_enqueue_rt_handler(stream_buffer_all);
while (1) {
rx_buffer_count = hal.stream.get_rx_buffer_count();
// Check if there is data in the buffer
if (rx_buffer_count > 0) {
// Reset the idle timer
idle_start_time = HAL_GetTick();
// Reset index for new message
rx_index = 0;
// Read data from the stream
while (rx_buffer_count > 0 && rx_index < RX_DATA_SIZE) {
if ((c = hal.stream.read()) != -1) {
RxData[rx_index++] = (uint8_t)c; // Store received character in RxData
rx_buffer_count--;
}
}
// Fill the remaining buffer space with zeros (optional, depending on your needs)
while (rx_index < RX_DATA_SIZE) {
RxData[rx_index++] = 0x00;
}
// Process the received data
modbus_process();
} else {
// Check for idle timeout
if ((HAL_GetTick() - idle_start_time) > IDLE_TIMEOUT_MS) {
// Handle idle timeout if necessary
break;
}
}
// Delay before next check (polling interval)
HAL_Delay(POLLING_INTERVAL_MS); // Adjust delay as necessary
}
}
static void status_checker(void) {
if (Coils_Database[0] != 0) {
// Decide which G-code file to execute based on Holding_Registers_Database[10]
const char* gcode_to_send = NULL;
switch (Holding_Registers_Database[10]) {
case 1:
gcode_to_send = gcode_file_1;
break;
case 2:
gcode_to_send = gcode_file_2;
break;
case 3:
gcode_to_send = gcode_file_3;
break;
default:
// No valid G-code file selection
break;
}
// If a valid G-code file is selected, send it
if (gcode_to_send != NULL) {
gcode = gcode_to_send; // Set the G-code to be sent
org_read_ptr = hal.stream.read; // Backup original read pointer
hal.stream.read = send_gcode; // Set read pointer to our G-code sender
while (send_gcode() != SERIAL_NO_DATA) {
uint8_t c = send_gcode();
}
hal.stream.read = org_read_ptr; // Restore original read pointer
Coils_Database[0] = 0; // Optionally reset the start/stop status
}
}
}
// Check the Holding_Registers_Database and send G-code commands if needed
static void check_register_and_execute_gcode(sys_state_t state) {
// Poll for and process received data
poll_for_received_data();
status_checker();
// Continue with any other real-time execution tasks
on_execute_realtime(state);
}
// Add info about our plugin to the $I report
static void on_report_my_options(bool newopt) {
if (on_report_options) {
on_report_options(newopt);
}
if (!newopt) {
// Add ASCII_EOL after the plugin information string
const char plugin_info[] = "[PLUGIN:Register Monitor v1.00]" ASCII_EOL;
hal.stream.write((uint8_t*)plugin_info);
}
}
void my_plugin_init(void) {
// Add info about our plugin to the $I report
on_report_options = grbl.on_report_options;
grbl.on_report_options = on_report_my_options;
// Add check register and execute G-code function to grblHAL foreground process
on_execute_realtime = grbl.on_execute_realtime;
grbl.on_execute_realtime = check_register_and_execute_gcode;
} |
You have a A serious issue with your code is that you use the default stream for reading the modbus commands, it is better to claim another stream for that - or you will have to stall the main loop from reading from the input while waiting for data. It is possible but not advisable - if you stall the main loop while gcode is executing motion just stops, you will have to delay stalling it until the gcode has finished running to avoid that.
|
I recently switched to a new UART2 stream, as I changed the default to UART1 in #define BOARD_NAME "MY MACHINE"
//#define HAS_BOARD_INIT
#define SERIAL_PORT 1 // GPIOA: TX = 2, RX = 3
#define SERIAL1_PORT 2 // GPIOA: TX = 2, RX = 3
// ... And this is my source code for The current code of `my_plugin.c`#include "driver.h"
#include <string.h>
#include <stdio.h>
#include "Modbus.h"
// Function pointers
static on_report_options_ptr on_report_options;
static on_execute_realtime_ptr on_execute_realtime;
static int16_t* org_read_ptr;
// Buffer sizes
#define RX_DATA_SIZE 16
#define TX_DATA_SIZE 16
// Global buffers and indices
uint8_t RxData[RX_DATA_SIZE];
static uint16_t rx_index = 0;
uint8_t TxData[TX_DATA_SIZE];
// External variables
extern uint16_t Holding_Registers_Database[50];
extern uint8_t Coils_Database[25];
// G-code files
const char* gcode_file_1 = "G-code string here";
const char* gcode_file_2 = "G-code string here";
const char* gcode_file_3 = "G-code string here";
// Function prototypes
static void poll_for_received_data(void);
static void status_checker(void);
static void flush_input_buffer(void);
static void modbus_process(void);
// Define and open a separate stream for Modbus communication
static io_stream_t modbus_stream;
static void modbus_process(void) {
if (RxData[0] == SLAVE_ID) {
if (RxData[1] == 0x03 || RxData[1] == 0x04) {
readHoldingRegs();
} else if (RxData[1] == 16) {
writeHoldingRegs();
} else if (RxData[1] == 0x01) {
readCoils();
} else if (RxData[1] == 0x05) {
writeSingleCoil();
}
}
}
// Function to flush the input buffer
static void flush_input_buffer(void) {
int16_t c;
while ((c = modbus_stream.read()) != -1) {
// Discard data
}
}
static const char* gcode = NULL;
// Function to send G-code commands via the communication stream
static int16_t send_gcode(void) {
static bool eol_ok = false;
if (gcode == NULL) {
return SERIAL_NO_DATA;
}
if (*gcode == '\0') {
if (eol_ok) {
gcode = NULL;
return SERIAL_NO_DATA;
}
eol_ok = true;
return ASCII_LF;
}
char c = *gcode++;
if ((eol_ok = (c == '|'))) {
c = ASCII_LF;
}
return (int16_t)c;
}
// Function to poll for received data, store it in RxData, and process it
static void poll_for_received_data(void) {
int16_t c;
uint32_t idle_start_time = HAL_GetTick();
const uint32_t IDLE_TIMEOUT_MS = 500;
const uint32_t POLLING_INTERVAL_MS = 200;
rx_index = 0;
while (1) {
// Check if data is available in the Modbus stream
if (modbus_stream.get_rx_buffer_count() > 0) {
idle_start_time = HAL_GetTick();
while (rx_index < RX_DATA_SIZE && (c = modbus_stream.read()) != -1) {
RxData[rx_index++] = (uint8_t)c;
}
modbus_process();
} else if ((HAL_GetTick() - idle_start_time) > IDLE_TIMEOUT_MS) {
break;
}
HAL_Delay(POLLING_INTERVAL_MS);
}
}
// Function to check the status and execute G-code commands if needed
static void status_checker(void) {
if (Coils_Database[0] != 0) {
const char* gcode_to_send = NULL;
switch (Holding_Registers_Database[10]) {
case 1:
gcode_to_send = gcode_file_1;
break;
case 2:
gcode_to_send = gcode_file_2;
break;
case 3:
gcode_to_send = gcode_file_3;
break;
default:
break;
}
if (gcode_to_send != NULL) {
gcode = gcode_to_send;
org_read_ptr = modbus_stream.read;
modbus_stream.read = send_gcode;
while (send_gcode() != SERIAL_NO_DATA) {
// Process G-code
}
modbus_stream.read = org_read_ptr;
Coils_Database[0] = 0;
}
}
}
// Real-time execution check
static void check_register_and_execute_gcode(sys_state_t state) {
poll_for_received_data();
status_checker();
on_execute_realtime(state);
}
// Add plugin information to the report
static void on_report_my_options(bool newopt) {
if (on_report_options) {
on_report_options(newopt);
}
if (!newopt) {
const char plugin_info[] = "[PLUGIN:Register Monitor v1.00]" ASCII_EOL;
modbus_stream.write((uint8_t*)plugin_info);
}
}
void my_plugin_init(void) {
// Initialize the Modbus stream
io_stream_t const *stream;
if ((stream = stream_open_instance(2, 115200, NULL, "Modbus UART")) == NULL) {
stream = stream_null_init(115200);
}
memcpy(&modbus_stream, stream, sizeof(io_stream_t));
// Set up the plugin
modbus_stream.set_enqueue_rt_handler(stream_buffer_all);
on_report_options = grbl.on_report_options;
grbl.on_report_options = on_report_my_options;
on_execute_realtime = grbl.on_execute_realtime;
grbl.on_execute_realtime = check_register_and_execute_gcode;
} |
I would get rid of the
If it works then you are good.
There is a stream function available for this, call |
I am currently working on integrating an HMI (Human-Machine Interface) to set and get values through the MCU's custom register map. Specifically, I am looking to achieve the following:
Given that the G-code files are limited in number, I have opted to store them directly on the MCU. My goal is for the MCU to manage all the G-code parsing and execution internally once a file is selected via the HMI.
Summary of Requirements:
Questions:
main
function to handle this custom communication with the HMI?Any guidance or solutions on how to implement this would be greatly appreciated. Thank you!
The text was updated successfully, but these errors were encountered: