STM32+UCOSII+ESP8266
参考链接:https://blog.csdn.net/qq_38410730/article/details/86538288
参考资料:朱有鹏老师网络编程部分教程、正点原子官方资料、百度查询
因为很早之前就一直想做一套完整的物联网系统,所以趁着这段时间的一个设计,就一边学一边尝试着做一套。系统数据采集部分采用大众化的STM32作为主控,再加上一些具有代表性的传感器;数据传输部分即联网媒介选择ESP8266串口WIFI模块;数据处理部分采用LAMP环境的云服务器处理,再加上前端网页共同组成一套完整的IOT系统(功能相对简单)。
数据采集部分。室内环境几个具有代表性的参数,主要是甲醛、可燃性气体、PM颗粒、温湿度等,故本系统分别用到了ZE08-CH2O甲醛模组、MQ2、PM、DHT11几个传感器。主控采用STM32F103C8T6,这一块因为网上资料非常多,全部开源,所以就不不一一讲述了。这部分主要讲一下我在移植UCOSII的时候,遇到的问题。因为系统涉及到很多传感器,需要进行大量任务,如果一条主任务,肯定是满足不了需求的,所以移植了自己相对较熟悉的UCOSII操作系统。参考教程即为正点原子官方提供的UCOSII移植资料,移植完了后,遇到了一些小问题,比如浮点数8字节对齐( __align(8) 和任务栈大小的设置,这些都可能会影响到某个任务出现卡死的现象,因为这些问题以及解决方法移植资料上面都会提及,就不一一赘述,此处说明是希望大家注意细节。
数据传输部分。数据上传到互联网则需要联网媒介作为中转,联网的方式有很多,无线、有线,无线又分很多种:无线串口、zigbee、WIFI、蓝牙、GPRS等等,基于多方面考虑,本系统采用ESP8266串口WIFI模块作为联网媒介。关于ESP8266的资料也非常多,原理、代码都讲得非常清楚,因为我采用的是正点原子开发板配套的WIFI模块,所以直接移植了自带的例程,采用的是TCP透传模式,采集终端作为客户端,不断向服务器上传数据。因为MiniStm32例程是自带TFTLCD显示屏的例程,加上WIFI模块,配合按键,可以手动输入模式、IP等等信息,当然了例程运行一切正常。但是因为我暂时不需要显示屏,所以就取消显示屏部分,只需要wifi部分,但是这时候出错了,始终连接不了服务器。此处卡死:
开始以为是服务器的问题,结果调试了很久终于发现了问题,因为例程是自带的配置串口2作为WIFI传输部分,我换了一个串口3马上就好了,当然了并不是串口2配置不对,而是因为例程是开启了DMA传输,即串口2采用DMA传输,可能是因为取消掉显示屏的原因,导致DMA配置部分有一些遗漏,所以DMA传输失败从而导致串口2通信失败,取消DMA传输立即OK。希望大家遇到了类似的问题引起注意。后面调试甲醛传感器的时候,因为其支持两种通信协议,分别是串口和ADC,刚开始采用串口3,后面发现一直失败,而且显示屏字体颜色变淡,读不到数据导致系统卡死,后面发现是因为TFTLCD显示屏和串口3的引脚复用了,PB10,PB11都被显示屏数据口占用,此时想到了端口复用功能,随后参考数据手册发现串口3的复用重映射引脚也同样被占用了,马上看了串口4、5都是一样的结果,没办法,只好改成ADC,采集模拟量,最后成功。
后台处理部分。本系统租用了一台腾讯云服务器,环境为LAMP(Linux+Apache+Mysql+Php)。因为服务器需要不断接收来自采集终端部分的数据,所以接收服务器采用了socket监听端口的方式来监听接收数据。参考:朱有鹏老师网络编程部分教程。代码附在后面。接收到数据后,需要实时存储到数据库,以更新数据,保证数据的实时性。
前端网页部分。采用CSS+DIV布局,PHP和服务器交互,目前实现了一部分,能够实时显示采集到的数据,但是目前采用的是比较古老的刷新页面的方法来刷新数据,后面将会加上AJAX局部刷新技术来更新动态数据,并且附上数据动态曲线走势图。
当前效果还比较简陋,暂不贴图,后面完善了再附上详细代码和效果图。望各位见谅...
附代码:
数据传输部分(esp8266):
/***********************************************************
** 函 数 名: wifi_sta_trans_config
** 功能描述: ATK-ESP8266模块TCP透传模式配置
** 输 入: 无
** 输 出: 无
** 返 回 值: 无
** 日 期: 2019/4/23
************************************************************/
void wifi_sta_trans_config(void)
{
//一、设置工作模式(需重启生效) 1:station模式 2:AP模式 3:兼容 AP+station模式
atk_8266_send_cmd("AT+CWMODE=1","OK",50);
//二、Wifi模块重启生效
atk_8266_send_cmd("AT+RST","ready",20);
delay_ms(1000); //延时3S等待重启成功
delay_ms(1000);
delay_ms(1000);
delay_ms(1000);
//三、模块连接上自己的路由
while(atk_8266_send_cmd("AT+CWJAP=\"MERCURY\",\"12345678\"","WIFI GOT IP",600));
//四、是否启用多路连接 0:单路连接模式 1:多路连接模式
atk_8266_send_cmd("AT+CIPMUX=0","OK",20);
//五、建立TCP连接 这四项分别代表了:要连接的ID号0~4 连接类型 远程服务器IP地址 远程服务器端口号
while(atk_8266_send_cmd("AT+CIPSTART=\"TCP\",\"192.168.x.x\",5000","CONNECT",200));
//六、开启透传模式 0:表示关闭 1:表示开启透传
atk_8266_send_cmd("AT+CIPMODE=1","OK",200);
//七、透传模式下开始发送数据 这个指令之后就可以直接发数据了
atk_8266_send_cmd("AT+CIPSEND","OK",50);
}
/***********************************************************
** 函 数 名: atk_8266_send_cmd
** 功能描述: 向ATK-ESP8266发送命令
** 输 入: cmd:发送的命令字符串 ack:期待的应答结果,如果为空,则表示不需要等待应答 waittime:等待时间(单位:10ms)
** 输 出: 无
** 返 回 值: 0,发送成功(得到了期待的应答结果) 1,发送失败
** 日 期: 2019/3/23
************************************************************/
u8 atk_8266_send_cmd(u8 *cmd,u8 *ack,u16 waittime)
{
u8 res=0;
USART2_RX_STA=0;
u2_printf("%s\r\n",cmd); //发送命令 (向WiFi模块发送)
if(ack&&waittime) //需要等待应答
{
while(--waittime) //等待倒计时
{
delay_ms(10);
if(USART2_RX_STA&0X8000)//接收到期待的应答结果
{
if(atk_8266_check_cmd(ack))
{
printf("ack:%s\r\n",(u8*)ack);
break;//得到有效数据
}
USART2_RX_STA=0;
}
}
if(waittime==0)res=1;
}
return res;
}
/***********************************************************
** 函 数 名: atk_8266_send_data
** 功能描述: 向ATK-ESP8266发送指定数据
** 输 入: data:发送的数据(不需要添加回车了) ack:期待的应答结果,如果为空,则表示不需要等待应答 waittime:等待时间(单位:10ms)
** 输 出: 无
** 返 回 值: 0,发送成功(得到了期待的应答结果)
** 日 期: 2019/3/23
************************************************************/
u8 atk_8266_send_data(u8 *data,u8 *ack,u16 waittime)
{
u8 res=0;
USART2_RX_STA=0;
u2_printf("%s",data); //发送命令
if(ack&&waittime) //需要等待应答
{
while(--waittime) //等待倒计时
{
delay_ms(10);
if(USART2_RX_STA&0X8000) //接收到期待的应答结果
{
if(atk_8266_check_cmd(ack))break;//得到有效数据
USART2_RX_STA=0;
}
}
if(waittime==0)res=1;
}
return res;
}
数据接收部分:
<?php
$my_severIP = "x.x.x.x"; //服务器IP
$my_serverPORT = 5000; //服务器端口号
$my_databs_server = "localhost"; //数据库服务器
$my_databs_user = "root"; //数据库用户名
$my_databs_passwd = "xxxxxxxxx"; //数据库密码
$my_databs_name = "xxx"; //数据库名称
//连接数据库服务器
$con = mysql_connect($my_databs_server,$my_databs_user,$my_databs_passwd);//连接数据库
if (!$con)
{
die('can`t connect to database $my_databs_user: ' . mysql_error());
}
else
{
echo "connect success...";
}
//选择数据库
mysql_select_db($my_databs_name, $con);
//**********第1步:socket_create 打开一个网络文件描述符****************
$socket_fd = socket_create(AF_INET,SOCK_STREAM,SOL_TCP);
//**********第2步:socket_bind 绑定sokfd、服务端IP和端口号*************
if(socket_bind($socket_fd,$my_severIP,$my_serverPORT) == false) //
{
echo 'server bind fail:'.socket_strerror(socket_last_error());
}
//**********第3步:socket_listen 监听端口******************************
if(socket_listen($socket_fd,4)==false)
{
echo 'server listen fail:'.socket_strerror(socket_last_error());
}
//**********第4步:socket_accept 阻塞等待客户端连接服务器**************
$client_fd = socket_accept($socket_fd);
//echo 'clieid :'.$client_fd;
//**********第5步:建立连接之后,进行通信(服务端接收)*******************************
while(true)
{
$string = (int)socket_read($client_fd,1024);//PHP_BINARY_READ
//echo 'receive_data:'.$string.PHP_EOL; //PHP_EOL为php的换行预定义常量
//$string = "66";
//更新数据表信息
var_dump($string);
$result = mysql_query("update indoor set value = '{$string}' where paraname = 'temp' ");
echo "update indoor set value = '{$string}' where paraname = 'temp' ";
var_dump($result);
}
//关掉数据库
mysql_close($con);
?>
前端网页部分代码:
<html>
<head>
<!--<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />-->
<meta http-equiv="refresh" content="5" /><!--每10s刷新一次页面,后期采用ajax技术优化-->
<title>室内环境监测系统</title>
<style type="text/css">
body {background-color: white}
</style>
</head>
<body>
<br>
<center><font face = 楷体 color = "blue" size = 7>室内环境在线监测系统</font></center>
<br>
<br>
<hr align = center color = "black" width = 100% size = 2>
<?php
$my_databs_server = "localhost"; //数据库服务器
$my_databs_user = "root"; //数据库用户名
$my_databs_passwd = "xxxxxx"; //数据库密码
$my_databs_name = "xxx"; //数据库名称
//连接数据库服务器
$con = mysql_connect($my_databs_server,$my_databs_user,$my_databs_passwd);//连接数据库
if (!$con)
{
die('can`t connect to database $my_databs_user: ' . mysql_error());
}
//选择数据库
mysql_select_db($my_databs_name, $con);
//选择数据表查看
$result = mysql_query("SELECT * FROM indoor");
//以表格形式输出
echo "<table border='1'>
<tr>
<th>参数</th>
<th>值</th>
<th>走势曲线</th>
<th>单位</th>
</tr>";
//从数据表中读取数据
while($row = mysql_fetch_array($result))
{
//echo "<br />";
//echo $row['paraname'] . " " . $row['value']. " " . $row['unit'];
//echo "<br />";
echo "<tr>";
echo "<td align=center width='300' height='200'> ".$row['paraname']." </td>";
echo "<td align=center width='300' height='200'> ".$row['value']. " </td>";
echo "<td align=center width='1000' height='200'>   </td>";
echo "<td align=center width='300' height='200'> ".$row['unit']." </td>";
echo "</tr>";
}
echo "</table>";
//关闭数据库
mysql_close($con);
?>
<br>
<hr align = center color = "black" width = 100% size = 2>
<p><center><a href = "../index.html"; style = " font-family:楷体; text-decoration:none; color:black; cursor:pointer; font-size:25px; ">返回首页</a></center></p>
</body>
</html>
持续更新中。。。