2021年7月26日 星期一

dd - 備份與回覆資料的小工具

Linux 與 freeBSD 系統中的 dd 指令是一個多功能的小工具,可以用於各種的資料拷貝動作:

  • 備份與回復整顆硬碟的資料。
  • 備份與回復原始設備檔案,例如 MBR(master boot record)。
  • 轉換資料格式,例如 ASCII 轉換為 EBCDIC,大小寫轉換等。
  • 建立固定大小的檔案。

dd 的原意為 data duplicator,但由於 dd 屬於較低階的資料處理工具,通常都會以管理者(root)權限來執行,如果稍有不慎,也很容易造成嚴重的後果(例如整顆硬碟的資料不見等等),所以有些人也把 dd 取名為 data destroyer。許多人在製作 Linux 的 USB 安裝隨身碟的時候,時常也會使用到 dd 指令。

基本使用方式

  • if=FILE:指定輸入檔案名稱(input file)為 FILE。
  • of=FILE:指定輸初檔案名稱(output file)為 FILE。
  • ibs=BYTES:指定輸入區塊大小(input block size),一次讀取 BYTES 位元組的資料,預設為 512
          位元組。
  • obs=BYTES:指定輸出區塊大小(output block size),一次寫入 BYTES 位元組的資料,預設為
          512 位元組。
  • bs=BYTES:指定 block size,一次讀取與寫入 BYTES 位元組的資料,此選項會覆蓋 ibs 與 obs 的
          設定。
  • cbs=BYTES:一次轉換 BYTES 位元組的資料。
  • count=N:只處理 N 個輸入區塊,每個區塊的大小為 ibs。
  • seek=N:在輸出時跳過輸出檔案的前 N 個區塊,每個區塊的大小為 obs。
  • skip=N:在輸入時跳過輸入檔案的前 N 個區塊,每個區塊的大小為 ibs。
  • conv=CONVS:指定資料的轉換選項,如果一次要指定多種轉換,則以逗點分隔。

以下是各種可用的轉換:

  • ascii:EBCDIC 轉 ASCII。
  • ebcdic:ASCII 轉 EBCDIC。
  • ibm:ASCII 轉 alternate EBCDIC。
  • block:將每一個區塊的資料結尾的換行字元替換為空白,並以空白將整個區塊補足 cbs 位元組。
  • unblock:將每個區塊結尾的空白字元替換為換行字元。
  • lcase:將大寫字母轉換成小寫。
  • ucase:將小寫字母轉換成大寫。
  • swab:將每一對輸入的位元組交換。
  • sync:將每一個輸入的區塊以 NUL 補足至 ibs 位元組的大小,如果是在 block 或是 unblock 的轉換
          中,則以空白字元來補足。
  • nocreat:不要建立輸出檔案。
  • notrunc:不要將輸出檔案截短。
  • noerror:發生錯誤時還是繼續執行。
  • fdatasync:讓資料同步實體寫入硬碟,不要留在緩衝區中。
再次提醒您,若以管理者權限執行 dd 時,請再三確認您所執行的指令內容是否正確,執行錯誤的指令可能導致整個系統與資料的損毀!

備份整顆硬碟

將 /dev/sda 所有的資料寫入 /dev/sdb:

sudo dd if=/dev/sda of=/dev/sdb

if 參數指定的是輸入檔案(input file),而 of 參數指定的是輸出檔案(output file),這行指令會將 /dev/sda 這顆硬碟的資料讀出來,然後寫進 /dev/sdb 這顆硬碟。

在整個過程中如果出現讀取錯誤的話,dd 就會停止執行,如果想要讓 dd 在出現讀取錯誤時還是繼續拷貝資料的話,就要加上 conv=noerror 參數,這個選項通常在備份資料時會使用到,另外加上 sync 可以讓 dd 以 synchronized I/O 的方式備份資料:

sudo dd if=/dev/sda of=/dev/sdb conv=noerror,sync

建立硬碟的備份影像檔

備份硬碟資料除了拿兩顆硬碟對拷之外,也可以直接將整顆硬碟的資料製作成影像檔,以這種方式備份資料會更有彈性:

sudo dd if=/dev/hda of=~/hdadisk.img

