⚡ Electric Vehicle C Simulator

Path through the gap between cans (inside), then back to the target — outer can fixed at 100 cm

Parameters

Full distance from start to target (7–10 m per rules)
Find the discrete angle that minimizes clearance while staying above this minimum
When checked, find the discrete angle that minimizes clearance while staying above the minimum
Distance between front and rear axles
Angle scope makes with EV axis (positive = right of forward)
Angle wheels sight line makes with wheel direction (positive = right of wheel)
List of possible steering angles (\(\delta\)) from holes/pegs
When checked, show wheel sighting line angle, block location calculation, and block on simulation

Trajectory

Path
Outer can (fixed)
Target
Scope sighting block
Wheel sighting block & line

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.

Phase 1 — Ramp Up

Over ~2 s, ramp power from standstill to 40%.

Prevents wheel slip on launch; keeps encoder readings accurate.

Phase 2 — Targeting Loop

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.

Phase 3 — Precision Stop

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.

Pseudocode (3-phase)
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
                    
Arduino code (3-phase)

                    
Targeting Loop

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.

Pseudocode (constant speed)
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
                    
Arduino code (constant speed)

                    
Power + Time

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.

Pseudocode (simple)
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)
                    
Arduino code (simple)