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


物联网赛题

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;

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/Dustinthewine/article/details/129778909