基于 Keil 的 STM32 全模块开发

发布于:2025-05-20 ⋅ 阅读:(16) ⋅ 点赞:(0)

1. 开发环境搭建

1.1 安装 Keil MDK

首先需要从 ARM 官网下载 Keil MDK-ARM 开发工具,目前最新版本是 5.38。安装过程中需要注意选择正确的安装路径,建议不要包含中文和空格。安装完成后需要进行注册,注册方法如下:

// Keil MDK 注册示例代码
#include <stdio.h>

int main() {
    printf("Keil MDK 注册方法:\n");
    printf("1. 运行 Keil uVision5\n");
    printf("2. 点击 File -> License Management\n");
    printf("3. 复制 CID 码\n");
    printf("4. 使用注册机生成 License Key\n");
    printf("5. 将生成的 License Key 粘贴到 License Management 窗口中\n");
    printf("6. 点击 Add LIC 完成注册\n");
    
    return 0;
}

1.2 安装 STM32 器件支持包

STM32 器件支持包(Device Family Pack, DFP)提供了特定 STM32 系列的头文件、启动文件和外设驱动库。安装方法如下:

// STM32 DFP 安装示例代码
#include <stdio.h>

int main() {
    printf("STM32 DFP 安装方法:\n");
    printf("1. 打开 Keil uVision5\n");
    printf("2. 点击 Pack Installer\n");
    printf("3. 在左侧选择 STMicroelectronics\n");
    printf("4. 在右侧选择需要的 STM32 系列和版本\n");
    printf("5. 点击 Install 进行安装\n");
    printf("6. 等待安装完成后关闭 Pack Installer\n");
    
    return 0;
}

1.3 创建第一个 STM32 项目

下面介绍如何在 Keil 中创建一个基于 STM32F103 的项目:

// 创建 STM32F103 项目示例代码
#include <stdio.h>

int main() {
    printf("创建 STM32F103 项目步骤:\n");
    printf("1. 打开 Keil uVision5\n");
    printf("2. 点击 Project -> New uVision Project\n");
    printf("3. 选择项目保存路径并命名\n");
    printf("4. 在 Device 对话框中选择 STM32F103C8T6\n");
    printf("5. 点击 OK\n");
    printf("6. 在 Manage Run-Time Environment 对话框中选择需要的组件\n");
    printf("7. 点击 OK 完成项目创建\n");
    
    return 0;
}

2. GPIO 开发

2.1 GPIO 基础知识

GPIO(General Purpose Input Output)是 STM32 最基本的外设,用于数字信号的输入输出。STM32F103 系列有多达 80 个 GPIO 引脚,分为 A~E 共 5 组。每个 GPIO 引脚可以配置为多种模式:

// GPIO 模式定义(stm32f10x_gpio.h)
typedef enum {
    GPIO_Mode_AIN = 0x0,           // 模拟输入
    GPIO_Mode_IN_FLOATING = 0x04,  // 浮空输入
    GPIO_Mode_IPD = 0x28,          // 下拉输入
    GPIO_Mode_IPU = 0x48,          // 上拉输入
    GPIO_Mode_Out_OD = 0x14,       // 开漏输出
    GPIO_Mode_Out_PP = 0x10,       // 推挽输出
    GPIO_Mode_AF_OD = 0x1C,        // 复用开漏输出
    GPIO_Mode_AF_PP = 0x18         // 复用推挽输出
} GPIOMode_TypeDef;

2.2 GPIO 输出配置

下面是一个配置 GPIO 为输出模式并控制 LED 闪烁的示例:

// GPIO 输出配置示例(LED 闪烁)
#include "stm32f10x.h"

void Delay(__IO uint32_t nCount);

int main(void) {
    GPIO_InitTypeDef GPIO_InitStructure;
    
    // 使能 GPIOC 时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);
    
    // 配置 PC13 为推挽输出
    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) {
        // 点亮 LED(PC13 输出低电平)
        GPIO_ResetBits(GPIOC, GPIO_Pin_13);
        Delay(0xFFFFF);
        
        // 熄灭 LED(PC13 输出高电平)
        GPIO_SetBits(GPIOC, GPIO_Pin_13);
        Delay(0xFFFFF);
    }
}

// 简单延时函数
void Delay(__IO uint32_t nCount) {
    for (; nCount != 0; nCount--);
}

2.3 GPIO 输入配置

下面是一个配置 GPIO 为输入模式并检测按键状态的示例:

// GPIO 输入配置示例(按键检测)
#include "stm32f10x.h"

void Delay(__IO uint32_t nCount);

