2021년 1월 20일 수요일

Arduino Nano 33 BLE보드와 TinyML 기반 딥러닝 처리 방법 및 사용기

이 글은 Arduino Nano 33 BLE 보드와 TinyML 기반 딥러닝 활용 방법에 대한 글이다. 

이 아두이노 마이크로 컨트롤러는 20달러로 저가 단일칩 독립형 컴퓨터이다. 웨어러블, 드론, 3D 프린터, 장난감, 스마트 플러그, 전기 스쿠터, 세탁기와 같은 장치에 내장된 컴퓨터이다. 이러한 장치를 인터넷으로 연결하면, 사물 인터넷이된다.

아두이노 커뮤니티는 TensorFlow Lite Micro를 새롭게 개발된 Arduino Nano 33 BLE Sense 보드에 도입했다. 이를 사용하면, 다양한 딥러닝 기계학습 어플리케이션을 손가락만한 아두이노 임베디드 보드에서 실행할 수 있다. 

아두이노에 사용하는 보드는 64MHz에서 실행되는 Arm Cortex-M4 마이크로 컨트롤러가 있으며 1MB 플래시 메모리와 256KB RAM이 있다. Arduino Nano 33 BLE Sense 보드는 손가락 크기보다 작다. 참고로, 임베디드 보드 개발시 다음과 같은 문제들로 데이터가 제대로 취득되지 않을 수 있다. 임베디드는 일반 컴퓨터보다 매우 민감하게 작동된다는 점을 고려해야 한다.

  • 소모 전류량 부족: 너무 많은 장치를 연결하지 않는다. 별도 전원을 준비한다.
  • 보드 불량(가끔식 발생함)
  • 회로 연결 오류: 테스터기로 확인해야 함
  • 불완전한 센서 문제: 센서 읽기에 지연, 인터럽트 사용, 보드 강제 리셋 등이 필요할 수 있음
  • 센서 간 충돌: 보드 내 조도 센서는 IMU등 다른 센서와 충돌이 있음. 테스트 필요함
  • 칩 자체 발열로 인한 센서값 오프셋: 캘리브레이션 처리가 필요하거나, 발열을 피할 수 있는 조건을 찾아야 함

이 부분은 아두이노 나노에 대한 많은 리포트에서 확인할 수 있다. 제대로 작동하지 않은 경우, 상세 내용은 다음 링크를 참고한다.

이 글은 아두이노 나노 33 센스 보드를 이용한 딥러닝 처리 방법을 간단히 이야기한다.

아두이노와 TinyML

기계 학습에는 신경망 모델을 마이크로 컨트롤러와 같은 메모리 제약있는 장치에서 실행할 때 사용할 수 있는 기술이 있다. 핵심 단계 중 하나는 부동 소수점을 8 비트 정수로 변환해 양자화하는 것이다. 이를 통해 딥러닝 계산을 더 빠르게 실행할 수 있다. 이런 임베디드 컴퓨터에서 기계학습(ML. Machine Learning) 처리하는 방법 중 하나가 TinyML이다. TinyML은 텐서플로우로 학습된 모델을 경량화해, 메모리 크기가 작은 아두이노 같은 소형 임베디드 컴퓨터에서 실행할 수 있도록 도와준다.

TinyML은 떠오르는 분야이다. 수십억 개의 마이크로 컨트롤러가 모든 종류의 장소에서 다양한 센서와 결합되어, 창의적이고 가치있는 TinyML 애플리케이션을 만들 수 있다. 아래 링크를 방문하면, TinyML 커뮤니티 글, 기술, 내용 및 예제를 확인할 수 있다.

Arduino Nano 33 BLE 기능

이 글을 따라하기 위해 다음과 같은 재료가 필요하다.

프로그래밍하려면 Arduino Web Editor를 사용하거나 Arduino IDE를 설치해야 한다. 

Arduino Nano 33 BLE Sense에는 다양한 온보드 센서가 있다.

  • 음성 – 디지털 마이크
  • 모션 – 9축 IMU (가속도계, 자이로 스코프, 자력계)
  • 환경 – 온도, 습도 및 압력
  • 빛 – 밝기, 색상 및 물체 근접

아울러, Bluetooth LE 연결이 있으므로 노트북, 모바일 앱 또는 기타 BLE 주변 장치로 데이터를 보낼 수 있다.

