Linux驱动(中断、异步通知):红外对射,并在Qt StatusBus使用指示灯进行显示

news2024/11/26 11:58:21

本文工作:

1、Linux驱动与应用程序编写:使用了设备树、中断、异步通知知识点,实现了红外对射状态的异步信息提醒

2、QT程序编写:自定义了一个“文本指示灯”类,并放置在QMainWidget的StatusBus中。

3、C与C++混合编程与调试:将Linux C下的面向过程转化为QT C++的面向对象。但是Linux C下signal函数需要使用static函数作为传入参数,但是C++中static的用法与C语言不一样,因此在C++中对抽象出的类进行了适配与修改

参考资料

1、《正点原子:I.MX6U嵌入式Linux驱动开发指南V1.6》

2、C/C++ static关键字详解(最全解析,static是什么,static如何使用,static的常考面试题)-CSDN博客

3、【创龙AM4379 Cortex-A9试用体验】之I/O中断异步通知驱动程序+QT捕获Linux系统信号+测试信号通知 - ARM技术论坛 - 电子技术论坛 - 广受欢迎的专业电子论坛! (elecfans.com)

4、Qt 封装一个简单的LED(指示灯)控件_qt 指示灯-CSDN博客

本文不对基础知识进行讲解,因此阅读本文需要一定的驱动/QT基础。

开发板:IMX6ULL

内核版本:4.1.15

QT版本:5.12

一、项目需求与效果

1、项目需求:

项目中的一个简单传感器:使用红外对射,检查物品是否放入了盒子中,并在QT界面的statusBus中,使用指示灯的形式进行显示与说明。

2、代码效果

红外对射-Linux驱动-QT

代码效果:屏幕左下角文本指示灯的变化

二、Linux驱动与应用程序编写

1、使用引脚说明

本次所使用的引脚为GPIO2_2,查看了I.MX6ULL核心板对GPIO的描述,该引脚可复用到GPIO1_IO02引脚上,因此我们需要先修改下设备树的文件。

2、修改设备树文件

在根节点目录下先添加节点:

gpio_infrared {
		compatible = "csx-gpio-infrared";
		pinctrl-names = "default";
		pinctrl-0 = <&pinctrl_infrared>;
		infrared-gpio = <&gpio1 2 GPIO_ACTIVE_LOW>;
		interrupts = < IRQ_TYPE_EDGE_BOTH>;
		status = "okay";
	};

项目中,我们需要极度关注是否有物体存入,进拿进取出都需要通知用户,因此需要中断类型使用IRQ_TYPE_EDGE_BOTH。

其中的pinctrl_infrared为:

pinctrl_infrared: infraredgrp {
		fsl,pins = <
		MX6UL_PAD_GPIO1_IO02__GPIO1_IO02 0x10B0
		>;
		};	

修改完之后,执行make dtbs,更换开发板的设备树之后,我们使用 ls /proc/device-tree查看是否有我们增加的设备树节点。

如上图,设备树修改成功,接下来就是编写驱动文件。

3、驱动文件编写

(1)结构体编写

/* 设备中断结构体 */
struct irq_gpio_infrared{
	int gpio;				/* gpio */
	int irqnum;				/* 中断号 */
	unsigned char value;	/* 读取的值 */
	char name[10];			/* 名字 */
	irqreturn_t (*handler)(int,void *); /* 中断服务函数 */
	
};

/* gpioinfrared设备结构体 */
struct gpio_infrared_dev{
	dev_t devid;			/* 设备号 	 */
	struct cdev cdev;		/* cdev 	*/
	struct class *class;	/* 类 		*/
	struct device *device;	/* 设备 	 */
	int major;				/* 主设备号	  */
	int minor;				/* 次设备号   */
	struct device_node	*nd; /* 设备节点 */
	struct irq_gpio_infrared irqgpioinfrared;	/* 中断结构体 */
	struct fasync_struct *async_queue; /* 异步相关结构体 */
};