int main(void) {
    GPIO_InitTypeDef GPIO_InitStructure;
    
    // 使能 GPIOC 和 GPIOB 时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC | RCC_APB2Periph_GPIOB, ENABLE);
    
    // 配置 PC13 为推挽输出(LED)
    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);
    
    // 配置 PB12 为上拉输入(按键)
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
    GPIO_Init(GPIOB, &GPIO_InitStructure);
    
    while (1) {
        // 检测按键是否按下(PB12 是否为低电平)
        if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_12) == 0) {
            Delay(0x20000);  // 消抖
            
            // 确认按键按下
            if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_12) == 0) {
                // 点亮 LED
                GPIO_ResetBits(GPIOC, GPIO_Pin_13);
                
                // 等待按键释放
                while (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_12) == 0);
                
                // 熄灭 LED
                GPIO_SetBits(GPIOC, GPIO_Pin_13);
            }
        }
    }
}

// 简单延时函数
void Delay(__IO uint32_t nCount) {
    for (; nCount != 0; nCount--);
}

3. 中断系统开发

3.1 中断基础知识

STM32F103 具有强大的中断系统,支持多达 60 个可屏蔽中断通道和 16 级可编程中断优先级。中断控制器为 NVIC(Nested Vectored Interrupt Controller)。

中断优先级分为抢占优先级和子优先级:

  • 抢占优先级高的中断可以打断正在执行的抢占优先级低的中断
  • 抢占优先级相同的中断,子优先级高的先执行
  • 抢占优先级和子优先级都相同的中断,按硬件编号顺序执行

3.2 外部中断配置

下面是一个配置外部中断检测按键的示例:

// 外部中断配置示例(按键中断)
#include "stm32f10x.h"

void GPIO_Configuration(void);
void NVIC_Configuration(void);
void EXTI_Configuration(void);

int main(void) {
    // 系统时钟配置
    SystemInit();
    
    // 配置 GPIO
    GPIO_Configuration();
    
    // 配置 NVIC
    NVIC_Configuration();
    
    // 配置 EXTI
    EXTI_Configuration();
    
    while (1) {
        // 主循环可以处理其他任务
    }
}

void GPIO_Configuration(void) {
    GPIO_InitTypeDef GPIO_InitStructure;
    
    // 使能 GPIOC 和 GPIOB 时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC | RCC_APB2Periph_GPIOB | 
                           RCC_APB2Periph_AFIO, ENABLE);
    
    // 配置 PC13 为推挽输出(LED)
    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);
    
    // 配置 PB12 为浮空输入(按键)
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
    GPIO_Init(GPIOB, &GPIO_InitStructure);
    
    // 将 PB12 连接到 EXTI 线
    GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource12);
}

void NVIC_Configuration(void) {
    NVIC_InitTypeDef NVIC_InitStructure;
    
    // 配置 NVIC 优先级分组为 2 位抢占优先级,2 位子优先级
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
    
    // 配置 EXTI15_10 中断
    NVIC_InitStructure.NVIC_IRQChannel = EXTI15_10_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);
}

void EXTI_Configuration(void) {
    EXTI_InitTypeDef EXTI_InitStructure;
    
    // 配置 EXTI 线 12 为下降沿触发
    EXTI_InitStructure.EXTI_Line = EXTI_Line12;
    EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
    EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;
    EXTI_InitStructure.EXTI_LineCmd = ENABLE;
    EXTI_Init(&EXTI_InitStructure);
}

// 外部中断 15-10 服务函数
void EXTI15_10_IRQHandler(void) {
    if (EXTI_GetITStatus(EXTI_Line12) != RESET) {
        // 清除中断标志
        EXTI_ClearITPendingBit(EXTI_Line12);
        
        // 延时消抖
        for (int i = 0; i < 100000; i++);
        
        // 检测按键是否仍然按下
        if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_12) == 0) {
            // 切换 LED 状态
            GPIOC->ODR ^= GPIO_Pin_13;
        }
    }
}

4. 定时器开发

4.1 定时器基础知识

STM32F103 系列微控制器包含多个定时器,可分为以下几类:

  • 高级控制定时器(TIM1、TIM8)
  • 通用定时器(TIM2~TIM5)
  • 基本定时器(TIM6、TIM7)
  • 看门狗定时器(IWDG、WWDG)
  • SysTick 定时器

不同类型的定时器功能不同,但基本工作原理相似。定时器的核心组件包括:

  • 计数器(CNT)
  • 预分频器(PSC)
  • 自动重载寄存器(ARR)
  • 比较寄存器(CCR)

4.2 通用定时器配置

下面是一个配置通用定时器 TIM3 产生 1ms 定时中断的示例:

// 通用定时器配置示例(1ms 定时中断)
#include "stm32f10x.h"

