مشروع مدمج · Arduino Uno R3

رادار بالموجات فوق الصوتية على Arduino Uno

رادار بالموجات فوق الصوتية على Arduino: HC-SR04 على سيرفو يمسح 150 درجة، وOLED يرسم شاشة الرادار، وبيزو يطلق نبضة لأي شيء أقرب من 20 سم. المشروع كله، Arduino Uno و4 قطع أخرى، يعمل هنا في متصفحك على البرنامج الثابت أدناه؛ افتحه في المحرر لتغيير التوصيل أو الكود وتشغيله مرة أخرى.

تدرّس؟ كلّف صفك به بنقرة واحدة.

متوسط About 35 minutes to build and run. يعمل في متصفحك. مجاني ودون حاجة إلى حساب.

تُباع القطع بنحو $37. وفي Mokxi هي مجانية.

رادار بالموجات فوق الصوتية · Arduino Uno R3live0.000 s 0.00x
انقر لفتحه في المحرر
الدائرة نفسها، تعمل هنا على المحاكي. اضغط على ما يمكن ضغطه؛ وانقر أي شيء آخر لفتحه في المحرر.

What you'll learn

  • Measuring distance from the time of an echo
  • Sweeping a sensor with a servo in fixed steps
  • Converting an angle and distance to screen x and y
  • Drawing on an SSD1306 OLED with the Adafruit library
  • Redrawing only what changed on a small display
  • Difficulty: متوسط
  • Time: About 35 minutes
  • Board: Arduino Uno R3
  • Parts: 8

كيف يعمل

An HC-SR04 sits on a servo. The servo turns in 3 degree steps from 15 to 165 degrees and back. At each step the sketch waits 20 ms, then measures the distance.

The OLED draws a radar screen. A sweep line points where the sensor points. A dot shows each echo within 50 cm, which is the edge of the screen. A dot stays until the sweep comes back and finds nothing there.

A piezo on pin 2 pings when something is closer than 20 cm. The Serial Monitor prints one line per step, with the angle and distance in centimeters.

Kits that can build it

This starter kit has every part in the circuit. Each kit's page lists what else it can build.

Wiring table

Every connection, pin by pin, read from the circuit itself. Each row joins the pin on the left to the pin on the right, by a jumper wire or a breadboard strip.

القطعة والطرفConnects to
Ultrasonic ranger pin TRIGArduino Uno R3 pin 11
Ultrasonic ranger pin ECHOArduino Uno R3 pin 10
Hobby servo pin PWMArduino Uno R3 pin 3
Piezo speaker pin 1Arduino Uno R3 pin 2
OLED display, 128x64 pin SDAArduino Uno R3 pin A4
OLED display, 128x64 pin SCLArduino Uno R3 pin A5
Ultrasonic ranger pin VCC5 V
Ultrasonic ranger pin GNDالأرضي
Hobby servo pin GNDالأرضي
Hobby servo pin VCC5 V
Piezo speaker pin 2الأرضي
OLED display, 128x64 pin GNDالأرضي
OLED display, 128x64 pin VCC5 V

الكود

البرنامج الثابت تمامًا كما يفتحه المحرر. غيّر سطرًا هناك واضغط «تشغيل»: يُترجَم في المتصفح.

sketch.ino · Arduino Uno R3
// Ultrasonic radar: an HC-SR04 on an SG90 servo sweeps from side to side and
// draws what it finds on a 128x64 OLED, like a radar screen.
//
//   servo     signal -> pin 3, VCC -> 5 V, GND -> GND
//   HC-SR04   TRIG -> pin 11, ECHO -> pin 10, VCC -> 5 V, GND -> GND
//   OLED      SDA -> A4, SCL -> A5, VCC -> 5 V, GND -> GND (address 0x3C)
//   piezo     pin 2 and GND: a short ping for anything closer than 20 cm
//
// The sensor sits on the servo horn. Every 3 degrees, from 15 to 165 and back,
// the servo turns, waits for the horn to get there, and the sensor measures.
// The sweep line on the screen points where the sensor points, and a dot is
// drawn at the distance it measured. A dot stays until the sweep comes back
// past it and finds nothing there.
//
// The Serial Monitor gets one line per step, "angle,distance." in centimeters,
// the format the well-known Processing radar sketch reads.

#include <Servo.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

const uint8_t SERVO_PIN = 3;
const uint8_t TRIG = 11;
const uint8_t ECHO = 10;
const uint8_t PIEZO = 2;

const int MIN_ANGLE = 15;
const int MAX_ANGLE = 165;
const int STEP = 3;
const int STEPS = (MAX_ANGLE - MIN_ANGLE) / STEP + 1;  // 51
const int RANGE_CM = 50;  // the edge of the screen
const int CLOSE_CM = 20;  // closer than this pings

// The radar's center is the middle of the bottom edge; 60 pixels is 50 cm.
const int CX = 64;
const int CY = 63;
const int RADIUS = 60;

