{"id":13893835,"url":"https://github.com/RobTillaart/X9C10X","last_synced_at":"2025-07-17T08:31:00.456Z","repository":{"id":43690376,"uuid":"458859739","full_name":"RobTillaart/X9C10X","owner":"RobTillaart","description":"Arduino Library for X9C10X series digital potentiometer.","archived":false,"fork":false,"pushed_at":"2024-04-13T09:14:57.000Z","size":27,"stargazers_count":12,"open_issues_count":0,"forks_count":2,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-04-02T22:03:52.754Z","etag":null,"topics":["arduino","digital","potentiometer"],"latest_commit_sha":null,"homepage":"","language":"C++","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/RobTillaart.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null},"funding":{"github":"RobTillaart","custom":"https://www.paypal.me/robtillaart"}},"created_at":"2022-02-13T16:08:26.000Z","updated_at":"2025-03-22T21:55:20.000Z","dependencies_parsed_at":"2024-04-13T10:29:13.906Z","dependency_job_id":"49ffc872-88b2-400e-afba-090c1b06708c","html_url":"https://github.com/RobTillaart/X9C10X","commit_stats":null,"previous_names":[],"tags_count":8,"template":false,"template_full_name":null,"purl":"pkg:github/RobTillaart/X9C10X","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RobTillaart%2FX9C10X","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RobTillaart%2FX9C10X/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RobTillaart%2FX9C10X/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RobTillaart%2FX9C10X/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/RobTillaart","download_url":"https://codeload.github.com/RobTillaart/X9C10X/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RobTillaart%2FX9C10X/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":265585279,"owners_count":23792712,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["arduino","digital","potentiometer"],"created_at":"2024-08-06T18:01:18.162Z","updated_at":"2025-07-17T08:31:00.179Z","avatar_url":"https://github.com/RobTillaart.png","language":"C++","funding_links":["https://github.com/sponsors/RobTillaart","https://www.paypal.me/robtillaart"],"categories":["C++"],"sub_categories":[],"readme":"\n[![Arduino CI](https://github.com/RobTillaart/X9C10X/workflows/Arduino%20CI/badge.svg)](https://github.com/marketplace/actions/arduino_ci)\n[![Arduino-lint](https://github.com/RobTillaart/X9C10X/actions/workflows/arduino-lint.yml/badge.svg)](https://github.com/RobTillaart/X9C10X/actions/workflows/arduino-lint.yml)\n[![JSON check](https://github.com/RobTillaart/X9C10X/actions/workflows/jsoncheck.yml/badge.svg)](https://github.com/RobTillaart/X9C10X/actions/workflows/jsoncheck.yml)\n[![GitHub issues](https://img.shields.io/github/issues/RobTillaart/X9C10X.svg)](https://github.com/RobTillaart/X9C10X/issues)\n\n[![License: MIT](https://img.shields.io/badge/license-MIT-green.svg)](https://github.com/RobTillaart/X9C10X/blob/master/LICENSE)\n[![GitHub release](https://img.shields.io/github/release/RobTillaart/X9C10X.svg?maxAge=3600)](https://github.com/RobTillaart/X9C10X/releases)\n[![PlatformIO Registry](https://badges.registry.platformio.org/packages/robtillaart/library/X9C10X.svg)](https://registry.platformio.org/libraries/robtillaart/X9C10X)\n\n\n# X9C10X\n\nArduino Library for X9C10X series digital potentiometer.\n\n\n## Description\n\nThis **experimental** library provides \n- a minimal X9C base class, \n- an elaborated X9C10X class and \n- four derived classes for specific digital potentiometer.\n\n| class  | resistance | tested  |  notes       |\n|:------:|:----------:|:-------:|:-------------|\n| X9C    | no support |    Y    |  minimalistic base class |\n| X9C10X |    generic |    Y    |  base class  |\n| X9C102 |    1 KΩ    |    N    |  10 \\* 10^2  |\n| X9C103 |   10 KΩ    |    Y    |  10 \\* 10^3  |\n| X9C104 |  100 KΩ    |    N    |  10 \\* 10^4  |\n| X9C503 |   50 KΩ    |    N    |  50 \\* 10^3  |\n\n\n_Note: Ω Ohm sign = ALT-234_\n\nThe X9C10X object keeps track of the position of the potentiometer,\nbut the user should set it with **setPosition(position, true);**\nOtherwise the library and device will probably not be in sync.\n\nSince 0.2.1 the library also supports **restoreInternalPosition(position)**\nto set the internal position with the value from the latest **store()** call.\nSee the examples.\n\n\n#### Multiple devices\n\nMultiple devices can be controlled by assigning them an unique selectPin (CS).\nThis behaviour is similar to the SPI select pin.\n\nIt should be possible to share the U/D and INC lines (not tested) when controlling\nmultiple X9C devices.\n\nNote: one should select one device at a time.\nSharing a CS pin or sending pulses to multiple devices at the same time will\ncause the library and devices get out of sync.\n\n\n#### PINOUT\n\n\n```cpp\n//  PINOUT  X9C10X TOP VIEW  (see datasheet)\n//\n//       +--------+\n//  INC  | o    o |  VCC\n//  U/D  | o    o |  CS\n//  RH   | o    o |  RL\n//  GND  | o    o |  Rwiper\n//       +--------+\n//\n//  INC     pulses\n//  U/D     UP = 1   DOWN = 0\n//  VCC     +5V\n//  GND     ground\n//  RH      resistor high end\n//  RL      resistor low end\n//  Rwiper  resistor wiper\n//  CS      chip select\n```\n\nIt is advised to use pull up resistors - e.g. 10 KΩ - on the CS, UD and INC line. \nThis will help the lines to start in a defined state and will\nimprove the signal quality. \nThe pulses esp. INC can be quite short, so especially with longer lines the\nquality can become an issue. (not investigated further)\n\n\n#### Related\n\n- https://github.com/RobTillaart/AD520x\n- https://github.com/RobTillaart/AD524X\n- https://github.com/RobTillaart/AD5245\n- https://github.com/RobTillaart/AD5144A\n- https://github.com/RobTillaart/AD5245\n- https://github.com/RobTillaart/AD5263\n- https://github.com/RobTillaart/X9C10X\n\n\n## Interface\n\n```cpp\n#include \"X9C10X.h\"\n```\n\n## X9C base class\n\nThis is the most minimalistic base class. \nIt does not provide position information but sometimes that is just enough.\n\n- **X9C()** Constructor.\n- **void begin(uint8_t pulsePin, uint8_t directionPin, uint8_t selectPin)** \nsets the INC, UD and CS pins used by the device.  \nNote: **begin()** has a hard coded 500uS delay so the device can wake up.\n- **void incr()** moves one position up (if possible). \n- **void decr()** moves one position down (if possible).\n- **void store()** stores the current position in NV-RAM to be used at the next restart. \nDoes not return a value as the position cannot be read from the device.\nSo the user must keep track of the position if needed.\n\n\n## X9C10X base class\n\nThis class is derived from the X9C class and adds position, Ohm and type information.\n\n- **X9C10X(uint32_t Ohm = 10000)** Constructor, default initializes the resistance to 10000 Ω. \nTo calibrate one can fill in any other (measured) value e.g. 9950 Ω.\nThis can be useful e.g. if one sets a fixed resistor parallel over the X9C one.\n- **void begin(uint8_t pulsePin, uint8_t directionPin, uint8_t selectPin)** \nsets the INC, UD and CS pins used by the device.  \nNote: **begin()** has a hard coded 500uS delay so the device can wake up.\n- **uint8_t setPosition(uint8_t position, bool forced = false)** sets the wiper \nto a position between 0 and 99. \nThe movement is relative to the current (internal) position.\nIf forced is set to true, the wiper will be moved to the closest \"end\" position \nand from there moved to the requested position. \nThe internal position is replaced by the new position.\nIf the new position \u003e 99 the new position is truncated to 99.\nReturns new position 0 .. 99.\n- **uint8_t getPosition()** returns the current (internal) position. 0..99\n- **bool incr()** moves one position up (if possible). \nReturns true if moved and false if already at end position\naccording to internal position math.\n- **bool decr()** moves one position down (if possible).\nReturns true if moved and false if already at begin position\naccording to internal position math.\n- **uint8_t store()** stores the current position in the NVRAM of the device. \nReturns the current position so it can later be used as position parameter \nfor **setPosition()** or **restoreInternalPosition()**.\n  - Warning: use with care (not tested).\n  - Note: **store()** blocks for 20 milliseconds.\n- **uint8_t restoreInternalPosition(uint8_t position)** hard overwrite of the current \n(internal) position to initialize the library with the value returned by **store()**.\nThe potentiometer will not be moved() in this process, and the user is responsible\nto provide the right value. \nReturns new position 0 .. 99.\nThis function allows users e.g. to save the position returned by **store()** in EEPROM \nto initialize the library with this EEPROM value after a reboot.\n  - Warning: use with care (not tested).\n\nNote: **begin()** changed in 0.2.0 as the implicit parameter position\nwas removed for the explicit function call to **setPosition()**.\nIf **setPosition()** is not called, the device uses the last stored\nvalue as position. Unfortunately the position cannot be read from the device.\nThis will result in a mismatch between the internal position and the \nexternal one. \n\nSince 0.2.1 the function **uint8_t restoreInternalPosition(uint8_t position)** \ngives some means to solve this, see examples.\nBe aware that if a system resets and the position has been changed since last \n**store()** the restore and therefore the library will not be in sync with the device. \nTo create a fool proof system additional hardware is needed, see Concept read position below.\n\n\n#### Ohm\n\n- **uint32_t getOhm()** returns the position expressed in Ohm.\nThe returned value does depend on the value passed in the constructor\nand the current position. \nNote: value returned might differ a bit from the actual value, see below.\n- **uint32_t getMaxOhm()** returns the maximum value ( =  parameter from constructor). Convenience function.\n- **uint32_t Ohm2Position(uint32_t value, bool invert = false)**\nCalculates (with rounding) the position nearest to the requested value. \nIf **invert == true** it uses the other wiper end as reference.\n\nOne can measure the resistance between RH and RL and use this value to set \nin the constructor. Although the value will not differ much from the datasheet\none can get more precise values from the **getOhm()** function this way.\n\n\n## Derived classes \n\nThere are 4 derived classes, each with a other (appropriate) default value for the resistance. \n\n- **X9C102(uint32_t Ohm = 1000)** idem.\n- **X9C103(uint32_t Ohm = 10000)** idem.\n- **X9C104(uint32_t Ohm = 100000)** idem.\n- **X9C503(uint32_t Ohm = 50000)** idem.\n\nThese classes have the same interface as the X9C10X base class.\nThe only difference is that the type is set to a non zero value.\n\n\n## Performance\n\nThe table below is tested on a (relative slow) Arduino UNO 16 MHz with IDE 1.18.19.\nOther processors might give similar or faster times. See performance example.\n\nX9C10X_LIB_VERSION: 0.1.2\n\n | function            | time in us | notes                   |\n |:--------------------|:----------:|:------------------------|\n | getPosition         |     4      | cached value            |\n | setPosition(33)     |   304      | from 0 to 33            |\n | setPosition(66)     |   524      | from 0 to 66            |\n | setPosition(99)     |   780      | from 0 to 99            |\n | setPosition(33)     |   288      | from 0 to 33            |\n | setPosition(66)     |   276      | from 33 to 66           |\n | setPosition(99)     |   276      | from 66 to 99           |\n | setPosition(33)     |    12      | from 33 to 33 = no move |\n | setPosition(66)     |     4      | from 66 to 66 = no move |\n | setPosition(99)     |     4      | from 99 to 99 = no move |\n | getMaxOhm           |     4      | cached value            |\n | getOhm()            |    48      |                         |\n | incr()              |    28.8    | average 10 calls        |\n | decr()              |    29.2    | average 10 calls        |\n\nTime per step is 780 / 99 = ~8 us per step on an UNO.\n\nNote: no performance improvements since 0.1.2\n\n\n## Operation\n\nSee examples.\n\n\n#### Voltage divider \n\nThe digital potentiometer (esp 10 KΩ and up) can be used as a voltage divider.\nConnect RL to GND and RH to +5V and you can do 5V in 100 steps of ~0.05V\nA voltage of **3V3** would be **setPosition(66)**. \n\nNote: check datasheet for the range of the max voltage and current allowed.\n\n\n#### Concept read position \n\nIf you need to make a robust system with X9C devices you can solder two devices \"in parallel\".\nOne to control whatever you need to control, and the other to create a feedback loop through analogRead().\nLets name them feedback device and control device.\nThe two devices should share the select, direction and pulse pins in hardware.\nThis way they will get the exact same pulses and signals and would therefore be in the exact same position\nafter initialization.\n\nThe feedback device would be a voltage divider, splitting 5 Volts in 100 level.\nTo read these levels you need at least an 8 bit ADC or better.\nThis setup would allow you to read the position in the control device 100% of the time. \n\nThe price is at least twice as high in terms of hardware, the performance will be less at some times \nand the code will be slightly more complex\n\nIt might be possible to measure the voltage of the wiper of the control device.\nHowever that might not always be easy or possible, due to voltage used, etc.\n\n\n## Future\n\n#### Must\n\n- update documentation\n  - concept of **read()** =\u003e put 2 X9C parallel and read one with analogRead().\n  \n#### Should\n\n- test different platforms\n- investigate and test **store()**\n\n#### Could\n\n- add error codes ?\n- add examples\n- test multiple devices configuration\n- would ohm in float be more precise/accurate?\n  - especially for the 1K?\n  - how exact is this device, does it make sense, linear enough?\n\n#### Wont\n\n- voltage divider example\n- in the constructor rename **Ohm** parameter to value? \n  - The potentiometer can be used as a voltage divider (see above)\n    so a better parameter name could be the anonymous **value**.\n  - **getOhm()** ==\u003e **getValue()**\n  - **getMaxOhm()** ==\u003e **getMaxValue()**\n  - think milliVolt, ohm, lux, speed, etc. \n    User can do this too with **getPosition() \\* factor**\n\n\n## Support\n\nIf you appreciate my libraries, you can support the development and maintenance.\nImprove the quality of the libraries by providing issues and Pull Requests, or\ndonate through PayPal or GitHub sponsors.\n\nThank you,\n\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FRobTillaart%2FX9C10X","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FRobTillaart%2FX9C10X","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FRobTillaart%2FX9C10X/lists"}