yahboom
HomeLearn
Solution
Blog
Online Store
About Us
EN/中文
ESP32 WiFi Camera Lite📖
1.ESP32 camera basic course
  • 1.Development environment
  • 2.Compile and use routine
  • 3.How to update firmware
2.Configure camera on PC
  • Configure camera on PC
3.View video on APP
  • View video on APP
4.Configure camera on MCU
IIC
  • Arduino
  • Microbit
  • STM32
  • MSPM0G3507
  • MSPM0G3519new
  • PICO
  • Raspberry Pi
  • Jetson
  • RDK X5
Serial Port
  • Arduino
  • Microbit
  • STM32
  • MSPM0G3507
  • MSPM0G3519new
  • PICO
  • Raspberry Pi
  • Jetson
  • RDK X5
5. APP remote control servo gimbal
  • 1. STM32 drives the servo gimbal
  • 2.Arduino drives the servo gimbal
  • 3.Pico
  • 4.MSPM0G3507 drives the servo gimbal
  • 5.MSPM0G3519 drives the servo gimbalnew
  • 6. Microbit drives the servo gimbal
  • 7. Raspberry Pi drives the servo gimbal
  • 8. Jetson drives the servo gimbal
  • 9.RDK X5 driving servo gimbal
6. Expansion tutorials
  • ESP32
  • Robot car
Download
  • RemoteCtrl_APP(iOS_Android)
  • 3D_Model_File
  • Code
  • Software_Tools
  • Module_fireware
  • Protocols_Hardware
Download ZIP

Welcome to ESP32 WiFi Camera Lite repository

Robot car

MSPM0G3507/MSPM0G3519 Competition Robot Car Tutorial — Steel Ball Control + Four-Channel Line Tracking

MSPM0G3507/MSPM0G3519 Competition Robot Car Tutorial — Steel Ball Control + Four-Channel Line Tracking1. Hardware Platform2. Software Environment3. Structural Overview3.1 Component List and Materials3.2 Physical Appearance3.3 Assembly Instructions4. Working Principle4.1 Wiring Pinout4.2 Wiring Diagram4.3 Control Principle5. Project File Structure6. Key Parameter Configuration6.1 Servo Ball Control PID (vision_servo_test.c / esp_ball_control.c)6.2 Line-Tracking PID (Four_linewalking_debug.c)6.3 Per-Task Line-Tracking Speeds (questions.c)6.4 Task Timeout Settings (questions.c)7. Task Descriptions7.1 State Machine Structure (questions.h)7.2 Task Details8. Key Code Explanation8.1 ESP32 Ball Control — ESP_Ball_Control_Update() (esp_ball_control.c)8.2 Line Tracking — Debug_LineWalking_Run() (Four_linewalking_debug.c)8.3 Combined Tracking + Ball Control — Debug_LineWalking_WithBall() (Four_linewalking_debug.c)8.4 Task 2 Steel Ball Reciprocation (questions.c)9. Operating Procedure


1. Hardware Platform

ComponentModel / Description
Main controllerMSPM0G3507/MSPM0G3519 development board
CameraESP32 vision recognition module (steel ball coordinate detection)
Line trackingFour-channel IR tracking module (digital output)
DisplayOLED 128×32 (I2C)
Servo9G servo (steel ball tilt control)
MotorTwo-wheel L-type 520 encoder motors (I2C driver board)
ButtonExpansion board K1 single-button control

2. Software Environment

  • IDE: Keil MDK V5 (ARM Compiler V6.7)

  • SDK: TI MSPM0 SDK 2.02.00.05

3. Structural Overview

3.1 Component List and Materials

  • Chassis: Black metal-etched base plate, drive motors, wheels. The chassis features pre-drilled screw holes and cable routing holes.

  • Mechanical arm structure: Blue/black metal servo brackets, metal link metal L-shaped / straight connectors, servo, metal flange plate.

  • Horizontal scale sliding platform: Transparent acrylic enclosure, hand-written digit scale and zero reference line, internal drive rail / lead screw.

  • Top vision bracket: Brushed metal strip (aluminum alloy / stainless steel), transparent acrylic L-shaped bracket, ESP32 vision module.

  • Hardware accessories: M2/M3 combination screws, nuts, washers, black cable ties, heat-shrink tubing.

