Source Code
Software organization, major modules, and repository links
Code / Software Architecture
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 filetask_user.py
USB serial user interface for calibration, mission start/stop, and runtime tuning of base speed, line gains, and motor gains.
View filetask_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 filetask_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 filetask_estimator.py
Observer task that combines wheel motion and IMU heading information to estimate traveled distance, heading, and robot position.
View filelinesensor_driver.py
Seven-channel reflectance sensor driver with white/black calibration, line visibility, dot counting, and centroid calculation for steering.
View filemotor_driver.py
Low-level H-bridge motor driver with PWM effort, direction control, and enable/disable functionality for each Romi motor.
View fileencoder.py
Quadrature encoder driver that updates wheel position and computes wheel velocity from timer-based encoder counts.
View fileimu_driver.py
BNO055 IMU interface over I2C, including fusion-mode changes, calibration status, stored calibration loading/saving, and yaw/yaw-rate access.
View filebump_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 filecotask.py / task_share.py
Cooperative scheduler, task definitions, shared-variable objects, and queue structures used throughout the project.
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.