利用WSL + VSCode + ESP-IDF6开发ESP32系列单片机指南
前言
我是在是接受不了沟槽的Windows了,开发体验太差,文件系统的性能不太行(可能跟我不会设置很大关系)导致当时编译ESP的工程够我出去溜一圈在回来的,比起来,Linux上开发程序显然性价比好的多。属于是一次开发之后一直爽了。
这篇博客的主要目的就是为了在WSL上开发单片机,之后开发其他的单片机就可以使用PlatformIO来解决问题,这里记录一下笔者的开发过程。
准备WSL
笔者是WSL常客,这里呢,笔者提醒一些设置,因为WSL会默认的Append上Windows的Path变量,笔者之前写前端被坑过,所以如果您希望您的WSL是专门用来开发单片机的话,建议在/etc/wsl.conf写上最后两行配置,这是告诉WSL加载环境变量的时候不要带上Windows的PATH,当然,如果这是您的意图,不要做。
❯ cat /etc/wsl.conf
[boot]
systemd=true
[automount]
enabled = true
options = "metadata"
mountFsTab = true
[interop]
appendWindowsPath = false
笔者喜欢使用的是Arch WSL,这里给想试试的朋友Github链接,或者是直接下默认的Arch WSL就好。
其余的WSL设置,没有任何新鲜的东西了
安装ESP-IDF prerequisites
笔者有代理,建议有代理的朋友,特别是使用Clash代理加速Github下载的朋友开启TUN模式,这个时候,Clash会虚拟化一个网卡,将整台电脑上任意网络活动都转发到虚拟网卡上,这样会让那些不使用环境变量走代理的脚本也用上代理,加速下载。
没有代理的朋友,单独参考一下ESP乐鑫官方教你咋下载:
(Tips:笔者建议您单独在一个独立的venv环境配置IDF开发所用的Python,这样不会炸整个WSL的Python环境)
下载好前提之后,按照思路下载ESP-IDF工具链就好了。这里不再重复。笔者建议你设置一个指向自己ESP-IDF工具链的export.sh,用在VSCode出问题看日志看不懂的时候,自己在命令行调试的时候可以方便的起起来环境的指令
alias export_esp_env="source /path/to/your/esp-idf/export.sh"
检查一下:
❯ export_esp_env # 测试一下我们的环境搭建好没有
Checking "python3" ...
Python 3.13.7
"python3" has been detected
Activating ESP-IDF 6.1
Setting IDF_PATH to '/home/charliechen/esp-idf'.
* Checking python version ... 3.13.7
* Checking python dependencies ... OK
* Deactivating the current ESP-IDF environment (if any) ... OK
* Establishing a new ESP-IDF environment ... OK
* Identifying shell ... zsh
* Detecting outdated tools in system ... OK - no outdated tools found
* Shell completion ... Autocompletion code generated
Done! You can now compile ESP-IDF projects.
Go to the project directory and run:
idf.py build
❯ idf.py --version # OK,很新,是6.1
ESP-IDF v6.1-dev-786-g130fdc7ce7-dirty
VSCode安装插件
VSCode安装的是ESP-IDF插件,下载好了之后,摁下Ctrl + Shift + P呼出命令面板,输入Configure ESP-IDF,这个时候,我们选择点击"ADVANCED"进入高级配置界面,选择我们刚刚下好的ESP-IDF工具链所在位置,和勾选所使用的Python环境(笔者是专门准备了一个独立的venv Python环境处理它),记得继续挂代理,这样的话配置的快。
笔者还额外建议你安装CMake的插件(CMake + CMake Tools)
准备工程
USB预备
下一步,我们就是要准备USB接入。WSL中的USB接入很麻烦。好在有一个好用的工具叫usbipd,这个工具的下载,很不幸,还是要翻墙的。
下载好这个工具并且安装结束后,打开管理员模式的CMD或者是Powershell,先插上你的ESP32开发板,准备设置,输入usbipd list,可以看到找到我们的ESP32端口的USB了,一般而言是COM打头的,记住BUSID是2-2
➜ usbipd list
Connected:
BUSID VID:PID DEVICE STATE
...
2-2 303a:1001 USB 串行设备 (COM5), USB JTAG/serial debug unit Shared
...
Persisted:
GUID DEVICE
下一步,我们要让他绑定到WSL上,以笔者的BUSID查出来是2-2为例子
# 不要复制粘贴,看你自己的BUSID操作!!!
usbipd bind -b 2-2 # 这个只用操作一次就好了,之后插拔的时候不用再输入它
usbipd attach --wsl --busid 2-2 # 这个指令会让USB Attach到WSL上,注意,此时的USB就没法在Windows主机上使用了,除非你断开它!
输入结束之后,最后一个指令的输出:
usbipd: info: Using WSL distribution 'simplechip' to attach; the device will be available in all WSL 2 distributions.
usbipd: info: Loading vhci_hcd module.
usbipd: info: Detected networking mode 'mirrored'.
usbipd: info: Using IP address 127.0.0.1 to reach the host.
这就是成功了
检查一下自己的WSL上有没有新的设备接入,一般而言,接入的映射设备是/dev/ttyACM*或者是/dev/ttyUSB*。笔者的就是/dev/ttyACM0
❯ ls /dev/ttyACM0
/dev/ttyACM0
❯ ls -lh /dev/ttyACM0
crw-rw---- 1 root uucp 166, 0 Nov 24 12:52 /dev/ttyACM0
Ctrl Shift P呼出命令面板,然后咱们就创建工程了,New Project设置自己所用的ESP32开发板的芯片型号,刚刚接入的USB端口等等信息。现在开始,模板中没有sample_project了,只有Hello World,现在勾选一下。
下一步是一个大坑------ESP-IDF在6.1版本的时候,支持最小化构建,如果你需要其他模块,比如说笔者的片子上有PSRAM,这就要关掉最小化构建。
idf_build_set_property(MINIMAL_BUILD OFF)
原本这里是ON,改成OFF,然后刷新一下就好了。(idf.py menuconfig或者是点击VSCode下侧状态栏,注意,不是VSCode的设置,而是显示自己芯片右侧的一个小齿轮),然后就可以开始设置自己芯片的属性了。注意,如果你有设置找不到,有可能是名称变更或者是MINIMAL_BUILD开启了,记得检查一下文档。
c
#include "esp_chip_info.h"
#include "esp_flash.h"
#include "esp_psram.h"
#include "esp_system.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "nvs_flash.h"
void app_main(void)
{
esp_err_t ret;
uint32_t flash_size;
esp_chip_info_t chip_info;
ret = nvs_flash_init();
if (ret == ESP_ERR_NVS_NO_FREE_PAGES
|| ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
ESP_ERROR_CHECK(nvs_flash_erase());
ret = nvs_flash_init();
}
esp_flash_get_size(NULL, &flash_size); /* 获取 FLASH 大小 */
esp_chip_info(&chip_info);
printf("cup%d\n", chip_info.cores); /* 获取 CPU 内核数并显示 */
/* 获取 FLASH 大小并显示 */
printf("FLASH size:%ld MB flash\n", flash_size / (1024 * 1024));
/* 获取 PARAM 大小并显示 */
printf("PSRAM size: %d bytes\n", esp_psram_get_size());
while (1) {
vTaskDelay(1000);
}
}
笔者玩的是ESP32S3,带PSRAM的,笔者建议您找您自己芯片的示例程序,拷贝一份编译一下试试看,编译也可以在下侧的状态工具栏中找到,这里不再教授了。编译成功后,会显示类似这样的东西:
Memory Type Usage Summary
┏━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━┳━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓
┃ Memory Type/Section ┃ Used [bytes] ┃ Used [%] ┃ Remain [bytes] ┃ Total [bytes] ┃
┡━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━╇━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━┩
│ Flash Code │ 96206 │ │ │ │
│ .text │ 96206 │ │ │ │
│ DIRAM │ 55167 │ 16.14 │ 286593 │ 341760 │
│ .text │ 39947 │ 11.69 │ │ │
│ .data │ 12820 │ 3.75 │ │ │
│ .bss │ 2400 │ 0.7 │ │ │
│ Flash Data │ 41428 │ │ │ │
│ .rodata │ 41172 │ │ │ │
│ .appdesc │ 256 │ │ │ │
│ IRAM │ 16384 │ 100.0 │ 0 │ 16384 │
│ .text │ 15356 │ 93.73 │ │ │
│ .vectors │ 1028 │ 6.27 │ │ │
│ RTC SLOW │ 36 │ 0.44 │ 8156 │ 8192 │
│ .force_slow │ 36 │ 0.44 │ │ │
│ RTC FAST │ 24 │ 0.29 │ 8168 │ 8192 │
│ .rtc_reserved │ 24 │ 0.29 │ │ │
└─────────────────────┴──────────────┴──────────┴────────────────┴───────────────┘
Total image size: 206821 bytes (.bin may be padded larger)
下一步,就是准备烧录了。
烧录和调试ESP32
烧录
烧录和调试会比较麻烦了,
ls -lh /dev/ttyACM0
crw-rw---- 1 root uucp 166, 0 Nov 24 12:52 /dev/ttyACM0
可以看到默认的情况下,我们是没有权限读写这个USB设备的,办法就是将自己加入到uucp中
sudo usermod -aG uucp $USER
newgrp uucp
groups # 检查一下自己的组
charliechen uucp wheel
现在就OK了,可以试一试Flash烧录
Connected to ESP32-S3 on /dev/ttyACM0:
Chip type: ESP32-S3 (QFN56) (revision v0.2)
Features: Wi-Fi, BT 5 (LE), Dual Core + LP Core, 240MHz, Embedded PSRAM 8MB (AP_3v3)
Crystal frequency: 40MHz
USB mode: USB-Serial/JTAG
MAC: 98:88:e0:00:86:f4
Stub flasher running.
Changing baud rate to 460800...
Changed.
Configuring flash size...
Flash will be erased from 0x00000000 to 0x00005fff...
Flash will be erased from 0x00008000 to 0x00008fff...
Flash will be erased from 0x00010000 to 0x00042fff...
SHA digest in image updated.
Wrote 22720 bytes (14489 compressed) at 0x00000000 in 0.4 seconds (429.3 kbit/s).
Hash of data verified.
Wrote 3072 bytes (132 compressed) at 0x00008000 in 0.1 seconds (485.9 kbit/s).
Hash of data verified.
Wrote 206944 bytes (114556 compressed) at 0x00010000 in 1.9 seconds (885.1 kbit/s).
Hash of data verified.
Hard resetting via RTS pin...
点击一下显示屏小按钮:
I (24) boot: ESP-IDF v6.1-dev-786-g130fdc7ce7-dirty 2nd stage bootloader
I (25) boot: compile time Nov 24 2025 10:36:40
I (25) boot: Multicore bootloader
I (27) boot: chip revision: v0.2
I (30) boot: efuse block revision: v1.3
I (33) qio_mode: Enabling default flash chip QIO
I (38) boot.esp32s3: Boot SPI Speed : 80MHz
I (41) boot.esp32s3: SPI Mode : QIO
I (45) boot.esp32s3: SPI Flash Size : 16MB
I (49) boot: Enabling RNG early entropy source...
I (53) boot: Partition Table:
I (56) boot: ## Label Usage Type ST Offset Length
I (62) boot: 0 nvs WiFi data 01 02 00009000 00006000
I (69) boot: 1 phy_init RF data 01 01 0000f000 00001000
I (75) boot: 2 factory factory app 00 00 00010000 001f0000
I (82) boot: 3 vfs Unknown data 01 81 00200000 00a00000
I (88) boot: 4 storage Unknown data 01 82 00c00000 00400000
I (95) boot: End of partition table
I (98) esp_image: segment 0: paddr=00010020 vaddr=3c020020 size=09fach ( 40876) map
I (112) esp_image: segment 1: paddr=00019fd4 vaddr=3fc91d00 size=03214h ( 12820) load
I (115) esp_image: segment 2: paddr=0001d1f0 vaddr=40374000 size=02e28h ( 11816) load
I (123) esp_image: segment 3: paddr=00020020 vaddr=42000020 size=169b0h ( 92592) map
I (142) esp_image: segment 4: paddr=000369d8 vaddr=40376e28 size=0ae18h ( 44568) load
I (151) esp_image: segment 5: paddr=000417f8 vaddr=50000000 size=00024h ( 36) load
I (158) boot: Loaded app from partition at offset 0x10000
I (158) boot: Disabling RNG early entropy source...
I (169) octal_psram: vendor id : 0x0d (AP)
I (169) octal_psram: dev id : 0x02 (generation 3)
I (169) octal_psram: density : 0x03 (64 Mbit)
I (171) octal_psram: good-die : 0x01 (Pass)
I (176) octal_psram: Latency : 0x01 (Fixed)
I (180) octal_psram: VCC : 0x01 (3V)
I (184) octal_psram: SRF : 0x01 (Fast Refresh)
I (189) octal_psram: BurstType : 0x01 (Hybrid Wrap)
I (194) octal_psram: BurstLen : 0x01 (32 Byte)
I (198) octal_psram: Readlatency : 0x02 (10 cycles@Fixed)
I (203) octal_psram: DriveStrength: 0x00 (1/1)
I (208) MSPI Timing: Enter psram timing tuning
I (213) esp_psram: Found 8MB PSRAM device
I (216) esp_psram: Speed: 80MHz
I (219) cpu_start: Multicore app
I (654) esp_psram: SPI SRAM memory test OK
I (663) cpu_start: GPIO 44 and 43 are used as console UART I/O pins
I (663) cpu_start: Pro cpu start user code
I (663) cpu_start: cpu freq: 240000000 Hz
I (665) app_init: Application information:
I (669) app_init: Project name: demo0
I (672) app_init: App version: 1
I (676) app_init: Compile time: Nov 24 2025 10:33:56
I (681) app_init: ELF file SHA256: b624351c6...
I (685) app_init: ESP-IDF: v6.1-dev-786-g130fdc7ce7-dirty
I (691) efuse_init: Min chip rev: v0.0
I (695) efuse_init: Max chip rev: v0.99
I (699) efuse_init: Chip rev: v0.2
I (703) heap_init: Initializing. RAM available for dynamic allocation:
I (709) heap_init: At 3FC95860 len 00053EB0 (335 KiB): RAM
I (714) heap_init: At 3FCE9710 len 00005724 (21 KiB): RAM
I (719) heap_init: At 3FCF0000 len 00008000 (32 KiB): DRAM
I (725) heap_init: At 600FE000 len 00001FE8 (7 KiB): RTCRAM
I (730) esp_psram: Adding pool of 8192K of PSRAM memory to heap allocator
I (737) spi_flash: detected chip: boya
I (740) spi_flash: flash io: qio
I (743) sleep_gpio: Configure to isolate all GPIO pins in sleep state
I (749) sleep_gpio: Enable automatic switching of GPIO sleep configuration
I (756) main_task: Started on CPU0
I (759) esp_psram: Reserving pool of 32K of internal memory for DMA/internal allocations
I (767) main_task: Calling app_main()
cup2
FLASH size:16 MB flash
PSRAM size: 8388608 bytes
OK,程序正常跑起来了,没毛病!
调试
调试也是一个大麻烦,笔者发现半天自己的OpenOCD跑不起来。
Info : esp_usb_jtag: VID set to 0x303a and PID to 0x1001
Info : esp_usb_jtag: capabilities descriptor set to 0x2000
Info : Listening on port 6666 for tcl connections
Info : Listening on port 4444 for telnet connections
❌ Error: libusb_open() failed with LIBUSB_ERROR_ACCESS
❌ Error: esp_usb_jtag: could not find or open device!
/home/charliechen/.espressif/tools/openocd-esp32/v0.12.0-esp32-20250707/openocd-esp32/share/openocd/scripts/target/esp_common.cfg:9: Error:
Traceback (most recent call last):
File "/home/charliechen/.espressif/tools/openocd-esp32/v0.12.0-esp32-20250707/openocd-esp32/share/openocd/scripts/target/esp_common.cfg", line 9, in script
For assistance with OpenOCD errors, please refer to our Troubleshooting FAQ: https://github.com/espressif/openocd-esp32/wiki/Troubleshooting-FAQ
OpenOCD Exit with non-zero error code 1
[Stopped]
说明对于OpenOCD而言,权限还是有点问题。这个时候,我们检查一下
ls -l /dev/bus/usb/001/002
crw-rw-r-- 1 root root 189, 1 Nov 24 12:08 /dev/bus/usb/001/002
ls /dev/ttyACM*
/dev/ttyACM0
当前 USB 设备属于 root:root,这麻烦了,难怪OpenOCD挂了,我们要告诉一下USB子系统正确的权限
sudo nano /etc/udev/rules.d/99-espressif.rules
写入如下内容
提示一下,
ATTR{idVendor}=="303a", ATTR{idProduct}=="1001"的内容不要乱写 ,需要你看OpenOCD的这里的提示:esp_usb_jtag: VID set to 0x303a and PID to 0x1001
# Espressif USB JTAG/Serial
SUBSYSTEM=="usb", ATTR{idVendor}=="303a", ATTR{idProduct}=="1001", MODE="0666", GROUP="wheel", TAG+="uaccess"
重启一下,然后记得拔掉USB设备重新插入
sudo udevadm control --reload-rules
sudo udevadm trigger
注意,这个时候还需要重新的USB WSL Attach,这样设备才会正确的出现。
重新测试一下,现在可以进行调试了!