Note: In addition to the accessories included in the v2 chassis kit, the 9G servo, ESP32 vision module, and tracking module can be purchased from our store. Other connecting rods, scales, water tubes, and hardware accessories must be sourced separately.

3.2 Physical Appearance

3.3 Assembly Instructions

Step 1: Install the Mobile Chassis and Motors

  • Mount the two drive motors (with wheels) on opposite sides of the black metal base plate. Ensure the wheels are oriented consistently and make full contact with the ground.

  • Install the MSPM0G3507/MSPM0G3519 main control board using screws in the designated position on the chassis. Be careful to avoid the wheel and motor rotation clearance.

Step 2: Build the Horizontal Scale and Sliding Platform

  • Refer to Figure 5: Assemble the transparent acrylic enclosure and insert the paperboard with the printed scale into it. This structure is mounted on the front of the chassis.

  • Ensure the rail or lead screw inside the sliding platform remains level, as this serves as the critical reference datum for subsequent mechanical arm coordinate calibration.

Step 3: Assemble the Mechanical Arm Joints

  • Refer to Figures 3 and 7: Press the green servo into the black metal bracket and secure it with the provided screws.

  • Install the servo arm and connect the blue/black metal linkages.

  • Refer to Figure 7 for the end effector assembly (connector / gripper). In the photos, this component uses a significant amount of transparent acrylic and hot-melt glue / resin for reinforcement. For actual assembly, 3D-printed or metal parts are recommended to ensure sufficient structural strength.

  • Mount the servo base to the rear of the chassis (refer to the layout in Figure 7).

Step 4: Install the Vertical Metal Bracket

  • Refer to Figures 1, 4, and 6: Take the brushed metal strip and mount it vertically in the center or front-side mounting slot of the chassis.

  • When securing with screws, ensure the metal strip is perpendicular to the chassis plane and does not wobble. In Figure 6, the nut is passed through and tightened from the back.

Step 5: Install the Top Vision Sensor

  • Refer to Figure 35: Position the ESP32 module with its back facing upward. Use a bracket and M3 screws to mount the module at the top of the vertical metal bracket.

  • Adjust the sensor's orientation to ensure it can see the track and scale in front of the robot.

4. Working Principle

4.1 Wiring Pinout

Motor Wiring:

MotorDriver Board
M2M-
V3V3
AH1B
BH1A
GGND
M1M+

Four-Channel Motor Driver Board:

Driver BoardMSPM0G3507/MSPM0G3519
5V5V
SCLPA15
SDAPA16
GNDGND

Four-Channel Tracking Module:

Tracking ModuleMSPM0G3507/MSPM0G3519
5V5V
X1PA24
X2PA25
X3PA26
X4PA27
GNDGND

OLED:

OLEDMSPM0G3507/MSPM0G3519
VCC5V
GNDGND
SDAPA28
SCLPA31

ESP32 Camera:

ESP32 Vision ModuleMSPM0G3507/MSPM0G3519
5V5V
GNDGND
TXDPA13 (UART3_RX)
RXDPA14 (UART3_TX)
Baud rate115200

4.2 Wiring Diagram

4.3 Control Principle

Robot Car Line-Tracking Control Flow:

Output

Feedback

Start Robot Car

Peripheral Initialization

Select Competition Task via Buttons

MCU: Process Data from Each Module

Control Motor Motion

End

 

  1. Four-channel IR sensors read the black line position -> compute deviation

  2. PID controller computes turning correction -> controls differential steering

  3. ESP32 camera detects steel ball position -> sends coordinates via UART3 -> servo PID controls swing arm angle

  4. Timer expiry or waypoint detection -> stop / state transition

PID Control Parameter Descriptions:

PID ParameterFunctionAdjustment Direction
P (Proportional)Determines response speed; large output for large errorsToo large: oscillation / serpentine motion; Too small: weak turns
I (Integral)Eliminates steady-state error; corrects accumulated errorToo large: overshoot; Too small: drifts to one side and does not return
D (Derivative)Suppresses oscillation; decelerates in advanceToo large: jittering; Too small: cannot brake

5. Project File Structure

Steel_ball_control_4walking/
├── main.c                     # Main program: peripheral initialization, main loop, state machine dispatch
├── APP/
│   ├── app_k230.c/h           # Legacy K230 serial protocol and ball control code (not used in current main flow)
│   ├── app_pid.c/h            # PID controller (positional + incremental)
│   ├── app_kalman.c/h         # One-dimensional Kalman filter
│   └── app_servo.c/h          # Servo PWM driver
├── BSP/
│   ├── Four_Tracking/
│   │   ├── Four_linewalking_debug.c/h # Four-channel line tracking + line-tracking PID + combined task (tracking + ball control)
│   │   └── Four_linewalking.c/h# Four-channel tracking module interface definitions
│   ├── Motor/
│   │   ├── app_motor.c/h      # Car motion control (Motion_Car_Control)
│   │   └── bsp_motor_iic.c/h# Motor driver board I2C protocol
│   ├── OLED/
│   │   ├── oled.c/h           # OLED driver + display function OLED_time()
│   │   └── oledfont.h         # Font data
│   ├── Key/
│   │   └── key.c/h            # Button driver + mode selection state machine
│   ├── Questions/
│   │   └── questions.c/h      # Competition task state machine (Task 1~5)
│   ├── Task/
│   │   └── task.c/h           # Timer-based task scheduler (Scheduler_Run)
│   ├── Timer/
│   │   └── timer.c/h          # 1 ms system clock Get_Time()
│   └── usart.c/h              # Serial initialization (UART0 debug, UART1 K230)
│   ├── bsp_camera_usart.c/h   # ESP32 UART3 receive, checksum and coordinate parsing (current vision pipeline)
│   └── esp_ball_control.c/h   # ESP32 coordinate-to-servo control wrapper
└── ti_msp_dl_config.c/h       # TI driver library configuration (SysConfig generated)

6. Key Parameter Configuration

6.1 Servo Ball Control PID (vision_servo_test.c / esp_ball_control.c)

​x
/* vision_servo_test.h: Parameters displayed in debug output. */
#define PID_KP             0.060f
#define PID_KI             0.0080f
#define PID_KD             0.800f

#define CAMERA_IMAGE_WIDTH 320U /* ESP32 image width (px) */
#define INPUT_CENTER_X     150.0f  /* X coordinate when the ball is centered on the arm (px) */
#define INPUT_CM_PX_POS    0.0863f /* cm/px for right side */
#define INPUT_CM_PX_NEG    0.0835f /* cm/px for left side */

#define PWM_CC_CENTER   60.0f   /* Servo CC center value (1500 us) */
#define PWM_CC_MIN      50.0f   /* Servo CC minimum */
#define PWM_CC_MAX      70.0f   /* Servo CC maximum */
#define PWM_CC_SCALE     1.15f  /* PID output to CC scaling factor */

#define REACH_ERR_PX     18.0f   /* Target-reached threshold */
#define RATE_LIMIT       4.0f   /* Per-frame CC change rate limit */
#define HISTORY_TIMEOUT_MS 300U  /* Vision frame interval timeout threshold */
#define KALMAN_Q         1.0f   /* ESP32 coordinate filter: process noise (increase for faster response) */
#define KALMAN_R         0.3f   /* ESP32 coordinate filter: measurement noise (increase for smoother output) */

Note: PID_KP/KI/KD are currently only used for startup debug information; the actual runtime parameters in pid_calc() are defined within the function body with base values of kp=0.058, ki=0.008, kd=0.720. When approaching the target, kp is dynamically reduced. Tuning should modify the runtime parameters inside pid_calc(), not just the print macros in the header file.

