kb:projects:usb_gamepad:using_gamepad_controller_with_planetcnc_tng_software

Using gamepad controller with PlanetCNC TNG software

One of the most common requests from users is how to use external USB device for purpose of expanding TNG functionality. This could be either simple machine control using a gamepad or two way communication where device sends data, and TNG also responds back.

This project will describe how to use external USB device with PlanetCNC TNG software. We will use gamepad controller to jog CNC machine.

Introduction

PlanetCNC software needs to “listen” to USB device anytime when it want to “say” something. Gamepad controller is a USB device that “screams” a lot, meaning, it sends constant stream of data. Data is arriving at the USB port non-stop.

Luckily, PlanetCNC TNG software offers set of expression functions, that help with handling and manipulation of USB data traffic.

Once we will successfully listen to our device and observe the data from gamepad controller, we will be able to program a expression script which will help us to jog CNC machine.


We recommend reading trough reference documentation of USB functions: USB Functions

This document will cover following topics:

  • recognizing USB device
  • identifying USB device path and USB data
  • device button functionality and button data interpretation
  • creating expression script file

Recognizing USB device

First, we need to identify USB device that we want to communicate with. Once our device is recognized from device list, we can obtain its USB device path. This path is our gateway to device data channel to which we will listen and read when anything arrives.

Under Help menu click: Show Log

Click HID devices button (red square) from the toolbar to display all HID devices connected to PC USB ports. Product description will help us identifying our gamepad controller(green square). After this we can easily see controllers USB path(yellow square).

Copy path of your device to clipboard and paste it to text editor for later use. Mark the path text, use right mouse button and click Copy.

Identifying USB data traffic

Now that we have our USB device recognized, we need to see what kind of data device sends. Based on data structure and values, we will be able to manipulate suitable data packets.

Let find out, what is the size of data that this controller sends. For this purpose we use usb_read() function.

Type into the MDI window:
NOTE: Use path of your device

path = "\\?\hid#vid_045e&pid_028e&ig_00#8&1fa5fd5b&0&0000#{4d1e55b2-f16f-11cf-88cb-001111000030}";
usb_open(path);
usb_read(path, 0);
usb_close(path);

If we run this, output window displays return value of 14. So this controller sends 14 bytes. This information is important once we use usb_addlistener() function.

usb_read(path, 0) = 14


Let also print the values using usb_readdata() function:

Type into the MDI window:
NOTE: Use path of your device

usb_readdata(path, 0);

If we run this, 14 byte values will be printed in the output window:

0x00, 0x80, 0xFF, 0x7F, 0x00, 0x80, 0xFF, 0x7F,
 0x00, 0x80, 0x00, 0x00, 0x00, 0x00

Creating Expression file

Lets print these values in loop so that any action on the controller will be immediately printed and we will be able observe and distinct which button/joystick affects certain byte(s) and in what way.

In your profile folder create file Expr_Gamepad_Controller.txt

Insert text below: NOTE: Use path of your device

#OnInit
path = "\\?\hid#vid_045e&pid_028e&ig_00#8&1fa5fd5b&0&0000#{4d1e55b2-f16f-11cf-88cb-001111000030}";
usb_open(path);
 
#Loop
usb_readdata(path, 0);
 
#OnShutdown
usb_close(path);

Save file and restart PlanetCNC TNG sw. Output window will continuously print 14byte values. These values are printed in two rows, meaning it is difficult to track them with the naked eye.

0x00, 0x80, 0xFF, 0x7F, 0x00, 0x80, 0xFF, 0x7F
0x00, 0x80, 0x00, 0x00, 0x00, 0x00
0x00, 0x80, 0xFF, 0x7F, 0x00, 0x80, 0xFF, 0x7F
0x00, 0x80, 0x00, 0x00, 0x00, 0x00
0x00, 0x80, 0xFF, 0x7F, 0x00, 0x80, 0xFF, 0x7F
0x00, 0x80, 0x00, 0x00, 0x00, 0x00
0x00, 0x80, 0xFF, 0x7F, 0x00, 0x80, 0xFF, 0x7F
0x00, 0x80, 0x00, 0x00, 0x00, 0x00
0x00, 0x80, 0xFF, 0x7F, 0x00, 0x80, 0xFF, 0x7F
0x00, 0x80, 0x00, 0x00, 0x00, 0x00

Lets try array_printdata() function, which will print data in one single line in the output window. Edit the original file with text below.

#OnInit
path = "\\?\hid#vid_045e&pid_028e&ig_00#8&1fa5fd5b&0&0000#{4d1e55b2-f16f-11cf-88cb-001111000030}";
usb_open(path);
hnd = array_new();
 
#Loop
usb_readarray(path, hnd, 0);
array_printdata(hnd, 14);
 
#OnShutdown
usb_close(path);
array_delete(hnd);

Save file and restart PlanetCNC TNG sw. Output window will continuously print 14byte values. There, much better for the eye.

