-
2021-04-04 15:45:52
W25Q64是华邦公司推出的大容量SPI FLASH产品,其容量为64Mb(8MB),应用较为广泛。
W25Q系列的器件在灵活性和性能方面远远超过普通的串行闪存器件。W25Q64将8M字节的容量分为128个块,每个块大小为64K字节,每个块又分为16个扇区,每个扇区4K个字节。W25Q64的最小擦除单位为一个扇区,也就是每次必须擦除4K个字节。所以,这需要给W25Q64开辟一个至少4K的缓存区,这样必须要求芯片有4K以上的SRAM才能有很好的操作。
W25Q64的擦写周期多达10W次,可将数据保存达20年之久,支持2.7~3.6V的电压,支持标准的SPI,还支持双输出/四输出的SPI,最大SPI时钟可达80Mhz。一、W25Q64硬件设计
F_CS 、SPI_MISO、SPI_SCK和SPI_MOSI四个引脚与单片机的IO口连接,可以是单片机的硬件SPI接口,也可以通过普通IO口来模拟SPI协议。二、W25Q64软件协议
W25Q64 Flash中文数据手册:
微信公众号 硬件之家 后台回复 W25Q64 获取下载链接
硬件之家,技术向前。
手册共18页,下面为手册第一页的预览:
硬件之家,技术向前。
更多相关内容 -
IO模拟SPI读取W25Q64_SPI 原子野火开发板通过.zip
2020-07-10 15:45:31该程序为用IO口模拟SPI读取W25Q64的程序, 在正点原子战舰开发板,野火霸道开发板上运行通过,可以读写W25Q64 里面的数据,只需要修改.h里的管脚定义,就可以运行率,程序简单明了,备注非常详细 -
W25Q64_128书签中文版.pdf
2020-01-25 22:00:53W25Q64 (64M-bit),W25Q16(16M-bit)和 W25Q32(32M-bit)是为系统提供一个最小的空间、引脚 和功耗的存储器解决方案的串行 Flash 存储器。25Q 系列比普通的串行 Flash 存储器更灵活,性能 更优越。基于双倍/四倍的 ... -
w25q64jv详细介绍
2017-09-06 16:16:00w25q64jv spi 详细介绍,W25Q64支持标准的SPI,双输出SPI和四输出SPI操作。标准的SPI指令利用单向的数据输入引脚在串行时钟输入上升沿串行地向器件写入指令、地址或数据。 -
W25Q64调试
2021-11-09 15:56:57W25Q64将8M字节的容量分为128个块,每个块大小为64K字节,每个块又分为16个扇区,每个扇区4K个字节。 引脚介绍 串行数据输入、输出和 IOs(DI、DO 和 IO0、IO1、IO2、IO3) W25Q64、W25Q16 和 W25Q32 支持标准 SPI...简介
W25Q系列的器件在灵活性和性能方面远远超过普通的串行闪存器件。W25Q64将8M字节的容量分为128个块,每个块大小为64K字节,每个块又分为16个扇区,每个扇区4K个字节。
引脚介绍
串行数据输入、输出和 IOs(DI、DO 和 IO0、IO1、IO2、IO3)
W25Q64、W25Q16 和 W25Q32 支持标准 SPI、双倍 SPI 和四倍 SPI。标准的 SPI 传输用单向的 DI(输入)引脚连续的写命令、地址或者数据在串行时钟(CLK)的上升沿时写入到芯片内。
写保护(/WP)
写保护引脚(/WP)用来保护状态寄存器。和状态寄存器的块保护位(SEC、TB、BP2、BP1 和BP0)和状态寄存器保护位(SRP)对存储器进行一部分或者全部的硬件保护。/WP 引脚低电平有效。当状态寄存器 2 的 QE 位被置位了,/WP 引脚(硬件写保护)的功能不可用。
保持端(/HOLD)
当/HOLD 引脚是有效时,允许芯片暂停工作。在/CS 为低电平时,当/HOLD 变为低电平,DO 引脚将变为高阻态,在 DI 和 CLK 引脚上的信号将无效。当/HOLD 变为高电平,芯片恢复工作。/HOLD 功能用在当有多个设备共享同一 SPI 总线时。/HOLD 引脚低电平有效。当状态寄存器 2 的 QE 位被置位了,/ HOLD 引脚的功能不可用。
串行时钟(CLK)
串行时钟输入引脚为串行输入和输出操作提供时序。(见 SPI 操作)。
设备数据传输是从高位开始,数据传输的格式为 8bit,数据采样从第二个时间边沿开始,空闲状态时,时钟线 clk 为高电平。W25Q64操作原理
通过SPI接口,用标准的SPI协议发送相应指令给flash,然后flash根据命令进行各种相关操作。
① 写使能:06H
② 读状态寄存器指令:05H
③ 写状态寄存器指令:01H
④ 读数据:03H
⑤ 页写:02H
⑥ 扇区擦除指令:20H
⑦ 块擦除指令:D8H
⑧ 芯片擦除指令:07H
⑨ 掉电指令:B9H
⑩ 读ID指令:90H
极性CPOL和相位CPHA
(1) CKPOL (Clock Polarity) = CPOL = POL = Polarity = (时钟)极性
(2) CKPHA (Clock Phase) = CPHA = PHA = Phase = (时钟)相位CPOL和CPHA,分别都可以是0或时1,对应的四种组合就是:
SPI的CPOL,表示当SCLK空闲idle的时候,其电平的值是低电平0还是高电平1:CPOL=0,时钟空闲idle时候的电平是低电平,所以当SCLK有效的时候,就是高电平,就是所谓的active-high。
CPOL=1,时钟空闲idle时候的电平是高电平,所以当SCLK有效的时候,就是低电平,就是所谓的active-low。
CPHA=0,表示第一个边沿:
对于CPOL=0,idle时候的是低电平,第一个边沿就是从低变到高,所以是上升沿;
对于CPOL=1,idle时候的是高电平,第一个边沿就是从高变到低,所以是下降沿;CPHA=1,表示第二个边沿:
对于CPOL=0,idle时候的是低电平,第二个边沿就是从高变到低,所以是下降沿;
对于CPOL=1,idle时候的是高电平,第一个边沿就是从低变到高,所以是上升沿;
示例代码
DTSI
w25q64: w25q64@00 { status = "okay"; compatible = "rockchip,w25q64"; reg = <0x00>; spi-max-frequency = <24000000>; wp-gpio = <&gpio0 12 GPIO_ACTIVE_HIGH>; /*GPIO0_B4*/ // spi-cs-high; spi-cpha; /*SPI mode: CPHA = 1*/ spi-cpol; /*SPI mode: CPOL = 1*/ };
源码
w25q64.c
#include <linux/init.h> #include <linux/module.h> #include <linux/ioctl.h> #include <linux/fs.h> #include <linux/device.h> #include <linux/err.h> #include <linux/list.h> #include <linux/errno.h> #include <linux/mutex.h> #include <linux/slab.h> #include <linux/compat.h> #include <linux/of.h> #include <linux/of_device.h> #include <linux/kernel.h> #include <linux/spi/spi.h> #include <linux/spi/spidev.h> #include <linux/delay.h> #include <linux/uaccess.h> #include <linux/of_gpio.h> #include "w25q64.h" /* * This supports access to SPI devices using normal userspace I/O calls. * Note that while traditional UNIX/POSIX I/O semantics are half duplex, * and often mask message boundaries, full SPI support requires full duplex * transfers. There are several kinds of internal message boundaries to * handle chipselect management and other protocol options. * * SPI has a character major number assigned. We allocate minor numbers * dynamically using a bitmask. You must use hotplug tools, such as udev * (or mdev with busybox) to create and destroy the /dev/spidevB.C device * nodes, since there is no fixed association of minor numbers with any * particular SPI bus or device. */ #define SPIDEV_MAJOR 155 /* assigned */ #define N_SPI_MINORS 32 /* ... up to 256 */ /*W25Q64 CMD*/ #define WRITE_ENABLE 0x06 #define PAGE_PROGRAM 0x02 #define READ_DATA 0x03 #define WRITE_STATUS_REG 0x01 #define READ_STATUS_REG 0x05 #define CHIP_ERASE 0xc7 #define SECTOR_ERASE 0x20 #define BLOCK_32KB_ERASE 0x52 #define BLOCK_64KB_ERASE 0xD8 #define READ_DEVICE_ID 0x90 #define READ_UID 0x9F static DECLARE_BITMAP(minors, N_SPI_MINORS); /* Bit masks for spi_device.mode management. Note that incorrect * settings for some settings can cause *lots* of trouble for other * devices on a shared bus: * * - CS_HIGH ... this device will be active when it shouldn't be * - 3WIRE ... when active, it won't behave as it should * - NO_CS ... there will be no explicit message boundaries; this * is completely incompatible with the shared bus model * - READY ... transfers may proceed when they shouldn't. * * REVISIT should changing those flags be privileged? */ #define SPI_MODE_MASK (SPI_CPHA | SPI_CPOL | SPI_CS_HIGH \ | SPI_LSB_FIRST | SPI_3WIRE | SPI_LOOP \ | SPI_NO_CS | SPI_READY | SPI_TX_DUAL \ | SPI_TX_QUAD | SPI_RX_DUAL | SPI_RX_QUAD) struct spidev_data { dev_t devt; spinlock_t spi_lock; struct spi_device *spi; struct list_head device_entry; /* TX/RX buffers are NULL unless this device is open (users > 0) */ struct mutex buf_lock; unsigned users; u8 *tx_buffer; u8 *rx_buffer; u32 speed_hz; unsigned int cur_addr; unsigned wp_gpio; }; static LIST_HEAD(device_list); static DEFINE_MUTEX(device_list_lock); static unsigned bufsiz = 4096; module_param(bufsiz, uint, S_IRUGO); MODULE_PARM_DESC(bufsiz, "data bytes in biggest supported SPI message"); /*-------------------------------------------------------------------------*/ static char spi_w25x_status(struct spi_device *spi) { int status; char tbuf[]={READ_STATUS_REG}; char rbuf[1] = {1}; struct spi_transfer t = { .tx_buf = tbuf, .len = ARRAY_SIZE(tbuf), }; struct spi_transfer r = { .rx_buf = rbuf, .len = ARRAY_SIZE(rbuf), }; struct spi_message m; spi_message_init(&m); spi_message_add_tail(&t, &m); spi_message_add_tail(&r, &m); status = spi_sync(spi, &m); return rbuf[0]; } //modified by xzq degain for retry 5 times @20211105 static int spi_w25x_wait_ready(struct spi_device *spi ) { char retval = 1; int retry = 5; dev_dbg(&spi->dev, "wait ready..."); do { retval = spi_w25x_status(spi); retval &= 0xff; retval &= 1; retry --; mdelay(5); }while((retval != 0) && (retry != 0)); if(retval) dev_err(&spi->dev,"no ready\n"); else dev_dbg(&spi->dev, "OK\n"); return 0; } //modified by xzq end static int spi_w25x_write_enable(struct spi_device *spi) { int status; char cmd_buf[1] = {WRITE_ENABLE}; struct spi_transfer cmd = { .tx_buf = cmd_buf, .len = ARRAY_SIZE(cmd_buf), }; struct spi_message m; spi_message_init(&m); spi_message_add_tail(&cmd, &m); status = spi_sync(spi, &m); dev_dbg(&spi->dev, "write enable\n"); return status; } static int spi_read_w25x_id_0(struct spi_device *spi) { int status; char tbuf[]={READ_UID}; char rbuf[5]; struct spi_transfer t = { .tx_buf = tbuf, .len = ARRAY_SIZE(tbuf), }; struct spi_transfer r = { .rx_buf = rbuf, .len = ARRAY_SIZE(rbuf), }; struct spi_message m; spi_message_init(&m); spi_message_add_tail(&t, &m); spi_message_add_tail(&r, &m); status = spi_sync(spi, &m); dev_err(&spi->dev, "ID = %02x %02x %02x %02x %02x\n", rbuf[0], rbuf[1], rbuf[2], rbuf[3], rbuf[4]); return status; } static int spi_w25x_sector_erase(struct spidev_data *spidev, unsigned long size) { int status; char cmd[4] = {SECTOR_ERASE}; struct spi_device *spi = spidev->spi; struct spi_transfer t = { .tx_buf = cmd, .len = ARRAY_SIZE(cmd), }; struct spi_message m; unsigned int flash_addr = spidev->cur_addr; int count = (int)size; for ( ; count > 0; count -= W25Q64_SECTOR) { cmd[1] = (unsigned char)((flash_addr & 0xff0000) >> 16); cmd[2] = (unsigned char)((flash_addr & 0xff00) >> 8); cmd[3] = (unsigned char)(flash_addr & 0xff); spi_w25x_write_enable(spi); spi_message_init(&m); spi_message_add_tail(&t, &m); status = spi_sync(spi, &m); spi_w25x_wait_ready(spi); dev_dbg(&spi->dev,"start addr: %x, sector erase OK\n", flash_addr); flash_addr += W25Q64_SECTOR; } return status; } static int spi_w25x_32kb_block_erase(struct spidev_data *spidev) { int status; char cmd[4] = {BLOCK_32KB_ERASE}; struct spi_device *spi = spidev->spi; struct spi_transfer t = { .tx_buf = cmd, .len = ARRAY_SIZE(cmd), }; struct spi_message m; cmd[1] = (unsigned char)((spidev->cur_addr & 0xff0000) >> 16); cmd[2] = (unsigned char)((spidev->cur_addr & 0xff00) >> 8); cmd[3] = (unsigned char)(spidev->cur_addr & 0xff); spi_w25x_write_enable(spi); spi_message_init(&m); spi_message_add_tail(&t, &m); status = spi_sync(spi, &m); spi_w25x_wait_ready(spi); dev_dbg(&spi->dev,"32kb block erase OK\n"); return status; } static int spi_w25x_64kb_block_erase(struct spidev_data *spidev) { int status; char cmd[4] = {BLOCK_64KB_ERASE}; struct spi_device *spi = spidev->spi; struct spi_transfer t = { .tx_buf = cmd, .len = ARRAY_SIZE(cmd), }; struct spi_message m; cmd[1] = (unsigned char)((spidev->cur_addr & 0xff0000) >> 16); cmd[2] = (unsigned char)((spidev->cur_addr & 0xff00) >> 8); cmd[3] = (unsigned char)(spidev->cur_addr & 0xff); spi_w25x_write_enable(spi); spi_message_init(&m); spi_message_add_tail(&t, &m); status = spi_sync(spi, &m); spi_w25x_wait_ready(spi); dev_dbg(&spi->dev,"64kb block erase OK\n"); return status; } static int spi_w25x_chip_erase(struct spi_device *spi) { int status; char chip_erase[1] = {CHIP_ERASE}; struct spi_transfer erase = { .tx_buf = chip_erase, .len = ARRAY_SIZE(chip_erase), }; struct spi_message m; spi_w25x_write_enable(spi); spi_message_init(&m); spi_message_add_tail(&erase, &m); status = spi_sync(spi, &m); spi_w25x_wait_ready(spi); dev_dbg(&spi->dev,"chip erase OK\n"); return status; } static loff_t spi_w25x_llseek(struct file *filp, loff_t offset, int orig) { loff_t ret = 0; struct spidev_data *spidev; spidev = filp->private_data; switch (orig) { case SEEK_SET: if (offset < 0) { ret = -EINVAL; break; } if ((unsigned int)offset > W25Q64_SIZE) { ret = -EINVAL; break; } spidev->cur_addr = (unsigned int)offset; ret = spidev->cur_addr; break; case SEEK_CUR: if ((spidev->cur_addr + offset) > W25Q64_SIZE) { ret = -EINVAL; break; } if ((spidev->cur_addr + offset) < 0) { ret = -EINVAL; break; } spidev->cur_addr += offset; ret = spidev->cur_addr; break; default: ret = - EINVAL; break; } dev_dbg(&spidev->spi->dev, "set curr addr:%02X\n", (unsigned int)ret); return ret; } static ssize_t spidev_sync(struct spidev_data *spidev, struct spi_message *message) { DECLARE_COMPLETION_ONSTACK(done); int status; struct spi_device *spi; spin_lock_irq(&spidev->spi_lock); spi = spidev->spi; spin_unlock_irq(&spidev->spi_lock); if (spi == NULL) status = -ESHUTDOWN; else status = spi_sync(spi, message); if (status == 0) status = message->actual_length; return status; } static inline ssize_t spidev_sync_write(struct spidev_data *spidev, size_t len) { int status; char cmd[1] = {PAGE_PROGRAM}; unsigned char addr[3]; struct spi_transfer c[] = { { .tx_buf = cmd, .len = ARRAY_SIZE(cmd), }, { .tx_buf = addr, .len = ARRAY_SIZE(addr), }, }; struct spi_transfer t = { .tx_buf = spidev->tx_buffer, .len = len, .speed_hz = spidev->speed_hz, }; struct spi_message m; addr[0] = (unsigned char)((spidev->cur_addr & 0xff0000) >> 16); addr[1] = (unsigned char)((spidev->cur_addr & 0xff00) >> 8); addr[2] = (unsigned char)(spidev->cur_addr & 0xff); spi_w25x_write_enable(spidev->spi); spi_message_init(&m); spi_message_add_tail(&c[0], &m); spi_message_add_tail(&c[1], &m); spi_message_add_tail(&t, &m); status = spidev_sync(spidev, &m); spi_w25x_wait_ready(spidev->spi); return status; } static inline ssize_t spidev_sync_read(struct spidev_data *spidev, size_t len) { int status; char cmd[] = {READ_DATA}; unsigned char addr[3]; struct spi_transfer t[] = { { .tx_buf = cmd, .len = ARRAY_SIZE(cmd), .speed_hz = spidev->speed_hz, }, { .tx_buf = addr, .len = ARRAY_SIZE(addr), }, { .rx_buf = spidev->rx_buffer, .len = len, .speed_hz = spidev->speed_hz, } }; struct spi_message m; addr[0] = (unsigned char)((spidev->cur_addr & 0xff0000) >> 16); addr[1] = (unsigned char)((spidev->cur_addr & 0xff00) >> 8); addr[2] = (unsigned char)(spidev->cur_addr & 0xff); spi_message_init(&m); spi_message_add_tail(&t[0], &m); spi_message_add_tail(&t[1], &m); spi_message_add_tail(&t[2], &m); status = spidev_sync(spidev, &m); spi_w25x_wait_ready(spidev->spi); return status; } /* Read-only message with current device setup */ static ssize_t spidev_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos) { struct spidev_data *spidev; ssize_t status = 0; /* chipselect only toggles at start or end of operation */ if (count > bufsiz) return -EMSGSIZE; spidev = filp->private_data; mutex_lock(&spidev->buf_lock); status = spidev_sync_read(spidev, count); if (status > 0) { unsigned long missing; missing = copy_to_user(buf, spidev->rx_buffer, status); if (missing == status) status = -EFAULT; else status = status - missing; } mutex_unlock(&spidev->buf_lock); return status; } /* Write-only message with current device setup */ static ssize_t spidev_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos) { struct spidev_data *spidev; ssize_t status = 0; unsigned long missing; /* chipselect only toggles at start or end of operation */ if (count > bufsiz) return -EMSGSIZE; spidev = filp->private_data; gpio_set_value(spidev->wp_gpio, 1); mutex_lock(&spidev->buf_lock); missing = copy_from_user(spidev->tx_buffer, buf, count); if (missing == 0) status = spidev_sync_write(spidev, count); else status = -EFAULT; mutex_unlock(&spidev->buf_lock); gpio_set_value(spidev->wp_gpio, 0); return status; } static long spidev_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) { int err = 0; int retval = 0; struct spidev_data *spidev; struct spi_device *spi; u32 tmp; /* Check type and command number */ if (_IOC_TYPE(cmd) != W25Q64_MAGIC) return -ENOTTY; /* Check access direction once here; don't repeat below. * IOC_DIR is from the user perspective, while access_ok is * from the kernel perspective; so they look reversed. */ if (_IOC_DIR(cmd) & _IOC_READ) err = !access_ok(VERIFY_WRITE, (void __user *)arg, _IOC_SIZE(cmd)); if (err == 0 && _IOC_DIR(cmd) & _IOC_WRITE) err = !access_ok(VERIFY_READ, (void __user *)arg, _IOC_SIZE(cmd)); if (err) return -EFAULT; /* guard against device removal before, or while, * we issue this ioctl. */ spidev = filp->private_data; spin_lock_irq(&spidev->spi_lock); spi = spi_dev_get(spidev->spi); spin_unlock_irq(&spidev->spi_lock); if (spi == NULL) return -ESHUTDOWN; /* use the buffer lock here for triple duty: * - prevent I/O (from us) so calling spi_setup() is safe; * - prevent concurrent SPI_IOC_WR_* from morphing * data fields while SPI_IOC_RD_* reads them; * - SPI_IOC_MESSAGE needs the buffer locked "normally". */ mutex_lock(&spidev->buf_lock); switch (cmd) { /* read requests */ case W25Q64_IOC_SECTOR_ERASE: retval = spi_w25x_sector_erase(spidev, arg); break; case W25Q64_IOC_32KB_BLOCK_ERASE: retval = spi_w25x_32kb_block_erase(spidev); break; case W25Q64_IOC_64KB_BLOCK_ERASE: retval = spi_w25x_64kb_block_erase(spidev); break; case W25Q64_IOC_CHIP_ERASE: retval = spi_w25x_chip_erase(spi); break; case SPI_IOC_RD_MODE: retval = __put_user(spi->mode & SPI_MODE_MASK, (__u8 __user *)arg); break; case SPI_IOC_RD_MODE32: retval = __put_user(spi->mode & SPI_MODE_MASK, (__u32 __user *)arg); break; case SPI_IOC_RD_LSB_FIRST: retval = __put_user((spi->mode & SPI_LSB_FIRST) ? 1 : 0, (__u8 __user *)arg); break; case SPI_IOC_RD_BITS_PER_WORD: retval = __put_user(spi->bits_per_word, (__u8 __user *)arg); break; case SPI_IOC_RD_MAX_SPEED_HZ: retval = __put_user(spidev->speed_hz, (__u32 __user *)arg); break; /* write requests */ case SPI_IOC_WR_MODE: case SPI_IOC_WR_MODE32: if (cmd == SPI_IOC_WR_MODE) retval = __get_user(tmp, (u8 __user *)arg); else retval = __get_user(tmp, (u32 __user *)arg); if (retval == 0) { u32 save = spi->mode; if (tmp & ~SPI_MODE_MASK) { retval = -EINVAL; break; } tmp |= spi->mode & ~SPI_MODE_MASK; spi->mode = (u16)tmp; retval = spi_setup(spi); if (retval < 0) spi->mode = save; else dev_dbg(&spi->dev, "spi mode %x\n", tmp); } break; case SPI_IOC_WR_LSB_FIRST: retval = __get_user(tmp, (__u8 __user *)arg); if (retval == 0) { u32 save = spi->mode; if (tmp) spi->mode |= SPI_LSB_FIRST; else spi->mode &= ~SPI_LSB_FIRST; retval = spi_setup(spi); if (retval < 0) spi->mode = save; else dev_dbg(&spi->dev, "%csb first\n", tmp ? 'l' : 'm'); } break; case SPI_IOC_WR_BITS_PER_WORD: retval = __get_user(tmp, (__u8 __user *)arg); if (retval == 0) { u8 save = spi->bits_per_word; spi->bits_per_word = tmp; retval = spi_setup(spi); if (retval < 0) spi->bits_per_word = save; else dev_dbg(&spi->dev, "%d bits per word\n", tmp); } break; case SPI_IOC_WR_MAX_SPEED_HZ: retval = __get_user(tmp, (__u32 __user *)arg); if (retval == 0) { u32 save = spi->max_speed_hz; spi->max_speed_hz = tmp; retval = spi_setup(spi); if (retval >= 0) spidev->speed_hz = tmp; else dev_dbg(&spi->dev, "%d Hz (max)\n", tmp); spi->max_speed_hz = save; } break; default: return -EINVAL; } mutex_unlock(&spidev->buf_lock); spi_dev_put(spi); return retval; } #ifdef CONFIG_COMPAT static long spidev_compat_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) { return spidev_ioctl(filp, cmd, (unsigned long)compat_ptr(arg)); } #else #define spidev_compat_ioctl NULL #endif /* CONFIG_COMPAT */ static int spidev_open(struct inode *inode, struct file *filp) { struct spidev_data *spidev; int status = -ENXIO; mutex_lock(&device_list_lock); list_for_each_entry(spidev, &device_list, device_entry) { if (spidev->devt == inode->i_rdev) { status = 0; break; } } if (status) { pr_debug("spidev: nothing for minor %d\n", iminor(inode)); goto err_find_dev; } if (!spidev->tx_buffer) { spidev->tx_buffer = kmalloc(bufsiz, GFP_KERNEL); if (!spidev->tx_buffer) { dev_err(&spidev->spi->dev, "open/ENOMEM\n"); status = -ENOMEM; goto err_find_dev; } } if (!spidev->rx_buffer) { spidev->rx_buffer = kmalloc(bufsiz, GFP_KERNEL); if (!spidev->rx_buffer) { dev_err(&spidev->spi->dev, "open/ENOMEM\n"); status = -ENOMEM; goto err_alloc_rx_buf; } } spidev->users++; filp->private_data = spidev; mutex_unlock(&device_list_lock); return 0; err_alloc_rx_buf: kfree(spidev->tx_buffer); spidev->tx_buffer = NULL; err_find_dev: mutex_unlock(&device_list_lock); return status; } static int spidev_release(struct inode *inode, struct file *filp) { struct spidev_data *spidev; mutex_lock(&device_list_lock); spidev = filp->private_data; filp->private_data = NULL; /* last close? */ spidev->users--; if (!spidev->users) { int dofree; kfree(spidev->tx_buffer); spidev->tx_buffer = NULL; kfree(spidev->rx_buffer); spidev->rx_buffer = NULL; spin_lock_irq(&spidev->spi_lock); if (spidev->spi) spidev->speed_hz = spidev->spi->max_speed_hz; /* ... after we unbound from the underlying device? */ dofree = (spidev->spi == NULL); spin_unlock_irq(&spidev->spi_lock); if (dofree) kfree(spidev); } mutex_unlock(&device_list_lock); return 0; } static const struct file_operations spidev_fops = { .owner = THIS_MODULE, /* REVISIT switch to aio primitives, so that userspace * gets more complete API coverage. It'll simplify things * too, except for the locking. */ .write = spidev_write, .read = spidev_read, .unlocked_ioctl = spidev_ioctl, .compat_ioctl = spidev_compat_ioctl, .open = spidev_open, .release = spidev_release, .llseek = spi_w25x_llseek, }; /*-------------------------------------------------------------------------*/ /* The main reason to have this class is to make mdev/udev create the * /dev/spidevB.C character device nodes exposing our userspace API. * It also simplifies memory management. */ static struct class *spidev_class; #ifdef CONFIG_OF static const struct of_device_id spidev_dt_ids[] = { { .compatible = "rockchip,w25q64" }, {}, }; MODULE_DEVICE_TABLE(of, spidev_dt_ids); #endif /*-------------------------------------------------------------------------*/ static int spidev_probe(struct spi_device *spi) { struct spidev_data *spidev; struct device_node *np = spi->dev.of_node; int status; unsigned long minor; dev_err(&spi->dev, "probe,rk3399.0\n"); /* * spidev should never be referenced in DT without a specific * compatible string, it is a Linux implementation thing * rather than a description of the hardware. */ if (spi->dev.of_node && !of_match_device(spidev_dt_ids, &spi->dev)) { dev_err(&spi->dev, "buggy DT: spidev listed directly in DT\n"); WARN_ON(spi->dev.of_node && !of_match_device(spidev_dt_ids, &spi->dev)); } /* Allocate driver data */ spidev = kzalloc(sizeof(*spidev), GFP_KERNEL); if (!spidev) return -ENOMEM; /* Initialize the driver data */ spidev->spi = spi; spin_lock_init(&spidev->spi_lock); mutex_init(&spidev->buf_lock); INIT_LIST_HEAD(&spidev->device_entry); /* If we can allocate a minor number, hook up this device. * Reusing minors is fine so long as udev or mdev is working. */ mutex_lock(&device_list_lock); minor = find_first_zero_bit(minors, N_SPI_MINORS); if (minor < N_SPI_MINORS) { struct device *dev; spidev->devt = MKDEV(SPIDEV_MAJOR, minor); dev = device_create(spidev_class, &spi->dev, spidev->devt, spidev, "w25q64"); status = PTR_ERR_OR_ZERO(dev); } else { dev_err(&spi->dev, "no minor number available!\n"); status = -ENODEV; } if (status == 0) { set_bit(minor, minors); list_add(&spidev->device_entry, &device_list); } mutex_unlock(&device_list_lock); spidev->speed_hz = spi->max_speed_hz; if (status == 0) spi_set_drvdata(spi, spidev); else kfree(spidev); spidev->wp_gpio = of_get_named_gpio(np, "wp-gpio", 0); if (!gpio_is_valid(spidev->wp_gpio)) { dev_err(&spi->dev, "wp-gpio: %d is invalid\n", spidev->wp_gpio); return -ENODEV; } status = gpio_request(spidev->wp_gpio, "wp-gpio"); if (status) { dev_err(&spi->dev, "wp-gpio: %d request failed!\n", spidev->wp_gpio); gpio_free(spidev->wp_gpio); return -ENODEV; } gpio_direction_output(spidev->wp_gpio, 0); gpio_export(spidev->wp_gpio, 0); spi_read_w25x_id_0(spi); return status; } static int spidev_remove(struct spi_device *spi) { struct spidev_data *spidev = spi_get_drvdata(spi); gpio_free(spidev->wp_gpio); /* make sure ops on existing fds can abort cleanly */ spin_lock_irq(&spidev->spi_lock); spidev->spi = NULL; spin_unlock_irq(&spidev->spi_lock); /* prevent new opens */ mutex_lock(&device_list_lock); list_del(&spidev->device_entry); device_destroy(spidev_class, spidev->devt); clear_bit(MINOR(spidev->devt), minors); if (spidev->users == 0) kfree(spidev); mutex_unlock(&device_list_lock); return 0; } static struct spi_driver spidev_spi_driver = { .driver = { .name = "w25q64", .of_match_table = of_match_ptr(spidev_dt_ids), }, .probe = spidev_probe, .remove = spidev_remove, /* NOTE: suspend/resume methods are not necessary here. * We don't do anything except pass the requests to/from * the underlying controller. The refrigerator handles * most issues; the controller driver handles the rest. */ }; /*-------------------------------------------------------------------------*/ static int __init spidev_init(void) { int status; /* Claim our 256 reserved device numbers. Then register a class * that will key udev/mdev to add/remove /dev nodes. Last, register * the driver which manages those device numbers. */ BUILD_BUG_ON(N_SPI_MINORS > 256); status = register_chrdev(SPIDEV_MAJOR, "w25q64", &spidev_fops); if (status < 0) return status; spidev_class = class_create(THIS_MODULE, "w25q64"); if (IS_ERR(spidev_class)) { unregister_chrdev(SPIDEV_MAJOR, spidev_spi_driver.driver.name); return PTR_ERR(spidev_class); } status = spi_register_driver(&spidev_spi_driver); if (status < 0) { class_destroy(spidev_class); unregister_chrdev(SPIDEV_MAJOR, spidev_spi_driver.driver.name); } return status; } module_init(spidev_init); static void __exit spidev_exit(void) { spi_unregister_driver(&spidev_spi_driver); class_destroy(spidev_class); unregister_chrdev(SPIDEV_MAJOR, spidev_spi_driver.driver.name); } module_exit(spidev_exit); MODULE_AUTHOR("Yubel"); MODULE_DESCRIPTION("User mode SPI device interface"); MODULE_LICENSE("GPL");
w25q64.h
#ifndef W25Q64_H #define W25Q64_H #include <linux/types.h> #include <linux/ioctl.h> #define W25Q64_SIZE 0x800000 #define W25Q64_PAGE_LENGTH 256 #define W25Q64_SECTOR 4096 #define W25Q64_32KB_BLOCK 32768 #define W25Q64_64KB_BLOCK 65536 /* IOCTL commands */ #define W25Q64_MAGIC 'k' /*Erase SPI Flash*/ #define W25Q64_IOC_SECTOR_ERASE _IOW(W25Q64_MAGIC, 6, __u32) #define W25Q64_IOC_32KB_BLOCK_ERASE _IOW(W25Q64_MAGIC, 7, __u32) #define W25Q64_IOC_64KB_BLOCK_ERASE _IOW(W25Q64_MAGIC, 8, __u32) #define W25Q64_IOC_CHIP_ERASE _IOW(W25Q64_MAGIC, 9, __u32) #endif /* W25Q64_H */
-
w25q64中文数据手册
2016-01-09 16:03:39W25Q80(8M-bit),W25Q16(16M-bit)和W25Q32(32M-bit)是为系统提供一个最小的空间、引脚和功耗的存储器解决方案的串行Flash存储器。25Q系列比普通的串行Flash存储器更灵活,性能更优越。基于双倍/四倍的SPI,它们能够... -
W25Q64芯片数据手册PDF
2018-10-18 16:22:59正版w25Q64官方数据手册,PDF文档,有目录,敬请采纳。 -
STM32CubeMx之硬件SPI驱动W25Q64
2022-04-04 15:55:12W25Q64(64M-bit)是为系统提供一个最小的空间、引脚和功耗的存储器解决方案的串行Flash存储器。W25Q64的内存空间结构:一页256字节,4K(4096字节)为一个扇区,16个扇区为1块,容量为8M字节,共有128个块,2048个扇区。 ...STM32CubeMx之硬件SPI驱动W25Q64
1.SPI简介
SPI是串行外设接口(Serial Peripheral Interface)的缩写,是一种高速的,全双工,同步的通信总线,并且在芯片的管脚上只占用四根线,节约了芯片的管脚,同时为PCB的布局上节省空间,提供方便,正是出于这种简单易用的特性,越来越多的芯片集成了这种通信协议。
SPI:高速同步串行口。是一种标准的四线同步双向串行总线,是串行外围设备接口。是Motorola首先在其MC68HCXX系列处理器上定义的。SPI接口主要应用在 EEPROM,FLASH,实时时钟,AD转换器,还有数字信号处理器和数字信号解码器之间。
该接口一般使用4条线:串行时钟线(SCLK)、主机输入/从机输出数据线MISO、主机输出/从机输入数据线MOSI和低电平有效的从机选择线SS(有的SPI接口芯片带有中断信号线INT、有的SPI接口芯片没有主机输出/从机输入数据线MOSI)。
SPI根据时钟极性(CPOL)和时钟相位(CPHA)的不同,能够产生4时钟时序。时钟极性(CPOL)控制时钟线空闲电平状态,时钟相位(CPHA)用来控制数据采样极性。模式0:CPOL=0,CPHA=0
时钟线空闲电平为低电平,第一个边沿采样数据,第二个边沿发送数据;
模式1:CPOL=0,CPHA=1
时钟线空闲电平为低电平,第一个边沿发送数据,第二个边沿采样数据;
模式2:CPOL=1,CPHA=0
时钟线空闲电平为高电平,第一个边沿采样数据,第二个边沿发送数据;
模式3:CPOL=1,CPHA=1
时钟线空闲电平为高电平,第一个边沿发送数据,第二个边沿采样数据;
2 硬件接口
- CPU:STM32F103ZE
- 屏幕:3.5寸TFTLCD屏
- Flash: W25Q64(SPI方式)
- 软件平台: STM32CubeMx
2.1 W25Q64简介
W25Q64(64M-bit),W25Q16(16M-bit)和W25Q32(32M-bit)是为系统提供一个最小的空间、引脚和功耗的存储器解决方案的串行Flash存储器。25Q系列比普通的串行Flash存储器更灵活,性能更优越。基于双倍/四倍的SPI,它们能够可以立即完成提供数据给RAM,包括存储声音、文本和数据。芯片支持的工作电压2.7V到3.6V,正常工作时电流小于5mA,掉电时低于1uA。所有芯片提供标准的封装。
W25Q64/16/32由每页256字节组成。每页的256字节用一次页编程指令即可完成。每次可以擦除16页(1个扇区)、128 页(32KB块)、256页(64KB块)和全片擦除。
W25Q64的内存空间结构:一页256字节,4K(4096字节)为一个扇区,16个扇区为1块,容量为8M字节,共有128个块,2048个扇区。
W25Q64可擦写周期至少10万次,数据保存20年。
W25Q64驱动方式为SPI,支持SPI总线的工作模式0(0,0)和3( 1,1)。模式0和模式3。2.2 硬件接口
引脚 说明 CS 片选(低电平选中) PB12 SPI2_MISO 主机输入从机输出PB14 SPI2_MOSI 主机输出从机输入PB15 SPI_SCK 时钟线PB13 2.3 软件设置
SPI2配置:
NSS引脚配置:
3 代码生成
3.1 SPI初始化
SPI配置信息可参考STM32中文参考手册第23.5.1SPI控制寄存器小结。
3.2 SPI读写一字节函数
uint8_t SPI2_WROneByte(uint8_t data) { uint8_t dat_rx=0; HAL_SPI_TransmitReceive(&hspi2,&data,&dat_rx,1,100); return dat_rx; }
3.3 W25Q64 编程
3.3.1 读取W25Q64制造商/芯片ID
示例代码:/*获取W25Q64设备ID*/ uint16_t W25Q64_GetDeviceID(void) { uint16_t id; W25Q64_CS(0);//选中W25Q64 SPI2_WROneByte(0x90);//发送指令0x90 //发送24位地址 SPI2_WROneByte(0); SPI2_WROneByte(0); SPI2_WROneByte(0); id=SPI2_WROneByte(0xff);//制造商ID:0xef id<<=8; id|=SPI2_WROneByte(0xff);//设备ID:0x16 W25Q64_CS(1);//取消选中 return id; }
3.3.2 W25Q64页编程0x02
页编程指令允许从一个字节到256字节的数据编程(一页)(编程之前必须保证内存空间是 0XFF)。允许写入指令之前,必须先发送设备写使能指令。写使能开启后,设备才能接收编程指令。开启页编程先拉底/ CS, 然后发送指令代码“02 h”,接着发送一个 24 位地址(A23-A0)(发送3次,每次 8 位) 和至少一个数据字节(数据字节不能超过256字节)。数据字节发送完毕,需要拉高片选线 CS/,并判断状态位,等待写入结束。
进行页编程时,如果数据字节数超过了256字节,地址将自动回到页的起始地址,覆盖掉之前的数据。在某些情况下,数据字节小于256字节(同一页内), 也可以正常对其他字节存放,不会有任何影响。如果存放超过256字节的数据,需要分次编程存放。3.3.3 W25Q64读数据0x03
读取数据指令允许按顺序读取一个字节的内存数据。当片选CS/拉低之后, 紧随其后是一个24位的地址(A23-A0)(需要发送3次,每次8个字节,先发高位)。芯片收到地址后,将要读的数据按字节大小转移出去,数据是先转移高位,对于单片机,时钟下降沿发送数据,上升沿接收数据。读数据时,地址会自动增加,允许连续的读取数据。这意味着读取整个内存的数据,只要用一个指令就可以读完。数据读取完成之后,片选信号/ CS 拉高。读取数据的指令序列,如上图所示。如果一个读数据指令而发出的时候,设备正在擦除扇区,或者(忙= 1),该读指令将被忽略,也不会对当前周期有什么影响。3.3.4 扇区擦除0x20
扇区擦除指令可以擦除指定一个扇区(4 k字节)内所有数据,将内存空间恢复到 0xFF 状态。写入扇区擦除指令之前必须执行设备写使能(发送设备写使能指令 0x06),并判断状态寄存器(状态寄存器位最低位必须等于0才能操作)。发送的扇区擦除指令前,先拉低/ CS, 接着发送扇区擦除指令码”20 h”,和24位地址(A23-A0),地址发送完毕后,拉高片选线 CS/,并判断状态位,等待擦除结束。擦除一个扇区的最少需要 150ms 时间。3.3.5 读状态0x05和0x35
读取状态寄存器的指令是8位的指令。发送指令之前,先将/ CS 拉低,再发送指令码“05 h”或者“35h”。设备收到读取状态寄存器的指令后,将状态信息(高位)依次移位发送出去,读出的状态信息,最低位为1代表忙,最低位为0代表可以操作,状态信息读取完毕,将片选线拉高。
读状态寄存器指令可以使用在任何时候,即使程序在擦除的过程中或者写状态寄存器周期正在进行中。这可以检测忙碌状态来确定周期是否完成,以确定设备是否可以接受另一个指令。3.3.6 W25Q64指令表
4 主函数
MX_GPIO_Init(); MX_FSMC_Init(); MX_USART1_UART_Init(); MX_SPI2_Init(); /* USER CODE BEGIN 2 */ char buff[200]; char buff_tx[]="HAL库配置SPI硬件时序驱动W25Q64S数据读写测试 -- Ver1.0"; char buf_rx[100]; NT35310_Init();//LCD初始化 LCD_Display_Str(LCD_WIDTH/2-strlen("W25Q64初始化")/2*8,20,16,(u8 *)"W25Q64初始化",BLACK); LCD_Display_Str(20,40,16,(u8 *)"W25Q64 OK",RED); uint16_t id=W25Q64_GetDeviceID(); snprintf(buff,sizeof(buff),"ID信息:%#x",id); LCD_Display_Str(20,60,16,(u8 *)buff,RED); LCD_Display_Str(LCD_WIDTH/2-strlen("W25Q64读写测试")/2*8,90,16,(u8 *)"W25Q64读写测试",BLACK); W25Q64_WriteData(100,buf_tx,sizeof(buf_tx)); W25Q64_ReadData(100,buf_rx,sizeof(buf_tx)); LCD_Display_Str(20,120,16,(u8 *)"W25Q64写数据:OK",RED); LCD_Display_Str(20,140,16,(u8 *)"W25Q64读数据:OK",RED); LCD_Display_Str(20,160,16,(u8 *)"数据内容:",RED); LCD_Display_Str(20,180,16,(u8 *)buf_rx,BLUE); while(1) { }
-
W25Q64中文文档
2018-09-03 16:26:43W25Q64系列FLASH存储器可以为用户提供存储解决方案,具有PCB板占用空间少,引脚数量少,功耗低等特点。与普通串行flash相比,使用更灵活,性能更出色。 -
STM32入门开发: 介绍SPI总线、读写W25Q64(FLASH)(硬件+模拟时序)
2021-06-07 08:51:15SPI总线:STM32本身支持SPI硬件时序,本文示例代码里同时采用模拟时序和硬件时序两种方式读写W25Q64。 模拟时序更加方便移植到其他单片机,更加方便学习理解SPI时序,通用性更高,不分MCU; 硬件时序效率更高,每个...一、环境介绍
编程软件: keil5
操作系统: win10
MCU型号: STM32F103ZET6
STM32编程方式: 寄存器开发 (方便程序移植到其他单片机)
SPI总线: STM32本身支持SPI硬件时序,本文示例代码里同时采用模拟时序和硬件时序两种方式读写W25Q64。
模拟时序更加方便移植到其他单片机,更加方便学习理解SPI时序,通用性更高,不分MCU;
硬件时序效率更高,每个MCU配置方法不同,依赖MCU硬件本身支持。
存储器件: 采用华邦W25Q64 flash存储芯片。
W25Q64这类似的Flash存储芯片在单片机里、嵌入式系统里还是比较常见,可以用来存储图片数据、字库数据、音频数据、保存设备运行日志文件等。
完整工程代码下载:https://download.csdn.net/download/xiaolong1126626497/19425042
二、华邦W25Q64介绍(FLASH存储类型)
2.1 W25Q64芯片功能介绍
W25Q64是为系统提供一个最小空间、最少引脚,最低功耗的串行Flash存储器,25Q系列比普通的串行Flash存储器更灵活,性能更优越。
W25Q64支持双倍/四倍的SPI,可以储存包括声音、文本、图片和其他数据;芯片支持的工作电压 2.7V 到 3.6V,正常工作时电流小于5mA,掉电时低于1uA,所有芯片提供标准的封装。
W25Q64的内存空间结构: 一页256字节,4K(4096 字节)为一个扇区,16个扇区为1块,容量为8M字节,共有128个块,2048 个扇区。
W25Q64每页大小由256字节组成,每页的256字节用一次页编程指令即可完成。
擦除指令分别支持: 16页(1个扇区)、128页、256页、全片擦除。
W25Q64支持标准串行外围接口(SPI),和高速的双倍/四倍输出,双倍/四倍用的引脚:串行时钟、片选端、串行数据 I/O0(DI)、I/O1(DO)、I/O2(WP)和 I/O3(HOLD)。
SPI 最高支持 80MHz,当用快读双倍/四倍指令时,相当于双倍输出时最高速率160MHz,四倍输出时最高速率 320MHz。这个传输速率比得上8位和16位的并行Flash存储器。
W25Q64支持 JEDEC 标准,具有唯一的 64 位识别序列号,方便区别芯片型号。2.2 W25Q64芯片特性详细介绍
●SPI串行存储器系列
-W25Q64:64M 位/8M 字节
-W25Q16:16M 位/2M 字节
-W25Q32:32M 位/4M 字节
-每 256 字节可编程页
●灵活的4KB扇区结构
-统一的扇区擦除(4K 字节)
-块擦除(32K 和 64K 字节)
-一次编程 256 字节
-至少 100,000 写/擦除周期
-数据保存 20 年
●标准、双倍和四倍SPI
-标准 SPI:CLK、CS、DI、DO、WP、HOLD
-双倍 SPI:CLK、CS、IO0、IO1、WP、HOLD
-四倍 SPI:CLK、CS、IO0、IO1、IO2、IO3●高级的安全特点
-软件和硬件写保护
-选择扇区和块保护
-一次性编程保护(1)
-每个设备具有唯一的64位ID(1)●高性能串行Flash存储器
-比普通串行Flash性能高6倍
-80MHz时钟频率
-双倍SPI相当于160MHz
-四倍SPI相当于320MHz
-40MB/S连续传输数据
-30MB/S随机存取(每32字节)
-比得上16位并行存储器
●低功耗、宽温度范围
-单电源 2.7V-3.6V
-工作电流 4mA,掉电<1μA(典型值)
-40℃~+85℃工作2.3 引脚介绍
下面只介绍W25Q64标准SPI接口,因为目前开发板上的封装使用的就是标准SPI接口。
引脚编号
引脚名称
I/O
功能
1
/CS
I
片选端输入
2
DO(IO1)
I/O
数据输出(数据输入输出 1)*1
3
/WP(IO2)
I/O
写保护输入(数据输入输出 2)*2
4
GND
地
5
DI(IO0)
I/O
数据输入(数据输入输出 0)*1
6
CLK
I
串行时钟输入
7
/HOLD(IO3)
I/O
保持端输入(数据输入输出 3)*2
8
VCC
电源
2.2.1 SPI片选(/CS)引脚用于使能和禁止芯片操作
CS引脚是W25Q64的片选引脚,用于选中芯片;当CS为高电平时,芯片未被选择,串行数据输出(DO、IO0、IO1、IO2 和 IO3)引脚为高阻态。未被选择时,芯片处于待机状态下的低功耗,除非芯片内部在擦除、编程。当/CS 变成低电平,芯片功耗将增长到正常工作,能够从芯片读写数据。上电后, 在接收新的指令前,/CS 必须由高变为低电平。上电后,/CS 必须上升到 VCC,在/CS 接上拉电阻可以完成这个操作。
2.2.2 串行数据输入、输出和 IOs(DI、DO 和 IO0、IO1、IO2、IO3)
W25Q64、W25Q16 和 W25Q32 支持标准 SPI、双倍 SPI 和四倍 SPI。
标准的 SPI 传输用单向的 DI(输入)引脚连续的写命令、地址或者数据在串行时钟(CLK)的上升沿时写入到芯片内。
标准的SPI 用单向的 DO(输出)在 CLK 的下降沿从芯片内读出数据或状态。
2.2.3 写保护(/WP)
写保护引脚(/WP)用来保护状态寄存器。和状态寄存器的块保护位(SEC、TB、BP2、BP1 和BP0)和状态寄存器保护位(SRP)对存储器进行一部分或者全部的硬件保护。/WP 引脚低电平有效。当状态寄存器 2 的 QE 位被置位了,/WP 引脚(硬件写保护)的功能不可用。
2.2.4 保持端(/HOLD)
当/HOLD 引脚是有效时,允许芯片暂停工作。在/CS 为低电平时,当/HOLD 变为低电平,DO 引脚将变为高阻态,在 DI 和 CLK 引脚上的信号将无效。当/HOLD 变为高电平,芯片恢复工作。/HOLD 功能用在当有多个设备共享同一 SPI 总线时。/HOLD 引脚低电平有效。当状态寄存器 2 的 QE 位被置位了,/ HOLD 引脚的功能不可用。
2.2.5 串行时钟(CLK)
串行时钟输入引脚为串行输入和输出操作提供时序。(见 SPI 操作)。
设备数据传输是从高位开始,数据传输的格式为 8bit,数据采样从第二个时间边沿开始,空闲状态时,时钟线 clk 为高电平。
2.3 内部结构框架图
2.4 W25Q64的标准SPI操作流程
W25Q64标准SPI总线接口包含四个信号: 串行时钟(CLK)、片选端(/CS)、串行数据输入(DI)和串行数据输出(DO)。
DI输入引脚在CLK的上升沿连续写命令、地址或数据到芯片内。
DO输出引脚在CLK的下降沿从芯片内读出数据或状态。
W25Q64分别支持SPI总线工作模式0和工作模式3。模式0和模式3的主要区别在于常态时的CLK信号不同;对于模式0来说,当SPI主机已准备好数据还没传输到串行Flash中时,CLK信号常态为低;
设备数据传输是从高位开始,数据传输的格式为8bit,数据采样从第二个时间边沿开始,空闲状态时,时钟线clk为高电平。
2.5 部分控制和状态寄存器介绍
2.5.1 W25Q64的指令表
指令名称
字节 1
(代码)
字节 2
字节 3
字节 4
字节 5
字节 6
写使能
06h
write_enabled
禁止写
04h
读状态寄存器 1
05h
(S7-S0)(2)
读状态寄存器 2
35h
(S15-S8)(2)
写状态寄存器
01h
(S7-S0)
(S15-S8)
页编程
02h
A23-A16
A15-A8
A7-A0
(D7-D0)
四倍页编程
32h
A23-A16
A15-A8
A7-A0
(D7-D0,…)(3)
块擦除(64KB)
D8h
A23-A16
A15-A8
A7-A0
块擦除(32KB)
52h
A23-A16
A15-A8
A7-A0
扇区擦除(4KB)
20h
A23-A16
A15-A8
A7-A0
全片擦除
C7h/60h
暂停擦除
75h
恢复擦除
7Ah
掉电模式
B9h
高性能模式
A3h
2.5.2 读状态寄存器1
状态寄存器1的内部结构如下:
状态寄存器1的S0位是当前W25Q64的忙状态;为1的时候表示设备正在执行程序(可能是在擦除芯片)或写状态寄存器指令,这个时候设备将忽略传来的指令, 除了读状态寄存器和擦除暂停指令外,其他写指令或写状态指令都无效, 当 S0 为 0 状态时指示设备已经执行完毕,可以进行下一步操作。
读状态寄存器1的时序如下:
读取状态寄存器的指令是 8 位的指令。发送指令之前,先将/CS 拉低,再发送指令码“05 h” 或者“35h”。设备收到读取状态寄存器的指令后,将状态信息(高位)依次移位发送出去,读出的状态信息,最低位为 1 代表忙,最低位为 0 代表可以操作,状态信息读取完毕,将片选线拉高。
读状态寄存器指令可以使用在任何时候,即使程序在擦除的过程中或者写状态寄存器周期正在进行中。这可以检测忙碌状态来确定周期是否完成,以确定设备是否可以接受另一个指令。
2.5.3 读制造商ID和芯片ID
时序图如下:
读取制造商/设备 ID 指令可以读取制造商 ID 和特定的设备 ID。读取之前,拉低 CS 片选信号,接着发送指令代码“90h” ,紧随其后的是一个 24 位地址(A23-A0)000000h。 设备收到指令之后,会发出华邦电子制造商 ID(EFh) 和设备ID(w25q64 为 16h)。如果 24 位地址设置为 000001h ,设备 ID 会先发出,然后跟着制造商 ID。制造商和设备ID可以连续读取。完成指令后,片选信号/ CS 拉高。
2.5.4 全片擦除(C7h/60h)
全芯片擦除指令,可以将整个芯片的所有内存数据擦除,恢复到 0XFF 状态。写入全芯片擦除指令之前必须执行设备写使能(发送设备写使能指令 0x06),并判断状态寄存器(状态寄存器位最低位必须等于 0 才能操作)。发送全芯片擦除指令前,先拉低/ CS,接着发送擦除指令码”C7h”或者是”60h”, 指令码发送完毕后,拉高片选线 CS/,,并判断状态位,等待擦除结束。全片擦除指令尽量少用,擦除会缩短设备的寿命。
2.5.5 读数据(03h)
读取数据指令允许按顺序读取一个字节的内存数据。当片选 CS/拉低之后,紧随其后是一个 24 位的地址(A23-A0)(需要发送 3 次,每次 8 个字节,先发高位)。芯片收到地址后,将要读的数据按字节大小转移出去,数据是先转移高位,对于单片机,时钟下降沿发送数据,上升沿接收数据。读数据时,地址会自动增加,允许连续的读取数据。这意味着读取整个内存的数据,只要用一个指令就可以读完。数据读取完成之后,片选信号/ CS 拉高。
读取数据的指令序列,如上图所示。如果一个读数据指令而发出的时候,设备正在擦除扇区,或者(忙= 1),该读指令将被忽略,也不会对当前周期有什么影响。
三、SPI时序介绍
SPI是串行外设接口(Serial Peripheral Interface)的缩写,是一种高速的,全双工,同步的通信总线,并且在芯片的管脚上只占用四根线,节约了芯片的管脚,同时为PCB的布局上节省空间。
SPI是一种高速、高效率的串行接口技术,一共有4根线。通常由一个主模块和一个或多个从模块组成,主模块选择一个从模块进行同步通信,从而完成数据的交换。SPI是一个环形结构,通信时需要至少4根线(在单向传输时3根线也可以)。分别是MISO(主设备数据输入)、MOSI(主设备数据输出)、SCLK(时钟)、CS(片选)。
(1)MISO– Master Input Slave Output,主设备数据输入,从设备数据输出;
(2)MOSI– Master Output Slave Input,主设备数据输出,从设备数据输入;
(3)SCLK – Serial Clock,时钟信号,由主设备产生;
(4)CS – Chip Select,从设备使能信号,由主设备控制。其中,CS是从芯片是否被主芯片选中的控制信号,也就是说只有片选信号为预先规定的使能信号时(高电位或低电位),主芯片对此从芯片的操作才有效。这就使在同一条总线上连接多个SPI设备成为可能。接下来就负责通讯的3根线了。通讯是通过数据交换完成的,这里先要知道SPI是串行通讯协议,也就是说数据是一位一位的传输的。这就是SCLK时钟线存在的原因,由SCLK提供时钟脉冲,SDI,SDO则基于此脉冲完成数据传输。数据输出通过 SDO线,数据在时钟上升沿或下降沿时改变,在紧接着的下降沿或上升沿被读取。完成一位数据传输,输入也使用同样原理。因此,至少需要8次时钟信号的改变(上沿和下沿为一次),才能完成8位数据的传输。
时钟信号线SCLK只能由主设备控制,从设备不能控制。这样的传输方式有一个优点,在数据位的传输过程中可以暂停,也就是时钟的周期可以为不等宽,因为时钟线由主设备控制,当没有时钟跳变时,从设备不采集或传送数据。SPI还是一个数据交换协议:因为SPI的数据输入和输出线独立,所以允许同时完成数据的输入和输出。芯片集成的SPI串行同步时钟极性和相位可以通过寄存器配置,IO模拟的SPI串行同步时钟需要根据从设备支持的时钟极性和相位来通讯。SPI通信原理比I2C要简单,IIC有应答机制,可以确保数据都全部发送成。SPI接口没有指定的流控制,没有应答机制确认是否接收到数据,速度上更加快。SPI总线通过时钟极性和相位可以配置成4种时序:
STM32F103参考手册,SPI章节介绍的时序图:
SPI时序比较简单,CPU如果没有硬件支持,可以直接写代码采用IO口模拟,下面是模拟时序的示例的代码:
SPI的模式1: u8 SPI_ReadWriteOneByte(u8 tx_data) { u8 i,rx_data=0; SCK=0; //空闲电平(默认初始化情况) for(i=0;i<8;i++) { /*1. 主机发送一位数据*/ SCK=0;//告诉从机,主机将要发送数据 if(tx_data&0x80)MOSI=1; //发送数据 else MOSI=0; SCK=1; //告诉从机,主机数据发送完毕 tx_data<<=1; //继续发送下一位 /*2. 主机接收一位数据*/ rx_data<<=1; //默认认为接收到0 if(MISO)rx_data|=0x01; } SCK=0; //恢复空闲电平 return rx_data; } SPI的模式2: u8 SPI_ReadWriteOneByte(u8 tx_data) { u8 i,rx_data=0; SCK=0; //空闲电平(默认初始化情况) for(i=0;i<8;i++) { /*1. 主机发送一位数据*/ SCK=1;//告诉从机,主机将要发送数据 if(tx_data&0x80)MOSI=1; //发送数据 else MOSI=0; SCK=0; //告诉从机,主机数据发送完毕 tx_data<<=1; //继续发送下一位 /*2. 主机接收一位数据*/ rx_data<<=1; //默认认为接收到0 if(MISO)rx_data|=0x01; } SCK=0; //恢复空闲电平 return rx_data; } SPI的模式3: u8 SPI_ReadWriteOneByte(u8 tx_data) { u8 i,rx_data=0; SCK=1; //空闲电平(默认初始化情况) for(i=0;i<8;i++) { /*1. 主机发送一位数据*/ SCK=1;//告诉从机,主机将要发送数据 if(tx_data&0x80)MOSI=1; //发送数据 else MOSI=0; SCK=0; //告诉从机,主机数据发送完毕 tx_data<<=1; //继续发送下一位 /*2. 主机接收一位数据*/ rx_data<<=1; //默认认为接收到0 if(MISO)rx_data|=0x01; } SCK=1; //恢复空闲电平 return rx_data; } SPI的模式4: u8 SPI_ReadWriteOneByte(u8 tx_data) { u8 i,rx_data=0; SCK=1; //空闲电平(默认初始化情况) for(i=0;i<8;i++) { /*1. 主机发送一位数据*/ SCK=0;//告诉从机,主机将要发送数据 if(tx_data&0x80)MOSI=1; //发送数据 else MOSI=0; SCK=1; //告诉从机,主机数据发送完毕 tx_data<<=1; //继续发送下一位 /*2. 主机接收一位数据*/ rx_data<<=1; //默认认为接收到0 if(MISO)rx_data|=0x01; } SCK=1; //恢复空闲电平 return rx_data; }
四、W25Q64的示例代码
4.1 STM32采用硬件SPI读写W25Q64示例代码
/* 函数功能:SPI初始化(模拟SPI) 硬件连接: MISO--->PB14 MOSI--->PB15 SCLK--->PB13 */ void SPI_Init(void) { /*开启时钟*/ RCC->APB1ENR|=1<<14; //开启SPI2时钟 RCC->APB2ENR|=1<<3; //PB GPIOB->CRH&=0X000FFFFF; //清除寄存器 GPIOB->CRH|=0XB8B00000; GPIOB->ODR|=0X7<<13; //PB13/14/15上拉--输出高电平 /*SPI2基本配置*/ SPI2->CR1=0X0; //清空寄存器 SPI2->CR1|=0<<15; //选择“双线双向”模式 SPI2->CR1|=0<<11; //使用8位数据帧格式进行发送/接收; SPI2->CR1|=0<<10; //全双工(发送和接收); SPI2->CR1|=1<<9; //启用软件从设备管理 SPI2->CR1|=1<<8; //NSS SPI2->CR1|=0<<7; //帧格式,先发送高位 SPI2->CR1|=0x0<<3;//当总线频率为36MHZ时,SPI速度为18MHZ,高速。 SPI2->CR1|=1<<2; //配置为主设备 SPI2->CR1|=1<<1; //空闲状态时, SCK保持高电平。 SPI2->CR1|=1<<0; //数据采样从第二个时钟边沿开始。 SPI2->CR1|=1<<6; //开启SPI设备。 } /* 函数功能:SPI读写一个字节 */ u8 SPI_ReadWriteOneByte(u8 data_tx) { u16 cnt=0; while((SPI2->SR&1<<1)==0) //等待发送区空--等待发送缓冲为空 { cnt++; if(cnt>=65530)return 0; //超时退出 u16=2个字节 } SPI2->DR=data_tx; //发送一个byte cnt=0; while((SPI2->SR&1<<0)==0) //等待接收完一个byte { cnt++; if(cnt>=65530)return 0; //超时退出 } return SPI2->DR; //返回收到的数据 } /* 函数功能:W25Q64初始化 硬件连接: MOSI--->PB15 MISO--->PB14 SCLK--->PB13 CS----->PB12 */ void W25Q64_Init(void) { /*1. 开时钟*/ RCC->APB2ENR|=1<<3; //PB /*2. 配置GPIO口模式*/ GPIOB->CRH&=0xFFF0FFFF; GPIOB->CRH|=0x00030000; W25Q64_CS=1; //未选中芯片 SPI_Init(); //SPI初始化 } /* 函数功能:读取芯片的ID号 */ u16 W25Q64_ReadID(void) { u16 id; /*1. 拉低片选*/ W25Q64_CS=0; /*2. 发送读取ID的指令*/ SPI_ReadWriteOneByte(0x90); /*3. 发送24位的地址-0*/ SPI_ReadWriteOneByte(0); SPI_ReadWriteOneByte(0); SPI_ReadWriteOneByte(0); /*4. 读取芯片的ID*/ id=SPI_ReadWriteOneByte(0xFF)<<8; id|=SPI_ReadWriteOneByte(0xFF); /*5. 拉高片选*/ W25Q64_CS=1; return id; } /* 函数功能:检测W25Q64状态 */ void W25Q64_CheckStat(void) { u8 stat=1; while(stat&1<<0) { W25Q64_CS=0; //选中芯片 SPI_ReadWriteOneByte(0x05); //发送读状态寄存器1指令 stat=SPI_ReadWriteOneByte(0xFF); //读取状态 W25Q64_CS=1; //取消选中芯片 } } /* 函数功能:页编程 说 明:一页最多写256个字节。 写数据之前,必须保证空间是0xFF 函数参数: u32 addr:页编程起始地址 u8 *buff:写入的数据缓冲区 u16 len :写入的字节长度 */ void W25Q64_PageWrite(u32 addr,u8 *buff,u16 len) { u16 i; W25Q64_Enabled(); //写使能 W25Q64_CS=0; //选中芯片 SPI_ReadWriteOneByte(0x02); //页编程指令 SPI_ReadWriteOneByte(addr>>16); //24~16地址 SPI_ReadWriteOneByte(addr>>8); //16~8地址 SPI_ReadWriteOneByte(addr); //8~0地址 for(i=0;i<len;i++) { SPI_ReadWriteOneByte(buff[i]); //8~0地址 } W25Q64_CS=1; //取消选中芯片 W25Q64_CheckStat(); //检测芯片忙状态 } /* 函数功能:连续读数据 函数参数: u32 addr:读取数据的起始地址 u8 *buff:读取数据存放的缓冲区 u32 len :读取字节的长度 */ void W25Q64_ReadByteData(u32 addr,u8 *buff,u32 len) { u32 i; W25Q64_CS=0; //选中芯片 SPI_ReadWriteOneByte(0x03); //读数据指令 SPI_ReadWriteOneByte(addr>>16); //24~16地址 SPI_ReadWriteOneByte(addr>>8); //16~8地址 SPI_ReadWriteOneByte(addr); //8~0地址 for(i=0;i<len;i++)buff[i]=SPI_ReadWriteOneByte(0xFF); W25Q64_CS=1; //取消选中芯片 } /* 函数功能:擦除一个扇区 函数参数: u32 addr:擦除扇区的地址范围 */ void W25Q64_ClearSector(u32 addr) { W25Q64_Enabled(); //写使能 W25Q64_CS=0; //选中芯片 SPI_ReadWriteOneByte(0x20); //扇区擦除指令 SPI_ReadWriteOneByte(addr>>16); //24~16地址 SPI_ReadWriteOneByte(addr>>8); //16~8地址 SPI_ReadWriteOneByte(addr); //8~0地址 W25Q64_CS=1; //取消选中芯片 W25Q64_CheckStat(); //检测芯片忙状态 } /* 函数功能:写使能 */ void W25Q64_Enabled(void) { W25Q64_CS=0; //选中芯片 SPI_ReadWriteOneByte(0x06); //写使能 W25Q64_CS=1; //取消选中芯片 } /* 函数功能:指定位置写入指定个数的数据,不考虑擦除问题 注意事项:W25Q64只能将1写为,不能将0写为1。 函数参数: u32 addr---写入数据的起始地址 u8 *buff---写入的数据 u32 len---长度 */ void W25Q64_WriteByteDataNoCheck(u32 addr,u8 *buff,u32 len) { u32 page_remain=256-addr%256; //计算当前页还可以写下多少数据 if(len<=page_remain) //如果当前写入的字节长度小于剩余的长度 { page_remain=len; } while(1) { W25Q64_PageWrite(addr,buff,page_remain); if(page_remain==len)break; //表明数据已经写入完毕 buff+=page_remain; //buff向后偏移地址 addr+=page_remain; //起始地址向后偏移 len-=page_remain; //减去已经写入的字节数 if(len>256)page_remain=256; //如果大于一页,每次就直接写256字节 else page_remain=len; } } /* 函数功能:指定位置写入指定个数的数据,考虑擦除问题,完善代码 函数参数: u32 addr---写入数据的起始地址 u8 *buff---写入的数据 u32 len---长度 说明:擦除的最小单位扇区,4096字节 */ static u8 W25Q64_READ_WRITE_CHECK_BUFF[4096]; void W25Q64_WriteByteData(u32 addr,u8 *buff,u32 len) { u32 i; u32 len_w; u32 sector_addr; //存放扇区的地址 u32 sector_move; //扇区向后偏移的地址 u32 sector_size; //扇区大小。(剩余的空间大小) u8 *p=W25Q64_READ_WRITE_CHECK_BUFF;//存放指针 sector_addr=addr/4096; //传入的地址是处于第几个扇区 sector_move=addr%4096; //计算传入的地址存于当前的扇区的偏移量位置 sector_size=4096-sector_move; //得到当前扇区剩余的空间 if(len<=sector_size) { sector_size=len; //判断第一种可能性、一次可以写完 } while(1) { W25Q64_ReadByteData(addr,p,sector_size); //读取剩余扇区里的数据 for(i=0;i<sector_size;i++) { if(p[i]!=0xFF)break; } if(i!=sector_size) //判断是否需要擦除 { W25Q64_ClearSector(sector_addr*4096); } // for(i=0;i<len;i++) // { // W25Q64_READ_WRITE_CHECK_BUFF[i]=buff[len_w++]; } // W25Q64_WriteByteDataNoCheck(addr,W25Q64_READ_WRITE_CHECK_BUFF,sector_size); W25Q64_WriteByteDataNoCheck(addr,buff,sector_size); if(sector_size==len)break; addr+=sector_size; //向后偏移地址 buff+=sector_size ;//向后偏移 len-=sector_size; //减去已经写入的数据 sector_addr++; //校验第下个扇区 if(len>4096) //表明还可以写一个扇区 { sector_size=4096;//继续写一个扇区 } else { sector_size=len; //剩余的空间可以写完 } } }
4.2 STM32采用硬件SPI读写W25Q64示例代码
#include "spi.h" /* 函数功能:SPI初始化(模拟SPI) 硬件连接: MISO--->PB14 MOSI--->PB15 SCLK--->PB13 */ void SPI_Init(void) { /*1. 开时钟*/ RCC->APB2ENR|=1<<3; //PB /*2. 配置GPIO口模式*/ GPIOB->CRH&=0x000FFFFF; GPIOB->CRH|=0x38300000; /*3. 上拉*/ SPI_MOSI=1; SPI_MISO=1; SPI_SCLK=1; } /* 函数功能:SPI读写一个字节 */ u8 SPI_ReadWriteOneByte(u8 data_tx) { u8 data_rx=0; //存放读取的数据 u8 i; for(i=0;i<8;i++) { SPI_SCLK=0; //准备发送数据 if(data_tx&0x80)SPI_MOSI=1; else SPI_MOSI=0; data_tx<<=1; //依次发送最高位 SPI_SCLK=1; //表示主机数据发送完成,表示从机发送完毕 data_rx<<=1; //表示默认接收的是0 if(SPI_MISO)data_rx|=0x01; } return data_rx; } #include "W25Q64.h" /* 函数功能:W25Q64初始化 硬件连接: MOSI--->PB15 MISO--->PB14 SCLK--->PB13 CS----->PB12 */ void W25Q64_Init(void) { /*1. 开时钟*/ RCC->APB2ENR|=1<<3; //PB /*2. 配置GPIO口模式*/ GPIOB->CRH&=0xFFF0FFFF; GPIOB->CRH|=0x00030000; W25Q64_CS=1; //未选中芯片 SPI_Init(); //SPI初始化 } /* 函数功能:读取芯片的ID号 */ u16 W25Q64_ReadID(void) { u16 id; /*1. 拉低片选*/ W25Q64_CS=0; /*2. 发送读取ID的指令*/ SPI_ReadWriteOneByte(0x90); /*3. 发送24位的地址-0*/ SPI_ReadWriteOneByte(0); SPI_ReadWriteOneByte(0); SPI_ReadWriteOneByte(0); /*4. 读取芯片的ID*/ id=SPI_ReadWriteOneByte(0xFF)<<8; id|=SPI_ReadWriteOneByte(0xFF); /*5. 拉高片选*/ W25Q64_CS=1; return id; } /* 函数功能:检测W25Q64状态 */ void W25Q64_CheckStat(void) { u8 stat=1; while(stat&1<<0) { W25Q64_CS=0; //选中芯片 SPI_ReadWriteOneByte(0x05); //发送读状态寄存器1指令 stat=SPI_ReadWriteOneByte(0xFF); //读取状态 W25Q64_CS=1; //取消选中芯片 } } /* 函数功能:页编程 说 明:一页最多写256个字节。 写数据之前,必须保证空间是0xFF 函数参数: u32 addr:页编程起始地址 u8 *buff:写入的数据缓冲区 u16 len :写入的字节长度 */ void W25Q64_PageWrite(u32 addr,u8 *buff,u16 len) { u16 i; W25Q64_Enabled(); //写使能 W25Q64_CS=0; //选中芯片 SPI_ReadWriteOneByte(0x02); //页编程指令 SPI_ReadWriteOneByte(addr>>16); //24~16地址 SPI_ReadWriteOneByte(addr>>8); //16~8地址 SPI_ReadWriteOneByte(addr); //8~0地址 for(i=0;i<len;i++) { SPI_ReadWriteOneByte(buff[i]); //8~0地址 } W25Q64_CS=1; //取消选中芯片 W25Q64_CheckStat(); //检测芯片忙状态 } /* 函数功能:连续读数据 函数参数: u32 addr:读取数据的起始地址 u8 *buff:读取数据存放的缓冲区 u32 len :读取字节的长度 */ void W25Q64_ReadByteData(u32 addr,u8 *buff,u32 len) { u32 i; W25Q64_CS=0; //选中芯片 SPI_ReadWriteOneByte(0x03); //读数据指令 SPI_ReadWriteOneByte(addr>>16); //24~16地址 SPI_ReadWriteOneByte(addr>>8); //16~8地址 SPI_ReadWriteOneByte(addr); //8~0地址 for(i=0;i<len;i++)buff[i]=SPI_ReadWriteOneByte(0xFF); W25Q64_CS=1; //取消选中芯片 } /* 函数功能:擦除一个扇区 函数参数: u32 addr:擦除扇区的地址范围 */ void W25Q64_ClearSector(u32 addr) { W25Q64_Enabled(); //写使能 W25Q64_CS=0; //选中芯片 SPI_ReadWriteOneByte(0x20); //扇区擦除指令 SPI_ReadWriteOneByte(addr>>16); //24~16地址 SPI_ReadWriteOneByte(addr>>8); //16~8地址 SPI_ReadWriteOneByte(addr); //8~0地址 W25Q64_CS=1; //取消选中芯片 W25Q64_CheckStat(); //检测芯片忙状态 } /* 函数功能:写使能 */ void W25Q64_Enabled(void) { W25Q64_CS=0; //选中芯片 SPI_ReadWriteOneByte(0x06); //写使能 W25Q64_CS=1; //取消选中芯片 } /* 函数功能:指定位置写入指定个数的数据,不考虑擦除问题 注意事项:W25Q64只能将1写为,不能将0写为1。 函数参数: u32 addr---写入数据的起始地址 u8 *buff---写入的数据 u32 len---长度 */ void W25Q64_WriteByteDataNoCheck(u32 addr,u8 *buff,u32 len) { u32 page_remain=256-addr%256; //计算当前页还可以写下多少数据 if(len<=page_remain) //如果当前写入的字节长度小于剩余的长度 { page_remain=len; } while(1) { W25Q64_PageWrite(addr,buff,page_remain); if(page_remain==len)break; //表明数据已经写入完毕 buff+=page_remain; //buff向后偏移地址 addr+=page_remain; //起始地址向后偏移 len-=page_remain; //减去已经写入的字节数 if(len>256)page_remain=256; //如果大于一页,每次就直接写256字节 else page_remain=len; } } /* 函数功能:指定位置写入指定个数的数据,考虑擦除问题,完善代码 函数参数: u32 addr---写入数据的起始地址 u8 *buff---写入的数据 u32 len---长度 说明:擦除的最小单位扇区,4096字节 */ static u8 W25Q64_READ_WRITE_CHECK_BUFF[4096]; void W25Q64_WriteByteData(u32 addr,u8 *buff,u32 len) { u32 i; u32 sector_addr; //存放扇区的地址 u32 sector_move; //扇区向后偏移的地址 u32 sector_size; //扇区大小。(剩余的空间大小) u8 *p=W25Q64_READ_WRITE_CHECK_BUFF;//存放指针 sector_addr=addr/4096; //传入的地址是处于第几个扇区 sector_move=addr%4096; //计算传入的地址存于当前的扇区的偏移量位置 sector_size=4096-sector_move; //得到当前扇区剩余的空间 if(len<=sector_size) { sector_size=len; //判断第一种可能性、一次可以写完 } while(1) { W25Q64_ReadByteData(addr,p,sector_size); //读取剩余扇区里的数据 for(i=0;i<sector_size;i++) { if(p[i]!=0xFF)break; } if(i!=sector_size) //判断是否需要擦除 { W25Q64_ClearSector(sector_addr*4096); } W25Q64_WriteByteDataNoCheck(addr,buff,sector_size); if(sector_size==len)break; addr+=sector_size; //向后偏移地址 buff+=sector_size ;//向后偏移 len-=sector_size; //减去已经写入的数据 sector_addr++; //校验第下个扇区 if(len>4096) //表明还可以写一个扇区 { sector_size=4096;//继续写一个扇区 } else { sector_size=len; //剩余的空间可以写完 } } }
-
SPI读写串行FLASH(W25Q64)
2022-03-25 15:18:26文章目录1、SPI协议1、硬件连接2、通讯时序2、W25Q64介绍3、SPI读写驱动编写4、源码 1、SPI协议 SPI 协议是由摩托罗拉公司提出的通讯协议(Serial Peripheral Interface),即串行外围设备接口,是一种高速全双工的... -
W25Q64Flash芯片
2020-05-20 11:38:04W25Q64Flash芯片STM32操作 通讯方式:SPI通讯 大小:8M(Byte) (128块(Block),每块64K字节,每块16个扇区(Sector),每个扇区4K字节,每个扇区16页,每页256个 ... -
SPI协议学习Cubmx——读写Flash W25Q64
2022-04-30 17:21:16芯片型号后两位表示芯片容量,例如 W25Q64 的 64 就是指 64Mbit 也就是 8M 的容量。它的 CS/CLK/DIO/DO 引脚分别连接到了 STM32 对应的 SPI 引脚 NSS/SCK/MOSI/MISO 上,其中 STM32 的 NSS 引脚虽然是其片上 SPI ... -
W25Q64Flash芯片STM32操作
2018-07-31 16:25:271、W25Q64Flash芯片介绍 通讯方式:SPI通讯 大小:8M(Byte) (128块(Block),每块64K字节,每块16个扇区(Sector),每个扇区4K字节,每个扇区16页,每页256个 字节) 特点:Flash芯片内的数据只能由1变0,... -
手把手系列--华邦W25Q64JV Flash操作指南
2021-12-04 11:07:34前段时间淘了一个STM32H750XBH6_ArtPi开发板,板载两颗华邦的Flash芯片,一颗为W25Q64JV(8Mbytes),通过STM32H750XBH6的QUASDSPI控制用于XIP;一颗为W25Q128JV(16Mbytes)用于数据存储。 那么本篇的目的就是给... -
Cortex-M4-SPI总线-读写W25Q64(二)
2022-04-10 16:12:29STM32F407--SPI1读写w25q64 -
W25Q80, W25Q16, W25Q32数据手册
2020-07-02 00:08:051. W25Q80 (8M-bit)、W25Q16 (16M-bit)和W25Q32 (32M-bit)串行闪存为空间、引脚和电源有限的系统提供了存储解决方案。25Q系列提供的灵活性和性能远远超过普通的串行闪存设备。他们是理想的代码隐藏到RAM,执行代码... -
Linux驱动开发-编写W25Q64(Flash)驱动
2022-03-21 09:28:47W25Q64是一颗SPI接口的Flash存储芯片,是华邦W25QXX系列里的一个具体型号,这个系列里包含了W25Q16,W25Q32,W25Q64,W5Q128等等。编程代码逻辑都差不多,主要是容量的区别。 -
剖析STM32F103读写W25Q64
2020-07-24 16:20:49最近使用STM32F103+W25Q64+USB+FATFS做了一个U盘设备。程序已经调试完成了,现在重新梳理一下知识再做一个记录。 STM32F103+USB是根据官方demo修改的,这一部分没啥可说的。关于FATFS的移植下一篇文章介绍。本篇文章... -
学习日记——W25Q64 FLASH—QSPI
2020-02-15 21:55:07W25Q64串行FLASH基础知识 大小:8M(Byte)(128块(Block),每块64K字节,每块16个扇区(Sector),每个扇区4K字 节,每个扇区16页,每页256个字节) 特点:Flash芯片内的数据只能由1变0,不能由0变1。 W25Q64... -
stm32 W25QXX系列驱动 W25Q80 W25Q16 W25Q32 W25Q64 W25Q128 W25Q256
2021-05-03 11:38:45W25QXX系列驱动,支持W25Q80,W25Q32,W25Q128,W25Q256,W25Q64,W25Q16,详细中文注释 -
dm8148中在原有支持W25Q64BVSSIG(spi flash)的基础上修改成支持W25Q128FVSIG(spi flash)
2019-09-05 16:05:45dm8148板自带的为8M的spi flash,型号为W25Q64BVSSIG,由于项目需要,改成16M的spi flash,型号为W25Q128FVSIG uboot上支持: 1.在u-boot/drivers/mtd/spi/winbond.c文件中增加 #define WINBOND_ID_W25Q128 0x4018... -
STM32CubeMX | 30-使用硬件SPI读写FLASH(W25Q64)
2020-07-26 11:13:45本篇详细的记录了如何使用STM32CubeMX配置 STM32G070RBT6 的硬件SPI外设与 SPI Flash 通信(W25Q64)。 1. 准备工作 硬件准备 开发板 首先需要准备一个开发板,这里我准备的是STM32G070RB的开发板 SPI Flash ... -
【STM32Cube-18】使用硬件QSPI读写SPI Flash(W25Q64)
2019-09-04 12:18:33本篇详细的记录了如何使用STM32CubeMX配置STM32L431RCT6的硬件QSPI外设与 SPI Flash 通信(W25Q64)。 -
Linux下SPI Flash-W25Q64驱动调试
2019-10-12 14:15:35W25Q64将8M字节的容量分为128个块(block),每个块大小为64K字节,每个块又分为16个扇区(sector),每个扇区4K字节。W25Q64的最小擦除单位为一个扇区,也就是每次必须擦除4K个字节。所以,这需要给W25Q64开辟一... -
使用cubemx工具的STM32对外部flash(W25Q64)的简单编程
2022-04-23 23:19:28全双工,同步的通信总线,并且在芯片的管脚上只占用四根线,节约了芯片的管脚,同时为PCB的布局上节省空间,提供方便,正是出于这种简单易用的特性,越来越多的芯片集成了这种通信协议,比如flash闪存芯片W25Q64等。... -
STM32CubeMx开发之路—13使用SPI读写W25Q64
2019-11-25 10:13:06STM32CubeMx开发之路—13使用SPI读写W25Q64 运行环境 Windows10 STM32CubeMX Version 5.2.0 Keil5(MDK5) Version 5.28.0.0 简介 本例程主要讲解如何使用硬件IIC读写24C02 STM32CubeMx基本配置 基础配置过程请... -
W25Q64 的 QSPI 模式 问题
2020-06-18 21:01:03由于工作需要, 使用了 W25Q64的 qspi模式, 一开始打样, 焊接了几块, 有2块不识别,没在意, 换新的 重新焊上就好了,于是 认为 某种原因导致 芯片损坏, 当时没想太多, 因为盒子里也有拆下来的旧件, 于是就... -
W25Q256FV-芯片手册.pdf
2020-05-22 17:11:55本手册提供W25Q256FV封装信息,引脚定义,功能描述,寄存器配置,电气性能等完整信息。该有的都在这了,英文版。 -
STM32驱动SPI FLASH(W25Q64)
2022-02-07 08:57:54@[TOC](STM32驱动SPI FLASH(W25Q64)) 原文链接:https://blog.csdn.net/wwt18811707971/article/details/77756312 注:博客所涉及的关于 stm32 的代码,均在仓库【stm32f013_study】下,包括底层驱动和应用测试代码...