Target position conversion uses independent calibration for left and right sides:

xxxxxxxxxx
float cm_px = (target_cm >= 0.0f) ? INPUT_CM_PX_POS : INPUT_CM_PX_NEG;
float target_x = INPUT_CENTER_X + target_cm / cm_px;
float err_px = target_x - x_flt;

PID Tuning Guide:

SymptomAdjustDirectionDescription
Ball oscillates near centerIncrease kd or tighten D limitIncrease dampingAlso check RATE_LIMIT
Ball stops before reaching targetIncrease kp or add standstill boostIncrease thrustFirst confirm coordinate calibration is correct
Ball overshoots and bounces farDecrease kp or increase kdReduce thrust, increase damping 
Ball settles off-center and does not returnIncrease kiStrengthen integralWatch out for integral saturation
Servo output changes too fastDecrease RATE_LIMITLimit per-frame CC change 

6.2 Line-Tracking PID (Four_linewalking_debug.c)

xxxxxxxxxx
#define DEBUG_LINE_KP                  55.0f 
#define DEBUG_LINE_KI                  0.00f
#define DEBUG_LINE_KD                  645.0f

Line-Tracking Tuning Guide:

SymptomAdjustDirection
Insufficient turning / line straddlingIncrease KPIncrease
Serpentine motionIncrease KD or decrease KPDamping / reduce thrust
Constant drift to one sideIncrease KIStrengthen integral

Inertia Compensation (used in the combined tracking + ball control task):

xxxxxxxxxx
#define BALL_INERTIA_FWD   -0.8f  // Forward inertia compensation (cm); larger values push the ball forward
#define BALL_INERTIA_TURN  -0.8f  // Turning inertia compensation (cm)
#define BALL_START_COMP    -5.0f  // Start-up compensation (cm); pushes the ball forward when the car accelerates and the ball lags behind
#define BALL_STOP_COMP      5.0f  // Stopping compensation (cm); pulls the ball back when the car stops and the ball overshoots
#define RAMP_TIME_MS       3000   // Start-up ramp time (ms); larger values mean gentler acceleration

6.3 Per-Task Line-Tracking Speeds (questions.c)

xxxxxxxxxx
#define TASK1_SPEED      316   // Task 1 pure line-following speed
#define TASK3_SPEED      300   // Task 3 A->B + ball control speed
#define TASK4_SPEED      232   // Task 4 one lap + ball control speed
#define TASK5_SPEED      232   // Task 5 specified position speed
#define RAMP_TIME_MS     3000  // Start-up ramp time

6.4 Task Timeout Settings (questions.c)

xxxxxxxxxx
#define TASK1_MAX_MS   20000    // Task 1 pure line-following 20s timeout
#define TASK2_MAX_MS    5000    // Task 2 steel ball reciprocation 5s timeout
#define TASK3_MAX_MS    8000    // Task 3 A->B + ball control 8s timeout
#define TASK4_MAX_MS   30000    // Task 4 one lap + ball control 30s timeout
#define TASK5_MAX_MS   30000    // Task 5 specified position 30s timeout

7. Task Descriptions

7.1 State Machine Structure (questions.h)

Main States (Main_State):

MacroValueTask Description
STOP_STATE0Stop
TASK_LINE_LAP1Task 1: Pure line-following one lap back to A, ≤ 20 s
TASK_BALL_CTRL2Task 2: Steel ball O → +5 → 0 → -5 (stop), < 5 s
TASK_AB_STAB3Task 3: A → B + ball stabilized at O, ≤ 8 s
TASK_LAP_STAB4Task 4: One lap back to A + ball stabilized at O, ≤ 30 s
TASK_LAP_POS5Task 5: One lap back to A + ball at specified position, ≤ 30 s

Sub-States (Sub_State):

MacroValueDescription
SUB_IDLE0Idle
SUB_INIT1Initialization (set speed, start timer)
SUB_RUNNING2Running
SUB_DONE3Completed (car stops but ball control continues; also entered on timeout)

7.2 Task Details

