[发布]嵌入式系统远程测控软件-基于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) 上位机软件源码后续也会陆续开源(一定的知识付费),供有兴趣的伙伴学习并定义自己的界面。

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

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

相关文章

shell编程(三)—— 控制语句

程序的运行除了顺序运行外&#xff0c;还可以通过控制语句来改变执行顺序。本文介绍bash的控制语句用法。 一、条件语句 Bash 中的条件语句让我们可以决定一个操作是否被执行。结果取决于一个包在[[ ]]里的表达式。 bash中的检测命令由[[]]包起来&#xff0c;用于检测一个条…

第一次视频学习

1、了解AI答题应用 1.1 业务流程架构图 1.2 自定义上传题目流程 1.3 时序图 这个比较困难&#xff0c;第一次了解到流式&#xff0c;便于前端与用户交互

【微服务】springcloud-alibaba 配置多环境管理使用详解

目录 一、前言 二、配置多环境问题概述 2.1 什么是微服务多环境配置管理 2.1.1 微服务多环境配置管理问题起源 2.2 为什么要做多环境配置管理 2.3 微服务多环境配置管理解决方案 三、springboot 配置多环境管理解决方案 3.1 前置准备 3.1.1 搭建一个springboot工程 3.…

【PowerDesigner】创建和管理CDM之使用实体间关系

目录 &#x1f30a;1. PowerDesigner简介 &#x1f30d;1.1 常用模型文件 &#x1f30d;1.2 PowerDesigner使用环境 &#x1f30a;2. 创建和管理CDM &#x1f30d;​​​​​​2.1 新建CDM &#x1f30d;2.2 使用实体间关系 &#x1f30c;a. 使用联系 &#x1f30c;b. …

嵌入式仪器模块:DMM LCR SMU 及自动化测试软件

• 6 位数字表显示 • 24 位分辨率 • 250 KSPS 采样率 • 电源和数字 I/O 均采用隔离抗噪技术 应用场景 • 电压、电流、电阻、电感、电容的高精度测量 • 二极管/三极管测试 通道1222输入阻抗电压10 MΩHigh-Z, 10 MΩHigh-Z电流10 Ω50 mΩ / 2 Ω / 2 KΩ2 KΩ / 2 M…

如何理解external

external 函数应该只被外部函数调用但也可以被内部调用&#xff0c;但是这种内部调用也是有外部调用机制&#xff0c;即新产生message! 例子1 // SPDX-License-Identifier: GPL-3.0pragma solidity >0.8.2 <0.9.0;contract ExternalDemo{address public caller;functi…

【图解IO与Netty系列】Netty核心组件解析

Netty核心组件解析 Bootstrap & ServerBootstrapEventLoop & EventLoopGroupChannelChannelHandler & ChannelPipeline & ChannelHandlerContextChannelHandlerChannelPipelineChannelHandlerContext ChannelFuture Bootstrap & ServerBootstrap Bootstra…

A股所有公司ZL申请与创新绩效分析(2000-2022年)

数据简介&#xff1a;专利是创新成果的主要载体&#xff0c;专利所包含的技术、经济、法律等信息主要通过结构化专利专利文献著录项的形式加以呈现。专利申请与创新绩效已成为衡量上市公司竞争力的重要指标。目前&#xff0c;各国ZSCQ管理部门开发管理的开放式平台是获取ZL数据…

OBS 录屏软件:录制圆形头像画中画,设置卡通人像(保姆级教程,有步骤图,建议收藏)

Mac分享吧 文章目录 一、OBS录屏软件 圆形头像画中画效果注意&#xff1a;圆形画中画仅需要在软件中设置一次&#xff0c;每次录制&#xff0c;使用带有圆形头像画中画的场景 录制视频即可。该场景不可删除&#xff01;&#xff01;&#xff01;若删除&#xff0c;则需要重新设…

时序数据库是Niche Market吗?

引言 DB-Engines的流行程度排行从其评估标准[4]可以看出完全不能够做为市场规模的评估标准。甚至于在知道市场规模后可以用这个排行作为一个避雷手册。毕竟现存市场小&#xff0c;可预见增长规模小&#xff0c;竞争大&#xff0c;创新不足&#xff0c;那只能卷价格&#xff0c…

