El PCF8591 es un conversor analógico a digital y digital a analógico de 8 bits de resolución que se comunica con el microcontrolador por medio del bus I2C.
Este componente puede ser útil básicamente en tres líneas de aplicación:
- Añadir una salida analógica, funcionalidad que no está presente en un microcontrolador convencional. El PCF8591 ofrece una salida analógica de 8 bits.
- Aumentar el número de entradas analógicas disponibles. El PCF8591 dispone de cuatro entradas analógicas de 8 bits.
- Incrementar la distancia entre los sensores analógicos y el microcontrolador sin perder o distorsionar la señal. Como el PCF8591 convierte en digital la señal medida y la transmite por I2C, el riesgo de interferencias o pérdida de señal disminuye lo máximo posible a la vez que es posible usar tanta distancia (longitud de cable) como permita el bus I2C
Aunque su resolución de 8 bits es modesta, puede servir para muchas aplicaciones y su precio y su sencillez de uso tanto a nivel de programación como a nivel electrónico lo han hecho muy popular. Como además está disponible en formato DIP es perfecto para pruebas y prototipos.
Para hacer más cómodas las pruebas existen en el mercado módulos que integran el PCF8591 y algunos sensores sencillos (luz y temperatura, normalmente) además de resistencias variables con las que establecer valores analógicos arbitrarios.
La parte fija de la dirección del PCF8591 en el bus I2C es 0B1001000 (siete bits). Estableciendo a nivel alto o bajo las patillas A0, A1 y A2 se pueden configurar los tres últimos bits para conectar hasta ocho de estos dispositivos en el mismo bus.
El PCF8591 dispone de un oscilador interno con el que gestionar el ciclo de conversión analógica a digital. En lugar de utilizar este oscilador puede trabajar con uno externo que se conecta a la entrada OSC; para activarlo, la entrada EXT debe conectarse a nivel bajo. Para utilizar el oscilador interno, la configuración más habitual, basta con dejar al aire la patilla OSC y conectar a nivel alto EXT.
Para establecer la referencia de medida de tensión del PCF8591 utiliza las entradas VREF y AGND. En la configuración más básica se puede conectar VREF a VDD y AGND a VSS, es decir, referirlo a la propia tensión de alimentación del circuito.
En el diagrama de ejemplo de la siguiente imagen se utiliza la conexión más habitual (y más sencilla). La dirección del dispositivo en el bus I2C es 0B1001000 por establecer a cero A0, A1 y A2. La tensión de referencia es la de alimentación del sistema y se utiliza el oscilador interno al conectar EXT a VDD.
Utilizar el PCF8591 desde una placa Arduino es muy sencillo con la librería Wire. En primer lugar hay que inicializar (solamente una vez en el programa) las comunicaciones I2C con Wire.begin().
Para activar la salida analógica del PCF8591 a cierto nivel, primero se inicia la transmisión al bus I2C con Wire.beginTransmission(), luego se activa la escritura analógica en AOUT con Wire.write() y el código de la operación correspondiente (la escritura es 0B01000000), también con Wire.write() se envía el valor del nivel y se libera el bus I2C con Wire.endTransmission(). En el ejemplo de abajo puede verse el proceso completo.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
#define DIRECCION_PCF8591 0B1001000 // 1 0 0 1 A2 A1 A0 #define SALIDA_ANALOGICA 0B01000000 // ES APROG 0 AI CHNN #define ESPERA_LECTURAS 1500 #include <Wire.h> long cronometro_lecturas=0; long tiempo_transcurrido; byte nivel=0; float conversion_nivel_voltaje=5000.0/255.0; // Nivel de referencia (5.0 V máximo) × 1000 (mV) / (256-1) pasos (8 bits, 1 byte 2^8=256) void setup() { Serial.begin(9600); Wire.begin(); } void loop() { tiempo_transcurrido=millis()-cronometro_lecturas; if(tiempo_transcurrido>ESPERA_LECTURAS) { cronometro_lecturas=millis(); Serial.print("Nivel: "); Serial.println(nivel); Serial.print("Voltaje: "); Serial.print((int)(nivel*conversion_nivel_voltaje)); Serial.println(" mV\n"); Wire.beginTransmission(DIRECCION_PCF8591); Wire.write(SALIDA_ANALOGICA); Wire.write(nivel++); Wire.endTransmission(); } } |
Los 1500 ms (un segundo y medio) de intervalo de medida del ejemplo anterior permiten usar un multímetro convencional para medir la precisión de la salida AOUT del PCF8591. Como experimento, también podemos usar las entradas analógicas de Arduino para medir la tensión generada. El siguiente ejemplo genera un nivel de salida en el PCF8591, calcula el voltaje teórico y mide con Arduino la tensión que llega a su entrada analógica A0.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
#define DIRECCION_PCF8591 0B1001000 // 1 0 0 1 A2 A1 A0 #define SALIDA_ANALOGICA 0B01000000 // ES APROG 0 AI CHNN #define ENTRADA_ANALOGICA A0 #define ESPERA_LECTURAS 1000 #include <Wire.h> long cronometro_lecturas=0; long tiempo_transcurrido; byte nivel=0; float conversion_nivel_voltaje=5000.0/255.0; // Nivel de referencia (5.0 V máximo) × 1000 (mV) / (256-1) pasos (8 bits, 1 byte 2^8=256) float conversion_nivel_arduino=5000.0/1023.0; // Nivel de referencia (5.0 V máximo) × 1000 (mV) / (1024-1) pasos (10 bits 2^10=1024) void setup() { Serial.begin(9600); Wire.begin(); } void loop() { tiempo_transcurrido=millis()-cronometro_lecturas; if(tiempo_transcurrido>ESPERA_LECTURAS) { cronometro_lecturas=millis(); Wire.beginTransmission(DIRECCION_PCF8591); Wire.write(SALIDA_ANALOGICA); Wire.write(nivel); Wire.endTransmission(); Serial.print("Nivel enviado: "); Serial.println(nivel); Serial.print("Voltaje calculado: "); Serial.print((int)(nivel*conversion_nivel_voltaje)); Serial.println(" mV"); Serial.print("Voltaje medido: "); Serial.print((int)(analogRead(ENTRADA_ANALOGICA)*conversion_nivel_arduino)); Serial.println(" mV\n"); nivel++; } } |
La lectura de las cuatro entradas analógicas del PCF8591 con la librería Wire también es muy sencilla. En primer lugar se activan las comunicaciones I2C, como siempre, solamente una vez en la aplicación, con Wire.begin() y para cada lectura se realizan los siguientes pasos:
-
Iniciar la conexión con el PCF8591 usando
Wire.beginTransmission()y la dirección del dispositivo. -
Activar una de las entradas enviando su dirección al PCF8591 con
Wire.write(). El código corresponde con el número de entrada, 0 para AIN0, 1 para AIN1, 2 para AIN2 y 3 para AIN3. -
Liberar el bus I2C usando
Wire.endTransmission() -
Solicitar con
Wire.requestFrom()dos bytes a la dirección del PCF8591, que devolverá la lectura analógica anterior y la actual. -
Leer la respuesta del PCF8591 con
Wire.read().
El proceso completo de lectura analógica del PCF8591 puede verse en el programa de ejemplo de abajo.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
#define DIRECCION_PCF8591 0B1001000 // 1 0 0 1 A2 A1 A0 #define DIRECCION_A0 0B00 #define DIRECCION_A1 0B01 #define DIRECCION_A2 0B10 #define DIRECCION_A3 0B11 #define ESPERA_LECTURAS 1000 #include <Wire.h> long cronometro_lecturas=0; long tiempo_transcurrido; void setup() { Serial.begin(9600); Wire.begin(); } void loop() { tiempo_transcurrido=millis()-cronometro_lecturas; if(tiempo_transcurrido>ESPERA_LECTURAS) { cronometro_lecturas=millis(); Wire.beginTransmission(DIRECCION_PCF8591); Wire.write(DIRECCION_A1); Wire.endTransmission(); Wire.requestFrom(DIRECCION_PCF8591,2); // Pedir dos bytes a la dirección del PCF8591 Wire.read(); // Ignorar el valor anterior del registro DAC Serial.println(Wire.read()); } } |
En las condiciones habituales el código anterior funcionará correctamente pero, como ya he explicado otras veces hablando de la librería Wire, lo más ortodoxo sería esperar a leer con Wire.read() después de asegurarse de que los datos están disponibles usando Wire.available().
En el siguiente programa de ejemplo de lectura analógica del PCF8591 con Arduino se ha implementado la espera antes de la lectura de los datos. Para evitar el bloqueo del programa, en el caso de que no llegaran los dos bytes que se esperan, se ha incluido un tiempo de espera, superado el cual se desiste de la lectura.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
#define DIRECCION_PCF8591 0B1001000 // 1 0 0 1 A2 A1 A0 #define DIRECCION_A0 0B00 #define DIRECCION_A1 0B01 #define DIRECCION_A2 0B10 #define DIRECCION_A3 0B11 #define ESPERA_LECTURAS 1000 #define TIMEOUT_I2C 10 // 10 milisegundos de espera antes de renunciar a leer el bus I2C #include <Wire.h> long cronometro_lecturas=0; long tiempo_transcurrido; long cronometro_timeout_i2c; void setup() { Serial.begin(9600); Wire.begin(); } void loop() { tiempo_transcurrido=millis()-cronometro_lecturas; if(tiempo_transcurrido>ESPERA_LECTURAS) { cronometro_lecturas=millis(); Wire.beginTransmission(DIRECCION_PCF8591); Wire.write(DIRECCION_A1); Wire.endTransmission(); Wire.requestFrom(DIRECCION_PCF8591,2); // Pedir dos bytes a la dirección del PCF8591 cronometro_timeout_i2c=millis(); do { tiempo_transcurrido=millis()-cronometro_timeout_i2c; } while(!Wire.available()&&tiempo_transcurrido<TIMEOUT_I2C); if(Wire.available()>1) { Wire.read(); // Ignorar el valor anterior del registro DAC Serial.println(Wire.read()); } } } |


