从0带你设计与实现基于STM32的智慧农业管理系统

本文分享自华为云社区《基于STM32的智慧农业管理系统设计与实现》,作者: DS小龙哥。

一、前言

1.1 项目介绍

【1】项目功能

随着全球农业现代化进程的加快,以及物联网、人工智能等先进技术的发展与应用,智慧农业已经成为现代农业发展的新趋势。基于精准感知、智能控制和远程管理的智慧农业系统能够显著提升农作物生产效率,降低资源消耗,实现环境友好型可持续农业生产。

在当前背景下,我国正大力推进数字乡村建设,智慧农业管理系统作为其中的重要组成部分,对于提高农业生产精细化管理水平,解决传统农业中信息获取不及时、人工管理成本高、决策缺乏科学依据等问题具有重要作用。

本设计开发一套基于STM32F103RCT6主控芯片的智慧农业管理系统,通过集成DHT11温湿度传感器、BH1750光照强度传感器以及土壤湿度检测传感器,实时监测农田环境和作物生长状态,并在超出阈值时通过蜂鸣器报警,提醒管理人员进行灌溉、施肥等操作。同时,采用NBIoT通信技术(BC26模块)将采集到的数据上传至云端,利用EMQX开源MQTT服务器框架部署于华为云ECS服务器上的MQTT服务器,实现数据的远程展示与处理。

系统支持微信小程序远程控制功能,使得农户或管理者可以随时随地查看农田环境参数、接收预警信息,并能远程手动控制灌溉设备、补光灯等,大大提高了农业生产的智能化和便捷性。此项目的实施不仅有助于推动我国农业信息化水平的提升,也有利于农业资源的高效利用,对保障国家粮食安全、促进农业增效、农民增收具有重要意义。

【2】设计实现的功能

(1)实时环境监测:系统通过集成的DHT11温湿度传感器、BH1750光照强度传感器以及土壤湿度检测传感器,实时监测农田环境中的温度、湿度、光照强度和土壤含水量等关键参数。当这些参数超过或低于预设阈值时,系统将自动触发蜂鸣器报警,提醒管理人员关注并采取相应措施。

(2)自动化管理与预警:根据土壤湿度传感器检测的数据,如果土壤湿度低于设定的适宜作物生长的含水量阀值,则系统会自动提醒管理者进行灌溉操作。同时,可以按照预设周期发送施肥提醒,以确保农作物在最佳时期得到充足的水分和养分供应。

(3)远程控制功能:利用NBIoT通信技术(BC26模块)将现场采集到的各项数据上传至云端MQTT服务器,并通过微信小程序实现远程访问和展示。用户可以通过微信小程序查看实时监测数据,以及对农田设备进行远程手动控制,如启动或关闭5V抽水泵进行灌溉,开启或关闭白色LED补光灯调节光照条件。

(4)数据上云与分析:基于EMQX开源MQTT服务器框架搭建的MQTT服务器,能够接收并处理STM32主控板传输的农业环境数据,并对接微信小程序平台,为用户提供直观易懂的数据图表和分析结果,便于农户或农业技术人员进行科学决策和精准管理。

【3】项目硬件模块组成

(1)主控模块: 采用STM32F103RCT6微控制器作为核心控制单元,负责整个系统的运行和管理。STM32F103RCT6具有丰富的外设接口、强大的处理能力和低功耗特性,能够实时处理传感器数据、执行逻辑判断,并通过无线通信模块发送和接收指令。

(2)环境监测模块:

  • 温湿度监测:使用DHT11温湿度传感器采集农田环境的温度和湿度信息。

  • 光照强度监测:采用BH1750光照强度传感器测量农田的光照强度。

  • 土壤湿度检测:使用土壤湿度检测传感器获取作物生长区域的土壤含水量数据。

(3)控制输出模块:

  • 补光灯控制:配置白色LED灯作为补光光源,根据光照强度监测结果,通过STM32主控板进行智能调节或远程手动控制。

  • 灌溉系统控制:采用5V抽水泵配合继电器实现灌溉功能,当土壤湿度低于预设阈值时,STM32主控板将控制继电器闭合,启动抽水泵进行灌溉;反之则停止灌溉。

(4)无线通信模块: 集成NBIoT-BC26模块,实现与云端服务器的数据交互。该模块具备广覆盖、低功耗、大连接的特点,可确保在各种复杂农业环境中稳定地传输数据至MQTT服务器。

