STM32实战:基于STM32F103的智能鱼缸自动投喂与换水系统

文章目录

    • 一、前言
      • [1.1 技术背景](#1.1 技术背景)
      • [1.2 应用场景](#1.2 应用场景)
      • [1.3 本文目标](#1.3 本文目标)
    • 二、系统架构设计
      • [2.1 硬件系统架构](#2.1 硬件系统架构)
      • [2.2 投喂器设计](#2.2 投喂器设计)
      • [2.3 换水系统设计](#2.3 换水系统设计)
      • [2.4 项目文件结构](#2.4 项目文件结构)
    • 三、环境准备
      • [3.1 硬件准备](#3.1 硬件准备)
      • [3.2 软件准备](#3.2 软件准备)
    • 四、核心模块实现
      • [4.1 舵机驱动](#4.1 舵机驱动)
        • [4.1.1 舵机驱动头文件](#4.1.1 舵机驱动头文件)
        • [4.1.2 舵机驱动实现](#4.1.2 舵机驱动实现)
      • [4.2 投喂控制器](#4.2 投喂控制器)
        • [4.2.1 投喂控制头文件](#4.2.1 投喂控制头文件)
        • [4.2.2 投喂控制实现](#4.2.2 投喂控制实现)
      • [4.3 换水控制器](#4.3 换水控制器)
        • [4.3.1 换水控制头文件](#4.3.1 换水控制头文件)
    • 五、故障排查与问题解决
    • 六、总结
      • [6.1 核心知识点回顾](#6.1 核心知识点回顾)
      • [6.2 性能指标](#6.2 性能指标)
      • [6.3 扩展方向](#6.3 扩展方向)

一、前言

1.1 技术背景

养鱼是许多人的爱好,但日常维护工作繁琐,尤其是定时投喂和定期换水。对于经常出差或工作繁忙的人来说,很难保证鱼缸的日常护理。智能鱼缸系统通过自动化技术,实现定时投喂、自动换水、水质监测等功能,让养鱼变得更加轻松。

自动投喂系统需要精确控制投喂量和投喂时间,避免过量投喂导致水质恶化。自动换水系统则需要精确控制换水量,保持水质稳定的同时避免对鱼类造成应激。

STM32F103作为主控芯片,配合RTC实时时钟、舵机控制、水位传感器、水泵控制等模块,可以构建一个功能完善的智能鱼缸管理系统。

1.2 应用场景

智能鱼缸系统的应用场景包括:

  • 家庭鱼缸:自动维护,减少日常工作量
  • 办公室鱼缸:无人值守自动运行
  • 水族店:批量管理多个鱼缸
  • 实验室:精确控制实验环境
  • 水产养殖:小规模自动化养殖

1.3 本文目标

通过本教程,你将学到:

  • RTC实时时钟的时间管理与定时任务
  • 舵机控制原理与精确角度控制
  • 水位检测与水泵控制
  • 水质监测(温度、pH值)
  • 定时器中断与任务调度
  • OLED中文显示与菜单设计
  • 多任务协调与状态机设计
  • 数据存储与历史记录
  • 异常处理与安全保护

完成本教程后,你将能够独立开发一个具备以下功能的智能鱼缸系统:

  • 每天最多8组定时投喂
  • 每次投喂量可调(1-10档)
  • 自动换水(单次换水量可调)
  • 水温实时监测
  • 水位监测与缺水保护
  • OLED显示当前状态和下次任务
  • 手动投喂/换水按钮
  • 数据断电保存

技术栈:

  • 主控芯片:STM32F103C8T6(Cortex-M3,72MHz)
  • 实时时钟:STM32内置RTC + 32.768kHz晶振
  • 投喂模块:SG90舵机 + 自制投喂器
  • 换水系统:12V水泵 + 水位传感器
  • 温度监测:DS18B20数字温度传感器
  • 水位检测:浮球开关/压力传感器
  • 显示模块:0.96寸OLED(I2C接口)
  • 存储芯片:AT24C02(EEPROM)
  • 输入设备:4个按键 + 1个急停按钮
  • 电源:5V/2A + 12V/2A双电源

二、系统架构设计

2.1 硬件系统架构

人机交互
环境监测
自动换水系统
自动投喂系统
主控处理单元
时间管理单元
RTC实时时钟

定时任务调度
多组闹钟

最多8组
STM32F103C8T6

Cortex-M3 72MHz
状态机

任务协调
安全保护

异常处理
SG90舵机

PWM控制
投喂器

旋转下料
投喂量控制

角度+时间
排水泵

12V水泵
进水泵

12V水泵
水位传感器

浮球开关
流量计

可选
DS18B20

水温监测
pH传感器

可选
OLED显示屏

状态+菜单
按键

手动控制
急停按钮

安全保护

2.2 投喂器设计

投喂器工作原理:

  1. 储料仓:存放鱼食,底部有出料口
  2. 旋转挡板:舵机带动挡板旋转,控制出料
  3. 角度控制
    • 0度:关闭(不出料)
    • 45度:小量出料
    • 90度:大量出料
  4. 时间控制:旋转持续时间决定投喂量

投喂量档位:

档位 舵机角度 持续时间 适用场景
1 30° 0.5s 少量鱼/小鱼
3 45° 1.0s 正常投喂
5 60° 1.5s 较多鱼
8 75° 2.0s 大量鱼
10 90° 3.0s 特殊需求

2.3 换水系统设计

换水流程:

  1. 排水阶段:启动排水泵,排出旧水
  2. 检测阶段:监测水位,达到设定值停止排水
  3. 静置阶段:等待几分钟,让水质稳定
  4. 进水阶段:启动进水泵,注入新水
  5. 完成阶段:达到目标水位,停止进水

安全保护:

  • 最低水位保护:水位过低时禁止排水
  • 最高水位保护:水位过高时禁止进水
  • 超时保护:单步操作超时自动停止
  • 急停按钮:紧急情况下立即停止所有操作

2.4 项目文件结构

📄 项目文件清单

复制代码
SmartFishTank/
├── Core/
│   ├── Inc/
│   │   ├── main.h
│   │   ├── rtc_driver.h
│   │   ├── servo_driver.h
│   │   ├── feeder.h
│   │   ├── water_change.h
│   │   ├── ds18b20_driver.h
│   │   ├── level_sensor.h
│   │   ├── pump_driver.h
│   │   ├── oled_driver.h
│   │   ├── key_driver.h
│   │   ├── eeprom_driver.h
│   │   ├── ui_manager.h
│   │   └── font.h
│   └── Src/
│       ├── main.c
│       ├── rtc_driver.c
│       ├── servo_driver.c
│       ├── feeder.c
│       ├── water_change.c
│       ├── ds18b20_driver.c
│       ├── level_sensor.c
│       ├── pump_driver.c
│       ├── oled_driver.c
│       ├── key_driver.c
│       ├── eeprom_driver.c
│       ├── ui_manager.c
│       └── font.c
├── Drivers/
│   ├── STM32F1xx_HAL_Driver/
│   └── CMSIS/
└── README.md

三、环境准备

3.1 硬件准备

必需硬件清单:

序号 器件名称 型号/规格 数量 备注
1 STM32最小系统板 STM32F103C8T6 1 核心控制器
2 RTC晶振 32.768kHz 1 备用电池供电
3 SG90舵机 180度 1 投喂控制
4 水泵 12V 扬程2m 2 进/排水
5 水位传感器 浮球开关 2 高/低水位
6 DS18B20 防水型 1 水温监测
7 OLED显示屏 0.96寸 I2C 1 信息显示
8 EEPROM芯片 AT24C02 1 数据存储
9 继电器模块 5V低电平触发 2 控制水泵
10 按键 轻触开关 4 用户输入
11 急停按钮 蘑菇头 1 安全保护
12 电源适配器 5V/2A 1 系统供电
13 电源适配器 12V/2A 1 水泵供电
14 杜邦线 母对母 若干 连接
15 ST-Link V2 1 调试下载

SG90舵机引脚:

线色 功能 连接
GND GND
VCC 5V
信号 PA0(TIM2_CH1)

DS18B20引脚:

引脚 功能 连接
VCC 电源 3.3V
DATA 数据 PA1(需4.7K上拉)
GND GND

3.2 软件准备

开发环境:

  • Keil MDK-ARM 5.38+ 或 STM32CubeIDE 1.12.0+
  • STM32F1xx HAL库
  • 串口调试助手

四、核心模块实现

4.1 舵机驱动

SG90舵机使用PWM信号控制角度,周期20ms,脉宽0.5ms-2.5ms对应0-180度。

4.1.1 舵机驱动头文件

📄 创建文件:Core/Inc/servo_driver.h

c 复制代码
/**
 * @file servo_driver.h
 * @brief SG90舵机驱动头文件
 */

#ifndef __SERVO_DRIVER_H
#define __SERVO_DRIVER_H

#include "stm32f1xx_hal.h"
#include <stdint.h>

/* 舵机角度范围 */
#define SERVO_MIN_ANGLE     0
#define SERVO_MAX_ANGLE     180
#define SERVO_MID_ANGLE     90

/* 函数声明 */
void Servo_Init(void);
void Servo_SetAngle(uint8_t angle);
void Servo_SetMicroseconds(uint16_t us);
void Servo_Off(void);

#endif /* __SERVO_DRIVER_H */
4.1.2 舵机驱动实现

📄 创建文件:Core/Src/servo_driver.c

c 复制代码
/**
 * @file servo_driver.c
 * @brief SG90舵机驱动实现
 */

#include "servo_driver.h"

/* PWM配置 */
#define SERVO_TIM           TIM2
#define SERVO_TIM_CHANNEL   TIM_CHANNEL_1
#define SERVO_GPIO_PORT     GPIOA
#define SERVO_GPIO_PIN      GPIO_PIN_0

/* 定时器周期:20ms (50Hz) */
/* 72MHz / 72 / 20000 = 50Hz */
#define SERVO_PWM_PERIOD    20000  /* 20ms = 20000us */

/* 脉宽范围:0.5ms - 2.5ms */
#define SERVO_MIN_PULSE     500    /* 0.5ms */
#define SERVO_MAX_PULSE     2500   /* 2.5ms */

static TIM_HandleTypeDef htim2;

void Servo_Init(void) {
    GPIO_InitTypeDef GPIO_InitStruct = {0};
    TIM_OC_InitTypeDef sConfigOC = {0};
    
    __HAL_RCC_TIM2_CLK_ENABLE();
    __HAL_RCC_GPIOA_CLK_ENABLE();
    
    /* 配置PWM输出引脚 */
    GPIO_InitStruct.Pin = SERVO_GPIO_PIN;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
    HAL_GPIO_Init(SERVO_GPIO_PORT, &GPIO_InitStruct);
    
    /* 配置定时器 */
    htim2.Instance = SERVO_TIM;
    htim2.Init.Prescaler = 72 - 1;      /* 72MHz / 72 = 1MHz */
    htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
    htim2.Init.Period = SERVO_PWM_PERIOD - 1;  /* 20ms周期 */
    htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
    HAL_TIM_PWM_Init(&htim2);
    
    /* 配置PWM通道 */
    sConfigOC.OCMode = TIM_OCMODE_PWM1;
    sConfigOC.Pulse = SERVO_MIN_PULSE;  /* 初始位置 */
    sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
    sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
    HAL_TIM_PWM_ConfigChannel(&htim2, &sConfigOC, SERVO_TIM_CHANNEL);
    
    /* 启动PWM */
    HAL_TIM_PWM_Start(&htim2, SERVO_TIM_CHANNEL);
}

void Servo_SetAngle(uint8_t angle) {
    if (angle > SERVO_MAX_ANGLE) {
        angle = SERVO_MAX_ANGLE;
    }
    
    /* 角度转脉宽 */
    uint16_t pulse = SERVO_MIN_PULSE + 
                     (angle * (SERVO_MAX_PULSE - SERVO_MIN_PULSE) / SERVO_MAX_ANGLE);
    
    __HAL_TIM_SET_COMPARE(SERVO_TIM, SERVO_TIM_CHANNEL, pulse);
}

void Servo_SetMicroseconds(uint16_t us) {
    if (us < SERVO_MIN_PULSE) us = SERVO_MIN_PULSE;
    if (us > SERVO_MAX_PULSE) us = SERVO_MAX_PULSE;
    
    __HAL_TIM_SET_COMPARE(SERVO_TIM, SERVO_TIM_CHANNEL, us);
}

void Servo_Off(void) {
    HAL_TIM_PWM_Stop(&htim2, SERVO_TIM_CHANNEL);
}

4.2 投喂控制器

4.2.1 投喂控制头文件

📄 创建文件:Core/Inc/feeder.h

c 复制代码
/**
 * @file feeder.h
 * @brief 自动投喂控制器头文件
 */

#ifndef __FEEDER_H
#define __FEEDER_H

#include <stdint.h>
#include <stdbool.h>

/* 投喂任务结构体 */
typedef struct {
    uint8_t hour;           /* 小时(0-23) */
    uint8_t minute;         /* 分钟(0-59) */
    uint8_t amount;         /* 投喂量(1-10档) */
    bool enabled;           /* 是否启用 */
} FeedingTask;

#define MAX_FEEDING_TASKS   8   /* 最大投喂任务数 */

/* 函数声明 */
void Feeder_Init(void);
int Feeder_AddTask(uint8_t hour, uint8_t minute, uint8_t amount);
int Feeder_RemoveTask(uint8_t index);
void Feeder_Feed(uint8_t amount);  /* 立即投喂 */
void Feeder_CheckAndFeed(void);    /* 检查定时任务 */
FeedingTask* Feeder_GetTasks(void);
uint8_t Feeder_GetTaskCount(void);
void Feeder_SaveTasks(void);
void Feeder_LoadTasks(void);

#endif /* __FEEDER_H */
4.2.2 投喂控制实现

📄 创建文件:Core/Src/feeder.c

c 复制代码
/**
 * @file feeder.c
 * @brief 自动投喂控制器实现
 */

#include "feeder.h"
#include "servo_driver.h"
#include "rtc_driver.h"
#include "eeprom_driver.h"
#include <string.h>

#define FEEDER_EEPROM_ADDR  0x20
#define FEEDER_MAGIC        0xFE01

/* 投喂参数表 */
static const struct {
    uint8_t angle;
    uint16_t durationMs;
} feedParams[11] = {
    {0, 0},      /* 0档:关闭 */
    {30, 500},   /* 1档 */
    {35, 600},   /* 2档 */
    {45, 800},   /* 3档 */
    {50, 1000},  /* 4档 */
    {60, 1200},  /* 5档 */
    {65, 1400},  /* 6档 */
    {70, 1600},  /* 7档 */
    {75, 2000},  /* 8档 */
    {80, 2500},  /* 9档 */
    {90, 3000}   /* 10档 */
};

static FeedingTask feedingTasks[MAX_FEEDING_TASKS];
static uint8_t taskCount = 0;
static uint32_t lastCheckTime = 0;

void Feeder_Init(void) {
    Servo_Init();
    Feeder_LoadTasks();
}

int Feeder_AddTask(uint8_t hour, uint8_t minute, uint8_t amount) {
    if (taskCount >= MAX_FEEDING_TASKS) {
        return -1;  /* 任务已满 */
    }
    
    if (hour > 23 || minute > 59 || amount < 1 || amount > 10) {
        return -1;  /* 参数错误 */
    }
    
    /* 检查是否重复 */
    for (int i = 0; i < taskCount; i++) {
        if (feedingTasks[i].hour == hour && 
            feedingTasks[i].minute == minute) {
            return -1;  /* 时间冲突 */
        }
    }
    
    feedingTasks[taskCount].hour = hour;
    feedingTasks[taskCount].minute = minute;
    feedingTasks[taskCount].amount = amount;
    feedingTasks[taskCount].enabled = true;
    taskCount++;
    
    Feeder_SaveTasks();
    return 0;
}

int Feeder_RemoveTask(uint8_t index) {
    if (index >= taskCount) {
        return -1;
    }
    
    /* 移动后续任务 */
    for (int i = index; i < taskCount - 1; i++) {
        feedingTasks[i] = feedingTasks[i + 1];
    }
    taskCount--;
    
    Feeder_SaveTasks();
    return 0;
}

void Feeder_Feed(uint8_t amount) {
    if (amount < 1 || amount > 10) {
        return;
    }
    
    uint8_t angle = feedParams[amount].angle;
    uint16_t duration = feedParams[amount].durationMs;
    
    /* 打开投喂口 */
    Servo_SetAngle(angle);
    HAL_Delay(duration);
    
    /* 关闭投喂口 */
    Servo_SetAngle(0);
    HAL_Delay(500);
}

void Feeder_CheckAndFeed(void) {
    RTC_TimeTypeDef currentTime;
    
    /* 每分钟检查一次 */
    uint32_t currentTick = HAL_GetTick();
    if (currentTick - lastCheckTime < 60000) {
        return;
    }
    lastCheckTime = currentTick;
    
    /* 获取当前时间 */
    if (RTC_GetTime(&currentTime) != HAL_OK) {
        return;
    }
    
    /* 检查所有任务 */
    for (int i = 0; i < taskCount; i++) {
        if (!feedingTasks[i].enabled) {
            continue;
        }
        
        if (feedingTasks[i].hour == currentTime.hours &&
            feedingTasks[i].minute == currentTime.minutes) {
            /* 执行投喂 */
            Feeder_Feed(feedingTasks[i].amount);
        }
    }
}

FeedingTask* Feeder_GetTasks(void) {
    return feedingTasks;
}

uint8_t Feeder_GetTaskCount(void) {
    return taskCount;
}

void Feeder_SaveTasks(void) {
    uint8_t buffer[sizeof(feedingTasks) + 3];
    buffer[0] = (FEEDER_MAGIC >> 8) & 0xFF;
    buffer[1] = FEEDER_MAGIC & 0xFF;
    buffer[2] = taskCount;
    memcpy(&buffer[3], feedingTasks, sizeof(feedingTasks));
    EEPROM_Write(FEEDER_EEPROM_ADDR, buffer, sizeof(buffer));
}

void Feeder_LoadTasks(void) {
    uint8_t buffer[sizeof(feedingTasks) + 3];
    EEPROM_Read(FEEDER_EEPROM_ADDR, buffer, sizeof(buffer));
    
    uint16_t magic = (buffer[0] << 8) | buffer[1];
    if (magic == FEEDER_MAGIC) {
        taskCount = buffer[2];
        if (taskCount > MAX_FEEDING_TASKS) {
            taskCount = 0;
        }
        memcpy(feedingTasks, &buffer[3], sizeof(feedingTasks));
    }
}

4.3 换水控制器

4.3.1 换水控制头文件

📄 创建文件:Core/Inc/water_change.h

c 复制代码
/**
 * @file water_change.h
 * @brief 自动换水控制器头文件
 */

#ifndef __WATER_CHANGE_H
#define __WATER_CHANGE_H

#include <stdint.h>
#include <stdbool.h>

typedef enum {
    WC_STATE_IDLE,          /* 待机 */
    WC_STATE_DRAINING,      /* 排水中 */
    WC_STATE_WAITING,       /* 静置中 */
    WC_STATE_FILLING,       /* 进水中 */
    WC_STATE_COMPLETED      /* 完成 */
} WaterChange_State;

/* 换水配置 */
typedef struct {
    uint8_t drainPercent;   /* 排水百分比(10-50%) */
    uint16_t waitTime;      /* 静置时间(秒) */
    bool autoMode;          /* 自动模式 */
    uint8_t scheduleDay;    /* 自动换水周期(天) */
} WaterChange_Config;

/* 函数声明 */
void WaterChange_Init(void);
void WaterChange_Start(uint8_t drainPercent);
void WaterChange_Stop(void);
void WaterChange_Process(void);
WaterChange_State WaterChange_GetState(void);
uint8_t WaterChange_GetProgress(void);  /* 进度0-100% */
void WaterChange_SetConfig(WaterChange_Config* config);
WaterChange_Config* WaterChange_GetConfig(void);

#endif /* __WATER_CHANGE_H */

五、故障排查与问题解决

5.1 舵机抖动或失控

问题1:舵机角度不准确或抖动

现象: 舵机无法到达指定角度,或持续抖动

原因分析:

  • 电源功率不足(SG90需要约100mA电流)
  • PWM信号受干扰
  • 舵机损坏
  • 机械阻力过大

解决方案:

  1. 独立供电

    5V电源 ---+--- 舵机VCC
    |
    电容(470μF)
    |
    GND

  2. 信号隔离

c 复制代码
/* 舵机信号线与电源线分开走线 */
/* 避免与电机线平行 */

5.2 水泵不工作

问题2:继电器吸合但水泵不转

现象: 听到继电器吸合声,但水泵不工作

原因分析:

  • 继电器触点接触不良
  • 水泵电源未接通
  • 水泵空转保护
  • 水泵损坏

解决方案:

  1. 检查继电器驱动
c 复制代码
/* 确保GPIO输出正确电平 */
HAL_GPIO_WritePin(PUMP_PORT, PUMP_PIN, GPIO_PIN_SET);   /* 吸合 */
HAL_GPIO_WritePin(PUMP_PORT, PUMP_PIN, GPIO_PIN_RESET); /* 释放 */
  1. 添加续流二极管

    继电器线圈 ---+--- 二极管(1N4007) ---+
    | |
    VCC GND


六、总结

6.1 核心知识点回顾

  1. RTC定时任务:实现了多组定时投喂任务的管理与执行。

  2. 舵机控制:掌握了PWM角度控制,实现了精确的投喂量控制。

  3. 水泵控制:实现了进排水泵的安全控制与水位监测。

  4. 状态机设计:使用状态机管理换水流程,确保操作安全。

  5. 安全保护:实现了多层次的异常检测与保护机制。

6.2 性能指标

指标 数值
定时精度 ±1分钟
投喂量精度 ±10%
换水精度 ±5%
温度精度 ±0.5℃
响应时间 <1秒

6.3 扩展方向

  • WiFi模块:添加ESP8266,实现远程控制
  • 手机APP:开发配套APP,查看状态和控制
  • 摄像头:添加摄像头,实时查看鱼缸
  • 自动喂食:添加自动喂食器,支持多种饲料
  • 水质监测:添加pH、氨氮等传感器
相关推荐
进击的小头2 小时前
第3篇:嵌入式芯片核心架构基础:冯·诺依曼架构与哈佛架构的本质差异与场景适配
单片机·嵌入式硬件·架构
UTP协同自动化测试2 小时前
用UTP标准版搭建物联网模组交联测试环境:APP + UART + I2C + GPIO + PWM
嵌入式硬件·物联网·测试工具
要退休的攻城狮2 小时前
跳到千问挖的坑里去了
c++·人工智能·嵌入式硬件·visualstudio
liuluyang5302 小时前
DW_apb_uart 16650 寄存器详解
单片机·嵌入式硬件·uart·基础外设
Wave8452 小时前
STM32低功耗模式
stm32·单片机·嵌入式硬件
Linux猿2 小时前
基于单片机的智能路灯控制系统设计 | 附源码
单片机·嵌入式硬件·课程设计·项目·系统设计·基于单片机的智能路灯控制系统
kaikaile19953 小时前
STM32 USB批量传输CDC类实现指南
stm32·单片机·嵌入式硬件
mftang3 小时前
nRF52805 时钟配置功能详细介绍
单片机·嵌入式硬件
Heartache boy3 小时前
野火STM32_HAL库版课程笔记-TB6612FNG驱动有刷电机
笔记·stm32·单片机