文章目录
- 前言
- 1、要求:
- 2、系统框图
- 2.1系统总体框图
- 2.2、stm32通过AHT20采集温湿度框图:
- 2.3、stm32通过modbus协议与上位机通信框图:
- 3、ModBus协议
- 1、协议概述
- 2、Modbus主/从协议原理
- 3、通用Modbus帧结构---协议数据单元(PDU)
- 4、两种Modbus串行传输模式
- 5、ModbusTCP通信结构
- 6、系统涉及到的通讯协议指令
- 7、部分协议功能码
- 一、MCU数据采集与上位机通信
- 1、温湿度采集(I2C协议)
- 2、上位机通信
- 二、传感器仿真数据远程采集
- 1.TCP套接字和modbus协议
- 2.UDP套接字和modbus协议
- 三、思考题
- 四、总结
前言
1、要求:
1.MCU数据采集与上位机通信:
采用STM32F103开发板+AHT20 模拟多监测点的温湿度数据采集。用串口/485总线完成采集终端与上位机进行数据传输。上位机可以运行Linux系统的树莓派3B+ (树莓派具备串口和有线、无线网络)。如果没有树莓派实物,可用PC机模拟,采用标准C语言或者python编程实现。
2.传感器仿真数据远程采集:
研究内容包括:RS485总线与ModBus通讯协议、数据库技术、网络通信编程开发。
上位机通过UDP/TCP套接字和Modbus应用层协议,完成至少两类数据(比如温湿度、应力)的远程周期性(比如30秒)实时采集和保存。采集数据写入Mysql数据库,Mysql可以安装在本地PC机上Win10或Ubuntu系统上。一条采集数据记录至少包括:时间戳、采集点编号、采集数据类型、采集数据信息。软件开发采用编程软件不作限定。
3.思考:
针对实际工程应用中的多传感器、多协议、多组网方式的复杂多变的技术要求,如果采用FPGA开发板编程的灵活定制特点来设计实现不同各种接口协议,是否存在合理性?有什么优势和弊端? 请简要对比分析。
注意:主要是方案的设计与编程调试。
2、系统框图
2.1系统总体框图
2.2、stm32通过AHT20采集温湿度框图:
这个模块我们计划采用的是AHT20温湿度传感器,通过I2C与stm32进行通信,stm32再通过ModBus协议进行上传通信。
2.3、stm32通过modbus协议与上位机通信框图:
上位机则利用 Modbus 协议发送指令,并接受从机的数据,同时要求上位机将接收到的数据储存在数据库中,并能从给定的 dmo-monitor.igong.com 读取已经部署的桥梁传感器的数据。
3、ModBus协议
由上面的系统框图可以看出,我们对modbus协议使用较多,我们这里阐述一下modbus协议的内容。
1、协议概述
1、Modbus协议是一种串行通信协议,是Modicon公司(现在的施耐德电气Schneider Electric)于1979年为使用可编程逻辑控制器(PLC)通信而发表的。Modbus协议是应用层协议,已经成为工业领域通信协议的业界标准,是工业电子设备之间常用的连接方式。
2、Modbus是一个master/slave架构的协议,有一个节点是master节点,其他使用Modbus协议参与通信的节点是slave节点,每一个slave设备都有一个唯一的地址。只有被指定为master节点的节点可以启动一个命令。所有的Modbus数据帧包含了校验码,保证传输的正确性。基本的ModBus命令能指令一个slave设备改变它的寄存器的某个值,控制或者读取一个I/O端口,以及指挥设备回送一个或者多个其寄存器中的数据。
2、Modbus主/从协议原理
Modbus串行链路协议是一个主-从协议。在同一时间,只能将一个主站连接到总线,将一个或多个从站(最大数量为247)连接到相同的串行总线。Modbus 通讯总是由主站发起,当从站没有收到来自主站的请求时,将不会发送数据。主站同时只能启动一个Modbus事务处理,从站之间不能相互通信。
3、通用Modbus帧结构—协议数据单元(PDU)
Modbus协议定义了一个与基础通信层无关的简单协议数据单元(PDU),特定总线或网络上的Modbus协议映射能够在应用数据单元(ADU)上引入一些附加域。
4、两种Modbus串行传输模式
RTU模式:每个8 Bit字节包含两个4 Bit的十六进制字符,其优点是在同样的波特率下,可比ASCII方式传送更多的数据,但是每个信息必须以连续的数据流传输。
ASCII模式:信息中的每个8 Bit字节需2个ASCII字符,其优点是准许字符的传输间隔达到1s 而不产生错误;
5、ModbusTCP通信结构
Modbus TCP/IP的通信设备:连接至TCP/IP网络的 Modbus TCP/IP客户机和服务器设备。
互连设备如:在TCP/IP网络和串行链路子网之间互连的网桥、路由器或网关等设备。
6、系统涉及到的通讯协议指令
7、部分协议功能码
一、MCU数据采集与上位机通信
1、温湿度采集(I2C协议)
温湿度读取模块这里我们采用的传感器是AHT20、I2C协议。I2C 通讯协议(Inter-Integrated Circuit)是由 Phiilps 公司开发的,由于它引脚少,硬件实现简单,可扩展性强,不需要 USART、CAN 等通讯协议的外部收发设备,现在被广泛地
使用在系统内多个集成电路(IC)间的通讯。
1、上电后等待一段时间待设备正常工作后再读取温湿度:
MX_GPIO_Init();
MX_I2C1_Init();
MX_USART1_UART_Init();
uint32_t CT_data[2]={0,0};
volatile int c1,t1;
rt_thread_delay(50);
AHT20_Init();
rt_thread_delay(2500);
2、CRC校验后读取温湿度数据,随后延时5分钟
AHT20_Read_CTdata_crc(CT_data); //crc校验后,读取AHT20的温度和湿度数据
c1 = CT_data[0]*100*10/1024/1024; //计算得到湿度值c1(放大了10倍)
t1 = CT_data[1]*200*10/1024/1024-500;//计算得到温度值t1(放大了10倍)
printf("湿度:%d%s",c1/10,"%");
printf("温度:%d%s",t1/10,"℃");
printf("\r\n");
rt_thread_mdelay(300000);//线程睡眠大约5分钟,挂起执行其他操作
2、上位机通信
这里我们的要求是模拟多监测点的温湿度数据的采集,这里我们可以采用移植RT-thread Nano的多任务处理框架,以防其他传感器的正常工作。
main函数中对Modbus 协议进行使能,并初始化 RT-Thread 进程:
eMBInit( MB_RTU, 0x01, 1, 115200, MB_PAR_NONE);//初始化modbus,走modbusRTU,从站地址为0x01,端口为1。
eMBEnable();//使能modbus
MX_RT_Thread_Init(); //初始化 RT-Thread 进程
在配置完AHT20的代码和modbus驱动文件后引入相关头文件:
#include "rtthread.h" // RT-Thread 头文件
#include "mb.h" // Modbus 头文件
#include "mbport.h" // Modbus 端口头文件
#include "AHT20.h" // AHT20传感器驱动头文件
extern void MX_RT_Thread_Init(void); // RT-Thread 初始化函数,初始化并执行各种进程
extern void MX_RT_Thread_Process(void); // RT-Thread 主进程
开启多线程,主任务是modbus监听,子任务是对程序进行测试:
#include "rtthread.h"
#include "main.h"
#include "i2c.h"
#include "usart.h"
#include "gpio.h"
#include "stdio.h"
#include "AHT20.h"
#include "mb.h"
#include "mbport.h"
struct rt_thread led1_thread;
rt_uint8_t rt_led1_thread_stack[128];
void led1_task_entry(void *parameter);
//初始化线程函数
void MX_RT_Thread_Init(void)
{
//初始化LED1线程
rt_thread_init(&led1_thread,"led1",led1_task_entry,RT_NULL,&rt_led1_thread_stack[0],sizeof(rt_led1_thread_stack),3,20);
//开启线程调度
rt_thread_startup(&led1_thread);
}
//主任务
void MX_RT_Thread_Process(void)
{
(void)eMBPoll(); //启动modbus监听
}
//LED1任务
void led1_task_entry(void *parameter)
{
while(1)
{
HAL_GPIO_WritePin(GPIOC,GPIO_PIN_13, GPIO_PIN_RESET);
rt_thread_delay(500);
HAL_GPIO_WritePin(GPIOC,GPIO_PIN_13, GPIO_PIN_SET);
rt_thread_delay(500);
}
}
在 eMBRegInputCB 函数中修改功能码为04用于读取输入寄存器:
eMBErrorCode eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs )
{
eMBErrorCode eStatus = MB_ENOERR;
int iRegIndex;
int i;
InputBuff[0] = 0x11;
InputBuff[1] = 0x22;
InputBuff[2] = 0x33;
InputBuff[3] = 0x44;
if( ( usAddress >= REG_INPUT_START ) && ( usAddress + usNRegs <= REG_INPUT_START + REG_INPUT_NREGS ) )
{
iRegIndex = ( int )( usAddress - usRegInputStart );
for(i=0;i<usNRegs;i++)
{
*pucRegBuffer=InputBuff[i+usAddress-1]>>8;
pucRegBuffer++;
*pucRegBuffer=InputBuff[i+usAddress-1]&0xff;
pucRegBuffer++;
}
}
else
{
eStatus = MB_ENOREG;
}
return eStatus;
}
在输出函数eMBRegInputCB中将传回的温湿度数据存到InputBuff[] 数组中进行输出:
eMBErrorCode eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs )
{
eMBErrorCode eStatus = MB_ENOERR;
int iRegIndex;
int i;
HAL_GPIO_WritePin(GPIOC,GPIO_PIN_13,GPIO_PIN_RESET); // 使用 PC13 引脚上的板载小灯泡进行测试——小灯泡亮
AHT20_Read_CTdata_crc(CT_data); //经过CRC校验,读取AHT20的温度和湿度数据 推荐每隔大于1S读一次
c1 = CT_data[0]*1000/1024/1024; //计算得到湿度值c1(放大了10倍)
t1 = CT_data[1]*2000/1024/1024-500;//计算得到温度值t1(放大了10倍)
t2 = t1/10 + (t1/10)%10; //温度的整数部分
t3 = t1%10; //温度的小数部分
c2 = c2/10 + (c1/10)%10; // 湿度的整数部分
c3 = c3%10; //湿度的小数部分
InputBuff[0] = t2;
InputBuff[1] = t3;
InputBuff[2] = c2;
InputBuff[3] = c3;
if( ( usAddress >= REG_INPUT_START ) && ( usAddress + usNRegs <= REG_INPUT_START + REG_INPUT_NREGS ) )
{
iRegIndex = ( int )( usAddress - usRegInputStart );
for(i=0;i<usNRegs;i++)
{
*pucRegBuffer=InputBuff[i+usAddress-1]>>8;
pucRegBuffer++;
*pucRegBuffer=InputBuff[i+usAddress-1]&0xff;
pucRegBuffer++;
}
}
else
{
eStatus = MB_ENOREG;
}
return eStatus;
}
由于手上没有AHT20模块无法进行效果的演示,这里只是对流程进行了大致阐述和部分重要模块代码的展示。
二、传感器仿真数据远程采集
1.TCP套接字和modbus协议
在通过TCP协议与远程数据服务建立联系的内容中主要是完成了对桥梁应力以及温度的采集。
crc校验:
def crc16(string):
#data = bytes.fromhex(string)
data=string
crc = 0xFFFF
for pos in data:
crc ^= pos
for i in range(8):
if ((crc & 1) != 0):
crc >>= 1
crc ^= 0xA001
else:
crc >>= 1
return hex(((crc & 0xff) << 8) + (crc >> 8))
数据库连接:
def MySQLConnect():
connection = pymysql.connect(
host='localhost', # IP,MySQL数据库服务器IP地址
port=3306, # 端口,默认3306,可以不输入
user='root', # 数据库用户名
password='密码', # 数据库登录密码
database='sensor', # 要连接的数据库
charset='utf8' # 字符集,注意不是'utf-8'
)
return connection
将数据插入到数据库:
def AddData(num,yb,wd,time):
# 连接数据库
conn = MySQLConnect()
# 使用cursor()方法创建一个游标对象cursor
cursor = conn.cursor()
# 插入数据库
sql = "INSERT INTO strain_sensor(id ,mic, strain_temp, time) VALUES (%s,%s,%s,%s); "
cursor.execute(sql, [num,yb, wd, time])
# 提交事务
conn.commit()
# 关闭游标
cursor.close()
# 关闭数据库连接
conn.close()
获取数据:
def getStain(cmd,num,time):
#print(cmd)
#print(num)
cmd = bytes.fromhex(cmd)
crc = crc16(cmd)
crc = bytes.fromhex(crc[2:])
cmd = cmd + crc
#print(cmd)
#发送对应的指令
tcp.send(cmd)
try:
data = tcp.recv(8192)
except socket.timeout:
print("超时")
sys.exit(1)
crc = data[-2:]
crc1 = crc16(data[:-2])
crc1 = crc1[2:]
if len(crc1) == 3:
crc1 = '0' + crc1
crc1 = bytes.fromhex(crc1)
if crc != crc1:
print("CRC16校验失败!")
sys.exit(2)
yb, wd = struct.unpack('>ii', data[4:12])
yb = yb / 100.0
wd = wd / 100.0
print("应变:", yb, "温度:", wd)
print(time)
yb = str(yb)
wd = str(wd)
AddData(num, yb, wd, time)
建立TCP连接:
效果:
3号传感器采集应变力和温度信息:
数据库:
2.UDP套接字和modbus协议
在使用UDP协议向远程桥梁监测数据服务系统获取数据的内容,我们实现了对温度传感器的采集以及对静力水准仪的信息采集。
CRC校验:
def crc16(string):
#data = bytes.fromhex(string)
data=string
crc = 0xFFFF
for pos in data:
crc ^= pos
for i in range(8):
if ((crc & 1) != 0):
crc >>= 1
crc ^= 0xA001
else:
crc >>= 1
return hex(((crc & 0xff) << 8) + (crc >> 8))
数据库连接:
def MySQLConnect():
connection = pymysql.connect(
host='localhost', # IP,MySQL数据库服务器IP地址
port=3306, # 端口,默认3306,可以不输入
user='root', # 数据库用户名
password='password', # 数据库登录密码
database='sensor', # 要连接的数据库
charset='utf8' # 字符集,注意不是'utf-8'
)
将获取到的温度湿度时间戳插入到数据库中:
def AddData1(wd,sd,time):
# 连接数据库
conn = MySQLConnect()
# 使用cursor()方法创建一个游标对象cursor
cursor = conn.cursor()
# 插入数据库
sql = "INSERT INTO temp_hum_sensor(temp, hum, time) VALUES (%s,%s,%s); "
cursor.execute(sql, [wd, sd, time])
# 提交事务
conn.commit()
# 关闭游标
cursor.close()
# 关闭数据库连接
conn.close()
获取到的传感器id、挠度、时间戳存到数据库中:
def AddData2(id,water_level,time):
# 连接数据库
conn = MySQLConnect()
# 使用cursor()方法创建一个游标对象cursor
cursor = conn.cursor()
# 插入数据库
sql = "INSERT INTO static_level(id, water_level, time) VALUES (%s,%s,%s); "
cursor.execute(sql, [id, water_level, time])
# 提交事务
conn.commit()
# 关闭游标
cursor.close()
# 关闭数据库连接
conn.close()
温湿度采集过程(modbus):
def getDataTemp(cmd):
#flag标志采集的次数
flag=0
last = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
print(last)
last1 = time.time()
cmd = bytes.fromhex(cmd)
#print(cmd)
crc = crc16(cmd)
crc = bytes.fromhex(crc[2:])
#得到发送的指令(modbus协议定义内容+校验)
cmd = cmd + crc
udp.sendto(cmd, ('demo-monitor.igong.com', 8001))
try:
data, addr = udp.recvfrom(8192)
except socket.timeout:
print("超时")
sys.exit(1)
crc = data[-2:]
crc1 = crc16(data[:-2])
crc1 = crc1[2:]
if (len(crc1) == 3):
crc1 = '0' + crc1
crc1 = bytes.fromhex(crc1)
# print(crc1)
if crc != crc1:
print("CRC16校验失败!")
sys.exit(2)
# 解析数据
wd, sd = struct.unpack('>ii', data[4:12])
wd = wd / 100.
print("温度:", wd, "湿度:", sd)
AddData1(wd, sd, last)
flag=flag+1
while True:
now= time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
#print(s)
now1=time.time()
#每隔5s获取一次数据
if(now1-last1>5):
udp.sendto(cmd, ('demo-monitor.igong.com', 8001))
try:
data, addr = udp.recvfrom(8192)
except socket.timeout:
print("超时")
sys.exit(1)
crc = data[-2:]
crc2=bytes.hex(crc)
#print(crc2)
crc1 = crc16(data[:-2])
crc1=crc1[2:]
if(len(crc1)==3):
crc1='0'+crc1
#print(crc1)
crc1=bytes.fromhex(crc1)
#print(crc1)
if crc != crc1:
print("CRC16校验失败!")
sys.exit(2)
#解析数据
wd, sd = struct.unpack('>ii', data[4:12])
wd = wd / 100.0
#当前时间
print(now)
#获取得到的数据
print("温度:", wd, "湿度:", sd)
last=now
last1=now1
wd=str(wd)
sd=str(sd)
AddData1(wd,sd,now)
flag = flag + 1
if flag >= 5:
str1 = input("请选择是否继续采集(y表示继续,n表示退出):")
if str1 == 'y':
flag = 0
continue
else:
break
选择传感器:
def getDataStaticLevel(cmd):
id=cmd[0:2]
#print(id)
if id=='02':
#print("2号")
id='00'+id
getData(id,cmd)
elif id=='03':
#print("3号")
id = '00' + id
getData(id,cmd)
elif id=='04':
#print("4号")
id = '00' + id
getData(id,cmd)
elif id=='05':
#print("5号")
id = '00' + id
getData(id,cmd)
与远程数据建立连接:
效果:
温湿度模块:
数据库:
挠度:
数据库:
三、思考题
针对实际工程应用中的多传感器、多协议、多组网方式的复杂多变的技术要求,如果采用FPGA开发板编程的灵活定制特点来设计实现不同各种接口协议,是否存在合理性?有什么优势和弊端? 请简要对比分析。
我认为是合理的,因为fpga本身编程的灵活性和数据处理的能力就足可以支撑他实现多种协议。优势:现在的中端FPGA已经在实验不同的接口协议,再者就是能够得到FPGA处理信息的能力。弊端;建立一个完整的协议栈付出会比较多,需要经过多次实验,就算通过了在信息安全方面的隐患也是存在的。
四、总结
此次方案设计的协议最主要的就是modbus了,之前的作业也有使用过,但在这一次的设计过程中就有加深了理解和熟悉。由于硬件资源的不支持,第一部分的内容主要就是根据前面实现过的作业进行一个阐述展示复习即可。在第二部分的对远程检测数据服务的连接中使用到了UDP\TCP两种连接方式,但是最主要的还是代码中实现modbus通信的实现流程的理解最为重要。所以此次设计收获满满的还是对modbus的学习!