1. 功能需求
按下按键A,常响2秒,打开报警功能,此时当电动车(振动传感器)晃动时,蜂鸣器叫,若想关闭蜂鸣器,按下按键B,则关闭报警功能,蜂鸣器停止叫声。
电动车报警系统
2. 硬件准备
2.1 硬件介绍
433M无线收发模块、蜂鸣器、振动传感器、STM32开发板、绝缘线、STLINK、typeC
2.1.1 433M无线收发模块
无线收发模块 433MHz (或称 RF433 射频小模块)采用高频射频技术,并结合了全数字技术和 AVR 单片机,成为一种微型收发器。

遥控器上有四个按键,分别对应 433M 模块的四个数据位输出脚 D0 、 D1 、 D2 、 D3 。按下按
键发射信号,对应的引脚就会输出高电平。
那么, 433M 模块一般按照工作模式分类,有三种工作模式:
- M 点动:瞬态输出,相当于自复位开关的状态收到信息输出高电平,无信号时为低电平。
- T 锁存:上电时为低,收到信号输出高电平并锁定高,再次收到信号输出低并锁定低。
- L 自锁:上电时为低,收到信号输出高电平并锁定高,同时将其他输出置低。
比如我的模块是 M4 点动,「 4 」表示有 4 个按键,按一下按键 A , D0 将输出一个高电平。
2.1.2 振动传感器
我用的是SW-18010P

接好 VCC 和 GND ,电源指示灯将被点亮。
当检测到震动时,板载指示灯点亮;震动停止,板载指示灯熄灭。
用螺丝刀转动「灵敏度调节电位器」可以调节灵敏度。大家可以将电位器旋到最左或最右,然后测试往哪边旋可以提高灵敏度。
SW-18015P 在无震动或者震动强度达不到设定阈值时, DO 口输出高电平;当外界震动强度超过设定阈值时,模块 D0 输出低电平。
2.1.3 蜂鸣器


如何让蜂鸣器鸣响?将 I/O 引脚拉低即可。
模块工作电压: 3.3V
2.2 引脚连接
|------------|-----|------|
| 433M无线收发模块 | GND | GND |
| | 5V | 5V |
| | D0 | PB4 |
| | D1 | PA11 |
|-------|-----|-----|
| 振动传感器 | VCC | 3V3 |
| | GND | GND |
| | DO | PA4 |
| | AO | 不接 |
|-----|-----|-----|
| 蜂鸣器 | GND | GND |
| | IO | PB7 |
| | VCC | 3V3 |
3. 分步实现功能
先分别实现振动点灯功能、无线收发信号、蜂鸣器功能,再集成
3.1 振动点灯功能
3.1.1 准备好写过的led.c文件
cpp
#include "led.h"
#include "sys.h"
//初始化GPIO函数
void led_init(void)
{
GPIO_InitTypeDef gpio_initstruct;
//打开时钟
__HAL_RCC_GPIOB_CLK_ENABLE(); // 使能GPIOB时钟
//调用GPIO初始化函数
gpio_initstruct.Pin = GPIO_PIN_8 | GPIO_PIN_9; // 两个LED对应的引脚
gpio_initstruct.Mode = GPIO_MODE_OUTPUT_PP; // 推挽输出
gpio_initstruct.Pull = GPIO_PULLUP; // 上拉
gpio_initstruct.Speed = GPIO_SPEED_FREQ_HIGH; // 高速
HAL_GPIO_Init(GPIOB, &gpio_initstruct);
//关闭LED
led1_off();
led2_off();
}
//点亮LED1的函数
void led1_on(void)
{
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_8, GPIO_PIN_RESET); // 拉低LED1引脚,点亮LED1
}
//熄灭LED1的函数
void led1_off(void)
{
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_8, GPIO_PIN_SET); // 拉高LED1引脚,熄灭LED1
}
//翻转LED1状态的函数
void led1_toggle(void)
{
HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_8);
}
//点亮LED2的函数
void led2_on(void)
{
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_9, GPIO_PIN_RESET); // 拉低LED2引脚,点亮LED2
}
//熄灭LED2的函数
void led2_off(void)
{
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_9, GPIO_PIN_SET); // 拉高LED2引脚,熄灭LED2
}
//翻转LED2状态的函数
void led2_toggle(void)
{
HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_9);
}
3.1.2 写exti.c文件
第一步:进行初始化,写初始化函数
写任何实验都必要的步骤
- 打开时钟
- 调用GPIO初始化函数
cpp
void exti_init(void)
{
GPIO_InitTypeDef gpio_initstruct;
//打开时钟
__HAL_RCC_GPIOA_CLK_ENABLE(); // 使能GPIOA时钟
//调用GPIO初始化函数
gpio_initstruct.Pin = GPIO_PIN_4; // 震动传感器对应的引脚
gpio_initstruct.Mode = GPIO_MODE_IT_FALLING; // 下降沿触发
gpio_initstruct.Pull = GPIO_PULLUP; // 上拉
HAL_GPIO_Init(GPIOA, &gpio_initstruct);
HAL_NVIC_SetPriority(EXTI4_IRQn, 2, 0); // 设置EXTI0中断线的优先级
HAL_NVIC_EnableIRQ(EXTI4_IRQn); // 使能中断
}
这里用到了中断,下次再详细讲中断,这次先简单了解一下~
①HAL_NVIC_SetPriority和HAL_NVIC_EnableIRQ都是中断配置函数
HAL_NVIC_SetPriority是中断优先级配置,HAL_NVIC_EnableIRQ是中断使能配置
设置中断分组 → 设置中断优先级 → 使能中断
②配置EXTI,其实就是按着顺序写实现中断功能

