Looking for a way to manage 10 buttons with interrupt on 2 pins. - Page 1
Forum section: electronics / projects

author: juaspo
author_rank: Newbie
timestamp: October 07, 2025, 12:58:09 am
content: Naturally we chose to use a button array to get the 10 button input. However we also need an interrupt sequence to know when to process the button press without having to resort to pulling. So this is the schematics we came up with:https://imgur.com/a/10button-circuit-PL5vcq2however this is giving quite unreliable results in two ways.1. The interrupt seems to only work on some buttons.2. Some buttons seem to give a far too big spread in analog value to the point where it overlaps with other button values making it hard to get reliable valuesWe are using an ESP32 Wroover that comes with a 12-bit ADCand the resistors are all 1% tolerancesome of the questions i'm wondering is, will this HW circuit actually work? meaning we have to tune some things in software to get it to work properlyor is it doomed to fail due to reasons I don't yet understand?Would it help to have a bigger spread of the resistor array?

author: Andy Watson
author_rank: Super Contributor
timestamp: October 07, 2025, 01:14:51 am
content: Do you need to simultaneously detect more than one button press? If not, you can maximise the "distance" between buttons with appropriate choice of resistor. On the other hand, if you want to detect any combination button of presses, you are going to need to construct an accurate DAC - look-up 2R2 divider networks.My guess as to why your present circuit is not working and returning semi-random results is "contact bounce". I have no idea how you have configured your software, however, most processors can read an analogue signal many times whilst the button is still bouncing.Edit: Actually, after thinking a bit longer about this: making a 10-bit DAC out of discrete components is asking for reliability problems. I would scrap the idea and use a dedicated processor (like say one of the low-range PICs) to do the button sampling and then pass the result on to the ESP32.

author: wraper
author_rank: Unknown
timestamp: October 07, 2025, 01:35:09 am
content: This approach guarantees widespread failures in the field after a few years. No sufficient wetting current, which means switches will have bad contact resistance after a while. No immunity to switch leakage (TACT switches often become leaky). Not to say there are too many buttons to even try this.QuoteAnother generally dumb idea.

author: Someone
author_rank: Super Contributor
timestamp: October 07, 2025, 02:47:14 am
content: Quote from: Andy Watson on October 07, 2025, 01:14:51 amAssuming they can multi-drop another slave off an I2C or SPI port there are many IO expanders targeting UI switch applications already including debouncing and "attention" interrupt merging. No need to program anything.

author: juaspo
author_rank: Newbie
timestamp: October 07, 2025, 09:05:06 am
content: Quote from: Andy Watson on October 07, 2025, 01:14:51 am- No only 1 button press needs to be detected so yea I could expand to use the entire range of the ADC- For de-bounce I'm currently just using a crude 300ms delay between each button press so this should not be the issue.- and yea using a dedicated IC is also an alternative although now I want to kind of figure out why this is not working as expectedQuote from: wraper on October 07, 2025, 01:35:09 amyea the longevity part is a valid point that I didn't really think of... 🤔 That could actually be reason enough to ditch this for another solution... thanks for the thought.As for the statement that an interrupt is a generally dumb idea, it would be a bit more helpful if you expanded a bit more on that as to why it's a dumb idea.Quote from: Someone on October 07, 2025, 02:47:14 amYea we could add it to I2C so I'll look for a suitable expander... any components that you know and can suggest right of the bat as a suitable one for this application.

author: RoGeorge
author_rank: Super Contributor
timestamp: October 07, 2025, 09:19:40 am
content: Nice project.In case you need digital pins only (so, when no ADC is available), the typical way to add buttons without wasting too many MCU pins is to use 1-2 more ICs, for example this kind of I2C to parallel:https://www.nxp.com/docs/en/data-sheet/PCA9564.pdfhttps://www.analog.com/media/en/technical-documentation/data-sheets/MAX7328-MAX7329.pdfWhen even more buttons are needed, the buttons can be put in an array, so 2 x 8 I/Os can read up to 8x8=64 buttons with 2 more ICs and only 2 MCU pins (for I2C).

