חזרה לשיעור 3Back to Lesson 3

שיעור 3 — קבצי הקודLesson 3 — code files

פותחים כל קובץ בתיקייה הנושאת את אותו שם ב־Arduino IDE. בוחרים Uno ופורט מתאים. אין צורך בספריות חיצוניות.Open each file inside its same-named folder in Arduino IDE. Select Uno and the appropriate port. No external libraries are required.

מעלים כשהזנת המנועים כבויה, מאמתים חיווט ודירוגים, ואז מפעילים עם הגלגלים באוויר. בהפעלה ראשונה מכיילים את LEFT_REVERSED ו־RIGHT_REVERSED.Upload with motor power off, verify wiring and ratings, then test with wheels raised. Calibrate LEFT_REVERSED and RIGHT_REVERSED on first use.

הורדת כל חמשת הסקצ׳ים — ZIPDownload all five sketches — ZIP

שלב 1 — מנוע אחדLevel 1 — one motor

ניסוי חד־פעמי בערוץ A: כיוון אחד, גלישה, כיוון הפוך. המתינו לעצירה לפני היפוך.One-shot channel A test: one direction, coast, reverse. Wait for the wheel to stop before reversal.

level1_one_motor.ino ↓

הצגת הקוד והעתקהView and copy code
// Arduino Uno + L298N. ENA/ENB jumper caps removed.
// Disconnect motor power while wiring/uploading; begin with wheels raised.
// Verify motor/module ratings and module-specific logic-supply wiring.
const byte ENA = 5, IN1 = 2, IN2 = 3;
const byte ENB = 6, IN3 = 4, IN4 = 7;
// Calibrate each side with wheels raised. These depend on physical mounting.
const bool LEFT_REVERSED = false;
const bool RIGHT_REVERSED = false;

void coastAll() {
  digitalWrite(ENA, LOW);
  digitalWrite(ENB, LOW);
}

void beginMotors() {
  pinMode(ENA, OUTPUT); pinMode(ENB, OUTPUT);
  coastAll();
  pinMode(IN1, OUTPUT); pinMode(IN2, OUTPUT);
  pinMode(IN3, OUTPUT); pinMode(IN4, OUTPUT);
  digitalWrite(IN1, LOW); digitalWrite(IN2, LOW);
  digitalWrite(IN3, LOW); digitalWrite(IN4, LOW);
}

void setMotor(byte en, byte a, byte b, int command, bool reversed) {
  command = constrain(command, -255, 255);
  if (reversed) command = -command;
  digitalWrite(en, LOW); // disable before changing direction inputs
  digitalWrite(a, command > 0 ? HIGH : LOW);
  digitalWrite(b, command < 0 ? HIGH : LOW);
  if (command != 0) analogWrite(en, abs(command));
  // command == 0 means COAST. brakeAll() is a separate operation.
}

void drive(int left, int right) {
  setMotor(ENA, IN1, IN2, left, LEFT_REVERSED);
  setMotor(ENB, IN3, IN4, right, RIGHT_REVERSED);
}

void brakeAll() {
  coastAll();
  digitalWrite(IN1, LOW); digitalWrite(IN2, LOW);
  digitalWrite(IN3, LOW); digitalWrite(IN4, LOW);
  digitalWrite(ENA, HIGH); digitalWrite(ENB, HIGH);
  // L298: enabled + equal direction inputs = electrical brake.
  // Braking generates current; check load ratings. Not a holding brake.
}

void setup() {
  beginMotors();
  delay(3000); // switch on motor power; wheels must be raised
  setMotor(ENA, IN1, IN2, 160, LEFT_REVERSED);
  delay(1000);
  coastAll(); delay(3000); // verify wheel is stopped before reverse
  setMotor(ENA, IN1, IN2, -160, LEFT_REVERSED);
  delay(1000);
  coastAll();
}
void loop() {} // reset to repeat; do not hold the shaft stalled

שלב 2 — נסיעה ופנייהLevel 2 — drive and turn

בדיקה חד־פעמית של שני הצדדים; כיילו קוטביות לפני הנחה על הרצפה.One-shot two-side test; calibrate polarity before floor testing.

level2_drive.ino ↓

הצגת הקוד והעתקהView and copy code
// Arduino Uno + L298N. ENA/ENB jumper caps removed.
// Disconnect motor power while wiring/uploading; begin with wheels raised.
// Verify motor/module ratings and module-specific logic-supply wiring.
const byte ENA = 5, IN1 = 2, IN2 = 3;
const byte ENB = 6, IN3 = 4, IN4 = 7;
// Calibrate each side with wheels raised. These depend on physical mounting.
const bool LEFT_REVERSED = false;
const bool RIGHT_REVERSED = false;

