STM32 PAJ7620U2手势识别模块(IIC通信)程序源码详解

        最近在自学设计下基于STM32单片机的项目,想用手势识别模块做一点好玩的,做个手势控制家居设备开关,另外正好借此巩固I²C 通信。因此,我想借这个机会在这里和大家分享一下自己学习STM32单片机时的所思所感吧,若有表述不对之处,还请各位大佬指出,我好立刻改正。

        话不多说,先上硬货。

一、PAJ7620U2模块解读

         项目手势识别检测模块选择基于PixArt公司PAJ7620U2传感器开发的一块PAJ7620手势识别模块(淘宝上很多这种模块)。

图1  PAJ7620U2传感器模块

        网上很多店铺卖的这种 PAJ7620手势识别模块,但其实都是基于传感器设计的,只不过外围电路设计和形状外观不太一样而已,整体功能特性基本都一样,所以大家不用过多去选择应该买哪家的。

(一)手势识别模块选型

        PAJ7620U2模块是基于PAJ7620U2传感器设计,功能与特性几乎会与PAJ7620U2传感器的数据手册保持一致

        那我们如何分析比较各个手势识别传感器模块?

        首先自己查找/找商家要到  相关 数据手册等资料,然后我们主要关注以下几个问题:

  1. 模块内置的手势类型有哪些?识别速度(采样频率)?有效检测距离?有效检测角度?
  2. 模块供电电压范围?采用的通讯接口是什么?模块功耗?

        通过上述问题,我们可以分析该传感器模块能不能满足项目需求。

        如下表所示,通过对PAJ7620U2传感器模块与市面上相同工作原理的APDS-9960模块比较分析,PAJ7620内置的手势类型更多,手势检测更为灵敏可靠,抗环境光干扰能力强,很适合作为本设计的手势识别感应。另外,供电电压/工作电压在2.8V~3.3V,可以直接用在我们的STM32单片机项目上

表   PAJ7620与APDS-9960参数比较

PAJ7620U2

APDS-9960

电源电压

3.3V/5V

3.3V/5V

工作原理

光学数组式环境亮度传感检测

光学数组式环境亮度传感检测

通讯接口

I²C 接口,400Khz(Max)

I²C 接口,400Khz(Max)

识别速度

240Hz

160Hz

工作环境光

<100K Lux(抗灯光干扰)

<50K Lux(环境干扰抑制)

有效探测距离

5~15cm

2~15cm

检测角度范围

60°~180°

60°

手势识别种类

内置 9 个手势类型
(上、下、左、右、前、后、顺时针旋转、逆时针旋转、挥动)

内置 4 个手势类型
(上、下、左、右)

功耗

3~10mA
(待机功耗电流 15uA)

5~15mA
(睡眠模式功率2.2μA)

(二)PAJ7620U2传感器的工作原理

图2  PAJ7620U2传感器内部框图

        从上图中,我们可以看到PAJ7620U2传感器内部自带 LED 驱动器(可发射红外线信号),内置有传感器感应阵列、目标信息提取阵列和手势识别阵列。另外,PAJ7620U2作为一种光学数组式传感器,其内置LED驱动器集成了环境光和光源抑制滤波器,模块基本不受环境光干扰。 

        红外LED手势识别原理:

        传感器工作时通过内部 LED 驱动器,驱动红外 LED 向外发射红外线信号,当传感器阵列在有效的距离中探测到物体时,目标信息提取阵列会对探测目标进行特征原始数据的获取,采集到的数据被保存在寄存器中,同时手势识别阵列会对原始数据进行识别处理,最后将手势结果存到寄存器中。

        根据 PAJ7620U2传感器数据手册,用户可通过I²C接口总线采集信号并迅速识别出UPDownRightLeft9种常用手势。另外PAJ7620U2还提供内置的接近检测功能,用于检测物体的接近或离开。

二、I²C传感器件有什么魅力

        很多人刚刚接触I²C、SPI、CAN等通信方式时都会有一堆的问题:为什么要学它?学它可以做什么?我该怎么去学习它呢?

        1、那什么是I²C通信呢?

        简单来说,采用两条信号线通信的同步串行总线。(在此不做更多说明)

        2、为什么要学习这些看起来“根本没多大用处”且“与我无关”的通信协议呢?

        大哥,你总不能永远靠串口吃饭吧? 况且真正到了实际项目中,通信的方式要根据环境选择,你总不能想当然的自己决定吧,学好这些通信方式,你才敢有底气去接手一些大项目吧。

        3、该怎么学呢?

        个人觉得,刚刚学习阶段,只要理解它大概的原理,然后会熟练调用函数就可以了。

(一)初识I²C通信

1、认识I²C的两根线:SCL时钟线和SDA数据线。

  •         SCL时钟线,是为整个通信过程提供了时钟信号(后面会说明).
  •         SDA数据线,在每一个周期里发送0或者1,用这些0和1传输数据。

2、如何传输数据呢?
        首先,要传输数据,你总的告诉“另一半”传输开始了吧,所以,开始信号是必不可少的,对应也要有结束信号咯,你的“另一半"接收到了数据,总得告诉你一声吧,所以,应答信号也是少不了的。记住啦,它一点也不难,还有,我会按照我的理解方式来描述它,不会枯燥的。

3、这些信号如何通过两根线就实现呢????
        数据线SDA以时钟线SCL作为参照。
        开始信号:SCL为高电平时,SDA由高电平向低转变。传输开始。
        结束信号:SCL为高电平时,SDA由低电平向高转变。传输结束。
        应答信号:接收数据的IC 在接收完8个bit的数据之后,向发送数据的IC返回特定脉冲的低电平,表示数据已被签收。 

4、放到实际实验中怎么理解呢?

        CPU向受控单元发送一段数据之后,等待受控单元发送一个应答信号,若未接受到应答信号,表示受控单元发生故障。这些信号中,只有开始信号时必须的。而且,只有当SCL上为低电平时,SDA上的电平才允许发生变化。

        上面说的这些通俗易懂,但都是必须要了解的知识点。

(二)在实战中解读IIC通信。

        其实,在工程中用到的绝大部分是调用I²C相关函数。
        下面就用手势识别模块举例,我们先看看PAJ7620U2手势识别模块的软件模拟的I²C库函数长啥样。

(1)首先是初始化I²C对应的引脚

        我认为硬件I²C和软件I²C所实现的功能都一样,不过模拟I²C使用的更广泛,因为方便啊,而且,STM32硬件I²C引脚很鸡肋,还不如不用。

        SDA和SCL都被拉高,表示为空闲状态(idle-state)

//PAJ2670 I2C初始化   
// GS_IIC_SCL    PE(7) ----- SCL
// GS_IIC_SDA    PE(8) ----- SDA	 
 
//PAJ2670 I2C初始化
void GS_i2c_init()
{
	GPIO_InitTypeDef GPIO_InitStructure;

	RCC_APB2PeriphClockCmd(	RCC_APB2Periph_GPIOF, ENABLE );	//使能GPIOF时钟

	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7|GPIO_Pin_8;  //端口配置
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP ;       //推挽输出
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;       //50Mhz速度
	GPIO_Init(GPIOF, &GPIO_InitStructure);

	GPIO_SetBits(GPIOF, GPIO_Pin_7|GPIO_Pin_8);//PF7、PF8均输出高	
}

(2)首先是初始化I²C对应的引脚

#include "paj7620u2_iic.h"
#include "paj7620u2.h"
#include "delay.h"

//产生IIC起始信号
static void GS_IIC_Start()
{
	GS_SDA_OUT();//sda线输出
	GS_IIC_SDA=1;	  	  
	GS_IIC_SCL=1;
	delay_us(4);
	GS_IIC_SDA=0;//START:when CLK is high,DATA change form high to low 
	delay_us(4);
	GS_IIC_SCL=0;//钳住I2C总线,准备发送或接收数据 
}

//产生IIC停止信号
static void GS_IIC_Stop()
{
	GS_SDA_OUT();//sda线输出
	GS_IIC_SCL=0;
	GS_IIC_SDA=0;//STOP:when CLK is high DATA change form low to high
	delay_us(4);
	GS_IIC_SCL=1; 
	GS_IIC_SDA=1;//发送I2C总线结束信号
	delay_us(4);				   	
}

//等待应答信号到来
//返回值:1,接收应答失败
//        0,接收应答成功
static u8 GS_IIC_Wait_Ack()
{
	
	u8 ucErrTime=0;
	GS_SDA_IN();  //SDA设置为输入  
	GS_IIC_SDA=1;delay_us(3);	   
	GS_IIC_SCL=1;delay_us(3);	 
	while(GS_READ_SDA)
	{
		ucErrTime++;
		if(ucErrTime>250)
		{
			GS_IIC_Stop();
			return 1;
		}
	}
	GS_IIC_SCL=0;//时钟输出0 	
	return 0;  
}

//产生ACK应答
static void GS_IIC_Ack()
{
	GS_IIC_SCL=0;
	GS_SDA_OUT();
	GS_IIC_SDA=0;
	delay_us(3);
	GS_IIC_SCL=1;
	delay_us(3);
	GS_IIC_SCL=0;
}

//不产生ACK应答		    
static void GS_IIC_NAck()
{
	GS_IIC_SCL=0;
	GS_SDA_OUT();
	GS_IIC_SDA=1;
	delay_us(2);
	GS_IIC_SCL=1;
	delay_us(2);
	GS_IIC_SCL=0;
}

//IIC发送一个字节
//返回从机有无应答
//1,有应答
//0,无应答			  
static void GS_IIC_Send_Byte(u8 txd)
{                        
	u8 t;   
	GS_SDA_OUT(); 	    
	GS_IIC_SCL=0;//拉低时钟开始数据传输
	for(t=0;t<8;t++)
	{              
		if((txd&0x80)>>7)
			GS_IIC_SDA=1;
		else
			GS_IIC_SDA=0;
		txd<<=1; 	  
		delay_us(5);  
		GS_IIC_SCL=1;
		delay_us(5); 
		GS_IIC_SCL=0;	
		delay_us(5);
	}	 
} 

//读1个字节,ack=1时,发送ACK,ack=0,发送nACK   
static u8 GS_IIC_Read_Byte(u8 ack)
{
	
	u8 i,receive=0;

	GS_SDA_IN();//SDA设置为输入
	for(i=0;i<8;i++ )
	{
		GS_IIC_SCL=0; 
		delay_us(4);
		GS_IIC_SCL=1;
		receive<<=1;
		if(GS_READ_SDA)receive++;   
		delay_us(4); 
	}					 
	if (!ack)
		GS_IIC_NAck();//发送nACK
	else
		GS_IIC_Ack(); //发送ACK   
	
	return receive;
}

//PAJ7620U2写一个字节数据
u8 GS_Write_Byte(u8 REG_Address,u8 REG_data)
{
	GS_IIC_Start();
	GS_IIC_Send_Byte(PAJ7620_ID);
	if(GS_IIC_Wait_Ack())
	{
		GS_IIC_Stop();//释放总线
		return 1;//没应答则退出
	}
	GS_IIC_Send_Byte(REG_Address);
	GS_IIC_Wait_Ack();	
	GS_IIC_Send_Byte(REG_data);
	GS_IIC_Wait_Ack();	
	GS_IIC_Stop();

	return 0;
}