struct gpio_infrared_dev gpioinfrared;	/* infrared设备 */

代码中我们需要注意的是,使用了中断结构体,用于红外对射,检测到物体时通知用户,在各种通知方法中,使用异步通知是最可行的,其他方法需要占据大量的cpu资源,不适合本次的应用场景,因此,中断服务其实就是使用异步通知对用户进行通知,因此,我们还需要使用异步通知的结构体。

(2)获取设备树节点的信息【设备树API、GPIO子系统API】

	/* 1、获取设备节点:gpioinfrared */
	gpioinfrared.nd = of_find_node_by_path("/gpio_infrared");
	if(gpioinfrared.nd == NULL) {
		printk("gpioinfrared node not find!\r\n");
		return -EINVAL;
	} else {
		printk("gpioinfrared node find!\r\n");
	}

	/* 2、 获取设备树中的gpio属性,得到GPIO所使用的编号 */
	gpioinfrared.irqgpioinfrared.gpio = of_get_named_gpio(gpioinfrared.nd, "infrared-gpio", 0);
	if(gpioinfrared.irqgpioinfrared.gpio < 0) {
		printk("can't get infrared-gpio");
		return -EINVAL;
	}else{
		printk("infrared-gpio num = %d\r\n", gpioinfrared.irqgpioinfrared.gpio);
	}
	
	/* 3、初始化使用的gpio */
	memset(gpioinfrared.irqgpioinfrared.name,0,sizeof(gpioinfrared.irqgpioinfrared.name));
	sprintf(gpioinfrared.irqgpioinfrared.name,"gpioinfrared%d",i);
	ret = gpio_direction_input(gpioinfrared.irqgpioinfrared.gpio);
	if(ret < 0) {
		printk("can't set gpio!\r\n");
	}

(3)中断申请

	/* 4、设置中断模式,返回中断号 */
	gpioinfrared.irqgpioinfrared.irqnum = gpio_to_irq(gpioinfrared.irqgpioinfrared.gpio);
	printk("gpio:%d,irq:%d",gpioinfrared.irqgpioinfrared.gpio,gpioinfrared.irqgpioinfrared.irqnum);
	/* 申请中断 */
	gpioinfrared.irqgpioinfrared.handler = goio_infrared_handler;
	ret = request_irq(gpioinfrared.irqgpioinfrared.irqnum, 
					  gpioinfrared.irqgpioinfrared.handler, 
		              IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING,
					  gpioinfrared.irqgpioinfrared.name, 
					  &gpioinfrared);
	if(ret < 0){
		printk("irq %d request fail infrared!\r\n", gpioinfrared.irqgpioinfrared.irqnum);
		printk("irq_name: %s\r\n",gpioinfrared.irqgpioinfrared.name);
		return -EFAULT;
	}
	if (ret==-EBUSY){
		printk("irq already apply\r\n");
	}

(4)编写红外对射的中断函数

/*
	GPIO红外中断服务函数,用于发送异步信息
*/
static irqreturn_t goio_infrared_handler(int irq,void *dev_id)
{
	struct gpio_infrared_dev *dev = (struct gpio_infrared_dev *)dev_id;

	printk("irq happend!\r\n");
	kill_fasync(&dev->async_queue, SIGIO, POLL_IN);
	return IRQ_RETVAL(IRQ_HANDLED);
}

如上述所说,就是用于发送异步通信的!在这里还使用内核进行打印输出,方便检测。

(5)编写异步通信函数

static int imx6uirq_fasync(int fd, struct file *filp, int on)
{
	struct gpio_infrared_dev *dev = (struct gpio_infrared_dev *)filp->private_data;
	return fasync_helper(fd, filp, on, &dev->async_queue);
}

static int imx6uirq_release(struct inode *inode, struct file *filp)
{
	return imx6uirq_fasync(-1, filp, 0);
}

(6)编写用户层数据接口

/*
 * @description		: 从设备读取数据 
 * @param - filp 	: 要打开的设备文件(文件描述符)
 * @param - buf 	: 返回给用户空间的数据缓冲区
 * @param - cnt 	: 要读取的数据长度
 * @param - offt 	: 相对于文件首地址的偏移
 * @return 			: 读取的字节数,如果为负值,表示读取失败
 */
static ssize_t infrared_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
	int ret = 0;
	unsigned char value=-1;
	struct gpio_infrared_dev *dev = (struct gpio_infrared_dev *)filp->private_data;
	value = gpio_get_value(dev->irqgpioinfrared.gpio);
	printk("gpioinfrared read:%d!\r\n",value);
	ret = copy_to_user(buf, &value, sizeof(value));
	return ret;
}

重点的内容都介绍完了,接下来就是检查下是否正确驱动编写是否正确了!make生成.ko文件后,传到开发板,使用insmod命令,会有如下输入:

此时,我们将红外进行对射后,就会有如下输出:

即驱动编写正确。接下来,我们先编写应用程序的代码,接收异步通知的提醒。

4、Linux C应用程序代码编写

应用程序接收异步通知需要以下三个步骤:

(1)使用signal函数设置(驱动程序所使用的)信号处理函数;

(2)将当前应用程序的内核号告知内核

(3)开启异步通知

signal(SIGIO, sigio_signal_func);   /* 设置信号SIGIO的处理函数     */
fcntl(fd, F_SETOWN, getpid());		/* 设置当前进程接收SIGIO信号   */
flags = fcntl(fd, F_GETFL);			/* 获取当前的进程状态 		  */
fcntl(fd, F_SETFL, flags | FASYNC);	/* 设置进程启用异步通知功能 	  */	
在此处的异步处理函数就很简单了,使用GPIO子系统的API获取当前的引脚数值,但是值得一提的是, signal函数的传入函数必须是使用static进行修饰的,在后面与C++代码对接时,就是因为static增加了不少的限制
/*
 * SIGIO信号处理函数
 * @param - signum 	: 信号值
 * @return 			: 无
 */
static void sigio_signal_func(int signum)
{
	int err = 0;
	unsigned int infrared_value = 0;
	err = read(fd, &infrared_value, sizeof(infrared_value));
	if(err < 0) {
		/* 读取错误 */
		printf("read error!\r\n");
	} else {
		printf("sigio signal:%d!\r\n",infrared_value);
	
	}
	
}

接下来,我们在进行交叉编译之后

arm-linux-gnueabihf-gcc infrared_asy.c -o infrared_asy

进行测试一下。

上图所示,测试成功!

三、C++ Qt程序编写

1、自定义LedWithText

 先根据自己的需要,对参考资料第四点对自封装的LED控件进行了简化,类名为QSimpleLed

QSimpleLed.cpp文件

#include "qsimpleled.h"

#include <QGradient>
#include <QPainter>
#include <QDebug>
#include <QColor>


QSimpleLed::QSimpleLed(QWidget *parent, QColor color,int radius)
    : QLabel(parent)
    , mColor(color)
{
    setMinimumSize(radius, radius);
    setMaximumSize(radius,radius);
}

void QSimpleLed::setStates(QSimpleLed::LEDSTATES states)
{
    switch (states) {
    case ON:
        resetStatus();

        mStates = ON;
        break;

    case OFF:
        resetStatus();
        break;

    case BLINK:
        resetStatus();

        if (!mBlinkTimer) {
            mBlinkTimer = new QTimer(this);
            connect(mBlinkTimer, &QTimer::timeout, this, &QSimpleLed::onBlinkTimerTimeout);
        }
        mBlinkTimer->setInterval(mInterval);
        mBlinkTimer->start();
        mStates = BLINK;
        break;

    default:
        qDebug() << "LED - unknown states!!!";
    }

    update();
}

void QSimpleLed::setColor(QColor color)
{
    mColor = color;
}



void QSimpleLed::paintEvent(QPaintEvent *event)
{
    Q_UNUSED(event);

    qreal realSize = qMin(width(), height());
    QPainter painter(this);

    painter.setRenderHint(QPainter::Antialiasing);      // 反锯齿
    painter.translate(width()/2, height()/2);           // 绘点移到控件中心处


    if(blinkState)
        painter.setBrush(QBrush(QColor(111,111,111)));
    else
        painter.setBrush(QBrush(mColor));

    painter.drawEllipse(QPointF(0, 0), realSize/2, realSize/2);

}

void QSimpleLed::resizeEvent(QResizeEvent *event)
{
    Q_UNUSED(event);
    update();
}



void QSimpleLed::onBlinkTimerTimeout()
{
    blinkState = !blinkState;
    update();
}

void QSimpleLed::resetStatus()
{
    if (mBlinkTimer && mBlinkTimer->isActive()) {
        mBlinkTimer->stop();
    }
    mStates = OFF;
}

QSimpleLed.h文件

#ifndef QSIMPLELED_H
#define QSIMPLELED_H

#include <QLabel>
#include <QTimer>
#include <QColor>

class QSimpleLed : public QLabel
{
    Q_OBJECT


public:

    enum LEDSTATES {
        ON       = 0,
        OFF      = 1,
        BLINK    = 2,
    };


    QSimpleLed(QWidget *parent = nullptr, QColor color = Qt::green,int radius=16);

    void setStates(LEDSTATES states);
    void setInterval(int msec) { mInterval = msec; }
    void setColor(QColor color);

protected:
    virtual void paintEvent(QPaintEvent *event) override;
    virtual void resizeEvent(QResizeEvent *event) override;

private slots:
    void onBlinkTimerTimeout();

private:
    void resetStatus();

    LEDSTATES mStates = OFF;
    QColor mColor;
    QTimer *mBlinkTimer = nullptr;
    int mInterval = 500;
    bool blinkState = false;

};



#endif // QSIMPLELED_H

再根据自己的需求,自定义一个widget,用于存放led(左)与text(右),我们在外部只需要传入led的状态、颜色、文本的内容即可,该类的名称为LedWithText。

LedWithText.cpp

#include "ledwithtext.h"
#include <QFont>
#include <QDebug>

ledWithText::ledWithText(QWidget *parent, QColor color, const QString &text)
    :QWidget(parent)
{


    ledLabel = new QSimpleLed(this);
    ledLabel->setColor(color);
//    ledLabel->setStyleSheet("border-width: 1px;border-style: solid;border-color: rgb(255, 170, 0);");

    textLabel = new QLabel(text,this);
    textLabel->adjustSize();
    textLabel->setAlignment(Qt::AlignVCenter);
    textLabel->setMinimumHeight(ledLabel->height()+5);
    textLabel->setVisible(!text.isEmpty());

    QFont ft;
    ft.setPointSize(14);
    textLabel->setFont(ft);

    layout = new QHBoxLayout(this);
    layout->addWidget(ledLabel);
    layout->addWidget(textLabel);
    layout->setSpacing(5);
    setLayout(layout);

    int adjustWith = ledLabel->width()+textLabel->width()+25;
    int adjustHeight = textLabel->height()+10;
//    qDebug()<<adjustHeight<<adjustWith;
    setGeometry(0,0,adjustWith,adjustHeight);
}

ledWithText::~ledWithText()
{
    delete  ledLabel;
    delete  textLabel;
}

void ledWithText::setLedColor(QColor color)
{
    ledLabel->setColor(color);
}

void ledWithText::setLedState(QSimpleLed::LEDSTATES states)
{
    ledLabel->setStates(states);
}



void ledWithText::setText(const QString &text)
{
    textLabel->setText(text);
    textLabel->adjustSize();
    textLabel->setVisible(!text.isEmpty());
    int adjustWith = ledLabel->width()+textLabel->width()+25;
    int adjustHeight = textLabel->height()+10;
    setGeometry(0,0,adjustWith,adjustHeight);
}

