0.2.1 X9C10X

This commit is contained in:
rob tillaart 2022-07-24 16:36:28 +02:00
parent f24d899e68
commit 7f65d0c013
10 changed files with 289 additions and 42 deletions

View File

@ -13,8 +13,10 @@ Arduino Library for X9C10X series digital potentiometer.
## Description ## Description
This **experimental** library provides a X9C base class, a X9C10X class and This **experimental** library provides
four derived classes for specific digital potentiometer. - a minimal X9C base class,
- an elaborated X9C10X class and
- four derived classes for specific digital potentiometer.
| class | resistance | tested | notes | | class | resistance | tested | notes |
|:------:|:----------:|:-------:|:-------------| |:------:|:----------:|:-------:|:-------------|
@ -29,10 +31,12 @@ four derived classes for specific digital potentiometer.
_Note: Ω Ohm sign = ALT-234_ _Note: Ω Ohm sign = ALT-234_
The X9C10X object keeps track of the position of the potentiometer, The X9C10X object keeps track of the position of the potentiometer,
but the user should set it with **setPosition(pos, true);** but the user should set it with **setPosition(position, true);**
Otherwise the library and device will probably not be in sync. Otherwise the library and device will probably not be in sync.
Since 0.2.0 the library has a minimal X9C class. See below. Since 0.2.1 the library also supports **restoreInternalPosition(position)**
to set the internal position with the value from the latest **store()** call.
See the examples.
### Multiple devices ### Multiple devices
@ -40,11 +44,12 @@ Since 0.2.0 the library has a minimal X9C class. See below.
Multiple devices can be controlled by assigning them an unique selectPin (CS). Multiple devices can be controlled by assigning them an unique selectPin (CS).
This behaviour is similar to the SPI select pin. This behaviour is similar to the SPI select pin.
It should be possible to share the U/D and INC lines (not tested) when controlling multiple X9C devices. It should be possible to share the U/D and INC lines (not tested) when controlling
multiple X9C devices.
Note: one should select one device at a time. Note: one should select one device at a time.
Sharing a CS pin or sending pulses to multiple devices at the same time will Sharing a CS pin or sending pulses to multiple devices at the same time will
cause the library and devices get oout of sync. cause the library and devices get out of sync.
### PINOUT ### PINOUT
@ -79,13 +84,14 @@ quality can become an issue. (not investigated further)
## Interface ## Interface
```cpp
#include "X9C10X.h"
```
## X9C base class ## X9C base class
This is the most minimalistic base class. This is the most minimalistic base class.
It does not provide position information but that is sometimes enough. It does not provide position information but sometimes that is just enough.
Use **\#include "X9C10X.h"**
- **X9C()** Constructor. - **X9C()** Constructor.
- **void begin(uint8_t pulsePin, uint8_t directionPin, uint8_t selectPin)** - **void begin(uint8_t pulsePin, uint8_t directionPin, uint8_t selectPin)**
@ -95,14 +101,12 @@ Note: **begin()** has a hard coded 500uS delay so the device can wake up.
- **void decr()** moves one position down (if possible). - **void decr()** moves one position down (if possible).
- **void store()** stores the current position in NV-RAM to be used at the next restart. - **void store()** stores the current position in NV-RAM to be used at the next restart.
Does not return a value as the position cannot be read from the device. Does not return a value as the position cannot be read from the device.
So the user should keep track of the position if needed. So the user must keep track of the position if needed.
## X9C10X base class ## X9C10X base class
This class is derived from the X9C class but adds position, Ohm and type information. This class is derived from the X9C class and adds position, Ohm and type information.
Use **\#include "X9C10X.h"**
- **X9C10X(uint32_t Ohm = 10000)** Constructor, default initializes the resistance to 10000 Ω. - **X9C10X(uint32_t Ohm = 10000)** Constructor, default initializes the resistance to 10000 Ω.
To calibrate one can fill in any other (measured) value e.g. 9950 Ω. To calibrate one can fill in any other (measured) value e.g. 9950 Ω.
@ -110,12 +114,14 @@ This can be useful e.g. if one sets a fixed resistor parallel over the X9C one.
- **void begin(uint8_t pulsePin, uint8_t directionPin, uint8_t selectPin)** - **void begin(uint8_t pulsePin, uint8_t directionPin, uint8_t selectPin)**
sets the INC, UD and CS pins used by the device. sets the INC, UD and CS pins used by the device.
Note: **begin()** has a hard coded 500uS delay so the device can wake up. Note: **begin()** has a hard coded 500uS delay so the device can wake up.
- **void setPosition(uint8_t position, bool forced = false)** sets the wiper - **uint8_t setPosition(uint8_t position, bool forced = false)** sets the wiper
to a position between 0 and 99. to a position between 0 and 99.
The movement is relative to the current (internal) position. The movement is relative to the current (internal) position.
If forced is set to true, the wiper will be moved to the closest "end" position If forced is set to true, the wiper will be moved to the closest "end" position
and from there moved to the requested position. and from there moved to the requested position.
The internal position is replaced by the new position. The internal position is replaced by the new position.
If the new position > 99 the new position is truncated to 99.
Returns new position 0 .. 99.
- **uint8_t getPosition()** returns the current (internal) position. 0..99 - **uint8_t getPosition()** returns the current (internal) position. 0..99
- **bool incr()** moves one position up (if possible). - **bool incr()** moves one position up (if possible).
Returns true if moved and false if already at end position Returns true if moved and false if already at end position
@ -123,10 +129,19 @@ according to internal position math.
- **bool decr()** moves one position down (if possible). - **bool decr()** moves one position down (if possible).
Returns true if moved and false if already at begin position Returns true if moved and false if already at begin position
according to internal position math. according to internal position math.
- **uint8_t store()** stores the current position in the NVRAM of the device, - **uint8_t store()** stores the current position in the NVRAM of the device.
and returns the current position so it can later be used as position parameter for **setPosition()**. Returns the current position so it can later be used as position parameter
Warning: use with care (not tested). for **setPosition()** or **restoreInternalPosition()**.
Note: **store()** blocks for 20 milliseconds. - Warning: use with care (not tested).
- Note: **store()** blocks for 20 milliseconds.
- **uint8_t restoreInternalPosition(uint8_t position)** hard overwrite of the current
(internal) position to initialize the library with the value returned by **store()**.
The potentiometer will not be moved() in this process, and the user is responsible
to provide the right value.
Returns new position 0 .. 99.
This function allows users e.g. to save the position returned by **store()** in EEPROM
to initialize the library with this EEPROM value after a reboot.
- Warning: use with care (not tested).
Note: **begin()** changed in 0.2.0 as the implicit parameter position Note: **begin()** changed in 0.2.0 as the implicit parameter position
was removed for the explicit function call to **setPosition()**. was removed for the explicit function call to **setPosition()**.
@ -135,6 +150,13 @@ value as position. Unfortunately the position cannot be read from the device.
This will result in a mismatch between the internal position and the This will result in a mismatch between the internal position and the
external one. external one.
Since 0.2.1 the function **uint8_t restoreInternalPosition(uint8_t position)**
gives some means to solve this, see examples.
Be aware that if a system resets and the position has been changed since last
**store()** the restore and therefore the library will not be in sync with the device.
To create a fool proof system additional hardware is needed, see Concept read position below.
#### Ohm #### Ohm
@ -165,7 +187,7 @@ These classes have the same interface as the X9C10X base class.
The only difference is that the type is set to a non zero value. The only difference is that the type is set to a non zero value.
#### Performance ## Performance
The table below is tested on a (relative slow) Arduino UNO 16 MHz with IDE 1.18.19. The table below is tested on a (relative slow) Arduino UNO 16 MHz with IDE 1.18.19.
Other processors might give similar or faster times. See performance example. Other processors might give similar or faster times. See performance example.
@ -191,6 +213,8 @@ X9C10X_LIB_VERSION: 0.1.2
Time per step is 780 / 99 = ~8 us per step on an UNO. Time per step is 780 / 99 = ~8 us per step on an UNO.
Note: no performance improvements since 0.1.2
## Operation ## Operation
@ -206,9 +230,30 @@ A voltage of **3V3** would be **setPosition(66)**.
Note: check datasheet for the range of the max voltage and current allowed. Note: check datasheet for the range of the max voltage and current allowed.
#### Concept read position
If you need to make a robust system with X9C devices you can solder two devices "in parallel".
One to control whatever you need to control, and the other to create a feedback loop through analogRead().
Lets name them feedback device and control device.
The two devices should share the select, direction and pulse pins in hardware.
This way they will get the exact same pulses and signals and would therefore be in the exact same position
after initialization.
The feedback device would be a voltage divider, splitting 5 Volts in 100 level.
To read these levels you need at least an 8 bit ADC or better.
This setup would allow you to read the position in the control device 100% of the time.
The price is at least twice as high in terms of hardware, the performance will be less at some times
and the code will be slightly more complex
It might be possible to measure the voltage of the wiper of the control device.
However that might not always be easy or possible, due to voltage used, etc.
## Future ## Future
- update documentation - update documentation
- concept of **read()** => put 2 X9C parallel and read one with analogRead().
- test different platforms - test different platforms
- add error codes ? - add error codes ?
- add examples - add examples
@ -225,5 +270,5 @@ Note: check datasheet for the range of the max voltage and current allowed.
- **getOhm()** ==> **getValue()** - **getOhm()** ==> **getValue()**
- **getMaxOhm()** ==> **getMaxValue()** - **getMaxOhm()** ==> **getMaxValue()**
- think milliVolt, ohm, lux, speed, etc. - think milliVolt, ohm, lux, speed, etc.
User can do this too with **getPosition() * factor** User can do this too with **getPosition() \* factor**

