Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat ph #1

Open
wants to merge 27 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .clang-format
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Language: Cpp
BasedOnStyle: WebKit
BreakBeforeBraces: Attach
NamespaceIndentation: None
30 changes: 30 additions & 0 deletions API.md
Original file line number Diff line number Diff line change
@@ -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.
30 changes: 30 additions & 0 deletions API_ru.md
Original file line number Diff line number Diff line change
@@ -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.
102 changes: 102 additions & 0 deletions examples/calibrate/calibrate.ino
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
/*

Берем плату Arduino или совместимую, подключаем по USB, прошиваем этим скетчем и
запускаем скетч. Выполняем указания в Serial Monitor.

Важно: надо ввести в Serial Monitor значение в милливольтах - 4 цифры, незначащие
правые дополнить нулями.

*/

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suggest using only one language in comments.

/*
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);
}
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why did you use microcontroller registers? After all, the calibration example won't work with the Arduino Due and ESP8226 boards?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the standard Arduino API, there is no way to read the voltage at the internal Vref. There is only a way to use it instead of Aref. Now, as you can see, there is a way only for different AVRs. The options for other controllers can be worked on in the next version.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I propose to write from which boards support the calibration process. And there are no other options easier than using Vref?

32 changes: 32 additions & 0 deletions examples/simpleReadPH/simpleReadPH.ino
Original file line number Diff line number Diff line change
@@ -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

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
TroykaPH phMeter(A4); // set used analog pin
TroykaPH phMeter(A0); // set used analog pin

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suggest using pin A0, it is used everywhere by default.


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: ");

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Serial.print("\nCurrent pH: ");
Serial.print("Current pH: ");

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For Arduino users, it is better to do a newline at the end of the output:
Serial.println()

Serial.print(phMeter.read(), 1);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Serial.print(phMeter.read(), 1);
Serial.print(phMeter.read());
Serial.println();

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two decimal places is normal for solutions of PH.

}
}
21 changes: 21 additions & 0 deletions keywords.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#######################################
# Syntax Coloring Map TroykaPH
#######################################

#######################################
# Datatypes (KEYWORD1)
#######################################

TroykaPH KEYWORD1

#######################################
# Methods and Functions (KEYWORD2)
#######################################

begin KEYWORD2
update KEYWORD2
read KEYWORD2

#######################################
# Constants (LITERAL1)
#######################################
9 changes: 9 additions & 0 deletions library.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
name=TroykaPH
version=1.0.0
author=Yury Botov <[email protected]>
maintainer=Amperka <amperka.ru>
sentence=Library for TroykaPH sensor.
paragraph=Allows calibrate and read sensor value
category=Sensor
url=https://github.com/amperka/TroykaPH
architectures=*
89 changes: 89 additions & 0 deletions src/TroykaPH.cpp
Original file line number Diff line number Diff line change
@@ -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, [email protected])
*
* Author: Yury Botov <[email protected]>
* 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;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suggest putting all the constants into a file TroykaPH.h


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;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
value = value / 10;
value =/ 10;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reduce the expression?

// 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;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similarly, I suggest putting all the constants into a file TroykaPH.h

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;
}
34 changes: 34 additions & 0 deletions src/TroykaPH.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* This file is a part of TroykaPH library.
*
* Product page: https://amperka.ru/product/zelo-folow-line-sensor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* Product page: https://amperka.ru/product/zelo-folow-line-sensor
* Product page: https://amperka.ru/product/troyka-ph-sensor

* © Amperka LLC (https://amperka.com, [email protected])
*
* Author: Yury Botov <[email protected]>
* 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);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is there an empty line between functions?

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__