author: amwales
author_rank: Regular Contributor
timestamp: October 07, 2025, 09:38:38 am
content: If you only want to use 3 pins for multiples of 8 buttons and don't want to use i2c or spi you can use a 74HC165 they cost about 7 cents, they can be daisy chained to provide multiple of 8.Lots of examples if you google 'using parallel to serial to read button press example'

author: wraper
author_rank: Unknown
timestamp: October 07, 2025, 12:02:34 pm
content: Quote from: juaspo on October 07, 2025, 09:05:06 amBecause switch bounce may generate thousands of interrupts, not to say interrupting something that may be a timing sensitive and cause hard to catch bugs. You should not use ISR unless really necessary. Just scan it constantly with a proper debounce algorithm unless device needs to go into a long low power sleep mode and woken up by a button press.

author: MarkF
author_rank: Super Contributor
timestamp: October 07, 2025, 12:11:28 pm
content: You might try replacing R22 with a diode.I do something similar on my model railroad for occupancy detection. It gives reliable results at varying voltage levels. I'm detecting a motor but your switches should work as well. And you don't have a resistor divider. Any pullup will result in 0.7V drop. I set the comparator level to 0.5V.

author: juaspo
author_rank: Newbie
timestamp: October 07, 2025, 02:56:22 pm
content: Quote from: wraper on October 07, 2025, 12:02:34 pmAh well those risks I'm aware of and that will be something to keep in mind. At this point it is just important to capture any user input when it happens. Meanwhile we also want to minimize load as there are some heavy calculations happening otherwise (which can be interrupted for user input)

author: coromonadalix
author_rank: Super Contributor
timestamp: October 07, 2025, 03:01:48 pm
content: a design like this is not for multi press / simultaneous, and you need to implement anti bounce

author: MarkF
author_rank: Super Contributor
timestamp: October 07, 2025, 05:30:13 pm
content: Quote from: wraper on October 07, 2025, 12:02:34 pmIn this use case, I don't see that switch bounce will be an issue.By the time you get into the interrupt service routine, determine which button was pressed, clear the interrupt, and return; any switch bounce will be long gone.I would wait until the end of the ISR to clear the interrupt instead of first thing (which I usually do). You would clear the interrupt first thing so you don't miss anything. But here you would wait til just before returning in order for any bounce to have settled.The interesting thing would be how fast your program will respond to a switch and how long the button will need to be pressed for it to be detected.

author: wraper
author_rank: Unknown
timestamp: October 07, 2025, 05:59:04 pm
content: Quote from: MarkF on October 07, 2025, 05:30:13 pmFirst of all, switch bounce can last for tens of milliseconds. And those who base their debounce routine on waiting till switch reading becomes stable, are totally clueless about how what they're doing combines with physical/electrical properties of a switch, and design annoying shit that not only has laggy interface but can miss button presses completely. Secondly, with this design, not only there will be single switch bounce, there will be detection of (higher resistance) random button presses.

author: PCB.Wiz
author_rank: Super Contributor
timestamp: October 07, 2025, 06:25:14 pm
content: Quote from: juaspo on October 07, 2025, 09:05:06 amThere are SO16 led drivers of the 1650 series that are sub 5c that scan up to 28 buttons.Not super low power, but the very high volume use has them low cost.If you do not expect multi button sense, a resistor string should be able to work.A modest capacitor is usually added at the MCU side to avoid noise degrading the ADC steps.Your comparator threshold may be wrong, if not all buttons trigger.If you poll anyway, you do not need an interrupt, just poll at 10’s of ms for two the same.Very low power designs can use interrupt wake up, but otherwise it just costs more parts.Buttons direct to 3v3 is generally avoided for ESD reasons, and a resistor string design often has an extra resistor for presence detect, so you can sense a broken cable.

author: xvr
author_rank: Frequent Contributor
timestamp: October 07, 2025, 07:22:15 pm
content: Connect identical resistors in series and connect the buttons between the resistor terminals and the ADC input. This circuit ensures that each button will generate a different voltage, regardless of the resistor tolerances. The resistor tolerances affect the voltage values, but each voltage will be different.

author: temperance
author_rank: Super Contributor
timestamp: October 07, 2025, 08:27:59 pm
content: QuoteAs stated earlier by wraper, the problem is not in selecting resistors but in the wetting current of the switches.

