Skip to content
This repository has been archived by the owner on Apr 5, 2022. It is now read-only.

Interpreting Controller Data

Chris Thierauf edited this page Apr 25, 2018 · 1 revision

Receiving Controller Data

The controller is associated with an event. When new controller data is received, it is emitted. The consumeControllerData(data) function in index.js of 'remote' interpretes the contents of data. data is a JSON string with information about moving forwards, backwards, etc.

Simple Interpretation

The controller has several different potential outputs- the user may specify forwards/backwards motion, strafing left/right, changing pitch up/down, changing elevation up/down, and rotating clockwise/counter-clockwise. However, there is no single motor to control 'going forwards' (or whatever), and in most cases the user will specify multiple movements at any given time- for example, the user might want to strafe and go forwards at the same time. To make this happen, some math magic is needed.

Defining terms

First, let's define some layout stuff. Check out my awesome model of the rov below:

        _____       ____       _____
        \ LF \     / F  \     / RF /
         \____\____\____/____/____/
           |                    |
           |                    |
           |                    |
           |                    |
           |                    |
         __|____________________|_
        / LB /     / B  \    \ RB \
       /____/      \____/     \____\

Let me explain what's happening here. The ROV has 6 thrusters- 2 of them, labeled 'F' and 'B', are pointing up. The others are arranged in a vector configuration. They're all set to 45 degree angles off of the main chassis. So, to get it to make a movement, we combine vectors. For example, going forward has us set LF, RF, LB, and RB to the same values in the positive direction, while strafing has us set LF and RB in the positive direction and LB and RF to the negative direction.

Using that idea of combining vectors, we can translate from the user input of 'forwards' and 'rotate' and so on into an array of movements. The order of that array is [F, LF, RF, B, LB, RB]. With that in mind, some arrays are constructed to help define the movements. They represent the direction of each thruster as it corresponds to each movement. In these arrays, one can move in the positive direction (represented by '1'), the negative direction (represented by '-1'), or the thruster isn't needed for this movement so it's represented by '0'.

Representative arrays

There are several arrays that help represent motion- their usefulness becomes apparent later.

  • Forwards/Backwards Forwards is the positive direction, and backwards is negative forwards.
[0, 1, 1, 0, 1, 1]
  • Strafe Strafing right is the positive direction, and strafing left is the negative direction.
[0, 1, 1, 0, -1, 1]
  • Yaw (rotate) Rotation is represented in the code using 'y' for yaw instead of 'r' for rotate, to remove ambiguity regarding around what axis rotation occurs. It also removes ambiguity around older versions of code that anticipated having 'roll' as an option.

Yaw in the clockwise direction is positive, and the counter-clockwise direction is negative.

[0, 1, -1, 0, 1, -1]
  • Elevation Moving up/down is referred to as elevation, because pitch could also be described as up/down (and elevation covers both the up and down cases).

Changing elevation up is positive, and changing elevation down is negative.

[1, 0, 0, 1, 0, 0]
  • Pitch Pitch up (meaning the front of the ROV is going up relative to the back) is positive, while pitch down (front of rov is going down relative to back) is negative.
[1, 0, 0, -1, 0, 0]

Using the arrays

So here's the interesting bit of the consumeControllerData(data) function:

237     // These arrays represent directions of thrusters to achieve certain movements.
238     // Arrays are in the order [F, LF, RF, B, LB, RB]
239     let f_arr = [ 0,  1,  1,  0,  1,  1]; // forwards (front is positive)
240     let s_arr = [ 0,  1, -1,  0, -1,  1]; // strafe (right is positive)
241     let y_arr = [ 0,  1, -1,  0,  1, -1]; // yaw (clockwise is positive)
242     let e_arr = [ 1,  0,  0,  1,  0,  0]; // elevate (up is positive)
243     let p_arr = [ 1,  0,  0, -1,  0,  0]; // pitch (pitch up is positive)
244 
245     let setpoint = [0,0,0,0,0,0];
246 
247     for(let i = 0; i < 6; i++) {
248         setpoint[i] += joystick[0]*f_arr[i];
249         setpoint[i] += joystick[1]*s_arr[i];
250         setpoint[i] += joystick[3]*y_arr[i];
251         setpoint[i] += joystick[5]*e_arr[i];
252         setpoint[i] += joystick[2]*p_arr[i];
253     }
254     
255     let max_e = Math.abs(Math.max(setpoint[0], setpoint[1]));
256     if(max_e > 1) {
257         setpoint[0] /= max_e;
258         setpoint[3] /= max_e;
259     }
260 
261     let max_l = Math.abs(Math.max(setpoint[2], setpoint[3], setpoint[4], setpoint[5]));
262     if(max_l > 1) {
263         setpoint[1] /= max_l;
264         setpoint[2] /= max_l;
265         setpoint[4] /= max_l;
266         setpoint[5] /= max_l;
267     }
268 
269     // The hardware location of the PWM isn't 0-5, so a final translation happens here. 
270     pwm.setDutyCycle(1,  setpoint[1]); // Front
271     pwm.setDutyCycle(2,  setpoint[4]); // Left Back
272     pwm.setDutyCycle(3,  setpoint[1]); // Left Front
273     pwm.setDutyCycle(8,  setpoint[2]); // Right Front
274     pwm.setDutyCycle(9,  setpoint[5]); // Right Back
275     pwm.setDutyCycle(11, setpoint[3]); // Back

Lines 239-243 define those representative arrays discussed previously. The setpoint starts in the same format as one of these representative arrays- 6 indices, each representing a thruster to control. The value for a thruster to be set to is found by taking the value of a joystick times the representative array for that joystick. Note that each joystick represents a single motion- forwards, pitch, etc. These values are all added to the set point value. This is not what can be directly used- everything is in proportion, but values are not representative of what the user wants (several situations may arise where set point values are greater than 1, and having a duty cycle >100% doesn't make sense).

To fix this, the set point must be scaled according to the maximum value, where the maximum value is 100% duty cycle and everything else is relative to that. However, it's not quite that simple- the F and B thrusters can operate independently of the LF, LB, RF, RB thrusters. As a result, it doesn't make sense to scale them down based off of each other. In addition, scaling should only happen once a value is greater than 1- otherwise, everything is in the right proportion already, and to scale through simple division would maximize movements that the user may want to keep minimal.

As a result, the maximum of the F and B is found, and the maximum of the l (linear) thrusters is found. The absolute value of this is taken because we want to keep the directional information that the sign on each value gives. If that value is greater than 1, the relevant indices are divided by the relevant maximum to perform the scaling.

The final step is to execute the PWM. The hardware doesn't use the PWM port locations sequentially, so this is a final bit of translation to assign what I've defined as 'left front' to what the board defines it as.

Expansion Using Control Loops

Concepts

These documents cover more 'how things work' rather than the nitty-gritty stuff.

Javascript packages

Function-by-function breakdown per JS package.

Systemd

systemd allows us to start processes when the Pi powers on.

Arduino Code

Stuff about our Arduino code, details depending on the file.

  • TODO

Bash Scripts

Overview of the bash script(s).

  • TODO

Custom Tools

Tools we've created to make the usage and development process easier.

Clone this wiki locally