0
1

RaspberryPi で ESP32 のヒューズビット設定・ファームウェア書き込みマシンを作成する

Last updated at Posted at 2023-11-10

ESP32 のファームウェアを書いたりヒューズビットを設定したりするのは通常 PC から行いますがめんどくさいので RaspberryPi で自動書き込みができるようのものを作りました。

用意するもの

  • RaspberryPi
  • USBメモリ
  • ターゲットボード
  • 電子ブザー
  • メスメスジャンパーケーブル
  • RapsberryPi 用電源
  • 書き込み治具
    今回は、手持ちの RaspberryPi B+ を使用しています。

書き込み治具

DevKit のような開発ボードであれば書き込み治具は必要はありません。
しかしながら今回のターゲットはESP32のオリジナルボードなので、以下のようなものを用意しています。

IMG_20231110_103746.jpg

この治具は、ESP32ボードにこのようなメンテポートを作っていて、

IMG_20231110_104455.jpg

そこにコンタクトプローブをクリップして書き込みを行っています。

IMG_20231110_104514.jpg

この治具は、ダウンロードモードでリセットを行うことができるスイッチを装備しています。

IMG_20231110_104714.jpg

なおオリジナルボードでも、GPIO0をLoにしてResetをするとダウンロードモードになります。

作業

OSの設定

OSイメージとして以下をダウンロード。

  • Raspberry Pi OS Lite
    • Release date: October 10th 2023
    • System: 32-bit
    • Kernel version: 6.1
    • Debian version: 12 (bookworm)
    • Size: 568MB

ダウンロードした 2023-10-10-raspios-bookworm-armhf-lite.img.xz をこちらの記事の通り microSD カードに書き込み
「Ubuntu Linux で Raspian ZIP ファイルを microSD カードに書き込む ( xz ファイルも )」
https://qiita.com/nanbuwks/items/2c296549ff72dacf7adc

microSD カードを RaspberryPi B+ にセットして起動、

$ sudo apt upgrade したあとに以下をインストール

  • vim
  • git
  • picocom

ESP32 と接続

picocom を使用して、ESP32 のダウンロードモードダイアログが取得できることを確認。

$ picocom -b 115200 /dev/ttyUSB0
.
.
.
st:0x1 (POWERON_RESET),boot:0x3 (DOWNLOAD_BOOT(UART0/UART1/SDIO_REI_REO_V2))
waiting for download

となるのを確認します。

esptool の設定

https://github.com/espressif/esptool より、 esptool をダウンロード

$ git clone https://github.com/espressif/esptool.git
$ cd esptool

https://qiita.com/nanbuwks/items/339c21478fd31664e5ff
の通り、必要なモジュールをインストールします。

$ sudo apt install python3-ecdsa python3-bitstring  python3-pip python3-intelhex python3-serial python3-yaml python3-cryptography

また、reedsolo を pip でインストールします。

$ sudo pip install reedsolo

error: externally-managed-environment

× This environment is externally managed
╰─> To install Python packages system-wide, try apt install
    python3-xyz, where xyz is the package you are trying to
    install.
    
    If you wish to install a non-Debian-packaged Python package,
    create a virtual environment using python3 -m venv path/to/venv.
    Then use path/to/venv/bin/python and path/to/venv/bin/pip. Make
    sure you have python3-full installed.
    
    For more information visit http://rptl.io/venv

note: If you believe this is a mistake, please contact your Python installation or OS distribution provider. You can override this, at the risk of breaking your Python installation or OS, by passing --break-system-packages.
hint: See PEP 668 for the detailed specification.

というエラーが出たので、

cf/.
https://www.jeffgeerling.com/blog/2023/how-solve-error-externally-managed-environment-when-installing-pip3

$ sudo rm -rf /usr/lib/python3.11/EXTERNALLY-MANAGED

としてから

$ sudo pip install reedsolo

とするとインストールできました。

なお、 cryptography は pip だとエラーが出たので

$ sudo pip3 install cryptography
Collecting cryptography
  Downloading cryptography-41.0.5.tar.gz (630 kB)
     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 630.5/630.5 kB 2.2 MB/s eta 0:00:00
  Installing build dependencies ... error
  error: subprocess-exited-with-error
  
  × pip subprocess to install build dependencies did not run successfully.
  │ exit code: 1
  ╰─> [111 lines of output]

先に示した apt でインストールしています。

以下のようにして動作確認します。

$ python3 espefuse.py -p /dev/ttyUSB0 summary

うまくいくと、ヒューズビットが表示されます。

espefuse.py v4.7-dev
Connecting..........
Detecting chip type... Unsupported detection protocol, switching and trying again...
Connecting...
Detecting chip type... ESP32

=== Run "summary" command ===
EFUSE_NAME (Block) Description  = [Meaningful Value] [Readable/Writeable] (Hex Value)
----------------------------------------------------------------------------------------
Calibration fuses:
ADC_VREF (BLOCK0)                                  True ADC reference voltage                         = 1107 R/W (0b00001)

Config fuses:
WR_DIS (BLOCK0)                                    Efuse write disable mask                           = 0 R/W (0x0000)
RD_DIS (BLOCK0)                                    Disable reading from BlOCK1-3                      = 0 R/W (0x0)
DISABLE_APP_CPU (BLOCK0)                           Disables APP CPU                                   = False R/W (0b0)
DISABLE_BT (BLOCK0)                                Disables Bluetooth                                 = False R/W (0b0)
DIS_CACHE (BLOCK0)                                 Disables cache                                     = False R/W (0b0)
CHIP_CPU_FREQ_LOW (BLOCK0)                         If set alongside EFUSE_RD_CHIP_CPU_FREQ_RATED; the = False R/W (0b0)
                                                    ESP32's max CPU frequency is rated for 160MHz. 24
                                                   0MHz otherwise                                    
CHIP_CPU_FREQ_RATED (BLOCK0)                       If set; the ESP32's maximum CPU frequency has been = True R/W (0b1)
                                                    rated                                            
BLK3_PART_RESERVE (BLOCK0)                         BLOCK3 partially served for ADC calibration data   = False R/W (0b0)
CLK8M_FREQ (BLOCK0)                                8MHz clock freq override                           = 52 R/W (0x34)
VOL_LEVEL_HP_INV (BLOCK0)                          This field stores the voltage level for CPU to run = 0 R/W (0b00)
                                                    at 240 MHz; or for flash/PSRAM to run at 80 MHz.0
                                                   x0: level 7; 0x1: level 6; 0x2: level 5; 0x3: leve
                                                   l 4. (RO)                                         
CODING_SCHEME (BLOCK0)                             Efuse variable block length scheme                
   = NONE (BLK1-3 len=256 bits) R/W (0b00)
CONSOLE_DEBUG_DISABLE (BLOCK0)                     Disable ROM BASIC interpreter fallback             = True R/W (0b1)
DISABLE_SDIO_HOST (BLOCK0)                                                                            = False R/W (0b0)
DISABLE_DL_CACHE (BLOCK0)                          Disable flash cache in UART bootloader             = False R/W (0b0)

Flash fuses:
FLASH_CRYPT_CNT (BLOCK0)                           Flash encryption is enabled if this field has an o = 0 R/W (0b0000000)
                                                   dd number of bits set                             
FLASH_CRYPT_CONFIG (BLOCK0)                        Flash encryption config (key tweak bits)           = 0 R/W (0x0)

Identity fuses:
CHIP_PACKAGE_4BIT (BLOCK0)                         Chip package identifier #4bit                      = False R/W (0b0)
CHIP_PACKAGE (BLOCK0)                              Chip package identifier                            = 1 R/W (0b001)
CHIP_VER_REV1 (BLOCK0)                             bit is set to 1 for rev1 silicon                   = True R/W (0b1)
CHIP_VER_REV2 (BLOCK0)                                                                                = True R/W (0b1)
WAFER_VERSION_MINOR (BLOCK0)                                                                          = 1 R/W (0b01)
WAFER_VERSION_MAJOR (BLOCK0)                       calc WAFER VERSION MAJOR from CHIP_VER_REV1 and CH = 3 R/W (0b011)
                                                   IP_VER_REV2 and apb_ctl_date (read only)          
PKG_VERSION (BLOCK0)                               calc Chip package = CHIP_PACKAGE_4BIT << 3 + CHIP_ = 1 R/W (0x1)
                                                   PACKAGE (read only)                               

Jtag fuses:
JTAG_DISABLE (BLOCK0)                              Disable JTAG                                       = False R/W (0b0)

Mac fuses:
MAC (BLOCK0)                                       MAC address                                       
   = a0:a3:b3:0d:13:d4 (CRC 0xa7 OK) R/W 
MAC_CRC (BLOCK0)                                   CRC8 for MAC address                               = 167 R/W (0xa7)
MAC_VERSION (BLOCK3)                               Version of the MAC field                           = 0 R/W (0x00)

Security fuses:
UART_DOWNLOAD_DIS (BLOCK0)                         Disable UART download mode. Valid for ESP32 V3 and = False R/W (0b0)
                                                    newer; only                                      
ABS_DONE_0 (BLOCK0)                                Secure boot V1 is enabled for bootloader image     = False R/W (0b0)
ABS_DONE_1 (BLOCK0)                                Secure boot V2 is enabled for bootloader image     = False R/W (0b0)
DISABLE_DL_ENCRYPT (BLOCK0)                        Disable flash encryption in UART bootloader        = False R/W (0b0)
DISABLE_DL_DECRYPT (BLOCK0)                        Disable flash decryption in UART bootloader        = False R/W (0b0)
KEY_STATUS (BLOCK0)                                Usage of efuse block 3 (reserved)                  = False R/W (0b0)
SECURE_VERSION (BLOCK3)                            Secure version for anti-rollback                   = 0 R/W (0x00000000)
BLOCK1 (BLOCK1)                                    Flash encryption key                              
   = 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 R/W 
BLOCK2 (BLOCK2)                                    Security boot key                                 
   = 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 R/W 
BLOCK3 (BLOCK3)                                    Variable Block 3                                  
   = 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 R/W 

Spi Pad fuses:
SPI_PAD_CONFIG_HD (BLOCK0)                         read for SPI_pad_config_hd                         = 0 R/W (0b00000)
SPI_PAD_CONFIG_CLK (BLOCK0)                        Override SD_CLK pad (GPIO6/SPICLK)                 = 0 R/W (0b00000)
SPI_PAD_CONFIG_Q (BLOCK0)                          Override SD_DATA_0 pad (GPIO7/SPIQ)                = 0 R/W (0b00000)
SPI_PAD_CONFIG_D (BLOCK0)                          Override SD_DATA_1 pad (GPIO8/SPID)                = 0 R/W (0b00000)
SPI_PAD_CONFIG_CS0 (BLOCK0)                        Override SD_CMD pad (GPIO11/SPICS0)                = 0 R/W (0b00000)

Vdd fuses:
XPD_SDIO_REG (BLOCK0)                              read for XPD_SDIO_REG                              = False R/W (0b0)
XPD_SDIO_TIEH (BLOCK0)                             If XPD_SDIO_FORCE & XPD_SDIO_REG                   = 1.8V R/W (0b0)
XPD_SDIO_FORCE (BLOCK0)                            Ignore MTDI pin (GPIO12) for VDD_SDIO on reset     = False R/W (0b0)

Flash voltage (VDD_SDIO) determined by GPIO12 on reset (High for 1.8V, Low/NC for 3.3V)

書き込みマシンに仕上げる

ブザーを鳴らす

GPIO17にブザーをつけて3秒鳴るスクリプトを作成します。

IMG_20231101_143118.jpg

beep3sec.py


import RPi.GPIO as GPIO
import time
GPIO.setmode(GPIO.BCM)
GPIO.setup(17,GPIO.OUT)
GPIO.output(17,GPIO.HIGH)
time.sleep(3)
GPIO.output(17,GPIO.LOW)
GPIO.cleanup()

実行してうまく鳴ったら、/usr/local/bin に実行権をつけて保存しておきます。
同様に、以下のスクリプトも /usr/local/bin に作っておきます。

booboo.py
import RPi.GPIO as GPIO
import time
GPIO.setmode(GPIO.BCM)
GPIO.setup(17,GPIO.OUT)
GPIO.output(17,GPIO.HIGH)
time.sleep(1)
GPIO.output(17,GPIO.LOW)
time.sleep(0.4)
GPIO.output(17,GPIO.HIGH)
time.sleep(1)
GPIO.output(17,GPIO.LOW)
GPIO.cleanup()



pipipipipi.py
import RPi.GPIO as GPIO
import time
GPIO.setmode(GPIO.BCM)
GPIO.setup(17,GPIO.OUT)
GPIO.output(17,GPIO.HIGH)
time.sleep(0.04)
GPIO.output(17,GPIO.LOW)
time.sleep(0.1)
GPIO.output(17,GPIO.HIGH)
time.sleep(0.04)
GPIO.output(17,GPIO.LOW)
time.sleep(0.1)
GPIO.output(17,GPIO.HIGH)
time.sleep(0.04)
GPIO.output(17,GPIO.LOW)
time.sleep(0.1)
GPIO.output(17,GPIO.HIGH)
time.sleep(0.04)
GPIO.output(17,GPIO.LOW)
time.sleep(0.1)
GPIO.output(17,GPIO.HIGH)
time.sleep(0.04)
GPIO.output(17,GPIO.LOW)
GPIO.cleanup()

espfuse.sh
#!/bin/bash

WRITECMD="/home/pi/esptool-master/esptool.py --chip esp32 --port /dev/ttyUSB0 --baud 921600 --before default_reset --after hard_reset write_flash -z --flash_mode dio --flash_freq 80m --flash_size detect 0xe000 /mnt/boot_app0.bin 0x1000 /mnt/bootloader_dio_80m.bin 0x10000 /mnt/firmware.bin 0x8000 /mnt/partitions.bin"

FUSECMD="/home/pi/esptool-master/espefuse.py  --do-not-confirm -p /dev/ttyUSB0  set_flash_voltage 3.3V"

FUSEOK="Flash voltage (VDD_SDIO) set to 3.3V by efuse."
FUSENOTYET="Flash voltage (VDD_SDIO) determined by GPIO12 on reset"
WRITEOK="Hash of data verified."

while true
do
  FUSECHECKRESULT=`/home/pi/esptool-master/espefuse.py  -p /dev/ttyUSB0  summary | tail -1`
  echo $FUSECHECKRESULT
  if [[ $FUSEOK = $FUSECHECKRESULT  ]]; then
        WRITERESULT=$(eval ${WRITECMD})
        if [[ $WRITERESULT = *"$WRITEOK"*  ]]; then
          echo $WRITEOK
          echo "OK"
          python3 /usr/local/bin/pipipipipi.py
        else
                echo $WRITERESULT
                echo "Fuse is already 3.3V set but firmware hashdata NG"
                python3 /usr/local/bin/booboo.py
        fi
  else
        if [[ "$FUSECHECKRESULT" =~ "$FUSENOTYET" ]]; then
            WRITERESULT=$(eval ${FUSECMD})
            echo $WRITERESULT

        else
                echo "fuse bit setting NG"
                python3 /usr/local/bin/booboo.py
        fi
  fi
done

ヘッドレスで動作するので、起動したことがわかるように音を鳴らすようにします。
/etc/rc.local の最後らへんを、書き換えます。

書き換え前

.
.
.
_IP=$(hostname -I) || true
if [ "$_IP" ]; then
  printf "My IP address is %s\n" "$_IP"
fi


exit 0

書き換え後

.
.
.
_IP=$(hostname -I) || true
if [ "$_IP" ]; then
  printf "My IP address is %s\n" "$_IP"
fi


python3 /usr/local/bin/beep3sec.py
exit 0

再起動して、音が鳴るかどうかチェック。

USBメモリでファームウェア管理

ファームウェアを USB メモリに入れて管理するようにします。

PC で Arduino IDE をCUIで起動、スケッチを書きこむところまで行います。そのときにコマンドラインに表示されるラインをコピーします。

python /home/nanbuwks/Downloads/ardublock/arduino/linux64/AKBONE2020_linux_1.8.13/portable/packages/esp32/tools/esptool_py/2.6.1/esptool.py --chip esp32 --port /dev/ttyUSB0 --baud 921600 --before default_reset --after hard_reset write_flash -z --flash_mode dio --flash_freq 80m --flash_size detect 0xe000 /home/nanbuwks/Downloads/ardublock/arduino/linux64/AKBONE2020_linux_1.8.13/portable/packages/esp32/hardware/esp32/1.0.4/tools/partitions/boot_app0.bin 0x1000 /home/nanbuwks/Downloads/ardublock/arduino/linux64/AKBONE2020_linux_1.8.13/portable/packages/esp32/hardware/esp32/1.0.4/tools/sdk/bin/bootloader_dio_80m.bin 0x10000 /tmp/arduino_build_335897/ESP32_DUALDIAL_OSS.ino.bin 0x8000 /tmp/arduino_build_335897/ESP32_DUALDIAL_OSS.ino.partitions.bin 

上記の例では、 ESP32_DUALDIAL_OSS というスケッチをビルドして書き込んでいますが、以下のファイルが使われていることがわかります。

  • boot_app0.bin
  • bootloader_dio_80m.bin
  • ESP32_DUALDIAL_OSS.ino.bin
  • ESP32_DUALDIAL_OSS.ino.partitions.bin

Arduino IDE を閉じると tmp ファイルは消えてしまいます。なので IDE を閉じずに別ターミナルでこれらを USB メモリにコピーします。

  • <スケッチ名>.ino.bin
  • <スケッチ名>.ino.partitions.bin

については、

  • firmware.bin
  • partitions.bin

に名前を変更します。

Screenshot from 2023-11-01 14-59-07.png

USBメモリを起動時にマウントするように、 /etc/rc.local を以下のように変更します。

.
.
.
_IP=$(hostname -I) || true
if [ "$_IP" ]; then
  printf "My IP address is %s\n" "$_IP"
fi


mount /dev/sda1 /mnt
python3 /usr/local/bin/beep3sec.py
exit 0

として再起動、ファームウェアが /mnt 下に見えることを確認します。

うまくいけば、以下のように自動起動を設定して完成です。



mount /dev/sda1 /mnt
python3 /usr/local/bin/beep3sec.py
/usr/local/bin/espfuse.sh
exit 0

使い方

IMG_20231110_105212.jpg

準備

  • ターゲットボードは「wait for download」 モードになるようにしておきます。
  • RaspberryPi の電源が必要です。使用する RaspberryPi に応じて十分な電源を取るようにしてください。

起動

電源ONにして、起動完了までしばらく待ちます。起動完了するとブザーが長く一度鳴ります。

書き込みループ

起動完了すると USBシリアルで接続しているかどうかの無限ループに入り、接続していなかったり正常な応答が無ければブザーが2回鳴ります。

正常な応答があれば、各種設定を行って終了したらピピピピピとブザーが5回鳴ります。ブザーが5回鳴ったら、連続して書き込みが行われないようにすぐにESP32を取り外してください。

応用

もちろん、ESP32 でなくても USBシリアル経由で設定するもの、あるいはその他GPIOあんどで設定するものも、入れ替えれば対応できます。

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1