[发布]嵌入式系统远程测控软件-基于Qt

news2024/11/30 0:45:21

目录

  • 一. 引言
  • 二. 软件功能
    • 2.1 原理
    • 2.2 软件功能
    • 2.3 运行环境
  • 三. 软件操作使用
    • 3.1 软件界面
    • 3.2 软件功能使用详解
      • 3.2.1 连接
      • 3.2.2 数据监测(串口示波器)
      • 3.2.3 数据修改
      • 3.2.4 数据保存
    • 3.3 软件的硬件连接
  • 四. 通信协议——STM32移植篇
    • 4.1 通信协议
    • 4.2 STM32如何传输浮点数
    • 4.3 简单移植(只进行简单数据监控)
    • 4.4 完整移植
  • 写在最后

一. 引言

当前,嵌入式系统的运用非常普遍。无论是智能家居,还是各类机器人控制系统,都离不开嵌入式硬件和软件的支撑。而在各类系统的开发、使用和维护过程中,数据可视化都是一项重要内容。通过可视化界面,达到“测”与“控”的目的。“测”即监测系统运行过程中的各类状态,以数字、仪表、曲线等形式进行展示。“控”即通过上位机进行指令下发,达到参数修改、远程指挥的目的。

然而,嵌入式系统(一般称为下位机)的开发和可视化界面(一般称为上位机)的开发都是有难度的。熟悉嵌入式系统开发的工程师、学生并不一定擅长上位机的开发。有时候做一些小项目,仅仅只需要监测一两个数据,专门去开发一个可视化软件耗时耗力、并不经济。本人长期从事基于单片机的机器人控制系统研发,略懂一点可视化软件开发,经常是一个嵌入式系统就要配套一个上位机,开发周期长、维护难度大。另外经常收到各类关于上位机开发的咨询,深知这方面需求巨大。

因此,基于快速开发的需求,本软件开发了一款通用的上位机软件。使用者只需按照本软件提供的通信协议进行工程移植,即可使用本软件进行嵌入式系统远程“测”“控”。

本软件非常适合对上位机开发不熟悉的工程师、学生使用,可以快速构建自己的远程数据采集系统,大大缩短开发周期。尤其对学生而言、可快速构建测控系统,方便采集数据,加速论文写作。

软件和配套STM32工程、说明文档的下载地址:嵌入式系统远程测控软件-串口示波器-远程修改参数

二. 软件功能

2.1 原理

如何实现上位机和下位机通信,熟悉单片机开发的对串口调试助手肯定不陌生。串口调试助手就是一个上位机,它可收可发,只是它只能以文本显示原始数据帧。当我们定义好通信协议,下位机和上位机都按照协议进行数据发与收,自然就能解析出我们想要的数据。本软件也是基于串口通信的,使用的是自定义通信协议。

2.2 软件功能

(1) 数据监测。以文本数据和曲线形式显示下位机上传的数据。
(2) 数据修改。对下位机的数据进行修改
(3) 历史数据保存。以日志文件形式保存部分监测的数据,供后期进行数据处理。

2.3 运行环境

电脑环境:本软件基于Qt5.14开发,发行版为绿色免安装版本,无需安装开发环境。电脑上安装CH340、CP2102等串口驱动即可。

单片机环境:任意单片机,只要能进行串口通信。

三. 软件操作使用

3.1 软件界面

直接双击exe文件即可打开主界面。启动后主界面如图所示,界面由多个区域组成。其中左侧两个区域,“端口”框内为设备连接操作部分,下方是快速设置寄存器值。右侧区域为标签页区域,有四个标签页,“数据曲线”标签页是进行数据显示(上行数据),“更改参数”标签页是进行数据修改(下发数据),“串口数据”标签页显示串口原始16进制数据。“ReadMe”标签页里面是一些版本信息和说明文字。

其中,“数据曲线”标签页又分成了多个标签页。“数据曲线00”是地址为0x00的数据,“数据曲线01”是地址为0x02的数据,依次类推。“自定义曲线1”和“自定义曲线2”两个标签页可以任意选择地址进行数据显示,可以分别最多显示4条曲线。

