Showing posts with label PCA9546. Show all posts
Showing posts with label PCA9546. Show all posts

Tuesday, September 9, 2014

Updated PCA9546 library

As mentioned in a previous post, I have updated the PCA9546 library in order to fix the problem of one of the methods of the class being implemented as private when it needs to be public.  I also implemented a constructor that allows initialization of the class without selecting any channel as active.  Lastly, I implemented a change to the selectChannel method to allow deselecting all channels.

As promised, I am posting the entirety of the update here for your convenience.

File: PCA9546.h

#ifndef PCA9546_H
#define PCA9546_h

#ifndef P
#define PBUFSIZE (66)
extern char buf[PBUFSIZE];

#define  P(x) strcpy_P(buf, PSTR(x))
#endif

typedef enum 
{
  PCA9546_ERROR = 0,
  PCA9546_SUCCESS
} PCA9546_Status;

#define PCA9546_NOCHANNEL (0) // No channel selected
#define PCA9546_CHANNEL_1 (1) // Bit 1
#define PCA9546_CHANNEL_2 (2) // Bit 2
#define PCA9546_CHANNEL_3 (4) // Bit 3
#define PCA9546_CHANNEL_4 (8) // Bit 4

class PCA9546
{
public:
  PCA9546(uint8_t i2c_address);
  PCA9546(uint8_t i2c_address, uint8_t channel);
  bool selectChannel(uint8_t channel);
  
  PCA9546_Status status;
  uint8_t channel;

private:
  uint8_t i2c_address;
  uint8_t i2c_read();
  void i2c_write(uint8_t data);
};


#endif

File: PCA9546.cpp

/*
 * PCA9546 Library for Arduino
 *
 * MIT License
 *
 * Copyright Jeff Whitlatch - ko7m - 2014
 */

#include <Arduino.h>
#include <Wire.h>
#include "PCA9546.h"
#include "debug.h"


#define DEBUG(x ...)  // Default to NO debug    
//#define DEBUG(x ...) debugUnique(x)    // UnComment for Debug

// Initialize the PCA9546 and disable all channels
PCA9546::PCA9546(uint8_t PCA9546_address)
  i2c_address = PCA9546_address;

  Wire.begin();
  selectChannel(PCA9546_NOCHANNEL);
}

// Initialize the PCA9546 and enable the channel(s) indicated
PCA9546::PCA9546(uint8_t PCA9546_address, uint8_t channel)
  i2c_address = PCA9546_address;

  Wire.begin();
  selectChannel(channel);
}

// Send a channel selection word to the PCA9546
bool PCA9546::selectChannel(uint8_t channel)
{
  // Sanity check value passed.  Only least significant 4 bits valid
  if (channel <= 0xf)
  {
    i2c_write(channel);
    debug(P("Successfully selected PCA9546 channel"));
    status = PCA9546_SUCCESS;
  }
  else
  {
    debug(P("PCA9546 channel selection failed"));
    status = PCA9546_ERROR;
  }
  return (PCA9546_SUCCESS == status);
}

// Write a byte to I2C device.  There is only a single register.  If multiple bytes written, last one wins.
void PCA9546::i2c_write(uint8_t data)
{
  Wire.beginTransmission(i2c_address);
  Wire.write(data);
  Wire.endTransmission();
}

// Read the one byte register from the I2C device
uint8_t PCA9546::i2c_read()
{
  uint8_t rdata = 0xFF;
  Wire.beginTransmission(i2c_address);
  Wire.requestFrom(i2c_address, (uint8_t)1);
  if (Wire.available()) rdata = Wire.read();
  return rdata;
}

Minima Controller - OLED Display

After some initial head scratching, I have my adaptation of the Minima sketch that supports my OLED display up and running on my controller shield.


I was initially not getting any joy out of the OLED display and it remained black despite my efforts.  However, I had stupidly placed the initialization of the PCA9546 multiplexer after the initialization of the OLED display.  Since the OLED cannot be addressed until the mux is set to channel 1, no joy.

I did write a somewhat useful utility that enumerates all i2c device id's that can be seen on the Arduino i2c bus as well as on each of the four channels of the mux which others may find useful.

Debugging this did point out to me an error in my PCA9546 code and a couple of changes I would like to make.

