Arduino nano 33 BLE - Central/Peripheral 연결
Arduino

Arduino nano 33 BLE - Central/Peripheral 연결

BLE

저전력 블루투스(Bluetooth Low Energy, BLE)는 개인용 무선 네트워크 (Wireless Personal Area Network, WPAN) 기술의 일종으로, 블루투스라는 이름을 갖고 있지만 기존의 블루투스 (Bluetooth Classic, BR/EDR)와는 사실상 별개의 기술이다. 2010년 출시된 Bluetooth 4.0 부터 저전력 (Low Energy, LE) 프로토콜을 지원하기 시작했고, 2016년 Bluetooth 5.0 출시와 함께 그 기능이 보다 확장되었다.

 

연결방식

2가지 모드가 있다. Advertise(Broadcast) Mode와 Connection Mode

 

Advertise(Broadcast) Mode

두 기기가 무선 통신을 하려면 서로의 존재를 알아야 한다. 누군가는 자신의 존재에 대해서 알리는 신호 Advertising Packet를 보내고 누군가는 그것을 찾아서 연결을 시도하는 과정이 Advertise Mode에서 이루어 진다.

  • Advertiser(Peripheral) : 일정한 주기로 신호를 방송하듯 주변에 뿌리는 디바이스.
  • Observer(Central) : Advertiser 에게 신호를 받기 위해 주기적으로 Scanning 을 하는 디바이스.

Connection Mode

Advertise Mode 에서 알게 된 기기 중에 하나를 선택해서 1:1 로 연결하는 것이 Connection Mode 이다. Connection Mode 로 전환 되고 나서는 서로 타이밍을 맞춰서 데이터를 주고 받으며, 이전 모드에서 진행했던 과정은 더 이상 필요 없다.

  • Peripheral(Slave) : 연결하기 위한 Advertise 신호를 주기적으로 보내다가, Central 디바이스가 연결 요청을 보내면, 이를 수락해 연결한다.
  • Central(Master) : 다른 디바이스의 Advertise 신호를 주기적으로 스캔하다가, 연결을 요청한다.


 

Arduino NANO 33 BLE 설치


NANO 33 BLE는 AVR 기반인 기존 아두이노와 다르게 ARM 기반의 제어칩을 사용한다. 그렇기 때문에 컴파일 방법이 달라, 보드를 새로 설치하여야 한다.


"툴" -> "보드" -> "보드 매니저..." 을 클릭하여 "nano 33"이라고 검색한 후 "Arduino Mbed OS Nano Boards"를 설치.

 


설치가 끝나면 "Arduino NANO 33 BLE" 가 보드 선택 창에 나타난다. 보드를 선택한 후 포트를 선택해 준다.

 

Arduino NANO 33 BLE 연결

 

기존에 있는 아두이노 BLE 예제를 이용하여 간단한 데이터 송수신 코드를 만들어봤다.

 

Central.ino

#include <ArduinoBLE.h>

void setup() {
  Serial.begin(9600);
  while (!Serial);

  // initialize the BLE hardware
  if (!BLE.begin()) {
    Serial.println("* Starting BLE module failed!");
    while (1);
  }

  // BLE device의 이름을 설정하는 부분.
  BLE.setLocalName("Nano 33 BLE (Central)"); 
  BLE.advertise();
  
  Serial.println("Arduino Nano 33 BLE Sense (Central Device)");
  Serial.println(" ");
  
  // client는 해당 UUID를 service로 advertising하고 있는 BLE device를 찾게된다. 상대의 UUID를 알고있다면 이런 식으로 연결할 수도 있다.
  BLE.scanForUuid("19b10000-e8f2-537e-4f6c-d104768a1214");
}

void loop() {
  // 인근에 advertising 중인 peripheral device를 발견했는지 확인해준다.
  BLEDevice peripheral = BLE.available();

  if (peripheral) {
    // discovered a peripheral, print out address, local name, and advertised service
    Serial.print("Found ");
    Serial.print(peripheral.address());
    Serial.print(" '");
    Serial.print(peripheral.localName());
    Serial.print("' ");
    Serial.print(peripheral.advertisedServiceUuid());
    Serial.println();

    if (peripheral.localName() != "Arduino Nano 33 BLE (Peripheral)") {
      return;
    }
    
    // stop scanning
    BLE.stopScan();

    controlLed(peripheral);

    // 연결 실패 시 재연결 시도
    BLE.scanForUuid("19b10000-e8f2-537e-4f6c-d104768a1214");
  }
}

