Декодируем сигнал с OOK-модуляцией и паяем кликер
16 октября 2017
Из этой заметки вы узнаете, как своими руками сделать пульт для презентаций (a.k.a кликер) из Arduino Leonardo и дешевого радиомодуля на 433 МГц. Помимо прочего, этот проект интересен тем, что в нем реализовано декодирование сигнала с OOK-модуляцией, чему при желании можно найти массу практических применений. Также в проекте утилизируется возможность микроконтроллера ATmega32U4 мастерски притворяться мышью или клавиатурой.
Примечание: Если вы пропустили заметку О работе пультов и радиомодулей на 433 МГц, сначала вам может иметь смысл прочитать ее. Помимо прочего, там с наглядными картинками объясняется, что такое OOK-модуляция. Если же это вы и так знаете, тогда смело читайте дальше.
В собранном виде устройство выглядит как-то так:
На фото слева находится Arduino Leonardo с воткнутым нее Proto Shiled. На шилде крепится уже знакомый нам радиомодуль, а также SMA-разъем для антенны. Это, собственно, основная часть кликера, то есть, та, что подключается к компьютеру. Пульт хоть и не сложно сделать самому, лично мне это не показалось слишком интересным проектом. Поэтому я воспользовался уже готовым пультом с eBay. На фото он изображен справа.
Что же касается кода прошивки, он у меня получился таким:
#include <Keyboard.h>
#define DATA_PIN A0
#define PACKAGE_SIZE 25 // # of short/long signals
#define COMMAND_DOWN 1
#define COMMAND_STOP 2
#define COMMAND_UP 3
#define COMMAND_LOCK 4
char package[PACKAGE_SIZE];
const char dipCode[] = { 1, 1, 0, 1, 0, 0, 1, 0 };
// Wait until next package
// If we see a low voltage for a long time we consider it a sync
void sync() {
unsigned long startTime, endTime;
do {
startTime = millis();
while(analogRead(DATA_PIN) < 600) {
delayMicroseconds(3);
}
endTime = millis();
} while(endTime - startTime < 7);
}
// Try to receive a new package into the package[] array
// Returns true on success and false on error
bool readPackage() {
unsigned long startTime, highStart, highEnd;
bool firstLoop = true;
int delayCtr;
startTime = millis();
for(int sigNum = 0; sigNum < PACKAGE_SIZE; sigNum++) {
if(!firstLoop) {
// wait until high signal
delayCtr = 0;
while((analogRead(DATA_PIN) < 300) && (delayCtr < 500/3)) {
delayMicroseconds(3);
delayCtr++;
}
if(delayCtr == 500/3)
return false; // too long low signal
}
firstLoop = false;
highStart = micros();
// wait until low signal
delayCtr = 0;
while((analogRead(DATA_PIN) >= 300) && (delayCtr < 1100/3)) {
delayMicroseconds(3);
delayCtr++;
}
if(delayCtr == 1100/3)
return false; // too long high signal
highEnd = micros();
// long: 1000 us, short: 330 us
package[sigNum] = highEnd - highStart > 500 ? 1 : 0;
}
if(millis() - startTime > 33)
return false; // package too long
return true; // package received
}
// Check if package has a valid format
bool checkPackage() {
bool hasZeroes = false;
bool hasOnes = false;
if(package[PACKAGE_SIZE - 1] != 0)
return false;
for(int i = 0; i < PACKAGE_SIZE/2; i++) {
if(package[i*2] != package[i*2 + 1])
return false;
hasZeroes = hasZeroes || (package[i*2] == 0);
hasOnes = hasOnes || (package[i*2] == 1);
}
return hasZeroes && hasOnes;
}
// Check if DIP code is correct
bool checkDipCode() {
for(uint16_t i = 0; i < sizeof(dipCode); i++)
if(package[i*2] != dipCode[i])
return false;
return true;
}
// Get command number or 0 in case of error
int readCommand() {
uint16_t i = sizeof(dipCode);
int cmdNum = 1;
for(; i < PACKAGE_SIZE/2; i++, cmdNum++) {
if(package[i*2] == 1)
return cmdNum;
}
return 0;
}
void setup() {
Serial.begin(9600);
Keyboard.begin();
}
void loop() {
sync();
if(readPackage() && checkPackage() && checkDipCode()) {
Serial.print("Good: ");
for(int i = 0; i < PACKAGE_SIZE; i++)
if(package[i] == 1)
Serial.print("1");
else
Serial.print("0");
int command = readCommand();
Serial.println(", command: " + String(command));
if(command == COMMAND_DOWN) {
Keyboard.press(KEY_RIGHT_ARROW);
delay(150);
} else if(command == COMMAND_UP) {
Keyboard.press(KEY_LEFT_ARROW);
delay(150);
}
Keyboard.releaseAll();
}
}
Как видите, мудрить что-то с прерываниями я не стал, так как в фоне устройство все равно ничего не делает. Подробно разжевывать код я, пожалуй, не буду, так как его не много, он достаточно прост и, к тому же, снабжен комментариями.
Интересно, что код работает не только с изображенным на фото модулем, но также и с альтернативными модулями, вроде таких. Интересно также, что в плане себестоимости этот конкретный проект совершенно себя не оправдывает. По моим подсчетам, суммарная стоимость использованных в нем компонентов составила около 16$. В то же время, готовый кликер можно купить на AliExpress где-то за 7$, особенно если найти магазин с распродажей. Впрочем, как уже отмечалось, куда больший интерес представляет не столько сам проект, сколько широкие возможности, открываемые перед нами умением декодировать сигнал с OOK-модуляцией.
Вот и все, чем я хотел сегодня поделиться. Полную версию исходного кода вы найдете на GitHub. Ну и, как обычно, я буду рад любым вашим вопросам и дополнениям.
Метки: AVR, Беспроводная связь, Электроника.
Вы можете прислать свой комментарий мне на почту, или воспользоваться комментариями в Telegram-группе.