void TIM3_Configuration(void);
void NVIC_Configuration(void);

uint32_t g_TickCount = 0;

int main(void) {
    // 系统时钟配置
    SystemInit();
    
    // 配置 TIM3
    TIM3_Configuration();
    
    // 配置 NVIC
    NVIC_Configuration();
    
    // 使能 TIM3
    TIM_Cmd(TIM3, ENABLE);
    
    while (1) {
        // 主循环可以处理其他任务
        // 每 1000ms 执行一次的任务
        if (g_TickCount >= 1000) {
            g_TickCount = 0;
            // 执行定时任务
        }
    }
}

void TIM3_Configuration(void) {
    TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
    
    // 使能 TIM3 时钟
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
    
    // TIM3 配置
    // 定时器时钟频率 = 72MHz / (71 + 1) = 1MHz
    // 定时器周期 = 1MHz / (999 + 1) = 1kHz = 1ms
    TIM_TimeBaseStructure.TIM_Period = 999;
    TIM_TimeBaseStructure.TIM_Prescaler = 71;
    TIM_TimeBaseStructure.TIM_ClockDivision = 0;
    TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
    TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);
    
    // 使能 TIM3 中断
    TIM_ITConfig(TIM3, TIM_IT_Update, ENABLE);
}

void NVIC_Configuration(void) {
    NVIC_InitTypeDef NVIC_InitStructure;
    
    // 配置 NVIC 优先级分组为 2 位抢占优先级,2 位子优先级
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
    
    // 配置 TIM3 中断
    NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);
}

// TIM3 中断服务函数
void TIM3_IRQHandler(void) {
    if (TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET) {
        // 清除中断标志
        TIM_ClearITPendingBit(TIM3, TIM_IT_Update);
        
        // 增加计数
        g_TickCount++;
    }
}

4.3 PWM 输出配置

下面是一个配置 TIM2 产生 PWM 信号控制 LED 亮度的示例:

// PWM 输出配置示例(LED 亮度控制)
#include "stm32f10x.h"

void TIM2_Configuration(void);
void GPIO_Configuration(void);

int main(void) {
    uint16_t dutyCycle = 0;
    uint8_t direction = 1;
    
    // 系统时钟配置
    SystemInit();
    
    // 配置 GPIO
    GPIO_Configuration();
    
    // 配置 TIM2
    TIM2_Configuration();
    
    while (1) {
        // 调整占空比实现呼吸灯效果
        if (direction) {
            dutyCycle++;
            if (dutyCycle >= 1000) direction = 0;
        } else {
            dutyCycle--;
            if (dutyCycle == 0) direction = 1;
        }
        
        // 设置 PWM 占空比
        TIM_SetCompare2(TIM2, dutyCycle);
        
        // 延时
        for (int i = 0; i < 5000; i++);
    }
}

void GPIO_Configuration(void) {
    GPIO_InitTypeDef GPIO_InitStructure;
    
    // 使能 GPIOA 和 TIM2 时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
    
    // 配置 PA1 (TIM2_CH2) 为复用推挽输出
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
}

void TIM2_Configuration(void) {
    TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
    TIM_OCInitTypeDef TIM_OCInitStructure;
    
    // TIM2 基本配置
    // 定时器时钟频率 = 72MHz / (71 + 1) = 1MHz
    // 定时器周期 = 1MHz / (999 + 1) = 1kHz
    TIM_TimeBaseStructure.TIM_Period = 999;
    TIM_TimeBaseStructure.TIM_Prescaler = 71;
    TIM_TimeBaseStructure.TIM_ClockDivision = 0;
    TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
    TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);
    
    // TIM2 PWM 模式配置
    TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
    TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
    TIM_OCInitStructure.TIM_Pulse = 0;  // 初始占空比为 0%
    TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
    TIM_OC2Init(TIM2, &TIM_OCInitStructure);
    
    // 使能 TIM2
    TIM_Cmd(TIM2, ENABLE);
}

5. ADC 开发

5.1 ADC 基础知识

STM32F103 内置了 12 位 ADC(Analog-to-Digital Converter),具有以下特点:

  • 最多 18 个通道(16 个外部通道和 2 个内部通道)
  • 转换范围:0~3.3V
  • 采样时间可配置
  • 支持单次转换、连续转换、扫描模式和间断模式

5.2 ADC 单通道配置

下面是一个配置 ADC1 采样通道 0(PA0)的示例:

// ADC 单通道配置示例(采样 PA0 电压)
#include "stm32f10x.h"

void ADC1_Configuration(void);
void GPIO_Configuration(void);

