When building interactive or agent based systems you will find many cases where you need to map inputs to outputs.
Analog sensors such as light detectors, distance detectors, potentiometers, joysticks and microphones are read with either analogRead() or special libraries that return sensor values over a wide number range. Sensor reading values will further vary depending on context and use.
When we want to push those values to actuators such as motors, servos, RGB and single color LEDs or speakers we need to respect the actuator control limits and ranges. Many actuators will be simply controlled with analogWrite() and need a number range from 0-255. But many others will have special needs, a motor may need not just speed information, but also direction data. A servo may only swing through 180 degrees. A speaker may have a range of several thousand hertz.
Given the potential complexity, we need a generic strategy for dealing with all these possibilities.
The Arduino Map function
The Arduino language has a built in map() function that can help us navigate these challenges.
The map() function takes much of the pain out of remapping sensor value ranges to actuator ranges. But we need to pay attention to the context of both the sensor and the actuator.
The map function command looks like this:
int mappedValue = map(rawValue, sourceLow, sourceHigh, targetLow, targetHigh);
where:
mappedValue :: the outcome of the mapping that we will send to our actuators
rawValue :: the raw sensor value we need to map
sourceLow :: the lowest value from our sensor (generally the low value of the source range)
sourceHigh :: the highest value from our sensor (generally the high value of the source range)
targetLow :: the lowest value for our actuator (generally the low value of the target range)
targetHigh :: the highest value for our actuator (generally the high value of the target range)
Visualizing the Mapping Function
Students with strong math backgrounds will likely be comfortable knowing that map() is simply creating equivalent ratios/fractions. For visual learnings that might not be meaningful at all.
The image below depicts an example of a mapping for an imagined sensor controlling a servo. The three horizontal number lines represent the full range of analogRead(), the actual range of the sensor and the control range for a servo.
I have imagined that the sensor range is 200-800. The servo range is 0-180 degrees.
The mappings follow (generic, then specific):
int mappedValue = map(sensorReading, sensorLow, sensorHigh, actuatorLow, actuatorHigh);
Specific mapping for this example:
int mappedValue = map(sensorReading,200,800,0,180);
Examples
It may be helpful to consider two examples.
Pot to Servo
Let’s take the case of a potentiometer controlling a servo. The range of values form the pot (sensor, input) with outer legs connected to power and ground via analogRead() will be 0-1023. The range of values that control a servo (actuator, output) are 0-180.
To map() the pot value to the servo position then would look like this:
int potValue = analogRead(pot_pin);
int servoPosition = (potValue, 0,1023,0,180);
Note that servos hold their position and pot’s hold their position so these are expected to give a stable system. Such a system gives users a strong sense of control. If you mapped a photocell to a servo you could anticipate a jumpy servo because the photocell varies with the tiniest fluctuations in light level. This kind of system can give users a sense of influence but not necessarily control. If needed such a system could be tuned and filtered to give control. I find influence much more interesting creatively.
Follow this Arduino tutorial for a demonstration of this idea.
Photocell to Speaker
Now, let’s consider the photocell mapped to a speaker. The photocell (sensor, input) will be read with analogRead(). But it is unlikely that the environment will cover that whole range. So you need to watch your sensor in context for a while to determine its in context range.
Let’s imagine the photocell has low end readings that are roughly 190 and a high end of 825. A speaker connected to Arduino has a frequency range of several thousand hertz. They range from a low around 31 hertz (click like) to very high pitched sounds over several thousand hertz. Human hearing ranges from 20-20000 hertz. We experience low frequencies as clicks and bass-like sounds. Increasing frequency increases the pitch. Young ears will find the upper range very painful. To start you might try a low frequency of 440, and a high of 2000. You will need to tune this in real life.
Using he imagined values above you could map() a photocell to speaker frequency like this:
int brightness == analogRead(photocell_pin);
int frequency = (brightness, 190,825,440,2000);
Remember — you need to sort out both of the specific number ranges for your context!
Conceptually you can map any input to any output. That is one of the most powerful parts of digital systems driven by code. Not all of your pairings will be pleasing experiences. Try mapping different sensors together — see what feels like a strong fit.
Search for unusual pairings — joy sticks have obvious mappings, what would an unexpected paring and mapping look like ?
Generalized Mapping
Finally it is worth understanding that while our first exposure to mapping may be from sensors to actuators, the principle can be used anywhere you need to convert on number line to another.
In general terms, mapping can be used to relate any range of source numbers to any range of target numbers. The following image depicts a general visualization of the mapping function.
The upper number line is the source range. The bottom number line is the target range. Actual values are given to reinforce the code use, but remember they could be ANY value.
int mappedValue = map(rawValue, sourceLow, sourceHigh, targetLow, targetHigh);
Specific mapping for this example:
int mappedValue = map(rawValue,100,3000,0,100);
Full Example: photocell to speaker
Video
Build the circuits:
Assemble the analogRead()-able photocell sensor circuit and a speaker.
Add Code
// mapping photocell to speaker
int photocell = A0; // where is the circuit ?
int currentBrightness;
// brightness limits for mapping
int brightnessLow = 0; // adjust both of these for your context
int brightnessHigh = 1023;
int speakerPin = 4; // where is the circuit?
// frequency limits for mapping
int speakerLow = 1500; // tune to your desired frequency range
int speakerHigh = 50;
void setup() {
pinMode(photocell, INPUT );
pinMode(speakerPin, OUTPUT);
Serial.begin(9600);
}
void loop() {
// GET LIGHT READING
currentBrightness = analogRead(photocell) ;
Serial.print(currentBrightness);
Serial.print('\t');
Serial.print(brightnessLow);
Serial.print('\t');
Serial.print(brightnessHigh);
Serial.print('\t');
int frequency = map(currentBrightness,brightnessLow,brightnessHigh,speakerLow,speakerHigh);
tone(speakerPin, frequency);
// uncomment below once you have your sensor limits sorted above.
Serial.print(frequency);
Serial.println();
} // end loop
Get the code on tangible github.
What to Expect
With circuits built and code uploaded variations in ambient light will change the pitch of the tone. Explore different mappings with the plotter open to understand how mapping will give you flexibility when constructing relationships between inputs and outputs.