外设驱动库开发笔记43:GPIO模拟SPI驱动

  SPI总线是我们常用的串行设备接口,一般情况下我们都会适应硬件SPI接口,但有些时候当硬件端口不足时,我们也希望可以使用软件来模拟SPI硬件接口,特别是要求不是很高的时候。在这一篇中我们将来讨论如何使用GPIO和软件来模拟SPI通讯接口。

1、功能概述

  SPI即串行外设接口,是一种同步串行通讯接口,用于微处理器及控制器和外围扩展芯片之间的串行连接,现已发展成为一种工业标准。

1.1、物理层

  SPI总线在物理层通常使用3条总线及1条片选线,3条总线分别为 SCK、 MOSI、 MISO,片选线为NSS,它们的作用介绍如下:
  NSS( Slave Select),从设备选择信号线,常称为片选信号线。在SPI协议中没有设备地址,所以需要使用NSS信号线来寻址,当主机要选择从设备时,把该从设备的NSS信号线设置为低电平,该从设备即被选中,即片选有效,接着主机开始与被选中的从设备进行SPI通讯。所以SPI通讯以NSS线置低电平为开始信号,以NSS线被拉高作为结束信号。
  SCK (Serial Clock),时钟信号线,用于通讯数据同步。它由通讯主机产生,决定了通讯的速率,不同的设备支持的最高时钟频率不一样,如 STM32 的 SPI 时钟频率最大为fpclk/2,两个设备之间通讯时,通讯速率受限于低速设备。
  MOSI (Master Output, Slave Input),主设备输出/从设备输入引脚。主机的数据从这条信号线输出,从机由这条信号线读入主机发送的数据,即这条线上数据的方向为主机到从机。
  MISO(Master Input,, Slave Output),主设备输入/从设备输出引脚。主机从这条信号线读入数据,从机的数据由这条信号线输出到主机,即在这条线上数据的方向为从机到主机。
  对于使用SPI总线来进行通讯的设备,一台主机可以与多台从机进行通讯,时钟与数据总线为公用,片选线每台从机都是独立的,具体连接方式如下图所示:

  也就是说,有多个SPI从设备与SPI主机通讯时,设备的时钟线和总线数据线 SCK、MOSI及MISO同时并联到相同的SPI总线上,即无论有多少个从设备,都共同只使用这 3 条总线。而每个从设备都拥有独立的一条NSS信号线,即有多少个从设备,就有多少条片选信号线。

1.2、协议层

  我们已经简述了SPI总线的物理连接方式,接下来我们再来了解一下具体的通讯协议。在了解协议前,我们需要清楚两个概念,即时钟的极性和时钟的相位。
  所谓时钟极性,通常称作CPOL,即是指在空闲状态下,时钟所处的电平状态。如果SCLK在数据发送之前和之后的空闲状态是高电平,那么就是CPOL=1;如果空闲状态SCLK是低电平,那么就是 CPOL=0。
  而时钟的相位,通常称作CPHA,就是指数据采样是在时钟脉冲的第1个跳变沿还是在第2个跳变沿。如果在SCK的第1个跳变沿进行数据采样,则CPHA=0;如果在SCK的第2个跳变沿采样,则CPHA=1。
  在通讯协议中,根据CPOL和CPHA的取值不同存在4种不同的配置方式,不同的配置方式对应不同的通讯模式。
  (1)当CPOL=0,CPHA=0时,空闲状态时钟SCK的电平要保持在低电平,而数据的采样时刻在时钟脉冲的奇数跳变边沿,其时序图如下:

  (2)当CPOL=0,CPHA=1时,空闲状态时钟SCK的电平要保持在低电平,而数据的采样时刻在时钟脉冲的偶数跳变边沿,其时序图如下:

  (3)当CPOL=1,CPHA=0时,空闲状态时钟SCK的电平要保持在高电平,而数据的采样时刻在时钟脉冲的奇数跳变边沿,其时序图如下:

  (4)当CPOL=1,CPHA=1时,空闲状态时钟SCK的电平要保持在高电平,而数据的采样时刻在时钟脉冲的偶数跳变边沿,其时序图如下:

  根据这时钟情况,将SPI总线的工作模式分为4种,如下图所示:

  而只有主机与从机拥有相同的工作模式时,主从机之间才可以正常通讯。在实际应用中,“模式 0”与“模式 3”是比较常见的工作模式,但在我们的驱动设计中应该兼顾这4种模式,以具备更广泛的适应性。

