Module 06: PWM Speed Control

Level: 🟢 Beginner Board: Arduino Uno Prerequisites: Module 05 Estimated time: 45–60 minutes Goal: Replace fixed-speed motor calls with PWM-controlled speed so the robot can accelerate, decelerate, and run at any speed between stop and full.


What You'll Learn

By the end of this module you will understand how PWM duty cycle controls effective motor voltage, why every DC motor has a deadband where it hums without turning, and how to write ramp functions that start and stop the robot smoothly without mechanical jerk.


Until now your robot has driven at full blast or stopped dead. Real motor control means commanding any speed in between. Smooth from start, smooth to stop. This module covers how PWM works inside the driver, why motors stall at low values, and how to write a proper ramp function.


6.1 How the H-bridge controls a motor

You've been sending commands to the L298N for two modules without really knowing what happens inside it. Understanding the circuit explains why specific problems occur and how to avoid them.

<svg viewBox="0 0 760 320" width="100%" xmlns="http://www.w3.org/2000/svg">
  <defs>
    <marker id="aFwd" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="5" markerHeight="5" orient="auto-start-reverse"><path d="M2 2L8 5L2 8" fill="none" stroke="#1a6b4a" stroke-width="2" stroke-linecap="round"/></marker>
    <marker id="aRev" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="5" markerHeight="5" orient="auto-start-reverse"><path d="M2 2L8 5L2 8" fill="none" stroke="#c8420a" stroke-width="2" stroke-linecap="round"/></marker>
  </defs>

  <!-- H-BRIDGE SCHEMATIC — forward state -->
  <!-- Title -->
  <text x="120" y="22" text-anchor="middle" font-family="IBM Plex Mono,monospace" font-size="11" fill="#a0c0f0">FORWARD</text>
  <text x="120" y="38" text-anchor="middle" font-family="Manrope,sans-serif" font-size="10" fill="#555">IN1=HIGH, IN2=LOW</text>
  <!-- Supply rails -->
  <line x1="60" y1="60" x2="180" y2="60" stroke="#c8420a" stroke-width="2"/>
  <line x1="60" y1="260" x2="180" y2="260" stroke="#444" stroke-width="2"/>
  <text x="50" y="64" text-anchor="end" font-family="IBM Plex Mono,monospace" font-size="9" fill="#c8420a">+V</text>
  <text x="50" y="264" text-anchor="end" font-family="IBM Plex Mono,monospace" font-size="9" fill="#888">GND</text>
  <!-- H structure vertical rails -->
  <line x1="80" y1="60" x2="80" y2="260" stroke="#555" stroke-width="1" stroke-dasharray="3 3"/>
  <line x1="160" y1="60" x2="160" y2="260" stroke="#555" stroke-width="1" stroke-dasharray="3 3"/>
  <!-- Switches — S1 CLOSED (top left) -->
  <rect x="68" y="68" width="24" height="24" rx="3" fill="#1a6b4a" stroke="#0e4030" stroke-width="1.5"/>
  <text x="80" y="83" text-anchor="middle" font-family="IBM Plex Mono,monospace" font-size="8" fill="white">S1</text>
  <text x="80" y="104" text-anchor="middle" font-family="Manrope,sans-serif" font-size="9" fill="#1a6b4a">CLOSED</text>
  <!-- S2 OPEN (bottom left) -->
  <rect x="68" y="208" width="24" height="24" rx="3" fill="#333" stroke="#555" stroke-width="1.5"/>
  <text x="80" y="223" text-anchor="middle" font-family="IBM Plex Mono,monospace" font-size="8" fill="#888">S2</text>
  <text x="80" y="244" text-anchor="middle" font-family="Manrope,sans-serif" font-size="9" fill="#555">OPEN</text>
  <!-- S3 OPEN (top right) -->
  <rect x="148" y="68" width="24" height="24" rx="3" fill="#333" stroke="#555" stroke-width="1.5"/>
  <text x="160" y="83" text-anchor="middle" font-family="IBM Plex Mono,monospace" font-size="8" fill="#888">S3</text>
  <text x="160" y="104" text-anchor="middle" font-family="Manrope,sans-serif" font-size="9" fill="#555">OPEN</text>
  <!-- S4 CLOSED (bottom right) -->
  <rect x="148" y="208" width="24" height="24" rx="3" fill="#1a6b4a" stroke="#0e4030" stroke-width="1.5"/>
  <text x="160" y="223" text-anchor="middle" font-family="IBM Plex Mono,monospace" font-size="8" fill="white">S4</text>
  <text x="160" y="244" text-anchor="middle" font-family="Manrope,sans-serif" font-size="9" fill="#1a6b4a">CLOSED</text>
  <!-- Motor center -->
  <rect x="100" y="140" width="40" height="28" rx="4" fill="#2a4a80" stroke="#4090e0" stroke-width="1.5"/>
  <text x="120" y="158" text-anchor="middle" font-family="IBM Plex Mono,monospace" font-size="9" fill="#a0c0f0">M</text>
  <!-- Current path (forward) -->
  <path d="M80 92 L80 140 L100 154" fill="none" stroke="#1a6b4a" stroke-width="2" marker-end="url(#aFwd)"/>
  <path d="M140 154 L160 154 L160 232" fill="none" stroke="#1a6b4a" stroke-width="2" marker-end="url(#aFwd)"/>

  <!-- REVERSE state -->
  <text x="360" y="22" text-anchor="middle" font-family="IBM Plex Mono,monospace" font-size="11" fill="#f09090">REVERSE</text>
  <text x="360" y="38" text-anchor="middle" font-family="Manrope,sans-serif" font-size="10" fill="#555">IN1=LOW, IN2=HIGH</text>
  <line x1="300" y1="60" x2="420" y2="60" stroke="#c8420a" stroke-width="2"/>
  <line x1="300" y1="260" x2="420" y2="260" stroke="#444" stroke-width="2"/>
  <line x1="320" y1="60" x2="320" y2="260" stroke="#555" stroke-width="1" stroke-dasharray="3 3"/>
  <line x1="400" y1="60" x2="400" y2="260" stroke="#555" stroke-width="1" stroke-dasharray="3 3"/>
  <!-- S1 OPEN -->
  <rect x="308" y="68" width="24" height="24" rx="3" fill="#333" stroke="#555" stroke-width="1.5"/>
  <text x="320" y="83" text-anchor="middle" font-family="IBM Plex Mono,monospace" font-size="8" fill="#888">S1</text>
  <!-- S2 CLOSED -->
  <rect x="308" y="208" width="24" height="24" rx="3" fill="#c8420a" stroke="#7a2000" stroke-width="1.5"/>
  <text x="320" y="223" text-anchor="middle" font-family="IBM Plex Mono,monospace" font-size="8" fill="white">S2</text>
  <text x="320" y="244" text-anchor="middle" font-family="Manrope,sans-serif" font-size="9" fill="#c8420a">CLOSED</text>
  <!-- S3 CLOSED -->
  <rect x="388" y="68" width="24" height="24" rx="3" fill="#c8420a" stroke="#7a2000" stroke-width="1.5"/>
  <text x="400" y="83" text-anchor="middle" font-family="IBM Plex Mono,monospace" font-size="8" fill="white">S3</text>
  <text x="400" y="104" text-anchor="middle" font-family="Manrope,sans-serif" font-size="9" fill="#c8420a">CLOSED</text>
  <!-- S4 OPEN -->
  <rect x="388" y="208" width="24" height="24" rx="3" fill="#333" stroke="#555" stroke-width="1.5"/>
  <text x="400" y="223" text-anchor="middle" font-family="IBM Plex Mono,monospace" font-size="8" fill="#888">S4</text>
  <!-- Motor -->
  <rect x="340" y="140" width="40" height="28" rx="4" fill="#2a4a80" stroke="#4090e0" stroke-width="1.5"/>
  <text x="360" y="158" text-anchor="middle" font-family="IBM Plex Mono,monospace" font-size="9" fill="#a0c0f0">M</text>
  <!-- Current path (reverse — opposite direction) -->
  <path d="M400 92 L400 140 L380 154" fill="none" stroke="#c8420a" stroke-width="2" marker-end="url(#aRev)"/>
  <path d="M340 154 L320 154 L320 232" fill="none" stroke="#c8420a" stroke-width="2" marker-end="url(#aRev)"/>

  <!-- COAST/STOP state -->
  <text x="600" y="22" text-anchor="middle" font-family="IBM Plex Mono,monospace" font-size="11" fill="#888">COAST STOP</text>
  <text x="600" y="38" text-anchor="middle" font-family="Manrope,sans-serif" font-size="10" fill="#555">ENA=0 (PWM off)</text>
  <line x1="540" y1="60" x2="660" y2="60" stroke="#c8420a" stroke-width="2"/>
  <line x1="540" y1="260" x2="660" y2="260" stroke="#444" stroke-width="2"/>
  <line x1="560" y1="60" x2="560" y2="260" stroke="#555" stroke-width="1" stroke-dasharray="3 3"/>
  <line x1="640" y1="60" x2="640" y2="260" stroke="#555" stroke-width="1" stroke-dasharray="3 3"/>
  <rect x="548" y="68" width="24" height="24" rx="3" fill="#333" stroke="#555" stroke-width="1"/>
  <text x="560" y="83" text-anchor="middle" font-family="IBM Plex Mono,monospace" font-size="8" fill="#555">S1</text>
  <rect x="548" y="208" width="24" height="24" rx="3" fill="#333" stroke="#555" stroke-width="1"/>
  <text x="560" y="223" text-anchor="middle" font-family="IBM Plex Mono,monospace" font-size="8" fill="#555">S2</text>
  <rect x="628" y="68" width="24" height="24" rx="3" fill="#333" stroke="#555" stroke-width="1"/>
  <text x="640" y="83" text-anchor="middle" font-family="IBM Plex Mono,monospace" font-size="8" fill="#555">S3</text>
  <rect x="628" y="208" width="24" height="24" rx="3" fill="#333" stroke="#555" stroke-width="1"/>
  <text x="640" y="223" text-anchor="middle" font-family="IBM Plex Mono,monospace" font-size="8" fill="#555">S4</text>
  <rect x="580" y="140" width="40" height="28" rx="4" fill="#2a2a24" stroke="#444" stroke-width="1.5"/>
  <text x="600" y="158" text-anchor="middle" font-family="IBM Plex Mono,monospace" font-size="9" fill="#555">M</text>
  <text x="600" y="290" text-anchor="middle" font-family="Manrope,sans-serif" font-size="10" fill="#555">all switches open</text>
  <text x="600" y="306" text-anchor="middle" font-family="Manrope,sans-serif" font-size="10" fill="#555">motor spins freely (coasts)</text>
