Low Level GPIO

Introduction to Low Level processor functions

Platform Test Tools SAM3U2 Firmware nRF51422 Firmware Software
Master User Code AP2 Emulator IAR

Prerequisite Modules


This module starts to dig in to the real engineering side of embedded systems development as we begin to look into the code behind the EiE firmware system and what it takes to understand a microcontroller at the register level. Registers are the memory locations that will make the microcontroller behave in certain ways depending on the logical value in this memory. If you plan to design embedded systems, this is absolutely essential knowledge.

A great place to start learning about registers is with GPIO: General Purpose Input Output. It is the simplest way to demonstrate the logical setup and operation of the processor since you can physically see and interact with the processor pins to compare what you are doing in your code.

Every processor is unique, though will share many of the same characteristics as other processors, especially in the same family of processors. This is not something you can guess at – you have to have the manufacturer’s documentation to reference. Make sure you have a copy of the SAM3U user guide. PLEASE DO NOT PRINT THE WHOLE DOCUMENT.

A learning process

While all this may seem confusing at first, as you learn more about processor peripherals and low level operation it becomes clear that there is a very repeatable process to follow. Aside from specific details, the steps to make any part of any microcontroller work are pretty much the same. If you get good at the process, than the details become the only thing to figure out regardless of the peripheral or even the processor. We still follow this exact process every time when learning a new peripheral:

  1. Read the whole datasheet / user manual section on the peripheral. Make notes on anything that stands out such as register or bit names, special conditions, tips and tricks, or additional information you might need. The notes you need to make become more obvious as you do this more often and start to know what you’re looking for.
  2. Study the registers. Determine which ones will be necessary to accomplish what you need the peripheral to do.
  3. Examine the block diagrams. See if they make sense, contain information that you might need, and how they compare to any other similar peripherals you have used before.
  4. Define the configuration values needed for the registers. Pay attention to special bits that enable power or clock signals to the peripheral.
  5. Add the peripheral configuration firmware and thoroughly test.. Make sure that the peripheral is being configured correctly using the debugger to inspect each register that you modify. This may take some time, and if it is not working it may take hours of debugging looking for the right bits to make the peripheral do what you want.

Spend the time to really understand the peripheral and be very confident it has been configured correctly. This time is a great investment. Front loading development effort can yield future time savings in multiples or orders of magnitude since problems identified early are substantially easier and faster to fix. Building your code on a weak foundation of knowledge is like building a house on quicksand.

Once low level firmware is coded you can start designing whatever code or API you intend to create that will allow applications to easily and reliably access the peripheral. You can often start with some very basic test code to verify the peripheral operation but can also be used in the API functions. This minimizes any wasted effort.

So let’s go through this process as an example. The majority of the information you need right now is in section 30: Parallel Input/Output Controller (PIO).

Read pages 509-523 in the User Guide (up to 30.7 PIO User Interface). You do not have to understand it all, but it is important to go through it. It might be useful to print a copy of just these pages so you can make notes.

Make a list of all the terms you do not understand and discuss them with your group. If you cannot determine what something means, make sure you ask questions or find the answer with an internet search. At this point you want to understand terms that you cannot even define – you do not need to understand the implementation of the PIO peripheral.


Table 30-2 in the user guide lists all of the registers that are applicable to the PIO peripheral and there are a lot of them! The first few are shown here:

6430F–ATARM–21-Feb-12, pg. 522

If you read the register names in the list, you should see they are mostly self-documenting. For example, it would be difficult to wrongly guess that the “Output Enable Register” is a register used to enable output on a pin, though you might not know what that means exactly. Also notice that the logical names are carefully named to line up with the full name. Each name starts with the name of the peripheral (PIO) and generally uses the first letters of the longer name. So it should make sense to you that the PIO’s Output Enable Register is PIO_OER.

