Port Expander compatibility - MCP23017 + Teensy 4.0

Status
Not open for further replies.

sid8580

Well-known member
It looks as if the MCP23017 is sold as a general purpose port expander for a wide range of 3.3v-5v platforms. Is there anything to be concerned about, if I use this with a T4.0?

For example the MCP23017-E/SO (randomly chosen variant at the top of a Google search) on Mouser is 16-bit, with a max speed of 1.7MHz. The T4.0 is 600MHz - the 1.7MHz max applies to I2C bus speed then? And 16 bits... the T4.0 is 32 bit. Again, I'm assuming these are specifications apply only to the bus, not the microcontroller itself?

Apologies if this sounds idiotic; I'd just rather not assume!

Thanks!
 
I've used the MCP23017 on a Teensy 4.0. It works fine, providing:

You use 3.3v for the VIN on the MC23017. It can be harmed with 5v input from the I2C device. If you must deal with 5v devices on the I2C bus, there are ways to shift the voltage levels so you don't fry the Teensy. The MCP23017 will work with either 3.3v or 5v applied to the VIN; (and)

I2C on the Teensy requires external pull-up resistors, unless the device has its own pull-up resistors. On the Teensy if you have a fairly simple I2C bus layout, you would want one 2.2K resistor between SDA (pin 18) and 3.3v, and another 2.2K resistor between SCL (pin 19) and 3.3v. Higher resistor values such as 4.7K usually used for 5v systems will generally work up until 10K or so. If you have a complex bus and/or long wires, you may need to use other resistors.

The default I2C bus speed is fairly slow. Generally for things involving human reaction times, it is not too slow.

There is an alternative call in the MCP23017 library where you can get 8 bits at a time, which reduces the number of I2C calls that must be made. For button presses, you can attach a pin to the Teensy which be attached to an interrupt, so that you know when a button in a group of 8 buttons has been pressed (i.e. you don't have to poll each button all of the time, you get an interrupt that tells you to poll the buttons in the next loop call).

I2C is a shared bus, where each slave device has an address. The MCP230917 has sevaral pins/solder pads that allow you to change the address. By doing so, you can attach multiple MCP23017's to a system.
 
Thanks. I've been down the road of I2C bus devices a few times now, mainly just curious as to how the speed and bit width disparity between the Teensy and a chip like this actually plays out. But this is a great list of things to bear in mind, I usually forget at least one of them in the process of experimenting with a new device :D Had to wonder about whether the vastly faster T4.0 would begin to have issues with any of this stuff and it looks like I will be fine in this case.

About using the 8-bit alternative call to gain the ability to use interrupts, that is really good info, I have many functions that are timing critical (sending MIDI data, reading a gyro/IMU every 4ms, etc.) - the more lean I can make things, the better.
 
Hi - I've used the SPI variant of these expanders - MCP23S17 and they work fine on the T4 and even with sub-optimal breadboard layout I have successfully overclocked them to 18MHz. In my use-case I am reading the outputs of ~20 rotary encoders using interrupts with this library -https://github.com/sumotoy/gpio_expander
and I see <3us latency interrupt<->read complete for 16 ports, and majority of that consists of the time to clock the data out, the T4 is vastly faster than that!
 
mainly just curious as to how the speed and bit width disparity between the Teensy and a chip like this actually plays out

How it actually plays out depends upon how you write the software.

1: Wait for the I/O operation to complete, and block interrupts while waiting
2: Wait for the I/O operation to complete, allowing interrupts (the default)
3: Start the I/O operation and later detect when it's completed, or ended in error.

With SPI and I2C, case #2 is by far the most common way. While waiting, your program can't do any of the other stuff it normally does. But interrupts do still work, so at least the interrupt-based part of any other libraries and code you've written will work while you wait.

So if your program uses any of the 7 serial ports at a relatively high baud rate, the interrupts will allow the serial driver code to move the incoming data from the serial port FIFOs into the larger buffer in memory. But your program won't be doing its normal work to check if new data has arrived in that buffer. If you don't wait too long, the buffer will simply have a little more data available for you to read when you're done waiting. But if you do wait too long, where "too long" depends on how quickly the incoming data speed can fill up that buffer, then you could possibly lose incoming bytes.

Almost everything using interrupts has a similar consequence, where the interrupt code handles the very urgent matters, but your program must still do the other work to make things operate. For another example, if using the Audio library, the interrupts will allow the audio system to keep working without glitching or disrupting your sound. But if the sound is a song or tune where your program changes the pitch and amplitude of synthesis oscillators or effects, waiting for an I/O operation to complete may mean your program makes the next change to audio settings slightly later than it would have otherwise. While those sorts of tiny timing variations aren't audible to humans, again the net effect can depend greatly on how you write your code. If you do the simplest thing, like a fixed delay between making the audio settings changes, those tiny extra delays could cause a cumulative increase in the total time taken. Even that may not matter in most cases, but if your project depends on keeping pace with some other system (or maybe musicians or machines playing other parts in harmony) then small accumulating delays might matter. Even there, good use of millis() or elaspedMillis could compensate for such an issue... so how timing issue really play out all depends on the needs of your project and how you craft the code.
 
I really appreciate the extra depth, Paul.

Now just indirectly directly related to the port expander, as I'm thinking aloud -

My current project uses gyro/IMU data (among many other things!) and I fought against yaw-axis drift for a long while. The source of the problem was timing; eventually it dawned on me that if IMU readings weren't taken at perfectly consistent intervals, it'd be like measuring the speed of a car by counting the number of stop signs passed, but without consistently knowing how much distance was between them... i.e. not awesome.

The problem got worse, the more stuff I tried to get the MCU to do (this is a motion and proximity based MIDI controller, wearable, wireless, and with an onboard visual UI, save/load configs, multiple performance macros, haptic and LED user feedback, there's a lot going on). After lots of battling to improve the efficiency of the software and even trying to write auto-compensation routines to adjust for the MCU load - I eventually learned I could take sensor readings via interrupt and that changed everything. So long as I don't go TOO crazy (right now, it's stable while sampling direction every 5ms/200hz), it's really accurate now and drift - while it still exists, it's no longer a show stopper.

The IMU itself isn't running though this port expander, instead I've got all of the chassis buttons and some status LEDs connected to it (which need to be read at a certain rate for the debouncer function). But add the RF transceiver, and as you mentioned some external timing requirements (it's converting movement to MIDI data, which it will eventually be able to store in a sequencer-like fashion), all already in place/working... and the possibility of some day making it a self-contained synth (perhaps via a second Teensy onboard just for that role) - timing only becomes more critically important.

I need to learn more about the specifics of interrupt use, what I've done so far is basic. I think I could go so far as to use one just as a sort of clock generator for the entire software, on which to base several things like to limiting MIDI output (and thus RF transmissions), and clock sync. The millis() timer method is useful but isn't consistent when things are under heavy processor load. I know I can't magically invent clock cycles by making certain things interrupt driven but I could put more thought into prioritization and use an interrupt as a more consistent timing source.... maybe...
 
Status
Not open for further replies.
Back
Top