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.
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 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.
The same calibration in MicroPython. input() waits for Enter in Thonny's shell, so the five readings are taken as you answer, and then the loop prints the position.
"""
Rotary Potentiometer - calibrating, MicroPython TK08 / /p/tk08
Wiring. Count from the square pad on the TinkerBlock board, knob
up, header at the bottom:
GND -> GND
VCC -> 3V3 (never 5V: the full turn puts VCC on your pin)
NC -> nothing (unconnected on the board)
SIGNAL -> GPIO 34 on an ESP32, GPIO 4 on an ESP32-S3,
GP26 on a Raspberry Pi Pico
Thonny
Run > Configure interpreter MicroPython (ESP32) or
MicroPython (Raspberry Pi Pico)
Answer in the shell: press Enter at each mark.
Nothing to install: machine and time are built in.
"""
from machine import ADC, Pin
import sys
import time
# The GPIO number SIGNAL is wired to. ESP32: 34. ESP32-S3: 4. Pico: 26.
POT_PIN = 4
pot = ADC(Pin(POT_PIN))
if sys.platform == "esp32":
pot.atten(ADC.ATTN_11DB) # widest range, roughly 0 to 3.1 V
MARK_PCT = [0, 25, 50, 75, 100]
def read_steady(): # sixteen readings, averaged
total = 0
for _ in range(16):
total += pot.read_u16() >> 4 # 0 to 4095
return total // 16
def position_of(reading, table):
if reading <= table[0]:
return MARK_PCT[0]
for i in range(1, len(table)):
if reading <= table[i]:
span = table[i] - table[i - 1]
f = (reading - table[i - 1]) / span if span > 0 else 0
return MARK_PCT[i - 1] + f * (MARK_PCT[i] - MARK_PCT[i - 1])
return MARK_PCT[-1]
table = []
for pct in MARK_PCT:
input("Turn the knob to {} % and press Enter.".format(pct))
table.append(read_steady())
print(" reads", table[-1])
while True:
reading = read_steady()
print(reading, " -> ", "{:.1f} %".format(position_of(reading, table)))
time.sleep_ms(200)There is no Uno here: an Uno cannot run MicroPython. Readings are scaled to 0 to 4095 to match the Arduino sketch on a 3.3 V board. Run it from Thonny, not as main.py: it needs you at the keyboard to answer.
When it does not work
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.
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.
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.
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.
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
Questions about this product
See what other owners have asked, and read their solutions.
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.