基于IMX6ULL的AP3216C的QT动态数据曲线图显示

前言:本文为手把手教学 Linux+QT 的典型基础项目 AP3216C 的数据折线图显示,项目使用正点原子的 IMX6ULL 阿尔法( Cortex-A7 系列)开发板。项目需要实现 AP3216C Linux 系统下的驱动,使用 QT 设计 AP3216C 的数据显示页面作为项目的应用层。该项目属于非常简单的入门级项目,核心目的是帮助大家熟悉 Linux 系统下的项目制作和工程研发过程。希望该项目可以帮助大家学会灵活使用 Linux 系统下的传感器开发!(文末有代码开源!

硬件实物图:

效果图:

本项目核心是教学 Linux+QT 的工程研发流程,如果专注于应用层工程学习的,可以直接使用正点原子提供的 Linux 镜像文件。本教程是适配正点提供的系统镜像的,可以直接使用!

一、AP3216C概述

I.MX6U-ALPHA 开发板上通过 I2C1 连接了一个三合一环境传感器:AP3216CAP3216C 是由敦南科技推出的一款传感器,其支持环境光强度(ALS)、接近距离(PS)和红外线强度(IR)这三个环境参数检测。该芯片可以通过 IIC 接口与主控制相连,并且支持中断,AP3216C 的特点如下:

①、I2C 接口,快速模式下波特率可以到 400Kbit/S
②、多种工作模式选择:ALS、PS+IR、ALS+PS+IR、PD 等等。
③、内建温度补偿电路。
④、宽工作温度范围(-30°C ~ +80°C)。
⑤、超小封装,4.1mm x 2.4mm x 1.35mm
⑥、环境光传感器具有 16 位分辨率。
⑦、接近传感器和红外传感器具有 10 位分辨率。

这个芯片设计的用途是给手机之类的使用,比如:返回当前环境光强以便调整屏幕亮度;用户接听电话时,将手机放置在耳边后,自动关闭屏幕避免用户误触碰 。

二、基于Linux的AP3216C驱动

2.1 AP3216C的设备树

Linux 内核也将 I2C 驱动分为两部分:

①、I2C 总线驱动,I2C 总线驱动就是 SOC 的 I2C 控制器驱动,也叫做 I2C 适配器驱动。
②、I2C 设备驱动,I2C 设备驱动就是针对具体的 I2C 设备而编写的驱动。

2.1.1 IO修改或添加

需要设置 UART4_TXD 和 UART4_RXD 这两个 IO,NXP 其实已经将他这两个 IO 设置好了,打开 imx6ull-alientek-emmc.dts,然后找到如下内容:

		pinctrl_i2c1: i2c1grp {
			fsl,pins = <
				MX6UL_PAD_UART4_TX_DATA__I2C1_SCL 0x4001b8b0
				MX6UL_PAD_UART4_RX_DATA__I2C1_SDA 0x4001b8b0 
			>;
		};

pinctrl_i2c1 就是 I2C1 的 IO 节点,这里将 UART4_TXDUART4_RXD 这两个 IO 分别复用为 I2C1_SCLI2C1_SDA,电气属性都设置为 0x4001b8b0。 

2.1.2 在 i2c1 节点追加 ap3216c 子节点

AP3216C 是连接到 I2C1 上的,因此需要在 i2c1 节点下添加 ap3216c 的设备子节点,在 imx6ull-alientek-emmc.dts 文件中找到 i2c1 节点

&i2c1 {
	clock-frequency = <100000>;			/* 时钟频率 */
	pinctrl-names = "default";
	pinctrl-0 = <&pinctrl_i2c1>;		/* pinctrl引脚电气属性 */
	status = "okay";

	ap3216c@1e {
		compatible = "alientek,ap3216c";
		reg = <0x1e>;
	};
};

编译设备树,传到开发板上,重启。此时我们系统的I2C设备有:

2.2 AP3216C 驱动编写

下面编写 AP3216 驱动:

ap3216creg.h:

#ifndef AP3216C_H
#define AP3216C_H
/***************************************************************
文件名  : ap3216creg.h
描述     : AP3216C寄存器地址描述头文件
***************************************************************/
#define AP3216C_ADDR     0X1E /* AP3216C器件地址  */
/* AP3316C寄存器 */
#define AP3216C_SYSTEMCONG 0x00 /* 配置寄存器       */
#define AP3216C_INTSTATUS 0X01 /* 中断状态寄存器   */
#define AP3216C_INTCLEAR 0X02 /* 中断清除寄存器   */
#define AP3216C_IRDATALOW 0x0A /* IR数据低字节     */
#define AP3216C_IRDATAHIGH 0x0B /* IR数据高字节     */
#define AP3216C_ALSDATALOW 0x0C /* ALS数据低字节    */
#define AP3216C_ALSDATAHIGH 0X0D /* ALS数据高字节    */
#define AP3216C_PSDATALOW 0X0E /* PS数据低字节     */
#define AP3216C_PSDATAHIGH 0X0F /* PS数据高字节     */
#endif

ap3216.c:

#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 "ap3216creg.h"

#define AP3216C_CNT	1
#define AP3216C_NAME	"ap3216c"

struct ap3216c_dev {
	dev_t devid;			/* 设备号 	 */
	struct cdev cdev;		/* cdev 	*/
	struct class *class;	/* 类 		*/
	struct device *device;	/* 设备 	 */
	struct device_node	*nd; /* 设备节点 */
	int major;			/* 主设备号 */
	void *private_data;	/* 私有数据 */
	unsigned short ir, als, ps;		/* 三个光传感器数据 */
};

static struct ap3216c_dev ap3216cdev;

/*
 * @description	: 从ap3216c读取多个寄存器数据
 * @param - dev:  ap3216c设备
 * @param - reg:  要读取的寄存器首地址
 * @param - val:  读取到的数据
 * @param - len:  要读取的数据长度
 * @return 		: 操作结果
 */
static int ap3216c_read_regs(struct ap3216c_dev *dev, u8 reg, void *val, int len)
{
	int ret;
	struct i2c_msg msg[2];
	struct i2c_client *client = (struct i2c_client *)dev->private_data;

	/* msg[0]为发送要读取的首地址 */
	msg[0].addr = client->addr;			/* ap3216c地址 */
	msg[0].flags = 0;					/* 标记为发送数据 */
	msg[0].buf = &reg;					/* 读取的首地址 */
	msg[0].len = 1;						/* reg长度*/

	/* msg[1]读取数据 */
	msg[1].addr = client->addr;			/* ap3216c地址 */
	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 {
		printk("i2c rd failed=%d reg=%06x len=%d\n",ret, reg, len);
		ret = -EREMOTEIO;
	}
	return ret;
}

/*
 * @description	: 向ap3216c多个寄存器写入数据
 * @param - dev:  ap3216c设备
 * @param - reg:  要写入的寄存器首地址
 * @param - val:  要写入的数据缓冲区
 * @param - len:  要写入的数据长度
 * @return 	  :   操作结果
 */
static s32 ap3216c_write_regs(struct ap3216c_dev *dev, u8 reg, u8 *buf, u8 len)
{
	u8 b[256];
	struct i2c_msg msg;
	struct i2c_client *client = (struct i2c_client *)dev->private_data;
	
	b[0] = reg;					/* 寄存器首地址 */
	memcpy(&b[1],buf,len);		/* 将要写入的数据拷贝到数组b里面 */
		
	msg.addr = client->addr;	/* ap3216c地址 */
	msg.flags = 0;				/* 标记为写数据 */

	msg.buf = b;				/* 要写入的数据缓冲区 */
	msg.len = len + 1;			/* 要写入的数据长度 */

	return i2c_transfer(client->adapter, &msg, 1);
}

/*
 * @description	: 读取ap3216c指定寄存器值,读取一个寄存器
 * @param - dev:  ap3216c设备
 * @param - reg:  要读取的寄存器
 * @return 	  :   读取到的寄存器值
 */
static unsigned char ap3216c_read_reg(struct ap3216c_dev *dev, u8 reg)
{
	u8 data = 0;

	ap3216c_read_regs(dev, reg, &data, 1);
	return data;

#if 0
	struct i2c_client *client = (struct i2c_client *)dev->private_data;
	return i2c_smbus_read_byte_data(client, reg);
#endif
}

/*
 * @description	: 向ap3216c指定寄存器写入指定的值,写一个寄存器
 * @param - dev:  ap3216c设备
 * @param - reg:  要写的寄存器
 * @param - data: 要写入的值
 * @return   :    无
 */
static void ap3216c_write_reg(struct ap3216c_dev *dev, u8 reg, u8 data)
{
	u8 buf = 0;
	buf = data;
	ap3216c_write_regs(dev, reg, &buf, 1);
}

/*
 * @description	: 读取AP3216C的数据,读取原始数据,包括ALS,PS和IR, 注意!
 *				: 如果同时打开ALS,IR+PS的话两次数据读取的时间间隔要大于112.5ms
 * @param - ir	: ir数据
 * @param - ps 	: ps数据
 * @param - ps 	: als数据 
 * @return 		: 无。
 */
void ap3216c_readdata(struct ap3216c_dev *dev)
{
	unsigned char i =0;
    unsigned char buf[6];
	
	/* 循环读取所有传感器数据 */
    for(i = 0; i < 6; i++)	
    {
        buf[i] = ap3216c_read_reg(dev, AP3216C_IRDATALOW + i);	
    }

    if(buf[0] & 0X80) 	/* IR_OF位为1,则数据无效 */
		dev->ir = 0;					
	else 				/* 读取IR传感器的数据   		*/
		dev->ir = ((unsigned short)buf[1] << 2) | (buf[0] & 0X03); 			
	
	dev->als = ((unsigned short)buf[3] << 8) | buf[2];	/* 读取ALS传感器的数据 			 */  
	
    if(buf[4] & 0x40)	/* IR_OF位为1,则数据无效 			*/
		dev->ps = 0;    													
	else 				/* 读取PS传感器的数据    */
		dev->ps = ((unsigned short)(buf[5] & 0X3F) << 4) | (buf[4] & 0X0F); 
}

/*
 * @description		: 打开设备
 * @param - inode 	: 传递给驱动的inode
 * @param - filp 	: 设备文件,file结构体有个叫做private_data的成员变量
 * 					  一般在open的时候将private_data指向设备结构体。
 * @return 			: 0 成功;其他 失败
 */
static int ap3216c_open(struct inode *inode, struct file *filp)
{
	filp->private_data = &ap3216cdev;

	/* 初始化AP3216C */
	ap3216c_write_reg(&ap3216cdev, AP3216C_SYSTEMCONG, 0x04);		/* 复位AP3216C 			*/
	mdelay(50);														/* AP3216C复位最少10ms 	*/
	ap3216c_write_reg(&ap3216cdev, AP3216C_SYSTEMCONG, 0X03);		/* 开启ALS、PS+IR 		*/
	return 0;
}

/*
 * @description		: 从设备读取数据 
 * @param - filp 	: 要打开的设备文件(文件描述符)
 * @param - buf 	: 返回给用户空间的数据缓冲区
 * @param - cnt 	: 要读取的数据长度
 * @param - offt 	: 相对于文件首地址的偏移
 * @return 			: 读取的字节数,如果为负值,表示读取失败
 */
static ssize_t ap3216c_read(struct file *filp, char __user *buf, size_t cnt, loff_t *off)
{
	short data[3];
	long err = 0;

	struct ap3216c_dev *dev = (struct ap3216c_dev *)filp->private_data;
	
	ap3216c_readdata(dev);

	data[0] = dev->ir;
	data[1] = dev->als;
	data[2] = dev->ps;
	err = copy_to_user(buf, data, sizeof(data));
	return 0;
}

/*
 * @description		: 关闭/释放设备
 * @param - filp 	: 要关闭的设备文件(文件描述符)
 * @return 			: 0 成功;其他 失败
 */
static int ap3216c_release(struct inode *inode, struct file *filp)
{
	return 0;
}

/* AP3216C操作函数 */
static const struct file_operations ap3216c_ops = {
	.owner = THIS_MODULE,
	.open = ap3216c_open,
	.read = ap3216c_read,
	.release = ap3216c_release,
};

 /*
  * @description     : i2c驱动的probe函数,当驱动与
  *                    设备匹配以后此函数就会执行
  * @param - client  : i2c设备
  * @param - id      : i2c设备ID
  * @return          : 0,成功;其他负值,失败
  */
static int ap3216c_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
	/* 1、构建设备号 */
	if (ap3216cdev.major) {
		ap3216cdev.devid = MKDEV(ap3216cdev.major, 0);
		register_chrdev_region(ap3216cdev.devid, AP3216C_CNT, AP3216C_NAME);
	} else {
		alloc_chrdev_region(&ap3216cdev.devid, 0, AP3216C_CNT, AP3216C_NAME);
		ap3216cdev.major = MAJOR(ap3216cdev.devid);
	}

	/* 2、注册设备 */
	cdev_init(&ap3216cdev.cdev, &ap3216c_ops);
	cdev_add(&ap3216cdev.cdev, ap3216cdev.devid, AP3216C_CNT);

	/* 3、创建类 */
	ap3216cdev.class = class_create(THIS_MODULE, AP3216C_NAME);
	if (IS_ERR(ap3216cdev.class)) {
		return PTR_ERR(ap3216cdev.class);
	}

	/* 4、创建设备 */
	ap3216cdev.device = device_create(ap3216cdev.class, NULL, ap3216cdev.devid, NULL, AP3216C_NAME);
	if (IS_ERR(ap3216cdev.device)) {
		return PTR_ERR(ap3216cdev.device);
	}

	ap3216cdev.private_data = client;

	return 0;
}

/*
 * @description     : i2c驱动的remove函数,移除i2c驱动的时候此函数会执行
 * @param - client 	: i2c设备
 * @return          : 0,成功;其他负值,失败
 */
static int ap3216c_remove(struct i2c_client *client)
{
	/* 删除设备 */
	cdev_del(&ap3216cdev.cdev);
	unregister_chrdev_region(ap3216cdev.devid, AP3216C_CNT);

	/* 注销掉类和设备 */
	device_destroy(ap3216cdev.class, ap3216cdev.devid);
	class_destroy(ap3216cdev.class);
	return 0;
}

/* 传统匹配方式ID列表 */
static const struct i2c_device_id ap3216c_id[] = {
	{"alientek,ap3216c", 0},  
	{}
};

/* 设备树匹配列表 */
static const struct of_device_id ap3216c_of_match[] = {
	{ .compatible = "alientek,ap3216c" },		/* 需要和设备树下的保持一致 */
	{ /* Sentinel */ }
};

/* i2c驱动结构体 */	
static struct i2c_driver ap3216c_driver = {
	.probe = ap3216c_probe,
	.remove = ap3216c_remove,
	.driver = {						/* 设备树的匹配方法 */
			.owner = THIS_MODULE,
		   	.name = "ap3216c",
		   	.of_match_table = ap3216c_of_match, 	
		   },
	.id_table = ap3216c_id,			/* 传统驱动匹配方法 */
};
		   
