You are on page 1of 12

Registros GPI/O Avr-gcc en Arduino.

avr-adma01
Registros GPI/O Avr-gcc en Arduino.
Aventuras del Mundo AVR adma-01.

Nota 0.1 manipular puertos en Arduino con los registros DDRx, PORTx y PINx.

"Y Llegara el da en que digitalWrite(); no de abasto y los aventureros de corazn buscaran mas
soluciones" a la hora de manipular todos los pines digitales del Arduino, probablemente cuando quieras
manipular muchos LEDs a diestra y siniestra o quieras armar un cubo LED, veras que digitalWrite();
no dar abasto.
"Y llegara el da en que quieras aprender a programar en C de AVR y la burbuja de Arduino no te dejara
escapar tan fcilmente ", evidentemente algun da querrs conocer que hay detras de DigitalWrite(); o queras
programar en lenguaje C para el ATMega328, querrs saber que significa PORTx, DDRx y PINx .
Oh simplemente quieres aprender a programar en lenguaje C para uControladores avr y te encontraras con
un sin fin de nuevos significados, procesamiento de datos, variables nuevas, en fin, este post surgi del hecho
de que tope con los mismo problemas anteriormente mencionados (por querer cambiar de el entorno
mikroC) y de indagar en Internet como un "Lurker" logre dar con toda la informacin necesaria para paso a
paso comprender y aprender a manipular los puertos de un micro-controlador en este caso de los AVR.
(agradecimientos y referencias al final).
comenzando desde cero:
para los iniciados ya en la rama de los uControladores manipular los puertos de los mismos es el ejercicio
inicial y mas fundamental para su aprendizaje, a continuacin comenzare a detallar poco a poco las
funciones ,codigos, registros y la manipulacin de los mismos.

primero: como reconocemos los puertos de un Micro-controlador:

si vemos la imagen superior de un ATMega168 smil del 328, nos llamara la atencin la cantidad de datos que
nos proporciona cada pin del Micro, si vemos la primera designacin para cada pin desde el interior hacia
afuera nos toparemos con el nombre del pin y su puerto.
si vemos el pin n2 del Atmega328 veremos que la primera designacin es PD0,que significa esto?,
PD0 = Puerto D pin n0. pin numero 0 correspondiente al puerto D del micro-controlador.
Cada micro-controlador tiene puertos de propsito general llamados GPI/O, o puertos de propsito general
de entrada y salida, segn el uControlador en cuestin, este tendr desde un puerto a N cantidad. Un puerto
consta de 8 pines, cada puerto es identificado con una letra, en algunos casos comienzan de la A y en otros la
letra B, como vemos en la imagen superior nuestro ATMega168 tiene tres puertos; el puerto B que va desde el
pin PB0 al PB7, el puerto C que va desde el pin PC0 al PC7 y el el puerto D que va desde el pin PD0 al PD7.
A la hora de programar en compiladores de C o C++ para Micros AVR para llamar a los puertos o utilizarlos
se les llama con la designacin de cada puerto B, C y D y ahora que sabemos como se identifican estos
puertos y los pines de cada uno podemos pasar a ver como manipularlos.

Para no dar un salto tan grande desde Arduino a Avr, podemos usar la misma IDE Arduino para manipular
los puertos a nuestro antojo, ya que la IDE compila en la utilidad avr-gcc, podemos utilizar las mismas
funciones y registros que usamos para manipular puertos a bajo nivel.
bien el segundo paso sera conocer que para manipular los puertos GPI/O nosotros usaremos lo que se conoce
como "registros de control de puertos" estos registros de control nos permiten operar la direccin, escritura y

lecturas logicas en cada pin o puerto del microcontrolador. para manipular un puerto por completo Existe tres
registros, los cuales son: DDRx, PORTx y PINx.

DDRx (Data Direction Register):


