G0001配置硬件SPI之读取外部flash
分享作者:F888
作者昵称:我爱小易
评测品牌:灵动微电子
评测型号:MM32G0001A1TC
发布时间:2025-10-14 11:05:51
1
前言
前面两篇帖子想必大家已经学会怎么配置串口了,今天我们来配置硬件''SPI
开源口碑分享内容

一、SPI原理
         SPI 通讯是有主从模式,所以对于主从两个设备来说,通信时钟(SCLK)必须要保持一致,所以引入时钟极性和时钟相位的概念。
所谓时钟极性和时钟相位所指的就是 SCLK 的特性,通过设置这两个值保证主从设备时钟的特性一致,这样才能保证 SPI 能够正常通信。
CPHA:时钟相位。表示 SCLK 的边沿,当 CPHA=0,表示第一个边沿,CPHA=1,表示第二个边沿,看不懂可以理解为CPHA=0就是上升沿(0到1的跳变),CPHA=1以此类推。(如果空闲时钟信号为高电平CPHA=0就是下降沿(1到0的跳变),CPHA=1以此类推)
CPOL:时钟极性。表示 SCLK 在空闲时段(IDLE)是是低电平。当 CPOL=0,idle 是低电平,CPOL=1,idle 是高电平,说白了就是高电平采样(0)还是低电平采样(1)。
所以就能够组成四种模式,有这个概念四种模式原理也很容易推理出来,这边举两个例子说明一下:
模式0(CPHA=0,CPOL=0):空闲时为低电平,上升沿(高电平)采样,低电平改变数据,这个模式可以说就是和I2C一样了(如果是用这个模式应该可以用I2C通讯)。

模式3(CPHA=1,CPOL=1):空闲时为高电平,上升沿(高电平)采样,低电平改变数据

二、硬件连接(W25Q80 Flash)

W25Q80 引脚连接至 G0001
VCC3.3V
GNDGND
CSPA15(软件控制)
CLKPA5
DO (MISO)PA6
DI (MOSI)PA7
WP/HOLD接 VCC(禁用写保护/挂起)

三、SPI 初始化详解

1. 使能时钟

RCC_AHBPeriphClockCmd(RCC_AHBENR_GPIOA, ENABLE);   // GPIOA
RCC_APB1PeriphClockCmd(RCC_APB1PERIPH_SPI1, ENABLE); // SPI1(MM32G0 中 SPI1 在 APB1)

2. 配置 GPIO 为复用功能

// SCK(PA5)、MOSI(PA7)、NSS(PA15) → 复用推挽输出
GPIO_Init_structure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_Init_structure.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_7 | GPIO_Pin_15;
GPIO_Init(GPIOA, &GPIO_Init_structure);

// MISO(PA6) → 上拉输入(或浮空输入)
GPIO_Init_structure.GPIO_Mode = GPIO_Mode_IPU; // 内部上拉
GPIO_Init_structure.GPIO_Pin = GPIO_Pin_6;
GPIO_Init(GPIOA, &GPIO_Init_structure);

// 配置复用功能为 AF0(SPI1)
GPIO_PinAFConfig(GPIOA, GPIO_PinSource5,  GPIO_AF_0);
GPIO_PinAFConfig(GPIOA, GPIO_PinSource6,  GPIO_AF_0);
GPIO_PinAFConfig(GPIOA, GPIO_PinSource7,  GPIO_AF_0);
GPIO_PinAFConfig(GPIOA, GPIO_PinSource15, GPIO_AF_0);

3. 配置 SPI 参数(模式 0)

CPOL=0, CPHA=0

