|
|
| 3번째 줄: |
3번째 줄: |
| == 개요 == | | == 개요 == |
| 2.4GHz ISM 밴드는 별도의 사용 허가(라이선스) 없이 누구나 쓸 수 있는 주파수 대역으로, Wi-Fi·블루투스와 동일한 대역이다. | | 2.4GHz ISM 밴드는 별도의 사용 허가(라이선스) 없이 누구나 쓸 수 있는 주파수 대역으로, Wi-Fi·블루투스와 동일한 대역이다. |
| | |
| | RF(Radio Frequency, 무선 주파수)통신 대역이다. |
|
| |
|
| == 왜 2.4GHz인가? == | | == 왜 2.4GHz인가? == |
|
| |
|
| === 원래는 "버려진 쓰레기 대역"이었다 (역사적 이유) === | | === 원래는 "버려진 쓰레기 대역"이었다. === |
| 전자레인지, 산업용 가열기, 의료용 투열기 같은 기기가 부산물로 RF 에너지를 내뿜는 대역이었음. 이미 노이즈가 심한 대역이라, 규제 당국이 "어차피 시끄러우니 통신에 써도 되게 열어주자"고 개방한 것. 전자레인지가 2.45GHz에서 동작하기 때문에 이 대역은 이미 간섭에 "희생된" 상태였고, 그래서 오히려 부담 없이 통신용풀렸다. | | 전자레인지, 산업용 가열기, 의료용 투열기 같은 기기가 부산물로 RF 에너지를 내뿜는 대역이었음. 이미 노이즈가 심한 대역이라, 규제 당국이 "어차피 시끄러우니 통신에 써도 되게 열어주자"고 개방한 것. 전자레인지가 2.45GHz에서 동작하기 때문에 이 대역은 이미 간섭에 "희생된" 상태였고, 그래서 오히려 부담 없이 통신용풀렸다. |
|
| |
|
| 25번째 줄: |
27번째 줄: |
| * 홈 오토메이션 및 센서 네트워크 | | * 홈 오토메이션 및 센서 네트워크 |
|
| |
|
| = 사용 모듈 =
| | * |
| | |
| === nRF24L01+ 모듈 규격 ===
| |
| nRF24L01+ 모듈은 이 대역에서 동작하는 GFSK 방식의 반이중(half-duplex) 트랜시버로, 한 순간에는 송신 또는 수신 중 하나만 수행하지만 소프트웨어적으로 역할을 빠르게 전환해 양방향 통신을 구현할 수 있다.<ref>How To Mechatronics, "nRF24L01 – How It Works".</ref>
| |
| 주요 특징:
| |
| | |
| * '''초저전력''': 송신 시 약 12 mA(0 dBm 기준)로 LED 한 개보다 적게 소비하며, 대기 22 µA, 파워다운 900 nA 수준이다. | |
| * '''저가''': 모듈당 미화 2달러 내외로 매우 저렴하다.
| |
| * '''다중 노드''': 최대 125개 채널, 채널당 6개 주소(파이프)를 지원해 하나의 수신 허브가 최대 6개 송신 노드와 통신할 수 있다.
| |
| * '''하드웨어 프로토콜''': Enhanced ShockBurst 엔진이 패킷 조립, 자동 응답(ACK), 재전송, CRC를 하드웨어에서 처리한다.
| |
| | |
| {| class="wikitable"
| |
| |+주요 사양
| |
| !항목
| |
| !표준 모듈
| |
| !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 전원 권장.
| |
| | |
| === 배선 (Arduino Uno / Nano 기준) ===
| |
| {| class="wikitable"
| |
| |+nRF24L01+ ↔ 아두이노 연결
| |
| !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 슬레이브로 동작하며, 아두이노(마스터)의 하드웨어 SPI 핀(MOSI, MISO, SCK)에 반드시 연결해야 한다. 여기에 더해 두 개의 제어 핀을 사용한다.
| |
| | |
| * '''CE (Chip Enable)''': 송신/수신 모드를 활성화하는 핀. 임의의 디지털 핀 사용 가능.
| |
| * '''CSN (Chip Select Not)''': SPI 슬레이브 선택 핀. 임의의 디지털 핀 사용 가능.
| |
| | |
| === Enhanced ShockBurst ===
| |
| Enhanced ShockBurst는 하드웨어 기반 프로토콜 엔진으로 다음을 자동 처리한다.
| |
| | |
| # '''자동 패킷 조립''': 프리앰블, 주소, CRC 필드를 페이로드에 자동 추가.
| |
| # '''자동 응답 및 재전송''': 수신 측이 ACK를 자동 전송하며, 송신 측은 ACK를 못 받으면 최대 15회까지 자동 재전송.
| |
| # '''동적 페이로드 길이''': 1~32바이트 가변 길이 지원.
| |
| # '''멀티 파이프''': 서로 다른 주소를 가진 6개 데이터 파이프를 동시에 수신 대기.
| |
| | |
| == 라이브러리 설치 ==
| |
| 가장 널리 쓰이는 라이브러리는 TMRh20이 유지보수하는 '''RF24''' 라이브러리다.
| |
| | |
| # 아두이노 IDE를 연다.
| |
| # 메뉴에서 '''스케치 → 라이브러리 포함하기 → 라이브러리 관리'''를 선택한다.
| |
| # 검색창에 <code>RF24</code>를 입력하고 TMRh20의 <code>RF24 by TMRh20</code>을 설치한다.
| |
| | |
| 필요한 헤더는 다음과 같다.<syntaxhighlight lang="cpp">
| |
| #include <SPI.h> // SPI 통신
| |
| #include <nRF24L01.h> // 레지스터 정의
| |
| #include <RF24.h> // RF24 드라이버 클래스
| |
| </syntaxhighlight>
| |
| | |
| == 예제 1: 단방향 통신 ("Hello World") ==
| |
| 가장 기본적인 예제로, 송신기가 문자열을 1초마다 보내고 수신기가 시리얼 모니터에 출력한다. 최신 RF24 라이브러리는 주소를 <code>uint64_t</code> 대신 '''바이트 배열'''로 지정하는 방식을 권장한다.
| |
| | |
| === 송신기 (Transmitter) ===
| |
| <syntaxhighlight lang="cpp">
| |
| #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);
| |
| }
| |
| </syntaxhighlight>
| |
| | |
| === 수신기 (Receiver) ===
| |
| <syntaxhighlight lang="cpp">
| |
| #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);
| |
| }
| |
| }
| |
| </syntaxhighlight>{{참고|송신기와 수신기의 '''주소(address)''', '''데이터 속도(setDataRate)''', '''채널(setChannel)'''은 반드시 서로 일치해야 통신이 된다.}}
| |
| | |
| == 예제 2: 구조체로 여러 값 한 번에 전송 ==
| |
| 온도·습도·기압처럼 여러 값을 한 패킷(≤ 32바이트)에 담아 보낼 때 <code>struct</code>를 사용한다.
| |
| | |
| === 공통 구조체 정의 ===
| |
| <syntaxhighlight lang="cpp">
| |
| struct SensorData {
| |
| byte id; // 1바이트
| |
| float temperature; // 4바이트
| |
| float humidity; // 4바이트
| |
| int pressure; // 2바이트
| |
| }; // 총 11바이트 (32바이트 이하)
| |
| </syntaxhighlight>
| |
| | |
| === 송신기 ===
| |
| <syntaxhighlight lang="cpp">
| |
| #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);
| |
| }
| |
| </syntaxhighlight>
| |
| | |
| === 수신기 ===
| |
| <syntaxhighlight lang="cpp">
| |
| #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);
| |
| }
| |
| }
| |
| </syntaxhighlight>
| |
| | |
| == 예제 3: 양방향 통신 (송수신 전환) ==
| |
| 한 노드가 송신과 수신을 번갈아 수행한다. 두 노드는 서로 다른 주소를 쓴다.<syntaxhighlight lang="cpp">
| |
| #include <SPI.h>
| |
| #include <nRF24L01.h>
| |
| #include <RF24.h>
| |
| | |
| RF24 radio(9, 10);
| |
| | |
| // 두 개의 주소: 이 노드가 A라면 상대는 B
| |
| const byte addresses[][6] = {"NODE1", "NODE2"};
| |
| | |
| 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);
| |
| }
| |
| </syntaxhighlight>
| |
| | |
| == 주요 함수 정리 ==
| |
| {| class="wikitable"
| |
| |+RF24 라이브러리 핵심 메서드
| |
| !함수
| |
| !설명
| |
| |-
| |
| |<code>radio.begin()</code>
| |
| |모듈 초기화. 성공 시 <code>true</code> 반환
| |
| |-
| |
| |<code>radio.openWritingPipe(addr)</code>
| |
| |송신 파이프 주소 설정
| |
| |-
| |
| |<code>radio.openReadingPipe(n, addr)</code>
| |
| |n번(0~5) 수신 파이프 주소 설정
| |
| |-
| |
| |<code>radio.startListening()</code>
| |
| |수신 모드 진입
| |
| |-
| |
| |<code>radio.stopListening()</code>
| |
| |송신 모드 진입
| |
| |-
| |
| |<code>radio.write(&buf, len)</code>
| |
| |데이터 송신(ACK 수신 시 true)
| |
| |-
| |
| |<code>radio.available()</code>
| |
| |수신 데이터 존재 여부
| |
| |-
| |
| |<code>radio.read(&buf, len)</code>
| |
| |수신 데이터 읽기
| |
| |-
| |
| |<code>radio.setPALevel(level)</code>
| |
| |출력 레벨: <code>RF24_PA_MIN / LOW / HIGH / MAX</code>
| |
| |-
| |
| |<code>radio.setDataRate(rate)</code>
| |
| |속도: <code>RF24_250KBPS / RF24_1MBPS / RF24_2MBPS</code>
| |
| |-
| |
| |<code>radio.setChannel(ch)</code>
| |
| |채널(0~125) 설정
| |
| |-
| |
| |<code>radio.setRetries(delay, count)</code>
| |
| |재전송 간격·횟수 설정
| |
| |-
| |
| |<code>radio.printDetails()</code>
| |
| |디버깅 정보 시리얼 출력
| |
| |}
| |
| | |
| == 문제 해결 (Troubleshooting) ==
| |
| | |
| * '''전송이 계속 실패하거나 데이터가 안 옴''' → 전원 문제일 가능성이 가장 큼. VCC–GND에 10~100 µF 커패시터 추가, 외장 3.3 V 전원 사용.
| |
| * '''송·수신이 안 됨''' → 양쪽의 주소·데이터 속도·채널이 일치하는지 확인.
| |
| * '''<code>radio.begin()</code>이 false''' → SPI 배선(MOSI/MISO/SCK) 및 CE/CSN 핀 번호 확인. <code>printDetails()</code>로 레지스터 값이 0x00/0xFF만 나오면 배선 불량.
| |
| * '''거리가 짧음''' → 데이터 속도를 250 kbps로 낮추고, PA 레벨을 높이거나 PA/LNA 외장 안테나 모듈 사용.
| |
| * '''간헐적 끊김''' → Wi-Fi(1~11 채널) 간섭 회피를 위해 상위 채널(예: 100번 이상) 사용.
| |
| | |
| == 안전 및 규제 ==
| |
| | |
| * 2.4GHz ISM 대역은 비면허 대역이나, 출력 및 사용 규정은 국가별로 다를 수 있으므로 고출력 PA/LNA 모듈 사용 시 국내 전파 규정을 확인한다.
| |
| * 다수 모듈을 밀집 배치할 경우 상호 간섭을 줄이기 위해 채널을 분리한다.
| |
| | |
| == 같이 보기 ==
| |
| | |
| * [[아두이노]]
| |
| * [[SPI]]
| |
| * [[ISM 밴드]]
| |
| * [[블루투스]]
| |
| * [[Wi-Fi]]
| |
| | |
| == 각주 ==
| |
| <references />
| |
| | |
| == 외부 링크 ==
| |
| | |
| * [https://github.com/nRF24/RF24 RF24 라이브러리 공식 GitHub 저장소]
| |
| * [https://nrf24.github.io/RF24/classRF24.html RF24 클래스 레퍼런스 문서]
| |