当前位置: 首页 > news >正文

基于 Keil 的 STM32 全模块开发

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) {// 点亮 LEDGPIO_ResetBits(GPIOC, GPIO_Pin_13);// 等待按键释放while (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_12) == 0);// 熄灭 LEDGPIO_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();// 配置 GPIOGPIO_Configuration();// 配置 NVICNVIC_Configuration();// 配置 EXTIEXTI_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();// 配置 TIM3TIM3_Configuration();// 配置 NVICNVIC_Configuration();// 使能 TIM3TIM_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 = 1msTIM_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();// 配置 GPIOGPIO_Configuration();// 配置 TIM2TIM2_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) = 1kHzTIM_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);// 使能 TIM2TIM_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();// 配置 GPIOGPIO_Configuration();// 配置 ADC1ADC1_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 通道 0ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_28Cycles5);// 使能 ADC1ADC_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();// 配置 GPIOGPIO_Configuration();// 配置 ADC1ADC1_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 和 1ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_28Cycles5);ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 2, ADC_SampleTime_28Cycles5);// 使能 ADC1ADC_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();// 配置 GPIOGPIO_Configuration();// 配置 NVICNVIC_Configuration();// 配置 USART1USART1_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);// 使能 USART1USART_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();// 配置 GPIOGPIO_Configuration();// 配置 I2C1I2C1_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// 初始化 I2C1I2C_Init(I2C1, &I2C_InitStructure);// 使能 I2C1I2C_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();// 配置 GPIOGPIO_Configuration();// 配置 SPI1SPI1_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.5MHzSPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;SPI_InitStructure.SPI_CRCPolynomial = 7;// 初始化 SPI1SPI_Init(SPI1, &SPI_InitStructure);// 使能 SPI1SPI_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();// 配置 NVICNVIC_Configuration();// 配置 RTCRTC_Configuration();// 设置初始时间为 00:00:00RTC_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();// 使能 LSERCC_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();// 配置 GPIOGPIO_Configuration();// 配置 NVICNVIC_Configuration();// 配置 EXTIEXTI_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 的底层原理和寄存器操作,将有助于开发出更加高效、稳定的嵌入式系统。

相关文章:

  • day30-模块和库的导入
  • 基于Qt的app开发第九天
  • ubuntu 20.04 ping baidu.coom可以通,ping www.baidu.com不通 【DNS出现问题】解决方案
  • 阿里云国际站与国内站:局势推进中的多维差异
  • C++ :STL
  • 蓝桥杯框架-LED蜂鸣器继电器
  • 【C++】哈希的概念与实现
  • PCL点云库点云数据处理入门系列教材目录(2025年5月更新....)
  • 从 Word2Vec 到 BERT:AI 不止是词向量,更是语言理解
  • 如何用 OceanBase 的 LOAD DATA 旁路导入进行大表迁移
  • python自学笔记3 控制结构
  • 用Python实现数据库数据自动化导出PDF报告:从MySQL到个性化文档的全流程实践
  • 在金融发展领域,嵌入式主板有什么优点?
  • goldenDB创建函数索引报错问题
  • 5G金融互联:迈向未来金融服务的极速与智能新时代
  • 每日c/c++题 备战蓝桥杯(洛谷P4715 【深基16.例1】淘汰赛 题解)
  • 安装NASM
  • 虚拟机的三个核心类加载器
  • 【VBA/word】批量替换字体大小
  • 深入解析分布式数据库TiDB:原理、优化与架构实践
  • 特朗普与普京就俄乌问题通话
  • 山西晋城一网红徒步野游线路据传发生驴友坠崖,当地已宣布封路
  • 1至4月国家铁路发送货物12.99亿吨,同比增长3.6%
  • 殷墟出土鸮尊时隔50年首次聚首,北京新展“看·见殷商”
  • 上海这场有温度的“人才集市”,为更多人才搭建“暖心桥”
  • 辽宁援疆前指总指挥王敬华已任新疆塔城地委副书记