void controlLed(BLEDevice peripheral) {
  // connect to the peripheral
  Serial.println("Connecting ...");

  if (peripheral.connect()) {
    Serial.println("Connected");
  } else {
    Serial.println("Failed to connect!");
    return;
  }

  // discover peripheral attributes
  Serial.println("Discovering attributes ...");
  if (peripheral.discoverAttributes()) {
    Serial.println("Attributes discovered");
  } else {
    Serial.println("Attribute discovery failed!");
    peripheral.disconnect();
    return;
  }

  BLECharacteristic flexCharacteristic = peripheral.characteristic("19b10001-e8f2-537e-4f6c-d104768a1214");


  
  while (peripheral.connected()) {
    // BLE 통신에서 넘겨주는 데이터 타입은 uint8_t이다. 정수나 실수를 그대로 넘겨줄 수 없기 때문에 받는 쪽에서는 적절한 캐스팅을 해줘야한다.
        flexCharacteristic.writeValue((byte)1);
    }
}
  • Serial 통신과 BLE를 초기화하고, Serial Monitor가 열리기를 기다린다.
  • BLE.scan()이 호출되면 scan을 시작한다.
  • BLE.available()는 인근에 advertising 중인 peripheral device를 발견했는지 확인해준다.
  • setLocalName은 BLE device의 이름을 설정하는 부분이다. 
  • BLE.scanForUuid("19b10000-e8f2-537e-4f6c-d104768a1214");을 통해 client는 해당 UUID를 service로 advertising하고 있는 BLE device를 찾게된다.
  • BLE device를 찾았다면 정보를 출력하고, BLE.stopScan()으로 scan을 멈춘다.
  • 연결 중에는 loop() 함수가 아니라, controlLED함수를 계속 돌게 된다.
  • BLECharacteristic flexCharacteristic = peripheral.characteristic("19b10001-e8f2-537e-4f6c-d104768a1214"); 문장을 통해 server의 characteristic을 받아온다. 그리고 적당히 예외처리 해준 뒤에 데이터를 읽는다. (이 코드에서는 따로 정보를 읽지않고, 바로 writeValue()로 데이터를 써준다.)
  • BLE 통신에서 넘겨주는 데이터 타입은 uint8_t이다. 정수나 실수를 그대로 넘겨줄 수 없기 때문에 받는 쪽에서는 적절한 캐스팅을 해줘야한다.

 

Peripheral.ino

#include <ArduinoBLE.h>

BLEService Service("19B10000-E8F2-537E-4F6C-D104768A1214"); // BLE Service

// BLE Characteristic - custom 128-bit UUID, read and writable by central
BLEIntCharacteristic FlexCharacteristic("19B10001-E8F2-537E-4F6C-D104768A1214", BLERead | BLEWrite);


void setup() {
  Serial.begin(9600);
  while (!Serial);

  // begin initialization
  if (!BLE.begin()) {
    Serial.println("starting BLE failed!");

    while (1);
  }

  // 디바이스 이름 설정
  BLE.setLocalName("Arduino Nano 33 BLE (Peripheral)");

  // service를 광고할 service 목록에 추가한다.
  BLE.setAdvertisedService(Service);

  // 사전에 정의한 characteristic을 service에 추가한다.
  Service.addCharacteristic(FlexCharacteristic);
  
  // 이제 server를 BLE device에 등록한다.
  BLE.addService(Service);
  
  // start advertising
  BLE.advertise();

  Serial.println("Nano 33 BLE (Peripheral Device)");
  Serial.println(" ");
}

void loop() {
  // 연결 대기
  BLEDevice central = BLE.central();
  Serial.println("- Discovering central device...");
  delay(500);
  
  // 성공적으로 연결되었다면
  if (central) {
    Serial.print("Connected to central: ");
    // 연결된 central 디바이스의 MAC 주소를 출력
    Serial.println(central.address());

    // while the central is still connected to peripheral:
    while (central.connected()) {
        if (FlexCharacteristic.value()) {   // 전송된 값이 있다면
          Serial.println(FlexCharacteristic.value());  // 값을 출력
        }
     }

    // 연결 실패 시 출력
    Serial.print(F("Disconnected from central: "));
    Serial.println(central.address());
  }
}
  • Service와 Characteristic을 선언할 때 사용되는 저 의미불명의 string은 UUID(universally unique identifier)라고 부르는 unique한 ID다.
    • BLE에서 제공하는 다양한 service가 이미 정의돼있다.
      https://www.bluetooth.com/ko-kr/specifications/assigned-numbers/
    • BLE의 공식 UUID는 16bits로 이뤄져있다. UUID는 16진수로 표현되므로 한 글자는 4bits를 사용하는 꼴이다. 따라서 공식 UUID는 4글자로 돼있다.
    • 우리가 직접 Service나 Characteristic을 정의할 수 있는데, 이 경우에는 커스텀 UUID가 필요하다. 커스텀 UUID는 128bits로 이뤄져있다.
    • 위 예제에서는 Service와 Characteristic을 커스텀으로 만들었으므로 32글자로 이뤄진 UUID를 선언했다. 

  • Characteristic에는 BLERead | BLEWrite 같은 여러 속성(Attributes)을 부여할 수 있다. 위 예제의 FlexCharacteristic라는 속성은 읽거나 쓸 수 있다.
    • BLEBroadcast – 특성이 광고되도록 합니다.
    • BLERead – 원격 장치가 특성 값을 읽을 수 있도록 합니다.
    • BLEWriteWithoutResponse – 원격 장치가 승인을 기대하지 않고 장치에 쓸 수 있습니다.
    • BLEWrite – 쓰기 성공 확인을 예상하면서 원격 장치에 쓰기 허용
    • BLENotify – 특성 값이 업데이트될 때마다 원격 장치에 알림을 보낼 수 있습니다.
    • BLEIndicate – BLENotify와 동일하지만 원격 장치에서 값을 읽었음을 나타내는 응답을 기대합니다.

  • Device의 이름을 등록했으니, 이제 service를 등록한다. setAdvertisedService()로 service를 광고할 service 목록에 추가한다.
  • Server를 등록했으니, 이제 characteristic을 등록한다. addCharacteristic()으로 사전에 정의한 characteristic을 service에 추가한다.
  • 이제 server를 BLE device에 등록한다.