1 前言
说来惭愧,因为我毕业比较早,我当年学的都是51单片机。然后再接触嵌入式,都是博通,高通,海思这些重型平台,都是直接上的Linux。
后面自学也是主要国际接轨,主要是Arduino和树莓派。看起来很多概念是很新很潮,但其实现在回头看看身边实际的工业项目,看起来还是STM32占比挺高的。另外看了下目前国内高校电子专业,用的主流也是STM32。
做一个对比如下:
| 指标 \ MCU | STM32 系列 | RP2350 (Raspberry Pi Pico 2) | ESP32 系列 | Nordic nRF52 / nRF53 | TI MSP430 / C2000 |
|---|---|---|---|---|---|
| 核心架构 | ARM Cortex-M0/M3/M4/M7/M33/M55 | Dual Cortex-M4F | Xtensa / RISC-V | ARM Cortex-M4/M33 | MSP430 / C28x |
| 主频 | 32--480 MHz | 240 MHz | 160--240 MHz | 64--128 MHz/ 128-- Application | 8--100 MHz |
| 浮点支持 | M4/M7/M33(FPU) | 有单精度FPU | 有(部分) | 有(部分) | 无/有限 |
| RAM | 小到 16 KB ~ 大到 1 MB+ | ~ 264 KB | 320 KB | 64--512 KB | 2--128 KB |
| Flash/存储 | 16 KB ~ 多 MB | ~ 16--32 MB 外挂 | 4--16 MB 外挂 | ~ 256 KB--1 MB | 16 KB--1 MB |
| ADC 精度 & 采样 | 12-16 bit 多通道 DMA | 12-bit | 12-bit | 12-bit | 10--16 bit(取决型号) |
| DMA 支持 | 完整 | 有 | 有 | 有 | 有 |
| 通信外设数量 | 丰富(USART/SPI/I2C/CAN/ FDCAN/USB/SPI) | 丰富(UART/SPI/I2C/PIO) | Wi-Fi/BT 内置 + UART/SPI/I2C | BLE 内置 + UART/SPI/I2C | UART/SPI/I2C/CAN(部分) |
| 无线 | 需外挂模块 | 需外挂 | Wi-Fi + BT 现成 | BLE 现成 | 需外挂 |
| 超低功耗能力 | 优秀(L 系列特别出色) | 中等 | 一般 | 优秀(低功耗 BLE 典型) | 极优(MSP430) / 中等 |
| 实时性 | 很强 | 很强 | 好(RTOS) | 很好 | 很强(C2000 强实时控制) |
| 工程生态 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐ |
| 供应 & 生命周期 | 长期稳定 | 好但更新快 | 好 | 好 | 好 |
| 成熟工业案例 | 极多 | 少 | 多(消费/IoT) | 多(穿戴/BLE) | 老牌工业/控制 |
| 工具链 | CubeMX + HAL/LL + Keil/GCC | CMake + GCC | ESP IDF | nRF SDK / Zephyr | CCS / GCC |
| 学习曲线 | 中等 | 低 | 中等 | 中等 | 低(MSP430) / 中等(C2000) |
| 典型价格 | 广泛 | 很低 | 低 | 中低 | 低 |
STM32还是更成熟,更稳定。量产项目估计最后还是要它。。。
好吧,还是再学学。
2 环境搭建
手上有两个STM32,一个是新的STM32F411,另外一个是老的STM32F103。还有一个ST LINK V2。都是吃灰了好久的,这个周末翻出来玩玩。

查了一下STM32的环境流程,相对树莓派和arduino,不得不说,真的是很麻烦。。。
前前后后下载了几个G的软件,有CubeMX,有CubeProgrammer,有CubeIDE。然后在VSCode中还装了STM32插件。

根据我的习惯,然后网上的资料,我整理的我的开发流程。
1 创建工程。使用CubeMX,主要是指定芯片,设置IO,配置工程信息。
2 编辑代码。我还是习惯VSCode。
3 编译。使用VSCode STM插件。
4 烧写,使用CubeProgrammer。
现在挨个说一下遇到的坑:
1 STLink不工作。
死活报错。后面才查到配置界面里面的Shared一定要打开,然后才能识别祖国版的STLink。
折腾了两三个小时。

2 STM32F411死活连不上
AI给的意见是点击Connect的时候,同时点击板子上的RST,然后迅速松开。。。这个操作真的很迷很考验手速啊。。。试了无数次还是不行。。。
最后无奈换成更古早的STM32F103,好了,一下就连上了。我在想可能是手焊的SWD接口没对吧。。。

