在嵌入式系统开发领域,蓝桥杯单片机竞赛一直是检验学生实践能力的重要舞台。第五届省赛的智能灌溉系统题目,不仅考察了对STC15F2K60S2单片机的掌握程度,更要求开发者具备传感器数据采集、实时时钟控制和人机交互设计等综合能力。本文将带你从零开始,一步步完成这个经典项目的复现。
复现智能灌溉系统需要准备以下硬件设备:
提示:购买元器件时注意电压匹配,所有外设必须兼容5V工作电压
STC15系列推荐使用Keil μVision开发环境,具体配置步骤如下:
makefile复制OPTIMIZE(3,SPEED)
CODE(0x0000-0xFFFF)
硬件连接参考表格:
| 模块 | 引脚连接 | 备注 |
|---|---|---|
| PCF8591 | SDA-P2.1, SCL-P2.0 | I2C通信接口 |
| DS1302 | RST-P1.3, SCLK-P1.7 | 三线制SPI接口 |
| 数码管段选 | P0口 | 需加上拉电阻 |
| 继电器控制 | P2.4 | 驱动电流需≥30mA |
完整的智能灌溉系统包含三个核心代码文件:
code复制SmartIrrigation/
├── main.c # 主控制逻辑
├── ds1302.c # 实时时钟驱动
├── ds1302.h # 时钟模块头文件
├── iic.c # I2C总线驱动
└── iic.h # I2C通信协议定义
主程序采用事件驱动+状态机的设计模式,关键流程如下:
c复制void main() {
Init_System(); // 硬件初始化
Delay300ms(); // 稳定等待
while(1) {
// 1. 读取实时时钟
if(TH0 < 0xf0) {
h = Read_Ds1302_Byte(0x85);
m = Read_Ds1302_Byte(0x83);
}
// 2. 定期采集湿度(每200ms)
if(count_adc > 199) {
shidu = (uchar)(read_adc(0x03)/2.57);
count_adc = 0;
}
// 3. 更新显示与状态
Display();
// 4. 按键扫描(每10ms)
if(count_key > 9) {
Scan_key();
count_key = 0;
}
}
}
湿度控制逻辑采用双模式设计:
flow复制st=>start: 开始
op1=>operation: 读取当前湿度值
cond1=>condition: 湿度 < 阈值?
op2=>operation: 启动灌溉(继电器ON)
op3=>operation: 停止灌溉(继电器OFF)
cond2=>condition: 报警模式?
op4=>operation: 触发蜂鸣器
e=>end: 返回
st->op1->cond1
cond1(yes)->cond2
cond1(no)->op3->e
cond2(yes)->op4->op2
cond2(no)->op2
阈值调节通过EEPROM保存,确保断电不丢失:
c复制void Scan_key() {
if(trg & 0x02) { // S6按键
flag_set = ~flag_set;
if(!flag_set) {
EA = 0;
write_24c02(0x00, shidu_th); // 保存到24C02
EA = 1;
}
}
}
原始驱动存在时序不稳定的问题,改进方案:
增加总线延时补偿:
c复制void Write_Ds1302(unsigned char temp) {
unsigned char i;
for (i=0;i<8;i++) {
SCK = 0;
_nop_(); _nop_(); // 增加2个空指令周期
SDA = temp & 0x01;
temp >>= 1;
SCK = 1;
_nop_(); _nop_();
}
}
添加时钟校验机制:
c复制bit Check_DS1302() {
Write_Ds1302_Byte(0x8E, 0x00); // 关闭写保护
Write_Ds1302_Byte(0x80, 0x55); // 写入测试值
if(Read_Ds1302_Byte(0x81) == 0x55)
return 1;
return 0;
}
土壤湿度传感器输出需要线性化处理:
c复制#define ADC_REF 5.0 // 参考电压5V
#define ADC_RES 256 // 8位分辨率
float read_humidity() {
uint raw = read_adc(0x03);
float voltage = (raw * ADC_REF) / ADC_RES;
// 传感器特性曲线校准
if(voltage < 1.0) return 100.0;
else if(voltage > 4.5) return 0.0;
else return (4.5 - voltage) * 28.57; // 线性转换
}
注意:实际应用中建议采集10次取平均值,消除噪声影响
| 错误类型 | 可能原因 | 解决方法 |
|---|---|---|
| "undefined identifier" | 头文件未包含或路径错误 | 检查#include路径,确认.h文件存在 |
| "data segment too large" | 变量占用过多RAM | 使用xdata关键字定义大数组 |
| "redefinition" | 重复定义函数/变量 | 检查extern声明和头文件保护宏 |
数码管显示异常:
c复制void Timer0_Service() interrupt 1 {
control(0x00, 0xc0); // 先关闭显示
// ...更新显示内容
}
I2C通信失败:
c复制#define DELAY_TIME 8 // 根据实际时钟调整
继电器误动作:
c复制if(relay) {
buzzer = 0; // 灌溉时关闭报警
}
通过HC-05模块实现手机监控:
c复制void UART_Init() {
SCON = 0x50; // 模式1
TMOD |= 0x20; // 定时器1模式2
TH1 = 0xFD; // 9600bps@11.0592MHz
TR1 = 1;
}
void Send_Data() {
SBUF = shidu; // 发送当前湿度
while(!TI);
TI = 0;
}
改进灌溉控制精度:
c复制float PID_Control(float setpoint, float input) {
static float errSum, lastErr;
float error = setpoint - input;
// 比例项
float P = kp * error;
// 积分项
errSum += error * dt;
float I = ki * errSum;
// 微分项
float D = kd * (error - lastErr) / dt;
lastErr = error;
return P + I + D;
}
通过休眠模式降低能耗:
c复制void Enter_Sleep() {
PCON |= 0x01; // 进入空闲模式
_nop_();
}
// 通过外部中断唤醒
void INT0_ISR() interrupt 0 {
PCON &= ~0x01; // 退出休眠
}
在完成基础功能后,建议尝试用示波器观察各模块的时序波形,这能帮助深入理解嵌入式系统的实时特性。我曾在一个实际项目中,发现DS1302的读取异常最终是由于电源纹波过大导致的,这个经验让我更加重视硬件稳定性的基础工作。