实验任务1:寄存器方式控制LED流水灯

1. 程序设计思路

GPIO寄存器地址和参数

STM32F103C8T6 GPIO寄存器基地址:

  • GPIOA: 0x4001 0800
  • GPIOB: 0x4001 0C00
  • GPIOC: 0x4001 1000

关键寄存器偏移地址:

  • CRL (端口配置低寄存器): +0x00
  • CRH (端口配置高寄存器): +0x04
  • IDR (输入数据寄存器): +0x08
  • ODR (输出数据寄存器): +0x0C
  • BSRR (位设置/清除寄存器): +0x10
  • BRR (位清除寄存器): +0x14

端口配置参数:

  • 推挽输出模式: 0x01 (CNF=00, MODE=01 → 2MHz输出)
  • 上拉输入模式: 0x08 (CNF=10, MODE=00)

电路连接

  • LED1 (红): PA0
  • LED2 (绿): PA1
  • LED3 (蓝): PA2
  • 板载LED: PC13

2. C语言寄存器方式程序

功能 1:硬件寄存器映射与定义

功能说明:定义 STM32 芯片的寄存器地址和结构体,建立软件操作与硬件寄存器的映射关系,为后续配置提供基础。

代码模块

// 寄存器地址定义
#define GPIOA_BASE 0x40010800
#define GPIOB_BASE 0x40010C00
#define GPIOC_BASE 0x40011000
#define RCC_BASE 0x40021000

// GPIO寄存器结构体指针
#define GPIOA ((GPIO_TypeDef *) GPIOA_BASE)
#define GPIOC ((GPIO_TypeDef *) GPIOC_BASE)
#define RCC ((RCC_TypeDef *) RCC_BASE)

// 寄存器结构体定义
typedef struct {
    volatile uint32_t CRL;
    volatile uint32_t CRH;
    volatile uint32_t IDR;
    volatile uint32_t ODR;
    volatile uint32_t BSRR;
    volatile uint32_t BRR;
    volatile uint32_t LCKR;
} GPIO_TypeDef;

typedef struct {
    volatile uint32_t CR;
    volatile uint32_t CFGR;
    volatile uint32_t CIR;
    volatile uint32_t APB2RSTR;
    volatile uint32_t APB1RSTR;
    volatile uint32_t AHBENR;
    volatile uint32_t APB2ENR;
    volatile uint32_t APB1ENR;
    volatile uint32_t BDCR;
    volatile uint32_t CSR;
} RCC_TypeDef;

功能 2:延时功能实现

功能说明:提供毫秒级延时函数,用于控制 LED 点亮的持续时间,实现闪烁效果的时间间隔控制。

代码模块

// 简单延时函数
void delay_ms(uint32_t ms) {
    for(uint32_t i = 0; i < ms * 1000; i++) {
        for(uint32_t j = 0; j < 72; j++) {
            __NOP();
        }
    }
}

功能 3:硬件初始化配置

功能说明:对 GPIO 端口进行时钟使能和工作模式配置,设置 LED 初始状态,为 LED 控制做好硬件准备。

代码模块

// 1. 使能GPIOA和GPIOC时钟
RCC->APB2ENR |= (1 << 2) | (1 << 4);  // 使能GPIOA和GPIOC时钟

// 2. 配置PA0,PA1,PA2为推挽输出(2MHz)
GPIOA->CRL &= ~(0xFFF << 0);  // 清空PA0-PA2配置位
GPIOA->CRL |= (0x01 << 0) | (0x01 << 4) | (0x01 << 8);  // PA0,PA1,PA2推挽输出

// 3. 配置PC13为推挽输出
GPIOC->CRH &= ~(0xF << 20);   // 清空PC13配置位
GPIOC->CRH |= (0x01 << 20);   // PC13推挽输出

