使用APDS-9960进行手势识别

图片

阅读我以前关于APDS-9960的文章的评论,其中涉及颜色识别和亮度,对我来说,有两件事变得显而易见:1)手势识别主题很有趣,并且2)这个主题没有公开。

的确,如果我讲了APDS-9960的说明,那么在不考虑手势的情况下,说明看起来有些不完整。 因此,我也有一些空闲时间来探索这个主题。

在本文中,我提醒您注意APDS-9960传感器提供的手势识别功能。

本文将考虑用于设置传感器,数据收集,处理和表示的机制。 您自己可以看到使用APDS-9960进行手势操作有多么容易。

上次,本文将附带一个代码,其中将详细描述发生的所有事情。 本文末尾提供了完整的代码版本。

随便一句话:APDS-9960没有内置的自动手势检测机制; 也就是说,在这里,我读到了它的意思,即寄存器,并且已经存在经过处理的手势-这不在APDS-9960中; 这意味着您必须编写自己的手势解释算法,稍后我们将做。

总的来说,这既好又不好。 并非如此-因为它可以使初学者对该传感器的研究变得复杂,但是很好,因为,加上近似数据,您甚至可以细化自己的各种手势。

但是,由于本文仅具有概述功能,因此我们只限于基本的UP-DOWN-LEFT-RIGHT手势。

好吧,让我们开始吧。

理论


我会给自己一点物资。

为了获得有关运动和运动方向的必要信息,APDS-9960使用一个IR LED和四个光电二极管,如下图所示,它们检测近红外(NIR)范围内的信号。

图片

IR LED(LED)具有背光功能,而光电二极管(UDLR)记录从“障碍物”反射的光。

光电二极管以这样的方式位于传感器上:根据“障碍物”的移动方向,相应的光电二极管将在输入端接收大部分反射的IR信号,并在输出端接收较小的一部分。 同时,APDS-9960上的文档明确告诉我们,您可以通过测量和比较UDLR光电二极管信号的幅度和相位差来解释运动方向。

图片

练习


要与APDS-9960以及上一次一起使用,我们将使用STM32VLDISCOVERY。 连接也没有改变。

配置APDS-9960

我们进行传感器的初始设置。

像这样:

APDS9960_init
void APDS9960_init(void) { i2c1_write(APDS9960_CONTROL, DEFAULT_PGAIN); i2c1_write(APDS9960_GPENTH, DEFAULT_GPENTH); i2c1_write(APDS9960_GEXTH, DEFAULT_GEXTH); i2c1_write(APDS9960_GCONF2, DEFAULT_GGAIN); i2c1_write(APDS9960_GPULSE, DEFAULT_PULSE_LENGTH); i2c1_write(APDS9960_PPULSE, DEFAULT_PULSE_LENGTH); } 

这是怎么回事 让我们做对。

 i2c1_write(APDS9960_CONTROL, DEFAULT_PGAIN); 

PGAIN(接近度增益控制)是控制接近度灵敏度的增益的参数。 给它分配一个值为2的值,它对应于增益的四倍。

 i2c1_write(APDS9960_GPENTH, DEFAULT_GPENTH); i2c1_write(APDS9960_GEXTH, DEFAULT_GEXTH); 

GPENTH(手势接近度输入阈值寄存器)-此参数设置用于确定手势识别开始的接近度阈值。

GEXTH(手势退出阈值寄存器)分别设置用于确定手势识别结束的阈值。

 i2c1_write(APDS9960_GCONF2, DEFAULT_GGAIN); 

在GCONF2(两个手势配置)寄存器中,我们仅将GGAIN(手势增益控制)参数显式设置为增益值的四倍。

 i2c1_write(APDS9960_GPULSE, DEFAULT_PULSE_LENGTH); i2c1_write(APDS9960_PPULSE, DEFAULT_PULSE_LENGTH); 

背光灯 默认情况下,红外LED背光源的电流值设置为0(对应于100 mA的电流),这将非常适合我们-我们将不会对其进行更改。

APDS-9960中的IR照明是一个脉冲序列,其特征在于手势GPULSE(手势脉冲计数和长度)的相应寄存器参数:GPLEN(手势脉冲长度)和GPULSE(手势脉冲数),以及PPULSE(接近脉冲计数寄存器)的近似值):PPLEN(接近脉冲长度)和PPULSE(接近脉冲计数)设置脉冲数和每个单独脉冲的周期。

定义GPLEN和PPLEN的值为2等于16μs,GPULSE和PPULSE的值为9,对应10个脉冲。

如您所见,与以前的APDS-9960评论中的颜色识别和照明设置相比,该设置要稍微复杂一些。

读取数据