(5)报警模块: 系统配备蜂鸣器用于异常情况报警,当环境参数超出设定范围时,主控板会驱动蜂鸣器发出声音警报。

1.2 设计思路

(1)系统需求分析:根据智慧农业管理的实际需求,确定需要监测的关键环境参数(温度、湿度、光照强度和土壤湿度),以及必要的控制功能(灌溉、补光灯控制等)。同时考虑远程监控与预警的需求,规划通过NBIoT通信技术实现数据上传及远程操控。

(2)硬件选型与设计:

  • 主控芯片选择STM32F103RCT6,因其具有丰富的外设接口、强大的处理能力和低功耗特性,能够满足系统实时数据采集与控制的要求。

  • 选用DHT11作为温湿度传感器,BH1750作为光照强度传感器,以及土壤湿度检测传感器,分别获取农田环境的基本信息。

  • 设计灌溉系统,使用5V抽水泵配合继电器控制灌溉,以响应土壤湿度的监测结果。

  • 采用白色LED灯作为补光光源,并接入主控板进行智能调节或远程控制。

  • 配备蜂鸣器用于异常情况报警。

  • 选用NBIoT-BC26模块确保无线通信稳定可靠,实现数据上云。

(3)软件架构设计:

  • 开发STM32的嵌入式软件程序,负责读取各传感器数据,执行逻辑判断,如环境参数超限时触发报警、根据土壤湿度自动或手动控制灌溉、周期性提醒施肥等操作。

  • 实现NBIoT通信协议栈,将现场采集的数据通过BC26模块发送至云端MQTT服务器。

  • 在云端部署EMQX开源MQTT服务器框架,接收并存储前端设备发送的数据。

  • 开发微信小程序客户端,对接MQTT服务器,展示农田环境的各项实时监测数据,提供远程手动控制界面。

1.3 传感器功能介绍

(1)DHT11温湿度传感器

  • 功能:用于实时监测农田环境中的温度和相对湿度。

  • 特点:DHT11是一种低成本、低功耗的数字式温湿度复合传感器,提供了一体化的解决方案。它能够直接输出经过校准的数字信号,便于微处理器直接读取,无需复杂的信号处理电路。

(2)BH1750光照强度传感器

  • 功能:测量农田或温室内的光照强度(照度),以判断当前光照条件是否满足作物生长需求。

  • 特点:BH1750是一款I²C接口的数字光照强度传感器,具有高精度和宽量程的特点,可精确检测光照强度,并支持多种分辨率模式切换以适应不同的应用场景。

(3)土壤湿度检测传感器

  • 功能:用于监测种植区域土壤的水分含量,作为决定灌溉与否的重要依据。

  • 特点:这类传感器通常采用电容式、电阻式或者频域反射(FDR)等原理来检测土壤湿度,通过转换为电信号变化,从而实现对土壤含水量的非破坏性测定。其特点是能反映土壤实际湿润状况,帮助实现精准灌溉。

(4)蜂鸣器报警模块

  • 功能:虽然不是传统意义上的传感器,但在本系统中作为报警装置使用,当环境参数超出预设阈值时,由主控芯片STM32控制蜂鸣器发出声音警报,提醒管理人员及时处理异常情况。

(5)5V抽水泵与继电器组合

  • 功能:抽水泵与继电器配合实现灌溉功能,继电器根据土壤湿度传感器的数据反馈控制抽水泵的开关状态,达到智能灌溉的目的。

  • 特点:继电器作为电子开关,可以远程控制大电流设备如抽水泵的通断,实现小电流控制大电流,同时隔离了主控制器与负载之间的电气连接,提高了系统的安全性。

(6)NBIoT-BC26模块

  • 功能:作为物联网通信组件,负责将采集到的各种数据无线传输至云端服务器,同时也接收来自云端的控制指令,实现远程数据交互和控制。

  • 特点:NBIoT(窄带物联网)技术具有低功耗、广覆盖、大连接的优点,特别适合于智慧农业这种需要大面积部署且网络连接要求稳定的场景。BC26模块是基于NBIoT标准的通信模块,具备良好的网络兼容性和稳定性。

1.4 开发工具的选择

