pico_fota OTA
A Bootloader That Refuses to Die: Firmware Recovery Over Ethernet, Served by a 92 KB Bootloader
0
Project description
pico_fota โ A Bootloader That Refuses to Die: Firmware Recovery Over Ethernet, Served by a 92 KB Bootloader
#W5500 #TOE #RP2040 #Bootloader #FOTA #Recovery #SecureBoot #AES-ECB #SHA256 #ioLibrary #Rollback
๐ Context: Commercial product firmware โ the recovery path of a shipping audio networking dongle by DickinsAudio (Australia). Verification status: WIZnet chip usage confirmed at code level (
Sn_MR_TCPsocket, ioLibrary DHCP, WIZnet OUI00:08:DC). Builds as a component of a private parent project; not standalone-reproducible. See Reproducibility Notes.
01 โ What is this project?
Every embedded engineer has met this failure: a firmware update goes wrong, the device boots into nothing, and the only way back is a screwdriver, a case teardown, and a USB cable. For a device sitting inside a rack, a ceiling, or a customer's studio wall, that is not a bug โ it is a truck roll.
The usual answer is a bootloader with an A/B slot layout and a rollback timer. That handles bad firmware. It does not handle no firmware โ the case where both slots are empty, corrupted, or where the application itself was never able to bring up the network to fetch a replacement.
pico_fota closes that last gap. It is a fork of Jakub Zimnol's pico_fota_bootloader (originally a Pico W / Wi-Fi design), re-engineered by DickinsAudio for a wired RP2040 product. The decisive change: the bootloader itself speaks Ethernet.
Hold the recovery button for five seconds at power-up and the RP2040 never reaches the application. Instead, a 92 KB bootloader brings up a WIZnet Ethernet controller, pulls a DHCP lease, opens a TCP listener on port 80, and serves a single-page HTML upload form. Drop a firmware binary into the browser, and the bootloader streams it directly into flash, verifies its SHA256, decrypts it with AES-ECB, swaps the slots, commits, and jumps into the freshly recovered application.
No JTAG. No BOOTSEL button. No case teardown. A network cable and a web browser.
02 โ Why put a web server in a bootloader?
๐ท Recovery must not depend on the thing that broke
A FOTA path implemented in the application is only as reliable as the application. If the update logic, the network stack, or the RTOS fails to start, the recovery path fails with it. Moving the network recovery into the bootloader makes it orthogonal to application health โ the code that rescues the device is the code that runs before anything can go wrong.
๐ท The upload client already exists on every desk
There is no custom flashing tool, no vendor utility, no Python script, and no driver install. The bootloader serves this to any browser on the LAN:
<h1>SYSTEM RECOVERY</h1>
Booted in recovery mode. A new firmware can be loaded here.
<input type="file" id="input" onchange="upload()">
<button onclick="location.href='reboot'">REBOOT</button>
A FileReader reads the binary, fetch() POSTs it as application/octet-stream, and the bootloader parses the body straight out of the socket. The entire client is 983 bytes of HTML served from a C string literal. A field technician needs no software and no training.
๐ท Wired, not wireless โ on purpose
The upstream project targeted the Pico W over Wi-Fi. This fork replaced that with Ethernet, and for a recovery path the reasoning is hard to argue with: no SSID, no PSK, no association state, no regulatory domain, no roaming, no 2.4 GHz congestion. A recovery mechanism that requires the operator to first configure wireless credentials is not a recovery mechanism. Plug in the cable, the link comes up, DHCP resolves, the page loads.
๐ท Where this sits in the WIZnet ecosystem
The RP2040 has no Ethernet MAC and no PHY. Reaching the network requires an external part, and the realistic choices are an SPI MAC+PHY with a software TCP/IP stack (ENC28J60 + LwIP), or a hardwired TCP/IP offload controller (WIZnet W5500 / W5100S). For an application, both work. For a 92 KB bootloader, only one does โ as the next section shows.
03 โ System architecture

Recovery flow

