项目介绍
使用N947开发板驱动TMF8821实现手势识别功能,可识别挥动,接近/远离等手部动作,并使用手势动作控制屏幕上的菜单功能。
硬件介绍
MCXN947 MINI
MCX N94x采用两个高性能Arm®Cortex®-M33内核,运行频率高达150MHz,提供2MB闪存以及可配置的带完整ECC的RAM、DSP协处理器、并集成了eIQ Neutron NPU。与单独的CPU内核相比,NPU可提供高达42倍的机器学习(ML)吞吐量提升,从而能够减少系统唤醒的时间,并降低整体功耗。
多核设计通过智能、高效地将工作负载分配到模拟和数字外设,提高了系统性能并降低了功耗。这些器件配备了MCUXpresso Developer Experience(MCUXpresso开发人员体验)支持,可优化、简化和加速嵌入式系统的开发工作。
MCX N94x系列具有更广泛的模拟和电机控制外设,而MCX N54x系列集成了众多外设,包括带PHY的高速USB、安全数字化SD卡和智能卡接口等。
TMF8821
dToF模块是基于 TMF8821 设计的直接飞行时间 (dToF) 传感器模块,TMF8821采用单个模块化封装,带有相关的 VCSEL(垂直腔面发射激光器)。dToF 设备基于 SPAD、TDC 和直方图技术,可实现 5000 mm 的检测范围。由于它的镜头位于 SPAD 上,它支持 3x3、4x4 和 3x6 多区域输出数据以及宽广的、动态可调的视野。VCSEL 上方的封装内的多透镜阵列 (MLA) 拓宽了 FoI(照明场)。原始数据的所有处理都在片上进行,TMF8821在其 I2C 接口上提供距离信息和置信度值。
方案框图
项目设计思路
TMF8821是一颗可以支持区域测量的dToF芯片,可以输出3X3的距离测量矩阵,同时还有一套置信度矩阵数据。
手势特点
这次实现的手势识别功能一共支持4种手势,向上挥手、向下挥手,手掌接近,手掌远离,分析这几种手势的特点。
向上挥手:根据手掌与传感器的距离涉及多种情况。这里我以较近的时候为主,手掌从传感器接受范围外移动到传感器接受范围外。下方的距离检测优先检测到数据,之后是中间,最后是上面。
向下挥手:根据手掌与传感器的距离涉及多种情况。这里我以较近的时候为主,手掌从传感器接受范围外移动到传感器接受范围外。上方的距离检测优先检测到数据,之后是中间,最后是下面。
手掌接近:根据手掌与传感器的距离涉及多种情况。这里我以较近的时候为主,手掌从传感器接受范围外移动到传感器接受范围内,接近传感器,最后移动到传感器接受范围外。
实测现象
根据以上特点,经过实测后发现,当传感器上方无遮挡时,也就是超出最大量程的时候,数据置信度均为0,为减少感应范围,将TMF8821配置为近距离测量模式。
当手从上方或下方挥动是,先经过的测量位置部分数据置信度会先达到255,同时得到准确测量距离,可以根据此特点判断挥手方向。
当手接近或远离时,测量到的数据置信度均为255,同时测量到的距离会变化。
功能实现
根据实测完成功能逻辑开发,当输出数据置信度全0时为空模式,当检测到有上方或下方某一域的置信度先变为255时,得到手挥动方向。当置信度全为255时,记录最开始测量到的距离和最后测量到的距离。当重新变为空模式后,判定两次测量到的距离差是否大于阈值,并根据正负得到手接近或远离手势,当不为接近或远离时,根据手挥动方向,得到向上挥手、向下挥手两种手势结果,并根据得到手势操作方式控制菜单界面。
软件流程图
关键代码
TMF8821初始化
void tmf8821_init()
{
tmf8821_iic_write_byte(0xe0,0x01);
SDK_DelayAtLeastUs(100 * 1000, SystemCoreClock);
while(tmf8821_iic_read_byte(0xe0) != 0x41)
{
SDK_DelayAtLeastUs(1 * 1000, SystemCoreClock);
}
uint8_t appid = tmf8821_iic_read_byte(0x00);
if(appid == 0x80)
{
tmf8821_iic_write(0x08, cmd_download_init, 4);
cmd_status_read_data[2] = 0;
while(cmd_status_read_data[0] != 0x00 || cmd_status_read_data[1] != 0x00 || cmd_status_read_data[2] != 0xFF)
{
SDK_DelayAtLeastUs(1 * 1000, SystemCoreClock);
tmf8821_iic_read(0x08, cmd_status_read_data, 3);
}
tmf8821_iic_write(0x08, cmd_set_addr, 5);
cmd_status_read_data[2] = 0;
while(cmd_status_read_data[0] != 0x00 || cmd_status_read_data[1] != 0x00 || cmd_status_read_data[2] != 0xFF)
{
SDK_DelayAtLeastUs(1 * 1000, SystemCoreClock);
tmf8821_iic_read(0x08, cmd_status_read_data, 3);
}
uint32_t image_pointer = 0;
while(image_pointer < tmf882x_image_length)
{
uint16_t len = 128;
if((tmf882x_image_length - image_pointer) < 128)
{
len = tmf882x_image_length - image_pointer;
}
data_ram[0] = 0x41;
data_ram[1] = len;
for(uint16_t i=0; i<len; i++)
{
data_ram[2+i] = tmf882x_image[image_pointer + i];
}
data_ram[2+len] = calculate_checksum(data_ram, len+2);
tmf8821_iic_write(0x08, data_ram, 2+len+1);
cmd_status_read_data[2] = 0;
while(cmd_status_read_data[0] != 0x00 || cmd_status_read_data[1] != 0x00 || cmd_status_read_data[2] != 0xFF)
{
SDK_DelayAtLeastUs(1 * 1000, SystemCoreClock);
tmf8821_iic_read(0x08, cmd_status_read_data, 3);
}
image_pointer += len;
}
tmf8821_iic_write(0x08, cmd_ramremap_reset, 3);
SDK_DelayAtLeastUs(3 * 1000, SystemCoreClock);
appid = tmf8821_iic_read_byte(0x00);
}
if(appid == 0x03)
{
tmf8821_iic_write_byte(0x08,0x16);
SDK_DelayAtLeastUs(1 * 1000, SystemCoreClock);
while(tmf8821_iic_read_byte(0x08) != 0x00)
{
SDK_DelayAtLeastUs(1 * 1000, SystemCoreClock);
}
tmf8821_iic_read(0x20, data_ram, 3);
data_ram[0] = 1;
data_ram[1] = 0x00;
tmf8821_iic_write(0x24, data_ram, 2);
tmf8821_iic_write_byte(0x34,6);
tmf8821_iic_write_byte(0x31,0x03);
tmf8821_iic_write_byte(0x08,0x15);
while(tmf8821_iic_read_byte(0x08) != 0x00)
{
SDK_DelayAtLeastUs(1 * 1000, SystemCoreClock);
}
tmf8821_iic_write_byte(0xe2,0x02);
tmf8821_iic_write_byte(0xe1,0xff);
tmf8821_iic_write_byte(0x08,0x6E);
tmf8821_iic_write_byte(0x08,0x10);
while(tmf8821_iic_read_byte(0x08) != 0x01)
{
SDK_DelayAtLeastUs(1 * 1000, SystemCoreClock);
}
}
}
TMF8821读取
void tmf8821_read_distance(uint8_t *confidence, uint16_t* distance)
{
while(GPIO_PinRead(BOARD_INITPINS_TMF_INT_GPIO, BOARD_INITPINS_TMF_INT_GPIO_PIN) != 0)
{}
uint8_t int_status = tmf8821_iic_read_byte(0xe1);
tmf8821_iic_write_byte(0xe1, int_status);
tmf8821_iic_read(0x38, data_ram, 3*18);
for(int i=0; i<18; i++)
{
confidence[i] = data_ram[3*i];
distance[i] = (data_ram[3*i + 2] << 8) + data_ram[3*i + 1];
}
}
手势识别部分
void gesture_recognition()
{
tmf8821_read_distance(confidence_original, distance_original);
for(int i=0;i<3;i++)
{
for(int j=0;j<3;j++)
{
confidence[i*3+j] = confidence_original[j*3+i];
distance[i*3+j] = distance_original[j*3+i];
}
}
if((confidence[0] == 255 || confidence[1] == 255 || confidence[2] == 255)
&& (confidence[6] != 255 && confidence[7] != 255 && confidence[8] != 255))
{
if(brandish_flag == 0)
brandish_flag = 1;
}
if((confidence[0] != 255 && confidence[1] != 255 && confidence[2] != 255)
&& (confidence[6] == 255 || confidence[7] == 255 || confidence[8] == 255))
{
if(brandish_flag == 0)
brandish_flag = 2;
}
if(confidence[0] == 255 && confidence[1] == 255 && confidence[2] == 255
&& confidence[3] == 255 && confidence[4] == 255 && confidence[5] == 255
&& confidence[6] == 255 && confidence[7] == 255 && confidence[8] == 255)
{
if(pressure1 == 0)
{
pressure1 = distance[4];
pressure2 = pressure1;
}
else
{
pressure2 = distance[4];
}
}
if(confidence[0] == 0 && confidence[1] == 0 && confidence[2] == 0
&& confidence[3] == 0 && confidence[4] == 0 && confidence[5] == 0
&& confidence[6] == 0 && confidence[7] == 0 && confidence[8] == 0)
{
if(((int32_t)pressure1 - (int32_t)pressure2) > 30)
{
mode = 3;
}
else if(((int32_t)pressure1 - (int32_t)pressure2) < -30)
{
mode = 4;
}
else if(brandish_flag != 0)
{
if(brandish_flag == 1)
{
mode = 1;
}
else
{
mode = 2;
}
}
brandish_flag = 0;
pressure1 = 0;
pressure2 = 0;
}
}
主代码
int main(void)
{
BOARD_InitHardware();
LCD_init();
GPIO_PinWrite(BOARD_INITPINS_TMF_EN_GPIO, BOARD_INITPINS_TMF_EN_GPIO_PIN, 1);
SDK_DelayAtLeastUs(100 * 1000, SystemCoreClock);
tmf8821_init();
table[func_index].current_operation();
LCD_update();
while(1)
{
gesture_recognition();
if(mode != 0)
{
LCD_Clear(0);
if(mode == 1) func_index=table[func_index].down;
else if(mode == 2) func_index=table[func_index].up;
else if(mode == 3) func_index=table[func_index].enter;
else if(mode == 4) func_index=table[func_index].back;
table[func_index].current_operation();
LCD_update();
mode = 0;
}
}
}
功能展示及说明
一级页面
LED的二级界面
LED1的三级界面
项目中遇到的难题和解决方法
问题:配置TMF8821 GPIO寄存器的代码无法生效
方法:最后加入tmf8821_iic_write_byte(0x08,0x10)后正常
对本次竞赛的心得体会
活动挺好的,希望加大力度。