author: Nominal Animal
author_rank: Super Contributor
timestamp: October 07, 2025, 09:11:46 pm
content: I'd use a cheap slave MCU with UART, with two bits identifying keypress, autorepeat, release, or special, and 6 bits identifying the key. The slave MCU would handle autorepeat and debouncing. You really only need one signal, UART RX on host MCU. On the slave, eight pins: UART TX, and 7 pins for a 3×4 matrix. If you add five (six for 12 keys) dual Schottky diodes, you can trivially do infinite rollover. I'd implement debounce in software: dead time after each transition on a key.One possibility would be WCH CH32V005D6U6 in QFN12 (2mm × 2mm), which LCSC sells for 0.1875€ apiece in sets of 5, and for under 0.10€ in sets of 2.5k. I believe, but have not tested/verified, that you can use a wake-up interrupt on the input pins (selecting all outputs), so that the MCU can stay in low-power standby mode until a keypress occurs. It isn'tparticularlylow power MCU, drawing up to 5mA if running on 48 MHz (overkill), and some 12µA in standby mode, but might be sufficient. No crystal needed, just a 100nF supply bypass capacitor.I know many do not like using an external MCU like this, but I do: it allowsmoredesign freedom while simplifying the main design. If one wants to replace a button with say a rotary encoder, all it changes is add or modify the UART report data or format. Minimal impact.

author: langwadt
author_rank: Super Contributor
timestamp: October 07, 2025, 09:40:21 pm
content: Quote from: Nominal Animal on October 07, 2025, 09:11:46 pmthe would be Rube Goldberg silly, just use an MCU with a few more pins

author: wraper
author_rank: Unknown
timestamp: October 07, 2025, 09:56:20 pm
content: ^when someone uses ESP32, IMHO using another MCU is a valid solution.

author: PCB.Wiz
author_rank: Super Contributor
timestamp: October 07, 2025, 11:10:56 pm
content: Quote from: juaspo on October 07, 2025, 12:58:09 amMany designs use resistor strings, so they are valid if done with care.Since you do not care about many-buttons-at-once, that makes a resistor string viable.You can do parallel or series R's, and simple series R's to GND have a couple of advantagesYou do not need to take 3v3 to the button board at all. That helps with ESD, and cuts wiring.There are no phantom buttons: the lowest-R button wins.To give equal voltage steps, the series resistors scale, as shown below. This solution has 11 values, one reserved for presence/option select.At 3.3V each band is ~275mV wide, so you centre your ADC checks on those steps, and this is very ADC tolerant.If you really want button interrupt action, a low power comparator on the MCU side set at 389mV (geometric mean of two steps) will show 'any button press'

author: mikeselectricstuff
author_rank: Super Contributor
timestamp: October 07, 2025, 11:29:16 pm
content: Reading buttons is trivially easy, just use polling or a timer interrupt at a few tens of mS. It rarely makes sense to trigger an int from a button, and makes it hard to debounce. The only exception is where it's needed to wake from sleep, but even then once awake, use polling.They don't need reading very often, reading uses negligible CPU cycles, and by reading periodically there is rarely any need to have any explicit debouncing as bounce times are typically shorter than the polling period.

author: HwAoRrDk
author_rank: Super Contributor
timestamp: October 08, 2025, 02:22:25 pm
content: Quote from: PCB.Wiz on October 07, 2025, 06:25:14 pmI would not recommend these "1650" chips (such as TM1650, GN1650). They have a dumb, flawed implementation of the interrupt output. In my testing, I found they only give an interrupt on the first press of any given key, but if you press the same key again, you do not get an interrupt.I was testing it for use with a numeric keypad for PIN entry, and just happened to be using a test PIN of "0000"; for a while I was very puzzled why I was only getting an interrupt for the first digit's key press, but not the rest! Anyway, essentially it means the interrupt output of these 1650 chips is useless, and you have to poll for keypresses. Well.. maybe not entirely useless; I guess you could at least use the interrupt as a wake-up for the MCU.Instead, I recommend the WCH CH455. That actually has a properly functioning interrupt output. A shame it's not pin-compatible with these 1650 chips, though.

