? ?零知開源(零知IDE)是一個(gè)專為電子初學(xué)者/電子興趣愛好者設(shè)計(jì)的開源軟硬件平臺(tái),在硬件上提供超高性價(jià)比STM32系列開發(fā)板、物聯(lián)網(wǎng)控制板。取消了Bootloader程序燒錄,讓開發(fā)重心從“配置環(huán)境”轉(zhuǎn)移到“創(chuàng)意實(shí)現(xiàn)”,極大降低了技術(shù)門檻。零知IDE編程軟件,內(nèi)置上千個(gè)覆蓋多場(chǎng)景的示例代碼,支持項(xiàng)目源碼一鍵下載,項(xiàng)目文章在線瀏覽。零知開源(零知IDE)平臺(tái)通過軟硬件協(xié)同創(chuàng)新,讓你的創(chuàng)意快速轉(zhuǎn)化為實(shí)物,來動(dòng)手試試吧!
?訪問零知實(shí)驗(yàn)室,獲取更多實(shí)戰(zhàn)項(xiàng)目和教程資源吧!
www.lingzhilab.com
?
?
項(xiàng)目概述
本項(xiàng)目基于零知標(biāo)準(zhǔn)板(主控芯片STM32F103RBT6)為核心控制器,結(jié)合先進(jìn)的PAJ7620U2手勢(shì)識(shí)別傳感器和WS2812B RGB LED燈帶,實(shí)現(xiàn)智能手勢(shì)開關(guān)控制功能。系統(tǒng)能夠?qū)崟r(shí)檢測(cè)手部在三維空間中的位置和運(yùn)動(dòng)軌跡,并將這些動(dòng)作信息轉(zhuǎn)換為直觀、絢麗的燈光效果
項(xiàng)目難點(diǎn)及解決方案
問題描述:WS2812B時(shí)序精度控制,STM32普通IO難以滿足嚴(yán)格時(shí)序要求
驅(qū)動(dòng)方案:使用SPI+DMA方式,確保時(shí)序準(zhǔn)確
WS2812B::WS2812Bspi.setClockDivider(SPI_CLOCK_DIV32); // 444ns脈沖 WS2812B::WS2812Bspi.dmaSendAsync(pixels, numBytes); // 異步DMA傳輸
一、系統(tǒng)接線部分
1.1 硬件清單
| 組件名稱 | 型號(hào)規(guī)格 | 數(shù)量 | 說明 |
|---|---|---|---|
| 主控開發(fā)板 | 零知標(biāo)準(zhǔn)板(STM32F103RBT6) | 1 | 主控制器,72MHz主頻 |
| 手勢(shì)傳感器 | PAJ7620U2 | 1 | 手勢(shì)識(shí)別,I2C接口,最大檢測(cè)距離15cm |
| RGB LED燈帶 | WS2812-8 RGB模塊 | 2 | 16顆燈珠,SPI驅(qū)動(dòng),單線控制 |
| 連接線 | 杜邦線(母對(duì)母) | 若干 | 用于模塊間連接 |
| 電源 | 5V/2A直流電源 | 1 | 為系統(tǒng)供電 |
1.2 接線方案表
根據(jù)代碼中的引腳定義,硬件接線方案如下:
PAJ7620U2傳感器接線
| 零知標(biāo)準(zhǔn)板引腳 | PAJ7620U2引腳 | 功能說明 |
|---|---|---|
| A5 | SCL | I2C時(shí)鐘線(軟件模擬) |
| A4 | SDA | I2C數(shù)據(jù)線(軟件模擬) |
| 3.3V | VCC | 傳感器供電(3.3V) |
| GND | GND | 電源地 |
WS2812B燈帶接線
WS2812B需要較大電流,建議使用獨(dú)立5V電源供電
| 零知標(biāo)準(zhǔn)板引腳 | WS2812B燈帶 | 功能說明 |
|---|---|---|
| 11 | DIN | SPI數(shù)據(jù)輸出 |
| 5V | VCC | 燈帶供電(5V) |
| GND | GND | 電源地 |
1.3 具體接線圖
?

1.4 接線實(shí)物圖

二、安裝與使用部分
2.1 開源平臺(tái)-輸入PAJ7620U2并搜索-代碼下載自動(dòng)打開

2.2 連接-驗(yàn)證-上傳

2.3 調(diào)試-串口監(jiān)視器

?
三、代碼講解部分
采用"單次讀取"策略,在主循環(huán)開頭一次性讀取isCursorInView/cursorX/cursorY,確保整個(gè)循環(huán)周期內(nèi)使用同一組傳感器數(shù)據(jù)
3.1手勢(shì)檢測(cè)狀態(tài)機(jī)
// 手勢(shì)檢測(cè)邏輯不再讀取傳感器,而是分析傳入的數(shù)據(jù) // 參數(shù): isPresent(手是否在), y(當(dāng)前Y坐標(biāo)) // 返回: 0(無), 1(向上), -1(向下) int checkGestureLogic(bool isPresent, int y) { static int gestureStartY = 0; static unsigned long gestureStartTime = 0; static bool gestureInProgress = false; // 冷卻時(shí)間檢查:避免重復(fù)觸發(fā) if (millis() - lastGestureTime < GESTURE_COOLDOWN) { return 0; } // 如果手移開了,重置檢測(cè)狀態(tài) if (!isPresent) { gestureInProgress = false; return 0; } // 如果還沒開始檢測(cè),記錄起點(diǎn) if (!gestureInProgress) { gestureStartY = y; gestureStartTime = millis(); gestureInProgress = true; return 0; } // 計(jì)算變化量和速度 unsigned long duration = millis() - gestureStartTime; int yChange = y - gestureStartY; // 只有當(dāng)持續(xù)時(shí)間足夠短且速度足夠快時(shí),才認(rèn)為是手勢(shì) // 增加 duration > 50 是為了避免極其短暫的噪點(diǎn) if (duration > 50 && abs(yChange) / (duration / 1000.0) > GESTURE_SPEED_THRESHOLD) { // 向上快速移動(dòng) (Y值減小) if (yChange < -GESTURE_THRESHOLD) { Serial.println("檢測(cè)到: 向上揮手 (開啟)"); lastGestureTime = millis(); gestureInProgress = false; return 1; } // 向下快速移動(dòng) (Y值增加) if (yChange > GESTURE_THRESHOLD) { Serial.println("檢測(cè)到: 向下?lián)]手 (關(guān)閉)"); lastGestureTime = millis(); gestureInProgress = false; return -1; } } // 超時(shí)重置 (如果動(dòng)作太慢,就視為普通移動(dòng)而非手勢(shì)) if (duration > 800) { gestureInProgress = false; } return 0; }
手部進(jìn)入檢測(cè)區(qū)域,記錄起始坐標(biāo)和時(shí)間,持續(xù)跟蹤Y坐標(biāo)變化,計(jì)算移動(dòng)速度和幅度
3.2系統(tǒng)狀態(tài)管理
// 系統(tǒng)核心狀態(tài)變量
bool systemOn = false; // 系統(tǒng)開關(guān)狀態(tài),初始為關(guān)閉
bool isIdleMode = true; // 是否處于待機(jī)模式
int idleEffectMode = 0; // 待機(jī)效果模式:0=流水燈, 1=呼吸燈
uint8_t globalBrightness = 30; // 全局亮度控制
void loop()
{
// 獲取傳感器數(shù)據(jù)
// ...
// 處理系統(tǒng)開關(guān)邏輯
if (!systemOn) {
// 關(guān)機(jī)狀態(tài):只響應(yīng)開啟手勢(shì)
if (detectedDir == 1) {
turnOnSystem();
}
delay(30);
return;
}
// 開機(jī)狀態(tài):優(yōu)先檢查關(guān)閉手勢(shì)
if (detectedDir == -1) {
turnOffSystem();
return;
}
// 正常的交互邏輯
// ...
}
狀態(tài)機(jī)流程圖

3.3 視覺交互反饋
開機(jī)動(dòng)畫效果
從中心向兩側(cè)漸變紫色光效,過渡到全彩虹色
void showStartupEffect() {
strip.clear();
strip.show();
delay(100);
// 從中心向兩側(cè)展開的紫色動(dòng)畫
int center = NUM_LEDS / 2;
int maxDistance = max(center, NUM_LEDS - center - 1);
for (int step = 0; step <= maxDistance; step++) {
strip.clear();
float brightnessFactor = (float)step / maxDistance;
brightnessFactor = constrain(brightnessFactor, 0.2, 1.0);
// 紫色(RGB: 148,0,211)
uint8_t r = 148 * brightnessFactor;
uint8_t b = 211 * brightnessFactor;
// 兩側(cè)對(duì)稱點(diǎn)亮
for (int dist = 0; dist <= step; dist++) {
if (center + dist < NUM_LEDS) strip.setPixelColor(center + dist, r, 0, b);
if (center - dist >= 0) strip.setPixelColor(center - dist, r, 0, b);
}
strip.show();
delay(60);
}
// 過渡到彩虹色
for (int i = 0; i < NUM_LEDS; i++) {
strip.setPixelColor(i, wheel((i * 256 / NUM_LEDS) % 256));
}
strip.show();
delay(500);
}
關(guān)機(jī)效果
關(guān)機(jī)效果為全部燈珠的亮度從當(dāng)前值漸降至 0 后熄滅
void turnOffAllLEDs() {
// 漸暗效果
for (int b = globalBrightness; b > 0; b -= 10) {
strip.setBrightness(b);
strip.show();
delay(10);
}
strip.clear();
strip.show();
globalBrightness = 30; // 重置亮度
}
狀態(tài)指示燈
初始化提示,閃爍第 0 個(gè)燈珠 3 次提示就緒
void blinkStatusLED(int times, int delayTime) {
for (int i = 0; i < times; i++) {
strip.setPixelColor(0, 0, 0, 255); // 藍(lán)色閃爍
strip.show();
delay(delayTime);
strip.setPixelColor(0, 0, 0, 0);
strip.show();
delay(delayTime);
}
}
3.4 手勢(shì)跟蹤效果
void updateHandTrackingEffect(int x, int y) { int ledIndex = map(x, Y_MIN, Y_MAX, 0, NUM_LEDS - 1); ledIndex = constrain(ledIndex, 0, NUM_LEDS - 1); for(int i = 0; i < NUM_LEDS; i++) { float intensity = trailEffect[i]; if(i == ledIndex) { // 當(dāng)前位置:白色高亮 strip.setPixelColor(i, 255, 255, 255); } else if(intensity > 0.05) { // 尾影位置:彩虹色 uint32_t col = wheel((i * 256 / NUM_LEDS) % 256); uint8_t r = ((col >> 16) & 0xFF) * intensity; uint8_t g = ((col >> 8) & 0xFF) * intensity; uint8_t b = (col & 0xFF) * intensity; strip.setPixelColor(i, r, g, b); } else { // 無尾影:關(guān)閉LED strip.setPixelColor(i, 0, 0, 0); } } }
3.5 系統(tǒng)待機(jī)
void updateWaterFlowEffect() { // 清屏 for(int i = 0; i < NUM_LEDS; i++) strip.setPixelColor(i, 0, 0, 0); static int dir = 1; // 移動(dòng)方向 idlePosition += dir; // 邊界處理和方向反轉(zhuǎn) if(idlePosition >= NUM_LEDS || idlePosition < 0) { dir *= -1; idlePosition += dir; idleColorIndex = (idleColorIndex + 1) % 7; // 切換顏色 } // 設(shè)置當(dāng)前光點(diǎn) uint32_t c = rainbowColors[idleColorIndex]; strip.setPixelColor(idlePosition, c); // 注意:這個(gè)版本簡(jiǎn)化了尾跡效果,可根據(jù)需要恢復(fù) } uint32_t wheel(uint8_t wheelPos) { wheelPos = 255 - wheelPos; // 反轉(zhuǎn)以獲得更鮮艷的顏色 if(wheelPos < 85) { return strip.Color(255 - wheelPos * 3, 0, wheelPos * 3); // 紅-?>紫 } if(wheelPos < 170) { wheelPos -= 85; return strip.Color(0, wheelPos * 3, 255 - wheelPos * 3); // 紫-?>青 } wheelPos -= 170; return strip.Color(wheelPos * 3, 255 - wheelPos * 3, 0); // 青->綠 }
3.6 完整代碼
/************************************************************************************** * 文件: /PAJ7620U2_Gesture_WS2812/PAJ7620U2_Gesture_WS2812.ino * 作者:零知實(shí)驗(yàn)室(深圳市在芯間科技有限公司) * -^^- 零知實(shí)驗(yàn)室,讓電子制作變得更簡(jiǎn)單! -^^- * 時(shí)間: 2025-12-26 * 說明: 基于零知標(biāo)準(zhǔn)板(STM32F103RBT6)驅(qū)動(dòng)PAJ7620U2手勢(shì)傳感器實(shí)現(xiàn)WS2812B燈帶控制, * 支持手部位置跟蹤的彩虹尾影效果,無手部時(shí)自動(dòng)切換為流水燈/呼吸燈待機(jī)效果 * 采用"單次讀取"策略,確保在任意狀態(tài)下均能準(zhǔn)確識(shí)別退出手勢(shì)。 ***************************************************************************************/ #include "RevEng_PAJ7620.h" #include // 手勢(shì)傳感器對(duì)象 RevEng_PAJ7620 sensor = RevEng_PAJ7620(); // LED燈帶配置 #define NUM_LEDS 16 WS2812B strip = WS2812B(NUM_LEDS); // 系統(tǒng)狀態(tài) int lastCursorX = 0; bool isIdleMode = true; bool systemOn = false; // 系統(tǒng)開關(guān)狀態(tài),初始為關(guān)閉 // 手部跟蹤變量 float trailEffect[NUM_LEDS] = {0}; float trailDecay = 0.85; // 待機(jī)效果變量 int idleEffectMode = 0; // 0:流水燈, 1:呼吸燈 int idleColorIndex = 0; int idlePosition = 0; float idlePulse = 0; unsigned long idleLastUpdate = 0; // 亮度控制 uint8_t globalBrightness = 30; // 全局亮度,0-255 // 手勢(shì)檢測(cè)變量 unsigned long lastGestureTime = 0; const unsigned long GESTURE_COOLDOWN = 800; // 稍微縮短冷卻時(shí)間提高響應(yīng) const int GESTURE_THRESHOLD = 1000; // 調(diào)低閾值使其更容易觸發(fā) const int GESTURE_SPEED_THRESHOLD = 600; // 調(diào)低速度閾值 // 坐標(biāo)范圍 const int Y_MIN = 384; const int Y_MAX = 3455; // 預(yù)定義顏色(彩虹色) uint32_t rainbowColors[7] = { strip.Color(255, 0, 0), // 紅色 strip.Color(255, 127, 0), // 橙色 strip.Color(255, 255, 0), // 黃色 strip.Color(0, 255, 0), // 綠色 strip.Color(0, 0, 255), // 藍(lán)色 strip.Color(75, 0, 130), // 靛藍(lán)色 strip.Color(148, 0, 211) // 紫色 }; // *************************************************************************** void setup() { Serial.begin(115200); // 初始化LED燈帶 strip.begin(); strip.clear(); strip.show(); // 初始化手勢(shì)傳感器 if(!sensor.begin()) { Serial.println("PAJ7620初始化失敗!"); while(true) {} } Serial.println("PAJ7620U2初始化成功!"); sensor.setCursorMode(); // 設(shè)置為光標(biāo)模式 Serial.println("n手勢(shì)控制LED系統(tǒng)已啟動(dòng)!"); Serial.println("向上快速揮手: 打開系統(tǒng)"); Serial.println("向下快速揮手: 關(guān)閉系統(tǒng)"); systemOn = false; turnOffAllLEDs(); blinkStatusLED(3, 300); } // *************************************************************************** void loop() { // 在循環(huán)開始時(shí),一次性讀取所有傳感器數(shù)據(jù) bool currentInView = sensor.isCursorInView(); int currentX = 0; int currentY = 0; if (currentInView) { currentX = sensor.getCursorX(); currentY = sensor.getCursorY(); } // 將讀取到的數(shù)據(jù)傳入手勢(shì)檢測(cè)函數(shù) (不讓函數(shù)內(nèi)部再次讀取) // 返回值: 0=無, 1=向上(開), -1=向下(關(guān)) int detectedDir = checkGestureLogic(currentInView, currentY); // 處理系統(tǒng)開關(guān)邏輯 if (!systemOn) { // 關(guān)機(jī)狀態(tài):只響應(yīng)開啟手勢(shì) if (detectedDir == 1) { turnOnSystem(); } delay(30); // 簡(jiǎn)單的防抖延遲 return; } // 開機(jī)狀態(tài):優(yōu)先檢查關(guān)閉手勢(shì) if (detectedDir == -1) { turnOffSystem(); return; } // 如果沒有開關(guān)機(jī)手勢(shì),執(zhí)行正常的LED顯示邏輯 strip.setBrightness(globalBrightness); if(currentInView) { // 手部跟蹤模式 isIdleMode = false; // 亮度跟隨Y軸變化 int brightness = map(currentY, Y_MIN, Y_MAX, 30, 255); brightness = constrain(brightness, 30, 255); if (abs(brightness - globalBrightness) > 5) { globalBrightness = brightness; strip.setBrightness(globalBrightness); } updateTrail(); // 使用剛才讀取的 currentX 更新效果 updateHandTrackingEffect(currentX, currentY); // 添加高亮 int ledIndex = map(currentX, Y_MIN, Y_MAX, 0, NUM_LEDS - 1); ledIndex = constrain(ledIndex, 0, NUM_LEDS - 1); trailEffect[ledIndex] = 1.0; lastCursorX = currentX; } else { // 待機(jī)模式 if(!isIdleMode) { resetIdleEffects(); isIdleMode = true; } updateTrailFast(); updateIdleEffect(); } strip.show(); delay(30); } // *************************************************************************** // 手勢(shì)檢測(cè)不再讀取傳感器,而是分析傳入的數(shù)據(jù) // 參數(shù): isPresent(手是否在), y(當(dāng)前Y坐標(biāo)) // 返回: 0(無), 1(向上), -1(向下) int checkGestureLogic(bool isPresent, int y) { static int gestureStartY = 0; static unsigned long gestureStartTime = 0; static bool gestureInProgress = false; unsigned long currentTime = millis(); // 冷卻時(shí)間檢查 if (currentTime - lastGestureTime < GESTURE_COOLDOWN) { return 0; } // 如果手移開了,重置檢測(cè)狀態(tài) if (!isPresent) { gestureInProgress = false; return 0; } // 如果還沒開始檢測(cè),記錄起點(diǎn) if (!gestureInProgress) { gestureStartY = y; gestureStartTime = currentTime; gestureInProgress = true; return 0; // 剛開始,還沒結(jié)果 } // 計(jì)算變化量和速度 unsigned long duration = currentTime - gestureStartTime; int yChange = y - gestureStartY; // 避免除以零 if (duration == 0) return 0; float speed = abs(yChange) / (duration / 1000.0); // 只有當(dāng)持續(xù)時(shí)間足夠短且速度足夠快時(shí),才認(rèn)為是手勢(shì) // 增加 duration > 50 是為了避免極其短暫的噪點(diǎn) if (duration > 50 && speed > GESTURE_SPEED_THRESHOLD) { // 向上快速移動(dòng) (Y值減小) if (yChange < -GESTURE_THRESHOLD) { Serial.println("檢測(cè)到: 向上揮手 (開啟)"); lastGestureTime = currentTime; gestureInProgress = false; return 1; } // 向下快速移動(dòng) (Y值增加) if (yChange > GESTURE_THRESHOLD) { Serial.println("檢測(cè)到: 向下?lián)]手 (關(guān)閉)"); lastGestureTime = currentTime; gestureInProgress = false; return -1; } } // 超時(shí)重置 (如果動(dòng)作太慢,就視為普通移動(dòng)而非手勢(shì)) if (duration > 800) { gestureInProgress = false; // 重置,這也允許連續(xù)跟蹤而不誤觸發(fā) } return 0; } // *************************************************************************** // 打開系統(tǒng) void turnOnSystem() { systemOn = true; Serial.println(">>> 系統(tǒng)啟動(dòng)"); resetIdleEffects(); clearTrailEffects(); globalBrightness = 30; strip.setBrightness(globalBrightness); showStartupEffect(); } // *************************************************************************** // 關(guān)閉系統(tǒng) void turnOffSystem() { systemOn = false; Serial.println(">>> 系統(tǒng)關(guān)閉"); turnOffAllLEDs(); } // *************************************************************************** // 啟動(dòng)效果 void showStartupEffect() { strip.clear(); strip.show(); delay(100); int center = NUM_LEDS / 2; int maxDistance = max(center, NUM_LEDS - center - 1); for (int step = 0; step <= maxDistance; step++) { strip.clear(); float brightnessFactor = (float)step / maxDistance; brightnessFactor = constrain(brightnessFactor, 0.2, 1.0); uint8_t r = 148 * brightnessFactor; uint8_t b = 211 * brightnessFactor; for (int dist = 0; dist <= step; dist++) { if (center + dist < NUM_LEDS) strip.setPixelColor(center + dist, r, 0, b); if (center - dist >= 0) strip.setPixelColor(center - dist, r, 0, b); } strip.show(); delay(60); } // 快速過渡到彩虹 for (int i = 0; i < NUM_LEDS; i++) { strip.setPixelColor(i, wheel((i * 256 / NUM_LEDS) % 256)); } strip.show(); delay(500); } // *************************************************************************** // LED 燈帶平滑漸暗關(guān)閉 void turnOffAllLEDs() { for (int b = globalBrightness; b > 0; b -= 10) { strip.setBrightness(b); strip.show(); delay(10); } strip.clear(); strip.show(); globalBrightness = 30; } // *************************************************************************** // 清除所有手勢(shì)跟蹤的軌跡變量,重置手勢(shì)軌跡效果 void clearTrailEffects() { for(int i = 0; i < NUM_LEDS; i++) trailEffect[i] = 0; } // *************************************************************************** // 控制第1個(gè)LED(索引0)按指定次數(shù)和間隔閃爍,用于設(shè)備狀態(tài)初始化成功提示 void blinkStatusLED(int times, int delayTime) { for (int i = 0; i < times; i++) { strip.setPixelColor(0, 0, 0, 255); strip.show(); delay(delayTime); strip.setPixelColor(0, 0, 0, 0); strip.show(); delay(delayTime); } } // *************************************************************************** // 緩慢衰減軌跡強(qiáng)度,用于手勢(shì)效果 void updateTrail() { for(int i = 0; i < NUM_LEDS; i++) { trailEffect[i] *= trailDecay; if(trailEffect[i] < 0.01) trailEffect[i] = 0; } } // *************************************************************************** // 快速衰減軌跡強(qiáng)度,用于待機(jī)效果 void updateTrailFast() { for(int i = 0; i < NUM_LEDS; i++) { trailEffect[i] *= 0.6; if(trailEffect[i] < 0.01) trailEffect[i] = 0; } } // *************************************************************************** // 根據(jù)手勢(shì)坐標(biāo)(x,y)更新LED燈帶的手勢(shì)跟蹤顯示效果 void updateHandTrackingEffect(int x, int y) { int ledIndex = map(x, Y_MIN, Y_MAX, 0, NUM_LEDS - 1); ledIndex = constrain(ledIndex, 0, NUM_LEDS - 1); for(int i = 0; i < NUM_LEDS; i++) { float intensity = trailEffect[i]; if(i == ledIndex) { strip.setPixelColor(i, 255, 255, 255); // 手勢(shì)當(dāng)前位置的LED顯示白色 (255,255,255) } else if(intensity > 0.05) { uint32_t col = wheel((i * 256 / NUM_LEDS) % 256); // 彩虹色拖尾 uint8_t r = ((col >> 16) & 0xFF) * intensity; uint8_t g = ((col >> 8) & 0xFF) * intensity; uint8_t b = (col & 0xFF) * intensity; strip.setPixelColor(i, r, g, b); } else { strip.setPixelColor(i, 0, 0, 0); } } } // *************************************************************************** // 重置所有待機(jī)狀態(tài)特效的參數(shù) void resetIdleEffects() { idlePosition = 0; idlePulse = 0; idleColorIndex = 0; idleLastUpdate = millis(); idleEffectMode = random(0, 2); } // *************************************************************************** // 每5秒自動(dòng)切換空閑狀態(tài)模式 void updateIdleEffect() { unsigned long currentTime = millis(); if(currentTime - idleLastUpdate > 5000) { idleEffectMode = (idleEffectMode + 1) % 2; idleLastUpdate = currentTime; } if(idleEffectMode == 0) updateWaterFlowEffect(); else updateBreathingEffect(); } // *************************************************************************** // 空閑狀態(tài)流水燈效果 void updateWaterFlowEffect() { for(int i = 0; i < NUM_LEDS; i++) strip.setPixelColor(i, 0, 0, 0); static int dir = 1; idlePosition += dir; if(idlePosition >= NUM_LEDS || idlePosition < 0) { dir *= -1; idlePosition += dir; idleColorIndex = (idleColorIndex + 1) % 7; } uint32_t c = rainbowColors[idleColorIndex]; strip.setPixelColor(idlePosition, c); } // *************************************************************************** // 空閑狀態(tài)呼吸燈效果 void updateBreathingEffect() { idlePulse += 0.05; if(idlePulse > 6.28) { idlePulse = 0; idleColorIndex = (idleColorIndex + 1) % 7; } float val = (sin(idlePulse) + 1.0) / 2.0 * 0.8 + 0.2; // 周期性生成 0~1 之間的亮度系數(shù)val uint32_t c = rainbowColors[idleColorIndex]; uint8_t r = ((c >> 16) & 0xFF) * val; uint8_t g = ((c >> 8) & 0xFF) * val; uint8_t b = (c & 0xFF) * val; for(int i = 0; i < NUM_LEDS; i++) strip.setPixelColor(i, r, g, b); } // *************************************************************************** // 生成對(duì)應(yīng)的彩虹顏色RGB,實(shí)現(xiàn)HSV到RGB的簡(jiǎn)易轉(zhuǎn)換 uint32_t wheel(uint8_t wheelPos) { wheelPos = 255 - wheelPos; if(wheelPos < 85) return strip.Color(255 - wheelPos * 3, 0, wheelPos * 3); if(wheelPos < 170) { wheelPos -= 85; return strip.Color(0, wheelPos * 3, 255 - wheelPos * 3); } wheelPos -= 170; return strip.Color(wheelPos * 3, 255 - wheelPos * 3, 0); } /****************************************************************************** * 深圳市在芯間科技有限公司 * 淘寶店鋪:在芯間科技零知板 * 店鋪網(wǎng)址:https://shop533070398.taobao.com * 版權(quán)說明: * 1.本代碼的版權(quán)歸【深圳市在芯間科技有限公司】所有,僅限個(gè)人非商業(yè)性學(xué)習(xí)使用。 * 2.嚴(yán)禁將本代碼或其衍生版本用于任何商業(yè)用途(包括但不限于產(chǎn)品開發(fā)、付費(fèi)服務(wù)、企業(yè)內(nèi)部使用等)。 * 3.任何商業(yè)用途均需事先獲得【深圳市在芯間科技有限公司】的書面授權(quán),未經(jīng)授權(quán)的商業(yè)使用行為將被視為侵權(quán)。 ******************************************************************************/
系統(tǒng)流程圖

四、操作流程
4.1 系統(tǒng)操作
初始化狀態(tài):燈帶全滅,第 0 個(gè)燈珠閃爍 3 次,串口提示 “向上快速揮手:打開系統(tǒng)”;
?

在傳感器上方快速向上揮手開啟系統(tǒng),在開機(jī)狀態(tài)下,快速向下?lián)]手關(guān)閉系統(tǒng),LED燈帶會(huì)逐漸變暗關(guān)閉
手勢(shì)控制:將手放在傳感器上方5-10cm處,LED燈帶會(huì)對(duì)應(yīng)顯示白色光標(biāo);左右平行移動(dòng)手部,光標(biāo)跟隨移動(dòng)并留下彩虹色尾影

手部離開傳感器區(qū)域,自動(dòng)進(jìn)入待機(jī)模式,每5秒在流水燈和呼吸燈之間切換
?
靈敏度調(diào)整
// 如果手勢(shì)不易觸發(fā),降低閾值 const int GESTURE_THRESHOLD = 800; // 降低幅度要求 const int GESTURE_SPEED_THRESHOLD = 400; // 降低速度要求 // 如果誤觸發(fā)過多,增加閾值 const int GESTURE_THRESHOLD = 1200; // 增加幅度要求 const int GESTURE_SPEED_THRESHOLD = 800; // 增加速度要求
視覺效果調(diào)整
// 調(diào)整尾影持續(xù)時(shí)間 float trailDecay = 0.9; // 值越大,尾影持續(xù)時(shí)間越長(zhǎng) // 調(diào)整亮度范圍 int brightness = map(currentY, Y_MIN, Y_MAX, 20, 100); // 縮小亮度變化范圍
4.2 視頻演示
?https://www.bilibili.com/video/BV1uivsBBEJo/?vd_source=a31e3d8d8ce008260eee442534c2f63d
系統(tǒng)初始化燈帶閃爍狀態(tài)燈提示就緒,手勢(shì)跟蹤模式下白色光點(diǎn)跟隨手部 X 軸,亮度隨手部 Y 軸平滑變化,尾影效果自然;移開手部自動(dòng)切換流水燈 / 呼吸燈
五、PAJ7620U2 手勢(shì)傳感器知識(shí)點(diǎn)講解
PAJ7620U2是一款內(nèi)置光源和環(huán)境光抑制濾波器集成的 LED,鏡頭和手勢(shì)感測(cè)器在一個(gè)小的立方體模組,能在黑暗或低光環(huán)境下工作,其模塊功能框圖如下圖所示

IIC 接口,支持高達(dá) 400KHz 通信速率;內(nèi)置 9 個(gè)手勢(shì)類型(上、下、左、右、前、后、順時(shí)針旋轉(zhuǎn)、逆時(shí)針旋轉(zhuǎn)、揮動(dòng)),支持輸出中斷;支持接近檢測(cè)功能,檢測(cè)物體體積大小和亮度
5.1 軟件I2C通信
MCU 通過 I2C 接口來控制 PAJ7620U2,PAJ7620U2 工作時(shí)通過內(nèi)部 LED 驅(qū)動(dòng)器
I2C 時(shí)序參數(shù)

| 參數(shù)說明 | 符號(hào) | 標(biāo)準(zhǔn)模式 | 快速模式 | 單位 |
|---|---|---|---|---|
| SCL 時(shí)鐘頻率 | fscl | 10 ~ 100 | 10 ~ 400 | kHz |
| 起始/重復(fù)起始條件保持時(shí)間(此后產(chǎn)生第一個(gè)時(shí)鐘脈沖) | tHD,STA | ≥ 4 | ≥ 0.6 | μs |
| 重復(fù)起始條件建立時(shí)間 | tSU,STA | ≥ 4.7 | ≥ 0.6 | μs |
| SCL 時(shí)鐘低電平時(shí)間 | tLOW | ≥ 4.7 | ≥ 1.3 | μs |
| SCL 時(shí)鐘高電平時(shí)間 | tHIGH | ≥ 4 | ≥ 0.6 | μs |
| 數(shù)據(jù)保持時(shí)間 | tHD,DAT? | ≥ 0 | ≥ 0 | μs |
| 數(shù)據(jù)建立時(shí)間 | tSU,DATt | ≥ 250 | ≥ 100 | ns |
| SDA 與 SCL 信號(hào)上升時(shí)間 | tr | ≤ 1000 | ≤ 300 | ns |
| SDA 與 SCL 信號(hào)下降時(shí)間 | tf | ≤ 300 | ≤ 300 | ns |
| 停止條件建立時(shí)間 | tSU,STO | ≥ 4 | ≥ 0.6 | μs |
| 停止條件到起始條件的總線空閑時(shí)間 | tBUF | ≥ 4.7 | ≥ 1.3 | μs |
當(dāng)傳感器陣列在有效的距離中探測(cè)到物體時(shí),目標(biāo)信息提取陣列會(huì)對(duì)探測(cè)目標(biāo)進(jìn)行特征原始數(shù)據(jù)的獲取,獲取的數(shù)據(jù)會(huì)存在寄存器中,同時(shí)手勢(shì)識(shí)別陣列會(huì)對(duì)原始數(shù)據(jù)進(jìn)行識(shí)別處理,最后將手勢(shì)結(jié)果存到寄存器中,用戶可根據(jù) I2C 接口對(duì)原始數(shù)據(jù)和手勢(shì)識(shí)別的結(jié)果進(jìn)行讀取
光標(biāo)模式數(shù)據(jù)讀取時(shí)序

5.2 寄存器配置
PAJ7620U2 的內(nèi)部有兩個(gè) BANK 寄存器區(qū)域,分別為 BANK0 和 BANK1。不同的區(qū)域用于訪問不同的功能寄存器,訪問某一 BANK 區(qū)域下的寄存器前需發(fā)送控制指令進(jìn)入該寄存器區(qū)域

進(jìn)入 BANK0 區(qū)域需向傳感器 0xEF 地址寫 0x00,而 BANK1 區(qū)域需向傳感器 0xEF 地址寫 0x01
使能工作寄存器

該寄存器地址為 0X72,用于使能 PAJ7620 工作,bit0 位設(shè)置為 1 則使能 PAJ7620 工作,設(shè)置為 0 則失能 PAJ7620 工作
手勢(shì)檢測(cè)輸出中斷使能寄存器

該寄存器地址為 0X41,用于手勢(shì)識(shí)別,bit0~bit7 位用于使能不同手勢(shì)識(shí)別結(jié)果的中斷輸出,默認(rèn)值為 0XFF
六、常見問題解答(FAQ)
Q1:手勢(shì)開關(guān)機(jī)偶爾誤觸發(fā)?
A:解決方案:增大GESTURE_THRESHOLD 或GESTURE_SPEED_THRESHOLD 閾值,提高觸發(fā)門檻;延長(zhǎng)GESTURE_COOLDOWN(如 1000ms),避免短時(shí)間重復(fù)觸發(fā)
Q2:如何增加更多LED?
A:修改步驟:更新NUM_LEDS定義,調(diào)整trailEffect數(shù)組大小,可能需要增加電源功率
項(xiàng)目資源整合
PAJ7620U2 數(shù)據(jù)手冊(cè): https://files.seeedstudio.com/wiki/Grove_Gesture_V_1.0/res/PAJ7620U2_DS_v1.5_05012022_Confidential.pdf
PAJ7620U2 庫(kù)文件: https://github.com/acrandal/RevEng_PAJ7620
審核編輯 黃宇
-
RGB
+關(guān)注
關(guān)注
4文章
831瀏覽量
61924 -
IDE
+關(guān)注
關(guān)注
0文章
365瀏覽量
49050 -
STM32F103RBT6
+關(guān)注
關(guān)注
0文章
4瀏覽量
7796 -
WS2812
關(guān)注
0文章
35瀏覽量
7136
發(fā)布評(píng)論請(qǐng)先 登錄
零知IDE——基于零知標(biāo)準(zhǔn)板驅(qū)動(dòng)PAJ7620U2手勢(shì)控制L9110風(fēng)扇模塊和SG90舵機(jī)系統(tǒng)
STM32驅(qū)動(dòng)PAJ7620手勢(shì)識(shí)別傳感器
CW32L012/F030靈眸X1智能小車——板載WS2812驅(qū)動(dòng)示例
零知IDE——基于STM32F103RBT6的PAJ7620U2手勢(shì)控制WS2812 RGB燈帶系統(tǒng)
【瑞薩RA6E2地奇星開發(fā)板試用】點(diǎn)亮 WS2812 全彩點(diǎn)陣屏
基于STM32F103C8T6驅(qū)動(dòng)WS2812彩燈模塊點(diǎn)亮RGB燈
【瑞薩RA6E2】驅(qū)動(dòng) WS2812 實(shí)現(xiàn) RGB 跑馬燈效果
【瑞薩RA6E2】點(diǎn)亮 WS2812 全彩點(diǎn)陣屏
零知開源——STM32F103RBT6驅(qū)動(dòng) ICM20948 九軸傳感器及 vofa + 上位機(jī)可視化教程
零知開源——STM32F103RBT6驅(qū)動(dòng) ICM20948 九軸傳感器及 vofa + 上位機(jī)可視化教程
零知IDE——基于STM32F103RBT6的PAJ7620U2手勢(shì)控制WS2812 RGB燈帶系統(tǒng)
評(píng)論