在 macOS Monterey 12.4 環境,使用 Micropython 在 ESP32 SSD1306 顯示 圖形


作者:順哥
email:palmbear@gmail.com

資料來源:
1.image2cpp
2.| ESP32 教學 | MicroPython | I2C OLED Image 顯示圖像 | 209 |
3.SSD1306.py


Table of Contents

[TOC]


前言

測試在 macOS Monterey 12.4 環境,使用 Micropython 在 ESP32 用 SSD1306 OLED 顯示 image 圖形。


Step 1:使用的圖形

在 google網站搜尋一張看起來順眼,個人使用無版權的黑色的熊熊圖(png檔),就用這張圖來做測試,這張圖的原始尺寸為 “981 x 562″。
網頁畫面:


Step 2:將圖形轉換成 byte arrays

因為在macOS上找不到適當軟體可以使用,在 google網站上找到這個
image2ccp

這個網站可以將 image 轉換為 byte arrays(或將您的 byte arrays 轉換回 image),然後給 OLED 顯示出來。
網頁畫面:

  1. 選擇圖形 Select image:
    點選「選擇檔案」,上傳圖形檔。
    網頁畫面:
  2. 圖形設定 image Settings:
    在「畫布尺寸」 Canvas size(s) 欄位中,填入 OLED 的像數規格,我使用 “128 x 64″ ,所以在這裡我們填入 “128″ 及 “64″ ,圖形的原始尺寸為 “981 x 562″,這個網站可以將圖形直接轉成我們需要的大小。
    接下來選擇 “stretch to fill canvas" 「拉伸填充畫布」。
    網頁畫面:
  3. 圖形預覽 Preview:
    隨即就會在「圖形預覽」Preview,看到圖形。
  4. 輸出 Output:
    在「代碼輸出格式」"Code output format" 欄位中,填入 “plain bytes" ,
    接下來在「繪製模式」"Draw mode" 欄位中,填入 “Vertical – 1 bit per pixel" ,
    最後按下 “Generate code" 擷取碼,並將16進位的碼 Copy 下來。
    網頁畫面:

Step 3:測試程式碼

我使用 資料來源2 的程式碼,SSD1306 module 使用 資料來源3 ,並依照自己的ESP32接腳,改用SoftI2C,我的程式碼如下:

from machine import Pin, SoftI2C #從machine 匯入 Pin, SoftI2C
import ssd1306 #匯入 ssd1306模組
import framebuf #匯入 fram3buf模組

i2c = SoftI2C(scl=Pin(22), sda=Pin(21), freq=400000) # 建立 i2c 物件

oled=ssd1306.SSD1306_I2C(128, 64, i2c) #建立 oled096 物件,第 1 個參數與第 2 個參數設定為 OLED 模組的像素,第 3 個參數為 I2C 的物件名稱

# img64 是一個 bytearrays 的列表(List) 
img64 = [
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f, 0x7f, 
0x3f, 0x1f, 0x1f, 0x0f, 0x07, 0x07, 0x07, 0x03, 0x03, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 
0x01, 0x01, 0x01, 0x01, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x07, 0x07, 
0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x03, 0x03, 0x03, 0x01, 0x01, 0x01, 0x01, 
0x01, 0x01, 0x01, 0x01, 0x03, 0x03, 0x03, 0x03, 0x07, 0x07, 0x07, 0x07, 0x0f, 0x0f, 0x0f, 0x1f, 
0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x7f, 0x7f, 
0x7f, 0x3f, 0x3f, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x3f, 0x1f, 0x07, 0x03, 0x01, 0x00, 0x00, 0x00, 
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x03, 0x03, 0x07, 0x2f, 0x7f, 0xff, 0xff, 0xff, 0xff, 
0xff, 0xff, 0xff, 0xff, 0xff, 0x3f, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x81, 0xc3, 0xf7, 
0xff, 0xff, 0xff, 0xff, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
0x00, 0x00, 0x00, 0x00, 0x08, 0x38, 0xf8, 0xf8, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfe, 0xfe, 
0xfe, 0xfe, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 
0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xfc, 0xfc, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 
0x80, 0x80, 0x80, 0xc0, 0xc0, 0x80, 0xc0, 0xc0, 0xc0, 0xe0, 0xe0, 0xe0, 0x20, 0x00, 0x00, 0x00, 
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
0x00, 0x80, 0xc0, 0xf0, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x03, 0x0f, 0x3f, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 
0xff, 0xff, 0x3f, 0x1f, 0x0f, 0x07, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
0x00, 0x00, 0xe0, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x1f, 0x02, 0x00, 0x00, 0x00, 
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xf0, 0xfc, 
0xfc, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0xf8, 0xf8, 0xf0, 0xe0, 0xc0, 0x80, 0x00, 0x00, 0x00, 
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x03, 0x07, 0x1f, 0x3f, 
0x7f, 0x3f, 0x7f, 0x7f, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 
0xc1, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xc0, 
0xc0, 0xe0, 0xf0, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x01, 0x00, 0x00, 0x00, 0x00, 
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0xfc, 0xfe, 0xff, 0xff, 0xff, 
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 
0xfe, 0xf8, 0xf8, 0xf8, 0xf8, 0xf0, 0xf8, 0xf8, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 
0xf0, 0xf0, 0xf0, 0xf0, 0xf8, 0xfc, 0xfd, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 
0xff, 0xff, 0xfc, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x03, 
0x87, 0x9f, 0xff, 0xff, 0xfc, 0xf8, 0xf8, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 
0xf0, 0xf0, 0xf0, 0xf8, 0xf9, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfd, 0xf0, 0xf0, 0xe0, 0xe0, 0xe0, 
0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc1, 0xc1, 0xc1, 0xc3, 0xc3, 
0xe7, 0xdf, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
]

