Scherz & Monk, Chapter 13

Microcontrollers

A microcontroller is a whole computer on one chip — CPU, memory, timers, and I/O. The pivotal idea of this chapter: the hardware design problem moves into software. Instead of wiring a 555, counters, and gates, you wire a few parts to I/O pins and write a program. Four ways a chip talks to the world — digital I/O, analog input, PWM output, and serial — structure everything.

Prerequisites: digital logic & 5V levels (Ch 12) + Ohm's law & dividers + basic if/else
17
Chapters
5
Simulations
0
Assumed Knowledge

Chapter 0: The Crash Problem

You want to build a small robot that wanders around a room and never crashes into anything. It has two infrared sensors up front to "see" obstacles, two servo-driven wheels to "walk," and a piezo buzzer to "talk." The behavior you want sounds simple: if something's on the left, turn right; if something's dead ahead, back up; otherwise drive forward.

Try to build that out of pure hardware and you hit a wall fast. You could feed each IR sensor into a comparator, AND/OR the comparator outputs together with logic gates, and wire a 555 timer to generate the servo pulses. But the moment the rule gets more interesting — "back up, then turn, then beep twice" — you are wiring a sequence of decisions over time. That is a flowchart. A flowchart is a program, not a circuit.

This is the moment a microcontroller earns its keep. A microcontroller is a complete tiny computer baked onto a single chip: a CPU to run instructions, flash memory to hold your program, RAM for scratch variables, and a set of I/O pins that connect to the outside world. A chip costing about a dollar reads the two sensor pins, runs the if/else logic in software, and writes pulse trains out to the servos. The hardware shrinks to "a few parts wired to pins"; the cleverness lives in code you can change without touching a soldering iron.

The hardware-only approach
2 comparators + 4 logic gates + a 555 + flip-flops to sequence the moves. Every behavior change means rewiring. Adding "beep twice when stuck" means more chips.
↓ replace with ↓
The microcontroller approach
2 sensors → input pins. 2 servos → output pins. Buzzer → one pin. The whole decision tree is an if/else in a loop(). Change behavior = re-flash, no rewiring.

Here is the decision tree, written the way you'd actually write it for an Arduino. Notice it reads exactly like the English rule above:

void loop() {
  bool leftBlocked  = (digitalRead(LEFT_IR)  == LOW);  // IR sees obstacle
  bool rightBlocked = (digitalRead(RIGHT_IR) == LOW);

  if (leftBlocked && rightBlocked) {
    backUp();          // dead ahead: reverse
    tone(BUZZER, 880, 100);
  } else if (leftBlocked) {
    turnRight();       // something left: steer right
  } else if (rightBlocked) {
    turnLeft();
  } else {
    driveForward();    // clear road
  }
}

That is the entire intelligence of the robot. The rest of this chapter is about the seams — the four ways the chip touches the physical world, and the handful of interface gotchas (pull-up resistors, the ~20 mA-per-pin current limit, inverted switch logic, 5V vs 3.3V) that trip up every beginner.

The big move. A microcontroller doesn't make new things possible — you could always build a wandering robot from discrete logic. It makes them cheap and changeable. The design problem migrates from "what gates do I wire?" to "what program do I write?" One chip plus software replaces a board full of timers, counters, and gates. That migration is the single most important idea in this chapter.
The Wandering Robot's Decision Tree

Toggle the two front IR sensors to "blocked" and watch which branch of the program fires and what the robot does. This whole logic block is software running on a $1 chip — no gates required.

Why is a microcontroller a better fit than a 555 + gates for the obstacle-avoiding robot?

Chapter 1: A Computer on a Chip

Open up a desktop computer and you find separate chips: a CPU, sticks of RAM, a hard drive, a graphics card, a clock generator. A microcontroller takes the essential subset of all of that and fuses it onto one piece of silicon a few millimeters across. That integration is the whole point — it is a self-contained computer that needs almost nothing external to run.

Every microcontroller has the same functional blocks. The CPU fetches and executes instructions one after another, driven by a clock running at tens of megahertz. Flash (ROM) holds your program permanently, even with the power off. RAM (SRAM) holds variables while the program runs, and is wiped on power-down. EEPROM is a small slab of non-volatile memory for settings you want to survive a reset.