折腾了两三个小时。
3 配置工程要选Makefile
最开始我忘了在哪里看的,说是可以用CMake构建工程。结果在VSCode的STM32插件中,各种报错,解决来解决去也编不出来。最后回来又重新试了下Makefile,很顺利一下就过了。。。
折腾了一两个小时。

4 配置VSCode插件
进了这个插件首先要配置那几件套,就是arm-gcc,make,ninja等。它是自带了一套,但是也麻烦了,因为都在外网大概率要被墙,能不能下载成功只有看天。反正我是试了很久都不行。最后无奈换成Pico的那一套环境,最后终于是配出来了。而且可以看到功能也很有限。

折腾两个小时。
5 三只蓝鲸的LED口。
最开始看资料,都说点灯使用的PA13,但是死活就是不亮。最后没办法又跑回去找卖家要了资料,看到灯原来是PA6和PA7。

折腾两个小时。
说真的,这个环境折腾得我都无语了。真不知道那么多学生是怎么配置这个环境的。。。
3 结果
好在赶在睡觉之前还是亮了。。。

代码很简单,如下:
cpp
/* USER CODE BEGIN Header */
/**
******************************************************************************
* @file : main.c
* @brief : Main program body
******************************************************************************
* @attention
*
* Copyright (c) 2026 STMicroelectronics.
* All rights reserved.
*
* This software is licensed under terms that can be found in the LICENSE file
* in the root directory of this software component.
* If no LICENSE file comes with this software, it is provided AS-IS.
*
******************************************************************************
*/
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
/* USER CODE END Includes */
/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
/* USER CODE END PTD */
/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
/* USER CODE END PD */
/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */
/* USER CODE END PM */
/* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN PV */
/* USER CODE END PV */
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
/* USER CODE BEGIN PFP */
/* USER CODE END PFP */
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
/* USER CODE END 0 */
/**
* @brief The application entry point.
* @retval int
*/
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
/* USER CODE BEGIN 2 */
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_6);
HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_7);
HAL_Delay(500);
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
/**
* @brief System Clock Configuration
* @retval None
*/
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
/** Initializes the RCC Oscillators according to the specified parameters
* in the RCC_OscInitTypeDef structure.
*/
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI;
RCC_OscInitStruct.HSIState = RCC_HSI_ON;
RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_NONE;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
Error_Handler();
}
/** Initializes the CPU, AHB and APB buses clocks
*/
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_HSI;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_0) != HAL_OK)
{
Error_Handler();
}
}
/**
* @brief GPIO Initialization Function
* @param None
* @retval None
*/
static void MX_GPIO_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
/* USER CODE BEGIN MX_GPIO_Init_1 */
/* USER CODE END MX_GPIO_Init_1 */
/* GPIO Ports Clock Enable */
__HAL_RCC_GPIOC_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
/*Configure GPIO pin Output Level */
HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, GPIO_PIN_RESET);
/*Configure GPIO pin Output Level */
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_6|GPIO_PIN_7, GPIO_PIN_RESET);
/*Configure GPIO pin : LED_Pin */
GPIO_InitStruct.Pin = LED_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(LED_GPIO_Port, &GPIO_InitStruct);
/*Configure GPIO pins : PA6 PA7 */
GPIO_InitStruct.Pin = GPIO_PIN_6|GPIO_PIN_7;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
/* USER CODE BEGIN MX_GPIO_Init_2 */
/* USER CODE END MX_GPIO_Init_2 */
}
/* USER CODE BEGIN 4 */
/* USER CODE END 4 */
/**
* @brief This function is executed in case of error occurrence.
* @retval None
*/
void Error_Handler(void)
{
/* USER CODE BEGIN Error_Handler_Debug */
/* User can add his own implementation to report the HAL error return state */
__disable_irq();
while (1)
{
}
/* USER CODE END Error_Handler_Debug */
}
#ifdef USE_FULL_ASSERT
/**
* @brief Reports the name of the source file and the source line number
* where the assert_param error has occurred.
* @param file: pointer to the source file name
* @param line: assert_param error line source number
* @retval None
*/
void assert_failed(uint8_t *file, uint32_t line)
{
/* USER CODE BEGIN 6 */
/* User can add his own implementation to report the file name and line number,
ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
/* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */
主要还是环境太折腾了,比起另外几家复杂不少。也许后面习惯了会好点吧。
STLink调试没有配置出来。。。空了再试试。