在这里插入图片描述

3.2 软件功能使用详解

3.2.1 连接

(1)通过下拉框设置串口号、波特率、数据位、停止位、校验位等串口参数,其中默认参数为波特率115200、数据位8、停止位1、校验位无。串口号在软件启动时自动搜索计算机当前可用串口并可以通过下拉框选取,当没有设备连接时下拉框为空,不可选择。
(2)点击“打开串口”按钮,即可与下位机连接。
(3)当需要断开连接时点击“关闭串口”按钮,即可与控制板断开连接。
(4)当软件运行过程中出现插拔通信线,会改变可用串口设备,点击“更新设备列表”按钮,即可在串口号下拉框中选取最新的可用串口设备。(目前暂时是这样,需要手动更新)

3.2.2 数据监测(串口示波器)

数据的传输和解析都是是按照地址——数据的形式进行的。目前设置了8个子标签页,每个子标签页包含一个图表用于数据曲线显示。其中“数据曲线00”是地址显示0x00的数据,“数据曲线01”显示地址为0x01的数据,依次类推,前6个图表都只显示一条曲线。“自定义曲线1”“自定义曲线2”两个标签页分别可以显示四条曲线(即四个地址的数据),并且这四个地址是可以自己设置的。

下图显示的是“数据曲线00”标签页的数据,图表下方横坐标是时间,时间是动态滚动的,左侧纵坐标是数据值。图表下侧以文本框形式显示数据的具体值。图表上方有三个输入框。“设置显示点数”设置图表区域显示的数据个数,这个数值越大,显示的点数越多,横坐标时间跨度越长。“设置y坐标范围”可以设置y轴坐标的最小值和最大值,使曲线能够完整显示在图框中。“数据曲线01”、“数据曲线02”、“数据曲线03”、“数据曲线04”、“数据曲线05”使用方法一样。

在这里插入图片描述

下图显示的是“自定义曲线1”标签页的数据,图表下方横坐标是时间,时间是动态滚动的,左侧纵坐标是数据值。图表上方有两排输入框。第一排三个输入框:“设置显示点数”设置图表区域显示的数据个数,这个数值越大,显示的点数越多,横坐标时间跨度越长。“设置y坐标范围”可以设置y轴坐标的最小值和最大值,使曲线能够完整显示在图框中。第二排四个输入框,“数据1”设置数据1的数据地址,“数据2”设置数据2的数据地址,“数据3”设置数据3的数据地址,“数据4”设置数据4的数据地址。四个数据前都有一个复选框,当勾选相应的复选框时,图像中就会显示这个数据的曲线,当取消勾选相应的复选框时,图像中就会不显示这个数据的曲线。“自定义曲线2”标签页使用方法一样。

在这里插入图片描述

左侧有一个“设置显示点数”的编辑框,所有的曲线显示时长我都改成在这个地方统一设置了,相应标签页的“设置显示点数”没激活。如果下位机每隔20ms上传一帧数据,那么500个点就是500*20ms = 10s。

3.2.3 数据修改

数据修改也是是按照地址——数据的形式进行的,在“更改参数”标签页和左侧“快速设置寄存器值”都可以修改参数。“更改参数”标签页里,“参数修改”那一块,可以修改0x80~0x8F一共16个数据,地址固定。点“查询参数”就可以查看当前值,点“载入参数”就把当前值填充到设置值那里(这一步非必须,只是为了方便,不需要一个一个再敲进去),然后在设定值那一列进行修改即可,改成自己想要的参数。最后点“写入参数”即可将参数写入进去。

右侧“指令下发”那一块,可以指定起始地址和数据个数,这样就可以应对所有场合了。比如将起始地址改成0xA0,数据个数改成10,点击“写入指令”,就会将从0xA0开始的10个数据写到单片机了。(不能超过16个数据,因为只给了16个框框)。

主界面左方“快速设置寄存器值”那里也是可以灵活修改参数,每个地址框都是可以滚动或敲入修改的,从而修改地址。数据框里的值一旦滚动或敲入则会自动下发数据下去。这个地方一次只能修改一个地址的数据,适用于测试场合。

