0 引言
一种基于深度学习的轴承故障诊断系统,对振动信号进行监测,实现从数据采集到特征识别的端到端智能故障诊断。
基于I.MX6ULL进行开发,完成IIC总线框架下的adxl345振动传感器驱动程序编写,其采用中断读取数据;下位机应用层采用QT多线程同步处理驱动层异步通知信号,实现不同频率下的数据采集,以及数据的实时绘制、网络传输等;开发windows客户端,在Qt中部署pytorch训练的神经网络模型,对来自下位机的数据进行特征识别,以及如频域实时绘制、监测信息显示、数据处理等功能。其主要功能如下表所示:
下位机(I.MX6ULL) | 数据采集控制(6.25hz-3200hz频率选择、量程设置、采样方向) | 开发环境及相应技术: ARM-linux-gcc 、arm-poky-linux-gnueabi-gcc Qt多线程、socket网络、界面、信号与槽、linux C、 Linux 驱动、C++、pytorch、libtorch |
时域数据实时绘制(≥800hz就会出现卡顿) | ||
网络数据传输与接收/服务器端 | ||
故障报警(蜂鸣器) | ||
上位机(windows 客户端) | 数据接收与发送/客户端 | |
故障诊断(特征识别) | ||
接受下位机的状态信息进行参数更新 | ||
实时傅里叶变换、时域特征参数计算并显示 | ||
其他(模型加载、数据保存、绘图、数据导入导出等) | ||
离线数据分析系统(全局、局部数据处理等) |
界面示意图如下图等所示:
上位机登录界面:
上位机(在线监测界面与离线分析界面):
下位机示意图:
诊断结果显示:
1 下位机数据采集
1.1 传感器驱动
传感器采用adxl345数字振动传感器,量程最高为±16g,采样频率为3200hz,根据奈奎斯特采样定理,获取到的最大信息在1600hz以内。驱动程序主要采用platform框架、iic协议读取数据、中断实现高速和低速数据采集(中断下半部读取adxl345的fifo然后使用异步通知使应用层读取数据)、利用IIO框架对传感器参数进行设置。(最开始想利用IIO框架的触发器和缓冲区来实现数据采集,后面发现并不能达到一定频率下的实时数据,所以利用中断来读取数据,但IIO框架也没有删除,利用互斥锁机制来对数据读取和写入进行保护)。
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of_gpio.h>
#include <linux/semaphore.h>
#include <linux/timer.h>
#include <linux/i2c.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/of_irq.h>
#include <linux/irq.h>
#include <linux/regmap.h>
#include <linux/iio/iio.h>
#include <linux/iio/sysfs.h>
#include <linux/iio/buffer.h>
#include <linux/iio/trigger.h>
#include <linux/iio/triggered_buffer.h>
#include <linux/iio/trigger_consumer.h>
#include <linux/types.h>
#include <linux/unaligned/be_byteshift.h>
#include "adxl345.h"
#define DATA_SIZE 32
#define ADXL345_CNT 1
#define ADXL345_NAME "adxl345"
#define ADXL345_INTERRUPT_NAME "ADXL345"
#define ADXL345_CHAN(_type, _channel2, _index) \
{ \
.type = _type, \
.modified = 1, \
.channel2 = _channel2, \
.info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | BIT(IIO_CHAN_INFO_FREQUENCY), \
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \
BIT(IIO_CHAN_INFO_CALIBBIAS), \
.scan_index = _index, \
.scan_type = { \
.sign = 's', \
.realbits = 16, \
.storagebits = 16, \
.shift = 0, \
.endianness = IIO_BE, \
}, \
}
static short BUF_DATA_X_Y_Z[DATA_SIZE];
unsigned char X_Y_Z_FLAG=2; /*采样方向,默认Z轴*/
static int interrupt_conter=0; /*中断计数*/
/* adxl345的扫描元素,三路振动数据*/
enum inv_adxl345_scan {
INV_ADXL345_SCAN_ACCL_X,
INV_ADXL345_SCAN_ACCL_Y,
INV_ADXL345_SCAN_ACCL_Z,
};
/*
* 扫描掩码,两种情况,全启动0X111,或者都不启动0X0
*/
static const unsigned long adxl345_scan_masks[]={
BIT(INV_ADXL345_SCAN_ACCL_X)|
BIT(INV_ADXL345_SCAN_ACCL_Y) |
BIT(INV_ADXL345_SCAN_ACCL_Z) ,
0,
};
static const struct iio_chan_spec adxl345_channels[] =
{
ADXL345_CHAN(IIO_ACCEL,IIO_MOD_X,INV_ADXL345_SCAN_ACCL_X),
ADXL345_CHAN(IIO_ACCEL,IIO_MOD_Y,INV_ADXL345_SCAN_ACCL_Y),
ADXL345_CHAN(IIO_ACCEL,IIO_MOD_Z,INV_ADXL345_SCAN_ACCL_Z),
};
/*设备结构体*/
struct adxl345_dev
{
dev_t devid; /* 设备号 */
struct cdev cdev; /* cdev */
struct class *class; /* 类 */
struct device *device; /* 设备 */
int major; /* 主设备号 */
struct i2c_client *client; /* i2c 设备 */
struct mutex lock;
int irqnum;
irqreturn_t (*handler)(int ,void *);
struct work_struct adxl345_irq_work;
struct device_node *nd;
int gpio;
struct fasync_struct *async_queue; /* 异步相关结构体 */
};
static struct adxl345_dev *myprivate_data; /* 私有数据 */
/* 以正负2g量程为例,4/2^13=,扩大10^9倍,就是488280加速度计分辨率*/
static const int accel_scale_adxl345[] =
{488280,976560,1953120,3906250};
/* 输出速率*/
static const int accel_frequency_adxl345[]=
{0.10,0.20,0.39,0.78,1.56,3.13,6.25,12.5,25,50,100,200,400,800,1600,3200};
/*
* @description: iic从adxl345读取寄存器的值
* @para-dev adxl345设备 reg 要读取的寄存器 val 读取数据缓冲区 len 要读取的数据长度
*/
static int adxl345_read_regs(struct adxl345_dev *dev, u8 reg, void *val, int len)
{
int ret=0;
struct i2c_msg msg[2];
struct i2c_client *client = dev->client;
/* msg[0]为发送要读取的首地址 */
msg[0].addr = client->addr; /* adxl345地址 */
msg[0].flags = 0; /* 标记为发送数据 */
msg[0].buf = ® /* 读取的寄存器首地址 */
msg[0].len = 1; /* reg长度*/
/* msg[1]读取数据 */
msg[1].addr = client->addr; /* adxl345地址 */
msg[1].flags = I2C_M_RD; /* 标记为读取数据*/
msg[1].buf = val; /* 读取数据缓冲区 */
msg[1].len = len; /* 要读取的数据长度*/
ret=i2c_transfer(client->adapter, msg, 2);
if(ret==2)
ret=0;
else
ret=-EREMOTEIO;
return ret;
}
/*
* @description: iic从adxl345写值
* @para-dev adxl345设备 reg 要写的寄存器 buf 写数据缓冲区 len 要写的数据长度
*/
static int adxl345_write_regs(struct adxl345_dev *dev, u8 reg, u8 *buf, u8 len)
{
int ret;
u8 b[256];
struct i2c_msg msg;
struct i2c_client *client =dev->client;
b[0] = reg; /* 寄存器首地址 */
memcpy(&b[1],buf,len); /* 将要写入的数据拷贝到数组b里面 */
msg.addr = client->addr; /* adxl345地址 */
msg.flags = 0; /* 标记为写数据 */
msg.buf = b; /* 要写入的数据缓冲区 */ /*根据iic时序 adxl345*/
msg.len = len + 1; /* 要写入的数据长度,加上寄存器地址的长度*/
ret= i2c_transfer(client->adapter, &msg, 1);
if(ret==1)
ret=0;
else
ret=-EREMOTEIO;
return ret;
}
/*
* @description: 向adxl345指定寄存器读一个寄存器的值
* @param -dev: adxl345设备
* @param - reg: 要读的寄存器
* @return
*/
static u8 adxl345_read_reg(struct adxl345_dev *dev, u8 reg)
{
u8 data = 0;
adxl345_read_regs(dev, reg, &data, 1);
return data;
}
/*
* @description : 向adxl345指定寄存器写入指定的值,写一个寄存器
* @param - dev: adxl345设备
* @param - reg: 要写的寄存器
* @param - data: 要写入的值
* @return : 无
*/
static void adxl345_write_reg(struct adxl345_dev *dev, u8 reg, u8 data)
{
u8 buf = 0;
buf = data;
adxl345_write_regs(dev, reg, &buf, 1);
}
/*利用IIO框架读取adxl345寄存器的数据*/
static int adxl345_read_channel_data(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan,
int *val)
{
int ind;
__be16 d ;
int axis=chan->channel2;
struct adxl345_dev *dev=iio_priv(indio_dev);
ind = (axis - IIO_MOD_X) * 2;
adxl345_read_regs(dev,DATAX0+ind,(u8*)&d,2);// 大端:d[0]低地址 d[1]高地址
*val=(short)d;
return IIO_VAL_INT;
}
/*
* @description : 读函数,当读取 sysfs 中的文件的时候最终此函数会执,
此函数里面会从传感器里面读取各种数据,然后上传给应用。
* @param - indio_dev : iio_dev
* @param - chan : 通道
* @param - val : 读取的值,如果是小数值的话,val 是整数部分。
* @param - val2 : 读取的值,如果是小数值的话,val2 是小数部分。
* @param - mask : 掩码。
*/
static int adxl345_read_raw(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan,
int *val, int *val2, long mask)
{
struct adxl345_dev *dev=iio_priv(indio_dev);
int ret = 0;
unsigned char regdata = 0;
switch (mask){
case IIO_CHAN_INFO_RAW:
mutex_lock(&dev->lock);
ret = adxl345_read_channel_data(indio_dev, chan, val); /* 读取通道值 */
mutex_unlock(&dev->lock);
return ret;
break;
case IIO_CHAN_INFO_SCALE:
mutex_lock(&dev->lock);
regdata=(adxl345_read_reg(dev,DATA_FORMAT)&0x3);
mutex_unlock(&dev->lock);
*val=0;
*val2=accel_scale_adxl345[regdata];
return IIO_VAL_INT_PLUS_NANO;/*组合宏, 值为val+val2/1000000000 */
break;
case IIO_CHAN_INFO_CALIBBIAS: //偏移量
switch (chan->channel2)
{
case IIO_MOD_X:
*val=(int)adxl345_read_reg(dev,OFSX);
return IIO_VAL_INT;
break;
case IIO_MOD_Y:
*val=(int)adxl345_read_reg(dev,OFSY);
return IIO_VAL_INT;
break;
case IIO_MOD_Z:
*val=(int)adxl345_read_reg(dev,OFSZ);
return IIO_VAL_INT;
break;
default:
break;
}
break;
case IIO_CHAN_INFO_FREQUENCY:
*val=accel_frequency_adxl345[adxl345_read_reg(dev,BW_RATE)];
return IIO_VAL_INT;
break;
default:
break;
}
return 0;
}
/*
* @description : 写函数,向 sysfs 中的文件写数据的时候最终此函数会
执行,一般在此函数里面设置传感器,比如量程等。
* @param - indio_dev : iio_dev
* @param - chan : 通道
* @param - val : 读取的值,如果是小数值的话,val 是整数部分。
* @param - val2 : 读取的值,如果是小数值的话,val2 是小数部分。
* @param - mask : 掩码。
*/
static int adxl345_write_raw(struct iio_dev *indio_dev, struct iio_chan_spec const *chan,
int val, int val2, long mask)
{
struct adxl345_dev *dev = iio_priv(indio_dev);
int ret = 0;
int i=0;
switch (mask){
case IIO_CHAN_INFO_SCALE:
for(i=0;i<ARRAY_SIZE(accel_scale_adxl345);i++){
if(accel_scale_adxl345[i]==val2){
switch (i){
case 0:
adxl345_write_reg(dev,DATA_FORMAT ,0X08);
break;
case 1:
adxl345_write_reg(dev,DATA_FORMAT ,0X09);
break;
case 2:
adxl345_write_reg(dev,DATA_FORMAT ,0X0A);
break;
case 3:
adxl345_write_reg(dev,DATA_FORMAT ,0X0B);
break;
default:
break;
}
}
}
break;
case IIO_CHAN_INFO_CALIBBIAS:
break;
case IIO_CHAN_INFO_FREQUENCY:
for(i=0;i<ARRAY_SIZE(accel_frequency_adxl345);i++){
if (accel_frequency_adxl345[i]==val){
adxl345_write_reg(dev,BW_RATE,i);
mdelay(50); // 等待更新
}
}
break;
default:
ret = -EINVAL;
break;
}
return ret;
}
/*
* @description : 用户空间写数据格式,比如我们在用户空间操作 sysfs 来设置传
* :感器的分辨率,如果分辨率带小数,那么这个小数传递到内核空间
* : 应该扩大多少倍,此函数就是用来设置这个的。
* @param - indio_dev : iio_dev
* @param - chan : 通道
* @param - mask : 掩码
*/
static int adxl345_write_raw_get_fmt(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan, long mask)
{
switch (mask) {
case IIO_CHAN_INFO_SCALE: /* 用户空间写的加速度计分辨率数据要乘以1000000000 */
return IIO_VAL_INT_PLUS_NANO;
break;
case IIO_CHAN_INFO_CALIBBIAS:
return IIO_VAL_INT_PLUS_NANO;
// return IIO_VAL_INT; //这个宏会报错
break;
case IIO_CHAN_INFO_FREQUENCY:
return IIO_VAL_INT_PLUS_NANO;
// return IIO_VAL_INT;
break;
}
return -EINVAL;
}
/*
* iio_info结构体变量
*/
static const struct iio_info adxl345_info =
{
.read_raw = adxl345_read_raw,
.write_raw = adxl345_write_raw,
.write_raw_get_fmt = &adxl345_write_raw_get_fmt,
};
/**
* @description: adxl345寄存器初始化函数
* @return {*}
*/
static int adxl345reg_init(struct adxl345_dev *dev)
{
u8 value;
/* 初始化adxl345*/
adxl345_write_reg(dev,INTENABLE,0X00); //使能中断
adxl345_write_reg(dev,DATA_FORMAT ,0X0B); //+-16g 中断高电平有效
adxl345_write_reg(dev,BW_RATE,0XF); //输出速率
adxl345_write_reg(dev,POWER_CTL,0X08);
adxl345_write_reg(dev,OFSX,0X00);
adxl345_write_reg(dev,OFSY,0X00);
adxl345_write_reg(dev,OFSZ,0X00);
// 中断配置
adxl345_write_reg(dev,INT_MAP,0X02);
adxl345_write_reg(dev,FIFO_CTL,0X9F);
adxl345_write_reg(dev,INTENABLE,0X83); //使能中断
value=(unsigned char)adxl345_read_reg(dev,DEVICEID);
printk("device id=%#x\r\n",value);
mdelay(5);
return 0;
}
/*中断下半步处理函数*/
void adxl345_irqwork_func(struct work_struct *work)
{
__be16 d ;
int i;
struct adxl345_dev *dev=container_of(work,struct adxl345_dev,adxl345_irq_work);
if(adxl345_read_reg(dev,INT_SORCE)&0X02){
mutex_lock(&dev->lock);
if(X_Y_Z_FLAG==0){
for(i=0;i<DATA_SIZE;i++) {
adxl345_read_regs(dev,DATAX0,(u8*)&d,2);
BUF_DATA_X_Y_Z[i]=(short)d;
}
}
else if(X_Y_Z_FLAG==1){
for(i=0;i<DATA_SIZE;i++) {
adxl345_read_regs(dev,DATAY0,(u8*)&d,2);
BUF_DATA_X_Y_Z[i]=(short)d;
}
}
else if(X_Y_Z_FLAG==2){
for(i=0;i<DATA_SIZE;i++) {
adxl345_read_regs(dev,DATAZ0,(u8*)&d,2);
BUF_DATA_X_Y_Z[i]=(short)d;
}
}
mutex_unlock(&dev->lock);
}
if(interrupt_conter%100==0)
printk("this is interuupt_%d\n",interrupt_conter);
interrupt_conter++;
kill_fasync(&dev->async_queue,SIGIO,POLL_IN);
adxl345_read_reg(dev,INT_SORCE); // 清除中断标志位
}
/*adxl345中断使能与关闭*/
static void interrupt_enbale(struct adxl345_dev *dev,bool this_flag)
{
mutex_lock(&dev->lock);
if(this_flag){
adxl345_write_reg(dev,INTENABLE,0X00);
adxl345_write_reg(dev,INT_MAP,0X02);
adxl345_write_reg(dev,FIFO_CTL,0X9F);
adxl345_write_reg(dev,INTENABLE,0X83);
}
else
adxl345_write_reg(dev,INTENABLE,0X00);
mutex_unlock(&dev->lock);
}
/*使用工作队列处理下半部*/
static irqreturn_t adxl345_handler(int irq, void *p)
{ struct iio_dev *in_dev=(struct iio_dev*) p;
struct adxl345_dev *dev=iio_priv(in_dev);
schedule_work(&dev->adxl345_irq_work);
return IRQ_RETVAL(IRQ_HANDLED);
}
/*打开设备,然后打开中断*/
static int adxl345_open(struct inode *inode, struct file *filp)
{
struct adxl345_dev *dev;
filp->private_data = myprivate_data;
dev =(struct adxl345_dev *)filp->private_data;
interrupt_enbale(dev,true);
return 0;
}
/*fasync函数,用于处理异步通知*/
static int adxl345_fasync(int fd, struct file *filp, int on)
{
struct adxl345_dev *dev =(struct adxl345_dev *)filp->private_data;
return fasync_helper(fd, filp, on, &dev->async_queue);
}
/*数据读取*/
static ssize_t adxl345_read(struct file *filp, char __user *buf, size_t cnt, loff_t *off)
{
int err=0;
struct adxl345_dev *dev =(struct adxl345_dev *)filp->private_data;
interrupt_enbale(dev,false);
mutex_lock(&dev->lock);
err=copy_to_user(buf,BUF_DATA_X_Y_Z,sizeof(BUF_DATA_X_Y_Z));
mutex_unlock(&dev->lock);
interrupt_enbale(dev,true);
if(err==0)
return DATA_SIZE;
else
return -1;
}
/*数据写入*/
/*应该重新定义write函数的读写格式,buf[0]为寄存器的地址,buf[1]为要写入的值*/
static ssize_t adxl345_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
int retvalue=0;
unsigned char databuf[1];
struct adxl345_dev *dev =(struct adxl345_dev *)filp->private_data;
retvalue = copy_from_user(databuf, buf, cnt);
if(retvalue < 0) {
printk(KERN_ERR"kernel write failed!\n");
return -EFAULT;
}
mutex_lock(&dev->lock);
X_Y_Z_FLAG=databuf[0];
mutex_unlock(&dev->lock);
return retvalue;
}
static int adxl345_release(struct inode *inode, struct file *filp)
{
struct adxl345_dev *dev =(struct adxl345_dev *)filp->private_data;
interrupt_enbale(dev,false);
return adxl345_fasync(-1, filp, 0);
}
/* adxl345操作函数 */
static const struct file_operations adxl345_ops =
{
.owner = THIS_MODULE,
.open = adxl345_open,
.read = adxl345_read,
.release = adxl345_release,
.write =adxl345_write,
.fasync =adxl345_fasync,
};
static int adxl345_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
int ret;
struct adxl345_dev *dev;
struct iio_dev *indio_dev;
/* 1、申请iio_dev内存 */
indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*dev));//使用 iio_device_alloc 函数来申请 iio_dev,并且一起申请了 adxl345_dev 的内存。
if (!indio_dev)
return -ENOMEM;
/* 2、获取adxl345_dev结构体地址 */
dev = iio_priv(indio_dev);
myprivate_data=dev;
dev->client = client;
i2c_set_clientdata(client, indio_dev); /* 保存indio_dev */
mutex_init(&dev->lock);
/*注册设备*/
/* 1、构建设备号 */
dev->major=0;
ret=alloc_chrdev_region(&dev->devid, 0, ADXL345_CNT, ADXL345_NAME);
dev->major = MAJOR(dev->devid);
if(ret<0) printk(KERN_ERR"adxl345 chardev err!\n");
printk("adxl345 major=%d,minor=%d\n",dev->major,MINOR(dev->devid));
/* 2、注册设备 */
dev->cdev.owner=THIS_MODULE;
cdev_init(&dev->cdev, &adxl345_ops);
ret=cdev_add(&dev->cdev, dev->devid, ADXL345_CNT);
if(ret<0) printk(KERN_ERR"adxl345 cedvadd err!\n");
/* 3、创建类 */
dev->class = class_create(THIS_MODULE, ADXL345_NAME);
if (IS_ERR(dev->class)) {
printk(KERN_ERR"adxl345 class creat failed!\n");
return PTR_ERR(dev->class);
}
/* 4、创建设备 */
dev->device = device_create(dev->class, NULL, dev->devid, NULL, ADXL345_NAME);
if (IS_ERR(dev->device))
return PTR_ERR(dev->device);
/*申请中断*/
// 初始化gpio
dev->nd=of_find_node_by_path("/soc/aips-bus@02100000/i2c@021a0000/adxl345@53");
if (dev->nd== NULL)
printk(KERN_INFO"adxl345interrupt-gpios node not find!\n");
dev->gpio=of_get_named_gpio(dev->nd,"adxl345_interrupt_gpios",0);
if(dev->gpio<0)
printk(KERN_INFO"can't get gpio\n");
/* 初始化 IO,并且设置成中断模式 */
gpio_request(dev->gpio,ADXL345_INTERRUPT_NAME);
gpio_direction_input(dev->gpio);
dev->irqnum=irq_of_parse_and_map(dev->nd,0);
printk("adxl345_gpio=[%d],irq_num=[%d]\n",dev->gpio,dev->irqnum);
dev->handler=adxl345_handler;
ret=request_irq(dev->irqnum,dev->handler,IRQF_TRIGGER_RISING,ADXL345_INTERRUPT_NAME,indio_dev);
if(ret < 0)
printk(KERN_INFO"irq %d request failed!\n",dev->irqnum);
/* 初始化 work */
INIT_WORK(&dev->adxl345_irq_work,adxl345_irqwork_func);
/* 3、iio_dev的其他成员变量 */
indio_dev->dev.parent = &client->dev;
indio_dev->info = &adxl345_info;
indio_dev->name = ADXL345_NAME;
indio_dev->modes = INDIO_DIRECT_MODE; /* 直接模式,提供sysfs接口 */
indio_dev->channels = adxl345_channels;
indio_dev->num_channels =ARRAY_SIZE(adxl345_channels);
indio_dev->available_scan_masks = adxl345_scan_masks;
/* 4、注册iio_dev */
ret = iio_device_register(indio_dev);
if (ret < 0) {
dev_err(&client->dev, "iio_device_register failed\n");
return ret;
}
adxl345reg_init(dev);
interrupt_enbale(dev,false);
return 0;
}
static int adxl345_remove(struct i2c_client *client)
{
struct iio_dev *indio_dev = i2c_get_clientdata(client);
struct adxl345_dev *dev=iio_priv(indio_dev);
/* 删除设备 */
cdev_del(&dev->cdev);
unregister_chrdev_region(dev->devid, ADXL345_CNT);
/* 注销掉类和设备 */
device_destroy(dev->class, dev->devid);
class_destroy(dev->class);
free_irq(dev->irqnum,indio_dev);
/*注销IIO */
iio_device_unregister(indio_dev);
return 0;
}
/* 传统匹配方式ID列表 */
static const struct i2c_device_id adxl345_id[] = {
{"adxl345", 0},
{}
};
/* 设备树匹配列表 */
static const struct of_device_id adxl345_of_match[] = {
{ .compatible = "adxl345" },
{ /* Sentinel */ }
};
/* i2c驱动结构体 */
static struct i2c_driver adxl345_driver = {
.probe = adxl345_probe,
.remove = adxl345_remove,
.driver = {
.owner = THIS_MODULE,
.name = "adxl345_driver",
.of_match_table = adxl345_of_match,
},
.id_table = adxl345_id,
};
static int __init adxl345_init(void)
{
int ret = 0;
ret = i2c_add_driver(&adxl345_driver);
return ret;
}
static void __exit adxl345_exit(void)
{
i2c_del_driver(&adxl345_driver);
}
module_init(adxl345_init);
module_exit(adxl345_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("ZHW-SYSTEAM-FAULT");
MODULE_INFO(intree, "Y");
1.2 应用层数据读取
应用层采用Qt 多线程同步处理异步通知信号,因为线程共享信号屏蔽字,主线程在创建子线程后,调用该函数,屏蔽信号,主线程只做界面绘制工作,空闲的子线程拿到信号就进行数据的读取。
void pthread_myset()
{
int n;
sigset_t set, oldset;
sigemptyset(&set);
sigemptyset(&oldset);
sigaddset(&set, SIGIO);
n= pthread_sigmask(SIG_BLOCK,&set, &oldset);
if(n==0) qDebug()<<"pthread success! "<< endl;
}
1.2.1 打开文件
void open_adxl345()
{
int flags = 0;
char const *filename="/dev/adxl345";
adxl345_fd=open(filename, O_RDWR);
if(adxl345_fd < 0){
return ;
}
adxl345_fd_flag=1; /*打开文件标志 */
signal(SIGIO, sigio_signal_func);
fcntl(adxl345_fd,__F_SETOWN, getpid()); /*将当前进程的进程号告内核*/
flags = fcntl(adxl345_fd, F_GETFD); /*获取当前的进程状态*/
fcntl(adxl345_fd, F_SETFL, flags | 00020000); /*设置进程启用异步通知功能*/
// 设置输出轴
write(adxl345_fd,direction_buf,sizeof(direction_buf));
}
1.2.2 数据读取
数据读取利用queqe容器:
void sigio_signal_func(int signum)
{
int retvalue;
retvalue= read(adxl345_fd,data,sizeof(data));
if(retvalue<0)
qDebug()<<"read's retvalue:"<<retvalue<<endl;
for(int i=0;i<32;i++)
queue_data.push(data[i]); //入队操作
}
子线程轮询处理数据:
void deal_data_class::working(bool flag){
this->is_can_run=flag;
if(!this->is_can_run)
return ;
short st_tamp;
double de_tamp;
while(this->is_can_run){
if((queue_data.size()>32)){ // 默认有32个数据才开始出队
st_tamp=queue_data.front();
queue_data.pop();
de_tamp=double(st_tamp*scale_select*9.8);
x_all_count+=x_step; // 绘图横坐标累加
_y.push_back(de_tamp);
_x.push_back(double(x_all_count));
data_net[read_count]=st_tamp;
read_count_plot++;
read_count++;
if(read_count_plot%32==0){
// 赋值给plot数组以绘图
time_data_and_x_lock.lock();
size_32_time_data.clear();
size_32_time_x.clear();
size_32_time_data=_y;
size_32_time_x=_x;
time_data_and_x_lock.unlock();
emit this->data_can_plot();
_x.clear();
_y.clear();
}
if(read_count%1024==0){
// qDebug()<< read_count_plot<<":readcount"<<endl;
if(network_status==1){ //网络数据数组复制
memcpy(send_data_net.data(),&data_net,sizeof(data_net));
net_flag=true; // 数据准备完善标志
}
read_count=0;
}
}
else {
QThread::sleep(1);continue;
}
}
}
1.3 文件打开与关闭
void read_data_class::working(bool flag)
{
if(flag){
if(adxl345_fd_flag==-1 || adxl345_fd_flag==0){
open_adxl345();
}
}
else{
// 关闭文件描述符与关闭异步通知
if(adxl345_fd_flag==1){
close_adxl345();
}
}
}
1.4 绘图
绘图用的是customplot,qt的qchart在数据量大的情况下就会特别卡顿。
1.5 数据发送
数据发送由主线层将套接字传递给子线程,由子线程发送。
2 上位机数据处理
2.1 离线数据系统
主要用于数据分割,后期处理,包括自定义的傅里叶变换长度,局部、全局数据分析。
2.2 在线监测系统
2.2.1 模型训练与模型加载
模型训练使用的pytorch框架,其数据预处理、模型构建,以及示范的LSTM1DCNN网络模型与作者该篇博客类似:基于LSTM的故障诊断_基于lstm的轴承故障检测_旧日之歌的博客-CSDN博客。
模型的推理从pth模型保存为pt模型:
import torch.nn.functional as F
from torchvision import transforms
from matplotlib import pyplot as plt
from numpy import dtype
from 数据划分 import *
import torchvision
from torch import nn, Tensor
from torch.nn import Conv1d,MaxPool1d,Flatten,Linear
import torch
device = torch.device("cpu")
class mylstm(nn.Module):
def __init__(self):
super(mylstm, self).__init__()
self.modle1 = nn.LSTM(input_size=1,hidden_size=16,num_layers=2, batch_first=True,dropout=0.2)
self.modle2 = nn.Sequential (
nn.Conv1d(512,128,1,1),
nn.MaxPool1d(2,2),
nn.Conv1d(128,32,4,4),
nn.Flatten(),
nn.Linear(64, 32),
nn.Dropout(0.1),
nn.ReLU(),
nn.Linear(32,4 ),
)
def forward(self, x):
h_0 = torch.randn(2,128,16)
c_0 = torch.randn(2,128, 16)
h_0 = h_0.to(device)
c_0 = c_0.to(device)
x = x.to(torch.float32)
x,(h_0,c_0)=self.modle1(x,(h_0,c_0))
x=self.modle2(x)
return x
from LSTM_1DCNN import *
model=mylstm()
state_dict=torch.load("libtorchtest_dict_practice_200.pth")
model.load_state_dict(state_dict)
model.to(device)
model.eval()
x=torch.randn(128,cut_long,1).to(device)
traced_script_module = torch.jit.trace(model, x)
traced_script_module.save('libtorchtest_dict_practice_200.pt')#保存模型位置
print("save is ok\n")
mymodel=torch.jit.load('libtorchtest_dict_practice_200.pt')
output=mymodel(x)
print(output)
2.2.2 模型的部署
模型的部署主要采用的是libtorch框架,如何将libtorch框架移植在QT里可搜索网上其他教程,但一般要注意以下几个点:
1、-INCLUDE:?searchsorted_cuda@native@at@@YA?AVTensor@2@AEBV32@0_N1@Z -INCLUDE:?warp_size@cuda@at@@YAHXZ 一定要在qt链接动态的时候加上这串字符
2、一定要注意libtorch和pytorch的版本问题,两者要完全对应,推理用的是cuda,那么部署能用cpu/cuda,如果推理用cpu,部署只能用cpu。
3、环境变量
2.2.3 模型加载与使用
//model_load 模型加载或者使用 model_select 选择模型 use_gpu gpu使用选择
void model_load::working(bool model_load,const std::string module_path_, int model_select,bool use_gpu)
{
if(model_load){ // 模型加载
this->module_path=module_path_;
if(model_select==1){ // 加载lstm——1dcnn模型
if(this->model_count[model_select]==1){
emit this->to_log_print("lstm1dcnn已加载");
return;
}
if (torch::cuda::is_available())
emit this->to_log_print("支持GPU");
if (torch::cuda::is_available() && use_gpu){
this->device_=torch::kCUDA;
emit this->to_log_print("the module for lstm1dcnn use cuda...");
}
else{
emit this->to_log_print("the module for lstm1dcnn use cpu...");
this->device_ = torch::kCPU;
}
try {
this->module_lstm1dcnn=torch::jit::load(this->module_path);
emit this->to_log_print("the module for lstm1dcnn load successed...");
}
catch (const c10::Error& e) {
std::cerr << "Error loading the model!\n";
emit this->to_log_print("the module for lstm1dcnn load failed...");
}
this->module_lstm1dcnn.to(this->device_);
this->module_lstm1dcnn.eval();
this->model_count[1]=1; // 模型lstm1dcnn加载标志
}
}
else{ // 模型识别
QString module_name;
if (torch::cuda::is_available() && use_gpu){
this->device_=torch::kCUDA;
emit this->to_log_print("the module is using cuda...");
}
else{
emit this->to_log_print("the module is using cpu...");
this->device_ = torch::kCPU;
}
at::TensorOptions ops=at::TensorOptions().dtype(at::kDouble).device(this->device_);
if(model_select==1 && this->model_count[1]==1){
this->module_lstm1dcnn.to(this->device_);
online_data.model_diagnosis_lock.lockForRead();
data_scaler(online_data.model_diagnosis); //数据归一化
this->input_tensor=torch::from_blob(online_data.model_diagnosis.data()
, {128,512,1},ops).to(this->device_).clone(); // 深度拷贝
online_data.model_diagnosis_lock.unlock();
this->output=this->module_lstm1dcnn.forward(std::vector<torch::jit::IValue>{this->input_tensor}).toTensor();
emit this->to_log_print("the module of lstm1dcnn'identify is OK...");
module_name="LSTM1DCNN";
// 结果输出
at::Tensor outputarg= this->output.argmax(1);
int output_result[4];
memset(output_result,0,sizeof(output_result));
for(int i=0;i<128;i++)
output_result[outputarg[i].item().toInt()]++;
emit this->diagnosis_result(output_result[0],output_result[1],output_result[2],output_result[3]);
int max_result=0;
int max_index=0;
// 记录最大结果以及结果汇总
for(int i=0;i<4;i++){
emit this->to_log_print(QString("当前%1诊断结果统计:%2").arg(i).arg(output_result[i]));
if(output_result[i]>max_result){
max_result=output_result[i];
max_index=i;
}
}
// 发送给tabelwidget
QDateTime Timedata = QDateTime::currentDateTime(); // 获取当前时间
QString str = Timedata.toString("yyyy-MM-dd-hh:mm:ss");
emit this->to_tablewidget("NULL", // 序号
str, // 时间
module_name, //模型
online_data.direction_sample, // 采样轴
QString("%1[%2/128]").arg(label_fault[max_index]).arg(max_result),// 诊断结果
QString("%1Hz").arg(online_data.hz_for_select)); //采样频率
}
}
// else{
// emit this->to_log_print("the module of lstm1dcnn unloaded......");
// return ;
// }
// if(model_select==2 && this->model_count[1]==2){
// // restnet模型识别
// }
// else{
// return ;
// }
// if(model_select==3 && this->model_count[3]==3){
// // cnn模型识别
// }
// else{
// return ;
// }
}
2.2.4 傅里叶变换
傅里叶变换采用的是fftw库,号称是世界上最快的傅里叶变换。测试结果与matlab所输出的结果一致。
void fft_function(vector<double>& after_fft_data,vector<double> before_fft_data,int fft_length)
{
fftw_complex *out;
fftw_complex *in;
fftw_plan p;
in =(fftw_complex*)fftw_malloc(sizeof(fftw_complex) * fft_length);
out=(fftw_complex*)fftw_malloc(sizeof(fftw_complex) * fft_length);
p=fftw_plan_dft_1d(fft_length,in,out,FFTW_FORWARD,FFTW_ESTIMATE);
for(int i=0;i<fft_length;i++){
in[i][0]=before_fft_data.at(i);
in[i][1]=0;
}
fftw_execute(p);
after_fft_data.clear();
after_fft_data.push_back(0); // 去除直流分量
for(int j=1;j<fft_length/2+1;j++){
after_fft_data.push_back(sqrt(out[j][0]*out[j][0]+out[j][1]*out[j][1])/fft_length*2);
}
fftw_destroy_plan(p);
if(in!=NULL)
{
fftw_free(in);
}
if(out!=NULL){
fftw_free(out);
}
}
2.2.5 网络数据读取
void Online_data_analysis::network_set_init()
{
// 创建通信的套接字
this->m_tcp =new QTcpSocket(this);
connect(this->m_tcp,&QTcpSocket::readyRead,this,[=](){
// qDebug()<<"it can ready"<<endl;
QByteArray array = this->m_tcp->readAll();
if(array.size()==(sizeof(short)*1024)){ // 读取的数据
short* p=(short*)array.data();
online_data.fft_and_datapara_lock.lockForWrite();
for(int i=0;i<1024;i++)
online_data.fft_and_datapara.push_back((online_data.scale_sample*(double)p[i]*9.8));
online_data.fft_and_datapara_lock.unlock();
online_data.net_read_count++;
//发送给fft时域状态参数计算变换等子线程
this->fft_and_data_deal_thread->working();
ui->output_log_plainTextEdit->appendPlainText(QString("从网络中读取次数为(2048字节/次):%1......").arg(online_data.net_read_count));
}
if(array.size()==5){ // 读取的初始化信息
unsigned char *p= (unsigned char*)array.data();
// online_data_struct online_dat在此函数内部初始化
// 每检测到初始化信息,所有数据都重新清除
ui->output_log_plainTextEdit->appendPlainText("状态重置>>>......");
this->parameter_show_of_come_net(p[0],p[1],p[2],p[3],p[4]);
}
});
// 服务器端断开连接
connect(this->m_tcp,&QTcpSocket::disconnected ,this,[=](){
ui->server_ip_lineEdit->setText("服务器连接已断开....");
ui->server_port_lineEdit->setText("服务器连接已断开....");
this->information_print_net_flag(0,"服务器连接已断开....");
this->m_tcp->close();
ui->pushButton_net_start->setDisabled(false);
ui->pushButton_net_end->setDisabled(true);
qDebug()<<"the net is disconnected"<<endl;
});
// 检测连接
connect(this->m_tcp,&QTcpSocket::connected ,this,[=](){
// 显示服务器IP和端口
ui->server_ip_lineEdit->setText(this->m_tcp->peerAddress().toString());
ui->server_port_lineEdit->setText( QString::number(this->m_tcp->peerPort()));
this->information_print_net_flag(1,"已连接服务器....");
ui->pushButton_net_start->setDisabled(true);
ui->pushButton_net_end->setDisabled(false);
QString str="the connect is init for client ,if recieved,server's read is ok";
this->m_tcp->write(str.toUtf8().data());
});
}
2.2.6 其他
频域折线图、柱状图、监测数据显示(导入与导出)、数据保存、界面状态参数更新等。
3 总结
该系统还存在许多不足之处与改进之处,最开始只想做个数据实时绘制界面,就没考windows端的上位机,导致许多数据处理逻辑都还在下位机,这对下位机的性能是个挑战,最好是把所有数据处理逻辑全都移交给上位机,下位机只做数据读取和发送。