关于MCU产品开发参数存储的几种方案

关于MCU产品开发参数存储的几种方案

  • Chapter1 关于MCU产品开发参数存储的几种方案
  • Chapter2 单片机参数处理[保存与读取]
  • Chapter3 嵌入式设备参数存储技巧
  • Chapter4 STM32硬件I2C的一点心得(AT24C32C和AT24C64C)


Chapter1 关于MCU产品开发参数存储的几种方案

原文链接

在工作中,几乎所有参与产品开发的产品都将实现参数存储功能。

通常,参数存储将使用以下存储介质,例如:eeprom,spi闪存,nand闪存,SD卡等,至于如何存储,现在有很多种类。

1.使用eeprom(以at24c02为例)定义结构,然后定义两个结构变量,一个用于读取参数,一个用于立即写入修改的参数。

参考:2.使用spi_flash(以w25q64为例)方法1与使用eeprom方法相同。

方法2使用文件系统并创建一个ini文件来获取参数。

Chapter2 单片机参数处理[保存与读取]

原文链接:https://blog.csdn.net/WangSanHuai2010/article/details/6988583

/*------------------------------------------------------------
 Func: 加载参数到系统
 Time: 2011-11-13
 Ver.: V1.0
 Note:
------------------------------------------------------------*/
void WFS_LoadParams(uint16 Addr,uint16 *Buffer,uint16 Length)
{
	Addr<<=1;Length<<=1;
	EEPROM_Read(Addr+2,(uint8 *)Buffer,Length);
}

参数按以上方法加载到内存,注意参数的起始地址为2,这是因为前两个字节区域要用来做校验用。

/*------------------------------------------------------------
 Func: 保存参数
 Time: 2011-11-13
 Ver.: V1.0
 Note:
------------------------------------------------------------*/
void WFS_SaveParams(uint16 Addr,uint16 *Buffer,uint16 Length)
{
	Addr<<=1;Length<<=1;
	EEPROM_Write(Addr+2,(uint8 *)Buffer,Length);
}

以上方法保存参数到EEPROM中,实际上与Load方法一一对应。

/*------------------------------------------------------------
 Func: 参数系统初始化
 Time: 2011-11-13
 Ver.: V1.0
 Note:
------------------------------------------------------------*/
uint8 WFS_InitParams(void *DefaultValues,uint16 Length)
{
	uint16 D;
	EEPROM_Read(0,(uint8 *)(&D),2);
	if(D!=0x55AA){
		D=0x55AA;
		EEPROM_Write(0,(uint8 *)(&D),2);
		EEPROM_Write(2,(uint8 *)DefaultValues,Length);
		return 0xFF;
	}
	return 0x00;
}

参数的初始化方法,首先读取EEPROM的0位置处的数据,判断是否为0x55AA合法标志,若不是0x55AA,则说明参数区为首次使用,需要进行初始化默认参数填充,于是将DefaultValues所指的默认值填入EEPROM中,并设置0x55AA标志,以后每次上电便会检测到参数的合法性。

以下为使用示例,存储了地址码,波特率,数据位,停止位四个参数,以及一个18字的数组。

const uint16 WFS_ParmasValue_Default[]=
{
	1,9600,8,1,
	0,0,0,0,0,0,
	0,0,0,0,0,0,
	0,0,0,0,0,0,
};

以下为参数进行初始化并加载到内存:


WFS_InitParams(WFS_ParmasValue_Default,sizeof(WFS_ParmasValue_Default));
WFS_LoadParams(0,&DevAddr,1);
WFS_LoadParams(1,&BaudRate,1);
WFS_LoadParams(2,&DataLength,1);
WFS_LoadParams(3,&StopBits,1);
WFS_LoadParams(4,Array,18);

以下为参数修改后进行保存:

BaudRate=115200;
StopBits=2;
WFS_SaveParams(1,&BaudRate,1);
WFS_LoadParams(3,&StopBits,1);

Chapter3 嵌入式设备参数存储技巧

原文链接