Este registro lo podemos comparar en el entorno Arduino con pinMode();, ya que este registro se encarga
de manipular la direccin de cada pin del puerto, esto significa que podemos indicar a cada pin del puerto x,
que trabajen como escritura o lectura, salida o entrada, o como en Arduino pinMode(3,OUTPUT); DDRx se
encarga de este trabajo, ahora la pregunta es como funciona este registro?.
DDRx es un registro que nos permite indicar el modo de operacin de cada pin del puerto, la x es para
indicar el puerto, si quisisemos manipular el puerto B del Atmega328 DDRx seria DDRB,si comparamos con
el entorno Arduino DDRx es similar a PinMode en el cual se debe indicar el numero de pin y la modalidad de
trabajo, en DDRx debes indicar en una instruccin ambas cosas.

Ejemplo:
teniendo en cuenta la imagen del ATMega328, (arriba), podremos observar los datos de los pines
correspondientes a cada puerto,( PB,PC,PD etc) y el nombre del pin en la placa Arduino. podemos observar
que los pines del puerto D, PD0 al PD7 son los pines de la placa Arduino digital 0,1,2,3,4,5,6 y 7, por ende
podemos usar este puerto como ejemplo para el registro DDRx.
sabemos que el puerto de esos pines es el D, por ende el registro sera DDRD, cabe mencionar que el pin PD0
y PD1 son los pines del Arduino RX y TX, estos pines debemos tratar de no ocuparlos demasiado debido a
que por ellos el atmega328 es programado desde la IDE Arduino, es decir estos pines deben ser siempre de
entrada.
entonces: DDRD = 11111100; "00" son los pines PD0 y PD1, RX y TX en Arduino, son denominados 0 para
indicarle al registro que estos pines sern de "lectura de datos", y el resto de los pines sern de escritura o
salida y por ende son designados como 1.
NPin = 76543210;
DDRD = 11111100;
1 = salida o write
0 = entrada o read;

En la funcin de Arduino pinMode(numero,modalidad) realiza el mismo trabajo, pero pin a pin, indicando el
numero de pin y si este es de lectura o escritura.

cdigo ejemplo
void setup(){
DDRD = 11111100;
}
PORTx (Pin Output Register):
Este registro en Arduino tendra su similar con la funcin digitalWrite(npin, estado); , este registro se
encarga de escribir el estado de cada uno de los puertos al cual este controlando, como vimos en DDRx el
cual designaba la modalidad de trabajo para cada Pin, PORTx se encarga por ahora de los pines que fueron
configurados como salida "1", o en Arduino pinMode(3,OUTPUT); para luego decir digitalWrite(3,HIGH);.

Como observamos en el esquema de arriba, al aplicar


PORTx todos los pines que fueron configurados como salidas,
en ellos PORTx escribir un valor lgico alto "1" o un
valor lgico bajo "0", (el valor 1 depende del voltaje logico del
circuito los mas comunes son 5 y 3.3 donde 5 y 3.3 son "1")
Ejemplo:
Siguiendo el esquema anterior donde ocupamos el puerto D,
entonces:
DDRD = 11111100;// pin 0 y 1 como entrada, del pin 2 al 7
como salida;
PORTD = 10101000;// pin 3 , 5 y 7 tienen en su salida un
valor lgico alto o 5v y el resto 0.
si aplicsemos PORTD como en ejemplo, si conectamos LEDs a
los pines digitales 2,3,4,5,6,7; podramos observar que los LEDs
conectados a los pines 3,5,7 estarn encendidos.

cdigo ejemplo
void setup(){
DDRD = 00000000;
PORTD = 10101000;
}
nota(digitalWrite() digitalWrite consume 50 ciclos de reloj aproximadamente),

(imagen ejemplo solo para referencia, no conecten el diagrama ejemplo, le faltan resistencias
de proteccin a cada LED)
PORTx para pines de Entrada.
PORTx tambin se ocupa cuando los pines de los puertos son configurados como entrada o de lectura con
el registro DDRx, y es empleado para activar resistencias de PULLUP, que significa esto?, que los pines de
entrada podemos reconectarlos a un uno lgico "1", y as evitar problemas de ruido o como tambin detectar el
cambio de 1 a 0, esto se aplica conectando a cada pin de entrada una resistencia conectada a Vcc o 5v, en
Arduino y por ende cada pin con PULLUP esta conectado a 1.

