2022湖南省物联网应用创新竞赛:基于物联网的校园直饮水管理系统

文章目录

  • 物联网赛题
  • A. 直饮水机
  • B. 后端服务器
  • C. 前端应用终端
  • D. 巡检装置
  • 一、思维导图
  • 二、硬件选择
  • 三、STM32端
  • 四、服务器端

  • 物联网赛题

    1.应用场景描述

    直饮水在校园和公共场所到处可见,如校园教室楼、图书馆、体育馆、食堂和宿舍,以及公共场所如火车站、机场、购物中心、公园等。直饮水给我们学习、工作和生活带来很多便利。然而在直饮水广泛使用的同时,我们也面临如下问题:

    1. 到了一个陌生地方,如何知道哪里有水喝?
    1. 所供饮水设备是否工作正常?水质是否合格?
    1. 作为直饮水管理部门,如何快速、准确、方便了解所管控设备的状况?
  • 基于物联网的校园直饮水管理系统让饮水、饮水人、饮水机、饮水管理和关注者等所有与“饮水”关联的人和物之间实现饮水信息相连相息。

    2. 竞赛题目

    不限平台,搭建基于物联网的校园直饮水管理系统。假设系统由直饮水机、后端服务器、前端应用终端、以及直饮水机专用巡检装置组成,各部分功能作用如下:

    A. 直饮水机

    设直饮水机组成和控制如图所示:

    1) 每台饮水机有一个唯一的ID号, 如:ID1=2001 ID2=2002等;。

    2) 饮水机可以读取学生校园卡 ( RFID卡,数量2个及以上)卡号CardNumber,只有当合法学生校园卡放置在读卡区(比赛场景仅要求读面到校园卡号,无需核实其合法性),饮水机才提供饮水服务;

    3) 每台饮水机有“复位”、“暂停”“正常”三种状态,其工作能被远皮远程后台控制: a)饮水机在 “复位”、“暂停”状态时不提供饮水服务,仅“正常状态提供饮水服务;

    b) 饮水机每次上电后进入“复位"并向后台发送复位消息。后台收到复位消息后,自动执行:

  • 向饮水机发送系统时钟(时分秒),以帮助饮水机同步时间(饮水机后续计时可由本地处理);

  • 根据系统设定情况向饮水机发出“正常”或“暂停”指令。

  • 4) 常温水箱内部设有高水位(Wh)、 低水位(W1)检测传感器(竞赛场景可用接近开关、红外开关、微动开关、或按键等代替):

    a. “正常”状态时,当水位低于Wl时自动启动加压水泵M (竞赛中用电机代替),经M加压的高压水通过净化装置净化后流入常温水箱;当水位高于Wh时加压泵M停止工作。

    b. 如果水位传感器状态指示:低于WI、同时高于Wh,判断为设备不正常,系统应提示设备故障(现场显示故障,蜂鸣器循环发“响0.4秒、停0.6秒”报警声,并向后台发出设备故障代码“Error1”);

    5) TDS ( 水中固体物质含量指标,单位ppm)是衡量水质的一一个重要指标。TDS测定与测量探头设计、测量电路、被测水温、标定方法等因素相关,具体到水质测量一般可简化为水温和电压的测量。例如:

    TDS=110*(Vcc-V)/V)*(1+0.02*(T-25))
    

    是一款水质传感器的TDS计算公式。假设在水源(净化前)和常温水箱(净化后)两处测定TDS以监测饮水机工作状况和饮水水质情况,并假设被测水温T=25°C、水质测量电路供 电电压Vcc=5 (伏):
    a. 水质计算公 式为:

    水源水质: TDS1=110*((Vcc-V1)/V1)(V1不等于0时)
    
    饮水水质: TDS2=110*(Vcc-V2)/V2) (V2 不等于0时)
    

    式中: V1、V2为水源和常温水箱两处水质探测得到的电压(竞赛场景用电压代替)。b)当水质TDS2值大于100ppm表示饮水水质不合格(现场显示警告,蜂鸣器循环发“响0.1秒、停0.9秒”警告声,并向后台发出警告代码“Warning1");

    6) 饮水量由流量计F (竞赛场景可用编码器、按键等代替,每一个转动量、或脉冲计数代表一定流量饮水)测定;

    7) 热饮水加热器P (可用指示灯代替)受热饮水温度T2自动控制:
    a) 供热饮水时,当T2低于设定值TH时加热、高于TH时停止加热。
    b)TH可本地或经移动终端尚设定和改变。

    8) 电磁水阀s1或s2 (竞赛中电磁水阀可用指示灯代替,设常温饮水和热饮水共用一个出水口,控制常温饮水或热饮水开闭,S1和S2不能同时“开”(即不能同时出常温水和热)。

    9) 按键K1 (常温水)和K2 (热水)操控饮水机出水
    a)停止出水(S1闭、S2闭)情况下:

    按下K1出常温水,松开K1维持出常温水状态不变:
    或按下K2出热水,松开K2维持出热水状态不变:

    b) 出常温水(S1开、S2闭)时:

    按下K1停止出水;
    或按下K2转换成出热水;

    c) 出热水(S1闭、S2开)时:

    按下K1转换成出常温水;
    或按下K2停止出水;

    d) 一次出水量(流量计F计数值)超过一定流量值时,也自动停止出水。

    10) 饮水机上能够显示(同时、分时、切换均可):

    a) 本地时间(时分秒)

    b) 水温(T1、T2)

    c) 水质(TDS1、 TDS2)

    11) 饮水机具备与后台服务器通信功能,将下列信息传到后台服务器供管理和应用:
    a) 饮水机ID;
    b) 饮水学生校园卡卡号(CardNumber);
    c)饮水时间(时分秒)
    d)饮水量
    e)故障时设备故障代码“Error1”
    f)警告时警告代码“Warning1”。

    12) 挑战性功能1:当饮水机临时断电,本地或经移动终端设置的热水控制参数TH,在饮水机复电后仍然自动有效;

    13) 挑战性功能2:当饮水机与后台服务器“断网”,联网后能够将“断网”期间发生的相关饮水事件数据不丢失地传送至后端服务器。竞赛申假设“断网”期间发生的饮水事件不小于2次。

    B. 后端服务器

    构建校园直饮水信息物联网后台服务器,支撑校园直饮水信息收集、储存、管理、应用。竞赛题目要求后台服务器:
    1) 与各饮水机之间建立通信联系;

    2) 收到某饮水机“复位”消息后,向饮水机回送系统时钟(时分秒) ,系统设定情况向饮水机发出正常”或“暂停”指令(参见A3.b)

    3) 后台服务器数据或数据库包含:

    a)饮水机位置信息, 如:

    1D=2001位置:图书馆、或其它
    1D-2002位置:教学楼、或其它

    b)饮水机工作属性:

    复位:(上电未与服务器连通时状态、不提供饮水服务)
    暂停: (该饮水机因故暂停工作、不提供饮水服务)
    正常: (该饮水机正常工作)

    4) 接收、记录饮水机发出的各种饮水信息(参见A.11);

    5) 给应用前端(移动端用户、计算机用户等)提供查询、统计和控制操作等。

    C. 前端应用终端

    校园直饮水信息系统前端应用如通过网络终端(如计算机)或移动终端(如智能手机App)提供的应用(竞赛作品简化,不区分饮水用户和管理用户),要求:

    1)选择实现:移动终端(智能手机App)或网络终端(网页浏览器),二选一即可,移动终端优先;

    2)应用终端上可修改后台服务器上饮水机工作属性(“正常”或“暂停”)。修改后,后台服务器应根据工作属性对饮水机进行同步控制;

    3)应用终端上可查询全部饮水机(不少于2台)位置、工作状态及水质信息;4)应用终端上可查询某饮水机某段时间内输出的饮水总量;

    5)应用终端上可查询某学生某段时间内在所有饮水机(不少于2台)上的饮水总量;6)饮水机“故障”、或“报警”时,应用终端上同步显示。

    D. 巡检装置

    专用于饮水机维护维修的移动装置。当巡检人员携带该巡检装置到达饮水机附近时,可与饮水机进行信息交互,方便维护维修人员快速了解饮水机状况,要求:

    1)与饮水机之间不经过后台服务器、公共通信网络,仅巡检装置与饮水机之间现场点对点、无线方式通信;

    2)巡检设备可显示连接的饮水机: ID 号,水质检测电压V1、V2,报警信息。


    一、思维导图

    1. 处理模块
    以Stm32开发板作为中心节点,外接多路传感器,分别采集实时数据,打印在LCD显示屏上。

    2. 服务器
    ①可以选择Linux平台作为服务器,通过TCP将Stm32上采集到的数据发送至服务器上然后解析出数据,插入本地数据库。
    ②可以选择用Qt开发 作服务器,并连接数据库,同样将Stm32传来的数据插入数据库中。

    3. Android或Web
    ①可以用Java开发Android,通过查看服务器中的数据库更新,发现异样可通过服务器进而控制Stm32端的操作。
    ②可以用Qt开发Android,通过查看服务器中的数据库更新,发现异样可通过服务器进而控制Stm32端的操作。
    ③可以用JSP来编写Web开发,通过查看服务器中的数据库更新,发现异样可通过服务器进而控制Stm32端的操作。

    4. 巡检装置
    ①可以外加一块Stm32板子,通过LoRa通信 查看各处理模块的状态
    ②可以通过蓝牙模块在手机上直接查看

    二、硬件选择

    以该赛题为例。

  • 饮水机控制模块: Stm32开发板(原子哥的战舰、野火的指南针等),
  • 冷、热水显示: LED灯(红灯表示热水,绿灯表示冷水)
  • 温度、湿度: DHT11
  • 温度、湿度、光照强度、海拔、压强: GY39
  • 校园卡号: RFID
  • 电泵: L298n、直流电机
  • 通信模块: ESP8266、蓝牙、LoRa
  • 报警: 蜂鸣器
  • 热冷水切换: 按键
  • 电压、电流: ADC
  • 显示屏: LCD、OLED、串口助手、终端
  • 以及若干按键、开关、LED灯、万用表、电烙铁、电池、杜邦线等等。
  • 三、STM32端

    本篇采用的模块有:

  • RFID(串口通信)
  • GY39(IIC通信)
  • ESP8266(串口通信)
  • OLED(IIC通信)
  • Timer(调制电机速度)
  • Flash (断网重传)
  • Beep (报警)
  • LED (状态显示)
  • ADC (测电压)
  • Usart (数据传输)
  • Key (控制逻辑)
  • 1. RFID

    unsigned char status;
    	unsigned char i;
    	Cmd_Read_Id[5] = 0x01;
    	TxCheckSum(Cmd_Read_Id,Cmd_Read_Id[1]);		//计算校验和
    	Uart3_Send_Data(Cmd_Read_Id,Cmd_Read_Id[1]);		 //发送读卡号ID命令
    	Delay(1600000);//等待模块返回数据,大于150MS
     	if(Rx3Flag == 1)
     	{	
    		Rx3Flag = 0;
    		status = RxCheckSum(Uart3RxBuf,Uart3RxBuf[1]);//对接收到的数据校验
    		if(status != 0)  //判断校验和是否正确
    		{
    			return 1;
    		}
    		
    		status = Uart3RxBuf[4];
    		if(status != 0)	//判断是否正确的读到卡
    		{
    		 	return 1;
    		}
    		
    		if((Uart3RxBuf[0] == 0x01)&&(Uart3RxBuf[2] == 0xa1))//判断是否为读卡号返回的数据包
    		{
    			rfid_id=0;
    			for(i=0;i<6;i++)//获取卡号ID,6字节		 
    			{
    				idout[i] = Uart3RxBuf[i+5];//从数组的第5个字节开始为卡号,长度为6字节
    				Number=Uart3RxBuf[5+i];
    				rfid_id=rfid_id*10+Number;
    			}
    			return rfid_id;		 //成功返回
    		}
     	} 
    	return 1;			//失败返回1
    

    2. LCD

    void LCD_Init(void);													   	//初始化
    void LCD_DisplayOn(void);													//开显示
    void LCD_DisplayOff(void);													//关显示
    void LCD_Clear(u16 Color);	 												//清屏
    void LCD_SetCursor(u16 Xpos, u16 Ypos);										//设置光标
    void LCD_DrawPoint(u16 x,u16 y);											//画点
    void LCD_Fast_DrawPoint(u16 x,u16 y,u16 color);								//快速画点
    u16  LCD_ReadPoint(u16 x,u16 y); 											//读点 
    void LCD_Draw_Circle(u16 x0,u16 y0,u8 r);						 			//画圆
    void LCD_DrawLine(u16 x1, u16 y1, u16 x2, u16 y2);							//画线
    void LCD_DrawRectangle(u16 x1, u16 y1, u16 x2, u16 y2);		   				//画矩形
    void LCD_Fill(u16 sx,u16 sy,u16 ex,u16 ey,u16 color);		   				//填充单色
    void LCD_Color_Fill(u16 sx,u16 sy,u16 ex,u16 ey,u16 *color);				//填充指定颜色
    void LCD_ShowChar(u16 x,u16 y,u8 num,u8 size,u8 mode);						//显示一个字符
    void LCD_ShowNum(u16 x,u16 y,u32 num,u8 len,u8 size);  						//显示一个数字
    void LCD_ShowHexNum(u16 x,u16 y,uint32_t num,u8 len,u8 size);  						//显示一个16数字
    void LCD_ShowxNum(u16 x,u16 y,u32 num,u8 len,u8 size,u8 mode);				//显示 数字
    void LCD_ShowString(u16 x,u16 y,u16 width,u16 height,u8 size,u8 *p);		//显示一个字符串,12/16字体
    
    void LCD_WriteReg(u16 LCD_Reg, u16 LCD_RegValue);
    u16 LCD_ReadReg(u16 LCD_Reg);
    void LCD_WriteRAM_Prepare(void);
    void LCD_WriteRAM(u16 RGB_Code);
    void LCD_SSD_BackLightSet(u8 pwm);							//SSD1963 背光控制
    void LCD_Scan_Dir(u8 dir);									//设置屏扫描方向
    void LCD_Display_Dir(u8 dir);								//设置屏幕显示方向
    void LCD_Set_Window(u16 sx,u16 sy,u16 width,u16 height);	//设置窗口			
    

    3. oled

    OLED_I2C_Init();			//端口初始化
    	
    	OLED_WriteCommand(0xAE);	//关闭显示
    	
    	OLED_WriteCommand(0xD5);	//设置显示时钟分频比/振荡器频率
    	OLED_WriteCommand(0x80);
    	
    	OLED_WriteCommand(0xA8);	//设置多路复用率
    	OLED_WriteCommand(0x3F);
    	
    	OLED_WriteCommand(0xD3);	//设置显示偏移
    	OLED_WriteCommand(0x00);
    	
    	OLED_WriteCommand(0x40);	//设置显示开始行
    	
    	OLED_WriteCommand(0xA1);	//设置左右方向,0xA1正常 0xA0左右反置
    	
    	OLED_WriteCommand(0xC8);	//设置上下方向,0xC8正常 0xC0上下反置
    
    	OLED_WriteCommand(0xDA);	//设置COM引脚硬件配置
    	OLED_WriteCommand(0x12);
    	
    	OLED_WriteCommand(0x81);	//设置对比度控制
    	OLED_WriteCommand(0xCF);
    
    	OLED_WriteCommand(0xD9);	//设置预充电周期
    	OLED_WriteCommand(0xF1);
    
    	OLED_WriteCommand(0xDB);	//设置VCOMH取消选择级别
    	OLED_WriteCommand(0x30);
    
    	OLED_WriteCommand(0xA4);	//设置整个显示打开/关闭
    
    	OLED_WriteCommand(0xA6);	//设置正常/倒转显示
    
    	OLED_WriteCommand(0x8D);	//设置充电泵
    	OLED_WriteCommand(0x14);
    
    	OLED_WriteCommand(0xAF);	//开启显示
    		
    	OLED_Clear();				//OLED清屏
    

    4. Key

    u8 KEY_Scan(u8 mode)
    {	 
    	static u8 key_up=1;//按键按松开标志
    	if(mode)key_up=1;  //支持连按		  
    	if(key_up&&(KEY0==0||KEY1==0||KEY2==0||WK_UP==1))
    	{
    		delay_ms(10);//去抖动 
    		key_up=0;
    		if(KEY0==0)return KEY0_PRES;
    		else if(KEY1==0)return KEY1_PRES;
    		else if(KEY2==0)return KEY2_PRES;
    		else if(WK_UP==1)return WKUP_PRES;
    	}else if(KEY0==1&&KEY1==1&&KEY2==1&&WK_UP==0)key_up=1; 	    
     	return 0;// 无按键按下
    }
    
    u8 KEY2_Scan(void)
    {
    	uint8_t LIE,HANG,k,i=0;
    	GPIO_Write(GPIOC, 0xF0);                            //D0-D3拉低 D4-D7拉高
    	if((GPIO_ReadInputData(GPIOC)&0xF0)!=0xF0)          //有按键按下
    	{
    	  delay_ms(40);                                     //消抖
    	   if((GPIO_ReadInputData(GPIOC)&0xF0)!=0xF0)       //再次判断是否有按下
    	   {
    		   LIE=GPIO_ReadInputData(GPIOC);                 //读取按键按下后得到的代码
    		   HANG=LIE;                                      //将代码复制给行
    		   LIE=~LIE;                                      //将键码取反,如按下某个键得到0111 0000 取反得到1000 1111
    		   LIE=LIE&0XF0;                                  //得到列1000 1111&1111 0000得到1000 0000,得到列数
    		   for(i=0;i<4&&((HANG&0xF0)!=0xF0);i++)          //逐次将行拉高,判断列数中原来变低的位是否变高
    		   {                                              //得到之前检测到低的列变高则退出
    			   GPIO_Write(GPIOC, (HANG&0xF0)|(0x01<<i));  //进行行扫描,逐次将行口线拉高,列保持为按下时的状态
    			   HANG=GPIO_ReadInputData(GPIOC);            //读取IO口,用以判断是否扫描到行坐标		   
    		   }
    		   HANG&=0x0F;                                    //将行值取出
    		   k=LIE|HANG;                                    //行列相加得到键码
    		   GPIO_Write(GPIOC, 0xF0);                       //D0-D3拉低 D4-D7拉高 此处将行列状态初始化为未按下时的状态
    			while((GPIO_ReadInputData(GPIOC)&0xF0)!=0xF0)  //判释放
    		   {
    				delay_ms(40);                             //后沿消抖,时间需要长一点,小按键消抖,时间可以短一点,大按键消抖严重消抖需要长一点
    		   }
    		   return k;                                      //·返回键码
    	   }
    	}	
    	return (0);                                         //无按键按下,返回0
    
    }
    

    5. beep

    void BEEP_Init(void)
    {
     
     GPIO_InitTypeDef  GPIO_InitStructure;
     	
     RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);	 //使能GPIOB端口时钟
     
     GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;				 //BEEP-->PB.8 端口配置
     GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; 		 //推挽输出
     GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;	 //速度为50MHz
     GPIO_Init(GPIOB, &GPIO_InitStructure);	 //根据参数初始化GPIOB.8
     
     GPIO_ResetBits(GPIOB,GPIO_Pin_8);//输出0,关闭蜂鸣器输出
    
    }
    
    void beep(void){	 
    		delay_init();		
    		BEEP=0;		  
    		delay_ms(300);//延时300ms 
    		BEEP=1;  
    		delay_ms(300);//延时300ms
    }
    
    

    6. ADC

    ADC_InitTypeDef ADC_InitStructure; 
    	GPIO_InitTypeDef GPIO_InitStructure;
    
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA |RCC_APB2Periph_ADC1, ENABLE );	  //使能ADC1通道时钟
     
    
    	RCC_ADCCLKConfig(RCC_PCLK2_Div6);   //设置ADC分频因子6 72M/6=12,ADC最大时间不能超过14M
    
    	//PA1 作为模拟通道输入引脚                         
    	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;
    	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;		//模拟输入引脚
    	GPIO_Init(GPIOA, &GPIO_InitStructure);	
    
    	ADC_DeInit(ADC1);  //复位ADC1 
    
    	ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;	//ADC工作模式:ADC1和ADC2工作在独立模式
    	ADC_InitStructure.ADC_ScanConvMode = DISABLE;	//模数转换工作在单通道模式
    	ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;	//模数转换工作在单次转换模式
    	ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;	//转换由软件而不是外部触发启动
    	ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;	//ADC数据右对齐
    	ADC_InitStructure.ADC_NbrOfChannel = 1;	//顺序进行规则转换的ADC通道的数目
    	ADC_Init(ADC1, &ADC_InitStructure);	//根据ADC_InitStruct中指定的参数初始化外设ADCx的寄存器   
    
      
    	ADC_Cmd(ADC1, ENABLE);	//使能指定的ADC1
    	
    	ADC_ResetCalibration(ADC1);	//使能复位校准  
    	 
    	while(ADC_GetResetCalibrationStatus(ADC1));	//等待复位校准结束
    	
    	ADC_StartCalibration(ADC1);	 //开启AD校准
     
    	while(ADC_GetCalibrationStatus(ADC1));	 //等待校准结束
     
    //	ADC_SoftwareStartConvCmd(ADC1, ENABLE);		//使能指定的ADC1的软件转换启动功能
    

    7. Timer

    void TIM7_IRQHandler(void)
    { 	
    	if (TIM_GetITStatus(TIM7, TIM_IT_Update) != RESET)//是更新中断
    	{	 
    		
    		USART2_RX_STA|=1<<15;	//标记接收完成
    		TIM_ClearITPendingBit(TIM7, TIM_IT_Update  );  //清除TIM7更新中断标志    
    		TIM_Cmd(TIM7, DISABLE);  //关闭TIM7 
    	}	    
    }
     
    //通用定时器7中断初始化,这里时钟选择为APB1的2倍
    //arr:自动重装值 psc:时钟预分频数
    //定时器溢出时间计算方法:Tout=((arr+1)*(psc+1))/Ft us.
    //Ft=定时器工作频率,单位:Mhz 
    //通用定时器中断初始化 
    void TIM7_Int_Init(u16 arr,u16 psc)
    {	
    	NVIC_InitTypeDef NVIC_InitStructure;
    	TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
    
    	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM7, ENABLE);//TIM7时钟使能    
    	
    	//定时器TIM7初始化
    	TIM_TimeBaseStructure.TIM_Period = arr; //设置在下一个更新事件装入活动的自动重装载寄存器周期的值	
    	TIM_TimeBaseStructure.TIM_Prescaler =psc; //设置用来作为TIMx时钟频率除数的预分频值
    	TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; //设置时钟分割:TDTS = Tck_tim
    	TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;  //TIM向上计数模式
    	TIM_TimeBaseInit(TIM7, &TIM_TimeBaseStructure); //根据指定的参数初始化TIMx的时间基数单位
     
    	TIM_ITConfig(TIM7,TIM_IT_Update,ENABLE ); //使能指定的TIM7中断,允许更新中断
    	
    	TIM_Cmd(TIM7,ENABLE);//开启定时器7
    	
    	NVIC_InitStructure.NVIC_IRQChannel = TIM7_IRQn;
    	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0 ;//抢占优先级0
    	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;		//子优先级2
    	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;			//IRQ通道使能
    	NVIC_Init(&NVIC_InitStructure);	//根据指定的参数初始化VIC寄存器
    	
    }
    
    

    8. ESP8266

    void esp8266_start_trans(void)
    {
    	printf("等待初始化\r\n");
    	while(esp8266_send_cmd((u8 *)"AT",(u8 *)"OK",1000));
    	//printf("等待初始化1\r\n");
    	
    	//设置工作模式 1:station模式   2:AP模式  3:兼容 AP+station模式
    	while(esp8266_send_cmd((u8*)"AT+CWMODE=3",(u8*)"OK",1000));
    	printf("设置工作模式成功\r\n");
    	
    	delay_ms(1000);
    
    	//让模块连接上自己的路由
    	while(esp8266_send_cmd((u8*)"AT+CWJAP=\"xxx\",\"xxxxxxxxx\"",(u8*)"WIFI GOT IP",1000));
    	printf("连接路由器成功\r\n");
    	delay_ms(1000);
    	
    	//=0:单路连接模式     =1:多路连接模式
    	while(esp8266_send_cmd((u8*)"AT+CIPMUX=0",(u8*)"OK",200)){printf("设置单路连接模式失败\r\n");}
    	printf("设置单路连接模式成功\r\n");
    	delay_ms(1000);
    
    	//建立TCP连接  这四项分别代表了 要连接的ID号0~4   连接类型  远程服务器IP地址   远程服务器端口号
    	while(esp8266_send_cmd((u8*)"AT+CIPSTART=\"TCP\",\"192.168.124.32\",8117",(u8*)"OK",5000));
    	printf("TCP连接成功\r\n");
    	delay_ms(1000);
    	
    	//是否开启透传模式  0:表示关闭 1:表示开启透传
    	esp8266_send_cmd((u8*)"AT+CIPMODE=1",(u8*)"OK",200);
    	printf("开启透传模式\r\n");
    	
    	//透传模式下 开始发送数据的指令 这个指令之后就可以直接发数据了
    	esp8266_send_cmd((u8*)"AT+CIPSEND",(u8*)"OK",50);
    	printf("开启透传成功\r\n");
    }
    

    9. GY39

    //**************************************
    //向IIC总线发送一个字节数据
    /*
    一个字节8bit,当SCL低电平时,准备好SDA,SCL高电平时,从机采样SDA
    */
    //**************************************
    void I2C_SendByte(u8 dat)
    {
      u8 i;
    	SCL_L;//SCL拉低,给SDA准备
      for (i=0; i<8; i++)         //8位计数器
      {
    		if(dat&0x80)//SDA准备
    		SDA_H;  
    		else 
    		SDA_L;
        SCL_H;                //拉高时钟,给从机采样
        delay_1us(5);        //延时保持IIC时钟频率,也是给从机采样有充足时间
        SCL_L;                //拉低时钟,给SDA准备
        delay_1us(5); 		  //延时保持IIC时钟频率
    		dat <<= 1;          //移出数据的最高位  
      }					 
    }
    //**************************************
    //从IIC总线接收一个字节数据
    //**************************************
    u8 I2C_RecvByte()
    {
        u8 i;
        u8 dat = 0;
        SDA_H;//释放SDA,给从机使用
        delay_1us(1);         //延时给从机准备SDA时间            
        for (i=0; i<8; i++)         //8位计数器
        { 
    		  dat <<= 1;
    			
          SCL_H;                //拉高时钟线,采样从机SDA
         
    		  if(SDA_read) //读数据    
    		   dat |=0x01;      
           delay_1us(5);     //延时保持IIC时钟频率		
           SCL_L;           //拉低时钟线,处理接收到的数据
           delay_1us(5);   //延时给从机准备SDA时间
        } 
        return dat;
    }
    //**************************************
    //向IIC设备写入一个字节数据
    //**************************************
    u8 Single_WriteI2C_byte(u8 Slave_Address,u8 REG_Address,u8 data)
    {
    	  if(I2C_Start()==0)  //起始信号
    		{I2C_Stop(); return RESET;}           
    
        I2C_SendByte(Slave_Address);   //发送设备地址+写信号
     	  if(!I2C_WaitAck()){I2C_Stop(); return RESET;}
       
    		I2C_SendByte(REG_Address);    //内部寄存器地址,
     	  if(!I2C_WaitAck()){I2C_Stop(); return RESET;}
       
    		I2C_SendByte(data);       //内部寄存器数据,
    	  if(!I2C_WaitAck()){I2C_Stop(); return RESET;}
    		
    		I2C_Stop();   //发送停止信号
    		
    		return SET;
    }
    //**************************************
    //从IIC设备读取一个字节数据
    //**************************************
    u8 Single_ReadI2C(u8 Slave_Address,u8 REG_Address,u8 *REG_data,u8 length)
    {
     if(I2C_Start()==0)  //起始信号
    		{I2C_Stop(); return RESET;}          
    	 
    	I2C_SendByte(Slave_Address);    //发送设备地址+写信号
     	if(!I2C_WaitAck()){I2C_Stop(); return RESET;} 
    	
    	I2C_SendByte(REG_Address);     //发送存储单元地址
     	if(!I2C_WaitAck()){I2C_Stop(); return RESET;} 
    	
    	if(I2C_Start()==0)  //起始信号
    			{I2C_Stop(); return RESET;}            
    
    	I2C_SendByte(Slave_Address+1);  //发送设备地址+读信号
     	if(!I2C_WaitAck()){I2C_Stop(); return RESET;}
    	
    	while(length-1)
    	{
    		*REG_data++=I2C_RecvByte();       //读出寄存器数据
    		I2C_SendACK(0);               //应答
    		length--;
    	}
    	*REG_data=I2C_RecvByte();  
    	I2C_SendACK(1);     //发送停止传输信号
    	I2C_Stop();                    //停止信号
    	return SET;
    }
    
    

    10. Usart

    void USART2_init(u32 bound)
    {  
    
    	NVIC_InitTypeDef NVIC_InitStructure;
    	GPIO_InitTypeDef GPIO_InitStructure;
    	USART_InitTypeDef USART_InitStructure;
    
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);	// GPIOB时钟
    	RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2,ENABLE); //串口3时钟使能
    
     	USART_DeInit(USART2);  //复位串口3
    		 //USART2_TX   PB10
      GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2; //PB10
      GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
      GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;	//复用推挽输出
      GPIO_Init(GPIOA, &GPIO_InitStructure); //初始化PB10
       
        //USART2_RX	  PB11
      GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;
      GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮空输入
      GPIO_Init(GPIOA, &GPIO_InitStructure);  //初始化PB11
    	
    	USART_InitStructure.USART_BaudRate = bound;//波特率一般设置为9600;
    	USART_InitStructure.USART_WordLength = USART_WordLength_8b;//字长为8位数据格式
    	USART_InitStructure.USART_StopBits = USART_StopBits_1;//一个停止位
    	USART_InitStructure.USART_Parity = USART_Parity_No;//无奇偶校验位
    	USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//无硬件数据流控制
    	USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;	//收发模式
      
    	USART_Init(USART2, &USART_InitStructure); //初始化串口	3
      
    
    	USART_Cmd(USART2, ENABLE);                    //使能串口 
    	
    	//使能接收中断
      USART_ITConfig(USART2, USART_IT_RXNE, ENABLE);//开启中断   
    	
    	//设置中断优先级
    	NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn;
    	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=2 ;//抢占优先级3
    	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;		//子优先级3
    	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;			//IRQ通道使能
    	NVIC_Init(&NVIC_InitStructure);	//根据指定的参数初始化VIC寄存器
    	
    	
    	TIM7_Int_Init(1000-1,7200-1);		//10ms中断
    	USART2_RX_STA=0;		//清零
    	TIM_Cmd(TIM7,DISABLE);			//关闭定时器7
    
    }
    

    11. LED

    //初始化PB5和PE5为输出口.并使能这两个口的时钟		    
    //LED IO初始化
    void LED_Init(void)
    {
     
     GPIO_InitTypeDef  GPIO_InitStructure;
     	
     RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB|RCC_APB2Periph_GPIOE, ENABLE);	 //使能PB,PE端口时钟
    	
     GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;				 //LED0-->PB.5 端口配置
     GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; 		 //推挽输出
     GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;		 //IO口速度为50MHz
     GPIO_Init(GPIOB, &GPIO_InitStructure);					 //根据设定参数初始化GPIOB.5
     GPIO_SetBits(GPIOB,GPIO_Pin_5);						 //PB.5 输出高
    
     GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;	    		 //LED1-->PE.5 端口配置, 推挽输出
     GPIO_Init(GPIOE, &GPIO_InitStructure);	  				 //推挽输出 ,IO口速度为50MHz
     GPIO_SetBits(GPIOE,GPIO_Pin_5); 						 //PE.5 输出高 
    }
     
    
    
    void LED1_Init(void)
    {
     
     GPIO_InitTypeDef  GPIO_InitStructure;
     GPIO_InitTypeDef  GPIO_InitStructure1;
     	
     RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);	 //使能PB,PE端口时钟
     RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD, ENABLE);	 //使能PB,PE端口时钟
    	
     GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8 | GPIO_Pin_9 |GPIO_Pin_10|GPIO_Pin_11 ;				 //LED0-->PB.5 端口配置
     GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; 		 //推挽输出
     GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;		 //IO口速度为50MHz
     GPIO_Init(GPIOC, &GPIO_InitStructure);					 //根据设定参数初始化GPIOB.5
    	
     GPIO_InitStructure1.GPIO_Pin = GPIO_Pin_12|GPIO_Pin_11 ;				 //LED0-->PB.5 端口配置
     GPIO_InitStructure1.GPIO_Mode = GPIO_Mode_Out_PP; 		 //推挽输出
     GPIO_InitStructure1.GPIO_Speed = GPIO_Speed_50MHz;		 //IO口速度为50MHz
     GPIO_Init(GPIOD, &GPIO_InitStructure1);					 //根据设定参数初始化GPIOB.5
    
     GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8 | GPIO_Pin_9 |GPIO_Pin_10|GPIO_Pin_11 ;	    		 //端口配置, 推挽输出
    	
     GPIO_InitStructure1.GPIO_Pin = GPIO_Pin_12|GPIO_Pin_11 ;	    		 //端口配置, 推挽输出
     GPIO_Init(GPIOD, &GPIO_InitStructure1);	  				 //推挽输出 ,IO口速度为50MHz
    	
     GPIO_Init(GPIOC, &GPIO_InitStructure);	  				 //推挽输出 ,IO口速度为50MHz
     //GPIO_SetBits(GPIOC,GPIO_Pin_0 | GPIO_Pin_1 |GPIO_Pin_2); 						 //PE.5 输出高 
    	
    
    }
    
    

    12. Motor

    void motor_gpio()
    {
      RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE); 
    	GPIO_InitTypeDef GPIO_InitStructure;
      GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;//推挽输出
    	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_14 | GPIO_Pin_15;
    	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    	GPIO_Init(GPIOB,&GPIO_InitStructure);
    	/*初始为低电平*/
    	GPIO_ResetBits(GPIOB,GPIO_Pin_14 | GPIO_Pin_15);
    }
    
    void TIM3_PWM_Init(u16 per,u16 psc)
    {
    	/*使能TIM4时钟*/
    	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);
    	
    	/*使能GPIO*/
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
    	
    	/*使能AFIO*/
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
    	
    	/*配置GPIO*/
    	GPIO_InitTypeDef GPIO_InitStructure;
    	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;//复用
    	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
    	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    	GPIO_Init(GPIOA,&GPIO_InitStructure);
    	
    //	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;//复用
    //	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1;
    //	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    //	GPIO_Init(GPIOB,&GPIO_InitStructure);
    	
    	/*设置重映射*/
    	//GPIO_PinRemapConfig(GPIO_PartialRemap_TIM3,ENABLE);//部分重映射	
    	
    	/*初始化定时器参数*/
    	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
    	TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;//选择时钟分频为1分频
    	TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;//选择计数模式为向上计数
    	TIM_TimeBaseInitStructure.TIM_Period = per;//配置周期(ARR自动重装器的值)
    	TIM_TimeBaseInitStructure.TIM_Prescaler = psc;//配置PSC预分频器的值
    	//TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;//重复计数器的值,高级计数器才需配置
    	TIM_TimeBaseInit(TIM3,&TIM_TimeBaseInitStructure);
    	//TIM_ClearFlag(TIM4,TIM_FLAG_Update);//先清除标志位,避免刚初始化就进入中断
    	
    	/*初始化PWM参数*/
    	TIM_OCInitTypeDef TIM_OCInitStructure;
    	TIM_OCInitStructure.TIM_Pulse = 0;
    	TIM_OCInitStructure.TIM_OCIdleState = TIM_OCIdleState_Reset;   //选择空闲状态下的非工作状态 低电平
    	TIM_OCInitStructure.TIM_OCNIdleState = TIM_OCNIdleState_Set;  //选择互补空闲状态下的非工作状态 低电平
    	TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;//选择PWM1模式
    	TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;//输出极性:高电平有效
    	TIM_OCInitStructure.TIM_OCNPolarity = TIM_OCNPolarity_Low;
    	TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;  //输出比较使能
    	TIM_OCInitStructure.TIM_OutputNState = TIM_OutputNState_Enable;  //互补输出比较使能
    	TIM_OC1Init(TIM3,&TIM_OCInitStructure);
    //	TIM_OC2Init(TIM3,&TIM_OCInitStructure);
    //	TIM_OC3Init(TIM3,&TIM_OCInitStructure);
    //	TIM_OC4Init(TIM3,&TIM_OCInitStructure);
    	
    	/*使能TIMX在CCRX上的预装载寄存器*/
    	TIM_OC1PreloadConfig(TIM3,TIM_OCPreload_Enable);
    //	TIM_OC2PreloadConfig(TIM3,TIM_OCPreload_Enable);
    //	TIM_OC3PreloadConfig(TIM3,TIM_OCPreload_Enable);
    //	TIM_OC4PreloadConfig(TIM3,TIM_OCPreload_Enable);
    	
    	TIM_CtrlPWMOutputs(TIM3,ENABLE);
    	
    	/*使能TIMX在ARR上的预装载寄存器允许位*/
    	//TIM_ARRPreloadConfig(TIM4,ENABLE);
    	
    	/*开启定时器*/
    	TIM_Cmd(TIM3,ENABLE);
    }	
    
    

    13. Flash

    void STMFLASH_Write(u32 WriteAddr,u16 *pBuffer,u16 NumToWrite)	
    {
    	u32 secpos;	   //扇区地址
    	u16 secoff;	   //扇区内偏移地址(16位字计算)
    	u16 secremain; //扇区内剩余地址(16位字计算)	   
     	u16 i;    
    	u32 offaddr;   //去掉0X08000000后的地址
    	if(WriteAddr<STM32_FLASH_BASE||(WriteAddr>=(STM32_FLASH_BASE+1024*STM32_FLASH_SIZE)))return;//非法地址
    	FLASH_Unlock();						//解锁
    	offaddr=WriteAddr-STM32_FLASH_BASE;		//实际偏移地址.
    	secpos=offaddr/STM_SECTOR_SIZE;			//扇区地址  0~127 for STM32F103RBT6
    	secoff=(offaddr%STM_SECTOR_SIZE)/2;		//在扇区内的偏移(2个字节为基本单位.)
    	secremain=STM_SECTOR_SIZE/2-secoff;		//扇区剩余空间大小   
    	if(NumToWrite<=secremain)secremain=NumToWrite;//不大于该扇区范围
    	while(1) 
    	{	
    		STMFLASH_Read(secpos*STM_SECTOR_SIZE+STM32_FLASH_BASE,STMFLASH_BUF,STM_SECTOR_SIZE/2);//读出整个扇区的内容
    		for(i=0;i<secremain;i++)//校验数据
    		{
    			if(STMFLASH_BUF[secoff+i]!=0XFFFF)break;//需要擦除  	  
    		}
    		if(i<secremain)//需要擦除
    		{
    			FLASH_ErasePage(secpos*STM_SECTOR_SIZE+STM32_FLASH_BASE);//擦除这个扇区
    			for(i=0;i<secremain;i++)//复制
    			{
    				STMFLASH_BUF[i+secoff]=pBuffer[i];	  
    			}
    			STMFLASH_Write_NoCheck(secpos*STM_SECTOR_SIZE+STM32_FLASH_BASE,STMFLASH_BUF,STM_SECTOR_SIZE/2);//写入整个扇区  
    		}else STMFLASH_Write_NoCheck(WriteAddr,pBuffer,secremain);//写已经擦除了的,直接写入扇区剩余区间. 				   
    		if(NumToWrite==secremain)break;//写入结束了
    		else//写入未结束
    		{
    			secpos++;				//扇区地址增1
    			secoff=0;				//偏移位置为0 	 
    		   	pBuffer+=secremain;  	//指针偏移
    			WriteAddr+=(secremain*2);	//写地址偏移	   
    		   	NumToWrite-=secremain;	//字节(16位)数递减
    			if(NumToWrite>(STM_SECTOR_SIZE/2))secremain=STM_SECTOR_SIZE/2;//下一个扇区还是写不完
    			else secremain=NumToWrite;//下一个扇区可以写完了
    		}	 
    	};	
    	FLASH_Lock();//上锁
    }
    


    四、服务器端

    1. TCP

    	//创建一个socket文件,也就是打开一个网络通讯端口,类型是IPV4(AF_INET)+TCP(SOCK_STREAM)
    	int serv_sock = socket(AF_INET, SOCK_STREAM,0);
    	  
    	// 设置SO_REUSEADDR选项
        int optval = 1;
        if (setsockopt(serv_sock, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)) < 0) {
            perror("setsockopt() failed");
            exit(EXIT_FAILURE);
        }  
    	  
    	//绑定服务器ip和端口到这个socket
    	struct sockaddr_in serv_addr;//这里因为是ipv4,使用的结构体是ipv4的地址类型sockaddr_in
    	memset(&serv_addr, 0, sizeof(serv_addr));//先清空一下初始的值,写上地址和端口号,可以用bzero(&serv_addr, sizeof(serv_addr));
    	serv_addr.sin_family = AF_INET;
    	serv_addr.sin_addr.s_addr = inet_addr("192.168.124.32");//本机ip环回地址,这里还可以使用inet_pton函数进行地址转换
    	serv_addr.sin_port = htons(8117);//随意选了一个端口8899
        bind(serv_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
    	  
        //将socket设置为监听状态
        if(listen(serv_sock,128)==-1){//设置最大连接数为128
    		printf("listen socket error: %s(errno: %d)\n",strerror(errno),errno);
    		exit(0);
    	}else{
    		printf("Waiting for client's request...\n");
    	}
    //接收客户端的请求连接后,返回一个新的socket(clnt_sock)用于和对应的客户端进行通信
        struct sockaddr_in clnt_addr;//作为一个传出参数
        socklen_t clnt_addr_size = sizeof(clnt_addr);//作为一个传入+传出参数
        int clnt_sock = accept(serv_sock, (struct sockaddr*)&clnt_addr, &clnt_addr_size);
      	if(clnt_sock!=-1)  {
      		printf("Connect success!\n\n");
    	  }
    

    2. MYSQL

    	MYSQL *sql;     //创造一个MYSQL句柄
    	sql = mysql_init(NULL);  //初始化MYSQL句柄
    	int res; 
    	int ret;
    	if(!sql)       //若初始化句柄失败
    	{	
    		printf("connect mysql failed");
    		return -1;
    	}
        /*尝试与mysql数据库连接*/
    	if(!mysql_real_connect(sql,"localhost","root","123456","test",0,NULL,0))
    	{
    		printf("failed to coonect mysql:%s\n",mysql_error(sql));
    	}
     	printf("connect success...........\n");
    
    	char insertDataStr[200] ;
    	sprintf(insertDataStr,"INSERT INTO water(NAME,ID,INSERTTIME,SUM,STATUS) VALUES ('%s',%s,now(),%s,'%s');",rc1,rc2,rc4,rc5);
    	ret = mysql_query(sql,insertDataStr);
    	if(ret >= 0)
    	{
    	    printf("insert ISMILELI info is success!\n");
    	   	student_get_all(sql);
    	}
        else
        {
    	     printf("insert ISMILELI info is failurel!\n");
        }
    mysql_close(sql);  //关闭连接,释放对象的内存空间
    mysql_library_end();    //如果不调用该函数,可能造成内存泄露 
    return 0;
    

    物联沃分享整理
    物联沃-IOTWORD物联网 » 2022湖南省物联网应用创新竞赛:基于物联网的校园直饮水管理系统

    发表评论