3. 开发环境搭建(下):工程模板创建、编译与下载、LED点灯实验、串口打印Hello World

上一节我们把工具链、IDE、调试器驱动都装好了。现在,是时候让代码真正跑起来了。

这一节我们要做四件事:创建工程模板、编译下载、点灯、打印Hello World。听起来简单,但每一步都有坑。我当年第一次点灯,折腾了整整一个下午——后来发现是芯片型号选错了。

3.1 创建工程模板——别每次都从零开始

我个人习惯,每个项目都从模板开始。为什么?因为芯片初始化代码(启动文件、链接脚本、时钟配置)每次几乎一样。你想想看,每次新建工程都重新配一遍时钟,多浪费时间。

3.1.1 选择芯片型号

以STM32F103C8T6为例(蓝板子最常见)。打开Keil,点Project → New μVision Project,然后:

  • 选一个干净的文件夹,比如 D:\workspace\bp_monitor_template
  • 输入工程名:bp_monitor
  • 在弹出的芯片选择窗口,搜索 STM32F103C8
注意: 我曾经因为选成了STM32F103C8T6A(带A后缀的版本),结果调试器死活连不上。后来发现是芯片ID不匹配。所以,一定要确认你板子上的丝印

3.1.2 添加必要的文件

一个最小工程需要这些文件:

文件/文件夹 说明
startup_stm32f103xb.s 启动文件,负责初始化堆栈、跳转到main
system_stm32f10x.c 系统时钟配置
stm32f10x.h 外设寄存器定义
main.c 你的主程序

这些文件可以从STM32标准外设库(StdPeriph_Lib)里复制。我个人习惯把库文件放在工程目录下的 Libraries 文件夹里,方便管理。

3.1.3 配置魔术棒(Target & C/C++)

点工具栏的「魔术棒」图标,进入工程配置:

  • Target标签:晶振频率填 8.0(外部晶振8MHz)
  • C/C++标签:Define框里加 USE_STDPERIPH_DRIVER, STM32F10X_MD
  • Debug标签:选 ST-Link Debugger,点Settings,确认SWD模式
小技巧: 我习惯在C/C++标签的Include Paths里把 .\Libraries\CMSIS.\Libraries\StdPeriph_Driver\inc 加进去。这样写代码时头文件不会报红。

3.2 编译与下载——第一次跑通

模板建好了,先写一个空main函数,编译试试:

int main(void)
{
    while(1);
}

Build 按钮(或者按F7)。如果0错误0警告,恭喜你,环境没问题。

然后点 Load 按钮(或者按F8),把程序下载到板子里。如果一切顺利,调试器会提示 Flash Download finished

常见问题: 如果下载时报 No target connected,先检查ST-Link的4根线(SWDIO、SWCLK、GND、3.3V)有没有接反。我遇到过最离谱的一次,是杜邦线内部断了——换了根线就好了。

3.3 LED点灯实验——嵌入式界的Hello World

点灯,说白了就是控制GPIO的电平。在血压计项目里,LED可以用来指示电源状态、测量进度等。

3.3.1 硬件连接

假设板子上有一个LED接在PC13(高电平点亮)。我们需要:

  • 使能GPIOC的时钟
  • 配置PC13为推挽输出
  • 写高电平点亮,写低电平熄灭

3.3.2 代码实现

#include "stm32f10x.h"

void delay(void)
{
    volatile uint32_t i;
    for(i = 0; i < 500000; i++);
}

int main(void)
{
    // 使能GPIOC时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);

    // 配置PC13为推挽输出
    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOC, &GPIO_InitStructure);

    while(1)
    {
        GPIO_SetBits(GPIOC, GPIO_Pin_13);   // 点亮
        delay();
        GPIO_ResetBits(GPIOC, GPIO_Pin_13); // 熄灭
        delay();
    }
}

编译下载后,如果LED以大约1秒的频率闪烁,说明成功了。

避坑指南: 我曾经在配置GPIO时忘了使能时钟,结果LED死活不亮。查了半小时才发现 RCC_APB2PeriphClockCmd 没调用。记住:STM32的所有外设,用之前必须先开时钟

3.4 串口打印Hello World——让板子开口说话

点灯只能看,串口才能「听」。在血压计开发中,串口用来输出调试信息、传感器数据等。

3.4.1 硬件连接

用USB转串口模块(比如CH340G)连接板子:

  • 板子TX(PA9) → 模块RX
  • 板子RX(PA10) → 模块TX
  • GND → GND

3.4.2 串口初始化代码

void USART1_Init(void)
{
    // 使能USART1和GPIOA时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE);

    // 配置PA9为复用推挽输出(TX)
    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    // 配置PA10为浮空输入(RX)
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    // 配置USART1:115200波特率,8位数据,1位停止位,无校验
    USART_InitTypeDef USART_InitStructure;
    USART_InitStructure.USART_BaudRate = 115200;
    USART_InitStructure.USART_WordLength = USART_WordLength_8b;
    USART_InitStructure.USART_StopBits = USART_StopBits_1;
    USART_InitStructure.USART_Parity = USART_Parity_No;
    USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
    USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;
    USART_Init(USART1, &USART_InitStructure);

    // 使能USART1
    USART_Cmd(USART1, ENABLE);
}

// 发送一个字符
void USART1_SendChar(uint8_t ch)
{
    while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
    USART_SendData(USART1, ch);
}

// 发送字符串
void USART1_SendString(char *str)
{
    while(*str)
    {
        USART1_SendChar(*str++);
    }
}

3.4.3 主函数调用

int main(void)
{
    USART1_Init();

    while(1)
    {
        USART1_SendString("Hello World from BP Monitor!\r\n");
        delay();
    }
}

打开串口助手(波特率115200),你应该能看到板子每隔1秒打印一次 Hello World from BP Monitor!

核心要点:
  • 串口配置的三个关键参数:波特率、数据位、停止位——两边必须一致
  • TX引脚要配成复用推挽输出,RX引脚配成浮空输入
  • 发送数据前要等待发送缓冲区空(TXE标志位)

3.5 小结

这一节我们完成了嵌入式开发最基础的三件事:建模板、点灯、打印。别小看这些,它们是后续所有复杂功能(ADC采集、传感器驱动、无线通信)的基石。

下一节,我们会正式进入血压计的核心——压力传感器数据采集。到时候,串口打印的数据就不再是Hello World,而是真实的血压值了。

嗯,先到这里。记得把今天的代码保存好,后面会反复用到。