void coastAll() {
  digitalWrite(ENA, LOW);
  digitalWrite(ENB, LOW);
}

void beginMotors() {
  pinMode(ENA, OUTPUT); pinMode(ENB, OUTPUT);
  coastAll();
  pinMode(IN1, OUTPUT); pinMode(IN2, OUTPUT);
  pinMode(IN3, OUTPUT); pinMode(IN4, OUTPUT);
  digitalWrite(IN1, LOW); digitalWrite(IN2, LOW);
  digitalWrite(IN3, LOW); digitalWrite(IN4, LOW);
}

void setMotor(byte en, byte a, byte b, int command, bool reversed) {
  command = constrain(command, -255, 255);
  if (reversed) command = -command;
  digitalWrite(en, LOW); // disable before changing direction inputs
  digitalWrite(a, command > 0 ? HIGH : LOW);
  digitalWrite(b, command < 0 ? HIGH : LOW);
  if (command != 0) analogWrite(en, abs(command));
  // command == 0 means COAST. brakeAll() is a separate operation.
}

void drive(int left, int right) {
  setMotor(ENA, IN1, IN2, left, LEFT_REVERSED);
  setMotor(ENB, IN3, IN4, right, RIGHT_REVERSED);
}

void brakeAll() {
  coastAll();
  digitalWrite(IN1, LOW); digitalWrite(IN2, LOW);
  digitalWrite(IN3, LOW); digitalWrite(IN4, LOW);
  digitalWrite(ENA, HIGH); digitalWrite(ENB, HIGH);
  // L298: enabled + equal direction inputs = electrical brake.
  // Braking generates current; check load ratings. Not a holding brake.
}

void setup() {
  beginMotors(); delay(3000);
  drive(160, 160); delay(800);
  brakeAll(); delay(1500);
  drive(0, 160); delay(400); // left coasts, right drives: turn left
  brakeAll(); delay(1500);
  coastAll();
}
void loop() {}

שלב 3 — חיישן מרחקLevel 3 — distance sensor

בדקו תחילה בלי הפעלת מנועים. Serial Monitor: 9600 baud. TRIG=D9, ECHO=D10.Test with motor power off first. Serial Monitor: 9600 baud. TRIG=D9, ECHO=D10.

level3_distance.ino ↓

הצגת הקוד והעתקהView and copy code
// Arduino Uno, HC-SR04: VCC=5V, GND=GND, TRIG=D9, ECHO=D10.
const byte TRIG = 9, ECHO = 10;
const unsigned long ECHO_TIMEOUT_US = 25000UL;

void beginSonar() {
  pinMode(TRIG, OUTPUT); digitalWrite(TRIG, LOW);
  pinMode(ECHO, INPUT);
}

float readDistanceCm() {
  digitalWrite(TRIG, LOW); delayMicroseconds(2);
  digitalWrite(TRIG, HIGH); delayMicroseconds(10);
  digitalWrite(TRIG, LOW);
  unsigned long us = pulseIn(ECHO, HIGH, ECHO_TIMEOUT_US);
  if (us == 0) return -1.0; // invalid, NOT a clear path
  float cm = us / 58.0;
  if (cm < 2.0 || cm > 400.0) return -1.0;
  return cm;
}

void printDistance(float cm) {
  if (cm < 0) Serial.println(F("INVALID: no reliable echo"));
  else { Serial.print(cm); Serial.println(F(" cm")); }
}

void setup() { Serial.begin(9600); beginSonar(); }
void loop() {
  printDistance(readDistanceCm());
  delay(70); // >60 ms between measurement starts
}

שלב 4 — עצירה מול מכשולLevel 4 — obstacle stop

בלימה במדידה לא תקינה או עד 20 ס״מ; חזרה לנסיעה מ־25 ס״מ. מהירות וספים דורשים כיול.Brake on invalid data or at 20 cm; resume at 25 cm. Speed and thresholds require calibration.

level4_obstacle_stop.ino ↓

הצגת הקוד והעתקהView and copy code
// Arduino Uno + L298N. ENA/ENB jumper caps removed.
// Disconnect motor power while wiring/uploading; begin with wheels raised.
// Verify motor/module ratings and module-specific logic-supply wiring.
const byte ENA = 5, IN1 = 2, IN2 = 3;
const byte ENB = 6, IN3 = 4, IN4 = 7;
// Calibrate each side with wheels raised. These depend on physical mounting.
const bool LEFT_REVERSED = false;
const bool RIGHT_REVERSED = false;

