아두이노 나노 33 BLE 센스로 오디오 데이터 전송하기
부품 및 소모품
프로젝트 소개
Chirp HQ에서는 아두이노가 다양한 센서, 강력한 프로세서, 그리고 무엇보다 내장 마이크를 탑재한 새로운 보드를 출시한다는 소식을 듣고 큰 기대를 했습니다. 아두이노 측에서 프리릴리즈 버전의 나노 33 센스 보드를 보내주었는데, 이 보드는 Chirp와 완벽하게 호환되어 메이커 커뮤니티가 소리를 이용해 데이터를 송수신할 수 있게 해줍니다.
Chirp는 아두이노 보드에 완전히 독특한 전송 메커니즘을 추가합니다. 소리로 데이터를 전송하는 기능 덕분에 해커들은 더 다양한 연결 옵션을 손쉽게 사용할 수 있게 되었습니다. 소리 데이터 전송의 주요 장점은 다음과 같습니다.
- 기기 독립적: 모바일 앱, 웹페이지, 심지어 오디오 파일만으로도 데이터 전송 가능
- 일대다 통신: 청취 범위 내의 모든 기기가 한 번의 트랜잭션으로 데이터 수신 가능
- 설정 불필요: 페어링, 비밀번호, 초기 설정 없이 바로 사용 가능
이 튜토리얼에서는 Chirp SDK를 사용해 RGB 값을 전송하여 온보드 LED의 색상을 변경하는 방법을 시연합니다. Chirp는 수년간의 연구를 통해 배경 소음에도 강인한 디코더를 구축했으므로 소음이 있는 환경에서도 잘 작동합니다.
시작하기
아직 가입하지 않았다면 developers.chirp.io에서 Chirp에 가입하세요. 가입 후 애플리케이션의 앱 키, 시크릿, 오디오 설정을 얻을 수 있습니다.
데이터 수신하기
나노 33 센스에는 내장 마이크가 탑재되어 있으므로, 라이브러리 매니저에서 직접 설치 가능한 Chirp SDK만 있으면 데이터를 수신할 수 있습니다.
도구 > 라이브러리 관리를 열고 "Chirp SDK"를 검색해 v3.3.0 이상을 설치하면 코딩을 시작할 준비가 끝납니다.
예제 코드를 열어보세요.
파일 > 예제 > ChirpSDK > Nano33SenseReceive
16khz-mono-embedded 프로토콜의 자격 증명을 credentials.h 파일에 복사해 넣으세요. 예제 코드는 수신된 데이터를 시리얼 모니터에 출력하지만, onReceivedCallback 함수를 수정하면 온보드 LED를 제어할 수 있습니다.
먼저 setup() 함수에서 LED 핀을 출력으로 설정합니다.
#define R_LED_PIN 22
#define G_LED_PIN 23
#define B_LED_PIN 24
void setup() {
pinMode(R_LED_PIN, OUTPUT);
pinMode(G_LED_PIN, OUTPUT);
pinMode(B_LED_PIN, OUTPUT);
}
데이터 페이로드에서 RGB 값을 추출해 LED를 구동합니다.
void onReceivedCallback(void *chirp, uint8_t *payload, size_t length, uint8_t channel) {
if (length > 0) {
// 높은 값은 낮은 밝기를 의미하므로 UINT8_MAX에서 뺌
analogWrite(R_LED_PIN, UINT8_MAX - payload[0]);
analogWrite(G_LED_PIN, UINT8_MAX - payload[1]);
analogWrite(B_LED_PIN, UINT8_MAX - payload[2]);
} else {
Serial.println("Decode failed");
}
}
아래 오디오 파일을 재생해 코드를 테스트해보세요. 각 파일은 LED 색상을 빨강→초록→파랑 순으로 변경합니다.
다음 단계는?
현재 아두이노용 Chirp SDK는 가청 주파수인 16khz-mono-embedded 프로토콜만 지원합니다. 향후 몇 달 안에 사람이 들을 수 없는 준초음파 대역에서 작동하는 비가청 버전이 출시될 예정입니다. 이를 활용하면 음악이나 영상 같은 기존 오디오에 Chirp 데이터를 임베드해 액션을 트리거할 수 있게 됩니다.
아두이노와 소리 데이터 전송을 활용한 더 많은 튜토리얼도 준비 중입니다. 직접 만든 프로젝트에서 Chirp를 사용했다면 트위터 @chirp로 태그하거나 contact@chirp.io로 연락해 주세요.
코드
Chirp RGB 값을 나노 33 센스로 전송 C/C++
Chirp를 사용해 RGB 값을 전송하고 온보드 LED 색상을 변경합니다. 이 튜토리얼에 첨부된 사운드 클립을 사용하거나, 3바이트 배열을 직접 전송해 테스트할 수 있습니다.
/**--------------------------------------------------------------------
아두이노 나노 33 센스 보드로 데이터 수신 예제 코드
@file Nano33SenseReceive.ino
@brief https://developers.chirp.io에서 개발자 계정 생성 후
"16khz-mono-embedded" 프로토콜의 키, 시크릿, 설정 문자열을
아래 CHIRP 정의에 복사하세요.
이 예제는 RGB 값을 수신 대기하며 온보드 LED 색상을 변경합니다.
회로:
- 아두이노 나노 33 BLE 보드
Copyright © 2011-2019, Asio Ltd. All rights reserved.
-------------------------------------------------------------------*/
#include <PDM.h>
#include "chirp_connect.h"
#define CHIRP_APP_KEY "YOUR_APP_KEY"
#define CHIRP_APP_SECRET "YOUR_APP_SECRET"
#define CHIRP_APP_CONFIG "YOUR_APP_CONFIG"
#define SAMPLE_RATE 16000
#define BUFFER_SIZE 256
#define R_LED_PIN 22
#define G_LED_PIN 23
#define B_LED_PIN 24
// 전역 변수
static chirp_connect_t *chirp = NULL;
short sampleBuffer[BUFFER_SIZE];
volatile int samplesRead;
// 함수 선언
void setupChirp(void);
void chirpErrorHandler(chirp_connect_error_code_t code);
void onPDMdata(void);
// 메인
void setup() {
Serial.begin(115200);
// while (!Serial);
pinMode(R_LED_PIN, OUTPUT);
pinMode(G_LED_PIN, OUTPUT);
pinMode(B_LED_PIN, OUTPUT);
setupChirp();
PDM.onReceive(onPDMdata);
PDM.setGain(30);
if (!PDM.begin(1, SAMPLE_RATE)) {
Serial.println("Failed to start PDM!");
while (1);
}
analogWrite(R_LED_PIN, UINT8_MAX);
analogWrite(G_LED_PIN, UINT8_MAX);
analogWrite(B_LED_PIN, UINT8_MAX);
}
void loop() {
if (samplesRead) {
chirp_connect_error_code_t err = chirp_connect_process_shorts_input(chirp, sampleBuffer, samplesRead);
chirpErrorHandler(err);
samplesRead = 0;
}
}
void onPDMdata() {
int bytesAvailable = PDM.available();
PDM.read(sampleBuffer, bytesAvailable);
samplesRead = bytesAvailable / sizeof(short);
}
// Chirp 콜백
void onReceivingCallback(void *chirp, uint8_t *payload, size_t length, uint8_t channel) {
Serial.println("Receiving data...");
analogWrite(R_LED_PIN, UINT8_MAX);
analogWrite(G_LED_PIN, UINT8_MAX);
analogWrite(B_LED_PIN, UINT8_MAX);
}
void onReceivedCallback(void *chirp, uint8_t *payload, size_t length, uint8_t channel) {
if (length) {
analogWrite(R_LED_PIN, UINT8_MAX - payload[0]);
analogWrite(G_LED_PIN, UINT8_MAX - payload[1]);
analogWrite(B_LED_PIN, UINT8_MAX - payload[2]);
} else {
analogWrite(R_LED_PIN, 0);
analogWrite(G_LED_PIN, UINT8_MAX);
analogWrite(B_LED_PIN, UINT8_MAX);
delay(500);
analogWrite(R_LED_PIN, UINT8_MAX);
delay(500);
analogWrite(R_LED_PIN, 0);
Serial.println("Decode failed");
}
}
void chirpErrorHandler(chirp_connect_error_code_t code) {
if (code != CHIRP_CONNECT_OK) {
const char *error_string = chirp_connect_error_code_to_string(code);
Serial.println(error_string);
exit(42);
}
}
void setupChirp(void) {
chirp = new_chirp_connect(CHIRP_APP_KEY, CHIRP_APP_SECRET);
if (chirp == NULL) {
Serial.println("Chirp initialisation failed.");
return;
}
chirp_connect_error_code_t err = chirp_connect_set_config(chirp, CHIRP_APP_CONFIG);
chirpErrorHandler(err);
char *info = chirp_connect_get_info(chirp);
Serial.println(info);
chirp_connect_free(info);
chirp_connect_callback_set_t callback_set = {
.on_state_changed = NULL,
.on_sending = NULL,
.on_sent = NULL,
.on_receiving = onReceivingCallback,
.on_received = onReceivedCallback
};
err = chirp_connect_set_callbacks(chirp, callback_set);
chirpErrorHandler(err);
err = chirp_connect_set_input_sample_rate(chirp, SAMPLE_RATE);
chirpErrorHandler(err);
err = chirp_connect_set_frequency_correction(chirp, 1.0096);
chirpErrorHandler(err);
err = chirp_connect_start(chirp);
chirpErrorHandler(err);
Serial.println("Chirp SDK initialised.");
Serial.flush();
}