第二章:开发环境搭建——让硬件听你指挥

说实话,做嵌入式开发这么多年,我见过太多人一上来就急着写代码,结果连LED都点不亮。嗯,这其实很正常。环境搭建这事儿,看着简单,但坑不少。今天我就带你一步步把STM32的开发环境搭起来,让硬件真正听你指挥。

2.1 硬件平台选择:为什么是STM32?

你可能要问:市面上那么多ARM芯片,为什么偏偏选STM32?

我个人习惯用STM32F103系列,原因有三:

  • 文档齐全:ST官方的手册、参考手册、勘误表,写得清清楚楚。我在项目中遇到过一些冷门芯片,手册写得跟天书似的,查个寄存器要翻半天。STM32就不会有这问题。
  • 生态成熟:从HAL库到LL库,从Keil到GCC,工具链多得是。你甚至能找到十年前的老帖子,里面还有人在讨论F103的坑。
  • 价格亲民:一块F103C8T6的核心板,淘宝上十几块钱。坏了不心疼,随便折腾。
我的建议:如果你刚开始学RTOS,买一块STM32F103C8T6的“蓝色小板子”就行。别一上来就搞H7系列,那玩意儿配置复杂,容易劝退。

2.2 交叉编译工具链:ARM GCC的安装

交叉编译,说白了就是在你的电脑上编译出ARM芯片能跑的代码。你电脑是x86架构,ARM芯片是ARM架构,编译器得知道怎么把代码翻译成ARM的机器语言。

我推荐用ARM GCC,免费、开源、社区活跃。安装步骤很简单:

  1. 去ARM官网下载gcc-arm-none-eabi工具链(Windows/Linux/Mac都有)
  2. 解压到某个目录,比如C:\arm-gcc
  3. bin目录加到系统环境变量里

验证安装是否成功,打开终端输入:

arm-none-eabi-gcc --version

如果看到版本号,恭喜你,环境搭好了。

注意:我曾经遇到过一个问题——下载了错误的版本。ARM GCC有arm-none-eabiarm-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。我们要做三件事:

  1. 使能GPIOC时钟
  2. 配置PC13为推挽输出
  3. 拉低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内核的第一行代码——启动文件。别急,慢慢来,环境搭好了,后面的事就顺了。

记住:环境搭建是基础中的基础。我见过太多人在这上面翻车,然后怀疑自己是不是不适合做嵌入式。其实不是的,只是环境没搭对。花点时间把这一步走稳,后面你会感谢自己的。