MCP23S08: 8 Bit I/O Expander: Part Two:
Extra Switch Inputs

In Part One about the MCP23S08, I discussed how to set up one of these i/o expanders as a bank of outputs to drive LEDs.  They can also be used as digital inputs.  They don’t do any good as analog inputs for things like pots.  You can use analog multiplexers for that.  As digital input expanders though, they make an excellent way to expand your systems switch capabilities.  I am using them for banks of tactile switches to step through options to control my synthesizer.  Follow the jump to see how I configure them…

To configure the MCP23S08, we set the i/o expanders registers in the same way described in Part One, except that we place different values in the configuration registers.  I configure the i/o expander in two stages.  I do this because I want to have all the value set in the registers before I turn on the interrupts.  This way I avoid triggering an unwanted interrupt.  So, the first set of transmission configure all the registers and the second turns on the interrupts.  I’m not 100% that this is necessary.  I might experiment with doing it all in one because the values probably aren’t activated until the chip select line goes high.

    tact_cs_enable();//Chip Select Pin goes to 0

    spi_simple_transmit(IOEXPANDER_U15_WRITE);//transmit the address for the chip we are targeting

    spi_simple_transmit(0x03);//transmit the register address for interrupt compare register

    spi_simple_transmit(0xFF);//transmit value for interrupt compare value, anything other than this will cause an interrupt

    spi_simple_transmit(0xFF);//transmit value for interrupt control register, compare present value to value in interrupt compare register

    spi_simple_transmit(0x08);//transmit value for i/o control register – sequential operation enabled, hardware address enable, interrupt active low

    spi_simple_transmit(0xFF);//transmit value for pull-up resistor register – all pull-up registers

    spi_simple_transmit(0x00);//transmit value for interrupt flag register – this write is ignored

    spi_simple_transmit(0x00);//transmit value for interrupt capure register – this write is ignored also

    spi_simple_transmit(0xFF);//transmit value for gpio register – value of i/o – won’t matter because all are inputs

    spi_simple_transmit(0xFF);//transmit value for output latch – ignored because all inputs

    spi_simple_transmit(0xFF);//transmit value for i/o dir = all inputs

    spi_simple_transmit(0x00);//transmit value for input polarity

    tact_cs_disable();

    tact_cs_enable();

    spi_simple_transmit(IOEXPANDER_U15_WRITE);//transmit the address for the chip we are targeting

    spi_simple_transmit(0x02);//transmit the register address for interrupt compare

    spi_simple_transmit(0xFF);//enable the interrupts

    tact_cs_disable();

For this i/o expander, I have eight buttons.  Each button is connected to a pin on the i/o expander on one side and to ground on the other.  The i/o expander ports are configured with their pull-up pins active, so they will have 5 Volts on them when a button is not pressed, and 0 Volts when a button is pressed.  The i/o expander is monitoring for anything other than 5 Volts being on any pin.  When that happens, it locks in the state of its pins in a special register called the “interrupt capture register.”  Then, it activates its interrupt pin. This pin is connected to a microcontroller pin which is similarly configured to trigger an interrupt when it senses a change in voltage.  On the AVR, this is pin D2.

    DDRD = 0xFB;//PORTD Data direction – D7 = Output, D2 = input

    PORTD = 0xFF;//PORTD I/O Value – D7 = High, D2 = pull-up

    EICRA |= (1 << ISC01);//Falling-edge interrupt trigger

    EIMSK |= (1 << INT0);//Interrupt 0 Activate
I set pin 2 to be an pull-up input.  The interrupt is activated when the pin goes from high to low.  I don’t want it to be level triggered because if I don’t process the interrupt right away, it will continue to trigger repeatedly.
When the interrupt is triggered, two things happen.  I set a flag so that the main part of the code knows that an interrupt happened and I set a timer to debounce the switch press.  If we respond to the switch press immediately, the switch will be bouncing between high and low and another interrupt will be triggered.  I set a timer for 200ms in this case.  I’ve been able to get away with less than that in the past without penalty, but these devices appear to be sensitive and 200 ms appears to be a good length of time for a switch press.  The interrupt service routine is below:

    ISR(INT0_vect)
    {
    g_ucext_int_flag_0 = 1;
    g_ucdebounce_timer = 200;//200ms debounce

    }
I haven’t shown the timer routine here.  I’ll assume if you’ve made it this far, you can handle that.
In the main code, I check for this flag to be set and manage all my synthesizer settings and LEDs and anything you imagine I could do based on a switch press.  You can’t process anything in the interrupt routine because of the debouncing unless you had the time to wait.  If you do decide to wait, make sure to clear the watchdog timer.  Anyways, have fun expanding your i/o!
Posted in General Electronics, Rockit and tagged , , , , , , , , , .