STM32的编程语言选择C语言,C语言执行效率高,大学里主学的C语言,C语言编译出来的可执行文件最接近于机器码,汇编语言执行效率最高,但是汇编的移植性比较差,目前在一些操作系统内核里还有一些低配的单片机使用的较多,平常的单片机编程还是以C语言为主。C语言的执行效率仅次于汇编,语法理解简单、代码通用性强,也支持跨平台,在嵌入式底层、单片机编程里用的非常多,当前的设计就是采用C语言开发。

开发工具选择Keil,keil是一家世界领先的嵌入式微控制器软件开发商,在2015年,keil被ARM公司收购。因为当前芯片选择的是STM32F103系列,STMF103是属于ARM公司的芯片构架、Cortex-M3内核系列的芯片,所以使用Kile来开发STM32是有先天优势的,而keil在各大高校使用的也非常多,很多教科书里都是以keil来教学,开发51单片机、STM32单片机等等。目前作为MCU芯片开发的软件也不只是keil一家独大,IAR在MCU微处理器开发领域里也使用的非常多,IAR扩展性更强,也支持STM32开发,也支持其他芯片,比如:CC2530,51单片机的开发。从软件的使用上来讲,IAR比keil更加简洁,功能相对少一些。如果之前使用过keil,而且使用频率较多,已经习惯再使用IAR是有点不适应界面的。

二、EMQX开源MQTT服务器框架

EMQX是一款开源的、云原生的分布式物联网MQTT消息服务器,设计目标是实现高可靠性,并支持承载海量物联网终端的MQTT连接,以及在海量物联网设备间实现低延时消息路由。基于Erlang/OTP平台开发,充分利用了Erlang/OTP的软实时、低延时和分布式特性。

以下是EMQX服务器框架的详细介绍:

(1)可扩展性:EMQX支持亿级的MQTT服务订阅,单节点能够支持500万MQTT设备连接,集群可扩展至1亿并发MQTT连接。这种强大的扩展能力使其能够适应不同规模的物联网应用。

(2)安全性:EMQX提供了多种安全机制,包括SSL/TLS、密码认证、增强认证和ACL(访问控制列表)等,以保障数据传输和访问的安全性。

(3)规则引擎:EMQX内置了基于SQL的规则引擎,能够实时过滤、转换和处理消息,提供灵活的消息处理机制。这使得应用程序能够根据业务需求对消息进行灵活处理。

(4)数据存储:EMQX企业版还提供了数据存储功能,将客户端上下线状态、订阅关系、离线消息、消息内容以及消息回执等操作记录到各种数据库中。这一功能在服务崩溃或客户端异常离线后,能够保留数据,确保数据的完整性和可靠性。

(5)集群设计:EMQX采用Masterless的大规模分布式集群架构,实现了系统的高可用性和水平扩展。集群设计包括维护订阅表、路由表和主题树等数据结构,以实现消息转发和投递给各节点上的订阅者。

(6)协议支持:EMQX完全支持MQTT 5.0和3.x协议标准,提供了更好的伸缩性、安全性和可靠性。同时,它还提供了对多种其他协议的支持,如WebSocket、TCP、SSL/TLS等。

(7)易用性:EMQX提供了丰富的API和插件管理功能,使得用户可以方便地查看在线客户端信息、踢出客户端、管理插件状态等。它还提供了可视化的管理界面和调试工具,方便用户进行监控和管理。

三、购买ECS云服务器

3.1 登录官网

www.huaweicloud.com/

3.2 购买ECS服务器

【1】选择ECS弹性服务器

【2】选择ECS服务器的区域、配置信息、操作系统(我选择的Ubuntu18.04 64位)。

【3】购买弹性公网IP,配置带宽。

【4】配置密码

【5】选择购买时长,我这里选择了1个月时长

【6】确认付费付款

收到邮件提醒,服务器创建成功。 (为了写教程,花费320元,买了一个月服务器)

【7】返回弹性服务器的控制台

【8】点击服务器名字,可以进入到详情页面。

3.3 配置安全组

要确保MQTT服务器常用的几个端口已经开放出出来。

3.4 安装FinalShell

Windows下安装 FinalShell 终端,方便使用SSH协议远程登录到云服务器。 (当然,使用其他方式登录也是一样的)

3.5 远程登录到云服务器终端

【1】新建连接,选择SSH连接。

【2】填入IP地址、用户名、密码

这里的主机就是填服务器的公网IP地址,密码就是创建服务器输入的密码,用户名直接用root。

【3】点击连接服务器

【4】第一次登录会弹出提示框,选择接受并保存

【5】接下来可以看到服务器已经登录成功了。

