Thursday, 27 August 2015

BOM

I've been trying to budget the project in terms of cost and weight of the quadcopter.
I have a model in mind of a simple 24" wide X shaped design made of wood, polycarbonate and sorbothane (for vibration dampening). With that model in mind and a quick survey of what I need to propel / control the quad, I get the below.

Description Size Units Weight (g) Wratio Price (CAD) Pratio Required TWeight Tprice
Structure
Polycarbonate Sheet 432 sq-inch 1000 2.3  $        23.90 0.06 128 296 7.1
Goujon SQ 1/2 48 lin-inch 99 2.1  $          1.78 0.04 48 99 1.8
Goujon RD 1/2 48 lin-inch 44 0.9  $          0.98 0.02 32 29 0.7
Insulation tube 36 lin-inch 17 0.5  $          1.29 0.04 6 3 0.2
Sorbothane 1/4" 30 DURO 50 sq-inch 50 1.0  $        26.95 0.54 8 8 4.3
Hardware (screws, nuts, etc.) 1 bulk 100 100.0  $        50.00 50.00 1 100 50.0
Propulsion/Energy
1050Kv Brushless Motor 1 motor 57 57.0  $        35.09 35.09 4 228 140.4
Propellers 10in/4.5p 4 approx 60 15.0  $          6.00 1.50 4 60 6.0
ESC 30A 1 approx 29 29.0  $        13.00 13.00 4 116 52.0
3S 5200mAh Lipo Pack 1 approx 330 330.0  $        35.00 35.00 1 330 35.0
Control
PCA9685 1 unit 7 7.0  $        15.85 15.85 1 7 15.9
Raspberry Pi 1 unit 45 45.0  $        46.11 46.11 1 45 46.1
MPU9250 1 unit 5 5.0  $        15.61 15.61 1 5 15.6
BMP280 adafruit 1 approx 5 5.0  $          9.95 9.95 1 5 10.0
GPS 1 approx 10 10.0  $        50.00 50.00 1 10 50.0
(optional) Xbee radio

Some components I've already bought and weighted, some are just approximations based on some quick web searches. I haven't locked on some components.

Still, I'm 11% under budget for weight, and 28% under budget for cost. I've excluded the cost of tools - they can always be reused for other projects. I've taken a similar commercially available kit (ELEV-8 V2 Quadcopter Kit from Parallax) as a reference. I've converted the amounts in Canadian currency (CAD).

Metric Total Target Delta %
Weight (g) 1341 1500 -159 -11%
Price (CAD) 435 600 -165 -28%

Friday, 21 August 2015

Pulse Width Modulation (PWM) with PCA9685

The theory

PWM (Pulse Width Modulation) is a modulation technique where the duration of a signal pulse is used to encode information. In the case of a quadcopter it's usually used to modulate the rotational speed of the motors/propellers. The signal is periodic and the duty cycle represent the proportion of the period where the signal is "on" (logical 1).



To generate such a signal, I'll be using the NXP PCA9685 chip on a pre-assembled Adafruit PCB module. The output channels will eventually be connected to ESC circuits (electronic speed controllers) that will drive the motors with a higher voltage/amperage than the Pi or the PCA9685 could deliver.

The signals could be generated on the GPIO pins of the Pi, but there is only 1 PWM dedicated hardware pin. Since we need 4 PWM signals (one for each motor), three of the signals would need to be generated in software - which would gobble up a lot of CPU power at high frequencies. It's therefore better to use a dedicated IC like the PCA9685.

A longer pulse width will increase propeller rotational speed. A shorter pulse width will decrease the propeller rotational speed. Some baselining and calibration will be required when I get there. For the moment I want to make sure that I can properly interface with the circuit and generate a PWM signal on any output pins.

What we need

You can find the NXP PCA9685 specification here. There are 3 things I need to code:
  1. Initialization routine of the PCA to make sure the IC is running in the proper mode and is ready to go.
  2. Function to modify the PWM frequency
    1. The IC has an internal oscillator (clock) of 25 MHz, but the output frequency of the PWM signal can only be set between 24Hz and 1526Hz
    2. To adjust the PWM frequency, a special register is used (prescale)
    3. The output frequency will need to be set according to what can be taken as input in my future ESC motor drivers (usually maximum of 500Hz according to what I gathered so far)
  3. Function to modify the duty cycle of the PWM signal
    1. The duty cycle will be changed according to the input throttle value - i.e. the speed at which we want the motors to spin
By the way, the duty cycle doesn't have to start exactly at the beginning of a period. You can have a delay before the duty cycle starts. That's why the PCA9685 allows you to define both an ON and an OFF value. The circuit uses 12 bits registers for those. That means that each period is divided in 4096 sub intervals.

The ON value will specify the interval when the signal goes to "1", the OFF value will specify the interval when the signal goes back to "0". For example: ON = 0 and OFF = 2048 means that the signal will be active for the first 50% of the period.

You'll see that I'm using the I2C interface I coded in the previous blog post to send the commands.

