目录
前言:
**注意事项:**本项目使用的是u8g2库进行显示驱动OLED,需要学会移植或者直接使用我的模板,具体可以看我的这个博文:手把手移植基于STM移植U8g2图形库教程------0.96寸OLED(附送整理资料)
买来的esp8266模可能不支持连接阿里云,需要烧写MQTT固件,具体操作可以看看我的这个博文:手把手连接阿里云教程
里面都会有所需的固件和工具和教程!
项目效果演示:
智能农业大棚
一、简介
这可以作为一个简单的课设或者用来水毕设的一个小项目,总体来说挺简单的,我做完后就打算开源出来大家一起学习一下,具体功能如下:
1、按键:
按键一:切换菜单
按键二:切换选项
按键三:+-或调节风扇 | 水泵 | 灯光
按键四:确认 | 连接阿里云平台
2、液晶屏幕显示:
菜单1:主界面显示空气温湿度,土壤湿度,光照强度,检测是否超过或低于临界值
菜单2:临界值调节
菜单3:风扇手动开关
菜单4:灯光手动开关
菜单5:水泵手动开关
菜单6:阿里云连接菜单界面
3、土壤湿度、空气温湿度、光照强度高于或低于临界值,蜂鸣器就会响
4、土壤湿度过低自动打开水泵
5、光照强度过低自动打开照明灯
6、空气温度过高会自动打开风扇
7、连接阿里云平台,可以在电脑上面查看各个数据
二、硬件需求准备
1、STM32F103最小核心开发板
2、0.96寸OLED屏幕
3、按键X4
4、TB6612FNG点击驱动模块
5、水泵
6、电机(带小风扇)
7、DHT11温湿度模块
8、esp8266模块
9、蜂鸣器
10、土壤湿度传感器
11、光照强度传感器
12、照明灯模块
三、硬件框图
四、CubeMX配置
这里只演示配置使用的外设配置过程!
4.1、按键、蜂鸣器GPIO口配置
按键配置为上拉模式、蜂鸣器低电平输出有效
4.2、ADC输入配置
ADC为读取土壤湿度和光照强度的ADC数值,这里开启两个ADC分别读取
ADC1------读取土壤湿度:
ADC2------读取光照强度:
4.3、IIC------驱动OLED
4.4、DHT11温湿度读取
这个使用过程中会对弈随意变动上下拉模式,所以这里就不配置了,使用的GPIO口为PB8
4.5、PWM配置------光照灯、水泵、风扇
4.6、串口------esp8266模块
开启中断:
4.7、定时器配置------按键消抖所需
开启中断:
最后生成工程即可。
五、部分代码实现思路
5.1、菜单实现思路
最常用的就是菜单索引法还有链表法,我这里菜单结构非常简单,所以我就使用索引法了,这里简单说一下,就是每一个菜单都对应一个结构体((这个项目没有二级界面,所以用不到进入索引和返回索引)):
cpp
typedef struct
{
int current_index;//当前索引
int last_index; //下一索引
int enter_index; //进入索引
int back_index; //返回索引
void(*current)(u8g2_t u8g2);
}Menu;
结构体成员包括以上的几个。
这个小项目有5个菜单页面:所以定义5个结构体(这个项目没有二级界面,所以用不到进入索引和返回索引):
cpp
Menu Menu_Table[30] =
{
// 当 下 进 返
{0, 1, 0, 0,(*Home_Menu)}, //主页显示界面
{1, 2, 0, 0,(*Set_Menu)}, //设置临界值界面
{2, 3, 0, 0,(*Fan_Menu)}, //风扇控制界面
{3, 4, 0, 0,(*Light_Menu)},//灯光控制界面
{4, 5, 0, 0,(*Water_Menu)},//水泵控制界面
{5, 0, 0, 0,(*Wifi_Menu)},//连接阿里云控制界面
};
然后定义一个当前显示菜单的索引值和当前菜单显示绘制函数:
cpp
int Current_Menu_index = 0;//当前菜单索引
void(*Current)(u8g2_t u8g2);//当前菜单索引执行绘制函数
然后初始化好按键,按下后执行跳转:
切换下一菜单使用例子(如果是其他的可以八next换成其他的结构体成员):
cpp
Current_Menu_index = Menu_Table[Current_Menu_index].last_index;//变换索引值
Current = Menu_Table[Current_Menu_index].current;//函数复制
(*Current)(u8g2);//执行绘制函数
在举一个例子帮助大家理解一下,比如,我们当前程序处在索引号为2(临界值界面),就会执行Set_Menu函数。此时,如果按下next按键,程序当前索引号就会变为3,并且执行索引号为3时候的Fan_Menu函数。以此类推!
5.2、OLED驱动
这里我是使用开源的u8g2库进行开发,这个库显示效果很好,而且也很好移植!具体可以参考我的这个博文:移植u8g2库,移植成功后使用也可以看看我的另外要给博文:u8g2库函数使用
5.3、按键消抖
我这里使用的是中断消抖:
cpp
unsigned char Key_PIN_Read()
{
unsigned char Temp=0;
if(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_12) == GPIO_PIN_RESET) Temp = 1;
if(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_13) == GPIO_PIN_RESET) Temp = 2;
if(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_14) == GPIO_PIN_RESET) Temp = 3;
if(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_15) == GPIO_PIN_RESET) Temp = 4;
return Temp;
}
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) //中断回调函数
{
Key_Slow++;
if(Key_Slow == 10) Key_Slow = 0;//按键消抖
}
void Key_Pro()
{
if(Key_Slow) return;//按键减速
Key_Slow=1;
Key_Val=Key_PIN_Read();
Key_Down=Key_Val & (Key_Val ^ Key_Old);//捕捉下降沿
Key_Up = ~Key_Val & (Key_Old ^ Key_Val);//捕捉按键上降沿
Key_Old=Key_Val;
switch(Key_Up)
{
case 1:
break;
case 2:
break;
case 3:
break;
case 4:
break;
}
}
5.4、DHT11温湿度驱动
手动配置GPIO模式:
cpp
#define DHT_HIGHT HAL_GPIO_WritePin(GPIOB, GPIO_PIN_8, GPIO_PIN_SET)
#define DHT_LOW HAL_GPIO_WritePin(GPIOB, GPIO_PIN_8, GPIO_PIN_RESET)
#define DHT_VALUE HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_8)
uint8_t datas[5];//空气温湿度数据
void delay_us(uint16_t cnt)
{
uint8_t i;
while(cnt)
{
for (i = 0; i < 10; i++)
{
}
cnt--;
}
}
void DHT_GPIO_Init(uint32_t Mode)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
__HAL_RCC_GPIOB_CLK_ENABLE();
GPIO_InitStruct.Pin = GPIO_PIN_8;
GPIO_InitStruct.Mode = Mode;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
}
void DHT11_Start(void)
{
DHT_GPIO_Init(GPIO_MODE_OUTPUT_PP);
DHT_HIGHT;
DHT_LOW;
HAL_Delay(30);
DHT_HIGHT;
DHT_GPIO_Init(GPIO_MODE_INPUT);
while(DHT_VALUE);
while(!DHT_VALUE);
while(DHT_VALUE);
}
void Read_Data_From_DHT()
{
int i;//轮
int j;//每一轮读多少次
char tmp;
char flag;
DHT11_Start();
DHT_GPIO_Init(GPIO_MODE_INPUT);
for(i= 0;i < 5;i++)
{
for(j=0;j<8;j++)
{
while(!DHT_VALUE);//等待卡g点
delay_us(40);
if(DHT_VALUE == 1)
{
flag = 1;
while(DHT_VALUE);
}
else
{
flag = 0;
}
tmp = tmp << 1;
tmp |= flag;
}
datas[i] = tmp;
}
}
数据显示以及文字显示:
cpp
void Printf_DHT11(u8g2_t u8g2,u8g2_uint_t x, u8g2_uint_t y, const uint8_t *font)
{
char var_buf[100];
char var_buf1[100];
Read_Data_From_DHT();//读取温湿度数据
u8g2_DrawXBMP(&u8g2, x, y, 16, 16, kong);
u8g2_DrawXBMP(&u8g2, x+16, y, 16, 16, qi);
u8g2_DrawXBMP(&u8g2, x+32, y, 16, 16, wen);
u8g2_DrawXBMP(&u8g2, x+48, y, 16, 16, du);
u8g2_DrawXBMP(&u8g2, x, y+16, 16, 16, kong);
u8g2_DrawXBMP(&u8g2, x+16, y+16, 16, 16, qi);
u8g2_DrawXBMP(&u8g2, x+32, y+16, 16, 16, wen);
u8g2_DrawXBMP(&u8g2, x+48, y+16, 16, 16, du);
sprintf(var_buf , ": %d.%d C",datas[2],datas[3]);
sprintf(var_buf1, ": %d.%d",datas[0],datas[1]);
u8g2_SetFont(&u8g2, font);
u8g2_DrawStr(&u8g2, x+64, y+16, var_buf);
u8g2_DrawStr(&u8g2, x+64, y+32, var_buf1);
}
5.5、光照强度、土壤湿度ADC转换
cpp
int Printf_Soil(u8g2_t u8g2,u8g2_uint_t x, u8g2_uint_t y, const uint8_t *font)
{
char var[100];
int value;
HAL_ADC_Start(&hadc1); //启动ADC单次转换
HAL_ADC_PollForConversion(&hadc1, 50); //等待ADC转换完成
value = HAL_ADC_GetValue(&hadc1); //读取ADC转换数据
value = 100 - value * 100 / 4096;
// Soil = value;
u8g2_DrawXBMP(&u8g2, x, y, 16, 16, tu);
u8g2_DrawXBMP(&u8g2, x+16, y, 16, 16, rang);
u8g2_DrawXBMP(&u8g2, x+32, y, 16, 16, shi);
u8g2_DrawXBMP(&u8g2, x+48, y, 16, 16, du);
sprintf(var , ": %d %%", value);
u8g2_SetFont(&u8g2, font);
u8g2_DrawStr(&u8g2, x+64, y+16, var);
return value;
}
int Printf_Light(u8g2_t u8g2,u8g2_uint_t x, u8g2_uint_t y, const uint8_t *font)
{
char var[100];
int value;
HAL_ADC_Start(&hadc2); //启动ADC单次转换
HAL_ADC_PollForConversion(&hadc2, 50); //等待ADC转换完成
value = HAL_ADC_GetValue(&hadc2); //读取ADC转换数据
value = 100 - value * 100 / 4096;
// Light = value;
u8g2_DrawXBMP(&u8g2, x, y, 16, 16, guang);
u8g2_DrawXBMP(&u8g2, x+16, y, 16, 16, zhao);
u8g2_DrawXBMP(&u8g2, x+32, y, 16, 16, qiang);
u8g2_DrawXBMP(&u8g2, x+48, y, 16, 16, du);
sprintf(var , ": %d %%", value);
u8g2_SetFont(&u8g2, font);
u8g2_DrawStr(&u8g2, x+64, y+16, var);
return value;
}
5.6、esp8266连接阿里云
这里我为了保险起见就是用了很多的延迟时间(连接时间过长),你们可以自己调节时间!
cpp
#define USERNAME "test&k28qfTAtkBg" //用户名
// "test&k28qfTAtkBg"
#define PASSWORD "d7c3b27a5d8ca954487de1e1946a4a08439a2c2508242268e9d90ce178178f39" //密码
// "d7c3b27a5d8ca954487de1e1946a4a08439a2c2508242268e9d90ce178178f39"
#define CLIENTID "k28qfTAtkBg.test|securemode=2\\,signmethod=hmacsha256\\,timestamp=1736673123047|" //设备名称
// "k28qfTAtkBg.test|securemode=2\\,signmethod=hmacsha256\\,timestamp=1736673123047|"
#define PRODUCTID "k28qfTAtkBg" //产品ID
// "k28qfTAtkBg"
#define DOMAINNAME "iot-06z00fj5kcoes6j.mqtt.iothub.aliyuncs.com" //域名
// "iot-06z00fj5kcoes6j.mqtt.iothub.aliyuncs.com"
#define DEVICENAME "test"
//WiFi连接函数
void Wifi_Connect()
{
printf("AT\r\n");//避免报错
HAL_Delay(500);
printf("AT+RESTORE\r\n");//恢复出厂
HAL_Delay(5000);
// printf("AT+RST\r\n");//复位
// HAL_Delay(5000);
printf("ATE0\r\n");//关闭回显
HAL_Delay(5000);
printf("AT+CWMODE=3\r\n");//设置双模式
HAL_Delay(5000);
printf("AT+CWJAP=\"USER_E191B0\",\"98599714\"\r\n");//设置WIFI密码和账号
HAL_Delay(5000);
printf("AT+MQTTUSERCFG=0,1,\"NULL\",\"%s\",\"%s\",0,0,\"\"\r\n",USERNAME,PASSWORD);//设置MQTT的username和password
HAL_Delay(5000);
printf("AT+MQTTCLIENTID=0,\"%s\"\r\n",CLIENTID); //设置CLIENTID
HAL_Delay(5000);
printf("AT+MQTTCONN=0,\"%s\",1883,1\r\n",DOMAINNAME);//设置域名
HAL_Delay(5000);
printf("AT+MQTTSUB=0,\"/%s/%s/user/get\",1\r\n",PRODUCTID,DEVICENAME);//订阅
}
六、功能拓展
后续拓展:
1、电脑阿里云平台控制单片机
2、添加语音模块
。。。。。。。。。。。。其他
后续打了PCB板子后有空会实现给大家,觉得有帮助的可以点点关注!