/*
 * @description	: 驱动入口函数
 * @param 		: 无
 * @return 		: 无
 */
static int __init ap3216c_init(void)
{
	int ret = 0;

	ret = i2c_add_driver(&ap3216c_driver);
	return ret;
}

/*
 * @description	: 驱动出口函数
 * @param 		: 无
 * @return 		: 无
 */
static void __exit ap3216c_exit(void)
{
	i2c_del_driver(&ap3216c_driver);
}

/* module_i2c_driver(ap3216c_driver) */

module_init(ap3216c_init);
module_exit(ap3216c_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("sneak");

驱动详解可查阅注释及配合上诉的I2C驱动框架的框图及数据手册理解

我们通过 在终端输入 cat 指令可以读取到传感器的数值变量,如下图:

三、AP3216C的QT程序设计

IMX6ULL 的 QT-UI 的部署与实现需要依赖 QT 的很多依赖包,各位读者可以根据实际情况进行移植。或者直接使用正点原子提供的系统镜像! 

3.1 Chart表制作

Qt Charts 很方便的绘制我们常见的曲线图折线图柱状图饼状图等图表。不用自己花精力去了解第三方组件的使用了或者开发第三方组件。Qt 的帮助文档里已经有说明 Qt Charts 主要部件的使用方法。需要用到时我们可以查看 Qt 文档就可以了。

本项目中我们需要使用曲线图去展示 AP3216CALS 光强度属性。

使用一个 QSplineSeries 对象(曲线),一个 QChart(图表),一个 QChartView(图表视图)。首先我们创建坐 chart 图表,然后创建两条坐标轴 axisXaxisY。将两条坐标轴添加到 chart 图表上,再将 splineSeries 曲线与坐标轴连系起来。最后再将 chart 图表添加到 chartView 图表视图中。曲线上的数据由系统产生随机数,使用定时器更新数据。

qtchart.pro:

QT       += core gui charts

greaterThan(QT_MAJOR_VERSION, 4): QT += widgets

CONFIG += c++11

# The following define makes your compiler emit warnings if you use
# any Qt feature that has been marked deprecated (the exact warnings
# depend on your compiler). Please consult the documentation of the
# deprecated API in order to know how to port your code away from it.
DEFINES += QT_DEPRECATED_WARNINGS

# You can also make your code fail to compile if it uses deprecated APIs.
# In order to do so, uncomment the following line.
# You can also select to disable deprecated APIs only up to a certain version of Qt.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000    # disables all the APIs deprecated before Qt 6.0.0

SOURCES += \
    main.cpp \
    mainwindow.cpp

HEADERS += \
    mainwindow.h

FORMS += \
    mainwindow.ui

# Default rules for deployment.
qnx: target.path = /tmp/$${TARGET}/bin
else: unix:!android: target.path = /opt/$${TARGET}/bin
!isEmpty(target.path): INSTALLS += target

mainwindow.h:

#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QChartView>
#include <QSplineSeries>
#include <QScatterSeries>
#include <QDebug>
#include <QValueAxis>
#include <QTimer>
#include <QMainWindow>

/*  必需添加命名空间 */
QT_CHARTS_USE_NAMESPACE

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

private:
    /* 接收数据接口 */
    void receivedData(int);

    /* 数据最大个数 */
    int maxSize;

    /* x轴上的最大值 */
    int maxX;

    /* y轴上的最大值 */
    int maxY;

    /* y轴 */
    QValueAxis *axisY;

    /* x轴 */
    QValueAxis *axisX;

    /* QList int类型容器 */
    QList<int> data;

    /* QSplineSeries对象(曲线)*/
    QSplineSeries *splineSeries;

    /* QChart图表 */
    QChart *chart;

    /* 图表视图 */
    QChartView *chartView;

    /* 定时器 */
    QTimer *timer;

private slots:
    void timerTimeOut();
};
#endif // MAINWINDOW_H

mainwindow.c:

#include "mainwindow.h"
#include <QDateTime>
MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
{
    /* 设置最显示位置与大小 */
    this->setGeometry(0, 0, 800, 480);
    /* 最大储存maxSize - 1个数据 */
    maxSize = 51;
    /* x轴上的最大值 */
    maxX = 5000;
    /* y轴最大值 */
    maxY = 40;

    /* splineSeries曲线实例化(折线用QLineSeries) */
    splineSeries = new QSplineSeries();
    /* 图表实例化 */
    chart = new QChart();
    /* 图表视图实例化 */
    chartView = new QChartView();

    /* 坐标轴 */
    axisY = new QValueAxis();
    axisX = new QValueAxis();
    /* 定时器 */
    timer = new QTimer(this);

    /* legend译图例类型,以绘图的颜色区分,本例设置为隐藏 */
    chart->legend()->hide();
    /* chart设置标题 */
    chart->setTitle("实时动态曲线示例");
    /* 添加一条曲线splineSeries */
    chart->addSeries(splineSeries);

    /* 设置显示格式 */
    axisY->setLabelFormat("%i");
    /* y轴标题 */
    axisY->setTitleText("温度/℃");
    /* y轴标题位置(设置坐标轴的方向) */
    chart->addAxis(axisY, Qt::AlignLeft);
    /* 设置y轴范围 */
    axisY->setRange(0, maxY);
    /* 将splineSeries附加于y轴上 */
    splineSeries->attachAxis(axisY);

    /* 设置显示格式 */
    axisX->setLabelFormat("%i");
    /* x轴标题 */
    axisX->setTitleText("时间/ms");
    /* x轴标题位置(设置坐标轴的方向) */
    chart->addAxis(axisX, Qt::AlignBottom);
    /* 设置x轴范围 */
    axisX->setRange(0, maxX);
    /* 将splineSeries附加于x轴上 */
    splineSeries->attachAxis(axisX);

    /* 将图表的内容设置在图表视图上 */
    chartView->setChart(chart);
    /* 设置抗锯齿 */
    chartView->setRenderHint(QPainter::Antialiasing);

    /* 设置为图表视图为中心部件 */
    setCentralWidget(chartView);

    /* 定时200ms */
    timer->start(200);
    /* 信号槽连接 */
    connect(timer, SIGNAL(timeout()), this, SLOT(timerTimeOut()));

    /* 设置随机种子,随机数初始化 */
    qsrand(time(NULL));
}

MainWindow::~MainWindow()
{
}

void MainWindow::timerTimeOut()
{
    /* 产生随机0~maxY之间的数据 */
    receivedData(qrand() % maxY );
}

void MainWindow::receivedData(int value)
{
    /* 将数据添加到data中 */
    data.append(value);

    /* 当储存数据的个数大于最大值时,把第一个数据删除 */
    while (data.size() > maxSize) {
        /* 移除data中第一个数据 */
        data.removeFirst();
    }

    /* 先清空 */
    splineSeries->clear();

    /* 计算x轴上的点与点之间显示的间距 */
    int xSpace = maxX / (maxSize - 1);

    /* 添加点,xSpace * i 表示第i个点的x轴的位置 */
    for (int i = 0; i < data.size(); ++i) {
        splineSeries->append(xSpace * i, data.at(i));
    }
}

QT Chart表效果:

3.2 AP3216C的数据读取

我们编写 QT 下的 AP3216C 的应用代码:

ap3216c.h:

#ifndef AP3216C_H
#define AP3216C_H

#include <QObject>
#include <QTimer>

class Ap3216c : public QObject
{
    Q_OBJECT

public:
    explicit Ap3216c(QObject *parent = 0);     //构造函数声明,"explicit"是一个C++关键字,用于修饰构造函数或函数,以禁止隐式转换
    ~Ap3216c();                                 //析构函数

    Q_INVOKABLE void setCapture(bool str);      //Q_INVOKABLE是一个宏,setCapture函数接受一个bool类型的参数str,用于设置某个对象的捕获状态

    QString alsData();      //光强传感器( ALS: Ambient Light Sensor)
    QString psData();       //接近传感器( PS: Proximity Sensor)
    QString irData();       //红外 LED( IR LED)

private:
    QTimer *timer;          //定时器timer
    QString alsdata;        //光强数据
    QString psdata;         //接近数据
    QString irdata;         //红外数据

    QString readAlsData();  //读取Ais数据
    QString readPsData();   //读取Ps数据
    QString readIrData();   //读取IR数据

    Q_PROPERTY(QString alsData READ alsData NOTIFY ap3216cDataChanged)
    Q_PROPERTY(QString psData READ psData NOTIFY ap3216cDataChanged)
    Q_PROPERTY(QString irData READ irData NOTIFY ap3216cDataChanged)
    /*Q_PROPERTY:这是一个宏,用于在类中声明属性。
     *QString:alsData是属性的类型,这里是一个QString类型的变量。
     *READ alsData:这表示该属性可以通过"alsData"方法进行读取。
     *NOTIFY ap3216cDataChanged:这表示当该属性发生变化时,会发出"ap3216cDataChanged"信号
     */

public slots:
    void timer_timeout();

signals:
    void ap3216cDataChanged();
};

#endif // AP3216C_H

ap3216c.c:

#include "ap3216c.h"
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <QDebug>

/* AP3216C的构造函数 */
Ap3216c::Ap3216c(QObject *parent) : QObject (parent)
{
    timer = new QTimer();                                            //定义一个定时器容器
    connect(timer, SIGNAL(timeout()),this,SLOT(timer_timeout()));     //将超时信号,与timer_timeout链接起来
}

/* 析构函数 */
Ap3216c::~Ap3216c()
{

}

/* 超时函数 */
void Ap3216c::timer_timeout()
{
    alsdata = readAlsData();
    psdata = readPsData();
    irdata = readIrData();
    emit ap3216cDataChanged();      //发送ap3216c数值变化信息
}

QString Ap3216c::readIrData()
{
    char const *filename = "/sys/class/misc/ap3216c/ir";
    int err = 0;
    int fd;
    char buf[10];

    fd = open(filename,O_RDONLY);      //只读模式打开文件
    if(fd < 0){
        close(fd);
        return "open file error!";
    }

    err = read(fd,buf,sizeof(buf));
    if(err < 0){
        close(fd);
        return "read data error!";
    }
    close(fd);

    QString irValue = buf;
    QStringList list = irValue.split("\n");     //将irValue按照换行符"\n"分割成一个字符串列表,并将结果存储在list对象中
    return list[0];
}

QString Ap3216c::readPsData()
{
    char const *filename = "/sys/class/misc/ap3216c/ps";
    int err = 0;
    int fd;
    char buf[10];

    fd = open(filename,O_RDONLY);       //只读模式打开文件
    if(fd < 0){
        close(fd);
        return "open file error!";
    }

    err = read(fd,buf,sizeof(buf));
    if(err < 0){
        close(fd);
        return "read data error!";
    }
    close(fd);

    QString irValue = buf;
    QStringList list = irValue.split("\n");     //将irValue按照换行符"\n"分割成一个字符串列表,并将结果存储在list对象中
    return list[0];
}

QString Ap3216c::readAlsData()
{
    char const *filename = "/sys/class/misc/ap3216c/als";
    int err = 0;
    int fd;
    char buf[10];

    fd = open(filename,O_RDONLY);       //只读模式打开文件
    if(fd < 0){
        close(fd);
        return "open file error!";
    }

    err = read(fd,buf,sizeof(buf));
    if(err < 0){
        close(fd);
        return "read data error!";
    }
    close(fd);

    QString irValue = buf;
    QStringList list = irValue.split("\n");     //将irValue按照换行符"\n"分割成一个字符串列表,并将结果存储在list对象中
    return list[0];
}

QString Ap3216c::alsData()
{
    return alsdata;
}

QString Ap3216c::irData()
{
    return irdata;
}

QString Ap3216c::psData()
{
    return psdata;
}

void Ap3216c::setCapture(bool str)
{
    if(str)
        timer->start(500);
    else
        timer->stop();
}

“Linux 系统之下一切皆文件”,上述代码就是通过 C 语言库函数去读取系统下的属性数值信息来做到获取 AP3216C 的数据值!

3.3 AP3216C与Chart联动

我们将 AP3216C Chart 进行联动配合,通过 Chart 实时读取显示 AP3216C ALS 属性。

mainwindow.h:

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QComboBox>
#include <QPushButton>
#include <QVBoxLayout>
#include <QLabel>
#include <QScrollArea>
#include <QDebug>
#include <QChartView>
#include <QSplineSeries>
#include <QScatterSeries>
#include <QDebug>
#include <QValueAxis>
#include "ap3216c.h"

/*  必需添加命名空间 */
QT_CHARTS_USE_NAMESPACE

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

