行业知识
基于I.MX6ULL的Linux C多线程物联网网关+STM32+Qt上位机+Linux C++多线程服务器(含web)的多种无线通信系统的智慧农场
前言
我国是农业大国,而非农业强国。近30年来农业高产量主要依靠农药化肥的大量投入,大部分化肥和水资源没有被有效利用而随地弃置,导致大量养分损失并造成环境污染。我国农业生产仍然以传统生产模式为主,传统耕种只能凭经验施肥灌溉,不仅浪费大量的人力物力,也对环境保护与水土保持构成严重威胁,对农业可持续性发展带来严峻挑战
基于I.MX6ULL的Linux C多线程物联网网关+STM32+Qt上位机+Linux C++多线程服务器(含web服务)的多种无线通信系统的智慧农场项目
技术栈+硬件选型
Linux c++应用编程(JS,WEB,HTML什么的我只知道一点点皮毛,只用了其一两个函数);
Linux socket编程,多线程编程,内核驱动编程,文件I/O
Qt/C++ 客户端开发;
Mysql 数据存储;
C语言下位机开发;
I.MX6ULL 挺贵,不建议买,我脑抽买了:
光敏模块 模拟天黑天亮;
水泵 抽水;
电机 散热
电机驱动模块 *2
土壤湿度检测传感器;
ZigBee DL_22 *2 45r;
HC-06 *2 蓝牙模块;
stm32c8t6 *4 下位机(便宜,够用,市面价格10r);
RC522(RFID模块 SPI协议) 与白卡通信 获取卡号;
DTH11 温湿度采集模块(单总线协议,市面价格 4r);
sg90 舵机模块(PWM协议 市面价9r );
8226 01-s WIFI模块*2 (uart协议 市面价格5r) 连接 C++ 服务器 和做热点;
蜂鸣器 RFID注册提示音
总设计流程
本系统一共有四个单片机,四个单片机上分别挂载了的不同传感器结点和一个通信模块,通过三种无线通信协议向物联网网关发送结点数据,物联网网关收到数据将数据上传至Linux C++云服务器,服务器监听了5个端口,分别是用于监听物联网网关的消息,web服务的80端口用与发送HTML给浏览器,与JavaScript通信更新HTML网页的端口,还有物联网网关终端Qt界面,用与对物联网网关的下结点的整体控制与显示,最后一个端口给一个Qt移动端的端口,用于智慧农场的一系列数据显示,整个物联网系统服务于智慧农场。两两单片机可以说毫无联系,但经过网关和服务器的连接,又显得联系紧密恰巧凸显一个完整的物联网控制系统。
STM32C8T6:
除了使用8266模块的单片机不需要编号因为8266会自动为其局域网下的用户编号,其他都需要编号,同一个无线传感器上编号必须不一样,还有就上传服务器的设备名字不能一样,Mysql将设备名字设为主键了,唯一。单片机需要每隔2s左右上传一次传感器结点数据,上传格式为 “ ID+传感器设备名字+#+value+操作符” 例如 ,002舵机#false0 , 001电机#true#LED3#false0 这样为一个数据包可一直延申,按格式写就好了,服务器按格式拆包。因为存在设备控制,所以需要预留控制接口,比如对收到的数据拆包,如果收到id与自身id相同,再判断传感器设备名字,如果传感器设备名字相同,在根据value去改变设备状态。操作符什么的在服务器介绍那边会讲诉清楚,知道有这个事情就可以了。RFID模块上传数据都很特殊,所以自己写了几个操作符专门服务于RFID设备的注册,与门禁比对。
I.MX6ULL Linux C 多线程物联网网关:
一共有四个通信模块,物联网网关通过三种无线通信模块接收数据MCU传来的数据,然后通过一个无线通信模块(8266 -01s Sation模式)连接Linux云服务器上传数据,所以可以通过三种协议向物联网网关发送数据,无线通信为块为ESP 8266-01s,HC-06,ZigBee,其中只有HC-06是一对一通信,其他都是可以一对多,三种通信模块都是串口(UART)驱动,其中一个ESP 8266-01s配置成AP 模式,AP 模式是指 ESP8266 模块自身作为一个热点,然后监听一个8888端口,单片机即可直接与其连接,从而实现物联网网关获取整个局域网的结点信息,另外一个ESP 8266-01s 配置成Sation 模式,Sation 模式是 ESP8266 模块通过路由器连接Linux云服务器,对设备的远程控制功就能通过互联网实现,用于将其他三个模块收到的消息上传Linux云服务器。ZigBee是硬件上按钮配置,配置成相同信道,广播模式就可以接收同信道的数据了。三种都是串口协议,所以我们需要编写I.MX6ULL的Linux驱动程序,这里I.MX6ULL的恩智浦官方已经写好了,但是我不会搞设备树那些,好不容易修改的设备树去编译驱动然后Uboot启动I.MX6ULL时,内核启动报错,我也不知道什么原因,用的是原子的的设备树和内核文件,当时搞不出来,我就中直接用配置寄存器的方式自己写4个串口驱动就行了,串口驱动还是简单的,照着裸机历程去配置寄存器就好了,有一些细微的区别就是定义寄存器地址需要映射出寄存器虚拟地址指针,读取寄存器修改寄存器的方式也不同。
驱动程序编写,最重要的就是文件操作结构体,用户与内核空间信息交互的桥梁,学过STM32都知道串口接收函数的编写吧,接收寄存读到的数据一直追加在一个BUF里面,直到当收到\r\n时,代表接收完毕,立一个标志位,用户空间一直通过read函数读取驱动文件,如果驱动文件中的标志位为一,则代表收到了一个完整的数据包,将数据包发给用户空间,然后清除标志位。这样用户空间获取到了该通信模块接收到的数据,这里不同的是8266的消息接收函数不一样! ! ! 当时因为接收函数都写一样的这个8266的一直收不到,这个8266收到的消息有前有多个\r\n所以需要对其过滤。
一共3模块,所以需要开启三个线程,三个线程打开各自的通信模块的UART驱动模块文件然后按照AT指令集用write函数将用户空间的数据发送到内核空间然后去发送AT指令配置模块,配置完成后就轮询获取接收标志位,如果收到数据,就通过一个 ESP 8266-01s 发送给Linux云服务器,所以思路就有了,我们创建三个线程,6个全局变量,其中三个为各自的接收数据BUF,另外三个为各自的标志位,子线程循环读取内核模块传给用户空间的消息,当read函数>0,说明内核模块中的标志位被置一,说明接收到了一个完整的数据包,然后将用户空间的对应标志位置一,主线程循环判断三个标志位,当其中一个标志位为一,就将对应的buf写入 上传Linux云服务器的那个8266的UART内核驱动文件,内核驱动文件就将其写入寄存器上传Linux云服务器,最后标志位赋值为0,一直轮询下去,然后需要将打开的文件的文件描述符置为全局变量方便主线程收到服务器的消息需要对特定的文件进行操作(写入寄存器给指定的无线通信模块发送数据)。
一共有三个对外接收消息的模块嘛,在网关我们可以知道该数据是哪个无线传感器来的数据,但是上传服务器器后,服务器不知道,所以我们将经过 8266-01s结点的消息都加1000 ,ZigBee加2000,HC-06加3000 ,这样其中单片机id为001经过ZigBee发过来就成了2001,同理蓝牙,但是8266不一样,他热点模式自动分配IP号,可以直接实现该局域网下点对点通信,所以8266的单片机不需要发送带id的数据包,这样就根据单片机的id值大小范围我们就知道了他所在结点区域,这样就可以实现服务器对其的控制,同样收到服务器传下来的控制数据包时,我们需要根据范围去判断该值的是哪个结点的数据包,然后减去该增加的数,最后传到指定单片机上。
主循环
while(1){if(WiFi8266Server_Flag==1){WiFi8266Server_handle();WiFi8266Server_Flag=0;}if(UARTServer_Flag==1){TCP_Server_handle();UARTServer_Flag=0;}if(ZigBee_Flag==1){ ZigBee_handle();ZigBee_Flag=0;}if(HC06_Flag==1){HC06_handle();HC06_Flag=0; }if(UART_Flag==1){ UART_handle(); UART_Flag=0; }} 串口3文件驱动编写,按照可以去写其他几个
#define NEWCHRIOBEE_CNT1 /* 设备号个数 */#define NEWCHRIOBEE_NAME"newchriobee"/* 名字 *//* 寄存器物理地址 */#define CCM_CGPR1_BASE (0x020C406C) //uart3 ,uart4#define CCM_CGPR0_BASE (0x020C4068) //uart2//3#define IOMUXC_UART3_TX_DATA_UART3_TX_BASE (0x020E00A4) #define IOMUXC_UART3_RX_DATA_UART3_RX_BASE (0x020E00A8)#define PAD_CTL_PAD_UART3_TX_DATA_BASE (0x020E0330)#define PAD_CTL_PAD_UART3_RX_DATA_BASE (0x020E0334)#define UART3_UFCR_BASE (0x021EC090)#define UART3_UBIR_BASE (0x021EC0A4) #define UART3_UBMR_BASE (0x021EC0A8)#define UART3_UCR1_BASE (0x021EC080)#define UART3_UCR2_BASE (0x021EC084)#define UART3_UCR3_BASE (0x021EC088)#define UART3_USR2_BASE (0x021EC098)#define UART3_URXD_BASE (0x021EC000)#define UART3_UTXD_BASE (0x021EC040)/* 映射后的寄存器虚拟地址指针 */static void __iomem * CCM_CGPR1;static void __iomem * CCM_CGPR0;static void __iomem *IOMUXC_UART3_TX_DATA_UART3_TX;static void __iomem *IOMUXC_UART3_RX_DATA_UART3_RX;static void __iomem *PAD_CTL_PAD_UART3_TX_DATA;static void __iomem *PAD_CTL_PAD_UART3_RX_DATA;static void __iomem *UART3_UFCR;static void __iomem *UART3_UBIR;static void __iomem *UART3_UBMR;static void __iomem *UART3_UCR1;static void __iomem *UART3_UCR2;static void __iomem *UART3_UCR3;static void __iomem *UART3_USR2;static void __iomem *UART3_URXD;static void __iomem *UART3_UTXD; void register_init(void); void uart_init(void); void uart_io_init(void); void uart_disable(void); void uart_enable(void); void uart_softreset(void); int getc(void); void myexit(void);unsigned char i; char RECS[100];void register_init(void){ printk("register_init\r\n"); CCM_CGPR1=ioremap(CCM_CGPR1_BASE,4); CCM_CGPR0=ioremap(CCM_CGPR0_BASE,4);IOMUXC_UART3_TX_DATA_UART3_TX=ioremap(IOMUXC_UART3_TX_DATA_UART3_TX_BASE, 4);IOMUXC_UART3_RX_DATA_UART3_RX=ioremap(IOMUXC_UART3_RX_DATA_UART3_RX_BASE,4);PAD_CTL_PAD_UART3_TX_DATA=ioremap(PAD_CTL_PAD_UART3_TX_DATA_BASE,4);PAD_CTL_PAD_UART3_RX_DATA=ioremap(PAD_CTL_PAD_UART3_RX_DATA_BASE,4);UART3_UFCR=ioremap(UART3_UFCR_BASE,4);UART3_UBIR=ioremap(UART3_UBIR_BASE,4);UART3_UBMR=ioremap(UART3_UBMR_BASE,4);UART3_UCR1=ioremap(UART3_UCR1_BASE,4);UART3_UCR2=ioremap(UART3_UCR2_BASE,4);UART3_UCR3=ioremap(UART3_UCR3_BASE,4);UART3_USR2=ioremap(UART3_USR2_BASE,4);UART3_URXD=ioremap(UART3_URXD_BASE,4);UART3_UTXD=ioremap(UART3_UTXD_BASE,4);}void uart_init(void){__u32 ret;uart_io_init(); writel(0XFFFFFFFF,CCM_CGPR1);writel(0XFFFFFFFF,CCM_CGPR0);uart_disable();uart_softreset();writel(0,UART3_UCR1); ret=readl(UART3_UCR2);ret|= (1
上一篇:新开的服务器地址怎么查?