Appendix: Commented C Code Listings

This appendix contains the most critical code sections from the autonomous drawing car project, with detailed comments explaining the algorithms and implementation details. All code follows the protothreads cooperative multitasking model, allowing multiple concurrent tasks (UDP receive, motor control, IMU reading) to run without preemption overhead.

1. Path Optimization Algorithms

File: picomobile/udp_and_vga/picow_udp_send_recv_data.c
Purpose: Implements nearest-neighbor and 2-opt optimization algorithms to find the shortest path through all waypoints while preserving the overall drawing flow. The optimization runs only when the user right-clicks to send waypoints, preventing VGA freezes during waypoint accumulation.

1.1 Nearest Neighbor Algorithm

/**
 * Nearest Neighbor (NN) path optimization
 * Greedily selects the closest unvisited waypoint at each step
 * Time complexity: O(n²) where n is the number of waypoints
 * 
 * @param points: Array of waypoint coordinates
 * @param count: Number of waypoints to optimize
 * @param start: Starting position (robot's current location)
 * @param route: Output array storing the optimized route indices
 */
static void compute_nearest_neighbor_route(const waypoint_point_t *points, int count,
                                           waypoint_point_t start, int *route) {
    bool visited[MAX_WAYPOINTS] = {0};  // Track which waypoints have been visited
    waypoint_point_t current = start;  // Start from robot's current position

    // Build route step by step
    for (int step = 0; step < count; step++) {
        float best_dist = FLT_MAX;  // Initialize to maximum distance
        int best_idx = -1;  // Index of closest unvisited waypoint
        
        // Find the closest unvisited waypoint
        for (int idx = 0; idx < count; idx++) {
            if (visited[idx]) {
                continue;  // Skip already visited waypoints
            }
            const float dist = waypoint_distance(current, points[idx]);
            if (dist < best_dist) {
                best_dist = dist;
                best_idx = idx;  // Update closest waypoint
            }
        }
        
        if (best_idx < 0) {
            break;  // No more unvisited waypoints
        }
        
        route[step] = best_idx;  // Add to route
        visited[best_idx] = true;  // Mark as visited
        current = points[best_idx];  // Move to next position
    }
}

1.2 2-Opt Local Improvement

/**
 * 2-Opt optimization: Improves route by swapping edge pairs
 * Iteratively finds and reverses route segments that reduce total distance
 * Continues until no further improvements can be made
 * 
 * @param points: Array of waypoint coordinates
 * @param count: Number of waypoints
 * @param start: Starting position
 * @param route: Route array (modified in-place)
 */
static void two_opt_local_improvement(const waypoint_point_t *points, int count,
                                      waypoint_point_t start, int *route) {
    if (count < 3) {
        return;  // Need at least 3 points for 2-opt to make sense
    }

    bool improved = true;
    // Keep iterating until no improvements are found
    while (improved) {
        improved = false;
        
        // Try all pairs of edges (i, i+1) and (j, j+1)
        for (int i = 0; i < count - 1; i++) {
            // Get the two endpoints of first edge
            const waypoint_point_t A = (i == 0) ? start : points[route[i - 1]];
            const waypoint_point_t B = points[route[i]];
            
            for (int j = i + 1; j < count; j++) {
                // Get the two endpoints of second edge
                const waypoint_point_t C = points[route[j]];
                const waypoint_point_t D = (j == count - 1) ? start : points[route[j + 1]];
                
                // Calculate current distance: A->B + C->D
                const float current = waypoint_distance(A, B) + waypoint_distance(C, D);
                // Calculate swapped distance: A->C + B->D
                const float swapped = waypoint_distance(A, C) + waypoint_distance(B, D);
                
                // If swapping reduces distance (with small tolerance), reverse the segment
                if (swapped + 1e-3f < current) {
                    reverse_route_segment(route, i, j);  // Reverse route[i..j]
                    improved = true;  // Found improvement, continue searching
                }
            }
        }
    }
}

2. Car Navigation and Motor Control