binacs
Ando buscando cosas por internet para un proyecto y da gusto como te expresas, es muy agradable encontrar gente como tu que no solo saben mucho sino que ademas son maestros.
Gracias
Víctor Ventura
Lo que da gusto son tus halagos. No hay para tanto pero ¡¡Muchas gracias!! y también por visitar el blog, claro 🙂
rafa
hola que tal, yo estoy trabajando con pic y ccs,
he visto este sensor y me gustaria acoplarlo a un 16f877A,
el problema que tengo es que cuando realiza una medida la hace bien, pero cuando vuelvo a medir me pone en pantalla el doble justo de la anterior.
te paso lo que he realizado a ver donde tengo el error.
gracias
void luces()
{
i2c_start(); // Inicio la comunicación I2C
i2c_write(0x90); // Envío Dirección I2C del PCF8591
delay_ms(0.1);
i2c_write(0x04); // Envío Configuración del PCF8591 para ADC por AIN0
delay_ms(0.1);
i2c_start(); // Inicio la comunicación I2C
i2c_write(0x91); // Envío Dirección I2C del PCF8591
lectura=i2c_read(); //leo la conversion de A0
i2c_stop();
lectura2=(lectura*5.0)/255.0;
for ( i=0;i<10;i++){
//trato la conversion
lcd_gotoxy(1,1);
printf(lcd_putc,"Tension:%f V ",lectura2); //y muestro el dato
}
}