四、Linux下安装EMQX

本章节将介绍如何在 Ubuntu 系统中下载安装并启动 EMQX。

支持的 Ubuntu 版本:

  • Ubuntu 22.04

  • Ubuntu 20.04

  • Ubuntu 18.04

4.1 官网地址

链接:www.emqx.io/docs/zh/v5....

4.2 通过Apt源安装

EMQX 支持通过 Apt 源安装,免除了用户需要手动处理依赖关系和更新软件包等的困扰,具有更加方便、安全和易用等优点。

在命令行终端,复制下面的命令过去,按下回车键。

【1】通过以下命令配置 EMQX Apt 源:

arduino 复制代码
curl -s https://assets.emqx.com/scripts/install-emqx-deb.sh | sudo bash

【2】运行以下命令安装 EMQX:

arduino 复制代码
sudo apt-get install emqx

【3】运行以下命令启动 EMQX:

sql 复制代码
sudo systemctl start emqx

过程如下:

4.3 EMQX常用的命令

arduino 复制代码
sudo systemctl emqx start    启动
sudo systemctl emqx stop     停止
sudo systemctl emqx restart  重启 

五、配置EMQX服务器

5.1 登录EMQX内置管理控制台

EMQX 提供了一个内置的管理控制台,即 EMQX Dashboard。方便用户通过 Web 页面就能轻松管理和监控 EMQX 集群,并配置和使用所需的各项功能。

在浏览器里输入: http://122.112.225.194:18083 就可以访问EMQX的后台管理页面。可以管理以连接的客户端或检查运行状态。

这里面的IP地址,就是自己ECS云服务器的公网IP地址。

打开浏览器后,输入地址后打开的效果:

默认用户名和密码:

arduino 复制代码
用户名:admin
密码:public

第一次登录会提示你修改新密码,如果不想设置,也可以选择跳过(公网服务器部署,还是要修改密码安全些)。

下面修改新密码:

登录成功的页面显示如下:

5.2 MQTT配置

这里可以配置MQTT的一些参数,根据自己的需求进行配置。

5.3 测试MQTT通信

新建一个客户端,点击连接。

连接之后,然后点击订阅,和发布,如果下面消息能正常的接收。说明MQTT服务器通信是已经正常,没问题了。

并且在这个页面也可以看到主题发布主题订阅的格式。

5.4 MQTT客户端登录服务器测试

接下来就打开我们自己的MQTT客户端登录MQTT服务器进行测试数据的通信。

端口选择: 1883

根据软件参数填入参数,登录,进行主题的发布和订阅。

说明: 目前还没有配置客户端认证,现在只要IP和端口输入正确,MQTT三元组可以随便输入,都可以登录上服务器的,服务器没有对三元组做校验。

EMQ X 默认配置中启用了匿名认证,任何客户端都能接入 EMQX。没有启用认证插件或认证插件没有显式允许/拒绝(ignore)连接请求时,EMQX 将根据匿名认证启用情况决定是否允许客户端连接。

然后打开EMQX的管理后台,可以看到我们的设备已经登录服务器了,名字为test1

在订阅主题的页面也可以看到我们客户端设备订阅的主题。

5.5 客户端认证配置

EMQX 默认配置中启用了匿名认证,任何客户端都能接入 EMQX。没有启用认证插件或认证插件没有显式允许/拒绝(ignore)连接请求时,EMQX 将根据匿名认证启用情况决定是否允许客户端连接。

在正式产品里肯定是要启用认证的,不然任何设备都能接入。

下面就介绍如何配置 客户端认证。

【1】打开客户端认证页面

【2】选择密码认证

【3】选择内置数据库

【4】设置认证方式(都可以默认,不用改),直接点击创建。

【5】创建成功后,点击用户管理

【6】添加用户

【7】添加成功

【8】添加完毕之后,打开MQTT客户端可以进行测试。

登录的时候,MQTT用户名和密码必须输入正确,按照上一步添加的信息进行如实填写,否则是无法登录服务器的。

5.6 客户端授权配置

客户端授权页面可以配置每个客户端(设备)的主题发布,订阅权限。限制它是否可以发布主题,订阅主题。 如果有需要就可以进行配置。

http://127.0.0.1:18083/#/authorization/detail/built\_in\_database?tab=users

【1】创建数据源

【2】选择内置数据库

【3】完成创建

【4】点击权限管理

