动手实践丨手把手教你用STM32做一个智能鱼缸
摘要:本文基于STM32单片机设计了一款基于物联网的智能鱼缸。
本文分享自华为云社区《基于STM32+华为云IOT设计的物联网鱼缸【玩转华为云】》,作者: DS小龙哥 。
1. 前言
为了缓解学习、生活、工作带来的压力,提升生活品质,许多人喜欢在家中、办公室等场所养鱼。为节省鱼友时间、劳力、增加养鱼乐趣;为此,本文基于STM32单片机设计了一款基于物联网的智能鱼缸。该鱼缸可以实现水温检测、水质检测、自动或手动换水、氛围灯灯光变换和自动或手动喂食等功能为一体的控制系统,鱼缸通过ESP8266连接华为云IOT物联网平台,并通过应用侧接口开发了上位机APP实现远程对鱼缸参数检测查看,并能远程控制。
从功能上分析,需要用到的硬件如下:
(1)STM32系统板
(2)水温温度检测传感器: 测量水温
(3)水质检测传感器: 测量水中的溶解性固体含量,反应水质。
(4)步进电机: 作为鱼饲料投食器
(5)RGB氛围灯: 采用RGB 3色灯,给鱼缸照明。
(6)抽水电动马达: 用来给鱼缸充氧,换水,加水等。
(7)ESP8266 WIFI:设置串口协议的WIFI,内置了TCP/IP协议栈,完善的AT指令,通过简单的指令就可以联网通信,但是当前采用的ESP8266没有烧写第三方固件,采用原本的原滋原味的官方固件,没有内置MQTT协议,代码里连接华为云物联网平台需要使用MQTT协议,所以在STM32代码里通过MQTT协议文档的字段结构自己实现了MQTT协议,在通过ESP8266的TCP相关的AT指令完成数据发送接收,完成与华为云IOT平台交互。
水产养殖水质常规检测的传感器有哪些?水产养殖水质常规检测的传感器有水质ph传感器、溶解氧传感器和温度传感器。
(1)水质ph传感器:
ph传感器是高智能化在线连续监测仪,由传感器和二次表两部分组成。可配三复合或两复合电极,以满足各种使用场所。配上纯水和超纯水电极,可适用于电导率小于3μs/cm的水质(如化学补给水、饱和蒸气、凝结水等)的pH值测量。
(2)溶解氧传感器:
氧气的消耗量与存在的氧含量成正比,而氧是通过可透膜扩散进来的。传感器与专门设计的监测溶氧的测量电路或电脑数据采集系统相连。 溶解氧传感器能够空气校准,一般校准所需时间较长,在使用后要注意保养。如果在养殖水中工作时间过长,就必须定期地清洗膜,对其进行额外保养。
在很多水产养殖中,每天测几次溶氧就可以了解溶氧情况。对池塘和许多水槽养殖系统。溶氧水平不会变化很快,池塘一般每天检测2~3次。 对于较高密度养殖系统,增氧泵故障发生可能不到1h就会造成鱼虾等大面积死亡。这些密度高的养殖系统要求有足够多的装备或每小时多次自动测量溶氧。
(3)温度传感器:
温度传感器有多种结构,包括热电偶、电阻温度传感器和热敏电阻。热电偶技术成熟,应用领域广,货源充足。选择热电偶必须满足温度范围要求,且其材料与环境相容。 电阻温度传感器(RTDs)的原理为金属的电阻随温度的改变而改变。大多电阻温度传感器(RTDs)由铂、镍或镍合金制成,其线性度比热电偶好,热切更加稳定,但容易破碎。 热敏电阻是电阻与温度具有负相关关系的半导体。热敏电阻比RTD和热电偶更灵敏,也更容易破碎,不能承受大的温差,但这一点在水产养殖中不成问题。
2. 硬件选型
2.1 STM32开发板
主控CPU采用STM32F103RCT6,这颗芯片包括48 KB SRAM、256 KB Flash、2个基本定时器、4个通用定时器、2个高级定时器、51个通用IO口、5个串口、2个DMA控制器、3个SPI、2个I2C、1个USB、1个CAN、3个12位ADC、1个12位DAC、1个SDIO接口,芯片属于大容量类型,配置较高,整体符合硬件选型设计。当前选择的这款开发板自带了一个1.4寸的TFT-LCD彩屏,可以显示当前传感器数据以及一些运行状态信息。
2.2 杜邦线
2.3 PCB板
2.4 步进电机
2.5 抽水马达
2.6 水温检测传感器
测温采用DS18B20,DS18B20是常用的数字温度传感器,其输出的是数字信号,具有体积小,硬件开销低,抗干扰能力强,精度高的特点。
DS18B20数字温度传感器接线方便,封装成后可应用于多种场合,如管道式,螺纹式,磁铁吸附式,不锈钢封装式,型号多种多样,有LTM8877,LTM8874等等。
主要根据应用场合的不同而改变其外观。封装后的DS18B20可用于电缆沟测温,高炉水循环测温,锅炉测温,机房测温,农业大棚测温,洁净室测温,弹药库测温等各种非极限温度场合。耐磨耐碰,体积小,使用方便,封装形式多样,适用于各种狭小空间设备数字测温和控制领域。
2.7 水质检测传感器
TDS (Total Dissolved Solids)、中文名总溶解固体、又称溶解性固体、又称溶解性固体总量、表明1升水肿容有多少毫克溶解性固体、一般来说、TDS值越高、表示水中含有溶解物越多、水就越不洁净、虽然在特定情况下TDS并不能有效反映水质的情况、但作为一种可快速检测的参数、TDS目前还可以作为有效的在水质情况反映参数来作为参考。常用的TDS检测设备为TDS笔、虽然价格低廉、简单易用、但不能把数据传给控制系统、做长时间的在线监测、并做水质状况分析、使用专门的仪器、虽然能传数据、精度也高、但价格很贵、为此这款TDS传感器模块、即插即用、使用简单方便、测量用的激励源采用交流信号、可有效防止探头极化、延长探头寿命的同时、也增加了输出信号的稳定性、TDS探头为防水探头、可长期侵入水中测量、该产品可以应用于生活用水、水培等领域的水质检测、有了这个传感器、可轻松DIY–套TDS检测仪了、轻松检测水的洁净程度。
2.8 ESP8266
■模块采用串口(LVTTL) 与MCU (或其他串口设备) 通信,内置TCP/IP协议栈,能够实现串口与WIFI之间的转换
■模块支持LVTTL串口, 兼容3…3V和5V单片机系统
■模块支持串 口转WIFI STA、串口转AP和WIFI STA+WIFI AP的模式,从而快速构建串口-WIFI数据传输方案
■模块小巧(19mm*29mm), 通过6个2.54mm间距排针与外部连接
3. 华为云IOT产品与设备创建
3.1 创建产品
链接://www.huaweicloud.com/product/iothub.html
点击右上角窗口创建产品。
填入产品信息。
接下来创建模型文件:
创建服务。
创建属性。根据鱼缸设备的传感器属性来添加属性。
(1)LED氛围灯
(2)抽水电机
(3)水质传感器
(4)水温温度计
3.2 创建设备
地址: //console.huaweicloud.com/iotdm/?region=cn-north-4#/dm-portal/device/all-device
点击右上角创建设备。
按照设备的情况进行填写信息。
设备创建后保存信息:
{ "device_id": "62cd6da66b9813541d510f64_dev1", "secret": "12345678" }
创建成功。
3.3 设备模拟调试
为了测试设备通信的过程,在设备页面点击调试。
选择设备调试:
3.4 MQTT三元组
为了方便能够以真实的设备登陆服务器进行测试,接下来需要先了解MQTT协议登录需要的参数如何获取,得到这些参数才可以接着进行下一步。
MQTT(Message Queuing Telemetry Transport)是一个基于客户端-服务器的消息发布/订阅传输协议,主要应用于计算能力有限,且工作在低带宽、不可靠的网络的远程传感器和控制设备,适合长连接的场景,如智能路灯等。
MQTTS是MQTT使用TLS加密的协议。采用MQTTS协议接入平台的设备,设备与物联网平台之间的通信过程,数据都是加密的,具有一定的安全性。
采用MQTT协议接入物联网平台的设备,设备与物联网平台之间的通信过程,数据没有加密,如果要保证数据的私密性可以使用MQTTS协议。
在这里可以使用华为云提供的工具快速得到MQTT三元组进行登录。
//support.huaweicloud.com/devg-iothub/iot_01_2127.html#ZH-CN_TOPIC_0240834853__zh-cn_topic_0251997880_li365284516112
工具的页面地址:
//iot-tool.obs-website.cn-north-4.myhuaweicloud.com/
根据提示填入信息,然后生成三元组信息即可。 这里填入的信息就是在创建设备的时候生成的信息。
DeviceId 62cd6da66b9813541d510f64_dev1 DeviceSecret 12345678 ClientId 62cd6da66b9813541d510f64_dev1_0_0_2022071609 Username 62cd6da66b9813541d510f64_dev1 Password a23fb6db6b5bc428971d5ccf64cc8f7767d15ca63bd5e6ac137ef75d175c77bf
3.5 平台接入地址
华为云的物联网服务器地址在这里可以获取:
//console.huaweicloud.com/iotdm/?region=cn-north-4#/dm-portal/home
MQTT (1883) a161a58a78.iot-mqtts.cn-north-4.myhuaweicloud.com
对应的IP地址是: 121.36.42.100
3.6 MQTT的主题订阅与发布格式
得到三元组之后,就可以登录MQTT服务器进行下一步的主题发布与订阅。
主题的格式详情:
//support.huaweicloud.com/api-iothub/iot_06_v5_3004.html
设备消息上报 $oc/devices/{device_id}/sys/messages/up 平台下发消息给设备 $oc/devices/{device_id}/sys/messages/down 上传的消息格式: { "services": [{ "service_id": "Connectivity", "properties": { "dailyActivityTime": 57 }, "event_time": "20151212T121212Z" }, { "service_id": "Battery", "properties": { "batteryLevel": 80 }, "event_time": "20151212T121212Z" } ] }
根据当前设备的格式总结如下:
ClientId 62cd6da66b9813541d510f64_dev1_0_0_2022071609 Username 62cd6da66b9813541d510f64_dev1 Password a23fb6db6b5bc428971d5ccf64cc8f7767d15ca63bd5e6ac137ef75d175c77bf //订阅主题: 平台下发消息给设备 $oc/devices/62cd6da66b9813541d510f64_dev1/sys/messages/down //设备上报数据 $oc/devices/62cd6da66b9813541d510f64_dev1/sys/properties/report //上报的属性消息 (一次可以上报多个属性,在json里增加就行了) {"services": [{"service_id": "fish","properties":{"LED":1}},{"service_id": "fish","properties":{"motor":1}},{"service_id": "fish","properties":{"水温":36.2}}]}
3.7 MQTT客户端模拟设备调试
得到信息之后,将参赛填入软件进行登录测试。
数据发送之后,在设备页面上可以看到设备已经在线了,并且收到了上传的数据。
4. STM32程序设计
4.1 硬件连线
硬件连接方式: 1. TFT 1.44 寸彩屏接线 GND 电源地 VCC 接5V或3.3v电源 SCL 接PC8(SCL) SDA 接PC9(SDA) RST 接PC10 DC 接PB7 CS 接PB8 BL 接PB11 2. 板载LED灯接线 LED1---PA8 LED2---PD2 3. 板载按键接线 K0---PA0 K1---PC5 K2---PA15 4. DS18B20温度传感器接线 DQ->PC6 + : 3.3V - : GND 5. 步进电机 ULN2003控制28BYJ-48步进电机接线: ULN2003接线: IN-D: PB15 d IN-C: PB14 c IN-B: PB13 b IN-A: PB12 a + : 5V - : GND 6. 抽水电机 GND---GND VCC---5V AO----PA4 7. 水质检测传感器 AO->PA1 + : 3.3V - : GND 8. RGB灯 PC13--R PC14--G PC15--B 9. ATK-ESP8266 WIFI接线 PA2(TX)--RXD 模块接收脚 PA3(RX)--TXD 模块发送脚 GND---GND 地 VCC---VCC 电源(3.3V~5.0V)
4.2 硬件原理图
4.3 汉字取模
4.4 程序下载
下载软件在资料包里。点击开始编程之后,点击开发板的复位键即可下载程序进去。
4.5 主要的信息连接代码
#include "stm32f10x.h" #include "led.h" #include "delay.h" #include "key.h" #include "usart.h" #include <string.h> #include "timer.h" #include "esp8266.h" #include "mqtt.h" #include "oled.h" #include "fontdata.h" #include "bh1750.h" #include "iic.h" #include "sht3x.h" #define ESP8266_WIFI_AP_SSID "aaa" //将要连接的路由器名称 --不要出现中文、空格等特殊字符 #define ESP8266_AP_PASSWORD "12345678" //将要连接的路由器密码 //华为云服务器的设备信息 #define MQTT_ClientID "62cd6da66b9813541d510f64_dev1_0_0_2022071609" #define MQTT_UserName "62cd6da66b9813541d510f64_dev1" #define MQTT_PassWord "a23fb6db6b5bc428971d5ccf64cc8f7767d15ca63bd5e6ac137ef75d175c77bf" //订阅与发布的主题 #define SET_TOPIC "$oc/devices/62cd6da66b9813541d510f64_dev1/sys/messages/down" //订阅 #define POST_TOPIC "$oc/devices/62cd6da66b9813541d510f64_dev1/sys/properties/report" //发布
4.6 ESP8266主要代码
u8 ESP8266_IP_ADDR[16]; //255.255.255.255 u8 ESP8266_MAC_ADDR[18]; //硬件地址 /* 函数功能: ESP8266命令发送函数 函数返回值:0表示成功 1表示失败 */ u8 ESP8266_SendCmd(char *cmd) { int RX_CNT=0; u8 i,j; for(i=0;i<10;i++) //检测的次数--发送指令的次数 { USARTx_StringSend(USART3,cmd); for(j=0;j<100;j++) //等待的时间 { delay_ms(50); if(USART3_RX_STA&0X8000) { RX_CNT=USART3_RX_STA&0x7FFF; USART3_RX_BUF[RX_CNT]='\0'; USART3_RX_STA=0; if(strstr((char*)USART3_RX_BUF,"OK")) { return 0; } } } } return 1; } /* 函数功能: ESP8266硬件初始化检测函数 函数返回值:0表示成功 1表示失败 */ u8 ESP8266_Init(void) { //退出透传模式 USARTx_StringSend(USART3,"+++"); delay_ms(100); //退出透传模式 USARTx_StringSend(USART3,"+++"); delay_ms(100); return ESP8266_SendCmd("AT\r\n"); } /* 函数功能: 一键配置WIFI为AP+TCP服务器模式 函数参数: char *ssid 创建的热点名称 char *pass 创建的热点密码 (最少8位) u16 port 创建的服务器端口号 函数返回值: 0表示成功 其他值表示对应错误值 */ u8 ESP8266_AP_TCP_Server_Mode(char *ssid,char *pass,u16 port) { char *p; u8 i; char ESP8266_SendCMD[100]; //组合发送过程中的命令 /*1. 测试硬件*/ if(ESP8266_SendCmd("AT\r\n"))return 1; /*2. 关闭回显*/ if(ESP8266_SendCmd("ATE0\r\n"))return 2; /*3. 设置WIFI模式*/ if(ESP8266_SendCmd("AT+CWMODE=2\r\n"))return 3; /*4. 复位*/ ESP8266_SendCmd("AT+RST\r\n"); delay_ms(1000); delay_ms(1000); delay_ms(1000); /*5. 关闭回显*/ if(ESP8266_SendCmd("ATE0\r\n"))return 5; /*6. 设置WIFI的AP模式参数*/ sprintf(ESP8266_SendCMD,"AT+CWSAP=\"%s\",\"%s\",1,4\r\n",ssid,pass); if(ESP8266_SendCmd(ESP8266_SendCMD))return 6; /*7. 开启多连接*/ if(ESP8266_SendCmd("AT+CIPMUX=1\r\n"))return 7; /*8. 设置服务器端口号*/ sprintf(ESP8266_SendCMD,"AT+CIPSERVER=1,%d\r\n",port); if(ESP8266_SendCmd(ESP8266_SendCMD))return 8; /*9. 查询本地IP地址*/ if(ESP8266_SendCmd("AT+CIFSR\r\n"))return 9; //提取IP地址 p=strstr((char*)USART3_RX_BUF,"APIP"); if(p) { p+=6; for(i=0;*p!='"';i++) { ESP8266_IP_ADDR[i]=*p++; } ESP8266_IP_ADDR[i]='\0'; } //提取MAC地址 p=strstr((char*)USART3_RX_BUF,"APMAC"); if(p) { p+=7; for(i=0;*p!='"';i++) { ESP8266_MAC_ADDR[i]=*p++; } ESP8266_MAC_ADDR[i]='\0'; } //打印总体信息 printf("当前WIFI模式:AP+TCP服务器\r\n"); printf("当前WIFI热点名称:%s\r\n",ssid); printf("当前WIFI热点密码:%s\r\n",pass); printf("当前TCP服务器端口号:%d\r\n",port); printf("当前TCP服务器IP地址:%s\r\n",ESP8266_IP_ADDR); printf("当前TCP服务器MAC地址:%s\r\n",ESP8266_MAC_ADDR); return 0; } /* 函数功能: TCP服务器模式下的发送函数 发送指令: */ u8 ESP8266_ServerSendData(u8 id,u8 *data,u16 len) { int RX_CNT=0; u8 i,j,n; char ESP8266_SendCMD[100]; //组合发送过程中的命令 for(i=0;i<10;i++) { sprintf(ESP8266_SendCMD,"AT+CIPSEND=%d,%d\r\n",id,len); USARTx_StringSend(USART3,ESP8266_SendCMD); for(j=0;j<10;j++) { delay_ms(50); if(USART3_RX_STA&0X8000) { RX_CNT=USART3_RX_STA&0x7FFF; USART3_RX_BUF[RX_CNT]='\0'; USART3_RX_STA=0; if(strstr((char*)USART3_RX_BUF,">")) { //继续发送数据 USARTx_DataSend(USART3,data,len); //等待数据发送成功 for(n=0;n<200;n++) { delay_ms(50); if(USART3_RX_STA&0X8000) { RX_CNT=USART3_RX_STA&0x7FFF; USART3_RX_BUF[RX_CNT]='\0'; USART3_RX_STA=0; if(strstr((char*)USART3_RX_BUF,"SEND OK")) { return 0; } } } } } } } return 1; } /* 函数功能: 配置WIFI为STA模式+TCP客户端模式 函数参数: char *ssid 创建的热点名称 char *pass 创建的热点密码 (最少8位) char *p 将要连接的服务器IP地址 u16 port 将要连接的服务器端口号 u8 flag 1表示开启透传模式 0表示关闭透传模式 函数返回值:0表示成功 其他值表示对应的错误 */ u8 ESP8266_STA_TCP_Client_Mode(char *ssid,char *pass,char *ip,u16 port,u8 flag) { char ESP8266_SendCMD[100]; //组合发送过程中的命令 //退出透传模式 //USARTx_StringSend(USART3,"+++"); //delay_ms(50); /*1. 测试硬件*/ if(ESP8266_SendCmd("AT\r\n"))return 1; /*2. 关闭回显*/ if(ESP8266_SendCmd("ATE0\r\n"))return 2; /*3. 设置WIFI模式*/ if(ESP8266_SendCmd("AT+CWMODE=1\r\n"))return 3; /*4. 复位*/ ESP8266_SendCmd("AT+RST\r\n"); delay_ms(1000); delay_ms(1000); delay_ms(1000); /*5. 关闭回显*/ if(ESP8266_SendCmd("ATE0\r\n"))return 5; /*6. 配置将要连接的WIFI热点信息*/ sprintf(ESP8266_SendCMD,"AT+CWJAP=\"%s\",\"%s\"\r\n",ssid,pass); if(ESP8266_SendCmd(ESP8266_SendCMD))return 6; /*7. 设置单连接*/ if(ESP8266_SendCmd("AT+CIPMUX=0\r\n"))return 7; /*8. 配置要连接的TCP服务器信息*/ sprintf(ESP8266_SendCMD,"AT+CIPSTART=\"TCP\",\"%s\",%d\r\n",ip,port); if(ESP8266_SendCmd(ESP8266_SendCMD))return 8; /*9. 开启透传模式*/ if(flag) { if(ESP8266_SendCmd("AT+CIPMODE=1\r\n"))return 9; //开启 if(ESP8266_SendCmd("AT+CIPSEND\r\n"))return 10; //开始透传 if(!(strstr((char*)USART3_RX_BUF,">"))) { return 11; } //如果想要退出发送: "+++" } printf("WIFI模式:STA+TCP客户端\r\n"); printf("Connect_WIFI热点名称:%s\r\n",ssid); printf("Connect_WIFI热点密码:%s\r\n",pass); printf("TCP服务器端口号:%d\r\n",port); printf("TCP服务器IP地址:%s\r\n",ip); return 0; } /* 函数功能: TCP客户端模式下的发送函数 发送指令: */ u8 ESP8266_ClientSendData(u8 *data,u16 len) { int RX_CNT=0; u8 i,j,n; char ESP8266_SendCMD[100]; //组合发送过程中的命令 for(i=0;i<10;i++) { sprintf(ESP8266_SendCMD,"AT+CIPSEND=%d\r\n",len); USARTx_StringSend(USART3,ESP8266_SendCMD); for(j=0;j<10;j++) { delay_ms(50); if(USART3_RX_STA&0X8000) { RX_CNT=USART3_RX_STA&0x7FFF; USART3_RX_BUF[RX_CNT]='\0'; USART3_RX_STA=0; if(strstr((char*)USART3_RX_BUF,">")) { //继续发送数据 USARTx_DataSend(USART3,data,len); //等待数据发送成功 for(n=0;n<200;n++) { delay_ms(50); if(USART3_RX_STA&0X8000) { RX_CNT=USART3_RX_STA&0x7FFF; USART3_RX_BUF[RX_CNT]='\0'; USART3_RX_STA=0; if(strstr((char*)USART3_RX_BUF,"SEND OK")) { return 0; } } } } } } } return 1; }