Programación avanzada de puertos

La mayoría de los puertos de los uC son multipropósito, es decir, en función de su configuración se comportan de una forma u otra. El ATmega 328p como cualquier otro uC tiene registros para cada puerto donde define si sera usado como entrada o salida. ATmega 328p tiene 3 bancos o grupos de puertos: PB (D8~D13), PC (A0~A5) y PD (D0~D7), es decir a PB y PC le faltan puertos debido a que no se dispone de pines suficientes al ser ATmega 328p un DIP-28 (en comparación del tradicional DIP-40 de la mayoria de uC).

Bancos y puertos de ATmega 328p
Banco2726252423222120
PB  D13D12D11D10D9D8
PC  A5A4A3A2A1A0
PDD7D6D5D4D3D2D1D0

ATmega 328p tiene 3 registro de 8 bits con los que administra estos 3 bancos:

1. Registros

Antes de meterme de lleno a explicarte los registros, quiero que primero veamos para que podría servir este esfuerzo.

Ventajas de usar registros:

Desventajas de usar registros:

En las librerías es muy recomendable usar la manipulación directa de registros, así se hacen mucho mas rápidas.

2. Digitales

Bien ahora que ya sabes para que nos meteremos en como manejar estos registros. En principio tenemos dos métodos: masivo es decir todos los puertos de un banco a la vez o puntual es decir un puerto de un banco en particular.

1.1 Masivo

Ya sabemos que ATmega 326P maneja 3 bancos (B, C y D) y que cada uno agrupa a un numero de puertos. Por otro lado tenemos 3 registros (DDRx, PORTx y PINx) que podemos manejar.

DDRD = 0b11111111;  //D0~D7 como salidas.
DDRD = 0b00000000;  //D0~D7 como entradas.
DDRB = 0b00000111;  //D8~D10 como salida D11~D13 como entradas.
PORTD = 0b11111111; //D0~D7 en HIGH.
PORTD = 0b00000000; //D0~D7 en LOW.
PORTB = 0b00000101; //D8+D10 en HIGH, D8, D11~D13 en LOW.
byte variable = PIND; //Guarda en una variable los estados de D0~D7 como un byte.

Se debe tener cuidado cuando se usa el PORTD y el puerto serie D0 (Rx) y D1 (Tx) son los usados por la UART y si se pone estos puertos como entradas o salidas, la UART será incapaz de leer o escribir datos en estos puertos. Este es un ejemplo de cuidado que se debe tener al usar esta programación en lugar de la capa de programación que nos ofrece Arduino.

Mediante los registros también podemos controlar las resistencias internas de pull-up que se usan basicamente para no dejar los pines al aire ya que esto genera ruido eléctrico. Se puede resolver este problema de dos maneras: poner una resistencia externa de 10K a Vcc (+5V) o usar los Pull-up internos del uC que producen el mismo efecto y hacen mas simple el circuitos.

Para habilitar las resistencias pull-up tenemos primero que configurar como entrada (0) el puerto mediante registro DDRx y luego escribir un 1 en el registro PORTx.

DDRD = 0b00000000;    //Configura todos los puertos del banco PORTD (D0~D7) como entradas.
PORTD = 0b00001111;   //Habilitar las Pull-ups de los puertos D0~D3.

Es casi tan fácil como usar las funciones digitalRead() y digitalWrite() de Arduino, pero con acceso directo al puerto se puede ahorrar espacio en la memoria flash porque cada operacion de leer el estado de un solo puerto ocupa 40 bytes de instrucciones y también puede ganar mucha velocidad, porque las funciones Arduino puede tomar más de 40 ciclos de reloj para leer o escribir un solo bit en un puerto.

1.2 Puntual

No es normal que se necesite definir, leer o escribir en un banco completo siempre, por ejemplo, si queremos encender un LED tendremos que actual sobre un solo puerto y no sobre todo el banco. Para eso podemos usar la macro Px donde x estará entre 0~7 para definir el puerto en particular. Ver tabla arriba.

DDRD = (1<<PD2);  //Configura el D2 como salida.
PORTD = (1<<PD4); //Pone D4 en HIGH.
byte variable = (PIND & (1<<PD1)); //Lee D1 y almacena su valor en la variable.

También podemos usar la macro Px varias veces en una misma instrucción, por ejemplo, en este código, se ejecutará algo de código sólo si los puertos D10 y D11 están en HIGH al mismo tiempo

DDRB = 0b00001100; // Los pines D10 y D11 son entradas, y el resto (D8+D9+D12+D13) salidas.
if (PINB & ((1<<PD2) | (1<<PD3))) {
   //Algún código que se ejecutará solo si los dos botones (D10+D11) se encuentran en HIGH.
}

