Skip to content

Commit

Permalink
0.0.2 initial release
Browse files Browse the repository at this point in the history
  • Loading branch information
RobTillaart committed Jul 23, 2020
1 parent 2f2b118 commit 5f2b1f6
Show file tree
Hide file tree
Showing 9 changed files with 645 additions and 0 deletions.
143 changes: 143 additions & 0 deletions PCF8591.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
//
// FILE: PCF8591.cpp
// AUTHOR: Rob Tillaart
// DATE: 2020-03-12
// VERSION: 0.0.2
// PURPOSE: I2C PCF8591 library for Arduino
// URL: https://github.com/RobTillaart/PCF8591
//
// HISTORY:
// 0.0.1 2020-03-12 initial version
// 0.0.2 2020-07-22 testing, refactor, documentation and examples

#include "PCF8591.h"

PCF8591::PCF8591(const uint8_t address)
{
_address = address;
_control = 0;
_dac = 0;
for (uint8_t i = 0; i < 4; i++) _adc[i] = 0;
_error = PCF8591_OK;
}


#if defined (ESP8266) || defined(ESP32)
void PCF8591::begin(uint8_t sda, uint8_t scl, uint8_t val)
{
Wire.begin(sda, scl);
analogWrite(val);
}
#endif

void PCF8591::begin(uint8_t val)
{
Wire.begin();
analogWrite(val);
}

bool PCF8591::isConnected()
{
Wire.beginTransmission(_address);
_error = Wire.endTransmission(); // default == 0 == PCF8591_OK
return( _error == PCF8591_OK);
}

// ADC PART
uint8_t PCF8591::analogRead(uint8_t channel, uint8_t mode)
{
if (mode > 3)
{
_error = PCF8591_MODE_ERROR;
return 0;
}
_control &= 0b01000100; // clear all except flags
_control |= (mode << 4);

_error = PCF8591_CHANNEL_ERROR;
switch(mode)
{
case 0:
if (channel > 3) return 0;
_control |= channel;
break;
case 1:
if (channel > 2) return 0;
_control |= (channel << 4);
break;
case 2:
if (channel > 2) return 0;
_control |= (channel << 4);
break;
case 3:
if (channel > 1) return 0;
_control |= (channel << 4);
break;
default:
return 0;
}
_error = PCF8591_OK;

// NOTE: one must read two values to get an up to date value.
// Page 8 datasheet.
Wire.beginTransmission(_address);
Wire.write(_control);
_error = Wire.endTransmission(); // default == 0 == PCF8591_OK
if (_error != 0) return PCF8591_I2C_ERROR;
if (Wire.requestFrom(_address, (uint8_t)2) != 2)
{
_error = PCF8591_I2C_ERROR;
return _adc[channel]; // known last value
}
Wire.read();
_adc[channel] = Wire.read();
return _adc[channel];
}


void PCF8591::analogRead4()
{
_control &= 0b01000100; // clear all except flags
uint8_t channel = 0;
_control |= channel;


enableINCR();
Wire.beginTransmission(_address);
Wire.write(_control);
_error = Wire.endTransmission(); // default == 0 == PCF8591_OK
if (_error != 0)
{
disableINCR();
return PCF8591_I2C_ERROR;
}
if (Wire.requestFrom(_address, (uint8_t)5) != 5)
{
_error = PCF8591_I2C_ERROR;
disableINCR();
return;
}

Wire.read();
for (uint8_t i = 0; i < 4; i++)
{
_adc[i] = Wire.read();
}
_error = PCF8591_OK;
disableINCR();
return;
}


// DAC PART
void PCF8591::analogWrite(uint8_t value)
{
_dac = value;
Wire.beginTransmission(_address);
Wire.write(_control);
Wire.write(_dac);
_error = Wire.endTransmission();
return;
}

// -- END OF FILE --
69 changes: 69 additions & 0 deletions PCF8591.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
#pragma once
//
// FILE: PCF8591.h
// AUTHOR: Rob Tillaart
// DATE: 2020-03-12
// VERSION: 0.0.1
// PURPOSE: I2C PCF8591 library for Arduino
// URL: https://github.com/RobTillaart/9581
//
// HISTORY:
// see PCF8591.cpp file
//

#include "Arduino.h"
#include "Wire.h"

#define PCF8591_LIB_VERSION "0.0.1"

#define PCF8591_OK 0x00
#define PCF8591_PIN_ERROR 0x81
#define PCF8591_I2C_ERROR 0x82
#define PCF8591_MODE_ERROR 0x83
#define PCF8591_CHANNEL_ERROR 0x84

// INTERNAL USE ONLY
#define PCF8591_DAC_FLAG 0x40
#define PCF8591_INCR_FLAG 0x04

