In this lesson, you will learn how to directly control hardware peripherals by using C pointers to access specific memory-mapped addresses.
The Concept of Memory-Mapped I/O
In embedded systems, the CPU communicates with hardware components—like LEDs, timers, or UART controllers—by reading from and writing to specific memory addresses. Unlike standard RAM, which stores your variables, these addresses are "wired" directly to the hardware's internal registers. To the CPU, an LED register looks exactly like an integer variable in memory; to the hardware, your write operation triggers a physical action.
Using Pointers for Hardware Access
To interact with these registers, we treat the hardware address as a raw memory pointer. Since the hardware expects specific data widths (like 32-bit values), we cast the memory address into a pointer of the appropriate type.
Consider a hypothetical GPIO (General Purpose Input/Output) register located at address 0x40021000. To toggle a pin, we use the following pattern:
// Define the address as a constant
#define GPIO_DATA_REG (*(volatile uint32_t*)0x40021000)
void set_led_on() {
// We treat the address as a pointer and dereference it
GPIO_DATA_REG |= 0x01;
}
Why use volatile?
You might notice the volatile keyword in our definition. This is critical in embedded systems. Without volatile, the compiler might see multiple writes to the same address and decide to "optimize" them away, assuming the value shouldn't change unless the software changes it. However, because this address points to physical hardware, the value can change due to external events (like a button press). volatile forces the compiler to perform a real memory access every time the code references that pointer, preventing the compiler from caching the value in a CPU register.
By combining pointer arithmetic and explicit type casting, we bridge the gap between abstract C code and the physical electrical signals that drive your microcontroller’s peripherals.
Key Takeaway: By casting a fixed memory address to a volatile pointer, you gain the ability to manipulate hardware registers as if they were standard variables in your C program.