Embedded Systems and Microcontrollers
A microcontroller is a whole computer on one chip, and you can buy one for the price of a sandwich. It has a processor, a little memory and a row of metal legs called pins. Those legs are the interesting part. With them a chip can light a lamp and feel a button. It can dim a light, point an arm at an exact angle, spin a motor both ways, and walk a stepper to a tenth of a degree. It can ask a sensor how warm the room is, and restart itself when it goes wrong. One simulated board and a parts list at the end.
One simulated board runs through every step. You write the firmware yourself in an editor and press
Upload and run. The board records what every pin did, with times attached, so you can play the run back
and slide through it while the LED, the servo horn, the motor and the pen move. Several steps hand you a
sketch that is already broken on purpose, because the fastest way to understand what
pinMode is for is to watch a board ignore you without it.
No electronics background, and no soldering iron. It helps to have done Build a Microprocessor, since
this course starts where that one stops: the processor inside the chip is the machine you built there,
and the pins are new. Every electrical word, from volt to inductance, is explained where it first
appears. So is every piece of the programming language, from what a function is to what
=== means.
The steps
Make a pin push 3.3 volts, and measure it
A microcontroller is a processor, some memory, and a row of metal legs. The processor part is the machine from Build a Microprocessor, doing what it did there. The legs are the new thing. They are the only way the chip can reach anything outside itself. Each leg is called a pin, and each one has a number printed next to it on the board.
A pin that the chip is driving is doing one of exactly two things. Either the chip has connected it to the supply rail, which on this board sits at 3.3 volts, or it has connected it to the ground rail, which sits at 0 volts. Writing a 1 from your program picks the first. Writing a 0 picks the second. There is no third setting and nothing in between.
What is a volt, actually?
Voltage is electrical push, measured between two points. A single AA cell pushes with 1.5 volts. Two of them in a line push with 3 volts. The board here runs on 3.3 volts, which is a small, safe push: you can hold both rails at once and feel nothing at all.
Push on its own moves nothing. Current is what flows when the push has somewhere to go, and it is measured in amps, or more usefully for small parts in milliamps, thousandths of an amp. A pin can supply roughly 20 milliamps before it complains, which is enough for an indicator lamp and nowhere near enough for a motor. That gap is the whole of Step 8.
So a bit is not an idea trapped inside the machine here. It is a voltage on a piece of metal, and you can put a meter on it. Do that first.
What is a bit, and what is a byte?
A bit is one thing that has two possible states, written 0 and 1. A light switch is a bit. A pin held low or high is a bit. One bit on its own can only ever say one of two things, so bits are used in groups.
Eight bits side by side is called a byte. Eight switches with two settings each give 2 x 2 x 2 x 2 x 2 x 2 x 2 x 2 = 256 different patterns. So a byte can hold any whole number from 0 to 255. The second lab in this step builds a byte out of eight real pins, one voltage each.
Why does the lamp need a resistor in front of it?
The lamp here is an LED, a part that gives out light when current runs through it the right way. An LED has no sense of moderation: offer it more current and it takes more, until it destroys itself. So a resistor goes in the path to limit how much current can flow. Resistance is measured in ohms, and the standard part for this job is 220 ohms.
The arithmetic is one division. An LED drops about 2 volts across itself, leaving 1.3 volts of the 3.3 to push current through the resistor. Current equals volts divided by ohms, so 1.3 divided by 220 gives about 0.006 amps, which is 6 milliamps. Comfortably bright, and comfortably inside what a pin can give. The lab shows that number as you toggle the pin.
Choose your first board without guessing
There is no best board, and anyone who names one without asking what you are making is guessing. There is a board that fits the thing you want to build, and finding it means saying out loud what the thing has to do and then reading numbers.
Five numbers do most of the work. The price. The clock speed in megahertz, which is millions of steps a second. The flash, which is the memory that holds your program even with the power off. The RAM, which is smaller, faster memory holding the values the program is working with right now and forgetting them the instant power is lost. And the current the chip draws while asleep, which decides on its own whether a battery lasts a week or a year.
What is a megahertz?
Hertz means once per second. A kilohertz is a thousand times a second and a megahertz is a million times a second. A board at 16 megahertz takes sixteen million small steps every second, and each step does a tiny piece of work such as adding two numbers or fetching one from memory.
Sixteen million sounds like plenty, and for blinking a lamp it is absurdly more than enough. It runs out when you ask for arithmetic on every one of a thousand sensor readings a second, or when you want a screen redrawn smoothly. Most beginner projects never notice the difference between 16 and 240.
Why does a board have two different kinds of memory?
Flash keeps what is written in it with no power, like a notebook. That is where your program lives, so the board still knows what to do when you plug it in tomorrow. Writing to flash is slow and it wears out after many thousands of rewrites, so a program is written once when you upload it and then only read.
RAM forgets everything the moment the power goes, like a whiteboard, and in exchange it is fast and can be rewritten endlessly. Every value your program keeps while it runs lives there. RAM is the number that usually runs out first: 2 kilobytes, which is 2048 bytes, disappears quickly once you want to remember a few hundred sensor readings.
How much is a microamp, and why does it decide a battery project?
A milliamp is a thousandth of an amp. A microamp is a millionth of an amp, so a thousand microamps make one milliamp. Battery size is quoted in milliamp hours: a coin cell holds about 220 milliamp hours, meaning it can give 220 milliamps for an hour, or 1 milliamp for 220 hours, and so on.
Now the arithmetic. A board that draws 30 milliamps flattens that coin cell in about seven hours. A board that sleeps at 10 microamps, which is 0.01 milliamps, would take 22000 hours, which is over two years. Same job, same cell, and the only thing that changed was one column in the table below.
Blink an LED, and fix the line that is missing
The program you put on a microcontroller is called firmware, and a small one like this is often called a sketch. It is written on a computer, then sent down a cable into the board's flash, which is called uploading. From then on the board runs it every time it is powered, with no computer attached.
A sketch has two named pieces of program. The board runs setup once, the moment it powers
up, and that is where you say what each pin is for. Then it runs loop over and over, forever,
as fast as it can. There is no end to a sketch. When loop finishes, the board calls it
again.
What is a function?
A function is a piece of program with a name on it. Everything between the two curly brackets
belongs to it. Writing delay(400) means run the function called delay, and hand
it the number 400 to work with. The things handed over go in the round brackets, and the function does
something with them.
Here setup and loop are two functions you write and never call yourself. The
board calls them for you, which is the whole arrangement: you fill in what should happen once and what
should happen forever, and the board does the calling.
Why the semicolons, the brackets, and the lines starting with two slashes?
One instruction is called a statement, and a semicolon marks the end of one, the way a full stop marks
the end of a sentence. Curly brackets { and } group statements together so a
function can hold several. Round brackets hold the values being handed to a function, and stay there even
when there is nothing to hand over, as in millis().
A line starting // is a comment. Everything after the two slashes is for people to read
and is ignored completely by the machine, so a comment is free and cannot break anything. The starting
sketch below has a comment where a missing line should be.
How long is a millisecond?
A thousandth of a second. Blinking your eye takes about 150 of them. delay(400) tells the
board to stand still and do nothing for 400 thousandths of a second, a little under half a second. That is
slow enough for a person to see the lamp change.
Later steps also use the microsecond, a millionth of a second, because that is the scale a switch bounces at and the scale a servo pulse is measured in. A thousand microseconds make one millisecond.
setup, upload it again, then press Check the
blink. If you get stuck, Show me one that works has an answer.digitalWrite(7, HIGH), nothing happens, and there is no error
message anywhere. What do you check first?pinMode first. Without it, pin 7 is still an input and the board
ignores the write completely, which is the one failure that produces total silence. A backwards LED is
worth checking second and is easy to spot. A delay that is too short would still show something on a
meter, and would show a flicker rather than nothing.Read a button, and stop the pin from guessing
Pins go both ways. Told to be an input, a pin stops pushing and starts listening.
digitalRead then hands your program a 1 if the voltage on that pin is above the halfway mark, and
a 0 if it is below. That is all a chip can tell you about the outside world through one pin.
A push button is not clever. It is two pieces of metal that touch while your finger is down and spring apart when you let go. Wire one between a pin and the ground rail and the pin is dragged to 0 volts while you hold it. So far so good.
Now ask the awkward question. When the button is open, what is that pin connected to? Nothing. And a pin connected to nothing does not read 0. It reads whatever it feels like.
What does === mean, and why three of them?
A single = puts a value into a name: count = 0 means make count hold zero.
Three of them is a question rather than an order. digitalRead(2) === LOW asks whether those
two things are the same, and the answer is either true or false. Using one where you meant three is a
classic bug, which is why the question form is deliberately different to look at.
if takes such a question and runs the block after it only when the answer is true.
else supplies a block to run instead when the answer is false. So an if with an
else always runs exactly one of the two blocks, never both and never neither.
What is a pull-up resistor, and how can it be overruled by a finger?
A pull-up is a resistor from the pin up to the 3.3 volt rail. While the button is open it is the only thing connected to the pin, so it holds the pin high and the reading is a steady 1. It works because nothing needs to flow: the pin is only listening, so a very weak connection is plenty.
Press the button and the pin now has two things attached: 10000 ohms up to 3.3 volts, and near zero
ohms straight down to ground. The direct path wins, and the pin sits at 0 volts. A tiny current trickles
down through the resistor while you hold it, about a third of a milliamp, which is why the resistor is
chosen large. Most chips have such a resistor built in, switched on by asking for
INPUT_PULLUP instead of INPUT. Then you need no extra part at all.
Why is a pressed button a 0? That feels backwards.
It is backwards, and it is normal. With a pull-up the resting state is high, so pressing is the thing that pulls the pin down. Open reads 1, pressed reads 0. Nothing is wrong with the wiring and nothing can be done about it except to write the code that matches.
You could wire it the other way, with a pull-down resistor to ground and the button going up to 3.3 volts, and then pressed would read 1. Almost nobody does, for one reason: the pull-up is already inside the chip and free and the pull-down is not. A whole industry reads its buttons upside down to save a part.
loop, upload again, then press Check my
answer.setup says
pinMode(4, INPUT). The program reads the pin a hundred times while nobody touches it, and
gets a mixture of 0s and 1s. Why?INPUT_PULLUP gives it something weak to hold on to and the readings go still. The
button is fine, and reading speed does not come into it: a pin that is being held properly reads the same
value a million times in a row.Count one press as one press
Now count presses. Keep a counter, which is just a name holding a number, and add one to it every time the pin falls from high to low. It is three lines of program and it does not work.
Inside a push button are two springy pieces of metal. On the way together they touch, bounce apart, touch again, and do that a handful of times over a few thousandths of a second before they settle. Your finger went down once. The pin saw the level fall five or seven times.
What is a counter, in program terms?
A name that holds a number, and a line that replaces what it holds with one more than that.
count = count + 1 reads the current value, adds one, and puts the result back under the same
name. The name is called a variable, because what it holds varies.
A counter declared outside every function, at the top of the sketch, keeps its value between one pass
through loop and the next. One declared inside loop would be created fresh each
pass and would never get past 1, which is a bug worth knowing about before you write it.
Why does metal bounce at all?
Because the contacts are springs. They have to be, or they would not push back against each other hard enough to make a reliable connection. A spring that arrives at a stop does not simply stay there, it rebounds, and while it is in the air the circuit is open again.
The bouncing dies away as the energy goes, which is why the gaps in the lab below get shorter each time. Bigger switches bounce for longer, worn ones bounce worse than new ones, and no switch has a published bounce time you can rely on. That is the point: this is a physical effect you design around rather than a number you look up.
Why is a window that is too long the worse bug?
The fix is a lockout: the moment you believe the pin has changed, stop looking at it for a while. That covers the way back up as well, because letting go bounces too. Too short a lockout and bounces slip through, so a counter reads six for four presses. That is annoying and obvious, and whoever is testing it notices immediately.
Too long a lockout swallows real presses that happen close together, so the counter reads three for four presses. Nothing on screen looks wrong, no reading is impossible, and the missing press leaves no trace. A bug that produces a plausible wrong answer survives testing much longer than one that produces an obviously wrong one.
Dim an LED without changing the voltage
A pin has two settings. It cannot sit at half a volt, and it cannot give out half a light. So how does every gadget in your house dim its lamps?
By switching. Turn the pin on and off hundreds of times a second, and choose what fraction of each cycle
it spends on. On for a tenth of each cycle and the lamp is dim. On for nine tenths and it is nearly full.
That fraction has a name, the duty cycle, and the whole technique is called pulse width modulation,
usually shortened to PWM. analogWrite(9, 128) asks for about half.
Why does fast flashing look dim rather than flashy?
Your eye adds up light over a short window, roughly a fiftieth of a second, and reports the total. Anything that flashes faster than that arrives inside one window and gets averaged. A lamp that is on for half of every cycle delivers half the light, and half the light is what you see: a steady dim lamp.
Below about 25 cycles a second the flashing lands in separate windows and you see it as flicker. This is not a property of the lamp, it is a property of you, which is why the lab lets you drag the frequency down until your own eye gives up.
Why 0 to 255, and not 0 to 100?
Because the setting is stored in one byte, and a byte holds 0 to 255. That is the natural size for the hardware that makes the pulses, so it is the size the function takes. 0 means never on. 255 means always on. 128 means on for a hair over half of each cycle, because 128 out of 255 is a shade more than half.
Doing the sum the other way is worth a moment: 200 out of 255 is about 78 hundredths, so
analogWrite(9, 200) averages about 78 hundredths of 3.3 volts, which is 2.6 volts. The pin
never once visits 2.6 volts. That is what the average of a square wave means.
What are let and while?
let b = 0; makes a new name and puts zero in it. Names made with let inside
a function exist only while that function is running, which is fine for something being counted up right
now and wrong for something that has to be remembered between passes.
while (b <= 250) { ... } runs the block, checks the question again, and keeps going for
as long as the answer is true. Something inside the block has to move towards making it false, or the
loop never ends. In the fade below, that something is b = b + 10, which is what eventually
pushes b past 250 and lets the loop finish.
analogWrite(9, 64) and put a very fast meter on pin 9. What does the
meter see?Point a servo horn at an exact angle
A servo is a box with a motor, a gearbox, a sensor that knows where the output shaft is, and a small controller, all in one. Because of that sensor it takes a different kind of order from a plain motor. You do not tell it how fast to turn. You tell it where to point. It works out the rest and holds position.
The order arrives as a pulse: the signal wire goes high, stays high for a set time, then goes low. A pulse 1 millisecond wide means go to one end of the travel, 2 milliseconds means the other end, and 1.5 milliseconds means the middle. The width is the whole message.
And the pulse repeats, about every 20 milliseconds, whether the angle changed or not.
How is a pulse different from just setting a pin high?
Only in that you are paying attention to how long it lasts. A pulse is a pin taken high and then put back low, and its width is the time it spent high. Setting a pin high and leaving it there is a pulse that has not ended yet.
What makes it useful is that time is easy to measure precisely and voltage is not. A wire loses a little voltage along its length and gains a little noise from its neighbours, but 1.5 milliseconds is 1.5 milliseconds at both ends of the wire. Sending information as a duration rather than as a level is a trick that turns up everywhere once you have seen it.
Why send the pulse again if the angle has not changed?
Because the servo forgets. Its controller expects a fresh instruction every 20 milliseconds or so, and after about a tenth of a second of silence it stops pushing and goes limp. That sounds like a flaw and is a safety feature: a servo that lost its signal wire would otherwise keep straining against whatever it was last told, quietly cooking itself.
The repetition is also what lets a servo hold against a push. Each new pulse is compared with where the shaft actually is, and if a hand has moved it, the controller drives it back. A plain motor told to move and then left alone has no idea it has been pushed.
Why does a servo get its own power supply?
A small hobby servo pulls a few hundred milliamps while moving and can spike over an amp when it is pushing against something. A pin can supply about 20. Powering a servo from a pin does not work at all. Powering one from the board's own supply usually drops the whole rail far enough to reset the chip, which looks like a firmware bug and is not one.
So the servo takes its power and ground straight from a supply, and only its signal wire goes to a pin. The one wire that must be shared is ground. Two circuits with no common ground have no agreement about what 0 volts means, so the servo cannot tell how high your pulse is.
Spin a motor both ways without killing a transistor
A plain DC motor is simpler than a servo and harder to drive. Put voltage across it and it spins. More voltage, faster. Swap which side is positive, and it spins the other way. It has no sensor, no controller and no opinion.
Two things stop you wiring one to a pin. It wants hundreds of milliamps and a pin has twenty, and a pin cannot swap its own polarity. Both are solved by four switches arranged in the shape of an H, with the motor as the crossbar. Close the top left and bottom right and current runs one way through the motor. Close the other pair and it runs the other way. That arrangement is called an H-bridge and the four switches in it are transistors. A transistor is a switch with no moving parts: a voltage on one leg decides whether current can flow between the other two. A pin can supply that deciding voltage easily and the current being let through can be far more than the pin could ever carry itself. On a diagram the four are labelled Q1 to Q4, which is what the buttons in the lab are called.
Then there is the part that destroys hardware. A motor winding is a coil of wire, and a coil objects strongly to having its current stopped.
Have I met a transistor before?
You are made of them. A transistor is the same part your processor is built out of, in its millions, switching billions of times a second. The ones in a bridge are simply much bigger, because a switch that carries an amp has to be built to carry an amp, while a switch that only feeds the next switch does not. Same idea, wildly different size.
Q is the traditional letter for a transistor on a wiring diagram, which is why the four are Q1 to Q4 rather than S1 to S4. In a real project you rarely wire four of them yourself. You buy a driver chip with all four inside, and the reason is in the next two notes.
Why does a coil fight back when you switch it off?
A coil with current running through it stores energy in a magnetic field, and it will do whatever it takes to keep that current flowing for a moment longer. This property is called inductance, measured in henries and a small motor's winding is about 0.0015 henries.
The voltage it produces is the inductance multiplied by how fast the current changes. Stop 0.9 amps in about a millionth of a second and that comes out at over a thousand volts, appearing across the switch that just opened. A transistor rated for 40 volts does not survive that, and the failure happens at switch-off rather than during running, which is why it is so puzzling the first time.
What does a flyback diode do about it?
A diode is a one way valve for current. Fitted across each switch the wrong way round for normal operation, it does nothing at all while the circuit is running. The instant a switch opens and the coil starts pushing, the diode becomes the easiest path available and the current runs round through it instead, dying away in the resistance of the winding.
With that path available the coil never has to produce a big voltage, because it never has to force current through anything that is refusing to carry it. The spike stops at about a volt above the supply. The lab shows both cases, and shows the number.
Move a stepper to an exact angle, and draw with it
A stepper motor does not spin smoothly, and that is the point of it. Around the outside sit several coils; in the middle sits a magnet. Energise one coil and the magnet swings to face it and stops. Energise the next one round and it takes another fixed step. Clock through the coils in order and the magnet walks round, one exact angle at a time.
A common size takes 200 steps for a full turn, so one step is 360 divided by 200, which is 1.8 degrees. Count the steps you have sent and you know the angle, with no sensor anywhere in the machine. That is called running open loop, and it is how almost every printer and 3D printer knows where its head is.
The cost of open loop is a real one. If the motor ever fails to follow a step, nothing notices.
Why does a coil pull a magnet at all?
A coil is a length of wire wound many times round a core of iron. Current through wire makes a magnetic field, and winding the wire into many turns stacks those fields on top of each other, so a modest current makes a strong magnet. Switch the current off and the magnet vanishes.
That switchable magnet is the whole trick of every motor in this course. In a stepper it pulls the permanent magnet in the middle round to face it. In the DC motor of Step 8 the same coils are switched automatically by brushes as the shaft turns, which is why that one spins continuously instead of stepping.
What is a half step?
Energise two neighbouring coils at once and the magnet cannot face either, so it settles halfway between them. Alternating single coils with pairs gives twice as many resting places: eight rows in the table instead of four, 400 steps a turn instead of 200, and each step 0.9 degrees instead of 1.8.
It is not free. A half step position is held by two coils pulling sideways at each other, so it has less holding force than a full step, and the strength of the steps now alternates. Real controllers push this much further with microstepping, feeding the coils partial currents to place the magnet anywhere, and each subdivision buys smoothness and loses grip.
How does a plotter know where it started?
It does not, and it cannot work it out. Counting steps tells you how far you have moved from where you began. Nothing at all about where that was. Power a plotter on and the head could be anywhere.
So real machines home first. They drive slowly towards one end until a switch is pressed, then call that position zero and count from there. Every trip your printer makes to the far left with a clunk before it starts printing is exactly this. One cheap switch buys the one piece of information counting cannot supply.
Turn a sensor's resistance into a number
Plenty of sensors do not answer yes or no. A light dependent resistor is a small disc whose resistance falls as light rises: about 10000 ohms in a dim room, a few hundred in sunlight. Nothing about it is a 1 or a 0, and there is a real number in there you would like to have.
A pin cannot measure resistance. It can only measure voltage. So the sensor is paired with an ordinary fixed resistor, one end at 3.3 volts and the other at ground, and the pin listens to the joint in the middle. The two parts share the 3.3 volts in proportion to their sizes, so the voltage at that joint moves as the sensor changes. The arrangement is called a voltage divider. It is the most used circuit in electronics.
Then the chip's converter measures that voltage and hands the program a whole number.
analogRead answers with something from 0 to 1023, where 0 means 0 volts and 1023 means 3.3.
Why 1024 steps and not 1000?
Because the converter works in bits. Ten bits give 2 multiplied by itself ten times, which is 1024 different values, numbered 0 to 1023. A converter with 12 bits, which many newer boards have, gives 4096 values numbered 0 to 4095. The count is always a power of two because that is what a row of bits counts.
One step is therefore 3.3 volts divided by 1024, about 3.2 thousandths of a volt. The converter cannot see anything finer than that, so two voltages 1 thousandth of a volt apart come back as the same number. Every measurement in every computer has a step size like this hiding in it.
The divider in numbers, once
Say the sensor is at 10000 ohms and the fixed resistor is also 10000. The two are equal, so they share
the 3.3 volts equally and the joint sits at 1.65 volts. That is 1.65 divided by 3.3, which is half of the
range, so analogRead answers about 512.
Now the room brightens and the sensor drops to 2000 ohms. The fixed resistor is now five times the bigger share, so the joint rises to 3.3 multiplied by 10000 divided by 12000, which is 2.75 volts, and the reading climbs to about 853. The general form is the supply multiplied by the lower resistor divided by both added together and the lab prints exactly that sum as you drag.
Why does the fixed resistor's size change what you can see?
The divider is most sensitive when the two resistances are similar, and goes flat when one is far bigger than the other. Fit a 1000 ohm resistor against a sensor that sits at 10000 in the range you care about and almost the whole voltage lands on the sensor, so the reading barely moves as the light changes.
So the fixed resistor is chosen to be about the sensor's resistance in the middle of the range you actually want to measure. Getting that wrong squashes the interesting part of the range into a handful of numbers, and no amount of programming can recover a spread the circuit never produced.
if (light < 400). In a room hovering right around a reading
of 400, the lamp flickers on and off constantly. What would you change?Ask a smart sensor for a reading over two wires
Some sensors have a small computer of their own inside. A weather sensor like the BME280 has already measured the temperature, pressure and humidity, corrected them, and put the answers away in numbered slots. You do not measure a voltage from it. You ask it a question and it answers with bytes.
The asking happens on two wires shared by every such chip on the board. One, called SDA, carries the bits. The other, called SCL, is a clock: it ticks, and everyone looks at SDA on each tick. Every chip on the pair has its own address, a small number it answers to, so one pair of wires can serve a dozen parts. The arrangement is called a bus, and this particular one is called I2C.
Inside each chip the answers live in numbered slots called registers. So one reading is: name the
address, name the register, and listen. Both of those numbers are usually written the way datasheets write
them, with 0x in front: 0x76 is one address and 0xFA is one register.
The note below unpacks what that means, and the sketch you write uses ordinary numbers for the same
values.
What is 0x76?
A number written in sixteens instead of tens. Ordinary counting has ten symbols, 0 to 9, and each place
to the left is worth ten times the one on its right. Hexadecimal has sixteen symbols: 0 to 9, then A for
ten, B for eleven, C, D, E and F for fifteen. Each place to the left is worth sixteen times the one on its
right, and the 0x at the front is a label saying which system this is.
So 0x76 is seven sixteens and six ones, which is 112 plus 6, which is 118. Datasheets use hexadecimal because one hexadecimal symbol is exactly four bits, so 0x76 is also the bit pattern 0111 0110 read straight off. The sketch below uses 118 and 250 where the datasheet says 0x76 and 0xFA, and they are the same numbers.
What is a register, and does reading one change anything?
A register here is a numbered pigeonhole inside the sensor, each one holding a single byte. Register 0xD0 holds a fixed number identifying what kind of chip this is, which is how a program checks it is talking to what it thinks it is. Registers 0xFA and 0xFB hold the latest temperature. Other registers hold settings you write to rather than read.
Reading a register does not consume it or change it. Ask twice and you get the same byte twice, unless the sensor has taken a new measurement in between. That is different from a queue, where reading takes an item away, and it is worth being clear which kind of thing you are reading from.
Why is one temperature split across two registers?
Because a register holds one byte, and a byte can only hold 0 to 255. The sensor wants to report 2137, meaning 21.37 degrees, and 2137 does not fit in a byte. So it splits the number: the top part goes in one register and the bottom part in the next.
Putting it back together is the same idea as reading a two digit number, except the base is 256 instead of 10. In 47, the 4 means four tens. Here the top byte means that many 256s. So the whole number is the top byte multiplied by 256, plus the bottom byte. For 2137 that is 8 lots of 256, which is 2048, plus 89, and 8 and 89 are exactly what the two registers hold.
readTemp five times against
sensor values the sketch on screen never sees, including one of only 0.09 degrees and one of 30. Code that
happens to give the right answer for 21.37 and nothing else does not pass. say prints a line
into the output panel, and return is how a function hands a value back to whoever called
it.Catch a press while the program is busy
Everything so far has read pins by asking. Round loop goes, and somewhere in it a
digitalRead takes a look. That is called polling, and it works right up until the loop
gets busy.
Suppose reading a sensor takes 180 milliseconds, which plenty of real sensors do. Then the program glances at the button once every 180 milliseconds and spends the rest of its time elsewhere. A finger press lasts about 40 milliseconds. Most presses now begin and end entirely between two glances, and as far as the program is concerned they never happened.
The fix is to stop asking. Ask the hardware to watch the pin instead.
What happens to the program while the handler runs?
The chip stops it mid-sentence. It writes down where it had got to and every number it was holding at that moment, jumps to your function, runs it, then puts all of that back exactly as it was and carries on from the next instruction. The interrupted code has no way of telling that anything happened.
The function that gets run is called an interrupt handler, or an interrupt service routine, which is
why you see ISR in other people's code. You never call it yourself. You hand its name to
attachInterrupt, with no brackets after the name, because you are naming the function rather
than running it.
Why must a handler be short?
Because while it runs, the rest of the machine is standing still. The main program is paused, and on most chips other interrupts are held off too, including the one that advances the millisecond counter. Time itself stops being counted properly.
So a delay inside a handler is a trap: the delay waits for a clock that is not moving, and
never ends. The rule is to get in, change one thing, and get out. A handler that sets a variable to say
something happened, leaving loop to do the actual work when it gets round to it, is the
normal shape.
RISING, FALLING or CHANGE?
An interrupt fires on an edge, meaning a moment when the level changes, and you choose which kind. RISING is the moment the pin goes from low to high. FALLING is high to low. CHANGE is either.
With a pull-up button, pressing is the falling edge and releasing is the rising one, so FALLING gives one call per press and CHANGE gives two. Picking CHANGE by accident is the reason a counter sometimes reads exactly double, which the checker in the second lab will tell you about if you try it.
loop at all. The chip watches the pin in hardware, which costs no program time
whatsoever, and wakes your code at the exact microsecond the level falls.setup, then press Check every press.delay inside the
handler. It reports that the clock is stopped in there, so a slow handler is worse than no handler at all.
It also tells you the difference between counting too few, which means nothing is watching the pin, and
counting too many, which usually means CHANGE where you wanted FALLING.loop, and loop also
refreshes a screen, which takes 300 milliseconds. What do people at the door experience?Run two jobs at two rates with no delay
delay has been useful and it is about to run out of road. It does not pause one thing, it
stops the entire program. Ask for one lamp blinking every 125 milliseconds and another every 200, both with
delays, and neither one keeps its time, because each is waiting inside the other's schedule.
The way out is to stop waiting and start checking the clock. millis() answers with how many
milliseconds the board has been running. Keep a note of when each job last ran, and each pass round
loop, ask whether enough time has gone by yet. If it has, do the job and update the note. If it
has not, do nothing and move on.
Nothing anywhere waits, so loop spins round thousands of times a second and every job gets
looked at constantly.
Where does millis() get its number?
From a counter in hardware that ticks along on its own, driven by the same crystal that clocks the processor. It counts whether your program is busy or not, which is what makes it trustworthy. Reading it costs almost nothing: it is a fetch, not a measurement.
It starts at zero when the board powers up, so the number is not a time of day, it is an age. It also
keeps counting during delay, which is a useful thing to know: a delay does not stop the clock,
it only stops your program from looking at it.
Why now - last >= 125 rather than testing for exactly 125?
Because you will not be looking at the exact millisecond. loop arrives whenever it
arrives, and if some other job took a moment you might glance at 124 and then at 127. Testing for equality
would miss 125 entirely and the lamp would never blink again.
Asking whether at least 125 have gone by can never miss. It fires on the first pass at or after the target, which may be a fraction late, and then the note is updated so the next one is measured from there. That single greater-or-equal is the difference between a lamp that blinks forever and one that stops the first time the board is busy.
What is 1 - on13 doing?
Flipping a value between 0 and 1 without an if. If on13 holds 0, then 1
minus 0 is 1. If it holds 1, then 1 minus 1 is 0. Assigning that back gives the opposite of what was
there.
Since digitalWrite treats 0 as low and anything else as high, the same variable can be
both the remembered state and the value written to the pin. Keeping a note of the state matters here:
with no delay to structure the code, the program has to remember whether the lamp is
currently on, because nothing else does.
delay(250) belongs
to task B, and task B keeps perfect time. It is task A, the innocent one, that is wrecked. A wait harms
everything except the thing doing the waiting, which is why one blocking call buried in a large program
is so hard to track down from its symptoms.delay(1000) and also has to notice a
40 millisecond button press. Someone suggests using delay(50) and reading the sensor every
twentieth pass. Is that better?millis() against a note of when the job last ran removes the
waiting rather than making it smaller. Calling it as good as millis() is the tempting answer
because 50 sounds small, and calling it worse gets the direction backwards.Make the board recover from a hang on its own
Firmware hangs. A sensor stops answering and the code waits for it forever. A loop's exit condition never comes true. Nothing crashes, no message appears, and the device simply stops being a device. If it is on a hillside or in a plant pot, nobody is going to walk over and unplug it.
The watchdog is a counter in hardware that counts upwards on its own and resets the chip if it ever
reaches its limit. Your program's job is to keep putting that counter back to zero, once each time round
loop. That is called petting it. A healthy program pets it constantly. A stuck program cannot,
because a stuck program is not running anything, and after the timeout the chip restarts itself.
Which means the placement of that one line is the entire design.
Why can the program not check on itself?
Because a stuck program is not executing any code that could do the checking. Whatever watchful function you write is stuck in the same place as everything else. The watch has to be kept by something that is not the program.
So the watchdog lives in hardware, alongside the processor rather than inside its instruction stream, and on most chips once it is switched on nothing in software can switch it off again. That refusal is deliberate: a bug that could disable the watchdog would take the safety net with it.
Where should the petting go?
Somewhere the program only reaches when it is healthy. The top of loop is the usual place,
because arriving there means the last pass finished. Petting from several places so that some path always
reaches one is how you defeat your own watchdog.
Two placements are worse than useless. Petting inside the loop that is stuck keeps the board alive while it does nothing at all. Petting from a timer interrupt does the same, since interrupts often keep running while the main program is jammed, and then the watchdog reports a healthy board forever.
What does a reset actually undo?
Everything in RAM. Every pin drops back to being an input at 0 volts, every variable loses its value,
and setup runs again from the top as if the power had been cycled. The program in flash is
untouched, which is why the board comes back rather than coming back empty.
So a design that survives a watchdog reset has to survive starting over halfway through whatever it was doing. A motor stops, which is usually right. A counter of how many bottles have passed is lost, which may not be, and if it matters it has to be written somewhere that survives a reset.
stuckSensor is waiting for an answer that is not coming. Switch a watchdog on in
setup, pet it in loop, then press Check that it recovers.setup, and the heartbeat comes back, over and
over, with nobody touching it. Notice also what the checker refuses: a watchdog with nothing petting it,
which resets the board whether it is stuck or not, and a board that resets but never blinks again
afterwards.loop. Months later the sketch grows a step that takes 4 seconds. What
happens?Checkpoint after timers and the watchdog
- Make a pin a real voltage, and read one back without letting it float.
- Pick a board from numbers instead of from what a tutorial happened to use.
- Find the missing
pinMode, and the upside down button test, on sight. - Debounce a switch, and know which direction of error is the dangerous one.
- Set brightness and speed with a duty cycle, and an angle with a pulse width.
- Drive a motor both ways through a bridge, and say why the diodes are there.
- Move a stepper to an exact angle, and explain why it needs to home first.
- Turn a resistance into a number through a divider, and choose the fixed resistor.
- Read a register out of an I2C chip and rebuild a number from two bytes.
- Use an interrupt for anything short and fast, and keep the handler tiny.
- Write a loop with several jobs at several rates and no
delayanywhere. - Fit a watchdog, and put the petting in the one place that is honest.
Useful links before the sensor and board chapters
- Build a Microprocessor. If you have not done it, go and see what is behind the pins: the gates, registers and clock that make those voltages happen in the first place.
- Computer Networks. Two wires and a clock got you as far as a sensor on the same board. That course starts from one wire between two machines and gets to the other side of the planet.
- Intro to Machine Learning. Once a device can measure things, the next question is what to conclude from a pile of measurements, and that is a different kind of building.
Measure a distance with a pulse of sound
A distance sensor that works by sound has two barrels on the front. One sends, one listens. The cheap one, sold as an HC-SR04, talks to the board through two pins: a trigger pin and an echo pin. In the labs below they are pins 9 and 10. Hold the trigger pin high for ten microseconds and the sensor lets out a short burst of clicks, too high for a person to hear. Sound that high is called ultrasound. The sensor then puts the echo pin high, and drops it again the moment the burst comes back.
So the echo pin stays high for exactly as long as the sound was in the air. That is a time, and you wanted a distance. Every sensor so far handed over a number that already stood for the thing you asked about, a voltage or a temperature. This one hands over a time, and turning it into a distance is your job. Sound travels about 343 metres a second through room temperature air, which is 34300 centimetres in a second. A second holds a million microseconds, so in one microsecond sound covers 0.0343 of a centimetre. Multiply the microseconds by that to get centimetres of flying, then halve the answer, because the sound crossed the gap twice.
The board asks for that measurement with one call, pulseIn, which watches the echo pin and
answers with how many microseconds it stayed high. But the pin does not always fall. A soft target soaks the
burst up and sends nothing back. A target at an angle sends most of the burst off to one side, and only a
trace of it back this way. Then the sketch is waiting for something that is never going to happen. So the waiting has to
have an end: after a set time pulseIn stops listening and answers 0, meaning no echo at all. A
sensor that cannot answer has to be able to say so.
Why sound? Light would get there quicker.
Much quicker, and that is the problem. Light crosses a room in about a hundred millionth of a second. Timing that needs a clock ticking billions of times a second, and parts that fast cost more than the rest of the robot put together. Sound is close to a million times slower than light, which turns the same job into something a cheap chip can do.
Crossing a room and coming back takes sound thousands of microseconds, and counting microseconds is what the hardware timer from Step 13 does without being asked twice. Sensors that time light do exist and they cost far more, because the hard part was never making the flash. It was catching it.
Why halve it? I want the distance to the wall, not the round trip.
The clock starts when the burst leaves and stops when it gets back, so what it timed was a there and back trip. Walk to a shop and home again and you have walked twice as far as the shop is away. Nothing at the far end can tell the sensor when the sound arrived there, so half a trip is not something it can time. It times the whole thing and you halve the answer.
This is the most common mistake in this step and it has a signature. Every reading comes out exactly twice what a tape measure says. If your numbers are all double, you have not halved. If they are all half, something is halving twice.
There is a second silence, and it sits at the other end of the range. For a short while after the trigger the sensor is still sending its own burst, and switching over to listening, so an echo arriving inside that window goes unheard. That is where the smallest distance in the datasheet comes from: the maker's page of numbers for this part says 2 centimetres. Closer than that and the echo pin never falls. The sketch waits right to the end and reports no reading. At that point, an object 1 centimetre away and no object at all produce the same observation, so the sensor cannot distinguish them.
What is the rest of the board doing while it waits for the echo?
Nothing. pulseIn sits on the echo pin and does not hand control back until the pin falls
or the wait runs out. That makes it a blocking call, the same trouble delay caused in
Step 13. While it sits there no other job in loop gets a turn, and a button press can come
and go unnoticed.
So the length of the wait is a real cost and not just a safety net. Every reading with no echo in it spends the whole wait doing nothing at all. A robot taking ten readings a second with a generous wait can spend most of its life listening for sounds that are not coming. Make the wait long enough for the furthest thing you need to see, and no longer.
Does warm air change the answer?
Yes, a little. Sound moves faster in warm air than in cold, by roughly 0.6 metres a second for every degree. The 343 in the sum is the speed in a room at about 20 degrees. On a hot day at 30 degrees the sound is about 6 metres a second quicker. That is under 2 hundredths of the whole speed, so a distance worked out with the room temperature number is under 2 hundredths wrong. Multiply that by the distance you care about and you have the error in centimetres.
Careful ultrasonic measurement puts a thermometer beside the sensor and corrects the speed before doing the sum. For a robot trying not to hit a chair leg, nobody bothers. The next lab shows why: the surface you are pointing at will let you down long before the temperature does.
readCm hands back the raw
microseconds it was given. Do the conversion inside readCm, and give back -1 when
pulseIn answers 0, then press Check against hidden targets.pulseIn into 0 centimetres. Press
A towel 90 cm away and run it again to watch that case arrive. Zero microseconds is not zero
distance. It is no answer at all, and -1 is how readCm says so. Three of the hidden targets
are silent ones, so a sketch that does the arithmetic and never tests for 0 cannot pass.pulseIn the whole time.Read a temperature, and find out your reading is noisy
Step 10 turned a resistance into a number, and Step 11 asked a chip on two wires for a number it had already worked out. This sensor manages with one wire. The board pulls that line low to open a slot, and the sensor either holds it down for a 0 or lets a pull-up snap it back up for a 1. Sixteen bits arrive that way. They already say the temperature, because the sensor counts in whole sixteenths of a degree.
Read it twice in a row and the two answers are different. Nothing in the room changed in that time. The difference is noise: the part of a reading that is not the thing you are measuring. Most readings are a little out, one way or the other, every single time. Now and then one comes back wildly out and is fine again on the next reading, and that is a different problem with a different cure.
What you do about it is a filter: readings go in, a steadier number comes out. Three cheap ones cover most of the ground. A moving average adds up the last few readings and divides. A median puts the last few in order and answers with the middle one. A fading average mixes each new reading into its own last answer. How many readings a filter is allowed to look at is called its window. None of the three is free. A filter can only work from readings that have already arrived, so its answer is always partly about the past.
How can one wire carry a number in both directions?
By taking turns, and by agreeing on time rather than on voltage. A slot is a short fixed stretch of time, and the board opens each one by pulling the line down. For a 1 the sensor lets go immediately and the pull-up drags the line back up. For a 0 the sensor keeps holding it down until the slot is nearly over. The board looks part way through the slot: still low is a 0, already back up is a 1.
The pull-up in the picture below is 4.7 kilohms, which is 4700 ohms, since kilo means a thousand here exactly as it did in kilohertz in Step 2. It is the part that makes sharing possible. Both ends can pull the line down and neither has to push it up, so the two can never fight over the wire. The sensor sends its lowest bit first, which is why the eight bits under the trace read right to left.
The numbers jump about. Is the sensor broken?
No, and telling apart the two ways they jump is most of this step. The first is everyday wobble: every reading is a little out, up or down, by roughly the same small amount. Some of that is the air really stirring next to the sensor. Some is electrical hum picked up on the wire. Some is the sensor having to choose one of its sixteenths when the truth sits between two of them.
The second is a reading that comes back far out and is back to normal immediately after. Nothing in a room warms by several degrees and cools again in a tenth of a second, so a reading like that is not about temperature at all. A bit got knocked over on the wire, or the sensor was disturbed part way through answering. The labs below call those wild readings, and no amount of careful averaging will make one of them true.
Why is a filter always late?
Because it has nothing but readings that have already happened. Take the average of the last five. Those five are spread over five readings' worth of time, and the middle of that stretch is two readings back. So the answer describes the room as it was two readings ago rather than now. Widen the window and the middle of it moves further into the past.
While nothing is changing, being late costs nothing: the past and the present are the same number. The bill arrives when the thing you are measuring really moves. Part way through the run in the second lab a hand closes round the sensor and the temperature climbs for real. A slow filter is still reporting the cold room while the hand is on it, and that is what makes the choice of window a decision rather than a preference.
Why does it matter how many numbers a filter has to remember?
Step 2 counted a board's RAM in kilobytes, and a small one has about two of them, which is 2048 bytes for everything the program is holding at once. A window of twenty-one readings means keeping twenty-one numbers and shuffling them along as each new one arrives. For one temperature that is nothing. For a dozen sensors, each with its own window, it stops being nothing.
The fading average never keeps a list. It holds its own last answer, takes one new reading, works out the next answer from those two, and forgets both of the old numbers. Its proper name is the exponential moving average, and that is all the name means: old readings fade away instead of dropping out all at once. It is the filter you find inside firmware that has no room to spare and the second lab measures how close its answer stays to the other two.
Ask an accelerometer which way is down
An accelerometer is a chip with a tiny weight inside it, hanging on springs too small to see. Shove the chip and the weight lags behind for an instant, so the springs bend, and what the chip reports is how far they bent. Being shoved like that is called acceleration. The chip measures it in more than one direction at once, and each direction it measures in is called an axis. The sensor in the first lab has two: one along the sensor and one through it.
Gravity bends those springs as well, and that is the part worth having. A sensor lying still on a table is being held up by the table. That push reaches the little weight inside it exactly as a shove would. So a still sensor reports a pull of about one g. It reports it pointing down. One g is the name for the size of gravity's pull at ground level. How that pull is shared between the two axes is how far over the board is tilted. Sitting still, an accelerometer tells a program which way down is.
Then somebody picks the board up, and it stops being true, because the chip cannot tell gravity from a shove. So these parts nearly always carry a second chip beside the first: a gyroscope, which reports how fast the board is turning, in degrees per second. It ignores shoves completely. It has a fault of its own. Both chips answer over the same two wires as the sensor in Step 11, SDA and SCL, at an address of their own.
Why does a sensor sitting still read anything at all?
Because holding something up is a push. The table pushes the chip upwards hard enough to stop it falling, and the weight inside has to be pushed up with it. The springs bend to do that pushing and the bend is what gets measured. The size of that bend, for anything held still at ground level, is what one g means.
Take the table away and the reading goes. A sensor dropped in mid-air reads nothing on any axis while it falls: nothing is holding the weight up any more, so no spring is bent. That is not a fault, and it is how a phone can tell it has been dropped before it lands. Every sensor in orbit reads the same, which is the real reason things float up there.
How do two pulls turn into one angle?
Lay the board flat and gravity pulls straight through it. The axis through the board reads about 1 g, and the axis along it reads about nothing. Stand the board on its edge and the two swap over. Tip it part way and the pull is shared between them and the way it is shared is the angle. The lab draws both readings as arrows out of the middle of the sensor, each one as long as the number it stands for. Watch the pull move from one axis to the other as the board turns.
Turning that pair of numbers into degrees is one line in any language. The function that does it is
called atan2, and you hand it the two readings and it answers with the angle. You do not need
to know how it works to use it, any more than you need to know how a square root button works. Sensors
sold today have three axes rather than two, which is what lets a device tell tipping forwards from tipping
sideways.
So there are two answers and neither is good enough. The accelerometer is wrong by a lot for a fraction of a second. The gyroscope is wrong by a little that never goes away and only grows. The two faults do not overlap, and that is what makes a fix possible. Keep the gyroscope's smooth answer, and let the accelerometer pull it gently back towards where down really is. Combining two sensors like that is called sensor fusion and the simplest one of all is the complementary filter.
It is one line of arithmetic, run once for every reading:
estimate = (1 - w) * (estimate + turnRate * 0.02) + w * accelAngle
Carry the estimate forward with the gyroscope first: add the turning rate multiplied by the time since the
last reading, which here is 0.02 of a second, because the sketch reads both chips fifty times a second. Then
pull the result a fraction of the way towards the angle the accelerometer is claiming. That fraction,
w, is the only thing there is to choose. At 0 the accelerometer is never looked at. At 1 the
estimate is thrown away and replaced by the accelerometer every single time.
Why not measure the gyroscope's error once and subtract it?
You can, and every serious sketch does. Hold the board still at power-up, average a few hundred readings, and whatever comes out is roughly what the chip reads while nothing is turning. Subtract that from every reading afterwards. It helps a great deal.
It does not fix the problem. The leftover moves as the chip warms up, so a number measured at breakfast is wrong by lunchtime. Whatever is left after subtracting still gets added up fifty times a second: an error of a hundredth of a degree per second is a whole degree after a hundred seconds, and two degrees after two hundred. The measurement also goes badly wrong if the board happened to be moving while you took it, since then you have recorded the movement and called it error. And a gyroscope only ever knows how far it has turned from wherever it started. Nothing in it knows which way down is, so sooner or later you have to ask the other chip.
Is this the same trick as the fading average in Step 16?
Nearly. Step 16 moved its answer a fixed fraction of the way towards each new reading of one sensor, to smooth out noise. This moves its answer a fixed fraction of the way towards a second sensor, to correct a slow error the first sensor cannot see. Same arithmetic, different job.
The name says how the two parts fit: w of the accelerometer and 1 minus w of
the gyroscope, every reading, adding up to one whole answer with nothing left over and nothing counted
twice. A cleverer version called the Kalman filter chooses the weight for itself, from how noisy each chip
has been lately. That is what a drone or a phone actually runs, and it is this idea with the weight no
longer a number you picked by hand.
w.Tell one chip package from another, and know which you can solder
Inside every chip is a flake of grey silicon smaller than a fingernail clipping, and you never touch it. What you buy is the package: the body that flake is sealed inside and the metal that body offers you to join to. The same chip, doing exactly the same job, is usually sold in five or six different packages. Which one you buy decides whether you can attach it yourself.
Attaching a part to a board means soldering it. You heat the part's metal and the board's metal with a hot tool called an iron. Then you feed in a wire of soft metal called solder, and it melts and runs over both. When it cools the two are one piece and current can cross. Some packages push their legs through holes drilled in the board and are soldered on the far side, which is called through-hole. The rest sit on top of the board, on patches of metal called pads, with no holes at all, which is called surface mount.
The size of the part does not decide how hard the soldering is, and neither does the number of legs. The pitch decides: the distance from the centre of one pin to the centre of the next. Take the width of the metal itself off that, and what is left is bare board between one pin and its neighbour. That gap is the room you have to be wrong in. A part that hides all its metal underneath its own body leaves you no room at all, because a tip cannot reach under there. The heat has to arrive some other way: through air blown hot enough to melt solder, or through a plate that heats the whole board from below.
What do DIP, SOIC, QFN and BGA actually stand for?
They are descriptions rather than brand names, and each one tells you the shape. DIP is dual in-line package: two parallel rows of legs, made to go through holes. SOIC is small outline integrated circuit, the same two rows shrunk and bent outwards to sit on top of the board instead. TSSOP is thin shrink small outline package, which is that idea shrunk again.
QFN is quad flat no-lead: four sides, flat, and no legs sticking out, because its metal is a ring of pads on the underside. BGA is ball grid array. It has no edge of pads at all, only a field of solder balls underneath, in rows and columns like squares on a chessboard. The number after the name counts the connections, so a DIP-28 has twenty-eight legs and a BGA-144 has one hundred and forty-four balls.
Why is a resistor called 0805, when it looks nothing like 8 by 5 of anything?
The four digits are the size of the part in hundredths of an inch, two for the length and two for the width. So 0805 is 0.08 by 0.05 inches, 0603 is 0.06 by 0.03, and 0402 is 0.04 by 0.02. An inch is 25.4 millimetres, so you can turn either figure into millimetres by multiplying. The lab prints the millimetre size of whichever part you pick, so you can check the arithmetic yourself.
The name says nothing about what is inside the box. Those same sizes carry resistors and every other two ended part. Somebody saying a board is all 0402 is telling you how small the parts are, and nothing about what they do.
If I cannot solder a QFN, is that chip off limits to me?
No, because somebody else will have soldered one already. A breakout board is a small piece of board with the awkward package fitted in the middle. Round its edge is a row of ordinary holes, 2.54 mm apart, the same spacing as the header pins along the edge of a board. The difficult joints are made by machine in a factory, and what reaches you is a part with holes big enough to push a wire into.
That is how most radio chips and most sensors get onto a home bench at all. You pay several times the price of the bare chip for the extra board and the fitting, which is a fair trade the first hundred times. The same trick puts fine pitched chips on a breadboard, the plastic block full of holes 2.54 mm apart that parts push into. Nothing else would get pads a fraction of a millimetre wide into holes that size.
Every pin on a package has a number. The datasheet is the maker's own sheet of drawings and figures for that part. It says what each number is for: which pin takes the supply, which takes ground, which one your button goes on. The numbering starts at pin 1 and runs round the part anticlockwise. Nothing about the metal says which end is which, because the legs at one end look exactly like the legs at the other.
So the package carries a mark, and that mark is the only thing on the part that says where the numbering starts. It might be a notch bitten out of one end, a printed dot in one corner, a bevelled edge down one side, or a corner cut off. Whichever it is, it sits beside pin 1. Solder a chip in turned round from the way the board was drawn for it and the supply arrives on the pin that expected ground. That is usually the end of the chip.
Why does swapping the supply and ground kill a chip so quickly?
Every pin has a pair of protection diodes built in behind it, one up to the supply rail and one down to ground. A diode is a one way valve for current, the same part you fitted across the motor switches in Step 8. In normal use both valves are shut. They are there to catch a stray voltage that climbs above the supply or drops below ground, and steer it away before it reaches anything delicate.
Put the part in backwards and the two rails are pushed on the wrong way round. That leaves those valves pointing the way the current wants to go, so the whole supply pours through them and through the metal threads inside the chip. Nothing is limiting it. Those threads are a fraction of the width of a hair, they heat in milliseconds, and something inside opens or melts before you have finished noticing the smell.
Find the one number in a datasheet that decides your circuit
Behind every chip is a document written by the company that makes it, called a datasheet. It runs to tens of pages of tables, and most of it will never matter to you. Nobody reads one from the front. A datasheet is a thing you search, and for a first circuit you are searching for four numbers.
Here they are. How much supply voltage the chip is designed to run on. How much current one pin may carry. How much current all the pins may carry added together. Those three sit in a block called the recommended operating conditions, and that block is where a design is meant to live. The fourth thing to find is a different block near the front, the absolute maximum ratings: the voltages and currents at which the chip is damaged. That block is the one people misread. An absolute maximum is not a setting. It is not a target. It is the edge of the map, and past it the sheet has stopped describing a chip that works.
Three habits make a single row readable. The columns headed min, typ and max are promises about every chip of that kind, not measurements of the one on your desk. Typ is what a middling chip does, and it is promised to nobody. Min and max are the edges the maker will stand behind. A symbol starting with V is a voltage and one starting with I is a current, on every sheet you will ever open. And an I/O pin is an input/output pin, meaning any of the ordinary pins your program can drive or read. Two legs are not I/O pins: VCC is the leg the 3.3 volt rail comes in on, and GND is the ground leg.
What does "damaged" actually mean? Does the chip explode?
Almost never. The thread of metal joining a pin to the outside world is thinner than a hair, and current warms whatever it flows through. A little warmth is nothing at all. Past the absolute maximum the heat arrives faster than the chip can shed it, and something inside melts, or the material around it changes for good. No bang, no smoke, usually nothing to see.
The nasty part is the timing. Sometimes the pin is deaf the moment you switch on. Sometimes the board works for a month, and then one pin stops while the rest of the chip carries on. You spend a weekend hunting a bug in a program that was never wrong. That is why "it worked when I tried it" says nothing at all about a rating, and why this block sits near the front of the sheet.
The rows say DC current, in or out of a pin. In or out of what?
DC is short for direct current, meaning current that flows steadily in one direction. The electricity in a wall socket does not: it goes back and forth fifty times a second, and that is AC. Everything on this board is DC. A row that also says continuous means the current may run that way for as long as you like, which is a much harder promise than surviving a flick.
Out of a pin and into a pin are both real. An LED wired from a pin down to GND lights when the pin is HIGH, and the current runs out of the pin: that is called sourcing. Wire the same LED from the 3.3 volt rail down to the pin instead. Now it lights when the pin is LOW, with the current running into the pin and away through the ground leg: that is sinking. Some chips can sink more than they can source and print two rows. This one prints a single row for both, which is why it is worded in or out.
Why is there a limit for the whole chip as well as one for each pin?
Count the metal. Every I/O pin draws its current from the same place: one leg marked VCC, and one thread of wire from that leg into the chip. Whatever all your pins take at once passes through that single leg, and so does the current the chip uses to run itself. The same is true on the way home through GND. Those legs and the black body they are set into, are called the chip's package. The package has limits of its own. They owe nothing to how cleverly you spread the load.
So a row of modest pins can kill a chip that no one of them could. Moving a load onto more pins helps the per pin row and does nothing whatever for the total row. A gadget that has to light more lamps than the total allows lights them a few at a time and cycles round fast. An eye sees them all lit at once, which leans on the same fact about you as the dimming in Step 6.
If a row allows a current, why do designers stay well under it?
Because the row is a promise about the chip, and everything else in the circuit is only roughly what it claims to be. A resistor sold as 220 ohms carries a tolerance, often 5 in every 100 either way. The one in your hand may really be 209 ohms. A supply called 3.3 volts may sit a little above that. The room may be warmer than the temperature printed at the top of the electrical block and a hot chip is allowed less than a cool one.
The space you leave between your design and the row is called margin. Aim exactly at a limit and the average part passes while some ordinary fraction of them fails. Those fail in somebody's house rather than on your bench. Deliberately working a part below its ratings is called derating, and using about two thirds of a limit is a common habit rather than a rule.
Move a byte over four wires, and see why it beats two
Two wires were enough in Step 11 because a thermometer has almost nothing to say. A few bytes, once or twice a second. Ask the same pair for a whole screenful of dots twenty times over and they run out of second. Screens and memory cards use a different bus, called SPI. It spends wires to buy speed, and there are four of them. SCK is the clock, and the board makes it. MOSI carries bits from the board out to the chip. MISO carries bits from the chip back to the board. CS is chip select, one wire per chip.
Chip select is the whole of addressing here. The board pulls one chip's CS low to mean this message is for you. Nothing else is needed: no address byte at the front, and no ACK tick, which on I2C is the slot where the chip pulls the wire down to say it heard you. Each side holds its byte in a shift register, which is eight bits in a row. On every clock pulse it pushes one bit onto its own wire, top bit first, and takes one bit in off the other wire. So a byte goes out and a byte comes back over the same eight pulses, both at once. That is called full duplex, and one wire cannot do it. On I2C the question and the answer take turns on SDA.
Two things have to match at both ends, and neither is carried on any wire. The first is which voltage the clock rests at while nothing is happening. The second is whether a side reads the data wire on the first edge of a bit or on the second. Two choices with two answers each make four, numbered mode 0 to mode 3, and both ends have to pick the same number. Nothing complains when they do not. The clock stays clean, the wires still wiggle, and the byte is wrong. Or worse: one side reads the wire at the very instant the other side changes it. The first lab measures that. It reports the margin on every read, meaning how long that voltage had been sitting still before anybody looked at it. Margins are in nanoseconds and a thousand of them make one microsecond.
If I only want to read a byte from the chip, what am I sending on MOSI?
Something, whether you meant to or not. The clock belongs to the board, and while it runs both shift registers shift. When the board wants only the chip's answer, it sends filler, usually 0xFF or 0x00. A chip that is expecting to be read ignores whatever arrives. It works the other way too: when the board wants only to send, an answer comes back anyway, and the program throws it away.
That is why the operation has a different name here. A program talking to an SPI chip does not read or write, it transfers, because one byte out and one byte in is the smallest thing that can happen. The first lab shows both bytes moving at once. The one going out is the row of squares you can click. The one coming back is chosen from the menu underneath.
Why are there four modes, when one would have done?
Because nobody ever settled it. SPI was described by one company and copied by everybody. No committee ever said which edge was the right one, so parts shipped with all four combinations, and all four are still on sale. A datasheet writes the two choices as CPOL and CPHA. Polarity is the level the clock rests at between bytes. Phase is whether the reading edge is the first edge of a bit or the second one.
They are two questions rather than four arbitrary numbers, which makes them easy to keep straight. Polarity is the top half of the mode number and phase is the bottom. Mode 0 is both zero. Mode 1 keeps the resting level and moves the reading edge. Mode 2 moves the resting level and keeps the edge. Mode 3 moves both. You never have to guess which one a part wants: it is printed in the datasheet, and setting it is one line in your program.
Why is a link with no margin worse than one that is plainly broken?
A read with no margin is a race. One side is changing the wire at the instant the other looks at it, and which value wins is decided by a few nanoseconds inside two parts that were never promised to agree. It can come out right. It can come out right every single time, on the board on your desk, with short wires, in a warm room.
Then the next board off the shelf has a slightly quicker chip on it, or the wires are longer, or the room is cold. Now it comes out wrong once an hour. A fault that shows up in one unit in ten costs far more than one that shows up in all of them, because the units that work are what stop anybody looking. The first lab will not name a winner for a read with no margin. It prints both possible bytes and says that nothing in the wiring chooses between them.
So which bus do I actually use, and what is UART?
Three questions decide it: how many chips, how fast, and how many pins you can spare. I2C is two wires however many chips hang off them. It pays for that with an address and an ACK on every message, so it suits several slow parts. SPI is three shared wires plus one chip select each. It buys back the address, the ACK and the waiting, so it suits one or two fast parts, such as a screen or a memory card. UART is the third one you will meet. The second lab puts it in the table beside the other two, and Step 21 is about it.
A UART link has no clock wire at all. It is one wire each way between two devices, with both ends agreed beforehand on how fast the bits will come. That is what the cable from a board to a computer uses to carry printed messages. There is no address, because there is nobody else on the wire to address. Nothing is shared either, so a second device costs two more pins and another link inside the chip. Chips are built with only a couple of those, and that limit arrives long before you run out of pins.
Send characters down one wire to a computer
You cannot see inside a running chip. It has no screen, and no window into its memory. So when a program does something you did not ask for, the way to find out why is to make the board tell you. It talks down a single wire, out of the pin marked TX, with the shared 0 V as the other side of the circuit. A program on your computer draws the characters as they arrive. Printing like this is how most faults in real embedded work get found, and it needs no extra hardware.
The bus in Step 11 came with a clock. SCL ticked, and every chip looked at SDA on each tick. The four wires in Step 20 had a clock too. This wire has none. It rests high. To send one character the sender drops it low for one bit time and a bit time is however long it has been told to hold a single bit. That first low is the start bit. Then come the eight bits of the character, one bit time each, smallest first. Then the wire goes back high for one bit time. That is the stop bit. Ten bit times and the character is gone. The part of the chip that does the counting is called a UART and the whole arrangement is often just called serial.
Nothing on the wire says how long a bit time is. Both ends have to be told the same number beforehand. That number is in bits per second, and it is called the baud rate. Tell them different numbers and the listener does not fall silent. It does not complain. It sees the fall. It counts out ten bit times of its own, at the wrong length. Its looks land in the wrong places, so it hands your computer the wrong numbers. What arrives is not nothing. It is text that is almost right, or rubbish.
With no clock wire, how does the far end know when to look?
The fall at the front of a character is the only event it ever gets. From that moment it counts its own bit times. It looks at the middle of each one, not at the edges. The middle is the point furthest from both edges. So the two ends can be a little out of step, and the look still lands inside the bit it belongs to.
How far out they may be follows from that. The tenth look is the last, and it comes nine and a half bit times after the fall. A small difference in speed has all nine and a half of them to pile up in. Once the pile-up passes half a bit time, that look crosses into the bit next door and the character changes. Then the next character starts the counting again from its own fall. That is why a long line is no worse than a short one: the error never gets more than ten bit times to grow in.
Where do numbers like 9600 and 115200 come from, and what does 8N1 mean?
A chip makes a bit time by counting ticks of its own clock. Only certain lengths come out exactly right, so everyone settled long ago on the same short list of speeds. 9600 is the old safe default, and 115200 is the usual fast one. Neither number is special in itself. A bigger one holds each bit for less time. More characters fit into a second, and there is less room for the two ends to disagree.
Serial settings are usually written as the speed and then 8N1. That is eight data bits,
then N for no extra check bit, then one stop bit. The check bit it is refusing is a parity bit. Some
setups add one so that a single flipped bit can be spotted. Nothing here sends one, and 8N1 is
what almost everything uses. Both ends have to agree on all three parts, not only the speed.
My computer has no pins like that. How does the wire get in?
On most boards a second small chip sits next to the main one. It turns this stream of bits into USB. So the cable you already plug in to power the board is carrying it. With a bare chip you add a small adapter that does the same job. Either way, the wire in this step is the wire, with a translator on the end of it.
At the computer, a terminal program draws one character for every number that arrives. It has a speed setting of its own, and that setting is the one people forget. The board gets changed to a new speed. The program on the computer is left at the old one. The screen then fills with nonsense from a board that is working perfectly.
Why bother with a stop bit, if the wire goes high again anyway?
It does two jobs. The first is to guarantee a high before the next character, so that the next fall is a real fall. Characters follow each other with no rest. Without that guaranteed high, two in a row could run together, with no edge between them for the listener to start from.
The second job is a free check. The tenth look expects to find the wire high. If it finds it low, the ten bit times did not line up with the ten that were sent. A real chip reports that as a framing error, and many programs answer by throwing the character away. The stop bit is where a mismatched speed shows up first, because the tenth look has had longest to drift.
Give the board power that does not sag when a motor starts
Every step so far has assumed 3.3 volts arrives on the board and stays there. It does not arrive on its own. What you have is a USB cable at about 5 volts, or four AA cells at 6, or a motor supply at 12. None of them hold still. The part that turns whatever you have into a steady 3.3 volts is called a regulator, and every board in this course has one.
There are two ways to build a regulator, and they fail differently. A linear one behaves like a resistor that adjusts itself. It holds its output at 3.3 volts and every volt it does not pass on it turns into heat. How much heat is one multiplication. Volts multiplied by amps gives watts and a watt says how fast energy is turning into heat. So the heat is the volts thrown away, times the current going through.
A switching regulator chops instead. It connects its input to a coil for part of each cycle, the way PWM chops a pin in Step 6. The coil carries the energy across the gaps, and almost nothing is thrown away. The price is an output that is never quite still. It is being topped up and allowed to fall again, thousands of times a second.
Why not just use a resistor to get from 5 volts down to 3.3?
Because the voltage a resistor drops depends on the current through it. That is the divider from Step 10, doing exactly what it did there. A board might take 30 milliamps while it is idle and 300 with its radio on. A resistor picked to drop 1.7 volts at 30 milliamps drops ten times that at 300, and the rail would move every time the board did anything.
A regulator watches its own output instead, and adjusts thousands of times a second, so the output stays put while the current wanders. What it cannot do is invent voltage. Both kinds need their input to sit above their output by some margin. Below that margin the output simply follows the input down.
Why does a warm regulator matter if it is still doing its job?
Because the heat has to go somewhere, and a part soldered flat to a board has almost nowhere to put it. A small package with no metal bolted to it warms up in proportion to the watts it is burning. Double the waste and you roughly double how far above room temperature it sits.
Past a temperature printed in its datasheet, the part turns itself off to save itself. The rail collapses and the board stops. Then the part cools and starts again. What you see is a device that works for half a minute, dies, and comes back. Nothing in the program is wrong, and reading the program will never show you that.
If the switching kind wastes so little, why does anybody still fit a linear one?
Cost and quiet. A linear part is a component with three legs and a capacitor at each end. A switching one needs a coil, more parts around it, and a careful board layout. It costs more. At small currents the heat a linear part makes is nothing worth avoiding.
The quiet matters when the board is measuring something. Chopping leaves a small wobble on the rail, called ripple, and the converter from Step 10 measures against that same rail. A reading taken at the wrong moment in the wobble comes back a count or two out. The lab below gives the ripple in millivolts and in converter counts, so you can see whether it would reach the number you care about.
Now the failure that catches people out. A motor at the instant it starts is not turning yet. Nothing inside it is pushing back, so it takes several times the current it takes at speed. That gulp lasts a few thousandths of a second. Feed the motor from the chip's own 3.3 volt rail and the regulator reaches the most current it will give, and the rail falls. Every chip has a line drawn across its own supply voltage, and below that line hardware inside the chip holds it in reset. That is the same full restart the watchdog causes in Step 14. A supply that dips through that line has a name, a brownout, and from the outside it looks exactly like a firmware bug.
Two things fix it. The first is a capacitor, which is two metal plates with a gap between them. It holds a small amount of electricity, and gives it back the instant the voltage across it starts to fall. That makes it a tiny local reservoir. One fitted right next to a chip for that job is called a decoupling capacitor, and its size is measured in microfarads, millionths of a farad. The second fix is to stop the motor drinking from the chip's rail at all, and give it a supply of its own.
Why does a motor take more current starting than it does running?
A motor turns because current in its coils makes a magnetic field, which is the switchable magnet of Step 9. Once the rotor is moving, those coils are dragged past a magnet. That produces a voltage of their own, pushing back against the supply, and the push-back is most of what limits the current in a running motor.
A rotor that has not started yet pushes back with nothing. The only thing limiting the current is then the resistance of the winding, which is small. For a little motor that is several times the running current. It dies away over a few thousandths of a second as the rotor picks up speed. A rotor that is jammed never picks up speed, so its current never dies away at all.
If the motor has its own supply, why does the chip's rail still move at all?
Because the two supplies still share a ground wire. They have to. Two circuits with no shared ground have no agreement about what 0 volts means, which is the point made about the servo in Step 7. Every amp the motor takes comes home along that shared wire.
A wire is a resistor with a very small value, a few hundredths of an ohm for a short thick one. Current times that small resistance is a small voltage. It appears as the ground under the chip lifting while the motor pulls. The chip cannot tell that apart from its supply dropping, because it only ever sees the difference between its two pins. Short and thick is the whole of the fix, and the lab below measures what is left.
Connect a 5 volt part to a 3.3 volt pin without killing it
Two boards on one bench do not have to agree about voltage. Your board's pins swing between 0 volts and its own 3.3 volt rail. The display or the older sensor you have just bought may run on 5 volts, and plenty of small sensors and memory cards run on 1.8. Join a pin on one to a pin on the other and two different rails now meet on one piece of wire. The two directions across that join are different problems, and only one of them destroys anything.
Going up, into a part that runs on more than you do, is a question about reading. An input decides between a 1 and a 0 by holding what arrives against two voltages out of its own datasheet. VIH is the level it promises to read as a 1. VIL is the level it promises to read as a 0. Between the two it has promised nothing, and either answer can come back. So there is no general answer to whether a 5 volt part reads 3.3 volts as a 1. There is an answer for the part in front of you, and it is a number on the sheet. Parts built to the older TTL numbers put the bar for a 1 a little above 2 volts, whatever rail they run on, and 3.3 clears that easily. Parts built to CMOS numbers set the bar at around seven tenths of their own rail. On 5 volts that is above 3.3, so the same wire tells them nothing.
Going down, into a part that runs on less, is not about reading at all. Every input pin has a diode built into it, joining the pin to its own rail. A diode is a one way valve for current, the part Step 8 fitted across a motor's switches. This one is there for small accidents. A brief spike off a long wire gets passed up into the rail instead of into the chip, and the diode can carry a few milliamps while it does that. A 5 volt output on a 3.3 volt pin is not a brief accident. It holds that pin over its absolute maximum, the block of numbers where a datasheet stops describing the chip and starts describing damage. It holds it there for as long as the thing is plugged in and the diode is what gives out. There are three ways to stop that and the lab has all three. Fit nothing at all, when the numbers allow it. Fit two resistors sharing the voltage, the way the divider in Step 10 shared it. Or fit one small transistor with a pull-up on each side, which is what sits on a board sold as a level shifter.
Where do the TTL and CMOS numbers come from?
TTL is the older family, named after the way those chips are built inside. Its two numbers are fixed and they are low, and they were settled by parts made in the 1970s. Chips made today keep to them so that old and new can be wired together. That is the whole reason a 5 volt part can be perfectly happy with 3.3 volts arriving: a decision taken half a century ago.
CMOS chips set both numbers as a fraction of their own rail instead, around seven tenths for a 1 and three tenths for a 0. Move such a part onto a 3.3 volt rail and its bar for a 1 comes down with it. Leave it on 5 volts and the bar sits above anything your board can send. The two buttons in the lab load each pair and print the numbers they are loading.
Where is this diode, and why has nobody mentioned it until now?
Because nothing has gone near it before. Every input pin has a pair of them, one up to the rail and one down to ground. They carry no current at all while the voltage on the pin stays between the two rails, which is where a pin normally lives. They start carrying only when something pushes the pin outside that range.
Once the pin is pushed above its rail, the diode holds it a little above the rail and will not let it climb further. Everything above that has to be dropped across whatever resistance sits in the path, and that is what decides the current. Current is the thing the diode has a rating for. So the same 5 volts is survivable through a large resistor and fatal through a plain wire, where the only resistance is the sending pin's own few tens of ohms. The lab works the current out for you, and names the rating when you go past it.
Why would putting a resistor in the way make a signal late?
A pin and the wire on it behave like a very small capacitor, the part from Step 22: two pieces of metal near each other holding a little charge. Before the voltage at the pin can change, that charge has to be moved in or out. The amount is tiny, measured in picofarads, which are millionths of a microfarad. The line above the trace in the lab says how much this model allows for a pin with a short jumper wire on it.
Resistance limits how fast charge can move, so it sets how long that filling takes. A pin driving directly has only its own few tens of ohms in the way, and the edge takes nanoseconds, thousandths of a microsecond. Put a 10 kilohm resistor in the way, a kilohm being a thousand ohms and the same edge takes hundreds of times longer. None of that matters while each level lasts a millisecond. When a level is shorter than the edge, the voltage is still on its way up when the receiver looks and the receiver reads whatever has arrived so far.
Why not fit two resistors everywhere and be done with it?
Three reasons, and the lab measures all three. A divider only works one way, from a higher rail down to a lower one, so it can never lift 1.8 volts to what a 3.3 volt CMOS input wants. It pulls current the whole time the line is high, which a battery notices. And it slows every edge, which costs nothing on a button and loses bits on a fast bus.
The transistor answers all three, and it works in both directions on the same wire. That last part is what I2C needs. The ACK tick in Step 11 was the sensor pulling SDA down, so both ends drive that one wire and a one way divider would be no use there. What has to be right when you fit one is which side is which. The deciding leg goes to the lower of the two rails and the leg marked source goes to that same lower side. The transistor drawn in the lab is marked BSS138, which is the part number most of these little boards carry. For a fast one way line, an SPI clock at several megahertz, people fit a chip made for the job instead.
Put it together: a line follower and a weather station
Two projects, and between them they use most of this course. Neither one is finished, and neither pretends to be. Each widget ends with a paragraph saying what it leaves out. It names the missing piece and the course that deals with it, and that list is worth as much as the project above it.
The first is a robot that follows black tape across a pale floor. It has two wheels, each driven through the H-bridge from Step 8. It has two sensors pointed at the floor just in front of them. A floor sensor is an LED shining down with a light sensor beside it, the kind Step 10 turned into a number. Black tape sends almost none of that light back, and pale floor sends back plenty. The converter answers with a count from 0 to 1023: 0 for bare floor, 1023 for a spot sitting completely over the tape.
The steering rule is one subtraction. Take the left count away from the right count. Zero means the tape is centred between the two sensors, so drive straight on. If the right sensor is the darker one, the tape has gone off to the right, so steer right. Steer harder the further apart the two counts are. How much harder is one number, called the turn strength here. Picture one sensor completely over the tape and the other on bare floor: the turn strength is the turn it asks for then, in degrees a second. Steering now changes what the sensors see, which changes the steering. That circle, where what a program does comes back to it as its next reading, is a closed loop. This is the first one in the course.
Why two sensors? Would one not do?
One sensor tells you how dark the floor is under it, and nothing else. A reading halfway between tape and floor means the edge of the tape is under the spot. That is equally true whether the tape has slipped to the left or to the right. A robot with one sensor has to hunt: swing one way, see whether the reading got darker, swing back if it did not. It works. It wags all the way down the straights.
Two sensors give a difference, and a difference has a sign. Whichever of the two is darker is the side the tape is on. There are two ways to get a difference of zero, mind. One is dead centre. The other is both spots out on bare floor with the tape nowhere near. The difference cannot tell those apart, but the two readings can, and when both stay near zero for long enough the robot below stops and says it has lost the tape.
If the rule is right, why does it wobble at all?
Because nothing in a real loop happens at the moment it is decided. The sensors sit in front of the wheels, so the robot feels a corner before the wheels reach it. The reading the program steers on was taken a few passes round the loop ago. The wheels then take a moment to settle to the new speed they have been given. Every correction arrives after the situation that asked for it.
A late correction that is small does no harm. A late correction that is large arrives once the error it was meant to cancel has gone. It carries the robot past the middle the other way, and the next one carries it back. That is the snaking you can produce in the lab below, and turning the steering up makes it worse rather than better. Measuring how late a loop is, and choosing the steering to match, is the whole of Control Systems.
The second project has no wheels and changes nothing. It asks the BME280 from Step 11 for a temperature and a pressure over the two wire bus, over and over, all day. It smooths the readings with one of Step 16's filters, keeps the newest handful, and throws the rest away. A device that measures and records but never acts is a data logger. The boards left in plant pots, on hillsides and inside walls are nearly all loggers.
Two things about it matter more than any single reading. The first is that a reading can arrive damaged. One bit of a byte flips on the way, or one of the two bytes never turns up and the program uses a zero in its place. Nothing in those bytes says which has happened, so a temperature that never existed goes into the log looking exactly like weather. The second is that the store is small, so most of the day gets thrown away. What is left is whatever the logger worked out while each reading was still in its hands.
Why throw readings away? Why not keep the whole day?
Because the memory on a chip like this is a few tens of thousands of bytes, and the program's own variables live in it too. Four bytes a reading sounds like nothing until the device is meant to sit there for a year. Sooner or later every logger has to decide what to forget.
The usual choice keeps two things. First, the newest few readings, so that recent history can be looked at. Second, a handful of summaries brought up to date as each reading arrives: the biggest jump so far, a running total, the highest and the lowest. Each of those is a couple of numbers being compared and replaced, so they cost the same after ten thousand readings as after ten. What you give up is any question you did not think of in advance, because the readings that would have answered it are gone.
How can smoothing ever make the answer worse?
A filter that leans on the last few readings answers with a mixture of them. Its answer therefore describes some moment part way through that stretch, not now. While the weather is barely moving, and the wobble is the only thing changing, that is a good trade. Lean on more minutes than the weather stays still for and the filter is reporting a temperature that has already gone.
Step 16 measured that trade on a run of readings. The lab below is harder on it. The weather in there was invented by the simulation, so the widget knows the true temperature, and it holds both the raw number and the kept number against it. No real board can do that. Working out what a sensor's answer means when nobody knows the true value is calibration. It is its own subject.
Follow the chip from reset to your first line of code
Pressing reset does not jump straight to setup() or main(). The processor first
reads a vector table at an address fixed by the chip. That table gives it an initial stack pointer and
the address of a reset handler. The handler selects clocks, copies variables with initial values from flash
into RAM, clears zero-initialised RAM, prepares the language runtime and only then calls the application.
The reset-cause register says whether power-on, a watchdog, a brownout, a pin or software caused the restart. Read it early and save it in a boot record. Otherwise a board that “restarts sometimes” erases its best clue on every boot. A fault handler needs the same discipline: preserve the stacked program counter, fault status and software version before resetting or entering a safe state.
Why does a program fail before main?
A bad vector address, wrong linker script, unsupported clock setup, stack outside RAM, failed memory copy or a constructor can all fault first. Attach the debugger at reset, inspect the program counter and vector words, then step through startup. Do not keep adding prints; the serial port may not exist yet.
Budget flash, RAM, stack and heap separately
Flash normally holds code and constants when power is off. RAM holds writable globals, stacks, heaps and peripheral buffers while the chip runs. A program can fit in flash and still crash because a nested interrupt, a large local array or a communications burst pushes the stack into other RAM.
Read the linker map after every release build. It names each section and symbol instead of giving one vague percentage. Avoid unbounded allocation in long-lived firmware; if a heap is needed, test fragmentation and allocation failure. Fill unused stack with a known byte at boot, then inspect its high-water mark after worst-case work and nested interrupts. Keep margin for library updates and failure logging.
What do memory-mapped registers have to do with RAM?
The processor uses addresses for both. Some ranges select RAM or flash; other ranges select peripheral registers. A write to a GPIO register changes hardware rather than storing an ordinary variable. Use the vendor header and the right access width. Reserved bits must be kept at their documented values.
Sample an analogue signal without inventing a new one
An ADC compares an input with a reference and returns one of a fixed number of codes. A 12-bit converter has 4096 levels, but that does not make it accurate to 12 bits. Reference tolerance, input noise, offset, gain error, board coupling and calibration all remain. The ADC’s sample capacitor also needs time to charge through the sensor’s source resistance; a high-resistance source may need a buffer or longer acquisition.
Filter the analogue signal before sampling. Any energy above half the sample rate can fold into the kept band as an alias. Sample faster than the signal plus a real filter transition, keep timestamps, and average only when the quantity can tolerate the added delay. Use an external reference or ratio measurement when supply movement would otherwise look like sensor movement.
Why does the first reading after changing channels look wrong?
The ADC’s small sampling capacitor still holds charge from the previous channel. It must settle toward the new voltage through the new source. Data sheets specify acquisition time and source impedance. A dummy conversion, slower ADC clock or buffer may be required; test across the full input range.
Keep the files that explain what the compiler put on the chip
The compiler turns each source file into machine code and relocation records. The linker combines them according to a linker script, places vectors and sections at real addresses, and emits an ELF file. Keep that ELF, its symbols, the map file, exact compiler flags, libraries and board definition with each released image. A bare binary can be flashed, but it cannot explain itself during a crash investigation.
Use warnings as errors for the warnings the project understands, static analysis for paths the compiler misses, and unit tests for pure logic on the host. Then use SWD or JTAG to halt at reset, inspect registers, set watchpoints and view memory without changing timing as much as print statements do. A logic analyser checks buses and pins; an oscilloscope checks voltage, edge shape and timing. Pick the tool that sees the failing layer.
Why does a release build behave differently?
Optimisation changes instruction order, removes unused reads and keeps values in registers. Undefined behaviour and data races often surface here. Reproduce with the exact release flags, keep debug symbols, inspect the disassembly around the fault, and fix the invalid assumption rather than disabling optimisation.
Use an RTOS when explicit tasks make the timing easier to prove
A timer-driven main loop can run many products. An RTOS becomes useful when several jobs block on different events, need clear priorities, or come from separate components. It provides tasks, queues, semaphores, timers and a scheduler. It does not create more CPU time, fix an overloaded design or make shared data safe by itself.
For each task, write its worst execution time, period or minimum arrival time, deadline, priority, stack and blocking resources. Utilisation is a first check. Under classic rate-monotonic assumptions, three independent periodic tasks are guaranteed schedulable below about 77.9%, but systems above that can still pass a full response-time calculation. Include interrupt work, critical sections and release jitter.
What is priority inversion?
A high-priority task waits for a lock held by a low-priority task, while a medium-priority task keeps pre-empting the low one. Priority inheritance can raise the lock owner long enough to release it. Better designs also keep critical sections short and avoid sharing resources across unrelated deadlines.
Count every wake-up before claiming years of battery life
Low-power firmware turns off clocks and peripherals, chooses a sleep state, and wakes from a timer or external event. Average current includes sensor warm-up, measurement, computation, radio search and retry, flash writes, regulator quiescent current and sleep leakage. A wake pin left floating can destroy the budget by waking the chip all night.
Measure a current trace over normal cycles and rare faults. Batteries also have peak-current limits, temperature loss, self-discharge, ageing and a voltage curve; the board may brown out before nominal mAh is used. Test an old/cold cell at the worst transmit or motor pulse, and log wake reasons so unexpected energy has a cause.
Why can a low-dropout regulator dominate sleep?
The MCU may sleep at a few microamps while the regulator itself draws tens or hundreds. Add every part’s quiescent current, pull-up current and leakage. A power switch for a sensor helps only if its own leakage and the sensor’s restart energy make the trade worthwhile.
Write persistent state so a power cut leaves one valid answer
Flash and EEPROM wear out after a finite number of erase or write cycles, and a power cut can stop a write halfway. Do not overwrite the only copy of a calibration, counter or configuration in place. Keep at least two records with a version, length, sequence number and CRC. Write an inactive record completely, verify it, then make one small commit marker visible.
On boot, validate both records and choose the newest complete one. Handle sequence wrap and unknown future versions. Rate-limit writes, spread them across pages when needed, and test by cutting power at every byte or state transition. A CRC detects accidental damage; a cryptographic signature is needed when an attacker may change the data.
Why not save the counter after every loop?
It can exhaust endurance quickly and wastes energy. Save only when the application can justify the wear, batch changes, or use a journal/wear-levelled store. If exact monotonic counting matters for security, use hardware designed for it or a protocol that cannot move backwards after a reset.
Route current paths, not just named wires
A schematic says which nodes connect. A PCB also decides the size and shape of every current loop. High-frequency current takes the nearby return path set by fields and plane geometry, not the route that looks shortest on a printed net list. Put a small decoupling capacitor close to each power pair so fast current circulates locally. Keep motor and switcher loops compact and away from sensitive ADC returns.
Protect external connectors where they enter the board. Add the clamp, current limit and filter needed for the expected ESD, surge or wiring fault. Check creepage, clearance, connector ratings and thermal rise for the product’s voltage and environment. Use a near-field probe and spectrum analyser for pre-compliance work, then run the required EMC and safety tests on the final enclosure and cables.
Why did the prototype pass but the enclosure fail?
Cable length, ground bonds, enclosure seams, battery leads and display windows change antennas and return paths. Test the assembled product in each operating mode. Record clock rates, radio channels, motor loads and cable arrangement so a failing emission can be reproduced.
Keep device identity, boot code and updates in one trust chain
A product needs a unique device identity, protected key storage and a record tying the physical unit to its manufacturing history. Avoid one secret copied across the fleet. Secure boot starts from a small root of trust, verifies the next image and checks that it targets the right hardware. Rollback protection stops a signed but vulnerable old release from returning.
An update uses a signed manifest, resumable chunks, a complete-image hash, an inactive slot and a trial boot with rollback. Decide who may use debug access after manufacture and how a failed unit can be analysed without exposing every device. Threat-model physical access, faulty inputs, network commands and the factory test path. The Connected Devices course follows these controls through a fleet.
Does secure boot make firmware safe?
No. It proves that an allowed signer approved the bytes and that the image meets policy. Bugs, unsafe commands and stolen signing keys remain possible. Keep signing keys offline or in a managed hardware-backed service, require review, and make revocation and recovery part of the release plan.
Generated firmware and TinyML models must meet the same bench evidence
An AI coding tool can draft a driver, state machine or test, but it does not know the exact board unless the requirement, data sheet, pin map and failure rules are supplied and checked. Review register writes, buffer bounds, integer widths, interrupt safety, timeouts and error paths. Compile with warnings, inspect the map, run deterministic tests, then compare logic traces and current/voltage measurements with the requirement.
A TinyML model adds a sensor contract, fixed preprocessing and an uncertain output. Budget its flash, RAM, latency and energy. Test on people, devices, sites and noise conditions that were not used for training. Keep confidence limits, a safe non-model fallback and all electrical limits outside the model. Monitor field drift and keep the prior signed model available for rollback. The TinyML course develops training, compression and deployment in detail.
What should an AI-generated peripheral driver prove?
It should prove the same things as handwritten code: correct register sequence and reserved bits, bounded waits, bus recovery, concurrency rules, power-state transitions and known behaviour for every hardware error. A vendor reference driver is useful comparison evidence, not a substitute for the product test.