【5】选择客户端ID,点击添加

【6】配置权限

5.7 数据转发(集成)

在集成选项里,可以对设备数据处理。 比如:转发到自己的HTTP服务器,转发到自己其他的MQTT服务器,创建规则,某些事件触发某些动作等等。

选择数据桥接。

可以把数据发送端自己的HTTP服务器,或者发送到其他的MQTT服务器。

选择HTTP服务 (如果自己有HTTP服务器,可以将数据转发给自己的HTTP服务器)。

七、MQTT客户端消息互发测试

7.1 添加2个设备

为了方便测试设备间互相订阅主题,数据收发,在客户端认证页面至少添加2个设备。我这里分别添加了test1test2

7.2 设备间测试

设备A订阅设备B的主题,设备B订阅设备A的主题,实现数据互发。

设备A的MQTT信息:

bash 复制代码
MQTT服务器地址:122.112.225.194
MQTT服务器端口号:1883
MQTT客户端ID:AAA
MQTT用户名:test1
MQTT登录密码:12345678
​
订阅主题:BBB/#
发布主题:AAA/1
发布的消息:{ "msg": "我是AAA设备" }

设备B的MQTT信息:

bash 复制代码
MQTT服务器地址:122.112.225.194
MQTT服务器端口号:1883
MQTT客户端ID:BBB
MQTT用户名:test2
MQTT登录密码:12345678
​
订阅主题:AAA/#
发布主题:BBB/1
发布的消息:{ "msg": "我是BBB设备" }

八、STM32硬件端开发

8.1 BC26模块的AT指令调试过程

BC20/BC26 开启GPS、连接MQTT服务器的AT指令发送流程。

(1)查询模块是否正常

AT
​
OK

(2)获取卡号,查询卡是否插好

objectivec 复制代码
AT+CIMI

460041052911195

OK

(3)激活网络

ini 复制代码
AT+CGATT=1

OK

(4)获取网络激活状态

objectivec 复制代码
AT+CGATT?

+CGATT: 1

OK

(5)查询网络质量

diff 复制代码
AT+CSQ

+CSQ: 26,0

OK

(6)检查网络状态

arduino 复制代码
AT+CEREG=? //检查网络状态
+CEREG: 0,1 //找网成功
OK

(7)激活GPS

ini 复制代码
激活GPS,要等一段时间
AT+QGNSSC=1

OK

(8)查询GPS激活状态

diff 复制代码
查询激活状态,1表示成功激活
AT+QGNSSC?

+QGNSSC: 1

OK

(9)获取一次GPS定位语句

ini 复制代码
AT+QGNSSRD="NMEA/RMC"
+QGNSSRD: $GNRMC,120715.00,A,3150.78179,N,11711.93433,E,0.000,,310818,,,A,V*19
OK

(10)连接MQTT服务器

ini 复制代码
AT+QMTOPEN=0,"a161a58a78.iot-mqtts.cn-north-4.myhuaweicloud.com",1883

OK

+QMTOPEN: 0,0

(11)登录MQTT服务器

ini 复制代码
命令格式: AT+QMTCONN=<tcpconnectID>,<clientID>,<username>,<password>
AT+QMTCONN=0,"6210e8acde9933029be8facf_dev1_0_0_2022021913","6210e8acde9933029be8facf_dev1","6cea55404b463e666cd7a6060daba745bbaa17fe7078dfef45f8151cdf19673d"

OK

+QMTCONN: 0,0,0

(12)订阅主题

ini 复制代码
命令格式: AT+QMTSUB=<tcpconnectID>,<msgID>,"<topic1>",<qos1>[,"<topic2>",<qos2>...]

AT+QMTSUB=0,1,"$oc/devices/6210e8acde9933029be8facf_dev1/sys/messages/down",2

OK

+QMTSUB: 0,1,0,2

(13)发布主题

ruby 复制代码
命令格式:AT+QMTPUB=<tcpconnectID>,<msgID>,<qos>,<retain>,"<topic>","<msg>"

先发送指令: 
AT+QMTPUB=0,0,0,0,"$oc/devices/6210e8acde9933029be8facf_dev1/sys/properties/repor"

等待返回 ">" 
接着发送数据.不需要加回车。
"{"services": [{"service_id": "gps","properties":{"longitude":12.345,"latitude":33.345}}]}"
数据发送完毕,再发送结束符。 十六进制的值--0x1a  。某些串口调试助手可以适应ctrl+z 快捷键输入0xA
等待模块返回"OK",到此数据发送完成。    
OK

