You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
356 lines
8.9 KiB
356 lines
8.9 KiB
/** @file pio.h
|
|
@author M. P. Hayes, UCECE
|
|
@date 11 Jan 2006
|
|
@brief PIO hardware abstraction for AVR microcontroller.
|
|
|
|
@defgroup pio PIO driver
|
|
|
|
This module implements a driver for general purpose I/O ports.
|
|
Its purpose is to provide a microcontroller independent
|
|
abstraction of a PIO pin. Here's an example:
|
|
|
|
@code
|
|
#include "pio.h"
|
|
|
|
#define LED_PIO PIO_DEFINE (PORT_B, 5)
|
|
|
|
void main (void)
|
|
{
|
|
system_init ();
|
|
pio_config_set (LED_PIO, PIO_OUTPUT_LOW);
|
|
|
|
pio_output_high (LED_PIO);
|
|
|
|
while (1);
|
|
}
|
|
@endcode
|
|
|
|
In this example, when port B5 is configured as an output it is set
|
|
low.
|
|
*/
|
|
#ifndef PIO_H
|
|
#define PIO_H
|
|
|
|
#include "system.h"
|
|
#ifdef __AVR__
|
|
#include <avr/io.h>
|
|
#endif
|
|
|
|
/** AVR microcontrollers use two flip-flops per PIO stored in two
|
|
separate registers: the data direction register (DDRx) and the
|
|
port register (PORTx). The DDRx register controls the whether the
|
|
pin is an input or an output; the PORTx register determines the
|
|
output state when the pin is an output but also doubles as an
|
|
enable for a pull-up resistor when the pin is an input.
|
|
|
|
Mode: DDRx PORTx
|
|
Input, no pull-up 0 0
|
|
Input, pull-up 0 1
|
|
Output, low 1 0
|
|
Output, high 1 1
|
|
|
|
These two registers cannot be changed at the same time so a
|
|
transient intermediate state can occur if both these registers
|
|
need to be changed. This is only a concern for pins that need to
|
|
switched between being an input and an output; say for a
|
|
bidirectional communication link.
|
|
|
|
Let's consider the scenario where we change DDRx before PORTx.
|
|
|
|
If we want to reconfigure a PIO from an input with no pull-up to a
|
|
high output it will be momentarily a low output.
|
|
|
|
If we want to reconfigure a PIO from an input with pull-up to a
|
|
low output it will be momentarily a high output.
|
|
|
|
Let's consider the alternative scenario where we change DDRx after PORTx.
|
|
|
|
If we want to reconfigure a PIO from an input with no pull-up to a
|
|
high output it will be momentarily an input with a pull-up. This
|
|
is not a bad option since a high-impedance load will not notice.
|
|
|
|
If we want to reconfigure a PIO from an input with pull-up to a
|
|
low output it will be momentarily an input with no-pullup.
|
|
|
|
p.s. I am sure this subtle quirk has caused much grief to some
|
|
embedded systems engineers.
|
|
*/
|
|
|
|
|
|
|
|
/** Define port names; note not all the ports are available on some AVRs. */
|
|
|
|
enum {PORT_A, PORT_B, PORT_C, PORT_D, PORT_E, PORT_F, PORT_G};
|
|
|
|
typedef uint16_t pio_t;
|
|
|
|
|
|
/** Define PIO pin types. The two flavours of PIO_OUTPUT are to
|
|
ensure the port is configured without glitching due to the time
|
|
taken before calling pio_output_set. */
|
|
typedef enum pio_config_enum
|
|
{
|
|
PIO_INPUT = 1, /** Configure as input pin. */
|
|
PIO_PULLUP, /** Configure as input pin with pullup enabled. */
|
|
PIO_OUTPUT_LOW, /** Configure as output, initially low. */
|
|
PIO_OUTPUT_HIGH, /** Configure as output, initially high. */
|
|
} pio_config_t;
|
|
|
|
|
|
/** Define the pins. */
|
|
#define PA0_PIO PIO_DEFINE(PORT_A, 0)
|
|
#define PA1_PIO PIO_DEFINE(PORT_A, 1)
|
|
#define PA2_PIO PIO_DEFINE(PORT_A, 2)
|
|
#define PA3_PIO PIO_DEFINE(PORT_A, 3)
|
|
#define PA4_PIO PIO_DEFINE(PORT_A, 4)
|
|
#define PA5_PIO PIO_DEFINE(PORT_A, 5)
|
|
#define PA6_PIO PIO_DEFINE(PORT_A, 6)
|
|
#define PA7_PIO PIO_DEFINE(PORT_A, 7)
|
|
|
|
#define PB0_PIO PIO_DEFINE(PORT_B, 0)
|
|
#define PB1_PIO PIO_DEFINE(PORT_B, 1)
|
|
#define PB2_PIO PIO_DEFINE(PORT_B, 2)
|
|
#define PB3_PIO PIO_DEFINE(PORT_B, 3)
|
|
#define PB4_PIO PIO_DEFINE(PORT_B, 4)
|
|
#define PB5_PIO PIO_DEFINE(PORT_B, 5)
|
|
#define PB6_PIO PIO_DEFINE(PORT_B, 6)
|
|
#define PB7_PIO PIO_DEFINE(PORT_B, 7)
|
|
|
|
#define PC0_PIO PIO_DEFINE(PORT_C, 0)
|
|
#define PC1_PIO PIO_DEFINE(PORT_C, 1)
|
|
#define PC2_PIO PIO_DEFINE(PORT_C, 2)
|
|
#define PC3_PIO PIO_DEFINE(PORT_C, 3)
|
|
#define PC4_PIO PIO_DEFINE(PORT_C, 4)
|
|
#define PC5_PIO PIO_DEFINE(PORT_C, 5)
|
|
#define PC6_PIO PIO_DEFINE(PORT_C, 6)
|
|
#define PC7_PIO PIO_DEFINE(PORT_C, 7)
|
|
|
|
#define PD0_PIO PIO_DEFINE(PORT_D, 0)
|
|
#define PD1_PIO PIO_DEFINE(PORT_D, 1)
|
|
#define PD2_PIO PIO_DEFINE(PORT_D, 2)
|
|
#define PD3_PIO PIO_DEFINE(PORT_D, 3)
|
|
#define PD4_PIO PIO_DEFINE(PORT_D, 4)
|
|
#define PD5_PIO PIO_DEFINE(PORT_D, 5)
|
|
#define PD6_PIO PIO_DEFINE(PORT_D, 6)
|
|
#define PD7_PIO PIO_DEFINE(PORT_D, 7)
|
|
|
|
|
|
#ifdef DEBUG
|
|
|
|
/* Define PIO as a unique integer. */
|
|
#define PIO_DEFINE(PORT, PORTBIT) ((PORT) * 8 + (PORTBIT))
|
|
|
|
|
|
/** Configure pio.
|
|
@param pio PIO to configure
|
|
@param config PIO configuration type
|
|
@return non-zero for success. */
|
|
bool pio_config_set (pio_t pio, pio_config_t config);
|
|
|
|
|
|
/** Return pio configuration
|
|
@param pio
|
|
@return config */
|
|
pio_config_t pio_config_get (pio_t pio);
|
|
|
|
|
|
/** Set pio high.
|
|
@param pio */
|
|
void pio_output_high (pio_t pio);
|
|
|
|
|
|
/** Set pio low.
|
|
@param pio */
|
|
void pio_output_low (pio_t pio);
|
|
|
|
|
|
/** Toggle pio.
|
|
@param pio */
|
|
void pio_output_toggle (pio_t pio);
|
|
|
|
|
|
/** Read input state from pio.
|
|
@param pio
|
|
@return input state of pio */
|
|
bool pio_input_get (pio_t pio);
|
|
|
|
|
|
/** Get output state of pio.
|
|
@param pio
|
|
@return output state of pio */
|
|
bool pio_output_get (pio_t pio);
|
|
|
|
|
|
/** Set pio to desired state.
|
|
@param pio
|
|
@param state value to write pio */
|
|
void pio_output_set (pio_t pio, bool state);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#else
|
|
|
|
/* WARNING: BEWARE ALL YE WHO LOOK BELOW! IT MAY DO YOUR HEAD IN.
|
|
|
|
The following uses advanced C techniques such as macros and inline
|
|
functions to achieve a fast PIO implementation. */
|
|
|
|
|
|
/** Define a PIO as a unique 16 bit number encoding the low part of
|
|
the PORT address offset in the low byte and the bit mask in the
|
|
high byte. PORTB is used for the pattern since PORTA is not
|
|
always defined for some AVRs. */
|
|
#define PIO_DEFINE(PORT, PORTBIT) ((((PORT) - PORT_B) * 3) | (BIT(PORTBIT) << 8))
|
|
|
|
|
|
/** Private macro to lookup bitmask. */
|
|
#define PIO_BITMASK_(PIO) ((PIO) >> 8)
|
|
|
|
|
|
/** Private macro to lookup port register. */
|
|
#define PIO_PORT_(PIO) (&PORTB + ((PIO) & 0xff))
|
|
|
|
|
|
/** Private macro to map a pio to its corresponding data direction
|
|
register (DDR). NB, the DDR and PORT registers must be separated
|
|
by the same number of bytes in all cases. PORTB is used for the
|
|
pattern since PORTA is not always defined for some AVRs. */
|
|
#define PIO_DDR_(PIO) (*(PIO_PORT_ (PIO) + (&DDRB - &PORTB)))
|
|
|
|
|
|
/** Private macro to map a pio to its input register (PIN). NB, the
|
|
PIN and PORT registers must be separated by the same number of
|
|
bytes in all cases. PORTB is used for the pattern since PORTA is
|
|
not always defined for some AVRs. */
|
|
#define PIO_PIN_(PIO) (*(PIO_PORT_ (PIO) + (&PINB - &PORTB)))
|
|
|
|
|
|
/** Private macro to access a pio data register. */
|
|
#define PIO_DATA_(PIO) (*PIO_PORT_ (PIO))
|
|
|
|
|
|
/** Configure pio.
|
|
@param pio PIO to configure
|
|
@param config PIO configuration type
|
|
@return non-zero for success. */
|
|
static inline
|
|
bool pio_config_set (pio_t pio, pio_config_t config)
|
|
{
|
|
switch (config)
|
|
{
|
|
case PIO_OUTPUT_LOW:
|
|
PIO_DDR_ (pio) |= PIO_BITMASK_ (pio);
|
|
PIO_DATA_ (pio) &= ~PIO_BITMASK_ (pio);
|
|
return 1;
|
|
|
|
case PIO_OUTPUT_HIGH:
|
|
PIO_DDR_ (pio) |= PIO_BITMASK_ (pio);
|
|
PIO_DATA_ (pio) |= PIO_BITMASK_ (pio);
|
|
return 1;
|
|
|
|
case PIO_INPUT:
|
|
PIO_DDR_ (pio) &= ~PIO_BITMASK_ (pio);
|
|
PIO_DATA_ (pio) &= ~PIO_BITMASK_ (pio);
|
|
return 1;
|
|
|
|
case PIO_PULLUP:
|
|
PIO_DDR_ (pio) &= ~PIO_BITMASK_ (pio);
|
|
PIO_DATA_ (pio) |= PIO_BITMASK_ (pio);
|
|
return 1;
|
|
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
|
|
/** Return pio configuration
|
|
@param pio
|
|
@return config */
|
|
static inline
|
|
pio_config_t pio_config_get (pio_t pio)
|
|
{
|
|
bool ddr;
|
|
bool port;
|
|
|
|
ddr = PIO_DDR_ (pio) & PIO_BITMASK_ (pio);
|
|
port = PIO_DATA_ (pio) & PIO_BITMASK_ (pio);
|
|
|
|
if (ddr)
|
|
{
|
|
if (port)
|
|
return PIO_OUTPUT_HIGH;
|
|
else
|
|
return PIO_OUTPUT_LOW;
|
|
}
|
|
|
|
if (port)
|
|
return PIO_PULLUP;
|
|
|
|
return PIO_INPUT;
|
|
}
|
|
|
|
|
|
/** Set pio high.
|
|
@param pio */
|
|
static inline
|
|
void pio_output_high (pio_t pio)
|
|
{
|
|
PIO_DATA_ (pio) |= PIO_BITMASK_ (pio);
|
|
}
|
|
|
|
|
|
/** Set pio low.
|
|
@param pio */
|
|
static inline
|
|
void pio_output_low (pio_t pio)
|
|
{
|
|
PIO_DATA_ (pio) &= ~PIO_BITMASK_ (pio);
|
|
}
|
|
|
|
|
|
/** Toggle pio.
|
|
@param pio */
|
|
static inline
|
|
void pio_output_toggle (pio_t pio)
|
|
{
|
|
PIO_DATA_ (pio) ^= PIO_BITMASK_ (pio);
|
|
}
|
|
|
|
|
|
/** Read input state from pio.
|
|
@param pio
|
|
@return input state of pio */
|
|
static inline
|
|
bool pio_input_get (pio_t pio)
|
|
{
|
|
return (PIO_PIN_ (pio) & PIO_BITMASK_ (pio)) != 0;
|
|
}
|
|
|
|
|
|
/** Get output state of pio.
|
|
@param pio
|
|
@return output state of pio */
|
|
static inline
|
|
bool pio_output_get (pio_t pio)
|
|
{
|
|
return (PIO_DATA_ (pio) & PIO_BITMASK_ (pio)) != 0;
|
|
}
|
|
|
|
|
|
/** Set pio to desired state.
|
|
@param pio
|
|
@param state value to write to pio */
|
|
static inline
|
|
void pio_output_set (pio_t pio, bool state)
|
|
{
|
|
state ? pio_output_high (pio) : pio_output_low (pio);
|
|
}
|
|
#endif
|
|
|
|
#endif
|