⚡ Electric Vehicle C Simulator
Path through the gap between cans (inside), then back to the target — outer can fixed at 100 cm
Parameters
Trajectory
Formulas
Pass point (path goes through the gap, inside the cans):
\[ y_{\mathrm{pass}} = 100 - m \]
where \(m\) = clearance from outer can edge (cm)
Circle center (vertical layout: \(c_y\) = center along track, \(c_x\) = center perpendicular):
\[ c_y = D/2 \quad \text{(vertical)}, \qquad c_x = \frac{y_{\mathrm{pass}}^2 - D^2/4}{2\,y_{\mathrm{pass}}} \quad \text{(horizontal)} \]
Turning radius:
\[ R = \sqrt{c_y^2 + c_x^2} \]
Initial heading angle \(\theta_0\) (point left at start):
\[ \theta_0 = \arctan\left(\frac{c_y}{-c_x}\right) \]
Wheel steering angle \(\delta\):
\[ \tan\delta = \frac{L}{R} \quad \Rightarrow \quad \delta = \arctan\left(\frac{L}{R}\right) \]
where \(L\) = wheelbase (cm)
Total path distance (arc length):
\[ s = R \cdot |\phi_{\mathrm{target}} - \phi_{\mathrm{start}}| \]
where \(\phi\) = angle from circle center to point (radians)
Lateral offset (place sighting block at target line, scope at middle):
\[ \text{Lateral offset} = D \cdot \tan(\theta_0 - \gamma) + \frac{L}{2}\left(\cos\theta_0 \cdot \tan(\theta_0 - \gamma) - \sin\theta_0\right) \]
where \(\gamma\) = scope angle (positive = right of forward), \(L\) = wheelbase. Scope assumed at middle of robot (\(L/2\) behind MP).
Motor Control Strategy
Use the Total Path Distance from the Trajectory tab as the target distance for encoder-based control.
Over ~2 s, ramp power from standstill to 40%.
Prevents wheel slip on launch; keeps encoder readings accurate.
Goal: Reach 1 m left with 4 s remaining.
In a loop: measure current speed and distance; adjust power to hit that point at exactly the right time.
Target speed ≈ 25 cm/s. Compensates for battery sag and friction.
From Phase 2 (without stopping), reduce power and creep slowly until robot is 2 cm from target distance, then cut power.
Account for drift/inertia; measure braking distance and offset stop trigger if needed.
START_RUN:
startTime = now()
rampStartTime = now()
currentPower = 0
PHASE_1_RAMP_UP: // ~2 s, 0→40%
WHILE rampElapsed < 2.0:
currentPower = 40 × (rampElapsed / 2.0)
drive(currentPower)
currentPower = 40
lastSpeedTime = now()
lastCounter = encoderCount
PHASE_2_TARGETING_LOOP: // Goal: 1 m left with 4 s remaining
WHILE distanceRemaining > 100:
distanceTraveled = encoderToCm(encoderCount)
distanceRemaining = arcLength - distanceTraveled
elapsed = now() - startTime
timeLeft = targetTime - elapsed
neededSpeed = distanceRemaining / timeLeft
neededSpeed = clamp(neededSpeed, 5, 150)
every 100 ms:
currentSpeed = (Δencoder / pulsesPerRev) × wheelCircumfrence / Δt
speedError = neededSpeed - currentSpeed
IF speedError > 5: currentPower += 2
ELSE IF speedError < -5: currentPower -= 2
currentPower = clamp(currentPower, 25, 60)
drive(currentPower)
PHASE_3_PRECISION_STOP: // Creep until 2 cm from target
drive(8) // slow creep power
WHILE distanceRemaining > 2:
drive(8)
drive(0) // STOP
DONE: display results
Goal: Reach 6 cm before the end with 0.3 s remaining.
Single phase: no ramp up or ramp down. A loop measures speed and adjusts power proportionally to the speed error (power change = Kp × error). Stops at 6 cm remaining.
Proportional control for precise speed tracking. Kp = 0.5, max change ±6% per 100 ms.
START_RUN:
startTime = now()
lastSpeedTime = now()
lastCounter = encoderCount
currentPower = 40 // initial guess
TARGETING_LOOP: // Goal: 6 cm left with 0.3 s remaining
WHILE distanceRemaining > 6:
distanceTraveled = encoderToCm(encoderCount)
distanceRemaining = arcLength - distanceTraveled
elapsed = now() - startTime
timeLeft = targetTime - elapsed
// Target the (6 cm, 0.3 s) point
distToTarget = distanceRemaining - 6
timeToTarget = timeLeft - 0.3
timeToTarget = max(timeToTarget, 0.05) // avoid div by zero
neededSpeed = distToTarget / timeToTarget
neededSpeed = clamp(neededSpeed, 5, 150)
every 100 ms:
currentSpeed = (Δencoder / pulsesPerRev) × wheelCircumfrence / Δt
speedError = neededSpeed - currentSpeed
powerChange = Kp × speedError // Kp = 0.5, proportional
powerChange = clamp(powerChange, -6, 6) // cap per cycle
currentPower += powerChange
currentPower = clamp(currentPower, 25, 75)
drive(currentPower)
drive(0) // STOP (reached 6 cm)
DONE: display results
Set motor power (0–100%) and run duration (seconds). Press start — motor runs at that power for that time, then stops.
No encoders, no ramps, no feedback. Good for quick tests and calibration.
SETUP_PHASE:
runPower = user_selected_power // 0–100 %
runTimeSec = user_selected_time // seconds
(via dial + Set button)
START_RUN:
runStartTime = now()
drive(runPower)
RUN_LOOP:
WHILE elapsed < runTimeSec:
elapsed = (now() - runStartTime) / 1000
drive(runPower) // hold constant power
drive(0) // STOP
DONE:
display elapsed time
(no encoder, no feedback, no ramps)