아두이노:RF통신 nRF24L01+
보이기
아두이노 관련 정보를 모으기 위한 틀. 틀:아두이노
- 아두이노:개요
- 아두이노:하드웨어
- 아두이노:코드
- 아두이노:핀 사용
- 아두이노:시리얼 통신
- 아두이노:편의함수
- 센서 사용
- 아두이노:LCD 사용
- 아두이노:스위치 사용
- 아두이노:릴레이
- 통신 관련, 정보 교환
- 아두이노:해결되지 않은 다양한 의문들
- 수업용 간단 실습
- 아두이노 모터 출력
- 미완문서
- 아두이노:스테핑 모터(미완)
- 분류:아두이노 프로젝트
nRF24L01+ 모듈을 이용한 통신을 다루는 문서. 2.4GHz의 이론에 대하여선 아두이노:2.4GHz 송수신 문서를 참고하자.
nRF24L01+ 모듈은 이 대역에서 동작하는 GFSK 방식의 반이중(half-duplex) 트랜시버로, 한 순간에는 송신 또는 수신 중 하나만 수행하지만 소프트웨어적으로 역할을 빠르게 전환해 양방향 통신을 구현할 수 있다.[1] 주요 특징:
- 초저전력: 송신 시 약 12 mA(0 dBm 기준)로 LED 한 개보다 적게 소비하며, 대기 22 µA, 파워다운 900 nA 수준이다.
- 저가: 모듈당 미화 2달러 내외로 매우 저렴하다.
- 다중 노드: 최대 125개 채널, 채널당 6개 주소(파이프)를 지원해 하나의 수신 허브가 최대 6개 송신 노드와 통신할 수 있다.
- 하드웨어 프로토콜: Enhanced ShockBurst 엔진이 패킷 조립, 자동 응답(ACK), 재전송, CRC를 하드웨어에서 처리한다.
| 항목 | 표준 모듈 | PA/LNA 모듈 |
|---|---|---|
| 동작 주파수 | 2.400 – 2.525 GHz | 2.400 – 2.525 GHz |
| 공급 전압 | 1.9 – 3.6 V | 1.9 – 3.6 V |
| 로직 입력 | 5 V 허용(tolerant) | 5 V 허용 |
| 출력 전력 | −18 dBm ~ 0 dBm | −18 dBm ~ +20 dBm |
| 송신 전류(최대) | 약 11.3 mA @ 0 dBm | 약 115 mA @ +20 dBm |
| 대기 전류 | 22 µA | 22 µA |
| 파워다운 전류 | 900 nA | 900 nA |
| 데이터 속도 | 250 kbps / 1 Mbps / 2 Mbps | 250 kbps / 1 Mbps / 2 Mbps |
| 페이로드 크기 | 1 – 32 바이트 | 1 – 32 바이트 |
| 채널 수 | 126 (1 MHz 간격) | 126 (1 MHz 간격) |
| 통신 거리 | 최대 약 100 m(개활지) | 최대 약 1,000 m(외장 안테나) |
- 기판 안테나형(PCB 안테나): 소형·컴팩트, 개활지 기준 약 100 m. 실내·벽 통과 시 거리 감소.
- PA/LNA + 외장 안테나형: 전력 증폭기(PA)와 저잡음 증폭기(LNA)를 내장해 최대 약 1 km까지 도달. 전류 소모가 크므로 별도 3.3 V 전원 권장.
| nRF24L01+ 핀 | 아두이노 Uno/Nano 핀 | 비고 |
|---|---|---|
| VCC | 3.3 V | 절대 5 V 연결 금지 |
| GND | GND | |
| CE | D9 (변경 가능) | 코드와 일치시킬 것 |
| CSN | D10 (변경 가능) | 코드와 일치시킬 것 |
| SCK | D13(칩마다 다름) | 하드웨어 SPI 고정 |
| MOSI | D11(칩마다 다름) | 하드웨어 SPI 고정 |
| MISO | D12(칩마다 다름) | 하드웨어 SPI 고정 |
| IRQ | (미사용) | 인터럽트용, 선택 사항 |
아두이노 내장 3.3 V 레귤레이터는 전류 공급 능력이 부족해 통신 불안정의 가장 흔한 원인이다.
- VCC–GND 사이에 10 µF ~ 100 µF 전해 커패시터를 최대한 모듈 가까이 병렬 연결한다.
- PA/LNA 모듈이나 다수 노드 사용 시 별도 외장 3.3 V 레귤레이터를 권장한다.
- 어댑터 보드(소켓형)를 쓰면 레귤레이터와 디커플링이 내장되어 편리하다.
모듈은 SPI 슬레이브로 동작하며, 아두이노(마스터)의 하드웨어 SPI 핀(MOSI, MISO, SCK)에 반드시 연결해야 한다. 여기에 더해 두 개의 제어 핀을 사용한다.
- CE (Chip Enable): 송신/수신 모드를 활성화하는 핀. 임의의 디지털 핀 사용 가능.
- CSN (Chip Select Not): SPI 슬레이브 선택 핀. 임의의 디지털 핀 사용 가능.
Enhanced ShockBurst는 하드웨어 기반 프로토콜 엔진으로 다음을 자동 처리한다.
- 자동 패킷 조립: 프리앰블, 주소, CRC 필드를 페이로드에 자동 추가.
- 자동 응답 및 재전송: 수신 측이 ACK를 자동 전송하며, 송신 측은 ACK를 못 받으면 최대 15회까지 자동 재전송.
- 동적 페이로드 길이: 1~32바이트 가변 길이 지원.
- 멀티 파이프: 서로 다른 주소를 가진 6개 데이터 파이프를 동시에 수신 대기.
가장 널리 쓰이는 라이브러리는 TMRh20이 유지보수하는 RF24 라이브러리다.
- 아두이노 IDE를 연다.
- 메뉴에서 스케치 → 라이브러리 포함하기 → 라이브러리 관리를 선택한다.
- 검색창에
RF24를 입력하고 TMRh20의RF24 by TMRh20을 설치한다.
필요한 헤더는 다음과 같다.
#include <SPI.h> // SPI 통신
#include <nRF24L01.h> // 레지스터 정의
#include <RF24.h> // RF24 드라이버 클래스
가장 기본적인 예제로, 송신기가 문자열을 1초마다 보내고 수신기가 시리얼 모니터에 출력한다. 최신 RF24 라이브러리는 주소를 uint64_t 대신 바이트 배열로 지정하는 방식을 권장한다.
#include <SPI.h>
#include <nRF24L01.h>
#include <RF24.h>
RF24 radio(9, 10); // CE, CSN 핀
const byte address[6] = "00001"; // 5바이트 주소(파이프)
void setup() {
Serial.begin(9600);
if (!radio.begin()) {
Serial.println(F("무선 모듈이 응답하지 않습니다!"));
while (1) {} // 무한 루프로 정지
}
radio.openWritingPipe(address); // 송신 파이프 열기
radio.setPALevel(RF24_PA_LOW); // 출력 레벨(테스트 시 LOW 권장)
radio.setDataRate(RF24_250KBPS); // 데이터 속도(거리↑, 안정성↑)
radio.stopListening(); // 송신 모드로 전환
}
void loop() {
const char text[] = "Hello World";
bool ok = radio.write(&text, sizeof(text));
Serial.println(ok ? F("전송 성공") : F("전송 실패"));
delay(1000);
}
#include <SPI.h>
#include <nRF24L01.h>
#include <RF24.h>
RF24 radio(9, 10); // CE, CSN 핀
const byte address[6] = "00001"; // 송신기와 동일해야 함
void setup() {
Serial.begin(9600);
if (!radio.begin()) {
Serial.println(F("무선 모듈이 응답하지 않습니다!"));
while (1) {}
}
radio.openReadingPipe(0, address); // 수신 파이프 0번 열기
radio.setPALevel(RF24_PA_LOW);
radio.setDataRate(RF24_250KBPS);
radio.startListening(); // 수신 모드로 전환
}
void loop() {
if (radio.available()) {
char text[32] = "";
radio.read(&text, sizeof(text));
Serial.print(F("수신: "));
Serial.println(text);
}
}
온도·습도·기압처럼 여러 값을 한 패킷(≤ 32바이트)에 담아 보낼 때 struct를 사용한다.
struct SensorData {
byte id; // 1바이트
float temperature; // 4바이트
float humidity; // 4바이트
int pressure; // 2바이트
}; // 총 11바이트 (32바이트 이하)
#include <SPI.h>
#include <nRF24L01.h>
#include <RF24.h>
RF24 radio(9, 10);
const byte address[6] = "00001";
struct SensorData {
byte id;
float temperature;
float humidity;
int pressure;
};
SensorData data;
void setup() {
radio.begin();
radio.openWritingPipe(address);
radio.setPALevel(RF24_PA_LOW);
radio.stopListening();
}
void loop() {
data.id = 1;
data.temperature = 24.5;
data.humidity = 55.2;
data.pressure = 1013;
radio.write(&data, sizeof(data));
delay(1000);
}
#include <SPI.h>
#include <nRF24L01.h>
#include <RF24.h>
RF24 radio(9, 10);
const byte address[6] = "00001";
struct SensorData {
byte id;
float temperature;
float humidity;
int pressure;
};
SensorData data;
void setup() {
Serial.begin(9600);
radio.begin();
radio.openReadingPipe(0, address);
radio.setPALevel(RF24_PA_LOW);
radio.startListening();
}
void loop() {
if (radio.available()) {
radio.read(&data, sizeof(data));
Serial.print(F("ID: ")); Serial.print(data.id);
Serial.print(F(" 온도: ")); Serial.print(data.temperature);
Serial.print(F(" 습도: ")); Serial.print(data.humidity);
Serial.print(F(" 기압: ")); Serial.println(data.pressure);
}
}
한 노드가 송신과 수신을 번갈아 수행한다. 두 노드는 서로 다른 주소를 쓴다.
#include <SPI.h>
#include <nRF24L01.h>
#include <RF24.h>
RF24 radio(9, 10);
// 두 개의 주소: 송신자와 수신자는 아래 노드 순서를 반대로 하면 된다.
const byte addresses[][6] = {"NODE1", "NODE2"}; // 문자열이 아니라 바이트 배열로 채널을 잡는다. 5개 문자열만 가능.
// 배열 크기는 6개로 하고, 마지막에는 \0을 넣어 종료를 알린다.
bool isNode1 = true; // 상대 노드에서는 false로 설정
void setup() {
Serial.begin(9600);
radio.begin();
if (isNode1) {
radio.openWritingPipe(addresses[1]); // NODE2로 송신
radio.openReadingPipe(1, addresses[0]); // NODE1로 수신
} else {
radio.openWritingPipe(addresses[0]);
radio.openReadingPipe(1, addresses[1]);
}
radio.setPALevel(RF24_PA_LOW);
}
void loop() {
// --- 송신 단계 ---
radio.stopListening();
const char msg[] = "PING";
radio.write(&msg, sizeof(msg));
// --- 수신 단계 ---
radio.startListening();
unsigned long started = millis();
while (!radio.available()) {
if (millis() - started > 200) return; // 200ms 타임아웃
}
char reply[32] = "";
radio.read(&reply, sizeof(reply));
Serial.print(F("응답 수신: "));
Serial.println(reply);
delay(500);
}
| 함수 | 설명 |
|---|---|
radio.begin()
|
모듈 초기화. 성공 시 true 반환
|
radio.openWritingPipe(addr)
|
송신 파이프 주소 설정 |
radio.openReadingPipe(n, addr)
|
n번(0~5) 수신 파이프 주소 설정 |
radio.startListening()
|
수신 모드 진입 |
radio.stopListening()
|
송신 모드 진입 |
radio.write(&buf, len)
|
데이터 송신(ACK 수신 시 true) |
radio.available()
|
수신 데이터 존재 여부 |
radio.read(&buf, len)
|
수신 데이터 읽기 |
radio.setPALevel(level)
|
출력 레벨: RF24_PA_MIN / LOW / HIGH / MAX
|
radio.setDataRate(rate)
|
속도: RF24_250KBPS / RF24_1MBPS / RF24_2MBPS
|
radio.setChannel(ch)
|
채널(0~125) 설정 |
radio.setRetries(delay, count)
|
재전송 간격·횟수 설정 |
radio.printDetails()
|
디버깅 정보 시리얼 출력 |
- 전송이 계속 실패하거나 데이터가 안 옴 → 전원 문제일 가능성이 가장 큼. VCC–GND에 10~100 µF 커패시터 추가, 외장 3.3 V 전원 사용.
- 송·수신이 안 됨 → 양쪽의 주소·데이터 속도·채널이 일치하는지 확인.
radio.begin()이 false → SPI 배선(MOSI/MISO/SCK) 및 CE/CSN 핀 번호 확인.printDetails()로 레지스터 값이 0x00/0xFF만 나오면 배선 불량.- 거리가 짧음 → 데이터 속도를 250 kbps로 낮추고, PA 레벨을 높이거나 PA/LNA 외장 안테나 모듈 사용.
- 간헐적 끊김 → Wi-Fi(1~11 채널) 간섭 회피를 위해 상위 채널(예: 100번 이상) 사용.
- 2.4GHz ISM 대역은 비면허 대역이나, 출력 및 사용 규정은 국가별로 다를 수 있으므로 고출력 PA/LNA 모듈 사용 시 국내 전파 규정을 확인한다.
- 다수 모듈을 밀집 배치할 경우 상호 간섭을 줄이기 위해 채널을 분리한다.
- ↑ How To Mechatronics, "nRF24L01 – How It Works".