+QMTPUB: 0,0,0

8.2 BH1750传感器

下面贴出的是 通过 I2C 接口与 BH1750 光照传感器通信,读取光敏值并通过串口打印的代码:

ini 复制代码
#include "stm32f1xx_hal.h"
#include "stdio.h"

I2C_HandleTypeDef hi2c1;
UART_HandleTypeDef huart1;

#define BH1750_ADDRESS (0x23 << 1) // BH1750地址

void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_USART1_UART_Init(void);
static void MX_I2C1_Init(void);

int main(void)
{
    HAL_Init();
    SystemClock_Config();
    MX_GPIO_Init();
    MX_USART1_UART_Init();
    MX_I2C1_Init();

    uint8_t data[2];
    uint16_t lux;

    while (1)
    {
        HAL_I2C_Master_Transmit(&hi2c1, BH1750_ADDRESS, (uint8_t[]){0x10}, 1, HAL_MAX_DELAY); // 设置单次高分辨率模式
        HAL_Delay(180); // 等待传感器测量完成

        HAL_I2C_Master_Receive(&hi2c1, BH1750_ADDRESS, data, 2, HAL_MAX_DELAY); // 读取光照值
        lux = (data[0] << 8) | data[1];

        char buffer[20];
        sprintf(buffer, "Lux: %d\r\n", lux);
        HAL_UART_Transmit(&huart1, (uint8_t *)buffer, strlen(buffer), HAL_MAX_DELAY); // 通过串口打印光照值

        HAL_Delay(1000); // 延时1秒
    }
}

void SystemClock_Config(void)
{
    RCC_OscInitTypeDef RCC_OscInitStruct = {0};
    RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

    RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
    RCC_OscInitStruct.HSEState = RCC_HSE_ON;
    RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
    RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
    RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
    RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
    if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
    {
        Error_Handler();
    }

    RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK | RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2;
    RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
    RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
    RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
    RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
    if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
    {
        Error_Handler();
    }
}

static void MX_I2C1_Init(void)
{
    hi2c1.Instance = I2C1;
    hi2c1.Init.ClockSpeed = 400000;
    hi2c1.Init.DutyCycle = I2C_DUTYCYCLE_2;
    hi2c1.Init.OwnAddress1 = 0;
    hi2c1.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT;
    hi2c1.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE;
    hi2c1.Init.OwnAddress2 = 0;
    hi2c1.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE;
    hi2c1.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE;
    if (HAL_I2C_Init(&hi2c1) != HAL_OK)
    {
        Error_Handler();
    }
}

static void MX_USART1_UART_Init(void)
{
    huart1.Instance = USART1;
    huart1.Init.BaudRate = 115200;
    huart1.Init.WordLength = UART_WORDLENGTH_8B;
    huart1.Init.StopBits = UART_STOPBITS_1;
    huart1.Init.Parity = UART_PARITY_NONE;
    huart1.Init.Mode = UART_MODE_TX_RX;
    huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
    huart1.Init.OverSampling = UART_OVERSAMPLING_16;
    if (HAL_UART_Init(&huart1) != HAL_OK)
    {
        Error_Handler();
    }
}

void Error_Handler(void)
{
    while (1)
    {
    }
}

#ifdef USE_FULL_ASSERT
void assert_failed(uint8_t *file, uint32_t line)
{
}
#endif

8.3 DHT11温湿度模块

下面贴出的是 DHT11 温湿度传感器读取环境温湿度数据并通过串口打印的代码:

ini 复制代码
#include "stm32f1xx_hal.h"
#include "stdio.h"

TIM_HandleTypeDef htim2;
UART_HandleTypeDef huart1;

#define DHT11_GPIO_PORT GPIOA
#define DHT11_GPIO_PIN GPIO_PIN_0

void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_USART1_UART_Init(void);
static void MX_TIM2_Init(void);

