LECTURE 11

Case Study: The GT Robot

Duration: 122 min of instruction Level: undergraduate, year II - recommended after Lecture 10 Discipline: Microcontrollers and Microprocessors Associated laboratory: Laboratory 07 PDF: download the material RO versiunea română

The previous lecture described a design method on a small example, with a single sensor and a single power output. A small example has the advantage of clarity, but it hides exactly what makes real design difficult: contention between subsystems for the same microcontroller resources. This lecture takes the same method through a project that has these difficulties in abundance - an autonomous mobile robot built for a student competition, followed from the rulebook to the lessons learned in the field.

1Scope and structure of the lecture6 min

The robot has a four-wheel chassis, of which two motors provide differential drive, four line sensors, two ultrasonic sensors, two optical encoders, an emergency button and a serial link. Almost every module studied in this course comes into play at the same time, and an ATmega328P with twenty usable pins offers fewer resources than the sum of the subsystems demands - design consists, to a large extent, of choosing the trade-offs.

Recap from Lecture 10
  • The method: description, specification, partitioning with interfaces fixed in advance, finite state machine, code in layers tested separately, a test plan linked to requirements (Lecture 10)
  • A resource margin (at least 20%) is the practical rule - we will see what happens when there is none left (Lecture 10)
  • Draw up a resource budget (pins, timers, ADC channels, interrupts) and explain the trade-offs imposed when it saturates
  • Explain why counting encoder pulses is done in an interrupt, while computing speed is done in the main loop
  • Describe the three physical phenomena that must be handled when driving a motor through an H-bridge: dead zone, command jump, emergency stop
  • Calculate the position error of a line-following robot from a proportional controller and explain the limits of the coefficient Kp
  • Recognize why the field-testing level cannot be replaced by the lab

2Context and project theme6 min

The competition setting imposes exactly what is usually missing from teaching projects: a specification written by someone else, which cannot be negotiated, a fixed deadline and a public trial in which the system either works or does not. The organizers publish the rulebook, the track dimensions, the size limits and the mandatory safety components - an emergency stop button reachable from outside, an automatic stop when the link is lost, power from the team's own batteries. All of these go directly into the specification, with no discussion.

The robot must autonomously follow a route marked with tape, go around obstacles, continuously report its own status, and be stoppable at any time from a remote control. What makes the project hard is not the algorithmic part - modest here, a proportional controller and a five-state machine - but fitting within the resources and meeting the timing.

3The robot specification9 min

The competition rulebook is the description of the system, in ordinary language, written by someone who does not design electronics: "the robot must follow the marked route" and "it must avoid collisions" - unverifiable phrasing. The team's first technical act is turning these into a specification with measurable requirements, through questions put to the organizer: how wide is the tape? What is the minimum turning radius? Does the route have intersections? Every answer becomes a requirement.

CodeRequirementAcceptance criterion
CF1Follows the 19mm tape, min. curvature 250mm10 laps with no loss of the route
CF3Obstacle at <250mm: stop within ≤300msmeasuring the stopping distance, 20 repeats
CF5Emergency stop: motors to zero within ≤50msoscilloscope, 20 repeats
CF7Odometry: error under 3% over 2m straight10 trials, compared with a tape measure
CN4No sensor fault produces a maximum commanddisconnecting each sensor in turn

CF4, CN3 and CN4 do not appear anywhere in the rulebook - they come from the list of questions about abnormal situations. CN4 expresses a non-obvious principle: a disconnected sensor produces, at the ADC input, an undefined value, and if the algorithm treats it as valid, the result can be a maximum command on the motor - exactly the dangerous behavior.

The emergency button must not depend only on software

Properly designed, it physically interrupts the power stage supply, through a contact in series with the H-bridge, and only secondarily notifies the microcontroller through a digital input. A button that only sets a bit read by the program does not protect exactly in the case that matters most: the one where the program has hung.

4System partitioning and the resource budget13 min

With the requirements fixed, partitioning follows: line sensors → position; encoders → speeds; ultrasonics → echo; operator interface → button/START/LED; output → PWM and direction to the H-bridge, frames to the USART. Each subsystem delivers a single quantity and completely hides its implementation behind it - if the type of line sensor or the bridge model changes, only a single subsystem gets rewritten.

