? ?零知開源(零知IDE)是一個專為電子初學者/電子興趣愛好者設計的開源軟硬件平臺,在硬件上提供超高性價比STM32系列開發板、物聯網控制板。取消了Bootloader程序燒錄,讓開發重心從“配置環境”轉移到“創意實現”,極大降低了技術門檻。零知IDE編程軟件,內置上千個覆蓋多場景的示例代碼,支持項目源碼一鍵下載,項目文章在線瀏覽。零知開源(零知IDE)平臺通過軟硬件協同創新,讓你的創意快速轉化為實物,來動手試試吧!
?訪問零知實驗室,獲取更多實戰項目和教程資源吧!
www.lingzhilab.com
?
項目概述
本項目使用零知標準板(主控芯片: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邏輯電平 |
| GND | GND | 地線 | |
| SCL | SCL (或對應I2C SCL) | I2C 時鐘線 | |
| SDA | SDA (或對應I2C SDA) | I2C 數據線 | |
| SG90 舵機 | 信號線 (橙) | 12 | PWM控制信號 |
| VCC (紅) | 3.3V(直插拓展板) | 電源正 | |
| GND (棕) | GND | 電源地 | |
| L9110 風扇 | INA | 9(PB7) | 電機控制腳A |
| INB | 5(PB6) | 電機控制腳B | |
| VCC | 5V (建議外接) | 電源正 | |
| 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配置
//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平滑移動算法
// 平滑移動舵機(防止舵機快速轉動時抖動或損壞)
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風扇控制算法
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%
// ============ 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 完整代碼
/************************************************************************************** * 文件: /Gesture_Control_Servo_Fan/Gesture_Control_Servo_Fan.ino * 作者:零知實驗室(深圳市在芯間科技有限公司) * -^^- 零知實驗室,讓電子制作變得更簡單! -^^- * 時間: 2025-12-30 * 說明:零知標準板(STM32F103RBT6) + PAJ7620U2 + L9110 手勢控制系統 * 功能:手勢控制舵機(12號引腳)和風扇(5,9號引腳) * 向上-風扇正轉,向下-舵機90°,向左-舵機0°,向右-舵機180° * 順時針-風扇正轉,逆時針-風扇反轉,揮手-風扇停止 * 向前-風扇加速,向后-風扇減速 ***************************************************************************************/ #include #include #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(" 審核編輯 黃宇
-
STM32
+關注
關注
2309文章
11162瀏覽量
373438 -
SG90
+關注
關注
0文章
10瀏覽量
11744 -
L9110
+關注
關注
3文章
13瀏覽量
17367
發布評論請先 登錄
零知IDE——基于STM32F103RBT6的PAJ7620U2手勢控制WS2812 RGB燈帶系統
利用PAJ7620U2 Gesture Sensor手勢識別控制小車
手勢傳感PAJ7620U2的智能家居應用分享
【RA4M2設計挑戰賽】基于PAJ7620U2手勢識別網關設計
使用Arduino和PAJ7620手勢傳感器制作手勢控制機器人
ATK-PAJ7620手勢識別模塊的用戶手冊免費下載
PAJ7620U2集成I2C接口手勢識別傳感器的數據手冊免費下載
PAJ7620U2帶I2C接口的集成手勢識別傳感器數據手冊免費下載
零知IDE——基于STM32F103RBT6的PAJ7620U2手勢控制WS2812 RGB燈帶系統
零知IDE——基于零知標準板驅動PAJ7620U2手勢控制L9110風扇模塊和SG90舵機系統
評論