Официальный сайт компании Arduino по адресу arduino.cc
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 ГГц прорвутся сквозь стены комнаты без окон к шпиёну, а свет точно нет.