From 5e172c05efa81b334dd855b6cd74a555f9fdac46 Mon Sep 17 00:00:00 2001 From: Marius Unsel Date: Thu, 15 May 2025 02:32:06 +0200 Subject: [PATCH] initial commit --- README.md | 10 + SyncGrips/SyncGrips.ino | 506 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 516 insertions(+) create mode 100644 README.md create mode 100644 SyncGrips/SyncGrips.ino diff --git a/README.md b/README.md new file mode 100644 index 0000000..cf53be1 --- /dev/null +++ b/README.md @@ -0,0 +1,10 @@ +# SyncGrips Firmware +## BLE-enabled Pushup Parallettes + + +## Display Library Configuration for TTGO Dev Board + +To config the [TFT eSPI library](https://github.com/Bodmer/TFT_eSPI), you have to edit `User_Setup_Select.h` in the library location. +Comment out `#include ` and uncomment `#include `. + + diff --git a/SyncGrips/SyncGrips.ino b/SyncGrips/SyncGrips.ino new file mode 100644 index 0000000..dd51966 --- /dev/null +++ b/SyncGrips/SyncGrips.ino @@ -0,0 +1,506 @@ +#define ESP32 +//#define LEFT_DEVICE +#define DEBUG_SAMPLING + + +#include "Wire.h" +#include "HX711.h" +//Libraries for OLED Display +#include +#include +#include "esp_timer.h" +#include + +#ifdef CORE_DEBUG_LEVEL +#undef CORE_DEBUG_LEVEL +#endif + +#define CORE_DEBUG_LEVEL 2 +#define LOG_LOCAL_LEVEL ESP_LOG_INFO + +#include "esp_log.h" +#include + + +#include +#include +#include + +#ifndef TFT_DISPOFF +#define TFT_DISPOFF 0x28 +#endif + +#ifndef TFT_SLPIN +#define TFT_SLPIN 0x10 +#endif +// PIN DEFINITIONS +#define TFT_MOSI 19 +#define TFT_SCLK 18 +#define TFT_CS 5 +#define TFT_DC 16 +#define TFT_RST 23 + +#define TFT_BL 4 // Display backlight control pin +#define ADC_EN 14 // ADC_EN is the ADC detection enable port +#define ADC_PIN 34 +#define ADC_POWER 34 + +#ifdef DEBUG_SAMPLING +#define SAMPLE_INDICATOR 27 +#define SAMPLE_INDICATOR_1 26 +#endif + +#ifdef LEFT_DEVICE +static const char* TAG = "SyncGrip L"; +#define BUTTON_1 (gpio_num_t)35 +#define BUTTON_2 (gpio_num_t)0 +#else +static const char* TAG = "SyncGrip R"; +#define BUTTON_1 (gpio_num_t)35 +#define BUTTON_2 (gpio_num_t)0 +#endif + + + +TFT_eSPI tft = TFT_eSPI(135, 240); // Invoke custom library + +Preferences preferences; + +#if CONFIG_IDF_TARGET_ESP32 + #define THRESHOLD 40 /* Greater the value, more the sensitivity */ +#else //ESP32-S2 and ESP32-S3 + default for other chips (to be adjusted) */ + #define THRESHOLD 5000 /* Lower the value, more the sensitivity */ +#endif + +RTC_DATA_ATTR int bootCount = 0; +touch_pad_t touchPin; + +#define SDA_1 21 +#define SCL_1 22 + +// Module connection pins (Digital Pins) +#define CLK 22 +#define DIO 21 + +enum DeviceState {UNCONNECTED, CONNECTED, CALIBRATE}; +enum DeviceState state = UNCONNECTED; + +int old_btn_state =1 ; +int btn_state = 0; + +float reading; +float maxReading; + + +uint64_t nLoopRuns = 0; +uint64_t nSamples = 0 ; +int missedSamples =0; + +HX711 scale; + +uint32_t scaleOffset; +float scaleScaler; + +// TIME SECTION +int64_t time_since_boot; +esp_timer_handle_t button_timer; +esp_timer_handle_t sampling_timer; + +static void button_timer_callback(void* arg); +static void sampling_timer_callback(void* arg); + + +// BLE SECTION +BLEServer *pServer = NULL; + +BLECharacteristic *sensor_val_characteristic = NULL; +BLECharacteristic *bat_val_characteristic = NULL; +BLECharacteristic *sync_characteristic = NULL; +BLECharacteristic *calibrate_characteristic = NULL; + +// See the following for generating UUIDs: +// https://www.uuidgenerator.net/ + +#define SERVICE_UUID "00567f83-72b0-4f28-b7ef-1fce18766da6" +#define SENSOR_VAL_CHARACTERISTIC_UUID "082c9546-6a26-4230-a7b2-8d13fa96da55" +#define BAT_VAL_CHARACTERISTIC_UUID "53aec279-cf8f-423f-bf13-38f778696fa0" +#define SYNC_CHARACTERISTIC_UUID "f1a13d24-57f0-4264-96bf-2c97c8a37561" +#define CALIBRATE_CHARACTERISTIC_UUID "89cc5093-096a-4d3e-b7db-18a979bb81fb" + +// TIMERS +void initButtonTimer(){ + const esp_timer_create_args_t button_timer_args = { + .callback = &button_timer_callback, + /* name is optional, but may help identify the timer when debugging */ + .name = "buttonpoll" + }; + ESP_ERROR_CHECK(esp_timer_create(&button_timer_args, &button_timer)); + ESP_ERROR_CHECK(esp_timer_start_periodic(button_timer, 100000));//every 100ms +} + +void initSamplingTimer(){ + const esp_timer_create_args_t sampling_timer_args = { + .callback = &sampling_timer_callback, + .name = "sampling" + }; + ESP_ERROR_CHECK(esp_timer_create(&sampling_timer_args, &sampling_timer)); + ESP_ERROR_CHECK(esp_timer_start_periodic(sampling_timer, 105000));//every 105ms +} + +// BLE CALLBACKS +class MyServerCallbacks : public BLEServerCallbacks +{ + void onConnect(BLEServer *pServer) + { + Serial.println("Connected"); + state = CONNECTED; + }; + + void onDisconnect(BLEServer *pServer) + { + state = UNCONNECTED; + Serial.println("Disconnected, starting advertising again..."); + // Start advertising + pServer->getAdvertising()->start(); + } +}; + + +float measureBattery(){ + // measures with 12 bit ADC on a voltage divider with 3.3V power supply + uint16_t readVal = analogRead(ADC_POWER); + float vBat = 2.*3.3*((0.0+readVal) / 4096.0); + ESP_LOGE(TAG, "Measure BAT: %d %fV", readVal, vBat); + return vBat; +} + +int batteryPercent(float vBat){ + return int(100*(vBat-3.3)/(4.2-3.3)); +} + +bool screenon = true; + +void toggleScreen(){ + screenon = !screenon; + if (screenon){ + digitalWrite(TFT_BL, TFT_BACKLIGHT_ON); + } + else { + digitalWrite(TFT_BL, !TFT_BACKLIGHT_ON); + } +} + +void tare(){ + state = CALIBRATE; + tft.fillScreen(TFT_BLACK); + tft.drawString("TARE", 120,32); + scale.tare(20); + uint32_t offset = scale.get_offset(); + preferences.begin("calibration", false); //false for r/w mode instead of just r + preferences.putULong("offset", offset); + preferences.end(); + tft.fillScreen(TFT_BLACK); + tft.drawString("DONE", 120,32); + delay(500); + state = CONNECTED; + Serial.println("DONE TARE"); +} + +void calibrate(int weightGrams){ + state = CALIBRATE; + tft.fillScreen(TFT_BLACK); + tft.drawString("calibrate", 120,32); + scale.calibrate_scale(weightGrams, 20); + float scaler = scale.get_scale(); + preferences.begin("calibration", false); //false for r/w mode instead of just r + preferences.putFloat("scale", scaler); + preferences.end(); + tft.fillScreen(TFT_BLACK); + tft.drawString("DONE", 120,32); + delay(500); + state = CONNECTED; +} + +class BatteryCharacteristicCallbacks: public BLECharacteristicCallbacks { + void onWrite(BLECharacteristic *pCharacteristic) { + float batVal = measureBattery(); + int batPercent = batteryPercent(batVal); + bat_val_characteristic->setValue(String(batPercent).c_str()); + bat_val_characteristic->notify(); + } +}; + +class SyncCharacteristicCallbacks: public BLECharacteristicCallbacks { + void onWrite(BLECharacteristic *pCharacteristic) { + // TODO handle SYNC event + std::string value = pCharacteristic->getValue(); + uint32_t int_val = (uint32_t) value.c_str(); + state = CALIBRATE; + ESP_ERROR_CHECK(esp_timer_stop(sampling_timer)); + #ifdef LEFT_DEVICE + delay(1000); + #else + delay(532); // measured 32ms offset + #endif + tft.fillScreen(TFT_RED); + delay(200); + nSamples = 0; + missedSamples = 0; + state = CONNECTED; + ESP_ERROR_CHECK(esp_timer_start_periodic(sampling_timer, 105000));//every 105ms + } +}; + +class CalibrateCharacteristicCallbacks: public BLECharacteristicCallbacks { + void onWrite(BLECharacteristic *pCharacteristic) { + state = CALIBRATE; + Serial.println("SHOW ME THE CALIBRATE"); + tft.fillScreen(TFT_RED); + delay(200); + int nGrams = atoi(pCharacteristic->getValue().c_str()); + if(nGrams == 0) { + tare(); + } + if(nGrams > 0) { + calibrate(nGrams); + } + state=CONNECTED; + } +}; + +class CharacteristicsCallbacks : public BLECharacteristicCallbacks { + void onWrite(BLECharacteristic *pCharacteristic) { + std::string value = pCharacteristic->getValue(); + uint32_t int_val = (uint32_t) value.c_str(); + Serial.println("some value arrived"); + tft.fillScreen(TFT_RED); + delay(2000); + } +}; + + +void drawReading(float reading, float maxReading) { + String out = String(reading<0?0:reading)+"kg"; + String out1 = String(maxReading)+"kg"; + tft.fillScreen(TFT_BLACK); + tft.drawString(out, 120, 32); + tft.drawString(out1, 120, 84); +} + +void drawDebugReading(float reading, int missedCount) { + String out = String(reading<0?0:reading)+"kg"; + String out1 = String(missedCount)+" "+ (state==UNCONNECTED ? "NC": "C"); + tft.fillScreen(TFT_BLACK); + tft.drawString(out, 120, 32); + tft.drawString(out1, 120, 84); +} + + +void initScale(){ + scale.begin(DIO, CLK); + preferences.begin("calibration", false); //false for r/w mode instead of just r + float scaler = preferences.getFloat("scale", -26.326582); + uint32_t offset = preferences.getULong("offset", 4294856599); + preferences.end(); + + scale.set_offset(offset); + scale.set_scale(scaler); +} + + +void initTFT(){ + tft.init(); + tft.setTextSize(5); + + if (TFT_BL > 0) { // TFT_BL has been set in the TFT_eSPI library in the User Setup file TTGO_T_Display.h + pinMode(TFT_BL, OUTPUT); // Set backlight pin to output mode + digitalWrite(TFT_BL, TFT_BACKLIGHT_ON); // Turn backlight on. TFT_BACKLIGHT_ON has been set in the TFT_eSPI library in the User Setup file TTGO_T_Display.h + } + #ifdef LEFT_DEVICE + tft.setRotation(1); + #else + tft.setRotation(3); + #endif + tft.fillScreen(TFT_RED); + delay(20); + tft.fillScreen(TFT_BLUE); + delay(20); + tft.fillScreen(TFT_GREEN); + delay(20); + tft.fillScreen(TFT_BLACK); + tft.setTextDatum(MC_DATUM); + + tft.drawString("Hello!", tft.width() / 2, tft.height() / 2); +} + + + + +int64_t currentReadingTimestamp; +int64_t readingStartTimestamp; + + +void setup(){ + Serial.begin(115200); + initTFT(); + //Increment boot number and print it every reboot + ++bootCount; + Serial.println("Boot number: " + String(bootCount)); + + // TOGGLE Pin for sampling timing mesurements + #ifdef DEBUG_SAMPLING + pinMode(SAMPLE_INDICATOR, OUTPUT); + pinMode(SAMPLE_INDICATOR_1, OUTPUT); + #endif + + initScale(); + + + pinMode(BUTTON_1, INPUT); + pinMode(BUTTON_2, INPUT); + + BLEDevice::init(TAG); + // Create the BLE Server + pServer = BLEDevice::createServer(); + pServer->setCallbacks(new MyServerCallbacks()); + // Create the BLE Service + BLEService *pService = pServer->createService(SERVICE_UUID); + delay(100); + + uint32_t props = BLECharacteristic::PROPERTY_READ | + BLECharacteristic::PROPERTY_WRITE | + BLECharacteristic::PROPERTY_NOTIFY | + BLECharacteristic::PROPERTY_INDICATE; + + // Create BLE Characteristics + sensor_val_characteristic = pService->createCharacteristic( + SENSOR_VAL_CHARACTERISTIC_UUID, + props); + + bat_val_characteristic = pService->createCharacteristic( + BAT_VAL_CHARACTERISTIC_UUID, + props); + + sync_characteristic = pService->createCharacteristic( + SYNC_CHARACTERISTIC_UUID, + props); + + calibrate_characteristic = pService->createCharacteristic( + CALIBRATE_CHARACTERISTIC_UUID, + props); + + + sensor_val_characteristic->setValue("0.0 0"); + + float vBat = measureBattery(); + int batPercent = batteryPercent(vBat); + /* Serial.println("VBAT: "+ String(vBat)+", "+String(batPercent)+"%"); */ + bat_val_characteristic->setValue(String(batPercent).c_str()); + //#ifdef LEFT_DEVICE + //bat_val_characteristic->setValue("7"); + //#else + //bat_val_characteristic->setValue("99"); + //#endif + + bat_val_characteristic->setCallbacks(new CharacteristicsCallbacks()); + sensor_val_characteristic->setCallbacks(new CharacteristicsCallbacks()); + sync_characteristic->setCallbacks(new SyncCharacteristicCallbacks()); + calibrate_characteristic->setCallbacks(new CalibrateCharacteristicCallbacks()); + + + // Start the BLE service + pService->start(); + + // Start advertising + pServer->getAdvertising()->start(); + + + Serial.println("Waiting for a client connection to notify..."); + tft.fillScreen(TFT_BLACK); + + //esp_sleep_enable_ext0_wakeup(GPIO_NUM_0, 0); + esp_sleep_enable_ext0_wakeup(BUTTON_1, 0); + + initButtonTimer(); + initSamplingTimer(); + time_since_boot = esp_timer_get_time(); + readingStartTimestamp = esp_timer_get_time(); + ESP_LOGI(TAG, "startup finished, time since boot: %lld us", time_since_boot); +} + + +void loop() { + nLoopRuns++; + + //time_since_boot = esp_timer_get_time(); + //ESP_LOGI(TAG, "loop called, time since boot: %lld us", time_since_boot); + + float vBat = measureBattery(); + int batPercent = batteryPercent(vBat); + //Serial.println("VBAT: "+ String(vBat)+", "+String(batPercent)+"%"); + bat_val_characteristic->setValue(String(batPercent).c_str()); + bat_val_characteristic->notify(); + delay(20000); +} + + +static void sampling_timer_callback(void* arg){ + digitalWrite(SAMPLE_INDICATOR, HIGH); + int64_t localTimestamp; + char valuesOut[20]; + + if (state == CALIBRATE) { + return; + } + nSamples = (nSamples + 1) % 3000; + + scale.wait_ready(); + if (scale.is_ready()) { + digitalWrite(SAMPLE_INDICATOR_1, HIGH); + reading = scale.get_units()/1000; + reading = reading <0 ? 0:reading; + //currentReadingTimestamp = esp_timer_get_time(); + //localTimestamp = currentReadingTimestamp - readingStartTimestamp; + sprintf(valuesOut, "%.2f %d", reading, nSamples);// localTimestamp); + // sending reading via BLE + if(state == CONNECTED){ + sensor_val_characteristic->setValue(valuesOut); + #ifdef LEFT_DEVICE + delay(10); + #endif + sensor_val_characteristic->notify(); + } + if (reading > maxReading){ maxReading=reading;} + digitalWrite(SAMPLE_INDICATOR_1, LOW); + if(screenon){ + //drawReading(reading, maxReading); + drawDebugReading(reading, missedSamples); + } + /* ESP_LOGI(TAG, "scale was ready, time since last reading: %lld us", localTimestamp); */ + } else { + missedSamples++; + drawDebugReading(reading, missedSamples); + } + digitalWrite(SAMPLE_INDICATOR, LOW); +} + +static void button_timer_callback(void* arg){ + btn_state = digitalRead(BUTTON_1); + if(btn_state && !old_btn_state){ + ESP_ERROR_CHECK(esp_timer_stop(button_timer)); + ESP_ERROR_CHECK(esp_timer_delete(button_timer)); + ESP_ERROR_CHECK(esp_timer_stop(sampling_timer)); + ESP_ERROR_CHECK(esp_timer_delete(sampling_timer)); + Serial.println("pressed sleep button"); + maxReading=0; + tft.fillScreen(TFT_BLACK); + tft.setTextDatum(MC_DATUM); + tft.drawString("Bye...", tft.width() / 2, tft.height() / 2); + delay(500); + esp_deep_sleep_start(); + //toggleScreen(); + } + old_btn_state = btn_state; + //Serial.println(nLoopRuns); + // nLoopRuns = 0; +}