this->devAddress was initialized to 0x40 (address of the PCA9685 on the I2C bus).
All the registers and bit masks are defined in an enum (e.g. ALL_LED_ON, MODE2, etc.) and the names are fully aligned with the NXP spec.

Initialization routine

The init routine does three things:

  1. Turn off all output channels (effectively setting a duty cycle of 0%)
    1. Writes 0 to the ALL_LED_ON and ALL_LED_OFF registers
  2. Set the right modes of operations
  3. Restart the module

As per the spec, we need to wait 500us after some operations to make sure the oscillator stabilizes.


int PCA9685::init() 
{ 
 debug_print("[PCA9685] Init...\n");
 debug_print("[PCA9685] Turning off all PWM channels\n");
 this->pBus->WriteRegisterByte(this->devAddress, ALL_LED_ON, 0);
 this->pBus->WriteRegisterByte(this->devAddress, ALL_LED_ON+1, 0);
 this->pBus->WriteRegisterByte(this->devAddress, ALL_LED_OFF, 0);
 this->pBus->WriteRegisterByte(this->devAddress, ALL_LED_OFF+1, 0);
 
 debug_print("[PCA9685] Set mode registers\n");
 // configure totem pole structured output
 this->pBus->WriteRegisterByte(this->devAddress, MODE2, OUTDRV);
 // respond to ALL_LED register changes 
 this->pBus->WriteRegisterByte(this->devAddress, MODE1, ALLCALL);
 
 usleep(5000); // wait for oscillator
 
 debug_print("[PCA9685] Reset\n");
 int mode = this->pBus->ReadRegisterByte(this->devAddress, MODE1);
 this->pBus->WriteRegisterByte(this->devAddress, MODE1, mode & ~SLEEP);
 
 usleep(5000); // wait for oscillator
 
 return 0;
}

Set the PWM frequency

We need a routine to modify the periodicity of the PWM signal. This is done by tweaking the prescale register value according to this formula (from the NXP spec).


The frequency of the internal clock is 25MHz. The IC also accepts an external clock up to 50MHz.
The refresh rate will be set according to what we need to drive the motors ESC - this will be the function's input parameter (e.g. 250 Hz)

int PCA9685::setPWMFequency (int frequency)
{
 debug_print("[PCA9685] Setting PWM frequency @ %dHz...\n", frequency);
 int prescale = floor((PCA9685_CLOCK_HZ / (float)(PCA9685_RESOLUTION * frequency)) + 0.5) - 1;
 
 debug_print("[PCA9685] Prescale @ %d...\n", prescale);
 
 // preserve original mode
 int mode = this->pBus->ReadRegisterByte(this->devAddress, MODE1);
 
 // disable restart, sleep, set prescale and reset
 this->pBus->WriteRegisterByte(this->devAddress, MODE1, (mode & ~RESTART) | SLEEP);
 this->pBus->WriteRegisterByte(this->devAddress, PRESCALE, prescale);
 this->pBus->WriteRegisterByte(this->devAddress, MODE1, mode);
 
 // wait for oscillator
 usleep(5000);
 
 // enable restart
 this->pBus->WriteRegisterByte(this->devAddress, MODE1, mode | RESTART);
 
 return 0;
}

Set the PWM duty cycle

This function takes 3 parameters: the channel id (between 0 and 15 as the PCA9685 has 16 output pins), and the interval values for when to turn the PWM signal on and off during a period (between 0 and 4095).

The channel id is used to determine what registers to write to as an offset from the base LED0_ON register at the bottom of the addressable register space.

int PCA9685::setPWM (int channel, int on, int off)
{
 int reg = LED0_ON + 4*channel;
  
 this->pBus->WriteRegisterByte(this->devAddress, reg, on & 0xFF);
 this->pBus->WriteRegisterByte(this->devAddress, reg+1, (on & 0xFF00) >> 8);
 this->pBus->WriteRegisterByte(this->devAddress, reg+2, off & 0xFF);
 this->pBus->WriteRegisterByte(this->devAddress, reg+3, (off & 0xFF00) >> 8);

 return 0;
}

All of the code is on bitbucket.

Performance

For the sake of making sure that running this on the Pi is performant enough, I've measured the elapsed time to set a new PWM duty cycle 5000 times in a loop.

I obtained 770 times per second. Assuming that the routine has its own thread running on one of the four cores, that means that in the best case scenario I can update the speed of all 4 motors 193 times/s, half the maximum input frequency of some ESC circuits I've looked into.  That frequency will be lower once we add the PID control loops....

Hopefully that will be enough. I've read on a forum that we need at least 50Hz for marginal performance and flight stability. I'll look into optimizing the I2C interface code just to make sure.

UPDATE

I've optimized the code to achieve 2500 Hz in PWM duty cycle setting instead of 770Hz.
I've done two things to achieve this:
  1. Used the PCA9685 register auto increment feature. It allows me to write an array of bytes to a base register, and the address will auto increment after each byte.
  2. Removed writing the "On" values. Since I don't need a delay before the start of the duty cycle, it can always be 0 and left untouched. I only write the "Off" values to modify the duty cycle length.
