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

目录

  • 一. 引言
  • 二. 软件功能
    • 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 数据监测

数据的传输和解析都是是按照地址——数据的形式进行的。目前设置了7个子标签页,每个子标签页包含一个图表用于数据曲线显示。其中“数据曲线00”是地址显示0x00的数据,“数据曲线01”显示地址为0x01的数据,依次类推,前五个图表都只显示一条曲线。“自定义曲线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) 上位机软件源码后续也会陆续开源(一定的知识付费),供有兴趣的伙伴学习并定义自己的界面。

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

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

相关文章

使用Redis的优势以及会引发的问题

优势 ①使用redis代表着高性能还有高并发&#xff0c;高性能很好理解&#xff0c;redis会缓存我们访问的数据。他是基于内存的&#xff0c;第一次访问数据库我们可能需要800ms&#xff0c;但是访问后如果使用redis进行缓存&#xff0c;第二次乃至后面访问相同的数据就只需要去…

嵌入式仪器模块:示波器模块和自动化测试软件

示波器模块 • 32 位分辨率 • 125 MSPS 采样率 • 支持单通道/双通道模块选择 • 低速模式可实现实时功率分布和整机功率检测 • 高速模式可实现信号分析和上电时序测量 应用场景 • 抓取并分析波形的周期、幅值、异常信号等指标 • 电源纹波与噪声分析 • 信号模板比…

档案数字化管理的工具有哪些

档案数字化管理的工具可以包括以下几种&#xff1a; 1. 扫描仪/数字拍摄仪&#xff1a;用于将纸质文件数字化为电子文件的工具。 2. OCR&#xff08;光学字符识别&#xff09;软件&#xff1a;用于将扫描或拍摄的图像文件转换为可编辑的文本文件。 3. 文件管理系统/专久智能电子…

RAG检索与生成的融合

1、rag定义 检索增强生成 (RAG) 模型代表了检索系统和生成模型两大不同但互补组件完美结合的杰作。通过无缝整合相关信息检索和生成与背景相关的响应&#xff0c;RAG模型在人工智能领域达到了前所未有的复杂程度。 2、rag工作流程 2.1、rag整体框架 query通过llm处理后&…

doris FE 在Windows环境下编译调试开发环境

前言&#xff1a; doris fe 在win下调试运行&#xff0c;和正常java项目有一些差异&#xff0c;主要是有与be&#xff08;c&#xff09;通信代码的生成 在win环境下不能直接生成&#xff0c;因此需要现在linux下生成之后&#xff0c;再拷贝到本地来&#xff0c;然后进行编译&a…

王学岗鸿蒙开发(北向)——————(十三)音乐播放器