int main(void) {
    uint16_t adcValue = 0;
    float voltage = 0.0;
    
    // 系统时钟配置
    SystemInit();
    
    // 配置 GPIO
    GPIO_Configuration();
    
    // 配置 ADC1
    ADC1_Configuration();
    
    while (1) {
        // 启动 ADC 转换
        ADC_SoftwareStartConvCmd(ADC1, ENABLE);
        
        // 等待转换完成
        while (!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC));
        
        // 读取 ADC 值
        adcValue = ADC_GetConversionValue(ADC1);
        
        // 计算电压值 (假设参考电压为 3.3V)
        voltage = (float)adcValue * 3.3 / 4095;
        
        // 可以将电压值通过串口输出或进行其他处理
    }
}

void GPIO_Configuration(void) {
    GPIO_InitTypeDef GPIO_InitStructure;
    
    // 使能 GPIOA 时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    
    // 配置 PA0 为模拟输入
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
}

void ADC1_Configuration(void) {
    ADC_InitTypeDef ADC_InitStructure;
    
    // 使能 ADC1 时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);
    
    // ADC1 配置
    ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
    ADC_InitStructure.ADC_ScanConvMode = DISABLE;
    ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;
    ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
    ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
    ADC_InitStructure.ADC_NbrOfChannel = 1;
    ADC_Init(ADC1, &ADC_InitStructure);
    
    // 配置 ADC1 通道 0
    ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_28Cycles5);
    
    // 使能 ADC1
    ADC_Cmd(ADC1, ENABLE);
    
    // 使能 ADC1 复位校准
    ADC_ResetCalibration(ADC1);
    
    // 等待复位校准完成
    while (ADC_GetResetCalibrationStatus(ADC1));
    
    // 开始 ADC1 校准
    ADC_StartCalibration(ADC1);
    
    // 等待校准完成
    while (ADC_GetCalibrationStatus(ADC1));
}

5.3 ADC 多通道配置

下面是一个配置 ADC1 采样多个通道的示例:

// ADC 多通道配置示例(采样 PA0 和 PA1 电压)
#include "stm32f10x.h"

void ADC1_Configuration(void);
void GPIO_Configuration(void);

uint16_t adcValues[2];

int main(void) {
    float voltage0 = 0.0;
    float voltage1 = 0.0;
    
    // 系统时钟配置
    SystemInit();
    
    // 配置 GPIO
    GPIO_Configuration();
    
    // 配置 ADC1
    ADC1_Configuration();
    
    while (1) {
        // 启动 ADC 转换
        ADC_SoftwareStartConvCmd(ADC1, ENABLE);
        
        // 等待转换完成
        while (!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC));
        
        // 读取 ADC 值
        adcValues[0] = ADC_GetConversionValue(ADC1);
        
        // 启动第二次转换
        ADC_SoftwareStartConvCmd(ADC1, ENABLE);
        
        // 等待转换完成
        while (!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC));
        
        // 读取 ADC 值
        adcValues[1] = ADC_GetConversionValue(ADC1);
        
        // 计算电压值
        voltage0 = (float)adcValues[0] * 3.3 / 4095;
        voltage1 = (float)adcValues[1] * 3.3 / 4095;
        
        // 可以将电压值通过串口输出或进行其他处理
    }
}

void GPIO_Configuration(void) {
    GPIO_InitTypeDef GPIO_InitStructure;
    
    // 使能 GPIOA 时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    
    // 配置 PA0 和 PA1 为模拟输入
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
}

void ADC1_Configuration(void) {
    ADC_InitTypeDef ADC_InitStructure;
    
    // 使能 ADC1 时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);
    
    // ADC1 配置
    ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
    ADC_InitStructure.ADC_ScanConvMode = ENABLE;  // 启用扫描模式
    ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;
    ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
    ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
    ADC_InitStructure.ADC_NbrOfChannel = 2;  // 两个通道
    ADC_Init(ADC1, &ADC_InitStructure);
    
    // 配置 ADC1 通道 0 和 1
    ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_28Cycles5);
    ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 2, ADC_SampleTime_28Cycles5);
    
    // 使能 ADC1
    ADC_Cmd(ADC1, ENABLE);
    
    // 使能 ADC1 复位校准
    ADC_ResetCalibration(ADC1);
    
    // 等待复位校准完成
    while (ADC_GetResetCalibrationStatus(ADC1));
    
    // 开始 ADC1 校准
    ADC_StartCalibration(ADC1);
    
    // 等待校准完成
    while (ADC_GetCalibrationStatus(ADC1));
}

6. 串口通信开发

6.1 串口基础知识