在这里插入图片描述
在这里插入图片描述

3.2.4 数据保存

除了数据测控外,软件的第三大核心功能是数据保存,这个保存过程是在后台自动进行的,当软件启动时就会开始记录,直到软件关闭时停止记录。日志文件的生成和命名也是自动的。格式为“data_year_month_date_hh_mm.csv”。其存放位置为.exe文件的同级目录下。如图所示。

在这里插入图片描述
文件打开之后的格式如图13所示。第一行为表头,从左至右前四列为小时、分钟、秒、毫秒,四位数据标记数据接收的时间,之后依次显示每个地址的数据:0x00地址的数据,0x01地址的数据,……,0x0F地址的数据。本版本软件暂时设置只保存到0x0F的数据,即最多保存17个数据,使用者在使用的过程中要注意,虽然曲线显示是没有地址限制的,但是超过0x0F地址的数据是不被保存的。所以要优先使用低地址的数据。

在这里插入图片描述

3.3 软件的硬件连接

最后,说明一下本软件的硬件连接方式。本软件与单片机的连接方式可为串口ttl、485、232任意一种,只要是UART通信协议即可。

电脑与单片机之间可以使用一根数据线进行连接。这种方式是有线方式。也可以使用无线数传模块进行连接。电脑和单片机各连接一个无线数传模块,即可实现远程无线通信。

四. 通信协议——STM32移植篇

4.1 通信协议

依照本软件所规定的通信协议进行数据上传,本软件才能正确解析数据,进行显示和保存。否则通信失败,软件无法正常使用。

本软件使用的自定义通信协议,数据帧长度为任意字节,可以一次发送所有数据,也可以分多个数据帧上传数据。当数据过多时,建议分多个数据帧发送,否则一帧长度过长,出错概率增大。

数据的保存和传输按照“地址-数据”形式,按照浮点数32位保存,每个数据占4个字节。每个数据都有自己的地址,地址从0开始编号。

有三种数据帧:

(1)单片机主动上传数据:
在这里插入图片描述
发送N个数据(32 bits)一共4N+6个帧字节。

(2)上位机下发更改数据:
在这里插入图片描述

发送N个数据(32bits)也是一共4N+6个帧字节。

(3)上位机下发查询数据:
在这里插入图片描述

查询从起始地址开始的N个数据,查询帧是6个字节。下位机收到数据按照上传数据格式上传。

4.2 STM32如何传输浮点数

数据的传输最简单的是传输整形数据,本软件的早期版本是传输uint16类型数据,每个数据占用两个字节。这种方法的缺点是:(1)传输浮点数时需要进行倍数处理。例如0.12,将其乘100变成整形的12,上位机收到后除100变成浮点型的0.12。这种方法较麻烦,哪些地址的数据需要进行倍数,需要下位机和上位机同时定义清楚。(2)有符号和无符号类型数据区分。uint16类型数据较简单,直接传输,直接解析,没问题。int16上位机解析时,就需要进行类型转换了。哪些地址的数据要进行(int16_t)类型转换,也要定义清楚。(3)表示的数据范围有限,16位整形无符号数只能到65535,有符号数除2减半。如果是浮点数,乘掉了倍数,表示范围直接缩水。如果是翻100倍,只能表示到655。

所以,最方便的就是直接传输浮点数,省去很多麻烦。当然浮点数的缺点就是,一个数据要占用4个字节。因此效率是传输整形数据的一半。

传输浮点数,需要定义一个联合体:

union float_data
{
    float f_data;
    uint8_t byte[4];
};

f_databyte[4]共用4个字节的内存单元,成员f_data是实际使用的数据,成员byte[4]是通信时用的数据,各司其职。

使用方法:

#include <stdio.h>
#include <stdint.h>

union float_data
{
    float f_data;
    uint8_t byte[4];
};

int main()
{
    union float_data me, you;
    me.f_data = 0.12;
    you.byte[3] = me.byte[3];
    you.byte[2] = me.byte[2];
    you.byte[1] = me.byte[1];
    you.byte[0] = me.byte[0];
    printf("you = %f", you.f_data);

    return 0;
}