class PCF8591
{
public:
explicit PCF8591(const uint8_t address = 0x48);

#if defined (ESP8266) || defined(ESP32)
void begin(uint8_t sda, uint8_t scl, uint8_t val = 0);
#endif
void begin(uint8_t val = 0);

bool isConnected();

// ADC PART
// auto increment not tested ==> use with care!
void enableINCR() { _control |= PCF8591_INCR_FLAG; };
void disableINCR() { _control &= ~PCF8591_INCR_FLAG; };
bool isINCREnabled() { return ((_control & PCF8591_INCR_FLAG) > 0); };

uint8_t analogRead(uint8_t channel, uint8_t mode = 0);
void analogRead4();
uint8_t lastRead(uint8_t channel) { return _adc[channel]; };

// DAC PART
void enableDAC() { _control |= PCF8591_DAC_FLAG; };
void disableDAC() { _control &= ~PCF8591_DAC_FLAG; };
bool isDACEnabled() { return ((_control & PCF8591_DAC_FLAG) > 0); };

void analogWrite(uint8_t value = 0);
uint8_t lastWrite() { return _dac; };

int lastError() { return _error; };

private:
uint8_t _address;
uint8_t _control;
uint8_t _dac;
uint8_t _adc[4];
int _error;
};

// END OF FILE
69 changes: 69 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,71 @@
# PCF8591

Arduino Library for PCF8591 I2C 4 channel 8 bit ADC + 1 channel 8 bit DAC

## Description

**warning** during tests I could overclock the chip at 650 kHz but it is only specified
to run at 100 kHz. After getting pretty hot it broke down.
So overclocking is fun but not recommended.

PCF8591 has one 8 bit ADC on board for 4 channels. The ADC is 8 bit and quite fast.
At 100 kHz one gets \> 2000 reads per second for **analogRead()** and
\> 2000 writes per second for **analogWrite()**.
Note that most time is probably spend on I2C communication.

The auto-increment functionality is used in the **analogRead4()** function.
The library only supports it for the mode 0 (plain ADC, no differential).
The **lastRead()** function is needed to get access to the values.
First tests shows it is 2.6 x faster than 4 individual reads.

**analogRead4()** needs investigation in the future for the other modi.

## Interface

#### generic

- **PCF8591(const uint8_t address)** constructor with I2C address, default is 0x48
- **begin(uint8_t sda, uint8_t scl, uint8_t val = 0)** set wire pins for ESP series.
Also set initial value for the DAC.
- **begin(uint8_t val = 0)** Set initial value for the DAC.
- **isConnected()** test to see if chip can be reached.

#### ADC part

The PCF8591 has four 8 bit ADC channels.

- **enableINCR()** used in analogRead4(); Could become private in the future.
- **disableINCR()** idem.
- **isINCREnabled()** idem.
- **analogRead(uint8_t channel, uint8_t mode = 0)** read one of the 4 analog ports.
Default mode is single ports. For comparator modes test datasheet.
- **analogRead4()** read all 4 channels in one call.
Uses **enableINCR()** to do that efficiently.
It is about 2.6 x faster than 4 individual **analogRead()**, although the latter
allows for optimized timing per channel.
Only 4x single ports mode supported for now, comparator modi needs investigation.
- **lastRead(uint8_t channel)** get last read value from cache.
This cache is filled both by **analogRead()** and **analogRead4()**. See example sketch.

#### DAC PART

The PCF8591 has one 8 bit DAC.

- **enableDAC()** switch on the analog output
- **disableDAC()** switch off the analog output (high impedance) Sort of energy saving mode.
- **isDACEnabled()** check the modus operandi.
- **analogWrite(uint8_t value = 0)** writes a value 0..255 to the DAC. CHeck datasheet for voltage.
- **lastWrite()** get last written value from cache.
- **lastError()** always check this value after a read / write to see if it was OK (== 0)

## Future

- Improve comparator modi support, how to do that?
dedicated functions? return type int8_t?
- **analogRead4()** needs investigation for the other modi. Does it work?
Is it user understandable?


## Operations

See examples.
78 changes: 78 additions & 0 deletions examples/PCF8591_demo/PCF8591_demo.ino
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
//
// FILE: PCF8591_demo.ino
// AUTHOR: Rob Tillaart
// VERSION: 0.1.0
// PURPOSE: demo
// DATE: 2020-07-22
// URL: https://github.com/RobTillaart/PCF8591

#include "PCF8591.h"

PCF8591 dev(0x48);

void setup()
{
Serial.begin(115200);
Serial.println(__FILE__);

Wire.begin();
Wire.setClock(100000UL);

#if defined (ESP8266) || defined(ESP32)
dev.begin(21, 22);
#endif
dev.begin();
}

void loop()
{
test_DAC();
delay(1000);
test_ADC_mode(0);
delay(1000);

// differential modes, check datasheet
test_ADC_mode(1);
delay(1000);
test_ADC_mode(2);
delay(1000);
test_ADC_mode(3);
delay(1000);
}


void test_DAC()
{
Serial.println(__FUNCTION__);
Serial.println("--------------");
dev.enableDAC();
for (int i = 0; i < 5000; i++)
{
uint8_t val = 127 + 127 * sin(i * 0.01);
Serial.println(val);
dev.analogWrite(val);
delay(1); // just to slow the effect
}
dev.disableDAC();
Serial.println();
}

void test_ADC_mode(uint8_t mode)
{
uint8_t channels[] = {4, 3, 3, 2 }; // channels per mode
Serial.println(__FUNCTION__);
Serial.println("--------------");
Serial.println("CH0\tCH1\tCH2\tCH3");
for (int m = 0; m < 20; m++)
{
for (uint8_t i = 0; i < channels[mode]; i++)
{
Serial.print(dev.analogRead(i, mode));
Serial.print('\t');
}
Serial.println();
}
Serial.println();
}

// -- END OF FILE --
Loading

0 comments on commit 5f2b1f6

Please sign in to comment.