STM32F103 内置多个 USART(Universal Synchronous/Asynchronous Receiver/Transmitter),支持以下通信模式:

  • 异步通信(UART)
  • 同步通信(USART)
  • 单线半双工通信
  • 智能卡模式
  • IrDA SIR 编码器 / 解码器

常用的串口参数包括:波特率、数据位、停止位、校验位。

6.2 串口发送接收配置

下面是一个配置 USART1 进行发送和接收的示例:

// 串口通信配置示例(USART1)
#include "stm32f10x.h"
#include <stdio.h>

void USART1_Configuration(void);
void GPIO_Configuration(void);
void NVIC_Configuration(void);

// 重定向 printf 函数到 USART1
int fputc(int ch, FILE *f) {
    USART_SendData(USART1, (uint8_t)ch);
    
    // 等待发送完成
    while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
    
    return ch;
}

int main(void) {
    uint8_t receiveData = 0;
    
    // 系统时钟配置
    SystemInit();
    
    // 配置 GPIO
    GPIO_Configuration();
    
    // 配置 NVIC
    NVIC_Configuration();
    
    // 配置 USART1
    USART1_Configuration();
    
    // 发送欢迎信息
    printf("Welcome to STM32 USART1 Demo!\r\n");
    
    while (1) {
        // 主循环可以处理其他任务
        // 接收到的数据会在中断服务函数中处理
    }
}

void GPIO_Configuration(void) {
    GPIO_InitTypeDef GPIO_InitStructure;
    
    // 使能 GPIOA 和 USART1 时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_USART1, ENABLE);
    
    // 配置 PA9 (USART1_TX) 为复用推挽输出
    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 (USART1_RX) 为浮空输入
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
}

void USART1_Configuration(void) {
    USART_InitTypeDef USART_InitStructure;
    
    // USART1 配置
    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_ITConfig(USART1, USART_IT_RXNE, ENABLE);
    
    // 使能 USART1
    USART_Cmd(USART1, ENABLE);
}

void NVIC_Configuration(void) {
    NVIC_InitTypeDef NVIC_InitStructure;
    
    // 配置 NVIC 优先级分组为 2 位抢占优先级,2 位子优先级
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
    
    // 配置 USART1 中断
    NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);
}

// USART1 中断服务函数
void USART1_IRQHandler(void) {
    uint8_t receiveData;
    
    if (USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) {
        // 读取接收到的数据
        receiveData = USART_ReceiveData(USART1);
        
        // 回显数据
        USART_SendData(USART1, receiveData);
        
        // 等待发送完成
        while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
        
        // 清除中断标志
        USART_ClearITPendingBit(USART1, USART_IT_RXNE);
    }
}

7. I2C 通信开发

7.1 I2C 基础知识

I2C(Inter-Integrated Circuit)是一种串行通信协议,由 Philips 公司开发,使用两根线(SDA 和 SCL)实现多设备通信。STM32F103 内置多个 I2C 控制器,支持以下特性:

  • 标准模式(100kHz)
  • 快速模式(400kHz)
  • 高速模式(3.4MHz)
  • 多主模式
  • 7 位或 10 位寻址

7.2 I2C 主设备配置

下面是一个配置 I2C1 作为主设备与 I2C 从设备通信的示例:

// I2C 主设备配置示例(I2C1)
#include "stm32f10x.h"

void I2C1_Configuration(void);
void GPIO_Configuration(void);

// I2C 延时函数
void I2C_Delay(void) {
    uint32_t i = 0;
    for (i = 0; i < 500; i++);
}

// I2C 写一个字节数据
uint8_t I2C_WriteByte(uint8_t slaveAddr, uint8_t regAddr, uint8_t data) {
    // 等待 I2C 总线空闲
    while (I2C_GetFlagStatus(I2C1, I2C_FLAG_BUSY));
    
    // 发送起始位
    I2C_GenerateSTART(I2C1, ENABLE);
    
    // 等待起始位发送完成
    while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT));
    
    // 发送从设备地址(写操作)
    I2C_Send7bitAddress(I2C1, slaveAddr << 1, I2C_Direction_Transmitter);
    
    // 等待地址发送完成
    while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));
    
    // 发送寄存器地址
    I2C_SendData(I2C1, regAddr);
    
    // 等待数据发送完成
    while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED));
    
    // 发送数据
    I2C_SendData(I2C1, data);
    
    // 等待数据发送完成
    while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED));
    
    // 发送停止位
    I2C_GenerateSTOP(I2C1, ENABLE);
    
    return 0;
}