這行指令會將 /dev/sda 這顆硬碟的資料讀出來,儲存至 ~/hdadisk.img 這個影像檔中。

從備份影像檔回復硬碟資料

如果要以影像檔回復硬碟的資料,就執行:

sudo dd if=hdadisk.img of=/dev/hdb

這樣就會把 hdadisk.img 影像檔中的資料回復至 /dev/sdb 這顆硬碟(原本 /dev/sdb 硬碟中的所有資料會被覆蓋掉,請小心使用)。

備份磁碟分割區

如果只要備份單一個磁碟分割區,可以使用:

sudo dd if=/dev/hda1 of=~/partition1.img

兩個磁碟分割區對拷:

sudo dd if=/dev/sda1 of=/dev/sdb1 bs=4096 conv=noerror,sync

這樣會將 /dev/sda1 的資料同步至 /dev/sdb1,使用前請確認 /dev/sdb1 分割區大小至少要比 /dev/sda1 大。

備份 CD/DVD 光碟

將光碟片放進光碟機之後,就可以使用這樣的指令將光碟整個備份成 ISO 檔:

sudo dd if=/dev/cdrom of=tgsservice.iso bs=2048

如果您放進光碟片時,系統會自動掛載,那麼在使用 dd 備份之前,建議可以先將光碟卸載,避免不必要的光碟讀取動作。

壓縮資料

如果資料很大,可以配合 gzip 將資料壓縮起來,直接輸出成壓縮過的影像檔:

sudo dd if=/dev/sda | gzip > sdadisk.img.gz

而要從壓縮過的影像檔回復資料,可以使用:

sudo gzip -dc sdadisk.img.gz | dd of=/dev/sda

MBR 的備份與回復

如果資料很大,可以配合 gzip 將資料壓縮起來,直接輸出成壓縮過的影像檔:

sudo dd if=/dev/sda | gzip > sdadisk.img.gz

而要從壓縮過的影像檔回復資料,可以使用:

sudo gzip -dc sdadisk.img.gz | dd of=/dev/sda

MBR 的備份與回復

備份 MBR(master boot record):

sudo dd if=/dev/sda of=mbr.img bs=446 count=1

回復 MBR:

sudo dd if=mbr.img of=/dev/sda

查看 MBR 內容:

sudo dd if=/dev/hda of=mbr.bin bs=512 count=1
od -xa mbr.bin

ASCII 與 EBCDIC 轉換

將 ASCII 轉換為 EBCDIC:

dd if=textfile.ascii of=textfile.ebcdic conv=ebcdic

將 EBCDIC 轉換為 ASCII:

dd if=textfile.ebcdic of=textfile.ascii conv=ascii

大小寫轉換

轉換為大寫:

dd if=file1 of=file2 conv=ucase

轉換為小寫:

dd if=file1 of=file2 conv=lcase

建立固定大小的檔案

建立一個 10MB 大小的檔案:

dd if=/dev/zero of=file1 bs=10485760 count=1

這裏的 block size 的計算方式為 10*1024*1024 = 10MB。

修改檔案內容

將檔案開頭的 512 bytes 改為 null:

dd if=/dev/zero of=file1 bs=512 count=1 conv=notrunc

這裡的 notrunc 參數代表不要將輸出檔案截短,只取代開頭的前 512 bytes,其餘內容不變。假設 file1 不存在,那麼這行指令就會建立一個 512 bytes 的 file1 檔案。

使用 dd 指令將 Linux ISO 檔製作成 Live USB 隨身碟

這裡介紹如何在 Linux 中將一般 Linux 的 ISO 檔用 dd 指令寫入 USB 隨身碟,製作成 Live USB 或是安裝系統用的隨身碟。

大部分的 Linux 發行版都會以 ISO 映像檔的形式提供使用者下載,以前大家都會將 ISO 映像檔用燒錄成 CD 或 DVD 安裝光碟,再放進機器的光碟機中安裝系統,不過現在光碟片已經逐漸被 USB 隨身碟所取代了,有些新電腦甚至已經不再配有光碟機,以 USB 隨身碟的方式來安裝系統已經成為主流。

如果手上沒有可用的 USB 隨身碟,也可以拿 SD 或 MicroSD 等記憶卡,接上讀卡機之後也可以作為安裝 Linux 用的儲存媒體,其使用方式跟一般的 USB 隨身碟一模一樣。

STEP 1

將 USB 隨身碟插入 Linux 的電腦中,查詢一下目前所有硬碟與 USB 隨身碟的狀況。

lsblk

NAME        MAJ:MIN RM  SIZE RO TYPE MOUNTPOINT
sda           8:0    1  7.6G  0 disk 
└─sda1        8:1    1  7.6G  0 part /media/pi/ESD-USB
sdc           8:33   1  7.4G  0 disk 
└─sdc1        8:33   1  7.4G  0 part /media/pi/disk
mmcblk0     179:0    0 14.9G  0 disk 
├─mmcblk0p1 179:1    0   63M  0 part /boot
└─mmcblk0p2 179:2    0 14.8G  0 part /

一般來說在 USB 隨身碟插入 Linux 系統之後,系統會自動將其掛載,我們必須先從這個列表中找出我們要使用的 USB 隨身碟是那一個,最簡單的方式就是從這裡的 SIZE 來看,若看不出來的話,就打開其掛載的目錄,實際看一下裡面的內容。

這裡我所要使用的 USB 隨身碟是掛載在 /media/pi/disk 這一個位置,而其對應的硬碟路徑則是 /dev/sdc,找到這個代號之後,就可以繼續下一步了。

在判斷磁碟代號的時候,要非常小心,絕對不可以搞錯,如果誤判磁碟代號的話,在執行後續的資料寫入動作時,就會可能造成整個系統損毀。

STEP 2

從 Linux 的桌面環境下卸載 USB 隨身碟(在檔案總管按下退出的按鈕),或是使用指令卸載:

umount /media/pi/disk

卸載後再次確認一下卸載的 USB 隨身碟是否正確:

lsblk

STEP 3

使用 dd 指令將 Linux 的 ISO 映像檔寫入 USB 隨身碟:

sudo dd if=ubuntu-16.10-desktop-amd64.iso of=/dev/sdc bs=1M

這個指令是將整個 ISO 檔的內容寫入 USB 隨身碟,所以要等比較久一點,在寫入的期間並不會有任何輸出訊息,完成後會出現類似這樣的訊息:

/dev/sdc bs=1M
1520+0 records in
1520+0 records out
1593835520 bytes (1.6 GB) copied, 493.732 s, 3.2 MB/s

這樣就完成了 USB 隨身碟的製作了,接著就可以將隨身碟拔下來,插入要安裝 Linux 系統的機器進行安裝了。

How to write FreeBSD image to USB disk for installation using dd command

FreeBSD is an open-source and free Unix-like operating system descended from the Berkeley Software Distribution (BSD). This quick tutorial explains how to download and prepare the USB stick/pen boot drive to install FreeBSD 12.2 or 13.x to install FreeBSD using the target computer’s USB port.

Head over to the FreeBSD download page and make sure you download the latest stable bootable version. I will download *-memstick.img*, which contains all of the files needed to install FreeBSD, its source, and the Ports Collection using a USB stick and boot the system.

STEP 1 - Downloading FreeBSD image

use the wget command you can download a file with curl too:

wget
https://download.freebsd.org/ftp/releases/amd64/amd64/ISO-IMAGES/12.2/FreeBSD-12.2-RELEASE-amd64-memstick.img.xz

Session :

--2020-12-30 00:46:08--  https://download.freebsd.org/ftp/releases/amd64/amd64/ISO-IMAGES/12.2/FreeBSD-12.2-RELEASE-amd64-memstick.img.xz
Resolving download.freebsd.org (download.freebsd.org)... 203.80.16.151, 2404:a8:3ff::15:0
Connecting to download.freebsd.org (download.freebsd.org)|203.80.16.151|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 687961180 (656M) [application/octet-stream]
Saving to: 'FreeBSD-12.2-RELEASE-amd64-memstick.img.xz'
             
FreeBSD-12.2-RELEAS 100%[==================>] 656.09M  8.26MB/s    in 81s     
             
2020-12-30 00:47:29 (8.09 MB/s) - 'FreeBSD-12.2-RELEASE-amd64-memstick.img.xz' saved [687961180/687961180]

Grab checksum file to verify boot disk image :

wget
https://download.freebsd.org/ftp/releases/amd64/amd64/ISO-IMAGES/12.2/CHECKSUM.SHA512-FreeBSD-12.2-RELEASE-amd64

Verify integrity of a file :

sha512sum --ignore-missing -c CHECKSUM.SHA512-FreeBSD-12.2-RELEASE-amd64"

We should see something as follows :

FreeBSD-12.2-RELEASE-amd64-memstick.img.xz: OK

For FreeBSD 13.x :

Download FreeBSD 13 by visiting official page:

$ wget https://download.freebsd.org/ftp/releases/amd64/amd64/ISO-IMAGES/13.0/FreeBSD-13.0-RELEASE-amd64-memstick.img.xz
$ wget https://download.freebsd.org/ftp/releases/amd64/amd64/ISO-IMAGES/13.0/CHECKSUM.SHA512-FreeBSD-13.0-RELEASE-amd64
$ sha512sum --ignore-missing -c CHECKSUM.SHA512-FreeBSD-13.0-RELEASE-amd64
FreeBSD-13.0-RELEASE-amd64-memstick.img.xz: OK
STEP 2 - Finding out your USB flash drive name

Insert the USB pen into your computer’s USB port and run the dmesg to see USB device name :

dmesg

The output indicates that I am using /dev/sda on Linux :

[ 2314.466297] usb 1-2: new high-speed USB device number 9 using xhci_hcd
[ 2314.629772] usb 1-2: New USB device found, idVendor=0951, idProduct=1666, bcdDevice= 0.01
[ 2314.629779] usb 1-2: New USB device strings: Mfr=1, Product=2, SerialNumber=3
[ 2314.629782] usb 1-2: Product: DataTraveler 3.0
[ 2314.629786] usb 1-2: Manufacturer: Kingston
[ 2314.629788] usb 1-2: SerialNumber: C81F660E262EF1713606D2C2
[ 2314.655320] usb-storage 1-2:1.0: USB Mass Storage device detected
[ 2314.655377] scsi host0: usb-storage 1-2:1.0
[ 2314.655474] usbcore: registered new interface driver usb-storage
[ 2314.656962] usbcore: registered new interface driver uas
[ 2315.663917] scsi 0:0:0:0: Direct-Access     Kingston DataTraveler 3.0      PQ: 0 ANSI: 6
[ 2315.664256] sd 0:0:0:0: Attached scsi generic sg0 type 0
[ 2315.665411] sd 0:0:0:0: [sda] 30218842 512-byte logical blocks: (15.5 GB/14.4 GiB)
[ 2315.665723] sd 0:0:0:0: [sda] Write Protect is off
[ 2315.665725] sd 0:0:0:0: [sda] Mode Sense: 4f 00 00 00
[ 2315.665939] sd 0:0:0:0: [sda] Write cache: disabled, read cache: enabled, doesn't support DPO or FUA
[ 2315.682431] sda: sda1 sda2
[ 2315.684945] sd 0:0:0:0: [sda] Attached SCSI removable disk
[ 2316.154272] ISO 9660 Extensions: Microsoft Joliet Level 3
[ 2316.155734] ISO 9660 Extensions: Microsoft Joliet Level 3
[ 2316.159081] ISO 9660 Extensions: RRIP_1991A

Your device name will vary. For instance, here is what reported on my FreeBSD system (/dev/da0) :

ugen0.3: <SanDisk Ultra> at usbus0
umass0 on uhub2
umass0: <SanDisk Ultra, class 0/0, rev 2.10/1.00, addr 2> on usbus0
umass0: SCSI over Bulk-Only; quirks = 0xc100
umass0:8:0: Attached to scbus8
da0 at umass-sim0 bus 0 scbus8 target 0 lun 0
da0: <SanDisk Ultra 1.00> Removable Direct Access SPC-4 SCSI device
da0: Serial Number 4C531003510422116045
da0: 40.000MB/s transfers
da0: 59232MB (121307136 512 byte sectors)
da0: quirks=0x2<lNO_6_BYTE>

Make sure you extract .xz file as follows :

xz -dv FreeBSD-12.2-RELEASE-amd64-memstick.img.xz

For FreeBSD 13.x image :

xz -dv FreeBSD-13.0-RELEASE-amd64-memstick.img.xz

FreeBSD-13.0-RELEASE-amd64-memstick.img.xz (1/1)
100 %     661.7 MiB / 1,040.4 MiB = 0.636   173 MiB/s       0:05
Step 3 - Writing FreeBSD image to USB flash drive

Warning:
Be careful with the correct USB pen device/drive name. The wrong device name will destroy the existing data on the specified USB drive.

Say, If your USB stick is /dev/sda under Linux, you would run :

# Find mounted partition for /dev/sda #
$ mount | grep '/dev/sda'
# Unmount all mounted partition for /dev/sda #
$ sudo umount /dev/sda2
# Write it #
$ sudo dd if=FreeBSD-12.2-RELEASE-amd64-memstick.img \
of=/dev/sda bs=1M conv=sync status=progress

For FreeBSD 13.x :

sudo dd if=FreeBSD-13.0-RELEASE-amd64-memstick.img \
of=/dev/sda bs=1M conv=sync status=progress

If your USB flash stick name is /dev/da0 under FreeBSD, you would run :

# Find mounted partition for /dev/da0 #
$ mount | grep '/dev/da0'
# Unmount all mounted partition for /dev/da0 #
$ sudo umount /dev/da0
# Write it #
$ sudo dd if=FreeBSD-12.2-RELEASE-amd64-memstick.img \
of=/dev/da0 bs=1M conv=sync
Step 4 - Installing FreeBSD 12.2

Insert the newly created USB pen drive with FreeBSD installer in the target computer. Please turn it on. Make sure you boot from USB media. For example :

Once booted, you will see the FreeBSD boot menu followed by FreeBSD installer :

Now follow the on-screen instructions to install the FreeBSD operating system.

2021年7月21日 星期三

ESP32 的第一課

ESP32 晶片內部有兩個處理器:「32 位元雙核心處理器」和「8 位元低功耗處理器(簡稱:ULP)」。其中 ESP32 主處理器的兩個核心分別叫做 PRO_CPUAPP_CPU

  • PRO_CPU:代表 Protocol CPU,負責處理和操控 WiFi、藍芽和其他介面(I2C、SPI...等)。
  • APP_CPU:代表 Application CPU,執行主程式碼。

規格比較

型號ESP32ESP8266
處理器Xtensa 雙核心或單核心 32 位元 LX6Xtensa 單核心 32 位元 L106
工作時脈160/240 MHz80 MHz
協同處理器ULP
SRAM 主記憶體520 KB120 KB
外部 Flash 快閃記憶體最高支援 16MB最高支援 16 MB
RTC(即時鐘)記憶體16 KB
WiFi2.4 GHz 頻段的 802.11 b/g/n2.4 GHz 頻段的 802.11 b/g/n
藍芽4.2 版 BR/EDR/BLE
乙太網路10/100 Mbps
CAN 介面 2.0
GPIO3416
UART 序列埠31.5(其中一組僅有傳送腳)
I2C 介面21
I2S 介面22
SPI 介面42
ADC(類比轉數位)18 個(12 位元),最大輸入電壓 3.6V1 個(10 位元),最大輸入電壓 1.1V
DAC(數位轉類比)2個(8 位元)2個(8 位元)
PWM 輸出168
觸控介面10
溫度感測器有,用於測量晶片溫度
霍爾感測器有,用於檢測磁力變化
安全性安全啟動 Flash 加密 1024 OTP
加密(crypto)AES、SHA-2、RSA、ECC、ENG
功耗深度睡眠 10 uA深度睡眠 2 uA
註①:CAN 是 Controller Area Network(控制器區域網路)通訊協定的簡稱,基本用於工廠、汽車、雜
   訊較多的場所。

ESP32 圖例

NodeMCU-32S

NodeMCU-32S Pins MAP

ESP32的功能接腳與限制

  • Serial
    硬體接腳 UART 0:TX(GPIO1)、RX(GPIO3)
    硬體接腳 UART 1:TX(GPIO9)、RX(GPIO10)
    硬體接腳 UART 2:TX(GPIO17)、RX(GPIO16)
    軟體模擬接腳:事實上,ESP32 軟體模擬序列埠,可以使用在任意接腳,其宣告語法為「Seria2.begin(9600,SERIAL_8N1,18,19)」,其中:
       Serial2:類別變數名
       9600:鮑率
       SERIAL_8N1:代表沒有同位檢查
       18:GPIO18 為 RX
       19:GPIO19 為 TX
  • I2C
    SDA:GPIO22
    SCL:GPIO21
  • SPI
    CLK:GPIO14
    MISO:GPIO12
    MOSI:GPIO13
    SS:GPIO15
  • VSPI
    SCK:GPIO18
    MISO:GPIO19
    MOSI:GPIO23
    SS:GPIO5
  • 接腳的限制
    (1)接腳的最大輸入電壓為 3.6V
    (2)不支援上拉電阻(INPUT_PULLUP)的接腳:32、35、34、39、36
    (3)不支援下拉電阻(INPUT_PULLDOWN)的接腳:0、1、2、3
    (4)接腳 34、35、36、39 只支援輸入
    (5)接腳 0、5、14、15 不建議設為 INPUT
    (6)接腳 1、3、12 不建議設為 OUTPUT
    (7)接腳 CLK、SD0、SD1、SD3、CMD 用於上傳程式至晶片,不能使用
    (8)接腳定額的電流為 6mA,最大電流為 12mA
註②:ESP32 的 GPIO 接腳 6、7、8、9、10、11 都連接到快閃記憶體,所以 Serial1 的
   RX(GPIO9)、TX(GPIO10)無法使用程式控制,是專門用來與記憶體溝通的接腳

ESP32 的 printf() 格式化輸出

雖然 Arduino 不支援 printf(),但基於 ESP-IDF 工具的 ESP32 跟 C 語言一樣,可以支援這個萬用的格式化輸出工具。

符號說明
%c單一字元(char)
%s字串(string)
%u無正負號(unsigned)整數
%d整數(integer)
%lu無正負長整數(unsigned long)
%ld長整數(long)
%x16 進位(hexadecimal)整數
%f浮點數(float),輸出全部整數部分,6 位小數部分,超過 6 位則四捨五入
%.nf輸出小數點後 n 位數字的浮點數,請留意 n 前面有個點「.」。例如:3.14159 用 %.3f 輸出為 3.142
%%顯示 %

數位輸出/輸入

接觸新的開發板時,最先要留意的是它的工作電壓規格

  • ESP32 開發版的工作電壓(VDD)是 3.3V。
  • 數位及類比腳的最大容許輸入電壓是 VDD+0.3V,也就是 3.6V。
  • pinMode(pin,mode);

    mode 可能的值:

    • INPUT:當接腳設成「數位輸入」時,部分接腳會因以下的事件而不建議使用。
      (1) TX(GPIO1)連接 USB 埠,開機時會輸出除錯訊息。
      (3) RX(GPIO1)連接 USB 埠,開機時會處於高電位狀態。
      (12) GPIO1 開機時須維持在低電為,否則不能開機。
    • OUTPUT:當接腳設成「數位輸出」時,部分接腳會因以下的事件而不建議使用。
      (1) GPIO0、GPIO5、GPIO14、GPIO15 開機時會輸出短暫 PWM 訊號,不建議使用。
      (1) GPIO1、GPIO3 連接 USB 埠。
      (1) GPIO34、GPIO35、GPIO36、GPIO39 僅具備數位輸入功能。
    • INPUT_PULLUP:GPIO32~GPIO39 不具備上拉電阻之功能。
    • INPUT_PULLDOWN:GPIO0~GPIO3 不具備下拉電阻之功能。

    讀取電容觸控接腳

    ESP32 有 10 個接腳具備電容觸控感應功能,可以當除觸控開關,觸控腳的感測值是類比值,會因接線形式(觸控面積大小)、距離等因素而不同。讀取觸控感測值的語法為:

    int touchValue(pin);

    可使用的觸控接腳如下表:
    T0(GPIO4)T1(GPIO0)T2(GPIO2)T3(GPIO5)T4(GPIO13)
    T5(GPIO12)T6(GPIO14)T7(GPIO27)T8(GPIO33)T9(GPIO32)

    範例:底下程式將在使用者碰接腳 T3(GPIO15)時,會點亮開發版內建的 LED。

    #include LED LED_BUILTIN
    const byte touchPin = 15;
    const int thresHold = 20;
    int touchValue ;
    void setup()
    {
        pinMode(LED_BUILTIN, OUTPUT);
    }
    
    void loop()
    {
        touchValue = touchRead(touchPin);
    
        if(touchValue < thresHold)
        {
            digitalWrite(LED, LOW);
        }
        else
        {
            digitalWrite(LED, HIGH);
        }
        delay(1000);
    }

    程式的執行效果:

    ESP32 的類比輸入

    ESP32 類比輸入電壓的上限是 3.6V,正常最高值為 3.3V,讀取類比輸入值的指令同樣是 analogRead(),接腳可用 A0(GPIO36)、A3(GPIO39)、A4(GPIO32)、A5(GPIO33)、A6(GPIO34)、A7(GPIO35) 等編號。

    Arduino UNO 的類比數位轉換器(ADC)採 10 位元取藥,所以量化值介於 0~1023,ESP32 的 ADC 預設採 12 位元取樣,量化值介於 0~4095。

    設定 ESP32 的 ADC 類比輸入電壓範圍與取樣位元的方法:

    • analogSetAttenuation(衰減值):其中衰減值可能為
    • ADC_0db:0db 衰減,電壓輸入上限為 1.00V。
    • ADC_2_5db:2.5db 衰減,電壓輸入上限為 1.34V。
    • ADC_6db:6db 衰減,電壓輸入上限為 2.00V。
    • ADC_11db:11db 衰減,電壓輸入上限為 3.6V。
    • analogSetWidth(寬度):設定 ADC 的取樣位元
    • analogSetWidth(9):取樣寬度為 0~511。
    • analogSetWidth(10):取樣寬度為 0~1023。
    • analogSetWidth(12):取樣寬度為 0~4095。
    • analogRead(pin)::可用的類比輸入接腳為
    • A0:GPIO36,只有輸入功能。
    • A3:GPIO39,只有輸入功能。
    • A6:GPIO34,只有輸入功能。
    • A7:GPIO35,只有輸入功能。
    • A4:GPIO32。
    • A5:GPIO33。

    由於 ESP32 類比輸入的最高電壓是 3.6V,但如果類比感測器的輸出電壓是 5V,直接將感測器 5V 的輸出接到 ESP32 的類比輸入接腳,很可能會造成 ESP32 的損壞,所以我們需要將 5V 轉為 3V3。

    透過右圖的接線方式,我們可以順利達成這個目標。

    ESP32 內建的霍爾感應器

    ESP32 晶片內建一個可以偵測磁場變化的「霍爾效應」感測器,霍爾感測器的輸出電壓和磁場的強度與距離成正比。

    霍爾感測器普遍應用在檢測物體移動、馬達轉速、開闔等。例如:無刷馬達的轉子包覆著磁石,在馬達內部裝設霍爾感測器,透過偵測磁石南北極的變化便可以測量馬達的轉速。在手機、平板電腦內部裝設霍爾感測器,同時在保護蓋說這一個磁石,便可以達成偵測保護蓋開闔的動作。

    註③:霍爾效應(Hall effect)是指當固體導體放置在一個磁場內,且有電流通過時,導體內的電荷載子
       會因受力而偏向一邊,繼而產生電壓。

    讀取霍爾感測值的函式叫做 hallRead(),它將傳回帶正負號的整數值。

    ESP32 的類比輸出

    ESP32 的 PWM 解析度不是固定值,而是跟頻率設定息息相關,根據樂鑫公司的 ESP32 官方技術文件(http://bit.ly/39wGPa5)指出:PWM 解析度隨著頻率提高而降低,解析度範圍介於 1~16 位元,頻率上限為 40MHz,解析度的計算公式請參考下圖。

    設定 ESP32 的 PWM 輸出指令

    設置 ESP32 的 PWM 輸出、頻率,須透過兩個函式來達成,韓式名字開頭的 ledc 代表 LED Control(LED 控制,簡稱 LEDC)。

    • ledcSetup(通道,頻率,解析度):設置 PWM 產生器
    • ledcAttachPin(接腳編號,通道)
      通道相當於 PWM 產生器,ESP32 內部共有 16 個 PWM 通道(0~15),每個通道的輸出都可設定給任一個數位輸出接腳。所以 ESP32 最多同時擁有 16 個 PWM 輸出腳。PWM 設定完畢,就可以執行 ledcWrite() 輸出 PWM 訊號。
    • ledcWrite(通道,PWM 輸出):參閱以下範例
      ledcSetup(0,5000,10); //--- 為通道 0 設定一個 5KHz、10 位元取樣頻率的 PWM 產生器
      ledcAttachPin(5,10); //--- 把 GPIO5 指定給通道 0 使用
      ledcWrite(0,614); //--- 在通道 0 輸出 614 的 PWM 訊號

    利用 PWM 輸出完成調光器

    實驗電路:利用一個 10K 的便電阻連接在 A4 接腳,接線示範圖如下

    程式碼如下:

    #define BITS 10
    
    void setup()
    {
        pinMode(LED_BUILTIN,OUTPUT);
        analogSetAttenuation(ADC_11db);
        analogSetWidth(BITS);
    
        ledcSetup(0,5000,BITS);
        ledcAttachPin(LED_BUILTIN,0);
    }
    
    void loop()
    {
        int val=1023-analogRead(A4);
        ledcWrite(0,val);
    }

    調控 PWM 訊號的頻率發出聲音

    ESP32 不支援 Arduino 的 tone() 函式,但 Arduino 的 tone() 本質上是用 PWM 來調控輸出波形(聲音)的頻率,所以 ESP32 也同樣可以用 PWM 訊號來驅動發音元件。透過改變訊號的「工作週期」來調整輸出電壓,以達成在揚聲器上發出不同音高的聲音。

    在 ESP32 上改變 PWM 頻率的函式語法:

    • ledcWriteTone(通道,頻率);
    • ledcWriteNote(通道,音名常數,音階編號);

    音名、音階編號與頻率的對應表:

    音名音名常數音階編號
    012345678
    CNOTE_C163365131262523104620934186
    C#NOTE_Cs173569139277554110922174435
    DNOTE_D183773147294587117523494699
    D#NOTE_Eb193978156311622124524894978
    ENOTE_E214182165330659131926375274
    FNOTE_F224487175349698139727945588
    F#NOTE_Fs234693185370740148029605920
    GNOTE_G254998196392784156831366272
    G#NOTE_Gs2652104208415831166133226645
    ANOTE_A2855110220440880176035207040
    A#NOTE_Bb2958117233466932186437297459
    BNOTE_Bb3162123247493988197639517902

    下面的兩行程式碼都可以在通道 0 產生中央 C 音:

    ledcWriteTone(0,262); //--- 在通道 0 產生 262Hz 的頻率
    ledcWriteNote(0,NOTE_C,4)

    另,Arduino 有個停止音頻輸出的 noTone(); 函式,在 ESP32 中可用底下這行敘述達成:

    ledcWriteTone(0,0); //--- 把通道 0 的頻率設為 0

    其他 LEDC 控制函式:

    • ledcRead(通道):傳回指定通道的工作週期(佔空比)值。
    • ledcReadFreq(通道):傳回指定通道的頻率值(若佔空比為 0 則傳回 0)。
    • ledcDetachPin(接腳):解除指定接腳的 LEDC 功能,即取消 PWM 輸出。

    最後,我們用一段程式來展示控制 ESP32 讓蜂鳴器發出聲音救護車警示音,接線法請參閱下圖。

    #define BITS 10
    #define BUZZER 22 //--- 蜂鳴器接腳
    
    void setup()
    {
        ledcSetup(0,2000,BITS); //--- PWM 設定為:通道 0、頻率 20KHz、解析度 10 位元
        ledcAttachPin(BUZZER,0);
    }
    
    void loop()
    {
        ledcWriteNote(0,NOTE_E,5);
        delay(500);
        ledcWriteNote(0,NOTE_A,4);
        delay(500);
    }