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 提供三种低功耗模式,按功耗从高到低排列:
- 睡眠模式(Sleep mode):只停止 CPU,所有外设继续运行,中断 / 唤醒事件可唤醒
- 停止模式(Stop mode):停止所有时钟,保留 SRAM 和寄存器内容,最低 2μA 功耗
- 待机模式(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 的底层原理和寄存器操作,将有助于开发出更加高效、稳定的嵌入式系统。