void ledWithText::setTextFontSize(int size)
{
    QFont ft;
    ft.setPointSize(size);
    textLabel->setFont(ft);
    textLabel->adjustSize();
    int adjustWith = ledLabel->width()+textLabel->width()+25;
    int adjustHeight = textLabel->height()+10;
    setGeometry(0,0,adjustWith,adjustHeight);
}

LedWithText.h

#ifndef LEDWITHTEXT_H
#define LEDWITHTEXT_H
#include <QLabel>
#include <ui_optimize/qsimpleled.h>
#include <QWidget>
#include <QHBoxLayout>

class ledWithText:public QWidget
{
    Q_OBJECT
public:
    ledWithText(QWidget *parent = nullptr, QColor color = Qt::green, const QString &text = "");
    ~ledWithText();
    void setLedColor(QColor color);
    void setLedState(QSimpleLed::LEDSTATES states);
    void setText(const QString &text);
    void setTextFontSize(int size);


private:
    QSimpleLed *ledLabel;
    QLabel *textLabel;
    QHBoxLayout *layout;
};

#endif // LEDWITHTEXT_H

2、使用MainWidget的StatusBus存放文本指示灯

我们要把这些控件放置在状态栏中,不管切换到什么界面都可以看到,就不需要一直重复写代码了,因此,我们需要使用MainWidget的StatusBus,Widget类没有StatusBus!不要用错

void MainWindow::state_bus_init()
{
    device_connect_label = new ledWithText(this,Qt::green,"设备正在连接");
    device_connect_label->setLedColor(Qt::blue);
    device_connect_label->setLedState(QSimpleLed::BLINK);
    device_connect_label->setTextFontSize(10);

    ui->statusbar->addWidget(device_connect_label);
    device_place_label = new ledWithText(this,Qt::green,"设备未放置");
    device_place_label->setTextFontSize(10);
    ui->statusbar->addWidget(device_place_label);
}

 就可以看到如下效果了。我们自定义的LedWithText类的效果就显示在左下角了。

3、Qt接受异步通知,并根据结果改变文本指示灯

按道理,最简单的做法,这一步只需要将应用层的代码搬过来就可以,但是在上文我们提到,在Linux C的应用程序中,signal函数需要传入static修饰的函数,但是Qt使用C++语言,想要将其转化为类(而不是面向过程),就需要解决static在C语言和C++的差异。

#include "infrareddev.h"


InfraredDev::InfraredDev(QObject *parent) : QObject(parent)
{
    fd = open(DEVNAME, O_RDWR);
    if (fd < 0) {
        qDebug()<<"Can't open file :"<<DEVNAME;

    }else{
        register_infrared_asy();
        qDebug()<<"infrared:init succeed!";
    }

}

InfraredDev::~InfraredDev()
{
    if(fd){
        close(fd);
    }
}

void InfraredDev::set_dev_name(char *name)
{
    dev_name = name;
}

char *InfraredDev::get_dev_name()
{
    return dev_name;
}

void InfraredDev::setLedWithText(ledWithText *obj)
{
    infrared_ledWithText = obj;
}

void InfraredDev::sigio_signal_func(int signum)
{
    int err = 0;
    unsigned int infrared_value = 0;
    err = read(fd, &infrared_value, sizeof(infrared_value));
    if(err < 0) {
//        InfraredDev instance; // 创建对象实例
//        emit instance.infraredSignal(infrared_value);
        /* 读取错误 */
        infrared_ledWithText->setLedColor(Qt::red);
        infrared_ledWithText->setLedState(QSimpleLed::BLINK);
        infrared_ledWithText->setText("获取设备失败");
        qDebug()<<"infrared:read error!";
    } else {
        if(infrared_value==0){
            infrared_ledWithText->setLedColor(Qt::green);
            infrared_ledWithText->setText("设备已放置");
        }else if(infrared_value==1){
            infrared_ledWithText->setLedColor(Qt::red);
            infrared_ledWithText->setLedState(QSimpleLed::BLINK);
            infrared_ledWithText->setText("设备未放置");
        }

        qDebug()<<"infrared:sigio signal!"<<infrared_value;

    }

}