1、前言
想必做嵌入式产品开发都遇到过设备需要保存参数,常用的方式就是按照结构体的方式管理参数,保存时将整个结构体数据保存在 Flash 中,方便下次读取。

1.1、目的
本文时分析嵌入式/单片机中参数保存的几种方式的优点和缺点(仅针对单片机/嵌入式开发而言),同时针对以结构体的方式解决一些弊端问题(重点在第 3 节)。

2、参数保存格式
2.1、结构体格式
该方式是嵌入式/单片机中开发最常用的,将所有的系统参数通过结构体的方式定义,然后保存数据,介绍一下该方式的优缺点。

储存方式:二进制 bin 文件格式
1
优点:

管理简单:无需额外的代码直接就能很方便的管理参数
内存最小:通过结构体的形式保存在Flash中,占用内存最小
缺点:

1.扩展性差:

从产品角度来说,产品需要升级,若是涉及增加参数,则升级后参数通常无法校验通过(通常包含长度校验等),导致参数被恢复默认
若是每个模块都存在自己的独有结构体参数定义,删除/新增时势必影响到其他的,导致设备升级后参数错乱(结构体中的变量地址在 bin 文件中是固定的)
2.阅读性差:
若参数需要导出,bin文件没有可读性

改进措施:
结构体增加预留定义,若之后需要新增参数,则在预留空间新增即可,能在一定程度上解决扩展性差的问题,即新增不影响原有的结构体大小和其他成员变量的位置,删除恢复成预留即可。

为啥说只能在一定程度上解决该问题,因为之后的升级某些模块可能很长时间或者从不需要增加新的参数,这种势必就会造成内存的无效占用,或者有些模块频繁增加参数导致预留大小不够等问题,只能在前期设计时多加思考预留的分配情况(毕竟内存只有那么大)

/*****************************
           改进之前
*****************************/
 
typedef struct
{
    uint8_t testParam;
    uint8_t testParam2;
} TestParam_t;    /* 某模块参数 */
 
typedef struct
{
    uint8_t testParam;
    uint8_t testParam2;
    TestParam_t tTestParam;
} SystemParam_t; /* 系统参数 */
 
/*****************************
           改进之后
*****************************/
 
typedef struct
{
    uint8_t testParam;
    uint8_t testParam2;
    uint8_t reserve[6];    // 预留
} TestParam_t;    /* 某模块参数 */
 
typedef struct
{
    uint8_t testParam;
    uint8_t testParam2;
    TestParam_t tTestParam;
    uint8_t reserve[50];   // 预留
} SystemParam_t; /* 系统参数 */

2.2、JSON格式
最近Json格式很是流行使用,特别是数据交换中用的很多,但是它也可以用来保存参数使用,JSON 的是 “{键:值}” 的方式。

储存方式:字符串格式,即文本的形式
1
优点:

扩展性好:由于Json的格式,找到对应键值(一般都是该变量的标识),就能找到对应的值
阅读性好:有标识所以导出参数文件通过普通的文本文件打开都能看懂
缺点:

管理相对复杂:没有结构体那么简单,不熟还得先学习 JSON 的写法
内存占用较大:内容不只有值,而且都按照字符串的形式保存的
使用相关困难:需要解析,C语言虽然有开源库,但是由于语言性质使用不方便,C++ 反而使用简单

{
    "SYS":
    {
        "testParam" : 2,
        "testParam2" : 5,
        "tTestParam":
        {
            "testParam" : 2,
            "testParam2" : 5
        }
    }
}
 
//压缩字符串为:
{"SYS":{"testParam":2,"testParam2":5,"tTestParam":{"testParam":2,"testParam2":5}}}

2.3、键值格式
和上述的 JSON 格式很类似,都是键值对的格式,但是比JSON简单

储存方式:字符串格式,即文本的形式
1
优点:

扩展性好:找到对应键值(一般都是该变量的标识),就能找到对应的值
阅读性好:有标识所以导出参数文件通过普通的文本文件打开都能看懂
缺点:

内存占用较大:内容不只有值,而且都按照字符串的形式保存的
使用稍微困难:需要简单解析处理
管理不变:不方便按照一定的规则管理各模块的参数

testParam=2
testParam2=5
T_testParam=2
T_testParam2=5

2.4 其他
还有其他,如 xml (类似JSON)等,就不多介绍了

3、编译器检查结构体的大小和成员变量的偏移
在第 2 节中介绍了关于参数保存的三种方式,但是对于嵌入式单片机开发而言,Flash 大小不富裕,所以通常都是通过二进制的形式保存的,所以这节重点解决结构体管理保存参数的扩展性问题。

先说一下痛点(虽然对扩展性问题做了改进措施,除了前面讲到的问题,还有其他痛点,虽不算问题,但是一旦出现往往最要命)

在原来的预留空间中新增参数,要确保新增后结构体的大小不变,否则会导致后面的其他参数偏移,最后升级设备后参数出现异常(如果客户升级那就是要命啊)
确保第一点,就必须在每次新增参数都要计算检查一下结构体的大小有没有发生变化,而且有没有对结构体中的其他成员也产生影响
每次新增参数,手动计算和校验 99% 可以检查出来,但是人总有粗心的时候(加班多了,状态不好…),且结构体存在填充,一不留神就以为没问题,提交代码,出版本(测试不一定能发现),给客户,升级后异常,客户投诉、扣工资(难啊…)
遇到这种问题后:难道编译器就不能在编译的时候检查这个大小或者结构体成员的偏移吗,每次手动计算校验好麻烦啊,一不留神还容易算错 # _ #

按照正常情况,编译器可不知道你写的结构体大小和你想要的多大,所以检查不出来(天啊,崩溃了0.0…)

别急,有另类的方式可以达到这种功能,在编译时让编译器为你检查,而且准确性 100%(当然,这个添加新参数时你还得简单根据新增的参数大小减少预留的大小,这个是必须要的)

见代码:

/**
  * @brief 检查结构体大小是否符合
  *        在编译时会进行检查
  * @param type 结构体类型
  * @param size 结构体检查大小
  */
#define TYPE_CHECK_SIZE(type, size) extern int sizeof_##type##_is_error [!!(sizeof(type)==(size_t)(size)) - 1]
 
/**
  * @brief 结构体成员
  * @param type   结构体类型
  * @param member 成员变量
  */
#define TYPE_MEMBER(type, member) (((type *)0)->member)
 
 
/**
  * @brief 检查结构体成员大小是否符合
  *        在编译时会进行检查
  * @param type 结构体类型
  * @param member 结构体类型
  * @param size 结构体检查大小
  */
#define TYPE_MEMBER_CHECK_SIZE(type, member, size) extern int sizeof_##type##_##member##_is_error \
    [!!(sizeof(TYPE_MEMBER(type, member))==(size_t)(size)) - 1]
 
 
/**
  * @brief 检查结构体中结构体成员大小是否符合
  *        在编译时会进行检查
  * @param type 结构体类型
  * @param member 结构体类型
  * @param size 结构体检查大小
  */
#define TYPE_CHILDTYPE_MEMBER_CHECK_SIZE(type, childtype, member, size) extern int sizeof_##type##_##childtype##_##member##_is_error \
    [!!(sizeof(TYPE_MEMBER(type, childtype.member))==(size_t)(size)) - 1]
 
 
/**
  * @brief 检查结构体成员偏移位置是否符合
  *        在编译时会进行检查
  * @param type 结构体类型
  * @param member 结构体成员
  * @param value 成员偏移
  */
#define TYPE_MEMBER_CHECK_OFFSET(type, member, value) \
         extern int offset_of_##member##_in_##type##_is_error \
        [!!(__builtin_offsetof(type, member)==((size_t)(value))) - 1]
 
 
/**
  * @brief 检查结构体成员偏移位置是否符合
  *        在编译时会进行检查
  * @param type 结构体类型
  * @param member 结构体成员
  * @param value 成员偏移
  */
