CC Blog Design Solutions Research & Design Hub

Get More I/O Now!

Written by Joseph Corleto

Going Beyond Your MCU’s Pin Count

Small microcontroller design projects often require additional I/O. In this article, Joseph presents several methods of expanding digital and analog I/O for these applications. He delves into some of the most common options, such as adding a shift register, a multiplexer, or an I/O expander, along with a few less conventional approaches. You could have as much I/O as your heart desires yet some approaches come with cost, size and development time tradeoffs.


  • What are common methods to expand a microcontroller’s I/O?
  • What are the pros and cons of using shift registers for I/O expansion?
  • How do multiplexers compare to shift registers for I/O expansion?
  • What are the benefits and drawbacks of using I/O expanders like the MCP23S17?
  • What challenges arise from using a dedicated MCU or FPGA for I/O expansion?
  • ESP32-S3
  • 74HC165
  • 74HC595
  • 74HC4051
  • MCP23S17
  • MCP23017
  • FPGA
  • SPI
  • UART
  • I2C
  • JTAG
  • Texas Instruments | www.TI.com
  • ESPRESSIF | www.espressif.com
  • Lattice Semiconductor | www.latticesemi.com

It seems like microcontroller memory just gets bigger and bigger, and often, it doesn’t make a significant dent in program space. When I mention this to the players in the firmware game that have been doing this for a while, they are quick to say how lucky we ‘youngins’ are to have limitless memory so that even the most inefficient code can get the job done right.

And they are correct! I seldom need to optimize my code for limited program space. For speed, yes, I do sometimes need to be sensitive to certain places in my program, but not for the entire program space. Instead, I can focus on abstracting my program such that it has multiple layers of files that nearly anyone can read without a hassle.

Even when I’m developing small microcontroller projects, I typically use only a small part of the memory, while still maintaining the price point the product is supposed to hit. But for the amount of I/O, this is a completely different story. In fact, I often find myself needing more digital and even analog inputs and outputs. Sure, I could just upsize and get a bigger microcontroller, but this is rarely the solution, because the microcontroller seems to be locked down.

Negotiating for a bigger MCU isn’t often a possibility, for reasons I’m given, such as: 1) we have always used this microcontroller and don’t want to deviate; 2) the microcontroller is specialized in its architecture, and the family does not offer more I/O; 3) it takes up more PCB real estate or requires more resources; or 4) It is cost prohibitive to upgrade to a bigger microcontroller. Depending on whom you talk to, the order of importance of these reasons varies. But the common problem is always the same—not enough I/O.

In this article, I present some options for getting more I/O for a given microcontroller design. I explain briefly how each method works, and (if necessary) I provide links to resources for further reading, should you want to get into more detail. Each method has its pros and cons, and I do my best to capture the most important ones for each. Throughout the article, I use the terms GPIO (general purpose I/O) and I/O (input/output) interchangeably, since they essential mean the same thing in the context of microcontrollers.

“IF I ONLY HAD A FEW MORE GPIOS . . .”

“If I only had a few more GPIOS….” These are words I often mutter to myself when I am trying to get a hardware design together for my beloved ESP32-S3-DevKitC-1. Let’s imagine that we are working on a design with this heavy-hitter of a module. Its pinout is shown in Figure 1.

Figure 1
Espressif ESP32-S3-DevKitC-1 shown as you would see it their docs. It has a good amount of GPIO—45 GPIOs, to be exact [1].
Figure 1
Espressif ESP32-S3-DevKitC-1 shown as you would see it their docs. It has a good amount of GPIO—45 GPIOs, to be exact [1].

Would you look at that—45 GPIOs! That is already a lot, but for our hypothetical design, let’s say we need access to one of each bus: SPI (six lines because of two chip-select lines), I2C bus (two lines), USB (three lines, because of an additional VBUS sense line), and one UART bus (two lines). Adding them together, 6 + 2 + 3 + 2 = 13. Already, we are already out of 29% of our GPIOs. And this isn’t an unusual design requirement; I have often seen these buses used all at once within multiple designs across multiple companies.

Oh wait, did you hear that? Our team lead just got a call from our project manager, and guess what? He wants us to have JTAG lines, too, and he wants those reserved for production as well. Boom, another four lines gone. There go 17 lines, or about 38% of our GPIOs. How do we…hold on, I just got a Teams message from the Test Engineering department, and they requested four outputs (they want four relays) and eight inputs because they want to monitor both the Normally Open and Normally Closed contacts for the relays, for a new “Power-On Self-Test” procedure. There go another eight lines. In other words, 29 lines or 64 percent are now used up.

