⚡ Electric Vehicle C Simulator

Trajectory tools and motor strategies for Electric Vehicle

Arc calibration

Lay out a centerline ending at a block and aim the riflescope along that line (+y, toward the block). Start the EV at (0, 0). Use each check-slot tab position for a fixed turn radius; vary run distance so the vehicle stops at different places. Record stop coordinates in cm — x = lateral (positive = left when facing +y), y = distance along the centerline forward from the start. Enter each trial below with its slot number.

Comma- or space-separated. Lines starting with # are ignored. Sheet: forward distance y, then slot (e.g. 8th), then lateral x — use a minus sign, or suffix L/R on the magnitude. Alternate: x, y, slot when the middle column is not a slot number.

Data & fitted arcs

Model

Coordinates. Origin at the start; +y along the riflescope / centerline toward the block; +x lateral (left when facing +y).

Shared heading. Let α be the angle from +y toward +x to the initial velocity direction (same for every slot). Initially \( \mathbf{\hat{t}} = (\sin\alpha,\,\cos\alpha) \) in (\(x\), \(y\)).

Circular arc through the origin. For a left (CCW) turn with radius R, the circle center is at \( (R\cos\alpha,\,-R\sin\alpha) \). Every stop satisfies:

\[ x^2 + y^2 = 2R\,(x\cos\alpha - y\sin\alpha) \]

For a right (CW) turn, use \(x^2 + y^2 = 2R\,(y\sin\alpha - x\cos\alpha)\).

Fitting. With α fixed, each slot’s radius is chosen by linear least squares so the above holds for all points in that slot. α is scanned on [−89°, 89°] and refined to minimize the sum of squared residuals of \(x^2+y^2 - 2R_k(\cdots)\).

Trajectory planner

Uses calibration α and per-slot R. For each slot, the fitted arc is rotated in-plane about the start until it passes through the target (0, D) on the centerline (+y). Centers use the branch with cx > 0 so the path bows toward +x. The can reference is fixed at (−100, D/2) cm. The table lists tangent ψ from +y, ψ − α, block lateral x = D tan(ψ − α) at y = D, and lateral clearance from the arc at y = D/2 to that can point.

Run Arc calibration and fit first, or enter α and R manually.
Angle from +y toward +x to initial heading (same α as calibration).
Target on centerline at (x, y) = (0, D).

Can marker on the plot: (−100, D/2) cm (fixed).

Slot radii R (cm)

Slot R

Sighting & headings

Slot Tangent ψ from +y (deg) ψ − α (deg) Block x = D tan(ψ − α) (cm) Mid clearance to can (cm)

Arcs through target

Trajectory geometry

An arc of radius R through (0,0) and (0,D) has center on y = D/2 with \(|cx|=\sqrt{R^2-(D/2)^2}\). We take cx positive so the bulge is toward +x (outer-can side).

Let \( \mathbf{c}_{\mathrm{cal}} \) be the calibrated center for that slot. Rotation \(\phi\) about the origin maps \(\mathbf{c}_{\mathrm{cal}}\) to \(\mathbf{c}_{\mathrm{tgt}}\) (same length \(R\)). The calibrated tangent \((\sin\alpha,\cos\alpha)\) rotates to \(\mathbf{t}'\); \(\psi=\mathrm{atan2}(t'_x,t'_y)\) from +\(y\). The sighting table uses \(\psi-\alpha\) and block lateral \(x = D\tan(\psi-\alpha)\) at \(y=D\). Mid-clearance uses the arc’s \(x\) at \(y=D/2\) on the path from \((0,0)\) to \((0,D)\); the can reference is fixed at \((-100,\,D/2)\), so clearance is \(x_{\mathrm{curve}}(D/2) - (-100) = x_{\mathrm{curve}}(D/2)+100\).

Motor Control Strategy

Use your measured arc length (encoder distance along the path) from calibration runs 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)