好像有几张图片被强制缩小了?看到这篇博客的人先对你们说声抱歉,我不知道怎么设置

文字就可以很长(文章宽度的全部),图片就只有文章宽度的2/3宽度

开新分页应该就是原始尺寸了,这点还是和大家说抱歉。。。

文章里面提到的页编程,就是写数据了,因为这是英文直译的结果(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);
}
} /* ====================================== */
/* =========== 【SPI】接收中断 ========== */
/* ====================================== */
#pragma vector=SPI_RXNE_vector
__interrupt void SPI_RXNE_IRQHandler(void)
{
//RxBuf[cnt++]=SPI_DR;
while(!(SPI_SR & 0x01));
UART1_sendchar(SPI_DR); if(count < 7 && command != 0) /* 地址+伪字节 < 7 并且 不是写使能、写禁止、芯片擦除进来的 */
{
if (command == 1) /* 执行页编程剩下的动作,先写24bit地址,然后给数据 */
{
if (count == 0) SPI_sendchar((testAddress & 0xFF0000) >> 16); // 【写】高位地址
else if(count == 1) SPI_sendchar((testAddress & 0xFF00) >> 8); // 【写】中间地址
else if(count == 2) SPI_sendchar(testAddress); // 【写】低位地址
else SPI_sendchar(0xaa); // 存入的数据
}
else if (command == 2) /* 执行读数据剩下的动作,先写24bit地址,然后给数据 */
{
if(count == 0) SPI_sendchar((testAddress & 0xFF0000) >> 16); // 【写】高位地址
else if(count == 1) SPI_sendchar((testAddress & 0xF000) >> 8); // 【写】中间地址
else if(count == 2) SPI_sendchar(testAddress); // 【写】低位地址
else SPI_sendchar(0xff); // 发送伪字节,制造时钟以便获得从机的数据
}
count++;
}
else /* count结束,或是写使能、写禁止、芯片擦除的复位 */
{
count = 0;
PC_ODR_ODR4 = 1; // 【/CS给高电平,等待下次命令给下降沿】
} }

循环7次,地址占3个字节,7 - 3 = 4,数据就占4个字节

这里我写入数据0xAA,四个数据就都是一样的了

这些数据保存在地址0x000000,这些东西都是写死的,要用时再根据自己的项目做修改就可以了

另外,一个地址可以理解为一个页面(page)

一样,在引脚图的上方介绍里,有一段英文『256-bytes per programmable page』

一个地址可以写256个字节,但我只用了4个哈

哦对了,上面的代码有用到几个定义的东西,我把它放在头文件了,这些也就是W25Q16相关的命令罢了

#define WriteEnable             0x06 // 写使能
#define WriteDisable 0x04 // 禁止写
#define WriteStatusRegister 0x01 // 写状态寄存器 #define ReadStatusRegister_1 0x05 // 读状态寄存器
#define ReadStatusRegister_2 0x35 // 读状态寄存器2 #define PageProgram 0x02 // 页编程
#define QuadPageProgram 0x32 #define EraseBlock64K 0xd8 // 块擦除(64KB)
#define EraseBlock32K 0x52 // 块擦除(32KB)
#define EraseSector4K 0x20 // 扇区擦除(4KB)
#define EraseChip 0xc7 // 芯片擦除
#define EraseChip2 0x60 // 已经有了一个,为什么还要另一个芯片擦除指令?
#define EraseSuspend 0x75
#define EraseResume 0x7a #define PowerDown 0xb9 // 掉电(可唤醒)
#define HighPerformanceMode 0xa3
#define ModeBitReset 0xff
#define ReleasePowerDownOrHPM 0xab // 掉电后可以释放掉电,然后器件会返回一个Device ID
#define Manufacturer 0x90 // 制造,芯片会返回器件ID
#define ReadUniqueID 0x4b
#define JEDEC_ID 0x9f #define ReadData 0x03
#define FastRead 0x0b // 快速读取
#define FaseReadDualOutput 0x3b // 快速读取(双输出)
#define FastReadDualIO 0xbb
#define FastReadQuadOutput 0x6b
#define FastReadQuadIO 0xeb

最后,这是读和写的代码,需要的可以下载:https://pan.baidu.com/s/1w9EZQNQDTY3SyT-B2D5L8g

提取码:kdsz