author: Picuino
author_rank: Super Contributor
timestamp: October 08, 2025, 02:37:22 pm
content: I also vote for using a small MCU to manage the mini keyboard and send the data via serial when queried (pooling).You can use a pin for each button with the advantage of being able to use internal pull-up resistors from the MCU, thus saving you from having to use these external resistors.

author: Picuino
author_rank: Super Contributor
timestamp: October 08, 2025, 02:51:22 pm
content: The advantage of this solution is that you can integrate all the anti-bounce logic and the measurement of the time in milliseconds that the key is pressed for, in order to give a different meaning to a long press as opposed to a short press of a button.All this logic is cumbersome, and you can take it out of the main processor and put it in a small microcontroller for this design and for any other design where you want to have a standalone keyboard.

author: wraper
author_rank: Unknown
timestamp: October 08, 2025, 07:43:03 pm
content: Quote from: Picuino on October 08, 2025, 02:37:22 pmI don't recommend that unless you pinch pennies and do not care about reliability. Logitech wireless mice and keyboards are such unreliable trash which are guaranteed start double clicking or missing clicks exactly because of that, often just after a few months of use. I consider it ok to not care about wetting current only with silicone rubber keypads, not switches with all metal contacts. It may be not that noticeable if switches are barely used, though.

author: Nominal Animal
author_rank: Super Contributor
timestamp: October 08, 2025, 08:34:10 pm
content: I admit, langwadt'squipgot under my skin. Edited to fix.Quote from: langwadt on October 07, 2025, 09:40:21 pmRube Goldberg silly would be to use a SPI-to-UART bridge connected to a microcontroller that uses an I²C I/O expander to poll the keys.The case here is similar to using Wiznet Ethernet chips with integrated TCP/IP stack to implement your "IoT" gadget.Let me explain for those who are interested:Your development cost and maintenance burden is only loosely dependent on thesizeof your system, and tightly dependent on itscomplexity. If you find you end up requiring pre-emptive multitasking, you step up a whole level of complexity compared to a simple loop.Interactive gadgets, be they electronic or software, are best implemented using anevent driven approach. It means that you model your inputs and outputs as event sources and sinks, and model the base behaviour as a finite state machine.Let's consider an example where you have an ESP32 with a WiFi stack running off a LiFePo battery, a small keyboard, an e-Ink display to show notes or messages. Why would you want to use a separate ten-cent processor to handle the keyboard, simply providing the keyboard events to the ESP32, instead of integrating all of it to the ESP32? Because that way managing the sleep states on the ESP32 would be much simpler. Both WiFi radio and the keyboard would be simple event sources, and whenever there is no event pending, the ESP32 can sleep. If the ESP32 also reads the keyboard, you have to combine the keyboard scanning to the event loop; usually this means a compromise, say treating the keyboard scanning as an time-based event, or using the initial keyboard interrupt as an event source to cause regular keyboard scans. In any case, the combined code is much more complex than the separated simple event model, with the keyboard providing events via UART, queued by the hardware nicely. Don't take my word for it, use your favourite MCU development board, and compare the two approaches. Don't count the lines, consider the complexity, and how mucheffortit takes to implement and to maintain the two different approaches.The exact same problem plagues most current desktop applications. They are created single-threaded, with user actions performed in the same thread that handles the input event. This means that if the processing takes a bit longer, the user interface freezes. Instead, you treat each user action as astart-doing-thatevent, passing it to one or more worker threads, which will respond with awork-completeevent (that will for example pop the pressed button back up, hide the progress bar overlay, and so on). It's not difficult, justdifferentapproach — and you find you can easily use more than one core for the computations needed, when you shift your thinking this way. This is particularly easy to do on the web in JavaScript, but is rarely done — because it would be"Rube Goldberg silly to bother to do that; the users can just get new computers if our service is too slow for them".

author: temperance
author_rank: Super Contributor
timestamp: October 08, 2025, 09:34:51 pm
content: Quote from: wraper on October 08, 2025, 07:43:03 pmIndeed, mouses,keyboards,... I bought a steelseries mouse to replace a 20 year old mouse because the gliders on that one were worn down too much. Pretty expensive mouse, this new mouser. The mouse lasted about a year and half or something. Guess what you find in this expensive mouse: some tactile switches connected to a microcontroller without pull up resistors other than the microcontroller internal weak pull up resistors. Those came out to be around 110 kOhm... Well done. Just another piece of trash. I now have mouses with optical switches because modern mouse engineers don't know how to size a pull up resistor for a tactile switch...