Task 1 (Pure Line-Following):

  • Line-following only; no ball control

  • Speed: TASK1_SPEED = 316 mm/s

  • Passes through 4 waypoints (A/B/C/D) and stops at A

  • Auto-stop after 20 s timeout

Task 2 (Steel Ball Control):

  • Car is stationary; only servo control

  • Sequence: O → +5 cm (settle) → 0 cm (settle) → -5 cm (settle)

  • Each phase waits for the ball to approach the target (≤ 0.5 cm) for 30 consecutive frames before transitioning

  • 5 s timeout

  • After completion, the ball is held at the -5 cm position

Task 3 (A → B + Ball Control):

  • Line-following and servo ball control simultaneously

  • Speed: TASK3_SPEED = 130 mm/s

  • Departs from A, passes through 1 waypoint, arrives at B

  • Ball is stabilized at center O during travel

  • 8 s timeout; car stops but ball control continues

Task 4 (One Lap + Ball Control):

  • Line-following and servo ball control simultaneously

  • Speed: TASK4_SPEED = 222 mm/s

  • Clockwise one lap, passing through 4 waypoints, returning to A

  • Ball is stabilized at center O during travel

  • 30 s timeout; car stops but ball control continues

Task 5 (Specified Position + Ball Control):

  • Same as Task 4, but the ball target position is configurable (Target_Position)

  • Speed: TASK5_SPEED = 222 mm/s

  • 30 s timeout; car stops but ball control continues


8. Key Code Explanation

8.1 ESP32 Ball Control — ESP_Ball_Control_Update() (esp_ball_control.c)

The ESP32 vision pipeline consists of three steps:

  1. Camera_Vision_Process() parses the 8-byte frame AA 55 FLAGS SEQ X_L X_H WIDTH CHECKSUM from the UART3 ring buffer and updates the global vision structure.

  2. ESP_Ball_Control_Update() is simply a wrapper around Vision_Servo_Test_Update(). The actual filtering, error calculation, and servo control are performed in vision_servo_test.c.

  3. The controller only processes new valid frames; when no new frame is available, it returns VISION_SERVO_NO_FRAME and does not drive the servo again.

The calling sequence in the main loop is:

xxxxxxxxxx
/* main.c */
Camera_Vision_Process();
ESP_Ball_Control_Run();

The wrapper function implementation is as follows:

xxxxxxxxxx
/* esp_ball_control.c */
VisionServoResult ESP_Ball_Control_Update(float target_cm, uint8_t hold)
{
    /* hold is currently reserved as an interface parameter and is not used by Vision_Servo_Test_Update. */
    return Vision_Servo_Test_Update(target_cm, hold);
}

The core control flow is shown below. The calibration values in the code must be based on actual device calibration results:

xxxxxxxxxx
/* vision_servo_test.c */
VisionServoResult Vision_Servo_Test_Update(float target_cm, uint8_t hold)
{
    uint32_t frame = vision.valid_n;
    if (frame == s_frame)
    {
        return VISION_SERVO_NO_FRAME;
    }

    s_frame = frame;

    /* Clear the integral and reset the filter on initialization, excessive vision interval, or target change. */
    uint8_t reset = 0U;
    if (!s_inited)
    {
        s_kf.first = 1U;
        s_pid.integral = 0.0f;
        s_pid.target_cm = target_cm;
        s_inited = 1U;
        reset = 1U;
    }
    else if ((s_valid_ms != 0U) &&
             ((vision.valid_ms - s_valid_ms) > HISTORY_TIMEOUT_MS))
    {
        s_kf.first = 1U;
        s_pid.integral = 0.0f;
        reset = 1U;
    }
    else if (fabsf(target_cm - s_pid.target_cm) > 0.1f)
    {
        s_pid.integral = 0.0f;
        s_pid.target_cm = target_cm;
        reset = 1U;
    }
    s_valid_ms = vision.valid_ms;

    /* Apply one-dimensional Kalman filtering to the ESP32 X coordinate output. */
    float x_flt = kalman_filter(&s_kf, (float)vision.x);

    /* Convert the steel ball position and target error using independent left/right calibration coefficients. */
    float cm_px = (x_flt >= INPUT_CENTER_X)
                    ? INPUT_CM_PX_POS : INPUT_CM_PX_NEG;
    float pos_cm = (x_flt - INPUT_CENTER_X) * cm_px;
    float err_px = vision_to_error_px(x_flt, target_cm);
    uint8_t reached = (fabsf(err_px) < REACH_ERR_PX);

    /* pid_calc returns the incremental servo CC value, which is then clamped and rate-limited. */
    float cc_req = PWM_CC_CENTER +
                   pid_calc(&s_pid, err_px, pos_cm, reset);
    set_servo(cc_req);

    return reached ? VISION_SERVO_REACHED : VISION_SERVO_MOVING;
}