【STM8】外挂存储器W25Q16的更多相关文章

  1. 关于PADS的一些概念和实用技巧(一)

    关于PADS的一些概念和实用技巧(一) 声明:引用请注明出处http://blog.csdn.net/lg1259156776/ 1. 关于part,CAE Decal,PCB Decal Part ...

  2. MCU与MPU的基本区别

    MCU与MPU的基本区别 题记:一般来说,mpu的价格是mcu的数倍. 参考资料: http://www.elecfans.com/d/1564656.html https://zhuanlan.zh ...

  3. STM8单片机启动流程彻底探究--基于IAR开发环境

    初学STM8会发现,STM8官方的固件库并没有提供一个.s文件的启动代码,那么她是如何启动然后跳转到main函数执行的呢 首先,我们根据ARM的只是可以推测,STM8也是通过复位向量来启动的,假设流程 ...

  4. stm8 iar开发

    1.一份官方库基本是通用的. 2.尽量依托cubex for stm8 依托理由: 1.不同型号,不同后缀的芯片,将会被配置不同的外设.比如stm8s103k3系列可能有的是串口1,但是stm8s10 ...

  5. IAR FOR STM8 学习笔记 IAR工程的建立

    STM8是ST意法半导体针对工业应用和消费电子开发而推出的8位单片机. 每种MCU都有自身的优点与缺点,与其它8-bit MCU相比,STM8 8-bit MCU最大的特点是: · 内核: o 最高f ...

  6. Genesis2000用c#开发外挂

    先上官方的说明 gateway is a command line utility for sending messages and commands to Genesis processes. Th ...

  7. 谈谈计算机上的那些存储器-Memory Hierarchy

    文章首发于浩瀚先森博客http://www.guohao1206.com/2016/12/07/1248.html 说到计算机上的存储器,很多人第一反应是硬盘,然后是内存. 其实在计算机上除了硬盘和内 ...

  8. STM8如何使用自带的bootloader

    1,首先确认你使用的STM8有没有自带的bootloader.参考下表 2,STM8空器件可以直接使用自带的bootloader. 3,STM8在使用SWIM烧录后,要想继续使用自带的bootload ...

  9. APP切图标记PS的外挂神器-Assistor PS(转)

    目前APP设计师们对Assistor PS 可是好评连连,说是切图仔的福音或救星.确实是这样的. 与其他切图标记软件不同的是,Assistor PS 是完全独立于 PS 本身的,说是一个外挂更加合适, ...

随机推荐

  1. 痞子衡嵌入式:聊聊i.MXRT1xxx上的普通GPIO与高速GPIO差异及其用法

    大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家介绍的是i.MXRT上的普通GPIO与高速GPIO差异. GPIO 可以说是 MCU 上最简单最常用的外设模块了,当一些原生功能外设接口模块不能 ...

  2. .NET 开源工作流: Slickflow流程引擎高级开发(九) -- 条件事件模式解释及应用

    前言:在流程流转过程中,有时候需要条件模式的支持,这样可以使得流程流转更加灵活多变.比如在业务变量满足一定的条件时,可以启动特定配置的流程(或者位于主流程内部的子流程).本文主要描述条件启动和条件中间 ...

  3. Flink 实践教程:入门(6):读取 PG 数据写入 ClickHouse

    作者:腾讯云流计算 Oceanus 团队 流计算 Oceanus 简介 流计算 Oceanus 是大数据产品生态体系的实时化分析利器,是基于 Apache Flink 构建的具备一站开发.无缝连接.亚 ...

  4. .NET6运行时动态更新限流阈值

    昨天博客园撑不住流量又崩溃了,很巧正在编写这篇文章,于是产生一个假想:如果博客园用上我这个限流组件会怎么样呢? 用户会收到几个429错误,并且多刷新几次就看到了内容,不会出现完全不可用. 还可以降低查 ...

  5. 『学了就忘』Linux软件包管理 — 46、yum命令详细介绍

    目录 1.yum命令的查询操作 2.使用yum命令安装服务 3.使用yum命令升级服务 4.使用yum命令卸载服务 5.yum组管理命令 (1)查询可以安装的软件组 (2)查询软件组内包含的软件 (3 ...

  6. ASP.NET Core 学习笔记 第五篇 ASP.NET Core 中的选项

    前言 还记得上一篇文章中所说的配置吗?本篇文章算是上一篇的延续吧.在 .NET Core 中读取配置文件大多数会为配置选项绑定一个POCO(Plain Old CLR Object)对象,并通过依赖注 ...

  7. Water 2.4 发布,一站式服务治理平台

    Water(水孕育万物...) Water 为项目开发.服务治理,提供一站式解决方案(可以理解为微服务架构支持套件).基于 Solon 框架开发,并支持完整的 Solon Cloud 规范:已在生产环 ...

  8. [atARC115F]Migration

    称$k$个物品的位置$(a_{1},a_{2},...,a_{k})$为一个状态,并设初始状态为$S$,结束状态为$T$ 定义状态的比较:首先根据$\sum_{i=1}^{k}h_{a_{i}}$,即 ...

  9. [atARC088F]Christmas Tree

    合并具有交换律,因此即将一个连通块(初始为空)与一条链合并(其中各选1点,初始直接替换) 把插入改为染色,等价于对树上的一条链(包括点和边)染色,其中恰好有1个已经被染色的点(初始任意) 对于&quo ...

  10. ant的xml解释

    ant必须以<project>开始和</project>结束 --project(父节点) --target(子节点) ---javac(孙节点) ---echo(孙节点)