相对I2C的优点缺点:1,传输速度快,可达80MHz;2,设计简单,学习容易;3,硬件资源消耗多,容易造成资源浪费
CPHA决定了是那个SCK开始采样
模式2和模式3类似与这两个模式,只不过SCK的高低电平翻转了而已
Dual SPI: 双重SPI模式,在一个SCK电平变化下,一次性交换两个数据
Quad SPI:四重SPI模式,同双重SPI模式
对于引脚旁括号的内容:即双重SPI模式或者四重SPI模式下的多位数据传输的通道,当为双重SPI模式时,DO(I01)和DI(IO0)就是一次性传输的两位数据,IO2和IO3同理。
一整个存储空间,首先先被划分为若干块,对应每一块又被划分为若干扇区,对于整个空间,又会被划分为很多页,每页256个字节
Page地址锁存器:用于指定我们要操作的页
Byte地址锁存器:用于指定操作我们指定的页中指定的字节
我们发送的24位地址(3字节地址)前两位是Page地址,会发送到Page地址锁存器中,后一位是字节地址,会发送到Byte地址锁存器中。
以上两个寄存器都有计数器,所以他们的地址指针是可以在读写之后可以自动加1的,实现从指定地址开始,连续读写多个字节的目的了。
写入的数据会先在RAM缓存区(Column Decode)中存储,在时序结束后,芯片再把缓存区中的数据复制到对应的Flash中,进行永久保存
因为SPI的写入频率非常高,而数据是要放进Flash中永久存储的,这个过程比较慢,所以写入的数据先放在页缓存区中存着,缓存区是RAM,所以速度非常快,可以跟上SPI总线的速度,但是这个缓存区也是有内存限制的(256Byte),所以写入的一个时序,连续写入的数据量不能超过256字节。
当我们发送好数据后,芯片才慢慢地把页缓存区的数据传入到Flash中,这会占用一定的时间,写入时序结束后,芯片就会进入“Buzy”的时间内,这时就会置标志位到status寄存器中,在这段时间内,芯片将不会响应新的读写时序。
写入操作时:
读取操作时:
Buzy如之前所说;
写使能:在每写入一个数据后,状态寄存器会自动配置为写失能,代表我们每写入一个字节之气那都需要配置为写使能,每一个写使能只能保证后续的一条写指令
ID
写使能
页编程(前三个字节指定地址,后面一个写入数据,如果继续写入数据,指定的地址会自动加1,但是要注意范围为页)
擦除指令(标注的是最小的扇区擦除)
读取ID
读取数据
1,开启时钟,配置GPIO口(软件SPI任选引脚)
- void MySPI_Init(void)
- {
- //引脚初始化
- RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
-
- GPIO_InitTypeDef GPIO_InitStruct;
- GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;
- GPIO_InitStruct.GPIO_Pin = GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_7;
- GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
- GPIO_Init(GPIOA, &GPIO_InitStruct);
-
- GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU;
- GPIO_InitStruct.GPIO_Pin = GPIO_Pin_6;
- GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
- GPIO_Init(GPIOA, &GPIO_InitStruct);
-
- MySPI_W_SS(1);
- MySPI_W_SCK(0);
- }
2,与软件I2C类似,使用函数封装一下置每个引脚高低电平的操作(模拟SPI)
- //用于模拟每个端口的操作
- void MySPI_W_SS(uint8_t Value)
- {
- GPIO_WriteBit(GPIOA, GPIO_Pin_4, (BitAction)Value);
- }
-
- void MySPI_W_SCK(uint8_t Value)
- {
- GPIO_WriteBit(GPIOA, GPIO_Pin_5, (BitAction)Value);
- }
-
- void MySPI_W_MOSI(uint8_t Value)
- {
- GPIO_WriteBit(GPIOA, GPIO_Pin_7, (BitAction)Value);
- }
-
- uint8_t MySPI_R_MISO(void)
- {
- GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_6);
- }
同时需要在初始化函数中添加
- MySPI_W_SS(1);
- MySPI_W_SCK(0);
3,根据时序来编写各个操作的函数
例如开始——只需要把SS置0
- void MySPI_Start(void){
- MySPI_W_SS(0);
- }
-
- void MySPI_Stop(void){
- MySPI_W_SS(1);
- }
- //掩码方式(优点,可以保持ByteSend不变)
- uint8_t MySPI_WriteReadByte(uint8_t ByteSend)
- {
- uint8_t i, ByteReceive=0x00;
- for (i = 0; i < 8; i ++)
- {
- MySPI_W_SCK(1);
- MySPI_W_MOSI(ByteSend & (0x80 >> i));
- if (MySPI_R_MISO() == 1){ByteReceive |= (0x80 >> i);}
- MySPI_W_SCK(0);
- }
- return ByteReceive;
- }
-
- //移位版(实现思路与SPI传输数据相同)
- uint8_t MySPI_WriteReadByte1(uint8_t ByteSend)
- {
- uint8_t i;
- for (i = 0; i < 8; i ++)
- {
- MySPI_W_SCK(1);
- MySPI_W_MOSI(ByteSend & 0x80);
- ByteSend <<= 1;
- if (MySPI_R_MISO() == 1){ ByteSend |= 0x01;}
- MySPI_W_SCK(0);
- }
- return ByteSend;
- }
4,编写W25Q64的相关函数
- void W25Q64_Init(void)
- {
- MySPI_Init();
- }
- void W25Q64_GetID(uint8_t *MID, uint16_t *DID)
- {
- MySPI_Start();
- //指令集中获取设备ID号的指令
- MySPI_WriteReadByte(W25Q64_JEDEC_ID);
- //第一个字节为厂商ID
- *MID = MySPI_WriteReadByte(W25Q64_DUMMY_BYTE);
- //第二个字节为设备ID高8位,第三个字节位设备ID低8位
- *DID = MySPI_WriteReadByte(W25Q64_DUMMY_BYTE);
- *DID <<= 8;
- *DID |= MySPI_WriteReadByte(W25Q64_DUMMY_BYTE);
- MySPI_Stop();
- }
-
- #include "stm32f10x.h" // Device header
- #include "MySPI.h"
- #include "W25Q64_ins.h"
-
- void W25Q64_Init(void)
- {
- MySPI_Init();
- }
-
- void W25Q64_ReadID(uint8_t *MID, uint16_t *DID)
- {
- MySPI_Start();
- //指令集中获取设备ID号的指令
- MySPI_SwapByte(W25Q64_JEDEC_ID);
- //第一个字节为厂商ID
- *MID = MySPI_SwapByte(W25Q64_DUMMY_BYTE);
- //第二个字节为设备ID高8位,第三个字节位设备ID低8位
- *DID = MySPI_SwapByte(W25Q64_DUMMY_BYTE);
- *DID <<= 8;
- *DID |= MySPI_SwapByte(W25Q64_DUMMY_BYTE);
- MySPI_Stop();
- }
-
- void W25Q64_WriteEnable(void)
- {
- MySPI_Start();
- MySPI_SwapByte(W25Q64_WRITE_ENABLE);
- MySPI_Stop();
- }
-
- void W25Q64_WaitBusy(void)
- {
- MySPI_Start();
- //读取状态寄存器比较特殊,写入以下值后,是连续读出状态寄存器的值
- uint32_t Timeout=100000;
- MySPI_SwapByte(W25Q64_READ_STATUS_REGISTER_1);
- while ((MySPI_SwapByte(W25Q64_DUMMY_BYTE) & 0x01 )== 0x01)
- {
- Timeout--;//防止卡死
- if (Timeout == 0)
- {
- break;
- }
- }
- MySPI_Stop();
- }
-
- //注意一次性只能发送一页(256个字节)的数据,Count最大值也只能是256
- void W25Q64_PageProgram(uint32_t Address, uint8_t *DataArray, uint16_t Count)
- {
- //事前等待(更高效,但是在每个操作函数的开头都需要加)
- W25Q64_WaitBusy();
-
- //写使能
- W25Q64_WriteEnable();
-
- MySPI_Start();
- //指令集中的PageProgram
- MySPI_SwapByte(W25Q64_PAGE_PROGRAM);
- //传输地址
- //因为地址是24位的,所以需要把地址分成8位一份发给从机
- MySPI_SwapByte(Address >> 16);
- MySPI_SwapByte(Address >> 8);
- MySPI_SwapByte(Address);
- //发送数据
- for (uint16_t i = 0; i < Count; i ++)
- {
- MySPI_SwapByte(DataArray[i]);
- }
- MySPI_Stop();
-
- }
-
- //擦除一个扇形区
- void W25Q64_SectorErase(uint32_t Address)
- {
- //事前等待(更高效,但是在每个操作函数的开头都需要加)
- W25Q64_WaitBusy();
- //写使能
- W25Q64_WriteEnable();
-
- MySPI_Start();
- MySPI_SwapByte(W25Q64_SECTOR_ERASE_4KB);
- MySPI_SwapByte(Address >> 16);
- MySPI_SwapByte(Address >> 8);
- MySPI_SwapByte(Address);
- MySPI_Stop();
-
- }
-
- void W25Q64_ReadData(uint32_t Address, uint8_t *Array, uint32_t Count)
- {
- //事前等待(更高效,但是在每个操作函数的开头都需要加)
- W25Q64_WaitBusy();
- MySPI_Start();
- MySPI_SwapByte(W25Q64_READ_DATA);
- MySPI_SwapByte(Address >> 16);
- MySPI_SwapByte(Address >> 8);
- MySPI_SwapByte(Address);
- for (uint32_t i = 0; i < Count; i ++)
- {
- Array[i] = MySPI_SwapByte(W25Q64_DUMMY_BYTE);
- }
- MySPI_Stop();
- }
-
主函数
- #include "stm32f10x.h" // Device header
- #include "Delay.h"
- #include "OLED.h"
- #include "W25Q64.h"
-
- uint8_t MID;
- uint16_t DID;
-
- uint8_t Array_W[4] = {0x01, 0x02, 0x03, 0x04};
- uint8_t Array_R[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, Array_W, 4);
- W25Q64_ReadData(0x000000, Array_R, 4);
-
- OLED_ShowHexNum(2,3,Array_W[0],2);
- OLED_ShowHexNum(2,6,Array_W[1],2);
- OLED_ShowHexNum(2,9,Array_W[2],2);
- OLED_ShowHexNum(2,12,Array_W[3],2);
-
- OLED_ShowHexNum(3,3,Array_R[0],2);
- OLED_ShowHexNum(3,6,Array_R[1],2);
- OLED_ShowHexNum(3,9,Array_R[2],2);
- OLED_ShowHexNum(3,12,Array_W[3],2);
- while(1)
- {
-
- }
- }
非连续传输的等待空隙在频率比较高时没太大影响,但是一旦频率很低,这个影响就不能被忽略了,所以我们在传输比较高频率的信号时不能使用非连续传输,可以使用连续输出或者进一步采用DMA自动转运。
其他内容真得看手册吧啊!
不用介绍
- void SPI_I2S_DeInit(SPI_TypeDef* SPIx);
- void SPI_Init(SPI_TypeDef* SPIx, SPI_InitTypeDef* SPI_InitStruct);
- void I2S_Init(SPI_TypeDef* SPIx, I2S_InitTypeDef* I2S_InitStruct);
- void SPI_StructInit(SPI_InitTypeDef* SPI_InitStruct);
- void I2S_StructInit(I2S_InitTypeDef* I2S_InitStruct);
- void SPI_Cmd(SPI_TypeDef* SPIx, FunctionalState NewState);
- void I2S_Cmd(SPI_TypeDef* SPIx, FunctionalState NewState);
- void SPI_I2S_ITConfig(SPI_TypeDef* SPIx, uint8_t SPI_I2S_IT, FunctionalState NewState);
- void SPI_I2S_DMACmd(SPI_TypeDef* SPIx, uint16_t SPI_I2S_DMAReq, FunctionalState NewState);
- //发送和接收数据
- void SPI_I2S_SendData(SPI_TypeDef* SPIx, uint16_t Data);
- uint16_t SPI_I2S_ReceiveData(SPI_TypeDef* SPIx);
标志位哥们
- FlagStatus SPI_I2S_GetFlagStatus(SPI_TypeDef* SPIx, uint16_t SPI_I2S_FLAG);
- void SPI_I2S_ClearFlag(SPI_TypeDef* SPIx, uint16_t SPI_I2S_FLAG);
- ITStatus SPI_I2S_GetITStatus(SPI_TypeDef* SPIx, uint8_t SPI_I2S_IT);
- void SPI_I2S_ClearITPendingBit(SPI_TypeDef* SPIx, uint8_t SPI_I2S_IT);
- //引脚初始化
- RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
- RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);
-
- GPIO_InitTypeDef GPIO_InitStruct;
- GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;
- GPIO_InitStruct.GPIO_Pin = GPIO_Pin_4;
- GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
- GPIO_Init(GPIOA, &GPIO_InitStruct);
-
- GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
- GPIO_InitStruct.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_7;
- GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
- GPIO_Init(GPIOA, &GPIO_InitStruct);
-
- GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU;
- GPIO_InitStruct.GPIO_Pin = GPIO_Pin_6;
- GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
- GPIO_Init(GPIOA, &GPIO_InitStruct);
- SPI_InitTypeDef SPI_InitStruct;
- //主从
- SPI_InitStruct.SPI_Mode = SPI_Mode_Master;
- /*裁剪SPI引脚的,即选择:
- 单线半双工的接收模式Rx
- 单线半双工的发送模式Tx
- 双线全双工Full
- 双线只接收模式RxOnly*/
- SPI_InitStruct.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
- //数据帧8位还是16位
- SPI_InitStruct.SPI_DataSize = SPI_DataSize_8b;
- //低位先行or高位先行
- SPI_InitStruct.SPI_FirstBit = SPI_FirstBit_MSB;
- //SCK时钟的频率(即选择分频系数
- SPI_InitStruct.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_128;
- //SPI模式
- //时钟极性-这里选择了默认为低电平
- SPI_InitStruct.SPI_CPOL = SPI_CPOL_Low;
- //1Edge即为0,2Enge即为1
- SPI_InitStruct.SPI_CPHA = SPI_CPHA_1Edge;
- //软件实现NSS
- SPI_InitStruct.SPI_NSS = SPI_NSS_Soft;
- //不用了解
- SPI_InitStruct.SPI_CRCPolynomial = 7;
- SPI_Init(SPI1,&SPI_InitStruct);
- SPI_Cmd(SPI1, ENABLE);
- MySPI_W_SS(1);
- uint8_t MySPI_SwapByte(uint8_t ByteSend)
- {
- //必须发送同时接收,两个过程是绑定进行的
- while(!SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE));
-
- SPI_I2S_SendData(SPI1, ByteSend);
-
- //在发送的同时MISO还会移位进行接收,发送和接收是同步的
- //接收移位完成了也就代表发送移位完成了
- //接收完成时会置标志位RXNE,我们可以借此来判断是否发送完数据
- while(!SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE));
- //标志位不需手动清除
- //读取数据
- return SPI_I2S_ReceiveData(SPI1);
- }