2021年9月17日 星期五

ESP32 CAMRA 二維碼(QR Code)辨識之門鎖控制 04-攝像模組之二維碼辨識

網頁最後修改時間:2021/09/17

之前在 ESP32 CAMRA 二維碼(QR Code)辨識之門鎖控制(下面簡稱 ESP32QRDoorLock) 系列的第二篇-週邊裝置的控制中,沒有談論到攝像頭的部分,主要是因為它是整個門鎖控制的主角,值得獨立寫一篇!

這一篇是 ESP32QRDoorLock 系列的第四篇:主要是說明如何使用 ESP32-CAM 辨識二維碼,比對出辨識後的二維碼,是否與預先定義的字串相符合,進而觸發相對應的處理動作。

內容有:


【(00)簡介】

本篇的目的很簡單,就是把二維碼辨識的功能,繼續加入到上一篇 x03_ESP32CamWiFiWatchdog.ino 中,並設定相對應的二維碼辨識後的狀態,做為系統工作流程的狀態轉換依據。


【(01)ESP32-CAM 二維碼辨識】

ESP32QRDoorLock 二維碼辨識的功能來自於函式庫的支援,使用者不需要深入了解二維碼辨識的方法或是撰寫相關的程式碼,只要設定好定期呼叫函式取得辨識的結果即可(這部分採用上一篇所介紹的 FreeRTOS 工作任務)。

到底要怎麼做呢?看下面的說明。


/*--*//**---/*///**---*-*////***--*/*///***----*///--*/*///**--*/*//**--**/*//
* (01-01)函式庫下載與安裝

函式庫的下載以及安裝教學都在下面的兩個連結中,進行下面步驟之前,請完成該函式庫的安裝。

** 函式庫下載:

** 函式庫安裝教學:


/*--*//**---/*///**---*-*////***--*/*///***----*///--*/*///**--*/*//**--**/*//
* (01-02)生成二維碼圖示

在網路上有很多網站提供二維碼圖示的生成服務,除了可自行尋找使用之外,要不就用我用的這個網站:QR碼產生器:免費、彩色、加LOGO (ioi.tw)

這裡要生成的二維碼圖示,主要是開門用的,它代表的文字內容是 Qh@4#I_6+。另外,也生成了文字內容 relay0.0,1 和 relay0.0,0 兩個二維碼的圖示。這幾個圖示後續會用到,請先準備好,不知道怎麼做的,看下面的說明。

打開瀏覽器輸入連結:https://qr.ioi.tw/zh/,依序進行設定:

  • QR碼內容輸入
    • 選擇QR碼內容純文字
    • 原生資料Qh@4#I_6+
  • QR碼設定
    • 檔案名稱:自行命名
    • 按下 "下載QR碼" 按鈕下載檔案

二維碼圖示還可以加上 LOGO,例如加上一行文字:

  • LOGO模式設定
    • 選擇LOGO模式文字-方塊
    • 選擇LOGO模式unlock


/*--*//**---/*///**---*-*////***--*/*///***----*///--*/*///**--*/*//**--**/*//
* (01-03)參考範例與說明

安裝函式庫之後,請由 Arduino IDE 選單中選擇開啟 "File/Examples/ESP32QRCodeReader/basic" 範例程式。

我對這個範例做了一些註解,並新增了 Line 10, 11 這兩行,用來輸出該工作任務所運行的核心號碼為何?

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
#include <Arduino.h>
#include <ESP32QRCodeReader.h>

//* 引數 CAMERA_MODEL_AI_THINKER,代表使用的是 ESP32-CAM 開發板
ESP32QRCodeReader reader(CAMERA_MODEL_AI_THINKER);

void onQrCodeTask(void *pvParameters)
{
    //* 輸出此工作任務執行的核心號碼
    Serial.print("onQrCodeTask: Executing on core ");
    Serial.println(xPortGetCoreID());

    //* 此結構用來儲存取得的二維碼辨識資料
    struct QRCodeData qrCodeData;

    while (true)
    {
        //* 取回二維碼辨識之後的資料,最多等待 100ms
        if (reader.receiveQrCode(&qrCodeData, 100))
        {
            Serial.println("Found QRCode");
            if (qrCodeData.valid)   // 如果資料有效
            {
                Serial.print("Payload: ");
                Serial.println((const char *)qrCodeData.payload);
            }
            else                    // 如果資料無效
            {
                Serial.print("Invalid: ");
                Serial.println((const char *)qrCodeData.payload);
            }
        }
        vTaskDelay(100 / portTICK_PERIOD_MS);
    }
}

