This file is the main integration point for the entire robot. It imports all required modules, initializes the hardware interfaces, creates the shared variables used for inter-task communication, instantiates the task objects, adds them to the cooperative scheduler, and then continuously runs the robot through round-robin task scheduling. In practical terms, main.py is what turns the individual subsystems into one complete autonomous robot.
While the detailed behavior of the robot lives inside separate files such as task_motor.py, task_line.py, task_imu.py, and task_course.py, this file is responsible for wiring everything together. It creates the exact objects used by the system, determines how those tasks communicate, and defines how often each task runs. Because of that, it is one of the most important files in the entire project.
LineSensorArray object is created using eight analog pins and an emitter pin, with symmetric sensor weights from -3.5 to 3.5.line_calib.json.
A major part of main.py is the creation of Shares. These are what allow the independent tasks to communicate without directly reaching into each other’s local variables.
leftMotorGo and rightMotorGo – enable or disable the left and right motor tasksvelSetpoint_L and velSetpoint_R – desired velocity setpoints for the two wheelsKpShare and KiShare – PI gains used by both motor tasksuL_volts and uR_volts – commanded motor voltages for the observersL_meas and sR_meas – measured wheel displacement values used by the observervBaseShare – base forward speed used by the line-following controllerlfEnableShare – enables or disables line-following modecourseEnableShare – master enable flag for the full autonomous course routinebumpEventShare – indicates that a bump event has occurredpsi_meas – measured heading from the IMUpsiDot_meas – measured yaw rate from the IMUs_hat, psi_hat, wL_hat, and wR_hat – observer-estimated forward position, heading, and wheel speedsimuCalReq – request flag for IMU calibrationestStreamEnable – enables estimator streaming output
This file also establishes the startup state of the full robot. The PI gains are initialized to Kp = 0.06 and Ki = 0.6, the line-follow base speed is initialized to 220.0, and all enable flags such as line-following, course mode, estimator streaming, bump event status, and motor-go flags are set to false initially. This ensures the robot powers up in a safe and predictable state rather than immediately starting to move.
After hardware and Shares are initialized, the file creates the main task objects that make up the full software system:
This file adds all major tasks to the cooperative scheduler using the Task class and task_list.append(). The final scheduler configuration uses different priorities and update periods depending on the importance and timing needs of each subsystem:
This priority structure is important. The bump task has the highest priority because safety and immediate collision response matter the most. The motor tasks also run frequently because wheel control needs to be updated quickly. Line following runs slightly slower, and user interaction or garbage collection can run at lower urgency.
At the bottom of the file, the system enters an infinite loop and repeatedly calls task_list.rr_sched(). This is what keeps the cooperative scheduler running. If a KeyboardInterrupt occurs, both motors are disabled before the program exits, which provides a clean and safe shutdown procedure. After exiting, the file prints task and share information for debugging.
This file is critical because it transforms a collection of independent modules into one complete robot. Without main.py, the individual tasks would still exist, but they would not know how to communicate, when to run, or which hardware objects to use. It is the file that determines how the robot boots, how the software system is assembled, and how the runtime behavior is scheduled.
From a systems perspective, main.py demonstrates one of the most important ideas in this project: integration. A robot like this is not just a line sensor, not just an IMU, and not just a motor controller. It is the interaction of all of those subsystems running together. This file captures that system-level design by showing how sensing, estimation, control, event handling, and user interaction are combined into a single autonomous platform.
task_list.append(Task(leftMotorTask.run, name="Left Mot. Task", priority=3, period=5, profile=True))
task_list.append(Task(rightMotorTask.run, name="Right Mot. Task", priority=3, period=5, profile=True))
task_list.append(Task(lineFollowTask.run, name="Line Follow Task", priority=2, period=10, profile=False))
task_list.append(Task(userTask.run, name="User Int. Task", priority=2, period=20, profile=False))
task_list.append(Task(imuTask.run, name="IMU Task", priority=1, period=20, profile=True))
task_list.append(Task(courseTask.run, name="Course Task", priority=4, period=20, profile=False))
task_list.append(Task(bumpTask.run, name="Bump Task", priority=5, period=5, profile=False))
task_list.append(Task(startBtnTask.run, name="Start Button", priority=3, period=20, profile=False))
task_list.append(Task(garbage, name="Garbage", priority=0, period=100, profile=False))
pwm_tim = Timer(3, freq=20000)
leftMotor = motor_driver(Pin.cpu.B1, Pin.cpu.B15, Pin.cpu.B14, pwm_tim, 4)
rightMotor = motor_driver(Pin.cpu.B0, Pin.cpu.B5, Pin.cpu.B4, pwm_tim, 3)
tim_enc_left = Timer(1, period=0xFFFF, prescaler=0)
tim_enc_right = Timer(2, period=0xFFFF, prescaler=0)
leftEncoder = encoder(tim_enc_left, Pin.cpu.A8, Pin.cpu.A9)
rightEncoder = encoder(tim_enc_right, Pin.cpu.A0, Pin.cpu.A1)
leftMotorGo = Share("b", name="Left Mot Go")
rightMotorGo = Share("b", name="Right Mot Go")
velSetpoint_L = Share("f", name="Vel Set L")
velSetpoint_R = Share("f", name="Vel Set R")