void InfraredDev::register_infrared_asy()
{
    int flags = 0;
    /* 设置信号SIGIO的处理函数 */
    signal(SIGIO, &InfraredDev::sigio_signal_func);

    fcntl(fd, F_SETOWN, getpid());		/* 设置当前进程接收SIGIO信号 	*/
    flags = fcntl(fd, F_GETFL);			/* 获取当前的进程状态 			*/
    fcntl(fd, F_SETFL, flags | FASYNC);	/* 设置进程启用异步通知功能 	*/
}



int InfraredDev::fd = -1; // 初始化为无效的文件描述符
ledWithText *InfraredDev::infrared_ledWithText = nullptr;
#ifndef INFRAREDDEV_H
#define INFRAREDDEV_H


#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"
#include "poll.h"
#include "sys/select.h"
#include "sys/time.h"
#include "linux/ioctl.h"
#include "signal.h"

#include <QObject>
#include <QDebug>
#include <QFile>
#include <QTextStream>
#include <QString>


#include "ui_optimize/ledwithtext.h"

#define DEVNAME "/dev/gpio_infrared"



class InfraredDev : public QObject
{
    Q_OBJECT
public:
    explicit InfraredDev(QObject *parent = nullptr);
    ~InfraredDev();
    void set_dev_name(char *name);
    char * get_dev_name();
    static ledWithText *infrared_ledWithText;
    static void setLedWithText(ledWithText *obj);


private:
    static int fd;	/* 文件描述符 */
    static QFile file;
    char *dev_name;
    static void sigio_signal_func(int signum);
    void register_infrared_asy(void);

};

#endif // INFRAREDDEV_H

因为static修饰的成员变量、成员函数并没有this指针,因此无法读取其他非static成员只允许读取static变量,因此我们需要将fd也需要使用static函数进行修饰,并且函数内不能包含其他no-static的变量。

因此在signal函数,我们就需要根据C++访问静态函数的规则,对参数进行传入了修改,使用通过(类名::静态成员)的方式来访问。

signal(SIGIO, &InfraredDev::sigio_signal_func);

 然后,我们需要让QMainwindow类知道,接收到异步通信了,需要刷新UI了。

(1)信号机制方法

我一开始是照常使用信号机制,定义了信号函数,使用emit函数进行发送,但是因为异步通知处理函数是static类的,无法直接调用其他non-static的成员,无法发送信号,因此我想是实例化一个类,发送类的实例,进而发送信号:

InfraredDev instance; // 创建对象实例
emit instance.infraredSignal(infrared_value);

代码没报错,但是执行过程出错了,在开发板上只能读取第一次的数值,发送后,就无法进行读取,由于是在开发板上进行运行的,无法进行debug,不知道是什么原因,如果有大神知道,还请不吝赐教!

(2)指针传入做法

之后,我看到了参考资料的3的做法,就想直接传入界面的指针,在异步通知里面,直接读取指针,进行界面的修改。因此我就根据,non-static成员函数可以访问static成员变量的特性,对该做法进行了改进。定义了一个设置控件的函数,外部传入一个控件指针,函数内部设置为static类型,因此完美解决了该问题。

void InfraredDev::setLedWithText(ledWithText *obj)
{
    infrared_ledWithText = obj;
}


// 在static函数就可以使用这个static指针进行设置了
void InfraredDev::sigio_signal_func(int signum){
   ......
   infrared_ledWithText->setLedColor(Qt::red);
   infrared_ledWithText->setLedState(QSimpleLed::BLINK);
   infrared_ledWithText->setText("设备未放置");

}

在infrared_dev.h中,存在该声明,为static成员变量

 static ledWithText *infrared_ledWithText;

那么,我们在外部只需要传入该控件的指针即可