void setup()
{
    Serial.begin(115200);
    Serial.println();

    //* ESP32-CAM 攝像機及其接腳初始化
    reader.setup();

    Serial.println("Setup QRCode Reader");

    // 二維碼辨識工作任務由 Core 1 負責,優先級別 5
    reader.beginOnCore(1);

    Serial.println("Begin on Core 1");

    // 設定一工作任務,一但有資料在二維碼辨識的資料在 Queue 中,就取回並顯示出辨識結果
    // 執行核心此時未知,在 onQrCodeTask 輸出執行核心號碼,優先級別 4
    xTaskCreate(onQrCodeTask, "onQrCode", 4 * 1024, NULL, 4, NULL);
}

void loop()
{
    delay(100);
}

這範例程式主要分為兩個部分:一個是函式庫初始化的部分,另一個是工作任務的建立。

完成範例程式的修改後,編譯、上傳後打開串口調試助手,底板開關一次後再按下 "打開串口" 按鈕(或按下 "打開串口" 按鈕,再按下 ESP32-CAM 開發板的 RST 按鈕)開始通訊。

輸出欄位中,紫色框住的文字部分是 onQrCodeTask 工作任務執行的核心號碼,往下則是進行二維碼辨識的結果。


/*--*//**---/*///**---*-*////***--*/*///***----*///--*/*///**--*/*//**--**/*//
* (01-04)提高二維碼辨識成功率與速度

從上一節的辨識二維碼的輸出可以得知,並不是每一次的辨識都會是成功的,雖然這是在測試時故意為之的,但實際做辨識時就是會這樣發生,而且辨識成功的機率還有可能會更低(應該會更低),原因是下面幾個因素所造成:

  • 二維碼圖示使用的材質
  • 二維碼圖示的清晰度
  • 二維碼圖片的大小
  • 攝像機與二維碼圖示的距離

二維碼圖示可以用列印的或是直接由手持裝置做顯示,基本來說用什麼材質沒關係,但它所呈現的清晰度一定要好,黑就是黑白就是白,主要就是要清楚,這不難理解吧!

屏除掉前面兩個因素的影響,後面的兩點,個人覺得是最至關重要影響辨識成功與否的關鍵因素!

攝像頭與二維碼之間的距離,取的是最小可辨識的距離,兩者之間的間距一定要大於這個值才行,使用之前請先搞清楚!

如下圖所示為影片中使用的二維碼圖示:一個材質為紙張,另一個為手機畫面。左邊圖示大小 62mm * 62mm,右邊圖示為 71mm * 71mm,攝像頭距離紙箱開口處 12.5mm。


【(02)參考程式碼】

參考(01-03)裡的範例,把二維碼辨識的程式碼加到 x03_ESP32CamWiFiWatchdog.ino 裡,裡面做了一些小修改,加入了辨識結果的狀態碼,和新增了一個工作任務。


/*--*//**---/*///**---*-*////***--*/*///***----*///--*/*///**--*/*//**--**/*//
* (02-01)監控二維碼辨識狀態

首先,在檔案的開頭處加入函式庫的標頭檔文件。

20
#include <ESP32QRCodeReader.h>

然後修改 Watchdog 逾時時間為 30 秒,不然還沒連線成功 ESP32 就會自己重置。

40
41
/*================== WATCHDOG =================*/
#define WDT_TIMEOUT     30      // 設定 Watchdog 計時器的時間

WATCHDOG 區域的下面,建立一個二維碼辨識的函式庫的全局實例。

43
44
/*================== QR CODE READER =================*/
ESP32QRCodeReader qrcodereader (CAMERA_MODEL_AI_THINKER);

lsInterrupt 區域的上方插入下面的程式碼。

此處新增了一個二維碼辨識結果的狀態全局變數(初始狀態為 OFF),和一個控制代碼(handle)。

76
77
78
79
80
81
82
83
84
85
/*================== TASK: ONQRCODE =================*/
enum onqrcode_state_t {
    OFF,
    WORKING,
    MATCH,
    UNMATCH,
    INVALID
} xOnQrCodeState = OFF;

TaskHandle_t xOnQrCodeTaskHandle;

往下,建立一個用來與二維碼辨識結果作比對的字串陣列的全局變數。現在只有第一個會用到,其他的可自發揮。