04 โ Why WIZnet? โญ
๐ท The technical core: 92 KB is the entire argument
The bootloader must fit in 92 KB of flash together with mbedTLS AES-ECB, mbedTLS SHA256, the flash driver, the slot-swap logic, an HTTP request parser, and a DHCP client. There is no room left for a software TCP/IP stack.
A software stack such as LwIP costs roughly 40โ60 KB of flash and tens of kilobytes of RAM for pbuf pools, TCP control blocks, and reassembly buffers โ before a single line of recovery logic is written. On top of that it needs a timer tick, periodic polling, and an ARP/ICMP/TCP state machine that must keep running while flash sectors are being erased with interrupts disabled.
The WIZnet controller removes that entire problem. TCP connection establishment, sequence numbers, ACK generation, retransmission, windowing, and reassembly all execute in silicon on the Ethernet chip. The bootloader's contribution is a handful of SPI register transactions:
socket(1, Sn_MR_TCP, 80, 0x00); // open socket 1 as a TCP server on port 80
listen(1); // hardware handles the three-way handshake
len = getSn_RX_RSR(1); // how many bytes are waiting in the chip's RX buffer
len = recv(1, g_ethernet_buf, len);
send(1, (uint8_t *)page_recover, sizeof(page_recover));
setSn_CR(1, Sn_CR_DISCON); // clean FIN
That is the complete TCP surface of this bootloader. It costs a few kilobytes of driver code and one 2 KB application buffer โ not a stack.
๐ท Socket mode: TCP / TOE (Sn_MR_TCP), plus ioLibrary DHCP
This project uses the WIZnet controller in TOE TCP server mode (Sn_MR_TCP) on socket 1, bound to port 80. This is the purest expression of what a TOE part is for: the host MCU never sees a TCP segment, only an ordered byte stream it can drain with recv().
The same socket index is reused earlier for the DHCP client, which ioLibrary's DHCP_init() / DHCP_run() implement over UDP. The bootloader makes up to ten DHCP attempts of two seconds each, then falls back to a static configuration:
DHCP_init(1, g_ethernet_buf); // socket 1, UDP under the hood
for (; wait > 0; wait--) {
if (DHCP_run() == DHCP_IP_LEASED) break;
sleep_ms(100);
gpio_put(LED_PIN, !gpio_get(LED_PIN)); // heartbeat while negotiating
}
DHCP_stop();
Note the LED toggle inside the DHCP poll loop โ the device blinks while it negotiates, so a technician standing in front of a rack can tell "searching" from "hung" without a serial console.
๐ท A detail that proves real deployment: the MAC address
pico_unique_board_id_t id;
pico_get_unique_board_id(&id);
net_info.mac[0] = 0x00; net_info.mac[1] = 0x08; net_info.mac[2] = 0xDC;
net_info.mac[3] = id.id[5];
net_info.mac[4] = id.id[6];
net_info.mac[5] = id.id[7];
setSHAR(net_info.mac);
00:08:DC is WIZnet's IEEE-registered OUI. The lower three bytes come from the RP2040's factory-unique board ID, so every unit in the field gets a distinct, deterministic, collision-free MAC with no provisioning step and no MAC EEPROM on the BOM. This is not tutorial code; this is what a manufacturer does when they need a thousand boards to coexist on a customer's network.
๐ท The SPI clock note
wizchip_spi_initialize(); // NOTE MAKE SURE TO PATCH THIS TO BE 36Mhz not 5Mhz SPI
The author's own comment records that the reference port's default 5 MHz SPI was raised to 36 MHz. That is not cosmetic. A recovery upload moves the better part of a megabyte through this link, and the recovery page tells the user to expect roughly two minutes. At 5 MHz that number would be considerably less friendly. The WIZnet SPI interface has the headroom to be pushed, and this project pushes it.
๐ท Versus the alternatives
| WIZnet TOE (this project) | ENC28J60 + LwIP | Wi-Fi (CYW43, upstream design) | |
|---|---|---|---|
| Flash cost in bootloader | ioLibrary driver + DHCP, a few KB | LwIP core โ 40โ60 KB | CYW43 firmware blob + stack, far larger |
| RAM cost | one 2 KB buffer | pbuf pools, TCBs, reassembly | driver + stack + firmware working set |
| TCP correctness | in silicon, vendor-validated | your build, your bugs | vendor stack |
Behaviour during flash_range_erase with IRQs off | chip buffers inbound data autonomously | stack starves, risk of drop | stack starves |
| Recovery prerequisites | plug in cable | plug in cable | SSID + PSK must already be configured |
| Deterministic bring-up time | link-up + DHCP | link-up + DHCP | scan + associate + DHCP |
The fourth row deserves emphasis. swap_images() runs with save_and_disable_interrupts() held across sector erases and programs. Any software stack would be blind for those windows. The WIZnet controller keeps receiving into its own internal RX buffer regardless of what the host MCU is doing โ which is precisely why the upload loop can safely alternate between recv() and flash_range_program().
๐ท Verified evidence โ
- โ
socket(1, Sn_MR_TCP, 80, 0x00)โ TOE TCP server mode, explicit in source - โ
wizchip_spi_initialize()โwizchip_reset()โwizchip_initialize()โwizchip_check()โ full ioLibrary bring-up sequence - โ
DHCP_init()/DHCP_run()/DHCP_stop()with static fallback and ten-attempt retry - โ
setSHAR()with WIZnet OUI00:08:DC+ RP2040 unique board ID - โ
ctlnetwork(CN_GET_NETINFO, ...)used to read back and log the leased address - โ
getSn_RX_RSR()polled for flow control against 256-byte flash write alignment - โ
setSn_CR(1, Sn_CR_DISCON)โ deliberate graceful close rather than a bareclose() - โ
Commit history shows staged, field-driven evolution: HTTP fallback added (2024-07-06), variable-length slots (2024-07-06), 5-second button hold and fast recovery blink (2024-07-07),
Content-Lengthcorrectness and case-insensitive method matching (2024-10-14), migration to the WIZnet SDK (2025-01-20)
05 โ Key components
๐ WIZnet Ethernet Controller โ TOE TCP server mode (Sn_MR_TCP) + ioLibrary DHCP
Driven through the standard ioLibrary_Driver API (socket.h, dhcp.h, wizchip_conf.h) and the RP2040 port layer (w5x00_spi.h, port_common.h). SPI raised to 36 MHz. Socket 1 serves double duty: UDP DHCP client during bring-up, then TCP listener on port 80. MAC derived from the WIZnet OUI plus the RP2040 unique ID.
๐ง RP2040 โ dual-core Cortex-M0+ host
Only the flash controller, SPI, GPIO, and watchdog are needed in recovery mode. hardware/flash.h for erase/program, hardware/watchdog.h for the reboot endpoint, pico/unique_id.h for MAC derivation.
๐ mbedTLS (via pico_mbedtls) โ AES-ECB + SHA256
Firmware images are AES-ECB encrypted at build time by scripts/aes_encrypt.py and appended with a SHA256 digest by scripts/sha256_append.py. The bootloader decrypts each 256-byte block on the way into flash and verifies the digest before permitting a swap. The AES key is compiled into both the bootloader and the image via -DPFB_AES_KEY=.
๐พ A/B slot flash layout with rollback
92 KB bootloader, 4 KB info page, 976 KB swap space per side (848 KB usable application slot), inside 2048 KB of RP2040 flash. Six persistent flags in the info page track app/download headers, download-slot validity, swapped state, post-rollback state, and pending-rollback state. Normal FOTA arms a rollback that fires unless the new application calls pfb_firmware_commit(). Recovery-mode uploads deliberately commit immediately โ there is no previous good image to fall back to.
๐ฅ๏ธ 983-byte single-page recovery client
A complete file-upload UI as a C string literal: FileReader โ fetch('upload', {method:'POST'}) โ application/octet-stream, plus a REBOOT button that maps to GET /reboot โ watchdog_reboot().
06 โ Application scenarios
01. Rack-mounted and installed AV equipment
The originating use case. A media dongle in a studio wall or a rack bay cannot be pulled for a USB reflash. One cable and a laptop browser restore it in about two minutes.
02. Industrial controllers behind a locked panel
Machinery cabinets are sealed, sometimes safety-interlocked. An Ethernet recovery path that lives below the application means a bricked update no longer means unbolting a panel and hunting for a JTAG header.
03. Fleet field service without vendor tooling
A contractor with no toolchain, no drivers, and no vendor utility can recover a unit. The device supplies its own client. This substantially lowers the cost and training burden of field support.
04. Development and CI hardware farms
A rack of boards under continuous integration will eventually be flashed with something that does not boot. A network recovery path in the bootloader makes that self-healing over the same LAN the farm already uses, with no per-board USB hub or debug probe.
05. Remote or lights-out installations
Where physical access is expensive โ remote sites, ceilings, vehicles, ships โ the difference between "reachable over the LAN" and "requires a visit" is the entire maintenance budget.
Conclusion
A 92 KB bootloader that serves its own web-based firmware recovery UI โ made possible because the TCP/IP stack lives in the Ethernet chip, not in the flash budget.
- โ Network firmware recovery implemented inside the bootloader, independent of application health
- โ
WIZnet controller used in TOE TCP server mode (
Sn_MR_TCP) on port 80, plus ioLibrary DHCP with static fallback - โ Complete recovery client delivered as 983 bytes of HTML โ no tooling, no drivers, no training
- โ
Hardware TCP offload lets the upload loop interleave
recv()with interrupt-disabledflash_range_erase()safely - โ
Per-unit MAC synthesised from the WIZnet OUI
00:08:DCand the RP2040 unique board ID โ zero provisioning, zero BOM cost - โ AES-ECB image encryption and SHA256 verification enforced in the recovery path, not only the normal FOTA path
- โ SPI pushed from 5 MHz to 36 MHz to keep a ~1 MB upload inside a two-minute window
- โ Wired Ethernet chosen over the upstream Wi-Fi design specifically to remove credential configuration from the recovery flow
- โ Shipping commercial firmware, evolved across two years of field-driven commits
Reproducibility Notes
This is production firmware for a specific commercial board, published as a component rather than as a tutorial. Prospective readers should know:
- Not standalone-buildable.
CMakeLists.txtlinks theETHERNET_FILES,IOLIBRARY_FILES, andDHCP_FILEStargets, which are defined by WIZnet's RP2040-HAT-C / WIZnet-PICO-C library tree. That tree, the port layer (w5x00_spi.c,port_common.h), the_WIZCHIP_definition, and the board header definingPICO_BUTTON_0all live in the private parent project. - README describes the upstream design. The documentation still reflects the original Pico W / Wi-Fi project and the original 36 KB bootloader size. The Ethernet recovery path and the 92 KB layout exist only in the code.
- To adapt it, drop the repository into a WIZnet-PICO-C-based project, define
_WIZCHIP_andPICO_BUTTON_0, and providepico_mbedtls. The recovery logic itself is self-contained inbootloader.c.
Q&A
Q. Why Sn_MR_TCP rather than MACRAW with a software stack? A. MACRAW hands raw Ethernet frames to the host and requires a full TCP/IP implementation above it โ exactly the 40โ60 KB this bootloader cannot afford. Sn_MR_TCP moves the entire stack into the controller, reducing the host side to socket calls. For a size-constrained bootloader, TOE mode is not a convenience, it is the enabling condition.
Q. Both DHCP and the HTTP server use socket 1. Is that a conflict? A. No โ they are sequential, not concurrent. DHCP_stop() releases the socket before socket(1, Sn_MR_TCP, 80, 0) reopens it as a TCP listener. With eight hardware sockets available the project could have used separate indices; reusing one keeps the recovery state machine strictly linear and easy to reason about.
Q. What happens if DHCP fails? A. After ten attempts of two seconds each, the bootloader calls network_initialize() with a static configuration of 192.168.0.100/24, gateway 192.168.0.1. A technician can therefore recover a unit on an isolated link with no DHCP server by setting a laptop to the same subnet.
Q. Flash erase disables interrupts. Doesn't that break the transfer? A. This is the sharpest argument for a TOE part. swap_images() and the flash programming path run under save_and_disable_interrupts(). A software stack would be unable to ACK or buffer during those windows. The WIZnet controller receives into its own internal RX buffer independently of the host, so the bootloader can simply poll getSn_RX_RSR(1) when it returns and drain whatever accumulated. TCP flow control is handled by the chip.
Q. Why doesn't the recovery upload arm a rollback like a normal FOTA does? A. Because there is nothing to roll back to. Recovery mode exists precisely when the application slot is not trustworthy, so the code calls pfb_firmware_commit() and _pfb_mark_is_not_after_rollback() immediately after a successful SHA256 check, then jumps straight into the new image rather than rebooting.
Q. Is serving an unauthenticated upload endpoint safe? A. Two controls apply. The endpoint only exists after a deliberate five-second physical button hold at power-up, so it is not reachable during normal operation. And every uploaded image must decrypt correctly under the compiled-in AES key and match its appended SHA256 before it is allowed to swap โ an attacker on the LAN cannot install an image without the build-time key.
Q. How does the bootloader know when the upload has finished? A. It drains the socket until no further data arrives, counting bytes into 256-byte aligned flash writes, and then validates the accumulated length with pfb_firmware_sha256_check(upload_done). The digest check is what confirms completeness; a truncated transfer simply fails verification and is rejected.
pico_fota โ ์ฃฝ์ง ์๋ ๋ถํธ๋ก๋: 92 KB ๋ถํธ๋ก๋๊ฐ ์ง์ ๋์ฐ๋ ์ด๋๋ท ํ์จ์ด ๋ณต๊ตฌ
#W5500 #TOE #RP2040 #Bootloader #FOTA #Recovery #SecureBoot #AES-ECB #SHA256 #ioLibrary #Rollback
๐ ๋ฐฐ๊ฒฝ: ์์ฉ ์ ํ ํ์จ์ด โ ํธ์ฃผ DickinsAudio์ ์ค๋์ค ๋คํธ์ํน ๋๊ธ์ ์ค์ ๋ก ํ์ฌ๋ ๋ณต๊ตฌ ๊ฒฝ๋ก. ๊ฒ์ฆ ์ํ: WIZnet ์นฉ ์ฌ์ฉ์ ์ฝ๋ ๋ ๋ฒจ์์ ํ์ธ (
Sn_MR_TCP์์ผ, ioLibrary DHCP, WIZnet OUI00:08:DC). ๋น๊ณต๊ฐ ์์ ํ๋ก์ ํธ์ ์ปดํฌ๋ํธ๋ก ๋น๋๋๋ฉฐ ๋จ๋ ์ฌํ์ ๋ถ๊ฐ. ์ฌํ์ฑ ์ฐธ๊ณ ํญ๋ชฉ ํ์ธ ์๋ง.
01 โ ์ด๋ค ํ๋ก์ ํธ์ธ๊ฐ?
์๋ฒ ๋๋ ์์ง๋์ด๋ผ๋ฉด ํ ๋ฒ์ฏค ๊ฒช๋ ์ํฉ์ด ์๋ค. ํ์จ์ด ์ ๋ฐ์ดํธ๊ฐ ์๋ชป๋๊ณ , ๊ธฐ๊ธฐ๋ ์๋ฌด๊ฒ๋ ๋ถํ ํ์ง ๋ชปํ๊ณ , ๋์ด๋ฆด ๋ฐฉ๋ฒ์ ๋๋ผ์ด๋ฒ๋ก ์ผ์ด์ค๋ฅผ ๋ฏ์ด USB๋ฅผ ๊ฝ๋ ๊ฒ๋ฟ์ธ ์ํฉ. ๋ ์์, ์ฒ์ฅ ์์, ๊ณ ๊ฐ์ฌ ์คํ๋์ค ๋ฒฝ ์์ ๋ค์ด๊ฐ ์๋ ์ฅ๋น๋ผ๋ฉด ์ด๊ฑด ๋ฒ๊ทธ๊ฐ ์๋๋ผ ์ถ์ฅ ๋น์ฉ์ด๋ค.
์ผ๋ฐ์ ์ธ ํด๋ฒ์ A/B ์ฌ๋กฏ ๊ตฌ์กฐ์ ๋กค๋ฐฑ ํ์ด๋จธ๋ฅผ ๊ฐ์ถ ๋ถํธ๋ก๋๋ค. ์ด๊ฑด ์๋ชป๋ ํ์จ์ด๋ ์ฒ๋ฆฌํ๋ค. ํ์ง๋ง ํ์จ์ด๊ฐ ์์ ์๋ ์ํฉ โ ์์ชฝ ์ฌ๋กฏ์ด ๋น์๊ฑฐ๋ ์์๋๊ฑฐ๋, ์ ์ด์ ์ ํ๋ฆฌ์ผ์ด์ ์ด ๋คํธ์ํฌ๋ฅผ ์ฌ๋ฆฌ์ง ๋ชปํด ์ ์ด๋ฏธ์ง๋ฅผ ๋ฐ์์ฌ ์์กฐ์ฐจ ์๋ ์ํฉ โ ์ ์ฒ๋ฆฌํ์ง ๋ชปํ๋ค.
pico_fota๋ ๋ฐ๋ก ๊ทธ ๋ง์ง๋ง ๊ตฌ๋ฉ์ ๋ง๋๋ค. Jakub Zimnol์ pico_fota_bootloader(์๋๋ Pico W / Wi-Fi ์ค๊ณ)๋ฅผ DickinsAudio๊ฐ ์ ์ RP2040 ์ ํ์ฉ์ผ๋ก ๋ค์ ์ค๊ณํ ํฌํฌ๋ค. ๊ฒฐ์ ์ ์ธ ๋ณ๊ฒฝ์ ์ ํ๋๋ค. ๋ถํธ๋ก๋ ์์ ์ด ์ด๋๋ท์ ๋งํ๋ค.
์ ์ ์ธ๊ฐ ์ ๋ณต๊ตฌ ๋ฒํผ์ 5์ด๊ฐ ๋๋ฅด๊ณ ์์ผ๋ฉด RP2040์ ์ ํ๋ฆฌ์ผ์ด์ ์ ๋๋ฌํ์ง ์๋๋ค. ๋์ 92 KB ๋ถํธ๋ก๋๊ฐ WIZnet ์ด๋๋ท ์ปจํธ๋กค๋ฌ๋ฅผ ์ด๊ธฐํํ๊ณ , DHCP๋ก IP๋ฅผ ๋ฐ๊ณ , 80๋ฒ ํฌํธ์ TCP ๋ฆฌ์ค๋๋ฅผ ์ด๊ณ , ๋จ์ผ ํ์ด์ง HTML ์ ๋ก๋ ํผ์ ์๋นํ๋ค. ๋ธ๋ผ์ฐ์ ์์ ํ์จ์ด ๋ฐ์ด๋๋ฆฌ๋ฅผ ๋์ ธ ๋ฃ์ผ๋ฉด ๋ถํธ๋ก๋๊ฐ ๊ทธ๊ฒ์ ํ๋์๋ก ์ง์ ์คํธ๋ฆฌ๋ฐํ๊ณ , SHA256์ ๊ฒ์ฆํ๊ณ , AES-ECB๋ก ๋ณตํธํํ๊ณ , ์ฌ๋กฏ์ ์ค์ํ๊ณ , ์ปค๋ฐํ ๋ค, ๋ฐฉ๊ธ ๋ณต๊ตฌ๋ ์ ํ๋ฆฌ์ผ์ด์ ์ผ๋ก ์ ํํ๋ค.
JTAG๋, BOOTSEL ๋ฒํผ๋, ์ผ์ด์ค ๋ถํด๋ ์๋ค. ๋์ ํ๋์ ์น ๋ธ๋ผ์ฐ์ ๋ฉด ๋๋ค.
02 โ ์ ๋ถํธ๋ก๋์ ์น์๋ฒ๋ฅผ ๋ฃ๋๊ฐ?
๐ท ๋ณต๊ตฌ ์๋จ์ด ๊ณ ์ฅ๋ ๋์์ ์์กดํด์ ์ ๋๋ค
์ ํ๋ฆฌ์ผ์ด์ ์ ๊ตฌํ๋ FOTA ๊ฒฝ๋ก๋ ์ ํ๋ฆฌ์ผ์ด์ ๋งํผ๋ง ์ ๋ขฐํ ์ ์๋ค. ์ ๋ฐ์ดํธ ๋ก์ง์ด๋ ๋คํธ์ํฌ ์คํ์ด๋ RTOS๋ , ํ๋๋ผ๋ ๊ธฐ๋์ ์คํจํ๋ฉด ๋ณต๊ตฌ ๊ฒฝ๋ก๋ ํจ๊ป ์ฃฝ๋๋ค. ๋คํธ์ํฌ ๋ณต๊ตฌ๋ฅผ ๋ถํธ๋ก๋๋ก ๋ด๋ฆฌ๋ฉด ์ด ๊ฒฝ๋ก๊ฐ ์ ํ๋ฆฌ์ผ์ด์ ์ํ์ **์ง๊ต(orthogonal)**ํด์ง๋ค. ๊ธฐ๊ธฐ๋ฅผ ๊ตฌ์กฐํ๋ ์ฝ๋๊ฐ, ๋ฌด์ธ๊ฐ ์๋ชป๋ ์ ์๊ธฐ ์ด์ ์ ์คํ๋๋ ์ฝ๋๊ฐ ๋๋ ๊ฒ์ด๋ค.
๐ท ์ ๋ก๋ ํด๋ผ์ด์ธํธ๋ ์ด๋ฏธ ๋ชจ๋ ์ฑ ์ ์์ ์๋ค
์ ์ฉ ํ๋์ฑ ํด๋, ๋ฒค๋ ์ ํธ๋ฆฌํฐ๋, ํ์ด์ฌ ์คํฌ๋ฆฝํธ๋, ๋๋ผ์ด๋ฒ ์ค์น๋ ์๋ค. ๋ถํธ๋ก๋๊ฐ LAN ์์ ์๋ฌด ๋ธ๋ผ์ฐ์ ์๋ ์ด๊ฑธ ๋ด๋ ค์ค๋ค:
<h1>SYSTEM RECOVERY</h1>
Booted in recovery mode. A new firmware can be loaded here.
<input type="file" id="input" onchange="upload()">
<button onclick="location.href='reboot'">REBOOT</button>
FileReader๊ฐ ๋ฐ์ด๋๋ฆฌ๋ฅผ ์ฝ๊ณ , fetch()๊ฐ application/octet-stream์ผ๋ก POSTํ๊ณ , ๋ถํธ๋ก๋๋ ์์ผ์์ ๋ฐ๋ก ๋ณธ๋ฌธ์ ํ์ฑํ๋ค. ํด๋ผ์ด์ธํธ ์ ์ฒด๊ฐ C ๋ฌธ์์ด ๋ฆฌํฐ๋ด์ ๋ด๊ธด 983๋ฐ์ดํธ HTML์ด๋ค. ํ์ฅ ๊ธฐ์ ์์๊ฒ ํ์ํ ์ํํธ์จ์ด๋, ๊ต์ก๋ ์๋ค.
๐ท ๋ฌด์ ์ด ์๋๋ผ ์ ์ โ ์๋๋ ์ ํ
์ ์คํธ๋ฆผ์ Pico W์ Wi-Fi๋ฅผ ํ๊ฒ์ผ๋ก ํ๋ค. ์ด ํฌํฌ๋ ๊ทธ๊ฒ์ ์ด๋๋ท์ผ๋ก ๊ต์ฒดํ๊ณ , ๋ณต๊ตฌ ๊ฒฝ๋ก๋ผ๋ ๊ด์ ์์ ๋ณด๋ฉด ๋ฐ๋ฐํ๊ธฐ ์ด๋ ค์ด ๋ ผ๋ฆฌ๋ค. SSID๋, PSK๋, association ์ํ๋, ๊ท์ ๋๋ฉ์ธ๋, ๋ก๋ฐ๋, 2.4 GHz ํผ์ก๋ ์๋ค. ๋ณต๊ตฌ๋ฅผ ํ๋ ค๋ฉด ๋จผ์ ๋ฌด์ ์๊ฒฉ์ฆ๋ช ์ ์ค์ ํด์ผ ํ๋ ๋ฉ์ปค๋์ฆ์ ๋ณต๊ตฌ ๋ฉ์ปค๋์ฆ์ด๋ผ ๋ถ๋ฅผ ์ ์๋ค. ์ผ์ด๋ธ์ ๊ฝ์ผ๋ฉด ๋งํฌ๊ฐ ์ฌ๋ผ์ค๊ณ , DHCP๊ฐ ํ๋ฆฌ๊ณ , ํ์ด์ง๊ฐ ์ด๋ฆฐ๋ค.
๐ท WIZnet ์ํ๊ณ์์์ ์์น
RP2040์๋ ์ด๋๋ท MAC๋ PHY๋ ์๋ค. ๋คํธ์ํฌ์ ๋ฟ์ผ๋ ค๋ฉด ์ธ๋ถ ๋ถํ์ด ํ์ํ๊ณ , ํ์ค์ ์ธ ์ ํ์ง๋ ์ํํธ์จ์ด TCP/IP ์คํ์ ์น๋ SPI MAC+PHY(ENC28J60 + LwIP)์ด๊ฑฐ๋ ํ๋์์ด์ด๋ TCP/IP ์คํ๋ก๋ ์ปจํธ๋กค๋ฌ(WIZnet W5500 / W5100S)๋ค. ์ ํ๋ฆฌ์ผ์ด์ ์ด๋ผ๋ฉด ๋ ๋ค ๋๋ค. 92 KB ๋ถํธ๋ก๋๋ผ๋ฉด ํ๋๋ฟ์ด๋ค โ ๋ค์ ์น์ ์ด ๊ทธ ์ด์ ๋ค.
03 โ ์์คํ ์ํคํ ์ฒ

๋ณต๊ตฌ ํ๋ฆ![]()
04 โ ์ WIZnet์ธ๊ฐ? โญ
๐ท ๊ธฐ์ ์ ํต์ฌ: 92 KB, ๊ทธ ์์ฒด๊ฐ ๊ทผ๊ฑฐ๋ค
๋ถํธ๋ก๋๋ 92 KB ํ๋์ ์์ mbedTLS AES-ECB, mbedTLS SHA256, ํ๋์ ๋๋ผ์ด๋ฒ, ์ฌ๋กฏ ์ค์ ๋ก์ง, HTTP ์์ฒญ ํ์, DHCP ํด๋ผ์ด์ธํธ๋ฅผ ํจ๊ป ๋ด์์ผ ํ๋ค. ์ํํธ์จ์ด TCP/IP ์คํ์ด ๋ค์ด๊ฐ ์๋ฆฌ๋ ๋จ์ง ์๋๋ค.
LwIP ๊ฐ์ ์ํํธ์จ์ด ์คํ์ ํ๋์ ์ฝ 40~60 KB์, pbuf ํยทTCP ์ปจํธ๋กค ๋ธ๋กยท์ฌ์กฐ๋ฆฝ ๋ฒํผ๋ก ์์ญ KB RAM์ ์๊ตฌํ๋ค. ๋ณต๊ตฌ ๋ก์ง ํ ์ค ์ฐ๊ธฐ ์ ์ ๋๋ ๋น์ฉ์ด๋ค. ๊ฑฐ๊ธฐ์ ํ์ด๋จธ ํฑ, ์ฃผ๊ธฐ์ ํด๋ง, ๊ทธ๋ฆฌ๊ณ ์ธํฐ๋ฝํธ๋ฅผ ๋ ์ฑ ํ๋์ ์นํฐ๋ฅผ ์ง์ฐ๋ ๋์์๋ ๊ณ์ ๋์์ผ ํ๋ ARP/ICMP/TCP ์ํ ๋จธ์ ๊น์ง ํ์ํ๋ค.
WIZnet ์ปจํธ๋กค๋ฌ๋ ์ด ๋ฌธ์ ๋ฅผ ํต์งธ๋ก ์์ค๋ค. TCP ์ฐ๊ฒฐ ์๋ฆฝ, ์ํ์ค ๋ฒํธ, ACK ์์ฑ, ์ฌ์ ์ก, ์๋์, ์ฌ์กฐ๋ฆฝ์ด ์ ๋ถ ์ด๋๋ท ์นฉ ์ค๋ฆฌ์ฝ์์ ์ํ๋๋ค. ๋ถํธ๋ก๋๊ฐ ๋ด๋นํ๋ ๊ฑด ๋ช ๊ฐ์ SPI ๋ ์ง์คํฐ ํธ๋์ญ์ ๋ฟ์ด๋ค:
socket(1, Sn_MR_TCP, 80, 0x00); // ์์ผ 1์ 80๋ฒ ํฌํธ TCP ์๋ฒ๋ก ๊ฐ์ค
listen(1); // 3-way ํธ๋์
ฐ์ดํฌ๋ ํ๋์จ์ด๊ฐ ์ฒ๋ฆฌ
len = getSn_RX_RSR(1); // ์นฉ RX ๋ฒํผ์ ๋๊ธฐ ์ค์ธ ๋ฐ์ดํธ ์
len = recv(1, g_ethernet_buf, len);
send(1, (uint8_t *)page_recover, sizeof(page_recover));
setSn_CR(1, Sn_CR_DISCON); // ์ ์์ ์ธ FIN
์ด๊ฒ ์ด ๋ถํธ๋ก๋์ TCP ํ๋ฉด ์ ๋ถ๋ค. ๋๋ผ์ด๋ฒ ์ฝ๋ ์ KB์ 2 KB ์ ํ๋ฆฌ์ผ์ด์ ๋ฒํผ ํ๋๋ฉด ๋์ด์ง, ์คํ์ด ์๋๋ค.
๐ท ์์ผ ๋ชจ๋: TCP / TOE (Sn_MR_TCP), ๊ทธ๋ฆฌ๊ณ ioLibrary DHCP
์ด ํ๋ก์ ํธ๋ WIZnet ์ปจํธ๋กค๋ฌ๋ฅผ ์์ผ 1์์ 80๋ฒ ํฌํธ์ ๋ฐ์ธ๋ฉํ TOE TCP ์๋ฒ ๋ชจ๋(Sn_MR_TCP) ๋ก ์ฌ์ฉํ๋ค. TOE ๋ถํ์ด ์กด์ฌํ๋ ์ด์ ๋ฅผ ๊ฐ์ฅ ์์ํ๊ฒ ๋ณด์ฌ์ฃผ๋ ํํ๋ค. ํธ์คํธ MCU๋ TCP ์ธ๊ทธ๋จผํธ๋ฅผ ํ ๋ฒ๋ ๋ณด์ง ์๊ณ , recv()๋ก ํผ๋ด๊ธฐ๋ง ํ๋ฉด ๋๋ ์์ ๋ณด์ฅ๋ ๋ฐ์ดํธ ์คํธ๋ฆผ๋ง ๋ณธ๋ค.
๊ฐ์ ์์ผ ์ธ๋ฑ์ค๋ ๊ทธ ์ ๋จ๊ณ์์ DHCP ํด๋ผ์ด์ธํธ๋ก๋ ์ฐ์ธ๋ค. ioLibrary์ DHCP_init() / DHCP_run()์ด UDP ์์์ ๊ตฌํํ ๊ฒ์ด๋ค. ๋ถํธ๋ก๋๋ 2์ด์ง๋ฆฌ DHCP ์๋๋ฅผ ์ต๋ ์ด ๋ฒ ๋ฐ๋ณตํ๊ณ , ์คํจํ๋ฉด ์ ์ ์ค์ ์ผ๋ก ํด๋ฐฑํ๋ค:
DHCP_init(1, g_ethernet_buf); // ์์ผ 1, ๋ด๋ถ์ ์ผ๋ก UDP
for (; wait > 0; wait--) {
if (DHCP_run() == DHCP_IP_LEASED) break;
sleep_ms(100);
gpio_put(LED_PIN, !gpio_get(LED_PIN)); // ํ์ ์ค ํํธ๋นํธ
}
DHCP_stop();
DHCP ํด๋ง ๋ฃจํ ์์ LED ํ ๊ธ์ ๋์ฌ๊ฒจ๋ณผ ๋งํ๋ค. ํ์ ์ค์๋ ๊ธฐ๊ธฐ๊ฐ ๊น๋นก์ด๋ฏ๋ก, ๋ ์์ ์ ๊ธฐ์ ์๊ฐ ์๋ฆฌ์ผ ์ฝ์ ์์ด๋ "ํ์ ์ค"๊ณผ "๋ฉ์ถค"์ ๊ตฌ๋ถํ ์ ์๋ค.
๐ท ์ค์ ์์ฐ์ ์ฆ๊ฑฐ: MAC ์ฃผ์
pico_unique_board_id_t id;
pico_get_unique_board_id(&id);
net_info.mac[0] = 0x00; net_info.mac[1] = 0x08; net_info.mac[2] = 0xDC;
net_info.mac[3] = id.id[5];
net_info.mac[4] = id.id[6];
net_info.mac[5] = id.id[7];
setSHAR(net_info.mac);
00:08:DC๋ WIZnet์ IEEE ๋ฑ๋ก OUI๋ค. ํ์ 3๋ฐ์ดํธ๋ RP2040 ๊ณต์ฅ ๊ณ ์ ๋ณด๋ ID์์ ๊ฐ์ ธ์จ๋ค. ์ฆ ํ์ฅ์ ๋๊ฐ๋ ๋ชจ๋ ์ ๋์ด ํ๋ก๋น์ ๋ ๊ณต์ ์์ด, BOM์ MAC EEPROM ์์ด, ์๋ก ๊ฒน์น์ง ์๋ ๊ฒฐ์ ๋ก ์ MAC์ ๊ฐ๋๋ค. ํํ ๋ฆฌ์ผ ์ฝ๋๊ฐ ์๋๋ผ, ๊ณ ๊ฐ ๋คํธ์ํฌ์์ ์์ฒ ๋๊ฐ ๊ณต์กดํด์ผ ํ๋ ์ ์กฐ์ฌ๊ฐ ์ค์ ๋ก ํ๋ ๋ฐฉ์์ด๋ค.
๐ท SPI ํด๋ญ ์ฃผ์
wizchip_spi_initialize(); // NOTE MAKE SURE TO PATCH THIS TO BE 36Mhz not 5Mhz SPI
๋ ํผ๋ฐ์ค ํฌํธ ๊ธฐ๋ณธ๊ฐ 5 MHz๋ฅผ 36 MHz๋ก ์ฌ๋ ธ๋ค๋ ์ ์ ๋ณธ์ธ์ ๊ธฐ๋ก์ด๋ค. ์ด๊ฑด ์ฅ์์ด ์๋๋ค. ๋ณต๊ตฌ ์ ๋ก๋๋ ์ด ๋งํฌ๋ก 1 MB์ ๊ฐ๊น์ด ๋ฐ์ดํฐ๋ฅผ ๋ฐ์ด ๋ฃ๊ณ , ๋ณต๊ตฌ ํ์ด์ง๋ ์ฌ์ฉ์์๊ฒ ์ฝ 2๋ถ์ ์๊ณ ํ๋ค. 5 MHz์๋ค๋ฉด ๊ทธ ์ซ์๋ ํจ์ฌ ๋ ์น์ ํ์ ๊ฒ์ด๋ค. WIZnet SPI ์ธํฐํ์ด์ค์๋ ๋ฐ์ด๋ถ์ผ ์ฌ์ ๊ฐ ์๊ณ , ์ด ํ๋ก์ ํธ๋ ๊ทธ๊ฒ์ ๋ฐ์ด๋ถ์ธ๋ค.
๐ท ๋์ ๋๋น ์ฐ์
| WIZnet TOE (๋ณธ ํ๋ก์ ํธ) | ENC28J60 + LwIP | Wi-Fi (CYW43, ์ ์คํธ๋ฆผ ์ค๊ณ) | |
|---|---|---|---|
| ๋ถํธ๋ก๋ ๋ด ํ๋์ ๋น์ฉ | ioLibrary ๋๋ผ์ด๋ฒ + DHCP, ์ KB | LwIP ์ฝ์ด ์ฝ 40~60 KB | CYW43 ํ์จ์ด ๋ธ๋กญ + ์คํ, ํจ์ฌ ํผ |
| RAM ๋น์ฉ | 2 KB ๋ฒํผ ํ๋ | pbuf ํ, TCB, ์ฌ์กฐ๋ฆฝ ๋ฒํผ | ๋๋ผ์ด๋ฒ + ์คํ + ํ์จ์ด ์ํน์ |
| TCP ์ ํ์ฑ | ์ค๋ฆฌ์ฝ, ๋ฒค๋ ๊ฒ์ฆ | ๋ด ๋น๋, ๋ด ๋ฒ๊ทธ | ๋ฒค๋ ์คํ |
IRQ ์ฐจ๋จ ์ค flash_range_erase | ์นฉ์ด ์์จ์ ์ผ๋ก ์์ ๋ฒํผ๋ง | ์คํ ์์ฌ, ์ ์ค ์ํ | ์คํ ์์ฌ |
| ๋ณต๊ตฌ ์ ์ ์กฐ๊ฑด | ์ผ์ด๋ธ ์ฐ๊ฒฐ | ์ผ์ด๋ธ ์ฐ๊ฒฐ | SSID + PSK ์ฌ์ ์ค์ ํ์ |
| ๊ฒฐ์ ๋ก ์ ๊ธฐ๋ ์๊ฐ | ๋งํฌ์ + DHCP | ๋งํฌ์ + DHCP | ์ค์บ + ์ฐ๊ฒฐ + DHCP |
๋ค ๋ฒ์งธ ํ์ด ํนํ ์ค์ํ๋ค. swap_images()๋ ์นํฐ ์๊ฑฐ์ ํ๋ก๊ทธ๋จ ์ ๊ตฌ๊ฐ์์ save_and_disable_interrupts()๋ฅผ ์ก๊ณ ์คํ๋๋ค. ์ด๋ค ์ํํธ์จ์ด ์คํ์ด๋ ๊ทธ ๊ตฌ๊ฐ ๋์ ๋์ด ๋จผ๋ค. WIZnet ์ปจํธ๋กค๋ฌ๋ ํธ์คํธ MCU๊ฐ ๋ฌด์์ ํ๋ ์๊ด์์ด ์์ฒด ๋ด๋ถ RX ๋ฒํผ๋ก ๊ณ์ ์์ ํ๋ค. ์
๋ก๋ ๋ฃจํ๊ฐ recv()์ flash_range_program()์ ์์ ํ๊ฒ ๋ฒ๊ฐ์ ์ํํ ์ ์๋ ์ด์ ๊ฐ ์ ํํ ์ด๊ฒ์ด๋ค.
๐ท ๊ฒ์ฆ๋ ์ฆ๊ฑฐ โ
- โ
socket(1, Sn_MR_TCP, 80, 0x00)โ TOE TCP ์๋ฒ ๋ชจ๋, ์์ค์ ๋ช ์ - โ
wizchip_spi_initialize()โwizchip_reset()โwizchip_initialize()โwizchip_check()โ ioLibrary ์ ์ฒด ๊ธฐ๋ ์ํ์ค - โ
DHCP_init()/DHCP_run()/DHCP_stop()+ ์ ์ ํด๋ฐฑ + 10ํ ์ฌ์๋ - โ
setSHAR()+ WIZnet OUI00:08:DC+ RP2040 ๊ณ ์ ๋ณด๋ ID - โ
ctlnetwork(CN_GET_NETINFO, ...)๋ก ํ ๋น๋ ์ฃผ์ ์ญ์กฐํ ๋ฐ ๋ก๊น - โ
256๋ฐ์ดํธ ํ๋์ ์ฐ๊ธฐ ์ ๋ ฌ์ ๋ง์ถ
getSn_RX_RSR()ํด๋ง ๊ธฐ๋ฐ ํ๋ฆ ์ ์ด - โ
๋จ์
close()๊ฐ ์๋setSn_CR(1, Sn_CR_DISCON)๋ช ์์ ์ ์ ์ข ๋ฃ - โ
์ปค๋ฐ ํ์คํ ๋ฆฌ์ ํ์ฅ ํผ๋๋ฐฑ ๊ธฐ๋ฐ์ ๋จ๊ณ์ ์งํ๊ฐ ๋จ์ ์์: HTTP ํด๋ฐฑ ์ถ๊ฐ(2024-07-06), ๊ฐ๋ณ ๊ธธ์ด ์ฌ๋กฏ(2024-07-06), 5์ด ๋ฒํผ ํ๋ ๋ฐ ๋ณต๊ตฌ ๋ชจ๋ ๊ณ ์ ์ ๋ฉธ(2024-07-07),
Content-Length์ ํฉ์ฑ ๋ฐ ๋์๋ฌธ์ ๋ฌด์ ๋ฉ์๋ ๋งค์นญ(2024-10-14), WIZnet SDK ์ด๊ด(2025-01-20)
05 โ ํต์ฌ ๊ตฌ์ฑ์์
๐ WIZnet ์ด๋๋ท ์ปจํธ๋กค๋ฌ โ TOE TCP ์๋ฒ ๋ชจ๋(Sn_MR_TCP) + ioLibrary DHCP
ํ์ค ioLibrary_Driver API(socket.h, dhcp.h, wizchip_conf.h)์ RP2040 ํฌํธ ๋ ์ด์ด(w5x00_spi.h, port_common.h)๋ฅผ ํตํด ๊ตฌ๋. SPI๋ 36 MHz๋ก ์ํฅ. ์์ผ 1์ด ๋ ์ญํ ์ ๊ฒธํ๋ค โ ๊ธฐ๋ ์์๋ UDP DHCP ํด๋ผ์ด์ธํธ, ์ดํ์๋ 80๋ฒ ํฌํธ TCP ๋ฆฌ์ค๋. MAC์ WIZnet OUI์ RP2040 ๊ณ ์ ID๋ก ํฉ์ฑ.
๐ง RP2040 โ ๋์ผ์ฝ์ด Cortex-M0+ ํธ์คํธ
๋ณต๊ตฌ ๋ชจ๋์์ ํ์ํ ๊ฑด ํ๋์ ์ปจํธ๋กค๋ฌ, SPI, GPIO, ์์น๋
๋ฟ. ์๊ฑฐ/ํ๋ก๊ทธ๋จ์ hardware/flash.h, ๋ฆฌ๋ถํธ ์๋ํฌ์ธํธ์ hardware/watchdog.h, MAC ํฉ์ฑ์ pico/unique_id.h.
๐ mbedTLS (pico_mbedtls ๊ฒฝ์ ) โ AES-ECB + SHA256
ํ์จ์ด ์ด๋ฏธ์ง๋ ๋น๋ ์์ ์ scripts/aes_encrypt.py๋ก AES-ECB ์ํธํ๋๊ณ scripts/sha256_append.py๋ก SHA256 ๋ค์ด์ ์คํธ๊ฐ ๋ง๋ถ๋๋ค. ๋ถํธ๋ก๋๋ ํ๋์์ ์ฐ๋ ๊ธธ๋ชฉ์์ 256๋ฐ์ดํธ ๋ธ๋ก๋ง๋ค ๋ณตํธํํ๊ณ , ์ค์์ ํ์ฉํ๊ธฐ ์ ์ ๋ค์ด์ ์คํธ๋ฅผ ๊ฒ์ฆํ๋ค. AES ํค๋ -DPFB_AES_KEY=๋ก ๋ถํธ๋ก๋์ ์ด๋ฏธ์ง ์์ชฝ์ ์ปดํ์ผ๋๋ค.
๐พ ๋กค๋ฐฑ์ ๊ฐ์ถ A/B ์ฌ๋กฏ ํ๋์ ๋ ์ด์์
RP2040 ํ๋์ 2048 KB ์์ ๋ถํธ๋ก๋ 92 KB, INFO ํ์ด์ง 4 KB, ํธ๋น ์ค์ ๊ณต๊ฐ 976 KB(์ ํ๋ฆฌ์ผ์ด์
์ฌ๋กฏ ๊ฐ์ฉ 848 KB). INFO ํ์ด์ง์ ์์ ํ๋๊ทธ 6๊ฐ๊ฐ app/download ํค๋, ๋ค์ด๋ก๋ ์ฌ๋กฏ ์ ํจ์ฑ, ์ค์ ์ฌ๋ถ, ๋กค๋ฐฑ ์งํ ์ฌ๋ถ, ๋กค๋ฐฑ ์์ ์ฌ๋ถ๋ฅผ ์ถ์ ํ๋ค. ์ ์ FOTA๋ ๋กค๋ฐฑ์ ์์ฝํ๋ฉฐ, ์ ์ ํ๋ฆฌ์ผ์ด์
์ด pfb_firmware_commit()์ ํธ์ถํ์ง ์์ผ๋ฉด ๋ฐ๋ํ๋ค. ๋ณต๊ตฌ ๋ชจ๋ ์
๋ก๋๋ ์๋์ ์ผ๋ก ์ฆ์ ์ปค๋ฐํ๋ค โ ๋๋์๊ฐ ์ ์ ์ด๋ฏธ์ง๊ฐ ์๊ธฐ ๋๋ฌธ์ด๋ค.
๐ฅ๏ธ 983๋ฐ์ดํธ ๋จ์ผ ํ์ด์ง ๋ณต๊ตฌ ํด๋ผ์ด์ธํธ
C ๋ฌธ์์ด ๋ฆฌํฐ๋ด์ ๋ด๊ธด ์๊ฒฐ๋ ํ์ผ ์
๋ก๋ UI: FileReader โ fetch('upload', {method:'POST'}) โ application/octet-stream. ์ฌ๊ธฐ์ GET /reboot โ watchdog_reboot()์ผ๋ก ๋งคํ๋๋ REBOOT ๋ฒํผ๊น์ง.
06 โ ์์ฉ ์๋๋ฆฌ์ค
01. ๋ ๋ง์ดํธ ๋ฐ ์ค์นํ AV ์ฅ๋น
๋ฐ์์ด ๋ ์ฌ์ฉ ์ฌ๋ก. ์คํ๋์ค ๋ฒฝ์ด๋ ๋ ๋ฒ ์ด์ ๋ค์ด๊ฐ ๋ฏธ๋์ด ๋๊ธ์ USB ์ฌํ๋์ฑ์ ์ํด ๋นผ๋ผ ์ ์๋ค. ์ผ์ด๋ธ ํ๋์ ๋ ธํธ๋ถ ๋ธ๋ผ์ฐ์ ๋ก ์ฝ 2๋ถ์ด๋ฉด ๋ณต๊ตฌ๋๋ค.
02. ์ ๊ธด ํจ๋ ๋ค์ ์ฐ์ ์ฉ ์ปจํธ๋กค๋ฌ
๊ธฐ๊ณ ์บ๋น๋์ ๋ฐํ๋ผ ์๊ณ , ์์ ์ธํฐ๋ก์ด ๊ฑธ๋ ค ์๋ ๊ฒฝ์ฐ๋ ์๋ค. ์ ํ๋ฆฌ์ผ์ด์ ๋ณด๋ค ์๋์ ์ฌ๋ ์ด๋๋ท ๋ณต๊ตฌ ๊ฒฝ๋ก๊ฐ ์์ผ๋ฉด, ์ ๋ฐ์ดํธ ์คํจ๊ฐ ๋ ์ด์ ํจ๋์ ๋ฏ๊ณ JTAG ํค๋๋ฅผ ์ฐพ๋ ์ผ๋ก ์ด์ด์ง์ง ์๋๋ค.
03. ๋ฒค๋ ํด๋ง ์๋ ํ์ฅ ์๋น์ค
ํด์ฒด์ธ๋, ๋๋ผ์ด๋ฒ๋, ๋ฒค๋ ์ ํธ๋ฆฌํฐ๋ ์๋ ํ๋ ฅ์ ์ฒด ์ธ๋ ฅ์ด ์ ๋์ ๋ณต๊ตฌํ ์ ์๋ค. ๊ธฐ๊ธฐ๊ฐ ์๊ธฐ ํด๋ผ์ด์ธํธ๋ฅผ ์ค์ค๋ก ์ ๊ณตํ๊ธฐ ๋๋ฌธ์ด๋ค. ํ์ฅ ์ง์์ ๋น์ฉ๊ณผ ๊ต์ก ๋ถ๋ด์ ํฌ๊ฒ ๋ฎ์ถ๋ค.
04. ๊ฐ๋ฐ ๋ฐ CI ํ๋์จ์ด ํ
CI๋ก ๋์๊ฐ๋ ๋ณด๋ ๋์ ๊ฒฐ๊ตญ ์ธ์ ๊ฐ ๋ถํ ๋์ง ์๋ ์ด๋ฏธ์ง๋ก ํ๋์ฑ๋๋ค. ๋ถํธ๋ก๋ ์์ ๋คํธ์ํฌ ๋ณต๊ตฌ ๊ฒฝ๋ก๋ ํ์ด ์ด๋ฏธ ์ฐ๊ณ ์๋ ๋์ผ LAN ์์์ ์ด๋ฅผ ์๊ฐ ์น์ ๋ก ๋ง๋ค๋ฉฐ, ๋ณด๋๋น USB ํ๋ธ๋ ๋๋ฒ๊ทธ ํ๋ก๋ธ๋ฅผ ์๊ตฌํ์ง ์๋๋ค.
05. ์๊ฒฉ ๋ฐ ๋ฌด์ธ ์ค์น ํ๊ฒฝ
๋ฌผ๋ฆฌ์ ์ ๊ทผ ๋น์ฉ์ด ๋น์ผ ๊ณณ โ ์๊ฒฉ์ง, ์ฒ์ฅ, ์ฐจ๋, ์ ๋ฐ โ ์์ "LAN์ผ๋ก ๋ฟ๋๋ค"์ "๋ฐฉ๋ฌธํด์ผ ํ๋ค"์ ์ฐจ์ด๋ ์ ์ง๋ณด์ ์์ฐ ์ ์ฒด๋ฅผ ๊ฐ๋ฅธ๋ค.
๊ฒฐ๋ก
92 KB ๋ถํธ๋ก๋๊ฐ ์์ฒด ์น ๊ธฐ๋ฐ ํ์จ์ด ๋ณต๊ตฌ UI๋ฅผ ์๋นํ๋ค โ TCP/IP ์คํ์ด ํ๋์ ์์ฐ์ด ์๋๋ผ ์ด๋๋ท ์นฉ ์์ ์ด๊ณ ์๊ธฐ์ ๊ฐ๋ฅํ ์ค๊ณ.
- โ ์ ํ๋ฆฌ์ผ์ด์ ์ํ์ ๋ฌด๊ดํ๊ฒ ๋์ํ๋ ๋ถํธ๋ก๋ ๋ด๋ถ ๋คํธ์ํฌ ํ์จ์ด ๋ณต๊ตฌ ๊ตฌํ
- โ
WIZnet ์ปจํธ๋กค๋ฌ๋ฅผ 80๋ฒ ํฌํธ TOE TCP ์๋ฒ ๋ชจ๋(
Sn_MR_TCP) ๋ก ํ์ฉ, ์ ์ ํด๋ฐฑ์ ๊ฐ์ถ ioLibrary DHCP ๋ณํ - โ ๋ณต๊ตฌ ํด๋ผ์ด์ธํธ ์ ์ฒด๋ฅผ 983๋ฐ์ดํธ HTML๋ก ์ ๋ฌ โ ํด๋งยท๋๋ผ์ด๋ฒยท๊ต์ก ๋ถํ์
- โ
ํ๋์จ์ด TCP ์คํ๋ก๋ ๋๋ถ์ ์
๋ก๋ ๋ฃจํ๊ฐ
recv()์ ์ธํฐ๋ฝํธ ์ฐจ๋จ ์ํ์flash_range_erase()๋ฅผ ์์ ํ๊ฒ ๊ต์ฐจ ์ํ - โ
WIZnet OUI
00:08:DC์ RP2040 ๊ณ ์ ๋ณด๋ ID๋ก ์ ๋๋ณ MAC ํฉ์ฑ โ ํ๋ก๋น์ ๋ ๊ณต์ ์ ๋ก, BOM ๋น์ฉ ์ ๋ก - โ AES-ECB ์ด๋ฏธ์ง ์ํธํ์ SHA256 ๊ฒ์ฆ์ ์ ์ FOTA ๊ฒฝ๋ก๋ฟ ์๋๋ผ ๋ณต๊ตฌ ๊ฒฝ๋ก์์๋ ๊ฐ์
- โ ์ฝ 1 MB ์ ๋ก๋๋ฅผ 2๋ถ ์์ ๋๋ด๊ธฐ ์ํด SPI๋ฅผ 5 MHz์์ 36 MHz๋ก ์ํฅ
- โ ๋ณต๊ตฌ ํ๋ฆ์์ ์๊ฒฉ์ฆ๋ช ์ค์ ์ ์ ๊ฑฐํ๊ธฐ ์ํด ์ ์คํธ๋ฆผ Wi-Fi ์ค๊ณ ๋์ ์ ์ ์ด๋๋ท์ ์ ํ
- โ 2๋ ์ ๊ฑธ์น ํ์ฅ ํผ๋๋ฐฑ ๊ธฐ๋ฐ ์ปค๋ฐ์ผ๋ก ์งํํ, ์ค์ ์ถํ ์ค์ธ ์์ฉ ํ์จ์ด
์ฌํ์ฑ ์ฐธ๊ณ
์ด ์ฝ๋๋ ํน์ ์์ฉ ๋ณด๋์ฉ ์์ฐ ํ์จ์ด์ด๋ฉฐ, ํํ ๋ฆฌ์ผ์ด ์๋๋ผ ์ปดํฌ๋ํธ๋ก ๊ณต๊ฐ๋ผ ์๋ค. ์ฐธ๊ณ ํ๋ ค๋ ๋ ์๋ ๋ค์์ ์์์ผ ํ๋ค.
- ๋จ๋
๋น๋ ๋ถ๊ฐ.
CMakeLists.txt๊ฐETHERNET_FILES,IOLIBRARY_FILES,DHCP_FILESํ๊ฒ์ ๋งํฌํ๋๋ฐ, ์ด๋ค์ WIZnet์ RP2040-HAT-C / WIZnet-PICO-C ๋ผ์ด๋ธ๋ฌ๋ฆฌ ํธ๋ฆฌ๊ฐ ์ ์ํ๋ค. ๊ทธ ํธ๋ฆฌ์ ํฌํธ ๋ ์ด์ด(w5x00_spi.c,port_common.h),_WIZCHIP_์ ์,PICO_BUTTON_0์ ์ ์ํ๋ ๋ณด๋ ํค๋๊ฐ ๋ชจ๋ ๋น๊ณต๊ฐ ์์ ํ๋ก์ ํธ์ ์๋ค. - README๋ ์ ์คํธ๋ฆผ ์ค๊ณ๋ฅผ ์ค๋ช ํ๋ค. ๋ฌธ์๋ ์ฌ์ ํ ์๋ณธ Pico W / Wi-Fi ํ๋ก์ ํธ์ ์๋์ 36 KB ๋ถํธ๋ก๋ ํฌ๊ธฐ๋ฅผ ๋ฐ์ํ๋ค. ์ด๋๋ท ๋ณต๊ตฌ ๊ฒฝ๋ก์ 92 KB ๋ ์ด์์์ ์ฝ๋์๋ง ์กด์ฌํ๋ค.
- ์ ์ฉํ๋ ค๋ฉด ์ด ์ ์ฅ์๋ฅผ WIZnet-PICO-C ๊ธฐ๋ฐ ํ๋ก์ ํธ์ ๋ฃ๊ณ ,
_WIZCHIP_๊ณผPICO_BUTTON_0์ ์ ์ํ๊ณ ,pico_mbedtls๋ฅผ ์ ๊ณตํ๋ฉด ๋๋ค. ๋ณต๊ตฌ ๋ก์ง ์์ฒด๋bootloader.cํ๋์ ์์กฑ์ ์ผ๋ก ๋ด๊ฒจ ์๋ค.
Q&A
Q. ์ ์ํํธ์จ์ด ์คํ์ ์น์ MACRAW๊ฐ ์๋๋ผ Sn_MR_TCP์ธ๊ฐ? A. MACRAW๋ ํธ์คํธ์ ์ ์ด๋๋ท ํ๋ ์์ ๋๊ธฐ๊ณ , ๊ทธ ์์ ์์ ํ TCP/IP ๊ตฌํ์ ์๊ตฌํ๋ค. ์ด ๋ถํธ๋ก๋๊ฐ ๊ฐ๋นํ ์ ์๋ ๋ฐ๋ก ๊ทธ 40~60 KB๋ค. Sn_MR_TCP๋ ์คํ ์ ์ฒด๋ฅผ ์ปจํธ๋กค๋ฌ๋ก ์ฎ๊ฒจ ํธ์คํธ ์ธก์ ์์ผ ํธ์ถ๋ก ์ถ์ํ๋ค. ํฌ๊ธฐ ์ ์ฝ์ด ์๋ ๋ถํธ๋ก๋์์ TOE ๋ชจ๋๋ ํธ์๊ฐ ์๋๋ผ ์ฑ๋ฆฝ ์กฐ๊ฑด์ด๋ค.
Q. DHCP์ HTTP ์๋ฒ๊ฐ ๋ชจ๋ ์์ผ 1์ ์ด๋ค. ์ถฉ๋ํ์ง ์๋? A. ์ถฉ๋ํ์ง ์๋๋ค. ๋์๊ฐ ์๋๋ผ ์์ฐจ๋ค. DHCP_stop()์ด ์์ผ์ ๋ฐ๋ฉํ ๋ค socket(1, Sn_MR_TCP, 80, 0)์ด TCP ๋ฆฌ์ค๋๋ก ๋ค์ ์ฐ๋ค. ํ๋์จ์ด ์์ผ์ด 8๊ฐ๋ ์์ผ๋ ์ธ๋ฑ์ค๋ฅผ ๋๋ ์๋ ์์์ง๋ง, ํ๋๋ฅผ ์ฌ์ฌ์ฉํจ์ผ๋ก์จ ๋ณต๊ตฌ ์ํ ๋จธ์ ์ด ์๊ฒฉํ ์ ํ์ผ๋ก ์ ์ง๋์ด ์ถ๋ก ํ๊ธฐ ์ฌ์์ง๋ค.
Q. DHCP๊ฐ ์คํจํ๋ฉด? A. 2์ด์ง๋ฆฌ ์๋๋ฅผ ์ด ๋ฒ ๋ฐ๋ณตํ ๋ค, ๋ถํธ๋ก๋๋ network_initialize()๋ก 192.168.0.100/24, ๊ฒ์ดํธ์จ์ด 192.168.0.1์ ์ ์ ์ค์ ์ ์ ์ฉํ๋ค. ๋ฐ๋ผ์ DHCP ์๋ฒ๊ฐ ์๋ ๊ณ ๋ฆฝ๋ ๋งํฌ์์๋ ๋
ธํธ๋ถ์ ๊ฐ์ ์๋ธ๋ท์ผ๋ก ๋ง์ถ๋ฉด ๋ณต๊ตฌํ ์ ์๋ค.
Q. ํ๋์ ์๊ฑฐ๋ ์ธํฐ๋ฝํธ๋ฅผ ๋๋ค. ์ ์ก์ด ๊นจ์ง์ง ์๋? A. TOE ๋ถํ์ ์จ์ผ ํ๋ ๊ฐ์ฅ ๋ ์นด๋ก์ด ๊ทผ๊ฑฐ๊ฐ ์ด ์ง์ ์ด๋ค. swap_images()์ ํ๋์ ํ๋ก๊ทธ๋๋ฐ ๊ฒฝ๋ก๋ save_and_disable_interrupts() ํ์์ ์คํ๋๋ค. ์ํํธ์จ์ด ์คํ์ด๋ผ๋ฉด ๊ทธ ๊ตฌ๊ฐ ๋์ ACK๋ ๋ฒํผ๋ง๋ ํ์ง ๋ชปํ๋ค. WIZnet ์ปจํธ๋กค๋ฌ๋ ํธ์คํธ์ ๋
๋ฆฝ์ ์ผ๋ก ์์ฒด ๋ด๋ถ RX ๋ฒํผ์ ์์ ํ๋ฏ๋ก, ๋ถํธ๋ก๋๋ ๋ณต๊ท ํ getSn_RX_RSR(1)์ ํด๋งํด ์์ธ ๋งํผ ํผ๋ด๊ธฐ๋ง ํ๋ฉด ๋๋ค. TCP ํ๋ฆ ์ ์ด๋ ์นฉ์ด ์ฒ๋ฆฌํ๋ค.
Q. ๋ณต๊ตฌ ์
๋ก๋๋ ์ ์ ์ FOTA์ฒ๋ผ ๋กค๋ฐฑ์ ์์ฝํ์ง ์๋? A. ๋๋์๊ฐ ๋์์ด ์๊ธฐ ๋๋ฌธ์ด๋ค. ๋ณต๊ตฌ ๋ชจ๋๋ ์ ํ๋ฆฌ์ผ์ด์
์ฌ๋กฏ์ ์ ๋ขฐํ ์ ์์ ๋ ์กด์ฌํ๋ฏ๋ก, SHA256 ๊ฒ์ฆ์ ์ฑ๊ณตํ๋ฉด ๊ณง๋ฐ๋ก pfb_firmware_commit()๊ณผ _pfb_mark_is_not_after_rollback()์ ํธ์ถํ๊ณ , ๋ฆฌ๋ถํธ ์์ด ์ ์ด๋ฏธ์ง๋ก ๋ฐ๋ก ์ ํํ๋ค.
Q. ์ธ์ฆ ์๋ ์ ๋ก๋ ์๋ํฌ์ธํธ๋ฅผ ์ฌ๋ ๊ฒ ์์ ํ๊ฐ? A. ๋ ๊ฐ์ง ํต์ ๊ฐ ๊ฑธ๋ฆฐ๋ค. ์ด ์๋ํฌ์ธํธ๋ ์ ์ ์ธ๊ฐ ์ 5์ด๊ฐ ๋ฌผ๋ฆฌ ๋ฒํผ์ ์๋์ ์ผ๋ก ๋๋ฌ์ผ๋ง ์กด์ฌํ๋ฏ๋ก ์ ์ ์ด์ฉ ์ค์๋ ๋๋ฌํ ์ ์๋ค. ๊ทธ๋ฆฌ๊ณ ์ ๋ก๋๋ ๋ชจ๋ ์ด๋ฏธ์ง๋ ์ปดํ์ผ๋ AES ํค๋ก ์ ์ ๋ณตํธํ๋๊ณ ๋ง๋ถ์ SHA256๊ณผ ์ผ์นํด์ผ๋ง ์ค์์ด ํ์ฉ๋๋ค. LAN ์์ ๊ณต๊ฒฉ์๋ ๋น๋ ํ์ ํค ์์ด๋ ์ด๋ฏธ์ง๋ฅผ ์ค์นํ ์ ์๋ค.
Q. ๋ถํธ๋ก๋๋ ์
๋ก๋๊ฐ ๋๋ ๊ฑธ ์ด๋ป๊ฒ ์๋? A. ๋ ์ด์ ๋ฐ์ดํฐ๊ฐ ์ค์ง ์์ ๋๊น์ง ์์ผ์ ๋น์ฐ๋ฉด์ 256๋ฐ์ดํธ ์ ๋ ฌ ํ๋์ ์ฐ๊ธฐ๋ก ๋ฐ์ดํธ๋ฅผ ์ธ๊ณ , ๋์ ๊ธธ์ด๋ฅผ pfb_firmware_sha256_check(upload_done)์ผ๋ก ๊ฒ์ฆํ๋ค. ์๊ฒฐ์ฑ์ ํ์ธํด์ฃผ๋ ๊ฒ์ ๋ค์ด์ ์คํธ ๊ฒ์ฌ์ด๋ฉฐ, ์๋ฆฐ ์ ์ก์ ๊ฒ์ฆ์ ์คํจํด ๊ทธ๋๋ก ๊ฑฐ๋ถ๋๋ค.
Original Link: https://github.com/dickinsaudio/pico_fota (branch: da_dongle) Upstream: https://github.com/JZimnol/pico_fota_bootloader Author: DickinsAudio (Australia) License: MIT