Adding a PCF 8591 ADC/DAC to ESP8266-01

ad-da-converter-module
YL-40 module

The ESP8266-01 is a great WiFi enabled  microcontroller but it only has 4 I/O pins broken out. Fortunately it does support I2C protocol so in spite of  the low number of pins, there still is a lot of hardware that can be added.
As the ESP8266-01 has no analog inputs at all, adding an ADC to it is something I wanted to do. After all,  sensors as LDR or NTC are still analog.
The PCF8591 is such an ADC: it is  a single-chip, single‑supply low‑power 8‑bit CMOS data acquisition device with four analog inputs, one analog output and a serial I²C‑bus interface. Three address pins A0, A1 and A2 are used for programming the hardware address, allowing the use of up to eight devices connected to the I²C‑bus without additional hardware. I doubt whether I need more than 8 of those chips. Lets just start with one. Although one can get the individual chip, I have chosen for a module that actually already has some sensors on it:

  • AIN0 – Jumper P5 – Light Dependent Resistor (LDR)
  • AIN1 – Jumper P4 – Thermistor
  • AIN2 – Not connected
  • AIN3 – Jumper P6 – Potentiometer
pcf8591-circ
YL-40 circuit

The I2C address of the PCF8591 is determined by the pins A0-A2. As they are close to the Ground pin, let’s start with grounding them (as they are on the module).
pcf8591adresThe address is 1001A2A1A0. With A2-A0 being LOW, that is 1001000=0x48.

base A2A1A0 Hex Dec
1001 000 48 72
1001 001 49 73
1001 010 4A 74
1001 011 4B 75
1001 100 4C 76
1001 101 4D 77
1001 110 4E 78
1001 111 4F 79

In some programs you will see the address as “0x90>>1” Which is 48 as well. The “0x90” counts the LSB of the 8 bit address, which is the R/W bit. With the Write Bit Low (=active) the full address is 10010000=0x90, but the rightshift 1 removes the LSB again, making it 0x48.
The module is hardwired to 0x48 as the three address lines are soldered to ground. So if you would want to use more than one module on the same I2C port you would need to do some de-soldering (or use   bare PCF8591 chips ofcourse).

Mini PCF8591 AD DA Shell Module

There is a (more expensive)  fully configurable module, that allows to set the  I2C address with jumpers. That module is daisy chainable with other I2C modules in the same range (there is for example a PCF8547 digital I/O module with similar connections). Making a module yrself is not hard either:

PCF8591
PCF8591

The control byte sets the operating mode of the PCF8591 and is described in section 7.2 of the datasheet, The upper nibble of the control register is used for enabling the analog output, and for programming the analog inputs as single-ended or differential inputs.
The lower nibble selects one of the analog input channels defined by the upper nibble. If the auto-increment flag is set the channel number is incremented automatically after each A/D conversion.
If the auto-increment mode is desired in applications where the internal oscillator is used, the analog output enable flag in the control byte (bit 6) should be set. This allows the internal oscillator to run continuously, thereby preventing conversion errors resulting from oscillator start-up delay. The analog output enable flag may be reset at other times to reduce quiescent power consumption.

As it is not my intention to explain the full innerworkings of the PCF8591, but just to show it is working with the ESP8266-01, I will skip a full technical discussion. For now it is enough to know that the PCF8591 can be read byte for byte, but it can also be read in ‘burst mode’, in which we read the 4 analog values all at once. The program I present is burstmode with autoincrement of the address. The reason we read 5 bytes instead of 4 is because the first byte contains old data. As the datasheet states in paragraph 8.4: “The first byte transmitted in a read cycle contains the conversion result code of the previous read cycle.”

#include "Wire.h"
int PCF8591=0x48; // I2C bus address
byte ana0, ana1, ana2, ana3;
void setup()
{
 Wire.pins(0,2);// just to make sure
 Wire.begin(0,2);// the SDA and SCL
}
void loop()
{
 Wire.beginTransmission(PCF8591); // wake up PCF8591
 Wire.write(0x04); // control byte: reads ADC0 then auto-increment
 Wire.endTransmission(); // end tranmission
 Wire.requestFrom(PCF8591, 5);
 ana0=Wire.read();// throw this one away
 ana0=Wire.read();
 ana1=Wire.read();
 ana2=Wire.read();
 ana3=Wire.read();
}

