Showing posts with label i2c. Show all posts
Showing posts with label i2c. Show all posts

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.




Monday, 17 August 2015

Enabling I2C

I've received the parts.

  • PCA9685 - the Adafruit module wasn't pre-assembled so I had to do some basic soldering to put the connectors on. Nothing complex, but I had to remaster some old skills.
  • Breakout cable - there were absolutely no indications on the cable as to its proper orientation on the Pi side. I initially connected it the wrong way and I think I may have shorted some GPIO pins while trying to figure out why there were no signals on random pins when playing with some GPIO tools. Beware - use a voltmeter and make sure the pins are coming up at their expected positions on the other side of the cable before attaching any components to it on a breadboard.
Here's what my setup looks like.



Connecting the PCA9685 module is quite simple and straightforward.

Pi connected to  PCA9685
GND GND
3.3V VCC
SDA SDA
SCL SCL

Since I'm not driving any motors yet, I don't need to connect anything else.
Make sure that you connect the PCA9685 VCC pin to the 3.3Volt pin of the Pi, and not the 5V pin.
5V power would need to be connected to the V+ pin to drive motors, LEDs, etc., and would need to come from a separate power supply in order not to drain too much power from the Pi and interfere with its stability.

The PCA9685 board also has jumpers that you can solder to change the address of the module on the I2C bus. By default the address is 0x40.

Once that's all connected, power up the Pi and install the correct tools and libraries. I'll develop in C++, but it doesn't hurt to have the Python stuff as well. By the way, SMBus is a derivative of the I2C bus and that's why some tools are common.
sudo apt-get install python-smbus
sudo apt-get install i2c-tools
sudo apt-get install libi2c-dev
Open /etc/modules
sudo nano /etc/modules
 Add those lines and reboot.
i2c-dev
i2c-bcm2708   
Since I have the Raspberry Pi 2, the I2C bus is on port 1 (instead of 0). To get the list of attached devices, run this command. It will search the /dev/i2c-1 device for all addresses and list them on the console.
sudo i2cdetect -y 1


I'll interface with the device in C++ in the next post...