├── .gitignore ├── .travis.yml ├── .vscode ├── extensions.json └── settings.json ├── LICENSE ├── README.md ├── include ├── README └── config.h ├── lib ├── INAbufer │ ├── INAbufer.cpp │ ├── INAbufer.h │ └── library.json ├── RTCutil │ ├── RTCutil.cpp │ ├── RTCutil.h │ └── library.json ├── TERMutil │ ├── TERMu8x8.h │ ├── TERMutil.cpp │ ├── TERMutil.h │ └── library.json └── readme.txt ├── platformio.ini ├── src └── main.cpp └── test └── test_main.cpp /.gitignore: -------------------------------------------------------------------------------- 1 | .pio 2 | .clang_complete 3 | .gcc-flags.json 4 | 5 | test/output_export.cpp 6 | 7 | .vscode/ 8 | !.vscode/settings.json 9 | !.vscode/extensions.json -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # Continuous Integration (CI) is the practice, in software 2 | # engineering, of merging all developer working copies with a shared mainline 3 | # several times a day < http://docs.platformio.org/page/ci/index.html > 4 | # 5 | # Documentation: 6 | # 7 | # * Travis CI Embedded Builds with PlatformIO 8 | # < https://docs.travis-ci.com/user/integration/platformio/ > 9 | # 10 | # * PlatformIO integration with Travis CI 11 | # < http://docs.platformio.org/page/ci/travis.html > 12 | # 13 | # * User Guide for `platformio ci` command 14 | # < http://docs.platformio.org/page/userguide/cmd_ci.html > 15 | # 16 | # 17 | # Please choice one of the following templates (proposed below) and uncomment 18 | # it (remove "# " before each line) or use own configuration according to the 19 | # Travis CI documentation (see above). 20 | # 21 | 22 | 23 | # 24 | # Template #1: General project. Test it using existing `platformio.ini`. 25 | # 26 | 27 | # language: python 28 | # python: 29 | # - "2.7" 30 | # 31 | # sudo: false 32 | # cache: 33 | # directories: 34 | # - "~/.platformio" 35 | # 36 | # install: 37 | # - pip install -U platformio 38 | # - platformio update 39 | # 40 | # script: 41 | # - platformio run 42 | 43 | 44 | # 45 | # Template #2: The project is intended to by used as a library with examples 46 | # 47 | 48 | # language: python 49 | # python: 50 | # - "2.7" 51 | # 52 | # sudo: false 53 | # cache: 54 | # directories: 55 | # - "~/.platformio" 56 | # 57 | # env: 58 | # - PLATFORMIO_CI_SRC=path/to/test/file.c 59 | # - PLATFORMIO_CI_SRC=examples/file.ino 60 | # - PLATFORMIO_CI_SRC=path/to/test/directory 61 | # 62 | # install: 63 | # - pip install -U platformio 64 | # - platformio update 65 | # 66 | # script: 67 | # - platformio ci --lib="." --board=ID_1 --board=ID_2 --board=ID_N 68 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See http://go.microsoft.com/fwlink/?LinkId=827846 3 | // for the documentation about the extensions.json format 4 | "recommendations": [ 5 | "platformio.platformio-ide" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "terminal.integrated.env.linux": { 3 | "PATH": "${env:HOME}/.platformio/penv/bin:${env:HOME}/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", 4 | "PLATFORMIO_CALLER": "vscode" 5 | }, 6 | "cSpell.words": [ 7 | "ADCs", 8 | "AFIO", 9 | "Adafruit", 10 | "ATmega", 11 | "FLITF", 12 | "FSMC", 13 | "GPIOA", 14 | "GPIOB", 15 | "GPIOC", 16 | "GPIOD", 17 | "GPIOE", 18 | "GPIOF", 19 | "GPIOG", 20 | "INAbufer", 21 | "LOLIN", 22 | "RTClib", 23 | "RTCs", 24 | "RTCutil", 25 | "SDIO", 26 | "SRAM", 27 | "TERMutil", 28 | "USART", 29 | "VCOMH", 30 | "WEMOS", 31 | "YYMMDD", 32 | "dtostrf" 33 | ] 34 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Alvaro Valdebenito 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PowerLogger 2 | DIY multi-channel voltage/current data logger 3 | 4 | Inspired by the Power-Meter/Logger project by [GreatScott][], 5 | this project is built around the [INA library][INAlib]. 6 | 7 | ## INAxxx devices 8 | The [INA library][INAlib], allows access to multiple INA219/INA226/INA3221 devices. 9 | As long as each device has a different I2C address, the library will 10 | auto discover them and store the device type and relevant configuration on the microcontroller's EEPROM. 11 | 12 | ## Buffered SD card 13 | SD card support is provided by the [SdFat library][SdFat]. 14 | The [CircularBuffer library][Buffer] provides the buffer for the measurements. 15 | 16 | ## Builtin RTC (STM32F1) 17 | STM32F1 microcontrollers have a builtin RTC. 18 | The [bluepill][] board has a 32 kHz RTC crystal 19 | and a dedicated pin (marked `VB` on the board and `Vbat` on the pinout) 20 | for a RTC backup battery. 21 | 22 | ## External RTC (optional) 23 | The following external RTCs are supported via the [RTClib library][RTClib]: 24 | DS1307, DS3231, PCF8583, PCF8563. 25 | 26 | ## Display (optional) 27 | Small displays are supported thanks to the [U8x8 constructors][U8x8] 28 | from the [U8g2 library][U8g2]. At the moment only some 128x64 and 128x32 I2C 29 | displays are supported. 30 | 31 | When a display is present, the print statements that otherwise would go to 32 | `Serial` are redirected to the display. The measurements are formatted 33 | to fit 6 channels on a 128x64 display and 2 channels on a 128x32 display. 34 | 35 | ## CSV file 36 | Voltage and current measurements are not written directly to the SD card. 37 | They are temporarily sotred on a circular buffer. 38 | 39 | The measurements from all INA devices taken "at the same time" 40 | are buffered as one `Record`. 41 | Each `Record` contains single timestamp, and the 42 | voltage and current measurements from each device. 43 | The INA devices are ordered according to their I2C address. 44 | 45 | When the buffer is full, each `Record` is written to the SD card 46 | as a single line on a CSV file. 47 | 48 | ## Single button pause/resume/shutdown 49 | Besides debouncing, the [OneButton library][OneButton] provides 50 | multiple functionality with a single tact button: 51 | - short press: pause/resume recording 52 | - long press: safe shutdown 53 | - double press: display backlight (if applicable) 54 | 55 | Short press will toggle pause/resume recording to the SD card. 56 | The contents of the buffer are written to the SD card before pausing. 57 | Measurements will continue, but they will not be saved to the buffer 58 | until recording is resumed. 59 | 60 | Safe shutdown can be implemented with [additional circuitry][softpower]. 61 | (for 3.3V replace the IRF7319 by a IRF7317, as sugested [here][battery]). 62 | A long press will write buffet to the SD card and power down the logger. 63 | A subsequent single press will power up the logger. 64 | 65 | A double press will switch on/off the display backlight, if applicable. 66 | The switching part of the [shutdown circuit][softpower] (P/N channel MOSFETS) 67 | can be be used to connect/disconnect the display backlight power pin (`BACKLIGHT_PIN` flag). 68 | If no `BACKLIGHT_PIN` flag is defined, the double press will put the display 69 | on [power save mode][U8x8]. 70 | 71 | # Instructions 72 | 73 | ## Project configuration 74 | Different hardware combinations are defined on `platformio.ini`. 75 | Other options, such as sampling frequency, number of channels and buffer size, 76 | are defined on `include/config.h`. Change as needed and copile the project with `pio run`. 77 | 78 | **Note** a single INA3221 counts as 3 INA channels. 79 | 80 | ## Project libraries on lib/ 81 | The libraries on `lib/` provide simplified interfaces to the RTC object (`lib/RTCutil`), 82 | display/serial `Print` objects (`lib/TERMutil`), 83 | and the buffering of INA measurements (`lib/INAbufer`). 84 | Each local library contains a `library.json` file describing the 85 | library dependencies and build flags. 86 | 87 | ## External library dependencies 88 | This project external library dependencies are described 89 | by `lib_deps` on `platformio.ini`, and `dependencies` in `lib/*/library.json`. 90 | The first time the project is compiled, 91 | the [PlatformIO Library Manager][piolib] will download and install all the dependencies. 92 | They will be installed to `~/.platformio/lib`, which is assumed to be the global library path. 93 | For a local install comment the `libdeps_dir` definition on `platformio.ini`. 94 | 95 | The following commands are provided for reference. 96 | ```bash 97 | # global install, so it can be used on other projects 98 | pio lib --global install INA SdFat CircularBuffer OneButton RTClib U8g2 99 | ``` 100 | 101 | The [MemoryFree library][MemoryFree] is not available on the [Library Manager][piolib], so we need to specify project repository. 102 | ```bash 103 | # global install from project repository 104 | pio lib --global install git@github.com:mpflaga/Arduino-MemoryFree.git 105 | ``` 106 | 107 | # Need for help? 108 | ## Test the project first 109 | The different components of this project are tested on `test/test_main.cpp`. 110 | The RAM usage is monitored using the [MemoryFree library][MemoryFree]. 111 | Please run `pio test` before opening an issue asking for support. 112 | 113 | [GreatScott]: https://www.instructables.com/id/Make-Your-Own-Power-MeterLogger/ 114 | [bluepill]: https://wiki.stm32duino.com/index.php?title=Blue_Pill 115 | [softpower]: http://www.mosaic-industries.com/embedded-systems/microcontroller-projects/electronic-circuits/push-button-switch-turn-on/microcontroller-latching-on-off 116 | [battery]: http://www.mosaic-industries.com/embedded-systems/microcontroller-projects/electronic-circuits/push-button-switch-turn-on/switching-battery-power 117 | 118 | [piolib]: http://docs.platformio.org/en/latest/librarymanager/index.html 119 | [INAlib]: https://github.com/SV-Zanshin/INA 120 | [INAfork]: https://github.com/avaldebe/INA/tree/stm32f1 121 | [SdFat]: https://github.com/greiman/SdFat 122 | [Buffer]: https://github.com/rlogiacco/CircularBuffer 123 | [OneButton]: https://github.com/mathertel/OneButton 124 | [RTClib]: https://github.com/adafruit/RTClib 125 | [U8g2]: https://github.com/olikraus/u8g2 126 | [U8x8]: https://github.com/olikraus/u8g2/wiki/u8x8reference 127 | [MemoryFree]: https://github.com/mpflaga/Arduino-MemoryFree 128 | -------------------------------------------------------------------------------- /include/README: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for project header files. 3 | 4 | A header file is a file containing C declarations and macro definitions 5 | to be shared between several project source files. You request the use of a 6 | header file in your project source file (C, C++, etc) located in `src` folder 7 | by including it, with the C preprocessing directive `#include'. 8 | 9 | ```src/main.c 10 | 11 | #include "header.h" 12 | 13 | int main (void) 14 | { 15 | ... 16 | } 17 | ``` 18 | 19 | Including a header file produces the same results as copying the header file 20 | into each source file that needs it. Such copying would be time-consuming 21 | and error-prone. With a header file, the related declarations appear 22 | in only one place. If they need to be changed, they can be changed in one 23 | place, and programs that include the header file will automatically use the 24 | new version when next recompiled. The header file eliminates the labor of 25 | finding and changing all the copies as well as the risk that a failure to 26 | find one copy will result in inconsistencies within a program. 27 | 28 | In C, the usual convention is to give header files names that end with `.h'. 29 | It is most portable to use only letters, digits, dashes, and underscores in 30 | header file names, and at most one dot. 31 | 32 | Read more about using header files in official GCC documentation: 33 | 34 | * Include Syntax 35 | * Include Operation 36 | * Once-Only Headers 37 | * Computed Includes 38 | 39 | https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html 40 | -------------------------------------------------------------------------------- /include/config.h: -------------------------------------------------------------------------------- 1 | #ifndef config_h 2 | #define config_h 3 | #include 4 | 5 | /* SD CARD: Reduced SPI speed for breadboards */ 6 | #ifdef BREADBOARD 7 | #define SPI_SPEED SD_SCK_MHZ(4) // highest speed supported by the board under 4 MHz. 8 | #else 9 | #define SPI_SPEED SD_SCK_MHZ(50) // best performance 10 | #endif 11 | #ifndef SD_CS 12 | #define SD_CS SS 13 | #endif 14 | // check for pin collision with TFT chip select pin 15 | #if defined DISPLAY_CS && DISPLAY_CS == SD_CS 16 | #error "DISPLAY_CS and SD_CS need different SS pins" 17 | #endif 18 | 19 | // Measurament frequency 20 | #define INA_CONVTIME 8600 // maximum conversion time [us]: \ 21 | // 8.510ms on INA219, 8.244ms elsewhere 22 | #define SAMPLES(frec, conv) ((uint16_t)frec * 1000 / (conv * 2)) 23 | #define INA_SAMPLES SAMPLES(FREQUENCY, INA_CONVTIME) // fit FREQUENCY 24 | #ifndef FREQUENCY // ms between measurements 25 | #define FREQUENCY 1000 // default 26 | #endif 27 | /* 28 | #ifndef SHORTPRESS // ms single press duration 29 | #define SHORTPRESS 100 // default 30 | #endif 31 | #ifndef LONGPRESS // ms long press duration 32 | #define LONGPRESS 500 // default 33 | #endif 34 | */ 35 | 36 | // Measurement array and buffer size 37 | #ifndef BUFFER_SIZE // Max buffer size 38 | #define BUFFER_SIZE 128 // bytes 39 | #endif 40 | 41 | #endif 42 | -------------------------------------------------------------------------------- /lib/INAbufer/INAbufer.cpp: -------------------------------------------------------------------------------- 1 | #include "INAbufer.h" // secret sauce ;-) 2 | CircularBuffer buffer; 3 | static char linebuffer[16]; // long enough for 1 line 4 | 5 | #include 6 | INA_Class INA; 7 | uint8_t Record::ina_count = 0; // no devices found so far 8 | 9 | Record::Record(uint32_t time) : time(time) 10 | { 11 | milliVolts = new uint32_t[ina_count]; 12 | microAmps = new uint32_t[ina_count]; 13 | for (uint8_t i = 0; i < ina_count; i++) 14 | { 15 | milliVolts[i] = INA.getBusMilliVolts(i); 16 | microAmps[i] = INA.getBusMicroAmps(i); 17 | } 18 | } 19 | 20 | char *Record::getRunTime(uint32_t s) 21 | { 22 | uint8_t d, h, m; 23 | d = s / 86400; 24 | s %= 86400; // 86400 secs on a day 25 | h = s / 3600; 26 | s %= 3600; // 3600 secs on a hour 27 | m = s / 60; 28 | s %= 60; // 60 secs on a minute 29 | sprintf(linebuffer, "%02u:%02u:%02u:%02u", d, h, m, (uint8_t)s); 30 | return linebuffer; 31 | } 32 | 33 | uint8_t Record::init(uint8_t maxBusAmps, uint32_t microOhmR) 34 | { 35 | ina_count = INA.begin(maxBusAmps, microOhmR); 36 | INA.setBusConversion(INA_CONVTIME); // see config.h for value 37 | INA.setShuntConversion(INA_CONVTIME); // see config.h for value 38 | INA.setAveraging(INA_SAMPLES); // see config.h for value 39 | INA.setMode(INA_MODE_CONTINUOUS_BOTH); // bus&shunt 40 | return ina_count; 41 | } 42 | 43 | uint8_t Record::init(Print *out) 44 | { 45 | init(); 46 | while (ina_count == 0) 47 | { 48 | out->print(F("ERROR: no INA devices found")); 49 | delay(1000); 50 | init(); // try again 51 | } 52 | 53 | for (uint8_t i = 0; i < ina_count; i++) 54 | { 55 | out->print(F("INA")); 56 | out->print(i); 57 | out->print(F(": ")); 58 | out->println(INA.getDeviceName(i)); 59 | } 60 | 61 | out->print(F("Buffer ")); 62 | out->print(size()); 63 | out->print(F("Bx")); 64 | out->print(max_len()); 65 | out->println("rec"); 66 | 67 | return ina_count; 68 | } 69 | 70 | void Record::header(Print *out) 71 | { 72 | out->print(F("millis")); 73 | for (uint8_t i = 0; i < ina_count; i++) 74 | { 75 | out->print(F(",ch")); 76 | out->print(i); 77 | out->print(F(" voltage [mV]")); 78 | out->print(F(",ch")); 79 | out->print(i); 80 | out->print(F(" current [uA]")); 81 | } 82 | out->println(); 83 | } 84 | 85 | void Record::print(Print *out) 86 | { 87 | out->print(time); 88 | for (uint8_t i = 0; i < ina_count; i++) 89 | { 90 | out->print(F(",")); 91 | out->print(milliVolts[i]); 92 | out->print(F(",")); 93 | out->print(microAmps[i]); 94 | } 95 | out->println(); 96 | } 97 | 98 | void Record::splash(Print *out, uint8_t width, uint8_t height) 99 | { 100 | bool header; 101 | uint8_t i; 102 | switch (width) 103 | { 104 | case 16 ... 255: // wide screen. eg 128x64 or 128x32 105 | header = height > ina_count; 106 | // 0123456789ABCDEF 107 | if (header) 108 | { 109 | out->print(F("# V [V] I [A]\n")); 110 | } 111 | // "1: 23.000 1.000" 112 | for (i = 0; i < ina_count; i++) 113 | { 114 | out->print(i); 115 | out->print(F(":")); 116 | out->print(dtostrf(getVolts(i), 7, 3, linebuffer)); 117 | out->print(dtostrf(getAmps(i), 7, 3, linebuffer)); 118 | out->print(F("\n")); 119 | } 120 | break; 121 | case 12 ... 15: // narrow screen, eg 98X68 122 | header = height > ina_count; 123 | // 0123456789ABC 124 | if (header) 125 | { 126 | out->print(F("# V [V] I [A]\n")); 127 | } 128 | // "1 23.00 1.000" 129 | for (i = 0; i < ina_count; i++) 130 | { 131 | out->print(i); 132 | out->print(dtostrf(getVolts(i), 6, 2, linebuffer)); 133 | out->print(dtostrf(getAmps(i), 6, 3, linebuffer)); 134 | out->print(F("\n")); 135 | } 136 | break; 137 | case 6 ... 11: // narrow screen, eg 84X48 or 64X48 138 | header = width > 7; 139 | for (i = 0; i < ina_count; i++) 140 | { 141 | // 012345678 142 | if (header) 143 | { 144 | out->print(F("V")); 145 | } 146 | // "V1 23.000" 147 | out->print(i); 148 | out->print(dtostrf(getVolts(i), 7, 3, linebuffer)); 149 | out->print(F("\n")); 150 | // 012345678 151 | if (header) 152 | { 153 | out->print(F("A")); 154 | } 155 | // "A1 1.000" 156 | out->print(i); 157 | out->print(dtostrf(getAmps(i), 7, 3, linebuffer)); 158 | out->print(F("\n")); 159 | } 160 | break; 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /lib/INAbufer/INAbufer.h: -------------------------------------------------------------------------------- 1 | #ifndef INAbufer_h 2 | #define INAbufer_h 3 | 4 | #include 5 | #include 6 | #include 7 | #include "config.h" // project configuration 8 | 9 | class Record 10 | { 11 | public: 12 | Record(){}; 13 | Record(uint32_t time); 14 | ~Record() 15 | { 16 | delete[] milliVolts; 17 | delete[] microAmps; 18 | }; 19 | 20 | inline uint32_t getTime() { return time; } 21 | inline uint32_t getMilliVolts(uint8_t i) { return (i < ina_count) ? milliVolts[i] : 0; } 22 | inline uint32_t getMicroAmps(uint8_t i) { return (i < ina_count) ? microAmps[i] : 0; } 23 | inline float getVolts(uint8_t i) { return (float)getMilliVolts(i) / 1000; } 24 | inline float getAmps(uint8_t i) { return (float)getMicroAmps(i) / 1000000; } 25 | 26 | static char *getRunTime(uint32_t secs); 27 | inline char *getRunTime() { return getRunTime(time / 1000); } 28 | 29 | static uint8_t init(uint8_t maxBusAmps = 1, uint32_t microOhmR = 100000); 30 | static uint8_t init(Print *out); 31 | void header(Print *out); 32 | void print(Print *out); 33 | void splash(Print *out, uint8_t cols, uint8_t rows); 34 | 35 | // size of 1 record from n channels 36 | #define RECORD_SIZE(n) ((1 + 2 * n) * sizeof(uint32_t)) 37 | static inline uint8_t size() 38 | { 39 | return RECORD_SIZE(ina_count); 40 | } 41 | 42 | // max records(n channels) in buffer 43 | #define BUFFER_MAX(n) (BUFFER_SIZE / RECORD_SIZE(n)) 44 | static inline uint8_t max_len() 45 | { 46 | return BUFFER_MAX(ina_count); 47 | } 48 | 49 | protected: 50 | static uint8_t ina_count; 51 | uint32_t time; 52 | uint32_t *milliVolts; 53 | uint32_t *microAmps; 54 | }; 55 | 56 | #include 57 | #define BUFFER_LEN BUFFER_MAX(2) 58 | extern CircularBuffer buffer; 59 | 60 | inline bool buffer_full() { return buffer.isFull() || buffer.size() >= Record::max_len(); } 61 | 62 | #endif 63 | -------------------------------------------------------------------------------- /lib/INAbufer/library.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "PowerLogger/INAbufer", 3 | "version": "0.1.0", 4 | "build": { 5 | "includeDir": "../../include/" 6 | }, 7 | "dependencies": [ 8 | { 9 | "name": "INA2xx", 10 | "version": ">=1.0.3", 11 | "author": "Arnd" 12 | }, 13 | { 14 | "name": "CircularBuffer", 15 | "version": ">=1.2.0", 16 | "authors": [ 17 | { 18 | "name": "AgileWare" 19 | }, 20 | { 21 | "name": "Roberto Lo Giacco", 22 | "maintainer": true 23 | } 24 | ] 25 | } 26 | ] 27 | } -------------------------------------------------------------------------------- /lib/RTCutil/RTCutil.cpp: -------------------------------------------------------------------------------- 1 | #ifdef HAST_RTC 2 | #include "RTCutil.h" 3 | 4 | // STM32F1 internal RTC 5 | #define INTERNAL_32kHz 32768 6 | #define INTERNAL_62kHz 62500 7 | 8 | // supported external RTCs 9 | #define DS1307 1307 10 | #define DS3231 3231 11 | #define PCF8583 8583 12 | #define PCF8563 8563 13 | 14 | #if HAST_RTC == INTERNAL_32kHz || HAST_RTC == INTERNAL_62kHz 15 | #define INTERNAL_RTC 16 | #ifndef STM32F1 17 | #error "Internal RTC options only for STM32F1" 18 | #endif 19 | #include // builtin RTC 20 | STM32RTC& rtc = STM32RTC::getInstance(); 21 | #include 22 | static tm now; 23 | static uint32_t unixtime; 24 | #elif HAST_RTC == DS1307 || HAST_RTC == DS3231 || HAST_RTC == PCF8583 || HAST_RTC == PCF8563 25 | #include // External RTC 26 | #if HAST_RTC == DS1307 27 | static RTC_DS1307 rtc; 28 | #elif HAST_RTC == DS3231 29 | static RTC_DS3231 rtc; 30 | #elif HAST_RTC == PCF8583 31 | static RTC_PCF8583 rtc; 32 | #elif HAST_RTC == PCF8563 33 | static RTC_PCF8563 rtc; 34 | #endif 35 | static DateTime now; 36 | static uint32_t unixtime; 37 | #else 38 | #error "Unknown RTC option" 39 | #endif 40 | 41 | #ifndef BUILD_TIME 42 | #error "Missing BUILD_TIME flag, try again with -D BUILD_TIME=$UNIX_TIME" 43 | #endif 44 | bool rtc_stale() 45 | { 46 | return rtc_now() < BUILD_TIME; 47 | } 48 | void rtc_init() 49 | { 50 | #if HAST_RTC == INTERNAL_32kHz // LSE: low-speed external oscillator 51 | rtc.setClockSource(STM32RTC::LSE_CLOCK); // 32768 Hz crystal 52 | #elif HAST_RTC == INTERNAL_62kHz // HSE: high-speed external oscillator 53 | rtc.setClockSource(STM32RTC::HSE_CLOCK); // 8 MHz/128 (62500 Hz) 54 | #endif 55 | rtc.begin(); // initialize RTC 24H format 56 | if (rtc_stale()) 57 | { 58 | rtc_now(BUILD_TIME); 59 | } 60 | } 61 | 62 | uint32_t rtc_now() 63 | { 64 | #ifdef INTERNAL_RTC 65 | unixtime = rtc.getEpoch(); 66 | time_t rawtime = unixtime; 67 | now = *localtime(&rawtime); 68 | #else 69 | now = rtc.now(); 70 | unixtime = now.unixtime(); 71 | #endif 72 | return unixtime; 73 | } 74 | 75 | uint32_t rtc_now(uint32_t time) 76 | { 77 | #ifdef INTERNAL_RTC 78 | rtc.setEpoch(time); 79 | #else 80 | rtc.adjust(DateTime(time)); 81 | #endif 82 | return rtc_now(); // update now/unixtime 83 | } 84 | 85 | char *rtc_fmt(const char fmt) 86 | { 87 | static char str[16]; // long enough for 1 line 88 | switch (fmt) 89 | { 90 | #ifdef INTERNAL_RTC 91 | case 'D': // long date 92 | strftime(str, sizeof(str), "%Y-%m-%d", &now); 93 | break; 94 | case 'd': // short date 95 | strftime(str, sizeof(str), "%y%m%d", &now); 96 | break; 97 | case 'T': // long time 98 | strftime(str, sizeof(str), "%H:%M:%S", &now); 99 | break; 100 | case 't': // short time 101 | strftime(str, sizeof(str), "%H%M", &now); 102 | break; 103 | case 'C': // file.csv 104 | strftime(str, sizeof(str), "%y%m%d.csv", &now); 105 | break; 106 | #else 107 | case 'D': // long date 108 | sprintf(str, "%04u-%02u-%02u", now.year(), now.month(), now.day()); 109 | break; 110 | case 'd': // short date 111 | sprintf(str, "%02d%02u%02u", now.year() - 2000, now.month(), now.day()); 112 | break; 113 | case 'T': // long time 114 | sprintf(str, "%02u:%02u:%02u", now.hour(), now.minute(), now.second()); 115 | break; 116 | case 't': // short time 117 | sprintf(str, "%02u%02u", now.hour(), now.minute()); 118 | break; 119 | case 'C': // YYMMDD.csv 120 | sprintf(str, "%02d%02u%02u.csv", now.year() - 2000, now.month(), now.day()); 121 | break; 122 | #endif 123 | } 124 | return str; 125 | } 126 | #endif 127 | -------------------------------------------------------------------------------- /lib/RTCutil/RTCutil.h: -------------------------------------------------------------------------------- 1 | #ifndef RTCutil_h 2 | #define RTCutil_h 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #ifdef HAST_RTC 9 | bool rtc_stale(); 10 | void rtc_init(); 11 | uint32_t rtc_now(); 12 | uint32_t rtc_now(uint32_t time); 13 | char *rtc_fmt(const char fmt); // returns internal buffer 14 | #else 15 | inline uint32_t rtc_init() { return 0; }; 16 | inline uint32_t rtc_now() { return 0; }; 17 | #endif 18 | 19 | #endif 20 | -------------------------------------------------------------------------------- /lib/RTCutil/library.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "PowerLogger/RTCutil", 3 | "version": "0.1.0", 4 | "build": { 5 | "flags": "-D BUILD_TIME=$UNIX_TIME" 6 | }, 7 | "dependencies": { 8 | "name": "RTClib", 9 | "version": ">=1.2.1", 10 | "author": "Adafruit" 11 | } 12 | } -------------------------------------------------------------------------------- /lib/TERMutil/TERMu8x8.h: -------------------------------------------------------------------------------- 1 | #ifndef TERMu8x8_h 2 | #define TERMu8x8_h 3 | 4 | // suported display drivers 5 | #define SSD1305 1305 6 | #define SSD1306 1306 7 | #define SSD1309 1309 8 | #define SH1106 1106 9 | #define SH1107 1107 10 | #define UC1701 1701 11 | #define PCD8544 8544 12 | #define HX1230 1230 13 | 14 | #if defined(TERM_U8X8) && defined(DISPLAY_SIZE) 15 | // defined directly on platfromio.ini 16 | #elif defined(DISPLAY_64X48) 17 | #define DISPLAY_SIZE 0x4030 18 | #ifdef TERM_U8X8 19 | #elif DISPLAY_64X48 == SSD1306 // WEMOS/LOLIN D1 mini OLED shield 20 | #define TERM_U8X8 U8X8_SSD1306_64X48_ER_F_HW_I2C 21 | #else 22 | #error "Unsupported DISPLAY_64X48" 23 | #endif 24 | #elif defined(DISPLAY_84X48) 25 | #define DISPLAY_SIZE 0x5430 26 | #ifdef TERM_U8X8 27 | #elif DISPLAY_84X48 == PCD8544 && defined(DISPLAY_SW_SPI) // Nokia 5110 LCD, SW SPI 28 | #define TERM_U8X8 U8X8_PCD8544_84X48_4W_SW_SPI 29 | #elif DISPLAY_84X48 == PCD8544 && defined(DISPLAY_HW_SPI) // Nokia 5110 LCD, HW SPI 30 | #define TERM_U8X8 U8X8_PCD8544_84X48_4W_HW_SPI 31 | #elif DISPLAY_84X48 == PCD8544 && defined(DISPLAY_H2_SPI) // Nokia 5110 LCD, HW SPI2 32 | #define TERM_U8X8 U8X8_PCD8544_84X48_2ND_4W_HW_SPI 33 | #else 34 | #error "Unsupported DISPLAY_84X48" 35 | #endif 36 | #elif defined(DISPLAY_98X68) 37 | #define DISPLAY_SIZE 0x5E44 38 | #ifdef TERM_U8X8 39 | #elif DISPLAY_98X68 == HX1230 && defined(DISPLAY_SW_SPI) // Nokia 5110 LCD, SW SPI 40 | #define TERM_U8X8 U8X8_HX1230_96X68_4W_SW_SPI 41 | #elif DISPLAY_98X68 == HX1230 && defined(DISPLAY_HW_SPI) // Nokia 5110 LCD, HW SPI 42 | #define TERM_U8X8 U8X8_HX1230_96X68_4W_HW_SPI 43 | #elif DISPLAY_98X68 == HX1230 && defined(DISPLAY_H2_SPI) // Nokia 5110 LCD, HW SPI2 44 | #define TERM_U8X8 U8X8_HX1230_96X68_2ND_4W_HW_SPI 45 | #else 46 | #error "Unsupported DISPLAY_98X68" 47 | #endif 48 | #elif defined(DISPLAY_128X32) 49 | #define DISPLAY_SIZE 0x8020 50 | #ifdef TERM_U8X8 51 | #elif DISPLAY_128X32 == SSD1305 52 | #define TERM_U8X8 U8X8_SSD1305_128X32_NONAME_HW_I2C 53 | #elif DISPLAY_128X32 == SSD1306 54 | #define TERM_U8X8 U8X8_SSD1306_128X32_UNIVISION_HW_I2C 55 | #else 56 | #error "Unsupported DISPLAY_128X32" 57 | #endif 58 | #elif defined(DISPLAY_128X64) 59 | #define DISPLAY_SIZE 0x8040 60 | #ifdef TERM_U8X8 61 | #elif DISPLAY_128X64 == SSD1305 62 | #define TERM_U8X8 U8X8_SSD1305_128X64_ADAFRUIT_HW_I2C 63 | #elif DISPLAY_128X64 == SSD1306 64 | #define TERM_U8X8 U8X8_SSD1306_128X64_NONAME_HW_I2C 65 | //#define TERM_U8X8 U8X8_SSD1306_128X64_VCOMH0_HW_I2C 66 | //#define TERM_U8X8 U8X8_SSD1306_128X64_ALT0_HW_I2C 67 | #elif DISPLAY_128X64 == SSD1309 68 | #define TERM_U8X8 U8X8_SSD1309_128X64_NONAME0_HW_I2C 69 | //#define TERM_U8X8 U8X8_SSD1309_128X64_NONAME2_HW_I2C 70 | #elif DISPLAY_128X64 == SH1106 71 | #define TERM_U8X8 U8X8_SH1106_128X64_NONAME_HW_I2C 72 | //#define TERM_U8X8 U8X8_SH1106_128X64_VCOMH0_HW_I2C 73 | //#define TERM_U8X8 U8X8_SH1106_128X64_WINSTAR_HW_I2C 74 | #elif DISPLAY_128X64 == UC1701 && defined(DISPLAY_SW_SPI) // SW SPI 75 | #define TERM_U8X8 U8X8_UC1701_MINI12864_4W_SW_SPI 76 | #elif DISPLAY_128X64 == UC1701 && defined(DISPLAY_HW_SPI) // HW SPI 77 | #define TERM_U8X8 U8X8_UC1701_MINI12864_4W_HW_SPI 78 | #elif DISPLAY_128X64 == UC1701 && defined(DISPLAY_H2_SPI) // HW SPI2 79 | #define TERM_U8X8 U8X8_UC1701_MINI12864_2ND_4W_HW_SPI 80 | #else 81 | #error "Unsupported DISPLAY_128X64" 82 | #endif 83 | #elif defined(DISPLAY_128X128) 84 | #define DISPLAY_SIZE 0x8080 85 | #ifdef TERM_U8X8 86 | #elif DISPLAY_128X128 == SH1107 87 | #define TERM_U8X8 U8X8_SH1107_128X128_HW_I2C 88 | #else 89 | #error "Unsupported DISPLAY_128X128" 90 | #endif 91 | #endif 92 | 93 | #ifndef DISPLAY_SIZE 94 | #ifdef TERM_U8X8 95 | #error "Missing DISPLAY_SIZE flag, e.g. -D DISPLAY_SIZE=0x8040" 96 | #else // e.g. Serial.print 97 | #define DISPLAY_SIZE 0xA0A0 // 20x20 characters 98 | #endif 99 | #endif 100 | 101 | #ifdef DISPLAY_SIZE 102 | // decode DISPLAY_SIZE 103 | // display.size: hex defined by DISPLAY_SIZE flag (eg 0x8040) 104 | // display.width: display width in pixels (eg 128 pixels) 105 | // display.height: display height in pixels (eg 32 pixels) 106 | // display.cols: max text cols (width/8) (eg 16 cols) 107 | // display.rows max text rows (height/8) (eg 8 rows) 108 | #include 109 | const union { 110 | uint16_t size; 111 | struct 112 | { 113 | uint8_t height, width; 114 | }; 115 | struct 116 | { 117 | uint8_t : 3, rows : 5, : 3, cols : 5; 118 | }; 119 | } display = {DISPLAY_SIZE}; 120 | #endif 121 | 122 | #endif 123 | -------------------------------------------------------------------------------- /lib/TERMutil/TERMutil.cpp: -------------------------------------------------------------------------------- 1 | #include "TERMutil.h" 2 | #ifdef TERM_U8X8 3 | #ifdef DISPLAY_RST 4 | static const uint8_t reset = DISPLAY_RST; 5 | #else 6 | static const uint8_t reset = U8X8_PIN_NONE; // trigger the right U8X8 constructor 7 | #endif 8 | #if defined(DISPLAY_SW_SPI) // SW SPI 9 | TERM_U8X8 TERMINAL(DISPLAY_SW_SPI, reset); 10 | #elif defined(DISPLAY_HW_SPI) // HW SPI1 11 | TERM_U8X8 TERMINAL(DISPLAY_HW_SPI, reset); 12 | #elif defined(DISPLAY_H2_SPI) // HW SPI2 13 | TERM_U8X8 TERMINAL(DISPLAY_H2_SPI, reset); 14 | #else 15 | TERM_U8X8 TERMINAL(reset); 16 | #endif 17 | #endif 18 | 19 | void TERMINAL_begin(uint32_t baudrate) 20 | { 21 | #ifdef TERM_U8X8 22 | TERMINAL.begin(); 23 | TERMINAL.setFont(u8x8_font_chroma48medium8_r); 24 | TERMINAL.clear(); 25 | #elif defined(TERMINAL) 26 | TERMINAL.begin(baudrate); // 57600 for ATmega328p 3.3V 8Mhz 27 | while (!TERMINAL) 28 | { 29 | delay(10); 30 | } // wait for USB Serial 31 | #endif 32 | } 33 | 34 | void TERMINAL_toggle() 35 | { 36 | #ifdef TERM_U8X8 37 | static bool is_on = true; // backlight on 38 | is_on ^= true; // same as !is_on 39 | // enable (1) or disable (0) power save mode for the display 40 | TERMINAL.setPowerSave(is_on ? 0 : 1); 41 | #endif 42 | } 43 | -------------------------------------------------------------------------------- /lib/TERMutil/TERMutil.h: -------------------------------------------------------------------------------- 1 | #ifndef TERMutil_h 2 | #define TERMutil_h 3 | #include 4 | 5 | #include 6 | #include "TERMu8x8.h" 7 | #ifdef TERM_U8X8 8 | #include 9 | extern TERM_U8X8 TERMINAL; 10 | #elif defined(NO_TERMINAL) || defined(TERMINAL) 11 | #else 12 | #define TERMINAL Serial 13 | #endif 14 | 15 | void TERMINAL_begin(uint32_t baudrate = 57600); 16 | void TERMINAL_toggle(); 17 | 18 | inline void TERMINAL_clear(uint32_t ms = 0) 19 | { 20 | delay(ms); 21 | #ifdef TERM_U8X8 22 | TERMINAL.clear(); 23 | #endif 24 | } 25 | 26 | inline void TERMINAL_home() 27 | { 28 | #ifdef TERM_U8X8 29 | TERMINAL.home(); 30 | #endif 31 | } 32 | 33 | #endif 34 | -------------------------------------------------------------------------------- /lib/TERMutil/library.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "PowerLogger/TERMutil", 3 | "version": "0.1.0", 4 | "dependencies": { 5 | "name": "U8g2", 6 | "version": ">=2.23.16", 7 | "author": "oliver" 8 | } 9 | } -------------------------------------------------------------------------------- /lib/readme.txt: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for the project specific (private) libraries. 3 | PlatformIO will compile them to static libraries and link to executable file. 4 | 5 | The source code of each library should be placed in separate directory, like 6 | "lib/private_lib/[here are source files]". 7 | 8 | For example, see how can be organized `Foo` and `Bar` libraries: 9 | 10 | |--lib 11 | | | 12 | | |--Bar 13 | | | |--docs 14 | | | |--examples 15 | | | |--src 16 | | | |- Bar.c 17 | | | |- Bar.h 18 | | | |- library.json (optional, custom build options, etc) http://docs.platformio.org/page/librarymanager/config.html 19 | | | 20 | | |--Foo 21 | | | |- Foo.c 22 | | | |- Foo.h 23 | | | 24 | | |- readme.txt --> THIS FILE 25 | | 26 | |- platformio.ini 27 | |--src 28 | |- main.c 29 | 30 | Then in `src/main.c` you should use: 31 | 32 | #include 33 | #include 34 | 35 | // rest H/C/CPP code 36 | 37 | PlatformIO will find your libraries automatically, configure preprocessor's 38 | include paths and build them. 39 | 40 | More information about PlatformIO Library Dependency Finder 41 | - http://docs.platformio.org/page/librarymanager/ldf.html 42 | -------------------------------------------------------------------------------- /platformio.ini: -------------------------------------------------------------------------------- 1 | ; PlatformIO Project Configuration File 2 | ; 3 | ; Build options: build flags, source filter 4 | ; Upload options: custom upload port, speed and extra flags 5 | ; Library options: dependencies, extra library storages 6 | ; Advanced options: extra scripting 7 | ; 8 | ; Please visit documentation for the other options and examples 9 | ; https://docs.platformio.org/page/projectconf.html 10 | 11 | [platformio] 12 | description = Multichannel Voltage/Current Logger 13 | default_envs = STM32f103c8 14 | 15 | [env] 16 | framework = arduino 17 | targets = checkprogsize 18 | lib_deps = 19 | sv-zanshin/INA2xx 20 | greiman/SdFat 21 | rlogiacco/CircularBuffer 22 | mathertel/OneButton 23 | olikraus/U8g2 24 | build_flags = 25 | !echo -D GIT_REV=\\\"`git describe`\\\" 26 | ; -D BREADBOARD 27 | 28 | [env:ATmega328p] 29 | ; @3.3V/8 MHz, RTC: DS1307; display: SH1106 128x64; tact button D3 30 | platform = atmelavr 31 | board = pro8MHzatmega328 32 | lib_deps = 33 | ${env.lib_deps} 34 | adafruit/RTClib 35 | build_flags = 36 | ${env.build_flags} 37 | -D HAST_RTC=DS1307 38 | -D DISPLAY_128X64=SH1106 39 | -D BUTTON_PIN=3 40 | -D HAS_SOFTPOWER 41 | 42 | [env:STM32f103c8] 43 | ; 64 KB flash, RTC: 32768 Hz crystal; display: UC1701 128x64 on SPI2; tact button PA0 44 | platform = ststm32 45 | debug_tool = stlink 46 | board = genericSTM32F103C8 47 | lib_deps = 48 | ${env.lib_deps} 49 | stm32duino/STM32duino RTC 50 | build_flags = 51 | ${env.build_flags} 52 | -D HAST_RTC=INTERNAL_32kHz 53 | -D DISPLAY_128X64=UC1701 54 | -D DISPLAY_SW_SPI=PB15,PB14,PB12,PB13 55 | -D BUTTON_PIN=PA0 56 | -D BACKLIGHT_PIN=PA8 57 | -D BUFFER_SIZE=2048 58 | -D FREQUENCY=250 59 | 60 | [env:STM32f103cb] 61 | ; 128 KB flash, RTC: 62500 Hz (8 MHz/128) clock; no display: use Serial1; tact button PA0 62 | platform = ststm32 63 | debug_tool = stlink 64 | board = genericSTM32F103CB 65 | lib_deps = 66 | ${env.lib_deps} 67 | stm32duino/STM32duino RTC 68 | build_flags = 69 | ${env.build_flags} 70 | -D HAST_RTC=INTERNAL_62kHz 71 | -D TERMINAL=Serial1 72 | -D BUTTON_PIN=PA0 73 | 74 | [env:ESP8266] 75 | ; tact button gpio0 (D3) 76 | platform = espressif8266 77 | board = d1_mini 78 | build_flags = 79 | ${env.build_flags} 80 | -D BUTTON_PIN=0 81 | 82 | [env:ESP32] 83 | ; tact button gpio17 (same place as D3 on d1_mini) 84 | platform = espressif32 85 | board = mhetesp32minikit 86 | build_flags = 87 | ${env.build_flags} 88 | -D BUTTON_PIN=17 89 | -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | PowerLogger 3 | DIY multi-channel voltage/current data logger 4 | https://github.com:avaldebe/PowerLogger/ 5 | 6 | Based on 7 | 8 | - DisplayReadings example fom INA library 9 | https://github.com/SV-Zanshin/INA 10 | 11 | - QuickStart example form the SdFat library 12 | https://github.com/greiman/SdFat 13 | 14 | - Object example on the CircularBuffer library 15 | https://github.com/rlogiacco/CircularBuffer 16 | 17 | - Terminal example from the U8g2 library 18 | https://github.com/olikraus/u8g2/ 19 | 20 | - BlinkMachine example from the OneButton library 21 | https://github.com/mathertel/OneButton 22 | 23 | - Unit Testing of a “Blink” Project 24 | http://docs.platformio.org/en/latest/tutorials/core/unit_testing_blink.html 25 | 26 | - STM32F103 low power settings 27 | http://github.com/cbm80amiga/N5110_STM32_power_consumption 28 | */ 29 | 30 | #include 31 | #include "config.h" // project configuration 32 | #include // secret sauce ;-) 33 | 34 | #include 35 | SdFat SD; // File system object. 36 | File CSV; // File object for filename 37 | 38 | #include 39 | #ifdef HAST_RTC 40 | #define FILENAME rtc_fmt('C') // 'YYMMDD.csv' 41 | #else 42 | #define FILENAME "INA.csv" 43 | #endif 44 | 45 | #include 46 | void sd_dump(); 47 | 48 | #include 49 | #ifndef BUTTON_PIN 50 | #error "Undefined BUTTON_PIN flag. Try with -D BUTTON_PIN=3" 51 | #endif 52 | OneButton button(BUTTON_PIN, true); // with INPUT_PULLUP 53 | 54 | bool recording = false; // write to SD (or not) 55 | void recording_toggle(); 56 | void safe_shutdown(); 57 | void backlight_toggle(); 58 | 59 | #ifdef __STM32F1__ 60 | #include 61 | #include 62 | #endif 63 | void low_power(); 64 | 65 | void setup() 66 | { 67 | low_power(); 68 | #ifdef LED_BUILTIN 69 | pinMode(LED_BUILTIN, OUTPUT); // blink LED with activity 70 | #endif 71 | rtc_init(); // set RTC if needed 72 | #ifdef SHORTPRESS 73 | button.setClickTicks(SHORTPRESS); // single press duration [ms] 74 | #endif 75 | #ifdef LONGPRESS 76 | button.setPressTicks(LONGPRESS); // long press duration [ms] 77 | #endif 78 | button.attachClick(recording_toggle); // pause/resume buffering 79 | button.attachPress(safe_shutdown); // dump buffen and power down 80 | #ifdef BACKLIGHT_PIN 81 | button.attachDoubleClick(backlight_toggle); // switch backlight on/off 82 | // display backlight attached/controlled by BACKLIGHT_PIN 83 | pinMode(BACKLIGHT_PIN, OUTPUT); 84 | digitalWrite(BACKLIGHT_PIN, LOW); // backlight off 85 | #else 86 | button.attachDoubleClick(TERMINAL_toggle); // switch display on/off 87 | #endif 88 | 89 | TERMINAL_begin(); // start TERMINAL 90 | 91 | // 1st splash page 92 | TERMINAL.println(F("PowerLogger")); 93 | #ifdef GIT_REV 94 | TERMINAL.print(F("\nRev:\n" GIT_REV "\n")); 95 | #endif 96 | TERMINAL.print(F("\nBuild:\n" __DATE__ "\n" __TIME__ "\n")); 97 | TERMINAL_clear(2000); // wait 2 sec before continuing 98 | 99 | // 2nd splash page 100 | TERMINAL.print(F("RTC @")); 101 | TERMINAL.println(rtc_now()); 102 | 103 | Record::init(&TERMINAL); // init/config INA devices 104 | if (!SD.begin(SD_CS, SPI_SPEED)) 105 | { 106 | SD.initErrorHalt(&TERMINAL); // errorcode/message to TERMINAL 107 | } 108 | TERMINAL.print(F("SD: ")); 109 | TERMINAL.println(FILENAME); 110 | if (!recording) 111 | { // wait until SD is resumed 112 | TERMINAL.println(F("SD: paused")); 113 | } 114 | TERMINAL_clear(2000); // wait 2 sec before continuing 115 | } 116 | 117 | void loop() 118 | { 119 | #ifdef LED_BUILTIN 120 | digitalWrite(LED_BUILTIN, LOW); // blink LED with activity 121 | #endif 122 | static uint32_t last = 0; 123 | do 124 | { // test at least once 125 | // check for button presses until is time for a new Record 126 | button.tick(); 127 | delay(10); 128 | } while (millis() - last < FREQUENCY); 129 | last = millis(); 130 | #ifdef LED_BUILTIN 131 | digitalWrite(LED_BUILTIN, HIGH); // blink LED with activity 132 | #endif 133 | 134 | // measurements from all INA devices 135 | Record *record = new Record(last); 136 | if (!record) 137 | { 138 | TERMINAL.println(F("Out of memory")); 139 | return; 140 | } 141 | TERMINAL_home(); 142 | record->splash(&TERMINAL, display.cols, display.rows); 143 | TERMINAL.print(record->getRunTime()); 144 | TERMINAL.print(recording ? F(" R:") : F(" P:")); 145 | TERMINAL.print(buffer.size()); 146 | TERMINAL.print(F(" \n")); 147 | 148 | if (recording) 149 | { 150 | buffer.unshift(record); // buffer new record 151 | if (buffer_full()) 152 | { 153 | sd_dump(); 154 | } // dump buffer to CSV file 155 | } 156 | else 157 | { 158 | delete record; // record from buffer 159 | } 160 | } 161 | 162 | void sd_dump() 163 | { 164 | if (buffer.isEmpty()) 165 | { 166 | return; 167 | } // nothing to write 168 | 169 | rtc_now(); // new date for filename 170 | bool newfile = !SD.exists(FILENAME); 171 | CSV = SD.open(FILENAME, FILE_WRITE); 172 | if (!CSV) 173 | { 174 | SD.initErrorHalt(&TERMINAL); 175 | } // errorcode/message to TERMINAL 176 | if (newfile) 177 | { 178 | buffer.first()->header(&CSV); 179 | } 180 | while (!buffer.isEmpty()) 181 | { 182 | buffer.first()->print(&CSV); 183 | delete buffer.shift(); 184 | } 185 | CSV.close(); 186 | } 187 | 188 | void recording_toggle() 189 | { 190 | if (recording) 191 | { 192 | sd_dump(); 193 | } // dump buffer to SD card 194 | recording = not recording; // pause/resume buffering 195 | } 196 | 197 | void safe_shutdown() 198 | { 199 | TERMINAL_clear(); 200 | TERMINAL.println(F("Safe shutdown")); 201 | sd_dump(); // dump buffer to SD card 202 | recording = false; // pause buffering 203 | #ifdef HAS_SOFTPOWER 204 | TERMINAL.println(F("Powering down")); 205 | pinMode(BUTTON_PIN, OUTPUT); 206 | digitalWrite(BUTTON_PIN, LOW); // trigger shutdown circuitry 207 | #else 208 | TERMINAL.println(F("Remove power")); 209 | #endif 210 | while (true) 211 | { 212 | delay(100); 213 | } 214 | } 215 | 216 | void backlight_toggle() 217 | { 218 | #ifdef BACKLIGHT_PIN 219 | bool is_on = digitalRead(BACKLIGHT_PIN); // backlight on? 220 | is_on ^= true; // same as !is_on 221 | // display backlight attached/controlled by BACKLIGHT_PIN 222 | digitalWrite(BACKLIGHT_PIN, is_on ? HIGH : LOW); 223 | #endif 224 | } 225 | 226 | void low_power() 227 | { 228 | #ifdef __STM32F1__ 229 | // unused ADCs 230 | adc_disable_all(); 231 | // unused clocks 232 | rcc_clk_disable(RCC_ADC1); 233 | rcc_clk_disable(RCC_ADC2); 234 | rcc_clk_disable(RCC_ADC3); 235 | rcc_clk_disable(RCC_AFIO); 236 | //rcc_clk_disable(RCC_BKP); // internal RTC(?) 237 | rcc_clk_disable(RCC_CRC); 238 | rcc_clk_disable(RCC_DAC); 239 | rcc_clk_disable(RCC_DMA1); 240 | rcc_clk_disable(RCC_DMA2); 241 | rcc_clk_disable(RCC_FLITF); 242 | rcc_clk_disable(RCC_FSMC); 243 | //rcc_clk_disable(RCC_GPIOA); 244 | //rcc_clk_disable(RCC_GPIOB); 245 | //rcc_clk_disable(RCC_GPIOC); 246 | rcc_clk_disable(RCC_GPIOD); 247 | rcc_clk_disable(RCC_GPIOE); 248 | rcc_clk_disable(RCC_GPIOF); 249 | rcc_clk_disable(RCC_GPIOG); 250 | //rcc_clk_disable(RCC_I2C1); // TERMINAL, external RTC 251 | rcc_clk_disable(RCC_I2C2); 252 | //rcc_clk_disable(RCC_PWR); // internal RTC(?) 253 | rcc_clk_disable(RCC_SDIO); 254 | //rcc_clk_disable(RCC_SPI1); // TERMINAL, SD card 255 | #ifndef DISPLAY_H2_SPI 256 | rcc_clk_disable(RCC_SPI2); // TERMINAL 257 | #endif 258 | rcc_clk_disable(RCC_SPI3); 259 | rcc_clk_disable(RCC_SRAM); 260 | rcc_clk_disable(RCC_TIMER1); 261 | rcc_clk_disable(RCC_TIMER2); 262 | rcc_clk_disable(RCC_TIMER3); 263 | rcc_clk_disable(RCC_TIMER4); 264 | rcc_clk_disable(RCC_TIMER5); 265 | rcc_clk_disable(RCC_TIMER6); 266 | rcc_clk_disable(RCC_TIMER7); 267 | rcc_clk_disable(RCC_TIMER8); 268 | rcc_clk_disable(RCC_TIMER9); 269 | rcc_clk_disable(RCC_TIMER10); 270 | rcc_clk_disable(RCC_TIMER11); 271 | rcc_clk_disable(RCC_TIMER12); 272 | rcc_clk_disable(RCC_TIMER13); 273 | rcc_clk_disable(RCC_TIMER14); 274 | #ifdef TERM_U8X8 275 | rcc_clk_disable(RCC_USART1); // TERMINAL 276 | rcc_clk_disable(RCC_USART2); // TERMINAL 277 | rcc_clk_disable(RCC_USART3); // TERMINAL 278 | #endif 279 | rcc_clk_disable(RCC_UART4); 280 | rcc_clk_disable(RCC_UART5); 281 | rcc_clk_disable(RCC_USB); 282 | #endif 283 | } 284 | -------------------------------------------------------------------------------- /test/test_main.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | PowerLogger 3 | DIY multi-channel voltage/current data logger 4 | https://github.com:avaldebe/PowerLogger/ 5 | 6 | Based on 7 | 8 | - DisplayReadings example fom INA library 9 | https://github.com/SV-Zanshin/INA 10 | 11 | - QuickStart example form the SdFat library 12 | https://github.com/greiman/SdFat 13 | 14 | - Object example on the CircularBuffer library 15 | https://github.com/rlogiacco/CircularBuffer 16 | 17 | - Terminal example from the U8g2 library 18 | https://github.com/olikraus/u8g2/ 19 | 20 | - BlinkMachine esmaple from the OneButton library 21 | https://github.com/mathertel/OneButton 22 | 23 | - Unit Testing of a “Blink” Project 24 | http://docs.platformio.org/en/latest/tutorials/core/unit_testing_blink.html 25 | */ 26 | 27 | #include 28 | #include 29 | #include 30 | 31 | #include // internal/external RTC 32 | #include // secret sauce ;-) 33 | 34 | #include 35 | SdFat SD; // File system object. 36 | File TEST; // File object for filename 37 | const char *FILENAME = "test.123"; 38 | 39 | #include 40 | OneButton button(BUTTON_PIN, true); // with INPUT_PULLUP 41 | 42 | // TERMINAL for additional messages 43 | #include 44 | #if defined(TERM_U8X8) || defined(NO_TERMINAL) 45 | // TERM_U8X8: messages to DISPLAY 46 | // NO_TERMINAL: messages via TEST_ASSERT_MESSAGE 47 | #elif defined(HAVE_HWSERIAL1) || defined(__STM32F1__) || defined(ESP32) 48 | #define TERMINAL Serial1 49 | #elif defined(ESP8266) 50 | #define TERMINAL MySerial 51 | #include 52 | SoftwareSerial TERMINAL(5, 4); // RX:D1, TX:D2 53 | #else 54 | #define NO_TERMINAL 55 | #endif 56 | 57 | // PASS/FAIL messages to TERMINAL 58 | #ifdef NO_TERMINAL 59 | #define TERM(msg) 60 | #define TERM_CLEAN(ms) 61 | #define TERM_FMEM(mem) 62 | #define TERM_FAIL(fail) 63 | #define TEST_TERM(ok, msg) TEST_ASSERT_MESSAGE(ok, msg) 64 | #else 65 | #include 66 | #define TERM(msg) TERMINAL.println(msg) 67 | #define TERM_CLEAR(ms) TERMINAL_clear(ms) 68 | #define TERM_FMEM(mem) TERMINAL.print(F("MEM "));TERMINAL.println(mem, DEC) 69 | #define TERM_FAIL(fail) TERMINAL.print((fail)?F(" Fail!\n"):F("")) 70 | #define TEST_TERM(ok, msg) TERM_FAIL(!(ok)); TEST_ASSERT(ok) 71 | #endif 72 | #include // included with SdFat 73 | 74 | void test_MEM(void) { 75 | int mem = FreeStack(); 76 | TERM_FMEM(mem/8); 77 | TEST_TERM(mem>0, "Not enough memory"); 78 | } 79 | 80 | void test_INA(void) { 81 | TERM(F("INA")); 82 | 83 | TERM(F(" count")); 84 | uint8_t count = Record::init(); 85 | TEST_TERM(count>0, "INA devices found"); 86 | 87 | TERM(F(" offset")); 88 | Record record(millis()); 89 | for (uint8_t i=0; iheader(&TEST); 122 | while (!buffer.isEmpty()) { 123 | record = buffer.shift(); 124 | record->print(&TEST); 125 | delete record; 126 | } 127 | TEST.close(); 128 | 129 | TERM(F(" exists")); 130 | ok = SD.exists(FILENAME); 131 | TEST_TERM(ok, "SD.exists"); 132 | 133 | TERM(F(" remove")); 134 | ok = SD.remove(FILENAME); 135 | TEST_TERM(ok, "SD.remove"); 136 | } 137 | 138 | void test_RTC(void) { 139 | TERM(F("RTC")); 140 | #ifndef HAST_RTC 141 | TEST_IGNORE_MESSAGE("No RTC, skip test"); 142 | #else 143 | #ifdef INTERNAL_RTC 144 | TERM(F(" exists")); 145 | Wire.beginTransmission(0x68); 146 | uint8_t error = Wire.endTransmission(); 147 | TEST_TERM(error==0, "RTC not found"); 148 | #endif 149 | TERM(F(" running")); 150 | rtc_init(); // update RTC if needed 151 | bool ok = !rtc_stale(); 152 | TEST_TERM(ok, "Stale RTC"); 153 | #endif 154 | } 155 | 156 | enum Press { NoPress, SinglePress, DoublePress, LongPress } press = NoPress; 157 | void setSinglePress(void) { press = SinglePress; } 158 | void setDoublePress(void) { press = DoublePress; } 159 | void setLongPress(void) { press = LongPress; } 160 | void button_wait(uint16_t wait_ms=2000) { // 1 sec max, by default 161 | press = NoPress; 162 | button.reset(); 163 | uint32_t start = millis(); 164 | while ((press==NoPress) && (millis()-start