出来的结果是一样的,0.12。聪明的读者可以发现,me和you对应下位机和上位机。只要me和you按照这种方式进行传输,就可以传输浮点数。传输多个浮点数,me和you就可以定义为一个数组,例如me[100], you[100]

数组的索引就是地址,数组的索引就是地址,数组的索引就是地址。 请记住这个思想和方法。

4.3 简单移植(只进行简单数据监控)

如果你只是想监测几个数据,可以非常迅速地实现。

下面给出示例代码,可以直接写在main函数里。

void main()
{
    // 省略
    union float_data register[10];
    uint8_t data[100];
    while(1)
	{
		uint8_t data;
		data[0] = 0xAA;
		data[1] = 0xFF;
		data[2] = 0;
		data[3] = 0;
		data[4] = 4;		
		data[5] = register[0].byte[3];
		data[6] = register[0].byte[2];
		data[7] = register[0].byte[1];
		data[8] = register[0].byte[0];
		data[9] = 0;
		for (i = 0; i < 9; i++)
			data[9] += data[i];
		for (i = 0; i < 10; i++)
			Uart1_send_char(data[i]);
		delay_ms(20);
	}
}

这段代码即按照通信协议进行“填空”,数据的起始地址是0x0000,有效数据长度是4,即一个数据,一帧数据总长为10个字节,最后一个字节是校验和。这段循环结构按照20ms的间隔定期上传数据。