</svg>

Fig 6.1: H-bridge operating states. Forward: S1 and S4 closed, current flows left-to-right through motor. Reverse: S2 and S3 closed, current flows right-to-left. Coast stop: all switches open, motor spins freely until friction stops it. Hard brake (not shown): both low-side switches closed simultaneously, shorting motor terminals to GND and creating opposing current that stops quickly.

The important thing to notice: when you call analogWrite(ENA, 0), you are not short-circuiting the motor; you are opening all switches. The motor coasts to a stop under its own friction. This is fine for normal stopping. If you need a hard, immediate stop (for example, when the robot detects a wall centimeters away), you need the brake state: set both IN1 and IN2 HIGH simultaneously. This connects both motor terminals to the same rail and creates a braking force through back-EMF.

Instructor note: Thermal note. The L298N chip body will get warm during sustained operation; this is expected. During pivot turns, both motors load the driver simultaneously and the chip can reach 60–70°C. If it is too hot to hold for two seconds, reduce CRUISE speed or add rest intervals between maneuvers. Mounting the module with the chip body facing upward and clear of other components allows adequate heat dissipation.

6.2 Why motors stall at low PWM values

A DC motor needs a minimum pulse width to overcome its own internal friction and the gearbox's static friction. Below that threshold, analogWrite() sends pulses that are too short for the armature to build up enough magnetic force to rotate. The motor hums, heats up, and does not move. This is called the deadband: the range of PWM values that command motion but produce none.

