CAFE

영상처리및제어

[제어] HM-10 (BLE 4.0) 과 PC 연결 (동글)

작성자한창호|작성시간18.10.11|조회수9,212 목록 댓글 0

HM-10과 동글이를 연결하여 PC에서 데이터를 송수신해보자. 먼저 HM-10의 특성인 BLE 4.0에 대해 알아보고, App을 이용하여 연결을 테스트한 후 PC와 연동을 해보자.

 

HM-10 특징

이것은 Bluetooth Low Energy (BLE 4.0) 모델이다. 따라서 기존의 Bluetooth classic과는 사용법이 다르다.

HM-10 명령 참고: http://www.martyncurrey.com/hm-10-bluetooth-4ble-modules/

 

AT 명령어 입력  Response 설명
  AT OK

  AT+BAUD? OK+BAUD[para] 0: 9600 (default)
1: 19200
2: 38400
3: 57600
4: 115200
5: 4800
6: 2400
7: 1200
8: 230400
 AT+MODE?
 AT+MODE[para]
 OK+Get:[para1]
 0: Transmission Mode
 1: PIO Collection Mode + Transmission
 2: Remote Control Mode + Transmission
0 : 무선연결이 이뤄지기 전에 UART(Serial 통신)를 통해서만 AT 커맨드를 전송할 수 있다.
1 : 무선으로 연결된 자잋에서 보낸 AT 커맨드를 인식하며, PIO2~PIO3을 OUTPUT용도로 사용이 가능함. 즉, PIO2핀의 3.3V 출력을 원격으로 on/off 제어할 수 있음. HM-10은 나머지 PIO4 ~ PIO11핀을 모두 OUTPUT 용도로 사용이 가능함.
2 : 무선으로 연결된 장치에서 보낸 AT커맨드를 인식하지만 INPUT 기능을 사용할 수 없음. PIO2~PIO11 핀을 모두 OUTPUT 용도로 사용이 가능함
 AT+ROLE?
 AT+ROLE[para1]


 OK+Get:[para1]0: Peripheral  (default) - 모드일때 검색 가능 (즉, 기기를 찾을 수 있다.)
1: Central
 AT+NAME?
 AT+NAME[para1]
 OK+NAME:[para1]
 OK+Set:[para1]
 BLE 모듈 스캔시 표시되는 이름 확인 커맨드, 모듈 이름, 12자 이내
 AT+PASS? OK+Get:[para1] 보안 설정이 필요한 경우 HM-10 모듈의 PIN코드를 설정 확인할 수 있음
PIN코드(000000~999999, default:000000)
 AT+TYPE[para1] OK+Get:[para1]0: Not need PIN code (default)
1: Pair not need PIN
2: Pair with PIN
3: Pair and Bond
 AT+RESET OK+RESET모듈 재시작 
 AT+ADTY?
 AT+ADTY[para1]
 OK+Get:[para1] 0: Advertising ScanResponse, connectable (블루투스 페어링 모드) - default
1: Only allow last device connect in 1.28 sec (블루투스 페어링 모드)
2: Only allow Advertising and ScanResponse (비콘ㅁ드+스캔 응답)
3: Only allow Advertising (비콘모드) 
 AT+UUID? OK+Get: [para1] 0x0001~0xFFFE  (default: FFE0) 
 AT+PWRM?

 OK+Get:[param]* Peripheral 모드에서 Sleep time 설정
0: Auto sleep
1: don't auto sleep  (default)
 AT+IMME? OK+Get:[para] * Central 모드에서 동작 타입 설정
0: when power on, work immediately
1: when module is powered on, only response the AT command , don't anything until AT+START is received, or can use AT+CON, AT+CONNL

 

HM-10 테스트 소스

 

아두이노 보드에서 hm-10을 연결 하고 앱(BLE Scanner)에서 연동해 보자.

SoftwareSerial 클래스는 연결이 안되어 Serial1을 사용하여 bluetooth에 연결했다. 다음은 속도를 자동으로 검색해 주는 소스이다.

 #include <SoftwareSerial.h>
 long baud[8]={ 4800, 9600, 14400, 19200, 28800, 38400, 57600, 115200 };
 //SoftwareSerial BTSerial(4,5);   //bluetooth module Tx:Digital 2 Rx:Digital 3


// 명령어를 보내고 Response를 받는다.
void sendCmd(char* cmd)
{
  Serial1.write(cmd);
  Serial.print(cmd);
  Serial.print("-->");
  delay(400);
  while (Serial1.available()) {  Serial.write(Serial1.read()); }
  Serial.println();
}

void setup() {
  pinMode(8, OUTPUT);    //HC-05
  digitalWrite(8,HIGH);
 
  Serial.begin(9600);

  delay(1000);
  Serial.println("Search baud rate: Send AT, wait for OK");
  
  // 여러종류의 baud를 설정하여 맞는 속도를 찾는다.
  for(int i=0;i<8;i++) { 
    Serial.print("Baud :");
    Serial.println(baud[i]);    
    Serial1.begin( baud[i] );
    delay(500);
    // AT  명령어를 2번 정도 보낸다. 간혹 기존에 명령어가 잘못 들어간 경우가 있음.
    Serial1.print("AT\r\n");
    delay(100);
    Serial1.print("AT");
    delay(100);   // 지연시간은 꼭 필요하다.
    while(Serial1.available()){
      Serial.print((char)Serial1.read());  // OK 문자를 받는다.
      }
  }  
 
  // HM-10 상태를 자동으로 점검하기
  Serial.println();
  Serial.println("HM-10 status");
  sendCmd("AT+ADDR?");
  sendCmd("AT+FILT?");
  sendCmd("AT+NAME?");
  sendCmd("AT+PASS?");
  sendCmd("AT+POWE?");
  sendCmd("AT+MODE?");
  sendCmd("AT+ROLE?");
  sendCmd("AT+ADTY?");
  sendCmd("AT+SHOW?");
  sendCmd("AT+IMME?");
  sendCmd("AT+RSSI?");
  sendCmd("AT+UUID?");
  sendCmd("AT+TYPE?");
  sendCmd("AT+DISC?");
  sendCmd("AT+PWRM?");
  sendCmd("AT+NOTP?");
  sendCmd("AT+IBEA?");
  sendCmd("AT+SAVE?");
  sendCmd("AT");
}


void loop()
{
  if (Serial1.available())
    Serial.write(Serial1.read());
  if (Serial.available())
    Serial1.write(Serial.read());
}

 

 