device_connect_label = new ledWithText(this,Qt::green,"设备正在连接");

......
infrared_dev = new InfraredDev();
infrared_dev->setLedWithText(device_place_label);

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

详细教程 - 从零开发 Vue 鸿蒙harmonyOS应用 第五节 (基于uni-app封装鸿蒙接口请求库)

随着鸿蒙系统的兴起,越来越多的app会采用鸿蒙开发。而鸿蒙开发必不可少的就是调用各种接口服务。为了简化接口的调用流程,我们通常会做一层封装。今天就来讲解一下,如何用uni-app封装鸿蒙的接口请求库。 一、新建项目 首先我们要新建一个鸿蒙项目啦&#xff01;当然选择第一个…

音频ncm格式转mp3格式

做个笔记&#xff0c;ncm格式转mp3格式 参考&#xff1a;传送门 import os import json import base64 import struct import logging import binascii from glob import glob from tqdm.auto import tqdm from textwrap import dedent from Crypto.Cipher import AES from mu…

批量移除dom上注册的事件

目录 一、问题 二、解决方法 三、总结 一、问题 1.在window上注册了事件&#xff0c;想要批量移除注册的事件&#xff0c;发现移除不了&#xff08;也不知道什么时候会被自动移除__) 二、解决方法 1.添加事件时增加同一个AbortController生成的 signal标识。使用AbortCon…

物流实时数仓:数仓搭建(DWD)二

系列文章目录 物流实时数仓&#xff1a;采集通道搭建 物流实时数仓&#xff1a;数仓搭建 物流实时数仓&#xff1a;数仓搭建&#xff08;DIM&#xff09; 物流实时数仓&#xff1a;数仓搭建&#xff08;DWD&#xff09;一 物流实时数仓&#xff1a;数仓搭建&#xff08;DWD&am…

【赠书第11期】Unity 3D游戏开发

文章目录 前言 1 Unity 3D简介 2 Unity 3D基本概念 2.1 场景&#xff08;Scene&#xff09; 2.2 游戏对象&#xff08;Game Object&#xff09; 2.3 组件&#xff08;Component&#xff09; 2.4 资源&#xff08;Asset&#xff09; 3 Unity 3D重要组件 3.1 物理引擎 …

基于SSM的图书馆预约座位系统的设计与实现(部署+源码+LW)

项目描述 临近学期结束&#xff0c;还是毕业设计&#xff0c;你还在做java程序网络编程&#xff0c;期末作业&#xff0c;老师的作业要求觉得大了吗?不知道毕业设计该怎么办?网页功能的数量是否太多?没有合适的类型或系统?等等。今天给大家介绍一篇基于SSM的图书馆预约座位…

如何使用JavaScript 将数据网格绑定到 GraphQL 服务

前言 作为一名前端开发人员&#xff0c;GraphQL对于我们来说是令人难以置信的好用。它可以用来简化数据访问&#xff0c;这让我们的工作变得更加容易。 什么是 GraphQL&#xff1f;它是一个抽象层&#xff0c;位于任意数量的数据源之上&#xff0c;并为您提供一个简单的 API …

【深度学习】注意力机制(三)

本文介绍一些注意力机制的实现&#xff0c;包括EMHSA/SA/SGE/AFT/Outlook Attention。 【深度学习】注意力机制&#xff08;一&#xff09; 【深度学习】注意力机制&#xff08;二&#xff09; 【深度学习】注意力机制&#xff08;四&#xff09; 【深度学习】注意力机制&a…

PCB设计规则中的经验公式_笔记

PCB设计规则中的经验公式 规则1 - 临界长度规则2 - 信号带宽与上升时间规则3- 时钟信号带宽规则4-信号传输速度规则5- 集肤 (效应) 深度规则6 - 50Ω传输线电容规则7 - 50Ω传输线电感规则8 - 回流路径电感规则9 - 地弹噪声规则10- 串行传输比特率与信号带宽规则11- PCB走线直流…