#define TYPE_CHILDTYPE_MEMBER_CHECK_OFFSET(type, childtype, member, value) \
         extern int offset_of_##member##_in_##type##_##childtype##_is_error \
        [!!(__builtin_offsetof(type, childtype.member)==((size_t)(value))) - 1]

通过以上代码,就能解决这个问题,这个写法只占用文本大小,编译后不占内存!!!

用法:

typedef struct
{
    uint8_t testParam;
    uint8_t testParam2;
    uint8_t reserve[6];    // 预留
} TestParam_t;    /* 某模块参数 */
 
TYPE_CHECK_SIZE(TestParam_t, 8); // 检查结构体的大小是否符合预期
 
typedef struct
{
    uint8_t testParam;
    uint8_t testParam2;
    TestParam_t tTestParam;
    uint8_t reserve[54];   // 预留
} SystemParam_t; /* 系统参数 */
 
TYPE_CHECK_SIZE(SystemParam_t, 64); // 检查结构体的大小是否符合预期
TYPE_MEMBER_CHECK_OFFSET(SystemParam_t, tTestParam, 2); // 检查结构体成员tTestParam偏移是否符合预期

假设新增了参数,预留写错了,导致结构体的大小不符合,则编译时报错,且提示内容也能快速定位问题。
在这里插入图片描述

Chapter4 STM32硬件I2C的一点心得(AT24C32C和AT24C64C)

原文链接:https://blog.csdn.net/whitefish520/article/details/110070972

从设备读写
一般的EEPROM,像AT24C02这种小容量的设备,地址都只需要8位,页大小一般是16字节一个页
而像AT24C32C、AT24C64C这种32K、64K字节的大容量EEPROM,8位地址是不够的,使用了16位地址,页大小在这两个器件中也变成了32字节
正是由于容量的不同,导致代码上需要做差异化处理,才能正确读取EEPROM芯片

以下代码可以参考,写的时候无法跨页,因此写大量数据的时候,只能一页一页的写,两次写之间保证5MS的间隔
读没有跨页的影响,可以一次性把全部数据读出来,但是要注意,读和写之间,是要有5MS的间隔的,否则读不到数据。也就是说每次写完,延迟5MS,就能保证后续的程序没有问题。

//#define I2C_MEMADD_SIZE		I2C_MEMADD_SIZE_8BIT			//小容量EEPROM芯片8位地址用此参数
#define I2C_MEMADD_SIZE		I2C_MEMADD_SIZE_16BIT		//大容量EEPROM芯片16位地址用此参数
#define ADDR_AT24C02_Write 0xA0		//EEPROM I2C写地址
#define ADDR_AT24C02_Read 0xA0+1	//EEPROM I2C读地址

typedef enum SYS_PARA_ENUM {
	LOCAL_IP = 0,
	UDP_LOCAL_PORT,
	UDP_PC_PORT,
	NETMASK,
	GATEWAY,
	SERVER_IP,
	SERVER_PORT,
	VERSION,
	SN_NUM,	//16字节
	SENSOR_TYPE = SN_NUM + 4,
	SENSOR_DATA_TYPE,
	SENSOR_INTERVAL,
	SYS_PARA_MAX,		//end
}sys_para_e;

//请注意,为了方便flash读写操作,此处的每一项均设为uint32_t类型
//如果不为uint32_t类型,则flash_write函数将出现错误
typedef struct SYS_PARA_TYPE {
	uint32_t local_ip;			//本机IP,大端模式
	uint32_t udp_local_port;	//本机端口号
	uint32_t udp_pc_port;		//PC端口号
	uint32_t netmask;			//本机子网掩码,大端模式
	uint32_t gateway;			//本机网关,大端模式
	uint32_t server_ip;			//服务器IP,大端模式
	uint32_t server_port;		//服务器端口号
	uint32_t version;			//stm32软件版本号
	uint32_t sn_num[4];			//SN号	5 6 7 8
	uint32_t sensor_type;		//传感器类型 9
	uint32_t sensor_data_type;	//传感器数据类型
	uint32_t sensor_interval;	//传感器采集时间间隔
}sys_para_t;

