STM32F103+DHT11模块+步进电机28BYJ-48 简单实现 智能浇水系统demo
@
前言
注意:浇水由LED1的亮灭进行模拟
源码参考:
正点原子定时器中断实验
正点原子RTC实验
正点原子RTC实验
正点原子TFTLCD显示实验
正点原子按键实验
正点原子蜂鸣器实验
开发板:正点原子 STM32F103 精英版
语言:C语言
开发环境:Keil5
开发板使用了 LED KEY BEEP TFTLCD TIM3 RTC USART DHT11模块 步进电机28BYJ-48 ULN2003驱动
程序仅供学习参考,会有一些bug(比如:报警时长、休息时长的问题)。
步进电机部分参考:STM32F103+步进电机28BYJ-48+ULN2003 实现简单的正反转demo
代码下载:
功能介绍:
- LCD显示当前时间(时:分:秒)、当前温度(CEL)、当前湿度(%RH)、温度上限(CEL)、湿度上限(%RH)、湿度下限(%RH)、浇水时长(min)、休息时长(min)、报警时长(sec)。已经当前设备处于的工作模式(working浇水中 resting休息中 running正常运行中)。
- LED0约2秒反转一次。LED1亮表示浇水,灭表示关水(浇水由LED1和步进电机来模拟)。
- 按键功能;KEY0 进入修改模式,分别针对当前时间、当前温度、当前湿度、温度上限、湿度上限、湿度下限、浇水时长、休息时长,报警时长的修改(处于修改下的数值会变红),最后退出修改模式。
在修改模式下:KEY1数值+1(封顶循环),KEY_UP数值-1(封底循环)
在普通模式下:KEY1进入休息,KEY_UP进入工作。(提供了手动控制) - 当前湿度低于下限或湿度正常但温度高于上限时,报警设置的时长(这里时间<=设定),开始进入浇水(LED1点亮模拟浇水,电机正转90度),浇水过程中如果湿度高于上限,可以打断浇水(直接进入休息,休息时长是工作时长+休息时长-已工作时长),否则浇水完毕后会进入休息(时长为休息时长,电机反转90度)。休息完毕后会重新进行条件判断。
- 配置的数据存储于flash中(首地址0X08070000)
- 串口打印相应的信息,具体参考效果图。
- 不接入DHT11模块是不行的,会等待模块接入。
- TIM4对独立看门狗进行投食,(100ms一投食,125ms的等待投食)
接线
DHT11 DATA —> PG11
3.3V供电
步进电机28BYJ-48 ULN2003驱动
+ —> 5V
- —> GND
IN1 —> PF1
IN2 —> PF2
IN3 —> PF3
IN4 —> PF4
效果图
开始运行(我已经设置好了一些配置),现在温湿度都正常。(ps:时间不是很准)
漏拍LED1了,补充
核心代码
main.c
#include "led.h"
#include "delay.h"
#include "key.h"
#include "sys.h"
#include "lcd.h"
#include "usart.h"
#include "rtc.h"
#include "beep.h"
#include "stmflash.h"
#include "dht11.h"
#include "timer3.h"
#include "timer4.h"
#include "step.h"
#include "wdg.h"
#define FLASH_SAVE_ADDR 0X08070000 //设置FLASH 保存地址(必须为偶数,且其值要大于本代码所占用FLASH的大小+0X08000000)
// 配置的结构体
typedef struct Config
{
u16 temp_max;
u16 rh_max;
u16 rh_min;
u16 water_time;
u16 rest_time;
u16 alarm_time;
}Config;
/* 显示时间,index特殊处理 */
void show_msg(u8 index, _calendar_obj calendar_temp, u16 temp, u16 rh, Config config);
int main(void)
{
/* 按键返回值 */
u8 key = 0;
/* 修改指向下标 */
u8 index = 0;
/* 日历结构体 */
_calendar_obj calendar_temp;
// 修改模式标志
u8 mode = 0;
// 计时用秒
u16 sec = 0;
// 工作标志位
u8 work_flag = 0;
// 休息标志位
u8 rest_flag = 0;
u16 temp = 0;
u16 rh = 0;
Config config = {40, 80, 50, 1, 1, 1};
// 电机标志位 1为正转了90度,浇水状态 0为关水状态
u8 motor_flag = 0;
/* 延时函数初始化 */
delay_init();
/* 设置中断优先级分组为组2:2位抢占优先级,2位响应优先级 */
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
/* 串口初始化为115200 */
uart_init(115200);
/* LED端口初始化 */
LED_Init();
/* LCD初始化 */
LCD_Init();
/* 按键初始化 */
KEY_Init();
/* RTC初始化 */
RTC_Init();
/* 初始化蜂鸣器端口 */
BEEP_Init();
// 10Khz的计数频率,计数到500为50ms
TIM3_Int_Init(499, 7199);
// 独立看门狗初始化 预分频数为4*2^1=8,重载值为625,溢出时间为125ms
IWDG_Init(1,625);
// 10Khz 的计数频率,计数到 1000 为 100ms
TIM4_Int_Init(999, 7199);
/*
步进电机初始化
IN4: PF4 d
IN3: PF3 c
IN2: PF2 b
IN1: PF1 a
*/
Step_Motor_GPIO_Init();
LED0 = 1;
// DHT11初始化 DATA -> PG11
while(DHT11_Init())
{
LCD_ShowString(30,130,200,16,16,"DHT11 Error");
delay_ms(200);
LCD_Fill(30,130,239,130+16,WHITE);
delay_ms(200);
}
DHT11_Read_Data((u8 *)&temp, (u8 *)&rh);
/*
* 显示时间
* 设置字体为蓝色
*/
POINT_COLOR = BLUE;
LCD_ShowString(60, 40, 300, 16, 16, "NOW : : ");
LCD_ShowString(60, 66, 100, 16, 16, "Temp: CEL");
LCD_ShowString(60, 82, 100, 16, 16, "RH : %RH");
LCD_ShowString(60, 162, 200, 12, 12, "Temp Max: CEL");
LCD_ShowString(60, 174, 200, 12, 12, "RH Max: %RH");
LCD_ShowString(60, 186, 200, 12, 12, "RH Min: %RH");
LCD_ShowString(60, 198, 200, 12, 12, "Water Time: min");
LCD_ShowString(60, 210, 200, 12, 12, "Rest Time: min");
LCD_ShowString(60, 222, 200, 12, 12, "Alarm Time: sec");
POINT_COLOR = BLACK;
LCD_ShowString(60, 260, 200, 16, 16, "Mode: ");
POINT_COLOR = BLUE;
calendar_temp = calendar;
delay_ms(1000);
DHT11_Read_Data((u8 *)&temp, (u8 *)&rh);
delay_ms(1000);
DHT11_Read_Data((u8 *)&temp, (u8 *)&rh);
// 从指定地址开始读出指定长度的数据
// ReadAddr:起始地址
// pBuffer:数据指针
// NumToWrite:半字(16位)数
STMFLASH_Read(FLASH_SAVE_ADDR, &config.temp_max, 1);
STMFLASH_Read(FLASH_SAVE_ADDR + 0X2, &config.rh_max, 1);
STMFLASH_Read(FLASH_SAVE_ADDR + 0X4, &config.rh_min, 1);
STMFLASH_Read(FLASH_SAVE_ADDR + 0X6, &config.water_time, 1);
STMFLASH_Read(FLASH_SAVE_ADDR + 0X8, &config.rest_time, 1);
STMFLASH_Read(FLASH_SAVE_ADDR + 0X10, &config.alarm_time, 1);
// flash初始值
if(65535 == config.temp_max && 65535 == config.rh_max && 65535 == config.rh_min && 65535 == config.water_time && 65535 == config.rest_time && 65535 == config.alarm_time)
{
config.temp_max = 40;
config.rh_max = 80;
config.rh_min = 50;
config.water_time = 1;
config.rest_time = 1;
config.alarm_time = 1;
}
printf("config.temp_max = %d, config.rh_max = %d, config.rh_min = %d, config.water_time = %d, config.rest_time = %d, config.alarm_time = %d\r\n",
config.temp_max, config.rh_max, config.rh_min, config.water_time, config.rest_time, config.alarm_time);
while (1)
{
if(work_flag == 1)
{
POINT_COLOR = RED;
LCD_ShowString(108, 260, 100, 16, 16, "Working");
}
else if(rest_flag == 1)
{
POINT_COLOR = GREEN;
LCD_ShowString(108, 260, 100, 16, 16, "Resting");
}
else
{
POINT_COLOR = BLACK;
LCD_ShowString(108, 260, 100, 16, 16, "Running");
}
POINT_COLOR = BLUE;
if(time % 40 == 0)
{
// 读取温湿度值
DHT11_Read_Data((u8 *)&temp, (u8 *)&rh);
// printf("Temp:%dCEL , Humidity:%d%%RH\r\n", temp, rh);
LED0 = !LED0;
}
// 约1秒 工作或休息计时中时
if(time % 20 == 0 && (work_flag == 1 || rest_flag == 1))
{
sec++;
}
// 不在工作和休息中,清空计时
if(work_flag == 0 && rest_flag == 0)
{
sec = 0;
}
// 工作时间到达
if(work_flag == 1)
{
if(sec >= config.alarm_time)
{
BEEP = 0;
}
if(sec / 60 >= config.water_time)
{
// 停止浇水
LED1 = 1;
printf("**** water finish ****\r\n");
// 工作标志位置0
work_flag = 0;
// 休息标志位置1
rest_flag = 1;
if(motor_flag == 1)
{
// 反转90度
motor_circle(16, 0, 2);
motor_flag = 0;
}
}
}
// 工作完后 休息时间到达
if(rest_flag == 1 && sec / 60 >= (config.water_time + config.rest_time))
{
printf("**** rest finish ****\r\n");
sec = 0;
rest_flag = 0;
}
/* 根据index显示 */
if (0 == index)
show_msg(index, calendar, temp, rh, config);
else
show_msg(index, calendar_temp, temp, rh, config);
/*
* 键处理函数
* 返回按键值
* mode:0,不支持连续按;1,支持连续按;
* 0,没有任何按键按下
* 1,KEY0按下
* 2,KEY1按下
* 3,KEY3按下 WK_UP
*/
key = KEY_Scan(1);
/* KEY0 进入修改模式,依次顺序循环 */
if (1 == key)
{
index++;
index = index % 10;
/* 进入修改 */
if (1 == index)
{
calendar_temp = calendar;
mode = 1;
}
/* 退出修改 */
else if (0 == index)
{
calendar = calendar_temp;
RTC_Set(calendar_temp.w_year, calendar_temp.w_month, calendar_temp.w_date, calendar_temp.hour, calendar_temp.min, calendar_temp.sec);
// 从指定地址开始写入指定长度的数据
// WriteAddr:起始地址(此地址必须为2的倍数!!)
// pBuffer:数据指针
// NumToWrite:半字(16位)数(就是要写入的16位数据的个数.)
STMFLASH_Write(FLASH_SAVE_ADDR, &config.temp_max, 1);
STMFLASH_Write(FLASH_SAVE_ADDR + 0X2, &config.rh_max, 1);
STMFLASH_Write(FLASH_SAVE_ADDR + 0X4, &config.rh_min, 1);
STMFLASH_Write(FLASH_SAVE_ADDR + 0X6, &config.water_time, 1);
STMFLASH_Write(FLASH_SAVE_ADDR + 0X8, &config.rest_time, 1);
STMFLASH_Write(FLASH_SAVE_ADDR + 0X10, &config.alarm_time, 1);
mode = 0;
}
}
/* KEY1 选中值+1 */
else if (2 == key)
{
if (1 == index)
{
calendar_temp.hour++;
calendar_temp.hour = calendar_temp.hour > 23 ? 0 : calendar_temp.hour;
}
else if (2 == index)
{
calendar_temp.min++;
calendar_temp.min = calendar_temp.min > 59 ? 0 : calendar_temp.min;
}
else if (3 == index)
{
calendar_temp.sec++;
calendar_temp.sec = calendar_temp.sec > 59 ? 0 : calendar_temp.sec;
}
else if (4 == index)
{
config.temp_max++;
config.temp_max = config.temp_max > 99 ? 0 : config.temp_max;
}
else if (5 == index)
{
config.rh_max++;
config.rh_max = config.rh_max > 99 ? (config.rh_min+1) : config.rh_max;
}
else if (6 == index)
{
config.rh_min++;
config.rh_min = config.rh_min > (config.rh_max-1) ? 0 : config.rh_min;
}
else if (7 == index)
{
config.water_time++;
config.water_time = config.water_time > 9 ? 1 : config.water_time;
}
else if (8 == index)
{
config.rest_time++;
config.rest_time = config.rest_time > 9 ? 1 : config.rest_time;
}
else if (9 == index)
{
config.alarm_time++;
config.alarm_time = config.alarm_time > 9 ? 1 : config.alarm_time;
}
// 普通模式下
else if (0 == index)
{
printf("**** KEY1 press, start rest ****\r\n");
LED1 = 1;
work_flag = 0;
// 休息计时开启
rest_flag = 1;
if(motor_flag == 1)
{
// 反转90度
motor_circle(16, 0, 2);
motor_flag = 0;
}
}
}
/* KEY_UP 选中值-1 */
else if (3 == key)
{
if (1 == index)
{
calendar_temp.hour = calendar_temp.hour == 0 ? 23 : calendar_temp.hour - 1;
}
else if (2 == index)
{
calendar_temp.min = calendar_temp.min == 0 ? 59 : calendar_temp.min - 1;
}
else if (3 == index)
{
calendar_temp.sec = calendar_temp.sec == 0 ? 59 : calendar_temp.sec - 1;
}
else if (4 == index)
{
config.temp_max = config.temp_max == 0 ? 100 : config.temp_max - 1;
}
else if (5 == index)
{
config.rh_max = config.rh_max == config.rh_min ? 100 : config.rh_max - 1;
}
else if (6 == index)
{
config.rh_min = config.rh_min == 0 ? config.rh_max - 1 : config.rh_min - 1;
}
else if (7 == index)
{
config.water_time = config.water_time == 0 ? 1 : config.water_time - 1;
}
else if (8 == index)
{
config.rest_time = config.rest_time == 0 ? 1 : config.rest_time - 1;
}
else if (9 == index)
{
config.alarm_time = config.alarm_time == 0 ? 1 : config.alarm_time - 1;
}
else if (0 == index)
{
printf("**** KEY_UP press, start work ****\r\n");
LED1 = 0;
BEEP = 1;
work_flag = 1;
if(motor_flag == 0)
{
// 正转90度
motor_circle(16, 1, 2);
motor_flag = 1;
}
}
}
else if (0 == key)
{
// 普通模式下
if (0 == mode)
{
// 湿度过低 且 没有在工作计时和休息计时中
if(rh <= config.rh_min && work_flag == 0 && rest_flag == 0)
{
printf("**** The humidity is too low, start watering ****\r\n");
BEEP = 1;
work_flag = 1;
// 模拟浇水
LED1 = 0;
if(motor_flag == 0)
{
// 正转90度
motor_circle(16, 1, 2);
motor_flag = 1;
}
}
// 湿度合适但温度过高 且 没有在工作计时和休息计时中
else if(config.rh_max > rh && rh > config.rh_min && temp >= config.temp_max && work_flag == 0 && rest_flag == 0)
{
printf("**** The temperature is too high, start watering ****\r\n");
BEEP = 1;
work_flag = 1;
// 模拟浇水
LED1 = 0;
if(motor_flag == 0)
{
// 正转90度
motor_circle(16, 1, 2);
motor_flag = 1;
}
}
// 湿度过高 且 不在休息计时中
if(rh >= config.rh_max && rest_flag == 0)
{
printf("**** If the humidity is too high, stop watering ****\r\n");
work_flag = 0;
// 休息计时开启
rest_flag = 1;
// 停止浇水
LED1 = 1;
if(motor_flag == 1)
{
// 反转90度
motor_circle(16, 0, 2);
motor_flag = 0;
}
}
}
}
delay_ms(20);
}
}
/* 显示信息,index特殊处理 */
void show_msg(u8 index, _calendar_obj calendar_temp, u16 temp, u16 rh, Config config)
{
POINT_COLOR = BLUE;
if (1 == index)
POINT_COLOR = RED;
LCD_ShowNum(116, 40, calendar_temp.hour, 2, 16);
POINT_COLOR = BLUE;
if (2 == index)
POINT_COLOR = RED;
LCD_ShowNum(140, 40, calendar_temp.min, 2, 16);
POINT_COLOR = BLUE;
if (3 == index)
POINT_COLOR = RED;
LCD_ShowNum(164, 40, calendar_temp.sec, 2, 16);
POINT_COLOR = BLUE;
if (4 == index)
POINT_COLOR = RED;
LCD_ShowNum(132, 162, config.temp_max, 2, 12);
POINT_COLOR = BLUE;
if (5 == index)
POINT_COLOR = RED;
LCD_ShowNum(132, 174, config.rh_max, 2, 12);
POINT_COLOR = BLUE;
if (6 == index)
POINT_COLOR = RED;
LCD_ShowNum(132, 186, config.rh_min, 2, 12);
POINT_COLOR = BLUE;
if (7 == index)
POINT_COLOR = RED;
LCD_ShowNum(132, 198, config.water_time, 2, 12);
POINT_COLOR = BLUE;
if (8 == index)
POINT_COLOR = RED;
LCD_ShowNum(132, 210, config.rest_time, 2, 12);
POINT_COLOR = BLUE;
if (9 == index)
POINT_COLOR = RED;
LCD_ShowNum(132, 222, config.alarm_time, 2, 12);
POINT_COLOR = BLUE;
LCD_ShowNum(116, 66, temp, 2, 16);
LCD_ShowNum(116, 82, rh, 2, 16);
}
参考用图
DHT11
步进电机28BYJ-48 ULN2003驱动