Mon, 27 Mar 2023 22:13:21 +0200
Added esp-idf-lib for a lot of sensors. Added the basic design for the BMP280 task.
--- a/CMakeLists.txt Sun Mar 26 22:22:45 2023 +0200 +++ b/CMakeLists.txt Mon Mar 27 22:13:21 2023 +0200 @@ -4,5 +4,9 @@ # CMakeLists in this exact order for cmake to work correctly cmake_minimum_required(VERSION 3.16) +set(PROJECT_VER "0.0.1") +set(PROJECT_NAME "iotbalkon") + +set(EXTRA_COMPONENT_DIRS esp-idf-lib/components) include($ENV{IDF_PATH}/tools/cmake/project.cmake) project(iotbalkon)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/CHANGELOG.md Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,248 @@ +# Changelog + +## v.0.9.2 + +### Features +- (ds3231): Added ds3231_get_squarewave_freq by @dizcza in https://github.com/UncleRus/esp-idf-lib/pull/447 +- (ads130e08): Driver for ADS130E08 ADC by @weslleymfd in https://github.com/UncleRus/esp-idf-lib/pull/462 +- (dps310): DPS310 driver by @trombik in https://github.com/UncleRus/esp-idf-lib/pull/463 + +### Bugfixes +- (pca9557): Fixed incorrect I2C address and register bug by @AxelLin in https://github.com/UncleRus/esp-idf-lib/pull/453 +- (max7219) Fix string bounds check by @UncleRus in https://github.com/UncleRus/esp-idf-lib/pull/471 +- (ci) Updated esp-idf versions by @trombik +- (ci) Updated actions/checkout to v3 by @trombik in https://github.com/UncleRus/esp-idf-lib/pull/476 +- (esp_idf_lib_helpers): Fixed ets_sys.h error by @UncleRus in https://github.com/UncleRus/esp-idf-lib/pull/479 +- (i2cdev): Fixed i2c param config and driver install order for esp32 target by @AxelLin in https://github.com/UncleRus/esp-idf-lib/pull/475 + +## New Contributors +* @AxelLin made their first contribution in https://github.com/UncleRus/esp-idf-lib/pull/453 +* @weslleymfd made their first contribution in https://github.com/UncleRus/esp-idf-lib/pull/462 + +### Documentation: https://esp-idf-lib.readthedocs.io/en/0.9.2/ + +## v.0.9.1 + +### Features +- (lc709203f): Driver for LC709203F battery fuel gauge by @jmpmscorp in https://github.com/UncleRus/esp-idf-lib/pull/433 +- (hmc5883l): Added support for HMC5983 by @dizcza in https://github.com/UncleRus/esp-idf-lib/pull/431 + +### Bugfixes +- (i2cdev): Fixed I2C driver reinstallation bug by @UncleRus in https://github.com/UncleRus/esp-idf-lib/pull/430 +- (chore): Updated io-console by @trombik in https://github.com/UncleRus/esp-idf-lib/pull/435 +- (doc): Reordered Usages of metadata in the project section by @trombik in https://github.com/UncleRus/esp-idf-lib/pull/439 +- (all): Fixed xprintf() format specifications in components and examples to support ESP-IDF v5 by @UncleRus +- (ci): Fixed generation of components list by @UncleRus in https://github.com/UncleRus/esp-idf-lib/pull/446 + +## New Contributors +- @jmpmscorp made their first contribution in https://github.com/UncleRus/esp-idf-lib/pull/433 + +### Documentation: https://esp-idf-lib.readthedocs.io/en/0.9.1/ + + +## v.0.9.0 + +### Features +- (docs) How to porting i2c libs by @dizcza in https://github.com/UncleRus/esp-idf-lib/pull/428 +- (sts21) Driver for STS21 temperature sensor by @UncleRus in https://github.com/UncleRus/esp-idf-lib/pull/326 +- (max31855) Driver for MAX31855 cold-junction compensated thermocouple-to-digital converter by @UncleRus in https://github.com/UncleRus/esp-idf-lib/pull/324 +- (ht16k33) Driver for HT16K33 LED driver by @chudsaviet in https://github.com/UncleRus/esp-idf-lib/pull/341 +- (ci) All-new awesome label-based workflow with auto-labeler by @trombik +- (ci) Introduced release-drafter GitHub Actions workflow by @trombik in https://github.com/UncleRus/esp-idf-lib/pull/338 +- (chore) Added clang-format options file by @UncleRus in https://github.com/UncleRus/esp-idf-lib/pull/421 +- (tca9548): Added example by @UncleRus in https://github.com/UncleRus/esp-idf-lib/pull/424 + +### Bugfixes +- (all) Fixed requirements in components using `esp_timer` by @EldritchJS in https://github.com/UncleRus/esp-idf-lib/pull/328 +- (lm75, pca9557) Fixed type casting warnings by @UncleRus in https://github.com/UncleRus/esp-idf-lib/pull/400 +- (ads111x) Fixed bug `in ads111x_is_busy()` (#418) by @UncleRus in https://github.com/UncleRus/esp-idf-lib/pull/419 + +## New Contributors +- @EldritchJS made their first contribution in https://github.com/UncleRus/esp-idf-lib/pull/328 +- @chudsaviet made their first contribution in https://github.com/UncleRus/esp-idf-lib/pull/341 + +### Documentation: https://esp-idf-lib.readthedocs.io/en/0.9.0/ + + +## v.0.8.3 + +### Changes that break compatibility +- ⚠ (bh1900nux, max31725, mcp23008, mcp23x17, mcp342x, mcp4725, pca9557, pcf8574, pcf8575, qmc5883l, sht3x, tca9548, tca95x5): The order of the arguments in `xxx_init_desc()` functions has been changed to bring all drivers to a common standard: instead of `xxx_init_desc(..., i2c_port_t port, uint8_t addr, ...)`, now `xxx_init_desc(..., uint8_t addr, i2c_port_t port, ...)`. Attention: both arguments are ints, so compiler will not throw an error when building your firmware without changing it! +- (common) Dropped support for ESP-IDF v3.x + +### Features +- (common) Added support for ESP32-C3 +- (hts221) Driver for HTS221 temperature and humidity sensor +- (hdc1000) Driver for HDC1000 temperature and humidity sensor +- (examples) Constant parameters in examples have been moved to Kconfig.projbuild files, so you can now setup examples by `idf.py menuconfig` or `make menuconfig` instead of modifying source code. +- (examples) Added README to all examples +- (hx711) Added function to read average data +- (bh1900nux) Added software reset function +- (ds3231) Added functions to set and get aging offset register +- (esp_idf_lib_helper): Moved ets_sys.h includes to the separate file + +### Bugfixes +- (i2cdev) Showing error name in error logging +- (color) Fixed narrowing conversion error +- (examples) Use SPI2_HOST in examples with ESP-IDF v4.x +- (rda5807) Replaced `ESP_LOGI()` to `ESP_LOGD()` +- (led_strip_spi) Fixed pointer arithmetic for esp8266 +- (led_strip_spi) Fixed "initialized field overwritten" warning +- (mcp960x) Fixed incorrect I2C addressing + +### Documentation: https://esp-idf-lib.readthedocs.io/en/0.8.3/ + +## v.0.8.2 + +### Features +- (max31865) Driver for MAX31865 resistance converter for platinum RTDs +- (pca9557) Driver for PCA9537/PCA9557 remote 4/8-bit I/O expanders for I2C-bus +- (ls7366r) Driver for LS7366R Quadrature Encoder Counter +- (ci) Added metadata support +- (bh1900nux) Driver for BH1900NUX temperature sensor + +### Bugfixes +- (scd30) Fixed type casting warning +- (ccs811) Fixed claculation bug in ccs811_set_environmental_data() +- (max7219) Fixed bug with chip number in 8x8 example +- (button) Added user context do descriptor +- (si7021) Fixed delay for SHT20 +- (led_strip_spi) Added brightness support +- (pcf8563) Fixed mdays/wdays bug +- (sht4x) Fixed bug in example +- (led_strip) Fixed memory leak when handling error in led_strip_init() + + +## v.0.8.1 + +### Features +- (button) Driver for GPIO button with debouncing +- (scd30) Driver for SCD30 CO₂ sensor +- (scd4x) Driver for SCD40/SCD41 miniature CO₂ sensor +- (aht) Driver for AHT10/AHT15/AHT20 temperature and humidity sensor + +### Bugfixes +- (mzh19b) Fixed bug with serial buffer length used by the driver +- (framebuffer) Used a mutex instead of flag, fixed "maybe-uninitialized" +- (sgp40) Multiple bugs fixed +- (sht4x) Added 10ms timout for reading serial number +- (rda5807m) Default I2C clock lowered, example added +- (color) Fix build with c++ +- (examples) Fixed build of examples for single core ESP32s (S2/C3) +- (ds18x20) Added split 18B20/18S20 reads to allow for use of DS18X20_ANY + without flubbing the conversion on an 18B20 +- (ds18x20) Added scratchpad write and copy commands + + +## v.0.8.0 + +### Features +- (wiegand) Wiegand protocol receiver +- (lib8tion) Math functions specifically designed for LED programming (port + from FastLED) +- (color) Common library for RGB and HSV colors (port from FastLED) +- (noise) Noise generation functions (port from FastLED) +- (framebuffer) RGB framebuffer and animation component +- (mhz19b) Added support for the MH-Z19B CO2 sensor +- (ci) Dropped support for ESP-IDF < v3.5 +- (ci) Preliminary and untested support for ESP32-C3 +- (ci) Support ESP8266 RTOS SDK v3.3 +- (doc) Added initial version of CONTRIBUTING.md + +### Bugfixes +- (sht3x) Fixed documentation +- (ds18x20) Fixed ds18x20_scan_devices(), new example +- (ultrasonic) Added more precise measurement functions +- (led_strip_spi) Fixed SPI buffer limitation for ESP8266 +- (led_strip) Fixed buffer overflow error for RGBW strips +- (led_strip) White component for RGBW strips now calculated automatically +- (led_strip) Fixed bug in led_strip_set_pixels() +- (led_strip) Added support for global brightness for ESP-IDF >= v4.4 +- (led_strip) Improved stability for WS2812B +- (refactoring) Updated component makefiles and CMakeLists +- (i2cdev) Added option to disable all mutexes + + +## v.0.7.3 + +### Features +- (led_strip_spi) #156 SPI-based driver for SK9822/APA102 LED strips +- (ds3502) #160 Driver for nonvolatile digital potentiometer DS3502 +- (sht4x) #165 Driver for SHT4x temperature and humidity sensor + +### Bugfixes +- (pca9685) b633f86 Speed-ups +- (max7219) #159 Add "minus" sign and fix maximum brightness + + +## v.0.7.2 + +### Features +- (tsl2591) #149 Driver for light-to-digital converter TSL2591 +- (sgp40) #137 Driver for SGP40 Indoor Air Quality Sensor +- (ccs811) #67 Driver for AMS CCS811 digital gas sensor + +### Bugfixes +- (ci) #147 Cache Espressif tools +- (ci) #155 Update v4.2 branch +- (led_strip) #153 Tweak led_strip example, add README +- (led_strip) #154 Fix range bug in led_strip_fill +- (sht3x, ...) #151 Typo corrections + SHT3x corrections and improvement +- (sht3x) #152 Fix periodic measurement +- (hx711) a2b9fc5 Fix incorrect spinlock usage +- (doc) Multiple fixes +- (pca9685) ce8f3fa Fix possible race condition + + +## v.0.7.1 + +### Features +- (mcp960x) #141 Driver + example for MCP960X/L0X/RL0X +- (tsys01) #142 Driver + example for TSYS01 + +### Bugfixes +- (qmc5883l) dd17522 Fix possible race condition +- (tca95x5) #144 Copy-paste error, add example +- (esp_idf_lib_helpers) #143 Invalid error message +- (tsl4531) c2e835d Fix possible race condition +- (sht3x) 8289262 Fix possible race confition in SS measurement, refactoring +- (bh1750, bmp180) d57488b Fix possible race condition + + +## v.0.7 + +### Features +- (ina260) #126 Driver for INA260 precision digital current and power monitor +- (rda5807m) #25 Driver for single-chip broadcast FM radio tuner RDA5807M +- (i2cdev) #138 I2C clock stertching support + + +## v.0.6.1 + +### Bugfixes +- (ina219) #100 Potential error in ina219_get_gain +- (bme680) #121 Pressure calculation for bme680 gives wrong results + + +## v.0.6 + +### Features +- (ci) #116 Port CI process from Travis CI to GitHub Actions +- (ci) Update CI build tools +- (ads111x) #117 Support of ADS101x on top ADS111x +- (led_strip) #120 Smart LED strips support + +### Bugfixes +- (ds1307) #110 wrong squarewave frequency returned +- (sht3x, hmc5883l, hx711) #118 SHT3x measurements fail after 72min +- (pca9685) d4f5e35 Fix full on/off +- (ina219) Typo fix + + +## v.0.5-beta + +### Features +- (mcp342x) #92 Driver for ADC MCP3426/MCP3427/MCP3428 + +### Bugfixes +- (ds1302) #97 Fix critical section exit
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/CONTRIBUTING.md Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,617 @@ +# How to contribute to `esp-idf-lib` + +## Table of Contents + +<!-- vim-markdown-toc GFM --> + +* [Possible contributions](#possible-contributions) + * [Submitting a bug report](#submitting-a-bug-report) + * [Submitting a fix](#submitting-a-fix) + * [Writing documentation](#writing-documentation) + * [Suggesting enhancements](#suggesting-enhancements) + * [Promoting the project](#promoting-the-project) + * [Writing code](#writing-code) +* [Development Life Cycle](#development-life-cycle) + * [Creating an Issue](#creating-an-issue) + * [Creating a feature branch in your fork and develop](#creating-a-feature-branch-in-your-fork-and-develop) + * [C Code style](#c-code-style) + * [`markdown` Code style](#markdown-code-style) + * [`git` branch name convention](#git-branch-name-convention) + * [Typical issues you will face in developments](#typical-issues-you-will-face-in-developments) + * [Writing a commit message](#writing-a-commit-message) + * [Updating README.md](#updating-readmemd) + * [Creating a Pull Request](#creating-a-pull-request) +* [Licenses](#licenses) + * [Acceptable licenses](#acceptable-licenses) + * [Acceptable license for new code](#acceptable-license-for-new-code) + * [Unacceptable licenses](#unacceptable-licenses) + +<!-- vim-markdown-toc --> + +## Possible contributions + +If you would like to contribute to `esp-idf-lib`, we would like to say _thank +you_. We appreciate your efforts and contributions. Here is possible +contributions you can make. + +* [Submitting a bug report](#submitting-a-bug-report) +* [Submitting a fix](#submitting-a-fix) +* [Writing documentation](#writing-documentation) +* [Suggesting enhancements](#suggesting-enhancements) +* [Promoting the project](#promoting-the-project) +* [Writing code](#writing-code) + +### Submitting a bug report + +In embedded device development, finding bugs is more difficult than in other +software development. There are too many uncontrollable factors: physical +environment, counterfeit IC chips, deviations in revisions and variations, +difficulties in automations. Even if the bug turned out to be not a bug, such +report is still appreciated as it is another proof that the code works as +expected in a different environment. + +Please include how to reproduce the bug in the Issue. The more context, the +better. For example: + +* The _full_ error message in text format and the entire code (comment with + ` ``` ` for short code, use [Gist](https://gist.github.com) for long code) +* The circuit diagram +* Captured signals by an oscilloscope or a signal analyser ([sigrok](https://sigrok.org/)) + +A question as a bug report is okay but we expect bug reporters to do their +homework. The homework include: + +* Reading the data sheets +* Reading [the official documentation of `esp-idf`](https://docs.espressif.com/projects/esp-idf) + (it's good, really) +* Understanding C language in general + +For introductory C tutorials, see: + +* [C Tutorial](https://www.tutorialspoint.com/cprogramming/) by + `Tutorialspoint` +* [C Programming](https://en.wikibooks.org/wiki/C_Programming) by + `Wikibooks` + +### Submitting a fix + +If you found a bug and a fix for it, please create a bug report before +creating a Pull Request unless the bug is subtle, typos, or easy to reproduce +and fix. Make sure to read [Development Life Cycle](#development-life-cycle) +as a Pull Request must meet the same standards documented in the section. + +A GitHub Actions workflow, +[pr-labeler-action](https://github.com/TimonVS/pr-labeler-action), is used to +label PRs by branch name. Your fix branch should have prefixes defined in +[.github/pr-labeler.yml](.github/pr-labeler.yml). Create a branch with one of +the prefixes. If you are fixing a bug, your branch name should be `bugfix-`. +The rest of branch name should be short, and descriptive. If the fix has +related issues, the branch name should include them. + +See also [`git` branch name convention](#git-branch-name-convention). + +```console +git checkout -b bugfix-issue-1 +``` + +Change the branch name before creating a PR if your branch name does not +follow the convention. + +```console +git branch --move bugfix-issue-1 +``` + +### Writing documentation + +Even if you are not an author of the code in the repository, you can write +documentation as a contribution. + +Creating and maintaining FAQ entries is one of great examples. Have you +encountered seemingly common issues while using a component? That might help +others. + +We encourage code authors to write documentation in the code so that the code +and the documentation is always synced. However, sometimes they are not. +Spotting such mistakes is always appreciated. + +Not all contributors are native English speakers. If you are, please let us +know ambiguity in the documentation, wrong usages of terms, and mistakes in +English grammar. For this case, please create a Pull Request (creating an +issue is optional). + +Create a branch that documents features, or fixes existing documentations. + +```console +git checkout -b doc-foo +``` + +See also [`git` branch name convention](#git-branch-name-convention). + +### Suggesting enhancements + +While we are not always able to write a driver for a chip, we still appreciate +a request for new driver. It is more likely to happen when: + +* the chip is _cool_ +* the chip is easily available +* the chip is affordable + +### Promoting the project + +If you find the project useful, we are interested in what you did with +`esp-idf-lib`, and _how_ you did it. + +* Writing a blog post about your porject with `esp-idf-lib` +* Mentioning the project in SNS + +### Writing code + +If you can write a driver for new chip, that would be great. Please read +[Development Life Cycle](#development-life-cycle). + +## Development Life Cycle + +In this section, a typical development life cycle is explained. + +### Creating an Issue + +If you are working on a new driver, or an existing one, please create an +Issue, and assign the Issue to yourself. + +`esp-idf-lib` aims at providing stable drivers for IC chips and general +components. IC chips are stable, in that a chip is manufactured for a long +time, retaining backward compatibilities. A driver for a chip usually requires +minimal maintenance once the driver becomes stable. However, network protocols, +graphics drivers, libraries for external services, are a moving-target. +Standards will change, external services will evolve, user expectations will +change, too. We think that such moving-targets should be maintained in a +dedicated repository. Do you think your code is a moving target? It might be +better to create a your own repository for the driver. If you are not sure, +ask in the Issue. + +### Creating a feature branch in your fork and develop + +_Feature branch workflow_ is adopted in our development. +[Git Feature Branch Workflow](https://www.atlassian.com/git/tutorials/comparing-workflows/feature-branch-workflow) +by `atlassian` explains the workflow in details. + +Fork the repository and clone it on your machine. +See [Fork a repo](https://docs.github.com/en/github/getting-started-with-github/fork-a-repo). + +Create a feature branch in your fork from the `master` branch. + +```console +git checkout master +``` + +Check out the feature branch. The feature branch name should start with +`feat-` or `feature-`. + +```console +git checkout -b feat-implement-foo +```` + +See also [`git` branch name convention](#git-branch-name-convention). + +Write your code. Test the code in your physical test environment. Commit your +changes and push them to your remote fork on GitHub. + +[`components/example`](components/example) has an example component, and +[`examples/example`](examples/example) has an example application for the +`example` component. + +```console +git add path/to/files +git commit -v +git push --set-upstream origin feat-implement-foo +```` + +See also [Writing a commit message](#writing-a-commit-message). + +At this point, our CI workflows will run to test the changes. The test +workflows include: + +* building the documentation, +* building all examples for all supported targets with all supported + `esp-idf` versions, and +* linting code and documentation + +You can see the test results in `Actions` page on your GitHub fork. To +merge your changes to `master` branch, all the tests must pass. + +Make sure you are working on the latest `master` of `esp-idf-lib`. To sync the +`master` in your fork and the latest `master` of `esp-idf-lib`, run: + +```console +git checkout master +git fetch upstream +git reset --hard upstream/master +``` + +If your branch has many commits, consider `git rebase` to reduce the number of +commits. This is especially useful when you are actively developing and the +commit history has many trial-and-error commits. + +```console +git checkout feat-implement-foo +git rebase -i master +git push -f +``` + +Note that `git rebase` rewrites the commit history. You should avoid `git +rebase` after you asked someone to review your code because the reviewer needs +additional steps to ensure the review result is included. + +### C Code style + +We use a style for source files based on [LLVM Coding Standards](https://llvm.org/docs/CodingStandards.html) +except some cases, notably brace wrapping. Here is a brief list of the styles. + +* Use `snake_case`, not `CamelCase` +* Use `SNAKE_CASE` in uppercase for macro name, e.g. `MACRO_NAME`. +* Use spaces. The indent width is four +* Use `\n`, or `LF` for line breaks +* Use `//` for inline comments. Use `/* */` for multi line comments after an + empty line +* Break before braces in *most cases* (functions, conditionals, control + statements, etc) +* Always check given arguments +* Always check return code, return value, or `errno` +* Return `esp_err_t` from functions where possible +* Document public functions, data types, and macros in header files +* Use suffix `_t` for `typedef`, e.g. `foo_t` +* Use suffix `_cb_t` for function `typedef`, e.g.`my_function_cb_t` +* Use suffix `_s` for `struct`, e.g. `my_struct_s` +* Wrap numbers in macro definition with parenthesis, e.g. `#define N_FOO (1)` +* Use `#include <foo.h> for headers that are not part of the component, such + as `string.h`, `esp_log,h`, and `i2cdev.h`. Use `#include "foo.h" when the + header is private, i.e. the header is the part of the component. + +The style should be followed for all new code. In general, code can be +considered "new code" when it makes up about 50% or more of the file(s) +involved. This is enough to break precedents in the existing code and use the +current style guidelines. + +See an example source files under [`components/exmaple`](components/example) +and, for complete rules, see [`.clang-format`](.clang-format) and the output +of `clang-format --dump-config`. + +New code will be tested in the CI, using `clang-format` (currently `LLVM` +version 10). + +To format your code without modifying the code, run: + +```console +clang-format10 components/example/example.c +``` + +To format your code in-place, run: + +```console +clang-format10 -i components/example/example.c +``` + +### `markdown` Code style + +We use [the default `markdownlint` rules](https://github.com/markdownlint/markdownlint/blob/master/docs/RULES.md) +with some non-defaults. Our style can be found in [`.mdlstyle.rb`](.mdlstyle.rb). + +| Rule | non-default options | +| ------------------------------------ | ---------------------------------------------- | +| `MD003` - Header style | use `#` for headers | +| `MD007` - Unordered list indentation | indent with 4 spaces | +| `MD013` - Line length | ignore line length in code blocks and tables | +| `MD024` - Multiple headers with the same content | `allow_different_nesting` is true | +| `MD029` - Ordered list item prefix | `style` is `ordered`, i.e. incremental numbers | + +In the CI, we use ruby version of `markdownlint`, or [`mdl`](https://rubygems.org/gems/mdl/) +gem, but [markdownlint for node.js](https://github.com/DavidAnson/markdownlint) +should also work. + +To test `markdown` style of a file, you need: + +* `ruby` 2.6 +* `bundler` 2.x + +```console +bundle install +bundle exec mdl path/to/file +``` + +The output shows path to the file, line number, and the rule. An example +output is shown below. + +```console +examples/led_strip_spi/README.md:30: MD040 Fenced code blocks should have a language specified +``` + +### `git` branch name convention + +We use the following convention for git branch name. Use one of branch name +prefixes when creating a branch. + +| Branch name prefix | Description | +|--------------------|-------------| +| `feat-`, and `feature-` | A feature branch that implements feature(s), or add enhancement(s) to existing code | +| `fix-`, and `bugfix-` | A bug fix branch that fixes bug(s). The rest of the branch name should include issue number, such as `fix-issue-1` | +| `ci-` | A branch that implements enhancement(s), or fixes issue(s) in CI | +| `chore-` | A branch that does not affect code or its behavior, such as updating `.gitignore` | +| `doc-`, and `documentation-` | Adding or updating documentation only, such as documenting undocumented features, or fixing existing documentation(s) | + +A GitHub Actions workflow automatically labels PRs depending on the branch +name prefixes so that the PR is automatically included in release notes. + +The rest of the branch name should be short, and descriptive. If your branch +fixes, implements, or relates to, an Issue, include the Issue number. Say, if +your branch fixes a bug reported Issue ${N}, the branch name should be +`fix-issue-${N}` so that reviewer immediately understand there is a related +Issue with your branch. Replace `${N}` with the Issue number, such as +`fix-issue-123` when the Issue number is 123. + +### Typical issues you will face in developments + +**Your code assumes a single target, such as `esp32`**. `esp-idf-lib` supports +other targets, notably `esp8266`. Make sure the driver supports various other +targets. If it cannot, such as the peripheral is not available on the target +chip, your code should bailout during the build by using `#error` C +preprocessor macro, and your driver must be excluded from the CI (TODO +document how). + +**Your code assumes a single SDK**. `esp-idf-lib` supports `master` and stable +versions of `esp-idf` and `ESP8266 RTOS SDK`. Generally, the SDKs retain +backward compatibilities, but sometimes not. Make sure to use `if` C +preprocessor macro to support different versions. [`esp_idf_lib_helpers`](components/esp_idf_lib_helpers) +component can help you. `ESP8266 RTOS SDK` shares many functions and +libraries, backported from `esp-idf`, but they are not identical. `I2C` +drivers written with [`i2cdev`](components/i2cdev) should work fine on ESP32 +and ESP8266, while SPI drivers need serious workarounds to support ESP8266. +[`led_strip_spi`](components/led_strip_spi) attempted to support both, but you +might want to write a different driver for each. + +**Your code assumes a single build method, such as `idf.py`**. Although `GNU make` +build method is considered as legacy, it is still a supported build method. +The CI builds your code twice; with `idf.py` and with `GNU make`. Both must be +successful. In ESP8266 RTOS SDK, `idf.py` is lagged behind from the one in +`esp-idf`. For ESP8266 target, the CI builds examples with `GNU make` only. + +Check return codes (most of functions in `esp-idf`), return values (e.g. +`malloc(3)`), or `errno` (e.g. some standard C functions). Propagate the +error by returning it from your function. An example: + +```c +#include <esp_err.h> +#include <esp_log.h> + +esp_err_t do_something() +{ + esp_err_t err; + + err = foo(); + if (err != ESP_OK) + { + ESP_LOGE("bar", "foo(): %s", esp_err_to_name(err)); + goto fail; + } +fail: + return err; +} +``` + +Note that newer `esp-idf` supports useful macros for error handling, such as +`ESP_GOTO_ON_ERROR` (see +[Error Handling](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/error-handling.html)), +but older versions do not have them yet. + +Check given arguments in functions, and return an appropriate error from one +of predefined errors (see +[Error Codes Reference](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/error-codes.html)). + +### Writing a commit message + +When you commit, prefix the first line of your commit message with `foo:`, +where `foo` is the name of component you are working on. Sometimes, it is not +possible because you are working on multiple components, i.e. fixing common +bugs in multiple components. In such cases, use `bugfix:`. Other commonly used +prefix words are: + +* `feature:` for features, or improvements, in multiple components +* `ci:` for fixes or improvements in the CI process +* `doc:` for fixes and improvements in the documentation + +These prefix words are for conventional purposes. Use common sense and make +the commit message clear so that others can understand what the change is. + +The rest of the first line should start with a verb. Examples: + +```text +foo: fix typos +``` + +```text +foo: resolve race condition in bar() +``` + +The first line should make sense when reading _"When you merge this, it will +`$THE_FIRST_LINE`"_. + +The second line of the commit message must be an empty line. + +In the rest of the commit message, write details of the change if necessary. +Explain what it does _and_ *why*. The lines in the commit message should be +limited to 72 characters or less where possible. + +Include a reference to an Issue when the commit fixes an Issue. + +```text +fixes #$ISSUE_NUMBER +``` + +When an Issue number or a Pull Request number is prefixed with certain +keywords, the referenced Issue or Pull Request will be closed. See [Linking a +pull request to an issue using a keyword](https://docs.github.com/en/github/managing-your-work-on-github/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword) +for the supported keywords. + +### Updating README.md + +Each component has a `.eil.yml` file in its component directory. The file is a +metadata file of the component. If you change the file, you need to update the +`README.md` in the project root directory. The `README.md` is generated from +the metadata and a template, `README.md.erb`. Generate `README.md` by: + +```console +bundle exec rake -C devtools readme > README.md +``` + +See also [`Metadata.md`](Metadata.md). + +### Creating a Pull Request + +To test your code, you need to create a Pull Request. It is not practical to +test code manually because you have to perform many tests. For instance, the +number of tests is all targets (`esp32`, `esp8266`, `esp32s2`, etc) * build +methods (`make` and `idf.py`) * supported `esp-idf` versions. Let the CI do it +for you. + +Before creating a Pull Request, make sure: + +1. You compiled the code and the build succeeded +1. Functions, macros, data types are documented in the code +1. An example application is provided under [`examples`](examples). In the + directory, create a directory `${COMPONENT_NAME}/default`. For instance, a + component `foo` must have `examples/foo/default`. Create an example + application in that directory. +1. Update [.github/labeler.yml](.github/labeler.yml). The component should + have a label for it. +1. You compiled the example code and the example application ran on a + physical device as expected and documented +1. All files are licensed under one of [Acceptable Licenses](#acceptable-licenses) + by including the license at the top of file +1. One of your commits in the feature branch, or the PR itself, mentions Issue + number so that the Issue will be automatically closed when the PR is merged + +When a PR is created, GitHub Actions workflows will: + +* label the PR with various labels, such as type of the PR (bug fix, or + feature) +* perform necessary tests depending on the changes (build the examples in a + matrix if the source code is modified, build the documentation if files + under `docs` are modified) + +After the CI processes complete, you will see "All checks have passed" or some +failures in the PR. To merge the PR, all checks must pass. Log is available +from the link, `Details`, in the failed test. + +If the PR does not pass the CI, update the branch with a fix. At this point, +`git rebase` may be used. For instance, if a commit has a typo and one of the +test fails because of syntax error, commit a fix of the syntax error and do +`git rebase` to merge the fix into the original commit that has introduced the +syntax error. + +```console +git add path/to/file +git commmit -v +git rebase -i master +``` + +`-i`, or `--interactive`, flag will launch a text editor where the history of +the branch can be edited with commands. The buffer of the editor will look +like: + +```text +pick f7f3f6d bugfix: fix issue foo +pick 310154e bugfix: fix a syntax error in f7f3f6d +``` + +The second commit, `310154e`, should be part of the previous, `f7f3f6d`. To +rewrite the commit history, replace `pick` with `fixup`. + +```text +pick f7f3f6d bugfix: fix issue foo +fixup 310154e bugfix: fix a syntax error in f7f3f6d +``` + +When you save and exit the editor, `git` rewrites the commit history as if +`310154e` was never committed. The commit `310154e` is now part of `f7f3f6d`. +If you don't save or modify the buffer, `git` will not rewrite the commit +history. + +`git rebase` can be used to tidy up the commits. To `rebase` or not to +`rebase` depends on the nature of commits. A single commit per PR is preferred, +but is not mandatory. + +See also +[7.6 Git Tools - Rewriting History](https://git-scm.com/book/en/v2/Git-Tools-Rewriting-History). + +When all the tests pass, ask the code owner to review the PR. The code owner +can be found in `.eli.yml` file in the component directory. From this point, +you should avoid to `git rebase` your feature branch. Otherwise, the reviewer +would have to review the PR from scratch. + +Developers who has write access to the repository will leave comments, ask +rewrites, and merge the PR. + +## Licenses + +We provide code that can be freely used, copied, modified, and distributed by +anyone and for any purpose. + +### Acceptable licenses + +We accept permissive licenses such as: + +* [ISC](https://spdx.org/licenses/ISC.html) License +* [MIT](https://spdx.org/licenses/MIT.html) License +* [BSD-2-Clause](https://spdx.org/licenses/BSD-2-Clause.html) License + +A list of licenses are available at +[SPDX License List](https://spdx.org/licenses/). + +### Acceptable license for new code + +New code is the one you own (you wrote it from scratch). The preferred +license to be applied to new code is a simplified ISC License. The license +must be included at the top in every files as long as practically possible. +The following is a preferred wording of the license. + +```c +/* + * SPDX-License-Identifier: ISC + * + * Copyright (c) YYYY YOUR NAME HERE <user@your.dom.ain> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + ``` + +Add `SPDX-License-Identifier: $YOUR_LICENSE` to your license header. +`$YOUR_LICENSE` is a SPDX License Identifier. + +* [ISC](https://spdx.org/licenses/ISC.html) +* [BSD-2-Clause](https://spdx.org/licenses/BSD-2-Clause.html) + +### Unacceptable licenses + +We do NOT accept `copyleft` licenses such as: + +* GPL License +* LGPL License +* GNU Affero General Public License (AGPL) + +We do NOT accept _long_ licenses. A license is considered as _long_ when +it has more than four clauses. + +We do NOT accept protective licenses that have additional restrictions, such +as: + +* Apache license version 2 or later +* various so-called _Shareware_ or _Freeware_
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/FAQ.md Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,145 @@ +# FAQ + +<!-- vim-markdown-toc GFM --> + +* [How to debug I2C-based drivers?](#how-to-debug-i2c-based-drivers) +* [Why are semaphores (mutexes) used in i2cdev routines?](#why-are-semaphores-mutexes-used-in-i2cdev-routines) +* [How can I connect multiple I2C devices?](#how-can-i-connect-multiple-i2c-devices) +* [How can I change frequency of I2C clock? At default frequency my device is unstable or not working at all.](#how-can-i-change-frequency-of-i2c-clock-at-default-frequency-my-device-is-unstable-or-not-working-at-all) +* [How to use internal pull-up resistors](#how-to-use-internal-pull-up-resistors) +* [Can I use I2C device drivers from interrupts?](#can-i-use-i2c-device-drivers-from-interrupts) +* [Porting I2C libs to I2Cdev](#porting-i2c-libs-to-i2cdev) + +<!-- vim-markdown-toc --> + +## How to debug I2C-based drivers? + +Common causes of I2C issues are: + +* wrong wiring +* wrong pull-up resistors +* wrong I2C address +* broken I2C module +* the driver has a bug + +When any of I2C-based drivers does not work, follow the steps below. + +Build an [_I2C scanner_ device](examples/i2c_scanner). The device is not +necessarily an ESP device. There are many examples for various platforms. +Search by keyword `i2c scanner`. + +Connect the I2C module to the I2C scanner device. Make sure appropriate +pull-up resistors are connected to `SCL` and `SDA` lines. + +Scan devices on the I2C bus. If the scanner does not find the I2C device, then +your wiring might have issues. If the scanner finds the I2C device, make sure +the address found is what the driver expects. If you have more than one same +I2C modules, try them all. + +If the scanner finds the I2C device and you are sure that the wiring is +correct, see the signals on the wire using an oscilloscope. Most oscilloscopes +can decode I2C signals and display I2C transactions in human-readable way. + +If the driver does not work after these steps, please [let us +know](https://github.com/UncleRus/esp-idf-lib/issues). + +## Why are semaphores (mutexes) used in i2cdev routines? + +i2cdev uses two types of mutexes: port and transactional. + +Port mutexes (there are only two of them, by the number of I2C ports) are +necessary to avoid problems in the situation mentioned in the documentation: + +> The I2C APIs are not thread-safe, if you want to use one I2C port in +different tasks, you need to take care of the multi-thread issue. + +They are taken before executing single I2C transactions and are released +immediately after them. + +Mutexes of the second type are created one per device and are necessary if +the same device is used in several tasks: + +```C +static i2c_dev_t some_dev; + +void task1(void *arg) +{ + // some_dev used here +} + +void task2(void *arg) +{ + // and here some_dev used too +} + +... +xTaskCreate(task1, "task1", ....); +xTaskCreate(task2, "task2", ....); + +``` + +These mutexes are used when single device operation requires several I2C +transactions in a row. + +## How can I connect multiple I2C devices? + +With i2cdev, you can use almost any way to connect I2C devices. + +For example, in the case of using SSD1306 and two MCP3428s, I would recommend +connecting them like this: + +* 2 GPIO outputs on ESP32 for dedicated screen connection via I2C bus 0 +* 2 GPIO outputs to the second I2C bus, to which 2 MCP3428 are connected with + different addresses. + +If you need to connect more than one sensor with the same addresses and there +are free GPIOs, then you can directly connect these sensors to individual GPIO +outputs instead of using the I2C multiplexer. i2cdev will take care of +reconfiguring I2C driver to the according outputs when you exchange data +with these devices. + +## How can I change frequency of I2C clock? At default frequency my device is unstable or not working at all. + +You can change the frequency after initializing the device handler, like this: + +```C +#include <esp_idf_lib_helpers.h> + +... + +ESP_ERROR_CHECK(ads111x_init_desc(&dev, addr, I2C_PORT, SDA_GPIO, SCL_GPIO)); + +#if HELPER_TARGET_IS_ESP32 + dev.cfg.master.clk_speed = 100000; // Hz +#endif + +ESP_ERROR_CHECK(ads111x_set_mode(&dev, ADS111X_MODE_CONTINUOUS)); // Continuous conversion mode +ESP_ERROR_CHECK(ads111x_set_data_rate(&dev, ADS111X_DATA_RATE_32)); // 32 samples per second +... +``` + +## How to use internal pull-up resistors + +Just enable them in `i2c_dev_t` config. For example: + +```C +... + +dev.cfg.scl_pullup_en = true; +dev.cfg.sda_pullup_en = true; +ESP_ERROR_CHECK(ads111x_init_desc(&dev, addr, I2C_PORT, SDA_GPIO, SCL_GPIO)); + +... +``` + +## Can I use I2C device drivers from interrupts? + +With default configuration you can't. Since the drivers use mutexes, this will +crash the system. But you can disable use of any I2C mutexes (both port and +device) in configuration: just enable CONFIG_I2CDEV_NOLOCK. Keep in mind that +after enabling this option all i2c device drivers will become non-thread safe. + + +## Porting I2C libs to I2Cdev + +See [porting.md](docs/porting.md).
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/Gemfile Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +source "https://rubygems.org" + +gem "irb" +gem "rake" +gem "rspec" +gem "rubocop" +gem "io-console", ">= 0.5.11"
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/Gemfile.lock Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,58 @@ +GEM + remote: https://rubygems.org/ + specs: + ast (2.4.2) + diff-lcs (1.4.4) + io-console (0.5.11) + irb (1.3.7) + reline (>= 0.2.7) + parallel (1.21.0) + parser (3.0.2.0) + ast (~> 2.4.1) + rainbow (3.0.0) + rake (13.0.6) + regexp_parser (2.1.1) + reline (0.2.7) + io-console (~> 0.5) + rexml (3.2.5) + rspec (3.10.0) + rspec-core (~> 3.10.0) + rspec-expectations (~> 3.10.0) + rspec-mocks (~> 3.10.0) + rspec-core (3.10.1) + rspec-support (~> 3.10.0) + rspec-expectations (3.10.1) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.10.0) + rspec-mocks (3.10.2) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.10.0) + rspec-support (3.10.3) + rubocop (1.22.3) + parallel (~> 1.10) + parser (>= 3.0.0.0) + rainbow (>= 2.2.2, < 4.0) + regexp_parser (>= 1.8, < 3.0) + rexml + rubocop-ast (>= 1.12.0, < 2.0) + ruby-progressbar (~> 1.7) + unicode-display_width (>= 1.4.0, < 3.0) + rubocop-ast (1.13.0) + parser (>= 3.0.1.1) + ruby-progressbar (1.11.0) + unicode-display_width (2.1.0) + +PLATFORMS + amd64-freebsd-14 + ruby + x86_64-linux + +DEPENDENCIES + io-console (>= 0.5.11) + irb + rake + rspec + rubocop + +BUNDLED WITH + 2.2.19
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/Metadata.md Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,309 @@ +# Metadata + +## Table of Contents + +<!-- vim-markdown-toc GFM --> + +* [Purpose](#purpose) +* [Files](#files) + * [`.eil.yml`](#eilyml) + * [`persons.yml`](#personsyml) + * [`groups.yml`](#groupsyml) + * [`targets.yml`](#targetsyml) +* [Resources](#resources) + * [Person](#person) + * [Target](#target) + * [License](#license) + * [Copyright](#copyright) + * [Group](#group) + * [Metadata](#metadata) + * [Component](#component) +* [Usages of metadata in the project](#usages-of-metadata-in-the-project) + * [Validating metadata of components](#validating-metadata-of-components) + * [Generating `README.md`](#generating-readmemd) +* [Known issues](#known-issues) + * [conditional `depends`](#conditional-depends) + +<!-- vim-markdown-toc --> + +This document describes metadata used in the project. The status of the +document is beta. + +## Purpose + +The purpose of metadata in the project is to automate works in development and +project management, to ensure the project policies, and to extend the project +to support third-party projects. + +## Files + +### `.eil.yml` + +The metadata file of a component. Each component must have `.eil.yml` in the +root directory of the component. The file format is YAML. + +An example path: `components/ads111x/.eil.yml`. + +### `persons.yml` + +`persons.yml` is a YAML file that contains a list of `Person`s. + +### `groups.yml` + +`groups.yml` is a YAML file that contains a list of `Group`s. + +### `targets.yml` + +`targets.yml` is a YAML file that contains a list of `Target`s. + +## Resources + +Resources defined here represents various objects used in the metadata. + +A resource has unique `name` as a primary key. + +When referring to a resource in another resource, use `name` as key and its +value to identify the resource. As a shorthand, you may use the name of a +resource as `String`. In this case, the value is assumed to be `name: $VALUE`. + +When a resource expects a `Person` as a value, + +```yaml +foo: + name: trombik +``` + +This is a shorthand version of the above example: + +```yaml +foo: trombik +``` + +### Person + +A `Person` represents a person. `Person` is used to describe a copyrights +holder and a code owner. A `Person` must be defined in `persons.yml` file. + +| Name | Type | Description | Required | +|------|------|-------------|----------| +| `name` | `String` | A unique ID string of the person. Use GitHub account or GitHub project if the person has one | Yes | +| `full_name` | `String` | Full name of the person or the project | No | +| `gh_id` | `String` | GitHub account name or project name | No | +| `email` | `String` | Email address of the person | No | +| `website` | `String` | Web site URL | No | + +When any of `gh_id`, `email`, or `website` is not available, `person` must +have a full name because it is used to identify the source of code. + +If the person does not have `gh_id`, use the full name for `name`. For example, +when the full name is "Foo Bar", use `name: FooB`. + +`Person` should have one or more of optional keys so that one can contact the +person. + +Examples: + +```yaml +name: trombik +gh_id: trombik +full_name: Tomoyuki Sakurai +email: y@trombik.org +website: https://github.com/trombik +``` + +```yaml +name: foo +full_name: Foo `bar` buz +# XXX other keys are optional, but strongly recommended. +``` + +### Target + +| Name | Type | Description | Required | +|------|------|-------------|----------| +| `name` | `String` | Name of the build target in `esp-idf`, or `esp8266`. | Yes | + +An example: + +```yaml +name: esp32 +``` + +### License + +| Name | Type | Description | Required | +|------|------|-------------|----------| +| `name` | `String` | SPDX License Identifier (see [the list of licenses](https://spdx.org/licenses/)) | Yes | + +An example: + +```yaml +name: BSD-3 +``` + +### Copyright + +| Name | Type | Description | Required | +|------|------|-------------|----------| +| `author` | `Person` | Copyrights holder. See also `Person`. | No | +| `name` | `String` | The value of `name` of `Person`. A shorthand for `author` | No | +| `year` | `Integer` | Registration year of the copyrights | Yes | + +`Copyright` must have only one of `author` and `name`, not both. + +Examples: + +```yaml +name: trombik +year: 2021 +``` + +The above example is a shorthand form of: + +```yaml +author: + name: trombik +year: 2021 +``` + +### Group + +A `Group` represents a group of `Component`s. A `Group` must be in +`groups.yml`. + +| Name | Type | Description | Required | +|------|------|-------------|----------| +| `name` | `String` | A unique ID of the group | Yes | +| `description` | `String` | Description of the group | Yes | + +`name` should be short, and memorable. Use `-` as a word separator. It must +not include spaces (`[0-9a-zA-Z-]+` in regular expression). + +An example: + +```yaml +name: adc-dac +description: ADC/DAC libraries +``` + +### Metadata + +`Metadata` is the content of `.eil.yml`. `Metadata` includes non-empty list of +`Component` under `components` top level key. + +An example: + +```yaml +--- +components: + - name: foo + # ... other keys go here ... +``` + +### Component + +| Name | Type | Description | Required | +|------|------|-------------|----------| +| `name` | `String` | The name of the component. Must be unique. | Yes | +| `description` | `String` | A short description of the component. | Yes | +| `group` | `Group` | The primary group name of the component. | Yes | +| `groups` | A list of `Group` | A list of zero or more of `Group` | No | +| `code_owners` | A list of `Person` | A list of one or more of `Person` | Yes | +| `depends` | A list of `Component` | Zero or more of `component` that the component depends on | No | +| `thread_safe` | `Strnig` | One of `yes`, `no`, and `N/A` | Yes | +| `targets` | A list of `Target` | One or more of supported `target` | Yes | +| `licenses` | A list of `License` | One or more of licenses used in the component | Yes | +| `copyrights` | A list of `Copyright` | One or more of copyright holder | Yes | + +FIXME `depends` must be a list because some drivers have conditional `REQUIRES` +in `CMakeLists.txt`. + +## Usages of metadata in the project + +The current implementation uses `ruby` and `rspec` ruby gem to validate +metadata in all components, and generate `README.md`. + +Requirements are: + +* `ruby` 2.7 (other version should also work) +* [`bundler`](https://bundler.io/) + +After installing requirements, run: + +```console +bundle install +``` + +### Validating metadata of components + +To validate metadata, run: + +```console +bundle exec rake -C devtools rspec +``` + +The implementation uses `rspec` to validate metadata because: + +1. the output is readable +2. requires less `ruby` knowledge to maintain the spec than validating + everything in ruby code +3. porting tests to other languages is easier than porting ruby code + +Under `spec` directory, there are: + +* `spec_helper.rb`, which is a helper for the test +* `*_spec.rb`, which is a test script +* other ruby files, such as `person.rb`, which are class definitions used in + the test + +The ruby classes for the test validate minimum requirements only, such as the +`.eil.yml` file exists, or a resource has a required primary key. Actual +test should be performed in `*_spec.rb` files. + +### Generating `README.md` + +`README.md` is generated from the metadata and `README.md.erb`. To update +`README.md`, run the following command at the repository root directory: + +```console +bundle exec rake -C devtools readme > README.md +``` + +## Known issues + +### conditional `depends` + +Some `CMakeLists.txt` conditionally sets `REQUIRES`. `depends` does not handle +the following case. + +```yaml +# for esp32 +depends: + - name: driver + - name: freertos + - name: log +``` + +```yaml +# for esp8266 +depends: + - name: esp8266 + - name: freertos + - name: log +``` + +A possible solution: + +```yaml +depends: + - name: driver + target: + - name: esp32 + - name: esp32s2 + - name: esp32c3 + - name: esp8266 + target: + - name: esp8266 + - name: freertos + - name: log +```
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/README.md Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,312 @@ +# ESP-IDF Components library + +[![Build Status](https://github.com/UncleRus/esp-idf-lib/workflows/Build%20examples/badge.svg)](https://github.com/UncleRus/esp-idf-lib/actions?query=workflow%3A%22Build+examples%22) +[![Build the documentation](https://github.com/UncleRus/esp-idf-lib/workflows/Build%20the%20documentation/badge.svg)](https://github.com/UncleRus/esp-idf-lib/actions?query=workflow%3A%22Build+the+documentation%22) +[![Docs Status](https://readthedocs.org/projects/esp-idf-lib/badge/?version=latest&style=flat)](https://esp-idf-lib.readthedocs.io/en/latest/) + +Components for Espressif ESP32 [ESP-IDF framework](https://github.com/espressif/esp-idf) +and [ESP8266 RTOS SDK](https://github.com/espressif/ESP8266_RTOS_SDK). + +Part of them ported from [esp-open-rtos](https://github.com/SuperHouse/esp-open-rtos). + +## Supported versions of frameworks and devices + +| Chip | Framework | Versions +|----------------|--------------------|----------------------- +| ESP32 | ESP-IDF | All officially supported versions (see [Support Period Policy](https://github.com/espressif/esp-idf/blob/master/SUPPORT_POLICY.md)) and `master` +| ESP32-S2 *[1]* | ESP-IDF | All officially supported versions and `master` +| ESP32-C3 *[1]* | ESP-IDF | All officially supported versions and `master` +| ESP8266 *[2]* | ESP8266 RTOS SDK | `master`, v3.4 + +[1] *Use "`idf.py set-target esp32s2`" or "`idf.py set-target esp32c3`" before "`idf.py menuconfig`" to change +the chip type.* + +[2] *Due to the incompatibility of ESP8266 drivers and hardware, some +libraries are not* *supported on ESP8266 (see "ESP8266" column in the tables).* + +## How to use + +### ESP32 + +Clone this repository somewhere, e.g.: + +```Shell +cd ~/myprojects/esp +git clone https://github.com/UncleRus/esp-idf-lib.git +``` + +Add path to components in your [project makefile](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/build-system-legacy.html), +e.g: + +```Makefile +PROJECT_NAME := my-esp-project +EXTRA_COMPONENT_DIRS := /home/user/myprojects/esp/esp-idf-lib/components +include $(IDF_PATH)/make/project.mk +``` + +or in [CMakeLists.txt](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/build-system.html): + +```CMake +cmake_minimum_required(VERSION 3.5) +set(EXTRA_COMPONENT_DIRS /home/user/myprojects/esp/esp-idf-lib/components) +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(my-esp-project) +``` + +or with CMake [FetchContent](https://cmake.org/cmake/help/latest/module/FetchContent.html) + +```CMake +cmake_minimum_required(VERSION 3.11) +include(FetchContent) +FetchContent_Declare( + espidflib + GIT_REPOSITORY https://github.com/UncleRus/esp-idf-lib.git +) +FetchContent_MakeAvailable(espidflib) +set(EXTRA_COMPONENT_DIRS ${espidflib_SOURCE_DIR}/components) +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(my-esp-project) +``` + +### ESP8266 RTOS SDK + +Clone this repository somewhere, e.g.: + +```Shell +cd ~/myprojects/esp +git clone https://github.com/UncleRus/esp-idf-lib.git +``` + +Add path to components in your [project makefile](https://docs.espressif.com/projects/esp8266-rtos-sdk/en/latest/api-guides/build-system.html), +e.g: + +```Makefile +PROJECT_NAME := my-esp-project +EXTRA_COMPONENT_DIRS := /home/user/myprojects/esp/esp-idf-lib/components +EXCLUDE_COMPONENTS := max7219 mcp23x17 led_strip max31865 ls7366r max31855 +include $(IDF_PATH)/make/project.mk +``` + +See [GitHub examples](https://github.com/UncleRus/esp-idf-lib/tree/master/examples) +or [GitLab examples](https://gitlab.com/UncleRus/esp-idf-lib/tree/master/examples). + +## Documentation + +- [Documentation](https://esp-idf-lib.readthedocs.io/en/latest/) +- [Frequently asked questions](FAQ.md) + +## Components + +### ADC/DAC libraries + +| Component | Description | License | Supported on | Thread safety +|--------------------------|----------------------------------------------------------------------------------|---------|--------------------|-------------- +| **ads111x** | Driver for ADS1113/ADS1114/ADS1115 and ADS1013/ADS1014/ADS1015 I2C ADC | BSD-3 | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | Yes +| **ads130e08** | Driver for ADS130E08 ADC | MIT | `esp32`, `esp32s3` | Yes +| **hx711** | Driver for HX711 24-bit ADC for weigh scales | BSD-3 | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | No +| **mcp342x** | Driver for 18-Bit, delta-sigma ADC MCP3426/MCP3427/MCP3428 | BSD-3 | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | Yes +| **mcp4725** | Driver for 12-bit DAC MCP4725 | BSD-3 | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | Yes +| **pcf8591** | Driver for 8-bit ADC and an 8-bit DAC PCF8591 | BSD-3 | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | Yes + +### Air quality sensors + +| Component | Description | License | Supported on | Thread safety +|--------------------------|----------------------------------------------------------------------------------|---------|--------------------|-------------- +| **ccs811** | Driver for AMS CCS811 digital gas sensor | BSD-3 | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | Yes +| **mhz19b** | Driver for MH-Z19B NDIR CO₂ sensor | BSD-3 | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | No +| **scd30** | Driver for SCD30 CO₂ sensor | BSD-3 | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | Yes +| **scd4x** | Driver for SCD40/SCD41 miniature CO₂ sensor | BSD-3 | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | Yes +| **sgp40** | Driver for SGP40 Indoor Air Quality Sensor for VOC Measurements | BSD-3 | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | Yes + +### Common libraries + +| Component | Description | License | Supported on | Thread safety +|--------------------------|----------------------------------------------------------------------------------|---------|--------------------|-------------- +| **color** | Common library for RGB and HSV colors | MIT | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | Yes +| **esp_idf_lib_helpers** | Common support library for esp-idf-lib | ISC | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | Yes +| **framebuffer** | RGB framebuffer component | MIT | `esp32`, `esp32s2`, `esp32c3` | Yes +| **i2cdev** | ESP-IDF I2C master thread-safe utilities | MIT | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | Yes +| **lib8tion** | Math functions specifically designed for LED programming | MIT | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | Yes +| **noise** | Noise generation functions | MIT | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | Yes +| **onewire** | Bit-banging 1-Wire driver | MIT | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | No + +### Current and power sensors + +| Component | Description | License | Supported on | Thread safety +|--------------------------|----------------------------------------------------------------------------------|---------|--------------------|-------------- +| **ina219** | Driver for INA219/INA220 bidirectional current/power monitor | BSD-3 | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | Yes +| **ina260** | Driver for INA260 precision digital current and power monitor | BSD-3 | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | Yes +| **ina3221** | Driver for INA3221 shunt and bus voltage monitor | ISC | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | Yes + +### GPIO expanders + +| Component | Description | License | Supported on | Thread safety +|--------------------------|----------------------------------------------------------------------------------|---------|--------------------|-------------- +| **mcp23008** | Driver for 8-bit I2C GPIO expander MCP23008 | BSD-3 | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | Yes +| **mcp23x17** | Driver for I2C/SPI 16 bit GPIO expanders MCP23017/MCP23S17 | BSD-3 | `esp32`, `esp32s2`, `esp32c3` | Yes +| **pca9557** | Driver for PCA9537/PCA9557/TCA9534 remote 4/8-bit I/O expanders for I2C-bus | BSD-3 | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | Yes +| **pcf8574** | Driver for PCF8574 remote 8-bit I/O expander for I2C-bus | MIT | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | Yes +| **pcf8575** | Driver for PCF8575 remote 16-bit I/O expander for I2C-bus | MIT | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | Yes +| **tca95x5** | Driver for TCA9535/TCA9555 remote 16-bit I/O expanders for I2C-bus | BSD-3 | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | Yes + +### Gas sensors + +| Component | Description | License | Supported on | Thread safety +|--------------------------|----------------------------------------------------------------------------------|---------|--------------------|-------------- +| **ccs811** | Driver for AMS CCS811 digital gas sensor | BSD-3 | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | Yes +| **mhz19b** | Driver for MH-Z19B NDIR CO₂ sensor | BSD-3 | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | No +| **scd30** | Driver for SCD30 CO₂ sensor | BSD-3 | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | Yes +| **scd4x** | Driver for SCD40/SCD41 miniature CO₂ sensor | BSD-3 | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | Yes + +### Humidity sensors + +| Component | Description | License | Supported on | Thread safety +|--------------------------|----------------------------------------------------------------------------------|---------|--------------------|-------------- +| **aht** | Driver for AHT10/AHT15/AHT20 temperature and humidity sensor | BSD-3 | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | Yes +| **bme680** | Driver for BME680 digital environmental sensor | BSD-3 | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | Yes +| **dht** | Driver for DHT11, AM2301 (DHT21, DHT22, AM2302, AM2321), Itead Si7021 | BSD-3 | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | No +| **hdc1000** | Driver for HDC1000 temperature and humidity sensor | BSD-3 | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | Yes +| **hts221** | Driver for HTS221 temperature and humidity sensor. | ISC | `esp32`, `esp32s2`, `esp32c3` | Yes +| **sht3x** | Driver for Sensirion SHT30/SHT31/SHT35 digital temperature and humidity sensor | BSD-3 | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | Yes +| **sht4x** | Driver for Sensirion SHT40/SHT41/SHT45 digital temperature and humidity sensor | BSD-3 | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | Yes +| **si7021** | Driver for Si7013/Si7020/Si7021/HTU2xD/SHT2x and compatible temperature and humidity sensors | BSD-3 | `esp32`, `esp32c3`, `esp8266`, `esp32s2`, `esp32c3` | Yes + +### Inertial measurement units + +| Component | Description | License | Supported on | Thread safety +|--------------------------|----------------------------------------------------------------------------------|---------|--------------------|-------------- +| **icm42670** | Driver for TDK ICM-42670-P 6-Axis IMU (found on ESP-RS board, https://github.com/esp-rs/esp-rust-board) | ICS | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | Yes + +### Input device drivers + +| Component | Description | License | Supported on | Thread safety +|--------------------------|----------------------------------------------------------------------------------|---------|--------------------|-------------- +| **button** | HW timer-based driver for GPIO buttons | MIT | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | Yes +| **encoder** | HW timer-based driver for incremental rotary encoders | BSD-3 | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | Yes +| **ls7366r** | Driver for LS7366R Quadrature Encoder Counter | MIT | `esp32`, `esp32s2`, `esp32c3` | Yes + +### LED drivers + +| Component | Description | License | Supported on | Thread safety +|--------------------------|----------------------------------------------------------------------------------|---------|--------------------|-------------- +| **ht16k33** | HT16K33 LED controller driver | MIT | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | Yes +| **led_strip** | RMT-based driver for WS2812B/SK6812/APA106/SM16703 LED strips | MIT | `esp32`, `esp32s2`, `esp32c3` | Yes +| **led_strip_spi** | SPI-based driver for SK9822/APA102 LED strips | MIT | `esp32`, `esp32c3`, `esp8266`, `esp32s2`, `esp32c3` | Yes +| **max7219** | Driver for 8-Digit LED display drivers, MAX7219/MAX7221 | BSD-3 | `esp32`, `esp32s2`, `esp32c3` | Yes + +### Light sensors + +| Component | Description | License | Supported on | Thread safety +|--------------------------|----------------------------------------------------------------------------------|---------|--------------------|-------------- +| **bh1750** | Driver for BH1750 light sensor | BSD-3 | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | Yes +| **tsl2561** | Driver for light-to-digital converter TSL2561 | BSD-3 | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | Yes +| **tsl2591** | Driver for light-to-digital converter TSL2591 | MIT | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | Yes +| **tsl4531** | Driver for digital ambient light sensor TSL4531 | BSD-3 | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | Yes +| **veml7700** | Driver for VEML7700 ambient light sensor | ISC | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | Yes + +### Magnetic sensors + +| Component | Description | License | Supported on | Thread safety +|--------------------------|----------------------------------------------------------------------------------|---------|--------------------|-------------- +| **hmc5883l** | Driver for 3-axis digital compass HMC5883L and HMC5983L | BSD-3 | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | Yes +| **qmc5883l** | Driver for QMC5883L 3-axis magnetic sensor | BSD-3 | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | Yes + +### Other misc libraries + +| Component | Description | License | Supported on | Thread safety +|--------------------------|----------------------------------------------------------------------------------|---------|--------------------|-------------- +| **ds3502** | Driver for nonvolatile digital potentiometer DS3502 | BSD-3 | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | Yes +| **example** | An example component | ISC | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | Yes +| **hd44780** | Driver for HD44780 compatible LCD text displays | BSD-3 | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | No +| **lc709203f** | Driver for LC709203F battery fuel gauge | ISC | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | Yes +| **pca9685** | Driver for 16-channel, 12-bit PWM PCA9685 | BSD-3 | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | Yes +| **rda5807m** | Driver for single-chip broadcast FM radio tuner RDA5807M | BSD-3 | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | Yes +| **tca9548** | Driver for TCA9548A/PCA9548A low-voltage 8-channel I2C switch | BSD-3 | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | Yes +| **tda74xx** | Driver for TDA7439/TDA7439DS/TDA7440D audioprocessors | MIT | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | Yes +| **ultrasonic** | Driver for ultrasonic range meters, e.g. HC-SR04, HY-SRF05 | BSD-3 | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | No +| **wiegand** | Wiegand protocol receiver | BSD-3 | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | No + +### Pressure sensors + +| Component | Description | License | Supported on | Thread safety +|--------------------------|----------------------------------------------------------------------------------|---------|--------------------|-------------- +| **bme680** | Driver for BME680 digital environmental sensor | BSD-3 | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | Yes +| **bmp180** | Driver for BMP180 digital pressure sensor | MIT | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | Yes +| **bmp280** | Driver for BMP280/BME280 digital pressure sensor | MIT | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | Yes +| **dps310** | Driver for DPS310 barometric pressure sensor | ISC | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | Yes +| **ms5611** | Driver for barometic pressure sensor MS5611-01BA03 | BSD-3 | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | Yes + +### Real-time clocks + +| Component | Description | License | Supported on | Thread safety +|--------------------------|----------------------------------------------------------------------------------|---------|--------------------|-------------- +| **ds1302** | Driver for DS1302 RTC module | BSD-3 | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | No +| **ds1307** | Driver for DS1307 RTC module | BSD-3 | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | Yes +| **ds3231** | Driver for DS1337 RTC and DS3231 high precision RTC module | MIT | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | Yes +| **pcf8563** | Driver for PCF8563 real-time clock/calendar | BSD-3 | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | Yes + +### Temperature sensors + +| Component | Description | License | Supported on | Thread safety +|--------------------------|----------------------------------------------------------------------------------|---------|--------------------|-------------- +| **aht** | Driver for AHT10/AHT15/AHT20 temperature and humidity sensor | BSD-3 | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | Yes +| **bh1900nux** | Driver for BH1900NUX temperature sensor | BSD-3 | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | Yes +| **bme680** | Driver for BME680 digital environmental sensor | BSD-3 | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | Yes +| **bmp180** | Driver for BMP180 digital pressure sensor | MIT | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | Yes +| **bmp280** | Driver for BMP280/BME280 digital pressure sensor | MIT | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | Yes +| **dht** | Driver for DHT11, AM2301 (DHT21, DHT22, AM2302, AM2321), Itead Si7021 | BSD-3 | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | No +| **dps310** | Driver for DPS310 barometric pressure sensor | ISC | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | Yes +| **ds18x20** | Driver for DS18B20/DS18S20 families of 1-Wire temperature sensor ICs | BSD-3 | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | No +| **hdc1000** | Driver for HDC1000 temperature and humidity sensor | BSD-3 | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | Yes +| **hts221** | Driver for HTS221 temperature and humidity sensor. | ISC | `esp32`, `esp32s2`, `esp32c3` | Yes +| **lm75** | Driver for LM75, a digital temperature sensor and thermal watchdog | BSD-3 | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | Yes +| **max31725** | Driver for MAX31725/MAX31726 temperature sensors | BSD-3 | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | Yes +| **max31855** | Driver for MAX31855 cold-junction compensated thermocouple-to-digital converter | BSD-3 | `esp32`, `esp32s2`, `esp32c3` | Yes +| **max31865** | Driver for MAX31865 resistance converter for platinum RTDs | BSD-3 | `esp32`, `esp32s2`, `esp32c3` | Yes +| **mcp960x** | Driver for MCP9600/MCP9601, thermocouple EMF to temperature converter | BSD-3 | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | Yes +| **mcp9808** | Driver for MCP9808 Digital Temperature Sensor | BSD-3 | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | Yes +| **ms5611** | Driver for barometic pressure sensor MS5611-01BA03 | BSD-3 | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | Yes +| **sht3x** | Driver for Sensirion SHT30/SHT31/SHT35 digital temperature and humidity sensor | BSD-3 | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | Yes +| **sht4x** | Driver for Sensirion SHT40/SHT41/SHT45 digital temperature and humidity sensor | BSD-3 | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | Yes +| **si7021** | Driver for Si7013/Si7020/Si7021/HTU2xD/SHT2x and compatible temperature and humidity sensors | BSD-3 | `esp32`, `esp32c3`, `esp8266`, `esp32s2`, `esp32c3` | Yes +| **sts21** | Driver for STS21 temperature sensor | BSD-3 | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | Yes +| **tsys01** | Driver for precision digital temperature sensor TSYS01 | BSD-3 | `esp32`, `esp8266`, `esp32s2`, `esp32c3` | Yes + +## Library maintainers + +- [Ruslan V. Uss](https://github.com/UncleRus) +- [Tomoyuki Sakurai](https://github.com/trombik) + +## Credits + +- [Tomoyuki Sakurai](https://github.com/trombik), developer of the LM75 and + SK9822/APA102 drivers, author of the RTOS SDK ESP82666 support, master CI +- [Gunar Schorcht](https://github.com/gschorcht), developer of SHT3x, BME680 + and CCS811 drivers +- [Brian Schwind](https://github.com/bschwind), developer of TS2561 and + TSL4531 drivers +- [Andrej Krutak](https://github.com/andree182), developer of BH1750 driver +- Frank Bargstedt, developer of BMP180 driver +- [sheinz](https://github.com/sheinz), developer of BMP280 driver +- [Jonathan Hartsuiker](https://github.com/jsuiker), developer of DHT driver +- [Grzegorz Hetman](https://github.com/hetii), developer of DS18B20 driver +- [Alex Stewart](https://github.com/astewart-consensus), developer of DS18B20 driver +- [Richard A Burton](mailto:richardaburton@gmail.com), developer of DS3231 driver +- [Bhuvanchandra DV](https://github.com/bhuvanchandra), developer of DS3231 driver +- [Zaltora](https://github.com/Zaltora), developer of INA3231 driver +- [Bernhard Guillon](https://gitlab.com/mrnice), developer of MS5611-01BA03 driver +- [Pham Ngoc Thanh](https://github.com/panoti), developer of PCF8591 driver +- [Lucio Tarantino](https://github.com/dianlight), developer of ADS111x driver +- [Julian Dörner](https://github.com/juliandoerner), developer of TSL2591 driver +- [FastLED community](https://github.com/FastLED), developers of `lib8tion`, + `color` and `noise` libraries +- [Erriez](https://github.com/Erriez), developer of MH-Z19B driver +- [David Douard](https://github.com/douardda), developer of MH-Z19B driver +- [Nate Usher](https://github.com/nated0g), developer of SCD30 driver +- [Josh Kallus](https://github.com/Jkallus), developer of LS7366R driver +- [saasaa](https://github.com/saasaa), developer of HTS221 driver +- [Timofei Korostelev](https://github.com/chudsaviet), developer of HT16K33 driver +- [Jose Manuel Perez](https://github.com/jmpmscorp), developer of LC709203F driver +- [Weslley Duarte](https://github.com/weslleymfd), developer of ADS130E08 driver +- [Jan Veeh](https://github.com/janveeh), developer of ICM42670 driver +- [Marc Luehr](https://github.com/Th3Link), developer of VEML7700 driver
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/ads111x/.eil.yml Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,26 @@ +--- +components: + - name: ads111x + description: | + Driver for ADS1113/ADS1114/ADS1115 and ADS1013/ADS1014/ADS1015 I2C ADC + group: adc-dac + groups: [] + code_owners: UncleRus + depends: + - name: i2cdev + - name: log + - name: esp_idf_lib_helpers + thread_safe: yes + targets: + - name: esp32 + - name: esp8266 + - name: esp32s2 + - name: esp32c3 + licenses: + - name: BSD-3 + copyrights: + - author: + name: UncleRus + year: 2016 + - name: dianlight + year: 2020
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/ads111x/CMakeLists.txt Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,5 @@ +idf_component_register( + SRCS ads111x.c + INCLUDE_DIRS . + REQUIRES i2cdev log esp_idf_lib_helpers +)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/ads111x/LICENSE Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,26 @@ +Copyright (c) 2016, 2018 Ruslan V. Uss <unclerus@gmail.com> + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, +this list of conditions and the following disclaimer in the documentation +and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of itscontributors +may be used to endorse or promote products derived from this software without +specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/ads111x/ads111x.c Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,354 @@ +/* + * Copyright (c) 2016 Ruslan V. Uss <unclerus@gmail.com> + * Copyright (c) 2020 Lucio Tarantino <https://github.com/dianlight> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of itscontributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file ads111x.c + * + * ESP-IDF driver for ADS1113/ADS1114/ADS1115, ADS1013/ADS1014/ADS1015 I2C ADC + * + * Ported from esp-open-rtos + * + * Copyright (c) 2016, 2018 Ruslan V. Uss <unclerus@gmail.com> + * Copyright (c) 2020 Lucio Tarantino <https://github.com/dianlight> + * + * BSD Licensed as described in the file LICENSE + */ + +#include <esp_log.h> +#include <esp_idf_lib_helpers.h> +#include "ads111x.h" + +#define I2C_FREQ_HZ 1000000 // Max 1MHz for esp32 + +#define REG_CONVERSION 0 +#define REG_CONFIG 1 +#define REG_THRESH_L 2 +#define REG_THRESH_H 3 + +#define COMP_QUE_OFFSET 1 +#define COMP_QUE_MASK 0x03 +#define COMP_LAT_OFFSET 2 +#define COMP_LAT_MASK 0x01 +#define COMP_POL_OFFSET 3 +#define COMP_POL_MASK 0x01 +#define COMP_MODE_OFFSET 4 +#define COMP_MODE_MASK 0x01 +#define DR_OFFSET 5 +#define DR_MASK 0x07 +#define MODE_OFFSET 8 +#define MODE_MASK 0x01 +#define PGA_OFFSET 9 +#define PGA_MASK 0x07 +#define MUX_OFFSET 12 +#define MUX_MASK 0x07 +#define OS_OFFSET 15 +#define OS_MASK 0x01 + +#define CHECK(x) do { esp_err_t __; if ((__ = x) != ESP_OK) return __; } while (0) +#define CHECK_ARG(VAL) do { if (!(VAL)) return ESP_ERR_INVALID_ARG; } while (0) + +static const char *TAG = "ads111x"; + +const float ads111x_gain_values[] = { + [ADS111X_GAIN_6V144] = 6.144, + [ADS111X_GAIN_4V096] = 4.096, + [ADS111X_GAIN_2V048] = 2.048, + [ADS111X_GAIN_1V024] = 1.024, + [ADS111X_GAIN_0V512] = 0.512, + [ADS111X_GAIN_0V256] = 0.256, + [ADS111X_GAIN_0V256_2] = 0.256, + [ADS111X_GAIN_0V256_3] = 0.256 +}; + +static esp_err_t read_reg(i2c_dev_t *dev, uint8_t reg, uint16_t *val) +{ + uint8_t buf[2]; + esp_err_t res; + if ((res = i2c_dev_read_reg(dev, reg, buf, 2)) != ESP_OK) + { + ESP_LOGE(TAG, "Could not read from register 0x%02x", reg); + return res; + } + *val = (buf[0] << 8) | buf[1]; + + return ESP_OK; +} + +static esp_err_t write_reg(i2c_dev_t *dev, uint8_t reg, uint16_t val) +{ + uint8_t buf[2] = { val >> 8, val }; + esp_err_t res; + if ((res = i2c_dev_write_reg(dev, reg, buf, 2)) != ESP_OK) + { + ESP_LOGE(TAG, "Could not write 0x%04x to register 0x%02x", val, reg); + return res; + } + + return ESP_OK; +} + +static esp_err_t read_conf_bits(i2c_dev_t *dev, uint8_t offs, uint16_t mask, + uint16_t *bits) +{ + CHECK_ARG(dev); + + uint16_t val; + + I2C_DEV_TAKE_MUTEX(dev); + I2C_DEV_CHECK(dev, read_reg(dev, REG_CONFIG, &val)); + I2C_DEV_GIVE_MUTEX(dev); + + ESP_LOGD(TAG, "Got config value: 0x%04x", val); + + *bits = (val >> offs) & mask; + + return ESP_OK; +} + +static esp_err_t write_conf_bits(i2c_dev_t *dev, uint16_t val, uint8_t offs, + uint16_t mask) +{ + CHECK_ARG(dev); + + uint16_t old; + + I2C_DEV_TAKE_MUTEX(dev); + I2C_DEV_CHECK(dev, read_reg(dev, REG_CONFIG, &old)); + I2C_DEV_CHECK(dev, write_reg(dev, REG_CONFIG, (old & ~(mask << offs)) | (val << offs))); + I2C_DEV_GIVE_MUTEX(dev); + + return ESP_OK; +} + +#define READ_CONFIG(OFFS, MASK, VAR) do { \ + CHECK_ARG(VAR); \ + uint16_t bits; \ + CHECK(read_conf_bits(dev, OFFS, MASK, &bits)); \ + *VAR = bits; \ + return ESP_OK; \ + } while(0) + + +/////////////////////////////////////////////////////////////////////////////// + +esp_err_t ads111x_init_desc(i2c_dev_t *dev, uint8_t addr, i2c_port_t port, + gpio_num_t sda_gpio, gpio_num_t scl_gpio) +{ + CHECK_ARG(dev); + + if (addr != ADS111X_ADDR_GND && addr != ADS111X_ADDR_VCC + && addr != ADS111X_ADDR_SDA && addr != ADS111X_ADDR_SCL) + { + ESP_LOGE(TAG, "Invalid I2C address"); + return ESP_ERR_INVALID_ARG; + } + + dev->port = port; + dev->addr = addr; + dev->cfg.sda_io_num = sda_gpio; + dev->cfg.scl_io_num = scl_gpio; +#if HELPER_TARGET_IS_ESP32 + dev->cfg.master.clk_speed = I2C_FREQ_HZ; +#endif + return i2c_dev_create_mutex(dev); +} + +esp_err_t ads111x_free_desc(i2c_dev_t *dev) +{ + CHECK_ARG(dev); + + return i2c_dev_delete_mutex(dev); +} + +esp_err_t ads111x_is_busy(i2c_dev_t *dev, bool *busy) +{ + CHECK_ARG(dev && busy); + + uint16_t r; + CHECK(read_conf_bits(dev, OS_OFFSET, OS_MASK, &r)); + *busy = !r; + + return ESP_OK; +} + +esp_err_t ads111x_start_conversion(i2c_dev_t *dev) +{ + return write_conf_bits(dev, 1, OS_OFFSET, OS_MASK); +} + +esp_err_t ads111x_get_value(i2c_dev_t *dev, int16_t *value) +{ + CHECK_ARG(dev && value); + + I2C_DEV_TAKE_MUTEX(dev); + I2C_DEV_CHECK(dev, read_reg(dev, REG_CONVERSION, (uint16_t *)value)); + I2C_DEV_GIVE_MUTEX(dev); + + return ESP_OK; +} + +esp_err_t ads101x_get_value(i2c_dev_t *dev, int16_t *value) +{ + CHECK_ARG(dev && value); + + I2C_DEV_TAKE_MUTEX(dev); + I2C_DEV_CHECK(dev, read_reg(dev, REG_CONVERSION, (uint16_t *)value)); + I2C_DEV_GIVE_MUTEX(dev); + + *value = *value >> 4; + if (*value > 0x07FF) + { + // negative number - extend the sign to 16th bit + *value |= 0xF000; + } + return ESP_OK; +} + +esp_err_t ads111x_get_gain(i2c_dev_t *dev, ads111x_gain_t *gain) +{ + READ_CONFIG(PGA_OFFSET, PGA_MASK, gain); +} + +esp_err_t ads111x_set_gain(i2c_dev_t *dev, ads111x_gain_t gain) +{ + return write_conf_bits(dev, gain, PGA_OFFSET, PGA_MASK); +} + +esp_err_t ads111x_get_input_mux(i2c_dev_t *dev, ads111x_mux_t *mux) +{ + READ_CONFIG(MUX_OFFSET, MUX_MASK, mux); +} + +esp_err_t ads111x_set_input_mux(i2c_dev_t *dev, ads111x_mux_t mux) +{ + return write_conf_bits(dev, mux, MUX_OFFSET, MUX_MASK); +} + +esp_err_t ads111x_get_mode(i2c_dev_t *dev, ads111x_mode_t *mode) +{ + READ_CONFIG(MODE_OFFSET, MODE_MASK, mode); +} + +esp_err_t ads111x_set_mode(i2c_dev_t *dev, ads111x_mode_t mode) +{ + return write_conf_bits(dev, mode, MODE_OFFSET, MODE_MASK); +} + +esp_err_t ads111x_get_data_rate(i2c_dev_t *dev, ads111x_data_rate_t *rate) +{ + READ_CONFIG(DR_OFFSET, DR_MASK, rate); +} + +esp_err_t ads111x_set_data_rate(i2c_dev_t *dev, ads111x_data_rate_t rate) +{ + return write_conf_bits(dev, rate, DR_OFFSET, DR_MASK); +} + +esp_err_t ads111x_get_comp_mode(i2c_dev_t *dev, ads111x_comp_mode_t *mode) +{ + READ_CONFIG(COMP_MODE_OFFSET, COMP_MODE_MASK, mode); +} + +esp_err_t ads111x_set_comp_mode(i2c_dev_t *dev, ads111x_comp_mode_t mode) +{ + return write_conf_bits(dev, mode, COMP_MODE_OFFSET, COMP_MODE_MASK); +} + +esp_err_t ads111x_get_comp_polarity(i2c_dev_t *dev, ads111x_comp_polarity_t *polarity) +{ + READ_CONFIG(COMP_POL_OFFSET, COMP_POL_MASK, polarity); +} + +esp_err_t ads111x_set_comp_polarity(i2c_dev_t *dev, ads111x_comp_polarity_t polarity) +{ + return write_conf_bits(dev, polarity, COMP_POL_OFFSET, COMP_POL_MASK); +} + +esp_err_t ads111x_get_comp_latch(i2c_dev_t *dev, ads111x_comp_latch_t *latch) +{ + READ_CONFIG(COMP_LAT_OFFSET, COMP_LAT_MASK, latch); +} + +esp_err_t ads111x_set_comp_latch(i2c_dev_t *dev, ads111x_comp_latch_t latch) +{ + return write_conf_bits(dev, latch, COMP_LAT_OFFSET, COMP_LAT_MASK); +} + +esp_err_t ads111x_get_comp_queue(i2c_dev_t *dev, ads111x_comp_queue_t *queue) +{ + READ_CONFIG(COMP_QUE_OFFSET, COMP_QUE_MASK, queue); +} + +esp_err_t ads111x_set_comp_queue(i2c_dev_t *dev, ads111x_comp_queue_t queue) +{ + return write_conf_bits(dev, queue, COMP_QUE_OFFSET, COMP_QUE_MASK); +} + +esp_err_t ads111x_get_comp_low_thresh(i2c_dev_t *dev, int16_t *th) +{ + CHECK_ARG(dev && th); + + I2C_DEV_TAKE_MUTEX(dev); + I2C_DEV_CHECK(dev, read_reg(dev, REG_THRESH_L, (uint16_t *)th)); + I2C_DEV_GIVE_MUTEX(dev); + + return ESP_OK; +} + +esp_err_t ads111x_set_comp_low_thresh(i2c_dev_t *dev, int16_t th) +{ + CHECK_ARG(dev); + + I2C_DEV_TAKE_MUTEX(dev); + I2C_DEV_CHECK(dev, write_reg(dev, REG_THRESH_L, th)); + I2C_DEV_GIVE_MUTEX(dev); + + return ESP_OK; +} + +esp_err_t ads111x_get_comp_high_thresh(i2c_dev_t *dev, int16_t *th) +{ + CHECK_ARG(dev && th); + + I2C_DEV_TAKE_MUTEX(dev); + I2C_DEV_CHECK(dev, read_reg(dev, REG_THRESH_H, (uint16_t *)th)); + I2C_DEV_GIVE_MUTEX(dev); + + return ESP_OK; +} + +esp_err_t ads111x_set_comp_high_thresh(i2c_dev_t *dev, int16_t th) +{ + CHECK_ARG(dev); + + I2C_DEV_TAKE_MUTEX(dev); + I2C_DEV_CHECK(dev, write_reg(dev, REG_THRESH_H, th)); + I2C_DEV_GIVE_MUTEX(dev); + + return ESP_OK; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/ads111x/ads111x.h Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,439 @@ +/* + * Copyright (c) 2016 Ruslan V. Uss <unclerus@gmail.com> + * Copyright (c) 2020 Lucio Tarantino <https://github.com/dianlight> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of itscontributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file ads111x.h + * @defgroup ads111x ads111x + * @{ + * + * ESP-IDF driver for ADS1113/ADS1114/ADS1115, ADS1013/ADS1014/ADS1015 I2C ADC + * + * Ported from esp-open-rtos + * + * Copyright (c) 2016, 2018 Ruslan V. Uss <unclerus@gmail.com> + * Copyright (c) 2020 Lucio Tarantino <https://github.com/dianlight> + * + * BSD Licensed as described in the file LICENSE + */ +#ifndef __ADS111X_H__ +#define __ADS111X_H__ + +#include <stdbool.h> +#include <esp_err.h> +#include <i2cdev.h> + +#ifdef __cplusplus +extern "C" { +#endif + +#define ADS111X_ADDR_GND 0x48 //!< I2C device address with ADDR pin connected to ground +#define ADS111X_ADDR_VCC 0x49 //!< I2C device address with ADDR pin connected to VCC +#define ADS111X_ADDR_SDA 0x4a //!< I2C device address with ADDR pin connected to SDA +#define ADS111X_ADDR_SCL 0x4b //!< I2C device address with ADDR pin connected to SCL + +#define ADS111X_MAX_VALUE 0x7fff //!< Maximum ADC value +#define ADS101X_MAX_VALUE 0x7ff + +// ADS101X overrides +#define ADS101X_DATA_RATE_128 ADS111X_DATA_RATE_8 +#define ADS101X_DATA_RATE_250 ADS111X_DATA_RATE_16 +#define ADS101X_DATA_RATE_490 ADS111X_DATA_RATE_32 +#define ADS101X_DATA_RATE_920 ADS111X_DATA_RATE_64 +#define ADS101X_DATA_RATE_1600 ADS111X_DATA_RATE_128 +#define ADS101X_DATA_RATE_2400 ADS111X_DATA_RATE_250 +#define ADS101X_DATA_RATE_3300 ADS111X_DATA_RATE_475 + +/** + * Gain amplifier + */ +typedef enum +{ + ADS111X_GAIN_6V144 = 0, //!< +-6.144V + ADS111X_GAIN_4V096, //!< +-4.096V + ADS111X_GAIN_2V048, //!< +-2.048V (default) + ADS111X_GAIN_1V024, //!< +-1.024V + ADS111X_GAIN_0V512, //!< +-0.512V + ADS111X_GAIN_0V256, //!< +-0.256V + ADS111X_GAIN_0V256_2, //!< +-0.256V (same as ADS111X_GAIN_0V256) + ADS111X_GAIN_0V256_3, //!< +-0.256V (same as ADS111X_GAIN_0V256) +} ads111x_gain_t; + +/** + * Gain amplifier values + */ +extern const float ads111x_gain_values[]; + +/** + * Input multiplexer configuration (ADS1115 only) + */ +typedef enum +{ + ADS111X_MUX_0_1 = 0, //!< positive = AIN0, negative = AIN1 (default) + ADS111X_MUX_0_3, //!< positive = AIN0, negative = AIN3 + ADS111X_MUX_1_3, //!< positive = AIN1, negative = AIN3 + ADS111X_MUX_2_3, //!< positive = AIN2, negative = AIN3 + ADS111X_MUX_0_GND, //!< positive = AIN0, negative = GND + ADS111X_MUX_1_GND, //!< positive = AIN1, negative = GND + ADS111X_MUX_2_GND, //!< positive = AIN2, negative = GND + ADS111X_MUX_3_GND, //!< positive = AIN3, negative = GND +} ads111x_mux_t; + +/** + * Data rate + */ +typedef enum +{ + ADS111X_DATA_RATE_8 = 0, //!< 8 samples per second + ADS111X_DATA_RATE_16, //!< 16 samples per second + ADS111X_DATA_RATE_32, //!< 32 samples per second + ADS111X_DATA_RATE_64, //!< 64 samples per second + ADS111X_DATA_RATE_128, //!< 128 samples per second (default) + ADS111X_DATA_RATE_250, //!< 250 samples per second + ADS111X_DATA_RATE_475, //!< 475 samples per second + ADS111X_DATA_RATE_860 //!< 860 samples per second +} ads111x_data_rate_t; + +/** + * Operational mode + */ +typedef enum +{ + ADS111X_MODE_CONTINUOUS = 0, //!< Continuous conversion mode + ADS111X_MODE_SINGLE_SHOT //!< Power-down single-shot mode (default) +} ads111x_mode_t; + +/** + * Comparator mode (ADS1114 and ADS1115 only) + */ +typedef enum +{ + ADS111X_COMP_MODE_NORMAL = 0, //!< Traditional comparator with hysteresis (default) + ADS111X_COMP_MODE_WINDOW //!< Window comparator +} ads111x_comp_mode_t; + +/** + * Comparator polarity (ADS1114 and ADS1115 only) + */ +typedef enum +{ + ADS111X_COMP_POLARITY_LOW = 0, //!< Active low (default) + ADS111X_COMP_POLARITY_HIGH //!< Active high +} ads111x_comp_polarity_t; + +/** + * Comparator latch (ADS1114 and ADS1115 only) + */ +typedef enum +{ + ADS111X_COMP_LATCH_DISABLED = 0, //!< Non-latching comparator (default) + ADS111X_COMP_LATCH_ENABLED //!< Latching comparator +} ads111x_comp_latch_t; + +/** + * Comparator queue + */ +typedef enum +{ + ADS111X_COMP_QUEUE_1 = 0, //!< Assert ALERT/RDY pin after one conversion + ADS111X_COMP_QUEUE_2, //!< Assert ALERT/RDY pin after two conversions + ADS111X_COMP_QUEUE_4, //!< Assert ALERT/RDY pin after four conversions + ADS111X_COMP_QUEUE_DISABLED //!< Disable comparator (default) +} ads111x_comp_queue_t; + +/** + * @brief Initialize device descriptor + * + * @param dev Device descriptor + * @param addr Device address + * @param port I2C port number + * @param sda_gpio GPIO pin for SDA + * @param scl_gpio GPIO pin for SCL + * @return `ESP_OK` on success + */ +esp_err_t ads111x_init_desc(i2c_dev_t *dev, uint8_t addr, i2c_port_t port, + gpio_num_t sda_gpio, gpio_num_t scl_gpio); + +/** + * @brief Free device descriptor + * + * @param dev Device descriptor + * @return `ESP_OK` on success + */ +esp_err_t ads111x_free_desc(i2c_dev_t *dev); + +/** + * @brief Get device operational status + * + * @param dev Device descriptor + * @param[out] busy True when device performing conversion + * @return `ESP_OK` on success + */ +esp_err_t ads111x_is_busy(i2c_dev_t *dev, bool *busy); + +/** + * @brief Begin a single conversion + * + * Only in single-shot mode. + * + * @param dev Device descriptor + * @return `ESP_OK` on success + */ +esp_err_t ads111x_start_conversion(i2c_dev_t *dev); + +/** + * @brief Read last conversion result + * + * @param dev Device descriptor + * @param[out] value Last conversion result + * @return `ESP_OK` on success + */ +esp_err_t ads111x_get_value(i2c_dev_t *dev, int16_t *value); + +/** + * @brief Read last conversion result for ADS101x + * + * @param dev Device descriptor + * @param[out] value Last conversion result + * @return `ESP_OK` on success + */ +esp_err_t ads101x_get_value(i2c_dev_t *dev, int16_t *value); + +/** + * @brief Read the programmable gain amplifier configuration + * + * ADS1114 and ADS1115 only. + * Use ::ads111x_gain_values[] for real voltage. + * + * @param dev Device descriptor + * @param[out] gain Gain value + * @return `ESP_OK` on success + */ +esp_err_t ads111x_get_gain(i2c_dev_t *dev, ads111x_gain_t *gain); + +/** + * @brief Configure the programmable gain amplifier + * + * ADS1114 and ADS1115 only. + * + * @param dev Device descriptor + * @param gain Gain value + * @return `ESP_OK` on success + */ +esp_err_t ads111x_set_gain(i2c_dev_t *dev, ads111x_gain_t gain); + +/** + * @brief Read the input multiplexer configuration + * + * ADS1115 only. + * + * @param dev Device descriptor + * @param[out] mux Input multiplexer configuration + * @return `ESP_OK` on success + */ +esp_err_t ads111x_get_input_mux(i2c_dev_t *dev, ads111x_mux_t *mux); + +/** + * @brief Configure the input multiplexer configuration + * + * ADS1115 only. + * + * @param dev Device descriptor + * @param mux Input multiplexer configuration + * @return `ESP_OK` on success + */ +esp_err_t ads111x_set_input_mux(i2c_dev_t *dev, ads111x_mux_t mux); + +/** + * @brief Read the device operating mode + * + * @param dev Device descriptor + * @param[out] mode Device operating mode + * @return `ESP_OK` on success + */ +esp_err_t ads111x_get_mode(i2c_dev_t *dev, ads111x_mode_t *mode); + +/** + * @brief Set the device operating mode + * + * @param dev Device descriptor + * @param mode Device operating mode + * @return `ESP_OK` on success + */ +esp_err_t ads111x_set_mode(i2c_dev_t *dev, ads111x_mode_t mode); + +/** + * @brief Read the data rate + * + * @param dev Device descriptor + * @param[out] rate Data rate + * @return `ESP_OK` on success + */ +esp_err_t ads111x_get_data_rate(i2c_dev_t *dev, ads111x_data_rate_t *rate); + +/** + * @brief Configure the data rate + * + * @param dev Device descriptor + * @param rate Data rate + * @return `ESP_OK` on success + */ +esp_err_t ads111x_set_data_rate(i2c_dev_t *dev, ads111x_data_rate_t rate); + +/** + * @brief Get comparator mode + * + * ADS1114 and ADS1115 only. + * + * @param dev Device descriptor + * @param[out] mode Comparator mode + * @return `ESP_OK` on success + */ +esp_err_t ads111x_get_comp_mode(i2c_dev_t *dev, ads111x_comp_mode_t *mode); + +/** + * @brief Set comparator mode + * + * ADS1114 and ADS1115 only. + * + * @param dev Device descriptor + * @param mode Comparator mode + * @return `ESP_OK` on success + */ +esp_err_t ads111x_set_comp_mode(i2c_dev_t *dev, ads111x_comp_mode_t mode); + +/** + * @brief Get polarity of the comparator output pin ALERT/RDY + * + * ADS1114 and ADS1115 only. + * + * @param dev Device descriptor + * @param[out] polarity Comparator output pin polarity + * @return `ESP_OK` on success + */ +esp_err_t ads111x_get_comp_polarity(i2c_dev_t *dev, ads111x_comp_polarity_t *polarity); + +/** + * @brief Set polarity of the comparator output pin ALERT/RDY + * + * ADS1114 and ADS1115 only. + * + * @param dev Device descriptor + * @param polarity Comparator output pin polarity + * @return `ESP_OK` on success + */ +esp_err_t ads111x_set_comp_polarity(i2c_dev_t *dev, ads111x_comp_polarity_t polarity); + +/** + * @brief Get comparator output latch mode + * + * ADS1114 and ADS1115 only. + * + * @param dev Device descriptor + * @param[out] latch Comparator output latch mode + * @return `ESP_OK` on success + */ +esp_err_t ads111x_get_comp_latch(i2c_dev_t *dev, ads111x_comp_latch_t *latch); + +/** + * @brief Set comparator output latch mode + * + * ADS1114 and ADS1115 only. + * + * @param dev Device descriptor + * @param latch Comparator output latch mode + * @return `ESP_OK` on success + */ +esp_err_t ads111x_set_comp_latch(i2c_dev_t *dev, ads111x_comp_latch_t latch); + +/** + * @brief Get comparator queue size + * + * Get number of the comparator conversions before pin ALERT/RDY + * assertion. ADS1114 and ADS1115 only. + * + * @param dev Device descriptor + * @param[out] queue Number of the comparator conversions + * @return `ESP_OK` on success + */ +esp_err_t ads111x_get_comp_queue(i2c_dev_t *dev, ads111x_comp_queue_t *queue); + +/** + * @brief Set comparator queue size + * + * Set number of the comparator conversions before pin ALERT/RDY + * assertion or disable comparator. ADS1114 and ADS1115 only. + * + * @param dev Device descriptor + * @param queue Number of the comparator conversions + * @return `ESP_OK` on success + */ +esp_err_t ads111x_set_comp_queue(i2c_dev_t *dev, ads111x_comp_queue_t queue); + +/** + * @brief Get the lower threshold value used by comparator + * + * @param dev Device descriptor + * @param[out] th Lower threshold value + * @return `ESP_OK` on success + */ +esp_err_t ads111x_get_comp_low_thresh(i2c_dev_t *dev, int16_t *th); + +/** + * @brief Set the lower threshold value used by comparator + * + * @param dev Device descriptor + * @param th Lower threshold value + * @return `ESP_OK` on success + */ +esp_err_t ads111x_set_comp_low_thresh(i2c_dev_t *dev, int16_t th); + +/** + * @brief Get the upper threshold value used by comparator + * + * @param dev Device descriptor + * @param[out] th Upper threshold value + * @return `ESP_OK` on success + */ +esp_err_t ads111x_get_comp_high_thresh(i2c_dev_t *dev, int16_t *th); + +/** + * @brief Set the upper threshold value used by comparator + * + * @param dev Device descriptor + * @param th Upper threshold value + * @return `ESP_OK` on success + */ +esp_err_t ads111x_set_comp_high_thresh(i2c_dev_t *dev, int16_t th); + +#ifdef __cplusplus +} +#endif + +/**@}*/ + +#endif /* __ADS111X_H__ */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/ads111x/component.mk Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,2 @@ +COMPONENT_ADD_INCLUDEDIRS = . +COMPONENT_DEPENDS = i2cdev log esp_idf_lib_helpers
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/ads130e08/.eil.yml Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,20 @@ +--- +components: + - name: ads130e08 + description: Driver for ADS130E08 ADC + group: adc-dac + groups: [] + code_owners: weslleymfd + depends: + - driver + - log + thread_safe: yes + targets: + - name: esp32 + - name: esp32s3 + licenses: + - name: MIT + copyrights: + - author: + name: weslleymfd + year: 2021
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/ads130e08/CMakeLists.txt Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,5 @@ +idf_component_register( + SRCS "ads130e08.c" + INCLUDE_DIRS "." + REQUIRES driver log +)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/ads130e08/LICENSE Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2021 Weslley M. F. Duarte <weslleymfd@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/ads130e08/ads130e08.c Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,627 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2021 Weslley M. F. Duarte <weslleymfd@gmail.com> + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +/** + * @file ads130e08.c + * + * ESP-IDF driver for ADS130E08 ADC + * + * Copyright (c) 2021 Weslley M. F. Duarte <weslleymfd@gmail.com> + * + * MIT Licensed as described in the file LICENSE + */ + +#include "ads130e08.h" +#include <esp_log.h> +#include <freertos/FreeRTOS.h> +#include <freertos/task.h> +#include <string.h> + +#define ADS130E08_CMD_WAKEUP (0x02) +#define ADS130E08_CMD_STANDBY (0x04) +#define ADS130E08_CMD_RESET (0x06) +#define ADS130E08_CMD_START (0x08) +#define ADS130E08_CMD_STOP (0x0A) +#define ADS130E08_CMD_RDATAC (0x10) +#define ADS130E08_CMD_SDATAC (0x11) +#define ADS130E08_CMD_RDATA (0x12) +#define ADS130E08_CMD_RREG (0x20) +#define ADS130E08_CMD_WREG (0x40) + +#define ADS130E08_REG_ID (0x00) +#define ADS130E08_REG_CONFIG1 (0x01) +#define ADS130E08_REG_CONFIG2 (0x02) +#define ADS130E08_REG_CONFIG3 (0x03) +#define ADS130E08_REG_FAULT (0x04) +#define ADS130E08_REG_CH1SET (0x05) +#define ADS130E08_REG_CH2SET (0x06) +#define ADS130E08_REG_CH3SET (0x07) +#define ADS130E08_REG_CH4SET (0x08) +#define ADS130E08_REG_CH5SET (0x09) +#define ADS130E08_REG_CH6SET (0x0A) +#define ADS130E08_REG_CH7SET (0x0B) +#define ADS130E08_REG_CH8SET (0x0C) +#define ADS130E08_REG_FAULT_STATP (0x12) +#define ADS130E08_REG_FAULT_STATN (0x13) +#define ADS130E08_REG_GPIO (0x14) + +#define ID_MASK_LOW_BITS (0x08) +#define ID_MASK_HIGH_BITS (0x10) + +#define CONFIG1_MASK_LOW_BITS (0xDE) +#define CONFIG1_MASK_HIGH_BITS (0x01) +#define CONFIG1_MASK_BITS_CLK_EN BIT(5) + +#define CONFIG2_MASK_LOW_BITS (0x88) +#define CONFIG2_MASK_HIGH_BITS (0x60) +#define CONFIG2_MASK_BITS_INT_TEST BIT(4) +#define CONFIG2_MASK_BITS_TEST_AMP BIT(2) +#define CONFIG2_MASK_BITS_TEST_FREQ (BIT(1) | BIT(0)) + +#define CONFIG3_MASK_LOW_BITS (0x13) +#define CONFIG3_MASK_HIGH_BITS (0x40) +#define CONFIG3_MASK_BITS_PD_REFBUF BIT(7) +#define CONFIG3_MASK_BITS_VREF_4V BIT(5) +#define CONFIG3_MASK_BITS_OPAMP_REF BIT(3) +#define CONFIG3_MASK_BITS_PD_OPAMP BIT(2) + +#define FAULT_MASK_LOW_BITS (0x1F) +#define FAULT_MASK_BITS_COMP_TH (BIT(7) | BIT(6) | BIT(5)) + +#define CHnSET_MASK_LOW_BITS (0x08) +#define CHnSET_MASK_BITS_PD BIT(7) +#define CHnSET_MASK_BITS_PGA (BIT(6) | BIT(5) | BIT(4)) +#define CHnSET_MASK_BITS_MUX (BIT(2) | BIT(1) | BIT(0)) + +#define MASK_BITS_IN1P_FAULT BIT(0) +#define MASK_BITS_IN2P_FAULT BIT(1) +#define MASK_BITS_IN3P_FAULT BIT(2) +#define MASK_BITS_IN4P_FAULT BIT(3) +#define MASK_BITS_IN5P_FAULT BIT(4) +#define MASK_BITS_IN6P_FAULT BIT(5) +#define MASK_BITS_IN7P_FAULT BIT(6) +#define MASK_BITS_IN8P_FAULT BIT(7) + +#define MASK_BITS_IN1N_FAULT BIT(0) +#define MASK_BITS_IN2N_FAULT BIT(1) +#define MASK_BITS_IN3N_FAULT BIT(2) +#define MASK_BITS_IN4N_FAULT BIT(3) +#define MASK_BITS_IN5N_FAULT BIT(4) +#define MASK_BITS_IN6N_FAULT BIT(5) +#define MASK_BITS_IN7N_FAULT BIT(6) +#define MASK_BITS_IN8N_FAULT BIT(7) + +#define MASK_BITS_GPIOC1 BIT(0) +#define MASK_BITS_GPIOC2 BIT(1) +#define MASK_BITS_GPIOC3 BIT(2) +#define MASK_BITS_GPIOC4 BIT(3) +#define MASK_BITS_GPIOD1 BIT(4) +#define MASK_BITS_GPIOD2 BIT(5) +#define MASK_BITS_GPIOD3 BIT(6) +#define MASK_BITS_GPIOD4 BIT(7) + +#define CLOCK_SPEED_HZ (4096000) /**< 4MHz */ + +#define ADS130E08_CONVERSION_CONST 0.0000732421875f /* 2.4 / 32768 */ + +#define CHECK(x) \ + do \ + { \ + esp_err_t __; \ + if ((__ = x) != ESP_OK) \ + return __; \ + } \ + while (0) +#define CHECK_ARG(VAL) \ + do \ + { \ + if (!(VAL)) \ + return ESP_ERR_INVALID_ARG; \ + } \ + while (0) +#define BV(x) (1 << (x)) + +static const char *TAG_ADS130E08 = "ads130e08"; + +static esp_err_t write_reg_8(ads130e08_t *dev, uint8_t reg, uint8_t val) +{ + spi_transaction_t t; + memset(&t, 0, sizeof(spi_transaction_t)); + + uint8_t tx[] = { (reg | ADS130E08_CMD_WREG), 0, val, 0 }; + + t.tx_buffer = tx; + t.length = sizeof(tx) * 8; + + return spi_device_transmit(dev->spi_dev, &t); +} + +static esp_err_t read_reg_8(ads130e08_t *dev, uint8_t reg, uint8_t *val) +{ + spi_transaction_t t; + memset(&t, 0, sizeof(spi_transaction_t)); + + uint8_t tx[] = { (reg | ADS130E08_CMD_RREG), 0, 0, 0 }; + uint8_t rx[sizeof(tx)]; + + t.tx_buffer = tx; + t.rx_buffer = rx; + t.length = sizeof(tx) * 8; + CHECK(spi_device_transmit(dev->spi_dev, &t)); + + *val = rx[2]; + + return ESP_OK; +} + +/////////////////////////////////////////////////////////////////////////////// + +esp_err_t ads130e08_init_desc(ads130e08_t *dev, spi_host_device_t host, gpio_num_t cs_pin) +{ + CHECK_ARG(dev); + + memset(&dev->spi_cfg, 0, sizeof(dev->spi_cfg)); + dev->spi_cfg.spics_io_num = cs_pin; + dev->spi_cfg.clock_speed_hz = CLOCK_SPEED_HZ; + dev->spi_cfg.mode = 1; + dev->spi_cfg.queue_size = 1; + dev->spi_cfg.cs_ena_pretrans = 1; + + return spi_bus_add_device(host, &dev->spi_cfg, &dev->spi_dev); +} + +esp_err_t ads130e08_free_desc(ads130e08_t *dev) +{ + CHECK_ARG(dev); + + return spi_bus_remove_device(dev->spi_dev); +} + +esp_err_t ads130e08_send_system_cmd(ads130e08_t *dev, ads130e08_system_cmd_t cmd) +{ + CHECK_ARG(dev); + + spi_transaction_t t; + memset(&t, 0, sizeof(spi_transaction_t)); + + uint8_t tx[] = { (cmd), 0 }; + + t.tx_buffer = tx; + t.length = sizeof(tx) * 8; + + return spi_device_transmit(dev->spi_dev, &t); +} + +esp_err_t ads130e08_send_data_read_cmd(ads130e08_t *dev, ads130e08_data_read_cmd_t cmd) +{ + CHECK_ARG(dev); + + spi_transaction_t t; + memset(&t, 0, sizeof(spi_transaction_t)); + + uint8_t tx[] = { (cmd), 0 }; + + t.tx_buffer = tx; + t.length = sizeof(tx) * 8; + + return spi_device_transmit(dev->spi_dev, &t); +} + +esp_err_t ads130e08_get_device_id(ads130e08_t *dev, uint8_t *id) +{ + CHECK_ARG(dev); + + uint8_t val; + + CHECK(read_reg_8(dev, ADS130E08_REG_ID, &val)); + + *id = val; + + return ESP_OK; +} + +esp_err_t ads130e08_set_device_config(ads130e08_t *dev, ads130e08_dev_config_t config) +{ + CHECK_ARG(dev); + + uint8_t config1 = 0x00, config2 = 0x00, config3 = 0x00; + + config1 = (config.clk_en | CONFIG1_MASK_HIGH_BITS) & ~(CONFIG1_MASK_LOW_BITS); + + CHECK(write_reg_8(dev, ADS130E08_REG_CONFIG1, config1)); + + config2 + = (config.int_test | config.test_amp | config.test_freq | CONFIG2_MASK_HIGH_BITS) & ~(CONFIG2_MASK_LOW_BITS); + + CHECK(write_reg_8(dev, ADS130E08_REG_CONFIG2, config2)); + + config3 = (config.pd_refbuf | config.vref_4v | config.opamp_ref | config.pd_opamp | CONFIG3_MASK_HIGH_BITS) + & ~(CONFIG3_MASK_LOW_BITS); + + CHECK(write_reg_8(dev, ADS130E08_REG_CONFIG3, config3)); + + return ESP_OK; +} + +esp_err_t ads130e08_get_device_config(ads130e08_t *dev, ads130e08_dev_config_t *config) +{ + CHECK_ARG(dev && config); + + uint8_t config1, config2, config3; + + CHECK(read_reg_8(dev, ADS130E08_REG_CONFIG1, &config1)); + + config->clk_en = (config1 & CONFIG1_MASK_BITS_CLK_EN); + + CHECK(read_reg_8(dev, ADS130E08_REG_CONFIG2, &config2)); + + config->int_test = (config2 & CONFIG2_MASK_BITS_INT_TEST); + config->test_amp = (config2 & CONFIG2_MASK_BITS_TEST_AMP); + config->test_freq = (config2 & CONFIG2_MASK_BITS_TEST_FREQ); + + CHECK(read_reg_8(dev, ADS130E08_REG_CONFIG3, &config3)); + + config->pd_refbuf = (config3 & CONFIG3_MASK_BITS_PD_REFBUF); + config->vref_4v = (config3 & CONFIG3_MASK_BITS_VREF_4V); + config->opamp_ref = (config3 & CONFIG3_MASK_BITS_OPAMP_REF); + config->pd_opamp = (config3 & CONFIG3_MASK_BITS_PD_OPAMP); + + return ESP_OK; +} + +#define UPPER_1_BITS 0x80 +#define LOWER_7_BITS 0x7f + +esp_err_t ads130e08_get_rdata(ads130e08_t *dev, ads130e08_raw_data_t *raw_data) +{ + // (24 status bits + 16 bits × 8 channels) = 152 bits data per device + 1 dummy bit when using daisy + // The format of the 24 status bits is (1100 + FAULT_STATP + FAULT_STATN + bits[7:4] of the GPIO: General- Purpose + // IO Register). The data format for each channel data is twos complement, MSB first. When channels are powered down + // using user register settings, the corresponding channel output is set to '0'. + + CHECK_ARG(dev); + + spi_transaction_t t; + memset(&t, 0, sizeof(spi_transaction_t)); + + uint8_t tx[] = { (ADS130E08_CMD_RDATA), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + uint8_t rx[sizeof(tx)] = { 0 }; + + t.tx_buffer = tx; + t.rx_buffer = rx; + t.length = sizeof(tx) * 8; + + CHECK(spi_device_transmit(dev->spi_dev, &t)); + + uint32_t status_bits; + + status_bits = ((rx[1] | rx[2] | rx[3]) & 0x00FFFFFF); + + raw_data->fault_statp = (uint8_t)(status_bits & 0x000FF000); + raw_data->fault_statn = (uint8_t)(status_bits & 0x00000FF0); + raw_data->gpios_level = (uint8_t)(status_bits & 0x0000000F); + + uint16_t adc_raw_two_complememt; + + size_t j = 0; + + for (size_t i = 0; i < 8; i++) + { + adc_raw_two_complememt = ((uint16_t)(rx[4 + j] << 8) | (uint16_t)(rx[4 + j + 1] << 0)); + raw_data->channels_raw[i] = (int16_t)adc_raw_two_complememt; + + j += 2; + } + + return ESP_OK; +} + +esp_err_t ads130e08_convert_raw_to_voltage(int16_t raw, uint8_t gain, float *volts) +{ + *volts = (ADS130E08_CONVERSION_CONST * raw) / gain; + + return ESP_OK; +} + +esp_err_t ads130e08_set_fault_detect_control(ads130e08_t *dev, ads130e08_fault_threshold_t fault_mode) +{ + CHECK_ARG(dev); + + uint8_t val = 0x00; + + val = (fault_mode) & ~(FAULT_MASK_LOW_BITS); + + CHECK(write_reg_8(dev, ADS130E08_REG_FAULT, val)); + + return ESP_OK; +} + +esp_err_t ads130e08_get_fault_detect_control(ads130e08_t *dev, ads130e08_fault_threshold_t *fault_mode) +{ + CHECK_ARG(dev); + + uint8_t val; + + CHECK(read_reg_8(dev, ADS130E08_REG_FAULT, &val)); + + *fault_mode = val; + + return ESP_OK; +} + +esp_err_t ads130e08_set_channel_config(ads130e08_t *dev, ads130e08_channel_t channel, ads130e08_channel_config_t config) +{ + CHECK_ARG(dev && channel); + + uint8_t val = 0x00; + + val = (config.enable | config.pga_gain | config.mode) & ~(CHnSET_MASK_LOW_BITS); + + CHECK(write_reg_8(dev, channel, val)); + + return ESP_OK; +} + +esp_err_t ads130e08_get_channel_config(ads130e08_t *dev, ads130e08_channel_t channel, + ads130e08_channel_config_t *config) +{ + CHECK_ARG(dev && config); + + uint8_t val; + CHECK(read_reg_8(dev, channel, &val)); + + config->enable = val & CHnSET_MASK_BITS_PD; + config->pga_gain = val & CHnSET_MASK_BITS_PGA; + config->mode = val & CHnSET_MASK_BITS_MUX; + + return ESP_OK; +} + +esp_err_t ads130e08_set_gpio_pin_mode(ads130e08_t *dev, ads130e08_gpio_pin_t gpio_pin, ads130e08_gpio_mode_t gpio_mode) +{ + CHECK_ARG(dev && gpio_pin && gpio_mode); + + uint8_t val; + + CHECK(read_reg_8(dev, ADS130E08_REG_GPIO, &val)); + + if (gpio_pin == ADS130E08_GPIO1) + { + val |= (gpio_mode & MASK_BITS_GPIOC1); + } + else if (gpio_pin == ADS130E08_GPIO2) + { + val |= (gpio_mode & MASK_BITS_GPIOC2); + } + else if (gpio_pin == ADS130E08_GPIO3) + { + val |= (gpio_mode & MASK_BITS_GPIOC3); + } + else if (gpio_pin == ADS130E08_GPIO4) + { + val |= (gpio_mode & MASK_BITS_GPIOC4); + } + else + { + return ESP_ERR_INVALID_ARG; + } + + CHECK(write_reg_8(dev, ADS130E08_REG_GPIO, val)); + + return ESP_OK; +} + +esp_err_t ads130e08_get_gpio_pin_mode(ads130e08_t *dev, ads130e08_gpio_pin_t gpio_pin, ads130e08_gpio_mode_t *gpio_mode) +{ + CHECK_ARG(dev && gpio_pin && gpio_mode); + + uint8_t val; + + CHECK(read_reg_8(dev, ADS130E08_REG_GPIO, &val)); + + if (gpio_pin == ADS130E08_GPIO1) + { + *gpio_mode = (val & MASK_BITS_GPIOC1); + } + else if (gpio_pin == ADS130E08_GPIO2) + { + *gpio_mode = (val & MASK_BITS_GPIOC2); + } + else if (gpio_pin == ADS130E08_GPIO3) + { + *gpio_mode = (val & MASK_BITS_GPIOC3); + } + else if (gpio_pin == ADS130E08_GPIO4) + { + *gpio_mode = (val & MASK_BITS_GPIOC4); + } + else + { + return ESP_ERR_INVALID_ARG; + } + + return ESP_OK; +} + +esp_err_t ads130e08_set_gpio_pin_level(ads130e08_t *dev, ads130e08_gpio_pin_t gpio_pin, + ads130e08_gpio_level_t gpio_level) +{ + CHECK_ARG(dev && gpio_pin && gpio_level); + + uint8_t val; + + CHECK(read_reg_8(dev, ADS130E08_REG_GPIO, &val)); + + if (gpio_pin == ADS130E08_GPIO1) + { + val |= (gpio_level & MASK_BITS_GPIOD1); + } + else if (gpio_pin == ADS130E08_GPIO2) + { + val |= (gpio_level & MASK_BITS_GPIOD2); + } + else if (gpio_pin == ADS130E08_GPIO3) + { + val |= (gpio_level & MASK_BITS_GPIOD3); + } + else if (gpio_pin == ADS130E08_GPIO4) + { + val |= (gpio_level & MASK_BITS_GPIOD4); + } + else + { + return ESP_ERR_INVALID_ARG; + } + + CHECK(write_reg_8(dev, ADS130E08_REG_GPIO, val)); + + return ESP_OK; +} + +esp_err_t ads130e08_get_gpio_pin_level(ads130e08_t *dev, ads130e08_gpio_pin_t gpio_pin, + ads130e08_gpio_level_t *gpio_level) +{ + CHECK_ARG(dev && gpio_pin && gpio_level); + + uint8_t val; + + CHECK(read_reg_8(dev, ADS130E08_REG_GPIO, &val)); + + if (gpio_pin == ADS130E08_GPIO1) + { + *gpio_level = (val & MASK_BITS_GPIOD1); + } + else if (gpio_pin == ADS130E08_GPIO2) + { + *gpio_level = (val & MASK_BITS_GPIOD2); + } + else if (gpio_pin == ADS130E08_GPIO3) + { + *gpio_level = (val & MASK_BITS_GPIOD3); + } + else if (gpio_pin == ADS130E08_GPIO4) + { + *gpio_level = (val & MASK_BITS_GPIOD4); + } + else + { + return ESP_ERR_INVALID_ARG; + } + + return ESP_OK; +} + +esp_err_t ads130e08_detect_fault_auto(ads130e08_t *dev, uint8_t *fault_statp, uint8_t *fault_statn) +{ + CHECK_ARG(dev); + + uint8_t fault_bits; + CHECK(read_reg_8(dev, ADS130E08_REG_FAULT_STATP, &fault_bits)); + *fault_statp = fault_bits; + + if (fault_bits & MASK_BITS_IN1P_FAULT) + { + ESP_LOGE(TAG_ADS130E08, "Automatic fault detection on positive input 1"); + } + + if (fault_bits & MASK_BITS_IN2P_FAULT) + { + ESP_LOGE(TAG_ADS130E08, "Automatic fault detection on positive input 2"); + } + + if (fault_bits & MASK_BITS_IN3P_FAULT) + { + ESP_LOGE(TAG_ADS130E08, "Automatic fault detection on positive input 3"); + } + + if (fault_bits & MASK_BITS_IN4P_FAULT) + { + ESP_LOGE(TAG_ADS130E08, "Automatic fault detection on positive input 4"); + } + + if (fault_bits & MASK_BITS_IN5P_FAULT) + { + ESP_LOGE(TAG_ADS130E08, "Automatic fault detection on positive input 5"); + } + + if (fault_bits & MASK_BITS_IN6P_FAULT) + { + ESP_LOGE(TAG_ADS130E08, "Automatic fault detection on positive input 6"); + } + + if (fault_bits & MASK_BITS_IN7P_FAULT) + { + ESP_LOGE(TAG_ADS130E08, "Automatic fault detection on positive input 7"); + } + + if (fault_bits & MASK_BITS_IN8P_FAULT) + { + ESP_LOGE(TAG_ADS130E08, "Automatic fault detection on positive input 8"); + } + + CHECK(read_reg_8(dev, ADS130E08_REG_FAULT_STATN, &fault_bits)); + *fault_statn = fault_bits; + + if (fault_bits & MASK_BITS_IN1N_FAULT) + { + ESP_LOGE(TAG_ADS130E08, "Automatic fault detection on negative input 1"); + } + + if (fault_bits & MASK_BITS_IN2N_FAULT) + { + ESP_LOGE(TAG_ADS130E08, "Automatic fault detection on negative input 2"); + } + + if (fault_bits & MASK_BITS_IN3N_FAULT) + { + ESP_LOGE(TAG_ADS130E08, "Automatic fault detection on negative input 3"); + } + + if (fault_bits & MASK_BITS_IN4N_FAULT) + { + ESP_LOGE(TAG_ADS130E08, "Automatic fault detection on negative input 4"); + } + + if (fault_bits & MASK_BITS_IN5N_FAULT) + { + ESP_LOGE(TAG_ADS130E08, "Automatic fault detection on negative input 5"); + } + + if (fault_bits & MASK_BITS_IN6N_FAULT) + { + ESP_LOGE(TAG_ADS130E08, "Automatic fault detection on negative input 6"); + } + + if (fault_bits & MASK_BITS_IN7N_FAULT) + { + ESP_LOGE(TAG_ADS130E08, "Automatic fault detection on negative input 7"); + } + + if (fault_bits & MASK_BITS_IN8N_FAULT) + { + ESP_LOGE(TAG_ADS130E08, "Automatic fault detection on negative input 8"); + } + + return ESP_OK; +} \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/ads130e08/ads130e08.h Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,420 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2021 Weslley M. F. Duarte <weslleymfd@gmail.com> + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +/** + * @file ads130e08.h + * @defgroup ads130e08 ads130e08 + * @{ + * + * ESP-IDF driver for ADS130E08 ADC + * + * Copyright (c) 2021 Weslley M. F. Duarte <weslleymfd@gmail.com> + * + * MIT Licensed as described in the file LICENSE + */ + +#ifndef __ADS130E08_H__ +#define __ADS130E08_H__ + +#include <driver/spi_master.h> +#include <driver/gpio.h> +#include <esp_err.h> + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + ADS130E08_CMD_WAKEUP = 0x02, + ADS130E08_CMD_STANDBY = 0x04, + ADS130E08_CMD_RESET = 0x06, + ADS130E08_CMD_START = 0x08, + ADS130E08_CMD_STOP = 0x0A +} ads130e08_system_cmd_t; + +typedef enum { + ADS130E08_CMD_RDATAC = 0x10, + ADS130E08_CMD_SDATAC = 0x11, + ADS130E08_CMD_RDATA = 0x12 +} ads130e08_data_read_cmd_t; + +typedef enum { + ADS130E08_CLK_OUT_DISABLED = 0x00, /**< Oscillator clock output disabled (default) */ + ADS130E08_CLK_OUT_ENABLED = 0x20 /**< Oscillator clock output enabled */ +} ads130e08_clk_en_t; + +typedef enum { + ADS130E08_INT_TEST_EXTERNAL = 0x00, /**< Test signals are driven externally (default) */ + ADS130E08_INT_TEST_INTERNAL = 0x10 /**< Test signals are generated internally */ +} ads130e08_int_test_t; + +typedef enum { + ADS130E08_TEST_AMP_CALIB_1X = 0x00, /**< 1 × –(VREFP – VREFN) / 2.4 mV (default) */ + ADS130E08_TEST_AMP_CALIB_2X = 0x04 /**< 2 × –(VREFP – VREFN) / 2.4 mV */ +} ads130e08_test_amp_t; + +typedef enum { + ADS130E08_TEST_FREQ_EXP_21 = 0x00, /**< Pulsed at fCLK / 2^21 (default) */ + ADS130E08_TEST_FREQ_EXP_20 = 0x01, /**< Pulsed at fCLK / 2^20 */ + ADS130E08_TEST_FREQ_AT_DC = 0x11 /**< At dc */ +} ads130e08_test_freq_t; + +typedef enum { + ADS130E08_INTERNAL_REF_BUFFER_DISABLED = 0x00, /**< Power-down internal reference buffer (default) */ + ADS130E08_INTERNAL_REF_BUFFER_ENABLED = 0x80 /**< Enable internal reference buffer */ +} ads130e08_pd_refbuf_t; + +typedef enum { + ADS130E08_REF_VOLTAGE_2_4V = 0x00, /**< VREFP is set to 2.4 V (default) */ + ADS130E08_REF_VOLTAGE_4_0V = 0x20, /**< VREFP is set to 4 V (only use with a 5-V analog supply) */ +} ads130e08_vref_4v_t; + +typedef enum { + ADS130E08_NON_INVERTING_CONNECT_OPAMP = 0x00, /**< Noninverting input connected to the OPAMPP pin (default) */ + ADS130E08_NON_INVERTING_CONNECT_AV = 0x08, /**< Noninverting input connected to (AVDD + AVSS) / 2 */ +} ads130e08_opamp_ref_t; + +typedef enum { + ADS130E08_OPAMP_DISABLED = 0x00, /**< Power-down op amp (default) */ + ADS130E08_OPAMP_ENABLED = 0x04, /**< Enable op amp */ +} ads130e08_pd_opamp_t; + +/** + * Channels + */ +typedef enum { + ADS130E08_CHANNEL_1 = 0x05, + ADS130E08_CHANNEL_2 = 0x06, + ADS130E08_CHANNEL_3 = 0x07, + ADS130E08_CHANNEL_4 = 0x08, + ADS130E08_CHANNEL_5 = 0x09, + ADS130E08_CHANNEL_6 = 0x0A, + ADS130E08_CHANNEL_7 = 0x0B, + ADS130E08_CHANNEL_8 = 0x0C +} ads130e08_channel_t; + +/** + * Fault detect comparator threshold + */ +typedef enum { + ADS130E08_MODE_1 = 0x00, /**< Comparator positive threshold: 95% , negative threshold: 5% (default) */ + ADS130E08_MODE_2 = 0x20, /**< Comparator positive threshold: 92.5% , negative threshold: 7.5% (default) */ + ADS130E08_MODE_3 = 0x40, /**< Comparator positive threshold: 90% , negative threshold: 10% (default) */ + ADS130E08_MODE_4 = 0x60, /**< Comparator positive threshold: 87.5% , negative threshold: 12.5% (default) */ + ADS130E08_MODE_5 = 0x80, /**< Comparator positive threshold: 85% , negative threshold: 15% (default) */ + ADS130E08_MODE_6 = 0xA0, /**< Comparator positive threshold: 80% , negative threshold: 20% (default) */ + ADS130E08_MODE_7 = 0xC0, /**< Comparator positive threshold: 75% , negative threshold: 25% (default) */ + ADS130E08_MODE_8 = 0xE0 /**< Comparator positive threshold: 70% , negative threshold: 30% (default) */ +} ads130e08_fault_threshold_t; + +/** + * Power down + */ +typedef enum { + ADS130E08_NORMAL_OPERATION = 0x00, /**< Normal operation (default) */ + ADS130E08_POWER_DOWN = 0x80 /**< Channel power-down */ +} ads130e08_pd_t; + +/** + * PGA gain + */ +typedef enum { + ADS130E08_PGA_1 = 0x10, /**< x1 */ + ADS130E08_PGA_2 = 0x20, /**< x2 */ + ADS130E08_PGA_8 = 0x50 /**< x8 */ +} ads130e08_pga_gain_t; + +/** + * MUX + */ +typedef enum { + ADS130E08_NORMAL_INPUT = 0x00, /**< Normal input (default) */ + ADS130E08_INPUT_SHORTED = 0x01, /**< Input shorted (for offset or noise measurements) */ + ADS130E08_MVDD = 0x03, /**< MVDD for supply measurement */ + ADS130E08_TEMPERATURE_SENSOR = 0x04, /**< Temperature sensor */ + ADS130E08_TEST_SIGNAL = 0x05 /**< Test signal */ +} ads130e08_mux_t; + +typedef enum { ADS130E08_FAULT_STATP = 0x12, ADS130E08_FAULT_STATN = 0x13 } ads130e08_fault_t; + +/** + * Fault status + */ +typedef enum { + ADS130E08_NO_FAULT_PRESENT = 0x00, /**< No fault present (default) */ + ADS130E08_FAULT_PRESENT = 0x01 /**< Fault present */ +} ads130e08_fault_status_t; + +/** + * GPIO mode + */ +typedef enum { + ADS130E08_GPIO_OUTPUT = 0x00, /**< Output */ + ADS130E08_GPIO_INPUT = 0x01 /**< Input (default) */ +} ads130e08_gpio_mode_t; + +/** + * GPIO level + */ +typedef enum { ADS130E08_GPIO_RESET = 0x00, ADS130E08_GPIO_SET = 0x01 } ads130e08_gpio_level_t; + +/** + * GPIOs + */ +typedef enum { + ADS130E08_GPIO1 = 0x01, + ADS130E08_GPIO2 = 0x02, + ADS130E08_GPIO3 = 0x03, + ADS130E08_GPIO4 = 0x04 +} ads130e08_gpio_pin_t; + +/** + * Number of devices + */ +typedef enum { ADS130E08_DEVICES_1 = 0x01, ADS130E08_DEVICES_2 = 0x02 } ads130e08_devices_n_t; + +/** + * Device descriptor + */ +typedef struct +{ + spi_device_interface_config_t spi_cfg; /**< SPI device configuration */ + spi_device_handle_t spi_dev; /**< SPI device handler */ +} ads130e08_t; + +/** + * Device configuration + */ +typedef struct +{ + ads130e08_clk_en_t clk_en; + ads130e08_int_test_t int_test; + ads130e08_test_amp_t test_amp; + ads130e08_test_freq_t test_freq; + ads130e08_pd_refbuf_t pd_refbuf; + ads130e08_vref_4v_t vref_4v; + ads130e08_opamp_ref_t opamp_ref; + ads130e08_pd_opamp_t pd_opamp; +} ads130e08_dev_config_t; + +/** + * Channel configuration + */ +typedef struct +{ + ads130e08_pd_t enable; + ads130e08_pga_gain_t pga_gain; + ads130e08_mux_t mode; +} ads130e08_channel_config_t; + +typedef struct +{ + uint8_t fault_statp; + uint8_t fault_statn; + uint8_t gpios_level; + int16_t channels_raw[8]; +} ads130e08_raw_data_t; + +/** + * @brief Initialize device descriptor + * + * @param dev Device descriptor + * @param host SPI host + * @param cs_pin CS GPIO number + * @return `ESP_OK` on success + */ +esp_err_t ads130e08_init_desc(ads130e08_t *dev, spi_host_device_t host, gpio_num_t cs_pin); + +/** + * @brief Free device descriptor + * + * @param dev Device descriptor + * @return `ESP_OK` on success + */ +esp_err_t ads130e08_free_desc(ads130e08_t *dev); + +/** + * @brief Send system_command to device + * + * @param dev Device descriptor + * @param cmd Command + * @return `ESP_OK` on success + */ +esp_err_t ads130e08_send_system_cmd(ads130e08_t *dev, ads130e08_system_cmd_t cmd); + +/** + * @brief Send data_read_command to device + * + * @param dev Device descriptor + * @param cmd Command + * @return `ESP_OK` on success + */ +esp_err_t ads130e08_send_data_read_cmd(ads130e08_t *dev, ads130e08_data_read_cmd_t cmd); + +/** + * @brief Get device id + * + * @param dev Device descriptor + * @param id Id + * @return `ESP_OK` on success + */ +esp_err_t ads130e08_get_device_id(ads130e08_t *dev, uint8_t *id); + +/** + * @brief Set device configuration + * + * @param dev Device descriptor + * @param config Device configurations + * @return `ESP_OK` on success + */ +esp_err_t ads130e08_set_device_config(ads130e08_t *dev, ads130e08_dev_config_t config); + +/** + * @brief Get device configuration + * + * @param dev Device descriptor + * @param config Device configurations + * @return `ESP_OK` on success + */ +esp_err_t ads130e08_get_device_config(ads130e08_t *dev, ads130e08_dev_config_t *config); + +/** + * @brief Reads raw data in "Read data by command" mode + * + * @param dev Device descriptor + * @param[out] raw_data Raw data + * @return `ESP_OK` on success + */ +esp_err_t ads130e08_get_rdata(ads130e08_t *dev, ads130e08_raw_data_t *raw_data); + +/** + * @brief Converts raw adc value to voltage + * + * @param raw Raw adc value + * @param gain Channel gain + * @param[out] volts Voltage value + * @return `ESP_OK` on success + */ +esp_err_t ads130e08_convert_raw_to_voltage(int16_t raw, uint8_t gain, float *volts); + +/** + * @brief Set fault detect control + * + * @param dev Pointer to device descriptor + * @param fault_mode Fault mode + * @return `ESP_OK` on success + */ +esp_err_t ads130e08_set_fault_detect_control(ads130e08_t *dev, ads130e08_fault_threshold_t fault_mode); + +/** + * @brief Get fault detect control + * + * @param dev Pointer to device descriptor + * @param[out] fault_mode Fault mode + * @return `ESP_OK` on success + */ +esp_err_t ads130e08_get_fault_detect_control(ads130e08_t *dev, ads130e08_fault_threshold_t *fault_mode); + +/** + * @brief Set channel configuration + * + * @param dev Pointer to device descriptor + * @param channel Channel + * @param config Channel configuration + * @return `ESP_OK` on success + */ +esp_err_t ads130e08_set_channel_config(ads130e08_t *dev, ads130e08_channel_t channel, + ads130e08_channel_config_t config); + +/** + * @brief Get channel configuration + * + * @param dev Pointer to device descriptor + * @param channel Channel + * @param[out]config Channel configuration + * @return `ESP_OK` on success + */ +esp_err_t ads130e08_get_channel_config(ads130e08_t *dev, ads130e08_channel_t channel, + ads130e08_channel_config_t *config); + +/** + * @brief Set GPIO pin mode + * + * @param dev Pointer to device descriptor + * @param gpio_pin GPIO pin number + * @param gpio_mode GPIO pin mode + * @return `ESP_OK` on success + */ +esp_err_t ads130e08_set_gpio_pin_mode(ads130e08_t *dev, ads130e08_gpio_pin_t gpio_pin, ads130e08_gpio_mode_t gpio_mode); + +/** + * @brief Get GPIO pin mode + * + * @param dev Pointer to device descriptor + * @param gpio_pin GPIO pin number + * @param[out] gpio_mode GPIO pin mode + * @return `ESP_OK` on success + */ +esp_err_t ads130e08_get_gpio_pin_mode(ads130e08_t *dev, ads130e08_gpio_pin_t gpio_pin, + ads130e08_gpio_mode_t *gpio_mode); + +/** + * @brief Set GPIO pin level + * + * @param dev Pointer to device descriptor + * @param gpio_pin GPIO pin number + * @param gpio_level GPIO pin level + * @return `ESP_OK` on success + */ +esp_err_t ads130e08_set_gpio_pin_level(ads130e08_t *dev, ads130e08_gpio_pin_t gpio_pin, + ads130e08_gpio_level_t gpio_level); + +/** + * @brief Get GPIO pin level + * + * @param dev Pointer to device descriptor + * @param gpio_pin GPIO pin number + * @param[out] gpio_level GPIO pin level + * @return `ESP_OK` on success + */ +esp_err_t ads130e08_get_gpio_pin_level(ads130e08_t *dev, ads130e08_gpio_pin_t gpio_pin, + ads130e08_gpio_level_t *gpio_level); + +/** + * @brief Run automatical fault detection cycle + * + * @param dev Device descriptor + * @param [out] fault_statp See datasheet + * @param [out] fault_statn See datasheet + * @return `ESP_OK` on success + */ +esp_err_t ads130e08_detect_fault_auto(ads130e08_t *dev, uint8_t *fault_statp, uint8_t *fault_statn); + +#ifdef __cplusplus +} +#endif + +/**@}*/ + +#endif /* __ADS130E08_H__ */ \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/ads130e08/component.mk Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,2 @@ +COMPONENT_ADD_INCLUDEDIRS = . +COMPONENT_DEPENDS = driver log
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/aht/.eil.yml Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,25 @@ +--- +components: + - name: aht + description: | + Driver for AHT10/AHT15/AHT20 temperature and humidity sensor + group: temperature + groups: + - humidity + code_owners: + - name: UncleRus + depends: + - name: i2cdev + - name: log + - name: esp_idf_lib_helpers + thread_safe: yes + targets: + - name: esp32 + - name: esp8266 + - name: esp32s2 + - name: esp32c3 + licenses: + - name: BSD-3 + copyrights: + - name: UncleRus + year: 2021
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/aht/CMakeLists.txt Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,5 @@ +idf_component_register( + SRCS aht.c + INCLUDE_DIRS . + REQUIRES i2cdev log esp_idf_lib_helpers +)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/aht/LICENSE Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,26 @@ +Copyright 2021 Ruslan V. Uss <unclerus@gmail.com> + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, +this list of conditions and the following disclaimer in the documentation +and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of itscontributors +may be used to endorse or promote products derived from this software without +specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/aht/aht.c Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,174 @@ +/* + * Copyright (c) 2021 Ruslan V. Uss <unclerus@gmail.com> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of itscontributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file aht.c + * + * ESP-IDF driver for humidty/temperature sensors AHT10/AHT15/AHT20 + * + * Copyright (c) 2021 Ruslan V. Uss <unclerus@gmail.com> + * + * BSD Licensed as described in the file LICENSE + */ +#include "aht.h" +#include <freertos/FreeRTOS.h> +#include <freertos/task.h> +#include <esp_log.h> + +#define I2C_FREQ_HZ 400000 // 400kHz + +static const char *TAG = "aht"; + +#define CMD_CALIBRATE_1X (0xe1) +#define CMD_CALIBRATE_20 (0xbe) +#define CMD_RESET (0xba) +#define CMD_MODE_NORMAL (0xa8) +#define CMD_START_MEASUREMENT (0xac) + +#define ARG_MODE_NORMAL (0x00) +#define ARG_MODE_CYCLE (0x20) +#define ARG_MODE_CALIBRATE (0x08) +#define ARG_MEAS_DATA (0x33) + +#define BIT_STATUS_BUSY BIT(7) +#define BIT_STATUS_CAL BIT(3) + +#define CHECK(x) do { esp_err_t __; if ((__ = x) != ESP_OK) return __; } while (0) +#define CHECK_ARG(VAL) do { if (!(VAL)) return ESP_ERR_INVALID_ARG; } while (0) + +static esp_err_t send_cmd_nolock(aht_t *dev, uint8_t cmd0, uint8_t cmd1, uint8_t cmd2, uint32_t delay_ms) +{ + uint8_t buf[3] = { cmd0, cmd1, cmd2 }; + CHECK(i2c_dev_write(&dev->i2c_dev, NULL, 0, &buf, 3)); + vTaskDelay(pdMS_TO_TICKS(delay_ms)); + + return ESP_OK; +} + +static esp_err_t setup_nolock(aht_t *dev) +{ + return send_cmd_nolock(dev, dev->type == AHT_TYPE_AHT1x ? CMD_CALIBRATE_1X : CMD_CALIBRATE_20, + (dev->mode == AHT_MODE_NORMAL ? ARG_MODE_NORMAL : ARG_MODE_CYCLE) | ARG_MODE_CALIBRATE, 0, 350); +} + +/////////////////////////////////////////////////////////////////////////////// + +esp_err_t aht_init_desc(aht_t *dev, uint8_t addr, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio) +{ + CHECK_ARG(dev); + + if (addr != AHT_I2C_ADDRESS_GND || addr > AHT_I2C_ADDRESS_VCC) + { + ESP_LOGE(TAG, "Invalid I2C address"); + return ESP_ERR_INVALID_ARG; + } + + dev->i2c_dev.port = port; + dev->i2c_dev.addr = addr; + dev->i2c_dev.cfg.sda_io_num = sda_gpio; + dev->i2c_dev.cfg.scl_io_num = scl_gpio; +#if HELPER_TARGET_IS_ESP32 + dev->i2c_dev.cfg.master.clk_speed = I2C_FREQ_HZ; +#endif + + return i2c_dev_create_mutex(&dev->i2c_dev); +} + +esp_err_t aht_free_desc(aht_t *dev) +{ + CHECK_ARG(dev); + + return i2c_dev_delete_mutex(&dev->i2c_dev); +} + +esp_err_t aht_init(aht_t *dev) +{ + CHECK_ARG(dev); + + I2C_DEV_TAKE_MUTEX(&dev->i2c_dev); + I2C_DEV_CHECK(&dev->i2c_dev, setup_nolock(dev)); + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); + + return ESP_OK; +} + +esp_err_t aht_reset(aht_t *dev) +{ + CHECK_ARG(dev); + + I2C_DEV_TAKE_MUTEX(&dev->i2c_dev); + + uint8_t cmd = CMD_RESET; + I2C_DEV_CHECK(&dev->i2c_dev, i2c_dev_write(&dev->i2c_dev, NULL, 0, &cmd, 1)); + vTaskDelay(pdMS_TO_TICKS(20)); + I2C_DEV_CHECK(&dev->i2c_dev, setup_nolock(dev)); + + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); + + return ESP_OK; +} + +esp_err_t aht_get_status(aht_t *dev, bool *busy, bool *calibrated) +{ + CHECK_ARG(dev && (busy || calibrated)); + + uint8_t status; + I2C_DEV_TAKE_MUTEX(&dev->i2c_dev); + I2C_DEV_CHECK(&dev->i2c_dev, i2c_dev_read(&dev->i2c_dev, NULL, 0, &status, 1)); + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); + if (busy) + *busy = (status & BIT_STATUS_BUSY) != 0; + if (calibrated) + *calibrated = (status & BIT_STATUS_CAL) != 0; + + return ESP_OK; +} + +esp_err_t aht_get_data(aht_t *dev, float *temperature, float *humidity) +{ + CHECK_ARG(dev && (temperature || humidity)); + + uint8_t buf[6]; + I2C_DEV_TAKE_MUTEX(&dev->i2c_dev); + I2C_DEV_CHECK(&dev->i2c_dev, send_cmd_nolock(dev, CMD_START_MEASUREMENT, ARG_MEAS_DATA, 0, 80)); + I2C_DEV_CHECK(&dev->i2c_dev, i2c_dev_read(&dev->i2c_dev, NULL, 0, buf, 6)); + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); + + if (humidity) + { + uint32_t raw = ((uint32_t)buf[1] << 12) | ((uint32_t)buf[2] << 4) | (buf[3] >> 4); + *humidity = (float)raw * 100 / 0x100000; + } + + if (temperature) + { + uint32_t raw = ((uint32_t)(buf[3] & 0x0f) << 16) | ((uint32_t)buf[4] << 8) | buf[5]; + *temperature = (float)raw * 200 / 0x100000 - 50; + } + + return ESP_OK; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/aht/aht.h Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,144 @@ +/* + * Copyright (c) 2021 Ruslan V. Uss <unclerus@gmail.com> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of itscontributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file aht.h + * @defgroup aht aht + * @{ + * + * ESP-IDF driver for humidty/temperature sensors AHT10/AHT15/AHT20 + * + * Copyright (c) 2021 Ruslan V. Uss <unclerus@gmail.com> + * + * BSD Licensed as described in the file LICENSE + */ +#ifndef __AHT_H__ +#define __AHT_H__ + +#include <i2cdev.h> +#include <stdbool.h> + +#define AHT_I2C_ADDRESS_GND 0x38 //!< Device address when ADDR pin connected to GND +#define AHT_I2C_ADDRESS_VCC 0x39 //!< Device address when ADDR pin connected to VCC + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Device types + */ +typedef enum { + AHT_TYPE_AHT1x = 0, //!< AHT10, AHT15 + AHT_TYPE_AHT20, //!< AHT20 +} aht_type_t; + +/** + * Device modes + */ +typedef enum { + AHT_MODE_NORMAL = 0, //!< Normal mode + AHT_MODE_CYCLE, //!< Continuous measurements mode, undocumented +} aht_mode_t; + +/** + * Device descriptor + */ +typedef struct +{ + i2c_dev_t i2c_dev; + aht_type_t type; + aht_mode_t mode; +} aht_t; + +/** + * @brief Initialize device descriptor + * + * @param dev Device descriptor + * @param addr Device I2C address + * @param port I2C port + * @param sda_gpio SDA GPIO + * @param scl_gpio SCL GPIO + * @return `ESP_OK` on success + */ +esp_err_t aht_init_desc(aht_t *dev, uint8_t addr, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio); + +/** + * @brief Free device descriptor + * + * @param dev Device descriptor + * @return `ESP_OK` on success + */ +esp_err_t aht_free_desc(aht_t *dev); + +/** + * @brief Init device + * + * @param dev Device descriptor + * @return `ESP_OK` on success + */ +esp_err_t aht_init(aht_t *dev); + +/** + * @brief Soft reset device + * + * @param dev Device descriptor + * @return `ESP_OK` on success + */ +esp_err_t aht_reset(aht_t *dev); + +/** + * @brief Get device status + * + * @param dev Device descriptor + * @param[out] busy Busy flag + * - true: device currently measuring + * - false: device in indle mode + * @param[out] calibrated Calibration success flag + * - true: sensor calibrated + * - false: sensor not calibrated + * @return `ESP_OK` on success + */ +esp_err_t aht_get_status(aht_t *dev, bool *busy, bool *calibrated); + +/** + * @brief Get temperature and relative humidity + * + * @param dev Device descriptor + * @param[out] temperature Temperature, degrees Celsius + * @param[out] humidity Relative humidity, percents + * @return `ESP_OK` on success + */ +esp_err_t aht_get_data(aht_t *dev, float *temperature, float *humidity); + +#ifdef __cplusplus +} +#endif + +/**@}*/ + +#endif /* __AHT_H__ */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/aht/component.mk Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,2 @@ +COMPONENT_ADD_INCLUDEDIRS = . +COMPONENT_DEPENDS = i2cdev log esp_idf_lib_helpers
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/bh1750/.eil.yml Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,26 @@ +--- +components: + - name: bh1750 + description: Driver for BH1750 light sensor + group: + name: light + groups: [] + code_owners: + - name: UncleRus + depends: + - name: i2cdev + - name: log + - name: esp_idf_lib_helpers + thread_safe: yes + targets: + - name: esp32 + - name: esp8266 + - name: esp32s2 + - name: esp32c3 + licenses: + - name: BSD-3 + copyrights: + - name: UncleRus + year: 2018 + - name: Andrej + year: 2017
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/bh1750/CMakeLists.txt Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,5 @@ +idf_component_register( + SRCS bh1750.c + INCLUDE_DIRS . + REQUIRES i2cdev log esp_idf_lib_helpers +)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/bh1750/LICENSE Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,27 @@ +Copyright (c) 2017 Andrej Krutak <dev@andree.sk> +Copyright (c) 2018 Ruslan V. Uss <unclerus@gmail.com> + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, +this list of conditions and the following disclaimer in the documentation +and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of itscontributors +may be used to endorse or promote products derived from this software without +specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/bh1750/bh1750.c Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,171 @@ +/* + * Copyright (c) 2017 Andrej Krutak <dev@andree.sk> + * Copyright (c) 2018 Ruslan V. Uss <unclerus@gmail.com> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of itscontributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file bh1750.c + * + * @ingroup bh1750 ESP-IDF driver for BH1750 light sensor + * + * ESP-IDF driver for BH1750 light sensor + * + * Datasheet: ROHM Semiconductor bh1750fvi-e.pdf + * + * Ported from esp-open-rtos + * + * Copyright (c) 2017 Andrej Krutak <dev@andree.sk>\n + * Copyright (c) 2018 Ruslan V. Uss <unclerus@gmail.com> + * + * BSD Licensed as described in the file LICENSE + */ +#include <stdio.h> +#include <freertos/FreeRTOS.h> +#include <esp_log.h> +#include <esp_idf_lib_helpers.h> +#include "bh1750.h" + + +#define OPCODE_HIGH 0x0 +#define OPCODE_HIGH2 0x1 +#define OPCODE_LOW 0x3 + +#define OPCODE_CONT 0x10 +#define OPCODE_OT 0x20 + +#define OPCODE_POWER_DOWN 0x00 +#define OPCODE_POWER_ON 0x01 +#define OPCODE_MT_HI 0x40 +#define OPCODE_MT_LO 0x60 + +#define I2C_FREQ_HZ 400000 + +static const char *TAG = "bh1750"; + +#define CHECK(x) do { esp_err_t __; if ((__ = x) != ESP_OK) return __; } while (0) +#define CHECK_ARG(VAL) do { if (!(VAL)) return ESP_ERR_INVALID_ARG; } while (0) + +inline static esp_err_t send_command_nolock(i2c_dev_t *dev, uint8_t cmd) +{ + return i2c_dev_write(dev, NULL, 0, &cmd, 1); +} + +static esp_err_t send_command(i2c_dev_t *dev, uint8_t cmd) +{ + I2C_DEV_TAKE_MUTEX(dev); + I2C_DEV_CHECK(dev, send_command_nolock(dev, cmd)); + I2C_DEV_GIVE_MUTEX(dev); + + return ESP_OK; +} + +esp_err_t bh1750_init_desc(i2c_dev_t *dev, uint8_t addr, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio) +{ + CHECK_ARG(dev); + + if (addr != BH1750_ADDR_LO && addr != BH1750_ADDR_HI) + { + ESP_LOGE(TAG, "Invalid I2C address"); + return ESP_ERR_INVALID_ARG; + } + + dev->port = port; + dev->addr = addr; + dev->cfg.sda_io_num = sda_gpio; + dev->cfg.scl_io_num = scl_gpio; +#if HELPER_TARGET_IS_ESP32 + dev->cfg.master.clk_speed = I2C_FREQ_HZ; +#endif + return i2c_dev_create_mutex(dev); +} + +esp_err_t bh1750_free_desc(i2c_dev_t *dev) +{ + CHECK_ARG(dev); + + return i2c_dev_delete_mutex(dev); +} + +esp_err_t bh1750_power_down(i2c_dev_t *dev) +{ + CHECK_ARG(dev); + + return send_command(dev, OPCODE_POWER_DOWN); +} + +esp_err_t bh1750_power_on(i2c_dev_t *dev) +{ + CHECK_ARG(dev); + + return send_command(dev, OPCODE_POWER_ON); +} + +esp_err_t bh1750_setup(i2c_dev_t *dev, bh1750_mode_t mode, bh1750_resolution_t resolution) +{ + CHECK_ARG(dev); + + uint8_t opcode = mode == BH1750_MODE_CONTINUOUS ? OPCODE_CONT : OPCODE_OT; + switch (resolution) + { + case BH1750_RES_LOW: opcode |= OPCODE_LOW; break; + case BH1750_RES_HIGH: opcode |= OPCODE_HIGH; break; + default: opcode |= OPCODE_HIGH2; break; + } + + CHECK(send_command(dev, opcode)); + + ESP_LOGD(TAG, "bh1750_setup(PORT = %d, ADDR = 0x%02x, VAL = 0x%02x)", dev->port, dev->addr, opcode); + + return ESP_OK; +} + +esp_err_t bh1750_set_measurement_time(i2c_dev_t *dev, uint8_t time) +{ + CHECK_ARG(dev); + + I2C_DEV_TAKE_MUTEX(dev); + I2C_DEV_CHECK(dev, send_command_nolock(dev, OPCODE_MT_HI | (time >> 5))); + I2C_DEV_CHECK(dev, send_command_nolock(dev, OPCODE_MT_LO | (time & 0x1f))); + I2C_DEV_GIVE_MUTEX(dev); + + return ESP_OK; +} + +esp_err_t bh1750_read(i2c_dev_t *dev, uint16_t *level) +{ + CHECK_ARG(dev && level); + + uint8_t buf[2]; + + I2C_DEV_TAKE_MUTEX(dev); + I2C_DEV_CHECK(dev, i2c_dev_read(dev, NULL, 0, buf, 2)); + I2C_DEV_GIVE_MUTEX(dev); + + *level = buf[0] << 8 | buf[1]; + *level = (*level * 10) / 12; // convert to LUX + + return ESP_OK; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/bh1750/bh1750.h Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,149 @@ +/* + * Copyright (c) 2017 Andrej Krutak <dev@andree.sk> + * Copyright (c) 2018 Ruslan V. Uss <unclerus@gmail.com> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of itscontributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file bh1750.h + * + * @defgroup bh1750 bh1750 + * @{ + * + * ESP-IDF driver for BH1750 light sensor + * + * Datasheet: ROHM Semiconductor bh1750fvi-e.pdf + * + * Ported from esp-open-rtos + * + * Copyright (c) 2017 Andrej Krutak <dev@andree.sk>\n + * Copyright (c) 2018 Ruslan V. Uss <unclerus@gmail.com> + * + * BSD Licensed as described in the file LICENSE + */ +#ifndef __BH1750_H__ +#define __BH1750_H__ + +#include <stdint.h> +#include <i2cdev.h> +#include <esp_err.h> + +#ifdef __cplusplus +extern "C" { +#endif + +#define BH1750_ADDR_LO 0x23 //!< I2C address when ADDR pin floating/low +#define BH1750_ADDR_HI 0x5c //!< I2C address when ADDR pin high + +/** + * Measurement mode + */ +typedef enum +{ + BH1750_MODE_ONE_TIME = 0, //!< One time measurement + BH1750_MODE_CONTINUOUS //!< Continuous measurement +} bh1750_mode_t; + +/** + * Measurement resolution + */ +typedef enum +{ + BH1750_RES_LOW = 0, //!< 4 lx resolution, measurement time is usually 16 ms + BH1750_RES_HIGH, //!< 1 lx resolution, measurement time is usually 120 ms + BH1750_RES_HIGH2 //!< 0.5 lx resolution, measurement time is usually 120 ms +} bh1750_resolution_t; + +/** + * @brief Initialize device descriptor + * + * @param[out] dev Device descriptor + * @param[in] addr I2C address, ::BH1750_ADDR_LO or ::BH1750_ADDR_HI + * @param[in] port I2C port number + * @param[in] sda_gpio GPIO pin number for SDA + * @param[in] scl_gpio GPIO pin number for SCL + * @return `ESP_OK` on success + */ +esp_err_t bh1750_init_desc(i2c_dev_t *dev, uint8_t addr, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio); + +/** + * @brief Free device descriptor + * + * @param dev Pointer to device descriptor + * @return `ESP_OK` on success + */ +esp_err_t bh1750_free_desc(i2c_dev_t *dev); + +/** + * @brief Power down device + * + * @param dev Pointer to device descriptor + * @return `ESP_OK` on success + */ +esp_err_t bh1750_power_down(i2c_dev_t *dev); + +/** + * @brief Power on device + * + * @param dev Pointer to device descriptor + * @return `ESP_OK` on success + */ +esp_err_t bh1750_power_on(i2c_dev_t *dev); + +/** + * @brief Setup device parameters + * + * @param dev Pointer to device descriptor + * @param mode Measurement mode + * @param resolution Measurement resolution + * @return `ESP_OK` on success + */ +esp_err_t bh1750_setup(i2c_dev_t *dev, bh1750_mode_t mode, bh1750_resolution_t resolution); + +/** + * @brief Set measurement time + * + * @param dev Pointer to device descriptor + * @param time Measurement time (see datasheet) + * @return `ESP_OK` on success + */ +esp_err_t bh1750_set_measurement_time(i2c_dev_t *dev, uint8_t time); + +/** + * @brief Read LUX value from the device. + * + * @param dev Pointer to device descriptor + * @param[out] level read value in lux units + * @return `ESP_OK` on success + */ +esp_err_t bh1750_read(i2c_dev_t *dev, uint16_t *level); + +#ifdef __cplusplus +} +#endif + +/**@}*/ + +#endif /* __BH1750_H__ */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/bh1750/component.mk Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,2 @@ +COMPONENT_ADD_INCLUDEDIRS = . +COMPONENT_DEPENDS = i2cdev log esp_idf_lib_helpers
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/bh1900nux/.eil.yml Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,23 @@ +--- +components: + - name: bh1900nux + description: Driver for BH1900NUX temperature sensor + group: temperature + groups: [] + code_owners: UncleRus + depends: + - i2cdev + - log + - esp_idf_lib_helpers + thread_safe: yes + targets: + - name: esp32 + - name: esp8266 + - name: esp32s2 + - name: esp32c3 + licenses: + - name: BSD-3 + copyrights: + - author: + name: UncleRus + year: 2021
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/bh1900nux/CMakeLists.txt Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,5 @@ +idf_component_register( + SRCS bh1900nux.c + INCLUDE_DIRS . + REQUIRES i2cdev log esp_idf_lib_helpers +)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/bh1900nux/LICENSE Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,26 @@ +Copyright (c) 2021 Ruslan V. Uss (https://github.com/UncleRus) + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, +this list of conditions and the following disclaimer in the documentation +and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of itscontributors +may be used to endorse or promote products derived from this software without +specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/bh1900nux/bh1900nux.c Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,255 @@ +/* + * Copyright (c) 2021 Ruslan V. Uss <unclerus@gmail.com> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of itscontributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file bh1900nux.c + * + * ESP-IDF driver for BH1900NUX temperature sensor + * + * Copyright (c) 2021 Ruslan V. Uss <unclerus@gmail.com> + * + * BSD Licensed as described in the file LICENSE + */ + +#include <esp_log.h> +#include <freertos/FreeRTOS.h> +#include <freertos/task.h> +#include <esp_idf_lib_helpers.h> +#include "bh1900nux.h" + +static const char *TAG = "bh1900nux"; + +static const float t_lsb = 0.0625; + +#define BH1900NUX_I2C_ADDR_MAX 0x4f + +#define I2C_FREQ_HZ 400000 // 400kHz + +#define REG_TEMP 0 +#define REG_CONF 1 +#define REG_TLOW 2 +#define REG_THIGH 3 +#define REG_RST 4 + +#define BIT_SHUTDOWN 0 +#define BIT_ALERT_POL 2 +#define BIT_FAULT_DQ0 3 +#define BIT_ALERT 6 +#define BIT_ONE_SHOT 7 +#define BIT_WT0 8 + +#define CONV_TIME_MS 35 +#define RESET_TIME_MS 250 + +#define CHECK(x) do { esp_err_t __; if ((__ = x) != ESP_OK) return __; } while (0) +#define CHECK_ARG(VAL) do { if (!(VAL)) return ESP_ERR_INVALID_ARG; } while (0) +#define BV(x) (1 << (x)) + +inline static uint16_t shuffle(uint16_t v) +{ + return (v << 8) | (v >> 8); +} + +static esp_err_t read_reg(bh1900nux_t *dev, uint8_t reg, uint16_t *val) +{ + I2C_DEV_TAKE_MUTEX(&dev->i2c_dev); + I2C_DEV_CHECK(&dev->i2c_dev, i2c_dev_read_reg(&dev->i2c_dev, reg, val, 2)); + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); + + *val = shuffle(*val); + + return ESP_OK; +} + +static esp_err_t write_reg(bh1900nux_t *dev, uint8_t reg, uint16_t val) +{ + uint16_t v = shuffle(val); + + I2C_DEV_TAKE_MUTEX(&dev->i2c_dev); + I2C_DEV_CHECK(&dev->i2c_dev, i2c_dev_write_reg(&dev->i2c_dev, reg, &v, 2)); + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); + + return ESP_OK; +} + +static esp_err_t read_temp(bh1900nux_t *dev, uint8_t reg, float *temp) +{ + CHECK_ARG(dev && temp); + + int16_t buf; + + CHECK(read_reg(dev, reg, (uint16_t *)&buf)); + + *temp = ((int16_t)buf >> 4) * t_lsb; + + return ESP_OK; +} + +static esp_err_t write_temp(bh1900nux_t *dev, uint8_t reg, float temp) +{ + CHECK_ARG(dev); + + return write_reg(dev, reg, (int16_t)(temp / t_lsb) << 4); +} + +static esp_err_t update_reg(bh1900nux_t *dev, uint8_t reg, uint16_t val, uint16_t mask) +{ + CHECK_ARG(dev); + + uint16_t tmp; + CHECK(read_reg(dev, reg, &tmp)); + CHECK(write_reg(dev, reg, (tmp & ~mask) | (val & ~mask))); + + return ESP_OK; +} + +/////////////////////////////////////////////////////////////////////////////// + +esp_err_t bh1900nux_init_desc(bh1900nux_t *dev, uint8_t addr, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio) +{ + CHECK_ARG(dev); + if (addr < BH1900NUX_I2C_ADDR_BASE || addr > BH1900NUX_I2C_ADDR_MAX) + { + ESP_LOGE(TAG, "Invalid device address: 0x%02x", addr); + return ESP_ERR_INVALID_ARG; + } + + dev->i2c_dev.port = port; + dev->i2c_dev.addr = addr; + dev->i2c_dev.cfg.sda_io_num = sda_gpio; + dev->i2c_dev.cfg.scl_io_num = scl_gpio; +#if HELPER_TARGET_IS_ESP32 + dev->i2c_dev.cfg.master.clk_speed = I2C_FREQ_HZ; +#endif + + return i2c_dev_create_mutex(&dev->i2c_dev); +} + +esp_err_t bh1900nux_free_desc(bh1900nux_t *dev) +{ + CHECK_ARG(dev); + + return i2c_dev_delete_mutex(&dev->i2c_dev); +} + +esp_err_t bh1900nux_reset(bh1900nux_t *dev) +{ + CHECK_ARG(dev); + + CHECK(write_reg(dev, REG_RST, 1)); + vTaskDelay(pdMS_TO_TICKS(RESET_TIME_MS)); + return bh1900nux_set_mode(dev, dev->mode); +} + +esp_err_t bh1900nux_set_mode(bh1900nux_t *dev, bh1900nux_mode_t mode) +{ + CHECK_ARG(dev); + + dev->mode = mode; + + uint16_t val, mask; + if (mode == BH1900NUX_MODE_CONTINUOUS) + { + val = BV(BIT_ONE_SHOT); + mask = BV(BIT_ONE_SHOT) | BV(BIT_SHUTDOWN); + } + else + { + val = BV(BIT_SHUTDOWN); + mask = BV(BIT_ONE_SHOT) | BV(BIT_SHUTDOWN); + } + + return update_reg(dev, REG_CONF, val, mask); +} + +esp_err_t bh1900nux_get_config(bh1900nux_t *dev, bh1900nux_wait_time_t *wt, bh1900nux_fault_queue_t *fq, bh1900nux_alert_polarity_t *ap) +{ + CHECK_ARG(dev && wt && fq && ap); + + uint16_t val; + CHECK(read_reg(dev, REG_CONF, &val)); + + *wt = (val >> BIT_WT0) & 3; + *fq = (val >> BIT_FAULT_DQ0) & 3; + *ap = (val >> BIT_ALERT_POL) & 1; + + return ESP_OK; +} + +esp_err_t bh1900nux_set_config(bh1900nux_t *dev, bh1900nux_wait_time_t wt, bh1900nux_fault_queue_t fq, bh1900nux_alert_polarity_t ap) +{ + CHECK_ARG(dev && fq <= BH1900NUX_FAULTS_6 && wt <= BH1900NUX_WT_3); + + uint16_t val = (wt << BIT_WT0) | (fq << BIT_FAULT_DQ0) | (ap != BH1900NUX_ALERT_LOW ? BV(BIT_ALERT_POL) : 0); + + CHECK(write_reg(dev, REG_CONF, val)); + CHECK(bh1900nux_set_mode(dev, dev->mode)); + + return ESP_OK; +} + +esp_err_t bh1900nux_one_shot(bh1900nux_t *dev, float *temp) +{ + CHECK_ARG(dev); + + if (dev->mode != BH1900NUX_MODE_SHUTDOWN) + { + ESP_LOGE(TAG, "Cannot perform one-shot measurement, device in invalid mode"); + return ESP_ERR_INVALID_STATE; + } + + CHECK(update_reg(dev, REG_CONF, BV(BIT_ONE_SHOT) | BV(BIT_SHUTDOWN), BV(BIT_ONE_SHOT) | BV(BIT_SHUTDOWN))); + + vTaskDelay(pdMS_TO_TICKS(CONV_TIME_MS)); + + return read_temp(dev, REG_TEMP, temp); +} + +esp_err_t bh1900nux_get_temperature(bh1900nux_t *dev, float *temp) +{ + return read_temp(dev, REG_TEMP, temp); +} + +esp_err_t bh1900nux_get_t_low(bh1900nux_t *dev, float *temp) +{ + return read_temp(dev, REG_TLOW, temp); +} + +esp_err_t bh1900nux_set_t_low(bh1900nux_t *dev, float temp) +{ + return write_temp(dev, REG_TLOW, temp); +} + +esp_err_t bh1900nux_get_t_high(bh1900nux_t *dev, float *temp) +{ + return read_temp(dev, REG_THIGH, temp); +} + +esp_err_t bh1900nux_set_t_high(bh1900nux_t *dev, float temp) +{ + return write_temp(dev, REG_THIGH, temp); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/bh1900nux/bh1900nux.h Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,217 @@ +/* + * Copyright (c) 2021 Ruslan V. Uss <unclerus@gmail.com> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of itscontributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file bh1900nux.h + * @defgroup bh1900nux bh1900nux + * @{ + * + * ESP-IDF driver for BH1900NUX temperature sensor + * + * Copyright (c) 2021 Ruslan V. Uss <unclerus@gmail.com> + * + * BSD Licensed as described in the file LICENSE + */ +#ifndef __BH1900NUX_H__ +#define __BH1900NUX_H__ + +#include <stdbool.h> +#include <i2cdev.h> +#include <esp_err.h> + +#define BH1900NUX_I2C_ADDR_BASE 0x48 //!< See full list in datasheet + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Fault queue size + */ +typedef enum { + BH1900NUX_FAULTS_1 = 0, //!< 1 fault to trigger ALERT pin + BH1900NUX_FAULTS_2, //!< 2 faults to trigger ALERT pin + BH1900NUX_FAULTS_4, //!< 4 faults to trigger ALERT pin + BH1900NUX_FAULTS_6 //!< 6 faults to trigger ALERT pin +} bh1900nux_fault_queue_t; + +/** + * ALERT pin polarity + */ +typedef enum { + BH1900NUX_ALERT_LOW = 0, //!< ALERT active low (default) + BH1900NUX_ALERT_HIGH //!< ALERT active high +} bh1900nux_alert_polarity_t; + +/** + * Device operating mode + */ +typedef enum { + BH1900NUX_MODE_CONTINUOUS = 0, //!< Continuous measurement mode (default) + BH1900NUX_MODE_SHUTDOWN //!< Shutdown mode +} bh1900nux_mode_t; + +/** + * Delay between measurements in continuous mode + */ +typedef enum { + BH1900NUX_WT_0 = 0, //!< 186240 * 16 / 450000 ~ 6.622s (Fosc = 450kHz) + BH1900NUX_WT_1, //!< 186240 * 4 / 450000 ~ 1.655s (Fosc = 450kHz) + BH1900NUX_WT_2, //!< 186240 / 450000 ~ 0.414s (Fosc = 450kHz) + BH1900NUX_WT_3, //!< 93120 / 450000 ~ 0.207s (Fosc = 450kHz) +} bh1900nux_wait_time_t; + +/** + * Device descriptor + */ +typedef struct { + i2c_dev_t i2c_dev; + bh1900nux_mode_t mode; +} bh1900nux_t; + +/** + * @brief Initialize device descriptor + * + * @param dev Device descriptor + * @param port I2C port number + * @param addr I2C address + * @param sda_gpio SDA GPIO + * @param scl_gpio SCL GPIO + * @return `ESP_OK` on success + */ +esp_err_t bh1900nux_init_desc(bh1900nux_t *dev, uint8_t addr, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio); + +/** + * @brief Free device descriptor + * + * @param dev Device descriptor + * @return `ESP_OK` on success + */ +esp_err_t bh1900nux_free_desc(bh1900nux_t *dev); + +/** + * @brief Software reset + * + * @param dev Device descriptor + * @return `ESP_OK` on success + */ +esp_err_t bh1900nux_reset(bh1900nux_t *dev); + +/** + * @brief Set device operating mode + * + * @param dev Device descriptor + * @param mode Operating mode + * @return `ESP_OK` on success + */ +esp_err_t bh1900nux_set_mode(bh1900nux_t *dev, bh1900nux_mode_t mode); + +/** + * @brief Read current device config + * + * @param dev Device descriptor + * @param[out] wt Delay between measurements + * @param[out] fq Fault queue size + * @param[out] ap ALERT pin polarity + * @return `ESP_OK` on success + */ +esp_err_t bh1900nux_get_config(bh1900nux_t *dev, bh1900nux_wait_time_t *wt, bh1900nux_fault_queue_t *fq, bh1900nux_alert_polarity_t *ap); + +/** + * @brief Configure device + * + * @param dev Device descriptor + * @param wt Delay between measurements + * @param fq Fault queue size + * @param ap ALERT pin polarity + * @return `ESP_OK` on success + */ +esp_err_t bh1900nux_set_config(bh1900nux_t *dev, bh1900nux_wait_time_t wt, bh1900nux_fault_queue_t fq, bh1900nux_alert_polarity_t ap); + +/** + * @brief Made a single-shot measurement + * + * Works only when device is in shutdown mode. + * + * @param dev Device descriptor + * @param[out] temp Temperature, deg.C + * @return `ESP_OK` on success + */ +esp_err_t bh1900nux_one_shot(bh1900nux_t *dev, float *temp); + +/** + * @brief Read temperature register + * + * @param dev Device descriptor + * @param[out] temp Temperature, deg.C + * @return `ESP_OK` on success + */ +esp_err_t bh1900nux_get_temperature(bh1900nux_t *dev, float *temp); + +/** + * @brief Read lower temperature limit register + * + * @param dev Device descriptor + * @param[out] temp Temperature, deg.C + * @return `ESP_OK` on success + */ +esp_err_t bh1900nux_get_t_low(bh1900nux_t *dev, float *temp); + +/** + * @brief Write lower temperature limit register + * + * @param dev Device descriptor + * @param temp Temperature, deg.C + * @return `ESP_OK` on success + */ +esp_err_t bh1900nux_set_t_low(bh1900nux_t *dev, float temp); + +/** + * @brief Read higher temperature limit register + * + * @param dev Device descriptor + * @param[out] temp Temperature, deg.C + * @return `ESP_OK` on success + */ +esp_err_t bh1900nux_get_t_high(bh1900nux_t *dev, float *temp); + +/** + * @brief Write higher temperature limit register + * + * @param dev Device descriptor + * @param temp Temperature, deg.C + * @return `ESP_OK` on success + */ +esp_err_t bh1900nux_set_t_high(bh1900nux_t *dev, float temp); + +#ifdef __cplusplus +} +#endif + +/**@}*/ + +#endif /* __BH1900NUX_H__ */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/bh1900nux/component.mk Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,2 @@ +COMPONENT_ADD_INCLUDEDIRS = . +COMPONENT_DEPENDS = i2cdev log esp_idf_lib_helpers
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/bme680/.eil.yml Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,29 @@ +--- +components: + - name: bme680 + description: | + Driver for BME680 digital environmental sensor + group: + name: pressure + groups: + - name: humidity + - name: temperature + code_owners: + - name: UncleRus + depends: + - name: i2cdev + - name: log + - name: esp_idf_lib_helpers + thread_safe: yes + targets: + - name: esp32 + - name: esp8266 + - name: esp32s2 + - name: esp32c3 + licenses: + - name: BSD-3 + copyrights: + - name: UncleRus + year: 2019 + - name: gschorcht + year: 2017
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/bme680/CMakeLists.txt Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,5 @@ +idf_component_register( + SRCS bme680.c + INCLUDE_DIRS . + REQUIRES i2cdev log esp_idf_lib_helpers +)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/bme680/LICENSE Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,27 @@ +Copyright (c) 2017 Gunar Schorcht (https://github.com/gschorcht) +Copyright (c) 2019 Ruslan V. Uss (https://github.com/UncleRus) + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, +this list of conditions and the following disclaimer in the documentation +and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of itscontributors +may be used to endorse or promote products derived from this software without +specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/bme680/README.md Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,342 @@ +# Driver for **BME680** digital **environmental sensor** + +Forked from [original driver by Gunar Schorcht](https://github.com/gschorcht/bme680-esp-idf). + +The driver supports multiple BME680 sensors which are connected to the same or +different I2C interfaces with different addresses. *SPI interface is not +supported yet*. + +It is for the usage with the ESP8266 and [ESP8266 RTOS SDK](https://github.com/espressif/ESP8266_RTOS_SDK) +or ESP32 and [ESP-IDF](https://github.com/espressif/esp-idf.git). + +## About the sensor + +BME680 is an ultra-low-power environmental sensor that integrates temperature, +pressure, humidity and gas sensors in only one unit. + +## Communication interface + +The BME680 sensor can be connected using I2C. + +The I2C interface supports data rates up to 1 Mbps (ESP-IDF limitation). It is +possible to connect multiple BME680 sensors with different I2C slave addresses +to the same I2C bus or to different I2C buses. Possible I2C slave addresses +are 0x76 and 0x77. + +## Measurement process + +Once the BME680 has been initialized, it can be used for measurements. The +BME680 operates in two different modes, the **sleep mode** and the +**forced mode**. + +The sensor starts after power-up automatically in the *sleep mode* where it +does not perform any measurement and consumes only 0.15 μA. Measurements are +only done in *forced mode*. + +**Please note:** There are two further undocumented modes, the *parallel* and +the *sequential* mode. They can't be supported by the driver, since it is +not clear what they do and how to use them. + +### Measurement cylce + +To perform measurements, the BME680 sensor has to be triggered to switch to +the **forced mode**. In this mode, it performs exactly one measurement of +temperature, pressure, humidity, and gas in that order, the so-called +**TPHG measurement cycle**. After the execution of this TPHG measurement cycle, +**raw sensor data** become available and the sensor returns automatically back +to sleep mode. + +Each of the individual measurements can be configured or skipped separately +via the sensor settings, see section **Measurement settings**. Dependent on +the configuration, the **duration of a TPHG measurement cycle** can vary from +some milliseconds up to about 4.5 seconds, especially if gas measurement is +enabled. + +To avoid the blocking of the user task during measurements, the measurement +process is therefore separated into the following steps: + +1. Trigger the sensor with function `bme680_force_measurement()` to switch + to *forced mode* in which it performs exactly one THPG measurement cycle. +2. Wait the measurement duration using function `vTaskDelay()` and the value + returned from function `bme680_get_measurement_duration()` or wait as long + as function `bme680_is_measuring` returns true. +3. Fetch the results as fixed point values with function + `bme680_get_results_fixed()` or as floating point values with function + `bme680_get_results_float()`. + +```C +... +// as long as sensor configuration isn't changed, the duration can be considered as constant +uint32_t duration +bme680_get_measurement_duration(sensor, &duration); +... +if (bme680_force_measurement(sensor) == ESP_OK) // STEP 1 +{ + // STEP 2: passive waiting until measurement results are available + vTaskDelay(duration); + + // STEP 3: get the results and do something with them + if (bme680_get_results_float(sensor, &values) == ESP_OK) + ... +} +... +``` + +Alternatively, busy waiting can be realized using function `bme680_is_measuring()`. + +```C +... +if (bme680_force_measurement(sensor) == ESP_OK) // STEP 1 +{ + // STEP 2: busy waiting until measurement results are available + bool busy; + do + { + ESP_ERROR_CHECK(bme680_is_measuring(sensor, &busy)); + } + while(busy); + + // STEP 3: get the results and do something with them + if (bme680_get_results_float(sensor, &values) == ESP_OK) + ... +} +... +``` + +For convenience, it is also possible to use the high-level functions +`bme680_measure_float()` or `bme680_measure_fixed()`. These functions combine +all 3 steps above within a single function and are therefore very easy to use. +**Please note** that these functions must not be used when they are called +from a software timer callback function since the calling task is delayed +using function *vTaskDelay*. + +```C +... +// ONE STEP: measure, wait, get the results and do something with them +if (bme680_measure_float(sensor, &values) == ESP_OK) + ... +... +``` + +#### Measurement results + +Once the sensor has finished the measurement raw data are available at the sensor. +Either function `bme680_get_results_fixed()` or function +`bme680_get_results_float()` can be used to fetch the results. Both functions +read raw data from the sensor and converts them into utilizable fixed point or +floating point sensor values. + +**Please note:** Conversion of raw sensor data into the final sensor values is +based on very complex calculations that use a large number of calibration +parameters. Therefore, the driver does not provide functions that only return +the raw sensor data. + +Dependent on sensor value representation, measurement results contain different +dimensions: + +| Value | Fixed Point | Floating Point | Conversion +| -------------- | ------------- | -------------- |--------------------- +| temperature | 1/100 °C | °C | float = fixed / 100 +| pressure | Pascal | hPascal | float = fixed / 100 +| humidity | 1/1000 % | % | float = fixed / 1000 +| gas_resistance | Ohm | Ohm | float = fixed + +The gas resistance value in Ohm represents the resistance of sensor's gas sensitive +layer. + +If the TPHG measurement cycle or fetching the results fails, invalid sensor values +are returned: + +| Invalid Value | Fixed Point | Floating Point +| -------------- | ----------- | --------------- +| temperature | INT16_MIN | -327.68 +| pressure | 0 | 0.0 +| humidity | 0 | 0.0 +| gas_resistance | 0 | 0.0 + +## Measurement settings + +The sensor allows to change a lot of measurement parameters. + +### Oversampling rates + +To increase the resolution of raw sensor data, the sensor supports +oversampling for temperature, pressure, and humidity measurements. Using +function `bme680_set_oversampling_rates()`, individual **oversampling rates** +can be defined for these measurements. With an oversampling rate *osr*, the +resolution of the according raw sensor data can be increased from 16 bit to +16+ld(*osr*) bit. + +Possible oversampling rates are 1x (default by the driver) 2x, 4x, 8x and 16x. +It is also possible to define an oversampling rate of 0. This **deactivates** +the corresponding measurement and the output values become invalid. + +```C +... +// Changes the oversampling rate for temperature to 4x and for pressure to 2x. Humidity measurement is skipped. +ESP_ERROR_CHECK(bme680_set_oversampling_rates(sensor, osr_4x, osr_2x, osr_none)); +... +``` + +### IIR Filter + +The sensor also integrates an internal IIR filter (low pass filter) to reduce +short-term changes in sensor output values caused by external disturbances. +It effectively reduces the bandwidth of the sensor output values. + +The filter can optionally be used for pressure and temperature data that are +subject to many short-term changes. With the IIR filter the resolution of +pressure and temperature data increases to 20 bit. Humidity and gas inside +the sensor does not fluctuate rapidly and does not require such a low pass +filtering. + +Using function `bme680_set_filter_size()`, the user task can change the +**size of the filter**. The default size is 3. If the size of the filter +becomes 0, the filter is **not used**. + +```C +... +// Change the IIR filter size for temperature and pressure to 7. +bme680_set_filter_size(sensor, iir_size_7); +... +// Don't use IIR filter +bme680_set_filter_size(sensor, iir_size_0); +... +``` + +### Heater profile + +For the gas measurement, the sensor integrates a heater. Parameters for this +heater are defined by **heater profiles**. The sensor supports up to 10 such +heater profiles, which are numbered from 0 to 9. Each profile consists of a +temperature set-point (the target temperature) and a heating duration. By +default, only the heater profile 0 with 320 degree Celsius as target temperature +and 150 ms heating duration is defined. + +**Please note:** According to the data sheet, target temperatures between 200 +and 400 degrees Celsius are typical and about 20 to 30 ms are necessary for +the heater to reach the desired target temperature. + +Function `bme680_set_heater_profile()` can be used to set the parameters +for one of the heater profiles 0 ... 9. Once the parameters of a heater profile +are defined, the gas measurement can be activated with that heater profile +using function `bme680_use_heater_profile()`. If -1 or `BME680_HEATER_NOT_USED` +is used as heater profile, gas measurement is deactivated completely. + +```C +... +// Change the heater profile 1 to 300 degree Celsius for 100 ms and activate it +bme680_set_heater_profile (sensor, 1, 300, 100); +bme680_use_heater_profile (sensor, 1); +... +// Deactivate gas measurement completely +bme680_use_heater_profile (sensor, BME680_HEATER_NOT_USED); +... + +``` + +If several heater profiles have been defined with function +`bme680_set_heater_profile()`, a sequence of gas measurements with different +heater parameters can be realized by a sequence of activations of different +heater profiles for successive TPHG measurements using function +`bme680_use_heater_profile()`. + +For example, if there were 5 heater profiles defined with following code +during the setup + +```C +bme680_set_heater_profile(sensor, 0, 200, 100); +bme680_set_heater_profile(sensor, 1, 250, 120); +bme680_set_heater_profile(sensor, 2, 300, 140); +bme680_set_heater_profile(sensor, 3, 350, 160); +bme680_set_heater_profile(sensor, 4, 400, 180); +``` + +the user task could use them as a sequence like following: + +```C +... +while (1) +{ + switch (count++ % 5) + { + case 0: bme680_use_heater_profile(sensor, 0); break; + case 1: bme680_use_heater_profile(sensor, 1); break; + case 2: bme680_use_heater_profile(sensor, 2); break; + case 3: bme680_use_heater_profile(sensor, 3); break; + case 4: bme680_use_heater_profile(sensor, 4); break; + } + + // measurement duration changes in each cycle + uint32_t duration; + bme680_get_measurement_duration(sensor, &duration); + + // trigger the sensor to start one TPHG measurement cycle + if (bme680_force_measurement(sensor) == ESP_OK) + { + vTaskDelay(duration); + + // get the results and do something with them + if (bme680_get_results_float(sensor, &values) == ESP_OK) + ... + } + ... +} +... +``` + +### Ambient temperature + +The heater resistance calculation algorithm takes into account the ambient +temperature of the sensor. Using function `bme680_set_ambient_temperature()`, +the ambient temperature either determined from the sensor itself or from +another temperature sensor can be set. + +```C +... +bme680_set_ambient_temperature(sensor, ambient); +... +``` + +## Usage + +First, the hardware configuration has to be established. This can differ +dependent on the communication interface and the number of sensors used. + +### Hardware configurations + +The driver supports multiple BME680 sensors at the same time that are +connected to I2C. Following figure show some possible hardware +configurations. + +First figure shows the configuration with only one sensor at I2C bus 0. + +```text + +------------------+ +----------+ + | ESP8266 / ESP32 | | BME680 | + | | | | + | GPIO 14 (SCL) ----> SCL | + | GPIO 13 (SDA) <---> SDA | + +------------------+ +----------+ +``` + +Next figure shows a possible configuration with two I2C buses. In that case, +the sensors can have same or different I2C slave addresses. + +```text + +------------------+ +----------+ + | ESP8266 / ESP32 | | BME680_1 | + | | | | + | GPIO 14 (SCL) ----> SCL | + | GPIO 13 (SDA) <---> SDA | + | | +----------+ + | | | BME680_2 | + | | | | + | GPIO 5 (SCL) ----> SCL | + | GPIO 4 (SDA) <---> SDA | + +------------------+ +----------+ +``` + +Further configurations are possible, e.g., two sensors that are connected +at the same I2C bus with different slave addresses. +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/bme680/bme680.c Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,1010 @@ +/* + * Copyright (c) 2017 Gunar Schorcht <https://github.com/gschorcht> + * Copyright (c) 2019 Ruslan V. Uss <unclerus@gmail.com> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of itscontributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * ESP-IDF driver for BME680 digital environmental sensor + * + * Forked from <https://github.com/gschorcht/bme680-esp-idf> + * + * Copyright (c) 2017 Gunar Schorcht <https://github.com/gschorcht>\n + * Copyright (c) 2019 Ruslan V. Uss <unclerus@gmail.com> + * + * BSD Licensed as described in the file LICENSE + */ +#include <string.h> +#include <inttypes.h> +#include <stdlib.h> +#include <esp_log.h> +#include <freertos/FreeRTOS.h> +#include <freertos/task.h> +#include <esp_idf_lib_helpers.h> +#include "bme680.h" + +#define I2C_FREQ_HZ 1000000 // Up to 3.4MHz, but esp-idf only supports 1MHz + +// modes: unfortunatly, only SLEEP_MODE and FORCED_MODE are documented +#define BME680_SLEEP_MODE 0x00 // low power sleeping +#define BME680_FORCED_MODE 0x01 // perform one TPHG cycle (field data 0 filled) +#define BME680_PARALLEL_MODE 0x02 // no information what it does :-( +#define BME680_SQUENTUAL_MODE 0x02 // no information what it does (field data 0+1+2 filled) + +// register addresses +#define BME680_REG_RES_HEAT_VAL 0x00 +#define BME680_REG_RES_HEAT_RANGE 0x02 +#define BME680_REG_RANGE_SW_ERROR 0x06 + +#define BME680_REG_IDAC_HEAT_BASE 0x50 // 10 regsrs idac_heat_0 ... idac_heat_9 +#define BME680_REG_RES_HEAT_BASE 0x5a // 10 registers res_heat_0 ... res_heat_9 +#define BME680_REG_GAS_WAIT_BASE 0x64 // 10 registers gas_wait_0 ... gas_wait_9 +#define BME680_REG_CTRL_GAS_0 0x70 +#define BME680_REG_CTRL_GAS_1 0x71 +#define BME680_REG_CTRL_HUM 0x72 +#define BME680_REG_STATUS 0x73 +#define BME680_REG_CTRL_MEAS 0x74 +#define BME680_REG_CONFIG 0x75 +#define BME680_REG_ID 0xd0 +#define BME680_REG_RESET 0xe0 + +// field data 0 registers +#define BME680_REG_MEAS_STATUS_0 0x1d +#define BME680_REG_MEAS_INDEX_0 0x1e +#define BME680_REG_PRESS_MSB_0 0x1f +#define BME680_REG_PRESS_LSB_0 0x20 +#define BME680_REG_PRESS_XLSB_0 0x21 +#define BME680_REG_TEMP_MSB_0 0x22 +#define BME680_REG_TEMP_LSB_0 0x23 +#define BME680_REG_TEMP_XLSB_0 0x24 +#define BME680_REG_HUM_MSB_0 0x25 +#define BME680_REG_HUM_LSB_0 0x26 +#define BME680_REG_GAS_R_MSB_0 0x2a +#define BME680_REG_GAS_R_LSB_0 0x2b + +// field data 1 registers (not documented, used in SEQUENTIAL_MODE) +#define BME680_REG_MEAS_STATUS_1 0x2e +#define BME680_REG_MEAS_INDEX_1 0x2f + +// field data 2 registers (not documented, used in SEQUENTIAL_MODE) +#define BME680_REG_MEAS_STATUS_2 0x3f +#define BME680_REG_MEAS_INDEX_2 0x40 + +// field data addresses +#define BME680_REG_RAW_DATA_0 BME680_REG_MEAS_STATUS_0 // 0x1d ... 0x2b +#define BME680_REG_RAW_DATA_1 BME680_REG_MEAS_STATUS_1 // 0x2e ... 0x3c +#define BME680_REG_RAW_DATA_2 BME680_REG_MEAS_STATUS_2 // 0x40 ... 0x4d +#define BME680_REG_RAW_DATA_LEN (BME680_REG_GAS_R_LSB_0 - BME680_REG_MEAS_STATUS_0 + 1) + +// calibration data registers +#define BME680_REG_CD1_ADDR 0x89 // 25 byte calibration data +#define BME680_REG_CD1_LEN 25 +#define BME680_REG_CD2_ADDR 0xe1 // 16 byte calibration data +#define BME680_REG_CD2_LEN 16 +#define BME680_REG_CD3_ADDR 0x00 // 8 byte device specific calibration data +#define BME680_REG_CD3_LEN 8 + +// register structure definitions +#define BME680_NEW_DATA_BITS 0x80 // BME680_REG_MEAS_STATUS<7> +#define BME680_NEW_DATA_SHIFT 7 // BME680_REG_MEAS_STATUS<7> +#define BME680_GAS_MEASURING_BITS 0x40 // BME680_REG_MEAS_STATUS<6> +#define BME680_GAS_MEASURING_SHIFT 6 // BME680_REG_MEAS_STATUS<6> +#define BME680_MEASURING_BITS 0x20 // BME680_REG_MEAS_STATUS<5> +#define BME680_MEASURING_SHIFT 5 // BME680_REG_MEAS_STATUS<5> +#define BME680_GAS_MEAS_INDEX_BITS 0x0f // BME680_REG_MEAS_STATUS<3:0> +#define BME680_GAS_MEAS_INDEX_SHIFT 0 // BME680_REG_MEAS_STATUS<3:0> + +#define BME680_GAS_R_LSB_BITS 0xc0 // BME680_REG_GAS_R_LSB<7:6> +#define BME680_GAS_R_LSB_SHIFT 6 // BME680_REG_GAS_R_LSB<7:6> +#define BME680_GAS_VALID_BITS 0x20 // BME680_REG_GAS_R_LSB<5> +#define BME680_GAS_VALID_SHIFT 5 // BME680_REG_GAS_R_LSB<5> +#define BME680_HEAT_STAB_R_BITS 0x10 // BME680_REG_GAS_R_LSB<4> +#define BME680_HEAT_STAB_R_SHIFT 4 // BME680_REG_GAS_R_LSB<4> +#define BME680_GAS_RANGE_R_BITS 0x0f // BME680_REG_GAS_R_LSB<3:0> +#define BME680_GAS_RANGE_R_SHIFT 0 // BME680_REG_GAS_R_LSB<3:0> + +#define BME680_HEAT_OFF_BITS 0x04 // BME680_REG_CTRL_GAS_0<3> +#define BME680_HEAT_OFF_SHIFT 3 // BME680_REG_CTRL_GAS_0<3> + +#define BME680_RUN_GAS_BITS 0x10 // BME680_REG_CTRL_GAS_1<4> +#define BME680_RUN_GAS_SHIFT 4 // BME680_REG_CTRL_GAS_1<4> +#define BME680_NB_CONV_BITS 0x0f // BME680_REG_CTRL_GAS_1<3:0> +#define BME680_NB_CONV_SHIFT 0 // BME680_REG_CTRL_GAS_1<3:0> + +#define BME680_SPI_3W_INT_EN_BITS 0x40 // BME680_REG_CTRL_HUM<6> +#define BME680_SPI_3W_INT_EN_SHIFT 6 // BME680_REG_CTRL_HUM<6> +#define BME680_OSR_H_BITS 0x07 // BME680_REG_CTRL_HUM<2:0> +#define BME680_OSR_H_SHIFT 0 // BME680_REG_CTRL_HUM<2:0> + +#define BME680_OSR_T_BITS 0xe0 // BME680_REG_CTRL_MEAS<7:5> +#define BME680_OSR_T_SHIFT 5 // BME680_REG_CTRL_MEAS<7:5> +#define BME680_OSR_P_BITS 0x1c // BME680_REG_CTRL_MEAS<4:2> +#define BME680_OSR_P_SHIFT 2 // BME680_REG_CTRL_MEAS<4:2> +#define BME680_MODE_BITS 0x03 // BME680_REG_CTRL_MEAS<1:0> +#define BME680_MODE_SHIFT 0 // BME680_REG_CTRL_MEAS<1:0> + +#define BME680_FILTER_BITS 0x1c // BME680_REG_CONFIG<4:2> +#define BME680_FILTER_SHIFT 2 // BME680_REG_CONFIG<4:2> +#define BME680_SPI_3W_EN_BITS 0x01 // BME680_REG_CONFIG<0> +#define BME680_SPI_3W_EN_SHIFT 0 // BME680_REG_CONFIG<0> + +#define BME680_SPI_MEM_PAGE_BITS 0x10 // BME680_REG_STATUS<4> +#define BME680_SPI_MEM_PAGE_SHIFT 4 // BME680_REG_STATUS<4> + +#define BME680_GAS_WAIT_BITS 0x3f // BME680_REG_GAS_WAIT+x<5:0> +#define BME680_GAS_WAIT_SHIFT 0 // BME680_REG_GAS_WAIT+x<5:0> +#define BME680_GAS_WAIT_MULT_BITS 0xc0 // BME680_REG_GAS_WAIT+x<7:6> +#define BME680_GAS_WAIT_MULT_SHIFT 6 // BME680_REG_GAS_WAIT+x<7:6> + +// commands +#define BME680_RESET_CMD 0xb6 // BME680_REG_RESET<7:0> +#define BME680_RESET_PERIOD 10 // reset time in ms + +#define BME680_RHR_BITS 0x30 // BME680_REG_RES_HEAT_RANGE<5:4> +#define BME680_RHR_SHIFT 4 // BME680_REG_RES_HEAT_RANGE<5:4> +#define BME680_RSWE_BITS 0xf0 // BME680_REG_RANGE_SW_ERROR<7:4> +#define BME680_RSWE_SHIFT 4 // BME680_REG_RANGE_SW_ERROR<7:4> + +// calibration data are stored in a calibration data map +#define BME680_CDM_SIZE (BME680_REG_CD1_LEN + BME680_REG_CD2_LEN + BME680_REG_CD3_LEN) +#define BME680_CDM_OFF1 0 +#define BME680_CDM_OFF2 BME680_REG_CD1_LEN +#define BME680_CDM_OFF3 BME680_CDM_OFF2 + BME680_REG_CD2_LEN + +// calibration parameter offsets in calibration data map +// calibration data from 0x89 +#define BME680_CDM_T2 1 +#define BME680_CDM_T3 3 +#define BME680_CDM_P1 5 +#define BME680_CDM_P2 7 +#define BME680_CDM_P3 9 +#define BME680_CDM_P4 11 +#define BME680_CDM_P5 13 +#define BME680_CDM_P7 15 +#define BME680_CDM_P6 16 +#define BME680_CDM_P8 19 +#define BME680_CDM_P9 21 +#define BME680_CDM_P10 23 +// calibration data from 0e1 +#define BME680_CDM_H2 25 +#define BME680_CDM_H1 26 +#define BME680_CDM_H3 28 +#define BME680_CDM_H4 29 +#define BME680_CDM_H5 30 +#define BME680_CDM_H6 31 +#define BME680_CDM_H7 32 +#define BME680_CDM_T1 33 +#define BME680_CDM_GH2 35 +#define BME680_CDM_GH1 37 +#define BME680_CDM_GH3 38 +// device specific calibration data from 0x00 +#define BME680_CDM_RHV 41 // 0x00 - res_heat_val +#define BME680_CDM_RHR 43 // 0x02 - res_heat_range +#define BME680_CDM_RSWE 45 // 0x04 - range_sw_error + +static const char *TAG = "bme680"; + +#define CHECK(x) do { esp_err_t __; if ((__ = x) != ESP_OK) return __; } while (0) +#define CHECK_ARG(VAL) do { if (!(VAL)) return ESP_ERR_INVALID_ARG; } while (0) +#define CHECK_LOGE(x, msg, ...) do { \ + esp_err_t __; \ + if ((__ = x) != ESP_OK) { \ + ESP_LOGE(TAG, msg, ## __VA_ARGS__); \ + return __; \ + } \ + } while (0) + +/** + * @brief Raw data (integer values) read from sensor + */ +typedef struct +{ + + bool gas_valid; // indicate that gas measurement results are valid + bool heater_stable; // indicate that heater temperature was stable + + uint32_t temperature; // degree celsius x100 + uint32_t pressure; // pressure in Pascal + uint16_t humidity; // relative humidity x1000 in % + uint16_t gas_resistance; // gas resistance data + uint8_t gas_range; // gas resistance range + + uint8_t gas_index; // heater profile used (0 ... 9) + uint8_t meas_index; + +} bme680_raw_data_t; + +#define lsb_msb_to_type(t,b,o) (t)(((t)b[o+1] << 8) | b[o]) +#define lsb_to_type(t,b,o) (t)(b[o]) +#define bme_set_reg_bit(byte, bitname, bit) ( (byte & ~bitname##_BITS) | \ + ((bit << bitname##_SHIFT) & bitname##_BITS) ) +#define bme_get_reg_bit(byte, bitname) ( (byte & bitname##_BITS) >> bitname##_SHIFT ) + +static inline esp_err_t read_reg_8_nolock(bme680_t *dev, uint8_t reg, uint8_t *data) +{ + return i2c_dev_read_reg(&dev->i2c_dev, reg, data, 1); +} + +static inline esp_err_t write_reg_8_nolock(bme680_t *dev, uint8_t reg, uint8_t data) +{ + return i2c_dev_write_reg(&dev->i2c_dev, reg, &data, 1); +} + +static esp_err_t read_reg_8(bme680_t *dev, uint8_t reg, uint8_t *data) +{ + I2C_DEV_TAKE_MUTEX(&dev->i2c_dev); + I2C_DEV_CHECK(&dev->i2c_dev, read_reg_8_nolock(dev, reg, data)); + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); + + return ESP_OK; +} + +static esp_err_t bme680_set_mode(bme680_t *dev, uint8_t mode) +{ + uint8_t reg; + + I2C_DEV_TAKE_MUTEX(&dev->i2c_dev); + I2C_DEV_CHECK(&dev->i2c_dev, read_reg_8_nolock(dev, BME680_REG_CTRL_MEAS, ®)); + reg = bme_set_reg_bit(reg, BME680_MODE, mode); + I2C_DEV_CHECK(&dev->i2c_dev, write_reg_8_nolock(dev, BME680_REG_CTRL_MEAS, reg)); + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); + + return ESP_OK; +} + +#define msb_lsb_xlsb_to_20bit(t,b,o) (t)((t) b[o] << 12 | (t) b[o+1] << 4 | b[o+2] >> 4) +#define msb_lsb_to_type(t,b,o) (t)(((t)b[o] << 8) | b[o+1]) + +#define BME680_RAW_P_OFF BME680_REG_PRESS_MSB_0-BME680_REG_MEAS_STATUS_0 +#define BME680_RAW_T_OFF (BME680_RAW_P_OFF + BME680_REG_TEMP_MSB_0 - BME680_REG_PRESS_MSB_0) +#define BME680_RAW_H_OFF (BME680_RAW_T_OFF + BME680_REG_HUM_MSB_0 - BME680_REG_TEMP_MSB_0) +#define BME680_RAW_G_OFF (BME680_RAW_H_OFF + BME680_REG_GAS_R_MSB_0 - BME680_REG_HUM_MSB_0) + +static esp_err_t bme680_get_raw_data(bme680_t *dev, bme680_raw_data_t *raw_data) +{ + if (!dev->meas_started) + { + ESP_LOGE(TAG, "Measurement was not started"); + return ESP_ERR_INVALID_STATE; + } + + uint8_t raw[BME680_REG_RAW_DATA_LEN] = { 0 }; + + if (!(dev->meas_status & BME680_NEW_DATA_BITS)) + { + // read measurement status from sensor + CHECK(read_reg_8(dev, BME680_REG_MEAS_STATUS_0, &dev->meas_status)); + // test whether there are new data + if (!(dev->meas_status & BME680_NEW_DATA_BITS)) + { + if (dev->meas_status & BME680_MEASURING_BITS) + { + ESP_LOGW(TAG, "Measurement is still running"); + return ESP_ERR_INVALID_STATE; + } + ESP_LOGW(TAG, "No new data"); + return ESP_ERR_INVALID_RESPONSE; + } + } + + dev->meas_started = false; + raw_data->gas_index = dev->meas_status & BME680_GAS_MEAS_INDEX_BITS; + + // if there are new data, read raw data from sensor + I2C_DEV_TAKE_MUTEX(&dev->i2c_dev); + I2C_DEV_CHECK(&dev->i2c_dev, i2c_dev_read_reg(&dev->i2c_dev, BME680_REG_RAW_DATA_0, raw, BME680_REG_RAW_DATA_LEN)); + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); + + raw_data->gas_valid = bme_get_reg_bit(raw[BME680_RAW_G_OFF + 1], BME680_GAS_VALID); + raw_data->heater_stable = bme_get_reg_bit(raw[BME680_RAW_G_OFF + 1], BME680_HEAT_STAB_R); + + raw_data->temperature = msb_lsb_xlsb_to_20bit(uint32_t, raw, BME680_RAW_T_OFF); + raw_data->pressure = msb_lsb_xlsb_to_20bit(uint32_t, raw, BME680_RAW_P_OFF); + raw_data->humidity = msb_lsb_to_type(uint16_t, raw, BME680_RAW_H_OFF); + raw_data->gas_resistance = ((uint16_t) raw[BME680_RAW_G_OFF] << 2) | raw[BME680_RAW_G_OFF + 1] >> 6; + raw_data->gas_range = raw[BME680_RAW_G_OFF + 1] & BME680_GAS_RANGE_R_BITS; + + /* + * BME680_REG_MEAS_STATUS_1, BME680_REG_MEAS_STATUS_2 + * These data are not documented and it is not really clear when they are filled + */ + ESP_LOGD(TAG, "Raw data: %" PRIu32 " %" PRIu32 " %d %d %d", raw_data->temperature, raw_data->pressure, + raw_data->humidity, raw_data->gas_resistance, raw_data->gas_range); + + return ESP_OK; +} + +/** + * @brief Calculate temperature from raw temperature value + * @ref BME280 datasheet, page 50 + */ +static int16_t bme680_convert_temperature(bme680_t *dev, uint32_t raw_temperature) +{ + bme680_calib_data_t *cd = &dev->calib_data; + + int64_t var1; + int64_t var2; + int16_t temperature; + + var1 = ((((raw_temperature >> 3) - ((int32_t) cd->par_t1 << 1))) * ((int32_t) cd->par_t2)) >> 11; + var2 = (((((raw_temperature >> 4) - ((int32_t) cd->par_t1)) * ((raw_temperature >> 4) - ((int32_t) cd->par_t1))) >> 12) + * ((int32_t) cd->par_t3)) >> 14; + cd->t_fine = (int32_t) (var1 + var2); + temperature = (cd->t_fine * 5 + 128) >> 8; + + return temperature; +} + +/** + * @brief Calculate pressure from raw pressure value + * @copyright Copyright (c) 2017 - 2018 Bosch Sensortec GmbH + * + * The algorithm was extracted from the original Bosch Sensortec BME680 driver + * published as open source. Divisions and multiplications by potences of 2 + * were replaced by shift operations for effeciency reasons. + * + * @ref [BME680_diver](https://github.com/BoschSensortec/BME680_driver) + * @ref BME280 datasheet, page 50 + */ +static uint32_t bme680_convert_pressure(bme680_t *dev, uint32_t raw_pressure) +{ + bme680_calib_data_t *cd = &dev->calib_data; + + int32_t var1; + int32_t var2; + int32_t var3; + int32_t pressure_comp; + + var1 = (((int32_t)cd->t_fine) >> 1) - 64000; + var2 = ((((var1 >> 2) * (var1 >> 2)) >> 11) * + (int32_t)cd->par_p6) >> 2; + var2 = var2 + ((var1 * (int32_t)cd->par_p5) << 1); + var2 = (var2 >> 2) + ((int32_t)cd->par_p4 << 16); + var1 = (((((var1 >> 2) * (var1 >> 2)) >> 13) * + ((int32_t)cd->par_p3 << 5)) >> 3) + + (((int32_t)cd->par_p2 * var1) >> 1); + var1 = var1 >> 18; + var1 = ((32768 + var1) * (int32_t)cd->par_p1) >> 15; + pressure_comp = 1048576 - raw_pressure; + pressure_comp = (int32_t)((pressure_comp - (var2 >> 12)) * ((uint32_t)3125)); + if (pressure_comp >= BME680_MAX_OVERFLOW_VAL) + pressure_comp = ((pressure_comp / var1) << 1); + else + pressure_comp = ((pressure_comp << 1) / var1); + var1 = ((int32_t)cd->par_p9 * (int32_t)(((pressure_comp >> 3) * + (pressure_comp >> 3)) >> 13)) >> 12; + var2 = ((int32_t)(pressure_comp >> 2) * + (int32_t)cd->par_p8) >> 13; + var3 = ((int32_t)(pressure_comp >> 8) * (int32_t)(pressure_comp >> 8) * + (int32_t)(pressure_comp >> 8) * + (int32_t)cd->par_p10) >> 17; + + pressure_comp = (int32_t)(pressure_comp) + ((var1 + var2 + var3 + + ((int32_t)cd->par_p7 << 7)) >> 4); + + return (uint32_t)pressure_comp; +} + +/** + * @brief Calculate humidty from raw humidity data + * @copyright Copyright (c) 2017 - 2018 Bosch Sensortec GmbH + * + * The algorithm was extracted from the original Bosch Sensortec BME680 driver + * published as open source. Divisions and multiplications by potences of 2 + * were replaced by shift operations for effeciency reasons. + * + * @ref [BME680_diver](https://github.com/BoschSensortec/BME680_driver) + */ +static uint32_t bme680_convert_humidity(bme680_t *dev, uint16_t raw_humidity) +{ + bme680_calib_data_t *cd = &dev->calib_data; + + int32_t var1; + int32_t var2; + int32_t var3; + int32_t var4; + int32_t var5; + int32_t var6; + int32_t temp_scaled; + int32_t humidity; + + temp_scaled = (((int32_t) cd->t_fine * 5) + 128) >> 8; + var1 = (int32_t) (raw_humidity - ((int32_t) ((int32_t) cd->par_h1 << 4))) + - (((temp_scaled * (int32_t) cd->par_h3) / ((int32_t) 100)) >> 1); + var2 = ((int32_t) cd->par_h2 + * (((temp_scaled * (int32_t) cd->par_h4) / ((int32_t) 100)) + + (((temp_scaled * ((temp_scaled * (int32_t) cd->par_h5) / ((int32_t) 100))) >> 6) / ((int32_t) 100)) + + (int32_t) (1 << 14))) >> 10; + var3 = var1 * var2; + var4 = (int32_t) cd->par_h6 << 7; + var4 = ((var4) + ((temp_scaled * (int32_t) cd->par_h7) / ((int32_t) 100))) >> 4; + var5 = ((var3 >> 14) * (var3 >> 14)) >> 10; + var6 = (var4 * var5) >> 1; + humidity = (((var3 + var6) >> 10) * ((int32_t) 1000)) >> 12; + + if (humidity > 100000) /* Cap at 100%rH */ + humidity = 100000; + else if (humidity < 0) + humidity = 0; + + return (uint32_t) humidity; +} + +/** + * @brief Lookup table for gas resitance computation + * @ref BME680 datasheet, page 19 + */ +static float lookup_table[16][2] = { + // const1, const2 // gas_range + { 1.0, 8000000.0 }, // 0 + { 1.0, 4000000.0 }, // 1 + { 1.0, 2000000.0 }, // 2 + { 1.0, 1000000.0 }, // 3 + { 1.0, 499500.4995 }, // 4 + { 0.99, 248262.1648 }, // 5 + { 1.0, 125000.0 }, // 6 + { 0.992, 63004.03226 }, // 7 + { 1.0, 31281.28128 }, // 8 + { 1.0, 15625.0 }, // 9 + { 0.998, 7812.5 }, // 10 + { 0.995, 3906.25 }, // 11 + { 1.0, 1953.125 }, // 12 + { 0.99, 976.5625 }, // 13 + { 1.0, 488.28125 }, // 14 + { 1.0, 244.140625 } // 15 +}; + +/** + * @brief Calculate gas resistance from raw gas resitance value and gas range + * @ref BME680 datasheet + */ +static uint32_t bme680_convert_gas(bme680_t *dev, uint16_t gas, uint8_t gas_range) +{ + bme680_calib_data_t *cd = &dev->calib_data; + + float var1 = (1340.0 + 5.0 * cd->range_sw_err) * lookup_table[gas_range][0]; + return var1 * lookup_table[gas_range][1] / (gas - 512.0 + var1); +} + +/** + * @brief Calculate internal duration representation + * + * Durations are internally representes as one byte + * + * duration = value<5:0> * multiplier<7:6> + * + * where the multiplier is 1, 4, 16, or 64. Maximum duration is therefore + * 64*64 = 4032 ms. The function takes a real world duration value given + * in milliseconds and computes the internal representation. + * + * @ref Datasheet + */ +static uint8_t bme680_heater_duration(uint16_t duration) +{ + uint8_t multiplier = 0; + + while (duration > 63) + { + duration = duration / 4; + multiplier++; + } + return (uint8_t) (duration | (multiplier << 6)); +} + +/** + * @brief Calculate internal heater resistance value from real temperature. + * + * @ref Datasheet of BME680 + */ +static uint8_t bme680_heater_resistance(const bme680_t *dev, uint16_t temp) +{ + if (!dev) + return 0; + + if (temp < BME680_HEATER_TEMP_MIN) + temp = BME680_HEATER_TEMP_MIN; + else if (temp > BME680_HEATER_TEMP_MAX) + temp = BME680_HEATER_TEMP_MAX; + + const bme680_calib_data_t *cd = &dev->calib_data; + + // from datasheet + double var1; + double var2; + double var3; + double var4; + double var5; + uint8_t res_heat_x; + + var1 = ((double) cd->par_gh1 / 16.0) + 49.0; + var2 = (((double) cd->par_gh2 / 32768.0) * 0.0005) + 0.00235; + var3 = (double) cd->par_gh3 / 1024.0; + var4 = var1 * (1.0 + (var2 * (double) temp)); + var5 = var4 + (var3 * (double) dev->settings.ambient_temperature); + res_heat_x = (uint8_t) (3.4 + * ((var5 * (4.0 / (4.0 + (double) cd->res_heat_range)) * (1.0 / (1.0 + ((double) cd->res_heat_val * 0.002)))) - 25)); + return res_heat_x; + +} + +/////////////////////////////////////////////////////////////////////////////// + +esp_err_t bme680_init_desc(bme680_t *dev, uint8_t addr, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio) +{ + CHECK_ARG(dev); + + if (addr != BME680_I2C_ADDR_0 && addr != BME680_I2C_ADDR_1) + { + ESP_LOGE(TAG, "Invalid I2C address"); + return ESP_ERR_INVALID_ARG; + } + + dev->i2c_dev.port = port; + dev->i2c_dev.addr = addr; + dev->i2c_dev.cfg.sda_io_num = sda_gpio; + dev->i2c_dev.cfg.scl_io_num = scl_gpio; +#if HELPER_TARGET_IS_ESP32 + dev->i2c_dev.cfg.master.clk_speed = I2C_FREQ_HZ; +#endif + + return i2c_dev_create_mutex(&dev->i2c_dev); +} + +esp_err_t bme680_free_desc(bme680_t *dev) +{ + CHECK_ARG(dev); + + return i2c_dev_delete_mutex(&dev->i2c_dev); +} + +esp_err_t bme680_init_sensor(bme680_t *dev) +{ + CHECK_ARG(dev); + + I2C_DEV_TAKE_MUTEX(&dev->i2c_dev); + + dev->meas_started = false; + dev->meas_status = 0; + dev->settings.ambient_temperature = 0; + dev->settings.osr_temperature = BME680_OSR_NONE; + dev->settings.osr_pressure = BME680_OSR_NONE; + dev->settings.osr_humidity = BME680_OSR_NONE; + dev->settings.filter_size = BME680_IIR_SIZE_0; + dev->settings.heater_profile = BME680_HEATER_NOT_USED; + memset(dev->settings.heater_temperature, 0, sizeof(uint16_t) * 10); + memset(dev->settings.heater_duration, 0, sizeof(uint16_t) * 10); + + // reset the sensor + I2C_DEV_CHECK(&dev->i2c_dev, write_reg_8_nolock(dev, BME680_REG_RESET, BME680_RESET_CMD)); + vTaskDelay(pdMS_TO_TICKS(BME680_RESET_PERIOD)); + + uint8_t chip_id = 0; + I2C_DEV_CHECK(&dev->i2c_dev, read_reg_8_nolock(dev, BME680_REG_ID, &chip_id)); + if (chip_id != 0x61) + { + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); + ESP_LOGE(TAG, "Chip id %02x is wrong, should be 0x61", chip_id); + return ESP_ERR_NOT_FOUND; + } + + uint8_t buf[BME680_CDM_SIZE]; + + I2C_DEV_CHECK(&dev->i2c_dev, i2c_dev_read_reg(&dev->i2c_dev, BME680_REG_CD1_ADDR, buf + BME680_CDM_OFF1, BME680_REG_CD1_LEN)); + I2C_DEV_CHECK(&dev->i2c_dev, i2c_dev_read_reg(&dev->i2c_dev, BME680_REG_CD2_ADDR, buf + BME680_CDM_OFF2, BME680_REG_CD2_LEN)); + I2C_DEV_CHECK(&dev->i2c_dev, i2c_dev_read_reg(&dev->i2c_dev, BME680_REG_CD3_ADDR, buf + BME680_CDM_OFF3, BME680_REG_CD3_LEN)); + + dev->calib_data.par_t1 = lsb_msb_to_type(uint16_t, buf, BME680_CDM_T1); + dev->calib_data.par_t2 = lsb_msb_to_type(int16_t, buf, BME680_CDM_T2); + dev->calib_data.par_t3 = lsb_to_type(int8_t, buf, BME680_CDM_T3); + + // pressure compensation parameters + dev->calib_data.par_p1 = lsb_msb_to_type(uint16_t, buf, BME680_CDM_P1); + dev->calib_data.par_p2 = lsb_msb_to_type(int16_t, buf, BME680_CDM_P2); + dev->calib_data.par_p3 = lsb_to_type(int8_t, buf, BME680_CDM_P3); + dev->calib_data.par_p4 = lsb_msb_to_type(int16_t, buf, BME680_CDM_P4); + dev->calib_data.par_p5 = lsb_msb_to_type(int16_t, buf, BME680_CDM_P5); + dev->calib_data.par_p6 = lsb_to_type(int8_t, buf, BME680_CDM_P6); + dev->calib_data.par_p7 = lsb_to_type(int8_t, buf, BME680_CDM_P7); + dev->calib_data.par_p8 = lsb_msb_to_type(int16_t, buf, BME680_CDM_P8); + dev->calib_data.par_p9 = lsb_msb_to_type(int16_t, buf, BME680_CDM_P9); + dev->calib_data.par_p10 = lsb_to_type(uint8_t, buf, BME680_CDM_P10); + + // humidity compensation parameters + dev->calib_data.par_h1 = (uint16_t) (((uint16_t) buf[BME680_CDM_H1 + 1] << 4) | (buf[BME680_CDM_H1] & 0x0F)); + dev->calib_data.par_h2 = (uint16_t) (((uint16_t) buf[BME680_CDM_H2] << 4) | (buf[BME680_CDM_H2 + 1] >> 4)); + dev->calib_data.par_h3 = lsb_to_type(int8_t, buf, BME680_CDM_H3); + dev->calib_data.par_h4 = lsb_to_type(int8_t, buf, BME680_CDM_H4); + dev->calib_data.par_h5 = lsb_to_type(int8_t, buf, BME680_CDM_H5); + dev->calib_data.par_h6 = lsb_to_type(uint8_t, buf, BME680_CDM_H6); + dev->calib_data.par_h7 = lsb_to_type(int8_t, buf, BME680_CDM_H7); + + // gas sensor compensation parameters + dev->calib_data.par_gh1 = lsb_to_type(int8_t, buf, BME680_CDM_GH1); + dev->calib_data.par_gh2 = lsb_msb_to_type(int16_t, buf, BME680_CDM_GH2); + dev->calib_data.par_gh3 = lsb_to_type(int8_t, buf, BME680_CDM_GH3); + + dev->calib_data.res_heat_range = (lsb_to_type(uint8_t, buf, BME680_CDM_RHR) & BME680_RHR_BITS) >> BME680_RHR_SHIFT; + dev->calib_data.res_heat_val = (lsb_to_type(int8_t, buf, BME680_CDM_RHV)); + dev->calib_data.range_sw_err = (lsb_to_type(int8_t, buf, BME680_CDM_RSWE) & BME680_RSWE_BITS) >> BME680_RSWE_SHIFT; + + // Set ambient temperature of sensor to default value (25 degree C) + dev->settings.ambient_temperature = 25; + + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); + + CHECK(bme680_set_oversampling_rates(dev, BME680_OSR_1X, BME680_OSR_1X, BME680_OSR_1X)); + CHECK(bme680_set_filter_size(dev, BME680_IIR_SIZE_3)); + + // Set heater default profile 0 to 320 degree Celcius for 150 ms + CHECK(bme680_set_heater_profile(dev, 0, 320, 150)); + CHECK(bme680_use_heater_profile(dev, 0)); + + return ESP_OK; +} + +esp_err_t bme680_force_measurement(bme680_t *dev) +{ + CHECK_ARG(dev); + if (dev->meas_started) + { + ESP_LOGE(TAG, "Measurement is already running"); + return ESP_ERR_INVALID_STATE; + } + + // Set the power mode to forced mode to trigger one TPHG measurement cycle + CHECK_LOGE(bme680_set_mode(dev, BME680_FORCED_MODE), + "Could not set forced mode to start TPHG measurement cycle"); + dev->meas_started = true; + dev->meas_status = 0; + + ESP_LOGD(TAG, "Started measurement"); + + return ESP_OK; +} + +/** + * @brief Estimate the measurement duration in RTOS ticks + * + * Timing formulas extracted from BME280 datasheet and test in some + * experiments. They represent the maximum measurement duration. + */ +esp_err_t bme680_get_measurement_duration(const bme680_t *dev, uint32_t *duration) +{ + CHECK_ARG(dev && duration); + + *duration = 0; /* Calculate in us */ + + // wake up duration from sleep into forced mode + *duration += 1250; + + // THP cycle duration which consumes 1963 µs for each measurement at maximum + if (dev->settings.osr_temperature) + *duration += (1 << (dev->settings.osr_temperature - 1)) * 2300; + if (dev->settings.osr_pressure) + *duration += (1 << (dev->settings.osr_pressure - 1)) * 2300 + 575; + if (dev->settings.osr_humidity) + *duration += (1 << (dev->settings.osr_humidity - 1)) * 2300 + 575; + + // if gas measurement is used + if (dev->settings.heater_profile != BME680_HEATER_NOT_USED && dev->settings.heater_duration[dev->settings.heater_profile] + && dev->settings.heater_temperature[dev->settings.heater_profile]) + { + // gas heating time + *duration += dev->settings.heater_duration[dev->settings.heater_profile] * 1000; + // gas measurement duration; + *duration += 2300 + 575; + } + + // round up to next ms (1 us ... 1000 us => 1 ms) + *duration += 999; + *duration /= 1000; + + // some ms tolerance + *duration += 5; + + // ceil to next integer value that is divisible by portTICK_PERIOD_MS and + // compute RTOS ticks (1 ... portTICK_PERIOD_MS = 1 tick) + *duration = (*duration + portTICK_PERIOD_MS - 1) / portTICK_PERIOD_MS; + + // Since first RTOS tick can be shorter than the half of defined tick period, + // the delay caused by vTaskDelay(duration) might be 1 or 2 ms shorter than + // computed duration in rare cases. Since the duration is computed for maximum + // and not for the typical durations and therefore tends to be too long, this + // should not be a problem. Therefore, only one additional tick used. + *duration += 1; + + return ESP_OK; +} + +esp_err_t bme680_is_measuring(bme680_t *dev, bool *busy) +{ + CHECK_ARG(dev && busy); + + // if measurement wasn't started, it is of course not measuring + if (!dev->meas_started) + { + *busy = false; + return ESP_OK; + } + + CHECK(read_reg_8(dev, BME680_REG_MEAS_STATUS_0, &dev->meas_status)); + *busy = dev->meas_status & BME680_MEASURING_BITS ? 1 : 0; + + return ESP_OK; +} + +esp_err_t bme680_get_results_fixed(bme680_t *dev, bme680_values_fixed_t *results) +{ + CHECK_ARG(dev && results); + + // fill data structure with invalid values + results->temperature = INT16_MIN; + results->pressure = 0; + results->humidity = 0; + results->gas_resistance = 0; + + bme680_raw_data_t raw; + CHECK(bme680_get_raw_data(dev, &raw)); + + // use compensation algorithms to compute sensor values in fixed point format + if (dev->settings.osr_temperature) + results->temperature = bme680_convert_temperature(dev, raw.temperature); + if (dev->settings.osr_pressure) + results->pressure = bme680_convert_pressure(dev, raw.pressure); + if (dev->settings.osr_humidity) + results->humidity = bme680_convert_humidity(dev, raw.humidity); + + if (dev->settings.heater_profile != BME680_HEATER_NOT_USED) + { + // convert gas only if raw data are valid and heater was stable + if (raw.gas_valid && raw.heater_stable) + results->gas_resistance = bme680_convert_gas(dev, raw.gas_resistance, raw.gas_range); + else if (!raw.gas_valid) + ESP_LOGW(TAG, "Gas data is not valid"); + else + ESP_LOGW(TAG, "Heater is not stable"); + } + + ESP_LOGD(TAG, "Fixed point sensor values - %d/100 deg.C, %" PRIu32 "/1000 %%, %" PRIu32 " Pa, %" PRIu32 " Ohm", + results->temperature, results->humidity, results->pressure, results->gas_resistance); + + return ESP_OK; +} + +esp_err_t bme680_get_results_float(bme680_t *dev, bme680_values_float_t *results) +{ + CHECK_ARG(dev && results); + + bme680_values_fixed_t fixed; + CHECK(bme680_get_results_fixed(dev, &fixed)); + + results->temperature = fixed.temperature / 100.0f; + results->pressure = fixed.pressure / 100.0f; + results->humidity = fixed.humidity / 1000.0f; + results->gas_resistance = fixed.gas_resistance; + + return ESP_OK; +} + +esp_err_t bme680_measure_fixed(bme680_t *dev, bme680_values_fixed_t *results) +{ + CHECK_ARG(dev && results); + + uint32_t duration; + CHECK(bme680_get_measurement_duration(dev, &duration)); + if (duration == 0) + { + ESP_LOGE(TAG, "Failed to get measurement duration"); + return ESP_FAIL; + } + + CHECK(bme680_force_measurement(dev)); + vTaskDelay(duration); + + return bme680_get_results_fixed(dev, results); +} + +esp_err_t bme680_measure_float(bme680_t *dev, bme680_values_float_t *results) +{ + CHECK_ARG(dev && results); + + uint32_t duration; + CHECK(bme680_get_measurement_duration(dev, &duration)); + if (duration == 0) + { + ESP_LOGE(TAG, "Failed to get measurement duration"); + return ESP_FAIL; + } + + CHECK(bme680_force_measurement(dev)); + vTaskDelay(duration); + + return bme680_get_results_float(dev, results); +} + +esp_err_t bme680_set_oversampling_rates(bme680_t *dev, bme680_oversampling_rate_t ost, + bme680_oversampling_rate_t osp, bme680_oversampling_rate_t osh) +{ + CHECK_ARG(dev); + + bool ost_changed = dev->settings.osr_temperature != ost; + bool osp_changed = dev->settings.osr_pressure != osp; + bool osh_changed = dev->settings.osr_humidity != osh; + + if (!ost_changed && !osp_changed && !osh_changed) + return ESP_OK; + + // Set the temperature, pressure and humidity oversampling + dev->settings.osr_temperature = ost; + dev->settings.osr_pressure = osp; + dev->settings.osr_humidity = osh; + + uint8_t reg; + + I2C_DEV_TAKE_MUTEX(&dev->i2c_dev); + if (ost_changed || osp_changed) + { + // read the current register value + I2C_DEV_CHECK(&dev->i2c_dev, read_reg_8_nolock(dev, BME680_REG_CTRL_MEAS, ®)); + + // set changed bit values + if (ost_changed) + reg = bme_set_reg_bit(reg, BME680_OSR_T, ost); + if (osp_changed) + reg = bme_set_reg_bit(reg, BME680_OSR_P, osp); + + // write back the new register value + I2C_DEV_CHECK(&dev->i2c_dev, write_reg_8_nolock(dev, BME680_REG_CTRL_MEAS, reg)); + } + if (osh_changed) + { + // read the current register value + I2C_DEV_CHECK(&dev->i2c_dev, read_reg_8_nolock(dev, BME680_REG_CTRL_HUM, ®)); + + // set changed bit value + reg = bme_set_reg_bit(reg, BME680_OSR_H, osh); + + // write back the new register value + I2C_DEV_CHECK(&dev->i2c_dev, write_reg_8_nolock(dev, BME680_REG_CTRL_HUM, reg)); + } + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); + + ESP_LOGD(TAG, "Setting oversampling rates done: osrt=%d osp=%d osrh=%d", + dev->settings.osr_temperature, dev->settings.osr_pressure, dev->settings.osr_humidity); + + return ESP_OK; +} + +esp_err_t bme680_set_filter_size(bme680_t *dev, bme680_filter_size_t size) +{ + CHECK_ARG(dev); + + if (dev->settings.filter_size == size) + return ESP_OK; + + /* Set the temperature, pressure and humidity settings */ + dev->settings.filter_size = size; + + uint8_t reg; + I2C_DEV_TAKE_MUTEX(&dev->i2c_dev); + + // read the current register value + I2C_DEV_CHECK(&dev->i2c_dev, read_reg_8_nolock(dev, BME680_REG_CONFIG, ®)); + // set changed bit value + reg = bme_set_reg_bit(reg, BME680_FILTER, size); + // write back the new register value + I2C_DEV_CHECK(&dev->i2c_dev, write_reg_8_nolock(dev, BME680_REG_CONFIG, reg)); + + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); + + ESP_LOGD(TAG, "Setting filter size done: size=%d", dev->settings.filter_size); + + return ESP_OK; +} + +esp_err_t bme680_set_heater_profile(bme680_t *dev, uint8_t profile, uint16_t temperature, uint16_t duration) +{ + CHECK_ARG(dev && profile < BME680_HEATER_PROFILES); + + bool temperature_changed = dev->settings.heater_temperature[profile] != temperature; + bool duration_changed = dev->settings.heater_duration[profile] != duration; + + if (!temperature_changed && !duration_changed) + return ESP_OK; + + // set external gas sensor configuration + dev->settings.heater_temperature[profile] = temperature; // degree Celsius + dev->settings.heater_duration[profile] = duration; // milliseconds + + // compute internal gas sensor configuration parameters + uint8_t heat_dur = bme680_heater_duration(duration); // internal duration value + uint8_t heat_res = bme680_heater_resistance(dev, temperature); // internal temperature value + + I2C_DEV_TAKE_MUTEX(&dev->i2c_dev); + // set internal gas sensor configuration parameters if changed + if (temperature_changed) + I2C_DEV_CHECK(&dev->i2c_dev, write_reg_8_nolock(dev, BME680_REG_RES_HEAT_BASE + profile, heat_res)); + if (duration_changed) + I2C_DEV_CHECK(&dev->i2c_dev, write_reg_8_nolock(dev, BME680_REG_GAS_WAIT_BASE + profile, heat_dur)); + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); + + + ESP_LOGD(TAG, "Setting heater profile %d done: temperature=%d duration=%d" + " heater_resistance=%02x heater_duration=%02x", profile, dev->settings.heater_temperature[profile], + dev->settings.heater_duration[profile], heat_dur, heat_res); + + return ESP_OK; +} + +esp_err_t bme680_use_heater_profile(bme680_t *dev, int8_t profile) +{ + CHECK_ARG(dev); + CHECK_ARG(profile >= -1 && profile < BME680_HEATER_PROFILES); + + if (dev->settings.heater_profile == profile) + return ESP_OK; + + dev->settings.heater_profile = profile; + + uint8_t reg = 0; // set + // set active profile + reg = bme_set_reg_bit(reg, BME680_NB_CONV, profile != BME680_HEATER_NOT_USED ? profile : 0); + + // enable or disable gas measurement + reg = bme_set_reg_bit(reg, BME680_RUN_GAS, + (profile != BME680_HEATER_NOT_USED && dev->settings.heater_temperature[profile] && dev->settings.heater_duration[profile])); + + I2C_DEV_TAKE_MUTEX(&dev->i2c_dev); + I2C_DEV_CHECK(&dev->i2c_dev, write_reg_8_nolock(dev, BME680_REG_CTRL_GAS_1, reg)); + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); + + return ESP_OK; +} + +esp_err_t bme680_set_ambient_temperature(bme680_t *dev, int16_t ambient) +{ + CHECK_ARG(dev); + + if (dev->settings.ambient_temperature == ambient) + return ESP_OK; + + // set ambient temperature configuration + dev->settings.ambient_temperature = ambient; // degree Celsius + + // update all valid heater profiles + uint8_t data[10]; + for (int i = 0; i < BME680_HEATER_PROFILES; i++) + data[i] = dev->settings.heater_temperature[i] + ? bme680_heater_resistance(dev, dev->settings.heater_temperature[i]) + : 0; + + I2C_DEV_TAKE_MUTEX(&dev->i2c_dev); + I2C_DEV_CHECK(&dev->i2c_dev, i2c_dev_write_reg(&dev->i2c_dev, BME680_REG_RES_HEAT_BASE, data, 10)); + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); + + ESP_LOGD(TAG, "Setting heater ambient temperature done: ambient=%d", dev->settings.ambient_temperature); + + return ESP_OK; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/bme680/bme680.h Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,449 @@ +/* + * Copyright (c) 2017 Gunar Schorcht <https://github.com/gschorcht> + * Copyright (c) 2019 Ruslan V. Uss <unclerus@gmail.com> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of itscontributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file bme680.h + * @defgroup bme680 bme680 + * @{ + * + * ESP-IDF driver for BME680 digital environmental sensor + * + * Forked from <https://github.com/gschorcht/bme680-esp-idf> + * + * Copyright (c) 2017 Gunar Schorcht <https://github.com/gschorcht>\n + * Copyright (c) 2019 Ruslan V. Uss <unclerus@gmail.com> + * + * BSD Licensed as described in the file LICENSE + */ +#ifndef __BME680_H__ +#define __BME680_H__ + +#include <stdbool.h> +#include <i2cdev.h> +#include <esp_err.h> + +#ifdef __cplusplus +extern "C" { +#endif + +#define BME680_I2C_ADDR_0 0x76 +#define BME680_I2C_ADDR_1 0x77 + +#define BME680_MAX_OVERFLOW_VAL INT32_C(0x40000000) // overflow value used in pressure calculation (bme680_convert_pressure) + +#define BME680_HEATER_TEMP_MIN 200 //!< min. 200 degree Celsius +#define BME680_HEATER_TEMP_MAX 400 //!< max. 200 degree Celsius +#define BME680_HEATER_PROFILES 10 //!< max. 10 heater profiles 0 ... 9 +#define BME680_HEATER_NOT_USED -1 //!< heater not used profile + +/** + * Fixed point sensor values (fixed THPG values) + */ +typedef struct +{ + int16_t temperature; //!< temperature in degree C * 100 (Invalid value INT16_MIN) + uint32_t pressure; //!< barometric pressure in Pascal (Invalid value 0) + uint32_t humidity; //!< relative humidity in % * 1000 (Invalid value 0) + uint32_t gas_resistance; //!< gas resistance in Ohm (Invalid value 0) +} bme680_values_fixed_t; + +/** + * Floating point sensor values (real THPG values) + */ +typedef struct +{ + float temperature; //!< temperature in degree C (Invalid value -327.68) + float pressure; //!< barometric pressure in hPascal (Invalid value 0.0) + float humidity; //!< relative humidity in % (Invalid value 0.0) + float gas_resistance; //!< gas resistance in Ohm (Invalid value 0.0) +} bme680_values_float_t; + +/** + * Filter size + */ +typedef enum { + BME680_IIR_SIZE_0 = 0, //!< Filter is not used + BME680_IIR_SIZE_1, + BME680_IIR_SIZE_3, + BME680_IIR_SIZE_7, + BME680_IIR_SIZE_15, + BME680_IIR_SIZE_31, + BME680_IIR_SIZE_63, + BME680_IIR_SIZE_127 +} bme680_filter_size_t; + +/** + * Oversampling rate + */ +typedef enum { + BME680_OSR_NONE = 0, //!< Measurement is skipped, output values are invalid + BME680_OSR_1X, //!< Default oversampling rates + BME680_OSR_2X, + BME680_OSR_4X, + BME680_OSR_8X, + BME680_OSR_16X +} bme680_oversampling_rate_t; + +/** + * @brief Sensor parameters that configure the TPHG measurement cycle + * + * T - temperature measurement + * P - pressure measurement + * H - humidity measurement + * G - gas measurement + */ +typedef struct +{ + bme680_oversampling_rate_t osr_temperature; //!< T oversampling rate (default `BME680_OSR_1X`) + bme680_oversampling_rate_t osr_pressure; //!< P oversampling rate (default `BME680_OSR_1X`) + bme680_oversampling_rate_t osr_humidity; //!< H oversampling rate (default `BME680_OSR_1X`) + bme680_filter_size_t filter_size; //!< IIR filter size (default `BME680_IIR_SIZE_3`) + + int8_t heater_profile; //!< Heater profile used (default 0) + uint16_t heater_temperature[10]; //!< Heater temperature for G (default 320) + uint16_t heater_duration[10]; //!< Heater duration for G (default 150) + + int8_t ambient_temperature; //!< Ambient temperature for G (default 25) +} bme680_settings_t; + +/** + * @brief Data structure for calibration parameters + * + * These calibration parameters are used in compensation algorithms to convert + * raw sensor data to measurement results. + */ +typedef struct +{ + uint16_t par_t1; //!< calibration data for temperature compensation + int16_t par_t2; + int8_t par_t3; + + uint16_t par_p1; //!< calibration data for pressure compensation + int16_t par_p2; + int8_t par_p3; + int16_t par_p4; + int16_t par_p5; + int8_t par_p7; + int8_t par_p6; + int16_t par_p8; + int16_t par_p9; + uint8_t par_p10; + + uint16_t par_h1; //!< calibration data for humidity compensation + uint16_t par_h2; + int8_t par_h3; + int8_t par_h4; + int8_t par_h5; + uint8_t par_h6; + int8_t par_h7; + + int8_t par_gh1; //!< calibration data for gas compensation + int16_t par_gh2; + int8_t par_gh3; + + int32_t t_fine; //!< temperature correction factor for P and G + uint8_t res_heat_range; + int8_t res_heat_val; + int8_t range_sw_err; +} bme680_calib_data_t; + +/** + * BME680 sensor device data structure type + */ +typedef struct +{ + i2c_dev_t i2c_dev; //!< I2C device descriptor + + bool meas_started; //!< Indicates whether measurement started + uint8_t meas_status; //!< Last sensor status (for internal use only) + + bme680_settings_t settings; //!< Sensor settings + bme680_calib_data_t calib_data; //!< Calibration data of the sensor +} bme680_t; + +/** + * @brief Initialize device descriptor + * + * @param dev Device descriptor + * @param addr BME680 address + * @param port I2C port number + * @param sda_gpio GPIO pin for SDA + * @param scl_gpio GPIO pin for SCL + * @return `ESP_OK` on success + */ +esp_err_t bme680_init_desc(bme680_t *dev, uint8_t addr, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio); + +/** + * @brief Free device descriptor + * + * @param dev Device descriptor + * @return `ESP_OK` on success + */ +esp_err_t bme680_free_desc(bme680_t *dev); + +/** + * @brief Initialize a BME680 sensor + * + * The function initializes the sensor device data structure, probes the + * sensor, soft resets the sensor, and configures the sensor with the + * the following default settings: + * + * - Oversampling rate for temperature, pressure, humidity is osr_1x + * - Filter size for pressure and temperature is iir_size 3 + * - Heater profile 0 with 320 degree C and 150 ms duration + * + * The sensor must be connected to an I2C bus. + * + * @param dev Device descriptor + * @return `ESP_OK` on success + */ +esp_err_t bme680_init_sensor(bme680_t *dev); + +/** + * @brief Force one single TPHG measurement + * + * The function triggers the sensor to start one THPG measurement cycle. + * Parameters for the measurement like oversampling rates, IIR filter sizes + * and heater profile can be configured before. + * + * Once the TPHG measurement is started, the user task has to wait for the + * results. The duration of the TPHG measurement can be determined with + * function *bme680_get_measurement_duration*. + * + * @param dev Device descriptor + * @return `ESP_OK` on success + */ +esp_err_t bme680_force_measurement(bme680_t *dev); + +/** + * @brief Get estimated duration of a TPHG measurement + * + * The function returns an estimated duration of the TPHG measurement cycle + * in RTOS ticks for the current configuration of the sensor. + * + * This duration is the time required by the sensor for one TPHG measurement + * until the results are available. It strongly depends on which measurements + * are performed in the THPG measurement cycle and what configuration + * parameters were set. It can vary from 1 RTOS (10 ms) tick up to 4500 RTOS + * ticks (4.5 seconds). + * + * If the measurement configuration is not changed, the duration can be + * considered as constant. + * + * @param dev Device descriptor + * @param[out] duration Duration of TPHG measurement cycle in ticks or 0 on error + * @return `ESP_OK` on success + */ +esp_err_t bme680_get_measurement_duration(const bme680_t *dev, uint32_t *duration); + +/** + * @brief Get the measurement status + * + * The function can be used to test whether a measurement that was started + * before is still running. + * + * @param dev Device descriptor + * @param[out] busy true if measurement is still running or false otherwise + * @return `ESP_OK` on success + */ +esp_err_t bme680_is_measuring(bme680_t *dev, bool *busy); + +/** + * @brief Get results of a measurement in fixed point representation + * + * The function returns the results of a TPHG measurement that has been + * started before. If the measurement is still running, the function fails + * and returns invalid values (see type declaration). + * + * @param dev Device descriptor + * @param[out] results pointer to a data structure that is filled with results + * @return `ESP_OK` on success + */ +esp_err_t bme680_get_results_fixed(bme680_t *dev, bme680_values_fixed_t *results); + +/** + * @brief Get results of a measurement in floating point representation + * + * The function returns the results of a TPHG measurement that has been + * started before. If the measurement is still running, the function fails + * and returns invalid values (see type declaration). + * + * @param dev Device descriptor + * @param[out] results pointer to a data structure that is filled with results + * @return `ESP_OK` on success + */ +esp_err_t bme680_get_results_float(bme680_t *dev, bme680_values_float_t *results); + +/** + * @brief Start a measurement, wait and return the results (fixed point) + * + * This function is a combination of functions above. For convenience it + * starts a TPHG measurement using ::bme680_force_measurement(), then it waits + * the measurement duration for the results using `vTaskDelay()` and finally it + * returns the results using function ::bme680_get_results_fixed(). + * + * Note: Since the calling task is delayed using function `vTaskDelay()`, this + * function must not be used when it is called from a software timer callback + * function. + * + * @param dev Device descriptor + * @param[out] results pointer to a data structure that is filled with results + * @return `ESP_OK` on success + */ +esp_err_t bme680_measure_fixed(bme680_t *dev, bme680_values_fixed_t *results); + +/** + * @brief Start a measurement, wait and return the results (floating point) + * + * This function is a combination of functions above. For convenience it + * starts a TPHG measurement using ::bme680_force_measurement(), then it waits + * the measurement duration for the results using `vTaskDelay` and finally it + * returns the results using function ::bme680_get_results_float(). + * + * Note: Since the calling task is delayed using function `vTaskDelay()`, this + * function must not be used when it is called from a software timer callback + * function. + * + * @param dev Device descriptor + * @param[out] results pointer to a data structure that is filled with results + * @return `ESP_OK` on success + */ +esp_err_t bme680_measure_float(bme680_t *dev, bme680_values_float_t *results); + +/** + * @brief Set the oversampling rates for measurements + * + * The BME680 sensor allows to define individual oversampling rates for + * the measurements of temperature, pressure and humidity. Using an + * oversampling rate of *osr*, the resolution of raw sensor data can be + * increased by ld(*osr*) bits. + * + * Possible oversampling rates are 1x (default), 2x, 4x, 8x, 16x, see type + * ::bme680_oversampling_rate_t. The default oversampling rate is 1. + * + * Please note: Use ::BME680_OSR_NONE to skip the corresponding measurement. + * + * @param dev Device descriptor + * @param osr_t oversampling rate for temperature measurements + * @param osr_p oversampling rate for pressure measurements + * @param osr_h oversampling rate for humidity measurements + * @return `ESP_OK` on success + */ +esp_err_t bme680_set_oversampling_rates(bme680_t *dev, bme680_oversampling_rate_t osr_t, + bme680_oversampling_rate_t osr_p, bme680_oversampling_rate_t osr_h); + +/** + * @brief Set the size of the IIR filter + * + * The sensor integrates an internal IIR filter (low pass filter) to reduce + * short-term changes in sensor output values caused by external disturbances. + * It effectively reduces the bandwidth of the sensor output values. + * + * The filter can optionally be used for pressure and temperature data that + * are subject to many short-term changes. Using the IIR filter, increases the + * resolution of pressure and temperature data to 20 bit. Humidity and gas + * inside the sensor does not fluctuate rapidly and does not require such a + * low pass filtering. + * + * The default filter size is 3 (::BME680_IIR_SIZE_3). + * + * Please note: If the size of the filter is 0, the filter is not used. + * + * @param dev Device descriptor + * @param size IIR filter size + * @return `ESP_OK` on success + */ +esp_err_t bme680_set_filter_size(bme680_t *dev, bme680_filter_size_t size); + +/** + * @brief Set a heater profile for gas measurements + * + * The sensor integrates a heater for the gas measurement. Parameters for this + * heater are defined by so called heater profiles. The sensor supports up to + * 10 heater profiles, which are numbered from 0 to 9. Each profile consists of + * a temperature set-point (the target temperature) and a heating duration. + * + * This function sets the parameters for one of the heater profiles 0 ... 9. + * To activate the gas measurement with this profile, use function + * ::bme680_use_heater_profile(), see below. + * + * Please note: According to the data sheet, a target temperatures of between + * 200 and 400 degrees Celsius are typical and about 20 to 30 ms are necessary + * for the heater to reach the desired target temperature. + * + * @param dev Device descriptor + * @param profile heater profile 0 ... 9 + * @param temperature target temperature in degree Celsius + * @param duration heating duration in milliseconds + * @return `ESP_OK` on success + */ +esp_err_t bme680_set_heater_profile(bme680_t *dev, uint8_t profile, uint16_t temperature, uint16_t duration); + +/** + * @brief Activate gas measurement with a given heater profile + * + * The function activates the gas measurement with one of the heater + * profiles 0 ... 9 or deactivates the gas measurement completely when + * -1 or ::BME680_HEATER_NOT_USED is used as heater profile. + * + * Parameters of the activated heater profile have to be set before with + * function ::bme680_set_heater_profile() otherwise the function fails. + * + * If several heater profiles have been defined with function + * ::bme680_set_heater_profile(), a sequence of gas measurements with different + * heater parameters can be realized by a sequence of activations of different + * heater profiles for successive TPHG measurements using this function. + * + * @param dev Device descriptor + * @param profile 0 ... 9 to activate or -1 to deactivate gas measure + * @return `ESP_OK` on success + */ +esp_err_t bme680_use_heater_profile(bme680_t *dev, int8_t profile); + +/** + * @brief Set ambient temperature + * + * The heater resistance calculation algorithm takes into account the ambient + * temperature of the sensor. This function can be used to set this ambient + * temperature. Either values determined from the sensor itself or from + * another temperature sensor can be used. The default ambient temperature + * is 25 degree Celsius. + * + * @param dev Device descriptor + * @param temperature ambient temperature in degree Celsius + * @return `ESP_OK` on success + */ +esp_err_t bme680_set_ambient_temperature(bme680_t *dev, int16_t temperature); + +#ifdef __cplusplus +} +#endif + +/**@}*/ + +#endif /* __BME680_H__ */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/bme680/component.mk Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,2 @@ +COMPONENT_ADD_INCLUDEDIRS = . +COMPONENT_DEPENDS = i2cdev log esp_idf_lib_helpers
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/bmp180/.eil.yml Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,28 @@ +--- +components: + - name: bmp180 + description: | + Driver for BMP180 digital pressure sensor + group: + name: pressure + groups: + - name: temperature + code_owners: + - name: UncleRus + depends: + - name: i2cdev + - name: log + - name: esp_idf_lib_helpers + thread_safe: yes + targets: + - name: esp32 + - name: esp8266 + - name: esp32s2 + - name: esp32c3 + licenses: + - name: MIT + copyrights: + - name: UncleRus + year: 2018 + - name: FrankB + year: 2015
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/bmp180/CMakeLists.txt Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,5 @@ +idf_component_register( + SRCS bmp180.c + INCLUDE_DIRS . + REQUIRES i2cdev log esp_idf_lib_helpers +)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/bmp180/LICENSE Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2015 Frank Bargstedt +Copyright (c) 2018 Ruslan V. Uss <unclerus@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/bmp180/bmp180.c Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,263 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2015 Frank Bargstedt + * Copyright (c) 2018 Ruslan V. Uss <unclerus@gmail.com> + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +/** + * @file bmp180.c + * + * ESP-IDF driver for BMP180 digital pressure sensor + * + * Ported from esp-open-rtos + * + * Copyright (c) 2015 Frank Bargstedt\n + * Copyright (c) 2018 Ruslan V. Uss <unclerus@gmail.com> + * + * MIT Licensed as described in the file LICENSE + */ +#include "bmp180.h" +#include <inttypes.h> +#include <esp_err.h> +#include <esp_log.h> +#include <ets_sys.h> +#include <esp_idf_lib_helpers.h> + +#define I2C_FREQ_HZ 1000000 // Max 1MHz for esp-idf + +static const char *TAG = "bmp180"; + +#define BMP180_RX_QUEUE_SIZE 10 +#define BMP180_TASK_PRIORITY 9 + +#define BMP180_VERSION_REG 0xD0 +#define BMP180_CONTROL_REG 0xF4 +#define BMP180_RESET_REG 0xE0 +#define BMP180_OUT_MSB_REG 0xF6 +#define BMP180_OUT_LSB_REG 0xF7 +#define BMP180_OUT_XLSB_REG 0xF8 + +#define BMP180_CALIBRATION_REG 0xAA + +// Values for BMP180_CONTROL_REG +#define BMP180_MEASURE_TEMP 0x2E +#define BMP180_MEASURE_PRESS 0x34 + +// CHIP ID stored in BMP180_VERSION_REG +#define BMP180_CHIP_ID 0x55 + +// Reset value for BMP180_RESET_REG +#define BMP180_RESET_VALUE 0xB6 + +#define CHECK(x) do { esp_err_t __; if ((__ = x) != ESP_OK) return __; } while (0) +#define CHECK_ARG(VAL) do { if (!(VAL)) return ESP_ERR_INVALID_ARG; } while (0) + +static esp_err_t bmp180_read_reg_16(i2c_dev_t *dev, uint8_t reg, int16_t *r) +{ + uint8_t d[] = { 0, 0 }; + + CHECK(i2c_dev_read_reg(dev, reg, d, 2)); + *r = ((int16_t)d[0] << 8) | (d[1]); + + return ESP_OK; +} + +static inline esp_err_t bmp180_start_measurement(i2c_dev_t *dev, uint8_t cmd) +{ + return i2c_dev_write_reg(dev, BMP180_CONTROL_REG, &cmd, 1); +} + +static esp_err_t bmp180_get_uncompensated_temperature(i2c_dev_t *dev, int32_t *ut) +{ + // Write Start Code into reg 0xF4. + CHECK(bmp180_start_measurement(dev, BMP180_MEASURE_TEMP)); + + // Wait 5ms, datasheet states 4.5ms + ets_delay_us(5000); + + int16_t v; + CHECK(bmp180_read_reg_16(dev, BMP180_OUT_MSB_REG, &v)); + *ut = v; + return ESP_OK; +} + +static esp_err_t bmp180_get_uncompensated_pressure(i2c_dev_t *dev, bmp180_mode_t oss, uint32_t *up) +{ + uint16_t us; + + // Limit oss and set the measurement wait time. The datasheet + // states 4.5, 7.5, 13.5, 25.5ms for oss 0 to 3. + switch (oss) + { + case BMP180_MODE_ULTRA_LOW_POWER: us = 5000; break; + case BMP180_MODE_STANDARD: us = 8000; break; + case BMP180_MODE_HIGH_RESOLUTION: us = 14000; break; + default: oss = BMP180_MODE_ULTRA_HIGH_RESOLUTION; us = 26000; break; + } + + // Write Start Code into reg 0xF4 + CHECK(bmp180_start_measurement(dev, BMP180_MEASURE_PRESS | (oss << 6))); + + ets_delay_us(us); + + uint8_t d[] = { 0, 0, 0 }; + uint8_t reg = BMP180_OUT_MSB_REG; + CHECK(i2c_dev_read_reg(dev, reg, d, 3)); + + uint32_t r = ((uint32_t)d[0] << 16) | ((uint32_t)d[1] << 8) | d[2]; + r >>= 8 - oss; + *up = r; + + return ESP_OK; +} + +esp_err_t bmp180_init_desc(bmp180_dev_t *dev, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio) +{ + CHECK_ARG(dev); + + dev->i2c_dev.port = port; + dev->i2c_dev.addr = BMP180_DEVICE_ADDRESS; + dev->i2c_dev.cfg.sda_io_num = sda_gpio; + dev->i2c_dev.cfg.scl_io_num = scl_gpio; +#if HELPER_TARGET_IS_ESP32 + dev->i2c_dev.cfg.master.clk_speed = I2C_FREQ_HZ; +#endif + + return i2c_dev_create_mutex(&dev->i2c_dev); +} + +esp_err_t bmp180_free_desc(bmp180_dev_t *dev) +{ + CHECK_ARG(dev); + + return i2c_dev_delete_mutex(&dev->i2c_dev); +} + +esp_err_t bmp180_init(bmp180_dev_t *dev) +{ + CHECK_ARG(dev); + + + I2C_DEV_TAKE_MUTEX(&dev->i2c_dev); + + uint8_t id; + I2C_DEV_CHECK(&dev->i2c_dev, i2c_dev_read_reg(&dev->i2c_dev, BMP180_VERSION_REG, &id, 1)); + if (id != BMP180_CHIP_ID) + { + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); + ESP_LOGE(TAG, "Invalid device ID: 0x%02x", id); + return ESP_ERR_NOT_FOUND; + } + + I2C_DEV_CHECK(&dev->i2c_dev, bmp180_read_reg_16(&dev->i2c_dev, BMP180_CALIBRATION_REG + 0, &dev->AC1)); + I2C_DEV_CHECK(&dev->i2c_dev, bmp180_read_reg_16(&dev->i2c_dev, BMP180_CALIBRATION_REG + 2, &dev->AC2)); + I2C_DEV_CHECK(&dev->i2c_dev, bmp180_read_reg_16(&dev->i2c_dev, BMP180_CALIBRATION_REG + 4, &dev->AC3)); + I2C_DEV_CHECK(&dev->i2c_dev, bmp180_read_reg_16(&dev->i2c_dev, BMP180_CALIBRATION_REG + 6, (int16_t *)&dev->AC4)); + I2C_DEV_CHECK(&dev->i2c_dev, bmp180_read_reg_16(&dev->i2c_dev, BMP180_CALIBRATION_REG + 8, (int16_t *)&dev->AC5)); + I2C_DEV_CHECK(&dev->i2c_dev, bmp180_read_reg_16(&dev->i2c_dev, BMP180_CALIBRATION_REG + 10, (int16_t *)&dev->AC6)); + I2C_DEV_CHECK(&dev->i2c_dev, bmp180_read_reg_16(&dev->i2c_dev, BMP180_CALIBRATION_REG + 12, &dev->B1)); + I2C_DEV_CHECK(&dev->i2c_dev, bmp180_read_reg_16(&dev->i2c_dev, BMP180_CALIBRATION_REG + 14, &dev->B2)); + I2C_DEV_CHECK(&dev->i2c_dev, bmp180_read_reg_16(&dev->i2c_dev, BMP180_CALIBRATION_REG + 16, &dev->MB)); + I2C_DEV_CHECK(&dev->i2c_dev, bmp180_read_reg_16(&dev->i2c_dev, BMP180_CALIBRATION_REG + 18, &dev->MC)); + I2C_DEV_CHECK(&dev->i2c_dev, bmp180_read_reg_16(&dev->i2c_dev, BMP180_CALIBRATION_REG + 20, &dev->MD)); + + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); + + ESP_LOGD(TAG, "AC1:=%d AC2:=%d AC3:=%d AC4:=%u AC5:=%u AC6:=%u", dev->AC1, dev->AC2, dev->AC3, dev->AC4, dev->AC5, dev->AC6); + ESP_LOGD(TAG, "B1:=%d B2:=%d", dev->B1, dev->B2); + ESP_LOGD(TAG, "MB:=%d MC:=%d MD:=%d", dev->MB, dev->MC, dev->MD); + + if (dev->AC1== 0 || dev->AC2 == 0 || dev->AC3 == 0 || + dev->AC4 == 0 || dev->AC5 == 0 || dev->AC6 == 0 || + dev->B1 == 0 || dev->B2 == 0 || + dev->MB == 0 || dev->MC == 0 || dev->MD == 0) + { + return ESP_ERR_INVALID_RESPONSE; + } + + return ESP_OK; +} + +/////////////////////////////////////////////////////////////////////////////// + +esp_err_t bmp180_measure(bmp180_dev_t *dev, float *temperature, uint32_t *pressure, bmp180_mode_t oss) +{ + CHECK_ARG(dev && temperature && pressure); + + I2C_DEV_TAKE_MUTEX(&dev->i2c_dev); + + // Temperature is always needed, also required for pressure only. + // + // Calculation taken from BMP180 Datasheet + int32_t T, P; + int32_t UT, X1, X2, B5; + UT = 0; + I2C_DEV_CHECK(&dev->i2c_dev, bmp180_get_uncompensated_temperature(&dev->i2c_dev, &UT)); + + X1 = ((UT - (int32_t)dev->AC6) * (int32_t)dev->AC5) >> 15; + X2 = ((int32_t)dev->MC << 11) / (X1 + (int32_t)dev->MD); + B5 = X1 + X2; + T = (B5 + 8) >> 4; + + if (temperature) + *temperature = T / 10.0; + + ESP_LOGD(TAG, "T:= %" PRIi32 ".%d", T / 10, abs(T % 10)); + + if (pressure) + { + int32_t X3, B3, B6; + uint32_t B4, B7, UP = 0; + + I2C_DEV_CHECK(&dev->i2c_dev, bmp180_get_uncompensated_pressure(&dev->i2c_dev, oss, &UP)); + + // Calculation taken from BMP180 Datasheet + B6 = B5 - 4000; + X1 = ((int32_t)dev->B2 * ((B6 * B6) >> 12)) >> 11; + X2 = ((int32_t)dev->AC2 * B6) >> 11; + X3 = X1 + X2; + + B3 = ((((int32_t)dev->AC1 * 4 + X3) << oss) + 2) >> 2; + X1 = ((int32_t)dev->AC3 * B6) >> 13; + X2 = ((int32_t)dev->B1 * ((B6 * B6) >> 12)) >> 16; + X3 = ((X1 + X2) + 2) >> 2; + B4 = ((uint32_t)dev->AC4 * (uint32_t)(X3 + 32768)) >> 15; + B7 = ((uint32_t)UP - B3) * (uint32_t)(50000UL >> oss); + + if (B7 < 0x80000000UL) + P = (B7 * 2) / B4; + else + P = (B7 / B4) * 2; + + X1 = (P >> 8) * (P >> 8); + X1 = (X1 * 3038) >> 16; + X2 = (-7357 * P) >> 16; + P = P + ((X1 + X2 + (int32_t)3791) >> 4); + + *pressure = P; + + ESP_LOGD(TAG, "P:= %" PRIi32, P); + } + + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); + + return ESP_OK; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/bmp180/bmp180.h Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,131 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2015 Frank Bargstedt + * Copyright (c) 2018 Ruslan V. Uss <unclerus@gmail.com> + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +/** + * @file bmp180.h + * @defgroup bmp180 bmp180 + * @{ + * + * ESP-IDF driver for BMP180 digital pressure sensor + * + * Ported from esp-open-rtos + * + * Copyright (c) 2015 Frank Bargstedt\n + * Copyright (c) 2018 Ruslan V. Uss <unclerus@gmail.com> + * + * MIT Licensed as described in the file LICENSE + */ +#ifndef __BMP180_H__ +#define __BMP180_H__ + +#include <stdbool.h> +#include <i2cdev.h> +#include <esp_err.h> + +#define BMP180_DEVICE_ADDRESS 0x77 //!< I2C address + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * BMP180 device descriptor + */ +typedef struct +{ + i2c_dev_t i2c_dev; + + int16_t AC1; + int16_t AC2; + int16_t AC3; + uint16_t AC4; + uint16_t AC5; + uint16_t AC6; + + int16_t B1; + int16_t B2; + + int16_t MB; + int16_t MC; + int16_t MD; +} bmp180_dev_t; + +/** + * Hardware accuracy mode. + * See Table 3 of the datasheet + */ +typedef enum +{ + BMP180_MODE_ULTRA_LOW_POWER = 0, //!< 1 sample, 4.5 ms + BMP180_MODE_STANDARD, //!< 2 samples, 7.5 ms + BMP180_MODE_HIGH_RESOLUTION, //!< 4 samples, 13.5 ms + BMP180_MODE_ULTRA_HIGH_RESOLUTION //!< 8 samples, 25.5 ms +} bmp180_mode_t; + + +/** + * @brief Initialize device descriptor + * + * @param dev Device descriptor + * @param port I2C port number + * @param sda_gpio GPIO pin number for SDA + * @param scl_gpio GPIO pin number for SCL + * @return `ESP_OK` on success + */ +esp_err_t bmp180_init_desc(bmp180_dev_t *dev, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio); + +/** + * @brief Free device descriptor + * + * @param dev Pointer to BMP180 device descriptor + * @return `ESP_OK` on success + */ +esp_err_t bmp180_free_desc(bmp180_dev_t *dev); + +/** + * @brief Initialize device + * + * @param dev Pointer to BMP180 device descriptor + * @return `ESP_OK` on success + */ +esp_err_t bmp180_init(bmp180_dev_t *dev); + +/** + * @brief Measure temperature and pressure + * + * @param dev Pointer to BMP180 device descriptor + * @param[out] temperature Temperature in degrees Celsius + * @param[out] pressure Pressure in Pa + * @param oss Measurement mode + * @return `ESP_OK` on success + */ +esp_err_t bmp180_measure(bmp180_dev_t *dev, float *temperature, uint32_t *pressure, bmp180_mode_t oss); + +#ifdef __cplusplus +} +#endif + +/**@}*/ + +#endif /* __BMP180_H__ */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/bmp180/component.mk Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,2 @@ +COMPONENT_ADD_INCLUDEDIRS = . +COMPONENT_DEPENDS = i2cdev log esp_idf_lib_helpers
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/bmp280/.eil.yml Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,28 @@ +--- +components: + - name: bmp280 + description: | + Driver for BMP280/BME280 digital pressure sensor + group: + name: pressure + groups: + - name: temperature + code_owners: + - name: UncleRus + depends: + - name: i2cdev + - name: log + - name: esp_idf_lib_helpers + thread_safe: yes + targets: + - name: esp32 + - name: esp8266 + - name: esp32s2 + - name: esp32c3 + licenses: + - name: MIT + copyrights: + - name: UncleRus + year: 2018 + - name: sheinz + year: 2016
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/bmp280/CMakeLists.txt Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,5 @@ +idf_component_register( + SRCS bmp280.c + INCLUDE_DIRS . + REQUIRES i2cdev log esp_idf_lib_helpers +)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/bmp280/LICENSE Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2016 sheinz (https://github.com/sheinz) +Copyright (c) 2018 Ruslan V. Uss (https://github.com/UncleRus) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/bmp280/bmp280.c Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,411 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2016 sheinz <https://github.com/sheinz> + * Copyright (c) 2018 Ruslan V. Uss <unclerus@gmail.com> + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +/** + * @file bmp280.c + * + * ESP-IDF driver for BMP280/BME280 digital pressure sensor + * + * Ported from esp-open-rtos + * + * Copyright (c) 2016 sheinz <https://github.com/sheinz>\n + * Copyright (c) 2018 Ruslan V. Uss <unclerus@gmail.com> + * + * MIT Licensed as described in the file LICENSE + */ + +#include "bmp280.h" +#include <inttypes.h> +#include <esp_log.h> +#include <esp_idf_lib_helpers.h> + +#define I2C_FREQ_HZ 1000000 // Max 1MHz for esp-idf + +static const char *TAG = "bmp280"; + +/** + * BMP280 registers + */ +#define BMP280_REG_TEMP_XLSB 0xFC /* bits: 7-4 */ +#define BMP280_REG_TEMP_LSB 0xFB +#define BMP280_REG_TEMP_MSB 0xFA +#define BMP280_REG_TEMP (BMP280_REG_TEMP_MSB) +#define BMP280_REG_PRESS_XLSB 0xF9 /* bits: 7-4 */ +#define BMP280_REG_PRESS_LSB 0xF8 +#define BMP280_REG_PRESS_MSB 0xF7 +#define BMP280_REG_PRESSURE (BMP280_REG_PRESS_MSB) +#define BMP280_REG_CONFIG 0xF5 /* bits: 7-5 t_sb; 4-2 filter; 0 spi3w_en */ +#define BMP280_REG_CTRL 0xF4 /* bits: 7-5 osrs_t; 4-2 osrs_p; 1-0 mode */ +#define BMP280_REG_STATUS 0xF3 /* bits: 3 measuring; 0 im_update */ +#define BMP280_REG_CTRL_HUM 0xF2 /* bits: 2-0 osrs_h; */ +#define BMP280_REG_RESET 0xE0 +#define BMP280_REG_ID 0xD0 +#define BMP280_REG_CALIB 0x88 +#define BMP280_REG_HUM_CALIB 0x88 + +#define BMP280_RESET_VALUE 0xB6 + +#define CHECK(x) do { esp_err_t __; if ((__ = x) != ESP_OK) return __; } while (0) +#define CHECK_ARG(VAL) do { if (!(VAL)) return ESP_ERR_INVALID_ARG; } while (0) +#define CHECK_LOGE(dev, x, msg, ...) do { \ + esp_err_t __; \ + if ((__ = x) != ESP_OK) { \ + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); \ + ESP_LOGE(TAG, msg, ## __VA_ARGS__); \ + return __; \ + } \ + } while (0) + +static esp_err_t read_register16(i2c_dev_t *dev, uint8_t reg, uint16_t *r) +{ + uint8_t d[] = { 0, 0 }; + + CHECK(i2c_dev_read_reg(dev, reg, d, 2)); + *r = d[0] | (d[1] << 8); + + return ESP_OK; +} + +inline static esp_err_t write_register8(i2c_dev_t *dev, uint8_t addr, uint8_t value) +{ + return i2c_dev_write_reg(dev, addr, &value, 1); +} + +static esp_err_t read_calibration_data(bmp280_t *dev) +{ + CHECK(read_register16(&dev->i2c_dev, 0x88, &dev->dig_T1)); + CHECK(read_register16(&dev->i2c_dev, 0x8a, (uint16_t *)&dev->dig_T2)); + CHECK(read_register16(&dev->i2c_dev, 0x8c, (uint16_t *)&dev->dig_T3)); + CHECK(read_register16(&dev->i2c_dev, 0x8e, &dev->dig_P1)); + CHECK(read_register16(&dev->i2c_dev, 0x90, (uint16_t *)&dev->dig_P2)); + CHECK(read_register16(&dev->i2c_dev, 0x92, (uint16_t *)&dev->dig_P3)); + CHECK(read_register16(&dev->i2c_dev, 0x94, (uint16_t *)&dev->dig_P4)); + CHECK(read_register16(&dev->i2c_dev, 0x96, (uint16_t *)&dev->dig_P5)); + CHECK(read_register16(&dev->i2c_dev, 0x98, (uint16_t *)&dev->dig_P6)); + CHECK(read_register16(&dev->i2c_dev, 0x9a, (uint16_t *)&dev->dig_P7)); + CHECK(read_register16(&dev->i2c_dev, 0x9c, (uint16_t *)&dev->dig_P8)); + CHECK(read_register16(&dev->i2c_dev, 0x9e, (uint16_t *)&dev->dig_P9)); + + ESP_LOGD(TAG, "Calibration data received:"); + ESP_LOGD(TAG, "dig_T1=%d", dev->dig_T1); + ESP_LOGD(TAG, "dig_T2=%d", dev->dig_T2); + ESP_LOGD(TAG, "dig_T3=%d", dev->dig_T3); + ESP_LOGD(TAG, "dig_P1=%d", dev->dig_P1); + ESP_LOGD(TAG, "dig_P2=%d", dev->dig_P2); + ESP_LOGD(TAG, "dig_P3=%d", dev->dig_P3); + ESP_LOGD(TAG, "dig_P4=%d", dev->dig_P4); + ESP_LOGD(TAG, "dig_P5=%d", dev->dig_P5); + ESP_LOGD(TAG, "dig_P6=%d", dev->dig_P6); + ESP_LOGD(TAG, "dig_P7=%d", dev->dig_P7); + ESP_LOGD(TAG, "dig_P8=%d", dev->dig_P8); + ESP_LOGD(TAG, "dig_P9=%d", dev->dig_P9); + + return ESP_OK; +} + +static esp_err_t read_hum_calibration_data(bmp280_t *dev) +{ + uint16_t h4, h5; + + CHECK(i2c_dev_read_reg(&dev->i2c_dev, 0xa1, &dev->dig_H1, 1)); + CHECK(read_register16(&dev->i2c_dev, 0xe1, (uint16_t *)&dev->dig_H2)); + CHECK(i2c_dev_read_reg(&dev->i2c_dev, 0xe3, &dev->dig_H3, 1)); + CHECK(read_register16(&dev->i2c_dev, 0xe4, &h4)); + CHECK(read_register16(&dev->i2c_dev, 0xe5, &h5)); + CHECK(i2c_dev_read_reg(&dev->i2c_dev, 0xe7, (uint8_t *)&dev->dig_H6, 1)); + + dev->dig_H4 = (h4 & 0x00ff) << 4 | (h4 & 0x0f00) >> 8; + dev->dig_H5 = h5 >> 4; + ESP_LOGD(TAG, "Calibration data received:"); + ESP_LOGD(TAG, "dig_H1=%d", dev->dig_H1); + ESP_LOGD(TAG, "dig_H2=%d", dev->dig_H2); + ESP_LOGD(TAG, "dig_H3=%d", dev->dig_H3); + ESP_LOGD(TAG, "dig_H4=%d", dev->dig_H4); + ESP_LOGD(TAG, "dig_H5=%d", dev->dig_H5); + ESP_LOGD(TAG, "dig_H6=%d", dev->dig_H6); + + return ESP_OK; +} + +esp_err_t bmp280_init_desc(bmp280_t *dev, uint8_t addr, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio) +{ + CHECK_ARG(dev); + + if (addr != BMP280_I2C_ADDRESS_0 && addr != BMP280_I2C_ADDRESS_1) + { + ESP_LOGE(TAG, "Invalid I2C address"); + return ESP_ERR_INVALID_ARG; + } + + dev->i2c_dev.port = port; + dev->i2c_dev.addr = addr; + dev->i2c_dev.cfg.sda_io_num = sda_gpio; + dev->i2c_dev.cfg.scl_io_num = scl_gpio; +#if HELPER_TARGET_IS_ESP32 + dev->i2c_dev.cfg.master.clk_speed = I2C_FREQ_HZ; +#endif + + return i2c_dev_create_mutex(&dev->i2c_dev); +} + +esp_err_t bmp280_free_desc(bmp280_t *dev) +{ + CHECK_ARG(dev); + + return i2c_dev_delete_mutex(&dev->i2c_dev); +} + +esp_err_t bmp280_init_default_params(bmp280_params_t *params) +{ + CHECK_ARG(params); + + params->mode = BMP280_MODE_NORMAL; + params->filter = BMP280_FILTER_OFF; + params->oversampling_pressure = BMP280_STANDARD; + params->oversampling_temperature = BMP280_STANDARD; + params->oversampling_humidity = BMP280_STANDARD; + params->standby = BMP280_STANDBY_250; + + return ESP_OK; +} + +esp_err_t bmp280_init(bmp280_t *dev, bmp280_params_t *params) +{ + CHECK_ARG(dev && params); + + I2C_DEV_TAKE_MUTEX(&dev->i2c_dev); + + CHECK_LOGE(dev, i2c_dev_read_reg(&dev->i2c_dev, BMP280_REG_ID, &dev->id, 1), "Sensor not found"); + + if (dev->id != BMP280_CHIP_ID && dev->id != BME280_CHIP_ID) + { + CHECK_LOGE(dev, ESP_ERR_INVALID_VERSION, + "Invalid chip ID: expected: 0x%x (BME280) or 0x%x (BMP280) got: 0x%x", + BME280_CHIP_ID, BMP280_CHIP_ID, dev->id); + } + + // Soft reset. + CHECK_LOGE(dev, write_register8(&dev->i2c_dev, BMP280_REG_RESET, BMP280_RESET_VALUE), "Failed to reset sensor"); + + // Wait until finished copying over the NVP data. + while (1) + { + uint8_t status; + if (!i2c_dev_read_reg(&dev->i2c_dev, BMP280_REG_STATUS, &status, 1) && (status & 1) == 0) + break; + } + + CHECK_LOGE(dev, read_calibration_data(dev), "Failed to read calibration data"); + + if (dev->id == BME280_CHIP_ID) + { + CHECK_LOGE(dev, read_hum_calibration_data(dev), "Failed to read humidity calibration data"); + } + + uint8_t config = (params->standby << 5) | (params->filter << 2); + ESP_LOGD(TAG, "Writing config reg=%x", config); + + CHECK_LOGE(dev, write_register8(&dev->i2c_dev, BMP280_REG_CONFIG, config), "Failed to configure sensor"); + + if (params->mode == BMP280_MODE_FORCED) + { + params->mode = BMP280_MODE_SLEEP; // initial mode for forced is sleep + } + + uint8_t ctrl = (params->oversampling_temperature << 5) | (params->oversampling_pressure << 2) | (params->mode); + + if (dev->id == BME280_CHIP_ID) + { + // Write crtl hum reg first, only active after write to BMP280_REG_CTRL. + uint8_t ctrl_hum = params->oversampling_humidity; + ESP_LOGD(TAG, "Writing ctrl hum reg=%x", ctrl_hum); + CHECK_LOGE(dev, write_register8(&dev->i2c_dev, BMP280_REG_CTRL_HUM, ctrl_hum), "Failed to control sensor"); + } + + ESP_LOGD(TAG, "Writing ctrl reg=%x", ctrl); + CHECK_LOGE(dev, write_register8(&dev->i2c_dev, BMP280_REG_CTRL, ctrl), "Failed to control sensor"); + + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); + + return ESP_OK; +} + +esp_err_t bmp280_force_measurement(bmp280_t *dev) +{ + CHECK_ARG(dev); + + I2C_DEV_TAKE_MUTEX(&dev->i2c_dev); + + uint8_t ctrl; + I2C_DEV_CHECK(&dev->i2c_dev, i2c_dev_read_reg(&dev->i2c_dev, BMP280_REG_CTRL, &ctrl, 1)); + ctrl &= ~0b11; // clear two lower bits + ctrl |= BMP280_MODE_FORCED; + ESP_LOGD(TAG, "Writing ctrl reg=%x", ctrl); + CHECK_LOGE(dev, write_register8(&dev->i2c_dev, BMP280_REG_CTRL, ctrl), "Failed to start forced mode"); + + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); + + return ESP_OK; +} + +esp_err_t bmp280_is_measuring(bmp280_t *dev, bool *busy) +{ + CHECK_ARG(dev && busy); + + I2C_DEV_TAKE_MUTEX(&dev->i2c_dev); + + const uint8_t regs[2] = { BMP280_REG_STATUS, BMP280_REG_CTRL }; + uint8_t status[2]; + CHECK_LOGE(dev, i2c_dev_read(&dev->i2c_dev, regs, 2, status, 2), "Failed to read status registers"); + + // Check mode - FORCED means BM280 is busy (it switches to SLEEP mode when finished) + // Additionally, check 'measuring' bit in status register + *busy = ((status[1] & 0b11) == BMP280_MODE_FORCED) || (status[0] & (1 << 3)); + + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); + + return ESP_OK; +} + +/** + * Compensation algorithm is taken from BMP280 datasheet. + * + * Return value is in degrees Celsius. + */ +static inline int32_t compensate_temperature(bmp280_t *dev, int32_t adc_temp, int32_t *fine_temp) +{ + int32_t var1, var2; + + var1 = ((((adc_temp >> 3) - ((int32_t)dev->dig_T1 << 1))) * (int32_t)dev->dig_T2) >> 11; + var2 = (((((adc_temp >> 4) - (int32_t)dev->dig_T1) * ((adc_temp >> 4) - (int32_t)dev->dig_T1)) >> 12) * (int32_t)dev->dig_T3) >> 14; + + *fine_temp = var1 + var2; + return (*fine_temp * 5 + 128) >> 8; +} + +/** + * Compensation algorithm is taken from BMP280 datasheet. + * + * Return value is in Pa, 24 integer bits and 8 fractional bits. + */ +static inline uint32_t compensate_pressure(bmp280_t *dev, int32_t adc_press, int32_t fine_temp) +{ + int64_t var1, var2, p; + + var1 = (int64_t)fine_temp - 128000; + var2 = var1 * var1 * (int64_t)dev->dig_P6; + var2 = var2 + ((var1 * (int64_t)dev->dig_P5) << 17); + var2 = var2 + (((int64_t)dev->dig_P4) << 35); + var1 = ((var1 * var1 * (int64_t)dev->dig_P3) >> 8) + ((var1 * (int64_t)dev->dig_P2) << 12); + var1 = (((int64_t)1 << 47) + var1) * ((int64_t)dev->dig_P1) >> 33; + + if (var1 == 0) + { + return 0; // avoid exception caused by division by zero + } + + p = 1048576 - adc_press; + p = (((p << 31) - var2) * 3125) / var1; + var1 = ((int64_t)dev->dig_P9 * (p >> 13) * (p >> 13)) >> 25; + var2 = ((int64_t)dev->dig_P8 * p) >> 19; + + p = ((p + var1 + var2) >> 8) + ((int64_t)dev->dig_P7 << 4); + return p; +} + +/** + * Compensation algorithm is taken from BME280 datasheet. + * + * Return value is in Pa, 24 integer bits and 8 fractional bits. + */ +static inline uint32_t compensate_humidity(bmp280_t *dev, int32_t adc_hum, int32_t fine_temp) +{ + int32_t v_x1_u32r; + + v_x1_u32r = fine_temp - (int32_t)76800; + v_x1_u32r = ((((adc_hum << 14) - ((int32_t)dev->dig_H4 << 20) - ((int32_t)dev->dig_H5 * v_x1_u32r)) + (int32_t)16384) >> 15) + * (((((((v_x1_u32r * (int32_t)dev->dig_H6) >> 10) * (((v_x1_u32r * (int32_t)dev->dig_H3) >> 11) + (int32_t)32768)) >> 10) + + (int32_t)2097152) * (int32_t)dev->dig_H2 + 8192) >> 14); + v_x1_u32r = v_x1_u32r - (((((v_x1_u32r >> 15) * (v_x1_u32r >> 15)) >> 7) * (int32_t)dev->dig_H1) >> 4); + v_x1_u32r = v_x1_u32r < 0 ? 0 : v_x1_u32r; + v_x1_u32r = v_x1_u32r > 419430400 ? 419430400 : v_x1_u32r; + return v_x1_u32r >> 12; +} + +esp_err_t bmp280_read_fixed(bmp280_t *dev, int32_t *temperature, uint32_t *pressure, uint32_t *humidity) +{ + CHECK_ARG(dev && temperature && pressure); + + int32_t adc_pressure; + int32_t adc_temp; + uint8_t data[8]; + + // Only the BME280 supports reading the humidity. + if (dev->id != BME280_CHIP_ID) + { + if (humidity) + *humidity = 0; + humidity = NULL; + } + + I2C_DEV_TAKE_MUTEX(&dev->i2c_dev); + + // Need to read in one sequence to ensure they match. + size_t size = humidity ? 8 : 6; + CHECK_LOGE(dev, i2c_dev_read_reg(&dev->i2c_dev, 0xf7, data, size), "Failed to read data"); + + adc_pressure = data[0] << 12 | data[1] << 4 | data[2] >> 4; + adc_temp = data[3] << 12 | data[4] << 4 | data[5] >> 4; + ESP_LOGD(TAG, "ADC temperature: %" PRIi32, adc_temp); + ESP_LOGD(TAG, "ADC pressure: %" PRIi32, adc_pressure); + + int32_t fine_temp; + *temperature = compensate_temperature(dev, adc_temp, &fine_temp); + *pressure = compensate_pressure(dev, adc_pressure, fine_temp); + + if (humidity) + { + int32_t adc_humidity = data[6] << 8 | data[7]; + ESP_LOGD(TAG, "ADC humidity: %" PRIi32, adc_humidity); + *humidity = compensate_humidity(dev, adc_humidity, fine_temp); + } + + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); + + return ESP_OK; +} + +esp_err_t bmp280_read_float(bmp280_t *dev, float *temperature, float *pressure, float *humidity) +{ + int32_t fixed_temperature; + uint32_t fixed_pressure; + uint32_t fixed_humidity; + CHECK(bmp280_read_fixed(dev, &fixed_temperature, &fixed_pressure, humidity ? &fixed_humidity : NULL)); + *temperature = (float)fixed_temperature / 100; + *pressure = (float)fixed_pressure / 256; + if (humidity) + *humidity = (float)fixed_humidity / 1024; + + return ESP_OK; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/bmp280/bmp280.h Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,250 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2016 sheinz <https://github.com/sheinz> + * Copyright (c) 2018 Ruslan V. Uss <unclerus@gmail.com> + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +/** + * @file bmp280.h + * @defgroup bmp280 bmp280 + * @{ + * + * ESP-IDF driver for BMP280/BME280 digital pressure sensor + * + * Ported from esp-open-rtos + * + * Copyright (c) 2016 sheinz <https://github.com/sheinz>\n + * Copyright (c) 2018 Ruslan V. Uss <unclerus@gmail.com> + * + * MIT Licensed as described in the file LICENSE + */ +#ifndef __BMP280_H__ +#define __BMP280_H__ + +#include <stdint.h> +#include <stdbool.h> +#include <esp_err.h> +#include <i2cdev.h> + +#ifdef __cplusplus +extern "C" { +#endif + +#define BMP280_I2C_ADDRESS_0 0x76 //!< I2C address when SDO pin is low +#define BMP280_I2C_ADDRESS_1 0x77 //!< I2C address when SDO pin is high + +#define BMP280_CHIP_ID 0x58 //!< BMP280 has chip-id 0x58 +#define BME280_CHIP_ID 0x60 //!< BME280 has chip-id 0x60 + +/** + * Mode of BMP280 module operation. + */ +typedef enum { + BMP280_MODE_SLEEP = 0, //!< Sleep mode + BMP280_MODE_FORCED = 1, //!< Measurement is initiated by user + BMP280_MODE_NORMAL = 3 //!< Continues measurement +} BMP280_Mode; + +typedef enum { + BMP280_FILTER_OFF = 0, + BMP280_FILTER_2 = 1, + BMP280_FILTER_4 = 2, + BMP280_FILTER_8 = 3, + BMP280_FILTER_16 = 4 +} BMP280_Filter; + +/** + * Pressure oversampling settings + */ +typedef enum { + BMP280_SKIPPED = 0, //!< no measurement + BMP280_ULTRA_LOW_POWER = 1, //!< oversampling x1 + BMP280_LOW_POWER = 2, //!< oversampling x2 + BMP280_STANDARD = 3, //!< oversampling x4 + BMP280_HIGH_RES = 4, //!< oversampling x8 + BMP280_ULTRA_HIGH_RES = 5 //!< oversampling x16 +} BMP280_Oversampling; + +/** + * Stand by time between measurements in normal mode + */ +typedef enum { + BMP280_STANDBY_05 = 0, //!< stand by time 0.5ms + BMP280_STANDBY_62 = 1, //!< stand by time 62.5ms + BMP280_STANDBY_125 = 2, //!< stand by time 125ms + BMP280_STANDBY_250 = 3, //!< stand by time 250ms + BMP280_STANDBY_500 = 4, //!< stand by time 500ms + BMP280_STANDBY_1000 = 5, //!< stand by time 1s + BMP280_STANDBY_2000 = 6, //!< stand by time 2s BMP280, 10ms BME280 + BMP280_STANDBY_4000 = 7, //!< stand by time 4s BMP280, 20ms BME280 +} BMP280_StandbyTime; + +/** + * Configuration parameters for BMP280 module. + * Use function ::bmp280_init_default_params() to use default configuration. + */ +typedef struct { + BMP280_Mode mode; + BMP280_Filter filter; + BMP280_Oversampling oversampling_pressure; + BMP280_Oversampling oversampling_temperature; + BMP280_Oversampling oversampling_humidity; + BMP280_StandbyTime standby; +} bmp280_params_t; + +/** + * Device descriptor + */ +typedef struct { + uint16_t dig_T1; + int16_t dig_T2; + int16_t dig_T3; + uint16_t dig_P1; + int16_t dig_P2; + int16_t dig_P3; + int16_t dig_P4; + int16_t dig_P5; + int16_t dig_P6; + int16_t dig_P7; + int16_t dig_P8; + int16_t dig_P9; + + /* Humidity compensation for BME280 */ + uint8_t dig_H1; + int16_t dig_H2; + uint8_t dig_H3; + int16_t dig_H4; + int16_t dig_H5; + int8_t dig_H6; + + i2c_dev_t i2c_dev; //!< I2C device descriptor + uint8_t id; //!< Chip ID +} bmp280_t; + +/** + * @brief Initialize device descriptor + * + * @param dev Device descriptor + * @param addr BMP280 address + * @param port I2C port number + * @param sda_gpio GPIO pin for SDA + * @param scl_gpio GPIO pin for SCL + * @return `ESP_OK` on success + */ +esp_err_t bmp280_init_desc(bmp280_t *dev, uint8_t addr, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio); + +/** + * @brief Free device descriptor + * + * @param dev Device descriptor + * @return `ESP_OK` on success + */ +esp_err_t bmp280_free_desc(bmp280_t *dev); + +/** + * @brief Initialize default parameters + * + * Default configuration: + * + * - mode: NORMAL + * - filter: OFF + * - oversampling: x4 + * - standby time: 250ms + * + * @param[out] params Default parameters + * @return `ESP_OK` on success + */ +esp_err_t bmp280_init_default_params(bmp280_params_t *params); + +/** + * @brief Initialize BMP280 module + * + * Probes for the device, soft resets the device, reads the calibration + * constants, and configures the device using the supplied parameters. + * + * This may be called again to soft reset the device and initialize it again. + * + * @param dev Device descriptor + * @param params Parameters + * @return `ESP_OK` on success + */ +esp_err_t bmp280_init(bmp280_t *dev, bmp280_params_t *params); + +/** + * @brief Start measurement in forced mode + * + * The module remains in forced mode after this call. + * Do not call this method in normal mode. + * + * @param dev Device descriptor + * @return `ESP_OK` on success + */ +esp_err_t bmp280_force_measurement(bmp280_t *dev); + +/** + * @brief Check if BMP280 is busy + * + * @param dev Device descriptor + * @param[out] busy true if BMP280 measures temperature/pressure + * @return `ESP_OK` on success + */ +esp_err_t bmp280_is_measuring(bmp280_t *dev, bool *busy); + +/** + * @brief Read raw compensated temperature and pressure data + * + * Temperature in degrees Celsius times 100. + * + * Pressure in Pascals in fixed point 24 bit integer 8 bit fraction format. + * + * Humidity is optional and only read for the BME280, in percent relative + * humidity as a fixed point 22 bit integer and 10 bit fraction format. + * + * @param dev Device descriptor + * @param[out] temperature Temperature, deg.C * 100 + * @param[out] pressure Pressure + * @param[out] humidity Humidity, optional + * @return `ESP_OK` on success + */ +esp_err_t bmp280_read_fixed(bmp280_t *dev, int32_t *temperature, + uint32_t *pressure, uint32_t *humidity); + +/** + * @brief Read compensated temperature and pressure data + * + * Humidity is optional and only read for the BME280. + * + * @param dev Device descriptor + * @param[out] temperature Temperature, deg.C + * @param[out] pressure Pressure, Pascal + * @param[out] humidity Relative humidity, percents (optional) + * @return `ESP_OK` on success + */ +esp_err_t bmp280_read_float(bmp280_t *dev, float *temperature, + float *pressure, float *humidity); + +#ifdef __cplusplus +} +#endif + +/**@}*/ + +#endif // __BMP280_H__
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/bmp280/component.mk Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,2 @@ +COMPONENT_ADD_INCLUDEDIRS = . +COMPONENT_DEPENDS = i2cdev log esp_idf_lib_helpers
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/button/.eil.yml Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,22 @@ +--- +components: + - name: button + description: | + HW timer-based driver for GPIO buttons + group: input + groups: [] + code_owners: + - name: UncleRus + depends: + - name: driver + thread_safe: yes + targets: + - name: esp32 + - name: esp8266 + - name: esp32s2 + - name: esp32c3 + licenses: + - name: MIT + copyrights: + - name: UncleRus + year: 2021
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/button/CMakeLists.txt Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,13 @@ +if(${IDF_TARGET} STREQUAL esp8266) + set(req esp8266 esp_timer) +elseif(${IDF_VERSION_MAJOR} STREQUAL 4 AND ${IDF_VERSION_MINOR} STREQUAL 1 AND ${IDF_VERSION_PATCH} STREQUAL 3) + set(req driver) +else() + set(req driver esp_timer) +endif() + +idf_component_register( + SRCS button.c + INCLUDE_DIRS . + REQUIRES ${req} +)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/button/Kconfig Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,28 @@ +menu "Button" + + config BUTTON_MAX + int "Maximum number of buttons" + range 1 10 + default 5 + + config BUTTON_POLL_TIMEOUT + int "Poll timeout, ms" + range 1 1000 + default 10 + + config BUTTON_LONG_PRESS_TIMEOUT + int "Timeout of long press, ms" + range 100 10000 + default 1000 + + config BUTTON_AUTOREPEAT_TIMEOUT + int "Timeout before autorepeat, ms" + range 100 10000 + default 500 + + config BUTTON_AUTOREPEAT_INTERVAL + int "Autorepeat interval, ms" + range 100 10000 + default 250 + +endmenu \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/button/LICENSE Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 Ruslan V. Uss + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/button/button.c Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,182 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2021 Ruslan V. Uss <unclerus@gmail.com> + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +/** + * @file button.c + * + * ESP-IDF driver for simple GPIO buttons. + * + * Supports anti-jitter, autorepeat, long press. + * + * Copyright (c) 2021 Ruslan V. Uss <unclerus@gmail.com> + * + * MIT Licensed as described in the file LICENSE + */ +#include "button.h" +#include <esp_timer.h> + +#define DEAD_TIME_US 50000 // 50ms + +#define POLL_TIMEOUT_US (CONFIG_BUTTON_POLL_TIMEOUT * 1000) +#define AUTOREPEAT_TIMEOUT_US (CONFIG_BUTTON_AUTOREPEAT_TIMEOUT * 1000) +#define AUTOREPEAT_INTERVAL_US (CONFIG_BUTTON_AUTOREPEAT_INTERVAL * 1000) +#define LONG_PRESS_TIMEOUT_US (CONFIG_BUTTON_LONG_PRESS_TIMEOUT * 1000) + +static button_t *buttons[CONFIG_BUTTON_MAX] = { NULL }; +static esp_timer_handle_t timer = NULL; + +#define CHECK(x) do { esp_err_t __; if ((__ = x) != ESP_OK) return __; } while (0) +#define CHECK_ARG(VAL) do { if (!(VAL)) return ESP_ERR_INVALID_ARG; } while (0) + +static void poll_button(button_t *btn) +{ + if (btn->internal.state == BUTTON_PRESSED && btn->internal.pressed_time < DEAD_TIME_US) + { + // Dead time, ignore all + btn->internal.pressed_time += POLL_TIMEOUT_US; + return; + } + + if (gpio_get_level(btn->gpio) == btn->pressed_level) + { + // button is pressed + if (btn->internal.state == BUTTON_RELEASED) + { + // pressing just started, reset pressing/repeating time and run callback + btn->internal.state = BUTTON_PRESSED; + btn->internal.pressed_time = 0; + btn->internal.repeating_time = 0; + btn->callback(btn, BUTTON_PRESSED); + return; + } + // increment pressing time + btn->internal.pressed_time += POLL_TIMEOUT_US; + + // check autorepeat + if (btn->autorepeat) + { + // check autorepeat timeout + if (btn->internal.pressed_time < AUTOREPEAT_TIMEOUT_US) + return; + // increment repeating time + btn->internal.repeating_time += POLL_TIMEOUT_US; + + if (btn->internal.repeating_time >= AUTOREPEAT_INTERVAL_US) + { + // reset repeating time and run callback + btn->internal.repeating_time = 0; + btn->callback(btn, BUTTON_CLICKED); + } + return; + } + + if (btn->internal.state == BUTTON_PRESSED && btn->internal.pressed_time >= LONG_PRESS_TIMEOUT_US) + { + // button perssed long time, change state and run callback + btn->internal.state = BUTTON_PRESSED_LONG; + btn->callback(btn, BUTTON_PRESSED_LONG); + } + } + else if (btn->internal.state != BUTTON_RELEASED) + { + // button released + bool clicked = btn->internal.state == BUTTON_PRESSED; + btn->internal.state = BUTTON_RELEASED; + btn->callback(btn, BUTTON_RELEASED); + if (clicked) + btn->callback(btn, BUTTON_CLICKED); + } +} + +static void poll(void *arg) +{ + for (size_t i = 0; i < CONFIG_BUTTON_MAX; i++) + if (buttons[i] && buttons[i]->callback) + poll_button(buttons[i]); +} + +//////////////////////////////////////////////////////////////////////////////// + +static const esp_timer_create_args_t timer_args = { + .arg = NULL, + .name = "poll_buttons", + .dispatch_method = ESP_TIMER_TASK, + .callback = poll, +}; + +esp_err_t button_init(button_t *btn) +{ + CHECK_ARG(btn); + + if (!timer) + CHECK(esp_timer_create(&timer_args, &timer)); + + esp_timer_stop(timer); + + esp_err_t res = ESP_ERR_NO_MEM; + + for (size_t i = 0; i < CONFIG_BUTTON_MAX; i++) + { + if (buttons[i] == btn) + break; + + if (!buttons[i]) + { + btn->internal.state = BUTTON_RELEASED; + btn->internal.pressed_time = 0; + btn->internal.repeating_time = 0; + res = gpio_set_direction(btn->gpio, GPIO_MODE_INPUT); + if (res != ESP_OK) break; + if (btn->internal_pull) + { + res = gpio_set_pull_mode(btn->gpio, btn->pressed_level ? GPIO_PULLDOWN_ONLY : GPIO_PULLUP_ONLY); + if (res != ESP_OK) break; + } + buttons[i] = btn; + break; + } + } + + CHECK(esp_timer_start_periodic(timer, POLL_TIMEOUT_US)); + return res; +} + +esp_err_t button_done(button_t *btn) +{ + CHECK_ARG(btn); + + esp_timer_stop(timer); + + esp_err_t res = ESP_ERR_INVALID_ARG; + + for (size_t i = 0; i < CONFIG_BUTTON_MAX; i++) + if (buttons[i] == btn) + { + buttons[i] = NULL; + res = ESP_OK; + break; + } + + CHECK(esp_timer_start_periodic(timer, POLL_TIMEOUT_US)); + return res; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/button/button.h Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,112 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2021 Ruslan V. Uss <unclerus@gmail.com> + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +/** + * @file button.h + * @defgroup button button + * @{ + * + * ESP-IDF driver for simple GPIO buttons. + * + * Supports anti-jitter, auto repeat, long press. + * + * Copyright (c) 2021 Ruslan V. Uss <unclerus@gmail.com> + * + * MIT Licensed as described in the file LICENSE + */ +#ifndef __COMPONENTS_BUTTON_H__ +#define __COMPONENTS_BUTTON_H__ + +#include <stdint.h> +#include <stdbool.h> +#include <driver/gpio.h> +#include <esp_err.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Typedef of button descriptor + */ +typedef struct button_s button_t; + +/** + * Button states/events + */ +typedef enum { + BUTTON_PRESSED = 0, + BUTTON_RELEASED, + BUTTON_CLICKED, + BUTTON_PRESSED_LONG, +} button_state_t; + +/** + * Callback prototype + * + * @param btn Pointer to button descriptor + * @param state Button action (new state) + */ +typedef void (*button_event_cb_t)(button_t *btn, button_state_t state); + +/** + * Button descriptor struct + */ +struct button_s +{ + gpio_num_t gpio; //!< GPIO + bool internal_pull; //!< Enable internal pull-up/pull-down + uint8_t pressed_level; //!< Logic level of pressed button + bool autorepeat; //!< Enable autorepeat + button_event_cb_t callback; //!< Button callback + void *ctx; //!< User data + struct { + button_state_t state; + uint32_t pressed_time; + uint32_t repeating_time; + } internal; //!< Internal button state +}; + +/** + * @brief Init button + * + * @param btn Pointer to button descriptor + * @return `ESP_OK` on success + */ +esp_err_t button_init(button_t *btn); + +/** + * @brief Deinit button + * + * @param btn Pointer to button descriptor + * @return `ESP_OK` on success + */ +esp_err_t button_done(button_t *btn); + +#ifdef __cplusplus +} +#endif + +/**@}*/ + +#endif /* __COMPONENTS_BUTTON_H__ */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/button/component.mk Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,7 @@ +COMPONENT_ADD_INCLUDEDIRS = . + +ifdef CONFIG_IDF_TARGET_ESP8266 +COMPONENT_DEPENDS = esp8266 +else +COMPONENT_DEPENDS = driver +endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/ccs811/.eil.yml Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,27 @@ +--- +components: + - name: ccs811 + description: | + Driver for AMS CCS811 digital gas sensor + group: air-quality + groups: + - name: gas + code_owners: + - name: UncleRus + depends: + - name: i2cdev + - name: log + - name: esp_idf_lib_helpers + thread_safe: yes + targets: + - name: esp32 + - name: esp8266 + - name: esp32s2 + - name: esp32c3 + licenses: + - name: BSD-3 + copyrights: + - name: UncleRus + year: 2021 + - name: gschorcht + year: 2017
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/ccs811/CMakeLists.txt Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,5 @@ +idf_component_register( + SRCS ccs811.c + INCLUDE_DIRS . + REQUIRES i2cdev log esp_idf_lib_helpers +)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/ccs811/LICENSE Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,27 @@ +Copyright (c) 2017 Gunar Schorcht (https://github.com/gschorcht) +Copyright (c) 2020 Ruslan V. Uss (https://github.com/UncleRus) + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, +this list of conditions and the following disclaimer in the documentation +and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of itscontributors +may be used to endorse or promote products derived from this software without +specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/ccs811/README.md Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,426 @@ +# Driver for the ams CCS811 digital gas sensor for monitoring indoor air quality + +The driver is for the usage with the ESP-IDF. + +## About the sensor + +The CCS811 is an ultra-low power digital sensor which detects +**Volatile Organic Compounds (VOC)** for Indoor Air Quality (IAQ) monitoring +that. The sensor allows to + +- convert raw sensor data to Total Volatile Organic Compound (TVOC) and + equivalent CO2 (eCO2), +- compensate gas readings due to temperature and humidity using an external + sensor, +- trigger interrupts when new measurement results are available or eCO2 value + exceeds thresholds, +- correct baseline automatically or manually +- connect a NTC thermistor to provide means of calculating the local ambient + temperature. + +The sensor uses an I2C interface and supports clock stretching. See the notes +on clock stretching during I2C interface intialization. + +## Measurement Process + +### Sensor modes + +The CCS811 can operate in 5 different modes: + +Mode | Driver symbol | Period | RAW data | IAQ values +---- | ------------- | ------ |:--------:|:----------: +Idle, Low Current Mode | `CCS811_MODE_IDLE` | - | - | - +Constant Power Mode | `CCS811_MODE_1S` | 1 s | X | X +Pulse Heating Mode | `CCS811_MODE_10S` | 10 s | X | X +Low Power Pulse Heating Mode | `CCS811_MODE_60S` | 60 s | X | X +Constant Power Mode | `CCS811_MODE_250MS` | 250 ms | X | - + +After power up, the sensor starts automatically in *Idle, Low Current Mode* +(`CCS811_MODE_IDLE`). To start periodic measurements, the mode of the sensor +has to be changed to any measurement mode. Measurement modes with with +different rates of periodic measurements are available, see table above. + +**Please note:** In *Constant Power Mode* with measurements every 250 ms +(`CCS811_MODE_250MS`) only raw data are available. In all other measurement +modes, the Indoor Air Quality (IAQ) values are available additionally. +The *Constant Power Mode* with measurements every 250 ms (`CCS811_MODE_250ms`) +is only intended for systems where an external host system wants to run an +algorithm with raw data. + +Once the sensor is initialized with function `ccs811_init()`, function +`ccs811_set_mode()` can be used to start periodic measurements with a given +period. + +```C +ESP_ERROR_CHECK(i2cdev_init()); +... +static ccs811_dev_t sensor; +memset(&sensor, 0, sizeof(ccs811_dev_t)); // Zero descriptor +ESP_ERROR_CHECK(ccs811_init_desc(&sensor, 0, CCS811_I2C_ADDRESS_1, 5, 4); +... +if (ccs811_init(&sensor) == ESP_OK) +{ + ... + // start periodic measurement with one measurement per second + ESP_ERROR_CHECK(ccs811_set_mode(&sensor, CCS811_MODE_1S)); +} +... +``` + +**Please note:** + +1. After setting the mode, the sensor is in conditioning period that needs up + to 20 minutes, before accurate readings are generated, see the data sheet for + more details. + +2. During the early-live (burn-in) period, the CCS811 sensor should run for + 48 hours in the selected mode of operation to ensure sensor performance is + stable, see the datasheet for more details. + +3. When the sensor operating mode is changed to a new mode with a lower sample + rate, e.g., from *Pulse Heating Mode* (`CCS811_MODE_10S`) to *Low Power Pulse + Heating Mode* (`CCS811_MODE_60S`), it should be placed in *Idle, Low Current + Mode* (`CCS811_MODE_IDLE`) for at least 10 minutes before enabling the new + mode. + +When a sensor operating mode is changed to a new mode with a higher sample +rate, e.g., from *Low Power Pulse Heating Mode* (`CCS811_MODE_60S`) to +*Pulse Heating Mode* (`CCS811_MODE_10S`), there is no requirement to wait +before enabling the new mode. + +## Measurement results + +Once the measurement mode is set, the user task can use function +`ccs811_get_results()` with same rate as the measurement rate to fetch the +results. The function returns **raw data** as well as **Indoor Air Quality +(IAQ)** values. + +While raw data represents simply the current through the sensor and the voltage +across the sensor with the selected current, IAQ values are the results of the +processing these raw data by the sensor. IAQ values consist of the **equivalent +CO2 (eCO2)** with a range from 400 ppm to 8192 ppm and **Total Volatile Organic +Compound (TVOC)** with a range from 0 ppb to 1187 ppb. + +```C +uint16_t iaq_tvoc; +uint16_t iaq_eco2; +uint8_t raw_i; +uint16_t raw_v; +... +// get the results and do something with them +if (ccs811_get_results(&sensor, &tvoc, &eco2, &raw_i, &raw_v) == ESP_OK) +{ + ... +} +... +``` + +If some of the results are not needed, the corresponding pointer parameters +can be set to NULL. + +If the function `ccs811_get_results()` is called and no new data are +available, e.g., due to the sensor mode time tolerance of 2%, the function +still returns successfully. In this case, the results of the last measurement +are returned and the error code CCS811_ERR_NO_NEW_DATA. + +**Please note:** + +1. In *Constant Power Mode* with measurements every 250 ms (`CCS811_MODE_250MS`) + only raw data are available. + +2. The rate of fetching data must not be greater than the rate of measurement. + Due to the sensor mode timing tolerance of 2 %, the rate of fetching data + should be lower than the measurement rate. + +3. If the function is called and no new data are available, the results of the + latest measurement are returned and error code CCS811_ERR_NO_NEW_DATA is set. + +### Compensation + +If information about the environment like temperature and humidity are available +from another sensor, they can be used by CCS811 to compensate gas readings due +to temperature and humidity changes. Function `ccs811_set_environmental_data()` +can be used to set these environmental data. + +```C +float temperature; +float humidity; +... +if (sht3x_get_results(sht3x, &temperature, &humidity) == ESP_OK) + // set CCS811 environmental data with values fetched from SHT3x + ccs811_set_environmental_data(ccs811, temperature, humidity); +... +``` + +### NTC + +CCS811 supports an external interface for connecting a negative thermal +coefficient thermistor (R_NTC) to provide a cost effective and power efficient +means of calculating the local ambient temperature. The sensor measures the +voltage V_NTC across R_NTC as well as the voltage V_REF across a connected +reference resistor (R_REF). Function `ccs811_get_ntc_resistance()` can be used +to fetch the current resistance of R_NTC. It uses the resistance of R_REF and +measured voltages V_REF and V_NTV with the following equation: + +```text + R_NTC = R_REF / V_REF * V_NTC +``` + +Using the data sheet of the NTC, the ambient temperature can be calculated. See +application note ams AN000372 for more details. For example, with Adafruit +CCS811 Air Quality Sensor Breakout the ambienttemperature can be determined as +following: + +```C +... +#define CCS811_R_REF 100000 // resistance of the reference resistor +#define CCS811_R_NTC 10000 // resistance of NTC at a reference temperature +#define CCS811_R_NTC_TEMP 25 // reference temperature for NTC +#define CCS811_BCONSTANT 3380 // B constant + +// get NTC resistance +uint32_t r_ntc; +ccs811_get_ntc_resistance(&sensor, CCS811_R_REF, &r_ntc); + +// calculation of temperature from application note ams AN000372 +double ntc_temp; +ntc_temp = log((double)r_ntc / CCS811_R_NTC); // 1 +ntc_temp /= CCS811_BCONSTANT; // 2 +ntc_temp += 1.0 / (CCS811_R_NTC_TEMP + 273.15); // 3 +ntc_temp = 1.0 / ntc_temp; // 4 +ntc_temp -= 273.15; // 5 +.... +``` + +### Interrupts + +CCS811 supports two types of interrupts that can be used to fetch data: + +- data ready interrupt (INT_DATA_RDY) +- threshold interrupt (INT_THRESHOLD) + +#### Data ready interrupt + +At the end of each measurement cycle (every 250 ms, 1 second, 10 seconds, or +60 seconds), CCS811 can optionally trigger an interrupt. The signal *nINT* is +driven low as soon as new sensor values are ready to read. It will stop being +driven low when sensor data are read with function `ccs811_get_results()`. + +The interrupt is disabled by default. It can be enabled with function +`ccs811_enable_interrupt()`. + +```C +... +// enable the data ready interrupt +ESP_ERROR_CHECK(ccs811_enable_interrupt(&sensor, true)); +... +``` + +#### Threshold interrupt + +The user task can choose that the data ready interrupt is not generated every +time when new sensor values become ready but only if the eCO2 value moves from +the current range (LOW, MEDIUM, or HIGH) into another range by more than a +hysteresis value. Hysteresis is used to prevent multiple interrupts close to a +threshold. + +The interrupt is disabled by default and can be enabled with function +`ccs811_set_eco2_thresholds()`. The ranges are defined by parameters *low* and +*high* as following + +- **LOW** - below parameter value *low* +- **MEDIUM** - between parameter values *low* and *high* +- **HIGH** - above parameter value *high* is range **HIGH**. + +If all parameters have valid values, the function sets the thresholds and +enables the data ready interrupt. Using 0 for all parameters disables the +interrupt. + +```C +... +// set threshold parameters and enable threshold interrupt mode +ESP_ERROR_CHECK(ccs811_set_eco2_thresholds(&sensor, 600, 1100, 40)); +... +``` + +### Baseline + +CCS81 supports automatic baseline correction over a minimum time of 24 hours. +Using function `ccs811_get_baseline()`, the current baseline value can be saved +before the sensor is powered down. This baseline can then be restored with +function `ccs811_set_baseline()` after sensor is powered up again to continue +the automatic baseline process. + +## Usage + +First, the hardware configuration has to be established. + +### Communication interface settings + +Dependent on the hardware configuration, the communication interface settings +have to be defined. + +```C +// define I2C interfaces at which CCS811 sensors can be connected +#define I2C_PORT 0 +#define I2C_SCL_PIN 14 +#define I2C_SDA_PIN 13 + +// define GPIO for interrupt +#define INT_GPIO 5 +``` + +### Main program + +Before using the CCS811 driver, function `i2cdev_init()` needs to be called. + +**Please note:** CCS811 uses clock streching that can be longer than the +default I2C clock stretching. Therefore the clock stretching parameter of I2C +has to be set to at least `CCS811_I2C_CLOCK_STRETCH`. + +```C +static ccs811_dev_t sensor; // pointer to sensor device data structure +... +memset(&sensor, 0, sizeof(ccs811_dev_t)); +i2cdev_init(); // Init i2cdev library +i2c_set_timeout(I2C_PORT, CCS811_I2C_CLOCK_STRETCH); +... +ccs811_init_desc(&sensor, I2C_PORT, CCS811_I2C_ADDRESS_1, I2C_SDA_PIN, I2C_SCL_PIN); +``` + +Once I2C library initialized, function `ccs811_init()` has to be called +for each CCS811 sensor to initialize the sensor and to check its availability +as well as its error state. + +```C +... +if (ccs811_init(&sensor) == ESP_OK) +{ + ... +} +... +``` + +If initialization of the sensor was successful, the sensor mode has be set to +start periodic measurement. The sensor mode can be changed anytime later. + +```C +... +// start periodic measurement with one measurement per second +ccs811_set_mode(&sensor, CCS811_MODE_1S); +... +``` + +Finally, a user task that uses the sensor has to be created. + +```C +xTaskCreate(user_task, "user_task", 256, NULL, 2, 0); +``` + +The user task can use different approaches to fetch new data. Either new data +are fetched periodically or the interrupt signal *nINT* is used when new data +are available or eCO2 value exceeds defined thresholds. + +If new data are fetched **periodically** the implementation of the user task is +quite simply and could look like following. + +```C +void user_task(void *pvParameters) +{ + uint16_t tvoc; + uint16_t eco2; + + TickType_t last_wakeup = xTaskGetTickCount(); + + while (1) + { + // get the results and do something with them + if (ccs811_get_results(&sensor, &tvoc, &eco2, 0, 0) == ESP_OK) + ... + // passive waiting until 1 second is over + vTaskDelayUntil(&last_wakeup, 1000 / portTICK_PERIOD_MS); + } +} +... +``` + +The user task simply fetches new data with the same rate as the measurements +are performed. + +**Please note:** The rate of fetching the measurement results must be not +greater than the rate of periodic measurements of the sensor, however, it +*should be less* to avoid conflicts caused by the timing tolerance of the +sensor. + +A different approach is to use the **interrupt** *nINT*. This interrupt signal +is either triggered every time when new data are available (INT_DATA_RDY) or +only whenever eCO2 value exceeds defined thresholds (INT_THRESHOLD). In both +cases, the user has to implement an interrupt handler that either fetches the +data directly or triggers a task, that is waiting to fetch the data. + +```C +... +TaskHandle_t nINT_task; + +// Interrupt handler which resumes user_task_interrupt on interrupt + +void nINT_handler(uint8_t gpio) +{ + xTaskResumeFromISR(nINT_task); +} + +// User task that fetches the sensor values. + +void user_task_interrupt(void *pvParameters) +{ + uint16_t tvoc; + uint16_t eco2; + + while (1) + { + // task suspends itself and waits to be resumed by interrupt handler + vTaskSuspend(NULL); + + // after resume get the results and do something with them + if (ccs811_get_results(&sensor, &tvoc, &eco2, 0, 0) == ESP_OK) + ... + } +} +... + +xTaskCreate(user_task_interrupt, "user_task_interrupt", 256, NULL, 2, &nINT_task); +... +``` + +In this example, a task is defined which suspends itself in each cycle to wait +for fetching the data. The task is resumed by the interrupt handler. + +Finally, the interrupt handler has to be activated for the GPIO which is +connected to the interrupt signal. Furthermore, the interrupt has to be enabled +in the CCS811 sensor. + +Function `ccs811_enable_interrupt()` enables the interrupt that is triggered +whenever new data are available (INT_DATA_RDY). + +```C +... +// activate the interrupt for INT_GPIO and set the interrupt handler +gpio_set_interrupt(INT_GPIO, GPIO_INTTYPE_EDGE_NEG, nINT_handler); + +// enable the data ready interrupt INT_DATA_RDY +ccs811_enable_interrupt(&sensor, true); +... +``` + +Function `ccs811_set_eco2_thresholds()` enables the interrupt that is triggered +whenever eCO2 value exceeds the thresholds (INT_THRESHOLD) defined by parameters. + +```C +... +// activate the interrupt for INT_GPIO and set the interrupt handler +gpio_set_interrupt(INT_GPIO, GPIO_INTTYPE_EDGE_NEG, nINT_handler); + +// set threshold parameters and enable threshold interrupt mode INT_THRESHOLD +ccs811_set_eco2_thresholds(&sensor, 600, 1100, 40); +... +```
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/ccs811/ccs811.c Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,582 @@ +/* + * Copyright (c) 2017 Gunar Schorcht <https://github.com/gschorcht> + * Copyright (c) 2019 Ruslan V. Uss <unclerus@gmail.com> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of itscontributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file ccs811.c + * + * ESP-IDF driver for AMS CCS811 digital gas sensor connected to I2C + * + * Ported from esp-open-rtos + * + * Copyright (c) 2017 Gunar Schorcht <https://github.com/gschorcht>\n + * Copyright (c) 2020 Ruslan V. Uss <unclerus@gmail.com>\n + * + * BSD Licensed as described in the file LICENSE + */ +#include <string.h> +#include <inttypes.h> +#include <esp_log.h> +#include <esp_idf_lib_helpers.h> +#include <freertos/FreeRTOS.h> +#include <freertos/task.h> + +#include "ccs811.h" + +#define I2C_FREQ_HZ 400000 // 400kHz max + +static const char *TAG = "ccs811"; + +/* CCS811 register addresses */ +#define CCS811_REG_STATUS 0x00 +#define CCS811_REG_MEAS_MODE 0x01 +#define CCS811_REG_ALG_RESULT_DATA 0x02 +#define CCS811_REG_RAW_DATA 0x03 +#define CCS811_REG_ENV_DATA 0x05 +#define CCS811_REG_NTC 0x06 +#define CCS811_REG_THRESHOLDS 0x10 +#define CCS811_REG_BASELINE 0x11 + +#define CCS811_REG_HW_ID 0x20 +#define CCS811_REG_HW_VER 0x21 +#define CCS811_REG_FW_BOOT_VER 0x23 +#define CCS811_REG_FW_APP_VER 0x24 + +#define CCS811_REG_ERROR_ID 0xe0 + +#define CCS811_REG_APP_ERASE 0xf1 +#define CCS811_REG_APP_DATA 0xf2 +#define CCS811_REG_APP_VERIFY 0xf3 +#define CCS811_REG_APP_START 0xf4 +#define CCS811_REG_SW_RESET 0xff + +// status register bits +#define CCS811_STATUS_ERROR 0x01 // error, details in CCS811_REG_ERROR +#define CCS811_STATUS_DATA_RDY 0x08 // new data sample in ALG_RESULT_DATA +#define CCS811_STATUS_APP_VALID 0x10 // valid application firmware loaded +#define CCS811_STATUS_FW_MODE 0x80 // firmware is in application mode + +// error register bits +#define CCS811_ERR_WRITE_REG_INV 0x01 // invalid register address on write +#define CCS811_ERR_READ_REG_INV 0x02 // invalid register address on read +#define CCS811_ERR_MEASMODE_INV 0x04 // invalid requested measurement mode +#define CCS811_ERR_MAX_RESISTANCE 0x08 // maximum sensor resistance exceeded +#define CCS811_ERR_HEATER_FAULT 0x10 // heater current not in range +#define CCS811_ERR_HEATER_SUPPLY 0x20 // heater voltage not applied correctly + +/** + * Type declarations + */ + +typedef struct +{ + uint8_t reserved_1 :2; + uint8_t int_thresh :1; // interrupt if new ALG_RESULT_DAT crosses on of the thresholds + uint8_t int_datardy:1; // interrupt if new sample is ready in ALG_RESULT_DAT + uint8_t drive_mode :3; // mode number binary coded +} ccs811_meas_mode_reg_t; + +#define CHECK(x) do { esp_err_t __; if ((__ = x) != ESP_OK) return __; } while (0) +#define CHECK_ARG(VAL) do { if (!(VAL)) return ESP_ERR_INVALID_ARG; } while (0) +#define CHECK_LOGE(x, msg, ...) do { \ + esp_err_t __; \ + if ((__ = x) != ESP_OK) { \ + ESP_LOGE(TAG, msg, ## __VA_ARGS__); \ + return __; \ + } \ + } while (0) + +/////////////////////////////////////////////////////////////////////////////// +/// Static functions + +static esp_err_t read_reg_nolock(ccs811_dev_t *dev, uint8_t reg, uint8_t *data, uint32_t len) +{ + ESP_LOGD(TAG, "Read %" PRIu32 " bytes from i2c slave starting at reg addr %02x.", len, reg); + + esp_err_t res = i2c_dev_read_reg(&dev->i2c_dev, reg, data, len); + if (res != ESP_OK) + { + ESP_LOGE(TAG, "Error %d on read %" PRIu32 " bytes from I2C slave reg addr %02x.", res, len, reg); + return res; + } + + return ESP_OK; +} + +static esp_err_t write_reg_nolock(ccs811_dev_t *dev, uint8_t reg, uint8_t *data, uint32_t len) +{ + ESP_LOGD(TAG, "Write %" PRIu32 " bytes to i2c slave starting at reg addr %02x", len, reg); + + esp_err_t res = i2c_dev_write_reg(&dev->i2c_dev, reg, data, len); + if (res != ESP_OK) + { + ESP_LOGE(TAG, "Error %d on write %" PRIu32 " bytes to i2c slave register %02x.", res, len, reg); + return res; + } + + return ESP_OK; +} + +static esp_err_t ccs811_is_available(ccs811_dev_t *dev) +{ + CHECK_ARG(dev); + + uint8_t reg_data[5]; + + // check hardware id (register 0x20) and hardware version (register 0x21) + I2C_DEV_TAKE_MUTEX(&dev->i2c_dev); + I2C_DEV_CHECK(&dev->i2c_dev, read_reg_nolock(dev, CCS811_REG_HW_ID, reg_data, 5)); + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); + + if (reg_data[0] != 0x81) + { + ESP_LOGE(TAG, "Wrong hardware ID %02x, should be 0x81", reg_data[0]); + return CCS811_ERR_HW_ID; + } + + ESP_LOGD(TAG, "hardware version: %02x", reg_data[1]); + ESP_LOGD(TAG, "firmware boot version: %02x", reg_data[3]); + ESP_LOGD(TAG, "firmware app version: %02x", reg_data[4]); + + return ESP_OK; +} + +static esp_err_t ccs811_enable_threshold(ccs811_dev_t *dev, bool enabled) +{ + CHECK_ARG(dev); + + ccs811_meas_mode_reg_t reg; + + // first, enable/disable the data ready interrupt + CHECK(ccs811_enable_interrupt(dev, enabled)); + + I2C_DEV_TAKE_MUTEX(&dev->i2c_dev); + + // read measurement mode register value + I2C_DEV_CHECK(&dev->i2c_dev, read_reg_nolock(dev, CCS811_REG_MEAS_MODE, (uint8_t *)®, 1)); + + // second, enable/disable the threshold interrupt mode + reg.int_thresh = enabled; + + // write back measurement mode register + I2C_DEV_CHECK_LOGE(&dev->i2c_dev, + write_reg_nolock(dev, CCS811_REG_MEAS_MODE, (uint8_t *)®, 1), + "Could not set measurement mode register."); + + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); + + return ESP_OK; +} + +static esp_err_t ccs811_check_error_status(ccs811_dev_t *dev) +{ + uint8_t status; + uint8_t err_reg; + + I2C_DEV_TAKE_MUTEX(&dev->i2c_dev); + + // check status register + I2C_DEV_CHECK(&dev->i2c_dev, read_reg_nolock(dev, CCS811_REG_STATUS, &status, 1)); + + if (!status & CCS811_STATUS_ERROR) + { + // everything is fine + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); + return ESP_OK; + } + + // Check the error register + I2C_DEV_CHECK(&dev->i2c_dev, read_reg_nolock(dev, CCS811_REG_ERROR_ID, &err_reg, 1)); + + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); + + if (err_reg & CCS811_ERR_WRITE_REG_INV) + { + ESP_LOGE(TAG, "Received an invalid register for write."); + return CCS811_ERR_WR_REG_INV; + } + + if (err_reg & CCS811_ERR_READ_REG_INV) + { + ESP_LOGE(TAG, "Received an invalid register for read."); + return CCS811_ERR_RD_REG_INV; + } + + if (err_reg & CCS811_ERR_MEASMODE_INV) + { + ESP_LOGE(TAG, "Received an invalid measurement mode request."); + return CCS811_ERR_MM_INV; + } + + if (err_reg & CCS811_ERR_MAX_RESISTANCE) + { + ESP_LOGE(TAG, "Sensor resistance measurement has reached or exceeded the maximum range."); + return CCS811_ERR_MAX_RESIST; + } + + if (err_reg & CCS811_ERR_HEATER_FAULT) + { + ESP_LOGE(TAG, "Heater current not in range."); + return CCS811_ERR_HEAT_FAULT; + } + + if (err_reg & CCS811_ERR_HEATER_SUPPLY) + { + ESP_LOGE(TAG, "Heater voltage is not being applied correctly."); + return CCS811_ERR_HEAT_SUPPLY; + } + + return ESP_OK; +} + +/////////////////////////////////////////////////////////////////////////////// +/// Public functions + +esp_err_t ccs811_init_desc(ccs811_dev_t *dev, uint8_t addr, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio) +{ + CHECK_ARG(dev); + if (addr != CCS811_I2C_ADDRESS_1 && addr != CCS811_I2C_ADDRESS_2) + { + ESP_LOGE(TAG, "Invalid device address: 0x%02x", addr); + return ESP_ERR_INVALID_ARG; + } + + dev->i2c_dev.port = port; + dev->i2c_dev.addr = addr; + dev->i2c_dev.cfg.sda_io_num = sda_gpio; + dev->i2c_dev.cfg.scl_io_num = scl_gpio; +#if HELPER_TARGET_IS_ESP32 + dev->i2c_dev.cfg.master.clk_speed = I2C_FREQ_HZ; +#endif + dev->i2c_dev.timeout_ticks = I2CDEV_MAX_STRETCH_TIME; + + return i2c_dev_create_mutex(&dev->i2c_dev); +} + +esp_err_t ccs811_free_desc(ccs811_dev_t *dev) +{ + CHECK_ARG(dev); + + return i2c_dev_delete_mutex(&dev->i2c_dev); +} + +esp_err_t ccs811_init(ccs811_dev_t *dev) +{ + CHECK_ARG(dev); + + // init sensor data structure + dev->mode = CCS811_MODE_IDLE; + + // check whether sensor is available including the check of the hardware + // id and the error state + CHECK_LOGE(ccs811_is_available(dev), "Sensor is not available."); + + I2C_DEV_TAKE_MUTEX(&dev->i2c_dev); + + const static uint8_t sw_reset[4] = { 0x11, 0xe5, 0x72, 0x8a }; + + // doing a software reset first + I2C_DEV_CHECK_LOGE(&dev->i2c_dev, + write_reg_nolock(dev, CCS811_REG_SW_RESET, (uint8_t *)sw_reset, 4), + "Could not reset the sensor."); + + uint8_t status; + + // wait 100 ms after the reset + vTaskDelay(pdMS_TO_TICKS(100)); + + // get the status to check whether sensor is in bootloader mode + I2C_DEV_CHECK_LOGE(&dev->i2c_dev, + read_reg_nolock(dev, CCS811_REG_STATUS, &status, 1), + "Could not read status register 0x%02x.", CCS811_REG_STATUS); + + // if sensor is in bootloader mode (FW_MODE == 0), it has to switch + // to the application mode first + if (!(status & CCS811_STATUS_FW_MODE)) + { + // check whether valid application firmware is loaded + if (!(status & CCS811_STATUS_APP_VALID)) + { + ESP_LOGE(TAG, "Sensor is in boot mode, but has no valid application."); + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); + return CCS811_ERR_NO_APP; + } + + // swtich to application mode + uint8_t r = CCS811_REG_APP_START; + I2C_DEV_CHECK_LOGE(&dev->i2c_dev, + i2c_dev_write(&dev->i2c_dev, NULL, 0, &r, 1), + "Could not start application."); + + // wait 100 ms after starting the app + vTaskDelay(pdMS_TO_TICKS(100)); + + // get the status to check whether sensor switched to application mode + I2C_DEV_CHECK_LOGE(&dev->i2c_dev, + read_reg_nolock(dev, CCS811_REG_STATUS, &status, 1), + "Could not read application status."); + if (!(status & CCS811_STATUS_FW_MODE)) + { + ESP_LOGE(TAG, "Could not start application, invalid status 0x%02x.", status); + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); + return CCS811_ERR_APP_START_FAIL; + } + } + + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); + + // try to set default measurement mode to CCS811_MODE_1S + CHECK(ccs811_set_mode(dev, CCS811_MODE_1S)); + + return ESP_OK; +} + +esp_err_t ccs811_set_mode(ccs811_dev_t *dev, ccs811_mode_t mode) +{ + CHECK_ARG(dev); + + ccs811_meas_mode_reg_t reg; + + I2C_DEV_TAKE_MUTEX(&dev->i2c_dev); + + // read measurement mode register value + I2C_DEV_CHECK(&dev->i2c_dev, read_reg_nolock(dev, CCS811_REG_MEAS_MODE, (uint8_t *)®, 1)); + + reg.drive_mode = mode; + + // write back measurement mode register + I2C_DEV_CHECK_LOGE(&dev->i2c_dev, + write_reg_nolock(dev, CCS811_REG_MEAS_MODE, (uint8_t *)®, 1), + "Could not set measurement mode."); + + // check whether setting measurement mode were succesfull + I2C_DEV_CHECK_LOGE(&dev->i2c_dev, + read_reg_nolock(dev, CCS811_REG_MEAS_MODE, (uint8_t *)®, 1), + "Could not set measurement mode."); + + if (reg.drive_mode != mode) + { + ESP_LOGE(TAG, "Could not set measurement mode to %d", mode); + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); + return CCS811_ERR_MM_INV; + } + + dev->mode = mode; + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); + + return ESP_OK; +} + +#define CCS811_ALG_DATA_ECO2_HB 0 +#define CCS811_ALG_DATA_ECO2_LB 1 +#define CCS811_ALG_DATA_TVOC_HB 2 +#define CCS811_ALG_DATA_TVOC_LB 3 +#define CCS811_ALG_DATA_STATUS 4 +#define CCS811_ALG_DATA_ERROR_ID 5 +#define CCS811_ALG_DATA_RAW_HB 6 +#define CCS811_ALG_DATA_RAW_LB 7 + +esp_err_t ccs811_get_results(ccs811_dev_t *dev, uint16_t *iaq_tvoc, + uint16_t *iaq_eco2, uint8_t *raw_i, uint16_t *raw_v) +{ + CHECK_ARG(dev); + + if (dev->mode == CCS811_MODE_IDLE) + { + ESP_LOGE(TAG, "Sensor is in idle mode and not performing measurements."); + return CCS811_ERR_WRONG_MODE; + } + + if (dev->mode == CCS811_MODE_250MS && (iaq_tvoc || iaq_eco2)) + { + ESP_LOGE(TAG, "Sensor is in constant power mode, only raw data are available every 250ms"); + return CCS811_ERR_NO_IAQ_DATA; + } + + uint8_t data[8]; + + // read IAQ sensor values and RAW sensor data including status and error id + I2C_DEV_TAKE_MUTEX(&dev->i2c_dev); + I2C_DEV_CHECK_LOGE(&dev->i2c_dev, + read_reg_nolock(dev, CCS811_REG_ALG_RESULT_DATA, data, 8), + "Could not read sensor data."); + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); + + // check for errors + if (data[CCS811_ALG_DATA_STATUS] & CCS811_STATUS_ERROR) + return ccs811_check_error_status(dev); + + // check whether new data are ready, if not, latest values are read from sensor + if (!(data[CCS811_ALG_DATA_STATUS] & CCS811_STATUS_DATA_RDY)) + { + ESP_LOGD(TAG, "No new data."); + return CCS811_ERR_NO_NEW_DATA; + } + + // if *iaq* is not NULL return IAQ sensor values + if (iaq_tvoc) + *iaq_tvoc = data[CCS811_ALG_DATA_TVOC_HB] << 8 | data[CCS811_ALG_DATA_TVOC_LB]; + if (iaq_eco2) + *iaq_eco2 = data[CCS811_ALG_DATA_ECO2_HB] << 8 | data[CCS811_ALG_DATA_ECO2_LB]; + + // if *raw* is not NULL return RAW sensor data + if (raw_i) + *raw_i = data[CCS811_ALG_DATA_RAW_HB] >> 2; + if (raw_v) + *raw_v = (data[CCS811_ALG_DATA_RAW_HB] & 0x03) << 8 | data[CCS811_ALG_DATA_RAW_LB]; + + return ESP_OK; +} + +esp_err_t ccs811_get_ntc_resistance(ccs811_dev_t *dev, uint32_t r_ref, + uint32_t *res) +{ + CHECK_ARG(dev && res); + + uint8_t data[4]; + + // read baseline register + I2C_DEV_TAKE_MUTEX(&dev->i2c_dev); + I2C_DEV_CHECK(&dev->i2c_dev, read_reg_nolock(dev, CCS811_REG_NTC, data, 4)); + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); + + // calculation from application note ams AN000372 + uint16_t v_ref = (uint16_t) (data[0]) << 8 | data[1]; + uint16_t v_ntc = (uint16_t) (data[2]) << 8 | data[3]; + + *res = (v_ntc * r_ref / v_ref); + + return ESP_OK; +} + +esp_err_t ccs811_set_environmental_data(ccs811_dev_t *dev, + float temperature, float humidity) +{ + CHECK_ARG(dev); + + uint16_t hum_conv = humidity * 512.0f + 0.5f; + uint16_t temp_conv = (temperature + 25.0f) * 512.0f + 0.5f; + + + // fill environmental data + uint8_t data[4] = { + (uint8_t)((hum_conv >> 8) & 0xFF), (uint8_t)(hum_conv & 0xFF), + (uint8_t)((temp_conv >> 8) & 0xFF), (uint8_t)(temp_conv & 0xFF) + }; + + // send environmental data to the sensor + I2C_DEV_TAKE_MUTEX(&dev->i2c_dev); + I2C_DEV_CHECK_LOGE(&dev->i2c_dev, + write_reg_nolock(dev, CCS811_REG_ENV_DATA, data, 4), + "Could not write environmental data to sensor."); + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); + + return ESP_OK; +} + +esp_err_t ccs811_set_eco2_thresholds(ccs811_dev_t *dev, uint16_t low, + uint16_t high, uint8_t hysteresis) +{ + CHECK_ARG(dev); + + // check whether interrupt has to be disabled + if (!low && !high && !hysteresis) + return ccs811_enable_threshold(dev, false); + + // check parameters + if (low < CCS_ECO2_RANGE_MIN || high > CCS_ECO2_RANGE_MAX || low > high || !hysteresis) + { + ESP_LOGE(TAG, "Wrong threshold parameters"); + CHECK(ccs811_enable_threshold(dev, false)); + return CCS811_ERR_WRONG_PARAMS; + } + + // fill the threshold data + uint8_t data[5] = { low >> 8, low & 0xff, high >> 8, high & 0xff, hysteresis }; + + // write threshold data to the sensor + I2C_DEV_TAKE_MUTEX(&dev->i2c_dev); + I2C_DEV_CHECK_LOGE(&dev->i2c_dev, + write_reg_nolock(dev, CCS811_REG_THRESHOLDS, data, 5), + "Could not write threshold interrupt data to sensor."); + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); + + // finally enable the threshold interrupt mode + return ccs811_enable_threshold(dev, true); +} + +esp_err_t ccs811_enable_interrupt(ccs811_dev_t *dev, bool enabled) +{ + CHECK_ARG(dev); + + ccs811_meas_mode_reg_t reg; + + I2C_DEV_TAKE_MUTEX(&dev->i2c_dev); + + // read measurement mode register value + I2C_DEV_CHECK(&dev->i2c_dev, read_reg_nolock(dev, CCS811_REG_MEAS_MODE, (uint8_t *)®, 1)); + + reg.int_datardy = enabled; + reg.int_thresh = false; // threshold mode must not enabled + + // write back measurement mode register + I2C_DEV_CHECK(&dev->i2c_dev, write_reg_nolock(dev, CCS811_REG_MEAS_MODE, (uint8_t *)®, 1)); + + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); + + return ESP_OK; +} + +esp_err_t ccs811_get_baseline(ccs811_dev_t *dev, uint16_t *baseline) +{ + CHECK_ARG(dev && baseline); + + uint8_t data[2]; + + I2C_DEV_TAKE_MUTEX(&dev->i2c_dev); + // read baseline register + I2C_DEV_CHECK(&dev->i2c_dev, read_reg_nolock(dev, CCS811_REG_BASELINE, data, 2)); + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); + + *baseline = (uint16_t) (data[0]) << 8 | data[1]; + + return ESP_OK; +} + +esp_err_t ccs811_set_baseline(ccs811_dev_t *dev, uint16_t baseline) +{ + CHECK_ARG(dev); + + uint8_t data[2] = { baseline >> 8, baseline & 0xff }; + + I2C_DEV_TAKE_MUTEX(&dev->i2c_dev); + // write baseline register + I2C_DEV_CHECK(&dev->i2c_dev, write_reg_nolock(dev, CCS811_REG_BASELINE, data, 2)); + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); + + return ESP_OK; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/ccs811/ccs811.h Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,313 @@ +/* + * Copyright (c) 2017 Gunar Schorcht <https://github.com/gschorcht> + * Copyright (c) 2019 Ruslan V. Uss <unclerus@gmail.com> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of itscontributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file ccs811.h + * @defgroup ccs811 ccs811 + * @{ + * + * ESP-IDF driver for AMS CCS811 digital gas sensor connected to I2C + * + * Ported from esp-open-rtos + * + * Copyright (c) 2017 Gunar Schorcht <https://github.com/gschorcht>\n + * Copyright (c) 2020 Ruslan V. Uss <unclerus@gmail.com>\n + * + * BSD Licensed as described in the file LICENSE + */ +#ifndef __CCS811_H__ +#define __CCS811_H__ + +#include <stdbool.h> +#include <i2cdev.h> +#include <esp_err.h> + +//!< CCS811 I2C addresses +#define CCS811_I2C_ADDRESS_1 0x5a //!< default +#define CCS811_I2C_ADDRESS_2 0x5b + +#define CCS811_ERR_BASE 0xa000 + +//!< CCS811 driver error codes ORed with error codes for I2C the interface +#define CCS811_ERR_BOOT_MODE (CCS811_ERR_BASE + 1) //!< firmware is in boot mode +#define CCS811_ERR_NO_APP (CCS811_ERR_BASE + 2) //!< no application firmware loaded +#define CCS811_ERR_NO_NEW_DATA (CCS811_ERR_BASE + 3) //!< no new data samples are ready +#define CCS811_ERR_NO_IAQ_DATA (CCS811_ERR_BASE + 4) //!< no new data samples are ready +#define CCS811_ERR_HW_ID (CCS811_ERR_BASE + 5) //!< wrong hardware ID +#define CCS811_ERR_INV_SENS (CCS811_ERR_BASE + 6) //!< invalid sensor ID +#define CCS811_ERR_WR_REG_INV (CCS811_ERR_BASE + 7) //!< invalid register addr on write +#define CCS811_ERR_RD_REG_INV (CCS811_ERR_BASE + 8) //!< invalid register addr on read +#define CCS811_ERR_MM_INV (CCS811_ERR_BASE + 9) //!< invalid measurement mode +#define CCS811_ERR_MAX_RESIST (CCS811_ERR_BASE + 10) //!< max sensor resistance reached +#define CCS811_ERR_HEAT_FAULT (CCS811_ERR_BASE + 11) //!< heater current not in range +#define CCS811_ERR_HEAT_SUPPLY (CCS811_ERR_BASE + 12) //!< heater voltage not correct +#define CCS811_ERR_WRONG_MODE (CCS811_ERR_BASE + 13) //!< wrong measurement mode +#define CCS811_ERR_RD_STAT_FAILED (CCS811_ERR_BASE + 14) //!< read status register failed +#define CCS811_ERR_RD_DATA_FAILED (CCS811_ERR_BASE + 15) //!< read sensor data failed +#define CCS811_ERR_APP_START_FAIL (CCS811_ERR_BASE + 16) //!< sensor app start failure +#define CCS811_ERR_WRONG_PARAMS (CCS811_ERR_BASE + 17) //!< wrong parameters used + +// ranges +#define CCS_ECO2_RANGE_MIN 400 +#define CCS_ECO2_RANGE_MAX 8192 +#define CCS_TVOC_RANGE_MIN 0 +#define CCS_TVOC_RANGE_MAX 1187 + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief CCS811 operation modes + */ +typedef enum +{ + CCS811_MODE_IDLE = 0, //!< Idle, low current mode + CCS811_MODE_1S = 1, //!< Constant Power mode, IAQ values every 1 s + CCS811_MODE_10S = 2, //!< Pulse Heating mode, IAQ values every 10 s + CCS811_MODE_60S = 3, //!< Low Power Pulse Heating, IAQ values every 60 s + CCS811_MODE_250MS = 4 //!< Constant Power mode, RAW data every 250 ms +} ccs811_mode_t; + +/** + * @brief CCS811 sensor device data structure + */ +typedef struct +{ + i2c_dev_t i2c_dev; //!< I2C device handle + ccs811_mode_t mode; //!< operation mode +} ccs811_dev_t; + +/** + * @brief Initialize device descriptor + * + * @param dev Pointer to the sensor device data structure + * @param addr Sensor address + * @param port I2C port number + * @param sda_gpio GPIO pin number for SDA + * @param scl_gpio GPIO pin number for SCL + * @returns ESP_OK on success + */ +esp_err_t ccs811_init_desc(ccs811_dev_t *dev, uint8_t addr, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio); + +/** + * @brief Free device descriptor + * + * @param dev Pointer to the sensor device data structure + * @returns ESP_OK on success + */ +esp_err_t ccs811_free_desc(ccs811_dev_t *dev); + +/** + * @brief Initialize a CCS811 sensor + * + * The function initializes the CCS811 sensor and checks its availability. + * + * @param dev Pointer to the sensor device data structure + * + * @returns ESP_OK on success + */ +esp_err_t ccs811_init(ccs811_dev_t *dev); + +/** + * @brief Set the operation mode of the sensor + * + * The function sets the operating mode of the sensor. If the parameter + * *mode* is either ::CCS811_MODE_1S, ::CCS811_MODE_10S, ::CCS811_MODE_60S + * or ::CCS811_MODE_250MS, the sensor starts a periodic measurement with + * the specified period. Function ::ccs811_get_results() can then be used at + * the same rate to get the results. + * + * In ::CCS811_MODE_1S, ::CCS811_MODE_10S and ::CCS811_MODE_60S, raw sensor + * data as well as IAQ values calculated by the sensor values are available. + * In ::CCS811_MODE_250MS, only raw data are available. + * + * In case, parameter mode is ::CCS811_MODE_IDLE, the sensor does not perform + * any measurements. + * + * Please note: Mode timings are subject to typical 2% tolerance due + * to accuracy of internal sensor clock. + * + * Please note: After setting the sensor mode, the sensor needs up to + * 20 minutes, before accurate readings are generated. + * + * Please note: When a sensor operating mode is changed to a new mode with + * a lower sample rate, e.g., from ::CCS811_MODE_60S to ::CCS811_MODE_1S, it + * should be placed in *mode_idle* for at least 10 minutes before enabling + * the new mode. + * + * @param dev Pointer to the sensor device data structure + * @param mode Operation mode of the sensor + * + * @returns ESP_OK on success + */ +esp_err_t ccs811_set_mode(ccs811_dev_t *dev, ccs811_mode_t mode); + +/** + * @brief Get latest IAQ sensor values and/or RAW sensor data + * + * The function reads the IAQ sensor values (TVOC and eCO2) and/or the raw + * sensor data. If some of the results are not needed, the corresponding + * pointer parameters can be set to NULL. + * + * Please note: If the function is called and no new data are available, + * e.g., due to the sensor mode time tolerance of 2%, the function still + * returns successfully. In this case, the results of the last measurement + * are returned and the error code CCS811_ERR_NO_NEW_DATA is set. + * + * Please note: In ::CCS811_MODE_250MS, only RAW data are available. In + * that case, the function fails with error_code CCS811_ERR_NO_IAQ_DATA + * if parameters *iaq_tvoc* and *iaq_eco2* are not NULL. + * + * @param dev pointer to the sensor device data structure + * @param iaq_tvoc TVOC total volatile organic compound (0 - 1187 ppb) + * @param iaq_eco2 eCO2 equivalent CO2 (400 - 8192 ppm) + * @param raw_i current through the sensor used for measuring (0 - 63 uA) + * @param raw_v voltage across the sensor measured (0 - 1023 = 1.65 V) + * + * @returns ESP_OK on success + */ +esp_err_t ccs811_get_results(ccs811_dev_t *dev, uint16_t *iaq_tvoc, uint16_t *iaq_eco2, uint8_t *raw_i, uint16_t *raw_v); + +/** + * @brief Get the resistance of connected NTC thermistor + * + * CCS811 supports an external interface for connecting a negative thermal + * coefficient thermistor (R_NTC) to provide a cost effective and power + * efficient means of calculating the local ambient temperature. The sensor + * measures the voltage V_NTC across the R_NTC as well as the voltage V_REF + * across a connected reference resistor (R_REF). + * The function returns the current resistance of R_NTC using the equation + * + * R_NTC = R_REF / V_REF * V_NTC + * + * Using the data sheet of the NTC, the ambient temperature can be calculated. + * + * @param dev pointer to the sensor device data structure + * @param r_ref resistance of R_REF in Ohm + * @param[out] res resistance of R_NTC in Ohm + * @returns ESP_OK on success + */ +esp_err_t ccs811_get_ntc_resistance(ccs811_dev_t *dev, uint32_t r_ref, uint32_t *res); + +/** + * @brief Set environmental data + * + * If information about the environment are available from another sensor, + * they can be used by CCS811 to compensate gas readings due to + * temperature and humidity changes. + * + * @param dev pointer to the sensor device data structure + * @param temperature measured temperature in degree Celsius + * @param humidity measured relative humidity in percent + * @returns ESP_OK on success + */ +esp_err_t ccs811_set_environmental_data(ccs811_dev_t *dev, float temperature, float humidity); + +/** + * @brief Enable or disable data ready interrupt signal *nINT* + * + * At the end of each measurement cycle (250ms, 1s, 10s, 60s), CCS811 can + * optionally trigger an interrupt. The signal *nINT* is driven low as soon + * as new sensor values are ready to read. It will stop being driven low + * when sensor data are read with function *ccs811_get_results*. + * + * The interrupt is disabled by default. + * + * @param dev pointer to the sensor device data structure + * @param enabled if true, the interrupt is enabled, or disabled otherwise + * @returns ESP_OK on success + */ +esp_err_t ccs811_enable_interrupt(ccs811_dev_t *dev, bool enabled); + +/** + * @brief Set eCO2 threshold mode for data ready interrupts + * + * The user task can choose that the data ready interrupt is not generated + * every time when new sensor values become ready but only if the eCO2 value + * moves from the current range (LOW, MEDIUM, or HIGH) into another range by + * more than a hysteresis value. Hysteresis is used to prevent multiple + * interrupts close to a threshold. + * + * - LOW below parameter value *low* + * - MEDIUM between parameter values *low* and *high* + * - HIGH above parameter value *high* is range HIGH. + * + * If all parameters have valid values, the function sets the thresholds and + * enables the data ready interrupt. Using 0 for all parameters disables the + * interrupt. + * + * The interrupt is disabled by default. + * + * @param dev pointer to the sensor device data structure + * @param low threshold LOW to MEDIUM (> 400, default 1500) + * @param high threshold MEDIUM to HIGH (< 8192, default 2500) + * @param hysteresis hysteresis value (default 50) + * @returns ESP_OK on success + */ +esp_err_t ccs811_set_eco2_thresholds(ccs811_dev_t *dev, uint16_t low, uint16_t high, uint8_t hysteresis); + +/** + * @brief Get the current baseline value from sensor + * + * The sensor supports automatic baseline correction over a minimum time of + * 24 hours. Using this function, the current baseline value can be saved + * before the sensor is powered down. This baseline can then be restored after + * sensor is powered up again to continue the automatic baseline process. + * + * @param dev pointer to the sensor device data structure + * @param[out] baseline current baseline value on success, or 0 on error + * @returns ESP_OK on success + */ +esp_err_t ccs811_get_baseline(ccs811_dev_t *dev, uint16_t *baseline); + +/** + * @brief Write a previously stored baseline value to the sensor + * + * The sensor supports automatic baseline correction over a minimum time of + * 24 hours. Using this function, a previously saved baseline value be + * restored after the sensor is powered up to continue the automatic baseline + * process. + * + * Please note: The baseline must be written after the conditioning period + * of 20 min after power up. + * + * @param dev pointer to the sensor device data structure + * @param baseline baseline to be set + * @returns ESP_OK on success + */ +esp_err_t ccs811_set_baseline(ccs811_dev_t *dev, uint16_t baseline); + +#ifdef __cplusplus +} +#endif /* End of CPP guard */ + +/**@}*/ + +#endif /* __CCS811_H__ */ +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/ccs811/component.mk Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,2 @@ +COMPONENT_ADD_INCLUDEDIRS = . +COMPONENT_DEPENDS = i2cdev log
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/color/.eil.yml Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,22 @@ +--- +components: + - name: color + description: | + Common library for RGB and HSV colors + group: common + groups: [] + code_owners: + - name: UncleRus + depends: + - name: lib8tion + thread_safe: N/A + targets: + - name: esp32 + - name: esp8266 + - name: esp32s2 + - name: esp32c3 + licenses: + - name: MIT + copyrights: + - name: FastLED + year: 2013
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/color/CMakeLists.txt Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,13 @@ +if(${IDF_VERSION_MAJOR} STREQUAL 4 AND ${IDF_VERSION_MINOR} STREQUAL 1 AND ${IDF_VERSION_PATCH} STREQUAL 3) + message("Not including esp_timer in color") + set(req lib8tion) +else() + message("Including esp_timer in color") + set(req lib8tion) +endif() + +idf_component_register( + SRCS color.c + INCLUDE_DIRS . + REQUIRES ${req} +)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/color/LICENSE Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2013 FastLED + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/color/color.c Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,1102 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2013 FastLED + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "color.h" +#include <math.h> +#include <lib8tion.h> + +//////////////////////////////////////////////////////////////////////////////// + +#define APPLY_DIMMING(X) (X) +#define HSV_SECTION_6 (0x20) +#define HSV_SECTION_3 (0x40) + +rgb_t hsv2rgb_raw(hsv_t hsv) +{ + // Convert hue, saturation and brightness ( HSV/HSB ) to RGB + // "Dimming" is used on saturation and brightness to make + // the output more visually linear. + + // Apply dimming curves + uint8_t value = APPLY_DIMMING(hsv.val); + uint8_t saturation = hsv.sat; + + // The brightness floor is minimum number that all of + // R, G, and B will be set to. + uint8_t invsat = APPLY_DIMMING(255 - saturation); + uint8_t brightness_floor = (value * invsat) / 256; + + // The color amplitude is the maximum amount of R, G, and B + // that will be added on top of the brightness_floor to + // create the specific hue desired. + uint8_t color_amplitude = value - brightness_floor; + + // Figure out which section of the hue wheel we're in, + // and how far offset we are withing that section + uint8_t section = hsv.hue / HSV_SECTION_3; // 0..2 + uint8_t offset = hsv.hue % HSV_SECTION_3; // 0..63 + + uint8_t rampup = offset; // 0..63 + uint8_t rampdown = (HSV_SECTION_3 - 1) - offset; // 63..0 + + // We now scale rampup and rampdown to a 0-255 range -- at least + // in theory, but here's where architecture-specific decsions + // come in to play: + // To scale them up to 0-255, we'd want to multiply by 4. + // But in the very next step, we multiply the ramps by other + // values and then divide the resulting product by 256. + // So which is faster? + // ((ramp * 4) * othervalue) / 256 + // or + // ((ramp ) * othervalue) / 64 + // It depends on your processor architecture. + // On 8-bit AVR, the "/ 256" is just a one-cycle register move, + // but the "/ 64" might be a multicycle shift process. So on AVR + // it's faster do multiply the ramp values by four, and then + // divide by 256. + // On ARM, the "/ 256" and "/ 64" are one cycle each, so it's + // faster to NOT multiply the ramp values by four, and just to + // divide the resulting product by 64 (instead of 256). + // Moral of the story: trust your profiler, not your insticts. + + // Since there's an AVR assembly version elsewhere, we'll + // assume what we're on an architecture where any number of + // bit shifts has roughly the same cost, and we'll remove the + // redundant math at the source level: + + // // scale up to 255 range + // //rampup *= 4; // 0..252 + // //rampdown *= 4; // 0..252 + + // compute color-amplitude-scaled-down versions of rampup and rampdown + uint8_t rampup_amp_adj = (rampup * color_amplitude) / (256 / 4); + uint8_t rampdown_amp_adj = (rampdown * color_amplitude) / (256 / 4); + + // add brightness_floor offset to everything + uint8_t rampup_adj_with_floor = rampup_amp_adj + brightness_floor; + uint8_t rampdown_adj_with_floor = rampdown_amp_adj + brightness_floor; + + rgb_t rgb; + if (section) + { + if (section == 1) + { + // section 1: 0x40..0x7F + rgb.r = brightness_floor; + rgb.g = rampdown_adj_with_floor; + rgb.b = rampup_adj_with_floor; + } + else + { + // section 2; 0x80..0xBF + rgb.r = rampup_adj_with_floor; + rgb.g = brightness_floor; + rgb.b = rampdown_adj_with_floor; + } + } + else + { + // section 0: 0x00..0x3F + rgb.r = rampdown_adj_with_floor; + rgb.g = rampup_adj_with_floor; + rgb.b = brightness_floor; + } + + return rgb; +} + +rgb_t hsv2rgb_spectrum(hsv_t hsv) +{ + hsv.hue = scale8(hsv.hue, HUE_MAX_RAW); + return hsv2rgb_raw(hsv); +} + +#define K255 255 +#define K171 171 +#define K170 170 +#define K85 85 + +rgb_t hsv2rgb_rainbow(hsv_t hsv) +{ + // Yellow has a higher inherent brightness than + // any other color; 'pure' yellow is perceived to + // be 93% as bright as white. In order to make + // yellow appear the correct relative brightness, + // it has to be rendered brighter than all other + // colors. + // Level Y1 is a moderate boost, the default. + // Level Y2 is a strong boost. + const uint8_t Y1 = 1; + const uint8_t Y2 = 0; + + // G2: Whether to divide all greens by two. + // Depends GREATLY on your particular LEDs + const uint8_t G2 = 0; + + // Gscale: what to scale green down by. + // Depends GREATLY on your particular LEDs + const uint8_t Gscale = 0; + + + uint8_t hue = hsv.hue; + uint8_t sat = hsv.sat; + uint8_t val = hsv.val; + + uint8_t offset = hue & 0x1F; // 0..31 + + // offset8 = offset * 8 + uint8_t offset8 = offset << 3; + uint8_t third = scale8(offset8, (256 / 3)); // max = 85 + + uint8_t r, g, b; + + if (!(hue & 0x80)) + { + // 0XX + if (!(hue & 0x40)) + { + // 00X + //section 0-1 + if (!(hue & 0x20)) + { + // 000 + //case 0: // R -> O + r = K255 - third; + g = third; + b = 0; + } + else + { + // 001 + //case 1: // O -> Y + if (Y1) + { + r = K171; + g = K85 + third; + b = 0; + } + if (Y2) + { + r = K170 + third; + //uint8_t twothirds = (third << 1); + uint8_t twothirds = scale8(offset8, ((256 * 2) / 3)); // max=170 + g = K85 + twothirds; + b = 0; + } + } + } + else + { + //01X + // section 2-3 + if (!(hue & 0x20)) + { + // 010 + //case 2: // Y -> G + if (Y1) + { + //uint8_t twothirds = (third << 1); + uint8_t twothirds = scale8(offset8, ((256 * 2) / 3)); // max=170 + r = K171 - twothirds; + g = K170 + third; + b = 0; + } + if (Y2) + { + r = K255 - offset8; + g = K255; + b = 0; + } + } + else + { + // 011 + // case 3: // G -> A + r = 0; + g = K255 - third; + b = third; + } + } + } + else + { + // section 4-7 + // 1XX + if (!(hue & 0x40)) + { + // 10X + if (!(hue & 0x20)) + { + // 100 + //case 4: // A -> B + r = 0; + //uint8_t twothirds = (third << 1); + uint8_t twothirds = scale8(offset8, ((256 * 2) / 3)); // max=170 + g = K171 - twothirds; //K170? + b = K85 + twothirds; + + } + else + { + // 101 + //case 5: // B -> P + r = third; + g = 0; + b = K255 - third; + + } + } + else + { + if (!(hue & 0x20)) + { + // 110 + //case 6: // P -- K + r = K85 + third; + g = 0; + b = K171 - third; + + } + else + { + // 111 + //case 7: // K -> R + r = K170 + third; + g = 0; + b = K85 - third; + + } + } + } + + // This is one of the good places to scale the green down, + // although the client can scale green down as well. + if (G2) + g = g >> 1; + if (Gscale) + g = scale8_video(g, Gscale); + + // Scale down colors if we're desaturated at all + // and add the brightness_floor to r, g, and b. + if (sat != 255) + { + if (sat == 0) + { + r = 255; + b = 255; + g = 255; + } + else + { + uint8_t desat = 255 - sat; + desat = scale8_video(desat, desat); + + uint8_t satscale = 255 - desat; + //satscale = sat; // uncomment to revert to pre-2021 saturation behavior + + //nscale8x3_video( r, g, b, sat); + r = scale8(r, satscale); + g = scale8(g, satscale); + b = scale8(b, satscale); + + uint8_t brightness_floor = desat; + r += brightness_floor; + g += brightness_floor; + b += brightness_floor; + } + } + + // Now scale everything down if we're at value < 255. + if (val != 255) + { + + val = scale8_video(val, val); + if (val == 0) + { + r = 0; + g = 0; + b = 0; + } + else + { + // nscale8x3_video( r, g, b, val); + r = scale8(r, val); + g = scale8(g, val); + b = scale8(b, val); + } + } + + return rgb_from_values(r, g, b); +} + +#define FIXFRAC8(N,D) (((N) * 256) / (D)) + +// This function is only an approximation, and it is not +// nearly as fast as the normal HSV-to-RGB conversion. +// See extended notes in the .h file. +hsv_t rgb2hsv_approximate(rgb_t rgb) +{ + uint8_t r = rgb.r; + uint8_t g = rgb.g; + uint8_t b = rgb.b; + uint8_t h, s, v; + + // find desaturation + uint8_t desat = 255; + if (r < desat) desat = r; + if (g < desat) desat = g; + if (b < desat) desat = b; + + // remove saturation from all channels + r -= desat; + g -= desat; + b -= desat; + + s = 255 - desat; + if (s != 255) + { + // undo 'dimming' of saturation + s = 255 - sqrt16((255 - s) * 256); + } + // without lib8tion: float ... ew ... sqrt... double ew, or rather, ew ^ 0.5 + // if( s != 255 ) s = (255 - (256.0 * sqrt( (float)(255-s) / 256.0))); + + // at least one channel is now zero + // if all three channels are zero, we had a + // shade of gray. + if ((r + g + b) == 0) + { + // we pick hue zero for no special reason + hsv_t res = { + .h = 0, .s = 0, .v = 255 - s + }; + return res; + } + + // scale all channels up to compensate for desaturation + if (s < 255) + { + if (s == 0) + s = 1; + uint32_t scaleup = 65535 / (s); + r = ((uint32_t) (r) * scaleup) / 256; + g = ((uint32_t) (g) * scaleup) / 256; + b = ((uint32_t) (b) * scaleup) / 256; + } + + uint16_t total = r + g + b; + + // scale all channels up to compensate for low values + if (total < 255) + { + if (total == 0) + total = 1; + uint32_t scaleup = 65535 / (total); + r = ((uint32_t) (r) * scaleup) / 256; + g = ((uint32_t) (g) * scaleup) / 256; + b = ((uint32_t) (b) * scaleup) / 256; + } + + if (total > 255) + { + v = 255; + } + else + { + v = qadd8(desat, total); + // undo 'dimming' of brightness + if (v != 255) + v = sqrt16(v * 256); + // without lib8tion: float ... ew ... sqrt... double ew, or rather, ew ^ 0.5 + // if( v != 255) v = (256.0 * sqrt( (float)(v) / 256.0)); + } + + // since this wasn't a pure shade of gray, + // the interesting question is what hue is it + + // start with which channel is highest + // (ties don't matter) + uint8_t highest = r; + if (g > highest) highest = g; + if (b > highest) highest = b; + + if (highest == r) + { + // Red is highest. + // Hue could be Purple/Pink-Red,Red-Orange,Orange-Yellow + if (g == 0) + { + // if green is zero, we're in Purple/Pink-Red + h = (HUE_PURPLE + HUE_PINK) / 2; + h += scale8(qsub8(r, 128), FIXFRAC8(48, 128)); + } + else if ((r - g) > g) + { + // if R-G > G then we're in Red-Orange + h = HUE_RED; + h += scale8(g, FIXFRAC8(32, 85)); + } + else + { + // R-G < G, we're in Orange-Yellow + h = HUE_ORANGE; + h += scale8(qsub8((g - 85) + (171 - r), 4), FIXFRAC8(32, 85)); //221 + } + + } + else if (highest == g) + { + // Green is highest + // Hue could be Yellow-Green, Green-Aqua + if (b == 0) + { + // if Blue is zero, we're in Yellow-Green + // G = 171..255 + // R = 171.. 0 + h = HUE_YELLOW; + uint8_t radj = scale8(qsub8(171, r), 47); //171..0 -> 0..171 -> 0..31 + uint8_t gadj = scale8(qsub8(g, 171), 96); //171..255 -> 0..84 -> 0..31; + uint8_t rgadj = radj + gadj; + uint8_t hueadv = rgadj / 2; + h += hueadv; + //h += scale8( qadd8( 4, qadd8((g - 128), (128 - r))), + // FIXFRAC8(32,255)); // + } + else + { + // if Blue is nonzero we're in Green-Aqua + if ((g - b) > b) + { + h = HUE_GREEN; + h += scale8(b, FIXFRAC8(32, 85)); + } + else + { + h = HUE_AQUA; + h += scale8(qsub8(b, 85), FIXFRAC8(8, 42)); + } + } + + } + else /* highest == b */ + { + // Blue is highest + // Hue could be Aqua/Blue-Blue, Blue-Purple, Purple-Pink + if (r == 0) + { + // if red is zero, we're in Aqua/Blue-Blue + h = HUE_AQUA + ((HUE_BLUE - HUE_AQUA) / 4); + h += scale8(qsub8(b, 128), FIXFRAC8(24, 128)); + } + else if ((b - r) > r) + { + // B-R > R, we're in Blue-Purple + h = HUE_BLUE; + h += scale8(r, FIXFRAC8(32, 85)); + } + else + { + // B-R < R, we're in Purple-Pink + h = HUE_PURPLE; + h += scale8(qsub8(r, 85), FIXFRAC8(32, 85)); + } + } + + h += 1; + + return hsv_from_values(h, s, v); +} + +//////////////////////////////////////////////////////////////////////////////// + +rgb_t rgb_heat_color(uint8_t temperature) +{ + rgb_t heatcolor; + + // Scale 'heat' down from 0-255 to 0-191, + // which can then be easily divided into three + // equal 'thirds' of 64 units each. + uint8_t t192 = scale8_video(temperature, 191); + + // calculate a value that ramps up from + // zero to 255 in each 'third' of the scale. + uint8_t heatramp = t192 & 0x3F; // 0..63 + heatramp <<= 2; // scale up to 0..252 + + // now figure out which third of the spectrum we're in: + if (t192 & 0x80) + { + // we're in the hottest third + heatcolor.r = 255; // full red + heatcolor.g = 255; // full green + heatcolor.b = heatramp; // ramp up blue + } + else if (t192 & 0x40) + { + // we're in the middle third + heatcolor.r = 255; // full red + heatcolor.g = heatramp; // ramp up green + heatcolor.b = 0; // no blue + } + else + { + // we're in the coolest third + heatcolor.r = heatramp; // ramp up red + heatcolor.g = 0; // no green + heatcolor.b = 0; // no blue + } + + return heatcolor; +} + +//////////////////////////////////////////////////////////////////////////////// + +typedef uint16_t saccum87; + +void hsv_fill_solid_hsv(hsv_t *target, hsv_t color, size_t num) +{ + for (size_t i = 0; i < num; ++i) + target[i] = color; +} + +void rgb_fill_solid_hsv(rgb_t *target, hsv_t color, size_t num) +{ + rgb_t rgb = hsv2rgb_rainbow(color); + for (size_t i = 0; i < num; ++i) + target[i] = rgb; +} + +void rgb_fill_solid_rgb(rgb_t *target, rgb_t color, size_t num) +{ + for (size_t i = 0; i < num; ++i) + target[i] = color; +} + +void hsv_fill_gradient_hsv(hsv_t *target, size_t startpos, hsv_t startcolor, size_t endpos, hsv_t endcolor, + color_gradient_direction_t direction) +{ + // if the points are in the wrong order, straighten them + if (endpos < startpos) + { + size_t t = endpos; + hsv_t tc = endcolor; + endcolor = startcolor; + endpos = startpos; + startpos = t; + startcolor = tc; + } + + // If we're fading toward black (val=0) or white (sat=0), + // then set the endhue to the starthue. + // This lets us ramp smoothly to black or white, regardless + // of what 'hue' was set in the endcolor (since it doesn't matter) + if (endcolor.value == 0 || endcolor.saturation == 0) + endcolor.hue = startcolor.hue; + + // Similarly, if we're fading in from black (val=0) or white (sat=0) + // then set the starthue to the endhue. + // This lets us ramp smoothly up from black or white, regardless + // of what 'hue' was set in the startcolor (since it doesn't matter) + if (startcolor.value == 0 || startcolor.saturation == 0) + startcolor.hue = endcolor.hue; + + saccum87 huedistance87; + saccum87 satdistance87; + saccum87 valdistance87; + + satdistance87 = (endcolor.sat - startcolor.sat) << 7; + valdistance87 = (endcolor.val - startcolor.val) << 7; + + uint8_t huedelta8 = endcolor.hue - startcolor.hue; + + if (direction == COLOR_SHORTEST_HUES) + { + direction = COLOR_FORWARD_HUES; + if (huedelta8 > 127) + direction = COLOR_BACKWARD_HUES; + } + + if (direction == COLOR_LONGEST_HUES) + { + direction = COLOR_FORWARD_HUES; + if (huedelta8 < 128) + direction = COLOR_BACKWARD_HUES; + } + + if (direction == COLOR_FORWARD_HUES) + { + huedistance87 = huedelta8 << 7; + } + else /* direction == BACKWARD_HUES */ + { + huedistance87 = (uint8_t) (256 - huedelta8) << 7; + huedistance87 = -huedistance87; + } + + size_t pixeldistance = endpos - startpos; + int16_t divisor = pixeldistance ? pixeldistance : 1; + + saccum87 huedelta87 = huedistance87 / divisor; + saccum87 satdelta87 = satdistance87 / divisor; + saccum87 valdelta87 = valdistance87 / divisor; + + huedelta87 *= 2; + satdelta87 *= 2; + valdelta87 *= 2; + + accum88 hue88 = startcolor.hue << 8; + accum88 sat88 = startcolor.sat << 8; + accum88 val88 = startcolor.val << 8; + for (size_t i = startpos; i <= endpos; ++i) + { + target[i].hue = hue88 >> 8; + target[i].sat = sat88 >> 8; + target[i].val = val88 >> 8; + hue88 += huedelta87; + sat88 += satdelta87; + val88 += valdelta87; + } +} + +void rgb_fill_gradient_hsv(rgb_t *target, size_t startpos, hsv_t startcolor, size_t endpos, hsv_t endcolor, + color_gradient_direction_t direction) +{ + // if the points are in the wrong order, straighten them + if (endpos < startpos) + { + size_t t = endpos; + hsv_t tc = endcolor; + endcolor = startcolor; + endpos = startpos; + startpos = t; + startcolor = tc; + } + + // If we're fading toward black (val=0) or white (sat=0), + // then set the endhue to the starthue. + // This lets us ramp smoothly to black or white, regardless + // of what 'hue' was set in the endcolor (since it doesn't matter) + if (endcolor.value == 0 || endcolor.saturation == 0) + endcolor.hue = startcolor.hue; + + // Similarly, if we're fading in from black (val=0) or white (sat=0) + // then set the starthue to the endhue. + // This lets us ramp smoothly up from black or white, regardless + // of what 'hue' was set in the startcolor (since it doesn't matter) + if (startcolor.value == 0 || startcolor.saturation == 0) + startcolor.hue = endcolor.hue; + + saccum87 huedistance87; + saccum87 satdistance87; + saccum87 valdistance87; + + satdistance87 = (endcolor.sat - startcolor.sat) << 7; + valdistance87 = (endcolor.val - startcolor.val) << 7; + + uint8_t huedelta8 = endcolor.hue - startcolor.hue; + + if (direction == COLOR_SHORTEST_HUES) + { + direction = COLOR_FORWARD_HUES; + if (huedelta8 > 127) + direction = COLOR_BACKWARD_HUES; + } + + if (direction == COLOR_LONGEST_HUES) + { + direction = COLOR_FORWARD_HUES; + if (huedelta8 < 128) + direction = COLOR_BACKWARD_HUES; + } + + if (direction == COLOR_FORWARD_HUES) + { + huedistance87 = huedelta8 << 7; + } + else /* direction == BACKWARD_HUES */ + { + huedistance87 = (uint8_t) (256 - huedelta8) << 7; + huedistance87 = -huedistance87; + } + + size_t pixeldistance = endpos - startpos; + int16_t divisor = pixeldistance ? pixeldistance : 1; + + saccum87 huedelta87 = huedistance87 / divisor; + saccum87 satdelta87 = satdistance87 / divisor; + saccum87 valdelta87 = valdistance87 / divisor; + + huedelta87 *= 2; + satdelta87 *= 2; + valdelta87 *= 2; + + accum88 hue88 = startcolor.hue << 8; + accum88 sat88 = startcolor.sat << 8; + accum88 val88 = startcolor.val << 8; + for (size_t i = startpos; i <= endpos; ++i) + { + target[i] = hsv2rgb_rainbow(hsv_from_values(hue88 >> 8, sat88 >> 8, val88 >> 8)); + hue88 += huedelta87; + sat88 += satdelta87; + val88 += valdelta87; + } +} + +void rgb_fill_gradient_rgb(rgb_t *leds, size_t startpos, rgb_t startcolor, size_t endpos, rgb_t endcolor) +{ + // if the points are in the wrong order, straighten them + if (endpos < startpos) + { + size_t t = endpos; + rgb_t tc = endcolor; + endcolor = startcolor; + endpos = startpos; + startpos = t; + startcolor = tc; + } + + saccum87 rdistance87; + saccum87 gdistance87; + saccum87 bdistance87; + + rdistance87 = (endcolor.r - startcolor.r) << 7; + gdistance87 = (endcolor.g - startcolor.g) << 7; + bdistance87 = (endcolor.b - startcolor.b) << 7; + + size_t pixeldistance = endpos - startpos; + int16_t divisor = pixeldistance ? pixeldistance : 1; + + saccum87 rdelta87 = rdistance87 / divisor; + saccum87 gdelta87 = gdistance87 / divisor; + saccum87 bdelta87 = bdistance87 / divisor; + + rdelta87 *= 2; + gdelta87 *= 2; + bdelta87 *= 2; + + accum88 r88 = startcolor.r << 8; + accum88 g88 = startcolor.g << 8; + accum88 b88 = startcolor.b << 8; + for (uint16_t i = startpos; i <= endpos; ++i) + { + leds[i].r = r88 >> 8; + leds[i].g = g88 >> 8; + leds[i].b = b88 >> 8; + r88 += rdelta87; + g88 += gdelta87; + b88 += bdelta87; + } +} + +//////////////////////////////////////////////////////////////////////////////// + +hsv_t color_from_palette_hsv(const hsv_t *palette, uint8_t pal_size, uint8_t index, uint8_t brightness, bool blend) +{ + uint8_t div = 256 / pal_size; + + uint8_t hi = index / div; + uint8_t lo = index % div; + + const hsv_t *entry = palette + hi; + + uint8_t hue1 = entry->hue; + uint8_t sat1 = entry->sat; + uint8_t val1 = entry->val; + + if (blend && lo) + { + if (hi == pal_size - 1) + entry = palette; + else + ++entry; + + uint8_t f2 = lo * pal_size; + uint8_t f1 = 255 - f2; + + uint8_t hue2 = entry->hue; + uint8_t sat2 = entry->sat; + uint8_t val2 = entry->val; + + // Now some special casing for blending to or from + // either black or white. Black and white don't have + // proper 'hue' of their own, so when ramping from + // something else to/from black/white, we set the 'hue' + // of the black/white color to be the same as the hue + // of the other color, so that you get the expected + // brightness or saturation ramp, with hue staying + // constant: + + // If we are starting from white (sat=0) + // or black (val=0), adopt the target hue. + if (sat1 == 0 || val1 == 0) + hue1 = hue2; + + // If we are ending at white (sat=0) + // or black (val=0), adopt the starting hue. + if (sat2 == 0 || val2 == 0) + hue2 = hue1; + + sat1 = scale8(sat1, f1); + val1 = scale8(val1, f1); + + sat2 = scale8(sat2, f2); + val2 = scale8(val2, f2); + + // These sums can't overflow, so no qadd8 needed. + sat1 += sat2; + val1 += val2; + + uint8_t delta_hue = (uint8_t) (hue2 - hue1); + if (delta_hue & 0x80) + // go backwards + hue1 -= scale8(256 - delta_hue, f2); + else + // go forwards + hue1 += scale8(delta_hue, f2); + } + + if (brightness != 255) + val1 = scale8_video(val1, brightness); + + return hsv_from_values(hue1, sat1, val1); +} + +#include <stdlib.h> + +rgb_t color_from_palette_rgb(const rgb_t *palette, uint8_t pal_size, uint8_t index, uint8_t brightness, bool blend) +{ + uint8_t div = 256 / pal_size; + + uint8_t hi = index / div; + uint8_t lo = index % div; + + const rgb_t *entry = palette + hi; + + uint8_t red1 = entry->red; + uint8_t green1 = entry->green; + uint8_t blue1 = entry->blue; + + if (blend && lo) + { + if (hi == pal_size - 1) + entry = palette; + else + ++entry; + + uint8_t f2 = lo * pal_size; + uint8_t f1 = 255 - f2; + + uint8_t red2 = entry->red; + red1 = scale8(red1, f1); + red2 = scale8(red2, f2); + red1 += red2; + + uint8_t green2 = entry->green; + green1 = scale8(green1, f1); + green2 = scale8(green2, f2); + green1 += green2; + + uint8_t blue2 = entry->blue; + blue1 = scale8(blue1, f1); + blue2 = scale8(blue2, f2); + blue1 += blue2; + } + + if (brightness != 255) + { + if (brightness) + { + ++brightness; // adjust for rounding + // Now, since brightness is nonzero, we don't need the full scale8_video logic; + // we can just to scale8 and then add one (unless scale8 fixed) to all nonzero inputs. + if (red1) + red1 = scale8(red1, brightness); + if (green1) + green1 = scale8(green1, brightness); + if (blue1) + blue1 = scale8(blue1, brightness); + } + else + { + red1 = 0; + green1 = 0; + blue1 = 0; + } + } + + return rgb_from_values(red1, green1, blue1); +} + +//////////////////////////////////////////////////////////////////////////////// + +hsv_t blend(hsv_t existing, hsv_t overlay, fract8 amount, color_gradient_direction_t direction) +{ + if (amount == 0) + return existing; + + if (amount == 255) + { + existing = overlay; + return existing; + } + + fract8 amount_of_keep = 255 - amount; + uint8_t huedelta8 = overlay.hue - existing.hue; + + if (direction == COLOR_SHORTEST_HUES) + { + direction = COLOR_FORWARD_HUES; + if (huedelta8 > 127) + direction = COLOR_BACKWARD_HUES; + } + + if (direction == COLOR_LONGEST_HUES) + { + direction = COLOR_FORWARD_HUES; + if (huedelta8 < 128) + direction = COLOR_BACKWARD_HUES; + } + + if (direction == COLOR_FORWARD_HUES) + { + existing.hue = existing.hue + scale8(huedelta8, amount); + } + else /* direction == BACKWARD_HUES */ + { + huedelta8 = -huedelta8; + existing.hue = existing.hue - scale8(huedelta8, amount); + } + + existing.sat = scale8(existing.sat, amount_of_keep) + scale8(overlay.sat, amount); + existing.val = scale8(existing.val, amount_of_keep) + scale8(overlay.val, amount); + + return existing; +} + +void blur1d(rgb_t *leds, size_t num_leds, fract8 blur_amount) +{ + uint8_t keep = 255 - blur_amount; + uint8_t seep = blur_amount >> 1; + rgb_t carryover = rgb_from_code(0); + for (size_t i = 0; i < num_leds; ++i) + { + rgb_t cur = leds[i]; + rgb_t part = cur; + part = rgb_scale(part, seep); + cur = rgb_add_rgb(rgb_scale(cur, keep), carryover); + if (i) + leds[i - 1] = rgb_add_rgb(leds[i - 1], part); + leds[i] = cur; + carryover = part; + } +} + +void blur_columns(rgb_t *leds, size_t width, size_t height, fract8 blur_amount, xy_to_offs_cb xy, void *ctx) +{ + // blur columns + uint8_t keep = 255 - blur_amount; + uint8_t seep = blur_amount >> 1; + for (size_t col = 0; col < width; ++col) + { + rgb_t carryover = rgb_from_code(0); + for (size_t i = 0; i < height; ++i) + { + size_t offs = xy(ctx, col, i); + rgb_t cur = leds[offs]; + rgb_t part = cur; + part = rgb_scale(part, seep); + cur = rgb_add_rgb(rgb_scale(cur, keep), carryover); + if (i) + { + size_t prev_offs = xy(ctx, col, i - 1); + leds[prev_offs] = rgb_add_rgb(leds[prev_offs], part); + } + leds[offs] = cur; + carryover = part; + } + } +} + +void blur_rows(rgb_t *leds, size_t width, size_t height, fract8 blur_amount, xy_to_offs_cb xy, void *ctx) +{ + // blur rows same as columns, for irregular matrix + uint8_t keep = 255 - blur_amount; + uint8_t seep = blur_amount >> 1; + for (size_t row = 0; row < height; row++) + { + rgb_t carryover = rgb_from_code(0); + for (size_t i = 0; i < width; i++) + { + size_t offs = xy(ctx, i, row); + rgb_t cur = leds[offs]; + rgb_t part = cur; + part = rgb_scale(part, seep); + cur = rgb_add_rgb(rgb_scale(cur, keep), carryover); + if (i) + { + size_t prev_offs = xy(ctx, i - 1, row); + leds[prev_offs] = rgb_add_rgb(leds[prev_offs], part); + } + leds[offs] = cur; + carryover = part; + } + } +} + +void blur2d(rgb_t *leds, size_t width, size_t height, fract8 blur_amount, xy_to_offs_cb xy, void *ctx) +{ + blur_rows(leds, width, height, blur_amount, xy, ctx); + blur_columns(leds, width, height, blur_amount, xy, ctx); +} + +//////////////////////////////////////////////////////////////////////////////// + +uint8_t apply_gamma2brightness(uint8_t brightness, float gamma) +{ + float orig = (float)brightness / 255.0; + float adj = powf(orig, gamma) * 255.0; + uint8_t result = (uint8_t)adj; + if (brightness > 0 && !result) + result = 1; // never gamma-adjust a positive number down to zero + return result; +} + +rgb_t apply_gamma2rgb(rgb_t c, float gamma) +{ + rgb_t res = { + .r = apply_gamma2brightness(c.r, gamma), + .g = apply_gamma2brightness(c.g, gamma), + .b = apply_gamma2brightness(c.b, gamma), + }; + return res; +} + +rgb_t apply_gamma2rgb_channels(rgb_t c, float gamma_r, float gamma_g, float gamma_b) +{ + rgb_t res = { + .r = apply_gamma2brightness(c.r, gamma_r), + .g = apply_gamma2brightness(c.g, gamma_g), + .b = apply_gamma2brightness(c.b, gamma_b), + }; + return res; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/color/color.h Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,366 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2013 FastLED + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +/** + * @file color.h + * @defgroup color color + * @{ + * + * Functions for RGB and HSV colors + * + * Ported from FastLED + * + * MIT Licensed as described in the file LICENSE + */ +#ifndef __COLOR_H__ +#define __COLOR_H__ + +#include <stdint.h> + +#include "rgb.h" +#include "hsv.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define HUE_MAX_RAINBOW 255 +#define HUE_MAX_SPECTRUM 255 +#define HUE_MAX_RAW 191 + +//////////////////////////////////////////////////////////////////////////////// +// Color conversion + +/** + * @brief Convert HSV to RGB using balanced rainbow + * + * Convert a hue, saturation, and value to RGB using a visually balanced + * rainbow (vs a straight mathematical spectrum). This 'rainbow' yields + * better yellow and orange than a straight 'spectrum'. + * + * @note here hue is 0-255, not just 0-191 + * + * @param hsv HSV color + * @return RGB color + */ +rgb_t hsv2rgb_rainbow(hsv_t hsv); + +/** + * @brief Convert HSV to RGB using mathematically straight spectrum + * + * Convert a hue, saturation, and value to RGB using a mathematically straight + * spectrum (vs a visually balanced rainbow). This 'spectrum' will have more + * green & blue than a 'rainbow', and less yellow and orange. + * + * @note here hue is 0-255, not just 0-191 + * + * @param hsv HSV color + * @return RGB color + */ +rgb_t hsv2rgb_spectrum(hsv_t hsv); + +/** + * @brief Convert HSV to RGB using spectrum + * + * Convert hue, saturation, and value to RGB. This 'spectrum' conversion will + * be more green & blue than a real 'rainbow', and the hue is specified just in + * the range 0-191. + * Together, these result in a slightly faster conversion speed, at the expense + * of color balance. + * + * @note Hue is 0-191 only! Saturation & value are 0-255 each. + * + * @param hsv HSV color + * @return RGB color + */ +rgb_t hsv2rgb_raw(hsv_t hsv); + +/** + * @brief Recover approximate HSV values from RGB + * + * This function is a long-term work in process; expect results to change + * slightly over time as this function is refined and improved. + * + * This function is most accurate when the input is an RGB color that came + * from a fully-saturated HSV color to start with. + * + * This function is not nearly as fast as HSV-to-RGB. It is provided for + * those situations when the need for this function cannot be avoided, or + * when extremely high performance is not needed. + * + * Why is this 'only' an "approximation"? Not all RGB colors have HSV + * equivalents! For example, there is no HSV value that will ever convert + * to RGB(255,255,0) using the code provided in this library. + * So if you try to convert RGB(255,255,0) 'back' to HSV, you'll necessarily + * get only an approximation. + * Emphasis has been placed on getting the 'hue' as close as usefully possible, + * but even that's a bit of a challenge. + * The 8-bit HSV and 8-bit RGB color spaces are not a "bijection". + * Nevertheless, this function does a pretty good job, particularly at + * recovering the 'hue' from fully saturated RGB colors that originally came + * from HSV rainbow colors. + * The more desaturated the original RGB color is, the rougher the approximation, + * and the less accurate the results. + * + * @param rgb RGB color + * @return Approximated HSV color + */ +hsv_t rgb2hsv_approximate(rgb_t rgb); + +/** + * @brief Approximates a 'black body radiation' spectrum for a given 'heat' level. + * + * This is useful for animations of 'fire'. Heat is specified as an arbitrary scale + * from 0 (cool) to 255 (hot). + * This is NOT a chromatically correct 'black body radiation' spectrum, but it's + * surprisingly close, and it's fast and small. + */ +rgb_t rgb_heat_color(uint8_t temperature); + +//////////////////////////////////////////////////////////////////////////////// +// Fill functions + +/** + * Fill an array of HSV colors with a solid HSV color + */ +void hsv_fill_solid_hsv(hsv_t *target, hsv_t color, size_t num); + +/** + * Fill an array of RGB colors with a solid HSV color + */ +void rgb_fill_solid_hsv(rgb_t *target, hsv_t color, size_t num); + +/** + * Fill an array of RGB colors with a solid RGB color + */ +void rgb_fill_solid_rgb(rgb_t *target, rgb_t color, size_t num); + +/** + * @brief Fill an array of HSV colors with a smooth HSV gradient between two + * specified HSV colors. + * + * Since 'hue' is a value around a color wheel, + * there are always two ways to sweep from one hue + * to another. + * This function lets you specify which way you want + * the hue gradient to sweep around the color wheel: + * + * FORWARD_HUES: hue always goes clockwise + * BACKWARD_HUES: hue always goes counter-clockwise + * SHORTEST_HUES: hue goes whichever way is shortest + * LONGEST_HUES: hue goes whichever way is longest + * + * The default is SHORTEST_HUES, as this is nearly + * always what is wanted. + */ +void hsv_fill_gradient_hsv(hsv_t *target, size_t startpos, hsv_t startcolor, size_t endpos, hsv_t endcolor, + color_gradient_direction_t direction); + +static inline void hsv_fill_gradient2_hsv(hsv_t *target, size_t num, hsv_t c1, hsv_t c2, + color_gradient_direction_t direction) +{ + hsv_fill_gradient_hsv(target, 0, c1, num - 1, c2, direction); +} + +static inline void hsv_fill_gradient3_hsv(hsv_t *target, size_t num, hsv_t c1, hsv_t c2, hsv_t c3, + color_gradient_direction_t direction) +{ + size_t half = num / 2; + hsv_fill_gradient_hsv(target, 0, c1, half, c2, direction); + hsv_fill_gradient_hsv(target, half, c2, num - 1, c3, direction); +} + +static inline void hsv_fill_gradient4_hsv(hsv_t *target, size_t num, hsv_t c1, hsv_t c2, hsv_t c3, hsv_t c4, + color_gradient_direction_t direction) +{ + size_t onethird = num / 3; + size_t twothirds = num * 2 / 3; + hsv_fill_gradient_hsv(target, 0, c1, onethird, c2, direction); + hsv_fill_gradient_hsv(target, onethird, c2, twothirds, c3, direction); + hsv_fill_gradient_hsv(target, twothirds, c3, num - 1, c4, direction); +} + +/** + * Same as ::hsv_fill_gradient_hsv(), but for array of RGB + * + * The gradient is computed in HSV space, and then HSV values are + * converted to RGB as they're written into the RGB array. + */ +void rgb_fill_gradient_hsv(rgb_t *target, size_t startpos, hsv_t startcolor, size_t endpos, hsv_t endcolor, + color_gradient_direction_t direction); + +static inline void rgb_fill_gradient2_hsv(rgb_t *target, size_t num, hsv_t c1, hsv_t c2, + color_gradient_direction_t direction) +{ + rgb_fill_gradient_hsv(target, 0, c1, num - 1, c2, direction); +} + +static inline void rgb_fill_gradient3_hsv(rgb_t *target, size_t num, hsv_t c1, hsv_t c2, hsv_t c3, + color_gradient_direction_t direction) +{ + size_t half = num / 2; + rgb_fill_gradient_hsv(target, 0, c1, half, c2, direction); + rgb_fill_gradient_hsv(target, half, c2, num - 1, c3, direction); +} + +static inline void rgb_fill_gradient4_hsv(rgb_t *target, size_t num, hsv_t c1, hsv_t c2, hsv_t c3, hsv_t c4, + color_gradient_direction_t direction) +{ + size_t onethird = num / 3; + size_t twothirds = num * 2 / 3; + rgb_fill_gradient_hsv(target, 0, c1, onethird, c2, direction); + rgb_fill_gradient_hsv(target, onethird, c2, twothirds, c3, direction); + rgb_fill_gradient_hsv(target, twothirds, c3, num - 1, c4, direction); +} + +/** + * @brief Fill a range of LEDs with a smooth RGB gradient + * between two specified RGB colors. + * + * Unlike HSV, there is no 'color wheel' in RGB space, and therefore + * there's only one 'direction' for the gradient to go, and no + * 'direction' is needed. + */ +void rgb_fill_gradient_rgb(rgb_t *leds, size_t startpos, rgb_t startcolor, size_t endpos, rgb_t endcolor); + +static inline void rgb_fill_gradient2_rgb(rgb_t *target, size_t num, rgb_t c1, rgb_t c2) +{ + rgb_fill_gradient_rgb(target, 0, c1, num - 1, c2); +} + +static inline void rgb_fill_gradient3_rgb(rgb_t *target, size_t num, rgb_t c1, rgb_t c2, rgb_t c3) +{ + size_t half = num / 2; + rgb_fill_gradient_rgb(target, 0, c1, half, c2); + rgb_fill_gradient_rgb(target, half, c2, num - 1, c3); +} + +static inline void rgb_fill_gradient4_rgb(rgb_t *target, size_t num, rgb_t c1, rgb_t c2, rgb_t c3, rgb_t c4) +{ + size_t onethird = num / 3; + size_t twothirds = num * 2 / 3; + rgb_fill_gradient_rgb(target, 0, c1, onethird, c2); + rgb_fill_gradient_rgb(target, onethird, c2, twothirds, c3); + rgb_fill_gradient_rgb(target, twothirds, c3, num - 1, c4); +} + +//////////////////////////////////////////////////////////////////////////////// +// Palette functions + +/** + * Return an HSV color with 'index' from 'palette' (array of HSV colors). + * Even though palette has lesser than 256 explicily defined entries, you + * can use an 'index' from 0..255. The 'pal_size' explicit palette entries will + * be spread evenly across the 0..255 range, and the intermedate values + * will be HSV-interpolated between adjacent explicit entries. + */ +hsv_t color_from_palette_hsv(const hsv_t *palette, uint8_t pal_size, uint8_t index, uint8_t brightness, bool blend); + +/** + * Same for RGB palette + */ +rgb_t color_from_palette_rgb(const rgb_t *palette, uint8_t pal_size, uint8_t index, uint8_t brightness, bool blend); + +//////////////////////////////////////////////////////////////////////////////// +// Filter functions + +/** + * Function which must be provided by the application for use in two-dimensional + * filter functions. + */ +typedef size_t (*xy_to_offs_cb)(void *ctx, size_t x, size_t y); + +/** + * @brief One-dimensional blur filter. + * + * Spreads light to 2 line neighbors. + * + * 0 = no spread at all + * 64 = moderate spreading + * 172 = maximum smooth, even spreading + * + * 173..255 = wider spreading, but increasing flicker + * + * Total light is NOT entirely conserved, so many repeated calls to 'blur' + * will also result in the light fading, eventually all the way to black; + * this is by design so that it can be used to (slowly) clear the LEDs + * to black. + */ +void blur1d(rgb_t *leds, size_t num_leds, fract8 blur_amount); + +/** + * @brief Perform a blur1d on each column of a rectangular matrix + */ +void blur_columns(rgb_t *leds, size_t width, size_t height, fract8 blur_amount, xy_to_offs_cb xy, void *ctx); + +/** + * @brief Perform a blur1d on each row of a rectangular matrix + */ +void blur_rows(rgb_t *leds, size_t width, size_t height, fract8 blur_amount, xy_to_offs_cb xy, void *ctx); + +/** + * @brief Two-dimensional blur filter. + * + * Spreads light to 8 XY neighbors. + * + * 0 = no spread at all + * 64 = moderate spreading + * 172 = maximum smooth, even spreading + * + * 173..255 = wider spreading, but increasing flicker + * + * Total light is NOT entirely conserved, so many repeated calls to 'blur' + * will also result in the light fading, eventually all the way to black; + * this is by design so that it can be used to (slowly) clear the LEDs + * to black. + */ +void blur2d(rgb_t *leds, size_t width, size_t height, fract8 blur_amount, xy_to_offs_cb xy, void *ctx); + +//////////////////////////////////////////////////////////////////////////////// +// Gamma functions + +/** + * @brief Single gamma adjustment to a single scalar value. + * + * Bear in mind that RGB leds have only eight bits per channel of color resolution, + * and that very small, subtle shadings may not be visible. + */ +uint8_t apply_gamma2brightness(uint8_t brightness, float gamma); + +/** + * @brief Single gamma adjustment to each channel of a RGB color. + */ +rgb_t apply_gamma2rgb(rgb_t c, float gamma); + +/** + * @brief Different gamma adjustments for each channel of a RGB color. + */ +rgb_t apply_gamma2rgb_channels(rgb_t c, float gamma_r, float gamma_g, float gamma_b); + +#ifdef __cplusplus +} +#endif + +/**@}*/ + +#endif /* __COLOR_H__ */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/color/component.mk Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,2 @@ +COMPONENT_ADD_INCLUDEDIRS = . +COMPONENT_DEPENDS = lib8tion \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/color/hsv.h Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,109 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2013 FastLED + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +/** + * @file hsv.h + * @defgroup hsv hsv + * @{ + * + * Functions for HSV colors + * + * Ported from FastLED + * + * MIT Licensed as described in the file LICENSE + */ +#ifndef __COLOR_HSV_H__ +#define __COLOR_HSV_H__ + +#include <stdint.h> +#include <stdbool.h> +#include <lib8tion.h> + +#ifdef __cplusplus +extern "C" { +#endif + +#define HUE_RED 0 +#define HUE_ORANGE 32 +#define HUE_YELLOW 64 +#define HUE_GREEN 96 +#define HUE_AQUA 128 +#define HUE_BLUE 160 +#define HUE_PURPLE 192 +#define HUE_PINK 224 + +typedef enum +{ + COLOR_FORWARD_HUES = 0, + COLOR_BACKWARD_HUES, + COLOR_SHORTEST_HUES, + COLOR_LONGEST_HUES +} color_gradient_direction_t; + +/// HSV color representation +typedef struct +{ + union { + uint8_t h; + uint8_t hue; + }; + union { + uint8_t s; + uint8_t sat; + uint8_t saturation; + }; + union { + uint8_t v; + uint8_t val; + uint8_t value; + }; +} hsv_t; + +/// This allows testing a HSV for zero-ness +static inline bool hsv_is_zero(hsv_t a) +{ + return !(a.h | a.s | a.v); +} + +/// Create HSV color from values +static inline hsv_t hsv_from_values(uint8_t h, uint8_t s, uint8_t v) +{ + hsv_t res = { + .h = h, + .s = s, + .v = v + }; + return res; +} + +/// Computes a new color blended some fraction of the way between two other +/// colors. +hsv_t blend(hsv_t existing, hsv_t overlay, fract8 amount, color_gradient_direction_t direction); + +#ifdef __cplusplus +} +#endif + +/**@}*/ + +#endif /* __COLOR_HSV_H__ */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/color/rgb.h Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,280 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2013 FastLED + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +/** + * @file rgb.h + * @defgroup rgb rgb + * @{ + * + * Functions for RGB colors + * + * Ported from FastLED + * + * MIT Licensed as described in the file LICENSE + */ +#ifndef __COLOR_RGB_H__ +#define __COLOR_RGB_H__ + +#include <stdint.h> +#include <stdbool.h> +#include <lib8tion.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/// RGB color representation +typedef struct +{ + union { + uint8_t r; + uint8_t red; + }; + union { + uint8_t g; + uint8_t green; + }; + union { + uint8_t b; + uint8_t blue; + }; +} rgb_t; + +/// This allows testing a RGB for zero-ness +static inline bool rgb_is_zero(rgb_t a) +{ + return !(a.r | a.g | a.b); +} + +/// Create rgb_t color from 24-bit color code 0x00RRGGBB +static inline rgb_t rgb_from_code(uint32_t color_code) +{ + rgb_t res = { + .r = (uint8_t)((color_code >> 16) & 0xff), + .g = (uint8_t)((color_code >> 8) & 0xff), + .b = (uint8_t)(color_code & 0xff), + }; + return res; +} + +/// Create rgb_t color from values +static inline rgb_t rgb_from_values(uint8_t r, uint8_t g, uint8_t b) +{ + rgb_t res = { + .r = r, + .g = g, + .b = b, + }; + return res; +} + +/// Convert RGB color to 24-bit color code 0x00RRGGBB +static inline uint32_t rgb_to_code(rgb_t color) +{ + return ((uint32_t)color.r << 16) | ((uint32_t)color.g << 8) | color.b; +} + +/// Add a constant to each channel of RGB color, +/// saturating at 0xFF +static inline rgb_t rgb_add(rgb_t a, uint8_t val) +{ + rgb_t res = { + .r = qadd8(a.r, val), + .g = qadd8(a.g, val), + .b = qadd8(a.b, val), + }; + return res; +} + +/// Subtract a constant from each channel of RGB color, +/// saturating at 0x00 +static inline rgb_t rgb_sub(rgb_t a, uint8_t val) +{ + rgb_t res = { + .r = qsub8(a.r, val), + .g = qsub8(a.g, val), + .b = qsub8(a.b, val), + }; + return res; +} + +/// Multiply each of the channels by a constant, +/// saturating each channel at 0xFF +static inline rgb_t rgb_mul(rgb_t a, uint8_t val) +{ + rgb_t res = { + .r = qmul8(a.r, val), + .g = qmul8(a.g, val), + .b = qmul8(a.b, val), + }; + return res; +} + +/// Add one RGB to another, saturating at 0xFF for each channel +static inline rgb_t rgb_add_rgb(rgb_t a, rgb_t b) +{ + rgb_t res = { + .r = qadd8(a.r, b.r), + .g = qadd8(a.g, b.g), + .b = qadd8(a.b, b.b), + }; + return res; +} + +/// Subtract one RGB from another, saturating at 0x00 for each channel +static inline rgb_t rgb_sub_rgb(rgb_t a, rgb_t b) +{ + rgb_t res = { + .r = qsub8(a.r, b.r), + .g = qsub8(a.g, b.g), + .b = qsub8(a.b, b.b), + }; + return res; +} + +/// Scale down a RGB to N 256ths of it's current brightness, using +/// 'plain math' dimming rules, which means that if the low light levels +/// may dim all the way to 100% black. +static inline rgb_t rgb_scale(rgb_t a, uint8_t scaledown) +{ + rgb_t res = { + .r = scale8(a.r, scaledown), + .g = scale8(a.g, scaledown), + .b = scale8(a.b, scaledown), + }; + return res; +} + +/// Scale down a RGB to N 256ths of it's current brightness, using +/// 'video' dimming rules, which means that unless the scale factor is ZERO +/// each channel is guaranteed NOT to dim down to zero. If it's already +/// nonzero, it'll stay nonzero, even if that means the hue shifts a little +/// at low brightness levels. +static inline rgb_t rgb_scale_video(rgb_t a, uint8_t scaledown) +{ + rgb_t res = { + .r = scale8_video(a.r, scaledown), + .g = scale8_video(a.g, scaledown), + .b = scale8_video(a.b, scaledown), + }; + return res; +} + +/// rgb_fade_light is a synonym for rgb_scale_video(..., 255 - fade_factor) +static inline rgb_t rgb_fade_light(rgb_t a, uint8_t fade_factor) +{ + return rgb_scale_video(a, ~fade_factor); +} + +/// rgb_fade_light is a synonym for rgb_scale(..., 255 - fade_factor) +static inline rgb_t rgb_fade(rgb_t a, uint8_t fade_factor) +{ + return rgb_scale(a, ~fade_factor); +} + +/// Invert each channel of RGB color +static inline rgb_t rgb_invert(rgb_t a) +{ + rgb_t res = { + .r = (uint8_t) ~a.r, + .g = (uint8_t) ~a.g, + .b = (uint8_t) ~a.b, + }; + return res; +} + +/// Get the 'luma' of a RGB color - aka roughly how much light the RGB pixel +/// is putting out (from 0 to 255). +static inline uint8_t rgb_luma(rgb_t a) +{ + return scale8(a.r, 54) + scale8(a.g, 183) + scale8(a.b, 18); +} + +/// Get the average of the R, G, and B values +static inline uint8_t rgb_average_light(rgb_t a) +{ + return scale8(a.r, 85) + scale8(a.g, 85) + scale8(a.b, 85); +} + +/// Maximize the brightness of this RGB color +static inline rgb_t rgb_max_brightness(rgb_t a, uint8_t limit) +{ + uint8_t max = a.r; + if (a.g > max) max = a.g; + if (a.b > max) max = a.b; + + // stop div/0 when color is black + if (!max) return a; + + uint16_t factor = ((uint16_t)(limit) * 256) / max; + rgb_t res = { + .r = (uint8_t)((a.r * factor) / 256), + .g = (uint8_t)((a.g * factor) / 256), + .b = (uint8_t)((a.b * factor) / 256), + }; + return res; +} + +/// Return a new RGB color after performing a linear interpolation between +/// colors a and b +static inline rgb_t rgb_lerp8(rgb_t a, rgb_t b, fract8 frac) +{ + rgb_t res = { + .r = lerp8by8(a.r, b.r, frac), + .g = lerp8by8(a.g, b.g, frac), + .b = lerp8by8(a.b, b.b, frac), + }; + return res; +} + +/// Return a new RGB color after performing a linear interpolation between +/// colors a and b +static inline rgb_t rgb_lerp16(rgb_t a, rgb_t b, fract16 frac) +{ + rgb_t res = { + .r = (uint8_t)lerp16by16(a.r, b.r, frac), + .g = (uint8_t)lerp16by16(a.g, b.g, frac), + .b = (uint8_t)lerp16by16(a.b, b.b, frac), + }; + return res; +} + +/// Computes a new color blended some fraction of the way between two other +/// colors. +static inline rgb_t rgb_blend(rgb_t existing, rgb_t overlay, fract8 amount) +{ + rgb_t res = { + .r = blend8(existing.r, overlay.r, amount), + .g = blend8(existing.g, overlay.g, amount), + .b = blend8(existing.b, overlay.b, amount), + }; + return res; +} + +#ifdef __cplusplus +} +#endif + +/**@}*/ + +#endif /* __COLOR_RGB_H__ */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/dht/.eil.yml Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,28 @@ +--- +components: + - name: dht + description: | + Driver for DHT11, AM2301 (DHT21, DHT22, AM2302, AM2321), Itead Si7021 + group: humidity + groups: + - name: temperature + code_owners: + - name: UncleRus + depends: + - name: log + - name: esp_idf_lib_helpers + - name: freertos + - name: driver + thread_safe: no + targets: + - name: esp32 + - name: esp8266 + - name: esp32s2 + - name: esp32c3 + licenses: + - name: BSD-3 + copyrights: + - name: UncleRus + year: 2018 + - name: jsuiker + year: 2016
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/dht/CMakeLists.txt Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,11 @@ +if(${IDF_TARGET} STREQUAL esp8266) + set(req esp8266 freertos log esp_idf_lib_helpers) +else() + set(req driver freertos log esp_idf_lib_helpers) +endif() + +idf_component_register( + SRCS dht.c + INCLUDE_DIRS . + REQUIRES ${req} +)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/dht/LICENSE Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,27 @@ +Copyright (c) 2016 Jonathan Hartsuiker (https://github.com/jsuiker) +Copyright (c) 2018 Ruslan V. Uss (https://github.com/UncleRus) + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, +this list of conditions and the following disclaimer in the documentation +and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of itscontributors +may be used to endorse or promote products derived from this software without +specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/dht/component.mk Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,7 @@ +COMPONENT_ADD_INCLUDEDIRS = . + +ifdef CONFIG_IDF_TARGET_ESP8266 +COMPONENT_DEPENDS = esp8266 freertos log esp_idf_lib_helpers +else +COMPONENT_DEPENDS = driver freertos log esp_idf_lib_helpers +endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/dht/dht.c Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,259 @@ +/* + * Copyright (c) 2016 Jonathan Hartsuiker <https://github.com/jsuiker> + * Copyright (c) 2018 Ruslan V. Uss <unclerus@gmail.com> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of itscontributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file dht.c + * + * ESP-IDF driver for DHT11, AM2301 (DHT21, DHT22, AM2302, AM2321), Itead Si7021 + * + * Ported from esp-open-rtos + * + * Copyright (c) 2016 Jonathan Hartsuiker <https://github.com/jsuiker>\n + * Copyright (c) 2018 Ruslan V. Uss <unclerus@gmail.com>\n + * + * BSD Licensed as described in the file LICENSE + */ +#include "dht.h" + +#include <freertos/FreeRTOS.h> +#include <string.h> +#include <esp_log.h> +#include <ets_sys.h> +#include <esp_idf_lib_helpers.h> + +// DHT timer precision in microseconds +#define DHT_TIMER_INTERVAL 2 +#define DHT_DATA_BITS 40 +#define DHT_DATA_BYTES (DHT_DATA_BITS / 8) + +/* + * Note: + * A suitable pull-up resistor should be connected to the selected GPIO line + * + * __ ______ _______ ___________________________ + * \ A / \ C / \ DHT duration_data_low / \ + * \_______/ B \______/ D \__________________________/ DHT duration_data_high \__ + * + * + * Initializing communications with the DHT requires four 'phases' as follows: + * + * Phase A - MCU pulls signal low for at least 18000 us + * Phase B - MCU allows signal to float back up and waits 20-40us for DHT to pull it low + * Phase C - DHT pulls signal low for ~80us + * Phase D - DHT lets signal float back up for ~80us + * + * After this, the DHT transmits its first bit by holding the signal low for 50us + * and then letting it float back high for a period of time that depends on the data bit. + * duration_data_high is shorter than 50us for a logic '0' and longer than 50us for logic '1'. + * + * There are a total of 40 data bits transmitted sequentially. These bits are read into a byte array + * of length 5. The first and third bytes are humidity (%) and temperature (C), respectively. Bytes 2 and 4 + * are zero-filled and the fifth is a checksum such that: + * + * byte_5 == (byte_1 + byte_2 + byte_3 + byte_4) & 0xFF + * + */ + +static const char *TAG = "dht"; + +#if HELPER_TARGET_IS_ESP32 +static portMUX_TYPE mux = portMUX_INITIALIZER_UNLOCKED; +#define PORT_ENTER_CRITICAL() portENTER_CRITICAL(&mux) +#define PORT_EXIT_CRITICAL() portEXIT_CRITICAL(&mux) + +#elif HELPER_TARGET_IS_ESP8266 +#define PORT_ENTER_CRITICAL() portENTER_CRITICAL() +#define PORT_EXIT_CRITICAL() portEXIT_CRITICAL() +#endif + +#define CHECK_ARG(VAL) do { if (!(VAL)) return ESP_ERR_INVALID_ARG; } while (0) + +#define CHECK_LOGE(x, msg, ...) do { \ + esp_err_t __; \ + if ((__ = x) != ESP_OK) { \ + PORT_EXIT_CRITICAL(); \ + ESP_LOGE(TAG, msg, ## __VA_ARGS__); \ + return __; \ + } \ + } while (0) + + +/** + * Wait specified time for pin to go to a specified state. + * If timeout is reached and pin doesn't go to a requested state + * false is returned. + * The elapsed time is returned in pointer 'duration' if it is not NULL. + */ +static esp_err_t dht_await_pin_state(gpio_num_t pin, uint32_t timeout, + int expected_pin_state, uint32_t *duration) +{ + /* XXX dht_await_pin_state() should save pin direction and restore + * the direction before return. however, the SDK does not provide + * gpio_get_direction(). + */ + gpio_set_direction(pin, GPIO_MODE_INPUT); + for (uint32_t i = 0; i < timeout; i += DHT_TIMER_INTERVAL) + { + // need to wait at least a single interval to prevent reading a jitter + ets_delay_us(DHT_TIMER_INTERVAL); + if (gpio_get_level(pin) == expected_pin_state) + { + if (duration) + *duration = i; + return ESP_OK; + } + } + + return ESP_ERR_TIMEOUT; +} + +/** + * Request data from DHT and read raw bit stream. + * The function call should be protected from task switching. + * Return false if error occurred. + */ +static inline esp_err_t dht_fetch_data(dht_sensor_type_t sensor_type, gpio_num_t pin, uint8_t data[DHT_DATA_BYTES]) +{ + uint32_t low_duration; + uint32_t high_duration; + + // Phase 'A' pulling signal low to initiate read sequence + gpio_set_direction(pin, GPIO_MODE_OUTPUT_OD); + gpio_set_level(pin, 0); + ets_delay_us(sensor_type == DHT_TYPE_SI7021 ? 500 : 20000); + gpio_set_level(pin, 1); + + // Step through Phase 'B', 40us + CHECK_LOGE(dht_await_pin_state(pin, 40, 0, NULL), + "Initialization error, problem in phase 'B'"); + // Step through Phase 'C', 88us + CHECK_LOGE(dht_await_pin_state(pin, 88, 1, NULL), + "Initialization error, problem in phase 'C'"); + // Step through Phase 'D', 88us + CHECK_LOGE(dht_await_pin_state(pin, 88, 0, NULL), + "Initialization error, problem in phase 'D'"); + + // Read in each of the 40 bits of data... + for (int i = 0; i < DHT_DATA_BITS; i++) + { + CHECK_LOGE(dht_await_pin_state(pin, 65, 1, &low_duration), + "LOW bit timeout"); + CHECK_LOGE(dht_await_pin_state(pin, 75, 0, &high_duration), + "HIGH bit timeout"); + + uint8_t b = i / 8; + uint8_t m = i % 8; + if (!m) + data[b] = 0; + + data[b] |= (high_duration > low_duration) << (7 - m); + } + + return ESP_OK; +} + +/** + * Pack two data bytes into single value and take into account sign bit. + */ +static inline int16_t dht_convert_data(dht_sensor_type_t sensor_type, uint8_t msb, uint8_t lsb) +{ + int16_t data; + + if (sensor_type == DHT_TYPE_DHT11) + { + data = msb * 10; + } + else + { + data = msb & 0x7F; + data <<= 8; + data |= lsb; + if (msb & BIT(7)) + data = -data; // convert it to negative + } + + return data; +} + +esp_err_t dht_read_data(dht_sensor_type_t sensor_type, gpio_num_t pin, + int16_t *humidity, int16_t *temperature) +{ + CHECK_ARG(humidity || temperature); + + uint8_t data[DHT_DATA_BYTES] = { 0 }; + + gpio_set_direction(pin, GPIO_MODE_OUTPUT_OD); + gpio_set_level(pin, 1); + + PORT_ENTER_CRITICAL(); + esp_err_t result = dht_fetch_data(sensor_type, pin, data); + if (result == ESP_OK) + PORT_EXIT_CRITICAL(); + + /* restore GPIO direction because, after calling dht_fetch_data(), the + * GPIO direction mode changes */ + gpio_set_direction(pin, GPIO_MODE_OUTPUT_OD); + gpio_set_level(pin, 1); + + if (result != ESP_OK) + return result; + + if (data[4] != ((data[0] + data[1] + data[2] + data[3]) & 0xFF)) + { + ESP_LOGE(TAG, "Checksum failed, invalid data received from sensor"); + return ESP_ERR_INVALID_CRC; + } + + if (humidity) + *humidity = dht_convert_data(sensor_type, data[0], data[1]); + if (temperature) + *temperature = dht_convert_data(sensor_type, data[2], data[3]); + + ESP_LOGD(TAG, "Sensor data: humidity=%d, temp=%d", *humidity, *temperature); + + return ESP_OK; +} + +esp_err_t dht_read_float_data(dht_sensor_type_t sensor_type, gpio_num_t pin, + float *humidity, float *temperature) +{ + CHECK_ARG(humidity || temperature); + + int16_t i_humidity, i_temp; + + esp_err_t res = dht_read_data(sensor_type, pin, humidity ? &i_humidity : NULL, temperature ? &i_temp : NULL); + if (res != ESP_OK) + return res; + + if (humidity) + *humidity = i_humidity / 10.0; + if (temperature) + *temperature = i_temp / 10.0; + + return ESP_OK; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/dht/dht.h Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2016 Jonathan Hartsuiker <https://github.com/jsuiker> + * Copyright (c) 2018 Ruslan V. Uss <unclerus@gmail.com> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of itscontributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file dht.h + * @defgroup dht dht + * @{ + * + * ESP-IDF driver for DHT11, AM2301 (DHT21, DHT22, AM2302, AM2321), Itead Si7021 + * + * Ported from esp-open-rtos + * + * Copyright (c) 2016 Jonathan Hartsuiker <https://github.com/jsuiker>\n + * Copyright (c) 2018 Ruslan V. Uss <unclerus@gmail.com>\n + * + * BSD Licensed as described in the file LICENSE + * + * @note A suitable pull-up resistor should be connected to the selected GPIO line + * + */ +#ifndef __DHT_H__ +#define __DHT_H__ + +#include <driver/gpio.h> +#include <esp_err.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Sensor type + */ +typedef enum +{ + DHT_TYPE_DHT11 = 0, //!< DHT11 + DHT_TYPE_AM2301, //!< AM2301 (DHT21, DHT22, AM2302, AM2321) + DHT_TYPE_SI7021 //!< Itead Si7021 +} dht_sensor_type_t; + +/** + * @brief Read integer data from sensor on specified pin + * + * Humidity and temperature are returned as integers. + * For example: humidity=625 is 62.5 %, temperature=244 is 24.4 degrees Celsius + * + * @param sensor_type DHT11 or DHT22 + * @param pin GPIO pin connected to sensor OUT + * @param[out] humidity Humidity, percents * 10, nullable + * @param[out] temperature Temperature, degrees Celsius * 10, nullable + * @return `ESP_OK` on success + */ +esp_err_t dht_read_data(dht_sensor_type_t sensor_type, gpio_num_t pin, + int16_t *humidity, int16_t *temperature); + +/** + * @brief Read float data from sensor on specified pin + * + * Humidity and temperature are returned as floats. + * + * @param sensor_type DHT11 or DHT22 + * @param pin GPIO pin connected to sensor OUT + * @param[out] humidity Humidity, percents, nullable + * @param[out] temperature Temperature, degrees Celsius, nullable + * @return `ESP_OK` on success + */ +esp_err_t dht_read_float_data(dht_sensor_type_t sensor_type, gpio_num_t pin, + float *humidity, float *temperature); + +#ifdef __cplusplus +} +#endif + +/**@}*/ + +#endif // __DHT_H__
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/dps310/.eil.yml Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,26 @@ +--- +components: + - name: dps310 + description: | + Driver for DPS310 barometric pressure sensor + group: + name: pressure + groups: + - name: temperature + code_owners: + - name: trombik + depends: + - name: i2cdev + - name: log + - name: esp_idf_lib_helpers + thread_safe: yes + targets: + - name: esp32 + - name: esp8266 + - name: esp32s2 + - name: esp32c3 + licenses: + - name: ISC + copyrights: + - name: trombik + year: 2022
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/dps310/CMakeLists.txt Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,22 @@ +idf_component_register( + + # sources to compile + SRCS src/dps310.c src/helper_i2c.c + + # public headers + INCLUDE_DIRS include + + # private headers + PRIV_INCLUDE_DIRS priv_include + + # Component Requirements + # https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/build-system.html#component-requirements + + # components whose header files are #included from the public header files + # of this component. + REQUIRES log i2cdev + + # components whose header files are #included from any source files in + # this component, unless already listed in REQUIRES + PRIV_REQUIRES esp_idf_lib_helpers i2cdev +)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/dps310/Kconfig Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,10 @@ +menu "DPS310 driver" + choice DPS310_PROTOCOL + prompt "Choose protocol for digital interface" + default DPS310_PROTOCOL_USING_I2C + help + The protocol to use for digital interface + config DPS310_PROTOCOL_USING_I2C + bool "I2C" + endchoice +endmenu
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/dps310/LICENSE Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,13 @@ +Copyright (c) 2022 Tomoyuki Sakurai <y@trombik.org> + +Permission to use, copy, modify, and distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/dps310/component.mk Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,4 @@ +COMPONENT_DEPENDS = i2cdev log esp_idf_lib_helpers +COMPONENT_ADD_INCLUDEDIRS = include +COMPONENT_PRIV_INCLUDEDIRS = priv_include +COMPONENT_SRCDIRS := src
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/dps310/include/dps310.h Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,946 @@ +/* + * Copyright (c) 2022 Tomoyuki Sakurai <y@trombik.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Sponsored by @beriberikix <https://github.com/beriberikix> + */ + +/** + * @file dps310.h + * @defgroup dps310 dps310 + * @{ + * + * ESP-IDF driver for DPS310 barometric pressure sensor. Sponsored by @beriberikix. + * + * DPS310 supports I2C and SPI (3-wires and 4-wires) as digital interface. The + * driver currently supports: + * + * * I2C + * + * The driver currently does not support: + * + * * SPI + * * read measurements by interrupt + * * multi-master I2C configuration + * + * Note that the unit of pressure in this driver is pascal (Pa), not + * hectopascals (hPa). + * + * Note that the unit of altitude in this driver is meter. + * + */ +#if !defined(__DPS310_H__) +#define __DPS310_H__ + +/* standard headers */ +#include <stdint.h> +#include <stdbool.h> +#include <stdlib.h> + +/* esp-idf headers */ +#include <esp_err.h> + +/* esp-idf-lib headers */ +#include <i2cdev.h> + +#ifdef __cplusplus +extern "C" { +#endif + +#define DPS310_I2C_ADDRESS_0 0x76 //!< I2C address when SDO pin is low +#define DPS310_I2C_ADDRESS_1 0x77 //!< I2C address when SDO pin is high + +#define DPS310_PRODUCT_ID 0x01 //!< Product ID +#define DPS310_REVISION_ID 0x00 //!< Revision ID + +#define DPS310_AVERAGE_SEA_LEVEL_PRESSURE_hPa (1013.25) //!< Average sea-level pressure in hPa +#define DPS310_AVERAGE_SEA_LEVEL_PRESSURE_Pa (DPS310_AVERAGE_SEA_LEVEL_PRESSURE_hPa * 10) //!< Average sea-level pressure in Pa + +/** + * Mode of DPS310 module operation. See 4.1 Operating Modes. + */ +typedef enum { + DPS310_MODE_STANDBY = 0b000, //!< Standby mode + DPS310_MODE_COMMAND_PRESSURE = 0b001, //!< Command mode, pressure measurement + DPS310_MODE_COMMAND_TEMPERATURE = 0b010, //!< Command mode, temperature measurement + DPS310_MODE_BACKGROUND_PRESSURE = 0b101, //!< Background mode, continuous pressure measurement + DPS310_MODE_BACKGROUND_TEMPERATURE = 0b110, //!< Background mode, continuous temperature measurement + DPS310_MODE_BACKGROUND_ALL = 0b111, //!< Background mode, continuous pressure and temperature measurement +} dps310_mode_t; + +/** + * Pressure measurement rate. + */ +typedef enum { + DPS310_PM_RATE_1 = 0b000, //!< 1 measurements / sec + DPS310_PM_RATE_2 = 0b001, //!< 2 measurements / sec + DPS310_PM_RATE_4 = 0b010, //!< 4 measurements / sec + DPS310_PM_RATE_8 = 0b011, //!< 8 measurements / sec + DPS310_PM_RATE_16 = 0b100, //!< 16 measurements / sec + DPS310_PM_RATE_32 = 0b101, //!< 32 measurements / sec + DPS310_PM_RATE_64 = 0b110, //!< 64 measurements / sec + DPS310_PM_RATE_128 = 0b111, //!< 128 measurements / sec +} dps310_pm_rate_t; + +/** + * Pressure resolution, or oversampling rate. + */ +typedef enum { + DPS310_PM_PRC_1 = 0b000, //!< Single (Low Precision) + DPS310_PM_PRC_2 = 0b001, //!< 2 times (Low Power). + DPS310_PM_PRC_4 = 0b010, //!< 4 times + DPS310_PM_PRC_8 = 0b011, //!< 8 times + DPS310_PM_PRC_16 = 0b100, //!< 16 times (Standard) + DPS310_PM_PRC_32 = 0b101, //!< 32 times + DPS310_PM_PRC_64 = 0b110, //!< 64 times (High Precision) + DPS310_PM_PRC_128 = 0b111, //!< 128 times +} dps310_pm_oversampling_t; + +/** + * Temperature measurement source. Used for temperature measurement and + * temperature coefficients. + */ + +typedef enum { + DPS310_TMP_SRC_INTERNAL = 0, //!< Internal sensor (in ASIC) + DPS310_TMP_SRC_EXTERNAL = 1, //!< External sensor (in pressure sensor MEMS element) +} dps310_tmp_src_ext_t; + +/** + * Temperature measurement rate. + */ +typedef enum { + DPS310_TMP_RATE_1 = 0b000, //!< 1 measurements / sec + DPS310_TMP_RATE_2 = 0b001, //!< 2 measurements / sec + DPS310_TMP_RATE_4 = 0b010, //!< 4 measurements / sec + DPS310_TMP_RATE_8 = 0b011, //!< 8 measurements / sec + DPS310_TMP_RATE_16 = 0b100, //!< 16 measurements / sec + DPS310_TMP_RATE_32 = 0b101, //!< 32 measurements / sec + DPS310_TMP_RATE_64 = 0b110, //!< 64 measurements / sec + DPS310_TMP_RATE_128 = 0b111, //!< 128 measurements / sec +} dps310_tmp_rate_t; + +/** + * Pressure resolution, or oversampling rate. + */ +typedef enum { + DPS310_TMP_PRC_1 = 0b000, //!< Single (Low Precision) + DPS310_TMP_PRC_2 = 0b001, //!< 2 times (Low Power). + DPS310_TMP_PRC_4 = 0b010, //!< 4 times + DPS310_TMP_PRC_8 = 0b011, //!< 8 times + DPS310_TMP_PRC_16 = 0b100, //!< 16 times (Standard) + DPS310_TMP_PRC_32 = 0b101, //!< 32 times + DPS310_TMP_PRC_64 = 0b110, //!< 64 times (High Precision) + DPS310_TMP_PRC_128 = 0b111, //!< 128 times +} dps310_tmp_oversampling_t; + +/** + * Interupt (on SDO pin) active level. + */ +typedef enum { + DPS310_INT_HL_ACTIVE_LOW = 0, //!< Active low + DPS310_INT_HL_ACTIVE_HIGH = 1, //!< Active high +} dps310_int_hl_active_level_t; + +/** + * Mode of interupt when the FIFO is full. + */ +typedef enum { + DPS310_INT_FIFO_DISABLE = 0, //!< Disable interrupt when the FIFO is full + DPS310_INT_FIFO_ENABLE = 1, //!< Enable interrupt when the FIFO is full +} dps310_int_fifo_mode_t; + +/** + * Mode of interupt when a temperature measurement is ready + */ +typedef enum { + DPS310_INT_TMP_DISABLE = 0, //!< Disable interrupt when a temperature measurement is ready + DPS310_INT_TMP_ENABLE = 1, //!< Enable interrupt when a temperature measurement is ready +} dps310_int_tmp_mode_t; + +/** + * Mode of interupt when a pressure measurement is ready + */ +typedef enum { + DPS310_INT_PRS_DISABLE = 0, //!< Disable interrupt when a pressure measurement is ready + DPS310_INT_PRS_ENABLE = 1, //!< Enable interrupt when a pressure measurement is ready +} dps310_int_prs_mode_t; + +/** + * Mode of temperature result bit-shift. + */ +typedef enum { + DPS310_T_SHIFT_DISABLE = 0, //!< No shift. + DPS310_T_SHIFT_ENABLE = 1, //!< Shift result right in data register. + // Must be set to '1' when the oversampling + // rate is >8 times. +} dps310_t_shift_mode_t; + +/** + * Mode of pressure result bit-shift. + */ +typedef enum { + DPS310_P_SHIFT_DISABLE = 0, //!< No shift. + DPS310_P_SHIFT_ENABLE = 1, //!< Shift result right in data register. + // Must be set to '1' when the oversampling + // rate is >8 times. +} dps310_p_shift_mode_t; + +/** + * Mode of FIFO. + */ +typedef enum { + DPS310_FIFO_DISABLE = 0, //!< Disable FIFO. + DPS310_FIFO_ENABLE = 1, //!< Enable FIFO. +} dps310_fifo_en_mode_t; + +/** + * SPI mode. + */ +typedef enum { + DPS310_SPI_MODE_4WIRE = 0, //!< SPI 4-wires + DPS310_SPI_MODE_3WIRE = 1, //!< SPI 3-wires +} dps310_spi_mode_t; + +/** + * Type of measurement result in FIFO. + * + * When the type is DPS310_MEASUREMENT_EMPTY, the result is always zero. + * Otherwise, the result is the compensated value of each type. + */ +typedef enum { + DPS310_MEASUREMENT_TEMPERATURE = 0, //!< Temperature + DPS310_MEASUREMENT_PRESSURE, //!< Pressure + DPS310_MEASUREMENT_EMPTY, //!< Empty, no measurement available +} dps310_fifo_measurement_type_t; + +typedef struct { + dps310_fifo_measurement_type_t type; + float result; +} dps310_fifo_measurement_t; + +/** + * Configuration parameters for DPS310. + */ +typedef struct { + dps310_pm_rate_t pm_rate; + dps310_pm_oversampling_t pm_oversampling; + dps310_tmp_rate_t tmp_rate; + dps310_tmp_oversampling_t tmp_oversampling; + dps310_tmp_src_ext_t tmp_src; + dps310_tmp_src_ext_t tmp_coef; + dps310_int_fifo_mode_t int_fifo_mode; + dps310_int_tmp_mode_t int_tmp_mode; + dps310_int_prs_mode_t int_prs_mode; + dps310_t_shift_mode_t t_shift_mode; + dps310_p_shift_mode_t p_shift_mode; + dps310_fifo_en_mode_t fifo_en_mode; + dps310_spi_mode_t spi_mode; + +} dps310_config_t; + +/** + * A macro to set default dps310_config_t. + */ + +#define DPS310_CONFIG_DEFAULT() { \ + .pm_rate = DPS310_PM_RATE_1, \ + .pm_oversampling = DPS310_PM_PRC_16, \ + .tmp_rate = DPS310_TMP_RATE_1, \ + .tmp_oversampling = DPS310_TMP_PRC_16, \ + .tmp_src = DPS310_TMP_SRC_EXTERNAL, \ + .tmp_coef = DPS310_TMP_SRC_EXTERNAL, \ + .int_fifo_mode = DPS310_INT_FIFO_DISABLE, \ + .int_tmp_mode = DPS310_INT_TMP_DISABLE, \ + .int_prs_mode = DPS310_INT_PRS_DISABLE, \ + .t_shift_mode = DPS310_T_SHIFT_ENABLE, \ + .p_shift_mode = DPS310_P_SHIFT_ENABLE, \ + .fifo_en_mode = DPS310_FIFO_DISABLE, \ + .spi_mode = DPS310_SPI_MODE_4WIRE, \ +} + +/** + * Calibration Coefficients (COEF). + */ +typedef struct { + int32_t c0; + int32_t c1; + int32_t c00; + int32_t c10; + int32_t c01; + int32_t c11; + int32_t c20; + int32_t c21; + int32_t c30; +} dps310_coef_t; + +/** + * Device descriptor. + */ +typedef struct { + i2c_dev_t i2c_dev; //!< I2C device descriptor + uint8_t prod_id; //!< Product ID + uint8_t prod_rev; //!< Product revision + uint8_t t_rate; //!< latest P_rate + uint8_t p_rate; //!< latest T_rate + int32_t t_raw; //!< latest T_raw + dps310_coef_t coef; //!< coefficients + float offset; //!< offset in meter + float pressure_s; //!< calculated pressure at sea-level +} dps310_t; + +/** + * DPS310 registers + */ + +/* 7 Register Map */ +#define DPS310_REG_PRS_B2 0x00 +#define DPS310_REG_PRS_B1 0x01 +#define DPS310_REG_PRS_B0 0x02 +#define DPS310_REG_TMP_B2 0x03 +#define DPS310_REG_TMP_B1 0x04 +#define DPS310_REG_TMP_B0 0x05 +#define DPS310_REG_PRS_CFG 0x06 +#define DPS310_REG_TMP_CFG 0x07 +#define DPS310_REG_MEAS_CFG 0x08 +#define DPS310_REG_CFG_REG 0x09 +#define DPS310_REG_INT_STS 0x0a +#define DPS310_REG_FIFO_STS 0x0b +#define DPS310_REG_RESET 0x0c +#define DPS310_REG_ID 0x0d +#define DPS310_REG_COEF 0x10 +#define DPS310_REG_COEF_LEN (18) +#define DPS310_REG_COEF_SRCE 0x28 + +/* various masks */ +#define DPS310_REG_ID_REV_MASK (0x0f) +#define DPS310_REG_ID_PROD_MASK (0xf0) +#define DPS310_REG_RESET_FIFO_FLUSH_MASK (1 << 7) +#define DPS310_REG_RESET_SOFT_RST_MASK (0x0f) +#define DPS310_REG_PRS_CFG_PM_RATE_MASK (0b111 << 4) +#define DPS310_REG_PRS_CFG_TMP_RATE_MASK (0b111 << 4) +#define DPS310_REG_PRS_CFG_PM_PRC_MASK (0b1111) +#define DPS310_REG_PRS_CFG_TMP_EXT_MASK (1 << 7) +#define DPS310_REG_TMP_CFG_TMP_PRC_MASK (0b1111) +#define DPS310_REG_CFG_REG_INT_HL_MASK (1 << 7) +#define DPS310_REG_CFG_REG_INT_FIFO_MASK (1 << 6) +#define DPS310_REG_CFG_REG_INT_TMP_MASK (1 << 5) +#define DPS310_REG_CFG_REG_INT_PRS_MASK (1 << 4) +#define DPS310_REG_CFG_REG_T_SHIFT_MASK (1 << 3) +#define DPS310_REG_CFG_REG_P_SHIFT_MASK (1 << 2) +#define DPS310_REG_CFG_REG_FIFO_EN_MASK (1 << 1) +#define DPS310_REG_CFG_REG_SPI_MODE_MASK (1 << 0) +#define DPS310_REG_MEAS_CFG_COEF_RDY_MASK (1 << 7) +#define DPS310_REG_MEAS_CFG_SENSOR_RDY_MASK (1 << 6) +#define DPS310_REG_MEAS_CFG_TMP_RDY_MASK (1 << 5) +#define DPS310_REG_MEAS_CFG_PRS_RDY_MASK (1 << 4) +#define DPS310_REG_MEAS_CFG_MEAS_CTRL_MASK (0b111) +#define DPS310_REG_COEF_SRCE_MASK (1 << 7) +#define DPS310_REG_FIFO_STS_FIFO_EMPTY_MASK (1) +#define DPS310_REG_FIFO_STS_FIFO_FULL_MASK (1 << 1) + +/* See 3.6 Timing Characteristics */ +#define DPS310_I2C_FREQ_MAX_HZ (3400000) // Max 3.4 MHz + // +/* I2C master driver does not support higher than 1MHz + * https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/i2c.html#_CPPv4N12i2c_config_t9clk_speedE + */ +#define DPS310_I2C_FREQ_MAX_ESP_IDF_HZ (1000000) +#define DPS310_SPI_FREQ_MAX_HZ (10000000) // Max 10 MHz + // +/* See 4.3 Start-up sequence + * + * XXX the datasheet is ambiguous in the start-up sequence. Trim_complete is + * mentioned in nowhere. the DPS310-Pressure-Sensor by Infineon uses 50 ms. + * don't know what the "40ms" in the chart means. to be safe, use the sum of + * all the numbers in the chart. + */ +#define DPS310_TRIM_READY_DELAY_MS (3) // 2.5 ms +#define DPS310_SENSOR_READY_DELAY_MS (12) +#define DPS310_COEFFICIENTS_READY_DELAY_MS (40) +#define DPS310_STARTUP_DELAY_MS (DPS310_TRIM_READY_DELAY_MS + DPS310_SENSOR_READY_DELAY_MS + DPS310_COEFFICIENTS_READY_DELAY_MS) + +#define DPS310_PROD_ID 0x01 + +/* temperature and pressure use three resisters for 24 bits values. */ +#define DPS310_REG_SENSOR_VALUE_LEN (3) + +/* 4.8 FIFO Operation */ +#define DPS310_REG_FIFO DPS310_REG_PRS_B2 //! Resister address of FIFO. +#define DPS310_FIFO_EMPTY (0xff800000) //! the value of two's complement in the resisters when no measurement is in the FIFO. + +/* See 8.9 Soft Reset and FIFO flush (RESET) */ +#define DPS310_FIFO_FLUSH_VALUE (1 << 7) +#define DPS310_SOFT_RST_VALUE (0b1001) + +/** + * @brief Initialize device descriptor + * + * @param[out] dev The device descriptor. + * @param[in] addr DPS310's I2C address + * @param[in] port I2C port number to use. + * See available I2C port at: + * https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/i2c.html#_CPPv410i2c_port_t + * @param[in] sda_gpio GPIO pin for SDA + * @param[in] scl_gpio GPIO pin for SCL + * @return `ESP_OK` on success + */ +esp_err_t dps310_init_desc(dps310_t *dev, uint8_t addr, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio); + +/** + * @brief Free the device descriptor. + * + * The device descriptor must NOT be NULL. `dps310_free_desc()` does not + * `free()` the device descriptor on error. + * + * @param[out] dev Device descriptor + * @return `ESP_OK` on success + */ +esp_err_t dps310_free_desc(dps310_t *dev); + +/** + * @brief Initialize DPS310 module + * + * The function does the followings: + * + * - read the DPS310_REG_ID, and identify the product ID. Return ESP_FAIL if + * the product ID does not match expected product ID. + * - reset the chip + * - perform a quirk + * + * @param[in] dev Device descriptor + * @param[in] config Configuration + * @return `ESP_OK` on success + */ +esp_err_t dps310_init(dps310_t *dev, dps310_config_t *config); + +/** + * @brief Reset the device. + * + * Perform "soft reset" and ensure the chip is fully functional by delaying + * `DPS310_STARTUP_DELAY_MS`. + * + * @param[in] dev The device descriptor + * @return `ESP_OK` on success, `ESP_ERR_INVALID_ARG` when `dev` and/or `config` is NULL, or other errors when I2C communication fails. + */ +esp_err_t dps310_reset(dps310_t *dev); + +/** + * @brief Get pressure measurement rate. + * + * @param[in] dev The device descriptor + * @param[out] value the value in the resister + * @return `ESP_OK` on success. `ESP_ERR_INVALID_ARG` when `dev` and/or + * `value` is NULL, or other errors when I2C communication fails. + */ +esp_err_t dps310_get_rate_p(dps310_t *dev, dps310_pm_rate_t *value); + +/** + * @brief Set pressure measurement rate. + * + * @param[in] dev The device descriptor. + * @param[in] value The value to set. + * @return `ESP_OK` on success, `ESP_ERR_INVALID_ARG` when `dev` and/or `config` is NULL, or other errors when I2C communication fails. + */ +esp_err_t dps310_set_rate_p(dps310_t *dev, dps310_pm_rate_t value); + +/** + * @brief Get temperature measurement rate. + * + * @param[in] dev The device descriptor + * @param[out] value the value in the resister + * @return `ESP_OK` on success. `ESP_ERR_INVALID_ARG` when `dev` and/or + * `value` is NULL, or other errors when I2C communication fails. + */ +esp_err_t dps310_get_rate_t(dps310_t *dev, dps310_tmp_rate_t *value); + +/** + * @brief Set temperature measurement rate. + * + * @param[in] dev The device descriptor. + * @param[in] value The value to set. + * @return `ESP_OK` on success, `ESP_ERR_INVALID_ARG` when `dev` and/or `config` is NULL, or other errors when I2C communication fails. + */ +esp_err_t dps310_set_rate_t(dps310_t *dev, dps310_tmp_rate_t value); + +/** + * @brief Get pressure oversampling rate. + * + * @param[in] dev The device descriptor + * @param[out] value the value in the resister + * @return `ESP_OK` on success. `ESP_ERR_INVALID_ARG` when `dev` and/or + * `value` is NULL, or other errors when I2C communication fails. + */ +esp_err_t dps310_get_oversampling_p(dps310_t *dev, dps310_pm_oversampling_t *value); + +/** + * @brief Set pressure oversampling rate. + * + * @param[in] dev The device descriptor. + * @param[in] value The value to set. + * @return `ESP_OK` on success, `ESP_ERR_INVALID_ARG` when `dev` and/or `config` is NULL, or other errors when I2C communication fails. + */ +esp_err_t dps310_set_oversampling_p(dps310_t *dev, dps310_pm_oversampling_t value); + +/** + * @brief Get temperature oversampling rate. + * + * @param[in] dev The device descriptor + * @param[out] value the value in the resister + * @return `ESP_OK` on success. `ESP_ERR_INVALID_ARG` when `dev` and/or + * `value` is NULL, or other errors when I2C communication fails. + */ +esp_err_t dps310_get_oversampling_t(dps310_t *dev, dps310_tmp_oversampling_t *value); + +/** + * @brief Set temperature oversampling rate. + * + * @param[in] dev The device descriptor. + * @param[in] value The value to set. + * @return `ESP_OK` on success, `ESP_ERR_INVALID_ARG` when `dev` and/or `config` is NULL, or other errors when I2C communication fails. + */ +esp_err_t dps310_set_oversampling_t(dps310_t *dev, dps310_tmp_oversampling_t value); + +/** + * @brief Get temperature measurement source. + * + * @param[in] dev The device descriptor. + * @param[out] value the value in the resister. + * @return `ESP_OK` on success. `ESP_ERR_INVALID_ARG` when `dev` and/or `value` is NULL, or other errors when I2C communication fails. + */ +esp_err_t dps310_get_tmp_ext(dps310_t *dev, dps310_tmp_src_ext_t *value); + +/** + * @brief Set temperature measurement source. + * + * @param[in] dev The device descriptor. + * @param[in] value The value to set. + * @return `ESP_OK` on success, `ESP_ERR_INVALID_ARG` when `dev` is NULL, or other errors when I2C communication fails. + */ +esp_err_t dps310_set_tmp_ext(dps310_t *dev, dps310_tmp_src_ext_t value); + +/** + * @brief Set temperature coefficient source. + * + * @param[in] dev The device descriptor. + * @param[in] value The value to set. + * @return `ESP_OK` on success, `ESP_ERR_INVALID_ARG` when `dev` is NULL, or other errors when I2C communication fails. + */ +esp_err_t dps310_set_tmp_coef_ext(dps310_t *dev, dps310_tmp_src_ext_t value); + +/** + * @brief Get interrupt active level. + * + * @param[in] dev The device descriptor. + * @param[out] value the value in the resister. + * @return `ESP_OK` on success, `ESP_ERR_INVALID_ARG` when `dev` and/or `value` is NULL, or other errors when I2C communication fails. + */ +esp_err_t dps310_get_int_hl(dps310_t *dev, dps310_int_hl_active_level_t *value); + +/** + * @brief Set interrupt active level. + * + * @param[in] dev The device descriptor. + * @param[in] value The value to set. + * @return `ESP_OK` on success, `ESP_ERR_INVALID_ARG` when `dev` is NULL, or other errors when I2C communication fails. + */ +esp_err_t dps310_set_int_hl(dps310_t *dev, dps310_int_hl_active_level_t value); + +/** + * @brief Get the status of FIFO interrupt. + * + * @param[in] dev The device descriptor. + * @param[out] value Current configuration of INT_FIFO. + * @return `ESP_OK` on success, `ESP_ERR_INVALID_ARG` when `dev` and/or `value` is NULL, or other errors when I2C communication fails. + */ +esp_err_t dps310_get_int_fifo(dps310_t *dev, dps310_int_fifo_mode_t *value); + +/** + * @brief Set the status of FIFO interrupt. + * + * @param[in] dev The device descriptor. + * @param[out] value The value to set. + * @return `ESP_OK` on success, `ESP_ERR_INVALID_ARG` when `dev` is NULL, or other errors when I2C communication fails. + */ +esp_err_t dps310_set_int_fifo(dps310_t *dev, dps310_int_fifo_mode_t value); + +/** + * @brief Get the status of temperature interrupt. + * + * @param[in] dev The device descriptor. + * @param[out] value Current configuration of INT_TMP. + * @return `ESP_OK` on success, `ESP_ERR_INVALID_ARG` when `dev` and/or `value` is NULL, or other errors when I2C communication fails. + */ +esp_err_t dps310_get_int_tmp(dps310_t *dev, dps310_int_tmp_mode_t *value); + +/** + * @brief Set the status of temperature interrupt. + * + * @param[in] dev The device descriptor. + * @param[out] value The value to set. + * @return `ESP_OK` on success, `ESP_ERR_INVALID_ARG` when `dev` is NULL, or other errors when I2C communication fails. + */ +esp_err_t dps310_set_int_tmp(dps310_t *dev, dps310_int_tmp_mode_t value); + +/** + * @brief Get the status of pressure interrupt. + * + * @param[in] dev The device descriptor. + * @param[out] value Current configuration of INT_PRS. + * @return `ESP_OK` on success, `ESP_ERR_INVALID_ARG` when `dev` and/or `value` is NULL, or other errors when I2C communication fails. + */ +esp_err_t dps310_get_int_prs(dps310_t *dev, dps310_int_prs_mode_t *value); + +/** + * @brief Set the status of pressure interrupt. + * + * @param[in] dev The device descriptor. + * @param[out] value The value to set. + * @return `ESP_OK` on success, `ESP_ERR_INVALID_ARG` when `dev` is NULL, or other errors when I2C communication fails. + */ +esp_err_t dps310_set_int_prs(dps310_t *dev, dps310_int_prs_mode_t value); + +/** + * @brief Get the status of temperature result bit-shift. + * + * @param[in] dev The device descriptor. + * @param[out] value Current configuration of T_SHIFT. + * @return `ESP_OK` on success, `ESP_ERR_INVALID_ARG` when `dev` and/or `value` is NULL, or other errors when I2C communication fails. + */ +esp_err_t dps310_get_t_shift(dps310_t *dev, dps310_t_shift_mode_t *value); + +/** + * @brief Set the status of temperature result bit-shift. + * + * Must be set to DPS310_T_SHIFT_ENABLE when the oversampling rate is >8 times. + * + * @param[in] dev The device descriptor. + * @param[out] value The value to set. + * @return `ESP_OK` on success, `ESP_ERR_INVALID_ARG` when `dev` is NULL, or other errors when I2C communication fails. + */ +esp_err_t dps310_set_t_shift(dps310_t *dev, dps310_t_shift_mode_t value); + +/** + * @brief Get the status of pressure result bit-shift. + * + * @param[in] dev The device descriptor. + * @param[out] value Current configuration of T_SHIFT. + * @return `ESP_OK` on success, `ESP_ERR_INVALID_ARG` when `dev` and/or `value` is NULL, or other errors when I2C communication fails. + */ +esp_err_t dps310_get_p_shift(dps310_t *dev, dps310_p_shift_mode_t *value); + +/** + * @brief Set the status of pressure result bit-shift. + * + * Must be set to DPS310_P_SHIFT_ENABLE when the oversampling rate is >8 times. + * + * @param[in] dev The device descriptor. + * @param[out] value The value to set. + * @return `ESP_OK` on success, `ESP_ERR_INVALID_ARG` when `dev` is NULL, or other errors when I2C communication fails. + */ +esp_err_t dps310_set_p_shift(dps310_t *dev, dps310_p_shift_mode_t value); + +/** + * @brief Get the status of FIFO. + * + * @param[in] dev The device descriptor. + * @param[out] value Current configuration of FIFO_EN. + * @return `ESP_OK` on success, `ESP_ERR_INVALID_ARG` when `dev` and/or `value` is NULL, or other errors when I2C communication fails. + */ +esp_err_t dps310_get_fifo_en(dps310_t *dev, dps310_fifo_en_mode_t *value); + +/** + * @brief Set the status of FIFO. + * + * @param[in] dev The device descriptor. + * @param[out] value The value to set. + * @return `ESP_OK` on success, `ESP_ERR_INVALID_ARG` when `dev` is NULL, or other errors when I2C communication fails. + */ +esp_err_t dps310_set_fifo_en(dps310_t *dev, dps310_fifo_en_mode_t value); + +/** + * @brief Get the mode of SPI. + * + * @param[in] dev The device descriptor. + * @param[out] value Current configuration of SPI_MODE. + * @return `ESP_OK` on success, `ESP_ERR_INVALID_ARG` when `dev` and/or `value` is NULL, or other errors when I2C communication fails. + */ +esp_err_t dps310_get_spi_mode(dps310_t *dev, dps310_spi_mode_t *value); + +/** + * @brief Set the mode of SPI. + * + * @param[in] dev The device descriptor. + * @param[out] value The value to set. + * @return `ESP_OK` on success, `ESP_ERR_INVALID_ARG` when `dev` is NULL, or other errors when I2C communication fails. + */ +esp_err_t dps310_set_spi_mode(dps310_t *dev, dps310_spi_mode_t value); + +/** + * @brief Get Calibration Coefficients (COEF), update COEF in the device + * descriptor. + * + * @param[in] dev The device descriptor. + * @return `ESP_OK` on success. `ESP_ERR_INVALID_ARG` when `dev` is NULL, or other errors when I2C communication fails. + */ +esp_err_t dps310_get_coef(dps310_t *dev); + +/** + * @brief Get operating mode. + * + * @param[in] dev The device descriptor. + * @param[out] mode The operating mode. + */ +esp_err_t dps310_get_mode(dps310_t *dev, dps310_mode_t *mode); + +/** + * @brief Set operating mode. + * + * @param[in] dev The device descriptor. + * @param[in] mode The operating mode. + * @return `ESP_OK` on success. `ESP_ERR_INVALID_ARG` when `dev` is NULL, or other errors when I2C communication fails. + */ +esp_err_t dps310_set_mode(dps310_t *dev, dps310_mode_t mode); + +/** + * @brief Flush FIFO. + * + * @param[in] dev The device descriptor. + * @return `ESP_OK` on success. `ESP_ERR_INVALID_ARG` when `dev` is NULL, or other errors when I2C communication fails. + */ +esp_err_t dps310_flush_fifo(dps310_t *dev); + +/** + * @brief Enable or disable FIFO. + * + * The function performs flush (`dps310_flush_fifo()`) before disabling FIFO. + * + * @param[in] dev The device descriptor. + * @param[in] enable Enable FIFO when true, disable FIFO when false. + * @return `ESP_OK` on success. `ESP_ERR_INVALID_ARG` when `dev` is NULL, or other errors when I2C communication fails. + */ +esp_err_t dps310_enable_fifo(dps310_t *dev, bool enable); + +/** + * @brief Read the raw sensor value from resisters. + * + * The real raw value is 2's complement. The function internally converts the + * value from the 2's complement to uint32_t number. + * + * @param[in] dev The device descriptor. + * @param[in] reg Either `DPS310_REG_TMP_B2` or `DPS310_REG_PRS_B2`. + * @param[out] value The raw value in the three resisters. + * @return `ESP_OK` on success. `ESP_ERR_INVALID_ARG` when `dev` is NULL, or other errors when I2C communication fails. + */ +esp_err_t dps310_read_raw(dps310_t *dev, uint8_t reg, int32_t *value); + +/** + * @brief Read compensated pressure value. + * + * @param[in] dev The device descriptor. + * @param[out] pressure Compensated pressure value in Pascal (not hPa). + * @return `ESP_OK` on success. `ESP_ERR_INVALID_ARG` when `dev` is NULL, or other errors when I2C communication fails. + */ +esp_err_t dps310_read_pressure(dps310_t *dev, float *pressure); + +/** + * @brief Read compensated temperature value after waiting for PRES_RDY bit. + * + * @param[in] dev The device descriptor. + * @param[in] delay_ms Time in microseconds to wait when the value is not ready. + * @param[in] max_attempt Number of attempt to read. + * @param[out] pressure Compensated pressure value in Pascal (not hPa). + * @return `ESP_OK` on success. `ESP_ERR_INVALID_ARG` when `dev` is NULL. ESP_ERR_TIMEOUT when failed to read the measurement within max_attempt, or other errors when I2C communication fails. + */ +esp_err_t dps310_read_pressure_wait(dps310_t *dev, uint16_t delay_ms, uint8_t max_attempt, float *pressure); + +/** + * @brief Read compensated temperature value. + * + * @param[in] dev The device descriptor. + * @param[out] temperature Compensated temperature value. + * @return `ESP_OK` on success. `ESP_ERR_INVALID_ARG` when `dev` is NULL, or other errors when I2C communication fails. + */ +esp_err_t dps310_read_temp(dps310_t *dev, float *temperature); + +/** + * @brief Read compensated temperature value after waiting for TMP_RDY bit. + * + * @param[in] dev The device descriptor. + * @param[in] delay_ms Time in microseconds to wait when the value is not ready. + * @param[in] max_attempt Number of attempt to read. + * @param[out] temperature Compensated temperature value. + * @return `ESP_OK` on success. `ESP_ERR_INVALID_ARG` when `dev` is NULL. ESP_ERR_TIMEOUT when failed to read the measurement within max_attempt, or other errors when I2C communication fails. + */ +esp_err_t dps310_read_temp_wait(dps310_t *dev, uint16_t delay_ms, uint8_t max_attempt, float *temperature); + +/** + * @brief Test if a single bit in a resister is set. + * + * @param[in] dev The device descriptor. + * @param[in] reg The resister + * @param[in] mask bit mask to test + * @param[out] ready true when the bit is set, false when the bit is cleared. + * @return `ESP_OK` on success. `ESP_ERR_INVALID_ARG` when `dev` is NULL, or other errors when I2C communication fails. + */ +esp_err_t dps310_is_ready_for(dps310_t *dev, uint8_t reg, uint8_t mask, bool *ready); + +/** + * @brief Test COEF_RDY in MEAS_CFG resister is set. + * + * @param[in] dev The device descriptor. + * @param[out] ready true when the bit is set, false when the bit is cleared. + * @return `ESP_OK` on success. `ESP_ERR_INVALID_ARG` when `dev` is NULL, or other errors when I2C communication fails. + */ +esp_err_t dps310_is_ready_for_coef(dps310_t *dev, bool *ready); + +/** + * @brief Test SENSOR_RDY in MEAS_CFG resister is set. + * + * @param[in] dev The device descriptor. + * @param[out] ready true when the bit is set, false when the bit is cleared. + * @return `ESP_OK` on success. `ESP_ERR_INVALID_ARG` when `dev` is NULL, or other errors when I2C communication fails. + */ +esp_err_t dps310_is_ready_for_sensor(dps310_t *dev, bool *ready); + +/** + * @brief Test TMP_RDY in MEAS_CFG resister is set. + * + * @param[in] dev The device descriptor. + * @param[out] ready true when the bit is set, false when the bit is cleared. + * @return `ESP_OK` on success. `ESP_ERR_INVALID_ARG` when `dev` is NULL, or other errors when I2C communication fails. + */ +esp_err_t dps310_is_ready_for_temp(dps310_t *dev, bool *ready); + +/** + * @brief Test PRS_RDY in MEAS_CFG resister is set. + * + * @param[in] dev The device descriptor. + * @param[out] ready true when the bit is set, false when the bit is cleared. + * @return `ESP_OK` on success. `ESP_ERR_INVALID_ARG` when `dev` is NULL, or other errors when I2C communication fails. + */ +esp_err_t dps310_is_ready_for_pressure(dps310_t *dev, bool *ready); + +/** + * @brief Reset undocumented internal resisters. + * + * The function is supposed to fix an issue in the sensor by writing magic + * values to magic resisters. However, the issue is not documented. The + * latest data sheet does not mention the issue, nor an errata. + * + * After issuing magic commands, the function re-reads COEF and temperature + * once so that the subsequent pressure reads return compensated values with + * internal cached parameters. + * + * See: + * https://github.com/Infineon/DPS310-Pressure-Sensor#temperature-measurement-issue + * https://github.com/Infineon/DPS310-Pressure-Sensor/blob/3edb0e58dfd7691491ae8d7f6a86277b001ad93f/src/DpsClass.cpp#L442-L461 + * https://github.com/Infineon/DPS310-Pressure-Sensor/blob/ed02f803fc780cbcab54ed8b35dd3d718f2ebbda/src/Dps310.cpp#L84-L86 + * https://github.com/Infineon/DPS310-Pressure-Sensor/issues/15#issuecomment-475394536 + * + * @param[in] dev The device descriptor. + * @return `ESP_OK` on success. `ESP_ERR_INVALID_ARG` when `dev` is NULL, or other errors when I2C communication fails. + */ +esp_err_t dps310_quirk(dps310_t *dev); + +/** + * @brief See if FIFO is empty. + * + * @param[in] dev The device descriptor. + * @param[out] result The result. true if empty, false otherwise. + * @return `ESP_OK` on success. `ESP_ERR_INVALID_ARG` when `dev` is NULL, or other errors when I2C communication fails. + */ +esp_err_t dps310_is_fifo_empty(dps310_t *dev, bool *result); + +/** + * @brief Read measurement result from FIFO. + * + * @param[in] dev The device descriptor. + * @param[out] measurement Measured value. + * @return `ESP_OK` on success. `ESP_ERR_INVALID_ARG` when `dev` is NULL, or other errors when I2C communication fails. + */ +esp_err_t dps310_read_fifo(dps310_t *dev, dps310_fifo_measurement_t *measurement); + +/** + * @brief Start background measurement. + * + * This function is a syntax-sugar of `dps310_set_mode()` just for readbility + * and for an emphasis on a fact that measurement starts immediately after + * this. + * + * @param[in] dev The device descriptor. + * @param[in] mode The mode of background measurement. + * @return `ESP_OK` on success. `ESP_ERR_INVALID_ARG` when `dev` is NULL, or other errors when I2C communication fails. + */ +esp_err_t dps310_backgorund_start(dps310_t *dev, dps310_mode_t mode); + +/** + * @brief Stop background measurement. + * + * This function is a syntax-sugar of `dps310_set_mode()`. + * + * @param[in] dev The device descriptor. + * @return `ESP_OK` on success. `ESP_ERR_INVALID_ARG` when `dev` is NULL, or other errors when I2C communication fails. + */ +esp_err_t dps310_backgorund_stop(dps310_t *dev); + +/** + * @brief Calibrate altitude offset from the altitude of the device. + * + * Call this function before dps310_read_altitude() for higher accuracy. + * + * By default, the driver calculates altitude using average sea-level + * pressure. This function updates internal offset of altitude by reading + * pressure from the sensor, and given altitude. There are public web services + * that provide altitude at a specific location, such as Google Earth. + * + * The function attempts to keep original oversampling rates during + * calibration. When it fails to do so due to errors, the oversampling rates + * might be different. + * + * @param[in] dev The device descriptor. + * @param[in] altitude_real Real (known) altitude. + */ +esp_err_t dps310_calibrate_altitude(dps310_t *dev, float altitude_real); + +/** + * @brief Calculate altitude from pressure. + * + * Calculates altitude from pressure given. Call dps310_calibrate_altitude() + * before this function for higher accuracy. The function adds the offset to + * calculated altitude. + * + * @param[in] dev The device descriptor. + * @param[in] pressure The pressure. + * @param[out] altitude The calicurated altitude. + */ +esp_err_t dps310_calc_altitude(dps310_t *dev, float pressure, float *altitude); + +/** + * @brief Read pressure from the sensor, calculates altitude. + * + * Make sure that pressure measurement value is available. + * + * @param[in] dev The device descriptor. + * @param[out] altitude The calculated altitude. + */ +esp_err_t dps310_read_altitude(dps310_t *dev, float *altitude); + +#ifdef __cplusplus +} +#endif + +/**@}*/ + +#endif // __DPS310_H__
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/dps310/priv_include/helper_i2c.h Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2022 Tomoyuki Sakurai <y@trombik.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* + * A private header for I2C interface. Most of them are a wrapper of i2cdev + * functions. + * + * Functions in the header should be prefixed with `_` to indicate they are + * a private function. + * + * A postfix, `_nolock` in function name means the function does not acquire + * I2C lock in `i2cdev`. + */ + +#if !defined(__DPS310__HELPER_I2C__H__) +#define __DPS310__HELPER_I2C__H__ + +/* standard headers */ +#include <inttypes.h> + +/* esp-idf headers */ +#include <esp_err.h> +#include <i2cdev.h> + +/* private headers */ +#include "helper_macro.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Read a single byte from a 8-bit resister with locking. + */ +esp_err_t _read_reg(i2c_dev_t *dev, uint8_t reg, uint8_t *val); + +/** + * @brief Read a masked value from a 8-bit resister with locking. + * + * The returned value is bit-shifted. When `mask` is `0b1111000`, and the + * value in the resister is `0b00010000`, `val` is `0b0001`. + */ +esp_err_t _read_reg_mask(i2c_dev_t *dev, uint8_t reg, uint8_t mask, uint8_t *val); + +/** + * @brief Write a single byte to a 8-bit resister with locking. + */ +esp_err_t _write_reg(i2c_dev_t *dev, uint8_t reg, uint8_t *value); + +/** + * @brief Update a 8-bit resister with a masked value without locking. + * + * `mask` is the mask to update, and `val` is the value. The function reads + * the resister, and see if the bit values in the resister needs update. If + * the bit value is identical with `val`, it does not update the resister. + * + * `val` is automatically bit-shifted when updating the resister. + * + * Useful to update specific bits in a resister. + * + * See also: _update_reg(). + */ +esp_err_t _update_reg_nolock(i2c_dev_t *dev, uint8_t reg, uint8_t mask, uint8_t val); + +/** + * @brief Update a 8-bit resister with a masked value. + */ +esp_err_t _update_reg(i2c_dev_t *dev, uint8_t reg, uint8_t mask, uint8_t val); + +esp_err_t _wait_for_reg_bits(i2c_dev_t *dev, uint8_t reg, uint8_t mask, uint8_t val, uint8_t max_attempt, uint16_t delay_ms); + +#ifdef __cplusplus +} +#endif + +#endif // __DPS310__HELPER_I2C__H__
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/dps310/priv_include/helper_macro.h Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2022 Tomoyuki Sakurai <y@trombik.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#if !defined(__DPS310_HELPER_MACRO_H__) +#define __DPS310_HELPER_MACRO_H__ + +#define BV(x) ((uint8_t)(1 << (x))) +#define CHECK(x) do { esp_err_t __; if ((__ = x) != ESP_OK) return __; } while (0) +#define CHECK_ARG(VAL) do { if (!(VAL)) return ESP_ERR_INVALID_ARG; } while (0) +#define CHECK_LOGE(dev, x, msg, ...) do { \ + esp_err_t __; \ + if ((__ = x) != ESP_OK) { \ + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); \ + ESP_LOGE(TAG, msg, ## __VA_ARGS__); \ + return __; \ + } \ + } while (0) + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/dps310/src/dps310.c Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,1212 @@ +/* + * Copyright (c) 2022 Tomoyuki Sakurai <y@trombik.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/** + * @file dps310.c + * + * ESP-IDF driver for DPS310. Sponserd by @beriberikix. + * + * Note that the driver always tries to compensate raw values from the sensor. + * When compensation is not required, or undesired, users should implement + * their own functions to bypass compensation. + */ + +/* standard headers */ +#include <inttypes.h> +#include <string.h> +#include <assert.h> +#include <math.h> + +/* esp-idf headers */ +#include <freertos/FreeRTOS.h> +#include <freertos/task.h> +#include <esp_log.h> +#include <esp_idf_lib_helpers.h> +#include <esp_err.h> +#include <i2cdev.h> + +/* private headers */ +#include "helper_macro.h" +#include "helper_i2c.h" + +/* public headers */ +#include "dps310.h" + +#define DPS310_QUIRK_DELAY_MS (10) +#define DPS310_QUIRK_MAX_ATTEMPT (5) + +static const char *TAG = "dps310"; + +/* see 4.9.3 Compensation Scale Factors */ +#define N_SCALE_FACTORS (8) +static const int32_t scale_factors[N_SCALE_FACTORS] = { + 524288, + 1572864, + 3670016, + 7864320, + 253952, + 516096, + 1040384, + 2088960 +}; + +esp_err_t dps310_quirk(dps310_t *dev) +{ + esp_err_t err = ESP_FAIL; + const int magic_command_len = 5; + float ignore = 0; + const uint8_t magic_commands[5][2] = { + + /* reg address, value */ + { 0xA5, 0x0E }, + { 0x0F, 0x96 }, + { 0x62, 0x02 }, + { 0x0E, 0x00 }, + { 0x0F, 0x00 }, + }; + dps310_mode_t original_mode = 0; + + CHECK_ARG(dev); + ESP_LOGD(TAG, "dps310_quirk(): resetting resisters with magic numbers"); + for (int i = 0; i < magic_command_len; i++) + { + uint8_t reg = magic_commands[i][0]; + uint8_t value = magic_commands[i][1]; + err = _write_reg(&dev->i2c_dev, reg, &value); + if (err != ESP_OK) + { + ESP_LOGE(TAG, "_write_reg(): %s (reg: 0x%02X)", esp_err_to_name(err), reg); + break; + } + } + if (err != ESP_OK) + { + goto fail; + } + vTaskDelay(pdMS_TO_TICKS(DPS310_STARTUP_DELAY_MS)); + + /* sensor is ready? */ + err = _wait_for_reg_bits(&dev->i2c_dev, DPS310_REG_MEAS_CFG, DPS310_REG_MEAS_CFG_SENSOR_RDY_MASK, 1, DPS310_QUIRK_MAX_ATTEMPT, DPS310_QUIRK_DELAY_MS); + if (err != ESP_OK) + { + ESP_LOGE(TAG, "_wait_for_reg_bits(): %s", esp_err_to_name(err)); + goto fail; + } + /* coef is ready? */ + err = _wait_for_reg_bits(&dev->i2c_dev, DPS310_REG_MEAS_CFG, DPS310_REG_MEAS_CFG_COEF_RDY_MASK, 1, DPS310_QUIRK_MAX_ATTEMPT, DPS310_QUIRK_DELAY_MS); + if (err != ESP_OK) + { + ESP_LOGE(TAG, "_wait_for_reg_bits(): %s", esp_err_to_name(err)); + goto fail; + } + + ESP_LOGD(TAG, "dps310_quirk(): reading COEF"); + err = dps310_get_coef(dev); + if (err != ESP_OK) + { + ESP_LOGE(TAG, "dps310_get_coef(): %s", esp_err_to_name(err)); + goto fail; + } + + ESP_LOGD(TAG, "dps310_quirk(): keep the original operation mode"); + err = dps310_get_mode(dev, &original_mode); + if (err != ESP_OK) + { + ESP_LOGE(TAG, "dps310_get_mode(): %s", esp_err_to_name(err)); + goto fail; + } + + ESP_LOGD(TAG, "dps310_quirk(): setting mode to DPS310_MODE_COMMAND_TEMPERATURE"); + err = dps310_set_mode(dev, DPS310_MODE_COMMAND_TEMPERATURE); + if (err != ESP_OK) { + ESP_LOGE(TAG, "dps310_set_mode(): %s", esp_err_to_name(err)); + goto fail; + } + + ESP_LOGD(TAG, "dps310_quirk(): reading temperature just once"); + err = dps310_read_temp_wait(dev, DPS310_QUIRK_DELAY_MS, DPS310_QUIRK_MAX_ATTEMPT, &ignore); + if (err != ESP_OK) + { + ESP_LOGE(TAG, "dps310_read_temp_wait(): %s", esp_err_to_name(err)); + goto fail; + } + + ESP_LOGD(TAG, "dps310_quirk(): restore the original mode"); + err = dps310_set_mode(dev, original_mode); + if (err != ESP_OK) { + ESP_LOGE(TAG, "dps310_set_mode(): %s", esp_err_to_name(err)); + goto fail; + } + +fail: + return err; +} + +esp_err_t dps310_init_desc(dps310_t *dev, uint8_t addr, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio) +{ + esp_err_t err = ESP_FAIL; + + if (dev == NULL) + { + err = ESP_ERR_INVALID_ARG; + goto fail; + } + if (addr != DPS310_I2C_ADDRESS_0 && addr != DPS310_I2C_ADDRESS_1) + { + ESP_LOGE(TAG, "Invalid I2C address: expected %0X or %0X, got %0X", + DPS310_I2C_ADDRESS_0, DPS310_I2C_ADDRESS_1, addr); + err = ESP_ERR_INVALID_ARG; + goto fail; + } + + dev->i2c_dev.port = port; + dev->i2c_dev.addr = addr; + dev->i2c_dev.cfg.sda_io_num = sda_gpio; + dev->i2c_dev.cfg.scl_io_num = scl_gpio; +#if HELPER_TARGET_IS_ESP32 + dev->i2c_dev.cfg.master.clk_speed = DPS310_I2C_FREQ_MAX_ESP_IDF_HZ; +#endif + + ESP_LOGD(TAG, "Port: %u SCL: GPIO%i SDA: GPIO%i", + port, + dev->i2c_dev.cfg.scl_io_num, + dev->i2c_dev.cfg.sda_io_num); + err = i2c_dev_create_mutex(&dev->i2c_dev); + if (err != ESP_OK) + { + ESP_LOGE(TAG, "i2c_dev_create_mutex(): %s", esp_err_to_name(err)); + goto fail; + } + dev->pressure_s = DPS310_AVERAGE_SEA_LEVEL_PRESSURE_Pa; + dev->offset = 0; + +fail: + return err; + +} + +esp_err_t dps310_free_desc(dps310_t *dev) +{ + esp_err_t err = ESP_FAIL; + + err = i2c_dev_delete_mutex(&dev->i2c_dev); + if (err != ESP_OK) + { + ESP_LOGW(TAG, "i2c_dev_delete_mutex(): %s", esp_err_to_name(err)); + goto fail; + } +fail: + return err; +} + +static int pow_int(int base, int x) +{ + int result = 1; + + for (int i = 0; i < x ; ++i) + { + result *= base; + } + return result; +} + +esp_err_t dps310_init(dps310_t *dev, dps310_config_t *config) +{ + uint8_t reg_value = 0; + esp_err_t err = ESP_FAIL; + + if (dev == NULL) + { + ESP_LOGE(TAG, "dps310_init(): invalid dev"); + err = ESP_ERR_INVALID_ARG; + goto fail; + } + if (config == NULL) + { + ESP_LOGE(TAG, "dps310_init(): invalid config"); + err = ESP_ERR_INVALID_ARG; + goto fail; + } + + err = _read_reg(&dev->i2c_dev, DPS310_REG_ID, ®_value); + if (err != ESP_OK) + { + ESP_LOGE(TAG, "Device not found"); + goto fail; + } + + dev->prod_id = (DPS310_REG_ID_PROD_MASK & reg_value) >> 4; + dev->prod_rev = DPS310_REG_ID_REV_MASK & reg_value; + ESP_LOGD(TAG, "prod_id: 0x%x prod_rev: 0x%x", dev->prod_id, dev->prod_rev); + if (dev->prod_id != DPS310_PROD_ID) + { + ESP_LOGE(TAG, "Invalid prod ID: expected: 0x%x (DPS310) got: 0x%x", DPS310_PROD_ID, dev->prod_id); + err = ESP_FAIL; + goto fail; + } + + err = dps310_reset(dev); + if (err != ESP_OK) + { + ESP_LOGE(TAG, "dps310_reset(): %s", esp_err_to_name(err)); + goto fail; + } + + err = dps310_quirk(dev); + if (err != ESP_OK) + { + ESP_LOGE(TAG, "dps310_quirk(): %s", esp_err_to_name(err)); + goto fail; + } + + ESP_LOGD(TAG, "Pressure measurement rate: %i measurements / sec", pow_int(2, config->pm_rate)); + err = dps310_set_rate_p(dev, config->pm_rate); + if (err != ESP_OK) + { + goto fail; + } + ESP_LOGD(TAG, "Pressure oversampling: %i time(s)", pow_int(2, config->pm_oversampling)); + err = dps310_set_oversampling_p(dev, config->pm_oversampling); + if (err != ESP_OK) + { + goto fail; + } + ESP_LOGD(TAG, "Temperature measurement rate: %i measurements / sec", pow_int(2, config->tmp_rate)); + err = dps310_set_rate_t(dev, config->tmp_rate); + if (err != ESP_OK) + { + goto fail; + } + ESP_LOGD(TAG, "Temperature oversampling: %i time(s)", pow_int(2, config->tmp_oversampling)); + err = dps310_set_oversampling_t(dev, config->tmp_oversampling); + if (err != ESP_OK) + { + goto fail; + } + ESP_LOGD(TAG, "Temperature source: %s", config->tmp_src == DPS310_TMP_SRC_INTERNAL ? "internal" : "external"); + err = dps310_set_tmp_ext(dev, config->tmp_src); + if (err != ESP_OK) + { + goto fail; + } + ESP_LOGD(TAG, "Temperature COEF source: %s", config->tmp_coef == DPS310_TMP_SRC_INTERNAL ? "internal" : "external"); + err = dps310_set_tmp_coef_ext(dev, config->tmp_coef); + if (err != ESP_OK) + { + goto fail; + } + if (config->tmp_src != config->tmp_coef) + { + + /* XXX don't know when DPS310_TMP_SRC_INTERNAL should be used. the + * datasheet does not mention the differece. + */ + ESP_LOGW(TAG, "tmp_src and tmp_coef should be an identical source. Use DPS310_TMP_SRC_EXTERNAL in the config"); + } + ESP_LOGD(TAG, "Interrupt FIFO: %s", config->int_fifo_mode == DPS310_INT_FIFO_ENABLE ? "enabled" : "disabled"); + err = dps310_set_int_fifo(dev, config->int_fifo_mode); + if (err != ESP_OK) + { + goto fail; + } + ESP_LOGD(TAG, "Interrupt temperature: %s", config->int_tmp_mode == DPS310_INT_TMP_ENABLE ? "enabled" : "disabled"); + err = dps310_set_int_tmp(dev, config->int_tmp_mode); + if (err != ESP_OK) + { + goto fail; + } + ESP_LOGD(TAG, "Interrupt pressure: %s", config->int_prs_mode == DPS310_INT_PRS_ENABLE ? "enabled" : "disabled"); + err = dps310_set_int_prs(dev, config->int_prs_mode); + if (err != ESP_OK) + { + goto fail; + } + + ESP_LOGD(TAG, "Temperature result bit-shift: %s", config->t_shift_mode == DPS310_T_SHIFT_ENABLE ? "enabled" : "disabled"); + if (config->tmp_oversampling > DPS310_TMP_PRC_8 && config->t_shift_mode != DPS310_T_SHIFT_ENABLE) + { + ESP_LOGW(TAG, "Temperature result bit-shift must be enabled, but is disabled. Set DPS310_T_SHIFT_ENABLE"); + } + err = dps310_set_t_shift(dev, config->t_shift_mode); + if (err != ESP_OK) + { + goto fail; + } + + ESP_LOGD(TAG, "Pressure result bit-shift: %s", config->p_shift_mode == DPS310_P_SHIFT_ENABLE ? "enabled" : "disabled"); + if (config->pm_oversampling > DPS310_PM_PRC_8 && config->p_shift_mode != DPS310_P_SHIFT_ENABLE) + { + ESP_LOGW(TAG, "Pressure result bit-shift must be enabled, but is disabled. Set DPS310_P_SHIFT_ENABLE"); + } + err = dps310_set_p_shift(dev, config->p_shift_mode); + if (err != ESP_OK) + { + goto fail; + } + + ESP_LOGD(TAG, "FIFO: %s", config->fifo_en_mode == DPS310_FIFO_ENABLE ? "enabled" : "disabled"); + err = dps310_set_fifo_en(dev, config->fifo_en_mode); + if (err != ESP_OK) + { + goto fail; + } + + err = ESP_OK; + +fail: + return err; +} + +esp_err_t dps310_get_rate_p(dps310_t *dev, dps310_pm_rate_t *value) +{ + CHECK_ARG(dev && value); + + return _read_reg_mask(&dev->i2c_dev, DPS310_REG_PRS_CFG, DPS310_REG_PRS_CFG_PM_RATE_MASK, (uint8_t *)value); +} + +esp_err_t dps310_set_rate_p(dps310_t *dev, dps310_pm_rate_t value) +{ + CHECK_ARG(dev); + + return _update_reg(&dev->i2c_dev, DPS310_REG_PRS_CFG, DPS310_REG_PRS_CFG_PM_RATE_MASK, value); +} + +esp_err_t dps310_get_rate_t(dps310_t *dev, dps310_tmp_rate_t *value) +{ + CHECK_ARG(dev && value); + + return _read_reg_mask(&dev->i2c_dev, DPS310_REG_TMP_CFG, DPS310_REG_PRS_CFG_TMP_RATE_MASK, (uint8_t *)value); +} + +esp_err_t dps310_set_rate_t(dps310_t *dev, dps310_tmp_rate_t value) +{ + CHECK_ARG(dev); + + return _update_reg(&dev->i2c_dev, DPS310_REG_TMP_CFG, DPS310_REG_PRS_CFG_TMP_RATE_MASK, value); +} + +esp_err_t dps310_reset(dps310_t *dev) +{ + esp_err_t err = ESP_FAIL; + uint8_t value = DPS310_SOFT_RST_VALUE; + + CHECK_ARG(dev); + + ESP_LOGD(TAG, "Resetting the device"); + err = _write_reg(&dev->i2c_dev, DPS310_REG_RESET, &value); + if (err != ESP_OK) + { + ESP_LOGE(TAG, "failed to reset the device"); + goto fail; + } + vTaskDelay(pdMS_TO_TICKS(DPS310_STARTUP_DELAY_MS)); +fail: + return err; +} + +esp_err_t dps310_get_oversampling_p(dps310_t *dev, dps310_pm_oversampling_t *value) +{ + esp_err_t err = ESP_FAIL; + + CHECK_ARG(dev && value); + err = _read_reg_mask(&dev->i2c_dev, DPS310_REG_PRS_CFG, DPS310_REG_PRS_CFG_PM_PRC_MASK, (uint8_t *)value); + if (err == ESP_OK) + { + /* XXX when new p_rate is available, always keep it in dev as a cache */ + dev->p_rate = *value; + } + return err; +} + +esp_err_t dps310_set_oversampling_p(dps310_t *dev, dps310_pm_oversampling_t value) +{ + esp_err_t err = ESP_FAIL; + + CHECK_ARG(dev); + err = _update_reg(&dev->i2c_dev, DPS310_REG_PRS_CFG, DPS310_REG_PRS_CFG_PM_PRC_MASK, value); + if (err == ESP_OK) + { + /* XXX when new p_rate is available, always keep it in dev as a cache */ + dev->p_rate = value; + } + return err; +} + +esp_err_t dps310_get_oversampling_t(dps310_t *dev, dps310_tmp_oversampling_t *value) +{ + esp_err_t err = ESP_FAIL; + + CHECK_ARG(dev && value); + err = _read_reg_mask(&dev->i2c_dev, DPS310_REG_TMP_CFG, DPS310_REG_TMP_CFG_TMP_PRC_MASK, (uint8_t *)value); + if (err == ESP_OK) + { + /* XXX when new t_rate is available, always keep it in dev as a cache */ + dev->t_rate = *value; + } + return err; +} + +esp_err_t dps310_set_oversampling_t(dps310_t *dev, dps310_tmp_oversampling_t value) +{ + esp_err_t err = ESP_FAIL; + + CHECK_ARG(dev); + err = _update_reg(&dev->i2c_dev, DPS310_REG_TMP_CFG, DPS310_REG_TMP_CFG_TMP_PRC_MASK, value); + if (err == ESP_OK) + { + /* XXX when new t_rate is available, always keep it in dev as a cache */ + dev->t_rate = value; + } + return err; +} + +esp_err_t dps310_get_tmp_ext(dps310_t *dev, dps310_tmp_src_ext_t *value) +{ + CHECK_ARG(dev && value); + + return _read_reg_mask(&dev->i2c_dev, DPS310_REG_TMP_CFG, DPS310_REG_PRS_CFG_TMP_EXT_MASK, (uint8_t *)value); +} + +esp_err_t dps310_set_tmp_ext(dps310_t *dev, dps310_tmp_src_ext_t value) +{ + CHECK_ARG(dev); + + return _update_reg(&dev->i2c_dev, DPS310_REG_TMP_CFG, DPS310_REG_PRS_CFG_TMP_EXT_MASK, value); +} + +esp_err_t dps310_set_tmp_coef_ext(dps310_t *dev, dps310_tmp_src_ext_t value) +{ + CHECK_ARG(dev); + + return _update_reg(&dev->i2c_dev, DPS310_REG_COEF_SRCE, DPS310_REG_COEF_SRCE_MASK, value); +} + +esp_err_t dps310_get_int_hl(dps310_t *dev, dps310_int_hl_active_level_t *value) +{ + CHECK_ARG(dev && value); + + return _read_reg_mask(&dev->i2c_dev, DPS310_REG_CFG_REG, DPS310_REG_CFG_REG_INT_HL_MASK, (uint8_t *)value); +} + +esp_err_t dps310_set_int_hl(dps310_t *dev, dps310_int_hl_active_level_t value) +{ + CHECK_ARG(dev); + + return _update_reg(&dev->i2c_dev, DPS310_REG_CFG_REG, DPS310_REG_PRS_CFG_TMP_EXT_MASK, value); +} + +esp_err_t dps310_get_int_fifo(dps310_t *dev, dps310_int_fifo_mode_t *value) +{ + CHECK_ARG(dev && value); + + return _read_reg_mask(&dev->i2c_dev, DPS310_REG_CFG_REG, DPS310_REG_CFG_REG_INT_FIFO_MASK, (uint8_t *)value); +} + +esp_err_t dps310_set_int_fifo(dps310_t *dev, dps310_int_fifo_mode_t value) +{ + CHECK_ARG(dev); + + return _update_reg(&dev->i2c_dev, DPS310_REG_CFG_REG, DPS310_REG_CFG_REG_INT_FIFO_MASK, value); +} + +esp_err_t dps310_get_int_tmp(dps310_t *dev, dps310_int_tmp_mode_t *value) +{ + CHECK_ARG(dev && value); + + return _read_reg_mask(&dev->i2c_dev, DPS310_REG_CFG_REG, DPS310_REG_CFG_REG_INT_TMP_MASK, (uint8_t *)value); +} + +esp_err_t dps310_set_int_tmp(dps310_t *dev, dps310_int_tmp_mode_t value) +{ + return _update_reg(&dev->i2c_dev, DPS310_REG_CFG_REG, DPS310_REG_CFG_REG_INT_TMP_MASK, value); +} + +esp_err_t dps310_get_int_prs(dps310_t *dev, dps310_int_prs_mode_t *value) +{ + CHECK_ARG(dev && value); + + return _read_reg_mask(&dev->i2c_dev, DPS310_REG_CFG_REG, DPS310_REG_CFG_REG_INT_PRS_MASK, (uint8_t *)value); +} + +esp_err_t dps310_set_int_prs(dps310_t *dev, dps310_int_prs_mode_t value) +{ + CHECK_ARG(dev); + + return _update_reg(&dev->i2c_dev, DPS310_REG_CFG_REG, DPS310_REG_CFG_REG_INT_PRS_MASK, value); +} + +esp_err_t dps310_get_t_shift(dps310_t *dev, dps310_t_shift_mode_t *value) +{ + CHECK_ARG(dev && value); + + return _read_reg_mask(&dev->i2c_dev, DPS310_REG_CFG_REG, DPS310_REG_CFG_REG_T_SHIFT_MASK, (uint8_t *)value); +} + +esp_err_t dps310_set_t_shift(dps310_t *dev, dps310_t_shift_mode_t value) +{ + CHECK_ARG(dev); + + return _update_reg(&dev->i2c_dev, DPS310_REG_CFG_REG, DPS310_REG_CFG_REG_T_SHIFT_MASK, value); +} + +esp_err_t dps310_get_p_shift(dps310_t *dev, dps310_p_shift_mode_t *value) +{ + CHECK_ARG(dev && value); + + return _read_reg_mask(&dev->i2c_dev, DPS310_REG_CFG_REG, DPS310_REG_CFG_REG_P_SHIFT_MASK, (uint8_t *)value); +} + +esp_err_t dps310_set_p_shift(dps310_t *dev, dps310_p_shift_mode_t value) +{ + CHECK_ARG(dev); + + return _update_reg(&dev->i2c_dev, DPS310_REG_CFG_REG, DPS310_REG_CFG_REG_P_SHIFT_MASK, value); +} + +esp_err_t dps310_get_fifo_en(dps310_t *dev, dps310_fifo_en_mode_t *value) +{ + CHECK_ARG(dev && value); + + return _read_reg_mask(&dev->i2c_dev, DPS310_REG_CFG_REG, DPS310_REG_CFG_REG_FIFO_EN_MASK, (uint8_t *)value); +} + +esp_err_t dps310_set_fifo_en(dps310_t *dev, dps310_fifo_en_mode_t value) +{ + return _update_reg(&dev->i2c_dev, DPS310_REG_CFG_REG, DPS310_REG_CFG_REG_FIFO_EN_MASK, value); +} + +esp_err_t dps310_get_spi_mode(dps310_t *dev, dps310_spi_mode_t *value) +{ + CHECK_ARG(dev && value); + + return _read_reg_mask(&dev->i2c_dev, DPS310_REG_CFG_REG, DPS310_REG_CFG_REG_SPI_MODE_MASK, (uint8_t *)value); +} + +esp_err_t dps310_set_spi_mode(dps310_t *dev, dps310_spi_mode_t value) +{ + CHECK_ARG(dev); + + return _update_reg(&dev->i2c_dev, DPS310_REG_CFG_REG, DPS310_REG_CFG_REG_SPI_MODE_MASK, value); +} + +static int32_t two_complement_of(uint32_t value, uint8_t length) +{ + int32_t result = (uint32_t)value; + if (value & ((uint32_t)1 << (length - (uint32_t)1))) + { + result -= ((uint32_t) 1 << length); + } + return result; +} + +esp_err_t dps310_get_coef(dps310_t *dev) +{ + uint8_t reg_values[DPS310_REG_COEF_LEN]; + + esp_err_t err = ESP_FAIL; + + CHECK_ARG(dev); + I2C_DEV_TAKE_MUTEX(&dev->i2c_dev); + err = i2c_dev_read_reg(&dev->i2c_dev, DPS310_REG_COEF, reg_values, DPS310_REG_COEF_LEN); + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); + if (err != ESP_OK) + { + ESP_LOGE(TAG, "i2c_dev_read_reg(): %s", esp_err_to_name(err)); + goto fail; + } + + dev->coef.c0 = ((uint32_t)reg_values[0] << 4) | (((uint32_t)reg_values[1] >> 4) & 0x0F); + dev->coef.c0 = two_complement_of(dev->coef.c0, 12); + + dev->coef.c1 = (((uint32_t)reg_values[1] & 0x0F) << 8) | (uint32_t)reg_values[2]; + dev->coef.c1 = two_complement_of(dev->coef.c1, 12); + + dev->coef.c00 = ((uint32_t)reg_values[3] << 12) | ((uint32_t)reg_values[4] << 4) | (((uint32_t)reg_values[5] >> 4) & 0x0F); + dev->coef.c00 = two_complement_of(dev->coef.c00, 20); + + dev->coef.c10 = (((uint32_t)reg_values[5] & 0x0F) << 16) | ((uint32_t)reg_values[6] << 8) | (uint32_t)reg_values[7]; + dev->coef.c10 = two_complement_of(dev->coef.c10, 20); + + dev->coef.c01 = ((uint32_t)reg_values[8] << 8) | (uint32_t)reg_values[9]; + dev->coef.c01 = two_complement_of(dev->coef.c01, 16); + + dev->coef.c11 = ((uint32_t)reg_values[10] << 8) | (uint32_t)reg_values[11]; + dev->coef.c11 = two_complement_of(dev->coef.c11, 16); + + dev->coef.c20 = ((uint32_t)reg_values[12] << 8) | (uint32_t)reg_values[13]; + dev->coef.c20 = two_complement_of(dev->coef.c20, 16); + + dev->coef.c21 = ((uint32_t)reg_values[14] << 8) | (uint32_t)reg_values[15]; + dev->coef.c21 = two_complement_of(dev->coef.c21, 16); + + dev->coef.c30 = ((uint32_t)reg_values[16] << 8) | (uint32_t)reg_values[17]; + dev->coef.c30 = two_complement_of(dev->coef.c30, 16); +fail: + return err; + +} + +esp_err_t dps310_get_mode(dps310_t *dev, dps310_mode_t *mode) +{ + return _read_reg_mask(&dev->i2c_dev, DPS310_REG_MEAS_CFG, DPS310_REG_MEAS_CFG_MEAS_CTRL_MASK, (uint8_t *)mode); +} + +esp_err_t dps310_set_mode(dps310_t *dev, dps310_mode_t mode) +{ + return _update_reg(&dev->i2c_dev, DPS310_REG_MEAS_CFG, DPS310_REG_MEAS_CFG_MEAS_CTRL_MASK, mode); +} + +esp_err_t dps310_flush_fifo(dps310_t *dev) +{ + uint8_t value = DPS310_FIFO_FLUSH_VALUE; + + CHECK_ARG(dev); + return _write_reg(&dev->i2c_dev, DPS310_REG_RESET, &value); +} + +esp_err_t dps310_enable_fifo(dps310_t *dev, bool enable) +{ + esp_err_t err = ESP_FAIL; + + CHECK_ARG(dev); + if (!enable) + { + err = dps310_flush_fifo(dev); + if (err != ESP_OK) + { + ESP_LOGE(TAG, "dps310_flush_fifo(): %s", esp_err_to_name(err)); + goto fail; + } + } + err = _update_reg(&dev->i2c_dev, DPS310_REG_CFG_REG, DPS310_REG_CFG_REG_FIFO_EN_MASK, enable ? 1 : 0); + if (err != ESP_OK) + { + ESP_LOGE(TAG, "_update_reg(): %s", esp_err_to_name(err)); + goto fail; + } +fail: + return err; +} + +esp_err_t dps310_read_raw(dps310_t *dev, uint8_t reg, int32_t *value) +{ + uint8_t reg_values[DPS310_REG_SENSOR_VALUE_LEN] = {0}; + esp_err_t err = ESP_FAIL; + + CHECK_ARG(dev && value); + if (reg != DPS310_REG_TMP_B2 && reg != DPS310_REG_PRS_B2) + { + err = ESP_ERR_INVALID_ARG; + goto fail; + } + err = i2c_dev_read_reg(&dev->i2c_dev, reg, reg_values, DPS310_REG_SENSOR_VALUE_LEN); + if (err != ESP_OK) + { + ESP_LOGE(TAG, "i2c_dev_read_reg(): %s", esp_err_to_name(err)); + goto fail; + } + *value = ((uint32_t)reg_values[0] << 16) | ((uint32_t)reg_values[1] << 8) | (uint32_t)reg_values[2]; + *value = two_complement_of(*value, DPS310_REG_SENSOR_VALUE_LEN * 8); + ESP_LOGD(TAG, "raw value in %s resister: %" PRIi32, reg == DPS310_REG_TMP_B2 ? "temperature" : "pressure", *value); +fail: + return err; +} + +/* calcurate and return kT, or kP. */ +static float raw_to_scaled(int32_t raw, uint8_t rate) +{ + int32_t k = 0; + + assert(rate <= N_SCALE_FACTORS - 1); + k = scale_factors[rate]; + ESP_LOGD(TAG, "scale_factor: %" PRIi32, k); + + /* Traw_sc = Traw / kT */ + assert(k != 0); + return (float)raw / (float)k; +} + +static float compensate_temp(dps310_t *dev, uint32_t T_raw, uint8_t rate) +{ + + /* 4.9.2 How to Calculate Compensated Temperature Values */ + float result = 0; + float T_raw_scaled = 0; + + CHECK_ARG(dev); + T_raw_scaled = raw_to_scaled(T_raw, rate); + + /* Tcomp (°C) = c0 * 0.5 + c1 * Traw_sc */ + result = ((float)dev->coef.c0 * 0.5) + ((float)dev->coef.c1 * T_raw_scaled); + return result; +} + +static float compensate_pressure(dps310_t *dev, int32_t T_raw, int32_t T_rate, int32_t P_raw, int32_t P_rate) +{ + + /* 4.9.1 How to Calculate Compensated Pressure Values */ + float T_raw_scaled = 0; + float P_raw_scaled = 0; + + CHECK_ARG(dev); + + T_raw_scaled = raw_to_scaled(T_raw, T_rate); + P_raw_scaled = raw_to_scaled(P_raw, P_rate); + + /* Pcomp(Pa) = c00 + * + Praw_sc * (c10 + Praw_sc * (c20 + Praw_sc * c30)) + * + Traw_sc * c01 + * + Traw_sc * Praw_sc * (c11 + Praw_sc * c21) + */ + return (float)dev->coef.c00 + + P_raw_scaled * ((float)dev->coef.c10 + P_raw_scaled * ((float)dev->coef.c20 + P_raw_scaled * (float)dev->coef.c30)) + + T_raw_scaled * (float)dev->coef.c01 + + T_raw_scaled * P_raw_scaled * ((float)dev->coef.c11 + P_raw_scaled * (float)dev->coef.c21); +} + +esp_err_t dps310_read_pressure(dps310_t *dev, float *pressure) +{ + esp_err_t err = ESP_FAIL; + int32_t T_raw = 0; + int32_t P_raw = 0; + dps310_tmp_oversampling_t T_rate = 0; + dps310_pm_oversampling_t P_rate = 0; + + CHECK_ARG(dev && pressure); + err = dps310_get_oversampling_t(dev, &T_rate); + if (err != ESP_OK) + { + ESP_LOGE(TAG, "dps310_get_oversampling_t(): %s", esp_err_to_name(err)); + goto fail; + } + + err = dps310_get_oversampling_p(dev, &P_rate); + if (err != ESP_OK) + { + ESP_LOGE(TAG, "dps310_get_oversampling_p(): %s", esp_err_to_name(err)); + goto fail; + } + + err = dps310_read_raw(dev, DPS310_REG_TMP_B2, &T_raw); + if (err != ESP_OK) + { + ESP_LOGE(TAG, "dps310_read_raw(): %s", esp_err_to_name(err)); + goto fail; + } + err = dps310_read_raw(dev, DPS310_REG_PRS_B2, &P_raw); + if (err != ESP_OK) + { + ESP_LOGE(TAG, "dps310_read_raw(): %s", esp_err_to_name(err)); + goto fail; + } + *pressure = compensate_pressure(dev, T_raw, T_rate, P_raw, P_rate); +fail: + return err; +} + +esp_err_t dps310_read_pressure_wait(dps310_t *dev, uint16_t delay_ms, uint8_t max_attempt, float *pressure) +{ + esp_err_t err = ESP_FAIL; + + CHECK_ARG(dev && pressure); + err = _wait_for_reg_bits(&dev->i2c_dev, DPS310_REG_MEAS_CFG, DPS310_REG_MEAS_CFG_PRS_RDY_MASK, 1, max_attempt, delay_ms); + if (err != ESP_OK) + { + ESP_LOGE(TAG, "_wait_for_reg_bits(): %s", esp_err_to_name(err)); + goto fail; + } + err = dps310_read_pressure(dev, pressure); + if (err != ESP_OK) + { + ESP_LOGE(TAG, "dps310_read_pressure(): %s", esp_err_to_name(err)); + goto fail; + } +fail: + return err; +} + +esp_err_t dps310_read_temp(dps310_t *dev, float *temperature) +{ + esp_err_t err = ESP_FAIL; + int32_t T_raw = 0; + dps310_tmp_oversampling_t rate = 0; + + CHECK_ARG(dev && temperature); + err = dps310_get_oversampling_t(dev, &rate); + if (err != ESP_OK) + { + ESP_LOGE(TAG, "dps310_get_oversampling_t(): %s", esp_err_to_name(err)); + goto fail; + } + err = dps310_read_raw(dev, DPS310_REG_TMP_B2, &T_raw); + if (err != ESP_OK) + { + ESP_LOGE(TAG, "dps310_read_raw(): %s", esp_err_to_name(err)); + goto fail; + } + + /* XXX when latest t_raw is available, always keep it in dev as a cache */ + dev->t_raw = T_raw; + *temperature = compensate_temp(dev, dev->t_raw, rate); +fail: + return err; +} + +esp_err_t dps310_read_temp_wait(dps310_t *dev, uint16_t delay_ms, uint8_t max_attempt, float *temperature) +{ + esp_err_t err = ESP_FAIL; + + CHECK_ARG(dev && temperature); + err = _wait_for_reg_bits(&dev->i2c_dev, DPS310_REG_MEAS_CFG, DPS310_REG_MEAS_CFG_TMP_RDY_MASK, 1, max_attempt, delay_ms); + if (err != ESP_OK) + { + ESP_LOGE(TAG, "_wait_for_reg_bits(): %s", esp_err_to_name(err)); + goto fail; + } + err = dps310_read_temp(dev, temperature); + if (err != ESP_OK) + { + ESP_LOGE(TAG, "dps310_read_temp(): %s", esp_err_to_name(err)); + goto fail; + } +fail: + return err; +} + +esp_err_t dps310_is_ready_for(dps310_t *dev, uint8_t reg, uint8_t mask, bool *ready) +{ + esp_err_t err = ESP_FAIL; + uint8_t reg_value = 0; + + CHECK_ARG(dev && ready); + + err = _read_reg_mask(&dev->i2c_dev, reg, mask, ®_value); + if (err != ESP_OK) + { + ESP_LOGE(TAG, "_read_reg_mask(): %s", esp_err_to_name(err)); + goto fail; + } + *ready = reg_value == 1 ? true : false; +fail: + return err; +} + +esp_err_t dps310_is_ready_for_coef(dps310_t *dev, bool *ready) +{ + CHECK_ARG(dev && ready); + return dps310_is_ready_for(dev, DPS310_REG_MEAS_CFG, DPS310_REG_MEAS_CFG_COEF_RDY_MASK, ready); +} + +esp_err_t dps310_is_ready_for_sensor(dps310_t *dev, bool *ready) +{ + CHECK_ARG(dev && ready); + return dps310_is_ready_for(dev, DPS310_REG_MEAS_CFG, DPS310_REG_MEAS_CFG_SENSOR_RDY_MASK, ready); +} + +esp_err_t dps310_is_ready_for_temp(dps310_t *dev, bool *ready) +{ + CHECK_ARG(dev && ready); + return dps310_is_ready_for(dev, DPS310_REG_MEAS_CFG, DPS310_REG_MEAS_CFG_TMP_RDY_MASK, ready); +} + +esp_err_t dps310_is_ready_for_pressure(dps310_t *dev, bool *ready) +{ + CHECK_ARG(dev && ready); + return dps310_is_ready_for(dev, DPS310_REG_MEAS_CFG, DPS310_REG_MEAS_CFG_PRS_RDY_MASK, ready); +} + +static inline bool dps310_is_pressure_result(int32_t data) +{ + return (uint8_t)data & 0x01; +} + +static inline bool dps310_is_temp_result(int32_t data) +{ + return !dps310_is_pressure_result(data); +} + +static esp_err_t dps310_read_reg_sensor_raw(dps310_t *dev, uint8_t reg, int32_t *value) +{ + uint8_t reg_values[DPS310_REG_SENSOR_VALUE_LEN] = {0}; + esp_err_t err = ESP_FAIL; + + CHECK_ARG(dev && value); + if (reg != DPS310_REG_TMP_B2 && reg != DPS310_REG_PRS_B2) + { + err = ESP_ERR_INVALID_ARG; + goto fail; + } + err = i2c_dev_read_reg(&dev->i2c_dev, reg, reg_values, DPS310_REG_SENSOR_VALUE_LEN); + *value = (uint32_t)reg_values[0] << 16 | (uint32_t)reg_values[1] << 8 | (uint32_t)reg_values[2]; + ESP_LOGD(TAG, "reg_values: %" PRIi32, *value); + if (err != ESP_OK) + { + ESP_LOGE(TAG, "i2c_dev_read_reg(): %s", esp_err_to_name(err)); + goto fail; + } +fail: + return err; +} + +esp_err_t dps310_is_fifo_empty(dps310_t *dev, bool *result) +{ + esp_err_t err = ESP_FAIL; + uint8_t value = 0; + + CHECK_ARG(dev && result); + err = _read_reg_mask(&dev->i2c_dev, DPS310_REG_FIFO_STS, DPS310_REG_FIFO_STS_FIFO_EMPTY_MASK, &value); + if (err != ESP_OK) + { + ESP_LOGE(TAG, "_read_reg_mask(): %s", esp_err_to_name(err)); + goto fail; + } + *result = value == 1 ? true : false; + +fail: + return err; + +} + +esp_err_t dps310_read_fifo(dps310_t *dev, dps310_fifo_measurement_t *measurement) +{ + int32_t raw_value = 0; + esp_err_t err = ESP_OK; + + CHECK_ARG(dev && measurement); + + /* Read a measurement from FIFO. to compensate the value, additional + * parameters, such as t_rate, are necessary. Use cached parameters in the + * device descriptor so that the value can be returned by only one I2C + * reading. This means that the driver does not work in multi-master + * configuration. As long as cached parameters are in sync with the real + * values in registers, the returned value is always correct even if a + * parameter is modified during the background mode. + */ + err = dps310_read_reg_sensor_raw(dev, DPS310_REG_FIFO, &raw_value); + if (err != ESP_OK) + { + ESP_LOGE(TAG, "dps310_read_reg_sensor_raw(): %s", esp_err_to_name(err)); + goto fail; + } + measurement->type = dps310_is_pressure_result(raw_value) ? DPS310_MEASUREMENT_PRESSURE : DPS310_MEASUREMENT_TEMPERATURE; + raw_value = two_complement_of(raw_value, 24); + if (raw_value == DPS310_FIFO_EMPTY) + { + measurement->type = DPS310_MEASUREMENT_EMPTY; + } + + switch (measurement->type) + { + case DPS310_MEASUREMENT_TEMPERATURE: + ESP_LOGD(TAG, "DPS310_MEASUREMENT_TEMPERATURE: raw_value: %" PRIi32, raw_value); + measurement->result = compensate_temp(dev, raw_value, dev->t_rate); + + /* XXX when new t_raw is available, always keep it in dev as a + * cache */ + dev->t_raw = raw_value; + break; + ;; + case DPS310_MEASUREMENT_PRESSURE: + ESP_LOGD(TAG, "DPS310_MEASUREMENT_PRESSURE: raw_value: %" PRIi32, raw_value); + measurement->result = compensate_pressure(dev, dev->t_raw, dev->t_rate, raw_value, dev->p_rate); + break; + ;; + case DPS310_MEASUREMENT_EMPTY: + ESP_LOGD(TAG, "DPS310_MEASUREMENT_EMPTY: raw_value %" PRIi32, raw_value); + measurement->result = 0; + break; + default: + + /* NOT REACHED */ + abort(); + ;; + } + +fail: + return err; +} + +inline esp_err_t dps310_backgorund_start(dps310_t *dev, dps310_mode_t mode) +{ + CHECK_ARG(dev); + return dps310_set_mode(dev, mode); +} + +inline esp_err_t dps310_backgorund_stop(dps310_t *dev) +{ + CHECK_ARG(dev); + return dps310_set_mode(dev, DPS310_MODE_STANDBY); +} + +static int32_t dps310_calc_sea_level_pressure(float pressure, float altitude) +{ + return (int32_t)(pressure / pow(1.0 - altitude / 44330, 5.255)); +} + +static inline float calc_altitude(float pressure, float pressure_s) +{ + return 44330 * (1.0 - pow(pressure / pressure_s, 0.1903)); +} + +esp_err_t dps310_calibrate_altitude(dps310_t *dev, float altitude_real) +{ + esp_err_t err = ESP_FAIL; + float pressure = 0; + float temperature = 0; + float altitude_guess = 0; + dps310_pm_oversampling_t orig_oversmapling_p = 0; + dps310_tmp_oversampling_t orig_oversmapling_t = 0; + + CHECK_ARG(dev); + + /* keep original oversampling values */ + err = dps310_get_oversampling_p(dev, &orig_oversmapling_p); + if (err != ESP_OK) + { + ESP_LOGE(TAG, "dps310_get_oversampling_p(): %s", esp_err_to_name(err)); + goto get_oversampling_fail; + } + err = dps310_get_oversampling_t(dev, &orig_oversmapling_t); + if (err != ESP_OK) + { + ESP_LOGE(TAG, "dps310_get_oversampling_t(): %s", esp_err_to_name(err)); + goto get_oversampling_fail; + } + + /* modify oversampling values for accuracy */ + err = dps310_set_oversampling_p(dev, DPS310_PM_PRC_64); + if (err != ESP_OK) + { + ESP_LOGE(TAG, "dps310_set_oversampling_p(): %s", esp_err_to_name(err)); + goto fail; + } + err = dps310_set_oversampling_t(dev, DPS310_TMP_PRC_64); + if (err != ESP_OK) + { + ESP_LOGE(TAG, "dps310_set_oversampling_t(): %s", esp_err_to_name(err)); + goto fail; + } + + /* read temperature once in command mode */ + err = dps310_set_mode(dev, DPS310_MODE_COMMAND_TEMPERATURE); + if (err != ESP_OK) + { + ESP_LOGE(TAG, "dps310_set_mode(): %s", esp_err_to_name(err)); + goto fail; + } + err = dps310_read_temp_wait(dev, 100, 10, &temperature); + if (err != ESP_OK) + { + ESP_LOGE(TAG, "dps310_read_temp(): %s", esp_err_to_name(err)); + goto fail; + } + + /* read pressure once in command mode */ + err = dps310_set_mode(dev, DPS310_MODE_COMMAND_PRESSURE); + if (err != ESP_OK) + { + ESP_LOGE(TAG, "dps310_set_mode(): %s", esp_err_to_name(err)); + goto fail; + } + err = dps310_read_pressure_wait(dev, 100, 10, &pressure); + if (err != ESP_OK) + { + ESP_LOGE(TAG, "dps310_read_pressure(): %s", esp_err_to_name(err)); + goto fail; + } + + /* calibrate offset */ + dev->pressure_s = dps310_calc_sea_level_pressure(pressure, altitude_real); + altitude_guess = calc_altitude(pressure, dev->pressure_s); + dev->offset = altitude_real - altitude_guess; + + ESP_LOGI(TAG, "Calibration result:"); + ESP_LOGI(TAG, "\tPressure: %0.2f (Pa)", pressure); + ESP_LOGI(TAG, "\tCalculated Pressure at sea level: %0.2f (Pa)", dev->pressure_s); + ESP_LOGI(TAG, "\tCalculated altitude: %0.2f (m)", altitude_guess); + ESP_LOGI(TAG, "\tReal altitude: %0.2f (m)", altitude_real); + ESP_LOGI(TAG, "\tOffset: %0.2f (m)", dev->offset); +fail: + if (dps310_set_oversampling_t(dev, orig_oversmapling_t) != ESP_OK) + { + ESP_LOGW(TAG, "Restoring temperature oversampling failed"); + } + if (dps310_set_oversampling_p(dev, orig_oversmapling_p) != ESP_OK) + { + ESP_LOGW(TAG, "Restoring pressure oversampling failed"); + } +get_oversampling_fail: + return err; +} + + +static float pressure_to_altitude(float pressure, float pressure_s, float *altitude) +{ + esp_err_t err = ESP_FAIL; + + CHECK_ARG(altitude); + if (pressure_s == 0) + { + err = ESP_ERR_INVALID_ARG; + goto fail; + } + *altitude = 44330 * (1.0 - pow(pressure / pressure_s, 0.1903)); + err = ESP_OK; + +fail: + return err; +} + +esp_err_t dps310_calc_altitude(dps310_t *dev, float pressure, float *altitude) +{ + esp_err_t err = ESP_FAIL; + + CHECK_ARG(dev && altitude); + + err = pressure_to_altitude(pressure, dev->pressure_s, altitude); + if (err != ESP_OK) + { + ESP_LOGE(TAG, "pressure_to_altitude(): %s", esp_err_to_name(err)); + goto fail; + } + *altitude = *altitude + dev->offset; +fail: + return err; +} + +esp_err_t dps310_read_altitude(dps310_t *dev, float *altitude) +{ + esp_err_t err = ESP_FAIL; + float pressure = 0; + + CHECK_ARG(dev && altitude); + + err = dps310_read_pressure(dev, &pressure); + if (err != ESP_OK) + { + ESP_LOGE(TAG, "dps310_read_pressure(): %s", esp_err_to_name(err)); + goto fail; + } + err = dps310_calc_altitude(dev, pressure, altitude); + if (err != ESP_OK) + { + ESP_LOGE(TAG, "dps310_calc_altitude(): %s", esp_err_to_name(err)); + goto fail; + } + +fail: + return err; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/dps310/src/helper_i2c.c Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,176 @@ +/* + * Copyright (c) 2022 Tomoyuki Sakurai <y@trombik.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#if !defined(__DPS310__HELPER_I2C__H__) +#define __DPS310__HELPER_I2C__H__ + +/* standard headers */ +#include <inttypes.h> + +/* esp-idf headers */ +#include <freertos/FreeRTOS.h> +#include <freertos/task.h> +#include <esp_err.h> +#include <esp_log.h> +#include <i2cdev.h> + +/* private headers */ +#include "helper_macro.h" +#include "helper_i2c.h" + +#ifdef __cplusplus +extern "C" { +#endif + +static const char *TAG = "dps310/helper_i2c"; + +/** + * @brief Read a single byte from a 8-bit resister without locking, + */ +inline esp_err_t _read_reg_nolock(i2c_dev_t *dev, uint8_t reg, uint8_t *val); + +/** + * @brief Write a single byte to a 8-bit resister without locking. + */ +inline esp_err_t _write_reg_nolock(i2c_dev_t *dev, uint8_t reg, uint8_t val); + +/* count the number of trailing zero in a value, usually bitmasks. */ +static uint8_t _count_trailing_zero_bits(uint8_t v) +{ + uint8_t count = 0; + if (v) + { + v = (v ^ (v - 1)) >> 1; + for (count = 0; v; count++) + { + v >>= 1; + } + } + else + { + count = 8; + } + return count; +} + +esp_err_t _read_reg_nolock(i2c_dev_t *dev, uint8_t reg, uint8_t *val) +{ + return i2c_dev_read_reg(dev, reg, val, 1); +} + +esp_err_t _read_reg(i2c_dev_t *dev, uint8_t reg, uint8_t *val) +{ + CHECK_ARG(dev); + I2C_DEV_TAKE_MUTEX(dev); + I2C_DEV_CHECK(dev, _read_reg_nolock(dev, reg, val)); + I2C_DEV_GIVE_MUTEX(dev); + return ESP_OK; +} + +esp_err_t _read_reg_mask(i2c_dev_t *dev, uint8_t reg, uint8_t mask, uint8_t *val) +{ + uint8_t reg_value = 0; + + CHECK_ARG(dev && val); + + I2C_DEV_TAKE_MUTEX(dev); + I2C_DEV_CHECK(dev, _read_reg_nolock(dev, reg, ®_value)); + I2C_DEV_GIVE_MUTEX(dev); + *val = (reg_value & mask) >> _count_trailing_zero_bits(mask); + ESP_LOGV(TAG, "reg_value: %02X, val: %02X", reg_value, *val); + return ESP_OK; +} + +esp_err_t _write_reg_nolock(i2c_dev_t *dev, uint8_t reg, uint8_t val) +{ + return i2c_dev_write_reg(dev, reg, &val, 1); +} + +esp_err_t _update_reg_nolock(i2c_dev_t *dev, uint8_t reg, uint8_t mask, uint8_t val) +{ + uint8_t reg_value = 0; + uint8_t n_shift = 0; + + CHECK_ARG(dev); + CHECK(_read_reg_nolock(dev, reg, ®_value)); + + n_shift = _count_trailing_zero_bits(mask); + if (((reg_value >> n_shift) & val) == val) + { + ESP_LOGD(TAG, "register unchanged"); + } + else + { + reg_value = (reg_value & (~mask)) | (val << n_shift); + CHECK(_write_reg_nolock(dev, reg, reg_value)); + } + return ESP_OK; +} + +esp_err_t _write_reg(i2c_dev_t *dev, uint8_t reg, uint8_t *value) +{ + esp_err_t err = ESP_FAIL; + + CHECK_ARG(dev); + I2C_DEV_TAKE_MUTEX(dev); + err = _write_reg_nolock(dev, reg, *value); + I2C_DEV_GIVE_MUTEX(dev); + if (err != ESP_OK) + { + ESP_LOGE(TAG, "i2c_dev_write_reg(): %s", esp_err_to_name(err)); + } + return err; +} + +esp_err_t _update_reg(i2c_dev_t *dev, uint8_t reg, uint8_t mask, uint8_t val) +{ + CHECK_ARG(dev); + I2C_DEV_TAKE_MUTEX(dev); + I2C_DEV_CHECK(dev, _update_reg_nolock(dev, reg, mask, val)); + I2C_DEV_GIVE_MUTEX(dev); + return ESP_OK; +} + +esp_err_t _wait_for_reg_bits(i2c_dev_t *dev, uint8_t reg, uint8_t mask, uint8_t val, uint8_t max_attempt, uint16_t delay_ms) +{ + esp_err_t err = ESP_FAIL; + uint8_t reg_value = 0; + uint8_t attempts = 0; + + while (attempts < max_attempt) + { + attempts++; + err = _read_reg_mask(dev, reg, mask, ®_value); + if (err != ESP_OK) + { + ESP_LOGE(TAG, "_wait_for_reg_bits(): %s", esp_err_to_name(err)); + return err; + } + if (reg_value == val) + { + return ESP_OK; + } + vTaskDelay(pdMS_TO_TICKS(delay_ms)); + } + ESP_LOGE(TAG, "Timeout. delay_ms: %" PRIu16 "max_attempt: %" PRIu8, delay_ms, max_attempt); + return ESP_ERR_TIMEOUT; +} + +#ifdef __cplusplus +} +#endif + +#endif // __DPS310__HELPER_I2C__H__
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/ds1302/.eil.yml Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,27 @@ +--- +components: + - name: ds1302 + description: | + Driver for DS1302 RTC module + group: rtc + groups: [] + code_owners: + - name: UncleRus + depends: + - name: log + - name: esp_idf_lib_helpers + - name: freertos + - name: driver + thread_safe: no + targets: + - name: esp32 + - name: esp8266 + - name: esp32s2 + - name: esp32c3 + licenses: + - name: BSD-3 + copyrights: + - name: UncleRus + year: 2021 + - name: PavelM + year: 2016
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/ds1302/CMakeLists.txt Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,11 @@ +if(${IDF_TARGET} STREQUAL esp8266) + set(req esp8266 freertos log esp_idf_lib_helpers) +else() + set(req driver freertos log esp_idf_lib_helpers) +endif() + +idf_component_register( + SRCS ds1302.c + INCLUDE_DIRS . + REQUIRES ${req} +)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/ds1302/LICENSE Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,27 @@ +Copyright (c) 2016, 2019 Ruslan V. Uss <unclerus@gmail.com> +Copyright (c) 2016 Pavel Merzlyakov <merzlyakovpavel@gmail.com> + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, +this list of conditions and the following disclaimer in the documentation +and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of itscontributors +may be used to endorse or promote products derived from this software without +specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/ds1302/component.mk Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,7 @@ +COMPONENT_ADD_INCLUDEDIRS = . + +ifdef CONFIG_IDF_TARGET_ESP8266 +COMPONENT_DEPENDS = esp8266 freertos log esp_idf_lib_helpers +else +COMPONENT_DEPENDS = driver freertos log esp_idf_lib_helpers +endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/ds1302/ds1302.c Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,309 @@ +/* + * Copyright (c) 2016 Ruslan V. Uss <unclerus@gmail.com> + * Copyright (c) 2016 Pavel Merzlyakov <merzlyakovpavel@gmail.com> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of itscontributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file ds1302.h + * + * ESP-IDF driver for DS1302 RTC + * + * Ported from esp-open-rtos + * + * Copyright (c) 2016 Ruslan V. Uss <unclerus@gmail.com>\n + * Copyright (c) 2016 Pavel Merzlyakov <merzlyakovpavel@gmail.com> + * + * BSD Licensed as described in the file LICENSE + */ +#include <freertos/FreeRTOS.h> +#include <string.h> +#include <ets_sys.h> +#include <esp_idf_lib_helpers.h> +#include "ds1302.h" + +#define CH_REG 0x80 +#define WP_REG 0x8e + +#define CH_BIT (1 << 7) +#define WP_BIT (1 << 7) +#define HOUR12_BIT (1 << 7) +#define PM_BIT (1 << 5) + +#define CH_MASK ((uint8_t)(~CH_BIT)) +#define WP_MASK ((uint8_t)(~WP_BIT)) + +#define CLOCK_BURST 0xbe +#define RAM_BURST 0xfe + +#define SECONDS_MASK 0x7f +#define HOUR12_MASK 0x1f +#define HOUR24_MASK 0x3f + +#define GPIO_BIT(x) (1ULL << (x)) + +#define CHECK(x) do { esp_err_t __; if ((__ = x) != ESP_OK) return __; } while (0) +#define CHECK_ARG(VAL) do { if (!(VAL)) return ESP_ERR_INVALID_ARG; } while (0) + +#if HELPER_TARGET_IS_ESP32 +static portMUX_TYPE mux = portMUX_INITIALIZER_UNLOCKED; +#define PORT_ENTER_CRITICAL portENTER_CRITICAL(&mux) +#define PORT_EXIT_CRITICAL portEXIT_CRITICAL(&mux) +#else +#define PORT_ENTER_CRITICAL portENTER_CRITICAL() +#define PORT_EXIT_CRITICAL portEXIT_CRITICAL() +#endif + +#define CHECK_MUX(x) do { esp_err_t __; if ((__ = (x)) != ESP_OK) { PORT_EXIT_CRITICAL; return __; } } while (0) + +static uint8_t bcd2dec(uint8_t val) +{ + return (val >> 4) * 10 + (val & 0x0f); +} + +static uint8_t dec2bcd(uint8_t val) +{ + return ((val / 10) << 4) + (val % 10); +} + +inline static esp_err_t chip_enable(ds1302_t *dev) +{ + CHECK(gpio_set_level(dev->ce_pin, 1)); + ets_delay_us(4); + return ESP_OK; +} + +inline static esp_err_t chip_disable(ds1302_t *dev) +{ + return gpio_set_level(dev->ce_pin, 0); +} + +inline static esp_err_t prepare(ds1302_t *dev, gpio_mode_t mode) +{ + CHECK(gpio_set_direction(dev->io_pin, mode)); + CHECK(gpio_set_level(dev->sclk_pin, 0)); + return chip_enable(dev); +} + +inline static esp_err_t toggle_clock(ds1302_t *dev) +{ + CHECK(gpio_set_level(dev->sclk_pin, 1)); + ets_delay_us(1); + CHECK(gpio_set_level(dev->sclk_pin, 0)); + ets_delay_us(1); + return ESP_OK; +} + +static esp_err_t write_byte(ds1302_t *dev, uint8_t b) +{ + for (uint8_t i = 0; i < 8; i++) + { + CHECK(gpio_set_level(dev->io_pin, (b >> i) & 1)); + CHECK(toggle_clock(dev)); + } + return ESP_OK; +} + +static esp_err_t read_byte(ds1302_t *dev, uint8_t *b) +{ + *b = 0; + for (uint8_t i = 0; i < 8; i++) + { + *b |= gpio_get_level(dev->io_pin) << i; + CHECK(toggle_clock(dev)); + } + return ESP_OK; +} + +static esp_err_t read_register(ds1302_t *dev, uint8_t reg, uint8_t *val) +{ + PORT_ENTER_CRITICAL; + CHECK_MUX(prepare(dev, GPIO_MODE_OUTPUT)); + CHECK_MUX(write_byte(dev, reg | 0x01)); + CHECK_MUX(prepare(dev, GPIO_MODE_INPUT)); + CHECK_MUX(read_byte(dev, val)); + PORT_EXIT_CRITICAL; + return chip_disable(dev); +} + +static esp_err_t write_register(ds1302_t *dev, uint8_t reg, uint8_t val) +{ + PORT_ENTER_CRITICAL; + CHECK_MUX(prepare(dev, GPIO_MODE_OUTPUT)); + CHECK_MUX(write_byte(dev, reg)); + CHECK_MUX(write_byte(dev, val)); + PORT_EXIT_CRITICAL; + return chip_disable(dev); +} + +static esp_err_t burst_read(ds1302_t *dev, uint8_t reg, uint8_t *dst, uint8_t len) +{ + PORT_ENTER_CRITICAL; + CHECK_MUX(prepare(dev, GPIO_MODE_OUTPUT)); + CHECK_MUX(write_byte(dev, reg | 0x01)); + CHECK_MUX(prepare(dev, GPIO_MODE_INPUT)); + for (uint8_t i = 0; i < len; i++, dst++) + CHECK_MUX(read_byte(dev, dst)); + PORT_EXIT_CRITICAL; + return chip_disable(dev); +} + +static esp_err_t burst_write(ds1302_t *dev, uint8_t reg, uint8_t *src, uint8_t len) +{ + PORT_ENTER_CRITICAL; + CHECK_MUX(prepare(dev, GPIO_MODE_OUTPUT)); + CHECK_MUX(write_byte(dev, reg)); + for (uint8_t i = 0; i < len; i++, src++) + CHECK_MUX(write_byte(dev, *src)); + PORT_EXIT_CRITICAL; + return chip_disable(dev); +} + +static esp_err_t update_register(ds1302_t *dev, uint8_t reg, uint8_t mask, uint8_t val) +{ + uint8_t r; + CHECK(read_register(dev, reg, &r)); + return write_register(dev, reg, (r & mask) | val); +} + +/////////////////////////////////////////////////////////////////////////////// + +esp_err_t ds1302_init(ds1302_t *dev) +{ + CHECK_ARG(dev); + + gpio_config_t io_conf; + memset(&io_conf, 0, sizeof(gpio_config_t)); + io_conf.mode = GPIO_MODE_OUTPUT; + io_conf.pin_bit_mask = + GPIO_BIT(dev->ce_pin) | + GPIO_BIT(dev->io_pin) | + GPIO_BIT(dev->sclk_pin); + CHECK(gpio_config(&io_conf)); + + bool r; + CHECK(ds1302_is_running(dev, &r)); + dev->ch = !r; + + return ESP_OK; +} + +esp_err_t ds1302_start(ds1302_t *dev, bool start) +{ + CHECK_ARG(dev); + + CHECK(update_register(dev, CH_REG, CH_MASK, start ? 0 : CH_BIT)); + dev->ch = !start; + + return ESP_OK; +} + +esp_err_t ds1302_is_running(ds1302_t *dev, bool *running) +{ + CHECK_ARG(dev && running); + + uint8_t r; + CHECK(read_register(dev, CH_REG, &r)); + *running = !(r & CH_BIT); + dev->ch = !*running; + + return ESP_OK; +} + +esp_err_t ds1302_set_write_protect(ds1302_t *dev, bool wp) +{ + CHECK_ARG(dev); + + return update_register(dev, WP_REG, WP_MASK, wp ? WP_BIT : 0); +} + +esp_err_t ds1302_get_write_protect(ds1302_t *dev, bool *wp) +{ + CHECK_ARG(dev && wp); + + uint8_t r; + CHECK(read_register(dev, WP_REG, &r)); + *wp = (r & WP_BIT) != 0; + + return ESP_OK; +} + +esp_err_t ds1302_get_time(ds1302_t *dev, struct tm *time) +{ + CHECK_ARG(dev && time); + + uint8_t buf[7]; + CHECK(burst_read(dev, CLOCK_BURST, buf, 7)); + + time->tm_sec = bcd2dec(buf[0] & SECONDS_MASK); + time->tm_min = bcd2dec(buf[1]); + if (buf[2] & HOUR12_BIT) + { + // RTC in 12-hour mode + time->tm_hour = bcd2dec(buf[2] & HOUR12_MASK) - 1; + if (buf[2] & PM_BIT) + time->tm_hour += 12; + } + else time->tm_hour = bcd2dec(buf[2] & HOUR24_MASK); + time->tm_mday = bcd2dec(buf[3]); + time->tm_mon = bcd2dec(buf[4]) - 1; + time->tm_wday = bcd2dec(buf[5]) - 1; + time->tm_year = bcd2dec(buf[6]) + 100; + + return ESP_OK; +} + +esp_err_t ds1302_set_time(ds1302_t *dev, const struct tm *time) +{ + CHECK_ARG(dev && time); + + uint8_t buf[8] = { + dec2bcd(time->tm_sec) | (dev->ch ? CH_BIT : 0), + dec2bcd(time->tm_min), + dec2bcd(time->tm_hour), + dec2bcd(time->tm_mday), + dec2bcd(time->tm_mon + 1), + dec2bcd(time->tm_wday + 1), + dec2bcd(time->tm_year - 100), + 0 + }; + return burst_write(dev, CLOCK_BURST, buf, 8); +} + +esp_err_t ds1302_read_sram(ds1302_t *dev, uint8_t offset, void *buf, uint8_t len) +{ + CHECK_ARG(dev && buf && len); + CHECK_ARG(offset + len <= DS1302_RAM_SIZE); + + return burst_read(dev, RAM_BURST, (uint8_t *)buf, len); +} + +esp_err_t ds1302_write_sram(ds1302_t *dev, uint8_t offset, void *buf, uint8_t len) +{ + CHECK_ARG(dev && buf && len); + CHECK_ARG(offset + len <= DS1302_RAM_SIZE); + + return burst_write(dev, RAM_BURST, (uint8_t *)buf, len); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/ds1302/ds1302.h Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,160 @@ +/* + * Copyright (c) 2016 Ruslan V. Uss <unclerus@gmail.com> + * Copyright (c) 2016 Pavel Merzlyakov <merzlyakovpavel@gmail.com> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of itscontributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file ds1302.h + * @defgroup ds1302 ds1302 + * @{ + * + * ESP-IDF driver for DS1302 RTC + * + * Ported from esp-open-rtos + * + * Copyright (c) 2016 Ruslan V. Uss <unclerus@gmail.com>\n + * Copyright (c) 2016 Pavel Merzlyakov <merzlyakovpavel@gmail.com> + * + * BSD Licensed as described in the file LICENSE + */ +#ifndef __DS1302_H__ +#define __DS1302_H__ + +#include <stdbool.h> +#include <driver/gpio.h> +#include <time.h> +#include <esp_err.h> + +#ifdef __cplusplus +extern "C" { +#endif + +#define DS1302_RAM_SIZE 31 + +/** + * Device descriptor + */ +typedef struct +{ + gpio_num_t ce_pin; //!< GPIO pin connected to CE + gpio_num_t io_pin; //!< GPIO pin connected to chip I/O + gpio_num_t sclk_pin; //!< GPIO pin connected to SCLK + bool ch; //!< true if clock is halted +} ds1302_t; + +/** + * @brief Initialize device + * + * @param dev Device descriptor + * @return `ESP_OK` on success + */ +esp_err_t ds1302_init(ds1302_t *dev); + +/** + * @brief Start/stop clock + * + * @param dev Device descriptor + * @param start Start clock if true + * @return `ESP_OK` on success + */ +esp_err_t ds1302_start(ds1302_t *dev, bool start); + +/** + * @brief Get current clock state + * + * @param dev Device descriptor + * @param[out] running true if clock running + * @return `ESP_OK` on success + */ +esp_err_t ds1302_is_running(ds1302_t *dev, bool *running); + +/** + * @brief Enable/disable write protection + * + * @param dev Device descriptor + * @param wp Set RTC write-protected if true + * @return `ESP_OK` on success + */ +esp_err_t ds1302_set_write_protect(ds1302_t *dev, bool wp); + +/** + * @brief Get write protection status + * + * @param dev Device descriptor + * @param[out] wp true if RTC write-protected + * @return `ESP_OK` on success + */ +esp_err_t ds1302_get_write_protect(ds1302_t *dev, bool *wp); + +/** + * @brief Get current time + * + * @param dev Device descriptor + * @param[out] time Current time + * @return `ESP_OK` on success + */ +esp_err_t ds1302_get_time(ds1302_t *dev, struct tm *time); + +/** + * @brief Set time to RTC + * + * @param dev Device descriptor + * @param time Time + * @return `ESP_OK` on success + */ +esp_err_t ds1302_set_time(ds1302_t *dev, const struct tm *time); + +/** + * @brief Read RAM contents into the buffer + * + * @param dev Device descriptor + * @param offset Start byte, 0..55 + * @param[out] buf Buffer + * @param len Bytes to read, 1..56 + * + * @return `ESP_OK` on success + */ +esp_err_t ds1302_read_sram(ds1302_t *dev, uint8_t offset, void *buf, uint8_t len); + +/** + * @brief Write buffer to RTC RAM + * + * @param dev Device descriptor + * @param offset Start byte, 0..55 + * @param buf Buffer + * @param len Bytes to write, 1..56 + * + * @return `ESP_OK` on success + */ +esp_err_t ds1302_write_sram(ds1302_t *dev, uint8_t offset, void *buf, uint8_t len); + +#ifdef __cplusplus +} +#endif + +/**@}*/ + +#endif /* __DS1302_H__ */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/ds1307/.eil.yml Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,24 @@ +--- +components: + - name: ds1307 + description: | + Driver for DS1307 RTC module + group: rtc + groups: [] + code_owners: + - name: UncleRus + depends: + - name: i2cdev + - name: log + - name: esp_idf_lib_helpers + thread_safe: yes + targets: + - name: esp32 + - name: esp8266 + - name: esp32s2 + - name: esp32c3 + licenses: + - name: BSD-3 + copyrights: + - name: UncleRus + year: 2016
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/ds1307/CMakeLists.txt Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,5 @@ +idf_component_register( + SRCS ds1307.c + INCLUDE_DIRS . + REQUIRES i2cdev log esp_idf_lib_helpers +)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/ds1307/LICENSE Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,26 @@ +Copyright 2016, 2018 Ruslan V. Uss <unclerus@gmail.com> + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, +this list of conditions and the following disclaimer in the documentation +and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of itscontributors +may be used to endorse or promote products derived from this software without +specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/ds1307/component.mk Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,2 @@ +COMPONENT_ADD_INCLUDEDIRS = . +COMPONENT_DEPENDS = i2cdev log esp_idf_lib_helpers
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/ds1307/ds1307.c Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,269 @@ +/* + * Copyright (c) 2016 Ruslan V. Uss <unclerus@gmail.com> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of itscontributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file ds1307.c + * + * ESP-IDF driver for DS1307 real-time clock + * + * Ported from esp-open-rtos + * + * Copyright (c) 2016, 2018 Ruslan V. Uss <unclerus@gmail.com> + * + * BSD Licensed as described in the file LICENSE + */ +#include <esp_err.h> +#include <esp_idf_lib_helpers.h> +#include "ds1307.h" + +#define I2C_FREQ_HZ 400000 + +#define RAM_SIZE 56 + +#define TIME_REG 0 +#define CONTROL_REG 7 +#define RAM_REG 8 + +#define CH_BIT (1 << 7) +#define HOUR12_BIT (1 << 6) +#define PM_BIT (1 << 5) +#define SQWE_BIT (1 << 4) +#define OUT_BIT (1 << 7) + +#define CH_MASK 0x7f +#define SECONDS_MASK 0x7f +#define HOUR12_MASK 0x1f +#define HOUR24_MASK 0x3f +#define SQWEF_MASK 0xfc +#define SQWE_MASK 0xef +#define OUT_MASK 0x7f + +#define CHECK_ARG(ARG) do { if (!(ARG)) return ESP_ERR_INVALID_ARG; } while (0) + +static uint8_t bcd2dec(uint8_t val) +{ + return (val >> 4) * 10 + (val & 0x0f); +} + +static uint8_t dec2bcd(uint8_t val) +{ + return ((val / 10) << 4) + (val % 10); +} + +static esp_err_t update_register(i2c_dev_t *dev, uint8_t reg, uint8_t mask, uint8_t val) +{ + CHECK_ARG(dev); + + uint8_t old; + + I2C_DEV_TAKE_MUTEX(dev); + I2C_DEV_CHECK(dev, i2c_dev_read_reg(dev, reg, &old, 1)); + uint8_t buf = (old & mask) | val; + esp_err_t res = i2c_dev_write_reg(dev, reg, &buf, 1); + I2C_DEV_GIVE_MUTEX(dev); + + return res; +} + +esp_err_t ds1307_init_desc(i2c_dev_t *dev, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio) +{ + CHECK_ARG(dev); + + dev->port = port; + dev->addr = DS1307_ADDR; + dev->cfg.sda_io_num = sda_gpio; + dev->cfg.scl_io_num = scl_gpio; +#if HELPER_TARGET_IS_ESP32 + dev->cfg.master.clk_speed = I2C_FREQ_HZ; +#endif + return i2c_dev_create_mutex(dev); +} + +esp_err_t ds1307_free_desc(i2c_dev_t *dev) +{ + CHECK_ARG(dev); + + return i2c_dev_delete_mutex(dev); +} + +esp_err_t ds1307_start(i2c_dev_t *dev, bool start) +{ + return update_register(dev, TIME_REG, CH_MASK, start ? 0 : CH_BIT); +} + +esp_err_t ds1307_is_running(i2c_dev_t *dev, bool *running) +{ + CHECK_ARG(dev && running); + + uint8_t val; + + I2C_DEV_TAKE_MUTEX(dev); + I2C_DEV_CHECK(dev, i2c_dev_read_reg(dev, TIME_REG, &val, 1)); + I2C_DEV_GIVE_MUTEX(dev); + + *running = val & CH_BIT ? false : true; + + return ESP_OK; +} + +esp_err_t ds1307_get_time(i2c_dev_t *dev, struct tm *time) +{ + CHECK_ARG(dev && time); + + uint8_t buf[7]; + + I2C_DEV_TAKE_MUTEX(dev); + I2C_DEV_CHECK(dev, i2c_dev_read_reg(dev, TIME_REG, buf, 7)); + I2C_DEV_GIVE_MUTEX(dev); + + time->tm_sec = bcd2dec(buf[0] & SECONDS_MASK); + time->tm_min = bcd2dec(buf[1]); + if (buf[2] & HOUR12_BIT) + { + // RTC in 12-hour mode + time->tm_hour = bcd2dec(buf[2] & HOUR12_MASK) - 1; + if (buf[2] & PM_BIT) + time->tm_hour += 12; + } + else + time->tm_hour = bcd2dec(buf[2] & HOUR24_MASK); + time->tm_wday = bcd2dec(buf[3]) - 1; + time->tm_mday = bcd2dec(buf[4]); + time->tm_mon = bcd2dec(buf[5]) - 1; + time->tm_year = bcd2dec(buf[6]) + 100; + + return ESP_OK; +} + +esp_err_t ds1307_set_time(i2c_dev_t *dev, const struct tm *time) +{ + CHECK_ARG(dev && time); + + uint8_t buf[7] = { + dec2bcd(time->tm_sec), + dec2bcd(time->tm_min), + dec2bcd(time->tm_hour), + dec2bcd(time->tm_wday + 1), + dec2bcd(time->tm_mday), + dec2bcd(time->tm_mon + 1), + dec2bcd(time->tm_year - 100) + }; + + I2C_DEV_TAKE_MUTEX(dev); + I2C_DEV_CHECK(dev, i2c_dev_write_reg(dev, TIME_REG, buf, sizeof(buf))); + I2C_DEV_GIVE_MUTEX(dev); + + return ESP_OK; +} + +esp_err_t ds1307_enable_squarewave(i2c_dev_t *dev, bool enable) +{ + return update_register(dev, CONTROL_REG, SQWE_MASK, enable ? SQWE_BIT : 0); +} + +esp_err_t ds1307_is_squarewave_enabled(i2c_dev_t *dev, bool *sqw_en) +{ + CHECK_ARG(dev && sqw_en); + + uint8_t val; + + I2C_DEV_TAKE_MUTEX(dev); + I2C_DEV_CHECK(dev, i2c_dev_read_reg(dev, CONTROL_REG, &val, 1)); + I2C_DEV_GIVE_MUTEX(dev); + + *sqw_en = val & SQWE_BIT; + + return ESP_OK; +} + +esp_err_t ds1307_set_squarewave_freq(i2c_dev_t *dev, ds1307_squarewave_freq_t freq) +{ + return update_register(dev, CONTROL_REG, SQWEF_MASK, freq); +} + +esp_err_t ds1307_get_squarewave_freq(i2c_dev_t *dev, ds1307_squarewave_freq_t *sqw_freq) +{ + CHECK_ARG(dev && sqw_freq); + + uint8_t val; + + I2C_DEV_TAKE_MUTEX(dev); + I2C_DEV_CHECK(dev, i2c_dev_read_reg(dev, CONTROL_REG, &val, 1)); + I2C_DEV_GIVE_MUTEX(dev); + + *sqw_freq = val & ~SQWEF_MASK; + + return ESP_OK; +} + +esp_err_t ds1307_get_output(i2c_dev_t *dev, bool *out) +{ + CHECK_ARG(dev && out); + + uint8_t val; + + I2C_DEV_TAKE_MUTEX(dev); + I2C_DEV_CHECK(dev, i2c_dev_read_reg(dev, CONTROL_REG, &val, 1)); + I2C_DEV_GIVE_MUTEX(dev); + + *out = val & OUT_BIT; + + return ESP_OK; +} + +esp_err_t ds1307_set_output(i2c_dev_t *dev, bool value) +{ + return update_register(dev, CONTROL_REG, OUT_MASK, value ? OUT_BIT : 0); +} + +esp_err_t ds1307_read_ram(i2c_dev_t *dev, uint8_t offset, uint8_t *buf, uint8_t len) +{ + CHECK_ARG(dev && buf); + + if (offset + len > RAM_SIZE) + return ESP_ERR_NO_MEM; + + I2C_DEV_TAKE_MUTEX(dev); + I2C_DEV_CHECK(dev, i2c_dev_read_reg(dev, RAM_REG + offset, buf, len)); + I2C_DEV_GIVE_MUTEX(dev); + + return ESP_OK; +} + +esp_err_t ds1307_write_ram(i2c_dev_t *dev, uint8_t offset, uint8_t *buf, uint8_t len) +{ + CHECK_ARG(dev && buf); + + if (offset + len > RAM_SIZE) + return ESP_ERR_NO_MEM; + + I2C_DEV_TAKE_MUTEX(dev); + I2C_DEV_CHECK(dev, i2c_dev_write_reg(dev, RAM_REG + offset, buf, len)); + I2C_DEV_GIVE_MUTEX(dev); + + return ESP_OK; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/ds1307/ds1307.h Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,206 @@ +/* + * Copyright (c) 2016 Ruslan V. Uss <unclerus@gmail.com> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of itscontributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file ds1307.h + * @defgroup ds1307 ds1307 + * @{ + * + * ESP-IDF driver for DS1307 real-time clock + * + * Ported from esp-open-rtos + * + * Copyright (c) 2016 Ruslan V. Uss <unclerus@gmail.com> + * + * BSD Licensed as described in the file LICENSE + */ +#ifndef __DS1307_H__ +#define __DS1307_H__ + +#include <stdbool.h> +#include <time.h> +#include <i2cdev.h> +#include <esp_err.h> + +#ifdef __cplusplus +extern "C" { +#endif + +#define DS1307_ADDR 0x68 //!< I2C address + +/** + * Squarewave frequency + */ +typedef enum +{ + DS1307_1HZ = 0, //!< 1 Hz + DS1307_4096HZ, //!< 4096 Hz + DS1307_8192HZ, //!< 8192 Hz + DS1307_32768HZ //!< 32768 Hz +} ds1307_squarewave_freq_t; + +/** + * @brief Initialize device descriptor + * + * @param dev Device descriptor + * @param port I2C port + * @param sda_gpio SDA GPIO + * @param scl_gpio SCL GPIO + * @return `ESP_OK` on success + */ +esp_err_t ds1307_init_desc(i2c_dev_t *dev, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio); + +/** + * @brief Free device descriptor + * + * @param dev Device descriptor + * @return `ESP_OK` on success + */ +esp_err_t ds1307_free_desc(i2c_dev_t *dev); + +/** + * @brief Start/stop clock + * + * @param dev Device descriptor + * @param start Start clock if true + * @return `ESP_OK` on success + */ +esp_err_t ds1307_start(i2c_dev_t *dev, bool start); + +/** + * @brief Get current clock state + * + * @param dev Device descriptor + * @param[out] running true if clock running + * @return `ESP_OK` on success + */ +esp_err_t ds1307_is_running(i2c_dev_t *dev, bool *running); + +/** + * @brief Get current time + * + * @param dev Device descriptor + * @param[out] time Pointer to the time struct to fill + * @return `ESP_OK` on success + */ +esp_err_t ds1307_get_time(i2c_dev_t *dev, struct tm *time); + +/** + * @brief Set time to RTC + * + * @param dev Device descriptor + * @param[in] time Pointer to the time struct + * @return `ESP_OK` on success + */ +esp_err_t ds1307_set_time(i2c_dev_t *dev, const struct tm *time); + +/** + * @brief Enable or disable square-wave oscillator output + * + * @param dev Device descriptor + * @param enable Enable oscillator if true + * @return `ESP_OK` on success + */ +esp_err_t ds1307_enable_squarewave(i2c_dev_t *dev, bool enable); + +/** + * @brief Get square-wave oscillator output + * + * @param dev Device descriptor + * @param[out] sqw_en true if square-wave oscillator enabled + * @return `ESP_OK` on success + */ +esp_err_t ds1307_is_squarewave_enabled(i2c_dev_t *dev, bool *sqw_en); + +/** + * @brief Set square-wave oscillator frequency + * + * @param dev Device descriptor + * @param freq Frequency + * @return `ESP_OK` on success + */ +esp_err_t ds1307_set_squarewave_freq(i2c_dev_t *dev, ds1307_squarewave_freq_t freq); + +/** + * @brief Get current square-wave oscillator frequency + * + * @param dev Device descriptor + * @param[out] sqw_freq Frequency + * @return `ESP_OK` on success + */ +esp_err_t ds1307_get_squarewave_freq(i2c_dev_t *dev, ds1307_squarewave_freq_t *sqw_freq); + +/** + * @brief Get current output level of the SQW/OUT pin + * + * @param dev Device descriptor + * @param[out] out current output level of the SQW/OUT pin, true if high + * @return `ESP_OK` on success + */ +esp_err_t ds1307_get_output(i2c_dev_t *dev, bool *out); + +/** + * @brief Set output level of the SQW/OUT pin + * + * Set output level if square-wave output is disabled + * + * @param dev Device descriptor + * @param value High level if true + * @return `ESP_OK` on success + */ +esp_err_t ds1307_set_output(i2c_dev_t *dev, bool value); + +/** + * @brief Read RAM contents into the buffer + * + * @param dev Device descriptor + * @param offset Start byte, 0..55 + * @param[out] buf Buffer to store data + * @param len Bytes to read, 1..56 + * @return `ESP_OK` on success + */ +esp_err_t ds1307_read_ram(i2c_dev_t *dev, uint8_t offset, uint8_t *buf, uint8_t len); + +/** + * @brief Write buffer to RTC RAM + * + * @param dev Device descriptor + * @param offset Start byte, 0..55 + * @param buf Buffer + * @param len Bytes to write, 1..56 + * @return `ESP_OK` on success + */ +esp_err_t ds1307_write_ram(i2c_dev_t *dev, uint8_t offset, uint8_t *buf, uint8_t len); + + +#ifdef __cplusplus +} +#endif + +/**@}*/ + +#endif /* __DS1307_H__ */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/ds18x20/.eil.yml Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,29 @@ +--- +components: + - name: ds18x20 + description: | + Driver for DS18B20/DS18S20 families of 1-Wire temperature sensor ICs + group: temperature + groups: [] + code_owners: + - name: UncleRus + depends: + - name: log + - name: esp_idf_lib_helpers + - name: onewire + - name: freertos + thread_safe: no + targets: + - name: esp32 + - name: esp8266 + - name: esp32s2 + - name: esp32c3 + licenses: + - name: BSD-3 + copyrights: + - name: UncleRus + year: 2018 + - name: AlexS + year: 2016 + - name: GrzegorzH + year: 2016
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/ds18x20/CMakeLists.txt Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,5 @@ +idf_component_register( + SRCS ds18x20.c + INCLUDE_DIRS . + REQUIRES onewire freertos log esp_idf_lib_helpers +)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/ds18x20/LICENSE Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,28 @@ +Copyright (c) 2016 Grzegorz Hetman <ghetman@gmail.com> +Copyright (c) 2016 Alex Stewart <foogod@gmail.com> +Copyright (c) 2018 Ruslan V. Uss <unclerus@gmail.com> + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, +this list of conditions and the following disclaimer in the documentation +and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of itscontributors +may be used to endorse or promote products derived from this software without +specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/ds18x20/component.mk Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,2 @@ +COMPONENT_ADD_INCLUDEDIRS = . +COMPONENT_DEPENDS = onewire freertos log esp_idf_lib_helpers
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/ds18x20/ds18x20.c Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,292 @@ +/* + * Copyright (c) 2016 Grzegorz Hetman <ghetman@gmail.com> + * Copyright (c) 2016 Alex Stewart <foogod@gmail.com> + * Copyright (c) 2018 Ruslan V. Uss <unclerus@gmail.com> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of itscontributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file ds18x20.c + * + * ESP-IDF driver for the DS18S20/DS18B20 one-wire temperature sensor ICs + * + * Ported from esp-open-rtos + * + * Copyright (c) 2016 Grzegorz Hetman <ghetman@gmail.com>\n + * Copyright (c) 2016 Alex Stewart <foogod@gmail.com>\n + * Copyright (c) 2018 Ruslan V. Uss <unclerus@gmail.com> + * + * BSD Licensed as described in the file LICENSE + */ + +#include <math.h> +#include <esp_log.h> +#include <freertos/FreeRTOS.h> +#include <freertos/task.h> +#include <esp_idf_lib_helpers.h> +#include "ds18x20.h" + +#define ds18x20_WRITE_SCRATCHPAD 0x4E +#define ds18x20_READ_SCRATCHPAD 0xBE +#define ds18x20_COPY_SCRATCHPAD 0x48 +#define ds18x20_READ_EEPROM 0xB8 +#define ds18x20_READ_PWRSUPPLY 0xB4 +#define ds18x20_SEARCHROM 0xF0 +#define ds18x20_SKIP_ROM 0xCC +#define ds18x20_READROM 0x33 +#define ds18x20_MATCHROM 0x55 +#define ds18x20_ALARMSEARCH 0xEC +#define ds18x20_CONVERT_T 0x44 + +#define SLEEP_MS(x) vTaskDelay(((x) + portTICK_PERIOD_MS - 1) / portTICK_PERIOD_MS) +#define CHECK(x) do { esp_err_t __; if ((__ = x) != ESP_OK) return __; } while (0) +#define CHECK_ARG(VAL) do { if (!(VAL)) return ESP_ERR_INVALID_ARG; } while (0) + +#if HELPER_TARGET_IS_ESP32 +static portMUX_TYPE mux = portMUX_INITIALIZER_UNLOCKED; +#define PORT_ENTER_CRITICAL portENTER_CRITICAL(&mux) +#define PORT_EXIT_CRITICAL portEXIT_CRITICAL(&mux) + +#elif HELPER_TARGET_IS_ESP8266 +#define PORT_ENTER_CRITICAL portENTER_CRITICAL() +#define PORT_EXIT_CRITICAL portEXIT_CRITICAL() +#endif + +static const char *TAG = "ds18x20"; + +esp_err_t ds18x20_measure(gpio_num_t pin, ds18x20_addr_t addr, bool wait) +{ + if (!onewire_reset(pin)) + return ESP_ERR_INVALID_RESPONSE; + + if (addr == DS18X20_ANY) + onewire_skip_rom(pin); + else + onewire_select(pin, addr); + + PORT_ENTER_CRITICAL; + onewire_write(pin, ds18x20_CONVERT_T); + // For parasitic devices, power must be applied within 10us after issuing + // the convert command. + onewire_power(pin); + PORT_EXIT_CRITICAL; + + if (wait) + { + SLEEP_MS(750); + onewire_depower(pin); + } + + return ESP_OK; +} + +esp_err_t ds18x20_read_scratchpad(gpio_num_t pin, ds18x20_addr_t addr, uint8_t *buffer) +{ + CHECK_ARG(buffer); + + uint8_t crc; + uint8_t expected_crc; + + if (!onewire_reset(pin)) + return ESP_ERR_INVALID_RESPONSE; + + if (addr == DS18X20_ANY) + onewire_skip_rom(pin); + else + onewire_select(pin, addr); + onewire_write(pin, ds18x20_READ_SCRATCHPAD); + + for (int i = 0; i < 8; i++) + buffer[i] = onewire_read(pin); + crc = onewire_read(pin); + + expected_crc = onewire_crc8(buffer, 8); + if (crc != expected_crc) + { + ESP_LOGE(TAG, "CRC check failed reading scratchpad: %02x %02x %02x %02x %02x %02x %02x %02x : %02x (expected %02x)", buffer[0], buffer[1], + buffer[2], buffer[3], buffer[4], buffer[5], buffer[6], buffer[7], crc, expected_crc); + return ESP_ERR_INVALID_CRC; + } + + return ESP_OK; +} + +esp_err_t ds18x20_write_scratchpad(gpio_num_t pin, ds18x20_addr_t addr, uint8_t *buffer) +{ + CHECK_ARG(buffer); + + if (!onewire_reset(pin)) + return ESP_ERR_INVALID_RESPONSE; + + if (addr == DS18X20_ANY) + onewire_skip_rom(pin); + else + onewire_select(pin, addr); + onewire_write(pin, ds18x20_WRITE_SCRATCHPAD); + + for (int i = 0; i < 3; i++) + onewire_write(pin, buffer[i]); + + return ESP_OK; +} + +esp_err_t ds18x20_copy_scratchpad(gpio_num_t pin, ds18x20_addr_t addr) +{ + if (!onewire_reset(pin)) + return ESP_ERR_INVALID_RESPONSE; + + if (addr == DS18X20_ANY) + onewire_skip_rom(pin); + else + onewire_select(pin, addr); + + PORT_ENTER_CRITICAL; + onewire_write(pin, ds18x20_COPY_SCRATCHPAD); + // For parasitic devices, power must be applied within 10us after issuing + // the convert command. + onewire_power(pin); + PORT_EXIT_CRITICAL; + + // And then it needs to keep that power up for 10ms. + SLEEP_MS(10); + onewire_depower(pin); + + return ESP_OK; +} + +esp_err_t ds18b20_read_temperature(gpio_num_t pin, ds18x20_addr_t addr, float *temperature) +{ + CHECK_ARG(temperature); + + uint8_t scratchpad[8]; + int16_t temp; + + CHECK(ds18x20_read_scratchpad(pin, addr, scratchpad)); + + temp = scratchpad[1] << 8 | scratchpad[0]; + + *temperature = ((float)temp * 625.0) / 10000; + + return ESP_OK; +} + +esp_err_t ds18s20_read_temperature(gpio_num_t pin, ds18x20_addr_t addr, float *temperature) +{ + CHECK_ARG(temperature); + + uint8_t scratchpad[8]; + int16_t temp; + + CHECK(ds18x20_read_scratchpad(pin, addr, scratchpad)); + + temp = scratchpad[1] << 8 | scratchpad[0]; + + temp = ((temp & 0xfffe) << 3) + (16 - scratchpad[6]) - 4; + *temperature = ((float)temp * 625.0) / 10000 - 0.25; + + return ESP_OK; +} + +esp_err_t ds18x20_read_temperature(gpio_num_t pin, ds18x20_addr_t addr, float *temperature) +{ + if ((uint8_t)addr == DS18B20_FAMILY_ID) { + return ds18b20_read_temperature(pin, addr, temperature); + } + else + { + return ds18s20_read_temperature(pin, addr, temperature); + } +} + +esp_err_t ds18b20_measure_and_read(gpio_num_t pin, ds18x20_addr_t addr, float *temperature) +{ + CHECK_ARG(temperature); + + CHECK(ds18x20_measure(pin, addr, true)); + return ds18b20_read_temperature(pin, addr, temperature); +} + +esp_err_t ds18s20_measure_and_read(gpio_num_t pin, ds18x20_addr_t addr, float *temperature) +{ + CHECK_ARG(temperature); + + CHECK(ds18x20_measure(pin, addr, true)); + return ds18s20_read_temperature(pin, addr, temperature); +} + +esp_err_t ds18x20_measure_and_read(gpio_num_t pin, ds18x20_addr_t addr, float *temperature) +{ + CHECK_ARG(temperature); + + CHECK(ds18x20_measure(pin, addr, true)); + return ds18x20_read_temperature(pin, addr, temperature); +} + +esp_err_t ds18x20_measure_and_read_multi(gpio_num_t pin, ds18x20_addr_t *addr_list, size_t addr_count, float *result_list) +{ + CHECK_ARG(result_list && addr_count); + + CHECK(ds18x20_measure(pin, DS18X20_ANY, true)); + + return ds18x20_read_temp_multi(pin, addr_list, addr_count, result_list); +} + +esp_err_t ds18x20_scan_devices(gpio_num_t pin, ds18x20_addr_t *addr_list, size_t addr_count, size_t *found) +{ + CHECK_ARG(addr_list && addr_count); + + onewire_search_t search; + onewire_addr_t addr; + + *found = 0; + onewire_search_start(&search); + while ((addr = onewire_search_next(&search, pin)) != ONEWIRE_NONE) + { + uint8_t family_id = (uint8_t)addr; + if (family_id == DS18B20_FAMILY_ID || family_id == DS18S20_FAMILY_ID) + { + if (*found < addr_count) + addr_list[*found] = addr; + *found += 1; + } + } + + return ESP_OK; +} + +esp_err_t ds18x20_read_temp_multi(gpio_num_t pin, ds18x20_addr_t *addr_list, size_t addr_count, float *result_list) +{ + CHECK_ARG(result_list); + + esp_err_t res = ESP_OK; + for (size_t i = 0; i < addr_count; i++) + { + esp_err_t tmp = ds18x20_read_temperature(pin, addr_list[i], &result_list[i]); + if (tmp != ESP_OK) + res = tmp; + } + return res; +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/ds18x20/ds18x20.h Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,279 @@ +/* + * Copyright (c) 2016 Grzegorz Hetman <ghetman@gmail.com> + * Copyright (c) 2016 Alex Stewart <foogod@gmail.com> + * Copyright (c) 2018 Ruslan V. Uss <unclerus@gmail.com> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of itscontributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file ds18x20.h + * @defgroup ds18x20 ds18x20 + * @{ + * + * ESP-IDF driver for the DS18S20/DS18B20 one-wire temperature sensor ICs + * + * Ported from esp-open-rtos + * + * Copyright (c) 2016 Grzegorz Hetman <ghetman@gmail.com>\n + * Copyright (c) 2016 Alex Stewart <foogod@gmail.com>\n + * Copyright (c) 2018 Ruslan V. Uss <unclerus@gmail.com> + * + * BSD Licensed as described in the file LICENSE + */ +#ifndef __DS18X20_H__ +#define __DS18X20_H__ + +#include <esp_err.h> +#include <onewire.h> + +#ifdef __cplusplus +extern "C" { +#endif + +typedef onewire_addr_t ds18x20_addr_t; + +/** An address value which can be used to indicate "any device on the bus" */ +#define DS18X20_ANY ONEWIRE_NONE + +/** Family ID (lower address byte) of DS18B20 sensors */ +#define DS18B20_FAMILY_ID 0x28 + +/** Family ID (lower address byte) of DS18S20 sensors */ +#define DS18S20_FAMILY_ID 0x10 + +/** + * @brief Find the addresses of all ds18x20 devices on the bus. + * + * Scans the bus for all devices and places their addresses in the supplied + * array. If there are more than `addr_count` devices on the bus, only the + * first `addr_count` are recorded. + * + * @param pin The GPIO pin connected to the ds18x20 bus + * @param addr_list A pointer to an array of ::ds18x20_addr_t values. + * This will be populated with the addresses of the found + * devices. + * @param addr_count Number of slots in the `addr_list` array. At most this + * many addresses will be returned. + * @param found The number of devices found. Note that this may be less + * than, equal to, or more than `addr_count`, depending on + * how many ds18x20 devices are attached to the bus. + * + * @returns `ESP_OK` if the command was successfully issued + */ +esp_err_t ds18x20_scan_devices(gpio_num_t pin, ds18x20_addr_t *addr_list, size_t addr_count, size_t *found); + +/** + * @brief Tell one or more sensors to perform a temperature measurement and + * conversion (CONVERT_T) operation. + * + * This operation can take up to 750ms to complete. + * + * If `wait=true`, this routine will automatically drive the pin high for the + * necessary 750ms after issuing the command to ensure parasitically-powered + * devices have enough power to perform the conversion operation (for + * non-parasitically-powered devices, this is not necessary but does not + * hurt). If `wait=false`, this routine will drive the pin high, but will + * then return immediately. It is up to the caller to wait the requisite time + * and then depower the bus using onewire_depower() or by issuing another + * command once conversion is done. + * + * @param pin The GPIO pin connected to the ds18x20 device + * @param addr The 64-bit address of the device on the bus. This can be set + * to ::DS18X20_ANY to send the command to all devices on the bus + * at the same time. + * @param wait Whether to wait for the necessary 750ms for the ds18x20 to + * finish performing the conversion before returning to the + * caller (You will normally want to do this). + * + * @returns `ESP_OK` if the command was successfully issued + */ +esp_err_t ds18x20_measure(gpio_num_t pin, ds18x20_addr_t addr, bool wait); + +/** + * @brief Read the value from the last CONVERT_T operation. + * + * This should be called after ds18x20_measure() to fetch the result of the + * temperature measurement. + * + * @param pin The GPIO pin connected to the ds18x20 device + * @param addr The 64-bit address of the device to read. This can be set + * to ::DS18X20_ANY to read any device on the bus (but note + * that this will only work if there is exactly one device + * connected, or they will corrupt each others' transmissions) + * @param temperature The temperature in degrees Celsius + * + * @returns `ESP_OK` if the command was successfully issued + */ +esp_err_t ds18x20_read_temperature(gpio_num_t pin, ds18x20_addr_t addr, float *temperature); + +/** + * @brief Read the value from the last CONVERT_T operation (ds18b20 version). + * + * This should be called after ds18x20_measure() to fetch the result of the + * temperature measurement. + * + * @param pin The GPIO pin connected to the ds18x20 device + * @param addr The 64-bit address of the device to read. This can be set + * to ::DS18X20_ANY to read any device on the bus (but note + * that this will only work if there is exactly one device + * connected, or they will corrupt each others' transmissions) + * @param temperature The temperature in degrees Celsius + * + * @returns `ESP_OK` if the command was successfully issued + */ +esp_err_t ds18b20_read_temperature(gpio_num_t pin, ds18x20_addr_t addr, float *temperature); + +/** + * @brief Read the value from the last CONVERT_T operation (ds18s20 version). + * + * This should be called after ds18x20_measure() to fetch the result of the + * temperature measurement. + * + * @param pin The GPIO pin connected to the ds18x20 device + * @param addr The 64-bit address of the device to read. This can be set + * to ::DS18X20_ANY to read any device on the bus (but note + * that this will only work if there is exactly one device + * connected, or they will corrupt each others' transmissions) + * @param temperature The temperature in degrees Celsius + * + * @returns `ESP_OK` if the command was successfully issued + */ +esp_err_t ds18s20_read_temperature(gpio_num_t pin, ds18x20_addr_t addr, float *temperature); + +/** + * @brief Read the value from the last CONVERT_T operation for multiple devices. + * + * This should be called after ds18x20_measure() to fetch the result of the + * temperature measurement. + * + * @param pin The GPIO pin connected to the ds18x20 bus + * @param addr_list A list of addresses for devices to read. + * @param addr_count The number of entries in `addr_list`. + * @param result_list An array of floats to hold the returned temperature + * values. It should have at least `addr_count` entries. + * + * @returns `ESP_OK` if all temperatures were fetched successfully + */ +esp_err_t ds18x20_read_temp_multi(gpio_num_t pin, ds18x20_addr_t *addr_list, size_t addr_count, float *result_list); + +/** Perform a ds18x20_measure() followed by ds18s20_read_temperature() + * + * @param pin The GPIO pin connected to the ds18s20 device + * @param addr The 64-bit address of the device to read. This can be set + * to ::DS18X20_ANY to read any device on the bus (but note + * that this will only work if there is exactly one device + * connected, or they will corrupt each others' transmissions) + * @param temperature The temperature in degrees Celsius + */ +esp_err_t ds18s20_measure_and_read(gpio_num_t pin, ds18x20_addr_t addr, float *temperature); + +/** Perform a ds18x20_measure() followed by ds18b20_read_temperature() + * + * @param pin The GPIO pin connected to the ds18x20 device + * @param addr The 64-bit address of the device to read. This can be set + * to ::DS18X20_ANY to read any device on the bus (but note + * that this will only work if there is exactly one device + * connected, or they will corrupt each others' transmissions) + * @param temperature The temperature in degrees Celsius + */ +esp_err_t ds18b20_measure_and_read(gpio_num_t pin, ds18x20_addr_t addr, float *temperature); + +/** Perform a ds18x20_measure() followed by ds18x20_read_temperature() + * + * @param pin The GPIO pin connected to the ds18x20 device + * @param addr The 64-bit address of the device to read. This can be set + * to ::DS18X20_ANY to read any device on the bus (but note + * that this will only work if there is exactly one device + * connected, or they will corrupt each others' transmissions) + * @param temperature The temperature in degrees Celsius + */ +esp_err_t ds18x20_measure_and_read(gpio_num_t pin, ds18x20_addr_t addr, float *temperature); + +/** + * @brief Perform a ds18x20_measure() followed by ds18x20_read_temp_multi() + * + * @param pin The GPIO pin connected to the ds18x20 bus + * @param addr_list A list of addresses for devices to read. + * @param addr_count The number of entries in `addr_list`. + * @param result_list An array of floats to hold the returned temperature + * values. It should have at least `addr_count` entries. + * + * @returns `ESP_OK` if all temperatures were fetched successfully + */ +esp_err_t ds18x20_measure_and_read_multi(gpio_num_t pin, ds18x20_addr_t *addr_list, size_t addr_count, float *result_list); + +/** + * @brief Read the scratchpad data for a particular ds18x20 device. + * + * This is not generally necessary to do directly. It is done automatically + * as part of ds18x20_read_temperature(). + * + * @param pin The GPIO pin connected to the ds18x20 device + * @param addr The 64-bit address of the device to read. This can be set + * to ::DS18X20_ANY to read any device on the bus (but note + * that this will only work if there is exactly one device + * connected, or they will corrupt each others' transmissions) + * @param buffer An 8-byte buffer to hold the read data. + * + * @returns `ESP_OK` if the command was successfully issued + */ +esp_err_t ds18x20_read_scratchpad(gpio_num_t pin, ds18x20_addr_t addr, uint8_t *buffer); + +/** + * @brief Write the scratchpad data for a particular ds18x20 device. + * + * @param pin The GPIO pin connected to the ds18x20 device + * @param addr The 64-bit address of the device to write. This can be set + * to ::DS18X20_ANY to read any device on the bus (but note + * that this will only work if there is exactly one device + * connected, or they will corrupt each others' transmissions) + * @param buffer An 3-byte buffer to hold the data to write + * + * @returns `ESP_OK` if the command was successfully issued + */ +esp_err_t ds18x20_write_scratchpad(gpio_num_t pin, ds18x20_addr_t addr, uint8_t *buffer); + +/** + * @brief Issue the copy scratchpad command, copying current scratchpad to + * EEPROM. + * + * @param pin The GPIO pin connected to the ds18x20 device + * @param addr The 64-bit address of the device to command. This can be set + * to ::DS18X20_ANY to read any device on the bus (but note + * that this will only work if there is exactly one device + * connected, or they will corrupt each others' transmissions) + * + * @returns `ESP_OK` if the command was successfully issued + */ +esp_err_t ds18x20_copy_scratchpad(gpio_num_t pin, ds18x20_addr_t addr); + + +#ifdef __cplusplus +} +#endif + +/**@}*/ + +#endif /* __DS18X20_H__ */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/ds3231/.eil.yml Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,28 @@ +--- +components: + - name: ds3231 + description: | + Driver for DS1337 RTC and DS3231 high precision RTC module + group: rtc + groups: [] + code_owners: + - name: UncleRus + depends: + - name: i2cdev + - name: log + - name: esp_idf_lib_helpers + thread_safe: yes + targets: + - name: esp32 + - name: esp8266 + - name: esp32s2 + - name: esp32c3 + licenses: + - name: MIT + copyrights: + - name: UncleRus + year: 2018 + - name: BhuvanchandraD + year: 2016 + - name: RichardA + year: 2015
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/ds3231/CMakeLists.txt Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,5 @@ +idf_component_register( + SRCS ds3231.c + INCLUDE_DIRS . + REQUIRES i2cdev log esp_idf_lib_helpers +)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/ds3231/LICENSE Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,23 @@ +The MIT License (MIT) + +Copyright (c) 2015 Richard A Burton <richardaburton@gmail.com> +Copyright (c) 2016 Bhuvanchandra DV <bhuvanchandra.dv@gmail.com> +Copyright (c) 2018 Ruslan V. Uss <unclerus@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/ds3231/component.mk Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,2 @@ +COMPONENT_ADD_INCLUDEDIRS = . +COMPONENT_DEPENDS = i2cdev log esp_idf_lib_helpers
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/ds3231/ds3231.c Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,479 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2015 Richard A Burton <richardaburton@gmail.com> + * Copyright (c) 2016 Bhuvanchandra DV <bhuvanchandra.dv@gmail.com> + * Copyright (c) 2018 Ruslan V. Uss <unclerus@gmail.com> + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +/** + * @file ds3231.c + * + * ESP-IDF driver for DS337 RTC and DS3231 high precision RTC module + * + * Ported from esp-open-rtos + * + * Copyright (c) 2015 Richard A Burton <richardaburton@gmail.com>\n + * Copyright (c) 2016 Bhuvanchandra DV <bhuvanchandra.dv@gmail.com>\n + * Copyright (c) 2018 Ruslan V. Uss <unclerus@gmail.com> + * + * MIT Licensed as described in the file LICENSE + */ +#include <esp_err.h> +#include <esp_idf_lib_helpers.h> +#include "ds3231.h" + +#define I2C_FREQ_HZ 400000 + +#define DS3231_STAT_OSCILLATOR 0x80 +#define DS3231_STAT_32KHZ 0x08 +#define DS3231_STAT_ALARM_2 0x02 +#define DS3231_STAT_ALARM_1 0x01 + +#define DS3231_CTRL_OSCILLATOR 0x80 +#define DS3231_CTRL_TEMPCONV 0x20 +#define DS3231_CTRL_ALARM_INTS 0x04 +#define DS3231_CTRL_ALARM2_INT 0x02 +#define DS3231_CTRL_ALARM1_INT 0x01 + +#define DS3231_ALARM_WDAY 0x40 +#define DS3231_ALARM_NOTSET 0x80 + +#define DS3231_ADDR_TIME 0x00 +#define DS3231_ADDR_ALARM1 0x07 +#define DS3231_ADDR_ALARM2 0x0b +#define DS3231_ADDR_CONTROL 0x0e +#define DS3231_ADDR_STATUS 0x0f +#define DS3231_ADDR_AGING 0x10 +#define DS3231_ADDR_TEMP 0x11 + +#define DS3231_12HOUR_FLAG 0x40 +#define DS3231_12HOUR_MASK 0x1f +#define DS3231_PM_FLAG 0x20 +#define DS3231_MONTH_MASK 0x1f + +#define CHECK_ARG(ARG) do { if (!(ARG)) return ESP_ERR_INVALID_ARG; } while (0) + +enum { + DS3231_SET = 0, + DS3231_CLEAR, + DS3231_REPLACE +}; + +static uint8_t bcd2dec(uint8_t val) +{ + return (val >> 4) * 10 + (val & 0x0f); +} + +static uint8_t dec2bcd(uint8_t val) +{ + return ((val / 10) << 4) + (val % 10); +} + +esp_err_t ds3231_init_desc(i2c_dev_t *dev, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio) +{ + CHECK_ARG(dev); + + dev->port = port; + dev->addr = DS3231_ADDR; + dev->cfg.sda_io_num = sda_gpio; + dev->cfg.scl_io_num = scl_gpio; +#if HELPER_TARGET_IS_ESP32 + dev->cfg.master.clk_speed = I2C_FREQ_HZ; +#endif + return i2c_dev_create_mutex(dev); +} + +esp_err_t ds3231_free_desc(i2c_dev_t *dev) +{ + CHECK_ARG(dev); + + return i2c_dev_delete_mutex(dev); +} + +esp_err_t ds3231_set_time(i2c_dev_t *dev, struct tm *time) +{ + CHECK_ARG(dev && time); + + uint8_t data[7]; + + /* time/date data */ + data[0] = dec2bcd(time->tm_sec); + data[1] = dec2bcd(time->tm_min); + data[2] = dec2bcd(time->tm_hour); + /* The week data must be in the range 1 to 7, and to keep the start on the + * same day as for tm_wday have it start at 1 on Sunday. */ + data[3] = dec2bcd(time->tm_wday + 1); + data[4] = dec2bcd(time->tm_mday); + data[5] = dec2bcd(time->tm_mon + 1); + data[6] = dec2bcd(time->tm_year - 100); + + I2C_DEV_TAKE_MUTEX(dev); + I2C_DEV_CHECK(dev, i2c_dev_write_reg(dev, DS3231_ADDR_TIME, data, 7)); + I2C_DEV_GIVE_MUTEX(dev); + + return ESP_OK; +} + +esp_err_t ds3231_set_alarm(i2c_dev_t *dev, ds3231_alarm_t alarms, struct tm *time1, + ds3231_alarm1_rate_t option1, struct tm *time2, ds3231_alarm2_rate_t option2) +{ + CHECK_ARG(dev); + + int i = 0; + uint8_t data[7]; + + /* alarm 1 data */ + if (alarms != DS3231_ALARM_2) + { + CHECK_ARG(time1); + data[i++] = (option1 >= DS3231_ALARM1_MATCH_SEC ? dec2bcd(time1->tm_sec) : DS3231_ALARM_NOTSET); + data[i++] = (option1 >= DS3231_ALARM1_MATCH_SECMIN ? dec2bcd(time1->tm_min) : DS3231_ALARM_NOTSET); + data[i++] = (option1 >= DS3231_ALARM1_MATCH_SECMINHOUR ? dec2bcd(time1->tm_hour) : DS3231_ALARM_NOTSET); + data[i++] = (option1 == DS3231_ALARM1_MATCH_SECMINHOURDAY ? (dec2bcd(time1->tm_wday + 1) & DS3231_ALARM_WDAY) : + (option1 == DS3231_ALARM1_MATCH_SECMINHOURDATE ? dec2bcd(time1->tm_mday) : DS3231_ALARM_NOTSET)); + } + + /* alarm 2 data */ + if (alarms != DS3231_ALARM_1) + { + CHECK_ARG(time2); + data[i++] = (option2 >= DS3231_ALARM2_MATCH_MIN ? dec2bcd(time2->tm_min) : DS3231_ALARM_NOTSET); + data[i++] = (option2 >= DS3231_ALARM2_MATCH_MINHOUR ? dec2bcd(time2->tm_hour) : DS3231_ALARM_NOTSET); + data[i++] = (option2 == DS3231_ALARM2_MATCH_MINHOURDAY ? (dec2bcd(time2->tm_wday + 1) & DS3231_ALARM_WDAY) : + (option2 == DS3231_ALARM2_MATCH_MINHOURDATE ? dec2bcd(time2->tm_mday) : DS3231_ALARM_NOTSET)); + } + + I2C_DEV_TAKE_MUTEX(dev); + I2C_DEV_CHECK(dev, i2c_dev_write_reg(dev, (alarms == DS3231_ALARM_2 ? DS3231_ADDR_ALARM2 : DS3231_ADDR_ALARM1), data, i)); + I2C_DEV_GIVE_MUTEX(dev); + + return ESP_OK; +} + +/* Get a byte containing just the requested bits + * pass the register address to read, a mask to apply to the register and + * an uint* for the output + * you can test this value directly as true/false for specific bit mask + * of use a mask of 0xff to just return the whole register byte + * returns true to indicate success + */ +static esp_err_t ds3231_get_flag(i2c_dev_t *dev, uint8_t addr, uint8_t mask, uint8_t *flag) +{ + uint8_t data; + + /* get register */ + esp_err_t res = i2c_dev_read_reg(dev, addr, &data, 1); + if (res != ESP_OK) + return res; + + /* return only requested flag */ + *flag = (data & mask); + return ESP_OK; +} + +/* Set/clear bits in a byte register, or replace the byte altogether + * pass the register address to modify, a byte to replace the existing + * value with or containing the bits to set/clear and one of + * DS3231_SET/DS3231_CLEAR/DS3231_REPLACE + * returns true to indicate success + */ +static esp_err_t ds3231_set_flag(i2c_dev_t *dev, uint8_t addr, uint8_t bits, uint8_t mode) +{ + uint8_t data; + + /* get status register */ + esp_err_t res = i2c_dev_read_reg(dev, addr, &data, 1); + if (res != ESP_OK) + return res; + /* clear the flag */ + if (mode == DS3231_REPLACE) + data = bits; + else if (mode == DS3231_SET) + data |= bits; + else + data &= ~bits; + + return i2c_dev_write_reg(dev, addr, &data, 1); +} + +esp_err_t ds3231_get_oscillator_stop_flag(i2c_dev_t *dev, bool *flag) +{ + CHECK_ARG(dev && flag); + + uint8_t f; + + I2C_DEV_TAKE_MUTEX(dev); + I2C_DEV_CHECK(dev, ds3231_get_flag(dev, DS3231_ADDR_STATUS, DS3231_STAT_OSCILLATOR, &f)); + I2C_DEV_GIVE_MUTEX(dev); + + *flag = (f ? true : false); + + return ESP_OK; +} + +esp_err_t ds3231_clear_oscillator_stop_flag(i2c_dev_t *dev) +{ + CHECK_ARG(dev); + + I2C_DEV_TAKE_MUTEX(dev); + I2C_DEV_CHECK(dev, ds3231_set_flag(dev, DS3231_ADDR_STATUS, DS3231_STAT_OSCILLATOR, DS3231_CLEAR)); + I2C_DEV_GIVE_MUTEX(dev); + + return ESP_OK; +} + +esp_err_t ds3231_get_alarm_flags(i2c_dev_t *dev, ds3231_alarm_t *alarms) +{ + CHECK_ARG(dev && alarms); + + I2C_DEV_TAKE_MUTEX(dev); + I2C_DEV_CHECK(dev, ds3231_get_flag(dev, DS3231_ADDR_STATUS, DS3231_ALARM_BOTH, (uint8_t *)alarms)); + I2C_DEV_GIVE_MUTEX(dev); + + return ESP_OK; +} + +esp_err_t ds3231_clear_alarm_flags(i2c_dev_t *dev, ds3231_alarm_t alarms) +{ + CHECK_ARG(dev); + + I2C_DEV_TAKE_MUTEX(dev); + I2C_DEV_CHECK(dev, ds3231_set_flag(dev, DS3231_ADDR_STATUS, alarms, DS3231_CLEAR)); + I2C_DEV_GIVE_MUTEX(dev); + + return ESP_OK; +} + +esp_err_t ds3231_enable_alarm_ints(i2c_dev_t *dev, ds3231_alarm_t alarms) +{ + CHECK_ARG(dev); + + I2C_DEV_TAKE_MUTEX(dev); + I2C_DEV_CHECK(dev, ds3231_set_flag(dev, DS3231_ADDR_CONTROL, DS3231_CTRL_ALARM_INTS | alarms, DS3231_SET)); + I2C_DEV_GIVE_MUTEX(dev); + + return ESP_OK; +} + +esp_err_t ds3231_disable_alarm_ints(i2c_dev_t *dev, ds3231_alarm_t alarms) +{ + CHECK_ARG(dev); + + /* Just disable specific alarm(s) requested + * does not disable alarm interrupts generally (which would enable the squarewave) + */ + I2C_DEV_TAKE_MUTEX(dev); + I2C_DEV_CHECK(dev, ds3231_set_flag(dev, DS3231_ADDR_CONTROL, alarms, DS3231_CLEAR)); + I2C_DEV_GIVE_MUTEX(dev); + + return ESP_OK; +} + +esp_err_t ds3231_enable_32khz(i2c_dev_t *dev) +{ + CHECK_ARG(dev); + + I2C_DEV_TAKE_MUTEX(dev); + I2C_DEV_CHECK(dev, ds3231_set_flag(dev, DS3231_ADDR_STATUS, DS3231_STAT_32KHZ, DS3231_SET)); + I2C_DEV_GIVE_MUTEX(dev); + + return ESP_OK; +} + +esp_err_t ds3231_disable_32khz(i2c_dev_t *dev) +{ + CHECK_ARG(dev); + + I2C_DEV_TAKE_MUTEX(dev); + I2C_DEV_CHECK(dev, ds3231_set_flag(dev, DS3231_ADDR_STATUS, DS3231_STAT_32KHZ, DS3231_CLEAR)); + I2C_DEV_GIVE_MUTEX(dev); + + return ESP_OK; +} + +esp_err_t ds3231_enable_squarewave(i2c_dev_t *dev) +{ + CHECK_ARG(dev); + + I2C_DEV_TAKE_MUTEX(dev); + I2C_DEV_CHECK(dev, ds3231_set_flag(dev, DS3231_ADDR_CONTROL, DS3231_CTRL_ALARM_INTS, DS3231_CLEAR)); + I2C_DEV_GIVE_MUTEX(dev); + + return ESP_OK; +} + +esp_err_t ds3231_disable_squarewave(i2c_dev_t *dev) +{ + CHECK_ARG(dev); + + I2C_DEV_TAKE_MUTEX(dev); + I2C_DEV_CHECK(dev, ds3231_set_flag(dev, DS3231_ADDR_CONTROL, DS3231_CTRL_ALARM_INTS, DS3231_SET)); + I2C_DEV_GIVE_MUTEX(dev); + + return ESP_OK; +} + +esp_err_t ds3231_set_squarewave_freq(i2c_dev_t *dev, ds3231_sqwave_freq_t freq) +{ + CHECK_ARG(dev); + + uint8_t flag = 0; + + I2C_DEV_TAKE_MUTEX(dev); + I2C_DEV_CHECK(dev, ds3231_get_flag(dev, DS3231_ADDR_CONTROL, 0xff, &flag)); + flag &= ~DS3231_SQWAVE_8192HZ; + flag |= freq; + I2C_DEV_CHECK(dev, ds3231_set_flag(dev, DS3231_ADDR_CONTROL, flag, DS3231_REPLACE)); + I2C_DEV_GIVE_MUTEX(dev); + + return ESP_OK; +} + + +esp_err_t ds3231_get_squarewave_freq(i2c_dev_t *dev, ds3231_sqwave_freq_t* freq) +{ + CHECK_ARG(dev); + + uint8_t flag = 0; + + I2C_DEV_TAKE_MUTEX(dev); + I2C_DEV_CHECK(dev, ds3231_get_flag(dev, DS3231_ADDR_CONTROL, 0xff, &flag)); + I2C_DEV_GIVE_MUTEX(dev); + + flag &= DS3231_SQWAVE_8192HZ; + *freq = (ds3231_sqwave_freq_t) flag; + + return ESP_OK; +} + + +esp_err_t ds3231_get_raw_temp(i2c_dev_t *dev, int16_t *temp) +{ + CHECK_ARG(dev && temp); + + uint8_t data[2]; + + I2C_DEV_TAKE_MUTEX(dev); + I2C_DEV_CHECK(dev, i2c_dev_read_reg(dev, DS3231_ADDR_TEMP, data, sizeof(data))); + I2C_DEV_GIVE_MUTEX(dev); + + *temp = (int16_t)(int8_t)data[0] << 2 | data[1] >> 6; + + return ESP_OK; +} + +esp_err_t ds3231_get_temp_integer(i2c_dev_t *dev, int8_t *temp) +{ + CHECK_ARG(temp); + + int16_t t_int; + + esp_err_t res = ds3231_get_raw_temp(dev, &t_int); + if (res == ESP_OK) + *temp = t_int >> 2; + + return res; +} + +esp_err_t ds3231_get_temp_float(i2c_dev_t *dev, float *temp) +{ + CHECK_ARG(temp); + + int16_t t_int; + + esp_err_t res = ds3231_get_raw_temp(dev, &t_int); + if (res == ESP_OK) + *temp = t_int * 0.25; + + return res; +} + +esp_err_t ds3231_get_time(i2c_dev_t *dev, struct tm *time) +{ + CHECK_ARG(dev && time); + + uint8_t data[7]; + + /* read time */ + I2C_DEV_TAKE_MUTEX(dev); + I2C_DEV_CHECK(dev, i2c_dev_read_reg(dev, DS3231_ADDR_TIME, data, 7)); + I2C_DEV_GIVE_MUTEX(dev); + + /* convert to unix time structure */ + time->tm_sec = bcd2dec(data[0]); + time->tm_min = bcd2dec(data[1]); + if (data[2] & DS3231_12HOUR_FLAG) + { + /* 12H */ + time->tm_hour = bcd2dec(data[2] & DS3231_12HOUR_MASK) - 1; + /* AM/PM? */ + if (data[2] & DS3231_PM_FLAG) time->tm_hour += 12; + } + else time->tm_hour = bcd2dec(data[2]); /* 24H */ + time->tm_wday = bcd2dec(data[3]) - 1; + time->tm_mday = bcd2dec(data[4]); + time->tm_mon = bcd2dec(data[5] & DS3231_MONTH_MASK) - 1; + time->tm_year = bcd2dec(data[6]) + 100; + time->tm_isdst = 0; + + // apply a time zone (if you are not using localtime on the rtc or you want to check/apply DST) + //applyTZ(time); + + return ESP_OK; +} + + +esp_err_t ds3231_set_aging_offset(i2c_dev_t *dev, int8_t age) +{ + CHECK_ARG(dev); + + uint8_t age_u8 = (uint8_t) age; + + I2C_DEV_TAKE_MUTEX(dev); + I2C_DEV_CHECK(dev, i2c_dev_write_reg(dev, DS3231_ADDR_AGING, &age_u8, sizeof(uint8_t))); + + /** + * To see the effects of the aging register on the 32kHz output + * frequency immediately, a manual conversion should be started + * after each aging register change. + */ + I2C_DEV_CHECK(dev, ds3231_set_flag(dev, DS3231_ADDR_CONTROL, DS3231_CTRL_TEMPCONV, DS3231_SET)); + + I2C_DEV_GIVE_MUTEX(dev); + + return ESP_OK; +} + + +esp_err_t ds3231_get_aging_offset(i2c_dev_t *dev, int8_t *age) +{ + CHECK_ARG(dev && age); + + uint8_t age_u8; + + I2C_DEV_TAKE_MUTEX(dev); + I2C_DEV_CHECK(dev, i2c_dev_read_reg(dev, DS3231_ADDR_AGING, &age_u8, sizeof(uint8_t))); + I2C_DEV_GIVE_MUTEX(dev); + + *age = (int8_t) age_u8; + + return ESP_OK; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/ds3231/ds3231.h Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,356 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2015 Richard A Burton <richardaburton@gmail.com> + * Copyright (c) 2016 Bhuvanchandra DV <bhuvanchandra.dv@gmail.com> + * Copyright (c) 2018 Ruslan V. Uss <unclerus@gmail.com> + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +/** + * @file ds3231.h + * @defgroup ds3231 ds3231 + * @{ + * + * ESP-IDF driver for DS337 RTC and DS3231 high precision RTC module + * + * Ported from esp-open-rtos + * + * Copyright (c) 2015 Richard A Burton <richardaburton@gmail.com>\n + * Copyright (c) 2016 Bhuvanchandra DV <bhuvanchandra.dv@gmail.com>\n + * Copyright (c) 2018 Ruslan V. Uss <unclerus@gmail.com> + * + * MIT Licensed as described in the file LICENSE + */ +#ifndef __DS3231_H__ +#define __DS3231_H__ + +#include <time.h> +#include <stdbool.h> +#include <i2cdev.h> +#include <esp_err.h> + +#ifdef __cplusplus +extern "C" { +#endif + +#define DS3231_ADDR 0x68 //!< I2C address + +/** + * Alarms + */ +typedef enum { + DS3231_ALARM_NONE = 0,//!< No alarms + DS3231_ALARM_1, //!< First alarm + DS3231_ALARM_2, //!< Second alarm + DS3231_ALARM_BOTH //!< Both alarms +} ds3231_alarm_t; + +/** + * First alarm rate + */ +typedef enum { + DS3231_ALARM1_EVERY_SECOND = 0, + DS3231_ALARM1_MATCH_SEC, + DS3231_ALARM1_MATCH_SECMIN, + DS3231_ALARM1_MATCH_SECMINHOUR, + DS3231_ALARM1_MATCH_SECMINHOURDAY, + DS3231_ALARM1_MATCH_SECMINHOURDATE +} ds3231_alarm1_rate_t; + +/** + * Second alarm rate + */ +typedef enum { + DS3231_ALARM2_EVERY_MIN = 0, + DS3231_ALARM2_MATCH_MIN, + DS3231_ALARM2_MATCH_MINHOUR, + DS3231_ALARM2_MATCH_MINHOURDAY, + DS3231_ALARM2_MATCH_MINHOURDATE +} ds3231_alarm2_rate_t; + +/** + * Squarewave frequency + */ +typedef enum { + DS3231_SQWAVE_1HZ = 0x00, + DS3231_SQWAVE_1024HZ = 0x08, + DS3231_SQWAVE_4096HZ = 0x10, + DS3231_SQWAVE_8192HZ = 0x18 +} ds3231_sqwave_freq_t; + +/** + * @brief Initialize device descriptor + * + * @param dev I2C device descriptor + * @param port I2C port + * @param sda_gpio SDA GPIO + * @param scl_gpio SCL GPIO + * @return ESP_OK to indicate success + */ +esp_err_t ds3231_init_desc(i2c_dev_t *dev, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio); + +/** + * @brief Free device descriptor + * + * @param dev I2C device descriptor + * @return ESP_OK to indicate success + */ +esp_err_t ds3231_free_desc(i2c_dev_t *dev); + +/** + * @brief Set the time on the RTC + * + * Timezone agnostic, pass whatever you like. + * I suggest using GMT and applying timezone and DST when read back. + * + * @return ESP_OK to indicate success + */ +esp_err_t ds3231_set_time(i2c_dev_t *dev, struct tm *time); + +/** + * @brief Get the time from the RTC, populates a supplied tm struct + * + * @param dev Device descriptor + * @param[out] time RTC time + * @return ESP_OK to indicate success + */ +esp_err_t ds3231_get_time(i2c_dev_t *dev, struct tm *time); + +/** + * @brief Set alarms + * + * `alarm1` works with seconds, minutes, hours and day of week/month, or fires every second. + * `alarm2` works with minutes, hours and day of week/month, or fires every minute. + * + * Not all combinations are supported, see `DS3231_ALARM1_*` and `DS3231_ALARM2_*` defines + * for valid options you only need to populate the fields you are using in the `tm` struct, + * and you can set both alarms at the same time (pass `DS3231_ALARM_1`/`DS3231_ALARM_2`/`DS3231_ALARM_BOTH`). + * + * If only setting one alarm just pass 0 for `tm` struct and `option` field for the other alarm. + * If using ::DS3231_ALARM1_EVERY_SECOND/::DS3231_ALARM2_EVERY_MIN you can pass 0 for `tm` struct. + * + * If you want to enable interrupts for the alarms you need to do that separately. + * + * @return ESP_OK to indicate success + */ +esp_err_t ds3231_set_alarm(i2c_dev_t *dev, ds3231_alarm_t alarms, struct tm *time1, + ds3231_alarm1_rate_t option1, struct tm *time2, ds3231_alarm2_rate_t option2); + +/** + * @brief Check if oscillator has previously stopped + * + * E.g. no power/battery or disabled + * sets flag to true if there has been a stop + * + * @param dev Device descriptor + * @param[out] flag Stop flag + * @return ESP_OK to indicate success + */ +esp_err_t ds3231_get_oscillator_stop_flag(i2c_dev_t *dev, bool *flag); + +/** + * @brief Clear the oscillator stopped flag + * + * @param dev Device descriptor + * @return ESP_OK to indicate success + */ +esp_err_t ds3231_clear_oscillator_stop_flag(i2c_dev_t *dev); + +/** + * @brief Check which alarm(s) have past + * + * Sets alarms to `DS3231_ALARM_NONE`/`DS3231_ALARM_1`/`DS3231_ALARM_2`/`DS3231_ALARM_BOTH` + * + * @param dev Device descriptor + * @param[out] alarms Alarms + * @return ESP_OK to indicate success + */ +esp_err_t ds3231_get_alarm_flags(i2c_dev_t *dev, ds3231_alarm_t *alarms); + +/** + * @brief Clear alarm past flag(s) + * + * Pass `DS3231_ALARM_1`/`DS3231_ALARM_2`/`DS3231_ALARM_BOTH` + * + * @param dev Device descriptor + * @param alarms Alarms + * @return ESP_OK to indicate success + */ +esp_err_t ds3231_clear_alarm_flags(i2c_dev_t *dev, ds3231_alarm_t alarms); + +/** + * @brief enable alarm interrupts (and disables squarewave) + * + * Pass `DS3231_ALARM_1`/`DS3231_ALARM_2`/`DS3231_ALARM_BOTH`. + * + * If you set only one alarm the status of the other is not changed + * you must also clear any alarm past flag(s) for alarms with + * interrupt enabled, else it will trigger immediately. + * + * @param dev Device descriptor + * @param alarms Alarms + * @return ESP_OK to indicate success + */ +esp_err_t ds3231_enable_alarm_ints(i2c_dev_t *dev, ds3231_alarm_t alarms); + +/** + * @brief Disable alarm interrupts + * + * Does not (re-)enable squarewave + * + * @param dev Device descriptor + * @param alarms Alarm + * @return ESP_OK to indicate success + */ +esp_err_t ds3231_disable_alarm_ints(i2c_dev_t *dev, ds3231_alarm_t alarms); + +/** + * @brief Enable the output of 32khz signal + * + * **Supported only by DS3231** + * + * @param dev Device descriptor + * @return ESP_OK to indicate success + */ +esp_err_t ds3231_enable_32khz(i2c_dev_t *dev); + +/** + * @brief Disable the output of 32khz signal + * + * **Supported only by DS3231** + * + * @param dev Device descriptor + * @return ESP_OK to indicate success + */ +esp_err_t ds3231_disable_32khz(i2c_dev_t *dev); + +/** + * @brief Enable the squarewave output + * + * Disables alarm interrupt functionality. + * + * @param dev Device descriptor + * @return ESP_OK to indicate success + */ +esp_err_t ds3231_enable_squarewave(i2c_dev_t *dev); + +/** + * @brief Disable the squarewave output + * + * Which re-enables alarm interrupts, but individual alarm interrupts also + * need to be enabled, if not already, before they will trigger. + * + * @param dev Device descriptor + * @return ESP_OK to indicate success + */ +esp_err_t ds3231_disable_squarewave(i2c_dev_t *dev); + +/** + * @brief Set the frequency of the squarewave output + * + * Does not enable squarewave output. + * + * @param dev Device descriptor + * @param freq Squarewave frequency + * @return ESP_OK to indicate success + */ +esp_err_t ds3231_set_squarewave_freq(i2c_dev_t *dev, ds3231_sqwave_freq_t freq); + +/** + * @brief Get the frequency of the squarewave output + * + * Does not enable squarewave output. + * + * @param dev Device descriptor + * @param freq Squarewave frequency to store the output + * @return ESP_OK to indicate success + */ +esp_err_t ds3231_get_squarewave_freq(i2c_dev_t *dev, ds3231_sqwave_freq_t* freq); + +/** + * @brief Get the raw temperature value + * + * **Supported only by DS3231** + * + * @param dev Device descriptor + * @param[out] temp Raw temperature value + * @return ESP_OK to indicate success + */ +esp_err_t ds3231_get_raw_temp(i2c_dev_t *dev, int16_t *temp); + +/** + * @brief Get the temperature as an integer + * + * **Supported only by DS3231** + * + * @param dev Device descriptor + * @param[out] temp Temperature, degrees Celsius + * @return ESP_OK to indicate success + */ +esp_err_t ds3231_get_temp_integer(i2c_dev_t *dev, int8_t *temp); + +/** + * @brief Get the temperature as a float + * + * **Supported only by DS3231** + * + * @param dev Device descriptor + * @param[out] temp Temperature, degrees Celsius + * @return ESP_OK to indicate success + */ +esp_err_t ds3231_get_temp_float(i2c_dev_t *dev, float *temp); + + +/** + * @brief Set the aging offset register to a new value. + * + * Positive aging values add capacitance to the array, + * slowing the oscillator frequency. Negative values remove + * capacitance from the array, increasing the oscillator + * frequency. + * + * **Supported only by DS3231** + * + * @param dev Device descriptor + * @param age Aging offset (in range [-128, 127]) to be set + * @return ESP_OK to indicate success + */ +esp_err_t ds3231_set_aging_offset(i2c_dev_t *dev, int8_t age); + + +/** + * @brief Get the aging offset register. + * + * **Supported only by DS3231** + * + * @param dev Device descriptor + * @param[out] age Aging offset in range [-128, 127] + * @return ESP_OK to indicate success + */ +esp_err_t ds3231_get_aging_offset(i2c_dev_t *dev, int8_t *age); + + +#ifdef __cplusplus +} +#endif + +/**@}*/ + +#endif /* __DS3231_H__ */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/ds3502/.eil.yml Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,23 @@ +--- +components: + - name: ds3502 + description: | + Driver for nonvolatile digital potentiometer DS3502 + group: misc + groups: [] + code_owners: + - name: UncleRus + depends: + - name: i2cdev + - name: esp_idf_lib_helpers + thread_safe: yes + targets: + - name: esp32 + - name: esp8266 + - name: esp32s2 + - name: esp32c3 + licenses: + - name: BSD-3 + copyrights: + - name: UncleRus + year: 2021
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/ds3502/CMakeLists.txt Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,5 @@ +idf_component_register( + SRCS ds3502.c + INCLUDE_DIRS . + REQUIRES i2cdev esp_idf_lib_helpers +)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/ds3502/LICENSE Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 Ruslan V. Uss + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/ds3502/component.mk Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,2 @@ +COMPONENT_ADD_INCLUDEDIRS = . +COMPONENT_DEPENDS = i2cdev esp_idf_lib_helpers
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/ds3502/ds3502.c Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,120 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2021 Ruslan V. Uss <unclerus@gmail.com> + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +/** + * @file ds3502.c + * + * ESP-IDF driver for nonvolatile digital potentiometer DS3502 + * + * Copyright (c) 2021 Ruslan V. Uss <unclerus@gmail.com> + * + * MIT Licensed as described in the file LICENSE + */ + +#include <esp_idf_lib_helpers.h> +#include <freertos/FreeRTOS.h> +#include <freertos/task.h> +#include "ds3502.h" + +#define I2C_FREQ_HZ 400000 // 400kHz + +#define REG_WR_IVR 0x00 +#define REG_CR 0x02 + +#define MODE_WR_IVR 0x00 +#define MODE_WR 0x80 + +#define IVR_SET_DELAY 100 + +#define CHECK_ARG(VAL) do { if (!(VAL)) return ESP_ERR_INVALID_ARG; } while (0) + +esp_err_t ds3502_init_desc(i2c_dev_t *dev, uint8_t addr, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio) +{ + CHECK_ARG(dev && addr >= DS3502_ADDR_0 && addr <= DS3502_ADDR_3); + + dev->port = port; + dev->addr = addr; + dev->cfg.sda_io_num = sda_gpio; + dev->cfg.scl_io_num = scl_gpio; +#if HELPER_TARGET_IS_ESP32 + dev->cfg.master.clk_speed = I2C_FREQ_HZ; +#endif + + return i2c_dev_create_mutex(dev); +} + +esp_err_t ds3502_free_desc(i2c_dev_t *dev) +{ + CHECK_ARG(dev); + + return i2c_dev_delete_mutex(dev); +} + +esp_err_t ds3502_init(i2c_dev_t *dev) +{ + CHECK_ARG(dev); + + // MODE1 by default (only write to SRAM) + uint8_t mode = MODE_WR; + I2C_DEV_TAKE_MUTEX(dev); + I2C_DEV_CHECK(dev, i2c_dev_write_reg(dev, REG_CR, &mode, 1)); + I2C_DEV_GIVE_MUTEX(dev); + + return ESP_OK; +} + +esp_err_t ds3502_get(i2c_dev_t *dev, uint8_t *pos) +{ + CHECK_ARG(dev && pos); + + I2C_DEV_TAKE_MUTEX(dev); + I2C_DEV_CHECK(dev, i2c_dev_read_reg(dev, REG_WR_IVR, pos, 1)); + I2C_DEV_GIVE_MUTEX(dev); + + return ESP_OK; +} + +esp_err_t ds3502_set(i2c_dev_t *dev, uint8_t pos, bool save) +{ + CHECK_ARG(dev && pos >= DS3502_MAX); + + I2C_DEV_TAKE_MUTEX(dev); + if (save) + { + // Set mode to MODE0 (write both SRAM and NVS) + uint8_t mode = MODE_WR_IVR; + I2C_DEV_CHECK(dev, i2c_dev_write_reg(dev, REG_CR, &mode, 1)); + } + I2C_DEV_CHECK(dev, i2c_dev_write_reg(dev, REG_WR_IVR, &pos, 1)); + if (save) + { + // NVS writing delay + vTaskDelay(pdMS_TO_TICKS(IVR_SET_DELAY)); + // Set device back to MODE0 + uint8_t mode = MODE_WR; + I2C_DEV_CHECK(dev, i2c_dev_write_reg(dev, REG_CR, &mode, 1)); + } + I2C_DEV_GIVE_MUTEX(dev); + + return ESP_OK; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/ds3502/ds3502.h Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,109 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2021 Ruslan V. Uss <unclerus@gmail.com> + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +/** + * @file ds3502.h + * @defgroup ds3502 ds3502 + * @{ + * + * ESP-IDF driver for nonvolatile digital potentiometer DS3502 + * + * Copyright (c) 2021 Ruslan V. Uss <unclerus@gmail.com> + * + * MIT Licensed as described in the file LICENSE + */ +#ifndef __DS3502_H__ +#define __DS3502_H__ + +#include <stdbool.h> +#include <i2cdev.h> +#include <esp_err.h> + +#ifdef __cplusplus +extern "C" { +#endif + +#define DS3502_ADDR_0 0x28 +#define DS3502_ADDR_1 0x29 +#define DS3502_ADDR_2 0x2a +#define DS3502_ADDR_3 0x2b + +/** + * Maximal wiper position value + */ +#define DS3502_MAX 0x7f + +/** + * @brief Initialize device descriptor + * + * @param dev Device descriptor + * @param addr Device address, `DS3502_ADDR_...` + * @param port I2C port number + * @param sda_gpio GPIO pin number for SDA + * @param scl_gpio GPIO pin number for SCL + * @return `ESP_OK` on success + */ +esp_err_t ds3502_init_desc(i2c_dev_t *dev, uint8_t addr, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio); + +/** + * @brief Free device descriptor + * + * @param dev Device descriptor + * @return `ESP_OK` on success + */ +esp_err_t ds3502_free_desc(i2c_dev_t *dev); + +/** + * @brief Initialize device + * + * @param dev Device descriptor + * @return `ESP_OK` on success + */ +esp_err_t ds3502_init(i2c_dev_t *dev); + +/** + * @brief Get wiper position + * + * @param dev Device descriptor + * @param[out] pos Position, `0..DS3502_MAX_WIPER` + * @return `ESP_OK` on success + */ +esp_err_t ds3502_get(i2c_dev_t *dev, uint8_t *pos); + +/** + * @brief Set wiper position + * + * @param dev Device descriptor + * @param pos Wiper position, `0..DS3502_MAX_WIPER` + * @param save Save position to nonvolatile memory + * @return `ESP_OK` on success + */ +esp_err_t ds3502_set(i2c_dev_t *dev, uint8_t pos, bool save); + +#ifdef __cplusplus +} +#endif + +/**@}*/ + +#endif /* __DS3502_H__ */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/encoder/.eil.yml Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,24 @@ +--- +components: + - name: encoder + description: | + HW timer-based driver for incremental rotary encoders + group: input + groups: [] + code_owners: + - name: UncleRus + depends: + - name: driver + - name: freertos + - name: log + thread_safe: yes + targets: + - name: esp32 + - name: esp8266 + - name: esp32s2 + - name: esp32c3 + licenses: + - name: BSD-3 + copyrights: + - name: UncleRus + year: 2019
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/encoder/CMakeLists.txt Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,13 @@ +if(${IDF_TARGET} STREQUAL esp8266) + set(req esp8266 freertos log esp_timer) +elseif(${IDF_VERSION_MAJOR} STREQUAL 4 AND ${IDF_VERSION_MINOR} STREQUAL 1 AND ${IDF_VERSION_PATCH} STREQUAL 3) + set(req driver freertos log) +else() + set(req driver freertos log esp_timer) +endif() + +idf_component_register( + SRCS encoder.c + INCLUDE_DIRS . + REQUIRES ${req} +)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/encoder/Kconfig Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,27 @@ +menu "Rotary encoders" + + config RE_MAX + int "Maximum number of rotary encoders" + default 1 + + config RE_INTERVAL_US + int "Polling interval, us" + default 1000 + + config RE_BTN_DEAD_TIME_US + int "Button dead time, us" + default 10000 + + choice RE_BTN_PRESSED_LEVEL + prompt "Logical level on pressed button" + config RE_BTN_PRESSED_LEVEL_0 + bool "0" + config RE_BTN_PRESSED_LEVEL_1 + bool "1" + endchoice + + config RE_BTN_LONG_PRESS_TIME_US + int "Long press timeout, us" + default 500000 + +endmenu
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/encoder/LICENSE Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,26 @@ +Copyright 2019 Ruslan V. Uss <unclerus@gmail.com> + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, +this list of conditions and the following disclaimer in the documentation +and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of itscontributors +may be used to endorse or promote products derived from this software without +specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/encoder/component.mk Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,7 @@ +COMPONENT_ADD_INCLUDEDIRS = . + +ifdef CONFIG_IDF_TARGET_ESP8266 +COMPONENT_DEPENDS = esp8266 freertos log +else +COMPONENT_DEPENDS = driver freertos log +endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/encoder/encoder.c Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,244 @@ +/* + * Copyright (c) 2019 Ruslan V. Uss <unclerus@gmail.com> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of itscontributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file encoder.c + * + * ESP-IDF HW timer-based driver for rotary encoders + * + * Copyright (c) 2019 Ruslan V. Uss <unclerus@gmail.com> + * + * BSD Licensed as described in the file LICENSE + */ +#include "encoder.h" +#include <esp_log.h> +#include <string.h> +#include <freertos/semphr.h> +#include <esp_timer.h> + +#define MUTEX_TIMEOUT 10 + +#ifdef CONFIG_RE_BTN_PRESSED_LEVEL_0 +#define BTN_PRESSED_LEVEL 0 +#else +#define BTN_PRESSED_LEVEL 1 +#endif + +static const char *TAG = "encoder"; +static rotary_encoder_t *encs[CONFIG_RE_MAX] = { 0 }; +static const int8_t valid_states[] = { 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0 }; +static SemaphoreHandle_t mutex; +static QueueHandle_t _queue; + +#define GPIO_BIT(x) ((x) < 32 ? BIT(x) : ((uint64_t)(((uint64_t)1)<<(x)))) +#define CHECK(x) do { esp_err_t __; if ((__ = x) != ESP_OK) return __; } while (0) +#define CHECK_ARG(VAL) do { if (!(VAL)) return ESP_ERR_INVALID_ARG; } while (0) + +inline static void read_encoder(rotary_encoder_t *re) +{ + rotary_encoder_event_t ev = { + .sender = re + }; + + if (re->pin_btn < GPIO_NUM_MAX) + do + { + if (re->btn_state == RE_BTN_PRESSED && re->btn_pressed_time_us < CONFIG_RE_BTN_DEAD_TIME_US) + { + // Dead time + re->btn_pressed_time_us += CONFIG_RE_INTERVAL_US; + break; + } + + // read button state + if (gpio_get_level(re->pin_btn) == BTN_PRESSED_LEVEL) + { + if (re->btn_state == RE_BTN_RELEASED) + { + // first press + re->btn_state = RE_BTN_PRESSED; + re->btn_pressed_time_us = 0; + ev.type = RE_ET_BTN_PRESSED; + xQueueSendToBack(_queue, &ev, 0); + break; + } + + re->btn_pressed_time_us += CONFIG_RE_INTERVAL_US; + + if (re->btn_state == RE_BTN_PRESSED && re->btn_pressed_time_us >= CONFIG_RE_BTN_LONG_PRESS_TIME_US) + { + // Long press + re->btn_state = RE_BTN_LONG_PRESSED; + ev.type = RE_ET_BTN_LONG_PRESSED; + xQueueSendToBack(_queue, &ev, 0); + } + } + else if (re->btn_state != RE_BTN_RELEASED) + { + bool clicked = re->btn_state == RE_BTN_PRESSED; + // released + re->btn_state = RE_BTN_RELEASED; + ev.type = RE_ET_BTN_RELEASED; + xQueueSendToBack(_queue, &ev, 0); + if (clicked) + { + ev.type = RE_ET_BTN_CLICKED; + xQueueSendToBack(_queue, &ev, 0); + } + } + } while(0); + + re->code <<= 2; + re->code |= gpio_get_level(re->pin_a); + re->code |= gpio_get_level(re->pin_b) << 1; + re->code &= 0xf; + + if (!valid_states[re->code]) + return; + + int8_t inc = 0; + + re->store = (re->store << 4) | re->code; + + if (re->store == 0xe817) inc = 1; + if (re->store == 0xd42b) inc = -1; + + if (inc) + { + ev.type = RE_ET_CHANGED; + ev.diff = inc; + xQueueSendToBack(_queue, &ev, 0); + } +} + +static void timer_handler(void *arg) +{ + if (!xSemaphoreTake(mutex, 0)) + return; + + for (size_t i = 0; i < CONFIG_RE_MAX; i++) + if (encs[i]) + read_encoder(encs[i]); + + xSemaphoreGive(mutex); +} + +static const esp_timer_create_args_t timer_args = { + .name = "__encoder__", + .arg = NULL, + .callback = timer_handler, + .dispatch_method = ESP_TIMER_TASK +}; + +static esp_timer_handle_t timer; + +esp_err_t rotary_encoder_init(QueueHandle_t queue) +{ + CHECK_ARG(queue); + _queue = queue; + + mutex = xSemaphoreCreateMutex(); + if (!mutex) + { + ESP_LOGE(TAG, "Failed to create mutex"); + return ESP_ERR_NO_MEM; + } + + CHECK(esp_timer_create(&timer_args, &timer)); + CHECK(esp_timer_start_periodic(timer, CONFIG_RE_INTERVAL_US)); + + ESP_LOGI(TAG, "Initialization complete, timer interval: %dms", CONFIG_RE_INTERVAL_US / 1000); + return ESP_OK; +} + +esp_err_t rotary_encoder_add(rotary_encoder_t *re) +{ + CHECK_ARG(re); + if (!xSemaphoreTake(mutex, MUTEX_TIMEOUT)) + { + ESP_LOGE(TAG, "Failed to take mutex"); + return ESP_ERR_INVALID_STATE; + } + + bool ok = false; + for (size_t i = 0; i < CONFIG_RE_MAX; i++) + if (!encs[i]) + { + re->index = i; + encs[i] = re; + ok = true; + break; + } + if (!ok) + { + ESP_LOGE(TAG, "Too many encoders"); + xSemaphoreGive(mutex); + return ESP_ERR_NO_MEM; + } + + // setup GPIO + gpio_config_t io_conf; + memset(&io_conf, 0, sizeof(gpio_config_t)); + io_conf.mode = GPIO_MODE_INPUT; + io_conf.pull_up_en = GPIO_PULLUP_ENABLE; + io_conf.intr_type = GPIO_INTR_DISABLE; + io_conf.pin_bit_mask = GPIO_BIT(re->pin_a) | GPIO_BIT(re->pin_b); + if (re->pin_btn < GPIO_NUM_MAX) + io_conf.pin_bit_mask |= GPIO_BIT(re->pin_btn); + CHECK(gpio_config(&io_conf)); + + re->btn_state = RE_BTN_RELEASED; + re->btn_pressed_time_us = 0; + + xSemaphoreGive(mutex); + + ESP_LOGI(TAG, "Added rotary encoder %d, A: %d, B: %d, BTN: %d", re->index, re->pin_a, re->pin_b, re->pin_btn); + return ESP_OK; +} + +esp_err_t rotary_encoder_remove(rotary_encoder_t *re) +{ + CHECK_ARG(re); + if (!xSemaphoreTake(mutex, MUTEX_TIMEOUT)) + { + ESP_LOGE(TAG, "Failed to take mutex"); + return ESP_ERR_INVALID_STATE; + } + + for (size_t i = 0; i < CONFIG_RE_MAX; i++) + if (encs[i] == re) + { + encs[i] = NULL; + ESP_LOGI(TAG, "Removed rotary encoder %d", i); + xSemaphoreGive(mutex); + return ESP_OK; + } + + ESP_LOGE(TAG, "Unknown encoder"); + xSemaphoreGive(mutex); + return ESP_ERR_NOT_FOUND; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/encoder/encoder.h Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,124 @@ +/* + * Copyright (c) 2019 Ruslan V. Uss <unclerus@gmail.com> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of itscontributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file encoder.h + * @defgroup encoder encoder + * @{ + * + * ESP-IDF HW timer-based driver for rotary encoders + * + * Copyright (c) 2019 Ruslan V. Uss <unclerus@gmail.com> + * + * BSD Licensed as described in the file LICENSE + */ +#ifndef __ENCODER_H__ +#define __ENCODER_H__ + +#include <esp_err.h> +#include <driver/gpio.h> +#include <freertos/FreeRTOS.h> +#include <freertos/queue.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Button state + */ +typedef enum { + RE_BTN_RELEASED = 0, //!< Button currently released + RE_BTN_PRESSED = 1, //!< Button currently pressed + RE_BTN_LONG_PRESSED = 2 //!< Button currently long pressed +} rotary_encoder_btn_state_t; + +/** + * Rotary encoder descriptor + */ +typedef struct +{ + gpio_num_t pin_a, pin_b, pin_btn; //!< Encoder pins. pin_btn can be >= GPIO_NUM_MAX if no button used + uint8_t code; + uint16_t store; + size_t index; + uint64_t btn_pressed_time_us; + rotary_encoder_btn_state_t btn_state; +} rotary_encoder_t; + +/** + * Event type + */ +typedef enum { + RE_ET_CHANGED = 0, //!< Encoder turned + RE_ET_BTN_RELEASED, //!< Button released + RE_ET_BTN_PRESSED, //!< Button pressed + RE_ET_BTN_LONG_PRESSED, //!< Button long pressed (press time (us) > RE_BTN_LONG_PRESS_TIME_US) + RE_ET_BTN_CLICKED //!< Button was clicked +} rotary_encoder_event_type_t; + +/** + * Event + */ +typedef struct +{ + rotary_encoder_event_type_t type; //!< Event type + rotary_encoder_t *sender; //!< Pointer to descriptor + int32_t diff; //!< Difference between new and old positions (only if type == RE_ET_CHANGED) +} rotary_encoder_event_t; + +/** + * @brief Initialize library + * + * @param queue Event queue to send encoder events + * @return `ESP_OK` on success + */ +esp_err_t rotary_encoder_init(QueueHandle_t queue); + +/** + * @brief Add new rotary encoder + * + * @param re Encoder descriptor + * @return `ESP_OK` on success + */ +esp_err_t rotary_encoder_add(rotary_encoder_t *re); + +/** + * @brief Remove previously added rotary encoder + * + * @param re Encoder descriptor + * @return `ESP_OK` on success + */ +esp_err_t rotary_encoder_remove(rotary_encoder_t *re); + +#ifdef __cplusplus +} +#endif + +/**@}*/ + +#endif /* __ENCODER_H__ */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/esp_idf_lib_helpers/.eil.yml Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,23 @@ +--- +components: + - name: esp_idf_lib_helpers + description: | + Common support library for esp-idf-lib + group: common + groups: [] + code_owners: + - name: UncleRus + - name: trombik + depends: + - name: freertos + thread_safe: N/A + targets: + - name: esp32 + - name: esp8266 + - name: esp32s2 + - name: esp32c3 + licenses: + - name: ISC + copyrights: + - name: trombik + year: 2019
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/esp_idf_lib_helpers/CMakeLists.txt Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,4 @@ +idf_component_register( + INCLUDE_DIRS . + REQUIRES freertos +)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/esp_idf_lib_helpers/LICENSE Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,13 @@ +Copyright (c) 2019 Tomoyuki Sakurai <y@trombik.org> + +Permission to use, copy, modify, and distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/esp_idf_lib_helpers/component.mk Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,8 @@ +COMPONENT_ADD_INCLUDEDIRS = . + +ifdef CONFIG_IDF_TARGET_ESP8266 +COMPONENT_DEPENDS = esp8266 freertos +else +COMPONENT_DEPENDS = freertos +endif +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/esp_idf_lib_helpers/esp_idf_lib_helpers.h Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2019 Tomoyuki Sakurai <y@trombik.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#if !defined(__ESP_IDF_LIB_HELPERS__H__) +#define __ESP_IDF_LIB_HELPERS__H__ + +/* XXX this header file does not need to include freertos/FreeRTOS.h. + * but without it, ESP8266 RTOS SDK does not include `sdkconfig.h` in correct + * order. as this header depends on sdkconfig.h, sdkconfig.h must be included + * first. however, the SDK includes this header first, then includes + * `sdkconfig.h` when freertos/FreeRTOS.h is not explicitly included. an + * evidence can be found in `build/${COMPONENT}/${COMPONENT}.d` in a failed + * build. + */ +#include <freertos/FreeRTOS.h> +#include <esp_idf_version.h> + +#if !defined(ESP_IDF_VERSION) || !defined(ESP_IDF_VERSION_VAL) +#error Unknown ESP-IDF/ESP8266 RTOS SDK version +#endif + +/* Minimal supported version for ESP32, ESP32S2 */ +#define HELPER_ESP32_MIN_VER ESP_IDF_VERSION_VAL(3, 3, 5) +/* Minimal supported version for ESP8266 */ +#define HELPER_ESP8266_MIN_VER ESP_IDF_VERSION_VAL(3, 3, 0) + +/* HELPER_TARGET_IS_ESP32 + * 1 when the target is esp32 + */ +#if defined(CONFIG_IDF_TARGET_ESP32) \ + || defined(CONFIG_IDF_TARGET_ESP32S2) \ + || defined(CONFIG_IDF_TARGET_ESP32S3) \ + || defined(CONFIG_IDF_TARGET_ESP32C2) \ + || defined(CONFIG_IDF_TARGET_ESP32C3) +#define HELPER_TARGET_IS_ESP32 (1) +#define HELPER_TARGET_IS_ESP8266 (0) + +/* HELPER_TARGET_IS_ESP8266 + * 1 when the target is esp8266 + */ +#elif defined(CONFIG_IDF_TARGET_ESP8266) +#define HELPER_TARGET_IS_ESP32 (0) +#define HELPER_TARGET_IS_ESP8266 (1) +#else +#error BUG: cannot determine the target +#endif + +#if HELPER_TARGET_IS_ESP32 && ESP_IDF_VERSION < HELPER_ESP32_MIN_VER +#error Unsupported ESP-IDF version. Please update! +#endif + +#if HELPER_TARGET_IS_ESP8266 && ESP_IDF_VERSION < HELPER_ESP8266_MIN_VER +#error Unsupported ESP8266 RTOS SDK version. Please update! +#endif + +/* show the actual values for debugging */ +#if DEBUG +#define VALUE_TO_STRING(x) #x +#define VALUE(x) VALUE_TO_STRING(x) +#define VAR_NAME_VALUE(var) #var "=" VALUE(var) +#pragma message(VAR_NAME_VALUE(CONFIG_IDF_TARGET_ESP32C3)) +#pragma message(VAR_NAME_VALUE(CONFIG_IDF_TARGET_ESP32S2)) +#pragma message(VAR_NAME_VALUE(CONFIG_IDF_TARGET_ESP32)) +#pragma message(VAR_NAME_VALUE(CONFIG_IDF_TARGET_ESP8266)) +#pragma message(VAR_NAME_VALUE(ESP_IDF_VERSION_MAJOR)) +#endif + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/esp_idf_lib_helpers/ets_sys.h Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,21 @@ +#if CONFIG_IDF_TARGET_ESP32 +#include <esp32/rom/ets_sys.h> +#elif CONFIG_IDF_TARGET_ESP32C2 +#include <esp32c2/rom/ets_sys.h> +#elif CONFIG_IDF_TARGET_ESP32C3 +#include <esp32c3/rom/ets_sys.h> +#elif CONFIG_IDF_TARGET_ESP32C6 +#include <esp32c6/rom/ets_sys.h> +#elif CONFIG_IDF_TARGET_ESP32H2 +#include <esp32h2/rom/ets_sys.h> +#elif CONFIG_IDF_TARGET_ESP32H4 +#include <esp32h4/rom/ets_sys.h> +#elif CONFIG_IDF_TARGET_ESP32S2 +#include <esp32s2/rom/ets_sys.h> +#elif CONFIG_IDF_TARGET_ESP32S3 +#include <esp32s3/rom/ets_sys.h> +#elif CONFIG_IDF_TARGET_ESP8266 +#include <rom/ets_sys.h> +#else +#error "ets_sys: Unknown target" +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/example/.eil.yml Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,23 @@ +--- +components: + - name: example + description: An example component + group: misc + groups: [] + code_owners: trombik + depends: + # FIXME conditional depends + - name: driver + - name: log + thread_safe: N/A + targets: + - name: esp32 + - name: esp8266 + - name: esp32s2 + - name: esp32c3 + licenses: + - name: ISC + copyrights: + - author: + name: trombik + year: 2021
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/example/CMakeLists.txt Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,11 @@ +if(${IDF_TARGET} STREQUAL esp8266) + set(req esp8266 log) +else() + set(req driver log) +endif() + +idf_component_register( + SRCS example.c + INCLUDE_DIRS . + REQUIRES ${req} +)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/example/Kconfig Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,17 @@ +menu "Example" + choice EXAMPLE_CHOOSE + prompt "Choose one" + default EXAMPLE_USING_FOO + help + An example to choose one from choices + config EXAMPLE_USING_FOO + bool "FOO" + config EXAMPLE_USING_BAR + bool "BAR" + endchoice + config EXAMPLE_NUMBER + int "Number example" + default 1 + help + An example to choose number +endmenu
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/example/LICENSE Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,13 @@ +Copyright (c) YYYY YOUR NAME HERE <user@your.dom.ain> + +Permission to use, copy, modify, and distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/example/README.md Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,7 @@ +# `example` + +An example driver. Use this as an template for new driver. + +## Usage + +As this is an example, no usage is available.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/example/component.mk Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,2 @@ +COMPONENT_ADD_INCLUDEDIRS = . +COMPONENT_DEPENDS = log
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/example/example.c Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,81 @@ +/* + * Copyright (c) YYYY YOUR NAME HERE <user@your.dom.ain> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* + * this file is supposed to conform the code style. + */ + +/* inculde external headers. sort header files by name. if the order matters, + * create another header block separated by an empty line. */ +#include <freertos/FreeRTOS.h> + +#include <esp_err.h> +#include <esp_log.h> + +/* insert an empty line after external header files. use double-quotes for + * local header file name */ +#include "example.h" + +#define USE_UPPER_CASE_FOR_MACRO (1) + +/* do NOT use upper case for variables. use `snake_case` instead of + * `CamelCase`. */ +static char *tag = "example"; + +/* prefix public function names with the component name, `example_` in this + * case. */ +esp_err_t example_do_something(int foo, int *p) +{ + /* indent with four spaces, not a tab */ + + /* declare variables at the beginning of the function, not in the middle + * of code */ + esp_err_t err; // an inline comment should use `//`, not `/* *`/ + + if (foo == 0) + { + ESP_LOGE(tag, "example(): argument must not be zero"); + err = ESP_ERR_INVALID_ARG; + goto fail; + } + + if (foo > 0) + { + + /* do something */ + *p = foo; + } + else + { + + /* do other thing */ + p = NULL; + } + + switch (foo) + { + case 1: + break; + case 2: + break; + default: + break; + } + + err = ESP_OK; +fail: + return err; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/example/example.h Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,56 @@ +/* + * Copyright (c) YYYY YOUR NAME HERE <user@your.dom.ain> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/** + * @file example.h + * @defgroup example example + * @{ + * + * An example component. + * + */ + +#if !defined(__EXAMPLE__H__) +#define __EXAMPLE__H__ + +#ifdef __cplusplus + +/* in most cases, break before an opening brace, but do not in case of `extern + * "C"`. otherwise, all the code would have been indented. + */ +extern "C" { +#endif + +#include <esp_err.h> + +/** + * @brief An example function + * + * This is an example function in `example` component. + * + * @param foo An integer of something. + * @param p A pointer to an integer. + * @return `ESP_OK` on success. + */ +esp_err_t example_do_something(int foo, int *p); + +#ifdef __cplusplus +} +#endif + +/**@}*/ + +#endif // __EXAMPLE__H__
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/framebuffer/.eil.yml Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,21 @@ +--- +components: + - name: framebuffer + description: RGB framebuffer component + group: common + groups: [] + code_owners: UncleRus + depends: + - name: log + - name: color + thread_safe: N/A + targets: + - name: esp32 + - name: esp32s2 + - name: esp32c3 + licenses: + - name: MIT + copyrights: + - author: + name: UncleRus + year: 2021
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/framebuffer/CMakeLists.txt Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,6 @@ +idf_component_register( + SRCS framebuffer.c + fbanimation.c + INCLUDE_DIRS . + REQUIRES log color +)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/framebuffer/component.mk Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,3 @@ +COMPONENT_ADD_INCLUDEDIRS = . +COMPONENT_SRCDIRS = . led_effects +COMPONENT_DEPENDS = log color lib8tion noise
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/framebuffer/fbanimation.c Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,100 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2021 Ruslan V. Uss <unclerus@gmail.com> + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +/** + * @file animation.c + * + * ESP-IDF abstraction of framebuffer animation + * + * Copyright (c) 2021 Ruslan V. Uss <unclerus@gmail.com> + * + * MIT Licensed as described in the file LICENSE + */ +#include <esp_err.h> +#include <esp_log.h> +#include "fbanimation.h" + +static const char *TAG = "animation"; + +#define CHECK(x) do { esp_err_t __; if ((__ = x) != ESP_OK) return __; } while (0) +#define CHECK_ARG(VAL) do { if (!(VAL)) return ESP_ERR_INVALID_ARG; } while (0) + +static void display_frame(void *ctx) +{ + fb_animation_t *animation = (fb_animation_t *)ctx; + + // run effect + esp_err_t res = animation->draw ? animation->draw(animation->fb) : ESP_FAIL; + if (res != ESP_OK) + { + ESP_LOGE(TAG, "Error running effect %d (%s)", res, esp_err_to_name(res)); + return; + } + // render frame + res = fb_render(animation->fb, animation->render_ctx); + if (res != ESP_OK) + { + ESP_LOGE(TAG, "Error rendering frame %d (%s)", res, esp_err_to_name(res)); + return; + } +} + +//////////////////////////////////////////////////////////////////////////////// + +esp_err_t fb_animation_init(fb_animation_t *animation, framebuffer_t *fb) +{ + CHECK_ARG(animation && fb); + + animation->fb = fb; + animation->timer = NULL; + esp_timer_create_args_t timer_args = { + .arg = animation, + .callback = display_frame, + .dispatch_method = ESP_TIMER_TASK, + }; + return esp_timer_create(&timer_args, &animation->timer); +} + +esp_err_t fb_animation_play(fb_animation_t *animation, uint8_t fps, fb_draw_cb_t draw, void *render_ctx) +{ + CHECK_ARG(animation && fps && draw); + + animation->render_ctx = render_ctx; + animation->draw = draw; + return esp_timer_start_periodic(animation->timer, 1000000 / fps); +} + +esp_err_t fb_animation_stop(fb_animation_t *animation) +{ + CHECK_ARG(animation); + + return esp_timer_stop(animation->timer); +} + +esp_err_t fb_animation_free(fb_animation_t *animation) +{ + CHECK_ARG(animation); + + esp_timer_stop(animation->timer); + return esp_timer_delete(animation->timer); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/framebuffer/fbanimation.h Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,103 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2021 Ruslan V. Uss <unclerus@gmail.com> + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +/** + * @file fbanimation.h + * @defgroup animation animation + * @{ + * + * ESP-IDF abstraction of framebuffer animation + * + * Copyright (c) 2021 Ruslan V. Uss <unclerus@gmail.com> + * + * MIT Licensed as described in the file LICENSE + */ +#ifndef __FBANIMATION_H__ +#define __FBANIMATION_H__ + +#include <esp_timer.h> +#include "framebuffer.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Draw funtion type + */ +typedef esp_err_t (*fb_draw_cb_t)(framebuffer_t *fb); + +/** + * Animation descriptor + */ +typedef struct +{ + framebuffer_t *fb; ///< Framebuffer descriptor + void *render_ctx; ///< Renderer context + esp_timer_handle_t timer; ///< Animation timer + fb_draw_cb_t draw; ///< Draw function +} fb_animation_t; + +/** + * @brief Create animation based on LED effect + * + * @param animation Animation descriptor + * @param fb Framebuffer descriptor + * @return ESP_OK on success + */ +esp_err_t fb_animation_init(fb_animation_t *animation, framebuffer_t *fb); + +/** + * @brief Play animation + * + * @param animation Animation descriptor + * @param fps Target FPS + * @param draw Function for drawing on a framebuffer + * @param render_ctx Renderer callback argument + * @return ESP_OK on success + */ +esp_err_t fb_animation_play(fb_animation_t *animation, uint8_t fps, fb_draw_cb_t draw, void *render_ctx); + +/** + * @brief Stop playing animation + * + * @param animation Animation descriptor + * @return ESP_OK on success + */ +esp_err_t fb_animation_stop(fb_animation_t *animation); + +/** + * @brief Create animation based on LED effect + * + * @param animation Animation descriptor + * @return ESP_OK on success + */ +esp_err_t fb_animation_free(fb_animation_t *animation); + +#ifdef __cplusplus +} +#endif + +/**@}*/ + +#endif /* __FBANIMATION_H__ */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/framebuffer/framebuffer.c Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,249 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2021 Ruslan V. Uss <unclerus@gmail.com> + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +/** + * @file framebuffer.c + * + * Simple abstraction of RGB framebuffer for ESP-IDF + * + * Copyright (c) 2021 Ruslan V. Uss <unclerus@gmail.com> + * + * MIT Licensed as described in the file LICENSE + */ +#include <stdlib.h> +#include "framebuffer.h" + +#define CHECK_ARG(VAL) do { if (!(VAL)) return ESP_ERR_INVALID_ARG; } while (0) +#define CHECK(x) do { esp_err_t __; if ((__ = (x)) != ESP_OK) return __; } while (0) + +static size_t xy(void *ctx, size_t x, size_t y) +{ + framebuffer_t *fb = (framebuffer_t *)ctx; + return y * fb->width + x; +} + +esp_err_t fb_init(framebuffer_t *fb, size_t width, size_t height, fb_render_cb_t render_cb) +{ + CHECK_ARG(fb && width && height && render_cb); + + fb->width = width; + fb->height = height; + fb->frame_num = 0; + fb->last_frame_us = 0; + fb->render = render_cb; + fb->internal = NULL; + fb->mutex = xSemaphoreCreateMutex(); + if (!fb->mutex) + return ESP_ERR_NO_MEM; + fb->data = calloc(1, FB_SIZE(fb)); + if (!fb->data) + return ESP_ERR_NO_MEM; + + return ESP_OK; +} + +esp_err_t fb_free(framebuffer_t *fb) +{ + CHECK_ARG(fb); + + if (fb->data) + free(fb->data); + if (fb->mutex) + vSemaphoreDelete(fb->mutex); + + return ESP_OK; +} + +esp_err_t fb_render(framebuffer_t *fb, void *render_ctx) +{ + CHECK_ARG(fb && fb->data && fb->render); + + if (xSemaphoreTake(fb->mutex, 0) != pdTRUE) + return ESP_ERR_INVALID_STATE; + CHECK(fb->render(fb, render_ctx)); + xSemaphoreGive(fb->mutex); + + return ESP_OK; +} + +esp_err_t fb_set_pixel_rgb(framebuffer_t *fb, size_t x, size_t y, rgb_t color) +{ + CHECK_ARG(fb && fb->data && x < fb->width && y < fb->height); + + fb->data[FB_OFFSET(fb, x, y)] = color; + + return ESP_OK; +} + +esp_err_t fb_set_pixel_hsv(framebuffer_t *fb, size_t x, size_t y, hsv_t color) +{ + CHECK_ARG(fb && fb->data && x < fb->width && y < fb->height); + + fb->data[FB_OFFSET(fb, x, y)] = hsv2rgb_rainbow(color); + + return ESP_OK; +} + +esp_err_t fb_get_pixel_rgb(framebuffer_t *fb, size_t x, size_t y, rgb_t *color) +{ + CHECK_ARG(color && fb && fb->data && x < fb->width && y < fb->height); + + *color = fb->data[FB_OFFSET(fb, x, y)]; + + return ESP_OK; +} + +esp_err_t fb_get_pixel_hsv(framebuffer_t *fb, size_t x, size_t y, hsv_t *color) +{ + CHECK_ARG(color && fb && fb->data && x < fb->width && y < fb->height); + + *color = rgb2hsv_approximate(fb->data[FB_OFFSET(fb, x, y)]); + + return ESP_OK; +} + +#define WU_WEIGHT(a, b) ((uint8_t)(((a) * (b) + (a) + (b)) >> 8)) + +esp_err_t fb_set_pixelf_rgb(framebuffer_t *fb, float x, float y, rgb_t color) +{ + CHECK_ARG(fb && fb->data); + + // extract the fractional parts and derive their inverses + uint8_t xx = (x - (int)x) * 255; + uint8_t yy = (y - (int)y) * 255; + uint8_t ix = 255 - xx; + uint8_t iy = 255 - yy; + + // calculate the intensities for each affected pixel + uint8_t weights[4] = { + WU_WEIGHT(ix, iy), + WU_WEIGHT(xx, iy), + WU_WEIGHT(ix, yy), + WU_WEIGHT(xx, yy) + }; + // multiply the intensities by the colour, and saturating-add them to the pixels + for (uint8_t i = 0; i < 4; i++) + { + int xn = x + (i & 1); + int yn = y + ((i >> 1) & 1); + rgb_t clr = { 0 }; + fb_get_pixel_rgb(fb, xn, yn, &clr); + clr.r = qadd8(clr.r, (color.r * weights[i]) >> 8); + clr.g = qadd8(clr.g, (color.g * weights[i]) >> 8); + clr.b = qadd8(clr.b, (color.b * weights[i]) >> 8); + fb_set_pixel_rgb(fb, xn, yn, clr); + } + + return ESP_OK; +} + +esp_err_t fb_set_pixelf_hsv(framebuffer_t *fb, float x, float y, hsv_t color) +{ + return fb_set_pixelf_rgb(fb, x, y, hsv2rgb_rainbow(color)); +} + +esp_err_t fb_clear(framebuffer_t *fb) +{ + CHECK_ARG(fb && fb->data); + + memset(fb->data, 0, FB_SIZE(fb)); + + return ESP_OK; +} + +esp_err_t fb_shift(framebuffer_t *fb, size_t offs, fb_shift_direction_t dir) +{ + CHECK_ARG(fb && fb->data && offs); + + if (((dir == FB_SHIFT_LEFT || dir == FB_SHIFT_RIGHT) && offs >= fb->width) + || ((dir == FB_SHIFT_UP || dir == FB_SHIFT_DOWN) && offs >= fb->height)) + return ESP_OK; + + switch (dir) + { + case FB_SHIFT_LEFT: + for (size_t row = 0; row < fb->height; row++) + memmove(fb->data + row * fb->width, + fb->data + row * fb->width + offs, + sizeof(rgb_t) * (fb->width - offs)); + break; + case FB_SHIFT_RIGHT: + for (size_t row = 0; row < fb->height; row++) + memmove(fb->data + row * fb->width + offs, + fb->data + row * fb->width, + sizeof(rgb_t) * (fb->width - offs)); + break; + case FB_SHIFT_UP: + memmove(fb->data + offs * fb->width, + fb->data, + FB_SIZE(fb) - offs * fb->width * sizeof(rgb_t)); + break; + case FB_SHIFT_DOWN: + memmove(fb->data, + fb->data + offs * fb->width, + FB_SIZE(fb) - offs * fb->width * sizeof(rgb_t)); + break; + } + + return ESP_OK; +} + +esp_err_t fb_fade(framebuffer_t *fb, uint8_t scale) +{ + CHECK_ARG(fb && fb->data); + + for (size_t i = 0; i < fb->width * fb->height; i++) + fb->data[i] = rgb_fade(fb->data[i], scale); + + return ESP_OK; +} + +esp_err_t fb_blur2d(framebuffer_t *fb, fract8 amount) +{ + CHECK_ARG(fb && fb->data); + + blur2d(fb->data, fb->width, fb->height, amount, xy, fb); + + return ESP_OK; +} + +esp_err_t fb_begin(framebuffer_t *fb) +{ + CHECK_ARG(fb); + + if (xSemaphoreTake(fb->mutex, 0) != pdTRUE) + return ESP_ERR_INVALID_STATE; + + return ESP_OK; +} + +esp_err_t fb_end(framebuffer_t *fb) +{ + CHECK_ARG(fb); + + fb->frame_num++; + fb->last_frame_us = esp_timer_get_time(); + xSemaphoreGive(fb->mutex); + + return ESP_OK; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/framebuffer/framebuffer.h Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,250 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2021 Ruslan V. Uss <unclerus@gmail.com> + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +/** + * @file framebuffer.h + * @defgroup framebuffer framebuffer + * @{ + * + * Simple abstraction of RGB framebuffer for ESP-IDF + * + * Copyright (c) 2021 Ruslan V. Uss <unclerus@gmail.com> + * + * MIT Licensed as described in the file LICENSE + */ +#ifndef __FRAMEBUFFER_H__ +#define __FRAMEBUFFER_H__ + +#include <esp_err.h> +#include <color.h> +#include <freertos/FreeRTOS.h> +#include <freertos/semphr.h> + +#ifdef __cplusplus +extern "C" { +#endif + +#define FB_OFFSET(fb, x, y) ((fb)->width * (y) + (x)) + +#define FB_SIZE(fb) ((fb)->width * (fb)->height * sizeof(rgb_t)) + +typedef enum { + FB_SHIFT_LEFT = 0, + FB_SHIFT_RIGHT, + FB_SHIFT_UP, + FB_SHIFT_DOWN +} fb_shift_direction_t; + +typedef struct framebuffer_s framebuffer_t; + +/** + * Renderer callback prototype + */ +typedef esp_err_t (*fb_render_cb_t)(framebuffer_t *fb, void *arg); + +/** + * Framebuffer descriptor descriptor + */ +struct framebuffer_s +{ + rgb_t *data; ///< RGB framebuffer + size_t width; ///< Framebuffer width + size_t height; ///< Framebuffer height + size_t frame_num; ///< Number of rendered frames + uint64_t last_frame_us; ///< Time of last rendered frame since boot in microseconds + fb_render_cb_t render; ///< See ::fb_render() + uint8_t *internal; ///< Buffer for effect settings, internal vars, palettes and so on + SemaphoreHandle_t mutex; +}; + +/** + * @brief Initialize framebuffer + * + * @param fb Framebuffer descriptor + * @param width Frame width in pixels + * @param height Frame height in pixels + * @param render_cb Renderer callback function + * + * @return ESP_OK on success + */ +esp_err_t fb_init(framebuffer_t *fb, size_t width, size_t height, fb_render_cb_t render_cb); + +/** + * @brief Free Framebuffer descriptor buffers + * + * @param fb Framebuffer descriptor + * @return ESP_OK on success + */ +esp_err_t fb_free(framebuffer_t *fb); + +/** + * @brief Render frambuffer to actual display or LED strip + * + * Rendering is performed by calling the callback function with passing + * it as arguments \p fb and \p ctx + * + * @param fb Framebuffer descriptor + * @param ctx Argument to pass to callback + * @return ESP_OK on success + */ +esp_err_t fb_render(framebuffer_t *fb, void *ctx); + +/** + * @brief Set RGB color of framebuffer pixel + * + * @param fb Framebuffer descriptor + * @param x X coordinate + * @param y Y coordinate + * @param color RGB color + * @return ESP_OK on success + */ +esp_err_t fb_set_pixel_rgb(framebuffer_t *fb, size_t x, size_t y, rgb_t color); + +/** + * @brief Set HSV color of framebuffer pixel + * + * @param fb Framebuffer descriptor + * @param x X coordinate + * @param y Y coordinate + * @param color HSV color + * @return ESP_OK on success + */ +esp_err_t fb_set_pixel_hsv(framebuffer_t *fb, size_t x, size_t y, hsv_t color); + +/** + * @brief Set RGB pixel with subpixel resolution + * + * @param fb Framebuffer descriptor + * @param x X coordinate + * @param y Y coordinate + * @param color RGB color + * @return ESP_OK on success + */ +esp_err_t fb_set_pixelf_rgb(framebuffer_t *fb, float x, float y, rgb_t color); + +/** + * @brief Set HSV pixel with subpixel resolution + * + * @param fb Framebuffer descriptor + * @param x X coordinate + * @param y Y coordinate + * @param color HSV color + * @return ESP_OK on success + */ +esp_err_t fb_set_pixelf_hsv(framebuffer_t *fb, float x, float y, hsv_t color); + +/** + * @brief Get RGB color of framebuffer pixel + * + * @param fb Framebuffer descriptor + * @param x X coordinate + * @param y Y coordinate + * @param[out] color RGB color + * @return ESP_OK on success + */ +esp_err_t fb_get_pixel_rgb(framebuffer_t *fb, size_t x, size_t y, rgb_t *color); + +/** + * @brief Get HSV color of framebuffer pixel + * + * @param fb Framebuffer descriptor + * @param x X coordinate + * @param y Y coordinate + * @param[out] color HSV color + * @return ESP_OK on success + */ +esp_err_t fb_get_pixel_hsv(framebuffer_t *fb, size_t x, size_t y, hsv_t *color); + +/** + * @brief Clear framebuffer + * + * @param fb Framebuffer descriptor + * @return ESP_OK on success + */ +esp_err_t fb_clear(framebuffer_t *fb); + +/** + * @brief Shift framebuffer + * + * @param fb Framebuffer descriptor + * @param offs Shift size + * @param dir Shift direction + * @return ESP_OK on success + */ +esp_err_t fb_shift(framebuffer_t *fb, size_t offs, fb_shift_direction_t dir); + +/** + * @brief Fade pixels to black + * + * rgb_fade(pixel, scale) for all pixels in framebuffer + * + * @param fb Framebuffer descriptor + * @param scale Amount of scaling + * @return ESP_OK on success + */ +esp_err_t fb_fade(framebuffer_t *fb, uint8_t scale); + +/** + * @brief Aplly two-dimensional blur filter on framebuffer + * + * Spreads light to 8 XY neighbors. + * + * 0 = no spread at all + * 64 = moderate spreading + * 172 = maximum smooth, even spreading + * + * 173..255 = wider spreading, but increasing flicker + * + * @param fb Framebuffer descriptor + * @param amount Amount of bluring + * @return ESP_OK on success + */ +esp_err_t fb_blur2d(framebuffer_t *fb, fract8 amount); + +/** + * @brief Start frame rendering + * + * This function must be called in effects at the beginning of rendering frame + * + * @param fb Framebuffer descriptor + * @return ESP_OK on success + */ +esp_err_t fb_begin(framebuffer_t *fb); + +/** + * @brief Finish frame rendering + * + * This function must be called in effects at the end of rendering frame + * + * @param fb Framebuffer descriptor + * @return ESP_OK on success + */ +esp_err_t fb_end(framebuffer_t *fb); + +#ifdef __cplusplus +} +#endif + +/**@}*/ + +#endif /* __FRAMEBUFFER_H__ */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/hd44780/.eil.yml Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,22 @@ +--- +components: + - name: hd44780 + description: | + Driver for HD44780 compatible LCD text displays + group: misc + groups: [] + code_owners: + - name: UncleRus + depends: + - name: driver + thread_safe: no + targets: + - name: esp32 + - name: esp8266 + - name: esp32s2 + - name: esp32c3 + licenses: + - name: BSD-3 + copyrights: + - name: UncleRus + year: 2016
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/hd44780/CMakeLists.txt Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,11 @@ +if(${IDF_TARGET} STREQUAL esp8266) + set(req esp8266 freertos esp_idf_lib_helpers) +else() + set(req driver freertos esp_idf_lib_helpers) +endif() + +idf_component_register( + SRCS hd44780.c + INCLUDE_DIRS . + REQUIRES ${req} +)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/hd44780/LICENSE Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,26 @@ +Copyright (c) 2016, 2018 Ruslan V. Uss <unclerus@gmail.com> + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, +this list of conditions and the following disclaimer in the documentation +and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of itscontributors +may be used to endorse or promote products derived from this software without +specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/hd44780/component.mk Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,7 @@ +COMPONENT_ADD_INCLUDEDIRS = . + +ifdef CONFIG_IDF_TARGET_ESP8266 +COMPONENT_DEPENDS = esp8266 freertos log esp_idf_lib_helpers +else +COMPONENT_DEPENDS = driver freertos log esp_idf_lib_helpers +endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/hd44780/hd44780.c Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,291 @@ +/* + * Copyright (c) 2016 Ruslan V. Uss <unclerus@gmail.com> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of itscontributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file hd44780.c + * + * ESP-IDF driver for HD44780 compatible LCD text displays + * + * Ported from esp-open-rtos + * + * Copyright (c) 2016 Ruslan V. Uss <unclerus@gmail.com> + * + * BSD Licensed as described in the file LICENSE + */ +#include <string.h> +#include <esp_system.h> +#include <esp_idf_lib_helpers.h> +#include <ets_sys.h> +#include "hd44780.h" + +#define MS 1000 + +#define BV(x) (1 << (x)) +#define GPIO_BIT(x) (1ULL << (x)) + +#define DELAY_CMD_LONG (3 * MS) // >1.53ms according to datasheet +#define DELAY_CMD_SHORT (60) // >39us according to datasheet +#define DELAY_TOGGLE (1) // E cycle time >= 1μs, E pulse width >= 450ns, Data set-up time >= 195ns +#define DELAY_INIT (5 * MS) + +#define CMD_CLEAR 0x01 +#define CMD_RETURN_HOME 0x02 +#define CMD_ENTRY_MODE 0x04 +#define CMD_DISPLAY_CTRL 0x08 +#define CMD_SHIFT 0x10 +#define CMD_FUNC_SET 0x20 +#define CMD_CGRAM_ADDR 0x40 +#define CMD_DDRAM_ADDR 0x80 + +#define ARG_MOVE_RIGHT 0x04 +#define ARG_MOVE_LEFT 0x00 +#define CMD_SHIFT_LEFT (CMD_SHIFT | CMD_DISPLAY_CTRL | ARG_MOVE_LEFT) +#define CMD_SHIFT_RIGHT (CMD_SHIFT | CMD_DISPLAY_CTRL | ARG_MOVE_RIGHT) + +// CMD_ENTRY_MODE +#define ARG_EM_INCREMENT BV(1) +#define ARG_EM_SHIFT (1) + +// CMD_DISPLAY_CTRL +#define ARG_DC_DISPLAY_ON BV(2) +#define ARG_DC_CURSOR_ON BV(1) +#define ARG_DC_CURSOR_BLINK (1) + +// CMD_FUNC_SET +#define ARG_FS_8_BIT BV(4) +#define ARG_FS_2_LINES BV(3) +#define ARG_FS_FONT_5X10 BV(2) + +#define init_delay() do { ets_delay_us(DELAY_INIT); } while (0) +#define short_delay() do { ets_delay_us(DELAY_CMD_SHORT); } while (0) +#define long_delay() do { ets_delay_us(DELAY_CMD_LONG); } while (0) +#define toggle_delay() do { ets_delay_us(DELAY_TOGGLE); } while (0) + +#define CHECK_ARG(VAL) do { if (!(VAL)) return ESP_ERR_INVALID_ARG; } while (0) +#define CHECK(x) do { esp_err_t __; if ((__ = x) != ESP_OK) return __; } while (0) + +static const uint8_t line_addr[] = { 0x00, 0x40, 0x14, 0x54 }; + +static esp_err_t write_nibble(const hd44780_t *lcd, uint8_t b, bool rs) +{ + if (lcd->write_cb) + { + uint8_t data = (((b >> 3) & 1) << lcd->pins.d7) + | (((b >> 2) & 1) << lcd->pins.d6) + | (((b >> 1) & 1) << lcd->pins.d5) + | ((b & 1) << lcd->pins.d4) + | (rs ? 1 << lcd->pins.rs : 0) + | (lcd->backlight ? 1 << lcd->pins.bl : 0); + CHECK(lcd->write_cb(lcd, data | (1 << lcd->pins.e))); + toggle_delay(); + CHECK(lcd->write_cb(lcd, data)); + } + else + { + CHECK(gpio_set_level(lcd->pins.rs, rs)); + ets_delay_us(1); // Address Setup time >= 60ns. + CHECK(gpio_set_level(lcd->pins.e, true)); + CHECK(gpio_set_level(lcd->pins.d7, (b >> 3) & 1)); + CHECK(gpio_set_level(lcd->pins.d6, (b >> 2) & 1)); + CHECK(gpio_set_level(lcd->pins.d5, (b >> 1) & 1)); + CHECK(gpio_set_level(lcd->pins.d4, b & 1)); + toggle_delay(); + CHECK(gpio_set_level(lcd->pins.e, false)); + } + + return ESP_OK; +} + +static esp_err_t write_byte(const hd44780_t *lcd, uint8_t b, bool rs) +{ + CHECK(write_nibble(lcd, b >> 4, rs)); + CHECK(write_nibble(lcd, b, rs)); + + return ESP_OK; +} + +esp_err_t hd44780_init(const hd44780_t *lcd) +{ + CHECK_ARG(lcd && lcd->lines > 0 && lcd->lines < 5); + + if (!lcd->write_cb) + { + gpio_config_t io_conf; + memset(&io_conf, 0, sizeof(gpio_config_t)); + io_conf.mode = GPIO_MODE_OUTPUT; + io_conf.pin_bit_mask = + GPIO_BIT(lcd->pins.rs) | + GPIO_BIT(lcd->pins.e) | + GPIO_BIT(lcd->pins.d4) | + GPIO_BIT(lcd->pins.d5) | + GPIO_BIT(lcd->pins.d6) | + GPIO_BIT(lcd->pins.d7); + if (lcd->pins.bl != HD44780_NOT_USED) + io_conf.pin_bit_mask |= GPIO_BIT(lcd->pins.bl); + CHECK(gpio_config(&io_conf)); + } + + // switch to 4 bit mode + for (uint8_t i = 0; i < 3; i ++) + { + CHECK(write_nibble(lcd, (CMD_FUNC_SET | ARG_FS_8_BIT) >> 4, false)); + init_delay(); + } + CHECK(write_nibble(lcd, CMD_FUNC_SET >> 4, false)); + short_delay(); + + // Specify the number of display lines and character font + CHECK(write_byte(lcd, + CMD_FUNC_SET + | (lcd->lines > 1 ? ARG_FS_2_LINES : 0) + | (lcd->font == HD44780_FONT_5X10 ? ARG_FS_FONT_5X10 : 0), + false)); + short_delay(); + // Display off + CHECK(hd44780_control(lcd, false, false, false)); + // Clear + CHECK(hd44780_clear(lcd)); + // Entry mode set + CHECK(write_byte(lcd, CMD_ENTRY_MODE | ARG_EM_INCREMENT, false)); + short_delay(); + // Display on + CHECK(hd44780_control(lcd, true, false, false)); + + return ESP_OK; +} + +esp_err_t hd44780_control(const hd44780_t *lcd, bool on, bool cursor, bool cursor_blink) +{ + CHECK_ARG(lcd); + + CHECK(write_byte(lcd, + CMD_DISPLAY_CTRL + | (on ? ARG_DC_DISPLAY_ON : 0) + | (cursor ? ARG_DC_CURSOR_ON : 0) + | (cursor_blink ? ARG_DC_CURSOR_BLINK : 0), + false)); + short_delay(); + + return ESP_OK; +} + +esp_err_t hd44780_clear(const hd44780_t *lcd) +{ + CHECK_ARG(lcd); + + CHECK(write_byte(lcd, CMD_CLEAR, false)); + long_delay(); + + return ESP_OK; +} + +esp_err_t hd44780_gotoxy(const hd44780_t *lcd, uint8_t col, uint8_t line) +{ + CHECK_ARG(lcd && line < lcd->lines && line < sizeof(line_addr)); + + CHECK(write_byte(lcd, CMD_DDRAM_ADDR + line_addr[line] + col, false)); + short_delay(); + + return ESP_OK; +} + +esp_err_t hd44780_putc(const hd44780_t *lcd, char c) +{ + CHECK_ARG(lcd); + + CHECK(write_byte(lcd, c, true)); + short_delay(); + + return ESP_OK; +} + +esp_err_t hd44780_puts(const hd44780_t *lcd, const char *s) +{ + CHECK_ARG(lcd && s); + + while (*s) + { + CHECK(hd44780_putc(lcd, *s)); + s++; + } + + return ESP_OK; +} + +esp_err_t hd44780_switch_backlight(hd44780_t *lcd, bool on) +{ + CHECK_ARG(lcd); + if (lcd->pins.bl == HD44780_NOT_USED) + return ESP_ERR_NOT_SUPPORTED; + + if (!lcd->write_cb) + CHECK(gpio_set_level(lcd->pins.bl, on)); + else + CHECK(lcd->write_cb(lcd, on ? BV(lcd->pins.bl) : 0)); + + lcd->backlight = on; + + return ESP_OK; +} + +esp_err_t hd44780_upload_character(const hd44780_t *lcd, uint8_t num, const uint8_t *data) +{ + CHECK_ARG(lcd && data && num < 8); + + uint8_t bytes = lcd->font == HD44780_FONT_5X8 ? 8 : 10; + CHECK(write_byte(lcd, CMD_CGRAM_ADDR + num * bytes, false)); + short_delay(); + for (uint8_t i = 0; i < bytes; i ++) + { + CHECK(write_byte(lcd, data[i], true)); + short_delay(); + } + + CHECK(hd44780_gotoxy(lcd, 0, 0)); + + return ESP_OK; +} + +esp_err_t hd44780_scroll_left(const hd44780_t *lcd) +{ + CHECK_ARG(lcd); + + CHECK(write_byte(lcd, CMD_SHIFT_LEFT, false)); + short_delay(); + + return ESP_OK; +} + +esp_err_t hd44780_scroll_right(const hd44780_t *lcd) +{ + CHECK_ARG(lcd); + + CHECK(write_byte(lcd, CMD_SHIFT_RIGHT, false)); + short_delay(); + + return ESP_OK; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/hd44780/hd44780.h Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,194 @@ +/* + * Copyright (c) 2016 Ruslan V. Uss <unclerus@gmail.com> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of itscontributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file hd44780.h + * @defgroup hd44780 hd44780 + * @{ + * + * ESP-IDF driver for HD44780 compatible LCD text displays + * + * Ported from esp-open-rtos + * + * Copyright (c) 2016 Ruslan V. Uss <unclerus@gmail.com> + * + * BSD Licensed as described in the file LICENSE + */ +#ifndef __HD44780_H__ +#define __HD44780_H__ + +#include <stdint.h> +#include <stdbool.h> +#include <driver/gpio.h> +#include <esp_err.h> + +#ifdef __cplusplus +extern "C" { +#endif + +#define HD44780_NOT_USED 0xff + +/** + * LCD font type. Please refer to the datasheet + * of your module. + */ +typedef enum +{ + HD44780_FONT_5X8 = 0, + HD44780_FONT_5X10 +} hd44780_font_t; + +typedef struct hd44780 hd44780_t; + +typedef esp_err_t (*hd44780_write_cb_t)(const hd44780_t *lcd, uint8_t data); + +/** + * LCD descriptor. Fill it before use. + */ +struct hd44780 +{ + hd44780_write_cb_t write_cb; //!< Data write callback. Set it to NULL in case of direct LCD connection to GPIO + struct + { + uint8_t rs; //!< GPIO/register bit used for RS pin + uint8_t e; //!< GPIO/register bit used for E pin + uint8_t d4; //!< GPIO/register bit used for D4 pin + uint8_t d5; //!< GPIO/register bit used for D5 pin + uint8_t d6; //!< GPIO/register bit used for D5 pin + uint8_t d7; //!< GPIO/register bit used for D5 pin + uint8_t bl; //!< GPIO/register bit used for backlight. Set it `HD44780_NOT_USED` if no backlight used + } pins; + hd44780_font_t font; //!< LCD Font type + uint8_t lines; //!< Number of lines for LCD. Many 16x1 LCD has two lines (like 8x2) + bool backlight; //!< Current backlight state +}; + +/** + * @brief Init LCD + * + * Set cursor position to (0, 0) + * + * @param lcd LCD descriptor + * @return `ESP_OK` on success + */ +esp_err_t hd44780_init(const hd44780_t *lcd); + +/** + * @brief Control LCD + * + * On/off LCD, show/hide cursor, set cursor blink + * + * @param lcd LCD descriptor + * @param on Switch LCD on if true + * @param cursor Show cursor if true + * @param cursor_blink Enable cursor blinking if true + * @return `ESP_OK` on success + */ +esp_err_t hd44780_control(const hd44780_t *lcd, bool on, bool cursor, bool cursor_blink); + +/** + * @brief Clear LCD + * + * Clear memory and move cursor to (0, 0) + * + * @param lcd LCD descriptor + * @return `ESP_OK` on success + */ +esp_err_t hd44780_clear(const hd44780_t *lcd); + +/** + * @brief Move cursor + * + * @param lcd LCD descriptor + * @param col Column + * @param line Line + * @return `ESP_OK` on success + */ +esp_err_t hd44780_gotoxy(const hd44780_t *lcd, uint8_t col, uint8_t line); + +/** + * @brief Write character at cursor position + * + * @param lcd LCD descriptor + * @param c Character to write + * @return `ESP_OK` on success + */ +esp_err_t hd44780_putc(const hd44780_t *lcd, char c); + +/** + * @brief Write NULL-terminated string at cursor position + * + * @param lcd LCD descriptor + * @param s String to write + * @return `ESP_OK` on success + */ +esp_err_t hd44780_puts(const hd44780_t *lcd, const char *s); + +/** + * @brief Switch backlight + * + * @param lcd LCD descriptor + * @param on Turn backlight on if true + * @return `ESP_OK` on success + */ +esp_err_t hd44780_switch_backlight(hd44780_t *lcd, bool on); + +/** + * @brief Upload character data to the CGRAM + * + * After upload cursor will be moved to (0, 0). + * + * @param lcd LCD descriptor + * @param num Character number (0..7) + * @param data Character data: 8 or 10 bytes depending on the font + * @return `ESP_OK` on success + */ +esp_err_t hd44780_upload_character(const hd44780_t *lcd, uint8_t num, const uint8_t *data); + +/** + * @brief Scroll the display content to left by one character + * + * @param lcd LCD descriptor + * @return `ESP_OK` on success + */ +esp_err_t hd44780_scroll_left(const hd44780_t *lcd); + +/** + * @brief Scroll the display content to right by one character + * + * @param lcd LCD descriptor + * @return `ESP_OK` on success + */ +esp_err_t hd44780_scroll_right(const hd44780_t *lcd); + +#ifdef __cplusplus +} +#endif + +/**@}*/ + +#endif /* __HD44780_H__ */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/hdc1000/.eil.yml Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,25 @@ +--- +components: + - name: hdc1000 + description: | + Driver for HDC1000 temperature and humidity sensor + group: temperature + groups: + - humidity + code_owners: + - name: UncleRus + depends: + - name: i2cdev + - name: log + - name: esp_idf_lib_helpers + thread_safe: yes + targets: + - name: esp32 + - name: esp8266 + - name: esp32s2 + - name: esp32c3 + licenses: + - name: BSD-3 + copyrights: + - name: UncleRus + year: 2021
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/hdc1000/CMakeLists.txt Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,11 @@ +if(${IDF_VERSION_MAJOR} STREQUAL 4 AND ${IDF_VERSION_MINOR} STREQUAL 1 AND ${IDF_VERSION_PATCH} STREQUAL 3) + set(req i2cdev log esp_idf_lib_helpers) +else() + set(req i2cdev log esp_idf_lib_helpers esp_timer) +endif() + +idf_component_register( + SRCS hdc1000.c + INCLUDE_DIRS . + REQUIRES ${req} +)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/hdc1000/LICENSE Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,26 @@ +Copyright 2021 Ruslan V. Uss <unclerus@gmail.com> + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, +this list of conditions and the following disclaimer in the documentation +and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of itscontributors +may be used to endorse or promote products derived from this software without +specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/hdc1000/component.mk Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,2 @@ +COMPONENT_ADD_INCLUDEDIRS = . +COMPONENT_DEPENDS = i2cdev log esp_idf_lib_helpers
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/hdc1000/hdc1000.c Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,334 @@ +/* + * Copyright (c) 2021 Ruslan V. Uss <unclerus@gmail.com> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of itscontributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file hdc1000.c + * + * ESP-IDF driver for HDC1000 temperature and humidity sensor + * + * Copyright (c) 2021 Ruslan V. Uss <unclerus@gmail.com> + * + * BSD Licensed as described in the file LICENSE + */ +#include "hdc1000.h" +#include <freertos/FreeRTOS.h> +#include <freertos/task.h> +#include <esp_log.h> +#include <esp_timer.h> + +#define I2C_FREQ_HZ 400000 // 400kHz + +static const char *TAG = "hdc1000"; + +#define REG_TEMPERATURE 0x00 +#define REG_HUMIDITY 0x01 +#define REG_CONFIG 0x02 +#define REG_SERIAL_H 0xfb +#define REG_SERIAL_M 0xfc +#define REG_SERIAL_L 0xfd +#define REG_MANUF_ID 0xfe +#define REG_DEVICE_ID 0xff + +#define BIT_CONFIG_RST 15 +#define BIT_CONFIG_HEAT 13 +#define BIT_CONFIG_MODE 12 +#define BIT_CONFIG_BTST 11 +#define BIT_CONFIG_TRES 10 +#define BIT_CONFIG_HRES 8 + +#define MASK_CONFIG_HRES (3 << BIT_CONFIG_HRES) + +#define RESET_TIMEOUT_US (200 * 1000) +#define MEASURE_TIMEOUT_MS 10 // 6.5 ms + +#define CHECK(x) do { esp_err_t __; if ((__ = x) != ESP_OK) return __; } while (0) +#define CHECK_ARG(VAL) do { if (!(VAL)) return ESP_ERR_INVALID_ARG; } while (0) + +static inline uint16_t shuffle(uint16_t v) +{ + return (v >> 8) | (v << 8); +} + +static esp_err_t read_reg_nolock(hdc1000_t *dev, uint8_t reg, uint16_t *val) +{ + CHECK(i2c_dev_read_reg(&dev->i2c_dev, reg, val, 2)); + *val = shuffle(*val); + return ESP_OK; +} + +static esp_err_t write_reg_nolock(hdc1000_t *dev, uint8_t reg, uint16_t val) +{ + uint16_t v = shuffle(val); + return i2c_dev_write_reg(&dev->i2c_dev, reg, &v, 2); +} + +static esp_err_t update_reg_nolock(hdc1000_t *dev, uint8_t reg, uint16_t val, uint16_t mask) +{ + uint16_t v; + CHECK(read_reg_nolock(dev, reg, &v)); + v = (v & mask) | (val & mask); + return write_reg_nolock(dev, reg, v); +} + +static esp_err_t read_reg(hdc1000_t *dev, uint8_t reg, uint16_t *val) +{ + I2C_DEV_TAKE_MUTEX(&dev->i2c_dev); + I2C_DEV_CHECK(&dev->i2c_dev, read_reg_nolock(dev, reg, val)); + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); + return ESP_OK; +} + +static esp_err_t update_reg(hdc1000_t *dev, uint8_t reg, uint16_t val, uint16_t mask) +{ + I2C_DEV_TAKE_MUTEX(&dev->i2c_dev); + I2C_DEV_CHECK(&dev->i2c_dev, update_reg_nolock(dev, reg, val, mask)); + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); + return ESP_OK; +} + +static float calc_temperature(uint8_t *buf) +{ + uint16_t raw = ((uint16_t)buf[0] << 8) | buf[1]; + return raw / 65536.0f * 165.0f - 40; +} + +static float calc_humidity(uint8_t *buf) +{ + uint16_t raw = ((uint16_t)buf[0] << 8) | buf[1]; + return raw / 65536.0f * 100.0f; +} + +/////////////////////////////////////////////////////////////////////////////// + +esp_err_t hdc1000_init_desc(hdc1000_t *dev, uint8_t addr, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio) +{ + CHECK_ARG(dev); + + if (addr < HDC1000_I2C_ADDRESS_0 || addr > HDC1000_I2C_ADDRESS_3) + { + ESP_LOGE(TAG, "Invalid I2C address: 0x%02x", addr); + return ESP_ERR_INVALID_ARG; + } + + dev->i2c_dev.port = port; + dev->i2c_dev.addr = addr; + dev->i2c_dev.cfg.sda_io_num = sda_gpio; + dev->i2c_dev.cfg.scl_io_num = scl_gpio; +#if HELPER_TARGET_IS_ESP32 + dev->i2c_dev.cfg.master.clk_speed = I2C_FREQ_HZ; +#endif + + return i2c_dev_create_mutex(&dev->i2c_dev); +} + +esp_err_t hdc1000_free_desc(hdc1000_t *dev) +{ + CHECK_ARG(dev); + + return i2c_dev_delete_mutex(&dev->i2c_dev); +} + +esp_err_t hdc1000_init(hdc1000_t *dev) +{ + CHECK_ARG(dev); + + esp_err_t res = ESP_OK; + + I2C_DEV_TAKE_MUTEX(&dev->i2c_dev); + + // reset + I2C_DEV_CHECK(&dev->i2c_dev, update_reg_nolock(dev, REG_CONFIG, BIT(BIT_CONFIG_RST), BIT(BIT_CONFIG_RST))); + // wait + int64_t start = esp_timer_get_time(); + while (true) + { + if ((esp_timer_get_time() - start) >= RESET_TIMEOUT_US) + { + ESP_LOGE(TAG, "Timeout while reseting device"); + res = ESP_ERR_TIMEOUT; + goto exit; + } + uint16_t r; + I2C_DEV_CHECK(&dev->i2c_dev, read_reg_nolock(dev, REG_CONFIG, &r)); + if (!(r & BIT(BIT_CONFIG_RST))) + break; + vTaskDelay(1); + } + dev->mode = HDC1000_MEASURE_BOTH; + +exit: + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); + return res; +} + +esp_err_t hdc1000_get_serial(hdc1000_t *dev, uint64_t *serial) +{ + CHECK_ARG(dev && serial); + + I2C_DEV_TAKE_MUTEX(&dev->i2c_dev); + + *serial = 0; + + uint16_t *dst = (uint16_t *)serial; + I2C_DEV_CHECK(&dev->i2c_dev, read_reg_nolock(dev, REG_SERIAL_L, dst)); + I2C_DEV_CHECK(&dev->i2c_dev, read_reg_nolock(dev, REG_SERIAL_M, ++dst)); + I2C_DEV_CHECK(&dev->i2c_dev, read_reg_nolock(dev, REG_SERIAL_H, ++dst)); + + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); + + return ESP_OK; +} + +esp_err_t hdc1000_get_manufacturer_id(hdc1000_t *dev, uint16_t *id) +{ + CHECK_ARG(dev && id); + + return read_reg(dev, REG_MANUF_ID, id); +} + +esp_err_t hdc1000_get_device_id(hdc1000_t *dev, uint16_t *id) +{ + CHECK_ARG(dev && id); + + return read_reg(dev, REG_DEVICE_ID, id); +} + +esp_err_t hdc1000_get_battery_status(hdc1000_t *dev, bool *undervolt) +{ + CHECK_ARG(dev && undervolt); + + uint16_t v; + CHECK(read_reg(dev, REG_CONFIG, &v)); + *undervolt = v & BIT(BIT_CONFIG_BTST) ? true : false; + + return ESP_OK; +} + +esp_err_t hdc1000_get_heater(hdc1000_t *dev, bool *on) +{ + CHECK_ARG(dev && on); + + uint16_t v; + CHECK(read_reg(dev, REG_CONFIG, &v)); + *on = v & BIT(BIT_CONFIG_HEAT) ? true : false; + + return ESP_OK; +} + +esp_err_t hdc1000_set_heater(hdc1000_t *dev, bool on) +{ + CHECK_ARG(dev); + + return update_reg(dev, REG_CONFIG, on ? BIT(BIT_CONFIG_HEAT) : 0, BIT(BIT_CONFIG_HEAT)); +} + +esp_err_t hdc1000_set_measurement_mode(hdc1000_t *dev, hdc1000_measurement_mode_t mode) +{ + CHECK_ARG(dev && mode <= HDC1000_MEASURE_BOTH); + + dev->mode = mode; + return update_reg(dev, REG_CONFIG, mode == HDC1000_MEASURE_BOTH ? BIT(BIT_CONFIG_MODE) : 0, BIT(BIT_CONFIG_MODE)); +} + +esp_err_t hdc1000_get_resolution(hdc1000_t *dev, hdc1000_temperature_resolution_t *tres, hdc1000_humidity_resolution_t *hres) +{ + CHECK_ARG(dev && (tres || hres)); + + uint16_t v; + CHECK(read_reg(dev, REG_CONFIG, &v)); + + if (tres) + *tres = (v >> BIT_CONFIG_TRES) & 1; + if (hres) + *hres = (v >> BIT_CONFIG_HRES) & 3; + + return ESP_OK; +} + +esp_err_t hdc1000_set_resolution(hdc1000_t *dev, hdc1000_temperature_resolution_t tres, hdc1000_humidity_resolution_t hres) +{ + CHECK_ARG(dev && tres <= HDC1000_T_RES_11 && hres <= HDC1000_H_RES_8); + + return update_reg(dev, REG_CONFIG, (tres << BIT_CONFIG_TRES) | (hres << BIT_CONFIG_HRES), + BIT(BIT_CONFIG_TRES) | MASK_CONFIG_HRES); +} + +esp_err_t hdc1000_trigger_measurement(hdc1000_t *dev) +{ + CHECK_ARG(dev); + + uint8_t r = dev->mode == HDC1000_MEASURE_HUMIDITY ? REG_HUMIDITY : REG_TEMPERATURE; + + I2C_DEV_TAKE_MUTEX(&dev->i2c_dev); + I2C_DEV_CHECK(&dev->i2c_dev, i2c_dev_write(&dev->i2c_dev, NULL, 0, &r, 1)); + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); + + return ESP_OK; +} + +esp_err_t hdc1000_get_data(hdc1000_t *dev, float *t, float *rh) +{ + CHECK_ARG(dev && (t || rh)); + + if ((!t && dev->mode == HDC1000_MEASURE_TEMPERATURE) + || (!rh && dev->mode == HDC1000_MEASURE_HUMIDITY)) + { + ESP_LOGE(TAG, "Measurement mode does not match hdc1000_get_data() arguments"); + return ESP_ERR_INVALID_ARG; + } + + uint8_t buf[4] = { 0 }; + + I2C_DEV_TAKE_MUTEX(&dev->i2c_dev); + I2C_DEV_CHECK(&dev->i2c_dev, i2c_dev_read(&dev->i2c_dev, NULL, 0, buf, dev->mode == HDC1000_MEASURE_BOTH ? 4 : 2)); + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); + + switch (dev->mode) + { + case HDC1000_MEASURE_TEMPERATURE: + *t = calc_temperature(buf); + break; + case HDC1000_MEASURE_HUMIDITY: + *rh = calc_humidity(buf); + break; + case HDC1000_MEASURE_BOTH: + if (t) + *t = calc_temperature(buf); + if (rh) + *rh = calc_humidity(buf + 2); + break; + } + + return ESP_OK; +} + +esp_err_t hdc1000_measure(hdc1000_t *dev, float *t, float *rh) +{ + CHECK(hdc1000_trigger_measurement(dev)); + vTaskDelay(pdMS_TO_TICKS(dev->mode == HDC1000_MEASURE_BOTH ? 2 * MEASURE_TIMEOUT_MS : MEASURE_TIMEOUT_MS)); + return hdc1000_get_data(dev, t, rh); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/hdc1000/hdc1000.h Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,239 @@ +/* + * Copyright (c) 2021 Ruslan V. Uss <unclerus@gmail.com> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of itscontributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file hdc1000.h + * @defgroup hdc1000 hdc1000 + * @{ + * + * ESP-IDF driver for HDC1000 temperature and humidity sensor + * + * Copyright (c) 2021 Ruslan V. Uss <unclerus@gmail.com> + * + * BSD Licensed as described in the file LICENSE + */ +#ifndef __HDC1000_H__ +#define __HDC1000_H__ + +#include <i2cdev.h> +#include <stdbool.h> + +#define HDC1000_I2C_ADDRESS_0 0x40 //!< I2C address when ADR1 = 0, ADR0 = 0 +#define HDC1000_I2C_ADDRESS_1 0x41 //!< I2C address when ADR1 = 0, ADR0 = 1 +#define HDC1000_I2C_ADDRESS_2 0x42 //!< I2C address when ADR1 = 1, ADR0 = 0 +#define HDC1000_I2C_ADDRESS_3 0x43 //!< I2C address when ADR1 = 1, ADR0 = 1 + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Temperature resolution + */ +typedef enum { + HDC1000_T_RES_14 = 0, /**< 14 bits, default */ + HDC1000_T_RES_11, /**< 11 bits */ +} hdc1000_temperature_resolution_t; + +/** + * Humidity resolution + */ +typedef enum { + HDC1000_H_RES_14 = 0, /**< 14 bits, default */ + HDC1000_H_RES_11, /**< 11 bits */ + HDC1000_H_RES_8, /**< 8 bits */ +} hdc1000_humidity_resolution_t; + +/** + * Measurement mode + */ +typedef enum { + HDC1000_MEASURE_TEMPERATURE = 0, /**< Temperature only */ + HDC1000_MEASURE_HUMIDITY, /**< Humidity only */ + HDC1000_MEASURE_BOTH, /**< Both temperature and humidity, default */ +} hdc1000_measurement_mode_t; + +/** + * Device descriptor + */ +typedef struct +{ + i2c_dev_t i2c_dev; + hdc1000_measurement_mode_t mode; +} hdc1000_t; + +/** + * @brief Initialize device descriptor + * + * @param dev Device descriptor + * @param addr Device I2C address + * @param port I2C port + * @param sda_gpio SDA GPIO + * @param scl_gpio SCL GPIO + * @return `ESP_OK` on success + */ +esp_err_t hdc1000_init_desc(hdc1000_t *dev, uint8_t addr, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio); + +/** + * @brief Free device descriptor + * + * @param dev Device descriptor + * @return `ESP_OK` on success + */ +esp_err_t hdc1000_free_desc(hdc1000_t *dev); + +/** + * @brief Init device + * + * Soft-reset device, set default measurement mode and resolutions + * + * @param dev Device descriptor + * @return `ESP_OK` on success + */ +esp_err_t hdc1000_init(hdc1000_t *dev); + +/** + * @brief Read serial number of device + * + * @param dev Device descriptor + * @param[out] serial Serial number + * @return `ESP_OK` on success + */ +esp_err_t hdc1000_get_serial(hdc1000_t *dev, uint64_t *serial); + +/** + * @brief Read manufacturer ID of device + * + * @param dev Device descriptor + * @param[out] id Manufacturer ID + * @return `ESP_OK` on success + */ +esp_err_t hdc1000_get_manufacturer_id(hdc1000_t *dev, uint16_t *id); + +/** + * @brief Read device ID + * + * @param dev Device descriptor + * @param[out] id Device ID + * @return `ESP_OK` on success + */ +esp_err_t hdc1000_get_device_id(hdc1000_t *dev, uint16_t *id); + +/** + * @brief Read battery status + * + * @param dev Device descriptor + * @param[out] undervolt true when battery voltage is lower than 2.8V + * @return `ESP_OK` on success + */ +esp_err_t hdc1000_get_battery_status(hdc1000_t *dev, bool *undervolt); + +/** + * @brief Get heater status + * + * @param dev Device descriptor + * @param[out] on true when heater is enabled + * @return `ESP_OK` on success + */ +esp_err_t hdc1000_get_heater(hdc1000_t *dev, bool *on); + +/** + * @brief Switch heater on/off + * + * @param dev Device descriptor + * @param on true to enable heater + * @return `ESP_OK` on success + */ +esp_err_t hdc1000_set_heater(hdc1000_t *dev, bool on); + +/** + * @brief Set measurement mode + * + * @param dev Device descriptor + * @param mode Measurement mode + * @return `ESP_OK` on success + */ +esp_err_t hdc1000_set_measurement_mode(hdc1000_t *dev, hdc1000_measurement_mode_t mode); + +/** + * @brief Get measurement resolutions + * + * @param dev Device descriptor + * @param[out] tres Temperature measurement resultion + * @param[out] hres Humidity measurement resultion + * @return `ESP_OK` on success + */ +esp_err_t hdc1000_get_resolution(hdc1000_t *dev, hdc1000_temperature_resolution_t *tres, hdc1000_humidity_resolution_t *hres); + +/** + * @brief Set measurement resolutions + * + * @param dev Device descriptor + * @param tres Temperature measurement resultion + * @param hres Humidity measurement resultion + * @return `ESP_OK` on success + */ +esp_err_t hdc1000_set_resolution(hdc1000_t *dev, hdc1000_temperature_resolution_t tres, hdc1000_humidity_resolution_t hres); + +/** + * @brief Trigger measurement + * + * @param dev Device descriptor + * @return `ESP_OK` on success + */ +esp_err_t hdc1000_trigger_measurement(hdc1000_t *dev); + +/** + * @brief Read output data + * + * @param dev Device descriptor + * @param[out] t Temperature, degrees Celsius (nullable) + * @param[out] rh Relative humidity, % (nullable) + * @return `ESP_OK` on success + */ +esp_err_t hdc1000_get_data(hdc1000_t *dev, float *t, float *rh); + +/** + * @brief Measure + * + * Trigger measurement, wait and read output data + * + * @param dev Device descriptor + * @param[out] t Temperature, degrees Celsius (nullable) + * @param[out] rh Relative humidity, % (nullable) + * @return `ESP_OK` on success + */ +esp_err_t hdc1000_measure(hdc1000_t *dev, float *t, float *rh); + + +#ifdef __cplusplus +} +#endif + +/**@}*/ + +#endif /* __HDC1000_H__ */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/hmc5883l/.eil.yml Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,24 @@ +--- +components: + - name: hmc5883l + description: | + Driver for 3-axis digital compass HMC5883L and HMC5983L + group: magnetic + groups: [] + code_owners: + - name: UncleRus + depends: + - name: i2cdev + - name: log + - name: esp_idf_lib_helpers + thread_safe: yes + targets: + - name: esp32 + - name: esp8266 + - name: esp32s2 + - name: esp32c3 + licenses: + - name: BSD-3 + copyrights: + - name: UncleRus + year: 2016
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/hmc5883l/CMakeLists.txt Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,11 @@ +if(${IDF_VERSION_MAJOR} STREQUAL 4 AND ${IDF_VERSION_MINOR} STREQUAL 1 AND ${IDF_VERSION_PATCH} STREQUAL 3) + set(req i2cdev log esp_idf_lib_helpers) +else() + set(req i2cdev log esp_idf_lib_helpers esp_timer) +endif() + +idf_component_register( + SRCS hmc5883l.c + INCLUDE_DIRS . + REQUIRES ${req} +)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/hmc5883l/Kconfig Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,8 @@ +menu "HMC5883L" + +config HMC5883L_MEAS_TIMEOUT + int "Measurement timeout, microseconds" + default 6000 + range 500 10000 + +endmenu
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/hmc5883l/LICENSE Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,26 @@ +Copyright 2016, 2018 Ruslan V. Uss <unclerus@gmail.com> + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, +this list of conditions and the following disclaimer in the documentation +and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of itscontributors +may be used to endorse or promote products derived from this software without +specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/hmc5883l/component.mk Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,2 @@ +COMPONENT_ADD_INCLUDEDIRS = . +COMPONENT_DEPENDS = i2cdev log esp_idf_lib_helpers
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/hmc5883l/hmc5883l.c Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,358 @@ +/* + * Copyright (c) 2016 Ruslan V. Uss <unclerus@gmail.com> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of itscontributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file hmc5883l.c + * + * ESP-IDF Driver for 3-axis digital compass HMC5883L and HMC5983L + * + * Ported from esp-open-rtos + * + * Copyright (c) 2016, 2018 Ruslan V. Uss <unclerus@gmail.com> + * + * BSD Licensed as described in the file LICENSE + */ +#include "hmc5883l.h" +#include <inttypes.h> +#include <esp_log.h> +#include <esp_err.h> +#include <esp_timer.h> +#include <esp_idf_lib_helpers.h> + +#define REG_CR_A 0x00 +#define REG_CR_B 0x01 +#define REG_MODE 0x02 +#define REG_DX_H 0x03 +#define REG_DX_L 0x04 +#define REG_DZ_H 0x05 +#define REG_DZ_L 0x06 +#define REG_DY_H 0x07 +#define REG_DY_L 0x08 +#define REG_STAT 0x09 +#define REG_ID_A 0x0a +#define REG_ID_B 0x0b +#define REG_ID_C 0x0c + +#define BIT_MA 5 +#define BIT_DO 2 +#define BIT_GN 5 + +#define MASK_MD 0x03 +#define MASK_MA 0x60 +#define MASK_DO 0x1c +#define MASK_MS 0x03 +#define MASK_DR 0x01 +#define MASK_DL 0x02 + +#define I2C_FREQ_HZ 400000 + +static const char *TAG = "hmc5883l"; + +static const float gain_values [] = { + [HMC5883L_GAIN_1370] = 0.73, + [HMC5883L_GAIN_1090] = 0.92, + [HMC5883L_GAIN_820] = 1.22, + [HMC5883L_GAIN_660] = 1.52, + [HMC5883L_GAIN_440] = 2.27, + [HMC5883L_GAIN_390] = 2.56, + [HMC5883L_GAIN_330] = 3.03, + [HMC5883L_GAIN_230] = 4.35 +}; + +#define timeout_expired(start, len) ((uint64_t)(esp_timer_get_time() - (start)) >= (len)) +#define CHECK_ARG(VAL) do { if (!(VAL)) return ESP_ERR_INVALID_ARG; } while (0) + +esp_err_t hmc5883l_init_desc(hmc5883l_dev_t *dev, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio) +{ + CHECK_ARG(dev); + + dev->i2c_dev.port = port; + dev->i2c_dev.addr = HMC5883L_ADDR; + dev->i2c_dev.cfg.sda_io_num = sda_gpio; + dev->i2c_dev.cfg.scl_io_num = scl_gpio; +#if HELPER_TARGET_IS_ESP32 + dev->i2c_dev.cfg.master.clk_speed = I2C_FREQ_HZ; +#endif + return i2c_dev_create_mutex(&dev->i2c_dev); +} + +esp_err_t hmc5883l_free_desc(hmc5883l_dev_t *dev) +{ + CHECK_ARG(dev); + + return i2c_dev_delete_mutex(&dev->i2c_dev); +} + +static esp_err_t write_register(hmc5883l_dev_t *dev, uint8_t reg, uint8_t val) +{ + esp_err_t ret = i2c_dev_write_reg(&dev->i2c_dev, reg, &val, 1); + if (ret != ESP_OK) + ESP_LOGE(TAG, "Could not write 0x%02x to register 0x%02x, err = %d", val, reg, ret); + return ret; +} + +static inline esp_err_t read_register(hmc5883l_dev_t *dev, uint8_t reg, uint8_t *val) +{ + esp_err_t ret = i2c_dev_read_reg(&dev->i2c_dev, reg, val, 1); + if (ret != ESP_OK) + ESP_LOGE(TAG, "Could not read register 0x%02x, err = %d", reg, ret); + return ret; +} + +static esp_err_t update_register(hmc5883l_dev_t *dev, uint8_t reg, uint8_t mask, uint8_t val) +{ + uint8_t old; + esp_err_t ret = read_register(dev, reg, &old); + if (ret != ESP_OK) + return ret; + return write_register(dev, reg, (old & mask) | val); +} + +#define CHECK(X) do { \ + esp_err_t __ = X; \ + if (__ != ESP_OK) return __; \ + } while (0) + +esp_err_t hmc5883l_init(hmc5883l_dev_t *dev) +{ + CHECK_ARG(dev); + + I2C_DEV_TAKE_MUTEX(&dev->i2c_dev); + + uint32_t id = 0; + I2C_DEV_CHECK(&dev->i2c_dev, i2c_dev_read_reg(&dev->i2c_dev, REG_ID_A, &id, 3)); + if (id != HMC5883L_ID) + { + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); + ESP_LOGE(TAG, "Unknown ID: 0x%08" PRIx32 " != 0x%08x", id, HMC5883L_ID); + return ESP_ERR_NOT_FOUND; + } + + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); + + hmc5883l_gain_t gain; + CHECK(hmc5883l_get_gain(dev, &gain)); + dev->gain = gain_values[gain]; + + CHECK(hmc5883l_get_opmode(dev, &dev->opmode)); + + return ESP_OK; +} + +esp_err_t hmc5883l_get_opmode(hmc5883l_dev_t *dev, hmc5883l_opmode_t *val) +{ + CHECK_ARG(dev && val); + + I2C_DEV_TAKE_MUTEX(&dev->i2c_dev); + I2C_DEV_CHECK(&dev->i2c_dev, read_register(dev, REG_MODE, (uint8_t *)val)); + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); + + *val = (*val & MASK_MD) == 0 ? HMC5883L_MODE_CONTINUOUS : HMC5883L_MODE_SINGLE; + return ESP_OK; +} + +esp_err_t hmc5883l_set_opmode(hmc5883l_dev_t *dev, hmc5883l_opmode_t mode) +{ + CHECK_ARG(dev); + + I2C_DEV_TAKE_MUTEX(&dev->i2c_dev); + I2C_DEV_CHECK(&dev->i2c_dev, write_register(dev, REG_MODE, mode)); + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); + + dev->opmode = mode; + return ESP_OK; +} + +esp_err_t hmc5883l_get_samples_averaged(hmc5883l_dev_t *dev, hmc5883l_samples_averaged_t *val) +{ + CHECK_ARG(dev && val); + + I2C_DEV_TAKE_MUTEX(&dev->i2c_dev); + I2C_DEV_CHECK(&dev->i2c_dev, read_register(dev, REG_CR_A, (uint8_t *)val)); + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); + + *val = (*val & MASK_MA) >> BIT_MA; + return ESP_OK; +} + +esp_err_t hmc5883l_set_samples_averaged(hmc5883l_dev_t *dev, hmc5883l_samples_averaged_t samples) +{ + CHECK_ARG(dev); + + I2C_DEV_TAKE_MUTEX(&dev->i2c_dev); + I2C_DEV_CHECK(&dev->i2c_dev, update_register(dev, REG_CR_A, MASK_MA, samples << BIT_MA)); + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); + + return ESP_OK; +} + +esp_err_t hmc5883l_get_data_rate(hmc5883l_dev_t *dev, hmc5883l_data_rate_t *val) +{ + CHECK_ARG(dev && val); + + I2C_DEV_TAKE_MUTEX(&dev->i2c_dev); + I2C_DEV_CHECK(&dev->i2c_dev, read_register(dev, REG_CR_A, (uint8_t *)val)); + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); + + *val = (*val & MASK_DO) >> BIT_DO; + return ESP_OK; +} + +esp_err_t hmc5883l_set_data_rate(hmc5883l_dev_t *dev, hmc5883l_data_rate_t rate) +{ + CHECK_ARG(dev); + + I2C_DEV_TAKE_MUTEX(&dev->i2c_dev); + I2C_DEV_CHECK(&dev->i2c_dev, update_register(dev, REG_CR_A, MASK_DO, rate << BIT_DO)); + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); + + return ESP_OK; +} + +esp_err_t hmc5883l_get_bias(hmc5883l_dev_t *dev, hmc5883l_bias_t *val) +{ + CHECK_ARG(dev && val); + + I2C_DEV_TAKE_MUTEX(&dev->i2c_dev); + I2C_DEV_CHECK(&dev->i2c_dev, read_register(dev, REG_CR_A, (uint8_t *)val)); + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); + + *val &= MASK_MS; + return ESP_OK; +} + +esp_err_t hmc5883l_set_bias(hmc5883l_dev_t *dev, hmc5883l_bias_t bias) +{ + CHECK_ARG(dev); + + I2C_DEV_TAKE_MUTEX(&dev->i2c_dev); + I2C_DEV_CHECK(&dev->i2c_dev, update_register(dev, REG_CR_A, MASK_MS, bias)); + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); + + return ESP_OK; +} + +esp_err_t hmc5883l_get_gain(hmc5883l_dev_t *dev, hmc5883l_gain_t *val) +{ + CHECK_ARG(dev && val); + + I2C_DEV_TAKE_MUTEX(&dev->i2c_dev); + I2C_DEV_CHECK(&dev->i2c_dev, read_register(dev, REG_CR_B, (uint8_t *)val)); + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); + + *val >>= BIT_GN; + return ESP_OK; +} + +esp_err_t hmc5883l_set_gain(hmc5883l_dev_t *dev, hmc5883l_gain_t gain) +{ + I2C_DEV_TAKE_MUTEX(&dev->i2c_dev); + I2C_DEV_CHECK(&dev->i2c_dev, write_register(dev, REG_CR_B, gain << BIT_GN)); + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); + + dev->gain = gain_values[gain]; + return ESP_OK; +} + +esp_err_t hmc5883l_data_is_locked(hmc5883l_dev_t *dev, bool *val) +{ + CHECK_ARG(dev && val); + + I2C_DEV_TAKE_MUTEX(&dev->i2c_dev); + I2C_DEV_CHECK(&dev->i2c_dev, read_register(dev, REG_STAT, (uint8_t *)val)); + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); + + *val &= MASK_DL; + return ESP_OK; +} + +esp_err_t hmc5883l_data_is_ready(hmc5883l_dev_t *dev, bool *val) +{ + CHECK_ARG(dev && val); + + I2C_DEV_TAKE_MUTEX(&dev->i2c_dev); + I2C_DEV_CHECK(&dev->i2c_dev, read_register(dev, REG_STAT, (uint8_t *)val)); + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); + + *val &= MASK_DR; + return ESP_OK; +} + +esp_err_t hmc5883l_get_raw_data(hmc5883l_dev_t *dev, hmc5883l_raw_data_t *data) +{ + CHECK_ARG(dev && data); + + if (dev->opmode == HMC5883L_MODE_SINGLE) + { + // overwrite mode register for measurement + CHECK(hmc5883l_set_opmode(dev, dev->opmode)); + // wait for data + uint64_t start = esp_timer_get_time(); + bool dready = false; + do + { + CHECK(hmc5883l_data_is_ready(dev, &dready)); + if (timeout_expired(start, CONFIG_HMC5883L_MEAS_TIMEOUT)) + return ESP_ERR_TIMEOUT; + } while (!dready); + } + uint8_t buf[6]; + uint8_t reg = REG_DX_H; + + I2C_DEV_TAKE_MUTEX(&dev->i2c_dev); + I2C_DEV_CHECK(&dev->i2c_dev, i2c_dev_read_reg(&dev->i2c_dev, reg, buf, 6)); + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); + + data->x = ((int16_t)buf[REG_DX_H - REG_DX_H] << 8) | buf[REG_DX_L - REG_DX_H]; + data->y = ((int16_t)buf[REG_DY_H - REG_DX_H] << 8) | buf[REG_DY_L - REG_DX_H]; + data->z = ((int16_t)buf[REG_DZ_H - REG_DX_H] << 8) | buf[REG_DZ_L - REG_DX_H]; + + return ESP_OK; +} + +esp_err_t hmc5883l_raw_to_mg(const hmc5883l_dev_t *dev, const hmc5883l_raw_data_t *raw, hmc5883l_data_t *mg) +{ + CHECK_ARG(dev && raw && mg); + + mg->x = raw->x * dev->gain; + mg->y = raw->y * dev->gain; + mg->z = raw->z * dev->gain; + + return ESP_OK; +} + +esp_err_t hmc5883l_get_data(hmc5883l_dev_t *dev, hmc5883l_data_t *data) +{ + CHECK_ARG(data); + + hmc5883l_raw_data_t raw; + + CHECK(hmc5883l_get_raw_data(dev, &raw)); + CHECK(hmc5883l_raw_to_mg(dev, &raw, data)); + + return ESP_OK; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/hmc5883l/hmc5883l.h Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,325 @@ +/* + * Copyright (c) 2016 Ruslan V. Uss <unclerus@gmail.com> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of itscontributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file hmc5883l.h + * @defgroup hmc5883l hmc5883l + * @{ + * + * ESP-IDF Driver for 3-axis digital compass HMC5883L and HMC5983L + * + * Ported from esp-open-rtos + * + * Copyright (c) 2016 Ruslan V. Uss <unclerus@gmail.com> + * + * BSD Licensed as described in the file LICENSE + */ +#ifndef __HMC5883L_H__ +#define __HMC5883L_H__ + +#include <stdint.h> +#include <stdbool.h> +#include <i2cdev.h> +#include <esp_err.h> + +#ifdef __cplusplus +extern "C" { +#endif + +#define HMC5883L_ADDR 0x1e //!< I2C address + +#define HMC5883L_ID 0x00333448 //!< Chip ID, "H43" + +/** + * Device operating mode + */ +typedef enum +{ + HMC5883L_MODE_CONTINUOUS = 0, //!< Continuous mode + HMC5883L_MODE_SINGLE //!< Single measurement mode, default +} hmc5883l_opmode_t; + +/** + * Number of samples averaged per measurement + */ +typedef enum +{ + HMC5883L_SAMPLES_1 = 0, //!< 1 sample, default + HMC5883L_SAMPLES_2, //!< 2 samples + HMC5883L_SAMPLES_4, //!< 4 samples + HMC5883L_SAMPLES_8 //!< 8 samples +} hmc5883l_samples_averaged_t; + +/** + * Data output rate in continuous measurement mode + */ +typedef enum +{ + HMC5883L_DATA_RATE_00_75 = 0, //!< 0.75 Hz + HMC5883L_DATA_RATE_01_50, //!< 1.5 Hz + HMC5883L_DATA_RATE_03_00, //!< 3 Hz + HMC5883L_DATA_RATE_07_50, //!< 7.5 Hz + HMC5883L_DATA_RATE_15_00, //!< 15 Hz, default + HMC5883L_DATA_RATE_30_00, //!< 30 Hz + HMC5883L_DATA_RATE_75_00, //!< 75 Hz + HMC5883L_DATA_RATE_220_00 //!< 220 Hz, HMC5983 only +} hmc5883l_data_rate_t; + +/** + * Measurement mode of the device (bias) + */ +typedef enum +{ + HMC5883L_BIAS_NORMAL = 0, //!< Default flow, no bias + HMC5883L_BIAS_POSITIVE, //!< Positive bias configuration all axes, used for self test (see datasheet) + HMC5883L_BIAS_NEGATIVE //!< Negative bias configuration all axes, used for self test (see datasheet) +} hmc5883l_bias_t; + +/** + * Device gain + */ +typedef enum +{ + HMC5883L_GAIN_1370 = 0, //!< 0.73 mG/LSb, range -0.88..+0.88 G + HMC5883L_GAIN_1090, //!< 0.92 mG/LSb, range -1.3..+1.3 G, default + HMC5883L_GAIN_820, //!< 1.22 mG/LSb, range -1.9..+1.9 G + HMC5883L_GAIN_660, //!< 1.52 mG/LSb, range -2.5..+2.5 G + HMC5883L_GAIN_440, //!< 2.27 mG/LSb, range -4.0..+4.0 G + HMC5883L_GAIN_390, //!< 2.56 mG/LSb, range -4.7..+4.7 G + HMC5883L_GAIN_330, //!< 3.03 mG/LSb, range -5.6..+5.6 G + HMC5883L_GAIN_230 //!< 4.35 mG/LSb, range -8.1..+8.1 G +} hmc5883l_gain_t; + +/** + * Device descriptor + */ +typedef struct +{ + i2c_dev_t i2c_dev; //!< I2C device descriptor + hmc5883l_opmode_t opmode; //!< Operating mode + float gain; //!< Gain +} hmc5883l_dev_t; + +/** + * Raw measurement result + */ +typedef struct +{ + int16_t x; + int16_t y; + int16_t z; +} hmc5883l_raw_data_t; + +/** + * Measurement result, milligauss + */ +typedef struct +{ + float x; + float y; + float z; +} hmc5883l_data_t; + +/** + * @brief Initialize device descriptor + * + * @param dev Device descriptor + * @param port I2C port number + * @param sda_gpio GPIO pin number for SDA + * @param scl_gpio GPIO pin number for SCL + * @return `ESP_OK` on success + */ +esp_err_t hmc5883l_init_desc(hmc5883l_dev_t *dev, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio); + +/** + * @brief Free device descriptor + * + * @param dev Device descriptor + * @return `ESP_OK` on success + */ +esp_err_t hmc5883l_free_desc(hmc5883l_dev_t *dev); + +/** + * @brief Initialize device + * + * @param dev Device descriptor + * @return `ESP_OK` on success + */ +esp_err_t hmc5883l_init(hmc5883l_dev_t *dev); + +/** + * @brief Get operating mode + * + * @param dev Device descriptor + * @param[out] val Operating mode + * @return `ESP_OK` on success + */ +esp_err_t hmc5883l_get_opmode(hmc5883l_dev_t *dev, hmc5883l_opmode_t *val); + +/** + * @brief Set operating mode + * + * @param dev Device descriptor + * @param mode Operating mode + * @return `ESP_OK` on success + */ +esp_err_t hmc5883l_set_opmode(hmc5883l_dev_t *dev, hmc5883l_opmode_t mode); + +/** + * @brief Get number of samples averaged per measurement output + * + * @param dev Device descriptor + * @param[out] val Number of samples + * @return `ESP_OK` on success + */ +esp_err_t hmc5883l_get_samples_averaged(hmc5883l_dev_t *dev, hmc5883l_samples_averaged_t *val); + +/** + * @brief Set number of samples averaged per measurement output + * + * @param dev Device descriptor + * @param samples Number of samples + * @return `ESP_OK` on success + */ +esp_err_t hmc5883l_set_samples_averaged(hmc5883l_dev_t *dev, hmc5883l_samples_averaged_t samples); + +/** + * @brief Get data output rate in continuous measurement mode + * + * @param dev Device descriptor + * @param[out] val Data output rate + * @return `ESP_OK` on success + */ +esp_err_t hmc5883l_get_data_rate(hmc5883l_dev_t *dev, hmc5883l_data_rate_t *val); + +/** + * @brief Set data output rate in continuous measurement mode + * + * @param dev Device descriptor + * @param rate Data output rate + * @return `ESP_OK` on success + */ +esp_err_t hmc5883l_set_data_rate(hmc5883l_dev_t *dev, hmc5883l_data_rate_t rate); + +/** + * @brief Get measurement mode (bias of the axes) + * + * See datasheet for self test description. + * + * @param dev Device descriptor + * @param[out] val Bias + * @return `ESP_OK` on success + */ +esp_err_t hmc5883l_get_bias(hmc5883l_dev_t *dev, hmc5883l_bias_t *val); + +/** + * @brief Set measurement mode (bias of the axes) + * + * See datasheet for self test description. + * + * @param dev Device descriptor + * @param bias Bias + * @return `ESP_OK` on success + */ +esp_err_t hmc5883l_set_bias(hmc5883l_dev_t *dev, hmc5883l_bias_t bias); + +/** + * @brief Get device gain + * + * @param dev Device descriptor + * @param[out] val Current gain + * @return `ESP_OK` on success + */ +esp_err_t hmc5883l_get_gain(hmc5883l_dev_t *dev, hmc5883l_gain_t *val); + +/** + * @brief Set device gain + * @param dev Device descriptor + * @param gain Gain + * @return `ESP_OK` on success + */ +esp_err_t hmc5883l_set_gain(hmc5883l_dev_t *dev, hmc5883l_gain_t gain); + +/** + * @brief Get data state + * + * @param dev Device descriptor + * @param[out] val true when data is written to all six data registers + * @return `ESP_OK` on success + */ +esp_err_t hmc5883l_data_is_ready(hmc5883l_dev_t *dev, bool *val); + +/** + * @brief Get lock state + * + * If data is locked, any new data will not be placed in data registers until + * one of these conditions are met: + * - data have been read, + * - operating mode is changed, + * - the measurement configuration (bias) is changed, + * - power is reset. + * + * @param dev Device descriptor + * @param[out] val true when data registers is locked + * @return `ESP_OK` on success + */ +esp_err_t hmc5883l_data_is_locked(hmc5883l_dev_t *dev, bool *val); + +/** + * @brief Get raw magnetic data + * + * @param dev Device descriptor + * @param[out] data Raw data + * @return `ESP_OK` on success + */ +esp_err_t hmc5883l_get_raw_data(hmc5883l_dev_t *dev, hmc5883l_raw_data_t *data); + +/** + * @brief Convert raw magnetic data to milligausses + * + * @param dev Device descriptor + * @param raw Source raw data + * @param[out] mg Converted data + */ +esp_err_t hmc5883l_raw_to_mg(const hmc5883l_dev_t *dev, const hmc5883l_raw_data_t *raw, hmc5883l_data_t *mg); + +/** + * @brief Get magnetic data in milligausses + * + * @param dev Device descriptor + * @param[out] data Magnetic data + * @return `ESP_OK` on success + */ +esp_err_t hmc5883l_get_data(hmc5883l_dev_t *dev, hmc5883l_data_t *data); + +#ifdef __cplusplus +} +#endif + +/**@}*/ + +#endif /* __HMC5883L_H__ */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/ht16k33/.eil.yml Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,22 @@ +--- +components: + - name: ht16k33 + description: HT16K33 LED controller driver + group: led + groups: [] + code_owners: chudsaviet + depends: + - name: i2cdev + - name: log + thread_safe: yes + targets: + - name: esp32 + - name: esp8266 + - name: esp32s2 + - name: esp32c3 + licenses: + - name: MIT + copyrights: + - author: + name: chudsaviet + year: 2022
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/ht16k33/CMakeLists.txt Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,5 @@ +idf_component_register( + SRCS ht16k33.c + INCLUDE_DIRS . + REQUIRES i2cdev log +)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/ht16k33/LICENSE Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,13 @@ +Copyright (c) 2022 Timofei Korostelev <timofei_public@dranik.dev> + +Permission to use, copy, modify, and distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/ht16k33/README.md Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,19 @@ +# HT16K33 driver + +Driver for Holtek HT16K33 LED Controller. + +## Usage + +See the datasheet at https://www.holtek.com/documents/10179/116711/HT16K33v120.pdf . +Chip RAM is 16-byte memory where each bit represents idividual pixel. +It is possbile to change I2C address using jumpers, see the datasheet. + +* Init descriptor with `ht16k33_init_desc()`. +* Init device with `ht16k33_init()`. +* Optionally, set up brightness using `ht16k33_set_brightness()`. +* Turn display on and set blinking mode using `ht16k33_display_setup()`. Unfortunately, its a single command in HT16K33. +* Write RAM state to set individual pixels using `ht16k33_ram_write()`. +* At the end, deinitialize using `ht16k33_free_desc()`. + +See the example at `examples/ht16k33`. +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/ht16k33/component.mk Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,2 @@ +COMPONENT_ADD_INCLUDEDIRS = . +COMPONENT_DEPENDS = i2cdev log
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/ht16k33/ht16k33.c Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,217 @@ +/* + * Copyright (c) 2022 Timofei Korostelev <timofei_public@dranik.dev> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#include "ht16k33.h" + +#include <string.h> +#include <freertos/FreeRTOS.h> +#include <esp_err.h> +#include <esp_log.h> +#include <esp_idf_lib_helpers.h> + +#if HELPER_TRAGET_IS_ESP32 && ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 4, 0) +#include <esp_check.h> +#endif + +static const char* TAG = "ht16k33"; + +#define HT16K33_I2C_FREQ_HZ 400000 // 400kHz + +// All commands are one byte long. +// First 4 bits are command type. +// Last 4 bits are parameters. +#define HT16K33_CMD_RAM_SET_POINTER 0b0000 +#define HT16K33_CMD_SYSTEM_SETUP 0b0010 +#define HT16K33_CMD_SET_BRIGHTNESS 0b1110 +#define HT16K33_CMD_DISPLAY_SETUP 0b1000 +#define HT16K33_CMD_ROW_INT_SET 0b1010 + +#define HT16K33_SET_RAM_CMD_SIZE_BYTES (HT16K33_RAM_SIZE_BYTES + 1) + +#define HT16K33_BLINKING_0HZ 0b00 +#define HT16K33_BLINKING_2HZ 0b01 +#define HT16K33_BLINKING_1HZ 0b10 +#define HT16K33_BLINKING_05HZ 0b11 + +#define CHECK_ARG(VAL) \ + do { \ + if (!(VAL)) \ + return ESP_ERR_INVALID_ARG; \ + } while (0); + +#ifndef ESP_RETURN_ON_ERROR +#define ESP_RETURN_ON_ERROR(x, log_tag, format, ...) do { \ + esp_err_t err_rc_ = (x); \ + if (err_rc_ != ESP_OK) { \ + ESP_LOGE(log_tag, "%s(%d): " format, __FUNCTION__, __LINE__, ##__VA_ARGS__); \ + return err_rc_; \ + } \ + } while(0) +#endif + +static uint8_t ht16k33_blinking_freq_t_to_bits(ht16k33_blinking_freq_t freq) +{ + uint8_t result = 0; + switch (freq) { + case HTK16K33_F_0HZ: + result = HT16K33_BLINKING_0HZ; + break; + case HTK16K33_F_2HZ: + result = HT16K33_BLINKING_1HZ; + break; + case HTK16K33_F_1HZ: + result = HT16K33_BLINKING_1HZ; + break; + case HTK16K33_F_05HZ: + result = HT16K33_BLINKING_05HZ; + break; + } + return result; +} + +static esp_err_t zero_ram(i2c_dev_t* dev) +{ + CHECK_ARG(dev); + + uint8_t all_zeros[HT16K33_RAM_SIZE_BYTES]; + memset(all_zeros, 0, HT16K33_RAM_SIZE_BYTES); + + return ht16k33_ram_write(dev, all_zeros); +} + +// Send one-byte command. +static esp_err_t send_short_cmd(i2c_dev_t* dev, uint8_t cmd) +{ + CHECK_ARG(dev); + + I2C_DEV_TAKE_MUTEX(dev); + I2C_DEV_CHECK(dev, i2c_dev_write(dev, NULL, 0, &cmd, sizeof(uint8_t))); + I2C_DEV_GIVE_MUTEX(dev); + + return ESP_OK; +} + +esp_err_t ht16k33_init_desc(i2c_dev_t* dev, i2c_port_t port, + gpio_num_t sda_gpio, gpio_num_t scl_gpio, + uint8_t addr) +{ + CHECK_ARG(dev); + + memset(dev, 0, sizeof(i2c_dev_t)); + dev->port = port; + dev->addr = addr; + dev->cfg.sda_io_num = sda_gpio; + dev->cfg.scl_io_num = scl_gpio; +#if HELPER_TARGET_IS_ESP32 + dev->cfg.master.clk_speed = HT16K33_I2C_FREQ_HZ; +#endif + + return i2c_dev_create_mutex(dev); +} + +esp_err_t ht16k33_init(i2c_dev_t* dev) +{ + CHECK_ARG(dev); + + // Enable system oscillator. + ESP_RETURN_ON_ERROR( + send_short_cmd(dev, HT16K33_CMD_SYSTEM_SETUP << 4 | 0b0001), TAG, + "Can't start oscillator."); + + // Set display off, no blinking. + ESP_RETURN_ON_ERROR(ht16k33_display_setup(dev, 0, HTK16K33_F_0HZ), TAG, + "Can't set display off, no blinking."); + + // Set to ROW output. + ESP_RETURN_ON_ERROR( + send_short_cmd(dev, HT16K33_CMD_ROW_INT_SET << 4 | 0b0000), TAG, + "Can't set to ROW output"); + + // Set half brightness. + ESP_RETURN_ON_ERROR(ht16k33_set_brightness(dev, HT16K33_MAX_BRIGHTNESS / 2), + TAG, "Can't set initial brightness."); + + // RAM initializes with random values. Zero it. + ESP_RETURN_ON_ERROR(zero_ram(dev), TAG, "Can't zero the RAM."); + + return ESP_OK; +} + +esp_err_t ht16k33_free_desc(i2c_dev_t* dev) +{ + CHECK_ARG(dev); + + return i2c_dev_delete_mutex(dev); +} + +esp_err_t ht16k33_set_brightness(i2c_dev_t* dev, uint8_t brightness) +{ + CHECK_ARG(dev); + + if (brightness > 15) { + ESP_LOGE(TAG, "Brightness shall be set in range 0-15."); + return ESP_ERR_INVALID_ARG; + } + + ESP_RETURN_ON_ERROR( + send_short_cmd(dev, HT16K33_CMD_SET_BRIGHTNESS << 4 | brightness), TAG, + "Can't set brightness."); + + return ESP_OK; +} + +esp_err_t ht16k33_display_setup(i2c_dev_t* dev, uint8_t on_flag, + ht16k33_blinking_freq_t blinking) +{ + CHECK_ARG(dev); + + // on_flag is 1 bit. + if (on_flag > 0b1) { + ESP_LOGE(TAG, "on_flag can only be 0 or 1."); + return ESP_ERR_INVALID_ARG; + } + + // Blinking mode is just 2 bits. + if (blinking > 0b11) { + ESP_LOGE(TAG, "Use only HTK16K33_F_* constants."); + return ESP_ERR_INVALID_ARG; + } + + uint8_t blinking_bits = ht16k33_blinking_freq_t_to_bits(blinking); + + ESP_RETURN_ON_ERROR(send_short_cmd(dev, HT16K33_CMD_DISPLAY_SETUP << 4 | blinking_bits << 1 | on_flag), + TAG, "Can't do display setup."); + + return ESP_OK; +} + +esp_err_t ht16k33_ram_write(i2c_dev_t* dev, uint8_t* data) +{ + CHECK_ARG(dev); + + // First byte is pointer set command. + // Other bytes are the RAM values. + uint8_t cmd_seq[HT16K33_SET_RAM_CMD_SIZE_BYTES]; + // Set write pointer to position 0x00. + cmd_seq[0] = HT16K33_CMD_RAM_SET_POINTER << 4 | 0b0000; + memcpy(cmd_seq + 1, data, HT16K33_RAM_SIZE_BYTES); + + // Send whole command sequence at once. + I2C_DEV_TAKE_MUTEX(dev); + I2C_DEV_CHECK(dev, i2c_dev_write(dev, NULL, 0, cmd_seq, HT16K33_SET_RAM_CMD_SIZE_BYTES)); + I2C_DEV_GIVE_MUTEX(dev); + + return ESP_OK; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/ht16k33/ht16k33.h Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2022 Timofei Korostelev <timofei_public@dranik.dev> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/** + * @file ht16k33.h + * @defgroup ht16k33 ht16k33 + * @{ + * + * Holtek HT16K33 LED Controller driver. + * No keyscan features implemented. + * Tip: PWM brightness frequency depends on I2C clock. + * + * Manufacturer link: https://www.holtek.com/productdetail/-/vg/HT16K33 + * Datasheet: https://www.holtek.com/documents/10179/116711/HT16K33v120.pdf + * + */ + +#if !defined(__HT16K33_H__) +#define __HT16K33_H__ + +#include <driver/gpio.h> +#include <esp_err.h> +#include <i2cdev.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Default I2C address + */ +#define HT16K33_DEFAULT_ADDR 0x70 +#define HT16K33_MAX_BRIGHTNESS 15 +#define HT16K33_RAM_SIZE_BYTES 16 + +/** + * Display blinking frequencies. + */ +typedef enum { + HTK16K33_F_0HZ, + HTK16K33_F_2HZ, + HTK16K33_F_1HZ, + HTK16K33_F_05HZ, +} ht16k33_blinking_freq_t; + +/** + * @brief Initialize the HT16K33 device descriptor. + * + * @param[out] dev Device descriptor + * @param port I2C port number + * @param sda_gpio GPIO pin number for SDA + * @param scl_gpio GPIO pin number for SCL + * @param addr I2C address + * @return `ESP_OK` on success + */ +esp_err_t ht16k33_init_desc(i2c_dev_t *dev, i2c_port_t port, + gpio_num_t sda_gpio, gpio_num_t scl_gpio, + uint8_t addr); + +/** + * @brief Initialize HT16K33 device, reset all settings and zero chip RAM. + * + * @return ESP_OK in case of success + */ +esp_err_t ht16k33_init(i2c_dev_t *dev); + +/** + * @brief Free device descriptor. + * + * @param dev I2C device descriptor + * @return ESP_OK to indicate success + */ +esp_err_t ht16k33_free_desc(i2c_dev_t *dev); + +/** + * @brief Set brightness. + * + * @param dev I2C device descriptor + * @param brightness Brighness value in 0-15 range. + * @return ESP_OK to indicate success + */ +esp_err_t ht16k33_set_brightness(i2c_dev_t *dev, uint8_t brightness); + +/** + * @brief Display setup. ON/OFF and blinkng frequency. + * + * @param dev I2C device descriptor + * @param on_flag On flag, 0 or 1. + * @param blinking Blinking frequence. Limited options. See HT16K33_BLINKING_* + * constants. + * @return ESP_OK to indicate success + */ +esp_err_t ht16k33_display_setup(i2c_dev_t *dev, uint8_t on_flag, + ht16k33_blinking_freq_t blinking); + +/** + * @brief Write whole HT16K33_RAM_SIZE_BYTES into RAM. + * + * @param data Bytes to write. + * @return ESP_OK to indicate success + */ +esp_err_t ht16k33_ram_write(i2c_dev_t *dev, uint8_t *data); + +#ifdef __cplusplus +} +#endif + +/**@}*/ + +#endif // __HT16K33__H__
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/hts221/.eil.yml Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,24 @@ +--- +components: + - name: hts221 + description: | + Driver for HTS221 temperature and humidity sensor. + group: temperature + groups: + - humidity + code_owners: + -name: saasaa + depends: + - name: i2cdev + - name: log + - name: esp_idf_lib_helpers + thread_safe: N/A + targets: + - name: esp32 + - name: esp32s2 + - name: esp32c3 + licenses: + - name: ISC + copyrights: + - name: saasaa + year: 2021
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/hts221/CMakeLists.txt Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,11 @@ +if(${IDF_VERSION_MAJOR} STREQUAL 4 AND ${IDF_VERSION_MINOR} STREQUAL 1 AND ${IDF_VERSION_PATCH} STREQUAL 3) + set(req i2cdev log esp_idf_lib_helpers) +else() + set(req i2cdev log esp_idf_lib_helpers esp_timer) +endif() + +idf_component_register( + SRCS hts221.c + INCLUDE_DIRS . + REQUIRES ${req} +)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/hts221/LICENSE Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,13 @@ +Copyright (c) 2021 saasaa <mail@saasaa.xyz> + +Permission to use, copy, modify, and distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/hts221/component.mk Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,2 @@ +COMPONENT_ADD_INCLUDEDIRS = . +COMPONENT_DEPENDS = i2cdev log esp_idf_lib_helpers
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/hts221/hts221.c Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,400 @@ +/* + * Copyright (c) 2021 saasaa <mail@saasaa.xyz> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/** + * @file hts221.c + * @brief HTS221 driver + * @author saasaa + * + * ESP-IDF driver for HTS221 temperature and humidity sensor + * + * Datasheet: http://www.st.com/resource/en/datasheet/hts221.pdf + * Technical note on interpreting humidity and temperature readings in the + * HTS221 digital humidity sensor: + * https://www.st.com/resource/en/technical_note/tn1218-interpreting-humidity-and-temperature-readings-in-the-hts221-digital-humidity-sensor-stmicroelectronics.pdf + * Copyright (c) 2021 saasaa <mail@saasaa.xyz> + * + * ISC Licensed as described in the file LICENSE + * + */ +#include "hts221.h" + +#include <esp_err.h> +#include <esp_log.h> +#include <freertos/FreeRTOS.h> +#include <freertos/task.h> +#include <i2cdev.h> +#include <esp_timer.h> + +#define I2C_FREQ_HZ 400000 // 400kHz + +#define REG_WHOAMI 0x0F // R Device identification register +#define REG_AV_CONF 0x10 // R/W Humidity and temperature resolution mode +#define REG_CTRL_REG1 0x20 // R/W Control register 1 +#define REG_CTRL_REG2 0x21 // R/W Control register 2 +#define REG_CTRL_REG3 0x22 // R/W Control register 3 for data ready output signal +#define REG_STATUS_REG 0x27 // R Status register +#define REG_HUMIDITY_OUT_L 0x28 // R Relative humidity output register (LSB) +#define REG_HUMIDITY_OUT_H 0x29 // R Relative humidity output register (MSB) +#define REG_TEMP_OUT_L 0x2A // R Temperature output register (LSB) +#define REG_TEMP_OUT_H 0x2B // R Temperature output register (MSB) +#define REG_CALIB_START 0x30 // R/W Calibration start register + +#define BIT_AV_CONF_AVGT0 3 +#define BIT_AV_CONF_AVGH0 0 + +#define BIT_CTRL_REG1_PD 7 +#define BIT_CTRL_REG1_BDU 2 +#define BIT_CTRL_REG1_ODR 0 + +#define BIT_CTRL_REG2_BOOT 7 +#define BIT_CTRL_REG2_HEATER 1 +#define BIT_CTRL_REG2_ONE_SHOT 7 + +#define BIT_CTRL_REG3_DRDY_H_L 7 +#define BIT_CTRL_REG3_PP_OD 6 +#define BIT_CTRL_REG3_DRDY 2 + +#define BIT_STATUS_REG_H_DA 1 +#define BIT_STATUS_REG_T_DA 0 + +#define MASK_AV_CONF_AVG 0x07 +#define MASK_CTRL_REG1_ODR 0x03 +#define MASK_STATUS_REG_DA 0x03 + +#define WHOAMI 0xbc +#define REBOOT_TIMEOUT_US (100 * 1000) + +#define HTS221_AV_CONF_DEFAULT 0x1B // DEFAULT AV_CONF status register value according to datasheet + +#define CHECK(x) \ + do \ + { \ + esp_err_t __; \ + if ((__ = x) != ESP_OK) \ + return __; \ + } while (0) + +#define CHECK_ARG(VAL) \ + do \ + { \ + if (!(VAL)) \ + return ESP_ERR_INVALID_ARG; \ + } while (0) + +static const char *TAG = "hts221"; + +static esp_err_t write_reg(hts221_t *dev, uint8_t reg, uint8_t val) +{ + I2C_DEV_TAKE_MUTEX(&dev->i2c_dev); + I2C_DEV_CHECK(&dev->i2c_dev, i2c_dev_read_reg(&dev->i2c_dev, reg, &val, 1)); + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); + + return ESP_OK; +} + +static esp_err_t read_reg(hts221_t *dev, uint8_t reg, uint8_t *val) +{ + I2C_DEV_TAKE_MUTEX(&dev->i2c_dev); + I2C_DEV_CHECK(&dev->i2c_dev, i2c_dev_read_reg(&dev->i2c_dev, reg, val, 1)); + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); + + return ESP_OK; +} + +static esp_err_t update_reg(hts221_t *dev, uint8_t reg, uint8_t val, uint8_t mask) +{ + uint8_t b; + I2C_DEV_TAKE_MUTEX(&dev->i2c_dev); + I2C_DEV_CHECK(&dev->i2c_dev, i2c_dev_read_reg(&dev->i2c_dev, reg, &b, 1)); + b = (b & mask) | val; + I2C_DEV_CHECK(&dev->i2c_dev, i2c_dev_write_reg(&dev->i2c_dev, reg, &b, 1)); + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); + + return ESP_OK; +} + +inline static esp_err_t read_calibration_coeff(hts221_t *dev) +{ + uint8_t T0_T1_msb; + uint8_t H0_rH_x2; + uint8_t H1_rH_x2; + uint8_t T0_degC_x8_lsb; + uint8_t T1_degC_x8_lsb; + uint8_t H0_T0_OUT_lsb; + uint8_t H0_T0_OUT_msb; + uint8_t H1_T0_OUT_lsb; + uint8_t H1_T0_OUT_msb; + uint8_t T0_OUT_lsb; + uint8_t T0_OUT_msb; + uint8_t T1_OUT_lsb; + uint8_t T1_OUT_msb; + + // TODO: replace with block reading + I2C_DEV_CHECK(&dev->i2c_dev, i2c_dev_read_reg(&dev->i2c_dev, 0x30, &H0_rH_x2, 1)); + I2C_DEV_CHECK(&dev->i2c_dev, i2c_dev_read_reg(&dev->i2c_dev, 0x31, &H1_rH_x2, 1)); + I2C_DEV_CHECK(&dev->i2c_dev, i2c_dev_read_reg(&dev->i2c_dev, 0x32, &T0_degC_x8_lsb, 1)); + I2C_DEV_CHECK(&dev->i2c_dev, i2c_dev_read_reg(&dev->i2c_dev, 0x33, &T1_degC_x8_lsb, 1)); + I2C_DEV_CHECK(&dev->i2c_dev, i2c_dev_read_reg(&dev->i2c_dev, 0x35, &T0_T1_msb, 1)); + I2C_DEV_CHECK(&dev->i2c_dev, i2c_dev_read_reg(&dev->i2c_dev, 0x36, &H0_T0_OUT_lsb, 1)); + I2C_DEV_CHECK(&dev->i2c_dev, i2c_dev_read_reg(&dev->i2c_dev, 0x37, &H0_T0_OUT_msb, 1)); + I2C_DEV_CHECK(&dev->i2c_dev, i2c_dev_read_reg(&dev->i2c_dev, 0x3A, &H1_T0_OUT_lsb, 1)); + I2C_DEV_CHECK(&dev->i2c_dev, i2c_dev_read_reg(&dev->i2c_dev, 0x3B, &H1_T0_OUT_msb, 1)); + I2C_DEV_CHECK(&dev->i2c_dev, i2c_dev_read_reg(&dev->i2c_dev, 0x3C, &T0_OUT_lsb, 1)); + I2C_DEV_CHECK(&dev->i2c_dev, i2c_dev_read_reg(&dev->i2c_dev, 0x3D, &T0_OUT_msb, 1)); + I2C_DEV_CHECK(&dev->i2c_dev, i2c_dev_read_reg(&dev->i2c_dev, 0x3E, &T1_OUT_lsb, 1)); + I2C_DEV_CHECK(&dev->i2c_dev, i2c_dev_read_reg(&dev->i2c_dev, 0x3F, &T1_OUT_msb, 1)); + + dev->cal.H0_rH = (H0_rH_x2 >> 1); + dev->cal.H1_rH = (H1_rH_x2 >> 1); + dev->cal.T0_degC = ((T0_T1_msb & 0x03) << 8 | T0_degC_x8_lsb) >> 3; + dev->cal.T1_degC = ((T0_T1_msb & 0x0C) << 6 | T1_degC_x8_lsb) >> 3; + dev->cal.H0_T0_OUT = (H0_T0_OUT_msb << 8) | H0_T0_OUT_lsb; + dev->cal.H1_T0_OUT = (H1_T0_OUT_msb << 8) | H1_T0_OUT_lsb; + dev->cal.T0_OUT = (T0_OUT_msb << 8) | T0_OUT_lsb; + dev->cal.T1_OUT = (T1_OUT_msb << 8) | T1_OUT_lsb; + + ESP_LOGD(TAG, "Read calibration data"); + + return ESP_OK; +} + +/////////////////////////////////////////////////////////////////////////////// + +esp_err_t hts221_init_desc(hts221_t *dev, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio) +{ + CHECK_ARG(dev); + + dev->i2c_dev.port = port; + dev->i2c_dev.addr = HTS221_I2C_ADDRESS; + dev->i2c_dev.cfg.sda_io_num = sda_gpio; + dev->i2c_dev.cfg.scl_io_num = scl_gpio; +#if HELPER_TARGET_IS_ESP32 + dev->i2c_dev.cfg.master.clk_speed = I2C_FREQ_HZ; +#endif + return i2c_dev_create_mutex(&dev->i2c_dev); +} + +esp_err_t hts221_free_desc(hts221_t *dev) +{ + CHECK_ARG(dev); + + return i2c_dev_delete_mutex(&dev->i2c_dev); +} + +esp_err_t hts221_init(hts221_t *dev) +{ + CHECK_ARG(dev); + + esp_err_t res = ESP_OK; + + I2C_DEV_TAKE_MUTEX(&dev->i2c_dev); + + uint8_t b; + I2C_DEV_CHECK(&dev->i2c_dev, i2c_dev_read_reg(&dev->i2c_dev, REG_WHOAMI, &b, 1)); + if (b != WHOAMI) + { + ESP_LOGE(TAG, "Invalid WHOAMI value: 0x%02x", b); + res = ESP_ERR_INVALID_RESPONSE; + goto exit; + } + + // Reboot + b = BIT(BIT_CTRL_REG2_BOOT); + I2C_DEV_CHECK(&dev->i2c_dev, i2c_dev_write_reg(&dev->i2c_dev, REG_CTRL_REG2, &b, 1)); + // Wait + int64_t start = esp_timer_get_time(); + while (true) + { + if ((esp_timer_get_time() - start) >= REBOOT_TIMEOUT_US) + { + ESP_LOGE(TAG, "Timeout while rebooting device"); + res = ESP_ERR_TIMEOUT; + goto exit; + } + I2C_DEV_CHECK(&dev->i2c_dev, i2c_dev_read_reg(&dev->i2c_dev, REG_CTRL_REG2, &b, 1)); + if (!(b & BIT(BIT_CTRL_REG2_BOOT))) + break; + vTaskDelay(1); + } + + // Read calibration data + I2C_DEV_CHECK(&dev->i2c_dev, read_calibration_coeff(dev)); + + // Enable BDU, switch to active mode, DATA RATE = one shot + b = BIT(BIT_CTRL_REG1_PD) | BIT(BIT_CTRL_REG1_BDU); + I2C_DEV_CHECK(&dev->i2c_dev, i2c_dev_write_reg(&dev->i2c_dev, REG_CTRL_REG2, &b, 1)); + + // DRDY mode = Push-pull, DRDY active level = HIGH + b = 0; + I2C_DEV_CHECK(&dev->i2c_dev, i2c_dev_write_reg(&dev->i2c_dev, REG_CTRL_REG3, &b, 1)); + +exit: + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); + return res; +} + +esp_err_t hts221_get_power_mode(hts221_t *dev, bool *power_down) +{ + CHECK_ARG(dev && power_down); + + uint8_t raw; + CHECK(read_reg(dev, REG_CTRL_REG1, &raw)); + *power_down = (raw & BIT(BIT_CTRL_REG1_PD)) ? true : false; + + return ESP_OK; +} + +esp_err_t hts221_set_power_mode(hts221_t *dev, bool power_down) +{ + CHECK_ARG(dev); + + return update_reg(dev, REG_CTRL_REG1, power_down ? BIT(BIT_CTRL_REG1_PD) : 0, BIT(BIT_CTRL_REG1_PD)); +} + + +esp_err_t hts221_get_data_rate(hts221_t *dev, hts221_data_rate_t *dr) +{ + CHECK_ARG(dev && dr); + + uint8_t raw; + CHECK(read_reg(dev, REG_CTRL_REG1, &raw)); + *dr = (raw & MASK_CTRL_REG1_ODR); + + return ESP_OK; +} + +esp_err_t hts221_set_data_rate(hts221_t *dev, hts221_data_rate_t dr) +{ + CHECK_ARG(dev && dr <= HTS221_12_5HZ); + + return update_reg(dev, REG_CTRL_REG1, dr, MASK_CTRL_REG1_ODR); +} + +esp_err_t hts221_get_heater(hts221_t *dev, bool *enable) +{ + CHECK_ARG(dev && enable); + + uint8_t raw; + CHECK(read_reg(dev, REG_CTRL_REG2, &raw)); + *enable = (raw & BIT(BIT_CTRL_REG2_HEATER)) ? true : false; + + return ESP_OK; +} + +esp_err_t hts221_set_heater(hts221_t *dev, bool enable) +{ + CHECK_ARG(dev); + + return update_reg(dev, REG_CTRL_REG2, enable ? BIT(BIT_CTRL_REG2_HEATER) : 0, BIT(BIT_CTRL_REG2_HEATER)); +} + +esp_err_t hts221_get_averaging(hts221_t *dev, hts221_temperature_avg_t *t_avg, hts221_humidity_avg_t *rh_avg) +{ + CHECK_ARG(dev && (t_avg || rh_avg)); + + uint8_t raw; + CHECK(read_reg(dev, REG_AV_CONF, &raw)); + if (t_avg) + *t_avg = (raw >> BIT_AV_CONF_AVGT0) & MASK_AV_CONF_AVG; + if (rh_avg) + *t_avg = (raw >> BIT_AV_CONF_AVGH0) & MASK_AV_CONF_AVG; + + return ESP_OK; +} + +esp_err_t hts221_set_averaging(hts221_t *dev, hts221_temperature_avg_t t_avg, hts221_humidity_avg_t rh_avg) +{ + CHECK_ARG(dev && t_avg <= HTS221_AVGT_256 && rh_avg <= HTS221_AVGH_512); + + return write_reg(dev, REG_AV_CONF, (t_avg << BIT_AV_CONF_AVGT0) | (rh_avg << BIT_AV_CONF_AVGH0)); +} + +esp_err_t hts221_get_drdy_config(hts221_t *dev, bool *enable, hts221_drdy_mode_t *mode, hts221_drdy_level_t *active) +{ + CHECK_ARG(dev && (enable || mode || active)); + + uint8_t raw; + CHECK(read_reg(dev, REG_CTRL_REG3, &raw)); + + if (enable) + *enable = (raw >> BIT_CTRL_REG3_DRDY) & 1; + if (mode) + *mode = (raw >> BIT_CTRL_REG3_PP_OD) & 1; + if (active) + *active = (raw >> BIT_CTRL_REG3_DRDY_H_L) & 1; + + return ESP_OK; +} + +esp_err_t hts221_set_drdy_config(hts221_t *dev, bool enable, hts221_drdy_mode_t mode, hts221_drdy_level_t active) +{ + CHECK_ARG(dev && mode <= HTS221_DRDY_OPEN_DRAIN && active <= HTS221_DRDY_ACTIVE_LOW); + + return write_reg(dev, REG_CTRL_REG3, + (enable ? BIT(BIT_CTRL_REG3_DRDY) : 0) | (mode << BIT_CTRL_REG3_PP_OD) | (active << BIT_CTRL_REG3_DRDY_H_L)); +} + +esp_err_t hts221_is_data_ready(hts221_t *dev, bool *ready) +{ + CHECK_ARG(dev && ready); + + uint8_t raw; + CHECK(read_reg(dev, REG_STATUS_REG, &raw)); + + *ready = (raw & MASK_STATUS_REG_DA) == MASK_STATUS_REG_DA; + + return ESP_OK; +} + +esp_err_t hts221_start_oneshot(hts221_t *dev) +{ + CHECK_ARG(dev); + + return update_reg(dev, REG_CTRL_REG2, BIT(BIT_CTRL_REG2_ONE_SHOT), BIT(BIT_CTRL_REG2_ONE_SHOT)); +} + +esp_err_t hts221_get_data(hts221_t *dev, float *t, float *rh) +{ + CHECK_ARG(dev && (t || rh)); + + int16_t raw_rh; + int16_t raw_t; + uint8_t raw[4]; + + I2C_DEV_TAKE_MUTEX(&dev->i2c_dev); + I2C_DEV_CHECK(&dev->i2c_dev, i2c_dev_read_reg(&dev->i2c_dev, REG_HUMIDITY_OUT_L, &raw[0], 1)); + I2C_DEV_CHECK(&dev->i2c_dev, i2c_dev_read_reg(&dev->i2c_dev, REG_HUMIDITY_OUT_H, &raw[1], 1)); + I2C_DEV_CHECK(&dev->i2c_dev, i2c_dev_read_reg(&dev->i2c_dev, REG_TEMP_OUT_L, &raw[2], 1)); + I2C_DEV_CHECK(&dev->i2c_dev, i2c_dev_read_reg(&dev->i2c_dev, REG_TEMP_OUT_H, &raw[3], 1)); + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); + + raw_rh = (raw[1] << 8) | raw[0]; + raw_t = (raw[3] << 8) | raw[2]; + + if (rh) + { + *rh = (float)(dev->cal.H1_rH - dev->cal.H0_rH) * (raw_rh - dev->cal.H0_T0_OUT) / + (float)(dev->cal.H1_T0_OUT - dev->cal.H0_T0_OUT) + (float)dev->cal.H0_rH; + if (*rh > 100) + *rh = 100; + } + if (t) + *t = (float)(dev->cal.T1_degC - dev->cal.T0_degC) * (raw_t - dev->cal.T0_OUT) / + (float)(dev->cal.T1_OUT - dev->cal.T0_OUT) + (float)dev->cal.T0_degC; + + return ESP_OK; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/hts221/hts221.h Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,294 @@ +/* + * Copyright (c) 2021 saasaa <mail@saasaa.xyz> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/** + * @file hts221.h + * @brief HTS221 driver + * @author saasaa + * @defgroup hts221 hts221 + * @{ + * + * ESP-IDF driver for HTS221 temperature and humidity sensor + * + * Datasheet: http://www.st.com/resource/en/datasheet/hts221.pdf + * Technical note on interpreting humidity and temperature readings in the + * HTS221 digital humidity sensor: + * https://www.st.com/resource/en/technical_note/tn1218-interpreting-humidity-and-temperature-readings-in-the-hts221-digital-humidity-sensor-stmicroelectronics.pdf + * Copyright (c) 2021 saasaa <mail@saasaa.xyz> + * + * ISC Licensed as described in the file LICENSE + * + */ +#ifndef __HTS221_H__ +#define __HTS221_H__ + +#include <esp_err.h> +#include <i2cdev.h> +#include <stdbool.h> + +#ifdef __cplusplus +extern "C" { +#endif + +#define HTS221_I2C_ADDRESS 0x5F + +/** + * @brief Struct for storing calibration parameters. + * + * Unique for every HTS221 Sensor. + */ +typedef struct +{ + uint8_t H0_rH; + uint8_t H1_rH; + uint16_t T0_degC; + uint16_t T1_degC; + int16_t H0_T0_OUT; + int16_t H1_T0_OUT; + int16_t T0_OUT; + int16_t T1_OUT; +} hts221_calibration_param_t; + +/** + * @brief Number of averaged temperature samples + */ +typedef enum +{ + HTS221_AVGT_2 = 0, //!< 2 averaged temperature samples + HTS221_AVGT_4, //!< 4 averaged temperature samples + HTS221_AVGT_8, //!< 8 averaged temperature samples + HTS221_AVGT_1, //!< 16 averaged temperature samples + HTS221_AVGT_32, //!< 32 averaged temperature samples + HTS221_AVGT_64, //!< 64 averaged temperature samples + HTS221_AVGT_128, //!< 128 averaged temperature samples + HTS221_AVGT_256, //!< 256 averaged temperature samples +} hts221_temperature_avg_t; + +/** + * @brief Number of averaged humidity samples + */ +typedef enum +{ + HTS221_AVGH_4 = 0, //!< 4 averaged humidity samples + HTS221_AVGH_8, //!< 8 averaged humidity samples + HTS221_AVGH_16, //!< 16 averaged humidity samples + HTS221_AVGH_32, //!< 32 averaged humidity samples + HTS221_AVGH_64, //!< 64 averaged humidity samples + HTS221_AVGH_128, //!< 128 averaged humidity samples + HTS221_AVGH_256, //!< 256 averaged humidity samples + HTS221_AVGH_512, //!< 512 averaged humidity samples +} hts221_humidity_avg_t; + +/** + * @brief Output data rate + */ +typedef enum +{ + HTS221_ONE_SHOT = 0, + HTS221_1HZ, + HTS221_7HZ, + HTS221_12_5HZ, +} hts221_data_rate_t; + +typedef enum +{ + HTS221_DRDY_ACTIVE_HIGH = 0, + HTS221_DRDY_ACTIVE_LOW, +} hts221_drdy_level_t; + +typedef enum +{ + HTS221_DRDY_PUSH_PULL = 0, + HTS221_DRDY_OPEN_DRAIN, +} hts221_drdy_mode_t; + +typedef struct +{ + i2c_dev_t i2c_dev; + hts221_calibration_param_t cal; +} hts221_t; + +/** + * @brief Initialize the HTS221 Device descriptor + * + * @param[out] dev Device descriptor + * @param port I2C port number + * @param sda_gpio GPIO pin number for SDA + * @param scl_gpio GPIO pin number for SCL + * @return `ESP_OK` on success + */ +esp_err_t hts221_init_desc(hts221_t *dev, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio); + +/** + * @brief Free device descriptor + * + * @param dev Device descriptor + * @return `ESP_OK` on success + */ +esp_err_t hts221_free_desc(hts221_t *dev); + +/** + * @brief Reset device parameters, read calibration data + * + * Reboot device, reset calibration data + * + * @param dev Device descriptor + * @return `ESP_OK` on success + */ +esp_err_t hts221_init(hts221_t *dev); + +/** + * @brief Get power mode + * + * @param dev Device descriptor + * @param[out] power_down true if device in power down mode + * @return `ESP_OK` on success + */ +esp_err_t hts221_get_power_mode(hts221_t *dev, bool *power_down); + +/** + * @brief Set power mode + * + * @param dev Device descriptor + * @param power_down true to set device to power down mode + * @return `ESP_OK` on success + */ +esp_err_t hts221_set_power_mode(hts221_t *dev, bool power_down); + +/** + * @brief Get output data rate + * + * @param dev Device descriptor + * @param[out] dr Data rate + * @return `ESP_OK` on success + */ +esp_err_t hts221_get_data_rate(hts221_t *dev, hts221_data_rate_t *dr); + +/** + * @brief Set output data rate + * + * @param dev Device descriptor + * @param dr Data rate + * @return `ESP_OK` on success + */ +esp_err_t hts221_set_data_rate(hts221_t *dev, hts221_data_rate_t dr); + +/** + * @brief Get heater state + * + * @param dev Device descriptor + * @param[out] enable true when heater is enabled + * @return `ESP_OK` on success + */ +esp_err_t hts221_get_heater(hts221_t *dev, bool *enable); + +/** + * @brief Switch heater on/off + * + * @param dev Device descriptor + * @param enable true to switch heater on + * @return `ESP_OK` on success + */ +esp_err_t hts221_set_heater(hts221_t *dev, bool enable); + +/** + * @brief Get average configuration + * + * @param dev Device descriptor + * @param[out] t_avg Temperature average configuration + * @param[out] rh_avg Humidity average configuration + * @return `ESP_OK` on success + */ +esp_err_t hts221_get_averaging(hts221_t *dev, hts221_temperature_avg_t *t_avg, hts221_humidity_avg_t *rh_avg); + +/** + * @brief Set average configuration + * + * @param dev Device descriptor + * @param t_avg Temperature average configuration + * @param rh_avg Humidity average configuration + * @return `ESP_OK` on success + */ +esp_err_t hts221_set_averaging(hts221_t *dev, hts221_temperature_avg_t t_avg, hts221_humidity_avg_t rh_avg); + +/** + * @brief Get configuration of DRDY pin + * + * @param dev Device descriptor + * @param[out] enable true if DRDY pin is enabled + * @param[out] mode DRDY pin mode (Push-pull or open drain) + * @param[out] active Pin active level + * @return `ESP_OK` on success + */ +esp_err_t hts221_get_drdy_config(hts221_t *dev, bool *enable, hts221_drdy_mode_t *mode, hts221_drdy_level_t *active); + +/** + * @brief Set configuration of DRDY pin + * + * @param dev Device descriptor + * @param enable true if DRDY pin is enabled + * @param mode DRDY pin mode (Push-pull or open drain) + * @param active Pin active level + * @return `ESP_OK` on success + */ +esp_err_t hts221_set_drdy_config(hts221_t *dev, bool enable, hts221_drdy_mode_t mode, hts221_drdy_level_t active); + +/** + * @brief Check the availability of new RH/T data + * + * \p ready parameter will be true only if new data for both temperature and humidity is available. + * + * @param dev Device descriptor + * @param[out] ready true if new RH/T data available + * @return `ESP_OK` on success + */ +esp_err_t hts221_is_data_ready(hts221_t *dev, bool *ready); + +/** + * @brief Start one shot measurement + * + * @param dev Device descriptor + * @return `ESP_OK` on success + */ +esp_err_t hts221_start_oneshot(hts221_t *dev); + +/** + * @brief Get temperature and relative humidity + * + * @param dev Device descriptor + * @param[out] t Temperature, degrees Celsius + * @param[out] rh Relative humidity, % + * @return `ESP_OK` on success + */ +esp_err_t hts221_get_data(hts221_t *dev, float *t, float *rh); + +/** + * @brief Measure temperature and relative humidity in one shot mode + * + * @param dev Device descriptor + * @param[out] t Temperature, degrees Celsius + * @param[out] rh Relative humidity, % + * @return `ESP_OK` on success + */ +esp_err_t hts221_measure(hts221_t *dev, float *t, float *rh); + +#ifdef __cplusplus +} +#endif + +/**@}*/ + +#endif // __HTS221_H__
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/hx711/.eil.yml Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,24 @@ +--- +components: + - name: hx711 + description: | + Driver for HX711 24-bit ADC for weigh scales + group: adc-dac + groups: [] + code_owners: + - name: UncleRus + depends: + - name: driver + - name: freertos + - name: esp_idf_lib_helpers + thread_safe: no + targets: + - name: esp32 + - name: esp8266 + - name: esp32s2 + - name: esp32c3 + licenses: + - name: BSD-3 + copyrights: + - name: UncleRus + year: 2019
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/hx711/CMakeLists.txt Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,13 @@ +if(${IDF_TARGET} STREQUAL esp8266) + set(req esp8266 freertos esp_idf_lib_helpers esp_timer) +elseif(${IDF_VERSION_MAJOR} STREQUAL 4 AND ${IDF_VERSION_MINOR} STREQUAL 1 AND ${IDF_VERSION_PATCH} STREQUAL 3) + set(req driver freertos esp_idf_lib_helpers) +else() + set(req driver freertos esp_idf_lib_helpers esp_timer) +endif() + +idf_component_register( + SRCS hx711.c + INCLUDE_DIRS . + REQUIRES ${req} +)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/hx711/LICENSE Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,26 @@ +Copyright (c) 2019 Ruslan V. Uss <unclerus@gmail.com> + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, +this list of conditions and the following disclaimer in the documentation +and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of itscontributors +may be used to endorse or promote products derived from this software without +specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/hx711/component.mk Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,7 @@ +COMPONENT_ADD_INCLUDEDIRS = . + +ifdef CONFIG_IDF_TARGET_ESP8266 +COMPONENT_DEPENDS = esp8266 freertos esp_idf_lib_helpers +else +COMPONENT_DEPENDS = driver freertos esp_idf_lib_helpers +endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/hx711/hx711.c Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,173 @@ +/* + * Copyright (c) 2019 Ruslan V. Uss <unclerus@gmail.com> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of itscontributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file hx711.c + * + * ESP-IDF driver for HX711 24-bit ADC for weigh scales + * + * Copyright (c) 2019 Ruslan V. Uss <unclerus@gmail.com> + * + * BSD Licensed as described in the file LICENSE + */ +#include <freertos/FreeRTOS.h> +#include <freertos/task.h> +#include <esp_timer.h> +#include <ets_sys.h> +#include <esp_idf_lib_helpers.h> +#include "hx711.h" + +#define CHECK(x) do { esp_err_t __; if ((__ = x) != ESP_OK) return __; } while (0) +#define CHECK_ARG(VAL) do { if (!(VAL)) return ESP_ERR_INVALID_ARG; } while (0) + +#if HELPER_TARGET_IS_ESP32 +static portMUX_TYPE mux = portMUX_INITIALIZER_UNLOCKED; +#endif + +static uint32_t read_raw(gpio_num_t dout, gpio_num_t pd_sck, hx711_gain_t gain) +{ +#if HELPER_TARGET_IS_ESP32 + portENTER_CRITICAL(&mux); +#elif HELPER_TARGET_IS_ESP8266 + portENTER_CRITICAL(); +#endif + + // read data + uint32_t data = 0; + for (size_t i = 0; i < 24; i++) + { + gpio_set_level(pd_sck, 1); + ets_delay_us(1); + data |= gpio_get_level(dout) << (23 - i); + gpio_set_level(pd_sck, 0); + ets_delay_us(1); + } + + // config gain + channel for next read + for (size_t i = 0; i <= gain; i++) + { + gpio_set_level(pd_sck, 1); + ets_delay_us(1); + gpio_set_level(pd_sck, 0); + ets_delay_us(1); + } + +#if HELPER_TARGET_IS_ESP32 + portEXIT_CRITICAL(&mux); +#elif HELPER_TARGET_IS_ESP8266 + portEXIT_CRITICAL(); +#endif + + return data; +} + +/////////////////////////////////////////////////////////////////////////////// + +esp_err_t hx711_init(hx711_t *dev) +{ + CHECK_ARG(dev); + + CHECK(gpio_set_direction(dev->dout, GPIO_MODE_INPUT)); + CHECK(gpio_set_direction(dev->pd_sck, GPIO_MODE_OUTPUT)); + + CHECK(hx711_power_down(dev, false)); + + return hx711_set_gain(dev, dev->gain); +} + +esp_err_t hx711_power_down(hx711_t *dev, bool down) +{ + CHECK_ARG(dev); + + CHECK(gpio_set_level(dev->pd_sck, down)); + vTaskDelay(1); + + return ESP_OK; +} + +esp_err_t hx711_set_gain(hx711_t *dev, hx711_gain_t gain) +{ + CHECK_ARG(dev && gain <= HX711_GAIN_A_64); + + CHECK(hx711_wait(dev, 200)); // 200 ms timeout + + read_raw(dev->dout, dev->pd_sck, gain); + dev->gain = gain; + + return ESP_OK; +} + +esp_err_t hx711_is_ready(hx711_t *dev, bool *ready) +{ + CHECK_ARG(dev && ready); + + *ready = !gpio_get_level(dev->dout); + + return ESP_OK; +} + +esp_err_t hx711_wait(hx711_t *dev, size_t timeout_ms) +{ + uint64_t started = esp_timer_get_time() / 1000; + while (esp_timer_get_time() / 1000 - started < timeout_ms) + { + if (!gpio_get_level(dev->dout)) + return ESP_OK; + vTaskDelay(1); + } + + return ESP_ERR_TIMEOUT; +} + +esp_err_t hx711_read_data(hx711_t *dev, int32_t *data) +{ + CHECK_ARG(dev && data); + + uint32_t raw = read_raw(dev->dout, dev->pd_sck, dev->gain); + if (raw & 0x800000) + raw |= 0xff000000; + *data = *((int32_t *)&raw); + + return ESP_OK; +} + +esp_err_t hx711_read_average(hx711_t *dev, size_t times, int32_t *data) +{ + CHECK_ARG(dev && times && data); + + int32_t v; + *data = 0; + for (size_t i = 0; i < times; i++) + { + CHECK(hx711_wait(dev, 200)); + CHECK(hx711_read_data(dev, &v)); + *data += v; + } + *data /= (int32_t) times; + + return ESP_OK; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/hx711/hx711.h Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,143 @@ +/* + * Copyright (c) 2019 Ruslan V. Uss <unclerus@gmail.com> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of itscontributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file hx711.h + * @defgroup hx711 hx711 + * @{ + * + * ESP-IDF driver for HX711 24-bit ADC for weigh scales + * + * Copyright (c) 2019 Ruslan V. Uss <unclerus@gmail.com> + * + * BSD Licensed as described in the file LICENSE + */ +#ifndef __HX711_H__ +#define __HX711_H__ + +#include <driver/gpio.h> +#include <stdbool.h> +#include <esp_err.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Gain/channel + */ +typedef enum { + HX711_GAIN_A_128 = 0, //!< Channel A, gain factor 128 + HX711_GAIN_B_32, //!< Channel B, gain factor 32 + HX711_GAIN_A_64 //!< Channel A, gain factor 64 +} hx711_gain_t; + +/** + * Device descriptor + */ +typedef struct +{ + gpio_num_t dout; + gpio_num_t pd_sck; + hx711_gain_t gain; +} hx711_t; + +/** + * @brief Initialize device + * + * Prepare GPIO pins, power up device and set gain + * + * @param dev Device descriptor + * @return `ESP_OK` on success, `ESP_ERR_TIMEOUT` if device not found + */ +esp_err_t hx711_init(hx711_t *dev); + +/** + * @brief Set device power up/down + * + * @param dev Device descriptor + * @param down Set device power down if true, power up otherwise + * @return `ESP_OK` on success + */ +esp_err_t hx711_power_down(hx711_t *dev, bool down); + +/** + * @brief Set device gain and channel + * + * @param dev Device descriptor + * @param gain Gain + channel value + * @return `ESP_OK` on success, `ESP_ERR_TIMEOUT` if device not found + */ +esp_err_t hx711_set_gain(hx711_t *dev, hx711_gain_t gain); + +/** + * @brief Check if device ready to send data + * + * @param dev Device descriptor + * @param[out] ready true if data ready + * @return `ESP_OK` on success + */ +esp_err_t hx711_is_ready(hx711_t *dev, bool *ready); + +/** + * @brief Wait for device to become ready + * + * @param dev Device descriptor + * @param timeout_ms Maximum time to wait, milliseconds + * @return `ESP_OK` on success + */ +esp_err_t hx711_wait(hx711_t *dev, size_t timeout_ms); + +/** + * @brief Read raw data from device. + * + * Please call this function only when device is ready, + * otherwise communication errors may occur + * + * @param dev Device descriptor + * @param[out] data Raw ADC data + * @return `ESP_OK` on success + */ +esp_err_t hx711_read_data(hx711_t *dev, int32_t *data); + +/** + * @brief Read average data + * + * @param dev Device descriptor + * @param times Count of samples to read + * @param[out] data Average ADC data + * @return `ESP_OK` on success + */ +esp_err_t hx711_read_average(hx711_t *dev, size_t times, int32_t *data); + +#ifdef __cplusplus +} +#endif + +/**@}*/ + +#endif /* __HX711_H__ */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/i2cdev/.eil.yml Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,24 @@ +--- +components: + - name: i2cdev + description: | + ESP-IDF I2C master thread-safe utilities + group: common + groups: [] + code_owners: + - name: UncleRus + depends: + - name: driver + - name: freertos + - name: esp_idf_lib_helpers + thread_safe: yes + targets: + - name: esp32 + - name: esp8266 + - name: esp32s2 + - name: esp32c3 + licenses: + - name: MIT + copyrights: + - name: UncleRus + year: 2018
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/i2cdev/CMakeLists.txt Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,11 @@ +if(${IDF_TARGET} STREQUAL esp8266) + set(req esp8266 freertos esp_idf_lib_helpers) +else() + set(req driver freertos esp_idf_lib_helpers) +endif() + +idf_component_register( + SRCS i2cdev.c + INCLUDE_DIRS . + REQUIRES ${req} +)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/i2cdev/Kconfig Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,17 @@ +menu "I2C" + +config I2CDEV_TIMEOUT + int "I2C transaction timeout, milliseconds" + default 1000 + range 10 5000 + +config I2CDEV_NOLOCK + bool "Disable the use of mutexes" + default n + help + Attention! After enabling this option, all I2C device + drivers will become non-thread safe. + Use this option if you need to access your I2C devices + from interrupt handlers. + +endmenu \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/i2cdev/LICENSE Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2018 Ruslan V. Uss (https://github.com/UncleRus) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/i2cdev/component.mk Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,7 @@ +COMPONENT_ADD_INCLUDEDIRS = . + +ifdef CONFIG_IDF_TARGET_ESP8266 +COMPONENT_DEPENDS = esp8266 freertos esp_idf_lib_helpers +else +COMPONENT_DEPENDS = driver freertos esp_idf_lib_helpers +endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/i2cdev/i2cdev.c Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,338 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2018 Ruslan V. Uss <unclerus@gmail.com> + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +/** + * @file i2cdev.c + * + * ESP-IDF I2C master thread-safe functions for communication with I2C slave + * + * Copyright (c) 2018 Ruslan V. Uss <unclerus@gmail.com> + * + * MIT Licensed as described in the file LICENSE + */ +#include <string.h> +#include <inttypes.h> +#include <freertos/FreeRTOS.h> +#include <freertos/task.h> +#include <esp_log.h> +#include "i2cdev.h" + +static const char *TAG = "i2cdev"; + +typedef struct { + SemaphoreHandle_t lock; + i2c_config_t config; + bool installed; +} i2c_port_state_t; + +static i2c_port_state_t states[I2C_NUM_MAX]; + +#if CONFIG_I2CDEV_NOLOCK +#define SEMAPHORE_TAKE(port) +#else +#define SEMAPHORE_TAKE(port) do { \ + if (!xSemaphoreTake(states[port].lock, pdMS_TO_TICKS(CONFIG_I2CDEV_TIMEOUT))) \ + { \ + ESP_LOGE(TAG, "Could not take port mutex %d", port); \ + return ESP_ERR_TIMEOUT; \ + } \ + } while (0) +#endif + +#if CONFIG_I2CDEV_NOLOCK +#define SEMAPHORE_GIVE(port) +#else +#define SEMAPHORE_GIVE(port) do { \ + if (!xSemaphoreGive(states[port].lock)) \ + { \ + ESP_LOGE(TAG, "Could not give port mutex %d", port); \ + return ESP_FAIL; \ + } \ + } while (0) +#endif + +esp_err_t i2cdev_init() +{ + memset(states, 0, sizeof(states)); + +#if !CONFIG_I2CDEV_NOLOCK + for (int i = 0; i < I2C_NUM_MAX; i++) + { + states[i].lock = xSemaphoreCreateMutex(); + if (!states[i].lock) + { + ESP_LOGE(TAG, "Could not create port mutex %d", i); + return ESP_FAIL; + } + } +#endif + + return ESP_OK; +} + +esp_err_t i2cdev_done() +{ + for (int i = 0; i < I2C_NUM_MAX; i++) + { + if (!states[i].lock) continue; + + if (states[i].installed) + { + SEMAPHORE_TAKE(i); + i2c_driver_delete(i); + states[i].installed = false; + SEMAPHORE_GIVE(i); + } +#if !CONFIG_I2CDEV_NOLOCK + vSemaphoreDelete(states[i].lock); +#endif + states[i].lock = NULL; + } + return ESP_OK; +} + +esp_err_t i2c_dev_create_mutex(i2c_dev_t *dev) +{ +#if !CONFIG_I2CDEV_NOLOCK + if (!dev) return ESP_ERR_INVALID_ARG; + + ESP_LOGV(TAG, "[0x%02x at %d] creating mutex", dev->addr, dev->port); + + dev->mutex = xSemaphoreCreateMutex(); + if (!dev->mutex) + { + ESP_LOGE(TAG, "[0x%02x at %d] Could not create device mutex", dev->addr, dev->port); + return ESP_FAIL; + } +#endif + + return ESP_OK; +} + +esp_err_t i2c_dev_delete_mutex(i2c_dev_t *dev) +{ +#if !CONFIG_I2CDEV_NOLOCK + if (!dev) return ESP_ERR_INVALID_ARG; + + ESP_LOGV(TAG, "[0x%02x at %d] deleting mutex", dev->addr, dev->port); + + vSemaphoreDelete(dev->mutex); +#endif + return ESP_OK; +} + +esp_err_t i2c_dev_take_mutex(i2c_dev_t *dev) +{ +#if !CONFIG_I2CDEV_NOLOCK + if (!dev) return ESP_ERR_INVALID_ARG; + + ESP_LOGV(TAG, "[0x%02x at %d] taking mutex", dev->addr, dev->port); + + if (!xSemaphoreTake(dev->mutex, pdMS_TO_TICKS(CONFIG_I2CDEV_TIMEOUT))) + { + ESP_LOGE(TAG, "[0x%02x at %d] Could not take device mutex", dev->addr, dev->port); + return ESP_ERR_TIMEOUT; + } +#endif + return ESP_OK; +} + +esp_err_t i2c_dev_give_mutex(i2c_dev_t *dev) +{ +#if !CONFIG_I2CDEV_NOLOCK + if (!dev) return ESP_ERR_INVALID_ARG; + + ESP_LOGV(TAG, "[0x%02x at %d] giving mutex", dev->addr, dev->port); + + if (!xSemaphoreGive(dev->mutex)) + { + ESP_LOGE(TAG, "[0x%02x at %d] Could not give device mutex", dev->addr, dev->port); + return ESP_FAIL; + } +#endif + return ESP_OK; +} + +inline static bool cfg_equal(const i2c_config_t *a, const i2c_config_t *b) +{ + return a->scl_io_num == b->scl_io_num + && a->sda_io_num == b->sda_io_num +#if HELPER_TARGET_IS_ESP32 + && a->master.clk_speed == b->master.clk_speed +#elif HELPER_TARGET_IS_ESP8266 + && a->clk_stretch_tick == b->clk_stretch_tick +#endif + && a->scl_pullup_en == b->scl_pullup_en + && a->sda_pullup_en == b->sda_pullup_en; +} + +static esp_err_t i2c_setup_port(const i2c_dev_t *dev) +{ + if (dev->port >= I2C_NUM_MAX) return ESP_ERR_INVALID_ARG; + + esp_err_t res; + if (!cfg_equal(&dev->cfg, &states[dev->port].config) || !states[dev->port].installed) + { + ESP_LOGD(TAG, "Reconfiguring I2C driver on port %d", dev->port); + i2c_config_t temp; + memcpy(&temp, &dev->cfg, sizeof(i2c_config_t)); + temp.mode = I2C_MODE_MASTER; + + // Driver reinstallation + if (states[dev->port].installed) + { + i2c_driver_delete(dev->port); + states[dev->port].installed = false; + } +#if HELPER_TARGET_IS_ESP32 +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 1, 0) + // See https://github.com/espressif/esp-idf/issues/10163 + if ((res = i2c_driver_install(dev->port, temp.mode, 0, 0, 0)) != ESP_OK) + return res; + if ((res = i2c_param_config(dev->port, &temp)) != ESP_OK) + return res; +#else + if ((res = i2c_param_config(dev->port, &temp)) != ESP_OK) + return res; + if ((res = i2c_driver_install(dev->port, temp.mode, 0, 0, 0)) != ESP_OK) + return res; +#endif +#endif +#if HELPER_TARGET_IS_ESP8266 + // Clock Stretch time, depending on CPU frequency + temp.clk_stretch_tick = dev->timeout_ticks ? dev->timeout_ticks : I2CDEV_MAX_STRETCH_TIME; + if ((res = i2c_driver_install(dev->port, temp.mode)) != ESP_OK) + return res; + if ((res = i2c_param_config(dev->port, &temp)) != ESP_OK) + return res; +#endif + states[dev->port].installed = true; + + memcpy(&states[dev->port].config, &temp, sizeof(i2c_config_t)); + ESP_LOGD(TAG, "I2C driver successfully reconfigured on port %d", dev->port); + } +#if HELPER_TARGET_IS_ESP32 + int t; + if ((res = i2c_get_timeout(dev->port, &t)) != ESP_OK) + return res; + // Timeout cannot be 0 + uint32_t ticks = dev->timeout_ticks ? dev->timeout_ticks : I2CDEV_MAX_STRETCH_TIME; + if ((ticks != t) && (res = i2c_set_timeout(dev->port, ticks)) != ESP_OK) + return res; + ESP_LOGD(TAG, "Timeout: ticks = %" PRIu32 " (%" PRIu32 " usec) on port %d", dev->timeout_ticks, dev->timeout_ticks / 80, dev->port); +#endif + + return ESP_OK; +} + +esp_err_t i2c_dev_probe(const i2c_dev_t *dev, i2c_dev_type_t operation_type) +{ + if (!dev) return ESP_ERR_INVALID_ARG; + + SEMAPHORE_TAKE(dev->port); + + esp_err_t res = i2c_setup_port(dev); + if (res == ESP_OK) + { + i2c_cmd_handle_t cmd = i2c_cmd_link_create(); + i2c_master_start(cmd); + i2c_master_write_byte(cmd, dev->addr << 1 | (operation_type == I2C_DEV_READ ? 1 : 0), true); + i2c_master_stop(cmd); + + res = i2c_master_cmd_begin(dev->port, cmd, pdMS_TO_TICKS(CONFIG_I2CDEV_TIMEOUT)); + + i2c_cmd_link_delete(cmd); + } + + SEMAPHORE_GIVE(dev->port); + + return res; +} + +esp_err_t i2c_dev_read(const i2c_dev_t *dev, const void *out_data, size_t out_size, void *in_data, size_t in_size) +{ + if (!dev || !in_data || !in_size) return ESP_ERR_INVALID_ARG; + + SEMAPHORE_TAKE(dev->port); + + esp_err_t res = i2c_setup_port(dev); + if (res == ESP_OK) + { + i2c_cmd_handle_t cmd = i2c_cmd_link_create(); + if (out_data && out_size) + { + i2c_master_start(cmd); + i2c_master_write_byte(cmd, dev->addr << 1, true); + i2c_master_write(cmd, (void *)out_data, out_size, true); + } + i2c_master_start(cmd); + i2c_master_write_byte(cmd, (dev->addr << 1) | 1, true); + i2c_master_read(cmd, in_data, in_size, I2C_MASTER_LAST_NACK); + i2c_master_stop(cmd); + + res = i2c_master_cmd_begin(dev->port, cmd, pdMS_TO_TICKS(CONFIG_I2CDEV_TIMEOUT)); + if (res != ESP_OK) + ESP_LOGE(TAG, "Could not read from device [0x%02x at %d]: %d (%s)", dev->addr, dev->port, res, esp_err_to_name(res)); + + i2c_cmd_link_delete(cmd); + } + + SEMAPHORE_GIVE(dev->port); + return res; +} + +esp_err_t i2c_dev_write(const i2c_dev_t *dev, const void *out_reg, size_t out_reg_size, const void *out_data, size_t out_size) +{ + if (!dev || !out_data || !out_size) return ESP_ERR_INVALID_ARG; + + SEMAPHORE_TAKE(dev->port); + + esp_err_t res = i2c_setup_port(dev); + if (res == ESP_OK) + { + i2c_cmd_handle_t cmd = i2c_cmd_link_create(); + i2c_master_start(cmd); + i2c_master_write_byte(cmd, dev->addr << 1, true); + if (out_reg && out_reg_size) + i2c_master_write(cmd, (void *)out_reg, out_reg_size, true); + i2c_master_write(cmd, (void *)out_data, out_size, true); + i2c_master_stop(cmd); + res = i2c_master_cmd_begin(dev->port, cmd, pdMS_TO_TICKS(CONFIG_I2CDEV_TIMEOUT)); + if (res != ESP_OK) + ESP_LOGE(TAG, "Could not write to device [0x%02x at %d]: %d (%s)", dev->addr, dev->port, res, esp_err_to_name(res)); + i2c_cmd_link_delete(cmd); + } + + SEMAPHORE_GIVE(dev->port); + return res; +} + +esp_err_t i2c_dev_read_reg(const i2c_dev_t *dev, uint8_t reg, void *in_data, size_t in_size) +{ + return i2c_dev_read(dev, ®, 1, in_data, in_size); +} + +esp_err_t i2c_dev_write_reg(const i2c_dev_t *dev, uint8_t reg, const void *out_data, size_t out_size) +{ + return i2c_dev_write(dev, ®, 1, out_data, out_size); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/i2cdev/i2cdev.h Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,251 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2018 Ruslan V. Uss <unclerus@gmail.com> + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +/** + * @file i2cdev.h + * @defgroup i2cdev i2cdev + * @{ + * + * ESP-IDF I2C master thread-safe functions for communication with I2C slave + * + * Copyright (c) 2018 Ruslan V. Uss <unclerus@gmail.com> + * + * MIT Licensed as described in the file LICENSE + */ +#ifndef __I2CDEV_H__ +#define __I2CDEV_H__ + +#include <driver/i2c.h> +#include <freertos/FreeRTOS.h> +#include <freertos/semphr.h> +#include <esp_err.h> +#include <esp_idf_lib_helpers.h> + +#ifdef __cplusplus +extern "C" { +#endif + +#if HELPER_TARGET_IS_ESP8266 + +#define I2CDEV_MAX_STRETCH_TIME 0xffffffff + +#else + +#include <soc/i2c_reg.h> +#if defined(I2C_TIME_OUT_VALUE_V) +#define I2CDEV_MAX_STRETCH_TIME I2C_TIME_OUT_VALUE_V +#elif defined(I2C_TIME_OUT_REG_V) +#define I2CDEV_MAX_STRETCH_TIME I2C_TIME_OUT_REG_V +#else +#define I2CDEV_MAX_STRETCH_TIME 0x00ffffff +#endif + +#endif /* HELPER_TARGET_IS_ESP8266 */ + +/** + * I2C device descriptor + */ +typedef struct +{ + i2c_port_t port; //!< I2C port number + i2c_config_t cfg; //!< I2C driver configuration + uint8_t addr; //!< Unshifted address + SemaphoreHandle_t mutex; //!< Device mutex + uint32_t timeout_ticks; /*!< HW I2C bus timeout (stretch time), in ticks. 80MHz APB clock + ticks for ESP-IDF, CPU ticks for ESP8266. + When this value is 0, I2CDEV_MAX_STRETCH_TIME will be used */ +} i2c_dev_t; + +/** + * I2C transaction type + */ +typedef enum { + I2C_DEV_WRITE = 0, /**< Write operation */ + I2C_DEV_READ /**< Read operation */ +} i2c_dev_type_t; + +/** + * @brief Init library + * + * The function must be called before any other + * functions of this library. + * + * @return ESP_OK on success + */ +esp_err_t i2cdev_init(); + +/** + * @brief Finish work with library + * + * Uninstall i2c drivers. + * + * @return ESP_OK on success + */ +esp_err_t i2cdev_done(); + +/** + * @brief Create mutex for device descriptor + * + * This function does nothing if option CONFIG_I2CDEV_NOLOCK is enabled. + * + * @param dev Device descriptor + * @return ESP_OK on success + */ +esp_err_t i2c_dev_create_mutex(i2c_dev_t *dev); + +/** + * @brief Delete mutex for device descriptor + * + * This function does nothing if option CONFIG_I2CDEV_NOLOCK is enabled. + * + * @param dev Device descriptor + * @return ESP_OK on success + */ +esp_err_t i2c_dev_delete_mutex(i2c_dev_t *dev); + +/** + * @brief Take device mutex + * + * This function does nothing if option CONFIG_I2CDEV_NOLOCK is enabled. + * + * @param dev Device descriptor + * @return ESP_OK on success + */ +esp_err_t i2c_dev_take_mutex(i2c_dev_t *dev); + +/** + * @brief Give device mutex + * + * This function does nothing if option CONFIG_I2CDEV_NOLOCK is enabled. + * + * @param dev Device descriptor + * @return ESP_OK on success + */ +esp_err_t i2c_dev_give_mutex(i2c_dev_t *dev); + +/** + * @brief Check the availability of the device + * + * Issue an operation of \p operation_type to the I2C device then stops. + * + * @param dev Device descriptor + * @param operation_type Operation type + * @return ESP_OK if device is available + */ +esp_err_t i2c_dev_probe(const i2c_dev_t *dev, i2c_dev_type_t operation_type); + +/** + * @brief Read from slave device + * + * Issue a send operation of \p out_data register address, followed by reading \p in_size bytes + * from slave into \p in_data . + * Function is thread-safe. + * + * @param dev Device descriptor + * @param out_data Pointer to data to send if non-null + * @param out_size Size of data to send + * @param[out] in_data Pointer to input data buffer + * @param in_size Number of byte to read + * @return ESP_OK on success + */ +esp_err_t i2c_dev_read(const i2c_dev_t *dev, const void *out_data, + size_t out_size, void *in_data, size_t in_size); + +/** + * @brief Write to slave device + * + * Write \p out_size bytes from \p out_data to slave into \p out_reg register address. + * Function is thread-safe. + * + * @param dev Device descriptor + * @param out_reg Pointer to register address to send if non-null + * @param out_reg_size Size of register address + * @param out_data Pointer to data to send + * @param out_size Size of data to send + * @return ESP_OK on success + */ +esp_err_t i2c_dev_write(const i2c_dev_t *dev, const void *out_reg, + size_t out_reg_size, const void *out_data, size_t out_size); + +/** + * @brief Read from register with an 8-bit address + * + * Shortcut to ::i2c_dev_read(). + * + * @param dev Device descriptor + * @param reg Register address + * @param[out] in_data Pointer to input data buffer + * @param in_size Number of byte to read + * @return ESP_OK on success + */ +esp_err_t i2c_dev_read_reg(const i2c_dev_t *dev, uint8_t reg, + void *in_data, size_t in_size); + +/** + * @brief Write to register with an 8-bit address + * + * Shortcut to ::i2c_dev_write(). + * + * @param dev Device descriptor + * @param reg Register address + * @param out_data Pointer to data to send + * @param out_size Size of data to send + * @return ESP_OK on success + */ +esp_err_t i2c_dev_write_reg(const i2c_dev_t *dev, uint8_t reg, + const void *out_data, size_t out_size); + +#define I2C_DEV_TAKE_MUTEX(dev) do { \ + esp_err_t __ = i2c_dev_take_mutex(dev); \ + if (__ != ESP_OK) return __;\ + } while (0) + +#define I2C_DEV_GIVE_MUTEX(dev) do { \ + esp_err_t __ = i2c_dev_give_mutex(dev); \ + if (__ != ESP_OK) return __;\ + } while (0) + +#define I2C_DEV_CHECK(dev, X) do { \ + esp_err_t ___ = X; \ + if (___ != ESP_OK) { \ + I2C_DEV_GIVE_MUTEX(dev); \ + return ___; \ + } \ + } while (0) + +#define I2C_DEV_CHECK_LOGE(dev, X, msg, ...) do { \ + esp_err_t ___ = X; \ + if (___ != ESP_OK) { \ + I2C_DEV_GIVE_MUTEX(dev); \ + ESP_LOGE(TAG, msg, ## __VA_ARGS__); \ + return ___; \ + } \ + } while (0) + +#ifdef __cplusplus +} +#endif + +/**@}*/ + +#endif /* __I2CDEV_H__ */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/icm42670/.eil.yml Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,23 @@ +--- +components: + - name: icm42670 + description: Driver for TDK ICM-42670-P 6-Axis IMU (found on ESP-RS board, https://github.com/esp-rs/esp-rust-board) + group: imu + groups: [] + code_owners: janveeh + depends: + - i2cdev + - log + - esp_idf_lib_helpers + thread_safe: yes + targets: + - name: esp32 + - name: esp8266 + - name: esp32s2 + - name: esp32c3 + licenses: + - name: ICS + copyrights: + - author: + name: janveeh + year: 2022
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/icm42670/CMakeLists.txt Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,5 @@ +idf_component_register( + SRCS icm42670.c + INCLUDE_DIRS . + REQUIRES i2cdev log esp_idf_lib_helpers +)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/icm42670/LICENSE Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,16 @@ + +SPDX-License-Identifier: ISC + +Copyright (c) 2022 Jan Veeh <jan.veeh@motius.de> + +Permission to use, copy, modify, and distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/icm42670/component.mk Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,2 @@ +COMPONENT_ADD_INCLUDEDIRS = . +COMPONENT_DEPENDS = i2cdev log esp_idf_lib_helpers
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/icm42670/icm42670.c Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,722 @@ +/* + * SPDX-License-Identifier: ISC + * + * Copyright (c) 2022 Jan Veeh <jan.veeh@motius.de> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/** + * @file icm42670.c + * + * ESP-IDF driver for TDK ICM-42670-P IMU (found on ESP-RS board) + * + * Copyright (c) 2022 Jan Veeh (jan.veeh@motius.de) + * + * ISC Licensed as described in the file LICENSE + * + * Open TODOs: + * - FIFO reading and handling + * - APEX functions like pedometer, tilt-detection, low-g detection, freefall detection, ... + * + * + */ + +#include <freertos/FreeRTOS.h> +#include <freertos/task.h> +#include <esp_log.h> +#include <esp_idf_lib_helpers.h> +#include <ets_sys.h> +#include "icm42670.h" + +#define I2C_FREQ_HZ 1000000 // 1MHz + +static const char *TAG = "icm42670"; + +// register structure definitions +#define ICM42670_MCLK_RDY_BITS 0x08 // ICM42670_REG_MCLK_RDY<3> +#define ICM42670_MCLK_RDY_SHIFT 3 // ICM42670_REG_MCLK_RDY<3> + +#define ICM42670_SPI_AP_4WIRE_BITS 0x04 // ICM42670_REG_DEVICE_CONFIG<2> +#define ICM42670_SPI_AP_4WIRE_SHIFT 2 // ICM42670_REG_DEVICE_CONFIG<2> +#define ICM42670_SPI_MODE_BITS 0x01 // ICM42670_REG_DEVICE_CONFIG<0> +#define ICM42670_SPI_MODE_SHIFT 0 // ICM42670_REG_DEVICE_CONFIG<0> + +#define ICM42670_SOFT_RESET_DEVICE_CONFIG_BITS 0x10 // ICM42670_REG_SIGNAL_PATH_RESET<4> +#define ICM42670_SOFT_RESET_DEVICE_CONFIG_SHIFT 4 // ICM42670_REG_SIGNAL_PATH_RESET<4> +#define ICM42670_FIFO_FLUSH_BITS 0x04 // ICM42670_REG_SIGNAL_PATH_RESET<2> +#define ICM42670_FIFO_FLUSH_SHIFT 2 // ICM42670_REG_SIGNAL_PATH_RESET<2> + +#define ICM42670_I3C_DDR_SLEW_RATE_BITS 0x38 // ICM42670_REG_DRIVE_CONFIG1<5:3> +#define ICM42670_I3C_DDR_SLEW_RATE_SHIFT 3 // ICM42670_REG_DRIVE_CONFIG1<5:3> +#define ICM42670_I3C_SDR_SLEW_RATE_BITS 0x07 // ICM42670_REG_DRIVE_CONFIG1<2:0> +#define ICM42670_I3C_SDR_SLEW_RATE_SHIFT 0 // ICM42670_REG_DRIVE_CONFIG1<2:0> + +#define ICM42670_I2C_DDR_SLEW_RATE_BITS 0x38 // ICM42670_REG_DRIVE_CONFIG2<5:3> +#define ICM42670_I2C_DDR_SLEW_RATE_SHIFT 3 // ICM42670_REG_DRIVE_CONFIG2<5:3> +#define ICM42670_I2C_SDR_SLEW_RATE_BITS 0x07 // ICM42670_REG_DRIVE_CONFIG2<2:0> +#define ICM42670_I2C_SDR_SLEW_RATE_SHIFT 0 // ICM42670_REG_DRIVE_CONFIG2<2:0> + +#define ICM42670_SPI_SLEW_RATE_BITS 0x07 // ICM42670_REG_DRIVE_CONFIG3<2:0> +#define ICM42670_SPI_SLEW_RATE_SHIFT 0 // ICM42670_REG_DRIVE_CONFIG3<2:0> + +#define ICM42670_INT2_MODE_BITS 0x20 // ICM42670_REG_INT_CONFIG<5> +#define ICM42670_INT2_MODE_SHIFT 5 // ICM42670_REG_INT_CONFIG<5> +#define ICM42670_INT2_DRIVE_CIRCUIT_BITS 0x10 // ICM42670_REG_INT_CONFIG<4> +#define ICM42670_INT2_DRIVE_CIRCUIT_SHIFT 4 // ICM42670_REG_INT_CONFIG<4> +#define ICM42670_INT2_POLARITY_BITS 0x08 // ICM42670_REG_INT_CONFIG<3> +#define ICM42670_INT2_POLARITY_SHIFT 3 // ICM42670_REG_INT_CONFIG<3> +#define ICM42670_INT1_MODE_BITS 0x04 // ICM42670_REG_INT_CONFIG<2> +#define ICM42670_INT1_MODE_SHIFT 2 // ICM42670_REG_INT_CONFIG<2> +#define ICM42670_INT1_DRIVE_CIRCUIT_BITS 0x02 // ICM42670_REG_INT_CONFIG<1> +#define ICM42670_INT1_DRIVE_CIRCUIT_SHIFT 1 // ICM42670_REG_INT_CONFIG<1> +#define ICM42670_INT1_POLARITY_BITS 0x01 // ICM42670_REG_INT_CONFIG<0> +#define ICM42670_INT1_POLARITY_SHIFT 0 // ICM42670_REG_INT_CONFIG<0> + +#define ICM42670_ACCEL_LP_CLK_SEL_BITS 0x80 // ICM42670_REG_PWR_MGMT0<7> +#define ICM42670_ACCEL_LP_CLK_SEL_SHIFT 7 // ICM42670_REG_PWR_MGMT0<7> +#define ICM42670_IDLE_BITS 0x10 // ICM42670_REG_PWR_MGMT0<4> +#define ICM42670_IDLE_SHIFT 4 // ICM42670_REG_PWR_MGMT0<4> +#define ICM42670_GYRO_MODE_BITS 0x0C // ICM42670_REG_PWR_MGMT0<3:2> +#define ICM42670_GYRO_MODE_SHIFT 2 // ICM42670_REG_PWR_MGMT0<3:2> +#define ICM42670_ACCEL_MODE_BITS 0x03 // ICM42670_REG_PWR_MGMT0<1:0> +#define ICM42670_ACCEL_MODE_SHIFT 0 // ICM42670_REG_PWR_MGMT0<1:0> + +#define ICM42670_GYRO_UI_FS_SEL_BITS 0x60 // ICM42670_REG_GYRO_CONFIG0<6:5> +#define ICM42670_GYRO_UI_FS_SEL_SHIFT 5 // ICM42670_REG_GYRO_CONFIG0<6:5> +#define ICM42670_GYRO_ODR_BITS 0x0F // ICM42670_REG_GYRO_CONFIG0<3:0> +#define ICM42670_GYRO_ODR_SHIFT 0 // ICM42670_REG_GYRO_CONFIG0<3:0> + +#define ICM42670_ACCEL_UI_FS_SEL_BITS 0x60 // ICM42670_REG_ACCEL_CONFIG0<6:5> +#define ICM42670_ACCEL_UI_FS_SEL_SHIFT 5 // ICM42670_REG_ACCEL_CONFIG0<6:5> +#define ICM42670_ACCEL_ODR_BITS 0x0F // ICM42670_REG_ACCEL_CONFIG0<3:0> +#define ICM42670_ACCEL_ODR_SHIFT 0 // ICM42670_REG_ACCEL_CONFIG0<3:0> + +#define ICM42670_TEMP_FILT_BW_BITS 0x70 // ICM42670_REG_TEMP_CONFIG0<6:4> +#define ICM42670_TEMP_FILT_BW_SHIFT 4 // ICM42670_REG_TEMP_CONFIG0<6:4> + +#define ICM42670_GYRO_UI_FILT_BW_BITS 0x07 // ICM42670_REG_GYRO_CONFIG1<2:0> +#define ICM42670_GYRO_UI_FILT_BW_SHIFT 0 // ICM42670_REG_GYRO_CONFIG1<2:0> + +#define ICM42670_ACCEL_UI_AVG_BITS 0x70 // ICM42670_REG_ACCEL_CONFIG1<6:4> +#define ICM42670_ACCEL_UI_AVG_SHIFT 4 // ICM42670_REG_ACCEL_CONFIG1<6:4> +#define ICM42670_ACCEL_UI_FILT_BW_BITS 0x07 // ICM42670_REG_ACCEL_CONFIG1<2:0> +#define ICM42670_ACCEL_UI_FILT_BW_SHIFT 0 // ICM42670_REG_ACCEL_CONFIG1<2:0> + +#define ICM42670_DMP_POWER_SAVE_EN_BITS 0x08 // ICM42670_REG_APEX_CONFIG0<3> +#define ICM42670_DMP_POWER_SAVE_EN_SHIFT 3 // ICM42670_REG_APEX_CONFIG0<3> +#define ICM42670_DMP_INIT_EN_BITS 0x04 // ICM42670_REG_APEX_CONFIG0<2> +#define ICM42670_DMP_INIT_EN_SHIFT 2 // ICM42670_REG_APEX_CONFIG0<2> +#define ICM42670_DMP_MEM_RESET_EN_BITS 0x01 // ICM42670_REG_APEX_CONFIG0<0> +#define ICM42670_DMP_MEM_RESET_EN_SHIFT 0 // ICM42670_REG_APEX_CONFIG0<0> + +#define ICM42670_SMD_ENABLE_BITS 0x40 // ICM42670_REG_APEX_CONFIG1<6> +#define ICM42670_SMD_ENABLE_SHIFT 6 // ICM42670_REG_APEX_CONFIG1<6> +#define ICM42670_FF_ENABLE_BITS 0x20 // ICM42670_REG_APEX_CONFIG1<5> +#define ICM42670_FF_ENABLE_SHIFT 5 // ICM42670_REG_APEX_CONFIG1<5> +#define ICM42670_TILT_ENABLE_BITS 0x10 // ICM42670_REG_APEX_CONFIG1<4> +#define ICM42670_TILT_ENABLE_SHIFT 4 // ICM42670_REG_APEX_CONFIG1<4> +#define ICM42670_PED_ENABLE_BITS 0x08 // ICM42670_REG_APEX_CONFIG1<3> +#define ICM42670_PED_ENABLE_SHIFT 3 // ICM42670_REG_APEX_CONFIG1<3> +#define ICM42670_DMP_ODR_BITS 0x03 // ICM42670_REG_APEX_CONFIG1<1:0> +#define ICM42670_DMP_ODR_SHIFT 0 // ICM42670_REG_APEX_CONFIG1<1:0> + +#define ICM42670_WOM_INT_DUR_BITS 0x18 // ICM42670_REG_WOM_CONFIG<4:3> +#define ICM42670_WOM_INT_DUR_SHIFT 3 // ICM42670_REG_WOM_CONFIG<4:3> +#define ICM42670_WOM_INT_MODE_BITS 0x04 // ICM42670_REG_WOM_CONFIG<2> +#define ICM42670_WOM_INT_MODE_SHIFT 2 // ICM42670_REG_WOM_CONFIG<2> +#define ICM42670_WOM_MODE_BITS 0x02 // ICM42670_REG_WOM_CONFIG<1> +#define ICM42670_WOM_MODE_SHIFT 1 // ICM42670_REG_WOM_CONFIG<1> +#define ICM42670_WOM_EN_BITS 0x01 // ICM42670_REG_WOM_CONFIG<0> +#define ICM42670_WOM_EN_SHIFT 0 // ICM42670_REG_WOM_CONFIG<0> + +#define ICM42670_FIFO_MODE_BITS 0x02 // ICM42670_REG_FIFO_CONFIG1<1> +#define ICM42670_FIFO_MODE_SHIFT 1 // ICM42670_REG_FIFO_CONFIG1<1> +#define ICM42670_FIFO_BYPASS_BITS 0x01 // ICM42670_REG_FIFO_CONFIG1<0> +#define ICM42670_FIFO_BYPASS_SHIFT 0 // ICM42670_REG_FIFO_CONFIG1<0> + +#define ICM42670_ST_INT1_EN_BITS 0x80 // ICM42670_REG_INT_SOURCE0<7> +#define ICM42670_ST_INT1_EN_SHIFT 7 // ICM42670_REG_INT_SOURCE0<7> +#define ICM42670_FSYNC_INT1_EN_BITS 0x40 // ICM42670_REG_INT_SOURCE0<6> +#define ICM42670_FSYNC_INT1_EN_SHIFT 6 // ICM42670_REG_INT_SOURCE0<6> +#define ICM42670_PLL_RDY_INT1_EN_BITS 0x20 // ICM42670_REG_INT_SOURCE0<5> +#define ICM42670_PLL_RDY_INT1_EN_SHIFT 5 // ICM42670_REG_INT_SOURCE0<5> +#define ICM42670_RESET_DONE_INT1_EN_BITS 0x10 // ICM42670_REG_INT_SOURCE0<4> +#define ICM42670_RESET_DONE_INT1_EN_SHIFT 4 // ICM42670_REG_INT_SOURCE0<4> +#define ICM42670_DRDY_INT1_EN_BITS 0x08 // ICM42670_REG_INT_SOURCE0<3> +#define ICM42670_DRDY_INT1_EN_SHIFT 3 // ICM42670_REG_INT_SOURCE0<3> +#define ICM42670_FIFO_THS_INT1_EN_BITS 0x04 // ICM42670_REG_INT_SOURCE0<2> +#define ICM42670_FIFO_THS_INT1_EN_SHIFT 2 // ICM42670_REG_INT_SOURCE0<2> +#define ICM42670_FIFO_FULL_INT1_EN_BITS 0x02 // ICM42670_REG_INT_SOURCE0<1> +#define ICM42670_FIFO_FULL_INT1_EN_SHIFT 1 // ICM42670_REG_INT_SOURCE0<1> +#define ICM42670_AGC_RDY_INT1_EN_BITS 0x01 // ICM42670_REG_INT_SOURCE0<0> +#define ICM42670_AGC_RDY_INT1_EN_SHIFT 0 // ICM42670_REG_INT_SOURCE0<0> + +#define ICM42670_I3C_PROTOCOL_ERROR_INT1_EN_BITS 0x40 // ICM42670_REG_INT_SOURCE1<6> +#define ICM42670_I3C_PROTOCOL_ERROR_INT1_EN_SHIFT 6 // ICM42670_REG_INT_SOURCE1<6> +#define ICM42670_SMD_INT1_EN_BITS 0x08 // ICM42670_REG_INT_SOURCE1<3> +#define ICM42670_SMD_INT1_EN_SHIFT 3 // ICM42670_REG_INT_SOURCE1<3> +#define ICM42670_WOM_Z_INT1_EN_BITS 0x04 // ICM42670_REG_INT_SOURCE1<2> +#define ICM42670_WOM_Z_INT1_EN_SHIFT 2 // ICM42670_REG_INT_SOURCE1<2> +#define ICM42670_WOM_Y_INT1_EN_BITS 0x02 // ICM42670_REG_INT_SOURCE1<1> +#define ICM42670_WOM_Y_INT1_EN_SHIFT 1 // ICM42670_REG_INT_SOURCE1<1> +#define ICM42670_WOM_X_INT1_EN_BITS 0x01 // ICM42670_REG_INT_SOURCE1<0> +#define ICM42670_WOM_X_INT1_EN_SHIFT 0 // ICM42670_REG_INT_SOURCE1<0> + +// ICM42670_REG_INT_SOURCE3 and ICM42670_REG_INT_SOURCE4 same as 0 and 1 + +#define ICM42670_DMP_IDLE_BITS 0x04 // ICM42670_REG_APEX_DATA3<2> +#define ICM42670_DMP_IDLE_SHIFT 2 // ICM42670_REG_APEX_DATA3<2> +#define ICM42670_ACTIVITY_CLASS_BITS 0x03 // ICM42670_REG_APEX_DATA3<1:0> +#define ICM42670_ACTIVITY_CLASS_SHIFT 0 // ICM42670_REG_APEX_DATA3<1:0> + +#define ICM42670_FIFO_COUNT_FORMAT_BITS 0x40 // ICM42670_REG_INTF_CONFIG0<6> +#define ICM42670_FIFO_COUNT_FORMAT_SHIFT 6 // ICM42670_REG_INTF_CONFIG0<6> +#define ICM42670_FIFO_COUNT_ENDIAN_BITS 0x20 // ICM42670_REG_INTF_CONFIG0<5> +#define ICM42670_FIFO_COUNT_ENDIAN_SHIFT 5 // ICM42670_REG_INTF_CONFIG0<5> +#define ICM42670_SENSOR_DATA_ENDIAN_BITS 0x10 // ICM42670_REG_INTF_CONFIG0<4> +#define ICM42670_SENSOR_DATA_ENDIAN_SHIFT 4 // ICM42670_REG_INTF_CONFIG0<4> + +#define ICM42670_I3C_SDR_EN_BITS 0x08 // ICM42670_REG_INTF_CONFIG1<3> +#define ICM42670_I3C_SDR_EN_SHIFT 3 // ICM42670_REG_INTF_CONFIG1<3> +#define ICM42670_I3C_DDR_EN_BITS 0x04 // ICM42670_REG_INTF_CONFIG1<2> +#define ICM42670_I3C_DDR_EN_SHIFT 2 // ICM42670_REG_INTF_CONFIG1<2> +#define ICM42670_CLKSEL_BITS 0x03 // ICM42670_REG_INTF_CONFIG1<1:0> +#define ICM42670_CLKSEL_SHIFT 0 // ICM42670_REG_INTF_CONFIG1<1:0> + +#define ICM42670_DATA_RDY_INT_BITS 0x01 // ICM42670_REG_INT_STATUS_DRDY<0> +#define ICM42670_DATA_RDY_INT_SHIFT 0 // ICM42670_REG_INT_STATUS_DRDY<0> + +#define ICM42670_ST_INT_BITS 0x80 // ICM42670_REG_INT_STATUS<7> +#define ICM42670_ST_INT_SHIFT 7 // ICM42670_REG_INT_STATUS<7> +#define ICM42670_FSYNC_INT_BITS 0x40 // ICM42670_REG_INT_STATUS<6> +#define ICM42670_FSYNC_INT_SHIFT 6 // ICM42670_REG_INT_STATUS<6> +#define ICM42670_PLL_RDY_INT_BITS 0x20 // ICM42670_REG_INT_STATUS<5> +#define ICM42670_PLL_RDY_INT_SHIFT 5 // ICM42670_REG_INT_STATUS<5> +#define ICM42670_RESET_DONE_INT_BITS 0x10 // ICM42670_REG_INT_STATUS<4> +#define ICM42670_RESET_DONE_INT_SHIFT 4 // ICM42670_REG_INT_STATUS<4> +#define ICM42670_FIFO_THS_INT_BITS 0x04 // ICM42670_REG_INT_STATUS<2> +#define ICM42670_FIFO_THS_INT_SHIFT 2 // ICM42670_REG_INT_STATUS<2> +#define ICM42670_FIFO_FULL_INT_BITS 0x02 // ICM42670_REG_INT_STATUS<1> +#define ICM42670_FIFO_FULL_INT_SHIFT 1 // ICM42670_REG_INT_STATUS<1> +#define ICM42670_AGC_RDY_INT_BITS 0x01 // ICM42670_REG_INT_STATUS<0> +#define ICM42670_AGC_RDY_INT_SHIFT 0 // ICM42670_REG_INT_STATUS<0> + +#define ICM42670_SMD_INT_BITS 0x08 // ICM42670_REG_INT_STATUS2<3> +#define ICM42670_SMD_INT_SHIFT 3 // ICM42670_REG_INT_STATUS2<3> +#define ICM42670_WOM_X_INT_BITS 0x04 // ICM42670_REG_INT_STATUS2<2> +#define ICM42670_WOM_X_INT_SHIFT 2 // ICM42670_REG_INT_STATUS2<2> +#define ICM42670_WOM_Y_INT_BITS 0x02 // ICM42670_REG_INT_STATUS2<1> +#define ICM42670_WOM_Y_INT_SHIFT 1 // ICM42670_REG_INT_STATUS2<1> +#define ICM42670_WOM_Z_INT_BITS 0x01 // ICM42670_REG_INT_STATUS2<0> +#define ICM42670_WOM_Z_INT_SHIFT 0 // ICM42670_REG_INT_STATUS2<0> + +#define ICM42670_STEP_DET_INT_BITS 0x20 // ICM42670_REG_INT_STATUS3<5> +#define ICM42670_STEP_DET_INT_SHIFT 5 // ICM42670_REG_INT_STATUS3<5> +#define ICM42670_STEP_CNT_OVF_INT_BITS 0x10 // ICM42670_REG_INT_STATUS3<4> +#define ICM42670_STEP_CNT_OVF_INT_SHIFT 4 // ICM42670_REG_INT_STATUS3<4> +#define ICM42670_TILT_DET_INT_BITS 0x08 // ICM42670_REG_INT_STATUS3<3> +#define ICM42670_TILT_DET_INT_SHIFT 3 // ICM42670_REG_INT_STATUS3<3> +#define ICM42670_FF_DET_INT_BITS 0x04 // ICM42670_REG_INT_STATUS3<2> +#define ICM42670_FF_DET_INT_SHIFT 2 // ICM42670_REG_INT_STATUS3<2> +#define ICM42670_LOWG_DET_INT_BITS 0x02 // ICM42670_REG_INT_STATUS3<1> +#define ICM42670_LOWG_DET_INT_SHIFT 1 // ICM42670_REG_INT_STATUS3<1> + +#define CHECK(x) \ + do \ + { \ + esp_err_t __; \ + if ((__ = x) != ESP_OK) \ + return __; \ + } \ + while (0) +#define CHECK_ARG(VAL) \ + do \ + { \ + if (!(VAL)) \ + return ESP_ERR_INVALID_ARG; \ + } \ + while (0) + +static inline esp_err_t write_register(icm42670_t *dev, uint8_t reg, uint8_t value) +{ + CHECK_ARG(dev); + + return i2c_dev_write_reg(&dev->i2c_dev, reg, &value, 1); +} + +static inline esp_err_t read_register(icm42670_t *dev, uint8_t reg, uint8_t *value) +{ + CHECK_ARG(dev && value); + + return i2c_dev_read_reg(&dev->i2c_dev, reg, value, 1); +} + +static inline esp_err_t read_register_16(icm42670_t *dev, uint8_t upper_byte_reg, int16_t *value) +{ + CHECK_ARG(dev && value); + + esp_err_t err; + uint8_t reg_0, reg_1; + err = read_register(dev, upper_byte_reg, ®_1); + err = read_register(dev, upper_byte_reg + 1, ®_0); + *value = reg_0 | (reg_1 << 8); + + return err; +} + +static inline esp_err_t manipulate_register(icm42670_t *dev, uint8_t reg_addr, uint8_t mask, uint8_t shift, + uint8_t value) +{ + CHECK_ARG(dev); + + uint8_t reg; + I2C_DEV_TAKE_MUTEX(&dev->i2c_dev); + I2C_DEV_CHECK(&dev->i2c_dev, read_register(dev, reg_addr, ®)); + reg = (reg & ~mask) | (value << shift); + I2C_DEV_CHECK(&dev->i2c_dev, write_register(dev, reg_addr, reg)); + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); + + return ESP_OK; +} + +static inline esp_err_t read_mreg_register(icm42670_t *dev, icm42670_mreg_number_t mreg_num, uint8_t reg, + uint8_t *value) +{ + CHECK_ARG(dev && value); + + bool mclk_rdy; + CHECK(icm42670_get_mclk_rdy(dev, &mclk_rdy)); + if (!mclk_rdy) + { + ESP_LOGE(TAG, "MCLK not running, required to access MREG"); + return ESP_ERR_INVALID_RESPONSE; + } + + I2C_DEV_TAKE_MUTEX(&dev->i2c_dev); + I2C_DEV_CHECK(&dev->i2c_dev, write_register(dev, ICM42670_REG_BLK_SEL_R, mreg_num)); + I2C_DEV_CHECK(&dev->i2c_dev, write_register(dev, ICM42670_REG_MADDR_R, reg)); + ets_delay_us(10); // Wait for 10us until MREG write is complete + I2C_DEV_CHECK(&dev->i2c_dev, read_register(dev, ICM42670_REG_M_R, value)); + ets_delay_us(10); + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); + + return ESP_OK; +} + +static inline esp_err_t write_mreg_register(icm42670_t *dev, icm42670_mreg_number_t mreg_num, uint8_t reg, + uint8_t value) +{ + CHECK_ARG(dev); + + bool mclk_rdy; + CHECK(icm42670_get_mclk_rdy(dev, &mclk_rdy)); + if (!mclk_rdy) + { + ESP_LOGE(TAG, "MCLK not running, required to access MREG"); + return ESP_ERR_INVALID_RESPONSE; + } + + I2C_DEV_TAKE_MUTEX(&dev->i2c_dev); + I2C_DEV_CHECK(&dev->i2c_dev, write_register(dev, ICM42670_REG_BLK_SEL_W, mreg_num)); + I2C_DEV_CHECK(&dev->i2c_dev, write_register(dev, ICM42670_REG_MADDR_W, reg)); + I2C_DEV_CHECK(&dev->i2c_dev, write_register(dev, ICM42670_REG_M_W, value)); + ets_delay_us(10); // Wait for 10us until MREG write is complete + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); + + return ESP_OK; +} + +static inline esp_err_t manipulate_mreg_register(icm42670_t *dev, icm42670_mreg_number_t mreg_num, uint8_t reg_addr, + uint8_t mask, uint8_t shift, uint8_t value) +{ + CHECK_ARG(dev); + + uint8_t reg; + CHECK(read_mreg_register(dev, mreg_num, reg_addr, ®)); + reg = (reg & ~mask) | (value << shift); + CHECK(write_mreg_register(dev, mreg_num, reg_addr, reg)); + + return ESP_OK; +} +/////////////////////////////////////////////////////////////////////////////// + +esp_err_t icm42670_init_desc(icm42670_t *dev, uint8_t addr, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio) +{ + CHECK_ARG(dev); + + if (addr != ICM42670_I2C_ADDR_GND && addr != ICM42670_I2C_ADDR_VCC) + { + ESP_LOGE(TAG, "Invalid I2C address `0x%x`: must be one of 0x%x, 0x%x", addr, ICM42670_I2C_ADDR_GND, + ICM42670_I2C_ADDR_VCC); + return ESP_ERR_INVALID_ARG; + } + + dev->i2c_dev.port = port; + dev->i2c_dev.addr = addr; + dev->i2c_dev.cfg.sda_io_num = sda_gpio; + dev->i2c_dev.cfg.scl_io_num = scl_gpio; + dev->i2c_dev.timeout_ticks = 0; // set to default +#if HELPER_TARGET_IS_ESP32 + dev->i2c_dev.cfg.master.clk_speed = I2C_FREQ_HZ; +#endif + + return i2c_dev_create_mutex(&dev->i2c_dev); +} + +esp_err_t icm42670_free_desc(icm42670_t *dev) +{ + CHECK_ARG(dev); + + return i2c_dev_delete_mutex(&dev->i2c_dev); +} + +esp_err_t icm42670_init(icm42670_t *dev) +{ + CHECK_ARG(dev); + uint8_t reg; + + // check who_am_i register + I2C_DEV_TAKE_MUTEX(&dev->i2c_dev); + I2C_DEV_CHECK(&dev->i2c_dev, read_register(dev, ICM42670_REG_WHO_AM_I, ®)); + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); + if (reg != 0x67) + { + ESP_LOGE(TAG, "Error initializing ICM42670, who_am_i register did not return 0x67"); + return ESP_ERR_INVALID_RESPONSE; + } + ESP_LOGD(TAG, "Init: Chip ICM42670 detected"); + + // flush FIFO + CHECK(icm42670_flush_fifo(dev)); + // perform signal path reset + CHECK(icm42670_reset(dev)); + ESP_LOGD(TAG, "Init: Soft-Reset performed"); + + // wait 10ms + vTaskDelay(pdMS_TO_TICKS(10)); + + // set device in IDLE power state + CHECK(icm42670_set_idle_pwr_mode(dev, true)); + + // wait 10ms + vTaskDelay(pdMS_TO_TICKS(10)); + + // check if internal clock is running + bool mclk_rdy = false; + CHECK(icm42670_get_mclk_rdy(dev, &mclk_rdy)); + if (!mclk_rdy) + { + ESP_LOGE(TAG, "Error initializing icm42670, Internal clock not running"); + return ESP_ERR_INVALID_RESPONSE; + } + ESP_LOGD(TAG, "Init: Internal clock running"); + + return ESP_OK; +} + +esp_err_t icm42670_set_idle_pwr_mode(icm42670_t *dev, bool enable_idle) +{ + CHECK_ARG(dev); + + CHECK(manipulate_register(dev, ICM42670_REG_PWR_MGMT0, ICM42670_IDLE_BITS, ICM42670_IDLE_SHIFT, + (uint8_t)enable_idle)); + + return ESP_OK; +} + +esp_err_t icm42670_set_gyro_pwr_mode(icm42670_t *dev, icm42670_gyro_pwr_mode_t pwr_mode) +{ + CHECK_ARG(dev); + + CHECK(manipulate_register(dev, ICM42670_REG_PWR_MGMT0, ICM42670_GYRO_MODE_BITS, ICM42670_GYRO_MODE_SHIFT, pwr_mode)); + // no register writes should be performed within the next 200us + ets_delay_us(300); + + return ESP_OK; +} + +esp_err_t icm42670_set_accel_pwr_mode(icm42670_t *dev, icm42670_accel_pwr_mode_t pwr_mode) +{ + CHECK_ARG(dev); + + // certain odr and avg settings are not allowed in LP or LN mode + icm42670_accel_odr_t odr; + icm42670_accel_avg_t avg; + CHECK(icm42670_get_accel_odr(dev, &odr)); + CHECK(icm42670_get_accel_avg(dev, &avg)); + + if ((pwr_mode == ICM42670_ACCEL_ENABLE_LP_MODE) + && ((odr == ICM42670_ACCEL_ODR_800HZ) || (odr == ICM42670_ACCEL_ODR_1_6KHZ) + || ((odr == ICM42670_ACCEL_ODR_200HZ) && (avg == ICM42670_ACCEL_AVG_64X)))) + { + ESP_LOGE(TAG, "Accel ODR and AVG settings invalid for Low-power mode"); + return ESP_ERR_INVALID_ARG; + } + + if ((pwr_mode == ICM42670_ACCEL_ENABLE_LN_MODE) + && ((odr == ICM42670_ACCEL_ODR_6_25HZ) || (odr == ICM42670_ACCEL_ODR_3_125HZ) + || (odr == ICM42670_ACCEL_ODR_1_5625HZ))) + { + ESP_LOGE(TAG, "Accel ODR settings invalid for Low-noise mode"); + return ESP_ERR_INVALID_ARG; + } + + CHECK(manipulate_register(dev, ICM42670_REG_PWR_MGMT0, ICM42670_ACCEL_MODE_BITS, ICM42670_ACCEL_MODE_SHIFT, + pwr_mode)); + + return ESP_OK; +} + +esp_err_t icm42670_set_low_power_clock(icm42670_t *dev, icm42670_lp_clock_source_t clock_source) +{ + CHECK_ARG(dev); + + CHECK(manipulate_register(dev, ICM42670_REG_PWR_MGMT0, ICM42670_ACCEL_LP_CLK_SEL_BITS, + ICM42670_ACCEL_LP_CLK_SEL_SHIFT, clock_source)); + + return ESP_OK; +} + +esp_err_t icm42670_read_raw_data(icm42670_t *dev, uint8_t data_register, int16_t *data) +{ + CHECK_ARG(dev && data); + + I2C_DEV_TAKE_MUTEX(&dev->i2c_dev); + I2C_DEV_CHECK(&dev->i2c_dev, read_register_16(dev, data_register, data)); + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); + + return ESP_OK; +} + +esp_err_t icm42670_read_temperature(icm42670_t *dev, float *temperature) +{ + CHECK_ARG(dev && temperature); + + int16_t reg; + CHECK(icm42670_read_raw_data(dev, ICM42670_REG_TEMP_DATA1, ®)); + *temperature = (reg / 128.0) + 25; + + return ESP_OK; +} + +esp_err_t icm42670_reset(icm42670_t *dev) +{ + CHECK_ARG(dev); + + uint8_t reg = 1 << ICM42670_SOFT_RESET_DEVICE_CONFIG_SHIFT; + I2C_DEV_TAKE_MUTEX(&dev->i2c_dev); + I2C_DEV_CHECK(&dev->i2c_dev, write_register(dev, ICM42670_REG_SIGNAL_PATH_RESET, reg)); + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); + + return ESP_OK; +} + +esp_err_t icm42670_flush_fifo(icm42670_t *dev) +{ + CHECK_ARG(dev); + + uint8_t reg = 1 << ICM42670_FIFO_FLUSH_SHIFT; + I2C_DEV_TAKE_MUTEX(&dev->i2c_dev); + I2C_DEV_CHECK(&dev->i2c_dev, write_register(dev, ICM42670_REG_SIGNAL_PATH_RESET, reg)); + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); + ets_delay_us(2); // flush is done within 1.5us + + return ESP_OK; +} + +esp_err_t icm42670_set_gyro_fsr(icm42670_t *dev, icm42670_gyro_fsr_t range) +{ + CHECK_ARG(dev); + return manipulate_register(dev, ICM42670_REG_GYRO_CONFIG0, ICM42670_GYRO_UI_FS_SEL_BITS, + ICM42670_GYRO_UI_FS_SEL_SHIFT, range); +} + +esp_err_t icm42670_set_gyro_odr(icm42670_t *dev, icm42670_gyro_odr_t odr) +{ + CHECK_ARG(dev); + return manipulate_register(dev, ICM42670_REG_GYRO_CONFIG0, ICM42670_GYRO_ODR_BITS, ICM42670_GYRO_ODR_SHIFT, odr); +} + +esp_err_t icm42670_set_accel_fsr(icm42670_t *dev, icm42670_accel_fsr_t range) +{ + CHECK_ARG(dev); + return manipulate_register(dev, ICM42670_REG_ACCEL_CONFIG0, ICM42670_ACCEL_UI_FS_SEL_BITS, + ICM42670_ACCEL_UI_FS_SEL_SHIFT, range); +} + +esp_err_t icm42670_set_accel_odr(icm42670_t *dev, icm42670_accel_odr_t odr) +{ + CHECK_ARG(dev); + return manipulate_register(dev, ICM42670_REG_ACCEL_CONFIG0, ICM42670_ACCEL_ODR_BITS, ICM42670_ACCEL_ODR_SHIFT, odr); +} + +esp_err_t icm42670_set_temp_lpf(icm42670_t *dev, icm42670_temp_lfp_t lpf_bw) +{ + CHECK_ARG(dev); + return manipulate_register(dev, ICM42670_REG_TEMP_CONFIG0, ICM42670_TEMP_FILT_BW_BITS, ICM42670_TEMP_FILT_BW_SHIFT, + lpf_bw); +} + +esp_err_t icm42670_set_gyro_lpf(icm42670_t *dev, icm42670_gyro_lfp_t lpf_bw) +{ + CHECK_ARG(dev); + return manipulate_register(dev, ICM42670_REG_GYRO_CONFIG1, ICM42670_GYRO_UI_FILT_BW_BITS, + ICM42670_GYRO_UI_FILT_BW_SHIFT, lpf_bw); +} + +esp_err_t icm42670_set_accel_lpf(icm42670_t *dev, icm42670_accel_lfp_t lpf_bw) +{ + CHECK_ARG(dev); + return manipulate_register(dev, ICM42670_REG_ACCEL_CONFIG1, ICM42670_ACCEL_UI_FILT_BW_BITS, + ICM42670_ACCEL_UI_FILT_BW_SHIFT, lpf_bw); +} + +esp_err_t icm42670_set_accel_avg(icm42670_t *dev, icm42670_accel_avg_t avg) +{ + CHECK_ARG(dev); + return manipulate_register(dev, ICM42670_REG_ACCEL_CONFIG1, ICM42670_ACCEL_UI_AVG_BITS, ICM42670_ACCEL_UI_AVG_SHIFT, + avg); +} + +esp_err_t icm42670_config_int_pin(icm42670_t *dev, uint8_t int_pin, icm42670_int_config_t config) +{ + CHECK_ARG(dev && int_pin < 3 && int_pin > 0); + + uint8_t reg = config.mode << 2 | config.drive << 1 | config.polarity; + if (int_pin == 2) + { + return manipulate_register(dev, ICM42670_REG_INT_CONFIG, 0b00111000, ICM42670_INT2_POLARITY_SHIFT, reg); + } + else + { + return manipulate_register(dev, ICM42670_REG_INT_CONFIG, 0b00000111, ICM42670_INT1_POLARITY_SHIFT, reg); + } +} + +esp_err_t icm42670_set_int_sources(icm42670_t *dev, uint8_t int_pin, icm42670_int_source_t sources) +{ + CHECK_ARG(dev && int_pin < 3 && int_pin > 0); + + uint8_t reg1 = 0, reg2 = 0; + if (sources.self_test_done) + reg1 = reg1 | (1 << ICM42670_ST_INT1_EN_SHIFT); + if (sources.fsync) + reg1 = reg1 | (1 << ICM42670_FSYNC_INT1_EN_SHIFT); + if (sources.pll_ready) + reg1 = reg1 | (1 << ICM42670_PLL_RDY_INT1_EN_SHIFT); + if (sources.reset_done) + reg1 = reg1 | (1 << ICM42670_RESET_DONE_INT1_EN_SHIFT); + if (sources.data_ready) + reg1 = reg1 | (1 << ICM42670_DRDY_INT1_EN_SHIFT); + if (sources.fifo_threshold) + reg1 = reg1 | (1 << ICM42670_FIFO_THS_INT1_EN_SHIFT); + if (sources.fifo_full) + reg1 = reg1 | (1 << ICM42670_FIFO_FULL_INT1_EN_SHIFT); + if (sources.agc_ready) + reg1 = reg1 | (1 << ICM42670_AGC_RDY_INT1_EN_SHIFT); + if (sources.i3c_error) + reg2 = reg2 | (1 << ICM42670_I3C_PROTOCOL_ERROR_INT1_EN_SHIFT); + if (sources.smd) + reg2 = reg2 | (1 << ICM42670_SMD_INT1_EN_SHIFT); + if (sources.wom_z) + reg2 = reg2 | (1 << ICM42670_WOM_Z_INT1_EN_SHIFT); + if (sources.wom_y) + reg2 = reg2 | (1 << ICM42670_WOM_Y_INT1_EN_SHIFT); + if (sources.wom_x) + reg2 = reg2 | (1 << ICM42670_WOM_X_INT1_EN_SHIFT); + + if (int_pin == 1) + { + I2C_DEV_TAKE_MUTEX(&dev->i2c_dev); + I2C_DEV_CHECK(&dev->i2c_dev, write_register(dev, ICM42670_REG_INT_SOURCE0, reg1)); + I2C_DEV_CHECK(&dev->i2c_dev, write_register(dev, ICM42670_REG_INT_SOURCE1, reg2)); + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); + } + else + { + I2C_DEV_TAKE_MUTEX(&dev->i2c_dev); + I2C_DEV_CHECK(&dev->i2c_dev, write_register(dev, ICM42670_REG_INT_SOURCE3, reg1)); + I2C_DEV_CHECK(&dev->i2c_dev, write_register(dev, ICM42670_REG_INT_SOURCE4, reg2)); + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); + } + + return ESP_OK; +} + +esp_err_t icm42670_config_wom(icm42670_t *dev, icm42670_wom_config_t config) +{ + CHECK_ARG(dev); + + CHECK(manipulate_register(dev, ICM42670_REG_WOM_CONFIG, ICM42670_WOM_INT_DUR_BITS, ICM42670_WOM_INT_DUR_SHIFT, + config.trigger)); + CHECK(manipulate_register(dev, ICM42670_REG_WOM_CONFIG, ICM42670_WOM_INT_MODE_BITS, ICM42670_WOM_INT_MODE_SHIFT, + config.logical_mode)); + CHECK(manipulate_register(dev, ICM42670_REG_WOM_CONFIG, ICM42670_WOM_MODE_BITS, ICM42670_WOM_MODE_SHIFT, + config.reference)); + + // WoM threshold values + CHECK(write_mreg_register(dev, ICM42670_MREG1_RW, ICM42670_REG_ACCEL_WOM_X_THR, config.wom_x_threshold)); + CHECK(write_mreg_register(dev, ICM42670_MREG1_RW, ICM42670_REG_ACCEL_WOM_Y_THR, config.wom_y_threshold)); + CHECK(write_mreg_register(dev, ICM42670_MREG1_RW, ICM42670_REG_ACCEL_WOM_Z_THR, config.wom_z_threshold)); + + return ESP_OK; +} + +esp_err_t icm42670_enable_wom(icm42670_t *dev, bool enable) +{ + CHECK_ARG(dev); + return manipulate_register(dev, ICM42670_REG_WOM_CONFIG, ICM42670_WOM_EN_BITS, ICM42670_WOM_EN_SHIFT, enable); +} + +esp_err_t icm42670_get_mclk_rdy(icm42670_t *dev, bool *mclk_rdy) +{ + CHECK_ARG(dev && mclk_rdy); + + uint8_t reg; + I2C_DEV_TAKE_MUTEX(&dev->i2c_dev); + I2C_DEV_CHECK(&dev->i2c_dev, read_register(dev, ICM42670_REG_MCLK_RDY, ®)); + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); + if ((reg & ICM42670_MCLK_RDY_BITS) >> ICM42670_MCLK_RDY_SHIFT) + *mclk_rdy = true; + else + *mclk_rdy = false; + + return ESP_OK; +} + +esp_err_t icm42670_get_accel_odr(icm42670_t *dev, icm42670_accel_odr_t *odr) +{ + CHECK_ARG(dev && odr); + + uint8_t reg; + I2C_DEV_TAKE_MUTEX(&dev->i2c_dev); + I2C_DEV_CHECK(&dev->i2c_dev, read_register(dev, ICM42670_REG_ACCEL_CONFIG0, ®)); + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); + *odr = (reg & ICM42670_ACCEL_ODR_BITS) >> ICM42670_ACCEL_ODR_SHIFT; + + return ESP_OK; +} + +esp_err_t icm42670_get_accel_avg(icm42670_t *dev, icm42670_accel_avg_t *avg) +{ + CHECK_ARG(dev && avg); + + uint8_t reg; + I2C_DEV_TAKE_MUTEX(&dev->i2c_dev); + I2C_DEV_CHECK(&dev->i2c_dev, read_register(dev, ICM42670_REG_ACCEL_CONFIG1, ®)); + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); + *avg = (reg & ICM42670_ACCEL_UI_AVG_BITS) >> ICM42670_ACCEL_UI_AVG_SHIFT; + + return ESP_OK; +} \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/icm42670/icm42670.h Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,606 @@ +/* + * SPDX-License-Identifier: ISC + * + * Copyright (c) 2022 Jan Veeh <jan.veeh@motius.de> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/** + * @file icm42670.h + * @defgroup icm42670 icm42670 + * @{ + * + * ESP-IDF driver for TDK ICM-42670-P IMU (found on ESP-RS board) + * + * Copyright (c) 2022 Jan Veeh (jan.veeh@motius.de) + * + * ISC Licensed as described in the file LICENSE + */ + +#ifndef __ICM42670_H__ +#define __ICM42670_H__ + +#include <i2cdev.h> +#include <esp_err.h> + +#ifdef __cplusplus +extern "C" { +#endif + +#define ICM42670_I2C_ADDR_GND 0x68 +#define ICM42670_I2C_ADDR_VCC 0x69 + +// Registers USER BANK 0 +#define ICM42670_REG_MCLK_RDY 0x00 +#define ICM42670_REG_DEVICE_CONFIG 0x01 +#define ICM42670_REG_SIGNAL_PATH_RESET 0x02 +#define ICM42670_REG_DRIVE_CONFIG1 0x03 +#define ICM42670_REG_DRIVE_CONFIG2 0x04 +#define ICM42670_REG_DRIVE_CONFIG3 0x05 +#define ICM42670_REG_INT_CONFIG 0x06 +#define ICM42670_REG_TEMP_DATA1 0x09 +#define ICM42670_REG_TEMP_DATA0 0x0A +#define ICM42670_REG_ACCEL_DATA_X1 0x0B +#define ICM42670_REG_ACCEL_DATA_X0 0x0C +#define ICM42670_REG_ACCEL_DATA_Y1 0x0D +#define ICM42670_REG_ACCEL_DATA_Y0 0x0E +#define ICM42670_REG_ACCEL_DATA_Z1 0x0F +#define ICM42670_REG_ACCEL_DATA_Z0 0x10 +#define ICM42670_REG_GYRO_DATA_X1 0x11 +#define ICM42670_REG_GYRO_DATA_X0 0x12 +#define ICM42670_REG_GYRO_DATA_Y1 0x13 +#define ICM42670_REG_GYRO_DATA_Y0 0x14 +#define ICM42670_REG_GYRO_DATA_Z1 0x15 +#define ICM42670_REG_GYRO_DATA_Z0 0x16 +#define ICM42670_REG_TMST_FSYNCH 0x17 +#define ICM42670_REG_TMST_FSYNCL 0x18 +#define ICM42670_REG_APEX_DATA4 0x1D +#define ICM42670_REG_APEX_DATA5 0x1E +#define ICM42670_REG_PWR_MGMT0 0x1F +#define ICM42670_REG_GYRO_CONFIG0 0x20 +#define ICM42670_REG_ACCEL_CONFIG0 0x21 +#define ICM42670_REG_TEMP_CONFIG0 0x22 +#define ICM42670_REG_GYRO_CONFIG1 0x23 +#define ICM42670_REG_ACCEL_CONFIG1 0x24 +#define ICM42670_REG_APEX_CONFIG0 0x25 +#define ICM42670_REG_APEX_CONFIG1 0x26 +#define ICM42670_REG_WOM_CONFIG 0x27 +#define ICM42670_REG_FIFO_CONFIG1 0x28 +#define ICM42670_REG_FIFO_CONFIG2 0x29 +#define ICM42670_REG_FIFO_CONFIG3 0x2A +#define ICM42670_REG_INT_SOURCE0 0x2B +#define ICM42670_REG_INT_SOURCE1 0x2C +#define ICM42670_REG_INT_SOURCE3 0x2D +#define ICM42670_REG_INT_SOURCE4 0x2E +#define ICM42670_REG_FIFO_LOST_PKT0 0x2F +#define ICM42670_REG_FIFO_LOST_PKT1 0x30 +#define ICM42670_REG_APEX_DATA0 0x31 +#define ICM42670_REG_APEX_DATA1 0x32 +#define ICM42670_REG_APEX_DATA2 0x33 +#define ICM42670_REG_APEX_DATA3 0x34 +#define ICM42670_REG_INTF_CONFIG0 0x35 +#define ICM42670_REG_INTF_CONFIG1 0x36 +#define ICM42670_REG_INT_STATUS_DRDY 0x39 +#define ICM42670_REG_INT_STATUS 0x3A +#define ICM42670_REG_INT_STATUS2 0x3B +#define ICM42670_REG_INT_STATUS3 0x3C +#define ICM42670_REG_FIFO_COUNTH 0x3D +#define ICM42670_REG_FIFO_COUNTL 0x3E +#define ICM42670_REG_FIFO_DATA 0x3F +#define ICM42670_REG_WHO_AM_I 0x75 +#define ICM42670_REG_BLK_SEL_W 0x79 +#define ICM42670_REG_MADDR_W 0x7A +#define ICM42670_REG_M_W 0x7B +#define ICM42670_REG_BLK_SEL_R 0x7C +#define ICM42670_REG_MADDR_R 0x7D +#define ICM42670_REG_M_R 0x7E + +// MREG1 registers +#define ICM42670_REG_TMST_CONFIG1 0x00 +#define ICM42670_REG_FIFO_CONFIG5 0x01 +#define ICM42670_REG_FIFO_CONFIG6 0x02 +#define ICM42670_REG_FSYNC_CONFIG 0x03 +#define ICM42670_REG_INT_CONFIG0 0x04 +#define ICM42670_REG_INT_CONFIG1 0x05 +#define ICM42670_REG_SENSOR_CONFIG3 0x06 +#define ICM42670_REG_ST_CONFIG 0x13 +#define ICM42670_REG_SELFTEST 0x14 +#define ICM42670_REG_INTF_CONFIG6 0x23 +#define ICM42670_REG_INTF_CONFIG10 0x25 +#define ICM42670_REG_INTF_CONFIG7 0x28 +#define ICM42670_REG_OTP_CONFIG 0x2B +#define ICM42670_REG_INT_SOURCE6 0x2F +#define ICM42670_REG_INT_SOURCE7 0x30 +#define ICM42670_REG_INT_SOURCE8 0x31 +#define ICM42670_REG_INT_SOURCE9 0x32 +#define ICM42670_REG_INT_SOURCE10 0x33 +#define ICM42670_REG_APEX_CONFIG2 0x44 +#define ICM42670_REG_APEX_CONFIG3 0x45 +#define ICM42670_REG_APEX_CONFIG4 0x46 +#define ICM42670_REG_APEX_CONFIG5 0x47 +#define ICM42670_REG_APEX_CONFIG9 0x48 +#define ICM42670_REG_APEX_CONFIG10 0x49 +#define ICM42670_REG_APEX_CONFIG11 0x4A +#define ICM42670_REG_ACCEL_WOM_X_THR 0x4B +#define ICM42670_REG_ACCEL_WOM_Y_THR 0x4C +#define ICM42670_REG_ACCEL_WOM_Z_THR 0x4D +#define ICM42670_REG_OFFSET_USER0 0x4E +#define ICM42670_REG_OFFSET_USER1 0x4F +#define ICM42670_REG_OFFSET_USER2 0x50 +#define ICM42670_REG_OFFSET_USER3 0x51 +#define ICM42670_REG_OFFSET_USER4 0x52 +#define ICM42670_REG_OFFSET_USER5 0x53 +#define ICM42670_REG_OFFSET_USER6 0x54 +#define ICM42670_REG_OFFSET_USER7 0x55 +#define ICM42670_REG_OFFSET_USER8 0x56 +#define ICM42670_REG_ST_STATUS1 0x63 +#define ICM42670_REG_ST_STATUS2 0x64 +#define ICM42670_REG_FDR_CONFIG 0x66 +#define ICM42670_REG_APEX_CONFIG12 0x67 + +// MREG2 registers +#define ICM42670_REG_OTP_CTRL7 0x06 + +// MREG3 registers +#define ICM42670_REG_XA_ST_DATA 0x00 +#define ICM42670_REG_YA_ST_DATA 0x01 +#define ICM42670_REG_ZA_ST_DATA 0x02 +#define ICM42670_REG_XG_ST_DATA 0x03 +#define ICM42670_REG_YG_ST_DATA 0x04 +#define ICM42670_REG_ZG_ST_DATA 0x05 + +/* Gyro power mode */ +typedef enum { + ICM42670_GYRO_DISABLE = 0b00, + ICM42670_GYRO_STANDBY = 0b01, + ICM42670_GYRO_ENABLE_LN_MODE = 0b11 +} icm42670_gyro_pwr_mode_t; + +/* Accelerometer power mode */ +typedef enum { + ICM42670_ACCEL_DISABLE = 0b00, + ICM42670_ACCEL_ENABLE_LP_MODE = 0b10, + ICM42670_ACCEL_ENABLE_LN_MODE = 0b11 +} icm42670_accel_pwr_mode_t; + +/* Accelerometer low power mode clock source */ +typedef enum { + ICM42670_LP_CLK_WUO = 0, + ICM42670_LP_CLK_RCO = 1 +} icm42670_lp_clock_source_t; + +/* Gyro FSR (full scale range) */ +typedef enum { + ICM42670_GYRO_RANGE_2000DPS = 0b00, + ICM42670_GYRO_RANGE_1000DPS = 0b01, + ICM42670_GYRO_RANGE_500DPS = 0b10, + ICM42670_GYRO_RANGE_250DPS = 0b11 +} icm42670_gyro_fsr_t; + +/* Gyro ODR (output data rate) */ +typedef enum { + ICM42670_GYRO_ODR_12_5HZ = 0b1100, + ICM42670_GYRO_ODR_25HZ = 0b1011, + ICM42670_GYRO_ODR_50HZ = 0b1010, + ICM42670_GYRO_ODR_100HZ = 0b1001, + ICM42670_GYRO_ODR_200HZ = 0b1000, + ICM42670_GYRO_ODR_400HZ = 0b0111, + ICM42670_GYRO_ODR_800HZ = 0b0110, + ICM42670_GYRO_ODR_1_6KHZ = 0b0101 +} icm42670_gyro_odr_t; + +/* Accelerometer FSR (full scale range) */ +typedef enum { + ICM42670_ACCEL_RANGE_16G = 0b00, + ICM42670_ACCEL_RANGE_8G = 0b01, + ICM42670_ACCEL_RANGE_4G = 0b10, + ICM42670_ACCEL_RANGE_2G = 0b11 +} icm42670_accel_fsr_t; + +/* Accelerometer ODR (output data rate) */ +typedef enum { + ICM42670_ACCEL_ODR_1_5625HZ = 0b1111, + ICM42670_ACCEL_ODR_3_125HZ = 0b1110, + ICM42670_ACCEL_ODR_6_25HZ = 0b1101, + ICM42670_ACCEL_ODR_12_5HZ = 0b1100, + ICM42670_ACCEL_ODR_25HZ = 0b1011, + ICM42670_ACCEL_ODR_50HZ = 0b1010, + ICM42670_ACCEL_ODR_100HZ = 0b1001, + ICM42670_ACCEL_ODR_200HZ = 0b1000, + ICM42670_ACCEL_ODR_400HZ = 0b0111, + ICM42670_ACCEL_ODR_800HZ = 0b0110, + ICM42670_ACCEL_ODR_1_6KHZ = 0b0101 +} icm42670_accel_odr_t; + +/* Temperature LPF (low pass filter) */ +typedef enum { + ICM42670_TEMP_LFP_BYPASSED = 0b000, + ICM42670_TEMP_LFP_180HZ = 0b001, + ICM42670_TEMP_LFP_72HZ = 0b010, + ICM42670_TEMP_LFP_34HZ = 0b011, + ICM42670_TEMP_LFP_16HZ = 0b100, + ICM42670_TEMP_LFP_8HZ = 0b101, + ICM42670_TEMP_LFP_4HZ = 0b110 +} icm42670_temp_lfp_t; + +/* Gyro LPF (low pass filter) */ +typedef enum { + ICM42670_GYRO_LFP_BYPASSED = 0b000, + ICM42670_GYRO_LFP_180HZ = 0b001, + ICM42670_GYRO_LFP_121HZ = 0b010, + ICM42670_GYRO_LFP_73HZ = 0b011, + ICM42670_GYRO_LFP_53HZ = 0b100, + ICM42670_GYRO_LFP_34HZ = 0b101, + ICM42670_GYRO_LFP_25HZ = 0b110, + ICM42670_GYRO_LFP_16HZ = 0b111 +} icm42670_gyro_lfp_t; + +/* Accelerometer LPF (low pass filter) */ +typedef enum { + ICM42670_ACCEL_LFP_BYPASSED = 0b000, + ICM42670_ACCEL_LFP_180HZ = 0b001, + ICM42670_ACCEL_LFP_121HZ = 0b010, + ICM42670_ACCEL_LFP_73HZ = 0b011, + ICM42670_ACCEL_LFP_53HZ = 0b100, + ICM42670_ACCEL_LFP_34HZ = 0b101, + ICM42670_ACCEL_LFP_25HZ = 0b110, + ICM42670_ACCEL_LFP_16HZ = 0b111 +} icm42670_accel_lfp_t; + +/* Accelerometer averaging (for low power mode) */ +typedef enum { + ICM42670_ACCEL_AVG_2X = 0b000, + ICM42670_ACCEL_AVG_4X = 0b001, + ICM42670_ACCEL_AVG_8X = 0b010, + ICM42670_ACCEL_AVG_16X = 0b011, + ICM42670_ACCEL_AVG_32X = 0b100, + ICM42670_ACCEL_AVG_64X = 0b101 +} icm42670_accel_avg_t; + +/* Interrupt pin signal mode */ +typedef enum { + ICM42670_INT_MODE_PULSED = 0, + ICM42670_INT_MODE_LATCHED = 1 +} icm42670_int_mode_t; + +/* Interrupt pin signal type */ +typedef enum { + ICM42670_INT_DRIVE_OPEN_DRAIN = 0, + ICM42670_INT_DRIVE_PUSH_PULL = 1 +} icm42670_int_drive_t; + +/* Interrupt pin signal polarity */ +typedef enum { + ICM42670_INT_POLARITY_ACTIVE_LOW = 0, + ICM42670_INT_POLARITY_ACTIVE_HIGH = 1 +} icm42670_int_polarity_t; + +/* Interrupt pin configuration */ +typedef struct +{ + icm42670_int_mode_t mode; + icm42670_int_drive_t drive; + icm42670_int_polarity_t polarity; +} icm42670_int_config_t; + +/* Interrupt source */ +typedef struct +{ + bool self_test_done; + bool fsync; + bool pll_ready; + bool reset_done; + bool data_ready; + bool fifo_threshold; + bool fifo_full; + bool agc_ready; + bool i3c_error; + bool smd; + bool wom_z; + bool wom_y; + bool wom_x; +} icm42670_int_source_t; + +/* Wake on Motion interrupt assertion */ +typedef enum { + ICM42670_WOM_INT_DUR_FIRST = 0b00, + ICM42670_WOM_INT_DUR_SECOND = 0b01, + ICM42670_WOM_INT_DUR_THIRD = 0b10, + ICM42670_WOM_INT_DUR_FOURTH = 0b11 +} icm42670_wom_int_dur_t; + +/* Wake on Motion interrupt logical trigger */ +typedef enum { + ICM42670_WOM_INT_MODE_ALL_OR = 0, + ICM42670_WOM_INT_MODE_ALL_AND = 1 +} icm42670_wom_int_mode_t; + +/* Wake on Motion reference sample */ +typedef enum { + ICM42670_WOM_MODE_REF_INITIAL = 0, + ICM42670_WOM_MODE_REF_LAST = 1 +} icm42670_wom_mode_t; + +/* Wake on Motion configuration */ +typedef struct +{ + icm42670_wom_int_dur_t trigger; + icm42670_wom_int_mode_t logical_mode; + icm42670_wom_mode_t reference; + uint8_t wom_x_threshold; // 8-bit value between 0 and 1g (Resolution 1g/256=~3.9 mg) + uint8_t wom_y_threshold; // 8-bit value between 0 and 1g (Resolution 1g/256=~3.9 mg) + uint8_t wom_z_threshold; // 8-bit value between 0 and 1g (Resolution 1g/256=~3.9 mg) +} icm42670_wom_config_t; + +/* MREG 1-3 access */ +typedef enum { + ICM42670_MREG1_RW = 0x00, + ICM42670_MREG2_RW = 0x28, + ICM42670_MREG3_RW = 0x50 +} icm42670_mreg_number_t; + +/** + * Device descriptor + */ +typedef struct +{ + i2c_dev_t i2c_dev; + // TODO: add more vars for configuration +} icm42670_t; + +/** + * @brief Initialize device descriptor + * + * @param dev Device descriptor + * @param addr I2C device address, `ICM42670_I2C_ADDR_...` const + * @param port I2C port + * @param sda_gpio SDA GPIO pin + * @param scl_gpio SCL GPIO pin + * @return `ESP_OK` on success + */ +esp_err_t icm42670_init_desc(icm42670_t *dev, uint8_t addr, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio); + +/** + * @brief Free device descriptor + * + * @param dev Device descriptor + * @return `ESP_OK` on success + */ +esp_err_t icm42670_free_desc(icm42670_t *dev); + +/** + * @brief Initialize device + * + * @param dev Device descriptor + * @return `ESP_OK` on success + */ +esp_err_t icm42670_init(icm42670_t *dev); + +/** + * @brief Set device power mode + * + * @param dev Device descriptor + * @param enable_idle bool to enable idle mode + * @return `ESP_OK` on success + */ +esp_err_t icm42670_set_idle_pwr_mode(icm42670_t *dev, bool enable_idle); + +/** + * @brief Set gyro power mode + * + * @param dev Device descriptor + * @param pwr_mode struct of type icm42670_gyro_pwr_mode_t + * @return `ESP_OK` on success + */ +esp_err_t icm42670_set_gyro_pwr_mode(icm42670_t *dev, icm42670_gyro_pwr_mode_t pwr_mode); + +/** + * @brief Set accel power mode + * + * @param dev Device descriptor + * @param pwr_mode struct of type icm42670_accel_pwr_mode_t + * @return `ESP_OK` on success + */ +esp_err_t icm42670_set_accel_pwr_mode(icm42670_t *dev, icm42670_accel_pwr_mode_t pwr_mode); + +/** + * @brief Set clock source in LP mode + * + * @param dev Device descriptor + * @param clock_source struct of type icm42670_lp_clock_source_t + * @return `ESP_OK` on success + */ +esp_err_t icm42670_set_low_power_clock(icm42670_t *dev, icm42670_lp_clock_source_t clock_source); + +/** + * @brief Read temperature from device + * + * @param dev Device descriptor + * @param[out] temperature temperature, degree C + * @return `ESP_OK` on success + */ +esp_err_t icm42670_read_temperature(icm42670_t *dev, float *temperature); + +/** + * @brief Read 16-bit raw data registers (accelerometer and gyro values) + * + * @param dev Device descriptor + * @param data_register data register to read from + * @param[out] data accel or gyro data + * @return `ESP_OK` on success + */ +esp_err_t icm42670_read_raw_data(icm42670_t *dev, uint8_t data_register, int16_t *data); + +/** + * @brief Performs a soft-reset + * + * @param dev Device descriptor + * @return `ESP_OK` on success + */ +esp_err_t icm42670_reset(icm42670_t *dev); + +/** + * @brief Wipes the FIFO + * + * @param dev Device descriptor + * @return `ESP_OK` on success + */ +esp_err_t icm42670_flush_fifo(icm42670_t *dev); + +/** + * @brief Set the measurement FSR (Full Scale Range) of the gyro + * + * @param dev Device descriptor + * @param range struct of type icm42670_gyro_fsr_t + * @return `ESP_OK` on success + */ +esp_err_t icm42670_set_gyro_fsr(icm42670_t *dev, icm42670_gyro_fsr_t range); + +/** + * @brief Set the measurement ODR (Output Data Rate) of the gyro + * + * @param dev Device descriptor + * @param odr struct of type icm42670_gyro_odr_t + * @return `ESP_OK` on success + */ +esp_err_t icm42670_set_gyro_odr(icm42670_t *dev, icm42670_gyro_odr_t odr); + +/** + * @brief Set the measurement FSR (Full Scale Range) of the accelerometer + * + * @param dev Device descriptor + * @param range struct of type icm42670_accel_fsr_t + * @return `ESP_OK` on success + */ +esp_err_t icm42670_set_accel_fsr(icm42670_t *dev, icm42670_accel_fsr_t range); + +/** + * @brief Set the measurement ODR (Output Data Rate) of the accelerometer + * + * @param dev Device descriptor + * @param odr struct of type icm42670_accel_odr_t + * @return `ESP_OK` on success + */ +esp_err_t icm42670_set_accel_odr(icm42670_t *dev, icm42670_accel_odr_t odr); + +/** + * @brief Set the digital Low-Pass-Filter (LPF) of the temperature sensor + * + * @param dev Device descriptor + * @param lpf_bw struct of type icm42670_temp_lfp_t (bandwidth) + * @return `ESP_OK` on success + */ +esp_err_t icm42670_set_temp_lpf(icm42670_t *dev, icm42670_temp_lfp_t lpf_bw); + +/** + * @brief Set the digital Low-Pass-Filter (LPF) of the gyro + * + * @param dev Device descriptor + * @param lpf_bw struct of type icm42670_gyro_lfp_t (bandwidth) + * @return `ESP_OK` on success + */ +esp_err_t icm42670_set_gyro_lpf(icm42670_t *dev, icm42670_gyro_lfp_t lpf_bw); + +/** + * @brief Set the digital Low-Pass-Filter (LPF) of the accelerometer + * + * @param dev Device descriptor + * @param lpf_bw struct of type icm42670_accel_lfp_t (bandwidth) + * @return `ESP_OK` on success + */ +esp_err_t icm42670_set_accel_lpf(icm42670_t *dev, icm42670_accel_lfp_t lpf_bw); + +/** + * @brief Set the averaging filter of the accelerometer (ONLY IN LOW POWER MODE (LPM)) + * This field can not be changed, when accel sensor is in LPM! + * + * @param dev Device descriptor + * @param avg struct of type icm42670_accel_avg_t (averaging) + * @return `ESP_OK` on success + */ +esp_err_t icm42670_set_accel_avg(icm42670_t *dev, icm42670_accel_avg_t avg); + +/** + * @brief Configures the behaviour of an interrupt pin + * + * @param dev Device descriptor + * @param int_pin interrupt pin (1 or 2) + * @param config struct of type icm42670_int_config_t + * @return `ESP_OK` on success + */ +esp_err_t icm42670_config_int_pin(icm42670_t *dev, uint8_t int_pin, icm42670_int_config_t config); + +/** + * @brief Configures the sources for an interrupt + * + * @param dev Device descriptor + * @param int_pin interrupt pin (1 or 2) + * @param sources struct of type icm42670_int_source_t + * @return `ESP_OK` on success + */ +esp_err_t icm42670_set_int_sources(icm42670_t *dev, uint8_t int_pin, icm42670_int_source_t sources); + +/** + * @brief Configures the Wake on Motion (WoM) behaviour + * WoM can only be configured if WoM is not enabled + * + * @param dev Device descriptor + * @param config struct of type icm42670_wom_config_t + * @return `ESP_OK` on success + */ +esp_err_t icm42670_config_wom(icm42670_t *dev, icm42670_wom_config_t config); + +/** + * @brief Enable or Disable Wake on Motion (WoM) + * + * @param dev Device descriptor + * @param enable true to enable, false to disable + * @return `ESP_OK` on success + */ +esp_err_t icm42670_enable_wom(icm42670_t *dev, bool enable); + +/** + * @brief Get the status of the internal clock + * + * @param dev Device descriptor + * @param mclk_rdy true if internal clock is running + * @return `ESP_OK` on success + */ +esp_err_t icm42670_get_mclk_rdy(icm42670_t *dev, bool *mclk_rdy); + +/** + * @brief Get the output data rate (ODR) of the accel + * + * @param dev Device descriptor + * @param odr pointer to icm42670_accel_odr_t + * @return `ESP_OK` on success + */ +esp_err_t icm42670_get_accel_odr(icm42670_t *dev, icm42670_accel_odr_t *odr); + +/** + * @brief Get the status of the accel averaging + * + * @param dev Device descriptor + * @param avg pointer to icm42670_accel_avg_t + * @return `ESP_OK` on success + */ +esp_err_t icm42670_get_accel_avg(icm42670_t *dev, icm42670_accel_avg_t *avg); + +#ifdef __cplusplus +} +#endif + +/**@}*/ + +#endif // __ICM42670_H__
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/ina219/.eil.yml Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,23 @@ +--- +components: + - name: ina219 + description: Driver for INA219/INA220 bidirectional current/power monitor + group: current + groups: [] + code_owners: UncleRus + depends: + - name: i2cdev + - name: log + - name: esp_idf_lib_helpers + thread_safe: yes + targets: + - name: esp32 + - name: esp8266 + - name: esp32s2 + - name: esp32c3 + licenses: + - name: BSD-3 + copyrights: + - author: + name: UncleRus + year: 2019
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/ina219/CMakeLists.txt Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,5 @@ +idf_component_register( + SRCS ina219.c + INCLUDE_DIRS . + REQUIRES i2cdev log esp_idf_lib_helpers +)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/ina219/LICENSE Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,26 @@ +Copyright 2019 Ruslan V. Uss <unclerus@gmail.com> + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, +this list of conditions and the following disclaimer in the documentation +and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of itscontributors +may be used to endorse or promote products derived from this software without +specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/ina219/component.mk Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,2 @@ +COMPONENT_ADD_INCLUDEDIRS = . +COMPONENT_DEPENDS = i2cdev log esp_idf_lib_helpers
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/ina219/ina219.c Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,306 @@ +/* + * Copyright (c) 2019 Ruslan V. Uss <unclerus@gmail.com> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of itscontributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file ina219.c + * + * ESP-IDF driver for INA219/INA220 Zerø-Drift, Bidirectional + * Current/Power Monitor + * + * Copyright (c) 2019 Ruslan V. Uss <unclerus@gmail.com> + * + * BSD Licensed as described in the file LICENSE + */ +#include <esp_log.h> +#include <math.h> +#include <esp_idf_lib_helpers.h> +#include "ina219.h" + +#define I2C_FREQ_HZ 1000000 // Max 1 MHz for esp-idf, but supports up to 2.56 MHz + +static const char *TAG = "ina219"; + +#define REG_CONFIG 0 +#define REG_SHUNT_U 1 +#define REG_BUS_U 2 +#define REG_POWER 3 +#define REG_CURRENT 4 +#define REG_CALIBRATION 5 + +#define BIT_RST 15 +#define BIT_BRNG 13 +#define BIT_PG0 11 +#define BIT_BADC0 7 +#define BIT_SADC0 3 +#define BIT_MODE 0 + +#define MASK_PG (3 << BIT_PG0) +#define MASK_BADC (0xf << BIT_BADC0) +#define MASK_SADC (0xf << BIT_SADC0) +#define MASK_MODE (7 << BIT_MODE) +#define MASK_BRNG (1 << BIT_BRNG) + +#define DEF_CONFIG 0x399f + +#define CHECK(x) do { esp_err_t __; if ((__ = x) != ESP_OK) return __; } while (0) +#define CHECK_ARG(VAL) do { if (!(VAL)) return ESP_ERR_INVALID_ARG; } while (0) + +static const float u_shunt_max[] = { + [INA219_GAIN_1] = 0.04, + [INA219_GAIN_0_5] = 0.08, + [INA219_GAIN_0_25] = 0.16, + [INA219_GAIN_0_125] = 0.32, +}; + +static esp_err_t read_reg_16(ina219_t *dev, uint8_t reg, uint16_t *val) +{ + CHECK_ARG(val); + + I2C_DEV_TAKE_MUTEX(&dev->i2c_dev); + I2C_DEV_CHECK(&dev->i2c_dev, i2c_dev_read_reg(&dev->i2c_dev, reg, val, 2)); + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); + + *val = (*val >> 8) | (*val << 8); + + return ESP_OK; +} + +static esp_err_t write_reg_16(ina219_t *dev, uint8_t reg, uint16_t val) +{ + uint16_t v = (val >> 8) | (val << 8); + + I2C_DEV_TAKE_MUTEX(&dev->i2c_dev); + I2C_DEV_CHECK(&dev->i2c_dev, i2c_dev_write_reg(&dev->i2c_dev, reg, &v, 2)); + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); + + return ESP_OK; +} + +static esp_err_t read_conf_bits(ina219_t *dev, uint16_t mask, uint8_t bit, uint16_t *res) +{ + uint16_t raw; + CHECK(read_reg_16(dev, REG_CONFIG, &raw)); + + *res = (raw & mask) >> bit; + + return ESP_OK; +} + +/////////////////////////////////////////////////////////////////////////////// + +esp_err_t ina219_init_desc(ina219_t *dev, uint8_t addr, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio) +{ + CHECK_ARG(dev); + + if (addr < INA219_ADDR_GND_GND || addr > INA219_ADDR_SCL_SCL) + { + ESP_LOGE(TAG, "Invalid I2C address"); + return ESP_ERR_INVALID_ARG; + } + + dev->i2c_dev.port = port; + dev->i2c_dev.addr = addr; + dev->i2c_dev.cfg.sda_io_num = sda_gpio; + dev->i2c_dev.cfg.scl_io_num = scl_gpio; +#if HELPER_TARGET_IS_ESP32 + dev->i2c_dev.cfg.master.clk_speed = I2C_FREQ_HZ; +#endif + + return i2c_dev_create_mutex(&dev->i2c_dev); +} + +esp_err_t ina219_free_desc(ina219_t *dev) +{ + CHECK_ARG(dev); + + return i2c_dev_delete_mutex(&dev->i2c_dev); +} + +esp_err_t ina219_init(ina219_t *dev) +{ + CHECK_ARG(dev); + + CHECK(read_reg_16(dev, REG_CONFIG, &dev->config)); + + ESP_LOGD(TAG, "Initialize, config: 0x%04x", dev->config); + + return ESP_OK; +} + +esp_err_t ina219_reset(ina219_t *dev) +{ + CHECK_ARG(dev); + CHECK(write_reg_16(dev, REG_CONFIG, 1 << BIT_RST)); + + dev->config = DEF_CONFIG; + + ESP_LOGD(TAG, "Device reset"); + + return ESP_OK; +} + +esp_err_t ina219_configure(ina219_t *dev, ina219_bus_voltage_range_t u_range, + ina219_gain_t gain, ina219_resolution_t u_res, + ina219_resolution_t i_res, ina219_mode_t mode) +{ + CHECK_ARG(dev); + CHECK_ARG(u_range <= INA219_BUS_RANGE_32V); + CHECK_ARG(gain <= INA219_GAIN_0_125); + CHECK_ARG(u_res <= INA219_RES_12BIT_128S); + CHECK_ARG(i_res <= INA219_RES_12BIT_128S); + CHECK_ARG(mode <= INA219_MODE_CONT_SHUNT_BUS); + + dev->config = (u_range << BIT_BRNG) | + (gain << BIT_PG0) | + (u_res << BIT_BADC0) | + (i_res << BIT_SADC0) | + (mode << BIT_MODE); + + ESP_LOGD(TAG, "Config: 0x%04x", dev->config); + + return write_reg_16(dev, REG_CONFIG, dev->config); +} + +esp_err_t ina219_get_bus_voltage_range(ina219_t *dev, ina219_bus_voltage_range_t *range) +{ + CHECK_ARG(dev && range); + *range = 0; + return read_conf_bits(dev, MASK_BRNG, BIT_BRNG, (uint16_t *)range); +} + +esp_err_t ina219_get_gain(ina219_t *dev, ina219_gain_t *gain) +{ + CHECK_ARG(dev && gain); + *gain = 0; + return read_conf_bits(dev, MASK_PG, BIT_PG0, (uint16_t *)gain); +} + +esp_err_t ina219_get_bus_voltage_resolution(ina219_t *dev, ina219_resolution_t *res) +{ + CHECK_ARG(dev && res); + *res = 0; + return read_conf_bits(dev, MASK_BADC, BIT_BADC0, (uint16_t *)res); +} + +esp_err_t ina219_get_shunt_voltage_resolution(ina219_t *dev, ina219_resolution_t *res) +{ + CHECK_ARG(dev && res); + *res = 0; + return read_conf_bits(dev, MASK_SADC, BIT_SADC0, (uint16_t *)res); +} + +esp_err_t ina219_get_mode(ina219_t *dev, ina219_mode_t *mode) +{ + CHECK_ARG(dev && mode); + *mode = 0; + return read_conf_bits(dev, MASK_MODE, BIT_MODE, (uint16_t *)mode); +} + +esp_err_t ina219_calibrate(ina219_t *dev, float i_expected_max, float r_shunt) +{ + CHECK_ARG(dev); + + ina219_gain_t gain; + CHECK(ina219_get_gain(dev, &gain)); + + dev->i_lsb = (uint16_t)(u_shunt_max[gain] / r_shunt / 32767 * 100000000); + dev->i_lsb /= 100000000; + dev->i_lsb /= 0.0001; + dev->i_lsb = ceil(dev->i_lsb); + dev->i_lsb *= 0.0001; + + dev->p_lsb = dev->i_lsb * 20; + + uint16_t cal = (uint16_t)((0.04096) / (dev->i_lsb * r_shunt)); + + ESP_LOGD(TAG, "Calibration: %.04f A, %.04f Ohm, 0x%04x", i_expected_max, r_shunt, cal); + + return write_reg_16(dev, REG_CALIBRATION, cal); +} + +esp_err_t ina219_trigger(ina219_t *dev) +{ + CHECK_ARG(dev); + + uint16_t mode = (dev->config & MASK_MODE) >> BIT_MODE; + if (mode < INA219_MODE_TRIG_SHUNT || mode > INA219_MODE_TRIG_SHUNT_BUS) + { + ESP_LOGE(TAG, "Could not trigger conversion in this mode: %d", mode); + return ESP_ERR_INVALID_STATE; + } + + return write_reg_16(dev, REG_CONFIG, dev->config); +} + +esp_err_t ina219_get_bus_voltage(ina219_t *dev, float *voltage) +{ + CHECK_ARG(dev && voltage); + + int16_t raw; + CHECK(read_reg_16(dev, REG_BUS_U, (uint16_t *)&raw)); + + *voltage = (raw >> 3) * 0.004; + + return ESP_OK; +} + +esp_err_t ina219_get_shunt_voltage(ina219_t *dev, float *voltage) +{ + CHECK_ARG(dev && voltage); + + int16_t raw; + CHECK(read_reg_16(dev, REG_SHUNT_U, (uint16_t *)&raw)); + + *voltage = raw / 100000.0; + + return ESP_OK; +} + +esp_err_t ina219_get_current(ina219_t *dev, float *current) +{ + CHECK_ARG(dev && current); + + int16_t raw; + CHECK(read_reg_16(dev, REG_CURRENT, (uint16_t *)&raw)); + + *current = raw * dev->i_lsb; + + return ESP_OK; +} + +esp_err_t ina219_get_power(ina219_t *dev, float *power) +{ + CHECK_ARG(dev && power); + + int16_t raw; + CHECK(read_reg_16(dev, REG_POWER, (uint16_t *)&raw)); + + *power = raw * dev->p_lsb; + + return ESP_OK; +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/ina219/ina219.h Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,298 @@ +/* + * Copyright (c) 2019 Ruslan V. Uss <unclerus@gmail.com> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of itscontributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file ina219.h + * @defgroup ina219 ina219 + * @{ + * + * ESP-IDF driver for INA219/INA220 Zerø-Drift, Bidirectional + * Current/Power Monitor + * + * Copyright (c) 2019 Ruslan V. Uss <unclerus@gmail.com> + * + * BSD Licensed as described in the file LICENSE + */ +#ifndef __INA219_H__ +#define __INA219_H__ + +#include <i2cdev.h> +#include <esp_err.h> + +#ifdef __cplusplus +extern "C" { +#endif + +#define INA219_ADDR_GND_GND 0x40 //!< I2C address, A1 pin - GND, A0 pin - GND +#define INA219_ADDR_GND_VS 0x41 //!< I2C address, A1 pin - GND, A0 pin - VS+ +#define INA219_ADDR_GND_SDA 0x42 //!< I2C address, A1 pin - GND, A0 pin - SDA +#define INA219_ADDR_GND_SCL 0x43 //!< I2C address, A1 pin - GND, A0 pin - SCL +#define INA219_ADDR_VS_GND 0x44 //!< I2C address, A1 pin - VS+, A0 pin - GND +#define INA219_ADDR_VS_VS 0x45 //!< I2C address, A1 pin - VS+, A0 pin - VS+ +#define INA219_ADDR_VS_SDA 0x46 //!< I2C address, A1 pin - VS+, A0 pin - SDA +#define INA219_ADDR_VS_SCL 0x47 //!< I2C address, A1 pin - VS+, A0 pin - SCL +#define INA219_ADDR_SDA_GND 0x48 //!< I2C address, A1 pin - SDA, A0 pin - GND +#define INA219_ADDR_SDA_VS 0x49 //!< I2C address, A1 pin - SDA, A0 pin - VS+ +#define INA219_ADDR_SDA_SDA 0x4a //!< I2C address, A1 pin - SDA, A0 pin - SDA +#define INA219_ADDR_SDA_SCL 0x4b //!< I2C address, A1 pin - SDA, A0 pin - SCL +#define INA219_ADDR_SCL_GND 0x4c //!< I2C address, A1 pin - SCL, A0 pin - GND +#define INA219_ADDR_SCL_VS 0x4d //!< I2C address, A1 pin - SCL, A0 pin - VS+ +#define INA219_ADDR_SCL_SDA 0x4e //!< I2C address, A1 pin - SCL, A0 pin - SDA +#define INA219_ADDR_SCL_SCL 0x4f //!< I2C address, A1 pin - SCL, A0 pin - SCL + +/** + * Bus voltage range + */ +typedef enum { + INA219_BUS_RANGE_16V = 0, //!< 16V FSR + INA219_BUS_RANGE_32V //!< 32V FSR (default) +} ina219_bus_voltage_range_t; + +/** + * PGA gain for shunt voltage + */ +typedef enum { + INA219_GAIN_1 = 0, //!< Gain: 1, Range: +-40 mV + INA219_GAIN_0_5, //!< Gain: 1/2, Range: +-80 mV + INA219_GAIN_0_25, //!< Gain: 1/4, Range: +-160 mV + INA219_GAIN_0_125 //!< Gain: 1/8, Range: +-320 mV (default) +} ina219_gain_t; + +/** + * ADC resolution/averaging + */ +typedef enum { + INA219_RES_9BIT_1S = 0, //!< 9 bit, 1 sample, conversion time 84 us + INA219_RES_10BIT_1S = 1, //!< 10 bit, 1 sample, conversion time 148 us + INA219_RES_11BIT_1S = 2, //!< 11 bit, 1 sample, conversion time 276 us + INA219_RES_12BIT_1S = 3, //!< 12 bit, 1 sample, conversion time 532 us (default) + INA219_RES_12BIT_2S = 9, //!< 12 bit, 2 samples, conversion time 1.06 ms + INA219_RES_12BIT_4S = 10, //!< 12 bit, 4 samples, conversion time 2.13 ms + INA219_RES_12BIT_8S = 11, //!< 12 bit, 8 samples, conversion time 4.26 ms + INA219_RES_12BIT_16S = 12, //!< 12 bit, 16 samples, conversion time 8.51 ms + INA219_RES_12BIT_32S = 13, //!< 12 bit, 32 samples, conversion time 17.02 ms + INA219_RES_12BIT_64S = 14, //!< 12 bit, 64 samples, conversion time 34.05 ms + INA219_RES_12BIT_128S = 15, //!< 12 bit, 128 samples, conversion time 68.1 ms +} ina219_resolution_t; + +/** + * Operating mode + */ +typedef enum { + INA219_MODE_POWER_DOWN = 0, //!< Power-done + INA219_MODE_TRIG_SHUNT, //!< Shunt voltage, triggered + INA219_MODE_TRIG_BUS, //!< Bus voltage, triggered + INA219_MODE_TRIG_SHUNT_BUS, //!< Shunt and bus, triggered + INA219_MODE_DISABLED, //!< ADC off (disabled) + INA219_MODE_CONT_SHUNT, //!< Shunt voltage, continuous + INA219_MODE_CONT_BUS, //!< Bus voltage, continuous + INA219_MODE_CONT_SHUNT_BUS //!< Shunt and bus, continuous (default) +} ina219_mode_t; + +/** + * Device descriptor + */ +typedef struct +{ + i2c_dev_t i2c_dev; + + uint16_t config; + float i_lsb, p_lsb; +} ina219_t; + +/** + * @brief Initialize device descriptor + * + * @param dev Device descriptor + * @param addr Device I2C address + * @param port I2C port + * @param sda_gpio SDA GPIO + * @param scl_gpio SCL GPIO + * @return `ESP_OK` on success + */ +esp_err_t ina219_init_desc(ina219_t *dev, uint8_t addr, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio); + +/** + * @brief Free device descriptor + * + * @param dev Device descriptor + * @return `ESP_OK` on success + */ +esp_err_t ina219_free_desc(ina219_t *dev); + +/** + * @brief Init device + * + * Read current device configuration into `dev->config` + * + * @param dev Device descriptor + * @return `ESP_OK` on success + */ +esp_err_t ina219_init(ina219_t *dev); + +/** + * @brief Reset device + * + * Same as power-on reset. Resets all registers to default values. + * You still need to calibrate device to read current, otherwise + * only shunt voltage readings will be valid. + * + * @param dev Device descriptor + * @return `ESP_OK` on success + */ +esp_err_t ina219_reset(ina219_t *dev); + +/** + * @brief Set device configuration + * + * @param dev Device descriptor + * @param u_range Bus voltage range + * @param gain Shunt voltage gain + * @param u_res Bus voltage resolution and averaging + * @param i_res Shunt voltage resolution and averaging + * @param mode Device operational mode + * @return `ESP_OK` on success + */ +esp_err_t ina219_configure(ina219_t *dev, ina219_bus_voltage_range_t u_range, + ina219_gain_t gain, ina219_resolution_t u_res, + ina219_resolution_t i_res, ina219_mode_t mode); + +/** + * @brief Get bus voltage range + * + * @param dev Device descriptor + * @param[out] range Bus voltage range + * @return `ESP_OK` on success + */ +esp_err_t ina219_get_bus_voltage_range(ina219_t *dev, ina219_bus_voltage_range_t *range); + +/** + * @brief Get shunt voltage gain + * + * @param dev Device descriptor + * @param[out] gain Shunt voltage gain + * @return `ESP_OK` on success + */ +esp_err_t ina219_get_gain(ina219_t *dev, ina219_gain_t *gain); + +/** + * @brief Get bus voltage resolution and averaging + * + * @param dev Device descriptor + * @param[out] res Bus voltage resolution and averaging + * @return `ESP_OK` on success + */ +esp_err_t ina219_get_bus_voltage_resolution(ina219_t *dev, ina219_resolution_t *res); + +/** + * @brief Get shunt voltage resolution and averaging + * + * @param dev Device descriptor + * @param[out] res Shunt voltage resolution and averaging + * @return `ESP_OK` on success + */ +esp_err_t ina219_get_shunt_voltage_resolution(ina219_t *dev, ina219_resolution_t *res); + +/** + * @brief Get operating mode + * + * @param dev Device descriptor + * @param[out] mode Operating mode + * @return `ESP_OK` on success + */ +esp_err_t ina219_get_mode(ina219_t *dev, ina219_mode_t *mode); + +/** + * @brief Perform calibration + * + * Current readings will be valid only after calibration + * + * @param dev Device descriptor + * @param i_expected_max Maximum expected current, A + * @param r_shunt Shunt resistance, Ohm + * @return `ESP_OK` on success + */ +esp_err_t ina219_calibrate(ina219_t *dev, float i_expected_max, float r_shunt); + +/** + * @brief Trigger single conversion + * + * Function will return an error if current operating + * mode is not `INA219_MODE_TRIG_SHUNT`/`INA219_MODE_TRIG_BUS`/`INA219_MODE_TRIG_SHUNT_BUS` + * + * @param dev Device descriptor + * @return `ESP_OK` on success + */ +esp_err_t ina219_trigger(ina219_t *dev); + +/** + * @brief Read bus voltage + * + * @param dev Device descriptor + * @param[out] voltage Bus voltage, V + * @return `ESP_OK` on success + */ +esp_err_t ina219_get_bus_voltage(ina219_t *dev, float *voltage); + +/** + * @brief Read shunt voltage + * + * @param dev Device descriptor + * @param[out] voltage Shunt voltage, V + * @return `ESP_OK` on success + */ +esp_err_t ina219_get_shunt_voltage(ina219_t *dev, float *voltage); + +/** + * @brief Read current + * + * This function works properly only after calibration. + * + * @param dev Device descriptor + * @param[out] current Current, A + * @return `ESP_OK` on success + */ +esp_err_t ina219_get_current(ina219_t *dev, float *current); + +/** + * @brief Read power + * + * This function works properly only after calibration. + * + * @param dev Device descriptor + * @param[out] power Power, W + * @return `ESP_OK` on success + */ +esp_err_t ina219_get_power(ina219_t *dev, float *power); + +#ifdef __cplusplus +} +#endif + +/**@}*/ + +#endif /* __INA219_H__ */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/ina260/.eil.yml Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,23 @@ +--- +components: + - name: ina260 + description: Driver for INA260 precision digital current and power monitor + group: current + groups: [] + code_owners: UncleRus + depends: + - name: i2cdev + - name: log + - name: esp_idf_lib_helpers + thread_safe: yes + targets: + - name: esp32 + - name: esp8266 + - name: esp32s2 + - name: esp32c3 + licenses: + - name: BSD-3 + copyrights: + - author: + name: UncleRus + year: 2020
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/ina260/CMakeLists.txt Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,5 @@ +idf_component_register( + SRCS ina260.c + INCLUDE_DIRS . + REQUIRES i2cdev log esp_idf_lib_helpers +)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/ina260/LICENSE Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,26 @@ +Copyright 2020 Ruslan V. Uss <unclerus@gmail.com> + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, +this list of conditions and the following disclaimer in the documentation +and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of itscontributors +may be used to endorse or promote products derived from this software without +specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/ina260/component.mk Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,2 @@ +COMPONENT_ADD_INCLUDEDIRS = . +COMPONENT_DEPENDS = i2cdev log esp_idf_lib_helpers
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/ina260/ina260.c Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,312 @@ +/* + * Copyright (c) 2020 Ruslan V. Uss <unclerus@gmail.com> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of itscontributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file ina260.c + * + * ESP-IDF driver for INA260 precision digital current and power monitor + * + * Copyright (c) 2020 Ruslan V. Uss <unclerus@gmail.com> + * + * BSD Licensed as described in the file LICENSE + */ +#include <esp_log.h> +#include <math.h> +#include <esp_idf_lib_helpers.h> +#include <freertos/FreeRTOS.h> +#include <freertos/task.h> +#include "ina260.h" + +#define I2C_FREQ_HZ 400000 // no more than 400 kHz, otherwise enabling HS mode on the chip is required + +static const char *TAG = "ina260"; + +#define REG_CONFIG 0 +#define REG_CURRENT 1 +#define REG_BUS_VOLTAGE 2 +#define REG_POWER 3 +#define REG_MASK_EN 6 +#define REG_ALERT_LIMIT 7 +#define REG_MFR_ID 0xfe +#define REG_DIE_ID 0xff + +#define BIT_MODE 0 +#define BIT_ISHCT 3 +#define BIT_VBUSCT 6 +#define BIT_AVG 9 +#define BIT_RST 15 + +#define BIT_LEN 0 +#define BIT_APOL 1 +#define BIT_OVF 2 +#define BIT_CVRF 3 +#define BIT_AFF 4 +#define BIT_CNVR 10 +#define BIT_POL 11 +#define BIT_BUL 12 +#define BIT_BOL 13 +#define BIT_UCL 14 +#define BIT_OCL 15 + +#define MASK_MODE (7 << BIT_MODE) +#define MASK_ISHCT (7 << BIT_ISHCT) +#define MASK_VBUSCT (7 << BIT_VBUSCT) +#define MASK_AVG (7 << BIT_AVG) + +#define DEF_CONF 0x6000 + +#define BV(x) (1 << (x)) + +#define CHECK(x) do { esp_err_t __; if ((__ = x) != ESP_OK) return __; } while (0) +#define CHECK_ARG(VAL) do { if (!(VAL)) return ESP_ERR_INVALID_ARG; } while (0) + +static esp_err_t read_reg_16(ina260_t *dev, uint8_t reg, uint16_t *val) +{ + CHECK_ARG(val); + + I2C_DEV_TAKE_MUTEX(&dev->i2c_dev); + I2C_DEV_CHECK(&dev->i2c_dev, i2c_dev_read_reg(&dev->i2c_dev, reg, val, 2)); + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); + + *val = (*val >> 8) | (*val << 8); + + return ESP_OK; +} + +static esp_err_t write_reg_16(ina260_t *dev, uint8_t reg, uint16_t val) +{ + uint16_t v = (val >> 8) | (val << 8); + + I2C_DEV_TAKE_MUTEX(&dev->i2c_dev); + I2C_DEV_CHECK(&dev->i2c_dev, i2c_dev_write_reg(&dev->i2c_dev, reg, &v, 2)); + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); + + return ESP_OK; +} + +/////////////////////////////////////////////////////////////////////////////// + +esp_err_t ina260_init_desc(ina260_t *dev, uint8_t addr, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio) +{ + CHECK_ARG(dev); + + if (addr < INA260_ADDR(INA260_ADDR_PIN_GND, INA260_ADDR_PIN_GND) + || addr > INA260_ADDR(INA260_ADDR_PIN_SCL, INA260_ADDR_PIN_SCL)) + { + ESP_LOGE(TAG, "Invalid I2C address"); + return ESP_ERR_INVALID_ARG; + } + + dev->i2c_dev.port = port; + dev->i2c_dev.addr = addr; + dev->i2c_dev.cfg.sda_io_num = sda_gpio; + dev->i2c_dev.cfg.scl_io_num = scl_gpio; +#if HELPER_TARGET_IS_ESP32 + dev->i2c_dev.cfg.master.clk_speed = I2C_FREQ_HZ; +#endif + + return i2c_dev_create_mutex(&dev->i2c_dev); +} + +esp_err_t ina260_free_desc(ina260_t *dev) +{ + CHECK_ARG(dev); + + return i2c_dev_delete_mutex(&dev->i2c_dev); +} + +esp_err_t ina260_init(ina260_t *dev) +{ + CHECK_ARG(dev); + + CHECK(read_reg_16(dev, REG_CONFIG, &dev->config)); + CHECK(read_reg_16(dev, REG_MFR_ID, &dev->mfr_id)); + CHECK(read_reg_16(dev, REG_DIE_ID, &dev->die_id)); + + ESP_LOGD(TAG, "[0x%02x@%u] Found device MFR_ID: %04x, DIE_ID: %04x", + dev->i2c_dev.addr, dev->i2c_dev.port, dev->mfr_id, dev->die_id); + + return ESP_OK; +} + +esp_err_t ina260_reset(ina260_t *dev) +{ + CHECK_ARG(dev); + + ESP_LOGD(TAG, "[0x%02x@%u] Resetting...", dev->i2c_dev.addr, dev->i2c_dev.port); + CHECK(write_reg_16(dev, REG_CONFIG, 1 << BIT_RST)); + + vTaskDelay(pdMS_TO_TICKS(100)); + + return ina260_init(dev); +} + +esp_err_t ina260_set_config(ina260_t *dev, ina260_mode_t mode, ina260_averaging_mode_t avg_mode, + ina260_conversion_time_t vbus_ct, ina260_conversion_time_t ish_ct) +{ + CHECK_ARG(dev + && mode <= INA260_MODE_CONT_SHUNT_BUS + && avg_mode <= INA260_AVG_1024 + && vbus_ct <= INA260_CT_8244 + && ish_ct <= INA260_CT_8244); + + dev->config = DEF_CONF + | (avg_mode << BIT_AVG) + | (vbus_ct << BIT_VBUSCT) + | (ish_ct << BIT_ISHCT) + | (mode << BIT_MODE); + + return write_reg_16(dev, REG_CONFIG, dev->config); +} + +esp_err_t ina260_get_config(ina260_t *dev, ina260_mode_t *mode, ina260_averaging_mode_t *avg_mode, + ina260_conversion_time_t *vbus_ct, ina260_conversion_time_t *ish_ct) +{ + CHECK_ARG(dev); + + CHECK(read_reg_16(dev, REG_CONFIG, &dev->config)); + if (mode) + *mode = (dev->config & MASK_MODE) >> BIT_MODE; + if (avg_mode) + *avg_mode = (dev->config & MASK_AVG) >> BIT_AVG; + if (vbus_ct) + *vbus_ct = (dev->config & MASK_VBUSCT) >> BIT_VBUSCT; + if (ish_ct) + *ish_ct = (dev->config & MASK_ISHCT) >> BIT_ISHCT; + return ESP_OK; +} + +esp_err_t ina260_set_alert(ina260_t *dev, ina260_alert_mode_t mode, float limit, + bool cvrf, bool active_high, bool latch) +{ + CHECK_ARG(dev); + + uint16_t reg = 0; + bool wl = true; + int16_t l = 0; + switch (mode) + { + case INA260_ALERT_OCL: + reg |= BV(BIT_OCL); + l = (int16_t)(limit / 0.00125); + break; + case INA260_ALERT_UCL: + reg |= BV(BIT_UCL); + l = (int16_t)(limit / 0.00125); + break; + case INA260_ALERT_BOL: + reg |= BV(BIT_BOL); + l = (int16_t)(limit / 0.00125); + break; + case INA260_ALERT_BUL: + reg |= BV(BIT_BUL); + l = (int16_t)(limit / 0.00125); + break; + case INA260_ALERT_POL: + reg |= BV(BIT_POL); + l = (int16_t)(limit / 0.01); + break; + default: + wl = false; + } + if (cvrf) reg |= BV(BIT_CVRF); + if (active_high) reg |= BV(BIT_APOL); + if (latch) reg |= BV(BIT_LEN); + if (wl) + CHECK(write_reg_16(dev, REG_ALERT_LIMIT, l)); + + CHECK(write_reg_16(dev, REG_MASK_EN, reg)); + + return ESP_OK; +} + +esp_err_t ina260_trigger(ina260_t *dev) +{ + CHECK_ARG(dev); + + uint16_t mode = (dev->config & MASK_MODE) >> BIT_MODE; + if (mode < INA260_MODE_TRIG_SHUNT || mode > INA260_MODE_TRIG_SHUNT_BUS) + { + ESP_LOGE(TAG, "Could not trigger conversion in this mode: %d", mode); + return ESP_ERR_INVALID_STATE; + } + + return write_reg_16(dev, REG_CONFIG, dev->config); +} + +esp_err_t ina260_get_status(ina260_t *dev, bool *ready, bool *alert, bool *overflow) +{ + CHECK_ARG(dev && (ready || alert || overflow)); + + uint16_t val; + CHECK(read_reg_16(dev, REG_MASK_EN, &val)); + + if (ready) + *ready = val & BIT_CVRF ? 1 : 0; + if (alert) + *alert = val & BIT_AFF ? 1 : 0; + if (overflow) + *overflow = val & BIT_OVF ? 1 : 0; + + return ESP_OK; +} + +esp_err_t ina260_get_current(ina260_t *dev, float *current) +{ + CHECK_ARG(dev && current); + + int16_t raw; + CHECK(read_reg_16(dev, REG_CURRENT, (uint16_t *)&raw)); + *current = raw * 0.00125; + + return ESP_OK; +} + +esp_err_t ina260_get_bus_voltage(ina260_t *dev, float *voltage) +{ + CHECK_ARG(dev && voltage); + + uint16_t raw; + CHECK(read_reg_16(dev, REG_BUS_VOLTAGE, &raw)); + + *voltage = raw * 0.00125; + + return ESP_OK; +} + +esp_err_t ina260_get_power(ina260_t *dev, float *power) +{ + CHECK_ARG(dev && power); + + uint16_t raw; + CHECK(read_reg_16(dev, REG_POWER, &raw)); + + *power = raw * 0.01; + + return ESP_OK; +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/ina260/ina260.h Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,275 @@ +/* + * Copyright (c) 2020 Ruslan V. Uss <unclerus@gmail.com> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of itscontributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file ina260.h + * @defgroup ina260 ina260 + * @{ + * + * ESP-IDF driver for INA260 precision digital current and power monitor + * + * Copyright (c) 2020 Ruslan V. Uss <unclerus@gmail.com> + * + * BSD Licensed as described in the file LICENSE + * + * @todo Add support for SPI interface + */ +#ifndef __INA260_H__ +#define __INA260_H__ + +#include <i2cdev.h> +#include <esp_err.h> + +#ifdef __cplusplus +extern "C" { +#endif + +#define INA260_ADDR_PIN_GND 0x00 +#define INA260_ADDR_PIN_VS 0x01 +#define INA260_ADDR_PIN_SDA 0x02 +#define INA260_ADDR_PIN_SCL 0x03 + +/** + * Macro to define I2C address + * + * Examples: + * INA260_ADDR(INA260_ADDR_PIN_GND, INA260_ADDR_PIN_GND) = 0x40 (A0 = A1 = GND) + * INA260_ADDR(INA260_ADDR_PIN_VS, INA260_ADDR_PIN_SDA) = 0x49 (A0 = VS, A1 = SDA) + */ +#define INA260_ADDR(A0, A1) (0x40 | ((A1) << 2) | (A0)) + +/** + * Averaging mode. + * Determines the number of samples that are collected and averaged. + */ +typedef enum +{ + INA260_AVG_1 = 0, //!< 1 sample, default + INA260_AVG_4, //!< 4 samples + INA260_AVG_16, //!< 16 samples + INA260_AVG_64, //!< 64 samples + INA260_AVG_128, //!< 128 samples + INA260_AVG_256, //!< 256 samples + INA260_AVG_512, //!< 512 samples + INA260_AVG_1024 //!< 1024 samples +} ina260_averaging_mode_t; + +/** + * Conversion time + */ +typedef enum +{ + INA260_CT_140 = 0, //!< 140 us + INA260_CT_204, //!< 204 us + INA260_CT_332, //!< 332 us + INA260_CT_588, //!< 588 us + INA260_CT_1100, //!< 1.1 ms, default + INA260_CT_2116, //!< 2.116 ms + INA260_CT_4156, //!< 4.156 ms + INA260_CT_8244, //!< 8.244 ms +} ina260_conversion_time_t; + +/** + * Operating mode + */ +typedef enum +{ + INA260_MODE_POWER_DOWN = 0, //!< Power-done + INA260_MODE_TRIG_SHUNT = 1, //!< Shunt current, triggered + INA260_MODE_TRIG_BUS = 2, //!< Bus voltage, triggered + INA260_MODE_TRIG_SHUNT_BUS = 3, //!< Shunt current and bus voltage, triggered + INA260_MODE_POWER_DOWN2 = 4, //!< Power-done + INA260_MODE_CONT_SHUNT = 5, //!< Shunt current, continuous + INA260_MODE_CONT_BUS = 6, //!< Bus voltage, continuous + INA260_MODE_CONT_SHUNT_BUS = 7 //!< Shunt current and bus voltage, continuous (default) +} ina260_mode_t; + +/** + * Alert function mode + */ +typedef enum +{ + INA260_ALERT_DISABLED, //!< No alert function + INA260_ALERT_OCL, //!< Over current limit + INA260_ALERT_UCL, //!< Under current limit + INA260_ALERT_BOL, //!< Bus voltage over-voltage + INA260_ALERT_BUL, //!< Bus voltage under-voltage + INA260_ALERT_POL, //!< Power over-limit +} ina260_alert_mode_t; + +/** + * Device descriptor + */ +typedef struct +{ + i2c_dev_t i2c_dev; //!< I2C device descriptor + uint16_t config; //!< Current config + uint16_t mfr_id; //!< Manufacturer ID + uint16_t die_id; //!< Die ID +} ina260_t; + +/** + * @brief Initialize device descriptor. + * + * @param dev Device descriptor + * @param addr Device I2C address + * @param port I2C port + * @param sda_gpio SDA GPIO + * @param scl_gpio SCL GPIO + * @return `ESP_OK` on success + */ +esp_err_t ina260_init_desc(ina260_t *dev, uint8_t addr, i2c_port_t port, + gpio_num_t sda_gpio, gpio_num_t scl_gpio); + +/** + * @brief Free device descriptor. + * + * @param dev Device descriptor + * @return `ESP_OK` on success + */ +esp_err_t ina260_free_desc(ina260_t *dev); + +/** + * @brief Initialize device. + * + * Reads sensor configuration. + * + * @param dev Device descriptor + * @return `ESP_OK` on success + */ +esp_err_t ina260_init(ina260_t *dev); + +/** + * @brief Reset sensor. + * + * @param dev Device descriptor + * @return `ESP_OK` on success + */ +esp_err_t ina260_reset(ina260_t *dev); + +/** + * @brief Configure sensor. + * + * @param dev Device descriptor + * @param mode Operating mode + * @param avg_mode Averaging mode + * @param vbus_ct Bus voltage conversion time + * @param ish_ct Shunt current conversion time + * @return `ESP_OK` on success + */ +esp_err_t ina260_set_config(ina260_t *dev, ina260_mode_t mode, ina260_averaging_mode_t avg_mode, + ina260_conversion_time_t vbus_ct, ina260_conversion_time_t ish_ct); + +/** + * @brief Read sensor configuration. + * + * @param dev Device descriptor + * @param[out] mode Operating mode + * @param[out] avg_mode Averaging mode + * @param[out] vbus_ct Bus voltage conversion time + * @param[out] ish_ct Shunt current conversion time + * @return `ESP_OK` on success + */ +esp_err_t ina260_get_config(ina260_t *dev, ina260_mode_t *mode, ina260_averaging_mode_t *avg_mode, + ina260_conversion_time_t *vbus_ct, ina260_conversion_time_t *ish_ct); + +/** + * @brief Setup ALERT pin. + * + * @param dev Device descriptor + * @param mode Alert function mode + * @param limit Alert limit value + * @param cvrf If true also assert ALERT pin when device is ready to next conversion + * @param active_high Set active ALERT pin level is high + * @param latch Enable latch mode on ALERT pin (see ::ina260_get_status()) + * @return `ESP_OK` on success + */ +esp_err_t ina260_set_alert(ina260_t *dev, ina260_alert_mode_t mode, float limit, + bool cvrf, bool active_high, bool latch); + +/** + * @brief Trigger single conversion. + * + * Function will return an error if current operating + * mode is not `INA260_MODE_TRIG_SHUNT`/`INA260_MODE_TRIG_BUS`/`INA260_MODE_TRIG_SHUNT_BUS` + * + * @param dev Device descriptor + * @return `ESP_OK` on success + */ +esp_err_t ina260_trigger(ina260_t *dev); + +/** + * @brief Get device status. + * + * This function also clears ALERT state if latch mode is enabled for ALERT pin. + * + * @param dev Device descriptor + * @param[out] ready If true, device is ready for the next conversion + * @param[out] alert If true, there was alert + * @param[out] overflow If true, power data have exceeded max 419.43 W + * @return `ESP_OK` on success + */ +esp_err_t ina260_get_status(ina260_t *dev, bool *ready, bool *alert, bool *overflow); + +/** + * @brief Read current. + * + * This function works properly only after calibration. + * + * @param dev Device descriptor + * @param[out] current Current, A + * @return `ESP_OK` on success + */ +esp_err_t ina260_get_current(ina260_t *dev, float *current); + +/** + * @brief Read bus voltage. + * + * @param dev Device descriptor + * @param[out] voltage Bus voltage, V + * @return `ESP_OK` on success + */ +esp_err_t ina260_get_bus_voltage(ina260_t *dev, float *voltage); + +/** + * @brief Read power. + * + * This function works properly only after calibration. + * + * @param dev Device descriptor + * @param[out] power Power, W + * @return `ESP_OK` on success + */ +esp_err_t ina260_get_power(ina260_t *dev, float *power); + +#ifdef __cplusplus +} +#endif + +/**@}*/ + +#endif /* __INA260_H__ */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/ina3221/.eil.yml Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,26 @@ +--- +components: + - name: ina3221 + description: Driver for INA3221 shunt and bus voltage monitor + group: current + groups: [] + code_owners: UncleRus + depends: + - name: i2cdev + - name: log + - name: esp_idf_lib_helpers + thread_safe: yes + targets: + - name: esp32 + - name: esp8266 + - name: esp32s2 + - name: esp32c3 + licenses: + - name: ISC + copyrights: + - author: + name: UncleRus + year: 2019 + - author: + name: Zaltora + year: 2016
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/ina3221/CMakeLists.txt Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,5 @@ +idf_component_register( + SRCS ina3221.c + INCLUDE_DIRS . + REQUIRES i2cdev log esp_idf_lib_helpers +)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/ina3221/LICENSE Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2016 Zaltora (https://github.com/Zaltora) +Copyright (c) 2019 Ruslan V. Uss <unclerus@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/ina3221/component.mk Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,2 @@ +COMPONENT_ADD_INCLUDEDIRS = . +COMPONENT_DEPENDS = i2cdev log esp_idf_lib_helpers
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/ina3221/ina3221.c Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,321 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2016 Zaltora <https://github.com/Zaltora> + * Copyright (c) 2019 Ruslan V. Uss <unclerus@gmail.com> + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +/** + * @file ina3221.c + * + * ESP-IDF driver for Shunt and Bus Voltage Monitor INA3221 + * + * Ported from esp-open-rtos + * + * Copyright (c) 2016 Zaltora <https://github.com/Zaltora>\n + * Copyright (c) 2019 Ruslan V. Uss <unclerus@gmail.com> + * + * MIT Licensed as described in the file LICENSE + */ +#include <esp_log.h> +#include <esp_idf_lib_helpers.h> +#include "ina3221.h" + +static const char *TAG = "ina3221"; + +#define I2C_FREQ_HZ 1000000 // Max 1MHz for esp-idf, but device supports up to 2.44Mhz + +#define INA3221_REG_CONFIG (0x00) +#define INA3221_REG_SHUNTVOLTAGE_1 (0x01) +#define INA3221_REG_BUSVOLTAGE_1 (0x02) +#define INA3221_REG_CRITICAL_ALERT_1 (0x07) +#define INA3221_REG_WARNING_ALERT_1 (0x08) +#define INA3221_REG_SHUNT_VOLTAGE_SUM (0x0D) +#define INA3221_REG_SHUNT_VOLTAGE_SUM_LIMIT (0x0E) +#define INA3221_REG_MASK (0x0F) +#define INA3221_REG_VALID_POWER_UPPER_LIMIT (0x10) +#define INA3221_REG_VALID_POWER_LOWER_LIMIT (0x11) + +#define CHECK(x) do { esp_err_t __; if ((__ = x) != ESP_OK) return __; } while (0) +#define CHECK_ARG(VAL) do { if (!(VAL)) return ESP_ERR_INVALID_ARG; } while (0) + +static esp_err_t read_reg_16(ina3221_t *dev, uint8_t reg, uint16_t *val) +{ + CHECK_ARG(val); + + I2C_DEV_TAKE_MUTEX(&dev->i2c_dev); + I2C_DEV_CHECK(&dev->i2c_dev, i2c_dev_read_reg(&dev->i2c_dev, reg, val, 2)); + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); + + *val = (*val >> 8) | (*val << 8); // Swap + + return ESP_OK; +} + +static esp_err_t write_reg_16(ina3221_t *dev, uint8_t reg, uint16_t val) +{ + uint16_t v = (val >> 8) | (val << 8); // Swap + + I2C_DEV_TAKE_MUTEX(&dev->i2c_dev); + I2C_DEV_CHECK(&dev->i2c_dev, i2c_dev_write_reg(&dev->i2c_dev, reg, &v, 2)); + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); + + return ESP_OK; +} + +static esp_err_t write_config(ina3221_t *dev) +{ + return write_reg_16(dev, INA3221_REG_CONFIG, dev->config.config_register); +} + +static esp_err_t write_mask(ina3221_t *dev) +{ + return write_reg_16(dev, INA3221_REG_MASK, dev->mask.mask_register & INA3221_MASK_CONFIG); +} + +/////////////////////////////////////////////////////////////////////////////////// + +esp_err_t ina3221_init_desc(ina3221_t *dev, uint8_t addr, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio) +{ + CHECK_ARG(dev); + + if (addr < INA3221_I2C_ADDR_GND || addr > INA3221_I2C_ADDR_SCL) + { + ESP_LOGE(TAG, "Invalid I2C address"); + return ESP_ERR_INVALID_ARG; + } + + dev->i2c_dev.port = port; + dev->i2c_dev.addr = addr; + dev->i2c_dev.cfg.sda_io_num = sda_gpio; + dev->i2c_dev.cfg.scl_io_num = scl_gpio; +#if HELPER_TARGET_IS_ESP32 + dev->i2c_dev.cfg.master.clk_speed = I2C_FREQ_HZ; +#endif + + return i2c_dev_create_mutex(&dev->i2c_dev); +} + +esp_err_t ina3221_free_desc(ina3221_t *dev) +{ + CHECK_ARG(dev); + + return i2c_dev_delete_mutex(&dev->i2c_dev); +} + + +esp_err_t ina3221_sync(ina3221_t *dev) +{ + CHECK_ARG(dev); + + uint16_t data; + + // Sync config register + CHECK(read_reg_16(dev, INA3221_REG_CONFIG, &data)); + if (data != dev->config.config_register) + CHECK(write_config(dev)); + + // Sync mask register + CHECK(read_reg_16(dev, INA3221_REG_MASK, &data)); + if ((data & INA3221_MASK_CONFIG) != (dev->mask.mask_register & INA3221_MASK_CONFIG)) + CHECK(write_mask(dev)); + + return ESP_OK; +} + +esp_err_t ina3221_trigger(ina3221_t *dev) +{ + return write_config(dev); +} + +esp_err_t ina3221_get_status(ina3221_t *dev) +{ + return read_reg_16(dev, INA3221_REG_MASK, &dev->mask.mask_register); +} + +esp_err_t ina3221_set_options(ina3221_t *dev, bool mode, bool bus, bool shunt) +{ + CHECK_ARG(dev); + + dev->config.mode = mode; + dev->config.ebus = bus; + dev->config.esht = shunt; + return write_config(dev); +} + +esp_err_t ina3221_enable_channel(ina3221_t *dev, bool ch1, bool ch2, bool ch3) +{ + CHECK_ARG(dev); + + dev->config.ch1 = ch1; + dev->config.ch2 = ch2; + dev->config.ch3 = ch3; + return write_config(dev); +} + +esp_err_t ina3221_enable_channel_sum(ina3221_t *dev, bool ch1, bool ch2, bool ch3) +{ + CHECK_ARG(dev); + + dev->mask.scc1 = ch1; + dev->mask.scc2 = ch2; + dev->mask.scc3 = ch3; + return write_mask(dev); +} + +esp_err_t ina3221_enable_latch_pin(ina3221_t *dev, bool warning, bool critical) +{ + CHECK_ARG(dev); + + dev->mask.wen = warning; + dev->mask.cen = critical; + return write_mask(dev); +} + +esp_err_t ina3221_set_average(ina3221_t *dev, ina3221_avg_t avg) +{ + CHECK_ARG(dev); + + dev->config.avg = avg; + return write_config(dev); +} + +esp_err_t ina3221_set_bus_conversion_time(ina3221_t *dev, ina3221_ct_t ct) +{ + CHECK_ARG(dev); + + dev->config.vbus = ct; + return write_config(dev); +} + +esp_err_t ina3221_set_shunt_conversion_time(ina3221_t *dev, ina3221_ct_t ct) +{ + CHECK_ARG(dev); + + dev->config.vsht = ct; + return write_config(dev); +} + +esp_err_t ina3221_reset(ina3221_t *dev) +{ + CHECK_ARG(dev); + + dev->config.config_register = INA3221_DEFAULT_CONFIG; + dev->mask.mask_register = INA3221_DEFAULT_CONFIG; + dev->config.rst = 1; + return write_config(dev); +} + +esp_err_t ina3221_get_bus_voltage(ina3221_t *dev, ina3221_channel_t channel, float *voltage) +{ + CHECK_ARG(dev && voltage); + + int16_t raw; + + CHECK(read_reg_16(dev, INA3221_REG_BUSVOLTAGE_1 + channel * 2, (uint16_t *)&raw)); + *voltage = raw * 0.001; + + return ESP_OK; +} + +esp_err_t ina3221_get_shunt_value(ina3221_t *dev, ina3221_channel_t channel, float *voltage, float *current) +{ + CHECK_ARG(dev); + CHECK_ARG(voltage || current); + if (current && !dev->shunt[channel]) + { + ESP_LOGE(TAG, "No shunt configured for channel %u in device [0x%02x at %d]", channel, dev->i2c_dev.addr, dev->i2c_dev.port); + return ESP_ERR_INVALID_ARG; + } + + int16_t raw; + CHECK(read_reg_16(dev, INA3221_REG_SHUNTVOLTAGE_1 + channel * 2, (uint16_t *)&raw)); + float mvolts = raw * 0.005; // mV, 40uV step + + if (voltage) + *voltage = mvolts; + + if (current) + *current = mvolts * 1000.0 / dev->shunt[channel]; // mA + + return ESP_OK; +} + +esp_err_t ina3221_get_sum_shunt_value(ina3221_t *dev, float *voltage) +{ + CHECK_ARG(dev && voltage); + + int16_t raw; + + CHECK(read_reg_16(dev, INA3221_REG_SHUNT_VOLTAGE_SUM, (uint16_t *)&raw)); + *voltage = raw * 0.02; // mV + + return ESP_OK; +} + +esp_err_t ina3221_set_critical_alert(ina3221_t *dev, ina3221_channel_t channel, float current) +{ + CHECK_ARG(dev); + + int16_t raw = current * dev->shunt[channel] * 0.2; + return write_reg_16(dev, INA3221_REG_CRITICAL_ALERT_1 + channel * 2, *(uint16_t *)&raw); +} + +esp_err_t ina3221_set_warning_alert(ina3221_t *dev, ina3221_channel_t channel, float current) +{ + CHECK_ARG(dev); + + int16_t raw = current * dev->shunt[channel] * 0.2; + return write_reg_16(dev, INA3221_REG_WARNING_ALERT_1 + channel * 2, *(uint16_t *)&raw); +} + +esp_err_t ina3221_set_sum_warning_alert(ina3221_t *dev, float voltage) +{ + CHECK_ARG(dev); + + int16_t raw = voltage * 50.0; + return write_reg_16(dev, INA3221_REG_SHUNT_VOLTAGE_SUM_LIMIT, *(uint16_t *)&raw); +} + +esp_err_t ina3221_set_power_valid_upper_limit(ina3221_t *dev, float voltage) +{ + CHECK_ARG(dev); + if (!dev->config.ebus) + { + ESP_LOGE(TAG, "Bus is not enabled in device [0x%02x at %d]", dev->i2c_dev.addr, dev->i2c_dev.port); + return ESP_ERR_NOT_SUPPORTED; + } + + int16_t raw = voltage * 1000.0; + return write_reg_16(dev, INA3221_REG_VALID_POWER_UPPER_LIMIT, *(uint16_t *)&raw); +} + +esp_err_t ina3221_set_power_valid_lower_limit(ina3221_t *dev, float voltage) +{ + CHECK_ARG(dev); + if (!dev->config.ebus) + { + ESP_LOGE(TAG, "Bus is not enabled in device [0x%02x at %d]", dev->i2c_dev.addr, dev->i2c_dev.port); + return ESP_ERR_NOT_SUPPORTED; + } + + int16_t raw = voltage * 1000.0; + return write_reg_16(dev, INA3221_REG_VALID_POWER_LOWER_LIMIT, *(uint16_t *)&raw); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/ina3221/ina3221.h Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,387 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2016 Zaltora <https://github.com/Zaltora> + * Copyright (c) 2019 Ruslan V. Uss <unclerus@gmail.com> + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +/** + * @file ina3221.h + * @defgroup ina3221 ina3221 + * @{ + * + * ESP-IDF driver for Shunt and Bus Voltage Monitor INA3221 + * + * Ported from esp-open-rtos + * + * Copyright (c) 2016 Zaltora <https://github.com/Zaltora> + * Copyright (c) 2019 Ruslan V. Uss <unclerus@gmail.com> + * + * MIT Licensed as described in the file LICENSE + */ +#ifndef __INA3221_H__ +#define __INA3221_H__ + +#include <stdbool.h> +#include <i2cdev.h> +#include <esp_err.h> + +#ifdef __cplusplus +extern "C" { +#endif + +#define INA3221_I2C_ADDR_GND 0x40 ///< A0 to GND +#define INA3221_I2C_ADDR_VS 0x41 ///< A0 to Vs+ +#define INA3221_I2C_ADDR_SDA 0x42 ///< A0 to SDA +#define INA3221_I2C_ADDR_SCL 0x43 ///< A0 to SCL + +#define INA3221_BUS_NUMBER 3 ///< Number of shunt available + +/** + * Default register values after reset + */ +#define INA3221_DEFAULT_CONFIG (0x7127) +#define INA3221_DEFAULT_MASK (0x0002) +#define INA3221_DEFAULT_POWER_UPPER_LIMIT (0x2710) //10V +#define INA3221_DEFAULT_POWER_LOWER_LIMIT (0x2328) //9V + +#define INA3221_MASK_CONFIG (0x7C00) + +/** + * Number of samples + */ +typedef enum +{ + INA3221_AVG_1 = 0, ///< Default + INA3221_AVG_4, + INA3221_AVG_16, + INA3221_AVG_64, + INA3221_AVG_128, + INA3221_AVG_256, + INA3221_AVG_512, + INA3221_AVG_1024, +} ina3221_avg_t; + +/** + * Channel selection list + */ +typedef enum +{ + INA3221_CHANNEL_1 = 0, + INA3221_CHANNEL_2, + INA3221_CHANNEL_3, +} ina3221_channel_t; + +/** + * Conversion time in us + */ +typedef enum +{ + INA3221_CT_140 = 0, + INA3221_CT_204, + INA3221_CT_332, + INA3221_CT_588, + INA3221_CT_1100, ///< Default + INA3221_CT_2116, + INA3221_CT_4156, + INA3221_CT_8244, +} ina3221_ct_t; + +/** + * Config description register + */ +typedef union +{ + struct + { + uint16_t esht :1; ///< Enable/Disable shunt measure // LSB + uint16_t ebus :1; ///< Enable/Disable bus measure + uint16_t mode :1; ///< Single shot measure or continuous mode + uint16_t vsht :3; ///< Shunt voltage conversion time + uint16_t vbus :3; ///< Bus voltage conversion time + uint16_t avg :3; ///< number of sample collected and averaged together + uint16_t ch3 :1; ///< Enable/Disable channel 3 + uint16_t ch2 :1; ///< Enable/Disable channel 2 + uint16_t ch1 :1; ///< Enable/Disable channel 1 + uint16_t rst :1; ///< Set this bit to 1 to reset device // MSB + }; + uint16_t config_register; +} ina3221_config_t; + +/** + * Mask/enable description register + */ +typedef union +{ + struct + { + uint16_t cvrf :1; ///< Conversion ready flag (1: ready) // LSB + uint16_t tcf :1; ///< Timing control flag + uint16_t pvf :1; ///< Power valid flag + uint16_t wf :3; ///< Warning alert flag (Read mask to clear) (order : Channel1:channel2:channel3) + uint16_t sf :1; ///< Sum alert flag (Read mask to clear) + uint16_t cf :3; ///< Critical alert flag (Read mask to clear) (order : Channel1:channel2:channel3) + uint16_t cen :1; ///< Critical alert latch (1:enable) + uint16_t wen :1; ///< Warning alert latch (1:enable) + uint16_t scc3 :1; ///< channel 3 sum (1:enable) + uint16_t scc2 :1; ///< channel 2 sum (1:enable) + uint16_t scc1 :1; ///< channel 1 sum (1:enable) + uint16_t :1; ///< Reserved //MSB + }; + uint16_t mask_register; +} ina3221_mask_t; + +/** + * Device descriptor + */ +typedef struct +{ + i2c_dev_t i2c_dev; ///< I2C device descriptor + uint16_t shunt[INA3221_BUS_NUMBER]; ///< Memory of shunt value (mOhm) + ina3221_config_t config; ///< Memory of ina3221 config + ina3221_mask_t mask; ///< Memory of mask_config +} ina3221_t; + +/** + * @brief Initialize device descriptor + * + * @param dev Device descriptor + * @param addr Device I2C address + * @param port I2C port + * @param sda_gpio SDA GPIO + * @param scl_gpio SCL GPIO + * @return ESP_OK to indicate success + */ +esp_err_t ina3221_init_desc(ina3221_t *dev, uint8_t addr, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio); + +/** + * @brief Free device descriptor + * + * @param dev Device descriptor + * @return ESP_OK to indicate success + */ +esp_err_t ina3221_free_desc(ina3221_t *dev); + +/** + * @brief Write current config to device + * + * Sync internal config buffer and mask with external device register. (When struct is set manually). + * + * @param dev Device descriptor + * @return ESP_OK to indicate success + */ +esp_err_t ina3221_sync(ina3221_t *dev); + +/** + * @brief Trigger measurement + * + * Send current config register to trig a measurement in single-shot mode. + * + * @param dev Device descriptor + * @return ESP_OK to indicate success + */ +esp_err_t ina3221_trigger(ina3221_t *dev); + +/** + * @brief Read status from device + * + * Get mask register from the device, used to read flags. + * + * @param dev Device descriptor + * @return ESP_OK to indicate success + */ +esp_err_t ina3221_get_status(ina3221_t *dev); + +/** + * @brief Set options for bus and shunt + * + * @param dev Device descriptor + * @param mode Selection of measurement (true : continuous // false : single-shot) + * @param bus Enable/Disable bus measures + * @param shunt Enable/Disable shunt measures + * @return ESP_OK to indicate success + */ +esp_err_t ina3221_set_options(ina3221_t *dev, bool mode, bool bus, bool shunt); + +/** + * @brief Select channels + * + * @param dev Device descriptor + * @param ch1 Enable/Disable channel 1 (true : enable // false : disable) + * @param ch2 Enable/Disable channel 2 (true : enable // false : disable) + * @param ch3 Enable/Disable channel 3 (true : enable // false : disable) + * @return ESP_OK to indicate success + */ +esp_err_t ina3221_enable_channel(ina3221_t *dev, bool ch1, bool ch2, bool ch3); + +/** + * @brief Select channel to be sum (don't impact enable channel status) + * + * @param dev Device descriptor + * @param ch1 Enable/Disable channel 1 (true : enable // false : disable) + * @param ch2 Enable/Disable channel 2 (true : enable // false : disable) + * @param ch3 Enable/Disable channel 3 (true : enable // false : disable) + * @return ESP_OK to indicate success + */ +esp_err_t ina3221_enable_channel_sum(ina3221_t *dev, bool ch1, bool ch2, bool ch3); + +/** + * @brief enable/disable latch on warning and critical alert pin + * + * @param dev Device descriptor + * @param warning Enable/Disable warning latch (true : Latch // false : Transparent) + * @param critical Enable/Disable critical latch (true : Latch // false : Transparent) + * @return ESP_OK to indicate success + */ +esp_err_t ina3221_enable_latch_pin(ina3221_t *dev, bool warning, bool critical); + +/** + * @brief Set average (number of samples measured) + * + * @param dev Device descriptor + * @param avg Value of average selection + * @return ESP_OK to indicate success + */ +esp_err_t ina3221_set_average(ina3221_t *dev, ina3221_avg_t avg); + +/** + * @brief Set conversion time for bus. + * + * @param dev Device descriptor + * @param ct Value of conversion time selection + * @return ESP_OK to indicate success + */ +esp_err_t ina3221_set_bus_conversion_time(ina3221_t *dev, ina3221_ct_t ct); + +/** + * @brief Set conversion time for shunt. + * + * @param dev Device descriptor + * @param ct Value of conversion time selection + * @return ESP_OK to indicate success + */ +esp_err_t ina3221_set_shunt_conversion_time(ina3221_t *dev, ina3221_ct_t ct); + +/** + * @brief Reset device + * + * Device will be configured like POR (Power-On-Reset) + * + * @param dev Device descriptor + * @return ESP_OK to indicate success + */ +esp_err_t ina3221_reset(ina3221_t *dev); + +/** + * @brief Get Bus voltage (V) + * + * @param dev Device descriptor + * @param channel Select channel value to get + * @param voltage Data pointer to get bus voltage (V) + * @return ESP_OK to indicate success + */ +esp_err_t ina3221_get_bus_voltage(ina3221_t *dev, ina3221_channel_t channel, float *voltage); + +/** + * @brief Get Shunt voltage (mV) and current (mA) + * + * @param dev Device descriptor + * @param channel Select channel value to get + * @param voltage Data pointer to get shunt voltage (mV) + * @param current Data pointer to get shunt voltage (mA) + * @return ESP_OK to indicate success + */ +esp_err_t ina3221_get_shunt_value(ina3221_t *dev, ina3221_channel_t channel, float *voltage, float *current); + +/** + * @brief Get Shunt-voltage (mV) sum value of selected channels + * + * @param dev Device descriptor + * @param voltage Data pointer to get shunt voltage (mV) + * @return ESP_OK to indicate success + */ +esp_err_t ina3221_get_sum_shunt_value(ina3221_t *dev, float *voltage); + +/** + * @brief Set Critical alert + * + * Alert when measurement(s) is greater + * + * @param dev Device descriptor + * @param channel Select channel value to set + * @param current Value to set (mA) // max : 163800/shunt (mOhm) + * @return ESP_OK to indicate success + */ +esp_err_t ina3221_set_critical_alert(ina3221_t *dev, ina3221_channel_t channel, float current); + +/** + * @brief Set Warning alert + * + * Alert when average measurement(s) is greater + * + * @param dev Device descriptor + * @param channel Select channel value to set + * @param current Value to set (mA) // max : 163800/shunt (mOhm) + * @return ESP_OK to indicate success + */ +esp_err_t ina3221_set_warning_alert(ina3221_t *dev, ina3221_channel_t channel, float current); + +/** + * @brief Set Sum Warning alert + * + * Compared to each completed cycle of all selected channels : Sum register + * + * @param dev Device descriptor + * @param voltage voltage to set (mV) // max : 655.32 + * @return ESP_OK to indicate success + */ +esp_err_t ina3221_set_sum_warning_alert(ina3221_t *dev, float voltage); + +/** + * @brief Set Power-valid upper-limit + * + * Used to determine if power conditions are met. Bus needs to be enabled. + * If bus voltage exceed the value set, PV pin will be set high. + * + * @param dev Device descriptor + * @param voltage voltage to set (V) + * @return ESP_OK to indicate success + */ +esp_err_t ina3221_set_power_valid_upper_limit(ina3221_t *dev, float voltage); + +/** + * @brief Set Power-valid lower-limit + * + * Used to determine if power conditions are met. Bus needs to be enabled. + * If bus voltage drops below the value set, PV pin will be set low. + * + * @param dev Device descriptor + * @param voltage Voltage to set (V) + * @return ESP_OK to indicate success + */ +esp_err_t ina3221_set_power_valid_lower_limit(ina3221_t *dev, float voltage); + +#ifdef __cplusplus +} +#endif + +/**@}*/ + +#endif /* __INA3221_H__ */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/lc709203f/.eil.yml Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,23 @@ +--- +components: + - name: lc709203f + description: | + Driver for LC709203F battery fuel gauge + group: misc + groups: [] + code_owners: jmpmscorp + depends: + - name: i2cdev + - name: esp_idf_lib_helpers + thread_safe: yes + targets: + - name: esp32 + - name: esp8266 + - name: esp32s2 + - name: esp32c3 + licenses: + - name: ISC + copyrights: + - author: + name: jmpmscorp + year: 2022
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/lc709203f/CMakeLists.txt Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,5 @@ +idf_component_register( + SRCS lc709203f.c + INCLUDE_DIRS . + REQUIRES i2cdev esp_idf_lib_helpers +) \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/lc709203f/LICENSE Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,13 @@ +Copyright (c) 2022 Jose Manuel Perez <jmpmscorp@hotmail.com> + +Permission to use, copy, modify, and distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/lc709203f/component.mk Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,2 @@ +COMPONENT_ADD_INCLUDEDIRS = . +COMPONENT_DEPENDS = i2cdev esp_idf_lib_helpers
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/lc709203f/lc709203f.c Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,341 @@ +/* + * Copyright (c) 2022 Jose Manuel Perez <user@your.dom.ain> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* + * this file is supposed to conform the code style. + */ + +#include <freertos/FreeRTOS.h> +#include <esp_idf_lib_helpers.h> +#include <esp_err.h> +#include <esp_log.h> + +#include "lc709203f.h" + +#define LC709203F_I2C_ADDR 0x0B ///< LC709203F default i2c address +#define LC709003F_I2C_MAX_FREQ_HZ 400000 ///< 400kHz + +#define LC709203F_INIT_RSOC_VAL 0xAA55 ///< Value to init RSOC +#define LC709203F_CRC_POLYNOMIAL 0x07 /// Polynomial to calculare CRC-8-ATM + +// static char *tag = "lc709203f"; + +// clang-format off +#define CHECK(x) do { esp_err_t __; if ((__ = x) != ESP_OK) return __; } while (0) +#define CHECK_ARG(VAL) do { if (!(VAL)) return ESP_ERR_INVALID_ARG; } while (0) +// clang-format on + +static uint8_t s_lc709203f_calc_crc(uint8_t *data, size_t data_len) +{ + uint8_t crc = 0; + + for (size_t j = data_len; j; --j) + { + crc ^= *data++; + + for (size_t i = 8; i; --i) + { + crc = (crc & 0x80) ? (crc << 1) ^ LC709203F_CRC_POLYNOMIAL : (crc << 1); + } + } + + return crc; +} + +inline static esp_err_t s_i2c_dev_read_word(i2c_dev_t *dev, uint8_t reg, uint16_t *value) +{ + uint8_t read_data[6] = { 0 }; + uint8_t crc = 0; + + I2C_DEV_TAKE_MUTEX(dev); + I2C_DEV_CHECK(dev, i2c_dev_read_reg(dev, reg, read_data + 3, 3)); + I2C_DEV_GIVE_MUTEX(dev); + + read_data[0] = dev->addr << 1; + read_data[1] = reg; + read_data[2] = read_data[0] | 0x01; + + crc = s_lc709203f_calc_crc(read_data, 5); + + if (crc != read_data[5]) + { + return ESP_ERR_INVALID_CRC; + } + + if (value) + { + *value = read_data[3] | (read_data[4] << 8); + } + + return ESP_OK; +} + +inline static esp_err_t s_i2c_dev_write_reg_word(i2c_dev_t *dev, uint8_t reg, uint16_t value) +{ + uint8_t write_data[5]; + write_data[0] = dev->addr << 1; + write_data[1] = reg; + write_data[2] = value & 0xFF; + write_data[3] = value >> 8; + write_data[4] = s_lc709203f_calc_crc(write_data, 4); + + I2C_DEV_TAKE_MUTEX(dev); + I2C_DEV_CHECK(dev, i2c_dev_write_reg(dev, reg, write_data + 2, 3)); + I2C_DEV_GIVE_MUTEX(dev); + + return ESP_OK; +} + +esp_err_t lc709203f_init_desc(i2c_dev_t *dev, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio) +{ + CHECK_ARG(dev); + + dev->addr = LC709203F_I2C_ADDR; + dev->port = port; + dev->cfg.sda_io_num = sda_gpio; + dev->cfg.scl_io_num = scl_gpio; +#if HELPER_TARGET_IS_ESP32 + dev->cfg.master.clk_speed = LC709003F_I2C_MAX_FREQ_HZ; +#endif + + return i2c_dev_create_mutex(dev); +} + +esp_err_t lc709203f_free_desc(i2c_dev_t *dev) +{ + CHECK_ARG(dev); + + return i2c_dev_delete_mutex(dev); +} + +esp_err_t lc709203f_before_rsoc(i2c_dev_t *dev) +{ + CHECK_ARG(dev); + return s_i2c_dev_write_reg_word(dev, LC709203F_REG_BEFORE_RSOC, LC709203F_INIT_RSOC_VAL); +} + +esp_err_t lc709203f_initial_rsoc(i2c_dev_t *dev) +{ + CHECK_ARG(dev); + return s_i2c_dev_write_reg_word(dev, LC709203F_REG_INITIAL_RSOC, LC709203F_INIT_RSOC_VAL); +} + +esp_err_t lc709203f_get_alarm_low_rsoc(i2c_dev_t *dev, uint8_t *rsoc) +{ + CHECK_ARG(dev); + + return s_i2c_dev_read_word(dev, LC709203F_REG_ALARM_LOW_RSOC, (uint16_t *)rsoc); +} + +esp_err_t lc709203f_get_alarm_low_voltage(i2c_dev_t *dev, uint16_t *voltage) +{ + CHECK_ARG(dev); + + return s_i2c_dev_read_word(dev, LC709203F_REG_ALARM_LOW_VOLTAGE, voltage); +} + +esp_err_t lc709203f_get_apa(i2c_dev_t *dev, uint8_t *apa) +{ + CHECK_ARG(dev); + + return s_i2c_dev_read_word(dev, LC709203F_REG_APA, (uint16_t *)apa); +} + +esp_err_t lc709203f_get_apt(i2c_dev_t *dev, uint16_t *apt) +{ + CHECK_ARG(dev); + + return s_i2c_dev_read_word(dev, LC709203F_REG_APT, apt); +} + +esp_err_t lc709203f_get_battery_profile(i2c_dev_t *dev, lc709203f_battery_profile_t *profile) +{ + CHECK_ARG(dev); + + return s_i2c_dev_read_word(dev, LC709203F_REG_CHANGE_PARAMETER, (uint16_t *)profile); +} + +esp_err_t lc709203f_get_battery_profile_code(i2c_dev_t *dev, uint16_t *code) +{ + CHECK_ARG(dev); + + return s_i2c_dev_read_word(dev, LC709203F_REG_NUM_PARAMETER, code); +} + +esp_err_t lc709203f_get_cell_ite(i2c_dev_t *dev, uint16_t *ite) +{ + CHECK_ARG(dev); + + return s_i2c_dev_read_word(dev, LC709203F_REG_CELL_ITE, ite); +} + +esp_err_t lc709203f_get_cell_temperature(i2c_dev_t *dev, float *temperature) +{ + CHECK_ARG(dev); + + uint16_t temp = 0; + + esp_err_t ret = s_i2c_dev_read_word(dev, LC709203F_REG_CELL_TEMPERATURE, &temp); + + if (ret == ESP_OK) + { + *temperature = temp / 10.0; + } + + return ret; +} + +esp_err_t lc709203f_get_cell_temperature_celsius(i2c_dev_t *dev, float *temperature) +{ + esp_err_t ret = lc709203f_get_cell_temperature(dev, temperature); + + if (ret == ESP_OK) + { + *temperature = *temperature - 273; + } + + return ret; +} + +esp_err_t lc709203f_get_cell_voltage(i2c_dev_t *dev, uint16_t *voltage) +{ + CHECK_ARG(dev); + + return s_i2c_dev_read_word(dev, LC709203F_REG_CELL_VOLTAGE, voltage); +} + +esp_err_t lc709203f_get_current_direction(i2c_dev_t *dev, lc709203f_direction_t *direction) +{ + CHECK_ARG(dev); + + return s_i2c_dev_read_word(dev, LC709203F_REG_CELL_VOLTAGE, (uint16_t *)direction); +} + +esp_err_t lc709203f_get_ic_version(i2c_dev_t *dev, uint16_t *ic_version) +{ + CHECK_ARG(dev); + + return s_i2c_dev_read_word(dev, LC709203F_REG_IC_VERSION, ic_version); +} + +esp_err_t lc709203f_get_power_mode(i2c_dev_t *dev, lc709203f_power_mode_t *mode) +{ + CHECK_ARG(dev); + + return s_i2c_dev_read_word(dev, LC709203F_REG_IC_POWER_MODE, (uint16_t *)mode); +} + +esp_err_t lc709203f_get_rsoc(i2c_dev_t *dev, uint16_t *rsoc) +{ + CHECK_ARG(dev); + + return s_i2c_dev_read_word(dev, LC709203F_REG_RSOC, rsoc); +} + +esp_err_t lc709203f_get_temp_mode(i2c_dev_t *dev, lc709203f_temp_mode_t *mode) +{ + CHECK_ARG(dev); + + return s_i2c_dev_read_word(dev, LC709203F_REG_STATUS_BIT, (uint16_t *)mode); +} + +esp_err_t lc709203f_get_thermistor_b(i2c_dev_t *dev, uint16_t *value) +{ + CHECK_ARG(dev); + + return s_i2c_dev_read_word(dev, LC709203F_REG_THERMISTOR_B, value); +} + +esp_err_t lc709203f_set_alarm_low_rsoc(i2c_dev_t *dev, uint8_t rsoc) +{ + CHECK_ARG(dev); + CHECK_ARG(rsoc <= 100); + + return s_i2c_dev_write_reg_word(dev, LC709203F_REG_ALARM_LOW_RSOC, (uint16_t)rsoc); +} + +esp_err_t lc709203f_set_alarm_low_voltage(i2c_dev_t *dev, uint16_t voltage) +{ + CHECK_ARG(dev); + + return s_i2c_dev_write_reg_word(dev, LC709203F_REG_ALARM_LOW_VOLTAGE, voltage); +} + +esp_err_t lc709203f_set_apa(i2c_dev_t *dev, uint8_t apa) +{ + CHECK_ARG(dev); + + return s_i2c_dev_write_reg_word(dev, LC709203F_REG_APA, (uint16_t)apa); +} + +esp_err_t lc709203f_set_apt(i2c_dev_t *dev, uint16_t apt) +{ + CHECK_ARG(dev); + + return s_i2c_dev_write_reg_word(dev, LC709203F_REG_APT, apt); +} + +esp_err_t lc709203f_set_battery_profile(i2c_dev_t *dev, lc709203f_battery_profile_t profile) +{ + CHECK_ARG(dev); + + return s_i2c_dev_write_reg_word(dev, LC709203F_REG_CHANGE_PARAMETER, (uint16_t)profile); +} + +esp_err_t lc709203f_set_cell_temperature(i2c_dev_t *dev, float temperature) +{ + uint16_t temp = (uint16_t)(temperature * 10); + + CHECK_ARG(dev); + CHECK_ARG(temp >= 0x09e4 && temperature <= 0x0D04); + + return s_i2c_dev_write_reg_word(dev, LC709203F_REG_CELL_TEMPERATURE, temp); +} + +esp_err_t lc709203f_set_cell_temperature_celsius(i2c_dev_t *dev, float temperature) +{ + CHECK_ARG(dev); + + return lc709203f_set_cell_temperature(dev, temperature + 273); +} + +esp_err_t lc709203f_set_current_direction(i2c_dev_t *dev, lc709203f_direction_t direction) +{ + CHECK_ARG(dev); + + return s_i2c_dev_write_reg_word(dev, LC709203F_REG_CURRENT_DIRECTION, (uint16_t)direction); +} + +esp_err_t lc709203f_set_power_mode(i2c_dev_t *dev, lc709203f_power_mode_t mode) +{ + CHECK_ARG(dev); + + return s_i2c_dev_write_reg_word(dev, LC709203F_REG_IC_POWER_MODE, (uint16_t)mode); +} + +esp_err_t lc709203f_set_temp_mode(i2c_dev_t *dev, lc709203f_temp_mode_t mode) +{ + CHECK_ARG(dev); + + return s_i2c_dev_write_reg_word(dev, LC709203F_REG_STATUS_BIT, (uint16_t)mode); +} + +esp_err_t lc709203f_set_thermistor_b(i2c_dev_t *dev, uint16_t value) +{ + CHECK_ARG(dev); + + return s_i2c_dev_write_reg_word(dev, LC709203F_REG_THERMISTOR_B, value); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/lc709203f/lc709203f.h Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,440 @@ +/* + * Copyright (c) 2022 Jose Manuel Perez <user@your.dom.ain> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/** + * @file lc709203f.h + * @defgroup lc709203f lc709203f + * @{ + * + * LC709203F Battery Fuel Gauge driver + * + */ + +#ifndef __LC709203F__H__ +#define __LC709203F__H__ + +#include <esp_err.h> +#include <i2cdev.h> + +#ifdef __cplusplus +extern "C" { +#endif + +#define LC709203F_REG_BEFORE_RSOC 0x04 ///< Initialize before RSOC +#define LC709203F_REG_THERMISTOR_B 0x06 ///< Read/write thermistor B +#define LC709203F_REG_INITIAL_RSOC 0x07 ///< Initialize RSOC calculation +#define LC709203F_REG_CELL_TEMPERATURE 0x08 ///< Read/write batt temperature +#define LC709203F_REG_CELL_VOLTAGE 0x09 ///< Read batt voltage +#define LC709203F_REG_CURRENT_DIRECTION 0x0A ///< Read/write current direction +#define LC709203F_REG_APA 0x0B ///< Read/write Adjustment Pack Application +#define LC709203F_REG_APT 0x0C ///< Read/write Adjustment Pack Thermistor +#define LC709203F_REG_RSOC 0x0D ///< Read state of charge +#define LC709203F_REG_CELL_ITE 0x0F ///< Read batt indicator to empty +#define LC709203F_REG_IC_VERSION 0x11 ///< Read IC version +#define LC709203F_REG_CHANGE_PARAMETER 0x12 ///< Set the battery profile +#define LC709203F_REG_ALARM_LOW_RSOC 0x13 ///< Alarm on percent threshold +#define LC709203F_REG_ALARM_LOW_VOLTAGE 0x14 ///< Alarm on voltage threshold +#define LC709203F_REG_IC_POWER_MODE 0x15 ///< Sets sleep/power mode +#define LC709203F_REG_STATUS_BIT 0x16 ///< Temperature obtaining method +#define LC709203F_REG_NUM_PARAMETER 0x1A ///< Batt profile code + +/*! Battery temperature source */ +typedef enum { + LC709203F_TEMP_MODE_I2C = 0x0000, + LC709203F_TEMP_MODE_THERMISTOR = 0x0001, +} lc709203f_temp_mode_t; + +/*! Chip power state */ +typedef enum { + LC709203F_POWER_MODE_OPERATIONAL = 0x0001, + LC709203F_POWER_MODE_SLEEP = 0x0002, +} lc709203f_power_mode_t; + +typedef enum { + LC709203F_DIRECTION_AUTO = 0x0000, + LC709203F_DIRECTION_CHARGE = 0x0001, + LC709203F_DIRECTION_DISCHARGE = 0xFFFF, +} lc709203f_direction_t; + +typedef enum { + LC709203F_BATTERY_PROFILE_0 = 0x0000, + LC709203F_BATTERY_PROFILE_1 = 0x0001, +} lc709203f_battery_profile_t; + +/** + * @brief Initialize device descriptor + * + * @param[in] dev Device descriptor + * @param[in] port I2C port number + * @param[in] sda_gpio GPIO pin number for SDA + * @param[in] scl_gpio GPIO pin number for SCL + * @return + * `ESP_INVALID_ARG` null dev + * `ESP_OK` on success + */ +esp_err_t lc709203f_init_desc(i2c_dev_t *dev, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio); + +/** + * @brief Free device descriptor + * + * @param[in] dev Device descriptor + * @return + * `ESP_INVALID_ARG` null dev + * `ESP_OK` on success + */ +esp_err_t lc709203f_free_desc(i2c_dev_t *dev); + +/** + * @brief Executes RSOC initialization with sampled maximum voltage when 0xAA55 is set. + * + * @param[in] dev Device descriptor + * @return + * `ESP_INVALID_ARG` null dev + * `ESP_OK` on success + */ +esp_err_t lc709203f_before_rsoc(i2c_dev_t *dev); + +/** + * @brief Executes RSOC initialization when 0xAA55 is set. + * + * @param[in] dev Device descriptor + * @return + * `ESP_INVALID_ARG` null dev + * `ESP_OK` on success + */ +esp_err_t lc709203f_initial_rsoc(i2c_dev_t *dev); + +/** + * @brief Get alarm low rsoc (% unit) + * + * @note Disable alarm setting RSOC as 0 (0x0000) + * Enable alarm setting RSOC in + * + * @param[in] dev Device descriptor + * @param[out] rsoc RSOC value (%) + * @return + * `ESP_INVALID_ARG` null dev or rsoc out of range 0-100 + * `ESP_OK` on success + */ +esp_err_t lc709203f_get_alarm_low_rsoc(i2c_dev_t *dev, uint8_t *rsoc); + +/** + * @brief Get alarm low voltage (mV unit) + * + * @param[in] dev Device descriptor + * @param[out] voltage Voltage value (mV) + * @return + * `ESP_INVALID_ARG` null dev + * `ESP_OK` on success + */ +esp_err_t lc709203f_get_alarm_low_voltage(i2c_dev_t *dev, uint16_t *voltage); + +/** + * @brief Get APA (adjustment pack application) + * + * @param[in] dev Device descriptor + * @param[out] apa Current APA value + * @return + * `ESP_INVALID_ARG` null dev + * `ESP_OK` on success + */ +esp_err_t lc709203f_get_apa(i2c_dev_t *dev, uint8_t *apa); + +/** + * @brief Get APT (adjustment pack thermistor) + * + * @param[in] dev Device descriptor + * @param[out] apt Current APT value + * @return + * `ESP_INVALID_ARG` null dev + * `ESP_OK` on success + */ +esp_err_t lc709203f_get_apt(i2c_dev_t *dev, uint16_t *apt); + +/** + * @brief Get battery profile + * + * @param[in] dev Device descriptor + * @param[out] profile Current profile + * @return + * `ESP_INVALID_ARG` null dev + * `ESP_OK` on success + */ +esp_err_t lc709203f_get_battery_profile(i2c_dev_t *dev, lc709203f_battery_profile_t *profile); + +/** + * @brief Get battery profile code + * + * @param[in] dev Device descriptor + * @param[out] code Current profile code. 0x3001 or 0x0504 only. + * @return + * `ESP_INVALID_ARG` null dev + * `ESP_OK` on success + */ +esp_err_t lc709203f_get_battery_profile_code(i2c_dev_t *dev, uint16_t *code); + +/** + * @brief Get ITE (indicator to empty) "RSOC (%) on 0-1000 scale" + * + * @param[in] dev Device descriptor + * @param[out] ite Current RSOC value + * @return + * `ESP_INVALID_ARG` null dev + * `ESP_OK` on success + */ +esp_err_t lc709203f_get_cell_ite(i2c_dev_t *dev, uint16_t *ite); + +/** + * @brief Get cell temperature in ºK. Min. unit is 0.1K + * + * @note Conversion ºK -> ºC = ºK + 273 + * Conversion ºC -> ºK = ºK + 273 + * Typical values: + * 0ºC -> 0x0AAC + * 25ºC -> 0x0BA6 (default) + * + * @param[in] dev Device descriptor + * @param[out] temperature Current temperature + * @return + * `ESP_INVALID_ARG` null dev + * `ESP_OK` on success + */ +esp_err_t lc709203f_get_cell_temperature(i2c_dev_t *dev, float *temperature); + +/** + * @brief Get cell temperature in ºC. + * + * @param[in] dev Device descriptor + * @param[out] temperature Current temperature + * @return + * `ESP_INVALID_ARG` null dev + * `ESP_OK` on success + */ +esp_err_t lc709203f_get_cell_temperature_celsius(i2c_dev_t *dev, float *temperature); + +/** + * @brief Get cell voltage + * + * @param[in] dev Device descriptor + * @param[out] voltage Current voltage + * @return + * `ESP_INVALID_ARG` null dev + * `ESP_OK` on success + */ +esp_err_t lc709203f_get_cell_voltage(i2c_dev_t *dev, uint16_t *voltage); + +/** + * @brief Set current direction + * + * @param[in] dev Device descriptor + * @param[out] direction Current direction + * @return + * `ESP_INVALID_ARG` null dev + * `ESP_OK` on success + */ +esp_err_t lc709203f_get_direction(i2c_dev_t *dev, lc709203f_direction_t *direction); + +/** + * @brief Get ID number of an IC + * + * @param[in] dev Device descriptor + * @param[out] ic_version IC ID number + * @return + * `ESP_INVALID_ARG` null dev + * `ESP_OK` on success + */ +esp_err_t lc709203f_get_ic_version(i2c_dev_t *dev, uint16_t *ic_version); + +/** + * @brief Get power mode + * + * @param[in] dev Device descriptor + * @param[out] mode Power mode + * @return + * `ESP_INVALID_ARG` null dev + * `ESP_OK` on success + */ +esp_err_t lc709203f_get_power_mode(i2c_dev_t *dev, lc709203f_power_mode_t *mode); + +/** + * @brief Get RSOC (%) on 0-100 scale + * + * @param[in] dev Device descriptor + * @param[out] rsoc Current RSOC value + * @return + * `ESP_INVALID_ARG` null dev + * `ESP_OK` on success + */ +esp_err_t lc709203f_get_rsoc(i2c_dev_t *dev, uint16_t *rsoc); + +/** + * @brief Get temperature mode + * + * @param[in] dev Device descriptor + * @param[out] mode Temperature obtaining mode + * @return + * `ESP_INVALID_ARG` null dev + * `ESP_OK` on success + */ +esp_err_t lc709203f_get_temp_mode(i2c_dev_t *dev, lc709203f_temp_mode_t *mode); + +/** + * @brief Get B-constant of the thermistor to be measured + * + * @param[in] dev Device descriptor + * @param[out] value B-constant value + * @return + * `ESP_INVALID_ARG` null dev + * `ESP_OK` on success + */ +esp_err_t lc709203f_get_thermistor_b(i2c_dev_t *dev, uint16_t *value); + +/** + * @brief Set alarm low rsoc (% unit) + * + * @note Disable alarm setting RSOC as 0 (0x0000) + * Enable alarm setting RSOC in + * + * @param[in] dev Device descriptor + * @param[in] rsoc RSOC value (%) + * @return + * `ESP_INVALID_ARG` null dev or rsoc out of range 0-100 + * `ESP_OK` on success + */ +esp_err_t lc709203f_set_alarm_low_rsoc(i2c_dev_t *dev, uint8_t rsoc); + +/** + * @brief Set alarm low voltage (mV unit) + * + * @param[in] dev Device descriptor + * @param[in] voltage Voltage value (mV) + * @return + * `ESP_INVALID_ARG` null dev + * `ESP_OK` on success + */ +esp_err_t lc709203f_set_alarm_low_voltage(i2c_dev_t *dev, uint16_t voltage); + +/** + * @brief Set APA (adjustment pack application) + * + * @param[in] dev Device descriptor + * @param[in] apa Value to set + * @return `ESP_OK` on success + */ +esp_err_t lc709203f_set_apa(i2c_dev_t *dev, uint8_t apa); + +/** + * @brief Set APA (adjustment pack thermistor) + * + * @param[in] dev Device descriptor + * @param[in] apt Value to set + * @return + * @return + * `ESP_INVALID_ARG` null dev + * `ESP_OK` on success + */ +esp_err_t lc709203f_set_apt(i2c_dev_t *dev, uint16_t apt); + +/** + * @brief Set battery profile + * + * @param[in] dev Device descriptor + * @param[in] profile Current profile + * @return + * `ESP_INVALID_ARG` null dev + * `ESP_OK` on success + */ +esp_err_t lc709203f_set_battery_profile(i2c_dev_t *dev, lc709203f_battery_profile_t profile); + +/** + * @brief Set cell temperature in ºK. Min. unit is 0.1ºK + * + * @note Conversion ºK -> ºC = ºK - 273 + * Conversion ºC -> ºK = ºK + 273 + * Typical values: + * 0ºC -> 0x0AAC + * 25ºC -> 0x0BA6 (default) + * + * @param[in] dev Device descriptor + * @param[in] temperature Temperature to set + * @return + * `ESP_INVALID_ARG` null dev + * `ESP_OK` on success + */ +esp_err_t lc709203f_set_cell_temperature(i2c_dev_t *dev, float temperature); + +/** + * @brief Set cell temperature in ºC. + * + * @param[in] dev Device descriptor + * @param[in] temperature Temperature to set + * @return + * `ESP_INVALID_ARG` null dev + * `ESP_OK` on success + */ +esp_err_t lc709203f_set_cell_temperature_celsius(i2c_dev_t *dev, float temperature); + +/** + * @brief Set current direction + * + * @param[in] dev Device descriptor + * @param[in] direction Current direction + * @return + * `ESP_INVALID_ARG` null dev. In I2C mode, temperature not in range 0x09E4-0x0D04 () + * `ESP_OK` on success + */ +esp_err_t lc709203f_set_current_direction(i2c_dev_t *dev, lc709203f_direction_t direction); + +/** + * @brief Set power mode + * + * @param[in] dev Device descriptor + * @param[in] mode Power mode + * @return + * `ESP_INVALID_ARG` null dev + * `ESP_OK` on success + */ +esp_err_t lc709203f_set_power_mode(i2c_dev_t *dev, lc709203f_power_mode_t mode); + +/** + * @brief Set temperature mode + * + * @param[in] dev Device descriptor + * @param[in] mode Temperature obtaining mode + * @return + * `ESP_INVALID_ARG` null dev + * `ESP_OK` on success + */ +esp_err_t lc709203f_set_temp_mode(i2c_dev_t *dev, lc709203f_temp_mode_t mode); + +/** + * @brief Set B-constant of ther thermistor to be measured + * + * @param[in] dev Device descriptor + * @param[in] value B-constant value + * @return + * `ESP_INVALID_ARG` null dev + * `ESP_OK` on success + */ +esp_err_t lc709203f_set_thermistor_b(i2c_dev_t *dev, uint16_t value); + +#ifdef __cplusplus +} +#endif + +/**@}*/ + +#endif // __LC709203F__H__
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/led_strip/.eil.yml Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,23 @@ +--- +components: + - name: led_strip + description: RMT-based driver for WS2812B/SK6812/APA106/SM16703 LED strips + group: led + groups: [] + code_owners: UncleRus + depends: + - name: driver + - name: log + - name: color + - name: esp_idf_lib_helpers + thread_safe: yes + targets: + - name: esp32 + - name: esp32s2 + - name: esp32c3 + licenses: + - name: MIT + copyrights: + - author: + name: UncleRus + year: 2020
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/led_strip/CMakeLists.txt Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,5 @@ +idf_component_register( + SRCS led_strip.c + INCLUDE_DIRS . + REQUIRES driver log color esp_idf_lib_helpers +)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/led_strip/Kconfig Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,17 @@ +menu "LED strip" + +config LED_STRIP_FLUSH_TIMEOUT + int "Strip flush timeout, ms" + default 1000 + +config LED_STRIP_PAUSE_LENGTH + int "Delay between flushes, us" + default 50 + help + This delay is between the sending full data to the all LEDs in strip. + Without this delay, a bug with "missing" pixels is possible: + if delay between calls to led_strip_flush() is small, the LEDs consider + the new data package sent to all LEDs in strip to be a continuation of + the previous one. + +endmenu
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/led_strip/LICENSE Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 Ruslan V. Uss + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/led_strip/README.md Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,8 @@ +# RMT-based ESP-IDF driver for WS2812B/SK6812/APA106 LED strips + +**WARNING!** If you try to use this driver simultaneously with Wi-Fi, you may +encounter RMT transmission bugs. To avoid them, simply initialize device +descriptor from the task bound to the second processor core. + +Interrupt handlers assigned during the initialization of the RMT driver are +bound to the core on which the initialization took place.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/led_strip/component.mk Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,2 @@ +COMPONENT_ADD_INCLUDEDIRS = . +COMPONENT_DEPENDS = driver log color esp_idf_lib_helpers
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/led_strip/led_strip.c Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,280 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2020 Ruslan V. Uss <unclerus@gmail.com> + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +/** + * @file led_strip.c + * + * RMT-based ESP-IDF driver for WS2812B/SK6812/APA106/SM16703 LED strips + * + * Copyright (c) 2020 Ruslan V. Uss <unclerus@gmail.com> + * + * MIT Licensed as described in the file LICENSE + */ +#include "led_strip.h" +#include <esp_log.h> +#include <esp_attr.h> +#include <stdlib.h> +#include <ets_sys.h> +#include <esp_idf_lib_helpers.h> + +#if HELPER_TARGET_IS_ESP8266 +#error led_strip is not supported on ESP8266 +#endif + +#ifndef RMT_DEFAULT_CONFIG_TX +#define RMT_DEFAULT_CONFIG_TX(gpio, channel_id) \ + { \ + .rmt_mode = RMT_MODE_TX, \ + .channel = channel_id, \ + .gpio_num = gpio, \ + .clk_div = 80, \ + .mem_block_num = 1, \ + .tx_config = { \ + .carrier_freq_hz = 38000, \ + .carrier_level = RMT_CARRIER_LEVEL_HIGH, \ + .idle_level = RMT_IDLE_LEVEL_LOW, \ + .carrier_duty_percent = 33, \ + .carrier_en = false, \ + .loop_en = false, \ + .idle_output_en = true, \ + } \ + } +#endif + +static const char *TAG = "led_strip"; + +#define LED_STRIP_RMT_CLK_DIV 2 + +#define CHECK(x) do { esp_err_t __; if ((__ = x) != ESP_OK) return __; } while (0) +#define CHECK_ARG(VAL) do { if (!(VAL)) return ESP_ERR_INVALID_ARG; } while (0) + +#define COLOR_SIZE(strip) (3 + ((strip)->is_rgbw != 0)) + +static void IRAM_ATTR _rmt_adapter(const void *src, rmt_item32_t *dest, size_t src_size, + size_t wanted_num, size_t *translated_size, size_t *item_num, + const rmt_item32_t *bit0, const rmt_item32_t *bit1) +{ + if (!src || !dest) + { + *translated_size = 0; + *item_num = 0; + return; + } + size_t size = 0; + size_t num = 0; + uint8_t *psrc = (uint8_t *)src; + rmt_item32_t *pdest = dest; +#ifdef LED_STRIP_BRIGHTNESS + led_strip_t *strip; + esp_err_t r = rmt_translator_get_context(item_num, (void **)&strip); + uint8_t brightness = r == ESP_OK ? strip->brightness : 255; +#endif + while (size < src_size && num < wanted_num) + { +#ifdef LED_STRIP_BRIGHTNESS + uint8_t b = brightness != 255 ? scale8_video(*psrc, brightness) : *psrc; +#else + uint8_t b = *psrc; +#endif + for (int i = 0; i < 8; i++) + { + // MSB first + pdest->val = b & (1 << (7 - i)) ? bit1->val : bit0->val; + num++; + pdest++; + } + size++; + psrc++; + } + *translated_size = size; + *item_num = num; +} + +typedef struct { + rmt_item32_t bit0, bit1; +} led_rmt_t; + +static led_rmt_t rmt_items[LED_STRIP_TYPE_MAX] = { 0 }; + +static void IRAM_ATTR ws2812_rmt_adapter(const void *src, rmt_item32_t *dest, size_t src_size, + size_t wanted_num, size_t *translated_size, size_t *item_num) +{ + _rmt_adapter(src, dest, src_size, wanted_num, translated_size, item_num, &rmt_items[LED_STRIP_WS2812].bit0, &rmt_items[LED_STRIP_WS2812].bit1); +} + +static void IRAM_ATTR sk6812_rmt_adapter(const void *src, rmt_item32_t *dest, size_t src_size, + size_t wanted_num, size_t *translated_size, size_t *item_num) +{ + _rmt_adapter(src, dest, src_size, wanted_num, translated_size, item_num, &rmt_items[LED_STRIP_SK6812].bit0, &rmt_items[LED_STRIP_SK6812].bit1); +} + +static void IRAM_ATTR apa106_rmt_adapter(const void *src, rmt_item32_t *dest, size_t src_size, + size_t wanted_num, size_t *translated_size, size_t *item_num) +{ + _rmt_adapter(src, dest, src_size, wanted_num, translated_size, item_num, &rmt_items[LED_STRIP_APA106].bit0, &rmt_items[LED_STRIP_APA106].bit1); +} + +static void IRAM_ATTR sm16703_rmt_adapter(const void *src, rmt_item32_t *dest, size_t src_size, + size_t wanted_num, size_t *translated_size, size_t *item_num) +{ + _rmt_adapter(src, dest, src_size, wanted_num, translated_size, item_num, &rmt_items[LED_STRIP_SM16703].bit0, &rmt_items[LED_STRIP_SM16703].bit1); +} + +typedef enum { + ORDER_GRB, + ORDER_RGB, +} color_order_t; + +typedef struct { + uint32_t t0h, t0l, t1h, t1l; + color_order_t order; + sample_to_rmt_t adapter; +} led_params_t; + +static const led_params_t led_params[] = { + [LED_STRIP_WS2812] = { .t0h = 400, .t0l = 1000, .t1h = 1000, .t1l = 400, .order = ORDER_GRB, .adapter = ws2812_rmt_adapter }, + [LED_STRIP_SK6812] = { .t0h = 300, .t0l = 900, .t1h = 600, .t1l = 600, .order = ORDER_GRB, .adapter = sk6812_rmt_adapter }, + [LED_STRIP_APA106] = { .t0h = 350, .t0l = 1360, .t1h = 1360, .t1l = 350, .order = ORDER_RGB, .adapter = apa106_rmt_adapter }, + [LED_STRIP_SM16703] = { .t0h = 300, .t0l = 900, .t1h = 1360, .t1l = 350, .order = ORDER_RGB, .adapter = sm16703_rmt_adapter }, +}; + +/////////////////////////////////////////////////////////////////////////////// + +void led_strip_install() +{ + float ratio = (float)APB_CLK_FREQ / LED_STRIP_RMT_CLK_DIV / 1e09f; + + for (size_t i = 0; i < LED_STRIP_TYPE_MAX; i++) + { + // 0 bit + rmt_items[i].bit0.duration0 = (uint32_t)(ratio * led_params[i].t0h); + rmt_items[i].bit0.level0 = 1; + rmt_items[i].bit0.duration1 = (uint32_t)(ratio * led_params[i].t0l); + rmt_items[i].bit0.level1 = 0; + // 1 bit + rmt_items[i].bit1.duration0 = (uint32_t)(ratio * led_params[i].t1h); + rmt_items[i].bit1.level0 = 1; + rmt_items[i].bit1.duration1 = (uint32_t)(ratio * led_params[i].t1l); + rmt_items[i].bit1.level1 = 0; + } +} + +esp_err_t led_strip_init(led_strip_t *strip) +{ + CHECK_ARG(strip && strip->length > 0 && strip->type < LED_STRIP_TYPE_MAX); + + strip->buf = calloc(strip->length, COLOR_SIZE(strip)); + if (!strip->buf) + { + ESP_LOGE(TAG, "Not enough memory"); + return ESP_ERR_NO_MEM; + } + + rmt_config_t config = RMT_DEFAULT_CONFIG_TX(strip->gpio, strip->channel); + config.clk_div = LED_STRIP_RMT_CLK_DIV; + + CHECK(rmt_config(&config)); + CHECK(rmt_driver_install(config.channel, 0, 0)); + + CHECK(rmt_translator_init(config.channel, led_params[strip->type].adapter)); +#ifdef LED_STRIP_BRIGHTNESS + // No support for translator context prior to ESP-IDF 4.3 + CHECK(rmt_translator_set_context(config.channel, strip)); +#endif + + return ESP_OK; +} + +esp_err_t led_strip_free(led_strip_t *strip) +{ + CHECK_ARG(strip && strip->buf); + free(strip->buf); + + CHECK(rmt_driver_uninstall(strip->channel)); + + return ESP_OK; +} + +esp_err_t led_strip_flush(led_strip_t *strip) +{ + CHECK_ARG(strip && strip->buf); + + CHECK(rmt_wait_tx_done(strip->channel, pdMS_TO_TICKS(CONFIG_LED_STRIP_FLUSH_TIMEOUT))); + ets_delay_us(CONFIG_LED_STRIP_PAUSE_LENGTH); + return rmt_write_sample(strip->channel, strip->buf, + strip->length * COLOR_SIZE(strip), false); +} + +bool led_strip_busy(led_strip_t *strip) +{ + if (!strip) return false; + return rmt_wait_tx_done(strip->channel, 0) == ESP_ERR_TIMEOUT; +} + +esp_err_t led_strip_wait(led_strip_t *strip, TickType_t timeout) +{ + CHECK_ARG(strip); + + return rmt_wait_tx_done(strip->channel, timeout); +} + +esp_err_t led_strip_set_pixel(led_strip_t *strip, size_t num, rgb_t color) +{ + CHECK_ARG(strip && strip->buf && num <= strip->length); + size_t idx = num * COLOR_SIZE(strip); + switch (led_params[strip->type].order) + { + case ORDER_GRB: + strip->buf[idx] = color.g; + strip->buf[idx + 1] = color.r; + strip->buf[idx + 2] = color.b; + if (strip->is_rgbw) + strip->buf[idx + 3] = rgb_luma(color); + break; + case ORDER_RGB: + strip->buf[idx] = color.r; + strip->buf[idx + 1] = color.g; + strip->buf[idx + 2] = color.b; + if (strip->is_rgbw) + strip->buf[idx + 3] = rgb_luma(color); + break; + } + return ESP_OK; +} + +esp_err_t led_strip_set_pixels(led_strip_t *strip, size_t start, size_t len, rgb_t *data) +{ + CHECK_ARG(strip && strip->buf && len && start + len <= strip->length); + for (size_t i = 0; i < len; i++) + CHECK(led_strip_set_pixel(strip, i + start, data[i])); + return ESP_OK; +} + +esp_err_t led_strip_fill(led_strip_t *strip, size_t start, size_t len, rgb_t color) +{ + CHECK_ARG(strip && strip->buf && len && start + len <= strip->length); + + for (size_t i = start; i < start + len; i++) + CHECK(led_strip_set_pixel(strip, i, color)); + return ESP_OK; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/led_strip/led_strip.h Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,176 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2020 Ruslan V. Uss <unclerus@gmail.com> + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +/** + * @file led_strip.h + * @defgroup led_strip led_strip + * @{ + * + * RMT-based ESP-IDF driver for WS2812B/SK6812/APA106/SM16703 LED strips + * + * Copyright (c) 2020 Ruslan V. Uss <unclerus@gmail.com> + * + * MIT Licensed as described in the file LICENSE + */ +#ifndef __LED_STRIP_H__ +#define __LED_STRIP_H__ + +#include <driver/gpio.h> +#include <esp_err.h> +#include <driver/rmt.h> +#include <color.h> + +#ifdef __cplusplus +extern "C" { +#endif + +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 3, 0) +#define LED_STRIP_BRIGHTNESS 1 +#endif + +/** + * LED type + */ +typedef enum +{ + LED_STRIP_WS2812 = 0, + LED_STRIP_SK6812, + LED_STRIP_APA106, + LED_STRIP_SM16703, + + LED_STRIP_TYPE_MAX +} led_strip_type_t; + +/** + * LED strip descriptor + */ +typedef struct +{ + led_strip_type_t type; ///< LED type + bool is_rgbw; ///< true for RGBW strips +#ifdef LED_STRIP_BRIGHTNESS + uint8_t brightness; ///< Brightness 0..255, call ::led_strip_flush() after change. + ///< Supported only for ESP-IDF version >= 4.3 +#endif + size_t length; ///< Number of LEDs in strip + gpio_num_t gpio; ///< Data GPIO pin + rmt_channel_t channel; ///< RMT channel + uint8_t *buf; +} led_strip_t; + +/** + * @brief Setup library + * + * This method must be called before any other led_strip methods + */ +void led_strip_install(); + +/** + * @brief Initialize LED strip and allocate buffer memory + * + * @param strip Descriptor of LED strip + * @return `ESP_OK` on success + */ +esp_err_t led_strip_init(led_strip_t *strip); + +/** + * @brief Deallocate buffer memory and release RMT channel + * + * @param strip Descriptor of LED strip + * @return `ESP_OK` on success + */ +esp_err_t led_strip_free(led_strip_t *strip); + +/** + * @brief Send strip buffer to LEDs + * + * @param strip Descriptor of LED strip + * @return `ESP_OK` on success + */ +esp_err_t led_strip_flush(led_strip_t *strip); + +/** + * @brief Check if associated RMT channel is busy + * + * @param strip Descriptor of LED strip + * @return true if RMT peripherals is busy + */ +bool led_strip_busy(led_strip_t *strip); + +/** + * @brief Wait until RMT peripherals is free to send buffer to LEDs + * + * @param strip Descriptor of LED strip + * @param timeout Timeout in RTOS ticks + * @return `ESP_OK` on success + */ +esp_err_t led_strip_wait(led_strip_t *strip, TickType_t timeout); + +/** + * @brief Set color of single LED in strip + * + * This function does not actually change colors of the LEDs. + * Call ::led_strip_flush() to send buffer to the LEDs. + * + * @param strip Descriptor of LED strip + * @param num LED number, 0..strip length - 1 + * @param color RGB color + * @return `ESP_OK` on success + */ +esp_err_t led_strip_set_pixel(led_strip_t *strip, size_t num, rgb_t color); + +/** + * @brief Set colors of multiple LEDs + * + * This function does not actually change colors of the LEDs. + * Call ::led_strip_flush() to send buffer to the LEDs. + * + * @param strip Descriptor of LED strip + * @param start First LED index, 0-based + * @param len Number of LEDs + * @param data Pointer to RGB data + * @return `ESP_OK` on success + */ +esp_err_t led_strip_set_pixels(led_strip_t *strip, size_t start, size_t len, rgb_t *data); + +/** + * @brief Set multiple LEDs to the one color + * + * This function does not actually change colors of the LEDs. + * Call ::led_strip_flush() to send buffer to the LEDs. + * + * @param strip Descriptor of LED strip + * @param start First LED index, 0-based + * @param len Number of LEDs + * @param color RGB color + * @return `ESP_OK` on success + */ +esp_err_t led_strip_fill(led_strip_t *strip, size_t start, size_t len, rgb_t color); + +#ifdef __cplusplus +} +#endif + +/**@}*/ + +#endif /* __LED_STRIP_H__ */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/led_strip_spi/.eil.yml Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,28 @@ +--- +components: + - name: led_strip_spi + description: SPI-based driver for SK9822/APA102 LED strips + group: led + groups: [] + code_owners: trombik + depends: + # XXX conditional depends + - name: log + - name: color + - name: esp_idf_lib_helpers + thread_safe: N/A + targets: + - name: esp32 + - name: esp32c3 + - name: esp8266 + - name: esp32s2 + - name: esp32c3 + licenses: + - name: MIT + copyrights: + - author: + name: UncleRus + year: 2020 + - author: + name: trombik + year: 2021
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/led_strip_spi/CMakeLists.txt Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,12 @@ +if(${IDF_TARGET} STREQUAL esp8266) + set(req esp8266 log color esp_idf_lib_helpers) +else() + set(req driver log color esp_idf_lib_helpers) +endif() + +idf_component_register( + SRCS led_strip_spi.c led_strip_spi_sk9822.c + INCLUDE_DIRS . + REQUIRES ${req} +) +target_compile_options(${COMPONENT_LIB} PRIVATE -Werror=override-init)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/led_strip_spi/Kconfig Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,15 @@ +menu "LED strip SPI" + choice LED_STRIP_SPI_PROTOCOL_CHOOSE + prompt "Choose protocol for SPI" + default LED_STRIP_SPI_USING_SK9822 + help + The protocol to use SPI communication. + config LED_STRIP_SPI_USING_SK9822 + bool "SK9822 (and APA102)" + endchoice + config LED_STRIP_SPI_MUTEX_TIMEOUT_MS + int "Timeout in msec to obtain mutex" + default 1 + help + Timeout when obtaining mutex with xSemaphoreTake. +endmenu
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/led_strip_spi/LICENSE Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,22 @@ +MIT License + +Copyright (c) 2020 Ruslan V. Uss <unclerus@gmail.com> + 2021 Tomoyuki Sakurai <y@rombik.org> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/led_strip_spi/README.md Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,8 @@ +# `led_strip_spi` + +A driver for SPI-based addressable LEDs. + +Supported LEDs are: + +- SK9822 +- APA102 (not tested)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/led_strip_spi/component.mk Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,2 @@ +COMPONENT_ADD_INCLUDEDIRS = . +COMPONENT_DEPENDS = log color esp_idf_lib_helpers
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/led_strip_spi/led_strip_spi.c Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,370 @@ +/* + * MIT License + * + * Copyright (c) 2020 Ruslan V. Uss <unclerus@gmail.com> + * 2021 Tomoyuki Sakurai <y@rombik.org> + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +/** + * @file led_strip.c + * + * SPI-based ESP-IDF driver for SK9822 LED strips + * + */ +#include <string.h> +#include <esp_err.h> +#include <esp_log.h> +#include <esp_attr.h> +#include <freertos/FreeRTOS.h> +#include <freertos/semphr.h> +#include <esp_heap_caps.h> +#include <esp_idf_lib_helpers.h> +#include "led_strip_spi.h" + +#if defined(CONFIG_LED_STRIP_SPI_USING_SK9822) +#include "led_strip_spi_sk9822.h" +#endif + +#if HELPER_TARGET_IS_ESP32 +#include <driver/spi_master.h> +#elif HELPER_TARGET_IS_ESP8266 +#include <driver/spi.h> +#endif + +static const char *TAG = "led_strip_spi"; +static SemaphoreHandle_t mutex; + +#define CHECK(x) do { esp_err_t __; if ((__ = x) != ESP_OK) return __; } while (0) +#define CHECK_ARG(VAL) do { if (!(VAL)) return ESP_ERR_INVALID_ARG; } while (0) +#define MUTEX_TIMEOUT (CONFIG_LED_STRIP_SPI_MUTEX_TIMEOUT_MS / portTICK_PERIOD_MS) + +esp_err_t led_strip_spi_install() +{ + esp_err_t err; + + mutex = xSemaphoreCreateMutex(); + if (mutex == NULL) { + err = ESP_FAIL; + ESP_LOGE(TAG, "xSemaphoreCreateMutex(): failed"); + goto fail; + } + err = ESP_OK; +fail: + return err; +} + +#if HELPER_TARGET_IS_ESP32 +static esp_err_t led_strip_spi_init_esp32(led_strip_spi_t *strip) +{ + CHECK_ARG(strip); + + esp_err_t err = ESP_FAIL; + spi_bus_config_t bus_config = { + .mosi_io_num = strip->mosi_io_num, + .sclk_io_num = strip->sclk_io_num, + .miso_io_num = -1, + .quadhd_io_num = -1, + .quadwp_io_num = -1, +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 4, 0) + .data4_io_num = -1, + .data5_io_num = -1, + .data6_io_num = -1, +#endif + .flags = SPICOMMON_BUSFLAG_MASTER, + .max_transfer_sz = strip->max_transfer_sz, + }; + spi_device_interface_config_t device_interface_config = { + .clock_speed_hz = strip->clock_speed_hz, + .mode = 3, + .spics_io_num = -1, + .queue_size = strip->queue_size, + .command_bits = 0, + .address_bits = 0, + .dummy_bits = 0, + }; + + if (xSemaphoreTake(mutex, MUTEX_TIMEOUT) != pdTRUE) { + err = ESP_FAIL; + ESP_LOGE(TAG, "xSemaphoreTake(): timeout"); + goto fail_without_give; + } + + strip->buf = heap_caps_malloc(LED_STRIP_SPI_BUFFER_SIZE(strip->length), MALLOC_CAP_DMA | MALLOC_CAP_32BIT); + if (strip->buf == NULL) { + ESP_LOGE(TAG, "heap_caps_malloc()"); + err = ESP_ERR_NO_MEM; + goto fail; + } + memset(strip->buf, 0, LED_STRIP_SPI_BUFFER_SIZE(strip->length)); + + /* XXX length is in bit */ + strip->transaction.length = LED_STRIP_SPI_BUFFER_SIZE(strip->length) * 8; + + /* type-specific initialization */ +#if CONFIG_LED_STRIP_SPI_USING_SK9822 + err = led_strip_spi_sk9822_buf_init(strip); + if (err != ESP_OK) { + ESP_LOGE(TAG, "led_strip_spi_sk9822_buf_init(): %s", esp_err_to_name(err)); + goto fail; + } +#endif + ESP_LOGD(TAG, "SPI buffer initialized"); + + err = spi_bus_initialize(strip->host_device, &bus_config, strip->dma_chan); + if (err != ESP_OK) { + ESP_LOGE(TAG, "spi_bus_initialize(): %s", esp_err_to_name(err)); + goto fail; + } + ESP_LOGD(TAG, "SPI bus initialized"); + + err = spi_bus_add_device(strip->host_device, &device_interface_config, &strip->device_handle); + if (err != ESP_OK) { + ESP_LOGE(TAG, "spi_bus_add_device(): %s", esp_err_to_name(err)); + goto fail; + } + ESP_LOGI(TAG, "LED strip initialized"); +fail: + if (xSemaphoreGive(mutex) != pdTRUE) { + ESP_LOGE(TAG, "xSemaphoreGive(): failed"); + } +fail_without_give: + return err; +} +#endif + +#if HELPER_TARGET_IS_ESP8266 +static esp_err_t led_strip_spi_init_esp8266(led_strip_spi_t *strip) +{ + esp_err_t err; + spi_config_t spi_config; + spi_interface_t interface_config = { + + /* SPI mode 3, CPOL = 1, CPHA = 1 */ + .cpol = 1, + .cpha = 1, + .bit_tx_order = 0, + .bit_rx_order = 0, + .byte_tx_order = 0, + .byte_rx_order = 0, + .mosi_en = 1, + .miso_en = 0, + .cs_en = 0, + .reserved9 = 23, + }; + + if (xSemaphoreTake(mutex, MUTEX_TIMEOUT) != pdTRUE) { + err = ESP_FAIL; + ESP_LOGE(TAG, "xSemaphoreTake(): timeout"); + goto fail_without_give; + } + + strip->buf = malloc(LED_STRIP_SPI_BUFFER_SIZE(strip->length)); + if (strip->buf == NULL) { + ESP_LOGE(TAG, "malloc()"); + err = ESP_ERR_NO_MEM; + goto fail; + } + memset(strip->buf, 0, LED_STRIP_SPI_BUFFER_SIZE(strip->length)); + +#if CONFIG_LED_STRIP_SPI_USING_SK9822 + err = led_strip_spi_sk9822_buf_init(strip); + if (err != ESP_OK) { + ESP_LOGE(TAG, "led_strip_spi_sk9822_buf_init(): %s", esp_err_to_name(err)); + goto fail; + } +#endif + ESP_LOGI(TAG, "SPI buffer initialized"); + spi_config.interface = interface_config; + spi_config.mode = SPI_MASTER_MODE; + spi_config.clk_div = strip->clk_div; + spi_config.event_cb = NULL; + + err = spi_init(HSPI_HOST, &spi_config); + if (err != ESP_OK) { + ESP_LOGE(TAG, "spi_init(): %s", esp_err_to_name(err)); + goto fail; + } + ESP_LOGI(TAG, "SPI bus initialized"); + ESP_LOGI(TAG, "LED strip initialized"); +fail: + if (xSemaphoreGive(mutex) != pdTRUE) { + ESP_LOGE(TAG, "xSemaphoreGive(): failed"); + } +fail_without_give: + return err; +} +#endif + +esp_err_t led_strip_spi_init(led_strip_spi_t *strip) +{ +#if HELPER_TARGET_IS_ESP32 + return led_strip_spi_init_esp32(strip); +#elif HELPER_TARGET_IS_ESP8266 + return led_strip_spi_init_esp8266(strip); +#else +#error "Unknown target" +#endif +} + +esp_err_t led_strip_spi_free(led_strip_spi_t *strip) +{ + CHECK_ARG(strip); + + free(strip->buf); + return ESP_OK; +} + +#if HELPER_TARGET_IS_ESP32 +static esp_err_t led_strip_spi_flush_esp32(led_strip_spi_t *strip) +{ + esp_err_t err = ESP_FAIL; + spi_transaction_t* t; + + CHECK_ARG(strip); + if (!strip->transaction.tx_buffer) { + strip->transaction.tx_buffer = strip->buf; + } + strip->transaction.tx_buffer = strip->buf; + err = spi_device_queue_trans(strip->device_handle, &strip->transaction, portMAX_DELAY); + if (err != ESP_OK) { + ESP_LOGE(TAG, "spi_device_queue_trans(): %s", esp_err_to_name(err)); + goto fail; + } + err = spi_device_get_trans_result(strip->device_handle, &t, portMAX_DELAY); + if (err != ESP_OK) { + ESP_LOGE(TAG, "spi_device_get_trans_result(): %s", esp_err_to_name(err)); + goto fail; + } + err = ESP_OK; +fail: + return err; +} +#endif + +#if HELPER_TARGET_IS_ESP8266 + +#define ESP8266_SPI_MAX_DATA_LENGTH 64 // in bytes + +static esp_err_t led_strip_spi_flush_esp8266(led_strip_spi_t *strip) +{ + esp_err_t err = ESP_FAIL; + spi_trans_t trans = {0}; + int mosi_buffer_block_size, mosi_buffer_block_size_mod; + + CHECK_ARG(strip); + + /* XXX send ESP8266_SPI_MAX_DATA_LENGTH bytes data at a time. the + * documentation does not mention the limitation, but the SPI master + * driver complains: + * "spi: spi_master_trans(454): spi mosi must be shorter than 512 bits" */ + mosi_buffer_block_size = LED_STRIP_SPI_BUFFER_SIZE(strip->length) / ESP8266_SPI_MAX_DATA_LENGTH; + mosi_buffer_block_size_mod = LED_STRIP_SPI_BUFFER_SIZE(strip->length) % ESP8266_SPI_MAX_DATA_LENGTH; + + if (xSemaphoreTake(mutex, MUTEX_TIMEOUT) != pdTRUE) { + err = ESP_FAIL; + ESP_LOGE(TAG, "xSemaphoreTake(): timeout"); + goto fail_without_give; + } + + for (int i = 0; i < mosi_buffer_block_size; i++) { + trans.bits.mosi = ESP8266_SPI_MAX_DATA_LENGTH * 8; // bits, not bytes + trans.mosi = strip->buf + ESP8266_SPI_MAX_DATA_LENGTH * i; + err = spi_trans(HSPI_HOST, &trans); + if (err != ESP_OK) { + ESP_LOGE(TAG, "spi_trans(): %s", esp_err_to_name(err)); + goto fail; + } + } + if (mosi_buffer_block_size_mod > 0) { + trans.bits.mosi = mosi_buffer_block_size_mod * 8; // bits, not bytes + trans.mosi = strip->buf + ESP8266_SPI_MAX_DATA_LENGTH * mosi_buffer_block_size; + err = spi_trans(HSPI_HOST, &trans); + if (err != ESP_OK) { + ESP_LOGE(TAG, "spi_trans(): %s", esp_err_to_name(err)); + goto fail; + } + } +fail: + if (xSemaphoreGive(mutex) != pdTRUE) { + ESP_LOGE(TAG, "xSemaphoreGive(): failed"); + } +fail_without_give: + return err; +} +#endif +esp_err_t led_strip_spi_flush(led_strip_spi_t*strip) +{ +#if HELPER_TARGET_IS_ESP32 + return led_strip_spi_flush_esp32(strip); +#elif HELPER_TARGET_IS_ESP8266 + return led_strip_spi_flush_esp8266(strip); +#else +#error "Unknown target" +#endif +} + +esp_err_t led_strip_spi_set_pixel(led_strip_spi_t *strip, const int index, const rgb_t color) +{ + return led_strip_spi_set_pixel_brightness(strip, index, color, LED_STRIP_SPI_MAX_BRIGHTNESS); +} + +esp_err_t led_strip_spi_set_pixels(led_strip_spi_t*strip, const int start, size_t len, const rgb_t data) +{ + return led_strip_spi_set_pixels_brightness(strip, start, len, data, LED_STRIP_SPI_MAX_BRIGHTNESS); +} + +esp_err_t led_strip_spi_fill(led_strip_spi_t*strip, size_t start, size_t len, rgb_t color) +{ + return led_strip_spi_fill_brightness(strip, start, len, color, LED_STRIP_SPI_MAX_BRIGHTNESS); +} + +esp_err_t led_strip_spi_set_pixel_brightness(led_strip_spi_t *strip, const int index, const rgb_t color, const uint8_t brightness) +{ +#if CONFIG_LED_STRIP_SPI_USING_SK9822 + return led_strip_spi_set_pixel_sk9822(strip, index, color, brightness); +#endif + return ESP_ERR_NOT_SUPPORTED; +} + +esp_err_t led_strip_spi_set_pixels_brightness(led_strip_spi_t*strip, const int start, size_t len, const rgb_t data, const uint8_t brightness) +{ + esp_err_t err = ESP_FAIL; + + for (int i = 0; i < len; i++) { + err = led_strip_spi_set_pixel_brightness(strip, start + i, data, brightness); + if (err != ESP_OK) { + ESP_LOGE(TAG, "led_strip_spi_set_pixel(): %s", esp_err_to_name(err)); + goto fail; + } + } +fail: + return err; +} + +esp_err_t led_strip_spi_fill_brightness(led_strip_spi_t*strip, size_t start, size_t len, rgb_t color, const uint8_t brightness) +{ + CHECK_ARG(strip && len && start + len <= strip->length); + + for (size_t i = start; i < len; i++) { + CHECK(led_strip_spi_set_pixel_brightness(strip, i, color, brightness)); + } + return ESP_OK; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/led_strip_spi/led_strip_spi.h Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,197 @@ +/* + * MIT License + * + * Copyright (c) 2020 Ruslan V. Uss <unclerus@gmail.com> + * 2021 Tomoyuki Sakurai <y@rombik.org> + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +/** + * + * @file led_strip_spi.h + * @defgroup led_strip_spi led_strip_spi + * @{ + * + */ +#ifndef __LED_STRIP_SPI_H__ +#define __LED_STRIP_SPI_H__ + +#include <driver/gpio.h> +#include <esp_err.h> +#include <color.h> +#include <esp_idf_lib_helpers.h> + +#if HELPER_TARGET_IS_ESP32 +#include <driver/spi_master.h> +#include "led_strip_spi_esp32.h" +#define LED_STRIP_SPI_DEFAULT() LED_STRIP_SPI_DEFAULT_ESP32() ///< an alias of `LED_STRIP_SPI_DEFAULT_ESP32()` or `LED_STRIP_SPI_DEFAULT_ESP8266()` +typedef led_strip_spi_esp32_t led_strip_spi_t; +#endif + +#if HELPER_TARGET_IS_ESP8266 +#include <driver/spi.h> +#include "led_strip_spi_esp8266.h" +#define LED_STRIP_SPI_DEFAULT() LED_STRIP_SPI_DEFAULT_ESP8266() +typedef led_strip_spi_esp8266_t led_strip_spi_t; +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * * add LED_STRIP_SPI_USING_$NAME to Kconfig + * * define `LED_STRIP_SPI_BUFFER_SIZE(N_PIXEL)` that returns the required + * size of buffer for the $NAME. + */ + +#if defined(CONFIG_LED_STRIP_SPI_USING_SK9822) +#include "led_strip_spi_sk9822.h" +#else +#error "unknown LED type" +#endif + +/** + * Maximum brightness value for a pixel. + */ +#define LED_STRIP_SPI_MAX_BRIGHTNESS (100) + +/** + * @brief Setup the driver + * + * This method must be called before any other led_strip_spi methods + * @return `ESP_OK` on success + */ +esp_err_t led_strip_spi_install(); + +/** + * @brief Initialize LED strip and allocate buffer memory + * + * @param strip Descriptor of LED strip + * @return `ESP_OK` on success + */ +esp_err_t led_strip_spi_init(led_strip_spi_t*strip); + +/** + * @brief Free LED strip + * + * @param strip Descriptor of LED strip + * @return `ESP_OK` on success + */ +esp_err_t led_strip_spi_free(led_strip_spi_t *strip); + +/** + * @brief Send strip buffer to LEDs + * @param strip Descriptor of LED strip + * @return `ESP_OK` on success + */ +esp_err_t led_strip_spi_flush(led_strip_spi_t*strip); + +/** + * @brief Set color of single LED in strip. + * + * This function does not actually change colors of the LEDs. + * Call ::led_strip_spi_flush() to send buffer to the LEDs. + * + * @param strip Descriptor of LED strip + * @param num LED number, [0:strip.length - 1]. + * @param color RGB color + * @return `ESP_OK` on success + */ +esp_err_t led_strip_spi_set_pixel(led_strip_spi_t *strip, const int num, const rgb_t color); + +/** + * @brief Set color of single LED in strip. + * + * This function does not actually change colors of the LEDs. + * Call ::led_strip_spi_flush() to send buffer to the LEDs. + * + * @param strip Descriptor of LED strip + * @param num LED number, [0:strip.length - 1]. + * @param color RGB color + * @param brightness Brightness of the LED, [0:100]. + * @return `ESP_OK` on success + */ +esp_err_t led_strip_spi_set_pixel_brightness(led_strip_spi_t *strip, const int num, const rgb_t color, const uint8_t brightness); + +/** + * @brief Set colors of multiple LEDs + * + * This function does not actually change colors of the LEDs. + * Call ::led_strip_spi_flush() to send buffer to the LEDs. + * + * @param strip Descriptor of LED strip + * @param start First LED index, 0-based + * @param len Number of LEDs + * @param data Pointer to RGB data + * @return `ESP_OK` on success + */ +esp_err_t led_strip_spi_set_pixels(led_strip_spi_t*strip, const int start, size_t len, const rgb_t data); + +/** + * @brief Set colors of multiple LEDs + * + * This function does not actually change colors of the LEDs. + * Call ::led_strip_spi_flush() to send buffer to the LEDs. + * + * @param strip Descriptor of LED strip + * @param start First LED index, 0-based + * @param len Number of LEDs + * @param data Pointer to RGB data + * @param brightness Brightness of the LED, [0:100]. + * @return `ESP_OK` on success + */ +esp_err_t led_strip_spi_set_pixels_brightness(led_strip_spi_t*strip, const int start, size_t len, const rgb_t data, const uint8_t brightness); + +/** + * @brief Set multiple LEDs to the one color. + * + * This function does not actually change colors of the LEDs. + * Call ::led_strip_spi_flush() to send buffer to the LEDs. + * + * @param strip Descriptor of LED strip + * @param start First LED index, 0-based + * @param len Number of LEDs + * @param color RGB color + * @return `ESP_OK` on success + */ +esp_err_t led_strip_spi_fill(led_strip_spi_t*strip, size_t start, size_t len, rgb_t color); + +/** + * @brief Set multiple LEDs to the one color. + * + * This function does not actually change colors of the LEDs. + * Call ::led_strip_spi_flush() to send buffer to the LEDs. + * + * @param strip Descriptor of LED strip + * @param start First LED index, 0-based + * @param len Number of LEDs + * @param color RGB color + * @param brightness Brightness of the LEDs, [0:100]. + * @return `ESP_OK` on success + */ +esp_err_t led_strip_spi_fill_brightness(led_strip_spi_t*strip, size_t start, size_t len, rgb_t color, const uint8_t brightness); +#ifdef __cplusplus +} +#endif + +/**@}*/ + +#endif /* __LED_STRIP_SPI_H__ */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/led_strip_spi/led_strip_spi_esp32.h Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,109 @@ +/* + * MIT License + * + * Copyright (c) 2021 Tomoyuki Sakurai <y@rombik.org> + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +/** + * @file led_strip_spi_esp8266.h + * @defgroup led_strip_spi_esp32 led_strip_spi_esp32 + * @{ + */ + +#if !defined(__LED_STRIP_SPI_ESP32__H__) +#define __LED_STRIP_SPI_ESP32__H__ + +#include <esp_idf_lib_helpers.h> +#include <esp_idf_version.h> +#include <driver/spi_master.h> + +#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(4, 0, 0) +#define LED_STRIP_SPI_DEFAULT_HOST_DEVICE HSPI_HOST +#else +#define LED_STRIP_SPI_DEFAULT_HOST_DEVICE SPI2_HOST ///< Default is `SPI2_HOST` (`HSPI_HOST` if `esp-idf` version is v3.x). +#endif + +#if defined(CONFIG_IDF_TARGET_ESP32C3) +#define LED_STRIP_SPI_DEFAULT_MOSI_IO_NUM (7) +#define LED_STRIP_SPI_DEFAULT_SCLK_IO_NUM (6) +#else +#define LED_STRIP_SPI_DEFAULT_MOSI_IO_NUM (13) ///< GPIO pin number of `LED_STRIP_SPI_DEFAULT_HOST_DEVICE`'s MOSI (default is 13 for ESP32, 7 for ESP32C3) +#define LED_STRIP_SPI_DEFAULT_SCLK_IO_NUM (14) ///< GPIO pin number of `LED_STRIP_SPI_DEFAULT_HOST_DEVICE`'s SCLK (default is 14 for ESP32, 6 for ESP32C3) +#endif + +/** + * LED strip descriptor for ESP32-family. + */ +typedef struct +{ + void *buf; //< Pointer to the buffer + size_t length; //< Number of pixels + spi_host_device_t host_device; //< SPI host device name, such as `SPI2_HOST`. + int mosi_io_num; ///< GPIO number of SPI MOSI. + int sclk_io_num; ///< GPIO number of SPI SCLK. + int max_transfer_sz; ///< Maximum transfer size in bytes. Defaults to 4094 if 0. + int clock_speed_hz; ///< Clock speed in Hz. + int queue_size; ///< Queue size used by `spi_device_queue_trans()`. + spi_device_handle_t device_handle; ///< Device handle assigned by the driver. The caller must provdie this. + int dma_chan; ///< DMA channed to use. Either 1 or 2. + spi_transaction_t transaction; ///< SPI transaction used internally by the driver. +} led_strip_spi_esp32_t; + +/** + * Default DMA channel to use. Default is `SPI_DMA_CH_AUTO` for ESP-IDF v4.3 + * and newer, 1 for older versions. + */ + +#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(4, 3, 0) +#define LED_STRIP_SPI_DEFAULT_DMA_CHAN (1) +#else +#define LED_STRIP_SPI_DEFAULT_DMA_CHAN SPI_DMA_CH_AUTO +#endif +/** + * Macro to initialize ::led_strip_spi_esp32_t + * + * `buf`: `NULL`, + * `length`: 1, + * `host_device`: `LED_STRIP_SPI_DEFAULT_HOST_DEVICE`, + * `mosi_io_num`: `LED_STRIP_SPI_DEFAULT_MOSI_IO_NUM`, + * `max_transfer_sz`: 0, + * `clock_speed_hz`: 1000000, + * `queue_size`: 1, + * `device_handle`: `NULL`, + * `dma_chan`: 1 + */ +#define LED_STRIP_SPI_DEFAULT_ESP32() \ +{ \ + .buf = NULL, \ + .length = 1, \ + .host_device = LED_STRIP_SPI_DEFAULT_HOST_DEVICE, \ + .mosi_io_num = LED_STRIP_SPI_DEFAULT_MOSI_IO_NUM, \ + .sclk_io_num = LED_STRIP_SPI_DEFAULT_SCLK_IO_NUM, \ + .max_transfer_sz = 0, \ + .clock_speed_hz = 1000000, \ + .queue_size = 1, \ + .device_handle = NULL, \ + .dma_chan = LED_STRIP_SPI_DEFAULT_DMA_CHAN, \ +} + +/** @} */ + +#endif // __LED_STRIP_SPI_ESP32__H__
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/led_strip_spi/led_strip_spi_esp8266.h Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,63 @@ +/* + * MIT License + * + * Copyright (c) 2021 Tomoyuki Sakurai <y@rombik.org> + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#if !defined(__LED_STRIP_SPI_ESP8266__H__) +#define __LED_STRIP_SPI_ESP8266__H__ + +/** + * @file led_strip_spi_esp8266.h + * @defgroup led_strip_spi_esp8266 led_strip_spi_esp8266 + * + * @{ + */ + +#include <driver/spi.h> +#include "led_strip_spi_esp8266.h" + +/** + * @struct led_strip_spi_esp8266_t + * + * LED strip descriptor for ESP8266. + */ +typedef struct +{ + void *buf; ///< Pointer to the buffer. + size_t length; ///< Number of pixels. + spi_clk_div_t clk_div; ///< Value of `clk_div`, such as `SPI_2MHz_DIV`. See available values in `${IDF_PATH}/components/esp8266/include/driver/spi.h`. +} led_strip_spi_esp8266_t; + +/** + * @brief A macro to initialize led_strip_spi_esp8266_t. + * + * `length`: 1 `clk_div`: SPI_2MHz_DIV + */ +#define LED_STRIP_SPI_DEFAULT_ESP8266() \ +{ \ + .length = 1, \ + .clk_div = SPI_2MHz_DIV, \ +} + +/** @} */ + +#endif // __LED_STRIP_SPI_ESP8266__H__
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/led_strip_spi/led_strip_spi_sk9822.c Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,55 @@ +/* + * MIT License + * + * Copyright (c) 2021 Tomoyuki Sakurai <y@rombik.org> + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include <esp_err.h> +#include "led_strip_spi.h" +#include "led_strip_spi_sk9822.h" + +esp_err_t led_strip_spi_set_pixel_sk9822(led_strip_spi_t *strip, size_t num, rgb_t color, uint8_t brightness) +{ + int index = (num + 1) * 4; + uint8_t cooked_brightness = 0; + /* Don't divided the range equal instead, the bottom 10 % is actually 0 brightness + then every 3 percent after that increase the brightness level by 1 */ + if (brightness >= 100) { + cooked_brightness = 31; + } else if (brightness > 7){ + cooked_brightness = (brightness - 7) / 3; + } + ((uint8_t *)strip->buf)[index ] = LED_STRIP_SPI_FRAME_SK9822_LED_MSB3 | + (cooked_brightness & ((1 << LED_STRIP_SPI_FRAME_SK9822_LED_BRIGHTNESS_BITS) - 1)); + ((uint8_t *)strip->buf)[index + 1] = color.b; + ((uint8_t *)strip->buf)[index + 2] = color.g; + ((uint8_t *)strip->buf)[index + 3] = color.r; + return ESP_OK; +} + +esp_err_t led_strip_spi_sk9822_buf_init(led_strip_spi_t *strip) +{ + /* set mandatory bits in all LED frames */ + for (int i = 1; i <= strip->length; i++) { + ((uint8_t *)strip->buf)[i * 4] = LED_STRIP_SPI_FRAME_SK9822_LED_MSB3; + } + return ESP_OK; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/led_strip_spi/led_strip_spi_sk9822.h Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,96 @@ +/* + * MIT License + * + * Copyright (c) 2020 Ruslan V. Uss <unclerus@gmail.com> + * 2021 Tomoyuki Sakurai <y@rombik.org> + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +/** + * @file led_strip_spi_sk9822.h + * @defgroup led_strip_spi_sk9822 led_strip_spi_sk9822 + * @{ + * + * Functions and macros for SK9822 LED strips. + * + * SPI data frame consists of: + * + * - A start frame of 32 zero bits (<0x00> <0x00> <0x00> <0x00>). + * - 32 bit LED frames for each LED in the string (<0xE0+brightness> <blue> + * <green> <red>). + * - A SK9822 reset frame of 32 zero bits (<0x00> <0x00> <0x00> <0x00>). + * - An end frame consisting of at least (n/2) bits of 1, where n is the + * number of LEDs in the string. + * + * For the details, see + * [SK9822 – a clone of the APA102?](https://cpldcpu.wordpress.com/2016/12/13/sk9822-a-clone-of-the-apa102/) + * and + * [Understanding the APA102 “Superled”](https://cpldcpu.wordpress.com/2014/11/30/understanding-the-apa102-superled/). + * + */ +#if !defined(__LED_STRIP_SPI_SK9822_H__) +#define __LED_STRIP_SPI_SK9822_H__ + +#include <esp_err.h> + +#ifdef __cplusplus +extern "C" { +#endif + +#define LED_STRIP_SPI_FRAME_SK9822_START_SIZE (4) ///< The size in bytes of start frame. +#define LED_STRIP_SPI_FRAME_SK9822_LED_SIZE (4) ///< The size in bytes of each LED frame. +#define LED_STRIP_SPI_FRAME_SK9822_LEDS_SIZE(N_PIXEL) (LED_STRIP_SPI_FRAME_SK9822_LED_SIZE * N_PIXEL) ///< Total size in bytes of all LED frames in a strip. `N_PIXEL` is the number of pixels in the strip. +#define LED_STRIP_SPI_FRAME_SK9822_RESET_SIZE (4) ///< The size in bytes of reset frame. +#define LED_STRIP_SPI_FRAME_SK9822_END_SIZE(N_PIXEL) ((N_PIXEL / 16) + 1) ///< The size in bytes of the last frame. `N_PIXEL` is the number of pixels in the strip. + +#define LED_STRIP_SPI_FRAME_SK9822_LED_MSB3 (0xE0) ///< A magic number of [31:29] in LED frames. The bits must be 1 (APA102, SK9822) + +#define LED_STRIP_SPI_FRAME_SK9822_LED_BRIGHTNESS_BITS (5) ///< Number of bits used to describe the brightness of the LED + +#define LED_STRIP_SPI_BUFFER_SIZE(N_PIXEL) (\ + LED_STRIP_SPI_FRAME_SK9822_START_SIZE + \ + LED_STRIP_SPI_FRAME_SK9822_LEDS_SIZE(N_PIXEL) + \ + LED_STRIP_SPI_FRAME_SK9822_RESET_SIZE + \ + LED_STRIP_SPI_FRAME_SK9822_END_SIZE(N_PIXEL)) ///< A macro to caliculate required size of buffer. `N_PIXEL` is the number of pixels in the strip. + +/** + * @brief Initialize the buffer of SK9822 strip. + * @param[in] strip LED strip descriptor to initialize + * @return `ESP_OK` on success + */ +esp_err_t led_strip_spi_sk9822_buf_init(led_strip_spi_t *strip); + +/** + * @brief Set color of a pixel of SK9822 strip. + * @param[in] strip LED strip descriptor. + * @param[in] num Index of the LED pixel (zero-based). + * @param[in] color The color to set. + * @param[in] brightness The brightness to set, [0:100]. + * @return `ESP_OK` on success. + */ +esp_err_t led_strip_spi_set_pixel_sk9822(led_strip_spi_t *strip, size_t num, rgb_t color, uint8_t brightness); + +#ifdef __cplusplus +} +#endif + +/**@}*/ + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/lib8tion/.eil.yml Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,20 @@ +--- +components: + - name: lib8tion + description: Math functions specifically designed for LED programming + group: common + groups: [] + code_owners: UncleRus + depends: [] + thread_safe: N/A + targets: + - name: esp32 + - name: esp8266 + - name: esp32s2 + - name: esp32c3 + licenses: + - name: MIT + copyrights: + - author: + name: FastLED + year: 2013
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/lib8tion/CMakeLists.txt Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,11 @@ +if(${IDF_VERSION_MAJOR} STREQUAL 4 AND ${IDF_VERSION_MINOR} STREQUAL 1 AND ${IDF_VERSION_PATCH} STREQUAL 3) + set(req) +else() + set(req esp_timer) +endif() + +idf_component_register( + INCLUDE_DIRS . + SRCS lib8tion.c + REQUIRES ${req} +)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/lib8tion/LICENSE Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2013 FastLED + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/lib8tion/component.mk Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,1 @@ +COMPONENT_ADD_INCLUDEDIRS = .
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/lib8tion/lib8tion.c Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,26 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2013 FastLED + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "lib8tion.h" + +uint16_t rand16seed;
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/lib8tion/lib8tion.h Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,772 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2013 FastLED + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef __INC_LIB8TION_H +#define __INC_LIB8TION_H + +/* + + Fast, efficient 8-bit math functions specifically + designed for high-performance LED programming. + + Included are: + + - Saturating unsigned 8-bit add and subtract. + Instead of wrapping around if an overflow occurs, + these routines just 'clamp' the output at a maxumum + of 255, or a minimum of 0. Useful for adding pixel + values. E.g., qadd8(200, 100) = 255. + + qadd8(i, j) == MIN((i + j), 0xFF) + qsub8(i, j) == MAX((i - j), 0) + + - Saturating signed 8-bit ("7-bit") add. + qadd7(i, j) == MIN((i + j), 0x7F) + + + - Scaling (down) of unsigned 8- and 16- bit values. + Scaledown value is specified in 1/256ths. + scale8(i, sc) == (i * sc) / 256 + scale16by8(i, sc) == (i * sc) / 256 + + Example: scaling a 0-255 value down into a + range from 0-99: + downscaled = scale8(originalnumber, 100); + + A special version of scale8 is provided for scaling + LED brightness values, to make sure that they don't + accidentally scale down to total black at low + dimming levels, since that would look wrong: + scale8_video(i, sc) = ((i * sc) / 256) +? 1 + + Example: reducing an LED brightness by a + dimming factor: + new_bright = scale8_video(orig_bright, dimming); + + + - Fast 8- and 16- bit unsigned random numbers. + Significantly faster than Arduino random(), but + also somewhat less random. You can add entropy. + random8() == random from 0..255 + random8(n) == random from 0..(N-1) + random8(n, m) == random from N..(M-1) + + random16() == random from 0..65535 + random16(n) == random from 0..(N-1) + random16(n, m) == random from N..(M-1) + + random16_set_seed(k) == seed = k + random16_add_entropy(k) == seed += k + + + - Absolute value of a signed 8-bit value. + abs8(i) == abs(i) + + + - 8-bit math operations which return 8-bit values. + These are provided mostly for completeness, + not particularly for performance. + mul8(i, j) == (i * j) & 0xFF + add8(i, j) == (i + j) & 0xFF + sub8(i, j) == (i - j) & 0xFF + + + - Fast 16-bit approximations of sin and cos. + Input angle is a uint16_t from 0-65535. + Output is a signed int16_t from -32767 to 32767. + sin16(x) == sin((x/32768.0) * pi) * 32767 + cos16(x) == cos((x/32768.0) * pi) * 32767 + Accurate to more than 99% in all cases. + + - Fast 8-bit approximations of sin and cos. + Input angle is a uint8_t from 0-255. + Output is an UNsigned uint8_t from 0 to 255. + sin8(x) == (sin((x/128.0) * pi) * 128) + 128 + cos8(x) == (cos((x/128.0) * pi) * 128) + 128 + Accurate to within about 2%. + + + - Fast 8-bit "easing in/out" function. + ease8InOutCubic(x) == 3(x^i) - 2(x^3) + ease8InOutApprox(x) == + faster, rougher, approximation of cubic easing + ease8InOutQuad(x) == quadratic (vs cubic) easing + + - Cubic, Quadratic, and Triangle wave functions. + Input is a uint8_t representing phase withing the wave, + similar to how sin8 takes an angle 'theta'. + Output is a uint8_t representing the amplitude of + the wave at that point. + cubicwave8(x) + quadwave8(x) + triwave8(x) + + - Square root for 16-bit integers. About three times + faster and five times smaller than Arduino's built-in + generic 32-bit sqrt routine. + sqrt16(uint16_t x) == sqrt(x) + + - Dimming and brightening functions for 8-bit + light values. + dim8_video(x) == scale8_video(x, x) + dim8_raw(x) == scale8(x, x) + dim8_lin(x) == (x<128) ? ((x+1)/2) : scale8(x,x) + brighten8_video(x) == 255 - dim8_video(255 - x) + brighten8_raw(x) == 255 - dim8_raw(255 - x) + brighten8_lin(x) == 255 - dim8_lin(255 - x) + The dimming functions in particular are suitable + for making LED light output appear more 'linear'. + + + - Linear interpolation between two values, with the + fraction between them expressed as an 8- or 16-bit + fixed point fraction (fract8 or fract16). + lerp8by8( fromU8, toU8, fract8) + lerp16by8( fromU16, toU16, fract8) + lerp15by8( fromS16, toS16, fract8) + == from + ((to - from) * fract8) / 256) + lerp16by16(fromU16, toU16, fract16) + == from + ((to - from) * fract16) / 65536) + map8(in, rangeStart, rangeEnd) + == map(in, 0, 255, rangeStart, rangeEnd); + + - Beat generators which return sine or sawtooth + waves in a specified number of Beats Per Minute. + Sine wave beat generators can specify a low and + high range for the output. Sawtooth wave beat + generators always range 0-255 or 0-65535. + beatsin8(BPM, low8, high8) + = (sine(beatphase) * (high8-low8)) + low8 + beatsin16(BPM, low16, high16) + = (sine(beatphase) * (high16-low16)) + low16 + beatsin88(BPM88, low16, high16) + = (sine(beatphase) * (high16-low16)) + low16 + beat8(BPM) = 8-bit repeating sawtooth wave + beat16(BPM) = 16-bit repeating sawtooth wave + beat88(BPM88) = 16-bit repeating sawtooth wave + BPM is beats per minute in either simple form + e.g. 120, or Q8.8 fixed-point form. + BPM88 is beats per minute in ONLY Q8.8 fixed-point + form. + + Lib8tion is pronounced like 'libation': lie-BAY-shun + + */ + +#include <stdint.h> +#include <string.h> +#include <esp_timer.h> + +#define LIB8STATIC __attribute__ ((unused)) static inline +#define LIB8STATIC_ALWAYS_INLINE __attribute__ ((always_inline)) static inline + +///@defgroup lib8tion Fast math functions +///A variety of functions for working with numbers. +///@{ + +/////////////////////////////////////////////////////////////////////// +// +// typdefs for fixed-point fractional types. +// +// sfract7 should be interpreted as signed 128ths. +// fract8 should be interpreted as unsigned 256ths. +// sfract15 should be interpreted as signed 32768ths. +// fract16 should be interpreted as unsigned 65536ths. +// +// Example: if a fract8 has the value "64", that should be interpreted +// as 64/256ths, or one-quarter. +// +// +// fract8 range is 0 to 0.99609375 +// in steps of 0.00390625 +// +// sfract7 range is -0.9921875 to 0.9921875 +// in steps of 0.0078125 +// +// fract16 range is 0 to 0.99998474121 +// in steps of 0.00001525878 +// +// sfract15 range is -0.99996948242 to 0.99996948242 +// in steps of 0.00003051757 +// + +/// ANSI unsigned short _Fract. range is 0 to 0.99609375 +/// in steps of 0.00390625 +typedef uint8_t fract8; ///< ANSI: unsigned short _Fract + +/// ANSI: signed short _Fract. range is -0.9921875 to 0.9921875 +/// in steps of 0.0078125 +typedef int8_t sfract7; ///< ANSI: signed short _Fract + +/// ANSI: unsigned _Fract. range is 0 to 0.99998474121 +/// in steps of 0.00001525878 +typedef uint16_t fract16; ///< ANSI: unsigned _Fract + +/// ANSI: signed _Fract. range is -0.99996948242 to 0.99996948242 +/// in steps of 0.00003051757 +typedef int16_t sfract15; ///< ANSI: signed _Fract + +// accumXY types should be interpreted as X bits of integer, +// and Y bits of fraction. +// E.g., accum88 has 8 bits of int, 8 bits of fraction + +typedef uint16_t accum88; ///< ANSI: unsigned short _Accum. 8 bits int, 8 bits fraction +typedef int16_t saccum78; ///< ANSI: signed short _Accum. 7 bits int, 8 bits fraction +typedef uint32_t accum1616; ///< ANSI: signed _Accum. 16 bits int, 16 bits fraction +typedef int32_t saccum1516; ///< ANSI: signed _Accum. 15 bits int, 16 bits fraction +typedef uint16_t accum124; ///< no direct ANSI counterpart. 12 bits int, 4 bits fraction +typedef int32_t saccum114; ///< no direct ANSI counterpart. 1 bit int, 14 bits fraction + +/// typedef for IEEE754 "binary32" float type internals +typedef union +{ + uint32_t i; + float f; + struct + { + uint32_t mantissa :23; + uint32_t exponent :8; + uint32_t signbit :1; + }; + struct + { + uint32_t mant7 :7; + uint32_t mant16 :16; + uint32_t exp_ :8; + uint32_t sb_ :1; + }; + struct + { + uint32_t mant_lo8 :8; + uint32_t mant_hi16_exp_lo1 :16; + uint32_t sb_exphi7 :8; + }; +} IEEE754binary32_t; + +#include "lib8tion/math8.h" +#include "lib8tion/scale8.h" +#include "lib8tion/random8.h" +#include "lib8tion/trig8.h" + +/////////////////////////////////////////////////////////////////////// +// +// float-to-fixed and fixed-to-float conversions +// +// Note that anything involving a 'float' on AVR will be slower. + +/// sfract15ToFloat: conversion from sfract15 fixed point to +/// IEEE754 32-bit float. +LIB8STATIC float sfract15ToFloat(sfract15 y) +{ + return y / 32768.0; +} + +/// conversion from IEEE754 float in the range (-1,1) +/// to 16-bit fixed point. Note that the extremes of +/// one and negative one are NOT representable. The +/// representable range is basically +LIB8STATIC sfract15 floatToSfract15(float f) +{ + return f * 32768.0; +} + +/////////////////////////////////////////////////////////////////////// +// +// linear interpolation, such as could be used for Perlin noise, etc. +// + +// A note on the structure of the lerp functions: +// The cases for b>a and b<=a are handled separately for +// speed: without knowing the relative order of a and b, +// the value (a-b) might be overflow the width of a or b, +// and have to be promoted to a wider, slower type. +// To avoid that, we separate the two cases, and are able +// to do all the math in the same width as the arguments, +// which is much faster and smaller on AVR. + +/// linear interpolation between two unsigned 8-bit values, +/// with 8-bit fraction +LIB8STATIC uint8_t lerp8by8(uint8_t a, uint8_t b, fract8 frac) +{ + uint8_t result; + if (b > a) + { + uint8_t delta = b - a; + uint8_t scaled = scale8(delta, frac); + result = a + scaled; + } + else + { + uint8_t delta = a - b; + uint8_t scaled = scale8(delta, frac); + result = a - scaled; + } + return result; +} + +/// linear interpolation between two unsigned 16-bit values, +/// with 16-bit fraction +LIB8STATIC uint16_t lerp16by16(uint16_t a, uint16_t b, fract16 frac) +{ + uint16_t result; + if (b > a) + { + uint16_t delta = b - a; + uint16_t scaled = scale16(delta, frac); + result = a + scaled; + } + else + { + uint16_t delta = a - b; + uint16_t scaled = scale16(delta, frac); + result = a - scaled; + } + return result; +} + +/// linear interpolation between two unsigned 16-bit values, +/// with 8-bit fraction +LIB8STATIC uint16_t lerp16by8(uint16_t a, uint16_t b, fract8 frac) +{ + uint16_t result; + if (b > a) + { + uint16_t delta = b - a; + uint16_t scaled = scale16by8(delta, frac); + result = a + scaled; + } + else + { + uint16_t delta = a - b; + uint16_t scaled = scale16by8(delta, frac); + result = a - scaled; + } + return result; +} + +/// linear interpolation between two signed 15-bit values, +/// with 8-bit fraction +LIB8STATIC int16_t lerp15by8(int16_t a, int16_t b, fract8 frac) +{ + int16_t result; + if (b > a) + { + uint16_t delta = b - a; + uint16_t scaled = scale16by8(delta, frac); + result = a + scaled; + } + else + { + uint16_t delta = a - b; + uint16_t scaled = scale16by8(delta, frac); + result = a - scaled; + } + return result; +} + +/// linear interpolation between two signed 15-bit values, +/// with 8-bit fraction +LIB8STATIC int16_t lerp15by16(int16_t a, int16_t b, fract16 frac) +{ + int16_t result; + if (b > a) + { + uint16_t delta = b - a; + uint16_t scaled = scale16(delta, frac); + result = a + scaled; + } + else + { + uint16_t delta = a - b; + uint16_t scaled = scale16(delta, frac); + result = a - scaled; + } + return result; +} + +/// map8: map from one full-range 8-bit value into a narrower +/// range of 8-bit values, possibly a range of hues. +/// +/// E.g. map myValue into a hue in the range blue..purple..pink..red +/// hue = map8(myValue, HUE_BLUE, HUE_RED); +/// +/// Combines nicely with the waveform functions (like sin8, etc) +/// to produce continuous hue gradients back and forth: +/// +/// hue = map8(sin8(myValue), HUE_BLUE, HUE_RED); +/// +/// Mathematically simiar to lerp8by8, but arguments are more +/// like Arduino's "map"; this function is similar to +/// +/// map(in, 0, 255, rangeStart, rangeEnd) +/// +/// but faster and specifically designed for 8-bit values. +LIB8STATIC uint8_t map8(uint8_t in, uint8_t rangeStart, uint8_t rangeEnd) +{ + uint8_t rangeWidth = rangeEnd - rangeStart; + uint8_t out = scale8(in, rangeWidth); + out += rangeStart; + return out; +} + +/////////////////////////////////////////////////////////////////////// +// +// easing functions; see http://easings.net +// + +/// ease8InOutQuad: 8-bit quadratic ease-in / ease-out function +/// Takes around 13 cycles on AVR +LIB8STATIC uint8_t ease8InOutQuad(uint8_t i) +{ + uint8_t j = i; + if (j & 0x80) + { + j = 255 - j; + } + uint8_t jj = scale8(j, j); + uint8_t jj2 = jj << 1; + if (i & 0x80) + { + jj2 = 255 - jj2; + } + return jj2; +} + +/// ease16InOutQuad: 16-bit quadratic ease-in / ease-out function +// C implementation at this point +LIB8STATIC uint16_t ease16InOutQuad(uint16_t i) +{ + uint16_t j = i; + if (j & 0x8000) + { + j = 65535 - j; + } + uint16_t jj = scale16(j, j); + uint16_t jj2 = jj << 1; + if (i & 0x8000) + { + jj2 = 65535 - jj2; + } + return jj2; +} + +/// ease8InOutCubic: 8-bit cubic ease-in / ease-out function +/// Takes around 18 cycles on AVR +LIB8STATIC fract8 ease8InOutCubic(fract8 i) +{ + uint8_t ii = scale8(i, i); + uint8_t iii = scale8(ii, i); + + uint16_t r1 = (3 * (uint16_t) (ii)) - (2 * (uint16_t) (iii)); + + /* the code generated for the above *'s automatically + cleans up R1, so there's no need to explicitily call + cleanup_R1(); */ + + uint8_t result = r1; + + // if we got "256", return 255: + if (r1 & 0x100) + { + result = 255; + } + return result; +} + +/// ease8InOutApprox: fast, rough 8-bit ease-in/ease-out function +/// shaped approximately like 'ease8InOutCubic', +/// it's never off by more than a couple of percent +/// from the actual cubic S-curve, and it executes +/// more than twice as fast. Use when the cycles +/// are more important than visual smoothness. +/// Asm version takes around 7 cycles on AVR. +LIB8STATIC fract8 ease8InOutApprox(fract8 i) +{ + if (i < 64) + { + // start with slope 0.5 + i /= 2; + } + else if (i > (255 - 64)) + { + // end with slope 0.5 + i = 255 - i; + i /= 2; + i = 255 - i; + } + else + { + // in the middle, use slope 192/128 = 1.5 + i -= 64; + i += (i / 2); + i += 32; + } + + return i; +} + +/// triwave8: triangle (sawtooth) wave generator. Useful for +/// turning a one-byte ever-increasing value into a +/// one-byte value that oscillates up and down. +/// +/// input output +/// 0..127 0..254 (positive slope) +/// 128..255 254..0 (negative slope) +/// +/// On AVR this function takes just three cycles. +/// +LIB8STATIC uint8_t triwave8(uint8_t in) +{ + if (in & 0x80) + in = 255 - in; + return in << 1; +} + +// quadwave8 and cubicwave8: S-shaped wave generators (like 'sine'). +// Useful for turning a one-byte 'counter' value into a +// one-byte oscillating value that moves smoothly up and down, +// with an 'acceleration' and 'deceleration' curve. +// +// These are even faster than 'sin8', and have +// slightly different curve shapes. +// + +/// quadwave8: quadratic waveform generator. Spends just a little more +/// time at the limits than 'sine' does. +LIB8STATIC uint8_t quadwave8(uint8_t in) +{ + return ease8InOutQuad(triwave8(in)); +} + +/// cubicwave8: cubic waveform generator. Spends visibly more time +/// at the limits than 'sine' does. +LIB8STATIC uint8_t cubicwave8(uint8_t in) +{ + return ease8InOutCubic(triwave8(in)); +} + +/// squarewave8: square wave generator. Useful for +/// turning a one-byte ever-increasing value +/// into a one-byte value that is either 0 or 255. +/// The width of the output 'pulse' is +/// determined by the pulsewidth argument: +/// +///~~~ +/// If pulsewidth is 255, output is always 255. +/// If pulsewidth < 255, then +/// if input < pulsewidth then output is 255 +/// if input >= pulsewidth then output is 0 +///~~~ +/// +/// the output looking like: +/// +///~~~ +/// 255 +--pulsewidth--+ +/// . | | +/// 0 0 +--------(256-pulsewidth)-------- +///~~~ +/// +/// @param in +/// @param pulsewidth +/// @returns square wave output +LIB8STATIC uint8_t squarewave8(uint8_t in, uint8_t pulsewidth) +{ + return in < pulsewidth || pulsewidth == 255 ? 255 : 0; +} + +// Beat generators - These functions produce waves at a given +// number of 'beats per minute'. Internally, they use +// the Arduino function 'millis' to track elapsed time. +// Accuracy is a bit better than one part in a thousand. +// +// beat8(BPM) returns an 8-bit value that cycles 'BPM' times +// per minute, rising from 0 to 255, resetting to zero, +// rising up again, etc.. The output of this function +// is suitable for feeding directly into sin8, and cos8, +// triwave8, quadwave8, and cubicwave8. +// beat16(BPM) returns a 16-bit value that cycles 'BPM' times +// per minute, rising from 0 to 65535, resetting to zero, +// rising up again, etc. The output of this function is +// suitable for feeding directly into sin16 and cos16. +// beat88(BPM88) is the same as beat16, except that the BPM88 argument +// MUST be in Q8.8 fixed point format, e.g. 120BPM must +// be specified as 120*256 = 30720. +// beatsin8(BPM, uint8_t low, uint8_t high) returns an 8-bit value that +// rises and falls in a sine wave, 'BPM' times per minute, +// between the values of 'low' and 'high'. +// beatsin16(BPM, uint16_t low, uint16_t high) returns a 16-bit value +// that rises and falls in a sine wave, 'BPM' times per +// minute, between the values of 'low' and 'high'. +// beatsin88(BPM88, ...) is the same as beatsin16, except that the +// BPM88 argument MUST be in Q8.8 fixed point format, +// e.g. 120BPM must be specified as 120*256 = 30720. +// +// BPM can be supplied two ways. The simpler way of specifying BPM is as +// a simple 8-bit integer from 1-255, (e.g., "120"). +// The more sophisticated way of specifying BPM allows for fractional +// "Q8.8" fixed point number (an 'accum88') with an 8-bit integer part and +// an 8-bit fractional part. The easiest way to construct this is to multiply +// a floating point BPM value (e.g. 120.3) by 256, (e.g. resulting in 30796 +// in this case), and pass that as the 16-bit BPM argument. +// "BPM88" MUST always be specified in Q8.8 format. +// +// Originally designed to make an entire animation project pulse with brightness. +// For that effect, add this line just above your existing call to "FastLED.show()": +// +// uint8_t bright = beatsin8(60 /*BPM*/, 192 /*dimmest*/, 255 /*brightest*/)); +// FastLED.setBrightness(bright); +// FastLED.show(); +// +// The entire animation will now pulse between brightness 192 and 255 once per second. + +#define GET_MILLIS() (esp_timer_get_time() / 1000) + +/// beat16 generates a 16-bit 'sawtooth' wave at a given BPM, +/// with BPM specified in Q8.8 fixed-point format; e.g. +/// for this function, 120 BPM MUST BE specified as +/// 120*256 = 30720. +/// If you just want to specify "120", use beat16 or beat8. +LIB8STATIC uint16_t beat88(accum88 beats_per_minute_88, uint32_t timebase) +{ + // BPM is 'beats per minute', or 'beats per 60000ms'. + // To avoid using the (slower) division operator, we + // want to convert 'beats per 60000ms' to 'beats per 65536ms', + // and then use a simple, fast bit-shift to divide by 65536. + // + // The ratio 65536:60000 is 279.620266667:256; we'll call it 280:256. + // The conversion is accurate to about 0.05%, more or less, + // e.g. if you ask for "120 BPM", you'll get about "119.93". + return (((GET_MILLIS()) - timebase) * beats_per_minute_88 * 280) >> 16; +} + +/// beat16 generates a 16-bit 'sawtooth' wave at a given BPM +LIB8STATIC uint16_t beat16(accum88 beats_per_minute, uint32_t timebase) +{ + // Convert simple 8-bit BPM's to full Q8.8 accum88's if needed + if (beats_per_minute < 256) + beats_per_minute <<= 8; + return beat88(beats_per_minute, timebase); +} + +/// beat8 generates an 8-bit 'sawtooth' wave at a given BPM +LIB8STATIC uint8_t beat8(accum88 beats_per_minute, uint32_t timebase) +{ + return beat16(beats_per_minute, timebase) >> 8; +} + +/// beatsin88 generates a 16-bit sine wave at a given BPM, +/// that oscillates within a given range. +/// For this function, BPM MUST BE SPECIFIED as +/// a Q8.8 fixed-point value; e.g. 120BPM must be +/// specified as 120*256 = 30720. +/// If you just want to specify "120", use beatsin16 or beatsin8. +LIB8STATIC uint16_t beatsin88(accum88 beats_per_minute_88, uint16_t lowest, uint16_t highest, uint32_t timebase, uint16_t phase_offset) +{ + uint16_t beat = beat88(beats_per_minute_88, timebase); + uint16_t beatsin = (sin16(beat + phase_offset) + 32768); + uint16_t rangewidth = highest - lowest; + uint16_t scaledbeat = scale16(beatsin, rangewidth); + uint16_t result = lowest + scaledbeat; + return result; +} + +/// beatsin16 generates a 16-bit sine wave at a given BPM, +/// that oscillates within a given range. +LIB8STATIC uint16_t beatsin16(accum88 beats_per_minute, uint16_t lowest, uint16_t highest, uint32_t timebase, uint16_t phase_offset) +{ + uint16_t beat = beat16(beats_per_minute, timebase); + uint16_t beatsin = (sin16(beat + phase_offset) + 32768); + uint16_t rangewidth = highest - lowest; + uint16_t scaledbeat = scale16(beatsin, rangewidth); + uint16_t result = lowest + scaledbeat; + return result; +} + +/// beatsin8 generates an 8-bit sine wave at a given BPM, +/// that oscillates within a given range. +LIB8STATIC uint8_t beatsin8(accum88 beats_per_minute, uint8_t lowest, uint8_t highest, uint32_t timebase, uint8_t phase_offset) +{ + uint8_t beat = beat8(beats_per_minute, timebase); + uint8_t beatsin = sin8(beat + phase_offset); + uint8_t rangewidth = highest - lowest; + uint8_t scaledbeat = scale8(beatsin, rangewidth); + uint8_t result = lowest + scaledbeat; + return result; +} + +/// Return the current seconds since boot in a 16-bit value. Used as part of the +/// "every N time-periods" mechanism +LIB8STATIC uint16_t seconds16() +{ + uint32_t ms = GET_MILLIS(); + uint16_t s16; + s16 = ms / 1000; + return s16; +} + +/// Return the current minutes since boot in a 16-bit value. Used as part of the +/// "every N time-periods" mechanism +LIB8STATIC uint16_t minutes16() +{ + uint32_t ms = GET_MILLIS(); + uint16_t m16; + m16 = (ms / (60000L)) & 0xFFFF; + return m16; +} + +/// Return the current hours since boot in an 8-bit value. Used as part of the +/// "every N time-periods" mechanism +LIB8STATIC uint8_t hours8() +{ + uint32_t ms = GET_MILLIS(); + uint8_t h8; + h8 = (ms / (3600000L)) & 0xFF; + return h8; +} + +/// Helper routine to divide a 32-bit value by 1024, returning +/// only the low 16 bits. You'd think this would be just +/// result = (in32 >> 10) & 0xFFFF; +/// and on ARM, that's what you want and all is well. +/// Used to convert millis to 'binary seconds' aka bseconds: +/// one bsecond == 1024 millis. +LIB8STATIC uint16_t div1024_32_16(uint32_t in32) +{ + uint16_t out16 = (in32 >> 10) & 0xFFFF; + return out16; +} + +/// bseconds16 returns the current time-since-boot in +/// "binary seconds", which are actually 1024/1000 of a +/// second long. +LIB8STATIC uint16_t bseconds16() +{ + uint32_t ms = GET_MILLIS(); + uint16_t s16; + s16 = div1024_32_16(ms); + return s16; +} + +///@} + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/lib8tion/lib8tion/math8.h Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,291 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2013 FastLED + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef __INC_LIB8TION_MATH_H +#define __INC_LIB8TION_MATH_H + +///@ingroup lib8tion + +///@defgroup Math Basic math operations +/// Fast, efficient 8-bit math functions specifically +/// designed for high-performance LED programming. +///@{ + +/// add one byte to another, saturating at 0xFF +/// @param i - first byte to add +/// @param j - second byte to add +/// @returns the sum of i & j, capped at 0xFF +LIB8STATIC_ALWAYS_INLINE uint8_t qadd8(uint8_t i, uint8_t j) +{ + unsigned int t = i + j; + if (t > 255) + t = 255; + return t; +} + +/// Add one byte to another, saturating at 0x7F and -0x80 +/// @param i - first byte to add +/// @param j - second byte to add +/// @returns the sum of i & j, capped at 0xFF +LIB8STATIC_ALWAYS_INLINE int8_t qadd7(int8_t i, int8_t j) +{ + int16_t t = i + j; + if (t > 127) + t = 127; + else if (t < -128) + t = -127; + return t; +} + +/// subtract one byte from another, saturating at 0x00 +/// @returns i - j with a floor of 0 +LIB8STATIC_ALWAYS_INLINE uint8_t qsub8(uint8_t i, uint8_t j) +{ + int t = i - j; + if (t < 0) + t = 0; + return t; +} + +/// add one byte to another, with one byte result +LIB8STATIC_ALWAYS_INLINE uint8_t add8(uint8_t i, uint8_t j) +{ + int t = i + j; + return t; +} + +/// add one byte to two bytes, with two bytes result +LIB8STATIC_ALWAYS_INLINE uint16_t add8to16(uint8_t i, uint16_t j) +{ + uint16_t t = i + j; + return t; +} + +/// subtract one byte from another, 8-bit result +LIB8STATIC_ALWAYS_INLINE uint8_t sub8(uint8_t i, uint8_t j) +{ + int t = i - j; + return t; +} + +/// Calculate an integer average of two unsigned +/// 8-bit integer values (uint8_t). +/// Fractional results are rounded down, e.g. avg8(20,41) = 30 +LIB8STATIC_ALWAYS_INLINE uint8_t avg8(uint8_t i, uint8_t j) +{ + return (i + j) >> 1; +} + +/// Calculate an integer average of two unsigned +/// 16-bit integer values (uint16_t). +/// Fractional results are rounded down, e.g. avg16(20,41) = 30 +LIB8STATIC_ALWAYS_INLINE uint16_t avg16(uint16_t i, uint16_t j) +{ + return (uint32_t)((uint32_t)(i) + (uint32_t)(j)) >> 1; +} + +/// Calculate an integer average of two unsigned +/// 8-bit integer values (uint8_t). +/// Fractional results are rounded up, e.g. avg8r(20,41) = 31 +LIB8STATIC_ALWAYS_INLINE uint8_t avg8r(uint8_t i, uint8_t j) +{ + return (i + j + 1) >> 1; +} + +/// Calculate an integer average of two unsigned +/// 16-bit integer values (uint16_t). +/// Fractional results are rounded up, e.g. avg16r(20,41) = 31 +LIB8STATIC_ALWAYS_INLINE uint16_t avg16r(uint16_t i, uint16_t j) +{ + return (uint32_t)((uint32_t)(i) + (uint32_t)(j) + 1) >> 1; +} + +/// Calculate an integer average of two signed 7-bit +/// integers (int8_t) +/// If the first argument is even, result is rounded down. +/// If the first argument is odd, result is rounded up. +LIB8STATIC_ALWAYS_INLINE int8_t avg7(int8_t i, int8_t j) +{ + return (i >> 1) + (j >> 1) + (i & 0x1); +} + +/// Calculate an integer average of two signed 15-bit +/// integers (int16_t) +/// If the first argument is even, result is rounded down. +/// If the first argument is odd, result is rounded up. +LIB8STATIC_ALWAYS_INLINE int16_t avg15(int16_t i, int16_t j) +{ + return (i >> 1) + (j >> 1) + (i & 0x1); +} + +/// Calculate the remainder of one unsigned 8-bit +/// value divided by anoter, aka A % M. +/// Implemented by repeated subtraction, which is +/// very compact, and very fast if A is 'probably' +/// less than M. If A is a large multiple of M, +/// the loop has to execute multiple times. However, +/// even in that case, the loop is only two +/// instructions long on AVR, i.e., quick. +LIB8STATIC_ALWAYS_INLINE uint8_t mod8(uint8_t a, uint8_t m) +{ + while (a >= m) + a -= m; + return a; +} + +/// Add two numbers, and calculate the modulo +/// of the sum and a third number, M. +/// In other words, it returns (A+B) % M. +/// It is designed as a compact mechanism for +/// incrementing a 'mode' switch and wrapping +/// around back to 'mode 0' when the switch +/// goes past the end of the available range. +/// e.g. if you have seven modes, this switches +/// to the next one and wraps around if needed: +/// mode = addmod8( mode, 1, 7); +///LIB8STATIC_ALWAYS_INLINESee 'mod8' for notes on performance. +LIB8STATIC uint8_t addmod8(uint8_t a, uint8_t b, uint8_t m) +{ + a += b; + while (a >= m) + a -= m; + return a; +} + +/// Subtract two numbers, and calculate the modulo +/// of the difference and a third number, M. +/// In other words, it returns (A-B) % M. +/// It is designed as a compact mechanism for +/// incrementing a 'mode' switch and wrapping +/// around back to 'mode 0' when the switch +/// goes past the end of the available range. +/// e.g. if you have seven modes, this switches +/// to the next one and wraps around if needed: +/// mode = addmod8( mode, 1, 7); +///LIB8STATIC_ALWAYS_INLINESee 'mod8' for notes on performance. +LIB8STATIC uint8_t submod8(uint8_t a, uint8_t b, uint8_t m) +{ + a -= b; + while (a >= m) + a -= m; + return a; +} + +/// 8x8 bit multiplication, with 8 bit result +LIB8STATIC_ALWAYS_INLINE uint8_t mul8(uint8_t i, uint8_t j) +{ + return ((int)i * (int)(j)) & 0xFF; +} + +/// saturating 8x8 bit multiplication, with 8 bit result +/// @returns the product of i * j, capping at 0xFF +LIB8STATIC_ALWAYS_INLINE uint8_t qmul8(uint8_t i, uint8_t j) +{ + unsigned p = (unsigned)i * (unsigned)j; + if (p > 255) + p = 255; + return p; +} + +/// take abs() of a signed 8-bit uint8_t +LIB8STATIC_ALWAYS_INLINE int8_t abs8(int8_t i) +{ + if (i < 0) + i = -i; + return i; +} + +/// square root for 16-bit integers +/// About three times faster and five times smaller +/// than Arduino's general sqrt on AVR. +LIB8STATIC uint8_t sqrt16(uint16_t x) +{ + if (x <= 1) + return x; + + uint8_t low = 1; // lower bound + uint8_t hi, mid; + + hi = x > 7904 ? 255 : (x >> 5) + 8; // initial estimate for upper bound + + do + { + mid = (low + hi) >> 1; + if ((uint16_t)(mid * mid) > x) + { + hi = mid - 1; + } + else + { + if (mid == 255) + return 255; + low = mid + 1; + } + } while (hi >= low); + + return low - 1; +} + +/// blend a variable proportion(0-255) of one byte to another +/// @param a - the starting byte value +/// @param b - the byte value to blend toward +/// @param amountOfB - the proportion (0-255) of b to blend +/// @returns a byte value between a and b, inclusive +LIB8STATIC uint8_t blend8(uint8_t a, uint8_t b, uint8_t amountOfB) +{ + // The BLEND_FIXED formula is + // + // result = ( A*(amountOfA) + B*(amountOfB) )/ 256 + // + // …where amountOfA = 255-amountOfB. + // + // This formula will never return 255, which is why the BLEND_FIXED + SCALE8_FIXED version is + // + // result = ( A*(amountOfA) + A + B*(amountOfB) + B ) / 256 + // + // We can rearrange this formula for some great optimisations. + // + // result = ( A*(amountOfA) + A + B*(amountOfB) + B ) / 256 + // = ( A*(255-amountOfB) + A + B*(amountOfB) + B ) / 256 + // = ( A*(256-amountOfB) + B*(amountOfB) + B ) / 256 + // = ( A*256 + B + B*(amountOfB) - A*(amountOfB) ) / 256 // this is the version used in SCALE8_FIXED AVR below + // = ( A*256 + B + (B-A)*(amountOfB) ) / 256 // this is the version used in SCALE8_FIXED C below + + uint16_t partial; + uint8_t result; + + uint8_t amountOfA = 255 - amountOfB; + + partial = (a * amountOfA); + partial += a; + + partial += (b * amountOfB); + partial += b; + + result = partial >> 8; + + return result; +} + +///@} +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/lib8tion/lib8tion/random8.h Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,118 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2013 FastLED + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef __INC_LIB8TION_RANDOM_H +#define __INC_LIB8TION_RANDOM_H +///@ingroup lib8tion + +///@defgroup Random Fast random number generators +/// Fast 8- and 16- bit unsigned random numbers. +/// Significantly faster than Arduino random(), but +/// also somewhat less random. You can add entropy. +///@{ + +// X(n+1) = (2053 * X(n)) + 13849) +#define FASTLED_RAND16_2053 ((uint16_t)(2053)) +#define FASTLED_RAND16_13849 ((uint16_t)(13849)) + +#define APPLY_FASTLED_RAND16_2053(x) (x * FASTLED_RAND16_2053) + +/// random number seed +extern uint16_t rand16seed; // = RAND16_SEED; + +/// Generate an 8-bit random number +LIB8STATIC uint8_t random8() +{ + rand16seed = APPLY_FASTLED_RAND16_2053(rand16seed) + FASTLED_RAND16_13849; + // return the sum of the high and low bytes, for better + // mixing and non-sequential correlation + return (uint8_t)(((uint8_t)(rand16seed & 0xFF)) + ((uint8_t)(rand16seed >> 8))); +} + +/// Generate a 16 bit random number +LIB8STATIC uint16_t random16() +{ + rand16seed = APPLY_FASTLED_RAND16_2053(rand16seed) + FASTLED_RAND16_13849; + return rand16seed; +} + +/// Generate an 8-bit random number between 0 and lim +/// @param lim the upper bound for the result +LIB8STATIC uint8_t random8_to(uint8_t lim) +{ + uint8_t r = random8(); + r = (r * lim) >> 8; + return r; +} + +/// Generate an 8-bit random number in the given range +/// @param min the lower bound for the random number +/// @param lim the upper bound for the random number +LIB8STATIC uint8_t random8_between(uint8_t min, uint8_t lim) +{ + uint8_t delta = lim - min; + uint8_t r = random8_to(delta) + min; + return r; +} + +/// Generate an 16-bit random number between 0 and lim +/// @param lim the upper bound for the result +LIB8STATIC uint16_t random16_to(uint16_t lim) +{ + uint16_t r = random16(); + uint32_t p = (uint32_t) lim * (uint32_t) r; + r = p >> 16; + return r; +} + +/// Generate an 16-bit random number in the given range +/// @param min the lower bound for the random number +/// @param lim the upper bound for the random number +LIB8STATIC uint16_t random16_between(uint16_t min, uint16_t lim) +{ + uint16_t delta = lim - min; + uint16_t r = random16_to(delta) + min; + return r; +} + +/// Set the 16-bit seed used for the random number generator +LIB8STATIC void random16_set_seed(uint16_t seed) +{ + rand16seed = seed; +} + +/// Get the current seed value for the random number generator +LIB8STATIC uint16_t random16_get_seed() +{ + return rand16seed; +} + +/// Add entropy into the random number generator +LIB8STATIC void random16_add_entropy(uint16_t entropy) +{ + rand16seed += entropy; +} + +///@} + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/lib8tion/lib8tion/scale8.h Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,201 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2013 FastLED + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef __INC_LIB8TION_SCALE_H +#define __INC_LIB8TION_SCALE_H + +///@ingroup lib8tion + +///@defgroup Scaling Scaling functions +/// Fast, efficient 8-bit scaling functions specifically +/// designed for high-performance LED programming. +/// +/// Because of the AVR(Arduino) and ARM assembly language +/// implementations provided, using these functions often +/// results in smaller and faster code than the equivalent +/// program using plain "C" arithmetic and logic. +///@{ + +/// scale one byte by a second one, which is treated as +/// the numerator of a fraction whose denominator is 256 +/// In other words, it computes i * (scale / 256) +/// 4 clocks AVR with MUL, 2 clocks ARM +LIB8STATIC_ALWAYS_INLINE uint8_t scale8(uint8_t i, fract8 scale) +{ + return (((uint16_t) i) * (1 + (uint16_t) (scale))) >> 8; +} + +/// The "video" version of scale8 guarantees that the output will +/// be only be zero if one or both of the inputs are zero. If both +/// inputs are non-zero, the output is guaranteed to be non-zero. +/// This makes for better 'video'/LED dimming, at the cost of +/// several additional cycles. +LIB8STATIC_ALWAYS_INLINE uint8_t scale8_video(uint8_t i, fract8 scale) +{ + return (((int) i * (int) scale) >> 8) + ((i && scale) ? 1 : 0); +} + +/// scale three one byte values by a fourth one, which is treated as +/// the numerator of a fraction whose demominator is 256 +/// In other words, it computes r,g,b * (scale / 256) +/// +/// THIS FUNCTION ALWAYS MODIFIES ITS ARGUMENTS IN PLACE +LIB8STATIC void nscale8x3(uint8_t *r, uint8_t *g, uint8_t *b, fract8 scale) +{ + uint16_t scale_fixed = scale + 1; + *r = (((uint16_t) *r) * scale_fixed) >> 8; + *g = (((uint16_t) *g) * scale_fixed) >> 8; + *b = (((uint16_t) *b) * scale_fixed) >> 8; +} + +/// scale three one byte values by a fourth one, which is treated as +/// the numerator of a fraction whose demominator is 256 +/// In other words, it computes r,g,b * (scale / 256), ensuring +/// that non-zero values passed in remain non zero, no matter how low the scale +/// argument. +/// +/// THIS FUNCTION ALWAYS MODIFIES ITS ARGUMENTS IN PLACE +LIB8STATIC void nscale8x3_video(uint8_t *r, uint8_t *g, uint8_t *b, fract8 scale) +{ + uint8_t nonzeroscale = (scale != 0) ? 1 : 0; + *r = (*r == 0) ? 0 : (((int) *r * (int) (scale)) >> 8) + nonzeroscale; + *g = (*g == 0) ? 0 : (((int) *g * (int) (scale)) >> 8) + nonzeroscale; + *b = (*b == 0) ? 0 : (((int) *b * (int) (scale)) >> 8) + nonzeroscale; +} + +/// scale two one byte values by a third one, which is treated as +/// the numerator of a fraction whose demominator is 256 +/// In other words, it computes i,j * (scale / 256) +/// +/// THIS FUNCTION ALWAYS MODIFIES ITS ARGUMENTS IN PLACE +LIB8STATIC void nscale8x2(uint8_t *i, uint8_t *j, fract8 scale) +{ + uint16_t scale_fixed = scale + 1; + *i = (((uint16_t) *i) * scale_fixed) >> 8; + *j = (((uint16_t) *j) * scale_fixed) >> 8; +} + +/// scale two one byte values by a third one, which is treated as +/// the numerator of a fraction whose demominator is 256 +/// In other words, it computes i,j * (scale / 256), ensuring +/// that non-zero values passed in remain non zero, no matter how low the scale +/// argument. +/// +/// THIS FUNCTION ALWAYS MODIFIES ITS ARGUMENTS IN PLACE +LIB8STATIC void nscale8x2_video(uint8_t *i, uint8_t *j, fract8 scale) +{ + uint8_t nonzeroscale = (scale != 0) ? 1 : 0; + *i = (*i == 0) ? 0 : (((int) *i * (int) (scale)) >> 8) + nonzeroscale; + *j = (*j == 0) ? 0 : (((int) *j * (int) (scale)) >> 8) + nonzeroscale; +} + +/// scale a 16-bit unsigned value by an 8-bit value, +/// considered as numerator of a fraction whose denominator +/// is 256. In other words, it computes i * (scale / 256) +LIB8STATIC_ALWAYS_INLINE uint16_t scale16by8(uint16_t i, fract8 scale) +{ + return (i * (1 + ((uint16_t) scale))) >> 8; +} + +/// scale a 16-bit unsigned value by a 16-bit value, +/// considered as numerator of a fraction whose denominator +/// is 65536. In other words, it computes i * (scale / 65536) + +LIB8STATIC uint16_t scale16(uint16_t i, fract16 scale) +{ + return ((uint32_t) (i) * (1 + (uint32_t) (scale))) / 65536; +} +///@} + +///@defgroup Dimming Dimming and brightening functions +/// +/// Dimming and brightening functions +/// +/// The eye does not respond in a linear way to light. +/// High speed PWM'd LEDs at 50% duty cycle appear far +/// brighter then the 'half as bright' you might expect. +/// +/// If you want your midpoint brightness leve (128) to +/// appear half as bright as 'full' brightness (255), you +/// have to apply a 'dimming function'. +///@{ + +/// Adjust a scaling value for dimming +LIB8STATIC uint8_t dim8_raw(uint8_t x) +{ + return scale8(x, x); +} + +/// Adjust a scaling value for dimming for video (value will never go below 1) +LIB8STATIC uint8_t dim8_video(uint8_t x) +{ + return scale8_video(x, x); +} + +/// Linear version of the dimming function that halves for values < 128 +LIB8STATIC uint8_t dim8_lin(uint8_t x) +{ + if (x & 0x80) + { + x = scale8(x, x); + } + else + { + x += 1; + x /= 2; + } + return x; +} + +/// inverse of the dimming function, brighten a value +LIB8STATIC uint8_t brighten8_raw(uint8_t x) +{ + uint8_t ix = 255 - x; + return 255 - scale8(ix, ix); +} + +/// inverse of the dimming function, brighten a value +LIB8STATIC uint8_t brighten8_video(uint8_t x) +{ + uint8_t ix = 255 - x; + return 255 - scale8_video(ix, ix); +} + +/// inverse of the dimming function, brighten a value +LIB8STATIC uint8_t brighten8_lin(uint8_t x) +{ + uint8_t ix = 255 - x; + if (ix & 0x80) + { + ix = scale8(ix, ix); + } + else + { + ix += 1; + ix /= 2; + } + return 255 - ix; +} + +///@} +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/lib8tion/lib8tion/trig8.h Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,154 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2013 FastLED + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef __INC_LIB8TION_TRIG_H +#define __INC_LIB8TION_TRIG_H + +///@ingroup lib8tion + +///@defgroup Trig Fast trig functions +/// Fast 8 and 16-bit approximations of sin(x) and cos(x). +/// Don't use these approximations for calculating the +/// trajectory of a rocket to Mars, but they're great +/// for art projects and LED displays. +/// +/// On Arduino/AVR, the 16-bit approximation is more than +/// 10X faster than floating point sin(x) and cos(x), while +/// the 8-bit approximation is more than 20X faster. +///@{ + +/// Fast 16-bit approximation of sin(x). This approximation never varies more than +/// 0.69% from the floating point value you'd get by doing +/// +/// float s = sin(x) * 32767.0; +/// +/// @param theta input angle from 0-65535 +/// @returns sin of theta, value between -32767 to 32767. +LIB8STATIC int16_t sin16(uint16_t theta) +{ + static const uint16_t base[] = { 0, 6393, 12539, 18204, 23170, 27245, 30273, 32137 }; + static const uint8_t slope[] = { 49, 48, 44, 38, 31, 23, 14, 4 }; + + uint16_t offset = (theta & 0x3FFF) >> 3; // 0..2047 + if (theta & 0x4000) + offset = 2047 - offset; + + uint8_t section = offset / 256; // 0..7 + uint16_t b = base[section]; + uint8_t m = slope[section]; + + uint8_t secoffset8 = (uint8_t)(offset) / 2; + + uint16_t mx = m * secoffset8; + int16_t y = mx + b; + + if (theta & 0x8000) + y = -y; + + return y; +} + +/// Fast 16-bit approximation of cos(x). This approximation never varies more than +/// 0.69% from the floating point value you'd get by doing +/// +/// float s = cos(x) * 32767.0; +/// +/// @param theta input angle from 0-65535 +/// @returns sin of theta, value between -32767 to 32767. +LIB8STATIC int16_t cos16(uint16_t theta) +{ + return sin16(theta + 16384); +} + +/////////////////////////////////////////////////////////////////////// + +// sin8 & cos8 +// Fast 8-bit approximations of sin(x) & cos(x). +// Input angle is an unsigned int from 0-255. +// Output is an unsigned int from 0 to 255. +// +// This approximation can vary to to 2% +// from the floating point value you'd get by doing +// float s = (sin( x ) * 128.0) + 128; +// +// Don't use this approximation for calculating the +// "real" trigonometric calculations, but it's great +// for art projects and LED displays. +// +// On Arduino/AVR, this approximation is more than +// 20X faster than floating point sin(x) and cos(x) +// + +/// Fast 8-bit approximation of sin(x). This approximation never varies more than +/// 2% from the floating point value you'd get by doing +/// +/// float s = (sin(x) * 128.0) + 128; +/// +/// @param theta input angle from 0-255 +/// @returns sin of theta, value between 0 and 255 +LIB8STATIC uint8_t sin8(uint8_t theta) +{ + static const uint8_t b_m16_interleave[] = { 0, 49, 49, 41, 90, 27, 117, 10 }; + + uint8_t offset = theta; + if (theta & 0x40) + offset = (uint8_t)255 - offset; + offset &= 0x3F; // 0..63 + + uint8_t secoffset = offset & 0x0F; // 0..15 + if (theta & 0x40) + ++secoffset; + + uint8_t section = offset >> 4; // 0..3 + uint8_t s2 = section * 2; + const uint8_t *p = b_m16_interleave; + p += s2; + uint8_t b = *p; + ++p; + uint8_t m16 = *p; + + uint8_t mx = (m16 * secoffset) >> 4; + + int8_t y = mx + b; + if (theta & 0x80) + y = -y; + + y += 128; + + return y; +} + +/// Fast 8-bit approximation of cos(x). This approximation never varies more than +/// 2% from the floating point value you'd get by doing +/// +/// float s = (cos(x) * 128.0) + 128; +/// +/// @param theta input angle from 0-255 +/// @returns sin of theta, value between 0 and 255 +LIB8STATIC uint8_t cos8(uint8_t theta) +{ + return sin8(theta + 64); +} + +///@} +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/lm75/.eil.yml Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,24 @@ +--- +components: + - name: lm75 + description: | + Driver for LM75, a digital temperature sensor and thermal watchdog + group: temperature + groups: [] + code_owners: + - name: trombik + depends: + - name: i2cdev + - name: log + - name: esp_idf_lib_helpers + thread_safe: yes + targets: + - name: esp32 + - name: esp8266 + - name: esp32s2 + - name: esp32c3 + licenses: + - name: BSD-3 + copyrights: + - name: trombik + year: 2019
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/lm75/CMakeLists.txt Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,5 @@ +idf_component_register( + SRCS lm75.c + INCLUDE_DIRS . + REQUIRES i2cdev log esp_idf_lib_helpers +)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/lm75/LICENSE Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,13 @@ +Copyright (c) 2019 Tomoyuki Sakurai <y@trombik.org> + +Permission to use, copy, modify, and distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/lm75/component.mk Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,2 @@ +COMPONENT_ADD_INCLUDEDIRS = . +COMPONENT_DEPENDS = i2cdev log esp_idf_lib_helpers
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/lm75/lm75.c Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,307 @@ +/* + * Copyright (c) 2019 Tomoyuki Sakurai <y@trombik.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/** + * @file lm75.c + * + * ESP-IDF driver for LM75, a digital temperature sensor and thermal watchdog. + * + * The driver depends on i2cdev library in `esp-idf-lib`. + * + * The driver was written using LM75B. + */ +#include <esp_log.h> +#include <esp_err.h> +#include <esp_idf_lib_helpers.h> +#include "lm75.h" + +#define I2C_FREQ_HZ 1000000 // 1 Mhz + +#define LM75_MASK_SHUTDOWN (1 << 0) +#define LM75_MASK_OS_COMP_INT (1 << 1) +#define LM75_MASK_OS_POL (1 << 2) +#define LM75_MASK_OS_F_QUE ((1 << 4) | (1 << 3)) + +#define LM75_REG_CONF 0x01 +#define LM75_REG_TEMP 0x00 +#define LM75_REG_TOS 0x03 +#define LM75_REG_THYST 0x02 + +#define CHECK(x) do { esp_err_t __; if ((__ = x) != ESP_OK) return __; } while (0) +#define CHECK_ARG(VAL) do { if (!(VAL)) return ESP_ERR_INVALID_ARG; } while (0) + +/* use CHECK_LOGE after I2C_DEV_TAKE_MUTEX(). */ +#define CHECK_LOGE(dev, x, msg, ...) do { \ + esp_err_t __; \ + if ((__ = x) != ESP_OK) { \ + I2C_DEV_GIVE_MUTEX(dev); \ + ESP_LOGE(TAG, msg, ## __VA_ARGS__); \ + return __; \ + } \ + } while (0) + +const static char *TAG = "lm75"; + +/* read_register* and write_register* must be protected with I2C_DEV_TAKE_MUTEX */ +static esp_err_t read_register16(i2c_dev_t *dev, uint8_t reg, uint16_t *value) +{ + uint8_t data[] = { 0, 0 }; + CHECK(i2c_dev_read_reg(dev, reg, data, 2)); + *value = (data[0] << 8) | data[1]; + ESP_LOGV(TAG, "read_register16() reg: 0x%x value: 0x%x", reg, *value); + return ESP_OK; +} + +static esp_err_t read_register8(i2c_dev_t *dev, uint8_t reg, uint8_t *value) +{ + CHECK(i2c_dev_read_reg(dev, reg, value, 1)); + ESP_LOGV(TAG, "read_register8() reg: 0x%x value: 0x%x", reg, *value); + return ESP_OK; +} + +static esp_err_t write_register16(i2c_dev_t *dev, uint8_t reg, uint16_t value) +{ + ESP_LOGV(TAG, "write_register16(): reg: 0x%x, value: 0x%x", reg, value); + return i2c_dev_write(dev, ®, 2, &value, 2); +} + +static esp_err_t write_register8(i2c_dev_t *dev, uint8_t reg, uint8_t value) +{ + ESP_LOGV(TAG, "write_register8(): reg: 0x%x, value: 0x%x", reg, value); + return i2c_dev_write_reg(dev, reg, &value, 1); +} + +esp_err_t lm75_read_temperature(i2c_dev_t *dev, float *value) +{ + CHECK_ARG(dev); + uint16_t raw_data; + + I2C_DEV_TAKE_MUTEX(dev); + CHECK_LOGE(dev, read_register16(dev, LM75_REG_TEMP, &raw_data), + "lm75_read_temperature(): read_register16() failed: register: 0x%x", LM75_REG_TEMP); + I2C_DEV_GIVE_MUTEX(dev); + + *value = (raw_data >> 5) * 0.125; + return ESP_OK; +} + +esp_err_t lm75_init_desc(i2c_dev_t *dev, uint8_t addr, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio) +{ + CHECK_ARG(dev); + if (addr < LM75_I2C_ADDRESS_DEFAULT || addr > LM75_I2C_ADDRESS_MAX) { + ESP_LOGE(TAG, "lm75_init_desc(): Invalid I2C address `0x%x`. address must not be less than 0x%x, not be more than 0x%x", + addr, LM75_I2C_ADDRESS_DEFAULT, LM75_I2C_ADDRESS_MAX); + return ESP_ERR_INVALID_ARG; + } + + dev->port = port; + dev->addr = addr; + dev->cfg.sda_io_num = sda_gpio; + dev->cfg.scl_io_num = scl_gpio; +#if HELPER_TARGET_IS_ESP32 + dev->cfg.master.clk_speed = I2C_FREQ_HZ; +#endif + return i2c_dev_create_mutex(dev); +} + +esp_err_t lm75_set_os_threshold(i2c_dev_t *dev, const float value) +{ + CHECK_ARG(dev); + uint16_t reg_value; + + /* two's complement format with the resolution of 0.5 C degree. + * 7 LSB of the LSByte are equal to zero and should be ignored. + */ + if (value < 0) { + reg_value = ((uint16_t)(abs((int16_t)value) * 2) ^ 0xff) + 1; + } else { + reg_value = value * 2; + } + reg_value = reg_value << 7; + /* when the value is 25.0f: + * reg_value: 0x1900 9 bit reg_value: 0x32 value: 25.000000 */ + ESP_LOGV(TAG, "lm75_set_os_threshold(): reg_value: 0x%x 9 bit reg_value: 0x%x value: %f", + reg_value, reg_value >> 7, value); + + I2C_DEV_TAKE_MUTEX(dev); + CHECK_LOGE(dev, write_register16(dev, LM75_REG_TOS, reg_value), + "lm75_set_os_threshold(): write_register16() failed: register 0x%x", + LM75_REG_TOS); + I2C_DEV_GIVE_MUTEX(dev); + + return ESP_OK; +} + + +esp_err_t lm75_get_os_threshold(i2c_dev_t *dev, float *value) +{ + CHECK_ARG(dev); + uint16_t reg_value; + + I2C_DEV_TAKE_MUTEX(dev); + CHECK_LOGE(dev, read_register16(dev, LM75_REG_TOS, ®_value), + "lm75_get_os_threshold(): read_register16() failed: register: 0x%x", LM75_REG_TOS); + I2C_DEV_GIVE_MUTEX(dev); + + ESP_LOGV(TAG, "lm75_get_os_threshold(): reg_value: 0x%x 9 bit reg_value: 0x%x", reg_value, reg_value >> 7); + reg_value = reg_value >> 7; + if (reg_value & (1 << 10)) { + *value = ((reg_value | (1 << 10)) ^ 0xff) + 1; + *value *= -1; + *value /= 2; + } else { + *value = reg_value / 2; + } + + return ESP_OK; +} + +esp_err_t lm75_free_desc(i2c_dev_t *dev) +{ + CHECK_ARG(dev); + + return i2c_dev_delete_mutex(dev); +} + +static +esp_err_t lm75_set_bits_register8(i2c_dev_t *dev, uint8_t reg, uint8_t mask) +{ + CHECK_ARG(dev); + uint8_t value; + ESP_LOGV(TAG, "lm75_set_bits_register8(): reg: 0x%x, mask: 0x%x", reg, mask); + + I2C_DEV_TAKE_MUTEX(dev); + CHECK_LOGE(dev, read_register8(dev, reg, &value), + "lm75_set_bits_register8(): read_register8() failed reg: 0x%x", reg); + ESP_LOGV(TAG, "lm75_set_bits_register8(): value in register: 0x%x", value); + if ((value & mask) != mask) { + value |= mask; + ESP_LOGV(TAG, "lm75_set_bits_register8(): updating register with value: 0x%x", value); + CHECK_LOGE(dev, write_register8(dev, reg, value), + "lm75_set_bits_register8(): write_register8() failed reg: 0x%x", reg); + } else { + ESP_LOGV(TAG, "lm75_set_bits_register8(): register unchanged"); + } + I2C_DEV_GIVE_MUTEX(dev); + return ESP_OK; + +} + +static +esp_err_t lm75_clear_bits_register8(i2c_dev_t *dev, uint8_t reg, uint8_t mask) +{ + CHECK_ARG(dev); + uint8_t value; + ESP_LOGV(TAG, "lm75_clear_bits_register8(): reg: 0x%x, mask: 0x%x", reg, mask); + + I2C_DEV_TAKE_MUTEX(dev); + CHECK_LOGE(dev, read_register8(dev, reg, &value), + "read_register8() failed: register: 0x%x", reg); + if ((value & mask) == mask) { + value ^= mask; + ESP_LOGV(TAG, "lm75_clear_bits_register8(): updating register with value: 0x%x", value); + CHECK_LOGE(dev, write_register8(dev, reg, value), + "write_register8() failed: register 0x%x", reg); + } else { + ESP_LOGV(TAG, "lm75_clear_bits_register8(): register unchanged"); + } + I2C_DEV_GIVE_MUTEX(dev); + return ESP_OK; + +} + +esp_err_t lm75_shutdown(i2c_dev_t *dev) +{ + CHECK_ARG(dev); + ESP_LOGV(TAG, "lm75_shutdown(): trying to set shutdown bit"); + + return lm75_set_bits_register8(dev, LM75_REG_CONF, LM75_MASK_SHUTDOWN); +} + +esp_err_t lm75_wakeup(i2c_dev_t *dev) +{ + CHECK_ARG(dev); + ESP_LOGV(TAG, "lm75_wakeup(): trying to clear shutdown bit"); + + return lm75_clear_bits_register8(dev, LM75_REG_CONF, LM75_MASK_SHUTDOWN); +} + +esp_err_t lm75_set_os_polarity(i2c_dev_t *dev, const lm75_os_polarity_t v) +{ + ESP_LOGV(TAG, "lm75_set_os_polarity(): v: %d", v); + if (v > 1) { + ESP_LOGE(TAG, "lm75_set_os_polarity(): second argument must be %d or %d", + LM75_OSP_LOW, LM75_OSP_HIGH); + return ESP_ERR_INVALID_ARG; + } + + if (v == LM75_OSP_HIGH) { + return lm75_set_bits_register8(dev, LM75_REG_CONF, LM75_MASK_OS_POL); + } else { + return lm75_clear_bits_register8(dev, LM75_REG_CONF, LM75_MASK_OS_POL); + } +} +esp_err_t lm75_get_os_polarity(i2c_dev_t *dev, uint8_t *v) +{ + CHECK_ARG(dev); + uint8_t reg_value; + + I2C_DEV_TAKE_MUTEX(dev); + CHECK_LOGE(dev, read_register8(dev, LM75_REG_CONF, ®_value), + "lm75_get_os_polarity(): read_register8() failed: reg: 0x%x", LM75_REG_CONF); + I2C_DEV_GIVE_MUTEX(dev); + + *v = (reg_value & LM75_MASK_OS_POL) == 0 ? 0 : 1; + return ESP_OK; +} + +esp_err_t lm75_set_os_mode(i2c_dev_t *dev, lm75_os_mode_t v) +{ + ESP_LOGV(TAG, "lm75_set_os_mode(): v: %d", v); + if (v > 1) { + ESP_LOGE(TAG, "lm75_set_os_mode(): second argument must be %d or %d", + LM75_OS_MODE_COMP, LM75_OS_MODE_INT); + return ESP_ERR_INVALID_ARG; + } + + if (v == LM75_OS_MODE_INT) { + return lm75_set_bits_register8(dev, LM75_REG_CONF, LM75_MASK_OS_COMP_INT); + } else { + return lm75_clear_bits_register8(dev, LM75_REG_CONF, LM75_MASK_OS_COMP_INT); + } +} +/* + * Configuration register (0x00) + * | [7:5] | 4 | 3 | 2 | 1 | 0 | + * | Reserved | OS_F_QUE[1:0] | PS_POL | OS_COMP_INT | SHUTDOWN | + */ +esp_err_t lm75_init(i2c_dev_t *dev, const lm75_config_t config) +{ + CHECK_ARG(dev); + uint8_t value = 0; + + value = (config.mode << 0) | + (config.os_mode << 1) | + (config.os_mode << 2) | + (config.os_fault_queue << 3); + + I2C_DEV_TAKE_MUTEX(dev); + CHECK_LOGE(dev, write_register8(dev, LM75_REG_CONF, value), + "lm75_init(): write_register8() failed"); + I2C_DEV_GIVE_MUTEX(dev); + + return ESP_OK; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/lm75/lm75.h Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,196 @@ +/* + * Copyright (c) 2019 Tomoyuki Sakurai <y@trombik.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/** + * @file lm75.h + * @defgroup lm75 lm75 + * @{ + * + * ESP-IDF driver for LM75, a digital temperature sensor and thermal watchdog. + * + * The driver depends on i2cdev library in `esp-idf-lib`. + * + * The driver was written using LM75B. + * + * Short usage instruction: + * + * 1. Include lm75.h + * 2. Initialize I2C descriptor by i2cdev_init() + * 3. Initialize LM75 descriptor by lm75_init_desc() + * 4. Initialize LM75 by lm75_init() + * 5. Read temperature by lm75_read_temperature() + * + */ +#ifndef __LM75_H__ +#define __LM75_H__ + +#include <stdint.h> +#include <stdbool.h> +#include <i2cdev.h> +#include <esp_err.h> + +#ifdef __cplusplus +extern "C" { +#endif + +#define LM75_I2C_ADDRESS_DEFAULT (0x48) //!< Default I2C address (A0 == A1 == A2 == 0) +#define LM75_I2C_ADDRESS_MAX (0x4f) //!< I2C address (A0 == A1 == A2 == 1) + +/** + * Operation mode of LM75 + */ +typedef enum { + LM75_MODE_NORMAL = 0, //!< Normal operation mode + LM75_MODE_SHUTDOWN = 1 //!< Shutdown mode +} lm75_mode_t; + +/** + * Overtemperature Shutdown Polarity + */ +typedef enum { + LM75_OSP_LOW = 0, //!< Overtemperature Shutdown Polarity is active low + LM75_OSP_HIGH = 1 //!< OSP is active high +} lm75_os_polarity_t; + +/** + * Overtemperature Shutdown output mode + */ +typedef enum { + LM75_OS_MODE_COMP = 0, //!< OS output mode is comparator + LM75_OS_MODE_INT = 1 //!< OS output mode is interrupt +} lm75_os_mode_t; + +/** + * OS fault queue, the number of faults that must occur consecutively to + * activate the OS output + */ +typedef enum { + LM75_FAULT_QUEUE1 = 0b00, //!< 1 + LM75_FAULT_QUEUE2 = 0b01, //!< 2 + LM75_FAULT_QUEUE4 = 0b10, //!< 4 + LM75_FAULT_QUEUE6 = 0b11 //!< 6 +} lm75_fault_queue_t; + +/** + * Device configuration + */ +typedef struct { + lm75_mode_t mode; //!< Operation mode of the device + lm75_os_polarity_t os_pol; //!< OS Polarity + lm75_os_mode_t os_mode; //!< OS mode + lm75_fault_queue_t os_fault_queue; //!< OS fault queue +} lm75_config_t; + +/** + * @brief Initialize LM75 device descriptor + * + * i2cdev_init() must be called before this function. + * + * @param[out] dev pointer to LM75 device descriptor + * @param[in] addr I2C address of LM75 + * @param[in] port I2C port + * @param[in] sda_gpio GPIO number of SDA + * @param[in] scl_gpio GPIO number of SCL + * @return `ESP_OK` on success + */ +esp_err_t lm75_init_desc(i2c_dev_t *dev, uint8_t addr, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio); + +/** + * @brief Initialize LM75 + * + * lm75_init_desc() must be called before this function. + * + * @param[in] dev pointer to LM75 device descriptor + * @param[in] config configuration + */ +esp_err_t lm75_init(i2c_dev_t *dev, const lm75_config_t config); + +/** + * @brief free LM75 device descriptor + * @param dev Pointer to device descriptor + */ +esp_err_t lm75_free_desc(i2c_dev_t *dev); + +/** + * @brief Get the value of OS Polarity in the configuration register + * @param[in] dev pointer to LM75 device descriptor + * @param[out] v value of OS Polarity + * @return `ESP_OK` on success + */ +esp_err_t lm75_get_os_polarity(i2c_dev_t *dev, uint8_t *v); + +/** + * @brief Get the value of OS threshold in the configuration register + * @param[in] dev pointer to LM75 device descriptor + * @param[out] value value of OS threshold + * @return `ESP_OK` on success + */ +esp_err_t lm75_get_os_threshold(i2c_dev_t *dev, float *value); + +/** + * @brief Read the temperature + * @param[in] dev pointer to LM75 device descriptor + * @param[out] value temperature + * @return `ESP_OK` on success + */ +esp_err_t lm75_read_temperature(i2c_dev_t *dev, float *value); + +/** + * @brief Set OS mode + * @param[in] dev pointer to LM75 device descriptor + * @param[in] v OS mode + * @return `ESP_OK` on success + */ +esp_err_t lm75_set_os_mode(i2c_dev_t *dev, const lm75_os_mode_t v); + +/** + * @brief Set the value of OS Polarity in the configuration register + * + * @param[in] dev pointer to LM75 device descriptor + * @param[in] v value of OS Polarity + * @return `ESP_OK` on success + */ +esp_err_t lm75_set_os_polarity(i2c_dev_t *dev, const lm75_os_polarity_t v); + +/** + * @brief Set the value of OS threshold in the configuration register + * @param[in] dev pointer to LM75 device descriptor + * @param[in] value value of OS threshold + * @return `ESP_OK` on success + */ +esp_err_t lm75_set_os_threshold(i2c_dev_t *dev, const float value); + +/** + * @brief Shutdown LM75 + * @param[in] dev pointer to LM75 device descriptor + * @return `ESP_OK` on success + */ +esp_err_t lm75_shutdown(i2c_dev_t *dev); + +/** + * @brief Wake LM75 up + * @param[in] dev pointer to LM75 device descriptor + * @return `ESP_OK` on success + */ +esp_err_t lm75_wakeup(i2c_dev_t *dev); + +#ifdef __cplusplus +} +#endif + +/**@}*/ + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/ls7366r/.eil.yml Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,20 @@ +--- +components: + - name: ls7366r + description: Driver for LS7366R Quadrature Encoder Counter + group: input + groups: [] + code_owners: Jkallus + depends: + - name: driver + thread_safe: yes + targets: + - name: esp32 + - name: esp32s2 + - name: esp32c3 + licenses: + - name: MIT + copyrights: + - author: + name: Jkallus + year: 2021
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/ls7366r/CMakeLists.txt Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,5 @@ +idf_component_register( + SRCS ls7366r.c + INCLUDE_DIRS . + REQUIRES driver +) \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/ls7366r/LICENSE Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 Joshua Kallus + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/ls7366r/component.mk Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,2 @@ +COMPONENT_ADD_INCLUDEDIRS = . +COMPONENT_DEPENDS = driver
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/ls7366r/ls7366r.c Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,388 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2021 Joshua Kallus <joshk.kallus3@gmail.com> + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +/** + * @file ls7366r.c + * + * ESP-IDF driver for LS7366R Quadrature Encoder Counter + * + * Datasheet: https://lsicsi.com/datasheets/LS7366R.pdf + * + * Copyright (c) 2021 Joshua Kallus <joshk.kallus3@gmail.com> + * + * MIT Licensed as described in the file LICENSE + */ +#include "ls7366r.h" +#include <string.h> + +// Registers +#define REG_MDR0 0x08 +#define REG_MDR1 0x10 +#define REG_DTR 0x18 +#define REG_CNTR 0x20 +#define REG_OTR 0x28 +#define REG_STR 0x30 + +// Commands +#define CMD_CLR 0x00 +#define CMD_WR 0x80 +#define CMD_LOAD 0xC0 +#define CMD_RD 0x40 + +// Mode Bitmasks + +#define COUNTER_NON_QUAD 0x00 +#define COUNTER_1X_QUAD 0x01 +#define COUNTER_2X_QUAD 0x02 +#define COUNTER_4X_QUAD 0x03 + +#define COUNTER_FREE_RUN 0x00 +#define COUNTER_SINGLE_COUNT 0x04 +#define COUNTER_RANGE_LIMIT 0x08 +#define COUNTER_N_MODULE 0x0C + +#define COUNTER_INDEX_DISABLED 0x00 +#define COUNTER_INDEX_LOAD_CNTR 0x10 +#define COUNTER_INDEX_RESET_CNTR 0x20 +#define COUNTER_INDEX_LOAD_OTR 0x30 + +#define COUNTER_INDEX_ASYNC 0x00 +#define COUNTER_INDEX_SYNC 0x40 + +#define COUNTER_FILTER_CLOCK_DIV1 0x00 +#define COUNTER_FILTER_CLOCK_DIV2 0x80 + +#define COUNTER_MODE_32 0x00 +#define COUNTER_MODE_24 0x01 +#define COUNTER_MODE_16 0x02 +#define COUNTER_MODE_8 0x03 + +#define COUNTER_ENABLE 0x00 +#define COUNTER_DISABLE 0x04 + +#define COUNTER_FLAG_IDX 0x10 +#define COUNTER_FLAG_CMP 0x20 +#define COUNTER_FLAG_BW 0x40 +#define COUNTER_FLAG_CY 0x80 + +#define CHECK_ARG(VAL) do { if (!(VAL)) return ESP_ERR_INVALID_ARG; } while (0) + + +// Interface functions ////////////////////////////////////////////////////////////// +static esp_err_t write_command(spi_device_handle_t spi, uint8_t command) +{ + esp_err_t ret; + spi_transaction_t trans = { + .cmd = command, + .length = 0, + .tx_buffer = NULL, + .rx_buffer = NULL + }; + + spi_device_acquire_bus(spi, portMAX_DELAY); + ret = spi_device_transmit(spi, &trans); + spi_device_release_bus(spi); + return ret; +} + +static esp_err_t write_data(spi_device_handle_t spi, uint8_t command, uint8_t* data, uint8_t count) +{ + esp_err_t ret; + spi_transaction_t trans = { + .cmd = command, + .length = count * 8, + .flags = SPI_TRANS_USE_TXDATA, + .rx_buffer = NULL, + }; + + memcpy(trans.tx_data, data, count); + + spi_device_acquire_bus(spi, portMAX_DELAY); + ret = spi_device_transmit(spi, &trans); + spi_device_release_bus(spi); + return ret; +} + +static esp_err_t read_register(spi_device_handle_t spi, uint8_t command, uint8_t *data) +{ + esp_err_t ret; + spi_transaction_t trans = { + .cmd = command, + .tx_buffer = NULL, + .rxlength = 1 * 8, + .flags = SPI_TRANS_USE_RXDATA, + .length = 8 + }; + + spi_device_acquire_bus(spi, portMAX_DELAY); + ret = spi_device_transmit(spi, &trans); + spi_device_release_bus(spi); + + *data = trans.rx_data[0]; + return ret; +} + +static esp_err_t read_data(spi_device_handle_t spi, uint8_t command, uint8_t* data, uint8_t count) +{ + esp_err_t ret; + spi_transaction_t trans = { + .cmd = command, + .flags = SPI_TRANS_USE_RXDATA, + .rxlength = count * 8, + .tx_buffer = NULL, + .length = 8 * count, + }; + + spi_device_acquire_bus(spi, portMAX_DELAY); + ret = spi_device_transmit(spi, &trans); + spi_device_release_bus(spi); + + memcpy(data, trans.rx_data, count); + return ret; +} +////////////////////////////////////////////////////////////////////////////////////// + +///////////////// Control Functions /////////////////////////////////////////////////// +static esp_err_t write_mdr0(spi_device_handle_t spi, uint8_t settings) +{ + return write_data(spi, CMD_WR | REG_MDR0, &settings, 1); +} + +static esp_err_t write_mdr1(spi_device_handle_t spi, uint8_t settings) +{ + return write_data(spi, CMD_WR | REG_MDR1, &settings, 1); +} + +static esp_err_t write_dtr(spi_device_handle_t spi, uint8_t* data, uint8_t length) +{ + return write_data(spi, CMD_WR | REG_DTR, data, length); +} + +static esp_err_t read_mdr1(spi_device_handle_t spi, uint8_t *data) +{ + return read_register(spi, CMD_RD | REG_MDR1, data); +} + +static esp_err_t read_cntr(spi_device_handle_t spi, int32_t* data) +{ + esp_err_t ret; + uint8_t data_buf[4]; + uint32_t result; + ret = read_data(spi, CMD_RD | REG_CNTR, data_buf, 4); + result = data_buf[0]; + + for (uint8_t cnt = 1; cnt < 4; cnt++) + { + result <<= 8; + result |= data_buf[cnt]; + } + + *data = result; + return ret; +} + +static esp_err_t clear_cntr(spi_device_handle_t spi) +{ + return write_command(spi, CMD_CLR | REG_CNTR); +} + +static esp_err_t clear_str(spi_device_handle_t spi) +{ + return write_command(spi, CMD_CLR | REG_STR); +} + +static esp_err_t counter_enable(spi_device_handle_t spi) +{ + esp_err_t ret; + uint8_t mdr1_current; + ret = read_mdr1(spi, &mdr1_current); + if (ret != ESP_OK) + return ret; + ret = write_mdr1(spi, mdr1_current & 0xFB); // bit 3 of MDR1 is 0 enable, 1 disable 0xFB = 0b11111011 masks all but the bit we need to set to 0 + return ret; +} + +static esp_err_t counter_disable(spi_device_handle_t spi) +{ + esp_err_t ret; + uint8_t mdr1_current; + ret = read_mdr1(spi, &mdr1_current); + if (ret != ESP_OK) + return ret; + ret = write_mdr1(spi, mdr1_current | 0x04); // bit 3 of MDR1 is 0 enable, 1 disable 0x04 = 0b00000100 masks all but the bit we need to set to 1 + return ret; + +} +/////////////////////////////////////////////////////////////////////////////////////////// + + +esp_err_t ls7366r_init_desc(ls7366r_t *dev, spi_host_device_t host, uint32_t clock_speed_hz, gpio_num_t cs_pin) +{ + CHECK_ARG(dev); + memset(&dev->spi_cfg, 0, sizeof(dev->spi_cfg)); + dev->spi_cfg.spics_io_num = cs_pin; + dev->spi_cfg.clock_speed_hz = clock_speed_hz; + dev->spi_cfg.mode = 0; + dev->spi_cfg.queue_size = 1; + dev->spi_cfg.command_bits = 8; + return spi_bus_add_device(host, &dev->spi_cfg, &dev->spi_dev); +} + +esp_err_t ls7366r_free_desc(ls7366r_t *dev) +{ + CHECK_ARG(dev); + return spi_bus_remove_device(dev->spi_dev); +} + +esp_err_t ls7366r_set_config(ls7366r_t *dev, const ls7366r_config_t *config) +{ + esp_err_t ret; + + // Set MDR0 + uint8_t mdr0_set = 0x00; + switch (config->filter_clock_divider) + { + case LS7366R_FILTER_CLK_1: + mdr0_set |= COUNTER_FILTER_CLOCK_DIV1; + break; + case LS7366R_FILTER_CLK_2: + mdr0_set |= COUNTER_FILTER_CLOCK_DIV2; + break; + } + + switch (config->index_sync) + { + case LS7366R_INDEX_SYNCHRONOUS: + mdr0_set |= COUNTER_INDEX_SYNC; + break; + case LS7366R_INDEX_ASYNCHRONOUS: + mdr0_set |= COUNTER_INDEX_ASYNC; + break; + } + + switch (config->index_mode) + { + case LS7366R_INDEX_DISABLED: + mdr0_set |= COUNTER_INDEX_DISABLED; + break; + case LS7366R_INDEX_LOAD_CNTR: + mdr0_set |= COUNTER_INDEX_LOAD_CNTR; + break; + case LS7366R_INDEX_RESET_CNTR: + mdr0_set |= COUNTER_INDEX_RESET_CNTR; + break; + case LS7366R_INDEX_LOAD_OTR: + mdr0_set |= COUNTER_INDEX_LOAD_OTR; + break; + } + + switch (config->count_type) + { + case LS7366R_NON_QUAD: + mdr0_set |= COUNTER_NON_QUAD; + break; + case LS7366R_1x_QUAD: + mdr0_set |= COUNTER_1X_QUAD; + break; + case LS7366R_2x_QUAD: + mdr0_set |= COUNTER_2X_QUAD; + break; + case LS7366R_4X_QUAD: + mdr0_set |= COUNTER_4X_QUAD; + break; + } + + uint8_t mdr1_set = 0x00; + + uint8_t flag_borrow = config->flag_mode.borrow == LS7366R_FLAG_BORROW_ENABLE ? COUNTER_FLAG_BW : 0x00; + uint8_t flag_index = config->flag_mode.index == LS7366R_FLAG_INDEX_ENABLE ? COUNTER_FLAG_IDX : 0x00; + uint8_t flag_compare = config->flag_mode.compare == LS7366R_FLAG_COMPARE_ENABLE ? COUNTER_FLAG_CMP : 0x00; + uint8_t flag_carry = config->flag_mode.carry == LS7366R_FLAG_CARRY_ENABLE ? COUNTER_FLAG_CY : 0x00; + + mdr1_set = mdr1_set | flag_borrow | flag_carry | flag_compare | flag_index; + + switch (config->counter_enable) + { + case LS7366R_COUNTER_ENABLE: + mdr1_set |= COUNTER_ENABLE; + break; + case LS7366R_COUNTER_DISABLE: + mdr1_set |= COUNTER_DISABLE; + break; + } + + switch (config->counter_bits) + { + case LS7366R_8_BIT: + mdr1_set |= COUNTER_MODE_8; + break; + case LS7366R_16_BIT: + mdr1_set |= COUNTER_MODE_16; + break; + case LS7366R_24_BIT: + mdr1_set |= COUNTER_MODE_24; + break; + case LS7366R_32_BIT: + mdr1_set |= COUNTER_MODE_32; + break; + } + + ret = clear_cntr(dev->spi_dev); + if (ret != ESP_OK) + return ret; + ret = clear_str(dev->spi_dev); + if (ret != ESP_OK) + return ret; + ret = write_mdr0(dev->spi_dev, mdr0_set); + if (ret != ESP_OK) + return ret; + ret = write_mdr1(dev->spi_dev, mdr1_set); + return ret; +} + +esp_err_t ls7366r_get_count(ls7366r_t *dev, int32_t *count) +{ + return read_cntr(dev->spi_dev, count); +} + +esp_err_t ls7366r_set_compare_val(ls7366r_t *dev, int32_t cmp) +{ + uint8_t cmp_data[4]; + uint8_t* ptr = (uint8_t*)(&cmp); + cmp_data[3] = ptr[0]; + cmp_data[2] = ptr[1]; + cmp_data[1] = ptr[2]; + cmp_data[0] = ptr[3]; + + return write_dtr(dev->spi_dev, (uint8_t*)&cmp_data, 4); +} + +esp_err_t ls7366r_clear_counter(ls7366r_t *dev) +{ + return clear_cntr(dev->spi_dev); +} + +esp_err_t ls7366r_counter_enable(ls7366r_t *dev, bool enable) +{ + return enable ? counter_enable(dev->spi_dev) : counter_disable(dev->spi_dev); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/ls7366r/ls7366r.h Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,256 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2021 Joshua Kallus <joshk.kallus3@gmail.com> + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +/** + * @file ls7366r.h + * + * @defgroup ls7366r ls7366r + * @{ + * + * ESP-IDF driver for LS7366R Quadrature Encoder Counter + * + * Datasheet: https://lsicsi.com/datasheets/LS7366R.pdf + * + * Copyright (c) 2021 Joshua Kallus <joshk.kallus3@gmail.com> + * + * MIT Licensed as described in the file LICENSE + */ +#ifndef __LS7366R_H__ +#define __LS7366R_H__ + +#include <driver/spi_master.h> +#include <esp_err.h> +#include <driver/gpio.h> + +#ifdef __cplusplus +extern "C" { +#endif + +#define LS7366R_MAX_CLOCK_SPEED_HZ (2000000) // 2 MHz clock + +/** + * Counter type + */ +typedef enum +{ + LS7366R_NON_QUAD, // Up down counting with channel A being step and channel B being direction + LS7366R_1x_QUAD, // 1X quadrature + LS7366R_2x_QUAD, // 2X quadrature + LS7366R_4X_QUAD, // 4X quadrature (otherwise known as full quadrature) +} ls7366r_count_type_t; + +/** + * Count mode + */ +typedef enum +{ + LS7366R_FREE_RUN, // Free running count mode + LS7366R_SINGLE_COUNT, // Single cycle count mode (counter disabled with carry or borrow, re-enabled with reset or load) + LS7366R_RANGE_LIMIT, // Up and down count-ranges are limited between DTR and zero + LS7366R_N_MODULO, // Input count clock frequency is divided by a factor of (n+1), where n = DTR, in both up and down directions +} ls7366r_count_mode_t; + +/** + * Index mode + */ +typedef enum +{ + LS7366R_INDEX_DISABLED, // Disable index + LS7366R_INDEX_LOAD_CNTR, // Configure index as the "load CNTR" input (transfers DTR to CNTR) + LS7366R_INDEX_RESET_CNTR, // Configure index as the "reset CNTR" input (clears CNTR to 0) + LS7366R_INDEX_LOAD_OTR // Configure index as the "load OTR" input (transfers CNTR to OTR) +} ls7366r_index_mode_t; + +/** + * Index sync or async + */ +typedef enum +{ + LS7366R_INDEX_SYNCHRONOUS, // Asynchronous index + LS7366R_INDEX_ASYNCHRONOUS // Synchronous index (overridden in non-quadrature mode) +} ls7366r_index_sync_t; + +/** + * Counter bits + */ +typedef enum +{ + LS7366R_8_BIT, // 1 byte counter mode + LS7366R_16_BIT, // 2 byte counter mode + LS7366R_24_BIT, // 3 byte counter mode + LS7366R_32_BIT // 4 byte counter mode +} ls7366r_counter_bits_t; + +/** + * Counter clock divider + */ +typedef enum +{ + LS7366R_FILTER_CLK_1, // Filter clock division factor 1 + LS7366R_FILTER_CLK_2 // Filter clock division factor 2 +} ls7366r_filter_clock_divider_t; + +/** + * Counter enable + */ +typedef enum +{ + LS7366R_COUNTER_ENABLE, // Enable counting + LS7366R_COUNTER_DISABLE // Disable counting +} ls7366r_counter_enable_t; + +/** + * Counter flag carry mode + */ +typedef enum +{ + LS7366R_FLAG_CARRY_ENABLE, // Enable flag on carry bit + LS7366R_FLAG_CARRY_DISABLE // Disable flag on carry bit +} ls7366r_flag_carry_mode_t; + +/** + * Counter flag borrow mode + */ +typedef enum +{ + LS7366R_FLAG_BORROW_ENABLE, // Enable flag on borrow bit + LS7366R_FLAG_BORROW_DISABLE // Disable flag on borrow bit +} ls7366r_flag_borrow_mode_t; + +/** + * Counter flag index mode + */ +typedef enum +{ + LS7366R_FLAG_INDEX_ENABLE, // Enable flag on index bit + LS7366R_FLAG_INDEX_DISABLE // Disable flag on index bit +} ls7366r_flag_index_mode_t; + +/** + * Counter flag compare mode + */ +typedef enum +{ + LS7366R_FLAG_COMPARE_ENABLE, // Enable flag on compare bit + LS7366R_FLAG_COMPARE_DISABLE // Disable flag on compare bit +} ls7366r_flag_compare_enable_t; + +typedef struct +{ + ls7366r_flag_borrow_mode_t borrow; + ls7366r_flag_carry_mode_t carry; + ls7366r_flag_compare_enable_t compare; + ls7366r_flag_index_mode_t index; +} ls7366r_flag_mode_t; // LS7366R flag mode struct + +/** + * Device descriptor + */ +typedef struct +{ + spi_device_interface_config_t spi_cfg; + spi_device_handle_t spi_dev; +} ls7366r_t; // LS7366R device struct + +/** + * Device configuration + */ +typedef struct +{ + ls7366r_flag_mode_t flag_mode; // MDR1 + ls7366r_counter_enable_t counter_enable; // MDR1 + ls7366r_counter_bits_t counter_bits; // MDR1 + ls7366r_filter_clock_divider_t filter_clock_divider; // MDR0 + ls7366r_index_sync_t index_sync; // MDR0 + ls7366r_index_mode_t index_mode; // MDR0 + ls7366r_count_type_t count_type; // MDR0 +} ls7366r_config_t; // LS7366R config struct + +/** + * @brief Initialize device descriptor + * + * @param dev Device descriptor + * @param host SPI host + * @param clock_speed_hz SPI clock speed, Hz + * @param cs_pin CS GPIO number + * @return `ESP_OK` on success + */ +esp_err_t ls7366r_init_desc(ls7366r_t *dev, spi_host_device_t host, uint32_t clock_speed_hz, gpio_num_t cs_pin); + +/** + * @brief Free device descriptor + * + * @param dev Device descriptor + * @return `ESP_OK` on success + */ +esp_err_t ls7366r_free_desc(ls7366r_t *dev); + +/** + * @brief Configure device + * + * @param dev Device descriptor + * @param config Configuration + * @return `ESP_OK` on success + */ +esp_err_t ls7366r_set_config(ls7366r_t *dev, const ls7366r_config_t *config); + +/** + * @brief Get current count + * + * @param dev Device descriptor + * @param count Count variable + * @return `ESP_OK` on success + */ +esp_err_t ls7366r_get_count(ls7366r_t *dev, int32_t *count); + +/** + * @brief set value for compare + * + * @param dev Device descriptor + * @param cmp Compare value + * @return `ESP_OK` on success + */ +esp_err_t ls7366r_set_compare_val(ls7366r_t *dev, int32_t cmp); + +/** + * @brief clear counter value, set to 0 + * + * @param dev Device descriptor + * @return `ESP_OK` on success + */ +esp_err_t ls7366r_clear_counter(ls7366r_t *dev); + +/** + * @brief enable or disable counter + * + * @param dev Device descriptor + * @param enable Counter enabled or disabled + * @return `ESP_OK` on success + */ +esp_err_t ls7366r_counter_enable(ls7366r_t *dev, bool enable); + +#ifdef __cplusplus +} +#endif + +#endif /* __LS7366R_H__ */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/max31725/.eil.yml Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,23 @@ +--- +components: + - name: max31725 + description: Driver for MAX31725/MAX31726 temperature sensors + group: temperature + groups: [] + code_owners: UncleRus + depends: + - i2cdev + - log + - esp_idf_lib_helpers + thread_safe: yes + targets: + - name: esp32 + - name: esp8266 + - name: esp32s2 + - name: esp32c3 + licenses: + - name: BSD-3 + copyrights: + - author: + name: UncleRus + year: 2020
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/max31725/CMakeLists.txt Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,5 @@ +idf_component_register( + SRCS max31725.c + INCLUDE_DIRS . + REQUIRES i2cdev log esp_idf_lib_helpers +)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/max31725/LICENSE Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,26 @@ +Copyright (c) 2019 Ruslan V. Uss (https://github.com/UncleRus) + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, +this list of conditions and the following disclaimer in the documentation +and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of itscontributors +may be used to endorse or promote products derived from this software without +specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/max31725/component.mk Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,2 @@ +COMPONENT_ADD_INCLUDEDIRS = . +COMPONENT_DEPENDS = i2cdev log esp_idf_lib_helpers
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/max31725/max31725.c Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,217 @@ +/* + * Copyright (c) 2019 Ruslan V. Uss <unclerus@gmail.com> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of itscontributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file max31725.c + * + * ESP-IDF driver for MAX31725/MAX31726 temperature sensors + * + * Copyright (c) 2019 Ruslan V. Uss <unclerus@gmail.com> + * + * BSD Licensed as described in the file LICENSE + */ + +#include <esp_log.h> +#include <freertos/FreeRTOS.h> +#include <freertos/task.h> +#include <esp_idf_lib_helpers.h> +#include "max31725.h" + +static const char *TAG = "max31725"; + +static const float t_lsb = 0.00390625; + +#define MAX31725_I2C_ADDR_MAX 0x5f + +#define I2C_FREQ_HZ 400000 // 400kHz + +#define REG_TEMP 0 +#define REG_CONF 1 +#define REG_HYST 2 +#define REG_OS 3 + +#define BIT_SHUTDOWN 0 +#define BIT_COMP_INT 1 +#define BIT_OS_POL 2 +#define BIT_FAULT_Q0 3 +#define BIT_DATA_FMT 5 +#define BIT_TIMEOUT 6 +#define BIT_ONE_SHOT 7 + +#define FMT_SHIFT 64.0 +#define CONV_TIME_MS 50 + +#define CHECK(x) do { esp_err_t __; if ((__ = x) != ESP_OK) return __; } while (0) +#define CHECK_ARG(VAL) do { if (!(VAL)) return ESP_ERR_INVALID_ARG; } while (0) +#define BV(x) (1 << (x)) + +typedef union +{ + uint16_t udata; + int16_t sdata; +} temp_data_t; + +static esp_err_t read_temp(i2c_dev_t *dev, uint8_t reg, float *temp, max31725_data_format_t fmt) +{ + CHECK_ARG(dev && temp); + + temp_data_t buf; + + I2C_DEV_TAKE_MUTEX(dev); + I2C_DEV_CHECK(dev, i2c_dev_read_reg(dev, reg, &buf.udata, 2)); + I2C_DEV_GIVE_MUTEX(dev); + + buf.udata = (buf.udata << 8) | (buf.udata >> 8); + *temp = buf.sdata * t_lsb + (fmt == MAX31725_FMT_EXTENDED ? FMT_SHIFT : 0); + + return ESP_OK; +} + +static esp_err_t write_temp(i2c_dev_t *dev, uint8_t reg, float temp, max31725_data_format_t fmt) +{ + CHECK_ARG(dev); + + temp_data_t buf; + buf.sdata = (temp - (fmt == MAX31725_FMT_EXTENDED ? FMT_SHIFT : 0)) / t_lsb; + buf.udata = (buf.udata << 8) | (buf.udata >> 8); + + I2C_DEV_TAKE_MUTEX(dev); + I2C_DEV_CHECK(dev, i2c_dev_write_reg(dev, reg, &buf.udata, 2)); + I2C_DEV_GIVE_MUTEX(dev); + + return ESP_OK; +} + +/////////////////////////////////////////////////////////////////////////////// + +esp_err_t max31725_init_desc(i2c_dev_t *dev, uint8_t addr, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio) +{ + CHECK_ARG(dev); + if (addr < MAX31725_I2C_ADDR_BASE || addr > MAX31725_I2C_ADDR_MAX) + { + ESP_LOGE(TAG, "Invalid device address: 0x%02x", addr); + return ESP_ERR_INVALID_ARG; + } + + dev->port = port; + dev->addr = addr; + dev->cfg.sda_io_num = sda_gpio; + dev->cfg.scl_io_num = scl_gpio; +#if HELPER_TARGET_IS_ESP32 + dev->cfg.master.clk_speed = I2C_FREQ_HZ; +#endif + + return i2c_dev_create_mutex(dev); +} + +esp_err_t max31725_free_desc(i2c_dev_t *dev) +{ + CHECK_ARG(dev); + + return i2c_dev_delete_mutex(dev); +} + +esp_err_t max31725_get_config(i2c_dev_t *dev, max31725_mode_t *mode, max31725_data_format_t *fmt, max31725_fault_queue_t *fq, + max31725_os_polarity_t *op, max31725_os_mode_t *om) +{ + CHECK_ARG(dev && mode && fmt && fq && op && om); + + uint8_t b; + + I2C_DEV_TAKE_MUTEX(dev); + I2C_DEV_CHECK(dev, i2c_dev_read_reg(dev, REG_CONF, &b, 1)); + I2C_DEV_GIVE_MUTEX(dev); + + *mode = (b >> BIT_SHUTDOWN) & 1; + *fmt = (b >> BIT_DATA_FMT) & 1; + *fq = (b >> BIT_FAULT_Q0) & 3; + *op = (b >> BIT_OS_POL) & 1; + *om = (b >> BIT_COMP_INT) & 1; + + return ESP_OK; +} + +esp_err_t max31725_set_config(i2c_dev_t *dev, max31725_mode_t mode, max31725_data_format_t fmt, max31725_fault_queue_t fq, + max31725_os_polarity_t op, max31725_os_mode_t om) +{ + CHECK_ARG(dev && fq <= MAX31725_FAULTS_6); + + uint8_t b = (fmt != MAX31725_FMT_NORMAL ? BV(BIT_DATA_FMT) : 0) | + (fq << BIT_FAULT_Q0) | + (op != MAX31725_OS_LOW ? BV(BIT_OS_POL) : 0) | + (om != MAX31725_OS_COMPARATOR ? BV(BIT_COMP_INT) : 0) | + (mode != MAX31725_MODE_CONTINUOUS ? BV(BIT_SHUTDOWN) : 0); + + I2C_DEV_TAKE_MUTEX(dev); + I2C_DEV_CHECK(dev, i2c_dev_write_reg(dev, REG_CONF, &b, 1)); + I2C_DEV_GIVE_MUTEX(dev); + + return ESP_OK; +} + +esp_err_t max31725_one_shot(i2c_dev_t *dev, float *temp, max31725_data_format_t fmt) +{ + CHECK_ARG(dev); + + uint8_t b; + + I2C_DEV_TAKE_MUTEX(dev); + I2C_DEV_CHECK(dev, i2c_dev_read_reg(dev, REG_CONF, &b, 1)); + b |= BV(BIT_ONE_SHOT); + I2C_DEV_CHECK(dev, i2c_dev_write_reg(dev, REG_CONF, &b, 1)); + I2C_DEV_GIVE_MUTEX(dev); + + // wait 50 ms + vTaskDelay(pdMS_TO_TICKS(CONV_TIME_MS)); + + return read_temp(dev, REG_TEMP, temp, fmt); +} + +esp_err_t max31725_get_temperature(i2c_dev_t *dev, float *temp, max31725_data_format_t fmt) +{ + return read_temp(dev, REG_TEMP, temp, fmt); +} + +esp_err_t max31725_get_os_temp(i2c_dev_t *dev, float *temp, max31725_data_format_t fmt) +{ + return read_temp(dev, REG_OS, temp, fmt); +} + +esp_err_t max31725_set_os_temp(i2c_dev_t *dev, float temp, max31725_data_format_t fmt) +{ + return write_temp(dev, REG_OS, temp, fmt); +} + +esp_err_t max31725_get_hysteresis_temp(i2c_dev_t *dev, float *temp, max31725_data_format_t fmt) +{ + return read_temp(dev, REG_HYST, temp, fmt); +} + +esp_err_t max31725_set_hysteresis_temp(i2c_dev_t *dev, float temp, max31725_data_format_t fmt) +{ + return write_temp(dev, REG_HYST, temp, fmt); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/max31725/max31725.h Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,211 @@ +/* + * Copyright (c) 2019 Ruslan V. Uss <unclerus@gmail.com> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of itscontributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file max31725.h + * @defgroup max31725 max31725 + * @{ + * + * ESP-IDF driver for MAX31725/MAX31726 temperature sensors + * + * Copyright (c) 2019 Ruslan V. Uss <unclerus@gmail.com> + * + * BSD Licensed as described in the file LICENSE + */ +#ifndef __MAX31725_H__ +#define __MAX31725_H__ + +#include <stdbool.h> +#include <i2cdev.h> +#include <esp_err.h> + +#define MAX31725_I2C_ADDR_BASE 0x40 //!< See full list in datasheet + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Temperature data range + */ +typedef enum { + MAX31725_FMT_NORMAL = 0, //!< -128 deg.C .. +127.99609375 deg.C (default) + MAX31725_FMT_EXTENDED //!< -64 deg.C .. +191.99609375 deg.C (max 150 deg.C) +} max31725_data_format_t; + +/** + * Fault queue size + */ +typedef enum { + MAX31725_FAULTS_1 = 0, //!< 1 fault to trigger OS (default) + MAX31725_FAULTS_2, //!< 2 fault to trigger OS + MAX31725_FAULTS_4, //!< 4 fault to trigger OS + MAX31725_FAULTS_6 //!< 6 fault to trigger OS +} max31725_fault_queue_t; + +/** + * OS polarity + */ +typedef enum { + MAX31725_OS_LOW = 0, //!< OS active low (default) + MAX31725_OS_HIGH //!< OS active high +} max31725_os_polarity_t; + +/** + * Mode of OS operation + */ +typedef enum { + MAX31725_OS_COMPARATOR = 0, //!< OS comparator mode (default) + MAX31725_OS_INTERRUPT //!< OS interrupt mode +} max31725_os_mode_t; + +/** + * Device operating mode + */ +typedef enum { + MAX31725_MODE_CONTINUOUS = 0, //!< Continuous measurement mode (default) + MAX31725_MODE_SHUTDOWN //!< Shutdown mode +} max31725_mode_t; + +/** + * @brief Initialize device descriptor + * + * @param dev Device descriptor + * @param port I2C port number + * @param addr I2C address + * @param sda_gpio SDA GPIO + * @param scl_gpio SCL GPIO + * @return `ESP_OK` on success + */ +esp_err_t max31725_init_desc(i2c_dev_t *dev, uint8_t addr, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio); + +/** + * @brief Free device descriptor + * + * @param dev Device descriptor + * @return `ESP_OK` on success + */ +esp_err_t max31725_free_desc(i2c_dev_t *dev); + +/** + * @brief Read current device config + * + * @param dev Device descriptor + * @param[out] mode Operating mode + * @param[out] fmt Data format + * @param[out] fq Fault queue size + * @param[out] op OS polarity + * @param[out] om OS mode + * @return `ESP_OK` on success + */ +esp_err_t max31725_get_config(i2c_dev_t *dev, max31725_mode_t *mode, max31725_data_format_t *fmt, max31725_fault_queue_t *fq, + max31725_os_polarity_t *op, max31725_os_mode_t *om); + +/** + * @brief Configure device + * + * @param dev Device descriptor + * @param mode Operating mode + * @param fmt Data format + * @param fq Fault queue size + * @param op OS polarity + * @param om OS mode + * @return `ESP_OK` on success + */ +esp_err_t max31725_set_config(i2c_dev_t *dev, max31725_mode_t mode, max31725_data_format_t fmt, max31725_fault_queue_t fq, + max31725_os_polarity_t op, max31725_os_mode_t om); + +/** + * @brief Made a single-shot measurement + * + * Works only when device is in shutdown mode. + * Measurement time is ~50 ms. + * + * @param dev Device descriptor + * @param[out] temp Temperature, deg.C + * @param fmt Data format + * @return `ESP_OK` on success + */ +esp_err_t max31725_one_shot(i2c_dev_t *dev, float *temp, max31725_data_format_t fmt); + +/** + * @brief Read temperature register + * + * @param dev Device descriptor + * @param[out] temp Temperature, deg.C + * @param fmt Data format + * @return `ESP_OK` on success + */ +esp_err_t max31725_get_temperature(i2c_dev_t *dev, float *temp, max31725_data_format_t fmt); + +/** + * @brief Read OS threshold temperature + * + * @param dev Device descriptor + * @param[out] temp Temperature, deg.C + * @param fmt Data format + * @return `ESP_OK` on success + */ +esp_err_t max31725_get_os_temp(i2c_dev_t *dev, float *temp, max31725_data_format_t fmt); + +/** + * @brief Set OS threshold temperature + * + * @param dev Device descriptor + * @param temp Temperature, deg.C + * @param fmt Data format + * @return `ESP_OK` on success + */ +esp_err_t max31725_set_os_temp(i2c_dev_t *dev, float temp, max31725_data_format_t fmt); + +/** + * @brief Read OS hysteresis temperature + * + * @param dev Device descriptor + * @param[out] temp Temperature, deg.C + * @param fmt Data format + * @return `ESP_OK` on success + */ +esp_err_t max31725_get_hysteresis_temp(i2c_dev_t *dev, float *temp, max31725_data_format_t fmt); + +/** + * @brief Set OS hysteresis temperature + * + * @param dev Device descriptor + * @param temp Temperature, deg.C + * @param fmt Data format + * @return `ESP_OK` on success + */ +esp_err_t max31725_set_hysteresis_temp(i2c_dev_t *dev, float temp, max31725_data_format_t fmt); + +#ifdef __cplusplus +} +#endif + +/**@}*/ + +#endif /* __MAX31725_H__ */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/max31855/.eil.yml Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,21 @@ +--- +components: + - name: max31855 + description: Driver for MAX31855 cold-junction compensated thermocouple-to-digital converter + group: temperature + groups: [] + code_owners: UncleRus + depends: + - driver + - log + thread_safe: yes + targets: + - name: esp32 + - name: esp32s2 + - name: esp32c3 + licenses: + - name: BSD-3 + copyrights: + - author: + name: UncleRus + year: 2022
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/max31855/CMakeLists.txt Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,5 @@ +idf_component_register( + SRCS max31855.c + INCLUDE_DIRS . + REQUIRES driver log +)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/max31855/LICENSE Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,26 @@ +Copyright (c) 2022 Ruslan V. Uss <unclerus@gmail.com> + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, +this list of conditions and the following disclaimer in the documentation +and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of itscontributors +may be used to endorse or promote products derived from this software without +specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/max31855/component.mk Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,2 @@ +COMPONENT_ADD_INCLUDEDIRS = . +COMPONENT_DEPENDS = driver log
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/max31855/max31855.c Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,137 @@ +/* + * Copyright (c) 2022 Ruslan V. Uss <unclerus@gmail.com> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of itscontributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file max31855.c + * + * ESP-IDF driver for MAX31855 cold-junction compensated + * thermocouple-to-digital converter + * + * Copyright (c) 2022 Ruslan V. Uss <unclerus@gmail.com> + * + * BSD Licensed as described in the file LICENSE + */ +#include "max31855.h" +#include <string.h> +#include <math.h> +#include <freertos/FreeRTOS.h> +#include <freertos/task.h> + +#define CHECK(x) do { esp_err_t __; if ((__ = x) != ESP_OK) return __; } while (0) +#define CHECK_ARG(VAL) do { if (!(VAL)) return ESP_ERR_INVALID_ARG; } while (0) + +#define BIT_TC_SIGN 31 +#define BIT_TC 18 +#define BIT_CJ_SIGN 15 +#define BIT_CJ 4 +#define BIT_SCV 2 +#define BIT_SCG 1 +#define BIT_OC 0 + +#define MASK_CJ 0x0fff +#define MASK_TC 0x3fff + +#define SIGN_CJ 0xf000 +#define SIGN_TC 0xc000 + +#define LSB_TC 0.25f +#define LSB_CJ 0.0625f + +static inline esp_err_t read_32(max31855_t *dev, uint32_t *val) +{ + spi_transaction_t t; + memset(&t, 0, sizeof(spi_transaction_t)); + + uint8_t rx[4]; + + t.tx_buffer = NULL; + t.rx_buffer = rx; + t.length = 32; + CHECK(spi_device_transmit(dev->spi_dev, &t)); + + *val = ((uint32_t)rx[0] << 24) | ((uint32_t)rx[1] << 16) | ((uint32_t)rx[2] << 8) | rx[3]; + + return ESP_OK; +} + +static inline esp_err_t max31855_get_raw_data(max31855_t *dev, int16_t *tc_t, int16_t *cj_t, bool *scv, bool *scg, bool *oc) +{ + CHECK_ARG(dev && tc_t && cj_t && scv && scg && oc); + + uint32_t v = 0; + CHECK(read_32(dev, &v)); + + // faults + *scv = v & BIT(BIT_SCV) ? true : false; + *scg = v & BIT(BIT_SCG) ? true : false; + *oc = v & BIT(BIT_OC) ? true : false; + + // cold junction temperature + *cj_t = (int16_t)((v >> BIT_CJ) & MASK_CJ) | (v & BIT(BIT_CJ_SIGN) ? SIGN_CJ : 0); + + // thermocouple temperature + *tc_t = (int16_t)((v >> BIT_TC) & MASK_TC) | (v & BIT(BIT_TC_SIGN) ? SIGN_TC : 0); + + return ESP_OK; +} + +/////////////////////////////////////////////////////////////////////////////// + +esp_err_t max31855_init_desc(max31855_t *dev, spi_host_device_t host, uint32_t clock_speed_hz, gpio_num_t cs_pin) +{ + CHECK_ARG(dev); + + memset(&dev->spi_cfg, 0, sizeof(dev->spi_cfg)); + dev->spi_cfg.spics_io_num = cs_pin; + dev->spi_cfg.clock_speed_hz = clock_speed_hz; + dev->spi_cfg.mode = 0; + dev->spi_cfg.queue_size = 1; + dev->spi_cfg.cs_ena_pretrans = 1; + + return spi_bus_add_device(host, &dev->spi_cfg, &dev->spi_dev); +} + +esp_err_t max31855_free_desc(max31855_t *dev) +{ + CHECK_ARG(dev); + + return spi_bus_remove_device(dev->spi_dev); +} + +esp_err_t max31855_get_temperature(max31855_t *dev, float *tc_t, float *cj_t, bool *scv, bool *scg, bool *oc) +{ + CHECK_ARG(tc_t); + + int16_t raw_tc = 0, raw_cj = 0; + CHECK(max31855_get_raw_data(dev, &raw_tc, &raw_cj, scv, scg, oc)); + + *tc_t = (float)raw_tc * LSB_TC; + if (cj_t) + *cj_t = (float)raw_cj * LSB_CJ; + + return ESP_OK; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/max31855/max31855.h Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2022 Ruslan V. Uss <unclerus@gmail.com> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of itscontributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file max31855.h + * @defgroup max31855 max31855 + * @{ + * + * ESP-IDF driver for MAX31855 cold-junction compensated + * thermocouple-to-digital converter + * + * Copyright (c) 2022 Ruslan V. Uss <unclerus@gmail.com> + * + * BSD Licensed as described in the file LICENSE + */ +#ifndef __MAX31855_H__ +#define __MAX31855_H__ + +#include <stdint.h> +#include <stdbool.h> +#include <driver/spi_master.h> +#include <driver/gpio.h> +#include <esp_err.h> + +#ifdef __cplusplus +extern "C" { +#endif + +#define MAX31855_MAX_CLOCK_SPEED_HZ (5000000) // 5 MHz + +/** + * Device descriptor + */ +typedef struct +{ + spi_device_interface_config_t spi_cfg; /**< SPI device configuration */ + spi_device_handle_t spi_dev; /**< SPI device handler */ +} max31855_t; + +/** + * @brief Initialize device descriptor + * + * @param dev Device descriptor + * @param host SPI host + * @param cs_pin CS GPIO number + * @param clock_speed_hz SPI clock speed, Hz + * @return `ESP_OK` on success + */ +esp_err_t max31855_init_desc(max31855_t *dev, spi_host_device_t host, uint32_t clock_speed_hz, gpio_num_t cs_pin); + +/** + * @brief Free device descriptor + * + * @param dev Device descriptor + * @return `ESP_OK` on success + */ +esp_err_t max31855_free_desc(max31855_t *dev); + +/** + * @brief Read temperatures and sensor status + * + * @param dev Device descriptor + * @param[out] tc_t Thermocouple temperature, degrees Celsius + * @param[out] cj_t Cold junction temperature, degrees Celsius (NULL-able) + * @param[out] scv true when the thermocouple is short-circuited to VCC + * @param[out] scg true when the thermocouple is short-circuited to GND + * @param[out] oc true when the thermocouple is open (no connections) + * @return + */ +esp_err_t max31855_get_temperature(max31855_t *dev, float *tc_t, float *cj_t, bool *scv, bool *scg, bool *oc); + +#ifdef __cplusplus +} +#endif + +/**@}*/ + +#endif /* __MAX31855_H__ */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/max31865/.eil.yml Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,21 @@ +--- +components: + - name: max31865 + description: Driver for MAX31865 resistance converter for platinum RTDs + group: temperature + groups: [] + code_owners: UncleRus + depends: + - driver + - log + thread_safe: yes + targets: + - name: esp32 + - name: esp32s2 + - name: esp32c3 + licenses: + - name: BSD-3 + copyrights: + - author: + name: UncleRus + year: 2021
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/max31865/CMakeLists.txt Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,5 @@ +idf_component_register( + SRCS max31865.c + INCLUDE_DIRS . + REQUIRES driver log +)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/max31865/LICENSE Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,26 @@ +Copyright (c) 2017, 2018 Ruslan V. Uss <unclerus@gmail.com> + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, +this list of conditions and the following disclaimer in the documentation +and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of itscontributors +may be used to endorse or promote products derived from this software without +specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/max31865/component.mk Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,2 @@ +COMPONENT_ADD_INCLUDEDIRS = . +COMPONENT_DEPENDS = driver log
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/max31865/max31865.c Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,314 @@ +/* + * Copyright (c) 2021 Ruslan V. Uss <unclerus@gmail.com> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of itscontributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file max31865.c + * + * ESP-IDF driver for MAX31865, resistance converter for platinum RTDs + * + * Copyright (c) 2021 Ruslan V. Uss <unclerus@gmail.com> + * + * BSD Licensed as described in the file LICENSE + */ +#include "max31865.h" +#include <string.h> +#include <esp_log.h> +#include <math.h> +#include <freertos/FreeRTOS.h> +#include <freertos/task.h> + +static const char *TAG = "max31865"; + +#define REG_CONFIG (0x00) +#define REG_RTD_MSB (0x01) +#define REG_HIGH_FAULT_MSB (0x03) +#define REG_LOW_FAULT_MSB (0x05) +#define REG_FAULT_STATUS (0x07) + +#define BIT_CONFIG_50HZ BIT(0) +#define BIT_CONFIG_FAULT_CLEAR BIT(1) +#define BIT_CONFIG_FAULT_D2 BIT(2) +#define BIT_CONFIG_FAULT_D3 BIT(3) +#define BIT_CONFIG_3WIRE BIT(4) +#define BIT_CONFIG_1SHOT BIT(5) +#define BIT_CONFIG_AUTO BIT(6) +#define BIT_CONFIG_VBIAS BIT(7) + +#define MASK_CONFIG_FAULT (BIT_CONFIG_FAULT_D2 | BIT_CONFIG_FAULT_D3) + +typedef struct +{ + float a, b; +} rtd_coeff_t; + +static const rtd_coeff_t rtd_coeff[] = { + [MAX31865_ITS90] = { .a = 3.9083e-3f, .b = -5.775e-7f }, + [MAX31865_DIN43760] = { .a = 3.9848e-3f, .b = -5.8019e-7f }, + [MAX31865_US_INDUSTRIAL] = { .a = 3.9692e-3f, .b = -5.8495e-7f }, +}; + +#define CHECK(x) do { esp_err_t __; if ((__ = x) != ESP_OK) return __; } while (0) +#define CHECK_ARG(VAL) do { if (!(VAL)) return ESP_ERR_INVALID_ARG; } while (0) + +static esp_err_t write_reg_8(max31865_t *dev, uint8_t reg, uint8_t val) +{ + spi_transaction_t t; + memset(&t, 0, sizeof(spi_transaction_t)); + + uint8_t tx[] = { reg | 0x80, val }; + + t.tx_buffer = tx; + t.length = sizeof(tx) * 8; + + return spi_device_transmit(dev->spi_dev, &t); +} + +static esp_err_t read_reg_8(max31865_t *dev, uint8_t reg, uint8_t *val) +{ + spi_transaction_t t; + memset(&t, 0, sizeof(spi_transaction_t)); + + uint8_t tx[] = { reg, 0 }; + uint8_t rx[sizeof(tx)]; + + t.tx_buffer = tx; + t.rx_buffer = rx; + t.length = sizeof(tx) * 8; + CHECK(spi_device_transmit(dev->spi_dev, &t)); + + *val = rx[1]; + + return ESP_OK; +} + +static esp_err_t read_reg_16(max31865_t *dev, uint8_t reg, uint16_t *val) +{ + spi_transaction_t t; + memset(&t, 0, sizeof(spi_transaction_t)); + + uint8_t tx[] = { reg, 0, 0 }; + uint8_t rx[sizeof(tx)]; + + t.tx_buffer = tx; + t.rx_buffer = rx; + t.length = sizeof(tx) * 8; + CHECK(spi_device_transmit(dev->spi_dev, &t)); + + *val = (uint16_t)rx[1] << 8; + *val |= rx[2]; + + return ESP_OK; +} + +/////////////////////////////////////////////////////////////////////////////// + +esp_err_t max31865_init_desc(max31865_t *dev, spi_host_device_t host, uint32_t clock_speed_hz, gpio_num_t cs_pin) +{ + CHECK_ARG(dev); + + memset(&dev->spi_cfg, 0, sizeof(dev->spi_cfg)); + dev->spi_cfg.spics_io_num = cs_pin; + dev->spi_cfg.clock_speed_hz = clock_speed_hz; + dev->spi_cfg.mode = 1; + dev->spi_cfg.queue_size = 1; + dev->spi_cfg.cs_ena_pretrans = 1; + //dev->spi_cfg.flags = SPI_DEVICE_NO_DUMMY; + + return spi_bus_add_device(host, &dev->spi_cfg, &dev->spi_dev); +} + +esp_err_t max31865_free_desc(max31865_t *dev) +{ + CHECK_ARG(dev); + + return spi_bus_remove_device(dev->spi_dev); +} + +esp_err_t max31865_set_config(max31865_t *dev, const max31865_config_t *config) +{ + CHECK_ARG(dev && config); + + uint8_t val; + CHECK(read_reg_8(dev, REG_CONFIG, &val)); + + val &= ~(BIT_CONFIG_AUTO | BIT_CONFIG_3WIRE | BIT_CONFIG_VBIAS | BIT_CONFIG_50HZ); + + val |= config->mode == MAX31865_MODE_AUTO ? BIT_CONFIG_AUTO : 0; + val |= config->connection == MAX31865_3WIRE ? BIT_CONFIG_3WIRE : 0; + val |= config->v_bias ? BIT_CONFIG_VBIAS : 0; + val |= config->filter == MAX31865_FILTER_50HZ ? BIT_CONFIG_50HZ : 0; + + CHECK(write_reg_8(dev, REG_CONFIG, val)); + + return ESP_OK; +} + +esp_err_t max31865_get_config(max31865_t *dev, max31865_config_t *config) +{ + CHECK_ARG(dev && config); + + uint8_t val; + CHECK(read_reg_8(dev, REG_CONFIG, &val)); + + config->filter = val & BIT_CONFIG_50HZ ? MAX31865_FILTER_50HZ : MAX31865_FILTER_60HZ; + config->v_bias = val & BIT_CONFIG_VBIAS ? 1 : 0; + config->mode = val & BIT_CONFIG_AUTO ? MAX31865_MODE_AUTO : MAX31865_MODE_SINGLE; + config->connection = val & BIT_CONFIG_3WIRE ? MAX31865_3WIRE : MAX31865_2WIRE; + + return ESP_OK; +} + +esp_err_t max31865_start_measurement(max31865_t *dev) +{ + CHECK_ARG(dev); + + uint8_t val; + CHECK(read_reg_8(dev, REG_CONFIG, &val)); + val |= BIT_CONFIG_1SHOT; + CHECK(write_reg_8(dev, REG_CONFIG, val)); + + return ESP_OK; +} + +esp_err_t max31865_read_raw(max31865_t *dev, uint16_t *raw, bool *fault) +{ + CHECK_ARG(dev && raw && fault); + + CHECK(read_reg_16(dev, REG_RTD_MSB, raw)); + *fault = *raw & 1; + *raw >>= 1; + + return ESP_OK; +} + +// conversion code taken from Adafruit library +esp_err_t max31865_read_temperature(max31865_t *dev, float *temp) +{ + CHECK_ARG(dev && temp && dev->standard <= MAX31865_US_INDUSTRIAL); + + uint16_t raw; + bool fault; + CHECK(max31865_read_raw(dev, &raw, &fault)); + if (fault) + { + ESP_LOGE(TAG, "[CS %d] Fault detected", dev->spi_cfg.spics_io_num); + return ESP_FAIL; + } + + float r_rtd = raw * dev->r_ref / 32768; + + ESP_LOGD(TAG, "[CS %d] RTD resistance: %.8f", dev->spi_cfg.spics_io_num, r_rtd); + + const rtd_coeff_t *c = rtd_coeff + dev->standard; + + *temp = (sqrtf((c->a * c->a - (4 * c->b)) + (4 * c->b / dev->rtd_nominal * r_rtd)) - c->a) / (2 * c->b); + + if (*temp >= 0) + return ESP_OK; + + // below zero + r_rtd = r_rtd / dev->rtd_nominal * 100; // normalize to 100 Ohm + + float rpoly = r_rtd; + + *temp = -242.02; + *temp += 2.2228 * rpoly; + rpoly *= r_rtd; // square + *temp += 2.5859e-3 * rpoly; + rpoly *= r_rtd; // ^3 + *temp -= 4.8260e-6 * rpoly; + rpoly *= r_rtd; // ^4 + *temp -= 2.8183e-8 * rpoly; + rpoly *= r_rtd; // ^5 + *temp += 1.5243e-10 * rpoly; + + return ESP_OK; +} + +esp_err_t max31865_measure(max31865_t *dev, float *temp) +{ + CHECK(max31865_start_measurement(dev)); + vTaskDelay(pdMS_TO_TICKS(70)); + return max31865_read_temperature(dev, temp); +} + +esp_err_t max31865_detect_fault_auto(max31865_t *dev) +{ + CHECK_ARG(dev); + + uint8_t conf; + CHECK(read_reg_8(dev, REG_CONFIG, &conf)); + + uint8_t fault_bits = conf & MASK_CONFIG_FAULT; + if (fault_bits == BIT_CONFIG_FAULT_D2) + { + ESP_LOGD(TAG, "[CS %d] Automatic fault detection still running", dev->spi_cfg.spics_io_num); + return ESP_ERR_INVALID_STATE; + } + if (fault_bits == BIT_CONFIG_FAULT_D3) + { + ESP_LOGD(TAG, "[CS %d] Manual cycle 1 still running, waiting for user to start cycle 2", dev->spi_cfg.spics_io_num); + return ESP_ERR_INVALID_STATE; + } + if (fault_bits == (BIT_CONFIG_FAULT_D2 | BIT_CONFIG_FAULT_D3)) + { + ESP_LOGD(TAG, "[CS %d] Manual cycle 2 still running", dev->spi_cfg.spics_io_num); + return ESP_ERR_INVALID_STATE; + } + + CHECK(write_reg_8(dev, REG_CONFIG, BIT_CONFIG_VBIAS | BIT_CONFIG_FAULT_D2)); + + uint8_t tmp; + do + { + // FIXME endless loop + CHECK(read_reg_8(dev, REG_CONFIG, &tmp)); + } + while (tmp & MASK_CONFIG_FAULT); + + return ESP_OK; +} + +esp_err_t max31865_get_fault_status(max31865_t *dev, uint8_t *fault_status) +{ + CHECK_ARG(dev && fault_status); + + return read_reg_8(dev, REG_FAULT_STATUS, fault_status); +} + +esp_err_t max31865_clear_fault_status(max31865_t *dev) +{ + CHECK_ARG(dev); + + uint8_t val; + CHECK(read_reg_8(dev, REG_CONFIG, &val)); + val &= ~(BIT_CONFIG_1SHOT | BIT_CONFIG_FAULT_D2 | BIT_CONFIG_FAULT_D3); + val |= BIT_CONFIG_FAULT_CLEAR; + CHECK(write_reg_8(dev, REG_CONFIG, val)); + + return ESP_OK; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/max31865/max31865.h Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,226 @@ +/* + * Copyright (c) 2021 Ruslan V. Uss <unclerus@gmail.com> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of itscontributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file max31865.h + * @defgroup max31865 max31865 + * @{ + * + * ESP-IDF driver for MAX31865, resistance converter for platinum RTDs + * + * Copyright (c) 2021 Ruslan V. Uss <unclerus@gmail.com> + * + * BSD Licensed as described in the file LICENSE + */ +#ifndef __MAX31865_H__ +#define __MAX31865_H__ + +#include <stdint.h> +#include <stdbool.h> +#include <driver/spi_master.h> +#include <driver/gpio.h> +#include <esp_err.h> + +#ifdef __cplusplus +extern "C" { +#endif + +#define MAX31865_MAX_CLOCK_SPEED_HZ (1000000) // 1 MHz + +/** + * Conversion mode + */ +typedef enum { + MAX31865_MODE_SINGLE = 0, /**< Single consersion mode, default */ + MAX31865_MODE_AUTO /**< Automatic conversion mode at 50/60Hz rate */ +} max31865_mode_t; + +/** + * Notch frequencies for the noise rejection filter + */ +typedef enum { + MAX31865_FILTER_60HZ = 0, /**< 60Hz */ + MAX31865_FILTER_50HZ /**< 50Hz */ +} max31865_filter_t; + +/** + * Connection type + */ +typedef enum { + MAX31865_2WIRE = 0, /**< 2 wires */ + MAX31865_3WIRE, /**< 3 wires */ + MAX31865_4WIRE /**< 4 wires */ +} max31865_connection_type_t; + +/** + * Device configuration + */ +typedef struct +{ + max31865_mode_t mode; + max31865_connection_type_t connection; + bool v_bias; + max31865_filter_t filter; +} max31865_config_t; + +/** + * Temperature scale standard + */ +typedef enum { + MAX31865_ITS90 = 0, /**< ITS-90 */ + MAX31865_DIN43760, /**< DIN43760 */ + MAX31865_US_INDUSTRIAL /**< US INDUSTRIAL */ +} max31865_standard_t; + +/** + * Device descriptor + */ +typedef struct +{ + spi_device_interface_config_t spi_cfg; /**< SPI device configuration */ + spi_device_handle_t spi_dev; /**< SPI device handler */ + max31865_standard_t standard; /**< Temperature scale standard */ + float r_ref; /**< Reference resistor value, Ohms */ + float rtd_nominal; /**< RTD nominal resistance at 0 deg. C, Ohms (PT100 - 100 Ohms, PT1000 - 1000 Ohms) */ +} max31865_t; + +/** + * @brief Initialize device descriptor + * + * @param dev Device descriptor + * @param host SPI host + * @param clock_speed_hz SPI clock speed, Hz + * @param cs_pin CS GPIO number + * @return `ESP_OK` on success + */ +esp_err_t max31865_init_desc(max31865_t *dev, spi_host_device_t host, uint32_t clock_speed_hz, gpio_num_t cs_pin); + +/** + * @brief Free device descriptor + * + * @param dev Device descriptor + * @return `ESP_OK` on success + */ +esp_err_t max31865_free_desc(max31865_t *dev); + +/** + * @brief Configure device + * + * @param dev Device descriptor + * @param config Configuration + * @return `ESP_OK` on success + */ +esp_err_t max31865_set_config(max31865_t *dev, const max31865_config_t *config); + +/** + * @brief Read device configuration + * + * @param dev Device descriptor + * @param[out] config Configuration + * @return `ESP_OK` on success + */ +esp_err_t max31865_get_config(max31865_t *dev, max31865_config_t *config); + +/** + * @brief Trigger single-shot measurement + * + * @param dev Device descriptor + * @return `ESP_OK` on success + */ +esp_err_t max31865_start_measurement(max31865_t *dev); + +/** + * @brief Read raw RTD value + * + * @param dev Device descritptor + * @param[out] raw Raw 15 bits RTD value + * @param[out] fault true when fault is detected + * @return `ESP_OK` on success + */ +esp_err_t max31865_read_raw(max31865_t *dev, uint16_t *raw, bool *fault); + +/** + * @brief Read RTD value and convert it temperature + * + * @param dev Device descritptor + * @param[out] temp Temperature, deg. Celsius + * @return `ESP_OK` on success + */ +esp_err_t max31865_read_temperature(max31865_t *dev, float *temp); + +/** + * @brief Measure temperature + * + * Run full cycle of single-shot measurement: + * - trigger measurement + * - wait for 70ms + * - read and convert RTD value + * + * @param dev Device descritptor + * @param[out] temp Temperature, deg. Celsius + * @return `ESP_OK` on success + */ +esp_err_t max31865_measure(max31865_t *dev, float *temp); + +/** + * @brief Run automatical fault detection cycle + * + * After calling this function, device must be reconfigured + * + * @param dev Device descriptor + * @return `ESP_OK` on success + */ +esp_err_t max31865_detect_fault_auto(max31865_t *dev); + +/** + * @brief Get bits of current fault status + * + * See datasheet for fault status interpretation + * + * @param dev Device descriptor + * @param[out] fault_status Fault status bits + * @return `ESP_OK` on success + */ +esp_err_t max31865_get_fault_status(max31865_t *dev, uint8_t *fault_status); + +/** + * @brief Clear current fault status + * + * After calling this function, device must be reconfigured + * + * @param dev Device descriptor + * @return `ESP_OK` on success + */ +esp_err_t max31865_clear_fault_status(max31865_t *dev); + +#ifdef __cplusplus +} +#endif + +/**@}*/ + +#endif /* __MAX31865_H__ */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/max7219/.eil.yml Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,21 @@ +--- +components: + - name: max7219 + description: Driver for 8-Digit LED display drivers, MAX7219/MAX7221 + group: led + groups: [] + code_owners: UncleRus + depends: + - driver + - log + thread_safe: yes + targets: + - name: esp32 + - name: esp32s2 + - name: esp32c3 + licenses: + - name: BSD-3 + copyrights: + - author: + name: UncleRus + year: 2019
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/max7219/CMakeLists.txt Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,5 @@ +idf_component_register( + SRCS max7219.c + INCLUDE_DIRS . + REQUIRES driver log +)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/max7219/LICENSE Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,26 @@ +Copyright (c) 2017, 2018 Ruslan V. Uss <unclerus@gmail.com> + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, +this list of conditions and the following disclaimer in the documentation +and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of itscontributors +may be used to endorse or promote products derived from this software without +specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/max7219/component.mk Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,2 @@ +COMPONENT_ADD_INCLUDEDIRS = . +COMPONENT_DEPENDS = driver log
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/max7219/max7219.c Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,263 @@ +/* + * Copyright (c) 2019 Ruslan V. Uss <unclerus@gmail.com> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of itscontributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file max7219.c + * + * ESP-IDF driver for MAX7219/MAX7221 + * Serially Interfaced, 8-Digit LED Display Drivers + * + * Ported from esp-open-rtos + * + * Copyright (c) 2017 Ruslan V. Uss <unclerus@gmail.com> + * + * BSD Licensed as described in the file LICENSE + */ +#include "max7219.h" +#include <string.h> +#include <esp_log.h> + +#include "max7219_priv.h" + +static const char *TAG = "max7219"; + +#define ALL_CHIPS 0xff +#define ALL_DIGITS 8 + +#define REG_DIGIT_0 (1 << 8) +#define REG_DECODE_MODE (9 << 8) +#define REG_INTENSITY (10 << 8) +#define REG_SCAN_LIMIT (11 << 8) +#define REG_SHUTDOWN (12 << 8) +#define REG_DISPLAY_TEST (15 << 8) + +#define VAL_CLEAR_BCD 0x0f +#define VAL_CLEAR_NORMAL 0x00 + +#define CHECK(x) do { esp_err_t __; if ((__ = x) != ESP_OK) return __; } while (0) +#define CHECK_ARG(VAL) do { if (!(VAL)) return ESP_ERR_INVALID_ARG; } while (0) + +static inline uint16_t shuffle(uint16_t val) +{ + return (val >> 8) | (val << 8); +} + +static esp_err_t send(max7219_t *dev, uint8_t chip, uint16_t value) +{ + uint16_t buf[MAX7219_MAX_CASCADE_SIZE] = { 0 }; + if (chip == ALL_CHIPS) + { + for (uint8_t i = 0; i < dev->cascade_size; i++) + buf[i] = shuffle(value); + } + else buf[chip] = shuffle(value); + + spi_transaction_t t; + memset(&t, 0, sizeof(t)); + t.length = dev->cascade_size * 16; + t.tx_buffer = buf; + return spi_device_transmit(dev->spi_dev, &t); +} + +inline static uint8_t get_char(max7219_t *dev, char c) +{ + if (dev->bcd) + { + if (c >= '0' && c <= '9') + return c - '0'; + switch (c) + { + case '-': + return 0x0a; + case 'E': + case 'e': + return 0x0b; + case 'H': + case 'h': + return 0x0c; + case 'L': + case 'l': + return 0x0d; + case 'P': + case 'p': + return 0x0e; + } + return VAL_CLEAR_BCD; + } + + return font_7seg[(c - 0x20) & 0x7f]; +} + +/////////////////////////////////////////////////////////////////////////////// + +esp_err_t max7219_init_desc(max7219_t *dev, spi_host_device_t host, uint32_t clock_speed_hz, gpio_num_t cs_pin) +{ + CHECK_ARG(dev); + + memset(&dev->spi_cfg, 0, sizeof(dev->spi_cfg)); + dev->spi_cfg.spics_io_num = cs_pin; + dev->spi_cfg.clock_speed_hz = clock_speed_hz; + dev->spi_cfg.mode = 0; + dev->spi_cfg.queue_size = 1; + dev->spi_cfg.flags = SPI_DEVICE_NO_DUMMY; + + return spi_bus_add_device(host, &dev->spi_cfg, &dev->spi_dev); +} + +esp_err_t max7219_free_desc(max7219_t *dev) +{ + CHECK_ARG(dev); + + return spi_bus_remove_device(dev->spi_dev); +} + +esp_err_t max7219_init(max7219_t *dev) +{ + CHECK_ARG(dev); + if (!dev->cascade_size || dev->cascade_size > MAX7219_MAX_CASCADE_SIZE) + { + ESP_LOGE(TAG, "Invalid cascade size %d", dev->cascade_size); + return ESP_ERR_INVALID_ARG; + } + + uint8_t max_digits = dev->cascade_size * ALL_DIGITS; + if (dev->digits > max_digits) + { + ESP_LOGE(TAG, "Invalid digits count %d, max %d", dev->digits, max_digits); + return ESP_ERR_INVALID_ARG; + } + if (!dev->digits) + dev->digits = max_digits; + + // Shutdown all chips + CHECK(max7219_set_shutdown_mode(dev, true)); + // Disable test + CHECK(send(dev, ALL_CHIPS, REG_DISPLAY_TEST)); + // Set max scan limit + CHECK(send(dev, ALL_CHIPS, REG_SCAN_LIMIT | (ALL_DIGITS - 1))); + // Set normal decode mode & clear display + CHECK(max7219_set_decode_mode(dev, false)); + // Set minimal brightness + CHECK(max7219_set_brightness(dev, 0)); + // Wake up + CHECK(max7219_set_shutdown_mode(dev, false)); + + return ESP_OK; +} + +esp_err_t max7219_set_decode_mode(max7219_t *dev, bool bcd) +{ + CHECK_ARG(dev); + + dev->bcd = bcd; + CHECK(send(dev, ALL_CHIPS, REG_DECODE_MODE | (bcd ? 0xff : 0))); + CHECK(max7219_clear(dev)); + + return ESP_OK; +} + +esp_err_t max7219_set_brightness(max7219_t *dev, uint8_t value) +{ + CHECK_ARG(dev); + CHECK_ARG(value <= MAX7219_MAX_BRIGHTNESS); + + CHECK(send(dev, ALL_CHIPS, REG_INTENSITY | value)); + + return ESP_OK; +} + +esp_err_t max7219_set_shutdown_mode(max7219_t *dev, bool shutdown) +{ + CHECK_ARG(dev); + + CHECK(send(dev, ALL_CHIPS, REG_SHUTDOWN | !shutdown)); + + return ESP_OK; +} + +esp_err_t max7219_set_digit(max7219_t *dev, uint8_t digit, uint8_t val) +{ + CHECK_ARG(dev); + if (digit >= dev->digits) + { + ESP_LOGE(TAG, "Invalid digit: %d", digit); + return ESP_ERR_INVALID_ARG; + } + + if (dev->mirrored) + digit = dev->digits - digit - 1; + + uint8_t c = digit / ALL_DIGITS; + uint8_t d = digit % ALL_DIGITS; + + ESP_LOGV(TAG, "Chip %d, digit %d val 0x%02x", c, d, val); + + CHECK(send(dev, c, (REG_DIGIT_0 + ((uint16_t)d << 8)) | val)); + + return ESP_OK; +} + +esp_err_t max7219_clear(max7219_t *dev) +{ + CHECK_ARG(dev); + + uint8_t val = dev->bcd ? VAL_CLEAR_BCD : VAL_CLEAR_NORMAL; + for (uint8_t i = 0; i < ALL_DIGITS; i++) + CHECK(send(dev, ALL_CHIPS, (REG_DIGIT_0 + ((uint16_t)i << 8)) | val)); + + return ESP_OK; +} + +esp_err_t max7219_draw_text_7seg(max7219_t *dev, uint8_t pos, const char *s) +{ + CHECK_ARG(dev && s); + + while (*s && pos < dev->digits) + { + uint8_t c = get_char(dev, *s); + if (*(s + 1) == '.') + { + c |= 0x80; + s++; + } + CHECK(max7219_set_digit(dev, pos, c)); + pos++; + s++; + } + + return ESP_OK; +} + +esp_err_t max7219_draw_image_8x8(max7219_t *dev, uint8_t pos, const void *image) +{ + CHECK_ARG(dev && image); + + for (uint8_t i = pos, offs = 0; i < dev->digits && offs < 8; i++, offs++) + max7219_set_digit(dev, i, *((uint8_t *)image + offs)); + + return ESP_OK; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/max7219/max7219.h Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,174 @@ +/* + * Copyright (c) 2019 Ruslan V. Uss <unclerus@gmail.com> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of itscontributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file max7219.h + * @defgroup max7219 max7219 + * @{ + * + * ESP-IDF driver for MAX7219/MAX7221 + * Serially Interfaced, 8-Digit LED Display Drivers + * + * Ported from esp-open-rtos + * + * Copyright (c) 2017 Ruslan V. Uss <unclerus@gmail.com> + * + * BSD Licensed as described in the file LICENSE + */ +#ifndef __MAX7219_H__ +#define __MAX7219_H__ + +#include <stdint.h> +#include <stdbool.h> +#include <driver/spi_master.h> +#include <driver/gpio.h> // add by nopnop2002 +#include <esp_err.h> + +#ifdef __cplusplus +extern "C" { +#endif + +#define MAX7219_MAX_CLOCK_SPEED_HZ (10000000) // 10 MHz + +#define MAX7219_MAX_CASCADE_SIZE 8 +#define MAX7219_MAX_BRIGHTNESS 15 + +/** + * Display descriptor + */ +typedef struct +{ + spi_device_interface_config_t spi_cfg; + spi_device_handle_t spi_dev; + uint8_t digits; //!< Accessible digits in 7seg. Up to cascade_size * 8 + uint8_t cascade_size; //!< Up to `MAX7219_MAX_CASCADE_SIZE` MAX721xx cascaded + bool mirrored; //!< true for horizontally mirrored displays + bool bcd; +} max7219_t; + +/** + * @brief Initialize device descriptor + * + * @param dev Device descriptor + * @param host SPI host + * @param clock_speed_hz SPI clock speed, Hz + * @param cs_pin CS GPIO number + * @return `ESP_OK` on success + */ +esp_err_t max7219_init_desc(max7219_t *dev, spi_host_device_t host, uint32_t clock_speed_hz, gpio_num_t cs_pin); + +/** + * @brief Free device descriptor + * + * @param dev Device descriptor + * @return `ESP_OK` on success + */ +esp_err_t max7219_free_desc(max7219_t *dev); + +/** + * @brief Initialize display + * + * Switch it to normal operation from shutdown mode, + * set scan limit to the max and clear + * + * @param dev Display descriptor + * @return `ESP_OK` on success + */ +esp_err_t max7219_init(max7219_t *dev); + +/** + * @brief Set decode mode and clear display + * + * @param dev Display descriptor + * @param bcd true to set BCD decode mode, false to normal + * @return `ESP_OK` on success + */ +esp_err_t max7219_set_decode_mode(max7219_t *dev, bool bcd); + +/** + * @brief Set display brightness + * + * @param dev Display descriptor + * @param value Brightness value, 0..MAX7219_MAX_BRIGHTNESS + * @return `ESP_OK` on success + */ +esp_err_t max7219_set_brightness(max7219_t *dev, uint8_t value); + +/** + * @brief Shutdown display or set it to normal mode + * + * @param dev Display descriptor + * @param shutdown Shutdown display if true + * @return `ESP_OK` on success + */ +esp_err_t max7219_set_shutdown_mode(max7219_t *dev, bool shutdown); + +/** + * @brief Write data to display digit + * + * @param dev Display descriptor + * @param digit Digit index, 0..dev->digits - 1 + * @param val Data + * @return `ESP_OK` on success + */ +esp_err_t max7219_set_digit(max7219_t *dev, uint8_t digit, uint8_t val); + +/** + * @brief Clear display + * + * @param dev Display descriptor + * @return `ESP_OK` on success + */ +esp_err_t max7219_clear(max7219_t *dev); + +/** + * @brief Draw text on 7-segment display + * + * @param dev Display descriptor + * @param pos Start digit + * @param s Text + * @return `ESP_OK` on success + */ +esp_err_t max7219_draw_text_7seg(max7219_t *dev, uint8_t pos, const char *s); + +/** + * @brief Draw 64-bit image on 8x8 matrix + * + * @param dev Display descriptor + * @param pos Start digit + * @param image 64-bit buffer with image data + * @return `ESP_OK` on success + */ +esp_err_t max7219_draw_image_8x8(max7219_t *dev, uint8_t pos, const void *image); + +#ifdef __cplusplus +} +#endif + +/**@}*/ + +#endif /* __MAX7219_H__ */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/max7219/max7219_priv.h Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2019 Ruslan V. Uss <unclerus@gmail.com> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of itscontributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file max7219_priv.h + * + * ESP-IDF driver for MAX7219/MAX7221 + * Serially Interfaced, 8-Digit LED Display Drivers + * + * Ported from esp-open-rtos + * + * Copyright (c) 2017, 2018 Ruslan V. Uss <unclerus@gmail.com> + * + * BSD Licensed as described in the file LICENSE + */ +#ifndef __MAX7219_PRIV_H__ +#define __MAX7219_PRIV_H__ + +static const uint8_t font_7seg[] = { + /* ' ' ! " # $ % & ' ( ) */ + 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x00, 0x02, 0x4e, 0x78, + /* * + , - . / 0 1 2 3 */ + 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x7e, 0x30, 0x6d, 0x79, + /* 4 5 6 7 8 9 : ; < = */ + 0x33, 0x5b, 0x5f, 0x70, 0x7f, 0x7b, 0x00, 0x00, 0x0d, 0x09, + /* > ? @ A B C D E F G */ + 0x19, 0x65, 0x00, 0x77, 0x1f, 0x4e, 0x3d, 0x4f, 0x47, 0x5e, + /* H I J K L M N O P Q */ + 0x37, 0x06, 0x38, 0x57, 0x0e, 0x76, 0x15, 0x1d, 0x67, 0x73, + /* R S T U V W X Y Z [ */ + 0x05, 0x5b, 0x0f, 0x1c, 0x3e, 0x2a, 0x49, 0x3b, 0x6d, 0x4e, + /* \ ] ^ _ ` a b c d e */ + 0x00, 0x78, 0x00, 0x08, 0x02, 0x77, 0x1f, 0x4e, 0x3d, 0x4f, + /* f g h i j k l m n o */ + 0x47, 0x5e, 0x37, 0x06, 0x38, 0x57, 0x0e, 0x76, 0x15, 0x1d, + /* p q r s t u v w x y */ + 0x67, 0x73, 0x05, 0x5b, 0x0f, 0x1c, 0x3e, 0x2a, 0x49, 0x3b, + /* z { | } ~ */ + 0x6d, 0x4e, 0x06, 0x78, 0x00 +}; + + +#endif /* __MAX7219_PRIV_H__ */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/mcp23008/.eil.yml Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,23 @@ +--- +components: + - name: mcp23008 + description: Driver for 8-bit I2C GPIO expander MCP23008 + group: gpio + groups: [] + code_owners: UncleRus + depends: + - i2cdev + - log + - esp_idf_lib_helpers + thread_safe: yes + targets: + - name: esp32 + - name: esp8266 + - name: esp32s2 + - name: esp32c3 + licenses: + - name: BSD-3 + copyrights: + - author: + name: UncleRus + year: 2018
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/mcp23008/CMakeLists.txt Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,5 @@ +idf_component_register( + SRCS mcp23008.c + INCLUDE_DIRS . + REQUIRES i2cdev log esp_idf_lib_helpers +)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/mcp23008/LICENSE Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,26 @@ +Copyright (c) 2018 Ruslan V. Uss (https://github.com/UncleRus) + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, +this list of conditions and the following disclaimer in the documentation +and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of itscontributors +may be used to endorse or promote products derived from this software without +specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/mcp23008/component.mk Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,2 @@ +COMPONENT_ADD_INCLUDEDIRS = . +COMPONENT_DEPENDS = i2cdev log esp_idf_lib_helpers
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/mcp23008/mcp23008.c Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,302 @@ +/* + * Copyright (c) 2018 Ruslan V. Uss <unclerus@gmail.com> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of itscontributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file mcp23008.c + * + * ESP-IDF driver for I2C 8 bit GPIO expander MCP23008 + * + * Copyright (c) 2018 Ruslan V. Uss <unclerus@gmail.com> + * + * BSD Licensed as described in the file LICENSE + */ + +#include <esp_log.h> +#include <esp_idf_lib_helpers.h> +#include "mcp23008.h" + +#define I2C_FREQ_HZ 1000000 // Max 1MHz for esp-idf, but device supports up to 1.7Mhz + +#define REG_IODIR 0x00 +#define REG_IPOL 0x01 +#define REG_GPINTEN 0x02 +#define REG_DEFVAL 0x03 +#define REG_INTCON 0x04 +#define REG_IOCON 0x05 +#define REG_GPPU 0x06 +#define REG_INTF 0x07 +#define REG_INTCAP 0x08 +#define REG_GPIO 0x09 +#define REG_OLAT 0x0a + +#define BIT_IOCON_INTPOL 1 +#define BIT_IOCON_ODR 2 +//#define BIT_IOCON_HAEN 3 +#define BIT_IOCON_DISSLW 4 +#define BIT_IOCON_SREAD 5 + +static const char *TAG = "mcp23008"; + +#define CHECK(x) do { esp_err_t __; if ((__ = x) != ESP_OK) return __; } while (0) +#define CHECK_ARG(VAL) do { if (!(VAL)) return ESP_ERR_INVALID_ARG; } while (0) +#define BV(x) (1 << (x)) + +static esp_err_t read_reg(i2c_dev_t *dev, uint8_t reg, uint8_t *val) +{ + CHECK_ARG(dev && val); + + I2C_DEV_TAKE_MUTEX(dev); + I2C_DEV_CHECK(dev, i2c_dev_read_reg(dev, reg, val, 1)); + I2C_DEV_GIVE_MUTEX(dev); + + return ESP_OK; +} + +static esp_err_t write_reg(i2c_dev_t *dev, uint8_t reg, uint8_t val) +{ + CHECK_ARG(dev); + + I2C_DEV_TAKE_MUTEX(dev); + I2C_DEV_CHECK(dev, i2c_dev_write_reg(dev, reg, &val, 1)); + I2C_DEV_GIVE_MUTEX(dev); + + return ESP_OK; +} + +static esp_err_t read_reg_bit(i2c_dev_t *dev, uint8_t reg, bool *val, uint8_t bit) +{ + CHECK_ARG(dev && val); + + uint8_t buf; + + I2C_DEV_TAKE_MUTEX(dev); + I2C_DEV_CHECK(dev, i2c_dev_read_reg(dev, reg, &buf, 1)); + I2C_DEV_GIVE_MUTEX(dev); + + *val = (buf & BV(bit)) >> bit; + + return ESP_OK; +} + +static esp_err_t write_reg_bit(i2c_dev_t *dev, uint8_t reg, bool val, uint8_t bit) +{ + CHECK_ARG(dev); + + uint8_t buf; + + I2C_DEV_TAKE_MUTEX(dev); + I2C_DEV_CHECK(dev, i2c_dev_read_reg(dev, reg, &buf, 1)); + buf = (buf & ~BV(bit)) | (val ? BV(bit) : 0); + I2C_DEV_CHECK(dev, i2c_dev_write_reg(dev, reg, &buf, 1)); + I2C_DEV_GIVE_MUTEX(dev); + + return ESP_OK; +} + +/////////////////////////////////////////////////////////////////////////////// + +esp_err_t mcp23008_init_desc(i2c_dev_t *dev, uint8_t addr, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio) +{ + CHECK_ARG(dev); + if (addr < MCP23008_I2C_ADDR_BASE || addr > MCP23008_I2C_ADDR_BASE + 7) + { + ESP_LOGE(TAG, "Invalid device address: 0x%02x", addr); + return ESP_ERR_INVALID_ARG; + } + + dev->port = port; + dev->addr = addr; + dev->cfg.sda_io_num = sda_gpio; + dev->cfg.scl_io_num = scl_gpio; +#if HELPER_TARGET_IS_ESP32 + dev->cfg.master.clk_speed = I2C_FREQ_HZ; +#endif + + return i2c_dev_create_mutex(dev); +} + +esp_err_t mcp23008_free_desc(i2c_dev_t *dev) +{ + CHECK_ARG(dev); + + return i2c_dev_delete_mutex(dev); +} + +esp_err_t mcp23008_get_int_out_mode(i2c_dev_t *dev, mcp23008_int_out_mode_t *mode) +{ + CHECK_ARG(mode); + + uint8_t r; + CHECK(read_reg(dev, REG_IOCON, &r)); + + if (r & BV(BIT_IOCON_ODR)) + { + *mode = MCP23008_OPEN_DRAIN; + return ESP_OK; + } + + *mode = r & BV(BIT_IOCON_INTPOL) ? MCP23008_ACTIVE_HIGH : MCP23008_ACTIVE_LOW; + return ESP_OK; +} + +esp_err_t mcp23008_set_int_out_mode(i2c_dev_t *dev, mcp23008_int_out_mode_t mode) +{ + if (mode == MCP23008_OPEN_DRAIN) + return write_reg_bit(dev, REG_IOCON, true, BIT_IOCON_ODR); + + return write_reg_bit(dev, REG_IOCON, mode == MCP23008_ACTIVE_HIGH, BIT_IOCON_INTPOL); +} + +esp_err_t mcp23008_port_get_mode(i2c_dev_t *dev, uint8_t *val) +{ + return read_reg(dev, REG_IODIR, val); +} + +esp_err_t mcp23008_port_set_mode(i2c_dev_t *dev, uint8_t val) +{ + return write_reg(dev, REG_IODIR, val); +} + +esp_err_t mcp23008_port_get_pullup(i2c_dev_t *dev, uint8_t *val) +{ + return read_reg(dev, REG_GPPU, val); +} + +esp_err_t mcp23008_port_set_pullup(i2c_dev_t *dev, uint8_t val) +{ + return write_reg(dev, REG_GPPU, val); +} + +esp_err_t mcp23008_port_read(i2c_dev_t *dev, uint8_t *val) +{ + return read_reg(dev, REG_GPIO, val); +} + +esp_err_t mcp23008_port_write(i2c_dev_t *dev, uint8_t val) +{ + return write_reg(dev, REG_GPIO, val); +} + +esp_err_t mcp23008_get_mode(i2c_dev_t *dev, uint8_t pin, mcp23008_gpio_mode_t *mode) +{ + CHECK_ARG(mode && pin < 8); + + bool buf; + CHECK(read_reg_bit(dev, REG_IODIR, &buf, pin)); + *mode = buf ? MCP23008_GPIO_INPUT : MCP23008_GPIO_OUTPUT; + + return ESP_OK; +} + +esp_err_t mcp23008_set_mode(i2c_dev_t *dev, uint8_t pin, mcp23008_gpio_mode_t mode) +{ + CHECK_ARG(pin < 8); + + return write_reg_bit(dev, REG_IODIR, mode, pin); +} + +esp_err_t mcp23008_get_pullup(i2c_dev_t *dev, uint8_t pin, bool *enable) +{ + CHECK_ARG(pin < 8); + + return read_reg_bit(dev, REG_GPPU, enable, pin); +} + +esp_err_t mcp23008_set_pullup(i2c_dev_t *dev, uint8_t pin, bool enable) +{ + CHECK_ARG(pin < 8); + + return write_reg_bit(dev, REG_GPPU, enable, pin); +} + +esp_err_t mcp23008_get_level(i2c_dev_t *dev, uint8_t pin, uint32_t *val) +{ + CHECK_ARG(val && pin < 8); + + bool buf; + CHECK(read_reg_bit(dev, REG_GPIO, &buf, pin)); + *val = buf ? 1 : 0; + + return ESP_OK; +} + +esp_err_t mcp23008_set_level(i2c_dev_t *dev, uint8_t pin, uint32_t val) +{ + CHECK_ARG(pin < 8); + + return write_reg_bit(dev, REG_GPIO, val, pin); +} + +esp_err_t mcp23008_port_set_interrupt(i2c_dev_t *dev, uint8_t mask, mcp23008_gpio_intr_t intr) +{ + CHECK_ARG(dev); + + uint8_t int_en; + CHECK(read_reg(dev, REG_GPINTEN, &int_en)); + + if (intr == MCP23008_INT_DISABLED) + { + // disable interrupts + int_en &= ~mask; + CHECK(write_reg(dev, REG_GPINTEN, int_en)); + + return ESP_OK; + } + + uint8_t int_con; + CHECK(read_reg(dev, REG_INTCON, &int_con)); + + if (intr == MCP23008_INT_ANY_EDGE) + int_con &= ~mask; + else + { + int_con |= mask; + + uint8_t int_def; + CHECK(read_reg(dev, REG_DEFVAL, &int_def)); + if (intr == MCP23008_INT_LOW_EDGE) + int_def |= mask; + else + int_def &= ~mask; + CHECK(write_reg(dev, REG_DEFVAL, int_def)); + } + + CHECK(write_reg(dev, REG_INTCON, int_con)); + + // enable interrupts + int_en |= mask; + CHECK(write_reg(dev, REG_GPINTEN, int_en)); + + return ESP_OK; +} + +esp_err_t mcp23008_set_interrupt(i2c_dev_t *dev, uint8_t pin, mcp23008_gpio_intr_t intr) +{ + CHECK_ARG(pin < 8); + + return mcp23008_port_set_interrupt(dev, BV(pin), intr); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/mcp23008/mcp23008.h Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,273 @@ +/* + * Copyright (c) 2018 Ruslan V. Uss <unclerus@gmail.com> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of itscontributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file mcp23008.h + * @defgroup mcp23008 mcp23008 + * @{ + * + * ESP-IDF driver for I2C 8 bit GPIO expander MCP23008 + * + * Copyright (c) 2018 Ruslan V. Uss <unclerus@gmail.com> + * + * BSD Licensed as described in the file LICENSE + */ +#ifndef __MCP23008_H__ +#define __MCP23008_H__ + +#include <stdbool.h> +#include <i2cdev.h> +#include <esp_err.h> + +#define MCP23008_I2C_ADDR_BASE 0x20 + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * GPIO mode + */ +typedef enum +{ + MCP23008_GPIO_OUTPUT = 0, + MCP23008_GPIO_INPUT +} mcp23008_gpio_mode_t; + +/** + * INTA/INTB pins mode + */ +typedef enum +{ + MCP23008_ACTIVE_LOW = 0, //!< Low level on interrupt + MCP23008_ACTIVE_HIGH, //!< High level on interrupt + MCP23008_OPEN_DRAIN //!< Open drain +} mcp23008_int_out_mode_t; + +/** + * Interrupt mode + */ +typedef enum +{ + MCP23008_INT_DISABLED = 0, //!< No interrupt + MCP23008_INT_LOW_EDGE, //!< Interrupt on low edge + MCP23008_INT_HIGH_EDGE, //!< Interrupt on high edge + MCP23008_INT_ANY_EDGE //!< Interrupt on any edge +} mcp23008_gpio_intr_t; + +/** + * @brief Initialize device descriptor + * + * default SCL frequency is 1MHz + * + * @param dev Pointer to I2C device descriptor + * @param port I2C port number + * @param addr I2C address, + * @param sda_gpio SDA GPIO + * @param scl_gpio SCL GPIO + * @return `ESP_OK` on success + */ +esp_err_t mcp23008_init_desc(i2c_dev_t *dev, uint8_t addr, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio); + +/** + * @brief Free device descriptor + * + * @param dev Pointer to I2C device descriptor + * @return `ESP_OK` on success + */ +esp_err_t mcp23008_free_desc(i2c_dev_t *dev); + +/** + * @brief Get INT pins mode + * + * @param dev Pointer to I2C device descriptor + * @param[out] mode Buffer to store mode + * @return `ESP_OK` on success + */ +esp_err_t mcp23008_get_int_out_mode(i2c_dev_t *dev, mcp23008_int_out_mode_t *mode); + +/** + * @brief Set INT pins mode + * + * @param dev Pointer to I2C device descriptor + * @param mode INT pins mode + * @return `ESP_OK` on success + */ +esp_err_t mcp23008_set_int_out_mode(i2c_dev_t *dev, mcp23008_int_out_mode_t mode); + +/** + * @brief Get GPIO pins mode + * + * 0 - output, 1 - input for each bit in `val` + * + * @param dev Pointer to I2C device descriptor + * @param[out] val Buffer to store mode, 0 bit for GPIO0..7 bit for GPIO7 + * @return + */ +esp_err_t mcp23008_port_get_mode(i2c_dev_t *dev, uint8_t *val); + +/** + * @brief Set GPIO pins mode + * + * 0 - output, 1 - input for each bit in `val` + * + * @param dev Pointer to I2C device descriptor + * @param val Mode, 0 bit for GPIO0..7 bit for GPIO7 + * @return `ESP_OK` on success + */ +esp_err_t mcp23008_port_set_mode(i2c_dev_t *dev, uint8_t val); + +/** + * @brief Get GPIO pullups status + * + * 0 - pullup disabled, 1 - pullup enabled for each bit in `val` + * + * @param dev Pointer to I2C device descriptor + * @param[out] val Pullup status, 0 bit for GPIO0..7 bit for GPIO7 + * @return `ESP_OK` on success + */ +esp_err_t mcp23008_port_get_pullup(i2c_dev_t *dev, uint8_t *val); + +/** + * @brief Set GPIO pullups status + * + * 0 - pullup disabled, 1 - pullup enabled for each bit in `val` + * + * @param dev Pointer to I2C device descriptor + * @param val Pullup status, 0 bit for GPIO0..7 bit for GPIO7 + * @return `ESP_OK` on success + */ +esp_err_t mcp23008_port_set_pullup(i2c_dev_t *dev, uint8_t val); + +/** + * @brief Read GPIO port value + * + * @param dev Pointer to I2C device descriptor + * @param[out] val 8-bit GPIO port value, 0 bit for GPIO0..7 bit for GPIO7 + * @return `ESP_OK` on success + */ +esp_err_t mcp23008_port_read(i2c_dev_t *dev, uint8_t *val); + +/** + * @brief Write value to GPIO port + * + * @param dev Pointer to I2C device descriptor + * @param val GPIO port value, 0 bit for GPIO0..7 bit for GPIO7 + * @return `ESP_OK` on success + */ +esp_err_t mcp23008_port_write(i2c_dev_t *dev, uint8_t val); + +/** + * @brief Get GPIO pin mode + * + * @param dev Pointer to I2C device descriptor + * @param pin Pin number, 0..7 + * @param[out] mode GPIO pin mode + * @return `ESP_OK` on success + */ +esp_err_t mcp23008_get_mode(i2c_dev_t *dev, uint8_t pin, mcp23008_gpio_mode_t *mode); + +/** + * @brief Set GPIO pin mode + * + * @param dev Pointer to I2C device descriptor + * @param pin Pin number, 0..7 + * @param mode GPIO pin mode + * @return `ESP_OK` on success + */ +esp_err_t mcp23008_set_mode(i2c_dev_t *dev, uint8_t pin, mcp23008_gpio_mode_t mode); + +/** + * @brief Get pullup mode of GPIO pin + * + * @param dev Pointer to I2C device descriptor + * @param pin Pin number, 0..7 + * @param[out] enable pullup mode + * @return `ESP_OK` on success + */ +esp_err_t mcp23008_get_pullup(i2c_dev_t *dev, uint8_t pin, bool *enable); + +/** + * @brief Set pullup mode of GPIO pin + * + * @param dev Pointer to I2C device descriptor + * @param pin Pin number, 0..7 + * @param enable `true` to enable pullup + * @return `ESP_OK` on success + */ +esp_err_t mcp23008_set_pullup(i2c_dev_t *dev, uint8_t pin, bool enable); + +/** + * @brief Read GPIO pin level + * + * @param dev Pointer to I2C device descriptor + * @param pin Pin number, 0..7 + * @param[out] val `true` if pin currently in high state + * @return `ESP_OK` on success + */ +esp_err_t mcp23008_get_level(i2c_dev_t *dev, uint8_t pin, uint32_t *val); + +/** + * @brief Set GPIO pin level + * + * Pin must be set up as output. + * + * @param dev Pointer to I2C device descriptor + * @param pin Pin number, 0..7 + * @param[out] val `true` if pin currently in high state + * @return `ESP_OK` on success + */ +esp_err_t mcp23008_set_level(i2c_dev_t *dev, uint8_t pin, uint32_t val); + +/** + * @brief Setup interrupt for group of GPIO pins + * + * @param dev Pointer to I2C device descriptor + * @param mask Pins to setup + * @param intr Interrupt mode + * @return `ESP_OK` on success + */ +esp_err_t mcp23008_port_set_interrupt(i2c_dev_t *dev, uint8_t mask, mcp23008_gpio_intr_t intr); + +/** + * @brief Setup interrupt for GPIO pin + * + * @param dev Pointer to I2C device descriptor + * @param pin Pin number, 0..7 + * @param intr Interrupt mode + * @return `ESP_OK` on success + */ +esp_err_t mcp23008_set_interrupt(i2c_dev_t *dev, uint8_t pin, mcp23008_gpio_intr_t intr); + + +#ifdef __cplusplus +} +#endif + +/**@}*/ + +#endif /* __MCP23008_H__ */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/mcp23x17/.eil.yml Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,23 @@ +--- +components: + - name: mcp23x17 + description: Driver for I2C/SPI 16 bit GPIO expanders MCP23017/MCP23S17 + group: gpio + groups: [] + code_owners: UncleRus + depends: + - driver + - i2cdev + - log + - esp_idf_lib_helpers + thread_safe: yes + targets: + - name: esp32 + - name: esp32s2 + - name: esp32c3 + licenses: + - name: BSD-3 + copyrights: + - author: + name: UncleRus + year: 2018
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/mcp23x17/CMakeLists.txt Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,5 @@ +idf_component_register( + SRCS mcp23x17.c + INCLUDE_DIRS . + REQUIRES driver i2cdev log esp_idf_lib_helpers +)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/mcp23x17/Kconfig Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,11 @@ +menu "MCP23x17" + + choice MCP23X17_IFACE + prompt "Chip type" + config MCP23X17_IFACE_I2C + bool "MCP23017 [I2C]" + config MCP23X17_IFACE_SPI + bool "MCP23S17 [SPI]" + endchoice + +endmenu \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/mcp23x17/LICENSE Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,26 @@ +Copyright (c) 2018 Ruslan V. Uss (https://github.com/UncleRus) + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, +this list of conditions and the following disclaimer in the documentation +and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of itscontributors +may be used to endorse or promote products derived from this software without +specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/mcp23x17/component.mk Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,2 @@ +COMPONENT_ADD_INCLUDEDIRS = . +COMPONENT_DEPENDS = driver i2cdev log esp_idf_lib_helpers
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/mcp23x17/mcp23x17.c Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,483 @@ +/* + * Copyright (c) 2018 Ruslan V. Uss <unclerus@gmail.com> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of itscontributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file mcp23x17.c + * + * ESP-IDF driver for I2C/SPI 16 bit GPIO expanders MCP23017/MCP23S17 + * + * Copyright (c) 2018 Ruslan V. Uss <unclerus@gmail.com> + * + * BSD Licensed as described in the file LICENSE + */ + +#include <esp_log.h> +#include <string.h> +#include <esp_idf_lib_helpers.h> +#include "mcp23x17.h" + +static const char *TAG = "mcp23x17"; + +#define I2C_FREQ_HZ 1000000 // Max 1MHz for esp-idf, but device supports up to 1.7Mhz + +#define REG_IODIRA 0x00 +#define REG_IODIRB 0x01 +#define REG_IPOLA 0x02 +#define REG_IPOLB 0x03 +#define REG_GPINTENA 0x04 +#define REG_GPINTENB 0x05 +#define REG_DEFVALA 0x06 +#define REG_DEFVALB 0x07 +#define REG_INTCONA 0x08 +#define REG_INTCONB 0x09 +#define REG_IOCON 0x0A +#define REG_GPPUA 0x0C +#define REG_GPPUB 0x0D +#define REG_INTFA 0x0E +#define REG_INTFB 0x0F +#define REG_INTCAPA 0x10 +#define REG_INTCAPB 0x11 +#define REG_GPIOA 0x12 +#define REG_GPIOB 0x13 +#define REG_OLATA 0x14 +#define REG_OLATB 0x15 + +#define BIT_IOCON_INTPOL 1 +#define BIT_IOCON_ODR 2 +#define BIT_IOCON_HAEN 3 +#define BIT_IOCON_DISSLW 4 +#define BIT_IOCON_SEQOP 5 +#define BIT_IOCON_MIRROR 6 +#define BIT_IOCON_BANK 7 + +#define CHECK(x) do { esp_err_t __; if ((__ = x) != ESP_OK) return __; } while (0) +#define CHECK_ARG(VAL) do { if (!(VAL)) return ESP_ERR_INVALID_ARG; } while (0) +#define BV(x) (1 << (x)) + +#ifdef CONFIG_MCP23X17_IFACE_I2C + +static esp_err_t read_reg_16(mcp23x17_t *dev, uint8_t reg, uint16_t *val) +{ + CHECK_ARG(dev && val); + + I2C_DEV_TAKE_MUTEX(dev); + I2C_DEV_CHECK(dev, i2c_dev_read_reg(dev, reg, val, 2)); + I2C_DEV_GIVE_MUTEX(dev); + + return ESP_OK; +} + +static esp_err_t write_reg_16(mcp23x17_t *dev, uint8_t reg, uint16_t val) +{ + CHECK_ARG(dev); + + I2C_DEV_TAKE_MUTEX(dev); + I2C_DEV_CHECK(dev, i2c_dev_write_reg(dev, reg, &val, 2)); + I2C_DEV_GIVE_MUTEX(dev); + + return ESP_OK; +} + +static esp_err_t write_reg_bit_16(mcp23x17_t *dev, uint8_t reg, bool val, uint8_t bit) +{ + CHECK_ARG(dev); + + uint16_t buf; + + I2C_DEV_TAKE_MUTEX(dev); + I2C_DEV_CHECK(dev, i2c_dev_read_reg(dev, reg, &buf, 2)); + buf = (buf & ~BV(bit)) | (val ? BV(bit) : 0); + I2C_DEV_CHECK(dev, i2c_dev_write_reg(dev, reg, &buf, 2)); + I2C_DEV_GIVE_MUTEX(dev); + + return ESP_OK; +} + +static esp_err_t read_reg_bit_8(mcp23x17_t *dev, uint8_t reg, bool *val, uint8_t bit) +{ + CHECK_ARG(dev && val); + + uint8_t buf; + + I2C_DEV_TAKE_MUTEX(dev); + I2C_DEV_CHECK(dev, i2c_dev_read_reg(dev, reg, &buf, 1)); + I2C_DEV_GIVE_MUTEX(dev); + + *val = (buf & BV(bit)) >> bit; + + return ESP_OK; +} + +static esp_err_t write_reg_bit_8(mcp23x17_t *dev, uint8_t reg, bool val, uint8_t bit) +{ + CHECK_ARG(dev); + + uint8_t buf; + + I2C_DEV_TAKE_MUTEX(dev); + I2C_DEV_CHECK(dev, i2c_dev_read_reg(dev, reg, &buf, 1)); + buf = (buf & ~BV(bit)) | (val ? BV(bit) : 0); + I2C_DEV_CHECK(dev, i2c_dev_write_reg(dev, reg, &buf, 1)); + I2C_DEV_GIVE_MUTEX(dev); + + return ESP_OK; +} + +#else + +static esp_err_t read_reg_16(mcp23x17_t *dev, uint8_t reg, uint16_t *val) +{ + CHECK_ARG(dev && val); + + uint8_t rx[4] = { 0 }; + uint8_t tx[4] = { (dev->addr << 1) | 0x01, reg, 0, 0 }; + + spi_transaction_t t; + memset(&t, 0, sizeof(spi_transaction_t)); + t.rx_buffer = rx; + t.tx_buffer = tx; + t.length = 32; // 32 bits + + CHECK(spi_device_transmit(dev->spi_dev, &t)); + + *val = (rx[3] << 8) | rx[2]; + + return ESP_OK; +} + +static esp_err_t write_reg_16(mcp23x17_t *dev, uint8_t reg, uint16_t val) +{ + CHECK_ARG(dev); + + uint8_t tx[4] = { dev->addr << 1, reg, val, val >> 8 }; + + spi_transaction_t t; + memset(&t, 0, sizeof(spi_transaction_t)); + t.tx_buffer = tx; + t.length = 32; // 32 bits + + CHECK(spi_device_transmit(dev->spi_dev, &t)); + + return ESP_OK; +} + +static esp_err_t write_reg_bit_16(mcp23x17_t *dev, uint8_t reg, bool val, uint8_t bit) +{ + CHECK_ARG(dev); + + uint16_t buf; + + CHECK(read_reg_16(dev, reg, &buf)); + return write_reg_16(dev, reg, (buf & ~BV(bit)) | (val ? BV(bit) : 0)); +} + +static esp_err_t read_reg_8(mcp23x17_t *dev, uint8_t reg, uint8_t *val) +{ + CHECK_ARG(dev && val); + + uint8_t rx[3] = { 0 }; + uint8_t tx[3] = { (dev->addr << 1) | 0x01, reg, 0 }; + + spi_transaction_t t; + memset(&t, 0, sizeof(spi_transaction_t)); + t.rx_buffer = rx; + t.tx_buffer = tx; + t.length = 24; // 24 bits + + CHECK(spi_device_transmit(dev->spi_dev, &t)); + + *val = rx[2]; + + return ESP_OK; +} + +static esp_err_t write_reg_8(mcp23x17_t *dev, uint8_t reg, uint8_t val) +{ + CHECK_ARG(dev); + + uint8_t tx[3] = { dev->addr << 1, reg, val }; + + spi_transaction_t t; + memset(&t, 0, sizeof(spi_transaction_t)); + t.tx_buffer = tx; + t.length = 24; // 24 bits + + CHECK(spi_device_transmit(dev->spi_dev, &t)); + + return ESP_OK; +} + + +static esp_err_t read_reg_bit_8(mcp23x17_t *dev, uint8_t reg, bool *val, uint8_t bit) +{ + CHECK_ARG(dev && val); + + uint8_t buf; + + CHECK(read_reg_8(dev, reg, &buf)); + *val = (buf & BV(bit)) >> bit; + + return ESP_OK; +} + +static esp_err_t write_reg_bit_8(mcp23x17_t *dev, uint8_t reg, bool val, uint8_t bit) +{ + CHECK_ARG(dev); + + uint8_t buf; + + CHECK(read_reg_8(dev, reg, &buf)); + return write_reg_8(dev, reg, (buf & ~BV(bit)) | (val ? BV(bit) : 0)); +} + +#endif + +static esp_err_t read_reg_bit_16(mcp23x17_t *dev, uint8_t reg, bool *val, uint8_t bit) +{ + uint16_t buf; + + CHECK(read_reg_16(dev, reg, &buf)); + + *val = (buf & BV(bit)) >> bit; + + return ESP_OK; +} + +/////////////////////////////////////////////////////////////////////////////// + +#ifdef CONFIG_MCP23X17_IFACE_I2C + +esp_err_t mcp23x17_init_desc(mcp23x17_t *dev, uint8_t addr, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio) +{ + CHECK_ARG(dev); + if (addr < MCP23X17_ADDR_BASE || addr > MCP23X17_ADDR_BASE + 7) + { + ESP_LOGE(TAG, "Invalid device address: 0x%02x", addr); + return ESP_ERR_INVALID_ARG; + } + + dev->port = port; + dev->addr = addr; + dev->cfg.sda_io_num = sda_gpio; + dev->cfg.scl_io_num = scl_gpio; +#if HELPER_TARGET_IS_ESP32 + dev->cfg.master.clk_speed = I2C_FREQ_HZ; +#endif + + return i2c_dev_create_mutex(dev); +} + +esp_err_t mcp23x17_free_desc(mcp23x17_t *dev) +{ + CHECK_ARG(dev); + + return i2c_dev_delete_mutex(dev); +} + +#else + +esp_err_t mcp23x17_init_desc_spi(mcp23x17_t *dev, spi_host_device_t host, uint32_t clock_speed_hz, uint8_t addr, gpio_num_t cs_pin) +{ + CHECK_ARG(dev); + if (addr < MCP23X17_ADDR_BASE || addr > MCP23X17_ADDR_BASE + 7) + { + ESP_LOGE(TAG, "Invalid device address: 0x%02x", addr); + return ESP_ERR_INVALID_ARG; + } + + dev->addr = addr; + + memset(&dev->spi_cfg, 0, sizeof(dev->spi_cfg)); + dev->spi_cfg.spics_io_num = cs_pin; + dev->spi_cfg.clock_speed_hz = clock_speed_hz; + dev->spi_cfg.mode = 0; + dev->spi_cfg.queue_size = 1; + + return spi_bus_add_device(host, &dev->spi_cfg, &dev->spi_dev); +} + +esp_err_t mcp23x17_free_desc(mcp23x17_t *dev) +{ + CHECK_ARG(dev); + + return spi_bus_remove_device(dev->spi_dev); +} + +esp_err_t mcp23x17_setup_hw_addr(mcp23x17_t *dev, bool enable, uint8_t new_addr) +{ + CHECK(write_reg_bit_8(dev, REG_IOCON, enable, BIT_IOCON_HAEN)); + dev->addr = enable ? new_addr : MCP23X17_ADDR_BASE; + + return ESP_OK; +} + +#endif + +esp_err_t mcp23x17_get_int_out_mode(mcp23x17_t *dev, mcp23x17_int_out_mode_t *mode) +{ + CHECK_ARG(mode); + + bool buf; + CHECK(read_reg_bit_8(dev, REG_IOCON, &buf, BIT_IOCON_ODR)); + if (buf) + { + *mode = MCP23X17_OPEN_DRAIN; + return ESP_OK; + } + CHECK(read_reg_bit_8(dev, REG_IOCON, &buf, BIT_IOCON_INTPOL)); + *mode = buf ? MCP23X17_ACTIVE_HIGH : MCP23X17_ACTIVE_LOW; + + return ESP_OK; +} + +esp_err_t mcp23x17_set_int_out_mode(mcp23x17_t *dev, mcp23x17_int_out_mode_t mode) +{ + if (mode == MCP23X17_OPEN_DRAIN) + return write_reg_bit_8(dev, REG_IOCON, true, BIT_IOCON_ODR); + + return write_reg_bit_8(dev, REG_IOCON, mode == MCP23X17_ACTIVE_HIGH, BIT_IOCON_INTPOL); +} + +esp_err_t mcp23x17_port_get_mode(mcp23x17_t *dev, uint16_t *val) +{ + return read_reg_16(dev, REG_IODIRA, val); +} + +esp_err_t mcp23x17_port_set_mode(mcp23x17_t *dev, uint16_t val) +{ + return write_reg_16(dev, REG_IODIRA, val); +} + +esp_err_t mcp23x17_port_get_pullup(mcp23x17_t *dev, uint16_t *val) +{ + return read_reg_16(dev, REG_GPPUA, val); +} + +esp_err_t mcp23x17_port_set_pullup(mcp23x17_t *dev, uint16_t val) +{ + return write_reg_16(dev, REG_GPPUA, val); +} + +esp_err_t mcp23x17_port_read(mcp23x17_t *dev, uint16_t *val) +{ + return read_reg_16(dev, REG_GPIOA, val); +} + +esp_err_t mcp23x17_port_write(mcp23x17_t *dev, uint16_t val) +{ + return write_reg_16(dev, REG_GPIOA, val); +} + +esp_err_t mcp23x17_get_mode(mcp23x17_t *dev, uint8_t pin, mcp23x17_gpio_mode_t *mode) +{ + CHECK_ARG(mode); + + bool buf; + CHECK(read_reg_bit_16(dev, REG_IODIRA, &buf, pin)); + *mode = buf ? MCP23X17_GPIO_INPUT : MCP23X17_GPIO_OUTPUT; + + return ESP_OK; +} + +esp_err_t mcp23x17_set_mode(mcp23x17_t *dev, uint8_t pin, mcp23x17_gpio_mode_t mode) +{ + return write_reg_bit_16(dev, REG_IODIRA, mode, pin); +} + +esp_err_t mcp23x17_get_pullup(mcp23x17_t *dev, uint8_t pin, bool *enable) +{ + return read_reg_bit_16(dev, REG_GPPUA, enable, pin); +} + +esp_err_t mcp23x17_set_pullup(mcp23x17_t *dev, uint8_t pin, bool enable) +{ + return write_reg_bit_16(dev, REG_GPPUA, enable, pin); +} + +esp_err_t mcp23x17_get_level(mcp23x17_t *dev, uint8_t pin, uint32_t *val) +{ + CHECK_ARG(val); + + bool buf; + CHECK(read_reg_bit_16(dev, REG_GPIOA, &buf, pin)); + *val = buf ? 1 : 0; + + return ESP_OK; +} + +esp_err_t mcp23x17_set_level(mcp23x17_t *dev, uint8_t pin, uint32_t val) +{ + return write_reg_bit_16(dev, REG_GPIOA, val, pin); +} + +esp_err_t mcp23x17_port_set_interrupt(mcp23x17_t *dev, uint16_t mask, mcp23x17_gpio_intr_t intr) +{ + CHECK_ARG(dev); + + uint16_t int_en; + CHECK(read_reg_16(dev, REG_GPINTENA, &int_en)); + + if (intr == MCP23X17_INT_DISABLED) + { + // disable interrupts + int_en &= ~mask; + CHECK(write_reg_16(dev, REG_GPINTENA, int_en)); + + return ESP_OK; + } + + uint16_t int_con; + CHECK(read_reg_16(dev, REG_INTCONA, &int_con)); + + if (intr == MCP23X17_INT_ANY_EDGE) + int_con &= ~mask; + else + { + int_con |= mask; + + uint16_t int_def; + CHECK(read_reg_16(dev, REG_DEFVALA, &int_def)); + if (intr == MCP23X17_INT_LOW_EDGE) + int_def |= mask; + else + int_def &= ~mask; + CHECK(write_reg_16(dev, REG_DEFVALA, int_def)); + } + + CHECK(write_reg_16(dev, REG_INTCONA, int_con)); + + // enable interrupts + int_en |= mask; + CHECK(write_reg_16(dev, REG_GPINTENA, int_en)); + + return ESP_OK; +} + +esp_err_t mcp23x17_set_interrupt(mcp23x17_t *dev, uint8_t pin, mcp23x17_gpio_intr_t intr) +{ + return mcp23x17_port_set_interrupt(dev, BV(pin), intr); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/mcp23x17/mcp23x17.h Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,337 @@ +/* + * Copyright (c) 2018 Ruslan V. Uss <unclerus@gmail.com> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of itscontributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file mcp23x17.h + * @defgroup mcp23x17 mcp23x17 + * @{ + * + * ESP-IDF driver for I2C/SPI 16 bit GPIO expanders MCP23017/MCP23S17 + * + * Copyright (c) 2018 Ruslan V. Uss <unclerus@gmail.com> + * + * BSD Licensed as described in the file LICENSE + */ +#ifndef __MCP23X17_H__ +#define __MCP23X17_H__ + +#include <stddef.h> +#include <stdbool.h> +#include <i2cdev.h> +#include <driver/spi_master.h> +#include <driver/gpio.h> +#include <esp_err.h> + +#define MCP23X17_ADDR_BASE 0x20 + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef CONFIG_MCP23X17_IFACE_I2C + +typedef i2c_dev_t mcp23x17_t; + +#else + +#define MCP23X17_MAX_SPI_FREQ SPI_MASTER_FREQ_10M // 10MHz + +typedef struct +{ + spi_device_interface_config_t spi_cfg; + spi_device_handle_t spi_dev; + uint8_t addr; +} mcp23x17_t; + +#endif + +/** + * GPIO mode + */ +typedef enum +{ + MCP23X17_GPIO_OUTPUT = 0, + MCP23X17_GPIO_INPUT +} mcp23x17_gpio_mode_t; + +/** + * INTA/INTB pins mode + */ +typedef enum +{ + MCP23X17_ACTIVE_LOW = 0, //!< Low level on interrupt + MCP23X17_ACTIVE_HIGH, //!< High level on interrupt + MCP23X17_OPEN_DRAIN //!< Open drain +} mcp23x17_int_out_mode_t; + +/** + * Interrupt mode + */ +typedef enum +{ + MCP23X17_INT_DISABLED = 0, //!< No interrupt + MCP23X17_INT_LOW_EDGE, //!< Interrupt on low edge + MCP23X17_INT_HIGH_EDGE, //!< Interrupt on high edge + MCP23X17_INT_ANY_EDGE //!< Interrupt on any edge +} mcp23x17_gpio_intr_t; + +#ifdef CONFIG_MCP23X17_IFACE_I2C + +/** + * @brief Initialize device descriptor + * + * Default SCL frequency is 1MHz. + * + * @param dev Pointer to device descriptor + * @param port I2C port number + * @param addr I2C address + * @param sda_gpio SDA GPIO + * @param scl_gpio SCL GPIO + * @return `ESP_OK` on success + */ +esp_err_t mcp23x17_init_desc(mcp23x17_t *dev, uint8_t addr, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio); + +/** + * @brief Free device descriptor + * + * @param dev Pointer to device descriptor + * @return `ESP_OK` on success + */ +esp_err_t mcp23x17_free_desc(mcp23x17_t *dev); + +#else + +/** + * @brief Initialize device descriptor + * + * @param dev Pointer to device descriptor + * @param host SPI host + * @param clock_speed_hz SPI clock speed, Hz (max `MCP23X17_MAX_SPI_FREQ`) + * @param addr Device address + * @param cs_pin CS pin + * @return `ESP_OK` on success + */ +esp_err_t mcp23x17_init_desc_spi(mcp23x17_t *dev, spi_host_device_t host, uint32_t clock_speed_hz, uint8_t addr, gpio_num_t cs_pin); + +/** + * @brief Free device descriptor + * + * @param dev Pointer to device descriptor + * @return `ESP_OK` on success + */ +esp_err_t mcp23x17_free_desc_spi(mcp23x17_t *dev); + +#endif + +#ifdef CONFIG_MCP23X17_IFACE_SPI + +/** + * @brief Enable or disable hardware addressing (usage of pins A0..A2). + * + * Works only with MCP23S17. + * Warining! According to the datasheet, hardware addressing is disabled by default. + * + * @param dev Pointer to device descriptor + * @param enable `true` to enable hardware addressing + * @param new_addr New I2C address (`0b0100<A2><A1><A0>` after the addressing enabled). + * If `enable` is `false`, address will be set automatically + * @return `ESP_OK` on success + */ +esp_err_t mcp23x17_setup_hw_addr(mcp23x17_t *dev, bool enable, uint8_t new_addr); + +#endif + +/** + * @brief Get INTA/INTB pins mode + * + * @param dev Pointer to device descriptor + * @param[out] mode Buffer to store mode + * @return `ESP_OK` on success + */ +esp_err_t mcp23x17_get_int_out_mode(mcp23x17_t *dev, mcp23x17_int_out_mode_t *mode); + +/** + * @brief Set INTA/INTB pins mode + * + * @param dev Pointer to device descriptor + * @param mode INTA/INTB pins mode + * @return `ESP_OK` on success + */ +esp_err_t mcp23x17_set_int_out_mode(mcp23x17_t *dev, mcp23x17_int_out_mode_t mode); + +/** + * @brief Get GPIO pins mode + * + * 0 - output, 1 - input for each bit in `val` + * + * @param dev Pointer to device descriptor + * @param[out] val Buffer to store mode, 0 bit for PORTA/GPIO0..15 bit for PORTB/GPIO7 + * @return + */ +esp_err_t mcp23x17_port_get_mode(mcp23x17_t *dev, uint16_t *val); + +/** + * @brief Set GPIO pins mode + * + * 0 - output, 1 - input for each bit in `val` + * + * @param dev Pointer to device descriptor + * @param val Mode, 0 bit for PORTA/GPIO0..15 bit for PORTB/GPIO7 + * @return `ESP_OK` on success + */ +esp_err_t mcp23x17_port_set_mode(mcp23x17_t *dev, uint16_t val); + +/** + * @brief Get GPIO pullups status + * + * 0 - pullup disabled, 1 - pullup enabled for each bit in `val` + * + * @param dev Pointer to device descriptor + * @param[out] val Pullup status, 0 bit for PORTA/GPIO0..15 bit for PORTB/GPIO7 + * @return `ESP_OK` on success + */ +esp_err_t mcp23x17_port_get_pullup(mcp23x17_t *dev, uint16_t *val); + +/** + * @brief Set GPIO pullups status + * + * 0 - pullup disabled, 1 - pullup enabled for each bit in `val` + * + * @param dev Pointer to device descriptor + * @param val Pullup status, 0 bit for PORTA/GPIO0..15 bit for PORTB/GPIO7 + * @return `ESP_OK` on success + */ +esp_err_t mcp23x17_port_set_pullup(mcp23x17_t *dev, uint16_t val); + +/** + * @brief Read GPIO port value + * + * @param dev Pointer to device descriptor + * @param[out] val 16-bit GPIO port value, 0 bit for PORTA/GPIO0..15 bit for PORTB/GPIO7 + * @return `ESP_OK` on success + */ +esp_err_t mcp23x17_port_read(mcp23x17_t *dev, uint16_t *val); + +/** + * @brief Write value to GPIO port + * + * @param dev Pointer to device descriptor + * @param val GPIO port value, 0 bit for PORTA/GPIO0..15 bit for PORTB/GPIO7 + * @return `ESP_OK` on success + */ +esp_err_t mcp23x17_port_write(mcp23x17_t *dev, uint16_t val); + +/** + * @brief Get GPIO pin mode + * + * @param dev Pointer to device descriptor + * @param pin Pin number, 0 for PORTA/GPIO0..15 for PORTB/GPIO7 + * @param[out] mode GPIO pin mode + * @return `ESP_OK` on success + */ +esp_err_t mcp23x17_get_mode(mcp23x17_t *dev, uint8_t pin, mcp23x17_gpio_mode_t *mode); + +/** + * @brief Set GPIO pin mode + * + * @param dev Pointer to device descriptor + * @param pin Pin number, 0 for PORTA/GPIO0..15 for PORTB/GPIO7 + * @param mode GPIO pin mode + * @return `ESP_OK` on success + */ +esp_err_t mcp23x17_set_mode(mcp23x17_t *dev, uint8_t pin, mcp23x17_gpio_mode_t mode); + +/** + * @brief Get pullup mode of GPIO pin + * + * @param dev Pointer to device descriptor + * @param pin Pin number, 0 for PORTA/GPIO0..15 for PORTB/GPIO7 + * @param[out] enable pullup mode + * @return `ESP_OK` on success + */ +esp_err_t mcp23x17_get_pullup(mcp23x17_t *dev, uint8_t pin, bool *enable); + +/** + * @brief Set pullup mode of GPIO pin + * + * @param dev Pointer to device descriptor + * @param pin Pin number, 0 for PORTA/GPIO0..15 for PORTB/GPIO7 + * @param enable `true` to enable pullup + * @return `ESP_OK` on success + */ +esp_err_t mcp23x17_set_pullup(mcp23x17_t *dev, uint8_t pin, bool enable); + +/** + * @brief Read GPIO pin level + * + * @param dev Pointer to device descriptor + * @param pin Pin number, 0 for PORTA/GPIO0..15 for PORTB/GPIO7 + * @param[out] val `true` if pin currently in high state + * @return `ESP_OK` on success + */ +esp_err_t mcp23x17_get_level(mcp23x17_t *dev, uint8_t pin, uint32_t *val); + +/** + * @brief Set GPIO pin level + * + * Pin must be set up as output + * + * @param dev Pointer to device descriptor + * @param pin Pin number, 0 for PORTA/GPIO0..15 for PORTB/GPIO7 + * @param[out] val `true` if pin currently in high state + * @return `ESP_OK` on success + */ +esp_err_t mcp23x17_set_level(mcp23x17_t *dev, uint8_t pin, uint32_t val); + +/** + * @brief Setup interrupt for group of GPIO pins + * + * @param dev Pointer to device descriptor + * @param mask Pins to setup + * @param intr Interrupt mode + * @return `ESP_OK` on success + */ +esp_err_t mcp23x17_port_set_interrupt(mcp23x17_t *dev, uint16_t mask, mcp23x17_gpio_intr_t intr); + +/** + * @brief Setup interrupt for GPIO pin + * + * @param dev Pointer to device descriptor + * @param pin Pin number, 0 for PORTA/GPIO0..15 for PORTB/GPIO7 + * @param intr Interrupt mode + * @return `ESP_OK` on success + */ +esp_err_t mcp23x17_set_interrupt(mcp23x17_t *dev, uint8_t pin, mcp23x17_gpio_intr_t intr); + + +#ifdef __cplusplus +} +#endif + +/**@}*/ + +#endif /* __MCP23X17_H__ */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/mcp342x/.eil.yml Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,23 @@ +--- +components: + - name: mcp342x + description: Driver for 18-Bit, delta-sigma ADC MCP3426/MCP3427/MCP3428 + group: adc-dac + groups: [] + code_owners: UncleRus + depends: + - i2cdev + - log + - esp_idf_lib_helpers + thread_safe: yes + targets: + - name: esp32 + - name: esp8266 + - name: esp32s2 + - name: esp32c3 + licenses: + - name: BSD-3 + copyrights: + - author: + name: UncleRus + year: 2019
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/mcp342x/CMakeLists.txt Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,5 @@ +idf_component_register( + SRCS mcp342x.c + INCLUDE_DIRS . + REQUIRES i2cdev log esp_idf_lib_helpers +)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/mcp342x/LICENSE Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,26 @@ +Copyright (c) 2020 Ruslan V. Uss <unclerus@gmail.com> + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, +this list of conditions and the following disclaimer in the documentation +and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of itscontributors +may be used to endorse or promote products derived from this software without +specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/mcp342x/component.mk Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,2 @@ +COMPONENT_ADD_INCLUDEDIRS = . +COMPONENT_DEPENDS = i2cdev log esp_idf_lib_helpers
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/mcp342x/mcp342x.c Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,242 @@ +/* + * Copyright (c) 2020 Ruslan V. Uss <unclerus@gmail.com> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of itscontributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file mcp342x.c + * + * ESP-IDF driver for 18-Bit, multi-channel delta-sigma + * ADC MCP3426/MCP3427/MCP3428 + * + * Copyright (c) 2020 Ruslan V. Uss <unclerus@gmail.com> + * + * BSD Licensed as described in the file LICENSE + */ +#include <esp_idf_lib_helpers.h> +#include <esp_log.h> +#include <freertos/FreeRTOS.h> +#include <freertos/task.h> +#include "mcp342x.h" + +#define I2C_FREQ_HZ 400000 // 400kHz + +static const char *TAG = "mcp342x"; + +#define BV(x) (1 << (x)) + +#define BIT_RDY BV(7) +#define BIT_MODE BV(4) + +#define POS_CHAN 5 +#define POS_SR 2 +#define POS_GAIN 0 + +#define MASK_VAL 3 + +#define SIGN12 0xfffff000 +#define SIGN14 0xffffc000 +#define SIGN16 0xffff0000 +#define SIGN18 0xfffc0000 + +#define CHECK(x) do { esp_err_t __; if ((__ = x) != ESP_OK) return __; } while (0) +#define CHECK_ARG(VAL) do { if (!(VAL)) return ESP_ERR_INVALID_ARG; } while (0) + +static const uint32_t sample_time[] = { + [MCP342X_RES_12] = 4167, + [MCP342X_RES_14] = 16667, + [MCP342X_RES_16] = 66667, + [MCP342X_RES_18] = 266667 +}; + +static const float lsb[] = { + [MCP342X_RES_12] = 0.001, + [MCP342X_RES_14] = 0.00025, + [MCP342X_RES_16] = 0.0000625, + [MCP342X_RES_18] = 0.000015625 +}; + +static const int gain_val[] = { + [MCP342X_GAIN1] = 1, + [MCP342X_GAIN2] = 2, + [MCP342X_GAIN4] = 4, + [MCP342X_GAIN8] = 8 +}; + +static void get_cfg(mcp342x_t *dev, uint8_t reg) +{ + dev->mode = (reg & BIT_MODE) ? MCP342X_CONTINUOUS : MCP342X_ONESHOT; + dev->channel = (reg >> POS_CHAN) & MASK_VAL; + dev->resolution = (reg >> POS_SR) & MASK_VAL; + dev->gain = (reg >> POS_GAIN) & MASK_VAL; +} + +static inline uint8_t get_reg(mcp342x_t *dev) +{ + return (dev->mode == MCP342X_CONTINUOUS ? BIT_MODE : 0) + | ((dev->channel & MASK_VAL) << POS_CHAN) + | ((dev->resolution & MASK_VAL) << POS_SR) + | ((dev->gain & MASK_VAL) << POS_GAIN); +} + +/////////////////////////////////////////////////////////////////////////////// + +esp_err_t mcp342x_init_desc(mcp342x_t *dev, uint8_t addr, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio) +{ + CHECK_ARG(dev && addr >= MCP342X_ADDR_MIN && addr <= MCP342X_ADDR_MAX); + + dev->i2c_dev.port = port; + dev->i2c_dev.addr = addr; + dev->i2c_dev.cfg.sda_io_num = sda_gpio; + dev->i2c_dev.cfg.scl_io_num = scl_gpio; +#if HELPER_TARGET_IS_ESP32 + dev->i2c_dev.cfg.master.clk_speed = I2C_FREQ_HZ; +#endif + + return i2c_dev_create_mutex(&dev->i2c_dev); +} + +esp_err_t mcp342x_free_desc(mcp342x_t *dev) +{ + CHECK_ARG(dev); + + return i2c_dev_delete_mutex(&dev->i2c_dev); +} + +esp_err_t mcp342x_get_data(mcp342x_t *dev, int32_t *data, bool *ready) +{ + CHECK_ARG(dev); + + uint8_t buf[4] = { 0 }; + + I2C_DEV_TAKE_MUTEX(&dev->i2c_dev); + I2C_DEV_CHECK(&dev->i2c_dev, i2c_dev_read(&dev->i2c_dev, NULL, 0, buf, sizeof(buf))); + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); + + uint8_t reg = buf[(buf[3] & (MCP342X_RES_18 << POS_SR)) == (MCP342X_RES_18 << POS_SR) ? 3 : 2]; + get_cfg(dev, reg); + if (ready) *ready = !(reg & BIT_RDY); + + if (!data) return ESP_OK; + + uint32_t r = 0; + switch (dev->resolution) + { + case MCP342X_RES_12: + r = (buf[0] << 8) | buf[1]; + if (r & BV(11)) + r |= SIGN12; + break; + case MCP342X_RES_14: + r = (buf[0] << 8) | buf[1]; + if (r & BV(13)) + r |= SIGN14; + break; + case MCP342X_RES_16: + r = (buf[0] << 8) | buf[1]; + if (r & BV(15)) + r |= SIGN16; + break; + case MCP342X_RES_18: + r = (buf[0] << 16) | (buf[1] << 8) | buf[2]; + if (r & BV(17)) + r |= SIGN18; + break; + } + *data = *((int32_t *)&r); + + return ESP_OK; +} + +esp_err_t mcp342x_get_voltage(mcp342x_t *dev, float *volts, bool *ready) +{ + CHECK_ARG(volts); + + int32_t raw; + CHECK(mcp342x_get_data(dev, &raw, ready)); + *volts = lsb[dev->resolution] * raw / gain_val[dev->gain]; + + return ESP_OK; +} + +esp_err_t mcp342x_get_sample_time_us(mcp342x_t *dev, uint32_t *us) +{ + CHECK_ARG(dev && us && dev->resolution <= MCP342X_RES_18); + + *us = sample_time[dev->resolution]; + return ESP_OK; +} + +esp_err_t mcp342x_set_config(mcp342x_t *dev) +{ + CHECK_ARG(dev); + + uint8_t r = get_reg(dev); + + I2C_DEV_TAKE_MUTEX(&dev->i2c_dev); + I2C_DEV_CHECK(&dev->i2c_dev, i2c_dev_write(&dev->i2c_dev, NULL, 0, &r, 1)); + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); + + return ESP_OK; +} + +esp_err_t mcp342x_get_config(mcp342x_t *dev) +{ + return mcp342x_get_data(dev, NULL, NULL); +} + +esp_err_t mcp342x_start_conversion(mcp342x_t *dev) +{ + CHECK_ARG(dev); + + uint8_t r = get_reg(dev) | BIT_RDY; + + I2C_DEV_TAKE_MUTEX(&dev->i2c_dev); + I2C_DEV_CHECK(&dev->i2c_dev, i2c_dev_write(&dev->i2c_dev, NULL, 0, &r, 1)); + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); + + return ESP_OK; +} + +esp_err_t mcp342x_oneshot_conversion(mcp342x_t *dev, int32_t *data) +{ + CHECK_ARG(dev && data); + + dev->mode = MCP342X_ONESHOT; + + uint32_t st; + CHECK(mcp342x_get_sample_time_us(dev, &st)); + CHECK(mcp342x_start_conversion(dev)); + vTaskDelay(pdMS_TO_TICKS(st / 1000 + 1)); + bool ready; + CHECK(mcp342x_get_data(dev, data, &ready)); + if (!ready) + { + ESP_LOGE(TAG, "Data not ready"); + return ESP_FAIL; + } + + return ESP_OK; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/mcp342x/mcp342x.h Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,195 @@ +/* + * Copyright (c) 2020 Ruslan V. Uss <unclerus@gmail.com> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of itscontributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file mcp342x.h + * @defgroup mcp342x mcp342x + * @{ + * + * ESP-IDF driver for 18-Bit, multi-channel delta-sigma + * ADC MCP3426/MCP3427/MCP3428 + * + * Copyright (c) 2020 Ruslan V. Uss <unclerus@gmail.com> + * + * BSD Licensed as described in the file LICENSE + */ +#ifndef __MCP342X_H__ +#define __MCP342X_H__ + +#include <stdbool.h> +#include <i2cdev.h> +#include <esp_err.h> + +#ifdef __cplusplus +extern "C" { +#endif + +#define MCP342X_ADDR_MIN 0x68 +#define MCP342X_ADDR_MAX 0x6f + +/** + * Device operation mode + */ +typedef enum { + MCP342X_ONESHOT = 0, //!< One-shot conversion mode + MCP342X_CONTINUOUS //!< Continuous conversions mode, default +} mcp342x_mode_t; + +/** + * Input channel + */ +typedef enum { + MCP342X_CHANNEL1 = 0, //!< Channel 1, default + MCP342X_CHANNEL2, //!< Channel 2 + MCP342X_CHANNEL3, //!< Channel 3 (MCP3428 only, treated as channel 1 by the MCP3426/MCP3427) + MCP342X_CHANNEL4 //!< Channel 4 (MCP3428 only, treated as channel 2 by the MCP3426/MCP3427) +} mcp342x_channel_t; + +/** + * Resolution + */ +typedef enum { + MCP342X_RES_12 = 0, //!< 12 bits, 240 samples per second + MCP342X_RES_14, //!< 14 bits, 60 samples per second + MCP342X_RES_16, //!< 16 bits, 15 samples per second + MCP342X_RES_18 //!< 18 bits, 3.75 samples per second +} mcp342x_resolution_t; + +/** + * PGA gain + */ +typedef enum { + MCP342X_GAIN1 = 0,//!< x1, default + MCP342X_GAIN2, //!< x2 + MCP342X_GAIN4, //!< x4 + MCP342X_GAIN8 //!< x8 +} mcp342x_gain_t; + +/** + * Device descriptor + */ +typedef struct { + i2c_dev_t i2c_dev; //!< I2C device descriptor + mcp342x_mode_t mode; //!< Operational mode + mcp342x_channel_t channel; //!< Input channel + mcp342x_resolution_t resolution; //!< Resolution + mcp342x_gain_t gain; //!< PGA gain +} mcp342x_t; + +/** + * @brief Initialize device descriptor + * + * @param dev Device descriptor + * @param port I2C port + * @param addr Device address + * @param sda_gpio SDA GPIO pin + * @param scl_gpio SCL GPIO pin + * @return `ESP_OK` on success + */ +esp_err_t mcp342x_init_desc(mcp342x_t *dev, uint8_t addr, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio); + +/** + * @brief Free device descriptor + * + * @param dev Device descriptor + * @return `ESP_OK` on success + */ +esp_err_t mcp342x_free_desc(mcp342x_t *dev); + +/** + * @brief Configure device + * + * @param dev Device descriptor + * @return `ESP_OK` on success + */ +esp_err_t mcp342x_set_config(mcp342x_t *dev); + +/** + * @brief Read device configuration + * + * @param dev Device descriptor + * @return `ESP_OK` on success + */ +esp_err_t mcp342x_get_config(mcp342x_t *dev); + +/** + * @brief Get conversion time in microseconds + * + * @param dev Device descriptor + * @param[out] us Conversion time, us + * @return `ESP_OK` on success + */ +esp_err_t mcp342x_get_sample_time_us(mcp342x_t *dev, uint32_t *us); + +/** + * @brief Start conversion + * + * @param dev Device descriptor + * @return `ESP_OK` on success + */ +esp_err_t mcp342x_start_conversion(mcp342x_t *dev); + +/** + * @brief Get raw ADC value + * + * @param dev Device descriptor + * @param[out] data ADC value + * @param[out] ready Data validity flag + * @return `ESP_OK` on success + */ +esp_err_t mcp342x_get_data(mcp342x_t *dev, int32_t *data, bool *ready); + +/** + * @brief Get ADC voltage + * + * @param dev Device descriptor + * @param[out] volts ADC voltage, volts + * @param[out] ready Data validity flag + * @return `ESP_OK` on success + */ +esp_err_t mcp342x_get_voltage(mcp342x_t *dev, float *volts, bool *ready); + +/** + * @brief Do a single conversion + * + * - start conversion + * - wait conversion time + * - read conversion result + * + * @param dev Device descriptor + * @param[out] data ADC value + * @return `ESP_OK` on success + */ +esp_err_t mcp342x_oneshot_conversion(mcp342x_t *dev, int32_t *data); + +#ifdef __cplusplus +} +#endif + +/**@}*/ + +#endif /* __MCP342X_H__ */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/mcp4725/.eil.yml Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,23 @@ +--- +components: + - name: mcp4725 + description: Driver for 12-bit DAC MCP4725 + group: adc-dac + groups: [] + code_owners: UncleRus + depends: + - i2cdev + - log + - esp_idf_lib_helpers + thread_safe: yes + targets: + - name: esp32 + - name: esp8266 + - name: esp32s2 + - name: esp32c3 + licenses: + - name: BSD-3 + copyrights: + - author: + name: UncleRus + year: 2016
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/mcp4725/CMakeLists.txt Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,5 @@ +idf_component_register( + SRCS mcp4725.c + INCLUDE_DIRS . + REQUIRES i2cdev log esp_idf_lib_helpers +)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/mcp4725/LICENSE Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,26 @@ +Copyright (c) 2016, 2019 Ruslan V. Uss <unclerus@gmail.com> + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, +this list of conditions and the following disclaimer in the documentation +and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of itscontributors +may be used to endorse or promote products derived from this software without +specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/mcp4725/component.mk Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,2 @@ +COMPONENT_ADD_INCLUDEDIRS = . +COMPONENT_DEPENDS = i2cdev log esp_idf_lib_helpers
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/mcp4725/mcp4725.c Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,184 @@ +/* + * Copyright (c) 2016 Ruslan V. Uss <unclerus@gmail.com> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of itscontributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file mcp4725.c + * + * ESP-IDF Driver for 12-bit DAC MCP4725 + * + * Ported from esp-open-rtos + * + * Copyright (c) 2016, 2019 Ruslan V. Uss <unclerus@gmail.com> + * + * BSD Licensed as described in the file LICENSE + */ + +#include <esp_log.h> +#include <esp_idf_lib_helpers.h> +#include "mcp4725.h" + +static const char *TAG = "mcp4725"; + +#define I2C_FREQ_HZ 1000000 // Max 1MHz for esp-idf, but device supports up to 3.4Mhz + +#define CMD_DAC 0x40 +#define CMD_EEPROM 0x60 +#define BIT_READY 0x80 + +#define CHECK(x) do { esp_err_t __; if ((__ = x) != ESP_OK) return __; } while (0) +#define CHECK_ARG(VAL) do { if (!(VAL)) return ESP_ERR_INVALID_ARG; } while (0) + +static esp_err_t read_data(i2c_dev_t *dev, void *data, uint8_t size) +{ + I2C_DEV_TAKE_MUTEX(dev); + I2C_DEV_CHECK(dev, i2c_dev_read(dev, NULL, 0, data, size)); + I2C_DEV_GIVE_MUTEX(dev); + + return ESP_OK; +} + +esp_err_t mcp4725_init_desc(i2c_dev_t *dev, uint8_t addr, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio) +{ + CHECK_ARG(dev); + if (addr < MCP4725A0_I2C_ADDR0 || addr > MCP4725A2_I2C_ADDR1) + { + ESP_LOGE(TAG, "Invalid device address: 0x%02x", addr); + return ESP_ERR_INVALID_ARG; + } + + dev->port = port; + dev->addr = addr; + dev->cfg.sda_io_num = sda_gpio; + dev->cfg.scl_io_num = scl_gpio; +#if HELPER_TARGET_IS_ESP32 + dev->cfg.master.clk_speed = I2C_FREQ_HZ; +#endif + + return i2c_dev_create_mutex(dev); +} + +esp_err_t mcp4725_free_desc(i2c_dev_t *dev) +{ + CHECK_ARG(dev); + + return i2c_dev_delete_mutex(dev); +} + +esp_err_t mcp4725_eeprom_busy(i2c_dev_t *dev, bool *busy) +{ + CHECK_ARG(dev && busy); + + uint8_t res; + CHECK(read_data(dev, &res, 1)); + + *busy = !(res & BIT_READY); + + return ESP_OK; +} + +esp_err_t mcp4725_get_power_mode(i2c_dev_t *dev, bool eeprom, mcp4725_power_mode_t *mode) +{ + CHECK_ARG(dev && mode); + + uint8_t buf[4]; + CHECK(read_data(dev, buf, eeprom ? 4 : 1)); + + *mode = (eeprom ? buf[3] >> 5 : buf[0] >> 1) & 0x03; + + return ESP_OK; +} + +esp_err_t mcp4725_set_power_mode(i2c_dev_t *dev, bool eeprom, mcp4725_power_mode_t mode) +{ + CHECK_ARG(dev); + + uint16_t value; + CHECK(mcp4725_get_raw_output(dev, eeprom, &value)); + + uint8_t data[] = { + (eeprom ? CMD_EEPROM : CMD_DAC) | (((uint8_t)mode & 3) << 1), + value >> 4, + value << 4 + }; + + I2C_DEV_TAKE_MUTEX(dev); + I2C_DEV_CHECK(dev, i2c_dev_write(dev, NULL, 0, data, 3)); + I2C_DEV_GIVE_MUTEX(dev); + + return ESP_OK; +} + +esp_err_t mcp4725_get_raw_output(i2c_dev_t *dev, bool eeprom, uint16_t *value) +{ + CHECK_ARG(dev && value); + + uint8_t buf[5]; + CHECK(read_data(dev, buf, eeprom ? 5 : 3)); + + *value = eeprom + ? ((uint16_t)(buf[3] & 0x0f) << 8) | buf[4] + : ((uint16_t)buf[0] << 4) | (buf[1] >> 4); + + return ESP_OK; +} + +esp_err_t mcp4725_set_raw_output(i2c_dev_t *dev, uint16_t value, bool eeprom) +{ + CHECK_ARG(dev); + + uint8_t data[] = { + (eeprom ? CMD_EEPROM : CMD_DAC), + value >> 4, + value << 4 + }; + + ESP_LOGV(TAG, "Set output value to %u", value); + + I2C_DEV_TAKE_MUTEX(dev); + I2C_DEV_CHECK(dev, i2c_dev_write(dev, NULL, 0, data, 3)); + I2C_DEV_GIVE_MUTEX(dev); + + return ESP_OK; +} + +esp_err_t mcp4725_get_voltage(i2c_dev_t *dev, float vdd, bool eeprom, float *voltage) +{ + CHECK_ARG(voltage); + + uint16_t value; + CHECK(mcp4725_get_raw_output(dev, eeprom, &value)); + + *voltage = vdd / MCP4725_MAX_VALUE * value; + + return ESP_OK; +} + +esp_err_t mcp4725_set_voltage(i2c_dev_t *dev, float vdd, float value, bool eeprom) +{ + return mcp4725_set_raw_output(dev, MCP4725_MAX_VALUE / vdd * value, eeprom); +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/mcp4725/mcp4725.h Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,171 @@ +/* + * Copyright (c) 2016 Ruslan V. Uss <unclerus@gmail.com> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of itscontributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file mcp4725.h + * @defgroup mcp4725 mcp4725 + * @{ + * + * ESP-IDF Driver for 12-bit DAC MCP4725 + * + * Ported from esp-open-rtos + * + * Copyright (c) 2016 Ruslan V. Uss <unclerus@gmail.com> + * + * BSD Licensed as described in the file LICENSE + */ +#ifndef __MCP4725_H__ +#define __MCP4725_H__ + +#include <stdbool.h> +#include <i2cdev.h> +#include <esp_err.h> + +#ifdef __cplusplus +extern "C" { +#endif + +#define MCP4725A0_I2C_ADDR0 0x60 +#define MCP4725A0_I2C_ADDR1 0x61 +#define MCP4725A1_I2C_ADDR0 0x62 +#define MCP4725A1_I2C_ADDR1 0x63 +#define MCP4725A2_I2C_ADDR0 0x64 +#define MCP4725A2_I2C_ADDR1 0x65 + +#define MCP4725_MAX_VALUE 0x0fff + +/** + * Power mode, see datasheet + */ +typedef enum +{ + MCP4725_PM_NORMAL = 0, //!< Normal mode + MCP4725_PM_PD_1K, //!< Power down, 1kOhm resistor to ground + MCP4725_PM_PD_100K, //!< Power down, 100kOhm resistor to ground + MCP4725_PM_PD_500K, //!< Power down, 500kOhm resistor to ground +} mcp4725_power_mode_t; + +/** + * @brief Initialize device descriptor + * + * Default SCL frequency is 1MHz + * + * @param dev I2C device descriptor + * @param port I2C port number + * @param addr I2C address, + * @param sda_gpio SDA GPIO + * @param scl_gpio SCL GPIO + * @return `ESP_OK` on success + */ +esp_err_t mcp4725_init_desc(i2c_dev_t *dev, uint8_t addr, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio); + +/** + * @brief Free device descriptor + * + * @param dev I2C device descriptor + * @return `ESP_OK` on success + */ +esp_err_t mcp4725_free_desc(i2c_dev_t *dev); + +/** + * @brief Get device EEPROM status + * + * @param dev I2C device descriptor + * @param busy true when EEPROM is busy + * @return `ESP_OK` on success + */ +esp_err_t mcp4725_eeprom_busy(i2c_dev_t *dev, bool *busy); + +/** + * @brief Get power mode + * + * @param dev I2C device descriptor + * @param eeprom Read power mode from EEPROM if true + * @param[out] mode Power mode + * @return `ESP_OK` on success + */ +esp_err_t mcp4725_get_power_mode(i2c_dev_t *dev, bool eeprom, mcp4725_power_mode_t *mode); + +/** + * @brief Set power mode + * + * @param dev I2C device descriptor + * @param mode Power mode + * @param eeprom Store mode to device EEPROM if true + * @return `ESP_OK` on success + */ +esp_err_t mcp4725_set_power_mode(i2c_dev_t *dev, bool eeprom, mcp4725_power_mode_t mode); + +/** + * @brief Get current DAC value + * + * @param dev I2C device descriptor + * @param eeprom Read value from device EEPROM if true + * @param[out] value Raw output value, 0..4095 + * @return `ESP_OK` on success + */ +esp_err_t mcp4725_get_raw_output(i2c_dev_t *dev, bool eeprom, uint16_t *value); + +/** + * @brief Set DAC output value + * + * @param dev I2C device descriptor + * @param value Raw output value, 0..4095 + * @param eeprom Store value to device EEPROM if true + * @return `ESP_OK` on success + */ +esp_err_t mcp4725_set_raw_output(i2c_dev_t *dev, uint16_t value, bool eeprom); + +/** + * @brief Get current DAC output voltage + * + * @param dev I2C device descriptor + * @param vdd Device operating voltage, volts + * @param eeprom Read voltage from device EEPROM if true + * @param[out] voltage Current output voltage, volts + * @return `ESP_OK` on success + */ +esp_err_t mcp4725_get_voltage(i2c_dev_t *dev, float vdd, bool eeprom, float *voltage); + +/** + * @brief Set DAC output voltage + * + * @param dev I2C device descriptor + * @param vdd Device operating voltage, volts + * @param value Output value, volts + * @param eeprom Store value to device EEPROM if true + * @return `ESP_OK` on success + */ +esp_err_t mcp4725_set_voltage(i2c_dev_t *dev, float vdd, float value, bool eeprom); + +#ifdef __cplusplus +} +#endif + +/**@}*/ + +#endif /* __MCP4725_H__ */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/mcp960x/.eil.yml Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,23 @@ +--- +components: + - name: mcp960x + description: Driver for MCP9600/MCP9601, thermocouple EMF to temperature converter + group: temperature + groups: [] + code_owners: UncleRus + depends: + - i2cdev + - log + - esp_idf_lib_helpers + thread_safe: yes + targets: + - name: esp32 + - name: esp8266 + - name: esp32s2 + - name: esp32c3 + licenses: + - name: BSD-3 + copyrights: + - author: + name: UncleRus + year: 2020
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/mcp960x/CMakeLists.txt Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,5 @@ +idf_component_register( + SRCS mcp960x.c + INCLUDE_DIRS . + REQUIRES i2cdev log esp_idf_lib_helpers +)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/mcp960x/LICENSE Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,26 @@ +Copyright 2020 Ruslan V. Uss <unclerus@gmail.com> + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, +this list of conditions and the following disclaimer in the documentation +and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of itscontributors +may be used to endorse or promote products derived from this software without +specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/mcp960x/component.mk Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,2 @@ +COMPONENT_ADD_INCLUDEDIRS = . +COMPONENT_DEPENDS = i2cdev log esp_idf_lib_helpers
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/mcp960x/mcp960x.c Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,430 @@ +/* + * Copyright (c) 2020 Ruslan V. Uss <unclerus@gmail.com> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of itscontributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file mcp960x.c + * + * ESP-IDF driver for MCP960X/L0X/RL0X + * Thermocouple EMF to Temperature Converter + * + * Copyright (c) 2020 Ruslan V. Uss <unclerus@gmail.com> + * + * BSD Licensed as described in the file LICENSE + */ +#include <esp_log.h> +#include <esp_idf_lib_helpers.h> +#include "mcp960x.h" + +#define I2C_FREQ_HZ 100000 // 100 KHz + +static const char *TAG = "mcp960x"; + +#define REG_T_HOT 0x00 +#define REG_T_DELTA 0x01 +#define REG_T_COLD 0x02 +#define REG_RAW 0x03 +#define REG_STATUS 0x04 +#define REG_SENS_CONF 0x05 +#define REG_DEV_CONF 0x06 +#define REG_ALERT_CONF 0x08 +#define REG_ALERT_HYST 0x0c +#define REG_ALERT_LIM 0x10 +#define REG_DEV_ID 0x20 + +#define BIT_ST_ALERT1 0 +#define BIT_ST_ALERT2 1 +#define BIT_ST_ALERT3 2 +#define BIT_ST_ALERT4 3 +#define BIT_ST_OC 4 +#define BIT_ST_SC 5 +#define BIT_ST_TH 6 +#define BIT_ST_BURST 7 + +#define MASK_ST_MODE (3 << BIT_ST_OC) + +#define BIT_AC_OUT_EN 0 +#define BIT_AC_COMP_INT 1 +#define BIT_AC_ACT_LVL 2 +#define BIT_AC_T_DIR 3 +#define BIT_AC_T_SRC 4 +#define BIT_AC_INT_CLR 7 + +#define BIT_SC_FILTER 0 +#define BIT_SC_TC_TYPE 4 + +#define MASK_SC_FILTER 7 +#define MASK_SC_TC_TYPE (7 << BIT_SC_TC_TYPE) + +#define BIT_DC_MODE 0 +#define BIT_DC_SAMPLES 2 +#define BIT_DC_ADC_RES 5 +#define BIT_DC_SENS_RES 7 + +#define MASK_DC_MODE (3 << BIT_DC_MODE) +#define MASK_DC_SAMPLES (7 << BIT_DC_SAMPLES) +#define MASK_DC_ADC_RES (3 << BIT_DC_ADC_RES) + +#define BV(x) (1 << (x)) + +#define CHECK(x) do { esp_err_t __; if ((__ = x) != ESP_OK) return __; } while (0) +#define CHECK_ARG(VAL) do { if (!(VAL)) return ESP_ERR_INVALID_ARG; } while (0) + +#define LSB 0.0625 + +inline static uint16_t shuffle(uint16_t x) +{ + return (x >> 8) | (x << 8); +} + +inline static esp_err_t write_reg_8(mcp960x_t *dev, uint8_t reg, uint8_t val) +{ + return i2c_dev_write_reg(&dev->i2c_dev, reg, &val, 1); +} + +inline static esp_err_t read_reg_8(mcp960x_t *dev, uint8_t reg, uint8_t *val) +{ + return i2c_dev_read_reg(&dev->i2c_dev, reg, val, 1); +} + +static esp_err_t read_reg_8_lock(mcp960x_t *dev, uint8_t reg, uint8_t *val) +{ + I2C_DEV_TAKE_MUTEX(&dev->i2c_dev); + I2C_DEV_CHECK(&dev->i2c_dev, read_reg_8(dev, reg, val)); + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); + + return ESP_OK; +} + +static esp_err_t write_reg_8_lock(mcp960x_t *dev, uint8_t reg, uint8_t val) +{ + I2C_DEV_TAKE_MUTEX(&dev->i2c_dev); + I2C_DEV_CHECK(&dev->i2c_dev, write_reg_8(dev, reg, val)); + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); + + return ESP_OK; +} + +static esp_err_t write_reg_16(mcp960x_t *dev, uint8_t reg, uint16_t val) +{ + uint16_t v = shuffle(val); + return i2c_dev_write_reg(&dev->i2c_dev, reg, &v, 2); +} + +static esp_err_t read_reg_16(mcp960x_t *dev, uint8_t reg, uint16_t *val) +{ + CHECK(i2c_dev_read_reg(&dev->i2c_dev, reg, val, 2)); + *val = shuffle(*val); + return ESP_OK; +} + +inline static esp_err_t write_reg_16_float(mcp960x_t *dev, uint8_t reg, float val) +{ + return write_reg_16(dev, reg, (uint16_t)(val / LSB)); +} + +static esp_err_t read_reg_16_float(mcp960x_t *dev, uint8_t reg, float *val) +{ + uint16_t raw; + CHECK(read_reg_16(dev, reg, &raw)); + *val = (float)raw * LSB; + return ESP_OK; +} + +static esp_err_t read_reg_16_float_lock(mcp960x_t *dev, uint8_t reg, float *val) +{ + CHECK_ARG(dev && val); + + I2C_DEV_TAKE_MUTEX(&dev->i2c_dev); + I2C_DEV_CHECK(&dev->i2c_dev, read_reg_16_float(dev, reg, val)); + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); + + return ESP_OK; +} + + +/////////////////////////////////////////////////////////////////////////////// + +esp_err_t mcp960x_init_desc(mcp960x_t *dev, uint8_t addr, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio) +{ + CHECK_ARG(dev); + + if (addr < (MCP960X_ADDR_BASE) || addr > (MCP960X_ADDR_DEFAULT)) + { + ESP_LOGE(TAG, "Invalid I2C address"); + return ESP_ERR_INVALID_ARG; + } + + dev->i2c_dev.port = port; + dev->i2c_dev.addr = addr; + dev->i2c_dev.cfg.sda_io_num = sda_gpio; + dev->i2c_dev.cfg.scl_io_num = scl_gpio; +#if HELPER_TARGET_IS_ESP32 + dev->i2c_dev.cfg.master.clk_speed = I2C_FREQ_HZ; +#endif + + // Min. stretch time ~70us + dev->i2c_dev.timeout_ticks = I2CDEV_MAX_STRETCH_TIME; + + return i2c_dev_create_mutex(&dev->i2c_dev); +} + +esp_err_t mcp960x_free_desc(mcp960x_t *dev) +{ + CHECK_ARG(dev); + + return i2c_dev_delete_mutex(&dev->i2c_dev); +} + +esp_err_t mcp960x_init(mcp960x_t *dev) +{ + CHECK_ARG(dev); + + uint16_t r; + + I2C_DEV_TAKE_MUTEX(&dev->i2c_dev); + I2C_DEV_CHECK(&dev->i2c_dev, read_reg_16(dev, REG_DEV_ID, &r)); + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); + + dev->revision = r; + dev->id = r >> 8; + + ESP_LOGD(TAG, "Device found, ID:Rev 0x%02x:0x%02x", dev->id, dev->revision); + + return ESP_OK; +} + +esp_err_t mcp960x_set_sensor_config(mcp960x_t *dev, mcp960x_thermocouple_t th, uint8_t filter) +{ + CHECK_ARG(dev && th <= MCP960X_TYPE_R && filter <= MCP960X_FILTER_MAX); + + return write_reg_8_lock(dev, REG_SENS_CONF, (th << BIT_SC_TC_TYPE) | filter); +} + +esp_err_t mcp960x_get_sensor_config(mcp960x_t *dev, mcp960x_thermocouple_t *th, uint8_t *filter) +{ + CHECK_ARG(dev && (th || filter)); + + uint8_t r; + CHECK(read_reg_8_lock(dev, REG_SENS_CONF, &r)); + + if (th) + *th = (r & MASK_SC_TC_TYPE) >> BIT_SC_TC_TYPE; + if (filter) + *filter = (r & MASK_SC_FILTER) >> BIT_SC_FILTER; + + return ESP_OK; +} + +esp_err_t mcp960x_set_device_config(mcp960x_t *dev, mcp960x_mode_t mode, mcp960x_burst_samples_t bs, + mcp960x_adc_resolution_t adc_res, mcp960x_tc_resolution_t tc_res) +{ + CHECK_ARG(dev + && mode <= MCP960X_MODE_BURST + && bs <= MCP960X_SAMPLES_128 + && adc_res <= MCP960X_ADC_RES_12 + && tc_res <= MCP960X_TC_RES_0_25); + + return write_reg_8_lock(dev, REG_DEV_CONF, + (mode << BIT_DC_MODE) | + (bs << BIT_DC_SAMPLES) | + (adc_res << BIT_DC_ADC_RES) | + (tc_res << BIT_DC_SENS_RES)); +} + +esp_err_t mcp960x_get_device_config(mcp960x_t *dev, mcp960x_mode_t *mode, mcp960x_burst_samples_t *bs, + mcp960x_adc_resolution_t *adc_res, mcp960x_tc_resolution_t *tc_res) +{ + CHECK_ARG(dev && (mode || bs || adc_res || tc_res)); + + uint8_t r; + CHECK(read_reg_8_lock(dev, REG_SENS_CONF, &r)); + + if (mode) + *mode = (r & MASK_DC_MODE) >> BIT_DC_MODE; + if (bs) + *bs = (r & MASK_DC_SAMPLES) >> BIT_DC_SAMPLES; + if (adc_res) + *adc_res = (r & MASK_DC_ADC_RES) >> BIT_DC_ADC_RES; + if (tc_res) + *tc_res = (r >> BIT_DC_SENS_RES) & 1; + + return ESP_OK; +} + +esp_err_t mcp960x_set_mode(mcp960x_t *dev, mcp960x_mode_t mode) +{ + CHECK_ARG(dev && mode <= MCP960X_MODE_BURST); + + uint8_t r; + I2C_DEV_TAKE_MUTEX(&dev->i2c_dev); + I2C_DEV_CHECK(&dev->i2c_dev, read_reg_8(dev, REG_DEV_CONF, &r)); + r = (r & ~MASK_DC_MODE) | (mode << BIT_DC_MODE); + I2C_DEV_CHECK(&dev->i2c_dev, write_reg_8(dev, REG_DEV_CONF, r)); + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); + + return ESP_OK; +} + +esp_err_t mcp960x_get_raw_adc_data(mcp960x_t *dev, int32_t *data) +{ + CHECK_ARG(dev && data); + + uint8_t raw[3]; + + I2C_DEV_TAKE_MUTEX(&dev->i2c_dev); + I2C_DEV_CHECK(&dev->i2c_dev, i2c_dev_read_reg(&dev->i2c_dev, REG_RAW, raw, 3)); + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); + + uint32_t v = ((uint32_t)raw[0] << 16) | ((uint32_t)raw[1] << 8) | raw[2]; + if (v & 0x800000) v |= 0xff000000; + *((uint32_t *)data) = v; + + return ESP_OK; +} + +esp_err_t mcp960x_get_thermocouple_temp(mcp960x_t *dev, float *t) +{ + return read_reg_16_float_lock(dev, REG_T_HOT, t); +} + +esp_err_t mcp960x_get_delta_temp(mcp960x_t *dev, float *t) +{ + return read_reg_16_float_lock(dev, REG_T_DELTA, t); +} + +esp_err_t mcp960x_get_ambient_temp(mcp960x_t *dev, float *t) +{ + return read_reg_16_float_lock(dev, REG_T_COLD, t); +} + +esp_err_t mcp960x_get_status(mcp960x_t *dev, bool *temp_ready, bool *burst_ready, mcp960x_status_t *status, + bool *alert1, bool *alert2, bool *alert3, bool *alert4) +{ + CHECK_ARG(dev && (temp_ready || burst_ready || status || alert1 || alert2 || alert3 || alert4)); + + uint8_t r; + CHECK(read_reg_8_lock(dev, REG_STATUS, &r)); + + if (temp_ready) + *temp_ready = (r >> BIT_ST_TH) & 1; + if (burst_ready) + *burst_ready = (r >> BIT_ST_BURST) & 1; + if (status) + *status = (r & MASK_ST_MODE) >> BIT_ST_OC; + if (alert1) + *alert1 = (r >> BIT_ST_ALERT1) & 1; + if (alert2) + *alert2 = (r >> BIT_ST_ALERT2) & 1; + if (alert3) + *alert3 = (r >> BIT_ST_ALERT3) & 1; + if (alert4) + *alert4 = (r >> BIT_ST_ALERT4) & 1; + + return ESP_OK; +} + +esp_err_t mcp960x_set_alert_config(mcp960x_t *dev, mcp960x_alert_t alert, mcp960x_alert_mode_t mode, + mcp960x_alert_level_t active_lvl, mcp960x_alert_temp_dir_t temp_dir, mcp960x_alert_source_t src, + float limit, uint8_t hyst) +{ + CHECK_ARG(dev + && alert <= MCP960X_ALERT_4 + && mode <= MCP960X_ALERT_INT + && active_lvl <= MCP960X_ACTIVE_HIGH + && temp_dir <= MCP960X_RISING + && src <= MCP960X_ALERT_SRC_TC); + + I2C_DEV_TAKE_MUTEX(&dev->i2c_dev); + I2C_DEV_CHECK(&dev->i2c_dev, write_reg_8(dev, REG_ALERT_CONF + alert, + (mode > MCP960X_ALERT_DISABLED ? BV(BIT_AC_OUT_EN) : 0) | + (mode > MCP960X_ALERT_DISABLED ? (mode - 1) << BIT_AC_COMP_INT : 0) | + (active_lvl << BIT_AC_ACT_LVL) | + (temp_dir << BIT_AC_T_DIR) | + (src << BIT_AC_T_SRC))); + I2C_DEV_CHECK(&dev->i2c_dev, write_reg_8(dev, REG_ALERT_HYST + alert, hyst)); + I2C_DEV_CHECK(&dev->i2c_dev, write_reg_16_float(dev, REG_ALERT_LIM + alert, limit)); + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); + + return ESP_OK; +} + +esp_err_t mcp960x_get_alert_config(mcp960x_t *dev, mcp960x_alert_t alert, mcp960x_alert_mode_t *mode, + mcp960x_alert_level_t *active_lvl, mcp960x_alert_temp_dir_t *temp_dir, mcp960x_alert_source_t *src, + float *limit, uint8_t *hyst) +{ + CHECK_ARG(dev && alert <= MCP960X_ALERT_4 + && (mode || active_lvl || temp_dir || src || limit || hyst)); + + I2C_DEV_TAKE_MUTEX(&dev->i2c_dev); + + if (mode || active_lvl || temp_dir || src) + { + uint8_t v; + I2C_DEV_CHECK(&dev->i2c_dev, read_reg_8(dev, REG_ALERT_CONF + alert, &v)); + if (mode) + *mode = v & BV(BIT_AC_OUT_EN) ? ((v >> BIT_AC_COMP_INT) & 1) + 1 : 0; + if (active_lvl) + *active_lvl = (v >> BIT_AC_ACT_LVL) & 1; + if (temp_dir) + *temp_dir = (v >> BIT_AC_T_DIR) & 1; + if (src) + *src = (v >> BIT_AC_T_SRC) & 1; + } + if (limit) + I2C_DEV_CHECK(&dev->i2c_dev, read_reg_16_float(dev, REG_ALERT_LIM + alert, limit)); + if (hyst) + I2C_DEV_CHECK(&dev->i2c_dev, read_reg_8(dev, REG_ALERT_LIM + alert, hyst)); + + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); + + return ESP_OK; +} + +esp_err_t mcp960x_get_alert_status(mcp960x_t *dev, mcp960x_alert_t alert, bool *status) +{ + CHECK_ARG(dev && alert <= MCP960X_ALERT_4 && status); + + uint8_t r; + CHECK(read_reg_8_lock(dev, REG_STATUS, &r)); + *status = (r >> (BIT_ST_ALERT1 + alert)) & 1; + + return ESP_OK; +} + +esp_err_t mcp960x_clear_alert_int(mcp960x_t *dev, mcp960x_alert_t alert) +{ + CHECK_ARG(dev && alert <= MCP960X_ALERT_4); + + uint8_t r; + I2C_DEV_TAKE_MUTEX(&dev->i2c_dev); + I2C_DEV_CHECK(&dev->i2c_dev, read_reg_8(dev, REG_ALERT_CONF + alert, &r)); + I2C_DEV_CHECK(&dev->i2c_dev, write_reg_8(dev, REG_ALERT_CONF + alert, r | BV(BIT_AC_INT_CLR))); + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); + + return ESP_OK; +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/mcp960x/mcp960x.h Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,381 @@ +/* + * Copyright (c) 2020 Ruslan V. Uss <unclerus@gmail.com> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of itscontributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file mcp960x.h + * @defgroup mcp960x mcp960x + * @{ + * + * ESP-IDF driver for MCP960X/L0X/RL0X + * Thermocouple EMF to Temperature Converter + * + * Copyright (c) 2020 Ruslan V. Uss <unclerus@gmail.com> + * + * BSD Licensed as described in the file LICENSE + */ +#ifndef __MCP960X_H__ +#define __MCP960X_H__ + +#include <i2cdev.h> +#include <esp_err.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Basic I2C address. Device support 8 addresses, ranging from 0x60 to 0x67. + */ +#define MCP960X_ADDR_BASE 0x60 +/** + * Default I2C address (ADDR pin is connected to VDD or floating). + */ +#define MCP960X_ADDR_DEFAULT 0x67 + +#define MCP960X_FILTER_OFF 0 +#define MCP960X_FILTER_MAX 7 + +/** + * Thermocouple type + */ +typedef enum { + MCP960X_TYPE_K = 0, /**< default */ + MCP960X_TYPE_J, + MCP960X_TYPE_T, + MCP960X_TYPE_N, + MCP960X_TYPE_S, + MCP960X_TYPE_E, + MCP960X_TYPE_B, + MCP960X_TYPE_R, +} mcp960x_thermocouple_t; + +/** + * ADC measurement resolution + */ +typedef enum { + MCP960X_ADC_RES_18 = 0, /**< 18 bits / 2 uV, 320 ms, default */ + MCP960X_ADC_RES_16, /**< 16 bits / 8 uV, 80 ms */ + MCP960X_ADC_RES_14, /**< 14 bits / 32 uV, 20 ms */ + MCP960X_ADC_RES_12, /**< 12 bits / 128 uV, 5 ms */ +} mcp960x_adc_resolution_t; + +/** + * Number of temperature samples in burst mode + */ +typedef enum { + MCP960X_SAMPLES_1 = 0, /**< default */ + MCP960X_SAMPLES_2, + MCP960X_SAMPLES_4, + MCP960X_SAMPLES_8, + MCP960X_SAMPLES_16, + MCP960X_SAMPLES_32, + MCP960X_SAMPLES_64, + MCP960X_SAMPLES_128, +} mcp960x_burst_samples_t; + +/** + * Operational mode + */ +typedef enum { + MCP960X_MODE_NORMAL = 0, /**< default */ + MCP960X_MODE_SHUTDOWN, + MCP960X_MODE_BURST, +} mcp960x_mode_t; + +/** + * Cold-Junction/Ambient sensor resolution + */ +typedef enum { + MCP960X_TC_RES_0_0625 = 0, /**< 0.0625°C, conversion time: 250 ms */ + MCP960X_TC_RES_0_25, /**< 0.25°C, conversion time: 63 ms */ +} mcp960x_tc_resolution_t; + +/** + * Device status + */ +typedef enum { + MCP960X_OK = 0, /**< Device is functioning normally */ + MCP960X_OPEN_CIRCUIT, /**< Open circuit detected on thermocouple input (MCP9601 only) */ + MCP960X_SHORT_CIRCUIT, /**< Short circuit detected on thermocouple input (MCP9601 only) */ +} mcp960x_status_t; + +/** + * Alert + */ +typedef enum { + MCP960X_ALERT_1 = 0, + MCP960X_ALERT_2, + MCP960X_ALERT_3, + MCP960X_ALERT_4, +} mcp960x_alert_t; + +/** + * Alert mode + */ +typedef enum { + MCP960X_ALERT_DISABLED, /**< Alert disabled */ + MCP960X_ALERT_COMP, /**< Comparator mode */ + MCP960X_ALERT_INT, /**< Interrupt mode */ +} mcp960x_alert_mode_t; + +/** + * Alert output active level + */ +typedef enum { + MCP960X_ACTIVE_LOW = 0, /**< Active-low */ + MCP960X_ACTIVE_HIGH /**< Active-high */ +} mcp960x_alert_level_t; + +/** + * Alert temperature direction + */ +typedef enum { + MCP960X_FALLING = 0, /**< Alert limit for falling or cooling temperatures */ + MCP960X_RISING /**< Alert limit for rising or heating temperatures */ +} mcp960x_alert_temp_dir_t; + +/** + * Alert monitoring source + */ +typedef enum { + MCP960X_ALERT_SRC_TH = 0, /**< Alert monitor for thermocouple temperature TH */ + MCP960X_ALERT_SRC_TC /**< Alert monitor for cold-junction sensor TC */ +} mcp960x_alert_source_t; + +/** + * Device descriptor + */ +typedef struct +{ + i2c_dev_t i2c_dev; /**< I2C device descriptor */ + uint8_t id; /**< Hardware ID */ + uint8_t revision; /**< Hardware revision */ +} mcp960x_t; + +/** + * @brief Initialize device descriptor + * + * @param dev Device descriptor + * @param addr Device I2C address + * @param port I2C port + * @param sda_gpio SDA GPIO + * @param scl_gpio SCL GPIO + * @return `ESP_OK` on success + */ +esp_err_t mcp960x_init_desc(mcp960x_t *dev, uint8_t addr, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio); + +/** + * @brief Free device descriptor + * + * @param dev Device descriptor + * @return `ESP_OK` on success + */ +esp_err_t mcp960x_free_desc(mcp960x_t *dev); + +/** + * @brief Init device + * + * Read device HW ID and revision + * + * @param dev Device descriptor + * @return `ESP_OK` on success + */ +esp_err_t mcp960x_init(mcp960x_t *dev); + +/** + * @brief Setup thermocouple parameters + * + * @param dev Device descriptor + * @param th Thermocouple type + * @param filter Digital filtering level, 0..MCP960X_FILTER_MAX + * @return `ESP_OK` on success + */ +esp_err_t mcp960x_set_sensor_config(mcp960x_t *dev, mcp960x_thermocouple_t th, uint8_t filter); + +/** + * @brief Get thermocouple parameters + * + * @param dev Device descriptor + * @param[out] th Thermocouple type, NULL-able + * @param[out] filter Digital filtering level, 0..MCP960X_FILTER_MAX, NULL-able + * @return `ESP_OK` on success + */ +esp_err_t mcp960x_get_sensor_config(mcp960x_t *dev, mcp960x_thermocouple_t *th, uint8_t *filter); + +/** + * @brief Set device configuration + * + * @param dev Device descriptor + * @param mode Operation mode + * @param bs Number of temperature samples in burst mode + * @param adc_res ADC measurement resolution + * @param tc_res Cold-Junction/Ambient sensor resolution + * @return `ESP_OK` on success + */ +esp_err_t mcp960x_set_device_config(mcp960x_t *dev, mcp960x_mode_t mode, mcp960x_burst_samples_t bs, + mcp960x_adc_resolution_t adc_res, mcp960x_tc_resolution_t tc_res); + +/** + * @brief Get device configuration + * + * @param dev Device descriptor + * @param[out] mode Operation mode, NULL-able + * @param[out] bs Number of temperature samples in burst mode, NULL-able + * @param[out] adc_res ADC measurement resolution, NULL-able + * @param[out] tc_res Cold-Junction/Ambient sensor resolution, NULL-able + * @return `ESP_OK` on success + */ +esp_err_t mcp960x_get_device_config(mcp960x_t *dev, mcp960x_mode_t *mode, mcp960x_burst_samples_t *bs, + mcp960x_adc_resolution_t *adc_res, mcp960x_tc_resolution_t *tc_res); + +/** + * @brief Switch device operation mode + * + * @param dev Device descriptor + * @param mode Operation mode + * @return `ESP_OK` on success + */ +esp_err_t mcp960x_set_mode(mcp960x_t *dev, mcp960x_mode_t mode); + +/** + * @brief Get raw ADC data + * + * @param dev Device descriptor + * @param[out] data Raw ADC data + * @return `ESP_OK` on success + */ +esp_err_t mcp960x_get_raw_adc_data(mcp960x_t *dev, int32_t *data); + +/** + * @brief Get thermocouple temperature + * + * Returns cold-junction compensated and error-corrected + * thermocouple temperature + * + * @param dev Device descriptor + * @param[out] t Temperature, degrees Celsius + * @return `ESP_OK` on success + */ +esp_err_t mcp960x_get_thermocouple_temp(mcp960x_t *dev, float *t); + +/** + * @brief Get thermocouple junctions delta temperature + * + * Returns error corrected thermocouple hot-junction temperature without the + * cold-junction compensation + * + * @param dev Device descriptor + * @param[out] t Temperature, degrees Celsius + * @return `ESP_OK` on success + */ +esp_err_t mcp960x_get_delta_temp(mcp960x_t *dev, float *t); + +/** + * @brief Get cold-junction/ambient temperature + * + * @param dev Device descriptor + * @param[out] t Temperature, degrees Celsius + * @return `ESP_OK` on success + */ +esp_err_t mcp960x_get_ambient_temp(mcp960x_t *dev, float *t); + +/** + * @brief Get device status + * + * @param dev Device descriptor + * @param[out] temp_ready Temperature conversion complete if true, NULL-able + * @param[out] burst_ready Burst conversion complete if true, NULL-able (burst mode only) + * @param[out] status Device status, NULL-able + * @param[out] alert1 Alert 1 status, NULL-able + * @param[out] alert2 Alert 2 status, NULL-able + * @param[out] alert3 Alert 3 status, NULL-able + * @param[out] alert4 Alert 4 status, NULL-able + * @return `ESP_OK` on success + */ +esp_err_t mcp960x_get_status(mcp960x_t *dev, bool *temp_ready, bool *burst_ready, mcp960x_status_t *status, + bool *alert1, bool *alert2, bool *alert3, bool *alert4); + +/** + * @brief Setup temperature alert + * + * @param dev Device descriptor + * @param alert Alert index + * @param mode Alert mode + * @param active_lvl Alert output active level + * @param temp_dir Alert temperature direction + * @param src Alert temperature source + * @param limit Alert limit + * @param hyst Hysteresis + * @return `ESP_OK` on success + */ +esp_err_t mcp960x_set_alert_config(mcp960x_t *dev, mcp960x_alert_t alert, mcp960x_alert_mode_t mode, + mcp960x_alert_level_t active_lvl, mcp960x_alert_temp_dir_t temp_dir, mcp960x_alert_source_t src, + float limit, uint8_t hyst); + +/** + * @brief Get temperature alert configuration + * + * @param dev Device descriptor + * @param alert Alert index + * @param[out] mode Alert mode, NULL-able + * @param[out] active_lvl Alert output active level, NULL-able + * @param[out] temp_dir Alert temperature direction, NULL-able + * @param[out] src Alert temperature source, NULL-able + * @param[out] limit Alert limit, NULL-able + * @param[out] hyst Hysteresis, NULL-able + * @return `ESP_OK` on success + */ +esp_err_t mcp960x_get_alert_config(mcp960x_t *dev, mcp960x_alert_t alert, mcp960x_alert_mode_t *mode, + mcp960x_alert_level_t *active_lvl, mcp960x_alert_temp_dir_t *temp_dir, mcp960x_alert_source_t *src, + float *limit, uint8_t *hyst); + +/** + * @brief Get alert status + * + * @param dev Device descriptor + * @param alert Alert index + * @param[out] status Alert status + * @return `ESP_OK` on success + */ +esp_err_t mcp960x_get_alert_status(mcp960x_t *dev, mcp960x_alert_t alert, bool *status); + +/** + * @brief Clear alert interrupt + * + * @param dev Device descriptor + * @param alert Alert index + * @return `ESP_OK` on success + */ +esp_err_t mcp960x_clear_alert_int(mcp960x_t *dev, mcp960x_alert_t alert); + +#ifdef __cplusplus +} +#endif + +/**@}*/ + +#endif /* __MCP960X_H__ */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/mcp9808/.eil.yml Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,23 @@ +--- +components: + - name: mcp9808 + description: Driver for MCP9808 Digital Temperature Sensor + group: temperature + groups: [] + code_owners: UncleRus + depends: + - i2cdev + - log + - esp_idf_lib_helpers + thread_safe: yes + targets: + - name: esp32 + - name: esp8266 + - name: esp32s2 + - name: esp32c3 + licenses: + - name: BSD-3 + copyrights: + - author: + name: UncleRus + year: 2020
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/mcp9808/CMakeLists.txt Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,5 @@ +idf_component_register( + SRCS mcp9808.c + INCLUDE_DIRS . + REQUIRES i2cdev log esp_idf_lib_helpers +)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/mcp9808/LICENSE Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,26 @@ +Copyright (c) 2020 Ruslan V. Uss <unclerus@gmail.com> + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, +this list of conditions and the following disclaimer in the documentation +and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of itscontributors +may be used to endorse or promote products derived from this software without +specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/mcp9808/component.mk Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,2 @@ +COMPONENT_ADD_INCLUDEDIRS = . +COMPONENT_DEPENDS = i2cdev log esp_idf_lib_helpers
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/mcp9808/mcp9808.c Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,331 @@ +/* + * Copyright (c) 2020 Ruslan V. Uss <unclerus@gmail.com> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of itscontributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file mcp9808.c + * + * ESP-IDF driver for MCP9808 Digital Temperature Sensor + * + * Copyright (c) 2020 Ruslan V. Uss <unclerus@gmail.com> + * + * BSD Licensed as described in the file LICENSE + */ +#include <esp_idf_lib_helpers.h> +#include "mcp9808.h" + +#include <math.h> +#include <esp_log.h> + +static const char *TAG = "mcp9808"; + +#define I2C_FREQ_HZ 400000 // 400 kHz + +#define MANUFACTURER_ID 0x0054 +#define DEVICE_ID 0x04 + +#define REG_CONFIG 1 +#define REG_T_UPPER 2 +#define REG_T_LOWER 3 +#define REG_T_CRIT 4 +#define REG_T_A 5 +#define REG_MANUF 6 +#define REG_ID 7 +#define REG_RES 8 + +#define BIT_CONFIG_HYST 9 +#define BIT_CONFIG_SHDN 8 +#define BIT_CONFIG_CRIT_LOCK 7 +#define BIT_CONFIG_WIN_LOCK 6 +#define BIT_CONFIG_INT_CLR 5 +#define BIT_CONFIG_ALERT_STAT 4 +#define BIT_CONFIG_ALERT_CTRL 3 +#define BIT_CONFIG_ALERT_SEL 2 +#define BIT_CONFIG_ALERT_POL 1 +#define BIT_CONFIG_ALERT_MODE 0 + +#define BIT_T_SIGN 12 + +#define BIT_T_A_LOWER 13 +#define BIT_T_A_UPPER 14 +#define BIT_T_A_CRIT 15 + +#define BV(x) (1 << (x)) + +#define CHECK(x) do { esp_err_t __; if ((__ = x) != ESP_OK) return __; } while (0) +#define CHECK_ARG(VAL) do { if (!(VAL)) return ESP_ERR_INVALID_ARG; } while (0) + +static inline esp_err_t read_reg_16_nolock(i2c_dev_t *dev, uint8_t reg, uint16_t *val) +{ + CHECK(i2c_dev_read_reg(dev, reg, val, 2)); + *val = (*val << 8) | (*val >> 8); + return ESP_OK; +} + +static inline esp_err_t write_reg_16_nolock(i2c_dev_t *dev, uint8_t reg, uint16_t val) +{ + uint16_t buf = (val << 8) | (val >> 8); + return i2c_dev_write_reg(dev, reg, &buf, 2); +} + +static esp_err_t read_reg_16(i2c_dev_t *dev, uint8_t reg, uint16_t *val) +{ + I2C_DEV_TAKE_MUTEX(dev); + I2C_DEV_CHECK(dev, read_reg_16_nolock(dev, reg, val)); + I2C_DEV_GIVE_MUTEX(dev); + + return ESP_OK; +} + +static esp_err_t write_reg_16(i2c_dev_t *dev, uint8_t reg, uint16_t val) +{ + I2C_DEV_TAKE_MUTEX(dev); + I2C_DEV_CHECK(dev, write_reg_16_nolock(dev, reg, val)); + I2C_DEV_GIVE_MUTEX(dev); + + return ESP_OK; +} + +static esp_err_t update_reg_16(i2c_dev_t *dev, uint8_t reg, uint16_t mask, uint16_t val) +{ + uint16_t old; + + I2C_DEV_TAKE_MUTEX(dev); + I2C_DEV_CHECK(dev, read_reg_16_nolock(dev, reg, &old)); + I2C_DEV_CHECK(dev, write_reg_16_nolock(dev, reg, (old & mask) | val)); + I2C_DEV_GIVE_MUTEX(dev); + + return ESP_OK; +} + +static esp_err_t write_temp(i2c_dev_t *dev, uint8_t reg, float t) +{ + uint16_t v; + if (t < 0) + v = (((1023 - ((uint16_t)(fabsf(t) * 4.0)) + 1) << 2) & 0x0fff) | BV(BIT_T_SIGN); + else + v = ((uint16_t)(t * 4.0) << 2) & 0x0fff; + + return write_reg_16(dev, reg, v); +} + +static esp_err_t read_temp(i2c_dev_t *dev, uint8_t reg, float *t, uint16_t *raw) +{ + uint16_t v; + CHECK(read_reg_16(dev, reg, &v)); + + *t = (v & 0x0fff) / 16.0; + if (v & BV(BIT_T_SIGN)) *t -= 256; + + if (raw) *raw = v; + + return ESP_OK; +} + +/////////////////////////////////////////////////////////////////////////////// + +esp_err_t mcp9808_init_desc(i2c_dev_t *dev, uint8_t addr, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio) +{ + CHECK_ARG(dev); + + // I2C Address verification is not needed because the device may have a custom factory address + + dev->port = port; + dev->addr = addr; + dev->cfg.sda_io_num = sda_gpio; + dev->cfg.scl_io_num = scl_gpio; +#if HELPER_TARGET_IS_ESP32 + dev->cfg.master.clk_speed = I2C_FREQ_HZ; +#endif + + return i2c_dev_create_mutex(dev); +} + +esp_err_t mcp9808_free_desc(i2c_dev_t *dev) +{ + CHECK_ARG(dev); + + return i2c_dev_delete_mutex(dev); +} + +esp_err_t mcp9808_init(i2c_dev_t *dev) +{ + CHECK_ARG(dev); + + uint16_t v; + + CHECK(read_reg_16(dev, REG_MANUF, &v)); + if (v != MANUFACTURER_ID) + { + ESP_LOGE(TAG, "Invalid manufacturer ID 0x%04x", v); + return ESP_ERR_INVALID_RESPONSE; + } + + CHECK(read_reg_16(dev, REG_ID, &v)); + if ((v >> 8) != DEVICE_ID) + { + ESP_LOGE(TAG, "Invalid device ID 0x%02x", v); + return ESP_ERR_INVALID_RESPONSE; + } + ESP_LOGD(TAG, "Device revision: 0x%02x", v & 0xff); + + // clear all lock bits + return write_reg_16(dev, REG_CONFIG, 0); +} + +esp_err_t mcp9808_set_mode(i2c_dev_t *dev, mcp9808_mode_t mode) +{ + CHECK_ARG(dev); + + return update_reg_16(dev, REG_CONFIG, ~BV(BIT_CONFIG_SHDN), + mode == MCP9808_SHUTDOWN ? BV(BIT_CONFIG_SHDN) : 0); +} + +esp_err_t mcp9808_get_mode(i2c_dev_t *dev, mcp9808_mode_t *mode) +{ + CHECK_ARG(dev && mode); + + uint16_t v; + CHECK(read_reg_16(dev, REG_CONFIG, &v)); + *mode = v & BV(BIT_CONFIG_SHDN) ? MCP9808_SHUTDOWN : MCP9808_CONTINUOUS; + + return ESP_OK; +} + +esp_err_t mcp9808_set_resolution(i2c_dev_t *dev, mcp9808_resolution_t res) +{ + CHECK_ARG(dev); + + return write_reg_16(dev, REG_RES, res); +} + +esp_err_t mcp9808_get_resolution(i2c_dev_t *dev, mcp9808_resolution_t *res) +{ + CHECK_ARG(dev && res); + + return read_reg_16(dev, REG_RES, (uint16_t *)res); +} + +esp_err_t mcp9808_set_alert_config(i2c_dev_t *dev, mcp9808_alert_mode_t mode, + mcp9808_alert_select_t sel, mcp9808_alert_polarity_t polarity, mcp9808_hysteresis_t hyst) +{ + CHECK_ARG(dev); + + if (mode == MCP9808_ALERT_DISABLED) + return update_reg_16(dev, REG_CONFIG, ~BV(BIT_CONFIG_ALERT_CTRL), 0); + + uint16_t mask = ~(BV(BIT_CONFIG_ALERT_CTRL) | BV(BIT_CONFIG_ALERT_SEL) + | BV(BIT_CONFIG_ALERT_MODE) | BV(BIT_CONFIG_ALERT_POL) | (3 << BIT_CONFIG_HYST)); + + return update_reg_16(dev, REG_CONFIG, mask, + BV(BIT_CONFIG_ALERT_CTRL) + | (mode == MCP9808_ALERT_COMPARATOR ? 0 : BV(BIT_CONFIG_ALERT_MODE)) + | ((sel & 1) << BIT_CONFIG_ALERT_SEL) + | ((polarity & 1) << BIT_CONFIG_ALERT_POL) + | ((hyst & 3) << BIT_CONFIG_HYST)); +} + +esp_err_t mcp9808_get_alert_config(i2c_dev_t *dev, mcp9808_alert_mode_t *mode, + mcp9808_alert_select_t *sel, mcp9808_alert_polarity_t *polarity, mcp9808_hysteresis_t *hyst) +{ + CHECK_ARG(dev && mode && sel && polarity && hyst); + + uint16_t v; + CHECK(read_reg_16(dev, REG_CONFIG, &v)); + + if (v & BV(BIT_CONFIG_ALERT_CTRL)) + *mode = v & BV(BIT_CONFIG_ALERT_MODE) ? MCP9808_ALERT_INTERRUPT : MCP9808_ALERT_COMPARATOR; + else + *mode = MCP9808_ALERT_DISABLED; + *sel = v & BV(BIT_CONFIG_ALERT_SEL) ? MCP9808_ALERT_CRIT : MCP9808_ALERT_UP_LOW_CRIT; + *polarity = v & BV(BIT_CONFIG_ALERT_POL) ? MCP9808_ALERT_HIGH : MCP9808_ALERT_LOW; + *hyst = v >> BIT_CONFIG_HYST; + + return ESP_OK; +} + +esp_err_t mcp9808_set_limits(i2c_dev_t *dev, float t_upper, float t_lower, float t_crit) +{ + CHECK_ARG(dev); + + CHECK(write_temp(dev, REG_T_UPPER, t_upper)); + CHECK(write_temp(dev, REG_T_LOWER, t_lower)); + CHECK(write_temp(dev, REG_T_CRIT, t_crit)); + + return ESP_OK; +} + +esp_err_t mcp9808_get_limits(i2c_dev_t *dev, float *t_upper, float *t_lower, float *t_crit) +{ + CHECK_ARG(dev && t_upper && t_lower && t_crit); + + CHECK(read_temp(dev, REG_T_UPPER, t_upper, NULL)); + CHECK(read_temp(dev, REG_T_LOWER, t_lower, NULL)); + CHECK(read_temp(dev, REG_T_CRIT, t_crit, NULL)); + + return ESP_OK; +} + +esp_err_t mcp9808_set_alert_status(i2c_dev_t *dev, bool alert) +{ + CHECK_ARG(dev); + + return update_reg_16(dev, REG_CONFIG, ~BV(BIT_CONFIG_ALERT_STAT), + alert ? BV(BIT_CONFIG_ALERT_STAT) : 0); +} + +esp_err_t mcp9808_get_alert_status(i2c_dev_t *dev, bool *alert) +{ + CHECK_ARG(dev && alert); + + uint16_t v; + CHECK(read_reg_16(dev, REG_CONFIG, &v)); + *alert = v & BV(BIT_CONFIG_ALERT_STAT) ? true : false; + + return ESP_OK; +} + +esp_err_t mcp9808_clear_interrupt(i2c_dev_t *dev) +{ + CHECK_ARG(dev); + + return update_reg_16(dev, REG_CONFIG, ~BV(BIT_CONFIG_INT_CLR), BV(BIT_CONFIG_INT_CLR)); +} + +esp_err_t mcp9808_get_temperature(i2c_dev_t *dev, float *t, bool *lower, bool *upper, bool *crit) +{ + CHECK_ARG(dev && t); + + uint16_t v; + + CHECK(read_temp(dev, REG_T_A, t, &v)); + if (lower) *lower = v & BV(BIT_T_A_LOWER) ? true : false; + if (upper) *upper = v & BV(BIT_T_A_UPPER) ? true : false; + if (crit) *crit = v & BV(BIT_T_A_CRIT) ? true : false; + + return ESP_OK; +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/mcp9808/mcp9808.h Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,269 @@ +/* + * Copyright (c) 2020 Ruslan V. Uss <unclerus@gmail.com> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of itscontributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file mcp9808.h + * @defgroup mcp9808 mcp9808 + * @{ + * + * ESP-IDF driver for MCP9808 Digital Temperature Sensor + * + * Copyright (c) 2020 Ruslan V. Uss <unclerus@gmail.com> + * + * BSD Licensed as described in the file LICENSE + */ +#ifndef __MCP9808_H__ +#define __MCP9808_H__ + +#include <stdbool.h> +#include <i2cdev.h> + +#ifdef __cplusplus +extern "C" { +#endif + +#define MCP9808_I2C_ADDR_000 0x18 //!< I2C address, pins: A0=0, A1=0, A2=0 +#define MCP9808_I2C_ADDR_001 0x19 //!< I2C address, pins: A0=1, A1=0, A2=0 +#define MCP9808_I2C_ADDR_010 0x1A //!< I2C address, pins: A0=0, A1=1, A2=0 +#define MCP9808_I2C_ADDR_011 0x1B //!< I2C address, pins: A0=1, A1=1, A2=0 +#define MCP9808_I2C_ADDR_100 0x1C //!< I2C address, pins: A0=0, A1=0, A2=1 +#define MCP9808_I2C_ADDR_101 0x1D //!< I2C address, pins: A0=1, A1=0, A2=1 +#define MCP9808_I2C_ADDR_110 0x1E //!< I2C address, pins: A0=0, A1=1, A2=1 +#define MCP9808_I2C_ADDR_111 0x1F //!< I2C address, pins: A0=1, A1=1, A2=1 + +/** + * Device mode + */ +typedef enum { + MCP9808_CONTINUOUS = 0, //!< Continuous measurement mode, default + MCP9808_SHUTDOWN //!< Shutdown mode +} mcp9808_mode_t; + +/** + * T upper and T lower hysteresis + */ +typedef enum { + MCP9808_HYST_0 = 0, //!< 0.0 deg.C, default + MCP9808_HYST_1_5, //!< 1.5 deg.C + MCP9808_HYST_3, //!< 3.0 deg.C + MCP9808_HYST_6 //!< 6.0 deg.C +} mcp9808_hysteresis_t; + +/** + * Alert output mode + */ +typedef enum { + MCP9808_ALERT_DISABLED = 0, //!< Alert output disabled, default + MCP9808_ALERT_COMPARATOR, //!< Alert output in comparator mode + MCP9808_ALERT_INTERRUPT //!< Alert output in interrupt mode +} mcp9808_alert_mode_t; + +/** + * Alert output select + */ +typedef enum { + MCP9808_ALERT_UP_LOW_CRIT = 0, //!< Alert when T > T upper or T < T lower or T > T crit, default + MCP9808_ALERT_CRIT, //!< Alert when T > T crit +} mcp9808_alert_select_t; + +/** + * Alert output polarity + */ +typedef enum { + MCP9808_ALERT_LOW = 0, //!< Active-low, pull-up resistor required, default + MCP9808_ALERT_HIGH, //!< Active-high +} mcp9808_alert_polarity_t; + +/** + * Temperature resolution + */ +typedef enum { + MCP9808_RES_0_5 = 0, //!< Resolution = +0.5 deg.C, conversion time = 30 ms, typical sample rate = 33 Hz + MCP9808_RES_0_25, //!< Resolution = +0.25 deg.C, conversion time = 65 ms, typical sample rate = 15 Hz + MCP9808_RES_0_125, //!< Resolution = +0.125 deg.C, conversion time = 130 ms, typical sample rate = 7 Hz + MCP9808_RES_0_0625 //!< Resolution = +0.0625 deg.C, conversion time = 250 ms, typical sample rate = 4 Hz, default +} mcp9808_resolution_t; + +/** + * @brief Initialize device descriptor + * + * @param dev Device descriptor + * @param addr Device I2C address + * @param port I2C port + * @param sda_gpio SDA GPIO + * @param scl_gpio SCL GPIO + * @return `ESP_OK` on success + */ +esp_err_t mcp9808_init_desc(i2c_dev_t *dev, uint8_t addr, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio); + +/** + * @brief Free device descriptor + * + * @param dev Device descriptor + * @return `ESP_OK` on success + */ +esp_err_t mcp9808_free_desc(i2c_dev_t *dev); + +/** + * @brief Init device + * + * Set device configuration to default, clear lock bits + * + * @param dev Device descriptor + * @return `ESP_OK` on success + */ +esp_err_t mcp9808_init(i2c_dev_t *dev); + +/** + * @brief Set device mode + * + * @param dev Device descriptor + * @param mode Power mode + * @return `ESP_OK` on success + */ +esp_err_t mcp9808_set_mode(i2c_dev_t *dev, mcp9808_mode_t mode); + +/** + * @brief Get device mode + * + * @param dev Device descriptor + * @param[out] mode Current power mode + * @return `ESP_OK` on success + */ +esp_err_t mcp9808_get_mode(i2c_dev_t *dev, mcp9808_mode_t *mode); + +/** + * @brief Set temperature resolution + * + * @param dev Device descriptor + * @param res Resolution + * @return `ESP_OK` on success + */ +esp_err_t mcp9808_set_resolution(i2c_dev_t *dev, mcp9808_resolution_t res); + +/** + * @brief Get temperature resolution + * + * @param dev Device descriptor + * @param[out] res Resolution + * @return `ESP_OK` on success + */ +esp_err_t mcp9808_get_resolution(i2c_dev_t *dev, mcp9808_resolution_t *res); + +/** + * @brief Configure alert parameters + * + * @param dev Device descriptor + * @param mode Alert mode + * @param sel Alert window (see datasheet) + * @param polarity Alert output polarity + * @param hyst Alert limits hysteresis + * @return `ESP_OK` on success + */ +esp_err_t mcp9808_set_alert_config(i2c_dev_t *dev, mcp9808_alert_mode_t mode, + mcp9808_alert_select_t sel, mcp9808_alert_polarity_t polarity, mcp9808_hysteresis_t hyst); + +/** + * @brief Get alert configuration + * + * @param dev Device descriptor + * @param[out] mode Alert mode + * @param[out] sel Alert window (see datasheet) + * @param[out] polarity Alert output polarity + * @param[out] hyst Alert limits hysteresis + * @return `ESP_OK` on success + */ +esp_err_t mcp9808_get_alert_config(i2c_dev_t *dev, mcp9808_alert_mode_t *mode, + mcp9808_alert_select_t *sel, mcp9808_alert_polarity_t *polarity, mcp9808_hysteresis_t *hyst); + +/** + * @brief Set alert temperature limits + * + * @param dev Device descriptor + * @param t_upper Upper temperature + * @param t_lower Lower temperature + * @param t_crit Critical temperature + * @return `ESP_OK` on success + */ +esp_err_t mcp9808_set_limits(i2c_dev_t *dev, float t_upper, float t_lower, float t_crit); + +/** + * @brief Get alert temperature limits + * + * @param dev Device descriptor + * @param[out] t_upper Upper temperature + * @param[out] t_lower Lower temperature + * @param[out] t_crit Critical temperature + * @return `ESP_OK` on success + */ +esp_err_t mcp9808_get_limits(i2c_dev_t *dev, float *t_upper, float *t_lower, float *t_crit); + +/** + * @brief Set alert status + * + * @param dev Device descriptor + * @param alert True for alert + * @return `ESP_OK` on success + */ +esp_err_t mcp9808_set_alert_status(i2c_dev_t *dev, bool alert); + +/** + * @brief Get current alert status + * + * @param dev Device descriptor + * @param[out] alert Alert status + * @return `ESP_OK` on success + */ +esp_err_t mcp9808_get_alert_status(i2c_dev_t *dev, bool *alert); + +/** + * @brief Clear interrupt bit + * + * @param dev Device descriptor + * @return `ESP_OK` on success + */ +esp_err_t mcp9808_clear_interrupt(i2c_dev_t *dev); + +/** + * @brief Read temperature + * + * @param dev Device descriptor + * @param[out] t Ambient temperature + * @param[out] lower True if T a < T lower, can be NULL + * @param[out] upper True if T a > T upper, can be NULL + * @param[out] crit True if T a >= T critical, can be NULL + * @return `ESP_OK` on success + */ +esp_err_t mcp9808_get_temperature(i2c_dev_t *dev, float *t, bool *lower, bool *upper, bool *crit); + +#ifdef __cplusplus +} +#endif + +/**@}*/ + +#endif /* __MCP9808_H__ */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/mhz19b/.eil.yml Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,26 @@ +--- +components: + - name: mhz19b + description: Driver for MH-Z19B NDIR CO₂ sensor + group: air-quality + groups: + - gas + code_owners: UncleRus + depends: + - log + - esp_idf_lib_helpers + thread_safe: no + targets: + - name: esp32 + - name: esp8266 + - name: esp32s2 + - name: esp32c3 + licenses: + - name: BSD-3 + copyrights: + - author: + name: Erriez + year: 2020 + - author: + name: DavidD + year: 2021
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/mhz19b/CMakeLists.txt Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,9 @@ +if(${IDF_VERSION_MAJOR} STREQUAL 4 AND ${IDF_VERSION_MINOR} STREQUAL 1 AND ${IDF_VERSION_PATCH} STREQUAL 3) + set(req log esp_idf_lib_helpers driver) +else() + set(req log esp_idf_lib_helpers driver esp_timer) +endif() + +idf_component_register(SRCS "mhz19b.c" + INCLUDE_DIRS "." + REQUIRES ${req})
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/mhz19b/LICENSE Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,26 @@ +Copyright (c) 2021 David Douard <david.douard@sdfa3.org> + +Redistribution and use in source and binary forms, with or woithout +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, +this list of conditions and the following disclaimer in the documentation +and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of itscontributors +may be used to endorse or promote products derived from this software without +specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/mhz19b/README.md Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,61 @@ +# Driver for the MH-Z19B NDIR CO2 sensor + +This driver is heavily inspired from [Erriez MH-Z19B CO2 sensor library for +Arduino](https://github.com/Erriez/ErriezMHZ19B). + +It uses an UART serial port to communicate with the sensor. Since the UART +must configured a specific way (9600 8N1), the `mhz19b_init()` takes care of +configuring it. It will however not start detecting/reading from the sensor. +This must be done. Typical usage would be: + +```C +[...] +#include "esp_log.h" +#include "mhz19b.h" + +#define MHZ19B_TX 12 +#define MHZ19B_RX 13 + +void app_main(void) +{ + int16_t co2; + mhz19b_dev_t dev; + char version[6]; + uint16_t range; + bool autocal; + + mhz19b_init(&dev, UART_NUM_1, MHZ19B_TX, MHZ19B_RX); + + while (!mhz19b_detect(&dev)) + { + ESP_LOGI(TAG, "MHZ-19B not detected, waiting..."); + vTaskDelay(1000 / portTICK_PERIOD_MS); + } + + mhz19b_get_version(&dev, version, 5); + ESP_LOGI(TAG, "MHZ-19B firmware version: %s", version); + ESP_LOGI(TAG, "MHZ-19B set range and autocal"); + + mhz19b_set_range(&dev, MHZ19B_RANGE_5000); + mhz19b_set_auto_calibration(&dev, false); + + mhz19b_get_range(&dev, &range); + ESP_LOGI(TAG, " range: %d", range); + + mhz19b_get_auto_calibration(&dev, &autocal); + ESP_LOGI(TAG, " autocal: %s", autocal ? "ON" : "OFF"); + + while (mhz19b_is_warming_up(&dev)) + { + ESP_LOGI(TAG, "MHZ-19B is warming up"); + vTaskDelay(1000 / portTICK_PERIOD_MS); + } + + while (1) { + mhz19b_read_CO2(&dev, &co2); + ESP_LOGI(TAG, "CO2: %d", co2); + vTaskDelay(5000 / portTICK_PERIOD_MS); + } +} + +```
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/mhz19b/component.mk Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,2 @@ +COMPONENT_ADD_INCLUDEDIRS = . +COMPONENT_DEPENDS = log
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/mhz19b/mhz19b.c Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,295 @@ +/* + * Copyright (c) 2020 Erriez <https://github.com/Erriez> + * Copyright (c) 2021 David Douard <david.douard@sdfa3.org> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of itscontributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file mhz19b.c + * + * ESP-IDF driver for MH-Z19B NDIR CO2 sensor connected to UART + * + * Inspired from https://github.com/Erriez/ErriezMHZ19B + * + * Copyright (c) 2020 Erriez <https://github.com/Erriez> + * Copyright (c) 2021 David Douard <david.douard@sdfa3.org> + * + * BSD Licensed as described in the file LICENSE + */ +#include <string.h> +#include <esp_idf_lib_helpers.h> +#include <esp_log.h> +#include <esp_timer.h> + +#include "mhz19b.h" + +static const char *TAG = "mhz19b"; + +#define CHECK(x) do { esp_err_t __; if ((__ = x) != ESP_OK) return __; } while (0) +#define CHECK_ARG(VAL) do { if (!(VAL)) return ESP_ERR_INVALID_ARG; } while (0) + +esp_err_t mhz19b_init(mhz19b_dev_t *dev, uart_port_t uart_port, gpio_num_t tx_gpio, gpio_num_t rx_gpio) +{ + CHECK_ARG(dev); + + uart_config_t uart_config = { + .baud_rate = 9600, + .data_bits = UART_DATA_8_BITS, + .parity = UART_PARITY_DISABLE, + .stop_bits = UART_STOP_BITS_1, + .flow_ctrl = UART_HW_FLOWCTRL_DISABLE, +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 3, 0) + .source_clk = UART_SCLK_APB, +#endif + }; + CHECK(uart_driver_install(uart_port, MHZ19B_SERIAL_BUF_LEN * 2, 0, 0, NULL, 0)); + CHECK(uart_param_config(uart_port, &uart_config)); +#if HELPER_TARGET_IS_ESP32 + CHECK(uart_set_pin(uart_port, tx_gpio, rx_gpio, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE)); +#endif + + dev->uart_port = uart_port; + // buffer for the incoming data + dev->buf = malloc(MHZ19B_SERIAL_BUF_LEN); + if (!dev->buf) + return ESP_ERR_NO_MEM; + dev->last_value = -1; + dev->last_ts = esp_timer_get_time(); + return ESP_OK; +} + +esp_err_t mhz19b_free(mhz19b_dev_t *dev) +{ + CHECK_ARG(dev && dev->buf); + + free(dev->buf); + dev->buf = NULL; + return ESP_OK; +} + +bool mhz19b_detect(mhz19b_dev_t *dev) +{ + CHECK_ARG(dev); + + uint16_t range; + // Check valid PPM range + if ((mhz19b_get_range(dev, &range) == ESP_OK) && (range > 0)) + return true; + + // Sensor not detected, or invalid range returned + // Try recover by calling setRange(MHZ19B_RANGE_5000); + return false; +} + +bool mhz19b_is_warming_up(mhz19b_dev_t *dev, bool smart_warming_up) +{ + CHECK_ARG(dev); + + // Wait at least 3 minutes after power-on + if (esp_timer_get_time() < MHZ19B_WARMING_UP_TIME_US) + { + if (smart_warming_up) + { + ESP_LOGD(TAG, "Using smart warming up detection"); + + int16_t co2, last_co2; + last_co2 = dev->last_value; + // Sensor returns valid data after CPU reset and keep sensor powered + if (mhz19b_read_co2(dev, &co2) != ESP_OK) + return false; + if ((last_co2 != -1) && (last_co2 != co2)) + // CO2 value changed since last read, no longer warming-up + return false; + } + // Warming-up + return true; + } + + // Not warming-up + return false; +} + +bool mhz19b_is_ready(mhz19b_dev_t *dev) +{ + if (!dev) return false; + + // Minimum CO2 read interval (Built-in LED flashes) + if ((esp_timer_get_time() - dev->last_ts) > MHZ19B_READ_INTERVAL_MS) { + return true; + } + + return false; +} + +esp_err_t mhz19b_read_co2(mhz19b_dev_t *dev, int16_t *co2) +{ + CHECK_ARG(dev && co2); + + // Send command "Read CO2 concentration" + CHECK(mhz19b_send_command(dev, MHZ19B_CMD_READ_CO2, 0, 0, 0, 0, 0)); + + // 16-bit CO2 value in response Bytes 2 and 3 + *co2 = (dev->buf[2] << 8) | dev->buf[3]; + dev->last_ts = esp_timer_get_time(); + dev->last_value = *co2; + + return ESP_OK; +} + +esp_err_t mhz19b_get_version(mhz19b_dev_t *dev, char *version) +{ + CHECK_ARG(dev && version); + + // Clear version + memset(version, 0, 5); + + // Send command "Read firmware version" (NOT DOCUMENTED) + CHECK(mhz19b_send_command(dev, MHZ19B_CMD_GET_VERSION, 0, 0, 0, 0, 0)); + + // Copy 4 ASCII characters to version array like "0443" + for (uint8_t i = 0; i < 4; i++) { + // Version in response Bytes 2..5 + version[i] = dev->buf[i + 2]; + } + + return ESP_OK; +} + +esp_err_t mhz19b_set_range(mhz19b_dev_t *dev, mhz19b_range_t range) +{ + CHECK_ARG(dev); + + // Send "Set range" command + return mhz19b_send_command(dev, MHZ19B_CMD_SET_RANGE, + 0x00, 0x00, 0x00, (range >> 8), (range & 0xff)); +} + +esp_err_t mhz19b_get_range(mhz19b_dev_t *dev, uint16_t *range) +{ + CHECK_ARG(dev && range); + + // Send command "Read range" (NOT DOCUMENTED) + CHECK(mhz19b_send_command(dev, MHZ19B_CMD_GET_RANGE, 0, 0, 0, 0, 0)); + + // Range is in Bytes 4 and 5 + *range = (dev->buf[4] << 8) | dev->buf[5]; + + // Check range according to documented specification + if ((*range != MHZ19B_RANGE_2000) && (*range != MHZ19B_RANGE_5000)) + return ESP_ERR_INVALID_RESPONSE; + + return ESP_OK; +} + +esp_err_t mhz19b_set_auto_calibration(mhz19b_dev_t *dev, bool calibration_on) +{ + CHECK_ARG(dev); + + // Send command "Set Automatic Baseline Correction (ABC logic function)" + return mhz19b_send_command(dev, MHZ19B_CMD_SET_AUTO_CAL, (calibration_on ? 0xA0 : 0x00), 0, 0, 0, 0); +} + +esp_err_t mhz19b_get_auto_calibration(mhz19b_dev_t *dev, bool *calibration_on) +{ + CHECK_ARG(dev && calibration_on); + + // Send command "Get Automatic Baseline Correction (ABC logic function)" (NOT DOCUMENTED) + CHECK(mhz19b_send_command(dev, MHZ19B_CMD_GET_AUTO_CAL, 0, 0, 0, 0, 0)); + + // Response is located in Byte 7: 0 = off, 1 = on + *calibration_on = dev->buf[7] & 0x01; + + return ESP_OK; +} + +esp_err_t mhz19b_start_calibration(mhz19b_dev_t *dev) +{ + CHECK_ARG(dev); + + // Send command "Zero Point Calibration" + return mhz19b_send_command(dev, MHZ19B_CMD_CAL_ZERO_POINT, 0, 0, 0, 0, 0); +} + +esp_err_t mhz19b_send_command(mhz19b_dev_t *dev, uint8_t cmd, uint8_t b3, uint8_t b4, uint8_t b5, uint8_t b6, uint8_t b7) +{ + CHECK_ARG(dev && dev->buf); + + uint8_t txBuffer[MHZ19B_SERIAL_RX_BYTES] = { 0xFF, 0x01, cmd, b3, b4, b5, b6, b7, 0x00 }; + + // Check initialized +#if HELPER_TARGET_IS_ESP32 && ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 0, 0) + if (!uart_is_driver_installed(dev->uart_port)) + return ESP_ERR_INVALID_STATE; +#endif + + // Add CRC Byte + txBuffer[8] = mhz19b_calc_crc(txBuffer); + + // Clear receive buffer + uart_flush(dev->uart_port); + + // Write serial data + uart_write_bytes(dev->uart_port, (char *) txBuffer, sizeof(txBuffer)); + + // Clear receive buffer + memset(dev->buf, 0, MHZ19B_SERIAL_BUF_LEN); + + // Read response from serial buffer + int len = uart_read_bytes(dev->uart_port, dev->buf, + MHZ19B_SERIAL_RX_BYTES, + MHZ19B_SERIAL_RX_TIMEOUT_MS / portTICK_PERIOD_MS); + if (len < 9) + return ESP_ERR_TIMEOUT; + + // Check received Byte[0] == 0xFF and Byte[1] == transmit command + if ((dev->buf[0] != 0xFF) || (dev->buf[1] != cmd)) + return ESP_ERR_INVALID_RESPONSE; + + // Check received Byte[8] CRC + if (dev->buf[8] != mhz19b_calc_crc(dev->buf)) + return ESP_ERR_INVALID_CRC; + + // Return result + return ESP_OK; +} + +// ---------------------------------------------------------------------------- +// Private functions +// ---------------------------------------------------------------------------- + +uint8_t mhz19b_calc_crc(uint8_t *data) +{ + uint8_t crc = 0; + + // Calculate CRC on 8 data Bytes + for (uint8_t i = 1; i < 8; i++) + crc += data[i]; + + crc = 0xFF - crc; + crc++; + + // Return calculated CRC + return crc; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/mhz19b/mhz19b.h Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,279 @@ +/* + * Copyright (c) 2020 Erriez <https://github.com/Erriez> + * Copyright (c) 2021 David Douard <david.douard@sdfa3.org> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of itscontributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file mhz19b.h + * @defgroup mhz19b mhz19b + * @{ + * + * ESP-IDF driver for MH-Z19B NDIR CO2 sensor connected to UART + * + * Inspired from https://github.com/Erriez/ErriezMHZ19B + * + * Copyright (c) 2020 Erriez <https://github.com/Erriez>\n + * Copyright (c) 2021 David Douard <david.douard@sdfa3.org> + * + * BSD Licensed as described in the file LICENSE + */ +#ifndef __MHZ19B_H__ +#define __MHZ19B_H__ + +#include <stdbool.h> +#include <driver/uart.h> +#include <driver/gpio.h> +#include <esp_err.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief MHZ19B sensor device data structure + */ +typedef struct +{ + uart_port_t uart_port; ///< UART port used to communicate + uint8_t *buf; ///< read buffer attached to this device + int16_t last_value; ///< last read value + int64_t last_ts; ///< timestamp of the last sensor co2 level reading +} mhz19b_dev_t; + +//! 3 minutes warming-up time after power-on before valid data returned +#define MHZ19B_WARMING_UP_TIME_MS (3UL * 60000UL) +#define MHZ19B_WARMING_UP_TIME_US (3UL * 60000UL * 1000UL) + +//! Minimum response time between CO2 reads (EXPERIMENTALLY DEFINED) +#define MHZ19B_READ_INTERVAL_MS (5UL * 1000UL) + +//! Fixed 9 Bytes response +#define MHZ19B_SERIAL_RX_BYTES 9 +//! 128 is the minimal value the UART driver will accept (at least on esp32) +#define MHZ19B_SERIAL_BUF_LEN 128 + +//! Response timeout between 15..120 ms at 9600 baud works reliable for all commands +#define MHZ19B_SERIAL_RX_TIMEOUT_MS 120 + +// Documented commands +#define MHZ19B_CMD_SET_AUTO_CAL 0x79 ///< set auto calibration on/off +#define MHZ19B_CMD_READ_CO2 0x86 ///< read CO2 concentration +#define MHZ19B_CMD_CAL_ZERO_POINT 0x87 ///< calibrate zero point at 400ppm +#define MHZ19B_CMD_CAL_SPAN_PIONT 0x88 ///< calibrate span point (NOT IMPLEMENTED) +#define MHZ19B_CMD_SET_RANGE 0x99 ///< set detection range + +// Not documented commands +#define MHZ19B_CMD_GET_AUTO_CAL 0x7D ///< get auto calibration status (NOT DOCUMENTED) +#define MHZ19B_CMD_GET_RANGE 0x9B ///< get range detection (NOT DOCUMENTED) +#define MHZ19B_CMD_GET_VERSION 0xA0 ///< get firmware version (NOT DOCUMENTED) + +/** + * @brief PPM range + */ +typedef enum { + MHZ19B_RANGE_2000 = 2000, ///< 2000 ppm + MHZ19B_RANGE_5000 = 5000, ///< 5000 ppm (Default) +} mhz19b_range_t; + + +/** + * @brief Initialize device descriptor + * + * @param dev Pointer to the sensor device data structure + * @param uart_port UART poert number + * @param tx_gpio GPIO pin number for TX + * @param rx_gpio GPIO pin number for RX + * + * @return ESP_OK on success + */ +esp_err_t mhz19b_init(mhz19b_dev_t *dev, uart_port_t uart_port, gpio_num_t tx_gpio, gpio_num_t rx_gpio); + +/** + * @brief Free device descriptor + * + * @param dev Pointer to the sensor device data structure + * + * @return ESP_OK on success + */ +esp_err_t mhz19b_free(mhz19b_dev_t *dev); + +/** + * @brief Detect sensor by checking range response + * + * @param dev Pointer to the sensor device data structure + * + * @return true if sensor is detected + */ +bool mhz19b_detect(mhz19b_dev_t *dev); + +/** + * @brief Check if sensor is warming up + * + * @param dev Pointer to the sensor device data structure + * @param smart_warming_up Smart check + * + * @return true if sensor is warming up + */ +bool mhz19b_is_warming_up(mhz19b_dev_t *dev, bool smart_warming_up); + +/** + * @brief Check minimum interval between CO2 reads + * + * Not described in the datasheet, but it is the same frequency as the built-in LED blink. + * + * @param dev Pointer to the sensor device data structure + * + * @return true if ready to call ::mhz19b_read_co2() + */ +bool mhz19b_is_ready(mhz19b_dev_t *dev); + +/** + * @brief Read CO2 from sensor + * + * @param dev Pointer to the sensor device data structure + * @param[out] co2 CO2 level + * - < 0: MH-Z19B response error codes. + * - 0..399 ppm: Incorrect values. Minimum value starts at 400ppm outdoor fresh air. + * - 400..1000 ppm: Concentrations typical of occupied indoor spaces with good air exchange. + * - 1000..2000 ppm: Complaints of drowsiness and poor air quality. Ventilation is required. + * - 2000..5000 ppm: Headaches, sleepiness and stagnant, stale, stuffy air. Poor concentration, loss of + * attention, increased heart rate and slight nausea may also be present. + * - Higher values are extremely dangerous and cannot be measured. + * + * @return ESP_OK on success + */ +esp_err_t mhz19b_read_co2(mhz19b_dev_t *dev, int16_t *co2); + +/** + * @brief Get firmware version (NOT DOCUMENTED) + * + * @details + * This is an undocumented command, but most sensors returns ASCII "0430 or "0443". + * + * @param dev Pointer to the sensor device data structure + * @param[out] version + * Returned character pointer to version (must be at least 5 Bytes)\n + * Only valid when return is set to ESP_OK. + * + * @return ESP_OK on success + */ +esp_err_t mhz19b_get_version(mhz19b_dev_t *dev, char *version); + +/** + * @brief Set CO2 range + * + * @param dev Pointer to the sensor device data structure + * @param range Range of the sensor (2000 or 5000, in ppm) + * + * @return ESP_OK on success + */ +esp_err_t mhz19b_set_range(mhz19b_dev_t *dev, mhz19b_range_t range); + +/** + * @brief Get CO2 range in PPM (NOT DOCUMENTED) + * + * @details + * This function verifies valid read ranges of 2000 or 5000 ppm.\n + * Note: Other ranges may be returned, but are undocumented and marked as invalid. + * + * @param dev Pointer to the sensor device data structure + * @param range Current value of the range of the sensor (output) + * + * @return ESP_OK on success + */ +esp_err_t mhz19b_get_range(mhz19b_dev_t *dev, uint16_t *range); + +/** + * @brief Enable or disable automatic calibration + * + * @param dev Pointer to the sensor device data structure + * @param calibration_on + * - true: Automatic calibration on. + * - false: Automatic calibration off. + * + * @return ESP_OK on success + */ +esp_err_t mhz19b_set_auto_calibration(mhz19b_dev_t *dev, bool calibration_on); + +/** + * @brief Get status of automatic calibration (NOT DOCUMENTED) + * + * @param dev Pointer to the sensor device data structure + * @param[out] calibration_on Automatic calibration status + * + * @return ESP_OK on success + */ +esp_err_t mhz19b_get_auto_calibration( + mhz19b_dev_t *dev, bool *calibration_on); // (NOT DOCUMENTED) + +/** + * @brief Start Zero Point Calibration manually at 400ppm + * + * @details + * The sensor must be powered-up for at least 20 minutes in fresh air at 400ppm room + * temperature. Then call this function once to execute self calibration.\n + * Recommended to use this function when auto calibration is off. + * + * @param dev Pointer to the sensor device data structure + * + * @return ESP_OK on success + */ +esp_err_t mhz19b_start_calibration(mhz19b_dev_t *dev); + +/** + * @brief Send serial command to sensor and read response + * + * @details + * Send command to sensor and read response, protected with a receive timeout.\n + * Result is available in the device descriptor buffer. + * + * @param dev Pointer to the sensor device data structure + * @param cmd Command Byte + * @param b3 Byte 3 (default 0) + * @param b4 Byte 4 (default 0) + * @param b5 Byte 5 (default 0) + * @param b6 Byte 6 (default 0) + * @param b7 Byte 7 (default 0) + */ +esp_err_t mhz19b_send_command(mhz19b_dev_t *dev, uint8_t cmd, + uint8_t b3, uint8_t b4, uint8_t b5, + uint8_t b6, uint8_t b7); + +/** + * @brief Calculate CRC on 8 data Bytes buffer + * + * @param data Buffer pointer to calculate CRC. + * @return Calculated 8-bit CRC. + */ +uint8_t mhz19b_calc_crc(uint8_t *data); + +#ifdef __cplusplus +} +#endif /* End of CPP guard */ + +/**@}*/ + +#endif /* __MHZ19B_H__ */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/ms5611/.eil.yml Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,27 @@ +--- +components: + - name: ms5611 + description: Driver for barometic pressure sensor MS5611-01BA03 + group: pressure + groups: + - name: temperature + code_owners: UncleRus + depends: + - i2cdev + - log + - esp_idf_lib_helpers + thread_safe: yes + targets: + - name: esp32 + - name: esp8266 + - name: esp32s2 + - name: esp32c3 + licenses: + - name: BSD-3 + copyrights: + - author: + name: UncleRus + year: 2018 + - author: + name: BernhardG + year: 2016
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/ms5611/CMakeLists.txt Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,5 @@ +idf_component_register( + SRCS ms5611.c + INCLUDE_DIRS . + REQUIRES i2cdev log esp_idf_lib_helpers +)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/ms5611/LICENSE Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,27 @@ +Copyright (c) 2016 Bernhard Guillon <Bernhard.Guillon@begu.org> +Copyright (c) 2018 Ruslan V. Uss <unclerus@gmail.com> + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, +this list of conditions and the following disclaimer in the documentation +and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of itscontributors +may be used to endorse or promote products derived from this software without +specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/ms5611/component.mk Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,2 @@ +COMPONENT_ADD_INCLUDEDIRS = . +COMPONENT_DEPENDS = i2cdev log esp_idf_lib_helpers
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/ms5611/ms5611.c Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,261 @@ +/* + * Copyright (c) 2016 Bernhard Guillon <Bernhard.Guillon@begu.org> + * Copyright (c) 2018 Ruslan V. Uss <unclerus@gmail.com> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of itscontributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file ms5611.c + * + * ESP-IDF driver for barometric pressure sensor MS5611-01BA03 + * + * Ported from esp-open-rtos + * + * Copyright (c) 2016 Bernhard Guillon <Bernhard.Guillon@begu.org>\n + * Copyright (c) 2018 Ruslan V. Uss <unclerus@gmail.com> + * + * BSD Licensed as described in the file LICENSE + */ + +#include <esp_idf_lib_helpers.h> +#include "ms5611.h" + +#include <stddef.h> +#include <esp_system.h> +#include <esp_log.h> +#include <freertos/FreeRTOS.h> +#include <freertos/task.h> +#include <ets_sys.h> + +#define I2C_FREQ_HZ 400000 // 400 kHz + +#define CMD_CONVERT_D1 0x40 +#define CMD_CONVERT_D2 0x50 +#define CMD_ADC_READ 0x00 +#define CMD_RESET 0x1E + +#define PROM_ADDR_SENS 0xa2 +#define PROM_ADDR_OFF 0xa4 +#define PROM_ADDR_TCS 0xa6 +#define PROM_ADDR_TCO 0xa8 +#define PROM_ADDR_T_REF 0xaa +#define PROM_ADDR_TEMPSENS 0xac + +#define CHECK(x) do { esp_err_t __; if ((__ = x) != ESP_OK) return __; } while (0) +#define CHECK_ARG(VAL) do { if (!(VAL)) return ESP_ERR_INVALID_ARG; } while (0) + +static const char *TAG = "ms5611"; + +static inline esp_err_t send_command(ms5611_t *dev, uint8_t cmd) +{ + return i2c_dev_write(&dev->i2c_dev, NULL, 0, &cmd, 1); +} + +static inline uint16_t shuffle(uint16_t val) +{ + return ((val & 0xff00) >> 8) | ((val & 0xff) << 8); +} + +static inline esp_err_t read_prom(ms5611_t *dev) +{ + uint16_t tmp; + + // FIXME calculate CRC (AN502) + + CHECK(i2c_dev_read_reg(&dev->i2c_dev, PROM_ADDR_SENS, &tmp, 2)); + dev->config_data.sens = shuffle(tmp); + CHECK(i2c_dev_read_reg(&dev->i2c_dev, PROM_ADDR_OFF, &tmp, 2)); + dev->config_data.off = shuffle(tmp); + CHECK(i2c_dev_read_reg(&dev->i2c_dev, PROM_ADDR_TCS, &tmp, 2)); + dev->config_data.tcs = shuffle(tmp); + CHECK(i2c_dev_read_reg(&dev->i2c_dev, PROM_ADDR_TCO, &tmp, 2)); + dev->config_data.tco = shuffle(tmp); + CHECK(i2c_dev_read_reg(&dev->i2c_dev, PROM_ADDR_T_REF, &tmp, 2)); + dev->config_data.t_ref = shuffle(tmp); + CHECK(i2c_dev_read_reg(&dev->i2c_dev, PROM_ADDR_TEMPSENS, &tmp, 2)); + dev->config_data.tempsens = shuffle(tmp); + + return ESP_OK; +} + +static esp_err_t read_adc(ms5611_t *dev, uint32_t *result) +{ + uint8_t tmp[3]; + + CHECK(i2c_dev_read_reg(&dev->i2c_dev, 0, tmp, 3)); + *result = (tmp[0] << 16) | (tmp[1] << 8) | tmp[2]; + + // If we are to fast the ADC will return 0 instead of the actual result + return *result == 0 ? ESP_ERR_INVALID_RESPONSE : ESP_OK; +} + +static void wait_conversion(ms5611_t *dev) +{ + uint32_t us = 8220; + switch (dev->osr) + { + case MS5611_OSR_256: us = 500; break; // 0.5ms + case MS5611_OSR_512: us = 1100; break; // 1.1ms + case MS5611_OSR_1024: us = 2100; break; // 2.1ms + case MS5611_OSR_2048: us = 4100; break; // 4.1ms + case MS5611_OSR_4096: us = 8220; break; // 8.22ms + } + ets_delay_us(us); +} + +static inline esp_err_t get_raw_temperature(ms5611_t *dev, uint32_t *result) +{ + CHECK(send_command(dev, CMD_CONVERT_D2 + dev->osr)); + wait_conversion(dev); + CHECK(read_adc(dev, result)); + + return ESP_OK; +} + +static inline esp_err_t get_raw_pressure(ms5611_t *dev, uint32_t *result) +{ + CHECK(send_command(dev, CMD_CONVERT_D1 + dev->osr)); + wait_conversion(dev); + CHECK(read_adc(dev, result)); + + return ESP_OK; +} + +static esp_err_t ms5611_reset(ms5611_t *dev) +{ + I2C_DEV_TAKE_MUTEX(&dev->i2c_dev); + I2C_DEV_CHECK(&dev->i2c_dev, send_command(dev, CMD_RESET)); + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); + + return ESP_OK; +} + +/////////////////////////Public////////////////////////////////////// + +esp_err_t ms5611_init_desc(ms5611_t *dev, uint8_t addr, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio) +{ + CHECK_ARG(dev); + + if (addr != MS5611_ADDR_CSB_HIGH && addr != MS5611_ADDR_CSB_LOW) + { + ESP_LOGE(TAG, "Invalid I2C address 0x%02x", addr); + return ESP_ERR_INVALID_ARG; + } + + dev->i2c_dev.port = port; + dev->i2c_dev.addr = addr; + dev->i2c_dev.cfg.sda_io_num = sda_gpio; + dev->i2c_dev.cfg.scl_io_num = scl_gpio; +#if HELPER_TARGET_IS_ESP32 + dev->i2c_dev.cfg.master.clk_speed = I2C_FREQ_HZ; +#endif + + return i2c_dev_create_mutex(&dev->i2c_dev); +} + +esp_err_t ms5611_free_desc(ms5611_t *dev) +{ + CHECK_ARG(dev); + + return i2c_dev_delete_mutex(&dev->i2c_dev); +} + +esp_err_t ms5611_init(ms5611_t *dev, ms5611_osr_t osr) +{ + CHECK_ARG(dev); + + dev->osr = osr; + + // First of all we need to reset the chip + CHECK(ms5611_reset(dev)); + // Wait a bit for the device to reset + vTaskDelay(pdMS_TO_TICKS(10)); + // Get the config + CHECK(read_prom(dev)); + + return ESP_OK; +} + +esp_err_t ms5611_get_sensor_data(ms5611_t *dev, int32_t *pressure, float *temperature) +{ + CHECK_ARG(dev && pressure && temperature); + + // Second order temperature compensation see datasheet p8 + uint32_t raw_pressure = 0; + uint32_t raw_temperature = 0; + + I2C_DEV_TAKE_MUTEX(&dev->i2c_dev); + I2C_DEV_CHECK(&dev->i2c_dev, get_raw_pressure(dev, &raw_pressure)); + I2C_DEV_CHECK(&dev->i2c_dev, get_raw_temperature(dev, &raw_temperature)); + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); + + // dT = D2 - T_ref = D2 - C5 * 2^8 + int32_t dt = raw_temperature - ((int32_t)dev->config_data.t_ref << 8); + // Actual temperature (-40...85C with 0.01 resolution) + // TEMP = 20C +dT * TEMPSENSE =2000 + dT * C6 / 2^23 + int64_t temp = (2000 + (int64_t)dt * dev->config_data.tempsens / 8388608); + // Offset at actual temperature + // OFF=OFF_t1 + TCO * dT = OFF_t1(C2) * 2^16 + (C4*dT)/2^7 + int64_t off = (int64_t)((int64_t)dev->config_data.off * 65536) + + (((int64_t)dev->config_data.tco * dt) / 128); + // Sensitivity at actual temperature + // SENS=SENS_t1 + TCS *dT = SENS_t1(C1) *2^15 + (TCS(C3) *dT)/2^8 + int64_t sens = (int64_t)(((int64_t)dev->config_data.sens) * 32768) + + (((int64_t)dev->config_data.tcs * dt) / 256); + + // Set defaults for temp >= 2000 + int64_t t_2 = 0; + int64_t off_2 = 0; + int64_t sens_2 = 0; + int64_t help = 0; + if (temp < 2000) + { + // Low temperature + t_2 = ((dt * dt) >> 31); // T2 = dT^2/2^31 + help = (temp - 2000); + help = 5 * help * help; + off_2 = help >> 1; // OFF_2 = 5 * (TEMP - 2000)^2/2^1 + sens_2 = help >> 2; // SENS_2 = 5 * (TEMP - 2000)^2/2^2 + if (temp < -1500) + { + // Very low temperature + help = (temp + 1500); + help = help * help; + off_2 = off_2 + 7 * help; // OFF_2 = OFF_2 + 7 * (TEMP + 1500)^2 + sens_2 = sens_2 + ((11 * help) >> 1); // SENS_2 = SENS_2 + 7 * (TEMP + 1500)^2/2^1 + } + } + + temp = temp - t_2; + off = off - off_2; + sens = sens - sens_2; + + // Temperature compensated pressure (10...1200mbar with 0.01mbar resolution + // P = digital pressure value * SENS - OFF = (D1 * SENS/2^21 -OFF)/2^15 + *pressure = (int32_t)(((int64_t)raw_pressure * (sens / 0x200000) - off) / 32768); + *temperature = (float)temp / 100.0; + + return ESP_OK; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/ms5611/ms5611.h Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,139 @@ +/* + * Copyright (c) 2016 Bernhard Guillon <Bernhard.Guillon@begu.org> + * Copyright (c) 2018 Ruslan V. Uss <unclerus@gmail.com> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of itscontributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file ms5611.h + * @defgroup ms5611 ms5611 + * @{ + * + * ESP-IDF driver for barometric pressure sensor MS5611-01BA03 + * + * Ported from esp-open-rtos + * + * Copyright (c) 2016 Bernhard Guillon <Bernhard.Guillon@begu.org>\n + * Copyright (c) 2018 Ruslan V. Uss <unclerus@gmail.com> + * + * BSD Licensed as described in the file LICENSE + */ +#ifndef __MS5611_H__ +#define __MS5611_H__ + +#include <stdint.h> +#include <i2cdev.h> +#include <esp_err.h> + +#ifdef __cplusplus +extern "C" { +#endif + +#define MS5611_ADDR_CSB_HIGH 0x76 +#define MS5611_ADDR_CSB_LOW 0x77 + +/** + * Oversampling ratio + */ +typedef enum +{ + MS5611_OSR_256 = 0x00, //!< 256 samples per measurement + MS5611_OSR_512 = 0x02, //!< 512 samples per measurement + MS5611_OSR_1024 = 0x04, //!< 1024 samples per measurement + MS5611_OSR_2048 = 0x06, //!< 2048 samples per measurement + MS5611_OSR_4096 = 0x08 //!< 4096 samples per measurement +} ms5611_osr_t; + +/** + * Configuration data + */ +typedef struct +{ + uint16_t sens; //!< C1 Pressure sensitivity | SENS_t1 + uint16_t off; //!< C2 Pressure offset | OFF_t1 + uint16_t tcs; //!< C3 Temperature coefficient of pressure sensitivity | TCS + uint16_t tco; //!< C4 Temperature coefficient of pressure offset | TCO + uint16_t t_ref; //!< C5 Reference temperature | T_ref + uint16_t tempsens; //!< C6 Temperature coefficient of the temperature | TEMPSENSE +} ms5611_config_data_t; + +/** + * Device descriptor + */ +typedef struct +{ + i2c_dev_t i2c_dev; //!< I2C device settings + ms5611_osr_t osr; //!< Oversampling setting + ms5611_config_data_t config_data; //!< Device configuration, filled upon initialize +} ms5611_t; + +/** + * @brief Initialize device descriptor + * + * @param dev Device descriptor + * @param addr I2C address, `MS5611_ADDR_CSB_HIGH` or `MS5611_ADDR_CSB_LOW` + * @param port I2C port + * @param sda_gpio GPIO pin for SDA + * @param scl_gpio GPIO pin for SCL + * @return `ESP_OK` on success + */ +esp_err_t ms5611_init_desc(ms5611_t *dev, uint8_t addr, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio); + +/** + * @brief Free device descriptor + * + * @param dev Device descriptor + * @return `ESP_OK` on success + */ +esp_err_t ms5611_free_desc(ms5611_t *dev); + +/** + * @brief Init MS5611-01BA03 + * + * Reset device and read calibration data + * + * @param dev Device descriptor + * @param osr Oversampling ratio + * @return `ESP_OK` on success + */ +esp_err_t ms5611_init(ms5611_t *dev, ms5611_osr_t osr); + +/** + * @brief Measure pressure and temperature + * + * @param dev Device descriptor + * @param[out] pressure Pressure, Pa + * @param[out] temperature Temperature, degrees Celsius + * @return `ESP_OK` on success + */ +esp_err_t ms5611_get_sensor_data(ms5611_t *dev, int32_t *pressure, float *temperature); + +#ifdef __cplusplus +} +#endif + +/**@}*/ + +#endif /* __MS5611_H__ */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/noise/.eil.yml Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,21 @@ +--- +components: + - name: noise + description: Noise generation functions + group: common + groups: [] + code_owners: UncleRus + depends: + - lib8tion + thread_safe: N/A + targets: + - name: esp32 + - name: esp8266 + - name: esp32s2 + - name: esp32c3 + licenses: + - name: MIT + copyrights: + - author: + name: FastLED + year: 2013
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/noise/CMakeLists.txt Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,5 @@ +idf_component_register( + SRCS noise.c + INCLUDE_DIRS . + REQUIRES lib8tion +)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/noise/LICENSE Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2013 FastLED + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/noise/component.mk Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,2 @@ +COMPONENT_ADD_INCLUDEDIRS = . +COMPONENT_DEPENDS = lib8tion \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/noise/noise.c Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,491 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2013 FastLED + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "noise.h" + +#define ALWAYS_INLINE static inline __attribute__((always_inline)) + +const static uint8_t _p[] = { + 151, 160, 137, 91, 90, 15, 131, 13, 201, 95, 96, 53, 194, 233, 7, 225, + 140, 36, 103, 30, 69, 142, 8, 99, 37, 240, 21, 10, 23, 190, 6, 148, + 247, 120, 234, 75, 0, 26, 197, 62, 94, 252, 219, 203, 117, 35, 11, 32, + 57, 177, 33, 88, 237, 149, 56, 87, 174, 20, 125, 136, 171, 168, 68, 175, + 74, 165, 71, 134, 139, 48, 27, 166, 77, 146, 158, 231, 83, 111, 229, 122, + 60, 211, 133, 230, 220, 105, 92, 41, 55, 46, 245, 40, 244, 102, 143, 54, + 65, 25, 63, 161, 1, 216, 80, 73, 209, 76, 132, 187, 208, 89, 18, 169, + 200, 196, 135, 130, 116, 188, 159, 86, 164, 100, 109, 198, 173, 186, 3, 64, + 52, 217, 226, 250, 124, 123, 5, 202, 38, 147, 118, 126, 255, 82, 85, 212, + 207, 206, 59, 227, 47, 16, 58, 17, 182, 189, 28, 42, 223, 183, 170, 213, + 119, 248, 152, 2, 44, 154, 163, 70, 221, 153, 101, 155, 167, 43, 172, 9, + 129, 22, 39, 253, 19, 98, 108, 110, 79, 113, 224, 232, 178, 185, 112, 104, + 218, 246, 97, 228, 251, 34, 242, 193, 238, 210, 144, 12, 191, 179, 162, 241, + 81, 51, 145, 235, 249, 14, 239, 107, 49, 192, 214, 31, 181, 199, 106, 157, + 184, 84, 204, 176, 115, 121, 50, 45, 127, 4, 150, 254, 138, 236, 205, 93, + 222, 114, 67, 29, 24, 72, 243, 141, 128, 195, 78, 66, 215, 61, 156, 180, + 151 +}; + +#define P(x) _p[(x)] + +#define EASE8(x) (ease8InOutQuad(x)) +#define EASE16(x) (ease16InOutQuad(x)) + +#define FADE(x) scale16(x,x) +#define LERP(a,b,u) lerp15by16(a,b,u) + +ALWAYS_INLINE int16_t grad16_3d(uint8_t hash, int16_t x, int16_t y, int16_t z) +{ + hash = hash & 15; + int16_t u = hash < 8 ? x : y; + int16_t v = hash < 4 ? y : hash == 12 || hash == 14 ? x : z; + if (hash & 1) + u = -u; + if (hash & 2) + v = -v; + + return avg15(u, v); +} + +ALWAYS_INLINE int16_t grad16_2d(uint8_t hash, int16_t x, int16_t y) +{ + hash = hash & 7; + int16_t u, v; + if (hash < 4) + { + u = x; + v = y; + } + else + { + u = y; + v = x; + } + if (hash & 1) + u = -u; + if (hash & 2) + v = -v; + + return avg15(u, v); +} + +ALWAYS_INLINE int16_t grad16_1d(uint8_t hash, int16_t x) +{ + hash = hash & 15; + int16_t u, v; + if (hash > 8) + { + u = x; + v = x; + } + else if (hash < 4) + { + u = x; + v = 1; + } + else + { + u = 1; + v = x; + } + if (hash & 1) + u = -u; + if (hash & 2) + v = -v; + + return avg15(u, v); +} + +ALWAYS_INLINE int8_t selectBasedOnHashBit(uint8_t hash, uint8_t bitnumber, int8_t a, int8_t b) +{ + int8_t result; + result = (hash & (1 << bitnumber)) ? a : b; + return result; +} + +ALWAYS_INLINE int8_t grad8_3d(uint8_t hash, int8_t x, int8_t y, int8_t z) +{ + hash &= 0x0f; + + int8_t u, v; + u = selectBasedOnHashBit(hash, 3, y, x); + + v = hash < 4 ? y : hash == 12 || hash == 14 ? x : z; + + if (hash & 1) + u = -u; + if (hash & 2) + v = -v; + + return avg7(u, v); +} + +ALWAYS_INLINE int8_t grad8_2d(uint8_t hash, int8_t x, int8_t y) +{ + // since the tests below can be done bit-wise on the bottom + // three bits, there's no need to mask off the higher bits + // hash = hash & 7; + int8_t u, v; + if (hash & 4) + { + u = y; + v = x; + } + else + { + u = x; + v = y; + } + + if (hash & 1) + u = -u; + if (hash & 2) + v = -v; + + return avg7(u, v); +} + +ALWAYS_INLINE int8_t grad8_1d(uint8_t hash, int8_t x) +{ + // since the tests below can be done bit-wise on the bottom + // four bits, there's no need to mask off the higher bits + // hash = hash & 15; + int8_t u, v; + if (hash & 8) + { + u = x; + v = x; + } + else + { + if (hash & 4) + { + u = 1; + v = x; + } + else + { + u = x; + v = 1; + } + } + + if (hash & 1) + u = -u; + if (hash & 2) + v = -v; + + return avg7(u, v); +} + +ALWAYS_INLINE int8_t lerp7by8(int8_t a, int8_t b, fract8 frac) +{ + int8_t result; + if (b > a) + { + uint8_t delta = b - a; + uint8_t scaled = scale8(delta, frac); + result = a + scaled; + } + else + { + uint8_t delta = a - b; + uint8_t scaled = scale8(delta, frac); + result = a - scaled; + } + return result; +} + +int16_t inoise16_3d_raw(uint32_t x, uint32_t y, uint32_t z) +{ + // Find the unit cube containing the point + uint8_t X = (x >> 16) & 0xFF; + uint8_t Y = (y >> 16) & 0xFF; + uint8_t Z = (z >> 16) & 0xFF; + + // Hash cube corner coordinates + uint8_t A = P(X) + Y; + uint8_t AA = P(A) + Z; + uint8_t AB = P(A + 1) + Z; + uint8_t B = P(X + 1) + Y; + uint8_t BA = P(B) + Z; + uint8_t BB = P(B + 1) + Z; + + // Get the relative position of the point in the cube + uint16_t u = x & 0xFFFF; + uint16_t v = y & 0xFFFF; + uint16_t w = z & 0xFFFF; + + // Get a signed version of the above for the grad function + int16_t xx = (u >> 1) & 0x7FFF; + int16_t yy = (v >> 1) & 0x7FFF; + int16_t zz = (w >> 1) & 0x7FFF; + uint16_t N = 0x8000L; + + u = ease16InOutQuad(u); + v = ease16InOutQuad(v); + w = ease16InOutQuad(w); + + // skip the log fade adjustment for the moment, otherwise here we would + // adjust fade values for u,v,w + int16_t X1 = lerp15by16(grad16_3d(P(AA), xx, yy, zz), grad16_3d(P(BA), xx - N, yy, zz), u); + int16_t X2 = lerp15by16(grad16_3d(P(AB), xx, yy - N, zz), grad16_3d(P(BB), xx - N, yy - N, zz), u); + int16_t X3 = lerp15by16(grad16_3d(P(AA + 1), xx, yy, zz - N), grad16_3d(P(BA + 1), xx - N, yy, zz - N), u); + int16_t X4 = lerp15by16(grad16_3d(P(AB + 1), xx, yy - N, zz - N), grad16_3d(P(BB + 1), xx - N, yy - N, zz - N), u); + + int16_t Y1 = lerp15by16(X1, X2, v); + int16_t Y2 = lerp15by16(X3, X4, v); + + return lerp15by16(Y1, Y2, w); +} + +uint16_t inoise16_3d(uint32_t x, uint32_t y, uint32_t z) +{ + int32_t ans = inoise16_3d_raw(x, y, z); + ans = ans + 19052L; + uint32_t pan = ans; + pan *= 440L; + return pan >> 8; +} + +int16_t inoise16_2d_raw(uint32_t x, uint32_t y) +{ + // Find the unit cube containing the point + uint8_t X = x >> 16; + uint8_t Y = y >> 16; + + // Hash cube corner coordinates + uint8_t A = P(X) + Y; + uint8_t AA = P(A); + uint8_t AB = P(A + 1); + uint8_t B = P(X + 1) + Y; + uint8_t BA = P(B); + uint8_t BB = P(B + 1); + + // Get the relative position of the point in the cube + uint16_t u = x & 0xFFFF; + uint16_t v = y & 0xFFFF; + + // Get a signed version of the above for the grad function + int16_t xx = (u >> 1) & 0x7FFF; + int16_t yy = (v >> 1) & 0x7FFF; + uint16_t N = 0x8000L; + + u = ease16InOutQuad(u); + v = ease16InOutQuad(v); + + int16_t X1 = lerp15by16(grad16_2d(P(AA), xx, yy), grad16_2d(P(BA), xx - N, yy), u); + int16_t X2 = lerp15by16(grad16_2d(P(AB), xx, yy - N), grad16_2d(P(BB), xx - N, yy - N), u); + + return lerp15by16(X1,X2,v); +} + +uint16_t inoise16_2d(uint32_t x, uint32_t y) +{ + int32_t ans = inoise16_2d_raw(x, y); + ans = ans + 17308L; + uint32_t pan = ans; + pan *= 484L; + return (pan >> 8); +} + +int16_t inoise16_1d_raw(uint32_t x) +{ + // Find the unit cube containing the point + uint8_t X = x >> 16; + + // Hash cube corner coordinates + uint8_t A = P(X); + uint8_t AA = P(A); + uint8_t B = P(X + 1); + uint8_t BA = P(B); + + // Get the relative position of the point in the cube + uint16_t u = x & 0xFFFF; + + // Get a signed version of the above for the grad function + int16_t xx = (u >> 1) & 0x7FFF; + uint16_t N = 0x8000L; + + u = ease16InOutQuad(u); + + return lerp15by16(grad16_1d(P(AA), xx), grad16_1d(P(BA), xx - N), u); +} + +uint16_t inoise16_1d(uint32_t x) +{ + return ((uint32_t)((int32_t)inoise16_1d_raw(x) + 17308L)) << 1; +} + +int8_t inoise8_3d_raw(uint16_t x, uint16_t y, uint16_t z) +{ + // Find the unit cube containing the point + uint8_t X = x >> 8; + uint8_t Y = y >> 8; + uint8_t Z = z >> 8; + + // Hash cube corner coordinates + uint8_t A = P(X) + Y; + uint8_t AA = P(A) + Z; + uint8_t AB = P(A + 1) + Z; + uint8_t B = P(X + 1) +Y; + uint8_t BA = P(B) + Z; + uint8_t BB = P(B + 1) +Z; + + // Get the relative position of the point in the cube + uint8_t u = x; + uint8_t v = y; + uint8_t w = z; + + // Get a signed version of the above for the grad function + int8_t xx = ((uint8_t)x >> 1) & 0x7F; + int8_t yy = ((uint8_t)y >> 1) & 0x7F; + int8_t zz = ((uint8_t)z >> 1) & 0x7F; + uint8_t N = 0x80; + + u = ease8InOutQuad(u); + v = ease8InOutQuad(v); + w = ease8InOutQuad(w); + + int8_t X1 = lerp7by8(grad8_3d(P(AA), xx, yy, zz), grad8_3d(P(BA), xx - N, yy, zz), u); + int8_t X2 = lerp7by8(grad8_3d(P(AB), xx, yy - N, zz), grad8_3d(P(BB), xx - N, yy - N, zz), u); + int8_t X3 = lerp7by8(grad8_3d(P(AA + 1), xx, yy, zz - N), grad8_3d(P(BA + 1), xx - N, yy, zz - N), u); + int8_t X4 = lerp7by8(grad8_3d(P(AB + 1), xx, yy - N, zz - N), grad8_3d(P(BB + 1), xx - N, yy - N, zz - N), u); + + int8_t Y1 = lerp7by8(X1, X2, v); + int8_t Y2 = lerp7by8(X3, X4, v); + + return lerp7by8(Y1, Y2, w); +} + +uint8_t inoise8_3d(uint16_t x, uint16_t y, uint16_t z) +{ + int8_t n = inoise8_3d_raw(x, y, z); // -64..+64 + n += 64; // 0..128 + return qadd8(n, n); // 0..255 +} + +int8_t inoise8_2d_raw(uint16_t x, uint16_t y) +{ + // Find the unit cube containing the point + uint8_t X = x >> 8; + uint8_t Y = y >> 8; + + // Hash cube corner coordinates + uint8_t A = P(X)+Y; + uint8_t AA = P(A); + uint8_t AB = P(A + 1); + uint8_t B = P(X+1)+Y; + uint8_t BA = P(B); + uint8_t BB = P(B + 1); + + // Get the relative position of the point in the cube + uint8_t u = x; + uint8_t v = y; + + // Get a signed version of the above for the grad function + int8_t xx = ((uint8_t)x >> 1) & 0x7F; + int8_t yy = ((uint8_t)y >> 1) & 0x7F; + uint8_t N = 0x80; + + u = ease8InOutQuad(u); + v = ease8InOutQuad(v); + + int8_t X1 = lerp7by8(grad8_2d(P(AA), xx, yy), grad8_2d(P(BA), xx - N, yy), u); + int8_t X2 = lerp7by8(grad8_2d(P(AB), xx, yy - N), grad8_2d(P(BB), xx - N, yy - N), u); + + return lerp7by8(X1, X2, v); +} + +uint8_t inoise8_2d(uint16_t x, uint16_t y) +{ + int8_t n = inoise8_2d_raw(x, y); // -64..+64 + n += 64; // 0..128 + return qadd8(n, n); // 0..255 +} + +// output range = -64 .. +64 +int8_t inoise8_1d_raw(uint16_t x) +{ + // Find the unit cube containing the point + uint8_t X = x >> 8; + + // Hash cube corner coordinates + uint8_t A = P(X); + uint8_t AA = P(A); + uint8_t B = P(X + 1); + uint8_t BA = P(B); + + // Get the relative position of the point in the cube + uint8_t u = x; + + // Get a signed version of the above for the grad function + int8_t xx = ((uint8_t)x >> 1) & 0x7F; + uint8_t N = 0x80; + + u = ease8InOutQuad(u); + + return lerp7by8(grad8_1d(P(AA), xx), grad8_1d(P(BA), xx - N), u); +} + +uint8_t inoise8_1d(uint16_t x) +{ + int8_t n = inoise8_1d_raw(x); // -64..+64 + n += 64; // 0..128 + return qadd8(n, n); // 0..255 +} + +void fill_raw_noise8(uint8_t *pData, uint8_t num_points, uint8_t octaves, uint16_t x, int scale, uint16_t time) +{ + uint32_t _xx = x; + uint32_t scx = scale; + for (int o = 0; o < octaves; ++o) + { + for (int i = 0, xx = _xx; i < num_points; ++i, xx += scx) + pData[i] = qadd8(pData[i], inoise8_2d(xx, time) >> o); + + _xx <<= 1; + scx <<= 1; + } +} + +void fill_raw_noise16into8(uint8_t *pData, uint8_t num_points, uint8_t octaves, uint32_t x, int scale, uint32_t time) +{ + uint32_t _xx = x; + uint32_t scx = scale; + for (int o = 0; o < octaves; ++o) + { + for (int i = 0, xx = _xx; i < num_points; ++i, xx += scx) + { + uint32_t accum = (inoise16_2d(xx, time)) >> o; + accum += (pData[i] << 8); + if (accum > 65535) + { + accum = 65535; + } + pData[i] = accum >> 8; + } + + _xx <<= 1; + scx <<= 1; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/noise/noise.h Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,100 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2013 FastLED + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef __NOISE_H__ +#define __NOISE_H__ + +#include <lib8tion.h> + +///@file noise.h +/// Noise functions provided by the library. + +#ifdef __cplusplus +extern "C" { +#endif + +///@defgroup Noise Noise functions +///Perlin noise function definitions +///@{ +/// @name scaled 16 bit noise functions +///@{ +/// 16 bit, fixed point implementation of Perlin's Noise. Coordinates are +/// 16.16 fixed point values, 32 bit integers with integral coordinates in the high 16 +/// bits and fractional in the low 16 bits, and the function takes 1d, 2d, and 3d coordinate +/// values. These functions are scaled to return 0-65535 +uint16_t inoise16_3d(uint32_t x, uint32_t y, uint32_t z); +uint16_t inoise16_2d(uint32_t x, uint32_t y); +uint16_t inoise16_1d(uint32_t x); +///@} + +/// @name raw 16 bit noise functions +///@{ +/// 16 bit raw versions of the noise functions. These values are not scaled/altered and have +/// output values roughly in the range (-18k,18k) +int16_t inoise16_3d_raw(uint32_t x, uint32_t y, uint32_t z); +int16_t inoise16_2d_raw(uint32_t x, uint32_t y); +int16_t inoise16_1d_raw(uint32_t x); +///@} + +/// @name 8 bit scaled noise functions +///@{ +/// 8 bit, fixed point implementation of perlin's Simplex Noise. Coordinates are +/// 8.8 fixed point values, 16 bit integers with integral coordinates in the high 8 +/// bits and fractional in the low 8 bits, and the function takes 1d, 2d, and 3d coordinate +/// values. These functions are scaled to return 0-255 +uint8_t inoise8_3d(uint16_t x, uint16_t y, uint16_t z); +uint8_t inoise8_2d(uint16_t x, uint16_t y); +uint8_t inoise8_1d(uint16_t x); +///@} + +/// @name 8 bit raw noise functions +///@{ +/// 8 bit raw versions of the noise functions. These values are not scaled/altered and have +/// output values roughly in the range (-70,70) +int8_t inoise8_3d_raw(uint16_t x, uint16_t y, uint16_t z); +int8_t inoise8_2d_raw(uint16_t x, uint16_t y); +int8_t inoise8_1d_raw(uint16_t x); +///@} + +///@name raw fill functions +///@{ +/// Raw noise fill functions - fill into a 1d or 2d array of 8-bit values using either 8-bit noise or 16-bit noise +/// functions. +///@param pData the array of data to write into +///@param num_points the number of points of noise to compute +///@param octaves the number of octaves to use for noise +///@param x the x position in the noise field +///@param y the y position in the noise field for 2d functions +///@param scalex the scale (distance) between x points when filling in noise +///@param scaley the scale (distance) between y points when filling in noise +///@param time the time position for the noise field +void fill_raw_noise8(uint8_t *pData, uint8_t num_points, uint8_t octaves, uint16_t x, int scale, uint16_t time); +void fill_raw_noise16into8(uint8_t *pData, uint8_t num_points, uint8_t octaves, uint32_t x, int scale, uint32_t time); +///@} +///@} + +#ifdef __cplusplus +} +#endif + +#endif /* __NOISE_H__ */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/onewire/.eil.yml Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,29 @@ +--- +components: + - name: onewire + description: Bit-banging 1-Wire driver + group: common + groups: [] + code_owners: UncleRus + depends: + # XXX conditional depends + - driver + - freertos + - log + - esp_idf_lib_helpers + thread_safe: no + targets: + - name: esp32 + - name: esp8266 + - name: esp32s2 + - name: esp32c3 + licenses: + # XXX what `MIT *` means? + - name: MIT + copyrights: + - author: + name: UncleRus + year: 2016 + - author: + name: zeroday + year: 2016
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/onewire/CMakeLists.txt Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,11 @@ +if(${IDF_TARGET} STREQUAL esp8266) + set(req esp8266 freertos esp_idf_lib_helpers) +else() + set(req driver freertos esp_idf_lib_helpers) +endif() + +idf_component_register( + SRCS onewire.c + INCLUDE_DIRS . + REQUIRES ${req} +)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/onewire/Kconfig Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,9 @@ +menu "OneWire" + +config ONEWIRE_CRC8_TABLE + bool "Fast CRC-8 algorithm" + default "y" + help + Compute a Dallas Semiconductor 8 bit CRC using a CRC table located in flash + +endmenu
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/onewire/LICENSE Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,31 @@ +The MIT License (MIT) + +Copyright (c) 2014 zeroday nodemcu.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +------------------------------------------------------------------------------- + +Portions copyright (C) 2000 Dallas Semiconductor Corporation, under the +following additional terms: + +Except as contained in this notice, the name of Dallas Semiconductor +shall not be used except as stated in the Dallas Semiconductor +Branding Policy. +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/onewire/component.mk Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,7 @@ +COMPONENT_ADD_INCLUDEDIRS = . + +ifdef CONFIG_IDF_TARGET_ESP8266 +COMPONENT_DEPENDS = esp8266 freertos esp_idf_lib_helpers +else +COMPONENT_DEPENDS = driver freertos esp_idf_lib_helpers +endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/onewire/onewire.c Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,548 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2014 zeroday nodemcu.com + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * ------------------------------------------------------------------------------- + * Portions copyright (C) 2000 Dallas Semiconductor Corporation, under the + * following additional terms: + * + * Except as contained in this notice, the name of Dallas Semiconductor + * shall not be used except as stated in the Dallas Semiconductor + * Branding Policy. + */ + +/** + * @file onewire.c + * + * Routines to access devices using the Dallas Semiconductor 1-Wire(tm) + * protocol. + * + * This is a port of a bit-banging one wire driver based on the implementation + * from NodeMCU. + * + * This, in turn, appears to have been based on the PJRC Teensy driver + * (https://www.pjrc.com/teensy/td_libs_OneWire.html), by Jim Studt, Paul + * Stoffregen, and a host of others. + * + * The original code is licensed under the MIT license. The CRC code was taken + * (at least partially) from Dallas Semiconductor sample code, which was licensed + * under an MIT license with an additional clause (prohibiting inappropriate use + * of the Dallas Semiconductor name). See the accompanying LICENSE file for + * details. + */ + +#include <string.h> +#include <freertos/FreeRTOS.h> +#include <freertos/task.h> +#include <ets_sys.h> +#include <esp_idf_lib_helpers.h> +#include "onewire.h" + +#define ONEWIRE_SELECT_ROM 0x55 +#define ONEWIRE_SKIP_ROM 0xcc +#define ONEWIRE_SEARCH 0xf0 + +#if HELPER_TARGET_IS_ESP8266 +#define PORT_ENTER_CRITICAL portENTER_CRITICAL() +#define PORT_EXIT_CRITICAL portEXIT_CRITICAL() +#define OPEN_DRAIN_MODE GPIO_MODE_OUTPUT_OD + +#elif HELPER_TARGET_IS_ESP32 +static portMUX_TYPE mux = portMUX_INITIALIZER_UNLOCKED; +#define PORT_ENTER_CRITICAL portENTER_CRITICAL(&mux) +#define PORT_EXIT_CRITICAL portEXIT_CRITICAL(&mux) +#define OPEN_DRAIN_MODE GPIO_MODE_INPUT_OUTPUT_OD +#else +#error BUG: Unknown target +#endif + +// Waits up to `max_wait` microseconds for the specified pin to go high. +// Returns true if successful, false if the bus never comes high (likely +// shorted). +static inline bool _onewire_wait_for_bus(gpio_num_t pin, int max_wait) +{ + bool state; + for (int i = 0; i < ((max_wait + 4) / 5); i++) + { + if (gpio_get_level(pin)) + break; + ets_delay_us(5); + } + state = gpio_get_level(pin); + // Wait an extra 1us to make sure the devices have an adequate recovery + // time before we drive things low again. + ets_delay_us(1); + return state; +} + +static void setup_pin(gpio_num_t pin, bool open_drain) +{ + gpio_set_direction(pin, open_drain ? OPEN_DRAIN_MODE : GPIO_MODE_OUTPUT); + gpio_set_pull_mode(pin, GPIO_PULLUP_ONLY); +} + +// Perform the onewire reset function. We will wait up to 250uS for +// the bus to come high, if it doesn't then it is broken or shorted +// and we return false; +// +// Returns true if a device asserted a presence pulse, false otherwise. +// +bool onewire_reset(gpio_num_t pin) +{ + setup_pin(pin, true); + + gpio_set_level(pin, 1); + // wait until the wire is high... just in case + if (!_onewire_wait_for_bus(pin, 250)) + return false; + + gpio_set_level(pin, 0); + ets_delay_us(480); + + PORT_ENTER_CRITICAL; + gpio_set_level(pin, 1); // allow it to float + ets_delay_us(70); + bool r = !gpio_get_level(pin); + PORT_EXIT_CRITICAL; + + // Wait for all devices to finish pulling the bus low before returning + if (!_onewire_wait_for_bus(pin, 410)) + return false; + + return r; +} + +static bool _onewire_write_bit(gpio_num_t pin, bool v) +{ + if (!_onewire_wait_for_bus(pin, 10)) + return false; + PORT_ENTER_CRITICAL; + if (v) + { + gpio_set_level(pin, 0); // drive output low + ets_delay_us(10); + gpio_set_level(pin, 1); // allow output high + ets_delay_us(55); + } + else + { + gpio_set_level(pin, 0); // drive output low + ets_delay_us(65); + gpio_set_level(pin, 1); // allow output high + } + ets_delay_us(1); + PORT_EXIT_CRITICAL; + + return true; +} + +static int _onewire_read_bit(gpio_num_t pin) +{ + if (!_onewire_wait_for_bus(pin, 10)) + return -1; + + PORT_ENTER_CRITICAL; + gpio_set_level(pin, 0); + ets_delay_us(2); + gpio_set_level(pin, 1); // let pin float, pull up will raise + ets_delay_us(11); + int r = gpio_get_level(pin); // Must sample within 15us of start + ets_delay_us(48); + PORT_EXIT_CRITICAL; + + return r; +} + +// Write a byte. The writing code uses open-drain mode and expects the pullup +// resistor to pull the line high when not driven low. If you need strong +// power after the write (e.g. DS18B20 in parasite power mode) then call +// onewire_power() after this is complete to actively drive the line high. +// +bool onewire_write(gpio_num_t pin, uint8_t v) +{ + for (uint8_t bitMask = 0x01; bitMask; bitMask <<= 1) + if (!_onewire_write_bit(pin, (bitMask & v))) + return false; + + return true; +} + +bool onewire_write_bytes(gpio_num_t pin, const uint8_t *buf, size_t count) +{ + for (size_t i = 0; i < count; i++) + if (!onewire_write(pin, buf[i])) + return false; + + return true; +} + +// Read a byte +// +int onewire_read(gpio_num_t pin) +{ + int r = 0; + + for (uint8_t bitMask = 0x01; bitMask; bitMask <<= 1) + { + int bit = _onewire_read_bit(pin); + if (bit < 0) + return -1; + else if (bit) + r |= bitMask; + } + return r; +} + +bool onewire_read_bytes(gpio_num_t pin, uint8_t *buf, size_t count) +{ + size_t i; + int b; + + for (i = 0; i < count; i++) + { + b = onewire_read(pin); + if (b < 0) + return false; + buf[i] = b; + } + return true; +} + +bool onewire_select(gpio_num_t pin, onewire_addr_t addr) +{ + uint8_t i; + + if (!onewire_write(pin, ONEWIRE_SELECT_ROM)) + return false; + + for (i = 0; i < 8; i++) + { + if (!onewire_write(pin, addr & 0xff)) + return false; + addr >>= 8; + } + + return true; +} + +bool onewire_skip_rom(gpio_num_t pin) +{ + return onewire_write(pin, ONEWIRE_SKIP_ROM); +} + +bool onewire_power(gpio_num_t pin) +{ + // Make sure the bus is not being held low before driving it high, or we + // may end up shorting ourselves out. + if (!_onewire_wait_for_bus(pin, 10)) + return false; + + setup_pin(pin, false); + gpio_set_level(pin, 1); + + return true; +} + +void onewire_depower(gpio_num_t pin) +{ + setup_pin(pin, true); +} + +void onewire_search_start(onewire_search_t *search) +{ + // reset the search state + memset(search, 0, sizeof(*search)); +} + +void onewire_search_prefix(onewire_search_t *search, uint8_t family_code) +{ + uint8_t i; + + search->rom_no[0] = family_code; + for (i = 1; i < 8; i++) + { + search->rom_no[i] = 0; + } + search->last_discrepancy = 64; + search->last_device_found = false; +} + +// Perform a search. If the next device has been successfully enumerated, its +// ROM address will be returned. If there are no devices, no further +// devices, or something horrible happens in the middle of the +// enumeration then ONEWIRE_NONE is returned. Use OneWire::reset_search() to +// start over. +// +// --- Replaced by the one from the Dallas Semiconductor web site --- +//-------------------------------------------------------------------------- +// Perform the 1-Wire Search Algorithm on the 1-Wire bus using the existing +// search state. +// Return 1 : device found, ROM number in ROM_NO buffer +// 0 : device not found, end of search +// +onewire_addr_t onewire_search_next(onewire_search_t *search, gpio_num_t pin) +{ + //TODO: add more checking for read/write errors + uint8_t id_bit_number; + uint8_t last_zero, search_result; + int rom_byte_number; + int8_t id_bit, cmp_id_bit; + onewire_addr_t addr; + unsigned char rom_byte_mask; + bool search_direction; + + // initialize for search + id_bit_number = 1; + last_zero = 0; + rom_byte_number = 0; + rom_byte_mask = 1; + search_result = 0; + + // if the last call was not the last one + if (!search->last_device_found) + { + // 1-Wire reset + if (!onewire_reset(pin)) + { + // reset the search + search->last_discrepancy = 0; + search->last_device_found = false; + return ONEWIRE_NONE; + } + + // issue the search command + onewire_write(pin, ONEWIRE_SEARCH); + + // loop to do the search + do + { + // read a bit and its complement + id_bit = _onewire_read_bit(pin); + cmp_id_bit = _onewire_read_bit(pin); + + if ((id_bit == 1) && (cmp_id_bit == 1)) + break; + else + { + // all devices coupled have 0 or 1 + if (id_bit != cmp_id_bit) + search_direction = id_bit; // bit write value for search + else + { + // if this discrepancy if before the Last Discrepancy + // on a previous next then pick the same as last time + if (id_bit_number < search->last_discrepancy) + search_direction = ((search->rom_no[rom_byte_number] & rom_byte_mask) > 0); + else + // if equal to last pick 1, if not then pick 0 + search_direction = (id_bit_number == search->last_discrepancy); + + // if 0 was picked then record its position in LastZero + if (!search_direction) + last_zero = id_bit_number; + } + + // set or clear the bit in the ROM byte rom_byte_number + // with mask rom_byte_mask + if (search_direction) + search->rom_no[rom_byte_number] |= rom_byte_mask; + else + search->rom_no[rom_byte_number] &= ~rom_byte_mask; + + // serial number search direction write bit + _onewire_write_bit(pin, search_direction); + + // increment the byte counter id_bit_number + // and shift the mask rom_byte_mask + id_bit_number++; + rom_byte_mask <<= 1; + + // if the mask is 0 then go to new SerialNum byte rom_byte_number and reset mask + if (rom_byte_mask == 0) + { + rom_byte_number++; + rom_byte_mask = 1; + } + } + } while (rom_byte_number < 8); // loop until through all ROM bytes 0-7 + + // if the search was successful then + if (!(id_bit_number < 65)) + { + // search successful so set last_discrepancy,last_device_found,search_result + search->last_discrepancy = last_zero; + + // check for last device + if (search->last_discrepancy == 0) + search->last_device_found = true; + + search_result = 1; + } + } + + // if no device found then reset counters so next 'search' will be like a first + if (!search_result || !search->rom_no[0]) + { + search->last_discrepancy = 0; + search->last_device_found = false; + return ONEWIRE_NONE; + } + else + { + addr = 0; + for (rom_byte_number = 7; rom_byte_number >= 0; rom_byte_number--) + { + addr = (addr << 8) | search->rom_no[rom_byte_number]; + } + //printf("Ok I found something at %08x%08x...\n", (uint32_t)(addr >> 32), (uint32_t)addr); + } + return addr; +} + +// The 1-Wire CRC scheme is described in Maxim Application Note 27: +// "Understanding and Using Cyclic Redundancy Checks with Maxim iButton Products" +// + +#ifdef CONFIG_ONEWIRE_CRC8_TABLE +// This table comes from Dallas sample code where it is freely reusable, +// though Copyright (c) 2000 Dallas Semiconductor Corporation +static const uint8_t dscrc_table[] = { + 0, 94, 188, 226, 97, 63, 221, 131, 194, 156, 126, 32, 163, 253, 31, 65, + 157, 195, 33, 127, 252, 162, 64, 30, 95, 1, 227, 189, 62, 96, 130, 220, + 35, 125, 159, 193, 66, 28, 254, 160, 225, 191, 93, 3, 128, 222, 60, 98, + 190, 224, 2, 92, 223, 129, 99, 61, 124, 34, 192, 158, 29, 67, 161, 255, + 70, 24, 250, 164, 39, 121, 155, 197, 132, 218, 56, 102, 229, 187, 89, 7, + 219, 133, 103, 57, 186, 228, 6, 88, 25, 71, 165, 251, 120, 38, 196, 154, + 101, 59, 217, 135, 4, 90, 184, 230, 167, 249, 27, 69, 198, 152, 122, 36, + 248, 166, 68, 26, 153, 199, 37, 123, 58, 100, 134, 216, 91, 5, 231, 185, + 140, 210, 48, 110, 237, 179, 81, 15, 78, 16, 242, 172, 47, 113, 147, 205, + 17, 79, 173, 243, 112, 46, 204, 146, 211, 141, 111, 49, 178, 236, 14, 80, + 175, 241, 19, 77, 206, 144, 114, 44, 109, 51, 209, 143, 12, 82, 176, 238, + 50, 108, 142, 208, 83, 13, 239, 177, 240, 174, 76, 18, 145, 207, 45, 115, + 202, 148, 118, 40, 171, 245, 23, 73, 8, 86, 180, 234, 105, 55, 213, 139, + 87, 9, 235, 181, 54, 104, 138, 212, 149, 203, 41, 119, 244, 170, 72, 22, + 233, 183, 85, 11, 136, 214, 52, 106, 43, 117, 151, 201, 74, 20, 246, 168, + 116, 42, 200, 150, 21, 75, 169, 247, 182, 232, 10, 84, 215, 137, 107, 53 +}; + +// +// Compute a Dallas Semiconductor 8 bit CRC. These show up in the ROM +// and the registers. (note: this might better be done without to +// table, it would probably be smaller and certainly fast enough +// compared to all those delayMicrosecond() calls. But I got +// confused, so I use this table from the examples.) +// +uint8_t onewire_crc8(const uint8_t *data, uint8_t len) +{ + uint8_t crc = 0; + + while (len--) + crc = dscrc_table[crc ^ *data++]; + + return crc; +} +#else +// +// Compute a Dallas Semiconductor 8 bit CRC directly. +// this is much slower, but much smaller, than the lookup table. +// +uint8_t onewire_crc8(const uint8_t *data, uint8_t len) +{ + uint8_t crc = 0; + + while (len--) + { + uint8_t inbyte = *data++; + for (int i = 8; i; i--) + { + uint8_t mix = (crc ^ inbyte) & 0x01; + crc >>= 1; + if (mix) + crc ^= 0x8C; + inbyte >>= 1; + } + } + return crc; +} +#endif + +// Compute the 1-Wire CRC16 and compare it against the received CRC. +// Example usage (reading a DS2408): +// // Put everything in a buffer so we can compute the CRC easily. +// uint8_t buf[13]; +// buf[0] = 0xF0; // Read PIO Registers +// buf[1] = 0x88; // LSB address +// buf[2] = 0x00; // MSB address +// WriteBytes(net, buf, 3); // Write 3 cmd bytes +// ReadBytes(net, buf+3, 10); // Read 6 data bytes, 2 0xFF, 2 CRC16 +// if (!CheckCRC16(buf, 11, &buf[11])) { +// // Handle error. +// } +// +// @param input - Array of bytes to checksum. +// @param len - How many bytes to use. +// @param inverted_crc - The two CRC16 bytes in the received data. +// This should just point into the received data, +// *not* at a 16-bit integer. +// @param crc - The crc starting value (optional) +// @return 1, iff the CRC matches. +bool onewire_check_crc16(const uint8_t* input, size_t len, const uint8_t* inverted_crc, uint16_t crc_iv) +{ + uint16_t crc = ~onewire_crc16(input, len, crc_iv); + return (crc & 0xFF) == inverted_crc[0] && (crc >> 8) == inverted_crc[1]; +} + +// Compute a Dallas Semiconductor 16 bit CRC. This is required to check +// the integrity of data received from many 1-Wire devices. Note that the +// CRC computed here is *not* what you'll get from the 1-Wire network, +// for two reasons: +// 1) The CRC is transmitted bitwise inverted. +// 2) Depending on the endian-ness of your processor, the binary +// representation of the two-byte return value may have a different +// byte order than the two bytes you get from 1-Wire. +// @param input - Array of bytes to checksum. +// @param len - How many bytes to use. +// @param crc - The crc starting value (optional) +// @return The CRC16, as defined by Dallas Semiconductor. +uint16_t onewire_crc16(const uint8_t* input, size_t len, uint16_t crc_iv) +{ + uint16_t crc = crc_iv; + static const uint8_t oddparity[16] = { 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0 }; + + uint16_t i; + for (i = 0; i < len; i++) + { + // Even though we're just copying a byte from the input, + // we'll be doing 16-bit computation with it. + uint16_t cdata = input[i]; + cdata = (cdata ^ crc) & 0xff; + crc >>= 8; + + if (oddparity[cdata & 0x0F] ^ oddparity[cdata >> 4]) + crc ^= 0xC001; + + cdata <<= 6; + crc ^= cdata; + cdata <<= 1; + crc ^= cdata; + } + return crc; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/onewire/onewire.h Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,307 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2014 zeroday nodemcu.com + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * ------------------------------------------------------------------------------- + * Portions copyright (C) 2000 Dallas Semiconductor Corporation, under the + * following additional terms: + * + * Except as contained in this notice, the name of Dallas Semiconductor + * shall not be used except as stated in the Dallas Semiconductor + * Branding Policy. + */ + +/** + * @file onewire.h + * @defgroup onewire onewire + * @{ + * + * @brief Routines to access devices using the Dallas Semiconductor 1-Wire(tm) + * protocol. + * + * This is a port of a bit-banging one wire driver based on the implementation + * from NodeMCU. + * + * This, in turn, appears to have been based on the PJRC Teensy driver + * (https://www.pjrc.com/teensy/td_libs_OneWire.html), by Jim Studt, Paul + * Stoffregen, and a host of others. + * + * The original code is licensed under the MIT license. The CRC code was taken + * (at least partially) from Dallas Semiconductor sample code, which was licensed + * under an MIT license with an additional clause (prohibiting inappropriate use + * of the Dallas Semiconductor name). See the accompanying LICENSE file for + * details. + */ +#ifndef __ONEWIRE_H__ +#define __ONEWIRE_H__ + +#include <stdbool.h> +#include <stdint.h> +#include <driver/gpio.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Type used to hold all 1-Wire device ROM addresses (64-bit) + */ +typedef uint64_t onewire_addr_t; + +/** + * Structure to contain the current state for onewire_search_next(), etc + */ +typedef struct +{ + uint8_t rom_no[8]; + uint8_t last_discrepancy; + bool last_device_found; +} onewire_search_t; + +/** + * ::ONEWIRE_NONE is an invalid ROM address that will never occur in a device + * (CRC mismatch), and so can be useful as an indicator for "no-such-device", + * etc. + */ +#define ONEWIRE_NONE ((onewire_addr_t)(0xffffffffffffffffLL)) + +/** + * @brief Perform a 1-Wire reset cycle. + * + * @param pin The GPIO pin connected to the 1-Wire bus. + * + * @return `true` if at least one device responds with a presence pulse, + * `false` if no devices were detected (or the bus is shorted, etc) + */ +bool onewire_reset(gpio_num_t pin); + +/** + * @brief Issue a 1-Wire "ROM select" command to select a particular device. + * + * It is necessary to call ::onewire_reset() before calling this function. + * + * @param pin The GPIO pin connected to the 1-Wire bus. + * @param addr The ROM address of the device to select + * + * @return `true` if the "ROM select" command could be successfully issued, + * `false` if there was an error. + */ +bool onewire_select(gpio_num_t pin, const onewire_addr_t addr); + +/** + * @brief Issue a 1-Wire "skip ROM" command to select *all* devices on the bus. + * + * It is necessary to call ::onewire_reset() before calling this function. + * + * @param pin The GPIO pin connected to the 1-Wire bus. + * + * @return `true` if the "skip ROM" command could be successfully issued, + * `false` if there was an error. + */ +bool onewire_skip_rom(gpio_num_t pin); + +/** + * @brief Write a byte on the onewire bus. + * + * The writing code uses open-drain mode and expects the pullup resistor to + * pull the line high when not driven low. If you need strong power after the + * write (e.g. DS18B20 in parasite power mode) then call ::onewire_power() + * after this is complete to actively drive the line high. + * + * @param pin The GPIO pin connected to the 1-Wire bus. + * @param v The byte value to write + * + * @return `true` if successful, `false` on error. + */ +bool onewire_write(gpio_num_t pin, uint8_t v); + +/** + * @brief Write multiple bytes on the 1-Wire bus. + * + * See ::onewire_write() for more info. + * + * @param pin The GPIO pin connected to the 1-Wire bus. + * @param buf A pointer to the buffer of bytes to be written + * @param count Number of bytes to write + * + * @return `true` if all bytes written successfully, `false` on error. + */ +bool onewire_write_bytes(gpio_num_t pin, const uint8_t *buf, size_t count); + +/** + * @brief Read a byte from a 1-Wire device. + * + * @param pin The GPIO pin connected to the 1-Wire bus. + * + * @return the read byte on success, negative value on error. + */ +int onewire_read(gpio_num_t pin); + +/** + * @brief Read multiple bytes from a 1-Wire device. + * + * @param pin The GPIO pin connected to the 1-Wire bus. + * @param[out] buf A pointer to the buffer to contain the read bytes + * @param count Number of bytes to read + * + * @return `true` on success, `false` on error. + */ +bool onewire_read_bytes(gpio_num_t pin, uint8_t *buf, size_t count); + +/** + * @brief Actively drive the bus high to provide extra power for certain + * operations of parasitically-powered devices. + * + * For parasitically-powered devices which need more power than can be + * provided via the normal pull-up resistor, it may be necessary for some + * operations to drive the bus actively high. This function can be used to + * perform that operation. + * + * The bus can be depowered once it is no longer needed by calling + * ::onewire_depower(), or it will be depowered automatically the next time + * ::onewire_reset() is called to start another command. + * + * @note Make sure the device(s) you are powering will not pull more current + * than the ESP32/ESP8266 is able to supply via its GPIO pins (this is + * especially important when multiple devices are on the same bus and + * they are all performing a power-intensive operation at the same time + * (i.e. multiple DS18B20 sensors, which have all been given a + * "convert T" operation by using ::onewire_skip_rom())). + * + * @note This routine will check to make sure that the bus is already high + * before driving it, to make sure it doesn't attempt to drive it high + * while something else is pulling it low (which could cause a reset or + * damage the ESP32/ESP8266). + * + * @param pin The GPIO pin connected to the 1-Wire bus. + * + * @return `true` on success, `false` on error. + */ +bool onewire_power(gpio_num_t pin); + +/** + * @brief Stop forcing power onto the bus. + * + * You only need to do this if you previously called ::onewire_power() to drive + * the bus high and now want to allow it to float instead. Note that + * onewire_reset() will also automatically depower the bus first, so you do + * not need to call this first if you just want to start a new operation. + * + * @param pin The GPIO pin connected to the 1-Wire bus. + */ +void onewire_depower(gpio_num_t pin); + +/** + * @brief Clear the search state so that it will start from the beginning on + * the next call to ::onewire_search_next(). + * + * @param[out] search The onewire_search_t structure to reset. + */ +void onewire_search_start(onewire_search_t *search); + +/** + * @brief Setup the search to search for devices with the specified + * "family code". + * + * @param[out] search The onewire_search_t structure to update. + * @param family_code The "family code" to search for. + */ +void onewire_search_prefix(onewire_search_t *search, uint8_t family_code); + +/** + * @brief Search for the next device on the bus. + * + * The order of returned device addresses is deterministic. You will always + * get the same devices in the same order. + * + * @note It might be a good idea to check the CRC to make sure you didn't get + * garbage. + * + * @return the address of the next device on the bus, or ::ONEWIRE_NONE if + * there is no next address. ::ONEWIRE_NONE might also mean that + * the bus is shorted, there are no devices, or you have already + * retrieved all of them. + */ +onewire_addr_t onewire_search_next(onewire_search_t *search, gpio_num_t pin); + +/** + * @brief Compute a Dallas Semiconductor 8 bit CRC. + * + * These are used in the ROM address and scratchpad registers to verify the + * transmitted data is correct. + */ +uint8_t onewire_crc8(const uint8_t *data, uint8_t len); + +/** + * @brief Compute the 1-Wire CRC16 and compare it against the received CRC. + * + * Example usage (reading a DS2408): + * @code{.c} + * // Put everything in a buffer so we can compute the CRC easily. + * uint8_t buf[13]; + * buf[0] = 0xF0; // Read PIO Registers + * buf[1] = 0x88; // LSB address + * buf[2] = 0x00; // MSB address + * onewire_write_bytes(pin, buf, 3); // Write 3 cmd bytes + * onewire_read_bytes(pin, buf+3, 10); // Read 6 data bytes, 2 0xFF, 2 CRC16 + * if (!onewire_check_crc16(buf, 11, &buf[11])) { + * // TODO: Handle error. + * } + * @endcode + * + * @param input Array of bytes to checksum. + * @param len Number of bytes in `input` + * @param inverted_crc The two CRC16 bytes in the received data. + * This should just point into the received data, + * *not* at a 16-bit integer. + * @param crc_iv The crc starting value (optional) + * + * @return `true` if the CRC matches, `false` otherwise. + */ +bool onewire_check_crc16(const uint8_t* input, size_t len, const uint8_t* inverted_crc, uint16_t crc_iv); + +/** + * @brief Compute a Dallas Semiconductor 16 bit CRC. + * + * This is required to check the integrity of data received from many 1-Wire + * devices. Note that the CRC computed here is *not* what you'll get from the + * 1-Wire network, for two reasons: + * + * 1. The CRC is transmitted bitwise inverted. + * 2. Depending on the endian-ness of your processor, the binary + * representation of the two-byte return value may have a different + * byte order than the two bytes you get from 1-Wire. + * + * @param input Array of bytes to checksum. + * @param len How many bytes are in `input`. + * @param crc_iv The crc starting value (optional) + * + * @return the CRC16, as defined by Dallas Semiconductor. + */ +uint16_t onewire_crc16(const uint8_t* input, size_t len, uint16_t crc_iv); + +#ifdef __cplusplus +} +#endif + +/**@}*/ + +#endif /* __ONEWIRE_H__ */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/pca9557/.eil.yml Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,23 @@ +--- +components: + - name: pca9557 + description: Driver for PCA9537/PCA9557/TCA9534 remote 4/8-bit I/O expanders for I2C-bus + group: gpio + groups: [] + code_owners: UncleRus + depends: + - i2cdev + - log + - esp_idf_lib_helpers + thread_safe: yes + targets: + - name: esp32 + - name: esp8266 + - name: esp32s2 + - name: esp32c3 + licenses: + - name: BSD-3 + copyrights: + - author: + name: UncleRus + year: 2021
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/pca9557/CMakeLists.txt Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,5 @@ +idf_component_register( + SRCS pca9557.c + INCLUDE_DIRS . + REQUIRES i2cdev log esp_idf_lib_helpers +)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/pca9557/LICENSE Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,26 @@ +Copyright (c) 2021 Ruslan V. Uss <unclerus@gmail.com> + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, +this list of conditions and the following disclaimer in the documentation +and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of itscontributors +may be used to endorse or promote products derived from this software without +specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/pca9557/component.mk Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,2 @@ +COMPONENT_ADD_INCLUDEDIRS = . +COMPONENT_DEPENDS = i2cdev log esp_idf_lib_helpers
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/pca9557/pca9557.c Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,175 @@ +/* + * Copyright (c) 2021 Ruslan V. Uss <unclerus@gmail.com> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of itscontributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file pca9557.c + * + * ESP-IDF driver for PCA9537/PCA9557/TCA9534 remote 4/8-bit I/O expanders for I2C-bus + * + * Copyright (c) 2021 Ruslan V. Uss <unclerus@gmail.com> + * + * BSD Licensed as described in the file LICENSE + */ + +#include <esp_idf_lib_helpers.h> +#include "pca9557.h" + +#define I2C_FREQ_HZ 400000 + +#define REG_IN 0x00 +#define REG_OUT 0x01 +#define REG_POL 0x02 +#define REG_CONF 0x03 + +#define CHECK(x) do { esp_err_t __; if ((__ = x) != ESP_OK) return __; } while (0) +#define CHECK_ARG(VAL) do { if (!(VAL)) return ESP_ERR_INVALID_ARG; } while (0) + +static esp_err_t read_reg_8(i2c_dev_t *dev, uint8_t reg, uint8_t *val) +{ + CHECK_ARG(dev && val); + + I2C_DEV_TAKE_MUTEX(dev); + I2C_DEV_CHECK(dev, i2c_dev_read_reg(dev, reg, val, 1)); + I2C_DEV_GIVE_MUTEX(dev); + + return ESP_OK; +} + +static esp_err_t write_reg_8(i2c_dev_t *dev, uint8_t reg, uint8_t val) +{ + CHECK_ARG(dev); + + I2C_DEV_TAKE_MUTEX(dev); + I2C_DEV_CHECK(dev, i2c_dev_write_reg(dev, reg, &val, 1)); + I2C_DEV_GIVE_MUTEX(dev); + + return ESP_OK; +} + +static esp_err_t read_bit(i2c_dev_t *dev, uint8_t reg, uint8_t bit, uint32_t *val) +{ + CHECK_ARG(dev && val); + + uint8_t v; + CHECK(read_reg_8(dev, reg, &v)); + *val = v & BIT(bit) ? 1 : 0; + + return ESP_OK; +} + +static esp_err_t write_bit(i2c_dev_t *dev, uint8_t reg, uint8_t bit, uint32_t val) +{ + CHECK_ARG(dev); + + uint8_t v; + + I2C_DEV_TAKE_MUTEX(dev); + I2C_DEV_CHECK(dev, i2c_dev_read_reg(dev, reg, &v, 1)); + v = (v & ~BIT(bit)) | (val ? BIT(bit) : 0); + I2C_DEV_CHECK(dev, i2c_dev_write_reg(dev, reg, &v, 1)); + I2C_DEV_GIVE_MUTEX(dev); + + return ESP_OK; +} + +//////////////////////////////////////////////////////////////////////////////// + +esp_err_t pca9557_init_desc(i2c_dev_t *dev, uint8_t addr, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio) +{ + CHECK_ARG(dev && ( + (addr & PCA9557_I2C_ADDR_BASE) == PCA9557_I2C_ADDR_BASE || + (addr & TCA9534_I2C_ADDR_BASE) == TCA9534_I2C_ADDR_BASE || + addr == PCA9537_I2C_ADDR)); + + dev->port = port; + dev->addr = addr; + dev->cfg.sda_io_num = sda_gpio; + dev->cfg.scl_io_num = scl_gpio; +#if HELPER_TARGET_IS_ESP32 + dev->cfg.master.clk_speed = I2C_FREQ_HZ; +#endif + + return i2c_dev_create_mutex(dev); +} + +esp_err_t pca9557_free_desc(i2c_dev_t *dev) +{ + CHECK_ARG(dev); + + return i2c_dev_delete_mutex(dev); +} + +esp_err_t pca9557_port_get_mode(i2c_dev_t *dev, uint8_t *mode) +{ + return read_reg_8(dev, REG_CONF, mode); +} + +esp_err_t pca9557_port_set_mode(i2c_dev_t *dev, uint8_t mode) +{ + return write_reg_8(dev, REG_CONF, mode); +} + +esp_err_t pca9557_port_get_polarity(i2c_dev_t *dev, uint8_t *pol) +{ + return read_reg_8(dev, REG_POL, pol); +} + +esp_err_t pca9557_port_set_polarity(i2c_dev_t *dev, uint8_t pol) +{ + return write_reg_8(dev, REG_POL, pol); +} + +esp_err_t pca9557_port_read(i2c_dev_t *dev, uint8_t *val) +{ + return read_reg_8(dev, REG_IN, val); +} + +esp_err_t pca9557_port_write(i2c_dev_t *dev, uint8_t val) +{ + return write_reg_8(dev, REG_OUT, val); +} + +esp_err_t pca9557_get_mode(i2c_dev_t *dev, uint8_t pin, pca9557_mode_t *mode) +{ + return read_bit(dev, REG_CONF, pin, (uint32_t *)mode); +} + +esp_err_t pca9557_set_mode(i2c_dev_t *dev, uint8_t pin, pca9557_mode_t mode) +{ + return write_bit(dev, REG_CONF, pin, mode); +} + +esp_err_t pca9557_get_level(i2c_dev_t *dev, uint8_t pin, uint32_t *val) +{ + return read_bit(dev, REG_IN, pin, val); +} + +esp_err_t pca9557_set_level(i2c_dev_t *dev, uint8_t pin, uint32_t val) +{ + return write_bit(dev, REG_OUT, pin, val); +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/pca9557/pca9557.h Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,194 @@ +/* + * Copyright (c) 2021 Ruslan V. Uss <unclerus@gmail.com> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of itscontributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file pca9557.h + * @defgroup pca9557 pca9557 + * @{ + * + * ESP-IDF driver for PCA9537/PCA9557/TCA9534 remote 4/8-bit I/O expanders for I2C-bus + * + * Copyright (c) 2021 Ruslan V. Uss <unclerus@gmail.com> + * + * BSD Licensed as described in the file LICENSE + */ +#ifndef __PCA9557_H__ +#define __PCA9557_H__ + +#include <stddef.h> +#include <i2cdev.h> +#include <esp_err.h> + +#ifdef __cplusplus +extern "C" { +#endif + +#define PCA9537_I2C_ADDR 0x49 ///< I2C address for PCA9537 +#define PCA9557_I2C_ADDR_BASE 0x18 ///< Base I2C address for PCA9557 +#define TCA9534_I2C_ADDR_BASE 0x20 ///< Base I2C address for TCA9534 + +/** + * Pin modes (directions) + */ +typedef enum { + PCA9557_MODE_OUTPUT = 0, + PCA9557_MODE_INPUT, +} pca9557_mode_t; + +/** + * @brief Initialize device descriptor + * + * Default SCL frequency is 400kHz + * + * @param dev Pointer to I2C device descriptor + * @param port I2C port number + * @param addr I2C address (`0b0011<A2><A1><A0>` for PCA9557/`PCA9537_I2C_ADDR` for PCA9537, `TCA9534_I2C_ADDR_BASE` for TCA9534) + * @param sda_gpio SDA GPIO + * @param scl_gpio SCL GPIO + * @return `ESP_OK` on success + */ +esp_err_t pca9557_init_desc(i2c_dev_t *dev, uint8_t addr, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio); + +/** + * @brief Free device descriptor + * + * @param dev Pointer to I2C device descriptor + * @return `ESP_OK` on success + */ +esp_err_t pca9557_free_desc(i2c_dev_t *dev); + +/** + * @brief Get directions of I/O pins + * + * 0 - output, 1 - input for each bit in `mode` + * + * @param dev Pointer to device descriptor + * @param[out] mode I/O directions + * @return `ESP_OK` on success + */ +esp_err_t pca9557_port_get_mode(i2c_dev_t *dev, uint8_t *mode); + +/** + * @brief Set directions of I/O pins + * + * 0 - output, 1 - input for each bit in `mode` + * + * @param dev Pointer to device descriptor + * @param mode I/O directions + * @return `ESP_OK` on success + */ +esp_err_t pca9557_port_set_mode(i2c_dev_t *dev, uint8_t mode); + +/** + * @brief Get input polarity settings + * + * 0 - normal input polarity, 1 - inverted for each bit in `pol` + * + * @param dev Pointer to device descriptor + * @param[out] pol Input polarity settings + * @return `ESP_OK` on success + */ +esp_err_t pca9557_port_get_polarity(i2c_dev_t *dev, uint8_t *pol); + +/** + * @brief Set input polarity settings + * + * 0 - normal input polarity, 1 - inverted for each bit in `pol` + * + * @param dev Pointer to device descriptor + * @param pol Input polarity settings + * @return `ESP_OK` on success + */ +esp_err_t pca9557_port_set_polarity(i2c_dev_t *dev, uint8_t pol); + +/** + * @brief Read I/O port value + * + * @param dev Pointer to I2C device descriptor + * @param[out] val 8-bit GPIO port value for PCA9557 or 4-bit port value for PCA9537 + * @return `ESP_OK` on success + */ +esp_err_t pca9557_port_read(i2c_dev_t *dev, uint8_t *val); + +/** + * @brief Write value to I/O port + * + * @param dev Pointer to I2C device descriptor + * @param val 8-bit GPIO port value for PCA9557 or 4-bit port value for PCA9537 + * @return ESP_OK on success + */ +esp_err_t pca9557_port_write(i2c_dev_t *dev, uint8_t val); + +/** + * @brief Read I/O pin mode + * + * @param dev Pointer to device descriptor + * @param pin Pin number, 0..7 for PCA9557, 0..3 for PC9537 + * @param[out] mode Pin mode + * @return `ESP_OK` on success + */ +esp_err_t pca9557_get_mode(i2c_dev_t *dev, uint8_t pin, pca9557_mode_t *mode); + +/** + * @brief Set I/O pin mode + * + * @param dev Pointer to device descriptor + * @param pin Pin number, 0..7 for PCA9557, 0..3 for PC9537 + * @param mode Pin mode + * @return `ESP_OK` on success + */ +esp_err_t pca9557_set_mode(i2c_dev_t *dev, uint8_t pin, pca9557_mode_t mode); + +/** + * @brief Read I/O pin level + * + * @param dev Pointer to device descriptor + * @param pin Pin number, 0..7 for PCA9557, 0..3 for PC9537 + * @param[out] val 1 if pin currently in high state, 0 otherwise + * @return `ESP_OK` on success + */ +esp_err_t pca9557_get_level(i2c_dev_t *dev, uint8_t pin, uint32_t *val); + +/** + * @brief Set I/O pin level + * + * Pin must be set up as output + * + * @param dev Pointer to device descriptor + * @param pin Pin number, 0..7 for PCA9557, 0..3 for PC9537 + * @param val Pin level. 1 - high, 0 - low + * @return `ESP_OK` on success + */ +esp_err_t pca9557_set_level(i2c_dev_t *dev, uint8_t pin, uint32_t val); + +#ifdef __cplusplus +} +#endif + +/**@}*/ + +#endif /* __PCA9557_H__ */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/pca9685/.eil.yml Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,23 @@ +--- +components: + - name: pca9685 + description: Driver for 16-channel, 12-bit PWM PCA9685 + group: misc + groups: [] + code_owners: UncleRus + depends: + - i2cdev + - log + - esp_idf_lib_helpers + thread_safe: yes + targets: + - name: esp32 + - name: esp8266 + - name: esp32s2 + - name: esp32c3 + licenses: + - name: BSD-3 + copyrights: + - author: + name: UncleRus + year: 2016
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/pca9685/CMakeLists.txt Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,5 @@ +idf_component_register( + SRCS pca9685.c + INCLUDE_DIRS . + REQUIRES i2cdev log esp_idf_lib_helpers +)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/pca9685/LICENSE Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,26 @@ +Copyright 2016, 2018 Ruslan V. Uss <unclerus@gmail.com> + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, +this list of conditions and the following disclaimer in the documentation +and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of itscontributors +may be used to endorse or promote products derived from this software without +specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/pca9685/component.mk Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,2 @@ +COMPONENT_ADD_INCLUDEDIRS = . +COMPONENT_DEPENDS = i2cdev log esp_idf_lib_helpers
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/pca9685/pca9685.c Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,372 @@ +/* + * Copyright (c) 2016 Ruslan V. Uss <unclerus@gmail.com> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of itscontributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file pca9685.c + * + * ESP-IDF driver for 16-channel, 12-bit PWM PCA9685 + * + * Ported from esp-open-rtos + * + * Copyright (c) 2016 Ruslan V. Uss <unclerus@gmail.com> + * + * BSD Licensed as described in the file LICENSE + */ + +#include "pca9685.h" +#include <esp_idf_lib_helpers.h> +#include <inttypes.h> +#include <esp_system.h> +#include <esp_log.h> +#include <ets_sys.h> + +#define I2C_FREQ_HZ 1000000 // 1 Mhz + +#define REG_MODE1 0x00 +#define REG_MODE2 0x01 +#define REG_SUBADR1 0x02 +#define REG_SUBADR2 0x03 +#define REG_SUBADR3 0x04 +#define REG_ALLCALLADR 0x05 +#define REG_LEDX 0x06 +#define REG_ALL_LED 0xfa +#define REG_PRE_SCALE 0xfe + +#define MODE1_RESTART (1 << 7) +#define MODE1_EXTCLK (1 << 6) +#define MODE1_AI (1 << 5) +#define MODE1_SLEEP (1 << 4) + +#define MODE1_SUB_BIT 3 + +#define MODE2_INVRT (1 << 4) +#define MODE2_OUTDRV (1 << 2) + +#define LED_FULL_ON_OFF (1 << 4) + +#define REG_LED_N(x) (REG_LEDX + (x) * 4) +#define OFFS_REG_LED_ON 1 +#define OFFS_REG_LED_OFF 3 + +#define INTERNAL_FREQ 25000000 + +#define MIN_PRESCALER 0x03 +#define MAX_PRESCALER 0xff +#define MAX_SUBADDR 2 + +#define WAKEUP_DELAY_US 500 + +#define CHECK_ARG(VAL) do { if (!(VAL)) return ESP_ERR_INVALID_ARG; } while (0) +#define CHECK_ARG_LOGE(VAL, msg, ...) do { if (!(VAL)) { ESP_LOGE(TAG, msg, ## __VA_ARGS__); return ESP_ERR_INVALID_ARG; } } while (0) +#define CHECK(x) do { esp_err_t __; if ((__ = x) != ESP_OK) return __; } while (0) + +static const char *TAG = "pca9685"; + +inline static uint32_t round_div(uint32_t x, uint32_t y) +{ + return (x + y / 2) / y; +} + +inline static esp_err_t write_reg(i2c_dev_t *dev, uint8_t reg, uint8_t val) +{ + return i2c_dev_write_reg(dev, reg, &val, 1); +} + +inline static esp_err_t read_reg(i2c_dev_t *dev, uint8_t reg, uint8_t *val) +{ + return i2c_dev_read_reg(dev, reg, val, 1); +} + +static esp_err_t update_reg(i2c_dev_t *dev, uint8_t reg, uint8_t mask, uint8_t val) +{ + uint8_t v; + + CHECK(read_reg(dev, reg, &v)); + v = (v & ~mask) | val; + CHECK(write_reg(dev, reg, v)); + + return ESP_OK; +} + +static esp_err_t dev_sleep(i2c_dev_t *dev, bool sleep) +{ + CHECK(update_reg(dev, REG_MODE1, MODE1_SLEEP, sleep ? MODE1_SLEEP : 0)); + if (!sleep) + ets_delay_us(WAKEUP_DELAY_US); + + return ESP_OK; +} + +/////////////////////////////////////////////////////////////////////////////// +/// Public + +esp_err_t pca9685_init_desc(i2c_dev_t *dev, uint8_t addr, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio) +{ + CHECK_ARG(dev); + + dev->port = port; + dev->addr = addr; + dev->cfg.sda_io_num = sda_gpio; + dev->cfg.scl_io_num = scl_gpio; +#if HELPER_TARGET_IS_ESP32 + dev->cfg.master.clk_speed = I2C_FREQ_HZ; +#endif + + return i2c_dev_create_mutex(dev); +} + +esp_err_t pca9685_free_desc(i2c_dev_t *dev) +{ + CHECK_ARG(dev); + + return i2c_dev_delete_mutex(dev); +} + +esp_err_t pca9685_init(i2c_dev_t *dev) +{ + CHECK_ARG(dev); + + I2C_DEV_TAKE_MUTEX(dev); + // Enable autoincrement + I2C_DEV_CHECK(dev, update_reg(dev, REG_MODE1, MODE1_AI, MODE1_AI)); + I2C_DEV_GIVE_MUTEX(dev); + + return ESP_OK; +} + +esp_err_t pca9685_set_subaddr(i2c_dev_t *dev, uint8_t num, uint8_t subaddr, bool enable) +{ + CHECK_ARG(dev); + CHECK_ARG_LOGE(num <= MAX_SUBADDR, "Invalid subadress number (%d), must be in (0..2)", num); + + I2C_DEV_TAKE_MUTEX(dev); + I2C_DEV_CHECK(dev, write_reg(dev, REG_SUBADR1 + num, subaddr << 1)); + + uint8_t mask = 1 << (MODE1_SUB_BIT - num); + I2C_DEV_CHECK(dev, update_reg(dev, REG_MODE1, mask, enable ? mask : 0)); + I2C_DEV_GIVE_MUTEX(dev); + + return ESP_OK; +} + +esp_err_t pca9685_restart(i2c_dev_t *dev) +{ + CHECK_ARG(dev); + + I2C_DEV_TAKE_MUTEX(dev); + uint8_t mode; + I2C_DEV_CHECK(dev, read_reg(dev, REG_MODE1, &mode)); + if (mode & MODE1_RESTART) + { + I2C_DEV_CHECK(dev, write_reg(dev, REG_MODE1, mode & ~MODE1_SLEEP)); + ets_delay_us(WAKEUP_DELAY_US); + } + I2C_DEV_CHECK(dev, write_reg(dev, REG_MODE1, (mode & ~MODE1_SLEEP) | MODE1_RESTART)); + I2C_DEV_GIVE_MUTEX(dev); + + return ESP_OK; +} + +esp_err_t pca9685_is_sleeping(i2c_dev_t *dev, bool *sleeping) +{ + CHECK_ARG(dev && sleeping); + + uint8_t v; + I2C_DEV_TAKE_MUTEX(dev); + I2C_DEV_CHECK(dev, read_reg(dev, REG_MODE1, &v)); + I2C_DEV_GIVE_MUTEX(dev); + *sleeping = v & MODE1_SLEEP; + + return ESP_OK; +} + +esp_err_t pca9685_sleep(i2c_dev_t *dev, bool sleep) +{ + CHECK_ARG(dev); + + I2C_DEV_TAKE_MUTEX(dev); + I2C_DEV_CHECK(dev, dev_sleep(dev, sleep)); + I2C_DEV_GIVE_MUTEX(dev); + + return ESP_OK; +} + +esp_err_t pca9685_is_output_inverted(i2c_dev_t *dev, bool *inv) +{ + CHECK_ARG(dev && inv); + + uint8_t v; + I2C_DEV_TAKE_MUTEX(dev); + I2C_DEV_CHECK(dev, read_reg(dev, REG_MODE2, &v)); + I2C_DEV_GIVE_MUTEX(dev); + *inv = v & MODE2_INVRT; + + return ESP_OK; +} + +esp_err_t pca9685_set_output_inverted(i2c_dev_t *dev, bool inverted) +{ + CHECK_ARG(dev); + + I2C_DEV_TAKE_MUTEX(dev); + I2C_DEV_CHECK(dev, update_reg(dev, REG_MODE2, MODE2_INVRT, inverted ? MODE2_INVRT : 0)); + I2C_DEV_GIVE_MUTEX(dev); + + return ESP_OK; +} + +esp_err_t pca9685_get_output_open_drain(i2c_dev_t *dev, bool *od) +{ + CHECK_ARG(dev && od); + + uint8_t v; + I2C_DEV_TAKE_MUTEX(dev); + I2C_DEV_CHECK(dev, read_reg(dev, REG_MODE2, &v)); + I2C_DEV_GIVE_MUTEX(dev); + *od = v & MODE2_OUTDRV; + + return ESP_OK; +} + +esp_err_t pca9685_set_output_open_drain(i2c_dev_t *dev, bool od) +{ + CHECK_ARG(dev); + + I2C_DEV_TAKE_MUTEX(dev); + I2C_DEV_CHECK(dev, update_reg(dev, REG_MODE2, MODE2_OUTDRV, od ? 0 : MODE2_OUTDRV)); + I2C_DEV_GIVE_MUTEX(dev); + + return ESP_OK; +} + +esp_err_t pca9685_get_prescaler(i2c_dev_t *dev, uint8_t *prescaler) +{ + CHECK_ARG(dev && prescaler); + + I2C_DEV_TAKE_MUTEX(dev); + I2C_DEV_CHECK(dev, read_reg(dev, REG_PRE_SCALE, prescaler)); + I2C_DEV_GIVE_MUTEX(dev); + + return ESP_OK; +} + +esp_err_t pca9685_set_prescaler(i2c_dev_t *dev, uint8_t prescaler) +{ + CHECK_ARG(dev); + CHECK_ARG_LOGE(prescaler >= MIN_PRESCALER, + "Invalid prescaler value: (%" PRIu8 "), must be >= 3", prescaler); + + I2C_DEV_TAKE_MUTEX(dev); + I2C_DEV_CHECK(dev, dev_sleep(dev, true)); + I2C_DEV_CHECK(dev, write_reg(dev, REG_PRE_SCALE, prescaler)); + I2C_DEV_CHECK(dev, dev_sleep(dev, false)); + I2C_DEV_GIVE_MUTEX(dev); + + return ESP_OK; +} + +esp_err_t pca9685_get_pwm_frequency(i2c_dev_t *dev, uint16_t *freq) +{ + CHECK_ARG(dev); + CHECK_ARG(freq); + + uint8_t prescale; + I2C_DEV_TAKE_MUTEX(dev); + I2C_DEV_CHECK(dev, read_reg(dev, REG_PRE_SCALE, &prescale)); + I2C_DEV_GIVE_MUTEX(dev); + *freq = INTERNAL_FREQ / ((uint32_t)PCA9685_MAX_PWM_VALUE * (prescale + 1)); + + return ESP_OK; +} + +esp_err_t pca9685_set_pwm_frequency(i2c_dev_t *dev, uint16_t freq) +{ + uint32_t prescaler = round_div(INTERNAL_FREQ, (uint32_t)PCA9685_MAX_PWM_VALUE * freq) - 1; + CHECK_ARG_LOGE(prescaler >= MIN_PRESCALER && prescaler <= MAX_PRESCALER, + "Invalid prescaler value (%" PRIu32 "), must be in (%d..%d)", prescaler, + MIN_PRESCALER, MAX_PRESCALER); + return pca9685_set_prescaler(dev, prescaler); +} + +esp_err_t pca9685_set_pwm_value(i2c_dev_t *dev, uint8_t channel, uint16_t val) +{ + CHECK_ARG(dev); + CHECK_ARG_LOGE(channel <= PCA9685_CHANNEL_ALL, + "Invalid channel %d, must be in (0..%d)", channel, PCA9685_CHANNEL_ALL); + CHECK_ARG_LOGE(val <= PCA9685_MAX_PWM_VALUE, + "Invalid PWM value %d, must be in (0..PCA9685_MAX_PWM_VALUE)", val); + + uint8_t reg = channel == PCA9685_CHANNEL_ALL ? REG_ALL_LED : REG_LED_N(channel); + + bool full_on = val >= PCA9685_MAX_PWM_VALUE; + bool full_off = val == 0; + + uint16_t raw = full_on ? 4095 : val; + + uint8_t buf[4] = { + 0, + full_on ? LED_FULL_ON_OFF : 0, + raw, + full_off ? LED_FULL_ON_OFF | (raw >> 8) : raw >> 8 + }; + + I2C_DEV_TAKE_MUTEX(dev); + I2C_DEV_CHECK(dev, i2c_dev_write_reg(dev, reg, buf, 4)); + I2C_DEV_GIVE_MUTEX(dev); + + return ESP_OK; +} + +esp_err_t pca9685_set_pwm_values(i2c_dev_t *dev, uint8_t first_ch, uint8_t channels, + const uint16_t *values) +{ + CHECK_ARG(values); + CHECK_ARG_LOGE(channels > 0 && first_ch + channels - 1 < PCA9685_CHANNEL_ALL, + "Invalid first_ch or channels: (%d, %d)", first_ch, channels); + + + size_t size = channels * 4; + uint8_t buf[size]; + for (uint8_t ch = first_ch; ch < first_ch + channels; ch++) + { + bool full_on = values[ch] >= PCA9685_MAX_PWM_VALUE; + bool full_off = values[ch] == 0; + + uint16_t val = full_on ? 4095 : values[ch]; + + buf[ch * 4] = 0; + buf[ch * 4 + 1] = full_on ? LED_FULL_ON_OFF : 0; + buf[ch * 4 + 2] = val; + buf[ch * 4 + 3] = full_off ? LED_FULL_ON_OFF | (val >> 8) : val >> 8; + } + + I2C_DEV_TAKE_MUTEX(dev); + I2C_DEV_CHECK(dev, i2c_dev_write_reg(dev, REG_LED_N(first_ch), buf, size)); + I2C_DEV_GIVE_MUTEX(dev); + + return ESP_OK; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/pca9685/pca9685.h Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,251 @@ +/* + * Copyright (c) 2016 Ruslan V. Uss <unclerus@gmail.com> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of itscontributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file pca9685.h + * @defgroup pca9685 pca9685 + * @{ + * + * ESP-IDF driver for 16-channel, 12-bit PWM PCA9685 + * + * Ported from esp-open-rtos + * + * Copyright (c) 2016 Ruslan V. Uss <unclerus@gmail.com> + * + * BSD Licensed as described in the file LICENSE + */ +#ifndef __PCA9685_H__ +#define __PCA9685_H__ + +#include <stdbool.h> +#include <i2cdev.h> +#include <esp_err.h> + +#ifdef __cplusplus +extern "C" { +#endif + +#define PCA9685_ADDR_BASE 0x40 //!< Base I2C device address + +#define PCA9685_MAX_PWM_VALUE 4096 + +/** + * PWM channel + */ +typedef enum +{ + PCA9685_CHANNEL_0 = 0,//!< PCA9685_CHANNEL_0 + PCA9685_CHANNEL_1, //!< PCA9685_CHANNEL_1 + PCA9685_CHANNEL_2, //!< PCA9685_CHANNEL_2 + PCA9685_CHANNEL_3, //!< PCA9685_CHANNEL_3 + PCA9685_CHANNEL_4, //!< PCA9685_CHANNEL_4 + PCA9685_CHANNEL_5, //!< PCA9685_CHANNEL_5 + PCA9685_CHANNEL_6, //!< PCA9685_CHANNEL_6 + PCA9685_CHANNEL_7, //!< PCA9685_CHANNEL_7 + PCA9685_CHANNEL_8, //!< PCA9685_CHANNEL_8 + PCA9685_CHANNEL_9, //!< PCA9685_CHANNEL_9 + PCA9685_CHANNEL_10, //!< PCA9685_CHANNEL_10 + PCA9685_CHANNEL_11, //!< PCA9685_CHANNEL_11 + PCA9685_CHANNEL_12, //!< PCA9685_CHANNEL_12 + PCA9685_CHANNEL_13, //!< PCA9685_CHANNEL_13 + PCA9685_CHANNEL_14, //!< PCA9685_CHANNEL_14 + PCA9685_CHANNEL_15, //!< PCA9685_CHANNEL_15 + PCA9685_CHANNEL_ALL //!< All channels +} pca9685_channel_t; + +/** + * @brief Initialize device descriptor + * + * @param dev Pointer to I2C device descriptor + * @param addr PCA9685 address + * @param port I2C port number + * @param sda_gpio GPIO pin number for SDA + * @param scl_gpio GPIO pin number for SCL + * @return `ESP_OK` on success + */ +esp_err_t pca9685_init_desc(i2c_dev_t *dev, uint8_t addr, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio); + +/** + * @brief Free device descriptor + * + * @param dev Pointer to I2C device descriptor + * @return `ESP_OK` on success + */ +esp_err_t pca9685_free_desc(i2c_dev_t *dev); + +/** + * @brief Init device + * + * @param dev Device descriptor + * @return `ESP_OK` on success + */ +esp_err_t pca9685_init(i2c_dev_t *dev); + +/** + * @brief Setup device subaddress + * + * See section 7.3.6 if the datasheet + * + * @param dev Device descriptor + * @param num Subaddress number, 0..2 + * @param subaddr Subaddress, 7 bit + * @param enable True to enable subaddress, false to disable + * @return `ESP_OK` on success + */ +esp_err_t pca9685_set_subaddr(i2c_dev_t *dev, uint8_t num, uint8_t subaddr, bool enable); + +/** + * @brief Restart device + * + * See section 7.3.1.1 of the datasheet + * + * @param dev Device descriptor + * @return `ESP_OK` on success + */ +esp_err_t pca9685_restart(i2c_dev_t *dev); + +/** + * @brief Check if device is in sleep mode + * + * @param dev Device descriptor + * @param[out] sleeping True if device is sleeping + * @return `ESP_OK` on success + */ +esp_err_t pca9685_is_sleeping(i2c_dev_t *dev, bool *sleeping); + +/** + * @brief Switch device to low-power mode or wake it up + * + * @param dev Device descriptor + * @param sleep True for sleep mode, false for wake up + * @return `ESP_OK` on success + */ +esp_err_t pca9685_sleep(i2c_dev_t *dev, bool sleep); + +/** + * @brief Get logic inversion of the outputs + * + * @param dev Device descriptor + * @param[out] inv True if outputs are inverted, false otherwise + * @return `ESP_OK` on success + */ +esp_err_t pca9685_is_output_inverted(i2c_dev_t *dev, bool *inv); + +/** + * @brief Logically invert outputs + * + * See section 7.7 of the datasheet + * + * @param dev Device descriptor + * @param inverted True for inverted outputs + * @return `ESP_OK` on success + */ +esp_err_t pca9685_set_output_inverted(i2c_dev_t *dev, bool inverted); + +/** + * @brief Get outputs mode + * + * @param dev Device descriptor + * @param[out] od True if outputs are in open drain mode + * @return `ESP_OK` on success + */ +esp_err_t pca9685_get_output_open_drain(i2c_dev_t *dev, bool *od); + +/** + * @brief Set outputs mode + * + * @param dev Device descriptor + * @param od True to set open drain mode, false to normal mode + * @return `ESP_OK` on success + */ +esp_err_t pca9685_set_output_open_drain(i2c_dev_t *dev, bool od); + +/** + * @brief Get PWM frequency prescaler + * + * @param dev Device descriptor + * @param[out] prescaler Frequency prescaler + * @return `ESP_OK` on success + */ +esp_err_t pca9685_get_prescaler(i2c_dev_t *dev, uint8_t *prescaler); + +/** + * @brief Set PWM frequency prescaler + * + * @param dev Device descriptor + * @param prescaler Prescaler value + * @return `ESP_OK` on success + */ +esp_err_t pca9685_set_prescaler(i2c_dev_t *dev, uint8_t prescaler); + +/** + * @brief Get PWM frequency + * + * @param dev Device descriptor + * @param[out] freq PWM frequency, Hz + * @return `ESP_OK` on success + */ +esp_err_t pca9685_get_pwm_frequency(i2c_dev_t *dev, uint16_t *freq); + +/** + * @brief Set PWM frequency + * + * @param dev Device descriptor + * @param freq PWM frequency, Hz + * @return `ESP_OK` on success + */ +esp_err_t pca9685_set_pwm_frequency(i2c_dev_t *dev, uint16_t freq); + +/** + * @brief Set PWM value on output channel + * + * @param dev Device descriptor + * @param channel Channel number, 0..15 or >15 for all channels + * @param val PWM value, 0..4096 + * @return `ESP_OK` on success + */ +esp_err_t pca9685_set_pwm_value(i2c_dev_t *dev, uint8_t channel, uint16_t val); + +/** + * @brief Set PWM values on multiple output channels + * + * @param dev Device descriptor + * @param first_ch First channel, 0..15 + * @param channels Number of channels to update + * @param values Array of the channel values, each 0..4096 + * @return `ESP_OK` on success + */ +esp_err_t pca9685_set_pwm_values(i2c_dev_t *dev, uint8_t first_ch, uint8_t channels, + const uint16_t *values); + +#ifdef __cplusplus +} +#endif + +/**@}*/ + +#endif /* __PCA9685_H__ */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/pcf8563/.eil.yml Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,23 @@ +--- +components: + - name: pcf8563 + description: Driver for PCF8563 real-time clock/calendar + group: rtc + groups: [] + code_owners: UncleRus + depends: + - i2cdev + - log + - esp_idf_lib_helpers + thread_safe: yes + targets: + - name: esp32 + - name: esp8266 + - name: esp32s2 + - name: esp32c3 + licenses: + - name: BSD-3 + copyrights: + - author: + name: UncleRus + year: 2020
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/pcf8563/CMakeLists.txt Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,5 @@ +idf_component_register( + SRCS pcf8563.c + INCLUDE_DIRS . + REQUIRES i2cdev log esp_idf_lib_helpers +)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/pcf8563/LICENSE Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,26 @@ +Copyright (c) 2020 Ruslan V. Uss <unclerus@gmail.com> + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, +this list of conditions and the following disclaimer in the documentation +and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of itscontributors +may be used to endorse or promote products derived from this software without +specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/pcf8563/component.mk Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,2 @@ +COMPONENT_ADD_INCLUDEDIRS = . +COMPONENT_DEPENDS = i2cdev log esp_idf_lib_helpers
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/pcf8563/pcf8563.c Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,370 @@ +/* + * Copyright (c) 2020 Ruslan V. Uss <unclerus@gmail.com> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of itscontributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file pcf8563.c + * + * ESP-IDF driver for PCF8563 real-time clock/calendar + * + * Copyright (c) 2020 Ruslan V. Uss <unclerus@gmail.com> + * + * BSD Licensed as described in the file LICENSE + */ +#include "pcf8563.h" + +#define I2C_FREQ_HZ 400000 + +#define REG_CTRL_STATUS1 0x00 +#define REG_CTRL_STATUS2 0x01 +#define REG_VL_SECONDS 0x02 +#define REG_MINUTES 0x03 +#define REG_HOURS 0x04 +#define REG_DAYS 0x05 +#define REG_WEEKDAYS 0x06 +#define REG_CENT_MONTHS 0x07 +#define REG_YEARS 0x08 +#define REG_ALARM_MIN 0x09 +#define REG_ALARM_HOUR 0x0a +#define REG_ALARM_DAY 0x0b +#define REG_ALARM_WDAY 0x0c +#define REG_CLKOUT 0x0d +#define REG_TIMER_CTRL 0x0e +#define REG_TIMER 0x0f + +#define BIT_YEAR_CENTURY 7 +#define BIT_VL 7 +#define BIT_AE 7 +#define BIT_CLKOUT_FD 0 +#define BIT_CLKOUT_FE 7 + +#define BIT_CTRL_STATUS2_TIE 0 +#define BIT_CTRL_STATUS2_AIE 1 +#define BIT_CTRL_STATUS2_TF 3 +#define BIT_CTRL_STATUS2_AF 4 + +#define BIT_TIMER_CTRL_TE 7 + +#define MASK_TIMER_CTRL_TD 0x03 +#define MASK_ALARM 0x7f + +#define MASK_MIN 0x7f +#define MASK_HOUR 0x3f +#define MASK_MDAY 0x3f +#define MASK_WDAY 0x07 +#define MASK_MON 0x1f + +#define BV(x) ((uint8_t)(1 << (x))) + +#define CHECK(x) do { esp_err_t __; if ((__ = x) != ESP_OK) return __; } while (0) +#define CHECK_ARG(ARG) do { if (!(ARG)) return ESP_ERR_INVALID_ARG; } while (0) + +static uint8_t bcd2dec(uint8_t val) +{ + return (val >> 4) * 10 + (val & 0x0f); +} + +static uint8_t dec2bcd(uint8_t val) +{ + return ((val / 10) << 4) + (val % 10); +} + +static inline esp_err_t read_reg_nolock(i2c_dev_t *dev, uint8_t reg, uint8_t *val) +{ + return i2c_dev_read_reg(dev, reg, val, 1); +} + +static inline esp_err_t write_reg_nolock(i2c_dev_t *dev, uint8_t reg, uint8_t val) +{ + return i2c_dev_write_reg(dev, reg, &val, 1); +} + +static esp_err_t update_reg_nolock(i2c_dev_t *dev, uint8_t reg, uint8_t mask, uint8_t val) +{ + uint8_t v; + CHECK(read_reg_nolock(dev, reg, &v)); + CHECK(write_reg_nolock(dev, reg, (v & ~mask) | val)); + return ESP_OK; +} + +static esp_err_t read_reg(i2c_dev_t *dev, uint8_t reg, uint8_t *val) +{ + I2C_DEV_TAKE_MUTEX(dev); + I2C_DEV_CHECK(dev, read_reg_nolock(dev, reg, val)); + I2C_DEV_GIVE_MUTEX(dev); + + return ESP_OK; +} + +static esp_err_t write_reg(i2c_dev_t *dev, uint8_t reg, uint8_t val) +{ + I2C_DEV_TAKE_MUTEX(dev); + I2C_DEV_CHECK(dev, write_reg_nolock(dev, reg, val)); + I2C_DEV_GIVE_MUTEX(dev); + + return ESP_OK; +} + +static esp_err_t update_reg(i2c_dev_t *dev, uint8_t reg, uint8_t mask, uint8_t val) +{ + I2C_DEV_TAKE_MUTEX(dev); + I2C_DEV_CHECK(dev, update_reg_nolock(dev, reg, mask, val)); + I2C_DEV_GIVE_MUTEX(dev); + + return ESP_OK; +} + +/////////////////////////////////////////////////////////////////////////////// + +esp_err_t pcf8563_init_desc(i2c_dev_t *dev, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio) +{ + CHECK_ARG(dev); + + dev->port = port; + dev->addr = PCF8563_I2C_ADDR; + dev->cfg.sda_io_num = sda_gpio; + dev->cfg.scl_io_num = scl_gpio; +#if HELPER_TARGET_IS_ESP32 + dev->cfg.master.clk_speed = I2C_FREQ_HZ; +#endif + return i2c_dev_create_mutex(dev); +} + +esp_err_t pcf8563_free_desc(i2c_dev_t *dev) +{ + CHECK_ARG(dev); + + return i2c_dev_delete_mutex(dev); +} + +esp_err_t pcf8563_set_time(i2c_dev_t *dev, struct tm *time) +{ + CHECK_ARG(dev && time); + + bool ovf = time->tm_year >= 200; + + uint8_t data[7] = { + dec2bcd(time->tm_sec), + dec2bcd(time->tm_min), + dec2bcd(time->tm_hour), + dec2bcd(time->tm_mday), + dec2bcd(time->tm_wday), + dec2bcd(time->tm_mon + 1) | (ovf ? BV(BIT_YEAR_CENTURY) : 0), + dec2bcd(time->tm_year - (ovf ? 200 : 100)) + }; + + I2C_DEV_TAKE_MUTEX(dev); + I2C_DEV_CHECK(dev, i2c_dev_write_reg(dev, REG_VL_SECONDS, data, 7)); + I2C_DEV_GIVE_MUTEX(dev); + + return ESP_OK; +} + +esp_err_t pcf8563_get_time(i2c_dev_t *dev, struct tm *time, bool *valid) +{ + CHECK_ARG(dev && time && valid); + + uint8_t data[7]; + + I2C_DEV_TAKE_MUTEX(dev); + I2C_DEV_CHECK(dev, i2c_dev_read_reg(dev, REG_VL_SECONDS, data, 7)); + I2C_DEV_GIVE_MUTEX(dev); + + *valid = data[0] & BV(BIT_VL) ? false : true; + time->tm_sec = bcd2dec(data[0] & ~BV(BIT_VL)); + time->tm_min = bcd2dec(data[1] & MASK_MIN); + time->tm_hour = bcd2dec(data[2] & MASK_HOUR); + time->tm_mday = bcd2dec(data[3] & MASK_MDAY); + time->tm_wday = bcd2dec(data[4] & MASK_WDAY); + time->tm_mon = bcd2dec(data[5] & MASK_MON) - 1; + time->tm_year = bcd2dec(data[6]) + (data[5] & BV(BIT_YEAR_CENTURY) ? 200 : 100); + + return ESP_OK; +} + +esp_err_t pcf8563_set_clkout(i2c_dev_t *dev, pcf8563_clkout_freq_t freq) +{ + CHECK_ARG(dev); + + return write_reg(dev, REG_CLKOUT, + freq == PCF8563_DISABLED + ? 0 + : (BV(BIT_CLKOUT_FE) | ((freq - 1) & 3)) + ); +} + +esp_err_t pcf8563_get_clkout(i2c_dev_t *dev, pcf8563_clkout_freq_t *freq) +{ + CHECK_ARG(dev && freq); + + uint8_t v; + CHECK(read_reg(dev, REG_CLKOUT, &v)); + *freq = v & BV(BIT_CLKOUT_FE) ? (v & 3) + 1 : PCF8563_DISABLED; + + return ESP_OK; +} + +esp_err_t pcf8563_set_timer_settings(i2c_dev_t *dev, bool int_enable, pcf8563_timer_clock_t clock) +{ + CHECK_ARG(dev); + + I2C_DEV_TAKE_MUTEX(dev); + I2C_DEV_CHECK(dev, update_reg_nolock(dev, REG_CTRL_STATUS2, + BV(BIT_CTRL_STATUS2_TIE), int_enable ? BV(BIT_CTRL_STATUS2_TIE) : 0)); + I2C_DEV_CHECK(dev, update_reg_nolock(dev, REG_TIMER_CTRL, MASK_TIMER_CTRL_TD, clock)); + I2C_DEV_GIVE_MUTEX(dev); + + return ESP_OK; +} + +esp_err_t pcf8563_get_timer_settings(i2c_dev_t *dev, bool *int_enabled, pcf8563_timer_clock_t *clock) +{ + CHECK_ARG(dev && int_enabled && clock); + + uint8_t s, t; + I2C_DEV_TAKE_MUTEX(dev); + I2C_DEV_CHECK(dev, read_reg_nolock(dev, REG_CTRL_STATUS2, &s)); + I2C_DEV_CHECK(dev, read_reg_nolock(dev, REG_TIMER_CTRL, &t)); + I2C_DEV_GIVE_MUTEX(dev); + + *int_enabled = s & BV(BIT_CTRL_STATUS2_TIE) ? true : false; + *clock = t & MASK_TIMER_CTRL_TD; + + return ESP_OK; +} + +esp_err_t pcf8563_set_timer_value(i2c_dev_t *dev, uint8_t value) +{ + CHECK_ARG(dev); + + return write_reg(dev, REG_TIMER, value); +} + +esp_err_t pcf8563_get_timer_value(i2c_dev_t *dev, uint8_t *value) +{ + CHECK_ARG(dev && value); + + return read_reg(dev, REG_TIMER, value); +} + +esp_err_t pcf8563_start_timer(i2c_dev_t *dev) +{ + CHECK_ARG(dev); + + return update_reg(dev, REG_TIMER_CTRL, BV(BIT_TIMER_CTRL_TE), BV(BIT_TIMER_CTRL_TE)); +} + +esp_err_t pcf8563_stop_timer(i2c_dev_t *dev) +{ + CHECK_ARG(dev); + + return update_reg(dev, REG_TIMER_CTRL, BV(BIT_TIMER_CTRL_TE), 0); +} + +esp_err_t pcf8563_get_timer_flag(i2c_dev_t *dev, bool *timer) +{ + CHECK_ARG(dev && timer); + + uint8_t v; + CHECK(read_reg(dev, REG_CTRL_STATUS2, &v)); + *timer = v & BIT_CTRL_STATUS2_TF ? true : false; + + return ESP_OK; +} + +esp_err_t pcf8563_clear_timer_flag(i2c_dev_t *dev) +{ + CHECK_ARG(dev); + + return update_reg(dev, REG_CTRL_STATUS2, BV(BIT_CTRL_STATUS2_TF), 0); +} + +esp_err_t pcf8563_set_alarm(i2c_dev_t *dev, bool int_enable, uint32_t flags, struct tm *time) +{ + CHECK_ARG(dev && time); + + I2C_DEV_TAKE_MUTEX(dev); + I2C_DEV_CHECK(dev, update_reg_nolock(dev, REG_CTRL_STATUS2, + BV(BIT_CTRL_STATUS2_AIE), int_enable ? BV(BIT_CTRL_STATUS2_AIE) : 0)); + uint8_t data[4] = { + dec2bcd(time->tm_min) | (flags & PCF8563_ALARM_MATCH_MIN ? 0 : BV(BIT_AE)), + dec2bcd(time->tm_hour) | (flags & PCF8563_ALARM_MATCH_HOUR ? 0 : BV(BIT_AE)), + dec2bcd(time->tm_mday) | (flags & PCF8563_ALARM_MATCH_DAY ? 0 : BV(BIT_AE)), + dec2bcd(time->tm_wday) | (flags & PCF8563_ALARM_MATCH_WEEKDAY ? 0 : BV(BIT_AE)), + }; + I2C_DEV_CHECK(dev, i2c_dev_write_reg(dev, REG_ALARM_MIN, data, 4)); + I2C_DEV_GIVE_MUTEX(dev); + + return ESP_OK; +} + +esp_err_t pcf8563_get_alarm(i2c_dev_t *dev, bool *int_enabled, uint32_t *flags, struct tm *time) +{ + CHECK_ARG(dev && int_enabled && flags && time); + + uint8_t data[4], s; + + I2C_DEV_TAKE_MUTEX(dev); + I2C_DEV_CHECK(dev, read_reg_nolock(dev, REG_CTRL_STATUS2, &s)); + I2C_DEV_CHECK(dev, i2c_dev_read_reg(dev, REG_ALARM_MIN, data, 4)); + I2C_DEV_GIVE_MUTEX(dev); + + *int_enabled = s & BV(BIT_CTRL_STATUS2_AIE) ? true : false; + *flags = 0; + if (!(data[0] & BV(BIT_AE))) + *flags |= PCF8563_ALARM_MATCH_MIN; + if (!(data[1] & BV(BIT_AE))) + *flags |= PCF8563_ALARM_MATCH_HOUR; + if (!(data[2] & BV(BIT_AE))) + *flags |= PCF8563_ALARM_MATCH_DAY; + if (!(data[3] & BV(BIT_AE))) + *flags |= PCF8563_ALARM_MATCH_WEEKDAY; + + time->tm_min = bcd2dec(data[0] & MASK_ALARM); + time->tm_hour = bcd2dec(data[1] & MASK_ALARM); + time->tm_mday = bcd2dec(data[2] & MASK_ALARM); + time->tm_wday = bcd2dec(data[3] & MASK_ALARM); + + return ESP_OK; +} + +esp_err_t pcf8563_get_alarm_flag(i2c_dev_t *dev, bool *alarm) +{ + CHECK_ARG(dev && alarm); + + uint8_t v; + CHECK(read_reg(dev, REG_CTRL_STATUS2, &v)); + *alarm = v & BIT_CTRL_STATUS2_AF ? true : false; + + return ESP_OK; +} + +esp_err_t pcf8563_clear_alarm_flag(i2c_dev_t *dev) +{ + CHECK_ARG(dev); + + return update_reg(dev, REG_CTRL_STATUS2, BV(BIT_CTRL_STATUS2_AF), 0); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/pcf8563/pcf8563.h Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,256 @@ +/* + * Copyright (c) 2020 Ruslan V. Uss <unclerus@gmail.com> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of itscontributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file pcf8563.h + * @defgroup pcf8563 pcf8563 + * @{ + * + * ESP-IDF driver for PCF8563 real-time clock/calendar + * + * Copyright (c) 2020 Ruslan V. Uss <unclerus@gmail.com> + * + * BSD Licensed as described in the file LICENSE + */ +#ifndef __PCF8563_H__ +#define __PCF8563_H__ + +#include <i2cdev.h> +#include <stdbool.h> +#include <time.h> +#include <esp_err.h> + +#ifdef __cplusplus +extern "C" { +#endif + +#define PCF8563_I2C_ADDR 0x51 + +/** + * Frequency output at pin CLKOUT + */ +typedef enum { + PCF8563_DISABLED = 0, //!< CLKOUT output is inhibited and set high-impedance + PCF8563_32768HZ, //!< 32768 Hz + PCF8563_1024HZ, //!< 1024 Hz + PCF8563_32HZ, //!< 32 Hz + PCF8563_1HZ, //!< 1 Hz +} pcf8563_clkout_freq_t; + +/** + * Timer clock + */ +typedef enum { + PCF8563_TIMER_4096HZ = 0, //!< 4096 Hz + PCF8563_TIMER_64HZ, //!< 64 Hz + PCF8563_TIMER_1HZ, //!< 1 Hz + PCF8563_TIMER_1_60HZ //!< 1/60 Hz +} pcf8563_timer_clock_t; + +/** + * Flags to setup alarm + */ +typedef enum { + PCF8563_ALARM_MATCH_MIN = 0x01, //!< Alarm when minute matched + PCF8563_ALARM_MATCH_HOUR = 0x02, //!< Alarm when hour matched + PCF8563_ALARM_MATCH_DAY = 0x04, //!< Alarm when day matched + PCF8563_ALARM_MATCH_WEEKDAY = 0x08 //!< Alarm when weekday matched +} pcf8563_alarm_flags_t; + +/** + * @brief Initialize device descriptor + * + * @param dev I2C device descriptor + * @param port I2C port + * @param sda_gpio SDA GPIO + * @param scl_gpio SCL GPIO + * @return `ESP_OK` on success + */ +esp_err_t pcf8563_init_desc(i2c_dev_t *dev, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio); + +/** + * @brief Free device descriptor + * + * @param dev I2C device descriptor + * @return `ESP_OK` on success + */ +esp_err_t pcf8563_free_desc(i2c_dev_t *dev); + +/** + * @brief Set the time on the RTC + * + * @param dev I2C device descriptor + * @param time Pointer to time struct + * @return `ESP_OK` on success + */ +esp_err_t pcf8563_set_time(i2c_dev_t *dev, struct tm *time); + +/** + * @brief Get the time from the RTC + * + * @param dev I2C device descriptor + * @param[out] time Pointer to time struct + * @param[out] valid Time validity, false when RTC had power failures + * @return `ESP_OK` on success + */ +esp_err_t pcf8563_get_time(i2c_dev_t *dev, struct tm *time, bool *valid); + +/** + * @brief Set output frequency on CLKOUT pin + * + * @param dev I2C device descriptor + * @param freq Frequency + * @return `ESP_OK` on success + */ +esp_err_t pcf8563_set_clkout(i2c_dev_t *dev, pcf8563_clkout_freq_t freq); + +/** + * @brief Get current frequency on CLKOUT pin + * + * @param dev I2C device descriptor + * @param[out] freq Frequency + * @return `ESP_OK` on success + */ +esp_err_t pcf8563_get_clkout(i2c_dev_t *dev, pcf8563_clkout_freq_t *freq); + +/** + * @brief Setup timer + * + * @param dev I2C device descriptor + * @param int_enable true for enable interrupt on timer + * @param clock Timer frequency + * @return `ESP_OK` on success + */ +esp_err_t pcf8563_set_timer_settings(i2c_dev_t *dev, bool int_enable, pcf8563_timer_clock_t clock); + +/** + * @brief Get timer settings + * + * @param dev I2C device descriptor + * @param[out] int_enabled true if timer interrupt is enabled + * @param[out] clock Timer frequency + * @return `ESP_OK` on success + */ +esp_err_t pcf8563_get_timer_settings(i2c_dev_t *dev, bool *int_enabled, pcf8563_timer_clock_t *clock); + +/** + * @brief Set timer register value + * + * @param dev I2C device descriptor + * @param value Value to set int timer register + * @return `ESP_OK` on success + */ +esp_err_t pcf8563_set_timer_value(i2c_dev_t *dev, uint8_t value); + +/** + * @brief Get timer register value + * + * @param dev I2C device descriptor + * @param[out] value Timer value + * @return `ESP_OK` on success + */ +esp_err_t pcf8563_get_timer_value(i2c_dev_t *dev, uint8_t *value); + +/** + * @brief Start timer + * + * @param dev I2C device descriptor + * @return `ESP_OK` on success + */ +esp_err_t pcf8563_start_timer(i2c_dev_t *dev); + +/** + * @brief Stop timer + * + * @param dev I2C device descriptor + * @return `ESP_OK` on success + */ +esp_err_t pcf8563_stop_timer(i2c_dev_t *dev); + +/** + * @brief Get state of the timer flag + * + * @param dev I2C device descriptor + * @param[out] timer true when flag is set + * @return `ESP_OK` on success + */ +esp_err_t pcf8563_get_timer_flag(i2c_dev_t *dev, bool *timer); + +/** + * @brief Clear timer flag + * + * @param dev I2C device descriptor + * @return `ESP_OK` on success + */ +esp_err_t pcf8563_clear_timer_flag(i2c_dev_t *dev); + +/** + * @brief Setup alarm + * + * @param dev I2C device descriptor + * @param int_enable true to enable alarm interrupt + * @param flags Alarm types, combination of pcf8563_alarm_flags_t values + * @param time Alarm time. Only tm_min, tm_hour, tm_mday and tm_wday are used + * @return `ESP_OK` on success + */ +esp_err_t pcf8563_set_alarm(i2c_dev_t *dev, bool int_enable, uint32_t flags, struct tm *time); + +/** + * @brief Get alarm settings + * + * @param dev I2C device descriptor + * @param[out] int_enabled true if alarm interrupt is enabled + * @param[out] flags Selected alarm types, combination of pcf8563_alarm_flags_t values + * @param[out] time Alarm time. Only tm_min, tm_hour, tm_mday and tm_wday are used + * @return `ESP_OK` on success + */ +esp_err_t pcf8563_get_alarm(i2c_dev_t *dev, bool *int_enabled, uint32_t *flags, struct tm *time); + +/** + * @brief Get alarm flag + * + * @param dev I2C device descriptor + * @param[out] alarm true if alarm occurred + * @return `ESP_OK` on success + */ +esp_err_t pcf8563_get_alarm_flag(i2c_dev_t *dev, bool *alarm); + +/** + * @brief Clear alarm flag + * + * @param dev I2C device descriptor + * @return `ESP_OK` on success + */ +esp_err_t pcf8563_clear_alarm_flag(i2c_dev_t *dev); + +#ifdef __cplusplus +} +#endif + +/**@}*/ + +#endif /* __PCF8563_H__ */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/pcf8574/.eil.yml Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,23 @@ +--- +components: + - name: pcf8574 + description: Driver for PCF8574 remote 8-bit I/O expander for I2C-bus + group: gpio + groups: [] + code_owners: UncleRus + depends: + - i2cdev + - log + - esp_idf_lib_helpers + thread_safe: yes + targets: + - name: esp32 + - name: esp8266 + - name: esp32s2 + - name: esp32c3 + licenses: + - name: MIT + copyrights: + - author: + name: UncleRus + year: 2018
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/pcf8574/CMakeLists.txt Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,5 @@ +idf_component_register( + SRCS pcf8574.c + INCLUDE_DIRS . + REQUIRES i2cdev log esp_idf_lib_helpers +)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/pcf8574/LICENSE Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2018 Ruslan V. Uss (https://github.com/UncleRus) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/pcf8574/component.mk Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,2 @@ +COMPONENT_ADD_INCLUDEDIRS = . +COMPONENT_DEPENDS = i2cdev log esp_idf_lib_helpers \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/pcf8574/pcf8574.c Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,99 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2018 Ruslan V. Uss <unclerus@gmail.com> + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +/** + * @file pcf8574.c + * + * ESP-IDF driver for PCF8574 compatible remote 8-bit I/O expanders for I2C-bus + * + * Copyright (c) 2018 Ruslan V. Uss <unclerus@gmail.com> + * + * MIT Licensed as described in the file LICENSE + */ + +#include <esp_err.h> +#include <esp_idf_lib_helpers.h> +#include "pcf8574.h" + +#define I2C_FREQ_HZ 100000 + +#define CHECK(x) do { esp_err_t __; if ((__ = x) != ESP_OK) return __; } while (0) +#define CHECK_ARG(VAL) do { if (!(VAL)) return ESP_ERR_INVALID_ARG; } while (0) +#define BV(x) (1 << (x)) + +static esp_err_t read_port(i2c_dev_t *dev, uint8_t *val) +{ + CHECK_ARG(dev && val); + + I2C_DEV_TAKE_MUTEX(dev); + I2C_DEV_CHECK(dev, i2c_dev_read(dev, NULL, 0, val, 1)); + I2C_DEV_GIVE_MUTEX(dev); + + return ESP_OK; +} + +static esp_err_t write_port(i2c_dev_t *dev, uint8_t val) +{ + CHECK_ARG(dev); + + I2C_DEV_TAKE_MUTEX(dev); + I2C_DEV_CHECK(dev, i2c_dev_write(dev, NULL, 0, &val, 1)); + I2C_DEV_GIVE_MUTEX(dev); + + return ESP_OK; +} + +/////////////////////////////////////////////////////////////////////////////// + +esp_err_t pcf8574_init_desc(i2c_dev_t *dev, uint8_t addr, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio) +{ + CHECK_ARG(dev); + CHECK_ARG(addr & 0x20); + + dev->port = port; + dev->addr = addr; + dev->cfg.sda_io_num = sda_gpio; + dev->cfg.scl_io_num = scl_gpio; +#if HELPER_TARGET_IS_ESP32 + dev->cfg.master.clk_speed = I2C_FREQ_HZ; +#endif + + return i2c_dev_create_mutex(dev); +} + +esp_err_t pcf8574_free_desc(i2c_dev_t *dev) +{ + CHECK_ARG(dev); + + return i2c_dev_delete_mutex(dev); +} + +esp_err_t pcf8574_port_read(i2c_dev_t *dev, uint8_t *val) +{ + return read_port(dev, val); +} + +esp_err_t pcf8574_port_write(i2c_dev_t *dev, uint8_t val) +{ + return write_port(dev, val); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/pcf8574/pcf8574.h Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,92 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2018 Ruslan V. Uss <unclerus@gmail.com> + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +/** + * @file pcf8574.h + * @defgroup pcf8574 pcf8574 + * @{ + * + * ESP-IDF driver for PCF8574 compatible remote 8-bit I/O expanders for I2C-bus + * + * Copyright (c) 2018 Ruslan V. Uss <unclerus@gmail.com> + * + * MIT Licensed as described in the file LICENSE + */ +#ifndef __PCF8574_H__ +#define __PCF8574_H__ + +#include <stddef.h> +#include <i2cdev.h> +#include <esp_err.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Initialize device descriptor + * + * Default SCL frequency is 100kHz + * + * @param dev Pointer to I2C device descriptor + * @param port I2C port number + * @param addr I2C address (0b0100[A2][A1][A0] for PCF8574, 0b0111[A2][A1][A0] for PCF8574A) + * @param sda_gpio SDA GPIO + * @param scl_gpio SCL GPIO + * @return `ESP_OK` on success + */ +esp_err_t pcf8574_init_desc(i2c_dev_t *dev, uint8_t addr, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio); + +/** + * @brief Free device descriptor + * + * @param dev Pointer to I2C device descriptor + * @return `ESP_OK` on success + */ +esp_err_t pcf8574_free_desc(i2c_dev_t *dev); + +/** + * @brief Read GPIO port value + * + * @param dev Pointer to I2C device descriptor + * @param val 8-bit GPIO port value + * @return `ESP_OK` on success + */ +esp_err_t pcf8574_port_read(i2c_dev_t *dev, uint8_t *val); + +/** + * @brief Write value to GPIO port + * + * @param dev Pointer to I2C device descriptor + * @param value GPIO port value + * @return ESP_OK on success + */ +esp_err_t pcf8574_port_write(i2c_dev_t *dev, uint8_t value); + +#ifdef __cplusplus +} +#endif + +/**@}*/ + +#endif /* __PCF8574_H__ */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/pcf8575/.eil.yml Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,23 @@ +--- +components: + - name: pcf8575 + description: Driver for PCF8575 remote 16-bit I/O expander for I2C-bus + group: gpio + groups: [] + code_owners: UncleRus + depends: + - i2cdev + - log + - esp_idf_lib_helpers + thread_safe: yes + targets: + - name: esp32 + - name: esp8266 + - name: esp32s2 + - name: esp32c3 + licenses: + - name: MIT + copyrights: + - author: + name: UncleRus + year: 2019
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/pcf8575/CMakeLists.txt Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,5 @@ +idf_component_register( + SRCS pcf8575.c + INCLUDE_DIRS . + REQUIRES i2cdev log esp_idf_lib_helpers +)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/pcf8575/LICENSE Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2018 Ruslan V. Uss (https://github.com/UncleRus) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/pcf8575/component.mk Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,2 @@ +COMPONENT_ADD_INCLUDEDIRS = . +COMPONENT_DEPENDS = i2cdev log esp_idf_lib_helpers
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/pcf8575/pcf8575.c Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,98 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2019 Ruslan V. Uss <unclerus@gmail.com> + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +/** + * @file pcf8575.c + * + * ESP-IDF driver for PCF8575 remote 16-bit I/O expander for I2C-bus + * + * Copyright (c) 2019 Ruslan V. Uss <unclerus@gmail.com> + * + * MIT Licensed as described in the file LICENSE + */ +#include <esp_err.h> +#include <esp_idf_lib_helpers.h> +#include "pcf8575.h" + +#define I2C_FREQ_HZ 400000 + +#define CHECK(x) do { esp_err_t __; if ((__ = x) != ESP_OK) return __; } while (0) +#define CHECK_ARG(VAL) do { if (!(VAL)) return ESP_ERR_INVALID_ARG; } while (0) +#define BV(x) (1 << (x)) + +static esp_err_t read_port(i2c_dev_t *dev, uint16_t *val) +{ + CHECK_ARG(dev && val); + + I2C_DEV_TAKE_MUTEX(dev); + I2C_DEV_CHECK(dev, i2c_dev_read(dev, NULL, 0, val, 2)); + I2C_DEV_GIVE_MUTEX(dev); + + return ESP_OK; +} + +static esp_err_t write_port(i2c_dev_t *dev, uint16_t val) +{ + CHECK_ARG(dev); + + I2C_DEV_TAKE_MUTEX(dev); + I2C_DEV_CHECK(dev, i2c_dev_write(dev, NULL, 0, &val, 2)); + I2C_DEV_GIVE_MUTEX(dev); + + return ESP_OK; +} + +/////////////////////////////////////////////////////////////////////////////// + +esp_err_t pcf8575_init_desc(i2c_dev_t *dev, uint8_t addr, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio) +{ + CHECK_ARG(dev); + CHECK_ARG(addr & 0x20); + + dev->port = port; + dev->addr = addr; + dev->cfg.sda_io_num = sda_gpio; + dev->cfg.scl_io_num = scl_gpio; +#if HELPER_TARGET_IS_ESP32 + dev->cfg.master.clk_speed = I2C_FREQ_HZ; +#endif + + return i2c_dev_create_mutex(dev); +} + +esp_err_t pcf8575_free_desc(i2c_dev_t *dev) +{ + CHECK_ARG(dev); + + return i2c_dev_delete_mutex(dev); +} + +esp_err_t pcf8575_port_read(i2c_dev_t *dev, uint16_t *val) +{ + return read_port(dev, val); +} + +esp_err_t pcf8575_port_write(i2c_dev_t *dev, uint16_t val) +{ + return write_port(dev, val); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/pcf8575/pcf8575.h Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,91 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2019 Ruslan V. Uss <unclerus@gmail.com> + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +/** + * @file pcf8575.h + * @defgroup pcf8575 pcf8575 + * @{ + * + * ESP-IDF driver for PCF8575 remote 16-bit I/O expander for I2C-bus + * + * Copyright (c) 2019 Ruslan V. Uss <unclerus@gmail.com> + * + * MIT Licensed as described in the file LICENSE + */ +#ifndef __PCF8575_H__ +#define __PCF8575_H__ + +#include <stddef.h> +#include <i2cdev.h> +#include <esp_err.h> + +#ifdef __cplusplus +extern "C" { +#endif + +#define PCF8575_I2C_ADDR_BASE 0x20 + +/** + * @brief Initialize device descriptor + * + * Default SCL frequency is 400kHz + * + * @param dev Pointer to I2C device descriptor + * @param port I2C port number + * @param addr I2C address (`0b0100<A2><A1><A0>` for PCF8575) + * @param sda_gpio SDA GPIO + * @param scl_gpio SCL GPIO + * @return `ESP_OK` on success + */ +esp_err_t pcf8575_init_desc(i2c_dev_t *dev, uint8_t addr, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio); + +/** + * @brief Free device descriptor + * @param dev Pointer to I2C device descriptor + * @return `ESP_OK` on success + */ +esp_err_t pcf8575_free_desc(i2c_dev_t *dev); + +/** + * @brief Read GPIO port value + * @param dev Pointer to I2C device descriptor + * @param val 8-bit GPIO port value + * @return `ESP_OK` on success + */ +esp_err_t pcf8575_port_read(i2c_dev_t *dev, uint16_t *val); + +/** + * @brief Write value to GPIO port + * @param dev Pointer to I2C device descriptor + * @param value GPIO port value + * @return ESP_OK on success + */ +esp_err_t pcf8575_port_write(i2c_dev_t *dev, uint16_t value); + +#ifdef __cplusplus +} +#endif + +/**@}*/ + +#endif /* __PCF8575_H__ */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/pcf8591/.eil.yml Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,26 @@ +--- +components: + - name: pcf8591 + description: Driver for 8-bit ADC and an 8-bit DAC PCF8591 + group: adc-dac + groups: [] + code_owners: UncleRus + depends: + - i2cdev + - log + - esp_idf_lib_helpers + thread_safe: yes + targets: + - name: esp32 + - name: esp8266 + - name: esp32s2 + - name: esp32c3 + licenses: + - name: BSD-3 + copyrights: + - author: + name: UncleRus + year: 2017 + - author: + name: PhamNgocT + year: 2017
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/pcf8591/CMakeLists.txt Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,5 @@ +idf_component_register( + SRCS pcf8591.c + INCLUDE_DIRS . + REQUIRES i2cdev log esp_idf_lib_helpers +)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/pcf8591/LICENSE Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,27 @@ +Copyright (c) 2017 Pham Ngoc Thanh <pnt239@gmail.com> +Copyright (c) 2017, 2018 Ruslan V. Uss <unclerus@gmail.com> + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, +this list of conditions and the following disclaimer in the documentation +and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of itscontributors +may be used to endorse or promote products derived from this software without +specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/pcf8591/component.mk Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,2 @@ +COMPONENT_ADD_INCLUDEDIRS = . +COMPONENT_DEPENDS = i2cdev log esp_idf_lib_helpers
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/pcf8591/pcf8591.c Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,122 @@ +/* + * Copyright (c) 2017 Pham Ngoc Thanh <pnt239@gmail.com> + * Copyright (c) 2017 Ruslan V. Uss <unclerus@gmail.com> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of itscontributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file pcf8591.c + * + * ESP-IDF driver for 8-bit analog-to-digital conversion and + * an 8-bit digital-to-analog conversion PCF8591 + * + * Ported from esp-open-rtos + * + * Copyright (c) 2017 Pham Ngoc Thanh <pnt239@gmail.com>\n + * Copyright (c) 2017, 2018 Ruslan V. Uss <unclerus@gmail.com> + * + * BSD Licensed as described in the file LICENSE + */ +#include <stddef.h> +#include <esp_log.h> +#include <esp_idf_lib_helpers.h> +#include "pcf8591.h" + +static const char *TAG = "pcf8591"; + +#define I2C_FREQ_HZ 100000 + +#define BV(x) (1 << (x)) + +#define CTRL_AD_CH_MASK 0x03 + +#define CTRL_AD_IN_PRG 4 +#define CTRL_AD_IN_PRG_MASK (0x03 << CTRL_AD_IN_PRG) + +#define CTRL_DA_OUT_EN 6 + +#define CHECK_ARG(VAL) do { if (!(VAL)) return ESP_ERR_INVALID_ARG; } while (0) + +esp_err_t pcf8591_init_desc(i2c_dev_t *dev, uint8_t addr, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio) +{ + CHECK_ARG(dev); + if ((addr & PCF8591_DEFAULT_ADDRESS) != PCF8591_DEFAULT_ADDRESS) + { + ESP_LOGE(TAG, "Invalid I2C address 0x%02x", addr); + return ESP_ERR_INVALID_ARG; + } + + dev->port = port; + dev->addr = addr; + dev->cfg.sda_io_num = sda_gpio; + dev->cfg.scl_io_num = scl_gpio; +#if HELPER_TARGET_IS_ESP32 + dev->cfg.master.clk_speed = I2C_FREQ_HZ; +#endif + i2c_dev_create_mutex(dev); + + return ESP_OK; +} + +esp_err_t pcf8591_free_desc(i2c_dev_t *dev) +{ + CHECK_ARG(dev); + + return i2c_dev_delete_mutex(dev); +} + +esp_err_t pcf8591_read(i2c_dev_t *dev, pcf8591_input_conf_t conf, uint8_t channel, uint8_t *value) +{ + CHECK_ARG(dev && value); + if (channel >= 4) + { + ESP_LOGE(TAG, "Invalid channel number %d", channel); + return ESP_ERR_INVALID_ARG; + } + + uint8_t control_reg = + ((conf << CTRL_AD_IN_PRG) & CTRL_AD_IN_PRG_MASK) | + (channel & CTRL_AD_CH_MASK) | + BV(CTRL_DA_OUT_EN); + + I2C_DEV_TAKE_MUTEX(dev); + I2C_DEV_CHECK(dev, i2c_dev_read_reg(dev, control_reg, value, 1)); + I2C_DEV_GIVE_MUTEX(dev); + + return ESP_OK; +} + +esp_err_t pcf8591_write(i2c_dev_t *dev, uint8_t value) +{ + CHECK_ARG(dev); + + uint8_t buf[2] = { BV(CTRL_DA_OUT_EN), value }; + + I2C_DEV_TAKE_MUTEX(dev); + I2C_DEV_CHECK(dev, i2c_dev_write(dev, NULL, 0, buf, sizeof(buf))); + I2C_DEV_GIVE_MUTEX(dev); + + return ESP_OK; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/pcf8591/pcf8591.h Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2017 Pham Ngoc Thanh <pnt239@gmail.com> + * Copyright (c) 2017 Ruslan V. Uss <unclerus@gmail.com> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of itscontributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file pcf8591.h + * @defgroup pcf8591 pcf8591 + * @{ + * + * ESP-IDF driver for 8-bit analog-to-digital conversion and + * an 8-bit digital-to-analog conversion PCF8591 + * + * Ported from esp-open-rtos + * + * Copyright (c) 2017 Pham Ngoc Thanh <pnt239@gmail.com>\n + * Copyright (c) 2017 Ruslan V. Uss <unclerus@gmail.com> + * + * BSD Licensed as described in the file LICENSE + */ +#ifndef __PCF8591_H__ +#define __PCF8591_H__ + +#include <i2cdev.h> +#include <esp_err.h> + +#ifdef __cplusplus +extern "C" { +#endif + +#define PCF8591_DEFAULT_ADDRESS 0x48 + +/** + * Analog inputs configuration, see datasheet + */ +typedef enum { + PCF8591_IC_4_SINGLES = 0, //!< Four single-ended inputs + PCF8591_IC_DIFF, //!< Three differential inputs + PCF8591_IC_2_SINGLES_DIFF, //!< Two single-ended and differential mixed + PCF8591_IC_2_DIFFS //!< Two differential inputs +} pcf8591_input_conf_t; + +/** + * @brief Initialize device descriptor + * + * @param dev Device descriptor + * @param addr I2C device address + * @param port I2C port number + * @param sda_gpio GPIO pin for SDA + * @param scl_gpio GPIO pin for SCL + * @return `ESP_OK` on success + */ +esp_err_t pcf8591_init_desc(i2c_dev_t *dev, uint8_t addr, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio); + +/** + * @brief Free device descriptor + * + * @param dev Device descriptor + * @return `ESP_OK` on success + */ +esp_err_t pcf8591_free_desc(i2c_dev_t *dev); + +/** + * @brief Read input value of an analog pin + * + * @param dev Device descriptor + * @param conf Analog inputs configuration + * @param channel Analog channel + * @param[out] value Analog value + * @return `ESP_OK` on success + */ +esp_err_t pcf8591_read(i2c_dev_t *dev, pcf8591_input_conf_t conf, uint8_t channel, uint8_t *value); + +/** + * @brief Write value to analog output + * + * @param dev Device descriptor + * @param value DAC value + * @return `ESP_OK` on success + */ +esp_err_t pcf8591_write(i2c_dev_t *dev, uint8_t value); + + +#ifdef __cplusplus +} +#endif + +/**@}*/ + +#endif /* __PCF8591_H__ */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/qmc5883l/.eil.yml Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,23 @@ +--- +components: + - name: qmc5883l + description: Driver for QMC5883L 3-axis magnetic sensor + group: magnetic + groups: [] + code_owners: UncleRus + depends: + - i2cdev + - log + - esp_idf_lib_helpers + thread_safe: yes + targets: + - name: esp32 + - name: esp8266 + - name: esp32s2 + - name: esp32c3 + licenses: + - name: BSD-3 + copyrights: + - author: + name: UncleRus + year: 2019
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/qmc5883l/CMakeLists.txt Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,5 @@ +idf_component_register( + SRCS qmc5883l.c + INCLUDE_DIRS . + REQUIRES i2cdev log esp_idf_lib_helpers +)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/qmc5883l/LICENSE Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,26 @@ +Copyright 2019 Ruslan V. Uss <unclerus@gmail.com> + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, +this list of conditions and the following disclaimer in the documentation +and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of itscontributors +may be used to endorse or promote products derived from this software without +specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/qmc5883l/component.mk Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,2 @@ +COMPONENT_ADD_INCLUDEDIRS = . +COMPONENT_DEPENDS = i2cdev log esp_idf_lib_helpers
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/qmc5883l/qmc5883l.c Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,257 @@ +/* + * Copyright (c) 2019 Ruslan V. Uss <unclerus@gmail.com> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of itscontributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file qmc5883l.c + * + * ESP-IDF Driver for 3-axis magnetic sensor QMC5883L + * + * Copyright (c) 2019 Ruslan V. Uss <unclerus@gmail.com> + * + * BSD Licensed as described in the file LICENSE + */ + +#include <esp_log.h> +#include <esp_idf_lib_helpers.h> +#include "qmc5883l.h" + +#define I2C_FREQ_HZ 400000 // 400kHz + +#define REG_XOUT_L 0x00 +#define REG_XOUT_H 0x01 +#define REG_YOUT_L 0x02 +#define REG_YOUT_H 0x03 +#define REG_ZOUT_L 0x04 +#define REG_ZOUT_H 0x05 +#define REG_STATE 0x06 +#define REG_TOUT_L 0x07 +#define REG_TOUT_H 0x08 +#define REG_CTRL1 0x09 +#define REG_CTRL2 0x0a +#define REG_FBR 0x0b +#define REG_ID 0x0d + +#define MASK_MODE 0xfe +#define MASK_ODR 0xf3 + +static const char *TAG = "qmc5883l"; + +#define CHECK(x) do { esp_err_t __; if ((__ = x) != ESP_OK) return __; } while (0) +#define CHECK_ARG(VAL) do { if (!(VAL)) return ESP_ERR_INVALID_ARG; } while (0) + +inline static esp_err_t write_reg_nolock(qmc5883l_t *dev, uint8_t reg, uint8_t val) +{ + return i2c_dev_write_reg(&dev->i2c_dev, reg, &val, 1); +} + +inline static esp_err_t read_reg_nolock(qmc5883l_t *dev, uint8_t reg, uint8_t *val) +{ + return i2c_dev_read_reg(&dev->i2c_dev, reg, val, 1); +} + +static esp_err_t write_reg(qmc5883l_t *dev, uint8_t reg, uint8_t val) +{ + CHECK_ARG(dev); + + I2C_DEV_TAKE_MUTEX(&dev->i2c_dev); + I2C_DEV_CHECK(&dev->i2c_dev, write_reg_nolock(dev, reg, val)); + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); + + return ESP_OK; +} + +static esp_err_t read_reg(qmc5883l_t *dev, uint8_t reg, uint8_t *val) +{ + CHECK_ARG(dev && val); + + I2C_DEV_TAKE_MUTEX(&dev->i2c_dev); + I2C_DEV_CHECK(&dev->i2c_dev, read_reg_nolock(dev, reg, val)); + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); + + return ESP_OK; +} + +/////////////////////////////////////////////////////////////////////////////// + +esp_err_t qmc5883l_init_desc(qmc5883l_t *dev, uint8_t addr, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio) +{ + CHECK_ARG(dev); + + dev->i2c_dev.port = port; + dev->i2c_dev.addr = addr; + dev->i2c_dev.cfg.sda_io_num = sda_gpio; + dev->i2c_dev.cfg.scl_io_num = scl_gpio; +#if HELPER_TARGET_IS_ESP32 + dev->i2c_dev.cfg.master.clk_speed = I2C_FREQ_HZ; +#endif + return i2c_dev_create_mutex(&dev->i2c_dev); +} + +esp_err_t qmc5883l_free_desc(qmc5883l_t *dev) +{ + CHECK_ARG(dev); + + return i2c_dev_delete_mutex(&dev->i2c_dev); +} + +esp_err_t qmc5883l_reset(qmc5883l_t *dev) +{ + CHECK(write_reg(dev, REG_CTRL2, 0x80)); + dev->range = QMC5883L_RNG_2; + + return ESP_OK; +} + +esp_err_t qmc5883l_get_chip_id(qmc5883l_t *dev, uint8_t *id) +{ + return read_reg(dev, REG_ID, id); +} + +esp_err_t qmc5883l_set_mode(qmc5883l_t *dev, qmc5883l_mode_t mode) +{ + CHECK_ARG(dev && mode <= QMC5883L_MODE_CONTINUOUS); + + uint8_t v; + + I2C_DEV_TAKE_MUTEX(&dev->i2c_dev); + I2C_DEV_CHECK(&dev->i2c_dev, read_reg_nolock(dev, REG_CTRL1, &v)); + I2C_DEV_CHECK(&dev->i2c_dev, write_reg_nolock(dev, REG_CTRL1, (v & 0xfe) | mode)); + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); + + return ESP_OK; +} + +esp_err_t qmc5883l_get_mode(qmc5883l_t *dev, qmc5883l_mode_t *mode) +{ + CHECK_ARG(mode); + + uint8_t v; + CHECK(read_reg(dev, REG_CTRL1, &v)); + *mode = v & 1; + + return ESP_OK; +} + +esp_err_t qmc5883l_set_config(qmc5883l_t *dev, qmc5883l_odr_t odr, qmc5883l_osr_t osr, qmc5883l_range_t rng) +{ + CHECK_ARG(dev && odr <= QMC5883L_DR_200 && osr <= QMC5883L_OSR_512 && rng <= QMC5883L_RNG_8); + + uint8_t v; + I2C_DEV_TAKE_MUTEX(&dev->i2c_dev); + I2C_DEV_CHECK(&dev->i2c_dev, read_reg_nolock(dev, REG_CTRL1, &v)); + dev->range = rng; + I2C_DEV_CHECK(&dev->i2c_dev, write_reg_nolock(dev, REG_FBR, 1)); // Define set/reset period + I2C_DEV_CHECK(&dev->i2c_dev, write_reg_nolock(dev, REG_CTRL1, + (v & 0x03) | ((odr & 3) << 2) | ((rng & 1) << 4) | ((osr & 3) << 6))); + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); + + return ESP_OK; +} + +esp_err_t qmc5883l_get_config(qmc5883l_t *dev, qmc5883l_odr_t *odr, qmc5883l_osr_t *osr, qmc5883l_range_t *rng) +{ + CHECK_ARG(odr && osr && rng); + + uint8_t v; + CHECK(read_reg(dev, REG_CTRL1, &v)); + *odr = (v >> 2) & 3; + *osr = (v >> 6) & 3; + *rng = (v >> 4) & 1; + + return ESP_OK; +} + +esp_err_t qmc5883l_set_int(qmc5883l_t *dev, bool enable) +{ + return write_reg(dev, REG_CTRL2, enable ? 1 : 0); +} + +esp_err_t qmc5883l_get_int(qmc5883l_t *dev, bool *enable) +{ + CHECK_ARG(enable); + + uint8_t v; + CHECK(read_reg(dev, REG_CTRL2, &v)); + *enable = v & 1; + + return ESP_OK; +} + +esp_err_t qmc5883l_data_ready(qmc5883l_t *dev, bool *ready) +{ + CHECK_ARG(ready); + + uint8_t v; + CHECK(read_reg(dev, REG_STATE, &v)); + *ready = v & 1; + + return ESP_OK; +} + +esp_err_t qmc5883l_get_raw_data(qmc5883l_t *dev, qmc5883l_raw_data_t *raw) +{ + CHECK_ARG(dev && raw); + + I2C_DEV_TAKE_MUTEX(&dev->i2c_dev); + esp_err_t ret = i2c_dev_read_reg(&dev->i2c_dev, REG_XOUT_L, raw, 6); + if (ret != ESP_OK) + ESP_LOGE(TAG, "Could not read data register, err = %d", ret); + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); + return ret; +} + +esp_err_t qmc5883l_raw_to_mg(qmc5883l_t *dev, qmc5883l_raw_data_t *raw, qmc5883l_data_t *data) +{ + CHECK_ARG(dev && raw && data); + + float f = (dev->range == QMC5883L_RNG_2 ? 2000.0 : 8000.0) / 32768; + + data->x = raw->x * f; + data->y = raw->y * f; + data->z = raw->z * f; + + return ESP_OK; +} + +esp_err_t qmc5883l_get_data(qmc5883l_t *dev, qmc5883l_data_t *data) +{ + qmc5883l_raw_data_t raw; + CHECK(qmc5883l_get_raw_data(dev, &raw)); + return qmc5883l_raw_to_mg(dev, &raw, data); +} + +esp_err_t qmc5883l_get_raw_temp(qmc5883l_t *dev, int16_t *temp) +{ + CHECK_ARG(dev && temp); + + I2C_DEV_TAKE_MUTEX(&dev->i2c_dev); + esp_err_t ret = i2c_dev_read_reg(&dev->i2c_dev, REG_TOUT_L, temp, 2); + if (ret != ESP_OK) + ESP_LOGE(TAG, "Could not read TOUT register, err = %d", ret); + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); + return ret; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/qmc5883l/qmc5883l.h Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,268 @@ +/* + * Copyright (c) 2019 Ruslan V. Uss <unclerus@gmail.com> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of itscontributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file qmc5883l.h + * @defgroup qmc5883l qmc5883l + * @{ + * + * ESP-IDF Driver for 3-axis magnetic sensor QMC5883L + * + * Copyright (c) 2019 Ruslan V. Uss <unclerus@gmail.com> + * + * BSD Licensed as described in the file LICENSE + */ +#ifndef __QMC5883L_H__ +#define __QMC5883L_H__ + +#include <stdint.h> +#include <stdbool.h> +#include <i2cdev.h> +#include <esp_err.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Default I2C address + */ +#define QMC5883L_I2C_ADDR_DEF 0x0d + +/** + * Output data rate + */ +typedef enum { + QMC5883L_DR_10 = 0, //!< 10Hz + QMC5883L_DR_50, //!< 50Hz + QMC5883L_DR_100, //!< 100Hz + QMC5883L_DR_200, //!< 200Hz +} qmc5883l_odr_t; + +/** + * Oversampling rate + */ +typedef enum { + QMC5883L_OSR_64 = 0, //!< 64 samples + QMC5883L_OSR_128, //!< 128 samples + QMC5883L_OSR_256, //!< 256 samples + QMC5883L_OSR_512, //!< 512 samples +} qmc5883l_osr_t; + +/** + * Field range + */ +typedef enum { + QMC5883L_RNG_2 = 0,//!< -2G..+2G + QMC5883L_RNG_8 //!< -8G..+8G +} qmc5883l_range_t; + +/** + * Mode + */ +typedef enum { + QMC5883L_MODE_STANDBY = 0, //!< Standby low power mode, no measurements + QMC5883L_MODE_CONTINUOUS //!< Continuous measurements +} qmc5883l_mode_t; + + +/** + * Raw measurement result + */ +typedef struct +{ + int16_t x; + int16_t y; + int16_t z; +} qmc5883l_raw_data_t; + +/** + * Measurement result, milligauss + */ +typedef struct +{ + float x; + float y; + float z; +} qmc5883l_data_t; + +/** + * Device descriptor + */ +typedef struct { + i2c_dev_t i2c_dev; + qmc5883l_range_t range; +} qmc5883l_t; + +/** + * @brief Initialize device descriptor + * + * @param dev Device descriptor + * @param port I2C port number + * @param addr I2C address + * @param sda_gpio SDA GPIO + * @param scl_gpio SCL GPIO + * @return `ESP_OK` on success + */ +esp_err_t qmc5883l_init_desc(qmc5883l_t *dev, uint8_t addr, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio); + +/** + * @brief Free device descriptor + * + * @param dev Device descriptor + * @return `ESP_OK` on success + */ +esp_err_t qmc5883l_free_desc(qmc5883l_t *dev); + +/** + * @brief Reset device + * + * @param dev Device descriptor + * @return `ESP_OK` on success + */ +esp_err_t qmc5883l_reset(qmc5883l_t *dev); + +/** + * @brief Read chip ID + * + * @param dev Device descriptor + * @param[out] id Chip ID + * @return `ESP_OK` on success + */ +esp_err_t qmc5883l_get_chip_id(qmc5883l_t *dev, uint8_t *id); + +/** + * @brief Set device mode + * + * @param dev Device descriptor + * @param mode Mode + * @return `ESP_OK` on success + */ +esp_err_t qmc5883l_set_mode(qmc5883l_t *dev, qmc5883l_mode_t mode); + +/** + * @brief Read current device mode + * + * @param dev Device descriptor + * @param[out] mode Mode + * @return `ESP_OK` on success + */ +esp_err_t qmc5883l_get_mode(qmc5883l_t *dev, qmc5883l_mode_t *mode); + +/** + * @brief Set device configuration + * + * @param dev Device descriptor + * @param odr Output data rate + * @param osr Oversampling + * @param rng Field range + * @return `ESP_OK` on success + */ +esp_err_t qmc5883l_set_config(qmc5883l_t *dev, qmc5883l_odr_t odr, qmc5883l_osr_t osr, qmc5883l_range_t rng); + +/** + * @brief Read current device configuration + * + * @param dev Device descriptor + * @param[out] odr Output data rate + * @param[out] osr Oversampling + * @param[out] rng Field range + * @return `ESP_OK` on success + */ +esp_err_t qmc5883l_get_config(qmc5883l_t *dev, qmc5883l_odr_t *odr, qmc5883l_osr_t *osr, qmc5883l_range_t *rng); + +/** + * @brief Enable/disable interrupt pin + * + * @param dev Device descriptor + * @param enable Enable interrupt if true + * @return `ESP_OK` on success + */ +esp_err_t qmc5883l_set_int(qmc5883l_t *dev, bool enable); + +/** + * @brief Get interrupt pin state + * + * @param dev Device descriptor + * @param[out] enable Interrupt pin enabled if true + * @return `ESP_OK` on success + */ +esp_err_t qmc5883l_get_int(qmc5883l_t *dev, bool *enable); + +/** + * @brief Get magnetic data state + * + * @param dev Device descriptor + * @param[out] ready Magnetic data ready to read if true + * @return `ESP_OK` on success + */ +esp_err_t qmc5883l_data_ready(qmc5883l_t *dev, bool *ready); + +/** + * @brief Read raw magnetic data + * + * @param dev Device descriptor + * @param[out] raw Raw magnetic data + * @return `ESP_OK` on success + */ +esp_err_t qmc5883l_get_raw_data(qmc5883l_t *dev, qmc5883l_raw_data_t *raw); + +/** + * @brief Convert raw magnetic data to milligauss + * + * @param dev Device descriptor + * @param raw Raw magnetic data + * @param[out] data Magnetic data in mG + * @return `ESP_OK` on success + */ +esp_err_t qmc5883l_raw_to_mg(qmc5883l_t *dev, qmc5883l_raw_data_t *raw, qmc5883l_data_t *data); + +/** + * @brief Read magnetic data in milligauss + * + * @param dev Device descriptor + * @param[out] data Magnetic data in mG + * @return `ESP_OK` on success + */ +esp_err_t qmc5883l_get_data(qmc5883l_t *dev, qmc5883l_data_t *data); + +/** + * @brief Read raw temperature data (see datasheet) + * + * @param dev Device descriptor + * @param[out] temp Raw temperature data + * @return `ESP_OK` on success + */ +esp_err_t qmc5883l_get_raw_temp(qmc5883l_t *dev, int16_t *temp); + +#ifdef __cplusplus +} +#endif + +/**@}*/ + +#endif /* __QMC5883L_H__ */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/rda5807m/.eil.yml Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,23 @@ +--- +components: + - name: rda5807m + description: Driver for single-chip broadcast FM radio tuner RDA5807M + group: misc + groups: [] + code_owners: UncleRus + depends: + - i2cdev + - log + - esp_idf_lib_helpers + thread_safe: yes + targets: + - name: esp32 + - name: esp8266 + - name: esp32s2 + - name: esp32c3 + licenses: + - name: BSD-3 + copyrights: + - author: + name: UncleRus + year: 2018
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/rda5807m/CMakeLists.txt Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,5 @@ +idf_component_register( + SRCS rda5807m.c + INCLUDE_DIRS . + REQUIRES i2cdev log esp_idf_lib_helpers +)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/rda5807m/LICENSE Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,26 @@ +Copyright (c) 2019 Ruslan V. Uss <unclerus@gmail.com> + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, +this list of conditions and the following disclaimer in the documentation +and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of itscontributors +may be used to endorse or promote products derived from this software without +specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/rda5807m/component.mk Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,2 @@ +COMPONENT_ADD_INCLUDEDIRS = . +COMPONENT_DEPENDS = i2cdev log esp_idf_lib_helpers
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/rda5807m/rda5807m.c Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,539 @@ +/* + * Copyright (c) 2018 Ruslan V. Uss <unclerus@gmail.com> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of itscontributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file rda5807m.c + * + * ESP-IDF driver for single-chip broadcast FM radio tuner RDA5807M + * + * Copyright (c) 2018 Ruslan V. Uss <unclerus@gmail.com> + * + * BSD Licensed as described in the file LICENSE + */ +#include "rda5807m.h" +#include <inttypes.h> +#include <esp_idf_lib_helpers.h> +#include <esp_log.h> +#include <string.h> +#include <esp_err.h> + +#define I2C_FREQ_HZ 100000 // 100kHz + +#define I2C_ADDR_SEQ 0x10 +#define I2C_ADDR_IDX 0x11 + +// Registers +#define REG_CHIP_ID 0x00 +#define REG_CTRL 0x02 +#define REG_CHAN 0x03 +#define REG_R4 0x04 +#define REG_VOL 0x05 +#define REG_R7 0x07 +#define REG_RA 0x0a +#define REG_RB 0x0b +#define REG_RDSA 0x0c +#define REG_RDSB 0x0d +#define REG_RDSC 0x0c +#define REG_RDSD 0x0d + +// Bits +#define BIT_CTRL_ENABLE 0 +#define BIT_CTRL_SOFT_RESET 1 +#define BIT_CTRL_NEW_METHOD 2 +#define BIT_CTRL_RDS_EN 3 +#define BIT_CTRL_CLK_MODE 4 +#define BIT_CTRL_SKMODE 7 +#define BIT_CTRL_SEEK 8 +#define BIT_CTRL_SEEKUP 9 +#define BIT_CTRL_RCLK_DIRECT 10 +#define BIT_CTRL_RCLK_NC 11 +#define BIT_CTRL_BASS 12 +#define BIT_CTRL_MONO 13 +#define BIT_CTRL_DMUTE 14 +#define BIT_CTRL_DHIZ 15 + +#define BIT_CHAN_SPACE 0 +#define BIT_CHAN_BAND 2 +#define BIT_CHAN_TUNE 4 +#define BIT_CHAN_CHAN 6 + +#define BIT_R4_AFCD 8 +#define BIT_R4_SOFTMUTE_EN 9 +#define BIT_R4_DE 11 + +#define BIT_VOL_VOLUME 0 +#define BIT_VOL_SEEKTH 8 +#define BIT_VOL_INT_MODE 15 + +#define BIT_R7_SOFTBLEND_EN 1 +#define BIT_R7_50M 9 +#define BIT_R7_TH_SOFTBLEND 10 + +#define BIT_RA_READCHAN 0 +#define BIT_RA_ST 10 +#define BIT_RA_BLK_E 11 +#define BIT_RA_RDSS 12 +#define BIT_RA_SF 13 +#define BIT_RA_STC 14 +#define BIT_RA_RDSR 15 + +#define BIT_RB_BLERB 0 +#define BIT_RB_BLERA 2 +#define BIT_RB_ABCD_E 4 +#define BIT_RB_FM_READY 7 +#define BIT_RB_FM_ST 8 +#define BIT_RB_RSSI 9 + +// Masks +#define MASK_CHAN_SPACE 0x0003 +#define MASK_CHAN_BAND 0x000c +#define MASK_CHAN_CHAN 0xffc0 // high 10 bits + +#define MASK_VOL_VOLUME 0x000f +#define MASK_VOL_SEEKTH 0x0f00 + +#define MASK_RA_READCHAN 0x3ff + +#define MAX_CHAN 0x3ff + +#define BV(x) (1 << (x)) +#define CHECK(x) do { esp_err_t __; if ((__ = x) != ESP_OK) return __; } while (0) +#define CHECK_ARG(VAL) do { if (!(VAL)) return ESP_ERR_INVALID_ARG; } while (0) + +static const uint8_t spacings[] = { + [RDA5807M_CHAN_SPACE_100] = 100, + [RDA5807M_CHAN_SPACE_200] = 200, + [RDA5807M_CHAN_SPACE_50] = 50, + [RDA5807M_CHAN_SPACE_25] = 25 +}; + +typedef struct +{ + uint32_t lower; + uint32_t upper; +} band_limit_t; + +static const band_limit_t band_limits[] = { + [RDA5807M_BAND_87_108] = {87000, 108000}, + [RDA5807M_BAND_76_91] = {76000, 91000}, + [RDA5807M_BAND_76_108] = {76000, 108000}, + [RDA5807M_BAND_65_76] = {65000, 76000}, + [RDA5807M_BAND_50_76] = {50000, 76000} +}; + +static const char *TAG = "rda5807m"; + +static inline esp_err_t read_register_nolock(rda5807m_t *dev, uint8_t reg, uint16_t *val) +{ + dev->i2c_dev.addr = I2C_ADDR_IDX; + CHECK(i2c_dev_read_reg(&dev->i2c_dev, reg, val, 2)); + + *val = (*val >> 8) | (*val << 8); + + return ESP_OK; +} + +static inline esp_err_t write_register_nolock(rda5807m_t *dev, uint8_t reg, uint16_t val) +{ + uint16_t v = (val >> 8) | (val << 8); + + dev->i2c_dev.addr = I2C_ADDR_IDX; + return i2c_dev_write_reg(&dev->i2c_dev, reg, &v, 2); +} + +static esp_err_t read_register(rda5807m_t *dev, uint8_t reg, uint16_t *val) +{ + I2C_DEV_TAKE_MUTEX(&dev->i2c_dev); + I2C_DEV_CHECK(&dev->i2c_dev, read_register_nolock(dev, reg, val)); + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); + + return ESP_OK; +} + +static esp_err_t update_register_nolock(rda5807m_t *dev, uint8_t reg, uint16_t mask, uint16_t val) +{ + uint16_t old; + + CHECK(read_register_nolock(dev, reg, &old)); + CHECK(write_register_nolock(dev, reg, (old & ~mask) | val)); + + return ESP_OK; +} + +static esp_err_t update_register(rda5807m_t *dev, uint8_t reg, uint16_t mask, uint16_t val) +{ + I2C_DEV_TAKE_MUTEX(&dev->i2c_dev); + I2C_DEV_CHECK(&dev->i2c_dev, update_register_nolock(dev, reg, mask, val)); + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); + + return ESP_OK; +} + +static esp_err_t read_registers_bulk(rda5807m_t *dev, uint16_t *val, uint8_t count) +{ + I2C_DEV_TAKE_MUTEX(&dev->i2c_dev); + dev->i2c_dev.addr = I2C_ADDR_SEQ; + I2C_DEV_CHECK(&dev->i2c_dev, i2c_dev_read(&dev->i2c_dev, NULL, 0, val, count * 2)); + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); + + for (uint8_t i = 0; i < count; i++) + val[i] = (val[i] >> 8) | (val[i] << 8); + + return ESP_OK; +} + +/////////////////////////////////////////////////////////////////////////////// + +esp_err_t rda5807m_init_desc(rda5807m_t *dev, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio) +{ + CHECK_ARG(dev); + + dev->i2c_dev.port = port; + dev->i2c_dev.addr = I2C_ADDR_IDX; + dev->i2c_dev.cfg.sda_io_num = sda_gpio; + dev->i2c_dev.cfg.scl_io_num = scl_gpio; +#if HELPER_TARGET_IS_ESP32 + dev->i2c_dev.cfg.master.clk_speed = I2C_FREQ_HZ; +#endif + return i2c_dev_create_mutex(&dev->i2c_dev); +} + +esp_err_t rda5807m_free_desc(rda5807m_t *dev) +{ + CHECK_ARG(dev); + + return i2c_dev_delete_mutex(&dev->i2c_dev); +} + +esp_err_t rda5807m_init(rda5807m_t *dev, rda5807m_clock_freq_t clock_freq) +{ + CHECK_ARG(dev); + + I2C_DEV_TAKE_MUTEX(&dev->i2c_dev); + // reset + I2C_DEV_CHECK(&dev->i2c_dev, write_register_nolock(dev, REG_CTRL, BV(BIT_CTRL_SOFT_RESET) | BV(BIT_CTRL_ENABLE))); + + I2C_DEV_CHECK(&dev->i2c_dev, write_register_nolock(dev, REG_CTRL, + ((clock_freq & 7) << BIT_CTRL_CLK_MODE) | // Set clock mode + BV(BIT_CTRL_DHIZ) | // Enable audio output + BV(BIT_CTRL_DMUTE) | // Disable mute + BV(BIT_CTRL_RDS_EN) | // Enable RDS + BV(BIT_CTRL_ENABLE) // Enable chip + )); + // De-emphasis = 50us + I2C_DEV_CHECK(&dev->i2c_dev, write_register_nolock(dev, REG_R4, BV(BIT_R4_DE))); + // Set volume = 0, Seek threshold = ~32 dB SNR, INT_MODE = 1 (?) + I2C_DEV_CHECK(&dev->i2c_dev, write_register_nolock(dev, REG_VOL, BV(BIT_VOL_INT_MODE) | (8 << BIT_VOL_SEEKTH))); + + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); + + dev->band = RDA5807M_BAND_87_108; + dev->spacing = RDA5807M_CHAN_SPACE_100; + + ESP_LOGD(TAG, "Device initialized"); + + return ESP_OK; +} + +esp_err_t rda5807m_get_state(rda5807m_t *dev, rda5807m_state_t *state) +{ + CHECK_ARG(dev && state); + + uint16_t r[6]; + uint16_t ctrl; + CHECK(read_registers_bulk(dev, r, 6)); + CHECK(read_register(dev, REG_CTRL, &ctrl)); + + if (r[0] & BIT_RA_SF) state->seek_status = RDA5807M_SEEK_FAILED; + else if (r[0] & BIT_RA_STC) state->seek_status = RDA5807M_SEEK_COMPLETE; + else if (ctrl & BIT_CTRL_SEEK) state->seek_status = RDA5807M_SEEK_STARTED; + else state->seek_status = RDA5807M_SEEK_NONE; + + state->frequency = (r[0] & MASK_RA_READCHAN) * spacings[dev->spacing] + band_limits[dev->band].lower; + state->stereo = (r[0] & BIT_RA_ST) != 0; + state->station = (r[1] & BIT_RB_FM_ST) != 0; + state->rds_ready = (r[0] & BIT_RA_RDSR) != 0; + state->rssi = r[1] >> BIT_RB_RSSI; + + memcpy(state->rds, &r[2], 8); + + return ESP_OK; +} + +esp_err_t rda5807m_get_volume(rda5807m_t *dev, uint8_t *vol) +{ + CHECK_ARG(dev && vol); + + uint16_t v; + CHECK(read_register(dev, REG_VOL, &v)); + *vol = v & MASK_VOL_VOLUME; + + return ESP_OK; +} + +esp_err_t rda5807m_set_volume(rda5807m_t *dev, uint8_t vol) +{ + CHECK_ARG(dev && vol <= RDA5807M_VOL_MAX); + + CHECK(update_register(dev, REG_VOL, MASK_VOL_VOLUME, vol)); + + ESP_LOGD(TAG, "Volume set to %d", vol); + + return ESP_OK; +} + +esp_err_t rda5807m_get_mute(rda5807m_t *dev, bool *mute) +{ + CHECK_ARG(dev && mute); + + uint16_t v; + CHECK(read_register(dev, REG_CTRL, &v)); + *mute = !(v & BV(BIT_CTRL_DMUTE)); + + return ESP_OK; +} + +esp_err_t rda5807m_set_mute(rda5807m_t *dev, bool mute) +{ + CHECK_ARG(dev); + + CHECK(update_register(dev, REG_CTRL, BV(BIT_CTRL_DMUTE), mute ? 0 : BV(BIT_CTRL_DMUTE))); + + ESP_LOGD(TAG, "Mute %s", mute ? "enabled" : "disabled"); + + return ESP_OK; +} + +esp_err_t rda5807m_get_softmute(rda5807m_t *dev, bool *softmute) +{ + CHECK_ARG(dev && softmute); + + uint16_t v; + CHECK(read_register(dev, REG_R4, &v)); + *softmute = v & BV(BIT_R4_SOFTMUTE_EN); + + return ESP_OK; +} + +esp_err_t rda5807m_set_softmute(rda5807m_t *dev, bool softmute) +{ + CHECK_ARG(dev); + + CHECK(update_register(dev, REG_R4, BV(BIT_R4_SOFTMUTE_EN), softmute ? BV(BIT_R4_SOFTMUTE_EN) : 0)); + + ESP_LOGD(TAG, "Softmute %s", softmute ? "enabled" : "disabled"); + + return ESP_OK; +} + +esp_err_t rda5807m_get_bass_boost(rda5807m_t *dev, bool *bass_boost) +{ + CHECK_ARG(dev && bass_boost); + + uint16_t v; + CHECK(read_register(dev, REG_CTRL, &v)); + *bass_boost = v & BV(BIT_CTRL_BASS); + + return ESP_OK; +} + +esp_err_t rda5807m_set_bass_boost(rda5807m_t *dev, bool bass_boost) +{ + CHECK_ARG(dev); + + CHECK(update_register(dev, REG_CTRL, BV(BIT_CTRL_BASS), bass_boost ? BV(BIT_CTRL_BASS) : 0)); + + ESP_LOGD(TAG, "Bass-boost %s", bass_boost ? "enabled" : "disabled"); + + return ESP_OK; +} + +esp_err_t rda5807m_get_mono(rda5807m_t *dev, bool *mono) +{ + CHECK_ARG(dev && mono); + + uint16_t v; + CHECK(read_register(dev, REG_CTRL, &v)); + *mono = v & BV(BIT_CTRL_MONO); + + return ESP_OK; +} + +esp_err_t rda5807m_set_mono(rda5807m_t *dev, bool mono) +{ + CHECK_ARG(dev); + + CHECK(update_register(dev, REG_CTRL, BV(BIT_CTRL_MONO), mono ? BV(BIT_CTRL_MONO) : 0)); + + ESP_LOGD(TAG, "Mono %s", mono ? "enabled" : "disabled"); + + return ESP_OK; +} + +esp_err_t rda5807m_get_band(rda5807m_t *dev, rda5807m_band_t *band) +{ + CHECK_ARG(dev && band); + + uint16_t v; + CHECK(read_register(dev, REG_CHAN, &v)); + v = (v & MASK_CHAN_BAND) >> BIT_CHAN_BAND; + if (v == 3) + { + uint16_t r7; + CHECK(read_register(dev, REG_R7, &r7)); + *band = (r7 >> BIT_R7_50M) & 1 ? RDA5807M_BAND_65_76 : RDA5807M_BAND_50_76; + } + else *band = v; + + dev->band = *band; + + return ESP_OK; +} + +esp_err_t rda5807m_set_band(rda5807m_t *dev, rda5807m_band_t band) +{ + CHECK_ARG(dev && band <= RDA5807M_BAND_50_76); + + I2C_DEV_TAKE_MUTEX(&dev->i2c_dev); + I2C_DEV_CHECK(&dev->i2c_dev, update_register_nolock(dev, REG_CHAN, MASK_CHAN_BAND, + (band <= RDA5807M_BAND_65_76 ? band : RDA5807M_BAND_65_76) << BIT_CHAN_BAND)); + if (band >= RDA5807M_BAND_65_76) + I2C_DEV_CHECK(&dev->i2c_dev, update_register_nolock(dev, REG_R7, BV(BIT_R7_50M), + band == RDA5807M_BAND_65_76 ? BV(BIT_R7_50M) : 0)); + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); + + dev->band = band; + + ESP_LOGD(TAG, "Band: %" PRIu32 "..%" PRIu32 " kHz", band_limits[band].lower, band_limits[band].upper); + + return ESP_OK; +} + +esp_err_t rda5807m_get_channel_spacing(rda5807m_t *dev, rda5807m_channel_spacing_t *spacing) +{ + CHECK_ARG(dev && spacing); + + uint16_t v; + CHECK(read_register(dev, REG_CHAN, &v)); + dev->spacing = *spacing = v & MASK_CHAN_SPACE; + + return ESP_OK; +} + +esp_err_t rda5807m_set_channel_spacing(rda5807m_t *dev, rda5807m_channel_spacing_t spacing) +{ + CHECK_ARG(dev && spacing <= RDA5807M_CHAN_SPACE_25); + + CHECK(update_register(dev, REG_CTRL, MASK_CHAN_SPACE, spacing)); + dev->spacing = spacing; + + return ESP_OK; +} + +esp_err_t rda5807m_get_frequency_khz(rda5807m_t *dev, uint32_t *frequency) +{ + CHECK_ARG(dev && frequency); + + uint16_t chan; + CHECK(read_registers_bulk(dev, &chan, 1)); + chan &= MASK_RA_READCHAN; + + *frequency = chan * spacings[dev->spacing] + band_limits[dev->band].lower; + + return ESP_OK; +} + +esp_err_t rda5807m_set_frequency_khz(rda5807m_t *dev, uint32_t frequency) +{ + CHECK_ARG(dev); + + if (frequency < band_limits[dev->band].lower || frequency > band_limits[dev->band].upper) + { + ESP_LOGE(TAG, "Could not set frequency: %" PRIu32 " kHz is out of bounds (%" PRIu32 "..%" PRIu32 ")", + frequency, band_limits[dev->band].lower, band_limits[dev->band].upper); + return ESP_ERR_INVALID_ARG; + } + + uint16_t chan = (frequency - band_limits[dev->band].lower) / spacings[dev->spacing]; + if (chan > MAX_CHAN) + { + ESP_LOGE(TAG, "Could not set frequency to %" PRIu32 " kHz with current band/spacing settings", frequency); + return ESP_ERR_INVALID_ARG; + } + + CHECK(update_register(dev, REG_CHAN, MASK_CHAN_CHAN | BV(BIT_CHAN_TUNE), + (chan << BIT_CHAN_CHAN) | BV(BIT_CHAN_TUNE))); + + ESP_LOGD(TAG, "Frequency: %" PRIu32 " kHz", chan * spacings[dev->spacing] + band_limits[dev->band].lower); + + return ESP_OK; +} + +esp_err_t rda5807m_get_afc(rda5807m_t *dev, bool *afc) +{ + CHECK_ARG(dev && afc); + + uint16_t v; + CHECK(read_register(dev, REG_R4, &v)); + *afc = !(v & BV(BIT_R4_AFCD)); + + return ESP_OK; +} + +esp_err_t rda5807m_set_afc(rda5807m_t *dev, bool afc) +{ + CHECK_ARG(dev); + + return update_register(dev, REG_R4, BV(BIT_R4_AFCD), afc ? 0 : BV(BIT_R4_AFCD)); +} + +esp_err_t rda5807m_seek_start(rda5807m_t *dev, bool up, bool wrap, uint8_t threshold) +{ + CHECK_ARG(dev && threshold <= RDA5807M_SEEK_TH_MAX); + + I2C_DEV_TAKE_MUTEX(&dev->i2c_dev); + I2C_DEV_CHECK(&dev->i2c_dev, update_register_nolock(dev, REG_VOL, MASK_VOL_SEEKTH, + threshold << BIT_VOL_SEEKTH)); + I2C_DEV_CHECK(&dev->i2c_dev, update_register_nolock(dev, REG_CTRL, + BV(BIT_CTRL_SEEK) | BV(BIT_CTRL_SEEKUP) | BV(BIT_CTRL_SKMODE), + BV(BIT_CTRL_SEEK) | (up ? BV(BIT_CTRL_SEEKUP) : 0) | (wrap ? 0 : BV(BIT_CTRL_SKMODE)))); + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); + + ESP_LOGD(TAG, "Seek started: %s, %s at bound, SNR threshold: %d", up ? "up" : "down", wrap ? "wrap" : "stop", threshold); + + return ESP_OK; +} + +esp_err_t rda5807m_seek_stop(rda5807m_t *dev) +{ + CHECK_ARG(dev); + CHECK(update_register(dev, REG_CTRL, BV(BIT_CTRL_SEEK), 0)); + + ESP_LOGD(TAG, "Seek stopped"); + + return ESP_OK; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/rda5807m/rda5807m.h Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,353 @@ +/* + * Copyright (c) 2018 Ruslan V. Uss <unclerus@gmail.com> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of itscontributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file rda5807m.h + * @defgroup rda5807m rda5807m + * @{ + * + * ESP-IDF driver for single-chip broadcast FM radio tuner RDA5807M + * + * Copyright (c) 2018 Ruslan V. Uss <unclerus@gmail.com> + * + * BSD Licensed as described in the file LICENSE + */ +#ifndef __RDA5807M_H__ +#define __RDA5807M_H__ + +#include <i2cdev.h> +#include <stdbool.h> + +#ifdef __cplusplus +extern "C" { +#endif + +#define RDA5807M_RSSI_MAX 0x3f +#define RDA5807M_SEEK_TH_DEF 0x08 +#define RDA5807M_SEEK_TH_MAX 0x0f +#define RDA5807M_VOL_MAX 0x0f + +/** + * Clock mode + */ +typedef enum +{ + RDA5807M_CLK_32768HZ = 0, //!< 32768 Hz, default + RDA5807M_CLK_12MHZ = 1, //!< 12 MHz + RDA5807M_CLK_13MHZ = 2, //!< 13 MHz + RDA5807M_CLK_19_2MHZ = 3, //!< 19.2 MHz + RDA5807M_CLK_24MHZ = 5, //!< 24 MHz + RDA5807M_CLK_26MHZ = 6, //!< 26 MHz + RDA5807M_CLK_38_4MHZ = 7, //!< 38.4 MHz +} rda5807m_clock_freq_t; + +/** + * Channel spacing (frequency step) + */ +typedef enum +{ + RDA5807M_CHAN_SPACE_100 = 0, //!< 100 KHz, default + RDA5807M_CHAN_SPACE_200, //!< 200 KHz + RDA5807M_CHAN_SPACE_50, //!< 50 KHz + RDA5807M_CHAN_SPACE_25 //!< 25 KHz +} rda5807m_channel_spacing_t; + +/** + * FM Band + */ +typedef enum { + RDA5807M_BAND_87_108 = 0, //!< 87..108 MHz (US/Europe), default + RDA5807M_BAND_76_91, //!< 76..91 MHz (Japan) + RDA5807M_BAND_76_108, //!< 76..108 MHz (Worldwide) + RDA5807M_BAND_65_76, //!< 65..76 MHz (Eastern Europe) + RDA5807M_BAND_50_76 //!< 50..76 MHz (Eastern Europe wide) +} rda5807m_band_t; + +/** + * Seek status + */ +typedef enum { + RDA5807M_SEEK_NONE = 0, //!< There is currently no station seek + RDA5807M_SEEK_STARTED, //!< Seeking is in progress + RDA5807M_SEEK_COMPLETE, //!< Seeking is complete + RDA5807M_SEEK_FAILED //!< Seeking is failed - no stations with RSSI > threshold found +} rda5807m_seek_status_t; + +/** + * Overall device status + */ +typedef struct +{ + rda5807m_seek_status_t seek_status; //!< Seek status + bool station; //!< True if tuned to a station + bool stereo; //!< True if stereo is available + bool rds_ready; //!< True if RDS data is ready + uint8_t rssi; //!< RSSI, 0..RDA5807M_RSSI_MAX (logarithmic scale) + uint32_t frequency; //!< Current frequency, kHz + uint16_t rds[4]; //!< RDS data +} rda5807m_state_t; + +/** + * Device descriptor + */ +typedef struct +{ + i2c_dev_t i2c_dev; //!< I2C device descriptor + rda5807m_band_t band; //!< Current band + rda5807m_channel_spacing_t spacing; //!< Current spacing +} rda5807m_t; + +/** + * @brief Initialize device descriptor + * + * @param dev Device descriptor + * @param port I2C port + * @param sda_gpio SDA GPIO + * @param scl_gpio SCL GPIO + * @return `ESP_OK` on success + */ +esp_err_t rda5807m_init_desc(rda5807m_t *dev, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio); + +/** + * @brief Free device descriptor + * + * @param dev Device descriptor + * @return `ESP_OK` on success + */ +esp_err_t rda5807m_free_desc(rda5807m_t *dev); + +/** + * @brief Initialize device + * + * @param dev Device descriptor + * @param clock_freq RCLK frequency, usually `RDA5807M_CLK_32768HZ` + * @return `ESP_OK` on success + */ +esp_err_t rda5807m_init(rda5807m_t *dev, rda5807m_clock_freq_t clock_freq); + +/** + * @brief Get current device status + * + * @param dev Device descriptor + * @param[out] state Device status descriptor + * @return `ESP_OK` on success + */ +esp_err_t rda5807m_get_state(rda5807m_t *dev, rda5807m_state_t *state); + +/** + * @brief Get volume level (DAC gain) + * + * @param dev Device descriptor + * @param[out] vol Volume level, 0..RDA5807M_VOL_MAX + * @return `ESP_OK` on success + */ +esp_err_t rda5807m_get_volume(rda5807m_t *dev, uint8_t *vol); + +/** + * @brief Set volume level (DAC gain) + * + * Volume scale is logarithmic. When 0, device is muted and output impedance + * is very large. + * + * @param dev Device descriptor + * @param vol Volume level, 0..RDA5807M_VOL_MAX + * @return `ESP_OK` on success + */ +esp_err_t rda5807m_set_volume(rda5807m_t *dev, uint8_t vol); + +/** + * @brief Get current mute state + * + * @param dev Device descriptor + * @param[out] mute Mute state + * @return `ESP_OK` on success + */ +esp_err_t rda5807m_get_mute(rda5807m_t *dev, bool *mute); + +/** + * @brief Mute/unmute device + * + * @param dev Device descriptor + * @param mute Mute if true + * @return `ESP_OK` on success + */ +esp_err_t rda5807m_set_mute(rda5807m_t *dev, bool mute); + +/** + * @brief Get current soft mute state + * + * @param dev Device descriptor + * @param[out] softmute Soft mute state + * @return `ESP_OK` on success + */ +esp_err_t rda5807m_get_softmute(rda5807m_t *dev, bool *softmute); + +/** + * @brief Enable/disable soft mute + * + * @param dev Device descriptor + * @param softmute If true, enable soft mute + * @return `ESP_OK` on success + */ +esp_err_t rda5807m_set_softmute(rda5807m_t *dev, bool softmute); + +/** + * @brief Get current state of the bass boost feature + * + * @param dev Device descriptor + * @param[out] bass_boost Bass boost state + * @return `ESP_OK` on success + */ +esp_err_t rda5807m_get_bass_boost(rda5807m_t *dev, bool *bass_boost); + +/** + * @brief Enable/disable bass boost feature + * + * @param dev Device descriptor + * @param bass_boost If true, enable bass boost + * @return `ESP_OK` on success + */ +esp_err_t rda5807m_set_bass_boost(rda5807m_t *dev, bool bass_boost); + +/** + * @brief Get forced mono state + * + * @param dev Device descriptor + * @param[out] mono Forced mono state + * @return `ESP_OK` on success + */ +esp_err_t rda5807m_get_mono(rda5807m_t *dev, bool *mono); + +/** + * @brief Enable/disable forced mono + * + * @param dev Device descriptor + * @param mono If true, audio will be in mono even if stereo is available + * @return `ESP_OK` on success + */ +esp_err_t rda5807m_set_mono(rda5807m_t *dev, bool mono); + +/** + * @brief Get current band + * + * @param dev Device descriptor + * @param[out] band Current band + * @return `ESP_OK` on success + */ +esp_err_t rda5807m_get_band(rda5807m_t *dev, rda5807m_band_t *band); + +/** + * @brief Switch device to band + * + * @param dev Device descriptor + * @param band New band + * @return `ESP_OK` on success + */ +esp_err_t rda5807m_set_band(rda5807m_t *dev, rda5807m_band_t band); + +/** + * @brief Get current channel spacing (frequency step) + * + * @param dev Device descriptor + * @param[out] spacing Current channel spacing + * @return `ESP_OK` on success + */ +esp_err_t rda5807m_get_channel_spacing(rda5807m_t *dev, rda5807m_channel_spacing_t *spacing); + +/** + * @brief Set channel spacing (frequency step) + * + * @param dev Device descriptor + * @param spacing Channel spacing, usually `RDA5807M_CHAN_SPACE_100` + * @return `ESP_OK` on success + */ +esp_err_t rda5807m_set_channel_spacing(rda5807m_t *dev, rda5807m_channel_spacing_t spacing); + +/** + * @brief Get frequency the device is tuned to + * + * @param dev Device descriptor + * @param[out] frequency Frequency, kHz + * @return `ESP_OK` on success + */ +esp_err_t rda5807m_get_frequency_khz(rda5807m_t *dev, uint32_t *frequency); + +/** + * @brief Tune device to a frequency + * + * @param dev Device descriptor + * @param frequency Frequency, kHz + * @return `ESP_OK` on success + */ +esp_err_t rda5807m_set_frequency_khz(rda5807m_t *dev, uint32_t frequency); + +/** + * @brief Get current state of the automatic frequency control + * + * @param dev Device descriptor + * @param[out] afc AFC state + * @return `ESP_OK` on success + */ +esp_err_t rda5807m_get_afc(rda5807m_t *dev, bool *afc); + +/** + * @brief Enable/disable automatic frequency control + * + * @param dev Device descriptor + * @param afc AFC state + * @return `ESP_OK` on success + */ +esp_err_t rda5807m_set_afc(rda5807m_t *dev, bool afc); + +/** + * @brief Start seeking stations + * + * @param dev Device descriptor + * @param up Seeking direction: true - up, false - down + * @param wrap If true, wrap at the upper or lower band limit and + * continue seeking, else stop seeking at bounds + * @param threshold Seeking SNR threshold, 0..`RDA5807M_SEEK_TH_MAX`. + * Usually it's `RDA5807M_SEEK_TH_DEF` + * @return `ESP_OK` on success + */ +esp_err_t rda5807m_seek_start(rda5807m_t *dev, bool up, bool wrap, uint8_t threshold); + +/** + * @brief Stop seeking stations + * + * @param dev Device descriptor + * @return `ESP_OK` on success + */ +esp_err_t rda5807m_seek_stop(rda5807m_t *dev); + +#ifdef __cplusplus +} +#endif + +/**@}*/ + +#endif /* __RDA5807M_H__ */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/scd30/.eil.yml Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,30 @@ +--- +components: + - name: scd30 + description: Driver for SCD30 CO₂ sensor + group: air-quality + groups: + - name: gas + code_owners: UncleRus + depends: + - i2cdev + - log + - esp_idf_lib_helpers + thread_safe: yes + targets: + - name: esp32 + - name: esp8266 + - name: esp32s2 + - name: esp32c3 + licenses: + - name: BSD-3 + copyrights: + - author: + name: UncleRus + year: 2021 + - author: + name: Sensirion + year: 2021 + - author: + name: nated0g + year: 2021
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/scd30/CMakeLists.txt Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,5 @@ +idf_component_register( + SRCS scd30.c + INCLUDE_DIRS . + REQUIRES i2cdev log esp_idf_lib_helpers +)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/scd30/LICENSE Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,28 @@ +Copyright (c) 2021, Sensirion AG +Copyright (c) 2021 Ruslan V. Uss <unclerus@gmail.com> +Copyright (c) 2021 Nate Usher <n.usher87@gmail.com> + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, +this list of conditions and the following disclaimer in the documentation +and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of itscontributors +may be used to endorse or promote products derived from this software without +specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/scd30/component.mk Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,2 @@ +COMPONENT_ADD_INCLUDEDIRS = . +COMPONENT_DEPENDS = i2cdev log esp_idf_lib_helpers
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/scd30/scd30.c Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,343 @@ +/* + * Copyright (c) 2021, Sensirion AG + * Copyright (c) 2021 Ruslan V. Uss <unclerus@gmail.com> + * Copyright (c) 2021 Nate Usher <n.usher87@gmail.com> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of itscontributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file scd30.c + * + * ESP-IDF driver for Sensirion SCD30 CO2 sensor. + * + * Adapted from https://github.com/UncleRus/esp-idf-lib/tree/master/components/scd4x + * + * Copyright (c) 2021, Sensirion AG + * Copyright (c) 2021 Ruslan V. Uss <unclerus@gmail.com> + * Copyright (c) 2021 Nate Usher <n.usher87@gmail.com> + * + * BSD Licensed as described in the file LICENSE + */ +#include <freertos/FreeRTOS.h> +#include <freertos/task.h> +#include <esp_log.h> +#if CONFIG_IDF_TARGET_ESP32 +#include <esp32/rom/ets_sys.h> +#elif CONFIG_IDF_TARGET_ESP32S2 +#include <esp32s2/rom/ets_sys.h> +#elif CONFIG_IDF_TARGET_ESP32S3 +#include <esp32s3/rom/ets_sys.h> +#elif CONFIG_IDF_TARGET_ESP32C3 +#include <esp32c3/rom/ets_sys.h> +#elif CONFIG_IDF_TARGET_ESP32H2 +#include <esp32h2/rom/ets_sys.h> +#elif CONFIG_IDF_TARGET_ESP32C2 +#include <esp32c2/rom/ets_sys.h> +#endif +#include <esp_idf_lib_helpers.h> +#include "scd30.h" + +#define I2C_FREQ_HZ 100000 // 100kHz + +static const char *TAG = "scd30"; + +#define CMD_TRIGGER_CONTINUOUS_MEASUREMENT (0x0010) +#define CMD_STOP_CONTINUOUS_MEASUREMENT (0x0104) +#define CMD_SET_MEASUREMENT_INTERVAL (0x4600) +#define CMD_GET_DATA_READY_STATUS (0x0202) +#define CMD_READ_MEASUREMENT (0x0300) +#define CMD_ACTIVATE_AUTOMATIC_SELF_CALIBRATION (0x5306) +#define CMD_SET_FORCED_RECALIBRATION_VALUE (0x5204) +#define CMD_SET_TEMPERATURE_OFFSET (0x5403) +#define CMD_ALTITUDE_COMPENSATION (0x5102) +#define CMD_READ_FIRMWARE_VERSION (0xD100) +#define CMD_SOFT_RESET (0XD304) + +#define CHECK(x) \ + do \ + { \ + esp_err_t __; \ + if ((__ = x) != ESP_OK) \ + return __; \ + } while (0) +#define CHECK_ARG(VAL) \ + do \ + { \ + if (!(VAL)) \ + return ESP_ERR_INVALID_ARG; \ + } while (0) + +static uint8_t crc8(const uint8_t *data, size_t count) +{ + uint8_t res = 0xff; + + for (size_t i = 0; i < count; ++i) + { + res ^= data[i]; + for (uint8_t bit = 8; bit > 0; --bit) + { + if (res & 0x80) + res = (res << 1) ^ 0x31; + else + res = (res << 1); + } + } + return res; +} + +static inline uint16_t swap(uint16_t v) +{ + return (v << 8) | (v >> 8); +} + +static esp_err_t send_cmd(i2c_dev_t *dev, uint16_t cmd, uint16_t *data, size_t words) +{ + uint8_t buf[2 + words * 3]; + // add command + *(uint16_t *)buf = swap(cmd); + if (data && words) + // add arguments + for (size_t i = 0; i < words; i++) + { + uint8_t *p = buf + 2 + i * 3; + *(uint16_t *)p = swap(data[i]); + *(p + 2) = crc8(p, 2); + } + + ESP_LOGV(TAG, "Sending buffer:"); + ESP_LOG_BUFFER_HEX_LEVEL(TAG, buf, sizeof(buf), ESP_LOG_VERBOSE); + + return i2c_dev_write(dev, NULL, 0, buf, sizeof(buf)); +} + +static esp_err_t read_resp(i2c_dev_t *dev, uint16_t *data, size_t words) +{ + uint8_t buf[words * 3]; + CHECK(i2c_dev_read(dev, NULL, 0, buf, sizeof(buf))); + + ESP_LOGV(TAG, "Received buffer:"); + ESP_LOG_BUFFER_HEX_LEVEL(TAG, buf, sizeof(buf), ESP_LOG_VERBOSE); + + for (size_t i = 0; i < words; i++) + { + uint8_t *p = buf + i * 3; + uint8_t crc = crc8(p, 2); + if (crc != *(p + 2)) + { + ESP_LOGE(TAG, "Invalid CRC 0x%02x, expected 0x%02x", crc, *(p + 2)); + return ESP_ERR_INVALID_CRC; + } + data[i] = swap(*(uint16_t *)p); + } + return ESP_OK; +} + +static esp_err_t execute_cmd(i2c_dev_t *dev, uint16_t cmd, uint32_t timeout_ms, + uint16_t *out_data, size_t out_words, uint16_t *in_data, size_t in_words) +{ + CHECK_ARG(dev); + + I2C_DEV_TAKE_MUTEX(dev); + I2C_DEV_CHECK(dev, send_cmd(dev, cmd, out_data, out_words)); + if (timeout_ms) + { + if (timeout_ms > 10) + vTaskDelay(pdMS_TO_TICKS(timeout_ms)); + else + ets_delay_us(timeout_ms * 1000); + } + if (in_data && in_words) + I2C_DEV_CHECK(dev, read_resp(dev, in_data, in_words)); + I2C_DEV_GIVE_MUTEX(dev); + + return ESP_OK; +} + +////////////////////////////////////////////////////////////////// + +esp_err_t scd30_init_desc(i2c_dev_t *dev, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio) +{ + CHECK_ARG(dev); + + dev->port = port; + dev->addr = SCD30_I2C_ADDR; + dev->cfg.sda_io_num = sda_gpio; + dev->cfg.scl_io_num = scl_gpio; +#if HELPER_TARGET_IS_ESP32 + dev->cfg.master.clk_speed = I2C_FREQ_HZ; +#endif + + return i2c_dev_create_mutex(dev); +} + +esp_err_t scd30_free_desc(i2c_dev_t *dev) +{ + CHECK_ARG(dev); + + return i2c_dev_delete_mutex(dev); +} + +esp_err_t scd30_trigger_continuous_measurement(i2c_dev_t *dev, uint16_t p_comp) +{ + CHECK_ARG(p_comp == 0 || (p_comp >= 700 && p_comp <= 1400)); + + return execute_cmd(dev, CMD_TRIGGER_CONTINUOUS_MEASUREMENT, 0, &p_comp, 1, NULL, 0); +} + +esp_err_t scd30_stop_continuous_measurement(i2c_dev_t *dev) +{ + return execute_cmd(dev, CMD_STOP_CONTINUOUS_MEASUREMENT, 1, NULL, 0, NULL, 0); +} + +esp_err_t scd30_get_measurement_interval(i2c_dev_t *dev, uint16_t *interval_seconds) +{ + CHECK_ARG(interval_seconds); + return execute_cmd(dev, CMD_SET_MEASUREMENT_INTERVAL, 1, NULL, 0, interval_seconds, 1); +} + +esp_err_t scd30_set_measurement_interval(i2c_dev_t *dev, uint16_t interval_seconds) +{ + CHECK_ARG(interval_seconds > 2 && interval_seconds < 1800); + return execute_cmd(dev, CMD_SET_MEASUREMENT_INTERVAL, 1, &interval_seconds, 1, NULL, 0); +} + +esp_err_t scd30_get_data_ready_status(i2c_dev_t *dev, bool *data_ready) +{ + CHECK_ARG(data_ready); + + uint16_t status; + CHECK(execute_cmd(dev, CMD_GET_DATA_READY_STATUS, 1, NULL, 0, &status, 1)); + *data_ready = status != 0; + + return ESP_OK; +} + +esp_err_t scd30_read_measurement(i2c_dev_t *dev, float *co2, float *temperature, float *humidity) +{ + CHECK_ARG(co2 || temperature || humidity); + + union { + uint32_t u32; + float f; + } tmp; + uint16_t buf[6]; + CHECK(execute_cmd(dev, CMD_READ_MEASUREMENT, 3, NULL, 0, buf, 6)); + if (co2) + { + tmp.u32 = ((uint32_t)buf[0] << 16) | buf[1]; + *co2 = tmp.f; + } + if (temperature) + { + tmp.u32 = ((uint32_t)buf[2] << 16) | buf[3]; + *temperature = tmp.f; + } + if (humidity) + { + tmp.u32 = ((uint32_t)buf[4] << 16) | buf[5]; + *humidity = tmp.f; + } + return ESP_OK; +} + +esp_err_t scd30_get_automatic_self_calibration(i2c_dev_t *dev, bool *enabled) +{ + CHECK_ARG(enabled); + + return execute_cmd(dev, CMD_ACTIVATE_AUTOMATIC_SELF_CALIBRATION, 1, NULL, 0, (uint16_t *)enabled, 1); +} + +esp_err_t scd30_set_automatic_self_calibration(i2c_dev_t *dev, bool enabled) +{ + return execute_cmd(dev, CMD_ACTIVATE_AUTOMATIC_SELF_CALIBRATION, 1, (uint16_t *)&enabled, 1, NULL, 0); +} + +esp_err_t scd30_get_forced_recalibration_value(i2c_dev_t *dev, uint16_t *correction_value) +{ + CHECK_ARG(correction_value); + + return execute_cmd(dev, CMD_SET_FORCED_RECALIBRATION_VALUE, 1, + NULL, 0, correction_value, 1); +} + +esp_err_t scd30_set_forced_recalibration_value(i2c_dev_t *dev, uint16_t target_co2_concentration) +{ + CHECK_ARG(target_co2_concentration); + + return execute_cmd(dev, CMD_SET_FORCED_RECALIBRATION_VALUE, 1, + &target_co2_concentration, 1, NULL, 0); +} + +esp_err_t scd30_get_temperature_offset_ticks(i2c_dev_t *dev, uint16_t *t_offset) +{ + CHECK_ARG(t_offset); + + return execute_cmd(dev, CMD_SET_TEMPERATURE_OFFSET, 1, NULL, 0, t_offset, 1); +} + +esp_err_t scd30_get_temperature_offset(i2c_dev_t *dev, float *t_offset) +{ + CHECK_ARG(t_offset); + uint16_t raw; + + CHECK(scd30_get_temperature_offset_ticks(dev, &raw)); + + *t_offset = (float)raw / 100; + return ESP_OK; +} + +esp_err_t scd30_set_temperature_offset_ticks(i2c_dev_t *dev, uint16_t t_offset) +{ + return execute_cmd(dev, CMD_SET_TEMPERATURE_OFFSET, 1, &t_offset, 1, NULL, 0); +} + +esp_err_t scd30_set_temperature_offset(i2c_dev_t *dev, float t_offset) +{ + uint16_t raw = (uint16_t)(t_offset * 100); + return scd30_set_temperature_offset_ticks(dev, raw); +} + +esp_err_t scd30_get_sensor_altitude(i2c_dev_t *dev, uint16_t *altitude) +{ + CHECK_ARG(altitude); + + return execute_cmd(dev, CMD_ALTITUDE_COMPENSATION, 1, NULL, 0, altitude, 1); +} + +esp_err_t scd30_set_sensor_altitude(i2c_dev_t *dev, uint16_t altitude) +{ + return execute_cmd(dev, CMD_ALTITUDE_COMPENSATION, 1, &altitude, 1, NULL, 0); +} + +esp_err_t scd30_read_firmware_version(i2c_dev_t *dev, uint16_t *firmware_version) +{ + CHECK_ARG(firmware_version); + return execute_cmd(dev, CMD_READ_FIRMWARE_VERSION, 1, NULL, 0, firmware_version, 1); +} + +esp_err_t scd30_soft_reset(i2c_dev_t *dev) +{ + return execute_cmd(dev, CMD_SOFT_RESET, 0, NULL, 0, NULL, 0); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/scd30/scd30.h Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,310 @@ +/* + * Copyright (c) 2021, Sensirion AG + * Copyright (c) 2021 Ruslan V. Uss <unclerus@gmail.com> + * Copyright (c) 2021 Nate Usher <n.usher87@gmail.com> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of itscontributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file scd30.h + * @defgroup scd30 scd30 + * @{ + * + * ESP-IDF driver for Sensirion SCD30 CO2 sensor. + * + * Adapted from https://github.com/UncleRus/esp-idf-lib/tree/master/components/scd4x + * + * Copyright (c) 2021, Sensirion AG + * Copyright (c) 2021 Ruslan V. Uss <unclerus@gmail.com> + * Copyright (c) 2021 Nate Usher <n.usher87@gmail.com> + * + * BSD Licensed as described in the file LICENSE + */ +#ifndef __SCD30_H__ +#define __SCD30_H__ + +#include <i2cdev.h> +#include <esp_err.h> +#include <stdbool.h> + +#ifdef __cplusplus +extern "C" { +#endif + +#define SCD30_I2C_ADDR 0x61 + +/** + * @brief Initialize device descriptor. + * + * @param dev Device descriptor + * @param port I2C port + * @param sda_gpio SDA GPIO + * @param scl_gpio SCL GPIO + * @return `ESP_OK` on success + */ +esp_err_t scd30_init_desc(i2c_dev_t *dev, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio); + +/** + * @brief Free device descriptor. + * + * @param dev Device descriptor + * @return `ESP_OK` on success + */ +esp_err_t scd30_free_desc(i2c_dev_t *dev); + +/** + * @brief Trigger continuous measurement. + * + * Signal update interval default is 2 seconds. + * + * @param dev Device descriptor + * @param p_comp Optional ambient pressure compensation in mBar, 0 to deactivate + * @return `ESP_OK` on success + */ +esp_err_t scd30_trigger_continuous_measurement(i2c_dev_t *dev, uint16_t p_comp); + +/** + * @brief Stop continuous measurement. + * + * Stops the continuous measurement of the SCD30. + * + * @param dev Device descriptor + * @return `ESP_OK` on success + */ +esp_err_t scd30_stop_continuous_measurement(i2c_dev_t *dev); + +/** + * @brief Get measurement interval + * + * Gets the interval used bythe SCD30 sensor to measure in continuous measurement mode. + * Saved in non-volatile memory. + * + * @param dev Device descriptor + * @param interval_seconds Measurement interval in seconds + * + * @return `ESP_OK` on success + */ +esp_err_t scd30_get_measurement_interval(i2c_dev_t *dev, uint16_t *interval_seconds); + +/** + * @brief Set measurement interval + * + * Sets the interval used bythe SCD30 sensor to measure in continuous measurement mode. + * Saved in non-volatile memory. + * + * @param dev Device descriptor + * @param interval_seconds Measurement interval in seconds + * + * @return `ESP_OK` on success + */ +esp_err_t scd30_set_measurement_interval(i2c_dev_t *dev, uint16_t interval_seconds); + +/** + * @brief Check whether new measurement data is available for read-out. + * + * @param dev Device descriptor + * @param data_ready true if data is ready, false otherwise + * @return `ESP_OK` on success + */ +esp_err_t scd30_get_data_ready_status(i2c_dev_t *dev, bool *data_ready); + +/** + * @brief Read sensor output and convert. + * + * When new measurement data is available it can be read out with the following command. + * Make sure that the measurement is completed by calling scd30_get_data_ready_status() + * + * @param dev Device descriptor + * @param co2 CO₂ concentration in ppm + * @param temperature Temperature in degrees Celsius (°C) + * @param humidity Relative humidity in percent RH + * @return `ESP_OK` on success + */ +esp_err_t scd30_read_measurement(i2c_dev_t *dev, float *co2, float *temperature, float *humidity); + +/** + * @brief Get automatic self calibration (ASC) state. + * + * By default, the ASC is disabled. + * + * @param dev Device descriptor. + * @param enabled true if ASC is enabled, false otherwise + * @return `ESP_OK` on success + */ +esp_err_t scd30_get_automatic_self_calibration(i2c_dev_t *dev, bool *enabled); + +/** + * @brief Enable or disable automatic self calibration (ASC). + * + * By default, the ASC is disabled. + * + * @param dev Device descriptor. + * @param enabled true to enable ASC, false to disable ASC + * @return `ESP_OK` on success + */ +esp_err_t scd30_set_automatic_self_calibration(i2c_dev_t *dev, bool enabled); + +/** + * @brief Get Forced Recalibration Value + * + * See scd_30_set_forced_recalibration_value. + * + * The most recently used reference value is retained in volatile memory and + * can be read out with the command sequence given below. After repowering + * the sensor, the command will return the standard reference value of 400 ppm. + * + * @param dev Device descriptor. + * @param correction_value FRC correction value in CO₂ ppm + * + * @return `ESP_OK` on success + */ +esp_err_t scd30_get_forced_recalibration_value(i2c_dev_t *dev, + uint16_t *correction_value); + +/** + * @brief Set Forced Recalibration Value. + * + * Forced recalibration (FRC) is used to compensate for sensor drifts when a reference + * value of the CO2 concentration in close proximity to the SCD30 is available. + * For best results,the sensor has to be run in a stable environment in continuous + * mode at a measurement rateof 2s for at least two minutes before applying the FRC + * command and sending the reference value. Setting a reference CO2 concentration + * by the method described here will always supersede corrections from the ASC + * + * Imposes a permanent update to the calibration curve which persists after repowering + * the sensor. + * @param dev Device descriptor. + * @param target_co2_concentration Target CO₂ concentration in ppm. (400 <= val <= 2000) + * + * @return `ESP_OK` on success + */ +esp_err_t scd30_set_forced_recalibration_value(i2c_dev_t *dev, + uint16_t target_co2_concentration); + +/** + * @brief Get temperature offset in ticks. + * + * Get the current temperature offset value saved in non-volatile memory. + * + * @note Only available in idle mode. + * + * @param dev Device descriptor + * @param t_offset Temperature offset. + * Convert value to °C by: value * 100; + * @return `ESP_OK` on success + */ +esp_err_t scd30_get_temperature_offset_ticks(i2c_dev_t *dev, uint16_t *t_offset); + +/** + * @brief Get temperature offset in °C. + * + * See ::scd30_get_temperature_offset_ticks() for more details. + * + * @param dev Device descriptor + * @param t_offset Temperature offset in degrees Celsius (°C) + * @return `ESP_OK` on success + */ +esp_err_t scd30_get_temperature_offset(i2c_dev_t *dev, float *t_offset); + +/** + * @brief Set temperature offset in ticks. + * + * Set the temperature offset value to be saved in non-volatile memory. + * The last set value will be used for temperature offset compensation after repowering. + * + * @param dev Device descriptor + * @param t_offset Temperature offset. + * Convert °C to value by: T / 100; + * @return `ESP_OK` on success + */ +esp_err_t scd30_set_temperature_offset_ticks(i2c_dev_t *dev, uint16_t t_offset); + +/** + * @brief Set temperature offset in °C. + * + * See ::scd30_set_temperature_offset_ticks() for more details. + * + * @param dev Device descriptor + * @param t_offset Temperature offset in degrees Celsius (°C) + * @return `ESP_OK` on success + */ +esp_err_t scd30_set_temperature_offset(i2c_dev_t *dev, float t_offset); + +/** + * @brief Get configured sensor altitude. + * + * Get configured sensor altitude in meters above sea level. Per default, the + * sensor altitude is set to 0 meter above sea-level. + * + * @note Only available in idle mode. + * + * @param dev Device descriptor. + * @param altitude Sensor altitude in meters. + * @return `ESP_OK` on success + */ +esp_err_t scd30_get_sensor_altitude(i2c_dev_t *dev, uint16_t *altitude); + +/** + * @brief Set sensor altitude in meters above sea level. + * + * Note that setting a sensor altitude to the sensor overrides any pressure + * compensation based on a previously set ambient pressure. + * + * @param dev Device descriptor. + * @param altitude Sensor altitude in meters. + * @return `ESP_OK` on success + */ +esp_err_t scd30_set_sensor_altitude(i2c_dev_t *dev, uint16_t altitude); + +/** + * @brief Get firmware version. + * + * Following command can be used to read out the firmware version of SCD30 module + * The MSB is the major firmware version, the LSB is the minor firmware version + * @param dev Device descriptor + * @param firmware_version Firmware version + * + * @return `ESP_OK` on success + */ +esp_err_t scd30_read_firmware_version(i2c_dev_t *dev, uint16_t *firmware_version); + +/** + * @brief Reset the sensor + * + * Soft reset mechanism that forces the sensor into the same state as after powering up + * After soft reset the sensor will reload all calibrated data. + * + * @param dev Device descriptor + * @return `ESP_OK` on success + */ +esp_err_t scd30_soft_reset(i2c_dev_t *dev); + +#ifdef __cplusplus +} +#endif + +/**@}*/ + +#endif /* __SCD3O_H__ */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/scd4x/.eil.yml Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,27 @@ +--- +components: + - name: scd4x + description: Driver for SCD40/SCD41 miniature CO₂ sensor + group: air-quality + groups: + - name: gas + code_owners: UncleRus + depends: + - i2cdev + - log + - esp_idf_lib_helpers + thread_safe: yes + targets: + - name: esp32 + - name: esp8266 + - name: esp32s2 + - name: esp32c3 + licenses: + - name: BSD-3 + copyrights: + - author: + name: UncleRus + year: 2021 + - author: + name: Sensirion + year: 2021
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/scd4x/CMakeLists.txt Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,5 @@ +idf_component_register( + SRCS scd4x.c + INCLUDE_DIRS . + REQUIRES i2cdev log esp_idf_lib_helpers +)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/scd4x/LICENSE Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,27 @@ +Copyright (c) 2021, Sensirion AG +Copyright (c) 2021 Ruslan V. Uss <unclerus@gmail.com> + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, +this list of conditions and the following disclaimer in the documentation +and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of itscontributors +may be used to endorse or promote products derived from this software without +specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/scd4x/component.mk Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,2 @@ +COMPONENT_ADD_INCLUDEDIRS = . +COMPONENT_DEPENDS = i2cdev log esp_idf_lib_helpers
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/scd4x/scd4x.c Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,365 @@ +/* + * Copyright (c) 2021, Sensirion AG + * Copyright (c) 2021 Ruslan V. Uss <unclerus@gmail.com> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of itscontributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file scd4x.c + * + * ESP-IDF driver for SCD4x CO2 sensor. + * + * Ported from https://github.com/Sensirion/raspberry-pi-i2c-scd4x + * + * Copyright (c) 2021, Sensirion AG + * Copyright (c) 2021 Ruslan V. Uss <unclerus@gmail.com> + * + * BSD Licensed as described in the file LICENSE + */ +#include <freertos/FreeRTOS.h> +#include <freertos/task.h> +#include <esp_log.h> +#include <ets_sys.h> +#include <esp_idf_lib_helpers.h> +#include "scd4x.h" + +#define I2C_FREQ_HZ 100000 // 100kHz + +static const char *TAG = "scd4x"; + +#define CMD_START_PERIODIC_MEASUREMENT (0x21B1) +#define CMD_READ_MEASUREMENT (0xEC05) +#define CMD_STOP_PERIODIC_MEASUREMENT (0x3F86) +#define CMD_SET_TEMPERATURE_OFFSET (0x241D) +#define CMD_GET_TEMPERATURE_OFFSET (0x2318) +#define CMD_SET_SENSOR_ALTITUDE (0x2427) +#define CMD_GET_SENSOR_ALTITUDE (0x2322) +#define CMD_SET_AMBIENT_PRESSURE (0xE000) +#define CMD_PERFORM_FORCED_RECALIBRATION (0x362F) +#define CMD_SET_AUTOMATIC_SELF_CALIBRATION_ENABLED (0x2416) +#define CMD_GET_AUTOMATIC_SELF_CALIBRATION_ENABLED (0x2313) +#define CMD_START_LOW_POWER_PERIODIC_MEASUREMENT (0x21AC) +#define CMD_GET_DATA_READY_STATUS (0xE4B8) +#define CMD_PERSIST_SETTINGS (0x3615) +#define CMD_GET_SERIAL_NUMBER (0x3682) +#define CMD_PERFORM_SELF_TEST (0x3639) +#define CMD_PERFORM_FACTORY_RESET (0x3632) +#define CMD_REINIT (0x3646) +#define CMD_MEASURE_SINGLE_SHOT (0x219D) +#define CMD_MEASURE_SINGLE_SHOT_RHT_ONLY (0x2196) +#define CMD_POWER_DOWN (0x36E0) +#define CMD_WAKE_UP (0x36F6) + +#define CHECK(x) do { esp_err_t __; if ((__ = x) != ESP_OK) return __; } while (0) +#define CHECK_ARG(VAL) do { if (!(VAL)) return ESP_ERR_INVALID_ARG; } while (0) + +static uint8_t crc8(const uint8_t *data, size_t count) +{ + uint8_t res = 0xff; + + for (size_t i = 0; i < count; ++i) + { + res ^= data[i]; + for (uint8_t bit = 8; bit > 0; --bit) + { + if (res & 0x80) + res = (res << 1) ^ 0x31; + else + res = (res << 1); + } + } + return res; +} + +static inline uint16_t swap(uint16_t v) +{ + return (v << 8) | (v >> 8); +} + +static esp_err_t send_cmd(i2c_dev_t *dev, uint16_t cmd, uint16_t *data, size_t words) +{ + uint8_t buf[2 + words * 3]; + // add command + *(uint16_t *)buf = swap(cmd); + if (data && words) + // add arguments + for (size_t i = 0; i < words; i++) + { + uint8_t *p = buf + 2 + i * 3; + *(uint16_t *)p = swap(data[i]); + *(p + 2) = crc8(p, 2); + } + + ESP_LOGV(TAG, "Sending buffer:"); + ESP_LOG_BUFFER_HEX_LEVEL(TAG, buf, sizeof(buf), ESP_LOG_VERBOSE); + + return i2c_dev_write(dev, NULL, 0, buf, sizeof(buf)); +} + +static esp_err_t read_resp(i2c_dev_t *dev, uint16_t *data, size_t words) +{ + uint8_t buf[words * 3]; + CHECK(i2c_dev_read(dev, NULL, 0, buf, sizeof(buf))); + + ESP_LOGV(TAG, "Received buffer:"); + ESP_LOG_BUFFER_HEX_LEVEL(TAG, buf, sizeof(buf), ESP_LOG_VERBOSE); + + for (size_t i = 0; i < words; i++) + { + uint8_t *p = buf + i * 3; + uint8_t crc = crc8(p, 2); + if (crc != *(p + 2)) + { + ESP_LOGE(TAG, "Invalid CRC 0x%02x, expected 0x%02x", crc, *(p + 2)); + return ESP_ERR_INVALID_CRC; + } + data[i] = swap(*(uint16_t *)p); + } + return ESP_OK; +} + +static esp_err_t execute_cmd(i2c_dev_t *dev, uint16_t cmd, uint32_t timeout_ms, + uint16_t *out_data, size_t out_words, uint16_t *in_data, size_t in_words) +{ + CHECK_ARG(dev); + + I2C_DEV_TAKE_MUTEX(dev); + I2C_DEV_CHECK(dev, send_cmd(dev, cmd, out_data, out_words)); + if (timeout_ms) + { + if (timeout_ms > 10) + vTaskDelay(pdMS_TO_TICKS(timeout_ms)); + else + ets_delay_us(timeout_ms * 1000); + } + if (in_data && in_words) + I2C_DEV_CHECK(dev, read_resp(dev, in_data, in_words)); + I2C_DEV_GIVE_MUTEX(dev); + + return ESP_OK; +} + +/////////////////////////////////////////////////////////////////////////////// + +esp_err_t scd4x_init_desc(i2c_dev_t *dev, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio) +{ + CHECK_ARG(dev); + + dev->port = port; + dev->addr = SCD4X_I2C_ADDR; + dev->cfg.sda_io_num = sda_gpio; + dev->cfg.scl_io_num = scl_gpio; +#if HELPER_TARGET_IS_ESP32 + dev->cfg.master.clk_speed = I2C_FREQ_HZ; +#endif + + return i2c_dev_create_mutex(dev); +} + +esp_err_t scd4x_free_desc(i2c_dev_t *dev) +{ + CHECK_ARG(dev); + + return i2c_dev_delete_mutex(dev); +} + +esp_err_t scd4x_start_periodic_measurement(i2c_dev_t *dev) +{ + return execute_cmd(dev, CMD_START_PERIODIC_MEASUREMENT, 1, NULL, 0, NULL, 0); +} + +esp_err_t scd4x_read_measurement_ticks(i2c_dev_t *dev, uint16_t *co2, uint16_t *temperature, uint16_t *humidity) +{ + CHECK_ARG(co2 || temperature || humidity); + + uint16_t buf[3]; + CHECK(execute_cmd(dev, CMD_READ_MEASUREMENT, 1, NULL, 0, buf, 3)); + if (co2) + *co2 = buf[0]; + if (temperature) + *temperature = buf[1]; + if (humidity) + *humidity = buf[2]; + + return ESP_OK; +} + +esp_err_t scd4x_read_measurement(i2c_dev_t *dev, uint16_t *co2, float *temperature, float *humidity) +{ + CHECK_ARG(co2 || temperature || humidity); + uint16_t t_raw, h_raw; + + CHECK(scd4x_read_measurement_ticks(dev, co2, &t_raw, &h_raw)); + if (temperature) + *temperature = (float)t_raw * 175.0f / 65536.0f - 45.0f; + if (humidity) + *humidity = (float)h_raw * 100.0f / 65536.0f; + + return ESP_OK; +} + +esp_err_t scd4x_stop_periodic_measurement(i2c_dev_t *dev) +{ + return execute_cmd(dev, CMD_STOP_PERIODIC_MEASUREMENT, 500, NULL, 0, NULL, 0); +} + +esp_err_t scd4x_get_temperature_offset_ticks(i2c_dev_t *dev, uint16_t *t_offset) +{ + CHECK_ARG(t_offset); + + return execute_cmd(dev, CMD_GET_TEMPERATURE_OFFSET, 1, NULL, 0, t_offset, 1); +} + +esp_err_t scd4x_get_temperature_offset(i2c_dev_t *dev, float *t_offset) +{ + CHECK_ARG(t_offset); + uint16_t raw; + + CHECK(scd4x_get_temperature_offset_ticks(dev, &raw)); + + *t_offset = (float)raw * 175.0f / 65536.0f; + + return ESP_OK; +} + +esp_err_t scd4x_set_temperature_offset_ticks(i2c_dev_t *dev, uint16_t t_offset) +{ + return execute_cmd(dev, CMD_SET_TEMPERATURE_OFFSET, 1, &t_offset, 1, NULL, 0); +} + +esp_err_t scd4x_set_temperature_offset(i2c_dev_t *dev, float t_offset) +{ + uint16_t raw = (uint16_t)(t_offset * 65536.0f / 175.0f + 0.5f); + return scd4x_set_temperature_offset_ticks(dev, raw); +} + +esp_err_t scd4x_get_sensor_altitude(i2c_dev_t *dev, uint16_t *altitude) +{ + CHECK_ARG(altitude); + + return execute_cmd(dev, CMD_GET_SENSOR_ALTITUDE, 1, NULL, 0, altitude, 1); +} + +esp_err_t scd4x_set_sensor_altitude(i2c_dev_t *dev, uint16_t altitude) +{ + return execute_cmd(dev, CMD_SET_SENSOR_ALTITUDE, 1, &altitude, 1, NULL, 0); +} + +esp_err_t scd4x_set_ambient_ressure(i2c_dev_t *dev, uint16_t pressure) +{ + return execute_cmd(dev, CMD_SET_AMBIENT_PRESSURE, 1, &pressure, 1, NULL, 0); +} + +esp_err_t scd4x_perform_forced_recalibration(i2c_dev_t *dev, uint16_t target_co2_concentration, + uint16_t *frc_correction) +{ + CHECK_ARG(frc_correction); + + return execute_cmd(dev, CMD_PERFORM_FORCED_RECALIBRATION, 400, + &target_co2_concentration, 1, frc_correction, 1); +} + +esp_err_t scd4x_get_automatic_self_calibration(i2c_dev_t *dev, bool *enabled) +{ + CHECK_ARG(enabled); + + return execute_cmd(dev, CMD_GET_AUTOMATIC_SELF_CALIBRATION_ENABLED, 1, NULL, 0, (uint16_t *)enabled, 1); +} + +esp_err_t scd4x_set_automatic_self_calibration(i2c_dev_t *dev, bool enabled) +{ + return execute_cmd(dev, CMD_SET_AUTOMATIC_SELF_CALIBRATION_ENABLED, 1, (uint16_t *)&enabled, 1, NULL, 0); +} + +esp_err_t scd4x_start_low_power_periodic_measurement(i2c_dev_t *dev) +{ + return execute_cmd(dev, CMD_START_LOW_POWER_PERIODIC_MEASUREMENT, 0, NULL, 0, NULL, 0); +} + +esp_err_t scd4x_get_data_ready_status(i2c_dev_t *dev, bool *data_ready) +{ + CHECK_ARG(data_ready); + + uint16_t status; + CHECK(execute_cmd(dev, CMD_GET_DATA_READY_STATUS, 1, NULL, 0, &status, 1)); + *data_ready = (status & 0x7ff) != 0; + + return ESP_OK; +} + +esp_err_t scd4x_persist_settings(i2c_dev_t *dev) +{ + return execute_cmd(dev, CMD_PERSIST_SETTINGS, 800, NULL, 0, NULL, 0); +} + +esp_err_t scd4x_get_serial_number(i2c_dev_t *dev, uint16_t *serial0, uint16_t *serial1, uint16_t *serial2) +{ + CHECK_ARG(serial0 && serial1 && serial2); + + uint16_t buf[3]; + CHECK(execute_cmd(dev, CMD_GET_SERIAL_NUMBER, 1, NULL, 0, buf, 3)); + *serial0 = buf[0]; + *serial1 = buf[1]; + *serial2 = buf[2]; + + return ESP_OK; +} + +esp_err_t scd4x_perform_self_test(i2c_dev_t *dev, bool *malfunction) +{ + CHECK_ARG(malfunction); + + return execute_cmd(dev, CMD_PERFORM_SELF_TEST, 10000, NULL, 0, (uint16_t *)malfunction, 1); +} + +esp_err_t scd4x_perform_factory_reset(i2c_dev_t *dev) +{ + return execute_cmd(dev, CMD_PERFORM_FACTORY_RESET, 800, NULL, 0, NULL, 0); +} + +esp_err_t scd4x_reinit(i2c_dev_t *dev) +{ + return execute_cmd(dev, CMD_REINIT, 20, NULL, 0, NULL, 0); +} + +esp_err_t scd4x_measure_single_shot(i2c_dev_t *dev) +{ + return execute_cmd(dev, CMD_MEASURE_SINGLE_SHOT, 5000, NULL, 0, NULL, 0); +} + +esp_err_t scd4x_measure_single_shot_rht_only(i2c_dev_t *dev) +{ + return execute_cmd(dev, CMD_MEASURE_SINGLE_SHOT_RHT_ONLY, 50, NULL, 0, NULL, 0); +} + +esp_err_t scd4x_power_down(i2c_dev_t *dev) +{ + return execute_cmd(dev, CMD_POWER_DOWN, 1, NULL, 0, NULL, 0); +} + +esp_err_t scd4x_wake_up(i2c_dev_t *dev) +{ + return execute_cmd(dev, CMD_WAKE_UP, 20, NULL, 0, NULL, 0); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/scd4x/scd4x.h Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,428 @@ +/* + * Copyright (c) 2021, Sensirion AG + * Copyright (c) 2021 Ruslan V. Uss <unclerus@gmail.com> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of itscontributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file scd4x.h + * @defgroup scd4x scd4x + * @{ + * + * ESP-IDF driver for SCD4x CO2 sensor. + * + * Ported from https://github.com/Sensirion/raspberry-pi-i2c-scd4x + * + * Copyright (c) 2021, Sensirion AG + * Copyright (c) 2021 Ruslan V. Uss <unclerus@gmail.com> + * + * BSD Licensed as described in the file LICENSE + */ +#ifndef __SCD4X_H__ +#define __SCD4X_H__ + +#include <i2cdev.h> +#include <esp_err.h> +#include <stdbool.h> + +#ifdef __cplusplus +extern "C" { +#endif + +#define SCD4X_I2C_ADDR 0x62 + +/** + * @brief Initialize device descriptor. + * + * @param dev Device descriptor + * @param port I2C port + * @param sda_gpio SDA GPIO + * @param scl_gpio SCL GPIO + * @return `ESP_OK` on success + */ +esp_err_t scd4x_init_desc(i2c_dev_t *dev, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio); + +/** + * @brief Free device descriptor. + * + * @param dev Device descriptor + * @return `ESP_OK` on success + */ +esp_err_t scd4x_free_desc(i2c_dev_t *dev); + +/** + * @brief Start periodic measurement. + * + * Signal update interval is 5 seconds. + * + * @note This command is only available in idle mode. + * + * @param dev Device descriptor + * @return `ESP_OK` on success + */ +esp_err_t scd4x_start_periodic_measurement(i2c_dev_t *dev); + +/** + * @brief Read sensor output. + * + * The measurement data can only be read out once per signal update interval + * as the buffer is emptied upon read-out. If no data is available in the + * buffer, the sensor returns a NACK. To avoid a NACK response the + * ::scd4x_get_data_ready_status() can be called to check data status. + * + * @note This command is only available in measurement mode. The firmware + * updates the measurement values depending on the measurement mode. + * + * @param dev Device descriptor + * @param co2 CO₂ concentration in ppm + * @param temperature Convert value to °C by: -45 °C + 175 °C * value/2^16 + * @param humidity Convert value to %RH by: 100%RH * value/2^16 + * @return `ESP_OK` on success + */ +esp_err_t scd4x_read_measurement_ticks(i2c_dev_t *dev, uint16_t *co2, uint16_t *temperature, uint16_t *humidity); + +/** + * @brief Read sensor output and convert. + * + * See ::scd4x_read_measurement_ticks() for more details. + * + * @note This command is only available in measurement mode. The firmware + * updates the measurement values depending on the measurement mode. + * + * @param dev Device descriptor + * @param co2 CO₂ concentration in ppm + * @param temperature Temperature in degrees Celsius (°C) + * @param humidity Relative humidity in percent RH + * @return `ESP_OK` on success + */ +esp_err_t scd4x_read_measurement(i2c_dev_t *dev, uint16_t *co2, float *temperature, float *humidity); + +/** + * @brief Stop periodic measurement. + * + * Stop periodic measurement and return to idle mode for sensor configuration + * or to safe energy. + * + * @note This command is only available in measurement mode. + * + * @param dev Device descriptor + * @return `ESP_OK` on success + */ +esp_err_t scd4x_stop_periodic_measurement(i2c_dev_t *dev); + +/** + * @brief Get temperature offset in ticks. + * + * The temperature offset represents the difference between the measured + * temperature by the SCD4x and the actual ambient temperature. Per default, + * the temperature offset is set to 4°C. + * + * @note Only available in idle mode. + * + * @param dev Device descriptor + * @param t_offset Temperature offset. + * Convert value to °C by: 175 * value / 2^16 + * @return `ESP_OK` on success + */ +esp_err_t scd4x_get_temperature_offset_ticks(i2c_dev_t *dev, uint16_t *t_offset); + +/** + * @brief Get temperature offset in °C. + * + * See ::scd4x_get_temperature_offset_ticks() for more details. + * + * @note Only available in idle mode. + * + * @param dev Device descriptor + * @param t_offset Temperature offset in degrees Celsius (°C) + * @return `ESP_OK` on success + */ +esp_err_t scd4x_get_temperature_offset(i2c_dev_t *dev, float *t_offset); + +/** + * @brief Set temperature offset in ticks. + * + * Setting the temperature offset of the SCD4x inside the customer device + * correctly allows the user to leverage the RH and T output signal. Note + * that the temperature offset can depend on various factors such as the + * SCD4x measurement mode, self-heating of close components, the ambient + * temperature and air flow. Thus, the SCD4x temperature offset should be + * determined inside the customer device under its typical operation and in + * thermal equilibrium. + * + * @note Only available in idle mode. + * + * @param dev Device descriptor + * @param t_offset Temperature offset. + * Convert °C to value by: T * 2^16 / 175 + * @return `ESP_OK` on success + */ +esp_err_t scd4x_set_temperature_offset_ticks(i2c_dev_t *dev, uint16_t t_offset); + +/** + * @brief Set temperature offset in °C. + * + * See ::scd4x_set_temperature_offset_ticks() for more details. + * + * @note Only available in idle mode. + * + * @param dev Device descriptor + * @param t_offset Temperature offset in degrees Celsius (°C) + * @return `ESP_OK` on success + */ +esp_err_t scd4x_set_temperature_offset(i2c_dev_t *dev, float t_offset); + +/** + * @brief Get configured sensor altitude. + * + * Get configured sensor altitude in meters above sea level. Per default, the + * sensor altitude is set to 0 meter above sea-level. + * + * @note Only available in idle mode. + * + * @param dev Device descriptor. + * @param altitude Sensor altitude in meters. + * @return `ESP_OK` on success + */ +esp_err_t scd4x_get_sensor_altitude(i2c_dev_t *dev, uint16_t *altitude); + +/** + * @brief Set sensor altitude in meters above sea level. + * + * Note that setting a sensor altitude to the sensor overrides any pressure + * compensation based on a previously set ambient pressure. + * + * @note Only available in idle mode. + * + * @param dev Device descriptor. + * @param altitude Sensor altitude in meters. + * @return `ESP_OK` on success + */ +esp_err_t scd4x_set_sensor_altitude(i2c_dev_t *dev, uint16_t altitude); + +/** + * @brief Set ambient pressure. + * + * The set_ambient_pressure command can be sent during periodic measurements + * to enable continuous pressure compensation. Note that setting an ambient + * pressure to the sensor overrides any pressure compensation based on a + * previously set sensor altitude. + * + * @note Available during measurements. + * + * @param dev Device descriptor. + * @param pressure Ambient pressure in hPa. + * Convert value to Pa by: value * 100 + * @return `ESP_OK` on success + */ +esp_err_t scd4x_set_ambient_ressure(i2c_dev_t *dev, uint16_t pressure); + +/** + * @brief Perform forced recalibration. + * + * To successfully conduct an accurate forced recalibration, the following + * steps need to be carried out: + * - Operate the SCD4x in a periodic measurement mode for > 3 minutes in an + * environment with homogenous and constant CO₂ concentration. + * - Stop periodic measurement. Wait 500 ms. + * - Subsequently call scd4x_perform_forced_recalibration() and optionally + * read out the baseline correction. A return value of 0xffff indicates + * that the forced recalibration failed. + * + * @param dev Device descriptor. + * @param target_co2_concentration Target CO₂ concentration in ppm. + * @param frc_correction FRC correction value in CO₂ ppm or 0xFFFF + * if the command failed. + * @return `ESP_OK` on success + */ +esp_err_t scd4x_perform_forced_recalibration(i2c_dev_t *dev, + uint16_t target_co2_concentration, uint16_t *frc_correction); + +/** + * @brief Get automatic self calibration (ASC) state. + * + * By default, the ASC is enabled. + * + * @param dev Device descriptor. + * @param enabled true if ASC is enabled, false otherwise + * @return `ESP_OK` on success + */ +esp_err_t scd4x_get_automatic_self_calibration(i2c_dev_t *dev, bool *enabled); + +/** + * @brief Enable or disable automatic self calibration (ASC). + * + * By default, the ASC is enabled. + * + * @param dev Device descriptor. + * @param enabled true to enable ASC, false to disable ASC + * @return `ESP_OK` on success + */ +esp_err_t scd4x_set_automatic_self_calibration(i2c_dev_t *dev, bool enabled); + +/** + * @brief Start low power periodic measurement. + * + * Signal update interval is 30 seconds. + * + * @note This command is only available in idle mode. + * + * @param dev Device descriptor + * @return `ESP_OK` on success + */ +esp_err_t scd4x_start_low_power_periodic_measurement(i2c_dev_t *dev); + +/** + * @brief Check whether new measurement data is available for read-out. + * + * @param dev Device descriptor + * @param data_ready true if data is ready, false otherwise + * @return `ESP_OK` on success + */ +esp_err_t scd4x_get_data_ready_status(i2c_dev_t *dev, bool *data_ready); + +/** + * @brief Store current configuration in EEPROM. + * + * Configuration settings such as the temperature offset, sensor altitude and + * the ASC enabled/disabled parameter are by default stored in the volatile + * memory (RAM) only and will be lost after a power-cycle. This funciton + * stores the current configuration in the EEPROM of the SCD4x, making them + * resistant to power-cycling. To avoid unnecessary wear of the EEPROM, + * function should only be called when persistence is required and if actual + * changes to the configuration have been made. Note that field calibration + * history (i.e. FRC and ASC) is stored in the EEPROM automatically. + * + * @param dev Device descriptor + * @return `ESP_OK` on success + */ +esp_err_t scd4x_persist_settings(i2c_dev_t *dev); + +/** + * @brief Read serial number. + * + * Reading out the serial number can be used to identify the chip and to verify + * the presence of the sensor. Function returns 3 words. Together, the 3 words + * constitute a unique serial number with a length of 48 bits (big endian format). + * + * @param dev Device descriptor + * @param serial0 First word of the 48 bit serial number + * @param serial1 Second word of the 48 bit serial number + * @param serial2 Third word of the 48 bit serial number + * @return `ESP_OK` on success + */ +esp_err_t scd4x_get_serial_number(i2c_dev_t *dev, uint16_t *serial0, uint16_t *serial1, uint16_t *serial2); + +/** + * @brief Perform self-test. + * + * This function can be used as an end-of-line test to confirm sensor + * functionality. + * + * @param dev Device descriptor + * @param malfunction true if malfunction detected, false if device is OK + * @return `ESP_OK` on success + */ +esp_err_t scd4x_perform_self_test(i2c_dev_t *dev, bool *malfunction); + +/** + * @brief Factory reset sensor. + * + * Initiates the reset of all configurations stored in the EEPROM and erases the + * FRC and ASC algorithm history. + * + * @param dev Device descriptor + * @return `ESP_OK` on success + */ +esp_err_t scd4x_perform_factory_reset(i2c_dev_t *dev); + +/** + * @brief Reinitialize sensor. + * + * The reinit command reinitializes the sensor by reloading user settings from + * EEPROM. Before sending the reinit command, the stop measurement command must + * be issued. If reinit command does not trigger the desired re-initialization, + * a power-cycle should be applied to the SCD4x. + * + * @note Only available in idle mode. + * + * @param dev Device descriptor + * @return `ESP_OK` on success + */ +esp_err_t scd4x_reinit(i2c_dev_t *dev); + +/** + * @brief Perform single measurement. + * + * On-demand measurement of CO₂ concentration, relative humidity and temperature. + * The sensor output is read with the ::scd4x_read_measurement() function. + * + * @note Only available in idle mode. + * + * @param dev Device descriptor + * @return `ESP_OK` on success + */ +esp_err_t scd4x_measure_single_shot(i2c_dev_t *dev); + +/** + * @brief Perform single measurement of of relative humidity and temperature + * only. + * + * @note Only available in idle mode. + * + * @param dev Device descriptor + * @return `ESP_OK` on success + */ +esp_err_t scd4x_measure_single_shot_rht_only(i2c_dev_t *dev); + +/** + * @brief Put the sensor from idle to sleep mode. + * + * Call this function to reduce current consumption. + * + * @note Only available in idle mode. + * + * @param dev Device descriptor + * @return `ESP_OK` on success + */ +esp_err_t scd4x_power_down(i2c_dev_t *dev); + +/** + * @brief Wake up sensor from sleep mode to idle mode. + * + * @note Only available in sleep mode. + * + * @param dev Device descriptor + * @return `ESP_OK` on success + */ +esp_err_t scd4x_wake_up(i2c_dev_t *dev); + +#ifdef __cplusplus +} +#endif + +/**@}*/ + +#endif /* __SCD4X_H__ */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/sgp40/.eil.yml Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,23 @@ +--- +components: + - name: sgp40 + description: Driver for SGP40 Indoor Air Quality Sensor for VOC Measurements + group: air-quality + groups: [] + code_owners: UncleRus + depends: + - i2cdev + - log + - esp_idf_lib_helpers + thread_safe: yes + targets: + - name: esp32 + - name: esp8266 + - name: esp32s2 + - name: esp32c3 + licenses: + - name: BSD-3 + copyrights: + - author: + name: UncleRus + year: 2020
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/sgp40/CMakeLists.txt Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,5 @@ +idf_component_register( + SRCS sgp40.c sensirion_voc_algorithm.c + INCLUDE_DIRS . + REQUIRES i2cdev log esp_idf_lib_helpers +)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/sgp40/LICENSE Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,27 @@ +Copyright 2020 Sensirion AG +Copyright 2020 Ruslan V. Uss <unclerus@gmail.com> + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, +this list of conditions and the following disclaimer in the documentation +and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its contributors +may be used to endorse or promote products derived from this software without +specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/sgp40/component.mk Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,2 @@ +COMPONENT_ADD_INCLUDEDIRS = . +COMPONENT_DEPENDS = i2cdev log esp_idf_lib_helpers
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/sgp40/sensirion_voc_algorithm.c Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,804 @@ +/* + * Copyright (c) 2020, Sensirion AG + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * * Neither the name of Sensirion AG nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "sensirion_voc_algorithm.h" + +/* The fixed point arithmetic parts of this code were originally created by + * https://github.com/PetteriAimonen/libfixmath + */ + +/*!< the maximum value of fix16_t */ +#define FIX16_MAXIMUM 0x7FFFFFFF +/*!< the minimum value of fix16_t */ +#define FIX16_MINIMUM 0x80000000 +/*!< the value used to indicate overflows when FIXMATH_NO_OVERFLOW is not + * specified */ +#define FIX16_OVERFLOW 0x80000000 +/*!< fix16_t value of 1 */ +#define FIX16_ONE 0x00010000 + +inline fix16_t fix16_from_int(int32_t a) { + return a * FIX16_ONE; +} + +inline int32_t fix16_cast_to_int(fix16_t a) { + return (a >> 16); +} + +/*! Multiplies the two given fix16_t's and returns the result. */ +static fix16_t fix16_mul(fix16_t inArg0, fix16_t inArg1); + +/*! Divides the first given fix16_t by the second and returns the result. */ +static fix16_t fix16_div(fix16_t inArg0, fix16_t inArg1); + +/*! Returns the square root of the given fix16_t. */ +static fix16_t fix16_sqrt(fix16_t inValue); + +/*! Returns the exponent (e^) of the given fix16_t. */ +static fix16_t fix16_exp(fix16_t inValue); + +static fix16_t fix16_mul(fix16_t inArg0, fix16_t inArg1) { + // Each argument is divided to 16-bit parts. + // AB + // * CD + // ----------- + // BD 16 * 16 -> 32 bit products + // CB + // AD + // AC + // |----| 64 bit product + int32_t A = (inArg0 >> 16), C = (inArg1 >> 16); + uint32_t B = (inArg0 & 0xFFFF), D = (inArg1 & 0xFFFF); + + int32_t AC = A * C; + int32_t AD_CB = A * D + C * B; + uint32_t BD = B * D; + + int32_t product_hi = AC + (AD_CB >> 16); + + // Handle carry from lower 32 bits to upper part of result. + uint32_t ad_cb_temp = AD_CB << 16; + uint32_t product_lo = BD + ad_cb_temp; + if (product_lo < BD) + product_hi++; + +#ifndef FIXMATH_NO_OVERFLOW + // The upper 17 bits should all be the same (the sign). + if (product_hi >> 31 != product_hi >> 15) + return FIX16_OVERFLOW; +#endif + +#ifdef FIXMATH_NO_ROUNDING + return (product_hi << 16) | (product_lo >> 16); +#else + // Subtracting 0x8000 (= 0.5) and then using signed right shift + // achieves proper rounding to result-1, except in the corner + // case of negative numbers and lowest word = 0x8000. + // To handle that, we also have to subtract 1 for negative numbers. + uint32_t product_lo_tmp = product_lo; + product_lo -= 0x8000; + product_lo -= (uint32_t)product_hi >> 31; + if (product_lo > product_lo_tmp) + product_hi--; + + // Discard the lowest 16 bits. Note that this is not exactly the same + // as dividing by 0x10000. For example if product = -1, result will + // also be -1 and not 0. This is compensated by adding +1 to the result + // and compensating this in turn in the rounding above. + fix16_t result = (product_hi << 16) | (product_lo >> 16); + result += 1; + return result; +#endif +} + +static fix16_t fix16_div(fix16_t a, fix16_t b) { + // This uses the basic binary restoring division algorithm. + // It appears to be faster to do the whole division manually than + // trying to compose a 64-bit divide out of 32-bit divisions on + // platforms without hardware divide. + + if (b == 0) + return FIX16_MINIMUM; + + uint32_t remainder = (a >= 0) ? a : (-a); + uint32_t divider = (b >= 0) ? b : (-b); + + uint32_t quotient = 0; + uint32_t bit = 0x10000; + + /* The algorithm requires D >= R */ + while (divider < remainder) { + divider <<= 1; + bit <<= 1; + } + +#ifndef FIXMATH_NO_OVERFLOW + if (!bit) + return FIX16_OVERFLOW; +#endif + + if (divider & 0x80000000) { + // Perform one step manually to avoid overflows later. + // We know that divider's bottom bit is 0 here. + if (remainder >= divider) { + quotient |= bit; + remainder -= divider; + } + divider >>= 1; + bit >>= 1; + } + + /* Main division loop */ + while (bit && remainder) { + if (remainder >= divider) { + quotient |= bit; + remainder -= divider; + } + + remainder <<= 1; + bit >>= 1; + } + +#ifndef FIXMATH_NO_ROUNDING + if (remainder >= divider) { + quotient++; + } +#endif + + fix16_t result = quotient; + + /* Figure out the sign of result */ + if ((a ^ b) & 0x80000000) { +#ifndef FIXMATH_NO_OVERFLOW + if (result == FIX16_MINIMUM) + return FIX16_OVERFLOW; +#endif + + result = -result; + } + + return result; +} + +static fix16_t fix16_sqrt(fix16_t x) { + // It is assumed that x is not negative + + uint32_t num = x; + uint32_t result = 0; + uint32_t bit; + uint8_t n; + + bit = (uint32_t)1 << 30; + while (bit > num) + bit >>= 2; + + // The main part is executed twice, in order to avoid + // using 64 bit values in computations. + for (n = 0; n < 2; n++) { + // First we get the top 24 bits of the answer. + while (bit) { + if (num >= result + bit) { + num -= result + bit; + result = (result >> 1) + bit; + } else { + result = (result >> 1); + } + bit >>= 2; + } + + if (n == 0) { + // Then process it again to get the lowest 8 bits. + if (num > 65535) { + // The remainder 'num' is too large to be shifted left + // by 16, so we have to add 1 to result manually and + // adjust 'num' accordingly. + // num = a - (result + 0.5)^2 + // = num + result^2 - (result + 0.5)^2 + // = num - result - 0.5 + num -= result; + num = (num << 16) - 0x8000; + result = (result << 16) + 0x8000; + } else { + num <<= 16; + result <<= 16; + } + + bit = 1 << 14; + } + } + +#ifndef FIXMATH_NO_ROUNDING + // Finally, if next bit would have been 1, round the result upwards. + if (num > result) { + result++; + } +#endif + + return (fix16_t)result; +} + +static fix16_t fix16_exp(fix16_t x) { +// Function to approximate exp(); optimized more for code size than speed + +// exp(x) for x = +/- {1, 1/8, 1/64, 1/512} +#define NUM_EXP_VALUES 4 + static const fix16_t exp_pos_values[NUM_EXP_VALUES] = { + F16(2.7182818), F16(1.1331485), F16(1.0157477), F16(1.0019550)}; + static const fix16_t exp_neg_values[NUM_EXP_VALUES] = { + F16(0.3678794), F16(0.8824969), F16(0.9844964), F16(0.9980488)}; + const fix16_t* exp_values; + + fix16_t res, arg; + uint16_t i; + + if (x >= F16(10.3972)) + return FIX16_MAXIMUM; + if (x <= F16(-11.7835)) + return 0; + + if (x < 0) { + x = -x; + exp_values = exp_neg_values; + } else { + exp_values = exp_pos_values; + } + + res = FIX16_ONE; + arg = FIX16_ONE; + for (i = 0; i < NUM_EXP_VALUES; i++) { + while (x >= arg) { + res = fix16_mul(res, exp_values[i]); + x -= arg; + } + arg >>= 3; + } + return res; +} + +static void VocAlgorithm__init_instances(VocAlgorithmParams* params); +static void +VocAlgorithm__mean_variance_estimator__init(VocAlgorithmParams* params); +static void VocAlgorithm__mean_variance_estimator___init_instances( + VocAlgorithmParams* params); +static void VocAlgorithm__mean_variance_estimator__set_parameters( + VocAlgorithmParams* params, fix16_t std_initial, + fix16_t tau_mean_variance_hours, fix16_t gating_max_duration_minutes); +static void +VocAlgorithm__mean_variance_estimator__set_states(VocAlgorithmParams* params, + fix16_t mean, fix16_t std, + fix16_t uptime_gamma); +static fix16_t +VocAlgorithm__mean_variance_estimator__get_std(VocAlgorithmParams* params); +static fix16_t +VocAlgorithm__mean_variance_estimator__get_mean(VocAlgorithmParams* params); +static void VocAlgorithm__mean_variance_estimator___calculate_gamma( + VocAlgorithmParams* params, fix16_t voc_index_from_prior); +static void VocAlgorithm__mean_variance_estimator__process( + VocAlgorithmParams* params, fix16_t sraw, fix16_t voc_index_from_prior); +static void VocAlgorithm__mean_variance_estimator___sigmoid__init( + VocAlgorithmParams* params); +static void VocAlgorithm__mean_variance_estimator___sigmoid__set_parameters( + VocAlgorithmParams* params, fix16_t L, fix16_t X0, fix16_t K); +static fix16_t VocAlgorithm__mean_variance_estimator___sigmoid__process( + VocAlgorithmParams* params, fix16_t sample); +static void VocAlgorithm__mox_model__init(VocAlgorithmParams* params); +static void VocAlgorithm__mox_model__set_parameters(VocAlgorithmParams* params, + fix16_t SRAW_STD, + fix16_t SRAW_MEAN); +static fix16_t VocAlgorithm__mox_model__process(VocAlgorithmParams* params, + fix16_t sraw); +static void VocAlgorithm__sigmoid_scaled__init(VocAlgorithmParams* params); +static void +VocAlgorithm__sigmoid_scaled__set_parameters(VocAlgorithmParams* params, + fix16_t offset); +static fix16_t VocAlgorithm__sigmoid_scaled__process(VocAlgorithmParams* params, + fix16_t sample); +static void VocAlgorithm__adaptive_lowpass__init(VocAlgorithmParams* params); +static void +VocAlgorithm__adaptive_lowpass__set_parameters(VocAlgorithmParams* params); +static fix16_t +VocAlgorithm__adaptive_lowpass__process(VocAlgorithmParams* params, + fix16_t sample); + +void VocAlgorithm_init(VocAlgorithmParams* params) { + + params->mVoc_Index_Offset = F16(VocAlgorithm_VOC_INDEX_OFFSET_DEFAULT); + params->mTau_Mean_Variance_Hours = + F16(VocAlgorithm_TAU_MEAN_VARIANCE_HOURS); + params->mGating_Max_Duration_Minutes = + F16(VocAlgorithm_GATING_MAX_DURATION_MINUTES); + params->mSraw_Std_Initial = F16(VocAlgorithm_SRAW_STD_INITIAL); + params->mUptime = F16(0.); + params->mSraw = F16(0.); + params->mVoc_Index = 0; + VocAlgorithm__init_instances(params); +} + +static void VocAlgorithm__init_instances(VocAlgorithmParams* params) { + + VocAlgorithm__mean_variance_estimator__init(params); + VocAlgorithm__mean_variance_estimator__set_parameters( + params, params->mSraw_Std_Initial, params->mTau_Mean_Variance_Hours, + params->mGating_Max_Duration_Minutes); + VocAlgorithm__mox_model__init(params); + VocAlgorithm__mox_model__set_parameters( + params, VocAlgorithm__mean_variance_estimator__get_std(params), + VocAlgorithm__mean_variance_estimator__get_mean(params)); + VocAlgorithm__sigmoid_scaled__init(params); + VocAlgorithm__sigmoid_scaled__set_parameters(params, + params->mVoc_Index_Offset); + VocAlgorithm__adaptive_lowpass__init(params); + VocAlgorithm__adaptive_lowpass__set_parameters(params); +} + +void VocAlgorithm_get_states(VocAlgorithmParams* params, int32_t* state0, + int32_t* state1) { + + *state0 = VocAlgorithm__mean_variance_estimator__get_mean(params); + *state1 = VocAlgorithm__mean_variance_estimator__get_std(params); + return; +} + +void VocAlgorithm_set_states(VocAlgorithmParams* params, int32_t state0, + int32_t state1) { + + VocAlgorithm__mean_variance_estimator__set_states( + params, state0, state1, F16(VocAlgorithm_PERSISTENCE_UPTIME_GAMMA)); + params->mSraw = state0; +} + +void VocAlgorithm_set_tuning_parameters(VocAlgorithmParams* params, + int32_t voc_index_offset, + int32_t learning_time_hours, + int32_t gating_max_duration_minutes, + int32_t std_initial) { + + params->mVoc_Index_Offset = (fix16_from_int(voc_index_offset)); + params->mTau_Mean_Variance_Hours = (fix16_from_int(learning_time_hours)); + params->mGating_Max_Duration_Minutes = + (fix16_from_int(gating_max_duration_minutes)); + params->mSraw_Std_Initial = (fix16_from_int(std_initial)); + VocAlgorithm__init_instances(params); +} + +void VocAlgorithm_process(VocAlgorithmParams* params, int32_t sraw, + int32_t* voc_index) { + + if ((params->mUptime <= F16(VocAlgorithm_INITIAL_BLACKOUT))) { + params->mUptime = + (params->mUptime + F16(VocAlgorithm_SAMPLING_INTERVAL)); + } else { + if (((sraw > 0) && (sraw < 65000))) { + if ((sraw < 20001)) { + sraw = 20001; + } else if ((sraw > 52767)) { + sraw = 52767; + } + params->mSraw = (fix16_from_int((sraw - 20000))); + } + params->mVoc_Index = + VocAlgorithm__mox_model__process(params, params->mSraw); + params->mVoc_Index = + VocAlgorithm__sigmoid_scaled__process(params, params->mVoc_Index); + params->mVoc_Index = + VocAlgorithm__adaptive_lowpass__process(params, params->mVoc_Index); + if ((params->mVoc_Index < F16(0.5))) { + params->mVoc_Index = F16(0.5); + } + if ((params->mSraw > F16(0.))) { + VocAlgorithm__mean_variance_estimator__process( + params, params->mSraw, params->mVoc_Index); + VocAlgorithm__mox_model__set_parameters( + params, VocAlgorithm__mean_variance_estimator__get_std(params), + VocAlgorithm__mean_variance_estimator__get_mean(params)); + } + } + *voc_index = (fix16_cast_to_int((params->mVoc_Index + F16(0.5)))); + return; +} + +static void +VocAlgorithm__mean_variance_estimator__init(VocAlgorithmParams* params) { + + VocAlgorithm__mean_variance_estimator__set_parameters(params, F16(0.), + F16(0.), F16(0.)); + VocAlgorithm__mean_variance_estimator___init_instances(params); +} + +static void VocAlgorithm__mean_variance_estimator___init_instances( + VocAlgorithmParams* params) { + + VocAlgorithm__mean_variance_estimator___sigmoid__init(params); +} + +static void VocAlgorithm__mean_variance_estimator__set_parameters( + VocAlgorithmParams* params, fix16_t std_initial, + fix16_t tau_mean_variance_hours, fix16_t gating_max_duration_minutes) { + + params->m_Mean_Variance_Estimator__Gating_Max_Duration_Minutes = + gating_max_duration_minutes; + params->m_Mean_Variance_Estimator___Initialized = false; + params->m_Mean_Variance_Estimator___Mean = F16(0.); + params->m_Mean_Variance_Estimator___Sraw_Offset = F16(0.); + params->m_Mean_Variance_Estimator___Std = std_initial; + params->m_Mean_Variance_Estimator___Gamma = + (fix16_div(F16((VocAlgorithm_MEAN_VARIANCE_ESTIMATOR__GAMMA_SCALING * + (VocAlgorithm_SAMPLING_INTERVAL / 3600.))), + (tau_mean_variance_hours + + F16((VocAlgorithm_SAMPLING_INTERVAL / 3600.))))); + params->m_Mean_Variance_Estimator___Gamma_Initial_Mean = + F16(((VocAlgorithm_MEAN_VARIANCE_ESTIMATOR__GAMMA_SCALING * + VocAlgorithm_SAMPLING_INTERVAL) / + (VocAlgorithm_TAU_INITIAL_MEAN + VocAlgorithm_SAMPLING_INTERVAL))); + params->m_Mean_Variance_Estimator___Gamma_Initial_Variance = F16( + ((VocAlgorithm_MEAN_VARIANCE_ESTIMATOR__GAMMA_SCALING * + VocAlgorithm_SAMPLING_INTERVAL) / + (VocAlgorithm_TAU_INITIAL_VARIANCE + VocAlgorithm_SAMPLING_INTERVAL))); + params->m_Mean_Variance_Estimator__Gamma_Mean = F16(0.); + params->m_Mean_Variance_Estimator__Gamma_Variance = F16(0.); + params->m_Mean_Variance_Estimator___Uptime_Gamma = F16(0.); + params->m_Mean_Variance_Estimator___Uptime_Gating = F16(0.); + params->m_Mean_Variance_Estimator___Gating_Duration_Minutes = F16(0.); +} + +static void +VocAlgorithm__mean_variance_estimator__set_states(VocAlgorithmParams* params, + fix16_t mean, fix16_t std, + fix16_t uptime_gamma) { + + params->m_Mean_Variance_Estimator___Mean = mean; + params->m_Mean_Variance_Estimator___Std = std; + params->m_Mean_Variance_Estimator___Uptime_Gamma = uptime_gamma; + params->m_Mean_Variance_Estimator___Initialized = true; +} + +static fix16_t +VocAlgorithm__mean_variance_estimator__get_std(VocAlgorithmParams* params) { + + return params->m_Mean_Variance_Estimator___Std; +} + +static fix16_t +VocAlgorithm__mean_variance_estimator__get_mean(VocAlgorithmParams* params) { + + return (params->m_Mean_Variance_Estimator___Mean + + params->m_Mean_Variance_Estimator___Sraw_Offset); +} + +static void VocAlgorithm__mean_variance_estimator___calculate_gamma( + VocAlgorithmParams* params, fix16_t voc_index_from_prior) { + + fix16_t uptime_limit; + fix16_t sigmoid_gamma_mean; + fix16_t gamma_mean; + fix16_t gating_threshold_mean; + fix16_t sigmoid_gating_mean; + fix16_t sigmoid_gamma_variance; + fix16_t gamma_variance; + fix16_t gating_threshold_variance; + fix16_t sigmoid_gating_variance; + + uptime_limit = F16((VocAlgorithm_MEAN_VARIANCE_ESTIMATOR__FIX16_MAX - + VocAlgorithm_SAMPLING_INTERVAL)); + if ((params->m_Mean_Variance_Estimator___Uptime_Gamma < uptime_limit)) { + params->m_Mean_Variance_Estimator___Uptime_Gamma = + (params->m_Mean_Variance_Estimator___Uptime_Gamma + + F16(VocAlgorithm_SAMPLING_INTERVAL)); + } + if ((params->m_Mean_Variance_Estimator___Uptime_Gating < uptime_limit)) { + params->m_Mean_Variance_Estimator___Uptime_Gating = + (params->m_Mean_Variance_Estimator___Uptime_Gating + + F16(VocAlgorithm_SAMPLING_INTERVAL)); + } + VocAlgorithm__mean_variance_estimator___sigmoid__set_parameters( + params, F16(1.), F16(VocAlgorithm_INIT_DURATION_MEAN), + F16(VocAlgorithm_INIT_TRANSITION_MEAN)); + sigmoid_gamma_mean = + VocAlgorithm__mean_variance_estimator___sigmoid__process( + params, params->m_Mean_Variance_Estimator___Uptime_Gamma); + gamma_mean = + (params->m_Mean_Variance_Estimator___Gamma + + (fix16_mul((params->m_Mean_Variance_Estimator___Gamma_Initial_Mean - + params->m_Mean_Variance_Estimator___Gamma), + sigmoid_gamma_mean))); + gating_threshold_mean = + (F16(VocAlgorithm_GATING_THRESHOLD) + + (fix16_mul( + F16((VocAlgorithm_GATING_THRESHOLD_INITIAL - + VocAlgorithm_GATING_THRESHOLD)), + VocAlgorithm__mean_variance_estimator___sigmoid__process( + params, params->m_Mean_Variance_Estimator___Uptime_Gating)))); + VocAlgorithm__mean_variance_estimator___sigmoid__set_parameters( + params, F16(1.), gating_threshold_mean, + F16(VocAlgorithm_GATING_THRESHOLD_TRANSITION)); + sigmoid_gating_mean = + VocAlgorithm__mean_variance_estimator___sigmoid__process( + params, voc_index_from_prior); + params->m_Mean_Variance_Estimator__Gamma_Mean = + (fix16_mul(sigmoid_gating_mean, gamma_mean)); + VocAlgorithm__mean_variance_estimator___sigmoid__set_parameters( + params, F16(1.), F16(VocAlgorithm_INIT_DURATION_VARIANCE), + F16(VocAlgorithm_INIT_TRANSITION_VARIANCE)); + sigmoid_gamma_variance = + VocAlgorithm__mean_variance_estimator___sigmoid__process( + params, params->m_Mean_Variance_Estimator___Uptime_Gamma); + gamma_variance = + (params->m_Mean_Variance_Estimator___Gamma + + (fix16_mul( + (params->m_Mean_Variance_Estimator___Gamma_Initial_Variance - + params->m_Mean_Variance_Estimator___Gamma), + (sigmoid_gamma_variance - sigmoid_gamma_mean)))); + gating_threshold_variance = + (F16(VocAlgorithm_GATING_THRESHOLD) + + (fix16_mul( + F16((VocAlgorithm_GATING_THRESHOLD_INITIAL - + VocAlgorithm_GATING_THRESHOLD)), + VocAlgorithm__mean_variance_estimator___sigmoid__process( + params, params->m_Mean_Variance_Estimator___Uptime_Gating)))); + VocAlgorithm__mean_variance_estimator___sigmoid__set_parameters( + params, F16(1.), gating_threshold_variance, + F16(VocAlgorithm_GATING_THRESHOLD_TRANSITION)); + sigmoid_gating_variance = + VocAlgorithm__mean_variance_estimator___sigmoid__process( + params, voc_index_from_prior); + params->m_Mean_Variance_Estimator__Gamma_Variance = + (fix16_mul(sigmoid_gating_variance, gamma_variance)); + params->m_Mean_Variance_Estimator___Gating_Duration_Minutes = + (params->m_Mean_Variance_Estimator___Gating_Duration_Minutes + + (fix16_mul(F16((VocAlgorithm_SAMPLING_INTERVAL / 60.)), + ((fix16_mul((F16(1.) - sigmoid_gating_mean), + F16((1. + VocAlgorithm_GATING_MAX_RATIO)))) - + F16(VocAlgorithm_GATING_MAX_RATIO))))); + if ((params->m_Mean_Variance_Estimator___Gating_Duration_Minutes < + F16(0.))) { + params->m_Mean_Variance_Estimator___Gating_Duration_Minutes = F16(0.); + } + if ((params->m_Mean_Variance_Estimator___Gating_Duration_Minutes > + params->m_Mean_Variance_Estimator__Gating_Max_Duration_Minutes)) { + params->m_Mean_Variance_Estimator___Uptime_Gating = F16(0.); + } +} + +static void VocAlgorithm__mean_variance_estimator__process( + VocAlgorithmParams* params, fix16_t sraw, fix16_t voc_index_from_prior) { + + fix16_t delta_sgp; + fix16_t c; + fix16_t additional_scaling; + + if ((params->m_Mean_Variance_Estimator___Initialized == false)) { + params->m_Mean_Variance_Estimator___Initialized = true; + params->m_Mean_Variance_Estimator___Sraw_Offset = sraw; + params->m_Mean_Variance_Estimator___Mean = F16(0.); + } else { + if (((params->m_Mean_Variance_Estimator___Mean >= F16(100.)) || + (params->m_Mean_Variance_Estimator___Mean <= F16(-100.)))) { + params->m_Mean_Variance_Estimator___Sraw_Offset = + (params->m_Mean_Variance_Estimator___Sraw_Offset + + params->m_Mean_Variance_Estimator___Mean); + params->m_Mean_Variance_Estimator___Mean = F16(0.); + } + sraw = (sraw - params->m_Mean_Variance_Estimator___Sraw_Offset); + VocAlgorithm__mean_variance_estimator___calculate_gamma( + params, voc_index_from_prior); + delta_sgp = (fix16_div( + (sraw - params->m_Mean_Variance_Estimator___Mean), + F16(VocAlgorithm_MEAN_VARIANCE_ESTIMATOR__GAMMA_SCALING))); + if ((delta_sgp < F16(0.))) { + c = (params->m_Mean_Variance_Estimator___Std - delta_sgp); + } else { + c = (params->m_Mean_Variance_Estimator___Std + delta_sgp); + } + additional_scaling = F16(1.); + if ((c > F16(1440.))) { + additional_scaling = F16(4.); + } + params->m_Mean_Variance_Estimator___Std = (fix16_mul( + fix16_sqrt((fix16_mul( + additional_scaling, + (F16(VocAlgorithm_MEAN_VARIANCE_ESTIMATOR__GAMMA_SCALING) - + params->m_Mean_Variance_Estimator__Gamma_Variance)))), + fix16_sqrt(( + (fix16_mul( + params->m_Mean_Variance_Estimator___Std, + (fix16_div( + params->m_Mean_Variance_Estimator___Std, + (fix16_mul( + F16(VocAlgorithm_MEAN_VARIANCE_ESTIMATOR__GAMMA_SCALING), + additional_scaling)))))) + + (fix16_mul( + (fix16_div( + (fix16_mul( + params->m_Mean_Variance_Estimator__Gamma_Variance, + delta_sgp)), + additional_scaling)), + delta_sgp)))))); + params->m_Mean_Variance_Estimator___Mean = + (params->m_Mean_Variance_Estimator___Mean + + (fix16_mul(params->m_Mean_Variance_Estimator__Gamma_Mean, + delta_sgp))); + } +} + +static void VocAlgorithm__mean_variance_estimator___sigmoid__init( + VocAlgorithmParams* params) { + + VocAlgorithm__mean_variance_estimator___sigmoid__set_parameters( + params, F16(0.), F16(0.), F16(0.)); +} + +static void VocAlgorithm__mean_variance_estimator___sigmoid__set_parameters( + VocAlgorithmParams* params, fix16_t L, fix16_t X0, fix16_t K) { + + params->m_Mean_Variance_Estimator___Sigmoid__L = L; + params->m_Mean_Variance_Estimator___Sigmoid__K = K; + params->m_Mean_Variance_Estimator___Sigmoid__X0 = X0; +} + +static fix16_t VocAlgorithm__mean_variance_estimator___sigmoid__process( + VocAlgorithmParams* params, fix16_t sample) { + + fix16_t x; + + x = (fix16_mul(params->m_Mean_Variance_Estimator___Sigmoid__K, + (sample - params->m_Mean_Variance_Estimator___Sigmoid__X0))); + if ((x < F16(-50.))) { + return params->m_Mean_Variance_Estimator___Sigmoid__L; + } else if ((x > F16(50.))) { + return F16(0.); + } else { + return (fix16_div(params->m_Mean_Variance_Estimator___Sigmoid__L, + (F16(1.) + fix16_exp(x)))); + } +} + +static void VocAlgorithm__mox_model__init(VocAlgorithmParams* params) { + + VocAlgorithm__mox_model__set_parameters(params, F16(1.), F16(0.)); +} + +static void VocAlgorithm__mox_model__set_parameters(VocAlgorithmParams* params, + fix16_t SRAW_STD, + fix16_t SRAW_MEAN) { + + params->m_Mox_Model__Sraw_Std = SRAW_STD; + params->m_Mox_Model__Sraw_Mean = SRAW_MEAN; +} + +static fix16_t VocAlgorithm__mox_model__process(VocAlgorithmParams* params, + fix16_t sraw) { + + return (fix16_mul((fix16_div((sraw - params->m_Mox_Model__Sraw_Mean), + (-(params->m_Mox_Model__Sraw_Std + + F16(VocAlgorithm_SRAW_STD_BONUS))))), + F16(VocAlgorithm_VOC_INDEX_GAIN))); +} + +static void VocAlgorithm__sigmoid_scaled__init(VocAlgorithmParams* params) { + + VocAlgorithm__sigmoid_scaled__set_parameters(params, F16(0.)); +} + +static void +VocAlgorithm__sigmoid_scaled__set_parameters(VocAlgorithmParams* params, + fix16_t offset) { + + params->m_Sigmoid_Scaled__Offset = offset; +} + +static fix16_t VocAlgorithm__sigmoid_scaled__process(VocAlgorithmParams* params, + fix16_t sample) { + + fix16_t x; + fix16_t shift; + + x = (fix16_mul(F16(VocAlgorithm_SIGMOID_K), + (sample - F16(VocAlgorithm_SIGMOID_X0)))); + if ((x < F16(-50.))) { + return F16(VocAlgorithm_SIGMOID_L); + } else if ((x > F16(50.))) { + return F16(0.); + } else { + if ((sample >= F16(0.))) { + shift = (fix16_div( + (F16(VocAlgorithm_SIGMOID_L) - + (fix16_mul(F16(5.), params->m_Sigmoid_Scaled__Offset))), + F16(4.))); + return ((fix16_div((F16(VocAlgorithm_SIGMOID_L) + shift), + (F16(1.) + fix16_exp(x)))) - + shift); + } else { + return (fix16_mul( + (fix16_div(params->m_Sigmoid_Scaled__Offset, + F16(VocAlgorithm_VOC_INDEX_OFFSET_DEFAULT))), + (fix16_div(F16(VocAlgorithm_SIGMOID_L), + (F16(1.) + fix16_exp(x)))))); + } + } +} + +static void VocAlgorithm__adaptive_lowpass__init(VocAlgorithmParams* params) { + + VocAlgorithm__adaptive_lowpass__set_parameters(params); +} + +static void +VocAlgorithm__adaptive_lowpass__set_parameters(VocAlgorithmParams* params) { + + params->m_Adaptive_Lowpass__A1 = + F16((VocAlgorithm_SAMPLING_INTERVAL / + (VocAlgorithm_LP_TAU_FAST + VocAlgorithm_SAMPLING_INTERVAL))); + params->m_Adaptive_Lowpass__A2 = + F16((VocAlgorithm_SAMPLING_INTERVAL / + (VocAlgorithm_LP_TAU_SLOW + VocAlgorithm_SAMPLING_INTERVAL))); + params->m_Adaptive_Lowpass___Initialized = false; +} + +static fix16_t +VocAlgorithm__adaptive_lowpass__process(VocAlgorithmParams* params, + fix16_t sample) { + + fix16_t abs_delta; + fix16_t F1; + fix16_t tau_a; + fix16_t a3; + + if ((params->m_Adaptive_Lowpass___Initialized == false)) { + params->m_Adaptive_Lowpass___X1 = sample; + params->m_Adaptive_Lowpass___X2 = sample; + params->m_Adaptive_Lowpass___X3 = sample; + params->m_Adaptive_Lowpass___Initialized = true; + } + params->m_Adaptive_Lowpass___X1 = + ((fix16_mul((F16(1.) - params->m_Adaptive_Lowpass__A1), + params->m_Adaptive_Lowpass___X1)) + + (fix16_mul(params->m_Adaptive_Lowpass__A1, sample))); + params->m_Adaptive_Lowpass___X2 = + ((fix16_mul((F16(1.) - params->m_Adaptive_Lowpass__A2), + params->m_Adaptive_Lowpass___X2)) + + (fix16_mul(params->m_Adaptive_Lowpass__A2, sample))); + abs_delta = + (params->m_Adaptive_Lowpass___X1 - params->m_Adaptive_Lowpass___X2); + if ((abs_delta < F16(0.))) { + abs_delta = (-abs_delta); + } + F1 = fix16_exp((fix16_mul(F16(VocAlgorithm_LP_ALPHA), abs_delta))); + tau_a = + ((fix16_mul(F16((VocAlgorithm_LP_TAU_SLOW - VocAlgorithm_LP_TAU_FAST)), + F1)) + + F16(VocAlgorithm_LP_TAU_FAST)); + a3 = (fix16_div(F16(VocAlgorithm_SAMPLING_INTERVAL), + (F16(VocAlgorithm_SAMPLING_INTERVAL) + tau_a))); + params->m_Adaptive_Lowpass___X3 = + ((fix16_mul((F16(1.) - a3), params->m_Adaptive_Lowpass___X3)) + + (fix16_mul(a3, sample))); + return params->m_Adaptive_Lowpass___X3; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/sgp40/sensirion_voc_algorithm.h Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,202 @@ +/* + * Copyright (c) 2020, Sensirion AG + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * * Neither the name of Sensirion AG nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file sensirion_voc_algorithm.h + * @defgroup sgp40 sgp40 + * @{ + * + * Library for calculating concentration of volatile organic compounds + */ + +#ifndef VOCALGORITHM_H_ +#define VOCALGORITHM_H_ + +#include <stdlib.h> +#include <stdint.h> +#include <stdbool.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/* The fixed point arithmetic parts of this code were originally created by + * https://github.com/PetteriAimonen/libfixmath + */ + +typedef int32_t fix16_t; + +#define F16(x) \ + ((fix16_t)(((x) >= 0) ? ((x)*65536.0 + 0.5) : ((x)*65536.0 - 0.5))) + +#define VocAlgorithm_SAMPLING_INTERVAL (1.) +#define VocAlgorithm_INITIAL_BLACKOUT (45.) +#define VocAlgorithm_VOC_INDEX_GAIN (230.) +#define VocAlgorithm_SRAW_STD_INITIAL (50.) +#define VocAlgorithm_SRAW_STD_BONUS (220.) +#define VocAlgorithm_TAU_MEAN_VARIANCE_HOURS (12.) +#define VocAlgorithm_TAU_INITIAL_MEAN (20.) +#define VocAlgorithm_INIT_DURATION_MEAN ((3600. * 0.75)) +#define VocAlgorithm_INIT_TRANSITION_MEAN (0.01) +#define VocAlgorithm_TAU_INITIAL_VARIANCE (2500.) +#define VocAlgorithm_INIT_DURATION_VARIANCE ((3600. * 1.45)) +#define VocAlgorithm_INIT_TRANSITION_VARIANCE (0.01) +#define VocAlgorithm_GATING_THRESHOLD (340.) +#define VocAlgorithm_GATING_THRESHOLD_INITIAL (510.) +#define VocAlgorithm_GATING_THRESHOLD_TRANSITION (0.09) +#define VocAlgorithm_GATING_MAX_DURATION_MINUTES ((60. * 3.)) +#define VocAlgorithm_GATING_MAX_RATIO (0.3) +#define VocAlgorithm_SIGMOID_L (500.) +#define VocAlgorithm_SIGMOID_K (-0.0065) +#define VocAlgorithm_SIGMOID_X0 (213.) +#define VocAlgorithm_VOC_INDEX_OFFSET_DEFAULT (100.) +#define VocAlgorithm_LP_TAU_FAST (20.0) +#define VocAlgorithm_LP_TAU_SLOW (500.0) +#define VocAlgorithm_LP_ALPHA (-0.2) +#define VocAlgorithm_PERSISTENCE_UPTIME_GAMMA ((3. * 3600.)) +#define VocAlgorithm_MEAN_VARIANCE_ESTIMATOR__GAMMA_SCALING (64.) +#define VocAlgorithm_MEAN_VARIANCE_ESTIMATOR__FIX16_MAX (32767.) + +/** + * Struct to hold all the states of the VOC algorithm. + */ +typedef struct { + fix16_t mVoc_Index_Offset; + fix16_t mTau_Mean_Variance_Hours; + fix16_t mGating_Max_Duration_Minutes; + fix16_t mSraw_Std_Initial; + fix16_t mUptime; + fix16_t mSraw; + fix16_t mVoc_Index; + fix16_t m_Mean_Variance_Estimator__Gating_Max_Duration_Minutes; + bool m_Mean_Variance_Estimator___Initialized; + fix16_t m_Mean_Variance_Estimator___Mean; + fix16_t m_Mean_Variance_Estimator___Sraw_Offset; + fix16_t m_Mean_Variance_Estimator___Std; + fix16_t m_Mean_Variance_Estimator___Gamma; + fix16_t m_Mean_Variance_Estimator___Gamma_Initial_Mean; + fix16_t m_Mean_Variance_Estimator___Gamma_Initial_Variance; + fix16_t m_Mean_Variance_Estimator__Gamma_Mean; + fix16_t m_Mean_Variance_Estimator__Gamma_Variance; + fix16_t m_Mean_Variance_Estimator___Uptime_Gamma; + fix16_t m_Mean_Variance_Estimator___Uptime_Gating; + fix16_t m_Mean_Variance_Estimator___Gating_Duration_Minutes; + fix16_t m_Mean_Variance_Estimator___Sigmoid__L; + fix16_t m_Mean_Variance_Estimator___Sigmoid__K; + fix16_t m_Mean_Variance_Estimator___Sigmoid__X0; + fix16_t m_Mox_Model__Sraw_Std; + fix16_t m_Mox_Model__Sraw_Mean; + fix16_t m_Sigmoid_Scaled__Offset; + fix16_t m_Adaptive_Lowpass__A1; + fix16_t m_Adaptive_Lowpass__A2; + bool m_Adaptive_Lowpass___Initialized; + fix16_t m_Adaptive_Lowpass___X1; + fix16_t m_Adaptive_Lowpass___X2; + fix16_t m_Adaptive_Lowpass___X3; +} VocAlgorithmParams; + +/** + * Initialize the VOC algorithm parameters. Call this once at the beginning or + * whenever the sensor stopped measurements. + * @param params Pointer to the VocAlgorithmParams struct + */ +void VocAlgorithm_init(VocAlgorithmParams *params); + +/** + * Get current algorithm states. Retrieved values can be used in + * VocAlgorithm_set_states() to resume operation after a short interruption, + * skipping initial learning phase. This feature can only be used after at least + * 3 hours of continuous operation. + * @param params Pointer to the VocAlgorithmParams struct + * @param state0 State0 to be stored + * @param state1 State1 to be stored + */ +void VocAlgorithm_get_states(VocAlgorithmParams *params, int32_t *state0, + int32_t *state1); + +/** + * Set previously retrieved algorithm states to resume operation after a short + * interruption, skipping initial learning phase. This feature should not be + * used after inerruptions of more than 10 minutes. Call this once after + * VocAlgorithm_init() and the optional VocAlgorithm_set_tuning_parameters(), if + * desired. Otherwise, the algorithm will start with initial learning phase. + * @param params Pointer to the VocAlgorithmParams struct + * @param state0 State0 to be restored + * @param state1 State1 to be restored + */ +void VocAlgorithm_set_states(VocAlgorithmParams *params, int32_t state0, + int32_t state1); + +/** + * Set parameters to customize the VOC algorithm. Call this once after + * VocAlgorithm_init(), if desired. Otherwise, the default values will be used. + * + * @param params Pointer to the VocAlgorithmParams struct + * @param voc_index_offset VOC index representing typical (average) + * conditions. Range 1..250, default 100 + * @param learning_time_hours Time constant of long-term estimator. + * Past events will be forgotten after about + * twice the learning time. + * Range 1..72 [hours], default 12 [hours] + * @param gating_max_duration_minutes Maximum duration of gating (freeze of + * estimator during high VOC index signal). + * 0 (no gating) or range 1..720 [minutes], + * default 180 [minutes] + * @param std_initial Initial estimate for standard deviation. + * Lower value boosts events during initial + * learning period, but may result in larger + * device-to-device variations. + * Range 10..500, default 50 + */ +void VocAlgorithm_set_tuning_parameters(VocAlgorithmParams *params, + int32_t voc_index_offset, + int32_t learning_time_hours, + int32_t gating_max_duration_minutes, + int32_t std_initial); + +/** + * Calculate the VOC index value from the raw sensor value. + * + * @param params Pointer to the VocAlgorithmParams struct + * @param sraw Raw value from the SGP40 sensor + * @param voc_index Calculated VOC index value from the raw sensor value. Zero + * during initial blackout period and 1..500 afterwards + */ +void VocAlgorithm_process(VocAlgorithmParams *params, int32_t sraw, + int32_t *voc_index); + +#ifdef __cplusplus +} +#endif + +/**@}*/ + +#endif /* VOCALGORITHM_H_ */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/sgp40/sgp40.c Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,229 @@ +/** + * @file sgp40.c + * + * ESP-IDF driver for SGP40 Indoor Air Quality Sensor for VOC Measurements + * + * Copyright (c) 2020 Ruslan V. Uss <unclerus@gmail.com> + * + * BSD Licensed as described in the file LICENSE + */ +#include <esp_err.h> +#include <esp_idf_lib_helpers.h> +#include <esp_log.h> +#include "sgp40.h" +#include <math.h> +#include <freertos/FreeRTOS.h> +#include <freertos/task.h> +#include <ets_sys.h> + +#define I2C_FREQ_HZ 400000 + +static const char *TAG = "sgp40"; + +#define CMD_SOFT_RESET 0x0006 +#define CMD_FEATURESET 0x202f +#define CMD_MEASURE_RAW 0x260f +#define CMD_SELF_TEST 0x280e +#define CMD_SERIAL 0x3682 +#define CMD_HEATER_OFF 0x3615 + +#define TIME_SOFT_RESET 10 +#define TIME_FEATURESET 10 +#define TIME_MEASURE_RAW 30 +#define TIME_SELF_TEST 250 +#define TIME_HEATER_OFF 10 +#define TIME_SERIAL 10 + +#define SELF_TEST_OK 0xd400 + +#define CHECK(x) do { esp_err_t __; if ((__ = x) != ESP_OK) return __; } while (0) +#define CHECK_ARG(ARG) do { if (!(ARG)) return ESP_ERR_INVALID_ARG; } while (0) + +static uint8_t crc8(const uint8_t *data, size_t count) +{ + uint8_t res = 0xff; + + for (size_t i = 0; i < count; ++i) + { + res ^= data[i]; + for (uint8_t bit = 8; bit > 0; --bit) + { + if (res & 0x80) + res = (res << 1) ^ 0x31; + else + res = (res << 1); + } + } + return res; +} + +static inline uint16_t swap(uint16_t v) +{ + return (v << 8) | (v >> 8); +} + +static esp_err_t send_cmd(i2c_dev_t *dev, uint16_t cmd, uint16_t *data, size_t words) +{ + uint8_t buf[2 + words * 3]; + // add command + *(uint16_t *)buf = swap(cmd); + if (data && words) + // add arguments + for (size_t i = 0; i < words; i++) + { + uint8_t *p = buf + 2 + i * 3; + *(uint16_t *)p = swap(data[i]); + *(p + 2) = crc8(p, 2); + } + + ESP_LOGV(TAG, "Sending buffer:"); + ESP_LOG_BUFFER_HEX_LEVEL(TAG, buf, sizeof(buf), ESP_LOG_VERBOSE); + + return i2c_dev_write(dev, NULL, 0, buf, sizeof(buf)); +} + +static esp_err_t read_resp(i2c_dev_t *dev, uint16_t *data, size_t words) +{ + uint8_t buf[words * 3]; + CHECK(i2c_dev_read(dev, NULL, 0, buf, sizeof(buf))); + + ESP_LOGV(TAG, "Received buffer:"); + ESP_LOG_BUFFER_HEX_LEVEL(TAG, buf, sizeof(buf), ESP_LOG_VERBOSE); + + for (size_t i = 0; i < words; i++) + { + uint8_t *p = buf + i * 3; + uint8_t crc = crc8(p, 2); + if (crc != *(p + 2)) + { + ESP_LOGE(TAG, "Invalid CRC 0x%02x, expected 0x%02x", crc, *(p + 2)); + return ESP_ERR_INVALID_CRC; + } + data[i] = swap(*(uint16_t *)p); + } + return ESP_OK; +} + +static esp_err_t execute_cmd(sgp40_t *dev, uint16_t cmd, uint32_t timeout_ms, + uint16_t *out_data, size_t out_words, uint16_t *in_data, size_t in_words) +{ + CHECK_ARG(dev); + + I2C_DEV_TAKE_MUTEX(&dev->i2c_dev); + I2C_DEV_CHECK(&dev->i2c_dev, send_cmd(&dev->i2c_dev, cmd, out_data, out_words)); + if (timeout_ms) + { + if (timeout_ms > 10) + vTaskDelay(pdMS_TO_TICKS(timeout_ms)); + else + ets_delay_us(timeout_ms * 1000); + } + if (in_data && in_words) + I2C_DEV_CHECK(&dev->i2c_dev, read_resp(&dev->i2c_dev, in_data, in_words)); + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); + + return ESP_OK; +} + +//////////////////////////////////////////////////////////////////////////////// + +esp_err_t sgp40_init_desc(sgp40_t *dev, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio) +{ + CHECK_ARG(dev); + + dev->i2c_dev.port = port; + dev->i2c_dev.addr = SGP40_ADDR; + dev->i2c_dev.cfg.sda_io_num = sda_gpio; + dev->i2c_dev.cfg.scl_io_num = scl_gpio; +#if HELPER_TARGET_IS_ESP32 + dev->i2c_dev.cfg.master.clk_speed = I2C_FREQ_HZ; +#endif + return i2c_dev_create_mutex(&dev->i2c_dev); +} + +esp_err_t sgp40_free_desc(sgp40_t *dev) +{ + CHECK_ARG(dev); + + return i2c_dev_delete_mutex(&dev->i2c_dev); +} + +esp_err_t sgp40_init(sgp40_t *dev) +{ + CHECK_ARG(dev); + + CHECK(execute_cmd(dev, CMD_SERIAL, TIME_SERIAL, NULL, 0, dev->serial, 3)); + CHECK(execute_cmd(dev, CMD_FEATURESET, TIME_FEATURESET, NULL, 0, &dev->featureset, 1)); + + ESP_LOGD(TAG, "Device found. S/N: 0x%04x%04x%04x, featureset 0x%04x", + dev->serial[0], dev->serial[1], dev->serial[2], dev->featureset); + + VocAlgorithm_init(&dev->voc); + + return ESP_OK; +} + +esp_err_t sgp40_soft_reset(sgp40_t *dev) +{ + CHECK_ARG(dev); + + return execute_cmd(dev, CMD_SOFT_RESET, TIME_SOFT_RESET, NULL, 0, NULL, 0); +} + +esp_err_t sgp40_self_test(sgp40_t *dev) +{ + CHECK_ARG(dev); + + uint16_t res; + CHECK(execute_cmd(dev, CMD_SELF_TEST, TIME_SELF_TEST, NULL, 0, &res, 1)); + + return res == SELF_TEST_OK ? ESP_OK : ESP_FAIL; +} + +esp_err_t sgp40_heater_off(sgp40_t *dev) +{ + CHECK_ARG(dev); + + return execute_cmd(dev, CMD_HEATER_OFF, TIME_HEATER_OFF, NULL, 0, NULL, 0); +} + +esp_err_t sgp40_measure_raw(sgp40_t *dev, float humidity, float temperature, uint16_t *raw) +{ + CHECK_ARG(dev && raw); + + uint16_t params[2]; + if (isnan(humidity) || isnan(temperature)) + { + params[0] = 0x8000; + params[1] = 0x6666; + ESP_LOGW(TAG, "Uncompensated measurement"); + } + else + { + if (humidity < 0) + humidity = 0; + else if (humidity > 100) + humidity = 100; + + if (temperature < -45) + temperature = -45; + else if (temperature > 129.76) + temperature = 129.76; + + params[0] = (uint16_t)(humidity / 100.0 * 65536); + params[1] = (uint16_t)((temperature + 45) / 175.0 * 65535); + } + + return execute_cmd(dev, CMD_MEASURE_RAW, TIME_MEASURE_RAW, params, 2, raw, 1); +} + +esp_err_t sgp40_measure_voc(sgp40_t *dev, float humidity, float temperature, int32_t *voc_index) +{ + CHECK_ARG(dev && voc_index); + + uint16_t raw; + CHECK(sgp40_measure_raw(dev, humidity, temperature, &raw)); + VocAlgorithm_process(&dev->voc, raw, voc_index); + + return ESP_OK; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/sgp40/sgp40.h Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,122 @@ +/** + * @file sgp40.h + * @defgroup sgp40 sgp40 + * @{ + * + * ESP-IDF driver for SGP40 Indoor Air Quality Sensor for VOC Measurements + * + * Copyright (c) 2020 Ruslan V. Uss <unclerus@gmail.com> + * + * BSD Licensed as described in the file LICENSE + */ +#ifndef __SGP40_H__ +#define __SGP40_H__ + +#include <stdbool.h> +#include <time.h> +#include <i2cdev.h> +#include <esp_err.h> +#include "sensirion_voc_algorithm.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define SGP40_ADDR 0x59 //!< I2C address + +/** + * Device descriptor + */ +typedef struct +{ + i2c_dev_t i2c_dev; + uint16_t serial[3]; + uint16_t featureset; + VocAlgorithmParams voc; +} sgp40_t; + +/** + * @brief Initialize device descriptor + * + * @param dev Device descriptor + * @param port I2C port + * @param sda_gpio SDA GPIO + * @param scl_gpio SCL GPIO + * @return `ESP_OK` on success + */ +esp_err_t sgp40_init_desc(sgp40_t *dev, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio); + +/** + * @brief Free device descriptor + * + * @param dev Device descriptor + * @return `ESP_OK` on success + */ +esp_err_t sgp40_free_desc(sgp40_t *dev); + +/** + * @brief Read device information, initialize the VOC algorithm + * + * @param dev Device descriptor + * @return `ESP_OK` on success + */ +esp_err_t sgp40_init(sgp40_t *dev); + +/** + * @brief Reset device, than put it to idle mode + * + * @param dev Device descriptor + * @return `ESP_OK` on success + */ +esp_err_t sgp40_soft_reset(sgp40_t *dev); + +/** + * @brief Perform a self-test + * + * @param dev Device descriptor + * @return `ESP_OK` on success + */ +esp_err_t sgp40_self_test(sgp40_t *dev); + +/** + * @brief Turn hotplate off, stop measurement and put device to idle mode + * + * @param dev Device descriptor + * @return `ESP_OK` on success + */ +esp_err_t sgp40_heater_off(sgp40_t *dev); + +/** + * @brief Perform a measurement + * + * @param dev Device descriptor + * @param humidity Relative humidity, percents. Use NaN if + * you want uncompensated measurement + * @param temperature Temperature, degrees Celsius. Use NaN if + * you want uncompensated measurement + * @param[out] raw Raw value, proportional to the logarithm + * of the resistance of the sensing element + * @return `ESP_OK` on success + */ +esp_err_t sgp40_measure_raw(sgp40_t *dev, float humidity, float temperature, uint16_t *raw); + +/** + * @brief Perform a measurement and update VOC index + * + * @param dev Device descriptor + * @param humidity Relative humidity, percents. Use NaN if + * you want uncompensated measurement + * @param temperature Temperature, degrees Celsius. Use NaN if + * you want uncompensated measurement + * @param[out] voc_index Calculated VOC index + * @return + */ +esp_err_t sgp40_measure_voc(sgp40_t *dev, float humidity, float temperature, int32_t *voc_index); + +#ifdef __cplusplus +} +#endif + +/**@}*/ + +#endif /* __SGP40_H__ */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/sht3x/.eil.yml Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,27 @@ +--- +components: + - name: sht3x + description: Driver for Sensirion SHT30/SHT31/SHT35 digital temperature and humidity sensor + group: temperature + groups: + - name: humidity + code_owners: UncleRus + depends: + - i2cdev + - log + - esp_idf_lib_helpers + thread_safe: yes + targets: + - name: esp32 + - name: esp8266 + - name: esp32s2 + - name: esp32c3 + licenses: + - name: BSD-3 + copyrights: + - author: + name: UncleRus + year: 2016 + - author: + name: gschorcht + year: 2017
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/sht3x/CMakeLists.txt Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,11 @@ +if(${IDF_VERSION_MAJOR} STREQUAL 4 AND ${IDF_VERSION_MINOR} STREQUAL 1 AND ${IDF_VERSION_PATCH} STREQUAL 3) + set(req i2cdev log esp_idf_lib_helpers) +else() + set(req i2cdev log esp_idf_lib_helpers esp_timer) +endif() + +idf_component_register( + SRCS sht3x.c + INCLUDE_DIRS . + REQUIRES ${req} +)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/sht3x/LICENSE Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,27 @@ +Copyright (c) 2017 Gunar Schorcht (https://github.com/gschorcht) +Copyright (c) 2019 Ruslan V. Uss (https://github.com/UncleRus) + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, +this list of conditions and the following disclaimer in the documentation +and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of itscontributors +may be used to endorse or promote products derived from this software without +specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/sht3x/README.md Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,4 @@ +# Driver for SHT3x digital temperature and humidity sensor + +This driver is fork of [original SHT3x driver](https://github.com/gschorcht/sht3x-esp-idf) +by Gunar Schorcht (@gschorcht), made to be compatible with the i2cdev library.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/sht3x/component.mk Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,2 @@ +COMPONENT_ADD_INCLUDEDIRS = . +COMPONENT_DEPENDS = i2cdev log esp_idf_lib_helpers
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/sht3x/sht3x.c Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,324 @@ +/* + * Copyright (c) 2017 Gunar Schorcht <https://github.com/gschorcht> + * Copyright (c) 2019 Ruslan V. Uss <unclerus@gmail.com> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of itscontributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file sht3x.c + * + * ESP-IDF driver for Sensirion SHT3x digital temperature and humidity sensor + * + * Forked from <https://github.com/gschorcht/sht3x-esp-idf> + * + * Copyright (c) 2017 Gunar Schorcht <https://github.com/gschorcht>\n + * Copyright (c) 2019 Ruslan V. Uss <unclerus@gmail.com> + * + * BSD Licensed as described in the file LICENSE + */ + +#include <esp_log.h> +#include <freertos/FreeRTOS.h> +#include <freertos/task.h> +#include <esp_timer.h> +#include <esp_idf_lib_helpers.h> +#include "sht3x.h" + +#define I2C_FREQ_HZ 1000000 // 1MHz + +const char *TAG = "sht3x"; + +#define SHT3X_STATUS_CMD 0xF32D +#define SHT3X_CLEAR_STATUS_CMD 0x3041 +#define SHT3X_RESET_CMD 0x30A2 +#define SHT3X_FETCH_DATA_CMD 0xE000 +#define SHT3X_STOP_PERIODIC_MEAS_CMD 0x3093 +#define SHT3X_HEATER_ON_CMD 0x306D +#define SHT3X_HEATER_OFF_CMD 0x3066 + +static const uint16_t SHT3X_MEASURE_CMD[6][3] = { + {0x2400, 0x240b, 0x2416}, // [SINGLE_SHOT][H,M,L] without clock stretching + {0x2032, 0x2024, 0x202f}, // [PERIODIC_05][H,M,L] + {0x2130, 0x2126, 0x212d}, // [PERIODIC_1 ][H,M,L] + {0x2236, 0x2220, 0x222b}, // [PERIODIC_2 ][H,M,L] + {0x2334, 0x2322, 0x2329}, // [PERIODIC_4 ][H,M,L] + {0x2737, 0x2721, 0x272a} // [PERIODIC_10][H,M,L] +}; + +// due to the fact that ticks can be smaller than portTICK_PERIOD_MS, one and +// a half tick period added to the duration to be sure that waiting time for +// the results is long enough +#define TIME_TO_TICKS(ms) (1 + ((ms) + (portTICK_PERIOD_MS-1) + portTICK_PERIOD_MS/2 ) / portTICK_PERIOD_MS) + +#define SHT3X_MEAS_DURATION_REP_HIGH 15 +#define SHT3X_MEAS_DURATION_REP_MEDIUM 6 +#define SHT3X_MEAS_DURATION_REP_LOW 4 + +// measurement durations in us +static const uint16_t SHT3X_MEAS_DURATION_US[3] = { + SHT3X_MEAS_DURATION_REP_HIGH * 1000, + SHT3X_MEAS_DURATION_REP_MEDIUM * 1000, + SHT3X_MEAS_DURATION_REP_LOW * 1000 +}; + +// measurement durations in RTOS ticks +static const uint8_t SHT3X_MEAS_DURATION_TICKS[3] = { + TIME_TO_TICKS(SHT3X_MEAS_DURATION_REP_HIGH), + TIME_TO_TICKS(SHT3X_MEAS_DURATION_REP_MEDIUM), + TIME_TO_TICKS(SHT3X_MEAS_DURATION_REP_LOW) +}; + +#define CHECK(x) do { esp_err_t __; if ((__ = x) != ESP_OK) return __; } while (0) +#define CHECK_ARG(VAL) do { if (!(VAL)) return ESP_ERR_INVALID_ARG; } while (0) + +#define G_POLYNOM 0x31 + +static inline uint16_t shuffle(uint16_t val) +{ + return (val >> 8) | (val << 8); +} + +static uint8_t crc8(uint8_t data[], int len) +{ + // initialization value + uint8_t crc = 0xff; + + // iterate over all bytes + for (int i = 0; i < len; i++) + { + crc ^= data[i]; + for (int i = 0; i < 8; i++) + { + bool xor = crc & 0x80; + crc = crc << 1; + crc = xor ? crc ^ G_POLYNOM : crc; + } + } + return crc; +} + +static esp_err_t send_cmd_nolock(sht3x_t *dev, uint16_t cmd) +{ + cmd = shuffle(cmd); + + return i2c_dev_write(&dev->i2c_dev, NULL, 0, &cmd, 2); +} + +static esp_err_t send_cmd(sht3x_t *dev, uint16_t cmd) +{ + I2C_DEV_TAKE_MUTEX(&dev->i2c_dev); + I2C_DEV_CHECK(&dev->i2c_dev, send_cmd_nolock(dev, cmd)); + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); + + return ESP_OK; +} + +static esp_err_t start_nolock(sht3x_t *dev, sht3x_mode_t mode, sht3x_repeat_t repeat) +{ + dev->mode = mode; + dev->repeatability = repeat; + CHECK(send_cmd_nolock(dev, SHT3X_MEASURE_CMD[mode][repeat])); + dev->meas_start_time = esp_timer_get_time(); + dev->meas_started = true; + dev->meas_first = true; + + return ESP_OK; +} + +static inline bool is_measuring(sht3x_t *dev) +{ + // not running if measurement is not started at all or + // it is not the first measurement in periodic mode + if (!dev->meas_started || !dev->meas_first) + return false; + + // not running if time elapsed is greater than duration + uint64_t elapsed = esp_timer_get_time() - dev->meas_start_time; + + return elapsed < SHT3X_MEAS_DURATION_US[dev->repeatability]; +} + +static esp_err_t get_raw_data_nolock(sht3x_t *dev, sht3x_raw_data_t raw_data) +{ + if (!dev->meas_started) + { + ESP_LOGE(TAG, "Measurement is not started"); + return ESP_ERR_INVALID_STATE; + } + if (is_measuring(dev)) + { + ESP_LOGE(TAG, "Measurement is still running"); + return ESP_ERR_INVALID_STATE; + } + + // read raw data + uint16_t cmd = shuffle(SHT3X_FETCH_DATA_CMD); + CHECK(i2c_dev_read(&dev->i2c_dev, &cmd, 2, raw_data, sizeof(sht3x_raw_data_t))); + + // reset first measurement flag + dev->meas_first = false; + + // reset measurement started flag in single shot mode + if (dev->mode == SHT3X_SINGLE_SHOT) + dev->meas_started = false; + + // check temperature crc + if (crc8(raw_data, 2) != raw_data[2]) + { + ESP_LOGE(TAG, "CRC check for temperature data failed"); + return ESP_ERR_INVALID_CRC; + } + + // check humidity crc + if (crc8(raw_data + 3, 2) != raw_data[5]) + { + ESP_LOGE(TAG, "CRC check for humidity data failed"); + return ESP_ERR_INVALID_CRC; + } + + return ESP_OK; +} + +/////////////////////////////////////////////////////////////////////////////// + +esp_err_t sht3x_init_desc(sht3x_t *dev, uint8_t addr, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio) +{ + CHECK_ARG(dev); + + dev->i2c_dev.port = port; + dev->i2c_dev.addr = addr; + dev->i2c_dev.cfg.sda_io_num = sda_gpio; + dev->i2c_dev.cfg.scl_io_num = scl_gpio; +#if HELPER_TARGET_IS_ESP32 + dev->i2c_dev.cfg.master.clk_speed = I2C_FREQ_HZ; +#endif + + return i2c_dev_create_mutex(&dev->i2c_dev); +} + +esp_err_t sht3x_free_desc(sht3x_t *dev) +{ + CHECK_ARG(dev); + + return i2c_dev_delete_mutex(&dev->i2c_dev); +} + +esp_err_t sht3x_init(sht3x_t *dev) +{ + CHECK_ARG(dev); + + dev->mode = SHT3X_SINGLE_SHOT; + dev->meas_start_time = 0; + dev->meas_started = false; + dev->meas_first = false; + + return send_cmd(dev, SHT3X_CLEAR_STATUS_CMD); +} + +esp_err_t sht3x_set_heater(sht3x_t *dev, bool enable) +{ + CHECK_ARG(dev); + + return send_cmd(dev, enable ? SHT3X_HEATER_ON_CMD : SHT3X_HEATER_OFF_CMD); +} + +esp_err_t sht3x_compute_values(sht3x_raw_data_t raw_data, float *temperature, float *humidity) +{ + CHECK_ARG(raw_data && (temperature || humidity)); + + if (temperature) + *temperature = ((((raw_data[0] * 256.0) + raw_data[1]) * 175) / 65535.0) - 45; + + if (humidity) + *humidity = ((((raw_data[3] * 256.0) + raw_data[4]) * 100) / 65535.0); + + return ESP_OK; +} + +esp_err_t sht3x_measure(sht3x_t *dev, float *temperature, float *humidity) +{ + CHECK_ARG(dev && (temperature || humidity)); + + sht3x_raw_data_t raw_data; + + I2C_DEV_TAKE_MUTEX(&dev->i2c_dev); + I2C_DEV_CHECK(&dev->i2c_dev, start_nolock(dev, SHT3X_SINGLE_SHOT, SHT3X_HIGH)); + vTaskDelay(SHT3X_MEAS_DURATION_TICKS[SHT3X_HIGH]); + I2C_DEV_CHECK(&dev->i2c_dev, get_raw_data_nolock(dev, raw_data)); + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); + + return sht3x_compute_values(raw_data, temperature, humidity); +} + +uint8_t sht3x_get_measurement_duration(sht3x_repeat_t repeat) +{ + return SHT3X_MEAS_DURATION_TICKS[repeat]; // in RTOS ticks +} + +esp_err_t sht3x_start_measurement(sht3x_t *dev, sht3x_mode_t mode, sht3x_repeat_t repeat) +{ + CHECK_ARG(dev); + + I2C_DEV_TAKE_MUTEX(&dev->i2c_dev); + I2C_DEV_CHECK(&dev->i2c_dev, start_nolock(dev, mode, repeat)); + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); + + return ESP_OK; +} + +esp_err_t sht3x_stop_periodic_measurement(sht3x_t *dev) +{ + CHECK_ARG(dev); + + CHECK(send_cmd(dev, SHT3X_STOP_PERIODIC_MEAS_CMD)); + dev->mode = SHT3X_SINGLE_SHOT; + dev->meas_start_time = 0; + dev->meas_started = false; + dev->meas_first = false; + + return ESP_OK; +} + +esp_err_t sht3x_get_raw_data(sht3x_t *dev, sht3x_raw_data_t raw_data) +{ + CHECK_ARG(dev && raw_data); + + I2C_DEV_TAKE_MUTEX(&dev->i2c_dev); + I2C_DEV_CHECK(&dev->i2c_dev, get_raw_data_nolock(dev, raw_data)); + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); + + return ESP_OK; +} + +esp_err_t sht3x_get_results(sht3x_t *dev, float *temperature, float *humidity) +{ + CHECK_ARG(dev && (temperature || humidity)); + + sht3x_raw_data_t raw_data; + + CHECK(sht3x_get_raw_data(dev, raw_data)); + + return sht3x_compute_values(raw_data, temperature, humidity); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/sht3x/sht3x.h Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,268 @@ +/* + * Copyright (c) 2017 Gunar Schorcht <https://github.com/gschorcht> + * Copyright (c) 2019 Ruslan V. Uss <unclerus@gmail.com> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of itscontributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file sht3x.h + * @defgroup sht3x sht3x + * @{ + * + * ESP-IDF driver for Sensirion SHT3x digital temperature and humidity sensor + * + * Forked from <https://github.com/gschorcht/sht3x-esp-idf> + * + * Copyright (c) 2017 Gunar Schorcht <https://github.com/gschorcht>\n + * Copyright (c) 2019 Ruslan V. Uss <unclerus@gmail.com> + * + * BSD Licensed as described in the file LICENSE + */ +#ifndef __SHT3X_H__ +#define __SHT3X_H__ + +#include <stdbool.h> +#include <i2cdev.h> +#include <esp_err.h> + +#ifdef __cplusplus +extern "C" { +#endif + +#define SHT3X_I2C_ADDR_GND 0x44 +#define SHT3X_I2C_ADDR_VDD 0x45 + +#define SHT3X_RAW_DATA_SIZE 6 + +typedef uint8_t sht3x_raw_data_t[SHT3X_RAW_DATA_SIZE]; + +/** + * Possible measurement modes + */ +typedef enum +{ + SHT3X_SINGLE_SHOT = 0, //!< one single measurement + SHT3X_PERIODIC_05MPS, //!< periodic with 0.5 measurements per second (mps) + SHT3X_PERIODIC_1MPS, //!< periodic with 1 measurements per second (mps) + SHT3X_PERIODIC_2MPS, //!< periodic with 2 measurements per second (mps) + SHT3X_PERIODIC_4MPS, //!< periodic with 4 measurements per second (mps) + SHT3X_PERIODIC_10MPS //!< periodic with 10 measurements per second (mps) +} sht3x_mode_t; + +/** + * Possible repeatability modes + */ +typedef enum +{ + SHT3X_HIGH = 0, + SHT3X_MEDIUM, + SHT3X_LOW +} sht3x_repeat_t; + +/** + * Device descriptor + */ +typedef struct +{ + i2c_dev_t i2c_dev; //!< I2C device descriptor + + sht3x_mode_t mode; //!< used measurement mode + sht3x_repeat_t repeatability; //!< used repeatability + + bool meas_started; //!< indicates whether measurement started + uint64_t meas_start_time; //!< measurement start time in us + bool meas_first; //!< first measurement in periodic mode +} sht3x_t; + +/** + * @brief Initialize device descriptor + * + * @param dev Device descriptor + * @param port I2C port + * @param addr Device address + * @param sda_gpio SDA GPIO + * @param scl_gpio SCL GPIO + * @return `ESP_OK` on success + */ +esp_err_t sht3x_init_desc(sht3x_t *dev, uint8_t addr, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio); + +/** + * @brief Free device descriptor + * + * @param dev Device descriptor + * @return `ESP_OK` on success + */ +esp_err_t sht3x_free_desc(sht3x_t *dev); + +/** + * @brief Initialize sensor + * + * @param dev Device descriptor + * @return `ESP_OK` on success + */ +esp_err_t sht3x_init(sht3x_t *dev); + +/** + * @brief Enable/disable heater + * + * @param dev Device descriptor + * @param enable True to enable, false to disable + * @return `ESP_OK` on success + */ +esp_err_t sht3x_set_heater(sht3x_t *dev, bool enable); + +/** + * @brief High level measurement function + * + * For convenience this function comprises all three steps to perform + * one measurement in only one function: + * + * 1. Starts a measurement in single shot mode with high reliability + * 2. Waits using `vTaskDelay()` until measurement results are available + * 3. Returns the results in kind of floating point sensor values + * + * This function is the easiest way to use the sensor. It is most suitable + * for users that don't want to have the control on sensor details. + * + * @note The function delays the calling task up to 30 ms to wait for + * the measurement results. This might lead to problems when function + * is called from a software timer callback function. + * + * @param dev Device descriptor + * @param temperature Temperature in degree Celsius + * @param humidity Humidity in percent + * @return `ESP_OK` on success + */ +esp_err_t sht3x_measure(sht3x_t *dev, float *temperature, float *humidity); + +/** + * @brief Get the duration of a measurement in RTOS ticks. + * + * The function returns the duration in RTOS ticks required by the sensor to + * perform a measurement for the given repeatability. Once a measurement is + * started with function ::sht3x_start_measurement() the user task can use this + * duration in RTOS ticks directly to wait with function `vTaskDelay()` until + * the measurement results can be fetched. + * + * @note The duration only depends on repeatability level. Therefore, + * it can be considered as constant for a repeatability. + * + * @param repeat Repeatability, see type ::sht3x_repeat_t + * @return Measurement duration given in RTOS ticks + */ +uint8_t sht3x_get_measurement_duration(sht3x_repeat_t repeat); + +/** + * @brief Start the measurement in single shot or periodic mode + * + * The function starts the measurement either in *single shot mode* + * (exactly one measurement) or *periodic mode* (periodic measurements) + * with given repeatability. + * + * In the *single shot mode*, this function has to be called for each + * measurement. The measurement duration has to be waited every time + * before the results can be fetched. + * + * In the *periodic mode*, this function has to be called only once. Also + * the measurement duration has to be waited only once until the first + * results are available. After this first measurement, the sensor then + * automatically performs all subsequent measurements. The rate of periodic + * measurements can be 10, 4, 2, 1 or 0.5 measurements per second (mps). + * + * @note Due to inaccuracies in timing of the sensor, the user task + * should fetch the results at a lower rate. The rate of the periodic + * measurements is defined by the parameter \p mode. + * + * @param dev Device descriptor + * @param mode Measurement mode, see type ::sht3x_mode_t + * @param repeat Repeatability, see type ::sht3x_repeat_t + * @return `ESP_OK` on success + */ +esp_err_t sht3x_start_measurement(sht3x_t *dev, sht3x_mode_t mode, sht3x_repeat_t repeat); + +/** + * @brief Stop the periodic mode measurements + * + * The function stops the measurements in *periodic mode* + * (periodic measurements) and the sensor returns in *single shot mode* + * + * @param dev Device descriptor + * @return `ESP_OK` on success + */ +esp_err_t sht3x_stop_periodic_measurement(sht3x_t *dev); + +/** + * @brief Read measurement results from sensor as raw data + * + * The function read measurement results from the sensor, checks the CRC + * checksum and stores them in the byte array as following. + * + * data[0] = Temperature MSB + * data[1] = Temperature LSB + * data[2] = Temperature CRC + * data[3] = Humidity MSB + * data[4] = Humidity LSB + * data[2] = Humidity CRC + * + * In case that there are no new data that can be read, the function fails. + * + * @param dev Device descriptor + * @param raw_data Byte array in which raw data are stored + * @return `ESP_OK` on success + */ +esp_err_t sht3x_get_raw_data(sht3x_t *dev, sht3x_raw_data_t raw_data); + +/** + * @brief Computes sensor values from raw data + * + * @param raw_data Byte array that contains raw data + * @param temperature Temperature in degree Celsius + * @param humidity Humidity in percent + * @return `ESP_OK` on success + */ +esp_err_t sht3x_compute_values(sht3x_raw_data_t raw_data, float *temperature, float *humidity); + +/** + * @brief Get measurement results in form of sensor values + * + * The function combines function ::sht3x_get_raw_data() and function + * ::sht3x_compute_values() to get the measurement results. + * + * In case that there are no results that can be read, the function fails. + * + * @param dev Device descriptor + * @param temperature Temperature in degree Celsius + * @param humidity Humidity in percent + * @return `ESP_OK` on success + */ +esp_err_t sht3x_get_results(sht3x_t *dev, float *temperature, float *humidity); + +#ifdef __cplusplus +} +#endif + +/**@}*/ + +#endif /* __SHT3X_H__ */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/sht4x/.eil.yml Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,25 @@ +--- +components: + - name: sht4x + description: | + Driver for Sensirion SHT40/SHT41/SHT45 digital temperature and humidity sensor + group: temperature + groups: + - name: humidity + code_owners: UncleRus + depends: + - i2cdev + - log + - esp_idf_lib_helpers + thread_safe: yes + targets: + - name: esp32 + - name: esp8266 + - name: esp32s2 + - name: esp32c3 + licenses: + - name: BSD-3 + copyrights: + - author: + name: UncleRus + year: 2021
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/sht4x/CMakeLists.txt Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,11 @@ +if(${IDF_VERSION_MAJOR} STREQUAL 4 AND ${IDF_VERSION_MINOR} STREQUAL 1 AND ${IDF_VERSION_PATCH} STREQUAL 3) + set(req i2cdev log esp_idf_lib_helpers) +else() + set(req i2cdev log esp_idf_lib_helpers esp_timer) +endif() + +idf_component_register( + SRCS sht4x.c + INCLUDE_DIRS . + REQUIRES ${req} +)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/sht4x/LICENSE Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,26 @@ +Copyright (c) 2021 Ruslan V. Uss (https://github.com/UncleRus) + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, +this list of conditions and the following disclaimer in the documentation +and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of itscontributors +may be used to endorse or promote products derived from this software without +specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/sht4x/component.mk Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,2 @@ +COMPONENT_ADD_INCLUDEDIRS = . +COMPONENT_DEPENDS = i2cdev log esp_idf_lib_helpers
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/sht4x/sht4x.c Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,313 @@ +/* + * Copyright (c) 2021 Ruslan V. Uss <unclerus@gmail.com> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of itscontributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file sht4x.c + * + * ESP-IDF driver for Sensirion SHT40/SHT41 digital temperature and humidity sensor + * + * Copyright (c) 2021 Ruslan V. Uss <unclerus@gmail.com> + * + * BSD Licensed as described in the file LICENSE + */ + +#include <esp_log.h> +#include <freertos/FreeRTOS.h> +#include <freertos/task.h> +#include <esp_idf_lib_helpers.h> +#include <esp_timer.h> +#include "sht4x.h" + +#define I2C_FREQ_HZ 1000000 // 1MHz + +static const char *TAG = "sht4x"; + +#define CMD_RESET 0x94 +#define CMD_SERIAL 0x89 +#define CMD_MEAS_HIGH 0xfd +#define CMD_MEAS_MED 0xf6 +#define CMD_MEAS_LOW 0xe0 +#define CMD_MEAS_H_HIGH_LONG 0x39 +#define CMD_MEAS_H_HIGH_SHORT 0x32 +#define CMD_MEAS_H_MED_LONG 0x2f +#define CMD_MEAS_H_MED_SHORT 0x24 +#define CMD_MEAS_H_LOW_LONG 0x1e +#define CMD_MEAS_H_LOW_SHORT 0x15 + +#define CHECK(x) do { esp_err_t __; if ((__ = x) != ESP_OK) return __; } while (0) +#define CHECK_ARG(VAL) do { if (!(VAL)) return ESP_ERR_INVALID_ARG; } while (0) + +#define G_POLYNOM 0x31 + +static uint8_t crc8(uint8_t data[], size_t len) +{ + uint8_t crc = 0xff; + + for (size_t i = 0; i < len; i++) + { + crc ^= data[i]; + for (size_t i = 0; i < 8; i++) + crc = crc & 0x80 ? (crc << 1) ^ G_POLYNOM : crc << 1; + } + return crc; +} + +static inline size_t get_duration_ms(sht4x_t *dev) +{ + switch (dev->heater) + { + case SHT4X_HEATER_HIGH_LONG: + case SHT4X_HEATER_MEDIUM_LONG: + case SHT4X_HEATER_LOW_LONG: + return 1100; + case SHT4X_HEATER_HIGH_SHORT: + case SHT4X_HEATER_MEDIUM_SHORT: + case SHT4X_HEATER_LOW_SHORT: + return 110; + default: + switch (dev->repeatability) + { + case SHT4X_HIGH: + return 10; + case SHT4X_MEDIUM: + return 5; + default: + return 2; + } + } +} + +static inline uint8_t get_meas_cmd(sht4x_t *dev) +{ + switch (dev->heater) + { + case SHT4X_HEATER_HIGH_LONG: + return CMD_MEAS_H_HIGH_LONG; + case SHT4X_HEATER_HIGH_SHORT: + return CMD_MEAS_H_HIGH_SHORT; + case SHT4X_HEATER_MEDIUM_LONG: + return CMD_MEAS_H_MED_LONG; + case SHT4X_HEATER_MEDIUM_SHORT: + return CMD_MEAS_H_MED_SHORT; + case SHT4X_HEATER_LOW_LONG: + return CMD_MEAS_H_LOW_LONG; + case SHT4X_HEATER_LOW_SHORT: + return CMD_MEAS_H_LOW_SHORT; + default: + switch (dev->repeatability) + { + case SHT4X_HIGH: + return CMD_MEAS_HIGH; + case SHT4X_MEDIUM: + return CMD_MEAS_MED; + default: + return CMD_MEAS_LOW; + } + } +} + +static inline esp_err_t send_cmd_nolock(sht4x_t *dev, uint8_t cmd) +{ + ESP_LOGD(TAG, "Sending cmd %02x...", cmd); + return i2c_dev_write(&dev->i2c_dev, NULL, 0, &cmd, 1); +} + +static inline esp_err_t read_res_nolock(sht4x_t *dev, sht4x_raw_data_t res) +{ + CHECK(i2c_dev_read(&dev->i2c_dev, NULL, 0, res, SHT4X_RAW_DATA_SIZE)); + + ESP_LOGD(TAG, "Got response %02x %02x %02x %02x %02x %02x", + res[0], res[1], res[2], res[3], res[4], res[5]); + + if (res[2] != crc8(res, 2) || res[5] != crc8(res + 3, 2)) + { + ESP_LOGE(TAG, "Invalid CRC"); + return ESP_ERR_INVALID_CRC; + } + + return ESP_OK; +} + +static esp_err_t send_cmd(sht4x_t *dev, uint8_t cmd) +{ + I2C_DEV_TAKE_MUTEX(&dev->i2c_dev); + I2C_DEV_CHECK(&dev->i2c_dev, send_cmd_nolock(dev, cmd)); + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); + + return ESP_OK; +} + +static esp_err_t read_res(sht4x_t *dev, sht4x_raw_data_t res) +{ + I2C_DEV_TAKE_MUTEX(&dev->i2c_dev); + I2C_DEV_CHECK(&dev->i2c_dev, read_res_nolock(dev, res)); + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); + + return ESP_OK; +} + +static esp_err_t exec_cmd(sht4x_t *dev, uint8_t cmd, size_t delay_ticks, sht4x_raw_data_t res) +{ + I2C_DEV_TAKE_MUTEX(&dev->i2c_dev); + I2C_DEV_CHECK(&dev->i2c_dev, send_cmd_nolock(dev, cmd)); + if (delay_ticks) + vTaskDelay(delay_ticks); + I2C_DEV_CHECK(&dev->i2c_dev, read_res_nolock(dev, res)); + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); + + return ESP_OK; +} + +static inline bool is_measuring(sht4x_t *dev) +{ + // not running if measurement is not started + if (!dev->meas_started) + return false; + + // not running if time elapsed is greater than duration + uint64_t elapsed = esp_timer_get_time() - dev->meas_start_time; + return elapsed < get_duration_ms(dev) * 1000; +} + +/////////////////////////////////////////////////////////////////////////////// + +esp_err_t sht4x_init_desc(sht4x_t *dev, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio) +{ + CHECK_ARG(dev); + + dev->i2c_dev.port = port; + dev->i2c_dev.addr = SHT4X_I2C_ADDRESS; + dev->i2c_dev.cfg.sda_io_num = sda_gpio; + dev->i2c_dev.cfg.scl_io_num = scl_gpio; +#if HELPER_TARGET_IS_ESP32 + dev->i2c_dev.cfg.master.clk_speed = I2C_FREQ_HZ; +#endif + + return i2c_dev_create_mutex(&dev->i2c_dev); +} + +esp_err_t sht4x_free_desc(sht4x_t *dev) +{ + CHECK_ARG(dev); + + return i2c_dev_delete_mutex(&dev->i2c_dev); +} + +esp_err_t sht4x_init(sht4x_t *dev) +{ + CHECK_ARG(dev); + + dev->repeatability = SHT4X_HIGH; + dev->heater = SHT4X_HEATER_OFF; + + sht4x_raw_data_t s; + CHECK(exec_cmd(dev, CMD_SERIAL, pdMS_TO_TICKS(10), s)); + dev->serial = ((uint32_t)s[0] << 24) | ((uint32_t)s[1] << 16) | ((uint32_t)s[3] << 8) | s[4]; + + return sht4x_reset(dev); +} + +esp_err_t sht4x_reset(sht4x_t *dev) +{ + dev->meas_start_time = 0; + dev->meas_started = false; + + CHECK(send_cmd(dev, CMD_RESET)); + vTaskDelay(1); + + return ESP_OK; +} + +esp_err_t sht4x_measure(sht4x_t *dev, float *temperature, float *humidity) +{ + CHECK_ARG(dev && (temperature || humidity)); + + sht4x_raw_data_t raw; + CHECK(exec_cmd(dev, get_meas_cmd(dev), sht4x_get_measurement_duration(dev), raw)); + + return sht4x_compute_values(raw, temperature, humidity); +} + +esp_err_t sht4x_start_measurement(sht4x_t *dev) +{ + CHECK_ARG(dev); + + if (is_measuring(dev)) + { + ESP_LOGE(TAG, "Measurement is still running"); + return ESP_ERR_INVALID_STATE; + } + + dev->meas_start_time = esp_timer_get_time(); + CHECK(send_cmd(dev, get_meas_cmd(dev))); + dev->meas_started = true; + + return ESP_OK; +} + +size_t sht4x_get_measurement_duration(sht4x_t *dev) +{ + if (!dev) return 0; + + size_t res = pdMS_TO_TICKS(get_duration_ms(dev)); + return res == 0 ? 1 : res; +} + +esp_err_t sht4x_get_raw_data(sht4x_t *dev, sht4x_raw_data_t raw) +{ + CHECK_ARG(dev); + + if (is_measuring(dev)) + { + ESP_LOGE(TAG, "Measurement is still running"); + return ESP_ERR_INVALID_STATE; + } + + dev->meas_started = false; + return read_res(dev, raw); +} + +esp_err_t sht4x_compute_values(sht4x_raw_data_t raw_data, float *temperature, float *humidity) +{ + CHECK_ARG(raw_data && (temperature || humidity)); + + if (temperature) + *temperature = ((uint16_t)raw_data[0] << 8 | raw_data[1]) * 175.0 / 65535.0 - 45.0; + + if (humidity) + *humidity = ((uint16_t)raw_data[3] << 8 | raw_data[4]) * 125.0 / 65535.0 - 6.0; + + return ESP_OK; +} + +esp_err_t sht4x_get_results(sht4x_t *dev, float *temperature, float *humidity) +{ + sht4x_raw_data_t raw; + CHECK(sht4x_get_raw_data(dev, raw)); + + return sht4x_compute_values(raw, temperature, humidity); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/sht4x/sht4x.h Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,229 @@ +/* + * Copyright (c) 2021 Ruslan V. Uss <unclerus@gmail.com> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of itscontributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file sht4x.h + * @defgroup sht4x sht4x + * @{ + * + * ESP-IDF driver for Sensirion SHT40/SHT41 digital temperature and humidity sensor + * + * Copyright (c) 2021 Ruslan V. Uss <unclerus@gmail.com> + * + * BSD Licensed as described in the file LICENSE + */ +#ifndef __SHT4X_H__ +#define __SHT4X_H__ + +#include <stdbool.h> +#include <i2cdev.h> +#include <esp_err.h> + +#ifdef __cplusplus +extern "C" { +#endif + +#define SHT4X_I2C_ADDRESS 0x44 + +#define SHT4X_RAW_DATA_SIZE 6 + +typedef uint8_t sht4x_raw_data_t[SHT4X_RAW_DATA_SIZE]; + +/** + * Possible heater modes + */ +typedef enum +{ + SHT4X_HEATER_OFF = 0, /**< Heater is off, default */ + SHT4X_HEATER_HIGH_LONG, /**< High power (~200mW), 1 second pulse */ + SHT4X_HEATER_HIGH_SHORT, /**< High power (~200mW), 0.1 second pulse */ + SHT4X_HEATER_MEDIUM_LONG, /**< Medium power (~110mW), 1 second pulse */ + SHT4X_HEATER_MEDIUM_SHORT, /**< Medium power (~110mW), 0.1 second pulse */ + SHT4X_HEATER_LOW_LONG, /**< Low power (~20mW), 1 second pulse */ + SHT4X_HEATER_LOW_SHORT, /**< Low power (~20mW), 0.1 second pulse */ +} sht4x_heater_t; + +/** + * Possible repeatability modes + */ +typedef enum +{ + SHT4X_HIGH = 0, + SHT4X_MEDIUM, + SHT4X_LOW +} sht4x_repeat_t; + +/** + * Device descriptor + */ +typedef struct +{ + i2c_dev_t i2c_dev; //!< I2C device descriptor + + uint32_t serial; //!< device serial number + + sht4x_heater_t heater; //!< used measurement mode + sht4x_repeat_t repeatability; //!< used repeatability + + bool meas_started; //!< indicates whether measurement started + uint64_t meas_start_time; //!< measurement start time in us +} sht4x_t; + +/** + * @brief Initialize device descriptor + * + * @param dev Device descriptor + * @param port I2C port + * @param sda_gpio SDA GPIO + * @param scl_gpio SCL GPIO + * @return `ESP_OK` on success + */ +esp_err_t sht4x_init_desc(sht4x_t *dev, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio); + +/** + * @brief Free device descriptor + * + * @param dev Device descriptor + * @return `ESP_OK` on success + */ +esp_err_t sht4x_free_desc(sht4x_t *dev); + +/** + * @brief Initialize sensor + * + * @param dev Device descriptor + * @return `ESP_OK` on success + */ +esp_err_t sht4x_init(sht4x_t *dev); + +/** + * @brief Soft-reset sensor + * + * @param dev Device descriptor + * @return `ESP_OK` on success + */ +esp_err_t sht4x_reset(sht4x_t *dev); + +/** + * @brief High level measurement function + * + * For convenience this function comprises all three steps to perform + * one measurement in only one function: + * + * 1. Starts a measurement + * 2. Waits using `vTaskDelay()` until measurement results are available + * 3. Returns the results in kind of floating point sensor values + * + * This function is the easiest way to use the sensor. It is most suitable + * for users that don't want to have the control on sensor details. + * + * @note The function delays the calling task up to 1.1 s to wait for + * the measurement results. This might lead to problems when the function + * is called from a software timer callback function. + * + * @param dev Device descriptor + * @param temperature Temperature in degree Celsius + * @param humidity Humidity in percent + * @return `ESP_OK` on success + */ +esp_err_t sht4x_measure(sht4x_t *dev, float *temperature, float *humidity); + +/** + * @brief Start the measurement. + * + * @param dev Device descriptor + * @return `ESP_OK` on success + */ +esp_err_t sht4x_start_measurement(sht4x_t *dev); + +/** + * @brief Get the duration of a measurement in RTOS ticks. + * + * The function returns the duration in RTOS ticks required by the sensor to + * perform a measurement for the given repeatability and heater setting. + * Once a measurement is started with function ::sht4x_start_measurement() + * the user task can use this duration in RTOS ticks directly to wait + * with function `vTaskDelay()` until the measurement results can be fetched. + * + * @param dev Device descriptor + * @return Measurement duration given in RTOS ticks + */ +size_t sht4x_get_measurement_duration(sht4x_t *dev); + +/** + * @brief Read measurement results from sensor as raw data + * + * The function read measurement results from the sensor, checks the CRC + * checksum and stores them in the byte array as following. + * + * data[0] = Temperature MSB + * data[1] = Temperature LSB + * data[2] = Temperature CRC + * data[3] = Humidity MSB + * data[4] = Humidity LSB + * data[5] = Humidity CRC + * + * In case that there are no new data that can be read, the function fails. + * + * @param dev Device descriptor + * @param[out] raw Byte array in which raw data are stored + * @return `ESP_OK` on success + */ +esp_err_t sht4x_get_raw_data(sht4x_t *dev, sht4x_raw_data_t raw); + +/** + * @brief Computes sensor values from raw data + * + * @param raw_data Byte array that contains raw data + * @param[out] temperature Temperature in degree Celsius + * @param[out] humidity Humidity in percent + * @return `ESP_OK` on success + */ +esp_err_t sht4x_compute_values(sht4x_raw_data_t raw_data, float *temperature, float *humidity); + +/** + * @brief Get measurement results in form of sensor values + * + * The function combines function ::sht4x_get_raw_data() and function + * ::sht4x_compute_values() to get the measurement results. + * + * In case that there are no results that can be read, the function fails. + * + * @param dev Device descriptor + * @param[out] temperature Temperature in degree Celsius + * @param[out] humidity Humidity in percent + * @return `ESP_OK` on success + */ +esp_err_t sht4x_get_results(sht4x_t *dev, float *temperature, float *humidity); + +#ifdef __cplusplus +} +#endif + +/**@}*/ + +#endif /* __SHT4X_H__ */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/si7021/.eil.yml Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,27 @@ +--- +components: + - name: si7021 + description: | + Driver for Si7013/Si7020/Si7021/HTU2xD/SHT2x and compatible temperature + and humidity sensors + group: temperature + groups: + - name: humidity + code_owners: UncleRus + depends: + - i2cdev + - log + - esp_idf_lib_helpers + thread_safe: yes + targets: + - name: esp32 + - name: esp32c3 + - name: esp8266 + - name: esp32s2 + - name: esp32c3 + licenses: + - name: BSD-3 + copyrights: + - author: + name: UncleRus + year: 2019
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/si7021/CMakeLists.txt Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,5 @@ +idf_component_register( + SRCS si7021.c + INCLUDE_DIRS . + REQUIRES i2cdev log esp_idf_lib_helpers +)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/si7021/LICENSE Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,26 @@ +Copyright (c) 2019 Ruslan V. Uss <unclerus@gmail.com> + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, +this list of conditions and the following disclaimer in the documentation +and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of itscontributors +may be used to endorse or promote products derived from this software without +specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/si7021/component.mk Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,2 @@ +COMPONENT_ADD_INCLUDEDIRS = . +COMPONENT_DEPENDS = i2cdev log esp_idf_lib_helpers
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/si7021/si7021.c Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,318 @@ +/* + * Copyright (c) 2019 Ruslan V. Uss <unclerus@gmail.com> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of itscontributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file si7021.c + * + * ESP-IDF driver for Si7013/Si7020/Si7021/HTU2xD/SHT2x and + * compatible temperature and humidity sensors + * + * Copyright (c) 2019 Ruslan V. Uss <unclerus@gmail.com> + * + * BSD Licensed as described in the file LICENSE + */ +#include <esp_log.h> +#include <freertos/FreeRTOS.h> +#include <freertos/task.h> +#include <esp_idf_lib_helpers.h> +#include "si7021.h" + +#define I2C_FREQ_HZ 400000 // 400kHz + +static const char *TAG = "si7021"; + +#define DELAY_MS 100 // fixed delay (100 ms for SHT20) + +#define CMD_MEAS_RH_HOLD 0xe5 // not used, can't stretch clock +#define CMD_MEAS_RH_NOHOLD 0xf5 +#define CMD_MEAS_T_HOLD 0xe3 // not used, can't stretch clock +#define CMD_MEAS_T_NOHOLD 0xf3 +#define CMD_READ_T 0xe0 // not used +#define CMD_RESET 0xfe +#define CMD_WRITE_USER_REG 0xe6 +#define CMD_READ_USER_REG 0xe7 +#define CMD_WRITE_HEATER_REG 0x51 +#define CMD_READ_HEATER_REG 0x11 +#define CMD_READ_ID_1 0xfa0f +#define CMD_READ_ID_2 0xfcc9 +#define CMD_READ_FW_REV_1 0x84b8 + +#define BIT_USER_REG_RES0 0 +#define BIT_USER_REG_HTRE 2 +#define BIT_USER_REG_RES1 7 + +#define HEATER_MASK 0x0f + +#define BV(x) (1 << (x)) + +#define CHECK(x) do { esp_err_t __; if ((__ = x) != ESP_OK) return __; } while (0) +#define CHECK_ARG(VAL) do { if (!(VAL)) return ESP_ERR_INVALID_ARG; } while (0) + +static bool check_crc(uint16_t value, uint8_t crc) +{ + uint32_t row = (uint32_t)value << 8; + row |= crc; + + uint32_t divisor = 0x988000; + + for (int i = 0; i < 16; i++) + { + if (row & (uint32_t)1 << (23 - i)) + row ^= divisor; + divisor >>= 1; + } + + return !row; +} + +static esp_err_t measure(i2c_dev_t *dev, uint8_t cmd, uint16_t *raw) +{ + I2C_DEV_TAKE_MUTEX(dev); + // write command + I2C_DEV_CHECK(dev, i2c_dev_write(dev, NULL, 0, &cmd, 1)); + + // wait + vTaskDelay(DELAY_MS / portTICK_PERIOD_MS); + + // read data + uint8_t buf[3]; + I2C_DEV_CHECK(dev, i2c_dev_read(dev, NULL, 0, buf, 3)); + I2C_DEV_GIVE_MUTEX(dev); + + *raw = ((uint16_t)buf[0] << 8) | buf[1]; + + if (!check_crc(*raw, buf[2])) + { + ESP_LOGE(TAG, "Invalid CRC"); + return ESP_ERR_INVALID_RESPONSE; + } + + return ESP_OK; +} + +/////////////////////////////////////////////////////////////////////////////// + +esp_err_t si7021_init_desc(i2c_dev_t *dev, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio) +{ + CHECK_ARG(dev); + + dev->port = port; + dev->addr = SI7021_I2C_ADDR; + dev->cfg.sda_io_num = sda_gpio; + dev->cfg.scl_io_num = scl_gpio; +#if HELPER_TARGET_IS_ESP32 + dev->cfg.master.clk_speed = I2C_FREQ_HZ; +#endif + + return i2c_dev_create_mutex(dev); +} + +esp_err_t si7021_free_desc(i2c_dev_t *dev) +{ + CHECK_ARG(dev); + + return i2c_dev_delete_mutex(dev); +} + +esp_err_t si7021_reset(i2c_dev_t *dev) +{ + CHECK_ARG(dev); + + uint8_t cmd = CMD_RESET; + I2C_DEV_TAKE_MUTEX(dev); + I2C_DEV_CHECK(dev, i2c_dev_write(dev, NULL, 0, &cmd, 1)); + I2C_DEV_GIVE_MUTEX(dev); + + vTaskDelay(pdMS_TO_TICKS(DELAY_MS)); + + return ESP_OK; +} + +esp_err_t si7021_get_heater(i2c_dev_t *dev, bool *on) +{ + CHECK_ARG(dev && on); + + uint8_t u; + + I2C_DEV_TAKE_MUTEX(dev); + I2C_DEV_CHECK(dev, i2c_dev_read_reg(dev, CMD_READ_USER_REG, &u, 1)); + I2C_DEV_GIVE_MUTEX(dev); + + *on = u & BV(BIT_USER_REG_HTRE); + + return ESP_OK; +} + +esp_err_t si7021_set_heater(i2c_dev_t *dev, bool on) +{ + CHECK_ARG(dev); + + uint8_t u; + + I2C_DEV_TAKE_MUTEX(dev); + I2C_DEV_CHECK(dev, i2c_dev_read_reg(dev, CMD_READ_USER_REG, &u, 1)); + u = (u & ~BV(BIT_USER_REG_HTRE)) | (on ? 1 : 0); + I2C_DEV_CHECK(dev, i2c_dev_write_reg(dev, CMD_WRITE_USER_REG, &u, 1)); + I2C_DEV_GIVE_MUTEX(dev); + + return ESP_OK; +} + +esp_err_t si7021_get_heater_current(i2c_dev_t *dev, uint8_t *level) +{ + CHECK_ARG(dev && level); + + uint8_t h; + + I2C_DEV_TAKE_MUTEX(dev); + I2C_DEV_CHECK(dev, i2c_dev_read_reg(dev, CMD_READ_HEATER_REG, &h, 1)); + I2C_DEV_GIVE_MUTEX(dev); + + *level = h & HEATER_MASK; + + return ESP_OK; +} + +esp_err_t si7021_set_heater_current(i2c_dev_t *dev, uint8_t level) +{ + CHECK_ARG(dev && level <= SI7021_MAX_HEATER_CURRENT); + + I2C_DEV_TAKE_MUTEX(dev); + I2C_DEV_CHECK(dev, i2c_dev_write_reg(dev, CMD_WRITE_HEATER_REG, &level, 1)); + I2C_DEV_GIVE_MUTEX(dev); + + return ESP_OK; +} + +esp_err_t si7021_get_resolution(i2c_dev_t *dev, si7021_resolution_t *r) +{ + CHECK_ARG(dev && r); + + uint8_t u; + + I2C_DEV_TAKE_MUTEX(dev); + I2C_DEV_CHECK(dev, i2c_dev_read_reg(dev, CMD_READ_USER_REG, &u, 1)); + I2C_DEV_GIVE_MUTEX(dev); + + *r = ((u >> BIT_USER_REG_RES1) << 1) | (u & BV(BIT_USER_REG_RES0)); + + return ESP_OK; +} + +esp_err_t si7021_set_resolution(i2c_dev_t *dev, si7021_resolution_t r) +{ + CHECK_ARG(dev && r <= SI7021_RES_RH11_T11); + + uint8_t u; + + I2C_DEV_TAKE_MUTEX(dev); + I2C_DEV_CHECK(dev, i2c_dev_read_reg(dev, CMD_READ_USER_REG, &u, 1)); + u = (u & ~(BV(BIT_USER_REG_RES0) | BV(BIT_USER_REG_RES1))) | ((r & 2) << (BIT_USER_REG_RES1 - 1)) | (r & 1); + I2C_DEV_CHECK(dev, i2c_dev_write_reg(dev, CMD_WRITE_USER_REG, &u, 1)); + I2C_DEV_GIVE_MUTEX(dev); + + return ESP_OK; +} + +esp_err_t si7021_measure_temperature(i2c_dev_t *dev, float *t) +{ + CHECK_ARG(dev && t); + + uint16_t raw; + CHECK(measure(dev, CMD_MEAS_T_NOHOLD, &raw)); + *t = raw * 175.72 / 65536 - 46.85; + + return ESP_OK; +} + +esp_err_t si7021_measure_humidity(i2c_dev_t *dev, float *rh) +{ + CHECK_ARG(dev && rh); + + uint16_t raw; + CHECK(measure(dev, CMD_MEAS_RH_NOHOLD, &raw)); + *rh = raw * 125.0 / 65536 - 6; + + return ESP_OK; +} + +esp_err_t si7021_get_serial(i2c_dev_t *dev, uint64_t *serial, bool sht2x_mode) +{ + CHECK_ARG(dev && serial); + + uint8_t buf[8]; + uint16_t cmd; + + I2C_DEV_TAKE_MUTEX(dev); + + // read SNA, ignore CRC + cmd = CMD_READ_ID_1; + I2C_DEV_CHECK(dev, i2c_dev_read(dev, &cmd, 2, buf, 8)); + *serial = sht2x_mode + ? ((uint64_t)buf[0] << 40) | ((uint64_t)buf[2] << 32) | ((uint64_t)buf[4] << 24) | ((uint64_t)buf[6] << 16) + : ((uint64_t)buf[0] << 56) | ((uint64_t)buf[2] << 48) | ((uint64_t)buf[4] << 40) | ((uint64_t)buf[6] << 32); + + // read SNB, ignore CRC + cmd = CMD_READ_ID_2; + I2C_DEV_CHECK(dev, i2c_dev_read(dev, &cmd, 2, buf, 6)); + *serial |= sht2x_mode + ? ((uint32_t)buf[0] << 8) | ((uint64_t)buf[3] << 56) | ((uint64_t)buf[4] << 48) | buf[1] + : ((uint32_t)buf[0] << 24) | ((uint32_t)buf[1] << 16) | ((uint32_t)buf[3] << 8) | buf[4]; + + I2C_DEV_GIVE_MUTEX(dev); + + return ESP_OK; +} + +esp_err_t si7021_get_device_id(i2c_dev_t *dev, si7021_device_id_t *id) +{ + CHECK_ARG(id); + + uint64_t serial; + CHECK(si7021_get_serial(dev, &serial, false)); + + switch ((serial >> 24) & 0xff) + { + case 0x0d: + *id = SI_MODEL_SI7013; + break; + case 0x14: + *id = SI_MODEL_SI7020; + break; + case 0x15: + *id = SI_MODEL_SI7021; + break; + case 0x00: + case 0xff: + *id = SI_MODEL_SAMPLE; + break; + default: + *id = SI_MODEL_UNKNOWN; + } + + return ESP_OK; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/si7021/si7021.h Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,238 @@ +/* + * Copyright (c) 2019 Ruslan V. Uss <unclerus@gmail.com> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of itscontributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file si7021.h + * @defgroup si7021 si7021 + * @{ + * + * ESP-IDF driver for Si7013/Si7020/Si7021/HTU2xD/SHT2x and + * compatible temperature and humidity sensors + * + * Copyright (c) 2019 Ruslan V. Uss <unclerus@gmail.com> + * + * BSD Licensed as described in the file LICENSE + */ +#ifndef __SI7021_H__ +#define __SI7021_H__ + +#include <stdbool.h> +#include <i2cdev.h> +#include <esp_err.h> + +#ifdef __cplusplus +extern "C" { +#endif + +#define SI7021_I2C_ADDR 0x40 //!< I2C address + +#define SI7021_MAX_HEATER_CURRENT 0x0f //!< Maximum current of the heater for Si70xx + +/** + * Device model for Si70xx + */ +typedef enum { + SI_MODEL_SI7013 = 0, //!< Si7013 + SI_MODEL_SI7020, //!< Si7020 + SI_MODEL_SI7021, //!< Si7021 + SI_MODEL_SAMPLE, //!< Engineering sample + SI_MODEL_UNKNOWN //!< Unknown model +} si7021_device_id_t; + +/** + * Measurement resolution + */ +typedef enum { + SI7021_RES_RH12_T14 = 0, //!< Relative humidity: 12 bits, temperature: 14 bits + SI7021_RES_RH08_T12, //!< Relative humidity: 8 bits, temperature: 12 bits + SI7021_RES_RH10_T13, //!< Relative humidity: 10 bits, temperature: 13 bits + SI7021_RES_RH11_T11 //!< Relative humidity: 11 bits, temperature: 11 bits +} si7021_resolution_t; + +/** + * @brief Initialize device descriptor + * + * @param dev Device descriptor + * @param port I2C port + * @param sda_gpio SDA GPIO pin + * @param scl_gpio SCL GPIO pin + * @return `ESP_OK` on success + */ +esp_err_t si7021_init_desc(i2c_dev_t *dev, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio); + +/** + * @brief Free device descriptor + * + * @param dev Device descriptor + * @return `ESP_OK` on success + */ +esp_err_t si7021_free_desc(i2c_dev_t *dev); + +/** + * @brief Reset device + * @param dev Device descriptor + * @return `ESP_OK` on success + */ +esp_err_t si7021_reset(i2c_dev_t *dev); + +/** + * @brief Get heater state + * + * This function is supported by: + * + * - SI7013 + * - SI7020 + * - SI7021 + * - SHT2x + * + * @param dev Device descriptor + * @param[out] on `true` if heater enabled + * @return `ESP_OK` on success + */ +esp_err_t si7021_get_heater(i2c_dev_t *dev, bool *on); + +/** + * @brief Switch heater on/off + * + * This function is supported by: + * + * - SI7013 + * - SI7020 + * - SI7021 + * - SHT2x + * + * @param dev Device descriptor + * @param on if `true`, heater will be enabled + * @return `ESP_OK` on success + */ +esp_err_t si7021_set_heater(i2c_dev_t *dev, bool on); + +/** + * @brief Get heater current + * + * This function is supported by: + * + * - SI7013 + * - SI7020 + * - SI7021 + * + * @param dev Device descriptor + * @param[out] level Heater current, see datasheet + * @return `ESP_OK` on success + */ +esp_err_t si7021_get_heater_current(i2c_dev_t *dev, uint8_t *level); + +/** + * @brief Set heater current + * + * This function is supported by: + * + * - SI7013 + * - SI7020 + * - SI7021 + * + * @param dev Device descriptor + * @param level Heater current, see datasheet + * @return `ESP_OK` on success + */ +esp_err_t si7021_set_heater_current(i2c_dev_t *dev, uint8_t level); + +/** + * @brief Get measurement resolution + * + * @param dev Device descriptor + * @param[out] r Resolution + * @return `ESP_OK` on success + */ +esp_err_t si7021_get_resolution(i2c_dev_t *dev, si7021_resolution_t *r); + +/** + * @brief Set measurement resolution + * + * @param dev Device descriptor + * @param r Resolution + * @return `ESP_OK` on success + */ +esp_err_t si7021_set_resolution(i2c_dev_t *dev, si7021_resolution_t r); + +/** + * @brief Measure temperature + * + * @param dev Device descriptor + * @param[out] t Temperature, deg. Celsius + * @return `ESP_OK` on success + */ +esp_err_t si7021_measure_temperature(i2c_dev_t *dev, float *t); + +/** + * @brief Measure relative humidity + * + * @param dev Device descriptor + * @param[out] rh Relative humidity, % + * @return `ESP_OK` on success + */ +esp_err_t si7021_measure_humidity(i2c_dev_t *dev, float *rh); + +/** + * @brief Get serial number of device + * + * This function is supported by: + * + * - SI7013 + * - SI7020 + * - SI7021 + * - SHT2x + * + * @param dev Device descriptor + * @param[out] serial Serial no. + * @param sht2x_mode `true` for SHT2x devices + * @return `ESP_OK` on success + */ +esp_err_t si7021_get_serial(i2c_dev_t *dev, uint64_t *serial, bool sht2x_mode); + +/** + * @brief Get device model + * + * This function is supported by: + * + * - SI7013 + * - SI7020 + * - SI7021 + * + * @param dev Device descriptor + * @param[out] id Device model + * @return `ESP_OK` on success + */ +esp_err_t si7021_get_device_id(i2c_dev_t *dev, si7021_device_id_t *id); + +#ifdef __cplusplus +} +#endif + +/**@}*/ + +#endif /* __SI7021_H__ */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/sts21/.eil.yml Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,24 @@ +--- +components: + - name: sts21 + description: | + Driver for STS21 temperature sensor + group: temperature + groups: [] + code_owners: + - name: UncleRus + depends: + - name: i2cdev + - name: log + - name: esp_idf_lib_helpers + thread_safe: yes + targets: + - name: esp32 + - name: esp8266 + - name: esp32s2 + - name: esp32c3 + licenses: + - name: BSD-3 + copyrights: + - name: UncleRus + year: 2022
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/sts21/CMakeLists.txt Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,5 @@ +idf_component_register( + SRCS sts21.c + INCLUDE_DIRS . + REQUIRES i2cdev log esp_idf_lib_helpers +)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/sts21/LICENSE Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,26 @@ +Copyright 2022 Ruslan V. Uss <unclerus@gmail.com> + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, +this list of conditions and the following disclaimer in the documentation +and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of itscontributors +may be used to endorse or promote products derived from this software without +specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/sts21/component.mk Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,2 @@ +COMPONENT_ADD_INCLUDEDIRS = . +COMPONENT_DEPENDS = i2cdev log esp_idf_lib_helpers
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/sts21/sts21.c Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,261 @@ +/* + * Copyright (c) 2022 Ruslan V. Uss <unclerus@gmail.com> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of itscontributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file sts21.c + * + * ESP-IDF driver for humidty/temperature sensors sts2110/sts2115/sts2120 + * + * Copyright (c) 2021 Ruslan V. Uss <unclerus@gmail.com> + * + * BSD Licensed as described in the file LICENSE + */ +#include "sts21.h" +#include <ets_sys.h> +#include <esp_log.h> + +static const char *TAG = "sts21"; + +#define I2C_FREQ_HZ 400000 // 400kHz +#define I2C_ADDRESS 0x4a + +#define CMD_TRIGGER_NO_HOLD 0xf3 +#define CMD_WRITE_USER_REG 0xe6 +#define CMD_READ_USER_REG 0xe7 +#define CMD_SOFT_RESET 0xfe + +#define BIT_RES_H 7 +#define BIT_BATTERY_FAIL 6 +#define BIT_HEATER 2 +#define BIT_RES_L 0 + +#define CHECK(x) do { esp_err_t __; if ((__ = x) != ESP_OK) return __; } while (0) +#define CHECK_ARG(VAL) do { if (!(VAL)) return ESP_ERR_INVALID_ARG; } while (0) + +static const uint32_t measurement_time_us[] = { + [STS21_RESOLUTION_14] = 85000, + [STS21_RESOLUTION_13] = 43000, + [STS21_RESOLUTION_12] = 22000, + [STS21_RESOLUTION_11] = 11000, +}; + +static bool check_crc(uint16_t value, uint8_t crc) +{ + uint32_t row = (uint32_t)value << 8; + row |= crc; + + uint32_t divisor = 0x988000; + + for (int i = 0; i < 16; i++) + { + if (row & (uint32_t)1 << (23 - i)) + row ^= divisor; + divisor >>= 1; + } + + return !row; +} + +static esp_err_t update_user_reg(sts21_t *dev, uint8_t value, uint8_t mask) +{ + uint8_t r; + + I2C_DEV_TAKE_MUTEX(&dev->i2c_dev); + I2C_DEV_CHECK(&dev->i2c_dev, i2c_dev_read_reg(&dev->i2c_dev, CMD_READ_USER_REG, &r, 1)); + r = (r & ~mask) | value; + I2C_DEV_CHECK(&dev->i2c_dev, i2c_dev_write_reg(&dev->i2c_dev, CMD_WRITE_USER_REG, &r, 1)); + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); + + return ESP_OK; +} + +static esp_err_t read_user_reg(sts21_t *dev, uint8_t *value) +{ + I2C_DEV_TAKE_MUTEX(&dev->i2c_dev); + I2C_DEV_CHECK(&dev->i2c_dev, i2c_dev_read_reg(&dev->i2c_dev, CMD_READ_USER_REG, value, 1)); + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); + + return ESP_OK; +} + +static esp_err_t write_cmd(sts21_t *dev, uint8_t cmd) +{ + I2C_DEV_TAKE_MUTEX(&dev->i2c_dev); + I2C_DEV_CHECK(&dev->i2c_dev, i2c_dev_write(&dev->i2c_dev, NULL, 0, &cmd, 1)); + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); + + return ESP_OK; +} + +/////////////////////////////////////////////////////////////////////////////// + +esp_err_t sts21_init_desc(sts21_t *dev, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio) +{ + CHECK_ARG(dev); + + dev->i2c_dev.port = port; + dev->i2c_dev.addr = I2C_ADDRESS; + dev->i2c_dev.cfg.sda_io_num = sda_gpio; + dev->i2c_dev.cfg.scl_io_num = scl_gpio; +#if HELPER_TARGET_IS_ESP32 + dev->i2c_dev.cfg.master.clk_speed = I2C_FREQ_HZ; +#endif + + return i2c_dev_create_mutex(&dev->i2c_dev); +} + +esp_err_t sts21_free_desc(sts21_t *dev) +{ + CHECK_ARG(dev); + + return i2c_dev_delete_mutex(&dev->i2c_dev); +} + +esp_err_t sts21_init(sts21_t *dev) +{ + CHECK_ARG(dev); + + // soft reset + CHECK(write_cmd(dev, CMD_SOFT_RESET)); + dev->resolution = STS21_RESOLUTION_14; + + return sts21_set_heater_state(dev, false); +} + +esp_err_t sts21_get_resolution(sts21_t *dev, sts21_resolution_t *res) +{ + CHECK_ARG(dev && res); + + uint8_t r; + CHECK(read_user_reg(dev, &r)); + dev->resolution = *res = ((r >> BIT_RES_H) << 1) | (r & BIT(BIT_RES_L)); + + return ESP_OK; +} + +esp_err_t sts21_set_resolution(sts21_t *dev, sts21_resolution_t res) +{ + CHECK_ARG(dev); + + CHECK(update_user_reg(dev, (((res >> 1) & 1) << BIT_RES_H) | (res & 1), BIT(BIT_RES_H) | BIT(BIT_RES_L))); + dev->resolution = res; + + return ESP_OK; +} + +esp_err_t sts21_get_heater_state(sts21_t *dev, bool *on) +{ + CHECK_ARG(dev && on); + + uint8_t r; + CHECK(read_user_reg(dev, &r)); + *on = r & BIT(BIT_HEATER) ? true : false; + + return ESP_OK; +} + +esp_err_t sts21_set_heater_state(sts21_t *dev, bool on) +{ + CHECK_ARG(dev); + + return update_user_reg(dev, on ? BIT(BIT_HEATER) : 0, BIT(BIT_HEATER)); +} + +esp_err_t sts21_get_power_alert(sts21_t *dev, bool *alert) +{ + CHECK_ARG(dev && alert); + + uint8_t r; + CHECK(read_user_reg(dev, &r)); + *alert = r & BIT(BIT_BATTERY_FAIL) ? true : false; + + return ESP_OK; +} + +esp_err_t sts21_is_busy(sts21_t *dev, bool *busy) +{ + CHECK_ARG(dev && busy); + + I2C_DEV_TAKE_MUTEX(&dev->i2c_dev); + esp_err_t res = i2c_dev_probe(&dev->i2c_dev, I2C_DEV_READ); + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); + + if (res == ESP_FAIL) + { + // sensor NACKs I2C read operation when measurement is in progress + *busy = true; + return ESP_OK; + } + + if (res == ESP_OK) + { + *busy = false; + return ESP_OK; + } + + return res; +} + +esp_err_t sts21_trigger_measurement(sts21_t *dev) +{ + CHECK_ARG(dev); + + return write_cmd(dev, CMD_TRIGGER_NO_HOLD); +} + +esp_err_t sts21_read_temperature(sts21_t *dev, float *t) +{ + CHECK_ARG(dev && t); + + uint8_t buf[3]; + + I2C_DEV_TAKE_MUTEX(&dev->i2c_dev); + I2C_DEV_CHECK(&dev->i2c_dev, i2c_dev_read(&dev->i2c_dev, NULL, 0, buf, sizeof(buf))); + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); + + uint16_t raw = ((uint16_t)buf[0] << 8) | buf[1]; + + // calc crc + if (!check_crc(raw, buf[2])) + { + ESP_LOGE(TAG, "Invalid CRC. Raw data: 0x%04x, CRC: 0x%02x", raw, buf[2]); + return ESP_ERR_INVALID_CRC; + } + + raw &= 0xffff << (dev->resolution + 2); + // calc temperature + *t = (-46.85f + (175.72f * raw / 65536.0f)) * 1.8f + 32; + + return ESP_OK; +} + +esp_err_t sts21_measure(sts21_t *dev, float *t) +{ + CHECK(sts21_trigger_measurement(dev)); + ets_delay_us(measurement_time_us[dev->resolution]); + return sts21_read_temperature(dev, t); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/sts21/sts21.h Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,189 @@ +/* + * Copyright (c) 2022 Ruslan V. Uss <unclerus@gmail.com> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of itscontributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file sts21.h + * @defgroup sts21 sts21 + * @{ + * + * ESP-IDF driver for humidty/temperature sensors sts2110/sts2115/sts2120 + * + * Copyright (c) 2021 Ruslan V. Uss <unclerus@gmail.com> + * + * BSD Licensed as described in the file LICENSE + */ +#ifndef __sts21_H__ +#define __sts21_H__ + +#include <i2cdev.h> +#include <stdbool.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Measurement resolutions + */ +typedef enum { + STS21_RESOLUTION_14 = 0, /**< 14 bits, <= 85 ms, default */ + STS21_RESOLUTION_13, /**< 13 bits, <= 43 ms */ + STS21_RESOLUTION_12, /**< 12 bits, <= 22 ms */ + STS21_RESOLUTION_11, /**< 11 bits, <= 11 ms */ +} sts21_resolution_t; + +/** + * Device descriptor + */ +typedef struct +{ + i2c_dev_t i2c_dev; + sts21_resolution_t resolution; +} sts21_t; + +/** + * @brief Initialize device descriptor + * + * @param dev Device descriptor + * @param port I2C port + * @param sda_gpio SDA GPIO + * @param scl_gpio SCL GPIO + * @return `ESP_OK` on success + */ +esp_err_t sts21_init_desc(sts21_t *dev, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio); + +/** + * @brief Free device descriptor + * + * @param dev Device descriptor + * @return `ESP_OK` on success + */ +esp_err_t sts21_free_desc(sts21_t *dev); + +/** + * @brief Init device + * + * Perform soft-reset, set resolution to 14 bits, switch off the heater. + * + * @param dev Device descriptor + * @return `ESP_OK` on success + */ +esp_err_t sts21_init(sts21_t *dev); + +/** + * @brief Get measurement resolution + * + * @param dev Device descriptor + * @param[out] res Measurement resolution + * @return `ESP_OK` on success + */ +esp_err_t sts21_get_resolution(sts21_t *dev, sts21_resolution_t *res); + +/** + * @brief Set measurement resolution + * + * @param dev Device descriptor + * @param res Measurement resolution + * @return `ESP_OK` on success + */ +esp_err_t sts21_set_resolution(sts21_t *dev, sts21_resolution_t res); + +/** + * @brief Get heater state + * + * @param dev Device descriptor + * @param[out] on true when heater is on + * @return `ESP_OK` on success + */ +esp_err_t sts21_get_heater_state(sts21_t *dev, bool *on); + +/** + * @brief Switch heater on/off + * + * @param dev Device descriptor + * @param on true to switch heater on + * @return `ESP_OK` on success + */ +esp_err_t sts21_set_heater_state(sts21_t *dev, bool on); + +/** + * @brief Get power state + * + * @param dev Device descriptor + * @param[out] alert true when power voltage < 2.25V + * @return `ESP_OK` on success + */ +esp_err_t sts21_get_power_alert(sts21_t *dev, bool *alert); + +/** + * @brief Get measurement status + * + * @param dev Device descriptor + * @param[out] busy true when device performs measurement + * @return `ESP_OK` on success + */ +esp_err_t sts21_is_busy(sts21_t *dev, bool *busy); + +/** + * @brief Trigger measurement + * + * Before reading the measurement result, you must wait a time depending + * on the resolution or use ::sts21_is_busy() function + * + * @param dev Device descriptor + * @return `ESP_OK` on success + */ +esp_err_t sts21_trigger_measurement(sts21_t *dev); + +/** + * @brief Read measurement result + * + * @param dev Device descriptor + * @param[out] t Temperature in degrees Celsius + * @return `ESP_OK` on success + */ +esp_err_t sts21_read_temperature(sts21_t *dev, float *t); + +/** + * @brief Measure temperature and read result + * + * The function combines function ::sts21_trigger_measurement() and function + * ::sts21_read_temperature() to get the measurement results. + * + * @param dev Device descriptor + * @param[out] t Temperature in degrees Celsius + * @return `ESP_OK` on success + */ +esp_err_t sts21_measure(sts21_t *dev, float *t); + +#ifdef __cplusplus +} +#endif + +/**@}*/ + +#endif /* __sts21_H__ */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/tca9548/.eil.yml Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,24 @@ +--- +components: + - name: tca9548 + description: | + Driver for TCA9548A/PCA9548A low-voltage 8-channel I2C switch + group: misc + groups: [] + code_owners: UncleRus + depends: + - i2cdev + - log + - esp_idf_lib_helpers + thread_safe: yes + targets: + - name: esp32 + - name: esp8266 + - name: esp32s2 + - name: esp32c3 + licenses: + - name: BSD-3 + copyrights: + - author: + name: UncleRus + year: 2020
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/tca9548/CMakeLists.txt Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,5 @@ +idf_component_register( + SRCS tca9548.c + INCLUDE_DIRS . + REQUIRES i2cdev log esp_idf_lib_helpers +)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/tca9548/LICENSE Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,26 @@ +Copyright (c) 2020 Ruslan V. Uss <unclerus@gmail.com> + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, +this list of conditions and the following disclaimer in the documentation +and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of itscontributors +may be used to endorse or promote products derived from this software without +specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/tca9548/component.mk Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,2 @@ +COMPONENT_ADD_INCLUDEDIRS = . +COMPONENT_DEPENDS = i2cdev log esp_idf_lib_helpers
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/tca9548/tca9548.c Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2020 Ruslan V. Uss <unclerus@gmail.com> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of itscontributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file tca9548.c + * + * ESP-IDF driver for low-voltage 8-channel I2C switch TCA9548/PCA9548 + * + * Copyright (c) 2020 Ruslan V. Uss <unclerus@gmail.com> + * + * BSD Licensed as described in the file LICENSE + */ +#include <esp_idf_lib_helpers.h> +#include <esp_log.h> +#include "tca9548.h" + +#define I2C_FREQ_HZ 100000 // 100kHz + +static const char *TAG = "tca9548"; + +#define BYTE_TO_BINARY_PATTERN "%c%c%c%c%c%c%c%c" +#define BYTE_TO_BINARY(byte) \ + (byte & BV(7) ? '1' : '0'), \ + (byte & BV(6) ? '1' : '0'), \ + (byte & BV(5) ? '1' : '0'), \ + (byte & BV(4) ? '1' : '0'), \ + (byte & BV(3) ? '1' : '0'), \ + (byte & BV(2) ? '1' : '0'), \ + (byte & BV(1) ? '1' : '0'), \ + (byte & BV(0) ? '1' : '0') + +#define CHECK_ARG(VAL) do { if (!(VAL)) return ESP_ERR_INVALID_ARG; } while (0) + +esp_err_t tca9548_init_desc(i2c_dev_t *dev, uint8_t addr, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio) +{ + CHECK_ARG(dev && addr >= TCA9548_ADDR_0 && addr <= TCA9548_ADDR_7); + + dev->port = port; + dev->addr = addr; + dev->cfg.sda_io_num = sda_gpio; + dev->cfg.scl_io_num = scl_gpio; +#if HELPER_TARGET_IS_ESP32 + dev->cfg.master.clk_speed = I2C_FREQ_HZ; +#endif + + return i2c_dev_create_mutex(dev); +} + +esp_err_t tca9548_free_desc(i2c_dev_t *dev) +{ + CHECK_ARG(dev); + + return i2c_dev_delete_mutex(dev); +} + +esp_err_t tca9548_set_channels(i2c_dev_t *dev, uint8_t channels) +{ + CHECK_ARG(dev); + + I2C_DEV_TAKE_MUTEX(dev); + I2C_DEV_CHECK(dev, i2c_dev_write(dev, NULL, 0, &channels, 1)); + I2C_DEV_GIVE_MUTEX(dev); + ESP_LOGD(TAG, "[0x%02x at %d] Channels set to 0x%02x (0b" BYTE_TO_BINARY_PATTERN ")", + dev->addr, dev->port, channels, BYTE_TO_BINARY(channels)); + + return ESP_OK; +} + +esp_err_t tca9548_get_channels(i2c_dev_t *dev, uint8_t *channels) +{ + CHECK_ARG(dev && channels); + + I2C_DEV_TAKE_MUTEX(dev); + I2C_DEV_CHECK(dev, i2c_dev_read(dev, NULL, 0, channels, 1)); + I2C_DEV_GIVE_MUTEX(dev); + + return ESP_OK; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/tca9548/tca9548.h Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,116 @@ +/* + * Copyright (c) 2020 Ruslan V. Uss <unclerus@gmail.com> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of itscontributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file tca9548.h + * @defgroup tca9548 tca9548 + * @{ + * + * ESP-IDF driver for low-voltage 8-channel I2C switch TCA9548/PCA9548 + * + * Copyright (c) 2020 Ruslan V. Uss <unclerus@gmail.com> + * + * BSD Licensed as described in the file LICENSE + */ +#ifndef __TCA9548_H__ +#define __TCA9548_H__ + +#include <stdbool.h> +#include <i2cdev.h> +#include <esp_err.h> + +#ifdef __cplusplus +extern "C" { +#endif + +#define TCA9548_ADDR_0 0x70 +#define TCA9548_ADDR_1 0x71 +#define TCA9548_ADDR_2 0x72 +#define TCA9548_ADDR_3 0x73 +#define TCA9548_ADDR_4 0x74 +#define TCA9548_ADDR_5 0x75 +#define TCA9548_ADDR_6 0x76 +#define TCA9548_ADDR_7 0x77 + +#ifndef BV +#define BV(x) (1 << (x)) +#endif + +#define TCA9548_CHANNEL0 BV(0) +#define TCA9548_CHANNEL1 BV(1) +#define TCA9548_CHANNEL2 BV(2) +#define TCA9548_CHANNEL3 BV(3) +#define TCA9548_CHANNEL4 BV(4) +#define TCA9548_CHANNEL5 BV(5) +#define TCA9548_CHANNEL6 BV(6) +#define TCA9548_CHANNEL7 BV(7) + +/** + * @brief Initialize device descriptor + * + * @param dev Device descriptor + * @param port I2C port + * @param addr Device address + * @param sda_gpio SDA GPIO pin + * @param scl_gpio SCL GPIO pin + * @return `ESP_OK` on success + */ +esp_err_t tca9548_init_desc(i2c_dev_t *dev, uint8_t addr, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio); + +/** + * @brief Free device descriptor + * + * @param dev Device descriptor + * @return `ESP_OK` on success + */ +esp_err_t tca9548_free_desc(i2c_dev_t *dev); + +/** + * @brief Switch channels + * + * @param dev Device descriptor + * @param channels Channel flags, combination of TCA9548_CHANNELn + * @return `ESP_OK` on success + */ +esp_err_t tca9548_set_channels(i2c_dev_t *dev, uint8_t channels); + +/** + * @brief Read current channels configuration + * + * @param dev Device descriptor + * @param[out] channels Channel flags, combination of TCA9548_CHANNELn + * @return `ESP_OK` on success + */ +esp_err_t tca9548_get_channels(i2c_dev_t *dev, uint8_t *channels); + +#ifdef __cplusplus +} +#endif + +/**@}*/ + +#endif /* __TCA9548_H__ */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/tca95x5/.eil.yml Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,24 @@ +--- +components: + - name: tca95x5 + description: | + Driver for TCA9535/TCA9555 remote 16-bit I/O expanders for I2C-bus + group: gpio + groups: [] + code_owners: UncleRus + depends: + - i2cdev + - log + - esp_idf_lib_helpers + thread_safe: yes + targets: + - name: esp32 + - name: esp8266 + - name: esp32s2 + - name: esp32c3 + licenses: + - name: BSD-3 + copyrights: + - author: + name: UncleRus + year: 2019
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/tca95x5/CMakeLists.txt Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,5 @@ +idf_component_register( + SRCS tca95x5.c + INCLUDE_DIRS . + REQUIRES i2cdev log esp_idf_lib_helpers +)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/tca95x5/LICENSE Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,26 @@ +Copyright (c) 2019 Ruslan V. Uss <unclerus@gmail.com> + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, +this list of conditions and the following disclaimer in the documentation +and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of itscontributors +may be used to endorse or promote products derived from this software without +specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/tca95x5/component.mk Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,2 @@ +COMPONENT_ADD_INCLUDEDIRS = . +COMPONENT_DEPENDS = i2cdev log esp_idf_lib_helpers
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/tca95x5/tca95x5.c Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,138 @@ +/* + * Copyright (c) 2019 Ruslan V. Uss <unclerus@gmail.com> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of itscontributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file tca95x5.c + * + * ESP-IDF driver for TCA9535/TCA9555 remote 16-bit I/O expanders for I2C-bus + * + * Copyright (c) 2019 Ruslan V. Uss <unclerus@gmail.com> + * + * BSD Licensed as described in the file LICENSE + */ + +#include <esp_idf_lib_helpers.h> +#include "tca95x5.h" + +#define I2C_FREQ_HZ 400000 + +#define REG_IN0 0x00 +#define REG_OUT0 0x02 +#define REG_CONF0 0x06 + +#define CHECK(x) do { esp_err_t __; if ((__ = x) != ESP_OK) return __; } while (0) +#define CHECK_ARG(VAL) do { if (!(VAL)) return ESP_ERR_INVALID_ARG; } while (0) +#define BV(x) (1 << (x)) + +static esp_err_t read_reg_16(i2c_dev_t *dev, uint8_t reg, uint16_t *val) +{ + CHECK_ARG(dev && val); + + I2C_DEV_TAKE_MUTEX(dev); + I2C_DEV_CHECK(dev, i2c_dev_read_reg(dev, reg, val, 2)); + I2C_DEV_GIVE_MUTEX(dev); + + return ESP_OK; +} + +static esp_err_t write_reg_16(i2c_dev_t *dev, uint8_t reg, uint16_t val) +{ + CHECK_ARG(dev); + + I2C_DEV_TAKE_MUTEX(dev); + I2C_DEV_CHECK(dev, i2c_dev_write_reg(dev, reg, &val, 2)); + I2C_DEV_GIVE_MUTEX(dev); + + return ESP_OK; +} + +/////////////////////////////////////////////////////////////////////////////// + +esp_err_t tca95x5_init_desc(i2c_dev_t *dev, uint8_t addr, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio) +{ + CHECK_ARG(dev && (addr & TCA95X5_I2C_ADDR_BASE)); + + dev->port = port; + dev->addr = addr; + dev->cfg.sda_io_num = sda_gpio; + dev->cfg.scl_io_num = scl_gpio; +#if HELPER_TARGET_IS_ESP32 + dev->cfg.master.clk_speed = I2C_FREQ_HZ; +#endif + + return i2c_dev_create_mutex(dev); +} + +esp_err_t tca95x5_free_desc(i2c_dev_t *dev) +{ + CHECK_ARG(dev); + + return i2c_dev_delete_mutex(dev); +} + +esp_err_t tca95x5_port_get_mode(i2c_dev_t *dev, uint16_t *mode) +{ + return read_reg_16(dev, REG_CONF0, mode); +} + +esp_err_t tca95x5_port_set_mode(i2c_dev_t *dev, uint16_t mode) +{ + return write_reg_16(dev, REG_CONF0, mode); +} + +esp_err_t tca95x5_port_read(i2c_dev_t *dev, uint16_t *val) +{ + return read_reg_16(dev, REG_IN0, val); +} + +esp_err_t tca95x5_port_write(i2c_dev_t *dev, uint16_t val) +{ + return write_reg_16(dev, REG_OUT0, val); +} + +esp_err_t tca95x5_get_level(i2c_dev_t *dev, uint8_t pin, uint32_t *val) +{ + uint16_t v; + CHECK(read_reg_16(dev, REG_IN0, &v)); + *val = v & BV(pin) ? 1 : 0; + + return ESP_OK; +} + +esp_err_t tca95x5_set_level(i2c_dev_t *dev, uint8_t pin, uint32_t val) +{ + uint16_t v; + + I2C_DEV_TAKE_MUTEX(dev); + I2C_DEV_CHECK(dev, i2c_dev_read_reg(dev, REG_OUT0, &v, 2)); + v = (v & ~BV(pin)) | (val ? BV(pin) : 0); + I2C_DEV_CHECK(dev, i2c_dev_write_reg(dev, REG_OUT0, &v, 2)); + I2C_DEV_GIVE_MUTEX(dev); + + return ESP_OK; +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/tca95x5/tca95x5.h Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,141 @@ +/* + * Copyright (c) 2019 Ruslan V. Uss <unclerus@gmail.com> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of itscontributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file tca95x5.h + * @defgroup tca95x5 tca95x5 + * @{ + * + * ESP-IDF driver for TCA9535/TCA9555 remote 16-bit I/O expanders for I2C-bus + * + * Copyright (c) 2019 Ruslan V. Uss <unclerus@gmail.com> + * + * BSD Licensed as described in the file LICENSE + */ +#ifndef __TCA95X5_H__ +#define __TCA95X5_H__ + +#include <stddef.h> +#include <i2cdev.h> +#include <esp_err.h> + +#ifdef __cplusplus +extern "C" { +#endif + +#define TCA95X5_I2C_ADDR_BASE 0x20 + +/** + * @brief Initialize device descriptor + * + * Default SCL frequency is 400kHz + * + * @param dev Pointer to I2C device descriptor + * @param addr I2C address (`0b0100<A2><A1><A0>`) + * @param port I2C port number + * @param sda_gpio SDA GPIO + * @param scl_gpio SCL GPIO + * @return `ESP_OK` on success + */ +esp_err_t tca95x5_init_desc(i2c_dev_t *dev, uint8_t addr, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio); + +/** + * @brief Free device descriptor + * @param dev Pointer to I2C device descriptor + * @return `ESP_OK` on success + */ +esp_err_t tca95x5_free_desc(i2c_dev_t *dev); + +/** + * @brief Get GPIO pins mode + * + * 0 - output, 1 - input for each bit in `val` + * + * @param dev Pointer to device descriptor + * @param[out] mode Buffer to store mode, 0 bit for P0.0 .. 15 bit for P1.7 + * @return `ESP_OK` on success + */ +esp_err_t tca95x5_port_get_mode(i2c_dev_t *dev, uint16_t *mode); + +/** + * @brief Set GPIO pins mode + * + * 0 - output, 1 - input for each bit in `val` + * + * @param dev Pointer to device descriptor + * @param mode Mode, 0 bit for P0.0 .. 15 bit for P1.7 + * @return `ESP_OK` on success + */ +esp_err_t tca95x5_port_set_mode(i2c_dev_t *dev, uint16_t mode); + +/** + * @brief Read GPIO port value + * + * @param dev Pointer to I2C device descriptor + * @param val 16-bit GPIO port value, 0 bit for P0.0 .. 15 bit for P1.7 + * @return `ESP_OK` on success + */ +esp_err_t tca95x5_port_read(i2c_dev_t *dev, uint16_t *val); + +/** + * @brief Write value to GPIO port + * + * @param dev Pointer to I2C device descriptor + * @param val GPIO port value, 0 bit for P0.0 .. 15 bit for P1.7 + * @return ESP_OK on success + */ +esp_err_t tca95x5_port_write(i2c_dev_t *dev, uint16_t val); + +/** + * @brief Read GPIO pin level + * + * @param dev Pointer to device descriptor + * @param pin Pin number, 0 for P0.0 .. 15 for P1.7 + * @param[out] val `true` if pin currently in high state + * @return `ESP_OK` on success + */ +esp_err_t tca95x5_get_level(i2c_dev_t *dev, uint8_t pin, uint32_t *val); + +/** + * @brief Set GPIO pin level + * + * Pin must be set up as output + * + * @param dev Pointer to device descriptor + * @param pin Pin number, 0 for P0.0 .. 15 for P1.7 + * @param[out] val `true` if pin currently in high state + * @return `ESP_OK` on success + */ +esp_err_t tca95x5_set_level(i2c_dev_t *dev, uint8_t pin, uint32_t val); + +#ifdef __cplusplus +} +#endif + +/**@}*/ + +#endif /* __TCA95X5_H__ */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/tda74xx/.eil.yml Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,23 @@ +--- +components: + - name: tda74xx + description: Driver for TDA7439/TDA7439DS/TDA7440D audioprocessors + group: misc + groups: [] + code_owners: UncleRus + depends: + - i2cdev + - log + - esp_idf_lib_helpers + thread_safe: yes + targets: + - name: esp32 + - name: esp8266 + - name: esp32s2 + - name: esp32c3 + licenses: + - name: MIT + copyrights: + - author: + name: UncleRus + year: 2018
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/tda74xx/CMakeLists.txt Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,5 @@ +idf_component_register( + SRCS tda74xx.c + INCLUDE_DIRS . + REQUIRES i2cdev log esp_idf_lib_helpers +)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/tda74xx/LICENSE Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 Ruslan V. Uss + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/tda74xx/component.mk Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,2 @@ +COMPONENT_ADD_INCLUDEDIRS = . +COMPONENT_DEPENDS = i2cdev log esp_idf_lib_helpers
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/tda74xx/tda74xx.c Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,201 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2018 Ruslan V. Uss <unclerus@gmail.com> + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +/** + * @file tda74xx.c + * + * ESP-IDF driver for TDA7439/TDA7439DS/TDA7440 audioprocessors + * + * Copyright (c) 2018, 2020 Ruslan V. Uss <unclerus@gmail.com> + * + * MIT Licensed as described in the file LICENSE + */ + +#include <esp_log.h> +#include <esp_idf_lib_helpers.h> +#include "tda74xx.h" + +#define I2C_FREQ_HZ 100000 // 100kHz + +static const char *TAG = "tda74xx"; + +#define REG_INPUT_SELECTOR 0x00 +#define REG_INPUT_GAIN 0x01 +#define REG_VOLUME 0x02 +#define REG_BASS_GAIN 0x03 +#define REG_MID_GAIN 0x04 +#define REG_TREBLE_GAIN 0x05 +#define REG_ATTEN_R 0x06 +#define REG_ATTEN_L 0x07 + +#define MUTE_VALUE 0x38 + +#define CHECK(x) do { esp_err_t __; if ((__ = x) != ESP_OK) return __; } while (0) +#define CHECK_ARG(VAL) do { if (!(VAL)) return ESP_ERR_INVALID_ARG; } while (0) + +static esp_err_t write_reg(i2c_dev_t *dev, uint8_t reg, uint8_t val) +{ + I2C_DEV_TAKE_MUTEX(dev); + I2C_DEV_CHECK(dev, i2c_dev_write_reg(dev, reg, &val, 1)); + I2C_DEV_GIVE_MUTEX(dev); + + ESP_LOGD(TAG, "%02x -> %02x", val, reg); + + return ESP_OK; +} + +static esp_err_t read_reg(i2c_dev_t *dev, uint8_t reg, uint8_t *val) +{ + I2C_DEV_TAKE_MUTEX(dev); + I2C_DEV_CHECK(dev, i2c_dev_read_reg(dev, reg, val, 1)); + I2C_DEV_GIVE_MUTEX(dev); + + ESP_LOGD(TAG, "%02x <- %02x", *val, reg); + + return ESP_OK; +} + +esp_err_t tda74xx_init_desc(i2c_dev_t *dev, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio) +{ + CHECK_ARG(dev); + + dev->port = port; + dev->addr = TDA74XX_ADDR; + dev->cfg.sda_io_num = sda_gpio; + dev->cfg.scl_io_num = scl_gpio; +#if HELPER_TARGET_IS_ESP32 + dev->cfg.master.clk_speed = I2C_FREQ_HZ; +#endif + + return i2c_dev_create_mutex(dev); +} + +esp_err_t tda74xx_free_desc(i2c_dev_t *dev) +{ + CHECK_ARG(dev); + + return i2c_dev_delete_mutex(dev); +} + +esp_err_t tda74xx_set_input(i2c_dev_t *dev, uint8_t input) +{ + CHECK_ARG(dev && input <= TDA74XX_MAX_INPUT); + + return write_reg(dev, REG_INPUT_SELECTOR, input); +} + +esp_err_t tda74xx_get_input(i2c_dev_t *dev, uint8_t *input) +{ + CHECK_ARG(dev && input); + + return read_reg(dev, REG_INPUT_SELECTOR, input); +} + +esp_err_t tda74xx_set_input_gain(i2c_dev_t *dev, uint8_t gain_db) +{ + CHECK_ARG(dev && gain_db <= TDA74XX_MAX_INPUT_GAIN); + + return write_reg(dev, REG_INPUT_GAIN, gain_db / 2); +} + +esp_err_t tda74xx_get_input_gain(i2c_dev_t *dev, uint8_t *gain_db) +{ + CHECK_ARG(dev && gain_db); + + CHECK(read_reg(dev, REG_INPUT_GAIN, gain_db)); + *gain_db *= 2; + + return ESP_OK; +} + +esp_err_t tda74xx_set_volume(i2c_dev_t *dev, int8_t volume_db) +{ + CHECK_ARG(dev && volume_db <= TDA74XX_MAX_VOLUME && volume_db >= TDA74XX_MIN_VOLUME); + + return write_reg(dev, REG_VOLUME, volume_db == TDA74XX_MIN_VOLUME ? MUTE_VALUE : -volume_db); +} + +esp_err_t tda74xx_get_volume(i2c_dev_t *dev, int8_t *volume_db) +{ + CHECK_ARG(dev && volume_db); + + CHECK(read_reg(dev, REG_VOLUME, (uint8_t *)volume_db)); + *volume_db = -*volume_db; + return ESP_OK; +} + +esp_err_t tda74xx_set_equalizer_gain(i2c_dev_t *dev, tda74xx_band_t band, int8_t gain_db) +{ + CHECK_ARG(dev && gain_db >= TDA74XX_MIN_EQ_GAIN && gain_db <= TDA74XX_MAX_EQ_GAIN); + + uint8_t reg; + switch(band) + { + case TDA74XX_BAND_BASS: + reg = REG_BASS_GAIN; + break; + case TDA74XX_BAND_MIDDLE: + reg = REG_MID_GAIN; + break; + default: + reg = REG_TREBLE_GAIN; + } + + return write_reg(dev, reg, (gain_db + 14) / 2); +} + +esp_err_t tda74xx_get_equalizer_gain(i2c_dev_t *dev, tda74xx_band_t band, int8_t *gain_db) +{ + CHECK_ARG(dev && gain_db); + + uint8_t reg; + switch(band) + { + case TDA74XX_BAND_BASS: + reg = REG_BASS_GAIN; + break; + case TDA74XX_BAND_MIDDLE: + reg = REG_MID_GAIN; + break; + default: + reg = REG_TREBLE_GAIN; + } + + CHECK(read_reg(dev, reg, (uint8_t *)gain_db)); + *gain_db = *gain_db * 2 - 14; + return ESP_OK; +} + +esp_err_t tda74xx_set_speaker_attenuation(i2c_dev_t *dev, tda74xx_channel_t channel, uint8_t atten_db) +{ + CHECK_ARG(dev && atten_db <= TDA74XX_MAX_ATTEN); + + return write_reg(dev, channel == TDA74XX_CHANNEL_LEFT ? REG_ATTEN_L : REG_ATTEN_R, atten_db); +} + +esp_err_t tda74xx_get_speaker_attenuation(i2c_dev_t *dev, tda74xx_channel_t channel, uint8_t *atten_db) +{ + CHECK_ARG(dev && atten_db); + + return read_reg(dev, channel == TDA74XX_CHANNEL_LEFT ? REG_ATTEN_L : REG_ATTEN_R, atten_db); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/tda74xx/tda74xx.h Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,196 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2018 Ruslan V. Uss <unclerus@gmail.com> + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +/** + * @file tda74xx.h + * @defgroup tda74xx tda74xx + * @{ + * + * ESP-IDF driver for TDA7439/TDA7439DS/TDA7440 audioprocessors + * + * Copyright (c) 2018 Ruslan V. Uss <unclerus@gmail.com> + * + * MIT Licensed as described in the file LICENSE + */ +#ifndef __TDA74XX_H__ +#define __TDA74XX_H__ + +#include <stdbool.h> +#include <i2cdev.h> +#include <esp_err.h> + +#ifdef __cplusplus +extern "C" { +#endif + +#define TDA74XX_ADDR 0x44 //!< I2C address + +#define TDA74XX_MAX_INPUT 3 //!< Maximum input number + +#define TDA74XX_MIN_VOLUME -48 //!< Mute volume level, dB +#define TDA74XX_MAX_VOLUME 0 //!< Maximum colume level, dB + +#define TDA74XX_MAX_INPUT_GAIN 30 //!< Maximum input gain, dB + +#define TDA74XX_MIN_EQ_GAIN -14 //!< Minimum equalizer gain, dB +#define TDA74XX_MAX_EQ_GAIN 14 //!< Maximum equalizer gain, dB + +#define TDA74XX_MAX_ATTEN 56 //!< Maximum speaker attenuation level, dB + +/** + * Audio channel + */ +typedef enum { + TDA74XX_CHANNEL_LEFT = 0, + TDA74XX_CHANNEL_RIGHT +} tda74xx_channel_t; + +/** + * Equalizer band + */ +typedef enum { + TDA74XX_BAND_BASS = 0, + TDA74XX_BAND_MIDDLE, //!< Not supported on TDA7440 + TDA74XX_BAND_TREBLE, +} tda74xx_band_t; + +/** + * @brief Initialize device descriptor + * + * @param dev Device descriptor + * @param port I2C port number + * @param sda_gpio GPIO pin number for SDA + * @param scl_gpio GPIO pin number for SCL + * @return `ESP_OK` on success + */ +esp_err_t tda74xx_init_desc(i2c_dev_t *dev, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio); + +/** + * @brief Free device descriptor + * + * @param dev Device descriptor + * @return `ESP_OK` on success + */ +esp_err_t tda74xx_free_desc(i2c_dev_t *dev); + +/** + * @brief Switch input + * + * @param dev Device descriptor + * @param input Input #, 0..3 + * @return `ESP_OK` on success + */ +esp_err_t tda74xx_set_input(i2c_dev_t *dev, uint8_t input); + +/** + * @brief Get current input + * + * @param dev Device descriptor + * @param[out] input Input #, 0..3 + * @return `ESP_OK` on success + */ +esp_err_t tda74xx_get_input(i2c_dev_t *dev, uint8_t *input); + +/** + * @brief Set input gain, dB + * + * @param dev Device descriptor + * @param gain_db Gain, 0..30 dB + * @return `ESP_OK` on success + */ +esp_err_t tda74xx_set_input_gain(i2c_dev_t *dev, uint8_t gain_db); + +/** + * @brief Get input gain + * + * @param dev Device descriptor + * @param[out] gain_db Gain, 0..30 dB + * @return `ESP_OK` on success + */ +esp_err_t tda74xx_get_input_gain(i2c_dev_t *dev, uint8_t *gain_db); + +/** + * @brief Set master volume + * + * @param dev Device descriptor + * @param volume_db Volume, -48..0 dB + * @return `ESP_OK` on success + */ +esp_err_t tda74xx_set_volume(i2c_dev_t *dev, int8_t volume_db); + +/** + * @brief Get master volume + * + * @param dev Device descriptor + * @param[out] volume_db Volume, -48..0 dB + * @return `ESP_OK` on success + */ +esp_err_t tda74xx_get_volume(i2c_dev_t *dev, int8_t *volume_db); + +/** + * @brief Set equalizer gain + * + * @param dev Device descriptor + * @param band Band + * @param gain_db Gain, -14..14 dB in 2 dB step + * @return `ESP_OK` on success + */ +esp_err_t tda74xx_set_equalizer_gain(i2c_dev_t *dev, tda74xx_band_t band, int8_t gain_db); + +/** + * @brief Get equalizer gain + * + * @param dev Device descriptor + * @param band Band + * @param[out] gain_db Gain, -14..14 dB in 2 dB step + * @return `ESP_OK` on success + */ +esp_err_t tda74xx_get_equalizer_gain(i2c_dev_t *dev, tda74xx_band_t band, int8_t *gain_db); + +/** + * @brief Attenuate speaker + * + * @param dev Device descriptor + * @param channel Audio channel + * @param atten_db Attenuation, 0..56 dB + * @return `ESP_OK` on success + */ +esp_err_t tda74xx_set_speaker_attenuation(i2c_dev_t *dev, tda74xx_channel_t channel, uint8_t atten_db); + +/** + * @brief Get speaker attenuation + * + * @param dev Device descriptor + * @param channel Audio channel + * @param[out] atten_db Attenuation, 0..56 dB + * @return `ESP_OK` on success + */ +esp_err_t tda74xx_get_speaker_attenuation(i2c_dev_t *dev, tda74xx_channel_t channel, uint8_t *atten_db); + +#ifdef __cplusplus +} +#endif + +/**@}*/ + +#endif /* __TDA74XX_H__ */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/tsl2561/.eil.yml Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,26 @@ +--- +components: + - name: tsl2561 + description: Driver for light-to-digital converter TSL2561 + group: light + groups: [] + code_owners: UncleRus + depends: + - i2cdev + - log + - esp_idf_lib_helpers + thread_safe: yes + targets: + - name: esp32 + - name: esp8266 + - name: esp32s2 + - name: esp32c3 + licenses: + - name: BSD-3 + copyrights: + - author: + name: UncleRus + year: 2018 + - author: + name: bschwind + year: 2016
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/tsl2561/CMakeLists.txt Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,5 @@ +idf_component_register( + SRCS tsl2561.c + INCLUDE_DIRS . + REQUIRES i2cdev log esp_idf_lib_helpers +)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/tsl2561/LICENSE Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,27 @@ +Copyright (c) 2016 Brian Schwind (https://github.com/bschwind) +Copyright (c) 2018 Ruslan V. Uss <unclerus@gmail.com> + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, +this list of conditions and the following disclaimer in the documentation +and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of itscontributors +may be used to endorse or promote products derived from this software without +specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/tsl2561/component.mk Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,2 @@ +COMPONENT_ADD_INCLUDEDIRS = . +COMPONENT_DEPENDS = i2cdev log esp_idf_lib_helpers
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/tsl2561/tsl2561.c Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,444 @@ +/* + * Copyright (c) 2016 Brian Schwind <https://github.com/bschwind> + * Copyright (c) 2018 Ruslan V. Uss <unclerus@gmail.com> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of itscontributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file tsl2561.c + * + * ESP-IDF driver for TSL2561 light-to-digital converter + * + * Ported from esp-open-rtos + * + * Copyright (c) 2016 Brian Schwind <https://github.com/bschwind>\n + * Copyright (c) 2018 Ruslan V. Uss <unclerus@gmail.com> + * + * BSD Licensed as described in the file LICENSE + */ + +#include <freertos/FreeRTOS.h> +#include <freertos/task.h> +#include <esp_log.h> +#include <esp_idf_lib_helpers.h> +#include "tsl2561.h" + +#define I2C_FREQ_HZ 400000 // 400kHz + +static const char *TAG = "tsl2561"; + +// Registers +#define TSL2561_REG_COMMAND 0x80 +#define TSL2561_REG_CONTROL 0x00 +#define TSL2561_REG_TIMING 0x01 +#define TSL2561_REG_THRESHOLD_LOW_0 0x02 +#define TSL2561_REG_THRESHOLD_LOW_1 0x03 +#define TSL2561_REG_THRESHOLD_HIGH_0 0x04 +#define TSL2561_REG_THRESHOLD_HIGH_1 0x05 +#define TSL2561_REG_INTERRUPT 0x06 +#define TSL2561_REG_PART_ID 0x0A +#define TSL2561_REG_CHANNEL_0_LOW 0x0C +#define TSL2561_REG_CHANNEL_0_HIGH 0x0D +#define TSL2561_REG_CHANNEL_1_LOW 0x0E +#define TSL2561_REG_CHANNEL_1_HIGH 0x0F + +// TSL2561 Misc Values +#define TSL2561_ON 0x03 +#define TSL2561_OFF 0x00 +#define TSL2561_READ_WORD 0x20 + +// Integration times in useconds +#define TSL2561_INTEGRATION_TIME_13MS 20 +#define TSL2561_INTEGRATION_TIME_101MS 110 +#define TSL2561_INTEGRATION_TIME_402MS 420 // Default + +// Calculation constants +#define LUX_SCALE 14 +#define RATIO_SCALE 9 +#define CH_SCALE 10 +#define CHSCALE_TINT0 0x7517 +#define CHSCALE_TINT1 0x0fe7 + +// Constants from the TSL2561 data sheet +#define K1T 0x0040 // 0.125 * 2^RATIO_SCALE +#define B1T 0x01f2 // 0.0304 * 2^LUX_SCALE +#define M1T 0x01be // 0.0272 * 2^LUX_SCALE +#define K2T 0x0080 // 0.250 * 2^RATIO_SCALE +#define B2T 0x0214 // 0.0325 * 2^LUX_SCALE +#define M2T 0x02d1 // 0.0440 * 2^LUX_SCALE +#define K3T 0x00c0 // 0.375 * 2^RATIO_SCALE +#define B3T 0x023f // 0.0351 * 2^LUX_SCALE +#define M3T 0x037b // 0.0544 * 2^LUX_SCALE +#define K4T 0x0100 // 0.50 * 2^RATIO_SCALE +#define B4T 0x0270 // 0.0381 * 2^LUX_SCALE +#define M4T 0x03fe // 0.0624 * 2^LUX_SCALE +#define K5T 0x0138 // 0.61 * 2^RATIO_SCALE +#define B5T 0x016f // 0.0224 * 2^LUX_SCALE +#define M5T 0x01fc // 0.0310 * 2^LUX_SCALE +#define K6T 0x019a // 0.80 * 2^RATIO_SCALE +#define B6T 0x00d2 // 0.0128 * 2^LUX_SCALE +#define M6T 0x00fb // 0.0153 * 2^LUX_SCALE +#define K7T 0x029a // 1.3 * 2^RATIO_SCALE +#define B7T 0x0018 // 0.00146 * 2^LUX_SCALE +#define M7T 0x0012 // 0.00112 * 2^LUX_SCALE +#define K8T 0x029a // 1.3 * 2^RATIO_SCALE +#define B8T 0x0000 // 0.000 * 2^LUX_SCALE +#define M8T 0x0000 // 0.000 * 2^LUX_SCALE +#define K1C 0x0043 // 0.130 * 2^RATIO_SCALE +#define B1C 0x0204 // 0.0315 * 2^LUX_SCALE +#define M1C 0x01ad // 0.0262 * 2^LUX_SCALE +#define K2C 0x0085 // 0.260 * 2^RATIO_SCALE +#define B2C 0x0228 // 0.0337 * 2^LUX_SCALE +#define M2C 0x02c1 // 0.0430 * 2^LUX_SCALE +#define K3C 0x00c8 // 0.390 * 2^RATIO_SCALE +#define B3C 0x0253 // 0.0363 * 2^LUX_SCALE +#define M3C 0x0363 // 0.0529 * 2^LUX_SCALE +#define K4C 0x010a // 0.520 * 2^RATIO_SCALE +#define B4C 0x0282 // 0.0392 * 2^LUX_SCALE +#define M4C 0x03df // 0.0605 * 2^LUX_SCALE +#define K5C 0x014d // 0.65 * 2^RATIO_SCALE +#define B5C 0x0177 // 0.0229 * 2^LUX_SCALE +#define M5C 0x01dd // 0.0291 * 2^LUX_SCALE +#define K6C 0x019a // 0.80 * 2^RATIO_SCALE +#define B6C 0x0101 // 0.0157 * 2^LUX_SCALE +#define M6C 0x0127 // 0.0180 * 2^LUX_SCALE +#define K7C 0x029a // 1.3 * 2^RATIO_SCALE +#define B7C 0x0037 // 0.00338 * 2^LUX_SCALE +#define M7C 0x002b // 0.00260 * 2^LUX_SCALE +#define K8C 0x029a // 1.3 * 2^RATIO_SCALE +#define B8C 0x0000 // 0.000 * 2^LUX_SCALE +#define M8C 0x0000 // 0.000 * 2^LUX_SCALE + +#define CHECK(x) do { esp_err_t __; if ((__ = x) != ESP_OK) return __; } while (0) +#define CHECK_ARG(VAL) do { if (!(VAL)) return ESP_ERR_INVALID_ARG; } while (0) +#define SLEEP_MS(x) do { vTaskDelay(pdMS_TO_TICKS(x)); } while (0) + +static inline esp_err_t write_register(tsl2561_t *dev, uint8_t reg, uint8_t value) +{ + return i2c_dev_write_reg(&dev->i2c_dev, TSL2561_REG_COMMAND | reg, &value, 1); +} + +static inline esp_err_t read_register(tsl2561_t *dev, uint8_t reg, uint8_t *value) +{ + return i2c_dev_read_reg(&dev->i2c_dev, TSL2561_REG_COMMAND | reg, value, 1); +} + +static inline esp_err_t read_register_16(tsl2561_t *dev, uint8_t low_register_addr, uint16_t *value) +{ + uint8_t buf[2]; + + CHECK(i2c_dev_read_reg(&dev->i2c_dev, TSL2561_REG_COMMAND | TSL2561_READ_WORD | low_register_addr, buf, 2)); + *value = ((uint16_t)buf[1] << 8) | buf[0]; + + return ESP_OK; +} + +static inline esp_err_t enable(tsl2561_t *dev) +{ + return write_register(dev, TSL2561_REG_CONTROL, TSL2561_ON); +} + +static inline esp_err_t disable(tsl2561_t *dev) +{ + return write_register(dev, TSL2561_REG_CONTROL, TSL2561_OFF); +} + +static inline esp_err_t get_channel_data(tsl2561_t *dev, uint16_t *channel0, uint16_t *channel1) +{ + int sleep_ms; + + I2C_DEV_TAKE_MUTEX(&dev->i2c_dev); + I2C_DEV_CHECK(&dev->i2c_dev, enable(dev)); + + // Since we just enabled the chip, we need to sleep + // for the chip's integration time so it can gather a reading + switch (dev->integration_time) + { + case TSL2561_INTEGRATION_13MS: + sleep_ms = TSL2561_INTEGRATION_TIME_13MS; + break; + case TSL2561_INTEGRATION_101MS: + sleep_ms = TSL2561_INTEGRATION_TIME_101MS; + break; + default: + sleep_ms = TSL2561_INTEGRATION_TIME_402MS; + break; + } + SLEEP_MS(sleep_ms); + + I2C_DEV_CHECK(&dev->i2c_dev, read_register_16(dev, TSL2561_REG_CHANNEL_0_LOW, channel0)); + I2C_DEV_CHECK(&dev->i2c_dev, read_register_16(dev, TSL2561_REG_CHANNEL_1_LOW, channel1)); + + I2C_DEV_CHECK(&dev->i2c_dev, disable(dev)); + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); + ESP_LOGD(TAG, "integration time: %d ms channel0: 0x%x channel1: 0x%x", sleep_ms, *channel0, *channel1); + + return ESP_OK; +} + +/////////////////////////////////////////////////////////////////////////////// + +esp_err_t tsl2561_init_desc(tsl2561_t *dev, uint8_t addr, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio) +{ + CHECK_ARG(dev); + + if (addr != TSL2561_I2C_ADDR_GND && addr != TSL2561_I2C_ADDR_FLOAT && addr != TSL2561_I2C_ADDR_VCC) + { + ESP_LOGE(TAG, "Invalid I2C address `0x%x`: must be one of 0x%x, 0x%x, 0x%x", + addr, TSL2561_I2C_ADDR_GND, TSL2561_I2C_ADDR_FLOAT, TSL2561_I2C_ADDR_VCC); + return ESP_ERR_INVALID_ARG; + } + + dev->i2c_dev.port = port; + dev->i2c_dev.addr = addr; + dev->i2c_dev.cfg.sda_io_num = sda_gpio; + dev->i2c_dev.cfg.scl_io_num = scl_gpio; +#if HELPER_TARGET_IS_ESP32 + dev->i2c_dev.cfg.master.clk_speed = I2C_FREQ_HZ; +#endif + + return i2c_dev_create_mutex(&dev->i2c_dev); +} + +esp_err_t tsl2561_free_desc(tsl2561_t *dev) +{ + CHECK_ARG(dev); + + return i2c_dev_delete_mutex(&dev->i2c_dev); +} + +esp_err_t tsl2561_init(tsl2561_t *dev) +{ + CHECK_ARG(dev); + + I2C_DEV_TAKE_MUTEX(&dev->i2c_dev); + I2C_DEV_CHECK(&dev->i2c_dev, enable(dev)); + uint8_t control_reg; + I2C_DEV_CHECK(&dev->i2c_dev, read_register(dev, TSL2561_REG_CONTROL, &control_reg)); + if ((control_reg & TSL2561_ON)!= TSL2561_ON) + { + ESP_LOGE(TAG, "Error initializing tsl2561, control register wasn't set to ON"); + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); + return ESP_ERR_INVALID_RESPONSE; + } + + // Fetch the package type + uint8_t part_reg; + I2C_DEV_CHECK(&dev->i2c_dev, read_register(dev, TSL2561_REG_PART_ID, &part_reg)); + dev->package_type = part_reg >> 6; + + // Fetch the gain and integration time + uint8_t timing_reg; + I2C_DEV_CHECK(&dev->i2c_dev, read_register(dev, TSL2561_REG_TIMING, &timing_reg)); + dev->gain = timing_reg & 0x10; + dev->integration_time = timing_reg & 0x03; + + I2C_DEV_CHECK(&dev->i2c_dev, disable(dev)); + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); + + return ESP_OK; +} + +esp_err_t tsl2561_set_integration_time(tsl2561_t *dev, tsl2561_integration_time_t integration_time) +{ + CHECK_ARG(dev); + + I2C_DEV_TAKE_MUTEX(&dev->i2c_dev); + I2C_DEV_CHECK(&dev->i2c_dev, enable(dev)); + + I2C_DEV_CHECK(&dev->i2c_dev, write_register(dev, TSL2561_REG_TIMING, integration_time | dev->gain)); + dev->integration_time = integration_time; + + I2C_DEV_CHECK(&dev->i2c_dev, disable(dev)); + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); + + return ESP_OK; +} + +esp_err_t tsl2561_set_gain(tsl2561_t *dev, tsl2561_gain_t gain) +{ + CHECK_ARG(dev); + + I2C_DEV_TAKE_MUTEX(&dev->i2c_dev); + I2C_DEV_CHECK(&dev->i2c_dev, enable(dev)); + + I2C_DEV_CHECK(&dev->i2c_dev, write_register(dev, TSL2561_REG_TIMING, gain | dev->integration_time)); + dev->gain = gain; + + I2C_DEV_CHECK(&dev->i2c_dev, disable(dev)); + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); + + return ESP_OK; +} + +esp_err_t tsl2561_read_lux(tsl2561_t *dev, uint32_t *lux) +{ + CHECK_ARG(dev && lux); + + uint32_t ch_scale, channel1, channel0; + + switch (dev->integration_time) + { + case TSL2561_INTEGRATION_13MS: + ch_scale = CHSCALE_TINT0; + break; + case TSL2561_INTEGRATION_101MS: + ch_scale = CHSCALE_TINT1; + break; + default: + ch_scale = (1 << CH_SCALE); + break; + } + + // Scale if gain is 1x + if (dev->gain == TSL2561_GAIN_1X) + // 16x is nominal, so if the gain is set to 1x then + // we need to scale by 16 + ch_scale = ch_scale << 4; + + uint16_t ch0 = 0; + uint16_t ch1 = 0; + + CHECK(get_channel_data(dev, &ch0, &ch1)); + + // Scale the channel values + channel0 = (ch0 * ch_scale) >> CH_SCALE; + channel1 = (ch1 * ch_scale) >> CH_SCALE; + + // Find the ratio of the channel values (channel1 / channel0) + // Protect against divide by zero + uint32_t ratio1 = 0; + if (channel0 != 0) + ratio1 = (channel1 << (RATIO_SCALE+1)) / channel0; + + // Round the ratio value + uint32_t ratio = (ratio1 + 1) >> 1; + + uint32_t b = 0; + uint32_t m = 0; + switch (dev->package_type) + { + case TSL2561_PACKAGE_CS: + if (ratio <= K1C) + { + b = B1C; + m = M1C; + } + else if (ratio <= K2C) + { + b = B2C; + m = M2C; + } + else if (ratio <= K3C) + { + b = B3C; + m = M3C; + } + else if (ratio <= K4C) + { + b = B4C; + m = M4C; + } + else if (ratio <= K5C) + { + b = B5C; + m = M5C; + } + else if (ratio <= K6C) + { + b = B6C; + m = M6C; + } + else if (ratio <= K7C) + { + b = B7C; + m = M7C; + } + else if (ratio > K8C) + { + b = B8C; + m = M8C; + } + + break; + case TSL2561_PACKAGE_T_FN_CL: + if (ratio <= K1T) + { + b = B1T; + m = M1T; + } + else if (ratio <= K2T) + { + b = B2T; + m = M2T; + } + else if (ratio <= K3T) + { + b = B3T; + m = M3T; + } + else if (ratio <= K4T) + { + b = B4T; + m = M4T; + } + else if (ratio <= K5T) + { + b = B5T; + m = M5T; + } + else if (ratio <= K6T) + { + b = B6T; + m = M6T; + } + else if (ratio <= K7T) + { + b = B7T; + m = M7T; + } + else if (ratio > K8T) + { + b = B8T; + m = M8T; + } + + break; + default: + ESP_LOGE(TAG, "Invalid package type in CalculateLux"); + return ESP_ERR_NOT_SUPPORTED; + } + + int32_t temp; + temp = ((channel0 * b) - (channel1 * m)); + + // Round lsb (2^(LUX_SCALE−1)) + temp += (1 << (LUX_SCALE - 1)); + + // Strip off fractional portion + *lux = temp >> LUX_SCALE; + + return ESP_OK; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/tsl2561/tsl2561.h Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,155 @@ +/* + * Copyright (c) 2016 Brian Schwind <https://github.com/bschwind> + * Copyright (c) 2018 Ruslan V. Uss <unclerus@gmail.com> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of itscontributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file tsl2561.h + * @defgroup tsl2561 tsl2561 + * @{ + * + * ESP-IDF driver for TSL2561 light-to-digital converter + * + * Ported from esp-open-rtos + * + * Copyright (c) 2016 Brian Schwind <https://github.com/bschwind>\n + * Copyright (c) 2018 Ruslan V. Uss <unclerus@gmail.com> + * + * BSD Licensed as described in the file LICENSE + */ + +#ifndef __TSL2561_H__ +#define __TSL2561_H__ + +#include <i2cdev.h> +#include <esp_err.h> + +#ifdef __cplusplus +extern "C" { +#endif + +#define TSL2561_I2C_ADDR_GND 0x29 +#define TSL2561_I2C_ADDR_FLOAT 0x39 //!< Default I2C address +#define TSL2561_I2C_ADDR_VCC 0x49 + +/** + * Integration time + */ +typedef enum +{ + TSL2561_INTEGRATION_13MS = 0, //!< 13ms + TSL2561_INTEGRATION_101MS, //!< 101ms + TSL2561_INTEGRATION_402MS //!< 402ms, default +} tsl2561_integration_time_t; + +/** + * Gain + */ +typedef enum +{ + TSL2561_GAIN_1X = 0x00, //!< Default + TSL2561_GAIN_16X = 0x10 +} tsl2561_gain_t; + +typedef enum +{ + TSL2561_PACKAGE_CS = 0, + TSL2561_PACKAGE_T_FN_CL +} tsl2561_package_t; + +/** + * Device descriptor + */ +typedef struct +{ + i2c_dev_t i2c_dev; + tsl2561_integration_time_t integration_time; + tsl2561_gain_t gain; + tsl2561_package_t package_type; +} tsl2561_t; + +/** + * @brief Initialize device descriptor + * + * @param dev Device descriptor + * @param addr I2C device address, `TSL2561_I2C_ADDR_...` const + * @param port I2C port + * @param sda_gpio SDA GPIO pin + * @param scl_gpio SCL GPIO pin + * @return `ESP_OK` on success + */ +esp_err_t tsl2561_init_desc(tsl2561_t *dev, uint8_t addr, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio); + +/** + * @brief Free device descriptor + * + * @param dev Device descriptor + * @return `ESP_OK` on success + */ +esp_err_t tsl2561_free_desc(tsl2561_t *dev); + +/** + * @brief Initialize device + * + * @param dev Device descriptor + * @return `ESP_OK` on success + */ +esp_err_t tsl2561_init(tsl2561_t *dev); + +/** + * @brief Set device integration time + * + * @param dev Device descriptor + * @param integration_time Integration time + * @return `ESP_OK` on success + */ +esp_err_t tsl2561_set_integration_time(tsl2561_t *dev, tsl2561_integration_time_t integration_time); + +/** + * @brief Set device gain + * + * @param dev Device descriptor + * @param gain Gain + * @return `ESP_OK` on success + */ +esp_err_t tsl2561_set_gain(tsl2561_t *dev, tsl2561_gain_t gain); + +/** + * @brief Read light intensity from device + * + * @param dev Device descriptor + * @param[out] lux Light intensity, lux + * @return `ESP_OK` on success + */ +esp_err_t tsl2561_read_lux(tsl2561_t *dev, uint32_t *lux); + +#ifdef __cplusplus +} +#endif + +/**@}*/ + +#endif // __TSL2561_H__
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/tsl2591/.eil.yml Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,23 @@ +--- +components: + - name: tsl2591 + description: Driver for light-to-digital converter TSL2591 + group: light + groups: [] + code_owners: UncleRus + depends: + - i2cdev + - log + - esp_idf_lib_helpers + thread_safe: yes + targets: + - name: esp32 + - name: esp8266 + - name: esp32s2 + - name: esp32c3 + licenses: + - name: MIT + copyrights: + - author: + name: juliandoerner + year: 2020
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/tsl2591/CMakeLists.txt Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,5 @@ +idf_component_register( + SRCS tsl2591.c + INCLUDE_DIRS . + REQUIRES i2cdev log esp_idf_lib_helpers +)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/tsl2591/LICENSE Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 Julian Doerner (https://github.com/juliandoerner) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/tsl2591/component.mk Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,2 @@ +COMPONENT_ADD_INCLUDEDIRS = . +COMPONENT_DEPENDS = i2cdev log esp_idf_lib_helpers
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/tsl2591/tsl2591.c Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,660 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2020 Julian Doerner <https://github.com/juliandoerner> + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +/** + * @file tsl2591.c + * + * ESP-IDF driver for TSL2591 light-to-digital. + * + * Copyright (c) 2020 Julian Doerner <https://github.com/juliandoerner> + * + * MIT Licensed as described in the file LICENSE + */ + +#include <freertos/FreeRTOS.h> +#include <freertos/task.h> +#include <esp_log.h> +#include <esp_idf_lib_helpers.h> +#include "tsl2591.h" + +#define I2C_FREQ_HZ 400000 // 400kHz + +static const char *TAG = "tsl2591"; + +// Registers +#define TSL2591_REG_COMMAND 0x80 +#define TSL2591_REG_ENABLE 0x00 +#define TSL2591_REG_CONTROL 0x01 +#define TSL2591_REG_AILTL 0x04 // ALS interrupt low threshold low byte +#define TSL2591_REG_AILTH 0x05 // ALS interrupt low threshold high byte +#define TSL2591_REG_AIHTL 0x06 // ALS interrupt high threshold low byte +#define TSL2591_REG_AIHTH 0x07 // ALS interrupt high threshold high byte +#define TSL2591_REG_NPAILTL 0x08 // No ALS persist interrupt low threshold low byte +#define TSL2591_REG_NPAILTH 0x09 // No ALS persist interrupt low threshold high byte +#define TSL2591_REG_NPAIHTL 0x0A // No ALS persist interrupt high threshold low byte +#define TSL2591_REG_NPAIHTH 0x0B // No ALS persist interrupt high threshold high byte +#define TSL2591_REG_PERSIST 0x0C // Interrupt persistence filter +#define TSL2591_REG_C0DATAL 0x14 +#define TSL2591_REG_C0DATAH 0x15 +#define TSL2591_REG_C1DATAL 0x16 +#define TSL2591_REG_C1DATAH 0x17 +#define TSL2591_REG_STATUS 0x13 + +// TSL2591 command register transaction mode. +#define TSL2591_TRANSACTION_NORMAL 0x20 // Normal transaction for addressing registers +#define TSL2591_TRANSACTION_SPECIAL 0x60 // Special transactions for interrupt clearing + +// TSL2591 command register special functions. +#define TSL2591_SPECIAL_SET_INTR 0x04 // Forces interrupt +#define TSL2591_SPECIAL_CLEAR_INTR 0x06 // Clear ALS interrupt +#define TSL2591_SPECIAL_CLEAR_BOTH 0x07 // Clear ALS and no persist ALS interrupt +#define TSL2591_SPECIAL_CLEAR_NP_INTR 0x0A // Clear no persist ALS interrupt + +// TSL2591 integration times in useconds. +#define TSL2591_INTEGRATION_TIME_100MS 110 +#define TSL2591_INTEGRATION_TIME_200MS 210 +#define TSL2591_INTEGRATION_TIME_300MS 310 +#define TSL2591_INTEGRATION_TIME_400MS 410 +#define TSL2591_INTEGRATION_TIME_500MS 510 +#define TSL2591_INTEGRATION_TIME_600MS 610 + +// TSL2591 status flags. +#define TSL2591_STATUS_ALS_INTR 0x10 +#define TSL2591_STATUS_ALS_NP_INTR 0x20 +#define TSL2591_STATUS_ALS_VALID 0x01 + +// Calculation constants. +#define TSL2591_LUX_DF 408.0F + +#define CHECK(x) do { esp_err_t __; if ((__ = x) != ESP_OK) return __; } while (0) +#define CHECK_ARG(VAL) do { if (!(VAL)) return ESP_ERR_INVALID_ARG; } while (0) +#define SLEEP_MS(x) do { vTaskDelay(pdMS_TO_TICKS(x)); } while (0) + +// Read/write to registers. +static inline esp_err_t write_register(tsl2591_t *dev, uint8_t reg, uint8_t value) +{ + ESP_LOGD(TAG, "Writing register: 0x%x; Data: 0x%x.", reg, value); + return i2c_dev_write_reg(&dev->i2c_dev, + TSL2591_REG_COMMAND | TSL2591_TRANSACTION_NORMAL | reg, &value, 1); +} + +static inline esp_err_t read_register(tsl2591_t *dev, uint8_t reg, uint8_t *value) +{ + esp_err_t err; + err = i2c_dev_read_reg(&dev->i2c_dev, + TSL2591_REG_COMMAND | TSL2591_TRANSACTION_NORMAL | reg, value, 1); + ESP_LOGD(TAG, "Red register: 0x%x; Data: 0x%x.", reg, *value); + return err; +} + +// Write special function to command register. +static inline esp_err_t write_special_function(tsl2591_t *dev, uint8_t special_function) +{ + uint8_t function = TSL2591_REG_COMMAND | TSL2591_TRANSACTION_SPECIAL | special_function; + ESP_LOGD(TAG, "Calling special function: 0x%x with 0x%x.", special_function, function); + return i2c_dev_write(&dev->i2c_dev, NULL, 0, &function, 8); +} + +// Read/write enable register. +static inline esp_err_t write_enable_register(tsl2591_t *dev, uint8_t value) +{ + return write_register(dev, TSL2591_REG_ENABLE, value); +} + +static inline esp_err_t read_enable_register(tsl2591_t *dev, uint8_t *value) +{ + return read_register(dev, TSL2591_REG_ENABLE, value); +} + +// Read/write control register. +static inline esp_err_t write_control_register(tsl2591_t *dev, uint8_t value) +{ + return write_register(dev, TSL2591_REG_CONTROL, value); +} + +static inline esp_err_t read_control_register(tsl2591_t *dev, uint8_t *value) +{ + return read_register(dev, TSL2591_REG_CONTROL, value); +} + +// Read 16 bit from two consecutive registers. +// Note that the sensor will shadow for example C0DATAH if C0DATAL is read. +static inline esp_err_t read_register16(tsl2591_t *dev, uint8_t low_register, uint16_t *value) +{ + uint8_t buf[2]; + CHECK(i2c_dev_read_reg(&dev->i2c_dev, + TSL2591_REG_COMMAND | TSL2591_TRANSACTION_NORMAL | low_register, buf, 2)); + *value = (uint16_t)buf[1] << 8 | buf[0]; + + return ESP_OK; + +} + + +// Initialization. +esp_err_t tsl2591_init_desc(tsl2591_t *dev, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio) +{ + CHECK_ARG(dev); + ESP_LOGD(TAG, "Initialize descriptor"); + + dev->i2c_dev.port = port; + dev->i2c_dev.addr = TSL2591_I2C_ADDR; // tsl2591 has only one i2c address. + dev->i2c_dev.cfg.sda_io_num = sda_gpio; + dev->i2c_dev.cfg.scl_io_num = scl_gpio; +#if HELPER_TARGET_IS_ESP32 + dev->i2c_dev.cfg.master.clk_speed = I2C_FREQ_HZ; +#endif + + return i2c_dev_create_mutex(&dev->i2c_dev); +} + +esp_err_t tsl2591_free_desc(tsl2591_t *dev) +{ + CHECK_ARG(dev); + ESP_LOGD(TAG, "Free descriptor."); + + return i2c_dev_delete_mutex(&dev->i2c_dev); +} + +esp_err_t tsl2591_init(tsl2591_t *dev) +{ + CHECK_ARG(dev); + ESP_LOGD(TAG, "Initialize sensor."); + + I2C_DEV_TAKE_MUTEX(&dev->i2c_dev); + + uint8_t tmp_reg = 0; + I2C_DEV_CHECK(&dev->i2c_dev, read_enable_register(dev, &tmp_reg)); + dev->settings.enable_reg = tmp_reg; + ESP_LOGD(TAG, "Initial enable register: %x.", tmp_reg); + + I2C_DEV_CHECK(&dev->i2c_dev, read_control_register(dev, &tmp_reg)); + dev->settings.control_reg = tmp_reg; + ESP_LOGD(TAG, "Initial control register: %x.", tmp_reg); + + I2C_DEV_CHECK(&dev->i2c_dev, read_register(dev, TSL2591_REG_PERSIST, &tmp_reg)); + dev->settings.persistence_reg = tmp_reg; + ESP_LOGD(TAG, "Initial persistence filter: %x.", tmp_reg); + + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); + + // Wait until the first integration cycle is completed. + tsl2591_integration_time_t integration_time; + ESP_ERROR_CHECK(tsl2591_get_integration_time(dev, &integration_time)); + switch (integration_time) + { + case TSL2591_INTEGRATION_100MS: + SLEEP_MS(110); + break; + case TSL2591_INTEGRATION_200MS: + SLEEP_MS(210); + break; + case TSL2591_INTEGRATION_300MS: + SLEEP_MS(310); + break; + case TSL2591_INTEGRATION_400MS: + SLEEP_MS(410); + break; + case TSL2591_INTEGRATION_500MS: + SLEEP_MS(510); + break; + case TSL2591_INTEGRATION_600MS: + SLEEP_MS(610); + break; + } + + return ESP_OK; +} + + +// Measure. +esp_err_t tsl2591_get_channel_data(tsl2591_t *dev, uint16_t *channel0, uint16_t *channel1) +{ + CHECK_ARG(dev && channel0 && channel1); + + I2C_DEV_TAKE_MUTEX(&dev->i2c_dev); + + I2C_DEV_CHECK(&dev->i2c_dev, read_register16(dev, TSL2591_REG_C0DATAL, channel0)); + I2C_DEV_CHECK(&dev->i2c_dev, read_register16(dev, TSL2591_REG_C1DATAL, channel1)); + + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); + ESP_LOGD(TAG, "channel0: 0x%x channel1: 0x%x.", *channel0, *channel1); + + return ESP_OK; +} + +esp_err_t tsl2591_calculate_lux(tsl2591_t *dev, uint16_t channel0, uint16_t channel1, float *lux) +{ + CHECK_ARG(dev && lux); + + float atime, again; + switch (dev->settings.control_reg & 0x07) + { + case TSL2591_INTEGRATION_100MS: + atime = 100; + break; + case TSL2591_INTEGRATION_200MS: + atime = 200; + break; + case TSL2591_INTEGRATION_300MS: + atime = 300; + break; + case TSL2591_INTEGRATION_400MS: + atime = 400; + break; + case TSL2591_INTEGRATION_500MS: + atime = 500; + break; + case TSL2591_INTEGRATION_600MS: + atime = 600; + break; + default: + atime = 100; + } + + switch (dev->settings.control_reg & TSL2591_GAIN_MAX) + { + case TSL2591_GAIN_LOW: + again = 1; + break; + case TSL2591_GAIN_MEDIUM: + again = 25; + break; + case TSL2591_GAIN_HIGH: + again = 428; + break; + case TSL2591_GAIN_MAX: + again = 9876; + break; + default: + again = 1; + } + + // See Adafruit Arduino driver. + float cpl = (atime * again) / TSL2591_LUX_DF; + *lux = (((float)channel0 - (float)channel1)) * + (1.0F - ((float)channel1 / (float)channel0)) / cpl; + + return ESP_OK; +} + +esp_err_t tsl2591_get_lux(tsl2591_t *dev, float *lux) +{ + CHECK_ARG(dev && lux); + + uint16_t channel0, channel1; + CHECK(tsl2591_get_channel_data(dev, &channel0, &channel1)); + + return tsl2591_calculate_lux(dev, channel0, channel1, lux); +} + +// Setters and getters enable register. +esp_err_t tsl2591_set_power_status(tsl2591_t *dev, tsl2591_power_status_t power_status) +{ + CHECK_ARG(dev); + + I2C_DEV_TAKE_MUTEX(&dev->i2c_dev); + + I2C_DEV_CHECK(&dev->i2c_dev, + write_enable_register(dev, (dev->settings.enable_reg & ~TSL2591_POWER_ON) | power_status)); + dev->settings.enable_reg = (dev->settings.enable_reg & ~TSL2591_POWER_ON) | power_status; + + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); + + return ESP_OK; +} + +esp_err_t tsl2591_get_power_status(tsl2591_t *dev, tsl2591_power_status_t *power_status) +{ + CHECK_ARG(dev && power_status); + + *power_status = dev->settings.enable_reg & TSL2591_POWER_ON; + + return ESP_OK; +} + +esp_err_t tsl2591_set_als_status(tsl2591_t *dev, tsl2591_als_status_t als_status) +{ + CHECK_ARG(dev); + + I2C_DEV_TAKE_MUTEX(&dev->i2c_dev); + + I2C_DEV_CHECK(&dev->i2c_dev, + write_enable_register(dev, (dev->settings.enable_reg & ~TSL2591_ALS_ON) | als_status)); + dev->settings.enable_reg = (dev->settings.enable_reg & ~TSL2591_ALS_ON) | als_status; + + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); + + return ESP_OK; +} + +esp_err_t tsl2591_get_als_status(tsl2591_t *dev, tsl2591_als_status_t *als_status) +{ + CHECK_ARG(dev && als_status); + + *als_status = dev->settings.enable_reg & TSL2591_ALS_ON; + + return ESP_OK; +} + +esp_err_t tsl2591_set_interrupt(tsl2591_t *dev, tsl2591_interrupt_t interrupt) +{ + CHECK_ARG(dev); + + I2C_DEV_TAKE_MUTEX(&dev->i2c_dev); + + I2C_DEV_CHECK(&dev->i2c_dev, + write_enable_register(dev, (dev->settings.enable_reg & ~TSL2591_ALS_INTR_BOTH_ON) | interrupt)); + dev->settings.enable_reg = (dev->settings.enable_reg & ~TSL2591_ALS_INTR_BOTH_ON) | interrupt; + + uint8_t tmp = 0; + I2C_DEV_CHECK(&dev->i2c_dev, + read_enable_register(dev, &tmp)); + + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); + + return ESP_OK; +} + +esp_err_t tsl2591_get_interrupt(tsl2591_t *dev, tsl2591_interrupt_t *interrupt) +{ + CHECK_ARG(dev && interrupt); + + *interrupt = dev->settings.enable_reg & TSL2591_ALS_INTR_BOTH_ON; + + return ESP_OK; +} + +esp_err_t tsl2591_set_sleep_after_intr(tsl2591_t *dev, tsl2591_sleep_after_intr_t sleep_after_intr) +{ + CHECK_ARG(dev); + + I2C_DEV_TAKE_MUTEX(&dev->i2c_dev); + + I2C_DEV_CHECK(&dev->i2c_dev, + write_enable_register(dev, (dev->settings.enable_reg & ~TSL2591_SLEEP_AFTER_ON) | sleep_after_intr)); + dev->settings.enable_reg = (dev->settings.enable_reg & ~TSL2591_SLEEP_AFTER_ON) | sleep_after_intr; + + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); + + return ESP_OK; +} + +esp_err_t tsl2591_get_sleep_after_intr(tsl2591_t *dev, tsl2591_sleep_after_intr_t *sleep_after_intr) +{ + CHECK_ARG(dev && sleep_after_intr); + + *sleep_after_intr = dev->settings.enable_reg & TSL2591_SLEEP_AFTER_ON; + + return ESP_OK; +} + + +// Setters and getters control register. +esp_err_t tsl2591_set_integration_time(tsl2591_t *dev, tsl2591_integration_time_t integration_time) +{ + CHECK_ARG(dev); + + I2C_DEV_TAKE_MUTEX(&dev->i2c_dev); + + // Last 3 bits represent the integration time. + I2C_DEV_CHECK(&dev->i2c_dev, + write_control_register(dev, (dev->settings.control_reg & ~0x07) | integration_time)); + dev->settings.control_reg = (dev->settings.control_reg & ~0x07) | integration_time; + + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); + + return ESP_OK; +} + +esp_err_t tsl2591_get_integration_time(tsl2591_t *dev, tsl2591_integration_time_t *integration_time) +{ + CHECK_ARG(dev && integration_time); + + // Last 3 bits represent the integration time. + *integration_time = dev->settings.control_reg & 0x07; + + return ESP_OK; +} + +esp_err_t tsl2591_set_gain(tsl2591_t *dev, tsl2591_gain_t gain) +{ + CHECK_ARG(dev); + + I2C_DEV_TAKE_MUTEX(&dev->i2c_dev); + + I2C_DEV_CHECK(&dev->i2c_dev, + write_control_register(dev, (dev->settings.control_reg & ~TSL2591_GAIN_MAX) | gain)); + dev->settings.control_reg = (dev->settings.control_reg & ~TSL2591_GAIN_MAX) | gain; + + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); + + return ESP_OK; +} + +esp_err_t tsl2591_get_gain(tsl2591_t *dev, tsl2591_gain_t *gain) +{ + CHECK_ARG(dev && gain); + + *gain = dev->settings.control_reg & TSL2591_GAIN_MAX; + + return ESP_OK; +} + + +// Setter and getter persistence filter. +esp_err_t tsl2591_set_persistence_filter(tsl2591_t *dev, tsl2591_persistence_filter_t filter) +{ + CHECK_ARG(dev); + + I2C_DEV_TAKE_MUTEX(&dev->i2c_dev); + + I2C_DEV_CHECK(&dev->i2c_dev, + write_register(dev, TSL2591_REG_PERSIST, (dev->settings.persistence_reg & ~TSL2591_60_CYCLES) | filter)); + dev->settings.persistence_reg = (dev->settings.persistence_reg & ~TSL2591_60_CYCLES) | filter; + + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); + + return ESP_OK; +} + +esp_err_t tsl2591_get_persistence_filter(tsl2591_t *dev, tsl2591_persistence_filter_t *filter) +{ + CHECK_ARG(dev && filter); + + *filter = dev->settings.persistence_reg & TSL2591_60_CYCLES; + + return ESP_OK; +} + + +// Setters thresholds. +esp_err_t tsl2591_als_set_low_threshold(tsl2591_t *dev, uint16_t low_threshold) +{ + CHECK_ARG(dev); + + I2C_DEV_TAKE_MUTEX(&dev->i2c_dev); + + I2C_DEV_CHECK(&dev->i2c_dev, write_register(dev, TSL2591_REG_AILTL, low_threshold)); + I2C_DEV_CHECK(&dev->i2c_dev, write_register(dev, TSL2591_REG_AILTH, low_threshold >> 8)); + + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); + + return ESP_OK; +} + +esp_err_t tsl2591_als_set_high_threshold(tsl2591_t *dev, uint16_t high_threshold) +{ + CHECK_ARG(dev); + + I2C_DEV_TAKE_MUTEX(&dev->i2c_dev); + + I2C_DEV_CHECK(&dev->i2c_dev, write_register(dev, TSL2591_REG_AIHTL, high_threshold)); + I2C_DEV_CHECK(&dev->i2c_dev, write_register(dev, TSL2591_REG_AIHTH, high_threshold >> 8)); + + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); + + return ESP_OK; +} + +esp_err_t tsl2591_no_persist_set_low_threshold(tsl2591_t *dev, uint16_t low_threshold) +{ + CHECK_ARG(dev); + + I2C_DEV_TAKE_MUTEX(&dev->i2c_dev); + + I2C_DEV_CHECK(&dev->i2c_dev, write_register(dev, TSL2591_REG_NPAILTL, low_threshold)); + I2C_DEV_CHECK(&dev->i2c_dev, write_register(dev, TSL2591_REG_NPAILTH, low_threshold >> 8)); + + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); + + return ESP_OK; +} + +esp_err_t tsl2591_no_persist_set_high_threshold(tsl2591_t *dev, uint16_t high_threshold) +{ + CHECK_ARG(dev); + + I2C_DEV_TAKE_MUTEX(&dev->i2c_dev); + + I2C_DEV_CHECK(&dev->i2c_dev, write_register(dev, TSL2591_REG_NPAIHTL, high_threshold)); + I2C_DEV_CHECK(&dev->i2c_dev, write_register(dev, TSL2591_REG_NPAIHTH, high_threshold >> 8)); + + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); + + return ESP_OK; +} + + +// Special functions. +esp_err_t tsl2591_set_test_intr(tsl2591_t *dev) +{ + CHECK_ARG(dev); + + I2C_DEV_TAKE_MUTEX(&dev->i2c_dev); + + I2C_DEV_CHECK(&dev->i2c_dev, + write_special_function(dev, TSL2591_SPECIAL_SET_INTR)); + + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); + + return ESP_OK; +} + +esp_err_t tsl2591_clear_als_intr(tsl2591_t *dev) +{ + CHECK_ARG(dev); + + I2C_DEV_TAKE_MUTEX(&dev->i2c_dev); + + I2C_DEV_CHECK(&dev->i2c_dev, + write_special_function(dev, TSL2591_SPECIAL_CLEAR_INTR)); + + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); + + return ESP_OK; +} + +esp_err_t tsl2591_clear_als_np_intr(tsl2591_t *dev) +{ + CHECK_ARG(dev); + + I2C_DEV_TAKE_MUTEX(&dev->i2c_dev); + + I2C_DEV_CHECK(&dev->i2c_dev, + write_special_function(dev, TSL2591_SPECIAL_CLEAR_NP_INTR)); + + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); + + return ESP_OK; +} + +esp_err_t tsl2591_clear_both_intr(tsl2591_t *dev) +{ + CHECK_ARG(dev); + + I2C_DEV_TAKE_MUTEX(&dev->i2c_dev); + + I2C_DEV_CHECK(&dev->i2c_dev, + write_special_function(dev, TSL2591_SPECIAL_CLEAR_BOTH)); + + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); + + return ESP_OK; +} + + +// Getters status flags. +esp_err_t tsl2591_get_np_intr_flag(tsl2591_t *dev, bool *flag) +{ + CHECK_ARG(dev && flag); + + uint8_t tmp; + + I2C_DEV_TAKE_MUTEX(&dev->i2c_dev); + + I2C_DEV_CHECK(&dev->i2c_dev, + read_register(dev, TSL2591_REG_STATUS, &tmp)); + + *flag = tmp & TSL2591_STATUS_ALS_NP_INTR ? true : false; + + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); + + return ESP_OK; +} + +esp_err_t tsl2591_get_als_intr_flag(tsl2591_t *dev, bool *flag) +{ + CHECK_ARG(dev && flag); + + uint8_t tmp; + + I2C_DEV_TAKE_MUTEX(&dev->i2c_dev); + + I2C_DEV_CHECK(&dev->i2c_dev, + read_register(dev, TSL2591_REG_STATUS, &tmp)); + + *flag = tmp & TSL2591_STATUS_ALS_INTR ? true : false; + + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); + + return ESP_OK; +} + +esp_err_t tsl2591_get_als_valid_flag(tsl2591_t *dev, bool *flag) +{ + CHECK_ARG(dev && flag); + + uint8_t tmp; + + I2C_DEV_TAKE_MUTEX(&dev->i2c_dev); + + I2C_DEV_CHECK(&dev->i2c_dev, + read_register(dev, TSL2591_REG_STATUS, &tmp)); + + *flag = tmp & TSL2591_STATUS_ALS_VALID? true : false; + + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); + + return ESP_OK; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/tsl2591/tsl2591.h Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,443 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2020 Julian Doerner <https://github.com/juliandoerner> + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +/** + * @file tsl2591.h + * @defgroup tsl2591 tsl2591 + * @{ + * + * ESP-IDF driver for TSL2591 light-to-digital. + * + * Copyright (c) 2020 Julian Doerner <https://github.com/juliandoerner> + * + * MIT Licensed as described in the file LICENSE + */ + +#ifndef __TSL2591_H__ +#define __TSL2591_H__ + +#include <i2cdev.h> +#include <esp_err.h> + +#ifdef __cplusplus +extern "C" { +#endif + +#define TSL2591_I2C_ADDR 0x29 // TSL2591 has only one i2c address. + +/** + * Power status. The sensor measures only if ALS an Power is on. + */ +typedef enum +{ + TSL2591_POWER_OFF = 0x00, + TSL2591_POWER_ON = 0x01 //!< Default +} tsl2591_power_status_t; + +/** + * ALS status. The sensor measures only if ALS and Power is on. + */ +typedef enum +{ + TSL2591_ALS_OFF = 0x00, + TSL2591_ALS_ON = 0x02 //!< Default +} tsl2591_als_status_t; + +/** + * Interrupts. TSL2591 has two interrupt sources. + * Check the datasheet for details. + */ +typedef enum +{ + TSL2591_INTR_OFF = 0x00, //!< Default + TSL2591_ALS_INTR_ON = 0x10, + TSL2591_ALS_INTR_NP_ON = 0x80, + TSL2591_ALS_INTR_BOTH_ON = 0x90 +} tsl2591_interrupt_t; + +/** + * Interrupt sleep setting. + */ +typedef enum +{ + TSL2591_SLEEP_AFTER_OFF = 0x00, //!< Default + TSL2591_SLEEP_AFTER_ON = 0x40 +} tsl2591_sleep_after_intr_t; + +/** + * Integration time. + */ +typedef enum +{ + TSL2591_INTEGRATION_100MS = 0, //!< Default + TSL2591_INTEGRATION_200MS, + TSL2591_INTEGRATION_300MS, + TSL2591_INTEGRATION_400MS, + TSL2591_INTEGRATION_500MS, + TSL2591_INTEGRATION_600MS +} tsl2591_integration_time_t; + +/** + * Gain. + */ +typedef enum +{ + TSL2591_GAIN_LOW = 0x00, //!< Default + TSL2591_GAIN_MEDIUM = 0x10, + TSL2591_GAIN_HIGH = 0x20, + TSL2591_GAIN_MAX = 0x30 +} tsl2591_gain_t; + +/** + * Persistence filter. + */ +typedef enum +{ + TSL2591_EVERY_CYCLE = 0, //!< Default + TSL2591_NO_PERSIST, + TSL2591_2_CYCLES, + TSL2591_3_CYCLES, + TSL2591_5_CYCLES, + TSL2591_10_CYCLES, + TSL2591_15_CYCLES, + TSL2591_20_CYCLES, + TSL2591_25_CYCLES, + TSL2591_30_CYCLES, + TSL2591_35_CYCLES, + TSL2591_40_CYCLES, + TSL2591_45_CYCLES, + TSL2591_50_CYCLES, + TSL2591_55_CYCLES, + TSL2591_60_CYCLES +} tsl2591_persistence_filter_t; + +/** + * Device settings. + */ +typedef struct +{ + uint8_t enable_reg; + uint8_t control_reg; + uint8_t persistence_reg; +} tsl2591_settings_t; + +/** + * Device descriptor. + */ +typedef struct +{ + i2c_dev_t i2c_dev; + tsl2591_settings_t settings; + +} tsl2591_t; + + +/** + * @brief Initialize device descriptor + * + * @param dev Device descriptor + * @param port I2C port + * @param sda_gpio SDA GPIO pin + * @param scl_gpio SCL GPIO pin + * @return `ESP_OK` on success + */ +esp_err_t tsl2591_init_desc(tsl2591_t *dev, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio); + +/** + * @brief Free device descriptor + * + * @param dev Device descriptor + * @return `ESP_OK` on success + */ +esp_err_t tsl2591_free_desc(tsl2591_t *dev); + +/** + * @brief Initialize device + * + * @param dev Device descriptor + * @return `ESP_OK` on success + */ +esp_err_t tsl2591_init(tsl2591_t *dev); + +/** + * @brief Read channel data + * + * @param dev Device descriptor + * @param[out] channel0 Channel 0 data + * @param[out] channel1 Channel 1 data + */ +esp_err_t tsl2591_get_channel_data(tsl2591_t *dev, uint16_t *channel0, uint16_t *channel1); + +/** + * @brief Calculate light intensity from channels + * + * @param dev Device descriptor + * @param channel0 Channel0 data + * @param channel1 Channel1 data + * @param[out] lux Light intensity + * @return `ESP_OK` on success + */ +esp_err_t tsl2591_calculate_lux(tsl2591_t *dev, uint16_t channel0, uint16_t channel1, float *lux); + +/** + * @brief Get and calculate light intensity + * + * @param dev Device descriptor + * @param[out] lux Light intensity + * @return `ESP_OK` + */ +esp_err_t tsl2591_get_lux(tsl2591_t *dev, float *lux); + +/** + * @brief Set device power status + * + * @param dev Device descriptor + * @param power_status Power status + * @return `ESP_OK` on success + */ +esp_err_t tsl2591_set_power_status(tsl2591_t *dev, tsl2591_power_status_t power_status); + +/** + * @brief Get device power status + * + * @param dev Device descriptor + * @param[out] power_status Power status + * @return `ESP_OK` on success + */ +esp_err_t tsl2591_get_power_status(tsl2591_t *dev, tsl2591_power_status_t *power_status); + +/** + * @brief Set device ALS status + * + * @param dev Device descriptor + * @param als_status Als status + * @return `ESP_OK` on success + */ +esp_err_t tsl2591_set_als_status(tsl2591_t *dev, tsl2591_als_status_t als_status); + +/** + * @brief Get device ALS status + * + * @param dev Device descriptor + * @param[out] als_status Als status + * @return `ESP_OK` + */ +esp_err_t tsl2591_get_als_status(tsl2591_t *dev, tsl2591_als_status_t *als_status); + +/** + * @brief Set device interrupt mode + * + * @param dev Device descriptor + * @param interrupt interrupt mode + * @return `ESP_OK` on success + */ +esp_err_t tsl2591_set_interrupt(tsl2591_t *dev, tsl2591_interrupt_t interrupt); + +/** + * @brief Get device interrupt mode + * + * @param dev Device descriptor + * @param[out] interrupt interrupt mode + * @return `ESP_OK` + */ +esp_err_t tsl2591_get_interrupt(tsl2591_t *dev, tsl2591_interrupt_t *interrupt); + +/** + * @brief Set sleeping after interrupt + * + * @param dev Device descriptor + * @param sleep_after_intr Sleeping after interrupt + * @return `ESP_OK` on success + */ +esp_err_t tsl2591_set_sleep_after_intr(tsl2591_t *dev, tsl2591_sleep_after_intr_t sleep_after_intr); + +/** + * @brief Get sleeping after interrupt setting + * + * @param dev Device descriptor + * @param[out] sleep_after_intr Sleeping after interrupt + * @return `ESP_OK` on success + */ +esp_err_t tsl2591_get_sleep_after_intr(tsl2591_t *dev, tsl2591_sleep_after_intr_t *sleep_after_intr); + +/** + * @brief Set device integration time + * + * @param dev Device descriptor + * @param integration_time Integration time + * @return `ESP_OK` on success + */ +esp_err_t tsl2591_set_integration_time(tsl2591_t *dev, tsl2591_integration_time_t integration_time); + +/** + * @brief Get device integration time + * + * @param dev Device descriptor + * @param[out] integration_time Integration time + * @return `ESP_OK` on success + */ +esp_err_t tsl2591_get_integration_time(tsl2591_t *dev, tsl2591_integration_time_t *integration_time); + +/** + * @brief Set device gain + * + * @param dev Device descriptor + * @param gain Gain + * @return `ESP_OK` on success + */ +esp_err_t tsl2591_set_gain(tsl2591_t *dev, tsl2591_gain_t gain); + +/** + * @brief Get device gain + * + * @param dev Device descriptor + * @param[out] gain Gain + * @return `ESP_OK` on success + */ +esp_err_t tsl2591_get_gain(tsl2591_t *dev, tsl2591_gain_t *gain); + +/** + * @brief Set device persistence filter + * + * @param dev Device descriptor + * @param filter Persistence filter + * @return `ESP_OK` on success + */ +esp_err_t tsl2591_set_persistence_filter(tsl2591_t *dev, tsl2591_persistence_filter_t filter); + +/** + * @brief Get device persistence filter + * + * @param dev Device descriptor + * @param[out] filter Persistence filter + * @return `ESP_OK` on success + */ +esp_err_t tsl2591_get_persistence_filter(tsl2591_t *dev, tsl2591_persistence_filter_t *filter); + +/** + * @brief Set ALS interrupt low threshold + * + * @param dev Device descriptor + * @param low_threshold Low threshold + * @return `ESP_OK` on success + */ +esp_err_t tsl2591_als_set_low_threshold(tsl2591_t *dev, uint16_t low_threshold); + +/** + * @brief Set ALS interrupt high threshold + * + * @param dev Device descriptor + * @param high_threshold High threshold + * @return `ESP_OK` on success + */ +esp_err_t tsl2591_als_set_high_threshold(tsl2591_t *dev, uint16_t high_threshold); + +/** + * @brief Set no persist ALS interrupt low threshold + * + * @param dev Device descriptor + * @param low_threshold Low threshold + * @return `ESP_OK` on success + */ +esp_err_t tsl2591_no_persist_set_low_threshold(tsl2591_t *dev, uint16_t low_threshold); + +/** + * @brief Set no persist ALS interrupt high threshold + * + * @param dev Device descriptor + * @param high_threshold High threshold + * @return `ESP_OK` on success + */ +esp_err_t tsl2591_no_persist_set_high_threshold(tsl2591_t *dev, uint16_t high_threshold); + +/** + * @brief Set interrupt + * + * At least on interrupt must be enabled. + * + * @param dev Device descriptor + * @return `ESP_PK` on success + */ +esp_err_t tsl2591_set_test_intr(tsl2591_t *dev); + +/** + * @brief Clear ALS interrupt + * + * @param dev Device descriptor + * @return `ESP_OK` on success + */ +esp_err_t tsl2591_clear_als_intr(tsl2591_t *dev); + +/** + * @brief Clear ALS no persist interrupt + * + * @param dev Device descriptor + * @return `ESP_OK` on success + */ +esp_err_t tsl2591_clear_als_np_intr(tsl2591_t *dev); + +/** + * @brief Clear both interrupts + * + * @param dev Device descriptor + * @return `ESP_OK` on success + */ +esp_err_t tsl2591_clear_both_intr(tsl2591_t *dev); + +/** + * @brief Get ALS no persist interrupt flag + * + * @param dev Device descriptor + * @param[out] flag Interrupt flag + * @return `ESP_OK` on success + */ +esp_err_t tsl2591_get_np_intr_flag(tsl2591_t *dev, bool *flag); + +/** + * @brief Get ALS interrupt flag + * + * @param dev Device descriptor + * @param[out] flag Interrupt flag + * @return `ESP_OK` on success + */ +esp_err_t tsl2591_get_als_intr_flag(tsl2591_t *dev, bool *flag); + +/** + * @brief Get ALS validity flag + * + * This flag is set when integration cycle is completed + * after enabling ALS. + * + * @param dev Device descriptor + * @param[out] flag Validity flag + * @return `ESP_OK` on success + */ +esp_err_t tsl2591_get_als_valid_flag(tsl2591_t *dev, bool *flag); + +#ifdef __cplusplus +} +#endif + +/**@}*/ + +#endif // __TSL2591_H__
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/tsl4531/.eil.yml Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,26 @@ +--- +components: + - name: tsl4531 + description: Driver for digital ambient light sensor TSL4531 + group: light + groups: [] + code_owners: UncleRus + depends: + - i2cdev + - log + - esp_idf_lib_helpers + thread_safe: yes + targets: + - name: esp32 + - name: esp8266 + - name: esp32s2 + - name: esp32c3 + licenses: + - name: BSD-3 + copyrights: + - author: + name: UncleRus + year: 2019 + - author: + name: bschwind + year: 2017
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/tsl4531/CMakeLists.txt Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,5 @@ +idf_component_register( + SRCS tsl4531.c + INCLUDE_DIRS . + REQUIRES i2cdev log esp_idf_lib_helpers +)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/tsl4531/LICENSE Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,27 @@ +Copyright (c) 2017 Brian Schwind (https://github.com/bschwind) +Copyright (c) 2019 Ruslan V. Uss <unclerus@gmail.com> + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, +this list of conditions and the following disclaimer in the documentation +and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of itscontributors +may be used to endorse or promote products derived from this software without +specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/tsl4531/component.mk Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,2 @@ +COMPONENT_ADD_INCLUDEDIRS = . +COMPONENT_DEPENDS = i2cdev log esp_idf_lib_helpers
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/tsl4531/tsl4531.c Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,206 @@ +/* + * Copyright (c) 2017 Brian Schwind <https://github.com/bschwind> + * Copyright (c) 2019 Ruslan V. Uss <unclerus@gmail.com> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of itscontributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file tsl4531.c + * + * ESP-IDF driver for digital ambient light sensor TSL4531 + * + * Ported from esp-open-rtos + * + * Copyright (c) 2017 Brian Schwind <https://github.com/bschwind>\n + * Copyright (c) 2019 Ruslan V. Uss <unclerus@gmail.com> + * + * BSD Licensed as described in the file LICENSE + */ + +#include <esp_log.h> +#include <freertos/FreeRTOS.h> +#include <freertos/task.h> +#include <esp_idf_lib_helpers.h> +#include "tsl4531.h" + +#define I2C_FREQ_HZ 400000 + +static const char *TAG = "tsl4531"; + +// Registers +#define TSL4531_REG_COMMAND 0x80 +#define TSL4531_REG_CONTROL 0x00 +#define TSL4531_REG_CONFIG 0x01 +#define TSL4531_REG_DATA_LOW 0x04 +#define TSL4531_REG_DATA_HIGH 0x05 +#define TSL4531_REG_DEVICE_ID 0x0A + +// TSL4531 Misc Values +#define TSL4531_ON 0x03 +#define TSL4531_OFF 0x00 + +// Integration times in milliseconds +#define TSL4531_INTEGRATION_TIME_100MS 120 +#define TSL4531_INTEGRATION_TIME_200MS 240 +#define TSL4531_INTEGRATION_TIME_400MS 480 // Default + +#define CHECK(x) do { esp_err_t __; if ((__ = x) != ESP_OK) return __; } while (0) +#define CHECK_ARG(VAL) do { if (!(VAL)) return ESP_ERR_INVALID_ARG; } while (0) + +inline static esp_err_t write_register(tsl4531_t *dev, uint8_t reg, uint8_t val) +{ + return i2c_dev_write_reg(&dev->i2c_dev, TSL4531_REG_COMMAND | reg, &val, 1); +} + +inline static esp_err_t read_register(tsl4531_t *dev, uint8_t reg, uint8_t *val) +{ + return i2c_dev_read_reg(&dev->i2c_dev, TSL4531_REG_COMMAND | reg, val, 1); +} + +inline static esp_err_t read_register_16(tsl4531_t *dev, uint8_t reg, uint16_t *val) +{ + return i2c_dev_read_reg(&dev->i2c_dev, TSL4531_REG_COMMAND | reg, val, 2); +} + +inline static esp_err_t enable(tsl4531_t *dev) +{ + return write_register(dev, TSL4531_REG_CONTROL, TSL4531_ON); +} + +inline static esp_err_t disable(tsl4531_t *dev) +{ + return write_register(dev, TSL4531_REG_CONTROL, TSL4531_OFF); +} + +/////////////////////////////////////////////////////////////////////////////// + +esp_err_t tsl4531_init_desc(tsl4531_t *dev, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio) +{ + CHECK_ARG(dev); + + dev->i2c_dev.port = port; + dev->i2c_dev.addr = TSL4531_I2C_ADDR; + dev->i2c_dev.cfg.sda_io_num = sda_gpio; + dev->i2c_dev.cfg.scl_io_num = scl_gpio; +#if HELPER_TARGET_IS_ESP32 + dev->i2c_dev.cfg.master.clk_speed = I2C_FREQ_HZ; +#endif + + return i2c_dev_create_mutex(&dev->i2c_dev); +} + +esp_err_t tsl4531_free_desc(tsl4531_t *dev) +{ + CHECK_ARG(dev); + + return i2c_dev_delete_mutex(&dev->i2c_dev); +} + +esp_err_t tsl4531_init(tsl4531_t *dev) +{ + CHECK_ARG(dev); + + I2C_DEV_TAKE_MUTEX(&dev->i2c_dev); + I2C_DEV_CHECK(&dev->i2c_dev, enable(dev)); + + uint8_t control_reg; + I2C_DEV_CHECK(&dev->i2c_dev, read_register(dev, TSL4531_REG_CONTROL, &control_reg)); + if (control_reg != TSL4531_ON) + { + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); + ESP_LOGE(TAG, "Error initializing TSL4531, control register wasn't set to ON"); + return ESP_ERR_INVALID_RESPONSE; + } + + uint8_t id; + I2C_DEV_CHECK(&dev->i2c_dev, read_register(dev, TSL4531_REG_DEVICE_ID, &id)); + id >>= 4; + switch (id) + { + case TSL4531_PART_TSL45317: + case TSL4531_PART_TSL45313: + case TSL4531_PART_TSL45315: + case TSL4531_PART_TSL45311: + dev->part_id = id; + break; + default: + ESP_LOGW(TAG, "Unknown part id for TSL4531 sensor: %u", id); + } + + I2C_DEV_CHECK(&dev->i2c_dev, disable(dev)); + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); + + return ESP_OK; +} + +esp_err_t tsl4531_config(tsl4531_t *dev, tsl4531_integration_time_t integration_time, bool skip_power_save) +{ + CHECK_ARG(dev); + + I2C_DEV_TAKE_MUTEX(&dev->i2c_dev); + I2C_DEV_CHECK(&dev->i2c_dev, enable(dev)); + I2C_DEV_CHECK(&dev->i2c_dev, write_register(dev, TSL4531_REG_CONFIG, + (skip_power_save ? 0x08 : 0x00) | (0x03 & integration_time))); + I2C_DEV_CHECK(&dev->i2c_dev, disable(dev)); + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); + + dev->integration_time = integration_time; + dev->skip_power_save = skip_power_save; + + return ESP_OK; +} + +esp_err_t tsl4531_read_lux(tsl4531_t *dev, uint16_t *lux) +{ + CHECK_ARG(dev && lux); + + I2C_DEV_TAKE_MUTEX(&dev->i2c_dev); + I2C_DEV_CHECK(&dev->i2c_dev, enable(dev)); + + uint16_t multiplier; + switch (dev->integration_time) + { + case TSL4531_INTEGRATION_100MS: + multiplier = 4; + vTaskDelay(pdMS_TO_TICKS(TSL4531_INTEGRATION_TIME_100MS)); + break; + case TSL4531_INTEGRATION_200MS: + multiplier = 2; + vTaskDelay(pdMS_TO_TICKS(TSL4531_INTEGRATION_TIME_200MS)); + break; + default: + multiplier = 1; + vTaskDelay(pdMS_TO_TICKS(TSL4531_INTEGRATION_TIME_400MS)); + } + + uint16_t lux_data; + I2C_DEV_CHECK(&dev->i2c_dev, read_register_16(dev, TSL4531_REG_DATA_LOW, &lux_data)); + I2C_DEV_CHECK(&dev->i2c_dev, disable(dev)); + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); + + *lux = multiplier * lux_data; + + return ESP_OK; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/tsl4531/tsl4531.h Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,141 @@ +/* + * Copyright (c) 2017 Brian Schwind <https://github.com/bschwind> + * Copyright (c) 2019 Ruslan V. Uss <unclerus@gmail.com> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of itscontributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file tsl4531.h + * @defgroup tsl4531 tsl4531 + * @{ + * + * ESP-IDF driver for digital ambient light sensor TSL4531 + * + * Ported from esp-open-rtos + * + * Copyright (c) 2017 Brian Schwind <https://github.com/bschwind>\n + * Copyright (c) 2019 Ruslan V. Uss <unclerus@gmail.com> + * + * BSD Licensed as described in the file LICENSE + */ +#ifndef __TSL4531_H__ +#define __TSL4531_H__ + +#include <stdint.h> +#include <stdbool.h> +#include <i2cdev.h> +#include <esp_err.h> + +#ifdef __cplusplus +extern "C" { +#endif + +#define TSL4531_I2C_ADDR 0x29 + +/** + * Integration time + */ +typedef enum +{ + TSL4531_INTEGRATION_400MS = 0x00, //!< Default + TSL4531_INTEGRATION_200MS = 0x01, + TSL4531_INTEGRATION_100MS = 0x02, +} tsl4531_integration_time_t; + +/** + * Part IDs + */ +typedef enum +{ + TSL4531_PART_TSL45317 = 0x08, + TSL4531_PART_TSL45313 = 0x09, + TSL4531_PART_TSL45315 = 0x0A, + TSL4531_PART_TSL45311 = 0x0B +} tsl4531_part_id_t; + +/** + * Device descriptor + */ +typedef struct { + i2c_dev_t i2c_dev; + tsl4531_integration_time_t integration_time; + bool skip_power_save; + tsl4531_part_id_t part_id; +} tsl4531_t; + +/** + * @brief Initialize device descriptor + * + * @param dev Device descriptor + * @param port I2C port + * @param sda_gpio SDA GPIO pin + * @param scl_gpio SCL GPIO pin + * @return `ESP_OK` on success + */ +esp_err_t tsl4531_init_desc(tsl4531_t *dev, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio); + +/** + * @brief Free device descriptor + * + * @param dev Device descriptor + * @return `ESP_OK` on success + */ +esp_err_t tsl4531_free_desc(tsl4531_t *dev); + +/** + * @brief Initialize device + * + * @param dev Device descriptor + * @return `ESP_OK` on success + */ +esp_err_t tsl4531_init(tsl4531_t *dev); + +/** + * @brief Configure device + * + * @param dev Device descriptor + * @param integration_time Integration time + * @param skip_power_save PowerSave Mode. When true, the power save states are + * skipped following a light integration cycle for shorter sampling rates + * @return `ESP_OK` on success + */ +esp_err_t tsl4531_config(tsl4531_t *dev, tsl4531_integration_time_t integration_time, bool skip_power_save); + +/** + * @brief Read conversion results in lux + * + * @param dev Device descriptor + * @param[out] lux Conversion result in lux + * @return `ESP_OK` on success + */ +esp_err_t tsl4531_read_lux(tsl4531_t *dev, uint16_t *lux); + +#ifdef __cplusplus +} +#endif + +/**@}*/ + +#endif // __TSL4531_H__
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/tsys01/.eil.yml Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,24 @@ +--- +components: + - name: tsys01 + description: | + Driver for precision digital temperature sensor TSYS01 + group: temperature + groups: [] + code_owners: UncleRus + depends: + - i2cdev + - log + - esp_idf_lib_helpers + thread_safe: yes + targets: + - name: esp32 + - name: esp8266 + - name: esp32s2 + - name: esp32c3 + licenses: + - name: BSD-3 + copyrights: + - author: + name: UncleRus + year: 2020
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/tsys01/CMakeLists.txt Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,5 @@ +idf_component_register( + SRCS tsys01.c + INCLUDE_DIRS . + REQUIRES i2cdev log esp_idf_lib_helpers +)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/tsys01/LICENSE Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,26 @@ +Copyright 2020 Ruslan V. Uss <unclerus@gmail.com> + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, +this list of conditions and the following disclaimer in the documentation +and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of itscontributors +may be used to endorse or promote products derived from this software without +specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/tsys01/component.mk Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,2 @@ +COMPONENT_ADD_INCLUDEDIRS = . +COMPONENT_DEPENDS = i2cdev log esp_idf_lib_helpers
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/tsys01/tsys01.c Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,177 @@ +/* + * Copyright (c) 2020 Ruslan V. Uss <unclerus@gmail.com> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of itscontributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file tsys01.c + * + * ESP-IDF driver for digital temperature sensor TSYS01 + * + * Copyright (c) 2020 Ruslan V. Uss <unclerus@gmail.com> + * + * BSD Licensed as described in the file LICENSE + */ +#include <esp_log.h> +#include <esp_idf_lib_helpers.h> +#include <freertos/FreeRTOS.h> +#include <freertos/task.h> +#include "tsys01.h" + +#define I2C_FREQ_HZ 1000000 // 1MHz + +static const char *TAG = "tsys01"; + +#define CMD_RESET 0x1e +#define CMD_START 0x48 +#define CMD_READ 0x00 +#define CMD_PROM 0xa0 +#define CMD_SERIAL 0xac + +#define CHECK(x) do { esp_err_t __; if ((__ = x) != ESP_OK) return __; } while (0) +#define CHECK_ARG(VAL) do { if (!(VAL)) return ESP_ERR_INVALID_ARG; } while (0) + +inline static esp_err_t send_cmd_nolock(tsys01_t *dev, uint8_t cmd) +{ + return i2c_dev_write(&dev->i2c_dev, NULL, 0, &cmd, 1); +} + +static esp_err_t send_cmd(tsys01_t *dev, uint8_t cmd) +{ + I2C_DEV_TAKE_MUTEX(&dev->i2c_dev); + I2C_DEV_CHECK(&dev->i2c_dev, send_cmd_nolock(dev, cmd)); + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); + + return ESP_OK; +} + +inline static float calc_temp(tsys01_t *dev, uint16_t raw) +{ + return -2.0f * dev->cal[1] / 1000000000000000000000.0f * raw * raw * raw * raw + + 4.0f * dev->cal[2] / 10000000000000000.0f * raw * raw * raw + + -2.0f * dev->cal[3] / 100000000000.0f * raw * raw + + 1.0f * dev->cal[4] / 1000000.0f * raw + + -1.5f * dev->cal[5] / 100.0f; +} + +static esp_err_t get_temp_nolock(tsys01_t *dev, uint32_t *raw, float *t) +{ + uint8_t r[3]; + CHECK(i2c_dev_read_reg(&dev->i2c_dev, CMD_READ, r, 3)); + + if (raw) + *raw = ((uint32_t)r[0] << 16) | ((uint32_t)r[1] << 8) | r[2]; + if (t) + *t = calc_temp(dev, ((uint16_t)r[0] << 8) | r[1]); + + return ESP_OK; +} + +/////////////////////////////////////////////////////////////////////////////// + +esp_err_t tsys01_init_desc(tsys01_t *dev, uint8_t addr, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio) +{ + CHECK_ARG(dev); + + if (addr != TSYS01_I2C_ADDR1 && addr != TSYS01_I2C_ADDR2) + { + ESP_LOGE(TAG, "Invalid I2C address"); + return ESP_ERR_INVALID_ARG; + } + + dev->i2c_dev.port = port; + dev->i2c_dev.addr = addr; + dev->i2c_dev.cfg.sda_io_num = sda_gpio; + dev->i2c_dev.cfg.scl_io_num = scl_gpio; +#if HELPER_TARGET_IS_ESP32 + dev->i2c_dev.cfg.master.clk_speed = I2C_FREQ_HZ; +#endif + + return i2c_dev_create_mutex(&dev->i2c_dev); +} + +esp_err_t tsys01_free_desc(tsys01_t *dev) +{ + CHECK_ARG(dev); + + return i2c_dev_delete_mutex(&dev->i2c_dev); +} + +esp_err_t tsys01_init(tsys01_t *dev) +{ + CHECK_ARG(dev); + + I2C_DEV_TAKE_MUTEX(&dev->i2c_dev); + for (size_t i = 0; i < 8; i++) + { + I2C_DEV_CHECK(&dev->i2c_dev, i2c_dev_read_reg(&dev->i2c_dev, CMD_PROM + i * 2, dev->cal + i, 2)); + dev->cal[i] = (dev->cal[i] >> 8) | (dev->cal[i] << 8); + } + uint8_t r[4]; + I2C_DEV_CHECK(&dev->i2c_dev, i2c_dev_read_reg(&dev->i2c_dev, CMD_SERIAL, r, 2)); + I2C_DEV_CHECK(&dev->i2c_dev, i2c_dev_read_reg(&dev->i2c_dev, CMD_SERIAL + 2, r + 2, 2)); + dev->serial = ((uint32_t)r[0] << 16) | ((uint32_t)r[1] << 8) | r[2]; + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); + + return ESP_OK; +} + +esp_err_t tsys01_reset(tsys01_t *dev) +{ + CHECK_ARG(dev); + + return send_cmd(dev, CMD_RESET); +} + +esp_err_t tsys01_start(tsys01_t *dev) +{ + CHECK_ARG(dev); + + return send_cmd(dev, CMD_START); +} + +esp_err_t tsys01_get_temp(tsys01_t *dev, uint32_t *raw, float *t) +{ + CHECK_ARG(dev && (raw || t)); + + I2C_DEV_TAKE_MUTEX(&dev->i2c_dev); + I2C_DEV_CHECK(&dev->i2c_dev, get_temp_nolock(dev, raw, t)); + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); + + return ESP_OK; +} + +esp_err_t tsys01_measure(tsys01_t *dev, float *t) +{ + CHECK_ARG(dev && t); + + I2C_DEV_TAKE_MUTEX(&dev->i2c_dev); + I2C_DEV_CHECK(&dev->i2c_dev, send_cmd_nolock(dev, CMD_START)); + vTaskDelay(pdMS_TO_TICKS(10)); + I2C_DEV_CHECK(&dev->i2c_dev, get_temp_nolock(dev, NULL, t)); + I2C_DEV_GIVE_MUTEX(&dev->i2c_dev); + + return ESP_OK; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/tsys01/tsys01.h Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,137 @@ +/* + * Copyright (c) 2020 Ruslan V. Uss <unclerus@gmail.com> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of itscontributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file tsys01.h + * @defgroup tsys01 tsys01 + * @{ + * + * ESP-IDF driver for digital temperature sensor TSYS01 + * + * Copyright (c) 2020 Ruslan V. Uss <unclerus@gmail.com> + * + * BSD Licensed as described in the file LICENSE + */ +#ifndef __TSYS01_H__ +#define __TSYS01_H__ + +#include <i2cdev.h> +#include <esp_err.h> + +#ifdef __cplusplus +extern "C" { +#endif + +#define TSYS01_I2C_ADDR1 0x76 +#define TSYS01_I2C_ADDR2 0x77 + +/** + * Device descriptor + */ +typedef struct +{ + i2c_dev_t i2c_dev; //!< I2C device descriptor + uint16_t cal[8]; //!< Calibration values + uint32_t serial; //!< Serial number +} tsys01_t; + +/** + * @brief Initialize device descriptor. + * + * @param dev Device descriptor + * @param addr Device I2C address + * @param port I2C port + * @param sda_gpio SDA GPIO + * @param scl_gpio SCL GPIO + * @return `ESP_OK` on success + */ +esp_err_t tsys01_init_desc(tsys01_t *dev, uint8_t addr, i2c_port_t port, + gpio_num_t sda_gpio, gpio_num_t scl_gpio); + +/** + * @brief Free device descriptor. + * + * @param dev Device descriptor + * @return `ESP_OK` on success + */ +esp_err_t tsys01_free_desc(tsys01_t *dev); + +/** + * @brief Initialize device. + * + * Reads sensor configuration. + * + * @param dev Device descriptor + * @return `ESP_OK` on success + */ +esp_err_t tsys01_init(tsys01_t *dev); + +/** + * @brief Reset sensor. + * + * @param dev Device descriptor + * @return `ESP_OK` on success + */ +esp_err_t tsys01_reset(tsys01_t *dev); + +/** + * @brief Start temperature conversion. + * + * @param dev Device descriptor + * @return `ESP_OK` on success + */ +esp_err_t tsys01_start(tsys01_t *dev); + +/** + * @brief Read converted temperature from sensor. + * + * @param dev Device descriptor + * @param[out] raw Raw ADC value, NULL-able + * @param[out] t Temperature, degrees Celsius, NULL-able + * @return `ESP_OK` on success + */ +esp_err_t tsys01_get_temp(tsys01_t *dev, uint32_t *raw, float *t); + +/** + * @brief Perform temperature conversion + * + * This function starts temperature conversion, + * waits 10 ms and reads result. + * + * @param dev Device descriptor + * @param[out] t Temperature, degrees Celsius + * @return `ESP_OK` on success + */ +esp_err_t tsys01_measure(tsys01_t *dev, float *t); + +#ifdef __cplusplus +} +#endif + +/**@}*/ + +#endif /* __TSYS01_H__ */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/ultrasonic/.eil.yml Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,24 @@ +--- +components: + - name: ultrasonic + description: Driver for ultrasonic range meters, e.g. HC-SR04, HY-SRF05 + group: misc + groups: [] + code_owners: UncleRus + depends: + # XXX conditional depends + - driver + - freertos + - esp_idf_lib_helpers + thread_safe: no + targets: + - name: esp32 + - name: esp8266 + - name: esp32s2 + - name: esp32c3 + licenses: + - name: BSD-3 + copyrights: + - author: + name: UncleRus + year: 2016
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/ultrasonic/CMakeLists.txt Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,13 @@ +if(${IDF_TARGET} STREQUAL esp8266) + set(req esp8266 freertos esp_idf_lib_helpers esp_timer) +elseif(${IDF_VERSION_MAJOR} STREQUAL 4 AND ${IDF_VERSION_MINOR} STREQUAL 1 AND ${IDF_VERSION_PATCH} STREQUAL 3) + set(req driver freertos esp_idf_lib_helpers) +else() + set(req driver freertos esp_idf_lib_helpers esp_timer) +endif() + +idf_component_register( + SRCS ultrasonic.c + INCLUDE_DIRS . + REQUIRES ${req} +)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/ultrasonic/LICENSE Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,26 @@ +Copyright (c) 2016, 2018 Ruslan V. Uss <unclerus@gmail.com> + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, +this list of conditions and the following disclaimer in the documentation +and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of itscontributors +may be used to endorse or promote products derived from this software without +specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/ultrasonic/component.mk Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,7 @@ +COMPONENT_ADD_INCLUDEDIRS = . + +ifdef CONFIG_IDF_TARGET_ESP8266 +COMPONENT_DEPENDS = esp8266 freertos esp_idf_lib_helpers +else +COMPONENT_DEPENDS = driver freertos esp_idf_lib_helpers +endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/ultrasonic/ultrasonic.c Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,143 @@ +/* + * Copyright (c) 2016 Ruslan V. Uss <unclerus@gmail.com> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of itscontributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file ultrasonic.c + * + * ESP-IDF driver for ultrasonic range meters, e.g. HC-SR04, HY-SRF05 and the like + * + * Ported from esp-open-rtos + * + * Copyright (c) 2016 Ruslan V. Uss <unclerus@gmail.com> + * + * BSD Licensed as described in the file LICENSE + */ +#include <esp_idf_lib_helpers.h> +#include "ultrasonic.h" +#include <freertos/FreeRTOS.h> +#include <freertos/task.h> +#include <esp_timer.h> +#include <ets_sys.h> + +#define TRIGGER_LOW_DELAY 4 +#define TRIGGER_HIGH_DELAY 10 +#define PING_TIMEOUT 6000 +#define ROUNDTRIP_M 5800.0f +#define ROUNDTRIP_CM 58 + +#if HELPER_TARGET_IS_ESP32 +static portMUX_TYPE mux = portMUX_INITIALIZER_UNLOCKED; +#define PORT_ENTER_CRITICAL portENTER_CRITICAL(&mux) +#define PORT_EXIT_CRITICAL portEXIT_CRITICAL(&mux) + +#elif HELPER_TARGET_IS_ESP8266 +#define PORT_ENTER_CRITICAL portENTER_CRITICAL() +#define PORT_EXIT_CRITICAL portEXIT_CRITICAL() + +#else +#error cannot identify the target +#endif + +#define timeout_expired(start, len) ((esp_timer_get_time() - (start)) >= (len)) + +#define CHECK_ARG(VAL) do { if (!(VAL)) return ESP_ERR_INVALID_ARG; } while (0) +#define CHECK(x) do { esp_err_t __; if ((__ = x) != ESP_OK) return __; } while (0) +#define RETURN_CRITICAL(RES) do { PORT_EXIT_CRITICAL; return RES; } while(0) + +esp_err_t ultrasonic_init(const ultrasonic_sensor_t *dev) +{ + CHECK_ARG(dev); + + CHECK(gpio_set_direction(dev->trigger_pin, GPIO_MODE_OUTPUT)); + CHECK(gpio_set_direction(dev->echo_pin, GPIO_MODE_INPUT)); + + return gpio_set_level(dev->trigger_pin, 0); +} + + +esp_err_t ultrasonic_measure_raw(const ultrasonic_sensor_t *dev, uint32_t max_time_us, uint32_t *time_us) +{ + CHECK_ARG(dev && time_us); + + PORT_ENTER_CRITICAL; + + // Ping: Low for 2..4 us, then high 10 us + CHECK(gpio_set_level(dev->trigger_pin, 0)); + ets_delay_us(TRIGGER_LOW_DELAY); + CHECK(gpio_set_level(dev->trigger_pin, 1)); + ets_delay_us(TRIGGER_HIGH_DELAY); + CHECK(gpio_set_level(dev->trigger_pin, 0)); + + // Previous ping isn't ended + if (gpio_get_level(dev->echo_pin)) + RETURN_CRITICAL(ESP_ERR_ULTRASONIC_PING); + + // Wait for echo + int64_t start = esp_timer_get_time(); + while (!gpio_get_level(dev->echo_pin)) + { + if (timeout_expired(start, PING_TIMEOUT)) + RETURN_CRITICAL(ESP_ERR_ULTRASONIC_PING_TIMEOUT); + } + + // got echo, measuring + int64_t echo_start = esp_timer_get_time(); + int64_t time = echo_start; + while (gpio_get_level(dev->echo_pin)) + { + time = esp_timer_get_time(); + if (timeout_expired(echo_start, max_time_us)) + RETURN_CRITICAL(ESP_ERR_ULTRASONIC_ECHO_TIMEOUT); + } + PORT_EXIT_CRITICAL; + + *time_us = time - echo_start; + + return ESP_OK; +} + +esp_err_t ultrasonic_measure(const ultrasonic_sensor_t *dev, float max_distance, float *distance) +{ + CHECK_ARG(dev && distance); + + uint32_t time_us; + CHECK(ultrasonic_measure_raw(dev, max_distance * ROUNDTRIP_M, &time_us)); + *distance = time_us / ROUNDTRIP_M; + + return ESP_OK; +} + +esp_err_t ultrasonic_measure_cm(const ultrasonic_sensor_t *dev, uint32_t max_distance, uint32_t *distance) +{ + CHECK_ARG(dev && distance); + + uint32_t time_us; + CHECK(ultrasonic_measure_raw(dev, max_distance * ROUNDTRIP_CM, &time_us)); + *distance = time_us / ROUNDTRIP_CM; + + return ESP_OK; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/ultrasonic/ultrasonic.h Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2016 Ruslan V. Uss <unclerus@gmail.com> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of itscontributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file ultrasonic.h + * @defgroup ultrasonic ultrasonic + * @{ + * + * ESP-IDF driver for ultrasonic range meters, e.g. HC-SR04, HY-SRF05 and so on + * + * Ported from esp-open-rtos + * + * Copyright (c) 2016 Ruslan V. Uss <unclerus@gmail.com> + * + * BSD Licensed as described in the file LICENSE + */ +#ifndef __ULTRASONIC_H__ +#define __ULTRASONIC_H__ + +#include <driver/gpio.h> +#include <esp_err.h> + +#ifdef __cplusplus +extern "C" { +#endif + +#define ESP_ERR_ULTRASONIC_PING 0x200 +#define ESP_ERR_ULTRASONIC_PING_TIMEOUT 0x201 +#define ESP_ERR_ULTRASONIC_ECHO_TIMEOUT 0x202 + +/** + * Device descriptor + */ +typedef struct +{ + gpio_num_t trigger_pin; //!< GPIO output pin for trigger + gpio_num_t echo_pin; //!< GPIO input pin for echo +} ultrasonic_sensor_t; + +/** + * @brief Init ranging module + * + * @param dev Pointer to the device descriptor + * @return `ESP_OK` on success + */ +esp_err_t ultrasonic_init(const ultrasonic_sensor_t *dev); + +/** + * @brief Measure time between ping and echo + * + * @param dev Pointer to the device descriptor + * @param max_time_us Maximal time to wait for echo + * @param[out] time_us Time, us + * @return `ESP_OK` on success, otherwise: + * - ::ESP_ERR_ULTRASONIC_PING - Invalid state (previous ping is not ended) + * - ::ESP_ERR_ULTRASONIC_PING_TIMEOUT - Device is not responding + * - ::ESP_ERR_ULTRASONIC_ECHO_TIMEOUT - Distance is too big or wave is scattered + */ +esp_err_t ultrasonic_measure_raw(const ultrasonic_sensor_t *dev, uint32_t max_time_us, uint32_t *time_us); + +/** + * @brief Measure distance in meters + * + * @param dev Pointer to the device descriptor + * @param max_distance Maximal distance to measure, meters + * @param[out] distance Distance in meters + * @return `ESP_OK` on success, otherwise: + * - ::ESP_ERR_ULTRASONIC_PING - Invalid state (previous ping is not ended) + * - ::ESP_ERR_ULTRASONIC_PING_TIMEOUT - Device is not responding + * - ::ESP_ERR_ULTRASONIC_ECHO_TIMEOUT - Distance is too big or wave is scattered + */ +esp_err_t ultrasonic_measure(const ultrasonic_sensor_t *dev, float max_distance, float *distance); + +/** + * @brief Measure distance in centimeters + * + * @param dev Pointer to the device descriptor + * @param max_distance Maximal distance to measure, centimeters + * @param[out] distance Distance in centimeters + * @return `ESP_OK` on success, otherwise: + * - ::ESP_ERR_ULTRASONIC_PING - Invalid state (previous ping is not ended) + * - ::ESP_ERR_ULTRASONIC_PING_TIMEOUT - Device is not responding + * - ::ESP_ERR_ULTRASONIC_ECHO_TIMEOUT - Distance is too big or wave is scattered + */ +esp_err_t ultrasonic_measure_cm(const ultrasonic_sensor_t *dev, uint32_t max_distance, uint32_t *distance); + +#ifdef __cplusplus +} +#endif + +/**@}*/ + +#endif /* __ULTRASONIC_H__ */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/veml7700/.eil.yml Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,23 @@ +--- +components: + - name: veml7700 + description: Driver for VEML7700 ambient light sensor + group: light + groups: [] + code_owners: Th3Link + depends: + - i2cdev + - log + - esp_idf_lib_helpers + thread_safe: yes + targets: + - name: esp32 + - name: esp8266 + - name: esp32s2 + - name: esp32c3 + licenses: + - name: ISC + copyrights: + - author: + name: Th3Link + year: 2019
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/veml7700/CMakeLists.txt Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,5 @@ +idf_component_register( + SRCS veml7700.c + INCLUDE_DIRS . + REQUIRES i2cdev log esp_idf_lib_helpers +)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/veml7700/LICENSE Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,17 @@ +ISC License + +SPDX-License-Identifier: ISC + +Copyright (c) 2022 Marc Luehr <marcluehr@gmail.com> + +Permission to use, copy, modify, and distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/veml7700/README.md Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,104 @@ +# Driver for the VEML7700 ambient light sensor + +This is the driver for the VEML7700 ambiernt light sensor from Vishay. It is not the +cheapest solution but very easy to integrate, with a minimum of further components. +The sensor can be SMD soldered by hand on a custom PCB, but also several breakout boards +are available. + +## About the sensor + +The VEML7700 is a high-accuracy ambient light sensor. All measurements and filtering +is done on the chip which makes it easy to integrate. The sensitivity can be adjusted +by a gain setting and by changing the integration time. The output of the sensor is a +count value which must be converted using the resolution value in the driver. + +The sensor has power saving features which reduces the repetition of measurements. +It has also a power off feature to save even more power. + +The ambient light sensor value is filterd very clone to the caracteristic of the +human eye. Besides, als the white channel with a wider wavelength spectrum. In most +applications, the ambient light value will do just fine. + +## Power consumption + +- 0.5 μA in shut-down mode (@3.3V) +- down to 2 μA in power save mode 4 (@3.3V) +- 45 μA on 100ms integration time (@3.3V) + +## Communication interface + +I2C is used as communication interface without any further interrupt pins. It has six +command codes for the settings and the output values. Read the datasheet or application +note for further information. + +To reduce interactions with the integrated microcontroller, the interrupt feature can +be used. Therefore, one must configure the low and high threshold and enable the interrupt. + +## Interrupt application examples + +If values below a certain threshold is of interest, i.e. to activate lights when its +getting dark outside, the low threshold should be adjusted and setting the high threshold +to maximum (65535). + +Another application could be an automated rollershutter, then both thresholds sould be +set to trigger the up and down movement of rollershutters. + +## Measurement process + +The measurement takes time and the sensor should not be read out faster than the +measurement time. Therefore the application should be adjusted to the sensor configuration +regarting integration time and power save modes. Alternatively, the interrupt feature +can be used by repeatetive reading of the interrupt status. + +## Usage + +This driver uses i2cdev which must be initialized first. Then initialize the device +decriptor, one discriptor per device. The sensor can be used without configuration, +but has a high chance of over-saturation on sunlight. Therefore, change gain and +integration time to your needs and configure the device. + +Then, the veml7700_ambient_light and veml7700_white_channel functions can be used +to read out the brightness. The driver converts the counts from the device to lx using +the configuration. + +### Hardware configurations + +The driver supports multiple VEML7700 sensors at the same time that are +connected to I2C. Following figure show some possible hardware +configurations. + +First figure shows the configuration with one sensor at I2C bus 0. + +```text + +------------------+ +----------+ + | ESP8266 / ESP32 | | VEML7700 | + | | | | + | GPIO 14 (SCL) ----> SCL | + | GPIO 13 (SDA) <---> SDA | + +------------------+ +----------+ +``` + +Next figure shows a possible configuration with two I2C buses. + +```text + +------------------+ +----------+ + | ESP8266 / ESP32 | | VEML7700 | + | | | | + | GPIO 14 (SCL) ----> SCL | + | GPIO 13 (SDA) <---> SDA | + | | +----------+ + | | | VEML7700 | + | | | | + | GPIO 5 (SCL) ----> SCL | + | GPIO 4 (SDA) <---> SDA | + +------------------+ +----------+ +``` + +Only one sensor per I2C bus is possible, since it uses a unconfigurable I2C slave address. +However, one could also use GPIO controller bus buffer to connect the bus to different +devices. + +## References +[Datasheet](https://www.vishay.com/docs/84286/veml7700.pdf) + +[Application Note](https://www.vishay.com/docs/84323/designingveml7700.pdf)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/veml7700/component.mk Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,2 @@ +COMPONENT_ADD_INCLUDEDIRS = . +COMPONENT_DEPENDS = i2cdev log esp_idf_lib_helpers \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/veml7700/veml7700.c Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,276 @@ +/* + * SPDX-License-Identifier: ISC + * + * Copyright (c) 2022 Marc Luehr <marcluehr@gmail.com> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#define VEML7700_COMMAND_CODE_ALS_CONF_0 (0) +#define VEML7700_COMMAND_CODE_ALS_WH (1) +#define VEML7700_COMMAND_CODE_ALS_WL (2) +#define VEML7700_COMMAND_CODE_POWER_SAVING (3) +#define VEML7700_COMMAND_CODE_ALS (4) +#define VEML7700_COMMAND_CODE_WHITE (5) +#define VEML7700_COMMAND_CODE_ALS_INT (6) + +#define VEML7700_GAIN_MASK (0x1800) +#define VEML7700_GAIN_SHIFT (11) + +#define VEML7700_INTEGRATION_TIME_MASK (0x03C0) +#define VEML7700_INTEGRATION_TIME_SHIFT (6) + +#define VEML7700_PERSISTENCE_PROTECTION_MASK (0x0030) +#define VEML7700_PERSISTENCE_PROTECTION_SHIFT (4) + +#define VEML7700_INTERRUPT_ENABLE_MASK (0x0002) +#define VEML7700_INTERRUPT_ENABLE_SHIFT (1) + +#define VEML7700_SHUTDOWN_MASK (0x0001) +#define VEML7700_SHUTDOWN_SHIFT (0) + +#define VEML7700_POWER_SAVING_MODE_MASK (0x0060) +#define VEML7700_POWER_SAVING_MODE_SHIFT (1) + +#define VEML7700_POWER_SAVING_MODE_ENABLE_MASK (0x0001) +#define VEML7700_POWER_SAVING_MODE_ENABLE_SHIFT (0) + +#define VEML7700_INTERRUPT_STATUS_LOW_MASK (0x8000) +#define VEML7700_INTERRUPT_STATUS_LOW_SHIFT (15) +#define VEML7700_INTERRUPT_STATUS_HIGH_MASK (0x4000) +#define VEML7700_INTERRUPT_STATUS_HIGH_SHIFT (14) + +#define VEML7700_RESOLUTION_800MS_IT_GAIN_2 (36) +#define VEML7700_RESOLUTION_800MS_IT_GAIN_2_DIV (1000) + +/** + * @file veml7700.c + * + * ESP-IDF driver for VEML7700 brightness sensors for I2C-bus + * + * Copyright (c) 2022 Marc Luehr <marcluehr@gmail.com> + * + * MIT Licensed as described in the file LICENSE + */ + +#include "veml7700.h" + +#define I2C_FREQ_HZ (100000) + +#define CHECK(x) \ + do \ + { \ + esp_err_t __; \ + if ((__ = x) != ESP_OK) \ + return __; \ + } \ + while (0) +#define CHECK_ARG(VAL) \ + do \ + { \ + if (!(VAL)) \ + return ESP_ERR_INVALID_ARG; \ + } \ + while (0) + +static esp_err_t read_port(i2c_dev_t *dev, uint8_t command_code, uint16_t *data) +{ + CHECK_ARG(dev); + + I2C_DEV_CHECK(dev, i2c_dev_read(dev, &command_code, 1, data, 2)); + + return ESP_OK; +} + +static esp_err_t write_port(i2c_dev_t *dev, uint8_t command_code, uint16_t data) +{ + CHECK_ARG(dev); + + I2C_DEV_CHECK(dev, i2c_dev_write(dev, &command_code, 1, &data, 2)); + + return ESP_OK; +} + +static uint32_t resolution(veml7700_config_t *config) +{ + CHECK_ARG(config); + + uint32_t resolution = VEML7700_RESOLUTION_800MS_IT_GAIN_2; + switch (config->gain) + { + case VEML7700_GAIN_1: + resolution = resolution * 2; + break; + case VEML7700_GAIN_DIV_4: + resolution = resolution * 8; + break; + case VEML7700_GAIN_DIV_8: + resolution = resolution * 16; + break; + } + switch (config->integration_time) + { + case VEML7700_INTEGRATION_TIME_400MS: + resolution = resolution * 2; + break; + case VEML7700_INTEGRATION_TIME_200MS: + resolution = resolution * 4; + break; + case VEML7700_INTEGRATION_TIME_100MS: + resolution = resolution * 8; + break; + case VEML7700_INTEGRATION_TIME_50MS: + resolution = resolution * 16; + break; + case VEML7700_INTEGRATION_TIME_25MS: + resolution = resolution * 32; + break; + } + return resolution; +} + +/////////////////////////////////////////////////////////////////////////////// + +esp_err_t veml7700_init_desc(i2c_dev_t *dev, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio) +{ + CHECK_ARG(dev); + + dev->port = port; + dev->addr = VEML7700_I2C_ADDR; + dev->cfg.sda_io_num = sda_gpio; + dev->cfg.scl_io_num = scl_gpio; +#if HELPER_TARGET_IS_ESP32 + dev->cfg.master.clk_speed = I2C_FREQ_HZ; +#endif + + return i2c_dev_create_mutex(dev); +} + +esp_err_t veml7700_free_desc(i2c_dev_t *dev) +{ + CHECK_ARG(dev); + + return i2c_dev_delete_mutex(dev); +} + +esp_err_t veml7700_probe(i2c_dev_t *dev) +{ + CHECK_ARG(dev); + + /* use write request since read request causes a timeout; + * just doing a read is not intended to use by the chip, + * it is waiting for a command code + */ + I2C_DEV_TAKE_MUTEX(dev); + esp_err_t err = i2c_dev_probe(dev, I2C_DEV_WRITE); + I2C_DEV_GIVE_MUTEX(dev); + return err; +} + +esp_err_t veml7700_set_config(i2c_dev_t *dev, veml7700_config_t *config) +{ + CHECK_ARG(dev); + CHECK_ARG(config); + + uint16_t config_data = 0; + config_data |= config->gain << VEML7700_GAIN_SHIFT; + config_data |= config->integration_time << VEML7700_INTEGRATION_TIME_SHIFT; + config_data |= config->persistence_protect << VEML7700_PERSISTENCE_PROTECTION_SHIFT; + config_data |= config->interrupt_enable << VEML7700_INTERRUPT_ENABLE_SHIFT; + config_data |= config->shutdown << VEML7700_SHUTDOWN_SHIFT; + + uint16_t power_saving_data = 0; + power_saving_data |= config->power_saving_mode << VEML7700_POWER_SAVING_MODE_SHIFT; + power_saving_data |= config->power_saving_enable << VEML7700_POWER_SAVING_MODE_ENABLE_SHIFT; + + I2C_DEV_TAKE_MUTEX(dev); + I2C_DEV_CHECK(dev, write_port(dev, VEML7700_COMMAND_CODE_ALS_CONF_0, config_data)); + I2C_DEV_CHECK(dev, write_port(dev, VEML7700_COMMAND_CODE_ALS_WH, config->threshold_high)); + I2C_DEV_CHECK(dev, write_port(dev, VEML7700_COMMAND_CODE_ALS_WL, config->threshold_low)); + I2C_DEV_CHECK(dev, write_port(dev, VEML7700_COMMAND_CODE_POWER_SAVING, power_saving_data)); + I2C_DEV_GIVE_MUTEX(dev); + return ESP_OK; +} + +esp_err_t veml7700_get_config(i2c_dev_t *dev, veml7700_config_t *config) +{ + CHECK_ARG(dev); + CHECK_ARG(config); + + uint16_t config_data = 0; + uint16_t power_saving_data = 0; + + I2C_DEV_TAKE_MUTEX(dev); + I2C_DEV_CHECK(dev, read_port(dev, VEML7700_COMMAND_CODE_ALS_CONF_0, &config_data)); + I2C_DEV_CHECK(dev, read_port(dev, VEML7700_COMMAND_CODE_ALS_WH, &(config->threshold_high))); + I2C_DEV_CHECK(dev, read_port(dev, VEML7700_COMMAND_CODE_ALS_WL, &(config->threshold_low))); + I2C_DEV_CHECK(dev, read_port(dev, VEML7700_COMMAND_CODE_POWER_SAVING, &power_saving_data)); + I2C_DEV_GIVE_MUTEX(dev); + + config->gain = (config_data & VEML7700_GAIN_MASK) >> VEML7700_GAIN_SHIFT; + config->integration_time = (config_data & VEML7700_INTEGRATION_TIME_MASK) + >> VEML7700_INTEGRATION_TIME_SHIFT; + config->integration_time = (config_data & VEML7700_PERSISTENCE_PROTECTION_MASK) + >> VEML7700_PERSISTENCE_PROTECTION_SHIFT; + config->integration_time = (config_data & VEML7700_INTERRUPT_ENABLE_MASK) + >> VEML7700_INTERRUPT_ENABLE_SHIFT; + config->integration_time = (config_data & VEML7700_SHUTDOWN_MASK) + >> VEML7700_SHUTDOWN_SHIFT; + + config->power_saving_mode = (power_saving_data & VEML7700_POWER_SAVING_MODE_MASK) + >> VEML7700_POWER_SAVING_MODE_SHIFT; + config->power_saving_enable = (power_saving_data & VEML7700_POWER_SAVING_MODE_ENABLE_MASK) + >> VEML7700_POWER_SAVING_MODE_ENABLE_SHIFT; + + return ESP_OK; +} + +esp_err_t veml7700_get_ambient_light(i2c_dev_t *dev, veml7700_config_t *config, uint32_t *value) +{ + CHECK_ARG(dev); + CHECK_ARG(config); + + uint16_t raw_value = 0; + CHECK(read_port(dev, VEML7700_COMMAND_CODE_ALS, &raw_value)); + + *value = (raw_value * resolution(config)) / VEML7700_RESOLUTION_800MS_IT_GAIN_2_DIV; + return ESP_OK; +} + +esp_err_t veml7700_get_white_channel(i2c_dev_t *dev, veml7700_config_t *config, uint32_t *value) +{ + CHECK_ARG(dev); + CHECK_ARG(config); + + uint16_t raw_value = 0; + I2C_DEV_TAKE_MUTEX(dev); + I2C_DEV_CHECK(dev, read_port(dev, VEML7700_COMMAND_CODE_WHITE, &raw_value)); + I2C_DEV_GIVE_MUTEX(dev); + + *value = (raw_value * resolution(config)) / VEML7700_RESOLUTION_800MS_IT_GAIN_2_DIV; + return ESP_OK; +} + +esp_err_t veml7700_get_interrupt_status(i2c_dev_t *dev, bool *low_threshold, bool *high_threshold) +{ + CHECK_ARG(dev); + + uint16_t interrupt_status = 0; + I2C_DEV_TAKE_MUTEX(dev); + I2C_DEV_CHECK(dev, read_port(dev, VEML7700_COMMAND_CODE_ALS_INT, &interrupt_status)); + I2C_DEV_GIVE_MUTEX(dev); + + *high_threshold = interrupt_status & VEML7700_INTERRUPT_STATUS_HIGH_MASK; + *low_threshold = interrupt_status & VEML7700_INTERRUPT_STATUS_LOW_MASK; + return ESP_OK; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/veml7700/veml7700.h Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,164 @@ +/* + * SPDX-License-Identifier: ISC + * + * Copyright (c) 2022 Marc Luehr <marcluehr@gmail.com> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/** + * @file veml7700.h + * @defgroup veml7700 veml7700 + * @{ + * + * ESP-IDF driver for VEML7700 brightness sensors for I2C-bus + * + * Copyright (c) 2022 Marc Luehr <marcluehr@gmail.com> + * + * ISC Licensed as described in the file LICENSE + */ +#ifndef __VEML7700_H__ +#define __VEML7700_H__ + +#include <stddef.h> +#include <i2cdev.h> +#include <esp_err.h> + +#ifdef __cplusplus +extern "C" { +#endif + +#define VEML7700_I2C_ADDR (0x10) + +#define VEML7700_INTEGRATION_TIME_25MS (0b1100) +#define VEML7700_INTEGRATION_TIME_50MS (0b1000) +#define VEML7700_INTEGRATION_TIME_100MS (0b0000) +#define VEML7700_INTEGRATION_TIME_200MS (0b0001) +#define VEML7700_INTEGRATION_TIME_400MS (0b0010) +#define VEML7700_INTEGRATION_TIME_800MS (0b0011) + +#define VEML7700_GAIN_1 (0b00) +#define VEML7700_GAIN_2 (0b01) +#define VEML7700_GAIN_DIV_8 (0b10) +#define VEML7700_GAIN_DIV_4 (0b11) + +#define VEML7700_POWER_SAVING_MODE_500MS (0b00) +#define VEML7700_POWER_SAVING_MODE_1000MS (0b01) +#define VEML7700_POWER_SAVING_MODE_2000MS (0b10) +#define VEML7700_POWER_SAVING_MODE_4000MS (0b11) + +#define VEML7700_PERSISTENCE_PROTECTION_1 (0b00) +#define VEML7700_PERSISTENCE_PROTECTION_2 (0b01) +#define VEML7700_PERSISTENCE_PROTECTION_4 (0b10) +#define VEML7700_PERSISTENCE_PROTECTION_8 (0b11) + +/** + * VEML configuration descriptor + */ +typedef struct +{ + uint16_t gain : 2; //!< control the sensitivity + uint16_t integration_time : 4; //!< time to measure + uint16_t persistence_protect : 2; //!< sample count before the interrupt triggers + uint16_t interrupt_enable : 1; //!< enable threshold interrupt + uint16_t shutdown : 1; //!< set to 1 to shutdown the device, set to 0 to wakeup + uint16_t threshold_high; //!< high threshold for the interrupt + uint16_t threshold_low; //!< low threshold for the interrupt + uint16_t power_saving_mode : 2; //!< power saving mode + uint16_t power_saving_enable : 1; //!< enable the pover saving mode +} veml7700_config_t; + +/** + * @brief Initialize device descriptor + * + * Default SCL frequency is 100kHz. The I2C address is fix. + * + * @param dev Pointer to I2C device descriptor + * @param port I2C port number + * @param sda_gpio SDA GPIO + * @param scl_gpio SCL GPIO + * @return `ESP_OK` on success + */ +esp_err_t veml7700_init_desc(i2c_dev_t *dev, i2c_port_t port, gpio_num_t sda_gpio, gpio_num_t scl_gpio); + +/** + * @brief Free device descriptor + * + * @param dev Pointer to I2C device descriptor + * @return `ESP_OK` on success + */ +esp_err_t veml7700_free_desc(i2c_dev_t *dev); + +/** + * @brief Probe if the device exist on the bus + * + * @param dev Pointer to I2C device descriptor + * @return `ESP_OK` on success + */ +esp_err_t veml7700_probe(i2c_dev_t *dev); + +/** + * @brief Write the config to the device + * + * @param dev Pointer to I2C device descriptor + * @param config Pointer to the config descriptor + * @return `ESP_OK` on success + */ +esp_err_t veml7700_set_config(i2c_dev_t *dev, veml7700_config_t *config); + +/** + * @brief Read the config to the device + * + * @param dev Pointer to I2C device descriptor + * @param config Pointer to the config descriptor + * @return `ESP_OK` on success + */ +esp_err_t veml7700_get_config(i2c_dev_t *dev, veml7700_config_t *config); + +/** + * @brief Read ambient light sensor value from the device + * + * @param dev Pointer to I2C device descriptor + * @param config Pointer to the config descriptor + * @param value_lux Pointer as return value in lux + * @return `ESP_OK` on success + */ +esp_err_t veml7700_get_ambient_light(i2c_dev_t *dev, veml7700_config_t *config, uint32_t *value_lux); + +/** + * @brief Read white channel value from the device + * + * @param dev Pointer to I2C device descriptor + * @param config Pointer to the config descriptor + * @param value_lux Pointer as return value in lux + * @return `ESP_OK` on success + */ +esp_err_t veml7700_get_white_channel(i2c_dev_t *dev, veml7700_config_t *config, uint32_t *value_lux); + +/** + * @brief Read the interrupt status from the device + * + * @param dev Pointer to I2C device descriptor + * @param low_threshold Pointer to return the low threshold passed indicator + * @param high_threshold Pointer to return the high threshold passed indicator + * @return `ESP_OK` on success + */ +esp_err_t veml7700_get_interrupt_status(i2c_dev_t *dev, bool *low_threshold, bool *high_threshold); + +#ifdef __cplusplus +} +#endif + +/**@}*/ + +#endif /* __VEML7700_H__ */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/wiegand/.eil.yml Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,24 @@ +--- +components: + - name: wiegand + description: Wiegand protocol receiver + group: misc + groups: [] + code_owners: UncleRus + depends: + # XXX conditional depends + - driver + - log + - esp_idf_lib_helpers + thread_safe: no + targets: + - name: esp32 + - name: esp8266 + - name: esp32s2 + - name: esp32c3 + licenses: + - name: BSD-3 + copyrights: + - author: + name: UncleRus + year: 2021
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/wiegand/CMakeLists.txt Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,13 @@ +if(${IDF_TARGET} STREQUAL esp8266) + set(req esp8266 log esp_idf_lib_helpers esp_timer) +elseif(${IDF_VERSION_MAJOR} STREQUAL 4 AND ${IDF_VERSION_MINOR} STREQUAL 1 AND ${IDF_VERSION_PATCH} STREQUAL 3) + set(req driver log esp_idf_lib_helpers) +else() + set(req driver log esp_idf_lib_helpers esp_timer) +endif() + +idf_component_register( + SRCS wiegand.c + INCLUDE_DIRS . + REQUIRES ${req} +)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/wiegand/LICENSE Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,26 @@ +Copyright 2021 Ruslan V. Uss <unclerus@gmail.com> + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, +this list of conditions and the following disclaimer in the documentation +and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of itscontributors +may be used to endorse or promote products derived from this software without +specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/wiegand/component.mk Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,7 @@ +COMPONENT_ADD_INCLUDEDIRS = . + +ifdef CONFIG_IDF_TARGET_ESP8266 +COMPONENT_DEPENDS = esp8266 log esp_idf_lib_helpers +else +COMPONENT_DEPENDS = driver log esp_idf_lib_helpers +endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/wiegand/wiegand.c Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,202 @@ +/* + * Copyright (c) 2021 Ruslan V. Uss <unclerus@gmail.com> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of itscontributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file wiegand.c + * + * ESP-IDF Wiegand protocol receiver + * + * Copyright (c) 2021 Ruslan V. Uss <unclerus@gmail.com> + * + * BSD Licensed as described in the file LICENSE + */ +#include <esp_log.h> +#include <string.h> +#include <stdlib.h> +#include <esp_idf_lib_helpers.h> +#include "wiegand.h" + +static const char *TAG = "wiegand"; + +#define TIMER_INTERVAL_US 50000 // 50ms + +#define CHECK(x) do { esp_err_t __; if ((__ = x) != ESP_OK) return __; } while (0) +#define CHECK_ARG(VAL) do { if (!(VAL)) return ESP_ERR_INVALID_ARG; } while (0) + +static void isr_disable(wiegand_reader_t *reader) +{ + gpio_set_intr_type(reader->gpio_d0, GPIO_INTR_DISABLE); + gpio_set_intr_type(reader->gpio_d1, GPIO_INTR_DISABLE); +} + +static void isr_enable(wiegand_reader_t *reader) +{ + gpio_set_intr_type(reader->gpio_d0, GPIO_INTR_NEGEDGE); + gpio_set_intr_type(reader->gpio_d1, GPIO_INTR_NEGEDGE); +} + +#if HELPER_TARGET_IS_ESP32 +static void IRAM_ATTR isr_handler(void *arg) +#else +static void isr_handler(void *arg) +#endif +{ + wiegand_reader_t *reader = (wiegand_reader_t *)arg; + if (!reader->enabled) + return; + + int d0 = gpio_get_level(reader->gpio_d0); + int d1 = gpio_get_level(reader->gpio_d1); + + // ignore equal + if (d0 == d1) + return; + // overflow + if (reader->bits >= reader->size * 8) + return; + + esp_timer_stop(reader->timer); + + uint8_t value; + if (reader->bit_order == WIEGAND_MSB_FIRST) + value = (d0 ? 0x80 : 0) >> (reader->bits % 8); + else + value = (d0 ? 1 : 0) << (reader->bits % 8); + + if (reader->byte_order == WIEGAND_MSB_FIRST) + reader->buf[reader->size - reader->bits / 8 - 1] |= value; + else + reader->buf[reader->bits / 8] |= value; + + reader->bits++; + + esp_timer_start_once(reader->timer, TIMER_INTERVAL_US); +} + +static void timer_handler(void *arg) +{ + wiegand_reader_t *reader = (wiegand_reader_t *)arg; + + ESP_LOGD(TAG, "Got %d bits of data", reader->bits); + + wiegand_reader_disable(reader); + + if (reader->callback) + reader->callback(reader); + + wiegand_reader_enable(reader); + + isr_enable(reader); +} + +//////////////////////////////////////////////////////////////////////////////// + +esp_err_t wiegand_reader_init(wiegand_reader_t *reader, gpio_num_t gpio_d0, gpio_num_t gpio_d1, + bool internal_pullups, size_t buf_size, wiegand_callback_t callback, wiegand_order_t bit_order, + wiegand_order_t byte_order) +{ + CHECK_ARG(reader && buf_size && callback); + + esp_err_t res = gpio_install_isr_service(0); + if (res != ESP_OK && res != ESP_ERR_INVALID_STATE) + return res; + + memset(reader, 0, sizeof(wiegand_reader_t)); + reader->gpio_d0 = gpio_d0; + reader->gpio_d1 = gpio_d1; + reader->size = buf_size; + reader->buf = calloc(buf_size, 1); + reader->bit_order = bit_order; + reader->byte_order = byte_order; + reader->callback = callback; + + esp_timer_create_args_t timer_args = { + .name = TAG, + .arg = reader, + .callback = timer_handler, + .dispatch_method = ESP_TIMER_TASK + }; + CHECK(esp_timer_create(&timer_args, &reader->timer)); + + CHECK(gpio_set_direction(gpio_d0, GPIO_MODE_INPUT)); + CHECK(gpio_set_direction(gpio_d1, GPIO_MODE_INPUT)); + CHECK(gpio_set_pull_mode(gpio_d0, internal_pullups ? GPIO_PULLUP_ONLY : GPIO_FLOATING)); + CHECK(gpio_set_pull_mode(gpio_d1, internal_pullups ? GPIO_PULLUP_ONLY : GPIO_FLOATING)); + isr_disable(reader); + CHECK(gpio_isr_handler_add(gpio_d0, isr_handler, reader)); + CHECK(gpio_isr_handler_add(gpio_d1, isr_handler, reader)); + isr_enable(reader); + reader->enabled = true; + + ESP_LOGD(TAG, "Reader initialized on D0=%d, D1=%d", gpio_d0, gpio_d1); + + return ESP_OK; +} + +esp_err_t wiegand_reader_disable(wiegand_reader_t *reader) +{ + CHECK_ARG(reader); + + isr_disable(reader); + esp_timer_stop(reader->timer); + reader->enabled = false; + + ESP_LOGD(TAG, "Reader on D0=%d, D1=%d disabled", reader->gpio_d0, reader->gpio_d1); + + return ESP_OK; +} + +esp_err_t wiegand_reader_enable(wiegand_reader_t *reader) +{ + CHECK_ARG(reader); + + reader->bits = 0; + memset(reader->buf, 0, reader->size); + + isr_enable(reader); + reader->enabled = true; + + ESP_LOGD(TAG, "Reader on D0=%d, D1=%d enabled", reader->gpio_d0, reader->gpio_d1); + + return ESP_OK; +} + +esp_err_t wiegand_reader_done(wiegand_reader_t *reader) +{ + CHECK_ARG(reader && reader->buf); + + isr_disable(reader); + CHECK(gpio_isr_handler_remove(reader->gpio_d0)); + CHECK(gpio_isr_handler_remove(reader->gpio_d1)); + esp_timer_stop(reader->timer); + CHECK(esp_timer_delete(reader->timer)); + free(reader->buf); + + ESP_LOGD(TAG, "Reader removed"); + + return ESP_OK; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/components/wiegand/wiegand.h Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2021 Ruslan V. Uss <unclerus@gmail.com> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of itscontributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file wiegand.h + * @defgroup wiegand wiegand + * @{ + * + * ESP-IDF Wiegand protocol receiver + * + * Copyright (c) 2021 Ruslan V. Uss <unclerus@gmail.com> + * + * BSD Licensed as described in the file LICENSE + */ +#ifndef __WIEGAND_H__ +#define __WIEGAND_H__ + +#include <driver/gpio.h> +#include <esp_err.h> +#include <esp_timer.h> + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct wiegand_reader wiegand_reader_t; + +typedef void (*wiegand_callback_t)(wiegand_reader_t *reader); + +/** + * Bit and byte order of data + */ +typedef enum { + WIEGAND_MSB_FIRST = 0, + WIEGAND_LSB_FIRST +} wiegand_order_t; + +/** + * Wiegand reader descriptor + */ +struct wiegand_reader +{ + gpio_num_t gpio_d0, gpio_d1; + wiegand_callback_t callback; + wiegand_order_t bit_order; + wiegand_order_t byte_order; + + uint8_t *buf; + size_t size; + size_t bits; + esp_timer_handle_t timer; + bool start_parity; + bool enabled; +}; + +/** + * @brief Create and initialize reader instance. + * + * @param reader Reader descriptor + * @param gpio_d0 GPIO pin for D0 + * @param gpio_d1 GPIO pin for D0 + * @param internal_pullups Enable internal pull-up resistors for D0 and D1 GPIO + * @param buf_size Reader buffer size in bytes, must be large enough to + * contain entire Wiegand key + * @param callback Callback function for processing received codes + * @param bit_order Bit order of data + * @param byte_order Byte order of data + * @return `ESP_OK` on success + */ +esp_err_t wiegand_reader_init(wiegand_reader_t *reader, gpio_num_t gpio_d0, gpio_num_t gpio_d1, + bool internal_pullups, size_t buf_size, wiegand_callback_t callback, wiegand_order_t bit_order, + wiegand_order_t byte_order); + +/** + * @brief Disable reader + * + * While reader is disabled, it will not receive new data + * + * @param reader Reader descriptor + * @return `ESP_OK` on success + */ +esp_err_t wiegand_reader_disable(wiegand_reader_t *reader); + +/** + * @brief Enable reader + * + * @param reader Reader descriptor + * @return `ESP_OK` on success + */ +esp_err_t wiegand_reader_enable(wiegand_reader_t *reader); + +/** + * @brief Delete reader instance. + * + * @param reader Reader descriptor + * @return `ESP_OK` on success + */ +esp_err_t wiegand_reader_done(wiegand_reader_t *reader); + +#ifdef __cplusplus +} +#endif + +/**@}*/ + +#endif /* __WIEGAND_H__ */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/devtools/.rubocop.yml Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,134 @@ +--- +# inherit_from: .rubocop_todo.yml + +AllCops: + Exclude: + - "vendor/**/*" + # enable detailed explanations available in cops + # the default output is not enough to understand what is wrong + DisplayCopNames: true + ExtraDetails: true + DisplayStyleGuide: true + + # the default CacheRootDirectory is no longer `/tmp`, but a directory under + # `$HOME` and some Unix platforms use symlink to that path + AllowSymlinksInCacheRootDirectory: true + +Style/StringLiterals: + EnforcedStyle: double_quotes + +Style/SymbolArray: + # perefer brackets for `grep-ability` + EnforcedStyle: brackets + +Metrics/BlockLength: + Exclude: + - Guardfile + IgnoredMethods: + - describe + - context + - namespace + +Layout/LineLength: + Exclude: + # Gemfile is not application code + - "Gemfile" + # ignore heredoc for readability + AllowHeredoc: true + # URLs are almost always long + AllowURI: true + URISchemes: + - http + - https + - git + - ftp + IgnoreCopDirectives: true + +Gemspec/DateAssignment: # new in 1.10 + Enabled: true +Layout/LineEndStringConcatenationIndentation: # new in 1.18 + Enabled: true +Layout/SpaceBeforeBrackets: # new in 1.7 + Enabled: true +Lint/AmbiguousAssignment: # new in 1.7 + Enabled: true +Lint/AmbiguousOperatorPrecedence: # new in 1.21 + Enabled: true +Lint/AmbiguousRange: # new in 1.19 + Enabled: true +Lint/DeprecatedConstants: # new in 1.8 + Enabled: true +Lint/DuplicateBranch: # new in 1.3 + Enabled: true +Lint/DuplicateRegexpCharacterClassElement: # new in 1.1 + Enabled: true +Lint/EmptyBlock: # new in 1.1 + Enabled: true +Lint/EmptyClass: # new in 1.3 + Enabled: true +Lint/EmptyInPattern: # new in 1.16 + Enabled: true +Lint/IncompatibleIoSelectWithFiberScheduler: # new in 1.21 + Enabled: true +Lint/LambdaWithoutLiteralBlock: # new in 1.8 + Enabled: true +Lint/NoReturnInBeginEndBlocks: # new in 1.2 + Enabled: true +Lint/NumberedParameterAssignment: # new in 1.9 + Enabled: true +Lint/OrAssignmentToConstant: # new in 1.9 + Enabled: true +Lint/RedundantDirGlobSort: # new in 1.8 + Enabled: true +Lint/RequireRelativeSelfPath: # new in 1.22 + Enabled: true +Lint/SymbolConversion: # new in 1.9 + Enabled: true +Lint/ToEnumArguments: # new in 1.1 + Enabled: true +Lint/TripleQuotes: # new in 1.9 + Enabled: true +Lint/UnexpectedBlockArity: # new in 1.5 + Enabled: true +Lint/UnmodifiedReduceAccumulator: # new in 1.1 + Enabled: true +Security/IoMethods: # new in 1.22 + Enabled: true +Style/ArgumentsForwarding: # new in 1.1 + Enabled: true +Style/CollectionCompact: # new in 1.2 + Enabled: true +Style/DocumentDynamicEvalDefinition: # new in 1.1 + Enabled: true +Style/EndlessMethod: # new in 1.8 + Enabled: true +Style/HashConversion: # new in 1.10 + Enabled: true +Style/HashExcept: # new in 1.7 + Enabled: true +Style/IfWithBooleanLiteralBranches: # new in 1.9 + Enabled: true +Style/InPatternThen: # new in 1.16 + Enabled: true +Style/MultilineInPatternThen: # new in 1.16 + Enabled: true +Style/NegatedIfElseCondition: # new in 1.2 + Enabled: true +Style/NilLambda: # new in 1.3 + Enabled: true +Style/NumberedParameters: # new in 1.22 + Enabled: true +Style/NumberedParametersLimit: # new in 1.22 + Enabled: true +Style/QuotedSymbols: # new in 1.16 + Enabled: true +Style/RedundantArgument: # new in 1.4 + Enabled: true +Style/RedundantSelfAssignmentBranch: # new in 1.19 + Enabled: true +Style/SelectByRegexp: # new in 1.22 + Enabled: true +Style/StringChars: # new in 1.12 + Enabled: true +Style/SwapValues: # new in 1.1 + Enabled: true
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/devtools/README.md.erb Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,150 @@ +# ESP-IDF Components library + +[![Build Status](https://github.com/UncleRus/esp-idf-lib/workflows/Build%20examples/badge.svg)](https://github.com/UncleRus/esp-idf-lib/actions?query=workflow%3A%22Build+examples%22) +[![Build the documentation](https://github.com/UncleRus/esp-idf-lib/workflows/Build%20the%20documentation/badge.svg)](https://github.com/UncleRus/esp-idf-lib/actions?query=workflow%3A%22Build+the+documentation%22) +[![Docs Status](https://readthedocs.org/projects/esp-idf-lib/badge/?version=latest&style=flat)](https://esp-idf-lib.readthedocs.io/en/latest/) + +Components for Espressif ESP32 [ESP-IDF framework](https://github.com/espressif/esp-idf) +and [ESP8266 RTOS SDK](https://github.com/espressif/ESP8266_RTOS_SDK). + +Part of them ported from [esp-open-rtos](https://github.com/SuperHouse/esp-open-rtos). + +## Supported versions of frameworks and devices + +| Chip | Framework | Versions +|----------------|--------------------|----------------------- +| ESP32 | ESP-IDF | All officially supported versions (see [Support Period Policy](https://github.com/espressif/esp-idf/blob/master/SUPPORT_POLICY.md)) and `master` +| ESP32-S2 *[1]* | ESP-IDF | All officially supported versions and `master` +| ESP32-C3 *[1]* | ESP-IDF | All officially supported versions and `master` +| ESP8266 *[2]* | ESP8266 RTOS SDK | `master`, v3.4 + +[1] *Use "`idf.py set-target esp32s2`" or "`idf.py set-target esp32c3`" before "`idf.py menuconfig`" to change +the chip type.* + +[2] *Due to the incompatibility of ESP8266 drivers and hardware, some +libraries are not* *supported on ESP8266 (see "ESP8266" column in the tables).* + +## How to use + +### ESP32 + +Clone this repository somewhere, e.g.: + +```Shell +cd ~/myprojects/esp +git clone https://github.com/UncleRus/esp-idf-lib.git +``` + +Add path to components in your [project makefile](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/build-system-legacy.html), +e.g: + +```Makefile +PROJECT_NAME := my-esp-project +EXTRA_COMPONENT_DIRS := /home/user/myprojects/esp/esp-idf-lib/components +include $(IDF_PATH)/make/project.mk +``` + +or in [CMakeLists.txt](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/build-system.html): + +```CMake +cmake_minimum_required(VERSION 3.5) +set(EXTRA_COMPONENT_DIRS /home/user/myprojects/esp/esp-idf-lib/components) +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(my-esp-project) +``` + +or with CMake [FetchContent](https://cmake.org/cmake/help/latest/module/FetchContent.html) + +```CMake +cmake_minimum_required(VERSION 3.11) +include(FetchContent) +FetchContent_Declare( + espidflib + GIT_REPOSITORY https://github.com/UncleRus/esp-idf-lib.git +) +FetchContent_MakeAvailable(espidflib) +set(EXTRA_COMPONENT_DIRS ${espidflib_SOURCE_DIR}/components) +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(my-esp-project) +``` + +### ESP8266 RTOS SDK + +Clone this repository somewhere, e.g.: + +```Shell +cd ~/myprojects/esp +git clone https://github.com/UncleRus/esp-idf-lib.git +``` + +Add path to components in your [project makefile](https://docs.espressif.com/projects/esp8266-rtos-sdk/en/latest/api-guides/build-system.html), +e.g: + +```Makefile +PROJECT_NAME := my-esp-project +EXTRA_COMPONENT_DIRS := /home/user/myprojects/esp/esp-idf-lib/components +EXCLUDE_COMPONENTS := max7219 mcp23x17 led_strip max31865 ls7366r max31855 +include $(IDF_PATH)/make/project.mk +``` + +See [GitHub examples](https://github.com/UncleRus/esp-idf-lib/tree/master/examples) +or [GitLab examples](https://gitlab.com/UncleRus/esp-idf-lib/tree/master/examples). + +## Documentation + +- [Documentation](https://esp-idf-lib.readthedocs.io/en/latest/) +- [Frequently asked questions](FAQ.md) + +## Components +<% groups.sort_by!(&:description).each do |g| %> +### <%= g.description %> + +| Component | Description | License | Supported on | Thread safety +|--------------------------|----------------------------------------------------------------------------------|---------|--------------------|-------------- +<% components = all_components.select { |c| c.group_of?(g.name) }.sort_by!(&:name).each do |c| + name = format("%-24s", "**#{c.name}**") + description = format("%-80s", c.description) + licenses = format("%-7s", c.licenses.map(&:name).join(", ")) + supported_on = format("%-18s", c.targets.map { |t| "`#{t.name}`" }.join(", ")) -%> +| <%= name %> | <%= description %> | <%= licenses %> | <%= supported_on %> | <%= c.thread_safe ? "Yes" : "No" %> +<% end -%> +<% end -%> + +## Library maintainers + +- [Ruslan V. Uss](https://github.com/UncleRus) +- [Tomoyuki Sakurai](https://github.com/trombik) + +## Credits + +- [Tomoyuki Sakurai](https://github.com/trombik), developer of the LM75 and + SK9822/APA102 drivers, author of the RTOS SDK ESP82666 support, master CI +- [Gunar Schorcht](https://github.com/gschorcht), developer of SHT3x, BME680 + and CCS811 drivers +- [Brian Schwind](https://github.com/bschwind), developer of TS2561 and + TSL4531 drivers +- [Andrej Krutak](https://github.com/andree182), developer of BH1750 driver +- Frank Bargstedt, developer of BMP180 driver +- [sheinz](https://github.com/sheinz), developer of BMP280 driver +- [Jonathan Hartsuiker](https://github.com/jsuiker), developer of DHT driver +- [Grzegorz Hetman](https://github.com/hetii), developer of DS18B20 driver +- [Alex Stewart](https://github.com/astewart-consensus), developer of DS18B20 driver +- [Richard A Burton](mailto:richardaburton@gmail.com), developer of DS3231 driver +- [Bhuvanchandra DV](https://github.com/bhuvanchandra), developer of DS3231 driver +- [Zaltora](https://github.com/Zaltora), developer of INA3231 driver +- [Bernhard Guillon](https://gitlab.com/mrnice), developer of MS5611-01BA03 driver +- [Pham Ngoc Thanh](https://github.com/panoti), developer of PCF8591 driver +- [Lucio Tarantino](https://github.com/dianlight), developer of ADS111x driver +- [Julian Dörner](https://github.com/juliandoerner), developer of TSL2591 driver +- [FastLED community](https://github.com/FastLED), developers of `lib8tion`, + `color` and `noise` libraries +- [Erriez](https://github.com/Erriez), developer of MH-Z19B driver +- [David Douard](https://github.com/douardda), developer of MH-Z19B driver +- [Nate Usher](https://github.com/nated0g), developer of SCD30 driver +- [Josh Kallus](https://github.com/Jkallus), developer of LS7366R driver +- [saasaa](https://github.com/saasaa), developer of HTS221 driver +- [Timofei Korostelev](https://github.com/chudsaviet), developer of HT16K33 driver +- [Jose Manuel Perez](https://github.com/jmpmscorp), developer of LC709203F driver +- [Weslley Duarte](https://github.com/weslleymfd), developer of ADS130E08 driver +- [Jan Veeh](https://github.com/janveeh), developer of ICM42670 driver +- [Marc Luehr](https://github.com/Th3Link), developer of VEML7700 driver
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/devtools/Rakefile Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +task default: [:test] + +desc "Run all tests" +task test: [:rubocop, :rspec] + +desc "Run rubocop" +task :rubocop do + sh "rubocop" +end + +desc "Run rspec" +task :rspec do + sh "rspec --format d" +end + +desc "Update README.md" +task :readme do + require "erb" + require_relative "spec/group_list" + require_relative "spec/component" + + template = File.read("README.md.erb") + groups = GroupList.new("groups.yml").all + + # * select if it is a directory + # * make path to metadata file + # * read it + # * parse it as YAML + # * take all components under "components" key + # * flatten the list of components + # * create a Component from the item + all_components = Dir.children("../components") + .select { |f| File.directory?(File.join("../components", f)) } + .map { |c| File.join("../components", c, ".eil.yml") } + .map { |f| File.read(f) } + .map { |f| YAML.safe_load(f) } + .map { |y| y["components"] } + .flatten + .map { |c| Component.new(c) } + markdown = ERB.new(template, trim_mode: "%-").result(binding) + puts markdown +end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/devtools/cmake-get-requires/.gitignore Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,4 @@ +CMakeCache.txt +CMakeFiles +cmake_install.cmake +Makefile
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/devtools/cmake-get-requires/CMakeLists.txt Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,23 @@ +# a script to parse CMakeLists.txt of a component, print REQUIRES. +# +# usage: +# cmake -DCOMPONENT_NAME:STRING=framebuffer . >/dev/null +# +# output in stderr: +# REQUIRES:log;color + +cmake_minimum_required(VERSION 3.5) + +# mock idf_component_register() +function(idf_component_register) + set(multiValueArgs REQUIRES) + cmake_parse_arguments(MY "" "" "${multiValueArgs}" ${ARGN}) + + # print REQUIRES argument to stderr. MY_REQUIRES is a semicolon separated + # string, such as `foo;bar` + message(NOTICE "REQUIRES:${MY_REQUIRES}") +endfunction() + +include(${CMAKE_CURRENT_SOURCE_DIR}/../../components/${COMPONENT_NAME}/CMakeLists.txt) + +project(ProjectName)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/devtools/groups.yml Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,49 @@ +--- + +- name: adc-dac + description: ADC/DAC libraries + +- name: common + description: Common libraries + +- name: rtc + description: Real-time clocks + +- name: humidity + description: Humidity sensors + +- name: temperature + description: Temperature sensors + +- name: pressure + description: Pressure sensors + +- name: air-quality + description: Air quality sensors + +- name: gas + description: Gas sensors + +- name: current + description: Current and power sensors + +- name: magnetic + description: Magnetic sensors + +- name: light + description: Light sensors + +- name: gpio + description: GPIO expanders + +- name: led + description: LED drivers + +- name: input + description: Input device drivers + +- name: misc + description: Other misc libraries + +- name: imu + description: Inertial measurement units
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/devtools/persons.yml Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,117 @@ +--- +- name: UncleRus + email: unclerus@gmail.com + full_name: Ruslan V. Uss + gh_id: UncleRus + +- name: trombik + email: y@trombik.org + full_name: Tomoyuki Sakurai + gh_id: trombik + +- name: dianlight + full_name: Lucio Tarantino + gh_id: dianlight + +- name: Andrej + full_name: Andrej Krutak + email: dev@andree.sk + +- name: gschorcht + full_name: Gunar Schorcht + gh_id: gschorcht + +- name: FrankB + full_name: Frank Bargstedt + +- name: sheinz + gh_id: sheinz + +- name: FastLED + gh_id: FastLED + full_name: FastLED project + +- name: jsuiker + gh_id: jsuiker + +- name: PavelM + email: merzlyakovpavel@gmail.com + +- name: AlexS + email: foogod@gmail.com + +- name: GrzegorzH + email: ghetman@gmail.com + +- name: BhuvanchandraD + email: bhuvanchandra.dv@gmail.com + +- name: RichardA + email: richardaburton@gmail.com + +- name: Zaltora + gh_id: Zaltora + +- name: BernhardG + email: Bernhard.Guillon@begu.org + +- name: zeroday + # GH account does not exist + email: zeroday@nodemcu.com + +- name: PhamNgocT + # GH account exists, but not sure that is the correct one + email: pnt239@gmail.com + +- name: Sensirion + gh_id: Sensirion + +- name: nated0g + gh_id: nated0g + full_name: Nate Usher + +- name: bschwind + gh_id: bschwind + full_name: Brian Schwind + +- name: juliandoerner + gh_id: juliandoerner + full_name: Julian Doerner + +- name: Erriez + gh_id: Erriez + +- name: DavidD + full_name: David Douard + email: david.douard@sdfa3.org + +- name: Jkallus + full_name: Joshua Kallus + email: joshk.kallus3@gmail.com + gh_id: Jkallus + +- name: saasaa + full_name: Alexander Bodenseher + gh_id: saasaa + +- name: chudsaviet + full_name: Timofei Korostelev + email: timofei_public@dranik.dev + gh_id: chudsaviet + +- name: jmpmscorp + full_name: Jose Manuel Perez + gh_id: jmpmscorp + +- name: weslleymfd + full_name: Weslley Duarte + gh_id: weslleymfd + +- name: janveeh + full_name: Jan Veeh + gh_id: janveeh + +- name: Th3Link + full_name: Marc Luehr + gh_id: th3link + email: marcluehr@gmail.com
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/devtools/spec/component.rb Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,117 @@ +# frozen_string_literal: true + +require_relative "copyright" +require_relative "target" +require_relative "person" +require_relative "license" +require_relative "group" + +class Component + # Component represents a component in metadata. it is not a `component` in + # esp-idf. a metadata may contain multiple Components. usually, an esp-idf + # component has one `component` and its metadata usually contains one + # Component. but a project, such as `esp-idf-lib` repository, may have + # multiple `components`. + + # a list of valid keys. some values of keys are simply String or Integer + # object. others are objects, or resources, defined in the specification. + VALID_KEYS = %w[ + name + description + group + groups + code_owners + depends + thread_safe + targets + licenses + copyrights + ].freeze + + def initialize(hash) + validate_keys(hash) + @metadata = hash + @name = name + end + + attr_reader :metadata + + def validate_keys(hash) + # validate basic constraints only. the classes are for tests in spec + # files, providing readable tests and results. the actual specification is + # in the spec files, not in classes. rspec is more readable and maintainable + # than ruby code. + # + # maybe, if these classes are found to be useful, create a gem of the + # classes and specification tests in the gem. until then, keep the classes + # simple so that others can maintain the specification. + raise ArgumentError, "missing name" unless hash.key?("name") + raise ArgumentError, "empty name" if hash["name"].empty? + + hash.each_key do |k| + raise ArgumentError, "unknown key: `#{k}`" unless VALID_KEYS.include?(k) + end + end + + def to_s + metadata["name"] + end + + # special keys that return instances of classes. + def group + Group.new(metadata["group"]) + end + + def groups + metadata["groups"].map { |g| Group.new(g) } + end + + def code_owners + metadata["code_owners"].map { |p| Person.new(p) } + end + + def targets + metadata["targets"].map { |t| Target.new(t) } + end + + def licenses + metadata["licenses"].map { |l| License.new(l) } + end + + def copyrights + metadata["copyrights"].map { |c| Copyright.new(c) } + end + + def valid_key_with_question?(name) + name.to_s.end_with?("?") && VALID_KEYS.include?(name.to_s.chop) + end + + def valid_key?(name) + VALID_KEYS.include?(name.to_s) + end + + def description + # if description contains newline, remove it + metadata["description"].split("\n").join(" ") + end + + def method_missing(name, *args, &block) + # name?, description?, etc + return metadata.key?(name.to_s.chop) if valid_key_with_question?(name) + # name, etc + return metadata[name.to_s] if valid_key?(name) + + super + end + + def respond_to_missing?(name, include_private = false) + # when name is not something we don't know, do `super`, i.e. raising + # unknown methods error. + super unless valid_key_with_question?(name) || valid_key?(name) + end + + def group_of?(arg) + group_name = arg.respond_to?(:name) ? arg.name : arg + group.name == group_name || groups.map(&:name).include?(group_name) + end +end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/devtools/spec/component_spec.rb Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,169 @@ +# frozen_string_literal: true + +require_relative "spec_helper" + +VALID_THREAD_SAFE_VALUES = [true, false, "N/A"].freeze + +# rubocop:disable Metrics/BlockLength +metadata_array.each do |m| + RSpec.describe "metadata #{m}" do + it "has components" do + expect(m.components?).to be true + end + + it "has one or more of components" do + expect(m.components.length).to be >= 1 + end + + m.components.each do |c| + describe "component #{c}" do + subject { c } + it "does not raise error" do + expect { subject }.not_to raise_error + end + + describe "name" do + it "has name" do + expect(subject.name?).to be true + end + + it "has String name" do + expect(subject.name).to be_kind_of(String) + end + + it "has non-empty name" do + expect(subject.name).not_to be_empty + end + end + + describe "description" do + it "has description" do + expect(subject.description?).to be true + end + + it "has String description" do + expect(subject.description).to be_kind_of(String) + end + end + + describe "group" do + it "has a primary group" do + expect(subject.group?).to be true + end + + it "has a valid primary group" do + expect { subject.group }.not_to raise_error + end + end + + describe "groups" do + context "when it has one or more groups" do + it "has zero or more of groups" do + skip "it has no groups" unless subject.groups? + expect(subject.groups.length).to be >= 0 + end + + it "has valid groups" do + skip "it has no groups" unless subject.groups? + skip "it has zero group" if subject.groups? && subject.groups.empty? + expect { subject.groups }.not_to raise_error + end + end + end + + describe "depends" do + # XXX `depends` needs better tests because `depends` has zero or + # more of components, and some components are one in our components, + # others are one in esp-idf. we need to know one in `depends` + # actually exists. other resources, such as `People` resolves the + # issue by having a list of `People`. + context "when it has depends" do + it "has zero or more of depends" do + skip "it has no depends" unless subject.depends? + expect(subject.depends.length).to be >= 0 + end + + it "has valid depends" do + skip "it has no depends" unless subject.depends? + skip "it has zero depends" if subject.depends? && subject.depends.empty? + expect { subject.depends }.not_to raise_error + end + end + end + + describe "thread_safe" do + it "has thread_safe" do + expect(subject.thread_safe?).to be true + end + + it "has valid values of thread_safe" do + expect(VALID_THREAD_SAFE_VALUES).to include subject.thread_safe + end + end + + describe "targets" do + it "has targets" do + expect(subject.targets?).to be true + end + + it "has valid targets" do + expect { subject.targets }.not_to raise_error + end + end + + describe "licenses" do + it "has licenses" do + expect(subject.licenses?).to be true + end + + it "has valid licenses" do + expect { subject.licenses }.not_to raise_error + end + + it "has one or more of licenses" do + expect(subject.licenses.length).to be >= 1 + end + end + + describe "copyrights" do + it "has copyrights" do + expect(subject.copyrights?).to be true + end + + it "has valid copyrights" do + expect { subject.copyrights }.not_to raise_error + end + + it "has one or more of copyrights" do + expect(subject.copyrights.length).to be >= 1 + end + end + + describe "each copyright" do + it "has only one of name or author in copyrights" do + subject.copyrights.each do |copyright| + expect(copyright.name? && copyright.author?).to be false + end + end + + context "when a copyright has author" do + it "has valid Person as copyright author" do + subject.copyrights.select(&:author?).each do |copyright| + expect(copyright.author).to be_a Person + end + end + end + + context "when a copyright has name" do + it "has valid Person as copyright author" do + subject.copyrights.select(&:name?).each do |copyright| + expect(copyright.name).to be_a Person + end + end + end + end + end + end + end +end +# rubocop:enable Metrics/BlockLength
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/devtools/spec/copyright.rb Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +require_relative "person" + +class Copyright + VALID_KEYS = %w[ + author + name + year + ].freeze + + def initialize(hash) + raise ArgumentError, "expect Hash, got `#{hash.class}`" unless hash.is_a?(Hash) + + validate_keys(hash) + @metadata = hash + end + + attr_reader :metadata + + def validate_keys(hash) + hash.each_key do |k| + raise ArgumentError, "unknown key: `#{k}`. valid keys are: #{VALID_KEYS.join(' ')}" unless VALID_KEYS.include? k + end + end + + def author? + metadata.key?("author") + end + + def author + Person.new(metadata["author"]) + end + + def name + Person.new("name" => metadata["name"]) + end + + def name? + metadata.key?("name") + end + + def year + metadata["year"] + end + + def year? + metadata.key?("year") + end +end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/devtools/spec/group.rb Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +class Group + VALID_KEYS = %w[name description].freeze + + def initialize(arg) + validate_arg(arg) + validate_keys(arg) if arg.is_a? Hash + + @metadata = if arg.is_a? String + { "name" => arg } + else + arg + end + end + + def validate_keys(arg) + arg.each_key do |k| + raise ArgumentError, "unknown key: `#{k}`. valid keys are: #{VALID_KEYS.join(' ')}" unless VALID_KEYS.include? k + end + raise ArgumentError, "a key, `name` is required, but missing" unless arg.key?("name") + end + + def validate_arg(arg) + raise ArgumentError, "argument must be String or Hash" unless arg.is_a?(String) || arg.is_a?(Hash) + end + + def name? + @metadata.key?("name") + end + + def name + @metadata["name"] + end + + def description? + @metadata.key?("description") + end + + def description + @metadata["description"] + end + + def to_s + @metadata["name"] + end +end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/devtools/spec/group_list.rb Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +require "yaml" +require_relative "group" + +class GroupList + # path to `groups.yml` + def initialize(arg) + @path = File.expand_path(arg) + end + + attr_reader :path + + def load_file + File.read(path) + end + + def parse + YAML.safe_load(load_file) + end + + def metadata + return @metadata if @metadata + + @metadata = parse + end + + def all + metadata.map { |g| Group.new(g) } + end + + def lookup(name) + metadata.select { |g| g["name"] == name }.map { |g| Group.new(g) } + end +end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/devtools/spec/groups_spec.rb Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,66 @@ +# frozen_string_literal: true + +require_relative "spec_helper" +require_relative "group_list" + +file = File.join(File.dirname(__FILE__), "..", "groups.yml") + +RSpec.describe GroupList do + subject { GroupList.new(file) } + + it "creates new instance" do + expect { subject }.not_to raise_error + end + + describe "#load_file" do + it "does not raise" do + expect { subject.load_file }.not_to raise_error + end + end + + describe "#parse" do + it "does not raise" do + expect { subject.parse }.not_to raise_error + end + end + + describe "#metadata" do + it "returns Array" do + expect(subject.metadata).to be_a Array + end + end +end + +RSpec.describe "Group list metadata #{file}" do + groups = GroupList.new(file) + + groups.all.each do |group| + describe "Group #{group}" do + subject { group } + + it "is a Group" do + expect(group).to be_a Group + end + + it "has name as a key" do + expect(group.name?).to be true + end + + it "has non-empty name" do + expect(group.name).not_to be_empty + end + + it "has description as a key" do + expect(group.description?).to be true + end + + it "has non-empty description" do + expect(group.description).not_to be_empty + end + + it "is a unique group" do + expect(groups.lookup(group.name).length).to be 1 + end + end + end +end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/devtools/spec/license.rb Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +class License + def initialize(hash) + raise ArgumentError, "missing key `name`" unless hash.key?("name") + + @metadata = hash + end + + def name + @metadata["name"] + end + + def to_s + name + end +end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/devtools/spec/metadata.rb Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +require_relative "component" + +# A class that represents metadaata, `.eil.yml` +class Metadata + # path: path to component root directory + def initialize(path) + raise ArgumentError, "path is missing" unless path + + @path = path + @name = File.basename(path) + raise ArgumentError, "path `#{path}` does not have basename" if @name.empty? + + metadata + end + + attr_reader :path, :name + + def metadata + return @metadata if @metadata + + file = File.join(path, ".eil.yml") + @metadata = YAML.safe_load(File.read(file)) + rescue StandardError => e + warn "failed to open `#{file}`. does component `#{File.basename(path)}` have `.eil.yml` file?" + raise e + end + + def components? + metadata.key?("components") + end + + def components + metadata["components"].map { |c| Component.new(c) } + end + + def to_s + name + end +end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/devtools/spec/person.rb Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,100 @@ +# frozen_string_literal: true + +require "yaml" + +# A class that repesents Peson +class Person + PERSON_FILE = File.expand_path(File.join(File.dirname("__FILE__"), "persons.yml")).freeze + + def initialize(arg) + validate_arg(arg) + validate_keys(arg) if arg.is_a? Hash + + name = if arg.is_a? String + arg + else + arg["name"] + end + @metadata = lookup_person(name) + end + + attr_reader :metadata, :persons + + def validate_keys(hash) + raise ArgumentError, "missing key: `name`" unless hash.key?("name") + end + + def valid_arg_class?(arg) + arg.is_a?(String) || arg.is_a?(Hash) + end + + def validate_arg(arg) + raise ArgumentError, "String or dict is ecpected, but got `#{arg.class}`" unless valid_arg_class?(arg) + end + + def load_person_file + File.read(PERSON_FILE) + end + + def parse(string) + return @persons if @persons + + @persons = YAML.safe_load(string) + rescue StandardError => e + warn "failed to parse #{PERSON_FILE} as YAML" + raise e + end + + def lookup_person(name) + parse(load_person_file) + person = persons.select { |p| p["name"] == name } + raise ArgumentError, "cannot find Person with name `#{name}` in #{PERSON_FILE}" unless person + raise ArgumentError, "Person with name `#{name}` has duplicated entry in #{PERSON_FILE}" if person.length > 1 + + person.first + end + + def name? + metadata.key?("name") + end + + def name + metadata["name"] + end + + def full_name? + metadata.key?("full_name") + end + + def full_name + metadata["full_name"] + end + + def gh_id? + metadata.key?("gh_id") + end + + def gh_id + metadata["gh_id"] + end + + def email? + metadata.key?("email") + end + + def email + metadata["email"] + end + + def website? + metadata.key?("website") + end + + def website + metadata["website"] + end + + def to_s + metadata["name"] + end +end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/devtools/spec/person_list.rb Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +require "yaml" + +class PersonList + # path to `persons.yml` + def initialize(arg) + @path = File.expand_path(arg) + end + + attr_reader :path + + def load_file + File.read(path) + end + + def parse + YAML.safe_load(load_file) + end + + def metadata + return @metadata if @metadata + + @metadata = parse + end + + def all + metadata + end + + def lookup(name) + metadata.select { |g| g["name"] == name } + end +end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/devtools/spec/persons_spec.rb Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +require_relative "spec_helper" +require_relative "person_list" +require_relative "person" + +file = File.join(File.dirname(__FILE__), "..", "persons.yml") + +RSpec.describe "Person list metadata #{file}" do + persons = PersonList.new(file) + + persons.all.each do |person| + describe "Person #{person}" do + subject { Person.new(person) } + + it "has a name" do + expect(subject.name?).to be true + end + + it "has non-empty name" do + expect(subject.name).not_to be_empty + end + + it "has one or more of contact information" do + has_contact = subject.email? || subject.gh_id? || subject.full_name? || subject.website? + expect(has_contact).to be true + end + + it "is unique in persons.yml" do + expect(persons.lookup(subject.name).length).to be 1 + end + end + end +end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/devtools/spec/spec_helper.rb Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +require "rspec" +require "yaml" +require_relative "metadata" + +@component_dir = File.expand_path(File.join(File.dirname(__FILE__), "../../components")) + +def metadata_array + directories = Dir.children(Dir.new(@component_dir)).map { |c| File.join(@component_dir, c) } + directories = directories.select { |d| File.directory?(d) } + directories.map { |path| Metadata.new(path) } +end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/devtools/spec/target.rb Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,65 @@ +# frozen_string_literal: true + +require "yaml" + +# A class that represents build target + +class Target + VALID_KEYS = %w[name].freeze + TARGETS_FILE = File.expand_path(File.join(File.dirname("__FILE__"), "..", "targets.yml")).freeze + + def initialize(arg) + validate_arg(arg) + validate_keys(arg) if arg.is_a?(Hash) + @metadata = if arg.is_a?(String) + { "name" => arg } + else + arg + end + end + + attr_reader :metadata + + def valid_arg_class?(arg) + arg.is_a?(String) || arg.is_a?(Hash) + end + + def validate_arg(arg) + raise ArgumentError, "String or dict is ecpected, but got `#{arg.class}`" unless valid_arg_class?(arg) + end + + def validate_keys(hash) + hash.each_key do |k| + raise ArgumentError, "unknown key: `#{k}`. valid keys are: #{VALID_KEYS.join(' ')}" unless VALID_KEYS.include?(k) + end + end + + def name? + metadata.key?("name") + end + + def name + metadata["name"] + end + + def load_file + File.read(TARGETS_FILE) + end + + def parse(string) + YAML.safe_load(string) + rescue StandardError => e + warn "failed to parse #{TARGETS_FILE} as YAML" + raise e + end + + def targets + return @targets if @targets + + @targets = parse(load_file) + end + + def lookup(name) + targets.select { |t| t.name == name } + end +end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/devtools/spec/target_list.rb Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,6 @@ +# frozen_string_literal: true + +require "person_list" + +class TargetList < PersonList +end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/devtools/spec/targets_spec.rb Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +require_relative "spec_helper" +require_relative "target_list" + +file = File.join(File.dirname(__FILE__), "..", "targets.yml") + +RSpec.describe "Target list metadata #{file}" do + targets = TargetList.new(file) + + targets.all.each do |target| + describe "Target #{target}" do + subject { Target.new(target) } + + it "has a name" do + expect(subject.name?).to be true + end + + it "has non-empty name" do + expect(subject.name).not_to be_empty + end + + it "is a unique target in the list" do + expect(targets.lookup(subject.name).length).to be 1 + end + end + end +end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/esp-idf-lib/devtools/targets.yml Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,5 @@ +--- + +- name: esp32 + +- name: esp8266
--- a/main/CMakeLists.txt Sun Mar 26 22:22:45 2023 +0200 +++ b/main/CMakeLists.txt Mon Mar 27 22:13:21 2023 +0200 @@ -1,2 +1,2 @@ -idf_component_register(SRCS "iotbalkon.c" +idf_component_register(SRCS config.c iotbalkon.c task_bmp280.c INCLUDE_DIRS ".")
--- a/main/Kconfig.projbuild Sun Mar 26 22:22:45 2023 +0200 +++ b/main/Kconfig.projbuild Mon Mar 27 22:13:21 2023 +0200 @@ -2,6 +2,24 @@ orsource "$IDF_PATH/examples/common_components/env_caps/$IDF_TARGET/Kconfig.env_caps" + choice CODE_PROJECT + prompt "Select project build target" + default CODE_TESTING + help + Select to build for Testing or Production + + config CODE_TESTING + bool "Build for testing" + help + Select this to build for the test environment + + config CODE_PRODUCTION + bool "Build for production" + help + Select this to build for final production + + endchoice + menu "I2C bus" config I2C_MASTER_SCL
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/main/config.c Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,10 @@ + +#include "config.h" + +/* + * Sensors parameters and defines + */ +bmp280_params_t bmp280_params; +bmp280_t bmp280_dev; + +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/main/config.h Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,64 @@ +/** + * @file config.h + * @brief The 'iotbalkon' configuration data. + */ + +#ifndef _CONFIG_H +#define _CONFIG_H + +// Global includes for the project + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <stdbool.h> +#include <time.h> +#include <errno.h> +#include <sys/unistd.h> +#include <sys/fcntl.h> +#include <sys/time.h> + +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/semphr.h" +#include "freertos/event_groups.h" +//#include "freertos/queue.h" +#include "driver/gpio.h" +#include "driver/i2c.h" +//#include "driver/adc.h" +//#include "driver/rtc_io.h" +//#include "soc/sens_periph.h" +//#include "soc/rtc.h" +//#include "esp_adc_cal.h" +#include "esp_log.h" +//#include "esp_spiffs.h" +//#include "esp_vfs.h" +//#include "esp_system.h" +//#include "esp_wifi.h" +//#include "esp_wpa2.h" +//#include "esp_event.h" +//#include "nvs_flash.h" +//#include "lwip/sockets.h" +//#include "lwip/dns.h" +//#include "lwip/netdb.h" +//#include "mqtt_client.h" + +/* + * esp-idf-lib + */ +#include <i2cdev.h> +#include <bmp280.h> + +/* + * Application sources + */ +#include "task_bmp280.h" +//#include "task_wifi.h" +//#include "task_mqtt.h" +//#include "task_user.h" +//#include "xutil.h" + + + + +#endif
--- a/main/iotbalkon.c Sun Mar 26 22:22:45 2023 +0200 +++ b/main/iotbalkon.c Mon Mar 27 22:13:21 2023 +0200 @@ -1,16 +1,76 @@ - +/** + * @file iotbalkon.c + * @brief iotbalkon project. + */ -#include <stdio.h> -#include "freertos/FreeRTOS.h" -#include "freertos/task.h" -#include "driver/gpio.h" -#include "esp_log.h" - - +#include "config.h" static const char *TAG = "iotbalkon"; + +static TaskHandle_t xTaskBMP280 = NULL; + +extern BMP280_State *bmp280_state; ///< I2C state +extern SemaphoreHandle_t xSemaphoreBMP280; ///< I2C lock semaphore +extern bmp280_params_t bmp280_params; +extern bmp280_t bmp280_dev; + + void app_main(void) { - ESP_LOGI(TAG, "Starting"); +#ifdef CONFIG_CODE_PRODUCTION + ESP_LOGI(TAG, "Starting production"); +#endif +#ifdef CONFIG_CODE_TESTING + ESP_LOGI(TAG, "Starting testing"); +#endif + + ESP_ERROR_CHECK(i2cdev_init()); + + bmp280_init_default_params(&bmp280_params); + memset(&bmp280_dev, 0, sizeof(bmp280_t)); + + i2c_dev_t dev = { 0 }; + dev.cfg.sda_io_num = CONFIG_I2C_MASTER_SDA; + dev.cfg.scl_io_num = CONFIG_I2C_MASTER_SCL; + dev.cfg.master.clk_speed = 1000000; + + dev.addr = 0x39; + if (i2c_dev_probe(&dev, I2C_DEV_WRITE) == 0) { + ESP_LOGI(TAG, "Found ADPS-9930"); + } + dev.addr = 0x40; + if (i2c_dev_probe(&dev, I2C_DEV_WRITE) == 0) { + ESP_LOGI(TAG, "Found INA219 Battery"); + } + dev.addr = 0x41; + if (i2c_dev_probe(&dev, I2C_DEV_WRITE) == 0) { + ESP_LOGI(TAG, "Found INA219 Solar"); + } + dev.addr = 0x76; + if (i2c_dev_probe(&dev, I2C_DEV_WRITE) == 0) { + ESP_ERROR_CHECK(bmp280_init_desc(&bmp280_dev, BMP280_I2C_ADDRESS_0, 0, CONFIG_I2C_MASTER_SDA, CONFIG_I2C_MASTER_SCL)); + ESP_ERROR_CHECK(bmp280_init(&bmp280_dev, &bmp280_params)); + ESP_LOGI(TAG, "Found BMP280 @ 0x76 id: 0x%02x", bmp280_dev.id); + } + dev.addr = 0x77; + if (i2c_dev_probe(&dev, I2C_DEV_WRITE) == 0) { + ESP_LOGI(TAG, "Found BMP280 @ 0x77"); + } + + /* + * Create FreeRTOS tasks + */ + xSemaphoreBMP280 = xSemaphoreCreateMutex(); + + xTaskCreate(&task_bmp280, "task_bmp280", 2560, NULL, 8, &xTaskBMP280); + + /* + * Main application loop. + */ + while (1) { + request_bmp280(); + vTaskDelay(2000 / portTICK_PERIOD_MS); + } + // Not reached. }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/main/task_bmp280.c Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,84 @@ +/** + * @file task_bmp280.c + * @brief The FreeRTOS task to query the BMP280 sensor. + */ + + +#include "config.h" + + +static const char *TAG = "task_bmp280"; + +SemaphoreHandle_t xSemaphoreBMP280 = NULL; ///< Semaphore BMP280 task +EventGroupHandle_t xEventGroupBMP280; ///< Events BMP280 task +BMP280_State *bmp280_state; ///< Public state for other tasks + +extern bmp280_params_t bmp280_params; +extern bmp280_t bmp280_dev; + +const int TASK_BMP280_REQUEST_DONE = BIT0; ///< All requests are done. +const int TASK_BMP280_REQUEST_TB = BIT1; ///< Request Temperature and Barometer + + + +void request_bmp280(void) +{ + xEventGroupClearBits(xEventGroupBMP280, TASK_BMP280_REQUEST_DONE); + xEventGroupSetBits(xEventGroupBMP280, TASK_BMP280_REQUEST_TB); +} + + + +bool ready_bmp280(void) +{ + if (xEventGroupGetBits(xEventGroupBMP280) & TASK_BMP280_REQUEST_DONE) + return true; + return false; +} + + + +/* + * Task to read BMP280 sensor on request. + */ +void task_bmp280(void *pvParameter) +{ + float pressure, temperature, humidity; + + ESP_LOGI(TAG, "Starting task BMP280 sda=%d scl=%d", CONFIG_I2C_MASTER_SDA, CONFIG_I2C_MASTER_SCL); + bmp280_state = malloc(sizeof(BMP280_State)); + + bmp280_state->bmp280.valid = false; + bmp280_state->bmp280.fake = false; + bmp280_state->bmp280.address = 0; + bmp280_state->bmp280.error = BMP280_ERR_NONE; + + /* event handler and event group for this task */ + xEventGroupBMP280 = xEventGroupCreate(); + EventBits_t uxBits; + + /* + * Task loop forever. + */ + ESP_LOGI(TAG, "Starting loop BMP280 sensor"); + while (1) { + + uxBits = xEventGroupWaitBits(xEventGroupBMP280, TASK_BMP280_REQUEST_TB, pdFALSE, pdFALSE, portMAX_DELAY ); + + if (uxBits & TASK_BMP280_REQUEST_TB) { + + ESP_LOGI(TAG, "Requested BMP280 readings"); + + if (bmp280_read_float(&bmp280_dev, &temperature, &pressure, &humidity) != ESP_OK) { + ESP_LOGI(TAG, "Temperature/pressure reading failed"); + } else { + ESP_LOGI(TAG, "Pressure: %.2f Pa, Temperature: %.2f C", pressure, temperature); + } + xEventGroupClearBits(xEventGroupBMP280, TASK_BMP280_REQUEST_TB); + xEventGroupSetBits(xEventGroupBMP280, TASK_BMP280_REQUEST_DONE); +#if 1 +// ESP_LOGI(TAG, "Battery raw: %4d, atten: %d %.3f volt, error: %d", i2c_reading, atten, i2c_state->Batt_voltage / 1000.0, i2c_state->Batt_error); +#endif + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/main/task_bmp280.h Mon Mar 27 22:13:21 2023 +0200 @@ -0,0 +1,62 @@ +/** + * @file task_bmp280.h + * @brief The FreeRTOS task to query the BMP280 sensor connected to + * the I2C bus. + * The task will update the sensor state structures. + */ + +#ifndef _TASK_BMP280_H +#define _TASK_BMP280_H + +/* + * Error codes in this task + */ +#define BMP280_ERR_NONE 0 ///< No errors +//#define I2C_ERR_READ 1 ///< Generic read error + + +/** + * @brief BMP280 sensor + */ +typedef struct strBMP280 { + bool valid; ///< Valid measurement + bool fake; ///< Fake measurement + uint8_t address; ///< Device i2c address + float temperature; ///< Temperature in celsius + float humidity; + float pressure; ///< Pressure in hPa + int error; ///< Error result +} bmp280_tt; + + +/** + * @brief Structure containing the variables for the BMP280 task. + */ +typedef struct { + bmp280_tt bmp280; ///< Sensor results +} BMP280_State; + + + +/** + * @brief Request a new measurement from selected sensors. + */ +void request_bmp280(void); + + +/** + * @brief Check if results are ready + * @return true of results are ready, else false. + */ +bool ready_bmp280(void); + + +/** + * @brief The FreeRTOS task to update the BMP280 on request. + * @param pvParameters Parameters for the task. + */ +void task_bmp280(void *pvParameters); + + +#endif +
--- a/sdkconfig Sun Mar 26 22:22:45 2023 +0200 +++ b/sdkconfig Mon Mar 27 22:13:21 2023 +0200 @@ -351,14 +351,14 @@ CONFIG_ESPTOOLPY_FLASHFREQ_80M_DEFAULT=y CONFIG_ESPTOOLPY_FLASHFREQ="80m" # CONFIG_ESPTOOLPY_FLASHSIZE_1MB is not set -CONFIG_ESPTOOLPY_FLASHSIZE_2MB=y -# CONFIG_ESPTOOLPY_FLASHSIZE_4MB is not set +# CONFIG_ESPTOOLPY_FLASHSIZE_2MB is not set +CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y # CONFIG_ESPTOOLPY_FLASHSIZE_8MB is not set # CONFIG_ESPTOOLPY_FLASHSIZE_16MB is not set # CONFIG_ESPTOOLPY_FLASHSIZE_32MB is not set # CONFIG_ESPTOOLPY_FLASHSIZE_64MB is not set # CONFIG_ESPTOOLPY_FLASHSIZE_128MB is not set -CONFIG_ESPTOOLPY_FLASHSIZE="2MB" +CONFIG_ESPTOOLPY_FLASHSIZE="4MB" # CONFIG_ESPTOOLPY_HEADER_FLASHSIZE_UPDATE is not set CONFIG_ESPTOOLPY_BEFORE_RESET=y # CONFIG_ESPTOOLPY_BEFORE_NORESET is not set @@ -389,6 +389,8 @@ CONFIG_ENV_GPIO_RANGE_MAX=19 CONFIG_ENV_GPIO_IN_RANGE_MAX=19 CONFIG_ENV_GPIO_OUT_RANGE_MAX=19 +CONFIG_CODE_TESTING=y +# CONFIG_CODE_PRODUCTION is not set # # I2C bus @@ -1056,8 +1058,8 @@ # CONFIG_LOG_MAXIMUM_LEVEL_VERBOSE is not set CONFIG_LOG_MAXIMUM_LEVEL=3 CONFIG_LOG_COLORS=y -CONFIG_LOG_TIMESTAMP_SOURCE_RTOS=y -# CONFIG_LOG_TIMESTAMP_SOURCE_SYSTEM is not set +# CONFIG_LOG_TIMESTAMP_SOURCE_RTOS is not set +CONFIG_LOG_TIMESTAMP_SOURCE_SYSTEM=y # end of Log output # @@ -1555,6 +1557,81 @@ CONFIG_WIFI_PROV_STA_ALL_CHANNEL_SCAN=y # CONFIG_WIFI_PROV_STA_FAST_SCAN is not set # end of Wi-Fi Provisioning Manager + +# +# Button +# +CONFIG_BUTTON_MAX=5 +CONFIG_BUTTON_POLL_TIMEOUT=10 +CONFIG_BUTTON_LONG_PRESS_TIMEOUT=1000 +CONFIG_BUTTON_AUTOREPEAT_TIMEOUT=500 +CONFIG_BUTTON_AUTOREPEAT_INTERVAL=250 +# end of Button + +# +# DPS310 driver +# +CONFIG_DPS310_PROTOCOL_USING_I2C=y +# end of DPS310 driver + +# +# Rotary encoders +# +CONFIG_RE_MAX=1 +CONFIG_RE_INTERVAL_US=1000 +CONFIG_RE_BTN_DEAD_TIME_US=10000 +CONFIG_RE_BTN_PRESSED_LEVEL_0=y +# CONFIG_RE_BTN_PRESSED_LEVEL_1 is not set +CONFIG_RE_BTN_LONG_PRESS_TIME_US=500000 +# end of Rotary encoders + +# +# Example +# +CONFIG_EXAMPLE_USING_FOO=y +# CONFIG_EXAMPLE_USING_BAR is not set +CONFIG_EXAMPLE_NUMBER=1 +# end of Example + +# +# HMC5883L +# +CONFIG_HMC5883L_MEAS_TIMEOUT=6000 +# end of HMC5883L + +# +# I2C +# +CONFIG_I2CDEV_TIMEOUT=1000 +# CONFIG_I2CDEV_NOLOCK is not set +# end of I2C + +# +# LED strip +# +CONFIG_LED_STRIP_FLUSH_TIMEOUT=1000 +CONFIG_LED_STRIP_PAUSE_LENGTH=50 +# end of LED strip + +# +# LED strip SPI +# +CONFIG_LED_STRIP_SPI_USING_SK9822=y +CONFIG_LED_STRIP_SPI_MUTEX_TIMEOUT_MS=1 +# end of LED strip SPI + +# +# MCP23x17 +# +CONFIG_MCP23X17_IFACE_I2C=y +# CONFIG_MCP23X17_IFACE_SPI is not set +# end of MCP23x17 + +# +# OneWire +# +CONFIG_ONEWIRE_CRC8_TABLE=y +# end of OneWire # end of Component config # Deprecated options for backward compatibility