91
92
93
94
95
96
// 定義特定任務的 QR Code
// https://qr.ioi.tw/zh/
const char *pcQrCode[] {
    "Qh@4#I_6+",    // unlock
    "PfgW~!$&U"     // do nothing
};

setupOTA 函式的上方,插入下面作為工作任務的二維碼辨識資料處理的函式。

在這裡除了基本的辨識資料的輸出之外,就是設定相對應的二維碼辨識的狀態。不過,在這裡沒有使用這些狀態碼,下一篇總結的時候才會說到。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
/**========================================================================
 **                           onQrCodeTask()
 *?  監測二維碼辨識資料是否成功與否的任務
 *@param pvParameter void*  
 *========================================================================**/
void onQrCodeTask (void *pvParameters) {
    Serial.print("onQrCodeTask: Executing on core ");
    Serial.println(xPortGetCoreID());
    
    struct QRCodeData qrCodeData;

    while (true) {
        if (xOnQrCodeState == OFF) xOnQrCodeState = WORKING;

        if (qrcodereader.receiveQrCode(&qrCodeData, 100)) {
            Serial.println (F("Found QRCode"));
            if (qrCodeData.valid) {
                Serial.print (F("Payload: "));
                Serial.println ((const char*)qrCodeData.payload);
                if (strcmp((const char*)qrCodeData.payload, pcQrCode[0]) == 0) {
                    xOnQrCodeState = MATCH;
                } else {
                    xOnQrCodeState = UNMATCH;
                }
            } else {
                Serial.print (F("Invalid: "));
                Serial.println ((const char *)qrCodeData.payload);
                xOnQrCodeState = INVALID;
            }
       }
        vTaskDelay (pdMS_TO_TICKS(100));
    }
}


/*--*//**---/*///**---*-*////***--*/*///***----*///--*/*///**--*/*//**--**/*//
* (02-02)完整程式碼

整合上一小節所說的部份,將這些程式碼插入到 x03_ESP32CamWiFiWatchdog.ino 中就完成 x04_ESP32CamQRCodeReader.ino 程式的撰寫。

