LowLevelPortIo

From Nutwiki
Jump to: navigation, search

Memory Mapped or Port Mapped

Low level hardware ports (e.g. digitial or analog I/O, RS232 etc.) are typically controlled by specific registers. For example, most CPUs used in embedded systems offer bi-directional digital I/O port control by a set of registers like

  • Port direction register
    Each bit in this register specifies, if the corresponding port pin is used as an input or output.
  • Port output register
    If the corresponding bit in the port direction register is set to output, then the bit value in this register will directly set the port pin. If the bit is zero, the related output pin will be driven low. If the bit is set to one, then the pin will be driven high.
  • Port input register
    If the corresponding bit in the port direction register is set to input, then this register will reflect the current status at the related pin. If the signal level at the pin is low (e.g. tied to ground), the corresponding bit will be zero. If the level passes a certain voltage, then the bit in this register will change to one.

Depending on the CPU, hardware I/O port registers may be accessed like any other memory location. This is called memory mapped I/O. Some CPUs, most notably Intel's x86 series, offer a dedicated I/O bus, which is seperated from the memory bus. In this case the hardware registers are port mapped and specific CPU instructions like inp (read port) or outp (write port) must be used to access them.

Although all CPUs currently supported by Nut/OS provide memory mapped I/O, specific C language macros for port access are preferred in order to hide this difference. These macros can be easily adapted to both, memory mapped and port mapped hardware. Furthermore, it is easier to get applications running in an emulator, if access to I/O registers is distinguishable from access to memory locations.

Register Size

Beside differing access methods, CPUs provide different register sizes. Typically, 8-bit machines use 8-bit I/O registers while 32-bit machines use 32-bit I/O registers. Nevertheless, while 8-bit hardware may use 16 or 32 bit registers, almost all 32-bit CPUs are able to access registers with 16 or 8 bits in size. Thus, a number of macros for different register sizes is provided. The Nut/OS macros inr() and outr() provide access to the CPU specific register size (missing on 8-bit targets).

Nut/OS Port Access Macros

Register Size Value Type Output Macro Input Macro
8 Bit unsigned char outb(reg, val) inb(reg)
16 Bit unsigned short outw(reg, val) inw(reg)
CPU specific
8, 16 or 32 Bit
unsigned int outr(reg, val) inr(reg)


Using Digital I/O on AVR

Digital input requires two steps.

  1. Setting the desired pin to input mode.
  2. Reading the port input register.

Reading all 8 input levels at PORTB

<source lang="c">

  1. include <compiler.h> /* Provides compatibility among platforms. */

unsigned char val; outb(DDRB, 0x00); /* Set all eight bits on PORTB as inputs. */ val = inb(PINB); /* Read levels at all eight pins of PORTB into a variable. */ </source>

Using PORTB for output isn't hard either.

<source lang="c">

  1. include <compiler.h>

outb(DDRB, 0xFF); /* Use all eight bits on PORTB as outputs. */ outb(PORTB, 0x00); /* Set output levels at all eight pins of PORTB to low. */ outb(PORTB, 0xFF); /* Now set all levels to high. */ </source>

The following ports exist on Ethernut 1 and 2 with ATmega128 CPU:

Port Register Names Usage
A DDRA
PORTA
PINA
Not available for digital I/O.
Used as multiplexed address/data bus.
B DDRB
PORTB
PINB
Available for digital I/O.
Ethernut 1: Bit 1 must not be driven during SPI programming.
C DDRC
PORTC
PINC
Not available for digital I/O.
Used as address bus.
D DDRD
PORTD
PIND
Available for digital I/O.
Ethernut 1: Bits 2 and 3 may be used for RTS/CTS handshake when R32 and R33 are mounted.
E DDRE
PORTE
PINE
Partly available for digital I/O.
Bits 0 and 1 are used for RS232.
Bit 5 is used for Ethernet controller interrupt. May be switched to bit 6.
F DDRF
PORTF
PINF
Available for digital I/O.
JTAG must be disabled to use upper 4 bits.



Using Digital I/O on AT91 ARM

Here's how to read the level at the 8 least significant bits of the digital I/O port on the AT91R40008 CPU.

 #include <compiler.h> /* Provides compatibility among platforms. */
 