ejemplo:
0 = sin pullup.
1 = con pullup.
NPin = 76543210;
DDRD = 11100000; // pines 2,3,4 configurados como entrada.
PORTD= 00011100; //pines 2,3,4 se habilita la resistencia de pullup.

La imagen de referencia nos permite inferir como quedan habilitados los pines cuando estos son configurados
como lectura y adems habilitados con resistencias de PULLUP.

PINx (Pin Input Register):


solo va faltando una cosa por realizar y es la lectura con los pines GPI/O, leer un estado 1 o 0, el registro
PINx nos permite obtener el estado del puerto en general como tambin de un pin en especifico.

para entender podemos seguir los ejemplos anteriores y usar el puerto D.


byte estadoPuerto;//creamos una variable que contendr los datos de cada pin.
//en Arduino byte es la forma de referirse a la variable int8_t de las mismas caractersticas,
variable de 8 bits.
"como cada puerto solo posee 8 pines, usamos variables del mismo tamao, en Arduino byte es una variable
que puede almacenar datos de hasta 8 bits, por ende se vera en forma optima el estado de cada puerto
representado en cada bit"
DDRD = 00000000;//todos los pines del puerto D como entrada.
PORTD = 00000000;//indicamos que los pines no tendrn pullup.

Si los pines 2,3 y 4 los conectamos a 5v y los pines 6,7 y 8 a 0v (GND), el estado de los pines en el puerto
se veran de esta forma
El cdigo seria:
byte estadoPuerto;
void setup(){
DDRD = 0B00000000;
PORTD = 0B00000000;
Serial.begin(9600);
}
void loop(){
estadoPuerto = PIND;
Serial.println(estadoPuerto,BIN);
delay(1000);
}
Puerto serial = 56d = 111000b;
Estos son los tres grandes registros de manipulacin de puertos GPI/O a grandes rasgos, estos registros
pueden verse toscos a la hora de usarlos , pero se pueden complicar mas o simplificar si tenemos un correcto
uso de las operaciones lgicas digitales bsicas. las cuales son AND ("&"), OR ( "|" ) y NOT ("~"), y un extra ,
las operaciones shitfLeft y shiftRight "<< y >>"
resumen:
La operacin And es una multiplicacin lgica binaria, la cual retorna un 1 solamente cuando ambos datos
multiplicados son iguales a 1, "A and B = C" =>" A & B = C".
Ejemplo:
And = &.
0 & 0 = 0;
0 & 1 = 0;
1 & 0 = 0;
1 & 1 = 1;

La operacin Or es una suma lgica binaria , la cual retorna un 1 cuando cualquiera de datos sumados sea 1,
"A or B = C" =>" A | B = C".
Or = "|" ;
0 | 0 = 0;
0 | 1 = 1;
1 | 0 = 1;
1 | 1 = 1;
La operacin NOT nos retorna el inverso lgico de un dato binario.
"si A = 0" ==> "Not(A)= 1" ==>"~A = 1".
Not = "~".
~0 = 1;
~1 = 0;
Las operaciones shiftLeft y shiftRight son un tipo de instruccin para manipular datos, son usadas
para desplazar bits a lo largo de un dato como por ejemplo un byte (dato compuesto de 8 bits), ahora como se
usan:
Supongamos que tenemos una variable del tipo byte llamada "data", data consta 8 bits de informacin los
cuales son: "byte data = 00000000", pero nosotros dadas a circunstancias externas queremos que data en ves
de valer 0 tenga un valor binario de cuatro = "00000100", pero para llevar a cabo esto tendramos que volver a
re-asignar el nuevo valor a la variable, oh aplicar operaciones bsicas para cambiar el valor de "data", pero
contamos con la operacin "shiftLeft" la cual nos permite ingresar y desplazar un bit n espacios a la izquierda,
por ende ingresamos el bit 1 y lo desplazamos hacia la izquierda para que data sea igual a 4 en ves de 0,
ejemplo:

shiftLeft "<<"
Byte data = 00000000; //data = 0;
data = 1<<2;
//ingresamos el bit 1 lo ubicamos en 0 y lo desplazamos a
//la
//izquierda dos espacios
data = 00000100; //data = 4;
Cabe mencionar que al mover el bit hacia la derecha o a la izquierda, el vaci dejado por el bit al
desplazarse ser ocupado por "0" y esto ocurrir tanto si desplazamos un "0" o un "1".

ejemplos:
00000001; 1<<5 = 01100000;
11111111; 0<<3 = 11110000;
01010101; 1<<2 = 10101100;
para shiftRight;
11111110; 0>>5 = 00000011;
00011100; 1>>3 = 00010001;

Ahora que refrescamos nuestra memoria con las operaciones bsicas And, Or, Not y ademas haber conocido
las operaciones "shift" podemos comenzar las "Buenas practicas" de manipulacin de puertos I/O.

ej:
void setup(){
DDRD = 0xC; (0xC hexadecimal = 12 binario = 00001100;// pin PD2 y PD3 =Output
}
Lo que acabamos de hacer es configurar el registro para que los pines 2 y 3 con un numero hexadecimal y que en binario es igual a 12, esto concuerda con los pines 2 y 3 sean de escritura , pero podemos
representar de otra forma esta instruccin? la respuesta es si.

Ejemplos:
A)- DDRD = 0xC;//PD2 y PD3 =Output
B)- DDRD = 00001100;//PD2 y PD3 =Output
C)- DDRD |= (1<<2) | (1<<3);//PD2 y PD3 =Output
D)- DDRD |= 00000100 | 00001000;//PD2 y PD3 =Output
E)- DDRD = DDRD | 00000100 | 00001000;//PD2 y PD3 =Output
F)- DDRD = 00000000 | 00000100 | 00001000;//PD2 y PD3 =Output
Todas las instrucciones anteriores tienen el mismo resultado que los pines 2 y 3 sean de escritura, la
diferencia esta en la facilidad de lectura y lo optimizado de cada instruccin, por ende una instruccin mas
optimizada obtiene un mejor resultado en su ejecucin, como tambin que la instruccin sea mas clara y
precisa, la que cumple con todo esto es la instruccin C debido a que sabemos de forma clara que los pines 2
y 3 sern de salida en el puerto D, pero porque?.

C)- DDRD |= (1<<2) | (1<<3);

de ante mano sabemos que en 1<<2 el bit numero n 2 equivalente a PD2 sera de salida, como tambin el
bit n 3, PD3, 1<<3 sera salida, ademas 1<<2 es equivalente a 00000100 y 1<<3 es equivalente a 00001000,
por lo tanto la instruccin C se descompone en:
DDRD |= 00000100 | 00001000;
tambin sabemos que el operando " |= " es el equivalente de un auto "OR"
DDRD |= x ; es igual a DDRD = DDRD | x ; lo que nos deja:
DDRD = DDRD | 00000100 | 00001000;
que es igual a:
F)- DDRD = 00000000 | 00000100 | 00001000;

en resumen la instruccin C es la mas precisa, clara de entender y de manipular


todo lo anterior tambin es aplicable a los registros PORTx
en cdigo Arduino con instrucciones de Arduino y avr.

y PINx. vamos a poner un ejemplo

ARDUINO
void setup()
{
pinMode(2,OUTPUT);
}
void loop()
{
digitalWrite(2,HIGH);
delay(500);
digitalWrite(2,LOW);
delay(500);
}

