基于深度学习的轴承故障诊断系统

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 = &reg;					/* 读取的寄存器首地址 */
	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端的上位机,导致许多数据处理逻辑都还在下位机,这对下位机的性能是个挑战,最好是把所有数据处理逻辑全都移交给上位机,下位机只做数据读取和发送。

  • 7
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
单链表是一种常见的数据结构,用于存储一系列的节点连接起来的数据。 在C语言中,我们可以使用结构体来定义一个节点,包含一个数据域和一个指向下一个节点的指针域。代码如下: ```c struct Node { int data; struct Node* next; }; ``` 然后,我们可以定义一个指向链表头部的指针,表示整个链表的起始位置。初始化链表时,这个指针可以指向NULL。 ```c struct Node* head = NULL; ``` 接下来,我们可以实现一些基本的操作,例如插入节点、删除节点、遍历链表等。 插入节点的过程包括创建一个新节点,并将其插入到链表中适当的位置。 ```c void insertNode(int value) { struct Node* newNode = (struct Node*)malloc(sizeof(struct Node)); newNode->data = value; newNode->next = NULL; if (head == NULL) { head = newNode; } else { struct Node* curr = head; while (curr->next != NULL) { curr = curr->next; } curr->next = newNode; } } ``` 删除节点的过程需要找到目标节点,并调整前后节点的指针。 ```c void deleteNode(int value) { if (head == NULL) { return; } if (head->data == value) { struct Node* temp = head; head = head->next; free(temp); return; } struct Node* prev = head; struct Node* curr = head->next; while (curr != NULL && curr->data != value) { prev = curr; curr = curr->next; } if (curr != NULL) { prev->next = curr->next; free(curr); } } ``` 遍历链表的过程是从头节点开始,依次打印每个节点的数据。 ```c void printList() { struct Node* curr = head; while (curr != NULL) { printf("%d ", curr->data); curr = curr->next; } printf("\n"); } ``` 这是一个简单的单链表的实现示例,你可以根据需要进一步扩展和优化。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值