历史上的今天
返回首页

历史上的今天

今天是:2024年10月10日(星期四)

正在发生

2020年10月10日 | 【STM8】外挂存储器W25Q16

2020-10-10 来源:eefocus

文章里面提到的页编程,就是写数据了,因为这是英文直译的结果(PageProgram)

 

为了测试这个外挂Flash存储器,我在淘宝买了一个小板,3元不到

其实也可以直接买芯片回来自己接,反正没几个元件

这个芯片是用SPI通讯的

我找不到没水印的图片,暂时先用W25Q128的

不过他俩板子长得一模一样,元件也一样。除了芯片型号

板子上的LED和电阻串联,上电后LED就亮,没别的意思

电容是滤波用的,它紧靠芯片的VCC引脚

 

另外附上两个链接,这是我之前写的博客,是关于『STM8开发环境』和『STM8 - SPI通讯』,这篇博客的测试基础,是建立在STM8上的

关于如何接线,SPI通讯这篇博客有提到,如果有需要可以观看

STM8开发环境:https://www.cnblogs.com/PureHeart/p/10824556.html

STM8 - SPI通讯:https://www.cnblogs.com/PureHeart/p/10749264.html

 

SPI相关知识有了,就可以开始了

开始之前,还是先介绍一下大纲

【W25Q16芯片介绍】:芯片命名规则、芯片引脚图、引脚功能介绍

【W25Q16指令】:官方定义的指令,还有时序图介绍

【W25Q16初步测试】:执行其中一个指令(读取芯片ID),看看执行的效果,以此确认步骤是否正确,如果这一步都不正确,就不用谈最主要的读和写吧?

【W25Q16状态寄存器】:寄存器的一些状态,例如芯片是不是在忙、是不是处于保护状态、保护的区域、是否可写状态。。。等等

【W25Q16读、写、擦除】:读、写、擦除相关代码

 

【W25Q16芯片介绍】

 

应该很好理解,像W25Q02系列,就是2G的Flash,下方的红字也提醒了,这是2G bit,像我们下载的电影、音乐,这些都是byte为单位的,设计的时候要考虑一下

另外这是华邦的官网,选型方面,或是datasheet,都可以在这里找到:https://www.winbond.com/hq/product/code-storage-flash-memory/serial-nor-flash/?__locale=zh

 

不在官网找的话,我也有上传到我的度盘:https://pan.baidu.com/s/1bHmk4o1C3I5JweayWsFGqw

提取码:iq4j

 

 W25Q16的引脚如下

统一说明:前方有斜线的/,例如/CS,这个斜线代表低电平使能

【/CS】:片选引脚,低电平呢芯片工作,高电平芯片就罢工,当然,别想着一劳永逸这种事,直接把它接GND,我就吃到苦头了,这引脚请务必接GPIO

【DO】:数据输出

【/WP】:写保护,低电平呢只能读,高电平就随你读写

【GND】:接地

【DI】:数据输入(接收外来的指令)

【CLK】:时钟

【/HOLD】:数据暂停控制,低电平代表暂停,高电平工作,通常用于多个设备共享一个SPI,如果只有一主一从,可以把这引脚接VCC

【VCC】:2.7~3.6V

 

另外,这个芯片可以支持『双输出』和『四输出』,可以提升读数据的速度

具体的方法是把其他引脚的功能都改为输出(IO1、2、3、4)

就好比大家的车速都一样,道路有两条的情况下,一定比只有一条道路,处理车流量来的快

 

在引脚图的上方,有芯片的介绍,其中会看到104MHz、208MHz、416MHz

分别是SPI单输出、双输出和四输出

遗憾的是STM8的SPI,最快也只有10MHz左右,想要处理双输出和四输出,是不可能的

不过对于我的项目来说,这已经足够了

 

 

 【W25Q16指令】

 

 

 

下面介绍写使能的时序图,但是在『W25Q16初步测试』的环节中,会读取JEDEC ID(指令发送0x9F),最终看看W25Q16有没有反馈『生产商ID』和『芯片ID』给我

给下降沿的原因,在介绍引脚图时,片选引脚/CS已经说明了,下达每个指令之前,必须给下降沿

DI,也就是W25Q16接收的数据,0x06,文章往上拉找到指令的图片,找对应的位置,0x06就是写使能

DO,因为这个指令不需要反馈数据给主机,所以是高阻态

 

 

【W25Q16初步测试】

 

我是透过Uart来打印数据的,图片左上有示意图

用示意图上的1234来表示流程,就是『1 234 234 234 234 234 234 234 234 234 234』

234出现了十次,因为在『SPI接收中断』里面,判断count < 10

除了第一个『2』是指令(0x9F)以外,后面所有的『2』全部都是伪字节(0xFE),这是为了制造时钟给从机,在我另一篇博客有提到

下面贴上完整代码,另外附上链接,需要代码的朋友也可以下载

地址:https://pan.baidu.com/s/1Or5cWBaKLYl2-F-4qikSrA

提取码:4pbw


#include"iostm8s103F3.h"

#include "W25Qxx.h"

 

typedef unsigned char u8;

typedef unsigned short int u16;

typedef unsigned int u32;

 

void UART1_sendchar(unsigned char c);

void SPI_sendchar(unsigned char c);

 

u8 count = 0;

 

/* ====================================== */

/* ============ 【Uart】init ============ */

/* ====================================== */

void Init_UART1(void)

{

    UART1_CR1 = 0x00;

    UART1_CR2 = 0x00;

    UART1_CR3 = 0x00;

    // 设置波特率,必须注意以下几点:

    // (1) 必须先写BRR2

    // (2) BRR1存放的是分频系数的第11位到第4位,

    // (3) BRR2存放的是分频系数的第15位到第12位,和第3位

    // 到第0位

    // 例如对于波特率位9600时,分频系数=2000000/9600=208

    // 对应的十六进制数为00D0,BBR1=0D,BBR2=00

 

    UART1_BRR2 = 0x00;

    UART1_BRR1 = 0x0d;

 

    UART1_CR2 = 0x2c; // 允许接收,发送,开接收中断

}

 

/* ====================================== */

/* =========== 【Uart】发送函数 ========= */

/* ====================================== */

void UART1_sendchar(unsigned char c)

{

    while((UART1_SR & 0x80) == 0x00); // 等待发送缓冲区为空

    UART1_DR = c;

}

 

/* ====================================== */

/* =========== 【Uart】接收中断 ========= */

/* ====================================== */

#pragma vector= UART1_R_OR_vector//0x19

__interrupt void UART1_R_OR_IRQHandler(void)

{

    PC_ODR_ODR4 = 0; // 串口收到数据后进入中断,先给W25Qxx下降沿,等等透过SPI发送指令

    SPI_sendchar(UART1_DR); // 发送SPI数据(UART接收到什么就发什么),然后等待SPI中断,实现自发自收

}

 

/* ====================================== */

/* ============ 【SPI】init ============= */

/* ====================================== */

void Init_SPI(void)

{

    CLK_PCKENR1 |= 0x02; //打开SPI时钟

    /*PC6、PC5设置为输出,最大10MHz*/

    //PC_DDR = 0x60; // 用下方比较详细的写法

    //PC_CR1 = 0xe0; // 用下方比较详细的写法

    //PC_CR2 = 0x60; // 用下方比较详细的写法

     

    PC_DDR_DDR4 = 1;    // 配置PC4(/CS)端口为输出模式

    PC_CR1_C14 = 1;     // 配置PC4(/CS)端口为推挽输出模式

    PC_CR2_C24 = 1;     // 配置PC4(/CS)端口为高速率输出

     

    PC_DDR_DDR5 = 1;    // 配置PC5(SCK)端口为输出模式

    PC_CR1_C15 = 1;     // 配置PC5(SCK)端口为推挽输出模式

    PC_CR2_C25 = 1;     // 配置PC5(SCK)端口为高速率输出

     

    PC_DDR_DDR6 = 1;    // 配置PC6(MOSI)端口为输出模式

    PC_CR1_C16 = 1;     // 配置PC6(MOSI)端口为推挽输出模式

    PC_CR2_C26 = 1;     // 配置PC6(MOSI)端口为高速率输出

     

    PC_DDR_DDR7 = 0;    // 配置PC7(MISO)端口为输入模式

    PC_CR1_C17 = 1;     // 配置PC7(MISO)端口为弱上拉输入模式

    PC_CR2_C27 = 0;     // 禁止PC7(MISO)端口外部中断

     

    SPI_ICR_RXIE = 1; // 开启SPI中断接收

     

    // [7]先发MSB

    // [6]禁止SPI

    // [5][4][3]f_Master / 2

    // [2]主设备

    // [1]空闲时SCK保持低电平

    // [0]数据采样从第一个时钟沿开始

    SPI_CR1 = 0x04; /*MSB、1MHz、主设备、CPOL空闲为低、CPHA第一个时钟开始*/

     

    // [7]双线单向模式

    // [6]输入使能(只接收模式)

    // [5]CRC计算禁止

    // [4]下个发送数据来自Tx缓冲

    // [3]保留

    // [2]全双工(同时收发)

    // [1]使能软件从设备管理(不需要判断硬件CS位,节省一个引脚)

    // [0]主模式

    SPI_CR2 = 0x03; /*双线单向视距传输、CRC计算禁止、软件NSS、主模式*/

     

    SPI_CR1_SPE = 1; // 打开SPI

}

 