void main()
{
    // 省略
    union float_data register[10];
    uint8_t data[100];
    while(1)
	{
		data[0] = 0xAA;
		data[1] = 0xFF;
		data[2] = 0;
		data[3] = 0;
		data[4] = 4;		
		data[5] = register[0].byte[3];
		data[6] = register[0].byte[2];
		data[7] = register[0].byte[1];
		data[8] = register[0].byte[0];
		data[9] = 0;
		for (i = 0; i < 9; i++)
			data[9] += data[i];
		for (i = 0; i < 10; i++)
			Uart1_send_char(data[i]);

		data[0] = 0xAA;
		data[1] = 0xFF;
		data[2] = 0;
		data[3] = 5;
		data[4] = 8;		
		data[5] = register[0].byte[3];
		data[6] = register[0].byte[2];
		data[7] = register[0].byte[1];
		data[8] = register[0].byte[0];
		data[9] = register[0].byte[3];
		data[10] = register[0].byte[2];
		data[11] = register[0].byte[1];
		data[12] = register[0].byte[0];
		data[13] = 0;
		for (i = 0; i < 13; i++)
			data[13] += data[i];
		for (i = 0; i < 14; i++)
			Uart1_send_char(data[i]);
		delay_ms(20);
}

这段代码分两帧数据进行了数据传输。第一帧数据从0x0000地址开始,传输了一个数据(对应地址0x0000)。第二帧从0x0005开始,传输了两个数据(对应地址0x0005,0x0006)。

4.4 完整移植

使用全部功能,按照步骤移植,也很简单。

(1)工程里加入两个文件:transmission.htransmission.c

#ifndef __TRANSMISSION_H
#define __TRANSMISSION_H

#include "./SYSTEM/sys/sys.h"
#include "./SYSTEM/usart/usart.h"

union float_data
{
    float f_data;
    uint8_t byte[4];
};

extern union float_data float_register[255];
extern u8 send_buf[100];      // DMA传输缓冲区

u8 ComSendData(u16 addr, u8 len);
u8 cksum(u8 *data,u8 len);
void analyseData(u8 *data,u8 len);

#endif
#include "transmission.h"
#include "string.h"

u8 send_buf[100] = {0};
union float_data float_register[255];

u8 ComSendData(u16 addr, u8 num)
{
	u8 i;
	u8 len = 4*num+6;				//一帧字节的长度
	send_buf[0] = 0XAA;	   			//帧头
	send_buf[1] = 0XFF;	   			//帧头 上行
	send_buf[2] = addr << 8; 		//数据地址高8位
	send_buf[3] = addr; 			//数据地址低8位
	send_buf[4] = 4*num; 			//数据长度,单位为字节
	for (i = 0; i < num; i++)
	{
		send_buf[5 + 4*i] = float_register[addr + i].byte[3]; 			//数据高8位
		send_buf[6 + 4*i] = float_register[addr + i].byte[2]; 			
		send_buf[7 + 4*i] = float_register[addr + i].byte[1]; 			
		send_buf[8 + 4*i] = float_register[addr + i].byte[0]; 			//数据低8位
	}
		
	send_buf[len-1] = cksum(send_buf,len-1);			// 先清零,避免上一帧数据这个位置有数据
					
	for (i = 0; i < len; i++)
	{
		Uart1SendChar(send_buf[i]);
	}
	return 0;
}

// 相加校验,轻量计算
// 经使用,会出错,很奇怪
// 出错原因,sum没有初始化为0,晕
u8 cksum(u8 *data,u8 len)
{
	u8 sum = 0;
	u8 i;
	for (i = 0; i < len; i++)
		sum += data[i]; 		//计算校验和
	return sum;
}

void analyseData(u8 *data,u8 len)
{
	u16 addr,data_num;
	u8 i;
	u8 sum,sum1;
	if (data[0] != 0xAA) return;							// 帧头不对
	if (data[1] != 0xAA && data[1] != 0x55 ) return;		// 帧头不对

	if ((u8)cksum(data,len-1) != data[len-1]) return;			// (u8)强制类型转换要加上,不然出错		
	
	if (data[1] == 0x55)		// 写指令
	{
		addr = (u16)data[2] << 8 | (u16)data[3];
		data_num = data[4]/4;									// 要更改的寄存器数据数量
		for (i = 0; i < data_num;i++)							// 更改寄存器值
		{
			float_register[addr + i].byte[3] = data[5 + 4*i];
			float_register[addr + i].byte[2] = data[6 + 4*i];
			float_register[addr + i].byte[1] = data[7 + 4*i];
			float_register[addr + i].byte[0] = data[8 + 4*i];
		}

		ComSendData(addr,data_num);			// 上传控制参数	写完之后上传回去

	}
	if (data[1] == 0xAA)		// 读指令
	{
		addr = (u16)data[2] << 8 | (u16)data[3];
		data_num = data[4]/4;									// 要查询的寄存器数据数量
		ComSendData(addr,data_num);			// 上传控制参数	控制参数上传是慢速的。
	}
}

这里面干了三件事:(1)定义了一个内存空间:union float_data float_register[255],内存大小可以自己修改,这里给的255。(2)定义上传函数:u8 ComSendData(u16 addr, u8 len),从起始地址上传len个数据。(3)定义解析函数:void analyseData(u8 *data,u8 len),解析一帧数据,如果是写指令,就更新float_register中的值,并且更新完了把新的数据上传上去。如果是读指令,就按照给定的起始地址和数据量上传数据。

代码很简单,核心是其中的思想,请读者自行体会~

(2)定义串口中断函数和串口发送函数

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
	while (huart->Instance == USART1) //如果是串口1			// 有两种情况,0xAA AA是读指令,数据帧长度6个字节; 0xAA 55是写指令,数据长度 2N + 6字节
	{
		USART1_RX_BUF[USART1_RX_STA] = aRxBuffer1[0];
		if (USART1_RX_STA == 0 && USART1_RX_BUF[0] != 0xAA) break; //帧头不对,丢掉
		
		if (USART1_RX_STA == 1) 	
		{
			if( USART1_RX_BUF[1] != 0xAA && USART1_RX_BUF[1] != 0x55 )
			{
				USART1_RX_STA = 0;
				break; //帧头不对,丢掉
			}
		}
		USART1_RX_STA++;
		if (USART1_RX_STA > USART_REC_LEN) USART1_RX_STA = 0;  ///接收数据错误,重新开始接收

		
		if ( (USART1_RX_BUF[1] == 0xAA && USART1_RX_STA ==  6) ||  (USART1_RX_BUF[1] == 0x55 && USART1_RX_STA == USART1_RX_BUF[4] + 6) )
		{
			//analyseData(&USART1_RX_BUF[0]);	
			analyseData(USART1_RX_BUF, USART1_RX_STA);
					
			LED3_TOGGLE();

			memset(USART4_RX_BUF, 0, USART1_RX_STA);

			USART1_RX_STA = 0;
		}
		break;
	}
}

