rotary potentiometer/Using the number/08. Calibrating the turn
Using the number · 08 of 10

Calibrating the turn

map() from 0 to full scale assumes the reading rises in a straight line from exactly 0 to exactly full scale. Neither is quite true here. Five readings, taken once at the two ends and three marks between, turn any reading back into a position in percent.

What map() assumes

map(reading, 0, 1023, 0, 100) draws one straight line from the bottom of the range to the top. It is right when the reading rises evenly with the turn, from exactly 0 at one end to exactly full scale at the other.

On this block that is two assumptions too many. The reading bends, because the LED loads the wiper: hard on 5 V, less on 3.3 V. On an ESP32 the top end never reaches 4095 where the knob does; it reaches 4095 early, where the ADC stops. Neither is a fault, and neither is fixed by getting the two ends right.

From a reading to a position
VCC
Method
Knob75 %
Knob at
75 %
Sketch says
75 %
Worst anywhere
8 points
Five readings, taken with the knob at each end and at three marks between, and straight lines between them. Most of the bend comes out: the worst error falls from about 25 points to about 8, in the last quarter where the curve turns up most sharply. A sixth mark in that quarter would take out more.

A table instead of a line

The calibration sketch asks for five readings: at both ends of the turn and at a quarter, a half and three-quarters. Then, for any reading, it finds the two marks it falls between and draws a straight line between just those two. A bent curve becomes four short straight pieces, and short pieces follow a bend much better than one long one.

Pick the Uno in the figure and switch between Ends only and Five marks. From the ends alone the worst error is about 25 points, somewhere in the upper half. With the table it falls to about 8, all of it in the last quarter, where the model's curve turns up most sharply. A sixth mark at 90 % would take out most of what is left.

On 3.3 V the bend is smaller, and the table still earns its place: from the ends alone the worst error is about 14 points, and with the table about 8. On an ESP32 it also fixes the top: it records where the reading really stops instead of assuming it reaches 4095 at the end of the turn.

Taking the readings

Upload the sketch, open the Serial Monitor at 115200, and it asks for the first mark. Turn the knob fully to the end that reads lowest, type any key and press Enter, and it prints what it stored. Then a quarter-turn, and so on.

The marks do not need to be exact. With a turn of typically about 300 degrees, a quarter is about 75 degrees, and a guess by eye is close enough to take out most of the bend. A pencil line on the end of the knob and marks on a piece of tape make it repeatable.

The table holds for that block, on that board, with that VCC. Once you have numbers you like, copy them into table[] in the code, take the calibration loop out of setup(), and the sketch starts straight into reading.

The code

At start-up the sketch asks for the knob at five marks and stores a reading at each. Then it prints the raw reading and the position it stands for, found by straight lines between the stored readings. Open the Serial Monitor to answer it.

rotary_pot_calibrate.ino
/*
  Rotary Potentiometer - calibrating the turn            TK08 / /p/tk08

  Wiring. Count from the square pad on the TinkerBlock board, knob
  up, header at the bottom:

    GND    -> GND
    VCC    -> 5V on an Uno; 3V3 on an ESP32, ESP32-S3 or Pico
              (the full turn puts VCC on your analog pin)
    NC     -> nothing   (unconnected on the board)
    SIGNAL -> A0 on an Uno, GPIO 34 on an ESP32, GPIO 4 on an
              ESP32-S3, GP26 on a Raspberry Pi Pico

  Arduino IDE
    Tools > Board                 your board, e.g. ESP32S3 Dev Module
    Tools > Port                  the one that appears when you plug in
    Tools > USB CDC On Boot       Enabled   (ESP32-S3 only)
    Tools > Serial Monitor        115200; type a key, Enter, to answer
    No library needed.
*/

