본문 바로가기

ESPRESSIF/ESP32-C3

[ESP32-C3 SSM] BLE 5.0 - BLE HRM 심박수 모니터 테스트

ESP32-C3는 BLE 5.0이 내장되어 있다. BLE관련 여러 서비스 중 표준 HRM 서비스를 테스트 해보자.

 

HR 수집기는 데이터를 사용할 수 있을 때마다 HRM 특성으로부터 알림을 받는다. 측정값은 Bluetooth LE 패킷당 23바이트의 데이터로 전송되고 첫 번째 바이트는 Flag 라고 하며 데이터 형식에 대한 정보를 제공 한다.

 

  1. HR 데이터 형식 : HR 값이 UINT8 또는 UINT16 형식인지 나타내는 1비트.
  2. 센서 접촉(SC) : SC 기능 지원 여부, 지원 여부, 지원 여부를 나타내는 2비트.
  3. Energy Expended(EE) : HRM 특성에서 Energy Expended가 있음을 나타내는 1비트.
  4. RR-간격(RR) : RR-간격 측정이 HRM 특성에 존재하는지 여부를 알려주는 1비트.
  5. RFFU : 향후 사용을 위해 예약된 3비트

 

HR Value

HR 데이터의 형식이 UINT8인지 UINT16인지에 따라 각각 1바이트 또는 2바이트로 값을 읽을 수 있다. 이는 플래그 뒤에 오는 바이트부터 시작하여 수신된 데이터 패킷에서 검색할 수 있다. HR 값은 분당 심박수(bpm)이다.

 

RR 간격

 HRM 특성의 전송 간에 여러 RR 간격이 측정될 수 있으므로 RR 간격은 특성의 하위 필드에 표시. 서브 필드의 개수는 패킷의 전체 길이, HR 값의 형식, EE 존재 여부에 따라 결정. 그러므로,

  1. HR 값의 형식이 UINT8 인 경우 단일 HR 측정으로 알릴 수 있는 RR 간격 값의 최대 수
    • EE가 있는 경우 최대 RR 간격 수는 8
    • 그렇지 않으면 최대 RR 간격 수는 9

  2. HR 값의 형식이 UINT16 인 경우
    • EE가 있는 경우 최대 RR 간격 수는 7
    • 그렇지 않은 경우 최대 RR 간격 수는 8.

참고: RR 간격의 분해능은 1/1024초.

 

 

HRM 서비스를 생성하고 Charactistic을 등록해서 HRM 표준 데이터를 전송하는 ESP32-C3 BLE 코드

#include <BLEDevice.h>
#include <BLEUtils.h>
#include <BLEServer.h>
#include <BLE2902.h>
#include "math.h"

#define SERVICE_UUID        "4fafc201-1fb5-459e-8fcc-c5c9c331914b"
#define CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8"

int gBleConFlag = 0;

BLECharacteristic heartRateMeasurementCharacteristics(BLEUUID((uint16_t)0x2A37), BLECharacteristic::PROPERTY_NOTIFY);
BLECharacteristic sensorPositionCharacteristic(BLEUUID((uint16_t)0x2A38), BLECharacteristic::PROPERTY_READ);
BLEDescriptor heartRateDescriptor(BLEUUID((uint16_t)0x2901));
BLEDescriptor sensorPositionDescriptor(BLEUUID((uint16_t)0x2901));


class MyCallbacks: public BLECharacteristicCallbacks {
    void onWrite(BLECharacteristic *pCharacteristic) {
      std::string value = pCharacteristic->getValue();

      if (value.length() > 0) {
        Serial.println("*********");
        Serial.print("New value: ");
        for (int i = 0; i < value.length(); i++)
          Serial.print(value[i]);

        Serial.println();
        Serial.println("*********");
      }
    }

    void onConnect(BLEServer* pServer) {
      Serial.println("Connected!");

      gBleConFlag = 1;
      //pBpmNotifyTask->start();
    };

    void onDisconnect(BLEServer* pServer) {
      //pBpmNotifyTask->stop();

      gBleConFlag = 0;

      Serial.println("Disconnected!");
    }    
};