// I2C 读一个字节数据
uint8_t I2C_ReadByte(uint8_t slaveAddr, uint8_t regAddr) {
    uint8_t data = 0;
    
    // 等待 I2C 总线空闲
    while (I2C_GetFlagStatus(I2C1, I2C_FLAG_BUSY));
    
    // 发送起始位
    I2C_GenerateSTART(I2C1, ENABLE);
    
    // 等待起始位发送完成
    while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT));
    
    // 发送从设备地址(写操作)
    I2C_Send7bitAddress(I2C1, slaveAddr << 1, I2C_Direction_Transmitter);
    
    // 等待地址发送完成
    while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));
    
    // 发送寄存器地址
    I2C_SendData(I2C1, regAddr);
    
    // 等待数据发送完成
    while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED));
    
    // 发送重复起始位
    I2C_GenerateSTART(I2C1, ENABLE);
    
    // 等待起始位发送完成
    while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT));
    
    // 发送从设备地址(读操作)
    I2C_Send7bitAddress(I2C1, slaveAddr << 1, I2C_Direction_Receiver);
    
    // 等待地址发送完成
    while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED));
    
    // 禁止应答
    I2C_AcknowledgeConfig(I2C1, DISABLE);
    
    // 发送停止位
    I2C_GenerateSTOP(I2C1, ENABLE);
    
    // 等待接收数据
    while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_RECEIVED));
    
    // 读取数据
    data = I2C_ReceiveData(I2C1);
    
    // 重新使能应答
    I2C_AcknowledgeConfig(I2C1, ENABLE);
    
    return data;
}

int main(void) {
    uint8_t data = 0;
    
    // 系统时钟配置
    SystemInit();
    
    // 配置 GPIO
    GPIO_Configuration();
    
    // 配置 I2C1
    I2C1_Configuration();
    
    // 向从设备写入数据
    I2C_WriteByte(0x50, 0x00, 0xAA);
    
    // 延时
    I2C_Delay();
    
    // 从从设备读取数据
    data = I2C_ReadByte(0x50, 0x00);
    
    while (1) {
        // 主循环可以处理其他任务
    }
}

void GPIO_Configuration(void) {
    GPIO_InitTypeDef GPIO_InitStructure;
    
    // 使能 GPIOB 和 I2C1 时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1, ENABLE);
    
    // 配置 PB6 (I2C1_SCL) 和 PB7 (I2C1_SDA) 为复用开漏输出
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOB, &GPIO_InitStructure);
}

void I2C1_Configuration(void) {
    I2C_InitTypeDef I2C_InitStructure;
    
    // I2C1 配置
    I2C_InitStructure.I2C_Mode = I2C_Mode_I2C;
    I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2;
    I2C_InitStructure.I2C_OwnAddress1 = 0x00;  // 主设备地址可以不设置
    I2C_InitStructure.I2C_Ack = I2C_Ack_Enable;
    I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
    I2C_InitStructure.I2C_ClockSpeed = 100000;  // 100kHz
    
    // 初始化 I2C1
    I2C_Init(I2C1, &I2C_InitStructure);
    
    // 使能 I2C1
    I2C_Cmd(I2C1, ENABLE);
}

8. SPI 通信开发

8.1 SPI 基础知识

SPI(Serial Peripheral Interface)是一种高速全双工串行通信协议,由 Motorola 公司开发。STM32F103 内置多个 SPI 控制器,支持以下特性:

  • 主 / 从模式
  • 最高 18MHz 传输速率
  • 8 位或 16 位数据帧
  • 多种时钟极性和相位配置
  • 支持双线全双工、双线单工和单线模式

SPI 通信使用 4 根线:

  • SCK(时钟线)
  • MOSI(主设备输出从设备输入)
  • MISO(主设备输入从设备输出)
  • NSS(片选线)

8.2 SPI 主设备配置

下面是一个配置 SPI1 作为主设备与 SPI 从设备通信的示例:

// SPI 主设备配置示例(SPI1)
#include "stm32f10x.h"

void SPI1_Configuration(void);
void GPIO_Configuration(void);

// SPI 发送接收一个字节
uint8_t SPI_SendByte(uint8_t data) {
    // 等待发送缓冲区为空
    while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET);
    
    // 发送数据
    SPI_I2S_SendData(SPI1, data);
    
    // 等待接收缓冲区非空
    while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) == RESET);
    
    // 返回接收到的数据
    return SPI_I2S_ReceiveData(SPI1);
}

int main(void) {
    uint8_t sendData = 0x55;
    uint8_t receiveData = 0;
    
    // 系统时钟配置
    SystemInit();
    
    // 配置 GPIO
    GPIO_Configuration();
    
    // 配置 SPI1
    SPI1_Configuration();
    
    while (1) {
        // 使能从设备(拉低片选线)
        GPIO_ResetBits(GPIOA, GPIO_Pin_4);
        
        // 发送并接收数据
        receiveData = SPI_SendByte(sendData);
        
        // 禁止从设备(拉高片选线)
        GPIO_SetBits(GPIOA, GPIO_Pin_4);
        
        // 延时
        for (int i = 0; i < 100000; i++);
    }
}