Missing from the subsystem table: any module using the synchronous buses from Lecture 09. The team initially wanted a display and an inertial sensor on I2C, but both were dropped - the reason becomes clear immediately: there were no pins left.

How to get from the rules to the pin map: put the steps in order

The resource budget

The robot resource budget: demand versus availability

The Arduino Uno board offers twenty input-output pins. The budget is organized into four chapters: pins, timers, analog channels, interrupt sources.

PinDirectionSignal
PD0, PD1USARTRemote control commands / log
PD2, PD3InputINT0, INT1 - left/right encoders
PD4, PD7OutputLeft/right motor direction
PB1, PB2OutputOC1A, OC1B - motor PWM
PB0, PB3InputPCINT0/3 - ultrasonic echo
PC0...PC3AnalogThe four line sensors
PC4, PC5Analog / InputBattery voltage / emergency button
Match each robot subsystem to the microcontroller module it relies on

No pin was left free. Consequences, all conscious decisions: (1) four line sensors, not six - position is known more coarsely, the robot cuts corners at high speed. (2) I2C disappears - PC4/PC5 were given to the voltage sensor and the emergency button. (3) there is no free test pin left - the compromise solution was reusing the status LED pin, at the cost of an LED that blinks erratically during measurements.

A second conflict: two functions, a single USART The requirements call for a debugging console (receives the log) and a remote control (sends commands), but the ATmega328P has a single USART. A second, software USART would require disabling interrupts for the duration of every character, breaking odometry. The chosen solution: the two functions share the same lines - the remote control on RXD, the log on TXD, listened to only in the lab. Nothing is lost, because nobody reads the console during the trial.

5Timers, analog channels and interrupts10 min

Timer1 generates the two PWM signals for the motors, in 8-bit fast PWM mode, prescaling 8: the resulting frequency is 16,000,000/(8·256) = 7812.5 Hz, above the audible range, which removes the motor whine. Timer2 produces the 10 ms tick of the loop, in CTC mode, prescaling 1024, compare value 155: the period is 1024·156/16,000,000 = 9.98 ms. Timer0 is left to the microsecond time base, for measuring echoes.

Why Timer1's capture module was not used for the echoes

The datasheet recommends capture on ICP1 for precise measurement of a pulse width - but here Timer1 is already occupied by 8-bit PWM, its counter rolls over every 128 µs, while an echo can last up to 17.5 ms. The accepted compromise: reading the microsecond time base on the two edges, 4 µs resolution. Sound travels round-trip about 1cm in 58 µs, so 4 µs means under a millimeter - well under the sensor's own error. The compromise rests on a calculation, not an impression.

The analog-to-digital converter has six accessible channels - four for the line sensors, one for the battery voltage. At a prescaling of 128, the ADC clock is 125 kHz, one conversion takes 13 periods (104 µs); the five conversions in one cycle consume about 520 µs, over 5% of the 10 ms period - not much, but not negligible either.

Interrupt sources used: INT0/INT1 (encoders), the pin-change vector of port B (echoes), Timer2's compare match (the system tick), USART reception (commands). The pin-change vector is shared by an entire port - the routine must figure out which pin changed, by comparing the current value with the stored one, and it fires on both edges, which is useful here, because we need both.

Worked exercise - the load produced by counting the encoders

The wheels have an 80mm diameter, the encoder discs 60 slots, the designed maximum speed 0.6 m/s. The ISR takes measured about 4 µs, including context saving.

See the solution

Wheel circumference: π·80 = 251.3 mm; with 60 slots, one pulse every 251.3/60 = 4.19 mm. At 600 mm/s, one wheel produces 600/4.19 = 143 pulses/s. With two wheels and counting on a single edge: 286 interrupts/s, so 286·4 µs = 1.14 ms of processor time per second, that is 0.11%. Even doubling the resolution by counting both edges, the load stays under a quarter of a percent. Counting in the interrupt is practically free - which justifies choosing it over polling.

6Program architecture and the state machine13 min

The architecture is the hybrid one from Lecture 10: interrupts capture the fast events, a main loop paced by a 10 ms tick makes the decisions. The golden rule: nothing slow is allowed into the loop. If the loop waited synchronously for a sensor's echo, it would stall for up to 17.5 ms - more than the entire tick period; the robot would drive blind and run off the route in a curve. That is why the echo is measured through interrupts, and the log is deposited into a circular buffer. Measured with the oscilloscope, the processing pulse width was about 1.1 ms - 11% of the period, well under the 50% limit required by CN1, a margin that later allowed odometry to be added.

The state machine

StateEventNext state
WAITINGSTARTLINE FOLLOWING
LINE FOLLOWINGobstacle detectedAVOIDING
LINE FOLLOWINGline below thresholdLINE LOST
AVOIDINGavoidance finishedLINE FOLLOWING
LINE LOST2s with no lineWAITING
any stateemergency button / STOPEMERGENCY
Three decisions hidden in the diagram No direct transition AVOIDING→LINE LOST: during avoidance, the absence of the line is normal. No direct transition LINE LOST→AVOIDING: without knowing where the route is, a maneuver relative to it makes no sense. Leaving EMERGENCY is not automatic when the button is released, but requires an explicit action, so that an imperfect contact does not restart the motors. Each is a safety requirement translated into the absence of a transition.
volatile uint8_t tickFlag = 0;   /* raised by Timer2's ISR */

int main(void) {
    motorsStopImmediately();      /* safe state BEFORE configuration */
    portsInit(); pwmInit(); tickInit(); encodersInit();
    ultrasonicInit(); adcInit(); usartInit(38400);
    sei();

    for (;;) {
        if (!tickFlag) continue;      /* waiting for the tick; no active delay */
        tickFlag = 0;

        odometryUpdate(); lineSensorsRead();
        ultrasonicSchedule();  powerCheck();

        if (emergencyButton() || stopCommand()) {
            motorsStopImmediately(); state = ST_EMERGENCY;
        }

        switch (state) {
        case ST_FOLLOWING:
            if (frontDistance() < D_OBSTACLE_MM) { avoidanceStart(); state = ST_AVOIDING; }
            else if (!lineVisible()) { timerStart(&tSearch, 2000); state = ST_LINE_LOST; }
            else lineController();
            break;
        /* ... the other cases, one per state ... */
        default:                    /* impossible state: preventive stop */
            motorsStopImmediately(); state = ST_EMERGENCY; break;
        }
        logFillBuffer();        /* deposits bytes, does not wait for transmission */
    }
}

The emergency conditions are checked before the switch statement, so they apply from any state, and the default branch leads the system into the safe state - if the state variable ends up, for whatever reason, at an impossible value, the robot stops.

7Motor control: dead zone, ramp, emergency stop10 min

DC motors are driven with PWM, not with a variable continuous voltage - the winding inductance smooths the current, the motor behaves as if it received the average voltage, and the switching element dissipates little. Direction is obtained by reversing polarity through an H-bridge. The absolute rule: the two arms of the same branch never conduct at the same time, otherwise it is a direct short-circuit across the battery.

Three physical phenomena must be handled explicitly. The dead zone: below a certain duty cycle, the torque does not overcome the static friction in the gearbox, the motor stays still but draws current. Left uncompensated, the controller produces, for small errors, commands that never turn into any movement. Compensation compresses the useful range of the command over the interval between the measured threshold and the maximum value. The command jump: an abrupt transition from zero to maximum draws a current limited only by the winding resistance - the battery voltage sags (which can reset the microcontroller), the wheels slip (which breaks odometry). The solution: a ramp, limiting the command's variation per cycle; with a step of 8 units per 10 ms, the transition 0→255 takes 319 ms, imperceptible but enough to remove the current spike.

The emergency stop deliberately bypasses the ramp

CF5 requires zero within ≤50 ms; the ramp would need over 300 ms. The immediate-stop function writes zero directly into the compare registers, with no rate limiting - an exception justified by safety, which must be written as a comment, otherwise a later reader "fixes" it.

#define PWM_MIN 40      /* measured: below this threshold the motor does not turn */
#define RAMP_STEP 8      /* maximum command variation in a 10 ms tick */

