이 튜토리얼에서는 여러 개의 NRF24L01 트랜시버 모듈로 구성된 아두이노 무선 네트워크를 구축하는 방법을 배웁니다. 아래 비디오를 시청하거나 작성된 튜토리얼을 읽어보세요.
개요
예시로 5개의 노드 네트워크를 만들었으며, 각 노드는 네트워크 내의 어떤 노드와도 통신할 수 있고 동시에 송신기와 수신기 역할을 모두 수행합니다. 이 예제는 사실상 훨씬 더 큰 네트워크를 만드는 방법을 설명하도록 구성되어 있습니다. 정확하게는 단일 RF 채널에서 총 3,125개의 모듈이 서로 통신할 수 있습니다. 어떻게 작동하는지 살펴보겠습니다.
이전 튜토리얼에서 NRF24L01 모듈과 RF24 라이브러리를 사용해 두 아두이노 보드 간의 무선 통신 방법을 이미 배웠습니다. 이제 이 라이브러리에 더해 RF24Network 라이브러리를 사용할 텐데, 이 라이브러리는 여러 보드가 서로 통신하는 아두이노 무선 네트워크를 쉽게 구축할 수 있게 해줍니다. 네트워크 토폴로지가 어떻게 작동하는지 알아보겠습니다.
다중 NRF24L01 모듈 통신
단일 NRF24L01 모듈은 동시에 최대 6개의 다른 모듈을 적극적으로 수신 대기할 수 있습니다.
RF24Network 라이브러리는 이 기능을 활용해 트리 토폴로지로 네트워크를 구성합니다. 하나의 노드가 베이스(마스터)가 되고, 다른 모든 노드는 해당 노드 또는 다른 노드의 자식이 됩니다. 각 노드는 최대 5개의 자식을 가질 수 있으며, 이는 5단계 깊이까지 가능하므로 총 3,125개의 노드 네트워크를 만들 수 있습니다. 각 노드는 트리 내에서의 위치를 정확히 설명하는 15비트 주소로 정의되어야 합니다.
노드 주소는 8진수 형식으로 정의할 수 있습니다. 마스터 또는 베이스의 주소는 00, 베이스의 자식 주소는 01~05, 01 노드의 자식 주소는 011~051 식입니다.
참고로 노드 011이 노드 02와 통신하려면 노드 01과 베이스 노드 00을 거쳐야 하므로, 이 두 노드가 항상 활성 상태여야 통신이 성공합니다.
RF24Network 라이브러리를 이용한 아두이노 무선 서보 모터 제어
이 튜토리얼의 메인 예제를 설명하기 전에, 라이브러리 작동 방식을 더 잘 이해하기 위해 두 아두이노 보드 간 통신이라는 더 간단한 예제를 먼저 만들어보겠습니다. 이 예제의 회로도는 다음과 같습니다.
이 아두이노 튜토리얼에 필요한 구성품은 아래 링크에서 구할 수 있습니다:
NRF24L01 트랜시버 모듈
서보 모터
포텐쇼미터
아두이노 나노
첫 번째 아두이노의 포텐쇼미터로 두 번째 아두이노의 서보 모터를 제어합니다. 이제 소스 코드를 살펴보겠습니다.
포텐쇼미터 쪽(노드 00) 코드:
/* 아두이노 무선 네트워크 - 다중 NRF24L01 튜토리얼 == 예제 01 - 서보 제어 / 노드 00 - 포텐쇼미터 == by Dejan, www.HowToMechatronics.com 라이브러리: nRF24/RF24, https://github.com/nRF24/RF24 nRF24/RF24Network, https://github.com/nRF24/RF24Network */
#include <RF24.h>
#include <RF24Network.h>
#include <SPI.h>
RF24 radio(10, 9); // nRF24L01 (CE,CSN)
RF24Network network(radio); // 라디오를 네트워크에 포함
const uint16_t this_node = 00; // 이 노드의 주소 (8진수 형식: 04, 031 등)
const uint16_t node01 = 01;
void setup() {
SPI.begin();
radio.begin();
network.begin(90, this_node); // (채널, 노드 주소)
}
void loop() {
network.update();
unsigned long potValue = analogRead(A0); // 포텐쇼미터 값 읽기
unsigned long angleValue = map(potValue, 0, 1023, 0, 180); // 0-180 범위로 변환 (서보 제어용)
RF24NetworkHeader header(node01); // (데이터가 갈 주소)
bool ok = network.write(header, &angleValue, sizeof(angleValue)); // 데이터 전송
}
먼저 RF24, RF24Network 라이브러리와 SPI 라이브러리를 포함해야 합니다. 그런 다음 RF24 객체를 생성하고 이를 RF24Network 객체에 포함시킵니다. 여기서는 노드 주소를 8진수 형식으로 정의합니다. 이 노드는 00, 서보 쪽 다른 노드는 01입니다.
setup 섹션에서는 채널과 이 노드의 주소를 설정해 네트워크를 초기화합니다.
loop 섹션에서는 네트워크에서 모든 동작이 일어나는 update() 함수를 지속적으로 호출해야 합니다. 그런 다음 포텐쇼미터 값을 읽고 서보 제어에 적합한 0~180 값으로 변환합니다. 데이터가 갈 노드의 주소를 네트워크 헤더에 할당하고, write() 함수로 데이터를 다른 노드로 전송합니다. 첫 번째 매개변수에 주소 정보, 두 번째에 전송할 데이터 포인터, 세 번째에 데이터 크기가 들어갑니다.
서보 쪽(노드 01) 코드:
/* 아두이노 무선 네트워크 - 다중 NRF24L01 튜토리얼 == 예제 01 - 서보 제어 / 노드 01 - 서보 모터 == */
#include <RF24.h>
#include <RF24Network.h>
#include <SPI.h>
#include <Servo.h>
RF24 radio(10, 9); // nRF24L01 (CE,CSN)
RF24Network network(radio); // 라디오를 네트워크에 포함
const uint16_t this_node = 01; // 우리 노드 주소 (8진수 형식)
Servo myservo; // 서보 객체 생성
void setup() {
SPI.begin();
radio.begin();
network.begin(90, this_node); // (채널, 노드 주소)
myservo.attach(3); // (서보 핀)
}
void loop() {
network.update();
while ( network.available() ) { // 수신 데이터가 있는가?
RF24NetworkHeader header;
unsigned long incomingData;
network.read(header, &incomingData, sizeof(incomingData)); // 수신 데이터 읽기
myservo.write(incomingData); // 다른 노드의 포텐쇼미터에 따라 서보 이동
}
}
서보 모터 쪽에서도 앞서 설명한 것과 같은 방식으로 라이브러리와 객체를 정의합니다. 여기서는 이 노드의 주소가 8진수 형식으로 01입니다. 서보 모터를 정의한 후 loop 섹션에서 while() 루프와 available() 함수로 수신 데이터가 있는지 지속적으로 확인합니다. 데이터가 있으면 네트워크 헤더를 생성해 데이터를 수신하고, incomingData 변수에 저장합니다. read() 함수로 데이터를 읽어 이 변수에 저장한 뒤, 다른 노드의 포텐쇼미터 값에 따라 서보 모터를 움직입니다.
다중 NRF24L01 모듈을 사용한 아두이노 무선 네트워크
이 예제를 이해했으니 이제 본격적으로 5개의 아두이노가 서로 통신하는 무선 네트워크를 구축해 보겠습니다. 예제의 블록 다이어그램은 다음과 같습니다.
베이스에서 포텐쇼미터로 노드 01의 서보 모터를 제어하고, 두 번째 포텐쇼미터로 노드 022의 LED를 제어하며, 버튼으로 노드 012의 LED를 제어합니다. 베이스의 LED는 노드 02의 포텐쇼미터로 제어합니다. 또한 노드 012의 적외선 센서로 노드 01의 LED를 제어합니다. 이 예제는 동시에 데이터를 송수신하는 방법과 서로 다른 브랜치의 노드와 통신하는 방법을 모두 보여줍니다. 이제 아두이노 코드를 살펴보겠습니다.
관련: DIY 아두이노 RC 송신기
베이스 00 소스 코드
/* 아두이노 무선 네트워크 - 다중 NRF24L01 튜토리얼 == 베이스/마스터 노드 00 == by Dejan, www.HowToMechatronics.com 라이브러리: nRF24/RF24, https://github.com/nRF24/RF24 nRF24/RF24Network, https://github.com/nRF24/RF24Network */
#include <RF24Network.h>
#include <RF24.h>
#include <SPI.h>
#define button 2
#define led 3
RF24 radio(10, 9); // nRF24L01 (CE,CSN)
RF24Network network(radio); // 라디오를 네트워크에 포함
const uint16_t this_node = 00; // 이 노드 주소 (8진수 형식)
const uint16_t node01 = 01; // 다른 노드 주소 (8진수 형식)
const uint16_t node012 = 012;
const uint16_t node022 = 022;
void setup() {
SPI.begin();
radio.begin();
network.begin(90, this_node); // (채널, 노드 주소)
radio.setDataRate(RF24_2MBPS);
pinMode(button, INPUT_PULLUP);
pinMode(led, OUTPUT);
}
void loop() {
network.update();
//===== 수신 =====//
while ( network.available() ) { // 수신 데이터가 있는가?
RF24NetworkHeader header;
unsigned long incomingData;
network.read(header, &incomingData, sizeof(incomingData)); // 수신 데이터 읽기
analogWrite(led, incomingData); // LED 01에 PWM 출력 (디밍)
}
//===== 송신 =====//
// 노드 01에서 서보 제어
unsigned long potValue = analogRead(A0);
unsigned long angleValue = map(potValue, 0, 1023, 0, 180); // 서보 제어용
RF24NetworkHeader header2(node01); // (데이터가 갈 주소)
bool ok = network.write(header2, &angleValue, sizeof(angleValue)); // 데이터 전송
// 노드 012에서 LED 제어
unsigned long buttonState = digitalRead(button);
RF24NetworkHeader header4(node012); // (데이터가 갈 주소)
bool ok3 = network.write(header4, &buttonState, sizeof(buttonState)); // 데이터 전송
// 노드 022에서 LED 제어
unsigned long pot2Value = analogRead(A1);
RF24NetworkHeader header3(node022); // (데이터가 갈 주소)
bool ok2 = network.write(header3, &pot2Value, sizeof(pot2Value)); // 데이터 전송
}
마스터 노드에서는 앞서 설명한 대로 라이브러리와 객체를 정의하고, 마스터가 데이터를 보낼 다른 모든 노드를 정의합니다. loop 섹션에서는 수신 데이터가 있는지 지속적으로 확인합니다. 데이터가 있으면 읽어서 incomingData 변수에 저장하고 LED 밝기 제어에 사용합니다. 이 데이터는 노드 02의 포텐쇼미터에서 옵니다. 노드 02의 코드를 보면 설정이 거의 동일함을 알 수 있습니다. 중요한 것은 데이터를 보낼 올바른 주소를 할당하는 것입니다. 여기서는 마스터 00입니다. 포텐쇼미터 값을 읽어 0~255의 적절한 PWM 값으로 변환한 뒤 마스터로 전송합니다. 여기서는 millis() 함수를 사용해 10밀리초 간격으로 데이터를 전송합니다.
노드 02 소스 코드
/* 아두이노 무선 네트워크 - 다중 NRF24L01 튜토리얼 == 노드 02 (마스터 노드 00의 자식) == */
#include <RF24Network.h>
#include <RF24.h>
#include <SPI.h>
RF24 radio(10, 9); // nRF24L01 (CE,CSN)
RF24Network network(radio); // 라디오를 네트워크에 포함
const uint16_t this_node = 02; // 우리 노드 주소 (8진수 형식)
const uint16_t master00 = 00; // 다른 노드 주소 (8진수 형식)
const unsigned long interval = 10; //ms // 다른 유닛으로 데이터 전송 주기
unsigned long last_sent; // 마지막으로 전송한 시간
void setup() {
SPI.begin();
radio.begin();
network.begin(90, this_node); // (채널, 노드 주소)
radio.setDataRate(RF24_2MBPS);
}
void loop() {
network.update();
//===== 송신 =====//
unsigned long now = millis();
if (now - last_sent >= interval) { // 데이터 전송 시간이면 전송!
last_sent = now;
unsigned long potValue = analogRead(A0);
unsigned long ledBrightness = map(potValue, 0, 1023, 0, 255);
RF24NetworkHeader header(master00); // (데이터가 갈 주소)
bool ok = network.write(header, &ledBrightness, sizeof(ledBrightness)); // 데이터 전송
}
}
다음으로 마스터에서 포텐쇼미터 데이터를 노드 01로 보내 서보 모터를 제어합니다.
노드 01 소스 코드
/* 아두이노 무선 네트워크 - 다중 NRF24L01 튜토리얼 == 노드 02 (마스터 노드 00의 자식) == */
#include <RF24Network.h>
#include <RF24.h>
#include <SPI.h>
#include <Servo.h>
#define led 2
RF24 radio(10, 9); // nRF24L01 (CE,CSN)
RF24Network network(radio); // 라디오를 네트워크에 포함
const uint16_t this_node = 01; // 우리 노드 주소 (8진수 형식)
const uint16_t master00 = 00; // 다른 노드 주소 (8진수 형식)
Servo myservo; // 서보 객체 생성
void setup() {
SPI.begin();
radio.begin();
network.begin(90, this_node); // (채널, 노드 주소)
radio.setDataRate(RF24_2MBPS);
myservo.attach(3); // (서보 핀)
pinMode(led, OUTPUT);
}
void loop() {
network.update();
//===== 수신 =====//
while ( network.available() ) { // 수신 데이터가 있는가?
RF24NetworkHeader header;
unsigned long incomingData;
network.read(header, &incomingData, sizeof(incomingData)); // 수신 데이터 읽기
if (header.from_node == 0) { // 데이터가 노드 02(마스터)에서 온 경우
myservo.write(incomingData); // 서보를 특정 각도로 이동
}
if (header.from_node == 10) { // 데이터가 노드 012에서 온 경우
digitalWrite(led, !incomingData); // LED 02 켜기/끄기
}
}
}
노드 01은 두 개의 다른 노드에서 데이터를 수신합니다. 하나는 서보 제어용(마스터에서), 다른 하나는 LED 제어용(노드 012의 적외선 센서에서)입니다.
이 경우 header.from_node 속성을 사용해 데이터가 어느 노드에서 왔는지 확인합니다. 마스터에서 온 데이터면 서보를 제어하고, 노드 012에서 온 데이터면 LED를 제어합니다.
노드 012 소스 코드
/* 아두이노 무선 네트워크 - 다중 NRF24L01 튜토리얼 == 노드 012 (노드 01의 자식) == */
#include <RF24Network.h>
#include <RF24.h>
#include <SPI.h>
#define led 2
#define IR 3
RF24 radio(10, 9); // nRF24L01 (CE,CSN)
RF24Network network(radio); // 라디오를 네트워크에 포함
const uint16_t this_node = 012; // 우리 노드 주소 (8진수 형식)
const uint16_t node01 = 01; // 다른 노드 주소 (8진수 형식)
void setup() {
SPI.begin();
radio.begin();
network.begin(90, this_node); // (채널, 노드 주소)
radio.setDataRate(RF24_2MBPS);
pinMode(led, OUTPUT);
pinMode(IR, INPUT);
}
void loop() {
network.update();
//===== 수신 =====//
while ( network.available() ) { // 수신 데이터가 있는가?
RF24NetworkHeader header;
unsigned long buttonState;
network.read(header, &buttonState, sizeof(buttonState)); // 수신 데이터 읽기
digitalWrite(led, !buttonState); // LED 켜기/끄기
}
//===== 송신 =====//
unsigned long irV = digitalRead(IR); // IR 센서 읽기
RF24NetworkHeader header8(node01);
bool ok = network.write(header8, &irV, sizeof(irV)); // 데이터 전송
}
노드 012에서는 송수신이 모두 일어납니다. 적외선 센서가 앞서 언급한 노드 01의 LED를 제어하고, 이 노드의 LED는 마스터의 버튼으로 제어됩니다.
노드 022 소스 코드
/* 아두이노 무선 네트워크 - 다중 NRF24L01 튜토리얼 == 노드 022 (노드 02의 자식) == */
#include <RF24Network.h>
#include <RF24.h>
#include <SPI.h>
#define led1 2
#define led2 3
#define led3 4
#define led4 5
RF24 radio(10, 9); // nRF24L01 (CE,CSN)
RF24Network network(radio); // 라디오를 네트워크에 포함
const uint16_t this_node = 022; // 우리 노드 주소 (8진수 형식)
const uint16_t master00 = 00; // 다른 노드 주소 (8진수 형식)
void setup() {
SPI.begin();
radio.begin();
network.begin(90, this_node); // (채널, 노드 주소)
radio.setDataRate(RF24_2MBPS);
pinMode(led1, OUTPUT);
pinMode(led2, OUTPUT);
pinMode(led3, OUTPUT);
pinMode(led4, OUTPUT);
}
void loop() {
network.update();
//===== 수신 =====//
while ( network.available() ) { // 수신 데이터가 있는가?
RF24NetworkHeader header;
unsigned long potValue;
network.read(header, &potValue, sizeof(potValue)); // 수신 데이터 읽기
// 마스터의 다른 포텐쇼미터에서 온 값에 따라 LED 켜기
if (potValue > 240) { digitalWrite(led1, HIGH); } else { digitalWrite(led1, LOW); }
if (potValue > 480) { digitalWrite(led2, HIGH); } else { digitalWrite(led2, LOW); }
if (potValue > 720) { digitalWrite(led3, HIGH); } else { digitalWrite(led3, LOW); }
if (potValue > 960) { digitalWrite(led4, HIGH); } else { digitalWrite(led4, LOW); }
}
}
마지막으로 노드 022의 LED들은 마스터의 다른 포텐쇼미터에서 오는 데이터로 제어됩니다.
요약하자면, 모든 것이 올바르게 연결되어 있고 모든 노드가 항상 활성 상태라면, 우리의 작업은 노드 주소를 정확히 지정하는 것으로 귀결되며, 그 뒤의 모든 복잡한 작업은 놀라운 RF24Network 라이브러리가 처리해줍니다.
이 아두이노 프로젝트를 즐기셨고 새로운 것을 배우셨기를 바랍니다. 아래 댓글 섹션에서 자유롭게 질문해 주세요.