The pid_calc() computation includes: reducing Kp for small errors, accumulating and clamping the integral within a moderate error band, limiting the derivative term based on error magnitude, and adding a small boost when the ball has been stationary for an extended period. The final output is:

xxxxxxxxxx
output = (p + i + d) * PWM_CC_SCALE + boost;

The base parameters are kp=0.058, ki=0.008, kd=0.720. The servo output center is CC=60, range is 50~70, and the per-frame rate limit is 4. The code also computes a velocity_brake debug variable but does not add it to the final output, so the tutorial should not describe it as an active velocity brake.

8.2 Line Tracking — Debug_LineWalking_Run() (Four_linewalking_debug.c)

xxxxxxxxxx
/* Standalone four-channel line-following function. */
void Debug_LineWalking_Run(void)
{
    extern volatile uint32_t g_task_start_ms;
    uint32_t now = Get_Time();

    if ((s_debug_inited == 0U) || (g_task_start_ms != s_debug_last_task_start_ms))
    {
        s_debug_last_task_start_ms = g_task_start_ms;
        Debug_Line_Reset();
        s_debug_inited = 1U;
    }

    if ((now - s_debug_last_run_ms) < DEBUG_LINE_PERIOD_MS)
    {
        return;
    }
    s_debug_last_run_ms = now;

    float raw_error = Debug_Line_GetError();
    float line_error = raw_error;
    float turn_cmd = 0.0f;

    if (s_debug_line_lost != 0U)
    {
        if (s_debug_lost_start_ms == 0U)
        {
            s_debug_lost_start_ms = now;
        }
        uint32_t lost_ms = now - s_debug_lost_start_ms;

        if (lost_ms >= DEBUG_LINE_LOST_STOP_MS)
        {
            control_speed(0, 0, 0, 0);
            return;
        }

        if (lost_ms >= DEBUG_LINE_LOST_DEBOUNCE_MS)
        {
            float lost_turn = (lost_ms >= DEBUG_LINE_LOST_ESCALATE_MS)
                              ? DEBUG_LINE_LOST_TURN_HARD
                              : DEBUG_LINE_LOST_TURN_SOFT;
            float lost_dir = 0.0f;
            if (s_debug_last_direction > 0.0f)      { lost_dir = 1.0f; }
            else if (s_debug_last_direction < 0.0f) { lost_dir = -1.0f; }
            else if (s_debug_last_turn_dir > 0.0f)  { lost_dir = 1.0f; }
            else if (s_debug_last_turn_dir < 0.0f)  { lost_dir = -1.0f; }
            else                                    { lost_dir = 1.0f; }
            turn_cmd = lost_dir * lost_turn;
            s_debug_err_last = raw_error;
        }
        else
        {
            turn_cmd = Debug_Line_PID(raw_error);
        }
    }
    else
    {
        s_debug_lost_start_ms = 0U;

#if DEBUG_LINE_USE_KALMAN
        line_error = Kalman_Filter(&s_debug_line_kalman, raw_error);
#endif

        turn_cmd = Debug_Line_PID(line_error);

        if (turn_cmd > 1.0f)      { s_debug_last_turn_dir = 1.0f; }
        else if (turn_cmd < -1.0f){ s_debug_last_turn_dir = -1.0f; }
    }

    /* Convert the current debug turning quantity to the 5000 scale for ball inertia compensation. */
    pid_output_IRR = (int)(turn_cmd * (BALL_INERTIA_PID_MAX / DEBUG_LINE_OUT_MAX));

    float base_speed = (float)g_line_speed;

    if (base_speed <= 0.0f)
    {
        base_speed = DEBUG_LINE_BASE_SPEED;
    }

    float right_cmd = base_speed + turn_cmd;
    float left_cmd  = base_speed - turn_cmd;

    right_cmd = Debug_Clamp(right_cmd,
                            DEBUG_LINE_MIN_MOTOR_CMD,
                            DEBUG_LINE_MAX_MOTOR_CMD);
    left_cmd = Debug_Clamp(left_cmd,
                           DEBUG_LINE_MIN_MOTOR_CMD,
                           DEBUG_LINE_MAX_MOTOR_CMD);

    control_speed(0, (int16_t)right_cmd, 0, (int16_t)left_cmd);

    if ((now - s_debug_last_print_ms) >= DEBUG_LINE_PRINT_PERIOD_MS)
    {
        uint32_t ps = DL_GPIO_readPins(Sensor_four_PORT,
            Sensor_four_X1_PIN | Sensor_four_X2_PIN |
            Sensor_four_X3_PIN | Sensor_four_X4_PIN);
        int s1 = (ps & Sensor_four_X1_PIN) ? 1 : 0;
        int s2 = (ps & Sensor_four_X2_PIN) ? 1 : 0;
        int s3 = (ps & Sensor_four_X3_PIN) ? 1 : 0;
        int s4 = (ps & Sensor_four_X4_PIN) ? 1 : 0;

        printf("IR=%d%d%d%d E=%.2f T=%.2f L=%d R=%d LOST=%d\r\n",
               s1, s2, s3, s4,
               (double)line_error,
               (double)turn_cmd,
               (int)left_cmd,
               (int)right_cmd,
               (int)s_debug_line_lost);

        s_debug_last_print_ms = now;
    }
}