View File

@ -1,7 +1,7 @@
// //
// FILE: X9C10X.cpp // FILE: X9C10X.cpp
// AUTHOR: Rob Tillaart // AUTHOR: Rob Tillaart
// VERSION: 0.2.0 // VERSION: 0.2.1
// PURPOSE: Arduino Library for X9C10X series digital potentiometer. // PURPOSE: Arduino Library for X9C10X series digital potentiometer.
// URL: https://github.com/RobTillaart/X9C10X // URL: https://github.com/RobTillaart/X9C10X
// //
@ -15,12 +15,18 @@
// 0.2.0 2022-07-09 fix #7 incorrect signal during initialize // 0.2.0 2022-07-09 fix #7 incorrect signal during initialize
// remove position parameter from begin() // remove position parameter from begin()
// to make setting position more explicit. // to make setting position more explicit.
// update readme // update readme.md
// add uint8_t Ohm2Position() // add uint8_t Ohm2Position()
// 0.2.1 2022-07-23 fix #9 add restoreInternalPosition(pos)
// change return type setPosition() to indicate truncation
// update readme.md and comments
// update build-CI tests
#include "X9C10X.h" #include "X9C10X.h"
// minimum pulse width CLOCK = ? us (datasheet); // minimum pulse width CLOCK = ? us (datasheet);
// digitalWrite takes enough time on UNO / AVR so clock_delay == 0 // digitalWrite takes enough time on UNO / AVR so clock_delay == 0
// Note that if clock pulses are long enough the data pulses are too. // Note that if clock pulses are long enough the data pulses are too.
@ -33,6 +39,8 @@
#define X9C10X_UP HIGH #define X9C10X_UP HIGH
#define X9C10X_DOWN LOW #define X9C10X_DOWN LOW
#define X9C10X_MAXPOT 99
///////////////////////////////////////////////////////// /////////////////////////////////////////////////////////
// //
@ -122,8 +130,6 @@ void X9C::_move(uint8_t direction, uint8_t steps)
///////////////////////////////////////////////////////// /////////////////////////////////////////////////////////
// //
// X9C10X BASE CLASS // X9C10X BASE CLASS
@ -134,14 +140,14 @@ X9C10X::X9C10X(uint32_t maxOhm) : X9C()
} }
void X9C10X::setPosition(uint8_t position, bool forced) uint8_t X9C10X::setPosition(uint8_t position, bool forced)
{ {
if (position > 99) position = 99; if (position > 99)
// reference 0.1.0 {
// while (position > _position) incr(); position = 99;
// while (position < _position) decr(); }
// force to nearest end position first to minimize steps. // force to nearest end position first to minimize number of steps.
if (forced) if (forced)
{ {
if (position < 50) if (position < 50)
@ -165,6 +171,7 @@ void X9C10X::setPosition(uint8_t position, bool forced)
} }
_position = position; _position = position;
return _position;
} }
@ -193,6 +200,17 @@ uint8_t X9C10X::store()
} }
uint8_t X9C10X::restoreInternalPosition(uint8_t position)
{
if (position > 99)
{
position = 99;
}
_position = position;
return _position;
}
// rounding needed! // rounding needed!
uint32_t X9C10X::getOhm() uint32_t X9C10X::getOhm()
{ {

View File

@ -2,14 +2,14 @@
// //
// FILE: X9C10X.h // FILE: X9C10X.h
// AUTHOR: Rob Tillaart // AUTHOR: Rob Tillaart
// VERSION: 0.2.0 // VERSION: 0.2.1
// PURPOSE: Arduino Library for X9C10X series digital potentiometer. // PURPOSE: Arduino Library for X9C10X series digital potentiometer.
// URL: https://github.com/RobTillaart/X9C10X // URL: https://github.com/RobTillaart/X9C10X
#include "Arduino.h" #include "Arduino.h"
#define X9C10X_LIB_VERSION (F("0.2.0")) #define X9C10X_LIB_VERSION (F("0.2.1"))
///////////////////////////////////////////////////////// /////////////////////////////////////////////////////////
@ -25,6 +25,7 @@ public:
void begin(uint8_t pulsePin, uint8_t directionPin, uint8_t selectPin); void begin(uint8_t pulsePin, uint8_t directionPin, uint8_t selectPin);
// step size 1. // step size 1.
// return false if end of range reached.
bool incr(); bool incr();
bool decr(); bool decr();
@ -51,18 +52,27 @@ public:
X9C10X(uint32_t maxOhm = 10000); X9C10X(uint32_t maxOhm = 10000);
// position = 0..99 // position = 0..99
// values > 99 are truncated.
// forced = true will ignore the cached position // forced = true will ignore the cached position
// takes up to 150 steps as one cannot read the position from device. // takes up to 150 steps as one cannot read the position from device.
// forced = default false as that is safer and backwards compatible. // forced = default false as that is safer and backwards compatible.
void setPosition(uint8_t position, bool forced = false); // returns new position 0..99
uint8_t setPosition(uint8_t position, bool forced = false);
uint8_t getPosition() { return _position; }; uint8_t getPosition() { return _position; };
// step size 1. // step size 1.
// return false if end of range reached.
bool incr(); bool incr();
bool decr(); bool decr();
// use with care // use with care
// returns new position 0..99
uint8_t store(); uint8_t store();
// note: restoreInternalPosition() is not available in X9C base class.
// position = 0..99
// values > 99 are truncated.
// returns new position 0..99
uint8_t restoreInternalPosition(uint8_t position);
// current resistance in ohm. // current resistance in ohm.
uint32_t getOhm(); uint32_t getOhm();

View File

@ -153,6 +153,8 @@ void setup()
Serial.print("10 x decr():\t"); Serial.print("10 x decr():\t");
Serial.println(stop - start); Serial.println(stop - start);
delay(100); delay(100);
Serial.println("\ndone...");
} }