/**------------------------------------------------------------------------
* x04_ESP32CamQRCodeReader.ino
* ESP32-CAM 二維碼辨識。
*
* Adapted by Ruten.Proteus
* Test Date: 2021/09/17
*
* {功能說明}
* + [o] 修改 Watchdog 逾時時間;
* + [o] 移除 x03 輸出秒數的程式碼;
* + [o] 加入二維碼辨識的功能;
*
* {Result} Wrok !!!
*------------------------------------------------------------------------**/
#include <Arduino.h>
#include <WiFi.h>
#include <esp_task_wdt.h>
#include <FastLED.h>
#include <ESP32QRCodeReader.h>
/** 是否使用 OTA 功能? 不使用就註解下一行 */
#define ENABLE_OTA
#ifdef ENABLE_OTA
#include <ArduinoOTA.h>
#endif
/**------------------------------------------------------------------------
* GLOBAL VARIABLES
*------------------------------------------------------------------------**/
/*================== WIFI =================*/
//** 無線分享器名稱與密碼 */
const char* SSID = "填入無線分享器名稱";
const char* PASSWORD = "填入無線分享器連線密碼";
// 裝置名稱
const char* DEVICENAME = "ESP32QRDoorLock";
/*================== WATCHDOG =================*/
#define WDT_TIMEOUT 30 // 設定 Watchdog 計時器的時間
/*================== QR CODE READER =================*/
ESP32QRCodeReader qrcodereader (CAMERA_MODEL_AI_THINKER);
/*================== FASTLED =================*/
const int NUM_LEDS = #1; // 填入 LED 的數量取代 #1
const uint8_t DATA_PIN = #2; // 填入 WS2812 通訊接腳號碼取代 #2
// Define the array of leds
CRGB ws2812[NUM_LEDS];
/*================== GPIO =================*/
/** 電磁鎖 */
const uint8_t DOORLOCK = #3; // 填入電磁鎖驅動模組控制接腳號碼取代 #3
/** 門磁(磁簧)開關 */
const uint8_t LIMITSWITCH = #4; // 填入門磁開關訊號接接腳號碼取代 #4
// 門的狀態
#define OPEN HIGH
#define CLOSE LOW
#define dDoorState digitalRead(LIMITSWITCH)
/*================== TASK: WIFIRECONNECT =================*/
enum wifi_reconnect_state {
UNKNOWN,
RUNNING,
CONNECTING,
CONNECTED,
DISCONNECTED,
} xWifiReconnectState = UNKNOWN;
bool OTA_status = false;
TaskHandle_t xWiFiReConnectTaskHandle;
/*================== TASK: ONQRCODE =================*/
enum onqrcode_state_t {
OFF,
WORKING,
MATCH,
UNMATCH,
INVALID
} xOnQrCodeState = OFF;
TaskHandle_t xOnQrCodeTaskHandle;
/*================== lsInterrupt =================*/
volatile bool bIntTrigger = false;
unsigned long ulLastMicros;
// 定義特定任務的 QR Code
// https://qr.ioi.tw/zh/
const char *pcQrCode[] {
"Qh@4#I_6+", // unlock
"PfgW~!$&U" // do nothing
};
/*======================== END OF GLOBAL VARIABLES ========================*/
/**------------------------------------------------------------------------
* ENUM AND STRUCT
*------------------------------------------------------------------------**/
/*================== FASTLED =================*/
// Colors definition for WS2812
#define BLACK CRGB ( 0, 0 , 0) // Black
#define WHITE CRGB (255, 255, 255) // White
#define RED CRGB (255, 0, 0) // Red
#define ORANGE CRGB (255, 127, 0) // Orange
#define YELLOW CRGB (255, 255, 0) // Yellow
#define GREEN CRGB ( 0, 255, 0) // Green
#define BLUE CRGB ( 0, 0, 255) // Blue
#define INDIGO CRGB ( 0, 255, 255)
#define VIOLET CRGB (127, 0, 255) // Violet
/*========================= END OF ENUM AND STRUCT ========================*/
void blink (const int8_t blink_count, const TickType_t delay, const CRGB color);
/**========================================================================
** ioInit()
*? ESP32 接腳和 FastLED 初始化
*========================================================================**/
void ioInit () {
pinMode (DOORLOCK, OUTPUT);
digitalWrite (DOORLOCK, LOW);
pinMode (LIMITSWITCH, INPUT_PULLUP);
FastLED.addLeds<WS2812B, DATA_PIN, GRB>(ws2812, NUM_LEDS);
//* 開機關閉 LED
ws2812[0] = BLACK;
FastLED.show ();
}
/**========================================================================
** lsInterrupt()
*? 中斷處理函式
*========================================================================**/
void IRAM_ATTR lsInterrupt () {
if((long)(micros() - ulLastMicros) >= 150000L) {
bIntTrigger = true;
detachInterrupt (digitalPinToInterrupt(LIMITSWITCH));
}
ulLastMicros = micros();
}
/**========================================================================
** blink()
*? 系統運行狀態的指示
*@param blink_count const int8_t
*@param color CRGB
*========================================================================**/
void blink (const int8_t blink_count, // 閃爍次數
const CRGB color) // WS2812 顏色
{
uint8_t state = LOW;
for (int x = 0; x < (blink_count << 1); ++x) {
if (state ^= HIGH) {
ws2812[0] = color;
} else {
ws2812[0] = BLACK;
}
FastLED.show();
vTaskDelay (pdMS_TO_TICKS(50));
}
}
/**========================================================================
** doorControl()
*? 門鎖開關控制
*@param isMotor const bool
* 門鎖的形式。true: DC Motor; false: 電磁開關
*========================================================================**/
void doorControl (const bool isMotor) {
if (isMotor) {
// 如果使用電動鎖頭,寫程式在這裡
// ...
} else {
digitalWrite (DOORLOCK, HIGH);
vTaskDelay (pdMS_TO_TICKS(1000));
digitalWrite (DOORLOCK, LOW);
}
}
/**========================================================================
** wifiReconnectTask()
*? 監測無線連線狀態的任務。若出現問題會造成 WatchDog 重置系統重新連線。
*@param pvParameter void*
*========================================================================**/
void wifiReconnectTask (void *pvParameters) {
int wifiStatus = 0;
int wifiPrev = WL_CONNECTED;
int n = 0;
unsigned long previousMillis = 0;
const long interval = 1000;
esp_task_wdt_add(NULL); // 添加當前任務到看門狗計時器
for(;;) {
wifiStatus = WiFi.status();
#ifdef ENABLE_OTA
ArduinoOTA.handle();
#endif
if (OTA_status) // 如果有 OTA,重置 Watchdog 計時器
esp_task_wdt_reset();
/** 確認 WiFi 狀態是否已經改變? */
if ((wifiStatus == WL_CONNECTED)&&(wifiPrev != WL_CONNECTED)) {
xWifiReconnectState = CONNECTED;
esp_task_wdt_reset(); // 重置 Watchdog 計時器
Serial.printf ("\nConnected to WiFi!\nLocal IP address: %s\n\n",
(WiFi.localIP ().toString ()).c_str ());
}
else if ((wifiStatus != WL_CONNECTED)&&(wifiPrev == WL_CONNECTED)) {
xWifiReconnectState = DISCONNECTED;
esp_task_wdt_reset(); // 重置 Watchdog 計時器
Serial.println(F("\nWiFi disconnected!"));
WiFi.disconnect();
Serial.println(F("Scanning for WiFi networks"));
n = WiFi.scanNetworks(false, true);
Serial.println(F("Scan done"));
if (n == 0) {
Serial.println(F("No networks found. Resetting ESP32."));
esp_restart();
} else {
Serial.print(n);
Serial.println(F(" networks found"));
for (int i = 0; i < n; ++i) {
/** 列出每一個找到的網路的 SSID 和 RSSI */
Serial.print(i + 1); Serial.print(F(": "));
Serial.print(WiFi.SSID(i)); Serial.print(F(" ("));
Serial.print(WiFi.RSSI(i)); Serial.print(F(")"));
Serial.println((WiFi.encryptionType(i) == WIFI_AUTH_OPEN)?" ":"*");
vTaskDelay (pdMS_TO_TICKS(10));
}
}
esp_task_wdt_reset(); // 重置 Watchdog 計時器
Serial.println();
Serial.println(F("Please, check if your WiFi network is on the list and check if it's strong enough (greater than -90)."));
Serial.println("ESP32 will reset itself after "+String(WDT_TIMEOUT)+" seconds if can't connect to the network");
Serial.println("Connecting to: " + String(SSID));
xWifiReconnectState = CONNECTING;
WiFi.reconnect();
}
else if ((wifiStatus == WL_CONNECTED)&&(wifiPrev == WL_CONNECTED)) {
xWifiReconnectState = RUNNING;
esp_task_wdt_reset(); // 重置 Watchdog 計時器
vTaskDelay (pdMS_TO_TICKS(1000));
}
else {
/** 不要重置 Watchdoh 計時器,如果計時器到達設定的時間就會重置 ESP32 */
unsigned long currentMillis = millis();
if (currentMillis - previousMillis >= interval) {
previousMillis = currentMillis;
Serial.print (F("."));
}
}
wifiPrev = wifiStatus;
}
}
/**========================================================================
** onQrCodeTask()
*? 監測二維碼辨識資料是否成功與否的任務
*@param pvParameter void*
*========================================================================**/
void onQrCodeTask (void *pvParameters) {
Serial.print("onQrCodeTask: Executing on core ");
Serial.println(xPortGetCoreID());
struct QRCodeData qrCodeData;
while (true) {
if (xOnQrCodeState == OFF) xOnQrCodeState = WORKING;
if (qrcodereader.receiveQrCode(&qrCodeData, 100)) {
Serial.println (F("Found QRCode"));
if (qrCodeData.valid) {
Serial.print (F("Payload: "));
Serial.println ((const char*)qrCodeData.payload);
if (strcmp((const char*)qrCodeData.payload, pcQrCode[0]) == 0) {
xOnQrCodeState = MATCH;
} else {
xOnQrCodeState = UNMATCH;
}
} else {
Serial.print (F("Invalid: "));
Serial.println ((const char *)qrCodeData.payload);
xOnQrCodeState = INVALID;
}
}
vTaskDelay (pdMS_TO_TICKS(100));
}
}
/**========================================================================
** setupOTA
*? ESP32 OTA 韌體上傳功能
*========================================================================**/
void setupOTA () {
#ifdef ENABLE_OTA
ArduinoOTA
.onStart([]() {
String type;
if (ArduinoOTA.getCommand() == U_FLASH)
type = "sketch";
else // U_SPIFFS
type = "filesystem";
// NOTE: if updating SPIFFS this would be the place
// to unmount SPIFFS using SPIFFS.end()
Serial.println("Start updating " + type);
OTA_status = true;
})
.onEnd([]() {
Serial.println(F("\nEnd"));
})
.onProgress([](unsigned int progress, unsigned int total) {
Serial.printf("Progress: %u%%\r", (progress / (total / 100)));
esp_task_wdt_reset ();
OTA_status = true;
})
.onError([](ota_error_t error) {
Serial.printf("Error[%u]: ", error);
if (error == OTA_AUTH_ERROR) Serial.println(F("Auth Failed"));
else if (error == OTA_BEGIN_ERROR) Serial.println(F("Begin Failed"));
else if (error == OTA_CONNECT_ERROR) Serial.println(F("Connect Failed"));
else if (error == OTA_RECEIVE_ERROR) Serial.println(F("Receive Failed"));
else if (error == OTA_END_ERROR) Serial.println(F("End Failed"));
OTA_status = false;
esp_restart ();
});
ArduinoOTA.setHostname (DEVICENAME);
ArduinoOTA.begin();
#endif
}
void setup () {
delay (2000);
//* UART 初始化
Serial.begin (115200);
Serial.println (F("\nx04_ESP32CamQRCodeReader Booting..."));
//* WiFi 設定
WiFi.mode (WIFI_STA);
WiFi.setHostname (DEVICENAME);
WiFi.begin (SSID, PASSWORD);
//* 外設初始化
ioInit ();
//* OTA 設定
OTA_status = false;
setupOTA ();
//* WatchDog 初始化
esp_task_wdt_init (WDT_TIMEOUT, true);
/*======= WIFI RECONNECT =======*/
xTaskCreatePinnedToCore (
wifiReconnectTask, "wifiReconnectTask",
100000, NULL,
0, // Priotity 0
&xWiFiReConnectTaskHandle, 0 // Core 0
);
delay (250);
/*======= ESP32QRCODEREADER =======*/
qrcodereader.setup ();
Serial.print (F("Setup QRCode Reader"));
qrcodereader.beginOnCore (1);
Serial.println (F("Begin on Core 1"));
xTaskCreatePinnedToCore (
onQrCodeTask, "onQrCodeTask",
4 * 1024, NULL,
2, // Priority 2
&xOnQrCodeTaskHandle, 0 // Core 0
);
delay (250);
//* 印出 ESP32 Heap 和 PSRAM 的資訊
log_d("Total heap: %d", ESP.getHeapSize ());
log_d("Free heap: %d", ESP.getFreeHeap ());
log_d("Total PSRAM: %d", ESP.getPsramSize ());
log_d("Free PSRAM: %d", ESP.getFreePsram ());
}
void loop () {}