author: temperance
author_rank: Super Contributor
timestamp: October 08, 2025, 09:56:54 pm
content: Maybe the OP can reframe the question. What is connected to the other pins and does it make sense to use a low power I2C IO expander?

author: wraper
author_rank: Unknown
timestamp: October 08, 2025, 11:32:11 pm
content: Quote from: temperance on October 08, 2025, 09:34:51 pmI fixed my G604 by adding 3.3n MLCC capacitors with 10 Ohm series resistor in parallel to the switch. So it gives contacts some current punch during activation + will hold it activated for like 0.3 ms IIRC if contact bounce happens. Same switches keep lasting for about 5 years already flawlessly instead starting double clicking after 6 months.

author: PCB.Wiz
author_rank: Super Contributor
timestamp: October 09, 2025, 03:25:15 am
content: Quote from: HwAoRrDk on October 08, 2025, 02:22:25 pmAlternative parts could be the even simpler Serial LED drivers.16 outputs in SSOP24 seems to be a high volume sweet spot, and parts like SM16206 & TC5020 are open drain x 16. Remarkably cheap at sub ~5c.Easy to set any wetting currentand the common button bus can drive an interrupt if needed.Their moderate idle current is likely ok here.A simple SPI connection would send 0x0000 to wait, and then any key drives INT low.Then you send a single moving low (LE active) and readback the 1 or more lows from buttons.CLK speed would be adjusted to tolerate the CLK -> iOUT delays.The same approach can be used with open drain parts like TI/Nexperia 74HCS596, tho 2 are needed, but with CMOS logic Icc is lower.A single HEF4021/CD4021 can scan up to 15 buttons, on a SPI interface. Has 3 scan outputs and 5 needs pulldowns. Microamp static Icc levels.

author: Buriedcode
author_rank: Super Contributor
timestamp: October 09, 2025, 03:49:40 am
content: Theres a few tricks you can do with a single IO and some TTL IC's and passives, using the time constant of the passives to determine the state of the inputs of the logic.Someone has already mentioned a 74xx165 - you can put an RC circuit on the load pin, and connect it to the clock, so a long pulse reads in teh button states to teh shift register, and the short pulses are the clock - requires only 2 IO to read 8 inputs. As for an interrupt - you could use the clockline as an input with a pull up/down, and use diodes with the switches - its messy.I used a similar idea but with a 74xx4017 (or the CMOS one if its >5V) that just uses an IO to clock the decade counter that drives the buttons, with an IO reading the buttons common line. An RC circuit on the clock line provides the reset. Not sure how you'll get an interrupt out of that. Maybe have strong pull downs on each switch with a pweak pullup on the common?Both ideas are cheap, simple, but takes up board space when often its just easier to use a micro with more pins.I used both the above examples for reading DMX dip switches.

author: juaspo
author_rank: Newbie
timestamp: October 12, 2025, 09:08:05 am
content: Thanks for all the replies, I'm learning a lot in this threadQuote from: temperance on October 08, 2025, 09:56:54 pmYea to give some more context, It is for an audio analyzing device where the buttons in reality won't be used too frequently as it will mostly be used to set and adjust some parameters rather than requiring active continuous input like for instance a gamepad. I'm using the ESP32-WROOVER module which was picked for its performance specs (mainly for the audio processing requirements). However during design I realised it had a lot less usable pins than I expected. The board is already fully populated due to audio processing with an ES8388 (6pins total incl line in/out detect), I2C, power control, SD-card, status LED control and more. So all and all the pins got used up fast.As for the interrupt and debounce, there is really no reason why polling wouldn't be possible other than I've just been thought to use interrupt as a more "elegant solution". That said, there are and will be some heavy calculations (like FFT and audio features) for the ESP to do that are required in realtime so we want to reduce the amount of cycles it has to do "other stuff" as much as possible.I can also mention that this is a prototype build for a product I hope to be commercially viable so I am trying to get the BOM count and cost down as much as possible as. This is why I am exploring these options. On top of this, space is now also becoming a bit of an issues so solutions requiring expansions might be a problem. That said, longevity and reliability is an important aspect so right now I'm doubting the feasibility of using a resistor net button solution at all with change of wetting current over time.Btw does anyone have an idea why the circuit only produces an interrupt on Keys: 2,5,6,7 and 9 while not getting any interrupt from Keys 1,3,4,8 and 10?Reading the buttons directly I am able to detect all button press and read the values, albeit they fluctuate a bit... so it seems to be something off with the circuit

