项目基本情况:
远程对工业现场的设备进行开关控制和实时数据采集,基于modbus tcp协议,和webserver服务器,实现客户在网页进行获取设备的传感器数据和控制设备上的开关;
基本流程:网页通过web服务器对设备发送指令:如果是收集数据:因为设备通过modbus协议不断的将即时数据发送到服务器,当服务器获取到网页端的请求是获取传感器数据时就把设备的通过http协议发送给网页,客户就可以即时查看设备数据了,如果是控制设备,在网页上输入控制命令,服务器通过modbus协议格式对设备进行控制开关。
起源:
Modbus由Modicon公司于1979年开发,是一种工业现场总线协议标准。
Modbus通信协议具有多个变种,其中有支持串口,以太网多个版本,其中最著名的是Modbus RTU、Modbus ASCII和Modbus TCP三种;
分类:
1) Modbus RTU: 运行在串口上的协议,采用二进制表现形式以及紧凑型数据结构,通信效率高,应用广泛
2) Modbus ASCII: 运行在串口上的协议,采用ASCII码传输,并且利用特殊字符作为其字节的开始与结束标识,其传输效率要远远低于Modbus RTU协议,一般只有在通信数据量较小的情况下才考虑使用Modbus ASCII通信协议
3) Modbus TCP: 运行在以太网上的协议
为什么要使用modbus协议?
Modbus协议是现在国内工业领域应用最多的协议,不只PLC设备,各种终端设备,比如水控机、水表、电表、工业秤、各种采集设备
优势:免费、简单、容易使用
Modbus TCP特点:(本项目使用的是modbus tcp协议)
Modbus TCP:运行在以太网上的协议:
1) 采用主机和从机之间问答方式进行通信
2) Modbus TCP是应用层协议,基于传输层TCP协议实现
3) Modbus TCP端口号默认为502
任务:
编程实现采集传感器数据和控制硬件设备(传感器和硬件通过slave模拟)
传感器:2个,光线传感器、加速度传感器(x\y\z)
硬件设备:2个,led灯、蜂鸣器
要求:
1. 多任务编程:多线程、多进程
2. 循环1s采集一次数据,并将数据打印至终端
3. 同时从终端输入指令控制硬件设备
0 1 :led灯打开
0 0:led灯关闭
1 1:蜂鸣器开
1 0 : 蜂鸣器关
编程流程
1. 创建实例
modbus_new_tcp
2. 设置从机ID
modbus_set_slave
3. 和从机进行连接
modbus_connect
4. 寄存器进行操作
功能码对应函数
5. 关闭套接字
modbus_close
6. 释放实例
modbus_free
#include <stdio.h>
#include <stdlib.h>
#include <modbus-tcp.h>
#include <modbus.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <errno.h>
#include <string.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
void *handler(void *arg); //子线程
int create_shimd(char *buf); //创建共享内存
modbus_t *ctx = NULL; //定义modbus类型的 指针ctx;
uint16_t dest[64] = {0}; //定义数组 接收从机的回答;
uint8_t data[64] = {0}; //定义数组 接收从机的回答;
int addr, status;
key_t key; //创建key值
int main(int argc, char const *argv[])
{
if (argc != 3)
{
printf("please input%s<ip> <port>\n", argv[0]);
return -1;
}
key = ftok("/home/hq/app.c", 'b');
if (key < 0)
{
perror("ftok err");
return -1;
}
//1.以tcp方式创建modbus实例,并初始化
ctx = modbus_new_tcp(argv[1], atoi(argv[2]));
if (ctx != NULL)
{
printf("modbus_new_tcp ok\n");
}
else
{
printf("modbus_new_tcp err\n");
return -1;
}
//2.设置从机ID
modbus_set_slave(ctx, 1);
//3.和从机(slave)建立连接
modbus_connect(ctx);
//创建一个线程
pthread_t tid;
pthread_create(&tid, NULL, handler, NULL);
pthread_detach(tid);
while (1) //主线程
{
//创建消息队列
struct msgbuf
{
long type; //暗号 ,另外的程序必须用同样type值才能接收
int a;
int b;
char c[128];
};
struct msgbuf msg; //定义结构体变量
int msgid;
msgid = msgget(key, IPC_CREAT | IPC_EXCL | 0666);
if (msgid < 0)
{
if (errno == EEXIST)
msgid = msgget(key, 06666);
else
{
perror("msgget err");
return -1;
}
}
size_t s = sizeof(msg) - sizeof(long);
msgrcv(msgid, &msg, s, 0, 0); //第四个参数0:读取消息队列中第一个消息
//写入多个连续线圈的状态
//printf("(数字之间用1个空格隔开):\n0 1(led灯打开),0 0(led灯关闭),1 1(蜂鸣器开),1 0(蜂鸣器关)\n请输入:");
// int num = scanf("%d %d", &addr, &status);
modbus_write_bit(ctx, msg.a, msg.b);
modbus_read_bits(ctx, msg.a, 1, data); //读多个线圈//此时读一个
if (msg.a == 0)
sprintf(msg.c,"led灯的状态为:(1开,0关):%d\n\n", data[0]);
else if (msg.a == 1)
{
sprintf(msg.c,"蜂鸣器的状态为:(1开,0关):%d\n\n", data[0]);
}
msgsnd(msgid, &msg, s, 0);//0 阻塞 把控制后的数据发回浏览器
}
// pthread_join(tid,NULL);//阻塞,等待子线程结束后回收子线程资源后不阻塞 主线程才能执行剩余代码退出
modbus_close(ctx); //关闭套接字,先关闭,后释放(因为sockfd保存在实例空间里,先释放就不知道sockfd描述符是什么了)
modbus_free(ctx); //释放modbus实例
return 0;
}
void *handler(void *arg) //子线程
{
while (1)
{
//读取003H保持寄存器的值,可读取多个连续保持寄存器的值
int ret = modbus_read_registers(ctx, 0, 4, dest);
printf("成功读取到%d个保持寄存器数据\n", ret);
printf("光线:%#04x 加速度x:%#04x 加速度y:%#04x 加速度z:%#04x",
dest[0], dest[1], dest[2], dest[3]);
printf("\n");
char buf[128];
sprintf(buf, "光线:%#04x 加速度x:%#04x 加速度y:%#04x 加速度z:%#04x",
dest[0], dest[1], dest[2], dest[3]);
create_shimd(buf);
sleep(5);
}
pthread_exit(NULL);
}
int create_shimd(char *buf) //创建共享内存
{
//1.2创建共享内存
int shmid;
shmid = shmget(key, 128, IPC_CREAT | IPC_EXCL | 0666);
if (shmid < 0)
{
if (errno == EEXIST)
shmid = shmget(key, 128, 0666);
else
{
perror("shmget err");
return -1;
}
}
char *m = NULL;
//映射共享内存
m = (char *)shmat(shmid, NULL, 0);
//读取共享内存的数据
strcpy(m, buf);
return 0;
}