PIO_OER is the name that will be provided in the processor’s header file to reference the register in firmware. While you do not have to use the provided symbol, you would be crazy not to. Every register has an address so this header file definition is related to that address. If you want to know exactly the address of the register in memory, add the “Offset” value in Table 30-2 to the base address of the peripheral.


  1. What is the absolute address of the PIO Controller “A” PIO_OER register?
  2. From the list of registers, how many names can you read and take an educated guess about what they do (without looking further to the detailed description and bit functions)?
  3. Where is the evidence in this table that supports the statement, “microcontrollers always start up with GPIO set to high impedance inputs.”

The next thing to notice is that most registers are in groups of 3 with an Enable, Disable and Status function. So the register list is only a third as long as you thought! Register access like this seems very typical for ARM processor implementations across the various vendors whose processors we’ve worked with.

  • Enable registers are used to set (make logic high) bits in their corresponding register. Atmel also uses SET and CLEAR registers in other cases. The distinction is subtle but valid.
  • Disable registers are used to clear (make logic low) bits in their corresponding register. These are often called CLEAR registers.
  • Status registers are used to read the current value (bit states) in a register.

Enable and Disable registers are both write-only, which means you will never see a value other than 0 in a debugger window. A point that tends to be confusing is that you always write logic high values to the bits in these registers. If you write code to read these registers, the value you get is garbage (probably all 0s).

Complete the pseudo code to enable output on PB02 and disable output on PB03:


The Status register is read-only. If you try to write to this register, nothing happens. If you have code to read this register or just look at it in a debugger window, the bits are the result of operations with the Enable and Disable register and represent the current configuration of the peripheral. For the PIO Controller, most of the Status registers are initialized by hardware to the values shown in the Reset column. Not all processor registers are automatically initialized.

The reason there are Enable, Disable, and Status registers is partly due to convenience and code efficiency, and in some cases due to a legacy problem that embedded designers know as the “read-modify-write” issue. Programming in C tends to mask these issues, so it is excellent that processor vendors have adopted this type of interface to the hardware. As we go through the various low level modules in EiE, you will start to see examples of all of this, but we can start you thinking about this with a simple exercise.

Explain the difference between these two lines of code in terms of what happens to u8VariableA:

u8VariableA = 0xA5;
u8VariableA |= 0x5A;  

What is the value of u8VariableA after the code above?
What is the minimum number of lines of assembly code to implement each of the lines of C code?
The second line of code can result in one of the most difficult bugs to find in a firmware system – do you know why? Don’t worry about it yet, but a hint is that we will get to this in the Interrupt module.

Continue reading the user guide to see the detailed register descriptions. You should see that many registers operate “bit-wise” meaning that individual bits correspond to specific functions. For the PIO registers, many of the register bits map directly to the corresponding pin. For a 32-bit processor, registers are often (but not always) 32-bits wide. Since there are more than 32 IO lines on the SAM32U, multiple PIO registers are available, PIOA, PIOB and PIOC. PIOC is only available on the processors that have even more IO lines. Keeping with the PIO_OER example, the bit definitions are shown here:

6430F–ATARM–21-Feb-12, pg. 525

The bit functions are very simple and identical for P0-P31: write a 1 to a bit to make the corresponding pin an output. Writing a 0 to any bit location does nothing, which is what you would expect for an enable (or set) register. But what does this actually do? You should realize that any hardware signal that drives part of the development board circuit should require the corresponding pin to be configured as an output. If there is a single most fundamental piece of knowledge to understand about GPIO, this is it. We can pretty much guarantee that on any microcontroller you work with, you will have to specify where the pins are inputs or outputs.

As an example, consider the LED outputs on the EiE 1 development board. The related section from the schematic is shown. Now is a good time to remind you to have easy access to the hardware schematics for whatever design you are working on.


The shorthand logical names for the processor pins are on the right hand side, PB13-PB20. “PB” means the pins belong to PIOB. The actual hardware pin number does not matter to firmware and likely changes if you change to a different package. To keep our code self-documenting and easy to read, we define our own symbols that correspond to the bit location that will be used to access the signal of interest. These are found in our board-specific hardware definition file. The example shown below is from mpgl1-ehdw.03.h which should match the EIE 1 development boards.


Look closely and you can see that the symbol values are simply single bits at the correct bit number that corresponds to their hardware location. So these values are intended to used in bit-wise operations with registers.