1. I have mistakenly made the selectChannel() method private, when it should be public.
2. The channel argument to selectChannel() is currently a bit field.  The bottom 4 bits indicate which of the four channels should be enabled.  I believe I will change this to just take a channel number rather than a bit field.  I also need a way to deselect all channels.
3. I need to provide a constructor for the PCA9546 class that will allow initialization without selecting any channel.

I will provide an updated listing of PCA9546 class with these changes in a separate post.

Here is the code for scanning for devices connected to the i2c bus and all channels of the PCA9546.

// i2c_scanner
//

#include <Wire.h>
#include "PCA9546.h"

PCA9546 *mux;
char buf[66];

void setup()
{
  Wire.begin();

  Serial.begin(115200);
  Serial.println("i2c Scanner");
  mux = NULL;
}

void printAddress(byte address)
{
  if (address<16) Serial.print("0");
  Serial.println(address, HEX);
}

void loop()
{
  uint8_t nChannel;
  
  for (nChannel = 0; nChannel <= 4; nChannel++)
  {
    Serial.print("Channel ");
    Serial.println(nChannel, DEC);
    byte error, address;
    int nDevices;
  
    Serial.println("Scanning...");
  
    nDevices = 0;
    if (nChannel > 0)
    {
      if (mux == NULL)
      {
        mux = new PCA9546(0x70, 1 << (nChannel - 1));
      }
      else
      {
        bool fRc = mux->selectChannel(1 << (nChannel - 1));
      }
    }
    
    for(address = 1; address < 127; address++ ) 
    {
      Wire.beginTransmission(address);
      error = Wire.endTransmission();
      
      // Just for good measure, try again
      if (error != 0)
      {
        delay(10);
        Wire.beginTransmission(address);
        error = Wire.endTransmission();
      }
  
      if (error == 0)
      {
        Serial.print("i2c device found at address 0x");
        printAddress(address);  
        nDevices++;
      }
      else if (error == 4) 
      {
        Serial.print("Unknown error at address 0x");
        printAddress(address);
      }    
    }
    if (nDevices == 0)
      Serial.println("No i2c devices found\n");
    else
      Serial.println("");
  }

  // Hang the script
  while (1==1);
}

Sunday, September 7, 2014

Minima Controller Shield programming

UPDATED:

As was intended, programming of the controller shield is trivial.  A single additional class was required and no changes to other code or libraries was necessary.  With the Si570 devices behind a multiplexer, the only change necessary to the main Minima sketch is to select the multiplexer channel containing the Si570 you wish to talk to.  For a controller with a single Si570, selecting that channel needs only happen at startup and existing code continues to work unchanged.

I created a driver class for the PCA9546 as follows:

File: PCA9546.h

#ifndef PCA9546_H
#define PCA9546_h

#ifndef P
#define PBUFSIZE (66)
extern char buf[PBUFSIZE];

#define  P(x) strcpy_P(buf, PSTR(x))
#endif

typedef enum 
{
  PCA9546_ERROR = 0,
  PCA9546_SUCCESS
} PCA9546_Status;

#define PCA9546_CHANNEL_1 (1) // Bit 1
#define PCA9546_CHANNEL_2 (2) // Bit 2
#define PCA9546_CHANNEL_3 (4) // Bit 3
#define PCA9546_CHANNEL_4 (8) // Bit 4

class PCA9546
{
public:
  PCA9546(uint8_t i2c_address, uint8_t channel);

  PCA9546_Status status;
  uint8_t channel;

private:
  uint8_t i2c_address;
  bool selectChannel(uint8_t channel);
  uint8_t i2c_read();
  void i2c_write(uint8_t data);
};

#endif

File: PCA9546.cpp

/*
 * PCA9546 Library for Arduino
 *
 * MIT License
 *
 * Copyright Jeff Whitlatch - ko7m - 2014
 */

#include <Arduino.h>
#include <Wire.h>
#include "PCA9546.h"
#include "debug.h"


#define DEBUG(x ...)  // Default to NO debug    
//#define DEBUG(x ...) debugUnique(x)    // UnComment for Debug

// Initialize the PCA9546 and enable the channel(s) indicated
PCA9546::PCA9546(uint8_t PCA9546_address, uint8_t channel)

  i2c_address = PCA9546_address;

  Wire.begin();
  selectChannel(channel);
}

// Send a channel selection word to the PCA9546
bool PCA9546::selectChannel(uint8_t channel)
{
  // Sanity check value passed.  Only least significant 4 bits valid
  if (channel <= 0xf)
  {
    i2c_write(channel);
    debug(P("Successfully selected PCA9546 channel"));
    status = PCA9546_SUCCESS;
  }
  else
  {
    debug(P("PCA9546 channel selection failed"));
    status = PCA9546_ERROR;
  }
  return (PCA9546_SUCCESS == status);
}