Then come the parts that make it a controller rather than just a calculator. I/O ports are pins the CPU can read as digital inputs or drive as digital outputs. Timers/counters measure time and generate precise pulses without the CPU babysitting them. Interrupts let external events yank the CPU's attention instantly. A UART handles serial communication. An ADC turns analog voltages into numbers, and sometimes a DAC goes the other way. All on one chip, all clocked together.

BlockWhat it doesRobot example
CPURuns instructions, does the if/else logicDecides turn vs back-up
Flash / ROMStores the program (non-volatile)Holds the avoidance sketch
RAM / SRAMScratch variables (volatile)Sensor readings, counters
I/O portsDigital read/write pinsRead IR, write buzzer
TimerPrecise timing / pulse generationServo pulses, millis()
InterruptsInstant response to eventsBumper-switch hit
ADCAnalog voltage → numberRead a TMP36 temp sensor
UARTSerial Tx/RxDebug printf to a PC

The four "talking to the world" channels deserve special emphasis, because the rest of this chapter is organized around them. They are: digital I/O (Ch 3), analog input via the ADC (Ch 4), PWM output (Ch 5), and serial communication (Ch 8). Master those four and you can interface a microcontroller to almost anything.

Why "controller," not "computer"? A desktop CPU optimizes for raw speed and runs an operating system. A microcontroller optimizes for integration and determinism: it boots in microseconds, has no OS, and its pins connect straight to motors, LEDs, and sensors. It trades gigahertz for the ability to flip a physical pin HIGH at a precisely known instant. That predictability is exactly what controlling hardware demands.

Anatomy of a Microcontroller

Click any block to highlight it and read what it does. The CPU sits on a shared internal bus connecting it to memory and every peripheral.

Which microcontroller block keeps your program after the power is removed?

Chapter 2: Meet the Chips

The book anchors everything to two concrete chips so the abstractions stay grounded. Both are 8-bit AVR microcontrollers from the same family, running 5V logic at tens of megahertz. They sit at opposite ends of a size/capability spectrum.