File: picomobile/car_and_udp/picow_udp_send_recv_data.c
Purpose: Main control loop implementing a state machine for waypoint navigation. Uses IMU heading data to turn toward targets and time-based control for forward motion. The car has no wheel encoders, so position is estimated by sequentially updating the pose at each waypoint and correcting heading using the BNO055 IMU.

2.1 Navigation Helper Functions

/**
 * Calculate Euclidean distance from robot to target waypoint
 */
static float distance_to_waypoint(const pose_t *robot_pose, const waypoint_t *target)
{
    float dx = target->x - robot_pose->x;
    float dy = target->y - robot_pose->y;
    return sqrtf(dx * dx + dy * dy);
}

/**
 * Update robot heading from BNO055 IMU Euler angles
 * Reads yaw angle in degrees, converts to radians for internal use
 */
static void update_robot_heading_from_imu(void)
{
    float yaw_deg = fix2float15(euler[0]);   // heading in degrees (15.16 fixed point)
    robot.heading_rad = deg2rad(yaw_deg);    // store in radians
}

/**
 * Calculate absolute angle (in world frame) from robot to target waypoint
 * Returns angle in radians, range [-π, π]
 */
static float angle_to_waypoint(const pose_t *robot_pose, const waypoint_t *target)
{
    float dx = target->x - robot_pose->x;
    float dy = target->y - robot_pose->y;
    float waypoint_angle = atan2f(dy, dx);  // atan2 handles all quadrants correctly
    return waypoint_angle;   // radians
}

/**
 * Calculate signed turn angle error from current heading to waypoint
 * Normalizes error to [-π, π] range for shortest rotation direction
 * Positive error = turn right (clockwise), negative = turn left (counter-clockwise)
 */
static float turn_angle_to_waypoint(const pose_t *robot_pose, const waypoint_t *target)
{
    float desired = angle_to_waypoint(robot_pose, target);  // Desired heading
    float err     = desired - robot_pose->heading_rad;  // Heading error

    // Normalize to [-π, π] to ensure shortest rotation
    while (err >=  3.14159265f) err -= 2.0f * 3.14159265f;
    while (err < -3.14159265f) err += 2.0f * 3.14159265f;

    return err;
}

2.2 Main Control Loop (State Machine)

/**
 * Main movement control thread using protothreads
 * Implements a state machine with three states:
 *   - MOVE_WAITING_FOR_ROUTE: No waypoints available, car is stationary
 *   - MOVE_TURNING: Rotating to face the next waypoint
 *   - MOVE_DRIVING: Moving forward toward the waypoint
 * 
 * Handles dynamic waypoint addition: new waypoints can be added while car is moving
 */