第二步:此时就剩中断服务函数EXTI4_IRQHandler没写了,为实现所需功能,一般会用到回调函数HAL_GPIO_EXTI_Callback,后面根据需要功能再写回调函数
cpp
void EXTI4_IRQHandler(void)
{
HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_4);
}
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
}
第三步:设立标志位,这一步需要和main.c配合看才能理解
实际上是检测到振动和没有振动的区别:检测到振动,标志位为1,无振动,标志位为0
在main.c中实现主功能,如果检测到振动,即vibrate_flag_get() == 1,那么就点亮LED1两秒然后熄灭,此时要把标志位置为0,vibrate_flag_set(0),否则标志位一直为1就没有区分的效果了
cpp
uint8_t vibrate_flag_get(void)
{
uint8_t temp = vibrate_flag;
vibrate_flag = FALSE;
return temp;
}
void vibrate_flag_set(uint8_t value)
{
vibrate_flag = value;
}
第四步:设立标志位后,怎么和振动传感器联系起来------回调函数
当振动传感器感受到振动时,PA4收到低电平,见"2.1.2 振动传感器"
用HAL_GPIO_ReadPin读PA4,如果是低电平0(GPIO_PIN_RESET),则标志位为1
cpp
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
//delay_ms(20);
if (GPIO_Pin == GPIO_PIN_4)
{
if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_4) == GPIO_PIN_RESET)
//led1_toggle();
vibrate_flag = TRUE;
}
}
3.1.3 写main.c
cpp
#include "sys.h"
#include "delay.h"
#include "led.h"
#include "exti.h"
int main(void)
{
HAL_Init(); /* 初始化HAL库 */
stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟, 72Mhz */
led_init(); /* 初始化LED灯 */
exti_init(); /* 初始化EXTI */
while(1)
{
//如果检测到震动
if(vibrate_flag_get() == TRUE)
{
//那么就点亮LED1两秒,然后熄灭
led1_on();
delay_ms(2000);
led1_off();
vibrate_flag_set(FALSE);
}
}
}
别忘了补充.h文件的函数声明
3.2 无线收发信号功能
如果检测到A键按下,就翻转LED1状态;如果检测到B键按下,就翻转LED2状态
3.2.1 main.c
很容易理解,就是平常的初始化HAL库和一些函数、设置时钟,然后翻转灯的功能实现
可以扩展到电动车报警系统,翻转的把灯改为蜂鸣器就好了!
cpp
#include "sys.h"
#include "delay.h"
#include "led.h"
#include "exti.h"
int main(void)
{
HAL_Init(); /* 初始化HAL库 */
stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟, 72Mhz */
led_init(); /* 初始化LED灯 */
exti_init(); /* 初始化EXTI */
while(1)
{
if(buttonA_flag_get() == TRUE) //如果检测到A键按下
led1_toggle(); //则翻转LED1状态
if(buttonB_flag_get() == TRUE) //如果检测到B键按下
led2_toggle(); //则翻转LED2状态
}
}
3.2.2 exti.c
两个按键,在3.1.2的exti.c文件中只设置了一个中断,现在copy过来后,再加一个中断就好了
标志位也只设了一个,我们现在再加一个
等于都要3.1.2的双份的
cpp
#include "exti.h"
#include "sys.h"
#include "delay.h"
#include "led.h"
uint8_t buttonA_flag = FALSE; // 按键A按下的标志位
uint8_t buttonB_flag = FALSE; // 按键B按下的标志位
void exti_init(void)
{
GPIO_InitTypeDef gpio_initstruct;
//打开时钟
__HAL_RCC_GPIOA_CLK_ENABLE(); // 使能GPIOA时钟
__HAL_RCC_GPIOB_CLK_ENABLE();
//按键B中断配置
gpio_initstruct.Pin = GPIO_PIN_12; // 按键B对应的引脚
gpio_initstruct.Mode = GPIO_MODE_IT_RISING; // 上升沿触发
gpio_initstruct.Pull = GPIO_PULLDOWN; // 下拉
HAL_GPIO_Init(GPIOA, &gpio_initstruct);
HAL_NVIC_SetPriority(EXTI15_10_IRQn, 2, 0); // 设置EXTI15_10中断线的优先级
HAL_NVIC_EnableIRQ(EXTI15_10_IRQn); // 使能中断
//按键A中断配置
gpio_initstruct.Pin = GPIO_PIN_5; // 按键A对应的引脚
gpio_initstruct.Mode = GPIO_MODE_IT_RISING; // 上升沿触发
gpio_initstruct.Pull = GPIO_PULLDOWN; // 下拉
HAL_GPIO_Init(GPIOB, &gpio_initstruct);
HAL_NVIC_SetPriority(EXTI9_5_IRQn, 2, 0); // 设置EXTI9_5中断线的优先级
HAL_NVIC_EnableIRQ(EXTI9_5_IRQn); // 使能中断
}
void EXTI15_10_IRQHandler(void)
{
HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_12);
}
void EXTI9_5_IRQHandler(void)
{
HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_5);
}
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
//delay_ms(20);
if (GPIO_Pin == GPIO_PIN_12) //检测到B键按下
{
if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_12) == GPIO_PIN_SET)
//led2_toggle();
buttonB_flag = TRUE;
}
else if (GPIO_Pin == GPIO_PIN_5) //检测到A键按下
{
if(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_5) == GPIO_PIN_SET)
//led1_toggle();
buttonA_flag = TRUE;
}
}
uint8_t buttonA_flag_get(void)
{
uint8_t temp = buttonA_flag;
buttonA_flag = FALSE;
return temp;
}
void buttonA_flag_set(uint8_t value)
{
buttonA_flag = value;
}
uint8_t buttonB_flag_get(void)
{
uint8_t temp = buttonB_flag;
buttonB_flag = FALSE;
return temp;
}
void buttonB_flag_set(uint8_t value)
{
buttonB_flag = value;
}
led.c不变,补充.h文件就可尝试运行了
3.3 蜂鸣器功能
这个是最简单的,上面的会了,这个一看就懂了
beep.c
cpp
#include "beep.h"
#include "sys.h"
void beep_init(void)
{
GPIO_InitTypeDef gpio_initstruct;
__HAL_RCC_GPIOB_CLK_ENABLE();
gpio_initstruct.Pin=GPIO_PIN_8 ;
gpio_initstruct.Mode=GPIO_MODE_OUTPUT_PP;
gpio_initstruct.Speed=GPIO_SPEED_FREQ_HIGH;
gpio_initstruct.Pull=GPIO_PULLUP;
HAL_GPIO_Init(GPIOB,&gpio_initstruct);
void beep_off(void);
}
void beep_on(void)
{
HAL_GPIO_WritePin(GPIOB , GPIO_PIN_8, GPIO_PIN_RESET);
}
void beep_off(void)
{
HAL_GPIO_WritePin(GPIOB , GPIO_PIN_8, GPIO_PIN_SET);
}
main.c
cpp
#include "sys.h"
#include "uart1.h"
#include "delay.h"
#include "beep.h"
int main()
{
HAL_Init();
stm32_clock_init(RCC_PLL_MUL9);
beep_init();
beep_off();
//while(1)
//{
// beep_on();
// delay_ms(500);
// beep_off();
// delay_ms(500);
//}
}
4. 整体功能实现
4.1 思路