author: juaspo
author_rank: Newbie
timestamp: October 12, 2025, 10:17:49 am
content: Quote from: PCB.Wiz on October 07, 2025, 11:10:56 pmThis is a nice circuit indeed. My main concern now is reliability over time given that this, like other resistor net solutions are sensitive to resistance drift on the switches. We are using cheap nobrand tactile switches and hope that it will be sufficient. The switches will not be subjected to that many presses in general but that might actually even be worse in terms of oxidation etc.Quote from: Nominal Animal on October 07, 2025, 09:11:46 pmThis is indeed also a possibility and I get the benefits of offloading the button code, however I feel like this would only be reducing complexity if we needed advanced button management. The added complexity of managing multiple MCUs (flashing etc) and maintaining that code feels like it would outweigh our current simple need of just being able to reliably detect button press.I am not really ruling this out yet but rather comparing it with other dedicated ICs to mange the button presses to see which is the lowest hanging fruit to still meet the requirements.Quote from: HwAoRrDk on October 08, 2025, 02:22:25 pmWell that is quite a ridiculous flaw... I mean you couldn't even use it reliably to wake something up as you might have already spent your 1 interrupt per button before the sleepThanks for the warning though cause that would have been annoying to figure out the hard way. But as far as polling for buttons go it seems like a viable option at least.

author: wraper
author_rank: Unknown
timestamp: October 12, 2025, 11:54:26 am
content: Quote from: PCB.Wiz on October 07, 2025, 11:10:56 pmAnd in my life I've seen plenty of faulty PC monitors, TVs, VCRs due to this stupid approach. Often they could not be turned on, turned on/off randomly or experienced some weird behavior due to switches developing some leakage. Had they used normal approach with reasonable wetting current, that would almost never happen.

author: ledtester
author_rank: Super Contributor
timestamp: October 12, 2025, 03:39:46 pm
content: Quote from: juaspo on October 12, 2025, 09:08:05 amMy understanding is that the ESP32s have two CPU cores. Are you fully utilizing both cores?

author: WattsThat
author_rank: Frequent Contributor
timestamp: October 12, 2025, 03:59:11 pm
content: I’m a little late to the party but here is one of the better articles about switch debouncing with polling code examples/ideas.https://www.ganssle.com/debouncing.htm

author: juaspo
author_rank: Newbie
timestamp: October 12, 2025, 04:18:20 pm
content: Quote from: PCB.Wiz on October 09, 2025, 03:25:15 amI'm not sure I understand how this works... I'm a bit lost as to how I a LED-driver can work for reading buttons?Tried searching for SM16206 but only found them with Chinese datasheets

author: juaspo
author_rank: Newbie
timestamp: October 12, 2025, 04:26:05 pm
content: Quote from: ledtester on October 12, 2025, 03:39:46 pmYes it has and yes, the wifi is using the second core for frequent activity and we were thinking of moving over some more activity to the second core so for now its sort of our backup core.

author: PCB.Wiz
author_rank: Super Contributor
timestamp: October 12, 2025, 06:28:12 pm
content: Quote from: juaspo on October 12, 2025, 04:18:20 pmEach button connects to a led drive, and a common button bus line with pull-up feeds your MCU.The led driver is used as a simple shift register, set to lowest current drive.For scan, you shift through a single low/onIf you already have spi parts, you can share clk and mosi pins.A pair of 74HCS596 (open drain shift register) can be used the same way.

author: langwadt
author_rank: Super Contributor
timestamp: October 12, 2025, 06:51:27 pm
content: you already have I2C, just use something like a PCF8575

