Li-Fi коммуникация на Arduino Uno
- Войдите на сайт для отправки комментариев
Предыстория: Li-Fi - способ передачи данных посредством света. На текущий момент существуют промышленные устройства для домашних персональных компьютеров, работающие, как будто Wi-Fi, только вместо роутера в потолок вкручивается "лампочка", а в usb-порт вставляется "аналог сетевой карты", только световой. Стоит аналог роутера что-то вроде 900$, а сетевушка около 300$. Немного офигев от цен, погуглил, и таки да, господин Джон Пьет реализовал вариант Li-Fi коммуникацию для ардуин, и его проект я и хочу здесь представить, а то всё радио да радио. Умеющие читать на английском могут сразу обратиться к оригиналу:
https://github.com/jpiat/arduino/wiki/Arduino-simple-Visible-Light-Communication
Необходимое оборудование:
2x Arduino UNO - одна передатчик, вторая приёмник
2x светодиода одинакового цвета (у меня работало лучше всего с красными)
1х резистор 1 МОм
1х резистор 120 Ом (не обязательно)
Схема соединения:
Передатчик
D2 ------[resistor]----- led -------------- GND
Резистор очень желателен, но не обязателен.
Приёмник
|----1Mohm-----|
A3 ------|--- +led- ----|-------GND
Код передатчика:
/*
LiFi Emitter
The purpose of this demos is to demonstrate data communication using
a pair of blue LED (one led as emitter one led as receiver).
Communication can go at up to 600 bps (can depend on led quality)
Emitter hardware is the following:
I/O D2 ------[resistor]----- led -------------- GND
Using a blue led should not require resistor, one may be needed for red or green
A byte is sent as follow :
Start(0) 8bit data Stop(1)
Each bit is coded in manchester with
0 -> 10
1 -> 01
A data frame is formatted as follow :
0xAA : sent a number of time to help the received compute a signal average for the thresholding of analog values
0xD5 : synchronization byte to break preamble
0x02 : STX start of frame
N times Effective data excluding command symbols, with N < 32
0x03 : ETX end of frame
*/
#include <TimerOne.h>
#include <util/atomic.h>
//Start of what should be an include ...
//#define TRANSMIT_SERIAL
// change to alter communication speed,
// will lower values will result in faster communication
// the receiver must be tuned to the same value
#define SYMBOL_PERIOD 500 /* Defined a symbol period in us*/
#define WORD_LENGTH 10 /* Each byte is encoded on 10-bit with start, byte, stop */
#define SYNC_SYMBOL 0xD5 /* Synchronization symbol to send after a preamble, before data communication happens */
#define ETX 0x03
#define STX 0x02
//Fast manipulation of LED IO.
//These defines are for a LED connected on D13
/*#define OUT_LED() DDRB |= (1 << 5);
#define SET_LED() PORTB |= (1 << 5)
#define CLR_LED() PORTB &= ~(1 << 5)
*/
//These defines are for a RGB led connected to D2, D3, D4
/*#define OUT_LED() DDRD |= ((1 << 2) | (1 << 3) | (1 << 4))
#define SET_LED() PORTD |= ((1 << 2) | (1 << 3) | (1 << 4))
#define CLR_LED() PORTD &= ~((1 << 2) | (1 << 3) | (1 << 4))
*/
//These defines are for a single led connected to D2
#define OUT_LED() DDRD |= ((1 << 2))
#define SET_LED() PORTD |= ((1 << 2))
#define CLR_LED() PORTD &= ~((1 << 2))
unsigned char frame_buffer [38] ; //buffer for frame
char frame_index = -1; // index in frame
char frame_size = -1 ; // size of the frame to be sent
//state variables of the manchester encoder
unsigned char bit_counter = 0 ;
unsigned short data_word = 0 ; //8bit data + start + stop
unsigned char half_bit = 0 ;
unsigned long int manchester_data ;
void to_manchester(unsigned char data, unsigned long int * data_manchester) {
unsigned int i ;
(*data_manchester) = 0x02 ; // STOP symbol
(*data_manchester) = (*data_manchester) << 2 ;
for (i = 0 ; i < 8; i ++) {
if (data & 0x80) (*data_manchester) |= 0x02 ; // data LSB first
else (*data_manchester) |= 0x01 ;
(*data_manchester) = (*data_manchester) << 2 ;
data = data << 1 ; // to next bit
}
(*data_manchester) |= 0x01 ; //START symbol
}
//emitter interrupt
void emit_half_bit() {
if (manchester_data & 0x01) {
SET_LED();
} else {
CLR_LED();
}
bit_counter -- ;
manchester_data = (manchester_data >> 1);
if (bit_counter == 0) {
// is there still bytes to send in the frame ?
manchester_data = 0xAAAAAAAA ; // keep sending ones if nothing to send
if (frame_index >= 0 ) {
if (frame_index < frame_size) {
/*
Serial.println(frame_index, DEC);
Serial.println(frame_buffer[frame_index], HEX);
*/
to_manchester(frame_buffer[frame_index], &manchester_data);
frame_index ++ ;
} else {
frame_index = -1 ;
frame_size = -1 ;
}
}
bit_counter = WORD_LENGTH * 2 ;
//Serial.println(manchester_data, BIN);
}
}
void init_frame(unsigned char * frame) {
memset(frame, 0xAA, 3);
frame[3] = SYNC_SYMBOL ;
frame[4] = STX;
frame_index = -1 ;
frame_size = -1 ;
}
int create_frame(char * data, int data_size, unsigned char * frame) {
memcpy(&(frame[5]), data, data_size);
frame[5 + data_size] = ETX;
return 1 ;
}
int write(char * data, int data_size) {
if (frame_index >= 0) return -1 ;
if (data_size > 32) return -1 ;
create_frame(data, data_size, frame_buffer);
ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
frame_index = 0 ;
frame_size = data_size + 6 ;
}
return 0 ;
}
int transmitter_available() {
if (frame_index >= 0) return 0 ;
return 1 ;
}
void init_emitter() {
manchester_data = 0xFFFFFFFF ;
bit_counter = WORD_LENGTH * 2 ;
}
// the setup routine runs once when you press reset:
void setup()
{
// use D4 as ground [ by megavoid ]
//pinMode(4, OUTPUT);
//digitalWrite(4, LOW);
// initialize serial communication at 115200 bits per second:
Serial.begin(115200);
OUT_LED();
init_frame(frame_buffer);
init_emitter();
Timer1.initialize(SYMBOL_PERIOD); //1200 bauds
Timer1.attachInterrupt(emit_half_bit);
}
// the loop routine runs over and over again forever:
char * msg = "Hello World";
char com_buffer [32];
char com_buffer_nb_bytes = 0;
void loop()
{
#ifdef TRANSMIT_SERIAL
// constructing the data frame only if transmitter is ready to transmit
if (Serial.available() && transmitter_available()) {
char c = Serial.read();
com_buffer[com_buffer_nb_bytes] = c ;
com_buffer_nb_bytes ++ ;
if (com_buffer_nb_bytes >= 32 || c == '\n') {
if (write(com_buffer, com_buffer_nb_bytes) < 0) {
Serial.println("Transmitter is busy");
} else {
com_buffer_nb_bytes = 0 ;
}
}
}
delay(10);
#else
static int i = 0 ;
memcpy(com_buffer, msg, 11);
com_buffer[11] = i + '0' ;
if (write(com_buffer, 12) < 0) {
delay(10);
} else {
i ++ ;
if (i > 9) i = 0 ;
}
#endif
}
Код приёмника:
/*
LiFi Receiver
The purpose of this demos is to demonstrate data communication using
a pair of blue LED (one led as emitter one led as receiver).
Communication can go at up to 600 bps (can depend on led quality)
Receiver hardware :
|----1Mohm-----|
A3 ------|--- +led- ----|-------GND
A byte is sent as follow :
Start(0) 8bit data Stop(1), LSB first : 0 b0 b1 b2 b3 b4 b5 b6 b7 1
Each bit is coded in manchester with
time is from left to right
0 -> 10
1 -> 01
A data frame is formatted as follow :
0xAA : sent a number of time to help the receiver compute a signal average for the thresholding of analog values
0xD5 : synchronization byte to indicate start of a frame, breaks the regularity of the 0x55 pattern to be easily
0x02 : STX start of frame
N times Effective data excluding command symbols, max length 32 bytes
0x03 : ETX end of frame
*/
#include <TimerOne.h>
enum receiver_state {
IDLE, //waiting for sync
SYNC, //synced, waiting for STX
START, //STX received
DATA //receiving DATA
};
//#define DEBUG
//#define DEBUG_ANALOG
#define INT_REF /* Comment this to use AVCC reference voltage. To be used when the receiver LED generate low levels */
enum receiver_state frame_state = IDLE ;
//This defines receiver properties
#define SENSOR_PIN 3
#define SYMBOL_PERIOD 500
#define SAMPLE_PER_SYMBOL 4
#define WORD_LENGTH 10 // a byte is encoded as a 10-bit value with start and stop bits
#define SYNC_SYMBOL 0xD5 // this symbol breaks the premanble of the frame
#define ETX 0x03 // End of frame symbol
#define STX 0x02 //Start or frame symbol
// global variables for frame decoding
char frame_buffer[38] ;
int frame_index = -1 ;
int frame_size = -1 ;
//state variables of the thresholder
unsigned int signal_mean = 0 ;
unsigned long acc_sum = 0 ; //used to compute the signal mean value
unsigned int acc_counter = 0 ;
//manechester decoder state variable
long shift_reg = 0;
//Start of ADC managements functions
void ADC_setup() {
ADCSRA = bit (ADEN); // turn ADC on
ADCSRA |= bit (ADPS0) | bit (ADPS1) | bit (ADPS2); // Prescaler of 128
#ifdef INT_REF
ADMUX = bit (REFS0) | bit (REFS1); // internal 1.1v reference
#else
ADMUX = bit (REFS0) ; // external 5v reference
#endif
}
void ADC_start_conversion(int adc_pin) {
ADMUX &= ~(0x07) ; //clearing enabled channels
ADMUX |= (adc_pin & 0x07) ; // AVcc and select input port
bitSet (ADCSRA, ADSC) ;
}
int ADC_read_conversion() {
while (bit_is_set(ADCSRA, ADSC));
return ADC ;
}
//End of ADC management functions
#define START_SYMBOL 0x02
#define STOP_SYMBOL 0x01
#define START_STOP_MASK ((STOP_SYMBOL << 20) | (START_SYMBOL << 18) | STOP_SYMBOL) //STOP/START/16bits/STOP
#define SYNC_SYMBOL_MANCHESTER (0x6665) /* Sync symbol, encoded as a 16-bit Manchester value to help the decoding */
inline int is_a_word(long * manchester_word, int time_from_last_sync, unsigned int * detected_word) {
if (time_from_last_sync >= 20 || frame_state == IDLE) { // we received enough bits to test the sync
if (((*manchester_word) & START_STOP_MASK) == (START_STOP_MASK)) { // testing first position
(*detected_word) = ((*manchester_word) >> 2) & 0xFFFF;
if (frame_state == IDLE) {
if ((*detected_word) == SYNC_SYMBOL_MANCHESTER) return 2 ;
}
return 1 ;
// byte with correct framing
} else if (frame_state != IDLE && time_from_last_sync == 20) {
(*detected_word) = ((*manchester_word) >> 2) & 0xFFFF;
return 1 ;
}
}
return 0 ;
}
inline int insert_edge( long * manchester_word, char edge, int edge_period, int * time_from_last_sync, unsigned int * detected_word) {
int new_word = 0 ;
int is_a_word_value = 0 ;
int sync_word_detect = 0 ;
if ( ((*manchester_word) & 0x01) != edge ) { //mak sure we don't have same edge ...
if (edge_period > (SAMPLE_PER_SYMBOL + 1)) {
unsigned char last_bit = (*manchester_word) & 0x01 ;
(*manchester_word) = ((*manchester_word) << 1) | last_bit ; // signal was steady for longer than a single symbol,
(*time_from_last_sync) += 1 ;
is_a_word_value = is_a_word(manchester_word, (*time_from_last_sync), detected_word);
if (is_a_word_value > 0) { //found start stop framing
new_word = 1 ;
(*time_from_last_sync) = 0 ;
if (is_a_word_value > 1) sync_word_detect = 1 ; //we detected framing and sync word in manchester format
}
}
//storing edge value in word
if (edge < 0) {
(*manchester_word) = ( (*manchester_word) << 1) | 0x00 ; // signal goes down
} else {
(*manchester_word) = ( (*manchester_word) << 1) | 0x01 ; // signal goes up
}
(*time_from_last_sync) += 1 ;
is_a_word_value = is_a_word(manchester_word, (*time_from_last_sync), detected_word);
if (sync_word_detect == 0 && is_a_word_value > 0) { //if sync word was detected at previous position, don't take word detection into account
new_word = 1 ;
(*time_from_last_sync) = 0 ;
}
} else {
new_word = -1 ;
}
return new_word ;
}
#define EDGE_THRESHOLD 4 /* Defines the voltage difference between two samples to detect a rising/falling edge. Can be increased depensing on the environment */
int oldValue = 0 ;
int steady_count = 0 ;
int dist_last_sync = 0 ;
unsigned int detected_word = 0;
int new_word = 0;
char old_edge_val = 0 ;
void sample_signal_edge() {
char edge_val ;
//int sensorValue = analogRead(SENSOR_PIN); // this is too slow and should be replaced with interrupt-driven ADC
int sensorValue = ADC_read_conversion(); // read result of previously triggered conversion
ADC_start_conversion(SENSOR_PIN); // start a conversion for next loop
#ifndef DEBUG
#ifdef DEBUG_ANALOG
Serial.println(sensorValue, DEC);
#endif
#endif
if ((sensorValue - oldValue) > EDGE_THRESHOLD) edge_val = 1 ;
else if ((oldValue - sensorValue) > EDGE_THRESHOLD) edge_val = -1;
else edge_val = 0 ;
oldValue = sensorValue ;
if (edge_val == 0 || edge_val == old_edge_val || (edge_val != old_edge_val && steady_count < 2)) {
if ( steady_count < (4 * SAMPLE_PER_SYMBOL)) {
steady_count ++ ;
}
} else {
new_word = insert_edge(&shift_reg, edge_val, steady_count, &(dist_last_sync), &detected_word);
if (dist_last_sync > (8 * SAMPLE_PER_SYMBOL)) { // limit dist_last_sync to avoid overflow problems
dist_last_sync = 32 ;
}
//if(new_word >= 0){
steady_count = 0 ;
//}
}
old_edge_val = edge_val ;
}
int add_byte_to_frame(char * frame_buffer, int * frame_index, int * frame_size, enum receiver_state * frame_state , unsigned char data) {
if (data == SYNC_SYMBOL/* && (*frame_index) < 0*/) {
(*frame_index) = 0 ;
(*frame_size) = 0 ;
(*frame_state) = SYNC ;
//Serial.println("SYNC");
return 0 ;
}
if ((*frame_state) != IDLE) { // we are synced
frame_buffer[*frame_index] = data ;
(*frame_index) ++ ;
if (data == STX) {
//Serial.println("START");
(*frame_state) = START ;
return 0 ;
} else if (data == ETX) {
//Serial.println("END");
(*frame_size) = (*frame_index) ;
(*frame_index) = -1 ;
(*frame_state) = IDLE ;
//Serial.println("END");
return 1 ;
} else if ((*frame_index) >= 38) { //frame is larger than max size of frame ...
(*frame_index) = -1 ;
(*frame_size) = -1 ;
(*frame_state) = IDLE ;
return -1 ;
} else {
(*frame_state) = DATA ;
}
return 0 ;
}
return -1 ;
}
// the setup routine runs once when you press reset:
void setup()
{
// use A1 as groung [ by megavoid ]
//pinMode(A1, OUTPUT);
//digitalWrite(A1, LOW);
// initialize serial communication at 115200 bits per second:
int i;
Serial.begin(115200);
Serial.println("Start of receiver program");
ADC_setup();
ADC_start_conversion(SENSOR_PIN);
//analogReference(INTERNAL); // internal reference is 1.1v, should give better accuracy for the mv range of the led output.
Timer1.initialize(SYMBOL_PERIOD / SAMPLE_PER_SYMBOL); //1200 bauds oversampled by factor 4
Timer1.attachInterrupt(sample_signal_edge);
}
// the loop routine runs over and over again forever:
void loop()
{
int i;
unsigned char received_data;
char received_data_print ;
int nb_shift ;
int byte_added = 0 ;
if (new_word == 1) {
received_data = 0 ;
for (i = 0 ; i < 16 ; i = i + 2) { //decoding Manchester
received_data = received_data << 1 ;
if (((detected_word >> i) & 0x03) == 0x01) {
received_data |= 0x01 ;
} else {
received_data &= ~0x01 ;
}
}
received_data = received_data & 0xFF ;
#ifdef DEBUG
Serial.print(received_data & 0xFF, HEX);
Serial.print(", ");
Serial.println((char) received_data);
#endif
new_word = 0 ;
if ((byte_added = add_byte_to_frame(frame_buffer, &frame_index, &frame_size, &frame_state, received_data)) > 0) {
frame_buffer[frame_size - 1] = '\0';
Serial.println(&(frame_buffer[1]));
}
//if(frame_state != IDLE) Serial.println(received_data, HEX);
}
}
Выводы: практическая дальность (с использованием светодиода в качестве фотодиода) в моих экспериментах составила около 10 см на столе. Лучше всего коммуницировалось с использованием обычных красных 5мм светодиодов с али, с синими, зелеными и желтыми тоже работало, но похуже. Предполагаю, что можно заменить светодиод излучателя на лазерный, а светодиод приёмника на фотодиод и увеличить дальность до метров и, возможно, даже до десятков метров. Умельцы могут попробовать распаять оптоволокно и добиться восхитительных расстояний.
Мы с внуком делали на лазере от брелока для ключей, что в каждом кисоске продаются и фототранзисторе. Передавали аналоговый сигнал (модулировали лазер звуком с линейного выхода проигрывателя). Принятый сигнал пускали на аудио-усилитель и слушали на колонках. Качество звука воплне на уровне. Практическая дальность - противоположные углы моей мастерской (она 6х4 метра) - работает отлично, дальше не проверяли. Минус использования лазера - чем дальше, тем труднее попадать в фототранзистор - мельчайшее дрожание рук - лучик уходит. Но если не с рук, а в тиски вставить, то нормально. Да, игрушка была чисто аналоговая - без ардуины.
Аха, амплитудную модуляцию делали. А тут чувак заморочился с суровой цифрой, даже Манчестер прикрутил.
Мне сей проект понравился тем, что его может собрать любой за 5 минут из трёх копеечных деталей, которые есть у каждого, и он сразу заработает. А дальше, может поразвивают идею и прикрутят куда к своим поделиям.
Кул. В чём соль?
Просто ещё один способ обмена данными между ардуинами, но основанный не на радио, а на свете.
Например, ставим на баню ардуину-передатчик с солнечными панелями и лазером, и она беспроводно шлёт данные на ардуину-приёмник-сервер, установленную в доме.
Но для себя я практического применения так и не нашёл. :)
Ага :)) Или наоборот, защищаем секреты, 433 МГц и 2.4 ГГц прорвутся сквозь стены комнаты без окон к шпиёну, а свет точно нет.