The previous lecture looked at the microcontroller from the outside. Now we open it up. We answer a question that looks simple but is not: what happens, physically and logically, between the moment we write a line of code and the moment an LED turns on. We go through four layers - the representation of information (bits, bytes, hexadecimal), memory (registers, SRAM, Flash, EEPROM), the central unit (ALU, the status register, the stack) and the clock - using, as always, the concrete figures of the ATmega328P.
1Purpose and structure of the course6 min
The answer passes through four layers. The first is the representation of information - the way a real-world quantity becomes a string of zeros and ones. The second is memory - the place where those strings sit and wait. The third is the central processing unit - the machine that reads them, transforms them and writes them back. The fourth is the clock - the periodic signal that gives all these operations their rhythm.
- A microcontroller embeds a processor, memory and peripherals on the same silicon
- ATmega328P: AVR architecture, 32 KB Flash, 2 KB SRAM, 1 KB EEPROM, 16 MHz
Learning outcomes
- Convert a value between binary, decimal and hexadecimal, and identify the active bits
- Distinguish control registers from status registers, and drive a pin through registers
- Compare SRAM, Flash and EEPROM by volatility, granularity and write cycles
- Explain the role of the flags in SREG and the mechanism of stack overflow
- Compare the von Neumann architecture with Harvard and explain the AVR's two-stage pipeline
- Calculate the number of instructions per second and the SRAM available for an application
2The bit, the byte and the three notations10 min
A microcontroller does not know what a number, a letter or a temperature is. All it knows is that at a given point in the circuit the voltage is close to the supply or close to ground - this binary choice is the only elementary information the hardware can hold unambiguously, and it is called a bit.
The fundamental group of eight bits is called a byte - 2⁸ = 256 distinct values, enough for the Latin alphabet (ASCII) and exactly the usual resolution of a simple ADC. Multiples form in powers of two: 1 KB = 1024 bytes, not 1000.
Three notations for the same value
| Base | Advantage | Disadvantage |
|---|---|---|
| Binary (0b...) | the only one where you see the pin directly | long, eight characters per byte |
| Decimal | the only one in which we can judge magnitudes | no visible connection to the bits |
| Hexadecimal (0x...) | each digit covers exactly 4 bits - mechanical conversion to binary | the digits A-F must be memorized |
A register holds the binary value 1011 0100. Express it in decimal and
hexadecimal, and state which bits are active.
See the solution
We number the bits from right to left, starting at 0: bit 7=1, 6=0, 5=1, 4=1, 3=0, 2=1, 1=0, 0=0.
Decimal: 2⁷+2⁵+2⁴+2² = 128+32+16+4 = 180
Hexadecimal: split into two groups of 4 bits - 1011 = 8+2+1 = 11 = digit B;
0100 = 4. This gives 0xB4. Check: 11×16+4 = 180. ✓
The active bits are 7, 5, 4 and 2. If this byte had been written into the direction register of a port, the four corresponding pins would become outputs, the other four would remain inputs.
3Registers and special function registers11 min
A register is a very high-speed memory cell, built right inside the central unit or a peripheral module, to which the processor has immediate access - built from flip-flops placed next to the arithmetic unit, so that reading or writing does not require a bus cycle. Precisely because they are expensive in silicon area, registers are few, while memory is cheap and abundant.
By function, registers fall into four families: data (operands and results), address (on AVR, the X, Y, Z register pairs allow traversing an array with automatic increment), control (settings that determine the behavior of a module) and status (report conditions).
A control register is written by the program and read by the hardware: we issue a command, the circuit executes it. A status register is written by the hardware and read by the program: the circuit reports what happened, we take note of it. Trying to start a converter by writing into its status register is as futile as trying to start an engine by moving the tachometer needle.
Special function registers (SFR)
These are not storage locations, but interfaces - each is wired physically to a concrete
internal circuit. On the ATmega328P, each port is served by three registers: DDRx (direction:
1=output, 0=input), PORTx (the output level, or enabling the pull-up for
input) and PINx (reading the real electrical state).
DDRB |= (1 << PB5); /* PB5 becomes an output */
DDRD &= ~(1 << PD2); /* PD2 becomes an input */
PORTD |= (1 << PD2); /* enable the internal pull-up on PD2 */
PORTB |= (1 << PB5); /* turn on the LED */
PORTB &= ~(1 << PB5); /* turn off the LED */
PORTB ^= (1 << PB5); /* toggle the LED state */
if ((PIND & (1 << PD2)) == 0) { /* button pressed = level 0 */
PORTB |= (1 << PB5);
}
(1 « PB5) builds a mask: 0b00100000. |= performs a bitwise
OR - bit 5 becomes 1, the rest stay untouched. &= with the mask negated by
~ performs an AND with 0b11011111 - bit 5 becomes 0, the rest untouched.
^= performs an EXCLUSIVE-OR, which flips exactly the bit covered by the mask.
The first form writes all eight bits, turning off everything else that was lit on the other seven pins of the port. In a small program, where port B serves a single LED, the mistake does not show. In a program where port B also drives a relay and an SPI line, the effect appears only after weeks, as an intermittent fault impossible to reproduce. The rule: modify the desired bit, do not rewrite the whole register - except during initialization, where a complete state really is intended.
4The memory of a microcontroller11 min
Memory is divided, by purpose, into program memory (the instructions, which must survive a power interruption) and data memory (variables, intermediate results, the stack - permanently modified, may be volatile).
Volatile memories
SRAM (static RAM) uses a cell of 4-6 transistors, wired as two inverters in a loop - the loop sustains itself as long as power is present, so it needs no refreshing and works at high speeds. The price: an SRAM cell takes up several times more silicon than a DRAM one, which is why it is used where speed matters and quantity is small - the cache memory of processors and the data memory of microcontrollers. DRAM uses a capacitor and a transistor - the cell is small (cheap per bit), but the charge leaks away, so the content must be refreshed periodically by a dedicated controller; it is the type used for the main memory of computers.
Non-volatile memories
| Type | Rewriting | Typical use |
|---|---|---|
| MaskedROM | impossible (mask set at manufacturing) | fixed firmware at very large volume |
| PROM | a single time (fuses blown) | small runs, identification data |
| EPROM | with ultraviolet light | prototypes, older systems |
| Flash | by page (block), tens of thousands of cycles | program memory of microcontrollers |
| EEPROM | by byte, ~a hundred thousand cycles | configuration parameters, calibration |
The number of write cycles is limited (~100,000 per location), and the write speed is low (on the order of milliseconds per byte). A program that saves a value to EEPROM on every pass through the main loop will exhaust the cell within hours of operation - if the loop runs at a few kHz, exhaustion comes in minutes, not years.
Flash memory is non-volatile and electrically reprogrammable, but erasing cannot be done on individual locations, only on entire blocks (pages) - which is why erasing is said to be non-selective. It does support a large number of cycles (on the order of tens of thousands for program memories), which is why most current microcontrollers use it to store the program: it allows in-circuit reprogramming, hence updating the firmware of a product already shipped.
5The central unit: ALU, SREG, the stack12 min
The central processing unit (CPU) fetches instructions from memory, interprets them and executes them, generating the signals that drive the rest of the circuit. It splits into the arithmetic and logic unit (ALU) and the control unit.
The ALU and the general registers
The ALU takes one or two operands and an operation code as input, produces a result and a set of flags. There are few, elementary operations: add, subtract, compare, AND, OR, EXCLUSIVE-OR, negate, shift, increment, decrement - everything that counts as complicated computation is built from these, through the program.
The status register (SREG)
On every arithmetic operation, the ALU updates SREG, whose bits are called flags: Z (zero), C (carry - carry out of the most significant bit), N (negative - copies the sign bit), V (signed overflow), S (corrected sign, N⊕V), H (half-carry between nibbles), T (general-purpose bit), I (global interrupt enable - if it is not 1, no interrupt is taken into account).
A conditional branch instruction does not look at values, but at flags: a comparison discards the subtraction of the two operands and lets the flags say whether the result was zero, negative or had a carry. If another operation slips in between a comparison and the branch that depends on it, the outcome of the branch becomes unpredictable - in C this does not concern us (the compiler handles it), but in assembly it is one of the most frequent sources of bugs, alongside forgetting to save SREG on entry into an interrupt routine.
The control unit and the stack
The program counter (PC) holds the address of the next instruction - it increments automatically after each fetch; jumps and calls work by writing a new value into the PC, the only branching mechanism.
On AVR, the stack starts at the highest SRAM address and grows downward; global variables occupy the low addresses and grow upward. If the program has too many variables, calls too many levels deep, or an unbounded recursion, the two areas overlap - the stack writes over the variables or vice versa. The result: a microcontroller that behaves chaotically, resets itself or executes code from an absurd address, with the symptom appearing far from the real cause.
6Buses, von Neumann and Harvard11 min
The central unit communicates with memory and peripherals through buses: address (unidirectional, determines the maximum addressable capacity - a bus of n lines addresses 2ⁿ locations), data (bidirectional, its width gives the architecture its name - "8-bit" means an internal 8-bit data bus) and control (read/write commands, clock, interrupts).
von Neumann versus Harvard
In von Neumann, a single shared memory for data and instructions, a single bus. Execution requires at least two stages (fetch+decode, then execute), and the two accesses crowd onto the same path - a phenomenon called the von Neumann bottleneck. Advantage: flexibility - the program can modify its own code.
In Harvard, the memory spaces for instructions and data are physically separate, each with its own bus - fetching an instruction and accessing a data item can happen simultaneously.
Pipeline and hazards
The idea of overlapping execution stages is called a pipeline, by analogy with an assembly line - several instructions are simultaneously in progress, each at a different stage.
| Cycle 1 | Cycle 2 | Cycle 3 | Cycle 4 | |
|---|---|---|---|---|
| Instr. 1 | Fetch | Decode | Execute | |
| Instr. 2 | Fetch | Decode | Execute | |
| Instr. 3 | Fetch | Decode |
Situations that stall the line are called hazards: data hazards (an instruction needs the result of an earlier, unfinished one), control hazards (at branches, the outcome of the condition is not known when the next instruction has already been fetched - if the branch is taken, the instructions fetched for nothing are discarded) and structural hazards (two instructions need the same resource at the same time).
Fetch and execute. The consequence of a control hazard shows up directly in the timing tables of the data sheet: a conditional branch instruction takes two cycles if the branch is taken, and a single cycle if it is not - because in the first case the already-fetched instruction must be discarded. A small detail, but it explains why the duration of a carefully written loop is not perfectly constant.
7The instruction set: RISC versus CISC8 min
The instruction set is the collection of basic commands a microcontroller understands - defined in silicon, through the structure of the decoder, and it forms the boundary between hardware and software.
| Class | AVR examples | Effect |
|---|---|---|
| Arithmetic, logic | ADD, SUB, AND, EOR, LSL | operate on two registers, update SREG |
| Data transfer | MOV, LDI, LD, ST | move a byte between registers or memory |
| Decision | RJMP, BRNE, RCALL, RET | modify the PC, conditionally or not |
| Bit-level | SBI, CBI, SBIC | set/test a single bit, without touching the rest of the byte |
RISC versus CISC
A RISC processor recognizes a small, uniform set of operations; complicated operations are obtained by combining them, so a RISC program is longer in instruction count - but the instructions, simple and of the same size, decode quickly and mostly execute in a single cycle. This makes execution predictable, essential in real-time systems.
A CISC processor has a rich set (over eighty instructions), many specialized, differing in format and duration - a CISC program is shorter, an advantage when memory was expensive, but the duration of each instruction varies and decoding is complicated.
8ATmega328P architecture10 min
The ATmega328P uses a modified Harvard architecture: the program and data spaces are separate, as in Harvard, but there is a path (the LPM instruction, the Z register) through which the central unit can read constants from program memory - without it, a table of constants would need to be copied into SRAM at startup, needlessly consuming a scarce resource.
Three memories, with distinct roles: Flash of 32 KB (16384 words of 16 bits, so the PC
has exactly 14 bits), split into an application section and a bootloader section (a few hundred
bytes, receives the new program over the serial port); SRAM of 2 KB, with a unified address
space (32 general registers, then 64 I/O registers, then 160 extended I/O registers, only then the actual
SRAM - which is why PORTB behaves in C like an ordinary variable); and
EEPROM of 1 KB, with a separate address space, accessed through three dedicated registers.
A program keeps the last 300 measurements (16-bit integers) and a serial buffer of 128 bytes. Estimate the free SRAM on an ATmega328P.
See the solution
The array: 300 × 2 = 600 bytes. The buffer: 128 bytes. Total: 728 bytes.
Out of 2048 available: 2048 - 728 = 1320 bytes remaining - apparently.
From these 1320, we must subtract: the global variables of libraries (on an Arduino board with serial, often over 200 bytes), the constant strings copied into SRAM, and, above all, the space needed for the stack in the worst-case call chain, including on entering interrupts.
The conclusion: the real margin is not 1320 bytes, but a few hundred. If the requirement grew to 800 measurements, the array alone would take 1600 bytes and the project would no longer fit - solutions: reducing resolution to 8 bits where accuracy allows, a moving average instead of the full history, or external memory.
The compiler cannot know how deep the stack will be called during execution - the symptom is the chaotic behavior described for the stack. A healthy habit: always check, after compiling, the memory usage report, and treat SRAM usage above ~75% as a warning sign.
9The oscillator circuit and the instruction cycle11 min
Without the clock signal, the microcontroller is not broken, it freezes: the PC no longer advances, instructions are no longer fetched. A microcontroller whose clock has stopped draws very little current - the principle behind power-saving modes.
| Source | Typical accuracy | Notes |
|---|---|---|
| Internal RC oscillator | a few percent | no external components, drifts with voltage and temperature |
| Ceramic resonator | a few parts per thousand | three terminals, built-in capacitors, low cost |
| Quartz crystal | tens of parts per million | needs two load capacitors, standard when timing matters |
A quartz crystal oscillates at its own mechanical resonance frequency (piezoelectric effect), determined by its dimensions - not by voltage or temperature, hence extremely stable. The ATmega328P has an internal 8 MHz RC oscillator, factory-calibrated, divided by eight by default (starts at 1 MHz unless told otherwise); the clock source is selected through permanent configuration bits (fuses).
The ATmega328P runs with a 16 MHz crystal. Estimate the number of instructions per second and the duration of a simple instruction.
See the solution
T = 1/(16×10⁶) = 62.5 ns
Most AVR instructions execute in a single cycle, so an addition between two registers takes 62.5 ns, and the microcontroller executes approximately 16 million instructions per second (16 MIPS) - an upper bound: SRAM accesses take two cycles (125 ns), conditional branches take one or two, hardware multiplication two cycles.
Practical consequence: a loop that toggles a pin through bit-level instructions needs a few cycles per iteration, so the maximum frequency generated by the program is on the order of a few hundred kilohertz. For faster or more precise signals, the program is not used, but the hardware counting modules (Lecture 06).
10Powering the microcontroller8 min
The ATmega328P accepts a wide supply range, roughly 1.8-5.5 V - but the voltage range and the maximum frequency are linked: transistors switch more slowly at low voltage, so at 16 MHz the chip needs at least ~4.5 V, and at 1.8 V the guaranteed maximum frequency drops to a few megahertz. Powering it at 3.3 V while clocking it at 16 MHz is a deviation from the specification that can work on the lab bench and fail at extreme temperatures.
A microcontroller does not draw current uniformly - at every clock edge, tens of thousands of
transistors switch simultaneously and demand a current spike far larger than the average. Any wire
has parasitic inductance, and u = L·di/dt: a very short current spike means a
very large di/dt, hence a noticeable voltage drop right on the supply wires.
The decoupling capacitor, connected directly between the supply pins and ground, a few millimeters from the package, acts as a small local reservoir that instantly supplies the current spike, without going through the inductance of the traces. The consequences of its absence are hard to diagnose, precisely because they are intermittent: seemingly random resets, noisy analog readings, serial communication with rare errors, behavior that changes when you touch the board with your hand.
A practical rule with no exceptions: one 100 nF ceramic capacitor at every VCC-GND pair, as close as possible to the chip's pins, plus a ~10 µF electrolytic at the board's power input, for slower variations. On the ATmega328P the rule applies to both the VCC-GND pair and AVCC-GND (the analog part); the AREF pin also needs its own capacitor, because noise on the reference voltage shows up directly in the analog-to-digital conversion result.
11A wiring example6 min
What must be connected to an ATmega328P straight out of the bag for it to work? The answer is surprisingly short: a stabilized power supply and a clock source. The rest of the pins serve the application, not the chip itself.
The minimal setup: 5 V supply on VCC/AVCC, ground on GND, 100 nF decoupling capacitors near each pair; a clock source (a 16 MHz crystal with two ~22 pF load capacitors, or nothing if the internal oscillator is used); the RESET pin, active low, held at the supply through a ~10 kΩ pull-up resistor (left floating, it picks up noise and causes random resets); and, if the ADC is used, its own decoupling capacitor on AREF.
int main(void)
{
DDRB |= (1 << PB5); /* PB5 configured as an output */
while (1) {
PORTB ^= (1 << PB5); /* toggle the pin state */
for (volatile long i = 0; i < 200000L; i++) {
; /* approximate delay, through an empty loop */
}
}
return 0;
}
If the LED blinks, the power supply is fine, the clock is oscillating, reset is not asserting itself and the program has reached memory. If the LED stays lit steadily, the microcontroller is most likely stuck in reset. If nothing happens, the problem is in the power supply or the clock. If the LED blinks more slowly or faster than expected, the clock is running at a different frequency than assumed - the fuses are not configured for the desired clock source.
Without volatile, the compiler notices that the loop produces no visible effect and
removes it entirely during optimization - the delay disappears. The keyword volatile forbids
this optimization, forcing it to actually read and write the variable on every iteration.
The same problem appears with any variable modified in an interrupt routine and read in the
main program (Lecture 05).
12Frequent mistakes4 min
- "PORTB = 0b00100000; is equivalent to PORTB |= (1 << PB5);, just clearer." False - the first form rewrites all eight bits, turning off anything else that was on that port. The effect appears only after weeks, when the port also drives other peripherals. Always modify only the desired bit (|=, &=~, ^=), except during complete initialization.
- "32 KB of Flash is the real limit of an AVR application." Almost never - compiled code is dense and fits comfortably in Flash. The real constraint is the 2 KB of SRAM, quickly exhausted by arrays, buffers and the stack. Check the SRAM usage report after every compile, not just Flash.
- "Without a decoupling capacitor, the setup works anyway - I tested it on the bench." Working on the lab bench guarantees nothing - the effects of missing decoupling are intermittent and typically show up at different temperature, vibration or electrical load. Always place 100 nF as close as possible to every VCC-GND pair, no exceptions.
13Summary and glossary5 min
The elementary piece of information is the bit; eight bits form a byte, written equivalently in binary, decimal or hexadecimal - the last being the data-sheet convention, for the exact correspondence with groups of four bits. Registers are fast memory cells next to the central unit; special function registers are the interfaces through which the program directly commands peripherals, with the essential distinction control (written by the program) / status (written by the hardware). SRAM is volatile and fast, Flash and EEPROM are non-volatile but with limited write cycles. The central unit contains the ALU (with the SREG status register) and the control unit (with the program counter and the stack, whose overflow produces chaotic behavior that is hard to diagnose). The Harvard architecture, with separate buses for program and data, allows the AVR's two-stage pipeline and the execution of most instructions in a single cycle - which is why AVR is RISC. The ATmega328P has 32 KB Flash, 2 KB SRAM (the real constraint) and 1 KB EEPROM. The accuracy of the clock source matters critically for asynchronous serial communication, and decoupling capacitors are not optional.
14Self-check questions6 min
- The value of a register is 0x6D. Write it in binary and decimal and state the active bits.
- What is the functional difference between a control register and a status register?
- Why does the Harvard architecture allow most AVR instructions to execute in a single cycle, unlike von Neumann?
- What is a control hazard, and how does it explain the variable duration of a conditional branch on AVR?
- Compare SRAM, Flash and EEPROM by volatility, erase granularity and the number of write cycles.
- An ATmega328P is clocked at 8 MHz. Calculate the clock period and the approximate number of instructions per second.
- Explain, starting from u = L·di/dt, why the absence of decoupling capacitors produces seemingly random resets.
15Directions for further study2 min
The next lecture moves from the internal structure to the actual programming process: the toolchain, from source code to the bits in Flash, the Intel HEX format, the structure of Flash memory with the bootloader, and the programming modes (ISP, through the bootloader).
The registers, bits and wiring diagram from this lecture become a real setup in Laboratory 01B, the preparatory lab dedicated to working directly with bits and registers.