void GPIO_Configuration(void) {
    GPIO_InitTypeDef GPIO_InitStructure;
    
    // 使能 GPIOA 和 SPI1 时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_SPI1, ENABLE);
    
    // 配置 PA4 (SPI1_NSS) 为通用推挽输出
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
    
    // 配置 PA5 (SPI1_SCK) 和 PA7 (SPI1_MOSI) 为复用推挽输出
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_7;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
    
    // 配置 PA6 (SPI1_MISO) 为浮空输入
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
    
    // 初始状态下禁止从设备
    GPIO_SetBits(GPIOA, GPIO_Pin_4);
}

void SPI1_Configuration(void) {
    SPI_InitTypeDef SPI_InitStructure;
    
    // SPI1 配置
    SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
    SPI_InitStructure.SPI_Mode = SPI_Mode_Master;
    SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;
    SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;
    SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge;
    SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;
    SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_16;  // 4.5MHz
    SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;
    SPI_InitStructure.SPI_CRCPolynomial = 7;
    
    // 初始化 SPI1
    SPI_Init(SPI1, &SPI_InitStructure);
    
    // 使能 SPI1
    SPI_Cmd(SPI1, ENABLE);
}

9. RTC 实时时钟开发

9.1 RTC 基础知识

STM32F103 内置实时时钟(RTC),提供日历功能(年、月、日、时、分、秒)和闹钟功能。RTC 具有以下特点:

  • 独立的电源域,由 VBAT 引脚供电,主电源断电后仍能工作
  • 32.768kHz 低速外部晶振(LSE)或低速内部 RC 振荡器(LSI)
  • 支持亚秒级精度
  • 包含可编程闹钟和周期性唤醒功能

9.2 RTC 配置与使用

下面是一个配置 RTC 并获取当前时间的示例:

// RTC 配置示例
#include "stm32f10x.h"
#include <stdio.h>

void RTC_Configuration(void);
void NVIC_Configuration(void);

// 打印时间函数
void PrintTime(void) {
    uint32_t time = RTC_GetCounter();
    uint8_t hours = (time / 3600) % 24;
    uint8_t minutes = (time / 60) % 60;
    uint8_t seconds = time % 60;
    
    printf("Current Time: %02d:%02d:%02d\r\n", hours, minutes, seconds);
}

int main(void) {
    // 系统时钟配置
    SystemInit();
    
    // 配置 NVIC
    NVIC_Configuration();
    
    // 配置 RTC
    RTC_Configuration();
    
    // 设置初始时间为 00:00:00
    RTC_SetCounter(0);
    
    // 等待 RTC 寄存器同步
    RTC_WaitForSynchro();
    
    while (1) {
        // 每 1 秒打印一次时间
        if (RTC_GetFlagStatus(RTC_FLAG_SEC) != RESET) {
            // 清除秒标志
            RTC_ClearFlag(RTC_FLAG_SEC);
            
            // 打印当前时间
            PrintTime();
        }
    }
}

void NVIC_Configuration(void) {
    NVIC_InitTypeDef NVIC_InitStructure;
    
    // 配置 NVIC 优先级分组为 2 位抢占优先级,2 位子优先级
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
    
    // 配置 RTC 中断
    NVIC_InitStructure.NVIC_IRQChannel = RTC_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);
}

void RTC_Configuration(void) {
    // 使能 PWR 和 BKP 时钟
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE);
    
    // 允许访问 BKP 域
    PWR_BackupAccessCmd(ENABLE);
    
    // 复位 BKP 域
    BKP_DeInit();
    
    // 使能 LSE
    RCC_LSEConfig(RCC_LSE_ON);
    
    // 等待 LSE 就绪
    while (RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET);
    
    // 选择 LSE 作为 RTC 时钟源
    RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);
    
    // 使能 RTC 时钟
    RCC_RTCCLKCmd(ENABLE);
    
    // 等待 RTC 寄存器同步
    RTC_WaitForSynchro();
    
    // 等待上次写操作完成
    RTC_WaitForLastTask();
    
    // 设置 RTC 预分频器
    // RTC 时钟频率为 32.768kHz
    // 分频系数为 32768,得到 1Hz 的时钟
    RTC_SetPrescaler(32767);
    
    // 等待上次写操作完成
    RTC_WaitForLastTask();
    
    // 使能 RTC 秒中断
    RTC_ITConfig(RTC_IT_SEC, ENABLE);
    
    // 等待上次写操作完成
    RTC_WaitForLastTask();
}

