FPGA红外遥控

这个程序写了大概快一周了,每天大概花两个小时左右。

第一次写这种通讯的协议,自己写代码能力不够强,经验不足。犯了很多的错误。下面是我自己对这次的总结:

1.第一:写程序不能写的太多,写一点验证一点。初期,你不要太相信自己的代码水平。

2.第二:要花时间去验证你写的代码,比如用sigaltap去调试你的代码,或者用led,或者是数码管

3.第三:先把通信协议的原理看懂,这个所谓的看懂,是在你不断的思考,和实践中理解的。

4.第四:在简单的东西,也要练习,除非你是大佬。

 

首先:了解通讯的过程:

1.红外遥控器发出红外光,这个红外光是经过调制的,就是下面的第三个线

2.在FPGA开发板有个红外接收头,对接收的数据进行解码,就是下面第四条线

这里有个小细节:起始发射的不是高电平9ms吗,为什么接收时是低电平呢,这和红外接收头内部的结构有关,在内部有个三极管,当解调器(Demodulator)接收到高电平时,OUT输出为低电平.

红外内部结构如下图所示:

我们分析一下:发过来的数据,FPGA代码如何解析其中的数据

1.下面是遥控器发射的数据:

发射的数据:40位数据,地址码,地址反码,数据吗,数据反码(其中数据码就是对应按键的值,不同的按键,数据吗不同)

2.其中发射的数据‘“1”,时间大概是2.25ms,“0”大概1.12ms

3.下面是遥控器重复发射的数据

如何区分是否重复按键呢:

可以检测9ms之后的这个信号是2.25ms还是4.5ms,如果是4.5ms,就是发数据,如果是2.25ms,就是重复码

 

4.下面是红外接收头收到的数据

 

FPGA写程序的思路:

1.先检测下降沿,如果检测到下降沿,则开始对9ms这段数据进行记时,直到等到上升沿到来之后,停止计时,我们假设计时为t,判断时间是否大概在9ms左右,(t>8ms&&t<10ms),这点非常重要,千万别写t==9ms,判断,这样绝对是错的,只要t在9ms左右就可以,则进入下个状态,那如果t远远小于9ms,这数据无效,回到状态机空闲状态

2.我们继续对下个状态记时,然后判断t大概是多少,如果t在4.5左右,这进入接收数据状态,如果t在2.25ms左右,这进入重复码状态,如果都不是,这数据无效,回到状态机空闲状态

3.如果状态是接收数据状态,我们要对40个数据保存,数据是0,还是1,通过t的时间来判断,如果是0则是560us的低电平+1.12ms的高电平,如果是1则是560us的低电平+2.25ms的高电平,

 

代码如下:

//做了一周的程序,终于成功了
//学会了signaltap的调试
//要学会分解一个大的工程
module red_receive(
    input               clk,          //系统时钟 
    input               rst_n,        //系统复位信号,低电平有效
    input               remote_in,    //红外接收信号
    output  reg [7:0]   data,         //接收的数据
    output  reg         led    
);

//状态机状态
parameter  IDLE                 = 6'b00000;
parameter  START_9MS            = 6'b00001;
parameter  START_4_5MS_OR_2_5MS = 6'b00010;
parameter  START_RECEIVE_DATA   = 6'b00011;
parameter  START_REPEAT         = 6'b00100;

//状态机
reg [5:0] state_c;
reg [5:0] state_n;              //自己真的应该注意
wire sidle29ms_start;           //检测到9ms的那个时刻的下降沿开始了
reg  s9ms2s4_5ms_or_2_5ms_start; //检测到开始4.5ms的那个时刻的上升沿开始了
reg  s9ms2idle_start;           //检测出错
reg  s4_5ms2receive_data_start; //开始接收数据
reg  s4_5ms2repeat_code_start;  //重复码
reg  s4_5ms2idle;               //检测出错,回到起点
wire sreceive_data2idle_start;  //接受数据完成
wire srepeat2idle_start;        //重复码接收完成 