To be complete, when you are configuring a peripheral function using the Enable register, you should be diligent and not only write the Enable bits that you want set, but also write to the disable bits that you want clear. In almost every case, the Disable literal is just the inverse of the Enable literal when you are first setting up the registers.

As an example, here is pseudo code to set the red LED as the only output on PIOB using the definition from the hardware header file:


Note that while you could set up the register definitions to work this way, this is NOT the typical convention for ARM Cortex processors – we’ll get to that shortly.

GPIO Hardware

Embedded designers need to understand the physical and logical relationships that operate in the microcontroller. Writing a line of code to load a register actually translates to logic high and low voltage levels in the processor which will activate logic gates to make the corresponding function(s) execute. You do not have to understand how the logic works (it can involve dozens or hundreds of gates), but you should recognize that this is what is happening.

Block diagrams are also really useful at giving you a hint at what (if any) dependencies a peripheral has on/with other parts of the microcontroller. This may be parts of the core, different signal busses, external pins, or other peripherals. The figure below is the PIO Controller block diagram from which we can learn some important information.

6430F–ATARM–21-Feb-12, pg. 510

The obvious part of this diagram is that the PIO Controller connects directly to the physical hardware pins on the microcontroller which ultimately allows reading and writing digital logic signals to the world outside of the processor package (and in some cases analog levels, though that is not shown above). The PIO ties into the processor’s APB (Advanced Peripheral Bus), the details of which are outside the scope of this module.

The two Embedded Peripheral blocks refer to the other processor peripherals that can be multiplexed through the PIO Controller. For example, if you wanted to use a USART peripheral, you would tell the PIO Controller to output the logic signals from that peripheral instead of just a digital input or output directly from the PIO hardware.

We can see that there is a single connection from a PIO Controller to the NVIC (Nested Vectored Interrupt Controller) where the PIO interrupt is provided. As you work with ARM Cortex processors you will see this is very standard and we will explore more about this in the Interrupt module.

Lastly you can see that a clock signal from the PMC (Power Management Controller) is required to drive the PIO Controller. So to properly run the PIO Controller, we will have to check how to turn on the clock in the PMC. Clock and power control are often a bit tricky to figure out! If you are working with a peripheral and getting unexpected results as you try to read and write the peripheral registers, this is a good indication that the peripheral does not have power and/or a clock. The most obvious clue is seeing that a peripheral’s registers do not change when you write values to them when you’re looking in the debugger.

Logic Block Diagram
The majority of microcontroller peripheral descriptions will also include a block diagram of the peripheral logic to give you a bird’s eye view of how the logic works. These diagrams can end up being very useful to see how the registers influence the signal chain in the peripheral. Here is the main block diagram for the GPIO peripheral in the SAM3U2:

6430F–ATARM–21-Feb-12, pg. 512

This shows the hardware on one pin and in the actual processor this would be connected to all the pins on a port (up to 32). Array notation [] is used to indicate which pin is being referenced. So if we were discussing pin 20, we would include [20] in our notes.

From this diagram, we can determine the effect of a lot of the PIO registers without even knowing what they are. Using the figure below, we are going to trace the impacts of a value in PIOB_OER to see how it sets up the logic to allow us to use the PIOB_SODR and PIOB_CODR (Set and Clear Output Data Register) to end up with the logic signal we want appearing on PB_20_LED_RED pin.


To read this, consider the following:

  • The numbers in blue represent the logic levels that are propagating through the signal chain.
  • The numbers in green are the values to set the multiplexers (muxes) in the steering logic to get the signal we want. These come from other registers that we will touch only briefly on right now.
  • The red “X” values are the LED state we want which would be in PIOB_ODSR[20] depending on what we write to PIOB_SODR[20] and PIOB_CODR[20]. If X is 1, the voltage at PAD will be Vcc (logic high) and the LED is on; if X is 0, the voltage at PAD is 0 (logic low) and the LED is off.

So remember that by configuring PIOB_OER[20] to logic 1, we are telling the processor that we want this pin to be an output. Ultimately this turns on the output buffer at the very end so the PAD signal is driven high or low depending on the X at its input.