/* -----------------------------------------------------------------------------
函数名:  	i2c_write
作者:    	glx
日期:    	2020-11-10
功能:    	数据写入eeprom
输入参数:	pData:数据指针
返回值:  	类型:HAL_StatusTypeDef
			HAL_OK:操作成功
			HAL_ERROR:操作失败
修改记录:
------------------------------------------------------------------------------*/
HAL_StatusTypeDef i2c_write(void *pData)
{
	HAL_StatusTypeDef ret = HAL_ERROR;
	uint8_t i, page, pageSize;
	uint8_t *p = (uint8_t *)pData;
	if(pData != NULL)
	{
		if(I2C_MEMADD_SIZE == I2C_MEMADD_SIZE_8BIT)
		{
			pageSize = 16;			//一页16字节,不能跨页写
			page = SYS_PARA_MAX/4;
		}
		else if(I2C_MEMADD_SIZE == I2C_MEMADD_SIZE_16BIT)
		{
			pageSize = 32;			//一页32字节,不能跨页写
			page = SYS_PARA_MAX/8;
		}
		//写完整的页
		for(i = 0; i<page; i++)
		{
			ret = HAL_I2C_Mem_Write(&hi2c1, ADDR_AT24C02_Write, i*pageSize, I2C_MEMADD_SIZE, p, pageSize, 100);
			p += pageSize;
			if(ret != HAL_OK)
			{
				printf("I2C_Write Sys Para write error\r\n");
				return HAL_ERROR;
			}
			HAL_Delay(5);
		}
		//写残缺的页
		if(SYS_PARA_MAX > (page*4))	
		{
			ret = HAL_I2C_Mem_Write(&hi2c1, ADDR_AT24C02_Write, i*pageSize, I2C_MEMADD_SIZE, p, 4*SYS_PARA_MAX-pageSize*page, 100);
			if(ret != HAL_OK)
			{
				printf("I2C_Write Sys Para write error\r\n");
				return HAL_ERROR;
			}
			HAL_Delay(5);
		}
	}
	else
	{
		printf("I2C_Write pData NULL\r\n");
		return HAL_ERROR;
	}
	return HAL_OK;
}

/* -----------------------------------------------------------------------------
函数名:  	i2c_read
作者:    	glx
日期:    	2020-11-10
功能:    	读取eeprom数据
输入参数:	pData:数据指针
返回值:  	类型:HAL_StatusTypeDef
			HAL_OK:操作成功
			HAL_ERROR:操作失败
修改记录:
------------------------------------------------------------------------------*/
HAL_StatusTypeDef i2c_read(void *pData)
{
	HAL_StatusTypeDef ret = HAL_ERROR;
	if(pData != NULL)
	{
		ret = HAL_I2C_Mem_Read(&hi2c1, ADDR_AT24C02_Read, 0, I2C_MEMADD_SIZE, (uint8_t *)pData, SYS_PARA_MAX*4, 100);
		if(ret != HAL_OK)
		{
			printf("I2C_Read Sys Para read error\r\n");
			return HAL_ERROR;
		}		
	}
	else
	{
		printf("I2C_Read pData NULL\r\n");
		return HAL_ERROR;
	}
	return HAL_OK;
}

读写的代码


//用于保存系统参数
sys_para_t sys_para = {0};	
//用于保存系统默认参数
sys_para_t default_para = {
	.local_ip = 107<<24 | 10<<16 | 168<<8 | 192,
	.udp_local_port = 18080,
	.udp_pc_port = 18081,
	.netmask = 0<<24 | 255<<16 | 255<<8 | 255,
	.gateway = 1<<24 | 10<<16 | 168<<8 | 192,
	.server_ip = 9<<24 | 10<<16 | 168<<8 | 192,
	.server_port = 18082,
	.version = 20201124,
	.sn_num[0] = 0xffffffff,
	.sn_num[1] = 0xffffffff,
	.sn_num[2] = 0xffffffff,
	.sn_num[3] = 0xffffffff,
	.sensor_type = SENSOR_DOOR,
	.sensor_data_type = SENSOR_DATA_GPIO,
	.sensor_interval = 1000,
};