The point I am making here is that with design methodologies—Agile software development, for example— and the need to compete with others, requirements and features often creep in during the design process. So, without much notice, your happy hardware design now has you hunting for “just a few more GPIOs.”

Some ways to get those GPIOs are by using a shift register, a multiplexer, or some type of I/O expander. I will review each method below.

SHIFT REGISTER

If we do the math in our hypothetical example above, we have only 16 of the original 45 GPIOs left. That is not a whole lot, depending on the design requirements. For example, for a project I am releasing soon, I required constant monitoring of 18 buttons, three revision bits, and five PCB model bits. What I wound up implementing was a cascade of shift registers that I could latch data into, using its parallel inputs, and then clocking in the data serially. I already had an SPI bus in my design, so it only took away one additional line. Thus, I could add on my required 24 inputs. Half of that circuit is shown in Figure 2.

Figure 2
Using a parallel-in, serial-out shift register to gain more digital inputs.
Figure 2
Using a parallel-in, serial-out shift register to gain more digital inputs.

Just like that, by giving up only one more digital output—a chip-select line—you can gain many more digital inputs. Even for designs that do not have an SPI peripheral (which is not common, since almost every microcontroller has one built in), if you had to bit-bang SPI, it could be done easily. This shift register methods also works well if you need to add many more outputs. For inputs, shift register part numbers such as 74HC165 or CD4021B are commonplace and are relatively inexpensive. For outputs, part numbers 74HC595 or 74HC4094 are widely used.

The added benefit of using shift registers as outputs is that they usually can source and/or sink more current than your microcontroller can provide. The problem with using shift registers is that PCB real estate gets eaten up for every four or eight I/O you require. These also can only be used for digital I/O, not analog ones. Also, if you have a lot of them, you need to clock previous bits to get to the one of interest. This is usually not a huge issue, though, since these devices can be clocked quickly.

Fun fact: old-school Nintendo and Super Nintendo gaming controllers use this technique to read in all the button’s inputs, and is an excellent demonstration of the use of shift registers.

MULTIPLEXER

In the event that an SPI bus is not available, or the “complexity” of bit banging one is not feasible, a multiplexer is a good option. A common one to use is the 74HC4051, the connections of which are shown in Figure 3.

Figure 3
The internal workings and pinout of a common multiplexer, the 74HC4051 [2].
Figure 3
The internal workings and pinout of a common multiplexer, the 74HC4051 [2].

The cost is four GPIO to control the select and enable lines. It could be three if the enable line were always tied active. Comparing this to the shift register implementation, its not as easy to expand. Once the SPI lines are connected to the shift registers, cascading more of them does not require more I/O. But for this multiplexer implementation, every time we want to add eight more inputs, we need to add at least three more select lines.

But where the multiplexer has an advantage is that it can be used for either inputs or outputs, requiring only one part number to track. The multiplexer provides the ability to sense analog voltage as well. Shift registers can only help us with digital I/O, so performing tasks such as monitoring voltage buses or potentiometer positions is something they can’t do. But the handy 74HC4051 multiplexer does the trick for either digital or analog. As with the shift register, it’s fast in operation, but also its easier to select the data, since we can pick exactly the input or output we are interested in. Cost and size are fairly similar between the two parts, though.

DEDICATED EXPANDER

Indeed, this problem must be so common for hardware and firmware design because there are dedicated IC’s out in the wild to solve the I/O shortage problem. Enter the I/O expander. In essence, it’s like adding on another I/O port to our microcontroller via an SPI, UART of I2C data bus. The most common expanders are the MCP23S17 and MCP23017. They are the same part but one can be used for an SPI bus, and the for the I2C bus. Figure 4 shows its inner workings.

Figure 4
Block diagram of a typical I/O expander [3]. Notice how it has some interrupt logic.
Figure 4
Block diagram of a typical I/O expander [3]. Notice how it has some interrupt logic.

With the expander, Figure 4 demonstrates that we gain something that looks more like a GPIO port in a microcontroller. We have the ability to configure the registers to buy input or output. Other expanders offer internal pull-up or pull-down resistors. Just about all of them have interrupt logic. These features aren’t available with shift registers and multiplexers. We get a nice amount of GPIOs for every expander we add to the data bus of choice—16, to be exact. Additionally, we can add up to eight of these devices, for a total of 128 GPIOs! Must be a dream-come-true solution, right?

