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
Bluetooth Interfacing with HM-10
Desktop에서 연동하기
전체 소스 파일:
아직 수정 중이며, 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; } } |