/* Compresses the useful range 1..255 over PWM_MIN..PWM_MAX */
static uint8_t pwmFromCommand(int16_t magnitude) {
    if (magnitude <= 0) return 0;
    if (magnitude > 255) magnitude = 255;
    return (uint8_t)(PWM_MIN + ((uint32_t)magnitude * (PWM_MAX - PWM_MIN)) / 255);
}

static int16_t applyRamp(int16_t current, int16_t desired) {
    if (desired > current + RAMP_STEP) return current + RAMP_STEP;
    if (desired < current - RAMP_STEP) return current - RAMP_STEP;
    return desired;
}

/* Emergency stop: DELIBERATELY bypasses the ramp, for the 50 ms requirement. */
void motorsStopImmediately(void) {
    OCR1A = 0; OCR1B = 0;
    appliedCmdLeft = 0; appliedCmdRight = 0;
}

8Odometry from encoders10 min

Each drive wheel carries a slotted disc, read by an LED-phototransistor pair. With a 60-slot disc and an 80 mm wheel, one edge corresponds to 4.19 mm of travel.

Counting happens in the interrupt, the calculation in the main loop By polling, the program would have to read the pin more often than a pulse can occur - at 0.6 m/s, pulses arrive about 7 ms apart, a 10 ms loop would miss most of them. Symmetrically, the calculation (conversion to mm, dividing by time, linear and angular speed) has no business being in the interrupt - a floating-point division there would take hundreds of microseconds, delaying every other interrupt, including the one for the other wheel.

A single-channel encoder cannot distinguish the direction of rotation. The rigorous solution - a quadrature encoder - would need two more interrupt inputs, which we do not have. The compromise solution: the routine adds to the counter using the sign of the command given to the motor, assuming the wheel turns the way it was told to. The assumption is false when braking (inertia carries the wheel further) and when a wheel is blocked and pushed backward - both introduce odometry errors, accepted as the price of saving pins.

volatile int32_t pulseCountLeft = 0, pulseCountRight = 0;
volatile int8_t directionLeft = +1, directionRight = +1;   /* written by motorsCommand(), read by the ISR */

ISR(INT0_vect) { pulseCountLeft += directionLeft; }    /* rising edge, left encoder */
ISR(INT1_vect) { pulseCountRight += directionRight; }

/* The counter is 4 bytes; the read is protected, but the previous state of the I
   bit is restored, so we do not enable interrupts in a section where they were already off. */
int32_t encoderRead(volatile int32_t *pCounter) {
    int32_t v; uint8_t sreg = SREG;
    cli(); v = *pCounter; SREG = sreg;
    return v;
}
Odometry: resolution, speed and quantization error
Worked exercise - speed from pulses and the measurement window

In a 10 ms cycle, the counters increase by 12 pulses (left) and 10 (right). The robot's wheel track is 150 mm. Find the speeds and the quantization error.

See the solution

Directly on the 10 ms window: 12·4.19/0.01 = 5030 mm/s - absurd for a small robot. The reason: at 0.5 m/s, in 10 ms only about 1 pulse per wheel arrives, the result is dominated by quantization. Redone on a 100 ms window, with the same 12 and 10 pulses: vl=503 mm/s, vr=419 mm/s, so v=(503+419)/2=461 mm/s and ω=(419−503)/150=−0.56 rad/s (turning to the right). Quantization introduces an uncertainty of one pulse, that is 4.19/0.1=41.9 mm/s, about 8% of the speed. Practical conclusion: the calculation window must be longer than the control cycle - the counters are read every 10 ms, but speed is computed over the last ten readings, with a 100 ms delay that is acceptable, because speed is used for reporting, not for line following.

9Line following: the proportional controller11 min

The four line sensors are LED-phototransistor pairs facing the floor. The first mandatory operation is normalization: for each sensor, a calibration on the track before the trial records the value over open floor and over the tape, and the read value is brought, through linear interpolation, onto a common 0-100 scale. Without it, differences between individual phototransistors would make the sensors incomparable, and the computed position would be systematically shifted.

The line position is a weighted average: each sensor is assigned a conventional position (−3, −1, +1, +3), and the error is the center of mass of the normalized responses. The sum of the responses serves a double purpose - if it is below a threshold, no sensor sees the tape, the line is lost (the concrete implementation of requirement CN4).

