Existing algorithm for a potentiometer to "zero in" on a precise value within a wide range ?
Lets say the application is potentiometer in a radio tuning application where the number of discrete channels grossly exceeds the resolution of a normal 270 degree potentiometer.
The potentiometer initially gives "rough" access to the whole frequency range.
An algorithm "watches" the user attempting to "zero in" on a specific channel by looking at timings and magnitude of change and narrows the range accordingly until it looks like the user has reached his target, then clamps the value within a small range with the target and the mid point and with hysteresis. Of course, it must also work in the other direction also allowing the user to recover from a very narrow range, gently backing out to the full range as required to repeat the whole process.
I could imagine it being parameterised.
Full range units (say 1100)
target range units ( say 20 )
Timings: The user expects to get his exact station at the midpoint within 5-6 seconds.
Small adjustments within the target range should not force a complete reset to the full range, but a gradual backing out in that direction.
Maybe the resolution within the target range diminishes according to a logarithmic rule either side of the midpoint.
The user experience must also be intuitive so that (s)he soon "learns" how it works and believes that there is "some logic" to it.
Does anyone know of an algorithm in the public domain which addresses some of the main points here ? Even better a C++ implementation or even better an Arduino library ?
I think this only practical with a rotary encoder (more than 36 degrees rotation). The problem with a pot is, if you are rotating it slowly (for high precision/resolution) you can get to the end of the pot's rotation without reaching the number/count you're trying to reach. And after some time there is very-poor correlation between the pot position and the count.
I have seen a coaxial pot with one knob for course and the other for fine. You can still hit the rotation limit with the fine control, but if you always start the adjustment by setting the fine control to the center, it can work. (You could do the same thing with two regular pots.)
Ok. Thanks. The parallel between a mouse and tuner control did not occur to me but yes the issues are more or less the same. I'll look in more detail at that.
DVDdoug:
I think this only practical with a rotary encoder (more than 36 degrees rotation). The problem with a pot is, if you are rotating it slowly (for high precision/resolution) you can get to the end of the pot's rotation without reaching the number/count you're trying to reach. And after some time there is very-poor correlation between the pot position and the count.
I have seen a coaxial pot with one knob for course and the other for fine. You can still hit the rotation limit with the fine control, but if you always start the adjustment by setting the fine control to the center, it can work. (You could do the same thing with two regular pots.)
Ok. Thanks. But I believe a pot will do. The trick will be dynamically expanding (and compressing) the ranges as well as shifting the mid point, especially if a user is "seen" to be "struggling" away at one of the extremities of the existing range. Of course it is not trivial either, especially to design something that is intuitive to the user (backing out a bit and trying again after hitting a hard stop).
const byte TuningPotPin = A0;
const int LowEndChannel = 999;
const int HighEndChannel = 2099;
const int FullRange = HighEndChannel - LowEndChannel;
// Start in the middle of the dial
int Channel = (LowEndChannel + HighEndChannel) / 2;
void setup()
{
Serial.begin(115200);
}
void loop()
{
int dial = analogRead(TuningPotPin);
if (dial < 512) // Tuning down
{
Channel -= acceleration(512 - dial);
if (Channel < LowEndChannel)
Channel = LowEndChannel;
}
else if (dial > 512)
{
Channel += acceleration(dial - 512);
if (Channel > HighEndChannel)
Channel = HighEndChannel;
}
Serial.println(Channel);
}
// Pass in a number from 1 to 512
// Get a number back representing how far to move the tuning marker
int acceleration(int delta)
{
long dSquared = (long)delta * (long)delta;
const long maxSquared = 512L * 512L;
// Output is 0 for values up to 238 and FullRange at 512;
return (dSquared * FullRange) / maxSquared;
}
Thanks for that. I'll look in more detail when I have considered the subject a bit more thoroughly myself and how to represent the desired behaviour visually.
The solution I am currently considering would also have a timing element where rapid movements would tend to expand the range and slow movements would tend to contract the range and may also affect its linearity.