BLE 4.0   (https://blog-kr.zoyi.co/bluetooth-low-energy-ble/)

이전의 블루투스 스펙에서는 L2CAP, RFCOMM, SCO 같은 프로파일 바탕 위에 통신을 하기 위한 프로파일들이 특정하게 정의된 구조를 취했다. 하지만 블루투스 4.0은 다양한 장치를 지원하기 위해 새로운 프로파일을 정의하고 사용할 수 있게 하는 유연한 구조를 가지고 있다.

 

CENTRAL / PERIPHERAL

 

Central 은 scan 또는 게시검색을 수행하며, peripheral 은 게시(advertisement)를 만들어 보낸다. peripheral 은 오로지 하나의 central 장치에만 연결될 수 있다. peripheral 이 central 에 연결되면 게시(advertising)를 중단하기 때문이다.

 

GATT SERVER(SLAVE) / GATT CLIENT(MASTER)

 

GATT Client (central, 폰, 태블릿 등)에서는 GATT Server (peripheral, 센서장치) 로 데이터 요청을 보낸다. Peripheral 장치는 ATT lookup data, service, characteristic 에 대한 정의를 가지고 있다.

 

 

GATT (Generic Attribute Profile) : 두 BLE 장치간에 Service, Characteristic을 이용해서 데이터를 주고 받는 방법을 정의
Attribute Protocol (ATT) : GATT는 ATT의 최상위 구현체이며 GATT/ATT로 참조되기도 한다. 각각의 속성(Attribute)은 UUID를 가지며 128비트로 구성된다. ATT에 의해 부여된 속성은 특성(characteristic)과 서비스(Service)를 결정한다.
Characteristic : 하나의 특성(characteristic)은 하나의 값과 n개의 디스크립터를 포함한다.
Descriptor : 디스크립터는 특성의 값을 기술한다.
Service : 하나의 서비스는 특성들의 집합이다. 예를 들어 "Heart Rate Monitor"라고 불리는 서비스를 가지고 있다면 그 서비스는 "heart rate measurement"같은 특성을 포함한다.

 

iPhone과 Zikto Walk와의 연결과정

 

 

BLE Scanner

CUSTOM SERVICE에서 "W" 를 누르면 문자를 쓸 수 있다. 전송해보면 시리얼모니터에 뜨는 것을 확인할 수 있다.

 

 

다음은 Texsa Instrument BLE 툴이다. (http://processors.wiki.ti.com/images/f/f5/Sensor_Tag_and_BTool_Tutorial2.pdf)

 

참고:

https://github.com/espruino/winnus/blob/master/cpp/winnus.cpp

블루투스 LE4.0 기본 관련 소스

Bluetooth Interfacing with HM-10

 

 

Desktop에서 연동하기

 

전체 소스 파일:

BLE_con_181024.zip
16.35KB

 

아직 수정 중이며, BLE (HM-10) 한개만 테스트를 해 보았다. 차후 여러개를 선택하여 동시 테스트를 해볼 필요가 있다.

 

 

 

// 참조 사이트
https://github.com/espruino/winnus/blob/master/cpp/winnus.cpp
https://github.com/kkdai/BleConsoleWin/blob/master/BLEConsole/BleCentral.cpp
https ://github.com/matsujirushi/mjwinble/blob/master/mjwinbleproto/mjwinbleproto.cpp
Bluetooth Low Energy

 

마이크로소프트에서 제공하는 기본 API를 사용하여 작성했다.

 

#include <setupapi.h>
#include <bluetoothleapis.h>

#pragma comment(lib, "SetupAPI")
#pragma comment(lib, "BluetoothApis.lib")

 

Scan - 현재 Windows8에서 테스트를 했으며, 먼저 윈도우에서 pairing을 실행한 후 scan이 가능하다.

 

// 페어링 되었을때만 찾을 수 있다. 
int ScanBLE(GUID aGuid)
{
 SP_DEVINFO_DATA dd;
 HDEVINFO hDI = SetupDiGetClassDevs(&aGuid, NULL, NULL,  DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
 if (hDI == INVALID_HANDLE_VALUE) { printf("Invalid handle"); return 1; }
 // Enumerate through all devices in Set.
 dd.cbSize = sizeof(SP_DEVINFO_DATA);

 for (DWORD i = 0; SetupDiEnumDeviceInfo(hDI, i, &dd); i++)
 {
  bool hasError = false;
  DWORD nameData;
  LPTSTR nameBuffer = NULL;
  DWORD nameBufferSize = 0;
  while (!SetupDiGetDeviceRegistryProperty(
   hDI,
   &dd,
   SPDRP_FRIENDLYNAME,
  &nameData,
   (PBYTE)nameBuffer,
   nameBufferSize,
   &nameBufferSize))
  {
   if (GetLastError() == ERROR_INSUFFICIENT_BUFFER)
   {
    // Double the size to avoid problems on W2K MBCS systems per KB 888609.
    if (nameBuffer) delete(nameBuffer);
    nameBuffer = new wchar_t[nameBufferSize * 2];
   }
   else
   {
    hasError = true;
    printf("error: could not get name data (%d)\n", GetLastError());
    break;
   }
  }
  DWORD addressData;
  LPTSTR addressBuffer = NULL;
  DWORD addressBufferSize = 0;
  while (!SetupDiGetDeviceRegistryProperty(
   hDI,
   &dd,
   SPDRP_HARDWAREID,
   &addressData,
   (PBYTE)addressBuffer,
   addressBufferSize,
   &addressBufferSize))
  {
   if (GetLastError() == ERROR_INSUFFICIENT_BUFFER)
   {
    if (addressBuffer) delete(addressBuffer);
    addressBuffer = new wchar_t[addressBufferSize * 2];
   }
   else
   {
    hasError = true;
    printf("error: could not get address data (%d)\n", GetLastError());
    break;
   }   
  }
  LPTSTR deviceIdBuffer = NULL;
  DWORD deviceIdBufferSize = 0;
  while (!SetupDiGetDeviceInstanceId(
   hDI,
   &dd,
   deviceIdBuffer,
   deviceIdBufferSize,
   &deviceIdBufferSize))
  {
   if (GetLastError() == ERROR_INSUFFICIENT_BUFFER)
   {
    if (deviceIdBuffer) delete(deviceIdBuffer);
    deviceIdBuffer = new wchar_t[deviceIdBufferSize * 2];
   }
   else
   {
    hasError = true;
    printf("error: could not get device instance id (%d)\n", GetLastError());
    break;
   }
  }
  if (hasError) { continue; }
  std::string name = util::to_narrow(nameBuffer);
  std::string address = util::to_narrow(addressBuffer);
  std::string deviceId = util::to_narrow(deviceIdBuffer);
  std::cout << "Found--> Name: " << name << ", Addr: " << address << ", ID: " << deviceId << std::endl;
  delete[] nameBuffer;
  delete[] addressBuffer;
  delete[] deviceIdBuffer;
 }
 if (hDI) SetupDiDestroyDeviceInfoList(hDI); // SetupDiGetClassDevs() 함수 사용시 삭제필요.
 return 0;

 

Connect/Disconnect

 

int ConnectBLE(GUID aGuid)
{
 // Step 1: find the BLE device handle from its GUID
 // Get the handle
 g_hLEDevice = GetBLEHandle(aGuid);
 GetGATTClient(g_hLEDevice);
 return 0;



void DisconnectBLE(HANDLE hDevice)
{
 if (hDevice) {
  // disconnect the remote BLE
  WriteBLE(hDevice, &g_pCharacteristicBuffer[0], (UCHAR*)"AT+RESET", 8);
  SetLastError(NO_ERROR);
  CloseHandle(hDevice);
  hDevice = 0;
  printf("CloseHandle\n");
  if (GetLastError()) cout << "Error: " << GetLastErrorAsString() << endl;
  //ERROR_NO_UNICODE_TRANSLATION   1113 (0x459)
 }
 initBLEBuffers();
}

 

Read - Notify를 설정하여 callback 함수로 데이터를 읽어 들인다.

 

 void SetBLENotify(HANDLE hDevice, PBTH_LE_GATT_CHARACTERISTIC pCharacterisic)
{
 static BLUETOOTH_GATT_EVENT_HANDLE hRxEvent = NULL;
 if (hRxEvent == NULL) {
  cout << "Set notify mode: register callback function." << endl;
  // set the appropriate callback function when the descriptor change value
  if (pCharacterisic->IsNotifiable) {
   printf("Setting Notification for ServiceHandle %d\n", pCharacterisic->ServiceHandle);
   BTH_LE_GATT_EVENT_TYPE EventType = CharacteristicValueChangedEvent;
   BLUETOOTH_GATT_VALUE_CHANGED_EVENT_REGISTRATION EventParameterIn;
   EventParameterIn.Characteristics[0] = *pCharacterisic;
   EventParameterIn.NumCharacteristics = 1;


   HRESULT hr = BluetoothGATTRegisterEvent(
    hDevice,
    EventType,
    &EventParameterIn,
    callbackBLENotify,
    NULL,
    &hRxEvent,
    BLUETOOTH_GATT_FLAG_NONE);
   if (S_OK != hr) {
    printf("BluetoothGATTRegisterEvent - Actual Data %d\n", hr);
   }
  }
 }
 else {
  cout << "Unset notify mode: unregister callback function." << endl;
  if (hRxEvent) {
   HRESULT hRet = BluetoothGATTUnregisterEvent(hRxEvent, BLUETOOTH_GATT_FLAG_NONE); // BluetoothGATTRegisterEvent()
   if (hRet != S_OK) cout << "BluetoothGATTUnregisterEvent Error: " << GetLastError() << endl; // whatever. we don't care now!
  }
  hRxEvent = NULL;
 }
}


void callbackBLENotify(BTH_LE_GATT_EVENT_TYPE EventType, PVOID EventOutParameter, PVOID Context)
{
 PBLUETOOTH_GATT_VALUE_CHANGED_EVENT ValueChangedEventParameters = (PBLUETOOTH_GATT_VALUE_CHANGED_EVENT)EventOutParameter;
 HRESULT hr;
 if (0 == ValueChangedEventParameters->CharacteristicValue->DataSize) {
  hr = E_FAIL;
  printf("Fail: datasize 0\n");
 }
 else {
  for(ULONG i=0; i<ValueChangedEventParameters->CharacteristicValue->DataSize;i++) {
   printf("%0x ",ValueChangedEventParameters->CharacteristicValue->Data[i]);
  }  
 }
}

 

Write

void WriteBLE(HANDLE hDevice, PBTH_LE_GATT_CHARACTERISTIC pCharacterisic, unsigned char * data, unsigned long length)
{
 BTH_LE_GATT_RELIABLE_WRITE_CONTEXT relialeWriteContext = NULL;
 if (pCharacterisic == NULL) {
  cout << "pTXCharacteristic is null." << endl;
  return;
 }
 if (pCharacterisic->IsSignedWritable || pCharacterisic->IsWritable || pCharacterisic->IsWritableWithoutResponse) {
  HRESULT hr = BluetoothGATTBeginReliableWrite(
   hDevice,
   &relialeWriteContext,
   BLUETOOTH_GATT_FLAG_NONE);
  if (SUCCEEDED(hr))
  {
   PBTH_LE_GATT_CHARACTERISTIC_VALUE newValue = (PBTH_LE_GATT_CHARACTERISTIC_VALUE)new UCHAR[sizeof(ULONG)+length];
   newValue->DataSize = (ULONG)length;
   memcpy(newValue->Data, data, length);
   HRESULT hr = BluetoothGATTSetCharacteristicValue(
    hDevice,
    pCharacterisic,
    newValue,
    NULL,
    BLUETOOTH_GATT_FLAG_WRITE_WITHOUT_RESPONSE);

   delete newValue;
  }
  if (NULL != relialeWriteContext)
  {
   BluetoothGATTEndReliableWrite(
    hDevice,
    relialeWriteContext,
    BLUETOOTH_GATT_FLAG_NONE);
  }
 }
 else
 {
  cout << "characteristic is not writable"  << endl;
 }

 

 

다음검색
현재 게시글 추가 기능 열기

댓글

댓글 리스트
맨위로

카페 검색

카페 검색어 입력폼