I'm trying to build a MIDI controller and I'm looking for a clever way of using 20 potentiometers without having to have 20 analogue ports on the Arduino. So far all the functionality I need is for every potentiometer to print its current value on the serial monitor when I turn the knob. Is it possible at all?
By using an analogue multiplexer, like the 74HC4067, you can select which signal gets "routed" to the Arduino:
It works by using four of the Arduino's digital outputs as an "address", to select which path from Ix to COMMON is selected. They you can read the value of the analogue input A0.
This will handle 16 inputs only, so you'll need another multiplexer one for the remaining 4. Actually, this will give you capacity for 32 potentiometers in total. Since the Arduino has another analogue input, you don't need to do any address manipulation, and can share D0 to D3 with both multiplexers. Something like this will do:
If you can't get multiplexers quickly enough, or you have a bunch of small MOSFETs lying around, and you wish to punish yourself for something, then you could try the approach below.
By switching on M1 and M2, you connect a group of up to 8 potentiometers to the power rails, thereby "enabling" them. You switch them on by bringing D0 low. Then you can read their analogue values from A0 to A7.
Then you enable the second group of 8 potentiometers by taking D0 high (disabling the first group), and bringing D1 low, read the values, and so on.
You must ensure you never have two groups enabled at the same time, (by having D0, D1 etc simultaneously low), but if you do, resistors R1, R2 ... R7, R8 ... R10 prevent any accidental short circuiting of the power supply rails.
You can have as many groups as you have spare digital outputs to enable them. You'll need two N-channel MOSFETs and 1 P-channel, for each group of potentiometers.
User misk94555 had a brilliant idea. Power the groups of potentiometers from the Arduino's digital outputs! That's genious, but it does require that you take care to not overload the IO ports. I recommend trying to keep each IO sourcing/sinking less than 2mA, to keep their voltages as close to the power rails as possible. That means ideally the potentiometers should have a resistance of 20kΩ or more.
The circuit would be as follows:
This time, set all digital outputs D0, D1, D2 and D3 to high impedance, normally. If you want to read the first group of potentiometer positions, you set their power rails, controlled by D0 and D1, to high and low respectively, read the values and then return D0 and D1 to high impedance.
To read the second group, set D2 and D3 to high and low respectively, read the analogue values, and return D2 and D3 to high impedance.
You have as many groups as you have pairs of spare digital outputs.
I've never done this, so I can't vouch for its effectiveness, but it sure beats everything else for simplicity. All the complexity is in software.
The only concern I have is that the digital outputs will not be exactly 0V or 5V, so you may lose some precision in your readings. I think it's worth a try though.
I have overlooked the fact that there is still interference from unpowered groups in the last two circuits. It was pointed out that their effect can be compensated for with maths in software (some application of superposition, perhaps), but I doubt it's worth the effort.
Maybe there's something that can be done with diodes, to redeem the idea. Perhaps hold all groups' positive rails high, and only one group's negative low, and use diodes to isolate inactive groups. It reminds me of keyboard scanning, but with pots.
You could use the 74HC4067 analogue multiplexer. You need only one of the Arduino ADC-channels and 4 I/O-pins for switching between the 16 analogue channels.
The ATmega328P has 8 ADC channels, so you can use 7 + 16 ADC-channels.
You can get a module from Aliexpress for 50 cent.
Searche for "CD74HC4067 16-Kanal Analog Digital Multiplexer Breakout Board Modul Arduino" at Aliexpress or eBay.
You’ll need an analogue multiplexer such as a 4051 (that’s old-school CMOS but I’m sure there are more modern alternatives). You’d use a few, in this case three, IO pins to select the input that you want to feed into your analogue input and then perform a conversion. With a few multiplexers you can select from a many inputs as you like. The limiting factor is that you’ll need to wait a moment for the input to stabilise, so the scan rate is reduced if you have a lot of inputs.
Of course it is possible, in multiple different ways.
Use an external ADC, or a few of them, to have multiple potentiometers connected to a single ADC. Then you have ADC near the analog pots to reduce the noise prone analogue wiring and have a digital bus going between ADCs and Arduino.
No need to multiplex analogue signals in this case.
You could also add more MCUs with ADCs as a subsystem for handlind the analog inputs.
Or, you could just throw out the Arduino and buy a MCU that is more suitable to begin with. Many MCUs are much faster and have multiple ADCs with better bit depth for reading multiple analog channels simultaneously, in addition to just having enough analog input pins.
Analog mux chips are great, but they come with serious conditions.
Something I haven't seen mentioned (might have missed it) is that an analog mux has a very real resistance in its "ON" state, and this resistance is not even close to a constant value. Particularly, it varies as a function of the signal level going through it.
For this reason, the input impedance of the Arduino analog input(s) must be very high. In round numbers, if the input impedance is 10x the switch peak resistance, that produces a 10% error in the received voltage. If you pick a better mux chip such that the Arduino input impedance is 100x the switch resistance, this reduces the error 1% - ish. The real math depends on the value of the pot and the wiper position. It is messy but not complex.
I would suggest a simpler alternative solution, without using analog multiplexers or external ADC. You can get away with using just digital open-collector(open-drain) driver like ULN2003:
In this configuration you would activate one transistor at a time, leaving only one channel connected and other pots essentially floating while you perform a measurement.
Posting another answer because Simon Fitch's answer and misk94555's comments got me thinking about arranging resistors in a sort of "keyboard matrix" arrangement.
So basically we have 4 columns and 5 rows to give us a total of 20 pots. Resistors R21 - R24 are used as upper parts for voltage dividers and from the pots themselves only 2 pins are actually connected. High side is powered from pins PB0 - PB3. Low side is powered from PD3 - PD7. So for example, to read R12 value we would set PB0, PB1, PB3 as inputs, PB2 as output (and write 1 to it) then PD3, PD5..7 as inputs and PD4 as output (and write 0). This will form a voltage divider with R22 / R12 and we can read the value from ADC1 channel.
One downside here is that not full range of ADC is used (the voltage will go only from 0 to VCC/2 if R21..24 are the same value as pots) and the measured voltage will have a non-linear relationship with the actual pot's value. But the math to compensate for this is pretty straightforward.
Edit: This of course wouldn't work because as pointed out by @greybeard there are lots of parasitic paths, just like in keyboard matrices. So basically this whole idea is BS.
For the sake of completeness here's a proper version with diodes: (but then it isn't a 'pretty solution' anymore)
All of the promising answers are looking to multiplex/matrix the pots in some way, which is obviously the best way to reduce the part count.
Following the idea of multiplexing... is it required that the pot positions be read with an ADC port? What if, instead, you used a digital input port and measured the time required for an edge transition when charging or discharging a capacitor through the pot, driven by a digital output?
This would require some software work and you likely will want to average multiple readings to get a filtered value. There would be some initial precision issues due to the tolerances of your capacitor(s) which could be improved with a calibration option.