文章目录[隐藏]
最近在自学设计下基于STM32单片机的项目,想用手势识别模块做一点好玩的,做个手势控制家居设备开关,另外正好借此巩固I²C 通信。因此,我想借这个机会在这里和大家分享一下自己学习STM32单片机时的所思所感吧,若有表述不对之处,还请各位大佬指出,我好立刻改正。
话不多说,先上硬货。
一、PAJ7620U2模块解读
项目手势识别检测模块选择基于PixArt公司PAJ7620U2传感器开发的一块PAJ7620手势识别模块(淘宝上很多这种模块)。
图1 PAJ7620U2传感器模块
网上很多店铺卖的这种 PAJ7620手势识别模块,但其实都是基于传感器设计的,只不过外围电路设计和形状外观不太一样而已,整体功能特性基本都一样,所以大家不用过多去选择应该买哪家的。
(一)手势识别模块选型
PAJ7620U2模块是基于PAJ7620U2传感器设计,功能与特性几乎会与PAJ7620U2传感器的数据手册保持一致。
那我们如何分析比较各个手势识别传感器模块?
首先自己查找/找商家要到 相关 数据手册等资料,然后我们主要关注以下几个问题:
- 模块内置的手势类型有哪些?识别速度(采样频率)?有效检测距离?有效检测角度?
- 模块供电电压范围?采用的通讯接口是什么?模块功耗?
通过上述问题,我们可以分析该传感器模块能不能满足项目需求。
如下表所示,通过对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 |
5~15mA |
(二)PAJ7620U2传感器的工作原理
图2 PAJ7620U2传感器内部框图
从上图中,我们可以看到PAJ7620U2传感器内部自带 LED 驱动器(可发射红外线信号),内置有传感器感应阵列、目标信息提取阵列和手势识别阵列。另外,PAJ7620U2作为一种光学数组式传感器,其内置LED驱动器集成了环境光和光源抑制滤波器,模块基本不受环境光干扰。
红外LED手势识别原理:
传感器工作时通过内部 LED 驱动器,驱动红外 LED 向外发射红外线信号,当传感器阵列在有效的距离中探测到物体时,目标信息提取阵列会对探测目标进行特征原始数据的获取,采集到的数据被保存在寄存器中,同时手势识别阵列会对原始数据进行识别处理,最后将手势结果存到寄存器中。
根据 PAJ7620U2传感器数据手册,用户可通过I²C接口总线采集信号并迅速识别出UP、Down、Right、Left等9种常用手势。另外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下载https://download.csdn.net/download/qq_42605300/61839215
版权声明:本文为CSDN博主「夜半少年」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_42605300/article/details/120616257
暂无评论