Learning Goals 5 min
- Use
map(value, fromLow, fromHigh, toLow, toHigh)to convert a value from one range to another in a single readable line. - Use
constrain(value, min, max)to safely clip a value into a range, preventing it from blowing past the limits. - Combine the two so a noisy or out-of-range sensor reading is mapped cleanly to a useful output range (e.g. ADC 0–1023 → analogWrite 0–255).
Warm-Up 10 min
Yesterday you computed a percentage by hand: (raw − min) × 100 / (max − min). That formula works for any range conversion, but it's clunky. The Arduino has a built-in that does the same arithmetic in one line: map().
Quick puzzle
What value does this return?
map(512, 0, 1023, 0, 255)Reveal
127 (technically 127.6, rounded down to 127). The function says: "512 is in the middle of 0..1023; what's the value at the same fractional position in the range 0..255?" Answer: roughly 128. That's the exact maths you'd use to scale an ADC reading (0–1023) to a PWM value (0–255). One call, no maths in your sketch.
New Concept · Two tools, used together 20 min
map() — re-scale a value between ranges
map(value, fromLow, fromHigh, toLow, toHigh)It computes the linear scaling. Four endpoints, one input, returns the mapped output. Mathematically:
result = (value - fromLow) * (toHigh - toLow) / (fromHigh - fromLow) + toLowYou don't need to memorise the formula — that's the whole point of the function. Use cases:
| What you have | What you want | Call |
|---|---|---|
| ADC reading 0–1023 | PWM 0–255 | map(raw, 0, 1023, 0, 255) |
| ADC reading 0–1023 | Servo angle 0°–180° | map(raw, 0, 1023, 0, 180) |
| Pot 0–1023 | Volume 0–100% | map(raw, 0, 1023, 0, 100) |
| Thermistor mV 0–5000 | Temp display 0°–40° | map(mV, 0, 5000, 0, 40) (very rough!) |
Inverting the range
The clever bit — you can flip the to-range:
map(raw, 0, 1023, 255, 0) // LOW raw → HIGH PWM, HIGH raw → LOW PWMUseful when your sensor is "backwards" — e.g. an LDR with a pull-down where dark = low ADC. Flip the to-range so "dark" gives you a HIGH brightness number.
constrain() — clip to a range
constrain(value, lo, hi)Returns value unchanged if it's between lo and hi. Returns lo if below; hi if above. The simplest possible safety guard:
value | constrain(value, 0, 100) |
|---|---|
| -50 | 0 |
| 0 | 0 |
| 50 | 50 |
| 100 | 100 |
| 250 | 100 |
Why you almost always need both
The catch with map: it doesn't clip. If your input is outside the from-range, the output drifts outside the to-range — sometimes negative, sometimes way too high. Example:
map(1100, 0, 1023, 0, 255) // returns 274 — outside analogWrite's safe range!Feed 274 to analogWrite and the duty cycle wraps around badly. Always wrap a map in a constrain:
int bright = map(raw, 0, 1023, 0, 255);
bright = constrain(bright, 0, 255);
analogWrite(LED, bright);Belt and braces. The map handles the maths; the constrain stops weird inputs damaging anything. Same idea as wrapping a turn at a roundabout — even if you do it perfectly, the lane markings are there in case you don't.
Integer-only maths
Both functions work on ints and return longs. They do not do floating-point. For a 0–1023 → 0–180 mapping, map(512, 0, 1023, 0, 180) returns 90 — perfectly fine. For maths that needs decimals (Celsius from a thermistor) you'll use float directly without map.
Worked Example · Pot-controlled LED brightness, the clean way 20 min
Step 1 — wiring
Pot wiper → A0 (outer legs to +5V and GND), LED + 220 Ω resistor on pin ~9. Same as L02-07 stretch.
Step 2 — the v1 sketch (using division)
Yesterday you wrote:
int raw = analogRead(POT);
int bright = raw / 4;
analogWrite(LED, bright);That works but is fragile. If the sensor range changes (say you replace the pot with a 0–2.5 V sensor), the / 4 no longer matches. And 1023 / 4 = 255 exactly only by accident — for a sensor with a 0–800 range, the equivalent magic number would be 800/255 ≈ 3.14, which rounds badly.
Step 3 — the v2 sketch (using map + constrain)
// L02-10: pot dimmer using map + constrain
const int POT = A0;
const int LED = 9;
void setup() {
pinMode(LED, OUTPUT);
Serial.begin(9600);
}
void loop() {
int raw = analogRead(POT);
int bright = map(raw, 0, 1023, 0, 255);
bright = constrain(bright, 0, 255);
analogWrite(LED, bright);
Serial.print("raw: ");
Serial.print(raw);
Serial.print(" bright: ");
Serial.println(bright);
delay(100);
}Step 4 — try a non-standard sensor range
Suppose the pot only swings 100–900 (maybe its end-stops are slightly miscalibrated). With v1 (raw / 4) you'd never reach full bright (900/4 = 225 max) and never quite hit off (100/4 = 25 min). With v2 just change two numbers:
int bright = map(raw, 100, 900, 0, 255);
bright = constrain(bright, 0, 255);Now the dim end of the pot maps to 0 PWM and the bright end to 255 — and any reading below 100 or above 900 is safely clipped. This is the pattern you'll use for every analog sensor in Level 2.
Step 5 — combine with L02-09 calibration
You can feed the calibrated sensorMin and sensorMax straight into map instead of hard-coding 0 and 1023:
int bright = map(raw, sensorMin, sensorMax, 0, 255);
bright = constrain(bright, 0, 255);That single line is the heart of every "sensor → output" sketch you'll ever write. Combined with calibration in setup(), it's a tiny pipeline: raw → calibrated range → useful range → safely clipped → output.
Basic 7 min
Goal: Map the pot reading to a percentage (0–100) and print it. Two-line conversion using map + constrain.
Challenge 1 7 min
Goal: Map the pot to an inverted brightness — bright pot = dim LED, dim pot = bright LED. Use map's swapped to-range.
Challenge 2 6 min
Goal: Use a single pot to control THREE outputs at once — an LED (PWM brightness 0–255), the on-board LED (digital on/off via threshold), and a Serial-printed angle (0–180). Each output uses the same raw reading mapped differently.
Challenge 3 · The thermometer dial 15 min
Use the LDR (10 kΩ pull-down on A1) as a stand-in for a temperature sensor. Pretend the readings represent "temperature": 50 raw = 0°C (cold), 800 raw = 40°C (hot). Use map + constrain to compute "temperature" from the LDR reading and print it.
Then add: if the "temperature" is above 30, light the on-board pin-13 LED. (As a "too hot" warning.)
It works if:
- Cover the LDR with your hand → temp prints low (e.g. 0–5°C). LED off.
- Normal room light → temp prints around 20°C. LED off.
- Shine your phone torch on the LDR → temp prints high (e.g. 35–40°C). LED on.
- Test extremes: even with no light at all (LDR fully covered) the temp doesn't go below 0; even with the torch ramming straight in, it doesn't exceed 40. That's
constraindoing its work.
Recap 5 min
map(value, fromLow, fromHigh, toLow, toHigh) re-scales a value between two ranges in a single line. constrain(value, lo, hi) clips a value into a range. Use them together — always wrap a map in a constrain — and you have a clean, reusable pipeline from any sensor to any output. Tomorrow we put the whole Level 2 sensor toolkit (read, smooth, calibrate, map, constrain) to work on a real thermistor.
map(value, fL, fH, tL, tH)- Built-in. Linearly re-scales
valuefrom the rangefL..fHtotL..tH. Integer arithmetic, returnslong. constrain(value, lo, hi)- Built-in. Clamps
valueto lie betweenloandhi. Returnsloif too low,hiif too high, otherwisevalue. - Linear scaling
- A straight-line conversion between two ranges. Good for proportional signals (pot → PWM); inadequate for curved ones (thermistor → °C).
- Inverted range
- Swap the to-range of
mapso that a high input gives a low output. Useful for "backwards" sensors. - Belt and braces (or belt-and-suspenders)
- Doing two safety measures even when one would do.
map+constrainis the canonical example.
Extra Mission 5 min
Part 1 — Design a one-knob controller
One knob can control several things at once — a mixing desk, a game's difficulty dial, a lamp that also changes colour. Pick an everyday object. On paper, design how one pot sets at least three outputs.
Your design must include:
- A name and a one-sentence job for the device.
- Three outputs, each with its own
map()range — for example 0–255 brightness, 0–180 angle, three mood words. - Which outputs need
constrain(), and why. - A table showing what each output reads at raw 0, 512 and 1023.
Part 2 — Make it
Build it with the pot on A0. Every numeric output must use map() then constrain(). Print all the outputs on one line, separated by commas.
Bring back next class: the uploaded sketch and Serial Monitor screenshots at three knob positions. They should match your table.