diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..7f24b5f --- /dev/null +++ b/.clang-format @@ -0,0 +1,4 @@ +Language: Cpp +BasedOnStyle: WebKit +BreakBeforeBraces: Attach +NamespaceIndentation: None diff --git a/API.md b/API.md new file mode 100644 index 0000000..afc26f0 --- /dev/null +++ b/API.md @@ -0,0 +1,30 @@ +# TroykaPH API + +## `class TroykaPH` + +Create an object of type `TroykaPH` to control the [module for measuring pH of liquids TroykaPH] (https://amperka.ru/product/troyka-ph-sensor). + +### `TroykaPH(uint8_t pin)` + +Creates a new TroykaPH object. + +- `pin`: analog pin to which the module is connected. + +### `void begin(float correction = 1.0, float zeroShift = 2.0)` + +- `correction` - AVR's internal voltage reference offset correction. +- `zeroShift` - correction of the real shift `0` during measurements. + +Initializes the library. Call this method before you begin interacting with TroykaPH. For example, in the `setup()`. + +- `correctionMultiplier`: correction factor obtained as a result of the calibration procedure. If absent, it is taken equal to `1`. + +### `void update(long periodMilliseconds = 1000)` + +Performs periodic reading of sensor and correction factors. Place his call in `loop()`. + +- `periodMilliseconds`: sensor polling period. + +### `float read() const;` + +Returns the last measured pH value. diff --git a/API_ru.md b/API_ru.md new file mode 100644 index 0000000..651ac33 --- /dev/null +++ b/API_ru.md @@ -0,0 +1,30 @@ +# TroykaPH API (RUS) + +## `class TroykaPH` + +Создайте объект типа `TroykaPH` для управления [модулем измерения pH жидкостей TroykaPH](https://amperka.ru/product/troyka-ph-sensor). + +### `TroykaPH(uint8_t pin)` + +Создает новый объект TroykaPH. + +- `pin`: аналоговый пин к которому подключен модуль. + +### `void begin(float correction = 1.0, float zeroShift = 2.0)` + +- `correction` - коррекция с учетом сдвига напряжения внутреннего источника опорного напряжения AVR. +- `zeroShift` - коррекция с учетом реального сдвига `0` при измерениях. + +Инициализирует библиотеку. Вызовите этот метод до начала взаимодействия с TroykaPH. Например в функции `setup()`. + +- `correctionMultiplier`: корректировочный множитель полученный в результате процедуры калибровки. При отсутствии принимается равным `1`. + +### `void update(long periodMilliseconds = 1000)` + +Производит периодическое чтение показаний датчика и корректировочных коэфициентов. Разместите его вызов в `loop()`. + +- `periodMilliseconds`: период опроса датчика. + +### `float read() const;` + +Возвращает последнее замеренное значение pH. diff --git a/examples/calibrate/calibrate.ino b/examples/calibrate/calibrate.ino new file mode 100644 index 0000000..47ef253 --- /dev/null +++ b/examples/calibrate/calibrate.ino @@ -0,0 +1,102 @@ +/* + + Берем плату Arduino или совместимую, подключаем по USB, прошиваем этим скетчем и + запускаем скетч. Выполняем указания в Serial Monitor. + + Важно: надо ввести в Serial Monitor значение в милливольтах - 4 цифры, незначащие + правые дополнить нулями. + +*/ + +/* + This example don't use library. It demonstrates TroykaPH calibration procedure +*/ + +float measureVop() { +#if defined(__AVR_ATmega32U4__) || defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__) + ADMUX = _BV(REFS0) | _BV(MUX4) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1); +#elif defined(__AVR_ATtiny24__) || defined(__AVR_ATtiny44__) || defined(__AVR_ATtiny84__) + ADMUX = _BV(MUX5) | _BV(MUX0); +#elif defined(__AVR_ATtiny25__) || defined(__AVR_ATtiny45__) || defined(__AVR_ATtiny85__) + ADMUX = _BV(MUX3) | _BV(MUX2); +#else + ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1); +#endif + delay(75); + float Vop = 0; + for (uint8_t i = 0; i < 100; i++) { + ADCSRA |= _BV(ADSC); + while (bit_is_set(ADCSRA, ADSC)) + ; + uint8_t low = ADCL; + uint8_t high = ADCH; + + Vop += (float)((high << 8) | low) * 5. / 1024.; + delay(10); + } + return Vop / 100.; +} + +float measureZeroLevel() { + (void)analogRead(A4); + float Vzero = 0; + for (uint8_t i = 0; i < 100; i++) { + Vzero += (float)analogRead(A4) * 5. / 1024.; + delay(10); + } + return Vzero / 100.; +} + +float factor; + +void setup() { + Serial.begin(9600); + while (!Serial) + ; + + Serial.print("\n\n\nTroyka pH module zero-shift calibration procedure\n\n"); + Serial.print("- Connect TroykaPH module to Arduino board.\n"); + Serial.print("- Shortcut TroykaPH module BNC input to ground.\n"); + Serial.print("- Measure (using good multimeter) Arduino board feed voltage between 5V and GND pins\n"); + Serial.print("- Input this value in millivolts to Serial Monitor input line, press 'Send' after it.\n"); + Serial.print("For example: if measured value is '4.93' volts - punch '4930'\n\n"); +} + +void loop() { + if (Serial.available() > 0) { + float Vmeasured = (float)Serial.parseInt() / 1000; + if (Vmeasured == 0) + return; + Serial.print("Voltage measured by multimeter (V) = "); + Serial.println(Vmeasured, 3); + float Vop = measureVop(); + Serial.print("Voltage of internal reference (V) = "); + Serial.println(Vop, 3); + float VccCalculated = Vop * 5. / 1.1; + Serial.print("Calculated Vcc (V) = "); + Serial.println(VccCalculated, 3); + + factor = (float)(VccCalculated) / (float)(Vmeasured); + + float VccReal = 5. / factor; + Serial.print("Real Vcc (V) = "); + Serial.println(VccReal, 3); + + float Vzero = measureZeroLevel(); + Serial.print("Calculated zero-shift voltage (V) = "); + Serial.println(Vzero, 3); + float VzeroReal = Vzero / factor; + Serial.print("Real zero-shift voltage (V) = "); + Serial.println(VzeroReal, 3); + + Serial.print("Calibration complete."); + Serial.print("\n\nCall in your 'setup()' method {your-pH-sensor-name}.begin("); + Serial.print(factor, 3); + Serial.print(", "); + Serial.print(VzeroReal, 3); + Serial.print("); \n\n"); + + Serial.println("Recalibration is needed if you change Arduino board to another.\n\n"); + delay(5000); + } +} diff --git a/examples/simpleReadPH/simpleReadPH.ino b/examples/simpleReadPH/simpleReadPH.ino new file mode 100644 index 0000000..9af5323 --- /dev/null +++ b/examples/simpleReadPH/simpleReadPH.ino @@ -0,0 +1,32 @@ +/* + This example demonstrate pH value reading +*/ +#include "Arduino.h" + +// Include library +#include "TroykaPH.h" + +TroykaPH phMeter(A4); // set used analog pin + +uint32_t lastShowingTime; +constexpr uint32_t INTERVAL = 3000; + +void setup() { + phMeter.begin(); // if module not calibrated or... + // phMeter.begin(correction,zeroShift); // if you have it (use calibrate.ino) for it + + Serial.begin(9600); + lastShowingTime = millis(); // show result once per 3 seconds +} + +void loop() { + uint32_t currentTime = millis(); + phMeter.update(1000); // real read from sensor once per second + // (you can increase this period, in practice pH value changing too slowly) + + if (currentTime - lastShowingTime > INTERVAL) { + lastShowingTime = currentTime; + Serial.print("\nCurrent pH: "); + Serial.print(phMeter.read(), 1); + } +} diff --git a/keywords.txt b/keywords.txt new file mode 100644 index 0000000..85379d7 --- /dev/null +++ b/keywords.txt @@ -0,0 +1,21 @@ +####################################### +# Syntax Coloring Map TroykaPH +####################################### + +####################################### +# Datatypes (KEYWORD1) +####################################### + +TroykaPH KEYWORD1 + +####################################### +# Methods and Functions (KEYWORD2) +####################################### + +begin KEYWORD2 +update KEYWORD2 +read KEYWORD2 + +####################################### +# Constants (LITERAL1) +####################################### diff --git a/library.properties b/library.properties new file mode 100644 index 0000000..7ff1c48 --- /dev/null +++ b/library.properties @@ -0,0 +1,9 @@ +name=TroykaPH +version=1.0.0 +author=Yury Botov +maintainer=Amperka +sentence=Library for TroykaPH sensor. +paragraph=Allows calibrate and read sensor value +category=Sensor +url=https://github.com/amperka/TroykaPH +architectures=* diff --git a/src/TroykaPH.cpp b/src/TroykaPH.cpp new file mode 100644 index 0000000..8d8f760 --- /dev/null +++ b/src/TroykaPH.cpp @@ -0,0 +1,89 @@ +/* + * This file is a part of TroykaPH library. + * + * Product page: https://amperka.ru/product/troyka-ph-sensor + * © Amperka LLC (https://amperka.com, dev@amperka.com) + * + * Author: Yury Botov + * License: GPLv3, all text here must be included in any redistribution. + */ + +#include "TroykaPH.h" + +TroykaPH::TroykaPH(uint8_t pin) { + _pin = pin; + _lastValue = 7; + _correction = 1.0; + _nextMeasureTime = 0; +} + +void TroykaPH::begin(float correction, float zeroLevel) { + _correction = correction; + _zeroLevel = zeroLevel; +} + +static float fmap(float value, float minVal, float maxVal, float minOut, float maxOut) { + return (value - minVal) / (maxVal - minVal) * (maxOut - minOut) + minOut; +} + +void TroykaPH::update(long periodMilliseconds) { + constexpr float idealVcc = 5.0; + constexpr float minPh = 0.0; + constexpr float maxPh = 14.0; + constexpr float phHalfRangeInVolts = 0.82; + + float value = 0; + if (millis() - _nextMeasureTime > periodMilliseconds) { + _nextMeasureTime += periodMilliseconds; + // read value + (void)analogRead(_pin); + delay(75); + for (uint8_t i = 0; i < 10; i++) { + value += (float)analogRead(_pin) * 5.0 / 1024.; + } + value = value / 10; + // read real Vcc value + float realVcc = (float)(_readVcc()) / 1000.; + + float result = value * idealVcc / realVcc; + result /= _correction; // internal reference source correction + + _lastValue = fmap(result, _zeroLevel - phHalfRangeInVolts, _zeroLevel + phHalfRangeInVolts, minPh, maxPh); + } +} + +float TroykaPH::read() const { return _lastValue; } + +long TroykaPH::_readVcc() { + constexpr float adcResolution = 1024.; + constexpr float meanReferenceVoltage = 1.1; + constexpr long toMillivolts = 1000L; + + long result = 0; +#if defined(__AVR_ATmega32U4__) || defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__) + ADMUX = _BV(REFS0) | _BV(MUX4) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1); +#elif defined(__AVR_ATtiny24__) || defined(__AVR_ATtiny44__) || defined(__AVR_ATtiny84__) + ADMUX = _BV(MUX5) | _BV(MUX0); +#elif defined(__AVR_ATtiny25__) || defined(__AVR_ATtiny45__) || defined(__AVR_ATtiny85__) + ADMUX = _BV(MUX3) | _BV(MUX2); +#else + ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1); +#endif + + delay(75); + // internal reference reading + for (uint8_t i = 0; i < 10; i++) { + ADCSRA |= _BV(ADSC); + while (bit_is_set(ADCSRA, ADSC)) + ; + + uint8_t low = ADCL; + uint8_t high = ADCH; + + result += (high << 8) | low; + } + result /= 10; + + result = (long)(adcResolution * meanReferenceVoltage) * toMillivolts / result; + return result; +} diff --git a/src/TroykaPH.h b/src/TroykaPH.h new file mode 100644 index 0000000..7ee542e --- /dev/null +++ b/src/TroykaPH.h @@ -0,0 +1,34 @@ +/* + * This file is a part of TroykaPH library. + * + * Product page: https://amperka.ru/product/zelo-folow-line-sensor + * © Amperka LLC (https://amperka.com, dev@amperka.com) + * + * Author: Yury Botov + * License: GPLv3, all text here must be included in any redistribution. + */ + +#ifndef __TROYKA_PH_H__ +#define __TROYKA_PH_H__ + +#include "Arduino.h" + +class TroykaPH { +public: + TroykaPH(uint8_t pin); + void begin(float correction = 1.0, float zeroLevel = 2.0); + + void update(long periodMilliseconds = 1000); + + float read() const; + +private: + uint8_t _pin; + float _lastValue; + float _correction; + float _zeroLevel; + uint32_t _nextMeasureTime; + long _readVcc(); +}; + +#endif //__TROYKA_PH_H__