// RTC 中断服务函数
void RTC_IRQHandler(void) {
    if (RTC_GetITStatus(RTC_IT_SEC) != RESET) {
        // 清除 RTC 秒中断标志
        RTC_ClearITPendingBit(RTC_IT_SEC);
        
        // 等待上次写操作完成
        RTC_WaitForLastTask();
    }
}

10. 低功耗模式开发

10.1 低功耗模式基础知识

STM32F103 提供三种低功耗模式,按功耗从高到低排列:

  1. 睡眠模式(Sleep mode):只停止 CPU,所有外设继续运行,中断 / 唤醒事件可唤醒
  2. 停止模式(Stop mode):停止所有时钟,保留 SRAM 和寄存器内容,最低 2μA 功耗
  3. 待机模式(Standby mode):关闭所有外设,仅保留待机电路工作,最低 0.2μA 功耗

10.2 停止模式配置

下面是一个配置停止模式并通过外部中断唤醒的示例:

// 停止模式配置示例
#include "stm32f10x.h"

void GPIO_Configuration(void);
void NVIC_Configuration(void);
void EXTI_Configuration(void);

int main(void) {
    // 系统时钟配置
    SystemInit();
    
    // 配置 GPIO
    GPIO_Configuration();
    
    // 配置 NVIC
    NVIC_Configuration();
    
    // 配置 EXTI
    EXTI_Configuration();
    
    while (1) {
        // 点亮 LED 表示正常运行
        GPIO_ResetBits(GPIOC, GPIO_Pin_13);
        
        // 延时
        for (int i = 0; i < 1000000; i++);
        
        // 熄灭 LED 准备进入低功耗模式
        GPIO_SetBits(GPIOC, GPIO_Pin_13);
        
        // 进入停止模式
        // 配置 PWR 以在退出停止模式时使用 HSI 作为系统时钟
        PWR_EnterSTOPMode(PWR_Regulator_LowPower, PWR_STOPEntry_WFI);
        
        // 从这里开始是被唤醒后的代码
        // 系统时钟会自动配置为 HSI
    }
}

void GPIO_Configuration(void) {
    GPIO_InitTypeDef GPIO_InitStructure;
    
    // 使能 GPIOC 和 GPIOB 时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC | RCC_APB2Periph_GPIOB | 
                           RCC_APB2Periph_AFIO, ENABLE);
    
    // 配置 PC13 为推挽输出(LED)
    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);
    
    // 配置 PB12 为浮空输入(按键)
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
    GPIO_Init(GPIOB, &GPIO_InitStructure);
    
    // 将 PB12 连接到 EXTI 线
    GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource12);
}

void NVIC_Configuration(void) {
    NVIC_InitTypeDef NVIC_InitStructure;
    
    // 配置 NVIC 优先级分组为 2 位抢占优先级,2 位子优先级
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
    
    // 配置 EXTI15_10 中断
    NVIC_InitStructure.NVIC_IRQChannel = EXTI15_10_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);
}

void EXTI_Configuration(void) {
    EXTI_InitTypeDef EXTI_InitStructure;
    
    // 配置 EXTI 线 12 为上升沿触发
    EXTI_InitStructure.EXTI_Line = EXTI_Line12;
    EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
    EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;
    EXTI_InitStructure.EXTI_LineCmd = ENABLE;
    EXTI_Init(&EXTI_InitStructure);
}

// 外部中断 15-10 服务函数
void EXTI15_10_IRQHandler(void) {
    if (EXTI_GetITStatus(EXTI_Line12) != RESET) {
        // 清除中断标志
        EXTI_ClearITPendingBit(EXTI_Line12);
        
        // 延时消抖
        for (int i = 0; i < 100000; i++);
    }
}

11. 总结

本文详细介绍了基于 Keil 开发环境的 STM32 全模块开发,包括开发环境搭建、GPIO、中断系统、定时器、ADC、串口通信、I2C、SPI、RTC 和低功耗模式等内容。通过大量的代码示例,展示了如何配置和使用 STM32 的各种外设和功能。

STM32 微控制器凭借其丰富的外设资源、高性能和低功耗特性,广泛应用于工业控制、消费电子、物联网等领域。掌握基于 Keil 的 STM32 开发技术,将为嵌入式系统开发打下坚实的基础。

在实际开发中,建议结合 STM32CubeMX 工具进行初始化代码的生成,以提高开发效率。同时,深入理解 STM32 的底层原理和寄存器操作,将有助于开发出更加高效、稳定的嵌入式系统。


网站公告

今日签到

点亮在社区的每一天
去签到