Adafruit_SSD1306 display(128, 64, &Wire, -1);
Servo turret;

// The last distance seen at each step, in cm; 0 is nothing in range.
uint8_t seen[STEPS];
int angle = MIN_ANGLE;
int direction = STEP;

long measureCm() {
  digitalWrite(TRIG, LOW);
  delayMicroseconds(2);
  digitalWrite(TRIG, HIGH);
  delayMicroseconds(10);
  digitalWrite(TRIG, LOW);
  // 30 ms is about 5 m and back: anything longer is nothing at all.
  unsigned long us = pulseIn(ECHO, HIGH, 30000UL);
  if (us == 0) return 0;
  // Sound covers 0.0343 cm a microsecond, there and back: 58 us a centimeter.
  return us / 58;
}

// Where a point `cm` away at `deg` lands on the screen. 0 degrees is the
// right-hand side, 90 straight up, 180 the left.
void polar(int deg, int cm, int &x, int &y) {
  float r = cm * (float)RADIUS / RANGE_CM;
  float a = deg * 0.0174533;
  x = CX + (int)(r * cos(a));
  y = CY - (int)(r * sin(a));
}

// The end of the sweep line on the screen last time, so it can be rubbed out.
int lineX = -1;
int lineY = 0;

void ring() {
  display.drawCircle(CX, CY, RADIUS / 2, SSD1306_WHITE);
  display.drawCircle(CX, CY, RADIUS, SSD1306_WHITE);
  display.drawFastHLine(0, CY, 128, SSD1306_WHITE);
}

// Change only what moved. The driver sends just the parts of the screen that
// changed, so rubbing out the old line and drawing the new one is far less
// to send than clearing the screen and drawing it all again.
void drawScreen(int i, uint8_t was, long cm) {
  int x, y;
  if (lineX >= 0) display.drawLine(CX, CY, lineX, lineY, SSD1306_BLACK);
  if (was != 0) {
    polar(MIN_ANGLE + i * STEP, was, x, y);
    display.fillCircle(x, y, 2, SSD1306_BLACK);
  }
  // the reading, top left, where the rings never reach
  display.fillRect(0, 0, 26, 17, SSD1306_BLACK);
  display.setCursor(0, 0);
  display.print(angle);
  display.print((char)247);  // the degree sign in the built-in font
  display.setCursor(0, 9);
  if (cm == 0 || cm > RANGE_CM) {
    display.print("none");
  } else {
    display.print(cm);
    display.print("cm");
  }
  // put back whatever the rubbing out took off the rings and the other echoes
  ring();
  for (int k = 0; k < STEPS; k++) {
    if (seen[k] == 0) continue;
    polar(MIN_ANGLE + k * STEP, seen[k], x, y);
    display.fillCircle(x, y, 2, SSD1306_WHITE);
  }
  polar(angle, RANGE_CM, lineX, lineY);
  display.drawLine(CX, CY, lineX, lineY, SSD1306_WHITE);
  display.display();
}

void setup() {
  Serial.begin(115200);
  pinMode(TRIG, OUTPUT);
  pinMode(ECHO, INPUT);
  pinMode(PIEZO, OUTPUT);
  turret.attach(SERVO_PIN);
  turret.write(angle);
  if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
    Serial.println("No OLED at 0x3C: check SDA and SCL");
    while (true) {
    }
  }
  display.clearDisplay();
  display.setTextColor(SSD1306_WHITE);
  display.setTextSize(1);
  ring();
  display.display();
  delay(300);
  Serial.println("Radar sweeping");
}

void loop() {
  turret.write(angle);
  delay(20);  // an SG90 turns 3 degrees in about 5 ms; 20 leaves it settled
  long cm = measureCm();
  int i = (angle - MIN_ANGLE) / STEP;
  uint8_t was = seen[i];
  seen[i] = (cm > 0 && cm <= RANGE_CM) ? cm : 0;
  if (cm > 0 && cm < CLOSE_CM) tone(PIEZO, 1500, 25);
  drawScreen(i, was, cm);

  Serial.print(angle);
  Serial.print(',');
  Serial.print(cm);
  Serial.println('.');

  angle += direction;
  if (angle >= MAX_ANGLE || angle <= MIN_ANGLE) direction = -direction;
}

Code walkthrough

The key functions of the sketch, and what each one does.

measureCm sketch.ino

Sends a 10 microsecond pulse on TRIG and times the echo with pulseIn, up to 30 ms. No echo returns 0. Otherwise the time divided by 58 gives centimeters.

long measureCm() {
  digitalWrite(TRIG, LOW);
  delayMicroseconds(2);
  digitalWrite(TRIG, HIGH);
  delayMicroseconds(10);
  digitalWrite(TRIG, LOW);
  // 30 ms is about 5 m and back: anything longer is nothing at all.
  unsigned long us = pulseIn(ECHO, HIGH, 30000UL);
  if (us == 0) return 0;
  // Sound covers 0.0343 cm a microsecond, there and back: 58 us a centimeter.
  return us / 58;
}