static PT_THREAD (protothread_I_like_to_move_it_move_it(struct pt *pt))
{
    PT_BEGIN(pt);
    static enum {
        MOVE_WAITING_FOR_ROUTE = 0,
        MOVE_TURNING,
        MOVE_DRIVING
    } move_state = MOVE_WAITING_FOR_ROUTE;
    static uint64_t drive_end_time_us = 0;  // Timestamp when forward motion should stop
    static waypoint_t *current_target = NULL;
    static bool route_initialized = false;
    static int last_seen_waypoint_count = -1;  // Track waypoint count to detect new waypoints
    static int last_processed_waypoint_index = -1;  // Track progress through waypoints

    while (1) {
        uint32_t delay_us = 50000;  // Default 50ms delay (20 Hz control loop)

        // Check for new waypoint data
        // Format: [0]=waypoint_count, [1]=robot_x, [2]=robot_y, [3]=wp1_x, [4]=wp1_y, ...
        if (data_array[0] != -1 && data_array[1] != -1 && data_array[2] != -1) {
            int current_waypoint_count = data_array[0];
            
            // Only process if waypoint count increased (new waypoints added)
            if (current_waypoint_count > last_seen_waypoint_count) {
                if (!route_initialized) {
                    // First time: initialize robot position and load all waypoints
                    populate_robot();  // Set robot position from UDP data
                    populate_waypoints_from_index(0);
                    current_waypoint = 1;  // Start from waypoint 1 (skip waypoint 0 = home)
                    last_processed_waypoint_index = 0;
                    route_initialized = (loaded_waypoint_count > 1);
                    move_state = route_initialized ? MOVE_TURNING : MOVE_WAITING_FOR_ROUTE;
                } else {
                    // More waypoints added: load only new waypoints, keep current robot position
                    int start_index = last_processed_waypoint_index + 1;
                    populate_waypoints_from_index(start_index);
                    current_waypoint = start_index;  // Resume from first unvisited waypoint
                    
                    if (current_waypoint < loaded_waypoint_count) {
                        move_state = MOVE_TURNING;  // Resume navigation
                    } else {
                        move_state = MOVE_WAITING_FOR_ROUTE;  // All waypoints completed
                        set_left_motor_signed(0.0f);
                        set_right_motor_signed(0.0f);
                    }
                }
                
                last_seen_waypoint_count = current_waypoint_count;
            }
        }

        // State machine: handle current movement state
        switch (move_state) {
            case MOVE_WAITING_FOR_ROUTE:
                delay_us = 200000;  // Wait 200ms when no route available
                break;

            case MOVE_TURNING: {
                update_robot_heading_from_imu();  // Read current heading from IMU
                float err = turn_angle_to_waypoint(&robot, current_target);  // Calculate heading error
                
                if (fabsf(err) < ANGLE_TOL_RAD) {  // Within tolerance (10 degrees)
                    set_left_motor_signed(0.0f);  // Stop turning
                    set_right_motor_signed(0.0f);
                    
                    float dist = distance_to_waypoint(&robot, current_target);
                    if (dist < 0.01f) {  // Already at waypoint (within 1cm)
                        robot.x = current_target->x;  // Update robot position
                        robot.y = current_target->y;
                        last_processed_waypoint_index = current_waypoint;
                        current_waypoint++;  // Move to next waypoint
                        break;
                    }
                    
                    // Calculate drive time based on distance and speed
                    float time_s = dist / UNITS_PER_SECOND;  // UNITS_PER_SECOND = 50 units/second
                    drive_end_time_us = time_us_64() + (uint64_t)(time_s * 1000000.0f);
                    set_left_motor_signed(PWM_FORWARD);  // Start forward motion
                    set_right_motor_signed(PWM_FORWARD);
                    move_state = MOVE_DRIVING;  // Transition to driving state
                } else if (err < 0.0f) {
                    // Turn left: left motor backward, right motor forward
                    set_left_motor_signed(-TURN_PWM);
                    set_right_motor_signed(+TURN_PWM);
                } else {
                    // Turn right: left motor forward, right motor backward
                    set_left_motor_signed(+TURN_PWM);
                    set_right_motor_signed(-TURN_PWM);
                }
                delay_us = 50000;  // 50ms control loop during turning
                break;
            }

            case MOVE_DRIVING: {
                if (time_us_64() >= drive_end_time_us) {  // Time-based motion complete
                    set_left_motor_signed(0.0f);  // Stop motors
                    set_right_motor_signed(0.0f);
                    robot.x = current_target->x;  // Update position to waypoint
                    robot.y = current_target->y;
                    last_processed_waypoint_index = current_waypoint;
                    current_waypoint++;  // Move to next waypoint
                    move_state = MOVE_TURNING;  // Transition back to turning
                } else {
                    // Continue driving forward
                    set_left_motor_signed(PWM_FORWARD);
                    set_right_motor_signed(PWM_FORWARD);
                }
                delay_us = 50000;
                break;
            }
        }

        PT_YIELD_usec(delay_us);  // Yield control, resume after delay
    }

    PT_END(pt);
}

2.3 Motor Control Functions

/**
 * Set left motor speed with signed value
 * Positive = forward, negative = backward, zero = stop
 * Uses H-bridge with separate forward/reverse PWM channels
 */
void set_left_motor_signed(float speed) {
    if (speed > 0.0f) {
        // Forward: use forward PWM channel (GPIO16)
        left_forward_pin_pwm(speed);
        left_reverse_pin_pwm(0.0f);
    } else if (speed < 0.0f) {
        // Backward: use reverse PWM channel (GPIO7)
        left_forward_pin_pwm(0.0f);
        left_reverse_pin_pwm(-speed);  // Negate to get positive PWM value
    } else {
        // Stop: set both channels to zero
        left_forward_pin_pwm(0.0f);
        left_reverse_pin_pwm(0.0f);
    }
}

