My solution to running out of IO pins

Status
Not open for further replies.

granddad

Well-Known Member
Most Helpful Member
As I kept adding peripherals to a 66 pin PIC project , i realized i was going to end up with loads of connectors, or run out of usable IO pins .. So I scaled down to 44 pic PIC and I2C keeping interrupt provisions for the control and comms inputs. This MCP23017 I2C expander circuit provided the answer. Thought I would share, I still have a few things to code , but basic digits (TM1637) and Encoders work okay (ish)
 
The MCP23017 is my go-to solution when I need a lot of I/O. It can take a little effort to figure out the interface, but after that, the rest is easy.

I'm also an advocate of offloading tasks like LED multiplexing to a dedicated chip like the MAX7219 LED driver. Just send the data and don't worry about the details.

Interesting fact: I have shared the clock and data lines between I2C devices and the SPI MAX7219. The MAX ignores everything when /CS is high, and the I2C devices ignore MAX data not in the right format.
 
I have shared the clock and data lines between I2C devices and the SPI MAX7219.
I didnt consider sharing I2C signals with the TM1637 , as this IC has a non standard I2C bus protocol, it may work .. but may have corrupted the other real I2C devices, MCP23017 is a scratch head device
 
Funny coincidence... I just hooked up and tested an MCP23017 yesterday on an Arduino Nano. It works well but I was disappointed that the I2C module on the Nano won't go much faster than 400-kHz...
 
As a side note, It does not seem possible (may be wrong) with MCP23017 to switch between , having Banked , or Sequential registers and keep the original register contents, I tried to initialize the device sequentially , then check by reading back as banked, Nope ! took a while to sink in.. But a very useful IC , not tried to run higher than 400khz.


 
Last edited:
Those lighted encoders look very nice. How'd they do that (grin)?

As for sequential mode... I left the chip in "bank 0" with "sequential off" which allows updating pairs of registers. I've only tested the MCP23017 ports as outputs so far in my Arduino test program;

Code:
  /******************************************************************************
   *  MCP23017_Test                                                             *
   *                                                                            *
   *  Mike McLaren, K8LH                                                        *
   *  Micro Application Consultants                                             *
   *                                                                            *
   *  MCP23017 I2C driver experiment                                            *
   *                                                                            *
   *                                                                            *
   *                                                                            *
   *  05-Jan-2021    Arduino 1.8.13 / Arduino Nano                              *
   ******************************************************************************/

 //#include <Wire.h>          // switched to 'direct register'

  /******************************************************************************
   *  function prototypes                                                       *
   ******************************************************************************/

  /******************************************************************************
   *  hardware constants, helper macros, variables                              *
   ******************************************************************************/

   #define mcpAddr  0x20<<1   // MCP23017 I2C address shifted left 1 bit

   #define IODIRA   0x00      // MCP23017 register locations (BANK = 0)
   #define IODIRB   0x01
   #define POLA     0x02
   #define POLB     0x03
   #define INTENA   0x04
   #define INTENB   0x05
   #define DEFVALA  0x06
   #define DEFVALB  0x07
   #define INTCONA  0x08
   #define INTCONB  0x09
   #define IOCON    0x0A
   #define IOCON2   0x0B
   #define GPPUA    0x0C
   #define GPPUB    0x0D
   #define INTFA    0x0E
   #define INTFB    0x0F
   #define INTCAPA  0x10
   #define INTCAPB  0x11
   #define GPIOA    0x12
   #define GPIOB    0x13
   #define IOLATA   0x14
   #define IOLATB   0x15

   #define hiNibble(x) ((x >> 4) & 0x0F)
   #define loNibble(x) ((x >> 0) & 0x0F)

   const char hex[] = "0123456789ABCDEF";

  /******************************************************************************
   *  low level UART functions                                                  *
   ******************************************************************************/

   #define RX_READY   (UCSR0A & 1<<RXC0)  //
   #define TX_READY   (UCSR0A & 1<<UDRE0) //

   void put232(uint8_t data)              // ************************************
   { while(!TX_READY); UDR0 = data;       // send byte or character             *
   }                                      // ************************************

   void putStr(char *data)                // ************************************
   { while(*data) put232(*data++);        // send string variable               *
   }                                      // ************************************

  /*                                                                            *
   *  overload function for FlashStringHelper string constants                  *
   *                                                                            */
   void putStr(const __FlashStringHelper *ifsh)
   { PGM_P p = reinterpret_cast<PGM_P>(ifsh); 
     while(char c = pgm_read_byte(p++)) 
       put232(c);
   }                                      //

   uint8_t rxAvail(void)                  // ************************************
   { if(RX_READY)                         //                                    *
       return 1;                          //                                    *
     else                                 //                                    *
       return 0;                          //                                    *
   }                                      // ************************************

   uint8_t get232(void)                   // ************************************
   { return (uint8_t) UDR0;               //                                    *
   }                                      // ************************************

   void putHex(byte work)                 // ************************************
   { put232(hex[hiNibble(work)]);         //                                    *
     put232(hex[loNibble(work)]);         //                                    *
   }                                      // ************************************

  /******************************************************************************
   *  low level I2C functions                                                   *
   ******************************************************************************/

   #define TW_START   0xA4                // (1<<TWINT)|(1<<TWSTA)|(1<<TWEN)
   #define TW_READY   (TWCR & 0x80)       // ready when TWINT returns to logic 1
   #define TW_STATUS  (TWSR & 0xF8)       // returns value of status register
   #define TW_SEND    0x84                // (1<<TWINT)|(1<<TWEN)
   #define TW_STOP    0x94                // (1<<TWINT)|(1<<TWSTO)|(1<<TWEN)
   #define TW_ACK     0xC4                //
   #define TW_NAK     0x84                // 

   #define I2C_Stop() TWCR = TW_STOP      // macro
   
   void I2C_Init()                        // ************************************
   { TWSR = 0; TWBR = 72;                 // prescaler 1, 100-kHz clock         *
   }                                      // ************************************

   byte I2C_Start()                       // ************************************
   { TWCR = TW_START; while(!TW_READY);   // send start condition & wait        *
     return(TW_STATUS == 0x08);           // 1 if found, 0 otherwise            *
   }                                      // ************************************

   byte I2C_SendAddr(byte addr)           // ************************************
   { TWDR = addr;                         // load device's I2C bus address      *
     TWCR = TW_SEND; while(!TW_READY);    // send it & wait                     *
     return(TW_STATUS == 0x18);           // 1 if found, 0 otherwise            *
   }                                      // ************************************

   byte I2C_Write(byte data)              // ************************************
   { TWDR = data;                         // send data byte to slave            *
     TWCR = TW_SEND; while(!TW_READY);    // send it & wait                     *
     return(TW_STATUS != 0x28);           //                                    *
   }                                      // ************************************

   byte I2C_ReadNAK()                     // ************************************
   { TWCR = TW_NAK; while(!TW_READY);     //                                    *
     return TWDR;                         //                                    *
   }                                      // ************************************

  /******************************************************************************
   *                                                                            *
   ******************************************************************************/

   void writeReg(byte reg, byte value)    // ************************************
   { I2C_Start();                         //                                    *
     I2C_SendAddr(mcpAddr);               //                                    *
     I2C_Write(reg);                      //                                    *
     I2C_Write(value);                    //                                    *
     I2C_Stop();                          //                                    *
   }                                      // ************************************

   void writeReg(byte reg, byte valA, byte valB)  // ****************************
   { I2C_Start();                         //                                    *
     I2C_SendAddr(mcpAddr);               //                                    *
     I2C_Write(reg);                      //                                    *
     I2C_Write(valA);                     //                                    *
     I2C_Write(valB);                     //                                    *
     I2C_Stop();                          //                                    *
   }                                      // ************************************

   byte readReg(byte reg)                 // ************************************
   { byte value = 0;                      //                                    *
     I2C_Start();                         //                                    *
     I2C_SendAddr(mcpAddr);               //                                    *
     I2C_Write(reg);                      //                                    *
     I2C_Start();                         // restart                            *
     I2C_SendAddr(mcpAddr+1);             // read command                       *
     value = I2C_ReadNAK();               //                                    *
     I2C_Stop();                          //                                    *
     return value;                        //                                    *
   }                                      // ************************************

