A complete, self-contained learning guide + code + wiring
This repository is a beginner-level learning lab course for the NXP LPC2148 (ARM7TDMI-S). Each lab builds one concept at a time: bitwise operations → GPIO registers → switches → LEDs → seven‑segment.
Designed for a bare‑minimum build (no dev board required).
You can wire the circuit on a breadboard or simulate in Proteus or NI Multisim.
- LPC2148 microcontroller (LQFP64 package)
- 3.3V regulated supply (do not power GPIO with 5V)
- 12 MHz crystal + 2 × 22 pF capacitors
- Reset circuit: 10k pull‑up + pushbutton to GND
- Decoupling capacitors: at least 100 nF near each VDD pin (+ one 10 µF bulk cap)
- LEDs + resistors (330Ω–1kΩ)
- Push buttons + resistors (10k pull‑ups recommended)
- Seven‑segment display (common cathode) + resistors (330Ω–1kΩ)
- IAR Embedded Workbench (which I used) a comprehensive, integrated development environment (IDE) and toolchain for embedded systems. Another option would be Keil uVision an ARM toolchain that can build LPC214x projects.
- NI Multisim (which I used) or Proteus optional but recommended for beginners.
- Text editor (VSCode or an IDE)
A microcontroller is basically:
- A CPU core (ARM7TDMI‑S),
- Flash + RAM,
- Peripherals (GPIO, UART, Timers, ADC, etc.),
- Lots of memory‑mapped registers.
A register is a special hardware-controlled memory location.
Writing/reading a register changes hardware behavior.
Examples:
- GPIO direction register controls input vs output.
- GPIO set/clear registers drive pins HIGH/LOW.
- PINSEL registers choose alternate pin functions (UART, PWM, etc.)
A typical workflow for any peripheral is:
- Enable / select the function (often via a clock gate or a PINSEL register)
- Configure (direction, mode, speed, pull-up/pull-down, etc.)
- Use (read status, write output, handle events)
This course focuses on GPIO, so the “enable” step is mostly:
- “make sure the pin is configured as GPIO” (PINSEL bits set to 0 for that pin).
Embedded programming is full of bit manipulation, because registers are bitfields.
|(OR) → set bit(s)&(AND) → clear or test bit(s)^(XOR) → toggle bit(s)~(NOT) → invert bits<</>>shifts
reg |= (1U << n);reg &= ~(1U << n);if (reg & (1U << n)) { /* bit is 1 */ }reg = (reg & ~FIELD_MASK) | ((value << FIELD_SHIFT) & FIELD_MASK);You will use these patterns constantly in GPIO, timers, UART, ADC, etc.
LPC2148 has Port 0 and Port 1 GPIO registers.
- Port 0 has many usable pins, but some are reserved (e.g., P0.24/P0.26/P0.27).
- Port 1 is a 32-bit port, but P1.0–P1.15 are not available on LPC2148 packages. Only P1.16–P1.31 are usable GPIO pins.
This repo uses valid pins only.
Pins can be GPIO or another function. The PINSEL registers select the function:
PINSEL0→ P0.0..P0.15PINSEL1→ P0.16..P0.31PINSEL2→ P1.16..P1.31
For a pin to act as GPIO, its PINSEL function bits must be 00.
0= input1= output
Examples:
IO0DIR |= (1U << 4); // P0.4 output
IO0DIR &= ~(1U << 0); // P0.0 input- Read current logic level of pins.
- Be careful when writing (you can overwrite other pins).
- Writing
1to a bit sets that pin HIGH. - Writing
0has no effect.
Correct usage:
IO0SET = (1U << 4);- Writing
1clears that pin LOW. - Writing
0has no effect.
Correct usage:
IO0CLR = (1U << 4);IOxSET/IOxCLR are “command” registers:
- bits written as 1 take effect,
- bits written as 0 do nothing.
So IO0SET = (1<<4) does not disturb other pins.
For direction/config registers like
IOxDIR, you should use|=and&=~to preserve other bits.
Port 0 pins typically do not have built-in pull-up/pull-down resistors, so in real circuits you must add external pull-up resistors for active-low buttons.
You’ll learn these in order:
- Basic input & output
- Independent control (multiple signals)
- Seven-segment basics + mapping
- Macros (safer register manipulation)
- Small “libraries” (modular code)
- Wire the circuit.
- Create a new project in your IDE for LPC214x.
- Add the source files in this folder.
- Build & flash.
- Interact with the switches and observe the output.
- How to configure GPIO direction (input vs output)
- How to read a pin (
IOxPIN) - How to drive a pin (
IOxSET/IOxCLR) - How to structure embedded code clearly (comments + naming)
- Add a small software debounce (delay + stable reads).
- Change the pin mapping to match your specific board.
- Extend the logic:
- E01/E04: add a third LED behaviour state
- E03/E08: display a scrolling count when no switch is pressed
If your dev board uses different pins, change the
*_PINmacros (or the definitions inConfig.h/ headers).
One push-button controls two LEDs.
Connections used in code:
- LED1: P1.20
- LED2: P1.24
- SW1: P1.29 (active-low, pull-up to 3.3V)
Two push-buttons independently control two LEDs.
Connections used in code:
- LED1: P0.4
- LED2: P0.7
- SW1: P0.0 (active-low, pull-up)
- SW2: P0.2 (active-low, pull-up)
Select digits 0–9 using ten switches and show them on a 7-seg display.
Connections used in code:
- 7-seg segments a..g: P1.17..P1.23 (via resistors)
- Common cathode: GND
- SW0..SW9: P0.0..P0.9 (active-low, 10k pull-ups to 3.3V)
Uses safe macros (do { ... } while(0)) to wrap GPIO operations.
Connections used in code:
- LED1: P1.20
- LED2: P1.24
- SW1: P1.29 (active-low, pull-up)
Two switches control two LEDs using macro wrappers for init and IO.
Connections used in code:
- LED1: P0.4
- LED2: P0.7
- SW1: P0.0 (active-low, pull-up)
- SW2: P0.2 (active-low, pull-up)
Introduces a small reusable module LedSw.c/.h and Config.h.
Connections used in code:
- LED bank: P0.16..P0.23 (via resistors)
- SW1: P0.10 (active-low, pull-up)
Builds on E06: now two switches control two LEDs using the helper module.
Connections used in code:
- LED bank: P0.16..P0.23 (via resistors)
- SW1: P0.10 (active-low, pull-up)
- SW2: P0.11 (active-low, pull-up)
Cleaner main() by separating 7-seg logic (7Seg.*) and switch logic (switch.*).
Connections used in code:
- 7-seg segments a..g: P1.17..P1.23 (via resistors)
- Common cathode: GND
- SW0..SW9: P0.0..P0.9 (active-low, pull-ups)
- Add a 10k pull-up to 3.3V and connect button to GND (active-low).
- Add debounce (delay + re-sample) if needed.
- You might have wired LED as active-low (GPIO sinks current).
- Either rewire (GPIO -> R -> LED -> GND), or invert logic in code.
- Confirm whether your display is common cathode or common anode.
- Common anode requires inverted segment logic.
- Ensure your HEX file is attached to the MCU model.
- Make sure clock/reset circuit exists.
- Check that you used the correct pin names.
Once you finish these GPIO labs, the next “small projects” to build are:
- PLL / clock setup
- Timer/Counter (delays, time measurement)
- UART (serial console)
- ADC (read analog sensor)
- VIC (interrupts)
- LCD (4-bit and 8-bit)
- Keypad interfacing
- Relay and DC motor control (with driver IC)
- GSM, GPS, Bluetooth modules
- Ultrasonic, PIR, IR sensors
- LDR / sound / gas / flame sensors
J-Link, LPC-Link, or any ARM-compatible SWD/JTAG debugger (depending on your setup).
If you prefer not to build the minimal hardware, use an LPC2148 development board (Olimex-style boards are common). This repo still applies — only the wiring changes.
ARM LPC2148 (ARM7TDMI-S) Datasheet
See LICENSE.