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:指令集

INSTRUCTION NAME---指令的名字;     BYTE----字节X

 

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){}
}

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

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

相关文章

阿里云服务器u1和经济型e系列性能差异?哪个比较好?

阿里云服务器经济型e实例和云服务器u1有什么区别&#xff1f;同CPU内存配置下云服务器u1性能更强&#xff0c;u1实例价格也要更贵一些。经济型e实例属于共享型云服务器&#xff0c;不同实例vCPU会争抢物理CPU资源&#xff0c;并导致高负载时计算性能波动不稳定&#xff0c;而云…

人声分离网站,帮你快速提取视频中的人声和背景音乐

今天给大家带来一个可以分离人声的网站——音分轨&#xff0c;他运用人工智能算法可以将音频中的人声部分和音乐部分分离&#xff0c;使我们的视频制作过程可以更方便。 我们点击右下角“选择文件”上传一个音频&#xff0c;上传好音频后&#xff0c;人工智能就开始处理我们上传…

ESP32主板-MoonESP32

产品简介 Moon-ESP32主板&#xff0c;一款以双核芯片ESP32-E为主芯片的主控板&#xff0c;支持WiFi和蓝牙双模通信&#xff0c;低功耗&#xff0c;板载LED指示灯&#xff0c;引出所有IO端口&#xff0c;并提供多个I2C端口、SPI端口、串行端口&#xff0c;方便连接&#xff0c;…

根据条件关闭软件

使用下载工具时&#xff0c;经常出现磁盘空间已满&#xff0c;无法下载的情况。 使用shell写一个监控&#xff0c;每2分钟执行一次。判断当前磁盘的空间&#xff0c;低于2G时&#xff0c;关闭下载软件。 获取空间大小 ➜ ~ df -h …

OceanBase杨传辉传递亚运火炬:国产数据库为“智能亚运”提供稳稳支持

9 月 14 日&#xff0c;亚运火炬传递到了浙江台州&#xff0c;OceanBase 的 CTO 杨传辉作为火炬手交接了第 89 棒火炬。 2010 年&#xff0c;杨传辉作为创始成员之一参与自研原生分布式数据库 OceanBase。十年磨一剑&#xff0c;国产数据库 OceanBase 交出了一张优秀的成绩单&a…

若依cloud -【 100 ~ 】

100 分布式日志介绍 | RuoYi 分布式日志就相当于把日志存储在不同的设备上面。比如若依项目中有ruoyi-modules-file、ruoyi-modules-gen、ruoyi-modules-job、ruoyi-modules-system四个应用&#xff0c;每个应用都部署在单独的一台机器里边&#xff0c;应用对应的日志的也单独存…

「UG/NX」Block UI 指定方位SpecifyOrientation

✨博客主页何曾参静谧的博客📌文章专栏「UG/NX」BlockUI集合📚全部专栏「UG/NX」NX二次开发「UG/NX」BlockUI集合「VS」Visual Studio「QT」QT5程序设计「C/C+&#

Django系列:Django应用(app)的创建与配置

Django系列 Django应用&#xff08;app&#xff09;的创建与配置 作者&#xff1a;李俊才 &#xff08;jcLee95&#xff09;&#xff1a;https://blog.csdn.net/qq_28550263 邮箱 &#xff1a;291148484163.com 本文地址&#xff1a;https://blog.csdn.net/qq_28550263/article…

数据结构--排序(1)

文章目录 排序概念直接插入排序希尔排序冒泡排序堆排序选择排序验证不同排序的运行时间 排序概念 排序指的是通过某一特征关键字&#xff08;如信息量大小&#xff0c;首字母等&#xff09;来对一连串的数据进行重新排列的操作&#xff0c;实现递增或者递减的数据排序。 稳定…

【C++】静态成员函数 ( 静态成员函数概念 | 静态成员函数声明 | 静态成员函数访问 | 静态成员函数只能访问静态成员 )

文章目录 一、静态成员函数简介1、静态成员函数概念2、静态成员函数声明3、静态成员函数访问4、静态成员函数只能访问静态成员 二、代码示例 - 静态成员函数 一、静态成员函数简介 1、静态成员函数概念 静态成员函数归属 : 在 C 类中 , 静态成员函数 是一种 特殊的函数 , 该函数…