/* void writeReg(uint8_t reg, uint8_t value)
   { Wire.beginTransmission(mcpAddr);     //
     Wire.write(reg);                     //
     Wire.write(value);                   //
     Wire.endTransmission();              //
   }                                      //

   void writeReg(uint8_t reg, uint8_t portA, uint8_t portB)
   { Wire.beginTransmission(mcpAddr);     //
     Wire.write(reg);                     //
     Wire.write(portA);                   //
     Wire.write(portB);                   //
     Wire.endTransmission();              //
   }                                      //

  /******************************************************************************
   *                                                                            *
   ******************************************************************************/

   #define _BAUD (115200/2)               // 57600 or 115200 (double speed)
   #define _UBRR (F_CPU/16)/_BAUD-1       // Used for UBRRL and UBRRH 

   int main()                             // ************************************
   { DDRD |= 0b00000010;                  //                                    *
     DDRC |= 0b00100000;                  // PB4(SDA) input, PB5(SCL) output    *
     PORTC |= 0b00110000;                 // pull-ups                           *
     DDRB |= 0x01;                        // mclr Output                        *
                                          //                                    *
     UBRR0 = _UBRR;                       // setup USART                        *
     UCSR0A |= _BV(U2X0);                 //  " (57600 x 2 for 115200 baud)     *
     UCSR0B |= _BV(TXEN0);                //  "                                 *
     UCSR0B |= _BV(RXEN0);                //  "                                 *
     UCSR0C  = 3<<UCSZ00;                 // async' 8/N/1                       *
     put232('\n');                        //                                    *
     putStr(F(" PIC MCP23017 Test\n"));   //                                    *
     putStr(F(" Mike McLaren, K8LH \n")); //                                    *
                                          //                                    *
   //Wire.begin();                        //                                    *
   //Wire.setClock(400000);               // 400-kHz                            *
     I2C_Init();                          //                                    *
     writeReg(IOCON,0b00100000);          // sequential off                     *
     writeReg(IODIRA,0x00);               // port A outputs                     *
     writeReg(IODIRB,0x00);               // port B outputs                     *
     writeReg(IOLATA,0b00000000);         // port A latches                     *
     writeReg(IOLATB,0xAA);               // port B latches                     *

     byte x = 0;

     while(1)                             // **** loop **************************
     { if(rxAvail())                      // get serial working first           *
         put232(get232());                // ok, got it...                      *
       writeReg(IOLATA, x, x);            // test MCP23017 outputs              *
       _delay_ms(50); x++;                // ok, it works                       *
     }                                    // ************************************
   }
 
Last edited:
Status
Not open for further replies.
Cookies are required to use this site. You must accept them to continue using the site. Learn more…