View File

@ -0,0 +1,14 @@
compile:
# Choosing to run compilation tests on 2 different Arduino platforms
platforms:
- uno
# - due
# - zero
# - leonardo
# - m4
# - esp32
# - esp8266
- mega2560
libraries:
- "printHelpers"

View File

@ -0,0 +1,109 @@
//
// FILE: X9C10X_store.ino
// AUTHOR: Rob Tillaart
// PURPOSE: demo store
// NOTE: AVR Arduino UNO only
// be aware the internal EEPROM can wear out !
// WARNING: the restore mechanism demonstrated here will only work if the device
// has not moved since the last call to store() as in this example.
// It is the responsibility of the user to verify if that is the case.
//
#include "Arduino.h"
#include "X9C10X.h"
#include "EEPROM.h"
// PINOUT X9C10X TOP VIEW (see datasheet)
//
// +--------+
// INC | o o | VCC
// U/D | o o | CS
// RH | o o | RL
// GND | o o | Rwiper
// +--------+
//
// INC pulses
// U/D UP = 1 DOWN = 0
// VCC +5V
// GND ground
// RH resistor high end
// RL resistor low end
// Rwiper resistor wiper
// CS chip select
//
X9C10X pot(12345); // 100KΩ (ALT-234)
const uint8_t POT_ADDR = 0x10; // for EEPROM
void setup()
{
Serial.begin(115200);
while (!Serial);
Serial.println();
Serial.print("X9C10X_LIB_VERSION: ");
Serial.println(X9C10X_LIB_VERSION);
Serial.println();
pot.begin(8, 9, 10); // pulse, direction, select
uint8_t pos = 0;
EEPROM.get(POT_ADDR, pos);
// if EEPROM has no valid value or not been used (255).
if (pos > 99)
{
pot.setPosition(0, true); // adjust to your needs.
}
else
{
pot.restoreInternalPosition(pos);
}
Serial.print("RESTORE:\t");
Serial.println(pot.getPosition());
}
void loop()
{
if (Serial.available() > 0)
{
int c = Serial.read();
switch (c)
{
case '+' :
pot.incr();
Serial.print("POS:\t");
Serial.println(pot.getPosition());
break;
case '-' :
pot.decr();
Serial.print("POS:\t");
Serial.println(pot.getPosition());
break;
// QUIT
case 'q' :
uint8_t p = pot.store();
EEPROM.update(POT_ADDR, p);
Serial.print("\nSTORE:\t");
Serial.println(pot.getPosition());
Serial.println("\nreset to start again...");
while (1); // block forever after quit.
break;
}
}
}
// -- END OF FILE --