// Write a byte to I2C device.  There is only a single register.  If multiple bytes written, last one wins.
void PCA9546::i2c_write(uint8_t data)
{
  Wire.beginTransmission(i2c_address);
  Wire.write(data);
  Wire.endTransmission();
}

// Read the one byte register from the I2C device
uint8_t PCA9546::i2c_read()
{
  uint8_t rdata = 0xFF;
  Wire.beginTransmission(i2c_address);
  Wire.requestFrom(i2c_address, (uint8_t)1);
  if (Wire.available()) rdata = Wire.read();
  return rdata;
}

So, this new class has been added to the Minima source files.  In the Minima main sketch, I add the following code right before including "Si570.h":

#include "PCA9546.h"

I then add the definition of the PCA9546 I2C address right before the one for the Si570:

#define PCA9546_I2C_ADDRESS 0x70

I then create a global variable to hold the PCA9546 object right before the one defined to hold the Si570 object:

PCA9546 *mux;

Ok, so the only thing required now is to initialize the multiplexer and select channel 1.  The rest of the Minima sketch can run without change.  I put the following code right before the initialization of the Si570:

  // Initialize the PCA9546 multiplexer and select channel 1
  mux = new PCA9546(PCA9546_I2C_ADDRESS, PCA9546_CHANNEL_1);
  if (mux->status == PCA9546_ERROR)
  {
    printLine2CEL(P("PCA9546 init error"));
    delay(3000);

  }

I have posted the PCA9546 driver source and integration with Eldon Brown's (WA0UWH) latest Minima sketch on my public branch of my Minima repository on Github.  Please check it out at https://github.com/ko7m/radiono_ko7m.

If integration of the files above provides any grief for anyone cloning my shield, please contact me for assistance and I will be happy to help.  Direct email is ko7m at arrl.net.

Minima Controller Shield checkout

UPDATED:

My Minima controller Arduino shield checks out as functional.  With two Si570 devices on the board, powering them from the Arduino regulator is pushing things, so I have isolated the 3V3 rail from the Arduino and provided my own regulator.  However, with only a single Si570 on the board, I am within limits on the Arduino regulator.  Therefore, I jumpered the power input pin back to the Vin pin on the Arduino to supply power to the shield.  This is the brown jumper wire seen below.  The yellow wire is the ground to my scope probe.



Measuring the voltage off the regulator and the Si570 look good.  Probing the output of the Si570 as seen above, we have the following default frequency of 56.32 MHz output seen below.



For those following along, the schematic of the shield looks like this.  There really isn't anything to it.  Just a multiplexer in front of two Si570's, voltage regulation and breaking out some of the I/O pins to separate cable connections for the Minima.




Now to go figure out how to talk to the multiplexer and the Si570.

I removed the shield from the Arduino Uno and connected up a bus pirate to see if I can talk to the PCA9546.  When searching the I2C address space for devices, initially I got a "response" from all possible addresses.  Clearly this is wrong...

Probing the PCA9546 I found that the voltages were correct, but that the reset pin was held low.  As seen from the schematic above, this is connected to the reset button and the ICSP reset pin.  However, this pin on the shield is not pulled up to any voltage rail and currently relies on the connected UNO to supply this voltage.  So, rather than power the shield from the bus pirate, I plugged it back into the UNO and reinstalled my brown power jumper and just monitored SCL and SDA pins with the bus pirate.



Now interrogating the bus yielded the following results:

Bus Pirate v3
Firmware v4.2 Bootloader v4.2
DEVID:0x0447 REVID:0x3043 (B5)
http://dangerousprototypes.com


I2C>(1)
Searching 7bit I2C address space.
   Found devices at:
0xE0(0x70 W) 0xE1(0x70 R) 0xFE(0x7F W) 0xFF(0x7F R)

Checking the PCA9546 datasheet, we see the following:


If you look at the schematic above, you will notice that I connect A0-A2 to ground, so the device address should be 0x70 (this value is the 0xE0 shifted right one bit to remove the read/write bit) and the read/write addresses should be 0xE0 for write and 0xE1 for read.

So, it looks like the PCA9546 can be seen on the bus and so now it might be time to write some actual code.