UPDATE 8/24

I've been trying to increase the performance of those routines and figured out that I'm bumping into a bottleneck: the I2C bus clock frequency.
The default frequency on the Pi is 100kHz - but it's possible to tweak it !
I've cranked it to 400kHz and now I've achieved 8.3kHz in updating the PWM.
sudo nano /boot/config.txt

Add this line:
dtparam=i2c1_baudrate=400000

Wednesday, 19 August 2015

Making the code public

I've added a mutex using the POSIX pthread librairy to make sure the I2C address selection and IO operations are safe in a multi-threaded environment.

And I'm also making the code public on BitBucket.

https://bitbucket.org/sygnet/quadrasppi_public/src

Tuesday, 18 August 2015

I2C interface class in C++

Now that I have my PCA9685 I2C module properly connected to the Pi I2C bus comes the time of interfacing with it.

I decided to have a pure virtual IBus interface that could be used no matter what the underlying implementation is. The bus doesn't even have to be I2C, could be serial, or whatever. This way you just pass a pointer to the bus object to any routine that needs to communicate on the bus and it just calls pretty generic functions to read/write data to a device address/register pair (the register value could even be abstracted to be just a control command).

Here's what the class looks like:

class IBus
{
protected:

 bool debug;
 
public: 
 IBus (bool debug = false) {
  this->debug = debug;
 }
 virtual int ReadRegisterByte (int addr, int reg)= 0;
 virtual int WriteRegisterByte (int addr, int reg, int data) = 0;
};

The Raspian I2C implementation class derives from it.

class I2CRaspbian: public virtual IBus
{
private:
 
 int fd; // file descriptor 
 int selectedAddr; // currently selected device address
 int SelectAddr (int addr); // change device address selection
 
public:
 
 I2CRaspbian (int port, bool debug = false);
 virtual int ReadRegisterByte (int addr, int reg);
 virtual int WriteRegisterByte (int addr, int reg, int data);
};

The Linux/Raspbian implementation of I2C relies on the concept of device objects that are accessible like all files on the file system through read/write/IOCtl commands. You just need to have the right modules loaded in memory (that's what we did in the last post) and the right headers files to start coding.

#include <errno.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <linux/i2c-dev.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
The libi2c-dev module we installed contains user mode SMBUS function implementation that would normally only be available to a driver in kernel mode - the implementation relies solely on reading/writing through IOCTL ops. This is required to read from a device register. You don't need it if you only want to read data from a register-less device.

The I2C bus device file is opened in R/W access in the class constructor. The port number depends on your Pi revision.

I2CRaspbian::I2CRaspbian(int port, bool debug) : IBus(debug)
{
 // no address currently selected
 this->selectedAddr = 0; 

 // generate I2C device filename based on port number
 char filename[40];
 sprintf(filename, "/dev/i2c-%i", port);
 debug_print("Opening I2C channel on %s\n", filename);

 // open a the device file in R/W
 if ((this->fd = open(filename, O_RDWR)) < 0) {
  printf("Failed to open the bus.\n");
  exit(1);
 }
}
Before writing to or reading from a device, we need to select its address on the bus using IOCTL commands. The I2C_SLAVE is defined in the header we got when installing i2c-dev.
int I2CRaspbian::SelectAddr (int addr)
{
 // make sure to change the device address selection if its different from
 // the previous I/O
 if (this->selectedAddr != addr) 
 {
  debug_print("Changing device address selection.\n");
  if (ioctl(this->fd, I2C_SLAVE, addr) < 0) {
   printf("Failed to acquire bus access and/or talk to slave.\n");
   exit(errno);
  }
  this->selectedAddr = addr;
 }
 return 0;
}
To read from a device register, use the SMBUS interface (translate the read into an IOCTL):
int I2CRaspbian::ReadRegisterByte (int addr, int reg){

 // Select device
 SelectAddr(addr);

 // Read data using SMBUS interface
 int data = i2c_smbus_read_byte_data(this->fd, reg);
 debug_print("Read data from reg 0x%02X: 0x%02X\n", reg, data);
 
 return data;
}
To write to a device register, you can use a standard write command:

int I2CRaspbian::WriteRegisterByte (int addr, int reg, int data) 
{
 // Select device
 SelectAddr(addr);

 // Set buffer to write
 char buf[2] = {0};
 buf[0] = reg;
 buf[1] = data;

 // Write data using standard file write interface
 debug_print("Write data to reg 0x%02X: 0x%02X.\n", reg, data);
 if (write(this->fd, buf, 2) != 2) {
  printf("Failed to write to the i2c bus.\n");
  return errno;
 }

 return 0;
}

Important Note: Due to my implementation where a single pointer to a I2C class object is expected to be used everywhere in the code, we need to pay attention to the case where the process is multithreaded. If two threads are trying to access different devices on the bus, then we must insure that the selected address doesn't change (modified by a parallel thread) until the I/O is performed on the bus. To do this, I'll implement a POSIX mutex to make sure the Address Selection + IO operation becomes atomic.