Source Code

Software organization, major modules, and repository links

Code / Software Architecture

Scheduled Tasks 6
Scheduler Round-Robin

Our software was organized as a cooperative multitasking control system running on the STM32 Nucleo. The main program creates the hardware interfaces, allocates the shared variables, instantiates each task, and then runs the cotask scheduler continuously during operation.

The final program is divided into six scheduled behaviors: left motor control, right motor control, bump sensing, observer/state estimation, high-level navigation, and the user interface. These tasks communicate through shared variables so that sensing, control, and decision making can happen together without putting all robot behavior inside one large loop.

In addition to those scheduled tasks, the software also includes dedicated drivers for the motor hardware, quadrature encoders, line sensor array, IMU, and bumper inputs. Together, these files form the full control stack used during the final obstacle-course runs.

Main Files and Repository Structure

main.py

Main startup file. Initializes motors, encoders, line sensors, bump sensors, I2C, and IMU; creates shares and queues; instantiates all tasks; and starts the scheduler.

View file

task_user.py

USB serial user interface for calibration, mission start/stop, and runtime tuning of base speed, line gains, and motor gains.

View file

task_motor.py

Left and right motor tasks. Each task updates its encoder, computes velocity error, applies saturated motor effort, and publishes wheel motion data.

View file

task_navigator.py

Top-level autonomous navigation FSM. Handles line following, heading-hold motion, wall interaction, dot detection, turnaround, and final return-to-finish logic.

View file

task_estimator.py

Observer task that combines wheel motion and IMU heading information to estimate traveled distance, heading, and robot position.

View file

linesensor_driver.py

Seven-channel reflectance sensor driver with white/black calibration, line visibility, dot counting, and centroid calculation for steering.

View file

motor_driver.py

Low-level H-bridge motor driver with PWM effort, direction control, and enable/disable functionality for each Romi motor.

View file

encoder.py

Quadrature encoder driver that updates wheel position and computes wheel velocity from timer-based encoder counts.

View file

imu_driver.py

BNO055 IMU interface over I2C, including fusion-mode changes, calibration status, stored calibration loading/saving, and yaw/yaw-rate access.

View file

bump_sensor.py / task_bump.py

Bumper hardware interface and scheduled bump task. Debounces inputs, forms a bitmask, and reports wall-contact events during the run.

estimator.py

Observer model matrices and update function used by the scheduled observer task to propagate the robot state estimate.

View file

cotask.py / task_share.py

Cooperative scheduler, task definitions, shared-variable objects, and queue structures used throughout the project.

View Full Repository

Detailed Module Description

main.py

The main.py file is the entry point for the robot. It builds the two motor drivers, two encoders, the seven-sensor line array, the six bumper inputs, and the BNO055 IMU interface. After that, it allocates the system shares for commands, tuning parameters, setpoints, estimated states, and bump events. Finally, it creates the six scheduled tasks and runs them using the round-robin scheduler.

This file is also where the default starting parameters are loaded. At startup, the left and right motor gains are both set to 0.20, the base speed is set to 45.0, and the line following gains are initialized to Kp = 3.0 and Ki = 0.02.

Motor Control Layer

The low-level motor control is split across motor_driver.py, encoder.py, and task_motor.py. The driver file handles PWM, direction, and motor enable. The encoder file reads quadrature counts and converts them into wheel position and velocity. The scheduled motor task ties them together.

Each motor task moves through three states: initialize, wait, and run. In the run state, the encoder is updated, the measured velocity is compared with the requested setpoint, and a saturated control effort is applied using the motor gain share. The task also publishes the current motor effort and wheel distance back to the rest of the system.

Line Sensing

The line sensor driver is responsible for reading the seven analog reflectance sensors and converting those values into usable navigation information. It stores white and black calibration values, computes normalized readings, detects whether a line is visible, counts dark regions for dot detection, and computes a centroid used for steering correction.