// 4. 初始状态:所有LED熄灭
GPIOA->ODR |= (1 << 0) | (1 << 1) | (1 << 2);  // PA0,PA1,PA2高电平
GPIOC->ODR |= (1 << 13);                       // PC13高电平

功能 4:LED 循环闪烁控制

功能说明:通过循环控制不同 GPIO 引脚的电平状态,实现四个 LED(PA0、PA1、PA2、PC13)按顺序轮流点亮的流水灯效果。

代码模块

while(1) {
    // LED1亮(PA0低电平),其他灭
    GPIOA->ODR |= (1 << 1) | (1 << 2);  // PA1,PA2高
    GPIOC->ODR |= (1 << 13);            // PC13高
    GPIOA->ODR &= ~(1 << 0);            // PA0低
    delay_ms(1000);
    
    // LED2亮(PA1低电平),其他灭
    GPIOA->ODR |= (1 << 0) | (1 << 2);  // PA0,PA2高
    GPIOC->ODR |= (1 << 13);            // PC13高
    GPIOA->ODR &= ~(1 << 1);            // PA1低
    delay_ms(1000);
    
    // LED3亮(PA2低电平),其他灭
    GPIOA->ODR |= (1 << 0) | (1 << 1);  // PA0,PA1高
    GPIOC->ODR |= (1 << 13);            // PC13高
    GPIOA->ODR &= ~(1 << 2);            // PA2低
    delay_ms(1000);
    
    // 板载LED亮(PC13低电平),其他灭
    GPIOA->ODR |= (1 << 0) | (1 << 1) | (1 << 2);  // PA0,PA1,PA2高
    GPIOC->ODR &= ~(1 << 13);           // PC13低
    delay_ms(1000);
}

3. 包含板载LED的新程序

上述程序已经包含了板载PC13 LED的控制。在STM32F103C8T6最小系统板上,PC13连接了一个LED,当PC13输出低电平时LED亮,高电平时LED灭。

4.实验结果

在这里插入图片描述

实验任务2:标准外设库方式

1. 工程项目创建过程

步骤1:创建项目文件夹结构

Project/
├── CMSIS/
│   ├── core_cm3.c
│   ├── core_cm3.h
│   ├── stm32f10x.h
│   └── system_stm32f10x.c
├── Libraries/
│   └── STM32F10x_StdPeriph_Driver/
│       ├── inc/
│       │   ├── stm32f10x_gpio.h
│       │   ├── stm32f10x_rcc.h
│       │   └── ...
│       └── src/
│           ├── stm32f10x_gpio.c
│           ├── stm32f10x_rcc.c
│           └── ...
├── User/
│   ├── main.c
│   ├── stm32f10x_conf.h
│   └── stm32f10x_it.c
└── Project.uvprojx

步骤2:Keil中创建新项目

  1. Project → New μVision Project
  2. 选择STM32F103C8器件
  3. 不复制启动文件(手动添加)

步骤3:添加文件到项目

  • 添加CMSIS文件到项目
  • 添加标准外设库文件(GPIO、RCC等)
  • 创建main.c文件

2. 标准外设库程序

1. 头文件包含

#include "stm32f10x.h"
#include "stm32f10x_gpio.h"
#include "stm32f10x_rcc.h"

2. 硬件引脚定义

// LED引脚定义
#define LED1_PIN GPIO_Pin_0
#define LED2_PIN GPIO_Pin_1  
#define LED3_PIN GPIO_Pin_2
#define LED_ONBOARD_PIN GPIO_Pin_13

3. 函数声明

void GPIO_Configuration(void);
void Delay_ms(uint32_t nTime);

4. 主函数(程序入口)

