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拉低、拉高,中间不能插入别的事务。
写使能
/** * @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(); }等待空闲
/** * @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
/** * * @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页进行编程。
/** * * @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个扇区。
/** * * @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(); }读数据
/** * * @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(); }