This module is not scheduled as its own cotask, but it is heavily used by both the user task and the navigator. The user task calls it during calibration, while the navigator uses its centroid, line visibility, and dark-count outputs during autonomous motion.

User Interface and Runtime Tuning

The user interface lives in task_user.py and communicates over USB_VCP. It presents a command menu that allows the operator to print help, calibrate the line sensor, start the mission, stop the mission, and change the base speed, line gains, or motor gains while testing.

This task is important because it gives the robot a clean setup workflow. Instead of hard coding every value before each run, the operator can calibrate the line sensor and adjust the main tuning parameters directly through the interface.

Observer and IMU Support

The IMU and observer logic are split across imu_driver.py, estimator.py, and task_estimator.py. The IMU driver configures the BNO055 over I2C, loads or saves calibration coefficients, changes operating modes, and provides yaw and yaw-rate measurements.

The observer task reads left and right motor effort, left and right wheel distance, and the IMU yaw signals. It then calls the estimator model to update the state estimate and publishes the estimated traveled distance sHat, heading psiHat, and robot position xR and yR. This gives the navigator more reliable motion information than line sensing alone.

Navigation Logic

The top-level autonomous behavior is implemented in task_navigator.py. This file contains the main finite state machine that decides what the robot should do at each stage of the course. It does not drive the PWM hardware directly. Instead, it sets left and right wheel setpoints that the motor tasks track.

The navigator includes 14 states: wait, CP0 to CP1 line following, box stop, short forward motion, right turn, drive to wall, backup to line, left turn back to line, CP2 to CP3 line following, CP3 to dot search, hit-dot forward motion, turnaround, CP4 to finish, and the finished state. Depending on the state, it uses line following, heading hold, or turning behavior.

For line following, the navigator reads the line centroid and applies proportional-integral steering correction around the base speed. For turning and heading-hold motion, it uses the estimated heading and a heading gain to generate left/right wheel commands. This makes the navigation task the highest-level behavior coordinator in the project.

Bump Detection

Wall contact detection is handled by bump_sensor.py and task_bump.py. The bumper driver configures three right-side and three left-side bumper inputs with pull-ups and debounce timing, then exposes a contact bitmask. The bump task runs periodically, updates the bumper states, publishes that bitmask, and can optionally stop the robot if a bump event occurs.

In the final system, the bump task mainly serves as an event publisher for navigation. When the box-wall section is reached, the navigator watches the bump mask and uses that event to transition into the backup-and-return sequence.

Scheduler and Inter-Task Communication

The cooperative task framework is provided by cotask.py and task_share.py. The scheduler runs each generator-style task in turn, while the share and queue classes provide a safe way to exchange values such as motor commands, sensor outputs, estimated states, and event flags.

Even though the task objects store priorities and periods, the final program uses the round-robin scheduler call task_list.rr_sched() inside the main loop. The bump task is scheduled at 10 ms, while the other five scheduled tasks are each assigned a 20 ms period.

Task Timing Summary

Task Period Main Purpose
Left Motor Task 20 ms Encoder update, velocity error, and left motor effort output
Right Motor Task 20 ms Encoder update, velocity error, and right motor effort output
Bump Task 10 ms Bumper debounce, bitmask generation, and bump-event reporting
Observer Task 20 ms Estimated distance, heading, and robot-position update
Navigator Task 20 ms Top-level FSM for course sequencing and motion commands
User Interface Task 20 ms Calibration, parameter entry, and mission control

Summary

Our final codebase was organized around a modular cooperative-task structure. Low-level files handled hardware such as motors, encoders, bumpers, the line sensor array, and the IMU. Mid-level software handled motor commands and observer-based state estimation. At the highest level, the navigator selected the robot behavior needed for each section of the final course.

This structure made the project easier to debug, easier to tune, and easier to explain. Instead of writing one long control script, the project was divided into small files with clear jobs and well-defined connections through shared variables.