SPI_InitTypeDef SPI_Init_structure;
SPI_Init_structure.SPI_Mode              = SPI_Mode_Master;
SPI_Init_structure.SPI_NSS               = SPI_NSS_Soft;     // 软件控制 CS
SPI_Init_structure.SPI_BaudRatePrescaler = SPI_BaudratePrescaler_2; // 最高速(系统时钟/2)
SPI_Init_structure.SPI_CPOL              = SPI_CPOL_Low;     // 时钟空闲低电平
SPI_Init_structure.SPI_CPHA              = SPI_CPHA_1Edge;   // 数据在第一个边沿采样
SPI_Init_structure.SPI_DataSize          = SPI_DataSize_8b;
SPI_Init_structure.SPI_FirstBit          = SPI_FirstBit_MSB;
SPI_Init(SPI1, &SPI_Init_structure);
SPI_Cmd(SPI1, ENABLE);

四、SPI 数据交换函数

SPI通讯的核心原理就是交换,我们可以通过上面的函数直接就可以完成数据的接收与发送,如果你需要接收数据就可以通过一个变量去接收对应的返回值(因为SPI通讯的核心就是交换,要完成接收就得给一个东西去交换,建议用0xFF或0x00这两个数去换)

uint8_t SPI1_Exchang(uint8_t cmd)
{
    while (SPI_GetFlagStatus(SPI1, SPI_FLAG_TXEPT) != SET); // 等待发送缓冲空
    SPI_SendData(SPI1, cmd);
    while (SPI_GetFlagStatus(SPI1, SPI_FLAG_RXAVL) != SET); // 等待接收数据可用
    return SPI_ReceiveData(SPI1);
}

此函数是与 W25Q 通信的核心!

五、W25Q Flash 的存储结构与基本操作单位

在使用 W25Q 系列 SPI Flash(如 W25Q64、W25Q128 等)时,必须清楚其物理存储结构操作限制,否则极易出现“写入失败”“数据异常”等问题。

W25Q Flash 的存储组织遵循典型的 “块(Block)→ 扇区(Sector)→ 页(Page)” 三级结构,且 擦除、写入、读取的操作单位各不相同

操作类型最小单位典型大小说明
读取(Read)字节(Byte)1 字节可从任意地址开始,连续读取任意长度(受限于地址空间)
写入(Program)页(Page)256 字节一次最多写入 256 字节,且只能将 1 写为 0,不能反向
擦除(Erase)扇区(Sector) / 块(Block)扇区:4KB
块:32KB / 64KB
擦除后所有位变为 1(即 0xFF),这是写入的前提

为什么必须“先擦除再写入”?

Flash 的物理特性决定了:

  • 出厂或擦除后:每个 bit 为 1(即字节值为 0xFF)
  • 写入操作:只能将 1 → 0不能将 0 → 1
  • 因此,若某字节已写为 0x55(二进制 01010101),你无法直接将其改为 0xAA10101010),因为部分 bit 需要从 0 变回 1 —— 这是不允许的!

六、主函数测试:写入 0~255,再读回验证


int main(void)
{
    uint8_t write_buf[256], read_buf[256];
    
    GPIO_init();   // LED 控制
    uart_init();   // 调试输出
    spi_init();    // SPI 初始化

    // 填充测试数据
    for (int i = 0; i < 256; i++) write_buf[i] = i;

    // 擦除扇区(地址 0)
    W25QX_Sector_Erase(0);

    // 写入一页(256 字节)
    W25Q_WriteData_Page(write_buf, 0, 256);

    // 读回验证
    W25Q_ReadData(read_buf, 0, 256);
    for (int i = 0; i < 256; i++) {
        printf("%d ", read_buf[i]); // 输出 0 1 2 ... 255
    }

    // 再次擦除后读取(应全为 0xFF)
    W25QX_Sector_Erase(0);
    W25Q_ReadData(read_buf, 0, 256);
    printf("\nAfter erase:\n");
    for (int i = 0; i < 256; i++) {
        printf("%d ", read_buf[i]); 
    }

    while (1);
}

七、结果(这一次视频有两个)

1、读写ID

2、读写数据





全部评论
暂无评论
0/144