SPI读取W25Q64硬件驱动(HAL库)

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, &sector_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();

}
相关推荐
迅猛龙办公室1 小时前
Python实现斐波那契数列
算法
淡海水1 小时前
02-01-原理篇-Mark-Sweep与变种算法
算法·unity·c#·游戏引擎·.net·gc
xingzhemengyou11 小时前
STM32对函数进行CRC校验
stm32·单片机·嵌入式硬件
Navigator_Z1 小时前
LeetCode //C - 1276. Number of Burgers with No Waste of Ingredients
c语言·算法·leetcode
FPGA小徐1 小时前
STM32引脚详解:PC13-TAMPER-RTC 多功能复用引脚完全剖析(踩坑+配置+布线)
单片机·嵌入式硬件
keyipatience1 小时前
跳表详细解析
开发语言·数据结构·c++·算法·跳表
做运维的阿瑞1 小时前
mysql数据库分组查询:GROUP BY 与 HAVING 的执行逻辑
数据库·sql·mysql
M78佐菲2 小时前
ARM学习笔记(9)
linux·arm开发·笔记·嵌入式硬件·学习
铅笔小新z2 小时前
【stm32】中断与异常
stm32·单片机·嵌入式硬件