Learn

The Arduino map() function, on a knob you can turn

Beginner

Or see every lesson.

  • 455parts on the bench
  • 43boards running now
  • 1.00xreal time, on every board
Arduino Uno: a knob on A0 dims the LED on pin 9live0.000 s 0.00x
Click to open it in the editor
map() turns the knob’s 0 to 1023 into the LED’s 0 to 255. The serial monitor prints both.

map(value, fromLow, fromHigh, toLow, toHigh) re-scales a number from one range to another. Its most common job is the line map(analogRead(A0), 0, 1023, 0, 255), which turns a knob’s reading into an LED brightness: 0 stays 0, 1023 becomes 255, and everything in between lands in proportion.

The circuit above is exactly that: a potentiometer on A0 and an LED on pin 9 of an Uno. Press Run and turn the knob. With the knob in the middle the serial monitor prints a reading of 511 and a brightness of 127. Turn it all the way and it prints 1023 and 255, all the way back and 0 and 0, and the LED follows.

That is the quick win. The rest of this page is the four things map() does that surprise people: it rounds down, it does not clamp, it is happy to count backwards, and it only does whole numbers. Each one was run on an Uno in Mokxi, and the numbers below are what it printed.

Want the step-by-step version? The lesson "Reading a voltage" walks through this with checkpoints.

Open the lesson

The sketch, and the five numbers

The first number is the value to convert. The next two are the range it comes from, and the last two are the range you want. So map(reading, 0, 1023, 0, 255) reads as "reading, which runs from 0 to 1023, scaled to run from 0 to 255". For a servo you would write map(reading, 0, 1023, 0, 180), and the knob at 512 gives 90 degrees.

The sketch prints only when the reading has moved by more than 8, so a steady knob does not fill the monitor. Change the last 255 to 50 and press Run again: the LED never gets brighter than a glow, because the whole knob now maps onto 0 to 50.

firmware/uno/examples/sfpot (the sketch running above)
const int KNOB = A0;
const int LED = 9;

int shown = -1;

void setup() {
  pinMode(LED, OUTPUT);
  Serial.begin(9600);
  Serial.println("Mokxi Uno: knob on A0, LED on pin 9");
}

void loop() {
  int reading = analogRead(KNOB);
  int brightness = map(reading, 0, 1023, 0, 255);
  analogWrite(LED, brightness);
  if (abs(reading - shown) > 8) {
    shown = reading;
    Serial.print("reading ");
    Serial.print(reading);
    Serial.print("  brightness ");
    Serial.println(brightness);
  }
  delay(20);
}

How map() works inside

There is nothing hidden in it. map() is one line of whole-number arithmetic, and the Arduino reference prints it in full. Subtract the bottom of the first range, multiply by the size of the second range, divide by the size of the first, and add the bottom of the second.

We compiled that function under another name and checked it against the built-in map() for every reading from 0 to 1023, onto 0 to 255, 0 to 180 and 255 to 0. Every answer matched. Knowing the formula explains every surprise below: the division throws away the remainder, and nothing anywhere checks the answer is inside the range.

map(), as the Arduino reference gives it
long map(long x, long in_min, long in_max, long out_min, long out_max) {
  return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
}

It rounds down, so the top value is rare

Whole-number division drops the fraction. map(512, 0, 1023, 0, 255) is 127.6 on a calculator and 127 on the board. map(1022, 0, 1023, 0, 255) is 254.75, and the board says 254. Only a reading of exactly 1023 gives 255.

We counted, by running every reading from 0 to 1023 through map() on the Uno. Five readings give 0, most outputs get four readings each, and 255 gets one. On a real pot, with a little noise on the wiper, the LED will rarely sit at full brightness. That is usually fine. When it is not, divide by four instead: reading / 4, which is the same as map(reading, 0, 1024, 0, 256). Then every output from 0 to 255 gets exactly four readings, 512 gives 128, and 1023 gives 255, never 256.

It does not clamp: use constrain()