int main(void) {
    // GPIO配置
    GPIO_Configuration();
    
    while(1) {
        // LED1亮,其他灭
        GPIO_SetBits(GPIOA, LED2_PIN | LED3_PIN);
        GPIO_SetBits(GPIOC, LED_ONBOARD_PIN);
        GPIO_ResetBits(GPIOA, LED1_PIN);
        Delay_ms(1000);
        
        // LED2亮,其他灭
        GPIO_SetBits(GPIOA, LED1_PIN | LED3_PIN);
        GPIO_SetBits(GPIOC, LED_ONBOARD_PIN);
        GPIO_ResetBits(GPIOA, LED2_PIN);
        Delay_ms(1000);
        
        // LED3亮,其他灭
        GPIO_SetBits(GPIOA, LED1_PIN | LED2_PIN);
        GPIO_SetBits(GPIOC, LED_ONBOARD_PIN);
        GPIO_ResetBits(GPIOA, LED3_PIN);
        Delay_ms(1000);
        
        // 板载LED亮,其他灭
        GPIO_SetBits(GPIOA, LED1_PIN | LED2_PIN | LED3_PIN);
        GPIO_ResetBits(GPIOC, LED_ONBOARD_PIN);
        Delay_ms(1000);
    }
}

5. GPIO 初始化配置

void GPIO_Configuration(void) {
    GPIO_InitTypeDef GPIO_InitStructure;
    
    // 使能GPIO时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOC, ENABLE);
    
    // 配置PA0,PA1,PA2为推挽输出
    GPIO_InitStructure.GPIO_Pin = LED1_PIN | LED2_PIN | LED3_PIN;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
    
    // 配置PC13为推挽输出
    GPIO_InitStructure.GPIO_Pin = LED_ONBOARD_PIN;
    GPIO_Init(GPIOC, &GPIO_InitStructure);
    
    // 初始状态:所有LED熄灭
    GPIO_SetBits(GPIOA, LED1_PIN | LED2_PIN | LED3_PIN);
    GPIO_SetBits(GPIOC, LED_ONBOARD_PIN);
}

6. 毫秒级延时函数

void Delay_ms(uint32_t nTime) {
    for(; nTime > 0; nTime--) {
        for(uint32_t i = 0; i < 1000; i++) {
            for(uint32_t j = 0; j < 72; j++) {
                __NOP();
            }
        }
    }
}

7.实验结果

mmexport1759126327074

3. 时序分析和Keil仿真

软件延时周期分析:

  • 系统时钟:72MHz(默认)

  • 每个NOP指令:1个时钟周期

  • 延时函数周期:约1ms(实际需要校准)

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

Keil逻辑分析仪使用步骤:

  1. Project → Options for Target → Debug → Use Simulator

  2. 在Debug窗口中打开Logic Analyzer

  3. 添加要观察的引脚:GPIOA.0, GPIOA.1, GPIOA.2, GPIOC.13

  4. 运行仿真,观察波形

    按图示勾选DIalong DLL 的值改为图中值

在这里插入图片描述
在这里插入图片描述

查看波形来计算时间

在这里插入图片描述
在这里插入图片描述

实际周期测量:
通过逻辑分析仪可观察到:

  • 高电平持续时间:约1000ms
  • 低电平持续时间:约1000ms
  • 总周期:约2000ms(2秒)

实验一与实验二关系分析

相同点:

  1. 实现相同的功能:LED流水灯控制
  2. 使用相同的硬件资源
  3. 达到相同的视觉效果

不同点:

特性 实验一(寄存器方式) 实验二(库函数方式)
编程复杂度 高,需要直接操作寄存器 低,使用封装好的函数
可读性 较差,需要了解寄存器细节 较好,函数名直观
可移植性 差,与具体芯片绑定 较好,跨系列兼容
代码量 小,直接高效 较大,包含库开销
学习价值 深入理解硬件工作原理 快速开发应用
维护性 差,修改困难 好,易于维护

关系总结:
实验一是基础,帮助理解STM32的底层硬件工作原理;实验二是应用,展示如何利用官方库提高开发效率。两者相辅相成,寄存器方式的学习为理解库函数实现原理打下基础,而库函数方式则是实际项目开发的首选方法。

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