/**
 * PWM interrupt service routine
 * Called periodically by hardware PWM timer
 * Reads IMU Euler angles for heading estimation
 * NOTE: This runs in an interrupt context - keep it fast!
 */
void on_pwm_wrap() {
    // Clear the interrupt flag
    pwm_clear_irq(slice_fwd);
    pwm_clear_irq(slice_rev);

    // Read IMU Euler angles (yaw/pitch/roll)
    // Data is in 15.16 fixed point format
    // euler[0] = yaw (heading) in degrees
    bno055_read_euler(euler);
}

3. UDP Communication

File: picomobile/car_and_udp/picow_udp_send_recv_data.c
Purpose: Handles wireless UDP communication between PC-side and car-side Pico Ws. Receives waypoint data and sends acknowledgments.

3.1 UDP Receive Callback

/**
 * UDP receive callback function
 * Called by lwIP stack when a UDP packet arrives
 * This runs in an interrupt context - MUST be kept short!
 * 
 * @param arg: User argument (unused)
 * @param upcb: UDP protocol control block
 * @param p: Packet buffer containing received data
 * @param addr: Source IP address
 * @param port: Source port
 */
static void
udpecho_raw_recv(void *arg, struct udp_pcb *upcb, struct pbuf *p,
                 const ip_addr_t *addr, u16_t port)
{
    LWIP_UNUSED_ARG(arg);

    if (p != NULL) {
        // Copy packet payload to receive buffer
        // Format: [0]=waypoint_count, [1]=robot_x, [2]=robot_y, [3]=wp1_x, [4]=wp1_y, ...
        memcpy(recv_data, p->payload, UDP_MSG_LEN_MAX);
        
        // Signal protothread that new data is available
        // PT_SEM_SIGNAL can be called from ISR context
        PT_SEM_SIGNAL(pt, &new_udp_recv_s);
        
        // Free the packet buffer (required by lwIP)
        pbuf_free(p);
    }
}

/**
 * Initialize UDP receive socket
 * Binds to UDP_PORT (4444) and sets up receive callback
 */
void 
udpecho_raw_init(void)
{
    udpecho_raw_pcb = udp_new_ip_type(IPADDR_TYPE_ANY);  // Create UDP PCB
    p = pbuf_alloc(PBUF_TRANSPORT, UDP_MSG_LEN_MAX+1, PBUF_RAM);

    if (udpecho_raw_pcb != NULL) {
        err_t err;
        // Bind to local IP address and UDP_PORT
        err = udp_bind(udpecho_raw_pcb, netif_ip4_addr(netif_list), UDP_PORT);

        if (err == ERR_OK) {
            // Register receive callback
            udp_recv(udpecho_raw_pcb, udpecho_raw_recv, NULL);
        }
    }
}

4. Key Constants and Configuration

// Motor control constants
#define PWM_FORWARD        4000.0f   // PWM duty cycle for forward motion
#define TURN_PWM           3500.0f   // PWM level used while turning
#define ANGLE_TOL_DEG      10.0f      // Acceptable heading error (degrees)
#define ANGLE_TOL_RAD      (ANGLE_TOL_DEG * 3.14159265f / 180.0f)  // Convert to radians
#define UNITS_PER_SECOND  50.0f     // Robot speed: 50 units/second

// UDP configuration
#define UDP_PORT           4444      // UDP port for communication
#define UDP_MSG_LEN_MAX   1024      // Maximum UDP packet size
#define MAX_WAYPOINTS      256       // Maximum number of waypoints
#define MAX_DATA_SIZE     512       // Maximum data array size

// Robot pose structure
typedef struct {
    float x;              // X position (units)
    float y;              // Y position (units)
    float heading_rad;    // Heading angle (radians)
} pose_t;

// Waypoint structure
typedef struct {
    float x;              // X coordinate
    float y;              // Y coordinate
} waypoint_t;
Note: This appendix shows the most critical code sections. The complete source code is available in the repository.