数据结构---查找

个人介绍 hello hello~ &#xff0c;这里是 code袁~&#x1f496;&#x1f496; &#xff0c;欢迎大家点赞&#x1f973;&#x1f973;关注&#x1f4a5;&#x1f4a5;收藏&#x1f339;&#x1f339;&#x1f339; &#x1f981;作者简介&#xff1a;一名喜欢分享和记录学习的…

运 算 符

算术运算符 算术运算符包括&#xff1a;&#xff0c;-&#xff0c;*&#xff0c;/&#xff0c;%&#xff0c;&#xff0c;-- 当左右两边都是数值型时&#xff0c;则做加法运算。 当左右两边有一方为字符串&#xff0c;则做拼接运算。任何一个 Java 对象都可以转换为字符串。 …

.Net实现SCrypt Hash加密

方案1 &#xff08;加密后存储“算法设置”、“盐(随机值)”、“Hash值”&#xff0c;以“$”分隔&#xff09;&#xff1a; //Nuget引入SCrypt.NET库 using Org.BouncyCastle.Crypto.Generators; using Scrypt; using System; using System.Security.Cryptography; namespace …

8.让画面动起来

一、Unity Shader中的内置变量&#xff08;时间篇&#xff09; 动画效果往往都是把时间添加到一些变量的计算中&#xff0c;以便在时间变化的同时也可以随之变化。Unity shader提供了一系列关于时间的内置变量来允许我们方便地在Shader中访问运行时间&#xff0c;实现各种动画…

暴雨推出X705显示器:23.8英寸100Hz IPS屏

IT资讯 6月 7 日消息&#xff0c;日前&#xff0c;暴雨发布了一款 23.8 英寸 IPS 显示器&#xff0c;直屏、支持 100Hz 刷新率。 据官方介绍&#xff0c;X705 显示器分辨率为 19201080&#xff0c;亮度为 300 尼特&#xff08;典型值&#xff09;&#xff0c;对比度 1000:1&…

从Log4j和Fastjson RCE漏洞认识jndi注入

文章目录 前言JNDI注入基础介绍靶场搭建漏洞验证注入工具 log4j RCE漏洞分析漏洞靶场检测工具补丁绕过 Fastjson RCE漏洞分析漏洞靶场检测工具补丁绕过 总结 前言 接着前文的学习《Java反序列化漏洞与URLDNS利用链分析》&#xff0c;想了解为什么 Fastjson 反序列化漏洞的利用…

关于用宽带(拨号)连接VPN无法上网,但是wifi或者热点就可以的问题

参考链接&#xff1a;https://zhuanlan.zhihu.com/p/580929250https://zhuanlan.zhihu.com/p/580929250 https://blog.csdn.net/Yaoyao2024/article/details/132245249文章浏览阅读10w次&#xff0c;点赞161次&#xff0c;收藏515次。很多同学在学习访问学校提供的资源时或者一…

队列和栈的实现

文章目录 队列队列的定义队列常见的基本操作队列的顺序存储结构实现 栈栈的定义栈的常见基本操作栈的顺序存储实现 栈的链式存储实现 队列 队列的定义 队列&#xff08;queue&#xff09;是只允许在一端进行插入操作&#xff0c;而在另一端进行删除操作的线性表。队列是一种先…

Vitis HLS 学习笔记--聚合与解聚-AXI主接口

目录 1. 简介 2. 用法及语法 3. 详细解读 4. 总结 1. 简介 在使用 Vitis HLS 工具进行硬件设计时&#xff0c;如果你在接口上使用了结构体&#xff0c;工具会自动把结构体里的所有元素组合成一个整体。就像把一堆零件组装成一个玩具一样。这样做的好处是&#xff0c;数据可…

【西瓜书】大题

1.线性回归 思路&#xff1a;ywxb&#xff0c;w为一维数组&#xff0c;求均方误差MSE&#xff0c;对w和b分别求偏导为0得到关于w和b的闭式求解。预测第十年的代入ywxb求解即可。 2.查准率、查全率 思路&#xff1a;先计算每个算法测试结果的混淆矩阵&#xff0c;再根据混淆矩阵…