手把手教你DIY尼康ML-L3紅外遙控器


項目介紹

ML-L3是用於尼康部分型號相機的無線紅外遙控器,可以通過紅外方式來控制快門的釋放,支持B門拍攝。官方售價100RMB左右,山寨版售價10RMB左右。雖然也能實現基本的遙控功能,但是功能還是比較單一,如不能實現定時拍攝,即用來拍攝制作延時視頻的素材。本篇文章介紹如何通過Arduino、MCU或FPGA來控制紅外發射器,產生快門指令從而實現無線遙控快門的功能。

拆解ML-L3遙控器

為了實現ML-L3遙控器的功能,我們首先要了解無線遙控器的原理。當然最好的方式就是拆解一個ML-L3,然后看看內部的電路,然后測出紅外的編碼。但是手頭又沒有這樣的一個遙控器,有國外的網友已經拆解了並且測出了紅外編碼的波形,如下圖。

官方遙控器PCB板:

山寨遙控器PCB板:

從PCB板來看,果然還是官方的用料更足一些,通過測量紅外發射引腳,在按下按鈕時,紅外發射頭會發出一串脈沖信號,如下圖所示:

其中黑色的部分是38KHz的PWM方波,空白部分是低電平,以上波形就表示一個快門指令。

紅外遙控協議主要有兩種:NEC協議和Philips RC-5協議,NEC采用PWM方式調制,RC-5采用PPM方式調制。其中使用最多的是NEC協議,38KHz載波,一般是由引導碼+地址碼+地址反碼+數據+數據反碼構成。其中邏輯0和邏輯1的編碼如下:

基於Arduino的實現

好了,知道了快門指令的紅外波形,我們只需要寫個函數實現這一串脈沖信號就可以了。Arduino開發板,我手頭上有的是Circuit Playground Express這款開發板,板載一對紅外發射接收頭,和兩路按鍵,對於我們的功能已經是足夠用了。在使用前需要先安裝Cortex-M0的庫。

程序非常簡單,按下按鍵時,發出一個快門指令:


#include <Adafruit_CircuitPlayground.h>  

#define IR_Pin      25
#define Led_Pin     13
#define ButtonA_Pin  4
#define ButtonB_Pin  5

#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=26us
void loop()
{
  if (GET_BUTTONA())
  {
    delay(10);
    if (GET_BUTTONA())
    {
      sts = !sts;
      LED_SET(sts);
      Serial.println("Right button pressed!");
      OneShot();
    }
  }
  while (GET_BUTTONA());  //等待松開
}