en Avr en Arduino:
void setup()
{
DDRD |= (1<<2); // bit n2 equivalente a PD2 = 1, salida.
}
void loop()
{
PORTD |= (1<<2);// 00000100;2, HIGH
delay(500);
PORTD &= ~(1<<2);// 00000000;2, LOW
delay(500);
}
En el cdigo anterior en la seccin "setup" podemos observar al registro DDRD configurando el pin PD2 como
salida, DDRD |= (1<<2);,lo interesante aparece a la hora de manipular el pin D2, ocupamos la
siguiente instruccin para escribir un 1 o HIGH, PORTD |= (1<<2); sabemos que la operacin 1<<2 es igual a
0010 y provocara en el puerto que el pin n 2 pase de 0 a 1, de LOW a HIGH, ahora como podemos invertir el
proceso?. si miramos con atencin la instruccin que enva al pin n 2 a un estado LOW.
Si sabemos que en el estado anterior el puerto D fue configurado como, PORTD = 00000100;, la
siguiente instruccin PORTD &= ~(1<<2); tendr este efecto en el registro.
void loop(){
PORTD |= (1<<2);
portd = 00000000;
(or) + 00000100;// sumamos al registro anterior (1<<2)
= 00000100;//resultado pin 2 esta en estado HIGH
delay(500);

PORTD &= ~(1<<2);//= PORTD &= (0<<2);


//multiplicamos al registro la negacin de (1<<2) => ~(1<<2) = 0<<2;
portd = 00000100;
(and)* 00000000;// and al registro anterior con ~(1<<2). ~(1<<2) = (0<<2);
= 00000000;//obtenemos del And que el pin 2 vuelve a un estado LOW
delay(500);}
Podemos observar claramente como en el cdigo anterior se produce un HIGH en el pin dos por
la instruccin OR que suma en forma binaria un 1 al registro en la posicin n2, y por ende el
estado lgico cambia de 0 a 1, a continuacin la instruccin que viene multiplica un cero al uno anteriormente
configurado en PORTD por ende al realizar el and (&) entre 1 y 0 ( 1 & 0) la resultante sera un cero y el pin
pasara de tener un valor HIGH a uno LOW.
Ahora vamos a revisar que sucede en el caso contrario, cuando quiero leer el estado lgico de un pin del
Puerto D.
En el caso que quisiramos ver el estado lgico del pin 7 del puerto D , (pin digital 7 en Arduino).

Avr en Arduino
boolean estadoPin;

Arduino
boolean estadoPin;

