Appearance
Flash(SPI)
Serial Peripheral Interface串行外设设备接口
是一种全双工的协议, 传输的速率比较较高
SS(CS): 设备选择线, 片选信号, 每一个从机都有一根SS线, 低电平表示选中
SCK: 时钟, 用于信号的同步, STM32支持的最大频率为F~pclk~/2
MOSI: 主机输入, 从机发送
MISO: 主机发送, 从机接收
SPI1在APB2上面, SPI2/3在APB1上面
高位字节先行
时钟相位: 设备处于空闲状态的时候, SCK时钟线的电平状态, CPHA指数据采样的时刻, CPHA=0的时候会在奇数边沿采样, CPHA=1的时候会在偶数边沿采样
常用的是模式0和3
STM32的实现
SPI1最高36MHz, SPI2/3最高18MHz, 支持上面的四种模式, 数据可以是8位或16位, 也可以控制高位先行还是低位先行
还可以两根线传输同方向
SPI2/3可以作为I2S通讯, SPI3的引脚和下载器共用引脚
TXE表示发送缓冲区为空, 可以写入
RXNE表示受到一个数据, 可以读取
在接收的时候需要写入从而产生时钟
寄存器
- SPI_CR1: 控制寄存器
- SPI_CR2: 控制寄存器
- SPI_SR: 状态寄存器
- SPI_DR: 数据寄存器
SPI_CRCPR: 包含CRC计算数值
SPI_RXCRCR: SPI Rx CRC寄存器
CRC校验用于保证全双工通信的可靠性。数据发送和数据接收分别使用单独的CRC计算器。通 过对每一个接收位进行可编程的多项式运算来计算CRC。CRC的计算是在由SPI_CR1寄存器中 CPHA和CPOL位定义的采样时钟边沿进行的。 注意: 该SPI接口提供了两种CRC计算方法,取决于所选的发送和/或接收的数据帧格式:8位数据帧采 用CR8;16位数据帧采用CRC16
标准库
c
typedef struct
{
uint16_t SPI_Direction; /*!< Specifies the SPI unidirectional or bidirectional data mode.
This parameter can be a value of @ref SPI_data_direction
控制使用的模式, 几根线以及传输方向*/
uint16_t SPI_Mode; /*!< Specifies the SPI operating mode.
This parameter can be a value of @ref SPI_mode控制主机还是从机 */
uint16_t SPI_DataSize; /*!< Specifies the SPI data size.
This parameter can be a value of @ref SPI_data_size
控制数据的位数*/
uint16_t SPI_CPOL; /*!< Specifies the serial clock steady state.
This parameter can be a value of @ref SPI_Clock_Polarity
不用的时候时钟线的电平*/
uint16_t SPI_CPHA; /*!< Specifies the clock active edge for the bit capture.
This parameter can be a value of @ref SPI_Clock_Phase
在第几个边沿进行采集*/
uint16_t SPI_NSS; /*!< Specifies whether the NSS signal is managed by
hardware (NSS pin) or by software using the SSI bit.
This parameter can be a value of @ref SPI_Slave_Select_management
软件还是硬件控制片选*/
uint16_t SPI_BaudRatePrescaler; /*!< Specifies the Baud Rate prescaler value which will be
used to configure the transmit and receive SCK clock.
This parameter can be a value of @ref SPI_BaudRate_Prescaler.
@note The communication clock is derived from the master
clock. The slave clock does not need to be set. 时钟
分频因子*/
uint16_t SPI_FirstBit; /*!< Specifies whether data transfers start from MSB or LSB bit.
This parameter can be a value of @ref SPI_MSB_LSB_transmission
MSB还是LSB先行*/
uint16_t SPI_CRCPolynomial; /*!< Specifies the polynomial used for the CRC calculation.CRC校验 */
}SPI_InitTypeDef;
FLashW25Q64
8M字节, nor flash
DO: MISO
DI: MDIO
/CS: 片选
/WP: 写保护
/HOLD: 暂停通讯
CLK: 时钟
Standard SPI instructions use the unidirectional DI (input) pin to serially write instructions, addresses or data to the device on the rising edge of the Serial Clock (CLK) input pin. Standard SPI also uses the unidirectional DO (output) to read data or status from the device on the falling edge CLK. (可以使用模式0或者模式3)
分为127个块, 每个块64kb, 块又分为16个扇区, 每一个4kb, 在写入的数据之前需要进行擦除, 只能把为1的数据位改为0, 擦除的时候只能按照最小的单元进行, 在写入的时候没有限制, 为nor Flash, nand Flash需要以扇区进行写入
有一个状态寄存器, BUSY位为忙状态
- 实际读写
带括号的为返回值
擦除, 0x20之后发送三字节的地址
写入, 0x02
几个id可以读取, 用来验证是否连接成功
开发板情况
指南者CS连接PC0
实际实现
__I, __O, __IO: 控制编译器不会进行优化, 每次读取的时候都会从内存中获取数据
在写\擦除之前需要进行写使能
c
#ifndef __BSP_SPI_H__
#define __BSP_SPI_H__
#include "stm32f10x.h"
#include <stdio.h>
#define FLASH_SPIx SPI1
#define FLASH_SPIx_CLK RCC_APB2Periph_SPI1
#define FLASH_SPIx_APBxClkCmd RCC_APB2PeriphClockCmd
#define FLASH_SPIx_BAUDRATE 400000
#define SPIT_PLAG_TIMEOUT ((uint32_t)0x1000)
// USART GPIO 引脚宏定义
#define FLASH_SPIx_GPIO_CLK (RCC_APB2Periph_GPIOC | RCC_APB2Periph_GPIOA)
#define FLASH_SPIx_GPIO_APBxClkCmd RCC_APB2PeriphClockCmd
#define FLASH_SPIx_MISO_GPIO_PORT GPIOA
#define FLASH_SPIx_MISO_GPIO_PIN GPIO_Pin_6
#define FLASH_SPIx_MOSI_GPIO_PORT GPIOA
#define FLASH_SPIx_MOSI_GPIO_PIN GPIO_Pin_7
#define FLASH_SPIx_CLK_GPIO_PORT GPIOA
#define FLASH_SPIx_CLK_GPIO_PIN GPIO_Pin_5
#define FLASH_SPIx_CS_GPIO_PORT GPIOC
#define FLASH_SPIx_CS_GPIO_PIN GPIO_Pin_0
//控制CS引脚
#define FLASH_SPI_CS_HIGH GPIO_SetBits(FLASH_SPIx_CS_GPIO_PORT, FLASH_SPIx_CS_GPIO_PIN);
#define FLASH_SPI_CS_LOW GPIO_ResetBits(FLASH_SPIx_CS_GPIO_PORT, FLASH_SPIx_CS_GPIO_PIN);
/*信息输出*/
#define FLASH_DEBUG_ON 1
#define FLASH_INFO(fmt,arg...) printf("<<-FLASH-INFO->> "fmt"\n",##arg)
#define FLASH_ERROR(fmt,arg...) printf("<<-FLASH-ERROR->> "fmt"\n",##arg)
#define FLASH_DEBUG(fmt,arg...) do{\
if(FLASH_DEBUG_ON)\
printf("<<-FLASH-DEBUG->> [%d]"fmt"\n",__LINE__, ##arg);\
}while(0)
//无意义产生时钟数据
#define DUMMY 0x00
//命令
#define READ_IEDEC_ID 0x9f
#define ERASE_SECTOR 0x20
#define READ_STATUS 0x05
#define READ_DATA 0x03
#define WRITE_ENABLE 0x06
void Flash_SPI_Config(void);
uint8_t Flash_SPI_Send_Receive_Byte(uint8_t data);
uint8_t SPI_FLASH_Read_Byte(void);
uint32_t SPI_Read_ID(void);
void Flash_SPI_WatiFanish(void);
void Flash_ERASE_SECTOR(uint32_t addr);
void Flash_READ_SECTOR(uint32_t addr, uint8_t *buff, uint32_t numByteToRead);
void SPI_Write_Enable(void);
#endif
c
#include "bsp_spi.h"
static __IO uint32_t SPITimeOut = SPIT_PLAG_TIMEOUT;
static uint16_t SPI_TIMEOUT_UserCallback(uint8_t errorCode);
//初始化SPI1的引脚
static void Flash_GPIO_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
//初始化时钟
FLASH_SPIx_GPIO_APBxClkCmd(FLASH_SPIx_GPIO_CLK, ENABLE);
//设置GPIO, CLK
GPIO_InitStructure.GPIO_Pin = FLASH_SPIx_CLK_GPIO_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(FLASH_SPIx_CLK_GPIO_PORT, &GPIO_InitStructure);
//MOSI
GPIO_InitStructure.GPIO_Pin = FLASH_SPIx_MOSI_GPIO_PIN;
GPIO_Init(FLASH_SPIx_MOSI_GPIO_PORT, &GPIO_InitStructure);
//MISO
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_InitStructure.GPIO_Pin = FLASH_SPIx_MISO_GPIO_PIN;
GPIO_Init(FLASH_SPIx_MISO_GPIO_PORT, &GPIO_InitStructure);
//CS
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Pin = FLASH_SPIx_CS_GPIO_PIN;
GPIO_Init(FLASH_SPIx_CS_GPIO_PORT, &GPIO_InitStructure);
FLASH_SPI_CS_HIGH;
}
//初始化FLASH的SPI
void Flash_SPI_Config(void)
{
//初始化引脚
Flash_GPIO_Config();
//初始化时钟
FLASH_SPIx_APBxClkCmd(FLASH_SPIx_CLK, ENABLE);
//初始化结构体
SPI_InitTypeDef SPI_InitStructure;
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_2;
SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;
SPI_InitStructure.SPI_CPOL = SPI_CPOL_High; //使用模式3
SPI_InitStructure.SPI_CRCPolynomial = 0;//不使用CRC校验
SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;
SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;//大端先行
SPI_InitStructure.SPI_Mode = SPI_Mode_Master;
SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;
SPI_Init(FLASH_SPIx, &SPI_InitStructure);
//使能
SPI_Cmd(FLASH_SPIx, ENABLE);
}
//发送接收, 一个字节, 接收为返回值
uint8_t Flash_SPI_Send_Receive_Byte(uint8_t data)
{
SPITimeOut = SPIT_PLAG_TIMEOUT;
//读取寄存器是否为空
while(SPI_I2S_GetFlagStatus(FLASH_SPIx, SPI_I2S_FLAG_TXE)==RESET)
{
if((SPITimeOut--)==0)
return SPI_TIMEOUT_UserCallback(0);
}
//缓冲区为空, 开始发送
SPI_I2S_SendData(FLASH_SPIx,data);
//确认发送完成
while(SPI_I2S_GetFlagStatus(FLASH_SPIx, SPI_I2S_FLAG_RXNE)==RESET)
{
if((SPITimeOut--)==0)
return SPI_TIMEOUT_UserCallback(1);
}
return SPI_I2S_ReceiveData(FLASH_SPIx);
}
//读取一个字节, 作为返回值
uint8_t SPI_FLASH_Read_Byte()
{
return Flash_SPI_Send_Receive_Byte(DUMMY);
}
void SPI_Write_Enable(void)
{
FLASH_SPI_CS_LOW
Flash_SPI_Send_Receive_Byte(WRITE_ENABLE);
FLASH_SPI_CS_HIGH
}
//读取ID
uint32_t SPI_Read_ID(void)
{
uint32_t flash_id;
//片选
FLASH_SPI_CS_LOW
Flash_SPI_Send_Receive_Byte(READ_IEDEC_ID);
flash_id = SPI_FLASH_Read_Byte();
flash_id <<= 8;
flash_id |= SPI_FLASH_Read_Byte();
flash_id <<= 8;
flash_id |= SPI_FLASH_Read_Byte();
FLASH_SPI_CS_HIGH
return flash_id;
}
//等待操作完成, 通过判断Flash状态寄存器的状态完成
void Flash_SPI_WatiFanish(void)
{
SPITimeOut = SPIT_PLAG_TIMEOUT;
FLASH_SPI_CS_LOW
uint8_t status = 0;
do{
Flash_SPI_Send_Receive_Byte(READ_STATUS);
status = Flash_SPI_Send_Receive_Byte(DUMMY);
SPITimeOut--;
}while((status & 0x01 ==1) && SPITimeOut);
if(SPITimeOut==0)
{
SPI_TIMEOUT_UserCallback(2);
}
//取消片选
FLASH_SPI_CS_HIGH
}
//进行擦除4k
void Flash_ERASE_SECTOR(uint32_t addr)
{
SPI_Write_Enable();
Flash_SPI_WatiFanish();
//片选
FLASH_SPI_CS_LOW
//发送命令
Flash_SPI_Send_Receive_Byte(ERASE_SECTOR);
Flash_SPI_Send_Receive_Byte((addr>>16)&0xff);
Flash_SPI_Send_Receive_Byte((addr>>8)&0xff);
Flash_SPI_Send_Receive_Byte((addr>>0)&0xff);
FLASH_SPI_CS_HIGH
}
//读取的函数
void Flash_READ_SECTOR(uint32_t addr, uint8_t *buff, uint32_t numByteToRead)
{
//片选
FLASH_SPI_CS_LOW
//发送命令
Flash_SPI_Send_Receive_Byte(READ_DATA);
//发送地址
Flash_SPI_Send_Receive_Byte((addr>>16)&0xff);
Flash_SPI_Send_Receive_Byte((addr>>8)&0xff);
Flash_SPI_Send_Receive_Byte(addr&0xff);
//进行读取
while(numByteToRead--)
{
*buff = SPI_FLASH_Read_Byte();
buff++;
}
FLASH_SPI_CS_HIGH
}
//写入的时候最大为256, 且需要对齐
void Flash_Write_Sector(uint32_t addr, uint8_t *buff, uint32_t numByteToWrite){
//写使能
SPI_Write_Enable();
//片选
FLASH_SPI_CS_LOW
//发送命令
Flash_SPI_Send_Receive_Byte(WRITE_DATA);
//发送地址
Flash_SPI_Send_Receive_Byte((addr>>16)&0xff);
Flash_SPI_Send_Receive_Byte((addr>>8)&0xff);
Flash_SPI_Send_Receive_Byte(addr&0xff);
//进行读取
while(numByteToWrite--)
{
Flash_SPI_Send_Receive_Byte(*buff);
buff++;
}
FLASH_SPI_CS_HIGH
Flash_SPI_WatiFanish();
}
/**
* @brief 等待超时回调函数
* @param None.
* @retval None.
*/
static uint16_t SPI_TIMEOUT_UserCallback(uint8_t errorCode)
{
/* 等待超时后的处理,输出错误信息 */
FLASH_ERROR("SPI 等待超时!errorCode = %d",errorCode);
return 0;
}
地址规划
NAND FLASH
使用FMC-NAND FLASH接口进行驱动
是一种非易失性的存储器, 内部使用非线性宏单元模式, 为固态大容量内存提供了解决方案
存储的密度高, 速度快, 功耗低, 寿命长, 价格比较低
MT29F4G08/H27U4G8
板载的是512M的
SD卡, U盘, 固态硬盘使用的都是这一种, 但是坏块的问题, 还有EMMC这个有一个控制芯片解决坏块
存储
他的地址分为三类, 块地址, 页地址, 和列地址
这三个地址通过5个周期进行发送, 首先发送列地址, 之后是页地址和块地址
通常情况下会把块和页地址看为同一个地址, 理解为page的地址
命令
最后一个只能在同一个plane里面操作
出了查手册看时间,也可以通过检查忙引脚的状态
管理的难点
- 坏块的解决
接口(FMC)
FMC主要就是把读写的操作变成满足外部存储器的时序
主要就是配置FMC的时序寄存器