Posted on by

Okay so in a previous post we covered the basic theory and hardware behind building a reaction wheel – a cool module used in satellites to control their orientation in space. Now that we have our hardware set up, let’s move into software development!

There are three main steps to the detumbling software:

  1. Obtain data from the gyroscope
  2. Process data and decide what output to send
  3. Send output to the motor

Obtaining Data

Most sensors nowadays use serial communications to send data bit-wise (one bit after the other) between devices. There are several serial ‘protocols’ (methods of sending data) including SPI, CAN and I2C. SPI is very fast, but is generally unreliable for transmission distances more than a few inches. CAN is reliable over longer distances (commonly used for car internals) but tends to be a bit on the slow side. I2C is a happy medium between the two, having a fairly fast speed but still able to be transmitted over distances of a few metres. For a better look into I2C, Declan has written up an easy-to-understand explanation of I2C involving football!

Our gyro chip communicates over I2C, so we need to set up our Arduino to be able to talk to it. This is actually a fairly complex task to do, but fortunately there are many companies and hobbyists out there who write code to do this for us. These ‘libraries’ are commonly available online by searching the chip number – for example, our gyro has the chip number L3GD20. For this project we used the Adafruit_Sensor and Adafruit_L3GD20_U libraries.

Once these libraries are added to our Arduino libraries folder, we can examine the example code to work out how to use the library functions. The following code is extracted from one of these example programs, with the bare essentials to read in gyro data.

#include <Adafruit_Sensor.h>
#include <Adafruit_L3GD20_U.h> //include library header files to use library code later on

Adafruit_L3GD20_Unified gyro = Adafruit_L3GD20_Unified(20); //constructor for gyro object
gyro.begin();             //initialise gyro
sensors_event_t event;    //make a box to put data into
gyro.getEvent(&event);    //get data from gyro and put into box
float wZ = event.gyro.z;  //wZ is now the angular velocity (w) around the z (vertical) axis,
                          // i.e. how fast it is spinning when suspended from a string

 

Processing Data

Removing Bias

Before we can use this gyro data to do anything useful, we need to remove its ‘bias’. Most gyros have a bias in their measurements, meaning that all measurements will have some constant error in them – this is a bad thing! To remove this bias, we simply take a bunch of measurements when the sensor is not moving, average them to estimate the bias, then subtract this bias from all subsequent measurements to get the correct angular velocity. In code it looks like this:

//Calculate bias
for (i = 0; i < 100; i++){
  sensors_event_t event; 
  gyro.getEvent(&event);
  biasZ = biasZ + event.gyro.z;
  delay(10);  //pause for 10ms
}
biasZ = biasZ / 100;

//Correct subsequent measurements
sensors_event_t event; 
gyro.getEvent(&event);
float wZ = event.gyro.z - biasZ;

Controlling Output

Now our goal with the reaction wheel system is to detumble, or in other words, to have an angular velocity of 0°/s. To do that, we calculate the ‘error’ in our system (how much we are off by). For example, if we were spinning by 10°/s clockwise, then our error would be -10°/s (clockwise rotation is negative by convention).

To decide the output value (how fast we spin the motor), we use a very simple algorithm called a ‘proportional controller’ – simply speaking, the output of our motor is set to be proportional to the error in our angular velocity, or in math-speak:

Output = K ⨉ Error

(where K is some constant value). A very small K will not be very effective (like having a very weak motor), but too high a value of K will result in overcorrection (similar to oversteering a car), resulting in oscillations around your target value (0°/s). Tuning K to the optimum value is just a matter of experimenting to get the largest K that doesn’t overshoot.

don't be smol or swol
Effect of K on Controller Performance

 

In code, the controller looks like this (very simple):

float k = 2.0;
float motorSpeed = k * (-wZ); // error=target-wZ where target = 0

(Note that for this system, the best value of K happened to be 2.0, but for other systems it does not need to be a whole number.)

As with hardware, as a first development iteration, code should be as simple as possible (reducing development time at the cost of decreasing performance), but for future iterations a full PID controller is recommended (to reduce the time the system takes to stop spinning).

 

Outputting Data

Now that we have our desired output amount, we then ‘send’ this value to the motor via an H-bridge. In simple terms, the H-bridge supplies power to the motor straight from the 12V battery pack, at a voltage proportional to the signal received from the Arduino. This allows the Arduino to set the motor speed without having to supply power directly to the motor – if an Arduino supplies too much power it will die a swift and horrible death.
The way you control the motor is very similar to controlling the brightness of an LED, except that you can push current in either direction. For example, if you wanted to set the motor to spin anticlockwise at half speed, you would do the following:

  • Set pin A to 2.5V (half of the max voltage the Arduino can put out)
  • Set pin B to 0V (where pins A and B are connected to the H-bridge)

or in code:

analogWrite(pinA, 128);    //128 is 2.5V (255 is 5V)
digitalWrite(pinB, LOW);   //LOW is 0V

This would cause the H-bridge to supply a +6V voltage (half of the full 12V available) across the motor pins, driving the motor at half speed.

Alternatively, if you wanted to set the motor to spin at full speed clockwise, you would do the following:

  • Set pin A to 0V
  • Set pin B to 5V

corresponding to the code:

digitalWrite(pinA, LOW);
analogWrite(pinB, 255);

which would put a -12V voltage across the motor pins, driving it at full speed in reverse.

(Note that you’ll need pin A and B to be PWM enabled to do this – look up the Arduino pinout for this, or look for a ‘~’ next to the pin numbers.)

Thus, to output our desired motor speed we use following code:

float cappedMotorSpeed = min(1.0, abs(motorSpeed)); //cap speed at 100%
int motorOut = (int)(255 * cappedMotorSpeed);         //255 = 5V with analogWrite()

if (motorSpeed < 0){
  analogWrite(motorPinA, motorOut);
  digitalWrite(motorPinB, LOW);
} else {
  digitalWrite(motorPinA, LOW);
  analogWrite(motorPinB, motorOut);
}

 

Execute!

By repeating this ‘input-process-output’ sequence many times per second, we can detumble the reaction wheel system.
This is what this process looks like in action (using an RF module to activate/deactivate the wheel):

 

(Code available here)