Not so fast. An expander comes with a higher price tag. For designs that are highly price-sensitive, an expander may not fit the bill. Also, by choosing a dedicated expander, the design is limited to one family of parts, and designers could get stuck with re-writing drivers all over again if another manufacturer is selected. This additional step is compounded when you consider that the new drivers willrequire additional implementation and testing development by firmware engineers.

As for speed, these are pretty quick devices that feature an interrupt to limit polling routines; but the additional overhead of communication makes these technically slower than the shift registers and multiplexers. Personally, I think exapanders offer the best option if you are stuck with only a UART or I2C bus.

MCU I/O EXPANDER

If you are committed to small-to-medium-sized microcontroller for a project, perhaps the best I/O expansion option is to add another MCU, although this approach might seem counterintuitive at first glance.

By doing this, peripherals such as ADCs, DACs, I/O ports, and communication buses could be expanded relatively inexpensively. This works well when you simply cannot find that special part that does a specific task. It also works when the application demands more computational power and some heavy number crunching. When it gets asked by your main microcontroller for the result, it can be given in any format you desire. Even the communication medium and protocol itself can be customized. You gain ultimate flexibility, given that duplicating a microcontroller (or using a different one) provides all the peripherals you want. These are all major upsides.

But huge gains come with huge headaches. The development time now increases much more than with the previous solutions. When a team of people gets involved, sometimes the human factor gets in the way and slows down development. For example, I have seen debates among engineers explaining why they think their idea of how a protocol and architecture should look is much better than what the engineer next to them is proposing. Sometimes, it is a clear difference, but most times it’s hard to discern if one protocol implementation is better than another. This is where things get bogged down.

But even if everyone works well and there are zero debates (not likely!), there is still the development overhead in general. Firmware maintenance is necessary, and if the microcontroller being used as a special GPIO expander it may need a field update due to a bug, and this creates yet another maintenance headache.

MAKE YOUR OWN I/O EXPANDER WITH AN FPGA

Field programmable gate arrays (FPGAs) are wonderful devices. They are fast and come in many sizes. Do you want a shift register or digital multiplexer solution in a small form factor? It can be done easily in a small FPGA. However, in my opinion, using an FPGA in most circumstances, is not an option. It is not because the package, cost, and size are out of scale. For example, Figure 5 features a popular, inexpensive FPGA.

Figure 5
The CE40UL1K-CM36AI FPGA. It is less than $4 and provides 26 additional I/O [4].
Figure 5
The CE40UL1K-CM36AI FPGA. It is less than $4 and provides 26 additional I/O [4].

However, few engineers know how to create programs for FPGA I/O expanders. It is not like typical software development, because you are telling the FPGA what it should be, not what it should do, as you would with a microcontroller. If you are one of the lucky ones who works in a company where an FPGA engineer is around the corner, or if you possess the skills yourself, and require some serious processing power, then using an FPGA that has a microcontroller inside of it can be a great route to go.

USE A BIGGER MCU

All our previous solutions relied on another external device. But sometimes the easiest thing is “to just get a bigger hammer.” Microcontroller vendors are getting better at providing product families with application program interfaces (APIs) for their ICs. These are called “hardware abstraction layers” (HALs), which are essentially just APIs for using specific microcontrollers or hardware. They are highly specific APIs that hide away all the hardware details. Often, the HALs provided work well with other microcontrollers within the same family and offer little to no code effort. If size is not of concern or cost, then keeping it all under one roof may be the easiest and safest best for the design at hand.

As I mentioned in the beginning of this article, this is hard to justify with special-purposed MCUs. For example, the ESP32-S3 shines because it has a dual core and can easily handle BLE and Wi-Fi comms all by itself. But the option for many more pins for GPIOs is not available yet by the manufacturer. If we decide that we need the ESP32-S3, then one cannot simply “buy a bigger hammer.”

USE / MAKE A . . .

Are you still reading and want more choices? Surely, you can pick one of the previous solutions! In reality, there are a ton of other methods, parts, and techniques that you can use to gain more I/O; but I found that these are not as common in practice. For example, if you’d like to get more digital inputs, and they happen to be buttons, you can wire them up in a matrix configuration and enlist the help of matrix scanning ICs, such as the Texas Instruments TCA8418. Its typical use is shown in Figure 6.