0x00, 0x80, 0xFF, 0x7F, 0x00, 0x80, 0xFF, 0x7F, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00
0x00, 0x80, 0xFF, 0x7F, 0x00, 0x80, 0xFF, 0x7F, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00
0x00, 0x80, 0xFF, 0x7F, 0x00, 0x80, 0xFF, 0x7F, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00
0x00, 0x80, 0xFF, 0x7F, 0x00, 0x80, 0xFF, 0x7F, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00
0x00, 0x80, 0xFF, 0x7F, 0x00, 0x80, 0xFF, 0x7F, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00
0x00, 0x80, 0xFF, 0x7F, 0x00, 0x80, 0xFF, 0x7F, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00

Byte numbering:


Gamepad controller button description and button data interpretation

Now let's test controller buttons and observe which button affects which byte. Press buttons, move joystick and throttle to see which byte corresponds in the output window.
Image below describes the buttons of controller for better understanding:


After some testing, I was able to create a table which describes each buttons functionality and its corresponding byte with values.

NOT USED byte 0
Left joystick Horizontal byte 1 0x00 - 0xFF
Vertical byte 3 0x00 - 0xFF
NOT USED byte 4
Right joystick Vertical byte 5 0x00 - 0xFF
Horizontal byte 7 0x00 - 0xFF
NOT USED byte 6
NOT USED byte 8
Throttle Left throttle byte 9 0x80 - 0xFF
Right throttle 0x80 - 0x00
Buttons A byte 10 bit: 1
B bit: 2
X bit: 3
Y bit: 4
Left bit: 5
Right bit: 6
Select bit: 7
Start bit: 8
Direction pad Up byte 11 0x04
Up right 0x08
Right 0x0C
Down right 0x10
Down 0x14
Down left 0x18
Left 0x1C
Up left 0x20
NOT USED byte 12
NOT USED byte 13

Modifying expression file

Examples below demonstrate jogging with gamepad buttons.



Example 1:

Implements jogging using direction pad buttons.

;Create new array for USB data storage. 
;Enables USB communication 
;Adds USB listener, which reads data from USB device and saves it in array. Each time device sends new data, array data is refreshed and callback function '#OnUsbGamepad' is executed.
 
#OnInit
  hnd = array_new();
  path = "\\?\hid#vid_045e&pid_028e&ig_00#9&ad497a1&0&0000#{4d1e55b2-f16f-11cf-88cb-001111000030}";
  usb_open(path);
  usb_addlistener(path, "#OnUsbGamepad", hnd, 14); 
 
#OnShutdown
  usb_close(path);
  usb_remlistener(path);
  array_delete(hnd);
 
#OnUsbGamepad
 
; Parse USB data. Received data is stored in array. .arg2 argument holds array handle.
; According to the 'table', byte 11 represents 'Direction pad' values. We store it in private variable .dir.
  .dir = array_getdata(.arg2, 11);
 
 
; From .dir variable we calculate direction represented as angle and store its value to private variable .angle.
  .angle = -1;
  if(.dir == 0x04, .angle = 0);
  if(.dir == 0x08, .angle = 45);
  if(.dir == 0x0C, .angle = 90);
  if(.dir == 0x10, .angle = 135);
  if(.dir == 0x14, .angle = 180);
  if(.dir == 0x18, .angle  = 225);  
  if(.dir == 0x1C, .angle = 270);
  if(.dir == 0x20, .angle = 315);
 
; Now that we have angle values we can calculate x and y axis jogging parameters. These are stored in private variables .ax and .ay.
  .ax = 0;
  .ay = 0;
  if(.angle == 0,   .ay = +1);
  if(.angle == 180, .ay = -1);
  if(.angle == 90,  .ax = +1);
  if(.angle == 270, .ax = -1);
 
; If we jog in diagonal directions, factor 0.707 is used to compensate the longer distance.    
  ;0.707 = 1/sqrt(2)
  if(.angle == 45,  exec(.ax = +0.707, .ay = +0.707));
  if(.angle == 135, exec(.ax = +0.707, .ay = -0.707));
  if(.angle == 225, exec(.ax = -0.707, .ay = -0.707));
  if(.angle == 315, exec(.ax = -0.707, .ay = +0.707));
 
 
; Finally axis jogging parameters are sent to 'jog' function to perform jogging.
  jog(0, .ax, .ay, 0);
 



Example 2:

Implements jogging using left or right joystick.

#OnInit
  hnd = array_new();
  path = "\\?\hid#vid_045e&pid_028e&ig_00#9&ad497a1&0&0000#{4d1e55b2-f16f-11cf-88cb-001111000030}";
  usb_open(path);
  usb_addlistener(path, "#OnUsbGamepad", hnd, 14); 
 
#OnShutdown
  usb_close(path);
  usb_remlistener(path);
  array_delete(hnd);
 