<svg viewBox="0 0 760 280" width="100%" xmlns="http://www.w3.org/2000/svg">
  <!-- axes -->
  <line x1="80" y1="240" x2="700" y2="240" stroke="#1a1a18" stroke-width="1.5"/>
  <line x1="80" y1="40" x2="80" y2="245" stroke="#1a1a18" stroke-width="1.5"/>
  <!-- axis labels -->
  <text x="390" y="265" text-anchor="middle" font-family="IBM Plex Mono,monospace" font-size="11" fill="#6b6a65">analogWrite value (0–255)</text>
  <text x="30" y="150" text-anchor="middle" font-family="IBM Plex Mono,monospace" font-size="11" fill="#6b6a65" transform="rotate(-90,30,150)">actual RPM</text>
  <!-- X tick marks -->
  <line x1="80" y1="240" x2="80" y2="248" stroke="#1a1a18" stroke-width="1"/><text x="80" y="260" text-anchor="middle" font-family="IBM Plex Mono,monospace" font-size="9" fill="#6b6a65">0</text>
  <line x1="200" y1="240" x2="200" y2="248" stroke="#1a1a18" stroke-width="1"/><text x="200" y="260" text-anchor="middle" font-family="IBM Plex Mono,monospace" font-size="9" fill="#c8420a">~60</text>
  <line x1="330" y1="240" x2="330" y2="248" stroke="#1a1a18" stroke-width="1"/><text x="330" y="260" text-anchor="middle" font-family="IBM Plex Mono,monospace" font-size="9" fill="#6b6a65">128</text>
  <line x1="700" y1="240" x2="700" y2="248" stroke="#1a1a18" stroke-width="1"/><text x="700" y="260" text-anchor="middle" font-family="IBM Plex Mono,monospace" font-size="9" fill="#6b6a65">255</text>
  <!-- Y ticks -->
  <line x1="72" y1="240" x2="80" y2="240" stroke="#1a1a18" stroke-width="1"/><text x="68" y="244" text-anchor="end" font-family="IBM Plex Mono,monospace" font-size="9" fill="#6b6a65">0</text>
  <line x1="72" y1="140" x2="80" y2="140" stroke="#1a1a18" stroke-width="1"/><text x="68" y="144" text-anchor="end" font-family="IBM Plex Mono,monospace" font-size="9" fill="#6b6a65">50%</text>
  <line x1="72" y1="50" x2="80" y2="50" stroke="#1a1a18" stroke-width="1"/><text x="68" y="54" text-anchor="end" font-family="IBM Plex Mono,monospace" font-size="9" fill="#6b6a65">max</text>

  <!-- Deadband zone shading -->
  <rect x="80" y="40" width="120" height="200" fill="#fdf0e8" opacity=".6"/>
  <text x="140" y="65" text-anchor="middle" font-family="IBM Plex Mono,monospace" font-size="10" fill="#c8420a">DEADBAND</text>
  <text x="140" y="82" text-anchor="middle" font-family="Manrope,sans-serif" font-size="10" fill="#c8420a">motor hums,</text>
  <text x="140" y="96" text-anchor="middle" font-family="Manrope,sans-serif" font-size="10" fill="#c8420a">doesn't move</text>
  <line x1="200" y1="40" x2="200" y2="240" stroke="#c8420a" stroke-width="1" stroke-dasharray="4 3"/>

  <!-- Speed response curve -->
  <path d="M80 240 L200 240 Q220 238 240 220 Q310 170 400 140 Q520 100 700 54" fill="none" stroke="#1f4d8c" stroke-width="2.5"/>
  <text x="420" y="128" font-family="Manrope,sans-serif" font-size="11" fill="#1f4d8c">actual speed response</text>

  <!-- Ideal linear dashed -->
  <path d="M80 240 L700 54" fill="none" stroke="#6b6a65" stroke-width="1" stroke-dasharray="6 4"/>
  <text x="500" y="188" font-family="Manrope,sans-serif" font-size="10" fill="#6b6a65">ideal (linear)</text>

  <!-- Minimum usable PWM annotation -->
  <line x1="200" y1="220" x2="200" y2="240" stroke="#c8420a" stroke-width="2"/>
  <text x="200" y="210" text-anchor="middle" font-family="IBM Plex Mono,monospace" font-size="10" fill="#c8420a">MIN_SPEED</text>
  <text x="200" y="224" text-anchor="middle" font-family="Manrope,sans-serif" font-size="10" fill="#c8420a">~60–80 typical</text>