La manipulación directa de los puertos puede ser acompañada de las máscaras de bits, los operadores lógicos (NOT, AND, OR, y XOR) y operaciones de desplazamiento a la derecha e izquierda.

El siguiente ejemplo lee alternativamente 100 veces el pin D9 con digitalRead() y mediante el registro PINB y calcula el tiempo en microsegundos de ambas operaciones. Te sorprenden los resultados ?

unsigned long t,t1;
bool x=true;
int y;
void setup(){
	Serial.begin(9600);
	pinMode(9, INPUT_PULLUP);
}
void loop(){
	t = micros();
	if (x){
		for (byte n=0; n<100; n++){
			y = digitalRead(9);
		}
	}else{
		for (byte n=0; n<100; n++){
			y = PINB & (1 << PB1);
		}	
	}
	t1 = micros() - t;
	if (x){
		Serial.print("DigitalRead() = ");
	}else{
		Serial.print("PINx = ");
	}
	x = !x;
	Serial.println(t1);
}

3. Analogicos

La forma de manejar con registros las entradas analógicas correspondientes al puerto C con POR y PIN es para usar esos pines como I/O digitales, puesto que los pines de los uC son multipropósito como se ha dicho anteriormente.

En las entradas analógicas entran en juego los conversores Analógico Digital (ADC) y en las salidas analógicas entra el PWM que usa uno de los temporizadores del uC para hacer la forma de onda PWM.

3.1 ADC

Un uC solo entiende señales digitales (0+1), por lo que para leer señales analógicas necesitamos un conversos análogo a digital (ADC). El ATmega 329P tiene incluido un conversor analógico a digital (ADC) de 6 canales (por multiplexion) y 10 bits (1024 pasos), por lo que devuelva enteros entre 0~1023.

Esto significa que a pesar de que ATmega 328P tiene disponibles 6 pines analogicos (A0~A5) solo puede leer un pin analogico a la vez (por la multilexion), de modo de si requiere lecturas de alta velocidad se podría necesitar el uso de ADC externos. El datasheet de ATmega advierte de hacer lecturas rápidas entre pines analógicos (mas de 200 KHz) puede causar ruido eléctrico, por lo que se recomienda incluir retardos de 1 milisegundo entre lecturas.

También es posible alterar la tensión máxima (siempre por debajo de Vcc) que usa el ADC. Es Aref, que es la tensión contra la que todas las entradas analógicas hacen las conversiones. Reducir Aref tiene sentido para mejorar la resolución del ADC. Con Aref = 5V la resolución es de 4,88 mV (5/1023) por paso. Si tienes un sensor de 3V3 mejor pon Vref = 3V3 y obtendrás una resolución de 3.22 mV (3.3/1023) por paso.

El ADC interno también se puede usar en un modo de 8 bits (), donde sólo se usan los 8 bits más significativos de la resolución de 10 bits completa, esto podría ser útil cuando se trabaja en ambientes ruidosos y se ahorra tiempo de uC. El ADC también puede configurarse para que lleve a cabo una conversión y detenerse o puede ser configurado para funcionar en un modo de funcionamiento libre, la primera opción es la mejor opción cuando queremos leer diferentes pines, y el segundo es mejor cuando sólo tenemos que leer un pin y esto puede ahorrar algo de tiempo entre las conversiones.

Fuente: Luis Llamas

3.2 PWM

Las Salidas Pulse Width Modulation permiten simular salidas analógicas con ciertos pines digitales (D3, D5, D6, D9, D10, D11). PWM es una técnica para modificar el ancho de pulsos (duty cycle) de una señal. Lo que modificamos no es la frecuencia que siempre sera 490 Hz, sino que cambiaremos la proporción de la parte positiva y negativa de cada periodo. Sus usos son:

En la familia de uC AVR, el PWM se controla por hardware y ATmega 328p cuenta con 3 timers. Timer 0 (D5 + D6) y timer 2 (D3 + D11) dan una resolución de 8 bit (256 pasos) mientras que el timer 1 (D9 + D10) ofrece una resolución de 16 bits (65536 pasos). Todo lo que necesita hacer es inicializar e iniciar el temporizador y establecer el ciclo de trabajo. Estos temporizadores generan interrupciones cuando alcanzan el overflow o cuando alcanzan el registro de comparación. Los registros de control del timer/counter n (donde n 0~2) son TCCRnA y TCCRnB y tienen los principales controles de los temporizadores.

Definición de PWM en Arduino oficial