img064b = bytearray(img64)  # 將 img64 轉成 bytearray 型態,並取名 img64b
imgbuf=framebuf.FrameBuffer(img064b,128,64,framebuf.MONO_VLSB) #framebuf 第 1 個參數為影像資料的 bytearray名稱,第 2、3 個參數是影像的寬度與高度,第 4 個參數是像素形式與排列,MONO_VLSB 是指 單色+垂直+LSB
oled.blit(imgbuf, 0, 0) #blit方法,是將imgbuf 物件,由指定的 X,Y 位置,依序開始填入 OLED 記憶體
oled.invert(1) #以反白螢幕方式顯示圖形畫面
oled.show() #顯示螢幕畫面

程式說明

  1. 因為 framebuf 使用的資料型態是 bytearray,所以將 img64 轉成 bytearray 型態,並取名 img64b。
  2. 我們在轉檔時時是採用單色與垂直方向排列(Vertical)的方式存檔,所以預設轉出陣列就是 1btye 會有 8 像素,資料排列也就照著直線方式一條一條轉換,img64 原始資料共有 1024 個 bytes,換算成像素也就是1024×8=8192個像素,與我們OLED 的像素128*64=8192 Pixel 是相符的。
  3. mgbuf 為新建立的 framebuf 物件,framebuf 第 1 個參數為影像資料的 byte 序列,第 2、3 個參數分別為影像的寬度與高度,第 4 個參數就是像素形式與排列,MONO_VLSB 是指 單色+垂直+LSB(如果是其他形式可以填入framebuf.MONO_HLSB、framebuf.RGB565、framebuf.GS4_HMSB等。
  4. blit 這個方法也是源自 framebuf,將建立好的 imgbuf 物件,由指定的 X,Y 位置,依序開始填入 OLED 記憶體內。
    Micropython framebuf 模組的官方說明

成果

完成畫面:

我使用的硬體:

測試影片


tags: Micropython ESP32 Macbook pro 2021 macOS 12.4 Monterey

MacBook Pro Monterey,使用 Arduino IDE 燒錄 ESP8266 會出現燒錄錯誤 “pyserial or esptool directories not found next to this upload.py tool.”

資料來源:https://www.tweaking4all.com/forum/arduino/macos-aruino-ide-how-to-fix-pyserial-or-esptool-directories-not-found-next-to-this-upload-py-tool-error-esp8266/

錯誤訊息如下:

pyserial or esptool directories not found next to this upload.py tool.
An error occurred while uploading the sketch

macOS版本:Monterey 12.3.1

ESP8266 Core version:2.6.3

aArduino IDE version:1.8.19

解決方法:

  1. 打開這個檔案
    file~/Library/Arduino15/packages/esp8266/hardware/esp8266/2.6.3/tools/pyserial/serial/tools/list_ports_osx.py
  2. 註釋掉這兩行
iokit = ctypes.cdll.LoadLibrary('/System/Library/Frameworks/IOKit.framework/IOKit')    
cf = ctypes.cdll.LoadLibrary('/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation')

加入這兩行:

iokit = ctypes.cdll.LoadLibrary('/System/Library/Frameworks/IOKit.framework/IOKit')
cf = ctypes.cdll.LoadLibrary('/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation')

修改完成後,程式碼應該像下面這樣子才對:

from serial.tools import list_ports_common

#iokit = ctypes.cdll.LoadLibrary(ctypes.util.find_library('IOKit'))
#cf = ctypes.cdll.LoadLibrary(ctypes.util.find_library('CoreFoundation'))
iokit = ctypes.cdll.LoadLibrary('/System/Library/Frameworks/IOKit.framework/IOKit')
cf = ctypes.cdll.LoadLibrary('/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation')

kIOMasterPortDefault = ctypes.c_void_p.in_dll(iokit, "kIOMasterPortDefault")
kCFAllocatorDefault = ctypes.c_void_p.in_dll(cf, "kCFAllocatorDefault")
這樣就可以正常燒錄了!
Done!

2021 Macbook Pro 14吋 安裝 Arduino IDE 1.18.9 在 ESP32 發生 「exec: “python" : executable file not found in $PATH」錯誤

資料來源:
1. https://forum.arduino.cc/t/esp32-problem-with-compilation-on-macos-12-3-monterey/969771
2. https://forum.arduino.cc/t/mac-os-update-killed-esp32-sketch/969580/8

作者:順哥
email:palmbear@gmail.com

Table of Contents

[TOC]

前言

最近新購買2021 Macbook Pro 14吋 使用 Arduino IDE 1.18.9 在ESP32開發板上傳程式碼時,發生 「exec: “python": executable file not found in $PATH’」錯誤。上網查了發現解決方法,留下紀錄,以免後續再碰到時,不知如何解決。

問題

ESP32: problem with compilation on MacOS 12.3 Monterey

問題頁面

網頁畫面:

解決方法
網頁畫面:


我的解決方法

我按照網頁上的解決方法,更改
/Users/palmbearhung/Library/Arduino15/packages/esp32/hardware/esp32/1.0.6/platform.txt 文件內容:

# From
tools.gen_esp32part.cmd=python "{runtime.platform.path}/tools/gen_esp32part.py"

# To
tools.gen_esp32part.cmd=/usr/local/bin/python "{runtime.platform.path}/tools/gen_esp32part.py"

但是還時不能上傳!!!

Arduino IDE跟我說「no such file or directory」

將 python 更改為 python3 也不行!!!

這倒底是怎麼一回事呢?

後來把整個文章看完,才知道也有人跟我一樣。

但是有解決方法,就是:

找出 python3放在電腦的哪個位置?

使用 which python3 指令,找出 python3 的所在位置。

原來我的 python3 位置跟他們的不一樣!

更改完成後,儲存檔案。

再次上傳檔案,成功!!!


tags: Arduino ESP32 Macbook pro 2021 macOS 12.3.1 Monterey

在Arduino 使用ESP32與網路同步時間,獲取日期及時間

作者:順哥
email:palmbear@gmail.com

資料來源:
https://randomnerdtutorials.com/esp32-date-time-ntp-client-server-arduino/

Table of Contents

[TOC]

前言

如何使用 ESP32 和 Arduino IDE 從 NTP Server 同步日期和時間?

獲取日期和時間在Aiot應用中很有用。要從 NTP Server獲取時間,ESP32需要連接 Internet,而且不需要額外的硬體(如 RTC 時鐘)。


NTP(Network Time Protocol, 網路時間協議)

NTP想要將所有參與電腦的協調世界時(UTC)時間同步到ms的誤差內。NTP通常可以在internet保持幾十毫秒的誤差,並且在理想的區域網路環境中可以實現超過1毫秒的精度。

MCU與NTP Server同步示意圖:
資料來源:randomnerdtutorials.com


硬體

  1. AI Thinker ESP32-S(NODMCU-32 V1.2)
  2. ESP32擴展板
  3. USB充電傳輸線

軟體

  1. Arduino IDE 2.0.0 RC5 for MacOS:
  2. 需要的Library:

<WiFi.h>
<time.h>


程式碼

  1. 使用 ESP32 加上 ESP32擴展板,程式碼如下:
/*
  Rui Santos
  Complete project details at https://RandomNerdTutorials.com/esp32-date-time-ntp-client-server-arduino/

  Permission is hereby granted, free of charge, to any person obtaining a copy
  of this software and associated documentation files.

  The above copyright notice and this permission notice shall be included in all
  copies or substantial portions of the Software.
*/

#include <WiFi.h>
#include <time.h>

const char* ssid     = "Your wifi ssid";
const char* password = "your password";

const char* ntpServer = "time.google.com";
const long  gmtOffset_sec = 28800; //台灣時區+8hr,28800=8*60*60
const int   daylightOffset_sec = 0;  //台灣無日光節約時間

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

  // Connect to Wi-Fi
  Serial.print("Connecting to ");
  Serial.println(ssid);
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("");
  Serial.println("WiFi connected.");

  // Init and get the time
  configTime(gmtOffset_sec, daylightOffset_sec, ntpServer);
  printLocalTime();

  //disconnect WiFi as it's no longer needed
  WiFi.disconnect(true);
  WiFi.mode(WIFI_OFF);
}

void loop(){
  delay(5000);
  printLocalTime();
}

void printLocalTime(){
  struct tm timeinfo;
  if(!getLocalTime(&timeinfo)){
    Serial.println("Failed to obtain time");
    return;
  }
  Serial.println(&timeinfo, "%A, %Y %m %d %H:%M:%S"); //%A-Sunday,%Y-2022,%m-3,%d-27,%H:%M:%S-21:10:02
  Serial.println(&timeinfo, "%A, %B %d %Y %H:%M:%S"); //%A-Sunday,%B-March,%d-27,%Y-2022,%H:%M:%S-21:10:02
  Serial.print("Day of week: "); //顯示英文 星期
  Serial.println(&timeinfo, "%A"); //Day of week: Sunday
  Serial.print("Month: "); //顯示英文 月份
  Serial.println(&timeinfo, "%B"); //Month: March
  Serial.print("Day of Month: "); //顯示英文 日
  Serial.println(&timeinfo, "%d"); //Day of Month: 27
  Serial.print("Year: "); //顯示英文 西元年
  Serial.println(&timeinfo, "%Y"); //Year: 2022
  Serial.print("Hour: "); //顯示英文 時 24小時制
  Serial.println(&timeinfo, "%H"); //Hour: 21
  Serial.print("Hour (12 hour format): "); //顯示英文 時 12小時制
  Serial.println(&timeinfo, "%I"); //Hour (12 hour format): 9
  Serial.print("Minute: "); //顯示英文 分
  Serial.println(&timeinfo, "%M"); //Minute: 01
  Serial.print("Second: "); //顯示英文 秒
  Serial.println(&timeinfo, "%S"); //Second: 24

  Serial.println("Time variables"); //時間變數Time variables
  char timeHour[3]; //宣告時間小時變數:timeHour
  strftime(timeHour,3, "%H", &timeinfo);
  Serial.println(timeHour); //顯示時數:22
  char timeWeekDay[10]; //宣告時間星期變數:timeWeekDay
  strftime(timeWeekDay,10, "%A", &timeinfo);
  Serial.println(timeWeekDay); //顯示星期:Sunday
  Serial.println("*****************************************************");

}

tags: NTP Arduino ESP32 ESP8266 Wio terminal

Arduino IDE 2.0 RC3 安裝紀實


title: ‘Arduino IDE 2.0 RC3 安裝紀實’

作者:順哥
email:palmbear@gmail.com

前言

Arduino IDE 已經發展很久了,但是編譯的速度一直很慢,因應這個問題以及 Tiny ML 的應用,終於開發出新版本,目前還在 RC3版本,提供大家試用,這邊可以下載嘗鮮Arduino IDE 2.0 RC3

下載網頁畫面:


安裝

可以按照自己的系統下載對應的檔案來安裝,我下載 Windows 版本來安裝。

下載後執行檔案就會看到:

安裝畫面-1:

安裝畫面-2:

依照指示完成安裝即可,Arduino IDE 2.0 RC3 安裝完成後,開啟它。


環境設定

  1. 第一次開啟Arduino IDE 2.0 RC3會出現這個新畫面,原先是淺色系,我把它改成暗色系:
  2. 新的使用者介面長這樣,工具列的左側最靠中間有一個 搜尋及選擇開發板 的小視窗,而左側的5個圖示分別是:
    (1)Sketchbook 草稿碼資料夾
    (2)Boards Manager 開發版管理員
    (3)Library Manager 函式庫管理員
    (4)Dubug 偵錯
    (5)Search 搜尋
  3. 點選 File > Preferences :
  4. 接下來就會看到Preferences的相關設定,把常用的都打勾,並點選右下角的符號以增加其他開發板如下圖:
  5. 點選右下角符號後,請copy下面網址貼在框框內,一個網址一行,以增加ESP8266、ESP32 以及 Wio terminal這三塊開發板:
    http://arduino.esp8266.com/stable/package_esp8266com_index.json
    https://dl.espressif.com/dl/package_esp32_index.json
    https://files.seeedstudio.com/arduino/package_seeeduino_boards_index.json
  6. 接下來點選左邊上面算下來第2個圖示-Boards Manager,並搜尋 ESP8266 ,安裝2.6.3版比較沒有問題。如下圖:
  7. 隨後搜尋 ESP32 ,安裝1.0.4版比較沒有問題。如下圖:
  8. 最後搜尋 Seeed wio terminal ,安裝1.8.2版
  9. 關閉 Arduino IDE 2.0 RC3 ,重新開啟,這樣就完成環境設定了。

測試

  1. 使用 ESP32 加上 ESP32 擴展版 來寫一個閃爍內建 LED 的程式來測試一下,程式碼如下:
//閃爍內建LED
void setup() {
  pinMode(2, OUTPUT);  //宣告pin2(內建LED)作為輸出用途
}
void loop() {
  digitalWrite(2, HIGH);  //宣告pin2(內建LED)輸出高電位(3.3V)
  delay(1000);            //延遲1秒
  digitalWrite(2, LOW);   //宣告pin2(內建LED)輸出低電位(0V)
  delay(1000);            //延遲1秒
}
  1. Arduino 2.0 在編輯時,再輸入程式碼時會出現輸入指令格式提示,這應該是Arduino 2.0 除了速度變快之外,最大的改變了!
  2. 程式碼編輯完成,並上傳到ESP32上的完成畫面:
  3. ESP32 + 擴展板相片
  4. ESP32閃爍LED影片

待續!!!

tags: Arduino ESP32 ESP8266 Wio terminal

ESP32輔助板背面I2C使用方法

資料來源:

1.https://youyouyou.pixnet.net/blog/post/121069748-esp32-x-nbiot-%e8%bc%94%e5%8a%a9%e7%89%88-%e8%83%8c%e9%9d%a2i2c-lcd?pixfrom=related
2.IoT物聯網應用:使用 ESP32 開發版與 Arduino C 程式語言,尤濬哲老師著

本文實作尤老師製作的「ESP32輔助板」背面 I2C 使用方法,背面的I2C為附加的PCF85741 I2C晶片,因此使用的腳位是 26 ,27 而非 21 , 22。

要使用ESP32輔助板背面的 I2C 介面,必須將 SDA 設為 GPIO 26,SCL 設為 GPIO 27。每個 I2C 裝置都會有一個位址,宣告錯誤的位址是不會有任何反應的,所以必須先做一次 I2C 掃描,取得正確位置。

我們可以使用下面程式碼掃描 I2C 的位址:

#include <Wire.h>

void setup()
{
  Serial.begin (115200);  
  Wire.begin (26, 27);   // sda=GPIO_26, scl=GPIO_27
}

void Scanner ()
{
  Serial.println ();
  Serial.println ("I2C scanner. Scanning ...");
  byte count = 0;

  Wire.begin();
  for (byte i = 8; i < 120; i++)
  {
    Wire.beginTransmission (i); // Begin I2C transmission Address (i)
    if (Wire.endTransmission () == 0) //0=success(ACK response) 
    {
      Serial.print ("Found address: ");
      Serial.print (i, DEC);
      Serial.print (" (0x");
      Serial.print (i, HEX);     // PCF8574 7 bit address
      Serial.println (")");
      count++;
    }
  }
  Serial.print ("Found ");      
  Serial.print (count, DEC);        // numbers of devices
  Serial.println (" device(s).");
}

void loop()
{
  Scanner ();
  delay (5000);
}

我接上一個 0.96吋的 OLED 螢幕,掃描到的 I2C 的位置有3個,分別是

I2C scanner. Scanning …
Found address: 32 (0x20)
Found address: 60 (0x3C)
Found address: 80 (0x50)
Found 3 device(s).

I2C 位址掃描結果

用 Arduino 打開 AdafruitSSD1306 範例檔,我參考網路上資源加上了一些文字顯示,程式碼如下:

/**************************************************************************
 This is an example for our Monochrome OLEDs based on SSD1306 drivers

 Pick one up today in the adafruit shop!
 ------> http://www.adafruit.com/category/63_98

 This example is for a 128x64 pixel display using I2C to communicate
 3 pins are required to interface (two I2C and one reset).

 Adafruit invests time and resources providing this open
 source code, please support Adafruit and open-source
 hardware by purchasing products from Adafruit!

 Written by Limor Fried/Ladyada for Adafruit Industries,
 with contributions from the open source community.
 BSD license, check license.txt for more information
 All text above, and the splash screen below must be
 included in any redistribution.
 **************************************************************************/

#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels

// Declaration for an SSD1306 display connected to I2C (SDA, SCL pins)
// The pins for I2C are defined by the Wire-library. 
// On an arduino UNO:       A4(SDA), A5(SCL)
// On an arduino MEGA 2560: 20(SDA), 21(SCL)
// On an arduino LEONARDO:   2(SDA),  3(SCL), ...
#define OLED_RESET     4 // Reset pin # (or -1 if sharing Arduino reset pin)
#define SCREEN_ADDRESS 0x3C ///這裡要設定Address為 0x3C
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

#define NUMFLAKES     10 // Number of snowflakes in the animation example

#define LOGO_HEIGHT   16
#define LOGO_WIDTH    16
static const unsigned char PROGMEM logo_bmp[] =
{ 0b00000000, 0b11000000,
  0b00000001, 0b11000000,
  0b00000001, 0b11000000,
  0b00000011, 0b11100000,
  0b11110011, 0b11100000,
  0b11111110, 0b11111000,
  0b01111110, 0b11111111,
  0b00110011, 0b10011111,
  0b00011111, 0b11111100,
  0b00001101, 0b01110000,
  0b00011011, 0b10100000,
  0b00111111, 0b11100000,
  0b00111111, 0b11110000,
  0b01111100, 0b11110000,
  0b01110000, 0b01110000,
  0b00000000, 0b00110000 };

void setup() {
  Serial.begin (115200);  
  Wire.begin (26, 27);   // sda=GPIO_26, scl=GPIO_27,設I2C腳位為26,27
  // SSD1306_SWITCHCAPVCC = generate display voltage from 3.3V internally
  if(!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) {
    Serial.println(F("SSD1306 allocation failed"));
    for(;;); // Don't proceed, loop forever
  }

  // Show initial display buffer contents on the screen --
  // the library initializes this with an Adafruit splash screen.
  display.display();
  delay(2000); // Pause for 2 seconds

  // Clear the buffer
  display.clearDisplay();

  // Draw a single pixel in white
  display.drawPixel(10, 10, SSD1306_WHITE);

  // Show the display buffer on the screen. You MUST call display() after
  // drawing commands to make them visible on screen!
  display.display();
  delay(2000);
  // display.display() is NOT necessary after every single drawing command,
  // unless that's what you want...rather, you can batch up a bunch of
  // drawing operations and then update the screen all at once by calling
  // display.display(). These examples demonstrate both approaches...

  testdrawline();      // Draw many lines

  testdrawrect();      // Draw rectangles (outlines)

  testfillrect();      // Draw rectangles (filled)

  testdrawcircle();    // Draw circles (outlines)

  testfillcircle();    // Draw circles (filled)

  testdrawroundrect(); // Draw rounded rectangles (outlines)

  testfillroundrect(); // Draw rounded rectangles (filled)

  testdrawtriangle();  // Draw triangles (outlines)

  testfilltriangle();  // Draw triangles (filled)

  testdrawchar();      // Draw characters of the default font

  testdrawstyles();    // Draw 'stylized' characters

  testscrolltext();    // Draw scrolling text

  testdrawbitmap();    // Draw a small bitmap image

  // Invert and restore display, pausing in-between
  display.invertDisplay(true);
  delay(1000);
  display.invertDisplay(false);
  delay(1000);

  testanimate(logo_bmp, LOGO_WIDTH, LOGO_HEIGHT); // Animate bitmaps
}

void loop() {
  display.setTextColor(WHITE);                           //設定顯示文字的顏色
  display.setTextSize(1);                                        //設定第1行文字的大小為1
  display.setCursor(0,0);                                        //設定第1行文字在oled上顯示的座標
  display.println("Hello, world!");                         //設定第1行顯示的文字

  display.setTextSize(2);                                        //設定第2行文字的大小為2
  display.setCursor(0,15);                                      //設定第2行文字在oled上顯示的座標
  display.println("First");                                       //設定第2行顯示的文字

  display.setTextSize(3);                                        //設定第3行文字的大小為3
  display.setCursor(0,40);                                      //設定第3行文字在oled上顯示的座標
  display.println("Second");                                  //設定第3行顯示的文字

  display.display();                                                 //顯示資料緩衝區內的資料
  delay(2000);
  display.clearDisplay();                                        //清除資料緩衝區內的資料
}

void testdrawline() {
  int16_t i;

  display.clearDisplay(); // Clear display buffer

  for(i=0; i<display.width(); i+=4) {
    display.drawLine(0, 0, i, display.height()-1, SSD1306_WHITE);
    display.display(); // Update screen with each newly-drawn line
    delay(1);
  }
  for(i=0; i<display.height(); i+=4) {
    display.drawLine(0, 0, display.width()-1, i, SSD1306_WHITE);
    display.display();
    delay(1);
  }
  delay(250);

  display.clearDisplay();

  for(i=0; i<display.width(); i+=4) {
    display.drawLine(0, display.height()-1, i, 0, SSD1306_WHITE);
    display.display();
    delay(1);
  }
  for(i=display.height()-1; i>=0; i-=4) {
    display.drawLine(0, display.height()-1, display.width()-1, i, SSD1306_WHITE);
    display.display();
    delay(1);
  }
  delay(250);

  display.clearDisplay();

  for(i=display.width()-1; i>=0; i-=4) {
    display.drawLine(display.width()-1, display.height()-1, i, 0, SSD1306_WHITE);
    display.display();
    delay(1);
  }
  for(i=display.height()-1; i>=0; i-=4) {
    display.drawLine(display.width()-1, display.height()-1, 0, i, SSD1306_WHITE);
    display.display();
    delay(1);
  }
  delay(250);

  display.clearDisplay();

  for(i=0; i<display.height(); i+=4) {
    display.drawLine(display.width()-1, 0, 0, i, SSD1306_WHITE);
    display.display();
    delay(1);
  }
  for(i=0; i<display.width(); i+=4) {
    display.drawLine(display.width()-1, 0, i, display.height()-1, SSD1306_WHITE);
    display.display();
    delay(1);
  }

  delay(2000); // Pause for 2 seconds
}

void testdrawrect(void) {
  display.clearDisplay();

  for(int16_t i=0; i<display.height()/2; i+=2) {
    display.drawRect(i, i, display.width()-2*i, display.height()-2*i, SSD1306_WHITE);
    display.display(); // Update screen with each newly-drawn rectangle
    delay(1);
  }

  delay(2000);
}

void testfillrect(void) {
  display.clearDisplay();

  for(int16_t i=0; i<display.height()/2; i+=3) {
    // The INVERSE color is used so rectangles alternate white/black
    display.fillRect(i, i, display.width()-i*2, display.height()-i*2, SSD1306_INVERSE);
    display.display(); // Update screen with each newly-drawn rectangle
    delay(1);
  }

  delay(2000);
}

void testdrawcircle(void) {
  display.clearDisplay();

  for(int16_t i=0; i<max(display.width(),display.height())/2; i+=2) {
    display.drawCircle(display.width()/2, display.height()/2, i, SSD1306_WHITE);
    display.display();
    delay(1);
  }

  delay(2000);
}

void testfillcircle(void) {
  display.clearDisplay();

  for(int16_t i=max(display.width(),display.height())/2; i>0; i-=3) {
    // The INVERSE color is used so circles alternate white/black
    display.fillCircle(display.width() / 2, display.height() / 2, i, SSD1306_INVERSE);
    display.display(); // Update screen with each newly-drawn circle
    delay(1);
  }

  delay(2000);
}

void testdrawroundrect(void) {
  display.clearDisplay();

  for(int16_t i=0; i<display.height()/2-2; i+=2) {
    display.drawRoundRect(i, i, display.width()-2*i, display.height()-2*i,
      display.height()/4, SSD1306_WHITE);
    display.display();
    delay(1);
  }

  delay(2000);
}

void testfillroundrect(void) {
  display.clearDisplay();

  for(int16_t i=0; i<display.height()/2-2; i+=2) {
    // The INVERSE color is used so round-rects alternate white/black
    display.fillRoundRect(i, i, display.width()-2*i, display.height()-2*i,
      display.height()/4, SSD1306_INVERSE);
    display.display();
    delay(1);
  }

  delay(2000);
}

void testdrawtriangle(void) {
  display.clearDisplay();

  for(int16_t i=0; i<max(display.width(),display.height())/2; i+=5) {
    display.drawTriangle(
      display.width()/2  , display.height()/2-i,
      display.width()/2-i, display.height()/2+i,
      display.width()/2+i, display.height()/2+i, SSD1306_WHITE);
    display.display();
    delay(1);
  }

  delay(2000);
}

void testfilltriangle(void) {
  display.clearDisplay();

  for(int16_t i=max(display.width(),display.height())/2; i>0; i-=5) {
    // The INVERSE color is used so triangles alternate white/black
    display.fillTriangle(
      display.width()/2  , display.height()/2-i,
      display.width()/2-i, display.height()/2+i,
      display.width()/2+i, display.height()/2+i, SSD1306_INVERSE);
    display.display();
    delay(1);
  }

  delay(2000);
}

void testdrawchar(void) {
  display.clearDisplay();

  display.setTextSize(1);      // Normal 1:1 pixel scale
  display.setTextColor(SSD1306_WHITE); // Draw white text
  display.setCursor(0, 0);     // Start at top-left corner
  display.cp437(true);         // Use full 256 char 'Code Page 437' font

  // Not all the characters will fit on the display. This is normal.
  // Library will draw what it can and the rest will be clipped.
  for(int16_t i=0; i<256; i++) {
    if(i == '\n') display.write(' ');
    else          display.write(i);
  }

  display.display();
  delay(2000);
}

void testdrawstyles(void) {
  display.clearDisplay();

  display.setTextSize(1);             // Normal 1:1 pixel scale
  display.setTextColor(SSD1306_WHITE);        // Draw white text
  display.setCursor(0,0);             // Start at top-left corner
  display.println(F("Hello, world!"));

  display.setTextColor(SSD1306_BLACK, SSD1306_WHITE); // Draw 'inverse' text
  display.println(3.141592);

  display.setTextSize(2);             // Draw 2X-scale text
  display.setTextColor(SSD1306_WHITE);
  display.print(F("0x")); display.println(0xDEADBEEF, HEX);

  display.display();
  delay(2000);
}

void testscrolltext(void) {
  display.clearDisplay();

  display.setTextSize(2); // Draw 2X-scale text
  display.setTextColor(SSD1306_WHITE);
  display.setCursor(10, 0);
  display.println(F("scroll"));
  display.display();      // Show initial text
  delay(100);

  // Scroll in various directions, pausing in-between:
  display.startscrollright(0x00, 0x0F);
  delay(2000);
  display.stopscroll();
  delay(1000);
  display.startscrollleft(0x00, 0x0F);
  delay(2000);
  display.stopscroll();
  delay(1000);
  display.startscrolldiagright(0x00, 0x07);
  delay(2000);
  display.startscrolldiagleft(0x00, 0x07);
  delay(2000);
  display.stopscroll();
  delay(1000);
}

void testdrawbitmap(void) {
  display.clearDisplay();

  display.drawBitmap(
    (display.width()  - LOGO_WIDTH ) / 2,
    (display.height() - LOGO_HEIGHT) / 2,
    logo_bmp, LOGO_WIDTH, LOGO_HEIGHT, 1);
  display.display();
  delay(1000);
}

#define XPOS   0 // Indexes into the 'icons' array in function below
#define YPOS   1
#define DELTAY 2

void testanimate(const uint8_t *bitmap, uint8_t w, uint8_t h) {
  int8_t f, icons[NUMFLAKES][3];

  // Initialize 'snowflake' positions
  for(f=0; f< NUMFLAKES; f++) {
    icons[f][XPOS]   = random(1 - LOGO_WIDTH, display.width());
    icons[f][YPOS]   = -LOGO_HEIGHT;
    icons[f][DELTAY] = random(1, 6);
    Serial.print(F("x: "));
    Serial.print(icons[f][XPOS], DEC);
    Serial.print(F(" y: "));
    Serial.print(icons[f][YPOS], DEC);
    Serial.print(F(" dy: "));
    Serial.println(icons[f][DELTAY], DEC);
  }

  for(;;) { // Loop forever...
    display.clearDisplay(); // Clear the display buffer

    // Draw each snowflake:
    for(f=0; f< NUMFLAKES; f++) {
      display.drawBitmap(icons[f][XPOS], icons[f][YPOS], bitmap, w, h, SSD1306_WHITE);
    }

    display.display(); // Show the display buffer on the screen
    delay(200);        // Pause for 1/10 second

    // Then update coordinates of each flake...
    for(f=0; f< NUMFLAKES; f++) {
      icons[f][YPOS] += icons[f][DELTAY];
      // If snowflake is off the bottom of the screen...
      if (icons[f][YPOS] >= display.height()) {
        // Reinitialize to a random position, just off the top
        icons[f][XPOS]   = random(1 - LOGO_WIDTH, display.width());
        icons[f][YPOS]   = -LOGO_HEIGHT;
        icons[f][DELTAY] = random(1, 6);
      }
    }
  }
}

OLED顯示影片如下:https://www.youtube.com/watch?v=9GmNvnwMRaI

如果使用的OLED是 128×64,無法正常顯示的話,需要修改 Arduino下 Libraries 資料夾中Adafruit_SSD1306的 Adafruit_SSD1306.h,修改為 #define SSD1306_128_64。如下圖:

ESP32學習筆記-01:ESP32輔助板-01

資料來源:
1.IoT 物聯網應用:使用ESP32開發版與 Arduino C 程式語言
2.ESP32 x NBIoT 模組 i2c燈號測試程式

這是正式學習ESP32及Arduino C 語言的第一步,買了尤濬哲老師的書「IoT 物聯網應用:使用ESP32開發版與 Arduino C 程式語言」,以及ESP32、ESP32Cam、ESP32輔助板,入門IOT領域。

以下是尤老師的程式說明:
本範例說明如何使用本教學輔助板(ESP32 Matrix)的LED
教學板使用GPIO 26,27連接附加的I2C晶片
一、依照LED編號開啟或關閉:
MatrixInt(LED編號,開關),燈號(0~7),開關(1/0)

燈編號列表:
1.訊號指示燈:0~4
2.紅色LED:5
3.綠色LED:6
4.藍色LED:7

Example:
MatrixInt(1,1);//開1號燈
MatrixInt(5,0);//關5號燈


二、按信號強度開啟訊號指示燈:
MatrixLEDrssi(rssi);
MatrixLEDrssi(-60);//大於-65:5 顆Leds
MatrixLEDrssi(-70);//大於-75:4 顆Leds
MatrixLEDrssi(-80);//大於-85:3 顆Leds
MatrixLEDrssi(-90);//大於-95:2 顆Leds
MatrixLEDrssi(-100);//大於-105:1 顆Leds
//其餘均為0 Led

三、程式碼:(小弟有做一些小修改)

#include <Wire.h>
#include "MatrixInt.h"

void setup() {
  Serial.begin(115200);
  Wire.begin(26, 27);
}

void loop() {
  Serial.println("Turn on ALL LEDs by ID");
  for (int i = 0; i < 8; ++i)
  {
    MatrixInt(i, 1);//亮燈
    Serial.println("LED " +  String(i) + " 亮燈");//在序列監視埠輸出顯示「LED 號碼 亮燈」
    delay(1000);
    MatrixInt(i, 0);//關燈    
  }
  Serial.println("Turn on RSSI LED by RSSI value");

  for (int i = -50; i > -120; i=i-1)
  {
    Serial.println("RSSI = " +  String(i));
    MatrixLEDrssi(i);
    delay(300);
  }
  delay(1000);
}

ESP32 CAM With CH340燒錄 MicroPython in MacOS

資料來源:
1. https://lemariva.com/blog/2019/09/micropython-how-about-taking-photo-esp32
2. https://github.com/espressif/esptool
3. http://twarm.com/commerce/product_info.php?products_id=1323

硬體:
1. ESP32 CAM
2. MicroUSB傳輸線
3. 雙母頭杜邦線

軟體:
1. MicroPython 燒錄檔,下載連結:https://micropython.org/resources/firmware/esp32-20210623-v1.16.bin
2. esptool.py,一個可以燒錄ESP32CAM的 Free Software ,使用說明及安裝
3. Thonny ,Python IDE,可進行Python程式設計,也可以直接將程式碼燒錄到ESP32CAM,下載連結:https://thonny.org/

ESP32 CAM(副廠,內建USB-TTL介面),硬體相關資料如資料來源3

ESP32 CAM 接腳圖

將MicroUSB傳輸線連接到ESP32CAM,並將ESP32CAM的GPIO 0及GND短路,這樣就可以準備燒錄了。

將ESO32CAM的GPIO 0及GND短路
接線連接特寫

前往放置燒錄檔案bin檔所在位置的目錄,在目錄內點擊右鍵從終端機打開

先找出你使用的序列阜,

ls -l /dev/cu.*

先抹除ESP32 CAM的flash,我們使用esptool.py來燒錄。
如果你的USB TTL 是 CH340 請用這個

esptool.py --port /dev/cu.usbserial-1420 erase_flash

如果是 CP210x 請用下面的

esptool.py --port /dev/cu.SLAB_USBtoUART erase_flash

再寫入ESP32 CAM

#燒錄Micropython
esptool.py --chip esp32 --port /dev/cu.usbserial-1420 --baud 460800 write_flash -z 0x1000 <改成你的檔案(esp32-20210618-v1.16.bin)>

#燒錄Circuitpython
esptool.py --port /dev/cu.SLAB_USBtoUART --baud 460800 write_flash -z 0X1000 adafruit-circuitpython-adafruit_metro_esp32s2-en_US-6.3.0.bin
ESP32 CAM燒錄中
ESP32 CAM燒錄完成

韌體燒錄完畢,拆下GPIO 0及GND之間的短路線材,按下RST鍵,重新啟動ESP32CAM,即可使用 Thonny 來寫程式了。

參考資料3的燒錄流程

依照 資料來源3 的硬體資料說明,燒錄要一直按著「Flash」鍵,但是 esptool 燒錄,整個使用過程完全不需要按任何按鍵就完成燒錄了。

安裝 Thonny 完成後,開啟 Thonny 就會看到下面畫面,就代表 Thonny 已經與ESP32CAM完成連接了,並顯示你燒錄的 MicroPython 版本。

Thonny與ESP32CAM完成連接

如果沒有出現,起點選上方工具列點選 Run > Select Interpreter ,如下圖:

接著會出現下面視窗,選取你要連接的USBserial Port,在按下「OK」鍵,

這樣應該就可以連上 ESP32CAM了!