unsigned int val;
outr(PIO_ODR, 0xFF); /* Use lower 8 bits for input. */ outr(PIO_PER, 0xFF); /* Enable PIO function. */ val = inr(PIO_PDSR); /* Read pin status. */

For digital output, a sightly different method is used on Atmel's AT91 series. Different registers are provided to set and clear the bits. In other word, there is a specific register for setting a digital output to high and another one to set the output to low.

 #include <compiler.h>
 
outr(PIO_OER, 0xFF); /* Use lower 8 bits for output. */ outr(PIO_PER, 0xFF); /* Enable PIO function. */ outr(PIO_CODR, 0xFF); /* Set bits to low. */ outr(PIO_SODR, 0xFF); /* Set bits to high. */

Native AVR I/O Register Access

When looking to other AVR sample code apart from Nut/OS, you may have noticed that there are no specific input and output macros. Most AVR compilers allow to use I/O registers like C variables. For example, setting the four most significant bits of PORTB to outputs and the remaining four least significant bits to inputs can be written as

 DDRB = 0xF0;

This will even work for single bits, like setting output bit 4 of PORTB to 1, leaving all other bits unchanged.

 PORTB |= 0x10;

You can use this method in your Nut/OS applications as well. However, as explained above, this way of register access is not fully portable and therefore Nut/OS prefers input and output macros, like

 outb(DDRB, 0xF0);
 outb(PORTB, inb(PORTB) | 0x10);

However, that second statement may not be exactly translated in the same machine instructions as the statement that uses PORTB like a C variable. That's because on certain registers the CPU is able to modify single bits in a single instruction. When using inb() and outb(), the compiler will at least generate three machine instructions.

  1. Read value from PORTB register
  2. Set bit 4 of this value
  3. Write value back to PORTB register

To solve this problem, Nut/OS provides two additional macros, which allow to set or clear single bits. Setting bit 4 of register PORTB can be written as

 sbi(PORTB, 4);

Note, that sbi() as well as its counterpart cbi() for clearing bits expect bit numbers instead of mask values.

Native AT91 ARM I/O Register Access

Developers, which are familiar with the header files provided by Atmel for I/O register access will miss the register structure definitions. They are most convenient and produce highly readable code. However, they are not fully portable, very difficult or impossible to emulate and they can't be included into low level assembler code.

Although not recommended, you can use these header files in your Nut/OS applications.

Nut/OS Generic I/O Functions

A GPIO module is offered by Nut/OS, which offers I/O register access in a target independent way. When adding

#include <dev/gpio.h>

to your source file, you can use any of the functions given in the table below.

Function Purpose Example
GpioPinConfigGet Returns pin configuration.
GpioPinConfigSet Set pin configuration. GpioPinConfigSet(NUTGPIO_PORTB, 4, GPIO_CFG_OUTPUT);
GpioPortConfigSet Sets port wide pin configurations.
GpioPinGet Returns pin level.
GpioPinSet Sets pin to a specified level.
GpioPinSetLow Sets pin level to low. GpioPinSetLow(NUTGPIO_PORTB, 4);
GpioPinSetHigh Sets pin level to high. GpioPinSetHigh(NUTGPIO_PORTB, 4);
GpioPortGet Return all pin levels of a port.
GpioPortSet Set all pin of a port to a specified level.
GpioPortSetLow Set multiple pin levels of a port to low.
GpioPortSetHigh Set multiple pin levels of a port to high.
GpioRegisterIrqHandler Registers a GPIO pin interrupt handler.
GpioIrqEnable Enables a GPIO interrupt.
GpioIrqDisable Disable a GPIO interrupt.

Note, that these functions are not only much slower that the generic access methods, they also produce significantly larger code.

Common Pitfalls

No Input Change on AVR

Make sure you use inb(PINB), not inb(PORTB). The latter doesn't query the status of the input pin, but of its pull-up resistor.

16 Bit Register Access Fails on AVR

If two 8-bit accesses work while a single 16-bit access fails, then the register you are using requires a different order. The 16 bit outw() and inw() macros write or read the low byte first, followed by the high byte. This may not work on all registers, specifically with AVR timers/counters.

No Input Change on ARM

Most ARM CPUs require to enable the PIO clock in order to latch input pins. This may be even true for output pins.

See also