STM32项目分享:智能宠物项圈系统

目录

一、前言

二、项目简介

1.功能详解

2.主要器件

三、原理图设计

四、PCB硬件设计

PCB图

五、程序设计

六、实验效果

七、包含内容

项目分享


一、前言

项目成品图片:

哔哩哔哩视频链接:

https://www.bilibili.com/video/BV1PXBsBBEF7/?spm_id_from=333.337.search-card.all.click

(资料分享见文末)

二、项目简介

1.功能详解

基于STM32的智能宠物项圈系统

功能如下:

  1. 环境采集:光照强度传感器采集环境光照强度、心率血氧传感器检测宠物的心率血氧数据、温度传感器检测宠物的体温数据、GPS模块检测宠物的位置信息
  2. 显示功能:环境数据显示在OLED屏幕上
  3. 模式切换:通过按键可以切换手动模式和自动模式
  4. 自动模式:光照小于阈值开启LED照明灯、宠物心率/血氧/体温超过阈值时蜂鸣器报警提醒;单片机与手机蓝牙第一次连接后断开连接(单片机端检测不到APP循环发送的信息)时语音播报"小宠物,快回到主人身边"
  5. 手动模式:手动模式下可通过按键控制LED照明灯和蜂鸣器报警
  6. 阈值调节:可以通过按键进入系统设置界面,可设置心率、血氧和体温的光照的阈值
  7. 蓝牙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;
        }
    }
}

六、实验效果

七、包含内容

项目分享

相关推荐
电子科技圈2 小时前
芯科科技持续推动智能网联及边缘AI加速发展
人工智能·科技·嵌入式硬件·mcu·物联网·智能硬件·iot
qq_12498707532 小时前
基于微信小程序宠物服务系统(源码+论文+部署+安装)
java·spring boot·后端·微信小程序·小程序·毕业设计·宠物
帅次3 小时前
系统分析师-嵌入式系统分析与设计
嵌入式硬件·mcu·物联网·proteus·iot·嵌入式实时数据库·rtdbs
梁洪飞11 小时前
clk学习
linux·arm开发·嵌入式硬件·arm
eewj13 小时前
STM32中FCLK时钟信号的作用
stm32·单片机·嵌入式硬件
淘晶驰AK14 小时前
ESP32和STM32哪个更容易学?
stm32·单片机·嵌入式硬件
__万波__15 小时前
STM32L475实现精度更好的delay函数
stm32·单片机·嵌入式硬件
QK_0018 小时前
STM32-热敏传感器以及光敏传感器
stm32·单片机·嵌入式硬件
代码游侠19 小时前
复习——ARM Cortex-A 裸机开发深度解析
arm开发·笔记·嵌入式硬件·学习·架构