Serial communication(MODBUS relay board) with PlanetCNC TNG – Part 4

Last part of this tutorial series will describe how to properly design the expression code for Modbus serial communication.

In previous part we made two expression functions. One was responsible for turning relay ON the other one for turning it OFF. Our main concern was just to make it work, to see that relay clicks and that was it. Now we will try to do it the right way, using an efficient, structured and optimized code design. And to add to the complexity, we will use relay board  as flood actuator, so that M8/M9 gcode command activates/deactivates relay 1 of  Modbus relay board.

Create dedicated expression file

In root folder of profile folder create new text file. Use descriptive name for better  file transparency and organization. I used Expr_Modbus_RelayBoard_8.txt.
Such file name gives all information: This is an expression file, related to Modbus communication and dedicated to the relay board:

sdfbg

 

#OnInit and #OnShutdown functions

This is a built in event function which is executed only once, that is when PlanetCNC TNG software is initialized and/or when settings configuration is confirmed. We can associate this as a machine startup event. This is an ideal timing for initialization of certain parameters and/or variables. Initialization sets initial conditions and therefore predicted behavior.

To put this in context, lets take a look at the serial_open() function. It is used to open and configure COM port, and it accepts six parameters:

serial_open(port, baudrate, bits, parity, stopbits, flowcontrol)

Implementation: 
serial_open("COM10", 9600, 8, 0, 1, 0);

If function is used on multiple locations in the program, tracking its parameter configuration can be time consuming and prone to mistakes.

One way of avoiding this is to use variables as function parameters, which  are created and initialized within the #OnInit function.  This is very convenient when COM port configuration is changed due to different device or hardware settings etc.  With such approach we only change the variable values once while function implementation is left unchanged.

#OnInit
port = "COM10";
baudrate = 9600;
bits = 8;
parity = 0;
stopbits = 1;
flowcontrol = 0;
_relay_flood = 4;
Implementation: 
serial_open(port, baudrate, bits, parity, stopbits, flowcontrol)

NOTE: For this function we used local variables(port, baudrate, bits ect…) and global variable(_relay_flood).  Local variables scope is limited to only this Expr_Modbus_RelayBoard_8.txt file. Local variables do not have any effect and cannot be in conflict with variables using same name outside of this file. Global variable’s scope is not limited to local expr file, but its value and modification can be used in global files of TNG software, these can be other expr files or gcode program files or script files.

#OnShutdown function is also a one time event, this is when PlanetCNC TNG software is closed. We can associate this as a machine shutdown event.

 

Functions for Modbus data preparation, sending/reading and verifying

Instead of creating a function which would perform data preparation, sending/reading and verification all together, we can create three separate functions, each having its own purpose. This gives our Expr_Modbus_Relay_8.txt file a much more structured and transparent look.

  • Function  for data preparation
  • Function for send/read of request/response data
  • Function for response data verification

 

Function for data preparation

M8 script code will be modified in such way so that desired relay will be activated once flood state is activated.

M8 script code uses (expr, ) gcode comment command, which executes any expression function available within TNG software.

(expr, exec())

exec() expression function accepts multiple arguments. Argument can be expression function, value, parameter etc.. All are executed in sequential order, from first to last. Once the exec() function is executed, its arguments can be passed to other functions. And this is exactly how it is used here.

First argument is #<_relay_flood>:  This is a global parameter that is created by the user, and it defines the relay number of modbus relay board. This parameter is defined within the #OnInit function of our Expr_Modbus_Relay_8.txt.  See chapter above for use of implementation. This argument will be passed to function #Modbus_Relay as argument 1.

Second argument is 1: This is the value of relay state, 1 means that we want to turn relay ON. This argument will be passed to function #Modbus_Relay as argument 2.

Third argument is “#Modbus_Relay”: This is an expression function call. Function #Modbus_Relay is located in Expr_Modbus_RelayBoard_8.txt file.

M8 gcode command finally sets the M8 gcode modal state.

 

Whole M8 script code is below:

(expr, exec(#<_relay_flood>, 1, "#Modbus_Relay")) 
M8

Useful aspect of such approach is that we can use it with multiple script files. Only difference is that we change the relay number and state value.

 

#Modbus_Relay function will be solely responsible for data preparation that is sent to the relay board.

In order that relay 1 will either turn ON/OFF, correctly prepared and  structured data needs to be sent to the relay board. Modbus ADU includes device address ID byte, function code byte, data bytes and CRC bytes.

Device address and function code are defined with private variables: .addr and .fc:

.addr = 1; 
.fc = 0x05;

Relay number and its state(ON/OFF) are defined with private variables .relay_number, .relay_state. As mentioned earlier, their values are passed as argument values of exec function executed from M8 script file:

.relay_number = .arg1;
.relay_state = .arg2;

As per boards user manual, device response data length is 8 bytes. This value is defined with variable .resp_size. This variable is important for response data verification.

.resp_size = 8;

5th byte of the ADU accepts 0x00 for relay OFF or 0xFF for relay ON. Since .relay_state’s value is a passed as an argument value where we only use values 0 or 1, line below converts 0 to 0x00 or 1 to 0xFF:

if(.relay_state, .relay_state = 0xFF, .relay_state = 0x00);

New array handle is created where the ADU data is set:

.payload = array_new();

Array data consists of device address ID byte, function code byte, zero byte, relay number byte, relay state byte and zero byte. CRC bytes will be appended in the #Modbus_Write_ReadData function before sending the data.

array_setdata(.payload, 0, .addr, .fc, 0x00, .relay_number-1, .relay_state, 0x00);

Now that our request data (.payload array) is more or less set, it will be same as before, passed as argument 1 to the #Modbus_Write_ReadData function.

.resp_size will be passed as argument 2.

.rc = exec(.payload, .resp_size, '#Modbus_Write_ReadData');

 

Whole #Modbus_relay code is below:

#Modbus_Relay
debug('BEGIN #Modbus_Relay');

.relay_number = .arg1;
.relay_state = .arg2;

.addr = 1;
.fc = 0x05;
.resp_size = 8;

if(.relay_state, .relay_state = 0xFF, .relay_state = 0x00);
.payload = array_new();
array_setdata(.payload, 0, .addr, .fc, 0x00, .relay_number-1, .relay_state, 0x00);
.rc = exec(.payload, .resp_size, '#Modbus_Write_ReadData');
if(.rc != 0, exec(array_delete(.payload), return(-1)));
array_delete(.payload);
debug('END #Modbus_Relay');

NOTE: For this function we used private variables(.relay_number, .relay_state, .addr ect…) and global variable(_relay_flood).  Private variables visibility scope is limited by the expression function where it is used. Private variables do not have any effect and cannot be in conflict with variables using same name outside of expression function where they are used.

 

Function for send/read of request/response data

This function will be responsible for sending the request data and reading the response data over serial port.

Request data and size value are passed as argumens 1 and 2 from #Modbus_Relay function:

.payload = .arg1;
.resp_size = .arg2;

COM port is initialized and opened:

.rc = serial_open(port, baudrate, bits, parity, stopbits, flowcontrol);

Address ID and function code bytes are saved as private variables .addr and .fc. These variables are important for response data verification:

.addr = array_getdata(.payload, 0); 
.fc = array_getdata(.payload, 1);

Response data is set to .payload array and passed as an argument together with .addr, .fc and .resp_size variables to the #Check function.

serial_readarray(port, .payload, 150);
.rc = exec(.payload, .addr, .fc, .resp_size, '#Check');

Whole #Modbus_Write_ReadData code is below:

#Modbus_Write_ReadData

debug(str(" ",2),'BEGIN #Modbus_Write_ReadData');

.payload = .arg1;
.resp_size = .arg2;

;COM port init
.rc = serial_open(port, baudrate, bits, parity, stopbits, flowcontrol);
if(.rc != 0, exec(debug('COM port not available'), return(-1)));

.addr = array_getdata(.payload, 0);
.fc = array_getdata(.payload, 1);

;crc16 calculation 
.crc = array_crc16(.payload, 0, -1);

;set array data with crc
array_setdata16(.payload, -1, .crc);

;send array data 
serial_writearray(port, .payload);

array_clear(.payload);

;--------------------------------------------------------------------------
;read array data 
serial_readarray(port, .payload, 150);

;check returned array data
.rc = exec(.payload, .addr, .fc, .resp_size, '#Check');
if (.rc, exec(debug('response check failed with error code: ', .rc), serial_close(port), return(-1)));

serial_close(port);
debug(str(" ",2),'END #Modbus_Write_ReadData');

return(0);

 

Function for response data verification

This function is responsible for response data verification. Verification process basically compares values of address ID, function code, response size and CRC numbers of request and response data.

Response data, address ID(expected), function code(expected) and size(expected) are passed as arguments from #Modbus_Write_ReadData function and saved as private variables:

.hnd = .arg1;
.addr = .arg2;
.fc = .arg3;
.size = .arg4;

And then compared with actual response address ID, function code and size data:

if (.size != array_size(.hnd), return(-1));

if(.addr != array_getdata(.hnd, 0), return(-3));

if(.fc != array_getdata(.hnd, 1), return(-4));

Response CRC value is calculated from response data (without last two CRC bytes) and compared with response CRC bytes:

.crc1 = array_getdata16(.hnd, .size - 2);

.crc2 = array_crc16(.hnd, 0, .size - 2);

 if(.crc1 != .crc2, return(-2)); debug(str(" ",4),'CRC OK: ', .crc2); 

Whole #Check code is below:

#Check
debug(str(" ",3),'BEGIN #Check');

.hnd = .arg1;
.addr = .arg2; 
.fc = .arg3; 
.size = .arg4;

;compare size with array size
if (.size != array_size(.hnd), return(-1));

;read crc1 from data 
.crc1 = array_getdata16(.hnd, .size - 2);

;calc crc2 from data
.crc2 = array_crc16(.hnd, 0, .size - 2);

if(.crc1 != .crc2, return(-2));
debug(str(" ",4),'CRC OK: ', .crc2);

;compare addr with data from array
if(.addr != array_getdata(.hnd, 0), return(-3));
debug(str(" ",4),'Address OK: ', .addr);

;compare fc with data from array 
if(.fc != array_getdata(.hnd, 1), return(-4));
debug(str(" ",4),'Function Code OK: ', .fc);

debug(str(" ",3),'END #Check');

return(0);

Beautiful thing about such code design is that if we need to modify script code for M9 and M18 gcode files(to turn the relay OFF), we only do this:

;M18 code:
o<chk> if[NOT[EXISTS[#<pvalue>]]]
#<pvalue> = 0
o<chk> endif

(expr, exec(#<_relay_flood>, #<pvalue>, "#Modbus_Relay"))

M18 P#<pvalue>
;M9 code:
(expr, exec(#<_relay_flood>, 0, "#Modbus_Relay"))
M9

Functions #Modbus_Relay, #Modbus_Write_ReadData and #check will do the rest.

 

Below is visual representation of argument passing between files and/or functions:

 

 

 

 

Serial communication(MODBUS relay board) with PlanetCNC TNG – Part 3

In this part, we will use and configure Expr.txt file so that we will be able to control relay board via serial communication using Modbus protocol. Expression array and serial functions described in first two parts will be used in order to achieve this.

 

Modbus supports communication to and from multiple devices connected to the same wire of serial communication line.

In this tutorial we will use MODBUS RTU frame format.

Modbus “frame” consists of an Application Data Unit (ADU), which encapsulates a Protocol Data Unit (PDU):

ADU = Address + PDU + Error check

PDU = Function code + Data

If we put the table above in context, with TNG we will send suitable ADU frame to our slave device(relay board) in order that we will successfully control it.

By using array and serial expression functions, we will manipulate data in such way, that each byte will be at correct value and position of transmitted data.

We will use boards user manual for reference regarding the suitable data.

For more about the Modbus protocol you can read on its official site:
https://www.modbus.org/docs/Modbus_Application_Protocol_V1_1b3.pdf

 

Hardware used

As mentioned earlier, we will use Modbus RTU frame format, which by definition uses RS-485 electrical interface – differential signalling over twisted pair wire.

Relay board uses RS-485 communication interface and by that we need to make sure that differential line is terminated using termination resistor. In our case this is done by placing a jumper in proper position(on board). If multiple boards are used in daisy chain fashion, only last board needs to terminate the differential line. By doing this we prevent signal reflection and therefore data corruption.
This board will be our Modbus slave device.

As a master device, we will use PlanetCNC TNG software, which will send and receive data trough a PC’s COM port. My PC does not have COM port, let alone a RS-485 interface. This is why I will use USB to RS-485 converter. Such device is classified as a USB CDC(communication device class) device and emulates COM port of my computer.

Under Device Manager we can check COM ports ID as “COM11”:

RS-485 converter and relay board are connected using twisted pair wire.

 

Identifying relay board

Since this board uses Modbus protocol on top of serial communication(UART), we need to obtain  board’s serial communication parameters.

Namely, we need boards address ID and baudrate value.  Address ID is in domain of Modbus and it is an important piece of information, since it defines which slave device will respond to our transmitted data. In most cases, Modbus devices use DIP switches for address ID configuration or have some other means for configuring. This relay board does not use DIP switches and we will need to identify the device with different approach.

For start, we need some information about COM port that we intend to use. Specifically,  COM ports name, which is important parameter of the serial_open() command..

Type into the MDI:

=serial_list()

Output window will print the returned information:

COM ports name is “COM11”.

Using serial_open() command, we can check if this port is ready for transmission of data. We will use the most common serial port parameter setting: 9600–8-N-1 (9600 bits per second, eight (8) data bits, no (N) parity bit, and one (1) stop bit)

=serial_open("COM11",9600,8,0,1,0)
;baudrate at 9600
;8 data bits
;1 stop bits
;none parity
;no flowcontrol

Output window will print the returned information. Remember, this function’s return value gives 0, when port is successfully opened.

Great, now that this is settled, we can close it and we can prepare the data which will be sent to the relay board in order that we obtain board’s address ID.

 

Request message and response message data for reading address ID of relay board(as per user manual):

Request message HEX: 0x00 0x03 0x00 0x00 0x00 0x01 0x85 0xDB 
  First byte [0x00] is slave device address ID. But since we have no way of knowing it(board does not use DIP switches), we can use reserved value of "00".
    This means that all devices(or only one) on the bus will receive and acknowledge this data and give suitable response. 
 
  Second byte [0x03] is function code
    Function code in Modbus is a specific code used in a Modbus PDU to tell the Modbus slave device what type of memory (i.e. holding registers, input coils, etc)
    to access and what action to perform on that memory (i.e. reading or writing). This PDU uses function code 3, which is used for reading holding registers.
    Holding registers are the most universal 16-bit register, may be read or written, and may be used for a variety of things including inputs, outputs, configuration data, or any requirement for "holding" data.
  
  Bytes 3 to 6 [0x00 0x00 0x00 0x01] are starting register address and the number of registers we want to read. 
  
  Last two bytes [0x85 0xDB] are CRC 16-bit calculated value. CRC 16-bit value is first calculated and then appended to the end position of array.

Response message HEX: Expected return data should be 0x00, 0x03, 0x02, 0x00, 0x01, 0x44, 0x44. 
5th byte of the return data is boards address ID -> HEX: 0x01 ; DEC: 1

Whole code for reading board’s address ID:

=.payload = array_new()
array_setdata(.payload, 0, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01)
serial_open("COM11", 9600, 8, 0, 1, 0)
.crc = array_crc16(.payload, 0, -1)
array_setdata16(.payload, -1, .crc)
serial_writearray("COM11", .payload)
array_clear(.payload)
serial_readarray("COM11", .payload, 150)
serial_close("COM11")
.rc = array_getdata(.payload, 4)
print('Address ID:', .rc)
array_delete(.payload)
Description of used functions: 
=.payload = array_new()  -> new array with handle .payload is created
array_setdata(.payload, 0, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01) -> we set data to .payload array
serial_open("COM11", 9600, 8, 0, 1, 0) -> opens COM port and sets parameters
.crc = array_crc16(.payload, 0, -1) -> calculates CRC 16-bit value of .payload array data
array_setdata16(.payload, -1, .crc) -> we append 16-bit CRC data at the end position of .payload array
serial_writearray("COM11", .payload) -> sends .payload array data via COM11 port
array_clear(.payload) -> we clear .payload array data
serial_readarray("COM11", .payload, 150) -> we read the response data from relay board and set it to .payload array
serial_close("COM11") -> closes COM11 port
.rc = array_getdata(.payload, 4) -> we set the 5th byte (zero type numbering is used) of received data into .rc variable
print('Address ID:', .rc) -> prints .rc value
array_delete(.payload) -> deletes .payload array handle

 

Relay On/Off control

Now that we know board address ID, and since this is a relay board, lets control one of the relays on/off.
Request message and response message data for relay No. 1 ON(as per user manual):

Request message HEX: 0x01 0x05 0x00 0x00 0xFF 0x00 CRC-byte CRC-byte 
  First byte [0x01] is slave device address ID. As we know, address ID of relay board is 1.

  Second byte [0x05] is function code. Function code in Modbus is a specific code used in a Modbus PDU to tell the Modbus slave device what type of memory (i.e. holding registers, input coils, etc) to access
    and what action to perform on that memory (i.e. reading or writing). This PDU uses function code 5, which is used to write a single output to either ON or OFF in a remote device.
 
  Bytes 3 to 6 [0x00 0x00 0xFF 0x00] are coil address(first relay at 0...) and the constant value for ON 0xFF 0x00, and a constant value for OFF 0x00 0x00. 
 
  Last two bytes are CRC 16-bit calculated value. These two bytes are not set together with first six bytes. CRC 16-bit value is first calculated and then appended to the end position of array.

Whole code for turning relay 1 ON:

=.payload = array_new()
array_setdata(.payload, 0, 0x01, 0x05, 0x00, 0x00, 0xFF, 0x00)
serial_open("COM11", 9600, 8, 0, 1, 0)
.crc = array_crc16(.payload, 0, -1)
array_setdata16(.payload, -1, .crc)
serial_writearray("COM11", .payload)
array_clear(.payload)
serial_readarray("COM11", .payload, 150)
serial_close("COM11")
.rc = array_getdata(.payload, 4)
array_printdata(.payload)
print('Address ID:', .rc)
array_delete(.payload)

Whole code for turning relay 1 OFF:

=.payload = array_new()
array_setdata(.payload, 0, 0x01, 0x05, 0x00, 0x00, 0x00, 0x00) 
serial_open("COM11", 9600, 8, 0, 1, 0) 
.crc = array_crc16(.payload, 0, -1) 
array_setdata16(.payload, -1, .crc) 
serial_writearray("COM11", .payload) 
array_clear(.payload) 
serial_readarray("COM11", .payload, 150) 
serial_close("COM11") 
.rc = array_getdata(.payload, 4) 
array_printdata(.payload) print('Address ID:', .rc) 
array_delete(.payload)

 

Implementing Modbus communication within TNG software

Executing these commands from MDI is nice for testing, but not really practical in real application. Best way to implement MODBUS communication within the TNG would be to write an expression function which would inhibit all necessary code for master modbus <-> slave communication.

To read more about Expr file and its properties, you can follow link below:
CNC machine semaphore application using Expressions (Expr.txt)

 

 

Create new expr file and name it e.g. Expr_MB_relay.txt. We can include two expression functions in this file, one for relay ON and other for relay OFF.
Lets name them #RelayControl_ON and #RelayControl_OFF.  Include the corresponding code for each function.

The code of Expr_MB_relay.txt file would look like this:

#RelayControl_ON
.payload = array_new();
array_setdata(.payload, 0, 0x01, 0x05, 0x00, 0x00, 0xFF, 0x00);
serial_open("COM11", 9600, 8, 0, 1, 0); 
.crc = array_crc16(.payload, 0, -1); 
array_setdata16(.payload, -1, .crc); 
serial_writearray("COM11", .payload); 
array_clear(.payload); 
serial_readarray("COM11", .payload, 150); 
serial_close("COM11"); 
.rc = array_getdata(.payload, 4); 
array_printdata(.payload);
print('Address ID:', .rc); 
array_delete(.payload);

#RelayControl_OFF
.payload = array_new();
array_setdata(.payload, 0, 0x01, 0x05, 0x00, 0x00, 0x00, 0x00); 
serial_open("COM11", 9600, 8, 0, 1, 0); 
.crc = array_crc16(.payload, 0, -1); 
array_setdata16(.payload, -1, .crc); 
array_writearray("COM11", .payload); 
array_clear(.payload); 
serial_readarray("COM11", .payload, 150); 
serial_close("COM11"); 
.rc = array_getdata(.payload, 4); 
array_printdata(.payload)
print('Address ID:', .rc); 
array_delete(.payload);

 

You can run expression functions either from .NC file, script file, button file, M code file, expression file etc..

Now, if you would like to execute these two functions within a .NC program or gcode script file, you can use (expr, ) comment function:

%
.
.
.
G00 X0 Y20
G01 Z5
(expr,exec("#RelayControl_ON"))
G01 Z-1
G00 Z5
(expr,exec("#RelayControl_OFF"))
.
.
.
%

 

While both examples of Modbus functions described above would work, it would not be a good example of how such communication should be handled.

For each request data sent, we also need to read the response data. Response is an automated process from slave device, and we can use the response data in order that we can verify that request procedure has been acknowledged, processed and correctly executed. In next part we will describe how to correctly write more reliable expression code for Modbus communication.

 

Continued at part 4:
https://planet-cnc.com/serial-communication-with-planetcnc-tng-part-4/

 

 

 

 

 

 

Serial communication(MODBUS relay board) with PlanetCNC TNG – Part 2

We will continue with expression functions used for serial communication. In first part we described array expression functions, which are used to prepare the data for serial communication. Now we will describe serial expression functions that are responsible for actually sending the data.

serial_list()    - Displays all available COM ports of computer. 

serial_info(port) - Displays additional info of COM port of computer
  port - COM port name. Use string type ; e.g. "COM3"

serial_open(port,baudrate,bits,parity,stopbits,flowcontrol) - Opens serial COM port channel and sets serial communication parameters
  port - COM port name. Use string type ; e.g. "COM3"
  baudrate - baudrate value, e.g. 4800bd ; 9600bd ; 19200bd ; 115200bd 
  bits - data bits value, 5 ; 6 ; 7 ; 8 ; 9 data bits
  parity - parity value, 0 - no parity ; 1 - odd parity ; 2 - even parity
  stopbits - stop bit value , 0 - no stop bit ; 1 - one stop bit ; 2 - two stop bits
  flowcontrol - flow control value; 0 - no flow control ; 1 - flow control enabled
  Return value: Returned value is zero when port is successfully opened. Returned value is 1 when port has not been opened. Returned value is integer number. 

serial_config(port,baudrate,bits,parity,stopbits,flowcontrol) - Configures serial COM port channel communication parameters
  port - COM port name. Use string type ; e.g. "COM3" 
  baudrate - baudrate value, e.g. 4800bd ; 9600bd ; 19200bd ; 115200bd 
  bits - data bits value, 5 ; 6 ; 7 ; 8 ; 9 data bits 
  parity - parity value, 0 - no parity ; 1 - odd parity ; 2 - even parity 
  stopbits - stop bit value , 0 - no stop bit ; 1 - one stop bit ; 2 - two stop bits 
  flowcontrol - flow control value; 0 - no flow control ; 1 - flow control enabled
  Return value: Returned value is zero when port is successfully configured. Returned value is 1 when port has not been configured. Returned value is integer number. 

serial_close(port) - Closes serial COM port channel
  port - COM port name. Use string type ; e.g. "COM3" 
   Return value: Returned value is zero when port is successfully closed. Returned value is 1 when port has not been successfully closed. Returned value is integer number. 

serial_write(port,str) - Sends string type data trough serial COM port
  port - COM port name. Use string type ; e.g. "COM3" 
  str - String type data that will be sent ; String data should be enveloped in quotes
  Returned value: Return value is data size. Returned value is integer number.
  
serial_writedata(port,data) - Sends byte type data trough serial COM port
  port - COM port name. Use string type ; e.g. "COM3" 
  data - Byte type data that will be sent
  Returned value: Return value is data size. Returned value is integer number.

serial_writearray(port,hnd) - Sends array data trough serial COM port
  port - COM port name. Use string type ; e.g. "COM3" 
  hnd  - array handle  
  Return value: Return value is data size. Returned value is integer number.

serial_read(port,size,timeout(optional)) - Reads data trough serial COM port and prints it at the output window. 
  port - COM port name. Use string type ; e.g. "COM3" 
  size - data size limit to be read; 0 -> no limit in data size, 256 -> Max value of limit 
  timeout - timeout value in milliseconds. After time has elapsed, COM port will not read any incoming data. This argument is optional. 
  Return value: Return value is data size. Returned value is integer number.

serial_readdata(port,size,timeout(optional)) - Reads data trough serial COM port and prints it at the output window. 
  port - COM port name. Use string type ; e.g. "COM3" 
  size - data size limit to be read; 0 -> no limit in data size, 256 -> Max value of limit
  timeout - timeout value in milliseconds. After time has elapsed, COM port will not read any incoming data. This argument is optional. 
  Returned value: Return value is data size. Returned value is integer number.

serial_readarray(port,hnd,size,timeout) - Reads data trough serial COM port and saves it to array
  port - COM port name. Use string type ; e.g. "COM3" 
  hnd - array handle
  size - data size limit to be read; 0 -> no limit in data size, 256 -> Max value of limit
  timeout - timeout value in milliseconds. After time has elapsed, COM port will not read any incoming data. 
  Return value: Return value is data size. Returned value is integer number.

Now its time for a simple serial communication example. For this we will use two COM ports, terminal program and PlanetCNC TNG software.  Such approach is suitable for getting comfortable with  array and serial functions on a somewhat real communication case.

 

First lets check for available COM ports of our computer.  If your computer does not offer any physical serial COM port, you can use virtual COM port software.

Under Control Panel/Device Manager/Ports(COM&LPT) you can easily check available ports:

 

Same can be done in TNG software using a serial_info() command, which prints the following info:

 

I will use the COM4 and COM5 ports for communication between the TNG software and terminal program.

TNG software will be “attached” to COM5 and terminal program will be “attached” to COM4. In order that the communication channel between both ports is established, we need to connected COM4 and COM5 with each other.  Transmit line of COM4 is connected at the receive input of COM5, and transmit line of COM5 is connected at the receive input of COM4. So anything that will be sent at the COM5 end will arrive at the COM4 receiving end and vice versa.

 

Terminal program configuration:

HTerm terminal program is attached to COM4 port, communication parameters are set:

– baudrate at 9600

– 8 data bits

– 1 stop bits

-none parity

– no flowcontrol

 

Since both devices on serial bus need to be configured the same, we will use same port configuration in TNG using function: serial_open():

serial_open(“COM5”,9600,8,0,1,0)

It is mandatory that afters finished transmission, port is closed using serial_close() function.

For start, we just want to say hello to Hterm program, best way to do it is to send string data using serial_write() function.

So, our short MDI program would look like this:

 

At the Hterm’s end we receive the message:

 

It would only be appropriate that we answer the call and reply with: Hello PlanetCNC!

However, we need to make sure that PlanetCNC is ready to receive the call and is in “listening” mode, this is done using a serial_read() function:

Timeout value will open the listening window for 4 seconds, which gives more than enough time for Hterm to send the message:

Since this function only reads and prints the string data, we should be able to see the message in our output window:

 

But usually devices don’t talk to each other using string data for saying hello, but they use data in some structured manner.

This will all make more sense in the next chapter where we will use MODBUS to communicate with relay board.

 

Last example will describe a short and simple correspondence where we will send array data and set received data into array.

-new array with handle name hnd is created:

=hnd =array_new()

-arraydata is set with array_setdata() function:

array_setdata(hnd,0,0xA1,0xB2,0xC3)

-serial port is opened and configured:

serial_open("COM5", 9600, 8, 0, 1, 0)

-array data is sent:

serial_writearray("COM5",hnd)

-array is cleared and ready for incoming data:

array_clear(hnd)

-data is read and set to array

serial_readarray("COM5",hnd,5000)

port is closed and array is deleted, data is printed:

serial_close("COM5")
array_printdata(hnd)
array_delete(hnd)

Hterm received data:

Sent data from terminal:

 

Output window displays the contents of hnd array:

 

Continued at part 3:
https://planet-cnc.com/serial-communication-modbus-relay-board-with-planetcnc-tng-part-3/

 

 

 

 

 

 

 

 

 

Serial communication(MODBUS relay board) with PlanetCNC TNG – Part 1

This series of tutorials will try to explain and demonstrate how to use PlanetCNC TNG software for the means of serial communication.

The final goal would be a successful control of external MODBUS input/output relay board, but, to achieve this, we need to start with the basics.

 

PlanetCNC TNG software possesses all the tools for serial communication with external equipment. PlanetCNC motion controllers also support hardware UART serial interface, but this will not be a subject of this tutorial, at least not in this part.

While serial communication comes with its own rules, requirements and data framing, you would not need to worry for any of that. TNG already provides all required commands that implement serial specification so that you can just relax and read/send the data on command so to speak.

Since serial communication is associated with relatively large amount of data transfer and to make manipulation of this data easier, there is a special group of functions with a prefix array.

 

Array expression functions:

These functions are important tool for data manipulation, since serial communication requires correct data flow in a sense of sending/reading  structured data to/from designated  addresses of external devices. In short, these functions will help you to prepare the data you wish to send.

array_new() - Creates new array. Returns handle to newly created array. 
 Return value: Handle of newly created array. Handle is integer number.

array_delete(hnd) - Deletes array referenced by handle. 
  hnd - array handle
  Return value: Returns handle of the array that was just deleted, or zero if array does not exist. Returned value is integer number.  

array_clear(hnd) - Clears array data. 
  hnd - array handle
  Return value: Returns handle of the array that was just cleared, or zero if array does not exist. Returned value is integer number.

array_isvalid(hnd) - Checks if array with this handle is valid.
  hnd - array handle
  Return value: Returns value 1 for valid array, returns value 0 for invalid array. Returned value is integer number.

array_size(hnd) - Returns array data size. 
  hnd - array handle
  Return value: Returns array size. Returned value is integer number.
array_setstring(hnd, str) - Replaces array with string. Old data is cleared, array now contains only string data.  
  hnd - array handle 
  str - string data
  Return value: Returns array size. Returned value is integer number.
array_printstring(hnd) - Prints string of specified array. 
  hnd - array handle
  Return value: Returns array size. Returned value is integer number.
array_setdata(hnd,pos,data) - Sets byte data to array. Old data at position is cleared, array now contains new data. If new data is set at the end of array, data is appended.  
  hnd - array handle
  pos - data position ; 0: -start position of array -1:-end position of array ; Other: 1,2,3... (zero based numbering is used (1st position has number zero, 2nd number one etc…) )
  data - byte data
  Return value: Returns array size. Returned value is integer number.
array_setdata16(hnd,pos,data) - Sets two byte(16-bit) data to array. Old data at position is cleared, array now contains new data. If new data is set at the end of array, data is appended. 
  hnd - array handle
  pos - set data position ; 0: -start of array -1:-end of array ; Other: 1,2,3... (zero based numbering is used (1st position has number zero, 2nd number one etc…) )
  data - two byte data
  Return value: Returns array size. Returned value is integer number.
array_setdata32(hnd,pos,data) - Sets four byte(32-bit) data to array. Old data at position is cleared, array now contains new data. If new data is set at the end of array, data is appended. 
  hnd - array handle 
  pos - set data position ; 0: -start of array ; -1:-end of array ; Other: 1,2,3... (zero based numbering is used (1st position has number zero, 2nd number one etc…) ) 
  data - four byte data
  Return value: Returns array size. Returned value is integer number.
array_printdata(hnd) - Prints array data. 
  hnd - array handle
  Return value: Returns array size. Returned value is integer number.
array_crc16(hnd,pos,len) - Calculates 16-bit CRC number.
  hnd - array handle
  pos - CRC data start position ; 0: -start of array; Other: 1,2,3...zero based numbering is used (1st position has number zero, 2nd number one etc…) )
  len - CRC data length value ; -1:-end of array ; Other: 1,2,3...
  Return value: 16-bit CRC number. Value is integer number
array_crc32(hnd,pos,len) - Calculates 32-bit CRC number.
 hnd - array handle
 pos - CRC data start position ; 0: -start of array ; Other: 1,2,3...(zero based numbering is used (1st position has number zero, 2nd number one etc…) )
 len - CRC data length value ; -1:-end of array ; Other: 1,2,3... 
 Return value: 32-bit CRC number. Value is integer number
array_getdata(hnd,pos) - Returns byte data from array position. 
  hnd - array handle  
  pos - array data position ; 0: -first position of array ; Other: 1,2,3... (zero based numbering is used (1st position has number zero, 2nd number one etc…) )
  Return value: Return value is byte data. Value is integer number.
array_getdata16(hnd,pos) - Returns two byte data from array position.
  hnd - array handle 
  pos - array data position ; 0: -first position of array ; Other: 1,2,3... (zero based numbering is used (1st position has number zero, 2nd number one etc…) )
  Return value: Return value is two byte data. Value is integer number.
array_getdata32(hnd,pos) - Returns four byte data from array position.
  hnd - array handle 
  pos - array data position ; 0: -first position of array ; Other: 1,2,3... (zero based numbering is used (1st position has number zero, 2nd number one etc…) )
  Return value: Return value is four byte data. Value is integer number.
array_copy(hnd,from,pos,start,len) - Copies data from one array to another. 
  hnd - Destination array handle. Array to which we copy the data 
  from - Source array handle. Array from which we copy the data 
  pos - Destination position of copied data ; 0: -first position of array; -1: -end position ; Other: 1,2,3... (zero based numbering is used (1st position has number zero, 2nd number one etc…) )
        If data exist it will be replaced with new data for its length. If position is -1(end position of array), data will be appended. 
  start - Source start position of data to be copied ; 0: -first position of array ; Other: 1,2,3... (zero based numbering is used (1st position has number zero, 2nd number one etc…) ) 
  len - Length of the copied data 
  Return value: Return value is destination array size. Returned value is integer number.

array_resize(hnd,newsize) - Resizes array. 
  hnd - array handle of to be resized array
  newsize - new array size. When resizing, data can be added or removed. Negative number will preappend/remove the data at the start position of array and positive number will append/remove the data at the end position of array
  Return value: Returned value is new array size. Returned value is integer number. 

array_insert(hnd,pos,len) - Inserts byte data filled with zeros to existing array.
  hnd - array handle 
  pos - position of inserted data ; 0: -first position of array -1:-last position ; Other: 1,2,3... (zero based numbering is used (1st position has number zero, 2nd number one etc…) )
  len - length of inserted data
  Return value: Return value is array size. Returned value is integer number.

array_remove(hnd,pos,len)- Removes byte data from existing array 
  hnd - array handle
  pos - position where we want to remove the data ; 0: -first position of array ; Other: 1,2,3... (zero based numbering is used (1st position has number zero, 2nd number one etc…) )
  len - length of removed data
  Return value: Return value is array size. Returned value is integer number.

Example:

We would like to set a string data type(sequence of characters), to array with a handle name hnd. String data would be: PlanetCNC

First we need to create a array. Then we need to assign it a handle with a name hnd. Don’t forget to delete this array at the end of its use.

hnd = array_new()

In this array we need to copy string “PlanetCNC”:

array_setstring(hnd,"PlanetCNC")

We can  check the arrays data if we print its value in the output window:

array_printstring(hnd)

It is mandatory to delete the array handler at the end of its use:

array_delete(hnd)

You can use MDI input field to execute functions above. To display the expression evaluation use output window:

 

Let’s try to do this same example in a slightly different way. Since we usually want to communicate with different devices that usually provide the data in e.g. HEX type, we will set ASCII HEX representation values of the characters and achieve the same result.

To find the ASCII HEX values, use ASCII table for reference or online text to HEX converter.

The main difference is that instead of using array_setstring(), we used array_setdata() function:

Since we used HEX representation values of string PlanetCNC, array_printstring() will still print PlanetCNC:

 

To our string of characters “PlanetCNC”, we want to add HEX representation of characters ” rules!” Final text would be “PlanetCNC rules!” 

 

Here, is where we append additional data(” rules!”) to the array. Second argument “-1” defines the location of newly added data. -1 means it will be added at the end of the array.

 

With array_printdata(), we print all HEX values of array:

 

If we want to obtain desired data from a specific array location, we can use the array_getdata() function. Say we want to read the fourth byte of hnd array:

Output window will print the value of fourth byte. Decimal value 110 is equal in HEX 0x6E.

 

When sending data, there is a possibility of data corruption or data change. Receiver would somehow need to validate that the received data is indeed the one that sender intended to send.

For this purpose CRC code is used. CRC stands for “Cyclic redundancy check”, and it represents our data payload, in a form of calculated/encoded number, that is appended at the end of our data message. This way receiver can calculate the CRC of  received payload data and compare received CRC with the one it calculates on its own.

If everything is ok, both CRC numbers should be identical.

 

To generate 16-bit CRC value of the hnd array data, we can use array_crc16() function and save CRC number in the rc variable:

hnd represents the array that CRC16 function will calculate CRC number of

0 represents the start position of hnd array data

-1 represents the length or width of data to be calculated. Since -1  represents the end position of array, this means that we will calculate all data of hnd array.

rc is the variable to which we will save our CRC number.

 

Now we just need to append the 16-bit CRC value to our hnd  array and data is ready:

Since we want to append the two byte data to our array, we need to use array_setdata16() function.

hnd represents array that we will append data to

-1 represents the position of array where we will append data. -1 means that we will append it at the end position of array

rc is the variable that contains calculated CRC number, and it represents the appending data.

 

 

In the next part of this tutorial series we will describe function group for serial communication.

Serial expression functions:

serial_list - 
serial_info -

serial_open - 
serial_config - 
serial_close -

serial_write - 
serial_writedata - 
serial_writearray -

serial_read - 
serial_readdata - 
serial_readarray -

Continued at part 2:
https://planet-cnc.com/serial-communication-with-planetcnc-tng-part-2/

 

CNC machine semaphore application, using expressions (Expr.txt) – Part 3

At this step of the process we pretty much defined everything needed for semaphore application and we can begin writing the program.

Diagram of program

First we will illustrate simple program diagram(flowchart). Diagram is very helpful, because it visually  illustrates the program flow(in this case state machine) which we can use as a reference when designing program structure.

Diagram for state machine of our semaphore application:

 

Each state is able to transition to other state, which was defined earlier in the process. All transitions are conditioned, meaning in order for transition to happen, condition needs to be fulfilled.

So we will now describe each states possible transitions as also transition conditions.

It can transition to:

Run state
State transition condition: Start program

Estop state
State transition condition: Estop activated

Corresponding IDLE state parameter:

We can evaluate Idle state by combining three parameters.  Idle state is basically inverted run state, in a sense that no program or any other task is being executed. For this  we can use run state parameter !_hw_run. In order that we are 100% sure that system is not executing any remaining commands from the buffer, we also check _hw_buffempty parameter. We also need to be sure that Estop mode is not active with !_hw_estop.

(_hw_buffempty && !_hw_run) && !_hw_estop

 

Flowcharts:

 

#OnInit:

Below is a flowchart for #OnInit. #OnInit is a built in expression function, which is executed when TNG software is launched/initialized. It only has one iteration.  It is perfect for initialisation code of our semaphore.

At the beginning we define state decimal values and conditions for Alarm, Estop and Idle states.  Only these three states because, when system is powered up, it can only take up states Estop(button pressed), Alarm(alarm before system shutdown was not reset) or Idle(all is OK). So, depending on the conditions met, OnInit will set the state which will be then recognized in the #Loop function.

Snippet of code for flowchart above is following:

#OnInit
state_estop = 0; 
state_idle  = 1; 
state_pause = 2; 
state_run   = 3;  
state_alarm = 4;
from_init = 0;


cleared_not = !_alarm_cleared;
if(cleared_not,exec(state=state_alarm,from_init=1,msg('INIT: Please Reset Error')));

estop = _hw_estop && _hw_idle; 
if(estop, state=state_estop);

idle  = (_hw_buffempty && !_hw_run) && !_hw_estop && _alarm_cleared; 
if(idle, state=state_idle);

 

#Loop:

#Loop function’s role is that is constantly checks and recognizes active state of the system.

If we, for the sake of this tutorial say, that this will be #Loop’s first execution and iteration, and since we just launched the TNG system, somebody pressed the Estop button before system shutdown (many CNC machines actually recommend to activate Estop button before system shutdown). So, #OnInit recognized and set as state Estop state.

#Loop then checks for active state and executes corresponding procedure. In our case, Estop state -> #StateEstop

Snippet of code for flowchart above is following:

#Loop 
;Every 83ms
if (state == state_estop, exec('#StateEStop'));
if (state == state_idle, exec('#StateIdle'));
if (state == state_run, exec('#StateRun'));
if (state == state_pause, exec('#StatePause'));
if (state == state_alarm, exec('#StateAlarm'));

 

#StateEstop flowchart is below:

At the beginning we have two safety instances. First one checks if state is indeed StateEstop, second one checks if last valid state was not StateEstop. This is important since we execute the #StateEstopInit procedure only at first and initial state change.

#StateEstopInit procedure is where we actually set the new last state and activate corresponding light on the semaphore.

With _state_last = state_estop command we set new last state. _extout1|5 = 1  will activate Red light on our ExtInOut board. All other lights are in this state at 0.

 

Further we check for any state transition conditions being met. This will be continuously evaluated until Estop button is not released. So if Estop button is released and error has been reset, Idle state conditions are met and we execute procedure #StateEstopEnd and set new state, state_idle. And then we return back to #Loop, where Idle state is recognized and #StateIdle is executed.

Which state transitions conditions need to be checked within each state, is defined in our state machine at the beginning of this tutorial. 

 

Snippet of code for flowchart above is following:

#StateEStop
if (state != state_estop, return(0));
if (_state_last != state_estop, exec('#StateEStopInit'));

;Here we check for state transition conditions

idle = (_hw_buffempty && !_hw_run) && !_hw_estop; 
if(idle, exec('#StateEStopEnd', state = state_idle, return()));

if((!_alarm_cleared && !_hw_estop), exec('#StateEStopEnd', state = state_alarm, return()));

#StateEStopInit
_state_last = state_estop;
print('EStop: ',estop);
_extout1|5 = 1;
_extout1|1 = 0;
_extout1|2 = 0;
_extout1|3 = 0;

#StateEStopEnd

 

#StateRun flowchart is below:

At the beginning we have two safety instances. First one checks if state is indeed StateRun, second one checks if last valid state was not StateRun. This is important since we execute the #StateRunInit procedure only at first and initial state change.

#StateRunInit procedure is where we actually set the new last state and activate corresponding light on the semaphore.

With _state_last = state_run command we set new last state. _extout1|5 = 2  will activate Green light on our ExtInOut board. All other lights are in this state at 0.

 

Further we check for any state transition conditions being met. These conditions will be continuously evaluated until either Estop, Pause, Stop buttons are pressed or CNC doors are open.

Let’s say CNC doors are suddenly opened during program run (this is checked using the ExtInOut board’s input 1, _extin1|1).  Alarm state condition is met and program will execute procedure #StateRunEnd and set new state, state_alarm. Program returns back to #Loop function, where Alarm state is recognized and #StateAlarm procedure is executed.

Which state transitions conditions need to be checked within each state, is defined in our state machine at the beginning of this tutorial. 

 

 

 

CNC machine semaphore application, using expressions (Expr.txt) – Part 2

What is a State Machine and how it is used?

If you google State machine, this would be the average result more or less:

“A state machine is a behaviour model. It consists of a finite number of states and is therefore also called finite-state machine (FSM). Based on the current state and a given input the machine performs state transitions and produces outputs.” 

Sounds exactly what we need for our application doesn’t it?  5 states, which can transition one from another based on the pre-defined conditions.

Each semaphore state has its outputs and inputs.  State outputs are semaphore light configurations where specific relay output will be activated. State inputs are conditions that will cause the state transitions.

Both will be described in detail in Part 3 of this tutorial.

 

What is an Expr.txt file and how it is used?

  • Expr.txt is a file which contains functions that are evaluated by PlanetCNC TNG expression evaluator.
  • it can be very practical for PLC type applications where periodical evaluation is necessary or for program events
  • Expr.txt file can contain and use vast array of expression functions, parameters and other commands supported by PlanetCNC TNG
  • Expr.txt file is located in the profile folder of TNG software
  • Profile folder can contain multiple Expr.txt files. Specific Expr file naming convention is available for better distinction. e.g.: Expr_Semaphore
  • If multiple expr files use same function, then function will be called first in first file and only then the with the next file
  • Local variables(e.g. name convention of local variables: red_light, global variables: _red_light ) have scope only within the parent Expr file.
  • Each Expr file contains functions that are marked with symbol #.
  • Each function begins with symbol # and ends with the next # symbol.
  • Each expr file can use built-in functions that are related with program events

 

Built-in Expr functions:

  • #OnInit -> When TNG software is started/initialized, code under this function will be executed.
  • #OnShutdown -> On PlanetCNC TNG software shutdown request, code under this function will be executed, then TNG will shutdown.
  • #OnStart -> On NC program Start event, code under this function will be executed.
  • #OnEnd -> On NC program End event, code under this function will be executed.
  • #OnStop -> On NC program Stop event, code under this function will be executed.
  • #OnEStop -> On Estop event, code under this function will be executed.
  • #OnJog -> On Jogging event, code under this function will be executed.
  • #OnWheel -> On handwheel event, code under this function will be executed.
  • #OnCmd -> On MDI command event, code under this function will be executed.
  • #Loop          -> this loop is executed every 83ms, or the value of refresh rate set in settings, under: File/Settings/User Interface/Refresh Rate
  • #Loop5       ->this loop is executed every 5s
  • #Loop15      ->this loop is executed every 15s
  • #Loop60     ->this loop is executed every 60s
  • #Loop300  ->this loop is executed every 300s

 

 

 

CNC machine semaphore application, using expressions (Expr.txt) – Part 1

For this multi-part tutorial we will create CNC machine semaphore(signal tower lamp).

We hope that this tutorial will serve as a good example on how to set application goals and requirements as also on how to define the tools and solutions  for achieving them, while using powerful tools and flexibility of PlanetCNC motion control system.

 

How to begin?

First we need to clarify and define application requirements.

Semaphore application requirements

We want:

  • to use a semaphore with 5 lights.
  • Each light with different colour will visually represent dedicated machine state.
  • Combination of two active lights is allowed. This applies when NC program is running and pause is initiated.
  • Additionally, alarm should be activated if, at the beginning or during the program execution, front door of machine enclosure is open(ed) or pressure sensor is activated. Pressure error will be able to initiate alarm state also in Idle state.
  • In case of alarm, system should prevent or stop program execution.
  • Play siren sound to notify CNC operator.
  • Alarm notification is displayed on the main screen and at the status bar.
  • Alarm state needs to be reset. Hardware Reset button will be used.
  • If alarm is not reset and system is shut down, system will notify user to reset any pre-existing alarm at the system startup.

 

Machine states are:

  • Estop state
  • Idle state
  • Run state
  • Pause state
  • Alarm state

 

State description:

Estop

Estop state indicates that estop has been activated either using a PC keyboard key, controller estop pin (e-stop button) or Error pin indicating a motor driver fault.

This state will be indicated using active RED LIGHT on the semaphore.

 

Idle

Idle state indicates that machine is turned ON and connected with PC. Machine in Idle state does not perform any motion.

This state will be indicated using active YELLOW LIGHT on the semaphore.

 

Run

Run state indicates that CNC machine is executing the NC program.

This state will be indicated using active GREEN LIGHT on the semaphore.

 

Pause

Pause state indicates that machine is executing NC program, however program pause has been initiated, meaning program execution is on hold.

This state will be indicated using active ORANGE LIGHT in combination with active GREEN LIGHT(Run state) on the semaphore.

 

Alarm

Alarm state will indicate if CNC machine enclosure doors are open, at the start of program or during the program execution. So, if doors will be opened in any other state than Run, there is no need for an alarm.

This state will be indicated using a  BLUE LIGHT on the semaphore. In this state, we will also use siren sound alarm.

 

What hardware will we use?

So, we need to control 5 lights on the semaphore, and read one enclosure door switch. Meaning we need to occupy 5 digital outputs and 1 digital input of our MK3 controller.

But, digital input and output pins of controller are a precious commodity.  It would be better to use these pins for some other purpose.

More economic approach would be the use of ExtInOut board. This board expands the number of inputs and relay outputs used with controller by 8, respectively.

 

-ExtInOut board relays will control the semaphore lights, while one of the inputs will be used to evaluate CNC front door switch and Alarm reset button: ExtInOut board

-PlanetCNC Mk3 motion controller and EXT interface will be used for communication with external ExtInOut board: Mk3 controller

-Semaphore lights will be simulated with normal LED’s. If a real semaphore device would be used, integration would not be much different.

Programming the application

For a clearer and effective programming, we will use a concept called State Machine. Its functionality is suitable for the type of our application.

Drawing a flowchart will help us with visualization of such state machine and better overview of state definitions and state transition conditions.

Since we need constant and responsive background evaluation of various parameters and control of relay outputs, all our code will be included in the Expr.txt file.

 

 

In part 2 we will describe our state machine and Expr.txt file

Using PRINT command with PlanetCNC TNG software

PRINT command will display any text, variable or parameter value used in PlanetCNC TNG or gcode program.
It can be considered as a debug tool, trough which user can observe and diagnose execution of program.
As an output, PRINT command uses Output window/panel:

 

Example 1:

Click in the MDI window and type this text:
(PRINT,PRINT example nr.1)

 

This will display “PRINT example nr.1” in Output window/panel:

 

Example 2:

Now, lets say we want to print the value of parameter  _workoff_z .

Click in the MDI window and type this text:
(PRINT,#<_workoff_z>)

This will display  work offset Z value in Output window/panel:

 

Program debugging in PlanetCNC TNG software

Debugging tools in PlanetCNC TNG

For purposes of program observation or debugging, user can use PRINT and LOG commands, output window and state tabs for parameter value display. G-code ref manual will also come very handy.

Output window

To show/hide output panel in PlanetCNC TNG main window, click the middle square symbol in the upper right corner:

Output window will appear at the bottom middle section of the main window:

 

PRINT and LOG commands can be inserted within main gcode or script gcode at appropriate program lines. With output window, printed values can be then observed, while LOG command creates its own file that can save desired parameter values.

Example:

We want to observe values of the following parameters:

_current_tool
_tooloff
_tooloff_z
_workoff_z

These parameters can be displayed in Output window, Custom state tab or separate LOG file:

 

More on PRINT, LOG commands and how to create your own state tab can be found on links below:

PRINT: https://planet-cnc.com/using-print-command-with-planetcnc-tng-software/

LOG: https://planet-cnc.com/using-log-command-with-planetcnc-tng-software/

State tabs: https://planet-cnc.com/create-custom-tabs-planetcnc-tng-software/

 

 

Gcode in PlanetCNC TNG software

PlanetCNC TNG is a motion control software intended for control of CNC machines. As a standard, g-code is used as native programming language.

PlanetCNC TNG software processes gcode program and sends series of commands to motion controller. Controller interprets these commands and arranges coordinated machine motion.

 

Gcodes

G-code commands can be divided into multiple groups which differ from one another based on functionality and intended use.

Single G-code line, aka block, can include commands for modal state configuration, machine motion, program control and data manipulation, or peripheral control.
PlanetCNC TNG supports also gcode functions, expression functions, parameters, operators etc…

 

List of Gcode groups:
G-codes (G21,G17,G90, G00,G01…)
M-codes (M3,M5,M6…)
Other codes (F,S,T)
O-words (SUBROUTINE, CONDITION…)
Comments (LOGCREATE,LOG…)
Operators (+,-,EQ,GT,AND…)
G-code functions (Exists, Sin, Cos, Inc, Dec…)
Expression functions (infoIsInitialized, infoMachinePosition…)

 

Complete list of gcode commands and system parameters is available in PlanetCNC TNG G-code reference manual. This document is located in Doc folder of PlanetCNC TNG main installation folder. 

 

Simple g-code programs (i.e. toolpaths) can be easily written manually. For this purpose we recommend that users gets at least familiar with g-code. Get to know groups of g-code commands, syntax, format and programming guidelines. Even if user does not have an intention of manually writing his g-code programs, it can be in great help when debugging and understanding program and machine behaviour.

 

We really encourage our users that they explore PlanetCNC TNG scripts, they are a really great source for discovering different types of gcode programming and programming approaches. Single script code can offer a lot of new knowledge for a curious CNC beginner as also for a well rounded programmer.

More about accessing and configuration of scripts can be read here:
Accessing script files in PlanetCNC TNG

Using o-words with PlanetCNC TNG software: Loop blocks

You can use loop o-words with PlanetCNC TNG.

Sometimes you need to execute line(s) of g-code in a loop while evaluating specific condition.  When condition evaluates to false, program will exit loop. This comes useful for e.g. repetitive motion sequences, parameter manipulation etc…

O-word loop commands consist of:
O-do – Begin loop block
O-while – End loop block [or condition evaluation]
O-endwhile – End loop block
O-break – Exit loop block immediately
O-continue – Skip to next condition evaluation of  the while condition

You can use two types of loops: While – Endwhile or Do – While.

While – Endwhile:
Evaluates condition on the beginning of loop and then executes the lines of code inside the loop.

Do – While:
First executes the lines of code inside the loop and then evaluates condition at the end of loop.

Example of Do – While loop:

%
 #1 = 1
 o100 do
 (print, #1)
 #1 = [#1+1]
 o100 while[#1 LE 10]
 (print,loop finished)
 %

The content of the loop will repeat for as long as while condition at the end will remain true. Which means that as soon as parameter #1 exceeds value of 10, program will exit the loop and print

loop finished

 

Example of While – Endwhile loop:

%
#1 = 1
o100 while [#1 LE 10]
 (print, #1)
 #1 = [#1+1]
o100 endwhile
 (print,loop finished)
%

The content of the loop will repeat for as long as while condition at the beginning will remain true. Which means that as soon as parameter #1 exceeds value of 10, program will exit the loop and print

loop finished

Example of break used in a Do – While loop:

%
#<A> = [2**0]
#<B> = 0
#1 = 0
o100 do
 (print, #1)
 #1 = [#1+1]
 G04 P1
 o200 if [AND[#<A>, #<_hw_input>]]
  #<B> = 1
  o100 break
 o200 endif
o100 while[#1 LE 10]

o300 if [#<B> EQ 0]
 (print,loop finished)
 o300 else 
 (print,preemptive loop break)
o300 endif
%

The content of the loop will repeat for as long as input 1 of controller is not active and while condition at the end remains true. Which means that as long as input 1 remains un-active, counter will count to 10 and program will exit the loop normally and print:

loop finished

As soon as input becomes active during counting, program will exit loop and print:

preemptive loop break

Using o-words with PlanetCNC TNG software: Conditional statements

You can use o-word conditional statements with PlanetCNC TNG.

O-word conditional statement consist of  if, elseif, else and endif keywords.

All keywords of the same conditional statement should use same o-word number.

Example of if…endif conditional statement:

%
#1=10
o100 if [#1 EQ 10]
 (PRINT,Condition is true)
o100 endif
%

Parameter #1 is assigned value 10. o100 if statement checks if parameter #1 equals (EQ) value 10. Because condition is true, program executes print command that is included in this conditional statement. o100 if statement is ended with o100 endif.

This code will display in Log:

Example of if, else…endif conditional statement:

%
#1=10
o100 if [#1 EQ 9]
(PRINT,10 equals 9)
o100 else
(PRINT,10 does not equal 9)
o100 endif
%

Paramter #1 is assigned value 1o. o100 if statement checks if parameter #1 equals (EQ) value 9. Because condition is not true, program jumps to o100 else statement and executes print command unconditionally.

This code will display in Log:

 

Example of if, elseif…endif conditional statement:

%
 #1=10
 o100 if [#1 GT 20]
 (PRINT,10 is greater than 20)
 o100 elseif [#1 LT 20]
 (PRINT,10 is less than 20)
 o100 endif
 %

Paramter #1 is assigned value 1o. o100 if statement checks if parameter #1 is greater  than (GT) value 20. Because condition is not true program jumps to o100 elseif statement where its checked if parameter #1 is less then (LT) value 20. Because condition is true, program executes print command that is included in o100 elseif conditional statement. Also note how if, elseif and endif keywords use same o-word number.

This code will display in Log: