基于土壤湿度信息的智能农田灌溉系统设计

自己淋过雨,想为你撑把伞

        之所以会把自己三年前的本科毕业设计发布至平台上,其主要原因是对自己以前的过往再做个总结。人生嘛,只有一路走来回头再看的时候,才会感慨万千,触目良多,时不时会想,到底什么样的结局才配得上我这二十几年的颠沛流离(狗头^_^)。个人强烈建议高中学弟学妹们一定要好好学习,考上一个都是传道授业()的好大学(表达的可能有些不妥,但懂得都懂……)。

        本文为2021年本人本科毕业设计。时间跨度为2020.12~2021.05,代码、PCB和论文等全部独立完成。白天实习,下班写论文,先感谢下上海英业达科技有限公司(用公司示波器测试过波形)。对哦,我还没有开店,不要找我说:让我把店铺链接发你。

        毕业论文信息贴图为证!!!!

        言归正传:

摘要

        淡水资源匮乏已成为全人类重点关注的问题。虽然我国有着十分辽阔的地理面积,总体水资源也相对丰富,但水资源存在着分布不均匀、人均占有量少、水资源浪费严重等诸多问题。我国作为农业大国,农业的可持续发展离不开水资源的支持,因此,加强高效节水型农业的研究,提高水资源的利用率,对我国农业的可持续健康发展有着重大意义。农田智能灌溉不仅节约用水,减小劳动力的投入,而且可以提高农产品的产量和质量,随着物联网的快速发展,无线传感技术在农田灌溉研发中被普遍利用,相比有线传输方式,无线传输更加灵活和便捷。ZigBee 技术虽然方便灵活,但数据传输距离短,5G 技术虽然有低延时、大互联等特点,但成本较高,而 WiFi 无线网络具有传输距离长、质量高和高速率的特点,因此本文利用 WiFi 通信方式和云平台技术,设计了一种基于土壤湿度传感器的智能农田灌溉系统。

        该农田灌溉智能控制系统以 STM32F1 系列单片机为核心,以土壤湿度传感器、显示屏、WiFi 模组和按键等模块为外围设备,通过软硬件相结合的设计方式把他们组成一个完整的控制系统。利用单片机的 IO 实现对外部设备的控制和各个数据点之间的通信,以实现灌溉系统主控板、机智云的服务平台和终端控制设备三者之间的数据交流,实现农田智能灌溉的目的。论文最后对全文的研究工作进行了总结,并对下一步的目标和研究方向进行了讨论和展望。

        关键字:WiFi 模块;湿度传感器;STM32;数据显示;机智云;智能控制

1.绪论

1.1 课题研究背景及意义

        随着全球经济的快速发展,人类的生活水平也得到了大幅度提升,但资源匮乏问题也日益加重。人类对水的需求量不断增加,况且浪费现象也比较严重,气候科学家认为在未来50年内全球的平均气温将会显著上升,而这一问题的出现将会对干旱和半干旱地区获取充足淡水资源的能力构成了重大威胁,因此为了保障地球上所有生命的日常生活和人类社会的可持续发展,节约用水已经成为全人类的首要任务[1]。

        目前,农田灌溉用水的利用率普遍偏低,而且绝大部分地区依旧采用传统的灌溉模式,导致了水资源的大量浪费。用于农田灌溉的淡水占全球消耗淡水总量的70%,而且农业灌溉用水量直接影响农产品的质量和产量,又与农民的经济收入有直接联系,因此,在保障农产品产量和质量的同时,提高农田灌溉效率成为缓解水资源紧张的主要方案。随着物联网、云计算、AI和人工智能等现代高新科技的快速发展,智慧农业技术的研究、实施和推广成为我国未来农业发展的必经之路[2],所谓的智慧是指在无人直接参与的情况下,通过各种算法把执行机构、处理单元、采集单元进行完美结合,对农业生产过程中出现的各种数据信息进行的自动捕获、判断和精准控制,在促进农业高效节能发展的同时,又保证了农产品的绿色健康安全,符合我国农业长期健康发展的基本国情和战略要求[3]。当下,应加强智慧农业技术的研发力度,突破智慧农业发展过程中所涉及的各项核心技术和国外垄断技术,提高我国在突发状况下的应对能力,提升我国农业生产的智能化和信息化水平,降低智能控制设备的生产成本,提供各种性价比高的智能服务系统,提高农业生产的效率、效能和效益,促进我国现代农业快速发展[4,5,6]。

        本论文主要研究在农业生产中,如何利用智能农田灌溉系统解决水资源利用率低的问题,并分析了农田灌溉用水的趋势,探讨出具体的节水方案。通过推广农田智能化灌溉,在提高水资源利用率的同时,还可以提升农作物整体的质量和产量,帮助农民创造出更好的收益,促进我国农业的健康可持续发展[7]。

1.2 农田灌溉国内外发展现状分析

1.2.1 农田灌溉国外现状

        纵观全球农业灌溉的现状,发现农田灌溉存在发展不均衡、水资源利用率低等问题,一些发达国家早已使用农田智能灌溉这项技术,对农田灌溉的管理也非常规范,利用智能系统对滴灌或喷灌进行准确控制,但也存在着农田智能灌溉的局部性,并没有普及[3]。

        美国的水资源在地理上分布极不均匀,为了减少水资源分布矛盾带来的各种社会问题,特耗费大量人力物力修建许多的各种水利工程。美国作为农业大国,为了提高农业用水的效率,利用遥感技术、地理信息系统和全球定位系统等高科技对各种农业信息进行采取、传输、分析处理和执行,计算机终端系统根据土壤湿度变化情况可以精确计算出此时距开启水泵的时间间隔和每次需要的灌水量,科学指导农业灌溉[8,9]。

        以色列位于水资源比石油都贵的中东地区,以色列为了保障国家可持续发展,致力于研究节水灌溉技术,是数字化农业发展的典型国家。从早期的明渠灌溉变成管道灌溉,再到后面的滴灌,由粗放的传统灌溉蜕变成计算机智能控制灌溉,此项技术推广的灌溉面积占总耕地面积的55%左右,给国家带来了巨大的利益,不仅解决以色列本国居民的粮食问题,还缓解了周边国家粮食匮乏的问题,以色列成为了中东地区重要的粮食出口国[10,11]。以色列不仅在滴灌技术上处于世界领先地位,其大棚种植技术也位于世界先进水平,利用计算机和传感器准确地实时监测大棚中的各种物理信息,从而控制水肥、灯光、薄膜、灯光、排风口等设备的工作状态,以便有效的调节大棚的各个环境参数,实现智能农业生产的目的。

        印度位于亚洲的南部,人口数量多,其耕地面积约为1.2亿公顷,外加水资源匮乏,印度为了缓解粮食危机,一直致力发展智能农田灌溉技术。因为管理措施得当,智能灌溉技术和施行的农田面积得到了迅速增长,缓解了粮食匮乏的问题[12]。

        日本非常重视物联网农业的研究,是典型的网络化农业国家,在2014年,其物联网型农业总面积占全国的50%左右,截至2020年,日本农业物联网技术的运用占整个农业市场的四分之三左右,物联网农业的实行,大幅度提高了农产品的生产效率,缓解人口老龄化带来的问题,为日本农业发展带来了一条崭新的道路。

        一些国际领先的农机生产公司研制出种类丰富的智能控制系统和产品,比如耐特费姆研发出可以根据农作物的生长状况及水肥情况进行精确的灌溉和施肥,节约了大量劳动力的投入,又能及时远距离监控农作物的状况,使农作物的产量和质量都得到大幅度的提高[13]。以色列 ELDAR-GAL公司研制出一款能同时采集数十种农作物生长状况和环境信息的监测仪,协助主控制系统进行调节不同农作物的生长环境 [14]。

1.2.2 农田灌溉国内发展现状

        21世纪初,我国的节水灌溉技术开始飞速发展,但智能灌溉技术与发达国家相比,我国还是处在起步阶段,整体落后。目前,我国农田主要的灌溉方式有漫灌、管灌、喷灌、微喷、滴灌、渗灌等,农田灌溉有效利用系数为0.559。我国地理面积广阔、地理情况复杂、水资源分布不均匀等因素,导致各个地区的节水灌溉推广面积和发展水平出现了极大的差距。例如我国西部新疆地区属于温带大陆性气候,降雨量偏少,导致水资源极度匮乏,为了提高农作物的产量和质量,当地大力研发和推广滴灌水肥耦合技术。而北方总耕地面积占全国的60%左右,而水资源却只占20%左右,降水量在时间上分布也极不均匀,但是农业灌溉仍然采用大水漫灌方式,利用人工来判断灌溉程度和灌溉时间,造成水资源的极大浪费 [15]。

        1990年以来,一些研究机构和企业都试图把现代科技和农田灌溉相结合,开发出一套系统来控制农田灌溉,表明我国农田灌溉方式从人工阀门控制向自动阀门控制的转变。2008年,我国启动东北节水增产、西北节水增效、华北节水限制等特大节水工程,滴灌和喷灌实施面积大量增加,到2016年底,中国喷灌面积仅占我国灌溉总面积的5.60%(中华人民共和国水利部,2016)。因此,喷灌技术在我国仍有很大的发展潜力,我国喷灌技术朝着多样化、节水化和智能化的方向发展[13]。

        我国在智能农业研究上却任重而道远,目前我国物联网技术主要应用在农业环境信息的采集和传输环节,在智能处理阶段使用率较少,没有组成一个闭环控制系统,因此并没有达到真正的智能,没有发挥出物联网技术的擅长之处,但智能灌溉的相关研究也加快了我国智能农业发展的步伐。比如蔬菜大棚的远程控制、农田远程监测系统和一些无线网络控制系统也达到了国际先进水平。发展至今,智能农业控制系统可以根据农作物的需求进行智能控制,实现对灌溉、施肥等精确控制的目的[16]。

1.3 主要研究内容及要求

1.3.1 系统设计要求

        在一个自动控制系统中,必须包含四个部分:控制器、被控对象、执行机构和变送器,要实现对农田灌溉进行智能控制的目的,第一步是利用湿度传感器采集农田湿度信息,然后对采集到的数据进行分析,微控制器针对分析的结果做出相应的指令,从而实现对当前水泵各种状态的控制。因为农田面积比较广,为了实现远程监控并把得到各种信息发送至终端或手机APP,需要借助WiFi无线网络模组和能够远距离传输的WiFi网络。在一套完整的农田智能控制系统中,一般包含三个部分:应用层、传输层和感知控制层。

        本论文的农田灌溉系统设计要求如下:

        1、制作农田灌溉系统实物,实现自动灌溉、手动灌溉和远程监控功能;

        2、分析和绘制智能农田灌溉硬件电路原理图;

        3、编写程序,调试程序,调试结果要与设计要求数据相符;

        4、器件选型:湿度传感器,ESP模块,STM32单片机,继电器等;

       5、利用单片机和继电器控制实现自动灌溉技术,是否正在灌溉用继电器外接发光二极管模拟;

        6、利用WiFi模块实时监控农作物湿度并发送到电脑端、手机APP,以实现远程监控功能;

        7、手机APP上传至云平台的数据和指令通过路由和无线网络发送至WiFi模组,MCU对WiFi模组接收到的数据进行分析,从而控制继电器的工作状态和其它命令操作;

        8、利用E2PROM对不同植物种类生存的湿度范围进行存储,MCU通过I2C通信协议完成对E2PROM中数据读写的操作;。

        9、实现人机交互界面,通过按键对当前植物种类、工作模式、继电器工作状态和湿度范围信息进行修改,并及时把相关信息显示在OLED液晶屏、存储至AT24C512以及上发至云平台和手机APP。

