chinese直男口爆体育生外卖, 99久久er热在这里只有精品99, 又色又爽又黄18禁美女裸身无遮挡, gogogo高清免费观看日本电视,私密按摩师高清版在线,人妻视频毛茸茸,91论坛 兴趣闲谈,欧美 亚洲 精品 8区,国产精品久久久久精品免费

0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

【人臉+手勢(shì)AI識(shí)別模組】100元自己做一個(gè)數(shù)碼相機(jī)

AIoT行業(yè)洞察 ? 來源:AIoT行業(yè)洞察 ? 作者:AIoT行業(yè)洞察 ? 2025-09-16 16:06 ? 次閱讀
加入交流群
微信小助手二維碼

掃碼添加小助手

加入工程師交流群

以下作品由安信可社區(qū)用戶

dzy7455339制作

原貼地址

電子DIY作品】BW21數(shù)碼相機(jī)+BW21-CBV-KIT

一直想自己DIY一個(gè)相機(jī),但是奈何筆者個(gè)人水平有限,雖然有各種強(qiáng)大的芯片,但是自己用不了,后來有了ESP32但是拍攝出的畫面質(zhì)量不是很滿意,所以這個(gè)想法一直擱置。

看到安信可新出的BW21-CBV-Kit支持?jǐn)z像頭、1080p錄像和SD卡,最關(guān)鍵的是它還支持Arduino編程,讓筆者做相機(jī)的想法得以快速實(shí)現(xiàn)。

wKgZPGjJGmiAHH8PAABaUH_KpSw39.webpwKgZO2jJGmiAWOXAAAA8wnwwJ4o30.webp

1硬件準(zhǔn)備

考慮到個(gè)人硬件水平有限,這里直接使用了BW21-CBV-Kit開發(fā)板作為核心。圍繞核心功能相機(jī),需要準(zhǔn)備的外部設(shè)備還有電源、屏幕、閃光燈、計(jì)時(shí)器、按鍵這些功能。

另外板子雖然板載了一個(gè)模擬麥克風(fēng),但是實(shí)際使用起來比較差強(qiáng)人意,所以數(shù)字麥克風(fēng)也加入了外設(shè)的清單里。

以BW21-CBV-Kit為基礎(chǔ)做了2個(gè)擴(kuò)展版:

第一個(gè)擴(kuò)展版主要承載屏幕、按鍵以及BW21-CBV-Kit,

第二個(gè)擴(kuò)展版則集成了充電、RTC、閃光燈以及數(shù)字麥克風(fēng)。

因?yàn)椴粫?huì)使用3D軟件,這里直接在擴(kuò)展版的基礎(chǔ)上利用立創(chuàng)EDA的制作外殼功能畫了一個(gè)簡(jiǎn)單的外殼。

wKgZPGjJGmmAepjsAABEDErQ3RA93.webp

先對(duì)開發(fā)板進(jìn)行了單項(xiàng)基礎(chǔ)功能測(cè)試,發(fā)現(xiàn)2個(gè)問題:

一是選擇引腳的時(shí)候沒有避開SWD引腳,導(dǎo)致I2C通訊失敗。
二是板子和外殼留孔對(duì)不上,沒辦法只能重新來過。

幸好第二次板子功能正常,和外殼也很搭配。

wKgZO2jJGmmAJGGUAAC5jlY1mzE62.webp

2軟件

其實(shí)開發(fā)板在Arduino中準(zhǔn)備了很多使用的例子進(jìn)行使用,需要做的就是把這些組合起來。

這里用到的核心例程主要有Camera_2_LCD, SingleVideoWithAudio以及SDCARDsaveJPG幾個(gè)示例。

第一個(gè)示例實(shí)現(xiàn)了攝像頭畫面到屏幕
第二個(gè)示例實(shí)現(xiàn)了錄制MP4視頻到SD卡
第三個(gè)示例實(shí)現(xiàn)了拍攝圖像到SD卡

這是相機(jī)的3個(gè)核心任務(wù),在arduino中使用RTOS建立了3個(gè)程序,并用相應(yīng)的按鍵來控制這3個(gè)任務(wù)的啟動(dòng)或者停止。

wKgZPGjJGmmAZjFvAABTNuktL1o59.webp

核心功能之外,相機(jī)還需要一些簡(jiǎn)單的設(shè)置和顯示功能。比如設(shè)置時(shí)間、屏幕亮度、閃光燈開關(guān)、瀏覽相片、藍(lán)牙遙控等。

這里使用裸機(jī)直接寫了一個(gè)簡(jiǎn)單的目錄界面,使用按鍵進(jìn)行控制,在界面中可以進(jìn)行相片瀏覽、屏幕亮度調(diào)整以及藍(lán)牙遙控的開關(guān)等功能。

wKgZO2jJGmqAAE4wAAA-xq6zE0A12.webp

藍(lán)牙控制使用了一個(gè)Ai-M61-32S開發(fā)板來充當(dāng)藍(lán)牙遙控,當(dāng)然手機(jī)搜索對(duì)應(yīng)的廣播名稱并發(fā)送指令Snapshot也是能夠控制的。

BW21-CBV-Kit充當(dāng)主機(jī)設(shè)備掃描并連接特定名稱設(shè)備,Ai-M61-32S作為從機(jī)進(jìn)行廣播。

wKgZPGjJGmqAN01MAAA74D1txFQ19.webp

板子被封在殼子里不能像常規(guī)相機(jī)一樣直接拿取SD卡拷貝照片和視頻,參考官方例程實(shí)現(xiàn)了通過USB來讀取照片和視頻,功能的打開通過按鍵實(shí)現(xiàn)。

wKgZO2jJGmuAWzJIAAA7nOe75f026.webp

電源使用的充放電一體芯片,但是芯片充電的時(shí)候不能關(guān)機(jī),會(huì)導(dǎo)致充電時(shí)相機(jī)還處于運(yùn)行狀態(tài)。

這里使用ADC檢測(cè)電壓,如果檢測(cè)到電壓高于4.2V就進(jìn)入睡眠模式來實(shí)現(xiàn)假關(guān)機(jī)。

wKgZPGjJGmuAf6REAAArAk24Lvo21.webp

BW21-CBV-Kit開發(fā)板的代碼

