本教程是基于FPGA的手势识别实现教程,使用到的手势识别模块是PAJ7620U2,本文主要向各位读者阐述如何通过I2C协议去唤醒该模块,从模块状态转移图、模块波形图的绘制,到最后代码的编写及验证,一步一步教会各位读者如何利用FPGA去实现。
下面我们简单介绍一下该模块,该模块是在正点原子店铺购买的,他们提供了利用STM32驱动的手册,但我们要通过FPGA进行驱动,因此正点原子提供的手册,我们可以粗略浏览一下,提取关键信息。其中,各位读者需要注意的是:
I2C接口支持的通信速率最高为400KHZ,我们的系统时钟是50MHZ,因此必须进行分频操作。但是为了提高I2C代码复用性,我们采用250KHZ速率通信,I2C驱动时钟频率采用1MHZ。同时我们可以看到,该模块检测的手势姿势有多种,本次我们检测上、下、左、右四个姿势就可以了,其它姿势的检测,读者有兴趣的话可以自行查看数据手册完成配置。
为什么要进行唤醒操作呢?相信各位读者会有这个疑问,我们根据PAJ7620U2芯片手册,向各位读者详细阐述。我们先看一下初始化的步骤:
步骤一:上电,Vbus必须在Vdd之前上电;
步骤二:等待700us,让PAJ7620U2稳定;
步骤三:写入从机ID或者是I2C读取指令去唤醒。
步骤一我们不用关注,这是生产厂家需要关注的问题,我们看后面两个步骤。步骤二是在写入前先等待700us,为了更好地提升模块性能,这里我们等待1000us,因为该模块是通过I2C协议配置的,我们在初始状态下等待1000us后,再跳转到I2C开始状态。步骤三,我们根据数据手册:
根据这一个唤醒指令,I2C开始工作后,发送从机ID和一个W(WRITE)指令,从机ID如下:
因此发送的8bit指令为8’b1110_0110。
说明:初始状态(IDLE),等待1000us后跳转到开始状态,在SCL为高电平时候,检测到SDA由高电平变为低电平,即开始工作,如图所示:
检测到开始信号后,跳转到发送从设备地址状态,从设备地址为从设备ID+写指令,即8’b1110_0110,该8bit指令发送完成后,跳转到等待状态,等待1000us完成,跳转到结束状态。再结束状态下,在SCL为高电平时,检测到SDA由低电平变为高电平,即表示结束。结束信号标志如下图所示:
各位读者需要注意的是,结束信号完成后,状态由STOP状态变为IDLE状态,在空闲状态下,因为SCL和SDA有外接上拉电阻的作用,电平会持续拉高。
首先,对系统时钟进行分频操作,分频出1MHZ的时钟,用来驱动I2C模块运行:
i2c_clk就是我们分频出来的,1MHZ的时钟信号。接下来我们利用该时钟信号驱动后续三段式状态机的实现。IDLE状态如下:
cnt_wait计数器计数到最大值1000us时,状态机跳转到开始状态。START状态如下:
在检测到i2c_scl信号为高电平,i2c_sda信号下降沿时,状态由开始状态跳转为发送从设备地址状态。这里要注意的是,我们不直接用SDA信号的原因是,SDA是一个inout类型的信号,要借助三态门完成赋值,将SDA赋值给i2c_sda信号,我们通过对该信号操作,从而间接实现对SDA信号操作。SALVE_ADDR状态如下:
从设备地址发送完成,跳转到等待状态,等待1000us结束跳转到STOP状态,WAIT状态和STOP状态如下:
在这里我们必须考虑引入两个信号,一个是结束标志信号i2c_end,一个是模式信号mode。引入mode信号的原因是,我们需要发送不同的指令实现数据的接收及发送,因此这些操作有差异。我们本次的操作为唤醒操作,令mode=0,通过该信号,编写代码时,状态跳转就按唤醒操作来进行,避免后续状态跳转产生影响。
在这里我们直接上板抓取信号的波形,观察波形变化情况,来判断唤醒操作是否成功。设置如下:
我们在这里抓取跳转信号skip_en的上升沿,得到如下波形:
我们发现,状态在WAIT状态持续拉高而不见拉低,因为在WAIT状态下,需要延迟1000us,才跳转到STOP状态。因此我们需要更改一下触发条件:
在这里,抓取等待信号下降沿,即可查看后续波形。后续波形如下:
结束信号拉高,模式信号自增1,表示我们唤醒操作结束,代码编写无误。
#(
parameter SLAVE_ID = 7'b111_0011 ,
SYS_CLK_FREQ= 26'd50_000_000 ,
SCL_FREQ = 23'd250_000
)
(
input wire sys_clk ,
input wire sys_rst_n ,
output wire scl ,
inout wire sda
);
localparam CNT_CLK_MAX = (SYS_CLK_FREQ/SCL_FREQ) >> 2'd3 ;
localparam CNT_T1_MAX = 'd1000 ,
CNT_T2_MAX = 'd1000 ;
localparam IDLE = 'd0 ,
START = 'd1 ,
SLAVE_ADDR = 'd2 ,
WAIT = 'd3 ,
STOP = 'd4 ;
reg [4:0] n_state ;
reg [4:0] c_state ;
reg [4:0] cnt_clk ;
reg i2c_clk ;
reg skip_en_1 ;
reg [9:0] cnt_wait ;
reg i2c_scl ;
reg i2c_sda ;
reg i2c_end ;
reg [1:0] cnt_i2c_clk ;
reg [2:0] cnt_bit ;
reg [9:0] cnt_delay ;
reg [2:0] mode ;
reg [7:0] slave_addr ;
reg [7:0] device_addr ;
reg [7:0] wr_addr ;
wire sda_en ;
wire sda_in ;
assign scl = i2c_scl ;
assign sda_in = sda ;
assign sda_en = 1'b1 ;
assign sda = (sda_en == 1'b1) ? i2c_sda : 1'bz ;
always@(*)
case(mode)
3'd0 : begin
slave_addr <= {SLAVE_ID,1'b0} ; //激活
device_addr <= 8'd0 ;
wr_addr <= 8'd0 ;
end
default : begin
slave_addr <= slave_addr ;
device_addr <= device_addr ;
wr_addr <= wr_addr ;
end
endcase
///生成i2c设备驱动时钟/
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_clk <= 5'd0 ;
else if(cnt_clk == CNT_CLK_MAX - 1'b1)
cnt_clk <= 5'd0 ;
else
cnt_clk <= cnt_clk + 1'b1 ;
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
i2c_clk <= 1'b0 ;
else if(cnt_clk == CNT_CLK_MAX - 1'b1)
i2c_clk <= ~i2c_clk ;
else
i2c_clk <= i2c_clk ;
///
///状态机第一段
always@(posedge i2c_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
c_state <= IDLE ;
else
c_state <= n_state ;
//状态机第二段
always@(*)
case(c_state)
IDLE : if(skip_en_1 == 1'b1)
n_state <= START ;
else
n_state <= IDLE ;
START : if(skip_en_1 == 1'b1)
n_state <= SLAVE_ADDR ;
else
n_state <= START ;
SLAVE_ADDR : if(skip_en_1 == 1'b1)
n_state <= WAIT ;
else
n_state <= SLAVE_ADDR ;
WAIT : if(skip_en_1 == 1'b1)
n_state <= STOP ;
else
n_state <= WAIT ;
STOP : if(skip_en_1 == 1'b1)
n_state <= IDLE ;
else
n_state <= STOP ;
default : n_state <= IDLE ;
endcase
///状态机第三段///
always@(posedge i2c_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
begin
cnt_wait <= 10'd0 ;
skip_en_1 <= 1'b0 ;
cnt_i2c_clk <= 2'd0 ;
cnt_bit <= 3'd0 ;
i2c_end <= 1'b0 ;
mode <= 3'd0 ;
cnt_delay <= 10'd0 ;
end
else
case(c_state)
IDLE :begin
if(cnt_wait == CNT_T1_MAX - 1'b1)
cnt_wait <= 10'd0 ;
else
cnt_wait <= cnt_wait + 1'b1 ;
if((cnt_wait == CNT_T1_MAX - 2'd2)&&(mode == 3'd0))
skip_en_1 <= 1'b1 ;
else
skip_en_1 <= 1'b0 ;
end
START :begin
cnt_i2c_clk <= cnt_i2c_clk + 1'b1 ;
if((cnt_i2c_clk == 2'd2)&&(mode == 3'd0))
skip_en_1 <= 1'b1 ;
else
skip_en_1 <= 1'b0 ;
end
SLAVE_ADDR :begin
cnt_i2c_clk <= cnt_i2c_clk + 1'b1 ;
if((cnt_i2c_clk == 2'd3)&&(cnt_bit == 3'd7))
cnt_bit <= 3'd0 ;
else if(cnt_i2c_clk == 2'd3)
cnt_bit <= cnt_bit + 1'b1 ;
else
cnt_bit <= cnt_bit ;
if((cnt_i2c_clk == 2'd2)&&(cnt_bit == 3'd7)&&(mode == 3'd0))
skip_en_1 <= 1'b1 ;
else
skip_en_1 <= 1'b0 ;
end
WAIT :begin
cnt_delay <= cnt_delay + 1'b1 ;
if(cnt_delay == CNT_T2_MAX - 2'd2)
skip_en_1 <= 1'b1 ;
else
skip_en_1 <= 1'b0 ;
end
STOP :begin
cnt_i2c_clk <= cnt_i2c_clk + 1'b1 ;
if(cnt_i2c_clk == 2'd2)
i2c_end <= 1'b1 ;
else
i2c_end <= 1'b0 ;
if((cnt_i2c_clk == 2'd2)&&(mode == 3'd0))
skip_en_1 <= 1'b1 ;
else
skip_en_1 <= 1'b0 ;
if(i2c_end == 1'b1)
mode <= mode + 1'b1 ;
else
mode <= mode ;
end
default :begin
cnt_wait <= 10'd0 ;
skip_en_1 <= 1'b0 ;
cnt_i2c_clk <= 2'd0 ;
cnt_bit <= 3'd0 ;
i2c_end <= 1'b0 ;
mode <= mode ;
cnt_delay <= 10'd0 ;
end
endcase
always@(*)
case(c_state)
IDLE : i2c_scl <= 1'b1 ;
START : if(cnt_i2c_clk == 2'd3)
i2c_scl <= 1'b0 ;
else
i2c_scl <= 1'b1 ;
SLAVE_ADDR:
if((cnt_i2c_clk == 2'd1)||(cnt_i2c_clk == 2'd2))
i2c_scl <= 1'b1 ;
else
i2c_scl <= 1'b0 ;
WAIT : if((cnt_delay == 10'd0)||(cnt_delay == CNT_T2_MAX - 1'b1))
i2c_scl <= 1'b0 ;
else
i2c_scl <= 1'b1 ;
STOP : if(cnt_i2c_clk == 2'd0)
i2c_scl <= 1'b0 ;
else
i2c_scl <= 1'b1 ;
default : i2c_scl <= 1'b1 ;
endcase
always@(*)
case(c_state)
IDLE : i2c_sda <= 1'b1 ;
START : if(cnt_i2c_clk == 2'd0)
i2c_sda <= 1'b1 ;
else
i2c_sda <= 1'b0 ;
SLAVE_ADDR : i2c_sda <= slave_addr[7-cnt_bit] ;
WAIT : i2c_sda <= 1'b1 ;
STOP : if((cnt_i2c_clk == 2'd0)||(cnt_i2c_clk == 2'd1))
i2c_sda <= 1'b0 ;
else
i2c_sda <= 1'b1 ;
default : i2c_sda <= 1'b1 ;
endcase
endmodule
该部分介绍了如何唤醒PAJ7620U2手势识别模块,当然这只是配置该模块其中一个很小的部分,各位读者朋友可结合源码和本教程波形图和源码,一步一步自行尝试设计信号来驱动验证。