commit 4a75f0e565fbc130e06b5799a27851798ef934cc Author: Martin Hatina Date: Fri Sep 18 14:43:04 2020 +0200 init commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3b8da3a --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.pio +.vscode \ No newline at end of file diff --git a/include/README b/include/README new file mode 100644 index 0000000..194dcd4 --- /dev/null +++ b/include/README @@ -0,0 +1,39 @@ + +This directory is intended for project header files. + +A header file is a file containing C declarations and macro definitions +to be shared between several project source files. You request the use of a +header file in your project source file (C, C++, etc) located in `src` folder +by including it, with the C preprocessing directive `#include'. + +```src/main.c + +#include "header.h" + +int main (void) +{ + ... +} +``` + +Including a header file produces the same results as copying the header file +into each source file that needs it. Such copying would be time-consuming +and error-prone. With a header file, the related declarations appear +in only one place. If they need to be changed, they can be changed in one +place, and programs that include the header file will automatically use the +new version when next recompiled. The header file eliminates the labor of +finding and changing all the copies as well as the risk that a failure to +find one copy will result in inconsistencies within a program. + +In C, the usual convention is to give header files names that end with `.h'. +It is most portable to use only letters, digits, dashes, and underscores in +header file names, and at most one dot. + +Read more about using header files in official GCC documentation: + +* Include Syntax +* Include Operation +* Once-Only Headers +* Computed Includes + +https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html diff --git a/lib/README b/lib/README new file mode 100644 index 0000000..6debab1 --- /dev/null +++ b/lib/README @@ -0,0 +1,46 @@ + +This directory is intended for project specific (private) libraries. +PlatformIO will compile them to static libraries and link into executable file. + +The source code of each library should be placed in a an own separate directory +("lib/your_library_name/[here are source files]"). + +For example, see a structure of the following two libraries `Foo` and `Bar`: + +|--lib +| | +| |--Bar +| | |--docs +| | |--examples +| | |--src +| | |- Bar.c +| | |- Bar.h +| | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html +| | +| |--Foo +| | |- Foo.c +| | |- Foo.h +| | +| |- README --> THIS FILE +| +|- platformio.ini +|--src + |- main.c + +and a contents of `src/main.c`: +``` +#include +#include + +int main (void) +{ + ... +} + +``` + +PlatformIO Library Dependency Finder will find automatically dependent +libraries scanning project source files. + +More information about PlatformIO Library Dependency Finder +- https://docs.platformio.org/page/librarymanager/ldf.html diff --git a/platformio.ini b/platformio.ini new file mode 100644 index 0000000..98f93a6 --- /dev/null +++ b/platformio.ini @@ -0,0 +1,18 @@ +; PlatformIO Project Configuration File +; +; Build options: build flags, source filter +; Upload options: custom upload port, speed and extra flags +; Library options: dependencies, extra library storages +; Advanced options: extra scripting +; +; Please visit documentation for the other options and examples +; https://docs.platformio.org/page/projectconf.html + +[env:nanoatmega328new] +platform = atmelavr +board = nanoatmega328 +framework = arduino +lib_deps = + bogde/HX711@^0.7.4 + marcoschwartz/LiquidCrystal_I2C@^1.1.4 + thijse/EEPROMEx@0.0.0-alpha+sha.09d7586108 diff --git a/src/buttons.cpp b/src/buttons.cpp new file mode 100644 index 0000000..432842f --- /dev/null +++ b/src/buttons.cpp @@ -0,0 +1,60 @@ +#include "buttons.h" +#include "lcd.h" +#include "scale.h" + +void Buttons::init() { + pinMode(tare_button, INPUT); + pinMode(controller_button, INPUT); + pinMode(plus_button, INPUT); + pinMode(minus_button, INPUT); +} + +bool Buttons::is_any_pressed() { + return digitalRead(tare_button) || digitalRead(controller_button); +} + +void Buttons::on_button_pressed(Scale& scale, WeightControllerPool& pool, const LCD& lcd) { + Action buttonAction = get_button_action(); + switch (buttonAction) { + case Action::TAR: + scale.tare(); + break; + case Action::CALIBRATE: + scale.calibrate(); + break; + case Action::NEXT_CONTROLLER: + lcd.clear(); + pool.next(); + break; + default: + break; + } +} + +Buttons::Action Buttons::get_button_action() { + int button_pressed = 0; + for (int i = 0; i < buttons_count; i++) { + button_pressed |= digitalRead(buttons[i]) < i; + } + + if (last_pressed == button_pressed) { + + } + + Buttons::Action action; + if (button_pressed & 0x0001) { + action = Action::TAR; + } + else if (tare_pressed && !controller_pressed) { + action = Action::NEXT_CONTROLLER; + } + else { + action = Action::NONE; + } + + if (action == Action::TAR && (now - then) > 2000) { + action = Action::CALIBRATE; + } + + return action; +} \ No newline at end of file diff --git a/src/buttons.h b/src/buttons.h new file mode 100644 index 0000000..3d33bde --- /dev/null +++ b/src/buttons.h @@ -0,0 +1,30 @@ +#ifndef BUTTONS_H +#define BUTTONS_H + +#include +#include "lcd.h" +#include "scale.h" +#include "weight_controller.h" + +#define tare_button A0 +#define controller_button A1 +#define plus_button A2 +#define minus_button A3 + +class Buttons { +private: + enum class Action { + NONE, + TAR, + CALIBRATE, + NEXT_CONTROLLER + }; + +public: + void init(); + bool is_any_pressed(); + void on_button_pressed(Scale &scale, WeightControllerPool &pool, const LCD &lcd); + Buttons::Action get_button_action(); +}; + +#endif \ No newline at end of file diff --git a/src/lcd.cpp b/src/lcd.cpp new file mode 100644 index 0000000..387d2d3 --- /dev/null +++ b/src/lcd.cpp @@ -0,0 +1,89 @@ +#include "lcd.h" + +LCD::LCD() {} + +LCD::~LCD() { + delete lcd; +} + +void LCD::init() { + lcd = new LiquidCrystal_I2C(0x27, columns, rows); + lcd->init(); + lcd->backlight(); + lcd->setBacklight(HIGH); +} + +void LCD::print_init_message(int calibration_weight) const { + lcd->setCursor(0, 0); + lcd->print(F("YCM Scale")); + lcd->setCursor(0, 1); + lcd->print(F("Scaled to: ")); + lcd->print(calibration_weight); + lcd->print(F("g")); + delay(1500); +} + +void LCD::print_calibration(int calib_weight) const { + lcd->setCursor(0, 1); + lcd->print("Calibration: "); + lcd->print(calib_weight); + lcd->print(" g"); + delay(800); + clear_row(1); +} + +void LCD::print_weight(double weight, int precision, const char* unit) const { + // + // MAKE SURE NEGATIVE AND POSITIVE VALUES DON'T JUMP + // + if (weight > 0) { + lcd->print(" "); + } + + // + // PRINT + // + lcd->print(weight, precision); + lcd->print(" "); + lcd->print(unit); +} + +void LCD::print_weights(double grams, double grains) const { + lcd->setCursor(0, 0); + print_weight(grams, 3, "g "); + lcd->setCursor(0, 1); + print_weight(grains, 2, "grn "); +} + +void LCD::print_control_result(String name, double val_min, double val_max, int result) { + lcd->setCursor(0, 0); + lcd->print(name); + lcd->print(" "); + lcd->print(val_min, 2); + lcd->print("-"); + lcd->print(val_max, 2); + + lcd->setCursor(0, 1); + + if (result == -1) { + lcd->print("TOO LITTLE"); + } + else if (result == 1) { + lcd->print("TOO MUCH "); + } + else { + lcd->print("PASSED "); + } +} + +void LCD::clear_row(int row) const { + lcd->setCursor(0, row); + for (int i = 0; i < columns; i++) { + lcd->print(" "); + } + lcd->setCursor(0, row); +} + +void LCD::clear() const { + lcd->clear(); +} \ No newline at end of file diff --git a/src/lcd.h b/src/lcd.h new file mode 100644 index 0000000..d0e8ac9 --- /dev/null +++ b/src/lcd.h @@ -0,0 +1,27 @@ +#ifndef LCD_H +#define LCD_H + +#include +#include + +class LCD { +private: + LiquidCrystal_I2C* lcd = nullptr; // set the LCD address to 0x27 for a 16 chars and 2 line display + const int columns = 18; + const int rows = 2; + +public: + LCD(); + ~LCD(); + + void init(); + void print_init_message(int calibration_weight) const; + void print_calibration(int calib_weight) const; + void print_weight(double weight, int precision, const char* unit) const; + void print_weights(double grams, double grains) const; + void print_control_result(String name, double val_min, double val_max, int result); + void clear_row(int row) const; + void clear() const; +}; + +#endif \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..d402e6c --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,92 @@ +#include +#include + +#include "lcd.h" +#include "serial.h" +#include "scale.h" +#include "buttons.h" +#include "weight_controller.h" + +#define shift_element 3 + +// ===================== +// ======= SETUP ======= +// ===================== +LCD lcd; +SerialIO serial; +Scale scale; +Buttons buttons; +WeightControllerPool pool; + +void setup() +{ + // + // INIT + // + lcd.init(); + serial.init(); + scale.init(); + buttons.init(); + pool.init(); + + // + // DISPLAY INIT MESSAGES + // + serial.print_init_message(); + lcd.print_init_message(scale.get_calibration_weight()); + lcd.clear(); + // + // DISPLAY INIT MESSAGES + // +} + +void loop() +{ + // + // ON BUTTON PRESSED ACTION + // + if (buttons.is_any_pressed()) + { + buttons.on_button_pressed(scale, pool, lcd); + } + + // + // READ WEIGHT + // + scale.read(); + + if (pool.is_controller_displayed()) { + // + // SHOW CONTROL RESULT + // + WeightController &controller = pool.get(); + int result = controller.compare(scale.get_average_grain()); + lcd.print_control_result(controller.get_name(), controller.get_val_min(), controller.get_val_max(), result); + // + // SHOW CONTROL RESULT + // + } else { + // + // PRINT WEIGHT + // + lcd.print_weights(scale.get_average_gram(), scale.get_average_grain()); + // + // PRINT WEIGHT + // + } + + // + // PROCESS REQUESTS FROM SERIAL + // + if (serial.is_available()) + { + serial.on_command(scale, pool, lcd); + if (serial.is_debug_info_allowed()) + { + serial.print_debug_info(scale.get_raw(), scale.get_average_raw(), scale.get_offset(), scale.get_weight(), scale.get_actual_gram(), scale.get_average_gram(), scale.get_average_grain()); + } + } + // + // PROCESS REQUESTS FROM SERIAL + // +} \ No newline at end of file diff --git a/src/scale.cpp b/src/scale.cpp new file mode 100644 index 0000000..452a2d9 --- /dev/null +++ b/src/scale.cpp @@ -0,0 +1,43 @@ +#include "scale.h" + +void Scale::init() { + scaled_weight = EEPROM.readDouble(e_scale); + int gain = EEPROM.readInt(e_gain); + + hx711.begin(out, clck, gain); + offset = init_reading(); +} + +long Scale::init_reading() { + hx711.read_average(20); + return hx711.read_average(NUMBER_OF_READINGS); +} + +void Scale::read() { + raw = hx711.read(); + actual_gram = (raw - offset) / scaled_weight; + + if (abs(actual_gram - average_gram) > EPSILON) { + sum = 0; + count = 0; + sum_raw = 0; + average_raw = raw; + average_gram = actual_gram; + } + else { + sum += actual_gram; + sum_raw += raw; + count++; + average_gram = sum / count; + average_raw = sum_raw / count; + } +} + +void Scale::tare() { + offset = average_raw; +} + +void Scale::calibrate() { + scaled_weight = (raw - offset) / get_calibration_weight(); + EEPROM.updateDouble(e_scale, scaled_weight); +} \ No newline at end of file diff --git a/src/scale.h b/src/scale.h new file mode 100644 index 0000000..1b194ac --- /dev/null +++ b/src/scale.h @@ -0,0 +1,80 @@ +#ifndef SCALE_H +#define SCALE_H + +#include +#include + +#define e_scale 0 +#define e_calib_w 8 +#define e_gain 10 + +#define none 0 +#define tar 1 +#define cal 2 +#define shift_element 3 + +#define delay_vypis 800 + +#define NUMBER_OF_READINGS 30 + +class Scale { +private: + HX711 hx711; + + double sum = 0; + long sum_raw = 0; + int count = 0; + double average_gram = 0; + long average_raw = 0; + double EPSILON = 0.01; + + long raw; + + long offset = 0; + double scaled_weight = 15940.0; // aprox. 15 g + double actual_gram; + + const int out = 2; + const int clck = 3; + +public: + void init(); + long init_reading(); + void read(); + void tare(); + void calibrate(); + + long get_raw() const { + return raw; + } + + long get_average_raw() const { + return average_raw; + } + + long get_offset() const { + return offset; + } + + int get_calibration_weight() const { + return EEPROM.readInt(e_calib_w); + } + + double get_weight() const { + return scaled_weight; + } + + double get_actual_gram() const { + return actual_gram; + } + + double get_average_grain() const { + return average_gram / 0.06479891; + } + + double get_average_gram() const { + return average_gram; + } +}; + +#endif \ No newline at end of file diff --git a/src/serial.cpp b/src/serial.cpp new file mode 100644 index 0000000..64cc03b --- /dev/null +++ b/src/serial.cpp @@ -0,0 +1,119 @@ +#include "serial.h" +#include "lcd.h" + +void SerialIO::init() { + Serial.begin(9600); +} + +bool SerialIO::is_available() const { + return Serial.available(); +} + +void SerialIO::print_gain(int gain) const { + Serial.print(F("zosilnenie = ")); + Serial.print(gain, DEC); + Serial.println(); +} + +bool SerialIO::is_debug_info_allowed() const { + return this->debug_info_allowed; +} + +void SerialIO::print_init_message() const { + Serial.println(F(" milivahy")); + Serial.println(F(" ")); + Serial.println(F(" v - vypis hodnot zap-vyp")); + Serial.println(F(" r - presnost zobrazenia [r 3 2]")); + Serial.println(F(" t - tare - nulovanie")); + Serial.println(F(" c - kalibracia")); + Serial.println(F(" g - zosilnenie 64, 128 [g 128]")); + Serial.println(F(" w - kalibracne zavazie v g [ w 1]")); + Serial.println(F(" p - nastavenie porovnania [ p 1 IT_1 2.0 3.0]")); + Serial.println(); +} + +void SerialIO::on_command(Scale& scale, WeightControllerPool& pool, LCD& lcd) +{ + char var = Serial.read(); + switch (var) + { + case 'c': + scale.calibrate(); + lcd.print_calibration(scale.get_calibration_weight()); + Serial.println(); + Serial.print(F("Scale = ")); + Serial.println(scale.get_weight()); + break; + case 't': + scale.tare(); + break; + case 'v': + debug_info_allowed = !debug_info_allowed; + break; + case 'w': + int calib_weight = Serial.parseInt(); + Serial.print(F("calibration weight = ")); + Serial.print(calib_weight, DEC); + Serial.println(F(" g")); + EEPROM.writeInt(e_calib_w, calib_weight); + break; + case 'r': + des_m_g = Serial.parseInt(); + des_m_grain = Serial.parseInt(); + break; + case 'p': { + int index = Serial.parseInt(); + String name = Serial.readStringUntil(' '); + double val_min = Serial.parseFloat(); + double val_max = Serial.parseFloat(); + + for (int j = 0; j < pool.get_max(); j++) + { + WeightController& controller = pool.get(j); + Serial.println(); + Serial.print(controller.get_name()); + Serial.print(F(" ")); + Serial.print(controller.get_val_min(), 2); + Serial.print(F(" ")); + Serial.print(controller.get_val_max(), 2); + } + break; + } case 'g': + int gain = Serial.parseInt(); + if (gain == 64 || gain == 128) + { + EEPROM.writeInt(e_gain, gain); + } + print_gain(gain); + break; + case 'h': + print_init_message(); + break; + } +} + +void SerialIO::print_debug_info(long raw, double average_raw, long offset, double weight, double gram, double average_gram, double grain) const { + Serial.print(F("RAW = ")); + Serial.print(raw); + + Serial.print(F(", AV_RAW = ")); + Serial.print(average_raw); + + Serial.print(F(", offset = ")); + Serial.print(offset); + + Serial.print(F(", RAW-offset = ")); + Serial.print(raw - offset); + + Serial.print(F(", Scale = ")); + Serial.print(weight); + + Serial.print(F(", gram = ")); + Serial.print(gram, des_m_g); + + Serial.print(F(", average = ")); + Serial.print(average_gram, des_m_g); + + Serial.print(F(", grain = ")); + Serial.println(grain, des_m_grain); +} \ No newline at end of file diff --git a/src/serial.h b/src/serial.h new file mode 100644 index 0000000..3392bcb --- /dev/null +++ b/src/serial.h @@ -0,0 +1,29 @@ +#ifndef SERIAL_H +#define SERIAL_H + +#include +#include "weight_controller.h" +#include "scale.h" +#include "lcd.h" + +#define e_calib_w 8 +#define e_gain 10 + +class SerialIO { +private: + int des_m_g = 5; + int des_m_grain = 3; + + bool debug_info_allowed = false; + +public: + void init(); + bool is_available() const; + void print_gain(int gain) const; + bool is_debug_info_allowed() const; + void print_init_message() const; + void on_command(Scale& scale, WeightControllerPool &pool, LCD &lcd); + void print_debug_info(long raw, double average_raw, long offset, double weight, double gram, double average_gram, double grain) const; +}; + +#endif \ No newline at end of file diff --git a/src/weight_controller.cpp b/src/weight_controller.cpp new file mode 100644 index 0000000..4212694 --- /dev/null +++ b/src/weight_controller.cpp @@ -0,0 +1,45 @@ +#include "weight_controller.h" + +void WeightController::init(String name, double min, double max) { + this->name = name; + this->val_min = min; + this->val_max = max; +} + +int WeightController::compare(double weight) const { + if (val_min > weight) { + return -1; + } + else if (val_max < weight) { + return 1; + } + return 0; +} + + +void WeightControllerPool::next() { + if (current == 0) { + controller_displayed = true; + } + + current++; + if (current - 1 >= max) { + controller_displayed = false; + current = 0; + } +} + +WeightController& WeightControllerPool::get() { + return items[current - 1]; +} + +WeightController& WeightControllerPool::get(int index) { + if (index < 0) { + return items[0]; + } + else if (index > max) { + return items[max]; + } + + return items[index]; +} \ No newline at end of file diff --git a/src/weight_controller.h b/src/weight_controller.h new file mode 100644 index 0000000..ef23afe --- /dev/null +++ b/src/weight_controller.h @@ -0,0 +1,77 @@ +#ifndef WEIGHT_CONTROLLER_H +#define WEIGHT_CONTROLLER_H + +#include + +class WeightController { +private: + String name; + double val_min; + double val_max; + +public: + void init(String name, double min, double max); + int compare(double weight) const; + + String get_name() const { + return name; + } + + double get_val_min() const { + return val_min; + } + + double get_val_max() const { + return val_max; + } + + void set_name(String name) { + this->name = name; + } + + void set_min(double min) { + this->val_min = min; + } + + void set_max(double max) { + this->val_max = max; + } +}; + +class WeightControllerPool { +private: + int max = 4; + WeightController items[4]; + + int current = 0; + bool controller_displayed = false; + +public: + void init() { + items[0].init("9mm", 190, 200); + items[1].init(".223", 0, 1); + items[2].init("6.5", 200, 300); + items[3].init(".308", 2, 3); + } + + void set_item(int index, String name, double min, double max) { + WeightController &controller = items[index]; + controller.set_name(name); + controller.set_min(min); + controller.set_max(max); + } + + bool is_controller_displayed() const { + return controller_displayed; + } + + int get_max() const{ + return max; + } + + void next(); + WeightController &get(); + WeightController &get(int index); +}; + +#endif \ No newline at end of file diff --git a/test/README b/test/README new file mode 100644 index 0000000..b94d089 --- /dev/null +++ b/test/README @@ -0,0 +1,11 @@ + +This directory is intended for PlatformIO Unit Testing and project tests. + +Unit Testing is a software testing method by which individual units of +source code, sets of one or more MCU program modules together with associated +control data, usage procedures, and operating procedures, are tested to +determine whether they are fit for use. Unit testing finds problems early +in the development cycle. + +More information about PlatformIO Unit Testing: +- https://docs.platformio.org/page/plus/unit-testing.html