stabilize analog input

Status
Not open for further replies.

pisco

New member
Hi,

I'm currently working on a DIY project for a hardware MIDI controller for the iOS Synth iVCS3 goal is something like that http://www.synth-project.de/ivcs3_controller.html but until now I am prototyping with just one trim pot on a Teensy 3.2 board. The pot is connected to A0, GND and 3,3V

To use high resolution MIDI CC I use this code (base on https://forum.pjrc.com/threads/25940-14-bit-MIDI-over-USB)
Code:
// Fixed values, for demonstration
uint32_t channel = 1;          // channels are 1 to 16
uint32_t param = 123456;   // your parameter number here, 0 to 16k
uint32_t val = 54321;          // your value here, 0 to 16k
// in actual use this would come from some physical controller like an analog value
// or be computed by monitoring an encoder

void sendNRPNHR(uint32_t parameter, uint32_t value, uint32_t channel)  {
  // Send an arbitrary 14-bit value to an arbitrary 14-bit non-registered parameter
  // Note that param MSB and LSB are shared between RPN and NRPN, so set both at once
  // Also, null out the RPN (and NRPN) active parameter (best practice) after data is sent (as it persists)

  usb_midi_write_packed(0xB00B | (((channel - 1) & 0x0F) << 8)  | ((99 & 0x7F) << 16) | ((parameter & 0x7F) << 24));
  // Control change 98 for NRPN, LSB of param
  usb_midi_write_packed(0xB00B | (((channel - 1) & 0x0F) << 8)  | ((98 & 0x7F) << 16) | (((parameter >>7) & 0x7F) << 24));
  // Control change 38 for Data Entry LSB of param
  usb_midi_write_packed(0xB00B | (((channel - 1) & 0x0F) << 8)  | ((6 & 0x7F) << 16) | (((value >>7) & 0x7F) << 24));
  // Control change 6 for Data Entry MSB of value
  usb_midi_write_packed(0xB00B | (((channel - 1) & 0x0F) << 8)  | ((38 & 0x7F) << 16) | ((value & 0x7F) << 24));
}


void setup() {
}

// store previously sent values, to detect changes
int previousA0 = -1;
elapsedMillis msec = 0;

void loop() {
  // only check the analog inputs 50 times per second,
  // to prevent a flood of MIDI messages
  if (msec >= 80) {
    msec = 0;
    int n0 = analogRead(A0);

        if (n0 != previousA0) {
         int valA0 = map(n0, 0, 1023, 0, 16383);
         sendNRPNHR(param, valA0, 1); // channel 1
         previousA0 = n0;
        }
  }
}

I connected a 10K Bourns pot (also tried another) but the values are not stable and 'flapping', e.g. if I control the oscillator frequency it sound like a random vibrator.
As know from a other project I already tried ceramic capacitor 100nF between signal and ground which is a bit better but not that good. Any hints how to get the signal stable?
BTW: I'm a Arduino newbee.
regards
 
averaging or hysteresis or maybe go to 9 bit resolution on yr analog read (throw some LSBs away) ...9 bit resolution is pretty good for most midi purposes (all depends on what you are trying to do of course) I use 9 / 10 bits for slide potentiometers (aka faders) and that is a o k for volume control. Personally I use a deadspot of '2' (i.e. I update my midi vaue if the analog vaue changes by more than 2) ..that gets rid of jitter for me (maybe I loose a bit of resolution, but if its all jitterry what use is it anyway ....most peope prefer averaging

Maybe use AGND as well.

I remember using quite chunky analog values on pitch bend with also runs to 14 bit, and the steps were 'ok' ...... makes you wonder why bother with 14 bits for an audio parameter in midi
 
Last edited:
+1 to 'deadspots'. Most people's instinct (including mine) is to limit the frequency of readings. I've found it much more effective to ignore intermittent analog values.

e.g.
Code:
int previousA0 = -1;
int n0 = analogRead(A0);
if (n0 % 16 == 0 && n0 != previousA0) {
  n0 = n0 / 16;
  sendNRPNHR(param, valA0, 1); // channel 1
  previousA0 = n0;
}

Then you can read as fast as you want and the map() function isn't even necessary. With a deadspot of 15, it should be rock solid.
 
Last edited:
I saw your youtube video ... very cool! Looks like a really amazing project.

I had a thought ...why not use rotary encoders rather than rotary pots .... no jitter!! I use one for a pan control on my teensy controller. You can get nicely detented ones as well (adafruit have a real nice 24 click one).... I suppose there is a limit as to how many software rotary encoders you can have ...no idea on that... but there is quite efficient software code available it looks like it could work for any number (I only have one)...could be a go.

24 clicks a revolution ..you could have some really nice accurate controls (I kind of get why you want 14 bit after seeing your demo ... )... I have some encoder code somewhere (and there are actually hardware 'decoders' on the teensy and a hardware-based library imaginatively called "Encoder") .

I'll find my hysterisis code for you too, if you like. A bit different from monkeybiscuits...although I do like the idea of using modulo.

edit: because you are turning the nobs by hand (i.e. its not super fast, you could do without hardware interrupta (if you are looking at encodersthat is), and so just calling ditigal read quickly, you could probably have 8 jitter free encoders no worries ...super accurate too... Someone will know how many
 
Last edited:
Thank you for your feedback. About the encoders: As I'm new to DIY Hardware I have not a big overview on available parts. As I want to to as close as possible to analogue hardware feeling it then should be a seamless (optical) encoder without detents. Another issue would be the vernier dial knob, like from the original EMS VCS3, which is a 10-turn one. The one I've seen have no limitation, that means by the end of the 10th turn you would start at the 1 with a endless encoder. With a 10-trim pot I have the physical limitation. I had no idea how to handle that.
 
yes a good optical encoder would be the ticket ... indeed, from the internet, such encoders are often used for vco controls!! they are, unfortunately, about 30 bucks minimum, but they would be the bomb.

On the other hand, a nicely detented heavy nob has a really nice analog feel to it, if you want my 2 cents worth.

I don't know what you mean about a ten turn vernier knob ...easy enough to do with a rotary encoder (just keep count) ...vernier sounds to me like 'high resolution, so you might actually need a rotary encoder for that, to get say 200 steps a revolution over 10 revolutions is 2000 values ...you could be pushing it to get near that using a pot ... as for a physical stop after 10 turns, i suppose you could actuate a relay??? that is getting fiddly ... do you know how they did it in the orignal ems vcs3 ... or do you mean 10 detents? like with a resistor ladder behind it or something? like a grayhill rotary switch ... And grayhill make good optical encoders (but mostly detented i think)
 
Encoders are handy for some things like having your code set its value without having to physically turn the knob. But they're not without their own complications. For example the number of pulses isn't necessarily 1 per detent (the quadratic encoder I have is 4 pulses per detent). Also, you're likely to miss pulses and see screwy behavior if you don't use interrupts on the pins the encoder is connected to (especially with a longer loop). Could you set analogReadRes(14) ?
 
Encoders are handy for some things like having your code set its value without having to physically turn the knob. But they're not without their own complications. For example the number of pulses isn't necessarily 1 per detent (the quadratic encoder I have is 4 pulses per detent). More importantly, you're likely to miss pulses and see screwy behavior if you don't use interrupts on the pins the encoder is connected to (especially with a longer loop). And as previously mentioned, nice encoders are pricey anyway.

I use the solution in post #3 for translating 10-bit analog to 7-bit midi (oops). If you're translating to a resolution higher than 10-bit analog, how about setting analogReadRes(14) ?
 
I'm working on a ribbon controller for my electronic instruments. When I was using analogRead() to measure my ribbon, the values were super noisy....which then results in massive random jumps in the pitch that is produced. A key feature of my noise is that it went away if I unplugged my laptop from mains AC power. In other words, if the Teensy was powered by the laptop's battery, the analogRead values were super stable.

For me, averaging was insufficient to make my noise go away. The key for me was to add a small cap (<0.1uF) between the wiper of the ribbon and gnd. This filtered away all of the noise. It works great.

In my case, my ribbon setup naturally as a somewhat high output impedance because its voltage was being supplied via pull-up resisitor within the Teensy (~20-30 kOhm?) plus the resistance of the ribbon itself (0-20 kOhm). Perhaps this makes my situation different than yours, where maybe your applying 5V from a power rail directly to the top of your pot.

Still, I would consider putting a small cap on the wiper to ground. Too big a cap will introduce some glide (portamento) iin your output and too small of a cap will have insufficient filtering and the noise will return. So, if you don't feel like choosing your cap value based on computing the filter cutoff frequency (1/(2*pi*R*C)), you can tune it by ear.

Chip
 
Facing similar problem I wrote a library with several filters that could be daisy-chained and tried them in various combinations and with different settings. In the end I'd given up on low-pass filter because of unavoidable propagation delay (it is still useful for slow paced processes though).

What I am using now most often is step-limited oversampling filter. It works like this - first it reads sensor and limits its value to configurable range using previous value as starting point. This immediately cuts off all spikes (often present in mechanical resistors built into actuators). The adjusted value is placed into cyclic buffer and added to running sum. When the buffer is full its older value is subtracted from sum and discarded. This process is running as separate background thread utilizing all available CPU time left after main code. So, when main code needs a value the filter simply divides current running sum by buffer size and returns it.

You can call this averaging filter. I am calling it oversampling because the sensor is read all the time as fast as possible, not when the program needs the value. Note that too small step limit and too big buffer size effectively turn this into low-pass filter. For most projects so far step 2 and buffer 4 was satisfactory.

Oh, and sometimes I do use hysteresis filter as second stage, where program logic requires something important to be done on direction change. I guess this is not your case.
 
Status
Not open for further replies.
Back
Top