Obviously when you have these values read you will need to do something with them: print them out, put them on yr own webpage or upload them to e.g. Thingspeak. As printing from the ESP8266-01  is not always easy, I will show you how to upload the values to Thingspeak:

#include  // ESP8266WiFi.h library
#include "Wire.h"
int PCF8591=0x48; // I2C bus address
byte ana0, ana1, ana2, ana3;

const char* ssid     = "YourNetworkSSID";
const char* password = "YourPassword";
const char* host = "api.thingspeak.com";
const char* writeAPIKey = "YourWriteAPI";

void setup() {
  // Initialize sensor
 Wire.pins(0,2);// just to make sure
 Wire.begin(0,2);// the SDA and SCL

//  Connect to WiFi network
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
  }
}

void loop() {
 Wire.beginTransmission(PCF8591); // wake up PCF8591
 Wire.write(0x04); // control byte: reads ADC0 then auto-increment
 Wire.endTransmission(); // end tranmission
 Wire.requestFrom(PCF8591, 5);
 ana0=Wire.read();
 ana0=Wire.read();
 ana1=Wire.read();
 ana2=Wire.read();
 ana3=Wire.read();

// make TCP connections
  WiFiClient client;
  const int httpPort = 80;
  if (!client.connect(host, httpPort)) {
    return;
  }

  String url = "/update?key=";
  url+=writeAPIKey;
  url+="&field1=";
  url+=String(ana0);
  url+="&field2=";
  url+=String(ana1);
  url+="&field3=";
  url+=String(ana2);
  url+="&field3=";
  url+=String(ana3);
  url+="\r\n";
  
  // Request to the server
  client.print(String("GET ") + url + " HTTP/1.1\r\n" +
               "Host: " + host + "\r\n" + 
               "Connection: close\r\n\r\n");
    delay(1000);
}

DAC
The PCF8591 does not only have  4 ADC channels but also 1 DAC channel. Writing to the DAC is as follows:

#define PCF8591 (0x48) // I2C bus address
void setup()
{
 Wire.pins(0,2);// just to make sure
 Wire.begin(0,2);
}
void loop()
{
 for (int i=0; i=0; --i)
 {
 Wire.beginTransmission(PCF8591); // wake up PCF8591
 Wire.write(0x40); // turn on DAC b1000000
 Wire.write(i); 
 Wire.endTransmission();
 }
}

Calculations
A the board normally uses the 3.3v supply as the reference voltage:
The input voltage is determined with:
vIn = value * (3.3 / 255)
and the output voltage is:
vOut = (value / 255) * 3.3
or to find the value for a given voltage:
value = (vOut / 3.3) * 255

So if for instance I would write  the value of 50 to the DAC. The voltage would be: 0.64V
In order to test that, I hooked up Aout to the A0 of an ESP 8266-12 and found a value of 192.
To calculate that to a voltage that is  (192/1023)*3.3=0.162V. I guess that is close enough.
Oddly though when I hooked up the Aout to AIN2 or AIN1 that didn seem to give reliable readings. A value of 50 written to Aout should give a value of 50 on AIN, but oddly it didnt

In my version of the board the NTC channel only varied between 255 and 254, changing the temperatuur didnt seem to have any influence, but removing jumper P4 made the value go all over the place so i presume the channel is ok, and only the NTC might not be OK.

Differential Input
The PCF8591 is capable of more, it is  for instance possible to do differential measurements.
Suppose you want to  measure the difference between AIN0 and AIN1.
You do that as follows:

#include "Wire.h"
int PCF8591=0x48; // I2C bus address 
int Raw = 0;
float Voltage = 0.0;

void setup()
{
 Wire.pins(0,2);// just to make sure
 Wire.begin(0,2);
  Serial.begin(9600); //Not on an ESP8266-01 
  Wire.beginTransmission(PCF8591); // Wake up PCF8591
  Wire.write(0x11); // control Byte for differential input mode 
  Wire.endTransmission(); //
}
void loop()
{
  Wire.requestFrom(PCF8591, 1); // Get Data from channel 1

 RawValue=Wire.read();
 Voltage = (Raw * 3.3 )/ 255.0;
 Serial.print("Raw ADC = ");
 Serial.print(Raw);
 Serial.print(" Voltage = ");
 Serial.println(Voltage,3);
 delay(1000);
}

If you want to hook up this chip to a Raspbery Pi, have a look here.
There are other ways of expanding the ADC capabilities of the ESP8266:

 

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s