private:
    /* 主容器,Widget也可以当作一种容器 */
    QWidget *mainWidget;

    /* 界面水平区域布局 */
    QHBoxLayout *hboxLayout[2];

    /* 界面右侧区域布局 */
    QVBoxLayout *vboxLayout[4];

    /* 界面右侧区域容器 */
    QWidget *rightWidget;

    /* 用一个 QLabel 对象用于显示字符串 */
    QLabel *labelString;

    /* 容器作用,用于布局 */
    QWidget *widget[5];

    /* 标签文本 */
    QLabel *label[3];

    /* 数据标签 */
    QLabel *my_label[3];

    /* 数据最大个数 */
    int maxSize;

    /* x轴上的最大值 */
    int maxX;

    /* y轴上的最大值 */
    int maxY;

    /* y轴 */
    QValueAxis *axisY;

    /* x轴 */
    QValueAxis *axisX;

    /* QList int类型容器 */
    QList<int> data;

    /* QSplineSeries对象(曲线)*/
    QSplineSeries *splineSeries;

    /* QChart图表 */
    QChart *chart;

    /* 图表视图 */
    QChartView *chartView;

    /* ii2传感器类 */
    Ap3216c *ap3216c;

    /* 接收数据接口 */
    void receivedData(int);

    /* 布局初始化 */
    void layoutInit();


private slots:
    /* 获取ap3216传感器数据 */
    void getAp3216cData();
};
#endif // MAINWINDOW_H

mainwindow.c:

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QGuiApplication>
#include <QScreen>
#include <QDebug>

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
{
    /* 布局初始化 */
    layoutInit();
}

MainWindow::~MainWindow()
{
}

/* 布局初始化 */
void MainWindow::layoutInit()
{
    /* 获取屏幕的分辨率,Qt官方建议使用这
     * 种方法获取屏幕分辨率,防上多屏设备导致对应不上
     * 注意,这是获取整个桌面系统的分辨率
     */
    QList <QScreen *> list_screen = QGuiApplication::screens();

    /* 如果是ARM平台,直接设置大小为屏幕的大小 */
#if __arm__
    /* 重设大小 */
    this->resize(list_screen.at(0)->geometry().width(),
                 list_screen.at(0)->geometry().height());
#else
    /* 否则则设置主窗体大小为800x480 */
    this->resize(800,400);
#endif

    /* 实例化与布局,常规操作 */
    mainWidget = new QWidget();
    rightWidget = new QWidget();

    for(int i = 0; i < 2; i++)          //水平布局
        hboxLayout[i] = new QHBoxLayout();

    for(int i = 0; i < 5; i++)          //容器布局
        widget[i] = new QWidget();

    for(int i = 0; i < 4; i++)          //垂直布局
        vboxLayout[i] = new QVBoxLayout();

    for(int i = 0; i < 3; i++)          //数据变量
        my_label[i] = new QLabel();


/********************** chart表格 *****************************/
    /* 最大储存maxSize - 1个数据 */
    maxSize = 51;
    /* x轴上的最大值 */
    maxX = 5000;
    /* y轴最大值 */
    maxY = 40;

    /* splineSeries曲线实例化(折线用QLineSeries) */
    splineSeries = new QSplineSeries();
    /* 图表实例化 */
    chart = new QChart();
    /* 图表视图实例化 */
    chartView = new QChartView();

    /* 坐标轴 */
    axisY = new QValueAxis();
    axisX = new QValueAxis();

    /* legend译图例类型,以绘图的颜色区分,本例设置为隐藏 */
    chart->legend()->hide();
    /* chart设置标题 */
    chart->setTitle("环境光强度");
    /* 添加一条曲线splineSeries */
    chart->addSeries(splineSeries);

    /* 设置显示格式 */
    axisY->setLabelFormat("%i");
    /* y轴标题 */
    axisY->setTitleText("光通亮/Lux");
    /* y轴标题位置(设置坐标轴的方向) */
    chart->addAxis(axisY, Qt::AlignLeft);
    /* 设置y轴范围 */
    axisY->setRange(0, maxY);
    /* 将splineSeries附加于y轴上 */
    splineSeries->attachAxis(axisY);

    /* 设置显示格式 */
    axisX->setLabelFormat("%i");
    /* x轴标题 */
    axisX->setTitleText("时间/ms");
    /* x轴标题位置(设置坐标轴的方向) */
    chart->addAxis(axisX, Qt::AlignBottom);
    /* 设置x轴范围 */
    axisX->setRange(0, maxX);
    /* 将splineSeries附加于x轴上 */
    splineSeries->attachAxis(axisX);

    /* 将图表的内容设置在图表视图上 */
    chartView->setChart(chart);
    /* 设置抗锯齿 */
    chartView->setRenderHint(QPainter::Antialiasing);
/********************** chart表格 *****************************/

    /* 设置传感器数据标签 */
    QFont font;
    font.setPixelSize(18);

    QPalette pal;
    pal.setColor(QPalette::WindowText, Qt::black);

    QStringList list;
    list<<"环境光强度:"<<"接近距离:"<<"红外强度:";
    for (int i = 0; i < 3; i++) {
        label[i] = new QLabel();
        label[i]->setText(list[i]);
        label[i]->setFont(font);
        label[i]->setPalette(pal);
        label[i]->adjustSize();
    }

    /* 垂直容器布局 */
    vboxLayout[3]->addWidget(widget[0]);
    vboxLayout[3]->addWidget(widget[1]);
    vboxLayout[3]->addWidget(widget[2]);

    hboxLayout[0]->addWidget(chartView);        //将chart表格添加到水平布局中

    widget[3]->setLayout(hboxLayout[0]);
    widget[4]->setLayout(vboxLayout[3]);

    hboxLayout[1]->addWidget(widget[3]);
    hboxLayout[1]->addWidget(widget[4]);

    mainWidget->setLayout(hboxLayout[1]);

    this->setCentralWidget(mainWidget);

    /* als布局 */
    vboxLayout[0]->addWidget(label[0]);
    vboxLayout[0]->addWidget(my_label[0]);
    vboxLayout[0]->setAlignment(Qt::AlignTop | Qt::AlignVCenter);
    widget[0]->setLayout(vboxLayout[0]);

    /* ps布局 */
    vboxLayout[1]->addWidget(label[1]);
    vboxLayout[1]->addWidget(my_label[1]);
    vboxLayout[1]->setAlignment(Qt::AlignTop | Qt::AlignVCenter);
    widget[1]->setLayout(vboxLayout[1]);

    /* ir布局 */
    vboxLayout[2]->addWidget(label[2]);
    vboxLayout[2]->addWidget(my_label[2]);
    vboxLayout[2]->setAlignment(Qt::AlignTop | Qt::AlignVCenter);
    widget[2]->setLayout(vboxLayout[2]);

    ap3216c = new Ap3216c(this);

    /* 只能在开发板上开启获取数据,Ubuntu上是没有ap3216c传感器的 */
#if __arm__
    ap3216c->setCapture(true);
#endif

    connect(ap3216c,SIGNAL(ap3216cDataChanged()),this,SLOT(getAp3216cData()));
}