</svg>

Fig 6.2: DC motor speed vs PWM value. The deadband region (0 to ~60–80) produces no movement because the pulse is too short to overcome static friction. Above that threshold the motor starts, but the response is nonlinear at first before becoming roughly linear in the upper range. The minimum usable value varies by motor, load, and battery voltage; measure yours.

Finding your motor's minimum usable PWM value is a one-minute test. Write a sketch that slowly increments analogWrite from 0 to 255 in steps of 5, pausing 200ms between steps. Watch and listen. The value at which the wheels first begin to rotate is your MIN_SPEED constant. Use it as the floor in any speed command; never pass a value below it when you intend the robot to move.

Note: Your MIN_SPEED value is specific to your surface and load. On carpet or a slight incline the effective deadband is higher; re-check if the robot stalls in a new environment.

Why do the motors whine? The Arduino's PWM frequency on pins D5 and D6 is 976 Hz (set by Timer0). That is within human hearing range, so at low duty cycles you will hear a high-pitched whine from the motors. This is normal. The sound fades as the duty cycle increases and disappears at full speed. If it bothers you during testing, it is a useful audible indicator that the motors are receiving a signal even when they are in the deadband and not yet turning.

6.3 Acceleration ramps: smooth start and stop

Jumping from 0 to 180 in a single analogWrite() call causes the robot to lurch. The current spike can reset the Arduino if the battery wiring is marginal. The sudden torque stresses the gearbox. A ramp function solves all three by increasing speed gradually over a short window.

