IR sonar sweep
A servo swings a Sharp IR distance sensor back and forth like a radar dish. The bench charts every reading and each sweep ends with a little map of what it saw.
Wiring
| From | To board | Note |
|---|---|---|
| Sharp VCC (red) | 5V | The sensor needs 5 V. Its output stays below about 3.1 V, so it is safe for the ESP32 |
| Sharp GND (black) | GND | Put the 100 µF capacitor across VCC and GND, right at the sensor |
| Sharp Vo (yellow or white) | GPIO 34 | An ADC1 pin, so it keeps working if you add Wi-Fi later |
| Servo red | 5V | One micro servo is fine from USB |
| Servo brown | GND | |
| Servo orange | GPIO 18 |
Tested on this board
Not run on this board yet. Treat the wiring and code as a first draft.
Firmware, Arduino sketch for ESP32 Dev Module
// IR sonar sweep: a servo swings a Sharp IR distance sensor from side to side
// and maps what it sees. Open the bench: angle and cm chart live, and each sweep
// ends with a little top-down map.
#include <ESP32Servo.h>
const int SERVO_PIN = 18;
const int IR_PIN = 34; // an ADC1 pin: the sensor's analog output
const int STEP_DEG = 10; // degrees per step
const int SETTLE_MS = 60; // let the servo arrive and the sensor update (it refreshes every ~40 ms)
const int SAMPLES = 8; // readings averaged per step: the sensor is noisy
const float MIN_CM = 10, MAX_CM = 80; // the GP2Y0A21's useful range
Servo servo;
float distances[180 / STEP_DEG + 1];
// The GP2Y0A21YK's output falls as distance rises, roughly as a power curve.
// cm = 27.86 * V^-1.15 is a common fit to the datasheet's typical curve from
// 10 to 80 cm. Measure a few known distances and adjust it for real accuracy.
float readCm() {
uint32_t mv = 0;
for (int i = 0; i < SAMPLES; i++) {
mv += analogReadMilliVolts(IR_PIN);
delay(2);
}
const float volts = mv / (float)SAMPLES / 1000.0f;
if (volts < 0.35f) return MAX_CM + 1; // nothing in range
const float cm = 27.86f * powf(volts, -1.15f);
return constrain(cm, MIN_CM - 1, MAX_CM + 1);
}
// A top-down map of one sweep: the sensor at the bottom middle, '*' where it saw
// something. It contains no digits, so the bench's plotter ignores it.
void drawMap() {
const int W = 41, H = 11;
char grid[H][W + 1];
for (int y = 0; y < H; y++) {
memset(grid[y], ' ', W);
grid[y][W] = 0;
}
for (int i = 0; i * STEP_DEG <= 180; i++) {
const float cm = distances[i];
if (cm > MAX_CM) continue;
const float a = radians(i * STEP_DEG);
const int x = W / 2 + (int)roundf(cosf(a) * cm / MAX_CM * (W / 2));
const int y = H - 1 - (int)roundf(sinf(a) * cm / MAX_CM * (H - 1));
if (x >= 0 && x < W && y >= 0 && y < H) grid[y][x] = '*';
}
grid[H - 1][W / 2] = 'S';
Serial.println("+-----------------------------------------+");
for (int y = 0; y < H; y++) Serial.printf("|%s|\n", grid[y]);
Serial.println("+-----------------------------------------+");
}
void setup() {
Serial.begin(115200);
analogSetPinAttenuation(IR_PIN, ADC_11db); // the full 0 to 3.1 V range
servo.setPeriodHertz(50);
servo.attach(SERVO_PIN, 500, 2400); // pulse range for SG90-class servos
servo.write(0);
delay(500);
Serial.println("IR sweep ready");
}
int sweep = 0;
void loop() {
sweep++;
const bool forward = sweep % 2 == 1; // sweep back and forth, so the servo never jumps
float nearest = MAX_CM + 1;
int nearestAt = -1;
for (int i = 0; i * STEP_DEG <= 180; i++) {
const int angle = forward ? i * STEP_DEG : 180 - i * STEP_DEG;
servo.write(angle);
delay(SETTLE_MS);
const float cm = readCm();
distances[angle / STEP_DEG] = cm;
Serial.printf("angle = %d, cm = %.1f\n", angle, cm);
if (cm < nearest) {
nearest = cm;
nearestAt = angle;
}
}
drawMap();
if (nearestAt >= 0 && nearest <= MAX_CM) {
Serial.printf("sweep = %d, nearest_cm = %.1f, nearest_angle = %d\n", sweep, nearest, nearestAt);
}
}Wiring
| From | To board | Note |
|---|---|---|
| Sharp VCC (red) | 5V | The sensor needs 5 V. Its output stays below about 3.1 V, so it is safe for the ESP32 |
| Sharp GND (black) | GND | Put the 100 µF capacitor across VCC and GND, right at the sensor |
| Sharp Vo (yellow or white) | GPIO 2 | An ADC1 pin, so it keeps working if you add Wi-Fi later |
| Servo red | 5V | One micro servo is fine from USB |
| Servo brown | GND | |
| Servo orange | GPIO 5 |
Tested on this board
Not run on this board yet. Treat the wiring and code as a first draft.
Firmware, Arduino sketch for ESP32S3 Dev Module
// IR sonar sweep: a servo swings a Sharp IR distance sensor from side to side
// and maps what it sees. Open the bench: angle and cm chart live, and each sweep
// ends with a little top-down map.
#include <ESP32Servo.h>
const int SERVO_PIN = 5;
const int IR_PIN = 2; // an ADC1 pin: the sensor's analog output
const int STEP_DEG = 10; // degrees per step
const int SETTLE_MS = 60; // let the servo arrive and the sensor update (it refreshes every ~40 ms)
const int SAMPLES = 8; // readings averaged per step: the sensor is noisy
const float MIN_CM = 10, MAX_CM = 80; // the GP2Y0A21's useful range
Servo servo;
float distances[180 / STEP_DEG + 1];
// The GP2Y0A21YK's output falls as distance rises, roughly as a power curve.
// cm = 27.86 * V^-1.15 is a common fit to the datasheet's typical curve from
// 10 to 80 cm. Measure a few known distances and adjust it for real accuracy.
float readCm() {
uint32_t mv = 0;
for (int i = 0; i < SAMPLES; i++) {
mv += analogReadMilliVolts(IR_PIN);
delay(2);
}
const float volts = mv / (float)SAMPLES / 1000.0f;
if (volts < 0.35f) return MAX_CM + 1; // nothing in range
const float cm = 27.86f * powf(volts, -1.15f);
return constrain(cm, MIN_CM - 1, MAX_CM + 1);
}
// A top-down map of one sweep: the sensor at the bottom middle, '*' where it saw
// something. It contains no digits, so the bench's plotter ignores it.
void drawMap() {
const int W = 41, H = 11;
char grid[H][W + 1];
for (int y = 0; y < H; y++) {
memset(grid[y], ' ', W);
grid[y][W] = 0;
}
for (int i = 0; i * STEP_DEG <= 180; i++) {
const float cm = distances[i];
if (cm > MAX_CM) continue;
const float a = radians(i * STEP_DEG);
const int x = W / 2 + (int)roundf(cosf(a) * cm / MAX_CM * (W / 2));
const int y = H - 1 - (int)roundf(sinf(a) * cm / MAX_CM * (H - 1));
if (x >= 0 && x < W && y >= 0 && y < H) grid[y][x] = '*';
}
grid[H - 1][W / 2] = 'S';
Serial.println("+-----------------------------------------+");
for (int y = 0; y < H; y++) Serial.printf("|%s|\n", grid[y]);
Serial.println("+-----------------------------------------+");
}
void setup() {
Serial.begin(115200);
analogSetPinAttenuation(IR_PIN, ADC_11db); // the full 0 to 3.1 V range
servo.setPeriodHertz(50);
servo.attach(SERVO_PIN, 500, 2400); // pulse range for SG90-class servos
servo.write(0);
delay(500);
Serial.println("IR sweep ready");
}
int sweep = 0;
void loop() {
sweep++;
const bool forward = sweep % 2 == 1; // sweep back and forth, so the servo never jumps
float nearest = MAX_CM + 1;
int nearestAt = -1;
for (int i = 0; i * STEP_DEG <= 180; i++) {
const int angle = forward ? i * STEP_DEG : 180 - i * STEP_DEG;
servo.write(angle);
delay(SETTLE_MS);
const float cm = readCm();
distances[angle / STEP_DEG] = cm;
Serial.printf("angle = %d, cm = %.1f\n", angle, cm);
if (cm < nearest) {
nearest = cm;
nearestAt = angle;
}
}
drawMap();
if (nearestAt >= 0 && nearest <= MAX_CM) {
Serial.printf("sweep = %d, nearest_cm = %.1f, nearest_angle = %d\n", sweep, nearest, nearestAt);
}
}Wiring
| From | To board | Note |
|---|---|---|
| Sharp VCC (red) | 5V | The sensor needs 5 V. Its output stays below about 3.1 V, so it is safe for the ESP32 |
| Sharp GND (black) | GND | Put the 100 µF capacitor across VCC and GND, right at the sensor |
| Sharp Vo (yellow or white) | GPIO 3 | An ADC1 pin, so it keeps working if you add Wi-Fi later |
| Servo red | 5V | One micro servo is fine from USB |
| Servo brown | GND | |
| Servo orange | GPIO 4 |
Tested on this board
Not run on this board yet. Treat the wiring and code as a first draft.
Firmware, Arduino sketch for ESP32C3 Dev Module
// IR sonar sweep: a servo swings a Sharp IR distance sensor from side to side
// and maps what it sees. Open the bench: angle and cm chart live, and each sweep
// ends with a little top-down map.
#include <ESP32Servo.h>
const int SERVO_PIN = 4;
const int IR_PIN = 3; // an ADC1 pin: the sensor's analog output
const int STEP_DEG = 10; // degrees per step
const int SETTLE_MS = 60; // let the servo arrive and the sensor update (it refreshes every ~40 ms)
const int SAMPLES = 8; // readings averaged per step: the sensor is noisy
const float MIN_CM = 10, MAX_CM = 80; // the GP2Y0A21's useful range
Servo servo;
float distances[180 / STEP_DEG + 1];
// The GP2Y0A21YK's output falls as distance rises, roughly as a power curve.
// cm = 27.86 * V^-1.15 is a common fit to the datasheet's typical curve from
// 10 to 80 cm. Measure a few known distances and adjust it for real accuracy.
float readCm() {
uint32_t mv = 0;
for (int i = 0; i < SAMPLES; i++) {
mv += analogReadMilliVolts(IR_PIN);
delay(2);
}
const float volts = mv / (float)SAMPLES / 1000.0f;
if (volts < 0.35f) return MAX_CM + 1; // nothing in range
const float cm = 27.86f * powf(volts, -1.15f);
return constrain(cm, MIN_CM - 1, MAX_CM + 1);
}
// A top-down map of one sweep: the sensor at the bottom middle, '*' where it saw
// something. It contains no digits, so the bench's plotter ignores it.
void drawMap() {
const int W = 41, H = 11;
char grid[H][W + 1];
for (int y = 0; y < H; y++) {
memset(grid[y], ' ', W);
grid[y][W] = 0;
}
for (int i = 0; i * STEP_DEG <= 180; i++) {
const float cm = distances[i];
if (cm > MAX_CM) continue;
const float a = radians(i * STEP_DEG);
const int x = W / 2 + (int)roundf(cosf(a) * cm / MAX_CM * (W / 2));
const int y = H - 1 - (int)roundf(sinf(a) * cm / MAX_CM * (H - 1));
if (x >= 0 && x < W && y >= 0 && y < H) grid[y][x] = '*';
}
grid[H - 1][W / 2] = 'S';
Serial.println("+-----------------------------------------+");
for (int y = 0; y < H; y++) Serial.printf("|%s|\n", grid[y]);
Serial.println("+-----------------------------------------+");
}
void setup() {
Serial.begin(115200);
analogSetPinAttenuation(IR_PIN, ADC_11db); // the full 0 to 3.1 V range
servo.setPeriodHertz(50);
servo.attach(SERVO_PIN, 500, 2400); // pulse range for SG90-class servos
servo.write(0);
delay(500);
Serial.println("IR sweep ready");
}
int sweep = 0;
void loop() {
sweep++;
const bool forward = sweep % 2 == 1; // sweep back and forth, so the servo never jumps
float nearest = MAX_CM + 1;
int nearestAt = -1;
for (int i = 0; i * STEP_DEG <= 180; i++) {
const int angle = forward ? i * STEP_DEG : 180 - i * STEP_DEG;
servo.write(angle);
delay(SETTLE_MS);
const float cm = readCm();
distances[angle / STEP_DEG] = cm;
Serial.printf("angle = %d, cm = %.1f\n", angle, cm);
if (cm < nearest) {
nearest = cm;
nearestAt = angle;
}
}
drawMap();
if (nearestAt >= 0 && nearest <= MAX_CM) {
Serial.printf("sweep = %d, nearest_cm = %.1f, nearest_angle = %d\n", sweep, nearest, nearestAt);
}
}Parts
| Quantity | Part | Where to buy |
|---|---|---|
| 1× | ESP32 dev board | Buy at Amazon |
| 1× | Sharp GP2Y0A21YK0F IR distance sensor, 10 to 80 cm | Buy at Adafruit |
| 1× | Micro servo, SG90 or similar | Buy at Adafruit |
| 1× | 100 µF electrolytic capacitor | |
| 6× | Jumper wires |
Amazon buy links may include an Associates tag, and this site may earn a commission from qualifying Amazon purchases at no extra cost to you. Adafruit links are direct.
Libraries
ESP32Servo
Load it
- In the Arduino IDE, install the esp32 board package and pick the board named above the sketch.
- Install the libraries listed above from the Library Manager.
- Paste the sketch, choose the board's port and upload. Hold BOOT if the upload stalls at Connecting.
- Close the IDE's serial monitor, then open the bench here to watch the output.
Field notes
- Stick the IR sensor on the servo horn with tape or a dab of hot glue so it points the way the horn turns.
- The Sharp sensor draws its current in short bursts, which makes its output jumpy. The 100 µF capacitor across its power pins smooths that; the sketch also averages eight readings per step.
- It only measures from about 10 to 80 cm. Closer than 10 cm the reading folds back and looks farther away, so keep the first 10 cm in front of it clear.
- The cm figure comes from a typical curve for the GP2Y0A21. Check it with a ruler at two or three distances and adjust the 27.86 and -1.15 in readCm() if yours reads high or low.
- Dark, shiny or very angled surfaces send back less infrared light and may read farther than they are. A sheet of paper makes a good test target.
- Open the bench at 115200 baud: angle and cm chart live, and each sweep prints a top-down map with the sensor at the bottom.
Not tested yet
- Not tested on hardware yet. Compiles for every board listed with Arduino-ESP32 core 3.3.12.
Community builds
Loading build reports from people who made this.
Built it? Send a build report with your board and what the serial port printed. Working reports become the "Tested on this board" entries above.