Diferencia entre revisiones de «Programacion avanzada»
(Página creada con «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...») |
(→Puntual) |
||
Línea 80: | Línea 80: | ||
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 ? | 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 ? | ||
<pre> | <pre> | ||
+ | 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); | ||
+ | } | ||
</pre> | </pre> | ||
Revisión del 17:21 26 jun 2018
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).
Banco | 27 | 26 | 25 | 24 | 23 | 22 | 21 | 20 |
---|---|---|---|---|---|---|---|---|
PB | - | - | D13 | D12 | D11 | D10 | D9 | D8 |
PC | - | - | A5 | A4 | A3 | A2 | A1 | A0 |
PD | D7 | D6 | D5 | D4 | D3 | D2 | D1 | D0 |
ATmega 328p tiene 3 registro de 8 bits con los que administra estos 3 bancos:
- DDRx, determina si es entrada (0) o salida (1)
- PORTx, controla si el pin esta en nivel HIGH o LOW. También define Pull-up si es entrada.
- PINx, permite leer estado (solo lectura).
Contenido
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:
- Cada instrucción máquina necesita un ciclo de reloj a 16 MHz. Las funciones digitalRead() y digitalWrite() se componen cada una de ellas de varias instrucciones máquina, lo que puede influir negativamente en aplicaciones muy dependientes del tiempo. El Registro PORTx puede hacer el mismo trabajo en muchos menos ciclos de reloj.
- Si necesitas cambiar de estado muchos pines en lugar de hacer un loop for que tomara mucho tiempo puedes echar mano directamente al registro y establecer varios pines de una sola vez.
- Si estas al limite de memoria Flash, puedes usar este truco para hacer que tu código use menos bytes de programación.
Desventajas de usar registros:
- El código no es fácil de entender para novatos.
- Es mucho mas fácil que cometas errores de difícil depuración. Por ejemplo con DDRD = B11111111, pone todos los pines (D0~D7) como salida, incluso el pin D0 (Rx) lo que causar que el puerto serial deje de funcionar.
Nota: En las librerías es muy recomendable usar la manipulación directa de registros, así se hacen mucho mas rápidas.
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.
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 operación 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.
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); }