本帖最后由 bingbing12138 于 2025-1-9 22:12 编辑
#技术资源# 非常感谢极海官方给我这样一个测评的机会,本次主要使用G32A1465实现软件SPI读写W25Q64功能,
首先我们需要了解一下什么是SPI:
一、SPI通信:
1.SPI(Serial Peripheral Interface)是由Motorola公司开发的一种通用数据总线
2.四根通信线:SCK(Serial Clock)、MOSI(Master Output Slave Input)、MISO(Master Input Slave Output)、SS(Slave Select)
3.同步,全双工
4.支持总线挂载多设备(一主多从)
二、SPI硬件电路:
1.所有SPI设备的SCK、MOSI、MISO分别连在一起
2.主机另外引出多条SS控制线,分别接到各从机的SS引脚
3.输出引脚配置为推挽输出,输入引脚配置为浮空或上拉输入





三、SPI数据传输示意图:


四、SPI时序基本单元:
起始条件:SS从高电平切换到低电平
终止条件:SS从低电平切换到高电平
交换一个字节(模式0)
CPOL=0:空闲状态时,SCK为低电平
CPHA=0:SCK第一个边沿移入数据,第二个边沿移出数据

交换一个字节(模式1)
CPOL=0:空闲状态时,SCK为低电平
CPHA=1:SCK第一个边沿移出数据,第二个边沿移入数据
交换一个字节(模式2)
CPOL=1:空闲状态时,SCK为高电平
CPHA=0:SCK第一个边沿移入数据,第二个边沿移出数据
交换一个字节(模式3)
CPOL=1:空闲状态时,SCK为高电平
CPHA=1:SCK第一个边沿移出数据,第二个边沿移入数据
五、W25Q64简介:
W25Qxx系列是一种低成本、小型化、使用简单的非易失性存储器,常应用于数据存储、字库存储、固件程序存储等场景
存储介质:Nor Flash(闪存)
时钟频率:80MHz / 160MHz (Dual SPI) / 320MHz (Quad SPI)
存储容量(24位地址):
W25Q40: 4Mbit / 512KByte
W25Q80: 8Mbit / 1MByte
W25Q16: 16Mbit / 2MByte
W25Q32: 32Mbit / 4MByte
W25Q64: 64Mbit / 8MByte
W25Q128: 128Mbit / 16MByte
W25Q256: 256Mbit / 32MByte
六、Flash操作注意事项:
写入操作时:
- 写入操作前,必须先进行写使能
- 每个数据位只能由1改写为0,不能由0改写为1
- 写入数据前必须先擦除,擦除后,所有数据位变为1
- 擦除必须按最小擦除单元进行
- 连续写入多字节时,最多写入一页的数据,超过页尾位置的数据,会回到页首覆盖写入
- 写入操作结束后,芯片进入忙状态,不响应新的读写操作
读取操作时:
直接调用读取时序,无需使能,无需额外操作,没有页的限制,读取操作结束后不会进入忙状态,但不能在忙状态时读取
七、代码及效果展示
主循环代码:#include "user_config.h"
#include "g32a1xxx_pins.h"
#include "board.h"
#include "w25q64.h"
#include "bsp_spi.h"
uint8_t write_array[5] = {0xAA, 0xBB, 0xCC, 0xDD, 0xEE};
uint8_t read_array[5] = {0};
uint8_t MID;
uint16_t DID;
int main(void)
{
/* Initialize clock */
CLOCK_SYS_Init(&g_clockConfig);
W25Q64_Init();
W25Q64_ReadID(&MID, &DID);
W25Q64_SectorErase(0x002000);//擦除指定扇区
W25Q64_PageProgram(0x002000, write_array, 5);//在指定扇区写
W25Q64_ReadData(0x002000, read_array, 5);//从指定扇区读
while (1)
{
;
}
}
软件SPI.c#include "bsp_spi.h"
//片选
void MySPI_W_SS(uint8_t BitValue)
{
PINS_WritePin(CS_GPIO_BASE, CS_PIN, BitValue);
}
//时钟
void MySPI_W_SCK(uint8_t BitValue)
{
PINS_WritePin(CLK_GPIO_BASE, CLK_PIN, BitValue);
}
//主机输出
void MySPI_W_MOSI(uint8_t BitValue)
{
PINS_WritePin(MOSI_GPIO_BASE, MOSI_PIN, BitValue);
}
//从机输入
uint8_t MySPI_R_MISO(void)
{
return (uint8_t)((PINS_ReadPinsInput(MISO_GPIO_BASE) >> MISO_PIN) & 0x01U);
// return GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_6);
}
void BSP_SPI_Init(void)
{
/* Enable Clock to Port D */
CLOCK_SYS_ConfigModuleClock(PMA_CLK, NULL);
CLOCK_SYS_ConfigModuleClock(PMB_CLK, NULL);
/* Set pin mode */
PINS_SetMuxModeSel(CS_PORT_BASE, CS_PIN, PM_MUX_AS_GPIO);
PINS_SetMuxModeSel(CLK_PORT_BASE, CLK_PIN, PM_MUX_AS_GPIO);
PINS_SetMuxModeSel(MOSI_PORT_BASE, MOSI_PIN, PM_MUX_AS_GPIO);
/* Set pin interrupt */
PINS_SetPinIntSel(CS_PORT_BASE, CS_PIN, PM_DMA_INT_DISABLED);
PINS_SetPinIntSel(CLK_PORT_BASE, CLK_PIN, PM_DMA_INT_DISABLED);
PINS_SetPinIntSel(MOSI_PORT_BASE, MOSI_PIN, PM_DMA_INT_DISABLED);
/* GPIO Initialization */
PINS_SetPins(CS_GPIO_BASE, 1U << CS_PIN);
PINS_SetPins(CLK_GPIO_BASE, 1U << CLK_PIN);
PINS_SetPins(MOSI_GPIO_BASE, 1U << MOSI_PIN);
/* Set pin as output */
PINS_SetPinDir(CS_GPIO_BASE, CS_PIN, 1U);
PINS_SetPinDir(CLK_GPIO_BASE, CLK_PIN, 1U);
PINS_SetPinDir(MOSI_GPIO_BASE, MOSI_PIN, 1U);
/* Set pin mode */
PINS_SetMuxModeSel(MISO_PORT_BASE, MISO_PIN, PM_MUX_AS_GPIO);
/* Set pin interrupt */
PINS_SetPinIntSel(MISO_PORT_BASE, MISO_PIN, PM_DMA_INT_DISABLED);
/* set pin input to enabled state */
PINS_SetPmInputDisable(MISO_GPIO_BASE, 0U<<MISO_PIN);
/* set pin Pull-up resistor */
PINS_SetPullSel(MISO_PORT_BASE, MISO_PIN,PM_INTERNAL_PULL_UP_ENABLED);
MySPI_W_SS(1);//高电平默认不选中从机
MySPI_W_SCK(0);//模式0 空闲状态为低电平
}
void BSP_SPI_Start(void)
{
MySPI_W_SS(0);
}
void BSP_SPI_Stop(void)
{
MySPI_W_SS(1);
}
uint8_t BSP_SPI_SwapByte(uint8_t ByteSend)
{
uint8_t i,ByteReceive = 0x00;
for (i = 0; i < 8; i++)
{
//使用掩码方式
MySPI_W_MOSI(ByteSend & (0x80 >> i));//数据移出
MySPI_W_SCK(1);//时钟变为高电平
if (MySPI_R_MISO() == 1)
{
ByteReceive |= (0x80 >> i);//数据移入
}
MySPI_W_SCK(0);//时钟变为低电平
}
return ByteReceive;
}
软件SPI.h
#ifndef __BSP_SPI_H
#define __BSP_SPI_H
#include "g32a1xxx_pins.h"
#include "user_config.h"
//CS
#define CS_PIN (0U)
#define CS_PORT_BASE PMB
#define CS_GPIO_BASE GPIOB
//CLK
#define CLK_PIN (1U)
#define CLK_PORT_BASE PMB
#define CLK_GPIO_BASE GPIOB
//MOSI
#define MOSI_PIN (0U)
#define MOSI_PORT_BASE PMA
#define MOSI_GPIO_BASE GPIOA
//MISO
#define MISO_PIN (1U)
#define MISO_PORT_BASE PMA
#define MISO_GPIO_BASE GPIOA
void BSP_SPI_Init(void);
void BSP_SPI_Start(void);
void BSP_SPI_Stop(void);
uint8_t BSP_SPI_SwapByte(uint8_t ByteSend);
#endif
W25Q64.c
#include "w25q64.h"
#include "bsp_spi.h"
void W25Q64_Init(void)
{
BSP_SPI_Init();
}
void W25Q64_ReadID(uint8_t *MID, uint16_t *DID)
{
BSP_SPI_Start();
BSP_SPI_SwapByte(W25Q64_JEDEC_ID);
*MID = BSP_SPI_SwapByte(W25Q64_DUMMY_BYTE);
*DID = BSP_SPI_SwapByte(W25Q64_DUMMY_BYTE);
*DID <<= 8;
*DID |= BSP_SPI_SwapByte(W25Q64_DUMMY_BYTE);
BSP_SPI_Stop();
}
//写使能
void W25Q64_WriteEnable(void)
{
BSP_SPI_Start();//片选
BSP_SPI_SwapByte(W25Q64_WRITE_ENABLE);
BSP_SPI_Stop();
}
//等待状态寄存器1 为非忙状态
void W25Q64_WaitBusy(void)
{
uint32_t TimeOut;
BSP_SPI_Start();//片选
BSP_SPI_SwapByte(W25Q64_READ_STATUS_REGISTER_1);
// while((BSP_SPI_SwapByte(W25Q64_DUMMY_BYTE) & 0x01) == 0x01);
TimeOut = 100000;
while(BSP_SPI_SwapByte(W25Q64_DUMMY_BYTE) & 0x01)
{
TimeOut--;
if (TimeOut == 0)
{
break;
}
}
BSP_SPI_Stop();
}
//开始写之前 先打开写使能,之后执行数据写入
void W25Q64_PageProgram(uint32_t Address, uint8_t *DataArry, uint16_t Count)
{
uint16_t i;
W25Q64_WriteEnable();//写使能
BSP_SPI_Start();//片选
BSP_SPI_SwapByte(W25Q64_PAGE_PROGRAM);
BSP_SPI_SwapByte(Address >> 16);
BSP_SPI_SwapByte(Address >> 8);
BSP_SPI_SwapByte(Address);
for (i = 0; i < Count; i++)
{
BSP_SPI_SwapByte(DataArry[i]);
}
BSP_SPI_Stop();
W25Q64_WaitBusy();//等待忙完
}
//擦除指定扇区 开启写使能 之后开始擦除扇区 最后等待状态寄存器为非忙状态
void W25Q64_SectorErase(uint32_t Address)
{
W25Q64_WriteEnable();//写使能
BSP_SPI_Start();//片选
BSP_SPI_SwapByte(W25Q64_SECTOR_ERASE_4KB);
BSP_SPI_SwapByte(Address >> 16);
BSP_SPI_SwapByte(Address >> 8);
BSP_SPI_SwapByte(Address);
BSP_SPI_Stop();
W25Q64_WaitBusy();//等待忙完
}
//从指定地址读取数据
void W25Q64_ReadData(uint32_t Address, uint8_t *DataArry, uint16_t Count)
{
uint16_t i;
BSP_SPI_Start();//片选
BSP_SPI_SwapByte(W25Q64_READ_DATA);
BSP_SPI_SwapByte(Address >> 16);
BSP_SPI_SwapByte(Address >> 8);
BSP_SPI_SwapByte(Address);
for (i = 0; i < Count; i++)
{
DataArry[i] = BSP_SPI_SwapByte(W25Q64_DUMMY_BYTE);
}
BSP_SPI_Stop();
}
W25Q64.h#ifndef __W25Q64_H
#define __W25Q64_H
#include "g32a1xxx_pins.h"
#define W25Q64_WRITE_ENABLE 0x06
#define W25Q64_WRITE_DISABLE 0x04
#define W25Q64_READ_STATUS_REGISTER_1 0x05
#define W25Q64_READ_STATUS_REGISTER_2 0x35
#define W25Q64_WRITE_STATUS_REGISTER 0x01
#define W25Q64_PAGE_PROGRAM 0x02
#define W25Q64_QUAD_PAGE_PROGRAM 0x32
#define W25Q64_BLOCK_ERASE_64KB 0xD8
#define W25Q64_BLOCK_ERASE_32KB 0x52
#define W25Q64_SECTOR_ERASE_4KB 0x20
#define W25Q64_CHIP_ERASE 0xC7
#define W25Q64_ERASE_SUSPEND 0x75
#define W25Q64_ERASE_RESUME 0x7A
#define W25Q64_POWER_DOWN 0xB9
#define W25Q64_HIGH_PERFORMANCE_MODE 0xA3
#define W25Q64_CONTINUOUS_READ_MODE_RESET 0xFF
#define W25Q64_RELEASE_POWER_DOWN_HPM_DEVICE_ID 0xAB
#define W25Q64_MANUFACTURER_DEVICE_ID 0x90
#define W25Q64_READ_UNIQUE_ID 0x4B
#define W25Q64_JEDEC_ID 0x9F
#define W25Q64_READ_DATA 0x03
#define W25Q64_FAST_READ 0x0B
#define W25Q64_FAST_READ_DUAL_OUTPUT 0x3B
#define W25Q64_FAST_READ_DUAL_IO 0xBB
#define W25Q64_FAST_READ_QUAD_OUTPUT 0x6B
#define W25Q64_FAST_READ_QUAD_IO 0xEB
#define W25Q64_OCTAL_WORD_READ_QUAD_IO 0xE3
#define W25Q64_DUMMY_BYTE 0xFF
void W25Q64_Init(void);//初始化函数
void W25Q64_ReadID(uint8_t *MID, uint16_t *DID);//读取设备ID号
void W25Q64_PageProgram(uint32_t Address, uint8_t *DataArry, uint16_t Count);//在指定扇区写
void W25Q64_SectorErase(uint32_t Address);//擦除指定扇区
void W25Q64_ReadData(uint32_t Address, uint8_t *DataArry, uint16_t Count);//从指定扇区读
#endif
实物展示:
效果展示:
|