//接收数据
reg [31:0] receive_data;       //接收的数据
reg [6:0] cnt;
wire add_cnt;
wire end_cnt;

//上升沿,下降沿
wire  pos_remote_in,neg_remote_in;
reg   remote_in_d0,remote_in_d1;

//led控制
reg led_flag;
wire add_cnt1;
wire end_cnt1;
reg [23:0]   cnt1;
//计时
reg  [19:0]  neg_cnt,pos_cnt;

//20ns 50个是1us=1000*50
//9ms
parameter TIME_10MS   = 20'd500_000;  //50000*10
parameter TIME_8MS    = 20'd400_000; 
//4.5ms
parameter TIME_4MS    = 20'd200_000;  
parameter TIME_5MS    = 20'd250_000;
//2.25 逻辑‘1’
parameter TIME_1_4MS  = 20'd70_000;  
parameter TIME_3MS    = 20'd150_000;


//上升沿检测,下降沿检测,很常规的一种方法
assign  pos_remote_in = (~remote_in_d1) & remote_in_d0;    //上升沿检测,如果检测到上升沿,有0到1,之后下个周期为0
assign  neg_remote_in =  remote_in_d1 & (~remote_in_d0);   //下降沿

always  @(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
        remote_in_d0<=1'b0;
        remote_in_d1<=1'b0;
    end
    else  begin
        remote_in_d0<=remote_in;
        remote_in_d1<=remote_in_d0;
    end
end

//状态机初值
always@(posedge clk or negedge rst_n)begin
    if(!rst_n)begin
        state_c <= IDLE;
    end
    else begin
        state_c <= state_n;
    end
end

//在接收数据的各种状态
always@(*)begin
    case(state_c)
        IDLE:begin
            if(sidle29ms_start)begin   //9ms的下降沿开始,准备开始计数
                state_n = START_9MS;
            end
            else begin
                state_n = state_c;
            end
        end
        START_9MS:begin
            if(s9ms2s4_5ms_or_2_5ms_start)begin
                state_n = START_4_5MS_OR_2_5MS;
            end
            else if(s9ms2idle_start)begin
                state_n = IDLE;  //计数出错了
            end
            else begin
                state_n = state_c;
            end
        end
         START_4_5MS_OR_2_5MS:begin
            if(s4_5ms2receive_data_start)begin
                state_n = START_RECEIVE_DATA; //开始接收数据
            end
            else if(s4_5ms2repeat_code_start)begin
                state_n = START_REPEAT;  //重复码
            end
            else if(s4_5ms2idle)begin
                state_n = IDLE;  //计数出错了
            end
            else begin
                state_n = state_c;
            end
        end
        START_RECEIVE_DATA:begin
            if(sreceive_data2idle_start)begin
                state_n = IDLE;  //回到空闲状态
            end
            else begin
                state_n = state_c;
            end
        end
		  START_REPEAT:begin
            if(srepeat2idle_start)begin
                state_n = IDLE;  //回到空闲状态
            end
            else begin
                state_n = state_c;
            end
        end
        default:begin
            state_n = IDLE;
        end
    endcase
end

assign sidle29ms_start             =state_c==IDLE               && neg_remote_in;   //检测到下降沿,准备开始对9ms低电平计时
assign sreceive_data2idle_start    =state_c==START_RECEIVE_DATA && end_cnt;         //40位数据接收完成
assign srepeat2idle_start          =state_c==START_REPEAT       && pos_remote_in;   //重复码结束标志位
//9MS
always  @(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
        s9ms2s4_5ms_or_2_5ms_start<=0;
        s9ms2idle_start<=0;
    end
    else if(state_c==START_9MS && pos_remote_in) begin  //下降沿开始接收数据
        if(neg_cnt>TIME_8MS && neg_cnt<TIME_10MS) begin
           s9ms2s4_5ms_or_2_5ms_start<=1;
        end
        else begin
            s9ms2idle_start<=1;
        end 
    end
    else begin
         s9ms2s4_5ms_or_2_5ms_start<=0;
         s9ms2idle_start<=0;
    end
end

//4.5MS
always  @(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
        s4_5ms2receive_data_start<=0;
        s4_5ms2repeat_code_start<=0;
        s4_5ms2idle<=0;
    end
    else if(state_c==START_4_5MS_OR_2_5MS && neg_remote_in) begin  //下降沿开始接收数据
        if(pos_cnt>TIME_4MS&&pos_cnt<TIME_5MS) begin
              s4_5ms2receive_data_start<=1;
        end
        else if(pos_cnt>TIME_1_4MS&&pos_cnt<TIME_3MS)begin
             s4_5ms2repeat_code_start<=1;
        end
        else begin
             s4_5ms2idle<=1;
        end 
    end
    else begin
        s4_5ms2receive_data_start<=0;
        s4_5ms2repeat_code_start<=0;
        s4_5ms2idle<=0;
    end
end

///接收数据//
//需要接收32位的数据
always @(posedge clk or negedge rst_n)begin
    if(!rst_n)begin
         cnt <= 0;
    end
    else if(add_cnt)begin
        if(end_cnt)
            cnt <= 0;
        else
            cnt <= cnt + 1;
    end
end

assign add_cnt = state_c==START_RECEIVE_DATA&&neg_remote_in;  //成功接收一个     
assign end_cnt = add_cnt && cnt==32-1;   

//接收数据
always  @(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
        receive_data<=0;
    end
    else if(state_c==START_RECEIVE_DATA&&neg_remote_in) begin
        if(pos_cnt>TIME_1_4MS&&pos_cnt<TIME_3MS) begin   //在2.25ms左右
              receive_data[cnt]<=1;
        end
        else  begin
             receive_data[cnt]<=0;
        end
    end
end
///接收数据


//下降沿来了,开始计数,这个方法很好用,专门检测低电平的时间
//当低电平来了,低电平neg_cnt清零并开始自己计数,当上升沿来的那刻,去读neg_cnt这个值,就是低电平的时间
always  @(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
        neg_cnt<=0;   
    end
    else begin
        if(neg_remote_in) //重新清零
            neg_cnt<=0; 
       else 
            neg_cnt<=neg_cnt+1; 
    end
end

//上升沿来了,开始计数,
always  @(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
        pos_cnt<=0;   
    end
    else begin
        if(pos_remote_in) //重新清零
            pos_cnt<=0; 
       else 
            pos_cnt<=pos_cnt+1; 
    end
end



//下面是做测试用的
always  @(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
        led_flag<=0;
    end
    else if(srepeat2idle_start) begin
         led_flag<=1;
    end
    else if(end_cnt1) begin
         led_flag<=0;
    end
end

//100ms=5000000;
// 80ms=4000000;
always @(posedge clk or negedge rst_n)begin
    if(!rst_n)begin
        cnt1 <= 0;
    end
    else if(add_cnt1)begin
        if(end_cnt1)
            cnt1 <= 0;
        else
            cnt1 <= cnt1 + 1;
    end
end

assign add_cnt1 = led_flag ;       
assign end_cnt1 = add_cnt1 && cnt1==5000000-1;  //100ms

always  @(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
       led<=0;
    end
    else if(led_flag&&cnt1<4000000-1)begin  //80ms亮
      led<=1;
    end
    else begin
      led<=0;
    end
end


always  @(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
        data<=0;
    end
    else if(sreceive_data2idle_start) begin
	    data<=receive_data[23:16];  //最后接收的数据,将数据显示到数码管上
    end
end


endmodule

最后:一定要注意对定时的判断,最后你自己去用sigaltap去测试一下t大概在什么范围,就比如你判断接收的数据“1”

t>2ms&&t<2.5ms,你判断t是在2.25ms左右,但是在实际中,很可能     1.5ms<t<3ms,这个范围内,多用siganltap调试

如果还有疑问:可以看看正点原子的FPGA课程,里面讲了关于红外遥控的课程

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值