MFC串口通信控件MSCOMM32.OCX的安装注册

MSCOMM32.OCX是一个与Microsoft Corporation开发的MSComm控件相关联的文件。MSComm控件是软件应用程序用来与调制解调器、条形码读取器和其他串行设备等设备建立串行通信的通信控件。 下载地址1 https://download.csdn.net/download/m0_60352504/88345092 下载地址2 https://ww…

kafka的 ack 应答机制

目录 一 ack 应答机制 二 ISR 集合 一 ack 应答机制 kafka 为用户提供了三种应答级别&#xff1a; all&#xff0c;leader&#xff0c;0 acks &#xff1a;0 这一操作提供了一个最低的延迟&#xff0c;partition的leader接收到消息还没有写入磁盘就已经返回ack&#x…

虚拟机部署linux网络连接配置

1、虚拟机安装linux后&#xff0c;配置网络访问 虚拟机网络设置为NAT模式 linux网络配置好IP&#xff0c;主要是以下网络配置 2、linux没有ifconfig命令&#xff0c;ifconfig命令是在net-tools.x86_64包里 yum install net-tools.x86_64安装

Linus Torvalds接受来自微软的Linux Hyper-V升级

导读微软最近推送了一些变更&#xff0c;旨在改进即将发布的 Linux 内核 6.6 版本对 Hyper-V 的支持。这些改进包括在 Hyper-V 上支持 AMD SEV-SNP guest 和 Intel TDX guest。除了这两项&#xff0c;还有其他一些升级&#xff0c;如改进了 VMBus 驱动程序中的 ACPI&#xff08…

操作系统:体系结构

1.内核的划分 1.术语解释 时钟管理&#xff1a;利用时钟断实现计时功能。原语是一种特殊的程序,具有原子性。也就是说,这段程序的运行必须一气呵成&#xff0c;不可被“中断”Ubuntu、Centos的开发团队,其主要工作是实现非内核功能&#xff0c;而内核都是用了Linux内核。 内核…

单元测试 —— JUnit 5 参数化测试

JUnit 5参数化测试 目录 设置我们的第一个参数化测试参数来源 ValueSourceNullSource & EmptySourceMethodSourceCsvSourceCsvFileSourceEnumSourceArgumentsSource参数转换参数聚合奖励总结 如果您正在阅读这篇文章&#xff0c;说明您已经熟悉了JUnit。让我为您概括一下…

Java中swing的5种布局方式浅析

在一个传统的java项目中&#xff0c;遇到一个需要调整布局的需求&#xff0c;下面将学习网上大佬的文章&#xff0c;并将过程记录下来。 1、Java swing5种布局方式 1、 边界布局&#xff08;BorderLayout&#xff09;2、流式布局&#xff08;FlowLayout&#xff09;3、网格布局…

王道考研操作系统

王道考研操作系统 计算机系统概述操作系统的概念操作系统的特征操作系统的发展历程操作系统内核中断和异常![在这里插入图片描述](https://img-blog.csdnimg.cn/162452b4c60144e0bd500e180127c447.png)系统调用操作系统结构虚拟机错题 进程与线程进程控制进程通信线程和多线程模…

如何获取美团的热门商品和服务

导语 美团是中国最大的生活服务平台之一&#xff0c;提供了各种各样的商品和服务&#xff0c;如美食、酒店、旅游、电影、娱乐等。如果你想了解美团的热门商品和服务&#xff0c;你可以使用爬虫技术来获取它们。本文将介绍如何使用Python和BeautifulSoup库来编写一个简单的爬虫…

计算机视觉与深度学习-全连接神经网络-训练过程-批归一化- [北邮鲁鹏]

文章目录 思想批归一化操作批归一化与梯度消失经过BN处理 算法实现 思想 直接对神经元的输出进行批归一化 批归一化&#xff1a;对输出值进行归一化&#xff0c;将归一化结果平移缩放作为输出。 批归一化操作 小批量梯度下降算法回顾&#xff1a;每次迭代时会读入一批数据&am…