Trace through the diagram and describe what is happening. Write down the values at location [20] in each of the registers that are involved.

Here is our description of the signals involved:

  1. PIOB_OER[20] is set which produces the logic 1 value into the inverter at the top left to get a logic 0 at the first mux.
  2. The first mux is set to select path 1 which allows the PIOB_OSR signal through. The complimentary mux here must then allow the PIOB_ODSR[20] signal through (the current state of the LED we want).
  3. The second pair of muxes are set for path 0 – trust that for now, or read about PIO_MDSR. Therefore the OR gate output is 0 at the top, and the PIOB_ODSR[20] is allowed to propagate through to the output buffer.
  4. The output buffer control sees the 0 from the OR gate which is inverted so the output buffer is on. Therefore the X is driven on PAD.

In most cases, you usually do not have to so carefully look through the logic diagrams for a peripheral. Configuration values can typically be determined just by reading the register descriptions. However, understanding these diagrams and being able to trace through them to determine the register dependencies is really important especially if the configuration you set up does not work as expected. This happens to be a big hint for the Timer module when you get there.

Assume a pin is configured as an input. Trace the signal flow and describe what the signal will “see” as it propagates through the PIO peripheral and what registers will be involved. What register can be read to allow firmware to know if the input is logic high or low? If the input changes state at time t=0, when will this register be updated?

Register selection and configuration

So what registers do we actually need? It is very common to not require all of the registers that a peripheral offers. Though there are many configuration options for a peripheral, a lot of the choices you make depend heavily on the hardware that is attached so it is fairly safe to work only with the registers that will apply to the hardware. There are always exceptions to the rules!

From Table 30-2, the register groups are as follows:

  • PIO Enable / Disable / Status: used to select either GPIO function controlled by the PIO, or allocate one of two connected peripherals (where applicable). We definitely need this register.
  • Output Enable / Disable / Status: used to configure a pin as an output. This register has no effect if the corresponding pin has been assigned to a peripheral by disabling the PIO using PIO_PDR. Definitely needed.
  • Glitch Input Filter Enable / Disable / Status: enables a hardware filter to help remove very fast signal changes on an input pin. Possibly needed under certain hardware conditions.
  • Set Output / Clear Output / Output Data Status / Pin Data Status: The first three are on the output chain and will drive the PIN as long as the required other registers are configured. The PDSR is used to read the value of pins as inputs. For pins that are configured as outputs, then ODSR and PDSR should be the same. However for input pins, PDSR can be totally different than ODSR. This is an interesting scenario with common applications. Definitely needed for output pins.
  • Interrupt Enable / Disable / Mask / Status: Allows any pin signal to contribute to the overall PIO interrupt signal to the NVIC. More on this in the Interrupt module. Most likely needed, though depends on if we plan to use interrupts or polling on certain inputs.
  • Multi-driver Enable / Disable / Status: From the block diagram, we see that this allows both a peripheral and the ODSR to drive the output pin, sort of. In this state, built-in pull-up resistor would have to be used. This is more commonly called “open drain” output. Only special hardware configurations would need this. The EiE hardware does not require any open drain outputs, but we should make sure that this is disabled.
  • Pull-up Enable / Disable / Status: Used to connect a built-in weak pull-up resistor. This can be a life saver if you accidentally need a pull-up on a signal line but forgot to put it on the PCB. It is also used commonly for open-drain drivers or just to ensure unused lines are not floating without having to consume board space with external resistors. Likely needed.
  • Peripheral AB Select: used to select which of two available peripheral outputs are chosen to drive the output where required. Needed.
  • Filter configuration (4): several built-in filters for common input signals. Hardware features like this are nice, but perhaps should be used as a last resort. We will not use them but will make sure they are disabled.
  • Output Write Enable / Disable / Status: a locking function to prevent accidental writes to the output registers. We will not use this, though it does have some nice applications for critical systems.
  • Additional Interrupt Modes Enable / Disable / Mask: This allows further customization of how the input interrupts work. Basically it changes the interrupts from dual mode to single mode (e.g. from rising AND falling edge, to rising OR falling edge). Needed.
  • Edge Select / Level Select / Status: defines interrupt behaviour on input lines with interrupts enabled. Needed.
  • Falling Edge or Low Level / Rising Edge or High Level / Status: For interrupt configured pins. Yes, we will use this.
  • Lock Status / Write Protect Mode / Status: Additional protection / security on IO behaviour. Ultra robust systems could definitely make use of these types of protection and status. However, we will not use these as it is beyond the scope of what we want to cover here.