8.3 Combined Tracking + Ball Control — Debug_LineWalking_WithBall() (Four_linewalking_debug.c)

xxxxxxxxxx
/* Four-channel line tracking with steel ball inertia compensation. */
void Debug_LineWalking_WithBall(float ball_target_cm)
{
    static uint32_t last_task_start_ms = 0xFFFFFFFFU;
    static uint8_t  ramp_ready = 0;
    static uint8_t  stop_ready = 0;
    static uint32_t stop_start_ms = 0;
    static int      saved_spd = 0;
    static int      stop_spd = 0;
    extern volatile uint32_t g_task_start_ms;
    float start_comp = 0.0f;
    float stop_comp  = 0.0f;

    /* Record the cruising speed and reset ramp / stop states when the task restarts. */
    if (g_task_start_ms != last_task_start_ms)
    {
        last_task_start_ms = g_task_start_ms;
        saved_spd = g_line_speed;
        ramp_ready = 1;
        stop_ready = 0;
        stop_start_ms = 0;
        stop_spd = 0;
    }

    /* Stop phase: linearly decelerate from the current speed to 0 over BALL_STOP_TIME_MS. */
    if (g_slow_stop)
    {
        if (!stop_ready)
        {
            stop_start_ms = Get_Time();
            stop_spd = g_line_speed;
            stop_ready = 1;
        }

        uint32_t st = Get_Time() - stop_start_ms;
        if (st >= BALL_STOP_TIME_MS)
        {
            g_line_speed = 0;
            control_pwm(0, 0, 0, 0);
            ESP_Ball_Control_Update(ball_target_cm, 1U);
            return;
        }

        float stop_ratio = 1.0f - (float)st / (float)BALL_STOP_TIME_MS;
        if (stop_ratio < 0.0f) stop_ratio = 0.0f;
        if (stop_ratio > 1.0f) stop_ratio = 1.0f;
        stop_comp = BALL_STOP_COMP * stop_ratio;
        g_line_speed = (int)((float)stop_spd * stop_ratio);
    }
    else
    {
        stop_ready = 0;
        stop_start_ms = 0;
        stop_spd = 0;

        /* Start-up phase: linearly accelerate from minimum speed to cruising speed. */
        if (ramp_ready)
        {
            uint32_t et = Get_Time() - last_task_start_ms;
            if (et < BALL_START_COMP_TIME_MS)
                start_comp = BALL_START_COMP * (1.0f - (float)et / (float)BALL_START_COMP_TIME_MS);
            if (et < BALL_RAMP_TIME_MS)
            {
                float ramp = (float)et / (float)BALL_RAMP_TIME_MS;
                if (ramp < BALL_RAMP_MIN_RATIO) ramp = BALL_RAMP_MIN_RATIO;
                if (ramp > 1.0f) ramp = 1.0f;
                g_line_speed = (int)((float)saved_spd * ramp);
            }
            else
            {
                g_line_speed = saved_spd;
            }
        }
    }

    /* Run line tracking first, then process ESP32 data and send the compensated servo target. */
    Debug_LineWalking_Run();
    Camera_Vision_Process();

    float compensated = ball_target_cm;
    compensated -= BALL_INERTIA_FWD;
    if (pid_output_IRR > 0)
        compensated -= BALL_INERTIA_TURN * (float)pid_output_IRR / BALL_INERTIA_PID_MAX;
    else if (pid_output_IRR < 0)
        compensated += BALL_INERTIA_TURN * (float)(-pid_output_IRR) / BALL_INERTIA_PID_MAX;
    compensated += start_comp + stop_comp;

    ESP_Ball_Control_Update(compensated, 1U);
}

