Using I/O Pins

The Teensy has 21 I/O pins that you can use to control devices or read signals. The pins are named with a letter and number. Pins are grouped into 8 bit ports represented by the letter, and the number represents the individual bit within the port. B4 is bit 4 in port B, for example.

Accessing A Single Bit

Usually I/O pins are accessed as single bits, where bits are numbered 7 to 0. To write a single bit without changing the others, the logical OR and AND operation are used together with a number that causes only the desired bit to change. For example:

PORTD |= (1<<6);   /* set bit 6 in PORTD to 1 */

The number is 64, or 0x40 in hex notation, but it is usually written as (1<<6), which means 1 shifted 6 places, to indicate bit 6 is written with a 1.

To clear a bit to zero, the syntax is similar, using logical AND and inverting all the bits.

PORTD &= ~(1<<6);   /* clear bit 6 in PORTD to 0 */

This may seem complex or inefficient, but the compiler is able to recognize these sequences will set or clear a single bit in an I/O register, and it will generate only a single CBI or SBI instruction that is very efficient.

To read a single bit, the syntax specifies the entire byte, but an AND operation forces the 7 unwanted bits to zero.

if (PIND & (1<<2)) {
    /* Pin D2 is High */
} else {
    /* Pin D2 is Low */

Again, the compiler recognizes this syntax and uses an efficient bit test and jump instruction sequence.

I/O Registers

Each port (8 pin) has 3 registers. The "x" on the end is replaced by the actual port letter. For example, DDRC controls the direction of pins C0 through C7.

DDRxConfigure Direction: 0=Input, 1=Output
PORTxSet Output (when DDRx=1): 0=Low Output, 1=High Output
Config Input (when DDRx=0): 0=Normal, 1=Pullup Resistor
PINxRead the pin

One simple but highly effective usage strategy is to configure all the pins at the beginning of your program, and then use with PORTx registers to control the outputs and PINx registers to read the inputs.

You can change the direction of any pin at any time, which is necessary when interfacing other chips that communicate in both directions on a signal. Care should be taken to switch the pins to inputs before another chip begins driving the pin, so that both chips are not driving the wire at the same time.

You can also implement an "open collector" output by loading a zero into the PORTx register and then using the DDRx register to output low or allow the pin to "float".

Built-In Peripherals

Most pins have one or more alternate functions, which are controlled by built-in peripherals. By default, the perhipherals are disabled and you have complete control of all the pins using DDRx, PORTx and PINx.

When you enable a peripheral, it takes control of the associated pins. Some peripherals, such as the UART, will automatically override the DDRx register, others like the timers will control the pin but you must configure the DDRx register.

TODO: chart with all alternate pin function

When planning a project, usually a first step is to select the built-in perhipherals that will be used. The remaining pins may then be allocated to signals that you will control with DDRx, PORTx and PINx.