ML-L3是用于尼康部分型號(hào)
相機(jī)的無線
紅外遙控器,可以通過紅外方式來控制快門的釋放,支持B門拍攝。官方售價(jià)100RMB左右,山寨版售價(jià)10RMB左右。雖然也能實(shí)現(xiàn)基本的遙控功能,但是功能還是比較單一,如不能實(shí)現(xiàn)定時(shí)拍攝,即用來拍攝制作延時(shí)視頻的素材。本篇文章介紹如何通過Arduino、MCU或FPGA來控制紅外發(fā)射器,產(chǎn)生快門指令從而實(shí)現(xiàn)無線遙控快門的功能。
拆解ML-L3遙控器
為了實(shí)現(xiàn)ML-L3遙控器的功能,我們首先要了解無線遙控器的原理。當(dāng)然最好的方式就是拆解一個(gè)ML-L3,然后看看內(nèi)部的電路,然后測(cè)出紅外的編碼。但是手頭又沒有這樣的一個(gè)遙控器,有國(guó)外的網(wǎng)友已經(jīng)拆解了并且測(cè)出了紅外編碼的波形,如下圖。
官方遙控器PCB板:山寨遙控器PCB板:從PCB板來看,果然還是官方的用料更足一些,通過測(cè)量紅外發(fā)射引腳,在按下按鈕時(shí),紅外發(fā)射頭會(huì)發(fā)出一串脈沖信號(hào),如下圖所示:其中黑色的部分是38KHz的PWM方波,空白部分是低電平,以上波形就表示一個(gè)快門指令。
紅外遙控協(xié)議主要有兩種:NEC協(xié)議和Philips RC-5協(xié)議,NEC采用PWM方式調(diào)制,RC-5采用PPM方式調(diào)制。其中使用最多的是NEC協(xié)議,38KHz載波,一般是由引導(dǎo)碼 地址碼 地址反碼 數(shù)據(jù) 數(shù)據(jù)反碼構(gòu)成。其中邏輯0和邏輯1的編碼如下:
基于Arduino的實(shí)現(xiàn)
好了,知道了快門指令的紅外波形,我們只需要寫個(gè)函數(shù)實(shí)現(xiàn)這一串脈沖信號(hào)就可以了。Arduino開發(fā)板,我手頭上有的是
Circuit Playground Express這款開發(fā)板,板載一對(duì)紅外發(fā)射接收頭,和兩路按鍵,對(duì)于我們的功能已經(jīng)是足夠用了。在使用前需要先安裝Cortex-M0的庫。程序非常簡(jiǎn)單,按下按鍵時(shí),發(fā)出一個(gè)快門指令:
#include
#define IR_Pin 25#defineLed_Pin13#defineButtonA_Pin4#defineButtonB_Pin5
#define LED_ON digitalWrite(Led_Pin, LOW)#define LED_OFF digitalWrite(Led_Pin, HIGH)#define LED_SET(x) digitalWrite(Led_Pin, x)
#define IR_ON digitalWrite(IR_Pin, HIGH)#define IR_OFF digitalWrite(IR_Pin, LOW)
#define GET_BUTTONA() digitalRead(ButtonA_Pin)#define GET_BUTTONB() digitalRead(ButtonB_Pin)
int sts = 0;
void setup(){ pinMode(IR_Pin, OUTPUT); pinMode(Led_Pin, OUTPUT); pinMode(ButtonA_Pin, INPUT_PULLDOWN); pinMode(ButtonB_Pin, INPUT_PULLDOWN);
Serial.begin(9600);}
//Nikon ML-L3 紅外遙控器快門編碼:38KHz=26usvoid loop(){if(GET_BUTTONA()){ delay(10);if(GET_BUTTONA()){ sts = !sts; LED_SET(sts);Serial.println("Right button pressed!");OneShot();}}while(GET_BUTTONA()); //等待松開}
voidOneShot(){int i = 0;for(i = 76; i > 0; i--) //2100ms{ IR_ON; //13.5 delayMicroseconds(12); IR_OFF; //13.7 delayMicroseconds(12);} IR_OFF; delay(28); //2803usfor(i = 15; i > 0; i--) //393us{ IR_ON; delayMicroseconds(12); IR_OFF; delayMicroseconds(12);} IR_OFF; delayMicroseconds(1580); //1611us
for(i = 15; i > 0; i--){ IR_ON; delayMicroseconds(12); IR_OFF; delayMicroseconds(12);} delayMicroseconds(3580);for(i = 15; i > 0; i--){ IR_ON; delayMicroseconds(12); IR_OFF; delayMicroseconds(12);} IR_OFF;}
基于STM32的實(shí)現(xiàn)
在STM32F103上的實(shí)現(xiàn)也是非常簡(jiǎn)單,主要用到了GPIO控制和精確延時(shí)函數(shù)。紅外控制引腳和按鍵引腳可根據(jù)需要來調(diào)整。
//根據(jù)Nikon ML-L3紅外遙控器編碼協(xié)議,產(chǎn)生快門指令voidOneShot(void){int i = 0;for(i = 76; i > 0; i--) //2100ms{ IR_ON; //13.5 delay_us(12); IR_OFF; //13.7 delay_us(12);} IR_OFF; delay_ms(28); //2803usfor(i = 15; i > 0; i--) //393us{ IR_ON; delay_us(12); IR_OFF; delay_us(12);} IR_OFF; delay_us(1580); //1611us
for(i = 15; i > 0; i--){ IR_ON; delay_us(12); IR_OFF; delay_us(12);} delay_us(3580);for(i = 15; i > 0; i--){ IR_ON; delay_us(12); IR_OFF; delay_us(12);} IR_OFF;}
基于FPGA的實(shí)現(xiàn)
對(duì)于FPGA來說,這種波形的產(chǎn)生,時(shí)間可以控制的更精確,這取決于FPGA的時(shí)鐘,時(shí)鐘越高精度越高,而且可控性更強(qiáng)一些,就是實(shí)現(xiàn)起來稍微麻煩一些。
Verilog文件module ml_l3_pulse_gen(
input clk_50M, //20nsinput rst_n,input trig, //negedge trig
output pulse);
parameter T1_2000US = 100000;parameter T2_28000US = 1400000;parameter T3_400US = 20000;parameter T4_1580US = 79000;parameter T5_400US = T3_400US;parameter T6_3580US = 179000;parameter T7_400US = T3_400US;
parameter T1_STS = 1;parameter T2_STS = 2;parameter T3_STS = 3;parameter T4_STS = 4;parameter T5_STS = 5;parameter T6_STS = 6;parameter T7_STS = 7;parameter T8_STS = 8;parameter T0_STS = 0;parameter TIME_38KHZ = 658;
reg [7:0] cur_sts;reg [31:0] cnt_38khz;reg [31:0] cnt;reg [31:0] cnt_max;
reg en;reg pwm_38k;reg trig_reg;
assign pulse = (en) ? pwm_38k : 0;
always @ (posedge clk_50M)begin trig_reg <= trig;end
always @ (posedge clk_50M)beginif(!rst_n) cnt_max <= 0;else begincase(cur_sts) T0_STS : cnt_max <= 0; T1_STS : cnt_max <= T1_2000US; T2_STS : cnt_max <= T2_28000US; T3_STS : cnt_max <= T3_400US; T4_STS : cnt_max <= T4_1580US; T5_STS : cnt_max <= T5_400US; T6_STS : cnt_max <= T6_3580US; T7_STS : cnt_max <= T7_400US;default: cnt_max <= 0; endcaseendend
always @ (posedge clk_50M)beginif(!rst_n) en <= 0;elsebegincase(cur_sts)1,3,5,7: en <= 1;2,4,6,0: en <= 0;default: en <= 0; endcaseendend
always @ (posedge clk_50M)beginif(!rst_n) cnt <= 0;elsebeginif(cur_sts != T0_STS