儲存上面的檔案並上傳到 ESP32-CAM 後,開啟串口調試助手,重置或重啟 ESP32-CAM,完成開機並連線到無線網路,可得到與下面類似的輸出訊息。

接著,利用之前建立的二維碼圖示,讓 ESP32-CAM 進行辨識,可得到與下面類似的輸出訊息。


【(03)結論】

二維碼辨識是整個 ESP32QRDoorLock 中最重要的環節,因此知道怎麼去做辨識和使用函式庫就很重要。函式庫內部使用修改版的 Quire 函式庫和 MaixPy 專案的一些 OpenMV 的程式碼, 辨識的結果儲存於 FreeRTOS Queue 等待被讀出...,想深入了解的話就看看函式庫裡的程式碼吧!

下一篇,是整個系列的最後一篇,將整合這兩個工作任務執行時的輸出狀態值來做監控,轉換為整個系統工作流程中的狀態值,用來觸發相對應的處理動作


.

.


<<部落格相關文章>>


    .

    .

    4 則留言:

    1. 不好意思,我是高職學生,我想問一下我找不到zip檔要去哪裡找,我把檔案加進去,但它顯示沒有符合的zip檔,所以來跟你請教,我的專題製作也跟您的檔案有相關,如果能得到解惑,會很感謝您。

      回覆刪除
      回覆
      1. 在網頁開頭裡面有需要的函式庫下載連結以及安裝的說明,詳細看網頁裡的說明。

        刪除
      2. 我已經照著上面做了,但在編譯的時候找不到ESP32QRCodeReader.h這個檔案,但我有把他家到程式裡,草稿碼資料夾也有,那我該怎麼辦。

        刪除
      3. 你安裝的函式庫沒放在預設的資料夾中,所以 Arduino IDE 執行時找不到;所謂預設的資料夾可以看"手動函式庫安裝教學":https://www.arduino.cc/en/guide/libraries 連結中步驟的說明。

        刪除

    留言屬名為"Unknown"或"不明"的用戶,大多這樣的留言都會直接被刪除掉,不會得到任何回覆!

    發問問題,請描述清楚你(妳)的問題,別人回答前不會想去 "猜" 問題是什麼?

    不知道怎麼發問,請看 [公告] 部落格提問須知 - 如何問問題 !