//PAJ7620U2读一个字节数据
u8 GS_Read_Byte(u8 REG_Address)
{
	u8 REG_data;
	
	GS_IIC_Start();
	GS_IIC_Send_Byte(PAJ7620_ID);//发写命令
	if(GS_IIC_Wait_Ack())
	{
		 GS_IIC_Stop();//释放总线
		 return 0;//没应答则退出
	}		
	GS_IIC_Send_Byte(REG_Address);
	GS_IIC_Wait_Ack();
	GS_IIC_Start(); 
	GS_IIC_Send_Byte(PAJ7620_ID|0x01);//发读命令
	GS_IIC_Wait_Ack();
	REG_data = GS_IIC_Read_Byte(0);
	GS_IIC_Stop();

	return REG_data;
}
//PAJ7620U2读n个字节数据
u8 GS_Read_nByte(u8 REG_Address,u16 len,u8 *buf)
{
	GS_IIC_Start();
	GS_IIC_Send_Byte(PAJ7620_ID);//发写命令
	if(GS_IIC_Wait_Ack()) 
	{
		GS_IIC_Stop();//释放总线
		return 1;//没应答则退出
	}
	GS_IIC_Send_Byte(REG_Address);
	GS_IIC_Wait_Ack();

	GS_IIC_Start();
	GS_IIC_Send_Byte(PAJ7620_ID|0x01);//发读命令
	GS_IIC_Wait_Ack();
	while(len)
	{
		if(len==1)
		{
			*buf = GS_IIC_Read_Byte(0);
		}
		else
		{
			*buf = GS_IIC_Read_Byte(1);
		}
		buf++;
		len--;
	}
	GS_IIC_Stop();//释放总线

	return 0;
	
}
//PAJ7620唤醒
void GS_WakeUp()
{
	GS_IIC_Start();
	GS_IIC_Send_Byte(PAJ7620_ID);//发写命令
	GS_IIC_Stop();//释放总线
}

         说实话,感觉这样照着程序COPY代码太土了,而且浪费大家时间。

        下面,重要的干货来了:

  • 1、要明确一点,I²C是一种通信方式,不要习惯性想着I²C又该怎么配置?是否要开启对应的时钟?是否可以产生中断?等等,这些东西都是用给外设配置的,通信方式的底层函数基本是不会变的,你要做的就是基于已有的几种命令,与你的IC进行通信。
  • 2、I²C根本不难,然而,就代表不用敲代码了吗?错,大错特错,好记性不如烂笔头,找一个I²C通信例程,敲几遍.C文件里的代码,对于以后做项目还是很有帮助的。
  • 3.不要懒惰,学习这几种通信方式,最好是对比着学习,在接下来的几天里,我将会以这几种方式,分别呈上我对几种通信方式的理解。大牛不喜勿喷,谢谢。

三、STM32工程源代码

(一)使用前必读

实验器材:    STM32F103 板
实验目的:    学习ATK-PAJ7620U2手势识别模块的使用,实现9个手势识别(GS)的检测功能,输出结果通过USART1显示在串口调试助手中。
硬件连接:
        PAJ7620U2手势识别模块与STM32板子连接:

       GPIO引脚配置(本例程 PF7接模块的SCL脚、PF8接模块的SDA脚)

  • // GS_IIC_SCL    PF(7) ----- SCL
    // GS_IIC_SDA    PF(8) ----- SDA     
  •    INT悬空(未用到)。

实验现象:
        手势识别(GS)测试 - 实现PAJ7620U2自带9个手势识别的检测,

        向上(Up)、向下(Dowm)、向左(Left)、向右(Right)、向前(Forward)、向后(Backward)、顺时针(Clockwise)、逆时针(Counterclockwise)、和挥动(Wave)。当识别到正确的手势,手势类型会打印在串口1上(后续可以把它显示LCD/OLED屏幕上)。

注意事项:
        1、模块属于光学器件,传感器表层的不洁净,会容易导致测量不佳,因此,在使用模块前,保持传感器表层洁净,工作时请勿用手去触摸,以免模块工作不正常。 

 下面是PAJ7620传感器模块I2C引脚配置 头文件:

#ifndef __PAJ7620U2_IIC_H
#define __PAJ7620U2_IIC_H
//	 
//本程序只供学习使用,未经作者许可,不得用于其它任何用途
//STM32F10x开发板  PAJ7620传感器模块IIC驱动	(模块I2C引脚配置头文件)
//STM32学习有问题,请加入q群交流: 643807576

#include "sys.h"