void MainWindow::getAp3216cData()
{
    static QString als = ap3216c->alsData();
//    if (als != ap3216c->alsData()) {
//        als = ap3216c->alsData();
//    }
    als = ap3216c->alsData();

    static QString ps = ap3216c->psData();
//    if (ps != ap3216c->psData()) {
//        ps = ap3216c->psData();
//    }

    ps = ap3216c->psData();

    static QString ir = ap3216c->irData();
//    if (ir != ap3216c->irData()) {
//        ir = ap3216c->irData();
//    }

    ir = ap3216c->irData();

    my_label[0]->setText(als);
    my_label[1]->setText(ps);
    my_label[2]->setText(ir);


/**************** chart表数据 ******************/

    int num = als.toInt();      //字符串转int类型数据

    /* 将数据添加到data中 */
    data.append(num);

    /* 当储存数据的个数大于最大值时,把第一个数据删除 */
    while (data.size() > maxSize) {
        /* 移除data中第一个数据 */
        data.removeFirst();
    }

    /* 先清空 */
    splineSeries->clear();

    /* 计算x轴上的点与点之间显示的间距 */
    int xSpace = maxX / (maxSize - 1);

    /* 添加点,xSpace * i 表示第i个点的x轴的位置 */
    for (int i = 0; i < data.size(); ++i) {
        splineSeries->append(xSpace * i, data.at(i));
    }

}

AP3216CChart 联动的程序主要是进行 QT 的容器布局,通过 layoutInit() 函数进行布局初始化(UI设计部分),之后通过定时 timer 去定时读取 ap3216c 的传感器数据,在读取 ap3216c 的数据之后,通过 Chart 进行绘制出来!

实际项目:

四、项目效果

AP3216C+QT+Chart

五、项目代码

代码地址:基于IMX6ULL的AP3216C的QT动态数据曲线图显示代码资源-CSDN文库

如果积分不够的朋友,点波关注评论区留下邮箱,作者无偿提供源码和后续问题解答。求求啦关注一波吧 !!!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/39100.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

聊聊ChatGPT是如何组织对话的

为什么要组织对话&#xff1f; 总所周知&#xff0c;ChatGPT的训练大致可分为下图中展示的几个阶段&#xff0c;其中&#xff0c;在Pretraining阶段&#xff0c;模型的训练数据是纯文本&#xff0c;目标是根据上文预测下一个token&#xff0c;而在后面的几个阶段中&#xff0c…

【软件测试】selenium中元素的定位

1.元素的定位 不管用那种方式&#xff0c;必须保证页面上该属性的唯一性 1.CSS 定位 CSS(Cascading Style Sheets)是一种语言&#xff0c;它被用来描述HTML 和XML 文档的表现。 CSS 使用选择器来为页面元素绑定属性。这些选择器可以被selenium 用作另外的定位策略CSS的获取可…

scrapy ---分布式爬虫

导模块 pip install scrapy-redis 原来scrapy的Scheduler维护的是本机的任务队列&#xff08;待爬取的地址&#xff09;本机的去重队列&#xff08;放在集合中&#xff09;---》在本机内存中 如果把scrapy项目&#xff0c;部署到多台机器上&#xff0c;多台机器爬取的内容是重…

静态数码管——FPGA

文章目录 前言一、数码管1、数码管简介2、共阴极数码管or共阳极数码管3、共阴极与共阳极的真值表 二、系统设计1、模块框图2、RTL视图 三、源码1、seg_led_static模块2、time_count模块3、top_seg_led_static(顶层文件) 四、效果五、总结六、参考资料 前言 环境&#xff1a; 1、…

我爱学QT--qt的网络编程

学习地址&#xff1a; QT网络编程之TCP通信_哔哩哔哩_bilibili QT网络编程有TCP和UDP。 TCP编程需要用到两个类&#xff1a;QTcpServer和QTcpSocket 本节课目标&#xff1a; 完成一个服务器和一个客户端 首先是经典的几步 先设计ui再设计逻辑实现 多看看写的文件理解吧

【淘宝API接口系列】,商品详情接口响应参数有哪些?响应示例是否满足

商品数据&#xff1a;淘宝提供了商品的基本信息&#xff0c;包括商品名称、描述、规格、价格、销量、库存等信息。此外&#xff0c;也可以通过淘宝提供的API接口来获取商品的图片、评价、物流信息等详细数据。 响应参数 Version: Date:2022-04-04 名称类型必须示例值描述 ite…