// The pin SIGNAL is wired to, picked for the board you compile for.
// Uno: A0. ESP32: 34. ESP32-S3: 4. Pico: 26.
#if defined(ARDUINO_ARCH_AVR)
const int POT_PIN = A0;
#elif defined(ARDUINO_ARCH_RP2040)
const int POT_PIN = 26;
#elif defined(CONFIG_IDF_TARGET_ESP32S3)
const int POT_PIN = 4;
#else
const int POT_PIN = 34;         // the classic ESP32
#endif

const int MARKS = 5;
const int MARK_PCT[MARKS] = {0, 25, 50, 75, 100};
int table[MARKS];               // the reading at each mark

int readSteady() {              // sixteen readings, averaged
  long sum = 0;
  for (int i = 0; i < 16; i++) sum += analogRead(POT_PIN);
  return sum / 16;
}

void waitForEnter() {
  while (Serial.available()) Serial.read();
  while (!Serial.available()) delay(10);
  delay(50);
  while (Serial.available()) Serial.read();
}

// Position in percent: straight lines between the marks.
float positionOf(int reading) {
  if (reading <= table[0]) return MARK_PCT[0];
  for (int i = 1; i < MARKS; i++) {
    if (reading <= table[i]) {
      int span = table[i] - table[i - 1];
      float f = span > 0 ? float(reading - table[i - 1]) / span : 0;
      return MARK_PCT[i - 1] + f * (MARK_PCT[i] - MARK_PCT[i - 1]);
    }
  }
  return MARK_PCT[MARKS - 1];
}

void setup() {
  Serial.begin(115200);
#if !defined(ARDUINO_ARCH_AVR)
  analogReadResolution(12);     // the Pico's core starts at 10 bits
#endif
  delay(2000);                  // time to open the Serial Monitor
  for (int i = 0; i < MARKS; i++) {
    Serial.print("Turn the knob to ");
    Serial.print(MARK_PCT[i]);
    Serial.println(" % and press Enter.");
    waitForEnter();
    table[i] = readSteady();
    Serial.print("  reads ");
    Serial.println(table[i]);
  }
}

void loop() {
  int reading = readSteady();
  Serial.print(reading);
  Serial.print("  ->  ");
  Serial.print(positionOf(reading), 1);
  Serial.println(" %");
  delay(200);
}

To take each reading, type any character in the Serial Monitor's input box and press Enter: with the line ending set to No Line Ending, an empty Enter sends nothing. What you type is thrown away. Each stored reading is an average of sixteen, so jitter does not end up in the table. The table must rise from mark to mark: start at the end that reads lowest.

When it does not work

How exact do the marks have to be?

Not very. The table only has to be roughly right to take out most of the bend, and a quarter-turn guessed by eye is close enough. A pencil line on the knob's end and four marks on a strip of tape beside it make it repeatable.

The position runs backwards, 100 % at the start.

Your first reading was taken at the VCC end. The table has to rise from mark to mark, so take the 0 % reading at the end that reads lowest. If you need the knob to count the other way, subtract the position from 100 after the lookup.

Do I have to calibrate every time the board starts?

Only if you want to. Once you have the five numbers the sketch prints, copy them into the table in the code and delete the calibration step. They hold for that block on that board and that VCC; change any of the three and measure again.

On my ESP32 the last few degrees all say 100 %.

Its ADC stops rising at roughly 3.1 V, which on 3.3 V, with the LED holding the wiper down until near the end, is only the last few degrees of the turn. Everything past that reads 4095, the same as the 100 % mark, so the table calls it all 100 %. Nothing is wrong; those few degrees at the top are simply dead.

Where this goes next

Taking out the last few counts of jitter without making the knob feel slow.

Smoothing the reading

Edit this page — content/books/rotary-potentiometer/calibrating-the-turn.mdx

Community

Questions about this product

See what other owners have asked, and read their solutions.

Ask a question ↗

Rotary Potentiometer

Loading discussions…

Discuss this article

Ask about this page. The answer stays here, on the page it belongs to, for whoever hits the same wall next.

Browse Modules and blocks on the forum