void coastAll() {
  digitalWrite(ENA, LOW);
  digitalWrite(ENB, LOW);
}

void beginMotors() {
  pinMode(ENA, OUTPUT); pinMode(ENB, OUTPUT);
  coastAll();
  pinMode(IN1, OUTPUT); pinMode(IN2, OUTPUT);
  pinMode(IN3, OUTPUT); pinMode(IN4, OUTPUT);
  digitalWrite(IN1, LOW); digitalWrite(IN2, LOW);
  digitalWrite(IN3, LOW); digitalWrite(IN4, LOW);
}

void setMotor(byte en, byte a, byte b, int command, bool reversed) {
  command = constrain(command, -255, 255);
  if (reversed) command = -command;
  digitalWrite(en, LOW); // disable before changing direction inputs
  digitalWrite(a, command > 0 ? HIGH : LOW);
  digitalWrite(b, command < 0 ? HIGH : LOW);
  if (command != 0) analogWrite(en, abs(command));
  // command == 0 means COAST. brakeAll() is a separate operation.
}

void drive(int left, int right) {
  setMotor(ENA, IN1, IN2, left, LEFT_REVERSED);
  setMotor(ENB, IN3, IN4, right, RIGHT_REVERSED);
}

void brakeAll() {
  coastAll();
  digitalWrite(IN1, LOW); digitalWrite(IN2, LOW);
  digitalWrite(IN3, LOW); digitalWrite(IN4, LOW);
  digitalWrite(ENA, HIGH); digitalWrite(ENB, HIGH);
  // L298: enabled + equal direction inputs = electrical brake.
  // Braking generates current; check load ratings. Not a holding brake.
}
const byte TRIG = 9, ECHO = 10;
const unsigned long ECHO_TIMEOUT_US = 25000UL;

void beginSonar() {
  pinMode(TRIG, OUTPUT); digitalWrite(TRIG, LOW);
  pinMode(ECHO, INPUT);
}

float readDistanceCm() {
  digitalWrite(TRIG, LOW); delayMicroseconds(2);
  digitalWrite(TRIG, HIGH); delayMicroseconds(10);
  digitalWrite(TRIG, LOW);
  unsigned long us = pulseIn(ECHO, HIGH, ECHO_TIMEOUT_US);
  if (us == 0) return -1.0; // invalid, NOT a clear path
  float cm = us / 58.0;
  if (cm < 2.0 || cm > 400.0) return -1.0;
  return cm;
}

void printDistance(float cm) {
  if (cm < 0) Serial.println(F("INVALID: no reliable echo"));
  else { Serial.print(cm); Serial.println(F(" cm")); }
}

const float STOP_CM = 20.0, RESUME_CM = 25.0;
const int CRUISE_PWM = 160;
bool moving = false;

void setup() {
  beginMotors(); beginSonar(); Serial.begin(9600);
  brakeAll(); delay(3000); // motor power on only after wiring is verified
}

void loop() {
  float distanceCm = readDistanceCm();
  printDistance(distanceCm);
  if (distanceCm < 0 || distanceCm <= STOP_CM) moving = false;
  else if (distanceCm >= RESUME_CM) moving = true;
  // Between thresholds, retain previous state (hysteresis).
  if (moving) drive(CRUISE_PWM, CRUISE_PWM);
  else brakeAll();
  delay(70);
}

תוספת — פנייה מתוזמנתExtension — timed turn

מכונת מצבים עם millis; ממשיכה לבדוק מרחק בזמן פנייה. אין חיישן צד או גב ואין הבטחה לפינוי מכשול.A millis state machine keeps sampling during turns. No side/rear sensing and no guaranteed obstacle clearance.

bonus_turn.ino ↓

הצגת הקוד והעתקהView and copy code
// Arduino Uno + L298N. ENA/ENB jumper caps removed.
// Disconnect motor power while wiring/uploading; begin with wheels raised.
// Verify motor/module ratings and module-specific logic-supply wiring.
const byte ENA = 5, IN1 = 2, IN2 = 3;
const byte ENB = 6, IN3 = 4, IN4 = 7;
// Calibrate each side with wheels raised. These depend on physical mounting.
const bool LEFT_REVERSED = false;
const bool RIGHT_REVERSED = false;

void coastAll() {
  digitalWrite(ENA, LOW);
  digitalWrite(ENB, LOW);
}