A 200ms ramp from zero to full speed is imperceptible to a person watching. The difference in feel between a ramp start and a direct write is the difference between a tool and a toy.

<svg viewBox="0 0 760 250" width="100%" xmlns="http://www.w3.org/2000/svg">
  <!-- axes -->
  <line x1="60" y1="200" x2="720" y2="200" stroke="#1a1a18" stroke-width="1.5"/>
  <line x1="60" y1="30" x2="60" y2="205" stroke="#1a1a18" stroke-width="1.5"/>
  <text x="390" y="224" text-anchor="middle" font-family="IBM Plex Mono,monospace" font-size="11" fill="#6b6a65">time →</text>
  <text x="20" y="120" text-anchor="middle" font-family="IBM Plex Mono,monospace" font-size="11" fill="#6b6a65" transform="rotate(-90,20,120)">speed</text>

  <!-- BAD: instant jump -->
  <polyline points="70,200 70,60 280,60 280,200" fill="none" stroke="#c8420a" stroke-width="2" stroke-dasharray="6 3"/>
  <text x="170" y="52" text-anchor="middle" font-family="IBM Plex Mono,monospace" font-size="10" fill="#c8420a">direct write</text>
  <text x="170" y="66" text-anchor="middle" font-family="Manrope,sans-serif" font-size="10" fill="#c8420a">lurch, current spike</text>
  <!-- Good: ramp up and ramp down -->
  <polyline points="370,200 400,170 420,140 440,100 460,65 480,60 580,60 610,70 630,100 650,140 670,170 690,200" fill="none" stroke="#1a6b4a" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
  <text x="530" y="48" text-anchor="middle" font-family="IBM Plex Mono,monospace" font-size="10" fill="#1a6b4a">ramp up + ramp down</text>
  <text x="530" y="62" text-anchor="middle" font-family="Manrope,sans-serif" font-size="10" fill="#1a6b4a">smooth, no jerk</text>
  <!-- Annotations -->
  <!-- Ramp up bracket -->
  <line x1="370" y1="210" x2="480" y2="210" stroke="#1a6b4a" stroke-width="1"/>
  <line x1="370" y1="205" x2="370" y2="215" stroke="#1a6b4a" stroke-width="1"/>
  <line x1="480" y1="205" x2="480" y2="215" stroke="#1a6b4a" stroke-width="1"/>
  <text x="425" y="228" text-anchor="middle" font-family="IBM Plex Mono,monospace" font-size="10" fill="#1a6b4a">ramp ~150ms</text>
  <!-- Cruise -->
  <line x1="480" y1="210" x2="580" y2="210" stroke="#6b6a65" stroke-width="1"/>
  <text x="530" y="228" text-anchor="middle" font-family="IBM Plex Mono,monospace" font-size="10" fill="#6b6a65">cruise</text>
  <!-- Ramp down -->
  <line x1="580" y1="210" x2="690" y2="210" stroke="#c8420a" stroke-width="1"/>
  <line x1="580" y1="205" x2="580" y2="215" stroke="#c8420a" stroke-width="1"/>
  <line x1="690" y1="205" x2="690" y2="215" stroke="#c8420a" stroke-width="1"/>
  <text x="635" y="228" text-anchor="middle" font-family="IBM Plex Mono,monospace" font-size="10" fill="#c8420a">ramp ~150ms</text>
</svg>

Fig 6.3: Direct write vs. ramp profile. The direct write (left, dashed) jumps instantly to full speed, causing a current spike and mechanical jerk. The ramp profile (right) spreads the acceleration over ~150ms, which is enough to eliminate jerk while adding negligible time to the overall maneuver. Use the same approach for stopping: ramp down before calling stopMotors().

// Ramp up: from MIN_SPEED to targetSpeed over rampTime milliseconds
void rampUp(int targetSpeed, int rampTime) {
  int steps = targetSpeed - MIN_SPEED;
  if (steps <= 0) {                     // guard: target already at or below MIN_SPEED
    analogWrite(ENA, targetSpeed);
    analogWrite(ENB, targetSpeed - RTRIM);
    return;
  }
  int stepDelay = rampTime / steps;
  for (int spd = MIN_SPEED; spd <= targetSpeed; spd++) {
    analogWrite(ENA, spd);
    analogWrite(ENB, spd - RTRIM);
    delay(stepDelay);
  }
}

