While looking at other peoples Arduino codes, you may have come across a piece of code that looks like this:

What are DDRD and PORTD? Where are they defined, and how does assigning a value to them cause the GPIO to drive a certain value?

First, let’s talk about how the ATMEGA328 drives a GPIO pin. The figure above shows the hardware that the microcontroller uses: the key components are listed below:
- Pxn: this connects to the physical pin on the microcontroller and is accessible to circuits that connect to the microcontroller. This is what you drive or read from.
- DDxn: this is the Data Direction register. It enables the buffer that drives Pxn when the pin is configured as an output, and disables the buffer when the pin is configured as an input.
- PORTxn: this register holds the value that the buffer will drive if it is enabled.
- PINxn: this register holds the value that Pxn is driven to.
Here, x denotes port, while n denotes pin number; for example, port C pin 3 would have PC3, DDC3, etc. Say you want to configure Port D pin 0 as an output, driving a high; then, you would have to make DDD0 output a 1 and PORTD0 output a 1. Then, PIND0 will be driven high by the buffer. If you wanted Port D pin 0 to be an input, then you would make DDD0 store a 0 and see what value gets stored into PIND0.

Simple enough assuming you can read from/write to those registers, but how do you actually go about doing that? The ATMEGA328 uses a very popular scheme called memory mapped I/O. Memory mapped I/O treats the registers used by peripherals (ADCs, GPIO, UART, etc.) as being part of the memory. Just like how you can read and write to addresses in SRAM, if you write to a register’s address, then the microcontroller will understand that the data is being read from/ written to a peripheral register and sends the data to the correct register. For the ATMEGA328, if we want to configure I/O registers, we’ll need to write to registers located in addresses 0x0020 to 0x005F.
Great; now we know that we have to read and write registers to control peripherals, and we know we can address them using memory mapped I/O. But if we want to write to DDD0 and PORTD0, as well as read from PIND0, what addresses do we use? This is where the vendor comes in (Microchip / Atmel, in this case); they need to tell you want registers are located at what addresses. Looking through the datasheet, there are two locations where you can find this information: the register description for I/O ports, and the register summary register summary for the whole chip, both shown below:


Note that the address is given in two forms; one as the absolute address in parentheses and as an offset from the base address (which is 0x20 in this case). So, to set DDD0 and PORTD0, as well as read from PIND0, you’ll need to read/write bit 0 from registers DDRD, PORTD and PIND respectively, which have addresses 0x0A (0x2A), 0x0B (0x2B) and 0x09 (0x29), respectively.
Let’s do a quick recap: by looking at the GPIO driver circuit, we saw we can control and get information about the GPIO by reading and writing registers. Thanks to memory mapped I/O, we have a system for reading and writing to specific registers in the chip. And from the datasheet, we know what addresses we need to read and write. But here’s a question; in the example code given at the beginning of this post, we wrote to DDRD and PORTD. How does the compiler know what DDRD and PORTD are? Does it even know they’re 8 bit registers? If so, does it know what their addresses are?
The answer is yes, since the compiler doesn’t throw an error and the microcontroller behaves as expected. But how does it know? Well, the Arduino IDE hides this from you for the sake of user-friendliness, but if you examine your Arduino code through something like Platform I/O or Atmel Studio 7, then with enough snooping around you’ll find the following:

The picture above shows the definitions used to read and write registers. PIND, DDRD and PORTD are registers and have the address offsets of 0x09, 0x0A and 0x0B, respectively. The offset is put into the _SFR_IO8 macro, which calculates the absolute address using __SFR_OFFSET (which is 0x20, as expected). Then, the absolute address is cast as a pointer to a volatile 8 bit value using the _MMIO_BYTE macro, which is then dereferenced, allowing us to read and write the selected register. The definitions for PIND0, DDD0 and PORTD0 show what position in the register the relevant bits are, which means you can use these definitions instead of memorizing the bit position or looking it up in the datasheet. Thanks to these definitions, you can write much more human readable code; an example of setting the DDD0 bit is shown below, with and without macros. Note that the header files that these definitions come from, like the datasheet, are provided by the chip manufacturer or software you use for programming the microcontroller; you’re not expected to write these yourself.

For those interested, using the keyword volatile is extremely important. Volatile means the CPU must read or write from memory (or memory mapped I/O registers). If volatile is not used, then the CPU may have a local copy of DDRD in its cache, and when you change its value, the cached value changes, but not the actual data direction register. When volatile is used, the CPU doesn’t keep a local copy, ensuring that what you’re reading is directly from the memory mapped register, or you’re writing to the memory mapped register.