/* ====================================== */

/* =========== 【SPI】发送函数 ========== */

/* ====================================== */

void SPI_sendchar(unsigned char c)

{

    while(!(SPI_SR & 0x02));    // 等待发送缓冲区为空

    SPI_DR = c;                  // 将发送的数据写到数据寄存器

    //while(!(SPI_SR & 0x01));    // 等待接收缓冲区非空,这是轮询的方式,但是我想在中断来处理

    //UART1_sendchar(SPI_DR);

}

 

/* ====================================== */

/* =========== 【SPI】接收中断 ========== */

/* ====================================== */

#pragma vector=SPI_RXNE_vector

__interrupt void SPI_RXNE_IRQHandler(void)

{

    //RxBuf[cnt++]=SPI_DR;

    while(!(SPI_SR & 0x01));

    UART1_sendchar(SPI_DR); // 把SPI接收到的数据,透过UART,传回给USB转TTL小板

    count++;

    if(count < 10) SPI_sendchar(0xfe); // 发送伪字节

    else

    {

        count = 0;

        PC_ODR_ODR4 = 1; // 重新置为高电平,等待下一次的指令

    }

}

 

/* ====================================== */

/* ============== 【Main】 ============== */

/* ====================================== */

main()

{

    Init_UART1();

    Init_SPI();

    PC_ODR_ODR4 = 1; // 初始上电给高电平,后续W25Qxx在执行指令前,再给下降沿

     

    asm("rim"); // 开中断,sim为关中断

    while (1);

}


【W25Q16状态寄存器】

文章有点长,再说明一个寄存器就好了

先上一张图,这是状态寄存器里的内容

下面是寄存器内各个『位』的说明,另外『R』代表『只可读』,『W』代表『只可写』,『RW』代表『可读可写』

【BUSY】(R):芯片在忙的时候,状态=1,不忙时=0,什么时候在忙呢?执行『页编程』『任何一种擦除』『写状态』都是,芯片忙完这些事会自动清0

【WEL】(R):『写保护』位,执行写使能后,由芯片自动置1,芯片处于『写保护』时该位=0,写禁用状态发生在『通电时』『写禁止』『页编程』『任何一种擦除』和『写状态寄存器』

【BP0、1、2】(RW):这三位决定了需要保护的区域,例如一些固件,你不想后续被修改的东西,都可以保护。默认为0,另外,它和TB、SEC位有关。这里不做过多介绍,我的项目没有用到,还没研究,未来有时间再看看。

【TB】(RW):默认为0,可以决定是『顶部』或是『底部』需要保护,例如有100个保险柜,你要保护前10个,或是保护最后20个,具体位置请参考上面的图片。这里不做过多介绍,我的项目没有用到,还没研究,未来有时间再看看。

【SEC】:非易失性扇区保护位。这里不做过多介绍,我的项目没有用到,还没研究,未来有时间再看看。

【SRP0、1】(RW):状态寄存器保护位,默认为0。

       ❶ SPR=0:不能控制状态寄存器的『禁止写』

       ❷ SPR=1、引脚/WP=低电平:『写状态寄存器』的指令失效

       ❸ SPR=1、引脚/WP=高电平:可以执行『写状态寄存器』的指令

【SUS】(R):挂起状态位是状态寄存器,在执行擦除挂起(75h)指令后设置为1。SUS状态位通过擦除恢复(7ah)指令以及断电、通电循环清除为0。

【QE】(RW):四输出使能位是状态寄存器。当qebit设置为0状态(出厂默认值)时,/wp pinand/hold被启用。当qebit设置为1时,将启用四个io2和io3引脚,并禁用/wp和/hold功能。

 

Warning:如果在标准SPI或双SPI操作期间/wp或/hold引脚直接连接到电源或接地,则QE位不应设置为1。

 

看到这里的朋友,先和你们说声抱歉,读取状态寄存器我真的没有试出来,每次读取都是0x00 0x00 0x00 0x00。。。

我尝试执行『写使能』,然后读取状态寄存器,还是0x00 0x00 0x00 0x00。。。

我再尝试执行『写禁止』,然后读取状态寄存器,还是0x00 0x00 0x00 0x00。。。

照理说,『写使能』和『写禁止』应该会改变『WLE』这一位,结果没有,真是百思不得其解(读JEDEC ID都正常,所以不是我接线,或是SPI通讯的问题)(JEDEC上面说过了,是生产商ID)

唉。。。

 

【W25Q16读、写、擦除】

在说明读和写之前,先说明一下Flash的物理特性:Flash只能写0,不能写1

上一张图,来解释这个特性

 

有人说,写的时候不用擦除,那是因为特殊情况

第一天,Flash的值是0xFF(1111 1111),我写入0xF0(1111 0000)【高4位都是1➜1,没有影响】【低4位是1➜0,由于是写0的动作,所以无需擦除】

第二天,Flash的值是0xF0(1111 0000),我写入0x00(0000 0000)【高4位是1➜0,由于是写0的动作,所以无需擦除】【低4位是0➜0,这也是写0动作,无需擦除】

这些情况还不需要擦除,除非到某一天,你想写一个数据,不管这8位的哪一位要变成1,那么就必须擦除了

理解这个特性,能有效的增加Flash的寿命,在这篇博客,引脚图的上方,有芯片介绍,里面有一段英文『More than 100,000 erase/write cycles』

芯片能让你擦除10万次

具体要不要让你的程序复杂些,但是能让芯片寿命增长,就要自行斟酌了

 

讲了这么多,下面终于可以开始重头戏了

这个读写的代码,基本上和上面的读ID代码类似,只增加了两个变量,和修改两个中断

【1】定义两个变量,testAddress、command

【2】串口接收中断

【3】SPI接收中断


u32 testAddress = 0x000000;

u8 command = 0; // 【0:写使能、写禁止、芯片擦除】【1:写】【2:读】

 

/* ====================================== */

/* =========== 【Uart】接收中断 ========= */

/* ====================================== */

#pragma vector= UART1_R_OR_vector//0x19

__interrupt void UART1_R_OR_IRQHandler(void)

{

    PC_ODR_ODR4 = 0; // 串口收到数据后进入中断,先给W25Qxx下降沿,等等透过SPI发送指令

     

    if (UART1_DR == 0x01) // 页编程

    {

        command = 1;

        SPI_sendchar(PageProgram);

    }

    else if (UART1_DR == 0x02) // 读数据

    {

        command = 2;

        SPI_sendchar(ReadData);

    }

    else if (UART1_DR == 0x03) // 写使能

    {

        command = 0;

        SPI_sendchar(WriteEnable);

    }

    else if (UART1_DR == 0x04) // 写禁止

    {

        command = 0;

        SPI_sendchar(WriteDisable);

    }

    else if (UART1_DR == 0x05) // 芯片擦除

    {

        command = 0;

        SPI_sendchar(EraseChip);

    }

}

 

推荐阅读

史海拾趣

Helium公司的发展小趣事

随着Helium在物联网领域的不断突破和发展,其市场认可度逐渐提升。在2021年,Helium通过Token销售完成了1.11亿美元的融资;次年年初,又以12亿美元估值完成了2亿美元的D轮融资。这些融资的成功不仅为Helium提供了充足的资金支持,也进一步验证了其在物联网和区块链领域的商业价值和发展潜力。同时,Helium还吸引了众多明星机构和投资者的青睐,为其未来的发展奠定了坚实的基础。

宝乘(baocheng)公司的发展小趣事

随着产品质量的提升和技术的不断创新,宝乘公司开始积极拓展市场。公司与多家知名企业建立了战略合作关系,共同推动半导体功率器件在LED照明、电源、消费类电子等多个领域的应用。同时,宝乘还积极参加国内外电子行业的展览和交流活动,与同行交流学习,不断提升自身的竞争力。

Conditioning Semiconductor Devices Corp公司的发展小趣事

随着半导体行业的竞争加剧,价格战和技术更新速度不断加快。CSDC面临着巨大的市场压力。为了应对这些挑战,公司决定调整战略,专注于高端市场的开发。通过加大研发投入,提升产品质量和性能,CSDC逐渐在高端市场站稳了脚跟,实现了业务的稳步增长。

中环(Central)公司的发展小趣事

随着公司业务的不断拓展,中环在保持主业优势的同时,也积极开展多元化发展。2014年,公司开始开展光伏电站开发业务,进一步延伸了产业链。同时,公司还成立了东方环晟,开拓了组件业务。这些举措不仅为公司带来了新的增长点,也增强了其综合竞争力。

AIC [Analog Intergrations Corporation]公司的发展小趣事

随着公司业务的不断拓展,中环在保持主业优势的同时,也积极开展多元化发展。2014年,公司开始开展光伏电站开发业务,进一步延伸了产业链。同时,公司还成立了东方环晟,开拓了组件业务。这些举措不仅为公司带来了新的增长点,也增强了其综合竞争力。

迪一电子公司的发展小趣事

迪一电子公司成立于2006年,最初只是一家规模较小的半导体电子元器件制造企业。在创业初期,公司面临着资金紧张、技术落后和市场竞争激烈等多重困难。然而,创始人李政坚信半导体行业的发展潜力,带领团队不断研发新产品,提高产品质量,逐渐在市场上赢得了一席之地。

问答坊 | AI 解惑

MP3充电器原理与维修

MP3充电器是最易损坏的配件之一。笔者维修多款充电器后发现,它们基本上都是采用开关电源电路,电路结构大同小异。本文以市面上最常见的昕潮TJ-01型充电器为例,简述其工作原理与常见故障检修,电路见图1。工作原理 (1)开关振荡电路市电经D1~D4整 ...…

查看全部问答>

求一份关于串联和并联谐振的资料

最近在用线圈耦合做无线识别,用12M做载波,求谐振资料。 有功放或放大或滤波图的来几张,多谢。 芯片也行 xuchaoda@126.com…

查看全部问答>

标准数字电路 54-74HC全系列高速CMOS数据手册(中文

标准数字电路 54-74HC全系列高速CMOS数据手册(中文)…

查看全部问答>

变色水龙头

似乎自来水管里除了在管道生锈时才会流出红色的水,剩下便是饮料机的出水口会流出带颜色的饮料,其它情况下正常的自来水都是无色透明的。不过使用 LED Faucet Lights 便可以给家里的自来水加上颜色,不过只是在水流出时,通过 LED 灯来照明显示出颜 ...…

查看全部问答>

开关电源保护电路

摘要:为使开关电源在恶劣环境及突发故障状况下安全可靠,提出了几种实用的保护电路,并对电路的工作原理进行了详尽分析。…

查看全部问答>

如何做到长按power键唤醒系统!?

产品有了新需求:要求长按power键3秒钟,系统进入suspend状态;再长按power键3秒钟,唤醒系统。长按3s让系统suspend这个很好做,但是长按3s唤醒系统就让我郁闷了,这个似乎是控制不了,把power键设置为唤醒源之后,只要短按power键中断上来,系统就 ...…

查看全部问答>

如何使用VC实现两个GPRS之间的数据通信?

能否实现两个GPRS模块上网后点对点的信息交互? 怎样才能实现两个模块之间的通信呢?哪怕通过中转服务器也可以... 我不是通信专业的,搞不太清楚,请各位大牛帮忙,跪谢. 我的邮箱是veyou@yeah.net…

查看全部问答>

硬件仿真时怎么都进不了定时器中断

这是我写的一段测试ad转换速度的程序,但是硬件仿真时发现怎么都进不了定时器中断,换成DCO时钟源也不行,不知问题出在哪里,希望各位牛人指点,谢谢!!!! #include <msp430x14x.h> #define uint unsigned int #define uchar unsi ...…

查看全部问答>

电机控制 - AC 感应电机 (ACIM) 概述

本帖最后由 dontium 于 2015-1-23 13:14 编辑            AC 感应电机 (ACIM) 是消费电子类应用和工业应用中最受欢迎的电机,代表了工业革命的力量。   十九世纪末,Nicola Tesla 首次 ...…

查看全部问答>