串口中断函数随便怎么定义,我这里给的是字节中断,你可以按DMA空闲中断去写都行,总之就是要按照前面给的通信协议,把上位机下发下来的“修改数据”或“查询数据”的数据帧解析出来,然后调用transmission.c中的void analyseData(u8 *data,u8 len)函数进行解析数据。

前面设计一个串口发送单个字节的函数int Uart1SendChar(u8 ch):

int Uart1SendChar(u8 ch)
{
    while((USART1->SR&0X40)==0);//循环发送,直到发送完毕   
	USART1->DR=(u8)ch; 
    return ch;	
}

(3)测试代码:

提供的STM32工程里面,使用地址0x90作为正弦波和方波的幅值,地址0x91作为正弦波和方波的频率。地址0x00是正弦波值,地址0x01是方波值,每20ms上传一次数据。上位机通过修改0x90和0x91地址的数据,就可以更改正弦波和方波的幅值和频率。代码为:

//sign函数
float Sign(float input)
{
	float output=0;
	if(input>0)
	{
		output=1;
	}
	else if(input==0)
	{
		output=0;
	}
	else
	{
		output=-1;
	}
	return output;
}

int main(void)
{
    uint8_t len;
    uint16_t times = 0;
    
    HAL_Init();                             /* 初始化HAL库 */
    sys_stm32_clock_init(336, 8, 2, 7);     /* 设置时钟,168Mhz */
    delay_init(168);                        /* 延时初始化 */
    MX_USART1_UART_Init();                     /* 串口初始化为115200 */
    led_init();                             /* 初始化LED */

    u32 run_cnt = 0;
	u8 dt = 20;
	u8 i;
	float amplitude=100, frequency=0.5;
	float_register[0x90].f_data = 100;	
	float_register[0x91].f_data = 0.5;
	
	for (i = 0; i < 16; i++)
		float_register[0x80+i].f_data = i;		// 数据赋值, 测试上位机读数据
		
	while(1)
	{
		// 测试上位机写数据
		amplitude = float_register[0x90].f_data;
		frequency = float_register[0x91].f_data;

		// 测试从机主动上传数据
		float_register[0].f_data = amplitude * sin(2*3.14*run_cnt*frequency*dt*0.001);
		float_register[1].f_data = amplitude * Sign(float_register[0].f_data);
		
		ComSendData(0,2);			// 0地址开始3个数据
		
		delay_ms(dt); // 采样周期
		
		if (run_cnt++ % 10 == 0)  LED2_TOGGLE();		

	}
}

效果如图。开始时幅值为100,频率为0.5Hz,修改为1Hz、幅值200,波形变化了两次。

在这里插入图片描述

写在最后

(1)由于本软件是通用软件,所以都是以“地址0x80”之类的命名,而不是参数的实际意义,例如“PID参数”、“前进速度”等。这就需要聪明的你自己在脑袋里定义了。鱼和熊掌不可兼得。

(2)开发上位机有很多软件,如LabWindows、labview、Qt、C#等,包括万能的Matlab,各有利弊。就本人近几年的开发经验,Matlab的实时性是灾难,LabWindows界面太丑、Labview的非编程开发方式习惯不了。相对而言,Qt功能是最为强大的,很适合实时系统,大名鼎鼎的飞控地面站QGroundControl就是Qt开发的。想学习上位机编程的强烈建议Qt。

(3)本软件及其配套STM32示例工程下载需要一点点费用。一方面是作者的劳动成果,请养成知识付费的思想和行动自觉,本人平时也是经常性为知识付费,花几块几十块钱解决大问题是非常非常划算的。另一方面,付费的东西能够帮助你筛选掉一些无用资源。使用过程中有任何问题欢迎私信。本软件的下载地址:嵌入式系统远程测控软件-串口示波器-远程修改参数