1.3.2 系统研究内容

        针对国内灌溉现状,在分析了智能农业的主要技术后,本论文利用WiFi无线技术、传感器技术、云平台等相关技术,从硬件和软件两个方面设计出一款基于土壤湿度信息的智能农田灌溉系统,分别对系统代码部分、硬件模块单元和整体特性进行测试和分析,测试结果符合课题提出的各项要求。在做到节约水资源的同时,又减轻了农民的负担。本课题研究的主要内容包括以下几点:

        1、首先能够采集农田的湿度信息,这是实现智能农田灌溉的首要环节。

        2、完成数据采集工作后,主控MCU需要将自己采集到的数据结合预先设置的工作模式进行判断分析和制定决策。

        3、把采集到的数据和其他参数上传至终端设备和手机APP进行显示,实现这一过程需要借助有线网络或无线网络。

        4、MCU处理手机APP下发的数据指令,从而控制不同的工作状态。

        5、系统控制板的软硬件工作原理和实现过程。

1.4 本文内容安排

        本论文有六个章节:

        第一章:引出课题的研究背景和意义,分析国内外农田灌溉的发展现状,提出设计出一款基于土壤湿度信息的智能农田灌溉系统的设计要求和研究内容,并简述该灌溉系统在农业发展过程中带来的意义。

        第二章:提出控制板的各项设计原则,设计总体方案,并绘制系统硬件结构图。

        第三章:根据系统设计要求,选取各模块的具体型号并阐述型号选取原因,同时根据各模块的官方数据手册掌握其工作原理并设计出对应的硬件原理图。

        第四章:在机智云平台中开发智能灌溉控制项目,参考正点原子和野火的教学视频和HAL库开发书籍,编写并调试各模块的代码,最后烧录程序。

        第五章:对智能灌溉控制系统的各单元模块和系统整体特性分别进行测试分析,并展示测试数据图。

        第六章:总结全文工作,提出本系统的不足之处和大致解决方法,并对以后的研究内容进行了展望。

2.系统设计原则及总体方案设计

2.1 系统设计原则

        针对农田灌溉面临的实际问题,根据需求进行总体分析,设计出一种基于土壤湿度信息的智能农田灌溉系统。该智能灌溉系统面向的对象是农民,他们可能缺乏相关专业知识,对系统的运行原理不太了解,因此必须遵循以下设计原则进行设计:

        1、简约性:在满足设计要求的基础上,APP控制界面尽可能操作简单;

        2、扩展性:不同农作物的生存环境有所差异,必须增加数据存储功能;

        3、实时性:智能灌溉主板与终端设备和手机APP的数据传输要实时进行;

        4、低功耗:考虑到农田环境的复杂性,大面积的修建电力设施也不太现实,因此多采用太阳能供电装置,为了满足智能灌溉系统在连续阴雨天也能正常工作的要求,因此智能灌溉系统必须满足功耗低的要求;

        5、低成本:研发智能灌溉技术过程中会消耗大量资金和时间,因此在满足设计要求的前提下,最大程度地减少成本,避免出现农民用不起的情况发生。

2.2 系统设计方案

        系统总体结构图分为:手机APP、机智云服务平台、无线网络、控制板和水泵。其总体方案图如图2-1所示。

图2-1:系统总体结构图

3. 系统硬件电路设计

3.1 开发工具

        随着电子技术的快速发展,电路板的层数变得越来越多,对覆铜的面积、钻孔对、线宽和各种高速差分线的布局布线要求越来越严格,集成度越来越高,传统手工已经无法完成高质量、高精度电路板的设计,因此工程师都使用快捷、高效的EDA(Electronic Design Automation)设计软件来进行原理图绘制、布局布线、电路仿真等[17]。

        工欲善其事,必先利其器,目前主流的EDA开发软件都是国外的,比如:Cadence、PADS以及我毕业设计使用的Altium Designer等。有些国家就用禁止使用相关软件的手段来打压中我国高新技术企业,比如美国禁止中兴使用Cadence。国产的立创EDA,由于起步相对较晚,技术相对不成熟,企业使用率比较低,因为免费,同样吸引了学生和电子爱好者的青睐。本系统使用Altium Designer17来完成原理图和PCB的设计,Altium Designer是Altium公司研发的一款电子产品开发系统,该软件把原理图设计、电路仿真、布局布线、信号完整性分析和设计输出等技术完美的融合为一体,因为操作简单、界面布局规范等特点,因此可以轻松设计出自己需要的电路板,使用此软件可以提高电路设计的质量和效率 [18]。

3.2  单片机IO口的分配和最小系统

3.2.1  STM32F1简介

        本系统的主控采用意法半导体(ST)推出的一款基于32位的RISC内核增强型MCU,型号为STM32F103C8T6,采用LQFP48封装,工作频率最高为72MHz,内置128Kbytes的FLASH和20Kbytes的SRAM高速存储器。STM32自带各种通信接口,比如 UART、I2C、SPI等,在各方面都远比传统51单片机优越。我们日常生活很多电器的主控板都有STM32的身影,比如智能玩具车、全自动洗衣机、智能门锁、mini飞行器、智能可穿戴设备、3D打印机等。到目前为止,STM32得到众多工程师和市场的青睐,是32位微控制器金字塔中的顶尖存在 [19,20]。ST部分32位微控制器分类表如表3-1所示。

表3-1: 32位MCU家族部分分类表

CPU位数

内核

系列

描述

32位

Cortex-M0

STM32F0

入门级

STM32L0

低功耗

Cortex-M3

STM32F1

基础型

STM32F2

高性能

STM32L1

低功耗

Cortex-M4

STM32F3

混合型

STM32F4

高性能

STM32L4

低功耗

Cortex-M7

STM32F7

高性能

3.2.2  IO口的分配

        在设计系统原理图之前,一定要根据各模块之间的通信方式先把主控的引脚分配好,然后再开始设计原理图,这样可以把出错率降至最低,引脚分配具体见表3-2。

表3-2: STM32F103C8T6引脚分配表

引脚分配

引脚功能说明

电源IO

VBAT、VDD,VSS、VDDA,VSSA

晶振IO

HSE (8MHz),LSE (32.768KHZ)

下载IO

采用SWD下载方式:CLK,DIO

BOOT IO

BOOT0、BOOT1,用于设置系统的启动方式

复位IO

NRST,用于外部复位

上面五部分组成的系统就是单片机最小系统,IO功能不能随便改变

OLED IO

采用模拟I2C,不用特定IO口(PB6, PB7)

E2PROM IO

采用模拟I2C,不用特定IO口(PA2, PA3)

湿度模块IO

ADC采集,特定IO口(PA1:ADC1_CH1)

ESP-12F IO

与MCU通信方式为串口,UART3特定IO口(PB10,PB11)

串口调试IO

串口UART1,特定IO口(PA9,PA10)

按键IO

不用特定IO口(PA5, PA6, PA7)

继电器IO

不用特定IO口(PB1)

蜂鸣器IO

不用特定IO口(PB0)

LED IO

不用特定IO口(PA8)

3.2.3  单片机最小系统

        单片机最小系统是用最少的元器件组成可以正常工作的单片机系统。最重要的三个部分是电源(STM32的工作电压为3.3V)、晶振(分为HSE:8MHz,LSE:32.768KHz)、复位电路(低电平有效)。本控制系统的最小系统电路图如图3-1所示。

图3-1:STM32单片机最小系统

3.3  电源模块电路

        电子电路中的供电系统就和人体中的各种能量一样重要,供电系统直接关系到硬件电路是否能够稳定运行,而且一般各个功能单元所需要的工作电压也有所差异,如本系统中的OLED显示屏、继电器、蜂鸣器和运算放大器采用直流5V电压供电,其它外部硬件模块需要用到的直流电压为3.3V。因此需要采用两次稳压管理,接下来将分别介绍两种供电电路。

3.3.1  LM2596-5V稳压电路

        LM2596-5V是电源管理集成电路的开关降压调节器,最大输出电流为3A,具有良好的线性和负载调节特性[21]。在此元器件的外围加入极少的元器件就可以组成一个高效的稳压电路,其最大输入电压不超过40V,为了降低稳压IC的发热量,本系统采用12V电压供电,转化效率为80%。LM2596具体的电气特性参数见表3-3。系统控制板上12V转5V稳压电路图见图3-2。

表3-3:LM2596电气特性参数图

LM2596-5.0V ([Note 1] Test Circuit Figure 2)

Output Voltage(Vin=12V, ILOAD =0.5A, TJ =25℃)

Vout

4.9

5.0

5.1

V

Output Voltage(8.0V≤Vin≤40V,0.5A≤ILOAD≤3.0A)

Vout

-

-

-

V

TJ =25℃

-

4.8

5.0

5.2

TJ =-40℃ ~ +125℃

4.75

-

5.25

Efficiency (Vin=12V, ILOAD =3.0A)

η

80

%

图3-2:12V-5V稳压电路

3.3.2  AMS1117-3.3V稳压电路

        AMS1117-3.3是一种正向高效率线性稳压芯片,常用于微控制器的电源管理单元、嵌入式系统硬件供电单元、终端系统的电源管理和常见的各种电池供电设备,输出电压为3.3V,其具体电气特性参数见表3-4。

表3-4:AMS1117电气特性参数

AMS1117-3.3

MIN

TYP

MAX

V

IOUT=10Ma,VIN=5V,TJ=25℃

3.267

3.300

3.333

0≤IOUT≤800Ma,4.75V≤VIN≤10V

3.235

3.300

3.365

        安信可提出WiFi模组的供电要求,工作电压为直流3.3V,工作峰值电流大于500mA,而单路AMS1117线性DC-DC电路的最大输出电流为800mA, STM32单片机的最大工作电流为150mA,如果共用同一组稳压电路,其总电流接近AMS1117-3.3输出电流的极值。综合考虑后在成本和功能性之间选择了后者,采用双路LDO线性稳压电路,一方面为了更好的降低WiFi模组和其它需要3.3V电源供电的元器件之间的互扰情况,另一方面防止AMS1117长时间工作在临界状态而出现IC发热严重,甚至造成IC损坏的局面。MCU和WiFi模组电气参数如表3-5所示。系统3.3V稳压电路原理图如图3-3所示。

表3-5:MCU和WiFi模组电气参数

Mode

Symbol

Ratings

Max.(mA)

STM32

IVDD

Total current into VDD /VDDA  power lines

150

IVSS

Total current out of VSS  ground lines

150

IIO

Output current sunk by any I/O and control pin

25

Output current source by any I/Os and control pin

-25

IINJ(PIN)

Injected current on five volt tolerant pins

-5/+0

Injected current on any other pin

±5

ΣIINJ(PIN)

Total injected current

±25

ESP8266

VCC

Total current into VCC power lines

>500

GND

Total current out of GND ground lines

>500

图3-3:5V-3.3V稳压电路图

3.4  继电器&蜂鸣器&LED电路

3.4.1  继电器电路

        电磁继电器内部有铁芯和绕组组成,线圈的两端通电产生磁场,吸引衔铁,使常开端闭合,常闭端断开,工业生产中利用继电器实现用低压、小电流控制高压、强电流的目的。因为毕业设计主控板上继电器的另一端接的是水泵,工作电压为380V,为了避免或降低继电器在闭合和断开的瞬间对单片机所产生的干扰,因此在继电器的控制端加入光耦进行干扰隔离,使水泵对MCU的干扰降至最低。

        因为单片机的GPIO输出的电流不足以直接取得继电器,从控制成本和实用性的情况下考虑,智能灌溉主控板采用三极管对电流放大的方案,实现单片机间接控制继电器的目的。电路原理图如图3-4所示。

图3-4:继电器驱动电路

3.4.2  蜂鸣器电路

        智能灌溉控制板利用蜂鸣器实现报警的功能。蜂鸣器分为有源和无源,有源蜂鸣器内部有振荡电路,如果蜂鸣器的两个引脚之间存在正向压差,蜂鸣器就会发出声音;而无源蜂鸣器内部没有振荡电路,因此无源蜂鸣器必须在PWM脉冲控制下才能发出声音,改变PWM的占空比可以发出不同的音色。在控制板中,蜂鸣器只是单纯的起到报警的功能,因此使用有源蜂鸣器就可以满足要求,同时也减小了CPU的资源。驱动电路同样采用三极管驱动的方式,蜂鸣器电路图如图3-5所示。

图3-5:蜂鸣器驱动电路图

3.4.3  LED电路

        LED常常用来提示系统是否正常工作,本设计也不例外。系统板载两个LED灯,一个提示板子是否正常上电,另外一个提示单片机是否正常工作。电路连接图如图3-6所示。

图3-6:LED电路原理图

3.5  按键控制电路

        为了满足按键可以修改工作模式、湿度信息等功能,特采用GPIO的输入捕获完成按键的读取。普通机械按键按下和弹开瞬间都会出现抖动过程,抖动时间一般为5ms~10ms,按键波形见图3-7左。既然有抖动,为了防止误操作,就需要对按键进行消抖,消抖的方法包括软件消抖和硬件消抖。软件消抖是通过编写简单延时函数或者采用状态机的方式实现消抖,而硬件消抖是利用电阻电容的充放电过程,对按键抖动波形进行滤波。本系统的按键部分采用软件和硬件相结合的方式进行消抖。电路原理图见图3-7右。

图3-7:按键抖动(左)和电路原理图(右)

3.6  湿度采集电路

3.6.1  湿度传感器

        土壤湿度传感器作为系统控制板的信息采集单元,通过检测土壤的介电常数来获取土壤的含水率,将其转化为电压信号进行传输,具有易操作、速度快、自动化程度高、数据准等特点,是研究设计农田智能灌溉系统中的最重要一环[24]。

        土壤湿度传感器包括电阻式土壤湿度传感器和电容式土壤湿度传感器,其中电容式湿度传感器的特点是滞后小,线性度高,反应快,尺寸小,工作湿度范围大,但是存在稳定性不理想的毛病。输出信号的电压范围为0~3V,电容式土壤湿度传感器的实物和电路原理图3-8如图所示。

图3-8:电容式土壤湿度传感器模块实物图和电路图

        与电容式相比,电阻式土壤湿度传感器具有构造简单,价格低,设计自由度大等优点,温度特性相比较大,因此需要温度补偿,同时湿度探头也易腐蚀。输出信号的电压范围为0~5V,电阻式土壤湿度传感器的实物和电路原理图3-9如图所示。为了使系统控制板兼容不同的工作要求,特预留两个接口,可以随意改变接入土壤湿度传感器的种类。

图3-9:电阻式土壤湿度传感器模块实物图和电路图

3.6.2  运算放大器

        智能灌溉控制板为了兼容电阻式土壤湿度传感器和电容式土壤湿度传感器的不同工作电压,避免ADC采集的模拟量输入电压超过3.3V而烧坏单片机内部IO,特增加了LM358运算放大器部分,把湿度传感器模块输出0至5.0V的电压值同比例缩小至0至3.3V范围,然后再接至单片机的ADC引脚进行模拟量的采集。LM358芯片内部有两路独立、高增益的运算放大器,可以在双电源或电源电压范围很宽的单电源电路中稳定工作。在 Multisim14.0 的环境下对LM358运放电路进行设计和仿真,电路如图3-10所示。

图3-10:Multisim14.0环境下搭建的运放仿真测试图

假设3-10图中的运算放大器工作在理想状态下,输入电压Vi通过电阻R2加至运算放大器的同相输入端IN+,输出电压V0通过R1反馈到运算放大器的反相输入端IN-,组成电压串联负反馈放大电路[25]。

        根据等式(7),可知当土壤湿度传感器输出最大模拟量电压5V时,经运算放大器同比例缩小后变为3.3V,满足单片机ADC的电压采集范围,符合上图3-10的仿真结果。在实际工程中,系统板不支持电容式传感器和电阻式传感器同时工作,因此只使用LM358一路运算放大部分就满足系统设计要求。灌溉系统板中运算放大器电路连接图如图3-11所示。

图3-11:运放电路图

3.7  液晶显示和存储器电路

3.7.1  OLED简介

        有机发光二极管(OLED)是指有机半导体和发光材料在电场的作用下,通过注入和复合载流子而出现发光的现象。OLED模块由基板、阳极、阴极、电子注入层、空穴注入层、空穴传输层、电子阻挡层、电子传输层、空穴阻挡层、发光层等部分构成,具有功耗低、响应快、视角宽、分辨率高等特点[22]。本系统利用0.96寸的OLED液晶显示屏进行各种信息的显示,该液晶屏可以采用2线制I2C进行数据通信,操作简单,易于维护。0.96寸OLED模块内部原理图如图3-12所示:

图3-12:0.96寸OLED模块原理图

3.7.2  E2PROM简介

        在智能灌溉系统板设计过程中,考虑到不同农作物的生存环境也有所差异的问题,故决定在控制板上增加数据的存储功能。目前市面上的存储IC种类繁多,比如FLASH、EEPROM、磁盘等,其中FLASH存储器需要采用四线制SPI通信,E2PROM采用二线制IIC总线接口与单片机进行数据传输,因为STM32F103C8T6单片机可以使用的GPIO数量有限,因此智能灌溉控制主板上采用的存储芯片为EEPROM类型,型号为AT24C512,具有价格低、功耗小、操作简单等优点,其数据的读写都是使用电路进行操作,不需再外加其它设备来协助完成,而且可以按字节为单位修改数据,无需整个芯片擦除[23]。AT24C512芯片引脚图如图3-13所示。

图3-13:AT24C512芯片引脚图

        本次设计主控板A0、A1、A2直接与GND相连。根据数据手册可知该ROM芯片在一条I2C总线上有唯一的设备地址:1010_000+0或1,其中0代表写操作,1代表读操作。

3.7.3  电路连接方式

        系统板上AT24C512和0.96寸OLED模块电路原理图如图3-14所示。

图3-14:ROM和OLED硬件电路图

3.8  WiFi模组电路

3.8.1  乐鑫ESP8266EX简介

        乐鑫科技有限公司研发的ESP8266EX提供了高度集成的WiFi解决方案,MCU包括天线开关、射频、放大器、滤波器、电源管理等模块,具有功耗低、性能好等特点。ESP8266EX的GPIO控制方式和STM32操作方法相似,通过配置不同的寄存器可以分配出不同的功能。ESP8266EX的功能结构如图3-15所示。

        当ESP8266EX只负责无线上网的功能时,可以与任何MCU协作运行,只需要把相应的通讯接口互相连接即可,除了具有WiFi通信功能,ESP8266EX内部集成了32位微控制器和片上RAM,拥有强大的处理和存储能力[26]。ESP8266有两种工作模式:

        1、Stand-Alone 模式:ESP8266直接作为主控,可以独立运行;

        2、SIP模式:ESP8266 作为从芯片,辅助主设备接入WiFi,采用串口、SPI、I2C等通信方式与主芯片通讯。

        本设计模组采用SIP模式,主芯片STM32利用UART3与ESP8266进行通信。

图3-15: ESP8266EX功能结构图

3.8.2  安信可ESP-12F模组简介

        ESP-12F是一款拥有极小的封装尺寸和超低功耗的透传模块,在智能交通、智能家具、可穿戴设备、工业控制等领域都有它的身影。模块支持板载天线、IPEX接口和邮票孔接口三种形式[26]。ESP12-F模组引脚说明见表3-6。ESP-12F模组3D模型如图3-16所示。

表3-6:ESP12F模组引脚说明

PIN

Function

Description

1

RXD

UART_RXD,接收

2

TXD

UART_TXD,发送

5

RESET

外部复位信号,低电平复位,高电平工作(默认高)

6

GND

电源负极

8

VCC

电源正极

9

ANT

WiFi天线

11

GPIO0

下拉:下载模式;悬空:工作模式;

12

ADC

模数转换,输入范围:0V-1V

13

GPIO15

下拉时为工作模式

14

CH_PD

高电平:工作;低电平:模块供电关掉

15

GPIO2

开机上电时必须为高电平,禁止硬件下拉

ESP-12F模块工作模式有 STA、AP和STA+AP三种:

        1、STA 模式:模块连接互联网,手机或电脑利用互联网与WiFi模块进行远程通信。

        2、AP 模式:手机或电脑连接模块生成的热点,实现局域网通信。

        3、STA+AP 模式:上面两种模式相结合,实现互联网和局域网控制之间无缝切换,操作方便[27]。

ESP8266EX 模组内部电路原理如图3-17所示,本毕业设计采用STA模式。

图3-16:ESP-12F模组3D模型图

图3-17:ESP8266EX 模组内部电路图

3.8.3  WiFi模组电路图

        为了模块与单片机通信不互相产生干扰,增加小信号二极管进行隔离,预留WiFi模块的固件下载接口,WiFi模组与单片机的电路连接图如图3-18所示。

图3-18: WiFi模组与单片机的电路连接图

3.8.4  布局布线时的注意事项

        在PCB布局布线时,需要考虑模组在PCB板上放置的位置,尽可能减小电路板对模组射频部分产生的影响,因此,模组的天线部分延伸到PCB板框外或者挖空处理,模块要远离功率元器件、电磁器件,模块的GND和功率元器件、电磁器件的GND分开走。建议模组在PCB板上的位置如图3-19所示。

图3-19:模组在底板放置示意图

4、系统软件设计

4.1  机智云平台开发

4.1.1  机智云简介

       机智云是全球领先物联网开发和云服务平台,高新技术企业和物联网研发机构。机智云服务平台为开发者提供了一套自助式开发工具与开放的云端服务,其主要包括机智云云端、机智云设备端和SDK三个部分,如图4-1所示,其中WiFi设备包包含GAgent和微控制两部分,GAgent位于设备、云服务平台与手机APP之间的数据传输层,主要作用是为了数据的正常传输,而微控制器则负责与硬件电路进行数据交换和控制[26]。

图4-1:机智云平台的基本构造

4.1.2  项目开发

        在机智云平台上开发属于自己的产品。首先确定需要实现的大致功能,再进行细微修改,本设计功能分为:控制模式选择、手动控制开关、湿度信息调整、植物种类修改、水泵工作状态和当前湿度百分比显示。机智云平台建立的数据点包括以下几部分:

        1.控制模式选择:枚举类型,可写,用来控制当前工作模式,1:代表自动控制,0:代表手动控制;

        2.手动控制开关:布尔类型,可写,只有在工作模式为手动模式时才可以进行操作;

        3.植物种类选择:枚举类型,用来选择当前植物种类;

        4.湿度最大值和最小值:数值类型,范围1至100,在自动工作模式下可进行正常修改,否则强制为1,为了避免修改湿度时出现最小湿度大于最大湿度值,采用if判断,,同样湿度最大值也不能小于湿度最小值;

        5.当前湿度百分比:数值类型,只读,单片机ADC把采集到的模拟量转换成数字量后进行计算,转换为百分比的形式显示;计算方式为 Hum = -0.01*A+B;

        6.显示当前水泵工作状态:枚举类型,只读,和继电器工作状态保持一致。

        机智云平台创建的数据点如图4-2和表4-1所示。

图4-2:创建的数据点

表4-1:机智云平台数据点

显示名称

标识名

备注

读写类型

数据类型

控制模式选择

Control_mode

控制模式选择

可写

枚举

手动控制模式

Manual_control

手动控制模式

可写

布尔值

自动控制模式

Automatic_control

自动控制模式

可写

布尔值

植物种类选择

Crops_kind

农作物种类选择

可写

枚举

最低湿度值

Irrigate_Humidity_MIN

最低灌溉湿度值

可写

数值

最高湿度值

Irrigate_Humidity_MAX

最高灌溉湿度值

可写

数值

湿度百分比

Humidity

土壤湿度百分比

只读

数值

工作状态显示

Work_condition

水泵工作状态

只读

枚举

        完成数据点的创建之后,需要导出MCU开发需要的xx.c和xx.h初始代码文件,并添加到自己的项目工程中。下载MCU开发包界面如图4-3所示。

图4-3:MCU代码包导出界面

4.1.3  WiFi模组的固件烧写

        在机智云官方网站的下载中心,下载需要的固件版本,用USB-TTL串口转换器把电脑和WiFi模组连接在一起,通过安信可下载工具对模块进行固件烧录,目前机智云固件的版本为04020034,下载界面如图4-4所示。

图4-4:机智云固件版本

        固件下载完成之后,利用安信可FLASH烧录工具对WiFi模组进行固件的烧录,烧录步骤如下:

  1. 电脑插上USB转TTL电平工具,并RX、TX连接好;
  2. 选择.BIN文件,并填写固件存储首地址:0x00;
  3. 设置SPI速度;
  4. 设置SPI模式;
  5. 设置WiFi模组板载FLASH大小;
  6. 设置串口号和波特率;
  7. 设置WiFi模组特定GPIO口的电平;
  8. 开始烧录固件。

软件烧录界面如图4-5所示。

图4-5:WiFi固件烧录界面

4.2  STM32库函数开发

4.2.1  HAL库简介

        HAL的全称是Hardware Abstraction Layer (硬件抽象层),其目的是为了确保整个STM32系列拥有最大化的移植能力。HAL位于系统内核与硬件电路之间的接口层,其目的是将硬件电路抽象化。HAL驱动层提供了一个通用的简单API集与上层的应用程序、库或栈进行数据交互,HAL驱动程序的API分为两类,为STM32系列提供通用函数的通用API和定制函数的扩展API。例如,通信外设包含用于初始化和配置外设、以轮询方式管理数据传输、处理中断或DMA以及管理通信错误的API等,HAL驱动层检查所有函数的入口值来检测单片机运行时的状态,增强了HAL库的稳定性,也利于用户程序的开发和调试。所有API都带有用户回调函数,API可以调用用户回调函数来执行外围系统的初始化、反初始化等操作[19,20]。STM32F1的HAL库文件描述如表4-2所示。

表4-2:STM32F1的HAL库文件描述

STM32F1xx HAL库文件描述

类型

文件名

描述

是否必须

启动文件

stm32f103xx.s

启动文件,引导进入systemInit和main函数

外设和HAL

库相关文件

stm32f1xx.h

顶层头文件,根据芯片型号包含对应头文件

stm32f103xx.h

真正的顶层头文件,外设寄存器定义

system_stm32f1xx.h

主要是存放系统初始化函数SystemInit

system_stm32f1xx.c

sm32f1xx_hal.h

HAL库通用API比如(HAL_Init,HAL_DeInit,

HAL_Delay等)

sm32f1xx_hal.c

stm32f1xx_ppp.h

外设HAL库操作API头文件和源文件,每个外设对应一个源文件和头文件。

stm32f1xx_ppp.c

stm32f1xx_hal_ppp_ex.h

拓展的外设API头文件和源文件

stm32f1xx_hal_ppp_ex.c

stm32f1xx_II_ppp.h

在一些复杂外设中实现底层功能,它们在stm32f1xx_hal_ppp.c中被调用

stm32f1xx_II_ppp.c

stm32f1xx_hal_conf.h

外设头文件引入,以太网和时钟相关常量定义

内核头文件

core_cm3.h

主要是内核寄存器定义,例如Systick,SCB

core_cmFunc.h

CMSIS定义内核操作,一般不需要了解

core_cmInstr.h

core_cmSimd.h

cmsis_armcc.h

用户程序

文件

main.c

存放main函数,不一定要放这个文件

stm32f1xx_it.h

用户中断服务函数存放位置(不一定放这里)

stm32f1xx_it.c

stm32f1xx_hal_msp.c

回调函数存放文件

4.2.2  MDK开发环境搭建

        目前主流的STM32编译器有ARM公司的MDK,瑞典的IAR以及ST官方推出的Stm32cube IDE,各有优缺点,本系统的软件部分采用ARM公司的MDK作为开发环境,在安装完成MDK软件后,还需要安装芯片相应的支持包。MDK是ARM公司为常用的MCU微控制器开发的一款系统的软件开发解决方案,其中包括项目工程的创建,添加所有组件和代码编译调试 [19,20]。智能灌溉控制板采用STM32F103C8T6为主控,因此在新建工程时必须选择对应的型号,然后再进一步的进行软件开发。工程开发步骤如下:

  1. 安装MDK软件(不要出现中文路径);
  2. 安装STM32F1组件包;
  3. 新建工程并选取芯片型号;
  4. 添加官方HAL库library;
  5. 编写自己的代码;
  6. 代码仿真,下载验证。

4.2.3  代码流程图

        智能灌溉控制板的代码流程图如图4-6所示。

图4-6:MCU代码流程图

4.2.4  系统时钟程序

        在单片机系统中,系统时钟的地位相当于人体中的心脏,因此系统时钟的状态将直接决定单片机是否可以正常工作,对于STM32而言,系统时钟基准的输入分为:外部高速时钟(HSE)、外部低速时钟(LSE)、内部高速时钟(HSI)、内部低速时钟(LSI)。由于单片机内部集成的RC振荡器会受到单片机内部温度的影响(简称温漂),因此为了使单片机工作状态稳定,本系统采用外接8M的高速晶振(HSE)和32.768K的低速时钟(LSE)的方案。因为STM32F103C8T6支持的最大时钟速度为72MHz,而外接晶振的频率只有8MHz,为了满足STM3F1对SYS_CLK时钟速度要求,所以需要利用内部PLL锁相环把外接高速时钟的频率8MHz变成系统时钟72MHz[19,20]。单片机内部时钟树如图4-7所示。

图4-7:STM32系统时钟树

        单片机的内部高速时钟二分频和外部高速时钟是PLL锁相环时钟的来源,通过操作时钟配置寄存器来选取时钟源。本程序选外部高速时钟作为PLL的时钟来源,则系统时钟SYS_CLK=PLLCLK=72M[19,20]。系统时钟代码编写步骤为:

  1. 选取晶振的时钟源;
  2. 打开时钟源(工作);
  3. 进行时钟频率分频;
  4. 开启PLL锁相环;
  5. 对PLL进行倍频;
  6. 设置系统时钟源来自PLL;
  7. 分别设置APB1、APB2、AHB的时钟;
  8. 调用HAL库里的初始化函数。

具体代码如下:

void Stm32_Clock_Init(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.HSIState = RCC_HSI_ON;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
  RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
  HAL_RCC_OscConfig(&RCC_OscInitStruct)
  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;
  HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2);
}

4.2.5  蜂鸣器&继电器&LED程序

        STM32F1的IO模式可以由软件配置成浮空输入、上拉输入、下拉输入、模拟输入、开漏输出、推挽输出、推挽复用、开漏复用中的任何一种,蜂鸣器、继电器和LED的初始化程序都大致相同,只阐述一下关于LED的初始化和控制代码,在编写代码之前必须先选择所使用IO口的工作模式。IO口在输出高电平的情况下,LED才会亮,采用推挽输出,上拉模式,使LED在默认状态下就工作[19,20]。单片机内部GPIO结构图如图4-8所示。

图4-8:GPIO内部结构图

        LED初始化步骤:

        1、初始化所在时钟;

        2、选择对应引脚;

        3、设置输出模式;

        4、设置上拉模式;

        5、设置输出速度;

        6、调用HAL初始化GPIO。

        LED初始化代码如下:

void LED_Init(void)                                        //  LED初始化函数
{
    __HAL_RCC_GPIOA_CLK_ENABLE();                          //  使能GPIOA的时钟
    GPIO_InitTypeDef GPIO_Initure;          
    GPIO_Initure.Pin = GPIO_PIN_8;                         //PA8
    GPIO_Initure.Mode = GPIO_MODE_OUTPUT_PP;               //推挽输出
    GPIO_Initure.Pull =   GPIO_PULLUP;                     //上拉模式
    GPIO_Initure.Speed =     GPIO_SPEED_FREQ_MEDIUM;       //中速输出
    HAL_GPIO_Init(GPIOA,&GPIO_Initure);                    //LED初始化
}

        为了让操作LED状态的代码简介,利用三目运算实现对IO口的输出电平进行重定义,只需要控制n的真假就可以控制LED的亮灭,代码如下:

#define LED(n)(n? HAL_GPIO_WritePin(GPIOA,GPIO_PIN_8,GPIO_PIN_SET) : HAL_GPIO_WritePin(GPIOA,GPIO_PIN_8,GPIO_PIN_RESET) )

4.2.6  按键输入程序

        采用软硬件结合的消抖方法进行代码编写,步骤如下:

        1、先判断是否按下;

        2、延时10ms;

        3、再判断刚刚的按键是否还处于按下状态;

        4、返回按键值。

        按键消抖代码如下(GPIO初始化与LED类同,不在阐述):

uint8_t KEY_SCAN(uint8_t mode) 
{
    static uint8_t key_up=1;                              //按键松开标志
    if(mode==1)key_up=1;                                  //支持连按
if(key_up&&(KEY1_READ==1||KEY2_READ==1||KEY3_READ==1))
    {
       delay_ms(10);
       key_up=0;
       if(KEY1_READ==1)           return KEY1_PRES;      //UP按键
       else if(KEY2_READ==1)      return KEY2_PRES;      //down按键
       else if(KEY3_READ==1)      return KEY3_PRES;      //enter按键
    }
    else if(KEY1_READ==0||KEY2_READ==0||KEY3_READ==0)
           key_up=1;
    return 0;                                            //无按键按下
}

4.2.7  ADC采集程序

        ADC,模拟量转换为数字量,STM32F103C8T6内部的ADC外设非常强大,外挂12个模数转换通道,其中包括10个外部模拟信号源输入通道和2个内部信号源转换通道(包括内部温度传感器信号和电压检测单元信号),采集电压范围为VSSA至VDDA,操作ADC的相关寄存器可以设置成单次转换模式、连续转换模式、扫描或间断转换模式。外部或内部模拟电压信号经ADC转换后得到一串二进制数,设置ADC的注入通道数据偏移寄存器的值可以实现左对齐或右对齐得方式把转换得到的二进制数存储在16位数据寄存器中。ADC输入时钟是经过PCLK2时钟(72MHz)分频产生,最大可设置为14M,采样时间通过操作ADC采样时间寄存器进行设置,ADC采样时间最小可设置为1.5倍的时钟周期,则ADC转换时间公式为:T=采样时间+12.5个时钟周期[19,20]。

        本系统的ADC时钟是由PCLK2经6分频后得到的12M时钟,为了获得较高的准确度将采样时间设置为239.5个周期,则转换时间T=239.5周期+12.5周期=252周期=21us。单个ADC结构框图如图4-9所示。

图4-9:ADC结构框图

        土壤湿度传感器输出的模拟量电压范围为0~5V,经过运算放大器的同比例缩小后,变成0~3.3V的范围,模拟量经过ADC转换后,得到一串数字值,STM32单片机的ADC分辨率是12位,当数据寄存器满量程时则对应输入电压为3.3V,对应的十进制数为2^12=4096。假设转换后的数值为ADC_NUM ,且ADC_MAX1、ADC_MIN1分别对应的土壤湿度传感器在湿度0%和100%情况下所对应采样值,那么会有一个等式成立:湿度百分比=(100-(ADC_NUM-ADC_MIN1)*100/(ADC_MAX1-ADC_MIN1));

        ADC代码编写的步骤:

  1. 使能对应ADC的时钟;
  2. 设置ADC触发源;
  3. 设置采样模式;
  4. 调用HAL库初始化代码;
  5. 初始化对应GPIO;
  6. 获取采样值,并多次转换求平均值;
  7. 转化为对应的湿度百分比。

        ADC初始化代码如下:

void MY_ADC_Init(void)                                      //初始化ADC
{
    ADC1_Handler.Instance=ADC1;                             //ADC1
    ADC1_Handler.Init.DataAlign=ADC_DATAALIGN_RIGHT;        //右对齐
    ADC1_Handler.Init.ScanConvMode=DISABLE;                 //非扫描模式
    ADC1_Handler.Init.ContinuousConvMode=DISABLE;           //关闭连续转换
    ADC1_Handler.Init.NbrOfConversion=1;                    //只转换规则序列1
    ADC1_Handler.Init.DiscontinuousConvMode=DISABLE;        //连续采样模式
    ADC1_Handler.Init.NbrOfDiscConversion=0;                //不连续采样通道数为0
    ADC1_Handler.Init.ExternalTrigConv=ADC_SOFTWARE_START;  //软件触发 
    HAL_ADC_Init(&ADC1_Handler);                            //初始化   
}

        获取ADC的采样值,代码如下:

uint16_t Get_Adc(uint32_t ch)  
{
    ADC_ChannelConfTypeDef ADC1_ChanConf;
    ADC1_ChanConf.Channel=ch;                                  //通道
    ADC1_ChanConf.Rank=1;                                      //1个序列
    ADC1_ChanConf.SamplingTime=ADC_SAMPLETIME_239CYCLES_5;     //采样时间              
    HAL_ADC_ConfigChannel(&ADC1_Handler,&ADC1_ChanConf);       //通道配置
    HAL_ADC_Start(&ADC1_Handler);                              //开启ADC
    HAL_ADC_PollForConversion(&ADC1_Handler,10);               //轮询转换 
    return (uint16_t)HAL_ADC_GetValue(&ADC1_Handler);          //返回最近一次结果
}

        多次进行ADC采样,求取平均值,并转换为适度的百分比,代码如下:

uint16_t Get_Adc_Average(uint32_t ch,uint8_t times)
{
    uint32_t temp_val=0;
    uint8_t t;
    for(t=0;t<times;t++)                                  //ADC采样值次数
    {
       temp_val+=Get_Adc(ch);                             //ADC采样值累加
       delay_ms(5);
    }
    return temp_val/times;                                //返回多次转换的平均值
}

ADC_NUM = Get_Adc_Average(ADC_CHANNEL_1,20);              //获取采集值
printf("采集值:%d\r\n",ADC_NUM);                          //串口打印    
if(ADC_NUM>=ADC_MAX1)
    ADC_NUM = ADC_MAX1;
Humidity=(100-(ADC_NUM-ADC_MIN1)*100/(ADC_MAX1-ADC_MIN1));//湿度百分比

4.2.8  毫秒级定时器程序

        STM32的定时器按照功能可以分为:高级定时器、通用定时器、基本定时器、看门狗定时器、系统嘀嗒定时器,本系统使用通用定时器TIM2为WiFi模组提供精准定时基准。在编写代码之前首先了解定时器的工作原理以及注意事项。根据STM32定时器的结构框图得知,定时器结构主要分为时钟选择、触发方式、定时器通道、计数单元和输出控制[19,20]。STM32定时器结构框图如图4-10所示。

图4-10:STM32定时器结构框图

        STM32的通用定时器可以设置为向上、向下、中心对齐三种计数模式。本设计只是简单的产出更新溢出中断,采用向上计数模式就足以满足设计要求。计数寄存器从0计数到自动加载值后,产生一个溢出事件,计数寄存器清零后重新开始计数,定时时间T=时钟周期*(ARR+1)*(PSC+1),这里ARR和PSC分别设置为10-1和7200-1,系统时钟为72MHz,则T=1MS,计数模式和计时波形图如图4-11所示。

图4-11:计数器计数模式(上)和计时波形图(下)

        通用定时器2代码编写的主要步骤为:

        1、使能TIM2时钟;

        2、设置TIM2工作模式和各项参数;

        3、调用TIM2初始化函数;

        4、使能TIM2中断函数;

        5、TIM2中断分组设置;

        6、编写中断服务函数。

        具体代码如下:

void  TIM_1MS_Init(void)
{
    __HAL_RCC_TIM2_CLK_ENABLE();                               //使能TIM2时钟
    TIM2_Hander.Instance             = TIM2;                   //定时器二
    TIM2_Hander.Init.Prescaler       = 7200-1;                 //分频因子
    TIM2_Hander.Init.Period          = 10-1;                   //自动重载值
    TIM2_Hander.Init.CounterMode     = TIM_COUNTERMODE_UP;     //向上计数模式
    TIM2_Hander.Init.ClockDivision   = IM_CLOCKDIVISION_DIV1;  //1分频
    TIM2_Hander.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
    HAL_TIM_Base_Init(&TIM2_Hander);                           //初始化TIM2
    HAL_TIM_Base_Start_IT(&TIM2_Hander);                       //使能定时器2和定时器2更新中断:
}

        定时器初始化和设置定时器中断函数之后,毫秒级定时器就基本可以工作了,为了使WiFi模组知道定时器什么时候记录了1MS的时间断,因此需要在定时器更新中断回调函数里添加WiFi模组的定时器基准函数入口,代码如下:

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)  //更新中断
{  
    if(htim->Instance==TIM2)
    {
        gizTimerMs();                                             //机智云毫秒级基准
        gizwitsHandle((dataPoint_t *)&currentDataPoint);          //发送数据到云平台
    }
}

4.2.9  I2C协议程序

        在编写代码之前,首先要了解I2C(Inter-Integrated Circuit)通信协议的相关理论知识。I2C通信协议是Philips公司开发的一种双向同步串行通信总线,需要一根数据线(SDA)和一根时钟线(SCL),硬件电路简单,扩展能力强,因此在不需要串口、CAN总线等通讯协议的外部设备和在系统内多个集成电路(IC)间的通讯中广泛使用。对于并联在同一条I2C总线上的每个设备都有唯一的设备地址,SDA和SCL需要接上拉电阻使其在空闲状态下保持高电平[19,20]。物理连接图如图4-12所示。

图4-12:I2C总线物理连接图

        意法半导体ST为了避免侵犯Philips的I2C协议版权问题,采用了特殊的硬件设计。我在STM32F4开发板上做硬件I2C测试的时候,会莫名其妙的进入中断,然后卡死,因此为了避免本设计出现也卡死或其它bug,本程序采用软件模拟I2C的方法。I2C协议定义了开始信号、结束信号和应答信号,将逐个编写代码[19,20]。

        起始信号:时钟线SCL为高时,数据线SDA由高到低的跳变;停止信号:在时钟线SCL为高时,数据线SDA由低到高的跳变,起始信号和停止信号都是电平跳变转换的时序信号,而不是电平的高低。起始信号和停止信号波形如图4-13所示。

图4-13:起始信号和停止信号波形图

        因为SDA数据线在输入和输出状态之间来回切换,根据STM32F1参考手册的GPIO配置说明,通过操作GPIO的端口配置低寄存器CRL来控制IO口的输入输出方向,代码如下:

#define ROM_SDA_IN()   {GPIOA->CRL&=0XFFFF0FFF;GPIOA->CRL|=8<<3*4;} //PA3输入模式
#define ROM_SDA_OUT() {GPIOA->CRL&=0XFFFF0FFF;GPIOA->CRL|=3<<3*4;}  //PA3输出模式

        起始信号代码:

void ROM_I2C_START(void)                                   //开始信号
{
    ROM_SDA_OUT();                                         //输出模式
    HAL_GPIO_WritePin(GPIOA,GPIO_PIN_3,GPIO_PIN_SET);      //SDA信号拉-高
    HAL_GPIO_WritePin(GPIOA,GPIO_PIN_2,GPIO_PIN_SET);      //SCL信号拉-高
    delay_us(4);                                           //延时4us
    HAL_GPIO_WritePin(GPIOA,GPIO_PIN_3,GPIO_PIN_RESET);    //SDA信号拉-低
    delay_us(4);                                           //延时4us
    HAL_GPIO_WritePin(GPIOA,GPIO_PIN_2,GPIO_PIN_RESET);    //SCL信号拉-低
}

        停止信号代码:

void ROM_I2C_STOP(void)                                    //停止信号
{
    ROM_SDA_OUT();                                         //输出模式
    HAL_GPIO_WritePin(GPIOA,GPIO_PIN_3,GPIO_PIN_RESET);    //SDA信号拉-低
    HAL_GPIO_WritePin(GPIOA,GPIO_PIN_2,GPIO_PIN_RESET);    //SCL信号拉-低
    delay_us(4);                                           //延时
    HAL_GPIO_WritePin(GPIOA,GPIO_PIN_2,GPIO_PIN_SET);      //SCL信号拉-高
    HAL_GPIO_WritePin(GPIOA,GPIO_PIN_3,GPIO_PIN_SET);      //SDA信号拉-高
    delay_us(4);                                           //延时 
}

        主设备每发送一个8位数据后,数据线就在第9个时钟脉冲期间被释放,从设备会反馈一个电平信号。当电平信号为低时,从设备成功接收到主设备发送的数据,认定为有效应答(ACK);当电平信号为高时,从设备没有正常接收到主设备发送的数据,规定为无应答(NACK)[19,20]。应答信号如图4-14所示。

https://img-blog.csdn.net/20180514204129274

图4-14:应答信号和无应答信号波形图

        应答信号和无应答信号代码部分大同小异,为了减小代码的篇幅,就只列出产生应答信号代码:

void ROM_I2C_ACK(void)                                     //应答信号
{  
    HAL_GPIO_WritePin(GPIOA,GPIO_PIN_2,GPIO_PIN_RESET);    //SCL信号拉-低
    ROM_SDA_OUT();                                         //输出模式
    HAL_GPIO_WritePin(GPIOA,GPIO_PIN_3,GPIO_PIN_RESET);    //SDA信号拉-低
    delay_us(2);                                           //延时2us
    HAL_GPIO_WritePin(GPIOA,GPIO_PIN_2,GPIO_PIN_SET);      //SCL信号拉-高
    delay_us(2);                                           //延时2us
    HAL_GPIO_WritePin(GPIOA,GPIO_PIN_2,GPIO_PIN_RESET);    //SCL信号拉-低
}

        主设备通过I2C总线向从设备写数据时,数据的每个字节必须为8位,传输时高字节在前,低字节在后,每一个字节传输完成后必须紧跟一位应答信号ACK。如果在数据传输过程中从设备需要执行其他中断函数,可以把时钟线SCL强制拉低迫使主设备进入等待状态,等从设备执行结束中断函数后释放时钟线SCL,主从设备才能继续发送或接受下一个字节 [19,20],传输一个完整字节的波形如图4-15所示。

图4-15:传输一个完整字节的波形图

        为了减小代码的篇幅,只针对写一个字节进行论文的编写,根据时序图编写I2C向EEPROM写一个字节的程序,代码如下:

void ROM_Write_Byte(uint8_t TX_Byte )                          //I2C写一个字节数据
{
    uint16_t i;
    ROM_SDA_OUT();                                             //SDA设置为输出模式
    HAL_GPIO_WritePin(GPIOA,GPIO_PIN_2,GPIO_PIN_RESET);        //开始写入数据
    for(i=0;i<8;i++)                                           //循环写入数据
    {
       if((TX_Byte&0x80)>>7)
           HAL_GPIO_WritePin(GPIOA,GPIO_PIN_3,GPIO_PIN_SET);   //SDA写1
       else
           HAL_GPIO_WritePin(GPIOA,GPIO_PIN_3,GPIO_PIN_RESET); //SDA写0
       TX_Byte<<=1;                                            //数据左移1位
       delay_us(2);                                            //延时2us
        HAL_GPIO_WritePin(GPIOA,GPIO_PIN_2,GPIO_PIN_SET);      //SCL信号拉高
       delay_us(2);                                            //延时2us
        HAL_GPIO_WritePin(GPIOA,GPIO_PIN_2,GPIO_PIN_RESET);    //SCL拉低
       delay_us(2);                                            //延时2us
    }
}

4.2.10  OLED显示程序

        在硬件部分已经了解了OLED的通信方式,从SSD1306的官方数据手册可以得知OLED的设备地址为0111_10+SA0+0、1,前六位地址0111_10是出厂时设置好的,第七位地址是根据OLED液晶驱动电路中SA0所接入的电平高低来判断是1还是0,最后一位写操作或读操作判断是0还是1。因此,在对OLED进行写数据时,其设备地址为:0111_1000。OLED的分辨率为128X64,则代表在水平方向上分布有128个像素点,在垂直方向分布有64个像素点,SSD1306驱动芯片是以8个像素点为单位进行点亮的,控制主板OLED采用逐列扫描方式,也就是先画垂直方向的8个像素点,所以在画点的时候Y的取值为0-7,X的取值为0-127[19,20]。

        OLED代码编写步骤如下:

  1. 定义OLED显存数组;
  2. 编写画点函数;
  3. 编写写入数据和命令函数;
  4. 编写显存更新函数;
  5. 利用PCtoLCD2002生成数字、字符和汉字所对应的数组;
  6. 编写字符、数字、汉字显示函数;
  7. 调用官方初始化函数。

        定义OLED显存数组如表4-3所示。

表4-3:OLED的显存数组

OLED的显存数组

uint8_t OLED_GRAM[128][8];

[Y0]

[X0],[X1],[X2],[X3],...,[ X125],[X126],[X127]

[Y1]

[X0],[X1],[X2],[X3],...,[X125],[X126],[X127]

[Y2]

[X0],[X1],[X2],[X3],...,[X125],[X126],[X127]

[Y3]

[X0],[X1],[X2],[X3],...,[X125],[X126],[X127]

[Y4]

[X0],[X1],[X2],[X3],...,[X125],[X126],[X127]

[Y5]

[X0],[X1],[X2],[X3],...,[X125],[X126],[X127]

[Y6]

[X0],[X1],[X2],[X3],...,[X125],[X126],[X127]

[Y7]

[X0],[X1],[X2],[X3],...,[X125],[X126],[X127]

        缓存数组的作用是把需要在同一界面显示的信息先存放在一起,等需要刷新显示时,再通过I2C对OLED进行更新操作,代码如下:

void OLED_DrawPoint(uint8_t x,uint8_t y,uint8_t mode)
{
    uint8_t pos  = 0;                          //页
    uint8_t bx   = 0;                          //行
    uint8_t temp = 0;                       
    if(x>127||y>63)                            //超出范围
       return;       
    pos=7-(63-y)/8;                            //求出在哪页
    bx=(63-y)%8;                               //求出在哪行
    temp=1<<(7-bx);                            //数据左移
    if(mode)                                   //判断是否正常显示
       OLED_GRAM[x][pos]|=temp;                //存放至显存数组
    else
       OLED_GRAM[x][pos]&=~temp;               //存放至显存数组
}

        OLED可以显示字符、数字、图片和汉字,由于代码行数太多,本论文只介绍OLED显示汉字的原理,其他三种显示原理类似。根据SSD1306驱动IC的数据手册,对OLED进行写操作的时序如图4-16所示。

        根据时序图可知,在进行写操作之前必须有一个起始信号,后面紧跟着OLED在I2C总线上的设备地址+读写操作,然后产生一个应答信号ACK,再执行命令的写入,之后是一个应答信号ACK,最后进行数据的写入。往OLED液晶屏写数据的代码:

void OLED_Write_I2C(uint8_t dat,uint8_t com) //写入数据
{
    OLED_I2C_START();                        //起始信号
    OLED_Write_Byte(0x78);                   //写器件地址
    OLED_I2C_Wait_Ack();                     //等待应答信号
    If(com) 
        OLED_Write_Byte(0x40);               //写命令
    else OLED_Write_Byte(0x00);              //写数据   
    OLED_I2C_Wait_Ack();                     //等待应答信号
    OLED_Write_Byte(dat);                    //写入数据
    OLED_I2C_Wait_Ack();                     //等应答信号
    OLED_I2C_STOP();                         //停止信号
}

图4-16:OLED写操作时序图

        编写完OLED缓存代码和I2C写数据代码后,需要把数组里的数据通过I2C总线更新到OLED中,根据SSD1306的数据手册,写入数据先写对应的页地址,在写列地址,这样在128X64个点阵中就有唯一的地址,具体代码如下:

void OLED_Refresh_Gram_All(void)                  //更新显存
{
    uint8_t i,n;        
    for(i=0;i<8;i++)                              //循环发送更新显示
    { 
       OLED_Write_I2C(0xb0+i,1);                  //设置页地址(0~7)
       OLED_Write_I2C(0x00,1);                    //设置显示位置—列低地址
       OLED_Write_I2C(0x10,1);                    //设置显示位置—列高地址  
       for(n=0;n<128;n++)                         //循环写入数据
           OLED_Write_I2C(OLED_GRAM[n][i],0);     //写入数
    }  
}

        通过显示缓存和显示更新代码的编写,理论上OLED就可以正常显示,因为需要在OLED上显示汉字信息,因此就要编写汉字驱动代码来实现这一功能。在编写汉字显示代码之前,先利用字符生成工具生产所需汉字所对应的数组。打开软件后,修改刷新方向和显示格式,生成数组软件的操作界面如图4-17所示。

图4-17:数组生成操作界面

        显示汉字函数代码:

void OLED_ShowCHN(unsigned char x, unsigned char y, unsigned char N,unsigned char buff[],uint8_t mode)
{
    unsigned char    wm=0;
    unsigned int      adder=0;
    uint8_t          i = 0;
    uint8_t          t = 0;
    uint8_t          y0=y;
    uint8_t          temp = 0;
    uint8_t          size = 24;                 //字体大小
    for(i=0;i<N;i++)
    {
       adder=size*i;                            //每个汉字占24个字节
       for(wm=0;wm<size;wm++)                   //循环写入字节
       {
           temp = buff[adder];                  //取字节
           for(t=0;t<8;t++)                     //循环写入Bit
           {
              if(temp&0x80)                     //取最高位
                  OLED_DrawPoint(x,y,mode);     //画点
              else
                  OLED_DrawPoint(x,y,!mode);    //画点
              temp<<=1;
              y++;  
              if((y-y0)==size/2)
              {
                  y=y0;
                  x++;
                  break;
              }                
           }
           adder++;   
       }
    }
    OLED_Refresh_Gram_All();                     //更新显示 
}

        OLED液晶显示屏必须经过初始化才可以正常显示,因为OLED是I2C通信,所以在初始化OLED之前必须先初始化I2C通信协议,然后把官方提供的初始化代码经过简单的处理后,才可以正常的初始化OLED液晶显示屏,液晶显出初始化代码如下:

void OLED_Init(void)
{
    OLED_I2C_Init();       //OLED_I2C初始化
    HAL_Delay(100);        //这里的延时很重要 
    WriteCmd(0xAE);        //关闭显示
    WriteCmd(0x20);        //设置内存寻址方式 
    WriteCmd(0x10);        //00:水平寻址模式;01:垂直寻址模式;10:页面寻址模式
    WriteCmd(0xb0);        //“页面寻址方式”设置“页面起始地址”,0-7
    WriteCmd(0xc8);        //设置COM输出扫描方向
    WriteCmd(0x00);        //设置低列地址
    WriteCmd(0x10);        //设置高列地址
    WriteCmd(0x40);        //设置起始行地址
    WriteCmd(0x81);        //设置对比度控制寄存器
    WriteCmd(0xff);        //亮度调节 0x00~0xff
    WriteCmd(0xa1);        //设置列映射0到127
    WriteCmd(0xa6);        //设置正常显示
    WriteCmd(0xa8);        //设置多路复用比(1到64)
    WriteCmd(0x3F);       
    WriteCmd(0xa4);        //0xa4:表示输出遵循RAM内容;0xa5,表示忽略RAM内容
    WriteCmd(0xd3);        //设置显示抵消
    WriteCmd(0x00);        //不是抵消
    WriteCmd(0xd5);        //设置显示时钟分频比/振荡器频率
    WriteCmd(0xf0);        //设置划分比例
    WriteCmd(0xd9);        //设置pre-charge时期
    WriteCmd(0x22);       
    WriteCmd(0xda);        //设置com引脚硬件配置
    WriteCmd(0x12);
    WriteCmd(0xdb);        //设置vcomh
    WriteCmd(0x20);        //0x20,0.77xVcc
    WriteCmd(0x8d);        //设置直流-直流启用
    WriteCmd(0x14);       
    WriteCmd(0xaf);        //打开oled面板
    OLED_Fill_All(0x00);   //全屏灭
}

        只编写以上的代码还不足以满足本设计所需要显示的样式,比如ADC采集数值、开机图片和WiFi配置时显示的字符串,不过代码编写的过程大同小异,这里就不在阐述字符、数字以及图片的编写过程。

4.2.11  存储器的读写程序

        因为I2C对E2PROM的写时序逻辑和OLED大致相同,只是改变了设备地址,E2PROM的设备地址为1010_000(0:写,1:读),因此在这里只阐述读数据的时序、原理以及代码的编写,编写代码步骤为:

  1. 初始化I2C总线;
  2. ROM设备检测;
  3. 编写读、写单比特的函数;
  4. 编写读、写多比特的函数[27,28,57,58]。

        AT24C512的I2C读写时序分别如图4-18、4-19所示。

图4-18:ROM的I2C读时序

图4-19:ROM的I2C写时序

        根据读时序波形图可知,在读取数据时,先写入设备地址+写命令,随后写需要读出数据的存储地址,之后写入设备地址+读命令,最后读取需要的数据,读一个比特的代码如下:

uint8_t AT24CXX_ReadOneByte(uint16_t ReadAddr)
{              
    uint8_t temp=0;                               
    ROM_I2C_START(); 
    if(EE_TYPE>AT24C16)
    {
       ROM_Write_Byte(0XA0);                          //发送写命令
       ROM_I2C_Wait_Ack();                            //等待应答信号
       ROM_Write_Byte(ReadAddr>>8);                   //发送高地址    
    }
    else
        ROM_Write_Byte(0XA0+((ReadAddr/256)<<1));     //发器件地址0XA0,写数据
    ROM_I2C_Wait_Ack();                               //等待应答信号
    ROM_Write_Byte(ReadAddr%256);                     //发送低地址
    ROM_I2C_Wait_Ack();                               //等待应答信号
    ROM_I2C_START();                                  //停止信号  
    if(EE_TYPE > AT24C16)
       ROM_Write_Byte(0XA1);                          //进入接收模式
    else
        ROM_Write_Byte(0XA1+((ReadAddr/256)<<1));     //进入接收模式
    ROM_I2C_Wait_Ack();                               //等待应答信号
    temp=ROM_Read_Byte(0);                            //读取数据 
    ROM_I2C_STOP();                                   //产生停止信号     
    return temp;                                      //返回读取到的数据
}

        连续读取一定位的数据,代码如下:

void AT24CXX_Read(uint16_t ReadAddr,uint8_t *pBuffer,uint16_t NumToRead)
{
    while(NumToRead)
    {
        *pBuffer++=AT24CXX_ReadOneByte(ReadAddr++);   
        NumToRead--;
    }
}

        存储器存储各种农作物的湿度信息,每种农作物占用4个字节,其中低十六位数据有效,高16位地址保留。在低十六位数据中的高八位为湿度最大值,低八位为湿度最小值。每次读写操作的数据都是2个字节,因此需要使用按位与、按位或的方法对低八位和高八位进行拆分读取,如:

H_DATA=(ROM_Read_data & 0XFF00)>>8;             //读取高八位
L_DATA=(ROM_Read_data & 0X00FF);                //读取低八位

        存放各种农作物湿度数据的首地址如表4-4所示。

表4-4:AT24C512存储植物信息的首地址

植物种类

小麦

玉米

大豆

水稻

番薯

西红柿

辣椒

大葱

存储地址

0x00

0x31

0x63

0x95

0x127

0x159

0x191

0x223

植物种类

大蒜

韭菜

黄瓜

菠菜

白菜

土豆

樱桃树

梨树

存储地址

0x255

0x287

0x319

0x351

0x383

0x415

0x447

0x479

植物种类

苹果树

火龙果

柿子树

李子树

板栗树

预留地址

存储地址

0x511

0x543

0x575

0x607

0x639

0x640~0x65535

4.2.12  多级菜单程序

        菜单是人类使用最广泛的一种人机交互方式,通过按键实现在同一分组下的多个对象之间进行切换选择,在OLED屏幕上显示出各种可能的选择项。因为其操作简单,故本系统主板就利用菜单交互方式,以实现各种命令的控制和参数的修改。编写菜单的方法有很多种,比如利用结构体、数组和一些中断嵌套[28]。本设计的菜单采用结构体和数组结合的方式实现。先定义一个结构体,其中包含按键号、菜单索引号和当前正在执行的函数,部分代码如下:

typedef struct
{
     uint8_t current;                       //菜单号索引值
     uint8_t up;                            //上键
     uint8_t down;                          //下键
     uint8_t enter;                         //确认键
     void (*current_operation)(void);       //当前状态应该执行的操作
} key_table;

        然后定义一个数组,代码如下:

key_table  table[40]=
{
    {0,1,2,5,(*函数0)},                  //对应函数零
    {1,3,7,2,(*函数1)},                  //对应函数一
    {2,6,4,3,(*函数2)},                  //对应函数二
    {3,2,2,0,(*函数3)},                  //对应函数三
    {4,2,6,5,(*函数4)},                  //对应函数四
    {5,0,3,6,(*函数5)},                  //对应函数五
    {6,4,2,1,(*函数6)},                  //对应函数六
    ……
}

        其中,中间三个数据对应的是三个键按下执行函数的索引值,假如目前在执行第一个数组table[0]里的函数0,在此基础上按上键则执行函数1,按下键可以执行函数2,按确定键则执行函数5;假如目前在执行数组table[4]里面的函数4,若此时按下上键则进入函数2,如果按下键则执行函数6,按确定键则执行函数5,依此类推。程序结构图如图4-20所示。

图4-20:多级菜单流程图

4.2.13  UART收发程序

        串口通讯是设备之间常用的一种串行通讯方式,串口最基本的功能就是调试,还可以作为数据的通信接口,串口通信原理是把数据转换成连续的串行数据,低位在前,高位在后进行传输。其特点是占用引脚资源相对较少,缺点是传输速度较慢。UART通信方式至少需要三个引脚:数据接收引脚RXD和数据发送引脚TXD,以及电源地GND[19,20]。串口数据传输结构图和时序如图4-21和图4-22所示。

图4-21:STM32串口结构图

图4-22:UART时序图

        根据时序图对各个数据位进行分析:

        1、空闲位和起始位:在空闲状态下,数据线处于高电平,当数据线电平变成低电平则代表开始数据传输;

        2、数据位:在起始位之后紧跟着数据位;

        3、奇偶校验位:在传输过程结束后检测接收的数据是否正常,奇(偶)校验是增加此位使接收数据中“1”的个数为奇(偶)数;

        4、停止位:数据传输的结束标志,停止位的出现是为了提高两台设备之间时钟的同步程度,停止位的数量越多,代表着两台设备之间时钟同步的程度越大,数据传输的准确率越高,但传输速度也相对越慢;

        5、波特率:衡量数据传输速度的指标,波特率越大,数据传输越快,但传输错误的概率也越大,两个设备必须设置为相同的波特率,否则数据传输失败。

        只要懂得串口通信的原理才可以编写出正确的代码。本设计的主控板中,单片机的调试和WiFi与MCU之间的通信是串口通信方式,分别对应的串口号为:串口1和串口3,串口代码编写步骤:

  1. 使能相应时钟;
  2. 初始化串口号;
  3. 初始化对应GPIO(复用功能);
  4. 设置波特率、数据格式等参数;
  5. 编写数据接收和发送函数;

        串口1的部分代码如下:

#include "usart.h"
#include "delay.h"

UART_HandleTypeDef UART1_Handler;                          //UART句柄

void uart_init(u32 bound)                                  //UART 初始化设置
{  
    UART1_Handler.Instance=USART1;                         //USART1
    UART1_Handler.Init.BaudRate=bound;                     //波特率
    UART1_Handler.Init.WordLength=UART_WORDLENGTH_8B;      //8位数据格式
    UART1_Handler.Init.StopBits=UART_STOPBITS_1;           //一个停止位
    UART1_Handler.Init.Parity=UART_PARITY_NONE;            //无校验位
    UART1_Handler.Init.HwFlowCtl=UART_HWCONTROL_NONE;      //无硬件流控
    UART1_Handler.Init.Mode=UART_MODE_TX_RX;               //收发模式
    UART1_Handler.Init.OverSampling =UART_OVERSAMPLING_16;
    HAL_UART_Init(&UART1_Handler);                     
   HAL_UART_Receive_IT(&UART1_Handler, (u8 *)aRxBuffer, RXBUFFERSIZE);
    HAL_NVIC_EnableIRQ(USART1_IRQn);                       //使能USART1中断通道
    HAL_NVIC_SetPriority(USART1_IRQn,3,3);                 //抢占优先级3,子优先级3
}

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
    if(huart->Instance==USART1)                            //如果是串口1
    {
        if((USART_RX_STA&0x8000)==0)                       //接收未完成
        {
           if(USART_RX_STA&0x4000)                         //接收到了0x0d
           {
               if(aRxBuffer[0]!=0x0a)
                  USART_RX_STA=0;                          //接收错误,重新开始
           else 
                  USART_RX_STA|=0x8000;                    //接收成功
        }
       else                                                //还没收到0X0D
       {  
           if(aRxBuffer[0]==0x0d)
                USART_RX_STA|=0x4000;
           else
           {
               USART_RX_BUF[USART_RX_STA&0X3FFF]=aRxBuffer[0] ;
               USART_RX_STA++;
              if(USART_RX_STA>(USART_REC_LEN-1))
                  USART_RX_STA=0;
           }     
       }
    }
}

4.2.14  ESP-12F程序

        在机智云平台导出的MCU代码包,对源代码里的各个参数和命令的修改和添加,实现手机APP可以对主控板的工作模式和湿度信息进行修改的目的。因为湿度信息存放在ROM中,因此要对植物种类进行switch判断,再读出各个数据,用按位与、按位或进行数据的拆分读写。部分代码如下:

if(currentDataPoint.valueControl_mode == Control_mode_VALUE0)
{  
    currentDataPoint.valueManual_control=dataPointPtr->valueManual_control;
    printf("Evt: Watering_ON_OFF %d\n",currentDataPoint.valueManual_control);
}
else if(currentDataPoint.valueControl_mode == Control_mode_VALUE1)                          
    currentDataPoint.valueManual_control = 0;      //强制手动开关为关闭状态
else
    currentDataPoint.valueManual_control=currentDataPoint.valueManual_control;
    break;

case EVENT_Control_mode:                           //水泵工作模式选择(1、自动;0、手动)
    currentDataPoint.valueControl_mode = dataPointPtr->valueControl_mode;
    printf("Evt: EVENT_Mode %d \n", currentDataPoint.valueControl_mode);                              
    switch(currentDataPoint.valueControl_mode)
    {
        case Control_mode_VALUE0:                                   //手动工作模式
            break;
         case Control_mode_VALUE1:                                  //自动工作模式
            break;
        default:
            break;
    }
    break;

case EVENT_Crops_kind:                                              //农作物选择模式
    if(currentDataPoint.valueControl_mode == Control_mode_VALUE1)   //自动工作模式
    {
        currentDataPoint.valueCrops_kind = dataPointPtr->valueCrops_kind;  //获取种类 
        printf("Evt: EVENT_Kind %d\n", currentDataPoint.valueCrops_kind);  //串口打印
        switch(currentDataPoint.valueCrops_kind)                           //种类判断
        {
            case Crops_kind_VALUE0:                                        //小麦
                ROM_Read_data = AT24CXX_ReadLenByte(Kind_wheat_addre,4);   //读取湿度
                H_DATA = (ROM_Read_data & 0X0000FF00)>>8;                  //读取高八位
                L_DATA = (ROM_Read_data & 0X000000FF);                     //读取低八位
                currentDataPoint.valueIrrigate_Humidity_MIN = L_DATA;
                currentDataPoint.valueIrrigate_Humidity_MAX = H_DATA;       
                Control_Kind = 0;
            break;
        ......
        }
    }
......

4.2.15  主程序

        单片机的程序都是从main函数里开始执行的,在主函数中,先对各种外设代码进行初始化,随后进行对ROM、WiFi模块和湿度传感器进行检测,如果各个外设都准备完毕就进入while(1)中,随后一直调用菜单子函数,通过按键扫描判断按键号,控制函数的跳转,同时控制OLED显示不同的界面。主函数部分代码如下:

int main()
{
    key = KEY_SCAN(0);                              //按键扫描
    while(key)
    {  
       switch(key)                                  //按键选择
       {
            case KEY1_PRES:{
               func_index=table[func_index].up;     //向上翻
               OLED_Clear();                        //全屏灭
            break; }   
           
           case KEY2_PRES:{
               func_index=table[func_index].down;   //向下翻
              OLED_Clear();                         //全屏灭
           break;}

           case KEY3_PRES:{
               func_index=table[func_index].enter;  //确认
              OLED_Clear();                         //全屏灭
           break;}  
        
           default:
              break;
       }
       key = 0;
    }
    current_operation_index=table[func_index].current_operation;
    (*current_operation_index)();                   //执行当前操作函数
}  

4.2.16  工程代码烧录

        工程所有部分都完成后,需要对STM32单片机进行烧录,烧录工具使用ST官方的ST-Link[19,20],具体修改步骤如图4-23所示。

图4-23:烧录程序设置图

5、系统调试及分析

5.1  单个调试与分析

        随着集成电路的不断发展,数字示波器越来越符合设计需要。它先把模拟电信号通过高速AD转化为数字信号,处理器把数字信号转换成图像信息并通过屏幕显示,便于观测和研究信号的时序变化,本论文主要利用示波器对波形进行测试。