#define KP_LINE 18          /* command units per unit of error */
#define LINE_THRESHOLD 40         /* normalized sum below which the line is lost */
static const int8_t position[4] = { -3, -1, +1, +3 };

int16_t lineError(const uint8_t n[4], int16_t *pSum) {
    int16_t sum = 0; int32_t moment = 0;
    for (uint8_t i = 0; i < 4; i++) { sum += n[i]; moment += (int32_t)position[i] * n[i]; }
    *pSum = sum;
    if (sum < LINE_THRESHOLD) return 0;             /* undefined error: line is lost */
    return (int16_t)(moment * 100 / sum);
}

void lineController(void) {
    int16_t sum, e = lineError(nSensor, &sum);
    int16_t u = (int16_t)(((int32_t)KP_LINE * e) / 100);
    motorsCommand(BASE_SPEED + u, BASE_SPEED - u);
}

The correction is proportional to the error, added to one wheel's command and subtracted from the other's. With Kp too small, the robot reacts sluggishly, cutting corners on the inside; with Kp too large, it oscillates with growing amplitude around the line. The upper limit of Kp is set by the total delay in the loop (the cycle period, the motors' time constant, the robot's inertia).

Worked exercise - the position error and the resulting commands

The normalized sensors read 5, 20, 90, 35 (left→right). Base speed 120, Kp=18.

See the solution

Sum = 5+20+90+35 = 150 (above the threshold of 40, the line is visible). Moment = (−3·5)+(−1·20)+(1·90)+(3·35) = −15−20+90+105 = 160. The error (hundredths) = 160·100/150 = 106, that is 1.06 units to the right. Correction u = 18·106/100 = 19. Commands: left 120+19=139, right 120−19=101 - the robot steers to the right, toward the tape. The maximum possible error is ±3 units (±300 hundredths), so u ∈ [−54, 54], and the commands stay within [66, 174] - they never saturate at 255, a good property: if it saturated at maximum error, the robot would lose the ability to correct further, exactly in the toughest situation. All calculations used integer arithmetic, with an explicit scale factor of 100 - on a microcontroller with no floating point, this is not a matter of style, but the difference between a few microseconds and a few hundred.

10The test plan, with a field level8 min

The plan follows the four levels from Lecture 10, with one peculiarity: the last level cannot be done in the lab. A robot that is flawless on the workbench can fail on the track for reasons that have nothing to do with the program - the room's lighting, the floor's grip.

Module level: the pure functions (position error, dead-zone compression, the ramp) compiled for the host computer, tried at their limits. Subsystem level: wheels lifted off the ground, an increasing command to find the dead-zone threshold; ten manual turns to check 600 encoder pulses. Integration level: with the motors running, do the encoders still count correctly, or do bridge disturbances produce spurious edges? Field level: complete scenarios on the track.

TestRequirementHow it is performedCriterion
T2-wheels lifted, increasing commanddead-zone threshold identical ±5 on both wheels
T6-motors at maximum command, wheels blockedcounters do not increase from spurious edges
T8CF5emergency button at maximum speed, 20 repeatsbridge command to zero in <50ms
T11-random characters on the serial interfaceinvalid frames ignored, no lockup
Tests with no associated requirement come from atypical testing T2, T6, T11, T12 have no requirement code. If such a test uncovers a bug, the fix is made not only in the code, but also in the specification. T6 especially: if it fails, the remedy is not in software, but in the wiring - separating power traces from signal traces, twisting the motor wires, decoupling capacitors at the terminals.

11What worked and what did not8 min

A case study that reports only successes is of no use - the mistakes repeat with regularity in every generation of teams.

What worked was whatever had been decided on paper before it was built: partitioning with written interfaces allowed the traction module to be replaced halfway through the project with no change anywhere else in the program; the state machine gave defined behavior to unforeseen situations; the rule "nothing slow in the loop" never had to be reconsidered.