Defining start-up values

The PIO Controller registers are really straightforward even though there are a lot of them. Almost all are single-function, albeit applied to up to 32 pins. The tedious part comes from setting up the values to write into all of the registers we have identified as required. Many vendors offer very basic library functions that can take care of pin configuration. We’ve never like using these as it is unclear exactly how they work unless you look at the source code, and many obfuscate what they leave unconfigured.

It also seems that for a fully utilized processor where the majority of pins are connected to hardware and need configuration that the amount of code would actually be quite substantial if the vendor functions were used. Setting up a processor like this also seems to be difficult to read / debug the firmware, though that may be biased because we have done it our way for so long.

It is our preference to comment every bit in every register and build up a single hex value that can be written directly to the register of interest and configure all pins simultaneously with a single write to the register. This may sound extremely tedious, but with some cut and paste techniques when first setting up the hardware definition file for a new development board, it is really not that bad!

When we start work with a new processor, we set up the pin names and make a template listing of all the bits without any details. This template can then be copied and the actual bit functions defined (also done mostly with copy and paste). We build the nibbles and then write out the hex value as a #define. By our own convention, this value is named _INIT. PIOB_OER_INIT for the EiE1 dev board is shown here:

#define PIOB_OER_INIT (u32)0x01BFFFE0
    31 [0] PB_31_
    30 [0] PB_30_
    29 [0] PB_29_
    28 [0] PB_28_

    27 [0] PB_27_
    26 [0] PB_26_
    25 [0] PB_25_
    24 [1] PB_24_ANT_SRDY output enabled

    23 [1] PB_23_ANT_MRDY output enabled
    22 [0] PB_22_ANT_USPI2_CS
    21 [1] PB_21_ANT_RESET output enabled
    20 [1] PB_20_LED_RED output enabled

    19 [1] PB_19_LED_GRN output enabled
    18 [1] PB_18_LED_BLU output enabled
    17 [1] PB_17_LED_YLW output enabled
    16 [1] PB_16_LED_CYN output enabled

    15 [1] PB_15_LED_ORG output enabled
    14 [1] PB_14_LED_PRP output enabled
    13 [1] PB_13_LED_WHT output enabled
    12 [1] PB_12_LCD_BL_BLU output enabled

    11 [1] PB_11_LCD_BL_GRN output enabled
    10 [1] PB_10_LCD_BL_RED output enabled
    09 [1] PB_09_LCD_RST output enabled
    08 [1] PB_08_TP62 output enabled

    07 [1] PB_07_TP60 output enabled
    06 [1] PB_06_TP58 output enabled
    05 [1] PB_05_TP56 output enabled
    04 [0] PB_04_BLADE_AN1 input

    03 [0] PB_03_BLADE_AN0 input
    02 [0] PB_02_BUTTON3 input
    01 [0] PB_01_BUTTON2 input
    00 [0] PB_00_BUTTON1 input

The value of this is that it forces you to look at and configure every bit in each register. As you will see in later modules, some registers have multiple functions corresponding to different bits, so commenting what they do saves a lot of time looking up what certain bits do. The downside is that this is tedious, as mentioned, and you must be careful to not make errors either initially (especially when translated from nibble to the hex value). If you decide to change a setting, you must update the comment, the binary value in the comment, and the hex file.

Writing to registers with CMSIS convention

The last thing to understand is how to write the configured values into the registers we want. This is simple once you understand it. If you do not understand pointers, structs, and pointers to structs in C, you need to review that, first.