数据库小白看这里,这个Oracle数据库知识图谱你值得拥有

2022年前后&#xff0c;墨天轮社区曾陆续推出PostgreSQL知识图谱、MySQL知识图谱&#xff0c;并得到了大家的广泛好评。此后&#xff0c;便有众多朋友对Oracle知识图谱发起不断“催更“。经过近期的内容搜集整合、专家复审与打磨&#xff0c;墨天轮社区正式推出Oracle知识图谱&…

Mac搭建安卓模拟器(支持M1/M2)

引言 最近在研究Vue打包成app&#xff0c;给我的报价器搞一个移动端&#xff0c;奈何没有安卓手机用于测试。所以想到安装一个安卓模拟器。 看了下目前主流的安卓模拟器基本都不支持Mac版本。网易的mumu目前来看还是只支持Intel芯。 1. 简单版&#xff08;仅M系&#xff09;…

目标检测——R-CNN网络基础

目录 Overfeat模型RCNN模型算法流程候选区域生成CNN网络提取特征目标分类&#xff08;SVM&#xff09;目标定位预测过程 算法总结 Fast RCNN模型算法流程ROI Pooling目标分类和回归 模型训练模型总结 Overfeat模型 RCNN模型 算法流程 候选区域生成 CNN网络提取特征 目标分类&am…

掘金量化—Python SDK文档—3.变量约定

目录 Python SDK文档 3.变量约定 3.1 symbol - 代码标识 3.1.1交易所代码 3.1.2交易标的代码 3.1.3symbol 示例 3.1.4期货主力连续合约 3.2mode - 模式选择 3.2.1实时模式 3.2.2回测模式 3.3context - 上下文对象 3.3.1context.symbols - 订阅代码集合 3.3.2context.now - 当…

Docker 镜像解密:分层存储与构建原理多角度解析

&#x1f337;&#x1f341; 博主 libin9iOak带您 Go to New World.✨&#x1f341; &#x1f984; 个人主页——libin9iOak的博客&#x1f390; &#x1f433; 《面试题大全》 文章图文并茂&#x1f995;生动形象&#x1f996;简单易学&#xff01;欢迎大家来踩踩~&#x1f33…

实现将redis中的所有数据备份到数据库表中

实现将redis中的所有数据备份到数据库表中 /*** 将redis中的数据同步到mysql中*/Overridepublic void saveMysqlForRedis(){Jedis jedis new Jedis("127.0.0.1", 6379);//System.out.println(jedis.ping());Set keys jedis.keys("*");//列出所有的keyI…

直流有刷电机

直流有刷电机 直流有刷电机(Brushed DC motor) 具有结构简单、易于控制、成本低等特点&#xff0c;在一些功能简单的应用场合&#xff0c;或者说在能够满足必要的性能、低成本和足够的可靠性的前提下&#xff0c;直流有刷电机往往是一个很好的选择。例如便宜的电子玩具、各种风…

【分布式训练】基于Pytorch的分布式数据并行训练

基于Pytorch的分布式数据并行训练 动机为什么要并行分布数据&#xff1f;现有资料的不足 Outline整体框架图带解释的最小demo示例没有multiprocessing开启multiprocessing 分布式训练启动方式混合精度训练&#xff08;采用apex&#xff09; 参考资料 简介&#xff1a; 在PyTorc…

Element-UI 实现动态增加多个不同类型的输入框并校验(双重v-for表单验证)

文章目录 前言定义表单格式表单渲染和验证扩展 前言 在做复杂的动态表单&#xff0c;实现业务动态变动&#xff0c;比如有一条需要动态添加的el-form-item中包含了多个输入框&#xff0c;并实现表单验证&#xff0c;但在element-ui组件库中给出的表单校验中没有这样的格式&…

Day977.除了授权码许可类型,OAuth 2.0还支持什么授权流程? -OAuth 2.0

除了授权码许可类型&#xff0c;OAuth 2.0还支持什么授权流程&#xff1f; Hi&#xff0c;我是阿昌&#xff0c;今天学习记录的是关于除了授权码许可类型&#xff0c;OAuth 2.0还支持什么授权流程&#xff1f;的内容。 授权码许可的流程最完备、最安全没错儿&#xff0c;但它…

【Jenkins】Jenkins构建前端流水线

目录 一、前言二、新建前端流水线1、点击新建任务2、填写流水线名称&#xff08;这里我选择的是自由风格的软件项目&#xff09;&#xff0c;任务名称一般格式为&#xff1a;项目名称-前后端3、创建成功后的结果 三、配置前端流水线1、进入刚创建好的任务页面中&#xff0c;点击…

音视频H265编码; Socket通信实现N对N连接代码示例

H.265编码和Socket通信是两个不同的概念&#xff0c;它们分别涉及视频编码和网络通信。在实现N对N连接时&#xff0c;您可以将它们结合起来&#xff0c;但要注意每个方面的具体实现。 H.265编码&#xff08;视频编码&#xff09;&#xff1a; H.265编码涉及将视频数据进行压缩…

分布式文件系统与HDFS的shell操作及查看元数据

启动hadoop和hive的metastore查看sbin的目录下的文件 执行./start-all.sh 查看相关的进程

C语言 register关键字与“傲娇的编译器”

1.作用&#xff1a; “建议”编译器把变量放到寄存器内&#xff0c;编译器不一定听你的&#xff08;傲娇&#xff09;&#xff01;&#xff01; 2.速度&#xff1a;寄存器>高速缓存>内存&#xff0c;寄存器空间很宝贵&#xff08;非常非常少&#xff09; 3.寄存器不支持取…