|
Intro to Physical Computing Syllabus Research & Learning Other Class pages
ITP Help Pages |
MultiplexerMultiple Inputs to a Microcontroller Using a MultiplexerOriginal draft by Tom Igoe, Nov. 2008 What do you do when you run out of inputs on your microcontroller? This is a common problem. For example, the Arduino microcontroller module has only six analog inputs, but what if you're building a drum glove, and you want one analog input for each finger, so you can measure how hard each fingertip hits the table when you tap on it? Or what if you're making a touch-sensitive surface that reacts as you move your hand across it? There are several solutions to this problem, but one of the easiest is to use an analog multiplexer. A multiplexer is a chip that has several inputs, one output, and a series of address pins that let you choose which input is connected to the output. click on any image to see the large view To use a multiplexer to expand your sensor range, you connect the multiplexer's output to the microcontroller's input, then connect sensors to the multiplexer's inputs. Then you control the address pins on the multiplexer using a few digital outputs from your microcontroller. The diagram to the left shows a typical multiplexer-to-microcontroller connection. The multiplexer shown is a CD4067B which has 16 inputs and one output. Although this tutorial is shown on an Arduino Mini, the multiplexer can be used with most any microcontroller. By changing the state of the address pins, you change which of the sensors is being read by the microcontroller. For example, if all of the address pins are low, then the sensor on input 0 is connected to the output, and the microcontroller can read it. When A is high and the rest are low, the sensor on input 1 is read. When A is low and B is high, sensor 2 is read.
There's a pattern emerging here: the states of the inputs is a binary number. Here's a table that explains it:
In the example that follows, you'll connect three multiplexers to a microcontroller in order to read 48 photocells, in order to make a light-sensitive surface.
Parts you'll need:![]() Microcontroller. I used an Arduino mini ![]() 3 multiplexers, CD4067B
If you've never used a microcontroller before, you might want to start with a more basic tutorial. Step 1: Connect the microcontroller on the breadboardThe circuit shown here is the basic setup for an Arduino mini connected to a USB-to-serial converter. You can see power and ground from the USB are run to the rails of the breadboard so it's convenient for the other components on the board. The 0.1uF capacitor from the reset pin is connected to the RTS pin on the mini USB adaptor. This enables auto-reset when the serial port is opened, meaning you don't have to press the reset button every time you upload new code. If it gives you problems, you can remove it, and press reset every time.
Step 2: Connect the first multiplexerThe address pins are connected to pins 8, 9, 10, and 11. I chose these pins because it was convenient in terms of the board layout. Pins 8 and 9 on the Mini are on the same side as pins A and B on the multiplexer. You can change the pin numbers if your project needs those pins for something else. Just make sure that pins A, B, C, and D on the multiplexer are connected to four digital output pins in sequence, or the code won't work. The multiplexer's inhibit pin breaks the connection between input and output when you connect it to 5 volts. Connect it to ground so the chip will work all the time. The multiplexer's output is connected to the microcontroller's analog in pin 0.
Step 3: Add one sensorThe multiplexer's inputs are connected to 16 photocells in voltage divider circuits. The circuit for the photocell is just the same as it would be if you were connecting it directly to the microcontroller's analog in. Add one input, then test with the code below.
// the address pins will go in order from the first one:
#define firstAddressPin 8
void setup() {
Serial.begin(9600);
// set the output pins:
for (int pinNumber = firstAddressPin; pinNumber < firstAddressPin + 4; pinNumber++) {
pinMode(pinNumber, OUTPUT);
// set all the pins low to connect in 0 to the output:
digitalWrite(pinNumber, LOW);
}
}
void loop() {
// read the analog input and store it in the value array:
int analogReading = analogRead(analogInput+mux);
Serial.println(analogReading, DEC);
}
When you see values printing out in the Serial Monitor that change when you cover the photocell, you know it works. Step 4: Add fifteen more sensorsThere's not a lot of space around the multiplexer, so it's often easier to connect your sensors on a second board, as shown here. ![]() Eight inputs connected, numbers 0 through 7. It's wise to add all the resistors early on, so you don't have to work around the wires It takes awhile to wire lots of sensors, so it's a good idea to test with just one multiplexer first, to make sure you wired it correctly and that it works. Here's a test program that will print out the sensor values for all 16 channels of the multiplexer:
int sensorValue[16]; // an array to store the sensor values
// the address pins will go in order from the first one:
#define firstAddressPin 8
int analogInput = 0;
void setup() {
Serial.begin(9600);
// set the output pins:
for (int pinNumber = firstAddressPin; pinNumber < firstAddressPin + 4; pinNumber++) {
pinMode(pinNumber, OUTPUT);
}
}
void loop() {
for (int channelNum = 0; channelNum < 16; channelNum ++) {
// determine the four address pin values from the channelNum:
setChannel(channelNum);
// read the analog input and store it in the value array:
sensorValue[channelNum] = analogRead(analogInput);
delay(10);
// print the values as a single tab-separated line:
Serial.print(sensorValue[channelNum], DEC);
Serial.print(",");
}
// print a carriage return at the end of each read of the mux:
Serial.println();
}
void setChannel(int whichChannel) {
for (int bitPosition = 0; bitPosition < 4; bitPosition++) {
// shift value x bits to the right, and mask all but bit 0:
int bitValue = (whichChannel >> bitPosition) & 1;
// set the address pins:
int pinNumber = firstAddressPin + bitPosition;
digitalWrite(pinNumber, bitValue);
}
}
Step 5: Add the other two multiplexersEach multiplexer's output gets attached to a separate analog input of the microcontroller. The first is on analog 0, the second on analog 1, the third on analog 2. Although only three are shown here, you could easily add more for all your analog inputs. The address pins of the additional multiplexers can all be connected to the same four pins on the microcontroller as the first one. You can see in the picture below that they're daisy-chained. Step 6: Add the rest of the sensorsThe remaining sensors connect to the multiplexers in the same way as the ones on the first multiplexer. Because you've got so much wire to add, it's best to plan this in advance, and think about how you're going to organize the board. I checked the spacing on a few sample photocells first, then I removed them and added the resistors where I needed them, then added the connection wires, then finally added the photocells. Step 7: Program for all the inputsNow that you've got the whole board wired, modify the preceding program to read all three multiplexers. It's a pretty simple modification. All you need to do is to add a loop iterating over the whole routine for one multiplexer. Here's the code:
int sensorValue[48]; // an array to store the sensor values
// the address pins will go in order from the first one:
#define firstAddressPin 8
int analogInput = 0;
void setup() {
Serial.begin(9600);
// set the output pins:
for (int pinNumber = firstAddressPin; pinNumber < firstAddressPin + 4; pinNumber++) {
pinMode(pinNumber, OUTPUT);
}
}
void loop() {
// iterate once for every multiplexer (called muxes for short):
for (int mux = 0; mux < 3; mux++) {
for (int channelNum = 0; channelNum < 16; channelNum ++) {
// determine the four address pin values from the channelNum:
setChannel(channelNum);
// read the analog input and store it in the value array:
sensorValue[channelNum] = analogRead(analogInput+mux);
delay(10);
// print the values as a single tab-separated line:
Serial.print(sensorValue[channelNum], DEC);
Serial.print(",");
}
}
// print a carriage return at the end of each read of the mux:
Serial.println();
}
void setChannel(int whichChannel) {
for (int bitPosition = 0; bitPosition < 4; bitPosition++) {
// shift value x bits to the right, and mask all but bit 0:
int bitValue = (whichChannel >> bitPosition) & 1;
// set the address pins:
int pinNumber = firstAddressPin + bitPosition;
digitalWrite(pinNumber, bitValue);
}
}
Step 8: Graph itIt's not so interesting to see the numbers scroll by, so here's a Processing sketch that reads the serial data in and graphs the relative values of the sensors. It reads until it gets a newline character, then splits the string it got on the commas, and assigns each resulting value to a bar in a bar graph. This can be useful to help you figure out which of your sensors is working and which ones aren't.
// import the serial library:
import processing.serial.*;
Serial myPort; // instance of the serial library
int[] sensorValues = new int[48]; // array to hold the sensor values
void setup() {
// set the size of the window:
size(800,600);
// open the serial port. My Arduino shows up as the first port in the list.
// Yours may not, so check to see that you have the right port.
myPort = new Serial(this, Serial.list()[0], 9600);
// don't generate a serialEvent unless you get a linefeed in from the microcontroller:
myPort.bufferUntil('\n');
// clear the serial buffer:
myPort.clear();
// don't draw strokes around the shapes:
noStroke();
}
void draw() {
// nice green background:
background(99,234,120);
// dark green foreground:
fill(63,144,74);
// if there are sensor values, graph them:
if (sensorValues != null) {
// how many sensors? As many as are in the array:
int sensorCount = sensorValues.length;
// iterate over the array, draw a bar for each one:
for (int thisSensor = 0; thisSensor < sensorCount; thisSensor++) {
// calculate the horizontal position of the bar
// based on how many sensor reading you have:
float hPos = (thisSensor* width/sensorCount);
// calculate the height of the bar based on the sensor value:
float sensorHeight = map(sensorValues[thisSensor], 0, 1023, 0, height);
// calculate the starting vertitical position based on the height:
float yPos = height - sensorHeight;
// draw the bar:
rect (hPos, height - sensorHeight, width/sensorCount, sensorHeight);
}
}
}
void serialEvent(Serial myPort) {
// read the serial buffer:
String myString = myPort.readStringUntil('\n');
// if you got any bytes other than the linefeed:
if (myString != null) {
myString = trim(myString);
// split the string at the commas
// and convert the sections into integers:
int sensors[] = int(split(myString, ','));
// make sure you have enough readings:
if (sensors.length >= sensorValues.length) {
// put the readings into the global array:
for (int sensorNum = 0; sensorNum < sensors.length; sensorNum++) {
if (sensorNum < sensorValues.length) {
sensorValues[sensorNum] = sensors[sensorNum];
}
}
}
}
}
When it's working, you should get a graph something like this, except less even, unless you have lit your sensors as evenly as I have: That's it! You can apply this same technique with any type of sensor, analog or digital. Furthermore, you can add more multiplexers to as many available inputs as you have. Go forth and multiplex. Thanks to Chris Cerrito, Amanda Berensohn, and Eduardo Lytton for the inspiration. |