i2c_read(&sys_para);
i2c_write(&default_para);

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

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

相关文章

Python 批量检测ip地址连通性,以json格式显示(支持传参单IP或者网段)

代码 ########################################################################## File Name: check_ip_test.py# Author: eight# Mail: 18847097110163.com # Created Time: Thu 11 Apr 2024 08:52:45 AM CST################################################…

突破界限 千视将在 NAB 2024 展会上展示领先的 AV over IP 技术

突破界限&#xff01;千视将在 NAB 2024 展会上展示领先的 AV over IP技术 作为AV over IP领域的先驱者&#xff0c;Kiloview将于2024年4月14日至17日在NAB展会&#xff08;展台号&#xff1a;SU6029&#xff09;隆重登场&#xff0c;展示我们领先业界的AV over IP产品、解决方…

Windows下安装GPU版Pytorch

升级Driver到最新版本 Windows搜索栏中输入设备管理器找到显示适配器一项&#xff0c;点击展开&#xff0c;你将看到你的NVIDIA显卡列在其中右键点击你的NVIDIA显卡&#xff0c;选择更新驱动软件…。在弹出的对话框中&#xff0c;选择自动搜索更新的驱动软件。之后&#xff0c…

nginx反向代理conf

打开nginx配置。 对登录功能测试完毕后&#xff0c;接下来&#xff0c;我们思考一个问题&#xff1a;前端发送的请求&#xff0c;是如何请求到后端服务的&#xff1f; 前端请求地址&#xff1a;http://localhost/api/employee/login 后端接口地址&#xff1a;http://localho…

计算机网络——NAT技术

目录 前言 前篇 引言 SNAT&#xff08;Source Network Address Translation&#xff09;源网络地址转换 SNAT流程 确定性标记 DNAT&#xff08;Destination Network Address Translation&#xff0c;目标网络地址转换&#xff09; NAT技术重要性 前言 本博客是博主用于…

ShardingSphere再回首

概念&#xff1a; 连接&#xff1a;通过协议 方言及库存储的适配&#xff0c;连接数据和应用&#xff0c;关注多模数据苦之间的合作 增量&#xff1a;抓取库入口流量题提供重定向&#xff0c; 流量变形(加密脱敏)/鉴权/治理(熔断限流)/分析等 可插拔&#xff1a;微内核 DDL:cr…

ssm+vue的实验室课程管理系统(有报告)。Javaee项目,ssm vue前后端分离项目。

演示视频&#xff1a; ssmvue的实验室课程管理系统&#xff08;有报告&#xff09;。Javaee项目&#xff0c;ssm vue前后端分离项目。 项目介绍&#xff1a; 采用M&#xff08;model&#xff09;V&#xff08;view&#xff09;C&#xff08;controller&#xff09;三层体系结构…

来补上LangChain的CookBook(二)

LangChain Experssion Language 简称LCEL&#xff0c;感觉就是为了节省代码量&#xff0c;让程序猿们更好地搭建基于大语言模型的应用&#xff0c;而在LangChain框架中整了新的语法来搭建promptLLM的chain。来&#xff0c;大家直接看官网链接&#xff1a;LangChain Expression …

pandas(day10)

一. 各各品类产品交易指数对比 获取文件名 files glob.glob("./*.xlsx")# 读取数据&#xff0c;并改列名&#xff0c;增加一列 品牌 dfs [] for f in files:t f[2:4]df pd.read_excel(f)df["品牌"] tif t "拜耳":df.rename(columns{"…

C语言读取 .ico 文件并显示数据

原来是想做光标编辑器&#xff0c;自己把绘图板的内容导出为光标格式 鼠标指针文件格式解析——Windows&#xff08;一&#xff09; (qq.com) 代码来源自 Icons | Microsoft Learn 鄙人又补充些变量可以运行微软的代码 简单代码如下 #include <stdio.h> #include &l…