void beginMotors() {
  pinMode(ENA, OUTPUT); pinMode(ENB, OUTPUT);
  coastAll();
  pinMode(IN1, OUTPUT); pinMode(IN2, OUTPUT);
  pinMode(IN3, OUTPUT); pinMode(IN4, OUTPUT);
  digitalWrite(IN1, LOW); digitalWrite(IN2, LOW);
  digitalWrite(IN3, LOW); digitalWrite(IN4, LOW);
}

void setMotor(byte en, byte a, byte b, int command, bool reversed) {
  command = constrain(command, -255, 255);
  if (reversed) command = -command;
  digitalWrite(en, LOW); // disable before changing direction inputs
  digitalWrite(a, command > 0 ? HIGH : LOW);
  digitalWrite(b, command < 0 ? HIGH : LOW);
  if (command != 0) analogWrite(en, abs(command));
  // command == 0 means COAST. brakeAll() is a separate operation.
}

void drive(int left, int right) {
  setMotor(ENA, IN1, IN2, left, LEFT_REVERSED);
  setMotor(ENB, IN3, IN4, right, RIGHT_REVERSED);
}

void brakeAll() {
  coastAll();
  digitalWrite(IN1, LOW); digitalWrite(IN2, LOW);
  digitalWrite(IN3, LOW); digitalWrite(IN4, LOW);
  digitalWrite(ENA, HIGH); digitalWrite(ENB, HIGH);
  // L298: enabled + equal direction inputs = electrical brake.
  // Braking generates current; check load ratings. Not a holding brake.
}
const byte TRIG = 9, ECHO = 10;
const unsigned long ECHO_TIMEOUT_US = 25000UL;

void beginSonar() {
  pinMode(TRIG, OUTPUT); digitalWrite(TRIG, LOW);
  pinMode(ECHO, INPUT);
}

float readDistanceCm() {
  digitalWrite(TRIG, LOW); delayMicroseconds(2);
  digitalWrite(TRIG, HIGH); delayMicroseconds(10);
  digitalWrite(TRIG, LOW);
  unsigned long us = pulseIn(ECHO, HIGH, ECHO_TIMEOUT_US);
  if (us == 0) return -1.0; // invalid, NOT a clear path
  float cm = us / 58.0;
  if (cm < 2.0 || cm > 400.0) return -1.0;
  return cm;
}

void printDistance(float cm) {
  if (cm < 0) Serial.println(F("INVALID: no reliable echo"));
  else { Serial.print(cm); Serial.println(F(" cm")); }
}

const float STOP_CM = 20.0, RESUME_CM = 25.0, TOO_CLOSE_CM = 10.0;
const int CRUISE_PWM = 150, TURN_PWM = 150;
const unsigned long SAMPLE_MS = 70, BRAKE_MS = 300, TURN_MS = 400;
enum State { WAIT_CLEAR, FORWARD, BRAKING, TURNING, CHECKING };
void changeState(State next, unsigned long now);
State state = WAIT_CLEAR;
unsigned long stateSince = 0, lastSample = 0;
float distanceCm = -1.0;

void changeState(State next, unsigned long now) {
  state = next; stateSince = now;
}

void setup() {
  beginMotors(); beginSonar(); Serial.begin(9600);
  brakeAll(); delay(3000);
}

void loop() {
  unsigned long now = millis();
  bool fresh = false;
  if (now - lastSample >= SAMPLE_MS) {
    lastSample = now;
    distanceCm = readDistanceCm();
    printDistance(distanceCm);
    now = millis(); // pulseIn may have blocked for up to the timeout
    fresh = true;
    if (distanceCm < 0 || distanceCm <= TOO_CLOSE_CM) {
      changeState(WAIT_CLEAR, now); // require clear path to resume
    }
  }
  switch (state) {
    case WAIT_CLEAR:
      brakeAll();
      if (fresh && distanceCm >= RESUME_CM) changeState(FORWARD, now);
      break;
    case FORWARD:
      if (fresh && distanceCm <= STOP_CM) {
        brakeAll(); changeState(BRAKING, now);
      } else drive(CRUISE_PWM, CRUISE_PWM);
      break;
    case BRAKING:
      brakeAll();
      if (now - stateSince >= BRAKE_MS) changeState(TURNING, now);
      break;
    case TURNING:
      if (now - stateSince >= TURN_MS) {
        brakeAll(); changeState(CHECKING, now);
      } else drive(0, TURN_PWM); // no reverse; left coasts
      break;
    case CHECKING:
      brakeAll();
      if (fresh) {
        if (distanceCm >= RESUME_CM) changeState(FORWARD, now);
        else changeState(WAIT_CLEAR, now); // no repeated blind turns
      }
      break;
  }
}
V189