Since ARM licenses their cores to many different microcontroller manufacturers, there is a lot of inconsistency in documentation despite the similarities in hardware. When the Cortex family of processors came out, ARM introduced CMSIS – Cortex Microprocessor Software Interface Standard as an attempt to improve consistency and portability. You may read about CMSIS directly from ARM by clicking HERE.

The parts of CMSIS that correspond directly to the core are pretty good, though every IDE / compiler works differently and the built-in core features do not seem to be consistently implemented. In the case of a microcontroller where a manufacturer adds their own peripheral implementations, the only compatibility is style, but at least that is something.

To explain this, start by looking at the line of code in the GpioSetup() function:


We know that PIOB_OER_INIT is just the #define we built in the previous step. The code on the left of the assignment is a standard deference of a pointer to a struct. PIO_OER is the register we want to load and from reading the datasheet, we know that this value is an offset to some sort of base address of the peripheral. So “BASE_PIOB” makes sense, too. The AT91C is just the Atmel family name of the processor series. So how does this actually work and where to these values come from? Most importantly, if you want to write other registers, how do you figure out the pointer and struct to reference? The following process gives you the steps to find what you need for any register in any peripheral, so note this carefully!

Make sure you have the Master code loaded in IAR. Right click on PIO_OER and click “Go to definition of.” You should end up on line 1456 in AT91SAM3U4.h. PIO_OER is part of a struct typedef and all of the other registers for a PIO peripheral appear to be present in the order that they appear in the user guide. The type AT91_REG is confusing and is related to the IAR compiler – feel free to explore this on your own, but basically it is a 32-bit address value. A struct’s members are continuous in memory, therefore each register is 4 bytes apart and if you correlated this to the offsets in the user guide you would see that the addresses line up.


The typedef closes with:


This gives the struct a name that can be used directly or as a pointer, even though we always seem to use it as pointer.

Now, right click on AT91PS_PIO and use ctrl-shift-f to find in files. You should get 6 results as shown.


In this case you can see three base addresses (AT91C_BASE…) since there are up to three PIO controllers (A, B, C). Since we have been working with Port B, double click the entry for PIOB to go to that line. You should see:

#define AT91C_BASE_PIOB  (AT91_CAST(AT91PS_PIO)  0x400E0E00) // PIOB Base Address

So this is simply assigning the peripheral base address to the symbol AT91C_BASE_PIOB in a way that the compiler will be happy to use it like a regular address.

Copy the #define name and you can now put together your register assignment:


In most cases this method of finding the symbols you need will work, though we have noticed there are few errors or different conventions on a few registers that will force you to think a bit to find what you’re looking for. Good examples of this are in the Timer module.

In summary, write code to load a register as follows:

  1. Type out the register name from the datasheet and then “Go to definition of” this name.
  2. Go to the end of the struct typedef and “find in files” the name of the struct.
  3. Copy the base address mnemonic and build the pointer-to-struct deference and assignment statement.

Use what you have learned to write a few lines of code to determine if BUTTON0 is pressed. Assume that all of the required peripheral configuration is already complete.

Testing the GPIO setup

The last skill we need to develop here is debugging. Do the following:

  1. Load the Master code in IAR and launch the debugger.
  2. Put a break point on the first line of code in the GpioSetup(void) function and run to this line.
  3. Configure a two Register windows as shown so you can see the PIOA and PIOB registers.
  4. Single step over each register operation and see if you can predict the result of each line of code. What registers change? There are two lines of code that visibly affect the hardware. What are they, and what is happening to the hardware at these times?



  • Determine what register to modify to manually update bits to turn LEDs on and off and demonstrate that you can do this.
  • What register shows the logic state of the buttons changing? Demonstrate this happening.
  • Write code in user_app.c to make sure the code you wrote to check for a button press works. Turn on an LED if a button is pressed, turn it off when the button is released. Don’t worry about debouncing.

There was a LOT of information in this module which might make you think that working with any peripheral is going to be extremely difficult and time consuming. However, about 90% of what was covered here applies to any other peripheral, so you will not have to learn new syntax or process to make other peripherals work. Practice is the key so you can focus on understanding specific details about new peripherals.

2017-FEB-15: Draft release.