//GPIO引脚配置(本例程 PF7接模块的SCL脚、PF8接模块的SDA脚)	 
#define GS_IIC_SCL    PFout(7) 		//SCL(SCL_OUT)
#define GS_IIC_SDA    PFout(8) 		//SDA_OUT,用于发送SDA数据给IIC传感器模块
#define GS_READ_SDA   PFin(8) 		//SDA_IN,用于读取IIC传感器模块的SDA数据

//I/O方向配置(寄存器操作).
#define GS_SDA_IN()  {GPIOF->CRH&=0xFFFFFFF0;GPIOF->CRH|=4<<0;}	//PF8  浮空输入
#define GS_SDA_OUT() {GPIOF->CRH&=0xFFFFFFF0;GPIOF->CRH|=3<<0;}	//PF8  推挽输出(通用)

//PS:  I/O方向不会配置? :傻瓜式操作 ---> https://xinso.blog.csdn.net/article/details/115862486

u8 GS_Write_Byte(u8 REG_Address,u8 REG_data);
u8 GS_Read_Byte(u8 REG_Address);
u8 GS_Read_nByte(u8 REG_Address,u16 len,u8 *buf);
void GS_i2c_init(void);
void GS_WakeUp(void);

#endif

(二)单片机源程序 

#include "paj7620u2.h"
#include "paj7620u2_cfg.h"

static void paj7620u2_selectBank(bank_e bank);//选择PAJ7620U2 BANK区域
static u8 paj7620u2_wakeup(void);//PAJ7620U2唤醒

//PAJ7620U2初始化
//返回值:0:失败 1:成功
u8 paj7620u2_init()
{
	u8 i;
	u8 status;
	
	GS_i2c_init();//传感器I2C初始化							
	status = paj7620u2_wakeup();		//唤醒PAJ7620U2
	if(!status) 
		return 0;
	paj7620u2_selectBank(BANK0);	//进入BANK0寄存器区域
	for(i=0;i<INIT_SIZE;i++)						//初始化模块
	{
		GS_Write_Byte(init_Array[i][0], init_Array[i][1]);//初始化PAJ7620U2
	}
  paj7620u2_selectBank(BANK0);//切换回BANK0寄存器区域
	return 1;
}

GestureData *gesture;
void Gesture_Init(void)
{
	u8 i;
	paj7620u2_selectBank(BANK0);//进入BANK0寄存器区域
	for(i=0;i<GESTURE_SIZE;i++)
	{
		GS_Write_Byte(gesture_arry[i][0],gesture_arry[i][1]);//手势识别模式初始化
	}
	paj7620u2_selectBank(BANK0);//切换回BANK0寄存器区域

	gesture = (GestureData *)malloc(sizeof(GestureData));
	if(NULL == gesture){
		//
		printf("Error: struct \"GESTURE_DATA\" malloc failed\r\n");
	}
	memset(gesture, 0, sizeof(GestureData));
	
}

//选择PAJ7620U2 BANK区域
void paj7620u2_selectBank(bank_e bank)
{
	switch(bank)
	{
		case BANK0: GS_Write_Byte(PAJ_REGITER_BANK_SEL,PAJ_BANK0);break;//BANK0寄存器区域
		case BANK1: GS_Write_Byte(PAJ_REGITER_BANK_SEL,PAJ_BANK1);break;//BANK1寄存器区域
	}		
}

//PAJ7620U2唤醒
u8 paj7620u2_wakeup()
{ 
	u8 data=0x0a;
	GS_WakeUp();//唤醒PAJ7620U2
	delay_ms(5);//唤醒时间>400us
	GS_WakeUp();//唤醒PAJ7620U2
	delay_ms(5);//唤醒时间>400us
	paj7620u2_selectBank(BANK0);//进入BANK0寄存器区域
	data = GS_Read_Byte(0x00);//读取状态
	if(data!=0x20) return 0; //唤醒失败
	
	return 1;
}

主函数while(1)之前,记得初始化

//	 
//本程序只供学习使用,未经作者许可,不得用于其它任何用途
//STM32F10x开发板  PAJ7620传感器模块驱动	
//STM32学习有问题,请加入q群交流: 643807576

void PAJ7620_Init(void);