int main(void)
{
    HAL_Init();
    SystemClock_Config();
    MX_GPIO_Init();
    MX_USART1_UART_Init();
    MX_TIM2_Init();

    HAL_TIM_Base_Start(&htim2);

    while (1)
    {
        HAL_TIM_Base_Start(&htim2);
        HAL_Delay(2000); // 等待2秒

        // 发送开始信号
        HAL_GPIO_WritePin(DHT11_GPIO_PORT, DHT11_GPIO_PIN, GPIO_PIN_RESET);
        HAL_Delay(18);
        HAL_GPIO_WritePin(DHT11_GPIO_PORT, DHT11_GPIO_PIN, GPIO_PIN_SET);
        HAL_Delay(20);

        // 设置引脚为输入
        GPIO_InitTypeDef GPIO_InitStruct = {0};
        GPIO_InitStruct.Pin = DHT11_GPIO_PIN;
        GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
        GPIO_InitStruct.Pull = GPIO_PULLUP;
        HAL_GPIO_Init(DHT11_GPIO_PORT, &GPIO_InitStruct);

        // 等待 DHT11 响应
        while (!HAL_GPIO_ReadPin(DHT11_GPIO_PORT, DHT11_GPIO_PIN))
            ;
        while (HAL_GPIO_ReadPin(DHT11_GPIO_PORT, DHT11_GPIO_PIN))
            ;

        // 读取数据
        uint8_t data[5] = {0};
        for (int i = 0; i < 5; i++)
        {
            for (int j = 0; j < 8; j++)
            {
                while (!HAL_GPIO_ReadPin(DHT11_GPIO_PORT, DHT11_GPIO_PIN))
                    ;
                HAL_TIM_Base_Start(&htim2);
                while (HAL_GPIO_ReadPin(DHT11_GPIO_PORT, DHT11_GPIO_PIN))
                    ;
                if (HAL_TIM_Base_GetCounter(&htim2) > 40)
                    data[i] |= (1 << (7 - j));
            }
        }

        // 计算温度和湿度
        uint8_t humidity = data[0];
        uint8_t temperature = data[2];

        char buffer[50];
        sprintf(buffer, "Temperature: %d°C, Humidity: %d%%\r\n", temperature, humidity);
        HAL_UART_Transmit(&huart1, (uint8_t *)buffer, strlen(buffer), HAL_MAX_DELAY); // 通过串口打印温湿度数据
    }
}

void SystemClock_Config(void)
{
    RCC_OscInitTypeDef RCC_OscInitStruct = {0};
    RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

    RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
    RCC_OscInitStruct.HSEState = RCC_HSE_ON;
    RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
    RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
    RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
    RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
    if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
    {
        Error_Handler();
    }

    RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK | RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2;
    RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
    RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
    RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
    RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
    if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
    {
        Error_Handler();
    }
}

static void MX_TIM2_Init(void)
{
    htim2.Instance = TIM2;
    htim2.Init.Prescaler = 72 - 1;
    htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
    htim2.Init.Period = 65535;
    htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
    HAL_TIM_Base_Init(&htim2);
}

static void MX_USART1_UART_Init(void)
{
    huart1.Instance = USART1;
    huart1.Init.BaudRate = 115200;
    huart1.Init.WordLength = UART_WORDLENGTH_8B;
    huart1.Init.StopBits = UART_STOPBITS_1;
    huart1.Init.Parity = UART_PARITY_NONE;
    huart1.Init.Mode = UART_MODE_TX_RX;
    huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
    huart1.Init.OverSampling = UART_OVERSAMPLING_16;
    if (HAL_UART_Init(&huart1) != HAL_OK)
    {
        Error_Handler();
    }
}

void Error_Handler(void)
{
    while (1)
    {
    }
}

#ifdef USE_FULL_ASSERT
void assert_failed(uint8_t *file, uint32_t line)
{
}
#endif

8.4 土壤湿度传感器

通过 ADC 模块读取土壤湿度并通过串口打印的代码:

ini 复制代码
#include "stm32f1xx_hal.h"
#include "stdio.h"

ADC_HandleTypeDef hadc1;
UART_HandleTypeDef huart1;

void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_USART1_UART_Init(void);
static void MX_ADC1_Init(void);

int main(void)
{
    HAL_Init();
    SystemClock_Config();
    MX_GPIO_Init();
    MX_USART1_UART_Init();
    MX_ADC1_Init();

    uint16_t adc_value;

    while (1)
    {
        HAL_ADC_Start(&hadc1); // 启动 ADC 转换
        if (HAL_ADC_PollForConversion(&hadc1, 100) == HAL_OK)
        {
            adc_value = HAL_ADC_GetValue(&hadc1); // 读取 ADC 值
            char buffer[50];
            sprintf(buffer, "Soil Moisture: %d\r\n", adc_value);
            HAL_UART_Transmit(&huart1, (uint8_t *)buffer, strlen(buffer), HAL_MAX_DELAY); // 通过串口打印土壤湿度数据
        }
        HAL_Delay(1000); // 延时1秒
    }
}