What did not work was whatever had been decided on the fly. The first mistake: calibrating the sensors with values compiled into the program - at the competition, the lighting and the floor were different, the robot could no longer see the tape at all. The remedy: a calibration started through the start button, with the robot walked manually over the tape. The second: sharing the same supply between the electronics and the motors - under sudden acceleration, the voltage sagged enough that the microcontroller reset in the middle of the track. The fixes: a large capacitor at the regulator input, the acceleration ramp, and a voltage supervisor circuit. The third: trusting odometry as a source of absolute position - errors accumulate, and after a few meters the estimated position drifts visibly away from the real one; useful for short maneuvers, not for long-term navigation without periodic correction.

All three mistakes share one element

They were discovered in the move from the lab to the real environment - exactly the testing level teams schedule last and cut first when time runs out. The practical conclusion: get onto the real track as early as possible, with an incomplete robot, not late with a finished one.

Nothing that made this project possible was new knowledge about the ATmega328P - the timer registers, the interrupts, the ADC chain were all already known from earlier lectures. What decided the outcome was the order of decisions, the discipline of writing interfaces before implementations, and the habit of verifying by measurement, not by assumption - the difference between knowing how to program a microcontroller and knowing how to design a system with a microcontroller.

12Common mistakes4 min

  • We calibrate the sensors once, in the lab, and hard-code the thresholds. The lighting and floor color at the competition differ from the lab - the robot no longer sees the tape at all. Calibration must be done on site, through a procedure started on site (a dedicated button), never assumed from compiled values.
  • The electronics and the motors are powered from the same battery, to keep things simple. The current spike under sudden acceleration makes the voltage sag enough that the microcontroller resets. A large capacitor at the regulator input, an acceleration ramp, and a voltage supervisor circuit, applied together.
  • Odometry gives us the robot's position, so we can use it for long-distance navigation. Quantization, wheel slip and the assumed (not measured) direction of the wheels accumulate - the error grows without bound with distance. Odometry is useful only for short maneuvers, where the error has no time to build up; long distances need an independent periodic correction.

13Summary and glossary5 min

A complete project shows that the method from Lecture 10 behaves the same way under the pressure of insufficient resources as on the small example: interfaces written in advance allow parallel development and the replacement of a subsystem without breaking the rest; the resource budget (pins, timers, ADC channels, interrupts) turns "this microcontroller should be enough" into a verifiable list, and its saturation forces explicit, documented trade-offs rather than accidental ones; the state machine makes dangerous combinations impossible by construction; the rule "nothing slow in the loop" is checked by measurement with an oscilloscope, not by assumption; motor control requires explicitly handling the dead zone, the command jump, and the emergency-stop exception; odometry counts in an interrupt and computes in the loop, over a window long enough to dominate quantization noise; line following is a simple proportional controller, but with mandatory normalization and Kp limited by the loop's total delay; and the test plan adds, compared to lab projects, a field level that cannot be replaced. The final lesson has nothing to do with registers, but with discipline: interfaces before implementation, measurement before assumption.

Resource budget
a list of the resources demanded by the subsystems, compared explicitly with what the chosen microcontroller offers.
Dead zone
the command range below which the motor draws current but does not turn, due to static friction.
Acceleration ramp
limiting the command's variation per cycle, to remove the current spike at abrupt jumps.
Normalization
bringing the readings of different sensors onto a common scale, through calibration at the two extremes.
Field testing level
the last level of the test plan, which exercises real conditions that cannot be reproduced in the lab.

14Review questions7 min

  1. Turn the requirement "the robot must avoid collisions" into two or three verifiable requirements, with numeric values and a measurement method.
  2. Explain why the emergency stop button must not act only through software.
  3. Why could Timer1's capture module not be used to measure the echo?
  4. A colleague moves the speed calculation from the main loop into the encoder ISR, "to keep it more current". What breaks?
  5. What is the dead zone of a geared motor, and what behavior does it produce, if uncompensated, in a line-following robot?
  6. Describe the procedure for calibrating the line sensors on the track and explain why values compiled into the program cannot replace it.

15Closing the discipline2 min

This is the last lecture of the Microcontrollers and Microprocessors discipline. The whole journey - from the bits and registers of an isolated microcontroller to an autonomous system that must work correctly on the first try, on an unknown track - used, at every step, the same principles: measure, do not assume; write the interface before the implementation; test in layers.

The integrated application of all of these notions - acquisition, decision, control, with no delay() - is practiced in Laboratory 07, the discipline's final lab session.