보드 설명 영상
보드 레이아웃

좀 더 상세한 내용은 다음 링크를 참고한다.

딥러닝 예제 실행하기

이 글 예제는 원래 TensorFlow 블로그에 Sandeep Mistry, Dominic Pajak에 의해 작성되었음을 밝힌다. 이 개발자들은 음성인식, 모션 동작 인식, 카메라 비전 사람 인식 예제를 워크샵했다. 

  • micro_speech : Yes, No 음성 인식
  • magic_wand: 동작 인식
  • person_detection: 사람 인식

딥러닝 음성 인식

첫번째 예제는 간단한 음성 명령 인식 신경망 모델을 아두이노에 업로드할 것이다. 이를 위해, 먼저 아두이노 IDE를 실행하고, 다음과 같이 Arduino Nano 33 BLE 보드와 라이브러리를 설치한다.

아두이노IDE의 도구>보드 매니저 메뉴를 실행해 다음과 같이 BLE 보드를 검색하고 설치한다.

Arduino Nano 33 BLE 보드 설치

보드를 설치한 후, Arduino 보드를 노트북과 연결한다. 그리고, IDE에서 보드와 COM 포트를 선택한다. 
USB 포트 연결
Arduino Nano 33 BLE 보드 및 포트 선택

이제, 스케치>라이브러리 매니저 메뉴를 선택해, Arduino Tensorflow Lite 를 검색해 라이브러리를 설치한다.

라이브러리 설치 후 파일>예제 메뉴에 Arduino Tensorflow 예제 중 micro_speech 예제를 선택하고, 업로드 한다. 아마, 소스를 컴파일하고 보드에 업로드하는 데 시간이 몇분 걸릴것이다.
업로드 중 모습
업로드 성공 모습

이제 툴>시리얼 모니터 메뉴를 실행한다. 그리고, 아두이노 보드를 입에 가까이 대고, Yes, No 단어를 말해보자. 그럼, 다음과 같이 음성을 인식해 Yes, No 텍스트를 출력해 준다. 

음성 딥러닝 모델 실행 모습

동작 제스쳐 인식

이 예제 실행을 위해, Arduino_LSM9DS1 라이브러리를 검색해 설치한다. 파일>예제>Arduino_TensorflowLite 예제에서 magic_wand 를 선택한다. 소스코드가 열리면, 다음과 같이 업로드한다.


성공적으로 업로드되었다면, 다음과 같이, 나노 보드를 흔들어보자. 그럼, 제스쳐에 따라, O, W와 같은 형태가 시리얼 모니터에 출력될 것이다.
제스쳐 인식 결과
제스쳐 실행 예시

사람 인식
이 예제는 ArduCam 카메라가 준비되어야 한다. 
ArduCam (구매)

카메라와 보드간 회로는 다음과 같이 연결한다.
회로 연결 모습(참고)

사람 인식 예제 실행을 위해, 파일>예제>Arduino_TensorflowLite>person_detection을 선택한다. 앞에서 한것처럼, 메뉴에서 업로드 버튼을 클릭한다. 그럼, 사람 인식 딥러닝 모델이 보드에서 실행되어, 다음과 같이 사람을 인식할 수 있다.
person detection 실행 결과

Nano 33과 배터리 연결하는 방법은 다음 자료를 참고하길 바란다.

IMU 센서 데이터 획득

보드에 내장된 IMU 자세 센서를 사용해 그 데이터값을 얻어본다. 
파일>예제>Arduino_LSM9DS1>SimpleGyroscope파일을 열고, 업로드한다. 
다음과 같이 실행되면 성공한 것이다.

OLED 디스플레이 연결

이 나노 보드에는 OLED 디스플레이가 없다. 그러므로, SSD1306 으로 불리는 OLED 디스플레이를 연결할 경우가 가끔 생긴다. 

나노 보드와 연결은 다음과 같이 한다.


아두이노 IDE에서 adafruit_SSD1306 라이브러리를 설치한다.
그리고, 다음과 같은 소스 코드를 업로드해 본다. 

#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 32 // OLED display height, in pixels

#define OLED_RESET  -1
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

#define NUMFLAKES     10 // Number of snowflakes in the animation example