8.4 Task 2 Steel Ball Reciprocation (questions.c)

xxxxxxxxxx
// Three phases: +5 cm (1.8 s) -> 0 cm (1.8 s) -> -5 cm (1.8 s) -> DONE (hold at -5)
case SUB_RUNNING: {
    static int phase = 0;
    static uint32_t t0 = 0;
    if (t0 == 0) t0 = Get_Time();

    float target_cm;
    if (phase == 0) target_cm = +5.0f;
    else if (phase == 1) target_cm = 0.0f;
    else target_cm = -5.0f;

    ESP_Ball_Control_Update(target_cm, 1U);

    if (Get_Time() - t0 >= 1800) {     // 1.8 seconds per phase
        t0 = Get_Time();
        if (phase == 0) phase = 1;
        else if (phase == 1) phase = 2;
        else { phase = 0; Sub_State = SUB_DONE; }
    }
    break;
}
case SUB_DONE:
    ESP_Ball_Control_Update(-5.0f, 1U);    // Hold ball at the -5 cm position
    break;

9. Operating Procedure

  1. After wiring, power on. The ESP32 UART3 vision pipeline initializes and waits for 4 seconds.

  2. The mode selection screen appears on the OLED showing "Mode:1".

  3. Long press K1 to cycle through modes (1~5).

  4. Short press K1 to confirm and enter the selected mode.

  5. The robot car starts executing the task; the OLED displays the task name and timer.

  6. Short press K1 during task execution: Pause; Long press K1: Exit back to mode selection.

  7. After task completion or timeout, the car stops but ball control continues (the ball is held at the target position).

  8. Long press K1 to exit back to mode selection.

 

ESP32

COMPANY

  • About us
  • Team Style
  • Blog

LEARNING & EXPLORATION

  • Tutorials
  • Edge AI hardware
  • Educational robots
  • Embedded intelligent hardware

WHERE TO BUY

  • Yahboom Online Store
  • Amazon
  • Alibaba
  • AliExpress
  • Mercado

TECHNOLOGY SUPPORT

  • Email: support@yahboom.com

BE A DISTRIBUTOR

  • Email: sales@yahboom.com
QR Code

Copyright © 2017-2026 Yahboom. All Rights Reserved.

EN/中文