#define hrmServiceId BLEUUID((uint16_t)0x180D)
#define deviceInformationServiceId BLEUUID((uint16_t)0x180A)
#define batteryLevelServiceId BLEUUID((uint16_t)0x180F)

BLECharacteristic *_measurementCharacteristics = NULL;
BLECharacteristic *_sensorLocationCharacteristics = NULL;
BLECharacteristic *_controlPointCharacteristics = NULL;
BLECharacteristic *_batteryLevelCharacteristics = NULL;

void ble_init()
{
    BLEDevice::init("HRM1");
    BLEServer *pServer = BLEDevice::createServer();
    //pServer->setCallbacks(this);

    BLEService *hrmService = pServer->createService(hrmServiceId);

    _measurementCharacteristics = hrmService->createCharacteristic(
        BLEUUID((uint16_t)0x2A37), BLECharacteristic::PROPERTY_NOTIFY);

    // Client Characteristic Configuration
    _measurementCharacteristics->addDescriptor(new BLE2902());

    // Body Sensor Location
    /*
    0x00 Other
    0x01 Chest
    0x02 Wrist
    0x03 Finger
    0x04 Hand
    0x05 Ear Lobe
    0x06 Foot
    0x07–0xFF Reserved for Future Use*/
    byte sensorLocationValue[1] = { 0x01 }; // Chest
    BLECharacteristic *sensorLocation = hrmService->createCharacteristic(BLEUUID((uint16_t)0x2A38), BLECharacteristic::PROPERTY_READ);
    sensorLocation->setValue(sensorLocationValue, 1);

    // Characteristic User Description
    BLEDescriptor userDescription(BLEUUID((uint16_t)0x2901));
    userDescription.setValue("Demo 50-60 bpm");
    _measurementCharacteristics->addDescriptor(&userDescription);

    // Device Information Service - Did not help by it self.
    BLEService *deviceInformationService = pServer->createService(deviceInformationServiceId);
    BLECharacteristic *firmware = deviceInformationService->createCharacteristic(BLEUUID((uint16_t)0x2A26), BLECharacteristic::PROPERTY_READ);
    firmware->setValue("1.2.3");
    BLECharacteristic *hardware = deviceInformationService->createCharacteristic(BLEUUID((uint16_t)0x2A27), BLECharacteristic::PROPERTY_READ);
    hardware->setValue("4.5.6");
    BLECharacteristic *manufacturer = deviceInformationService->createCharacteristic(BLEUUID((uint16_t)0x2A29), BLECharacteristic::PROPERTY_READ);
    manufacturer->setValue("ACME");

    BLEService *batteryLevelService = pServer->createService(batteryLevelServiceId);
    _batteryLevelCharacteristics = batteryLevelService->createCharacteristic(BLEUUID((uint16_t)0x2A19), BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_NOTIFY);
    _batteryLevelCharacteristics->addDescriptor(new BLE2902());


    sensorLocation->setCallbacks(new MyCallbacks());

    deviceInformationService->start();
    hrmService->start();
    batteryLevelService->start();
    
    BLEAdvertising *pAdvertising = pServer->getAdvertising();
    pAdvertising->start();    
}


void setup() {
    Serial.begin(115200);
    ble_init();
}

byte flags = 0b00111110;
byte bpm;
byte heart[8] = { 0b00001110, 60, 0, 0, 0 , 0, 0, 0};
byte hrmPos[1] = {2};


int randomGen(int min, int max)
{
	int range; 
	range = max-min + 1;
	return rand()%range + min;
}

void loop() {
  if(gBleConFlag)
  {
      heart[1] = (byte)bpm;
      int energyUsed = 3000;
      heart[3] = energyUsed / 256;
      heart[2] = energyUsed - (heart[2] * 256);
      Serial.print("BPM: ");
      Serial.println(bpm);

      heartRateMeasurementCharacteristics.setValue(heart, 8);
      heartRateMeasurementCharacteristics.notify();

      sensorPositionCharacteristic.setValue(hrmPos, 1);
      bpm = (byte)randomGen(80,180);

      Serial.println(bpm);
  }
  delay(2000);
}

 

 

코드 다운로드 후 nRF ToolBox 앱에서 확인하면 정상적인 심박수가 출력 된는 것을 확인 할 수 있다.

 

반응형