4.2 代码
led.c
cpp
#include "led.h"
#include "sys.h"
//初始化GPIO函数
void led_init(void)
{
GPIO_InitTypeDef gpio_initstruct;
//打开时钟
__HAL_RCC_GPIOB_CLK_ENABLE(); // 使能GPIOB时钟
//调用GPIO初始化函数
gpio_initstruct.Pin = GPIO_PIN_8 | GPIO_PIN_9; // 两个LED对应的引脚
gpio_initstruct.Mode = GPIO_MODE_OUTPUT_PP; // 推挽输出
gpio_initstruct.Pull = GPIO_PULLUP; // 上拉
gpio_initstruct.Speed = GPIO_SPEED_FREQ_HIGH; // 高速
HAL_GPIO_Init(GPIOB, &gpio_initstruct);
//关闭LED
led1_off();
led2_off();
}
//点亮LED1的函数
void led1_on(void)
{
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_8, GPIO_PIN_RESET); // 拉低LED1引脚,点亮LED1
}
//熄灭LED1的函数
void led1_off(void)
{
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_8, GPIO_PIN_SET); // 拉高LED1引脚,熄灭LED1
}
//翻转LED1状态的函数
void led1_toggle(void)
{
HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_8);
}
//点亮LED2的函数
void led2_on(void)
{
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_9, GPIO_PIN_RESET); // 拉低LED2引脚,点亮LED2
}
//熄灭LED2的函数
void led2_off(void)
{
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_9, GPIO_PIN_SET); // 拉高LED2引脚,熄灭LED2
}
//翻转LED2状态的函数
void led2_toggle(void)
{
HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_9);
}
exti.c
cpp
#include "exti.h"
#include "sys.h"
#include "delay.h"
#include "led.h"
uint8_t buttonA_flag = FALSE; // 按键A按下的标志位
uint8_t buttonB_flag = FALSE; // 按键B按下的标志位
uint8_t vibrate_flag = FALSE; // 检测到震动标志位
void exti_init(void)
{
GPIO_InitTypeDef gpio_initstruct;
//打开时钟
__HAL_RCC_GPIOA_CLK_ENABLE(); // 使能GPIOA时钟
__HAL_RCC_GPIOB_CLK_ENABLE();
//按键B中断配置
gpio_initstruct.Pin = GPIO_PIN_12; // 按键B对应的引脚
gpio_initstruct.Mode = GPIO_MODE_IT_RISING; // 上升沿触发
gpio_initstruct.Pull = GPIO_PULLDOWN; // 下拉
HAL_GPIO_Init(GPIOA, &gpio_initstruct);
HAL_NVIC_SetPriority(EXTI15_10_IRQn, 2, 0); // 设置EXTI15_10中断线的优先级
HAL_NVIC_EnableIRQ(EXTI15_10_IRQn); // 使能中断
//按键A中断配置
gpio_initstruct.Pin = GPIO_PIN_5; // 按键A对应的引脚
gpio_initstruct.Mode = GPIO_MODE_IT_RISING; // 上升沿触发
gpio_initstruct.Pull = GPIO_PULLDOWN; // 下拉
HAL_GPIO_Init(GPIOB, &gpio_initstruct);
HAL_NVIC_SetPriority(EXTI9_5_IRQn, 2, 0); // 设置EXTI9_5中断线的优先级
HAL_NVIC_EnableIRQ(EXTI9_5_IRQn); // 使能中断
//震动传感器中断配置
gpio_initstruct.Pin = GPIO_PIN_4; // 震动传感器对应的引脚
gpio_initstruct.Mode = GPIO_MODE_IT_FALLING; // 下降沿触发
gpio_initstruct.Pull = GPIO_PULLUP; // 上拉
HAL_GPIO_Init(GPIOA, &gpio_initstruct);
HAL_NVIC_SetPriority(EXTI4_IRQn, 2, 0); // 设置EXTI0中断线的优先级
HAL_NVIC_EnableIRQ(EXTI4_IRQn); // 使能中断
}
void EXTI15_10_IRQHandler(void)
{
HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_12);
}
void EXTI9_5_IRQHandler(void)
{
HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_5);
}
void EXTI4_IRQHandler(void)
{
HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_4);
}
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
//delay_ms(20);
if (GPIO_Pin == GPIO_PIN_12) //检测到B键按下
{
if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_12) == GPIO_PIN_SET)
//led2_toggle();
buttonB_flag = TRUE;
}
else if (GPIO_Pin == GPIO_PIN_5) //检测到A键按下
{
if(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_5) == GPIO_PIN_SET)
//led1_toggle();
buttonA_flag = TRUE;
}
else if (GPIO_Pin == GPIO_PIN_4) //检测到震动
{
if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_4) == GPIO_PIN_RESET)
//led1_toggle();
vibrate_flag = TRUE;
}
}
uint8_t buttonA_flag_get(void)
{
uint8_t temp = buttonA_flag;
buttonA_flag = FALSE;
return temp;
}
void buttonA_flag_set(uint8_t value)
{
buttonA_flag = value;
}
uint8_t buttonB_flag_get(void)
{
uint8_t temp = buttonB_flag;
buttonB_flag = FALSE;
return temp;
}
void buttonB_flag_set(uint8_t value)
{
buttonB_flag = value;
}
uint8_t vibrate_flag_get(void)
{
uint8_t temp = vibrate_flag;
vibrate_flag = FALSE;
return temp;
}
void vibrate_flag_set(uint8_t value)
{
vibrate_flag = value;
}
alarm.c
cpp
#include "alarm.h"
#include "sys.h"
//初始化GPIO函数
void alarm_init(void)
{
GPIO_InitTypeDef gpio_initstruct;
//打开时钟
__HAL_RCC_GPIOB_CLK_ENABLE(); // 使能GPIOB时钟
//调用GPIO初始化函数
gpio_initstruct.Pin = GPIO_PIN_7; // 继电器对应的引脚
gpio_initstruct.Mode = GPIO_MODE_OUTPUT_PP; // 推挽输出
gpio_initstruct.Pull = GPIO_PULLUP; // 上拉
gpio_initstruct.Speed = GPIO_SPEED_FREQ_HIGH; // 高速
HAL_GPIO_Init(GPIOB, &gpio_initstruct);
//关闭LED
alarm_off();
}
//闭合继电器的函数
void alarm_on(void)
{
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_7, GPIO_PIN_RESET); // 拉低LED1引脚,点亮LED1
}
//松开继电器的函数
void alarm_off(void)
{
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_7, GPIO_PIN_SET); // 拉高LED1引脚,熄灭LED1
}
//获取继电器状态的函数
uint8_t alarm_status_get(void)
{
return (uint8_t)HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_7);
}
main.c
cpp
#include "sys.h"
#include "delay.h"
#include "led.h"
#include "exti.h"
#include "alarm.h"
int main(void)
{
HAL_Init(); /* 初始化HAL库 */
stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟, 72Mhz */
led_init(); /* 初始化LED灯 */
exti_init(); /* 初始化EXTI */
alarm_init(); /* 初始化继电器 */
uint8_t alert_mode = FALSE; //警戒模式标志
while(1)
{
//A按键是否按下?
if(buttonA_flag_get() == TRUE)
{
alarm_on();
delay_ms(2000);
alarm_off();
alert_mode = TRUE;
}
//B按键是否按下?
if(buttonB_flag_get() == TRUE)
{
if(alarm_status_get() == ALARM_STATUS_ON)
alarm_off();
else
{
alarm_on();
delay_ms(1000);
alarm_off();
}
alert_mode = FALSE;
}
//如果处于警戒模式
if(alert_mode == TRUE)
{
if(vibrate_flag_get() == TRUE)
alarm_on();
}
else
vibrate_flag_set(FALSE);
}
}
------------下一篇:详细学习中断------------