And although I haven't moved it from my workbench Pi4 system to the real mill a few years ago I did create the code to talk to a CANopen module that has PWM and relay drivers. USB as Serial port to CANUSB. https://canusbshop.com/
Written in Python but marked as .txt so it won't scare mail programs. It all appeared to work. Then life got in the way and as yet have not yet finished off the mist coolant system nor the power draw bar. John > -----Original Message----- > From: marcus.bow...@visible.eclipse.co.uk > [mailto:marcus.bow...@visible.eclipse.co.uk] > Sent: December 19, 2024 2:25 PM > To: Enhanced Machine Controller (EMC) > Subject: Re: [Emc-users] Biting the bullet > > On 2024-12-19 19:50, John Dammeyer wrote: > > Ha ha. I'm still running LinuxCNC 2.8 on my mill. Kinda afraid to > > upgrade. Like if it works why change? > > John > > Me too....same reasons... > Marcus > > > _______________________________________________ > Emc-users mailing list > Emc-users@lists.sourceforge.net > https://lists.sourceforge.net/lists/listinfo/emc-users
custom.hal
Description: Binary data
#!/usr/bin/python # Project LinuxCNC Python CANUSB Serial Port control derived from: # https://forum.linuxcnc.org/10-advanced-configuration/538-controlling-coolant-w-serial-port?start=0 # User: mozmck (Moses McKnight) # This application has been modified to use the Lawicel CANUSB treated as a USB serial port # sending out CAN messages in the CANopen format. # # References: # http://linuxcnc.org/docs/html/hal/halmodule.html # Version 0.2 12SEP21 # Written by # John Dammeyer # jo...@autoartisans.com # # Version 0.2 11SEP21 # -- Moved while ser.inWaiting(): into main try loop because CANUSB faults with buffer overflow # if the on change event PDO is not sent while the bus is active with other messages. # -- Next step is to create a seperate receive function which parses received messages for input # and another one that puts out a periodic NMT heartbeat message. import hal import time, serial # *** Issue this command from the terminal #sudo apt-get install python-serial # The serial-relays.py needs to be in the config directory for your mill. # For example: '/home/jerrybaca/emc2/configs/harborfrieghtmill' # Don't forget to sudo chmod +x serial-relays.py # *** Next Place the following lines into one of the Post HAL files. # custom.hal is probably a good enough place. #loadusr -Wn serial-relays ./serial-relays.py #net coolant-mist <= iocontrol.0.coolant-mist #net coolant-flood <= iocontrol.0.coolant-flood #net coolant-mist => serial-relays.relay1C #net coolant-flood => serial-relays.relay1D # *** End of HAL file instructions. # --- Global Variables --- h = hal.component("serial-relays") h.newpin("relay1A", hal.HAL_BIT, hal.HAL_IN) h.newpin("relay1B", hal.HAL_BIT, hal.HAL_IN) h.newpin("relay1C", hal.HAL_BIT, hal.HAL_IN) h.newpin("relay1D", hal.HAL_BIT, hal.HAL_IN) h.newpin("relay1E", hal.HAL_BIT, hal.HAL_IN) h.newpin("relay1F", hal.HAL_BIT, hal.HAL_IN) h.newpin("relay1G", hal.HAL_BIT, hal.HAL_IN) h.newpin("relay1H", hal.HAL_BIT, hal.HAL_IN) h.newpin("mister_pwm", hal.HAL_U32, hal.HAL_IN) h.newpin("fanspd_pwm", hal.HAL_U32, hal.HAL_IN) h.hewpin("switch1A", hal.HAL_BIT, hal.HAL_OUT) h.hewpin("switch1B", hal.HAL_BIT, hal.HAL_OUT) h.ready() # Serial stuff, change the serial port address by changing /dev/ttyS0 or COMx (for Windows) # For USB based Serial dongles that use FTDI hardware /dev/ttyUSB0 is often the default. # USB can be set for 115200 baud as long as the target hardware can support that since # it doesn't really delay the LinuxCNC side. ser = serial.Serial('/dev/ttyUSB0', 115200, bytesize=8, parity='N', stopbits=1, timeout=0, xonxoff=0, rtscts=0) # Create some Global variables for readability and education RPDO1 = 0x200 # Receive Process Data Object #1 RPDO2 = 0x300 # Receive Process Data Object #2 TPDO2 = 0x280 # Transmit Process Data Object #2 NodeID = 0x18 # xIM series Device Node ID (0x01..0x7F) PDO_ID = '' # The character string that holds the OR operation of the PDO+ID CANopenPDO_1_Msg = '' # For reporting what we send and so we don't have to recreate it each time. CANopenPDO_2_Msg = '' # For reporting what we send and so we don't have to recreate it each time. TempStr = "" # global so we can print what was sent. # Message Counter used to generate an 'F' command to read flags in case the 0x07 is missed. MsgCount = 0 TimeTick = 0 # Copies of CAN dataRelayImage = 0 # No relays set yet. Mist_PWM = 0 # Mister pump motor speed 0 Fan_PWM = 0 # Cooling fan off. # Individual Relay State values rA = 0 rB = 0 rC = 0 rD = 0 rE = 0 rF = 0 rG = 0 rH = 0 # Switch input state values. Set from received PDO CAN message sA = 0 sB = 0 # --- Methods --- # Function to send data to CANUSB dongle. # Parameters: # v1 is 0-100% PWM value placed into first 1 byte of PDO message # v2 is 0-100% PWM value placed into next byte of PDO message # Outputs: # Sends CANopen RPDO_1 message to CANUSB. def Send_PDO_1(v1, v2): global CANopenPDO_1_Msg # the message without the data. global TempStr ser.write(CANopenPDO_1_Msg.encode('utf-8')) # send PDO 3xx to node 0x18 with 1 byte TempStr = "{0:0{1}X}".format(v1 & 0xFF,2) ser.write(TempStr.encode('utf-8')) # Add PWM to message TempStr = "{0:0{1}X}".format((v1 >> 8) & 0xFF,2) ser.write(TempStr.encode('utf-8')) # Add PWM to message ser.write(CANopenPDO_1_Msg.encode('utf-8')) # send PDO 3xx to node 0x18 with 1 byte TempStr = "{0:0{1}X}".format(v2 & 0xFF,2) ser.write(TempStr.encode('utf-8')) # Add PWM to message TempStr = "{0:0{1}X}".format((v2 >> 8) & 0xFF,2) ser.write(TempStr.encode('utf-8')) # Add PWM to message ser.write('\r'.encode('utf-8')) # Tell CANUSB to send message with <CR> character. # Function to send data to CANUSB dongle. # Parameters: # r is relayimage placed into first byte of PDO message. # Outputs: # Sends CANopen RPDO_2 message to CANUSB. # Future Enhancements: # Pass PDO #, Node ID #, Array of bytes, Number of valid bytes. def Send_PDO_2(r): global CANopenPDO_2_Msg # the message without the data. global TempStr ser.write(CANopenPDO_2_Msg.encode('utf-8')) # send PDO 3xx to node 0x18 with 1 byte # Format bit image to two hex characters TempStr = "{0:0{1}X}".format(r,2) ser.write(TempStr.encode('utf-8')) # Add relay bit map to message TempStr = "{0:0{1}X}".format(v,4) ser.write(TempStr.encode('utf-8')) # Add fan speed to message ser.write('\r'.encode('utf-8')) # Tell CANUSB to send message with <CR> character. # Function to update local PDO RelayImage # Parameters: # r is which relay to switch (1..8) # does not validate relay # to see if in range. # level is TRUE or FALSE for relay ON or OFF. # Modifies: # Global RelayImage bitmap of relay values # def Update_Relays(r, level): global RelayImage # if this isn't here then the OR operation fails with uninitialized variable. # Create the text value of the relay image. bitmask = 1 << (r-1) # Select which relay to switch ON. if level == True: RelayImage = (RelayImage | bitmask) else: RelayImage = (RelayImage & ~bitmask) # End of Update_Relays # Function to update local Mister Pump PWM value # Parameters: # v is which new value range checked to keep within 100% # value should be 0..100% # Modifies: # Global Mist_PWM # def Update_Mist_PWM( v ): global Mist_PWM if (v > 100): Mist_PWM = 100 else: Mist_PWM = v # End of Update_Fan_PWM # Function to update local Cooling Fan Speed PWM value # Parameters: # v is which new value range checked to keep within 100% # value should be 0..100% # Modifies: # Global Fan_PWM # def Update_Fan_PWM( v ): global Fan_PWM if (v > 100): Fan_PWM = 100 else: Fan_PWM = v # End of Update_Fan_PWM # Function to Parse CANopen messages # Parameters: # s is the CANUSB format message for # ID SIZE DATA # iii l xx xx xx xx xx xx xx xx # Outputs: # If it receives an NMT message with Node in PRE-OP state it sends a GO OPERATIONAL message. # def ParseCANopenMsg(s): # pull apart ascii CANopen message print('Received CANopen Msg = '+s) # for now just print it. CANcmd = int(s[0:3],16) & 0x0780 # Pull out Type of CANopen message CANid = int(s[1:3],16) # Get the Node ID CANdlc = int(s[3]) # How many bytes in the message CANdata = [0] * 8 # print("CAN cmd", hex(CANcmd), " CAN ID ", hex(CANid), " CANdlc ", hex(CANdlc)) for x in range(CANdlc): # # Fill in data bytes. CANdata[x] = int(s[4+x*2:6+x*2], 16) # Would be more efficient with an array. # print(s[4+x*2:6+x*2]) # Handle Device Heartbeat messages. if (CANcmd == 0x700): CANdev_NMT_State = CANdata[0] # int(s[4:6],16) # Get the first byte if (CANdev_NMT_State == 0x7f): msg = "t00020100\r" # NMT Master GO Operational for everyone. ser.write(msg.encode('utf-8')) print("Sent NMT_GO_OPERATIONAL Message = " + msg) # End of ParseCANopenMsg # --- Mainline loop --- try: # Create text value of CAN Standard ID => PDO + Node ID and Message Length of 1 PDO_ID = "{0:0{1}X}".format(RPDO1+NodeID,3) CANopenPDO_1_Msg = 't' + PDO_ID + '4' # t2183 PDO_ID = "{0:0{1}X}".format(RPDO2+NodeID,3) CANopenPDO_2_Msg = 't' + PDO_ID + '1' # t3181 # Initialize CANUSB dongle ser.write('C\r'.encode('utf-8')) # Close first ser.write('F\r'.encode('utf-8')) # Clear Error Flags by reading them ser.write('S5\r'.encode('utf-8')) # set 250kbps ser.write('O\r'.encode('utf-8')) # Open CANUSB for communication. Send_PDO_1(Mist_PWM, Fan_PWM) # Update device PWM with default values. Send_PDO_2(RelayImage) # Update device relays with default values. # Main Loop while 1: # Flush messages to find the first one after a CR character. ser.read_until(expected='\r') s = '' # Check for messages from CANUSB. while (ser.in_waiting != 0): ch = ser.read(1) if (ch == b'\r'): # End of line message found. if (len(s) >= 1): ch = ord(s[0]) # check the first character in the line. # print('Len is = '+str(len(s))+ '. First char is = '+str(ch)) if (ch == ord('z')): # scrap 'z' response from CANUSB which means successful transmit.. pass elif (ch == ord('F')): # 'F' response print('Flags = '+str(s)) # tell us what it is. elif (ch == ord('t')): # 't' CAN message ParseCANopenMsg(s[1:]) # Parse message while fault active and no transmissions. elif (ch == 7): # Error occurred so BELL char received. ser.write('F\r'.encode('utf-8')) # Clear Error Flags by reading them print('Fault detected. Request Flags') s = '' else: # Accumulate CANUSB data for parsing. s = s + ch.decode("ascii") relayChanged = False # If a relay has changed then this flags it. if (rA != h.relay1A): # Test the HAL pin rA = h.relay1A # if it's changed then update our image. Update_Relays(1, rA) # Update the Byte Image too relayChanged = True # And flag it so a CANopen PDO will be sent. if (rB != h.relay1B): rB = h.relay1B Update_Relays(2, rB) relayChanged = True if (rC != h.relay1C): rC = h.relay1C Update_Relays(3, rC) relayChanged = True if (rD != h.relay1D): rD = h.relay1D Update_Relays(4, rD) relayChanged = True if (relayChanged): # Did a relay change? Send_PDO_2(RelayImage) # Update PDO. # Next check if LinuxCNC user has changed mister pump or fan PWM value if ((Mist_PWM != h.mister_pwm) or (Fan_PWM != h.fanspd_pwm)) Update_Mist_PWM(h.mister_pwm) # update both Update_Fan_PWM(h.fanspd_pwm) Send_PDO_1(Mist_PWM, Fan_PWM) # and then PDO message on change. # Now sleep before sending the update again. # Ideally here's where we'd set up a higher resolution timer to send the periodic # message but test the input messages more often. time.sleep(0.10) TimeTick += 1 if (TimeTick == 10): # 1 second TimeTick = 0; # Start over # Every 60 loops (seconds) send out an F to read error status MsgCount += 1 if MsgCount >= 60: MsgCount = 0 ser.write('F\r'.encode('utf-8')) # Clear Error Flags by reading them except KeyboardInterrupt: raise SystemExit
_______________________________________________ Emc-users mailing list Emc-users@lists.sourceforge.net https://lists.sourceforge.net/lists/listinfo/emc-users