diff --git a/LinuxCNC_ArduinoConnector.ino b/LinuxCNC_ArduinoConnector.ino index 73de9cb..9a26b6d 100644 --- a/LinuxCNC_ArduinoConnector.ino +++ b/LinuxCNC_ArduinoConnector.ino @@ -1,3 +1,10 @@ +//#include +//#include +//#include +//#include +//#include +//#include + /* LinuxCNC_ArduinoConnector By Alexander Richter, info@theartoftinkering.com 2022 @@ -35,6 +42,11 @@ joystick = 'R' -write only -Pin State: up/ down / -2147483648 to 2147483647 multiplexed LEDs = 'M' -read only -Pin State: 0,1 +LCD Variables: + LCD Float Variables = 'F' -write only -Pin State: integer (×1000) + LCD Integer Variables = 'N' -write only -Pin State: integer value + LCD Boolean Variables = 'B' -write only -Pin State: 0,1 + Keyboard Input: Matrix Keypad = 'M' -write only -Pin State: 0,1 @@ -302,7 +314,73 @@ int currentLED = 0; #endif - +//#define LCD_VARS +#ifdef LCD_VARS + // LCD Display Configuration + #define LCD_COLUMNS 20 + #define LCD_ROWS 4 + + // LCD Pin definitions + #define LCD_PINS_RS 82 + #define LCD_PINS_ENABLE 61 + #define LCD_PINS_D4 59 + #define LCD_PINS_D5 70 + #define LCD_PINS_D6 85 + #define LCD_PINS_D7 71 + + // LCD object initialization + LiquidCrystal lcd(LCD_PINS_RS, LCD_PINS_ENABLE, LCD_PINS_D4, LCD_PINS_D5, LCD_PINS_D6, LCD_PINS_D7); + + // Variable types + enum VarType { + VAR_FLOAT, + VAR_INT, + VAR_BOOL + }; + + // Structure for variable definition + struct LCDVar { + const char* name; // Variable name + uint8_t row; // Row on LCD (0-3) + uint8_t col; // Column on LCD (0-19) + VarType type; // Variable type + union { + float floatValue; // For float variables + int32_t intValue; // For int variables + bool boolValue; // For bool variables + } value; + union { + float floatOldValue; // For float variables + int32_t intOldValue; // For int variables + bool boolOldValue; // For bool variables + } oldValue; + uint8_t decimals; // Number of decimal places (for float) + }; + + // Macros to simplify LCD variable definitions + #define LCD_FLOAT(name, row, col, decimals) {name, row, col, VAR_FLOAT, {.floatValue = 0.0}, {.floatOldValue = -999.0}, decimals} + #define LCD_INT(name, row, col) {name, row, col, VAR_INT, {.intValue = 0}, {.intOldValue = -999}, 0} + #define LCD_BOOL(name, row, col) {name, row, col, VAR_BOOL, {.boolValue = false}, {.boolOldValue = false}, 0} + + // Variable definitions - modify as needed + LCDVar lcdVars[] = { + LCD_FLOAT("Float var 1: ", 0, 0, 3), // on row 0, column 0, 3 decimal place + LCD_FLOAT("Float var 2: ", 1, 0, 2), // on row 1, column 0, 2 decimal places + LCD_INT("Int var: ", 2, 0), + LCD_BOOL("Bool var: ", 3, 0) + }; + + // Macro to calculate the number of variables + #define LCD_VAR_COUNT (sizeof(lcdVars) / sizeof(lcdVars[0])) + + // Function to initialize LCD variables + void initLCDVars(); + + // Function to set LCD variable value from LinuxCNC + void setLCDVar(int index, float newValue); + void setLCDVar(int index, int32_t newValue); + void setLCDVar(int index, bool newValue); +#endif //#define DEBUG //####################################### END OF CONFIG ########################### @@ -391,15 +469,15 @@ int connectionState = 0; byte state = STATE_CMD; -char inputbuffer[5]; +char inputbuffer[32]; // Increased size for large float values (e.g., 123400) byte bufferIndex = 0; char cmd = 0; uint16_t io = 0; -uint16_t value = 0; +int32_t value = 0; // Changed from uint16_t to int32_t for LCD float values // Function Prototypes void readCommands(); -void commandReceived(char cmd, uint16_t io, uint16_t value); +void commandReceived(char cmd, uint16_t io, int32_t value); void multiplexLeds(); void readKeypad(); int readAbsKnob(); @@ -483,8 +561,16 @@ for(int col = 0; col < numCols; col++) { } #endif +#ifdef LCD_VARS + lcd.begin(LCD_COLUMNS, LCD_ROWS); + lcd.clear(); + // Initialize LCD variables + initLCDVars(); -//Setup Serial + delay(500); +#endif + + //Setup Serial Serial.begin(115200); while (!Serial){} comalive(); @@ -999,7 +1085,116 @@ void multiplexLeds() { } #endif -void commandReceived(char cmd, uint16_t io, uint16_t value){ +#ifdef LCD_VARS + +void initLCDVars() { + for (size_t i = 0; i < LCD_VAR_COUNT; i++) { + lcd.setCursor(lcdVars[i].col, lcdVars[i].row); + lcd.print(lcdVars[i].name); + // Clear the rest of the line + int spacesToClear = LCD_COLUMNS - (lcdVars[i].col + strlen(lcdVars[i].name)); + if (spacesToClear > 0) { + for (int j = 0; j < spacesToClear; j++) { + lcd.print(" "); + } + } + } +} + +void setLCDVar(int index, float newValue) { + // Set float variable value received from LinuxCNC + if (index >= 0 && (size_t)index < LCD_VAR_COUNT && lcdVars[index].type == VAR_FLOAT) { + lcdVars[index].value.floatValue = newValue; + + #ifdef DEBUG + Serial.print("Float var "); + Serial.print(index); + Serial.print(" set to: "); + Serial.println(newValue); + #endif + + // Check if value changed - compare newValue with oldValue + bool valueChanged = (newValue != lcdVars[index].oldValue.floatOldValue); + // Update LCD display if value changed + if (valueChanged) { + lcd.setCursor(lcdVars[index].col, lcdVars[index].row); + lcd.print(lcdVars[index].name); + lcd.print(lcdVars[index].value.floatValue, lcdVars[index].decimals); + // Clear the rest of the line + int spacesToClear = LCD_COLUMNS - (lcdVars[index].col + strlen(lcdVars[index].name) + String(lcdVars[index].value.floatValue, lcdVars[index].decimals).length()); + if (spacesToClear > 0) { + for (int j = 0; j < spacesToClear; j++) { + lcd.print(" "); + } + } + lcdVars[index].oldValue.floatOldValue = newValue; + } + } +} + +void setLCDVar(int index, int32_t newValue) { + if (index >= 0 && (size_t)index < LCD_VAR_COUNT && lcdVars[index].type == VAR_INT) { + lcdVars[index].value.intValue = newValue; + + #ifdef DEBUG + Serial.print("Int var "); + Serial.print(index); + Serial.print(" set to: "); + Serial.println(newValue); + #endif + + // Check if value changed - compare newValue with oldValue + bool valueChanged = (newValue != lcdVars[index].oldValue.intOldValue); + // Update LCD display if value changed + if (valueChanged) { + lcd.setCursor(lcdVars[index].col, lcdVars[index].row); + lcd.print(lcdVars[index].name); + lcd.print(lcdVars[index].value.intValue); + // Clear the rest of the line + int spacesToClear = LCD_COLUMNS - (lcdVars[index].col + strlen(lcdVars[index].name) + String(lcdVars[index].value.intValue).length()); + if (spacesToClear > 0) { + for (int j = 0; j < spacesToClear; j++) { + lcd.print(" "); + } + } + lcdVars[index].oldValue.intOldValue = newValue; + } + } +} + +void setLCDVar(int index, bool newValue) { + if (index >= 0 && (size_t)index < LCD_VAR_COUNT && lcdVars[index].type == VAR_BOOL) { + lcdVars[index].value.boolValue = newValue; + + #ifdef DEBUG + Serial.print("Bool var "); + Serial.print(index); + Serial.print(" set to: "); + Serial.println(newValue); + #endif + + // Check if value changed - compare newValue with oldValue + bool valueChanged = (newValue != lcdVars[index].oldValue.boolOldValue); + // Update LCD display if value changed + if (valueChanged) { + lcd.setCursor(lcdVars[index].col, lcdVars[index].row); + lcd.print(lcdVars[index].name); + lcd.print(lcdVars[index].value.boolValue ? "ON " : "OFF"); + // Clear the rest of the line - FIXED: Both "ON " and "OFF" are 3 characters + int spacesToClear = LCD_COLUMNS - (lcdVars[index].col + strlen(lcdVars[index].name) + 3); + if (spacesToClear > 0) { + for (int j = 0; j < spacesToClear; j++) { + lcd.print(" "); + } + } + lcdVars[index].oldValue.boolOldValue = newValue; + } + } +} + +#endif // LCD_VARS + +void commandReceived(char cmd, uint16_t io, int32_t value){ #ifdef OUTPUTS if(cmd == 'O'){ writeOutputs(io,value); @@ -1041,6 +1236,41 @@ void commandReceived(char cmd, uint16_t io, uint16_t value){ } #endif + #ifdef LCD_VARS + if(cmd == 'F'){ // Set float variable from LinuxCNC + // io = index of variable, value = integer part (multiplied by 1000) + // Convert back to float by dividing by 1000 + float floatValue = (float)value / 1000.0; + setLCDVar(io, floatValue); + lastcom=millis(); + } + if(cmd == 'N'){ // Set integer variable from LinuxCNC + // io = index of variable, value = integer value + int32_t intValue = (int32_t)value; + #ifdef DEBUG + Serial.print("Received INT command N"); + Serial.print(io); + Serial.print(":"); + Serial.println(intValue); + #endif + setLCDVar(io, intValue); + lastcom=millis(); + } + if(cmd == 'B'){ // Set boolean variable from LinuxCNC + // io = index of variable, value = boolean value (0 or 1) + bool boolValue = (value != 0); + #ifdef DEBUG + Serial.print("Received BOOL command B"); + Serial.print(io); + Serial.print(":"); + Serial.print(value); + Serial.print(" -> "); + Serial.println(boolValue ? "ON" : "OFF"); + #endif + setLCDVar(io, boolValue); + lastcom=millis(); + } + #endif if(cmd == 'E'){ lastcom=millis(); @@ -1071,35 +1301,37 @@ void readCommands(){ bufferIndex = 0; break; case STATE_IO: - if(isDigit(current)){ + if(isdigit(current)){ // Only accept digits for pin/index inputbuffer[bufferIndex++] = current; }else if(current == ':'){ inputbuffer[bufferIndex] = 0; - io = atoi(inputbuffer); + io = strtol(inputbuffer, NULL, 10); state = STATE_VALUE; bufferIndex = 0; } else{ #ifdef DEBUG - Serial.print("Ungültiges zeichen: "); + Serial.print("Invalid character: "); Serial.println(current); #endif } break; case STATE_VALUE: - if(isDigit(current)){ - inputbuffer[bufferIndex++] = current; + if(isdigit(current) || current == '-'){ // Accept digits and minus sign + if(bufferIndex < 31) { // Prevent buffer overflow + inputbuffer[bufferIndex++] = current; + } } else if(current == '\n'){ inputbuffer[bufferIndex] = 0; - value = atoi(inputbuffer); + value = strtol(inputbuffer, NULL, 10); // Use strtol() for better int32_t parsing commandReceived(cmd, io, value); state = STATE_CMD; } else{ #ifdef DEBUG - Serial.print("Ungültiges zeichen: "); + Serial.print("Invalid character: "); Serial.println(current); #endif diff --git a/README.md b/README.md index 786be00..d7fc782 100644 --- a/README.md +++ b/README.md @@ -33,12 +33,12 @@ It also supports Digital LEDs such as WS2812 or PL9823. This way you can have as | Quadrature Encoder Input | 3 or more | 1 or more | 1 or more | | Joystick Support (2Axis) | 8 | 6 | 3 | | Matrix Keyboard | 1 | 1 | 1 | +| LCD Variables Display | Up to 20 | Up to 20 | Up to 20 | | Multiplexed LEDs | ~ 1000 | ~ 1000 | ~ 1000 | Planned Features: - Temperature Probes using 4.7k Pullup-Resistor -- Support for i2C LCDs # Compatiblity This software works with LinuxCNC 2.8, 2.9 and 2.10. For 2.8, however, you have to change #!/usr/bin/python3.9 in the first line of arduino.py to #!/usr/bin/python2.7. @@ -54,6 +54,8 @@ Other Arduino compatible Boards like Teensy should work fine also. To Install LinuxCNC_ArduinoConnector.ino on your Arduino first work through the settings in the beginning of the file. The Settings are commented in the file. +**New Feature**: LCD Variables Display support is now available. You can configure LCD displays to show real-time machine data from LinuxCNC. See the [LCD Variables Display](#lcd-variables-display) section for detailed configuration instructions. + To test your Arduino you can connect to it after flashing with the Arduino IDE. Set your Baudrate to 115200. In the beginning the Arduino will Spam ```E0:0``` to the console. This is used to establish connection. Just return ```E0:0``` to it. You can now communicate with the Arduino. Further info is in the Chapter [Serial Communication](#serial-communication-over-usb) @@ -188,6 +190,49 @@ ch the Video explanation on Youtube: [![IMAGE ALT TEXT](https://img.youtube.com/vi/oOhzm7pbvXo/0.jpg)](https://www.youtube.com/watch?v=oOhzm7pbvXo&list=PLdrOU2f3sjtApTdxhmAiXL4lET_ZnntGc&index=4 "connect Matrix Keyboards to LinuxCNC using ArduinoC") +# LCD Variables Display +The software now supports displaying variables from LinuxCNC on an LCD display connected to the Arduino. This feature allows you to show real-time machine data such as feed rates, spindle speeds, positions, and status information. + +LCD Display with Variables + +*Example of LCD display showing different variable types: Float variables, Integer variables, and Boolean variables with real-time data from LinuxCNC* + +### LCD Hardware Support +- **Display Type**: Standard HD44780 compatible LCD displays (16x2, 20x4, etc.) +- **Connection**: 6-wire parallel interface (RS, Enable, D4, D5, D6, D7) +- **Pin Configuration**: Configurable in the Arduino sketch + +### Variable Types Supported +1. **Float Variables**: Display decimal values with configurable precision +2. **Integer Variables**: Display whole numbers +3. **Boolean Variables**: Display ON/OFF states + +### Configuration +In the Arduino sketch, you can define: +- LCD dimensions (columns and rows) +- Pin assignments for LCD connection +- Variable definitions with names, positions, and types +- Number of decimal places for float variables + +### LinuxCNC Integration +The Python connector creates HAL pins for each LCD variable: +- `lcd.floatvar.0`, `lcd.floatvar.1`, etc. for float variables +- `lcd.intvar.0`, `lcd.intvar.1`, etc. for integer variables +- `lcd.boolvar.0`, `lcd.boolvar.1`, etc. for boolean variables + +### Example Usage +Connect these pins to your LinuxCNC HAL components to display: +- Current feed rate and spindle speed +- Machine coordinates (X, Y, Z) +- Tool information and offsets +- Machine status and error messages + +### Setup Requirements +- Install LiquidCrystal library in Arduino IDE +- Configure LCD pins in the Arduino sketch +- Set the number of variables in both Arduino and Python files +- Connect your LCD display to the specified Arduino pins + # Multiplexed LEDs Special mode for Multiplexed LEDs. This mode is experimental and implemented to support Matrix Keyboards with integrated Key LEDs. Please provide feedback if u use this feature. check out this thread on LinuxCNC Forum for context. https://forum.linuxcnc.org/show-your-stuff/49606-matrix-keyboard-controlling-linuxcnc @@ -269,6 +314,9 @@ Data is always only send once, everytime it changes. | Latching Potentiometers | L | write only |0-max Position| | binary encoded Selector | K | write only |0-32 | | Matrix Keyboard | M | write only |0,1 | +| LCD Float Variables | F | read only |float value | +| LCD Integer Variables | N | read only |integer value | +| LCD Boolean Variables | B | read only |0,1 | | Quadrature Encoders | R | write only |0,1,counter | | Joystick | R | write only |counter | | Connection established | E | read/ write |0:0 | diff --git a/arduino-connector.py b/arduino-connector.py index c5abfad..d58e4a1 100644 --- a/arduino-connector.py +++ b/arduino-connector.py @@ -32,6 +32,7 @@ # Multiplexed LEDs = 'M' -read only -Pin State: 0,1 # Quadrature Encoders = 'R' -write only -Pin State: 0(down),1(up),-2147483648 to 2147483647(counter) # Joystick Input = 'R' -write only -Pin State: -2147483648 to 2147483647(counter) +# Float Variables = 'F' -write only -Pin State: float value (e.g., 123.456) @@ -115,7 +116,11 @@ JoyStickPins = [0,1] #Pins the Joysticks are connected to. #in this example X&Y Pins of the Joystick are connected to Pin A0& A1. - +# Enable Variables for LCD Display +# This allows you to send various data types from LinuxCNC to Arduino for display on LCD +LcdFloatVars = 4 # Number of float variables to support (set to 0 to disable) - should match the number of float variables in Arduino lcdVars array +LcdIntVars = 2 # Number of integer variables to support (set to 0 to disable) +LcdBoolVars = 2 # Number of boolean variables to support (set to 0 to disable) # Set how many Digital LED's you have connected. @@ -175,7 +180,7 @@ -Debug = 0 #only works when this script is run from halrun in Terminal. "halrun","loadusr arduino" now Debug info will be displayed. +Debug = 1 #ENABLED: Debug mode to check if values are being sent to Arduino ######## End of Config! ######## @@ -187,6 +192,12 @@ oldDLEDStates=[0]*DLEDcount oldMledStates = [0]*LedVccPins*LedGndPins +# Add tracking for LCD variables to only send when values change +# Initialize with values that will force first send +oldFloatVars = [-999.0] * LcdFloatVars # Initialize based on LcdFloatVars value +oldIntVars = [-999] * LcdIntVars # Initialize based on LcdIntVars value +oldBoolVars = [False] * LcdBoolVars # Initialize based on LcdBoolVars value + if LinuxKeyboardInput: import subprocess @@ -262,6 +273,18 @@ for port in range(JoySticks*2): c.newpin("counter.{}".format(JoyStickPins[port]), hal.HAL_S32, hal.HAL_OUT) +# setup Variable halpins (write-only for one-way communication from LinuxCNC to Arduino) +if LcdFloatVars > 0: + for port in range(LcdFloatVars): + c.newpin("lcd.floatvar.{}".format(port), hal.HAL_FLOAT, hal.HAL_IN) + +if LcdIntVars > 0: + for port in range(LcdIntVars): + c.newpin("lcd.intvar.{}".format(port), hal.HAL_S32, hal.HAL_IN) + +if LcdBoolVars > 0: + for port in range(LcdBoolVars): + c.newpin("lcd.boolvar.{}".format(port), hal.HAL_BIT, hal.HAL_IN) if QuadEncs > 0: for port in range(QuadEncs): @@ -353,6 +376,47 @@ def managageOutputs(): oldMledStates[mled] = State time.sleep(0.01) + # Manage Variables - Fixed: Convert float to integer*1000 and only send when changed + if LcdFloatVars > 0: + for port in range(LcdFloatVars): + State = c["lcd.floatvar.{}".format(port)] + # CRITICAL FIX: Convert float to integer * 1000 for Arduino compatibility + if oldFloatVars[port] != State: # Only send when value changes + intValue = round(State * 1000) # Use round() instead of int() for better precision + command = "F{}:{}\n".format(port, intValue) + arduino.write(command.encode()) + if (Debug):print ("Sending Float: F{}:{} (original: {})".format(port, intValue, State)) + oldFloatVars[port] = State # Update old value + time.sleep(0.01) + + if LcdIntVars > 0: + for port in range(LcdIntVars): + State = c["lcd.intvar.{}".format(port)] + # Only send when value changes + if oldIntVars[port] != State: + # FIXED: Integer variables start at index LcdFloatVars (after float vars) + arduino_index = LcdFloatVars + port + command = "N{}:{}\n".format(arduino_index, State) + arduino.write(command.encode()) + if (Debug):print ("Sending Int: N{}:{} (Arduino index {})".format(port, State, arduino_index)) + oldIntVars[port] = State # Update old value + time.sleep(0.01) + + if LcdBoolVars > 0: + for port in range(LcdBoolVars): + State = c["lcd.boolvar.{}".format(port)] + # Only send when value changes + if oldBoolVars[port] != State: + # FIXED: Boolean variables start at index LcdFloatVars + LcdIntVars + arduino_index = LcdFloatVars + LcdIntVars + port + # CRITICAL FIX: Convert boolean to 0/1 integer, not True/False text + boolValue = 1 if State else 0 + command = "B{}:{}\n".format(arduino_index, boolValue) + arduino.write(command.encode()) + if (Debug):print ("Sending Bool: B{}:{} (Arduino index {}, raw: {})".format(port, boolValue, arduino_index, State)) + oldBoolVars[port] = State # Update old value + time.sleep(0.01) + while True: try: diff --git a/images/lcd-variables-display.jpg b/images/lcd-variables-display.jpg new file mode 100644 index 0000000..d8f1e53 Binary files /dev/null and b/images/lcd-variables-display.jpg differ