#include "StreamIO.h"
#include "VideoStream.h"
#include "AudioStream.h"
#include "AudioEncoder.h"
#include "MP4Recording.h"
#include "AmebaFatFS.h"
#include "AmebaST7789.h"
#include "TJpg_Decoder.h"
#include "USBMassStorage.h"  //USB存儲(chǔ)
#include "sys_api.h"         //系統(tǒng)調(diào)用
#include "BLEDevice.h"
#include "PowerMode.h"
// wake up by AON timer :   0
// wake up by AON GPIO  :   1
// wake up by eRtc       :   2
#define WAKEUP_SOURCE 1
#define RETENTION     0
// set wake up AON GPIO pin :   21 / 22
#define WAKUPE_SETTING 21
//BLE相關(guān)
#define UART_SERVICE_UUID      "6E400001-B5A3-F393-E0A9-E50E24DCCA9E"
#define CHARACTERISTIC_UUID_RX "6E400002-B5A3-F393-E0A9-E50E24DCCA9E"
#define CHARACTERISTIC_UUID_TX "6E400003-B5A3-F393-E0A9-E50E24DCCA9E"
#define TARGET_DEVICE_NAME "Ble_cam_control"
#define STRING_BUF_SIZE 100
BLEAdvertData foundDevice;
BLEAdvertData targetDevice;
BLEClient* client;
BLERemoteService* UartService;
BLERemoteCharacteristic* Rx;
BLERemoteCharacteristic* Tx;
TaskHandle_t xBLETaskHandle = NULL;  // 按鍵任務(wù)全局句柄,初始為 NULL
int8_t g_connID = -1; // 存儲(chǔ)連接ID
bool g_bleReady = false; // 標(biāo)志位,表示BLE已準(zhǔn)備就緒
bool g_deviceFound = false; //              //flash
unsigned int photoCount = 0;         //照片編號(hào)
#define PHOTO_COUNTER_OFFSET 0x1E00  // Flash 偏移地址,用于存儲(chǔ)照片計(jì)數(shù)
#define MAX_PHOTO_COUNT 10000        // 防止溢出或異常值(可選)
#define FILENAME "photo"
/* eRtc相關(guān)定義*/
uint32_t rec_addr = 0;
uint32_t rec_len = 0;
uint32_t img_addr = 0;
uint32_t img_len = 0;
bool current_buffer = false;
AmebaFatFS fs;
#define CHANNEL_SCREEN 0
#define CHANNEL_RECORD 1
#define REC_BTN 0   //錄像按鈕
#define SNAP_BTN 4  //模式切換按鈕
CameraSetting configCam;
// Default preset configurations for each video channel:
// Channel 0 : 1920 x 1080 30FPS H264
// Channel 1 : 1280 x 720  30FPS H264
// Default audio preset configurations:
// 0 :  8kHz Mono Analog Mic
// 1 : 16kHz Mono Analog Mic
// 2 :  8kHz Mono Digital PDM Mic
// 3 : 16kHz Mono Digital PDM Mic
bool snapAnamiton = false;            //拍照動(dòng)畫通知
SemaphoreHandle_t xBinarySemaphore;   //等待信號(hào)拍照
SemaphoreHandle_t xBinarySemaphore1;  //等待信號(hào)開始錄像
VideoSetting config1(240, 304, 30, VIDEO_JPEG, 1);
VideoSetting config3(VIDEO_FHD, CAM_FPS, VIDEO_H264_JPEG, 1);
//VideoSetting configV(CHANNEL);
AudioSetting configA(3);
Audio audio;
AAC aac;
MP4Recording mp4;
StreamIO audioStreamer(1, 1);  // 1 Input Audio -> 1 Output AAC
StreamIO avMixStreamer(2, 1);  // 2 Input Video + Audio -> 1 Output MP4
bool isRecording = false;  //是否在錄像
TaskHandle_t displayTaskHandle = NULL;
bool tft_output(int16_t x, int16_t y, uint16_t w, uint16_t h, uint16_t *bitmap) {
  if (y > 240) {
    return 0;
  }
  tft.drawBitmap(x, y, w, h, bitmap);
  return 1;
}
void setup() {
  Serial.begin(115200);
  xBinarySemaphore = xSemaphoreCreateBinary();
  xBinarySemaphore1 = xSemaphoreCreateBinary();
  if (xBinarySemaphore1 == NULL || xBinarySemaphore == NULL) {
    Serial.println("? 信號(hào)量創(chuàng)建失?。?);
    while (1)
      ;  // 停機(jī)
  }
  // pinMode(FLASH_PIN,OUTPUT);
  // digitalWrite(FLASH_PIN,LOW);
  analogWrite(FLASH_PIN, 0);
  if (!fs.begin()) {
    Serial.println("? SD卡初始化失??!");
    while (1)
      ;
  }
  createDirIfNotExists(PHOTO_FOLDER);
  createDirIfNotExists(VIDEO_FOLDER);
  TJpgDec.setSwapBytes(true);
  TJpgDec.setJpgScale(currentScale);
  TJpgDec.setCallback(tft_output);
  Wire1.begin();
  rtc.begin();
  rtc.printTime(Serial);
  rtc.printTime(Serial);
  setCamera();
  tft.begin();
  tft.setRotation(1);
  tft.fillScreen(ST7789_BLACK);
  tft.flush();
  analogWrite(BL_PIN, TFT_BRIGHTNESS);
  // Configure camera video channel with video format information
  xTaskCreate(recordVideo, "record Video", 4096, NULL, 1, NULL);
  xTaskCreate(snapShot, "take photo", 4096, NULL, 1, NULL);
  xTaskCreate(displayTask, "Display Task", 4096, NULL, 1, &displayTaskHandle);
  setupButtons();
}
void loop() {  //進(jìn)入目錄
  if (digitalRead(PIN_BUTTON_SELECT) == HIGH) {
    vTaskDelay(pdMS_TO_TICKS(1000));  // 長按 1 秒進(jìn)入設(shè)置
    if (digitalRead(PIN_BUTTON_SELECT) == HIGH && !setMenuFlag) {
      setMenuFlag = true;
      navigateMainMenu();  // 進(jìn)入設(shè)置
    }
  }
  if (buttonPressed(SNAP_BTN) && !setMenuFlag) {
    xSemaphoreGive(xBinarySemaphore);
  }
  if (buttonPressed(REC_BTN) && !setMenuFlag) {
    xSemaphoreGive(xBinarySemaphore1);
  }
  //進(jìn)入U(xiǎn)SB
  if (buttonPressed(PIN_STORAGE)) {
    usbModeFlag = !usbModeFlag;
  }
  if (usbModeFlag && !usbStart) {
    vTaskSuspend(displayTaskHandle);
    tft.setFontColor(ST7789_WHITE);
    tft.setFontSize(2);
    tft.fillScreen(ST7789_BLACK);
    tft.setCursor(100, 100);
    tft.print("USB MODE");
    tft.flush();
    fs.end();
    USBMS.USBInit();
    USBMS.SDIOInit();
    USBMS.USBStatus();
    USBMS.initializeDisk();
    USBMS.loadUSBMassStorageDriver();
    usbStart = true;
  }
  if (usbStart && !usbModeFlag) {
    sys_reset();  //結(jié)束USB系統(tǒng)重啟
  }
  vTaskDelay(pdMS_TO_TICKS(100));
}
void createDirIfNotExists(const char *dirname) {
  char path[128];
  sprintf(path, "%s%s", fs.getRootPath(), dirname);
  if (!fs.exists(path)) {
    if (fs.mkdir(path)) {
      printf("創(chuàng)建文件夾: "%s"rn", path);
    } else {
      printf("創(chuàng)建文件夾失敗: "%s"rn", path);
    }
  } else {
    printf("文件夾已存在: "%s"rn", path);
  }
}
void recordVideo(void *pvParameters) {
  // Print information
  //printInfo();
  bool modifyTime = false;
  uint8_t y, mo, d, h, mi, se, wd;
  char filename[64] = { 0 };  // 靜態(tài)保存上次的文件名
  while (1) {
    if (xSemaphoreTake(xBinarySemaphore1, pdMS_TO_TICKS(1000)) == pdTRUE) {
      if (!isRecording) {
        modifyTime = true;
        isRecording = true;
        rtc.getTime(&y, &mo, &d, &h, &mi, &se, &wd);
        sprintf(filename, "%s/%04d%02d%02d_%02d%02d%02d", VIDEO_FOLDER, y, mo, d, h, mi, se);
        mp4.setRecordingFileName(filename);
        Serial.println("Recording STARTED");
        mp4.begin();
      } else {
        isRecording = false;
        if (mp4.getRecordingState() == 1) {
          mp4.end();
        }
      }
    }
    if (modifyTime && !isRecording && filename[0] != '') {
      if (mp4.getRecordingState() == 0) {
        Serial.print("修改時(shí)間");
        char pathBuf[128];
        const char *root = fs.getRootPath();  // 通常是 "/" 或 ""
        snprintf(pathBuf, sizeof(pathBuf), "%s%s.mp4", root, filename);
        Serial.print(fs.setLastModTime(pathBuf, 2000 + y, mo, d, h, mi, se));
        modifyTime = false;
      }
    }
    vTaskDelay(pdMS_TO_TICKS(100));
  }
}
void printInfo(void) {
  Serial.println("------------------------------");
  Serial.println("- Summary of Streaming -");
  Serial.println("------------------------------");
  Camera.printInfo();
  Serial.println("- Audio Information -");
  audio.printInfo();
  Serial.println("- MP4 Recording Information -");
  mp4.printInfo();
}
void snapShot(void *pvParameters) {
  FlashMemory.begin(FLASH_MEMORY_APP_BASE, 0x1000);
  photoCount = FlashMemory.readWord(PHOTO_COUNTER_OFFSET);
  Serial.print("讀取到已拍攝照片數(shù)量: ");
  Serial.println(photoCount);
  while (1) {
    if (xSemaphoreTake(xBinarySemaphore, portMAX_DELAY) == pdTRUE) {  //等待拍照通知 xSemaphoreGive(xBinarySemaphore);
      //fs.begin();
      char path[128];
      int n = snprintf(path, sizeof(path), "%s%s/%s%d.jpg",
                       fs.getRootPath(),
                       PHOTO_FOLDER,
                       FILENAME,
                       photoCount);
      if (n < 0) {
        Serial.println("照片路徑生成失??!");
      }
      File file = fs.open(path);
      vTaskDelay(pdMS_TO_TICKS(100));
      uint8_t y, mo, d, h, mi, se, wd;
      rtc.getTime(&y, &mo, &d, &h, &mi, &se, &wd);
      if (LEDON) {
        analogWrite(FLASH_PIN, LED_BRIGHTNESS);
      }
      snapAnamiton = true;
      Camera.getImage(CHANNEL_RECORD, &rec_addr, &rec_len);
      //vTaskSuspend(displayTaskHandle);
      if (LEDON) {
        analogWrite(FLASH_PIN, 0);
      }
      file.write((uint8_t *)rec_addr, rec_len);
      file.close();
      fs.setLastModTime(path, 2000 + y, mo, d, h, mi, se);
      photoCount++;
      FlashMemory.writeWord(PHOTO_COUNTER_OFFSET, photoCount);
      unsigned int checkValue = FlashMemory.readWord(PHOTO_COUNTER_OFFSET);
      if (checkValue == photoCount) {
        Serial.print("? 照片編號(hào)已更新: ");
        Serial.println(photoCount);
      } else {
        Serial.println("? Flash 寫入失敗!");
      }
      //vTaskResume(displayTaskHandle);
    } else {
      vTaskDelay(pdMS_TO_TICKS(100));
    }
  }
}
void displayTask(void *pvParameters) {
  unsigned long previousMillis = 0;  // 上次刷新時(shí)間
  int frameCount = 0;                // 幀計(jì)數(shù)器
  float fps = 0.0f;                  // 存儲(chǔ) FPS
  unsigned long lastVolMeTime = 0;
  int lastVoltagePercent = 0;
  lastVoltagePercent = batteryVoltConvert();  // 先讀一次
  while (1) {
    if (!setMenuFlag) {
      unsigned long currentMillis = millis();  // 獲取當(dāng)前時(shí)間
      Camera.getImage(CHANNEL_SCREEN, &img_addr, &img_len);
      bool next_buffer = !current_buffer;
      tft.setFrontBufferIndex(next_buffer);
      // uint16_t jpgWidth, jpgHeight;
      // // 僅獲取 JPEG 圖像尺寸(不顯示)
      // bool inform = TJpgDec.getJpgSize(&jpgWidth, &jpgHeight, (const uint8_t*)img_addr, img_len);
      // if (!inform) {
      //     printf("JPEG Size: %dx%dn", jpgWidth, jpgHeight);
      // } else {
      //     Serial.println("Failed to parse JPEG header");
      // }
      tft.fillScreen(ST7789_BLACK);
      TJpgDec.drawJpg(0, 0, (uint8_t *)img_addr, img_len);
      // 更新幀計(jì)數(shù)器
      frameCount++;
      if (currentMillis - previousMillis >= 1000) {
        fps = frameCount * 1000.0f / (currentMillis - previousMillis);  // 計(jì)算每秒幀數(shù)
        frameCount = 0;                                                 // 重置幀計(jì)數(shù)器
        previousMillis = currentMillis;                                 // 更新上次刷新時(shí)間
      }
      char fpsStr[32];
      sprintf(fpsStr, "FPS: %.1f", fps);  // 格式化 FPS 顯示
      tft.setCursor(5, 5);
      tft.drawString(fpsStr);  // 在屏幕中央上方顯示 FPS
      //Serial.println(fpsStr);
      if (isRecording) {
        static int reverseTime = 0;
        if (reverseTime < 30) {
          tft.fillCircle(20, 120, 15, ST7789_GREEN);
        }
        reverseTime++;
        if (reverseTime >= 60) {
          reverseTime = 0;
        }
      }
      if (millis() - lastVolMeTime > 30000) {
        lastVoltagePercent = batteryVoltConvert();
        lastVolMeTime = millis();
      }
      drawBattery(280, 5, 20, 10, lastVoltagePercent);
      drawLightningBolt(110, 2, 5, LEDON);
      if (snapAnamiton) {
        snapAnamiton = false;
        tft.drawRect(0, 0, 320, 240, ST7789_WHITE, 10);
      }
      if(!g_bleReady){                                        //連接未連接狀態(tài)切換
        drawBluetoothSymbol(180, 8, 10,ST7789_BLACK,enableBLE); 
      }else{
        drawBluetoothSymbol(180, 8, 10,ST7789_WHITE,enableBLE);  
      }

      tft.flush();
      current_buffer = next_buffer;
    } else {
      vTaskDelay(pdMS_TO_TICKS(100));
    }
    vTaskDelay(pdMS_TO_TICKS(1));
  }
}
bool buttonPressed(int pin) {
  if (digitalRead(pin) == HIGH) {
    vTaskDelay(pdMS_TO_TICKS(20));  // 簡(jiǎn)單消抖
    if (digitalRead(pin) == HIGH) {
      while (digitalRead(pin) == HIGH) {
        vTaskDelay(pdMS_TO_TICKS(10));
      }
      return true;
    }
  }
  return false;
}
/*RTC相關(guān)函數(shù)*/
void setupButtons() {
  pinMode(PIN_BUTTON_UP, INPUT_PULLDOWN);
  pinMode(PIN_BUTTON_DOWN, INPUT_PULLDOWN);
  pinMode(PIN_BUTTON_SELECT, INPUT_PULLDOWN);
  pinMode(REC_BTN, INPUT_PULLDOWN);
  pinMode(SNAP_BTN, INPUT_PULLDOWN);
  pinMode(PIN_STORAGE, INPUT_PULLDOWN);
  pinMode(BTN_PREV, INPUT_PULLDOWN);
  pinMode(BTN_NEXT, INPUT_PULLDOWN);
}
void displayTimeSetting(uint8_t y, uint8_t mo, uint8_t d, uint8_t h, uint8_t mi, uint8_t se, int8_t highlight) {
  tft.fillScreen(ST7789_WHITE);
  tft.setCursor(50, 20);
  tft.setFontColor(ST7789_BLACK);
  tft.setFontSize(2);
  tft.print("Set Time");
  tft.setFontSize(1);
  const char *labels[] = { "Year:", "Month:", "Day:", "Hour:", "Minute:", "Second:" };
  int values[] = { 2000 + y, mo, d, h, mi, se };
  int x_pos = 40, y_pos = 60;
  for (int i = 0; i < 6; i++) {
    tft.setCursor(x_pos, y_pos + i * 20);
    tft.setFontColor(i == highlight ? ST7789_RED : ST7789_BLACK);
    tft.print(labels[i]);
    tft.print(" ");
    tft.print(values[i]);
  }
  tft.setCursor(40, y_pos + 6 * 20);
  tft.setFontColor(ST7789_BLUE);
  tft.print("Press SELECT to save");
  tft.flush();
}
// 菜單項(xiàng)定義
enum MainMenu {
  MENU_PHOTO_BROWSER,
  MENU_BRIGHTNESS_SCREEN,
  MENU_BRIGHTNESS_LED,
  MENU_SET_TIME,
  MENU_EXIT,
  MENU_COUNT
};
// 當(dāng)前選中的主菜單項(xiàng)
int8_t currentMenuIndex = 0;
// ==================== 顯示主菜單 ====================
void displayMainMenu() {
  tft.fillScreen(ST7789_WHITE);
  tft.setCursor(50, 20);
  tft.setFontColor(ST7789_BLACK);
  tft.setFontSize(2);
  tft.print("Main Menu");
  tft.setFontSize(1);
  const char *menuItems[] = {
    "Photo Browser",
    "Screen Brightness",
    "LED Brightness",
    "Set Time",
    "Exit Menu"
  };
  int x_pos = 40, y_pos = 60;
  for (int i = 0; i < MENU_COUNT; i++) {
    tft.setCursor(x_pos, y_pos + i * 25);
    tft.setFontColor(i == currentMenuIndex ? ST7789_RED : ST7789_BLACK);
    tft.print("?> ");
    tft.print(menuItems[i]);
  }
  tft.setCursor(40, y_pos + MENU_COUNT * 25);
  tft.setFontColor(ST7789_BLUE);
  tft.print("SELECT to enter");
  tft.flush();
}
// ==================== 主菜單導(dǎo)航與執(zhí)行 ====================
void navigateMainMenu() {
  buttonPressed(PIN_BUTTON_SELECT);
  currentMenuIndex = 0;  // 默認(rèn)選中第一項(xiàng)
  while (true) {
    displayMainMenu();
    if (buttonPressed(PIN_BUTTON_UP)) {
      currentMenuIndex = (currentMenuIndex - 1 + MENU_COUNT) % MENU_COUNT;
    }
    if (buttonPressed(PIN_BUTTON_DOWN)) {
      currentMenuIndex = (currentMenuIndex + 1) % MENU_COUNT;
    }
    if (buttonPressed(PIN_BUTTON_SELECT)) {
      // 根據(jù)選擇進(jìn)入第二級(jí)操作
      switch (currentMenuIndex) {
        case MENU_PHOTO_BROWSER:
          enterPhotoBrowser();  // 你需要實(shí)現(xiàn)這個(gè)函數(shù)
          break;
        case MENU_BRIGHTNESS_SCREEN:
          adjustScreenBrightness();  // 下面提供示例
          break;
        case MENU_BRIGHTNESS_LED:
          adjustLEDBrightness();  // 下面提供示例
          break;
        case MENU_SET_TIME:
          setTimeWithButtons();  // 你已有的函數(shù)
          break;
        case MENU_EXIT:
          tft.setFontColor(ST7789_WHITE);
          tft.setFontSize(1);
          setMenuFlag = false;  // 新增:退出菜單
          return;               // 退出 navigateMainMenu 函數(shù),回到主循環(huán)
      }
    }
    vTaskDelay(pdMS_TO_TICKS(100));
  }
}
void adjustScreenBrightness() {
  uint8_t brightness = getCurrentBrightness();  // 你需要實(shí)現(xiàn):讀取當(dāng)前亮度值(0-255)
  bool exiting = false;
  while (!exiting) {
    tft.fillScreen(ST7789_WHITE);
    tft.setCursor(50, 100);
    tft.setFontColor(ST7789_BLACK);
    tft.setFontSize(2);
    tft.print("Screen Brightness:");
    tft.setCursor(100, 140);
    tft.print(brightness);
    tft.setCursor(40, 180);
    tft.setFontColor(ST7789_BLUE);
    tft.print("UP/DOWN: Adjust");
    tft.setCursor(40, 200);
    tft.print("SELECT: Back");
    tft.flush();
    if (buttonPressed(PIN_BUTTON_UP)) {
      brightness = min(255, brightness + 5);
      setScreenBrightness(brightness);  // 你需要實(shí)現(xiàn)這個(gè)函數(shù)(如通過PWM)
      while (buttonPressed(PIN_BUTTON_UP))
        ;
    }
    if (buttonPressed(PIN_BUTTON_DOWN)) {
      brightness = max(0, brightness - 5);
      setScreenBrightness(brightness);
      while (buttonPressed(PIN_BUTTON_DOWN))
        ;
    }
    if (buttonPressed(PIN_BUTTON_SELECT)) {
      while (buttonPressed(PIN_BUTTON_SELECT))
        ;
      exiting = true;
    }
    vTaskDelay(pdMS_TO_TICKS(50));
  }
}
void adjustLEDBrightness() {
  uint8_t ledBrightness = getCurrentLEDBrightness();  // 讀取當(dāng)前LED亮度
  bool exiting = false;
  uint8_t count = 0;
  while (!exiting) {
    tft.fillScreen(ST7789_WHITE);
    tft.setCursor(50, 100);
    if (count == 0) {
      tft.setFontColor(ST7789_GREEN);
    } else if (count == 1) {
      tft.setFontColor(ST7789_BLACK);
    }
    tft.setFontSize(2);
    tft.print("LED Brightness state:");
    tft.setCursor(100, 140);
    tft.print(ledBrightness);
    tft.setCursor(40, 180);
    tft.setFontColor(ST7789_BLUE);
    tft.print("UP/DOWN: Adjust");
    tft.setCursor(40, 200);
    tft.print("SELECT: Back");
    tft.setCursor(150, 140);
    if (count == 0) {
      tft.setFontColor(ST7789_BLACK);
    } else if (count == 1) {
      tft.setFontColor(ST7789_GREEN);
    }
    if (LEDON) {
      tft.print("ON");
    } else {
      tft.print("OFF");
    }
    tft.flush();
    if (count == 0) {
      if (buttonPressed(PIN_BUTTON_UP)) {
        ledBrightness = min(255, ledBrightness + 5);
        setLEDBrightness(ledBrightness);  // 你需要實(shí)現(xiàn):控制LED(如PWM)
      }
      if (buttonPressed(PIN_BUTTON_DOWN)) {
        ledBrightness = max(0, ledBrightness - 5);
        setLEDBrightness(ledBrightness);
      }
    }
    if (count == 1) {
      if (buttonPressed(PIN_BUTTON_UP)) {
        LEDON = !LEDON;
      }
      if (buttonPressed(PIN_BUTTON_DOWN)) {
        LEDON = !LEDON;
      }
    }
    if (count >= 2) {
      exiting = true;
    }
    if (buttonPressed(PIN_BUTTON_SELECT)) {
      count++;
    }
    vTaskDelay(pdMS_TO_TICKS(50));
  }
}
void enterPhotoBrowser() {
  tft.setFontSize(1);
  tft.setFontColor(ST7789_WHITE);
  tft.fillScreen(ST7789_BLACK);
  tft.flush();
  setScale();
  if (!listJpgFiles(PHOTO_FOLDER)) {
    tft.setCursor(50, 50);
    tft.print("No JPG files found!");
    vTaskDelay(pdMS_TO_TICKS(1000));
    return;
  }
  Serial.print("Found");
  Serial.print(imageCount);
  Serial.println("JPG files.");
  if (imageCount > 0) {
    loadAndDisplayJpg(imageList[currentImageIndex]);
  } else {
    vTaskDelay(pdMS_TO_TICKS(1000));
    return;
  }
  bool selectWasPressed = false;
  unsigned long selectPressStartime = 0;
  const uint16_t LONG_PRESS_THRESHOLD = 1000;
  while (1) {
    bool selectIsPressed = digitalRead(PIN_BUTTON_SELECT) == HIGH;
    if (selectIsPressed && !selectWasPressed) {
      selectWasPressed = true;
      selectPressStartime = millis();
    }
    if (!selectIsPressed && selectWasPressed) {
      selectWasPressed = false;
      unsigned long pressDuration = millis() - selectPressStartime;
    }
    if (selectIsPressed && selectWasPressed) {
      if (millis() - selectPressStartime >= LONG_PRESS_THRESHOLD) {
        buttonPressed(PIN_BUTTON_SELECT);
        resetPictureView();
        resetScale();
        Serial.println("exit photo review");
        return;
      }
    }
      if (buttonPressed(BTN_PREV)) {
        prevImage();
      }
      if (buttonPressed(BTN_NEXT)) {
        nextImage();
      }
      if (buttonPressed(PIN_BUTTON_DOWN)) {
        decreaseScale();
      }
      if (buttonPressed(PIN_BUTTON_UP)) {
        increaseScale();
      }
      bool recButtonIsPressed = digitalRead(REC_BTN) == HIGH;  // 假設(shè)按鈕連接到GND
      static unsigned long recPressStartTime = 0;
      if (recButtonIsPressed) {
        if (recPressStartTime == 0) {
          recPressStartTime = millis();  // 記錄按下開始時(shí)間
        } else if (millis() - recPressStartTime >= 200) {
          // 長按超過閾值,執(zhí)行刪除
          deleteCurrentImage();
          recPressStartTime = 0;  // 重置計(jì)時(shí)器
          continue;               // 跳過本次循環(huán)剩余邏輯
        }
      } else {
        recPressStartTime = 0;  // 如果按鍵釋放,重置計(jì)時(shí)器
      }
    vTaskDelay(pdMS_TO_TICKS(100));
  }
  // 等待用戶按 SELECT 返回
}
void setLEDBrightness(uint8_t ledBrightness) {
  LED_BRIGHTNESS = ledBrightness;
}
uint8_t getCurrentLEDBrightness() {
  return LED_BRIGHTNESS;
}
void setScreenBrightness(uint8_t brightness) {
  TFT_BRIGHTNESS = brightness;
  analogWrite(BL_PIN, TFT_BRIGHTNESS);
}
uint8_t getCurrentBrightness() {
  return TFT_BRIGHTNESS;
}
void setTimeWithButtons() {
  uint8_t y, mo, d, h, mi, se, wd;
  rtc.getTime(&y, &mo, &d, &h, &mi, &se, &wd);
  setTimeState = SET_YEAR;  // 從年份開始
  while (setTimeState < SET_DONE) {
    displayTimeSetting(y, mo, d, h, mi, se, setTimeState);
    if (buttonPressed(PIN_BUTTON_UP)) {
      switch (setTimeState) {
        case SET_YEAR: y = (y + 1) % 100; break;
        case SET_MONTH: mo = (mo % 12) + 1; break;
        case SET_DAY: d = (d % 31) + 1; break;
        case SET_HOUR: h = (h + 1) % 24; break;
        case SET_MINUTE: mi = (mi + 1) % 60; break;
        case SET_SECOND: se = (se + 1) % 60; break;
      }
    }
    if (buttonPressed(PIN_BUTTON_DOWN)) {
      switch (setTimeState) {
        case SET_YEAR: y = (y + 99) % 100; break;  // -1
        case SET_MONTH: mo = ((mo + 10) % 12) + 1; break;
        case SET_DAY: d = ((d + 29) % 31) + 1; break;
        case SET_HOUR: h = (h + 23) % 24; break;
        case SET_MINUTE: mi = (mi + 59) % 60; break;
        case SET_SECOND: se = (se + 59) % 60; break;
      }
    }
    if (buttonPressed(PIN_BUTTON_SELECT)) {
      setTimeState++;  // 進(jìn)入下一項(xiàng)
      if (setTimeState == SET_DONE) {
        break;  // 結(jié)束
      }
    }
    vTaskDelay(pdMS_TO_TICKS(100));  // 防止太快
  }
  rtc.setTimeAutoWeekday(2000 + y, mo, d, h, mi, se);  // 自動(dòng)計(jì)算星期
  // 顯示成功
  tft.fillScreen(ST7789_BLACK);
  tft.flush();
  tft.setCursor(50, 100);
  tft.setFontColor(ST7789_WHITE);
  tft.setFontSize(2);
  tft.print("Time Set!");
  tft.flush();
  vTaskDelay(pdMS_TO_TICKS(1000));
}
// 掃描 JPG 文件
bool listJpgFiles(const char *path) {
  char result_buf[1024];  // 必須足夠大
  char fullPath[128];
  char *p;
  sprintf(fullPath, "%s%s", fs.getRootPath(), path);
  int count = fs.readDir(fullPath, result_buf, sizeof(result_buf));
  if (count != 0) {
    Serial.println("Failed to read directory or it's empty");
    return false;
  }
  imageCount = 0;
  p = result_buf;
  while (strlen(p) > 0) {
    String filename = String(p);
    // 判斷是否為 JPG 文件
    if (filename.endsWith(".jpg") || filename.endsWith(".JPG") || filename.endsWith(".jpeg") || filename.endsWith(".JPEG")) {
      // 復(fù)制到 imageList,注意不要超出緩沖區(qū)
      if (imageCount < MAX_IMAGES) {
        strlcpy(imageList[imageCount], p, 32);
        Serial.print("Found: ");
        Serial.println(imageList[imageCount]);
        imageCount++;
      } else {
        Serial.println("Max image limit reached!");
        break;
      }
    }
    p += strlen(p) + 1;  // 移動(dòng)到下一個(gè)文件名
  }
  return (imageCount > 0);
}
// 加載并顯示 JPG
bool loadAndDisplayJpg(const char *filename) {
  static char fullPath[128];
  sprintf(fullPath, "%s%s/%s", fs.getRootPath(), PHOTO_FOLDER, filename);
  File file = fs.open(fullPath);
  if (!file) {
    Serial.print("Failed to open ");
    Serial.println(filename);
    return false;
  } else {
    Serial.print("open ");
    Serial.println(filename);
  }
  size_t fileSize = file.size();
  if (fileSize == 0 || fileSize >= MAX_JPG_SIZE) {
    Serial.println("File too large or empty!");
    file.close();
    return false;
  } else {
    Serial.print("fileSize:");
    Serial.println(fileSize);
  }
  size_t bytesRead = file.read(jpgBuffer, fileSize);
  file.close();
  if (bytesRead != fileSize) {
    Serial.println("Read error!");
    return false;
  } else {
    Serial.print("bytesRead:");
    Serial.println(bytesRead);
  }
  // 解碼前先獲取圖片尺寸(不繪制)
  uint16_t width, height;
  bool result = TJpgDec.getJpgSize(&width, &height, jpgBuffer, bytesRead);
  if (result != false) {
    Serial.println("Failed to get JPG size");
    return false;
  }
  currentJpgWidth = width;
  currentJpgHeight = height;
  tft.fillScreen(ST7789_BLACK);
  // 使用 reviewX, reviewY 作為偏移繪制
  TJpgDec.drawJpg(reviewX, reviewY, jpgBuffer, bytesRead);  // 內(nèi)部會(huì)根據(jù) scale 和偏移繪制
  // 顯示信息
  char timeStr[64];
  uint16_t y, mo, d, h, mi, se;
  fs.getLastModTime(fullPath, &y, &mo, &d, &h, &mi, &se);
  snprintf(timeStr, sizeof(timeStr), "%02u-%02u-%02u %02u:%02u:%02u", y, mo, d, h, mi, se);
  tft.setCursor(5, 5);
  tft.print(timeStr);
  tft.setCursor(290, 220);
  tft.print(TFTshowScale().c_str());
  tft.flush();
  return true;
}
// 切換圖片
void prevImage() {
  if (imageCount == 0) return;
  currentImageIndex = (currentImageIndex - 1 + imageCount) % imageCount;
  loadAndDisplayJpg(imageList[currentImageIndex]);
}
void nextImage() {
  if (imageCount == 0) return;
  currentImageIndex = (currentImageIndex + 1) % imageCount;
  loadAndDisplayJpg(imageList[currentImageIndex]);
}
void increaseScale() {
  currentScale = 2 * currentScale;
  if (currentScale > 8) {
    currentScale = 8;
  }
  TJpgDec.setJpgScale(currentScale);
  loadAndDisplayJpg(imageList[currentImageIndex]);
}
void decreaseScale() {
  currentScale = currentScale / 2;
  if (currentScale < 1) {
    currentScale = 1;
  }
  TJpgDec.setJpgScale(currentScale);
  loadAndDisplayJpg(imageList[currentImageIndex]);
}
String TFTshowScale() {
  switch (currentScale) {
    case 1: return "4X";
    case 2: return "2X";
    case 4: return "1X";
    case 8: return "1/2X";
    default: return "?X";  // 必須有 default
  }
}
void setCamera() {
  //config3.setRotation(1);//右轉(zhuǎn)90度
  //config3.setJpegQuality(9);
  config1.setRotation(1);  //右轉(zhuǎn)90度
  config1.setJpegQuality(7);
  //config3.setBitrate(50 * 1024 * 1024);//更改錄像碼率
  Camera.configVideoChannel(CHANNEL_RECORD, config3);
  Camera.configVideoChannel(CHANNEL_SCREEN, config1);
  Camera.videoInit(CHANNEL_RECORD);
  Camera.videoInit(CHANNEL_SCREEN);
  // Configure audio peripheral for audio data output
  audio.configAudio(configA);
  audio.begin();
  // Configure AAC audio encoder
  aac.configAudio(configA);
  aac.begin();
  // Configure MP4 with identical video format information
  // Configure MP4 recording settings
  mp4.configVideo(config3);
  mp4.configAudio(configA, CODEC_AAC);
  mp4.setRecordingDuration(600);
  mp4.setRecordingFileCount(1);
  //mp4.setRecordingFileName("TestRecordingAudioVideo");
  // Configure StreamIO object to stream data from audio channel to AAC encoder
  audioStreamer.registerInput(audio);
  audioStreamer.registerOutput(aac);
  if (audioStreamer.begin() != 0) {
    Serial.println("StreamIO link start failed");
  }
  // Configure StreamIO object to stream data from video channel and AAC encoder to MP4 recording
  avMixStreamer.registerInput1(Camera.getStream(CHANNEL_RECORD));
  avMixStreamer.registerInput2(aac);
  avMixStreamer.registerOutput(mp4);
  if (avMixStreamer.begin() != 0) {
    Serial.println("StreamIO link start failed");
  }
  // Start data stream from video channel
  Camera.channelBegin(CHANNEL_RECORD);
  Camera.channelBegin(CHANNEL_SCREEN);
  configCam.setContrast(45);  //降低對(duì)比度
  // Start recording MP4 data to SD card
}
void resetPictureView() {
  reviewX = 0;
  reviewY = 0;
}
void resetScale() {
  currentScale = 1;
  TJpgDec.setJpgScale(currentScale);
}
void setScale() {
  currentScale = 4;
  TJpgDec.setJpgScale(currentScale);
}
void deleteCurrentImage() {
  if (imageCount == 0) return;
  // 構(gòu)建完整路徑
  char fullPath[128];
  sprintf(fullPath, "%s/%s", PHOTO_FOLDER, imageList[currentImageIndex]);
  // 顯示確認(rèn)提示(可選)
  tft.fillRectangle(0, 100, 320, 60, ST7789_BLACK);
  tft.setCursor(10, 110);
  tft.print("Delete this photo?");
  tft.setCursor(10, 130);
  tft.print("Hold REC to confirm");
  tft.setCursor(10, 150);
  tft.print("or release to cancel");
  tft.flush();
  // 等待用戶確認(rèn)
  unsigned long start = millis();
  while (millis() - start < 3000) {  // 5秒超時(shí)
    vTaskDelay(pdMS_TO_TICKS(2000));
    if (digitalRead(REC_BTN) == HIGH) {  // 假設(shè)按鈕連接到GND
      // 用戶繼續(xù)長按確認(rèn)刪除
      if (fs.remove(fullPath)) {
        Serial.println("Deleted: ");
        Serial.println(imageList[currentImageIndex]);
        // 從內(nèi)存列表中移除
        for (int i = currentImageIndex; i < imageCount - 1; i++) {
          strcpy(imageList[i], imageList[i + 1]);
        }
        imageCount--;
        currentImageIndex = min(currentImageIndex, imageCount - 1);  // 更新currentImageIndex
        // 自動(dòng)加載新圖片
        if (imageCount > 0) {
          loadAndDisplayJpg(imageList[currentImageIndex]);
        } else {
          tft.fillScreen(ST7789_BLACK);
          tft.setCursor(50, 50);
          tft.print("No images left.");
          tft.flush();
          vTaskDelay(pdMS_TO_TICKS(1000));
        }
      } else {
        tft.setCursor(10, 170);
        tft.print("Delete failed!");
        tft.flush();
        vTaskDelay(pdMS_TO_TICKS(1000));
        loadAndDisplayJpg(imageList[currentImageIndex]);  // 重新顯示當(dāng)前圖
      }
      while (digitalRead(REC_BTN) == HIGH) {
        vTaskDelay(pdMS_TO_TICKS(10));
      }
      return;
    } else {
      // 取消
      loadAndDisplayJpg(imageList[currentImageIndex]);  // 重新顯示當(dāng)前圖
      return;
    }
    vTaskDelay(pdMS_TO_TICKS(50));
  }
  // 超時(shí)取消
  loadAndDisplayJpg(imageList[currentImageIndex]);
}
int batteryVoltConvert() {
  float voltage = vBatRate * analogRead(PIN_VOLTAGE);
  if (voltage < VOLTAGE_BASE) {
    return 0;
  }
  if (voltage > 4.21){
    PowerMode.begin(DEEPSLEEP_MODE, WAKEUP_SOURCE, RETENTION, WAKUPE_SETTING);
    Serial.print("Enter DeepSleep Mode");
    tft.fillScreen(ST7789_BLACK);
    tft.setFontSize(2);
    tft.setCursor(20, 140);
    tft.print("Camera will Enter DeepSleep Mode");
    tft.flush();
    delay(2000);
    PowerMode.start();
  }
  int voltagePercent = (voltage - VOLTAGE_BASE) * 100;
  return voltagePercent;
}
void drawBattery(int x, int y, int width, int height, int level) {
  // 繪制電池外框
  uint16_t color;
  if (level < 30) {
    color = ST7789_RED;
  } else {
    color = ST7789_BLUE;
  }
  tft.drawRect(x, y, width + 2, height, ST7789_WHITE);
  tft.fillRectangle(x + width + 2, y + height / 4, 4, height / 2, ST7789_WHITE);  // 電池正極頭
  // 根據(jù)電量level計(jì)算需要填充的線條數(shù)量
  int lines = map(level, 0, 100, 0, height / (height / 10));  // 將電量映射到線條數(shù)
  for (int i = 0; i < lines; i++) {
    tft.drawFastVLine(x + 2 + i * 2, y + 2, height - 4, color);  // 豎線,留邊距
  }
}
void drawLightningBolt(int x, int y, int size, bool on) {
    // 定義閃電標(biāo)志的各個(gè)點(diǎn)
     // 定義第一個(gè)三角形的頂點(diǎn)坐標(biāo)(閃電的上部)
    int x0_1 = x;  // 右上角x坐標(biāo)
    int y0_1 = y;  // 右上角y坐標(biāo)
    int x1_1 = x0_1-size;  // 左下角x坐標(biāo)
    int y1_1 = size+y0_1;  // 左下角y坐標(biāo)
    int x2_1 = x0_1;  // 底部x坐標(biāo)
    int y2_1 = y1_1;  // 底部y坐標(biāo)
    // 定義第二個(gè)三角形的頂點(diǎn)坐標(biāo)(閃電的下部)
    int x0_2 = x+1;  // 上部x坐標(biāo)108
    int y0_2 = y+size+1;  // 上部y坐標(biāo)13
    int x1_2 = x0_2+1;  // 左下角x坐標(biāo)108
    int y1_2 = y0_2+size; // 左下角y坐標(biāo)23
    int x2_2 = x0_2+size;   // 右上角x坐標(biāo)118
    int y2_2 = y0_2; // 右上角y坐標(biāo)13
    tft.fillTriangle(x0_1, y0_1, x1_1, y1_1, x2_1, y2_1, ST7789_WHITE);
    tft.fillTriangle(x0_2, y0_2, x1_2, y1_2, x2_2, y2_2, ST7789_WHITE);
    if(!on){
      tft.drawCircle(x0_2, y0_2, size+1, ST7789_GREEN);
      tft.drawFastHLine(x1_1, y1_1, size*2+1, ST7789_GREEN);
    }
}
void setBLEcontrol(){
  bool exiting = false;
  bool currentBLEState = enableBLE;
  while (!exiting) {
    tft.fillScreen(ST7789_WHITE);
    tft.setCursor(50, 100);
    tft.setFontColor(ST7789_BLACK);
    tft.setFontSize(2);
    tft.print("BLE Setting:");
    tft.setCursor(100, 140);
    if(currentBLEState){
      tft.print("ON");
    }else{
      tft.print("OFF");
    } 
    tft.setCursor(40, 180);
    tft.setFontColor(ST7789_BLUE);
    tft.print("UP/DOWN: Adjust");
    tft.setCursor(40, 200);
    tft.print("SELECT: Back");
    tft.flush();
    if (buttonPressed(PIN_BUTTON_UP)) {
      currentBLEState = !currentBLEState;
    }
    if (buttonPressed(PIN_BUTTON_DOWN)) {
      currentBLEState = !currentBLEState;
    }
    if (buttonPressed(PIN_BUTTON_SELECT)) {
      exiting = true;
    }
    vTaskDelay(pdMS_TO_TICKS(50));
  }
  if(enableBLE != currentBLEState){
      enableBLE = currentBLEState;
    if(!BLETaskState){
      if(xBLETaskHandle == NULL){
        xTaskCreate(scanAndConnectTask, "ScanConnect", 4096, NULL, 2, &xBLETaskHandle);
      }
      BLETaskState = true;
    }
  }
}
void scanCB(T_LE_CB_DATA* p_data) { //BLE掃描回調(diào)函數(shù)
    foundDevice.parseScanInfo(p_data);
    if (foundDevice.hasName()) {
        if (foundDevice.getName() == TARGET_DEVICE_NAME) {
            Serial.print("Found BLE Device at address ");
            Serial.println(foundDevice.getAddr().str());
            targetDevice = foundDevice;
            g_deviceFound = true; // getUUID().str());
    Serial.print("Received string: ");
    Serial.println(String(msg));
    if (strcmp(msg, "Snapshot") == 0) { 
      Serial.println("shot");
      if(!setMenuFlag && enableBLE){
        xSemaphoreGive(xBinarySemaphore);
      } 
    }
}
void scanAndConnectTask(void *pvParameters) {
    Serial.println("scanAndConnectTask started");
    // 初始化BLE
    BLE.init();
    BLE.setScanCallback(scanCB);
    BLE.beginCentral(1);
    while (1) {
        // 重置狀態(tài)
      if(enableBLE){
        g_bleReady = false;
        client = nullptr;
        UartService = nullptr;
        Rx = nullptr;
        Tx = nullptr;
        g_connID = -1;
        Serial.println("Starting BLE scan...");
        BLE.configScan()->startScan(2000); // 掃描2秒
        // 等待找到目標(biāo)設(shè)備
        while (!g_deviceFound) {
            vTaskDelay(pdMS_TO_TICKS(100)); // 等待100ms
            static uint32_t scanStartTime = millis();
            if (millis() - scanStartTime > 5000) { // 掃描超過5秒未找到
                Serial.println("Scan timeout. Retrying...");
                break;
            }
        }
        if (!g_deviceFound) {
            Serial.println("Device not found in this scan cycle. Retrying...");
            vTaskDelay(pdMS_TO_TICKS(100));
            continue;
        }

        // 連接設(shè)備
        if (BLE.configConnection()->connect(targetDevice, 2000) == 0) {
            g_connID = BLE.configConnection()->getConnId(targetDevice);
            if (g_connID >= 0 && BLE.connected(g_connID)) {
                Serial.println("BLE Connected successfully!");

                // 配置客戶端
                BLE.configClient();
                client = BLE.addClient(g_connID);
                if (client == nullptr) {
                    Serial.println("Failed to create BLE client");
                    continue; // 重新開始循環(huán)
                }
                Serial.println("Discovering services...");
                client->discoverServices();

                // 等待服務(wù)發(fā)現(xiàn)完成
                while (!client->discoveryDone()) {
                    Serial.print(".");
                    vTaskDelay(pdMS_TO_TICKS(1000));
                }
                Serial.println("nService discovery completed.");
                // 獲取UART服務(wù)和特征
                UartService = client->getService(UART_SERVICE_UUID);
                if (UartService != nullptr) {
                    Tx = UartService->getCharacteristic(CHARACTERISTIC_UUID_TX);
                    if (Tx != nullptr) {
                        Serial.println("TX characteristic found");
                        Tx->setBufferLen(STRING_BUF_SIZE);
                        Tx->setNotifyCallback(notificationCB);
                        Tx->enableNotifyIndicate(); // 啟用通知
                    } else {
                        Serial.println("TX characteristic not found!");
                    }
                    Rx = UartService->getCharacteristic(CHARACTERISTIC_UUID_RX);
                    if (Rx != nullptr) {
                        Serial.println("RX characteristic found");
                        Rx->setBufferLen(STRING_BUF_SIZE);
                    } else {
                        Serial.println("RX characteristic not found!");
                    }
                } else {
                    Serial.println("UART Service not found!");
                }
                // 如果所有關(guān)鍵組件都就緒,設(shè)置標(biāo)志
                if (Tx != nullptr && Rx != nullptr) {
                    g_bleReady = true;
                    Serial.println("BLE UART ready. Tasks can now operate.");
                } else {
                    Serial.println("BLE setup incomplete. Reconnecting...");
                }
            } else {
                Serial.println("Connection failed or not established.");
            }
        } else {
            Serial.println("Connect command failed.");
        }
        // 如果連接失敗或斷開,等待一段時(shí)間后重試
        if (!g_bleReady) {
            Serial.println("Retrying connection in 5 seconds...");
            vTaskDelay(pdMS_TO_TICKS(5000));
        } else {
            // 連接成功,但需要監(jiān)聽斷開事件(簡(jiǎn)化處理:如果斷開,外層循環(huán)會(huì)重試)
            // 在實(shí)際應(yīng)用中,應(yīng)監(jiān)聽BLE斷開事件
            while (g_bleReady && BLE.connected(g_connID)) {
                vTaskDelay(pdMS_TO_TICKS(100)); // 保持任務(wù)運(yùn)行,監(jiān)聽通知
            }
            Serial.println("BLE disconnected. Reconnecting...");
            // 當(dāng)連接斷開時(shí),g_bleReady 會(huì)在下次循環(huán)開始時(shí)被重置
        }
      }
      vTaskDelay(pdMS_TO_TICKS(100));
    }
}
void drawBluetoothSymbol(int16_t centerX, int16_t centerY, int16_t size, uint16_t color, bool enable) {
    // 計(jì)算藍(lán)牙標(biāo)志的各點(diǎn)坐標(biāo)
    float offset = sin(45)*sin(45)*size/2;
    uint16_t x1 = centerX - offset;
    uint16_t y1 = centerY -size/2 +offset;//左上角
    uint16_t x2 = centerX + offset;
    uint16_t y2 = centerY +offset;//右下角
    uint16_t x3 = centerX + offset;
    uint16_t y3 = centerY -size/2 +offset;//右上角
    uint16_t x4 = centerX - offset;
    uint16_t y4 = centerY + offset;//左下角
    if(enable){
      tft.fillCircle(centerX, centerY, size-2, ST7789_RED);
    }else{
      tft.fillCircle(centerX, centerY, size-2, ST7789_GREEN);
    }

    tft.drawFastVLine(centerX, centerY-size/2, size,color);
    tft.drawLine(x1, y1, x2, y2, color);
    tft.drawLine(x3, y3, x4, y4, color);
    tft.drawLine(centerX, centerY-size/2, x3, y3, color);
    tft.drawLine(centerX, centerY+size/2, x2, y2, color);
    // 繪制右上部分
}

Ai-M61-32S開發(fā)板的代碼

#include "shell.h"
#include 
#include "task.h"
#include "board.h"
#include "bluetooth.h"
#include "conn.h"
#include "conn_internal.h"
#if defined(BL702) || defined(BL602)
#include "ble_lib_api.h"
#elif defined(BL616)
#include "btble_lib_api.h"
#include "bl616_glb.h"
#include "rfparam_adapter.h"
#elif defined(BL808)
#include "btble_lib_api.h"
#include "bl808_glb.h"
#endif
#include "gatt.h"
#include "ble_tp_svc.h"
#include "hci_driver.h"
#include "hci_core.h"
#include "bflb_gpio.h" //包含GPIO庫文件
static struct bflb_device_s *uart0;
struct bflb_device_s *gpio;
extern void shell_init_with_task(struct bflb_device_s *shell);
void led_task(void *pvParameters); 
void init_LED_GPIO(void);
#define BUTTON_PIN    GPIO_PIN_2
#define GREEN_LED_PIN      GPIO_PIN_14
#define BLUE_LED_PIN     GPIO_PIN_15
#define RED_LED_PIN     GPIO_PIN_12
TaskHandle_t xLedTaskHandle = NULL;  // 按鍵任務(wù)全局句柄,初始為 NULL
bool ble_connected_flag = false; // 按鍵任務(wù)運(yùn)行標(biāo)志
// 定義 NUS 服務(wù) UUID
#define BT_UUID_NUS_SERVICE 
    BT_UUID_DECLARE_128(BT_UUID_128_ENCODE(0x6E400001, 0xB5A3, 0xF393, 0xE0A9, 0xE50E24DCCA9E))
// 定義 TX 特征 UUID(設(shè)備發(fā)送數(shù)據(jù),我們接收)
#define BT_UUID_NUS_TX 
    BT_UUID_DECLARE_128(BT_UUID_128_ENCODE(0x6E400003, 0xB5A3, 0xF393, 0xE0A9, 0xE50E24DCCA9E))
// 定義 RX 特征 UUID(我們發(fā)送數(shù)據(jù),設(shè)備接收)
#define BT_UUID_NUS_RX 
    BT_UUID_DECLARE_128(BT_UUID_128_ENCODE(0x6E400002, 0xB5A3, 0xF393, 0xE0A9, 0xE50E24DCCA9E))
// 聲明 Characteristic 值存儲(chǔ)空間
static uint8_t custom_rx_value[20] = {0}; // 接收緩沖區(qū)
static uint8_t custom_tx_value[20] = {0}; // 發(fā)送緩沖區(qū)
static uint16_t custom_rx_len = 0;
static uint16_t custom_tx_len = 0;
// 前向聲明回調(diào)函數(shù)
static ssize_t custom_char_rx_write(struct bt_conn *conn,
                                    const struct bt_gatt_attr *attr,
                                    const void *buf, uint16_t len,
                                    uint16_t offset, uint8_t flags);
// 函數(shù)聲明
int ble_send_data(const uint8_t *data, uint16_t len);
// 定義 GATT 屬性表
// 回調(diào)函數(shù):當(dāng) CCCD 被修改時(shí)調(diào)用
static void custom_ccc_changed(const struct bt_gatt_attr *attr, uint16_t value)
{
    ARG_UNUSED(attr);
    bool enabled = (value == BT_GATT_CCC_NOTIFY);
    printf("TX notifications %sn", enabled ? "ON" : "OFF");
}
static ssize_t custom_char_tx_read(struct bt_conn *conn,
                                   const struct bt_gatt_attr *attr,
                                   void *buf, uint16_t len,
                                   uint16_t offset)
{
    const char *value = "Hello from BL616!";  // 你想返回的數(shù)據(jù)
    uint16_t value_len = strlen(value);
    // 使用 GATT 工具函數(shù)安全返回?cái)?shù)據(jù)
    return bt_gatt_attr_read(conn, attr, buf, len, offset, value, value_len);
}
static ssize_t custom_char_rx_read(struct bt_conn *conn,
                                   const struct bt_gatt_attr *attr,
                                   void *buf, uint16_t len,
                                   uint16_t offset)
{
    return bt_gatt_attr_read(conn, attr, buf, len, offset,
                             custom_rx_value, custom_rx_len);
}
static struct bt_gatt_attr custom_service_attrs[] = {
    // 1. 服務(wù)聲明 (Service Declaration)
    BT_GATT_PRIMARY_SERVICE(BT_UUID_NUS_SERVICE),
    // 2. RX Characteristic: 手機(jī) → 設(shè)備 (寫入)
    BT_GATT_CHARACTERISTIC(BT_UUID_NUS_RX,
                           BT_GATT_CHRC_WRITE | BT_GATT_CHRC_WRITE_WITHOUT_RESP,
                           BT_GATT_PERM_WRITE | BT_GATT_PERM_READ,
                           custom_char_rx_read,  // 可選:允許手機(jī)讀回
                           custom_char_rx_write,
                           NULL),
    // 3. TX Characteristic: 設(shè)備 → 手機(jī) (通知)
    BT_GATT_CHARACTERISTIC(BT_UUID_NUS_TX,
                           BT_GATT_CHRC_NOTIFY,
                           BT_GATT_PERM_READ,
                           custom_char_tx_read,  // 允許手機(jī)讀取當(dāng)前值
                           NULL,
                           NULL),
    // 4. CCCD: 客戶端特征配置描述符 (必須緊跟在 TX 特征后)
    BT_GATT_CCC(custom_ccc_changed, BT_GATT_PERM_READ | BT_GATT_PERM_WRITE),
};
// 定義 GATT 服務(wù)
static struct bt_gatt_service custom_service =
    BT_GATT_SERVICE(custom_service_attrs);
// 保存連接句柄,用于 notify
static struct bt_conn *current_conn = NULL;
// 寫回調(diào)函數(shù)實(shí)現(xiàn)
static ssize_t custom_char_rx_write(struct bt_conn *conn,
                                    const struct bt_gatt_attr *attr,
                                    const void *buf, uint16_t len,
                                    uint16_t offset, uint8_t flags)
{
    if (offset + len > sizeof(custom_rx_value)) {
        return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN);
    }
    // 拷貝數(shù)據(jù)
    memcpy(custom_rx_value + offset, buf, len);
    custom_rx_len = offset + len;
    printf("Received from phone: %.*sn", custom_rx_len, custom_rx_value);
    // 回顯給手機(jī)(可選)
    if (current_conn) {
        memcpy(custom_tx_value, custom_rx_value, custom_rx_len);
        custom_tx_len = custom_rx_len;
        bt_gatt_notify(current_conn, &custom_service.attrs[3], custom_tx_value, custom_tx_len);
    }
    return len;
}
static int btblecontroller_em_config(void)
{
    extern uint8_t __LD_CONFIG_EM_SEL;
    volatile uint32_t em_size;
    em_size = (uint32_t)&__LD_CONFIG_EM_SEL;
    if (em_size == 0) {
        GLB_Set_EM_Sel(GLB_WRAM160KB_EM0KB);
    } else if (em_size == 32*1024) {
        GLB_Set_EM_Sel(GLB_WRAM128KB_EM32KB);
    } else if (em_size == 64*1024) {
        GLB_Set_EM_Sel(GLB_WRAM96KB_EM64KB);
    } else {
        GLB_Set_EM_Sel(GLB_WRAM96KB_EM64KB);
    }
    return 0;
}
static void ble_connected(struct bt_conn *conn, u8_t err)
{
    if(err || conn->type != BT_CONN_TYPE_LE)
    {
        return;
    }
    printf("%s",__func__);
    bflb_gpio_set(gpio, GREEN_LED_PIN);   // 點(diǎn)亮綠色 LED
    bflb_gpio_reset(gpio, RED_LED_PIN); // 熄滅紅色LED
    current_conn = bt_conn_ref(conn); // 保存連接句柄
    ble_connected_flag = true;
}
static void ble_disconnected(struct bt_conn *conn, u8_t reason)
{ 
    int ret;
    if(conn->type != BT_CONN_TYPE_LE)
    {
        return;
    }
    printf("%s",__func__);
    bflb_gpio_reset(gpio, GREEN_LED_PIN);   // 點(diǎn)亮綠色 LED
    bflb_gpio_set(gpio, RED_LED_PIN); // 熄滅紅色LED
    ble_connected_flag = false;
    // enable adv
    if (current_conn) {
        bt_conn_unref(current_conn);
        current_conn = NULL;
    }
    ret = set_adv_enable(true);
    if(ret) {
        printf("Restart adv fail. rn");
    }
}
static struct bt_conn_cb ble_conn_callbacks = {
    .connected  =   ble_connected,
    .disconnected   =   ble_disconnected,
};
static void ble_start_adv(void)
{
    struct bt_le_adv_param param;
    int err = -1;
    struct bt_data adv_data[1] = {
        BT_DATA_BYTES(BT_DATA_FLAGS, BT_LE_AD_NO_BREDR | BT_LE_AD_GENERAL)
    };
    struct bt_data adv_rsp[1] = {
        BT_DATA_BYTES(BT_DATA_MANUFACTURER_DATA, "BL616")
    };
    memset(?m, 0, sizeof(param));
    // Set advertise interval
    param.interval_min = BT_GAP_ADV_FAST_INT_MIN_2;
    param.interval_max = BT_GAP_ADV_FAST_INT_MAX_2;
    /*Get adv type, 0:adv_ind,  1:adv_scan_ind, 2:adv_nonconn_ind 3: adv_direct_ind*/
    param.options = (BT_LE_ADV_OPT_CONNECTABLE | BT_LE_ADV_OPT_USE_NAME | BT_LE_ADV_OPT_ONE_TIME); 
    err = bt_le_adv_start(?m, adv_data, ARRAY_SIZE(adv_data), adv_rsp, ARRAY_SIZE(adv_rsp));
    if(err){
        printf("Failed to start advertising (err %d) rn", err);
    }
    printf("Start advertising success.rn");
}
void bt_enable_cb(int err)
{
    if (!err) {
        bt_addr_le_t bt_addr;
        bt_get_local_public_address(&bt_addr);
        printf("BD_ADDR:(MSB)%02x:%02x:%02x:%02x:%02x:%02x(LSB) rn",
            bt_addr.a.val[5], bt_addr.a.val[4], bt_addr.a.val[3], bt_addr.a.val[2], bt_addr.a.val[1], bt_addr.a.val[0]);
        bt_conn_cb_register(&ble_conn_callbacks);
        bt_set_name("Ble_cam_control");
        bt_gatt_service_register(&custom_service); // 注冊(cè)自定義服務(wù)
        //ble_tp_init();

        // start advertising
        ble_start_adv();
    }
}
int main(void)
{
    board_init();
    init_LED_GPIO();
    configASSERT((configMAX_PRIORITIES > 4));

    uart0 = bflb_device_get_by_name("uart0");
    shell_init_with_task(uart0);
    /* set ble controller EM Size */
    btblecontroller_em_config();
#if defined(BL616)
    /* Init rf */
    if (0 != rfparam_init(0, NULL, 0)) {
        printf("PHY RF init failed!rn");
        return 0;
    }
#endif
    // Initialize BLE controller
    #if defined(BL702) || defined(BL602)
    ble_controller_init(configMAX_PRIORITIES - 1);
    #else
    btble_controller_init(configMAX_PRIORITIES - 1);
    #endif
    // Initialize BLE Host stack
    hci_driver_init();
    bt_enable(bt_enable_cb);
    xTaskCreate(led_task, "LED_Task", 512, NULL, configMAX_PRIORITIES - 2, &xLedTaskHandle);
    vTaskStartScheduler();
    while (1) {
    }
}
void init_LED_GPIO(void)
{gpio = bflb_device_get_by_name("gpio");
bflb_gpio_init(gpio, GREEN_LED_PIN, GPIO_OUTPUT | GPIO_PULLUP | GPIO_SMT_EN | GPIO_DRV_0);
bflb_gpio_init(gpio, BLUE_LED_PIN, GPIO_OUTPUT | GPIO_PULLUP | GPIO_SMT_EN | GPIO_DRV_0);
bflb_gpio_init(gpio, RED_LED_PIN, GPIO_OUTPUT | GPIO_PULLUP | GPIO_SMT_EN | GPIO_DRV_0);
bflb_gpio_init(gpio, BUTTON_PIN, GPIO_INPUT | GPIO_PULLDOWN | GPIO_SMT_EN | GPIO_DRV_0);
}
void led_task(void *pvParameters)
{
    uint8_t button_last_state = 0;  // 上一次按鍵狀態(tài)(0:釋放,1:按下)
    while (1) {
        uint8_t button_current = bflb_gpio_read(gpio, BUTTON_PIN);
        if(!ble_connected_flag) {
            // 如果未連接藍(lán)牙,則保持紅色LED點(diǎn)亮,綠色和藍(lán)色LED熄滅
            bflb_gpio_set(gpio, RED_LED_PIN);   // 點(diǎn)亮紅色 LED
            bflb_gpio_reset(gpio, GREEN_LED_PIN); // 熄滅綠色LED
            bflb_gpio_reset(gpio, BLUE_LED_PIN); // 熄滅藍(lán)色LED
            vTaskDelay(100 / portTICK_PERIOD_MS); // 延時(shí),避免CPU占用過高
            continue; // 跳過按鍵檢測(cè),繼續(xù)循環(huán)
        }
        // 檢測(cè)從“按下”到“釋放”的跳變(上升沿)
        if (button_last_state == 1 && button_current == 0) {
            // 消抖:確認(rèn)釋放狀態(tài)
            vTaskDelay(10 / portTICK_PERIOD_MS);
            if (bflb_gpio_read(gpio, BUTTON_PIN) == 0) {
                // 確認(rèn)按鍵已釋放,觸發(fā)動(dòng)作
                printf("Button Released! Turn on Green LED.n");
                bflb_gpio_set(gpio, GREEN_LED_PIN);   // 點(diǎn)亮綠色 LED
                bflb_gpio_reset(gpio, BLUE_LED_PIN); // 熄滅藍(lán)色LED
            }
        }else if (button_last_state == 0 && button_current == 1) {
            // 消抖:確認(rèn)按下狀態(tài)
            vTaskDelay(10 / portTICK_PERIOD_MS);
            if (bflb_gpio_read(gpio, BUTTON_PIN) == 1) {
                // 確認(rèn)按鍵已按下,觸發(fā)動(dòng)作
                printf("Button Pressed! Turn on blue LED.n");
                bflb_gpio_set(gpio, BLUE_LED_PIN);   // 點(diǎn)亮藍(lán)色LED
                bflb_gpio_reset(gpio, GREEN_LED_PIN); // 熄滅綠色LED
                ble_send_data((uint8_t*)"Snapshot", 8); // 發(fā)送BLE數(shù)據(jù)
            }
        }
        // 更新按鍵狀態(tài)
        button_last_state = button_current;
        // 主循環(huán)延時(shí),避免 CPU 占用過高
        vTaskDelay(20 / portTICK_PERIOD_MS);
    }
}
int ble_send_data(const uint8_t *data, uint16_t len)
{
    if (!current_conn || !data || len == 0 || len > sizeof(custom_tx_value)) {
        return -1;
    }
    memcpy(custom_tx_value, data, len);
    custom_tx_len = len;
    // 發(fā)送通知
    int err = bt_gatt_notify(current_conn, &custom_service.attrs[3], custom_tx_value, custom_tx_len);
    if (err) {
        printf("Notify failed: %dn", err);
        return -1;
    }
    printf("Sent to phone: %.*sn", len, data);
    return 0;
}

視頻演示

https://www.bilibili.com/video/BV1rFYPzyE8e/?spm_id_from=888.80997.embed_other.whitelist&t=139.890003&bvid=BV1rFYPzyE8e&vd_source=54c5db21948db2378659b7e8e42bafbf

wKgZO2jJGmuALIWXAAA1nOJ8xRY57.webp


審核編輯 黃宇

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場(chǎng)。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請(qǐng)聯(lián)系本站處理。 舉報(bào)投訴
  • AI
    AI
    +關(guān)注

    關(guān)注

    88

    文章

    36980

    瀏覽量

    289840
  • 開發(fā)板
    +關(guān)注

    關(guān)注

    25

    文章

    5990

    瀏覽量

    109962
  • Arduino
    +關(guān)注

    關(guān)注

    190

    文章

    6508

    瀏覽量

    194690
收藏 人收藏
加入交流群
微信小助手二維碼

掃碼添加小助手

加入工程師交流群

    評(píng)論

    相關(guān)推薦
    熱點(diǎn)推薦

    基于開源鴻蒙的RKNN人臉識(shí)別應(yīng)用案例

    本期內(nèi)容由AI Model SIG提供,介紹了在開源鴻蒙中,基于RK3588的RKNN人臉識(shí)別應(yīng)用開發(fā)全流程。
    的頭像 發(fā)表于 09-03 09:55 ?3170次閱讀
    基于開源鴻蒙的RKNN<b class='flag-5'>人臉</b><b class='flag-5'>識(shí)別</b>應(yīng)用案例

    如何挑選人臉識(shí)別終端?人臉識(shí)別體機(jī)品牌排行榜

    考慮這些人臉識(shí)別終端的具體應(yīng)用場(chǎng)景在哪里。下面,小編就總結(jié)了2025年最新的人臉識(shí)別體機(jī)品牌排行榜:
    的頭像 發(fā)表于 08-18 10:44 ?1004次閱讀
    如何挑選<b class='flag-5'>人臉</b><b class='flag-5'>識(shí)別</b>終端?<b class='flag-5'>人臉</b><b class='flag-5'>識(shí)別</b><b class='flag-5'>一</b>體機(jī)品牌排行榜

    如何打造個(gè)屬于自己手勢(shì)識(shí)別應(yīng)用

    期小編給大家介紹了和MediaPipe的相遇之路,本期小編將帶著大家起來動(dòng)手,如何打造個(gè)屬于自己
    的頭像 發(fā)表于 07-29 10:12 ?637次閱讀
    如何打造<b class='flag-5'>一</b><b class='flag-5'>個(gè)</b>屬于<b class='flag-5'>自己</b>的<b class='flag-5'>手勢(shì)</b><b class='flag-5'>識(shí)別</b>應(yīng)用

    人臉方向識(shí)別算法

    人臉識(shí)別
    深蕾半導(dǎo)體
    發(fā)布于 :2025年07月22日 09:58:29

    用Wi-Fi藍(lán)牙模組Ai-M62-CBS做一個(gè)電子沙漏

    以下作品由安信可社區(qū)用戶 bzhou830 制作 1. 項(xiàng)目簡(jiǎn)介 本項(xiàng)目通過M62模組設(shè)計(jì)的超級(jí)mini板搭配兩個(gè)8*8點(diǎn)陣,并利用水銀開關(guān)作為重力檢測(cè)部件完成個(gè)電子沙漏。外殼采用3
    的頭像 發(fā)表于 07-08 10:32 ?414次閱讀
    用Wi-Fi藍(lán)牙<b class='flag-5'>模組</b><b class='flag-5'>Ai</b>-M62-CBS<b class='flag-5'>做一個(gè)</b>電子沙漏

    基于LockAI視覺識(shí)別模塊:C++人臉識(shí)別

    = face_system.Predict(input_mat); 作用:FaceRecognitionSystem類中的個(gè)函數(shù),用于實(shí)現(xiàn)人臉識(shí)別。 參數(shù)說明: input_ma
    發(fā)表于 07-01 12:01

    【BPI-CanMV-K230D-Zero開發(fā)板體驗(yàn)】人臉檢測(cè)、手勢(shì)識(shí)別、車牌識(shí)別

    pl.destroy() # 銷毀PipeLine實(shí)例 效果 人臉關(guān)鍵部位 人臉關(guān)鍵部位應(yīng)用是雙模型應(yīng)用,首先對(duì)視頻的每幀圖像進(jìn)行人臉檢測(cè),然后對(duì)檢測(cè)到的每
    發(fā)表于 06-30 20:44

    OBOO鷗柏丨AI數(shù)字人觸摸屏查詢觸控人臉識(shí)別語音交互體機(jī)上市

    OBOO鷗柏丨AI數(shù)字人觸摸屏查詢觸控人臉識(shí)別語音交互體機(jī)上市分析OBOO鷗柏品牌推出的AI數(shù)字人觸摸屏查詢觸控
    的頭像 發(fā)表于 05-21 20:22 ?561次閱讀
    OBOO鷗柏丨<b class='flag-5'>AI</b>數(shù)字人觸摸屏查詢觸控<b class='flag-5'>人臉</b><b class='flag-5'>識(shí)別</b>語音交互<b class='flag-5'>一</b>體機(jī)上市

    無需接線!1個(gè)底板可測(cè)試海凌科5款人臉識(shí)別模塊

    一個(gè)人臉識(shí)別模塊就要買不同的測(cè)試底板?試用款新的人臉識(shí)別模塊,每次都要重新接線?海凌科通用型測(cè)試底板FO101解決這
    的頭像 發(fā)表于 05-12 12:06 ?592次閱讀
    無需接線!1<b class='flag-5'>個(gè)</b>底板可測(cè)試海凌科5款<b class='flag-5'>人臉</b><b class='flag-5'>識(shí)別</b>模塊

    人臉識(shí)別門禁終端的般故障排查方法

    天波作為資深的智能硬件廠商,每年出廠落地應(yīng)用的產(chǎn)品數(shù)以萬計(jì)。其中,各種人臉識(shí)別門禁考勤體機(jī)、刷臉核驗(yàn)體機(jī)、人臉
    的頭像 發(fā)表于 04-27 10:45 ?1353次閱讀
    <b class='flag-5'>人臉</b><b class='flag-5'>識(shí)別</b>門禁終端的<b class='flag-5'>一</b>般故障排查方法

    《DNESP32S3使用指南-IDF版_V1.6》第五十九章 人臉識(shí)別實(shí)驗(yàn)

    識(shí)別系列相關(guān)技術(shù)。本章,我們使用樂鑫AI庫來實(shí)現(xiàn)人臉識(shí)別功能。本章分為如下幾個(gè)部分:59.1 硬件設(shè)計(jì)59.2 軟件設(shè)計(jì)59.3 下載驗(yàn)
    發(fā)表于 03-26 09:40

    可智能深度學(xué)習(xí)的AI攝像機(jī)模組方案

    、方案簡(jiǎn)介 AI攝像機(jī)模組主要定位為行業(yè)AI攝像機(jī)模組,廣泛應(yīng)用在泛安防行業(yè),實(shí)現(xiàn)人臉
    發(fā)表于 03-21 11:28

    游戲機(jī)與數(shù)碼相機(jī)的理想選擇:HT4088高性能充電芯片

    討論HT4088在高性能便攜式設(shè)備中的應(yīng)用,如游戲機(jī)和數(shù)碼相機(jī),提供快速充電解決方案。
    的頭像 發(fā)表于 03-13 17:24 ?907次閱讀
    游戲機(jī)與<b class='flag-5'>數(shù)碼相機(jī)</b>的理想選擇:HT4088高性能充電芯片

    數(shù)碼相機(jī)D700說明書

    電子發(fā)燒友網(wǎng)站提供《數(shù)碼相機(jī)D700說明書.pdf》資料免費(fèi)下載
    發(fā)表于 03-12 18:04 ?0次下載

    安信可AI人臉識(shí)別方案

    作為神仙世界的高科技,"無接觸式開鎖",人臉識(shí)別技術(shù)也被廣泛應(yīng)用在現(xiàn)代生活中,安信可也有AI人臉識(shí)別方案!
    的頭像 發(fā)表于 02-25 14:39 ?608次閱讀
    安信可<b class='flag-5'>AI</b><b class='flag-5'>人臉</b><b class='flag-5'>識(shí)別</b>方案