零知IDE——基于零知标准板驱动PAJ7620U2手势控制L9110风扇模块和SG90舵机系统

✔零知开源(零知IDE)是一个专为电子初学者/电子兴趣爱好者设计的开源软硬件平台,在硬件上提供超高性价比STM32系列开发板、物联网控制板。取消了Bootloader程序烧录,让开发重心从 "配置环境" 转移到 "创意实现",极大降低了技术门槛。零知IDE编程软件,内置上千个覆盖多场景的示例代码,支持项目源码一键下载,项目文章在线浏览。零知开源(零知IDE)平台通过软硬件协同创新,让你的创意快速转化为实物,来动手试试吧!

✔访问零知实验室,获取更多实战项目和教程资源吧!

www.lingzhilab.com

目录

一、系统接线部分

[1.1 硬件清单](#1.1 硬件清单)

[1.2 接线方案表](#1.2 接线方案表)

[1.3 具体接线图](#1.3 具体接线图)

[1.4 接线实物图](#1.4 接线实物图)

二、安装与使用部分

三、代码讲解部分

[3.1 软件I2C配置](#3.1 软件I2C配置)

[3.2 平滑移动算法](#3.2 平滑移动算法)

[3.3 风扇控制算法](#3.3 风扇控制算法)

[3.4 PWM定时器手动配置](#3.4 PWM定时器手动配置)

[3.5 完整代码](#3.5 完整代码)

四、操作流程

[4.1 系统操作](#4.1 系统操作)

[4.2 手势操作演示](#4.2 手势操作演示)

[4.3 视频演示](#4.3 视频演示)

[五、PAJ7620U2 手势传感器技术讲解](#五、PAJ7620U2 手势传感器技术讲解)

[5.1 I2C 通信](#5.1 I2C 通信)

[5.2 寄存器机制](#5.2 寄存器机制)

六、常见问题解答 (FAQ)

[Q1: 串口提示 "PAJ7620初始化失败" 怎么办?](#Q1: 串口提示 "PAJ7620初始化失败" 怎么办?)

[Q2: 风扇转速很慢或不转?](#Q2: 风扇转速很慢或不转?)


项目概述

本项目使用零知标准板 (主控芯片:STM32F103RBT6)作为核心控制器,结合PAJ7620U2 手势传感器实现对L9110 风扇模块和SG90舵机的智能控制。系统通过识别9种不同的手势动作(上下、左右、顺时针/逆时针、挥手、前推、后拉)分别控制风扇的启停、正反转、调速以及舵机的精确角度定位,实现了无接触式智能交互体验

项目难点及解决方案

问题描述:零知标准板的analogWrite()函数导致系统卡死

解决方案 :放弃analogWrite()函数,手动配置STM32硬件定时器,直接操作定时器寄存器

一、系统接线部分

1.1 硬件清单

名称 型号/参数 数量 说明
主控板 零知标准板 (STM32F103RBT6) 1 核心控制器
扩展板 零知标准板-扩展板 1 传感器扩展板
手势传感器 PAJ7620U2 1 I2C接口,识别9种手势
风扇驱动模块 L9110 / L9110S 1 双路H桥,控制电机正反转
舵机 SG90 (180度) 1 控制风向摆动
杜邦线 公对母/公对公 若干 连接线

1.2 接线方案表

注意:请严格按照以下代码定义的引脚进行连接,否则程序无法正常工作。

模块 引脚名称 连接到零知标准板 (STM32) 功能说明
PAJ7620U2 VIN 3.3V 通常是3.3V逻辑电平
PAJ7620U2 GND GND 地线
PAJ7620U2 SCL SCL (或对应I2C SCL) I2C 时钟线
PAJ7620U2 SDA SDA (或对应I2C SDA) I2C 数据线
SG90 舵机 信号线 (橙) 12 PWM控制信号
SG90 舵机 VCC (红) 3.3V(直插拓展板) 电源正
SG90 舵机 GND (棕) GND 电源地
L9110 风扇 INA 9(PB7) 电机控制脚A
L9110 风扇 INB 5(PB6) 电机控制脚B
L9110 风扇 VCC 5V (建议外接) 电源正
L9110 风扇 GND GND 电源地

PS:本项目采用扩展板直插零知标准板,请注意I2C接口线序,与开发板定义的内容不一致,需要将外接的带锁扣端子转杜邦线调整为VIN、GND、SCL和SDA;舵机直插D12 PWM接口,舵机黄色信号线靠近''D12''丝印一端

1.3 具体接线图

**请注意:**如果风扇使用外部电源,务必将外部电源的负极(-)连接到零知标准板的 GND,否则控制信号无法形成回路

1.4 接线实物图

二、安装与使用部分

2.1 开源平台-输入"PAJ7620U2" 并搜索-代码下载自动打开

2.2 连接-验证-上传

2.3 调试-串口监视器

三、代码讲解部分

本项目的代码结构清晰,采用了模块化设计,代码从初始化、手势处理逻辑和硬件控制部分展开

3.1 软件I2C配置

cpp 复制代码
//1. I2C写寄存器
uint8_t RevEng_PAJ7620::writeRegister(uint8_t i2cAddress, uint8_t dataByte) {
  wireHandle->beginTransmission(PAJ7620_I2C_BUS_ADDR);  // 0x73
  wireHandle->write(i2cAddress);   // 寄存器地址
  wireHandle->write(dataByte);     // 数据
  return wireHandle->endTransmission();
}

//2. I2C读寄存器
uint8_t RevEng_PAJ7620::readRegister(uint8_t i2cAddress, uint8_t byteCount, uint8_t data[]) {
  wireHandle->beginTransmission(PAJ7620_I2C_BUS_ADDR);
  wireHandle->write(i2cAddress);
  uint8_t result = wireHandle->endTransmission();
  if (result) return result;  // 通信错误
  
  wireHandle->requestFrom((int)PAJ7620_I2C_BUS_ADDR, (int)byteCount);
  while (wireHandle->available()) {
    *data = wireHandle->read();
    data++;
  }
  return 0;
}

软件I2C通过GPIO模拟I2C时序,虽然速度略慢,但稳定性更高

3.2 平滑移动算法

cpp 复制代码
// 平滑移动舵机(防止舵机快速转动时抖动或损坏)
void moveServoSmoothly() {
  int step = 2;  // 默认每次移动2度

  // 根据目标位置调整移动步长
  // 如果是移动到0度或180度(左右手势),使用较大步长实现快速响应
  if (targetServoPos == 0 || targetServoPos == 180) {
    step = 5;  // 大步长,快速移动
  }

  // 根据当前位置和目标位置的关系,逐步移动
  if (currentServoPos < targetServoPos) {
    // 当前位置小于目标位置,向右转
    currentServoPos = min(currentServoPos + step, targetServoPos);
  } 
  else if (currentServoPos > targetServoPos) {
    // 当前位置大于目标位置,向左转
    currentServoPos = max(currentServoPos - step, targetServoPos);
  }

  myServo.write(currentServoPos);  // 写入新位置
  delay(15);  // 给舵机一点时间响应
}

一种非阻塞式的控制思路,利用 loop() 的快速刷新特性实现了类似PID控制的缓启缓停效果

3.3 风扇控制算法

cpp 复制代码
void controlFan(int speed, int direction) {
  // 限制速度在有效范围 [0, 255]
  speed = constrain(speed, 0, FAN_MAX_SPEED);
  
  if (direction == 1) {
    // 反转: IA=0, IB=PWM
    setPWM(FAN_IA_PIN, 0);
    setPWM(FAN_IB_PIN, speed);
    fanDirection = 1;
  } 
  else if (direction == -1) {
    // 正转: IA=PWM, IB=0
    setPWM(FAN_IA_PIN, speed);
    setPWM(FAN_IB_PIN, 0);
    fanDirection = -1;
  } 
  else {
    // 停止: IA=0, IB=0
    setPWM(FAN_IA_PIN, 0);
    setPWM(FAN_IB_PIN, 0);
    fanDirection = 0;
  }
  
  fanSpeed = speed;
}

H桥驱动原理

L9110内部包含一个H桥电路,通过控制4个开关管实现电机正反转

IA=HIGH, IB=LOW → 正转 IA=LOW, IB=HIGH → 反转

IA=LOW, IB=LOW → 停止 IA=HIGH, IB=HIGH → 刹车(不常用)

3.4 PWM定时器手动配置

定时器工作原理

PWM频率 = 时钟频率 / (预分频系数 × 重装载值)

= 72MHz / (1 × 65535) ≈ 1098Hz

占空比 = 比较值 / 重装载值 × 100%

cpp 复制代码
// ============ PWM初始化 ============
void initPWMTimer() {
  Serial.println("[PWM] 初始化定时器...");
  
  // 配置引脚为PWM模式
  pinMode(FAN_IA_PIN, PWM);  // 引脚9 (PB7)
  pinMode(FAN_IB_PIN, PWM);  // 引脚5 (PB6)
  
  // 暂停定时器进行配置
  Timer4.pause();
  
  // 设置PWM参数
  Timer4.setPrescaleFactor(1);     // 预分频=1(不分频)
  Timer4.setOverflow(65535);       // ARR=65535(16位最大)
  
  // 初始化比较值(占空比0%)
  Timer4.setCompare(TIMER_CH1, 0);  // CCR1=0 (引脚5)
  Timer4.setCompare(TIMER_CH2, 0);  // CCR2=0 (引脚9)
  
  // 刷新寄存器并启动定时器
  Timer4.refresh();
  Timer4.resume();
  
  Serial.println("[PWM] 定时器初始化完成");
}

// ============ PWM占空比设置 ============
void setPWM(int pin, uint8_t dutyCycle) {
  // 将0-255映射到0-65535
  uint16_t compareValue = (uint32_t)dutyCycle * 65535 / 255;
  
  if (pin == FAN_IA_PIN) {
    Timer4.setCompare(TIMER_CH2, compareValue);  // 引脚9
  } else if (pin == FAN_IB_PIN) {
    Timer4.setCompare(TIMER_CH1, compareValue);  // 引脚5
  }
}

参数说明

参数 含义 取值范围 本项目设置
预分频系数 时钟分频倍数 1-65536 1(不分频)
重装载值(ARR) 计数器最大值 1-65535 65535(最大分辨率)
比较值(CCR) 高电平持续计数 0-ARR 0-65535
占空比 CCR/ARR 0%-100% 用户输入0-255映射

3.5 完整代码

cpp 复制代码
/**************************************************************************************
 * 文件: /Gesture_Control_Servo_Fan/Gesture_Control_Servo_Fan.ino
 * 作者:零知实验室(深圳市在芯间科技有限公司)
 * -^^- 零知实验室,让电子制作变得更简单! -^^-
 * 时间: 2025-12-30
 * 说明:零知标准板(STM32F103RBT6) + PAJ7620U2 + L9110 手势控制系统
 * 功能:手势控制舵机(12号引脚)和风扇(5,9号引脚)
 *       向上-风扇正转,向下-舵机90°,向左-舵机0°,向右-舵机180°
 *       顺时针-风扇正转,逆时针-风扇反转,挥手-风扇停止
 *       向前-风扇加速,向后-风扇减速
 ***************************************************************************************/

#include <Wire.h>
#include <Servo.h>
#include "RevEng_PAJ7620.h"

// 对象创建
RevEng_PAJ7620 sensor;
Servo myServo;

// 引脚定义
const int SERVO_PIN = 12;

// 风扇引脚 - PB6(引脚5)和PB7(引脚9)对应Timer4
const int FAN_IB_PIN = 5;     // PB6 - TIM4_CH1
const int FAN_IA_PIN = 9;     // PB7 - TIM4_CH2

// 系统参数
#define FAN_MIN_SPEED 80      // 风扇最低启动速度
#define FAN_MAX_SPEED 255      // 风扇最大速度
#define SPEED_STEP 175          // 每次调速的步长

// 状态变量
int currentServoPos = 90;      // 舵机当前位置(角度)
int targetServoPos = 90;       // 舵机目标位置(角度)
int fanSpeed = 0;              // 当前风扇速度(0-255)
int fanDirection = 0;          // 风扇方向:0=停止,1=正转,-1=反转

// 手势检测冷却时间,防止重复触发
unsigned long lastGestureTime = 0;
const unsigned long GESTURE_COOLDOWN = 500;  // 毫秒

bool systemReady = false;      // 系统是否就绪

// 系统状态标志
// ==================== 初始化函数 ====================
void setup() {
  // 第1步:初始化串口通信
  initSerial();
  
  // 第2步:初始化I2C总线(PAJ7620传感器需要)
  initI2C();
  
  // 第3步:初始化舵机
  initServo();
  
  // 第4步:初始化风扇控制引脚
  initFan();
  
  // 第5步:初始化手势传感器
  initGestureSensor();
  
  // 第6步:显示功能说明
  printFunctionMenu();
  
  // 第7步:系统就绪提示
  systemStartupComplete();
  
  systemReady = true;  // 标记系统已就绪
}

// ==================== 主循环函数 ====================
void loop() {
  unsigned long currentTime = millis();

  // 检测手势(带冷却时间,避免同一个手势重复触发)
  if (currentTime - lastGestureTime > GESTURE_COOLDOWN) {
    Gesture gesture = sensor.readGesture();  // 读取当前手势

    // 如果检测到有效手势,则处理
    if (gesture != GES_NONE) {
      lastGestureTime = currentTime;  // 更新最后手势时间
      handleGesture(gesture);         // 调用手势处理函数
    }
  }

  // 平滑移动舵机到目标位置(每次循环移动一小步)
  if (currentServoPos != targetServoPos) {
    moveServoSmoothly();
  }

  delay(50);  // 主循环延迟,不要太短以免CPU负担过重
}

// ==================== PWM相关函数 ====================

// 设置PWM占空比
void setPWM(int pin, uint8_t dutyCycle) {
  // 计算比较值:dutyCycle / 255 * overflow
  uint16_t compareValue = (uint32_t)dutyCycle * 65535 / 255;
  
  if (pin == FAN_IA_PIN) {
    // 引脚9 (PB7) 使用 Timer4 Channel2
    Timer4.setCompare(TIMER_CH2, compareValue);
  } 
  else if (pin == FAN_IB_PIN) {
    // 引脚5 (PB6) 使用 Timer4 Channel1
    Timer4.setCompare(TIMER_CH1, compareValue);
  }
}

// 初始化PWM定时器
void initPWMTimer() {
  Serial.println("[PWM] 初始化定时器...");
  
  // 配置引脚为PWM模式
  pinMode(FAN_IA_PIN, PWM);  // 引脚9 (PB7)
  pinMode(FAN_IB_PIN, PWM);  // 引脚5 (PB6)
  
  // 暂停定时器4进行配置
  Timer4.pause();
  
  // 设置PWM参数
  // 72MHz / 1 / 65535 ≈ 1098Hz
  Timer4.setPrescaleFactor(1);     // 不分频
  Timer4.setOverflow(65535);       // 16位最大分辨率
  
  // 初始化占空比为0(风扇停止)
  Timer4.setCompare(TIMER_CH1, 0);  // 引脚5 (FAN_IB_PIN)
  Timer4.setCompare(TIMER_CH2, 0);  // 引脚9 (FAN_IA_PIN)
  
  // 刷新并启动定时器
  Timer4.refresh();
  Timer4.resume();
  
  Serial.println("[PWM] 定时器初始化完成 (引脚5=PB6/CH1, 引脚9=PB7/CH2)");
}

// ==================== 初始化函数详细实现 ====================

// 初始化串口通信
void initSerial() {
  Serial.begin(115200);
  delay(300);  // 等待串口稳定
  
  Serial.println("\n╔═══════════════════╗");
  Serial.println("║    零知实验室 - 手势控制系统 V2.0    ║");
  Serial.println("╚═══════════════════╝\n");
  Serial.println("【系统初始化开始】\n");
}

// 初始化I2C总线
void initI2C() {
  Serial.print("[1/5] I2C总线初始化...");
  Wire.begin();
  delay(100);
  Serial.println(" ✓");
}

// 初始化舵机
void initServo() {
  Serial.print("[2/5] 舵机初始化...");
  
  myServo.attach(SERVO_PIN);
  myServo.write(currentServoPos);  // 设置初始位置90度
  delay(500);  // 等待舵机转到初始位置
  
  Serial.print(" ✓ (初始位置: ");
  Serial.print(currentServoPos);
  Serial.println("°)");
}

// 初始化风扇控制引脚
void initFan() {
  Serial.print("[3/5] 风扇模块初始化...");
  
  // 先初始化PWM定时器
  initPWMTimer();

  pinMode(LED_BUILTIN, OUTPUT);
  
  // 确保风扇初始状态为停止
  controlFan(0, 0);
  
  delay(200);
  Serial.println(" ✓ (状态: 停止)");
}

// 初始化PAJ7620手势传感器
void initGestureSensor() {
  Serial.println("[4/5] PAJ7620手势传感器初始化...");
  
  bool sensorInitialized = false;
  
  // 尝试5次初始化
  for (int attempt = 1; attempt <= 5; attempt++) {
    Serial.print("      尝试 ");
    Serial.print(attempt);
    Serial.print("/5...");
    
    if (sensor.begin()) {
      sensorInitialized = true;
      Serial.println(" ✓ 成功!");
      break;
    }
    
    Serial.println(" ✗ 失败");
    delay(500);
  }

  // 如果初始化失败,进入错误处理
  if (!sensorInitialized) {
    handleSensorInitError();
  }
}

// 传感器初始化失败处理
void handleSensorInitError() {
  Serial.println("\n╔════════════════════╗");
  Serial.println("║         ❌ PAJ7620初始化失败!         ║");
  Serial.println("╚════════════════════╝");
  Serial.println("\n【故障排查清单】");
  Serial.println("  □ 1. 传感器VCC是否接3.3V(不能接5V!)");
  Serial.println("  □ 2. GND是否正确接地");
  Serial.println("  □ 3. SDA和SCL引脚是否正确连接");
  Serial.println("  □ 4. 杜邦线接触是否良好");
  Serial.println("  □ 5. 传感器与开发板距离不要太远");
  Serial.println("  □ 6. 检查传感器是否损坏(闻是否有烧焦味)");
  Serial.println("\n系统已停止运行,请修复后重新上电。\n");
  
  // LED快速闪烁表示错误状态
  while (1) {
    digitalWrite(LED_BUILTIN, HIGH);
    delay(50);
    digitalWrite(LED_BUILTIN, LOW);
    delay(50);
  }
}

// 显示功能菜单
void printFunctionMenu() {
  Serial.println("[5/5] 系统配置加载... ✓\n");
  
  Serial.println("\n========================================");
  Serial.println("           手势功能说明");
  Serial.println("========================================");
  Serial.println("  向上   ↑  : 风扇正转启动");
  Serial.println("  向下   ↓  : 系统复位(舵机90°)");
  Serial.println("  向左   ←  : 舵机转到0°");
  Serial.println("  向右   →  : 舵机转到180°");
  Serial.println("  顺时针 ↻  : 风扇正转");
  Serial.println("  逆时针 ↺  : 风扇反转");
  Serial.println("  挥手   ✋ : 风扇停止");
  Serial.println("  向前   ⇨  : 风扇加速");
  Serial.println("  向后   ⇦  : 风扇减速");
  Serial.println("========================================\n");
}

// 系统启动完成提示
void systemStartupComplete() {
  Serial.println("【系统初始化完成】\n");
  
  // LED闪烁3次表示系统就绪
  for (int i = 0; i < 3; i++) {
    digitalWrite(LED_BUILTIN, HIGH);
    delay(150);
    digitalWrite(LED_BUILTIN, LOW);
    delay(150);
  }
  
  Serial.println("✓ 系统就绪,等待手势输入...\n");
  Serial.println("═══════════════════════════════════════════\n");
}

// ==================== 风扇控制函数 ====================
// 控制风扇的转速和方向
// speed: 速度值(0-255)
// direction: 方向(1=反转,-1=正转,0=停止)
void controlFan(int speed, int direction) {
  // 限制速度在有效范围内
  speed = constrain(speed, 0, FAN_MAX_SPEED);
  
  if (direction == 1) {
    // 反转:IA(引脚9)=0, IB(引脚5)=PWM
    setPWM(FAN_IA_PIN, 0);
    setPWM(FAN_IB_PIN, speed);
    fanDirection = 1;
  } 
  else if (direction == -1) {
    // 正转:IA(引脚9)=PWM, IB(引脚5)=0
    setPWM(FAN_IA_PIN, speed);
    setPWM(FAN_IB_PIN, 0);
    fanDirection = -1;
  } 
  else {
    // 停止:两个引脚都输出0
    setPWM(FAN_IA_PIN, 0);
    setPWM(FAN_IB_PIN, 0);
    fanDirection = 0;
  }
  
  fanSpeed = speed;
}

// 停止风扇
void stopFan() {
  controlFan(0, 0);
  Serial.println("→ 风扇: 已停止");
}

// 风扇正转
void fanForward() {
  // 如果当前是停止状态,使用最小速度启动
  if (fanDirection == 0) {
    fanSpeed = FAN_MIN_SPEED;
  }
  controlFan(fanSpeed, -1);
  Serial.print("→ 风扇: 正转 | 速度: ");
  Serial.println(fanSpeed);
}

// 风扇反转
void fanReverse() {
  // 如果当前是停止状态,使用最小速度启动
  if (fanDirection == 0) {
    fanSpeed = FAN_MIN_SPEED;
  }
  controlFan(fanSpeed, 1);
  Serial.print("→ 风扇: 反转 | 速度: ");
  Serial.println(fanSpeed);
}

// 风扇加速
void fanSpeedUp() {
  // 只有在风扇运行时才能加速
  if (fanDirection != 0) {
    // 增加速度,但不超过最大值
    fanSpeed = constrain(fanSpeed + SPEED_STEP, FAN_MIN_SPEED, FAN_MAX_SPEED);
    controlFan(fanSpeed, fanDirection);  // 应用新速度
    Serial.print("→ 风扇: 加速至 ");
    Serial.println(fanSpeed);
  } else {
    Serial.println("⚠ 提示: 请先启动风扇(向上或顺时针手势)");
  }
}

// 风扇减速
void fanSpeedDown() {
  // 只有在风扇运行时才能减速
  if (fanDirection != 0) {
    // 降低速度,但不低于最小值
    fanSpeed = constrain(fanSpeed - SPEED_STEP, FAN_MIN_SPEED, FAN_MAX_SPEED);
    controlFan(fanSpeed, fanDirection);  // 应用新速度
    Serial.print("→ 风扇: 减速至 ");
    Serial.println(fanSpeed);
  } else {
    Serial.println("⚠ 提示: 请先启动风扇(向上或顺时针手势)");
  }
}

// ==================== 舵机控制函数 ====================
// 平滑移动舵机(防止舵机快速转动时抖动或损坏)
void moveServoSmoothly() {
  int step = 2;  // 默认每次移动2度

  // 根据目标位置调整移动步长
  // 如果是移动到0度或180度(左右手势),使用较大步长实现快速响应
  if (targetServoPos == 0 || targetServoPos == 180) {
    step = 5;  // 大步长,快速移动
  }

  // 根据当前位置和目标位置的关系,逐步移动
  if (currentServoPos < targetServoPos) {
    // 当前位置小于目标位置,向右转
    currentServoPos = min(currentServoPos + step, targetServoPos);
  } 
  else if (currentServoPos > targetServoPos) {
    // 当前位置大于目标位置,向左转
    currentServoPos = max(currentServoPos - step, targetServoPos);
  }

  myServo.write(currentServoPos);  // 写入新位置
  delay(15);  // 给舵机一点时间响应
}

// ==================== 手势处理函数 ====================
void handleGesture(Gesture gesture) {
  Serial.print("✋ 检测到手势: ");

  // 根据不同的手势类型执行相应动作
  switch (gesture) {
    case GES_UP:
      // 向上手势:启动风扇正转
      Serial.println("向上 ↑");
      fanForward();
      break;

    case GES_DOWN:
      // 向下手势:系统复位(舵机回中间,风扇停止)
      Serial.println("向下 ↓");
      Serial.println("→ 执行系统复位");
      targetServoPos = 90;  // 舵机回到90度中间位置
      // stopFan();            // 风扇停止
      break;

    case GES_LEFT:
      // 向左手势:舵机转到0度(最左侧)
      Serial.println("向左 ←");
      targetServoPos = 0;
      Serial.println("→ 舵机: 转向0°");
      break;

    case GES_RIGHT:
      // 向右手势:舵机转到180度(最右侧)
      Serial.println("向右 →");
      targetServoPos = 180;
      Serial.println("→ 舵机: 转向180°");
      break;

    case GES_CLOCKWISE:
      // 顺时针旋转手势:风扇正转
      Serial.println("顺时针 ↻");
      fanForward();
      break;

    case GES_ANTICLOCKWISE:
      // 逆时针旋转手势:风扇反转
      Serial.println("逆时针 ↺");
      fanReverse();
      break;

    case GES_WAVE:
      // 挥手手势:停止风扇
      Serial.println("挥手 ✋");
      stopFan();
      break;

    case GES_FORWARD:
      // 向前推手势:风扇加速
      Serial.println("向前 ⇨");
      fanSpeedUp();
      break;

    case GES_BACKWARD:
      // 向后拉手势:风扇减速
      Serial.println("向后 ⇦");
      fanSpeedDown();
      break;

    default:
      // 未识别的手势
      Serial.println("未识别的手势");
      return;
  }

  // 显示当前系统状态
  displayStatus();
  Serial.println("---");
}

// ==================== 状态显示函数 ====================
void displayStatus() {
  Serial.print("📊 当前状态 | 舵机: ");
  Serial.print(currentServoPos);
  Serial.print("° → ");
  Serial.print(targetServoPos);
  Serial.print("° | 风扇: ");

  // 显示风扇状态(正转/反转/停止)
  if (fanDirection == -1) {
    Serial.print("正转");
  } else if (fanDirection == 1) {
    Serial.print("反转");
  } else {
    Serial.print("停止");
  }
  
  Serial.print("(速度: ");
  Serial.print(fanSpeed);
  Serial.println(")");
}

/******************************************************************************
 * 深圳市在芯间科技有限公司
 * 淘宝店铺:在芯间科技零知板
 * 店铺网址:https://shop533070398.taobao.com
 * 版权说明:
 *  1.本代码的版权归【深圳市在芯间科技有限公司】所有,仅限个人非商业性学习使用。
 *  2.严禁将本代码或其衍生版本用于任何商业用途(包括但不限于产品开发、付费服务、企业内部使用等)。
 *  3.任何商业用途均需事先获得【深圳市在芯间科技有限公司】的书面授权,未经授权的商业使用行为将被视为侵权。
******************************************************************************/

系统流程图

四、操作流程

4.1 系统操作

初始化状态:风扇关闭、舵机调整到90°,串口输出初始化日志,板载LED灯慢闪烁3次表示系统就绪

4.2 手势操作演示

启动风扇 :手掌在传感器上方向上挥动,风扇开始正转。

调节方向: 手掌向左 挥动,舵机带动风扇转向左侧;向右挥动则转向右侧。

停止系统: 对着传感器挥手(Wave),风扇立即停止。

4.3 视频演示

零知STM32手势控制风扇系统 - 隔空操作实测

本视频演示了系统的实际上手效果。可以看到识别响应非常灵敏,舵机转动平滑,完全实现了无需物理接触的智能控制

五、PAJ7620U2 手势传感器技术讲解

PAJ7620U2 是一款基于红外成像原理的集成手势识别传感器。

工作原理

传感器内部集成了一个红外发射LED(IR LED)和一个光电二极管阵列。

当手在传感器上方移动时,红外光被手部反射,传感器阵列检测反射光的位置变化、相位偏移和强度差异。通过内置的硬件算法引擎,将这些光信号转化为具体的手势动作(如向上、向下、顺时针等)。

技术参数

参数 数值 说明
工作电压 2.8V-3.6V 使用3.3V供电
I2C频率 100kHz/400kHz 支持标准/快速模式
检测距离 5-15cm 最佳识别距离
视角范围 60° 有效识别角度

5.1 I2C 通信

设备I2C地址通常为 0x73,主机通过发送从机地址、寄存器地址,实现对 PAJ7620U2 内部寄存器的读写操作,从而完成传感器初始化、手势模式配置和手势数据读取

主控板通过 I2C 总线周期性读取 GESTURE_VALID_FLAG 寄存器,当检测到标志位为 1 时,读取 GESTURE_DATA 寄存器的手势编码,转换为对应的手势指令

5.2 寄存器机制

PAJ7620U2 的内部有两个 BANK 寄存器区域,分别为 BANK0 和 BANK1。不同的区域用于访问不同的功能寄存器,访问某一 BANK 区域下的寄存器前需发送控制指令进入该寄存器区域

访问寄存器 0xEF 并写入 0x00 进入 Bank0(常用手势数据都在这里);访问寄存器 0xEF 并写入 0x01 进入 Bank1(用于高级配置)。

函数底层已经帮我们封装好了对 0x43 寄存器的读取,并将其映射为 GES_UPGES_DOWN 等枚举值

PAJ7620U2 内部集成了多个功能寄存器,本项目主要使用初始化配置寄存器和手势识别相关寄存器,核心寄存器如下:

寄存器地址 寄存器名称 功能说明
0x00 MODE_SELECT 模式选择寄存器,用于配置传感器工作模式,本项目设置为手势识别模式(0x01)
0x01-0x03 INIT_CONFIG 初始化配置寄存器,用于设置传感器的采样率、增益等参数,确保手势识别精度
0x43-0x44 GESTURE_DATA 手势数据寄存器,存储识别到的手势类型编码,主机通过读取该寄存器获取手势信息
0x45 GESTURE_VALID_FLAG 手势有效标志寄存器,当检测到有效手势时,该寄存器置 1,主机可通过该标志判断是否有手势输入
0x60-0x6F GESTURE_CONFIG 手势配置寄存器,用于使能 / 禁用特定手势类型(如上下、左右、旋转等),本项目使能全部 9 种手势

六、常见问题解答 (FAQ)

Q1: 串口提示 "PAJ7620初始化失败" 怎么办?

A:检查SDA/SCL线是否接反,确认传感器供电是3.3V。PAJ7620模块容易因杜邦线接触不良导致I2C ACK丢失,请重新拔插连线

Q2: 风扇转速很慢或不转?

A:L9110模块输入PWM占空比过低,可能无法克服电机静摩擦力。代码中定义了FAN_MIN_SPEED 60 ,如果电机没反应,可适当调大该数值。

项目资源整合

PAJ7620U2 数据手册: PAJ7620U2 Datasheet

PAJ7620U2 库文件: RevEng_PAJ7620

相关推荐
清风66666614 小时前
基于单片机的智能家居门铃系统设计
单片机·嵌入式硬件·毕业设计·智能家居·课程设计·期末大作业
qq_1780570714 小时前
IntelliJ IDEA is not responding报错解决
java·ide·intellij-idea
MS1896377374614 小时前
乾芯课堂系列(二)乾芯DSP开发环境 – QX-IDE安装使用入门
ide·mcu·dsp开发
山上春14 小时前
把 Odoo 日志升级成 IDE 级体验:彩色高亮、可点击源码、统一格式(VS Code)
ide
锻炼²1 天前
stm32 HAL配置usb全速 自定义HID类详解
stm32·usb·usb枚举过程·全速传输
polarislove02141 天前
10.1 [ADC] 逐次逼近型ADC-嵌入式铁头山羊STM32笔记
笔记·stm32·嵌入式硬件
qq_672592751 天前
STM32超声测距离的测量精度评估
stm32·硬件架构·硬件工程
单片机系统设计1 天前
基于STM32的智能垃圾桶/语音分类/自动开盖/矩阵按键
stm32·矩阵·毕业设计·语音识别·智能垃圾桶
-曾牛1 天前
【汇编语言入门】从第一个加法程序吃透汇编核心基础
汇编·单片机·嵌入式硬件·汇编语言·病毒分析·lcx·逆向开发
三品吉他手会点灯1 天前
STM32F103 学习笔记-21-串口通信(第3节)-STM32串口初始化结构体和固件库讲解
笔记·stm32·单片机·嵌入式硬件·学习