stm32(SPI读写W25Q18)

SPI 是什么?

SPI是串行外设接口(Serial Peripheral Interface)的缩写,是一种高速的,全双工,同步的通信总 线,并且在芯片的管脚上只占用四根线,节约了芯片的管脚,同时为PCB的布局上节省空间,提 供方便,正是出于这种简单易用的特性,越来越多的芯片集成了这种通信协议,比如 AT91RM9200 。

SPI 包含 4 条总线,SPI 总线包含 4 条总线,分别为SS、SCK、MOSI、MISO。它们的作用介绍如 下 :

  • (1) MISO -- Master Input Slave Output,主设备数据输入,从设备数据输出
  • (2) MOSI -- Master Output Slave Input,主设备数据输出,从设备数据输入
  • (3) SCK -- Serial Clock,时钟信号,由主 设备产生
  • (4) CS -- Chip Select,片选信号,由主设备控制

SPI 工作原理

SPI 工作模式

时钟极性(CPOL): 没有数据传输时时钟线的空闲状态电平

0:SCK在空闲状态保持低电平 1:SCK在空闲状态保持高 电平

时钟相位(CPHA): 时钟线在第几个时钟边沿采样数据

0:SCK的第一(奇数)边沿进行数据位采样,数据在第一个时 钟边沿被锁存

1:SCK的第二(偶数)边沿进行数据位采样,数据在第二个时钟边沿被锁存

模式 0 和模式 3 最常用。

模式 0 时序图:

模式 3 时序图:

W25Q128 介绍

什么是 W25Q128 ?

W25Q128 是华邦公司推出的一款 SPI 接口的 NOR Flash 芯片,其存储空间为 128 Mbit,相当于 16M 字节。 Flash 是常用的用于储存数据的半导体器件,它具有容量大,可重复擦写、按"扇区/块"擦除、掉 电后数据可继续保存的特性。

Flash 是有一个物理特性:只能写 0 ,不能写 1 ,写 1 靠擦除。

W25Q128 存储架构

一般按扇区(4k)进行擦除。

可以按 章 -- 节 -- 页 -- 字 进行理解。

写使能 (06H)

执行页写,扇区擦除,块擦除,片擦除,写状态寄存器等指令前,需要写使能。

拉低CS片选 → 发送06H → 拉高CS片选

读状态寄存器(05H)

拉低CS片选 → 发送05H→ 返回SR1的值 → 拉高CS片选

读时序(03H)

拉低CS片选 → 发送03H→ 发送24位地址 → 读取数据(1~n) → 拉高CS片选

页写时序 (02H)

页写命令最多可以向FLASH传输256个字节的数据。

拉低CS片选 → 发送02H→ 发送24位地址 → 发送数据(1~n) → 拉高CS片选

扇区擦除时序(20H)

写入数据前,检查内存空间是否全部都是 0XFF ,不满足需擦除。 拉

低CS片选 → 发送20H→ 发送24位地址 → 拉高CS片选

W25Q128 状态寄存器 W25Q128 一共有 3 个状态寄存器,它们的作用是跟踪芯片的状态。 其中,状态寄存器 1 较为常用。

W25Q128 常见操作流程

以下流程省略了拉低/拉高片选信号CS

写操作

实验:使用 SPI 通讯读写 W25Q128 模块

硬件接线 :VCC -- 3.3V CS -- PA4 CLK -- PA5 DO -- PA6 DI -- PA7

cubeMX配置

w25q128_write_nocheck流程图

代码实现

main.c