void rampDown(int fromSpeed, int rampTime) {
  int steps = fromSpeed - MIN_SPEED;
  if (steps <= 0) {                     // guard: already at or below MIN_SPEED
    stopMotors();
    return;
  }
  int stepDelay = rampTime / steps;
  for (int spd = fromSpeed; spd >= MIN_SPEED; spd--) {
    analogWrite(ENA, spd);
    analogWrite(ENB, spd - RTRIM);
    delay(stepDelay);
  }
  stopMotors();
}

// Usage:
const int MIN_SPEED = 65;   // measure yours
const int CRUISE    = 180;
const int RTRIM     = 9;    // from Module 5 calibration

// Forward motion with smooth ramp:
setDirectionForward();
rampUp(CRUISE, 150);
delay(1200);  // cruise
rampDown(CRUISE, 150);

Advanced path: oscilloscope reading and duty cycle. If you have access to an oscilloscope, connect a probe to the ENA line and ground to GND while the motor runs. You will see a square wave. The period is about 2ms (490Hz). At analogWrite(180), the HIGH portion is 180/255 = 70.6% of the period, or roughly 1.4ms. The LOW portion is 0.6ms. The motor's inductance averages this to approximately 0.706 × Vsupply. At 6V battery, the motor sees about 4.2V effective. This is exactly the voltage you would need to apply DC to run the motor at 70% speed; PWM is just a time-averaged approximation of that DC level. Duty cycle calculation: duty% = (analogWrite value / 255) × 100. At 255, duty = 100% (always HIGH, equivalent to direct connection). At 0, duty = 0% (always LOW, no power).

Lab 06: Smooth ramp-up and ramp-down

Measure your motor's MIN_SPEED, then write a sketch that drives the robot forward using rampUp() and rampDown() instead of direct analogWrite() calls. The robot should start without jerking, cruise, and stop without sliding.

  1. Write a short calibration sketch: in loop(), increment a speed variable from 0 to 255 in steps of 1, call analogWrite(ENA, speed) and analogWrite(ENB, speed), and delay 30ms between each step. Watch the wheels. Note the value at which rotation begins reliably. That is your MIN_SPEED.
  2. Add MIN_SPEED to your constants at the top of your main sketch.
  3. Implement rampUp() and rampDown() as shown in the code block above.
  4. Write a setDirectionForward() helper function that sets IN1–IN4 without touching the enable pins. This keeps direction logic separate from speed logic.
  5. In loop(), call setDirectionForward(), then rampUp(180, 150), then delay(1500) for the cruise, then rampDown(180, 150). Follow with stopMotors() and a long delay before repeating.
  6. Upload and observe. The start should have no lurch. The stop should be progressive, not a hard cut. If you see jerk at start, your MIN_SPEED is set too low; increase it by 10 and retry.
  7. Extend: add a ramp into a pivot turn. rampDown to MIN_SPEED, execute the turn at low speed, rampUp again. Note how the robot's behavior changes compared to snapping from cruise to full pivot.

Module milestone: The robot starts, cruises, and stops in a straight line with no visible jerk at either transition. MIN_SPEED and RTRIM are documented as constants. Ready for Module 7.


Self-Check: Module 06

Key Terms Glossary

Term Definition
duty cycle The percentage of time a PWM signal is HIGH within one period. analogWrite(255) = 100% duty. analogWrite(128) = 50% duty. Determines effective motor voltage.
deadband The range of PWM values that send a signal to the motor but produce no rotation because the pulse is too brief to overcome static friction. Typically 0–60 for a small DC gear motor.
hard brake Stopping method that sets both low-side H-bridge switches closed, shorting motor terminals to GND. Creates a braking current through back-EMF. Stops faster than coast but stresses the driver.
back-EMF The voltage a spinning motor generates when its power is cut. Acts as a generator opposing continued rotation. Used in hard braking and detected in some encoder-free speed estimation methods.
ramp function A speed profile that gradually increases or decreases PWM value over time rather than jumping instantly. Eliminates mechanical jerk and reduces current spikes at startup.
MIN_SPEED The lowest PWM value at which the motor reliably starts turning under normal load. Specific to each motor and should be measured, not assumed. Used as the starting point for ramp functions.

Previous: ← Module 05: Differential Steering & Turning · Next: Module 07: Remote Control via Bluetooth →

Related Blog Posts