The Arduino map() function is an interesting beast. Very technically it works exactly as its documented to work, but not the way almost every example uses it.
Here’s an example you can find in hundreds of sketches online, including the actual documentation for map():
val = map(val, 0, 1023, 0, 255);
This is a simple map, and one would expect that every four ticks on the input would map to one tick on the output (that is, {0,1,2,3} -> 0, {4,5,6,7} -> 1, etc). But that’s not what the function above actually does.
To show the issue, let’s make the output range smaller (but still an even divisor):
val = map(val, 0, 1023, 0, 15);
This should result in an even distribution, 64 input ticks per one output tick. To test this, I wrote a quick script implementing the Arduino map logic (which they were nice enough to document):
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 script prints a table of output values and the number of times that value was returned. Here’s the output for map(?, 0, 1023, 0, 15) for each value in 0..1023:
map(0..1023, 0, 1023, 0, 15);
0 69
1 68
2 68
3 68
4 68
5 69
6 68
7 68
8 68
9 68
10 69
11 68
12 68
13 68
14 68
15 1
That’s definitely not an even distribution. Now here’s a really stark example:
map(0..1023, 0, 1023, 0, 1);
0 1023
1 1
That’s a pretty egregious imbalance.
I mentioned earlier that the function’s actually working how it’s documented to work, just not how it’s usually used in examples. The map() docs state that “[t]he map() function uses integer math so will not generate fractions, when the math might indicate that it should do so. Fractional remainders are truncated, and are not rounded or averaged.”
This completely makes sense – if you imagine a range of 1024 values between 0 and one, all of them will be less than 1 except the last value, and since it’s integer arithmetic, all the less-than-1 values are 0.
The solution is fairly simple – increase the in_max and out_max args by one more than the actual maximum value (and then wrap the output in constrain(), which you ought to have done anyway). It’s fairly easy to work through why this works in your head, but here are the same examples I gave above with the increased maximums:
map(0..1023, 0, 1024, 0, 2);
0 512
1 512
map(0..1023, 0, 1024, 0, 16);
0 64
1 64
2 64
3 64
4 64
5 64
6 64
7 64
8 64
9 64
10 64
11 64
12 64
13 64
14 64
15 64
I have worked through this and I now understand how to get the values I want out of constrain. What I don’t know, however, is why the docs and the examples don’t address this issue. I know I’m not the first person to find this issue because I’ve found sketches on the internet where people are doing this (and they’re doing it in a way that leads me to believe they’re doing it on purpose), but I couldn’t get Google to show me any pages where someone actually addresses this point. I wonder why?
As for why I was looking into this, I was playing around with a couple of pots and an LCD, making a fakey etch-a-sketch using the 2-row LCD I have for such things. I could not figure out why my vertical control would only drop me down a line when I had the pot turned 100%. Now I know…
Edit 2011-09-13:
I posted this in the arduino.cc forum last night. As I expected, this is an issue known in the arduino community. Among other things, I was referenced to this post discussing the issue. My problem with all of this isn’t so much that it’s odd behavior, but that it’s been odd for at least 2 years and there’s no mention of the oddness in the map() documentation. The docs are a wiki, so I proposed an addition in the forum thread, maybe I’ll get to add it and help someone out in the future.