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.
Try It Yourself 20 min
Goal: Map the pot reading to a percentage (0–100) and print it. Two-line conversion using map + constrain.
Hint
int pct = map(raw, 0, 1023, 0, 100);
pct = constrain(pct, 0, 100);
Serial.print(pct); Serial.println("%");Goal: Map the pot to an inverted brightness — bright pot = dim LED, dim pot = bright LED. Use map's swapped to-range.
Hint
int bright = map(raw, 0, 1023, 255, 0); // swapped!
bright = constrain(bright, 0, 255);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.
Hint
void loop() {
int raw = analogRead(POT);
// 1) PWM brightness on pin 9
int b = constrain(map(raw, 0, 1023, 0, 255), 0, 255);
analogWrite(9, b);
// 2) On-board LED on if raw >= 512 (half-way up)
digitalWrite(13, raw >= 512 ? HIGH : LOW);
// 3) Servo-style angle 0..180 printed
int angle = constrain(map(raw, 0, 1023, 0, 180), 0, 180);
Serial.println(angle);
delay(100);
}One sensor reading, three differently-mapped outputs. This is the pattern for "one knob, multiple effects" — used in synthesizers, lighting boards, every sort of control panel.
Mini-Challenge · 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.
Reveal one valid sketch
const int LDR = A1;
const int LED = 13;
const int RAW_COLD = 50;
const int RAW_HOT = 800;
const int TEMP_COLD = 0;
const int TEMP_HOT = 40;
const int TEMP_WARN = 30;
void setup() {
pinMode(LED, OUTPUT);
Serial.begin(9600);
}
void loop() {
int raw = analogRead(LDR);
int temp = map(raw, RAW_COLD, RAW_HOT, TEMP_COLD, TEMP_HOT);
temp = constrain(temp, TEMP_COLD, TEMP_HOT);
Serial.print("raw: "); Serial.print(raw);
Serial.print(" pretend-temp: "); Serial.print(temp);
Serial.println(" °C");
digitalWrite(LED, temp >= TEMP_WARN ? HIGH : LOW);
delay(200);
}Notice all the magic numbers are named constants at the top. If your room is unusual (very bright or very dark) you can re-calibrate by changing two values, without touching the loop. That's exactly the pattern L02-11 uses for the real thermistor.
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.
Homework 5 min
Build a one-knob dashboard. Wire the pot to A0 and use a single analogRead to drive:
- An LED on pin ~9 (full PWM brightness 0–255).
- The on-board LED on pin 13 (on if pot is in the top 25%).
- A printed servo angle 0°–180°.
- A printed "mood": calm if 0–30%, working if 30–70%, alert if 70–100%.
Every numeric output should pass through map + constrain. Print all four outputs on one Serial line, comma-separated.
Bring back next class:
- The sketch saved as
hw-l02-10.ino. - A note answering: "What goes wrong if I remove the
constraincall in front ofanalogWrite?"