Addressing 23017/4051 using I2C Serial Interface & PROGMEM

Hello, I'm trying to understand how to use an I2c port expander to drive 16 LEDs. An Example of what I'm trying to do is coded like this:

#include <Wire.h>
lastLedVal = 0;
// Setup I2C devices
Wire.begin(); // start the I2C interface
// Initilise registers 
gpio_write(ledAddress, ddrTrigger, I2CregisterConfig); // Make into outputs
gpio_write(ledAddress, 0, I2CregisterInput); // turn them all off
doSensorScan(); // get initial states

// the value of threshold determins the on / off point
void lookForChange(){
int ledVal = 0;
int ledMask = 1;
for(int i=0; i<16; i++){
if(currentState[i] < threshold) ledVal |= ledMask; // add a 1 in the position of sensors under threshold
ledMask = ledMask << 1;
}
if(lastLedVal != ledVal) { // something has changed
ledMask = 1;
for(int i=0; i<16; i++){
if((ledMask & ledVal) != (ledMask & lastLedVal)){
if((ledMask & ledVal) == 0) {
// note off
controlSend(0x80, control[i], 0x00); // turn off control message
//noTone(8);
 
}
else{
// note on
controlSend(0x90, control[i], currentState[i]>>3); // turn on control message
//tone(8, notes[i], 20);

}
}
ledMask = ledMask << 1;
}
// Update the trigger LEDs
gpio_writeByteInverse(ledAddress, ledVal, I2CregisterInput);
}
lastLedVal = ledVal; // record current state of LEDs and MIDI notes / messages

}

From my research I've learned that it's called exclusive disjunction.

Logical disjunction [i.e.] exclusive disjunction
The logical operation exclusive disjunction, also called exclusive or (symbolized by the prefix operator J, or by the infix operators XOR, EOR, EXOR, ? or ?, /??ks ??r/ or /?z?r/), is a type of logical disjunction on two operands that results in a value of true if exactly one of the operands has a value of true.[1] A simple way to state this is "one or the other but not both."
Put differently, exclusive disjunction is a logical operation on two logical values, typically the values of two propositions, that produces a value of true only in cases where the truth value of the operands differ.

My question is: are the code snippet and the quote above the same?
A related post [Tronixstuff] MCP23017 | I2C Port | Help wanted - #15 by Pitchoilcan - LEDs and Multiplexing - Arduino Forum deals with wiring and the required libraries, this post is concerned with the coding of the chip.
Arduino - Home
curious thing is that I don't see the caret symbol ^ in the code I see symbols like: &, =!, <<, ==0 etc. I have the arduino ref docs so I recognize shift left, shit right, is not and so on, But I'm little unclear on what the code is doing. Can someone help me understand what the code is doing.

Thanks much.

That article is missing a few (read: all?) of the actual bitwise operators.

If you look at the "Bitwise Operators" section of the Reference Page, you'll see all of them.
http://arduino.cc/it/Reference/HomePage

Comparison and Boolean Operators are shown as well.

IMHO opinion you should think about what you want to do and build it up from there, as this code is doing something very specific.

Here's what the code in lookForChange is doing:

for(int i=0; i<16; i++){
if(currentState < threshold) ledVal |= ledMask; // add a 1 in the position of sensors under threshold
ledMask = ledMask << 1;
}
This builds up an integer sized (16 bits) mask of devices with a 1 in the position where the device's current state is less than the threshold. The loop counter is just used to step through the 16 bits and move the ledMask bit through all 16 positions by the right shift (ie, it start in 0001 then 0010 then 0100, etc). You end up with a pattern of 16 bits defining the devices that meet the condition in ledVal because ledMask is ORed into the original 0 value of ledVal (0 OR 1 is 1, 0 OR 0 is 0).
The next loop uses a similar technique to check the bits. By using AND instead of OR, you can extract the value that was packed into the 16 bits (eg, 0110 AND 0001 is 0000 - we are looking at the value of the rightmost bit - and 0110 AND 0010 is 0010 - this is not zero and so means that the second bit was turned on. The code
if((ledMask & ledVal) != (ledMask & lastLedVal)){
uses this technique to check which bit changes since the last time, as ledMask is used to skip through the bits. Note in this case we don't care what the value is, just that it is not the same as last time.
I think the rest of the code is straightforward. Not sure if this answers your question, but I am confident it explains the code...

Thanks marco_c, what I want to do is something very specific and that is: turn on 1 of 16 LEDs depending on which of 16 sensors and pressed. So when the program starts ALL LEDS are off i.e. writtten LOW, when one of the sensors are turned on (by Touch) a corresponding LED is written HIGH, when the senors are OFF the corresponding LEDs are off. So there are only two values. 0 or 1. One is LED on and the other is LED off. So IYHO this snippet is sufficient to accomplish is objective?

It can be modified to do what you want but it is also probably more complex than it needs to be.

If the LED is on while the input is on, then you really don't need a lot of logic. Remember that the loop function is running all the time (ie, when it is finished it starts again), so you really only need to check that your input and write the same value to the output.

So build up the 16 bits as for the ledMask and then write those same 16 bits to the output. Would that work?

Otherwise the code will do what you need with the right mods.

I forgot that you had I2C in the way so it is not a straight copy/paste between digital pins, but the concept remains (As you responded as I was changing the post).

With all embedded/microcontrol applications, the circuitry needs to be developed with the software(or vice versa) because they are interdependent. If your hardware is EXACTLY the same as the example, then you can also make the code exactly the same.

If you are changing the hardware, then you need to understand what the changes are and modify the code to suit. In this case, you need to understand the characterictics of the original device and those of the device you are using instead and go from there.

OK then, based on what you have said and done, then you should be able to contruct a 16 bit integer with appropriate bits turned on and send it out to turn on the LEDs. I don't think there is a need to check if the status changed from last time, so eliminating all the code that does that will simplify your routine somewhat.

Good luck with your project!

Thanks again, for letting me bend your ear.
from my notes thus far we have Comparison Operators and Bitwise Operators in the code snippet
The pre-setup:

#include <Wire.h>
// Defines
#define I2CregisterInput 0
#define I2CregisterOutput 2
#define I2CregisterPolarity 4
#define I2CregisterConfig 6
#define I2CregisterInterrupt 8
#define I2CregisterExpander 10
// I2C device addresses
#define ledAddress   (0x20 | 0x0)     // address of trigger LED indicators output
// Control Switch device bit masks
#define ddrTrigger 0x00000     // data direction register for trigger indictor LEDs
// Variable definations
int currentstate[16]; // current state of sensors
int laststate[16];    // the last state of the sensors
int threshold = 0x90;  // sets the threshold value in deciding if a sensor is pressd.
int w;

The setup

// Start of code
void setup() {
   w = 0;
// Setup I2C devices
   Wire.begin();                      // start the I2C interface
// Initilise registers 
   gpio_write(ledAddress, ddrTrigger, I2CregisterConfig);   // Make into outputs
   gpio_write(ledAddress, 0, I2CregisterInput);            // turn them all off

The algorithm

// the value of threshold determins the on / off point

  int x = 0;
  int y = 1;
  for(int i=0; i<16; i++){
    if(currentstate[i] < threshold) x |= y;   // add a 1 in the position of sensors under threshold
     y = y << 1;
   }
   if(w != x) {   // something has changed
   y = 1;

     for(int i=0; i<16; i++){
       if((y & x) != (y & w)){
         if((y & x) == 0) {
           // midi note off          
         }
         else{
           // midi note on 
         }
       }
       y = y << 1;
     }
     // Update the trigger LEDs
       gpio_writeByteInverse(ledAddress, x, I2CregisterInput);
   }
   w = x;        // record current state of LEDs 
}

upon review I find it more appropriate to call THIS the OUTPUT section

void gpio_write(int address, int data, int reg) {
  //  Send output register address
  Wire.beginTransmission(address);
  Wire.send(reg);
  //  Connect to device and send two bytes
  Wire.send(0xff & data);  //  low byte
  Wire.send(data >> 8);    //  high byte
  Wire.endTransmission();
}

void gpio_writeByteInverse(int address, int data, int reg) {
  //  Send output register address
  Wire.beginTransmission(address);
  Wire.send(reg);
  //  Connect to device and send two bytes
    Wire.send(data >> 8);    //  high byte
    Wire.send(0xff & data);  //  low byte
  Wire.endTransmission();
}

int gpio_read(int address) {
  int data = 0;
 //  Send input register address
  Wire.beginTransmission(address);
  Wire.send(I2CregisterInput);
  Wire.endTransmission();
  //  Connect to device and request two bytes
 // Wire.beginTransmission(address);
  Wire.requestFrom(address, 2);
 if (!Wire.available()) { } // do nothing until data arrives
    data = Wire.receive();

 if (!Wire.available()) { } // do nothing until data arrives
    data |= Wire.receive() << 8;
  Wire.endTransmission();
  return data;
}

That's a bit vague. What specifically is the problem?

All of those compiler errors will come with a line number at the start that tells you the exact line the error happens, so you should look on that line. Don't worry too much about the number of errors, as the first few often cascade a flurry as the compiler gets confused about where it is. Just focus on the first one and work your way down the list.

Also, make sure that the example code is compatible with the version 1.0 IDE as there were some changes at that time that cause all sorts of errors.

This bit of the code seems not to have a closing brace in the code online:

//This for loop is used to scroll through and store the 16 inputs on the FIRST multiplexer
for (int i=0; i<16; i++)
{
//The following 4 commands set the correct logic for the control pins to select the desired input
//See the Arduino Bitwise AND Reference: & - Arduino Reference
//See the Aruino Bitshift Reference: << - Arduino Reference
digitalWrite(CONTROL0, (i&15)>>3);
digitalWrite(CONTROL1, (i&7)>>2);
digitalWrite(CONTROL2, (i&3)>>1);
digitalWrite(CONTROL3, (i&1));

That would cause a lot of errors as the nesting of braces is all out of sequence after that.

In your humble opinion do you think I'm merging these two sketches correctly? http://mayhewlabs.com/code/Mux_Shield_AnalogIn_Example.pde is the setup that comes with the mux shield. This is my attempt to merge the code snippet we are discussing into the mux shield setup.

121:8: x was not declared in this scope
At global scope:
125:3: 'Serial' does not have a type
126:17: 'i' does not have a type

putting a closing bracket didn't fix it. I fear I've made larger faux pas.

the vanilla mux code is herehttp://mayhewlabs.com/code/Mux_Shield_AnalogIn_Example.pde
and the mpc23017/mcp23016 LED snippets are taken from here http://www.thebox.myzen.co.uk/Hardware/MIDI_Footsteps_files/Footsteps%20Software.zip
MIDI Footsteps
what am I doing wrong?

I have cobbled together your code from the sections above and created a sketch, but I am in no way sure that you have put all the code up as there were gaps and stuff looked like it was missing. Anyway, the code below compiles ok (no errors). No idea if it runs ok, as I don't have your hardware.

You had some syntax errors and then the main problem was your example used an obsolete version of the TwoWire library with calls that needed to be substituted.

Here's a couple of tips:

  • Format the code properly. Often you will see what is wrong (especially missing brackets and such) just from the formatting.
  • Call your variables descriptive names (no need for an essay though!) and it will help you be able to 'read' what the code is doing (eg, instead or x or y call them mask and value).
  • When you post code, leave it all together (ie, don't split it up). Invariably you will miss bits and they are harder for others to download.
#include <Wire.h>

// Defines
#define I2CregisterInput ((uint8_t)0)
#define I2CregisterOutput ((uint8_t)2)
#define I2CregisterPolarity ((uint8_t)4)
#define I2CregisterConfig ((uint8_t)6)
#define I2CregisterInterrupt ((uint8_t)8)
#define I2CregisterExpander ((uint8_t)10)

// I2C device addresses
#define ledAddress   (0x20 | 0x0)     // address of trigger LED indicators output

// Control Switch device bit masks
#define ddrTrigger 0x00000     // data direction register for trigger indictor LEDs

// Variable definations
int currentstate[16]; // current state of sensors
int laststate[16];    // the last state of the sensors
int threshold = 0x90;  // sets the threshold value in deciding if a sensor is pressd.
int w;

// Start of code
void setup() 
{
   w = 0;
// Setup I2C devices
   Wire.begin();                      // start the I2C interface
// Initilise registers 
   gpio_write(ledAddress, ddrTrigger, I2CregisterConfig);   // Make into outputs
   gpio_write(ledAddress, 0, I2CregisterInput);            // turn them all off
}

void loop()
{
// the value of threshold determines the on / off point

  int x = 0;
  int y = 1;
  
  for (int i=0; i<16; i++)
  {
    if(currentstate[i] < threshold) x |= y;   // add a 1 in the position of sensors under threshold
     y = y << 1;
  }
  if(w != x) 
  {   // something has changed
    y = 1;
  
    for(int i=0; i<16; i++)
    {
      if((y & x) != (y & w))
      {
        if((y & x) == 0) 
        {
           // midi note off          
        }
        else
        {
          // midi note on 
        }
      }
         y = y << 1;
    }
    // Update the trigger LEDs
    gpio_writeByteInverse(ledAddress, x, I2CregisterInput);
  }

   w = x;        // record current state of LEDs 
}

void gpio_write(int address, int data, int reg) 
{
  //  Send output register address
  Wire.beginTransmission(address);
  Wire.write(reg);
  //  Connect to device and send two bytes
  Wire.write(0xff & data);  //  low byte
  Wire.write(data >> 8);    //  high byte
  Wire.endTransmission();
}

void gpio_writeByteInverse(int address, int data, int reg) 
{
  //  Send output register address
  Wire.beginTransmission(address);
  Wire.write(reg);
  //  Connect to device and send two bytes
  Wire.write(data >> 8);    //  high byte
  Wire.write(0xff & data);  //  low byte
  Wire.endTransmission();
}

int gpio_read(int address) 
{
  int data = 0;
  
  //  Send input register address
  Wire.beginTransmission(address);
  Wire.write(I2CregisterInput);
  Wire.endTransmission();
  //  Connect to device and request two bytes
  // Wire.beginTransmission(address);
  Wire.requestFrom(address, 2);
  if (!Wire.available()) { } // do nothing until data arrives
    data = Wire.read();

  if (!Wire.available()) { } // do nothing until data arrives
    data |= Wire.read() << 8;
  Wire.endTransmission();
  return data;
}

What version of the Arduino IDE are you using?

write() substitutes for send()
read() substitutes for receive()

if you change them back you should be ok.

If you are using Windows the latest version is 1.0 and you really should get it (free) from the Arduino website.

The version number for the Windows version can be found in the Help menu, About. The popup contains the version number.

Not sure about other operating systems.

Once you get it to compile, make changes a few at a time and compile so that you know what caused the errors (the last few things you added). This is the best way to build up experience in debugging compiler errors.

#include "Defines.h"
#include <Wire.h>
//Give convenient names to the control pins
#define CONTROL0 5    
#define CONTROL1 4
#define CONTROL2 3
#define CONTROL3 2

//Create arrays for data from the the MUXs
//See the Arduino Array Reference: http://www.arduino.cc/en/Reference/Array
int mux0array[16];
int mux1array[16];
int mux2array[16];

void setup()
{
    w = 0;

Well, where is this file?

sketch_may08b.cpp:11:21: error: Defines.h: No such file or directory

sketch_may08b.cpp: In function 'void setup()':
sketch_may08b:28: error: 'w' was not declared in this scope

You don't have "w" declared.

I made a few changes, mainly to make the mux array a 2 dimensional array that allows you to use loops (always easier).

Good job so far. Remember to compile often to work out mistakes more easily.

passeetryagainapril2012.ino (10.5 KB)

Basically the MCP23017 and the MCP23016 are not the same chip. The clue is in the fact that they have different numbers.
While they have roughly similar features there are a number of major differences that stop one being a direct replacement for the other:-

  1. The pinout of the two chips is different therefore any schematic or PCB will have to be changed to accomidate this.
  2. The MCP23017 has an external reset pin that needs driving or at least biasing.
  3. The MCP23016 has an external RC to determine the internal clock frequency, this affects things like time to respond to an interrupt.
  4. Most importantly the registers are at different addresses in the two devices so any software will have to take this into account.
  5. There are also more registers in the MCP23017 giving it more options than the MCP23016.

This is what I have now, but this comes from Grumpy_Mike's project using MCP23016, unmodified.

// Defines
// I2C registers
#define I2CregisterInput 0
#define I2CregisterOutput 2
#define I2CregisterPolarity 4
#define I2CregisterConfig 6
#define I2CregisterInterrupt 8
#define I2CregisterExpander 10
// I2C device addresses
#define ledAddress   (0x20 | 0x0)     // address of trigger LED indicators output
// Control Switch device bit masks
#define ddrTrigger 0x00000     // data direction register for trigger indictor LEDs
  1. The pinout of the two chips is different therefore any schematic or PCB will have to be changed to accomidate this.
  2. The MCP23017 has an external reset pin that needs driving or at least biasing.
  3. The MCP23016 has an external RC to determine the internal clock frequency, this affects things like time to respond to an interrupt.
  4. Most importantly the registers are at different addresses in the two devices so any software will have to take this into account.
  5. There are also more registers in the MCP23017 giving it more options than the MCP23016.

thanks for your reply and post Grumpy_Mike, To start off I couldn't find the mcp23016 online for sale. I wired the 23017 as described in example on the tronixstuff website. And was able to test the LEDS going in sequence a.k.a knightrider scanner (centipede shield library and sketch) so I've had atleast the basic wiring changes for the mcp23017 to work. I haven't gotten the mx shield yet as I wanted to try it with just one maybe two mcp23017's first. Not sure how to bias the external reset pin and most importantly I don't know how to edit the code to accommodate for the register being at different addresses. I hope I don't make you feel as overwhelmed as I do, but my I'm really up against a deadline. marco_c has been a great help in rewriting my sketch but it'll be wrong if I don't provide the right information. And based on what you've said I might have provided some erroneous info. Can you help me at least with points #2 and #4 of your reply. Your 5th point about the MCP23017 having more options makes it some complicated (for me) to use at this point :frowning:

This is what I used to test the mcp23017 chip knightrider style (no human interaction) centipede_shield [macetech documentation]

This is the chip I have and am attempting to use

This is the one uesed by Grumpy_Mike in the midifootsteps project ( from where I get the I2C setup we are discussing)

so parts dealing with the latter should be make to suite the former.....

Example of the MCP23017 using centipede library and sketch. KnightRider style. No Human interaction. The mus shield is wired up to my inputs i.e. Piezo sensors. thought not in use here the midi shield is used to output midi note data (again not used here). Stay tuned.
PS adafru.it has a library for the mcp23017 however Grumpy_mike uses an MCP23016 with just the wire library. Please note that in all cases the wire library in being used

The centipede shield was NOT used in this demo!
My i2c bus wiring
in the above photo I'm using the mega 2560 but in my project and in the test I'm using the duemilanove. The only resistor used are on the 16 attached to the LED pins. Arduino pins 4 and 5 have no resistors at this point.

the library and the code can be found here centipede_shield [macetech documentation]