写在前面,本章节是为下一篇:FATFS文件系统移植,做准备;
(快写好,文章突然少了一半????????,气的头晕)
一、SPI通讯基本简述
1,是高速全双工、同步的通信总线,广泛地使用在ADC、LCD等设备与MCU间,要求通讯速率较高的场合;
2,硬件引脚结构
①SCK-时钟同步,由主机发送时钟信号,从机接受,并按照要求执行相应动作
②MOSI:master output slave input,主机输出,从机接受
③MISO:-------------------------------------,主机接受,从机输出
④SS:片选(从机收到SS低电平,人为被选中,与主机进行通讯),SPI模块中,一般不选用SPI模块的自带引脚,而是使用GPIO中的引脚的推挽模式进行输出;
(这样可以”无限“扩容 SPI的从机,而不受SPI模块SS引脚的数量限制)
3,SPI通讯的基本过程
①NSS片选(即上述的CS引脚),低电平表示选中模块,开始通讯;高电平表示停止通讯;
②触发位置,表示在这里可以进行数据位bit的变化;(一般是SCK的上升沿或者下降沿)
③采样位置,主机或者从机对MOSI或者MISO,进行数据的采集(一般是SCK的上升沿或者下降沿,具体由对应模式来决定);
4,CPOL/CPHA及通讯模式
上述在触发的采样的位置描述很模糊,原因:
实际到底在上升沿触发还是下降沿触发,上升沿采样还是下降沿采样,是由CPOL/CPHA决定
时钟极性CPOL:是指SPI通讯设备处于空闲状态时,SCK的电平状态;
CPOL=1,表示SCK在NSS片选位高电平时刻(未选中),SCK保持高电平状态;
CPOL=0,表示SCK在NSS片选位高电平时刻(未选中),SCK保持低电平状态;
时钟相位CPHA:是指数据的采样的时刻,即MOSI 、MISO的被主机或者采集采样的时间,是上升沿还是下降沿;
当CPHA=0时,MOSI或MISO数据线上的信号将会在SCK时钟线的“奇数边沿”被采样;
当CPHA=1时,MOSI或MISO数据线上的信号将会在SCK时钟线的“偶数边沿”被采样;
补充:奇数边沿、偶数边沿,就会关联到CPOL了,如果CPOL=0,那么奇数边沿就是上升沿
SPI四种模式
至此,SPI的基本介绍完毕;
二、STM32-SPI模块介绍
1,基本介绍
1-STM32的SPI外设可用作通讯的主机及从机;在多模块通讯的时刻 ,STM32会作为从机模式
2-支持最高的SCK时钟频率为fpclk/2 (STM32F10x型号的芯片默认fpclk1为72MHz,fpclk2为36MHz);这个主要和SPI所在时钟线决定
SPI1最高
3-完全支持SPI协议的4种模式;一般选用模式0和模式3,主要看从机芯片手册中,适用那种模式。主机和从机的协议模式要保持一致;
4-数据帧长度可设置为8位或16位;一般选用8位传递
5-可设置数据MSB先行或LSB先行,需要和从机保持一致,不然数据会异常;
6-支持双线全双工
三、STM32的SPI框图解析
①外部引脚:
②波特率发生器:主要根据时钟线给入的值,进行分频处理,分频值和控制模块的BR寄存器数值关联。一般采样2分频、4分频、8分频等等
③数据发送、接受模块:主要由三个寄存器组成,接受缓冲区、移位寄存器、发送缓冲区
在发送数据时刻,先将数据放到发送缓冲区,再由移位寄存器一位一位的发送出去;
需要注意的是:
1-数据发送,发送缓冲区非空,则表示数据发给到移位寄存器了,但是不表示数据发送完毕;
一般在接受寄存器非空时刻,认为数据发送完毕(具体看后续代码,这边记住就好);
2-数据接受,SPI中无法只接受,只要在发送数据时刻,才会启动SCK时钟,所有在数据接受过程中,发送部分要一致发空信号,比如0x00
④控制模块
1- 配置分频
2- 配置LSB\MSB先行
3- 8位、16位数据传递
四、STM32-gpio引脚配置
这边参考STM32芯片手册推荐方式配置
五、SPI模式结构体配置说明
至此STM32- SPI介绍完毕
六、FALSH模块介绍W25Q64BV
这边相等于对于芯片手册 ,进行一次说明
1,引脚说明
CS:片选
DO: 连接SPI的MISO
DI: 连接SPI的MOSI
clk:连接时钟clk
其余引脚按需配置
2,内存空间说明
空间由128个块组成,每一个块大小64kb,每一个块被分成16个扇区,其中每个扇区大小4kb
3,可写入标志位
对于内存写入的操作,无论是eeprom还是flash,都写入的时间都会相对较长,在写入数据或者读取数据前,要确认当前flash模式是否处于busy状态
4,出厂标号
出厂标号,是flash的内部写入值,一般是用于通讯识别spi和flash正常进行通讯;
5,W25Q64BV的各种指令
对于flash的各种操作都是通过SPI发送指令,从而达到和flash进行指令交互
指令的发送和数据接受发送协议,是控制flash交互最重要的部分
①写使能
//写使能
void write_enable(void)
{
chip_start();
spi_sendbit(0x06);
chip_stop();
printf("写使能成功!\n");
}
②读寄存器状态
//等待flash
void wait_nobusy(void)
{
uint8_t state;
chip_start();
spi_sendbit(0x05);
do{
state=spi_sendbit(0x00);
}while((state&0x01)==1);
chip_stop();
}
③读数据寄存器,最大一次读取4096个字节
//读取
void read_buffer(uint32_t addr,uint8_t* readdata,uint16_t readnum)
{
//等待flash no busy
wait_nobusy();
//启动
chip_start();
spi_sendbit(0x03);
spi_sendbit((addr>>16)&0xff);
spi_sendbit((addr>>8)&0xff);
spi_sendbit((addr)&0xff);
while(readnum)
{
* readdata=spi_sendbit(0x00);
readdata++;
readnum--;
}
printf("buffer读取成功!\n");
chip_stop();
//等待flash no busy
wait_nobusy();
}
④写数据,最大写入256一次,写入前必须进行擦除
//扇区擦除,擦除前要加上写使能
void clear_sector(uint32_t addr)
{
//写使能开启
write_enable();
//等待flash no busy
wait_nobusy();
//启动
chip_start();
spi_sendbit(0x20);
spi_sendbit((addr>>16)&0xff);
spi_sendbit((addr>>8)&0xff);
spi_sendbit((addr)&0xff);
printf("buffer擦除成功!\n");
chip_stop();
//等待flash no busy
wait_nobusy();
}
//写入前必须,先擦除
void write_buffer(uint32_t addr,uint8_t* writedata,uint16_t writenum)
{
//写使能开启
write_enable();
//等待flash no busy
wait_nobusy();
//启动
chip_start();
spi_sendbit(0x02);
spi_sendbit((addr>>16)&0xff);
spi_sendbit((addr>>8)&0xff);
spi_sendbit((addr)&0xff);
while(writenum)
{
spi_sendbit(* writedata);
writedata++;
writenum--;
}
printf("buffer写入成功!\n");
chip_stop();
//等待flash no busy
wait_nobusy();
}
⑤擦除数据
为什么写入数据前要擦除扇区:flash中的数据,只能从1变成0,而不能从0到1 ,所以每次写入数据时刻,要先将扇区擦除掉,再写入
每次擦除的
//扇区擦除,擦除前要加上写使能
void clear_sector(uint32_t addr)
{
//写使能开启
write_enable();
//等待flash no busy
wait_nobusy();
//启动
chip_start();
spi_sendbit(0x20);
spi_sendbit((addr>>16)&0xff);
spi_sendbit((addr>>8)&0xff);
spi_sendbit((addr)&0xff);
printf("buffer擦除成功!\n");
chip_stop();
//等待flash no busy
wait_nobusy();
}
⑥读取设备号信息
uint32_t read_flash_id(void)
{
uint32_t flashid=0;
//开始
chip_start();
spi_sendbit(0x9f);
flashid =spi_sendbit(0x00);
flashid<<=8;
flashid|=spi_sendbit(0x00);
flashid<<=8;
flashid|=spi_sendbit(0x00);
chip_stop();
return flashid;
}
至此,flashW25Q64BV介绍
下面正式进入代码模块
一、代码逻辑梳理
1,初始化时钟
2,初始化GPIO
3,初始化SPI1
4,启动spi1
5,配置启动和停止模块
6,配置发送 8bit数据并接受8bit数据传递(基本模块)
7,配置读falsh设备id指令
8,写使能
9,等待flash非busy
10,擦除使能
11,写入数据
12,读取数据
1,初始化时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE); //gpio时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1,ENABLE); //spi模块时钟
2,初始化GPIO
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.GPIO_Mode=GPIO_Mode_Out_PP; //PIN4 CS片选 普通推挽输出
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; //PIN5 CLK时钟 复用推挽输出
GPIO_InitStruct.GPIO_Pin=GPIO_Pin_5;
GPIO_Init( GPIOA, &GPIO_InitStruct);
GPIO_InitStruct.GPIO_Mode=GPIO_Mode_AF_PP; //PIN6 MISO 复用推挽输出
GPIO_InitStruct.GPIO_Pin=GPIO_Pin_6;
GPIO_Init( GPIOA, &GPIO_InitStruct);
GPIO_InitStruct.GPIO_Mode=GPIO_Mode_AF_PP; //PIN7 MOSI 复用推挽输出
GPIO_InitStruct.GPIO_Pin=GPIO_Pin_7;
GPIO_Init( GPIOA, &GPIO_InitStruct);
3,初始化SPI1
SPI_InitTypeDef SPI_InitStruct;
SPI_InitStruct.SPI_BaudRatePrescaler=SPI_BaudRatePrescaler_2; //二分频
SPI_InitStruct.SPI_CPHA=SPI_CPHA_2Edge;
SPI_InitStruct.SPI_CPOL=SPI_CPOL_High ;//采用模式3,空闲高电平,偶数边沿采样
SPI_InitStruct.SPI_CRCPolynomial=0;//不使用CRC校验
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( SPI1, &SPI_InitStruct);
4,启动spi1
SPI_Cmd( SPI1,ENABLE);
5,配置启动和停止模块
void chip_start(void)
{
GPIO_ResetBits(GPIOA,GPIO_Pin_4);
}
void chip_stop(void)
{
GPIO_SetBits(GPIOA,GPIO_Pin_4);
}
6,配置发送 8bit数据并接受8bit数据传递(基本模块)
uint8_t spi_sendbit(uint8_t byte)
{
//等待发送寄存器为空
while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE)==RESET);
//发送数据
SPI_I2S_SendData(SPI1, byte);
//等待接受寄存器非空,表示数据发送完毕
while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE)==RESET);
//返回接受数据
printf("发送完成\n");
return SPI_I2S_ReceiveData( SPI1);
}
7,配置读falsh设备id指令
uint32_t read_flash_id(void)
{
uint32_t flashid=0;
//开始
chip_start();
spi_sendbit(0x9f);
flashid =spi_sendbit(0x00);
flashid<<=8;
flashid|=spi_sendbit(0x00);
flashid<<=8;
flashid|=spi_sendbit(0x00);
chip_stop();
return flashid;
}
8,写使能
//写使能
void write_enable(void)
{
chip_start();
spi_sendbit(0x06);
chip_stop();
printf("写使能成功!\n");
}
9,等待flash非busy
//等待flash
void wait_nobusy(void)
{
uint8_t state;
chip_start();
spi_sendbit(0x05);
do{
state=spi_sendbit(0x00);
}while((state&0x01)==1);
chip_stop();
}
10,擦除使能
//扇区擦除,擦除前要加上写使能
void clear_sector(uint32_t addr)
{
//写使能开启
write_enable();
//等待flash no busy
wait_nobusy();
//启动
chip_start();
spi_sendbit(0x20);
spi_sendbit((addr>>16)&0xff);
spi_sendbit((addr>>8)&0xff);
spi_sendbit((addr)&0xff);
printf("buffer擦除成功!\n");
chip_stop();
//等待flash no busy
wait_nobusy();
}
11,写入数据,最大256
//写入前必须,先擦除
void write_buffer(uint32_t addr,uint8_t* writedata,uint16_t writenum)
{
//写使能开启
write_enable();
//等待flash no busy
wait_nobusy();
//启动
chip_start();
spi_sendbit(0x02);
spi_sendbit((addr>>16)&0xff);
spi_sendbit((addr>>8)&0xff);
spi_sendbit((addr)&0xff);
while(writenum)
{
spi_sendbit(* writedata);
writedata++;
writenum--;
}
printf("buffer写入成功!\n");
chip_stop();
//等待flash no busy
wait_nobusy();
}
12,读取数据,最大4096
//读取
void read_buffer(uint32_t addr,uint8_t* readdata,uint16_t readnum)
{
//等待flash no busy
wait_nobusy();
//启动
chip_start();
spi_sendbit(0x03);
spi_sendbit((addr>>16)&0xff);
spi_sendbit((addr>>8)&0xff);
spi_sendbit((addr)&0xff);
while(readnum)
{
* readdata=spi_sendbit(0x00);
readdata++;
readnum--;
}
printf("buffer读取成功!\n");
chip_stop();
//等待flash no busy
wait_nobusy();
}
在主函数中进行简单配置
#include "stm32f10x.h" // Device header
#include "LED.H"
#include "BSP_USART.H"
#include "BSP_SPI.H"
void delay(uint32_t time)
{
while(time--)
{
}
}
uint8_t writebuf[30];
uint8_t readbuf[30];
uint32_t id;
uint8_t i=0;
int main(void)
{
bsp_usart_config();
spi_flash_config();
for(i=0;i<30;i++)
{
writebuf[i]=i;
}
id=read_flash_id();
printf("id=%x\n",id);
clear_sector(0);
write_buffer(0,writebuf,30);
read_buffer(0,readbuf,30);
for(i=0;i<30;i++)
{
printf("0x%x ",readbuf[i]);
if((i%10==0)&&(i!=0))
printf("\n");
}
while(1)
{
}
}
最后补充一下,上面的写入是最大写入256个字节,如果想要无限字节(在内存允许下)的写入,则需要将原本的数据,进行分块处理,这边直接借用了野火的代码
/**
* @brief 对FLASH写入数据,调用本函数写入数据前需要先擦除扇区
* @param pBuffer,要写入数据的指针
* @param WriteAddr,写入地址
* @param NumByteToWrite,写入数据长度
* @retval 无
*/
void SPI_FLASH_BufferWrite(u8* pBuffer, u32 WriteAddr, u16 NumByteToWrite)
{
u8 NumOfPage = 0, NumOfSingle = 0, Addr = 0, count = 0, temp = 0;
/*mod运算求余,若writeAddr是SPI_FLASH_PageSize整数倍,运算结果Addr值为0*/
Addr = WriteAddr % SPI_FLASH_PageSize;
/*差count个数据值,刚好可以对齐到页地址*/
count = SPI_FLASH_PageSize - Addr;
/*计算出要写多少整数页*/
NumOfPage = NumByteToWrite / SPI_FLASH_PageSize;
/*mod运算求余,计算出剩余不满一页的字节数*/
NumOfSingle = NumByteToWrite % SPI_FLASH_PageSize;
/* Addr=0,则WriteAddr 刚好按页对齐 aligned */
if (Addr == 0)
{
/* NumByteToWrite < SPI_FLASH_PageSize */
if (NumOfPage == 0)
{
SPI_FLASH_PageWrite(pBuffer, WriteAddr, NumByteToWrite);
}
else /* NumByteToWrite > SPI_FLASH_PageSize */
{
/*先把整数页都写了*/
while (NumOfPage--)
{
SPI_FLASH_PageWrite(pBuffer, WriteAddr, SPI_FLASH_PageSize);
WriteAddr += SPI_FLASH_PageSize;
pBuffer += SPI_FLASH_PageSize;
}
/*若有多余的不满一页的数据,把它写完*/
SPI_FLASH_PageWrite(pBuffer, WriteAddr, NumOfSingle);
}
}
/* 若地址与 SPI_FLASH_PageSize 不对齐 */
else
{
/* NumByteToWrite < SPI_FLASH_PageSize */
if (NumOfPage == 0)
{
/*当前页剩余的count个位置比NumOfSingle小,一页写不完*/
if (NumOfSingle > count)
{
temp = NumOfSingle - count;
/*先写满当前页*/
SPI_FLASH_PageWrite(pBuffer, WriteAddr, count);
WriteAddr += count;
pBuffer += count;
/*再写剩余的数据*/
SPI_FLASH_PageWrite(pBuffer, WriteAddr, temp);
}
else /*当前页剩余的count个位置能写完NumOfSingle个数据*/
{
SPI_FLASH_PageWrite(pBuffer, WriteAddr, NumByteToWrite);
}
}
else /* NumByteToWrite > SPI_FLASH_PageSize */
{
/*地址不对齐多出的count分开处理,不加入这个运算*/
NumByteToWrite -= count;
NumOfPage = NumByteToWrite / SPI_FLASH_PageSize;
NumOfSingle = NumByteToWrite % SPI_FLASH_PageSize;
/* 先写完count个数据,为的是让下一次要写的地址对齐 */
SPI_FLASH_PageWrite(pBuffer, WriteAddr, count);
/* 接下来就重复地址对齐的情况 */
WriteAddr += count;
pBuffer += count;
/*把整数页都写了*/
while (NumOfPage--)
{
SPI_FLASH_PageWrite(pBuffer, WriteAddr, SPI_FLASH_PageSize);
WriteAddr += SPI_FLASH_PageSize;
pBuffer += SPI_FLASH_PageSize;
}
/*若有多余的不满一页的数据,把它写完*/
if (NumOfSingle != 0)
{
SPI_FLASH_PageWrite(pBuffer, WriteAddr, NumOfSingle);
}
}
}
}