# Example project for Platformio

- Connecting to Thingsboard and sending telemetry battery/rssi/bootcount via MQTT
- Sleeping ESP32 & SIM800 (~1mA consumption)
This commit is contained in:
Manolis Nikiforakis
2020-02-10 12:58:49 +02:00
parent eacd2834b0
commit 8e4eb04b47
9 changed files with 804 additions and 0 deletions
@@ -0,0 +1,5 @@
.pio
.vscode/.browse.c_cpp.db*
.vscode/c_cpp_properties.json
.vscode/launch.json
.vscode/ipch
@@ -0,0 +1,67 @@
# Continuous Integration (CI) is the practice, in software
# engineering, of merging all developer working copies with a shared mainline
# several times a day < https://docs.platformio.org/page/ci/index.html >
#
# Documentation:
#
# * Travis CI Embedded Builds with PlatformIO
# < https://docs.travis-ci.com/user/integration/platformio/ >
#
# * PlatformIO integration with Travis CI
# < https://docs.platformio.org/page/ci/travis.html >
#
# * User Guide for `platformio ci` command
# < https://docs.platformio.org/page/userguide/cmd_ci.html >
#
#
# Please choose one of the following templates (proposed below) and uncomment
# it (remove "# " before each line) or use own configuration according to the
# Travis CI documentation (see above).
#
#
# Template #1: General project. Test it using existing `platformio.ini`.
#
# language: python
# python:
# - "2.7"
#
# sudo: false
# cache:
# directories:
# - "~/.platformio"
#
# install:
# - pip install -U platformio
# - platformio update
#
# script:
# - platformio run
#
# Template #2: The project is intended to be used as a library with examples.
#
# language: python
# python:
# - "2.7"
#
# sudo: false
# cache:
# directories:
# - "~/.platformio"
#
# env:
# - PLATFORMIO_CI_SRC=path/to/test/file.c
# - PLATFORMIO_CI_SRC=examples/file.ino
# - PLATFORMIO_CI_SRC=path/to/test/directory
#
# install:
# - pip install -U platformio
# - platformio update
#
# script:
# - platformio ci --lib="." --board=ID_1 --board=ID_2 --board=ID_N
@@ -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
@@ -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 <Foo.h>
#include <Bar.h>
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
@@ -0,0 +1,23 @@
;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:win]
platform = espressif32
board = esp-wrover-kit
framework = arduino
monitor_speed = 115200
lib_deps = TinyGSM
StreamDebugger
CRC32
Thingsboard
ArduinoHttpClient
PubSubClient
ArduinoJSON
SparkFun BME280
@@ -0,0 +1,4 @@
# Example project for Platformio
- Connecting to Thingsboard and sending telemetry battery/rssi/bootcount via MQTT
- Sleeping ESP32 & SIM800 (~1mA consumption)
@@ -0,0 +1,341 @@
/*
Enclosure conditions test via TCALL SIM800 & BME280
*/
#define TINY_GSM_MODEM_SIM800
#define TINY_GSM_RX_BUFFER 1024 // Set RX buffer to 1Kb
//#define DUMP_AT_COMMANDS
#include "TinyGsmClient.h"
#include "ThingsBoard.h"
#include "SparkFunBME280.h"
#include "utilities.h"
#include "driver/adc.h"
#include <esp_wifi.h>
#define uS_TO_S_FACTOR 1000000 /* Conversion factor for micro seconds to seconds */
#define TIME_TO_SLEEP 40 /* Time ESP32 will go to sleep (in seconds) */
RTC_DATA_ATTR int bootCount = 0;
#define PIN_TX 25
#define PIN_RX 26
HardwareSerial serialGsm(1);
#define SerialAT serialGsm
#define TINY_GSM_USE_GPRS true
#define TINY_GSM_USE_WIFI false
// Your GPRS credentials
// Leave empty, if missing user or pass
const char apn[] = "internet";
const char user[] = "";
const char pass[] = "";
// TTGO T-Call pin definitions
#define MODEM_RST 5 // SIM800 RESET but also IP5306 IRQ: use IRQ Analyzing signals IP5306 It is in working condition or in standby mode: IRQ = 1 Work, IRQ = 0 When in standby
#define MODEM_PWKEY 4 // PWRKEY SIM800
#define MODEM_POWER_ON 23 // EN SY8089 4v4 regulator for SIM800
#define MODEM_TX 27
#define MODEM_RX 26
#define I2C_SDA 21
#define I2C_SCL 22
#define ADC_BAT 35 // TCALL 35
int bat_mv = 0;
BME280 mySensor;
bool isEnvSensor = true;
void shutdown();
void getBatteryFromADC();
#ifdef DUMP_AT_COMMANDS
#include "StreamDebugger.h"
StreamDebugger debugger(serialGsm, Serial);
TinyGsm modem(debugger);
#else
TinyGsm modem(serialGsm);
#endif
// See https://thingsboard.io/docs/getting-started-guides/helloworld/
// to understand how to obtain an access token
#define TOKEN "xxxxxxxxxxxxxxxxxxx" // Thingsboard token
#define THINGSBOARD_SERVER "192.168.1.1" // Thingsboard server
// Baud rate for debug serial
#define SERIAL_DEBUG_BAUD 115200
// Initialize GSM client
TinyGsmClient client(modem);
// Initialize ThingsBoard instance
ThingsBoard tb(client);
// Set to true, if modem is connected
bool modemConnected = false;
void print_wakeup_reason()
{
esp_sleep_wakeup_cause_t wakeup_reason;
wakeup_reason = esp_sleep_get_wakeup_cause();
switch (wakeup_reason)
{
case ESP_SLEEP_WAKEUP_EXT0:
Serial.println("Wakeup caused by external signal using RTC_IO");
break;
case ESP_SLEEP_WAKEUP_EXT1:
Serial.println("Wakeup caused by external signal using RTC_CNTL");
break;
case ESP_SLEEP_WAKEUP_TIMER:
Serial.println("Wakeup caused by timer");
break;
case ESP_SLEEP_WAKEUP_TOUCHPAD:
Serial.println("Wakeup caused by touchpad");
break;
case ESP_SLEEP_WAKEUP_ULP:
Serial.println("Wakeup caused by ULP program");
break;
default:
Serial.printf("Wakeup was not caused by deep sleep: %d\n", wakeup_reason);
break;
}
}
void GSM_ON(uint32_t time_delay)
{
// Set-up modem reset, enable, power pins
pinMode(MODEM_PWKEY, OUTPUT);
pinMode(MODEM_POWER_ON, OUTPUT);
pinMode(MODEM_RST, OUTPUT);
Serial.println("MODEM_RST & IP5306 IRQ: HIGH"); // IP5306 HIGH
digitalWrite(MODEM_RST, HIGH);
delay(time_delay);
Serial.println("MODEM_PWKEY: HIGH");
digitalWrite(MODEM_PWKEY, HIGH); // turning modem OFF
delay(time_delay);
Serial.println("MODEM_POWER_ON: HIGH");
digitalWrite(MODEM_POWER_ON, HIGH); //Enabling SY8089 4V4 for SIM800 (crashing when in battery)
delay(time_delay);
Serial.println("MODEM_PWKEY: LOW");
digitalWrite(MODEM_PWKEY, LOW); // turning modem ON
delay(time_delay);
}
void GSM_OFF()
{
pinMode(MODEM_PWKEY, OUTPUT);
pinMode(MODEM_POWER_ON, OUTPUT);
pinMode(MODEM_RST, OUTPUT);
digitalWrite(MODEM_PWKEY, HIGH); // turn of modem in case its ON from previous state
digitalWrite(MODEM_POWER_ON, LOW); // turn of modem psu in case its from previous state
digitalWrite(MODEM_RST, HIGH); // Keep IRQ high ? (or not to save power?)
}
void PowerManagment(uint32_t time_delay)
{
Wire.begin(I2C_SDA, I2C_SCL);
delay(100);
bool isOk = setPowerBoostKeepOn(true); // Disables auto-standby, to keep 5V high during ESP32 sleep after 32Sec
// Set console baud rate
Serial.begin(SERIAL_DEBUG_BAUD);
Serial.println(F("Started"));
Serial.println(String("IP5306 setPowerBoostKeepOn: ") + (isOk ? "OK" : "FAIL"));
Serial.println(String("IP5306 Battery level:") + getBatteryLevel());
getBatteryFromADC();
}
void setup()
{
Serial.begin(115200);
//Increment boot number and print it every reboot
++bootCount;
Serial.println("Boot number: " + String(bootCount));
//Print the wakeup reason for ESP32
print_wakeup_reason();
/*
First we configure the wake up source
We set our ESP32 to wake up every 5 seconds
*/
esp_sleep_enable_timer_wakeup(TIME_TO_SLEEP * uS_TO_S_FACTOR);
Serial.println("Setup ESP32 to sleep for " + String(TIME_TO_SLEEP) +
" Seconds");
PowerManagment(100);
if (bootCount == 1)
{
Serial.println("Going to sleep now, will skip GSM this time to confirm wake after 32s");
esp_deep_sleep_start();
}
}
void loop()
{
Serial.println("In loop");
GSM_ON(1000);
// Set GSM module baud rate and UART pins
SerialAT.begin(115200, SERIAL_8N1, MODEM_RX, MODEM_TX);
delay(3000);
String modemInfo = modem.getModemInfo();
Serial.print(F("Modem: "));
Serial.println(modemInfo);
isEnvSensor = true;
mySensor.setI2CAddress(0x76); //purple modules are at 0x76, default 0x77
if (mySensor.beginI2C() == false)
{ //Begin communication over I2C
Serial.println("The sensor did not respond. Please check wiring.");
isEnvSensor = false;
}
if (isEnvSensor)
{
Serial.print("Humidity: ");
Serial.print(mySensor.readFloatHumidity(), 0);
Serial.print(" Pressure: ");
Serial.print(mySensor.readFloatPressure(), 0);
Serial.print(" Alt: ");
//Serial.print(mySensor.readFloatAltitudeMeters(), 1);
Serial.print(mySensor.readFloatAltitudeFeet(), 1);
Serial.print(" Temp: ");
Serial.println(mySensor.readTempC(), 1);
}
Serial.print("GSM Battery: ");
Serial.print(modem.getBattPercent());
Serial.print("% ");
Serial.print(modem.getBattVoltage());
Serial.println("mV");
Serial.println(String("IP5306 Battery level:") + getBatteryLevel());
getBatteryFromADC();
delay(1000);
if (!modemConnected)
{
Serial.print(F("Waiting for network..."));
if (!modem.waitForNetwork(240000L))
{
Serial.println(" fail");
shutdown();
}
Serial.println(" OK");
Serial.print("Signal quality:");
Serial.println(modem.getSignalQuality());
Serial.print(F("Connecting to "));
Serial.print(apn);
if (!modem.gprsConnect(apn, user, pass))
{
Serial.println(" fail");
shutdown();
}
modemConnected = true;
Serial.println(" OK");
}
if (!tb.connected())
{
// Connect to the ThingsBoard
Serial.print("Connecting to: ");
Serial.print(THINGSBOARD_SERVER);
Serial.print(" with token ");
Serial.println(TOKEN);
if (!tb.connect(THINGSBOARD_SERVER, TOKEN))
{
Serial.println("Failed to connect");
shutdown();
}
}
Serial.println("Sending data...");
// Uploads new telemetry to ThingsBoard using MQTT.
// See https://thingsboard.io/docs/reference/mqtt-api/#telemetry-upload-api
// for more details
if (tb.sendAttributeInt("bootCount", bootCount))
Serial.println("send bootCount");
if (isEnvSensor)
{
if (tb.sendTelemetryInt("d_temp", mySensor.readTempC()))
Serial.println("send d_temp");
if (tb.sendTelemetryInt("d_hum", (int)mySensor.readFloatHumidity()))
Serial.println("d_hum");
if (tb.sendTelemetryInt("d_pres", (int)mySensor.readFloatPressure()))
Serial.println("d_press");
}
if (tb.sendTelemetryInt("d_bat_p", getBatteryLevel()))
Serial.println("d_bat_p");
if (tb.sendTelemetryInt("d_bat_mv", bat_mv))
Serial.println("d_bat_mv");
if (tb.sendTelemetryInt("d_gsm_rssi", modem.getSignalQuality())) //CSQ need to convert to RSSI
Serial.println("d_gsm_rssi");
delay(10000); //GSM post seems aynchronous?, need to give it some time
shutdown();
}
void shutdown()
{
modemConnected = false;
Serial.println(F("GPRS disconnect"));
modem.gprsDisconnect();
Serial.println("Radio off");
modem.radioOff();
Serial.println("GSM power off");
GSM_OFF();
// adc_power_off(); //soposed to save power ?
Serial.println("Going to sleep now");
Serial.flush();
esp_deep_sleep_start();
}
void getBatteryFromADC()
{
bat_mv = 0;
uint32_t oversample = 0;
for (size_t i = 0; i < 100; i++)
{
oversample += (uint32_t)analogRead(ADC_BAT);
}
bat_mv = (int)oversample / 100;
bat_mv = ((float)bat_mv / 4096) * 3600 * 2;
Serial.print("Battery from ADC: ");
Serial.print(bat_mv);
Serial.println("mV");
}
@@ -0,0 +1,268 @@
// ================ Power IC IP5306 ===================
#define IP5306_ADDR 0x75
#define IP5306_REG_SYS_CTL0 0x00
#define IP5306_REG_SYS_CTL1 (0x01)
#define IP5306_REG_SYS_CTL2 (0x02)
#define IP5306_REG_READ0 (0x70)
#define IP5306_REG_READ1 (0x71)
#define IP5306_REG_READ3 (0x78)
//- REG_CTL0
#define BOOST_ENABLE_BIT (0x20)
#define CHARGE_OUT_BIT (0x10)
#define BOOT_ON_LOAD_BIT (0x04)
#define BOOST_OUT_BIT (0x02)
#define BOOST_BUTTON_EN_BIT (0x01)
//- REG_CTL1
#define BOOST_SET_BIT (0x80)
#define WLED_SET_BIT (0x40)
#define SHORT_BOOST_BIT (0x20)
#define VIN_ENABLE_BIT (0x04)
//- REG_CTL2
#define SHUTDOWNTIME_MASK (0x0c)
#define SHUTDOWNTIME_64S (0x0c)
#define SHUTDOWNTIME_32S (0x04)
#define SHUTDOWNTIME_16S (0x08)
#define SHUTDOWNTIME_8S (0x00)
//- REG_READ0
#define CHARGE_ENABLE_BIT (0x08)
//- REG_READ1
#define CHARGE_FULL_BIT (0x08)
//- REG_READ2
#define LIGHT_LOAD_BIT (0x20)
#define LOWPOWER_SHUTDOWN_BIT (0x01)
#define IP5306_SYS_CTRL0 0x00
#define IP5306_SYS_CTRL1 0x01
#define IP5306_SYS_CTRL2 0x02
#define IP5306_CHARGER_CTRL0 0x20
#define IP5306_CHARGER_CTRL1 0x21
#define IP5306_CHARGER_CTRL3 0x22
#define IP5306_CHARGER_CTRL4 0x23
#define IP5306_DIG_CTRL0 0x24
#define IP5306_CHARGER_STATUS0 0x70
#define IP5306_CHARGER_STATUS1 0x71
#define IP5306_CHARGER_STATUS2 0x72
#define IP5306_KEY_CTRL 0x77
//! Error Code
#define IP5306_PASS 0
#define IP5306_FAIL -1
#define IP5306_INVALID -2
#define IP5306_NOT_INIT -3
bool setPowerBoostKeepOn(bool en)
{
Wire.beginTransmission(IP5306_ADDR);
Wire.write(IP5306_REG_SYS_CTL0);
if (en)
{
Wire.write(0b110111);
// 0x37 = 0b110111 TCALL example
/*
[1] Boost EN (default 1) [EXM note: if 0 ESP32 will not boot from battery]
[1] Charger EN (1) [EXM note: did not observe difference]
[1] Reserved (1) [EXM note: did not observe difference]
[1] Insert load auto power EN (1) [EXM note: did not observe difference]
[1] BOOST output normally open ( 1) [EXM note: if 0 will shutdown on ESP32 sleep after 32s]
[1] Key off EN: (0) [EXM note: could not detect difference]
*/
}
else
{
// Wire.write(0x35); // x35 => Autoshutdown - Will kill power of ESP32 while sleeping!
//HEX 35 = 0b110101
}
return Wire.endTransmission() == 0;
}
bool setOtherPower1(bool en)
{
if (en)
{
Wire.beginTransmission(IP5306_ADDR);
Wire.write(IP5306_REG_SYS_CTL1);
Wire.write(0x1D); // Set HEX:1D DEC:29 BIN:11101
/*
[1] Turn off boost control signal selection: short press twice
[1] Switch WLED flashlight control signal selection: short press twice
[1] Short press switch boost: disabled
[0] Whether to turn on Boost after VIN is pulled out: opened
[1] Batlow 3.0V Low Power Shutdown EN: enabled
*/
return Wire.endTransmission() == 0;
}
else
return false;
}
bool setOtherPower2(bool en)
{
if (en)
{
Wire.beginTransmission(IP5306_ADDR);
Wire.write(IP5306_REG_SYS_CTL2);
Wire.write(0x64); // Set HEX:64 DEC:100 BIN:1100100
return Wire.endTransmission() == 0;
}
else
return false;
}
// bool setPowerBoostKeepOn(bool en){
// uint8_t data;
// Wire.beginTransmission(IP5306_ADDR);
// Wire.write(IP5306_REG_SYS_CTL0);
// Wire.endTransmission();
// if(Wire.requestFrom(IP5306_ADDR, 1))
// {
// data = Wire.read();
// Wire.beginTransmission(IP5306_ADDR);
// Wire.write(IP5306_REG_SYS_CTL0);
// if (en) Wire.write(data | BOOST_OUT_BIT);
// else Wire.write(data &(~BOOST_OUT_BIT));
// Wire.endTransmission();
// return true;
// }
// return false;
// }
int8_t getBatteryLevel()
{
Wire.beginTransmission(IP5306_ADDR);
Wire.write(0x78);
if (Wire.endTransmission(false) == 0 && Wire.requestFrom(0x75, 1))
{
switch (Wire.read() & 0xF0)
{
case 0xE0:
return 25;
case 0xC0:
return 50;
case 0x80:
return 75;
case 0x00:
return 100;
default:
return 0;
}
}
return -1;
}
int readByte(uint8_t reg, uint8_t nbytes, uint8_t *data)
{
Wire.beginTransmission(IP5306_ADDR);
Wire.write(reg);
Wire.endTransmission();
Wire.requestFrom(IP5306_ADDR, nbytes);
uint8_t index = 0;
while (Wire.available())
data[index++] = Wire.read();
}
/*
int setBoostEnable(bool en);
int setChargerEnable(bool en);
int setPlugLoadOpened(bool en);
int setBoostNormallyOpen(bool en);
int setButtonPowerOffEnable(bool en);
int setShutdownOption(uint8_t arg);
int setLedControl(uint8_t arg);
int setShortPressBoost(bool en);
int setVinRemoveBoostOn(bool en);
int setLowBatteryShutdown(bool en);
int setLightLoadShutdownTime(uint8_t arg);
int setChargerFullVoltage(uint8_t arg);
int setBatteryStopChargerCurrent(uint8_t arg);
int setChargerVoltage(uint8_t arg);
int setBatteryVoltage(uint8_t arg);
int setConstantPressureCharging(uint8_t arg);
int setChargerLoopSeletc(uint8_t arg);
int setChargerCurrent(uint8_t arg);
bool isChargerEnable();
bool isChargerFull();
bool isHeavyLoad();
bool isButtonDouble();
bool isButtonLongPress();
bool isButtonShortPress();
0.423s INFO [IP5306] Register APP_BATTERY_IP5306_REG_SYS_CTL0 (0x00) value: 0x30
0.425s INFO [IP5306] Boost EN: enabled
0.430s INFO [IP5306] Charger EN: enabled
0.436s INFO [IP5306] Insert load auto power EN: disabled
0.443s INFO [IP5306] BOOST output normally open: disabled
0.450s INFO [IP5306] Key off EN: disabled
0.457s INFO [IP5306] Register APP_BATTERY_IP5306_REG_SYS_CTL1 (0x01) value: 0x1d
0.461s INFO [IP5306] Turn off boost control signal selection: short press twice
0.473s INFO [IP5306] Switch WLED flashlight control signal selection: short press twice
0.491s INFO [IP5306] Short press switch boost: disabled
0.498s INFO [IP5306] Whether to turn on Boost after VIN is pulled out: opened
0.507s INFO [IP5306] Batlow 3.0V Low Power Shutdown EN: enabled
0.511s INFO [IP5306] Register APP_BATTERY_IP5306_REG_SYS_CTL2 (0x02) value: 0x64
0.532s INFO [IP5306] Light load shutdown time setting: 32s
0.540s INFO [IP5306] Register APP_BATTERY_IP5306_REG_CHARG_CTL0 (0x20) value: 0x01
0.548s INFO [IP5306] Charging full stop setting: 4.17/4.275/4.32/4.365
0.571s INFO [IP5306] Register APP_BATTERY_IP5306_REG_CHARG_CTL1 (0x21) value: 0x89
0.574s INFO [IP5306] Battery end charge current detection: 500mA
0.581s INFO [IP5306] Charge undervoltage loop setting: 4.55
0.591s INFO [IP5306] Register APP_BATTERY_IP5306_REG_CHARG_CTL2 (0x22) value: 0x02
0.599s INFO [IP5306] Battery voltage setting: 4.2V
0.611s INFO [IP5306] Constant voltage charging voltage pressurization setting: 28mV
0.627s INFO [IP5306] Register APP_BATTERY_IP5306_REG_CHG_DIG_CTL0 (0x24) value: 0xd6
0.636s INFO [IP5306] Register APP_BATTERY_IP5306_REG_READ0 (0x70) value: 0x99
0.644s INFO [IP5306] Charging enable flag: Charging on
0.651s INFO [IP5306] Register APP_BATTERY_IP5306_REG_READ1 (0x71) value: 0x70
0.660s INFO [IP5306] Full of flags: Still charging
0.661s INFO [IP5306] Register APP_BATTERY_IP5306_REG_READ2 (0x72) value: 0x61
0.675s INFO [IP5306] Output light load flag: heavy load
0.683s INFO [IP5306] Register APP_BATTERY_IP5306_REG_READ3 (0x77) value: 0x83
0.690s INFO [IP5306] KEY button double click: 0
0.697s INFO [IP5306] KEY button long press: 1
0.703s INFO [IP5306] KEY button short press: 1
0.710s INFO [IP5306] Register APP_BATTERY_IP5306_REG_LEVEL (0x78) value: 0x00
*/
@@ -0,0 +1,11 @@
This directory is intended for PIO 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 PIO Unit Testing:
- https://docs.platformio.org/page/plus/unit-testing.html