现在,我们进入主程序循环,在该循环中,我们不时开始注册和解释光电二极管中的数据,并学习如何将一个手势与另一个手势区分开。

首先,让我们以手势和缩放功能启动APDS-9960。

 GesturesSet(GESTURES_START); 

然后我们立即开始跟踪GVALID参数。 GVALID(手势FIFO数据)是GSTATUS寄存器(手势状态寄存器)的参数,处于非零状态时,通知我们传感器具有可用的手势数据。

该文档告诉我们手势信息位于RAM区域的缓冲区中,该缓冲区的大小通常为32 x 4字节。

实际上,该缓冲区的实际大小可以通过读取GFLVL(手势FIFO级别)寄存器的值来找到,即 根据我的纯粹经验实验观察,获得GFLVL * 4。 像这样:

图片

好吧,如下所示,根据缓冲区的名称,缓冲区中的数据按照先进先出的顺序排列。 也就是说,粗略地说,来自每个光电二极管的信号“越早”到达,其在GFLVL中的“越高”。

可以从相应的手势FIFO寄存器读取光电二极管(UDLR)的数据:

-GFIFO_U(手势FIFO数据,UP)
-GFIFO_D(手势FIFO数据,向下)
-GFIFO_L(手势FIFO数据,左)
-GFIFO_R(手势FIFO数据,右)

每次从这些寄存器中读取值后,GFLVL都会减小。 因此,有必要以良好的方式读取整个缓冲区,直到GFLVL达到零为止。

要定义手势,我们只需要此缓冲区的前四个字节,就不需要更多。 因此,我们只会阅读它们。

 GestureUp = i2c1_read(APDS9960_GFIFO_U); GestureDown = i2c1_read(APDS9960_GFIFO_D); GestureLeft = i2c1_read(APDS9960_GFIFO_L); GestureRight = i2c1_read(APDS9960_GFIFO_R); 

手势识别

为了解释发生了哪种手势,我们将进行简单的计算:

 GestUpDown = GestureUp-GestureDown; GestLeftRight = GestureLeft-GestureRight; 

要确定当前发生了哪些手势,对我们而言,重要的不是GestUpDown和GestLeftRight的值,而是一个实数的符号。

也就是说,以GestUpDown和GestLeftRight变量的负值和正值作为输入,我们确定哪个手势是完美的。

下图显示了GestUpDown和GestLeftRight变量的真值表。

图片

现在重置GFLVL:

 GesturesSet(GESTURES_STOP); 

...然后回到主程序周期的开始。

现在整个代码:

main.c
 #include "stm32f10x.h" #define APDS9960_I2C_ADDR 0x39 #define APDS9960_ENABLE 0x80 #define APDS9960_GSTATUS 0xAF #define APDS9960_GFLVL 0xAE //Gesture FIFO Register (0xFC – 0xFF): #define APDS9960_GFIFO_U 0xFC #define APDS9960_GFIFO_D 0xFD #define APDS9960_GFIFO_L 0xFE #define APDS9960_GFIFO_R 0xFF #define APDS9960_CONTROL 0x8F #define APDS9960_GPENTH 0xA0 #define APDS9960_GEXTH 0xA1 #define APDS9960_GCONF2 0xA3 #define APDS9960_GPULSE 0xA6 #define APDS9960_PPULSE 0x8E #define GESTURES_START 0x01 #define GESTURES_STOP 0x02 #define DEFAULT_GPENTH 40 // Threshold for entering gesture mode #define DEFAULT_GEXTH 30 // Threshold for exiting gesture mode #define DEFAULT_PGAIN 8 // Proximity Gain Control: 4X #define DEFAULT_GGAIN 0x40 // Gesture Gain Control: 4X #define DEFAULT_PULSE_LENGTH 0x89 // 16us, 10 pulses /* Bit fields */ #define APDS9960_PON 0x01 #define APDS9960_AEN 0x02 #define APDS9960_PEN 0x04 #define APDS9960_WEN 0x08 #define APSD9960_AIEN 0x10 #define APDS9960_PIEN 0x20 #define APDS9960_GEN 0x40 #define APDS9960_GVALID 0x01 int GestUpDown = 0; int GestLeftRight = 0; //----------------------------------------------------------------------- uint8_t i2c1_read(uint8_t addr); void i2c1_write(uint8_t addr, uint8_t data); void I2C1_init(void) { I2C_InitTypeDef I2C_InitStructure; GPIO_InitTypeDef GPIO_InitStructure; RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB| RCC_APB2Periph_AFIO , ENABLE); GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7; GPIO_Init(GPIOB, &GPIO_InitStructure); I2C_StructInit(&I2C_InitStructure); I2C_InitStructure.I2C_ClockSpeed = 100000; I2C_InitStructure.I2C_OwnAddress1 = 0x01; I2C_InitStructure.I2C_Ack = I2C_Ack_Enable; I2C_Init(I2C1, &I2C_InitStructure); I2C_Cmd(I2C1, ENABLE); } //----------------------------------------------------------------------- void APDS9960_init(void) { i2c1_write(APDS9960_CONTROL, DEFAULT_PGAIN); i2c1_write(APDS9960_GPENTH, DEFAULT_GPENTH); i2c1_write(APDS9960_GEXTH, DEFAULT_GEXTH); i2c1_write(APDS9960_GCONF2, DEFAULT_GGAIN); i2c1_write(APDS9960_GPULSE, DEFAULT_PULSE_LENGTH); i2c1_write(APDS9960_PPULSE, DEFAULT_PULSE_LENGTH); } //----------------------------------------------------------------------- uint8_t i2c1_read(uint8_t addr) { uint8_t data; while(I2C_GetFlagStatus(I2C1, I2C_FLAG_BUSY)); I2C_GenerateSTART(I2C1, ENABLE); while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT)); I2C_Send7bitAddress(I2C1, APDS9960_I2C_ADDR<<1, I2C_Direction_Transmitter); while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)); I2C_SendData(I2C1, addr); while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED)); I2C_GenerateSTART(I2C1, ENABLE); while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT)); I2C_Send7bitAddress(I2C1, APDS9960_I2C_ADDR<<1, I2C_Direction_Receiver); while(!I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_BYTE_RECEIVED)); data = I2C_ReceiveData(I2C1); while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_RECEIVED)); I2C_AcknowledgeConfig(I2C1, DISABLE); I2C_GenerateSTOP(I2C1, ENABLE); while(I2C_GetFlagStatus(I2C1, I2C_FLAG_BUSY)); return data; } //----------------------------------------------------------------------- void i2c1_write(uint8_t addr, uint8_t data) { I2C_GenerateSTART(I2C1, ENABLE); while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT)); I2C_Send7bitAddress(I2C1, APDS9960_I2C_ADDR<<1, I2C_Direction_Transmitter); while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)); I2C_SendData(I2C1, addr); while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED)); I2C_SendData(I2C1, data); while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED)); I2C_GenerateSTOP(I2C1, ENABLE); while(I2C_GetFlagStatus(I2C1, I2C_FLAG_BUSY)) {}; } //----------------------------------------------------------------------- void GesturesSet(uint8_t GestSel) { switch (GestSel) { case GESTURES_START: i2c1_write(APDS9960_ENABLE, APDS9960_GEN | APDS9960_PEN | APDS9960_PON); break; case GESTURES_STOP: i2c1_write(APDS9960_ENABLE, APDS9960_PEN | APDS9960_PON); break; default: i2c1_write(APDS9960_ENABLE, APDS9960_GEN | APDS9960_PEN | APDS9960_PON); } } //----------------------------------------------------------------------- int main() { uint8_t GFLVL_buf = 0; uint8_t GSTATUS_buf = 0; uint8_t GestureUp = 0; uint8_t GestureDown = 0; uint8_t GestureLeft = 0; uint8_t GestureRight = 0; I2C1_init(); APDS9960_init(); while (1) { GFLVL_buf = 0; GSTATUS_buf = 0; GestureUp = 0; GestureDown = 0; GestureLeft = 0; GestureRight = 0; GestUpDown = 0; GestLeftRight = 0; GesturesSet(GESTURES_START); GSTATUS_buf = i2c1_read(APDS9960_GSTATUS); if(GSTATUS_buf & APDS9960_GVALID) { GFLVL_buf = i2c1_read(APDS9960_GFLVL); if(GFLVL_buf) { GestureUp = i2c1_read(APDS9960_GFIFO_U); GestureDown = i2c1_read(APDS9960_GFIFO_D); GestureLeft = i2c1_read(APDS9960_GFIFO_L); GestureRight = i2c1_read(APDS9960_GFIFO_R); //Truth table: //UP: GestUpDown(+) | GestLeftRight(+) //DOWN: GestUpDown(-) | GestLeftRight(-) //LEFT: GestUpDown(+) | GestLeftRight(-) //RIGHT: GestUpDown(-) | GestLeftRight(+) GestUpDown = GestureUp-GestureDown; GestLeftRight = GestureLeft-GestureRight; GesturesSet(GESTURES_STOP); } } } } 


我想指出,APDS-9960的手势机制工作得很好。 识别稳定,APDS-9960紫外和红外内置的滤光片工作良好。

我希望这些材料对某人有用。 谢谢您的关注。

Source: https://habr.com/ru/post/zh-CN424947/


All Articles