Teensy 4.0: Smoothing analog read - capacitor value calculation - help required

Christian_K

New member
I build a simple, working teensy 4.0 based midi-controller with Doepfer potentiometer wheels:
MidiController01_Steckplatine.png
s_PXL_20241003_125704426.MP.jpg


The full code is listed at the end of this post.

I have an issue with the value smoothing, which leads to midi-event flooding whilst the potentiometers are in between my floor and ceiling values , therefore this post, with the intention to get help.

(This is my first Arduino and Electronics project)

I used 3 different value smoothing concepts:
a.) Default software solution [1]
b.) Extended software solution [2]
c.) Teensy on board solution (`analogReadResolution() analogReadAveraging()`)

I still use approach c.) but the analogue readings are still not very smooth. Using a different analogReadResolution-setting does not help. With the settings analogReadResolution(10) and
analogReadAveraging(32) I still get following deviation, when i keep my pitchbend wheel still somewhere in the upper half:
Bildschirmfoto 2024-11-24 um 22.41.13.png


I would like to solve this root cause, i.e. I want to build a circuit, which prevents the deviations, by smoothing the current instead of smoothing unstable readings.

According to this post [3], analoge readings should be smoother, with 100nF capacitors added between the ground and the analogue board input. I bought various high quality 100nF capacitors, but they appear not to have any effect on the deviation at all, whilst added, i.e. the deviation plot does not look any different, compared to without the capacitors. 100nF might not fit for my purpose, as I have 3 potentiometers instead of 1 in the post behind [3].

So my questions are:
  • What is the electronic intention of the capacitors mentioned (i.e. what is the design pattern idea behind adding them)?
  • How can I calculate the proper capacitor value for my setup, i.e. what is the math behind the issue?
    • Do I have to triple the capacitor value?
    • Do I have to divide the capacitor value by 3?
  • Any other ideas to solve the problem by design in favour of having bad readings?

Thank you all in advance

[1] https://docs.arduino.cc/built-in-examples/analog/Smoothing/
[2] https://goetzmd.de/nicht-konstante-poti-werte-am-arduino-ausgleichen-tutorial/
[3] https://forum.pjrc.com/index.php?threads/teensy-3-1-analogread-0-1-uf-capacitor-but-what-kind.54027/

C:
const boolean serialMode = true;
const int midiChannel = 1;
const int midiCtrlNoModulation = 1;
const int potiAmount = 3;

int potiPin[] = { A0, A1, A2 };
int potiReadValue[] = { 0, 0, 0 };
int outputValue[] = { 0, 0, 0 };
int outputValueOld[] = { 0, 0, 0 };

void setup() {
  analogReadAveraging(32);
  analogReadResolution(10);
  if (serialMode) {
    Serial.begin(9600);
  }
}

void loop() {
  for (int i = 0; i < potiAmount; i++) {
    potiReadValue[i] = analogRead(potiPin[i]);
    if (i == 0) {
      // Spring centered pitch bend wheel
      outputValue[i] = midiCenterValueMappingPitch(potiReadValue[i], 150, 300, 313, 475);
    } else if (i == 1) {
      // Non-centered modulation wheel
      outputValue[i] = constrain(map(potiReadValue[i], 162, 502, 0, 127), 0, 127);
    } else if (i == 2) {
      // Non-centered aftertouch wheel
      outputValue[i] = constrain(map(potiReadValue[i], 144, 508, 0, 127), 0, 127);
    }

    if (outputValueOld[i] != outputValue[i]) {
      if (i == 0) usbMIDI.sendPitchBend(outputValue[i], midiChannel);
      else if (i == 1) usbMIDI.sendControlChange(midiCtrlNoModulation, outputValue[i], midiChannel);
      else if (i == 2) usbMIDI.sendAfterTouch(outputValue[i], midiChannel);
    }
    outputValueOld[i] = outputValue[i];
  }
  traceValuesWhenSerialOn();
  delay(5);
}

int midiCenterValueMappingPitch(int readValue, int lowerCeiling, int lowerThreshold, int upperThreshold, int upperCeiling) {
  // https://studiocode.dev/kb/MIDI/midi-pitch-bend/
  int result;
  if (readValue <= lowerThreshold) {
    result = constrain(map(readValue, lowerCeiling, lowerThreshold, -8192, -1), -8192, 1);
  } else if (readValue >= upperThreshold) {
    result = constrain(map(readValue, upperThreshold, upperCeiling, 1, 8191), 1, 8191);
  } else {
    result = 0;
  }
  return result;
}

void traceValuesWhenSerialOn() {
  // Prints the current read values as well as the mapped Values
  if (serialMode) {
    delay(100);
    Serial.print("|\t");
    for (int x = 0; x < potiAmount; x++) {
      Serial.print(potiReadValue[x]);
      Serial.print("\t");
    }
    Serial.print("|\t");
    for (int x = 0; x < potiAmount; x++) {
      Serial.print(outputValue[x]);
      Serial.print("\t");
    }
    Serial.println("");
  }
}
 
What is the scale of the graph? The code is all int but I'm seeing decimals. Are we jumping back and forth by 2 LSB, full scale or something else?

Let's focus on the modulation and aftertouch first. Do these work completely satisfactorily now? Let me expand a bit... By setting the analog read resolution to 10 bits you're constraining your analogRead() return values to the range between 0-1023, which corresponds to 10 bits. After this you're using the map function to map those values to 0-127, but you're telling the map function that for the modwheel the possible input value range is 162-502 and for the aftertouch wheel it is 144-508. Why?

I suggest here a simpler way of translating the 10-bit readings to 7 bits (0-127). Given the input resolution of 10 bits, you can just bit-shift the value to the right by three to discard the three least significant bits. Also, I'd use the bitwise AND operator to make sure the value is within the 7-bit range - if you want details on why, I can expand, but it'll get lengthy.

