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