map() Function Acting Weird

Hello everyone! First off, I have been playing with the basic functions of my Arduino Duemilanove for a while now, and am starting to delve deeper into the really cool stuff it can do. This is my first post here, and my first real problem I haven't been able to solve by searching through the Forums.

The program that I am working on is for an Iambic Keyer for sending morse code. I'm trying to learn morse code for my ham radio hobby, and got the idea to build my own keyer at the same time. I haven't actually worked on the main part of the code yet, focusing first on the input and display sections.

The basic hardware I have is a 1k ohm potentiometer (I've tried everything between 1k and 100k, with no difference), between the 5V and Ground rails, and the wiper attached to Analog Pin 0. I'm using 2 multiplexed 7-Segment Displays to display the speed.

The potentiometer will vary the code speed between 10 words per minute (fairly slow) to 40 wpm (very fast), and I had originally worked the code out using an equation to do the analog/digital to timing conversion. Poking around the Tutorials some more, I came across the map() function, and decided that I had to use this function! It greatly simplified my code, completely eliminating the equation sections.

But it also caused an oddity. As I vary the potentiometer, the number sequence goes from 10, 11, 12, 13, 14, 15, 17, 18, 19, etc. It never displays the least significant digit "6" (either 16, 26 or 36), it just skips right over and displays the 7. All the rest of the digits work fine.

As far as I can tell, the "elementValue" is correct, or at least close enough. It does vary pretty continuously, or at least as much as I can see with my eyes (no oscilloscope). Not having a 6 isn't a show stopper, but I would like it to look right.

The complete code contents I have is really messy at the moment, with all kinds of comments and commented-out sections while I figure out what works, but the code snippet below does contain all the pertinent sections. Really, my question is around the first 3 lines in void loop(), but all the rest should give you an idea of what I'm trying to do:

int analogPin = 0;  //input pin for the speed select potentiometer
int sensorValue = 0;
int elementSpeed = 0;
int elementValue = 0;
int msb = 0;  //setup the most signification bit for the Element Speed
int lsb = 0;  //setup the least significant bit for the Element Speed
int displayA = 8;   //this activates the 10's value 7-segment character
int displayB = 9;   //this activates the 1's value 7-segment character

void setup ()
{
 DDRD = B11111110;  //Sets Digital Pins 1-7 as Outputs, for Common Anode 
  pinMode (displayA, OUTPUT);
  pinMode (displayB, OUTPUT);
}

void loop ()
{
//read and convert the potentiometer value into the desired values
sensorValue = analogRead(analogPin);
elementSpeed = map(sensorValue, 0, 1020, 120, 30); 
elementValue = map(sensorValue, 0, 1020, 10, 40);

//then, break apart the equivalent WPM value into the msb and lsb digits for display
msb = elementValue / 10;  //identify the most significant digit
lsb = elementValue % 10;  //identify the lsb

digitalWrite(displayA, HIGH);  //this is the most significant digit. The alphabet soup here are the 7-segment display elements
if (msb == 0){digitalWrite(PORTD = B10000001, HIGH);}  //0 = ABCDEF
if (msb == 1){digitalWrite(PORTD = B11110011, HIGH);}  //1 = BC
if (msb == 2){digitalWrite(PORTD = B01001001, HIGH);}  //2 = ABDEG
if (msb == 3){digitalWrite(PORTD = B01100001, HIGH);}  //3 = ABCDG
if (msb == 4){digitalWrite(PORTD = B00110011, HIGH);}  //4 = BCFG  
if (msb == 5){digitalWrite(PORTD = B00100101, HIGH);}  //5 = ACDFG
if (msb == 6){digitalWrite(PORTD = B00000101, HIGH);}  //6 = ACDEFG
if (msb == 7){digitalWrite(PORTD = B11110001, HIGH);}  //7 = ABC
if (msb == 8){digitalWrite(PORTD = B00000000, HIGH);}  //8 = ABCDEFG
if (msb == 9){digitalWrite(PORTD = B00110001, HIGH);}  //9 = ABCFG
delay(10); //delay so the display appears to be constantly on
digitalWrite(displayA, LOW); //return the msb display to off

digitalWrite(displayB, HIGH); //the least significant digit
if (lsb == 0){digitalWrite(PORTD = B10000001, HIGH);}  //0 = ABCDEF
if (lsb == 1){digitalWrite(PORTD = B11110011, HIGH);}  //1 = BC
if (lsb == 2){digitalWrite(PORTD = B01001001, HIGH);}  //2 = ABDEG
if (lsb == 3){digitalWrite(PORTD = B01100001, HIGH);}  //3 = ABCDG
if (lsb == 4){digitalWrite(PORTD = B00110011, HIGH);}  //4 = BCFG  
if (lsb == 5){digitalWrite(PORTD = B00100101, HIGH);}  //5 = ACDFG
if (lsb == 6){digitalWrite(PORTD = B00000101, HIGH);}  //6 = ACDEFG
if (lsb == 7){digitalWrite(PORTD = B11110001, HIGH);}  //7 = ABC
if (lsb == 8){digitalWrite(PORTD = B00000000, HIGH);}  //8 = ABCDEFG
if (lsb == 9){digitalWrite(PORTD = B00110001, HIGH);}  //9 = ABCFG
delay(10); //delay so the display appears to be constantly on
digitalWrite(displayB, LOW); //return the lsb display to off

}

I haven't started the actual schematics or pcb layout yet, so unfortunately I don't have a complete picture of what the hardware looks like at the moment.

And of course, if anyone has ideas to further simplify my code, I would greatly appreciate it!

Thank you for your help!

The map function uses integer arithmetic, which might explain some of the problem. The analogRead function will return a value in the range from 0 to 1023. These are the values, then, that should be in the from range (not 0 to 1020).

I'm also leery of using map when the ranges go in reverse order. I'd consider changing the 120, 30 to 30, 120 in the first map call.

I'd also map 0 to 1023 to 0 to 30 in the second call, and add 10 to the returned value. The result should be exactly the same, but it's easier to see that it is working when both ranges start at the same point. If you allow the range to go from 0 to 32, the input range is an integer multiple of the output range, so you should see every integer in the output range.

PaulS, my first thought was that the problem was truncation of integer arithmetic too.

Then I realized the 0-1020 input range maps to the 10-40 output range an integral number of times. It seems to me that Meulfire crafted the 0-1020 range for that reason. If he doesn’t push the pot to the very high end of its range, the map function would still be useful, no?

Even considering truncation, the result should pass “through” the values 16, 26 and 36 on their way between 15 and 17, 25 and 27, and 35 and 37. That is, the values should be truncated to 16, 26 and 36 at some point in the range.

I abandoned a reply I was typing because I was stumped by the reported results. When I saw that you replied, I hurried to see what I had missed. (That’s a compliment to you, BTW.)

FWIW, I’ve never had a problem with mapping to a descending output range. I would also offer the opinion that mapping the second range to 0-30, then adding 10 to the result, defeats some of the benefit of the map function. (See the formula below, which shows that the map function does exactly that internally.) Personal preference, maybe.

I am tempted to create a spreadsheet of all the values between 0 and 1020, and calculate the mapped results for each input value, using the formula in the reference page for map():

Appendix

For the mathematically inclined, here’s the whole function

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

[edit]The syntax of

if (lsb == n){digitalWrite(PORTD = Bnnnnnnnn, HIGH);}

looks very weird. Could there be some special interaction with the bitstring for the segments for “6”?

Meulfire, can you try the following code? The code is an attempt to remove the hardware potentiometer from the problem. It also removes the map() function from consideration.

for (lsb=0; lsb<10; lsb++) {
  digitalWrite(displayA, HIGH);  //this is the most significant digit. The alphabet soup here are the 7-segment display elements
  if (msb == 0){digitalWrite(PORTD = B10000001, HIGH);}  //0 = ABCDEF
  if (msb == 1){digitalWrite(PORTD = B11110011, HIGH);}  //1 = BC
  if (msb == 2){digitalWrite(PORTD = B01001001, HIGH);}  //2 = ABDEG
  if (msb == 3){digitalWrite(PORTD = B01100001, HIGH);}  //3 = ABCDG
  if (msb == 4){digitalWrite(PORTD = B00110011, HIGH);}  //4 = BCFG  
  if (msb == 5){digitalWrite(PORTD = B00100101, HIGH);}  //5 = ACDFG
  if (msb == 6){digitalWrite(PORTD = B00000101, HIGH);}  //6 = ACDEFG
  if (msb == 7){digitalWrite(PORTD = B11110001, HIGH);}  //7 = ABC
  if (msb == 8){digitalWrite(PORTD = B00000000, HIGH);}  //8 = ABCDEFG
  if (msb == 9){digitalWrite(PORTD = B00110001, HIGH);}  //9 = ABCFG
  delay(10); //delay so the display appears to be constantly on
  digitalWrite(displayA, LOW); //return the msb display to off 

  digitalWrite(displayB, HIGH); //the least significant digit
  if (lsb == 0){digitalWrite(PORTD = B10000001, HIGH);}  //0 = ABCDEF
  if (lsb == 1){digitalWrite(PORTD = B11110011, HIGH);}  //1 = BC
  if (lsb == 2){digitalWrite(PORTD = B01001001, HIGH);}  //2 = ABDEG
  if (lsb == 3){digitalWrite(PORTD = B01100001, HIGH);}  //3 = ABCDG
  if (lsb == 4){digitalWrite(PORTD = B00110011, HIGH);}  //4 = BCFG  
  if (lsb == 5){digitalWrite(PORTD = B00100101, HIGH);}  //5 = ACDFG
  if (lsb == 6){digitalWrite(PORTD = B00000101, HIGH);}  //6 = ACDEFG
  if (lsb == 7){digitalWrite(PORTD = B11110001, HIGH);}  //7 = ABC
  if (lsb == 8){digitalWrite(PORTD = B00000000, HIGH);}  //8 = ABCDEFG
  if (lsb == 9){digitalWrite(PORTD = B00110001, HIGH);}  //9 = ABCFG
  delay(10); //delay so the display appears to be constantly on
  digitalWrite(displayB, LOW); //return the lsb display to off
}

Add an appropriate delay in the loop according to your age. ;)[/edit]

PaulS and TBAr, thank you for your responses!

I did some troubleshooting last night, of which I lost all my code because my computer crashed, and I didn't save my troubleshooting Sketch...

But I was on to something, so I wrote another, very simple, program, and it repeated what I was seeing yesterday.

int pinTwo = 2; 
int pinSeven = 7; 

void setup()
{
  pinMode(pinTwo, OUTPUT);
  pinMode(pinSeven, OUTPUT);
}

void loop()
{
  digitalWrite(pinTwo, HIGH);
  delay(1000);
  digitalWrite(pinSeven, HIGH);
  delay(1000);
  digitalWrite(pinTwo, LOW);
  delay(1000);
  digitalWrite(pinSeven, LOW);
  delay(1000);
}

I have LED's on pins 2 and 7, in a Common Anode configuration (microcontroller on the cathode).

When the program runs, Pin 2 lights on command, but when pin 7 is told to light, they both turn off. When 2 is told to turn off, then 7 turns on, and when they're both told to turn off, they both light about 1/2 brightness (almost like a 50% duty cycle on a pwm pin).

Basically, the map() function doesn't seem to be the problem, and even the PORTD = Bxxxxxxx commands are working as expected... it looks like it's an issue between Pins 2 and 7 in general. In my troubleshooting, I haven't found any other two pins reacting like that.

I've been searching through the forums for that issue, and haven't found anything yet. Is this a common problem, or is my microcontroller fried?

Is it only when using those two pins that you have a problem? If so, I’d say it’s shopping time.

What size resistors are you using, and where?

I haven't checked every single pin pair, but for the ones I did check (digital 0 through digital 8), those are the only two that do that.

I have a 330 ohm resistor between the 5V line and the Anode of the LED's, limiting current to 15mA, which should be well within spec. I have always been very careful with my current draw/drain, but it could be that I did over-current one or more pins at some point in the past.

This'll be a good opportunity to pick up a couple spare microcontrollers, and a programmer.

Thanks again for your help!