Numeric Distribution with Arduino’s Map Function

arduino ldrThe map function, in the Arduino’s implementation of C, scales a number from one range of values to another, and is often seen used when working with the analog to digital converters (ADCs) and the analogRead() function. As an example, the number 20 in the range 0–100 can be expressed as a percentage: 20%. When re-mapping that number to the range 0–1000, the number is scaled to be 20% of the new range, which is 200.

In this explanation of the map function, little guidance is given to beginners who may be unfamiliar with some of the terms, functions, and processes. Learning the Arduino Step-by-step at Udemy.com provides an extensive tutorial on the background information required. Electronic engineers and LabView users will find a suitable introduction to the technology in Arduino meets LabView – Wiring, Installation and Programming.

Map and AnalogRead

The Arduino’s ADCs can read 1024 levels between 0V and 5V, and so the value returned by the analogRead function is an integer in the range 0 through 1023. For many uses, this is fine. However, using map you can scale the value back to the range 0–5, which might be more helpful when specifically measuring voltages. For example:

int v2 = map(v1, 0, 1023, 0, 5);

Map accepts five arguments:

  1. The value to be scaled (v1).
  2. The start value of the source range (0).
  3. The end value of the source range (1023).
  4. The start value of the target range (0).
  5. The end value of the target range (5).

You should be aware of one important issue: map has been programmed using integer math. This means that, using the function call above, if v1 was 498 then v2 will be 2, not 2.43. To increase the accuracy of the converted reading, a possible solution is to map the value to a range in millivolts instead, 0mV–5000mV, which will return the value 2434mV (2.434V).

Uneven Distribution from Integer Math

Consider the following example:

int v2 = map(v1, 0, 1023, 0, 1);

You would expect that values of v1 from 0 through 511 would return 0, while values above that would return 1. But this is not what happens. Instead, all values from 0–1022 return 0, and only 1023 returns 1. This is due to the map function being programmed with integer math and discarding fractions, rather than rounding them or converting to an integer after the calculation is complete.

This issue particularly affects the distribution of numbers when mapping to a small target range, and it may be a source of problems in your projects. One way to improve the distribution of values is to increase the end values of the ranges specified in the function call:

int v2 = map(v1, 0, 1024, 0, 2);

This technique still does not give perfect regularity, but values of v1 from 0 through 511 return 0, and values of 512 and above return 1, as expected.

Reading the Position of a Potentiometer

In the circuit shown below, a 10Ko potentiometer (“pot”) is connected to the Arduino’s analog input pin 0, and forms a potential divider with the 10Ko fixed resistor. Conveniently, the Arduino’s ADCs can read the voltage level using the full range of values 0 through 1023.

MAP1

Potentiometers often turn through 270° from the far left position to the far right, and are popularly used in volume knobs and amplifiers with the numbers 1–10 written around them. To get the value of the pot in terms of the positions 1–10, you can map the reading to that range:

int v2 = map(v1, 0, 1024, 1, 11);

Note that this example suffers heavily from the same distribution problem as mentioned above, and so the end values of the ranges have been incremented. This amp does not go to 11.

You can also approximate the position of the pot (in terms of its rotation):

int r = map(v1, 0, 1023, 0, 270);

Many people would regard the 0° position to be in the center – not the far left as used in the preceding example – with the knob’s position marker pointing straight up. Thinking this way, the angles of rotation of the pot are -135° through 135°. The map function does not require that all values are positive, so the following call can be used to map the value to a range indicating the knob’s position with 0° as the center:

int r = map(v1, 0, 1023, -135, 135);

The Need for Calibration

The circuit and code above are convenient when each side of the potential divider is equal (a maximum 10Ko from the pot, and 10Ko from the fixed resistor). However, when using a different value pot or fixed resistor, the values read by the Arduino will not be so perfect.

Using a 470Ko potentiometer unbalances the potential divider formed with the 10Ko resistor and, to further complicate matters, not all pots are constructed equally. The 470Ko pot used while testing this circuit actually has a maximum resistance of 560Ko.

To (approximately) determine the value of the pot in these situations, you need to know the start and end values that are actually read by the Arduino. One way to do this is to write a short sketch that outputs the results of calls to analogRead, so that you can turn the pot from left to right and record the minimum and maximum values.

/*
 *
 * Udemy.com
 * Numeric Distribution with Arduino's Map Function
 *
 */
 
 // pin assignments
 int POT = 0;

 // initialize the serial port

 void setup() {
   Serial.begin(9600);
 }
 
 // read from the analog input connected to the pot
 // and print the value to the serial port.
 // the delay is only to avoid sending so much data
 // as to make it unreadable.
 void loop() {
   int v = analogRead(POT);
   Serial.println(v);
   delay(1000);
 }

With the “470Ko” pot, the values range from 0 through 912, not 0 to 1023. So you can map the values read by analogRead to the resistance range of the actual pot by using the following call:

int v2 = map(v1, 0, 912, 0, 560000);

The rotation of the pot would be calculated similarly:

int r = map(v1, 0, 912, -135, 135);

Implementing Map in Other Languages

If you want to experiment with the map function without using an Arduino, or need to write a piece of software that performs the same calculation as an Arduino project, the function definition below can be adapted for use in most C-derived languages (such as Java, C++, Objective C, or C#).

private 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;
}

Further Reading

Beginners to the Arduino typically start on digital electronics projects, and may be unfamiliar with using analog devices. For more information about components such as potentiometers and sensors, you can take a step-by-step course on the Arduino platform.