目录:
SPI简述
SPI总线网络
SPI工作机制
SPI初始化步骤
code
SPI简述:
1:SPI是一种高速,全双工的,同步的通信总线;
2:四线连接,MISO-主设备数据输入,从设备数据输出;MOSI-主设备数据输出,从设备数据输入;SCLK-时钟信号,由主设备产生;CS-从设备片选信号,由主设备控制。
3:主机和从机各有一个串行移位寄存器,两个寄存器中的数据在同一时刻被交换,如果只进行写操作,主机只需要忽略接收到的字节;如果主机要读取从机中的字节,就必须发送一个空字节来引发从机的传输。
4:SPI可以设置输出串行时钟的极性和相位,时钟极性与串行同步时钟的空闲状态的电平有关,CPOL=0,串行同步时钟空闲状态为低电平,反之为高;CPHA可以设置选择两种不同的传输协议,CPHA=0,串行同步时钟的第一个跳边沿数据被采样,反之,在串行同步时钟的第二个跳边沿数据被采样;
5:为了防止MISO总线冲突,同一时间只允许一个从设备与主设备通讯;
6:SPI主机和从机的时钟极性和相位应该一致;
7:在数据传输(数据交换)的过程中,每次接收到的数据必须在下一次数据传输之前被采样.如果之前接收到的数据没有被读取,那么这些已经接收完成的数据将有可能会被丢弃,导致SPI物理模块最终失效。因此,在程序中一般都会在SPI传输完数据后,去读取SPI设备里的数据,即使这些数据(DummyData)在我们的程序里是无用的。
SPI总线网络
SPI工作机制:
1:概述
2:时序
SPI初始化步骤:
1:配置相关引脚复用为SPI,使能SPI时钟;
2:设置SPI工作模式,包括主机或者从机、数据格式(高位在前还是低位在前)、设置串行时钟的极性和相位(采样方式)、SPI时钟频率(SPI的传输速度);
3:使能SPI;
code
spi.c-SPI驱动
/************************************************
*BOARD:STM32F103ZET6
*AUTHOR:Golf
*************************************************/
#include "spi.h"
void spi_init(void)
{
GPIO_InitTypeDef GPIO_InitStruct; //定义gpio结构体
SPI_InitTypeDef SPI_InitStruct; //SPI结构体初始化
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE); //使能GPIOB口时钟
RCC_APB1PeriphClockCmd( RCC_APB1Periph_SPI2, ENABLE ); //SPI2时钟使能
//GPIO结构体初始化
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP; //GPIO复用推挽
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_12 | GPIO_Pin_13 | GPIO_Pin_14 | GPIO_Pin_15; //SPI2_NSS复用 启用软件从设备管理 NSS引脚上的电平由SI位确定
GPIO_Init(GPIOA,&GPIO_InitStruct);
//SPI结构体初始化
SPI_InitStruct.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_2;
SPI_InitStruct.SPI_CPHA = SPI_CPHA_2Edge;
SPI_InitStruct.SPI_CPOL = SPI_CPOL_High;
SPI_InitStruct.SPI_CRCPolynomial = 7;
SPI_InitStruct.SPI_DataSize = SPI_DataSize_8b;
SPI_InitStruct.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
SPI_InitStruct.SPI_FirstBit = SPI_FirstBit_MSB;
SPI_InitStruct.SPI_Mode = SPI_Mode_Master;
SPI_InitStruct.SPI_NSS = SPI_NSS_Soft; //启动软件从设备管理
SPI_Init(SPI2,&SPI_InitStruct);
SPI_Cmd(SPI2,ENABLE); //spi2使能
}
//SPI发送一个字节
void SPI_Send_Byte(uint16_t data)
{
uint8_t cnt = 0;
while(SPI_I2S_GetFlagStatus(SPI2,SPI_I2S_FLAG_TXE) == RESET)
{
if(++cnt > 200)
return;
}
SPI_I2S_SendData(SPI2,data);
}
//SPI接受一个字节
unsigned char SPI_Receive_Byte(void)
{
uint8_t cnt = 0;
while(SPI_I2S_GetFlagStatus(SPI2,SPI_I2S_FLAG_RXNE) == RESET)
{
if(++cnt > 200)
return 0;
}
return SPI_I2S_ReceiveData(SPI2);
}
flash.c-w25q128驱动
#include "w25q128.h"
//指令定义
#define Write_Enable 0x06 //写使能
#define Write_Enable_Volatile_SR 0x50 //易失性寄存器写使能
#define Write_Disable 0x04 //写禁止
#define Read_SR1 0x05 //读状态寄存器1
#define Read_SR2 0x35 //读状态寄存器2
#define Write_Status_Register 0x01 //写状态寄存器
#define Read_Data 0x03 //读数据
#define Page_Program 0x02 //页编程命令
#define Sector_Erase 0x20 //扇区擦除
#define Block_Erase_32K 0x52 //32K块擦除
#define Block_Erase_64K 0xd8 //64K块擦除
#define Chip_Erase 0xc7 //芯片擦除 0x60
#define Power_Down 0xB9 //Power_Down模式
#define Release_Power_Down 0xab //恢复power_down模式
#define Read_Chip_ID 0x90 //读取设备ID
#define CS PBout(12)
//初始化SPI FLASH的IO口
void W25Q128_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);//使能GPIOB时钟
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_Init(GPIOB, &GPIO_InitStructure);
GPIO_SetBits(GPIOB,GPIO_Pin_12);
CS=1; //SPI FLASH不选中
SPI1_Init(); //初始化SPI
}
//等待空闲 超时退出
void Wait_idle(void)
{
uint8_t cnt = 0;
while((Read_SR1()&0x01) != 0x00)
{
if(cnt++ > 200) //200超时,退出循环
break;
}
}
//写使能 06H
void Write_Enable(void)
{
CS = 0; //拉低时钟
Spi_Send_Receive_Data(Write_Enable); //发送06H
CS = 1; //拉高时钟
}
//易失性寄存器写使能 50H
void Write_Enable_Volatile_SR(void)
{
CS = 0; //拉低时钟
Spi_Send_Receive_Data(Write_Enable_Volatile_SR); //发送50H
CS = 1; //拉高时钟
}
//写禁止
void Write_Disable(void)
{
CS = 0; //拉低时钟
Spi_Send_Receive_Data(Write_Disable); //发送50H
CS = 1; //拉高时钟
}
/*********************************************
*FUNC:读状态寄存器1
*INSTRUCTION:发送读状态寄存器1指令05H,返回状态寄存器1的值,返回的字节数,取决于主机发送的字节数,此指令可以在任何时候使用
**********************************************/
uint8_t Read_SR1(void)
{
uint8_t dat = 0;
CS = 0;
Spi_Send_Receive_Data(Read_SR1); //发送05H
dat = Spi_Send_Receive_Data(0xff); //发送一个字节0xff,返回一个字节(寄存器的值)
CS = 1;
return dat;
}
/*********************************************
*FUNC:读状态寄存器2
*INSTRUCTION:发送读状态寄存器1指令35H,返回状态寄存器1的值,返回的字节数,取决于主机发送的字节数,此指令可以在任何时候使用
**********************************************/
uint8_t Read_SR2(void)
{
uint8_t dat = 0;
CS = 0;
Spi_Send_Receive_Data(Read_SR2); //发送35H
dat = Spi_Send_Receive_Data(0xff); //发送一个字节0xff,返回一个字节(寄存器的值)
CS = 1;
return dat;
}
/*********************************************
*FUNC:写非易失性状态寄存器
*INSTRUCTION:写状态寄存器中非易失性的位,只对SRP0/SEC/TB/BP/CMP/LB/QE/SRP1有效
*********************************************/
void Write_SR_No_Volatile(uint16_t dat)
{
Write_Enable(); //写使能 06H
CS = 0;
Spi_Send_Receive_Data(Write_Status_Register); //发送01H
Spi_Send_Receive_Data((uint8_t)(dat>>8)); //发送dat数据
Spi_Send_Receive_Data((uint8_t)dat);
CS = 1;
Wait_idle(); //等待空闲
}
/*********************************************
*FUNC:写易失性状态寄存器
*INSTRUCTION:05H+01H
*********************************************/
void Write_SR_Volatile(uint16_t dat)
{
CS = 0;
Write_Enable_Volatile_SR(); //写使能 50H
Spi_Send_Receive_Data(Write_Status_Register); //发送01H
Spi_Send_Receive_Data((uint8_t)(dat>>8)); //发送dat数据
Spi_Send_Receive_Data((uint8_t)dat);
CS = 1;
Wait_idle(); //等待空闲
}
/*********************************************
*FUNC:从存储器读一个或者多个字节
*INSTRUCTION:可以从起始地址,读取单个或多个字节的数据,最多可以一次读出一个芯片
*********************************************/
void Read_Data(uint8_t *ptr,uint32_t address,uint8_t num)
{
CS = 0;
Spi_Send_Receive_Data(Read_Data); //0x03指令
Spi_Send_Receive_Data((uint8_t)(address >> 16)); //发送存储器地址 24位
Spi_Send_Receive_Data((uint8_t)(address >> 8));
Spi_Send_Receive_Data((uint8_t)address);
for(;num>0;num--)
{
*(ptr+i) = Spi_Send_Receive_Data(0xff);
}
CS = 1;
}
/***********************************************
*FUNC:页编程 每页256字节
*INSTRUCTION:也编程之前 应该擦除flash 允许1-256字节写入
***********************************************/
void Page_Program(uint8_t *ptr,uint32_t address,uint32_t num)
{
Write_Enable(); //写使能 06H
CS = 0;
Spi_Send_Receive_Data(Page_Program); //发送0x02指令
Spi_Send_Receive_Data((uint8_t)(address >> 16)); //发送存储器地址 24位
Spi_Send_Receive_Data((uint8_t)(address >> 8));
Spi_Send_Receive_Data((uint8_t)address);
for(;num>0;num--)
{
Spi_Send_Receive_Data(*(ptr+i));
}
CS = 1;
Wait_idle(); //等待空闲
}
//扇区内操作函数
void Handle_Sector(uint8_t *ptr,uint32_t address,uint32_t Byte_Num)
{
pageremain = 256 - address%256; //判断address所在页的剩余字节
if(Byte_Num < 256)
pageremain = Byte_Num;
else
pageremain = 256;
while(1) //使用循环进行页编程
{
Page_Program(ptr,address,pageremain);
if(pageremain == Byte_Num)
break;
address += pageremain;
ptr += pageremain;
Byte_Num -= pageremain;
if(Byte_Num > 256)
pageremain = 256;
else
pageremain = Byte_Num;
}
}
/***********************************************
*FUNC:写Flash
*INSTRUCTION:
*1:最终调用页编程指令;
*2: 判断扇区是否已擦除;
*3: 擦除的最小单位是扇区;
*4: 首先对地址进行分扇区,擦除当前扇区,再写扇区,然后进入下一个扇区进行写操作,每进入一个扇区都要判断当前所需写字节数量是否超出扇区范围
*5:扇区操作里,调用页编程函数,判断是否写结束。
*6: 此代码存储数据总是从扇区的第一个地址开始存储
***********************************************/
uint8_t Flash[4096];
void Write_Flash(uint8_t *ptr,uint32_t address,uint32_t Byte_Num)
{
uint8 i;
uint8_t *p;
uint16_t secpos = 0; //起始地址所在的扇区位置
uint16_t secoff = 0; //起始地址在当前扇区的偏移值
uint16_t secremain = 0; //起始地址开始,当前扇区剩余字节数
uint8_t pageremain = 0;
secpos = address/4096;
secoff = address%4096;
secremain = 4096 - secoff;
p = Flash;
if(Byte_Num < 4096) //当前扇区剩余字节够存放Byte_Num字节的数据
secremain = Byte_Num;
else
secremain = 4096;
while(1)
{
Sector_Erase(secpos); //擦数当前扇区,判断是都擦除成功
Read_Data(p,secpos*4096,4096);
for(i=0;i<4096;i++)
{
if(p[i] != 0xff)
break; //擦除失败
}
Handle_Sector(ptr,secpos*4096,secremain);
if(secremain == Byte_Num) //写FLASH结束
break;
else
{
secpos++;
ptr += secremain;
Byte_Num -= secremain;
if(Byte_Num < 4096)
secremain = Byte_Num;
else
secremain = 4096;
}
}
}
/***********************************************
*FUNC:擦除一个扇区
*INSTRUCTION:执行此命令之前,必须进行写实能命令,擦除4K-BYTE的空间,擦完之后,全部为0xff
***********************************************/
void Sector_Erase(uint32_t address)
{
Write_Enable(); //写使能 06H
CS = 0;
Spi_Send_Receive_Data(Page_Program); //发送0x20指令
Spi_Send_Receive_Data((uint8_t)(address >> 16)); //发送存储器地址 24位
Spi_Send_Receive_Data((uint8_t)(address >> 8));
Spi_Send_Receive_Data((uint8_t)address);
CS = 1;
Wait_idle(); //等待空闲
}
/**********************************************
*FUNC:擦除一个block
*INSTRUCTION:参数可写32k-byte和64k-byte,擦除完后全为0xff,首先进行写使能指令?
**********************************************/
void Block_Erase(uint8_t cmd,uint32_t address)
{
Write_Enable(); //写使能 06H
CS = 0;
Spi_Send_Receive_Data(cmd);
Spi_Send_Receive_Data((uint8_t)(address >> 16)); //发送存储器地址 24位
Spi_Send_Receive_Data((uint8_t)(address >> 8));
Spi_Send_Receive_Data(uint8_t)address);
CS = 1;
Wait_idle(); //等待空闲
}
/**********************************************
*FUNC:擦除整个芯片
*INSTRUCTION:发送0xc7/0x60就可擦除整个芯片 擦完后全为0xff
**********************************************/
void Chip_Erase(void)
{
Write_Enable(); //写使能 06H
CS = 0;
Spi_Send_Receive_Data(Chip_Erase);
CS = 1;
Wait_idle(); //等待空闲
}
/**********************************************
*FUNC:模块进入掉电模式,使得待机电流最小
*INSTRUCTION:进入此模式后,只有Release from Power-Down指令才能使芯片恢复到正常状态
**********************************************/
void Power_Down(void)
{
CS = 0;
Spi_Send_Receive_Data(Power_Down);
CS = 1;
Wait_idle(); //等待空闲
}
/**********************************************
*FUNC:唤醒Power_Down模式
*INSTRUCTION:发送Release_Power_Down即可
**********************************************/
void Release_Power_Down(void)
{
CS = 0;
Spi_Send_Receive_Data(Release_Power_Down);
CS = 1;
Wait_idle(); //等待空闲
}
/**********************************************
*FUNC:读取设备ID
*INSTRUCTION:先发指令 再发三个无效字节 最后发送0xff接收一个字节的数据 即设备ID
**********************************************/
uint8_t Release_Power_Down(void)
{
uint8_t dat;
CS = 0;
Spi_Send_Receive_Data(Release_Power_Down);
Spi_Send_Receive_Data(0xff);
Spi_Send_Receive_Data(0xff);
Spi_Send_Receive_Data(0xff);
dat = Spi_Send_Receive_Data(0xff);
CS = 1;
Wait_idle(); //等待空闲
return dat;
}
/***********************************************
*FUNC:读取芯片ID
*INSTRUCTION:发送指令90H+24位地址0x000000+芯片ID+制造商ID
***********************************************/
uint16_t Read_Chip_Id(void)
{
uin16_t id = 0;
CS = 0;
Spi_Send_Receive_Data(Read_Chip_ID);
Spi_Send_Receive_Data(0x00);
Spi_Send_Receive_Data(0x00);
Spi_Send_Receive_Data(0x00);
id = Spi_Send_Receive_Data(0xff)<<8;
id |= Spi_Send_Receive_Data(0xff);
CS = 1;
return id;
}