LPC2148 GPIO LED Blink in Keil uVision: Registers, Code and Flashing
The NXP LPC2148 is the ARM microcontroller most Indian engineering students meet first. It is built around the ARM7TDMI-S core, has 512 KB of on-chip flash and 32 KB of static RAM, runs at up to 60 MHz and includes UARTs, timers, PWM, two 10-bit ADCs, a DAC, I2C, SPI and full-speed USB. Lab kits from many Indian vendors use it with a 12 MHz crystal, a set of LEDs, switches, an LCD and a serial port.
In this tutorial you will write a bare-register LED blink program in C, build it in Keil uVision, and download it to the board through the serial bootloader using Flash Magic. You will also extend it into a running-light pattern controlled by a push button, which covers both GPIO output and input.
How LPC2148 GPIO Works
The LPC2148 has two GPIO ports. Port 0 covers pins P0.0 to P0.31 and Port 1 covers P1.16 to P1.31 (the lower half of Port 1 does not exist on this chip). Almost every pin is shared with other functions such as UART, ADC or PWM, so a pin connect block chooses which function drives the pin. The PINSEL0 register controls P0.0 to P0.15, PINSEL1 controls P0.16 to P0.31, and PINSEL2 controls Port 1. Each pin gets two bits in PINSEL0 and PINSEL1; the value 00 selects GPIO, which is the reset default for most pins.
Once a pin is GPIO, four registers control it. Each bit position matches the pin number, so bit 16 of IO1DIR is P1.16.
| Register | Purpose | How to use it |
|---|---|---|
| IO0DIR / IO1DIR | Direction | Write 1 to make a pin an output, 0 for input (reset value is all inputs) |
| IO0SET / IO1SET | Drive high | Writing 1 sets that pin high; writing 0 has no effect |
| IO0CLR / IO1CLR | Drive low | Writing 1 sets that pin low; writing 0 has no effect |
| IO0PIN / IO1PIN | Read pin state | Read to get the current level of every pin on the port |
Separate SET and CLR registers are a deliberate design choice. Writing IO1SET = (1 << 16); changes only P1.16 and touches no other pin, so you never need a read-modify-write sequence that could be broken by an interrupt. The LPC2148 also has faster GPIO registers (FIO0DIR, FIO0SET and so on) that are enabled through the SCS register, but the legacy registers used here are simpler and perfectly adequate for LEDs.
Hardware Setup
Most LPC2148 development boards already have eight LEDs on P1.16 to P1.23 and a few push buttons. Check your board schematic: some boards connect LEDs on Port 0 instead, and some drive the LED cathode from the pin so that a low output turns it on. If you are wiring your own, use the connections below.
| Component | LPC2148 pin | Connection |
|---|---|---|
| LED1 to LED8 | P1.16 to P1.23 | Pin to 330 ohm resistor to LED anode, LED cathode to GND |
| Push button | P0.16 | Pin to button to GND, with a 10 k pull-up resistor from the pin to 3.3 V |
| Boot jumper | P0.14 | Hold low while pressing reset to enter the serial bootloader |
| Crystal | XTAL1, XTAL2 | 12 MHz on most boards |
Creating the Keil uVision Project
- Open Keil uVision and choose Project, New uVision Project. Save it in an empty folder.
- Select the device NXP LPC2148. In MDK version 5 you need the legacy ARM7 support package installed for this device to appear.
- When asked to copy Startup.s into the project, click Yes. This file sets up the stacks for each ARM7 mode and, with its default settings, configures the PLL for a 60 MHz CPU clock from a 12 MHz crystal.
- Add a new file main.c to Source Group 1 and paste the code below.
- Open Options for Target. On the Target tab set Xtal to 12.0 MHz. On the Output tab tick Create HEX File.
- Press F7 to build. The output window should report 0 errors and the HEX file appears in the Objects folder.
#include <lpc214x.h>
#define LED1 (1u << 16) /* P1.16 */
#define LED_MASK (0xFFu << 16) /* P1.16 to P1.23 */
#define BUTTON (1u << 16) /* P0.16, active low */
/* Crude software delay. The count per millisecond depends on the
* clock, compiler optimisation and MAM settings, so calibrate it. */
static void delay_ms(unsigned int ms)
{
volatile unsigned int i, j;
for (i = 0; i < ms; i++)
for (j = 0; j < 5000; j++)
;
}
int main(void)
{
unsigned int pattern = 1;
int k;
PINSEL1 &= ~(3u << 0); /* P0.16 as GPIO (bits 1:0 = 00) */
IO0DIR &= ~BUTTON; /* P0.16 input */
IO1DIR |= LED_MASK; /* P1.16 to P1.23 outputs */
IO1CLR = LED_MASK; /* all LEDs off */
/* Part 1: blink LED1 five times */
for (k = 0; k < 5; k++) {
IO1SET = LED1;
delay_ms(300);
IO1CLR = LED1;
delay_ms(300);
}
/* Part 2: running light; direction reverses while button is held */
while (1) {
IO1CLR = LED_MASK;
IO1SET = (pattern << 16);
delay_ms(150);
if (IO0PIN & BUTTON) { /* button released: move left */
pattern <<= 1;
if (pattern > 0x80) pattern = 0x01;
} else { /* button pressed: move right */
pattern >>= 1;
if (pattern == 0) pattern = 0x80;
}
}
}
How the Code Works
The header lpc214x.h ships with Keil and defines every peripheral register as a volatile memory location, so IO1SET = LED1; compiles into a single store to the IO1SET address. PINSEL1 &= ~(3u << 0); clears the two selection bits for P0.16 to guarantee GPIO mode, even though that is already the reset default. Direction is set with |= and &= because IODIR is an ordinary read-write register, while SET and CLR are written directly.
In the running-light loop, the variable pattern holds a single 1 bit that is shifted left or right. Shifting it by 16 places it on the LED pins. IO0PIN & BUTTON returns non-zero when the pull-up holds the pin high (button released) and zero when the button connects the pin to ground. Because the loop waits 150 ms per step, mechanical switch bounce is not a problem here, but for counting presses you would need debouncing.
The software delay is only approximate. The inner loop count depends on the CPU clock, the compiler optimisation level and whether the Memory Accelerator Module is enabled. Measure ten blinks with a stopwatch and adjust the 5000 constant, or better, use a hardware timer as shown in the LPC2148 timer tutorial.
Flashing the HEX File with Flash Magic
- Connect the board's UART0 serial port to the PC through a USB-to-serial adapter or the onboard USB-UART chip, and note the COM port number.
- In Flash Magic select device LPC2148, the COM port, baud rate 9600, interface None (ISP) and oscillator 12 MHz.
- Tick Erase blocks used by Hex File and browse to the HEX file from the Objects folder.
- Put the board in ISP mode: hold P0.14 low (boot switch or jumper) and press reset. Many boards do this automatically through the DTR and RTS lines.
- Click Start. After programming completes, release P0.14 and press reset to run the program.
Troubleshooting
- Flash Magic cannot synchronise: the board is not in ISP mode, the COM port is wrong, or another program such as a terminal has the port open.
- LEDs stay on and turn off when expected on: the board drives LEDs active low, so swap IO1SET and IO1CLR.
- Nothing happens after flashing: P0.14 is still held low, so the bootloader runs instead of your program.
- Port 1 LEDs do not respond: check that PINSEL2 has not been set to route P1.16 to P1.25 to the trace port.
- Button always reads pressed or released: the pull-up resistor is missing, or the button is wired to 3.3 V instead of ground.
Summary
LPC2148 GPIO is controlled by PINSEL for pin function, IODIR for direction, IOSET and IOCLR for changing outputs without disturbing other pins, and IOPIN for reading inputs. Keil uVision builds the program with the supplied Startup.s, and Flash Magic loads the HEX file over UART0 when P0.14 is held low at reset. With these steps working, you are ready to add serial communication, ADC readings and timer-based delays.