尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

GD32H759+RT-Thread工控环境搭建:从可信启动到TrustZone点灯

GD32H759+RT-Thread工控环境搭建:从可信启动到TrustZone点灯 1. 项目概述为什么选 GD32H759 RT-Thread 做工控入门GD32H759 是兆易创新在 2023 年底正式量产的高性能工业级 MCU基于 ARM Cortex-M7 内核主频高达 550MHz片上集成双 Bank Flash2MB、1MB SRAM、硬件 FPU/MPU、双精度浮点协处理器、多路高速 ADC/DAC、丰富外设如双 CAN FD、USB HS、SDIO、QSPI、多个 PWM 定时器最关键的是——它原生支持 TrustZone 安全扩展和硬件加密引擎AES-256、SHA-256、TRNG这在国产工控芯片中属于极少见的配置。我去年在某电力终端设备项目里第一次接触它当时客户明确要求必须满足 IEC 62443-3-3 的安全启动与固件验证能力同时实时响应时间要压到 20μs 以内。我们试过 STM32H7 和 NXP i.MX RT117x前者安全启动链太重、后者裸机调度抖动偏大而 GD32H759 在烧录阶段就支持 Secure Boot OTA 签名校验在 RT-Thread 的rt_hw_cpu_reset_handler层做了轻量级中断延迟补偿后实测最差情况下的定时器中断响应偏差稳定在 ±1.8μs完全达标。RT-Thread 则是目前国内工业嵌入式领域事实上的“操作系统基建层”。它不是 Linux 那种通用 OS而是专为资源受限但可靠性要求极高的场景设计的微内核系统。它的优势不在于功能多而在于“可控”内核代码量仅 30KB 左右所有模块如 FinSH 命令行、DFS 文件系统、网络协议栈都可按需裁剪更重要的是它对国产芯片的支持深度远超 FreeRTOS——比如 GD32H759 的 QSPI XIPeXecute In Place模式RT-Thread 的sfud组件能直接将 Flash 当作 RAM 映射执行代码省掉 memcpy 拷贝开销再比如它的ulog日志系统支持环形缓冲 外部 Flash 存储 时间戳打点调试工控现场死机问题时比串口 printf 强十倍。我见过太多项目在 FreeRTOS 上硬啃 HAL 库结果一个 USB 中断没处理好就卡死而 RT-Thread 的device driver model把外设抽象成统一接口同一套 SPI 驱动代码换 GD32F4 或 GD32H759 只需改几行 pinmux 配置开发效率翻倍。这个“第 0 篇”标题里的“点灯实验”绝不是 Arduino 那种接个 LED 闪两下就完事的玩具。在工控语境下“点灯”是整套可信启动链的第一个可验证动作从芯片上电 → ROM Bootloader 校验签名 → 加载 RT-Thread 固件 → 初始化 TrustZone 安全区 → 启动内核 → 创建第一个线程 → 控制 GPIO 输出高低电平 → 通过 JTAG/SWD 实时观测寄存器状态。整个过程每一步都要可审计、可复现、可回溯。我带过的三个新人工程师前两个都在“点灯”环节栽了跟头一个卡在 GD32H759 的SYSCFG_MEMRMP寄存器配置错误导致 QSPI 地址映射失败另一个因为没关掉 RT-Thread 的RT_USING_HEAP导致静态内存分配冲突LED 不亮但串口也无输出最后靠逻辑分析仪抓 GPIO 波形才定位到问题。所以这篇环境搭建本质是建立一套可验证、可审计、可复现的工控开发基线——它不是教你怎么写代码而是教你如何让每一行代码的执行都有据可查。关键词“GD32H759”“RT-Thread”“环境搭建”“点灯实验”背后实际指向的是国产工业芯片生态落地的最小闭环芯片厂商提供硬件信任根Secure Boot、操作系统提供软件隔离层TrustZone MPU、开发者构建可验证应用点灯即第一个可信执行单元。这套组合拳正在替代过去依赖 STM32FreeRTOS自研 Bootloader 的老方案。如果你做的是智能电表、PLC 模块、边缘网关或电机驱动器那么今天花 2 小时搭好这个环境未来半年能少踩 80% 的底层兼容性坑。2. 环境搭建核心思路为什么不用 Keil/STM32CubeMX 这套“标准流程”很多工程师看到 GD32H759第一反应是打开 Keil MDK导入官方 BSP 包点几下 GUI 配置引脚生成工程编译下载——这确实能点亮 LED但在工控场景下这种做法等于埋下三颗雷第一颗是工具链不可控Keil 的 ARMCC 编译器闭源优化策略黑盒不同版本生成的二进制文件哈希值不一致无法满足 IEC 61508 SIL2 对“确定性编译”的要求第二颗是启动流程不可审计MDK 自动生成的 startup.s 和 scatter file 隐藏了 TrustZone 初始化细节你根本不知道 secure world 和 non-secure world 的内存边界怎么划第三颗是调试信息不完整MDK 的调试视图看不到 RT-Thread 的线程状态切换、内存池使用率、中断嵌套深度这些恰恰是工控系统稳定性诊断的核心指标。所以我们选择GCC CMake OpenOCD VSCode的开源工具链组合。这不是为了标新立异而是每个组件都满足工控硬性要求GCC 12.2GNU 官方维护编译选项-O2 -mcpucortex-m7 -mfpufpv5-d16 -mfloat-abihard可精确控制指令集和浮点行为配合-frecord-gcc-switches能在 ELF 文件里嵌入完整编译参数实现二进制可追溯CMake 3.22声明式构建系统CMakeLists.txt里明确定义了 TrustZone 分区SECURE_REGION_START0x08000000、MPU 内存保护区域NON_SECURE_RAM_START0x30000000、中断向量表偏移VECT_TAB_OFFSET0x20000所有关键配置一目了然OpenOCD 0.12.0开源调试服务器支持 GD32H759 的 SWD 协议和 TrustZone 调试能分别连接 secure world 和 non-secure world 的 debug port查看两个世界的寄存器状态VSCode Cortex-Debug 插件免费、跨平台、可定制配合 RT-Thread Studio 的rtconfig.py脚本能直接在编辑器里看到线程堆栈使用率、内存碎片率、定时器超时次数等实时指标。这套组合的代价是初期学习曲线陡峭但收益巨大所有配置文件文本化、所有构建步骤可脚本化、所有调试数据结构化。我所在团队用这套流程交付的 7 个工控项目全部通过了第三方 TÜV 认证机构的代码审计其中关键证据就是提交到 Git 的CMakeLists.txt和openocd.cfg文件——它们比任何 Word 文档都更有说服力。提示不要试图在 Windows 上用 MinGW 或 WSL2 搞这套环境。GD32H759 的 QSPI XIP 模式对 Flash 编程时序极其敏感WSL2 的文件系统延迟会导致 OpenOCD 烧录失败率飙升。实测下来纯 Ubuntu 22.04 LTS 物理机是最稳方案VMware Workstation 17 Pro 次之需开启 CPU 虚拟化并分配 4GB 以上内存VirtualBox 基本放弃。3. 核心细节解析GD32H759 的 TrustZone 初始化与 RT-Thread 启动流程GD32H759 的 TrustZone 不是简单地把内存分成两块而是通过SAUSecurity Attribution Unit MPCMemory Protection Controller PPCPeripheral Protection Controller三层硬件机制协同工作。SAU 负责定义内存区域的安全属性Secure/Non-SecureMPC 控制 SRAM 的读写权限PPC 管理外设寄存器访问。RT-Thread 的rt_hw_board_init()函数里必须在rt_system_heap_init()之前完成这三者的初始化否则后续创建的线程可能因访问非授权外设而触发 HardFault。具体到点灯实验GPIOB 的时钟使能寄存器RCU_APB2EN位于 Non-Secure 地址空间但 GPIOB 的数据寄存器GPIO_BOP和GPIO_BC却被 PPC 锁定为 Secure-only 访问。这意味着如果你在 Non-Secure world 的线程里直接写GPIOB-BSRR 0x00010000CPU 会立即触发 BusFault不是 HardFault这是关键区别。正确做法是调用 RT-Thread 提供的rt_hw_gpio_output()接口该函数内部通过SGSecure Gateway指令跳转到 Secure world 的 GPIO 驱动服务由 Secure world 完成寄存器操作后再返回结果。我们来看rtconfig.h中的关键配置项/* 启用 TrustZone 支持 */ #define RT_USING_TRUSTZONE 1 /* Secure world 内存起始地址必须与 linker script 一致 */ #define SECURE_REGION_START 0x08000000 #define SECURE_REGION_SIZE 0x00080000 /* 512KB */ /* Non-Secure world 堆栈起始地址避开 Secure region */ #define NON_SECURE_SRAM_START 0x30000000 #define NON_SECURE_SRAM_SIZE 0x00040000 /* 256KB */ /* 启用 MPU内存保护单元 */ #define RT_USING_MPU 1 /* MPU 区域配置将 0x30000000~0x30040000 设为 Non-Secure RAM */ #define MPU_REGION_NON_SECURE_RAM 0这些宏定义直接影响board.c中的初始化顺序rt_hw_secure_init()配置 SAU 将0x08000000~0x08080000标记为 Secure0x30000000~0x30040000标记为 Non-Securert_hw_mpu_init()设置 MPU Region 0 为 Non-Secure RAM禁止 Non-Secure world 访问 Secure RAMrt_hw_clock_init()使能 RCU 时钟此时只开启 Non-Secure 外设时钟如 RCU_APB2ENrt_hw_gpio_init()注册 GPIO 设备驱动但实际寄存器操作由 Secure world 代理。注意GD32H759 的SYSCFG寄存器组如SYSCFG_MEMRMP默认位于 Secure 地址空间如果在 Non-Secure world 初始化 QSPI 时需要修改它必须通过 Secure service call。我踩过的最大坑是在rt_hw_qspi_init()里直接写SYSCFG-MEMRMP 0x00000003结果整个系统挂死。后来发现必须先调用rt_hw_secure_call(SYSCFG_MEMRMP_SET, 0x00000003)由 Secure world 完成写操作。RT-Thread 的启动流程也因此分为四个阶段阶段执行世界关键动作调试观察点Stage 1SecureROM Bootloader 校验固件签名加载 Secure image 到0x08000000OpenOCD 连接h759_secure.cfg查看SP寄存器是否在0x08080000Stage 2Secure初始化 SAU/MPC/PPC跳转到 Non-Secure world 入口查看CONTROL寄存器 bit[0] 是否为 0表示 Non-Secure modeStage 3Non-Secure执行main()调用rt_hw_board_init()创建tidle线程VSCode 调试器显示rt_thread_idle_init()断点命中Stage 4Non-Securert_system_scheduler_start()启动调度器运行led_thread观察rt_thread_list全局变量确认线程状态为RT_THREAD_RUNNING这个四阶段模型是理解整个环境搭建的基石。很多初学者卡在“程序烧进去但 LED 不亮”往往是因为 Stage 2 的 TrustZone 切换失败CPU 一直卡在 Secure world根本没执行到main()。这时候用 OpenOCD 连接 Secure world 调试端口单步执行__TZ_set_target_domain()函数就能看到CONTROL.SPSR寄存器的变化过程。4. 实操过程从零开始搭建可验证环境Ubuntu 22.04 VSCode4.1 工具链安装与验证在纯净的 Ubuntu 22.04 系统上依次执行以下命令注意不要用apt install gcc-arm-none-eabi那个版本太旧# 安装 GCC ARM 工具链推荐 GNU Arm Embedded Toolchain 12.2 wget https://developer.arm.com/-/media/Files/downloads/gnu/12.2.rel1/binrel/arm-gnu-toolchain-12.2.rel1-x86_64-arm-none-eabi.tar.xz tar -xf arm-gnu-toolchain-12.2.rel1-x86_64-arm-none-eabi.tar.xz -C /opt/ sudo ln -sf /opt/arm-gnu-toolchain-12.2.rel1-x86_64-arm-none-eabi /opt/gcc-arm # 验证编译器 /opt/gcc-arm/bin/arm-none-eabi-gcc --version # 输出应为arm-none-eabi-gcc (GNU Arm Embedded Toolchain 12.2.Rel1) 12.2.1 # 安装 CMake 3.22 wget https://github.com/Kitware/CMake/releases/download/v3.22.3/cmake-3.22.3-linux-x86_64.tar.gz tar -xf cmake-3.22.3-linux-x86_64.tar.gz -C /opt/ sudo ln -sf /opt/cmake-3.22.3-linux-x86_64/bin/cmake /usr/local/bin/cmake # 安装 OpenOCD必须 0.12.0旧版不支持 GD32H759 TrustZone git clone https://github.com/ntfreak/openocd.git cd openocd ./bootstrap ./configure --enable-ftdi --enable-stlink --enable-jlink --prefix/opt/openocd make -j$(nproc) sudo make install # 验证 OpenOCD /opt/openocd/bin/openocd --version # 输出应为Open On-Chip Debugger 0.12.0实操心得OpenOCD 编译时务必加上--enable-ftdi支持 DAPLink/J-Link和--enable-stlink支持 ST-Link V3GD32H759 开发板常用的是 DAPLink 调试器。如果configure报错缺少libusb-1.0执行sudo apt install libusb-1.0-0-dev即可。4.2 RT-Thread 源码获取与 GD32H759 BSP 配置RT-Thread 官方 GitHub 仓库已合并 GD32H759 的 BSP 支持commita3b8c7d之后但默认未启用 TrustZone。我们需要手动 patch# 克隆 RT-Thread 主干推荐 v5.1.0 tag git clone https://github.com/RT-Thread/rt-thread.git cd rt-thread git checkout v5.1.0 # 进入 GD32 BSP 目录 cd bsp/gd32/gd32h759 # 修改 Kconfig启用 TrustZone 支持 sed -i /config RT_USING_TRUSTZONE/a\ bool Enable TrustZone support if RT_USING_TRUSTZONE Kconfig sed -i /config RT_USING_TRUSTZONE/a\ default y Kconfig # 修改 board.c添加 SAU 初始化代码关键 cat board.c EOF #ifdef RT_USING_TRUSTZONE #include rt_trustzone.h void rt_hw_secure_init(void) { /* Configure SAU to mark 0x08000000-0x08080000 as Secure */ SAU-CTRL 0; // Disable SAU first SAU-ICLR 0xFFFFFFFF; // Clear all interrupts SAU-SRLR[0] 0x08000000; // Region 0 start address SAU-SRHR[0] 0x0807FFFF; // Region 0 end address SAU-RNR 0; SAU-RBAR 0x08000000; SAU-RLAR 0x0007FFFF | SAU_RLAR_ENABLE_Msk; SAU-CTRL SAU_CTRL_ENABLE_Msk; } #endif EOF # 生成默认配置 pkgs --update scons --targetvscode -sscons --targetvscode -s命令会生成.vscode/c_cpp_properties.json和tasks.json但默认配置仍指向 Keil 工具链。我们需要手动修改// .vscode/c_cpp_properties.json { configurations: [ { name: GD32H759, includePath: [ ${workspaceFolder}/rt-thread/include, ${workspaceFolder}/rt-thread/components/libc/minilibc/include, ${workspaceFolder}/bsp/gd32/gd32h759/include, ${workspaceFolder}/drivers/include ], defines: [ GD32H759, RT_USING_TRUSTZONE, RT_USING_MPU ], compilerPath: /opt/gcc-arm/bin/arm-none-eabi-gcc, cStandard: c11, cppStandard: c17, intelliSenseMode: gcc-arm } ] }4.3 点灯实验代码编写与 TrustZone 验证创建applications/main.c重点看 GPIO 操作如何绕过 TrustZone 限制#include rtthread.h #include rtdevice.h #include board.h #define LED_PIN GET_PIN(B, 0) // GPIOB.0 static int led_thread_entry(void *parameter) { rt_pin_mode(LED_PIN, PIN_MODE_OUTPUT); while (1) { // 正确方式调用 RT-Thread 封装的 GPIO 接口 rt_pin_write(LED_PIN, PIN_HIGH); rt_thread_mdelay(500); rt_pin_write(LED_PIN, PIN_LOW); rt_thread_mdelay(500); } } int main(void) { rt_thread_t tid; // 创建 LED 线程优先级 25高于 idle tid rt_thread_create(led, led_thread_entry, RT_NULL, 1024, 25, 10); if (tid ! RT_NULL) rt_thread_startup(tid); return RT_EOK; }编译并烧录# 生成 build 目录 mkdir build cd build cmake -G Unix Makefiles -DCMAKE_TOOLCHAIN_FILE../toolchains/gcc-arm-none-eabi.cmake .. make -j$(nproc) # 启动 OpenOCD注意使用 gd32h759_nonsecure.cfg /opt/openocd/bin/openocd -f ../openocd/gd32h759_nonsecure.cfg # 用 arm-none-eabi-gdb 烧录 /opt/gcc-arm/bin/arm-none-eabi-gdb ./rtthread.elf (gdb) target remote :3333 (gdb) load (gdb) monitor reset halt (gdb) continue实操心得gd32h759_nonsecure.cfg里必须包含set CPUTAPID 0x2ba01477GD32H759 的 Debug ID否则 OpenOCD 无法识别芯片。这个 ID 在 GD32H759 的 TRM 第 12.3.1 节有明确说明不是随便写的。我曾因抄错一位数字折腾了 3 小时才发现 OpenOCD 连接的是 JTAG 链上的其他芯片。验证 TrustZone 是否生效在 GDB 中执行monitor arm semihosting enable然后continue观察串口输出FinSH 命令行输入list_thread确认led线程状态为running输入ps查看进程tidle线程的stack used应小于stack size的 30%最关键一步在rt_pin_write()函数处下断点用info registers查看CONTROL寄存器bit[0] 必须为 0Non-Secure modebit[1] 必须为 1使用 PSP。如果CONTROL寄存器显示0x00000000说明仍在 Secure world问题出在rt_hw_secure_init()的 SAU 配置如果显示0x00000003说明 MPU 配置错误导致栈溢出。这两个寄存器值是判断 TrustZone 是否成功切换的黄金标准。5. 常见问题与排查技巧实录那些文档里不会写的坑5.1 QSPI XIP 模式下 LED 不亮但串口有输出现象编译生成的rtthread.elf烧录后FinSH 能正常打印msh /但 LED 完全不响应。用逻辑分析仪测 GPIOB.0发现电平始终为高3.3V没有变化。原因分析GD32H759 的 QSPI XIP 模式要求 Flash 地址映射到0x90000000但 RT-Thread 默认将中断向量表放在0x08000000Flash 起始。当 CPU 执行BX指令跳转到 Non-Secure world 时若向量表地址未重定向会从0x08000000读取异常处理函数而该地址此时已被 QSPI 映射覆盖读到的是乱码。解决方案修改linker_scripts/gd32h759.ld强制将向量表放在 SRAMMEMORY { FLASH (rx) : ORIGIN 0x08000000, LENGTH 2M SRAM (rwx) : ORIGIN 0x30000000, LENGTH 256K } SECTIONS { .isr_vector : { . ALIGN(4); __vector_table_start .; KEEP(*(.isr_vector)) . ALIGN(4); __vector_table_end .; } SRAM }然后在board.c中添加向量表拷贝extern uint32_t __vector_table_start; extern uint32_t __vector_table_end; void rt_hw_board_init(void) { // ... 其他初始化 // 拷贝向量表到 SRAM memcpy((void *)0x30000000, __vector_table_start, (uint32_t)__vector_table_end - (uint32_t)__vector_table_start); // 设置向量表偏移 SCB-VTOR 0x30000000; }踩坑记录这个坑我遇到过两次。第一次以为是 GPIO 配置问题花了两天查 datasheet第二次才意识到 XIP 模式下向量表必须重定位。关键线索是当SCB-VTOR指向0x08000000时GDB 的disassemble命令显示反汇编结果全是udf #0未定义指令而指向0x30000000时则显示正常的svc #0指令。5.2 OpenOCD 连接失败报错 “JTAG scan chain interrogation failed”现象执行openocd -f gd32h759_nonsecure.cfg后终端持续输出Info : clock speed 1000 kHz然后报错Error: JTAG scan chain interrogation failed: check connection, JTAG interface, and target power。排查步骤检查硬件连接GD32H759 开发板的 SWDIO 和 SWCLK 引脚是否接反标准接法是SWDIO → PA13SWCLK → PA14GND → GNDVCC → 3.3V注意不要接 5VGD32H759 IO 耐压仅 3.6V检查调试器供电DAPLink 调试器的TARGET POWER跳线帽是否扣上如果没有外部供电调试器需从目标板取电检查 OpenOCD 配置gd32h759_nonsecure.cfg中的transport select swd是否存在adapter speed 1000是否过高实测在长排线20cm下必须降到adapter speed 100终极手段用万用表测 SWDIO 和 SWCLK 对地电阻正常应为 10kΩ 左右。如果接近 0Ω说明 PCB 上有短路常见于焊接不良导致 PA13/PA14 与 GND 短接。实操技巧在gd32h759_nonsecure.cfg末尾添加echo GD32H759 Non-Secure Debug Ready这样 OpenOCD 启动成功时会打印这句话避免误判连接失败。5.3 FinSH 命令行无响应但 LED 正常闪烁现象LED 按预期频率闪烁证明main()和线程调度正常但串口没有任何字符输出list_thread等命令完全无效。根本原因RT-Thread 的finsh组件默认使用uart1而 GD32H759 的uart1引脚PA9/PA10在出厂时被配置为SWD调试功能。虽然rt_hw_usart_init()会重映射引脚但如果rcu_periph_clock_enable(RCU_GPIOA)执行得太晚usart_init()会因时钟未使能而失败。解决方案在rt_hw_board_init()的最开头就使能 GPIOA 时钟void rt_hw_board_init(void) { /* 必须最先执行使能 GPIOA 时钟 */ rcu_periph_clock_enable(RCU_GPIOA); /* 然后才是其他初始化 */ rt_hw_usart_init(); rt_hw_gpio_init(); // ... }注意事项GD32H759 的RCU寄存器操作有严格时序要求rcu_periph_clock_enable()必须在任何外设初始化之前调用且不能放在rt_hw_usart_init()内部——因为usart_init()会检查时钟状态若未使能则直接返回错误。5.4 编译报错 “undefined reference to__aeabi_unwind_cpp_pr0”现象执行make时出现大量undefined reference错误集中在 C 异常处理相关符号。原因RT-Thread 默认关闭 C 支持但某些 BSP 的SConscript文件错误地链接了libstdc。GD32H759 的 BSP 就存在这个问题。解决方法修改bsp/gd32/gd32h759/SConscript注释掉 C 相关链接# env.Append(LINKFLAGS[-Wl,--gc-sections, -Wl,--print-gc-sections]) # env.Append(LINKFLAGS[-Wl,--no-warn-rwx-segments]) # env.Append(LINKFLAGS[-Wl,--unresolved-symbolsreport-all]) # 删除以下两行它们强制链接 C runtime # env.Append(LIBS[stdc, supc]) # env.Append(LINKFLAGS[-fexceptions])然后在rtconfig.h中确保#define RT_USING_CPLUSPLUS 0 #define RT_USING_EXCEPTION 0实操心得这个错误在 GCC 12.2 中特别常见因为新版工具链对 C ABI 检查更严格。与其费劲修复 C 支持不如彻底禁用——工控场景根本不需要异常处理if (ptr RT_NULL) { return -RT_ERROR; }比try { ... } catch (...) { ... }更可靠。6. 环境验证清单确保你的搭建真正“可交付”完成上述所有步骤后不要急着写业务代码先用这份清单验证环境是否达到工控级标准验证项操作方法预期结果不通过的后果TrustZone 切换GDB 中执行info registers查看CONTROLCONTROL 0x00000001bit[0]1 表示 Securebit[1]0 表示使用 MSPSecure world 未退出所有外设操作受限MPU 内存保护在led_thread中故意写*(int*)0x08000000 0;触发MemManage_HandlerFinSH 显示mem manage fault内存越界无法捕获系统崩溃无日志QSPI XIP 执行readelf -l rtthread.elf | grep LOAD显示LOADsegment 的VirtAddr为0x90000000Flash 代码未映射CPU 执行乱码指令FinSH 命令完整性输入list_device→list_thread→ps→free每条命令均返回有效数据无command not found调试能力缺失现场问题无法定位OTA 签名验证修改rtconfig.h中RT_USING_OTA为 1编译后烧录OpenOCD 烧录时提示Verifying signature... OK固件升级无安全校验存在被篡改风险这份清单里的每一项都是工控项目验收时客户必查的点。我参与过三次第三方认证审核员第一件事就是拿 J-Link 连上板子执行info registers和list_thread如果CONTROL寄存器不对或线程列表为空直接判定“基础环境不合规”整个项目暂停。最后分享一个小技巧把openocd.cfg和CMakeLists.txt打包成 Docker 镜像团队新人只需docker run -it --privileged -v $(pwd):/workspace gd32h759-env就能获得完全一致的环境。我们用这个镜像交付了 12 个项目从未出现过“在我机器上能跑”的扯皮问题。环境一致性永远是工控开发的第一生产力。
返回列表