CubeMx配置
CSS片选:选中时为低电平;
MISO:主机输入从机输出,上拉输入模式;
MOSI:主机输出从机输出,推挽输出模式;
SCK:时钟线。
选用SPI1,PB0作为片选线,全双工主机模式,配置如图:
串口用来检测打印输出。

全双工主机模式 进行通信。MSB高位优先传输 ,一次传输8bit;NSS 片选信号类型为软件控制。

设置波特率以及CPOL和CPHA的值,规则如下:
CPOL:SPI 空闲时,SCK是高电平还是低电平,为0时为低电平;
CPHA:在SCK的第几个边沿采样数据?可以设置为Edge1/2。
所以有4种情况:W25Q64支持Mode0/3,目前采用Mode0,PB0接片选,默认高电平。
|--------|------|-------|--------------|
| | CPOL | CPHA | 备注 |
| Mode 0 | 0 | 1Edge | 空闲为低电平,上升沿采样 |
| Mode 1 | 0 | 2Edge | 空闲为低电平,下降沿采样 |
| Mode 2 | 1 | 1Edge | 空闲为高电平,下降沿采样 |
| Mode 3 | 1 | 2Edge | 空闲为高电平,上升沿采样 |
flash写入读取规则
flash的写入和读取: 写入时,必须先写使能: 每个数据只能从1变为0,不能从0变为1。所以一般发送垃圾数据都发0XFF(什么都不改变) 写入数据前必须擦除,擦除后数据全为1,最小单位是扇区4KB 连续写入多字节,最多写入一页的数据 写入操作完成后芯片进入忙状态 读取: 不能在忙状态时读取。 地址格式:[ 1 byte 指令码 ] [ 3 byte 地址 ] [ N byte 数据 ] 读取/写入:发送指令,然后发送24位地址,然后读或者写数据
片选线拉低后才能进行通信,在 SPI Flash 中不仅是片选,也是"帧开始/结束"的标志,比如下面测试通信的代码,要按照:
起始条件:SS被选中,高电平转低电平;
交换一个字节(模式0):SCK空闲低电平,第一个边沿采样(移入移位寄存器),第二个边沿从移位寄存器移出数据;
结束条件:SS低电平转高电平。
的格式。
硬件驱动函数(SPI读取W25Q64)
地址格式
地址格式:首位不用,23位有效表示8M字节 0 0000000 00000000 00000000 如下: 0 0000000 0000 0000 00000000 127个块(7bit) 16个扇区(4bit) 16个页(4bit) 每页256B(8bit)
一次通信必须是完整的CS拉低、拉高,中间不能插入别的事务。
写使能
cpp
/**
* @retval 帧开始/起始信号,拉低片选线
*/
void MySPI_Start(void) {
//拉低片选线 PB0
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_RESET);
}
/**
* @retval 帧结束信号,拉高片选线
*/
void MySPI_Stop(void) {
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_SET);
}
/**
* @retval 写使能,发送写使能指令,表示要进行写入
*/
void W25Q64_WriteEnable(void) {//写使能
uint8_t write_enable = 0x06;
MySPI_Start();
HAL_SPI_Transmit(&hspi1, &write_enable, 1, 10);
MySPI_Stop();
}
等待空闲
cpp
/**
* @retval 状态寄存器1的bit0位是busy位,为1时表示忙,为0时表示空闲
*
*/
void W25Q64_WaitBusy(void) {
uint8_t cmd = 0x05;//读状态寄存器1指令
uint8_t status;
MySPI_Start();
HAL_SPI_Transmit(&hspi1, &cmd, 1, 100);//第0位是忙为
do {
HAL_SPI_Receive(&hspi1, &status, 1, 100);
}while ((status & 0x01) == 0x01);
MySPI_Stop();
}
获取设备ID
cpp
/**
*
* @param MID 制造商ID
* @param DID 设备ID
* @retval 获取设备ID,直接用三个uint8_t类型保存,可以串口发送
*/
void W25Q64_GetID(uint8_t* MID,uint8_t* DID1,uint8_t* DID2) {
uint8_t cmd = 0x9F;
uint8_t id[3];
MySPI_Start();
HAL_SPI_Transmit(&hspi1, &cmd, 1, 100);
HAL_SPI_Receive(&hspi1, id, 3, 100);
*MID = id[0];
*DID1 = id[1];
*DID2 = id[2];
MySPI_Stop();
}
页编程
页编程顺序为:写使能事务 → 页编程事务 → 等待空闲事务 。
WaitBusy 必须在页编程事务的CS拉高之后独立进行,不能穿插在页编程事务的Start和Stop之间。
页编程地址0x000FF100,倒数第三位是页地址,相当于对第1页进行编程。
cpp
/**
*
* @param Address 页编程的地址,24bit
* @param DataArr 写入数据的起始地址
* @param Count 写入数据的数目,最多可以写入256个B
*
*/
void W25Q64_PageProgram(uint32_t Address,uint8_t* DataArr,uint16_t Count) {
//写使能
W25Q64_WriteEnable();
MySPI_Start();
//发送指令
uint8_t cmd = 0x02;
HAL_SPI_Transmit(&hspi1, &cmd, 1, 100);
//发送24位地址
uint8_t address_buffer[3];
address_buffer[0] = (uint8_t)(Address >> 16) & 0xFF;
address_buffer[1] = (uint8_t)(Address >> 8) & 0xFF;
address_buffer[2] = Address & 0xFF;
HAL_SPI_Transmit(&hspi1, address_buffer, 3, 100);
//循环发送Count个数据
HAL_SPI_Transmit(&hspi1, DataArr, Count, 100);
//等待空闲(写入操作会置忙,读取不会,但空闲才能读取)
W25Q64_WaitBusy();
MySPI_Stop();
}
扇区擦除
按扇区擦除,对于24位地址0x000FF000,后两位是256个页,倒数第三位是页地址,第四第五位是扇区,所以相当于擦除第FF个扇区。
cpp
/**
*
* @param Address 要擦除的地址,按扇区擦除(4KB)
*/
void W25Q64_SectorEarse(uint32_t Address) {
//一次SPI事务中CS不能中途释放。
W25Q64_WriteEnable();
MySPI_Start();//擦除就是全置1
//发送指令
uint8_t sector_erase_4KB = 0x20;
HAL_SPI_Transmit(&hspi1, §or_erase_4KB, 1, 100);
//提供地址
uint8_t address_buffer[3];
address_buffer[0] = (uint8_t)(Address >> 16) & 0xFF;//高8位 右移16bit
address_buffer[1] = (uint8_t)(Address >> 8) & 0xFF;//中间8位
address_buffer[2] = Address & 0xFF;//低8位
//擦除
HAL_SPI_Transmit(&hspi1, address_buffer, 3, 100);
MySPI_Stop();
//等待擦除完毕/解除忙状态
W25Q64_WaitBusy();
}
读数据
cpp
/**
*
* @param Address 24位地址
* @param DataArr 存放读取的数据
* @param Count 数据个数
*
*/
void W25Q64_ReadData(uint32_t Address,uint8_t* DataArr,uint32_t Count) {
//等待忙状态取消
W25Q64_WaitBusy();
MySPI_Start();
//指令
uint8_t cmd = 0x03;
HAL_SPI_Transmit(&hspi1, &cmd, 1, 100);
//地址
uint8_t address_buffer[3];
address_buffer[0] = (uint8_t)(Address >> 16) & 0xFF;
address_buffer[1] = (uint8_t)(Address >> 8) & 0xFF;
address_buffer[2] = Address & 0xFF;
HAL_SPI_Transmit(&hspi1, address_buffer, 3, 100);
//接收数据
HAL_SPI_Receive(&hspi1, DataArr, Count, 100);
MySPI_Stop();
}