2、驱动设计与实现

  我们已经简要的描述了SPI通讯总线的物理连接和通讯协议,接下来我们将根据其协议的特性设计并实现基于GPIO模拟的SPI总线驱动。

2.1、对象定义

  我们依然使用基于对象的思想来实现基于GPIO模拟的SPI总线驱动。既然是基于对象,那么在使用一个对象之前我们需要先获得这个对象。所以我们必须先定义一个基于GPIO模拟的SPI总线的对象。

2.1.1、对象的抽象

  我们要得到基于GPIO模拟的SPI总线的对象,需要先分析其基本特性。一般来说,一个对象至少包含属性与操作两方面的特性。接下来我们就来从这两个方面思考一下基于GPIO模拟的SPI总线的对象。
  我们首先来考虑对象的属性,作为属性肯定是用于标识或记录对象特征的东西。我们在前面已经了解到SPI总线的一些特点和独特设定,那么这些特点和设定是否能成为对象的属性呢?
  我们考虑到作为同步总线,SPI总线的控制需要时钟,这关系到SPI总线的通讯速率,这一通讯速率不仅配置SPI总线的工作方式也标识当前的工作状态,所以我们将工作速率作为模拟SPI总线对象的一个属性。在前面我们讨论过SPI总线的工作模式,工作模式由CPOL和CPHA决定,所以在初始化总线时就会确定工作模式,所以我们需要记录CPOL和CPHA,我们将CPOL和CPHA也作为对象的属性。
  接下来我们考虑GPIO模拟SPI总线的操作问题。我们将那些对象要实现的,并且依赖于具体的平台的行为实现定义为对象的操作。对于GPIO模拟SPI总线来说,我们需要通过总线发送数据和接收数据,而接收和发送的实现都依赖于具体的软硬件平台,所以我们将发送数据和接收数据定义为对象的操作。SPI总线作为同步总线需要时钟,而时钟操作也是依赖于具体的软硬件平台来实现,所以我们将始终的控制也作为对象的操作。
  根据上述我们对GPIO模拟SPI总线的分析,我们可以定义GPIO模拟SPI总线对象类型如下:

/*定义GPIO模拟SPI接口对象*/
typedef struct SimuSPIObject{uint16_t CPOL:1;uint16_t CPHA:1;uint16_t period:14;   //确定速度为大于0K小于等于400K的整数,默认为100Kvoid (*SetSCKPin)(SimuSPIPinValueType op);        //设置SCL引脚void (*SetMOSIPin)(SimuSPIPinValueType op);        //设置SDA引脚uint8_t (*ReadMISOPin)(void);                  //读取SDA引脚位void (*Delayus)(volatile uint32_t period);    //速度延时函数
}SimuSPIObjectType;

2.1.2、对象初始化

  我们知道,在使用一个对象之前需要先对其进行初始化,所以这里我们来考虑GPIO模拟SPI对象的初始化函数。一般来说,初始化函数需要处理几个方面的问题。一是检查输入参数是否合理;二是为对象的属性赋初值;三是对对象做必要的初始化配置。据此我们设计GPIO模拟SPI对象的初始化函数如下:

/* GPIO模拟SPI通讯初始化 */
void SimuSPIInitialization(SimuSPIObjectType *simuSPIInstance,//初始化的模拟SPI对象uint32_t speed,      //时钟频率SimuSPICPOLType CPOL,        //时钟极性SimuSPICPHAType CPHA,        //时钟频率SimuSPISetSCKPin setSCK,     //SCK时钟操作函数指针SimuSPISetMOSIPin setMOSI,   //MOSI操作函数指针SimuSPIReadMISOPin getMISO,  //MISO操作函数指针SimuSPIDelayus delayus       //微秒延时操作函数指针)
{if((simuSPIInstance==NULL)||(setSCK==NULL)||(setMOSI==NULL)||(getMISO==NULL)||(delayus==NULL)){return;}simuSPIInstance->SetSCKPin=setSCK;simuSPIInstance->SetMOSIPin=setMOSI;simuSPIInstance->ReadMISOPin=getMISO;simuSPIInstance->Delayus=delayus;/*初始化速度,默认100K*/if((speed>0)&&(speed<=500)){simuSPIInstance->period=500/speed;}else{simuSPIInstance->period=5;}simuSPIInstance->CPOL=CPOL;simuSPIInstance->CPHA=CPHA;/*拉高总线,使处于空闲状态*/if(simuSPIInstance->CPOL==SimuSPI_POLARITY_LOW){simuSPIInstance->SetSCKPin(SimuSPI_Reset);}else{simuSPIInstance->SetSCKPin(SimuSPI_Set);}
}

2.2、对象操作

  我们已经定义了对象类型,也实现了对象的初始化函数,但我们还没有实现对象的具体操作,所以接下来我们就来实现对象的具体操作。

2.2.1、数据的发送

  在我们使用SPI来实现数据通讯时,免不了要发送数据,所以在我们使用GPIO模拟SPI端口时就需要解决数据发送的问题。这里我们考虑使用模拟SPI发送一个字节的问题,因为发送多个字节无非是多重复几次。根据前面分析的在不同模式下的时序图我们可以编写GPIO模拟SPI发送一个字节的函数如下:

/* 通过模拟SPI发送一个字节 */
static void SendByteBySimuSPI(SimuSPIObjectType *simuSPIInstance,uint8_t byte)
{
//    uint8_t length[2]={8,16};if(simuSPIInstance->CPOL==SimuSPI_POLARITY_LOW){/*拉低SCL引脚准备数据传输*/simuSPIInstance->SetSCKPin(SimuSPI_Reset);if(simuSPIInstance->CPHA==SimuSPI_PHASE_1EDGE)  //模式0{for(uint8_t count = 0; count < 8; count++){if(byte & 0x80)     //每次发送最高位{simuSPIInstance->SetMOSIPin(SimuSPI_Set);}else{simuSPIInstance->SetMOSIPin(SimuSPI_Reset);}byte <<= 1;         //发送一位后,左移一位simuSPIInstance->Delayus(simuSPIInstance->period);simuSPIInstance->SetSCKPin(SimuSPI_Set);simuSPIInstance->Delayus(simuSPIInstance->period);simuSPIInstance->SetSCKPin(SimuSPI_Reset);}}else    //模式1{for(uint8_t count = 0; count < 8; count++){if(byte & 0x80)     //每次发送最高位{simuSPIInstance->SetMOSIPin(SimuSPI_Set);}else{simuSPIInstance->SetMOSIPin(SimuSPI_Reset);}byte <<= 1;         //发送一位后,左移一位simuSPIInstance->SetSCKPin(SimuSPI_Set);simuSPIInstance->Delayus(simuSPIInstance->period);simuSPIInstance->SetSCKPin(SimuSPI_Reset);simuSPIInstance->Delayus(simuSPIInstance->period);}}}else{/*拉低SCL引脚准备数据传输*/simuSPIInstance->SetSCKPin(SimuSPI_Set);if(simuSPIInstance->CPHA==SimuSPI_PHASE_1EDGE)  //模式2{for(uint8_t count = 0; count < 8; count++){if(byte & 0x80)     //每次发送最高位{simuSPIInstance->SetMOSIPin(SimuSPI_Set);}else{simuSPIInstance->SetMOSIPin(SimuSPI_Reset);}byte <<= 1;         //发送一位后,左移一位simuSPIInstance->Delayus(simuSPIInstance->period);simuSPIInstance->SetSCKPin(SimuSPI_Reset);simuSPIInstance->Delayus(simuSPIInstance->period);simuSPIInstance->SetSCKPin(SimuSPI_Set);}}else    //模式3{for(uint8_t count = 0; count < 8; count++){if(byte & 0x80)     //每次发送最高位{simuSPIInstance->SetMOSIPin(SimuSPI_Set);}else{simuSPIInstance->SetMOSIPin(SimuSPI_Reset);}byte <<= 1;         //发送一位后,左移一位simuSPIInstance->Delayus(simuSPIInstance->period);simuSPIInstance->SetSCKPin(SimuSPI_Reset);simuSPIInstance->Delayus(simuSPIInstance->period);simuSPIInstance->SetSCKPin(SimuSPI_Set);}}}
}

2.2.2、数据的接收

  对于SPI端口来说不光需要发送数据,也需要从对方接收数据,同样的我们再次考虑接收一个字节的情况。同样我们根据前面对不同模式下,接收数据的时序要求可以编写接收一个字节的函数如下:

/* 通过模拟SPI接收一个字节 */
static uint8_t RecieveByteBySimuSPI(SimuSPIObjectType *simuSPIInstance)
{uint8_t receive = 0;if(simuSPIInstance->CPOL==SimuSPI_POLARITY_LOW){/*拉低SCL引脚准备数据传输*/simuSPIInstance->SetSCKPin(SimuSPI_Reset);if(simuSPIInstance->CPHA==SimuSPI_PHASE_1EDGE)  //模式0{for(uint8_t count = 0; count < 8; count++ ){simuSPIInstance->SetSCKPin(SimuSPI_Set);simuSPIInstance->Delayus(simuSPIInstance->period);receive <<= 1;if(simuSPIInstance->ReadMISOPin()){receive++;}simuSPIInstance->SetSCKPin(SimuSPI_Reset);simuSPIInstance->Delayus(simuSPIInstance->period);}}else    //模式1{simuSPIInstance->SetSCKPin(SimuSPI_Set);simuSPIInstance->Delayus(simuSPIInstance->period);for(uint8_t count = 0; count < 8; count++ ){simuSPIInstance->SetSCKPin(SimuSPI_Reset);simuSPIInstance->Delayus(simuSPIInstance->period);receive <<= 1;if(simuSPIInstance->ReadMISOPin()){receive++;}simuSPIInstance->SetSCKPin(SimuSPI_Set);simuSPIInstance->Delayus(simuSPIInstance->period);}simuSPIInstance->SetSCKPin(SimuSPI_Reset);}}else{/*拉低SCL引脚准备数据传输*/simuSPIInstance->SetSCKPin(SimuSPI_Set);if(simuSPIInstance->CPHA==SimuSPI_PHASE_1EDGE)  //模式2{for(uint8_t count = 0; count < 8; count++ ){simuSPIInstance->SetSCKPin(SimuSPI_Reset);simuSPIInstance->Delayus(simuSPIInstance->period);receive <<= 1;if(simuSPIInstance->ReadMISOPin()){receive++;}simuSPIInstance->SetSCKPin(SimuSPI_Set);simuSPIInstance->Delayus(simuSPIInstance->period);}}else    //模式3{simuSPIInstance->SetSCKPin(SimuSPI_Reset);simuSPIInstance->Delayus(simuSPIInstance->period);for(uint8_t count = 0; count < 8; count++ ){simuSPIInstance->SetSCKPin(SimuSPI_Set);simuSPIInstance->Delayus(simuSPIInstance->period);receive <<= 1;if(simuSPIInstance->ReadMISOPin()){receive++;}simuSPIInstance->SetSCKPin(SimuSPI_Reset);simuSPIInstance->Delayus(simuSPIInstance->period);}simuSPIInstance->SetSCKPin(SimuSPI_Set);}}return receive;
}

3、驱动的使用

  我们已经设计并实现了GPIO模拟SPI总线的驱动程序,接下来我们将基于这一驱动设计一个简单的用例,以验证驱动程序的正确性。

3.1、声明并初始化对象

  我们是基于对象来实现GPIO模拟SPI驱动的,所以在开始之前我们需要声明一个模拟SPI对象如下:

SimuSPIObjectType simuSPI;

  声明了这一对象之后,我们还需要对这一变量进行初始化才能使用。前面我们已经实现了对象变量的初始化函数,使用这一函数就可方便的初始化对象变量,该函数有多个输入数:

SimuSPIObjectType *simuSPIInstance,//初始化的模拟SPI对象
uint32_t speed,              //时钟频率
SimuSPICPOLType CPOL,        //时钟极性
SimuSPICPHAType CPHA,        //时钟相位
SimuSPIDataSizeType dataSize,//数据长度
SimuSPISetSCKPin setSCK,     //SCK时钟操作函数指针
SimuSPISetMOSIPin setMOSI,   //MOSI操作函数指针
SimuSPIReadMISOPin getMISO,  //MISO操作函数指针
SimuSPIDelayus delayus       //微秒延时操作函数指针

  在这些参数中simuSPIInstance为我们想要初始化的对象变量的指针。时钟极性、时钟相位以及数据长度都是枚举量,我们根据实际的使用要求选择输入即可。时钟频率为我们希望的时钟速度,最大500K。而余下的几个参数则都是回调函数的指针。而这几个函数则是我们在应用程序中需要实现的,它们的原型如下:

//设置SCL引脚
typedef void (*SimuSPISetSCKPin)(SimuSPIPinValueType op);
//设置SDA引脚
typedef void (*SimuSPISetMOSIPin)(SimuSPIPinValueType op);
//读取SDA引脚位
typedef uint8_t (*SimuSPIReadMISOPin)(void);
//速度延时函数
typedef void (*SimuSPIDelayus)(volatile uint32_t period);

  这些函数的实现与具体的应用平台有关,我们在STM32F407上基于HAL库实现的这些函数如下:

//设置SCL引脚
void SPISCKOperation(SimuSPIPinValueType op)
{GPIO_PinState PinState=(GPIO_PinState)op;HAL_GPIO_WritePin(GPIOSPI, SPISCK, PinState);
}//设置SDA引脚
void SPIMOSIOperation(SimuSPIPinValueType op)
{GPIO_PinState PinState=(GPIO_PinState)op;HAL_GPIO_WritePin(GPIOSPI, SPIMOSI, PinState);
}//读取SDA引脚位
uint8_t SPIMISORead(void)
{if(HAL_GPIO_ReadPin(GPIOSPI, SPIMISO)){return 1;}return 0;
}

  而延时操作函数则采用我们系统中通用的Delayus。有了这些参数后我们合一调用初始化函数对对象变量进行初始化如下:

/* GPIO模拟SPI通讯初始化 */SimuSPIInitialization(&simuSPI,//初始化的模拟SPI对象500,              //时钟频率SimuSPI_POLARITY_LOW,        //时钟极性SimuSPI_PHASE_1EDGE,        //时钟频率SimuSPI_DataSize_8Bit,//数据长度SPISCKOperation,     //SCK时钟操作函数指针SPIMOSIOperation,   //MOSI操作函数指针SPIMISORead,  //MISO操作函数指针Delayus       //微秒延时操作函数指针);

3.2、基于对象进行操作

  初始化这个对象变量后,我们就可以基于它操作这一对象了。我们基于驱动实现一个简单的读写数据的操作如下:

/* 使用模拟SPI读写数据*/
void SimuSPIDataExchange(void)
{uint8_t wDatas[3];uint8_t rDatas[3];/* 通过模拟SPI向从站写数据 */WriteDataBySimuSPI(&simuSPI,wDatas,3,1000);HAL_Delay(10);/* 通过模拟SPI自从站读数据 */ReadDataBySimuSPI(&simuSPI,rDatas, 3,1000);HAL_Delay(10);/* 通过模拟SPI实现对从站先写数据紧接读数据组合操作 */WriteReadDataBySimuSPI(&simuSPI, wDatas,3,rDatas, 3,1000);HAL_Delay(10);/* 通过模拟SPI实现对从站同时写和读数据组合操作*/WriteWhileReadDataBySimuSPI(&simuSPI, wDatas,rDatas,3,1000);}

  我们分别测试了读取数据、下发数据、同时写和读数据以及写完后再读等几种情况的测试,效果还是比较理想的。

4、应用总结

  在这一篇中,我们设计并实现了基于GPIO模拟的SPI接口驱动程序。并在此基础上设计了一个简单的测试应用。我们通过GPIO模拟的SPI接口向SPI接口的Flash中写数据、读数据、同时读写和先写后读试验都没有问题。
  在使用驱动程序时需要注意,由于是使用GPIO模拟的SPI端口,其速度是受到限制的,目前最快能够支持到500K,再快就不能支持了。所以这个驱动程序只能应用于通讯速度小于500K的设备。

欢迎关注:

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

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

相关文章

外设驱动库开发笔记44:DDC114 ADC驱动

在产品设计过程中&#xff0c;很多时候都会用到ADC器件&#xff0c;而在一些特殊场合还需要一些特别的ADC器件。我们在这篇中将讨论常用于医疗器件方面的&#xff0c;DDC114这款电流输入ADC&#xff0c;并为其设计一个驱动程序。 1、功能概述 模数转换器DDC114是一款电流输入型…

PID控制器改进笔记之七:改进PID控制器之防超调设定

我们已经设计了PID控制器&#xff0c;并根据实际使用的情况对器进行了诸多的改进。在这一篇中我们将讨论如何改进PID控制器超调的问题。 1、问题提出 在前面的文章中&#xff0c;我们曾推导过增量式PID控制器的公式&#xff0c;并且对其进行了离散化以适用于程序实现&#xff…

软件设计开发笔记3:基于QT的Modbus RTU主站

Modbus是一种常见的工业系统通讯协议。在我们的设计开发工作中经常使用到它。在这一篇中我们将简单实现一个基于QT的Modbus RTU主站上位工具。 1、概述 Modbus RTU主站应用很常见&#xff0c;有一些是通用的&#xff0c;有一些是专用的。而这里我们希望实现一个主要针对我们的…

外设驱动库开发笔记45:MS4515DO压力传感器驱动

很多时候我们需要检测流量和压力这些参数&#xff0c;比如我们要检测大气压&#xff0c;或者通过测量差压来获得输送流体的流量等&#xff0c;都需要用到压力传感器。这一篇我们就来讨论MS4515DO压力传感器的数据获取。 1、功能概述 MS4515DO是TE公司推出的一款基于PCB安装的小…

外设驱动库开发笔记48:MCP4725单通道DAC驱动

在产品设计过程中&#xff0c;我们经常会遇到数模转换的应用需求。在本篇种我们就来讨论一下MCP4725单通道数模转换器的驱动设计与实现。 1、功能概述 MCP4725是一个低功耗&#xff0c;高精度&#xff0c;单通道&#xff0c;12位缓冲电压输出数字到模拟转换器(DAC)与非易失性存…

如何确保不使用动态内存

在许多嵌入式应用程序中&#xff0c;内存分配必须是静态的&#xff0c;而不是动态的。意味着在应用程序中不应使用对malloc()或free()等内容的调用&#xff0c;因为它们可能会在运行时失败&#xff08;内存不足、堆碎片&#xff09;。 但是&#xff0c;当与第三方库甚至 C/C 标…

go 单元测试 testing 打印输出_2020,你需掌握go 单元测试进阶篇

本文说明go语言自带的测试框架未提供或者未方便地提供的测试方案&#xff0c;主要是用于解决写单元测试中比较头痛的依赖问题。也就是伪造模式&#xff0c;经典的伪造模式有桩对象(stub),模拟对象(mock)和伪对象(fake)。比较幸运的是&#xff0c;社区有丰富的第三方测试框架支持…

一文读懂Git工作流

Git是目前最流行的代码管理工具&#xff0c;相信大家也都是在用Git来管理自己团队的源代码。 团队一般为了规范开发&#xff0c;保持良好的代码提交记录以及维护 Git 分支结构清晰&#xff0c;方便后续维护等&#xff0c;都会迫切需要一个比较规范的 Git 工作流。 本文就是在…

xbox360fsd更新游戏封面_游戏类短视频创作指南

一&#xff0e;起步阶段1.内容发布垂直&#xff0c;整体风格一致&#xff0c;选定一个品类的游戏内容风格持续更新注意&#xff1a;冷启动时期不要频繁更换游戏类型2.账号IP化 根据自身风格特色打造独特的风格账号。有利延长账号生命周期&#xff0c;提升粉丝转化率。搞笑、中二…

开发者们都在关注的网站

开发者们都在关注的网站 &#x1f609; 综合类&#xff08;5个&#xff09; 1、GitHub 全球最大的编程开源社区&#xff0c;很多优秀的开源项目都在上边&#xff0c;不知道这个都不要说自己是程序员&#x1f602; 访问地址&#xff1a;https://github.com 2、CSDN 全球最大中…

ios framework 调用第三方 framework_Python基础:标准库和常用的第三方库

Python的标准库有&#xff1a;名称作用datetime为日期和时间处理同时提供了简单和复杂的方法。zlib直接支持通用的数据打包和压缩格式&#xff1a;zlib&#xff0c;gzip&#xff0c;bz2&#xff0c;zipfile&#xff0c;以及 tarfile。random提供了生成随机数的工具。math为浮点…

作图神器ProcessOn - 免费好用

因工作需要&#xff0c;我经常需要花一些流程图&#xff0c;时序图&#xff0c;架构图什么的&#xff0c;之前使用的Windows系统&#xff0c;大部分情况下就用的Visio来画图。后来为了工作方便&#xff0c;换成了Mac电脑&#xff0c;结果发现Mac上没有Visio&#xff0c;然后就在…

三电平igbt死区时间计算_基于大功率三电平IGBT模块并联的参考设计

当前的可再生能源行业中&#xff0c;光伏和风力发电均面临着补贴逐步退坡&#xff0c;平价上网时代即将到来的挑战。为应对这一挑战&#xff0c;光伏逆变器和风力变流器厂家研发的新品单机功率越来越高&#xff0c;以取得更低的单位功率成本。市场上1.5MW的集中式光伏逆变器和3…

手把手教你搭建开发环境之Java开发

大家好呀&#xff0c;从今天开始&#xff0c;我们的手把手系列教程就正式开始啦。 如果你觉得本文对你有一些帮助&#xff0c;欢迎大家关注、点赞、分享给需要的小伙伴们&#xff0c;谢谢大家啦。 前言 Java虽然是一个比较老的语言&#xff0c;但到现在依然充满了活力&#x…

opc服务器组态文件已写保护_远程组态软件不仅方便了PLC无线远程监控,也大大降低了工程成本...

远程组态软件不仅方便了PLC无线远程监控&#xff0c;也大大降低了工程成本组态软件远程监控1.本地上位SCADA系统采集分布各地现场PLC等设备运行的数据&#xff0c;并可以下发控制指令&#xff1b;2.提供稳定的OPC接口服务&#xff0c;常年稳定运行&#xff0c;规模可达10万数据…

奇妙的安全旅行之加密算法概述

前言 hi&#xff0c;大家好呀&#xff0c;信息安全作为当前社会中比较重要的一个课题&#xff0c;已经覆盖了人们生活的方方面面&#xff0c;虽然有时候我们可能并没有意识到&#xff0c;其实信息安全防护已经在背后默默的保护我们的信息安全了。例如&#xff0c;当你在互联网…

怎么调节电机启动值_开关式智能充电机-全自动充电机-铅酸电池充电机品牌-济南能华...

开关式智能充电机-全自动充电机-铅酸电池充电机品牌-济南能华NHCD系列 全自动智能充电机&#xff0c;可调智能充电机&#xff0c;可调直流充电机&#xff0c;可调全自动充电机 &#xff0c;可调蓄电池充电机 便携式可调智能充电机 便携式全自动充电机 大功率可调充电机 大功率智…

奇妙的安全旅行之MD算法

hi&#xff0c;大家好&#xff0c;今天我们开始介绍消息摘要算法中的MD&#xff08;Message Digest&#xff09;算法&#xff0c;MD算法家族包括&#xff1a;MD2&#xff0c;MD4&#xff0c;MD5&#xff0c;MD算法生成的消息摘长度要都是128位的。 其中MD5算法是消息摘要算法的…

的图层类型有哪些_东莞都市领航平面设计培训班都学习哪些内容?

平面设计的工作稳定性是很高的&#xff0c;经济繁荣时期毫无疑问&#xff0c;即使经济下滑&#xff0c;仍不会有很大影响&#xff0c;以前两年为例&#xff0c;北美的大规模裁员浪潮&#xff0c;给高科技行业带来巨大冲击&#xff0c;放慢了高科技产品的开发速度&#xff0c;当…

dockerfile拉取私库镜像_还在用Alpine作为你Docker的Python开发基础镜像?其实Ubuntu更好一点...

原文转载自「刘悦的技术博客」https://v3u.cn/a_id_173一般情况下&#xff0c;当你想为你的Python开发环境选择一个基础镜像时&#xff0c;大多数人都会选择Alpine&#xff0c;为什么&#xff1f;因为它太小了&#xff0c;仅仅只有 5 MB 左右&#xff08;对比 Ubuntu 系列镜像接…