#OnUsbGamepad
 
; Parse USB data. Received data is stored in array. .arg2 argument holds array handle.
; According to the 'table', bytes 1,3,5,7 represent left and right joystick values. We store them in private variables .lx, .ly and .rx, .ry.
; Depending on joystick position, jog parameters are calculated in range [-1...1].
  .lx = (array_getdata(.arg2, 1) - 0x80) / 0x80;
  .ly = (array_getdata(.arg2, 3) - 0x7F) / 0x80;
  .rx = (array_getdata(.arg2, 5) - 0x80) / 0x80;
  .ry = (array_getdata(.arg2, 7) - 0x7F) / 0x80;
 
 
  jog(0, .lx, -.ly, 0);
  jog(0, .rx, -.ry, 0);



Example 3:

Implements jogging with ABXY buttons

#OnInit
  hnd = array_new();
  path = "\\?\hid#vid_045e&pid_028e&ig_00#9&ad497a1&0&0000#{4d1e55b2-f16f-11cf-88cb-001111000030}";
  usb_open(path);
  usb_addlistener(path, "#OnUsbGamepad", hnd, 14); 
 
#OnShutdown
  usb_close(path);
  usb_remlistener(path);
  array_delete(hnd);
 
#OnUsbGamepad
 
; Parse USB data. Received data is stored in array. .arg2 argument holds array handle.
; According to the 'table', byte 10 represents ABXY button values. We store it in private variable .btn.
  .btn = array_getdata(.arg2, 10);
 
; To obtain particular button state, we use bitwise AND operation. 
; Bitwise AND operation between byte 10 (stored in variable .btn) and button bit will return 1 if bit is set (button is pressed) and 0 otherwise.
; Button states are stored in private variables: .btnA, .btnB, .btnX, .btnY.
  .btnA = .btn & 1;
  .btnB = .btn & 2;
  .btnX = .btn & 4;
  .btnY = .btn & 8;
 
; .ax and .ay values are initialized to zero. They are used to set jogging direction.
; When button state is 1, .ax and .ay are set to +1 or -1, depending on desired jogging direction.
  .ax = 0;
  .ay = 0;
 
  if(.btnX, .ax = -1);
  if(.btnB, .ax = +1);
  if(.btnA, .ay = -1);
  if(.btnY, .ay = +1);
 
  jog(0, .ax, .ay, 0);
 



Example 4:

Implements jogging with ABXY buttons and left throttle.

#OnInit
  hnd = array_new();
  path = "\\?\hid#vid_045e&pid_028e&ig_00#9&ad497a1&0&0000#{4d1e55b2-f16f-11cf-88cb-001111000030}";
  usb_open(path);
  usb_addlistener(path, "#OnUsbGamepad", hnd, 14); 
 
#OnShutdown
  usb_close(path);
  usb_remlistener(path);
  array_delete(hnd);
 
#OnUsbGamepad
; Parse USB data. Received data is stored in array. .arg2 argument holds array handle.
; According to the 'table', byte 10 represents ABXY button values. We store it in private variable .btn.
  .btn = array_getdata(.arg2, 10);
 
; To obtain particular button state, we use bitwise AND operation. Bitwise AND operation between byte 10(.btn) and button bit will return 1 once the button is pressed, otherwise it is 0. Button states are stored in private variables: .btnA, .btnB, .btnX, .btnY.
  .btnA =  .btn & 1;
  .btnB =  .btn & 2;
  .btnX =  .btn & 4;
  .btnY =  .btn & 8;
 
; Parse USB data. Received data is stored in array. .arg2 argument holds array handle.
; According to the 'table', byte 9 represents throttle buttons. We store it in private variable .th.
  .th = array_getdata(.arg2, 9);
 
; Left throttle button value is stored in private variable .lth. 
; Right throttle button value is stored in private variable .rth. 
; Both throttle button values are set to zero until any throttle button is pressed.
  .lth = 0;
  .rth = 0;
 
;If throttle buttons are pressed, .rth and .lth  values are calculated in range [-1..1] 
  if(.th < 0x80, .rth = .th / 0x80);
  if(.th > 0x80, .lth = (.th - 0x80) / 0x7F);
 
;Sets .ax and .ay values to zero
  .ax = 0;
  .ay = 0;
 
; With ABXY we define direction of jogging, with throttle we define speed of jogging. 
; If ABXY button(s) are pressed, and left throttle button is pressed, .ax and .ay values are calculated in range [-1..1].  
  if(.btnX, .ax = .ax - .lth);
  if(.btnB, .ax = .ax + .lth);
  if(.btnA, .ay = .ay - .lth);
  if(.btnY, .ay = .ay + .lth);
 
  jog(0, .ax, .ay, 0);
 
kb/projects/usb_gamepad/using_gamepad_controller_with_planetcnc_tng_software.txt · Last modified: 2023/03/27 17:09 by andrej

Page Tools