目录
一、前言
项目成品图片:

哔哩哔哩视频链接:
https://www.bilibili.com/video/BV1PXBsBBEF7/?spm_id_from=333.337.search-card.all.click
(资料分享见文末)
二、项目简介
1.功能详解
基于STM32的智能宠物项圈系统
功能如下:
- 环境采集:光照强度传感器采集环境光照强度、心率血氧传感器检测宠物的心率血氧数据、温度传感器检测宠物的体温数据、GPS模块检测宠物的位置信息
- 显示功能:环境数据显示在OLED屏幕上
- 模式切换:通过按键可以切换手动模式和自动模式
- 自动模式:光照小于阈值开启LED照明灯、宠物心率/血氧/体温超过阈值时蜂鸣器报警提醒;单片机与手机蓝牙第一次连接后断开连接(单片机端检测不到APP循环发送的信息)时语音播报"小宠物,快回到主人身边"
- 手动模式:手动模式下可通过按键控制LED照明灯和蜂鸣器报警
- 阈值调节:可以通过按键进入系统设置界面,可设置心率、血氧和体温的光照的阈值
- 蓝牙APP:通过蓝牙APP可接收宠物心率、血氧、体温、环境光照强度和GPS经纬度信息数据;可以通过按钮完成控制指令下发
2.主要器件
- STM32F103C8T6最小系统板
- OLED显示屏(4针IIC协议)
- MAX30102心率血氧传感器
- DS18B20温度传感器
- GPS传感器
- BT04A(蓝牙模块)
- JR6001语音模块
- 蜂鸣器
- LED灯
三、原理图设计

四、PCB硬件设计
PCB图