The ATmega328 (Arduino Uno's brain)

The ATmega328 is the chip on an Arduino Uno. It has 32 KB of flash for your program, 2 KB of SRAM for variables, and 1 KB of EEPROM for settings, clocked at 16 MHz. It exposes 14 digital I/O pins and 6 analog input pins, plus a hardware UART. Six of those digital pins (3, 5, 6, 9, 10, 11) can do PWM. It is the workhorse of hobby electronics.

The ATtiny85 (the tiny one)

The ATtiny85 is an 8-pin chip the size of a grain of rice, with 8 KB of flash, runs from 2.7–5.5V, and costs about $1. After you subtract power and ground, it gives you about five usable I/O pins. When your project is "blink an LED on a moisture reading" you don't need the Uno's bulk — the ATtiny does it for a dollar and a fingernail of board space.

SpecATmega328 (Uno)ATtiny85
Architecture8-bit AVR8-bit AVR
Flash32 KB8 KB
SRAM2 KB512 B
EEPROM1 KB512 B
Clock16 MHz~8–16 MHz
I/O pins14 digital + 6 analog~5
Supply5V (3.3V variants exist)2.7–5.5V
Cost~$2 (chip)~$1

Worked example — will the program fit?

Suppose your robot sketch compiles to 6.2 KB of machine code and declares about 400 bytes of global variables (sensor arrays, state flags). On the ATmega328: program uses 6.2 KB of 32 KB flash = 19% of flash; variables use 400 B of 2 KB SRAM = 20% of RAM. Comfortable fit. On the ATtiny85: 6.2 KB of 8 KB flash = 78% of flash — tight but possible — while 400 B of only 512 B SRAM = 78% of RAM, dangerously close to overflow. Run out of SRAM and the stack collides with your variables and the chip crashes mysteriously.

SRAM is the scarce resource. Beginners watch flash usage and ignore RAM, but on small chips it's RAM that bites. Every String, every large array, every long text literal printed over serial eats SRAM. A 100-character debug message you forgot to wrap in F() can be the byte that tips a tight ATtiny program into a crash. Flash is roomy; SRAM is precious.
Memory Budget: Will It Fit?

Set your program and variable sizes, then pick a chip. The bars show flash and SRAM usage; a red bar means you've overflowed.

Program size (KB) 6.2
Variables (bytes) 400
A sketch needs 1.5 KB of SRAM for its variables. Which chip can run it?

Chapter 3: Digital Inputs & Pull-ups

Reading a switch sounds trivial: wire a button to a pin and call digitalRead(). But there's a trap that catches everyone the first time. A microcontroller input pin reads HIGH when its voltage is near Vcc and LOW when it's near ground. The question is: what voltage is on the pin when the button is not pressed?

If the pin connects only to a button that goes to ground, then with the button open the pin connects to nothing. It is floating — its voltage drifts on stray noise, reading HIGH then LOW at random. The fix is a pull-up resistor: a resistor from the pin to Vcc that gently holds the pin HIGH when nothing else is driving it. Press the button and you connect the pin to ground, which wins (it's a hard short), pulling the pin LOW.

This produces the famous inverted logic: with a switch-to-ground and a pull-up, the pin reads HIGH when the button is released and LOW when pressed. Your code asks "is it LOW?" to detect a press. Microcontrollers include built-in internal pull-ups (20–40 kΩ on AVR) you enable in software with pinMode(pin, INPUT_PULLUP) — no external resistor needed.

pinMode(BUTTON, INPUT_PULLUP);   // enable internal ~30k pull-up
...
if (digitalRead(BUTTON) == LOW) {   // LOW = pressed (inverted!)
  doSomething();
}

Worked example — pull-up current

While the button is pressed, current flows from Vcc through the pull-up resistor straight to ground. That current is wasted as heat, so resistor choice is a trade-off between noise immunity (smaller R = stiffer pull, more immune) and wasted power (smaller R = more current).

I = Vcc / R

With a 5V supply: a 1 kΩ pull-up draws I = 5 V / 1000 Ω = 5 mA while pressed. A 10 kΩ pull-up draws I = 5 V / 10000 Ω = 0.5 mA — ten times less waste. Go too small, say 270 Ω, and I = 5 V / 270 Ω ≈ 18.5 mA, which is near the ~20 mA-per-pin sink limit — wasteful and risky. The 10 kΩ value is the usual sweet spot, and the internal 20–40 kΩ pull-ups push waste even lower (~0.15 mA).

Floating inputs lie. The single most common beginner bug is reading a pin with no pull-up (or pull-down) and getting random results — the switch "works sometimes." A floating CMOS input is a tiny antenna picking up mains hum and your hand's capacitance. Always define the resting state with a pull-up or pull-down. Internal pull-ups make this one line of code: INPUT_PULLUP.
Pull-up + Digital Read: Switch to Ground

A button wired switch-to-ground with a selectable pull-up to 5V. Click the button to press/release it. Watch the pin voltage, the HIGH/LOW reading (inverted!), and the live current through the resistor while pressed.

A normally-open button is wired to ground with a 10 kΩ pull-up to 5V. When you press it, what does the pin read and how much current flows through the resistor?

Chapter 4: Analog Inputs — the ADC

A digital pin only knows two states. But the world is analog: a temperature sensor outputs a smoothly varying voltage, a potentiometer sweeps continuously, a light sensor fades. To read these, the microcontroller uses an analog-to-digital converter (ADC) — a circuit that measures a voltage and returns an integer.

The Arduino's ADC is 10-bit, meaning it carves the range from 0V to a reference voltage (Vref, normally 5V) into 210 = 1024 levels, numbered 0 to 1023. A reading of 0 means 0V; a reading of 1023 means ~5V. Everything in between maps linearly. analogRead(pin) returns that integer.

count = round( Vin / Vref × (2n − 1) ) = round( Vin / 5 × 1023 )

The inverse — turning a count back into a voltage — is what you use to interpret a reading in your program:

Vin = count / 1023 × 5 V

The smallest voltage step the ADC can resolve, called 1 LSB (least significant bit), is the whole range divided by the number of steps: resolution = 5 V / 1023 ≈ 4.89 mV. Voltages closer together than that round to the same count — the ADC literally cannot tell them apart. This is quantization, and it's why analog readings come in a staircase, not a smooth ramp.

Worked example — reading a TMP36

A TMP36 temperature sensor outputs a voltage proportional to temperature. Suppose it sits at 0.75 V. The ADC returns:

count = round( 0.75 / 5 × 1023 ) = round( 0.15 × 1023 ) = round( 153.45 ) = 153

If the sensor drops slightly to 0.71 V: count = round(0.71 / 5 × 1023) = round(145.27) = 145. So a 0.04 V change moved the count by 8 steps — consistent with 1 step ≈ 4.89 mV (since 0.04 V / 4.89 mV ≈ 8). Going the other way, a reading of 512 means Vin = 512/1023 × 5 ≈ 2.50 V, exactly mid-scale.

More bits = finer steps, not a bigger range. A 10-bit ADC and a 12-bit ADC reading the same 0–5V range cover the same voltages — the 12-bit one just slices them into 4096 steps (1.22 mV each) instead of 1024 (4.89 mV). Resolution is about how finely you can distinguish nearby voltages, not how high you can read. And lowering Vref (e.g. to the internal 1.1V reference) also shrinks the step size, trading range for precision.
10-bit ADC Quantizer

Sweep the input voltage from 0 to 5V. The smooth analog input (orange dot) snaps to the nearest of 1024 quantization levels (the staircase). Read off the integer count and the reconstructed voltage.

Vin (volts) 0.750
On a 10-bit, 5V Arduino ADC, analogRead() returns 512. What voltage is on the pin?

Chapter 5: PWM Outputs — Fake Analog

You want to dim an LED or slow a motor. But a digital output pin only does two things: 5V or 0V. There is no "2.5V" setting. So how does an Arduino produce a continuous-looking brightness or speed? The answer is one of the most useful tricks in all of electronics: pulse-width modulation (PWM).

Instead of holding the pin at a steady in-between voltage, PWM flips it fully HIGH and fully LOW very fast — hundreds or thousands of times a second — and varies the fraction of time it spends HIGH. That fraction is the duty cycle D. At 50% duty the pin is HIGH half the time; at 25% it's HIGH a quarter of the time. The LED or motor can't react fast enough to follow each pulse, so it responds to the average.

Vavg = D × Vcc

On the Arduino you set PWM with analogWrite(pin, x) where x ranges 0 to 255 (8-bit). The duty cycle is D = x / 255. So analogWrite(pin, 0) is fully off, analogWrite(pin, 255) is fully on, and values between scale the average linearly. Only certain pins (3, 5, 6, 9, 10, 11 on the Uno) have the hardware timer support to do PWM.

analogWrite(9, 102);   // D = 102/255 = 0.40 -> ~2.0V average
analogWrite(9, 127);   // D = 127/255 = 0.498 -> ~2.49V (half brightness)
analogWrite(9, 0);     // fully off
analogWrite(9, 255);   // fully on

Worked example — hit a 2.0V average

You want a 2.0V average from a 5V supply to half-dim an LED. Solve for duty cycle:

D = Vavg / Vcc = 2.0 / 5 = 0.40 = 40%

Convert that duty cycle to an analogWrite value: x = D × 255 = 0.40 × 255 = 102. So analogWrite(pin, 102) gives a 40% duty cycle and a 2.0V average. Going the other way: analogWrite(pin, 127) gives D = 127/255 = 0.498, so Vavg = 0.498 × 5 ≈ 2.49 V — very nearly half-scale, the natural "half brightness" setting.

The motor doesn't care that it's a square wave. A DC motor is a big inductor with mechanical inertia. Pulse it at a few kHz and the inductance plus the spinning mass smooth the chopped voltage into a steady torque proportional to the duty cycle. Same for an LED: at PWM frequencies above ~100 Hz your eye fuses the flicker into a steady brightness set by D. PWM is "analog control with digital parts" — no DAC, no linear regulator, almost no wasted heat.
★ SHOWCASE: PWM → Brightness & Speed

Drag the duty cycle. The square wave on top shows the pin flipping HIGH/LOW. The LED glows and the motor spins at a rate tracking the average Vavg = D×5V. The matching analogWrite value is computed live. Press Animate to watch the waveform scroll.

Duty cycle D 40%
On a 5V Arduino, analogWrite(9, 64) produces approximately what duty cycle and average voltage?

Chapter 6: Driving Real Loads

An output pin can flip 5V, but it cannot deliver much current. An AVR pin reliably sources or sinks about 20 mA, and the whole chip is limited to roughly 200 mA total. An LED with a series resistor (~10 mA) is fine straight off a pin. A motor, a relay, or a string of LEDs is not — ask a pin for 100 mA and you'll brown out or destroy it.

The solution is to use the pin as a control signal and let a beefier component carry the load current. A transistor (BJT or MOSFET) acts as a switch: a tiny pin current at its base/gate turns on a much larger current through its collector/drain. The pin commands; the transistor does the heavy lifting from the main supply.

The flyback diode — non-negotiable for coils

Motors, relays, and solenoids are inductive. When you switch off the current through an inductor, its magnetic field collapses and tries to keep current flowing — generating a huge reverse voltage spike (V = −L·dI/dt) that can punch through your transistor. A flyback (freewheeling) diode placed across the coil, reverse-biased in normal operation, gives that collapsing current a safe loop to die out in. Leave it off and the spike eventually kills the switch.

// Pin -> resistor -> NPN base; motor from +supply to collector;
// flyback diode across the motor (cathode to +supply).
pinMode(MOTOR_DRIVE, OUTPUT);
analogWrite(MOTOR_DRIVE, 153);  // 60% speed via the transistor

Worked example — switching a 70 mA relay

You want to energize a relay coil that draws 70 mA at 5V. A pin maxes out near 20 mA, so direct drive is out by 3.5×. Instead, drive an NPN transistor through a base resistor. Pick the base current so the transistor saturates: for a transistor with current gain β ≈ 100, you need at least Ibase = 70 mA / 100 = 0.7 mA, and in practice 2–3 mA for solid saturation — well within the pin's 20 mA budget. The collector then sinks the full 70 mA from the supply, and a flyback diode across the coil tames the turn-off spike.

Servos and the H-bridge

The robot's wheels use servos, controlled by a pulse-width signal: a pulse between 1000 µs and 2000 µs (1500 µs = center) repeated every ~20 ms. To run a plain DC motor both directions, you need an H-bridge — four transistors that can connect the motor either way across the supply, giving forward, reverse, and brake.

The pin is a brain, not a muscle. Treat every output pin as a low-current logic signal. Anything that moves, heats, or glows brightly gets its own driver — a transistor, MOSFET, motor driver, or relay — powered from a supply rail, not from the pin. And any coil gets a flyback diode. Forget these and you'll burn out pins one project at a time.
Pin Current Budget

Set a load's current demand and decide: direct from a pin, or through a transistor? The meter turns red past the ~20 mA pin limit, showing when you must add a driver.

Load current (mA) 70
You need to switch a 70 mA relay coil from a 20 mA pin. What's the right approach?

Chapter 7: Timing & Interrupts

So far the robot runs a loop() that reads sensors, decides, and acts, over and over. This is polling: the CPU repeatedly asks "is anything happening?" Polling is simple and usually fine. But it has a fatal flaw — if the loop is busy doing something slow (a long delay(), a serial print, a sensor that takes milliseconds), it isn't looking at the other inputs, and a fast event can come and go unnoticed.

Imagine the robot's bumper switch. If the main loop is mid-way through a 500 ms turn maneuver and the bumper taps a wall for 5 ms, a polling loop checking the bumper only between maneuvers will miss the hit entirely. The robot keeps driving into the wall.

Interrupts: events that can't wait

An interrupt flips this around. You tell the hardware "watch this pin, and the instant it changes, stop whatever you're doing and run this function." The function is an interrupt service routine (ISR). The CPU is yanked away from the main loop, runs the ISR, then resumes exactly where it left off. The event is caught no matter how busy the loop was.

volatile bool bumped = false;

void onBump() { bumped = true; }   // ISR: keep it tiny

void setup() {
  pinMode(2, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(2), onBump, FALLING);
}
void loop() {
  if (bumped) { bumped = false; backUp(); }  // handle it
  ... long maneuvers here ...
}

Timers: keeping time without blocking

The other tool is the timer. millis() returns the milliseconds since boot and micros() the microseconds — both driven by a hardware timer that counts in the background, independent of your code. Instead of delay(1000) (which freezes the whole program for a second), you compare millis() against a saved timestamp, so the loop keeps spinning and stays responsive.

Worked example — the missed event

The main loop spends 50 ms per pass driving servos and printing debug text. A bumper pulse lasts 5 ms. With polling, the bumper is only checked once every 50 ms, so a 5 ms pulse has roughly a 5/50 = 10% chance of landing in the brief window where the code happens to look — it's missed ~90% of the time. With an interrupt, the pulse is caught in microseconds, every single time, regardless of what the loop is doing.

Interrupts buy you reaction time, polling buys you simplicity. Poll slow, tolerant things (a menu button, a temperature you read once a second). Use interrupts for fast, rare, must-not-miss events: an encoder tick, an emergency stop, a bump. Keep ISRs tiny — just set a volatile flag and get out — because while an ISR runs, the rest of the system (including other interrupts) waits.
Interrupt vs Polling: Catching the Event

A short event pulse fires while the main loop grinds through a busy block. Toggle between polling and interrupt mode and press Fire Event to see whether it gets caught or slips through the gap.

Your main loop is often busy for tens of milliseconds. You must never miss a 3 ms bumper pulse. What do you use?

Chapter 8: Serial Communication

A microcontroller often has to talk to other chips or a PC — send a sensor reading, receive a command, chain to a display. Doing that with one wire per bit (parallel) eats pins fast. The dominant approach is serial: send the bits one at a time down a single wire, fast enough that the slowness doesn't matter.

UART — the asynchronous workhorse

The UART sends bytes over two wires: Tx (transmit) and Rx (receive). There's no shared clock — instead both ends agree in advance on a speed, the baud rate (bits per second), commonly 9600. Each byte is wrapped in a frame. The standard frame is 8N1: 1 start bit, 8 data bits, No parity, 1 stop bit — 10 bits total to carry 8 bits of data. The data bits go out LSB first.

tbit = 1 / baud     bytes/s ≈ baud / 10   (8N1: 10 bits per byte)

Worked example — 9600 baud timing

At 9600 baud, each bit lasts tbit = 1 / 9600 ≈ 104 µs. A full 8N1 frame is 10 bits, so one byte takes 10 × 104 µs = 1.04 ms. The throughput is therefore baud / 10 = 9600 / 10 = 960 bytes/s — roughly a kilobyte per second. (Note it's baud/10, not baud/8, because the start and stop bits are overhead that carry no data.)

Serial.begin(9600);           // 9600 baud, 8N1 by default
Serial.println(analogRead(A0)); // send the reading + newline

I2C and SPI — the synchronous buses

When you're connecting chips on the same board, two clocked buses dominate. I2C uses just two wires — a clock (SCL) and a data line (SDA) — shared by many devices, each with an address; it tops out around 400 kbit/s. On the Uno, I2C lives on pins A4 (SDA) and A5 (SCL). SPI uses four wires (clock, MOSI, MISO, and a select line per device) and runs much faster — up to tens of Mbit/s — ideal for SD cards and displays.

BusWiresSpeedBest for
UART2 (Tx, Rx)~9600 – 115200 baudPC link, GPS, debug
I2C2 (SCL, SDA)≤ 400 kbit/sMany sensors on one bus
SPI4 (SCK, MOSI, MISO, SS)up to ~80 Mbit/sSD cards, displays
Baud must match on both ends. UART has no clock wire, so the receiver samples the line on a timer it sets from the agreed baud rate. If the two ends disagree — one at 9600, the other at 19200 — the sampling lands in the wrong place and you get garbage characters. The classic "my serial monitor shows gibberish" bug is almost always a baud mismatch. I2C and SPI dodge this by shipping an explicit clock wire.
Serial Bit Stream (8N1, LSB-first)

Pick a character and a baud rate. Watch its 8 bits serialized LSB-first, wrapped in a start bit (line pulled LOW) and a stop bit (line HIGH), with each bit's duration annotated. Press Send to animate the line.

Character code K
A two-wire bus with a clock (SCL) and data (SDA) line, supporting many addressed devices at up to ~400 kbit/s, is which protocol?

Chapter 9: Programming the Robot

Time to close the loop on Chapter 0. We have the four interface channels — digital input (the IR sensors and bumper), PWM/servo output (the wheels), and a digital output for the buzzer. The same circuit can be programmed in two different toolchains, which is exactly the book's running comparison: PBASIC on a BASIC Stamp, and C on an Arduino. Same wires, same decision tree, two languages.

The same logic, two ways

The BASIC Stamp's PBASIC is a friendly interpreted dialect: PULSOUT drives a servo pulse, IF...THEN branches, DEBUG prints. The Arduino's C is compiled and uses library calls like digitalRead, the Servo object, and tone. The differences are surface syntax — the structure (read sensors → decide → drive) is identical.

PBASIC (BASIC Stamp)

DO
  left  = IN0   ' read IR pins
  right = IN1
  IF left = 0 AND right = 0 THEN
    GOSUB back_up      ' both blocked
    FREQOUT 4, 100, 880
  ELSEIF left = 0 THEN
    GOSUB turn_right
  ELSEIF right = 0 THEN
    GOSUB turn_left
  ELSE
    GOSUB forward
  ENDIF
LOOP

Arduino C (ATmega328)

void loop() {
  bool L = digitalRead(LEFT_IR)==LOW;
  bool R = digitalRead(RIGHT_IR)==LOW;
  if (L && R) {
    backUp();          // both blocked
    tone(BUZZER, 880, 100);
  } else if (L) {
    turnRight();
  } else if (R) {
    turnLeft();
  } else {
    forward();
  }
}

The servo subroutine, where the timing lives

Driving forward means sending both wheel servos a pulse. Recall from Ch 6 that a servo wants a 1000–2000 µs pulse every ~20 ms; a continuous-rotation servo reads 1500 µs as "stop," 2000 µs as "full forward," 1000 µs as "full reverse." In PBASIC that's a PULSOUT; in Arduino C the Servo library hides the timer arithmetic behind writeMicroseconds():

// Arduino: drive both wheels forward
leftWheel.writeMicroseconds(2000);   // full forward
rightWheel.writeMicroseconds(1000);  // mirrored servo -> also forward
delay(20);                            // ~one servo frame
The toolchain is a choice, not the design. PBASIC is gentler to learn and debugs interactively; C is faster, more memory-efficient, and the industry default. But notice that picking one over the other changed nothing about the circuit, the pins, the pull-ups, the 20 mA limit, or the servo timing. Those are hardware facts. The program is just how you express the decision tree — which is the whole "design moves into software" thesis made concrete.
Robot State Machine

Toggle the IR sensors and bumper; the robot transitions between states (Forward, Turn, Back Up, Beep) and the matching servo pulse widths are shown. This is the program from Ch 0 made visible.

Rewriting the obstacle-avoider from PBASIC to Arduino C changes which of the following?

Chapter 10: Connections & Summary

Chapter 13 made one big argument: a microcontroller lets the hardware design problem move into software. You wire a few parts to I/O pins and write a program. Everything else was about the four channels the chip uses to touch the world — and the interface gotchas that make or break a build.

The four ways a micro talks to the world

ChannelDirectionKey ideaArduino call
Digital I/Oin / outHIGH/LOW; pull-ups define resting statedigitalRead / digitalWrite
Analog input (ADC)inVoltage → 0–1023 (10-bit)analogRead
PWM outputoutDuty cycle → average voltageanalogWrite
Serial (UART/I2C/SPI)in / outBits one wire at a timeSerial / Wire / SPI

Every key equation in one place

QuantityFormulaWorked value
ADC countround(Vin/Vref × 1023)0.75V → 153
ADC inverseVin = count/1023 × 5512 → 2.50V
ADC resolutionVref / 10235/1023 ≈ 4.89 mV
PWM averageVavg = D × VccD=0.40 → 2.0V
PWM valuex = D × 2550.40 → 102
Pull-up currentI = Vcc / R5V/10k = 0.5 mA
Serial bit timetbit = 1/baud9600 → 104 µs
Serial throughputbaud / 10 (8N1)9600 → 960 B/s
Servo pulse1000–2000 µs every 20 ms1500 µs = center

The interface gotchas cheat-sheet

Floating inputs. Always pull every input up or down. Switch-to-ground + pull-up = inverted logic (LOW = pressed). Use INPUT_PULLUP.
20 mA / pin. Pins are control signals, not power. Anything >~20 mA gets a transistor or driver. Whole chip < ~200 mA.
Flyback diode. Every coil (motor, relay, solenoid) gets a reverse diode across it to kill the turn-off spike.
5V vs 3.3V. This chapter assumes 5V logic. Mixing 5V and 3.3V parts needs level conversion (a divider or level-shifter), or you damage the 3.3V chip.

Where this connects in the book

"The microcontroller turned the soldering iron into a keyboard."
— the spirit of Chapter 13

You can now read a sensor, dim an LED, drive a motor, and talk to another chip — the whole vocabulary of embedded control. Chapter 14 asks: what if even a fast CPU running code one instruction at a time is too slow? Then you stop writing a program and start configuring hardware.

What is the single biggest idea of the microcontrollers chapter?
← Ch 12: Digital Electronics Ch 14: Programmable Logic →