Show HN: Esp32sim - 브라우저에서 ESP32 펌웨어 실행하기
요약
esp32sim은 Rust로 구현된 명령어 수준의 ESP32 에뮬레이터입니다. WebAssembly 버전은 설치나 계정 없이 브라우저에서 실행 가능하며, 실제 마스크 ROM부터 애플리케이션까지 다양한 칩(ESP32, S3, C3, C6)의 펌웨어를 지원합니다. 이 도구는 여러 SoC 아키텍처를 통합하여 주변장치와 실행 환경을 공유하는 구조로 설계되었습니다.
핵심 포인트
- 브라우저 기반 WebAssembly 에뮬레이터로 접근성이 높음
- ESP32, S3, C3, C6 등 다양한 칩의 명령어 수준 시뮬레이션 지원
- 실제 하드웨어와 유사한 주변장치(UART, GPIO 등)를 모델링함
- S3는 성숙 단계, C3/C6는 초안 단계로 개발 진행 중
esp32sim 은 Rust로 구현한 명령어 수준 ESP32 에뮬레이터로, Xtensa와 RISC-V 기반 4개 SoC에서 실제 마스크 ROM, 부트로더, 수정하지 않은 애플리케이션을 실행함
WebAssembly 버전은 설치나 계정 없이 브라우저에서 실행되며, 디스플레이와 터치, 오디오, 카메라를 사용하는 보드 데모와 자체 펌웨어 로딩을 지원함
칩별 CPU와 메모리 맵은 분리하되 주변장치와 실행 환경을 공유 하며, ESP32-S3는 AArch64 및 WebAssembly JIT를 갖추고 C3/C6는 인터프리터로 실행함
가상 WiFi AP 에 수정하지 않은 Espressif WiFi 바이너리를 연결하고 네이티브 환경에서는 NAT로 실제 네트워크에 접근할 수 있음. 브라우저에는 NAT가 없고, C6 무선 실행에는 아날로그 보정을 우회하는 스텁이 필요함
실제 하드웨어와의 차등 테스트 를 수행하지만 연속적인 사이클 단위 일치를 보장하지는 않음. S3는 성숙 단계, C3/C6는 초안 단계이며, S3도 일부 화면 갱신 부하에서는 실시간 실행이 지연됨
실행 범위와 칩별 차이
ESP32, ESP32-S3, ESP32-C3, ESP32-C6 를 명령어 수준에서 에뮬레이션하며, 실제 마스크 ROM부터 2단계 부트로더, FreeRTOS, 애플리케이션까지 부팅함
기본 실행 모델은 펌웨어를 패치하거나 중간에 스텁을 넣지 않는 방식이지만, C6 무선 실행에는 별도의 예외가 있음
클라우드나 계정 없이 사용할 수 있으며 프로젝트 라이선스는 MIT임
기존 ESP32 는 듀얼 Xtensa LX6 에서 ECO3 ROM을 부팅하고 GPIO, UART, 타이머를 지원함
LilyGO TTGO T-Display 보드 모델을 선택하면 ST7789 디스플레이와 버튼을 추가함
ESP32-S3 는 240MHz 듀얼 Xtensa LX7을 모델링하며, 블록 인터프리터와 AArch64 JIT를 사용함
처리 속도 표기는 초당 약 1억 5,000만~2억 4,000만 명령어이며, 지원 상태는 성숙 단계임
ESP32-C3 는 160MHz RV32IMC , ESP32-C6 는 160MHz RV32IMAC 단일 코어를 모델링함
두 칩은 블록 캐시나 JIT 없는 인터프리터를 사용하며, hello_world
는 실시간보다 충분히 빠르게 실행됨
모두 초안 단계이며, C3는 콘솔만 지원하고 애플리케이션 직접 부팅인 --boot app
은 지원하지 않음
C6는 자체 메모리 맵과 PLIC를 사용하며 디스플레이 보드와 무선 기능도 지원함
공통 실행 구조와 주변장치 모델
Machine<S: Soc>
하나가 모든 칩의 스케줄러, 장치 시간, 콘솔, 스크립트, 웹 UI, 로더, 보드 및 가상 네트워크를 담당함
각 칩이 코어, 버스, 인터럽트 라우팅을 연결하는 구조이며, CPU 크레이트는 Xtensa와 RISC-V로 분리됨
emu-core
는 Bus
, Core
, Trap
, ClockTree
, AArch64 인코더를 공유하고, esp-periph
는 공통 주변장치를 제공함
UART, USB-Serial/JTAG, 시스템 타이머, 타이머 그룹, GPIO, SPI 플래시 컨트롤러, GDMA, SHA/AES/RSA, I2S, RMT, I2C 등을 재사용함
C3는 자체 메모리 맵, 캐시 컨트롤러, 인터럽트 매트릭스를 추가함
C6는 자체 메모리 맵과 MMU, PLIC/INTPRI, L1 캐시, PCR, 리셋 원인과 RTC 타이머 등을 담당하는 저전력 블록을 추가함
Xtensa 실행기 는 윈도 레지스터, 루프, XEA2 예외와 인터럽트, FPU, MAC16, 불리언 연산, PIE SIMD를 구현함
RISC-V 실행기는 RV32IMAC 디코더, 인터프리터, 디스어셈블러를 제공함
공통 CLI는 칩을 선택해 실행하며, esp32sim-c3
와 esp32sim-c6
는 별칭 바이너리임
확장 방식과 설계는 구성요소 추가 안내 , 아키텍처 , 주변장치 지원 범위 , 설계 결정과 주의점 에서 확인할 수 있음
보드 모델과 실제 펌웨어 데모
SoC가 GPIO 에지, RMT 심벌 스트림, I2S 샘플 같은 핀 수준 이벤트 를 내보내면 BoardModel
이 이를 해석함
새 보드는 GPIO 변경, RMT 프레임, I2C 장치, 카메라 프레임 등을 처리하는 트레이트를 구현해 추가함
보드 없는 모드는 ESP32-S3 펌웨어를 콘솔 전용으로 실행함
기본 Atech 14포트 보드 는 ST7735, WS2812 링, 인코더, 버튼을 갖춤
Pocket Synth 데모는 별도 프로젝트인 atech-firmware 의 빌드 결과물을 사용함
PlatformIO 기반 SID 신시사이저와 실제 .sid
음악을 재생하는 cRSID 플레이어이며, 빌드된 이미지를 저장소에 포함함
Waveshare 터치 디스플레이 두 종류를 모델링함
Touch-LCD-4B는 LCD_CAM RGB 버스의 ST7701S 480×480 화면, GT911 터치, TCA9554, 코덱을 갖추고 esp32-screen
LVGL 패널을 실행함
Touch-AMOLED-1.8 V2는 QSPI의 CO5300 368×448 화면과 CST820 터치를 사용함
Waveshare ESP32-S3-CAM-OV5640 모델은 카메라 입력부터 보행자 감지, 소리 출력까지 실행함
CH32V003 IO 확장기, SCCB로 연결한 OV5640, ES8311/ES7210 코덱, I2S1 스피커, LCD_CAM DVP 카메라 포트를 모델링함
waveshare-autopling
은 IDF 5.5, 플래시 16MB, 옥탈 PSRAM 8MB 구성을 사용함
이미지 파일이나 브라우저의 사진 업로드/웹캠 입력을 받아, 에뮬레이션한 PIE SIMD에서 esp-dl
보행자 감지기를 실행하고 ES8311로 알림음을 출력함
Waveshare ESP32-C6-LCD-1.47 은 SPI2와 GDMA를 통한 ST7789 172×320 화면, WS2812, BOOT 버튼을 지원함
802.15.4 MAC의 에너지 스캔 응답을 이용해 LVGL 스펙트럼 스캐너 펌웨어를 실행함
무선 전송 시간에 맞춘 프레임 송수신도 지원하며, --cooja
로 Cooja-NG의 외부 모트가 되어 NDJSON으로 정확히 동기화함
수정하지 않은 Contiki-NG-on-IDF 이미지가 에뮬레이션된 MSP430 노드와 브로드캐스트를 주고받는 사례가 있음
브라우저 실행과 실시간 UI
설치 없는 온라인 데모 는 동일한 에뮬레이터를 WebAssembly로 컴파일 해 Web Worker 안에서 실행함
보드를 선택하고 디스크에서 ROM ELF와 펌웨어를 불러오거나, 호스팅된 매니페스트를 선택해 부팅함
Chrome에서 hello_world
, Touch-LCD-4B의 SID 플레이어, Atech 보드, ESP32-C3/C6 데모가 실시간으로 실행됨
S3에는 별도의 WebAssembly JIT 가 있으며, 자주 실행되는 블록과 영역을 컴파일하고 PIE 연산에 WASM SIMD를 사용함
온라인 데모에서 Linux-on-esp32-S3 의 Linux 6.11 에 로그인해 콘솔을 사용할 수 있음
pocket-tank 는 Touch-AMOLED-1.8 화면의 물고기를 4비트 트랜스포머로 제어함
데모 펌웨어는 저장소에 있지만, 마스크 ROM, xterm.js, Linux 이미지는 별도로 받아야 함
네이티브 에뮬레이터의 라이브 보드 UI 는 실시간 실행과 함께 웹 화면을 제공함
노브, LED 링, 버튼, 스피커 VU, 실제 방향 및 읽기 쉬운 별도 ST7735 화면, USB-CDC/UART0 콘솔, SDK JSON 입력창, WebAudio 출력을 갖춤
버튼 클릭과 노브의 휠/드래그/방향키 조작을 지원함
시간 지정 스크립트 로 에뮬레이션 시간에 맞춰 버튼 누르기, 인코더 회전, 직렬 입력, 실행 중지를 재현할 수 있음
WiFi와 실제 네트워크 연결
S3, C3, C6 및 기존 ESP32는 가상 액세스 포인트 에 수정하지 않은 Espressif WiFi 바이너리로 연결함
스캔, 인증, 연결, WPA2-PSK 4단계 핸드셰이크를 지원함
뒤쪽 가상 네트워크는 DHCP, ARP, ICMP, DNS와 호스트 시각 기반 SNTP를 제공함
네이티브 환경의 기본 NAT 모드 는 게스트 TCP/UDP를 일반 호스트 소켓으로 전달함
펌웨어 변경, 루트 권한, tun 장치 없이 실제 네트워크에 접근하며, 네트워크 연결을 거부하는 모드도 있음
esp32-screen
에너지 패널은 WiFi 접속과 주소 할당, 시각 동기화 후 HTTPS로 이틀치 전기 요금을 받아오고 LAN의 실제 Home Assistant를 조회함
C6 무선 모델 은 단일 스테이션, 레거시 전송률을 지원하며 절전 기능은 없음
PHY 베이스밴드 보정이 아날로그 하드웨어를 요구하므로 모든 C6 무선 실행에는 --stub bb_init=0
이 필요함
C3도 S3에서 파생한 자체 MAC 모델로 같은 AP와 네트워크에 접속하며, 단일 스테이션을 지원함
공통 WiFi 예제는 스캔, 연결, DHCP 임대, 핑 5회 를 수행하고 C6 화면에 상태를 표시함
화면을 제외한 S3/C3/C6 빌드 결과물이 회귀 테스트 입력으로 저장되어 있어 로컬 빌드 없이 실행할 수 있음
c6-radio-dashboard
는 한 펌웨어 안에서 WiFi와 802.15.4 에너지 스캔을 각각 별도 페이지로 실행함
사용법과 제약은 네트워킹 안내 에 있으며, WiFi 모델 , 패킷 경로 , C6 WiFi 모델 , 공통 네트워크 계층 문서도 제공함
부팅, 리셋과 디버깅
ESP-IDF에 포함된 ROM ELF 를 사용하며, ESP-IDF 없이 필요한 ROM과 데모 자산을 별도로 가져올 수도 있음
S3는 ROM과 부트로더를 건너뛰고 앱 이미지를 직접 로딩하는 모드도 지원함
기본 hello_world
예제는 ROM 배너, 부트로더와 앱 로그, 카운트다운을 출력한 뒤 실제 칩처럼 RTC 워치독으로 재부팅하며 약 30배속으로 실행됨
소프트웨어 리셋과 RTC 워치독 리셋 은 올바른 리셋 원인을 유지하면서 ROM부터 머신을 다시 시작함
첫 리셋에서 실행을 멈추는 옵션도 있음
MAC 주소, 리셋 원인, 스트랩 값을 실제 보드에 맞춰 비교할 수 있음
디버깅 기능 으로 명령어 추적, 중단점, 메모리 변경 감시, 메모리 조회, 심벌을 포함한 디스어셈블, PC 히스토그램을 제공함
알 수 없는 레지스터의 첫 접근 기록, 예외 횟수에 따른 중지, 디스플레이 GRAM 이미지 출력도 지원함
JIT를 끈 인터프리터 실행은 JIT 실행과 같은 결과를 내야 함
상세 옵션은 CLI 문서 , UI 연동은 웹 UI 프로토콜 에서 확인할 수 있음
실제 칩과의 비교 및 검증 범위
과거 수동 실행의 전체 명령어 목록 디코더 비교 에서 S3 약 97만 7,000개, C3 약 16만 1,000개, C6 약 12만 6,000개 명령어가 불일치 없이 통과함
현재 CI는 저장소에 포함된 샘플 디코더 코퍼스와 에뮬레이터의 기준 출력 회귀 테스트를 실행하며, 외부 전체 목록 테스트는 제외함
Xtensa objdump
테스트는 PIE 피연산자 비교를 제외함
구체적인 범위는 테스트 안내 , CI 워크플로 , Xtensa 디코더 테스트 에 명시됨
C3/C6는 동일 바이너리의 콘솔 출력 을 실제 보드와 비교함
C3는 세 번의 부팅 주기에서 208줄 중 205줄이 같았으며, 차이는 ROM의 Saved PC:
줄임
C6는 204줄 중 203줄이 같았고, 첫 부팅의 Saved PC:
만 달랐음. esp_restart()
이후에는 해당 줄도 일치함
두 비교에서 각각 에뮬레이터 버그 5개를 찾았으며, 장치 명령 경쟁 상태와 리셋 시 초기화되지 않는 사이클 카운터 등이 포함됨
S3는 JTAG 동기 단일 스텝 으로 실제 칩과 에뮬레이터를 비교함
같은 플래시 덤프, eFuse, 스트랩 핀, 주변장치 리셋 상태에서 시작하고 명령어마다 PC, a0..a15
, ps
, windowbase
를 비교함
ROM 리셋 경로 8,000스텝과 IDF 5.5 부트로더 3,000스텝 이상에서 PC 분기 차이가 없었음
남은 레지스터 차이는 이전 부팅에 따라 달라지는 RTC 영역 값임
이 검증은 중단 없는 사이클 단위 일치가 아님
단일 스텝이 강제하는 PS.INTLEVEL
을 마스킹하고, 디버거가 원자적으로 넘어가는 윈도 예외 핸들러를 숨김
실제 단일 스텝에는 밀리초가 걸리므로 반복 횟수가 달라지는 CCOUNT 기반 지연 루프를 재동기화함
비교 방식은 하드웨어 비교기 에 구현되어 있음
성능 최적화와 실시간 실행의 한계
S3는 명령어 블록을 한 번 디코딩해 캐시 하고 AArch64 네이티브 코드로 컴파일함
waiti
상태의 유휴 코어는 건너뛰고, 계산된 타이머 기한에 따라 장치 시간을 지연 처리하며, 인터럽트 소스가 변할 때만 인터럽트 라인을 다시 계산함
단계별 측정은 성능 문서 에 정리되어 있음
Pocket Synth는 유휴 상태와 음 재생 중에는 실시간 실행에 여유가 있지만 전체 화면 다시 그리기에서는 지연 이 생김
코어 1이 SPI를 비트뱅잉하며 100% 사용될 때 20ms당 480만 명령어, 즉 초당 2억 4,000만 명령어가 필요함
M 시리즈 MacBook에서 해당 작업은 초당 약 7,000만 명령어를 처리하며, 다시 그릴 때마다 약 0.3초 지연됨
UI의 적응형 오디오 버퍼 는 언더런 시 최대 400ms까지 늘어나 지연을 흡수하고, 실행 속도 조절기가 이후 지연을 회복함
0.5초 이상 뒤처지면 몰아서 실행하지 않고 시간을 재동기화함
UI는 실시간 여부, 지연 시간, 재동기화 횟수를 표시함
느린 실행 구간의 코어별 명령어 수와 PC를 기록하고, 브라우저 없이 UI 입력을 자동화해 갱신 간격, 지연, 오디오 전달량 을 측정하는 도구도 제공함
구현 근거와 라이선스
구현은 ESP-IDF 레지스터 헤더와 Xtensa 코어 설정 , RISC-V 명세, C3/C6 기술 참조 매뉴얼, 관찰한 펌웨어 동작을 바탕으로 작성됨
QEMU는 명령어 의미 확인에만 참고했으며 코드를 복사하지 않았음
프로젝트는 MIT 라이선스 로 배포됨
서드파티 코드와 데모 자산은 각자의 라이선스 를 유지함
pocket-tank 는 MIT이며 부트로더, 파티션 테이블, 앱은 저장소에 포함하고 모델은 별도로 가져옴
MicroPython T-Display 데모는 공식 ESP32_GENERIC v1.29.0 이미지와 MIT 라이선스의 st7789py
드라이버/폰트를 사용하며, 공식 이미지는 별도로 가져옴
마스크 ROM ELF 는 Apache-2.0, xterm.js 는 MIT, Linux 이미지 는 GPL-3.0이며 모두 저장소에 포함하지 않고 별도로 가져옴
ESP-IDF ADC 곡선 보정 계수와 S3 ADC1 보정 데이터는 Apache-2.0이며 esp-periph/src/sar_adc.rs
에 있음
댓글과 토론
AI 자동 생성 콘텐츠
본 콘텐츠는 GeekNews의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기