目录
扫描按键
接线
按键一头接 PB12
一头接 GND
,因为 PB12
默认是上拉 3.3V
的
按键初始化函数
void key_init(void) {
// GPIO 结构体变量
GPIO_InitTypeDef GPIO_InitStruct;
// 使能 GPIOB
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_12;
// 设置 PA12 为上拉输入
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStruct);
}
按键扫描函数
首先在上面定义一个案件引脚状态检测的宏定义
#define USER_KEY GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_12)
然后定义一个按键扫描和触发动作的函数
void key_scan(void) {
// 检测按键被按下
if (USER_KEY == 0) {
// 延时 10ms 再次检测防抖
delay(1, 10);
if (USER_KEY == 0) {
// 按键按下使板载 LED 闪烁一次
GPIO_ResetBits(GPIOC, GPIO_Pin_13);
delay(1, 100);
GPIO_SetBits(GPIOC, GPIO_Pin_13);
delay(1, 100);
}
}
}
主函数
这里面很简单,初始化之后,循环扫描按键即可
int main(void) {
delay_init();
led_init();
key_init();
while (1) {
key_scan();
}
}
编译下载运行之后,将 PB12
连接一下 GND
板载 LED
就闪烁一次,一直连着一直闪。
外部中断
首先需要添加一个外部中断固件库 EXTI
修改按键初始化函数
void key_init(void) {
// GPIO 结构体变量
GPIO_InitTypeDef GPIO_InitStruct;
// 外部中断结构体变量
EXTI_InitTypeDef EXTI_InitStruct;
// 中断优先级结构体变量
NVIC_InitTypeDef NVIC_InitStruct;
// 使能 GPIOB 和 AFIO 复用时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB|RCC_APB2Periph_AFIO, ENABLE);
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_12;
// 设置 PA12 为上拉输入
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStruct);
// PB12 中断线映射
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource12);
// 外部中断模式
EXTI_InitStruct.EXTI_Mode = EXTI_Mode_Interrupt;
// 中断线为 12
EXTI_InitStruct.EXTI_Line = EXTI_Line12;
// 下降沿触发
EXTI_InitStruct.EXTI_Trigger = EXTI_Trigger_Falling;
// 中断线使能
EXTI_InitStruct.EXTI_LineCmd = ENABLE;
// 外部中断初始化
EXTI_Init(&EXTI_InitStruct);
// 如果配置了中断,那么必须同时配置优先级
// 配置外部中断通道为 10~15,因为我们使用的是 12,通道定义可以在 stm32f10x.h 中查看
NVIC_InitStruct.NVIC_IRQChannel = EXTI15_10_IRQn;
// 抢占优先级为 0
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 0;
// 子优先级为 1
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 1;
// 使能外部中断通道
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
// 中断优先级初始化
NVIC_Init(&NVIC_InitStruct);
}
修改主函数
int main(void) {
// 设置抢占优先级为 2 位,可以在 misc.c 中查看此函数和参数
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
delay_init();
led_init();
// usart_init(115200);
key_init();
while (1) {
// key_scan();
}
}
添加中断服务函数
// 注释掉主函数中循环里的按键扫描,因为接下来会用中断服务函数触发
// 中断服务函数,函数名是固定的,根据设置的通道在 startup_stm3210x_md.s 中查看对应的终端服务函数名
void EXTI15_10_IRQHandler(void) {
// 判断中断线 12 是否被触发
if (EXTI_GetITStatus(EXTI_Line12) != RESET) {
// 消抖
delay(1, 30);
// 闪一下灯
GPIO_ResetBits(GPIOC, GPIO_Pin_13);
delay(1, 100);
GPIO_SetBits(GPIOC, GPIO_Pin_13);
delay(1, 100);
}
// 清除 EXTI_Line12 上的中断标志位
EXTI_ClearITPendingBit(EXTI_Line12);
}
根据教程中断触发的应该比扫描好一点,比如响应更准确,长按只触发一次。然而我的测试是长按还是会反复触发,甚至导致程序崩溃停止运行。
TIM 定时器
STM32 中有 4 个定时器(TIM)每个 TIM 包含 4 个独立通道。我们使用通用定时器来:
- 测量输入信号的脉宽(输入捕获)
- 产生输出波形(输出比较)
- 产生脉冲宽度调制波(PWM)
因此在使用 PWM 之前要先了解一下通用定时器的使用方法。
首先添加定时器固件库 stm32f10x_tim.c
初始化定时器
// 周期和频率经常需要改变,所以用变量来传递
void tim_init(uint16_t per, uint16_t psc) {
// 定时器结构体变量
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;
// 中断优先级结构体变量
NVIC_InitTypeDef NVIC_InitStruct;
// 使能 TIM3 的时钟,定时器寄存器名可以在 stm32f10x_rcc.h 中查看
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
// 自动重装载周期
TIM_TimeBaseInitStruct.TIM_Period = per;
// 设置预分频值(频率)
TIM_TimeBaseInitStruct.TIM_Prescaler = psc;
// 向上计数模式,其他模式可以在 stmstm32f10x_tim.h 中查看定义
TIM_TimeBaseInitStruct.TIM_CounterMode = TIM_CounterMode_Up;
// 设置时钟分割
TIM_TimeBaseInitStruct.TIM_ClockDivision = TIM_CKD_DIV1;
// 定时器初始化函数
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStruct);
// 使能 TIM3 中断,stmstm32f10x_tim.c 中查看实现
// 第二个参数表示中断源,此为中断更新
TIM_ITConfig(TIM3, TIM_IT_Update, ENABLE);
// 如果配置了中断,那么必须同时配置优先级
NVIC_InitStruct.NVIC_IRQChannel = TIM3_IRQn;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 1;
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStruct);
// TIM3 使能
TIM_Cmd(TIM3, ENABLE);
}
修改主函数
int main(void) {
// 设置抢占优先级为 2 位,可以在 misc.c 中查看此函数和参数
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
delay_init();
led_init();
// 定时器初始化(周期,分频)
// 定时 1s = (9999 + 1)*(7199 + 1)/72MHz
tim_init(9999, 7199);
while (1) {
}
}
- 由于我们的使用定时器中断来执行功能,所以还得在初始化阶段设置抢占优先级之后,才能在
tim_init()
中设置中断的抢占优先级和子优先级 - 主循环中依然不需要任何代码,下面的中断服务程序会编写我们要做的操作
定时器中断服务函数
void TIM3_IRQHandler(void) {
if (TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET) {
// 直接对寄存器位操作使 PC13 电平翻转
GPIOC->ODR ^= GPIO_Pin_13;
// 依然需要清除中断标记
TIM_ClearITPendingBit(TIM3, TIM_IT_Update);
}
}
PWM 输出
现在我们使用 “输出比较” 功能实现 PWM 输出功能。但是 PC13 是无法输出 PWM 信号的,因此我们需要查看对应的 开发手册 中的 “引脚定义” 章节,查看引脚功能表中哪个引脚复用了 TIM3 定时器:
可以看到 PA6 PA7 PB0 PB1
分别对应定时器 TIM3
的四个通道。因此我们可以使用 PA6
来实现 PWM
呼吸灯的功能。
初始化函数
void breath_light_tim_init(uint16_t per, uint16_t psc) {
GPIO_InitTypeDef GPIO_InitStruct;
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;
// 输出比较结构体变量
TIM_OCInitTypeDef TIM_OCInitStruct;
// 使能 GPIOA
// 因为 TIM3 是 PA6 的复用功能,所以需要开启 AFIO 复用时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_AFIO, ENABLE);
// 使能 TIM3 的时钟,定时器寄存器名可以在 stm32f10x_rcc.h 中查看
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_6;
// 设置 PA6 为推挽输出
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStruct);
TIM_TimeBaseInitStruct.TIM_Period = per;
TIM_TimeBaseInitStruct.TIM_Prescaler = psc;
TIM_TimeBaseInitStruct.TIM_CounterMode = TIM_CounterMode_Up;
// 时钟分割依然是默认的 1 分割
TIM_TimeBaseInitStruct.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStruct);
// 这次没有用到中断,所以不需要使能中断
// TIM_ITConfig(TIM3, TIM_IT_Update, ENABLE);
// PWM 模式 1,其他模式可以在 stm32f10x_tim.h 中查看
TIM_OCInitStruct.TIM_OCMode = TIM_OCMode_PWM1;
// 初始化占空比为 0
TIM_OCInitStruct.TIM_Pulse = 0;
// 输出比较极性低,stm32f10x_tim.h
TIM_OCInitStruct.TIM_OCPolarity = TIM_OCPolarity_Low;
// 比较输出使能
TIM_OCInitStruct.TIM_OutputState = TIM_OutputState_Enable;
// 初始化在 stm32f10x_rcc.h 中找到定义
// 因为我们使用的是 PA6 其复用的是 TIM3 的通道 1 CH1 所以我们使用 OC1 来进行初始化
TIM_OC1Init(TIM3, &TIM_OCInitStruct);
// TIM3 使能
TIM_Cmd(TIM3, ENABLE);
}
修改主函数
int main(void) {
uint8_t flag = 0;
uint16_t pwmValue = 0;
// 没有用到中断这里也不需要设置分组
// NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
delay_init();
led_init();
// (899+1)*(0+1)/72MHz = 1/(8e4)s,也就是说频率为 80KHz
breath_light_tim_init(899, 0);
while (1) {
// key_scan();
if (flag == 0) {
pwmValue++;
if (pwmValue > 350) flag = 1;
}
if (flag == 1) {
pwmValue--;
if (pwmValue == 0) flag = 0;
}
TIM_SetCompare1(TIM3, pwmValue);
delay(1, 10);
}
}