这个程序写了大概快一周了,每天大概花两个小时左右。
第一次写这种通讯的协议,自己写代码能力不够强,经验不足。犯了很多的错误。下面是我自己对这次的总结:
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课程,里面讲了关于红外遥控的课程