STM32CubeMX 与立创天空星开发板的深度融合:从零构建高效嵌入式工程
你有没有过这样的经历?——手头有一个全新的开发板,芯片型号陌生、外设繁多,光是点亮一个LED就要翻半天数据手册;配置串口时钟树算来算去还是出错;好不容易写完初始化代码,编译却报一堆头文件找不到……🤯
在今天这个快节奏的嵌入式开发时代,我们早已告别“寄存器裸写”的蛮荒年代。取而代之的,是一套高度自动化、标准化的现代开发范式。而其中最耀眼的一环,就是 STM32CubeMX + 立创天空星开发板 的黄金组合。
这不仅仅是一个工具链的选择,更是一种思维方式的跃迁 —— 它让我们从繁琐的底层细节中解放出来,真正把精力聚焦在“功能实现”和“系统设计”上。🚀
开发环境搭建:一切从这里开始
别急着点“Generate Code”,先稳住!🛠️
再强大的工具,也得建立在稳定可靠的开发环境之上。STM32CubeMX 虽然免费开源,但它背后依赖的是 Java 运行时、固件包管理器以及目标 IDE 的协同支持。一步没踩准,后续全是坑。
✅ 必须安装的前置条件
首先明确一点: STM32CubeMX 是基于 Java 的桌面应用 。这意味着你的电脑必须有兼容版本的 JRE(Java Runtime Environment)才能运行它。
📌 小贴士:推荐使用 OpenJDK 11,既轻量又稳定。避免使用 Java 17 或更高版本,因为部分旧版 CubeMX 对新 JVM 支持不佳。
安装步骤很简单:
- 前往 Adoptium 下载并安装
OpenJDK 11; - 解压后设置系统环境变量
JAVA_HOME指向 JDK 目录; - 打开命令行输入
java -version验证是否成功。
$ java -version
openjdk version "11.0.18" 2023-01-17
OpenJDK Runtime Environment Temurin-11.0.18+10 (build 11.0.18+10)
看到类似输出?恭喜,Java 环境就绪!
接下来才是重头戏 —— 安装 STM32CubeMX。
前往 ST官网下载页面 ,选择对应操作系统的安装包(Windows 推荐 .exe 安装程序)。安装过程中注意以下几点:
| 注意项 | 建议做法 |
|---|---|
| 安装路径 | 使用纯英文路径,如 C:\ST\STM32CubeMX ,避免中文或空格导致解析错误 |
| 管理员权限 | 首次运行建议右键“以管理员身份运行”,确保注册表和驱动正常写入 |
| 固件包更新 | 启动后立即进入 Help → Check for Updates ,拉取最新 MCU 支持包 |
💡 强烈建议开启在线固件包管理(Online Firmware Package Management) 。这样当你选择某个新型号时,CubeMX 会自动下载对应的 HAL 库、CMSIS 文件等资源,真正做到“按需加载”,省下几百MB无用组件。
比如,当我们搜索 “STM32G0B1RE” 时,CubeMX 内部其实是通过 XML 描述文件去远程服务器请求资源:
<firmwarePackage name="STM32G0xx_HAL_Driver" version="1.5.0">
<description>HAL driver for STM32G0 series</description>
<size>12.4 MB</size>
<downloadUrl>https://www.st.com/content/.../stm32g0xx_hal.zip</downloadUrl>
</firmwarePackage>
这套机制不仅提升了用户体验,还保证了所有开发者使用的库版本一致,极大减少了“我这边能跑,你那边报错”的团队协作难题 😅
立创天空星开发板:国产硬核玩家的新宠
说到立创天空星,熟悉嘉立创生态的朋友应该不陌生。它是 LCSC(LCSC = L E D C H I P S & S C H E M A T I C)推出的一款高性价比 STM32 开发平台,主打“快速原型 + 社区共享”。
目前主流批次搭载的是 STM32G0B1RET6 芯片,LQFP64 封装,主频高达 64MHz,集成丰富模拟外设(ADC/DAC)、低功耗特性优秀,非常适合物联网节点、智能传感器等应用场景。
但也别被名字迷惑了 —— 天空星其实有多个变种型号:
| 型号 | 核心芯片 | 主频 | 特点 |
|---|---|---|---|
| 天空星-G0 | STM32G0B1RET6 | 64MHz | 性价比高,适合入门学习 |
| 天空星-F4 | STM32F407VGT6 | 168MHz | 高性能,可用于图像处理 |
| 天空星-H7 | STM32H743ZIT6 | 480MHz | 极致性能,带 FPU 和外部 SDRAM |
所以第一步,一定要确认你手里那块板子到底是什么芯!🔍
最可靠的方法是查看板底丝印或者访问 立创商城产品页 查看规格书。
一旦确定型号,在 CubeMX 中的操作就非常直观了:
- 打开软件 → 点击 “New Project”
- 在搜索框输入 “STM32G0B1RE”
- 选择封装类型为 LQFP64
- 双击进入配置界面 ✅
此时,CubeMX 会自动加载该芯片的 设备数据库(Device Database) ,包括:
- 所有 GPIO 引脚的功能映射表
- 外设基地址与中断向量定义
- Flash/RAM 容量信息
- 默认时钟源配置(HSI/HSE/LSI)
这些数据都来源于 ST 提供的标准 SVD 文件(System View Description) ,一种 ARM Cortex-M 系列通用的硬件描述格式。有了它,CubeMX 才能做到“所见即所得”的图形化配置。
而且!CubeMX 还会自动生成一个关键头文件: stm32g0xx_hal_conf.h ,里面藏着很多影响系统行为的重要宏定义:
#define HSE_VALUE 8000000U /*!< 外部晶振频率 */
#define LSI_VALUE 32000U /*!< 内部低速振荡器 */
#define VDD_VALUE 3300U /*!< 供电电压 mV */
#define TICK_INT_PRIORITY ((uint32_t)0x0F) /*!< SysTick 中断优先级 */
⚠️ 划重点:如果你实际用的是 12MHz 晶振但没改 HSE_VALUE ,PLL 计算就会出错,最终 CPU 主频偏离预期!这种问题调试起来极其头疼,往往卡好几天才发现是这里错了……
工具链配置:通往真实世界的桥梁
选好了芯片,下一步就是告诉 CubeMX:“我要用哪个 IDE 来写代码?”毕竟生成的只是框架,真正的编程还得靠 Keil、IAR、VS Code 或者 STM32CubeIDE。
常见的导出选项包括:
| IDE 类型 | 工具链名称 | 典型路径 | 输出格式 |
|---|---|---|---|
| Keil MDK | MDK ARM | C:\Keil_v5 | .uvprojx |
| STM32CubeIDE | STM32CubeIDE | C:\STM32CubeIDE | Eclipse 项目结构 |
| GCC for ARM | GNU Tools Arm Embedded | C:\Program Files (x86)\GNU Arm Embedded Toolchain | Makefile |
| IAR | IAR ARM | C:\Program Files\IAR Systems\Embedded Workbench | .eww |
配置方法也很简单:进入 Preferences → Toolchains Installations ,手动指定各工具链的安装根目录即可。
如果忘记设置,导出时会出现经典的红字警告:“Toolchain not found”。这时候别慌,弹窗里通常会提示你浏览路径,补上就行。
不过更聪明的做法是提前配好,尤其是当你打算用 GCC + VS Code + PlatformIO 这套轻量级组合时。这类工作流特别适合喜欢折腾、追求极致效率的开发者。
举个例子,当选择 GCC 工具链导出后,CubeMX 会自动生成一份 Makefile ,内容如下:
TARGET = skyboard_demo
CC = arm-none-eabi-gcc
LD = arm-none-eabi-gcc
OBJCOPY = arm-none-eabi-objcopy
CFLAGS += -mcpu=cortex-m0plus -mthumb -Og -Wall
CFLAGS += -DUSE_HAL_DRIVER -DSTM32G0B1xx
INCLUDES += -I./Core/Inc -I./Drivers/STM32G0xx_HAL_Driver/Inc
这段 Makefile 看似普通,实则暗藏玄机:
-
-mcpu=cortex-m0plus:精准指定内核架构,STM32G0 正是基于 M0+ 内核; -
-mthumb:强制使用 Thumb 指令集,显著减小程序体积; -
-Og:调试优化等级,兼顾可读性与性能; -
-DUSE_HAL_DRIVER:启用 HAL 库条件编译开关; -
-DSTM32G0B1xx:触发特定芯片头文件包含逻辑; -
INCLUDES:声明头文件搜索路径,防止编译时报 “No such file or directory”
👉 这种全自动构建脚本生成能力,让跨平台协作变得前所未有的轻松。哪怕你是用 Windows 写代码,同事拿 Mac 编译也能一键搞定。
图形化引脚分配:让连接变得可视化
如果说传统的开发方式像在黑暗中接线,那么 STM32CubeMX 的 Pinout 视图就像是打开了灯💡。
点击顶部标签页 “Pinout & Configuration”,你会看到一颗清晰的 MCU 引脚布局图。每个引脚都有颜色标识:
- 🟩 绿色:已分配功能
- ⬜ 灰色:默认浮空输入
- 🟠 橙色:存在冲突或警告
- 🔴 红色:严重错误(如电源引脚误用)
以立创天空星为例,常见外设连接如下:
| 引脚 | 功能 | 复用编号 | 设备 |
|---|---|---|---|
| PA2 | USART2_TX | AF7 | CH340G(USB转串) |
| PA3 | USART2_RX | AF7 | CH340G |
| PA8 | TIM1_CH1 | AF2 | RGB LED(红) |
| PB14 | TIM15_CH1 | AF1 | RGB LED(绿) |
| PB15 | TIM15_CH2 | AF1 | RGB LED(蓝) |
要启用串口调试?只需在图形界面上点击 PA2 和 PA3,弹出菜单中选择 “USART2_TX” 和 “USART2_RX” 即可。系统会自动激活 AF7 复用模式,并提示工作模式为异步通信。
背后的代码长什么样呢?
void MX_GPIO_Init(void)
{
GPIO_InitTypeDef gpio_init_structure;
__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_RCC_GPIOB_CLK_ENABLE();
// 配置 PA2/PA3 为 USART2 复用功能
gpio_init_structure.Pin = GPIO_PIN_2 | GPIO_PIN_3;
gpio_init_structure.Mode = GPIO_MODE_AF_PP; // 复用推挽
gpio_init_structure.Pull = GPIO_NOPULL;
gpio_init_structure.Speed = GPIO_SPEED_FREQ_LOW;
gpio_init_structure.Alternate = GPIO_AF7_USART2;
HAL_GPIO_Init(GPIOA, &gpio_init_structure);
// 配置 RGB LED 控制引脚
gpio_init_structure.Pin = GPIO_PIN_8;
gpio_init_structure.Mode = GPIO_MODE_OUTPUT_PP;
gpio_init_structure.Pull = GPIO_NOPULL;
HAL_GPIO_Init(GPIOA, &gpio_init_structure);
gpio_init_structure.Pin = GPIO_PIN_14 | GPIO_PIN_15;
HAL_GPIO_Init(GPIOB, &gpio_init_structure);
}
看到没?所有 BSRR、MODER、OTYPER 寄存器操作都被封装成了 HAL_GPIO_Init() 函数调用。你不再需要记住每一位代表什么,只需要关注“我要做什么”。
这才是现代嵌入式开发该有的样子啊!👏
时钟树配置:掌控系统的“心跳”
如果说 GPIO 是手脚,那 RCC(Reset and Clock Control)模块就是心脏 ❤️。没有正确的时钟信号,整个系统都无法运转。
STM32 的时钟系统相当灵活,但也因此复杂。它允许你组合多种时钟源(HSI/HSE/PLL),并通过分频器分配给不同总线(AHB/APB1/APB2)。
在 CubeMX 的 “Clock Configuration” 页面中,你可以直接拖动滑块调整频率,实时预览结果:
[HSE 8MHz] → PLL → SYSCLK(64MHz) → AHB(64MHz) → APB1(64MHz), APB2(64MHz)
具体参数设置如下:
- 输入 HSE = 8MHz
- PLL M division = 1
- PLL N multiplication = 8
- PLL R division = 1
- 得到 SYSCLK = 8 × 8 / 1 = 64MHz
生成的代码片段如下:
RCC_OscInitTypeDef osc_init_structure = {0};
osc_init_structure.OscillatorType = RCC_OSCILLATORTYPE_HSE;
osc_init_structure.HSEState = RCC_HSE_ON;
osc_init_structure.PLL.PLLState = RCC_PLL_ON;
osc_init_structure.PLL.PLLSource = RCC_PLLSOURCE_HSE;
osc_init_structure.PLL.PLLM = 1;
osc_init_structure.PLL.PLLN = 8;
osc_init_structure.PLL.PLLR = 1;
if (HAL_RCC_OscConfig(&osc_init_structure) != HAL_OK)
{
Error_Handler();
}
这里的每一条配置都在直接影响系统稳定性:
-
HSEState = RCC_HSE_ON:启用外部高速晶振; -
PLL.PLLState = RCC_PLL_ON:开启锁相环进行倍频; -
PLLN = 8:决定倍频系数; - 若返回错误,则跳转至
Error_Handler(),便于定位启动失败原因。
值得一提的是,APB 上挂载的定时器(如 TIM1/TIM15)其时钟频率为 PCLK×2,因此即使 SYSCLK 是 64MHz,TIM 仍可达 128MHz 的计数频率,特别适合高精度 PWM 输出!
当然,并不是所有场景都需要满频运行。对于电池供电设备,我们可以动态降频至 4MHz 实现超低功耗待机,仅在事件触发时短暂升频处理任务。
void switch_to_high_performance(void)
{
RCC_ClkInitTypeDef clk_init_structure = {0};
uint32_t fLatency = 0;
clk_init_structure.ClockType = RCC_CLOCKTYPE_SYSCLK;
clk_init_structure.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
clk_init_structure.AHBCLKDivider = RCC_SYSCLK_DIV1;
clk_init_structure.APB1CLKDivider = RCC_HCLK_DIV1;
if (HAL_RCC_ClockConfig(&clk_init_structure, FLASH_LATENCY_2) != HAL_OK)
{
Error_Handler();
}
}
📌 关键点提醒:
- 必须先配置好 PLL 并等待锁定;
- 当主频 > 48MHz 时,Flash 需插入等待周期(FLASH_LATENCY_2);
- HAL_RCC_ClockConfig() 会同步更新所有外设时钟;
这类动态调频技术广泛应用于智能手表、无线传感器节点等领域,是节能设计的核心手段之一。
外设模块化配置:积木式开发体验
现代嵌入式系统不再是单一功能堆叠,而是多个外设协同工作的综合体。CubeMX 的设计理念正是“模块化配置”—— 每个外设作为一个独立单元启用与参数设定。
🛠️ USART 串口调试
调试是开发的生命线。通过串口连接 PC 端助手(如 XCOM、Tera Term),可以实时输出日志信息。
启用步骤:
- 在 Pinout 中设置 PA2/PA3 为 USART2_TX/RX;
- 在 Connectivity 栏找到 USART2,点击启用;
- 设置参数:
- Mode: Asynchronous
- Baud Rate: 115200
- Word Length: 8 Bits
- Stop Bits: 1
- Parity: None
生成代码:
huart2.Instance = USART2;
huart2.Init.BaudRate = 115200;
huart2.Init.WordLength = UART_WORDLENGTH_8B;
huart2.Init.StopBits = UART_STOPBITS_1;
huart2.Init.Parity = UART_PARITY_NONE;
huart2.Init.Mode = UART_MODE_TX_RX;
huart2.Init.HwFlowCtl = UART_HWCONTROL_NONE;
if (HAL_UART_Init(&huart2) != HAL_OK)
{
Error_Handler();
}
初始化完成后,就可以用 HAL_UART_Transmit() 发送字符串啦!
HAL_UART_Transmit(&huart2, (uint8_t*)"Hello World!\r\n", 14, HAL_MAX_DELAY);
💡 GPIO 控制 LED 与按键
最基础的人机交互莫过于 LED 和按键。
LED 输出配置
// 控制 RGB LED
void set_rgb_led(uint8_t red, uint8_t green, uint8_t blue)
{
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_8, red ? GPIO_PIN_RESET : GPIO_PIN_SET); // 共阳极,低电平亮
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_14, green ? GPIO_PIN_RESET : GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_15, blue ? GPIO_PIN_RESET : GPIO_PIN_SET);
}
按键输入检测
uint8_t read_user_button(void)
{
return HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_RESET; // 按下为低
}
💡 建议输入引脚启用内部上拉电阻( GPIO_PULLUP ),防止悬空误触发。
⏱️ 定时器 TIM 实现精确控制
定时器是实现延时、PWM、任务调度的核心。
以 TIM1 为例,配置 1ms 中断:
- 启用 TIM1;
- 设置 Counter Mode: Up;
- Prescaler = 6399 → 得到 10kHz 计数频率;
- Period = 99 → 溢出周期 100 次 = 1ms;
- NVIC Settings: Enable Interrupt。
中断回调函数:
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if (htim->Instance == TIM1)
{
HAL_IncTick(); // 更新 HAL 内部 tick
user_timer_handler(); // 用户任务
}
}
现在 HAL_Delay(10) 就能实现 10ms 非阻塞延时了!
工程生成与导入:打通最后一公里
点击 “Project Manager” → 设置项目名、路径、IDE 类型 → Generate Code!
生成后的项目结构清晰明了:
skyboard_project/
├── Core/
│ ├── Inc/ # 头文件
│ │ ├── main.h
│ │ ├── gpio.h
│ │ └── usart.h
│ └── Src/ # 源文件
│ ├── main.c
│ ├── gpio.c
│ └── usart.c
├── Drivers/
│ ├── CMSIS/
│ └── STM32G0xx_HAL_Driver/
└── MDK-ARM/ # Keil 工程文件
├── skyboard_demo.uvprojx
└── Objects/
导入 Keil 后必做检查清单 ✅
| 检查项 | 正确做法 |
|---|---|
| Include Paths | 添加 Core/Inc , Drivers/CMSIS/Include , Drivers/STM32G0xx_HAL_Driver/Inc |
| Startup File | 确保 startup_stm32g0b1xx.s 被加入 Source Group |
| Flash Algorithm | 选择 STM32G0B1CB 对应算法 |
| Optimization Level | 调试阶段建议 -O0 ,发布时改为 -Os |
常见错误排查:
🔧 头文件找不到?
→ 检查 Include Paths 是否完整,特别是 HAL 库路径。
🔧 下载失败“No target connected”?
→ 检查 SWD 接线:SWCLK→PA14,SWDIO→PA13,GND→GND
→ 确认未将 PA13/PA14 配置为普通 GPIO 导致 JTAG 被禁用
🔧 断点无法命中?
→ 关闭优化选项 -O0 ,否则局部变量可能被优化掉
功能验证实验:让代码真正跑起来
理论配置必须经得起实践检验。下面三个实验堪称嵌入式开发的“三大件”:
✅ 实验一:LED 闪烁
while (1)
{
HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin);
HAL_Delay(500);
}
观察板载 LED 是否按 0.5s 周期闪烁。若无反应,请检查:
- 是否在 CubeMX 中正确分配引脚?
- 是否焊接反向?
- 是否定义了
LED_GPIO_Port和LED_Pin?
✅ 实验二:串口打印调试信息
if (HAL_UART_Transmit(&huart2, (uint8_t*)"Hello SkyStar!\r\n", 15, HAL_MAX_DELAY) == HAL_OK)
{
HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin);
}
打开 XCOM 或 SecureCRT,波特率设为 115200,观察是否有输出。
✅ 实验三:定时器中断控制 LED
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if (htim->Instance == TIM1)
{
HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin);
}
}
配合 1ms 定时中断,实现非阻塞式 LED 闪烁,为未来接入 RTOS 打下基础。
安全扩展策略:如何不被重新生成覆盖?
这是很多人踩过的坑:辛辛苦苦写了几十行代码,结果一重新生成项目,全没了!!😱
原因很简单: CubeMX 默认会覆盖所有生成文件中的非用户区域代码。
解决办法只有一个: 使用 USER CODE 区域标记!
/* USER CODE BEGIN 0 */
// 此处添加全局变量、宏定义等,不会被覆盖
static uint32_t counter = 0;
/* USER CODE END 0 */
int main(void)
{
/* USER CODE BEGIN 1 */
// 初始化前插入代码
/* USER CODE END 1 */
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
/* USER CODE BEGIN WHILE */
while (1)
{
// 主循环任务放在这里
counter++;
HAL_Delay(10);
}
/* USER CODE END WHILE */
}
常见标签用途:
| 标签 | 用途 |
|---|---|
USER CODE BEGIN 0 | 文件开头,加全局变量 |
USER CODE BEGIN 4 | 中断回调中加逻辑 |
USER CODE BEGIN WHILE | 主循环中加任务 |
只要遵守这个规则,哪怕你删掉整个工程重来,只要保留这些区块,核心逻辑就不会丢失。
高级进阶:引入 FreeRTOS 实现多任务
当项目复杂度上升,裸机轮询架构已显吃力。此时,引入 FreeRTOS 成为必然选择。
幸运的是,STM32CubeMX 原生支持 FreeRTOS 集成!
启用步骤:
- Middleware → FREERTOS → Enabled
- 选择调度策略(推荐抢占式 Preemptive)
- 生成代码
CubeMX 会自动生成 freertos.c 文件,用于创建任务、队列、信号量等。
示例任务:
void StartTaskLed(void *argument)
{
for(;;)
{
HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin);
osDelay(500);
}
}
void StartTaskUart(void *argument)
{
uint8_t msg[] = "Heartbeat from FreeRTOS!\r\n";
for(;;)
{
HAL_UART_Transmit(&huart2, msg, sizeof(msg)-1, 100);
osDelay(2000);
}
}
并通过 osThreadNew() 注册:
osThreadNew(StartTaskLed, NULL, &led_task_attr);
osThreadNew(StartTaskUart, NULL, &uart_task_attr);
还可以使用消息队列传递传感器数据:
osMessageQueueId_t sensorQueue;
// 发送
osMessageQueuePut(sensorQueue, &data, 0, 0);
// 接收
osMessageQueueGet(sensorQueue, &recv_data, NULL, osWaitForever);
配合信号量保护共享资源(如 OLED 屏幕),彻底告别竞态条件。
中断机制强化:实现毫秒级响应
除了定时器,外部中断也是提升实时性的利器。
以用户按键为例:
- 将 PA0 设置为
GPIO_EXTI0 - NVIC 中启用 EXTI line1~0 interrupts
- 设置优先级(建议抢占优先级 2)
CubeMX 自动生成回调函数:
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
if (GPIO_Pin == USER_BTN_Pin)
{
HAL_Delay(50); // 简单消抖(实际建议用定时器)
if (HAL_GPIO_ReadPin(USER_BTN_GPIO_Port, USER_BTN_Pin) == GPIO_PIN_RESET)
{
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
vTaskNotifyGiveFromISR(xLedTaskHandle, &xHigherPriorityTaskWoken);
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
}
}
这样就能在中断中唤醒任务,实现毫秒级响应。
结语:从自动化到自主化
STM32CubeMX 不只是一个代码生成器,它代表着一种全新的嵌入式开发哲学: 先规划,再编码;重抽象,轻细节;强标准,弱耦合。
而立创天空星这样的国产开发板,则为我们提供了低成本、高可用的实践平台。两者结合,正是当代工程师快速迭代、高效创新的最佳拍档。
未来的路还很长 —— 你可以继续深入 HAL 底层机制,也可以尝试 LL 库追求极致性能;可以从 FreeRTOS 进阶到 Zephyr,甚至移植 Linux;可以接入 LoRa、WiFi、BLE 构建物联网系统……
但请记住: 每一个伟大的项目,都是从一次成功的 HAL_GPIO_TogglePin() 开始的。
现在,插上你的天空星,按下复位键,让世界听见你的第一声“Hello World”吧!🌍✨
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
1533

被折叠的 条评论
为什么被折叠?



