Embedded Systems
Programming (CENG/BİL 462)
Week 2
OSTİM Technical University Computer Engineering
Fall 2025
Şamil Korkmaz
Low Level Example LED blink with Arduino
Blink on board LED of Arduino UNO
Find pin register name of Arduino on board LED = PB5 (Port B, bit 5, also connected
to D13)
ATmega328P_Datasheet.pdf (includes DIP28 package, ~650 pages, 32MB) QR code
As you can see on page 12, ATmega328P (DIP28) has a PB5, connected to pin 19 of
the uC.
DIP package: Dual In-line Package. Since pins are spaced at 2.54 mm (0.1 inch), DIP
chips can be easily used on breadboards or perfboards for prototyping.
Find ATmega328P registers that controls Port B (page 100)
1. Data Register = 0x25 (datasheet, 14.4.2)
2. Data Direction Register = 0x24 (datasheet, 14.4.3)
void setup() {
*(volatile uint8_t*)0x24 = 32;
//0x24 Data Direction Register (DDR) for Port B on ATmega328P
//32 = 0b00100000, sets bit 5 (PB5 → Arduino pin13, onboard
LED) as output
}
void loop() {
*(volatile uint8_t*)0x25 = 32;
// 0x25 is the Port Data Register (PORT) for Port B.
// 32 = 0b00100000, sets PB5 → Arduino pin 13 to HIGH
delay(1000);
*(volatile uint8_t*)0x25 = 0; // Set Arduino PB5 → pin 13 LOW
delay(1000);
}
Write code (Wokwi) to set bit 5 of those registers (DDB5, PB5) and upload to Arduino
UNO
Why volatile: The compiler might assume that a memory location (like 0x24 or 0x25)
is just a regular variable and optimize accesses to it, such as caching its value in a
register or skipping redundant writes. In the loop() function, you write 32 to 0x25
(PORTB) to set PB5 HIGH, then write 0 to set it LOW. Without volatile, the compiler
might optimize the second write (0) by assuming the first write (32) is unnecessary or
can be skipped, especially if it thinks the value is not read elsewhere. This could
prevent the LED from toggling as intended.
Wokwi LED blink QR code.
Note that both the built in LED and LED at pin 13 blink because PB5 was connected
to both of them.
This code is tightly coupled to hardware due to hardcoding of register addresses.
Hardware dependency is only meaningful if it provides 100x performance/memory
gains. Otherwise, use abstraction layers so that your code can run on different
hardware.
ATmega328P SMD 32 pin package version (294 pages), page 3, note that it has 32
pins instead of DIP 28 pins. PB5 is connected to SMD pin 17, not pin 19 as in the DIP
package
SMD: Surface Mount Devices are smaller and better for compact products but harder
to prototype by hand
Since the chip’s pin 17 is still connected to Arduino dev board pin 13 and its register
addresses are the same (datasheet p.72, 13.4.2, 13.4.3), the same code would work
with this version too.
Why Two ATmega328P Package Types?
● Flexibility: Same chip can serve two markets:
○ Makers & education → DIP (Arduino Uno, school kits).
○ Commercial products → SMD (Arduino Nano, embedded boards,
consumer electronics).
● Pin availability: The SMD package exposes extra functions/pins that are
internally present but not bonded out in the DIP.
● Cost & volume: SMD is cheaper and faster to assemble in factories, while
DIP remains accessible for low-volume use.
Even more low level with an analog circuit using transistors, resistors and capacitors: I
Shrunk Blinky to 0 Bytes
Capacitors C1 and C2 (10 µF each) alternately charge and discharge, driving the
base of the opposite transistor. Q1 is on when C2 is discharging, causing the LED to
turn on.
Resistors (150kΩ) control the charge/discharge rate, setting the oscillation
frequency.
Collector resistors (1kΩ) limit the current to the load (LED in your circuit).
The LED flashes ON and OFF continuously as the transistors alternately switch.
The ESP32
We will be working with ESP32 ~ Upgraded Arduino + WiFi + Bluetooth
The ESP32
The ESP32 isn’t just a microcontroller—it’s a complete computer system on a chip:
● Brains (CPU cores + memory)
● Power saving (ULP coprocessor + RTC): Instead of keeping the main cores
awake to “poll” or check values, the ULP executes tiny loops in the
background at very low clock rates (typically ~150 kHz). ULP runs a loop like
“sample ADC → compare to threshold → if exceeded, wake main CPU.” This
allows the ESP32 to sleep most of the time and only wake when something
important happens.
● Communication (Wi-Fi, Bluetooth, Ethernet, CAN)
● Security (built-in crypto hardware). ESP32 has special built-in circuits
(hardware blocks) dedicated to cryptography. The main CPU (Xtensa cores)
runs your program. When you need encryption, hashing, or random numbers,
the CPU tells the crypto hardware accelerator block to do the heavy math.
That block is built into the ESP32 silicon itself — like a mini co-processor, not
a separate chip:
○ AES (Advanced Encryption Standard) → fast data scrambling.
○ SHA (Secure Hash Algorithm) → fast hashing.
○ RSA → used in secure key exchange.
○ RNG → generates random numbers securely.
○ These circuits run much faster and with less energy than if the CPU
tried to do the same math in software.
● Peripherals (digital I/O, ADC/DAC, touch, capacitance and hall sensors,
timers, memory Interfaces, UART, SPI, I²C, I²S, CAN, Ethernet, wireless and
low-power subsystems)
Exercise: Convert
Arduino UNO code to
ESP32 using register
addresses
Link to code: [Link]
Find ESP32 datasheet and show how it should be used to solve this exercise
const int LED_PIN = 2; // dev board built in LED
void setup() {
*(volatile uint32_t*)0x3FF44024 |= (1 << LED_PIN); // output
}
void loop() {
*(volatile uint32_t*)0x3FF44008 = (1 << LED_PIN); // HIGH
delay(1000);
*(volatile uint32_t*)0x3FF4400C = (1 << LED_PIN); // LOW
delay(1000);
}
On ESP32 WROOM, the register addresses are different. See
esp32_technical_reference_manual_en.pdf (782 pages), p.134, 6.12.1 GPIO Matrix
Register Summary
Copy this code to your
● Wokwi and connect an LED to pin 2
● Arduino IDE, upload it to your physical ESP32 dev board and confirm that the
built in LED blinks
This code is tightly coupled to hardware due to hardcoding of register addresses.
Hardware dependency is only meaningful if it provides 100x performance/memory
gains. Otherwise, use abstraction layers so that your code can run on different
hardware. Using hardware abstraction layer (HAL) allows us to run the same high
level code on both Arduino and ESP32. HAL handles hardware specific details.
const int LED_PIN = 2;
void setup() {
pinMode(LED_PIN, OUTPUT);
}
void loop() {
digitalWrite(LED_PIN, HIGH);
delay(500);
digitalWrite(LED_PIN, LOW);
delay(500);
}
Arduino Framework Functions are HAL:
● pinMode() - abstracts GPIO direction configuration
● digitalWrite() - abstracts GPIO output control
● delay() - abstracts timing/delay implementation
Benefits of HAL approach:
1. Portable (cross-platform) - Same code works on Arduino UNO, ESP32,
STM32, etc.
2. Readable - Clear intent without knowing register addresses
3. Maintainable - Less prone to errors, e.g. built in error handling that catches
invalid inputs at runtime
4. Faster development - No need to look up datasheets
Benefits of Register-level:
1. Performance - No function call overhead
2. Control - Access to all hardware features
3. Learning - Understand how hardware actually works
4. Size - Smaller compiled code
Board Support Package (BSP)
It’s a collection of software components that makes an operating system (or firmware)
run on a specific hardware board. Without it, the OS/application wouldn’t know how to
use the hardware. For an ESP32 board, Espressif provides the ESP-IDF (which is
basically a BSP + SDK).
Depending on the platform, a BSP typically includes:
● Startup code / Boot code – sets up the CPU, clock, memory, and low-level
hardware after reset.
● Device drivers – for peripherals like UART, SPI, I²C, GPIO, timers, ADC, etc.
● Board-specific initialization – e.g., pinmux configuration, memory mapping,
power setup.
● Hardware Abstraction Layer (HAL) – provides standard APIs for upper
software layers.
● Linker scripts & configuration files – define memory layout, stack, and heap
placement.
● Optional RTOS integration – adaptation layer so the RTOS can run on that
board.
Your App
↓
Arduino Framework (HAL) Computer
Software ↓
ESP-IDF (FreeRTOS, drivers)
Engineers
↓
Register Addresses
↓
Hardware Registers
↓ Electronics
Digital Logic Gates
Hardware ↓ Engineers
Transistors
↓
Silicon (Electron flow, Doping)
↓ Physicists
Quantum Mechanics
Abstraction layers
How did we get to our current technological level?
“If I have seen further it is by standing on the shoulders of Giants” Isaac Newton,
1675
The history of computing is about becoming faster, smaller, and more energy-efficient.
Understanding this history helps us appreciate what we have achieved.
1792: The first IoT device (!), the optical (semaphore - sign/signal-carrier) telegraph
was invented by Claude Chappe and his brothers in 1792, who succeeded in covering
France with a network of 556 stations stretching a total distance of 4,800 kilometres
(3,000 mi).
The effective information rate has been estimated at roughly 2–3 symbols per
minute. Since each symbol corresponded to one of 92 code numbers, which could
map to words or phrases, this was equivalent to about 1–2 words per minute (0.3
bps). It was 60 times faster than horse couriers, which made it revolutionary for
military and governmental communication at the time. For 200 km, horse courier ≈ 15
hours, telegraph ≈ 15 minutes (0.25 hours)
Le système Chappe was used for military and national communications until the
1850s. Napoleon installed semaphore towers along key routes (the first Internet /
IoT device), enabling him to receive real-time updates from distant armies and issue
orders that outpaced enemy messengers. During the Siege of Sevastopol—a pivotal
Allied (French, British, Ottoman, Sardinian) assault on Russian forces—a mobile
semaphore system relayed critical updates.
1825: William Sturgeon invented the electromagnet by wrapping a coil of wire around
an iron core and passing an electric current through the wire, which magnetized the
core. His key innovation was demonstrating that a relatively weak electric current
could produce a strong magnetic field in a compact device, unlike earlier, less
practical attempts. He used a U-shaped iron core, which concentrated the magnetic
field. He wrapped the core with a coil of wire, amplifying the magnetic effect.
1835: Joseph Henry invented the first electromechanical relay. Henry demonstrated
the potential of Sturgeon's device for long distance communication by sending an
electronic current over one mile of wire to activate an electromagnet which caused a
bell to strike. Thus the electric telegraph was born. When the weak current flowed
through the electromagnet’s coil, it created a magnetic field that attracted a movable
iron armature. This armature acted as a switch, closing a separate, local circuit
powered by a stronger, local battery.
You can build logic gates by combining relays
1847: Boolean algebra, George Boole in his first book The Mathematical Analysis of
Logic
The basic building block of a computer is the NAND gate, with switches: A NAND B =
NOT (A AND B)
Then you can build any other gate from NAND and build an adder, memory and then
the whole computer!
Game: Digital Logic Sim
A 4-bit adder requires 64 transistors (switches)
Claude Shannon, in his 1937 master's thesis he showed that relay-based circuits
could implement logical operations, including the NAND (NOT-AND) function.
Konrad Zuse’s Z3 (1941) and early IBM calculators used relays.
You can build a whole computer with relays
“To be or not to be, that is the question”, William Shakespeare’s Hamlet (Act 3, Scene
1)
1853: Edmond Becquerel observed that a heated metal or material could release
charged particles into the surrounding environment. This is called thermionic
emission, the process by which electrons are emitted from the surface of a material,
typically a metal or a coated cathode, when it is heated to a sufficiently high
temperature
1904: Vacuum diode, John Ambrose Fleming: When the cathode is heated, it emits
electrons into the vacuum via thermionic emission. The heat gives electrons enough
energy to escape the cathode’s surface.
1906: First vacuum triode, Lee De Forest. Switching is purely electronic, no
mechanical inertia. 1000x faster than relays (micro seconds vs milli seconds). Used
for amplification and switching in radios. Note that a single triode is larger than today's
single board computers like Raspberry Pi
1942: Electromechanical computers, V2 guidance computer ("Mischgerät")
Mechanical integration to calculate attitude, velocity and control (fin, engine cut-off)
signals
1945: ENIAC (Electronic Numerical Integrator and Computer) was the first electronic
digital computer to be general purpose programmable, 17,468 vacuum tubes
1947: Point-contact transistor, John Bardeen and Walter Brattain at Bell Labs.
1950: Bipolar junction transistor (BJT), William Shockley improved on this idea,
designing a more practical and reliable “sandwich” structure of semiconductor layers.
Transistors became faster than vacuum tubes because they are smaller, cooler, and
lower capacitance (faster signal change). Today’s MOSFETs switch in the GHz range.
5V compared to a triode’s 100V
1958: Integrated circuits: Multiple transistors + resistors + capacitors on one chip. All
the circuit elements could be made on a single slice of semiconductor material
1961: Apollo Guidance Computer using ICs. 5,600 ICs×2 NOR gates/IC×3
transistors/gate ≈ 34,000 transistors. Each IC was a flat-pack dual NOR gate built
from resistor–transistor logic (RTL).
1962: CTSS (Compatible Time-Sharing System, MIT): Considered the first practical
time-sharing operating system. It allowed multiple users on a mainframe to run jobs
concurrently by rapidly switching between them. It had preemptive scheduling
(forcibly interrupt a running process and switch the CPU to another process). It had a
hardware timer. Each user program was given a time slice (e.g., ~200 ms). When the
timer expired, it triggered an interrupt. CTSS saved the context of the running job,
restored another job’s context, and resumed execution.
1966: Autonetics D-17B Guidance Computer, mass produced for Minuteman II ICBM,
6,000 transistors
1969: Unix. Before Unix, operating systems were usually written in assembly
language and tied to one machine. Unix was rewritten in C (early 1970s), making it
portable across different hardware platforms. This was revolutionary: instead of
rewriting the OS for each computer, only the compiler was needed
1971: Intel 4004 microprocessor, ~2,300 transistors, 10,000 nm, first commercial
4-bit, enabling digital control to replace bulky hardware logic. Needs external chips for
RAM etc. Used in calculators.
1974: Intel 8048 microcontroller was developed, the first chip combining CPU,
RAM, ROM, and I/O on a single chip — the classic embedded controller. Cheap,
compact, and easy to integrate. Used in:
● IBM PC keyboards (the 8048 scanned the keys and sent signals to the main
CPU)
● Microwave ovens, washing machines, sewing machines
● Automotive engine control units (ECUs)
● Sound generation in video games
1978: The first x86 chip was the Intel 8086
1991: Linux (Linus Torvalds) – Unix-like open source kernel with GNU tools;
foundation of Android, servers, and supercomputers.
2000: FreeRTOS official release: Open-source RTOS for small MCUs, massively
adopted in IoT.
2005: The open-source Arduino platform democratized embedded system
development, making it accessible for hobbyists and prototyping in education and IoT.
2010s: Maker movement + ecosystem explosion with the help of affordable Chinese
products
2012: Raspberry Pi launch: Affordable Linux-capable single board computer, pivotal
in education & prototyping.
2016: ESP32 made wireless embedded computing affordable and widespread.
1990s: ARM processors became dominant due to low power and high efficiency.
Embedded systems started running real-time operating systems (RTOS) like
VxWorks, QNX, µC/OS.
2000s: Networking became important. Embedded Linux emerged, powering routers,
smart TVs, and set-top boxes.
2010s–Today: IoT, use of multicore processors and hardware accelerators (GPU,
TPU, FPGA), NVIDIA H100 GPU (2022, 4 nm) – ~80 billion transistors
Future Trends: AI-powered embedded systems for autonomous driving, robotics, and
medical diagnostics. Integration of cloud + edge computing, making embedded
devices intelligent nodes in larger cyber-physical systems.