复制代码
int main(void)
{
  /* USER CODE BEGIN 1 */
	uint8_t datatemp[TEXT_SIZE];
  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_SPI1_Init();
  MX_USART1_UART_Init();
  /* USER CODE BEGIN 2 */
	w25q128_init();
	
	/* 写入测试数据 */
	sprintf((char *)datatemp, "liangxu shuai");
	w25q128_write(datatemp, FLASH_WriteAddress, TEXT_SIZE);
	printf("数据写入完成!\r\n");
	
	/* 读出测试数据 */
	memset(datatemp, 0, TEXT_SIZE);
	w25q128_read(datatemp, FLASH_ReadAddress, TEXT_SIZE);
	printf("读出数据:%s\r\n", datatemp);
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

W25Q128.c

复制代码
#include "w25q128.h"
#include "spi.h"
#include "stdio.h"

/**
 * @brief       初始化W25Q128
 * @param       无
 * @retval      无
 */
void w25q128_init(void)
{
		uint16_t flash_type;
    spi1_read_write_byte(0xFF); /* 清除DR的作用 */
    W25Q128_CS(1);
		flash_type = w25q128_read_id();   /* 读取FLASH ID. */
		if (flash_type == W25Q128)
			printf("检测到W25Q128芯片\r\n");
}

/**
 * @brief       等待空闲
 * @param       无
 * @retval      无
 */
static void w25q128_wait_busy(void)
{
    while ((w25q128_rd_sr1() & 0x01) == 0x01);   /* 等待BUSY位清空 */
}

/**
 * @brief       读取W25Q128的状态寄存器1的值
 * @param       无
 * @retval      状态寄存器值
 */
uint8_t w25q128_rd_sr1(void)
{
    uint8_t rec_data = 0;
    
    W25Q128_CS(0);
    spi1_read_write_byte(FLASH_ReadStatusReg1);     /* 读状态寄存器1 */
    rec_data = spi1_read_write_byte(0xFF);
    W25Q128_CS(1);
    
    return rec_data;
}

/**
 * @brief       W25Q128写使能
 *   @note      将S1寄存器的WEL置位
 * @param       无
 * @retval      无
 */
void w25q128_write_enable(void)
{
    W25Q128_CS(0);
    spi1_read_write_byte(FLASH_WriteEnable);   /* 发送写使能 */
    W25Q128_CS(1);
}

/**
 * @brief       W25Q128发送地址
 * @param       address : 要发送的地址
 * @retval      无
 */
static void w25q128_send_address(uint32_t address)
{
    spi1_read_write_byte((uint8_t)((address)>>16));     /* 发送 bit23 ~ bit16 地址 */
    spi1_read_write_byte((uint8_t)((address)>>8));      /* 发送 bit15 ~ bit8  地址 */
    spi1_read_write_byte((uint8_t)address);             /* 发送 bit7  ~ bit0  地址 */
}

/**
 * @brief       擦除整个芯片
 *   @note      等待时间超长...
 * @param       无
 * @retval      无
 */
void w25q128_erase_chip(void)
{
    w25q128_write_enable();    /* 写使能 */
    w25q128_wait_busy();       /* 等待空闲 */
    W25Q128_CS(0);
    spi1_read_write_byte(FLASH_ChipErase);  /* 发送读寄存器命令 */ 
    W25Q128_CS(1);
    w25q128_wait_busy();       /* 等待芯片擦除结束 */
}

/**
 * @brief       擦除一个扇区
 *   @note      注意,这里是扇区地址,不是字节地址!!
 *              擦除一个扇区的最少时间:150ms
 *
 * @param       saddr : 扇区地址 根据实际容量设置
 * @retval      无
 */
void w25q128_erase_sector(uint32_t saddr)
{
    //printf("fe:%x\r\n", saddr);   /* 监视falsh擦除情况,测试用 */
    saddr *= 4096;
    w25q128_write_enable();        /* 写使能 */
    w25q128_wait_busy();           /* 等待空闲 */

    W25Q128_CS(0);
    spi1_read_write_byte(FLASH_SectorErase);    /* 发送写页命令 */
    w25q128_send_address(saddr);   /* 发送地址 */
    W25Q128_CS(1);
    w25q128_wait_busy();           /* 等待扇区擦除完成 */
}

/**
 * @brief       读取芯片ID
 * @param       无
 * @retval      FLASH芯片ID
 *   @note      芯片ID列表见: w25q128.h, 芯片列表部分
 */
uint16_t w25q128_read_id(void)
{
    uint16_t deviceid;

    W25Q128_CS(0);
    spi1_read_write_byte(FLASH_ManufactDeviceID);   /* 发送读 ID 命令 */
    spi1_read_write_byte(0);    /* 写入一个字节 */
    spi1_read_write_byte(0);
    spi1_read_write_byte(0);
    deviceid = spi1_read_write_byte(0xFF) << 8;     /* 读取高8位字节 */
    deviceid |= spi1_read_write_byte(0xFF);         /* 读取低8位字节 */
    W25Q128_CS(1);

    return deviceid;
}

/**
 * @brief       读取SPI FLASH
 *   @note      在指定地址开始读取指定长度的数据
 * @param       pbuf    : 数据存储区
 * @param       addr    : 开始读取的地址(最大32bit)
 * @param       datalen : 要读取的字节数(最大65535)
 * @retval      无
 */
void w25q128_read(uint8_t *pbuf, uint32_t addr, uint16_t datalen)
{
    uint16_t i;

    W25Q128_CS(0);
    spi1_read_write_byte(FLASH_ReadData);       /* 发送读取命令 */
    w25q128_send_address(addr);                /* 发送地址 */
    
    for(i=0;i<datalen;i++)
    {
        pbuf[i] = spi1_read_write_byte(0XFF);   /* 循环读取 */
    }
    
    W25Q128_CS(1);
}

/**
 * @brief       SPI在一页(0~65535)内写入少于256个字节的数据
 *   @note      在指定地址开始写入最大256字节的数据
 * @param       pbuf    : 数据存储区
 * @param       addr    : 开始写入的地址(最大32bit)
 * @param       datalen : 要写入的字节数(最大256),该数不应该超过该页的剩余字节数!!!
 * @retval      无
 */
static void w25q128_write_page(uint8_t *pbuf, uint32_t addr, uint16_t datalen)
{
    uint16_t i;

    w25q128_write_enable();    /* 写使能 */

    W25Q128_CS(0);
    spi1_read_write_byte(FLASH_PageProgram);    /* 发送写页命令 */
    w25q128_send_address(addr);                /* 发送地址 */

    for(i=0;i<datalen;i++)
    {
        spi1_read_write_byte(pbuf[i]);          /* 循环写入 */
    }
    
    W25Q128_CS(1);
    w25q128_wait_busy();       /* 等待写入结束 */
}

/**
 * @brief       无检验写SPI FLASH
 *   @note      必须确保所写的地址范围内的数据全部为0XFF,否则在非0XFF处写入的数据将失败!
 *              具有自动换页功能
 *              在指定地址开始写入指定长度的数据,但是要确保地址不越界!
 *
 * @param       pbuf    : 数据存储区
 * @param       addr    : 开始写入的地址(最大32bit)
 * @param       datalen : 要写入的字节数(最大65535)
 * @retval      无
 */
static void w25q128_write_nocheck(uint8_t *pbuf, uint32_t addr, uint16_t datalen)
{
    uint16_t pageremain;
    pageremain = 256 - addr % 256;  /* 单页剩余的字节数 */

    if (datalen <= pageremain)      /* 不大于256个字节 */
    {
        pageremain = datalen;
    }

    while (1)
    {
        /* 当写入字节比页内剩余地址还少的时候, 一次性写完
         * 当写入直接比页内剩余地址还多的时候, 先写完整个页内剩余地址, 然后根据剩余长度进行不同处理
         */
        w25q128_write_page(pbuf, addr, pageremain);

        if (datalen == pageremain)   /* 写入结束了 */
        {
            break;
        }
        else     /* datalen > pageremain */
        {
            pbuf += pageremain;         /* pbuf指针地址偏移,前面已经写了pageremain字节 */
            addr += pageremain;         /* 写地址偏移,前面已经写了pageremain字节 */
            datalen -= pageremain;      /* 写入总长度减去已经写入了的字节数 */

            if (datalen > 256)          /* 剩余数据还大于一页,可以一次写一页 */
            {
                pageremain = 256;       /* 一次可以写入256个字节 */
            }
            else     /* 剩余数据小于一页,可以一次写完 */
            {
                pageremain = datalen;   /* 不够256个字节了 */
            }
        }
    }
}

/**
 * @brief       写SPI FLASH
 *   @note      在指定地址开始写入指定长度的数据 , 该函数带擦除操作!
 *              SPI FLASH 一般是: 256个字节为一个Page, 4Kbytes为一个Sector, 16个扇区为1个Block
 *              擦除的最小单位为Sector.
 *
 * @param       pbuf    : 数据存储区
 * @param       addr    : 开始写入的地址(最大32bit)
 * @param       datalen : 要写入的字节数(最大65535)
 * @retval      无
 */
uint8_t g_w25q128_buf[4096];   /* 扇区缓存 */

void w25q128_write(uint8_t *pbuf, uint32_t addr, uint16_t datalen)
{
    uint32_t secpos;
    uint16_t secoff;
    uint16_t secremain;
    uint16_t i;
    uint8_t *w25q128_buf;

    w25q128_buf = g_w25q128_buf;
    secpos = addr / 4096;       /* 扇区地址 */
    secoff = addr % 4096;       /* 在扇区内的偏移 */
    secremain = 4096 - secoff;  /* 扇区剩余空间大小 */

    //printf("ad:%X,nb:%X\r\n", addr, datalen); /* 测试用 */
    if (datalen <= secremain)
    {
        secremain = datalen;    /* 不大于4096个字节 */
    }

    while (1)
    {
        w25q128_read(w25q128_buf, secpos * 4096, 4096);   /* 读出整个扇区的内容 */

        for (i = 0; i < secremain; i++)   /* 校验数据 */
        {
            if (w25q128_buf[secoff + i] != 0XFF)
            {
                break;      /* 需要擦除, 直接退出for循环 */
            }
        }

        if (i < secremain)   /* 需要擦除 */
        {
            w25q128_erase_sector(secpos);  /* 擦除这个扇区 */

            for (i = 0; i < secremain; i++)   /* 复制 */
            {
                w25q128_buf[i + secoff] = pbuf[i];
            }

            w25q128_write_nocheck(w25q128_buf, secpos * 4096, 4096);  /* 写入整个扇区 */
        }
        else        /* 写已经擦除了的,直接写入扇区剩余区间. */
        {
            w25q128_write_nocheck(pbuf, addr, secremain);  /* 直接写扇区 */
        }

        if (datalen == secremain)
        {
            break;  /* 写入结束了 */
        }
        else        /* 写入未结束 */
        {
            secpos++;               /* 扇区地址增1 */
            secoff = 0;             /* 偏移位置为0 */

            pbuf += secremain;      /* 指针偏移 */
            addr += secremain;      /* 写地址偏移 */
            datalen -= secremain;   /* 字节数递减 */

            if (datalen > 4096)
            {
                secremain = 4096;   /* 下一个扇区还是写不完 */
            }
            else
            {
                secremain = datalen;/* 下一个扇区可以写完了 */
            }
        }
    }
}
相关推荐
森利威尔电子-44 分钟前
森利威尔SL3180 替换LTC3639 150V降压恒压芯片 3A峰值电流能力转换效率高 静态电流低
单片机·嵌入式硬件·集成电路·芯片·电源芯片
qq_401700411 小时前
合宙4G模块Air724UG
嵌入式硬件
yugi9878382 小时前
基于51单片机的篮球计分器设计
单片机·嵌入式硬件·51单片机
编程之升级打怪2 小时前
什么是PWM
嵌入式硬件
zmj3203243 小时前
单片机内存在C 语言编译后的 “逻辑分区”
c语言·单片机·内存分区
魈学习ing4 小时前
IO口无法外部上拉到3.3V以上,或被钳位到3.8V左右
stm32
小柯博客4 小时前
STM32MP2 Secure Boot实战
stm32·单片机·嵌入式硬件
Ww.xh4 小时前
STM32调用AI接口完整教程
stm32·单片机·嵌入式硬件
ZYNQRFSOC4 小时前
基于安路PH2A系列FPGA的JESD204B接口测试
嵌入式硬件·fpga开发
LCG元5 小时前
STM32实战:基于STM32F407的LWIP以太网通信(TCP Server)
stm32·嵌入式硬件·tcp/ip