Embedded Development Wireless Keyboard Receiver in Practice: NRF24L01+ Dual-Mode Design, Low-Cost Custom Input Device
Want to make your own wireless keyboard receiver? Those wireless keyboards costing just tens of yuan on the market actually use NRF24L01+ modules at their core. Today we’ll disassemble this black box and build a dual-mode wireless keyboard receiver for less than 50 yuan - it can work as both a regular keyboard and a macro keyboard.
The inspiration for this project came from the mess of cables in my workshop. Every time I switch devices, I have to unplug and replug USB, which is annoying. So I decided to make my own, with customizable key mappings - it’s just awesome.
What Do You Need to Prepare?
| Item | Model/Specification | Price |
|---|---|---|
| Main Controller | Arduino Pro Micro (ATmega32U4) | ¥25 |
| Wireless Module | NRF24L01+ 2.4GHz | ¥8 |
| Receiver Module | NRF24L01+ (with PCB antenna) | ¥10 |
| Key Switches | Mechanical keyboard switches ×6 | ¥15 |
| PCB Prototype Board | 5×7cm | ¥5 |
| USB Cable | Micro USB | ¥5 |
| Jumper Wires | Male-to-male/Female-to-female | ¥5 |
| Total | ¥73 |
Note: For the receiver, it’s recommended to buy the version with PCB antenna for more stable signal. The transmitter can use a regular antenna since the distance is short.
Step 1: Hardware Connection
Let’s first look at the transmitter (keyboard side) wiring. The SPI pins of ATmega32U4 are fixed, don’t connect them wrong:
NRF24L01+ → Arduino Pro Micro
---------------------------------------
VCC → 3.3V (Never connect to 5V, it will burn!)
GND → GND
CE → D9
CSN → D10
SCK → D15
MOSI → D16
MISO → D14
IRQ → Not connected (optional)
⚠️ Pitfall Warning: NRF24L01+ is a 3.3V device. Although Pro Micro has 3.3V output, the current is limited. If the power supply is unstable, the module will repeatedly restart. It’s recommended to add a 10μF capacitor between VCC and GND.
The receiver wiring is similar, except the CE and CSN pins can be changed:
NRF24L01+ → Arduino Pro Micro (Receiver)
--------------------------------------------
VCC → 3.3V
GND → GND
CE → D8
CSN → D9
SCK → D15
MOSI → D16
MISO → D14
Step 2: Install Dependency Libraries
Open Arduino IDE and install the following libraries:
# Search and install via library manager, or use CLI
arduino-cli lib install "RF24"
arduino-cli lib install "HID-Project"
Or in the IDE:
-
Click “Tools” → “Manage Libraries…” in the menu bar
-
Search for
RF24in the library manager and click install -
Similarly search for and install the
HID-Projectlibrary
Principle Explanation: The RF24 library handles NRF24L01+ wireless communication, while HID-Project makes Arduino emulate a USB keyboard. ATmega32U4 natively supports USB HID, which is why it’s more suitable for this project than Uno/Nano.
Step 3: Transmitter Code (Keyboard Side)
#include <SPI.h>
#include <nRF24L01.h>
#include <RF24.h>
#include <HID-Project.h>
// Pin definitions
#define CE_PIN 9
#define CSN_PIN 10
RF24 radio(CE_PIN, CSN_PIN);
// Communication pipe addresses
const uint64_t pipes[2] = {0xF0F0F0F0E7LL, 0xF0F0F0F0D2LL};
// Key mapping (customizable)
struct KeyMapping {
uint8_t pin;
HIDKeys key;
bool lastState;
};
KeyMapping keys[] = {
{2, KEY_F1, HIGH}, // F1 macro key
{3, KEY_F2, HIGH}, // F2 macro key
{4, KEY_F3, HIGH}, // F3 macro key
{5, KEY_F4, HIGH}, // F4 macro key
{6, KEY_F5, HIGH}, // F5 macro key
{7, KEY_F6, HIGH}, // F6 macro key
};
void setup() {
Serial.begin(115200);
// Initialize key pins
for (int i = 0; i < sizeof(keys)/sizeof(keys[0]); i++) {
pinMode(keys[i].pin, INPUT_PULLUP);
}
// Initialize NRF24L01+
radio.begin();
radio.setPALevel(RF24_PA_HIGH);
radio.setDataRate(RF24_2MBPS);
radio.openWritingPipe(pipes[1]);
radio.openReadingPipe(1, pipes[0]);
radio.stopListening();
Serial.println("Transmitter ready");
}
void loop() {
for (int i = 0; i < sizeof(keys)/sizeof(keys[0]); i++) {
bool currentState = digitalRead(keys[i].pin);
if (currentState == LOW && keys[i].lastState == HIGH) {
// Key pressed
uint8_t packet[2] = {0x01, (uint8_t)keys[i].key};
radio.write(&packet, sizeof(packet));
Serial.print("Key pressed: ");
Serial.println(keys[i].key);
delay(50); // Debounce
}
keys[i].lastState = currentState;
}
}
Step 4: Receiver Code
#include <SPI.h>
#include <nRF24L01.h>
#include <RF24.h>
#include <HID-Project.h>
// Pin definitions
#define CE_PIN 8
#define CSN_PIN 9
RF24 radio(CE_PIN, CSN_PIN);
const uint64_t pipes[2] = {0xF0F0F0F0E7LL, 0xF0F0F0F0D2LL};
// Mode switching: 0=regular keyboard, 1=macro keyboard
uint8_t mode = 0;
#define MODE_SWITCH_PIN A0
void setup() {
Serial.begin(115200);
pinMode(MODE_SWITCH_PIN, INPUT_PULLUP);
// Initialize NRF24L01+
radio.begin();
radio.setPALevel(RF24_PA_HIGH);
radio.setDataRate(RF24_2MBPS);
radio.openWritingPipe(pipes[1]);
radio.openReadingPipe(1, pipes[0]);
radio.startListening();
// Initialize USB HID
BootKeyboard.begin();
Serial.println("Receiver ready, waiting for connection...");
}
void loop() {
// Check mode switch
if (digitalRead(MODE_SWITCH_PIN) == LOW) {
mode = 1 - mode;
delay(300); // Debounce
Serial.print("Switched to mode: ");
Serial.println(mode ? "Macro Keyboard" : "Regular Keyboard");
}
// Receive wireless data
if (radio.available()) {
uint8_t packet[2];
radio.read(&packet, sizeof(packet));
if (packet[0] == 0x01) {
// Key event
HIDKeys key = (HIDKeys)packet[1];
if (mode == 0) {
// Regular mode: direct forwarding
BootKeyboard.press(key);
delay(50);
BootKeyboard.release(key);
} else {
// Macro mode: execute preset macro
executeMacro(key);
}
Serial.print("Received key: ");
Serial.println(key);
}
}
}
// Macro definition example
void executeMacro(HIDKeys key) {
switch (key) {
case KEY_F1:
// Ctrl+C copy
BootKeyboard.press(KEY_LEFT_CTRL);
BootKeyboard.press('c');
delay(50);
BootKeyboard.releaseAll();
break;
case KEY_F2:
// Ctrl+V paste
BootKeyboard.press(KEY_LEFT_CTRL);
BootKeyboard.press('v');
delay(50);
BootKeyboard.releaseAll();
break;
case KEY_F3:
// Alt+Tab switch window
BootKeyboard.press(KEY_LEFT_ALT);
BootKeyboard.press(KEY_TAB);
delay(50);
BootKeyboard.releaseAll();
break;
case KEY_F4:
// Win+D show desktop
BootKeyboard.press(KEY_LEFT_GUI);
BootKeyboard.press('d');
delay(50);
BootKeyboard.releaseAll();
break;
default:
BootKeyboard.press(key);
delay(50);
BootKeyboard.release(key);
}
}
Step 5: Testing and Verification
After flashing the code, test according to the following steps:
-
Connect the transmitter Pro Micro to the computer via USB, flash the transmitter code
-
Connect the receiver Pro Micro to the computer, flash the receiver code
-
Press the keys on the transmitter, observe if the receiver correctly outputs the corresponding keyboard events. I tested in my workshop, with the transmitter on the workbench and the receiver plugged into the HTPC in the living room. It worked stably even through two walls.
Common Problem Troubleshooting
Problem 1: Receiver doesn’t receive data
-
Cause: Pipe address mismatch or insufficient power supply
-
Solution: Check if the
pipes[]array is consistent on both ends; use a multimeter to measure if VCC is stable at 3.3V; add a 10μF capacitor
Problem 2: Computer doesn’t recognize USB keyboard
-
Cause: ATmega32U4 firmware issue or poor quality USB cable
-
Solution: Try a USB cable that can transmit data (some can only charge); reflash the Bootloader
Problem 3: Noticeable key delay
-
Cause: Wireless rate set too low or debounce time too long
-
Solution: Change
setDataRatetoRF24_2MBPS; reduce debounce time from 50ms to 20ms
Problem 4: Macro keys execute multiple times
-
Cause: Key debounce logic issue
-
Solution: Ensure
lastStatestate machine is updated correctly; add 100ms delay after macro execution
Advanced Usage
This project is just a beginning. You can:
-
Add a small OLED screen to display current mode, battery level, and other information
-
Add a lithium battery and charging module to make a truly wireless portable version
-
Use ESP32-C3 to replace Pro Micro, supporting WiFi/Bluetooth dual-mode communication
-
Add a rotary encoder to implement knob control (adjusting volume, scrolling pages, etc.)
-
Design a custom PCB enclosure and use 3D printing to create a complete keyboard form factor
Summary
With a cost of 70 yuan, we made a dual-mode wireless keyboard receiver. The core is the combination of NRF24L01+ module + ATmega32U4. The greatest value of this project is not saving money, but being completely controllable - you can add whatever features you want without having to rely on manufacturers.
I’m already considering the next generation design: using ESP32-C3 as the main controller, supporting WiFi and Bluetooth dual-mode, and able to update firmware via OTA. I’ll share it with everyone then.
Hope this blog post is helpful to you!
Related Resources: