stm32(SPI读写W25Q18)

news2025/1/10 23:50:08

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;/* 下一个扇区可以写完了 */
            }
        }
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/751972.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Nginx】rewrite简单使用

前言 没有对正式的rewrite进行了解&#xff0c;为了能快速了解它是干嘛怎么用&#xff0c;找了一些有例子的博客进行简单学习了一下&#xff1b;由于每次看的间隔有点大&#xff0c;老忘记&#xff0c;这回专门写个超级快速理解的例子。 PS&#xff1a;下面的解释可能会不太对…

Serverless和EDA是绝配,亚马逊云科技CTO Werner表示需要用开放心态来重新审视架构

前一段有个很火的博客&#xff0c;讲的是一家全球流媒体企业的监测系统从Serverless微服务改成了单体&#xff0c;成本居然降低了90%&#xff01;这一下子可在网上炸锅了&#xff0c;特别是一些看不惯微服务的、单体应用的拥趸&#xff0c;更是坐不住了。但这并不像吃瓜群众看到…

ret2syscall-执行系统调用

题目 checksec一下&#xff1a; 32位程序&#xff0c;没有canary、PIE&#xff0c;开了NX&#xff0c;放入IDA看一下&#xff1a; 有gets&#xff0c;可以溢出&#xff0c;不过没有system函数&#xff0c;也不能执行shellcode&#xff0c;查看字符发现有/bin/sh&#xff0c;那就…

消息中间件系列 - RocketMQ

前言 本内容仅用于个人学习笔记&#xff0c;如有侵扰&#xff0c;联系删除 【尚硅谷】RocketMQ教程丨深度掌握MQ消息中间件_哔哩哔哩_bilibili 第一章 RocketMQ概述 一、MQ概述 1 、MQ简介 MQ&#xff0c;Message Queue&#xff0c;是一种提供消息队列服务的中间件&#…

SpringBoot(七)Filter的使用

思考一个问题&#xff0c;服务端对于客户端的请求&#xff0c;真的应该照单全收吗&#xff1f;不是的。比如拿我们之前实现的用户注册功能来看&#xff0c;如果用户的昵称带了一些不友好的字母或汉字&#xff0c;你是给他过滤掉呢还是让他注册呢&#xff1f;毫无疑问&#xff0…

Zint-条码生成软件-命令行调用-一维条码和二维条码

文章目录 1.Zint简介2.术语3.命令行3.1.输入数据3.2.直接输出3.3.选择条码类型3.4.调整高度3.5.调整空白3.6.添加边界条和框3.7.颜色设置3.8.旋转3.9.调整图像尺寸 4.界面5.总结 1.Zint简介 Zint项目旨在提供一个完整的跨平台开源条形码生成解决方案。该软件包目前包括一个基于…

零拷贝是如何实现的

零拷贝是如何实现的 零拷贝&#xff08;Zero-copy&#xff09;是一种优化技术&#xff0c;用于在数据传输过程中减少数据的拷贝次数&#xff0c;从而提高数据传输的效率和性能。传统的数据传输涉及多次内存拷贝操作&#xff0c;而零拷贝通过减少或避免这些拷贝操作来实现性能优…

HDC.Cloud 2023|邂逅AI,华为云CodeArts铸就研发效能10倍提升

2023年7月7日-9日&#xff0c;华为开发者大会2023&#xff08;Cloud&#xff09;在东莞松山湖隆重举行。期间&#xff0c;华为云主办了以“AI‘邂逅’一站式软件开发&#xff0c;CodeArts以10倍效能“绘”企业应用远景”为主题的分论坛。 华为云PaaS服务产品部副部长汪维敏携一…

用户注册倒计时案例

代码&#xff1a; <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>Document</title> </hea…

ORA-00845: MEMORY_TARGET not supported on this system

memory_target的值需要比tmpfs的小。 解决&#xff1a; vim /usr/local/oracle/admin/orcl/pfile/init.ora.6132023154657 强制加载init.ora.6132023154657文件启动数据库 SQL> startup force pfile/usr/local/oracle/admin/orcl/pfile/init.ora.6132023154657;

【原生基础篇】JavaScript中常用的数据输出方式解析

​ 在 JavaScript 中&#xff0c;一般使用如下几种方式进行数据的输出&#xff1a; JavaScript 基础到高级 Canvas游戏开发 原生JavaScipt案例合集 JavaScript DOM基础 1. 在浏览器的控制台输出 浏览器F12打开浏览器控制台(一般前端开发人员必备浏览器为谷歌浏览器&#xff0…

ArcGIS一张图的制作过程

来源&#xff1a;GIS荟 数据 首先&#xff0c;第一步是数据的准备和处理&#xff0c;这里没什么好说的。 使用的所有数据都是在网上下载的 OSM 数据。 第一步 主视觉 为了更好的说明该地图的完整操作过程&#xff0c;同时又要有条理、比较好理解&#xff0c;所以我采用拆分…

matlab处理数据

Matlab异常值处理https://blog.csdn.net/weixin_57345774/article/details/126965835?csdn_share_tail%7B%22type%22%3A%22blog%22%2C%22rType%22%3A%22article%22%2C%22rId%22%3A%22126965835%22%2C%22source%22%3A%22qq_53011270%22%7D&fromshareblogdetail 异常值识别和…

SAP从放弃到入门系列之WIP Batch(Work-in-Process)-Part2

文章目录 一、前言二、系统操作2.1 数据设置2.2 业务数据2.3 相关的NOTE&#xff1a;1473025 - FAQ: WIP 批次 三、其他 一、前言 这个测试验证的过程真的是很难受&#xff0c;由于资料太少&#xff0c;查到的SAP官方的文档也描述的非常粗略。因为我从一开始就是有目的的去验证…

性能测试工具 jmeter 录制脚本,传递 cookie,循环执行接口

目录 前言&#xff1a; 代理录制脚本 循环重复添加接口 登录并传递 cookie 给新建产品接口 循环执行脚本 前言&#xff1a; 在使用JMeter进行性能测试时&#xff0c;录制脚本是一种常用的方法。录制脚本可以帮助你捕获和重放用户与应用程序之间的交互&#xff0c;以模拟真…

【深度学习笔记】训练 / 验证 / 测试集

本专栏是网易云课堂人工智能课程《神经网络与深度学习》的学习笔记&#xff0c;视频由网易云课堂与 deeplearning.ai 联合出品&#xff0c;主讲人是吴恩达 Andrew Ng 教授。感兴趣的网友可以观看网易云课堂的视频进行深入学习&#xff0c;视频的链接如下&#xff1a; 神经网络和…

基于CBAM-CNN卷积神经网络预测研究(Python代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

Linux SSH访问虚拟机失败 Restarting network (via systemctl)

访问SSH访问服务器连接失败,重启network 失败 Restarting network (via systemctl): Job for network.service failed because the control process exited with error code. See “systemctl status network.service” and “journalctl -xe” for details.关闭 NetworkManage…

mayavi 远程可视化

似乎最近总是在处理远程可视化的问题 首先应该像配置open3d一样完成x服务器的配置&#xff0c;这里给出我自己的链接。 这时候我们进行安装&#xff0c;给出官网链接 pip install mayavi pip install PyQt5不出意外的话&#xff0c;将会出意外。 会提示如下报错qt.qpa.plugi…

关于海外的应用商店优化策略

关于应用商店优化&#xff0c;我们可以通过投放应用广告&#xff0c;或者是遵循ASO的实践&#xff0c;来优化应用程序从而获得自然流量。 如果开始进行应用商店优化&#xff0c;我们需要准备一份可用于日常的ASO清单。 建立清晰的应用商店优化策略&#xff0c;研究并了解市场…