void SystemClock_Config(void)
{
    // 略,根据实际情况配置系统时钟
}

static void MX_ADC1_Init(void)
{
    ADC_ChannelConfTypeDef sConfig = {0};

    hadc1.Instance = ADC1;
    hadc1.Init.ScanConvMode = DISABLE;
    hadc1.Init.ContinuousConvMode = DISABLE;
    hadc1.Init.DiscontinuousConvMode = DISABLE;
    hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START;
    hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
    hadc1.Init.NbrOfConversion = 1;
    if (HAL_ADC_Init(&hadc1) != HAL_OK)
    {
        Error_Handler();
    }

    sConfig.Channel = ADC_CHANNEL_0; // 修改为实际连接的通道
    sConfig.Rank = 1;
    sConfig.SamplingTime = ADC_SAMPLETIME_13CYCLES_5; // 根据实际情况调整采样时间
    if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
    {
        Error_Handler();
    }
}

static void MX_USART1_UART_Init(void)
{
    huart1.Instance = USART1;
    huart1.Init.BaudRate = 115200;
    huart1.Init.WordLength = UART_WORDLENGTH_8B;
    huart1.Init.StopBits = UART_STOPBITS_1;
    huart1.Init.Parity = UART_PARITY_NONE;
    huart1.Init.Mode = UART_MODE_TX_RX;
    huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
    huart1.Init.OverSampling = UART_OVERSAMPLING_16;
    if (HAL_UART_Init(&huart1) != HAL_OK)
    {
        Error_Handler();
    }
}

void Error_Handler(void)
{
    while (1)
    {
    }
}

#ifdef USE_FULL_ASSERT
void assert_failed(uint8_t *file, uint32_t line)
{
}
#endif

九、总结

本智慧农业管理系统设计与实现项目基于STM32F103RCT6微控制器为核心,通过集成DHT11温湿度传感器、BH1750光照强度传感器和土壤湿度检测传感器等设备,构建了一套全面的农田环境监测系统。当环境参数超出预设阈值时,系统能够实时报警并自动或提醒进行灌溉、施肥等操作,同时利用蜂鸣器发出声音警报。

在远程控制方面,系统采用NBIoT-BC26模块实现了无线通信功能,将采集到的数据传输至云端MQTT服务器,并通过EMQX开源框架搭建的服务器处理数据。用户可通过微信小程序随时随地查看农田环境的各项实时数据,实现对农作物生长环境的远程监控,并能便捷地执行手动灌溉、开启补光灯等远程控制操作。

本项目的成功实施,不仅有效提升了农业生产过程中的智能化水平,降低了人工管理成本,而且为实现精准农业和智慧农业提供了有力的技术支持。未来,随着物联网技术、云计算和人工智能技术的进一步发展,这套智慧农业管理系统将有望在更多领域推广使用,助力我国现代农业朝着更高效、智能、可持续的方向迈进。

点击关注,第一时间了解华为云新鲜技术~

相关推荐
许野平24 分钟前
Rust: 利用 chrono 库实现日期和字符串互相转换
开发语言·后端·rust·字符串·转换·日期·chrono
齐 飞2 小时前
MongoDB笔记01-概念与安装
前端·数据库·笔记·后端·mongodb
LunarCod2 小时前
WorkFlow源码剖析——Communicator之TCPServer(中)
后端·workflow·c/c++·网络框架·源码剖析·高性能高并发
码农派大星。3 小时前
Spring Boot 配置文件
java·spring boot·后端
杜杜的man3 小时前
【go从零单排】go中的结构体struct和method
开发语言·后端·golang
幼儿园老大*3 小时前
走进 Go 语言基础语法
开发语言·后端·学习·golang·go
llllinuuu4 小时前
Go语言结构体、方法与接口
开发语言·后端·golang
cookies_s_s4 小时前
Golang--协程和管道
开发语言·后端·golang
为什么这亚子4 小时前
九、Go语言快速入门之map
运维·开发语言·后端·算法·云原生·golang·云计算
想进大厂的小王4 小时前
项目架构介绍以及Spring cloud、redis、mq 等组件的基本认识
redis·分布式·后端·spring cloud·微服务·架构