author: PCB.Wiz
author_rank: Super Contributor
timestamp: October 12, 2025, 08:30:31 pm
content: Quote from: juaspo on October 12, 2025, 10:17:49 amThe switch contact is a fraction of the string, so that has little impact, but dry contact use may be a problem.Below is a new resistor string thatensures the least switch has > 0.1mA and 2.5V.Alps rates their TACT buttons appx like this : MAX (Resistive load) DC 50mA 16V DC Rating (min.) (Resistive load) 10μА 1V DCC&K and Mitsumi also mention 10uA mins on some models.Other vendors mention 0.1mA min, so I selected > 0.1mA and the presence check draws a bit less, to give 23/24 * Vcc

author: Nominal Animal
author_rank: Super Contributor
timestamp: October 12, 2025, 10:50:49 pm
content: Just to make sure you all understand the MCU-keypad approach, consider the attached schematics.AttachedCH32V005D6U6-numpad.pngis fully compatible with standard sealed 3×4 membrane keypad matrixes. Total BOM cost is on the order of 0.27€ in small batches (say a dozen), plus the membrane keypad or tactile keys.My preferred one,CH32V005D6U6-keypad12.png, is for normally open tactile switches, and can handle multiple simultaneous keypresses. Total BOM cost in small batches (say a dozen), including the standard 4.5–6mm tactile switches, would be about 0.45€.I think you'll find it difficult to find even I²C expanders at such prices.(CH32V005D6U6 (QFN12) is 0.1138€ in a full reel, or 0.19€ in lots of five. The resistors and capacitor are less than a cent in lots of hundred or more. Standard tactile buttons (4.5mm×4.5mm to 6mm×6mm) cost less than a cent apiece in lots of hundred or more. NXP BAT54A,215 common anode dual Schottky diodes also cost less than a cent in lots of hundred or more, but basically any BAT54A will perform about the same here.)To program these, one uses a WCH-LinkE dongle (under 8€ from WCH's official AliExpress store, shipped), connecting VCC, GND, and SWIO (pin 10, RX in the schematic), and for examplervprogto upload the new firmware. For firmware development, I suggest using thech32funcore by@cnlohr, and a GCC RISC-V toolchain. I obviously use software debouncing, via dead time after each initial press or release event on the key.Because the keypad could be developed and tested separately, you reduce the ESP32 complexity by not requiring a regular matrix scanning at all. You can use an UART, and use the internal FIFO to check events when you are able to in the main loop. When ESP32 is sleeping, you can simply let it wake up on an UART interrupt. If you also use the RX (TX on ESP32), you can also ask the keypad for the current state of all keys.When developing the ESP32 firmware, you can replace the keypad with an USB-to-serial dongle, and instead feed in the events from your development machine.To me, this is an obvious developmentbenefit, because the overall complexity is reduced, and handling keypad events is no longer time-sensitive, especially if your UART has a built-in FIFO buffer.

author: juaspo
author_rank: Newbie
timestamp: October 13, 2025, 06:43:40 pm
content: Quote from: Nominal Animal on October 12, 2025, 10:50:49 pmthis is actually pretty compelling. Thanks!

author: juaspo
author_rank: Newbie
timestamp: October 13, 2025, 08:09:04 pm
content: Quote from: Nominal Animal on October 12, 2025, 10:50:49 pmWhy is this your preferred one? does it have any obvious benefits over the other one?QuoteI dont have any experience working with these general MCUs. Do they only require the three pins to be programmed? That is they don't require the TX pin to confirm or give any information?How is software support on Linux systems for the programmer etc.And how is support in general (libs, tech specs)? all the pages and information I find seem to be in Chinese which can be a bit of a headache if you get stuck somewhere.

author: Doctorandus_P
author_rank: Super Contributor
timestamp: October 13, 2025, 09:09:56 pm
content: An idea for another method that may work.1. A single 100k pullup resistor.2. Add a capacitor.3. Each switch has a smaller (100 Ohm to 10k) resistor to GND.In this case, when you close the switch you get a negative flank that is easy to detect. And from the RC time you can calculate which button is pressed. Maybe you want to use polling during this time, but I guess you can also do it with a timer peripheral.Some uC's also have an ADC peripheral that can generate an ISR when a certain threshold is reached. With this method you can just use a string of resistors in between your switches. But it's probably best to only use the ADC value after a few more samples have been made so the contact bounce has passed.