AudioRenderer适合录音 AVPlayer:简单的本地单曲播放 MP3文件放置的地方 import media from ohos.multimedia.media import common from ohos.app.ability.common; Entry Component struct Index {//第1步&#xff1a;avPlayer:media.AVPlayer nullasync onPageShow(){//第…

665. 非递减数列(中等)

665. 非递减数列 1. 题目描述2.详细题解3.代码实现3.1 Python3.2 Java 1. 题目描述 题目中转&#xff1a;665. 非递减数列 2.详细题解 判断在最多改变 1 个元素的情况下&#xff0c;该数组能否变成一个非递减数列&#xff0c;一看到题目&#xff0c;不就是遍历判断有几处不…

解决阿里云的端口添加安全组仍然无法扫描到

发现用线上的网站扫不到这个端口&#xff0c;这个端口关了&#xff0c;但是没有更详细信息了 我用nmap扫了一下我的这个端口&#xff0c;发现主机是活跃的&#xff0c;但是有防火墙&#xff0c;我们列出云服务器上面的这个防火墙list&#xff0c;发现确实没有5566端口 参考&a…

Python框架scrapy有什么天赋异禀

Scrapy框架与一般的爬虫代码之间有几个显著的区别&#xff0c;这些差异主要体现在设计模式、代码结构、执行效率以及可扩展性等方面。下面是一些关键的不同点&#xff1a; 结构化与模块化&#xff1a; Scrapy&#xff1a;提供了高度结构化的框架&#xff0c;包括定义好的Spider…

⌈ 传知代码 ⌋ Flan-T5 使用指南

&#x1f49b;前情提要&#x1f49b; 本文是传知代码平台中的相关前沿知识与技术的分享~ 接下来我们即将进入一个全新的空间&#xff0c;对技术有一个全新的视角~ 本文所涉及所有资源均在传知代码平台可获取 以下的内容一定会让你对AI 赋能时代有一个颠覆性的认识哦&#x…

Unity | Shader基础知识(番外:了解内置Shader-Standard<二>)

目录 前言 一、Standard参数详解 1.NormalMap法线贴图 2.HeightMap高度贴图 3.Occlusion遮挡贴图 4.DetailMask细节遮挡 5.Emission自发光 6.Tiling铺地砖和Offset偏移度 二、作者的碎碎念 前言 Unity | Shader基础知识(番外&#xff1a;了解内置Shader-Standard&#x…

【qsort函数】

前言 我们要学习qsort函数并利用冒泡函数仿照qsort函数 首先我们要了解一下qsort&#xff08;快速排序&#xff09; 这是函数的的基本参数 void qsort (void* base, size_t num, size_t size,int (*compar)(const void*,const void*)); 简单解释一下 base&#xff1a;指向…

MySQL-数据处理函数

026-distinct去重 select job from emp;加个 distinct 就行了 select distinct job from emp;注意&#xff1a;这个去重只是将显示的结果去重&#xff0c;原表数据不会被更改。 select 永远不会改变原数据 select distinct deptno, job from emp order by deptno asc;027-数…

GUI编程02-布局管理器

流式布局 FlowLayout 东西南北中 BorderLayout 表格布局 GridLayout 流式布局 package YMP.GUI; ​ import java.awt.*; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; ​ public class TestFlowLayout {public static void main(String[] args…

XSS(跨站脚本攻击)

1.什么是xss XSS全称&#xff08;Cross Site Scripting&#xff09;跨站脚本攻击&#xff0c;为了避免和CSS层叠样式表名称冲突&#xff0c;所以改为了 XSS&#xff0c;是最常见的Web应用程序安全漏洞之一,XSS是指攻击者在网页中嵌入客户端脚本&#xff0c;通常是JavaScript编写…

【JMeter接口测试工具】第二节.JMeter项目实战(上)【实战篇】

文章目录 前言项目实战零、接口测试流程一、测试数据准备二、接口功能测试三、掌握测试用例编写四、自动化脚本架构搭建总结 前言 零、接口测试流程 1、制定测试计划,分配任务 2、从 API 文档中提取接口清单&#xff1a;对 API 文档简化,提高测试效率,接口清单就是对 API 文档…

“双一流名校”苏州大学计算机专业好考吗?苏州大学计算机考研考情分析

苏州大学&#xff08;Soochow University&#xff09;&#xff0c;简称“苏大”&#xff0c;坐落于历史文化名城苏州&#xff0c;国家“211工程”重点建设高校&#xff0c;国家国防科技工业局和江苏省人民政府共建高校&#xff0c;国家“双一流”世界一流学科建设高校&#xff…

“RabbitMQ入门指南:从入门到起飞,这一篇就够!打造高效消息通信系统的第一步“。

1.前言 RabbitMQ是一个开源的消息代理软件&#xff0c;它实现了高级消息队列协议&#xff08;AMQP&#xff09;的标准&#xff0c;并用Erlang语言编写。作为消息代理&#xff0c;RabbitMQ接收、存储和转发消息&#xff0c;帮助应用程序之间实现异步通信。它提供了一个强大而灵活…

设计模式 —— 观察者模式

设计模式 —— 观察者模式 什么是观察者模式观察者模式定义观察者模式的角色观察者模式的使用场景观察者模式的实现 被观察者&#xff08;Subject&#xff09;观察者&#xff08;Observer&#xff09;通知&#xff08;notify&#xff09;更新显示&#xff08;update&#xff09…

44【Aseprite 作图】樱花丸子——拆解

1 枝干 2 花朵&#xff1a;其实只要形状差不多都行&#xff0c;有三个颜色&#xff0c;中间花蕊颜色深一点&#xff0c;中间花蕊外的颜色偏白&#xff1b;不透明度也可以改一下&#xff0c;就变成不同颜色 3 丸子 最外层的颜色最深&#xff0c;中间稍浅&#xff0c;加一些高光…