polar sketch.ino

Converts an angle and distance to screen coordinates. The radar's center is the bottom middle of the screen, and 50 cm is 60 pixels long. Sine and cosine give the x and y.

// Where a point `cm` away at `deg` lands on the screen. 0 degrees is the
// right-hand side, 90 straight up, 180 the left.
void polar(int deg, int cm, int &x, int &y) {
  float r = cm * (float)RADIUS / RANGE_CM;
  float a = deg * 0.0174533;
  x = CX + (int)(r * cos(a));
  y = CY - (int)(r * sin(a));
}

drawScreen sketch.ino

Erases the old sweep line and the old dot, then draws the new reading in the corner. It puts back the rings and every remembered echo, draws the new sweep line, and sends the result to the display.

// Change only what moved. The driver sends just the parts of the screen that
// changed, so rubbing out the old line and drawing the new one is far less
// to send than clearing the screen and drawing it all again.
void drawScreen(int i, uint8_t was, long cm) {
  int x, y;
  if (lineX >= 0) display.drawLine(CX, CY, lineX, lineY, SSD1306_BLACK);
  if (was != 0) {
    polar(MIN_ANGLE + i * STEP, was, x, y);
    display.fillCircle(x, y, 2, SSD1306_BLACK);
  }
  // the reading, top left, where the rings never reach
  display.fillRect(0, 0, 26, 17, SSD1306_BLACK);
  display.setCursor(0, 0);
  display.print(angle);
  display.print((char)247);  // the degree sign in the built-in font
  display.setCursor(0, 9);
  if (cm == 0 || cm > RANGE_CM) {
    display.print("none");
  } else {
    display.print(cm);
    display.print("cm");
  }
  // put back whatever the rubbing out took off the rings and the other echoes
  ring();
  for (int k = 0; k < STEPS; k++) {
    if (seen[k] == 0) continue;
    polar(MIN_ANGLE + k * STEP, seen[k], x, y);
    display.fillCircle(x, y, 2, SSD1306_WHITE);
  // ... 5 more lines in the editor

loop sketch.ino

Turns the servo, waits, measures, and stores the reading for that step in seen. It pings if the object is under CLOSE_CM, draws, prints, and moves to the next angle. At either end of the sweep it reverses direction.

void loop() {
  turret.write(angle);
  delay(20);  // an SG90 turns 3 degrees in about 5 ms; 20 leaves it settled
  long cm = measureCm();
  int i = (angle - MIN_ANGLE) / STEP;
  uint8_t was = seen[i];
  seen[i] = (cm > 0 && cm <= RANGE_CM) ? cm : 0;
  if (cm > 0 && cm < CLOSE_CM) tone(PIEZO, 1500, 25);
  drawScreen(i, was, cm);

  Serial.print(angle);
  Serial.print(',');
  Serial.print(cm);
  Serial.println('.');

  angle += direction;
  if (angle >= MAX_ANGLE || angle <= MIN_ANGLE) direction = -direction;
}

Troubleshooting

The usual mistakes with this circuit, and the fix for each.

  • The sketch stops and prints No OLED at 0x3C This project
    Check SDA on A4 and SCL on A5, and the display's power and ground.
  • Dots show up on the wrong side This project
    0 degrees is the right-hand side, 90 is straight up and 180 is the left. Check how the sensor is mounted on the servo horn.
  • The servo resets the board or jitters This project
    Give the servo steady 5 V and join its ground to the Uno's ground.
  • Distance reads zero, or never changes. حساس المسافة بالموجات فوق الصوتية
    Power it from 5 V and share ground. Send a 10 microsecond HIGH pulse on TRIG, then time ECHO with pulseIn. Check TRIG and ECHO are not swapped.
  • Readings jump around or are wrong on a 3.3 V board. حساس المسافة بالموجات فوق الصوتية
    ECHO outputs 5 V, so divide it down for 3.3 V boards. Aim عند flat، hard surfaces. The range is about 2 cm to 4 m, and soft or angled targets bounce the sound away.
  • The servo jitters, twitches or resets the board when it moves. محرك سيرفو
    Power it from its own 5 V supply, not the Arduino 5 V pin, and join the grounds. A hobby servo can pull several hundred milliamps. Add a 100 uF to 470 uF capacitor across its supply.
  • The servo hums at the end of its travel or will not reach full angle. محرك سيرفو
    Stay inside the pulse range, about 1000 to 2000 us for most hobby servos. Pushing past the end stop makes it strain. Calibrate the min and max pulse for your servo.
  • A passive piezo stays silent when you just set the pin HIGH. سماعة بيزو
    A passive piezo needs a changing signal. Use tone(pin, frequency) or a PWM wave. Around 2 to 4 kHz is loudest. A buzzer with a built in oscillator makes sound from plain DC instead.

غيّره واحتفظ به

افتحه في المحرر، وغيّر الدائرة أو الكود، واحتفظ بنسختك في حساب مجاني.