5.1.1  运放调试

        对运算放大器输入输出电压进行测试,其电压波形图如图5-1所示。经计算运放输入输出比例符合等式(7) Vo=0.66Vi的计算结果。

图5-1:运放测试图

5.1.2  云平台数据点调试

        使用手机APP扫描机智云服务平台上的虚拟设备二维码,进行手机APP和服务平台之间的互相通信。通信测试图如图5-2所示。

图5-2:云平台数据通信测试图

5.1.3  串口调试

        代码调试和WiFi模组与MCU之间的通信都需要用到串口,所以必须对其进行调试,通过串口调试助手测试结果如图5-3所示。

图5-3:串口测试图

5.1.4  1MS定时器调试

        1MS定时器作为ESP-12F工作的基准时间,必须严格要求定时器产生中断的时间间隔在正常范围内。利用MDK软件对单片机进行代码调试,在定时器更新溢出中断函数里添加断点,运行代码并记录两次运行至断点的时间差。根据测试,时间差的范围为0.97至1.02MS,符合ESP-12F的工作条件。定时器调试图如图5-4所示。

图5-4:1MS基准定时器调试图

5.1.5  I2C读写时序调试

        根据I2C通信协议的时序图,完成代码的编写之后,为了检测I2C总线上的设备是否可以正常进行读写操作,就需要利用示波器抓拍I2C的读写时序。因为SDA和SCL在默认处于高电平状态,并且是SDA先从高电平转变成低电平,为了让示波器能够正常捕捉到需要的时序图,就要把触发条件设置为SDA的下降沿触发,分别对E2PROM的都写时序波形进行捕捉并分析。

        I2C的写时序的顺序为:写设备地址+写操作、再写读出数据存储的地址、然后写入数据,波形与实验预期一致,写时序波形图如图5-5所示。

图5-5:I2C写时序波形图

        I2C的读时序的顺序为:写设备地址+写操作、再写读出数据的地址、然后写设备地址+读操作,最后返回读到的数据。因为本设计采用单片机GPIO口内部上拉的模式,因此在读操作的时候,SDA不能瞬间释放,以至于在波形图中SDA的上升沿出现弧度现象,但是在SCL处于高电平时,SDA数据线已经处于稳定状态,因此不影响对E2PROM进行正常的读操作。I2C读时序波形图如图5-6和5-7所示。

图5-6:I2C读时序图前部分

图5-7:I2C读时序图后部分

5.1.6  ESP模组连接路由器调试

        先把菜单调成WiFi复位界面,对WiFi连接状态进行复位,再调节至WiFi连接界面,打开手机APP对WiFi模组与路由器进行连接(手机APP必须预先连接至路由器)。具体操作步骤如图5-8所示。

图5-8:WiFi连接路由器测试图

5.2  总体上电测试

        本设计系统主要包括三个显示方式:终端显示、手机APP显示和OLED显示;二种控制方式:手机APP控制方式和板载按键控制方式。只有它们的功能全部正常,才代表了一个项目的完成,接下来就对它们逐一进行测试。系统总体实物图如图5-9所示。

图5-9:智能灌溉系统总体实物图

5.2.1  终端显示测试

        在系统正常工作后,系统通过无线网络把相关数据上传到机智云平台,为了系统功能的正确性,进行终端显示测试,测试数据如图5-10所示。

图5-10:终端显示测试图

5.2.2  手机APP控制测试

        该控制系统必须实现手机APP进行远程控制的功能,因此对该功能进行测试是必不可少的一个环节,具体测试步骤如下:

        第一步:手机修改植物的生存湿度范围,当调整的湿度信息小于当前检测的湿度值,水泵则停止工作,测试数据如图5-11所示。

图5-11:修改湿度范围测试图

        第二步:系统控制板工作在手动模式的情况下,对手动改变水泵工作的状态进行功能性测试,如图5-12所示。

图5-12:手动控制测试图

        第三步:对植物种类修改的功能进行测试,如图5-13所示。

图5-13:植物种类选择测试图

5.2.3  按键控制测试

        当农田出现没有无线网络的时候,可以通过板载按键修改各个参数,具体测试步骤如下:

        第一步:对植物种类修改进行测试,如图5-14所示。

图5-14:植物种类选择界面

        第二步:对植物生存湿度范围的修改进行测试,如图5-15所示。

图5-15:湿度范围修改界面

        第三步,对工作模式选择进行测试,如图5-16所示。

图5-16:工作模式选择界面

        第四步,在手动模式下修改水泵工作状态测试,如图5-17所示。

图5-17:水泵工作状态修改界面

6.结论

        随着不可再生资源的紧缺和环境状况的恶化,农业灌溉所面临的问题也将更加严峻。农田智能灌溉方案不仅实时监控农田湿度信息,减少农业生产用水量,降低农业生产过程中对环境造成的污染,还可以减少劳动力的投入,增加农民收入,实现科学、智慧农业发展的目标。物联网技术和人工智能的快速发展,使该项技术在农业生产中有了很好的发展前景。本毕业设计是以智能灌溉为主要目的,针对农业生产中的发展需求,设计出一套手机APP远程控制和板载按键控制的智能农田灌溉系统,采用模块化的设计理论,利用代码将各个系统模块紧密结合,最终实现了数据的采集、传输、读写、显示、判断等功能,同时搭建了完整的WiFi通信链路,实现了有线设备接入无线 WiFi网络的目标,同时系统具有工作稳定、实时性好、控制简单等特点。把各个功能的改造升级后,可以应用到智能家居等场合,有很好的发展前景。

        由于本人时间、精力有限以及才学尚浅,本智能灌溉系统距离成熟还有一段距离,存在一下可以改进的地方:

        1、植物种类的添加有些繁琐,需要修改机智云的数据点,在代码中添加相应的结构体和枚举;

        2、不支持在线更新MCU程序和WiFi固件;

        3、主控板上只有一个土壤湿度信息的输入,会造成农田灌溉的片面性,因此需要增加多个监测点,把它们的数据发送到同一块控制主板上,进行全面的数据分析和控制,突出智能灌溉系统的准确性;

        4、增加模糊PID控制理论进行灌溉控制和加入定时功能;

        5、增加水泵转速的控制,更好的控制灌溉速度,也达到了节约用电的目的;

        6、在一些不支持无线网络的农田(比如山区、沙漠等一些人烟稀少的地方),土壤的湿度信息就不能上发至服务器,难以实现远程灌溉。解决方法是增加大量的信号基站或者采用卫星通讯的方式。

        目前我国智能农业生产模式还存在不全面、不“完全智能”等问题,但我坚信全面实现智慧农业生产的日子已不再遥远。谢谢!

参考文献

[1] 赵彩霞.改善农田灌溉管理的措施[J].农业科技与信息,2020(15):115-116.

[2] 林少芸.智慧农业发展背景下我国新型职业农民的培育路径探析[J].山西农经,2021(4):19-20.

[3] 李金.基于物联网的农田灌溉系统设计[D].安徽理工大学,2017.

[4] 赵春江.智慧农业发展现状及战略目标研究[J].农业工程技术,2019,39(6):14-17.

[5] 陈赜.物联网技术导论与实践[M].北京:人民邮电出版社,2017.1-12.

[6] 黄玉兰.物联网(RFID)核心技术教程[M].北京:人民邮电出版社,2016.4-10.

[7] 史增强.农田灌溉用水趋势及节水措施[J].陇西县灌溉管理所, 2020(10):1552.

[8] 康洁.美国节水发展的历史、现状及趋势[J].海河水利,2005(6):65-66.

[9] 李德旺,许春雨,宋建成.现代农业智能灌溉技术的研究现状与展望[J].江苏农业科学,2017,45(17):27-31.

[10] 郭玮.国外水资源开发利用战略综述[J].农业经济问题,2001(1):58-62.

[11] 姜元.基于物联网的智能农田远程监控系统的设计[D].哈尔滨理工大学,2016.

[12] 国亮,邵砾群,惠荣荣.基于国外经验的农业节水灌溉技术推广措施分析[J].陕西农业科学,2013(6):117-119.

[13] 李定.农田灌溉无线控制系统的设计研究[D].石河子大学,2013.

[14] 杜艳艳.国内外设施农业技术研究进展与发展趋势[J].广东农业科学,2010(4):346-349.

[15] 李志博,田军仓.农田智能灌溉系统的研究进展[J].宁夏工程技术,2019,18(3):275-279.

[16] 毕庆生,顿文涛,等.面向智能灌溉的物联网应用研究[J].农业网络信息,2014(5):40-43.

[17] 彭恰源.EDA技术在数字电路教学中的研究[J].科技论坛,2020(18):139-140.

[18] 王强.Altium Designer在电路设计中的应用[J].信息记录材料,2020,21(02):118-119.

[19] 张洋,刘军.STM32F7原理与应用[M].北京:北京航空航天大学出版社,2017.21-507.

[20] 刘火良,杨森.STM32 HAL库开发实战指南[M].北京:机械工业出版社,2013.15-239.

[21] 潘传勇,丁国臣,陈世夏.基于LM2596的不间断直流电源设计[J].现代电子技术,2013,36(17):107-109.

[22] 杨金玲.显示技术的TFT-LCD与OLED剖析[J].电子元器件与信息技术,2020,4(9):6-7.

[23] 徐锦钢,鄢妍.基于AT24C01的存储电路设计与应用[J].科技视界,2018 (29):23-24.

[24] 刘明.土壤湿度传感器在园林绿化灌溉上的应用初探[J].天津农林科技,2020(3):31-32,46.

[25] 华成英,董诗白.模拟电子技术基础(第四版)[M].北京:高等教育出版社,2006.325-335.

[26] 高蒙.基于机智云平台的远程监控系统开发关键技术研究[D].西安理工大学,2019.

[27] 周小辉,吴燕,刘梦颖,李书竹.基于WiFi的智能浇花系统设计与实现[J].智城实践.2021(05):4-5.

[28] 贾志成,程敏,宋涛,王彦,康志龙,郭艳菊.基于状态机的LCD多级菜单设计[J].经验交流,2012(2):73-75.

致 谢

        光阴似箭,六月的到来也意味着我的大学生涯画上了一个完整的句号,但对于我的人生来说却只是个小小的逗号。这四年对于夏蝉而言,是可望不可及的遥远;这四年对中华上下五千年而言,可谓是沧海一粟;这四年对于我而言,是知识上的蜕变、是为人格上的提升。在此向所有帮助过我的老师、同学、朋友和亲人表达我最真诚的感谢。

        此处省略……

        由于我的学术水平有限,论文难免会出现一些不足之处,欢迎各位老师和前辈高人指正,谢谢。最后用一句古诗“大鹏一日同风起,扶摇直上九万里”作为论文的结束语和大学时代结束的标志,愿自己不负韶华,继续追寻自己的梦想。谢谢!

附录A:电路原理图

附录B:通信协议表

通信标准

引脚说明

通信方式

通信方向

UART

(通用异步收发器)

TXD:发送端

RXD:接受端

GND:公共地

异步通信

全双工

USART

(同步串行收发器)

在UART的基础上

增加时钟信号线

同步通信

全双工

单总线

(1-wire)

DQ:发送/接受端

异步通信

半双工

SPI

(串行外设接口)

SCK:同步时钟

MISO:主机输入,从机输出

MOSI:主机输出,从机输入

CS:片选信号

同步通信

全双工

QSPI

(Queued SPI)

CLK:时钟

IO0、IO1、IO2、IO3:输入输出口

CS:片选信号

同步通信

全双工

I2C

(集成电路总线)

SCL:同步时钟

SDA:数据输入输出端

同步通信

半双工

……

……

……

……

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值