(4) 上位机软件源码后续也会陆续开源(一定的知识付费),供有兴趣的伙伴学习并定义自己的界面。

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

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

相关文章

Java SE LTS版本商用收费,有那些开源的替代方案?

&#x1f680; Java SE LTS版本商用收费&#xff0c;有那些开源的替代方案&#xff1f; 摘要 Java 对于云服务、大数据、电子商务、支付、欺诈和身份、交易等许多应用程序来说都是至关重要的语言。然而&#xff0c;Oracle 对 Java SE LTS 版本的商用收费政策引发了广泛关注和…

活体检验API在Java、Python、PHP中的使用教程

活体检验API是一种基于生物特征的身份验证技术&#xff0c;通过分析和识别用户的生物信息来确认其身份。这种技术广泛应用于各种领域&#xff0c;如金融、安全、社交媒体等&#xff0c;以提高身份验证的安全性和准确性。以下是描述”活体检验API”背景的一些关键点&#xff1a;…

python dropna怎么用

pandas的设计目标之一就是使得处理缺失数据的任务更加轻松些。pandas使用NaN作为缺失数据的标记。 使用dropna使得滤除缺失数据更加得心应手。 dropna常用参数&#xff1a; # DataFrame.dropna(axis0, howany, threshNone, subsetNone, inplaceFalse) 主要的2个参数&#xff…

首个共同祖先

题目链接 首个共同祖先 题目描述 注意点 所有节点的值都是唯一的p、q 为不同节点且均存在于给定的二叉树中 解答思路 第一种思路是后序遍历找到包含p和q时的根节点&#xff08;注意可能是p或q作为根节点&#xff09;&#xff0c;对于任一节点node&#xff0c;会先查找其左…

win10及11暂停更新永久

windows更新永久暂停 winr 输入 regedit 计算机\HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\WindowsUpdate\UX\Settings 新建 名称FlightSettingsMaxPauseDays 右击修改为9999代表禁用天数推迟,基数改16进制 点击高级选项调整截止日期,因为我一开始设置的6666所以是6666

ssldump一键分析网络流量(KALI工具系列二十二)

目录 1、KALI LINUX 简介 2、ssldump工具简介 3、在KALI中使用ssldump 3.1 目标主机IP&#xff08;win&#xff09; 3.2 KALI的IP 4、操作示例 4.1 监听指定网卡 4.2 指定端口 4.3 特定主机 4.4 解码文件 4.5 显示对话摘要 4.6 显示加密数据&#xff08;需要私钥&…

分贝通到餐解决方案全新升级

消费小票总是神出鬼没;申请、报销驴唇不对马嘴;虚报陷阱难逐一排查 部门餐标视而不见;单笔超支屡见不鲜;规则复杂总被吐槽记不住 发票、单据多如牛毛;发票合规层出不穷;审核对账气血两亏; ...... 针对上述问题,分贝通全面升级了到餐解决方案。 从一线CBD商务区到地方县城,优…

微信服务号营销新篇章:HubSpot与MessageBox的强强联合

随着全球数字化浪潮的推进&#xff0c;越来越多的企业开始寻求海外市场的拓展机会。在这个过程中&#xff0c;微信服务号作为一个强大的数字化工具&#xff0c;为企业提供了无限的商业可能。本文将详细解析微信服务号的概念、功能、使用方法&#xff0c;以及它与HubSpot、Messa…

一个网站稳定的重要性

1.网站的初期 在网站的初期需要在网站的开发和搭建上花时间&#xff0c;正是网站诞生的时候&#xff0c;但是网站的基础建设是非常的重要&#xff0c;也就是说网站的底层的架构要牢固&#xff0c;要有一种顾忌以后扩展和稳定的开发觉悟&#xff0c;只有基础做好了&#…

深度学习 --- stanford cs231 编程作业(assignment1,Q3: softmax classifier)