map() is happy to answer outside the range you gave it. Say you calibrated a light sensor and found it reads 200 in the dark and 800 in daylight, so you wrote map(reading, 200, 800, 0, 100) for a percentage. In bright sun it reads 900, and the sketch says 116 percent. Feed it 1200 on a 0 to 1023 range and you get 299, which is not a brightness analogWrite() was ever meant to be given.

The fix is one more call. constrain(map(reading, 200, 800, 0, 100), 0, 100) gave 100 for the same reading of 900. Keep the two together whenever the input can stray past the ends you measured, which for any real sensor is always.

Map, then clamp
void setup() {
  Serial.begin(9600);
  int reading = 900;
  Serial.println(map(reading, 200, 800, 0, 100));
  Serial.println(constrain(map(reading, 200, 800, 0, 100), 0, 100));
}

void loop() {
}

Backwards and below zero

Swap the last two numbers and the range runs the other way. map(300, 0, 1023, 255, 0) gave 181, so turning the knob up dims the LED. That is the easy fix for a pot wired backwards, or a light sensor that should light a lamp when it gets dark.

Negative numbers work too. map(512, 0, 1023, -40, 125) gave 42, which is how a sensor’s 0 to 5 V output becomes -40 to 125 degrees. Below zero, the rounding goes toward zero rather than down: map(-10, 0, 1023, 0, 255) gave -2, not -3.

Why map() uses long, and why decimals vanish

Try the arithmetic yourself with an int and it goes wrong. On an Uno an int is 16 bits and holds only up to 32767. int a = 1000; then a * 180 / 1023 printed -16, because 180000 does not fit and wrapped around before the division. map(1000, 0, 1023, 0, 180) printed 175, because map() does its multiply in a 32-bit long.

map() takes whole numbers only. map(2.7, 0, 10, 0, 100) printed 20, because 2.7 was cut to 2 on the way in, and the build tab warned: implicit conversion from 'double' to 'long' changes value from 2.7 to 2. For decimals, write the same formula with floats. The version below printed 27.00 for 2.7, and 2.50 for a reading of 512 scaled to 0 to 5 volts.

A map() for floats
float mapFloat(float x, float inMin, float inMax, float outMin, float outMax) {
  return (x - inMin) * (outMax - outMin) / (inMax - inMin) + outMin;
}

void setup() {
  Serial.begin(9600);
  Serial.println(mapFloat(2.7, 0, 10, 0, 100));
  Serial.println(mapFloat(512, 0, 1023, 0, 5));
}

void loop() {
}

Questions

Does Arduino map() constrain the result?

No. A value outside the first range gives an answer outside the second: a reading of 900 mapped from 200 to 800 onto 0 to 100 gives 116. Wrap it in constrain(), as in constrain(map(x, 200, 800, 0, 100), 0, 100).

Why does map(x, 0, 1023, 0, 255) almost never reach 255?

Because map() rounds down, only a reading of exactly 1023 gives 255. Five readings give 0 and most outputs get four. Divide the reading by 4 instead if you want every brightness to get the same share of the knob.

Can map() use decimals?

No. Its arguments and its answer are long, so 2.7 becomes 2 before it starts. Write the same formula with float arguments, as the mapFloat() function on this page does, when you need fractions.

How do I reverse a range with map()?

Swap the last two numbers: map(reading, 0, 1023, 255, 0) gives 255 at the bottom of the knob and 0 at the top. The first range can be reversed the same way.

What is the difference between map() and constrain()?

map() changes the scale of a number. constrain() leaves the scale alone and stops a number going past the two limits you give it. They are often used together, map() first.

What is the formula of the Arduino map() function?

(value - fromLow) * (toHigh - toLow) / (fromHigh - fromLow) + toLow, worked out in whole numbers with the remainder thrown away. So map(1000, 0, 1023, 0, 100) is 97 and map(512, 0, 1023, 0, 100) is 50, which is what an Uno here printed.

Sources

Where the facts on this page come from

Build this for real

Open the editor, change a value and watch the number move with it. Nothing to install, and no account needed.