Arduino map - Why it may not work exactly the way you think it does! The
map function is intended to change one range of values into another range
of values and a common use is to read an analogue input (10 bits long, so values range from 0 to 1023) and change the output to a byte so the output would be from 0 to 255.
Everything is fine 0 maps to 0, and 1023 maps to 255...
...with an even distribution (really?????).
The map() function is useful but it has a secret hiding within. Its
easy
enough to use (or so you think) until you look a bit deeper inside. An
even distribution is what you want but does it really do that?
Lets write a sketch to test out exactly what it does
void setup() {
Serial.begin(9600);
for (int adc = 0; adc < 1024; adc++) {
int mapped = map(adc, 0, 1023, 0, 255);
Serial.print(adc);
Serial.print(',');
Serial.println(mapped);
}
}
void loop() {
}
First part of the serial output | Final part of the serial output |
0,0 1,0 2,0 3,0 |
1007,251 1008,251 1009,251 1010,251 |
4,0 5,1 6,1 7,1 |
1011,252 1012,252 1013,252 1014,252 |
8,1 9,2 10,2 11,2 |
1015,253 1016,253 1017,253 1018,253 |
12,2 13,3 14,3 15,3 |
1019,254 1020,254 1021,254 1022,254 |
1023,255 |
The problem is the final output value has only 1 input for one output
i.e. 1023 results in 255, while 1019~1022 results in 254 as output.
What you really want is an even spread of values across the whole range.
To get to this point some of the other outputs must have had 5 values
as inputs (you can see that for adc values 0~4 - all 5 inputs result in
output of a zero.
This sketch increases the value in bins array every time map returns a
value - so each bins[] holds the number of times an output was created.
#define SAMPLES 1024
#define OUTPUTS 256
int bins[OUTPUTS];
const int binsize = (SAMPLES-1)/(OUTPUTS-1);
void setup() {
Serial.begin(9600);
Serial.println("Arduino map output distribution");
Serial.print("Bin size: ");
Serial.println(binsize);
// Initialise bins
for (int i = 0; i < OUTPUTS; i++) bins[i]=0;
for (int adc = 0; adc < SAMPLES; adc++) {
int mapped = map(adc, 0, SAMPLES-1, 0, OUTPUTS-1);
bins[mapped] +=1;
}
for(int i=0;i<OUTPUTS;i++) {
Serial.print("Bin: ");
Serial.print(i);
Serial.print(" count ");
Serial.print(bins[i]);
// Show bin distribution error
if ((bins[i] != binsize)) {
Serial.println(" ***") ;
} else Serial.println();
}
// Find wrong bin count
for(int i=0;i<OUTPUTS;i++) {
if (bins[i]!=binsize) {
Serial.print("Bin error: ");
Serial.println(i);
}
}
}
void loop() {
}
The last part of the output shows four bins with wrong values :
Bin: 253 count 4 Bin: 254 count 4 Bin: 255 count 1 *** Bin error: 0 Bin error: 85 Bin error: 170 Bin error: 255
The arduino map() reference has this to say:
"As previously mentioned, the map() function uses integer math. So fractions might get suppressed due to this. For example, fractions like 3/2, 4/3, 5/4 will all be returned as 1 from the map() function, despite their different actual values. So if your project requires precise calculations (e.g. voltage accurate to 3 decimal places), please consider avoiding map() and implementing the calculations manually in your code yourself."
The problem is that the upper value of 1023 is not exactly divisble
by 255 so you get a slight error. So it is a fraction that is
suppressed.
The code for the function is:
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;
}
The calculations are
0 *255/1023 = 0
1 *255/1023 = 0
2 *255/1023 = 0
3 *255/1023 = 0
4 *255/1023 = 0
5 *255/1023 = 1
1022 * 255/1023 = 254
1023 * 255/1023 = 255
These match the Arduino outputs.
The problem becomes even more apparent.
if you map 1023 to the output range 0 to 7 the problem becomes even
more apparent.
Change the code in the previous sketch redefining 'OUTPUTS' as 8.
The output then becomes:
Arduino map output distribution Bin size: 146 Bin: 0 count 147 *** Bin: 1 count 146 Bin: 2 count 146 Bin: 3 count 146 Bin: 4 count 146 Bin: 5 count 146 Bin: 6 count 146 Bin: 7 count 1 *** Bin error: 0 Bin error: 7
If you tried to map an ADC input using a potentiometer to control an 8
LED bargraph the only time the last LED would light is when the input is
1023!
To fix it make the values a multiple of a power of 2 so the fraction is not suppressed.
Note: For different ranges the difference between max and min must be a power of 2.
The following program uses power of 2 values as input and shows the corrected output.
//#define NUM_SAMPLES 1024 // Must be a power of 2 (Range 0-1023)
//#define NUM_OUTPUTS 256 // Must be a power of 2 (Range 0-255)
#define NUM_SAMPLES 1024 // Must be a power of 2 (Range 0-1023)
#define NUM_OUTPUTS 8 // Must be a power of 2 (Range 0-7)
#define NUM_BINS (NUM_OUTPUTS)
int bins[NUM_BINS];
const int binsize = (NUM_SAMPLES)/(NUM_OUTPUTS);
void setup() {
Serial.begin(9600);
Serial.println("Arduino map output distribution");
Serial.print("Bin size: ");
Serial.println(binsize);
// Initialise bins
for (int i = 0; i < NUM_BINS; i++) bins[i]=0;
for (int adc = 0; adc < NUM_SAMPLES; adc++) {
int mapped = map(adc, 0, NUM_SAMPLES, 0, NUM_OUTPUTS);
bins[mapped] +=1;
}
for(int i=0;i< NUM_BINS;i++) {
Serial.print("Bin: ");
Serial.print(i);
Serial.print(" count ");
Serial.print(bins[i]);
// Show bin distribution error
if ((bins[i] != binsize)) {
Serial.println(" ***") ;
} else Serial.println();
}
// Find wrong bin count
for(int i=0;i< NUM_BINS;i++) {
if (bins[i]!=binsize) {
Serial.print("Bin error: ");
Serial.println(i);
}
}
}
void loop() {
}
Output results: for NUM_SAMPLES = 1024, NUM_OUTPUTS = 8
Arduino map output distribution Bin size: 128 Bin: 0 count 128 Bin: 1 count 128 Bin: 2 count 128 Bin: 3 count 128 Bin: 4 count 128 Bin: 5 count 128 Bin: 6 count 128 Bin: 7 count 128
Note: The first example is also corrected with an even spread of 4 input values per output.
Another example
Here's another example using random samples and outputs i.e. non-power of 2 values.
#define SAMPLES 1976
#define OUTPUTS 153
Here 1976/153 = 12.91 and each bin has 13 values in it except the last that has one. So the algorithm gets it approximately right.
First part of the serial output |
Final part of the serial output |
Bin: 0 count 13 *** Bin: 1 count 13 *** Bin: 2 count 13 *** Bin: 3 count 13 *** Bin: 4 count 13 *** Bin: 5 count 13 *** Bin: 6 count 13 *** Bin: 7 count 13 *** |
Bin: 145 count 13 *** Bin: 146 count 13 *** Bin: 147 count 13 *** Bin: 148 count 13 *** Bin: 149 count 13 *** Bin: 150 count 13 *** Bin: 151 count 12 Bin: 152 count 1 *** |
Given it is such a pain to get working exactly right, its probably better to do one of the following.
With the ADXL345 acellerometer you can detect up to 16g! You can also find out how to use it for tap detection and more.
HMC5883L - How make a digital compass, Find out the differences between the HMC5883L and the QMC5883L and whether they are compatible.
Easily use an ESP8266 with the Arduino IDE and program your first sketch into the ESP8266
The MCP4725 chip is a 12 bit DAC with memory that outputs voltage that you can use for many dfferent purposes. Find out what they are in this page.
PCF8591: A four input ADC with single DAC. How good is this 8 bit ADC, and should you use it in your next project?
Arduino Nano ISP: How to program an ATmega328P using an Arduino Nano as the ISP programmmer. One common problem: Programming a sketch into the chip without a reset control - solved here.
New! Comments
Have your say about what you just read! Leave me a comment in the box below.