课程一中,已经配置好了调试串口、串口打印输出,以及PA5的LED灯闪烁功能。课程二要在课 程一基础上,做如下功能:
(1) 配置好STM32的I2C外设,
(2) 开发手势传感器PAJ7620的驱动代码,读取手势识别结果
(3) 将识别的手势结果,打印到 USART3串口上输出。
一. PAJ7620手势传感器
1.资料收集和参数性能
本次实验使用的是seeed studio的Grove-Gesture v1.0,其官方的文档地址为: https://wiki.seeedstudio.com/Grove-Gesture_v1.0/
上述链接文档里,提供了基于Arduino和Raspberry PI的使用例程,但是没有基于STM32的驱动。这些示例代码是使用Python开发的,研究下这些代码可以弄清楚PAJ7620传感器的使用步骤。
同时在页面上Resource章节,提供了 PCB图和手册的下载,手册地址如下,这也是非常重要的官方一手开发开发资料。
https://files.seeedstudio.com/wiki/Grove_Gesture_V_1.0/res/PAJ7620U2_Datasheet_V0.8_20140611.pdf
简单总结下硬件的基本参数:
l 内置9个手势类型:上,下,左,右,前,后,顺时针旋转,逆时针旋转,挥动。
l 支持接近检测功能,检测物体体积大小和亮度。
l 分辨率:60x60 pixels ,每个像素的大小是:20x20 um2 , 最大支持的速率:720fps。
l 模式:gesture / cursor / image modes
l 控制速度:在普通模式下:60~600°/s
l 通讯模式:I2C模式(for gesture/ cursor 模式):最高能达到400kbit/s
2. 工作原理
参考资料:
https://blog.csdn.net/weixin_36590806/article/details/124805530
https://gitee.com/RT-Thread-Mirror/Gesture_PAJ7620
https://gitee.com/zhagyuji/stm32f4_paj7620/tree/master/OLED/PAJ7620
通过研究官网给出的树莓派示例代码,以及搜索到的STM32F4上的一些零零散散的代码,基本弄清楚模块使用的原理和驱动步骤。
l 工作原理:PAJ7620u2工作时通过内部LED驱动器,驱动红外LED向外发射红外信号,当传感器阵列在有效地距离中探测到物体时,目标信息提取阵列会对探测目标进行特征原始数据地获取,获取数据会存在寄存器中,同时手势识别阵列会对原始数据进行识别处理,最后将手势结果存到寄存器中,用户可根据I2C接口对原始数据和手势数据地结果进行读取。
l 传感器支持好几种模式。使用前需要先将固定的大片配置字节写入到特定的寄存器中,将传感器配置为“手势模式”
l 在PAJ7620内部有两个BANK 寄存器区域,分别是BANK0,BANK1。想访问某个bank区域下的寄存器,需要在访问前发送控制指令进入该寄存器区域,具体控制指令为:进入BANK0区域,往传感器0XEF地址写0X00;进入BANK1区域,往传感器0XEF地址写0x01。
l 手势识别支持9种手势,识别结果存放在手势识别寄存器:0X43,0X44中,如下图所示:
二. 硬件接线
1. I2C引脚与开发板的接线连接
可以使用硬件I2C外设,也可以软件翻转引脚方式实现I2C。我使用硬件I2C,具体引脚为PB6/PB7。从下方I2C的连接图上看,需要SDA和SCL都设置为开漏输出模式,且接上拉电阻。
上图中上拉电阻是必须要有的,可以在接线时接2个物理电阻实现上拉,这样比较麻烦。另一种方法是,MCU的GPIO脚做输出时,可以通过配置实现引脚默认上拉或下拉,我们就采用这种方法。因此,后续CubeMX配置的时候,一定要将SDA和SCL的两个pin脚都修改为Pull-Up模式!!
查看开发板的引脚手册,找到PB6和PB7,在CN10的引脚3,5,7,9四根线就可以了。
这里需要提一下,传感器带的四针插头,与CN10的引脚间距不兼容。需要自己焊接,或者像我一样把自带的白色头拔下来,找两个杜邦线跳帽也拔下来,自制一个对接线,如图所示。
2. CubeMX配置硬件I2C外设
a.点击课程1工程,打开。
b.使能I2C1,并重新配置IO口PB6/PB7为I2C1。
c.设置I2C1的IO口为上拉/输出高速。一定要记住,配置GPIO引脚为上拉模式,否则需要自己外接上拉电阻。
三. 代码部分
1. I2C基本收发字节的函数
这部分,CubeMX配置好外设,HAL自带了I2C通讯的基本读写字节基本功能代码,从头文件中可以查看:
图中头文件中,HAL_I2C_Mem_Write()和HAL_I2C_Mem_Read()就是直接对外设地址的读写函数,实现PAJ7620驱动的时候可以直接使用,非常方便。
2. 实现PAJ7620的驱动部分
根据已有的代码和资料,开发驱动分为两个文件:paj7620.h/paj7620.c
先看头文件,除了基本的手势功能定义外,主要是三个函数:
再看c文件中,三个函数的实现部分。第一个初始化函数paj7620_init():
//PAJ7620初始化寄存器数组:219对 {Address, value} uint8_t Paj7620Init_Reg_Array[][2] = { {0xEF, 0x00}, {0x32, 0x29}, {0x33, 0x01}, {0x34, 0x00}, {0x35, 0x01}, {0x36, 0x00}, {0x37, 0x07}, {0x38, 0x17}, {0x39, 0x06}, {0x3A, 0x12}, {0x3F, 0x00}, {0x40, 0x02}, {0x41, 0xFF}, {0x42, 0x01}, {0x46, 0x2D}, {0x47, 0x0F}, {0x48, 0x3C}, {0x49, 0x00}, {0x4A, 0x1E}, {0x4B, 0x00}, {0x4C, 0x20}, {0x4D, 0x00}, {0x4E, 0x1A}, {0x4F, 0x14}, {0x50, 0x00}, //25 {0x51, 0x10}, {0x52, 0x00}, {0x5C, 0x02}, {0x5D, 0x00}, {0x5E, 0x10}, {0x5F, 0x3F}, {0x60, 0x27}, {0x61, 0x28}, {0x62, 0x00}, {0x63, 0x03}, {0x64, 0xF7}, {0x65, 0x03}, {0x66, 0xD9}, {0x67, 0x03}, {0x68, 0x01}, {0x69, 0xC8}, {0x6A, 0x40}, {0x6D, 0x04}, {0x6E, 0x00}, {0x6F, 0x00}, {0x70, 0x80}, {0x71, 0x00}, {0x72, 0x00}, {0x73, 0x00}, {0x74, 0xF0}, //50 {0x75, 0x00}, {0x80, 0x42}, {0x81, 0x44}, {0x82, 0x04}, {0x83, 0x20}, {0x84, 0x20}, {0x85, 0x00}, {0x86, 0x10}, {0x87, 0x00}, {0x88, 0x05}, {0x89, 0x18}, {0x8A, 0x10}, {0x8B, 0x01}, {0x8C, 0x37}, {0x8D, 0x00}, {0x8E, 0xF0}, {0x8F, 0x81}, {0x90, 0x06}, {0x91, 0x06}, {0x92, 0x1E}, {0x93, 0x0D}, {0x94, 0x0A}, {0x95, 0x0A}, {0x96, 0x0C}, {0x97, 0x05}, //75 {0x98, 0x0A}, {0x99, 0x41}, {0x9A, 0x14}, {0x9B, 0x0A}, {0x9C, 0x3F}, {0x9D, 0x33}, {0x9E, 0xAE}, {0x9F, 0xF9}, {0xA0, 0x48}, {0xA1, 0x13}, {0xA2, 0x10}, {0xA3, 0x08}, {0xA4, 0x30}, {0xA5, 0x19}, {0xA6, 0x10}, {0xA7, 0x08}, {0xA8, 0x24}, {0xA9, 0x04}, {0xAA, 0x1E}, {0xAB, 0x1E}, {0xCC, 0x19}, {0xCD, 0x0B}, {0xCE, 0x13}, {0xCF, 0x64}, {0xD0, 0x21}, //100 {0xD1, 0x0F}, {0xD2, 0x88}, {0xE0, 0x01}, {0xE1, 0x04}, {0xE2, 0x41}, {0xE3, 0xD6}, {0xE4, 0x00}, {0xE5, 0x0C}, {0xE6, 0x0A}, {0xE7, 0x00}, {0xE8, 0x00}, {0xE9, 0x00}, {0xEE, 0x07}, {0xEF, 0x01}, {0x00, 0x1E}, {0x01, 0x1E}, {0x02, 0x0F}, {0x03, 0x10}, {0x04, 0x02}, {0x05, 0x00}, {0x06, 0xB0}, {0x07, 0x04}, {0x08, 0x0D}, {0x09, 0x0E}, {0x0A, 0x9C}, //125 {0x0B, 0x04}, {0x0C, 0x05}, {0x0D, 0x0F}, {0x0E, 0x02}, {0x0F, 0x12}, {0x10, 0x02}, {0x11, 0x02}, {0x12, 0x00}, {0x13, 0x01}, {0x14, 0x05}, {0x15, 0x07}, {0x16, 0x05}, {0x17, 0x07}, {0x18, 0x01}, {0x19, 0x04}, {0x1A, 0x05}, {0x1B, 0x0C}, {0x1C, 0x2A}, {0x1D, 0x01}, {0x1E, 0x00}, {0x21, 0x00}, {0x22, 0x00}, {0x23, 0x00}, {0x25, 0x01}, {0x26, 0x00}, //150 {0x27, 0x39}, {0x28, 0x7F}, {0x29, 0x08}, {0x30, 0x03}, {0x31, 0x00}, {0x32, 0x1A}, {0x33, 0x1A}, {0x34, 0x07}, {0x35, 0x07}, {0x36, 0x01}, {0x37, 0xFF}, {0x38, 0x36}, {0x39, 0x07}, {0x3A, 0x00}, {0x3E, 0xFF}, {0x3F, 0x00}, {0x40, 0x77}, {0x41, 0x40}, {0x42, 0x00}, {0x43, 0x30}, {0x44, 0xA0}, {0x45, 0x5C}, {0x46, 0x00}, {0x47, 0x00}, {0x48, 0x58}, //175 {0x4A, 0x1E}, {0x4B, 0x1E}, {0x4C, 0x00}, {0x4D, 0x00}, {0x4E, 0xA0}, {0x4F, 0x80}, {0x50, 0x00}, {0x51, 0x00}, {0x52, 0x00}, {0x53, 0x00}, {0x54, 0x00}, {0x57, 0x80}, {0x59, 0x10}, {0x5A, 0x08}, {0x5B, 0x94}, {0x5C, 0xE8}, {0x5D, 0x08}, {0x5E, 0x3D}, {0x5F, 0x99}, {0x60, 0x45}, {0x61, 0x40}, {0x63, 0x2D}, {0x64, 0x02}, {0x65, 0x96}, {0x66, 0x00}, //200 {0x67, 0x97}, {0x68, 0x01}, {0x69, 0xCD}, {0x6A, 0x01}, {0x6B, 0xB0}, {0x6C, 0x04}, {0x6D, 0x2C}, {0x6E, 0x01}, {0x6F, 0x32}, {0x71, 0x00}, {0x72, 0x01}, {0x73, 0x35}, {0x74, 0x00}, {0x75, 0x33}, {0x76, 0x31}, {0x77, 0x01}, {0x7C, 0x84}, {0x7D, 0x03}, {0x7E, 0x01}, //219 }; //PAJ7620U2初始化,返回0则初始化失败 uint8_t paj7620_init(void) { uint8_t i,State,n; State = 0; //写入BANK0区域前, 先向 0xEF 地址写0 while(HAL_OK != HAL_I2C_Mem_Write(&hi2c1, PAJ7620_I2C_ADDRESS, PAJ_BANK_SELECT, I2C_MEMADD_SIZE_8BIT, &State, 1, 500)) { HAL_Delay(5); //printf("paj7620_init err: 1 \r\n"); msg="paj7620_init err: 1 \r\n"; HAL_UART_Transmit(&huart3, (uint8_t*)msg, strlen( msg),100); } for(i=0; i<219; i++) { //循环向BANK1中写入配置字节,共219字节 while(HAL_OK != HAL_I2C_Mem_Write(&hi2c1, PAJ7620_I2C_ADDRESS, Paj7620Init_Reg_Array[i][0], I2C_MEMADD_SIZE_8BIT, &Paj7620Init_Reg_Array[i][1], 1, 500)) { HAL_Delay(5); //printf("paj7620_init err: 2 \r\n"); msg="paj7620_init err: 2 \r\n"; HAL_UART_Transmit(&huart3, (uint8_t*)msg, strlen( msg),100); } HAL_Delay(5); } //读取BANK0区域前, 先向 0xEF 地址写0 while(HAL_OK != HAL_I2C_Mem_Write(&hi2c1, PAJ7620_I2C_ADDRESS, PAJ_BANK_SELECT, I2C_MEMADD_SIZE_8BIT,&State,1,500)) { HAL_Delay(5); //printf("paj7620_init err: 3 \r\n"); msg="paj7620_init err: 3 \r\n"; HAL_UART_Transmit(&huart3, (uint8_t*)msg, strlen( msg),100); } HAL_Delay(5); //读取地址0x32内存的值,存入n中 while(HAL_OK != HAL_I2C_Mem_Read(&hi2c1, PAJ7620_I2C_ADDRESS, 0x32, I2C_MEMADD_SIZE_8BIT, &n, 1, 500)) { HAL_Delay(5); //printf("paj7620_init err: 4 \r\n"); msg="paj7620_init err: 4 \r\n"; HAL_UART_Transmit(&huart3, (uint8_t*)msg, strlen( msg),100); } if(n != 0x29) { return 0; } return 1; }
第二个函数,手势识别 getGesture():
//检测手势并输出 uint16_t getGesture(void) { uint8_t Data[2]={0,0}; uint16_t Gesture_Data; HAL_I2C_Mem_Read(&hi2c1, PAJ7620_I2C_ADDRESS, PAJ_INT_FLAG1, I2C_MEMADD_SIZE_8BIT, &Data[0], 1, 500); HAL_Delay(5); HAL_I2C_Mem_Read(&hi2c1, PAJ7620_I2C_ADDRESS, PAJ_INT_FLAG2, I2C_MEMADD_SIZE_8BIT, &Data[1], 1, 500); HAL_Delay(5); Gesture_Data = (Data[1]<<8) | Data[0]; //printf("\n %x \r\n", Gesture_Data); return Gesture_Data; }
第三个函数,来应用手势识别结果搞事情。我这里是打印识别结果到串口3输出,函数为 usart_print_gesture():
void usart_print_gesture(uint16_t status) //通过串口打印手势信息 { if(status == PAJ_ZERO) //未检测到任何手势 { return; } switch(status) { case PAJ_UP: msg="Up\r\n"; break; case PAJ_DOWN: msg="Down\r\n"; break; case PAJ_LEFT: msg="Left\r\n"; break; case PAJ_RIGHT: msg="Right\r\n"; break; case PAJ_FORWARD: msg="Forward\r\n"; break; case PAJ_BACKWARD: msg="Backward\r\n"; break; case PAJ_CLOCKWISE: msg="Clockwise\r\n"; break; case PAJ_COUNT_CLOCKWISE: msg="AntiClockwise\r\n"; break; case PAJ_WAVE: msg="Wave\r\n"; break; default: msg="Error Gesture value ~~~\r\n"; } //通过调试串口,打印消息msg HAL_UART_Transmit(&huart3, (uint8_t*)msg, strlen( msg),100); }
3. 主函数中调用手势识别功能
在进入while循环之前,先调用paj7620的初始化函数
如上图,根据初始化结果,打印出 error 或者 OK信息。
然后在while循环里,调用手势检测函数,并打印结果到串口:
四 测试结果
代码编译下载,看下测试结果(b站视频)。 手势检测还是非常灵敏的!
https://www.bilibili.com/video/BV1SH4y1F7Cf?p=2
附完整工程源代码:
五. 视频讲解:
https://www.bilibili.com/video/BV1SH4y1F7Cf?p=3