void OneShot()
{
  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);  //2803us
  for (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的實現

在STM32F103上的實現也是非常簡單,主要用到了GPIO控制和精確延時函數。紅外控制引腳和按鍵引腳可根據需要來調整。

//根據Nikon ML-L3紅外遙控器編碼協議,產生快門指令
void OneShot(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);  //2803us
    for(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的實現

對於FPGA來說,這種波形的產生,時間可以控制的更精確,這取決於FPGA的時鍾,時鍾越高精度越高,而且可控性更強一些,就是實現起來稍微麻煩一些。

Verilog文件

module ml_l3_pulse_gen( 

input clk_50M,  //20ns
input 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)
begin
    if(!rst_n)
        cnt_max <= 0;
    else 
    begin
        case (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;        
        endcase
    end
end

always @ (posedge clk_50M)
begin
    if(!rst_n)
        en <= 0;
    else
    begin
        case (cur_sts)
            1,3,5,7 : en <= 1;
            2,4,6,0 : en <= 0;
            default : en <= 0;
        endcase
    end
end

always @ (posedge clk_50M)
begin
    if(!rst_n)
        cnt <= 0;
    else
    begin
        if(cur_sts != T0_STS && cnt < cnt_max)
            cnt <= cnt + 1;
        else 
            cnt <= 0;
    end
end

always @ (posedge clk_50M)
begin
    if(!rst_n)
        cur_sts <= T0_STS;
    else 
    begin
        case (cur_sts)
            T0_STS:
                if(trig_reg & !trig)
                    cur_sts <= T1_STS;
            T1_STS:
                if(cnt == T1_2000US)
                    cur_sts <= T2_STS;
            T2_STS:
                if(cnt == T2_28000US)
                    cur_sts <= T3_STS;        
            T3_STS:
                if(cnt == T3_400US)
                    cur_sts <= T4_STS;        
            T4_STS:
                if(cnt == T4_1580US)
                    cur_sts <= T5_STS;        
            T5_STS:
                if(cnt == T5_400US)
                    cur_sts <= T6_STS;        
            T6_STS:
                if(cnt == T6_3580US)
                    cur_sts <= T7_STS;        
            T7_STS:
                if(cnt == T7_400US)
                    cur_sts <= T0_STS;        
            default : 
                cur_sts <= T0_STS;
        endcase 
    end
end

/* 38KHz counter */
always @ (posedge clk_50M)
begin
    if(!rst_n)
        cnt_38khz <= 0;
    else 
    begin
        if(en && cnt_38khz < TIME_38KHZ)
            cnt_38khz <= cnt_38khz + 1;
        else 
            cnt_38khz <= 0;
    end
end

/* generate 38KHz pwm */
always @ (posedge clk_50M)
begin
    if(!rst_n)
        pwm_38k <= 0;
    else if(cnt_38khz == TIME_38KHZ)
        pwm_38k <= ~pwm_38k;
end

endmodule

仿真test bench 文件

`timescale 1ns/100ps

module ml_l3_pulse_gen_tb;

parameter SYSCLK_PERIOD = 20;// 50MHZ

reg SYSCLK;
reg NSYSRESET;
reg trig;

wire pulse;

initial
begin
    SYSCLK = 1'b0;
    NSYSRESET = 1'b0;
    trig = 0;
end

initial
begin
    #(SYSCLK_PERIOD * 10 )
        NSYSRESET = 1'b0;
        trig = 0;
    #(SYSCLK_PERIOD * 1000 )
        NSYSRESET = 1'b1;
    #(SYSCLK_PERIOD * 10 )
        trig = 1;
    #SYSCLK_PERIOD
        trig = 0;
end

always @(SYSCLK)
    #(SYSCLK_PERIOD / 2.0) SYSCLK <= !SYSCLK;

ml_l3_pulse_gen ml_l3_pulse_gen_0 (
    // Inputs
    .clk_50M(SYSCLK),
    .rst_n(NSYSRESET),
    .trig(trig),

    // Outputs
    .pulse(pulse)
);

endmodule

實際使用效果

對於實際的脈沖時間,不用特別的精確,誤差不要太大就行,最好使用示波器測量以下脈沖的時間。對於制作好的遙控器,只需要在相機周圍按下按鈕就可實現遙控快門。相機機身的紅外接收頭前后各有一個,可以方便在不同的位置遙控。如下圖所示。

總結

這款尼康ML-L3紅外遙控器的實現原理非常簡單,可擴展性強,可以根據需要自己添加功能,如添加固定時間間隔拍攝,固定張數拍攝,用於拍攝制作延時視頻所需要的圖片素材。當然,也可以使用手機上的遙控器來實現這個功能。

代碼獲取

以上代碼已經開源在Github和Gitee平台,地址如下。

  • Github開源地址: https://github.com/whik/nikon-wireless-remote-control-ML-L3-DIY.git
  • Gitee開源地址 : https://gitee.com/whik/nikon-wireless-remote-control-ML-L3-DIY.git

沒有使用代碼托管平台的朋友,可以在公眾號后台回復【尼康遙控器】也可以獲取代碼。

參考資料

文中的ML-L3拆解圖,Arduino代碼參考自以下鏈接內容。

推薦閱讀


  • 我的個人博客:www.wangchaochao.top
  • 我的公眾號:mcu149


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM