Greetings all,
I think I am stuck a bit. Let me start with a fast introduction and then get into the requirements.
I am working on an Arduino project to replace the "brain" of the Tomy Omnibot remote control robot. I put brain in quotes because the Omnibot is not really a robot and is more of a remote control toy. It's "brain" works by receiving and decoding tones of different frequency from a radio remote and then setting the state of output pins based on the command.
The tones are shaped into nice TTL level square waves.
The tones can come from the radio or the cassette deck.
The tones are 1400 to 2800 HZ in 200hz increments (1400, 1600, 1800, etc...) and a single outlier at 4600Hz. 9 tones in total.
The tone input is pulled down, so the tones are positive pulse first.
While the tones are nice square waves, they are not precise and there is of course radio noise to deal with which is not filtered out.
Requirements:
- Maintain all original hardware and functions of the Omnibot with the exception of the CPU
- Allow expansion of the original functions (to include new sensors, etc...)
- Remote commands may come in at any time. I need to either poll this quickly enough in the main code to catch remote commands or develop a reliable interrupt strategy.
- Reliably detect the tones that may vary by plus/minus 25HZ and reject noise quickly.
- Most commands are momentary, one is latched (tape start/stop) and the other is one tone for press and one tone for release (walkie talkie)
- Driving commands will drive motors as long as joystick is held, but stop when joystick is released. So I will need to poll this quickly enough to keep it driving when the joystick is held (the tone will be continuous in that case.)
My focus at the moment is on number 4: reliably detect tones and reject noise. I have some mostly working code that is ugly in my opinion and I am certain is not the best way to do it.
What I am doing (just testbed at the moment) is to call a function every loop to parse the tones. I am using pulseIn(2, HIGH) and since the tones are 50% duty cycle, I just multiply this by 2 to get the period and then divide this by 1 second to get the frequency. But then I have a ton of IF statements to reject anything out of band, then a ton of IF statements to convert anything +/- 50HZ to the correct frequency. Then a series of case statements to create an integer representing the remote command. This seems to be working fairly reliably and pretty fast, but it is ugly and I would really like some advice on how to make the routine more efficient. In the end, I want the remote to be responsive, but I do not want it to be the center-piece of the robot. I want to be able to move this code into an interrupt somehow so the newly intelligent Omnibot can act likely a real robot when not being commanded from the remote. Baby steps....
Here is my remote decoder function (serial print statements will be removed):
int getCommand(){
int command;
long highTime, freq, period;
highTime = pulseIn(2,HIGH);
period = highTime * 2;
freq=1000000/period;
if((freq < 1350) || (freq > 4650)) {
Serial.println("Invalid Command");
Serial.println("Freq: " + String(freq));
return 0;
}
if((freq > 2850) && (freq < 4550)) {
Serial.println("Invalid Command");
Serial.println("Freq: " + String(freq));
return 0;
}
if( (freq > 1350) && (freq < 1450)) freq = 1400;
if( (freq > 1550) && (freq < 1650)) freq = 1600;
if( (freq > 1750) && (freq < 1850)) freq = 1800;
if( (freq > 1950) && (freq < 2050)) freq = 2000;
if( (freq > 2150) && (freq < 2250)) freq = 2200;
if( (freq > 2350) && (freq < 2450)) freq = 2400;
if( (freq > 2550) && (freq < 2650)) freq = 2600;
if( (freq > 2750) && (freq < 2850)) freq = 2800;
if( (freq > 4550) && (freq < 4650)) freq = 4600;
Serial.println("High: " + String(highTime));
Serial.println("Period: " + String(period));
Serial.println("Freq: " + String(freq));
switch (freq) {
case 1400: //Talk ON
command = 1;
break;
case 1600: //Forward
command = 2;
break;
case 1800: //Right
command = 3;
break;
case 2000: //Reverse
command = 4;
break;
case 2200: //Left
command = 5;
break;
case 2400: //TAPE START/STOP (latched)
command = 6;
break;
case 2600: //Sound 1
command = 7;
break;
case 2800: //Sound 2
command = 8;
break;
case 4600: //Talk OFF
command = 9;
break;
default:
command = 0;
break;
}
return command;
}
I am intermediate level when it comes to programming. On the Arduino platform, I mainly only know how to work within the core libraries. I assume this would be so much more efficient to have running in a library under timer control, or maybe using the counter hardware instead of the way that I am doing it. But I am not certain how to accomplish either of those things on the Arduino.