View File

@ -21,6 +21,7 @@ getMaxOhm KEYWORD2
Ohm2Position KEYWORD2 Ohm2Position KEYWORD2
store KEYWORD2 store KEYWORD2
restoreInternalPosition KEYWORD2
getType KEYWORD2 getType KEYWORD2

View File

@ -15,7 +15,7 @@
"type": "git", "type": "git",
"url": "https://github.com/RobTillaart/X9C10X.git" "url": "https://github.com/RobTillaart/X9C10X.git"
}, },
"version": "0.2.0", "version": "0.2.1",
"license": "MIT", "license": "MIT",
"frameworks": "arduino", "frameworks": "arduino",
"platforms": "*", "platforms": "*",

View File

@ -1,5 +1,5 @@
name=X9C10X name=X9C10X
version=0.2.0 version=0.2.1
author=Rob Tillaart <rob.tillaart@gmail.com> author=Rob Tillaart <rob.tillaart@gmail.com>
maintainer=Rob Tillaart <rob.tillaart@gmail.com> maintainer=Rob Tillaart <rob.tillaart@gmail.com>
sentence=Arduino Library for X9C10X series digital potentiometer. sentence=Arduino Library for X9C10X series digital potentiometer.

View File

@ -28,12 +28,12 @@
#include "X9C10X.h" #include "X9C10X.h"
unittest_setup() unittest_setup()
{ {
fprintf(stderr, "X9C10X_LIB_VERSION: %s\n", (char *) X9C10X_LIB_VERSION); fprintf(stderr, "X9C10X_LIB_VERSION: %s\n", (char *) X9C10X_LIB_VERSION);
} }
unittest_teardown() unittest_teardown()
{ {
} }
@ -81,6 +81,26 @@ unittest(test_X9C10X_constructor)
} }
unittest(test_X9C10X_restore_internal_position)
{
X9C10X dp0;
dp0.begin(7, 8, 9);
assertEqual(0, dp0.restoreInternalPosition(0));
assertEqual(0, dp0.getPosition());
assertEqual(50, dp0.restoreInternalPosition(50));
assertEqual(50, dp0.getPosition());
// test truncation
assertEqual(99, dp0.restoreInternalPosition(99));
assertEqual(99, dp0.getPosition());
assertEqual(99, dp0.restoreInternalPosition(100));
assertEqual(99, dp0.getPosition());
assertEqual(99, dp0.restoreInternalPosition(255));
assertEqual(99, dp0.getPosition());
}
unittest(test_X9C10X_position) unittest(test_X9C10X_position)
{ {
X9C10X dp0; X9C10X dp0;
@ -88,12 +108,40 @@ unittest(test_X9C10X_position)
dp0.begin(7, 8, 9); dp0.begin(7, 8, 9);
assertEqual(0, dp0.getPosition()); assertEqual(0, dp0.getPosition());
fprintf(stderr, "setPosition step 9\n"); fprintf(stderr, "setPosition step 19\n");
for (uint8_t pos = 0; pos < 100; pos += 9) for (uint8_t pos = 0; pos < 100; pos += 19)
{ {
dp0.setPosition(pos); assertEqual(pos, dp0.setPosition(pos));
assertEqual(pos, dp0.getPosition()); assertEqual(pos, dp0.getPosition());
} }
// test truncation
assertEqual(99, dp0.setPosition(99));
assertEqual(99, dp0.setPosition(100));
assertEqual(99, dp0.setPosition(255));
}
unittest(test_X9C10X_store)
{
X9C10X dp0;
dp0.begin(7, 8, 9);
fprintf(stderr, "store step 19\n");
for (uint8_t pos = 0; pos < 100; pos += 19)
{
dp0.setPosition(pos);
assertEqual(pos, dp0.store());
}
// test truncation
dp0.setPosition(99);
assertEqual(99, dp0.store());
dp0.setPosition(100);
assertEqual(99, dp0.store());
dp0.setPosition(255);
assertEqual(99, dp0.store());
} }