void setup()
{
Serial.begin(9600);
DDRD |= (0<<7);
}
void loop()
{
estadoPin = PIND7;
Serial.println(estadoPin,BIN)
;
delay(500);

void setup()
{
Serial.begin(9600);
pinMode(7,INPUT);
}
void loop()
{
estadoPin = digitalRead(7);
Serial.println(estadoPin,BIN);
delay(500);
}

En el cdigo anterior podemos ver las pequeas diferencias entre las instrucciones en Arduino como
en manipulacin de registros, para obtener el estado actual de un determinado pin, ademas podemos apreciar
las semejanzas entre ambos cdigos a la hora de efectuarlo, se logra apreciar a simple vista que podemos
preguntar con el registro PINx por un determinado pin de x puerto, "PIND7", pero las preguntas surgen a la
hora de como puedo censar ese pin en un if o compararlo con el estado de otro pin.

los siguientes cdigos son de ejemplo para IDE en Arduino:

10

detectar si pin 7 es = 1;
void setup()
{
Serial.begin(9600);
pinMode(7,INPUT);
}
void loop()
{
byte estadoPin
= digitalRead(7);
if(estadoPin == HIGH){
Serial.println( "pin 7 = 1" );
delay(500);
}

detectar si pin 7 es = 1;
void setup()
{
Serial.begin(9600);
DDRD |= (0<<7);
}
void loop()
{
if(PIND & (1<<7)){
Serial.println("pin 7 = 1");
delay(500);
}
}

}
En ambos cdigos la funcin principal es detectar el estado actual del pin PD7, en Arduino lo hacemos
consultando a la instruccin "digitalRead(n pin)" la cual nos retorna el estado del pin.
en el proceso mediante manipulacin de registros la instruccin if(PIND & (1<<7)) compara el estado del puerto
D "PIND" y lo multiplica por el dato (1<<7), el cual es equivalente a 01000000, si la operacin and "&" arroja
un 1 el estado actual del pin PD7 es 1.
IF(PIND * 1<<7); = IF(01000000 & 01000000) = 01000000; SE CUMPLE
IF(PIND * 1<<7); = IF(00000000 & 01000000) = 00000000; NO SE CUMPLE

detectar si los pines 6 y 7 estn en 1


void setup()
{
Serial.begin(9600);
pinMode(7,INPUT);
pinMode(6,INPUT);
}
void loop()
{
byte Pin6
= digitalRead(6);
byte Pin7 = digitalRead(7);
if(pin6&pin7 == HIGH){
Serial.println("pin 6
1");
delay(500);
}
}

detectar si los pines 6 y 7 estn en 1

void setup()
{
Serial.begin(9600);
DDRD |= (0<<7) | (0<<6);
}
void loop()
{
if((PIND&((1<<7)|(1<<6)))){
Serial.println("pin 6 y 7
1" );
delay(500);
=
}
}

En el siguiente caso se consulta cuando dos pines son de entrada y se enva una frase por puerto Serial solo
cuando estos dos pines estn leyendo un valor logico 1, o 5v, en Arduino usamos nuevamente digitalRead()

11

pero guardando el estado en dos variables, y estas son multiplicadas con un & y comparadas con el valor
HIGH. mediante registros podemos obtener el mismo resultado sumando (1<<7)|(1<<6) y luego
compararlo mediante un & con el estado actual del puerto PIND&.
if((PIND&((1<<7)|(1<<6))))
if((PIND&((1<<7)|(1<<6))))
if((PIND&((1<<7)|(1<<6))))
if((PIND&((1<<7)|(1<<6))))

=
=
=
=

01100000
01000000
01000000
00000000

&
&
&
&

(01000000
(00000000
(01000000
(00000000

|
|
|
|

00100000); se cumple.
00100000); no se cumple.
00000000); no se cumple.
00000000); no se cumple.

encender y apagar pines 2,3,4,5


void setup()
{
pinMode(2,OUTPUT);
pinMode(3,OUTPUT);
pinMode(4,OUTPUT);
pinMode(5,OUTPUT);
}
void loop()
{
digitalWrite(2,HIGH);
digitalWrite(3,HIGH);
digitalWrite(4,HIGH);
digitalWrite(5,HIGH);
delay(500);
digitalWrite(2,LOW);
digitalWrite(3,LOW);
digitalWrite(4,LOW);
digitalWrite(5,LOW);
delay(500);
}

encender y apagar pines 2,3,4,5


void setup()
{
DDRD
|= (1<<2)|(1<<3)|(1<<4)|
(1<<5);
}
void loop()
{
PORTD
|= (1<<2)|(1<<3)|(1<<4)|
(1<<5) ;
delay(500);
PORTD
(0<<2)&(0<<3)&(0<<4)&(0<<5);
delay(500);
}

&=

Y para el final un simple cdigo de encendido y apagado de pines en gran cantidad, usando digitalWrite(), y la
manipulacin de puertos.

Bueno esperando que esta pequea incursin ma en los registros GPI/O sea til para quien se encuentre con
las mismas dudas, o quiera indagar al respecto vienen mas post respecto a diversos temas comunes de avr
gcc, como lecturas anlogas, puertos seriales y Pwm , se despide atte Ravc_cs.
referencias y agradecimientos:
El Blog de suhasm //ejemplos muy fciles de seguir respecto a los registros GPI/O
la traduccion de takashi de mikrocontroller.net/.// datos muy tiles y una gran biblioteca de datos respecto
aavr-gcc

12

You might also like