Figure 6
Implementation of the Texas Instruments TCA8418 I2C Controlled Keypad Scan IC [5]. Notice that it uses a matrix connection of the switches, usually employed by keypads.
Figure 6
Implementation of the Texas Instruments TCA8418 I2C Controlled Keypad Scan IC [5]. Notice that it uses a matrix connection of the switches, usually employed by keypads.

This is a narrow case for a design, and requires special care when connecting your switches together. It is also highly specialized, and if replacing this IC should become necessary, it can be real pain that requires the same level of effort as implementing a driver for a GPIO expander.

Another interesting and unusual concept is to use just one GPIO to monitor many buttons. The idea is to use one ADC channel with a resistor network, and arrange the button connections such that no matter the combination in which they are pressed, a unique ADC code will be generated. I won’t get into the details, but if this is something you think you could use, please check out the article on “Multiple Pushbuttons on one pin using adaptive ADC” [6].

If you still want yet another way to read in multiple button inputs (or driving LEDs)—and want to make your head spin—check out the use of tri-state multiplexing, also called “Charlieplexing” [7].

In all seriousness, these last few methods are not all that common in practice, but I encourage you at least to browse the links to understand that they are out there.

CONCLUSION

We all need more GPIO. Features and designs usually creep up the number of I/O desired over the product design cycle. It becomes harder to fulfill this requirement, since price usually must come down, and the overall form factor needs to shrink as well.

I am confident, however, that the techniques and methods discussed here could get you out of a tight place in a project or design. And if they don’t, try to refactor the design or perform a good old Internet search for yet even more ways to grab more GPIOs. Until next time…. 

REFERENCES
[1] https://www.digikey.com/en/products/detail/espressif-systems/ESP32-S3-WROOM-1-N8R8/15295891
[2] https://cdn.sparkfun.com/assets/learn_tutorials/5/5/3/74HC_HCT4051.pdf
[3] https://ww1.microchip.com/downloads/aemDocuments/documents/APID/ProductDocuments/DataSheets/MCP23017-Data-Sheet-DS20001952.pdf
[4] https://www.digikey.com/en/products/detail/lattice-semiconductor-corporation/ICE40UL1K-CM36AI/5129487
[5] https://www.ti.com/lit/ds/symlink/tca8418.pdf?HQS=dis-dk-null-digikeymode-dsf-pf-null-wwe&ts=1714317151611&ref_url=https%253A%252F%252Fwww.ti.com%252Fgeneral%252Fdocs%252Fsuppproductinfo.tsp%253FdistId%253D10%2526gotoUrl%253Dhttps%253A%252F%252Fwww.ti.com%252Flit%252Fgpn%252Ftca8418
[6] https://forums.parallax.com/discussion/172874/multiple-pushbuttons-on-one-pin-using-adaptive-adc
[7] https://en.wikipedia.org/wiki/Charlieplexing#:~:text=Charlieplexing%20(also%20known%20as%20tristate,a%20microcontroller%2C%20these%20entities%20being

PUBLISHED IN CIRCUIT CELLAR MAGAZINE • AUGUST 2024 #409 – Get a PDF of the issue

Keep up-to-date with our FREE Weekly Newsletter!

Don't miss out on upcoming issues of Circuit Cellar.


Note: We’ve made the Dec 2022 issue of Circuit Cellar available as a free sample issue. In it, you’ll find a rich variety of the kinds of articles and information that exemplify a typical issue of the current magazine.

Would you like to write for Circuit Cellar? We are always accepting articles/posts from the technical community. Get in touch with us and let's discuss your ideas.

Sponsor this Article
+ posts

Joseph Corleto holds a Master’s Degree in Electrical Engineering. Aside from working as a full-time Electrical Engineer, he has a small business (Bit Bang Gaming LLC), which creates video game electronics hardware, and is actively pursuing the creation of a project-based video course using the ESP32. He describes himself as an Electrical Engineering handyman since he has skills in firmware, R&D, PCB design, PCB assembly, and automated testing. You may reach him anytime via his email: Corleto.joseph@gmail.com.

Supporting Companies

Upcoming Events


Copyright © KCK Media Corp.
All Rights Reserved

Copyright © 2026 KCK Media Corp.

Get More I/O Now!

by Joseph Corleto time to read: 13 min