11:STM32---spl通信
目录
一:SPL通信
1:简历
2:硬件电路
3:移动数据图
4:SPI时序基本单元
A : 开/ 终条件
B:SPI时序基本单元
A:模式0
B:模式1
C:模式2
D:模式3
C:SPl时序
A:发送指令
B: 指定地址写
C:指定地址读
二: W25Q64
1:简历
2: 硬件电路
3:W25Q64框图
4: Flash操作注意事项
5:指令集
三:案例
A: 软件SPI读写W25Q64
1: 连接图
2:代码
B: 硬件SPI读写W25Q64
1:简历
2:框图
3:SPI基本结构
4: 主模式全双工连续传输
5: 非连续传输
6:连接图
7: 代码
一:SPL通信
1:简历
SPI(Serial Peripheral Interface)是由Motorola公司开发的一种通用数据总线
四根通信线:SCK(Serial Clock)、MOSI(Master Output Slave Input)、MISO(Master Input Slave Output)、SS(Slave Select)
同步(有时钟线),全双工 (传输线有2条,发送和接受线路)
支持总线挂载多设备(一主多从)
SPl没有应答机制
2:硬件电路
所有SPI设备的SCK、MOSI、MISO分别连在一起
主机另外引出多条SS控制线,分别接到各从机的SS引脚
输出引脚配置为推挽输出,输入引脚配置为浮空或上拉输入
SS也叫CS片选信号 : 和全部的从机连接, 用于选择主机和那个从机进行通信, 低电平有效; 每个从机的SS(CS)都和主机的SSX相互连接, SS对于主机来说的话,就是输出信号, 从机的话就是输入信号
IO的配置 : 都是以STM32为主角进行的. 主机输出信号配置---推挽输出, 主机输入信号配置----浮空或上拉输入
SCK : 时钟线, 时钟线完全由主机掌控, 所以对于主机来说,时钟线为输出; 对于所有从机来说,时钟线都为输入; 这样主机的同步时钟,就能送到各个从机了
MOSI : 主机输出,从机输入
MISO : 主机输入,从机输出
关于CS和MISO主机输入,从机输出 : 当从机没有被选中的时候,也就是SS段电平为1; 从机的MISO主机输入,从机输出必须切换为高阻态 , 高阻态就相当于引脚断开,不输出任何电平; 这样就可以防止,一条线有多个输出,而导致的电平冲突的问题了; 在SS为低电平时,MISO才允许变为推挽输出----从机的操作-------一般情况下我们只需要写主机的程序,从机的程序不需要我们操心
3:移动数据图
交换数据, 高位先行
SPI的数据收发,都是基于字节交换,这个基本单元来进行的 (移位模型)
首先,我们规定波特率发生器时钟的上升沿主机和从机都移出数据; 下将沿移入数据;
数据为从左往右运动,所以是高为先行, 首先波特率发生器时钟产生上生沿, 主机把它的最高位的数据放在MOSI上面, 从机把它最高位置的数据放在MISO上面; 在由特率发生器产生的下降沿移入数据; 在MISO数据线上从机的最高位的数据放在主机的最低位置上面; MOSI上面主机最高位的数据放在从机的最低位置
4:SPI时序基本单元
A : 开/ 终条件
起始条件:SS从高电平切换到低电平
终止条件:SS从低电平切换到高电平
B:SPI时序基本单元
在任何操作下, 我们只管主机(只写主机的代码) , 从机它自动操作(不用写从机的代码)
我们经常使用的是模式0
A:模式0
交换一个字节(模式0)
CPOL=0:空闲状态时,SCK为低电平
CPHA=0:SCK第一个边沿移入数据,第二个边沿移出数据
SCL上升沿主机和从机同步移入数据; SCL下降沿主机和从机同步移出数据
/** * @brief SPL交换数据--使用的为模式0DI(MOSI)----SPI主机输出从机输入DO(MISO)-------SPI主机输入从机输出我们只操作主机:首先主机移出最高位,放在MOSI上面,---主机操作需要我们来齐次从机把数据放在MISO上面----从机的操作不需要我们管* @param ByteSend: 主机给从机发送的数据* @retval 主机读取的数据----即从机给主机发送的数据*/ uint8_t MySPI_SwapByte(uint8_t ByteSend) { MySPI_W_SCK(0);//一般来说&是用来清零的; //一般来说|是用来值一的;uint8_t ByteReceive=0x00;for (uint8_t i=0;i<8;i++){MySPI_W_MOSI(ByteSend & (0x80>>i)); //MOSI主机输出数据 1000 0000 /*我们只操作主机: SCL上升沿主机和从机同步移入数据, 从机会自动把主机给它的最高为移动到了从机里面---从机不需要我们操作主机操作 : 主机需要把从机给它发送的数据移动到了主机里面---即读取MISO线上的数据*/MySPI_W_SCK(1);if (MySPI_R_MISO() == 1){ByteReceive |= (0x80 >> i);}//MySPI_R_MISO主机读取数据MySPI_W_SCK(0);//SCL下降沿主机和从机同步移出数据//|---置1}return ByteReceive; }
在任何操作下, 我们只管主机(只写主机的代码) , 从机它自动操作(不用写从机的代码)
B:模式1
交换一个字节(模式1)
CPOL=0:空闲状态时,SCK为低电平
CPHA=1:SCK第一个边沿移出数据,第二个边沿移入数据
SPl为了可以配置更多的时钟芯片, 给我们了2个可以自己调节的位, 分别为:CPOL (Clock Polarity)时钟极性和CPHA (Clock Phase)时钟相位配置这两个为, 就构成了4种模式
模式1 : 波特率发生器时钟的上升沿主机和从机都移出数据; 下将沿移入数据; 模式1的数据移动方式和 3:移动数据图 一样 , 详情参考----3:移动数据图
C:模式2
交换一个字节(模式2)
CPOL=1:空闲状态时,SCK为高电平
CPHA=0:SCK第一个边沿移入数据,第二个边沿移出数据
D:模式3
交换一个字节(模式3)
CPOL=1:空闲状态时,SCK为高电平
CPHA=1:SCK第一个边沿移出数据,第二个边沿移入数据
C:SPl时序
A:发送指令
规定 : SPL起始的第一个字节为指令集
发送指令
向SS指定的设备,发送指令(0x06)--0x06使能
B: 指定地址写
指定地址写
向SS指定的设备,发送写指令(0x02),---0x02写入的指令集
随后在指定地址(Address[23:0])下,写入指定数据(Data)
SPl没有应答机制, 交换一个字节后, 直接开始交换下一个字节
C:指定地址读
指定地址读
向SS指定的设备,发送读指令(0x03),---0x03发送指令的指令集
随后在指定地址(Address[23:0])下,读取从机数据(Data)
二: W25Q64
1:简历
W25Qxx系列是一种低成本、小型化、使用简单的非易失性存储器,常应用于数据存储、字库存储、固件程序存储等场景
存储介质:Nor Flash(闪存)
时钟频率:80MHz / 160MHz (Dual SPI) / 320MHz (Quad SPI)
存储容量(24位地址):
W25Q40: 4Mbit / 512KByte
W25Q80: 8Mbit / 1MByte
W25Q16: 16Mbit / 2MByte
W25Q32: 32Mbit / 4MByte
W25Q64: 64Mbit / 8MByte
W25Q128: 128Mbit / 16MByte
W25Q256: 256Mbit / 32MByte
2: 硬件电路
3:W25Q64框图
4: Flash操作注意事项
非易失性存储器---掉电不丢失
写入操作时:
写入操作前,必须先进行写使能------------是一种保护措施,防止你误操作的
每个数据位只能由1改写为0,不能由0改写为1--------------Flash并没有像RAM那样的, 直接完全覆盖改写的能力. eg:在某一个直接的储存单元首先储存了0xaa 1010 1010 在储存0x55 0101 0101 因为Flash没有直接覆盖数据的能力, 在加上第二条规定的限制实际储存的数据为: 0000 0000 不是0x55, 使用在写入第二给数据前必须擦除之前的数据
写入数据前必须先擦除,擦除后,所有数据位变为1--------------有专门的擦除电路把之前写的数据都值1(0xFF), 就可以弥补第二条规定的不足
擦除必须按最小擦除单元进行------------不能指定某一个字节去擦除, 要擦,就得一大片一起擦, 在我们这个芯片里; 你可以选择,整个芯片擦除, 也可以选择,按块擦除,或者按扇区擦除; 最小的擦除单元,就是一个扇区, 个扇区,是4KB,就是4096个字节
连续写入多字节时,最多写入一页的数据,超过页尾位置的数据,会回到页首覆盖写入--------一个写入时序,最多只能写一页的数据,也就是256字节; 一个页缓存区,它只有256字节; Flash的写入,太慢了. 跟不上SPI的频率. 所以写入的数据,会先放在RAM里暂存. 必须得,从页起始位置开始,才能最大写入256字节, 如果从页中间的地址开始写, 那写到页尾时,这个地址就会跳回到页首, 这会导致地址错乱
写入操作结束后,芯片进入忙状态,不响应新的读写操作--------要想知道芯片什么时候结束忙状态了, 我们可以使用读取状态寄存器的指令, 看一下状态寄存器的BUSY位是否为1, BUSY位为0时,芯片就不忙了,我们再进行操作
在发出擦除指令后,芯片也会进入忙状态, 我们也得等待忙状态结束后,才能进行后续操作
扇区擦除也是写入所以需要使能
读取操作时:
直接调用读取时序,无需使能,无需额外操作,没有页的限制,
读取操作结束后不会进入忙状态,但不能在忙状态时读取,
5:指令集
Write Enable----写使能指令集
Write Disable --------写失能指令集Read Status Register-1---------读状态寄存器1--作用: 判断寄存器在不在忙, 具体见 二: 4
Page Program----------页编程, 写数据,max为256个字节
Sector Erase (4KB)-------------按4KB的扇区擦除
JEDEC ID----------读取ID
Read Data-----读取数据
三:案例
A: 软件SPI读写W25Q64
1: 连接图
因为我们使用的是软件模拟SPL通信, 所以原则上外设的引脚可以随便和32端口连接, 使用端口模拟SPL通信
2:代码
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "mySPl.h"
#include "w25q64.h "//一般来说&是用来清零的;
//一般来说|是用来值一的;
void MySPI_W_SS(uint8_t BitValue)
{//也叫做CS片选段----在低电平是有效GPIO_WriteBit(GPIOA,GPIO_Pin_4,(BitAction)BitValue);}
void MySPI_W_SCK(uint8_t BitValue)
{//CLK(SCK) SPI时钟GPIO_WriteBit(GPIOA,GPIO_Pin_5,(BitAction)BitValue);}
void MySPI_W_MOSI(uint8_t BitValue)
{//MOSI-----主机输出GPIO_WriteBit(GPIOA,GPIO_Pin_7,(BitAction)BitValue);
}uint8_t MySPI_R_MISO(void)
{//MISO-----主机输入return GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_6);
}/**
* @brief DO(MISO) SPI主机输入从机输出---连接的是PA6; 都是以主机的角度看输出引脚配置为推挽输出,输入引脚配置为浮空或上拉输入PA6----浮空或上拉输入; 剩下的全部为推挽输出* @retval 无*/
void MYSPL_init()
{RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;//推挽输出GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4 | GPIO_Pin_5|GPIO_Pin_7;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA, &GPIO_InitStructure);GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //上拉输入GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA, &GPIO_InitStructure);//在开始时候默认SS(CS)为高电平,SCK为低电平MySPI_W_SS(1);MySPI_W_SCK(0);}void SPL_Start()
{MySPI_W_SS(1);MySPI_W_SS(0);
}
void SPL_Stop()
{MySPI_W_SS(0);MySPI_W_SS(1);
}/**
* @brief SPL交换数据--使用的为模式0DI(MOSI)----SPI主机输出从机输入DO(MISO)-------SPI主机输入从机输出我们只操作主机:首先主机移出最高位,放在MOSI上面,---主机操作需要我们来齐次从机把数据放在MISO上面----从机的操作不需要我们管* @param ByteSend: 主机给从机发送的数据* @retval 主机读取的数据----即从机给主机发送的数据*/
uint8_t MySPI_SwapByte(uint8_t ByteSend)
{ MySPI_W_SCK(0);//一般来说&是用来清零的;
//一般来说|是用来值一的;uint8_t ByteReceive=0x00;for (uint8_t i=0;i<8;i++){MySPI_W_MOSI(ByteSend & (0x80>>i)); //MOSI主机输出数据 1000 0000 /*我们只操作主机: SCL上升沿主机和从机同步移入数据, 从机会自动把主机给它的最高为移动到了从机里面---从机不需要我们操作主机操作 : 主机需要把从机给它发送的数据移动到了主机里面---即读取MISO线上的数据*/MySPI_W_SCK(1);if (MySPI_R_MISO() == 1){ByteReceive |= (0x80 >> i);}//MySPI_R_MISO主机读取数据MySPI_W_SCK(0);//SCL下降沿主机和从机同步移出数据//|---置1}return ByteReceive;
}//W25Q64_WaitBusy等待不忙的函数, 事后等待,只需要在写入操作之后调用---因为在写入后等待不忙读取的时候肯定不忙
//而事前等待,在写入操作和读取操作之前,都得调用
//我们采用事前等待
void W25Q64_init()
{MYSPL_init();}
/*** @brief 读取设备的ID号
步骤: 起始,先交换发送指令9F,随后连续交换接收3个字节,停止;
连续接收的3个字节: 第一个字节是广商ID”表示了是哪个广家生产.
后两个学节---是设备ID; 设备ID的高8为---表示存储器类型, 低8为--表示容量
* @param MID : 输出8位的厂商ID@param DID : 输出16位的设备ID因为函数内的返回值只能返一个,而用指针,只要知道地址就可以写入;
可以直接改变外补的2个参数, 相当于返回2个参数* @retval 无*/
void W25Q64_ReadID(uint8_t *MID, uint16_t *DID)
{ 一般来说&是用来清零的;//一般来说|是用来值一的;SPL_Start();MySPI_SwapByte(0x9F);//先交换发送指令9F*MID = MySPI_SwapByte(0xFF);//返回的第一个字节是广商ID; 这时候我们给我从机发送的数据没有实际的意义*DID = MySPI_SwapByte(0xFF);//返回的第二个字节设备ID的高8为---表示存储器类型*DID <<= 8;*DID |= MySPI_SwapByte(0xFF);返回的第二个字节设备低8为--表示容量SPL_Stop();
}/*** @brief 写使能函数*/
void W25Q64_WriteEnable(void)
{SPL_Start();MySPI_SwapByte(W25Q64_WRITE_ENABLE);//规定 : SPL起始的第一个字节为指令集SPL_Stop();
}/**
* @brief 等待忙函数--状态寄存器1 :作用看寄存器忙不忙要想知道芯片什么时候结束忙状态了, 我们可以使用读取状态寄存器的指令, 看一下状态寄存器的BUSY位是否为1, BUSY位为0时,芯片就不忙了,我们再进行操作*/
void W25Q64_WaitBusy(void)
{ uint32_t Count;SPL_Start();MySPI_SwapByte(W25Q64_READ_STATUS_REGISTER_1);//规定 : SPL起始的第一个字节为指令集Count=5000;while ((MySPI_SwapByte(W25Q64_DUMMY_BYTE) & 0x01) ==0x01){Count--;if (Count==0){break;}}SPL_Stop();
}
/*** @brief 写页编程-----主机给从机发送数据步骤 : 1---先发送指令; 2--然后连发3个字节,就是24位地址 3---之后继续发送DataByte1(数据1)、DataByte2、DataByte3, 最大是DataByte256* @param Address 步骤中的1和2步---也就是发送的地址
* @param *DataArray 主机给从机发送的数据, 这里面为一个数组* @param Count 数组的长度* @retval 无*/
void W25Q64_PageProgram(uint32_t Address, uint8_t *DataArray, uint16_t Count)
{ W25Q64_WaitBusy();//写入操作结束后,芯片进入忙状态,不响应新的读写操作W25Q64_WriteEnable();//写入操作前,必须先进行写使能SPL_Start();MySPI_SwapByte(W25Q64_PAGE_PROGRAM);//规定 : SPL起始的第一个字节为指令集MySPI_SwapByte(Address>>16); //一共为24位,把数据后移16为,先把前8为也就是第一个直接发送给从机MySPI_SwapByte(Address>>8);//发送给从机的第二给字节MySPI_SwapByte(Address);//发送给从机的第三给字节//下面为主机给从机发送的真正的数据for (uint8_t i=0; i<Count; i++){MySPI_SwapByte(DataArray[i]);}SPL_Stop();}/*** @brief 按4KB的扇区擦除* @param Address 擦除的地址步骤: 需要先发送指令0x20,再发送3个字节的地址,就行了* @retval 无*/
void W25Q64_SectorErase(uint32_t Address)
{ W25Q64_WaitBusy();//在发出擦除指令后,芯片也会进入忙状态, 我们也得等待忙状态结束后,才能进行后续操作W25Q64_WriteEnable(); //扇区擦除也是写入所以需要使能SPL_Start();MySPI_SwapByte(W25Q64_SECTOR_ERASE_4KB);MySPI_SwapByte(Address>>16); //一共为24位,把数据后移16为,先把前8为也就是第一个直接发送给从机MySPI_SwapByte(Address>>8);//发送给从机的第二给字节MySPI_SwapByte(Address);//发送给从机的第三给字节SPL_Stop();}/*** @brief 主机接受从机给主机发送的数据
步骤:流程是,交换发送指令03,再发送3个字节地址; 随后转入接收,就可以依次接收数据了* @param Address 起始行位置,范围:1~4* @param DataArray 起始列位置,范围:1~16* @param Count 要显示的数字,范围:0~1111 1111 1111 1111* @retval 无*/
void W25Q64_ReadData(uint32_t Address, uint8_t *DataArray, uint32_t Count)
{ W25Q64_WaitBusy();//读取操作结束后不会进入忙状态,但不能在忙状态时读取SPL_Start();MySPI_SwapByte(W25Q64_READ_DATA);//规定 : SPL起始的第一个字节为指令集MySPI_SwapByte(Address>>16); //一共为24位,把数据后移16为,先把前8为也就是第一个直接发送给从机MySPI_SwapByte(Address>>8);//发送给从机的第二给字节MySPI_SwapByte(Address);//发送给从机的第三给字节//下面为主机正在接受的数据, 从机给主机发送数据for (uint8_t i=0; i<Count; i++){DataArray[i] = MySPI_SwapByte(W25Q64_DUMMY_BYTE); //1111 1111 没有实际的意义}SPL_Stop();
}#define W25Q64_WRITE_ENABLE 0x06
#define W25Q64_WRITE_DISABLE 0x04
#define W25Q64_READ_STATUS_REGISTER_1 0x05
#define W25Q64_READ_STATUS_REGISTER_2 0x35
#define W25Q64_WRITE_STATUS_REGISTER 0x01
#define W25Q64_PAGE_PROGRAM 0x02
#define W25Q64_QUAD_PAGE_PROGRAM 0x32
#define W25Q64_BLOCK_ERASE_64KB 0xD8
#define W25Q64_BLOCK_ERASE_32KB 0x52
#define W25Q64_SECTOR_ERASE_4KB 0x20
#define W25Q64_CHIP_ERASE 0xC7
#define W25Q64_ERASE_SUSPEND 0x75
#define W25Q64_ERASE_RESUME 0x7A
#define W25Q64_POWER_DOWN 0xB9
#define W25Q64_HIGH_PERFORMANCE_MODE 0xA3
#define W25Q64_CONTINUOUS_READ_MODE_RESET 0xFF
#define W25Q64_RELEASE_POWER_DOWN_HPM_DEVICE_ID 0xAB
#define W25Q64_MANUFACTURER_DEVICE_ID 0x90
#define W25Q64_READ_UNIQUE_ID 0x4B
#define W25Q64_JEDEC_ID 0x9F
#define W25Q64_READ_DATA 0x03
#define W25Q64_FAST_READ 0x0B
#define W25Q64_FAST_READ_DUAL_OUTPUT 0x3B
#define W25Q64_FAST_READ_DUAL_IO 0xBB
#define W25Q64_FAST_READ_QUAD_OUTPUT 0x6B
#define W25Q64_FAST_READ_QUAD_IO 0xEB
#define W25Q64_OCTAL_WORD_READ_QUAD_IO 0xE3#define W25Q64_DUMMY_BYTE 0xFF //这个数据实际没有意义#endifuint8_t MID;
uint16_t DID;uint8_t ArrayWrite[] = {0x55, 0x66, 0x77, 0x88};
uint8_t ArrayRead[4];int main(void)
{OLED_Init();W25Q64_init();OLED_ShowString(1, 1, "MID: DID:");OLED_ShowString(2, 1, "W:");OLED_ShowString(3, 1, "R:");W25Q64_ReadID(&MID, &DID);OLED_ShowHexNum(1, 5, MID, 2);OLED_ShowHexNum(1, 12, DID, 4);W25Q64_SectorErase(0x000000);//擦除W25Q64_PageProgram(0x000000, ArrayWrite, 4);//写页编程W25Q64_ReadData(0x000000, ArrayRead, 4);//读取OLED_ShowHexNum(2, 3, ArrayWrite[0], 2);OLED_ShowHexNum(2, 6, ArrayWrite[1], 2);OLED_ShowHexNum(2, 9, ArrayWrite[2], 2);OLED_ShowHexNum(2, 12, ArrayWrite[3], 2);OLED_ShowHexNum(3, 3, ArrayRead[0], 2);OLED_ShowHexNum(3, 6, ArrayRead[1], 2);OLED_ShowHexNum(3, 9, ArrayRead[2], 2);OLED_ShowHexNum(3, 12, ArrayRead[3], 2);while (1){}
}
B: 硬件SPI读写W25Q64
1:简历
STM32内部集成了硬件SPI收发电路,可以由硬件自动执行时钟生成、数据收发等功能,减轻CPU的负担
可配置8位/16位数据帧、高位先行/低位先行
时钟频率: fPCLK / (2, 4, 8, 16, 32, 64, 128, 256)------PCLK在32中为72MHz表示参数的速度
支持多主机模型、主或从操作
可精简为半双工/单工通信
支持DMA
兼容I2S协议
STM32F103C8T6 硬件SPI资源:SPI1、SPI2
2:框图
图中表示的为低位先行, 但是可以通过调节 LSBFIRST来配置低位还是高位先行, 一般我们使用的是高位先行的方式.
(接受和发送)缓冲区-----实际上就是数据寄存器DR; 下面发送缓冲区,就是发送数据寄存器TDR; 上面接收缓冲区,就是接收数据寄存器RDR; 和串口那里一样,TDR和RDR占用同一个地址,统.叫作DR.
TEX: 数据奇存器和移位寄存器打配合,可以实现连续的数据流, 具体的流程如下: 第一个数据,写入到TDR, 当移位寄存器没有数据移位时, TDR的数据会立刻转入移位奇存器,开始移位; 这个转入时刻,会置状态寄存器的TXE为1, 表示发送寄存器空 当我们检查TXE置1后紧跟着,下一个数据,就可以提前写入到TDR里候着了数据发完,下一个数据就可以立刻跟进
RXNE : 然后移位寄存器这里,一旦有数据过来了, 它就会自动产生时钟,将数据移出去, 在移出的过程中,MISO (主机输入,从机输出) 的数据也会移入, 一旦数据移出完成,数据移入是不是也完成了, 这时,移入的数据,就会整体地从移位寄存器转入到接收缓冲区RDR , 这个时刻,会置状态奇存器的RXNE为1 , 表示接收寄存器非空. 当我们检查RXNE置1后,就要尽快把数据从RDR读出来. 在下一个数据到来之前,读出RDR,就可以实现连续接收
3:SPI基本结构
4: 主模式全双工连续传输
这个使用的是SPI时序基本单元的模式3
5: 非连续传输
6:连接图
硬件SPl的连线要根据引脚定义表来连接, 软件的话不用. 和I2C硬件的连接方式相同,都是根据引脚定义表来连接的.
7: 代码
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "mySPl.h"
#include "w25q64.h "//一般来说&是用来清零的;
//一般来说|是用来值一的;
void MySPI_W_SS(uint8_t BitValue)
{//也叫做CS片选段----在低电平是有效GPIO_WriteBit(GPIOA,GPIO_Pin_4,(BitAction)BitValue);}/**
* @brief DO(MISO) SPI主机输入从机输出---连接的是PA6; 都是以主机的角度看输出引脚配置为推挽输出,输入引脚配置为浮空或上拉输入PA6----浮空或上拉输入; 剩下的全部为推挽输出* @retval 无*/
void MYSPL_init()
{RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;//推挽输出GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA, &GPIO_InitStructure);GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;//复用推挽输出GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7| GPIO_Pin_5;//复用--控制的权力交给片上外设GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA, &GPIO_InitStructure);GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //上拉输入GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA, &GPIO_InitStructure);//在开始时候默认SS(CS)为高电平,SCK为低电平SPI_InitTypeDef SPl_initstruct;SPl_initstruct.SPI_BaudRatePrescaler=SPI_BaudRatePrescaler_128;//波特率预分频器的值SPl_initstruct.SPI_CPHA=SPI_CPHA_1Edge; //配置SPl的模式SPl_initstruct.SPI_CPOL=SPI_CPOL_Low; //配置SPl的模式SPl_initstruct.SPI_CRCPolynomial=7; //填入默认的7即可SPl_initstruct.SPI_DataSize=SPI_DataSize_8b; //8个字节的大小SPl_initstruct.SPI_Direction=SPI_Direction_2Lines_FullDuplex; //双线全双工SPl_initstruct.SPI_FirstBit=SPI_FirstBit_MSB; //选择高位先行还是低位先行; --高位先行SPl_initstruct.SPI_Mode=SPI_Mode_Master; //指定当前设备为主机还是从机; ---主机SPl_initstruct.SPI_NSS=SPI_NSS_Soft; //NSS使用软件模拟--软件模拟CSSPI_Init(SPI1,&SPl_initstruct);SPI_Cmd(SPI1,ENABLE);MySPI_W_SS(1);}void SPL_Start()
{MySPI_W_SS(1);MySPI_W_SS(0);
}
void SPL_Stop()
{MySPI_W_SS(0);MySPI_W_SS(1);
}/**
* @brief SPL交换数据--使用的为模式0DI(MOSI)----SPI主机输出从机输入DO(MISO)-------SPI主机输入从机输出我们只操作主机:首先主机移出最高位,放在MOSI上面,---主机操作需要我们来齐次从机把数据放在MISO上面----从机的操作不需要我们管* @param ByteSend: 主机给从机发送的数据* @retval 主机读取的数据----即从机给主机发送的数据*/
uint8_t MySPI_SwapByte(uint8_t ByteSend)
{ //此标志为”1'时表明发送缓冲器为空,可以写下一个待发送的数据进入缓冲器中。//当写入SPI DR时,TXE标志被清除。while (SPI_I2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_TXE)==RESET);//检查标志位SPI_I2S_SendData(SPI1,ByteSend);//此标志为'1时表明在接收缓冲器中包含有效的接收数据。读SPI数据寄存器可以清除此标志。while (SPI_I2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_RXNE)==RESET);//检查标志位return SPI_I2S_ReceiveData(SPI1);
}//W25Q64_WaitBusy等待不忙的函数, 事后等待,只需要在写入操作之后调用---因为在写入后等待不忙读取的时候肯定不忙
//而事前等待,在写入操作和读取操作之前,都得调用
//我们采用事前等待
void W25Q64_init()
{MYSPL_init();}
/*** @brief 读取设备的ID号
步骤: 起始,先交换发送指令9F,随后连续交换接收3个字节,停止;
连续接收的3个字节: 第一个字节是广商ID”表示了是哪个广家生产.
后两个学节---是设备ID; 设备ID的高8为---表示存储器类型, 低8为--表示容量
* @param MID : 输出8位的厂商ID@param DID : 输出16位的设备ID因为函数内的返回值只能返一个,而用指针,只要知道地址就可以写入;
可以直接改变外补的2个参数, 相当于返回2个参数* @retval 无*/
void W25Q64_ReadID(uint8_t *MID, uint16_t *DID)
{ 一般来说&是用来清零的;//一般来说|是用来值一的;SPL_Start();MySPI_SwapByte(0x9F);//先交换发送指令9F*MID = MySPI_SwapByte(0xFF);//返回的第一个字节是广商ID; 这时候我们给我从机发送的数据没有实际的意义*DID = MySPI_SwapByte(0xFF);//返回的第二个字节设备ID的高8为---表示存储器类型*DID <<= 8;*DID |= MySPI_SwapByte(0xFF);返回的第二个字节设备低8为--表示容量SPL_Stop();
}/*** @brief 写使能函数*/
void W25Q64_WriteEnable(void)
{SPL_Start();MySPI_SwapByte(W25Q64_WRITE_ENABLE);//规定 : SPL起始的第一个字节为指令集SPL_Stop();
}/**
* @brief 等待忙函数--状态寄存器1 :作用看寄存器忙不忙要想知道芯片什么时候结束忙状态了, 我们可以使用读取状态寄存器的指令, 看一下状态寄存器的BUSY位是否为1, BUSY位为0时,芯片就不忙了,我们再进行操作*/
void W25Q64_WaitBusy(void)
{ uint32_t Count;SPL_Start();MySPI_SwapByte(W25Q64_READ_STATUS_REGISTER_1);//规定 : SPL起始的第一个字节为指令集Count=5000;while ((MySPI_SwapByte(W25Q64_DUMMY_BYTE) & 0x01) ==0x01){Count--;if (Count==0){break;}}SPL_Stop();
}
/*** @brief 写页编程-----主机给从机发送数据步骤 : 1---先发送指令; 2--然后连发3个字节,就是24位地址 3---之后继续发送DataByte1(数据1)、DataByte2、DataByte3, 最大是DataByte256* @param Address 步骤中的1和2步---也就是发送的地址
* @param *DataArray 主机给从机发送的数据, 这里面为一个数组* @param Count 数组的长度* @retval 无*/
void W25Q64_PageProgram(uint32_t Address, uint8_t *DataArray, uint16_t Count)
{ W25Q64_WaitBusy();//写入操作结束后,芯片进入忙状态,不响应新的读写操作W25Q64_WriteEnable();//写入操作前,必须先进行写使能SPL_Start();MySPI_SwapByte(W25Q64_PAGE_PROGRAM);//规定 : SPL起始的第一个字节为指令集MySPI_SwapByte(Address>>16); //一共为24位,把数据后移16为,先把前8为也就是第一个直接发送给从机MySPI_SwapByte(Address>>8);//发送给从机的第二给字节MySPI_SwapByte(Address);//发送给从机的第三给字节//下面为主机给从机发送的真正的数据for (uint8_t i=0; i<Count; i++){MySPI_SwapByte(DataArray[i]);}SPL_Stop();}/*** @brief 按4KB的扇区擦除* @param Address 擦除的地址步骤: 需要先发送指令0x20,再发送3个字节的地址,就行了* @retval 无*/
void W25Q64_SectorErase(uint32_t Address)
{ W25Q64_WaitBusy();//在发出擦除指令后,芯片也会进入忙状态, 我们也得等待忙状态结束后,才能进行后续操作W25Q64_WriteEnable(); //扇区擦除也是写入所以需要使能SPL_Start();MySPI_SwapByte(W25Q64_SECTOR_ERASE_4KB);MySPI_SwapByte(Address>>16); //一共为24位,把数据后移16为,先把前8为也就是第一个直接发送给从机MySPI_SwapByte(Address>>8);//发送给从机的第二给字节MySPI_SwapByte(Address);//发送给从机的第三给字节SPL_Stop();}/*** @brief 主机接受从机给主机发送的数据
步骤:流程是,交换发送指令03,再发送3个字节地址; 随后转入接收,就可以依次接收数据了* @param Address 起始行位置,范围:1~4* @param DataArray 起始列位置,范围:1~16* @param Count 要显示的数字,范围:0~1111 1111 1111 1111* @retval 无*/
void W25Q64_ReadData(uint32_t Address, uint8_t *DataArray, uint32_t Count)
{ W25Q64_WaitBusy();//读取操作结束后不会进入忙状态,但不能在忙状态时读取SPL_Start();MySPI_SwapByte(W25Q64_READ_DATA);//规定 : SPL起始的第一个字节为指令集MySPI_SwapByte(Address>>16); //一共为24位,把数据后移16为,先把前8为也就是第一个直接发送给从机MySPI_SwapByte(Address>>8);//发送给从机的第二给字节MySPI_SwapByte(Address);//发送给从机的第三给字节//下面为主机正在接受的数据, 从机给主机发送数据for (uint8_t i=0; i<Count; i++){DataArray[i] = MySPI_SwapByte(W25Q64_DUMMY_BYTE); //1111 1111 没有实际的意义}SPL_Stop();
}#ifndef __W25Q64_INS_H
#define __W25Q64_INS_H#define W25Q64_WRITE_ENABLE 0x06
#define W25Q64_WRITE_DISABLE 0x04
#define W25Q64_READ_STATUS_REGISTER_1 0x05
#define W25Q64_READ_STATUS_REGISTER_2 0x35
#define W25Q64_WRITE_STATUS_REGISTER 0x01
#define W25Q64_PAGE_PROGRAM 0x02
#define W25Q64_QUAD_PAGE_PROGRAM 0x32
#define W25Q64_BLOCK_ERASE_64KB 0xD8
#define W25Q64_BLOCK_ERASE_32KB 0x52
#define W25Q64_SECTOR_ERASE_4KB 0x20
#define W25Q64_CHIP_ERASE 0xC7
#define W25Q64_ERASE_SUSPEND 0x75
#define W25Q64_ERASE_RESUME 0x7A
#define W25Q64_POWER_DOWN 0xB9
#define W25Q64_HIGH_PERFORMANCE_MODE 0xA3
#define W25Q64_CONTINUOUS_READ_MODE_RESET 0xFF
#define W25Q64_RELEASE_POWER_DOWN_HPM_DEVICE_ID 0xAB
#define W25Q64_MANUFACTURER_DEVICE_ID 0x90
#define W25Q64_READ_UNIQUE_ID 0x4B
#define W25Q64_JEDEC_ID 0x9F
#define W25Q64_READ_DATA 0x03
#define W25Q64_FAST_READ 0x0B
#define W25Q64_FAST_READ_DUAL_OUTPUT 0x3B
#define W25Q64_FAST_READ_DUAL_IO 0xBB
#define W25Q64_FAST_READ_QUAD_OUTPUT 0x6B
#define W25Q64_FAST_READ_QUAD_IO 0xEB
#define W25Q64_OCTAL_WORD_READ_QUAD_IO 0xE3#define W25Q64_DUMMY_BYTE 0xFF //这个数据实际没有意义#endifuint8_t MID;
uint16_t DID;uint8_t ArrayWrite[] = {0x55, 0x66, 0x77, 0x88};
uint8_t ArrayRead[4];int main(void)
{OLED_Init();W25Q64_init();OLED_ShowString(1, 1, "MID: DID:");OLED_ShowString(2, 1, "W:");OLED_ShowString(3, 1, "R:");W25Q64_ReadID(&MID, &DID);OLED_ShowHexNum(1, 5, MID, 2);OLED_ShowHexNum(1, 12, DID, 4);W25Q64_SectorErase(0x000000);//擦除W25Q64_PageProgram(0x000000, ArrayWrite, 4);//写页编程W25Q64_ReadData(0x000000, ArrayRead, 4);//读取OLED_ShowHexNum(2, 3, ArrayWrite[0], 2);OLED_ShowHexNum(2, 6, ArrayWrite[1], 2);OLED_ShowHexNum(2, 9, ArrayWrite[2], 2);OLED_ShowHexNum(2, 12, ArrayWrite[3], 2);OLED_ShowHexNum(3, 3, ArrayRead[0], 2);OLED_ShowHexNum(3, 6, ArrayRead[1], 2);OLED_ShowHexNum(3, 9, ArrayRead[2], 2);OLED_ShowHexNum(3, 12, ArrayRead[3], 2);while (1){}
}
相关文章:
11:STM32---spl通信
目录 一:SPL通信 1:简历 2:硬件电路 3:移动数据图 4:SPI时序基本单元 A : 开/ 终条件 B:SPI时序基本单元 A:模式0 B:模式1 C:模式2 D:模式3 C:SPl时序 A:发送指令 B: 指定地址写 C:指定地址读 二: W25Q64 1:简历 2: 硬件电路 3:W25Q64框图 4: Flash操作注意…...
kafka的 ack 应答机制
目录 一 ack 应答机制 二 ISR 集合 一 ack 应答机制 kafka 为用户提供了三种应答级别: all,leader,0 acks :0 这一操作提供了一个最低的延迟,partition的leader接收到消息还没有写入磁盘就已经返回ack&#x…...
Django系列:Django开发环境配置与第一个Django项目
Django系列 Django开发环境配置与第一个Django项目 作者:李俊才 (jcLee95):https://blog.csdn.net/qq_28550263 邮箱 :291148484163.com 本文地址:https://blog.csdn.net/qq_28550263/article/details/1328…...
iPad协议/微信协议最新版
一、了解微信的协议 在开发微信协议之前,需要先了解微信的协议。微信的协议包括登录协议、消息传输协议、文件传输协议、数据同步协议等。其中,登录协议是最重要的协议之一,包括登录验证、登录认证等。消息传输协议则是微信最核心的功能之一…...
URL字符解码
将网页编码文字还原: 例如:https%3A%2F%2Fwww.example.com%2F%3Fparam%3Dvalue%26key%3D%E4%B8%AD%E6%96%87 解码: https: // www.example.com/?paramvalue&key中文 代码: char hexValue(char ch) {if (isdigit(ch)){re…...
uni-app进行表单效验
Uni-app内置了一些表单验证方法,可以帮助我们对表单进行有效的验证。以下是一些常用的验证方法: 非空验证: if(!this.formData.name){uni.showToast({title: 请输入姓名,icon: none});return false; }手机号码验证: const phon…...
IO流内容总结
IO流作用 对文件或者网络中的数据进行读写操作。 简单记:输入流读数据,输出流写数据。 Java的输出流主要以OutputStream和Writer作为基类,输入流主要是以InputStream和Reader作为基类。 按处理数据单元分类 字节流 字节输入流ÿ…...
MySQL的进阶篇1-MySQL的存储引擎简介
存储引擎 MySQL的体系结构 0、客户端连机器【java、Python、JDBC等】 1、【MySQL服务器-连接层】认证,授权,连接池 2、【MySQL服务器-服务层】 {SQL接口(DML、DDL、存储过程、触发器)、解析器、查询优化器、缓存} 3、【MySQL…...
九芯电子丨语音智能风扇,助您畅享智慧生活
回忆童年时期的传统机械风扇,那“古老”的扇叶连摆动看起来是那么吃力。在一个闷热的夏夜,风扇的噪音往往令人印象深刻。但在今天,静音家用风扇已取代了传统的机械风扇。与此同时,随着智能化的发展,智能家居已逐渐成为…...
2101. 引爆最多的炸弹;752. 打开转盘锁;1234. 替换子串得到平衡字符串
2101. 引爆最多的炸弹 核心思想:枚举BFS。枚举每个炸弹最多引爆多少个炸弹,对每个炸弹进行dfs,一个炸弹能否引爆另一个炸弹是两个炸弹的圆心距离在第一个炸弹的半径之内。 752. 打开转盘锁 核心思想:典型BFS,就像水源扩散一样&a…...
校园学习《乡村振兴战略下传统村落文化旅游设计》许少辉八一新著
校园学习《乡村振兴战略下传统村落文化旅游设计》许少辉八一新著...
UOS服务器操作系统搭建离线yum仓库
UOS服务器操作系统搭建离线yum仓库 1050e版本操作系统(适用ARM64和AMD64)1、挂载everything镜像并同步2、配置本地仓库3、配置nginx发布离线源 1050e版本操作系统(适用ARM64和AMD64) 首先需要有everything镜像文件 服务端操作流…...
C# 实现数独游戏
1.数独单元 public struct SudokuCell{public SudokuCell() : this(0, 0, 0){}public SudokuCell(int x, int y, int number){X x; Y y; Number number;}public int X { get; set; }public int Y { get; set; }public int Number { get; set; }} 2.数独创建 public class …...
vscode + conda+ ffmpeg + numpy 的安装方式
Windows 搭建 环境 遇到的错误点: 解决,使用conda init conda activate myenv usage: conda-script.py [-h] [–no-plugins] [-V] COMMAND … conda-script.py: error: argument COMMAND: invalid choice: ‘activate’ (choose from ‘clean’, ‘comp…...
Python Union联合类型注解
视频版教程 Python3零基础7天入门实战视频教程 我们看下如下的示例: my_list2: list[int] [1, 2, 3, 4] my_dict2: dict[str, float] {"python222": 3.14, "java1234": 4.35} l1 [1, "python222", True] # 如何注解多种元素类型…...
提高接口自动化测试效率:使用 JMESPath 实现断言和数据提取!
前言 做接口自动化,断言是比不可少的。如何快速巧妙的提取断言数据就成了关键,当然也可以提高用例的编写效率。笔者在工作中接触到了JMESPath,那到底该如何使用呢?带着疑惑一起往下看。 JMESPath是啥? JMESPath 是一…...
【Linux操作系统教程】用户管理与权限管理你真的懂了吗(三)
😄作者简介: 小曾同学.com,一个致力于测试开发的博主⛽️,主要职责:测试开发、CI/CD 如果文章知识点有错误的地方,还请大家指正,让我们一起学习,一起进步。😊 座右铭:不想…...
华为全联接大会2023 | 尚宇亮:携手启动O3社区发布
2023年9月20日,在华为全联接大会2023上,华为正式发布“联接全球服务工程师,聚合用户服务经验”的知识经验平台,以“Online 在线、Open 开放、Orchestration 协同”为理念,由华为、伙伴和客户携手,共同构建知…...
MySQL数据库查缺补漏——基础篇
MySQL数据库查缺补漏-基础篇 基础篇 net start mysql80[服务名] net stop mysql80 create database pshdhx default charset utf8mb4; 为什么不使用utf8?因为其字符占用三个字节,有四个字节的字符,所有需要设置为utf8mb4; 数值类型&…...
ESP8266 WiFi物联网智能插座—电能计量
目录 1、芯片功能 2、性能指标 3、寄存器说明 4、UART通信协议 4.1、写操作帧格式和时序 4.2、读操作帧格式和时序 4.3、读取全电参数数据包 4.4、配置波特率 4.5、UART保护机制 5、功能说明 5.1、电流电压瞬态波形计量 5.2、有功功率 5.3、有功功率防潜动 5.4、电能计量 5.5、…...
“智慧”北京,人工智能引领“新风尚”
原创 | 文 BFT机器人 北京时间,9月15日,北京人工智能产业峰会暨中关村科学城科创大赛颁奖典礼在北京中关村举行,同时惠阳还举行了“中关村人工智能大模型产业集聚区”启动建设的揭牌仪式。 此次大会围绕北京AI产业的建设与发展,各…...
狮子鱼社区团购小程序v18.1独立全开源版+小程序前端
狮子鱼社区团购商城系统小程序V18.1独立开源版,该系统本身就非常完善也没更新的必要,此系统拿来即用非常方便,同一版一样人类小徐特别优化很多细节首页美化了下,如小程序端授权窗口美化了下,该版本用户授权接口正常。功…...
深拷贝和浅拷贝的区别
本文内容 主要阐述下深拷贝和浅拷贝的区别 通俗理解 深拷贝和浅拷贝最根本的区别在于是否真正获取一个对象的复制实体,而不是引用。 假设B复制了A,修改A的时候,看B是否发生变化: 如果B跟着也变了,说明是浅拷贝&…...
利用优化算法提高爬虫任务调度效率
目录 一、任务调度优化的重要性 二、选择合适的优化算法 三、建立任务调度模型 四、设计适应性函数 五、算法实施和调优 六、性能评估和优化结果分析 代码示例 总结 随着网络信息的爆炸式增长,网络爬虫在信息获取和数据挖掘等领域的应用越来越广泛。然而&am…...
Swiper的使用流程
1.官网查看演示 Swiper演示 - Swiper中文网 2.找到想使用的 比如想使用 卡片切换(255) 记住这个名字 3.去下载示例 下载Swiper - Swiper中文网 4.找到对应文件 5.根据里面引入的东西加到自己的页面 一定要引入swiper的 js 和 css html结构要按示例对应的三层结构 需要 …...
如何快速实现一个可视化看板?
一、用python实现一个可视化数据看板,最多支持多大体量的数据处理? Python可以通过多种可视化库来实现数据看板,例如Matplotlib、Seaborn、Plotly等。这些库可以处理各种规模的数据,从小型数据集到大型数据集都可以应用。 对于小型…...
基于PyTorch搭建FasterRCNN实现目标检测
基于PyTorch搭建FasterRCNN实现目标检测 1. 图像分类 vs. 目标检测 图像分类是一个我们为输入图像分配类标签的问题。例如,给定猫的输入图像,图像分类算法的输出是标签“猫”。 在目标检测中,我们不仅对输入图像中存在的对象感兴趣。我们还…...
线性表应用(非递减合并、分解链表、删除线性表)
将两个非递减的有序链表合并为一个非递增的有序链表。要求结果链表仍使用原来两个链表的存储空间,不另外占用其它的存储空间。表中允许有重复的数据。 #include<iostream> using namespace std; typedef struct list {int data;list* next; }list,*linklist;…...
【C++面向对象侯捷下】1.导读
文章目录 来源:我的百度网盘 百科全书 专家书籍 C标准库 C编译器...
Ubuntu22.04 vnc远程黑屏
一、原因 原因是Ubuntu22.04使用的gnome启用了Wayland。vnc、teamviewer、向日葵、todesk等均无法使用或者远程黑屏等。 简单的说vnc、teamviewer、向日葵、todesk等均基于xorg实现(xorg太流行),并不兼容Wayland,所以vnc无法正常…...
罗湖外贸网站建设/百度打广告怎么收费
最近做了一系列的单元测试相关的工作,除了各种规范及测试框架以外,讨论比较多的就是关于代码覆盖率的产生,c/c与其他的一些高级语言或者脚本语言相比较而言,例如 Java、.Net和php/python/perl/shell等,由于没有这些高级…...
做58网站怎么赚钱/网址怎么创建
关于编写出现1064的问题 原因如下 sql 语句中的字段和MySQL数据库关键字冲突,如update 、create、select、database等; sql语句标点错误,如单双引号误写,分号中文; 编码格式错误; 写sql语句的时候&…...
软件开发 网页设计网站/网站底部友情链接代码
容器是一个打包了应用和服务的环境,是一个轻量级的虚拟机,每一个容器都由一组特定的应用和必要的依赖库组成。 容器的管理操作 容器常见的命令:查看、创建、启动、终止和删除 创建容器 docker create docker run 二者的区别在于docker create…...
宁波网站设计推荐荣盛网络/seo属于什么
传送门 这题是多重背包的二进制分解01背包处理。 给个图: 我就不多说了,自己看吧。 代码: #include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<cstdlib> #include<c…...
邯郸网站改版找谁做/电商平台排行榜
要做动画,一般都是要获取元素的一些基本属性,比如宽,高,颜色等 但是对于动态创建的元素,是不能获取这些属性的 只有插入到dom中,经过渲染才能获取这些属性 但是一旦插入到dom当中,先获取在改变就会有跳动的效果 为了避免这种情况,有两种解决方案 一种是将元素设置绝对定位,获取…...
网站开发类投标文件/美国站外推广网站
下面就来看看什么是 JAR 文件包吧: 1. JAR 文件包 JAR 文件就是 Java Archi...