uniapp 小程序获取WiFi列表

<template><view ><button click"getWifiList">获取WiFi列表</button><scroll-view:scroll-top"scrollTop"scroll-yclass"content-pop"><viewclass"itemInfo"v-for"(item, index) in wifiList&…

unity——Button组件单击双击长按功能

1.实现单击、双击、长按功能 using UnityEngine; using UnityEngine.Events; using UnityEngine.EventSystems; public class ButtonControl_Click_Press_Double : MonoBehaviour, IPointerClickHandler, IPointerDownHandler, IPointerUpHandler, IPointerExitHandler {publi…

debian安装和基本使用

debian安装和基本使用 文章目录 debian安装和基本使用1. 为什么选择debian2. 如何下载Debian2.1 小型安装镜像2.2 完整安装镜像 3. Debian操作系统安装3.1 创建Debian虚拟机3.2 安装操作系统 4. Debian系统的初始设置4.1 桌面环境的配置4.2 配置网络4.3 生效网络配置4.4 配置de…

第24次修改了可删除可持久保存的前端html备忘录:文本编辑框不再隐藏,又增加了哔哩哔哩搜索和必应搜索

第24次修改了可删除可持久保存的前端html备忘录:文本编辑框不再隐藏&#xff0c;又增加了哔哩哔哩搜索和必应搜索. <!DOCTYPE html> <html lang"zh"><head><meta charset"UTF-8"><meta name"viewport" content"…

Python学习之-matplotlib详解

前言&#xff1a; Matplotlib 是一个 Python 的图表绘制库&#xff0c;广泛用于生成各种静态、动态和交互式的图表。它能够创建线图、散点图、条形图、饼图、直方图、误差线图、箱型图、热图、子图网络、散点矩阵等图表。 安装 Matplotlib&#xff1a; pip install matplotli…

RocketMQ的docker安装和SpringBoot的集成

1.Docker安装 1.1创建docker-compose.yml文件 version: 3.5 services:rmqnamesrv:image: foxiswho/rocketmq:servercontainer_name: rmqnamesrvports:- 9876:9876networks:rmq:aliases:- rmqnamesrvrmqbroker:image: foxiswho/rocketmq:brokercontainer_name: rmqbrokerports…

docker完美安装分布式任务调度平台XXL-JOB

分布式任务调度平台XXL-JOB 1、官方文档 自己看 https://www.xuxueli.com/xxl-job/#1.1%20%E6%A6%82%E8%BF%B0 2、使用docker部署 本人使用的腾讯云&#xff0c;安装docker暴露一下端口&#xff0c;就很舒服的安装这个服务了。 docker pull xuxueli/xxl-job-admin:2.4.03…

ELFK的部署

目录 一、ELFK&#xff08;FilebeatELK&#xff09; 1. Filebeat的作用 2. ELFK工作流程 3. ELFK的部署 3.1 服务器配置 3.2 服务器环境 3.3 安装filebeat 3.4 修改filebeat主配置文件 3.5 在logstash组件所在节点&#xff08;apache节点&#xff09;上新建一个logstas…

关闭笔记本自带的键盘

目录 一、问题 二、方法 【方法一】 【方法二】 一、问题 笔记本自带的键盘上的个别按键又坏了&#xff0c;可能是因为使用电脑时&#xff0c;最先坏的几个按键那里温度比较高&#xff0c;久而久之就烧坏了吧。距离上次更换新键盘才差不多一年&#xff0c;所以不打算再买新…

librosa.beat.beat_track()报错解决记录

在使用librosa.beat.beat_track(yy, srsr)时报错&#xff1a;AttributeError: module scipy.signal has no attribute hann&#xff0c;很奇怪的问题&#xff0c;大家好像都没有遇到这个错误&#xff1a; 很奇怪&#xff0c;这个hann明明是scipy.signal.windows里面的。。虚拟环…