stanford cs231 编程作业(assignment1&#xff0c;Q3: softmax classifier softmax classifier和svm classifier的assignment绝大多部分都是重复的&#xff0c;这里只捡几个重点。 1&#xff0c;softmax_loss_naive函数&#xff0c;尤其是dW部分 1&#xff0c;1 正向传递 第i张…

物业抄表与收费系统的现代化解决方案

1.系统简述 物业抄表与收费系统是当代物业管理方法不可或缺的一部分&#xff0c;它通过自动化的形式&#xff0c;高效地管理方法电力能源使用数据&#xff0c;提升收费标准高效率&#xff0c;降低人为失误&#xff0c;同时提供数据统计分析适用。该系统不但优化了物业企业的日…

人脸识别之--计算余弦相似度-android

余弦相似度是比对两个向量是否一致&#xff0c;余弦相似度是通过计算两个向量的夹角余弦值来衡量它们之间的相似度&#xff0c;算出来的值可以直接用作相似度的分数。 公式&#xff1a; 余弦相似度和欧式距离经常用来人脸识别特征对比。 其中&#xff1a; 1、余弦相似度是通…

vue富文本wangeditor加@人功能(vue2 vue3都可以)

依赖 "wangeditor/editor": "^5.1.23", "wangeditor/editor-for-vue": "^5.1.12", "wangeditor/plugin-mention": "^1.0.0",RichEditor.vue <template><div style"border: 1px solid #ccc; posit…

python中字典的创建

1.字典的概念 字典是一种存储键值对的结构。 在python中能够根据键&#xff08;key&#xff09;来快速找到值&#xff08;value&#xff09; 根据key能够快速的找到value&#xff08;一对一的映射关系&#xff09; 在python的字典中&#xff0c;可以同时包含很多个键值对&am…

江协科技STM32学习- 2安装Keil5-MDK

本文是根据哔哩哔哩网站上“江协科技STM32”视频的学习笔记&#xff0c;在这里会记录下江协科技STM32开发板的配套视频教程所作的实验和学习笔记内容。本文大量引用了江协科技STM32教学视频和链接中的内容。 引用&#xff1a; STM32入门教程-2023版 细致讲解 中文字幕_哔哩哔哩…

基于小波脊线的一维时间序列信号分解方法(MATLAB R2018A)

信号分解技术是把一个复杂信号分解为若干含有时频信息的简单信号&#xff0c;研可通过分解后的简单信号来读取和分析复杂信号的有效特征。因此&#xff0c;信号分解技术对分析结果的影响是不言而喻的。 傅里叶分解是早期常用的信号分解方法&#xff0c;最初被用于分析热过程&a…

这些代码是APP自动化插件开发的关键!

在移动互联网高速发展的今天&#xff0c;APP的自动化插件开发成为了提升应用功能性和用户体验的重要手段。 而在这一过程中&#xff0c;五段源代码的巧妙运用往往能够起到事半功倍的效果&#xff0c;本文将为您科普分享这五段关键的源代码&#xff0c;帮助您更好地理解和应用自…

SJ708-II安全帽垂直间距配带高度测量仪

一、主要用途 依据GB/T2811-2007和GB/T2812-2006最新国家标准研发&#xff0c;主要用于安全帽垂直间距和配带高度试验&#xff0c;是安全帽生产企业办理生产许可证以及LA(劳安)认证&#xff0c;监督检测单位&#xff0c;科研机构必备安全帽检测设备。 二、仪器特征 1、采用铝…

3dmax材质高清参数设置图

3ds Max是一款在设计领域内非常受推崇的软件&#xff0c;以其强大的建模功能和丰富的材质库而知名。设计师可以通过调整材质的参数来制作出更加真实的渲染效果。本文将介绍一些技巧&#xff0c;教您如何通过简单的调整来优化3ds Max中的材质设置&#xff0c;从而增强作品的视觉…

【Linux】Linux环境基础开发工具_6

文章目录 四、Linux环境基础开发工具gdb 未完待续 四、Linux环境基础开发工具 gdb 我们已经可以写代码了&#xff0c;也能够执行代码了&#xff0c;但是代码错了该如何调试呢&#xff1f;Linux中可以使用 gdb 工具进行调试。 我们写一个简单的程序&#xff1a; 但是我们尝试…