int main(void)
{		
	delay_init();	    	 //延时函数初始化	  
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //设置NVIC中断分组2:2位抢占优先级,2位响应优先级
	
	uart_init(115200);	    //串口初始化为115200
 	LED_Init();			    		//LED端口初始化
	PAJ7620_Init();					//LED1亮起、串口输出“PAJ7620U2 OK”--->说明PAJ7620模块初始化正常
	 
	TIM2_Int_Init(1299,7199); //130ms定时--手势传感数据采集,130ms是手势类型采样时间,采样时间越小,识别反应越灵敏,可以自己调一下采样时间到合适。
	
	while(1)
	{
		;
	}
}
//LED1亮起、串口输出“PAJ7620U2 OK”--->说明PAJ7620模块初始化正常
void  PAJ7620_Init(void)
{
	while(!paj7620u2_init())//PAJ7620U2传感器初始化
	{
		printf("PAJ7620U2_B Error!!!\r\n");
		delay_ms(500);
		LED0=!LED0;//DS0闪烁
	}
	LED1 =~LED1;	
	delay_ms(1000);
	Gesture_Init();
  printf("PAJ7620U2 OK\r\n");
}

在定时器里面加入手势识别轮询检测的中断服务程序:

//定时器2中断服务程序			/* 手势类型识别 */
void TIM2_IRQHandler(void)   //TIM2中断
{
	if (TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET)  //检查TIM2更新中断发生与否
	{
		TIM_ClearITPendingBit(TIM2, TIM_IT_Update  );  //清除TIMx更新中断标志 

		//手势识别
		gesture->detect = GS_Read_nByte(PAJ_GET_INT_FLAG1,2,&gesture->data[0]);//读取手势状态			
		if(!gesture->detect)
		{   
			gesture->type =(u16)gesture->data[1]<<8 | gesture->data[0];
			if(gesture->type) 
			{
				switch(gesture->type)
				{
					case GES_UP:               printf("Up\r\n");            gesture->valid=1;      break; //向上
					case GES_DOWN:             printf("Down\r\n");          gesture->valid=1;     break; //向下
					case GES_LEFT:            printf("Left\r\n");          gesture->valid=1;    break; //向左
					case GES_RIGHT:          	printf("Right\r\n");         gesture->valid=1;    break; //向右
					case GES_FORWARD:       	printf("Forward\r\n");       gesture->valid=1;   break; //向前
					case GES_BACKWARD:      		printf("Backward\r\n");      gesture->valid=1;   break; //向后
					case GES_CLOCKWISE:     		printf("Clockwise\r\n");     gesture->valid=1;   break; //顺时针
					case GES_ANTI_CLOCKWISE:  printf("AntiClockwise\r\n"); gesture->valid=1;    break; //逆时针
					case GES_WAVE:           printf("Wave\r\n");     gesture->valid=1;    break; //挥动
					default:  																				gesture->valid=0;   break;	
				}
			}
		}
	}
}

        谢谢观看,有问题需要请教的,请加入STM32学习交流群(QQ群号:643807576),本人QQ号2974278195有问题可添加看到必回。

项目工程源码下载↓:

STM32F103-PAJ7620-Project.zip-嵌入式文档类资源-CSDN下载icon-default.png?t=LA92https://download.csdn.net/download/qq_42605300/61839215

版权声明:本文为CSDN博主「夜半少年」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_42605300/article/details/120616257

生成海报
点赞 0

夜半少年

我还没有学会写个人说明!

暂无评论

发表评论

相关推荐

4路红外循迹模块使用教程

4路红外循迹模块使用教程 个人原创博客:点击浏览模块详细信息: 工作电压:DC 3.3V~5V 工作电流:尽量选择1A以上电源供电 工作温度:-10℃~50℃ 安装孔

基于STM32F407 DHT22温湿度测量

一、DHT22简介 1、DHT22介绍说明 DHT22数字温湿度传感器是一款含有已校准数字信号输出的温湿度复合传感器。它应用专用的数字模块采集技术和温湿度传感技术,确保产品具有极高的可靠性与卓越的长期稳定性。传感器包括一个电容