author: Nominal Animal
author_rank: Super Contributor
timestamp: October 14, 2025, 03:04:42 am
content: Quote from: juaspo on October 13, 2025, 08:09:04 pmBecause it handles multiple concurrent keypresses just fine. In your use case, the main benefit is that if one of the buttons gets stuck, the rest will still work; only the stuck one will not work. (It also means diagnostic procedure is simpler — although just replacing the keypad, at this price, is more cost-effective. For example, if an entire row or column of keys stops working, then it is more likely the pin or track is damaged, rather than the switches.) The added cost is six common anode dual Schottky diodes, and again, it only works with separate tactile switches, not the ready-made membrane keypads.CH32V005D6U6 should be able to wake up from standby (~ 11µA current) on any keypress event (by setting all columns high and triggering an interrupt on rising edge on any row). If RX is used, it would be useful to connect it also to PC0, so PC0 can be an input and wake up on a falling edge, so that commands from ESP32 to keypad would also wake up the MCU. Timing-wise, if one uses at most 38400 baud, requiring any command from ESP32 to keypad to be preceded by a zero should cover the expected 250µs wakeup time; I'd target somewhat lower baud rate for robustness anyway, say 9600 baud (~ 1ms latency per keypress).(RX or ESP32-to-keypad path would mostly be useful for a single command: please list all currently pressed keys. If you reserve zero for padding and end-of-response, then responses and events could use the exact same format: two bits for event (01 = press, 10 = release, 11 = autorepeat, 00 = reserved), and six bits for key ID, with zero ID reserved. I can elaborate on the protocol a bit if you want, but the basic idea is similar to both PS/2 keyboards and USB HID events as reported by the Linux input subsystem; nothing I've "invented" myself, just reusing known-good bits.)Quote from: juaspo on October 13, 2025, 08:09:04 pmThe CH32V005D6U6 only uses one bidirectional data pin, SWIO, so yes: only three pins needed for programming.Some other CH32Vnnn and CH32X03n use two pins: a bidirectional data pin, SWDIO, and a clock pin, SWCLK, so they need four.This interface can also be used for run-time debugging, including receiving "debug-printf()s" from the running code to your computer via WCH-LinkE (connected via USB). WCH-LinkE also supports ARM SWD and JTAG interfaces, by the way.Quote from: juaspo on October 13, 2025, 08:09:04 pmNo, the one data signal is bidirectional.Quote from: juaspo on October 13, 2025, 08:09:04 pmI use Linux myself, and only open-source tools to program CH32Vnnn and CH32X03n microcontrollers: GCC targeting RISC-V for development, and rvprog for uploading. Instead of vendor libraries and abstractions, check out cnlohr's ch32fun core. Links in my previous post.I find these simple enough that I don'twantlibraries; I end up writing self-contained code for these, because while powerful, they're also quite simple.Note: I/O pin selection is funky. For each pin, you choose between analog/ADC (if supported), GPIO, or peripheral. For each peripheral, you can choose between a small number of alternate pin sets. I haven't tried enabling conflicting sets, but I suspect it might be somewhat destructive. To pick the pins, I create a spreadsheet with the pin combination options, and see which set of peripheral configurations gives me what I want.Quote from: juaspo on October 13, 2025, 08:09:04 pmAgain, I use the available open source information, and consider the English datasheet and reference manual complete enough for my needs.Quote from: juaspo on October 13, 2025, 08:09:04 pmIf you openhttps://www.wch-ic.com/products/CH32V005.htmland scroll down to the bottom of the page, you'll see "Datasheet:" followed by two links. Open those two links in new tabs/windows, and click on the blue Download button on each. This gives you the English datasheet and reference manual. For me, these suffice.For a tutorial, I suggest you look at Stefan Wagner'sStefan Wagner's ch32v00x projects at OSHWLab, which have both hardware (schematics) and software (C/C++ sources) provided. (He's the maintainer of rvprog, the Python program one can use with WCH-LinkE to upload new firmware to CH32Vnnn and CH32X03n, instead of using WCH's tools.)You could also program these in the Arduino environment by using theopenwch ch32 Arduino core, by adding the board definition files for the board and MCU you use by modifying the existing one for CH32V003 evaluation board. I do not, because the Arduino abstraction is quite "heavy", and instead do my own.