第二章:开发环境搭建——让硬件听你指挥
说实话,做嵌入式开发这么多年,我见过太多人一上来就急着写代码,结果连LED都点不亮。嗯,这其实很正常。环境搭建这事儿,看着简单,但坑不少。今天我就带你一步步把STM32的开发环境搭起来,让硬件真正听你指挥。
2.1 硬件平台选择:为什么是STM32?
你可能要问:市面上那么多ARM芯片,为什么偏偏选STM32?
我个人习惯用STM32F103系列,原因有三:
- 文档齐全:ST官方的手册、参考手册、勘误表,写得清清楚楚。我在项目中遇到过一些冷门芯片,手册写得跟天书似的,查个寄存器要翻半天。STM32就不会有这问题。
- 生态成熟:从HAL库到LL库,从Keil到GCC,工具链多得是。你甚至能找到十年前的老帖子,里面还有人在讨论F103的坑。
- 价格亲民:一块F103C8T6的核心板,淘宝上十几块钱。坏了不心疼,随便折腾。
2.2 交叉编译工具链:ARM GCC的安装
交叉编译,说白了就是在你的电脑上编译出ARM芯片能跑的代码。你电脑是x86架构,ARM芯片是ARM架构,编译器得知道怎么把代码翻译成ARM的机器语言。
我推荐用ARM GCC,免费、开源、社区活跃。安装步骤很简单:
- 去ARM官网下载
gcc-arm-none-eabi工具链(Windows/Linux/Mac都有) - 解压到某个目录,比如
C:\arm-gcc - 把
bin目录加到系统环境变量里
验证安装是否成功,打开终端输入:
arm-none-eabi-gcc --version
如果看到版本号,恭喜你,环境搭好了。
arm-none-eabi和arm-linux-gnueabi两种,前者用于裸机开发(比如RTOS),后者用于带Linux的系统。千万别搞混了。
2.3 Makefile工程配置:从零开始
很多人喜欢用IDE,一键编译。但我建议你从Makefile开始。为什么?因为RTOS内核开发,你需要精确控制编译过程,IDE帮你做了太多“好事”,反而让你看不清底层。
一个最简单的Makefile长这样:
# 工具链前缀
PREFIX = arm-none-eabi-
CC = $(PREFIX)gcc
LD = $(PREFIX)ld
OBJCOPY = $(PREFIX)objcopy
# 编译选项
CFLAGS = -mcpu=cortex-m3 -mthumb -Wall -O0 -g
LDFLAGS = -T stm32f103.ld -nostartfiles
# 源文件
SRCS = main.c startup.c
OBJS = $(SRCS:.c=.o)
# 目标
TARGET = rtos_demo
all: $(TARGET).bin
$(TARGET).elf: $(OBJS)
$(LD) $(LDFLAGS) -o $@ $^
$(TARGET).bin: $(TARGET).elf
$(OBJCOPY) -O binary $< $@
%.o: %.c
$(CC) $(CFLAGS) -c -o $@ $<
clean:
rm -f *.o *.elf *.bin
这里有几个关键点:
-mcpu=cortex-m3:指定CPU内核,F103是Cortex-M3-mthumb:使用Thumb指令集,ARM Cortex-M系列只支持Thumb-T stm32f103.ld:链接脚本,告诉链接器内存布局-nostartfiles:不使用标准启动文件,我们自己写
避坑指南:我曾经在Makefile里忘了加-nostartfiles,结果链接器自动加上了C库的启动代码,导致我的中断向量表被覆盖。查了两天才发现,嗯,从那以后我再也不敢漏掉这个选项了。
2.4 链接脚本:芯片的“地图”
链接脚本告诉编译器:你的代码该放哪儿,数据该放哪儿。对于STM32F103C8T6,Flash是64KB,RAM是20KB。链接脚本长这样:
MEMORY
{
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 64K
RAM (rw) : ORIGIN = 0x20000000, LENGTH = 20K
}
SECTIONS
{
.text : {
*(.isr_vector) /* 中断向量表 */
*(.text) /* 代码 */
*(.rodata) /* 只读数据 */
} > FLASH
.data : {
*(.data) /* 初始化数据 */
} > RAM AT > FLASH
.bss : {
*(.bss) /* 未初始化数据 */
} > RAM
}
你想想看,如果没有这个脚本,编译器不知道代码该放哪儿,芯片上电后CPU从0x08000000开始执行,结果那里是空的——板子就成砖了。
2.5 点亮LED:验证环境
环境搭好了,总得验证一下。点亮LED是嵌入式界的“Hello World”。
STM32F103C8T6核心板上,PC13引脚通常接了一个LED。我们要做三件事:
- 使能GPIOC时钟
- 配置PC13为推挽输出
- 拉低PC13(注意:有些板子是低电平点亮)
代码很简单:
// main.c
#include "stm32f10x.h"
void delay(volatile uint32_t count) {
while (count--);
}
int main(void) {
// 使能GPIOC时钟
RCC->APB2ENR |= RCC_APB2ENR_IOPCEN;
// 配置PC13为推挽输出,50MHz
GPIOC->CRH &= ~(GPIO_CRH_CNF13 | GPIO_CRH_MODE13);
GPIOC->CRH |= GPIO_CRH_MODE13_0; // 输出模式,10MHz
while (1) {
GPIOC->BSRR = GPIO_BSRR_BR13; // 拉低,点亮
delay(1000000);
GPIOC->BSRR = GPIO_BSRR_BS13; // 拉高,熄灭
delay(1000000);
}
}
编译、烧录、上电。如果LED开始闪烁,恭喜你——环境搭建成功!
小技巧:如果你发现LED不亮,先别急着怀疑代码。用万用表量一下引脚电压,或者看看板子上的LED是不是焊反了。我遇到过一块板子,LED的正负极标反了,折腾了我半小时。
2.6 烧录工具:把代码送进芯片
代码编译好了,怎么烧进去?我推荐用ST-Link,便宜又好用。淘宝上十几块钱一个,配合st-flash工具,命令行烧录:
st-flash write rtos_demo.bin 0x08000000
如果你用OpenOCD,也可以:
openocd -f interface/stlink.cfg -f target/stm32f1x.cfg -c "program rtos_demo.bin 0x08000000 verify reset exit"
我个人习惯用st-flash,简单直接。OpenOCD功能更强大,但配置起来稍微麻烦点。
2.7 本章小结
嗯,到这里,你的开发环境就搭好了。我们做了这几件事:
- 选了STM32F103C8T6作为硬件平台
- 安装了ARM GCC交叉编译工具链
- 写了Makefile和链接脚本
- 点亮了LED,验证了环境
下一章,我们会开始写RTOS内核的第一行代码——启动文件。别急,慢慢来,环境搭好了,后面的事就顺了。
记住:环境搭建是基础中的基础。我见过太多人在这上面翻车,然后怀疑自己是不是不适合做嵌入式。其实不是的,只是环境没搭对。花点时间把这一步走稳,后面你会感谢自己的。