So, in code, something like:

Code:
int value = analogRead(pin);
int value7bit;

value7bit = (value >> 3) & 0x7F; /* 0x7F is hexadecimal for 127, which in binary would be 0111 1111 - lowest 7 bits all on */

edit; pitchbend handling also has incorrect range (or at least it seems so to me) but that can get somewhat more interesting - I can expand on that if you like.
 
@Christian_K You may want to consider using the ResponsiveAnalogRead library. That works like a charm.
Some time ago I built a MIDI fader using 3pcs 10KΩ faderpots (plus 3pcs rotary encoder).
Here is the schematic:
1732524584180.png


As you can see I added 3x 100nF caps to the fader signals to filter out noise. BTW, I used a Teensy LC but a Teensy 4.0 should work as well.

Here is the code (I stripped out the rotary encoder stuff):
C++:
#include <ResponsiveAnalogRead.h>

ResponsiveAnalogRead analogZero(A0, true);
ResponsiveAnalogRead analogOne(A1, true);
ResponsiveAnalogRead analogTwo(A2, true);

int value = 0;

void setup() {
  pinMode(A0, INPUT_DISABLE);
  pinMode(A1, INPUT_DISABLE);
  pinMode(A2, INPUT_DISABLE);
}

void loop() {
  // read faders ----------------------------------------------------------------------------
  analogZero.update();
  if (analogZero.hasChanged()) {
    value = constrain((map(analogZero.getValue(), 1, 1022, -8192, 8191)), -8192, 8191);
    usbMIDI.sendPitchBend(value, 1);  // ChannelFader #1 at ch 1
  }
  analogOne.update();
  if (analogOne.hasChanged()) {
    value = constrain((map(analogOne.getValue(), 1, 1022, -8192, 8191)), -8192, 8191);
    usbMIDI.sendPitchBend(value, 2);  // ChannelFader #2 at ch 2
  }
  analogTwo.update();
  if (analogTwo.hasChanged()) {
    value = constrain((map(analogTwo.getValue(), 1, 1022, -8192, 8191)), -8192, 8191);
    usbMIDI.sendPitchBend(value, 3);  // ChannelFader #3 at ch 3
  }
 
  usbMIDI.send_now();
  // discard incoming MIDI messages ---------------------------------------------------------
  while (usbMIDI.read()) {
  }
  delay(10);
}

Hope this helps to get you going.

Gruesse,
Paul
 
I suspect your problem is made worse by the capacitor(s). The T4 pulls a lot of bursty current from its 3.3V supply which causes voltage fluctuations. Adding the caps to the wipers will prevent potentiometers being ratiometric to the supply (which is used as the analog reference).

IIRC the T4 design doesn't have room for a separate filtered analog supply rail and ground so there is significant digital noise on the analog subsystem.

An external ADC with a _clean_ analog supply is the best way to reduce noise spikes here, and will enable capacitors to work well. Using an ADC built-in to a noisy digital chip is never very satisfactory, both because of the noise and the process not being optimized for analog.

The standard software approach to reducing the effect of noise is hysteresis, but with a lot of noise you then see unwanted hysteresis (unsurprizingly!). Another approach is limiting the sampling rate of the control, not great either.
 
your analogRead() return values to the range between 0-1023, which corresponds to 10 bits. After this you're using the map function to map those values to 0-127, but you're telling the map function that for the modwheel the possible input value range is 162-502 and for the aftertouch wheel it is 144-508. Why
The weird ranges are due to the endpoints of the doepfer modweel construction: Although the potentiometer potentially covers the full range from 0-1023, its range is limited by the mounting kit's metal limit stops so in case of
outputValue[i] = constrain(map(potiReadValue[i], 162, 502, 0, 127), 0, 127);
it means that
  • the actual lowest value you can get from the mod-wheel-potentiometer in the way it is currently mounted = 158.
  • the acutal highest value you can get from the mod-whell-potentiometer in the way it is currently mounted = 506.
PXL_20241127_230713993.MP.jpg


With the headroom of 8 I have defined a 'floor' value of 162 and a 'ceiling' value of 506, these values are projected to the min and max values of the intended controller output.

Do these work completely satisfactorily now?
Absolutely, except for the fact that when the potentiometers are 'somewhere in between', my DAW get's flooded with slightly deviated values.

Although the graph has a decimal scale, it just shows int values, i.e. The wheel is held fixed at 415 but the value readings shown deviate between 414 and 417 which triggers the flooding due to
(outputValueOld[i] != outputValue[i])
 
My strategy will be like this
  1. I will try the fourth smoothing concept I know now (thanks to you): ResponsiveAnalogRead library
  2. If this does lead to a significant improvement, i have to decide whether or not to sacrifice the intended luxury of not having a external ADC in favour of not having the flooding by trying to mount an external ADC, which would hopefully help.
IIRC the T4 design doesn't have room for a separate filtered analog supply rail and ground so there is significant digital noise on the analog subsystem.
(Keep in mind: I am a beginner) Question: Would it be constructive to think about adding something in between the usb-cable-male socket and the t4-usb-female socket, which would make the teensy feel like being powered as smoothly as by an external ADC? Something like adding a y-junction splitting the power coming over usb? :unsure:
 
The ResponsiveAnalogRead library will work perfectly for you. I use MIDI to control DAWs all the time with this setup. The cool thing is that you set a threshold value in which the pot has to move to detect a change and then read when the pot is changed.
 
Does ResponsiveAnalogRead work in both directions? i.e., when pot is above threshold moving downwards, as well as when pot is below threshold moving upwards.
 
Back
Top