五、程序设计
cpp
#include "stm32f10x.h"
#include "led.h" // LED驱动头文件
#include "beep.h" // 蜂鸣器驱动头文件
#include "usart.h" // 串口1(调试/打印)驱动头文件
#include "delay.h" // 延时函数头文件
#include "oled.h" // OLED显示屏驱动头文件
#include "key.h" // 按键驱动头文件
#include "Modules.h" // 模块相关结构体定义头文件
#include "adcx.h" // ADC采集(光照等)头文件
#include "flash.h" // FLASH存储(阈值/时间)头文件
#include "usart2.h" // 串口2(蓝牙通信)驱动头文件
#include "usart3.h" // 串口3(语音模块)驱动头文件
#include "TIM2.h" // 定时器2(定时中断)驱动头文件
#include "timer.h" // 定时器相关头文件
#include "GPS.h" // GPS模块(定位/经纬度)驱动头文件
#include "ds18b20.h"
#include "max30102_read.h"
#include "myiic.h"
/****************异方辰电子工作室******************
STM32
*文件 : STM32智能宠物项圈
*版本 : V2.0
*日期 : 2025.12.02
*MCU : STM32F103C8T6
*接口 : 见代码
*BILIBILI : 异方辰电子
*小红书 : 异方辰电子
*CSDN : 异方辰电子
*授权IP : 辰哥单片机设计、异方辰、YFC电子、北海单片机设计
**********************BEGIN***********************/
// 蓝牙状态枚举(对应BT_STATE_xxx)
#define BT_STATE_INIT 0 // 初始化
#define BT_STATE_CONNECTED 1 // 已连接
#define BT_STATE_DISCONNECTED 2 // 已断开
// 蓝牙管理器结构体(对应BT_Manager)
typedef struct {
uint32_t last_ack_time; // 最后一次收到心跳确认的时间
uint32_t last_disconnect_time; // 最后一次断开的时间
uint8_t state; // 蓝牙状态(BT_STATE_xxx)
uint8_t timeout_cnt; // 超时计数
uint32_t last_heartbeat_time; // 最后一次发送心跳的时间
} BT_Manager;
#define KEY_Long1 11
#define KEY_1 1
#define KEY_2 2
#define KEY_3 3
#define KEY_4 4
#define FLASH_START_ADDR 0x0801f000 //写入的起始地址
//#define RESET_FLAG_UNDONE 0x00 // 未执行过复位
//#define RESET_FLAG_DONE 0xAA // 已执行过复位
/**************** 纯软件蓝牙检测宏定义 ****************/
#define HEARTBEAT_CMD "BT_HEART\r\n"
#define HEARTBEAT_ACK "1"
#define HEARTBEAT_INTERVAL 1000
#define BT_TIMEOUT_MS 10000 // 断开超时阈值(10秒)
SensorModules sensorData;
SensorThresholdValue Sensorthreshold;
DriveModules driveData;
uint8_t mode = 1; //系统模式 1自动 2手动 3设置
u8 dakai;
u8 Flag_dakai;
uint8_t is_secondary_menu = 0;
uint8_t secondary_pos = 1;
uint8_t secondary_type = 0;
extern float gps_lat_decimal;
extern float gps_lon_decimal;
static uint8_t count_m = 1;
static uint8_t count_s = 1;
uint8_t auto_page = 1;
extern unsigned char p[16];
//static uint32_t gps_last_valid_time = 0;
//static uint8_t gps_signal_lost_counter = 0;
BT_Manager bt_manager;
//static uint8_t bt_has_connected = 0; // 是否曾经真正连接成功(收到过心跳)
static uint8_t bt_first_connect_disconnect = 0; // 是否经历「首次连接→断开」
static uint8_t bt_has_received_ack = 0; // 是否真正收到过心跳确认包(关键新增)
static uint8_t first_enter_auto_page2 = 0;
enum
{
AUTO_MODE = 1,
MANUAL_MODE,
SETTINGS_MODE
} MODE_PAGES;
/**
* @brief 主函数(程序入口)
* @param 无
* @retval int:返回值(实际未使用)
* @note 1. 初始化所有硬件模块
* 2. 读取FLASH保存的参数(阈值、时间)
* 3. 主循环:处理按键、模式切换、传感器数据、设备控制、显示刷新
*/
int main(void)
{
SystemInit();// 配置系统时钟为72MHz
delay_init(72); // 延时函数初始化(基于72MHz系统时钟)
TIM2_Init(72-1, 1000-1);
ADCX_Init(); // ADC初始化(用于光照强度采集)
LED_Init(); // LED初始化(GPIO配置)
BEEP_Init(); // 蜂鸣器初始化(GPIO配置)
uart_init(9600); // 串口1初始化(波特率9600,用于调试打印)
USART2_Init(); // 串口2初始化(波特率9600,用于蓝牙通信)
USART3_Config(); // 串口3初始化(用于语音模块通信)
Key_Init(); // 按键初始化(GPIO配置,中断/查询模式)
DS18B20_Init(); // 初始化DS18B20温度传感器
OLED_Init(); // OLED初始化(I2C/SPI配置)
GPS_Init(); // GPS初始化(缓冲区清空、状态标志重置)
Init_MAX30102();
srand((unsigned int)delay_get_tick());
// 初始化蓝牙管理器
uint32_t current_time = delay_get_tick();
bt_manager.last_ack_time = current_time;
bt_manager.last_disconnect_time = current_time;
bt_manager.state = BT_STATE_INIT; // 初始化为未初始化
bt_manager.timeout_cnt = 0;
bt_manager.last_heartbeat_time = 0;
// 清空所有标志(关键)
bt_first_connect_disconnect = 0;
bt_has_received_ack = 0; // 初始未收到任何心跳
delay_ms(100);
FLASH_ReadThreshold();
OLED_Clear();
// 状态管理静态变量
static uint8_t last_mode = 0;
static uint32_t last_sensor_time = 0;
static uint32_t last_display_time = 0;
// 参数有效性检查
if (Sensorthreshold.tempValue > 40 || Sensorthreshold.hrAvgValue > 120 ||
Sensorthreshold.spo2AvgValue > 100 || Sensorthreshold.luxValue > 500)
{
FLASH_W(FLASH_START_ADDR, 30, 95, 98, 100);
FLASH_ReadThreshold();
}
printf("系统启动,蓝牙初始状态: 初始化\n");
USART3_SendString("AF:30");
delay_ms(200);
USART3_SendString("A7:00001");
delay_ms(200);
while (1)
{
// static uint32_t last_gps_time = 0;
uint32_t current_time = delay_get_tick();
// ==================== 优先处理蓝牙通信 ====================
USART2_ProcessCmd(); // 处理串口命令
BT_Check_State_Simple(); // 检查蓝牙状态
// ==================== 传感器扫描 ====================
if(current_time - last_sensor_time > 100) {
SensorScan();
last_sensor_time = current_time;
}
// if (gps_data.is_data_ready) {
// GPS_ParseNMEA();
// }
// 如果已解析,显示数据
// if (gps_data.is_parsed) {
// GPS_DisplayAndSend();
// }
//
// last_gps_time = current_time;
// }
// ==================== 立即处理按键 ====================
uint8_t current_key_num = KeyNum; // 保存当前按键值(避免按键标志被多次处理)
// 模式切换按键立即处理(KEY1=模式切换,KEY_Long1=自动→设置)
if(current_key_num != 0)
{
switch(mode)
{
case AUTO_MODE: // 当前是自动模式
if(current_key_num == KEY_1) // KEY1=自动→手动
{
mode = MANUAL_MODE;
count_m = 1; // 手动模式默认光标指向灯光
driveData.LED_Flag = 0; // 切换时关闭LED
driveData.BEEP_Flag = 0; // 切换时关闭蜂鸣器
KeyNum = 0; // 清除按键标志
}
else if(current_key_num == KEY_Long1) // 长按KEY1=自动→设置
{
mode = SETTINGS_MODE;
count_s = 1; // 设置模式默认光标指向时间
KeyNum = 0; // 清除按键标志
}
break;
case MANUAL_MODE: // 当前是手动模式
if(current_key_num == KEY_1) // KEY1=手动→自动
{
mode = AUTO_MODE;
auto_page = 1; // 切回自动模式第一页
KeyNum = 0; // 清除按键标志
}
break;
case SETTINGS_MODE: // 当前是设置模式(按键在模式内部处理)
break;
}
}
// 模式切换检测:若当前模式与上一次不同,清屏并绘制新模式固定内容
if(last_mode != mode)
{
OLED_Clear(); // 清屏(避免模式间内容重叠)
last_mode = mode; // 更新上一次模式
// 绘制新模式的固定内容
switch(mode)
{
case AUTO_MODE:
OLED_autoPage1(); // 自动模式默认第一页
break;
case MANUAL_MODE:
OLED_manualPage1(); // 手动模式页面
break;
case SETTINGS_MODE:
OLED_settingsPage1(); // 设置模式第一页
break;
}
OLED_Refresh(); // 立即刷新显示
}
// 按当前模式执行对应逻辑
switch(mode)
{
case AUTO_MODE: // 自动模式
{
// 获取当前自动模式页面(处理KEY2切换)
uint8_t curr_auto_page = SetAuto();
if(curr_auto_page == 1)
{
SensorDataDisplay1(); // 第一页:显示传感器数据+蓝牙发送
}
else
{
SensorDataDisplay2(); // 第二页:显示GPS数据+蓝牙发送
}
AutoControl(); // 自动控制逻辑(LED/蜂鸣器)
Control_Manager(); // 执行设备控制(LED/蜂鸣器开关)
break;
}
case MANUAL_MODE: // 手动模式
{
// 手动模式状态管理静态变量
static uint8_t manual_page_initialized = 0; // 页面初始化标志
static uint8_t last_manual_count = 0; // 上一次控制项计数
static uint8_t last_LED_Flag = 0; // 上一次LED状态
static uint8_t last_BEEP_Flag = 0; // 上一次蜂鸣器状态
static uint8_t force_refresh = 0; // 强制刷新标志
// 模式切换时初始化状态
if(last_mode != mode)
{
manual_page_initialized = 0;
last_manual_count = 0;
last_LED_Flag = driveData.LED_Flag;
last_BEEP_Flag = driveData.BEEP_Flag;
force_refresh = 1; // 强制刷新显示
count_m = 1; // 光标默认指向灯光
driveData.LED_Flag = 0; // 初始关闭LED
driveData.BEEP_Flag = 0; // 初始关闭蜂鸣器
}
// 获取当前控制项(处理KEY2切换)
uint8_t current_manual_count = SetManual();
// 检测设备状态是否变化(变化则需要刷新显示)
uint8_t need_refresh = 0;
if(driveData.LED_Flag != last_LED_Flag || driveData.BEEP_Flag != last_BEEP_Flag)
{
need_refresh = 1;
last_LED_Flag = driveData.LED_Flag;
last_BEEP_Flag = driveData.BEEP_Flag;
}
// 页面未初始化、控制项变化、设备状态变化或强制刷新时,重新绘制页面
if(!manual_page_initialized || current_manual_count != last_manual_count || need_refresh || force_refresh)
{
OLED_manualPage1(); // 绘制固定内容(灯光、蜂鸣器名称)
OLED_manualOption(current_manual_count); // 绘制光标
ManualSettingsDisplay1(); // 绘制设备状态(开/关)
manual_page_initialized = 1; // 标记页面已初始化
last_manual_count = current_manual_count; // 更新控制项计数
force_refresh = 0; // 清除强制刷新标志
OLED_Refresh(); // 刷新显示
}
// 处理手动模式按键(KEY3/4控制设备开关)
if(current_key_num != 0)
{
ManualControl(current_manual_count);
OLED_manualPage1(); // 重新绘制固定内容
OLED_manualOption(current_manual_count); // 绘制光标
ManualSettingsDisplay1(); // 绘制设备状态
OLED_Refresh(); // 按键后立即刷新显示
KeyNum = 0; // 清除按键标志
}
// 确保显示内容始终正确(确保切到手动显示光标34)
OLED_manualPage1(); // 固定文字
OLED_manualOption(current_manual_count); // 光标
ManualSettingsDisplay1(); // 状态
Control_Manager(); // 执行设备控制(LED/蜂鸣器开关)
break;
}
case SETTINGS_MODE:
{
// 优化设置模式响应速度
static uint8_t is_threshold_page_inited = 0;
uint8_t curr_count_s = SetSelection();
// 立即处理设置模式内的按键
if(current_key_num != 0)
{
if (is_secondary_menu == 1)
{
// 二级菜单按键立即处理
if (current_key_num == KEY_2 || current_key_num == KEY_3 || current_key_num == KEY_4)
{
// 这里根据你的二级菜单逻辑处理
// 处理完后立即刷新
OLED_Refresh();
KeyNum = 0;
}
else if (current_key_num == KEY_1)
{
is_secondary_menu = 0;
secondary_pos = 1;
OLED_Clear();
OLED_settingsPage1();
SettingsThresholdDisplay1();
OLED_settingsOption(curr_count_s);
OLED_Refresh();
KeyNum = 0;
}
}
else
{
// 一级菜单按键立即处理
if (current_key_num == KEY_3 || current_key_num == KEY_4)
{
ThresholdSettings(curr_count_s);
SettingsThresholdDisplay1();
OLED_Refresh();
KeyNum = 0;
}
else if (current_key_num == KEY_1)
{
mode = AUTO_MODE;
is_threshold_page_inited = 0;
FLASH_W(FLASH_START_ADDR, Sensorthreshold.tempValue, Sensorthreshold.hrAvgValue,
Sensorthreshold.spo2AvgValue, Sensorthreshold.luxValue);
KeyNum = 0;
}
}
}
// 正常显示逻辑
if (is_secondary_menu == 1)
{
// 二级菜单显示逻辑(根据你的代码)
}
else
{
// 一级菜单显示
if (curr_count_s >= 1 && curr_count_s <= 4)
{
if (is_threshold_page_inited == 0)
{
OLED_settingsPage1();
is_threshold_page_inited = 1;
}
}
OLED_settingsOption(curr_count_s);
SettingsThresholdDisplay1();
}
break;
}
}
// 显示刷新频率控制:每50ms刷新一次(避免频繁刷新占用资源)
if(current_time - last_display_time > 50) {
OLED_Refresh();
last_display_time = current_time;
}
}
}
六、实验效果

七、包含内容
