基于物联网的校园直饮水管理系统(2022年湖南省物联网应用创新竞赛(技能赛))
1. 处理模块以Stm32开发板作为中心节点,外接多路传感器,分别采集实时数据,打印在LCD显示屏上。2. 服务器①可以选择Linux平台作为服务器,通过TCP将Stm32上采集到的数据发送至服务器上然后解析出数据,插入本地数据库。②可以选择用Qt开发 作服务器,并连接数据库,同样将Stm32传来的数据插入数据库中。3. Android或Web①可以用Java开发Android,通过查看服务器中的
物联网赛题
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;
更多推荐
所有评论(0)