HR人才测评,招聘企业中高层管理的岗位胜任力测评方案

不管是哪一个企业&#xff0c;中高层管理都是企业的核心层&#xff0c;在对这部分人才进行测评方案制定的时候&#xff0c;则要更加细致谨慎一些&#xff0c;避免出现人才录用失误的情况。 中高层管理人员是公司的支柱&#xff0c;需要具备的素质主要偏向于管理能力、综合素质…

CyclicBarrier学习一

一、定义 CyclicBarrier 字面意思回环栅栏&#xff08;循环屏障&#xff09;&#xff0c;通过它可以实现让一组线程等待至某个状态&#xff08;屏障点&#xff09;之后再全部同时执行。叫做回环是因为当所有等待线程都被释放以后&#xff0c;CyclicBarrier可以被重用。 CyclicB…

vue写了这么久了您是否知道:为什么data属性是一个函数而不是一个对象?

一、实例和组件定义data的区别 vue实例的时候定义data属性既可以是一个对象&#xff0c;也可以是一个函数 const app new Vue({el:"#app",// 对象格式data:{foo:"foo"},// 函数格式data(){return {foo:"foo"}} })组件中定义data属性&#xff…

信号与线性系统翻转课堂笔记4——连续LTI系统的微分方程模型与求解

信号与线性系统翻转课堂笔记4——连续LTI系统的微分方程模型与求解 The Flipped Classroom4 of Signals and Linear Systems 对应教材&#xff1a;《信号与线性系统分析&#xff08;第五版&#xff09;》高等教育出版社&#xff0c;吴大正著 一、要点 &#xff08;1&#x…

Zotero攻略

给大家分享一下我对于Zotero的使用。 1、下载链接 Zotero | Your personal research assistant 进入后直接下载即可 2、一些好用的插件 &#xff08;1&#xff09;Zotero Connector 下载地址&#xff1a;Zotero | Connectors 超级好用&#xff01;不用一篇一篇下PDF了&am…

geemap学习笔记028:Landsat8计算时间序列NDVI并导出

前言 本节则是以Landsat8影像数据为例&#xff0c;进行NDVI时间序列计算&#xff0c;并将得到的时间序列NDVI进行展示并导出。 1 导入库并显示地图 import ee import geemap import datetime import pandas as pd import os ee.Initialize()2 定义时间范围 # 定义日期范围 …

记录 | Visual Studio报错:const char*类型的值不能用于初始化char*类型

Visual Studio 报错&#xff1a; const char *”类型的值不能用于初始化“char *”类型的实体错误 解决办法&#xff1a; 1&#xff0c;强制类型转换&#xff0c;例如&#xff1a; char * Singer::pv[] {(char*)"other", (char*)"alto", (char*)"c…

【网络协议】网络运维管理神经-SNMP协议

文章目录 什么是SNMP&#xff1f;SNMP的组件SNMP的历史版本SNMP端口SNMP配置案例SNMP工作原理SNMP的基本工作原理SNMP的操作类型SNMP TrapsSNMP Inform SNMP的应用场景推荐阅读 什么是SNMP&#xff1f; SNMP&#xff08;Simple Network Management Protocol&#xff0c;简单网…

学习黑马vue

项目分析 项目下载地址&#xff1a;vue-admin-template-master: 学习黑马vue 项目下载后没有环境可参考我的篇文章&#xff0c;算是比较详细&#xff1a;vue安装与配置-CSDN博客 安装这两个插件可格式化代码&#xff0c;vscode这个软件是免费的&#xff0c;官网&#xff1a;…

2023年OceanBase开发者大会-核心PPT资料下载

一、峰会简介 2023年OceanBase开发者大会主要涵盖了OceanBase的最新技术进展、产品更新以及开发者工具的发布。大会发布了OceanBase 4.1版本&#xff0c;公布了两大友好工具&#xff0c;升级了文档的易用性&#xff0c;并统一了企业版和社区版的代码分支。这些举措全面呈现了O…