#define LOGO_HEIGHT   16
#define LOGO_WIDTH    16
static const unsigned char PROGMEM logo_bmp[] =
{ B00000000, B11000000,
  B00000001, B11000000,
  B00000001, B11000000,
  B00000011, B11100000,
  B11110011, B11100000,
  B11111110, B11111000,
  B01111110, B11111111,
  B00110011, B10011111,
  B00011111, B11111100,
  B00001101, B01110000,
  B00011011, B10100000,
  B00111111, B11100000,
  B00111111, B11110000,
  B01111100, B11110000,
  B01110000, B01110000,
  B00000000, B00110000 };

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

  // SSD1306_SWITCHCAPVCC = generate display voltage from 3.3V internally
  if(!display.begin(SSD1306_SWITCHCAPVCC)) {
    Serial.println(F("SSD1306 allocation failed"));
    for(;;); // Don't proceed, loop forever
  }

  // Show initial display buffer contents on the screen --
  // the library initializes this with an Adafruit splash screen.
  display.display();
  delay(2000); // Pause for 2 seconds

  // Clear the buffer
  display.clearDisplay();

  // Draw a single pixel in white
  display.drawPixel(10, 10, SSD1306_WHITE);

  // Show the display buffer on the screen. You MUST call display() after
  // drawing commands to make them visible on screen!
  display.display();
  delay(2000);
  // display.display() is NOT necessary after every single drawing command,
  // unless that's what you want...rather, you can batch up a bunch of
  // drawing operations and then update the screen all at once by calling
  // display.display(). These examples demonstrate both approaches...

  testdrawline();      // Draw many lines
  testscrolltext();    // Draw scrolling text
  testdrawbitmap(); 
}

void loop() {
}

void testdrawline() {
  int16_t i;

  display.clearDisplay(); // Clear display buffer

  for(i=0; i<display.width(); i+=4) {
    display.drawLine(0, 0, i, display.height()-1, SSD1306_WHITE);
    display.display(); // Update screen with each newly-drawn line
    delay(1);
  }
  for(i=0; i<display.height(); i+=4) {
    display.drawLine(0, 0, display.width()-1, i, SSD1306_WHITE);
    display.display();
    delay(1);
  }
  delay(250);
}

void testscrolltext(void) {
  display.clearDisplay();

  display.setTextSize(2); // Draw 2X-scale text
  display.setTextColor(SSD1306_WHITE);
  display.setCursor(10, 0);
  display.println(F("scroll"));
  display.display();      // Show initial text
  delay(100);

  // Scroll in various directions, pausing in-between:
  display.startscrollright(0x00, 0x0F);
  delay(2000);
  display.stopscroll();
  delay(1000);
  display.startscrollleft(0x00, 0x0F);
  delay(2000);
  display.stopscroll();
  delay(1000);
  display.startscrolldiagright(0x00, 0x07);
  delay(2000);
  display.startscrolldiagleft(0x00, 0x07);
  delay(2000);
  display.stopscroll();
  delay(1000);
}

void testdrawbitmap(void) {
  display.clearDisplay();

  display.drawBitmap(
    (display.width()  - LOGO_WIDTH ) / 2,
    (display.height() - LOGO_HEIGHT) / 2,
    logo_bmp, LOGO_WIDTH, LOGO_HEIGHT, 1);
  display.display();
  delay(1000);
}

OLED display가 동작하면 성공한 것이다. 

나노 BLE 모듈 센서 통합 테스트

앞서 정리한 내용을 응용해, 나노에 장착된 자이로센서, 가속도센서, 중력센서, 온습도 센서 등을 모두 테스트해 본다. 코드는 다음 깃허브에서 다운로드한다. 

업로드 컴파일 중 발생되는 라이브러리 헤더 에러는 앞서 설명한 대로 아두이노 라이브러리 검색 및 추가 기능으로 해결한다.

결과는 다음과 같다. 

이제, 이 나노 보드를 센싱할 건축 공간이나 부재에 부착하고, 데이터를 모아,유무선 통신으로 클라우드 플랫폼에 업로드하면, 데이터 분석을 할 수 있다. 

결론

이 글은 아두이노 나노 33 센스를 통해 TinyML을 간단히 사용해보고, 그 잠재력을 확인해 보았다. 실제 사용해 보면 알겠지만, 아직은 속도나 정확도가 그리 만족스럽지는 못하다. 다만, 손가락 크기의 보드에서 딥러닝 모델을 처리하고, 처리 데이터 결과를 클라우드 플랫폼에 전달할 수 있는 가능성이 많은 상상을 하게 만든다. 

TinyML은 응용목적에 따라 모델을 적절히 최적화한다면, 좀 더 나은 성능을 기대할 수 있을 것이다. 앞으로, 임베디드 보드를 이용한 딥러닝 처리는 더욱 보편화될 것이라 생각한다. 이 글을 통해, 관련 기술 개발 및 응용에 조금이나마 도움이 되었으면 한다.

레퍼런스






댓글 13개:

  1. 항상 유익한 연구를 잘 정리해 주셔서 감사합니다. 아두이노에서 Machine Learning을 지원한다니 참 새롭네요. 집에 아두이노가 엄청 많은데- 이제는 쓸모없겠구나 했는데. 이 블로그 글을 보니 다시 살려서 뭔가 해보고 싶네요!
    그럼 앞으로도 좋은 활동을 응원하겠습니다~~!
    좋은 하루가 되세요!!

    답글삭제
  2. TinyML에 요즘 관심 가지며 공부하고 있는 학생인데 정말 참고 잘 하고갑니다! 좋은 글 써주셔서 정말 감사합니다!!!

    답글삭제
  3. 작성자가 댓글을 삭제했습니다.

    답글삭제
  4. 좋은 정보 감사합니다. 제가 작업도중 arm_math.h 라이브러리 없다고 에러가 뜨는데 아무리 리서치 해봐도 arm_math.h 라이브러리는 안나와서 혹시 해결 방안이 있을까요?

    답글삭제
    답글
    1. 해당 보드 설정이나 보드와 맞지 않은 라이브러리 혹은 예제 사용하신 것 아니신지 체크 먼저 해보시는 게 좋을 듯 해요.

      삭제
  5. 노트북2에서 아두이노스케치 구동하고 어떤 메뉴로 연결해서 시리얼모니터와 시리얼플로터로 실시간 센서 값을 받을 수 있을까요?

    현재 노트북1에서 블루투스하는 것을 노트북2에서 페어링까지는 했습니다

    그런데 노트북 2에서 블루투스 com포트를 찾으니 ble로 잡힙니다

    le는 아두이노스케치에서 어느 메뉴를 말할까요?

    수신부 노트북에서 페어링이 되어 있는데 아두이노스케치에서 이 값을 연결할 수 있는 기능이 없을까요? com은 가능합니다



    ble는 불가능할까요? 나타내는 메뉴가 아두이노스케치에 없는 것일까요? 그래프를 그리려고 합니다

    송신부 아두이노나노 블루투스모듈 센서값 3.7리듐건전지 수신부 노트북 아두이노스케치 시리얼 모니터 이렇게 구성하려고 하는데 코딩은 송신부에서 센서값 구하는 코딩 만들고 이것을 블루투스모듈로 나타내는 코딩 만들어서 수신부에는 페어링하고 com포트 잡아서 시리얼모니터나 시라얼플로터로 나타내면 될까요?

    답글삭제
    답글
    1. 아래 유사한 예제 코드 참고 바랍니다.
      https://docs.arduino.cc/tutorials/nano-33-ble-sense/ble-device-to-device

      삭제
  6. ble버전 nano33iot+ 배터리 한세트와 무선으로 노트북과 블루투스로 연결해서 아두이노ide에서 시리얼모니터로 나타낼 수 있을까요?

    ble버전 nano33iot+ 배터리 한세트와 무선으로
    ble버전 nano33iot+ 노트북 한세트된 곳에 송수신이 될까요?

    코딩은 비슷할까요?

    답글삭제
    답글
    1. 같은 내용인 듯 한데, 아래 링크 참고 바랍니다.
      https://docs.arduino.cc/tutorials/nano-33-ble-sense/ble-device-to-device

      삭제
  7. nano33 iot 세트와 무선으로 노트북과 연결해서 아두이노ide로 시리얼모니터로 나타내는 것이 가능하다는 것일까요?

    답글삭제
  8. 네. 가능할 것 같습니다. 위 링크 참고 바래요.

    답글삭제