【.NET 与树莓派】九种手势识别模块(PAJ7620)
2021-05-30 06:00
标签:break 布尔值 顺时针 const string 识别 有用 结合 智能 你要是说手势识别这玩意儿到底用处有多大,真的不好说,大不算大,小也不算小。日常生活中见得比较多的像一些小台灯、厨房开关之类,都有使用手势识别。从实用方面看,厨房里装手势开关还不错的,有时候满手都是猪油鸡油的,再用手按按开关,过不了几个月,开关按钮都变成麦牙糖了。或者干脆整个手势开水龙头也行。不过话又说回来,这玩意儿目前的情况,识别率还不算高。你可能会说。花大价钱买个贵一些的就会准确率高了,这个嘛,还真不一定。你懂的,现在许多“高科技”产品,说难听一点就是商业泡沫,哄你去买。它加个传感器,可能成本就是3到5块钱,但它可以忽悠你这多么高端,所以我要卖贵60元。还有一些特熟悉的吹牛口号——“很贵,但很值得”、“不要买XXX,除非你看过我”。 手势感应有好几种芯片,老周买的是正点原子的 PAJ7620(主要是冲着九种手势识别这功能,有的只是六种手势识别)。话说这货也不便宜,说实话,当初还不如买亚博的。亚博的模块有个优点:支持多种接线法,可以用 X-pin 排线口,可以用杜邦线,也可以用鳄鱼夹。 该模块长这样子。 不要被图片误导了,拿到手之后,发现这玩意儿很小,这不,你看…… 手机拍照时,如果模块正在使用,你从手机屏幕上会看到有个亮点,这是PAJ7620上面的红外发射器。 此模块使用 IIC(I2C)协议通信,默认的从机地址是 0x73。操作作方式是读写寄存器。每个寄存器都有其各自的地址,只要向相应的地址写入字节,数据就会存到寄存器中。 1、读寄存器的方法:首先向从机地址0x73写入要读的寄存器的地址;然后从模块读取一个字节,这个字节就是该寄存器的值。 2、写寄存器的方法:向从机地址0x73写入两个字节——第一个字节指定寄存器的地址,第二个字节是要写入的值。 举例: a、要向寄存器0x42写入0x01,那么就向从机0x73发送两个字节:0x42、0x01。 b、要读取寄存器0x23的值,先向从机0x73发送一个字节0x23,然后读一个字节。 ========================================================== PAJ7620 模块的寄存器不多,操作起来也不算复杂。发现有些大伙伴们说模块没反应,是不是坏了?这个不好说,不过一般不会,买到坏的模块也是需要运气的。最大的可能是你操作的流程不对。因为这个模块有点奇葩(可以为了节约电费):通电后默认是处于休眠状态,所以是不会识别手势的。 所以老周估计这位同学大概是没有把模块唤醒就读取数据,那你读到的只能是00 00 00 00了。 好了,F话不扯,但老周也不打算把寄存器一个个地介绍,那样太无聊了,咱们结合实际的使用来阐述。 PAJ7620虽然寄存器不多,但它热爱分区。其寄存器总共分了两个带区——Bank 0 和 Bank 1。所以,有的寄存器位于 Bank 0,有的寄存器位于 Bank 1,咱们在操作时一定要注意,读写寄存器前要先切换带区,不然读到的值是不对的。 带区切换方法: * 第一带区:向寄存器 0xEF 写入 0x00; * 第二带区:向寄存器 0xEF 写入 0x01。 比如,寄存器地址 0x72 用于启用(使能)或禁用(失能)PAJ7620 模块,它位于 Bank 1 带区。要读写该寄存器,得分两步走(0x73是从机地址)。 step 1:---> 0x73 写入 0xEF 0x01 step 2:---> 0x73 读取 0x72 这个寄存器上面提过,它位于 Bank 1 中。向这个寄存器写入 0x00 会禁用PAJ7620模块,写入 0x01 启用此模块。 挂起,即休眠状态的值存放在寄存器 0x03 中,位于 Bank 0。寄存器的值只有第一个二进制位有用,0x00 表示模块正在工作,0x01 表示模块进入休眠。 要让模块进入休眠状态,步骤如下: 1、向0xEF发送0x01,选择 Bank 1; 2、向寄存器 0x72 写入 0x00,禁用模块; 3、向寄存器0xEF写入0x00,选择 Bank 0; 4、向寄存器0x03写入0x01,进入休眠。 通电后,模块默认也是进入挂起状态的,所以这时候是识别不了手势的,一定要先把它唤醒。唤醒比较简单,只需要正常的 IIC 信号就可以。正点原子的文档中讲述了一种唤醒方法:读取 0x00 寄存器,如果返回 0x20 表明成功唤醒。 模块被唤醒后仍然处于被禁用(失能)状怘,故唤醒后还要向地址为 0x72 的寄存器写入 0x01 才算完成。至于 0x03 寄存器(挂起)不必理会,它会自动清零。 有大伙伴说 PAJ7620 模块没反应,很可能就是在唤醒之后忘了使能(写 0x72 寄存器)模块。 至此,可以总结出,模块的初始化过程应该是这样的? 1、向从机 0x73 循环读取 0x00 寄存器,直到它返回 0x20,完成唤醒操作; 2、向寄存器 0xEF 写入 0x01 切换到 Bank 1 带区; 3、向寄存器 0x72 写入 0x01,使模块进入正常工作状态。 这两个寄存器并不是用来读取被检测到的手势,而是设定模块支持哪几个手势的检测。每个二进制位表示一种手势,若为1则表示可以检测该手势;若为0则模块不检测该手势。每个寄存器存放一个字节,共八位。咱们前面扯过,PAJ7620模块支持九种手势的识别,所以一个字节八位,放不下呢。寄存器 0x41 存放前八种手势的标志,寄存器 0x42 存放剩下一种手势。故实际上 0x42 中只用到了第一个二进制位,其余七个用不上。 这两个寄存器才是真正用来读取手势检测结果,同理,由于一个字节的八位不够用,所以用了两个寄存器。如果某一位的值为1则表明检测到此手势;反之为0就是没检测到。 0x41、0x42 与 0x43、0x44 中的二进制位是一一对应的。文档中的默认定义如下: 二进制位从低到高:上、下、左、右、前、后、顺时针、逆时针。剩下一个手势在第二个字节的最低位,手势为挥手——就是 Say Goodbye 的动作,手掌放在模块前来回摇动。 不过,这个定义只是相对的,毕竟我们在真实环境使用时。模块的安装方向可以旋转 X 角度。这时候,要多做测试,重新定义各个二进制位所对应的手势。按照正点原子的文档所述,正确的放置方位是这样的。 但老周是这样放的。 所以手势的方向就得重新定义了,总之,一个二进制位对应着一种手势,至于代表哪种手势,视你放置模块的方向来确定,可以多试试。 ==================================================== 好,上面内容是对模块的核心功能介绍,有了上面的认知,再将其转化为程序代码就好办了。为了用起来更香,比较好的方案是进行类封装——老周写了个PAJ7620类,此类包含以下方法: * WakeUp:唤醒模块; * Suspend:挂起模块; * SetEnable:启用/禁用模块; * GetGesture:获取检测到的手势; * SelectBank0 和 SelectBank1:切换寄存器带区。 PAJ7620 模块默认情况下会启用对九种手势的检测,因此老周的代码中未对寄存器 0x41 和 0x42 进行读写,有兴趣的大伙伴可以自己加上,反正操作都一样,就是对寄存器的读和写。 首先,咱们把要用到的寄存器地址作为常量声明,后面引用起来方便。 下面是模块的默认从机地址——0x73。 在类的构造函数中,咱们初始化 IIC 设备的连接。 从机地址使用默认地址,就是上面定义的常量 DEFAULT_ADDR。 接下来就是各种方法的实现了。先看两个寄存器带区的切换,这两个方法我都写成私有方法,没有必要公开。 由于要发送的只有两个字节,所以呢,这里可以用 stackalloc 直接在栈上分配内存,主要是速度快,当然你用传统的数组实例化方法也行。 byte[] buff = new byte[] { }; 第一个字节是选择带区的寄存器地址 0xEF,第二个字节就是带区编号。另一个方法的原理一样。 好,下面是 SetEnable 方法的实现,可以启用或禁用模块。 isenable 参数是个布尔值,如果是true,向寄存器0x72写入1,否则写入0。 接着是 Suspend 方法,挂起模块。 挂起前一定要将模块禁用,才能进入挂起状态。 下面是唤醒模块的方法。 WakeUp 方法其实分两个阶段:先是读寄存器0x00,在读寄存器时会向模块发信息,就等于发出唤醒信号(任何 IIC 通信都会包含 Start 时序),然后尝试五次,如果五次都唤不醒,估计是睡死了,就抛异常。 第二阶段是启用(使能)模块,调用 SetEnable 方法。 最后是核心方法,读出检测到的手势。 前文说过,手势共有九种,分配在两个字节上,第一个字节从寄存器 0x43 中读出,第二个从 0x44 中读出。为了用起来方便,老周把两个字节合起来,转换为 int 类型的值。从低位起,1 - 9位依次表示检测到的九种手势。 下面是完整代码,各位可以抄来即食。 好了,基本类型封装完毕,而后咱们就可以拿来耍了,这里老周没准备高级的应用,仅仅是写个测试程序。 硬件接线:只接VCC、GND、SCL、SDA四个针脚即可,其他可以不管。 VCC 接树莓派的 3.3V,5V也可以,模块上有做宽电压兼容; GND 接树莓派的GND; SCL 接树莓派的 GPIO 3; SDA 接树莓派的 GPIO 2。 运行这个程序后,你可以对着它做各种手势,然后随便按个键继续循环,屏幕会打印出各个二进制位的值。 前面老周说过,对九种手势的定义是相对的,取决于你把模块的安装方向和角度。不过,第九位(挥手)是不变的,因为不管你怎么安放,挥手的动作都是来回晃动几下,识别结果一样;再有,前、后两个手势也一样,把模块水平放置,发射光头朝上,然后你的手从上往下接近模块,就是向前的手势;相反,你的手从离模块较近的位置往上抬起就是向后。安装方向的不同一般只影响上、下、左、右四个方向上的手势。 这个模块其实识别的准确率不是很高,容易受干扰,比如你在旁边开个台灯,或者拿手电筒斜着在模块上晃几下,或者在它旁边吃烤鸭,都会导致识别错误,或者干脆识别不了。 至于说,使用这个模块能干吗呢?现在流行人工智……Zhang……哦不,Z能,所以,你可以用它来做个手势开灯,手势控制智能车转弯(估计会翻车),手势开门(不知道会不会夹到人),手势操作轮椅(有风险)。再深入一点的,上完厕所,对着马桶挥挥手,自动冲水,不带走一片云彩。 【.NET 与树莓派】九种手势识别模块(PAJ7620) 标签:break 布尔值 顺时针 const string 识别 有用 结合 智能 原文地址:https://www.cnblogs.com/tcjiaan/p/14687788.htmlNo.1 选择寄存器带区(地址:0xEF)
No.2 使能寄存器(地址:0x72)
No.3 挂起和唤醒模块
No.4 设置手势检测的标志位(寄存器地址:0x41 和 0x42)
No.5 手势检测结果(寄存器地址:0x43 和 0x44)
const byte SELECTE_BANK = 0xEF; //切换带区
const byte BANK0 = 0x00; //带区0
const byte BANK1 = 0x01; //带区1
const byte ISENABLE = 0x72; //使能/失能模块
const byte GES_DETECT = 0x43; //读取手势
const byte GES_DETECT2 = 0x44; //读取手势(第九种)
const byte SUSPEND = 0x03; //使模块挂起(休眠)
public const int DEFAULT_ADDR = 0x73;
private I2cDevice _device=default;
public Paj7620(int busid = 1, int address = DEFAULT_ADDR)
{
I2cConnectionSettings settings=new(busid, address);
_device = I2cDevice.Create(settings);
}
private void SelectBank0()
{
Spanbyte> buff = stackalloc byte[2]{
SELECTE_BANK,
BANK0
};
_device.Write(buff);
}
private void SelectBank1()
{
Spanbyte> buff = stackalloc byte[]
{
SELECTE_BANK, BANK1
};
_device.Write(buff);
}
public void SetEnable(bool isenable)
{
SelectBank1(); //先切换到 Bank 1
byte[] data =
{
ISENABLE, //0x72
(byte)(isenable? 0x01 : 0x00)
};
_device.Write(data);
}
public void Suspend()
{
// 先将其失能
SetEnable(false);
// 再挂起
SelectBank0(); //记得切换带区
byte[] data = {SUSPEND, 0x01};
_device.Write(data);
}
public void WakeUp()
{
int count = 0;
// 尝试唤醒
while(0==0)
{
_device.WriteByte(0x00);
// 等待700微秒即可
// 1毫秒一般够用
Sleep(1);
count++;
byte back = _device.ReadByte();
if(back == 0x20)
{
break;
}
if(count > 4)
{
// 多次尝试均无法唤醒模块
throw new Exception("模块无法唤醒");
}
Sleep(5);
}
// 使能
SetEnable(true);
}
public int GetGesture()
{
SelectBank0();
// 前八个
_device.WriteByte(GES_DETECT);
byte p1 = _device.ReadByte();
// 第九个
_device.WriteByte(GES_DETECT2);
byte p2 = _device.ReadByte();
// 合起来
return (p2 8) | p1;
}
using System;
using System.Device.I2c;
using static System.Threading.Thread;
namespace Device
{
public class Paj7620 : IDisposable
{
#region 寄存器列表
const byte SELECTE_BANK = 0xEF; //切换带区
const byte BANK0 = 0x00; //带区0
const byte BANK1 = 0x01; //带区1
const byte ISENABLE = 0x72; //使能/失能模块
const byte GES_DETECT = 0x43; //读取手势
const byte GES_DETECT2 = 0x44; //读取手势(第九种)
const byte SUSPEND = 0x03; //使模块挂起(休眠)
#endregion
///
using System;
using static System.Threading.Thread;
using static System.Console;
using Device;
namespace myapp
{
class Program
{
static bool isRunning = false;
static void Main(string[] args)
{
using Paj7620 paj = new();
// 唤醒
paj.WakeUp();
WriteLine("设备已唤醒");
CancelKeyPress += (_, _) => isRunning = false;
Sleep(500);
isRunning = true;
while (isRunning)
{
int res = paj.GetGesture();
// 变成二进制显示
string str = Convert.ToString(res, 2);
str = str.PadLeft(9, ‘0‘);
str = string.Join(" | ", str.ToCharArray());
WriteLine(str);
WriteLine("按任意键继续");
ReadKey(true);
}
}
}
}
上一篇:maven管理本地jar包
下一篇:js中的变量提升
文章标题:【.NET 与树莓派】九种手势识别模块(PAJ7620)
文章链接:http://soscw.com/index.php/essay/89426.html