PAJ7620U2手势识别——唤醒操作(1)

文章目录

  • 前言
  • 一、唤醒操作简介
  • 二、唤醒步骤
    • 1.状态转移图绘制
    • 2.模块波形图绘制
    • 3.上板验证
    • 4.参考代码
  • 总结


前言

  本教程是基于FPGA的手势识别实现教程,使用到的手势识别模块是PAJ7620U2,本文主要向各位读者阐述如何通过I2C协议去唤醒该模块,从模块状态转移图、模块波形图的绘制,到最后代码的编写及验证,一步一步教会各位读者如何利用FPGA去实现。
  下面我们简单介绍一下该模块,该模块是在正点原子店铺购买的,他们提供了利用STM32驱动的手册,但我们要通过FPGA进行驱动,因此正点原子提供的手册,我们可以粗略浏览一下,提取关键信息。其中,各位读者需要注意的是:
PAJ7620U2手势识别——唤醒操作(1)_第1张图片
  I2C接口支持的通信速率最高为400KHZ,我们的系统时钟是50MHZ,因此必须进行分频操作。但是为了提高I2C代码复用性,我们采用250KHZ速率通信,I2C驱动时钟频率采用1MHZ。同时我们可以看到,该模块检测的手势姿势有多种,本次我们检测上、下、左、右四个姿势就可以了,其它姿势的检测,读者有兴趣的话可以自行查看数据手册完成配置。

一、唤醒操作简介

  为什么要进行唤醒操作呢?相信各位读者会有这个疑问,我们根据PAJ7620U2芯片手册,向各位读者详细阐述。我们先看一下初始化的步骤:
PAJ7620U2手势识别——唤醒操作(1)_第2张图片
步骤一:上电,Vbus必须在Vdd之前上电;
步骤二:等待700us,让PAJ7620U2稳定;
步骤三:写入从机ID或者是I2C读取指令去唤醒。

  步骤一我们不用关注,这是生产厂家需要关注的问题,我们看后面两个步骤。步骤二是在写入前先等待700us,为了更好地提升模块性能,这里我们等待1000us,因为该模块是通过I2C协议配置的,我们在初始状态下等待1000us后,再跳转到I2C开始状态。步骤三,我们根据数据手册:
PAJ7620U2手势识别——唤醒操作(1)_第3张图片
根据这一个唤醒指令,I2C开始工作后,发送从机ID和一个W(WRITE)指令,从机ID如下:
在这里插入图片描述
因此发送的8bit指令为8’b1110_0110。

二、唤醒步骤

1.状态转移图绘制

PAJ7620U2手势识别——唤醒操作(1)_第4张图片
  说明:初始状态(IDLE),等待1000us后跳转到开始状态,在SCL为高电平时候,检测到SDA由高电平变为低电平,即开始工作,如图所示:
PAJ7620U2手势识别——唤醒操作(1)_第5张图片
  检测到开始信号后,跳转到发送从设备地址状态,从设备地址为从设备ID+写指令,即8’b1110_0110,该8bit指令发送完成后,跳转到等待状态,等待1000us完成,跳转到结束状态。再结束状态下,在SCL为高电平时,检测到SDA由低电平变为高电平,即表示结束。结束信号标志如下图所示:
PAJ7620U2手势识别——唤醒操作(1)_第6张图片
  各位读者需要注意的是,结束信号完成后,状态由STOP状态变为IDLE状态,在空闲状态下,因为SCL和SDA有外接上拉电阻的作用,电平会持续拉高。

2.模块波形图绘制

  首先,对系统时钟进行分频操作,分频出1MHZ的时钟,用来驱动I2C模块运行:
在这里插入图片描述
  i2c_clk就是我们分频出来的,1MHZ的时钟信号。接下来我们利用该时钟信号驱动后续三段式状态机的实现。IDLE状态如下:
PAJ7620U2手势识别——唤醒操作(1)_第7张图片
  cnt_wait计数器计数到最大值1000us时,状态机跳转到开始状态。START状态如下:
PAJ7620U2手势识别——唤醒操作(1)_第8张图片
  在检测到i2c_scl信号为高电平,i2c_sda信号下降沿时,状态由开始状态跳转为发送从设备地址状态。这里要注意的是,我们不直接用SDA信号的原因是,SDA是一个inout类型的信号,要借助三态门完成赋值,将SDA赋值给i2c_sda信号,我们通过对该信号操作,从而间接实现对SDA信号操作。SALVE_ADDR状态如下:
PAJ7620U2手势识别——唤醒操作(1)_第9张图片
  从设备地址发送完成,跳转到等待状态,等待1000us结束跳转到STOP状态,WAIT状态和STOP状态如下:
PAJ7620U2手势识别——唤醒操作(1)_第10张图片
  在这里我们必须考虑引入两个信号,一个是结束标志信号i2c_end,一个是模式信号mode。引入mode信号的原因是,我们需要发送不同的指令实现数据的接收及发送,因此这些操作有差异。我们本次的操作为唤醒操作,令mode=0,通过该信号,编写代码时,状态跳转就按唤醒操作来进行,避免后续状态跳转产生影响。

3.上板验证

  在这里我们直接上板抓取信号的波形,观察波形变化情况,来判断唤醒操作是否成功。设置如下:
PAJ7620U2手势识别——唤醒操作(1)_第11张图片
  我们在这里抓取跳转信号skip_en的上升沿,得到如下波形:
PAJ7620U2手势识别——唤醒操作(1)_第12张图片
  我们发现,状态在WAIT状态持续拉高而不见拉低,因为在WAIT状态下,需要延迟1000us,才跳转到STOP状态。因此我们需要更改一下触发条件:
PAJ7620U2手势识别——唤醒操作(1)_第13张图片

  在这里,抓取等待信号下降沿,即可查看后续波形。后续波形如下:
PAJ7620U2手势识别——唤醒操作(1)_第14张图片
  结束信号拉高,模式信号自增1,表示我们唤醒操作结束,代码编写无误。

4.参考代码

#(
	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手势识别模块,当然这只是配置该模块其中一个很小的部分,各位读者朋友可结合源码和本教程波形图和源码,一步一步自行尝试设计信号来驱动验证。

你可能感兴趣的:(fpga开发)