Last active
August 17, 2023 21:46
-
-
Save joshnuss/52a2dae98c4037ef70954432ee798fda to your computer and use it in GitHub Desktop.
Bluetooth Speedometer using ESP32 and Hall effect sensor
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* Using digital hall effect sensor SENS-M-10 (purchased at Abra) | |
* | |
* MCU Board: ESP2-WROOM-32 | |
*/ | |
#include <BLEDevice.h> | |
#include <BLEUtils.h> | |
#include <BLEServer.h> | |
#define MEASURE_PIN 23 | |
#define LED_BUILTIN 2 | |
#define WHEEL_CIRCUMFERANCE 0.7 | |
#define SAMPLES 10 | |
#define SERVICE_UUID "5aad4ed0-a7dd-46b2-965b-7a5a159b07a2" | |
#define CHARACTERISTIC_RPM_UUID "69758442-6da8-497c-a29c-48b2c48d6ceb" | |
typedef struct { | |
uint16_t ms; | |
uint16_t revolutions; | |
} sample_t; | |
class RPMCallbacks : public BLECharacteristicCallbacks { | |
private: | |
double* pRps; | |
public: | |
RPMCallbacks(double* pRps) { | |
this->pRps = pRps; | |
} | |
void onRead(BLECharacteristic *pCharacteristic) { | |
double rpm = *pRps * 60; | |
pCharacteristic->setValue(std::to_string(rpm)); | |
} | |
}; | |
volatile uint8_t nextSample = 0; | |
volatile sample_t samples[SAMPLES] = {}; | |
hw_timer_t * timer = NULL; | |
// temp vars for computing totals | |
volatile sample_t* pSample; | |
uint8_t n; | |
uint8_t totalRevolutions; | |
uint8_t totalSamples; | |
uint16_t minMs; | |
uint16_t maxMs; | |
double rps; | |
// interrupt handler is triggered every 100ms | |
void IRAM_ATTR timer_interrupt() { | |
// compute total revolutions and revolutions per second | |
totalRevolutions = 0; | |
totalSamples = 0; | |
minMs = 0; | |
maxMs = 0; | |
rps = 0; | |
// iterate thru samples | |
for (int i=0;i<SAMPLES;i++) { | |
n = (nextSample+i) % SAMPLES; | |
pSample = &samples[n]; | |
if (pSample->ms > 0) { | |
// good sample | |
totalSamples++; | |
totalRevolutions += pSample->revolutions; | |
minMs = std::min(minMs, (uint16_t)pSample->ms); | |
maxMs = std::max(maxMs, (uint16_t)pSample->ms); | |
} | |
} | |
// compute rps | |
if (totalSamples > 0) { | |
rps = totalRevolutions * 1.0 / totalSamples; | |
} | |
// prepare for a new sample | |
if (nextSample >= SAMPLES-1) { | |
nextSample = 0; | |
} else { | |
nextSample++; | |
} | |
// clear old sample data | |
samples[nextSample].ms = millis(); | |
samples[nextSample].revolutions = 0; | |
} | |
// interrupt handler is triggered when magnet enters or leaves field | |
void magnet_detection_changed() { | |
// check if magnet is nearby | |
if (digitalRead(MEASURE_PIN) == HIGH) { | |
// increase revolutions | |
samples[nextSample].revolutions++; | |
// turn on built-in LED | |
digitalWrite(LED_BUILTIN, LOW); | |
} else { | |
// turn off built-in LED | |
digitalWrite(LED_BUILTIN, HIGH); | |
} | |
} | |
void setup() { | |
Serial.begin(115200); | |
pinMode(MEASURE_PIN, INPUT); | |
pinMode(LED_BUILTIN, OUTPUT); | |
// prescaler 80 is for 80MHz clock. One tick of the timer is 1us | |
timer = timerBegin(0, 80, true); | |
// attach interrupt handler | |
timerAttachInterrupt(timer, &timer_interrupt, true); | |
// trigger timer every 100ms (100,000us) | |
timerAlarmWrite(timer, 100000, true); | |
// enable the timer | |
timerAlarmEnable(timer); | |
// trigger function when hall effect sensor changes (goes low->high or high->low) | |
attachInterrupt(MEASURE_PIN, magnet_detection_changed, CHANGE); | |
// setup BLE device | |
BLEDevice::init("Speedometer"); | |
BLEServer *pServer = BLEDevice::createServer(); | |
BLEService *pService = pServer->createService(SERVICE_UUID); | |
// RPM characteristic | |
BLECharacteristic *pRPMCharacteristic = pService->createCharacteristic( | |
CHARACTERISTIC_RPM_UUID, | |
BLECharacteristic::PROPERTY_READ | |
); | |
pRPMCharacteristic->setCallbacks(new RPMCallbacks(&rps)); | |
pService->start(); | |
// BLEAdvertising *pAdvertising = pServer->getAdvertising(); // this still is working for backward compatibility | |
BLEAdvertising *pAdvertising = BLEDevice::getAdvertising(); | |
BLEAdvertisementData advertisementData = BLEAdvertisementData(); | |
advertisementData.setName("Speedometer"); | |
pAdvertising->setAdvertisementData(advertisementData); | |
pAdvertising->addServiceUUID(SERVICE_UUID); | |
pAdvertising->setScanResponse(true); | |
pAdvertising->setMinPreferred(0x06); // functions that help with iPhone connections issue | |
pAdvertising->setMinPreferred(0x12); | |
BLEDevice::startAdvertising(); | |
} | |
void loop() { | |
// output calculations for debug | |
Serial.print("samples="); | |
Serial.print(totalSamples); | |
Serial.print(", revolutions="); | |
Serial.print(totalRevolutions); | |
Serial.print(", rps="); | |
Serial.print(rps); | |
Serial.print(", rpm="); | |
Serial.print(rps * 60); | |
Serial.print(", speed="); | |
Serial.print(rps * 60 * WHEEL_CIRCUMFERANCE); | |
Serial.println(); | |
delay(1000); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
I loaded this on my ESP-WROOM-32 and it worked to connect and read data fine (thanks for this). However it would not allow reconnecting after a disconnect without restarting the device. Found a suggestion on another forum to add a server callback for onDisconnect to start the advertising again which seems to resolve the problem:
and register the callback just after createServer():
pServer->setCallbacks(new MyServerCallbacks());