温州二井建设有限公司网站,安装网络要多少钱,无锡网站网页设计培训,先做网站再备案吗文章目录目的基础说明使用示例总结目的
Winbond#xff08;华邦#xff09;的 W25Qxx 系列 SPI FLASH 是比较常用的芯片#xff0c;这篇文章将演示单片机中通过SPI使用该芯片的操作过程。
本文使用沁恒官方的开发板 #xff08;CH32V307-EVT-R1沁恒RISC-V模块MCU赤兔评估…
文章目录目的基础说明使用示例总结目的
Winbond华邦的 W25Qxx 系列 SPI FLASH 是比较常用的芯片这篇文章将演示单片机中通过SPI使用该芯片的操作过程。
本文使用沁恒官方的开发板 CH32V307-EVT-R1沁恒RISC-V模块MCU赤兔评估板 进行演示。
基础说明
SPI的基础概念见下面文章 SPI基础概念https://blog.csdn.net/Naisu_kun/article/details/118102844
下面的例程中其实真正直接涉及到SPI的也就初始化、读取标志、收发数据几个操作。真正大头的都是根据芯片手册来操作。通常的操作就是主机向芯片写命令然后根据命令类型紧接着写入或读取数据。
芯片的命令可以参考芯片手册中 INSTRUCTIONS 章节比如我这里使用的 W25Q32FV 的手册《W25Q32FV Datasheet》
需要注意的是FLASH写操作的时候因为物理特性通常bit可以写0无法写1所以对已经写过数据的地址上写入数据前需要先擦除。另外擦除只能按扇区对于W25Qxx而已一个扇区为4k字节、块或者全片擦除。写入一次最多可以写入一页256字节。
使用示例
下面代码源自沁恒CH32V307官方例程这里调整了下代码次序和注释
#include debug.h/*此例程通过CH32V307的SPI1与W25Q系列Flash进行通讯引脚连接如下MCU Flash 说明PA2 CS 片选引脚低电平选中PA5 CLK 时钟PA7(SPI1_MOSI) DI 主机发送从机接收PA6(SPI1_MISO) DO 从机发送主机接收GND GND 地3.3V VCC 电源3.3V HOLD(RESET) 复位*//******************** 以下为SPI1基本操作函数声明 ********************/
void SPI1_Init(void);
u8 SPI1_ReadWriteByte(u8 TxData);/******************** 以下为华邦Falsh ID ********************/
#define W25Q80 0XEF13
#define W25Q16 0XEF14
#define W25Q32 0XEF15
#define W25Q64 0XEF16
#define W25Q128 0XEF17/******************** 以下为华邦Falsh操作指令 ********************/
#define W25X_WriteEnable 0x06
#define W25X_WriteDisable 0x04
#define W25X_ReadStatusReg 0x05
#define W25X_WriteStatusReg 0x01
#define W25X_ReadData 0x03
#define W25X_FastReadData 0x0B
#define W25X_FastReadDual 0x3B
#define W25X_PageProgram 0x02
#define W25X_BlockErase 0xD8
#define W25X_SectorErase 0x20
#define W25X_ChipErase 0xC7
#define W25X_PowerDown 0xB9
#define W25X_ReleasePowerDown 0xAB
#define W25X_DeviceID 0xAB
#define W25X_ManufactDeviceID 0x90
#define W25X_JedecDeviceID 0x9F/******************** 以下为Falsh操作函数声明 ********************/
u8 SPI_Flash_ReadSR(void);
void SPI_FLASH_Write_SR(u8 sr);
void SPI_Flash_Wait_Busy(void);
void SPI_FLASH_Write_Enable(void);
void SPI_FLASH_Write_Disable(void);
u16 SPI_Flash_ReadID(void);
void SPI_Flash_Erase_Sector(u32 Dst_Addr);
void SPI_Flash_Read(u8 *pBuffer, u32 ReadAddr, u16 size);
void SPI_Flash_Write_Page(u8 *pBuffer, u32 WriteAddr, u16 size);
void SPI_Flash_Write_NoCheck(u8 *pBuffer, u32 WriteAddr, u16 size);
void SPI_Flash_Write(u8 *pBuffer, u32 WriteAddr, u16 size);
void SPI_Flash_Erase_Chip(void);
void SPI_Flash_PowerDown(void);
void SPI_Flash_WAKEUP(void);/******************** 以下为主函数 ********************/int main(void) {NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);SystemCoreClockUpdate();Delay_Init();USART_Printf_Init(115200);printf(SystemClk:%d\r\n, SystemCoreClock);printf(\r\n);SPI1_Init(); // 初始化SPI1u16 Flash_Model SPI_Flash_ReadID(); // 读取Flash IDswitch(Flash_Model) {case W25Q80: printf(W25Q80 OK!\r\n); break;case W25Q16: printf(W25Q16 OK!\r\n); break;case W25Q32: printf(W25Q32 OK!\r\n); break;case W25Q64: printf(W25Q64 OK!\r\n); break;case W25Q128: printf(W25Q128 OK!\r\n); break;default: printf(Fail!\r\n); break;}const u8 msg[] CH32F103 SPI FLASH W25Qxx;const int size sizeof(msg);u8 buffer[size];printf(Start Erase W25Qxx....\r\n);SPI_Flash_Erase_Sector(0); // 擦除扇区printf(W25Qxx Erase Finished!\r\n);Delay_Ms(500);printf(Start Read W25Qxx....\r\n);SPI_Flash_Read(buffer, 0x0, size); // 读取数据printf(%s\r\n, buffer);Delay_Ms(500);printf(Start Write W25Qxx....\r\n);SPI_Flash_Write((u8*)msg, 0, size); // 写入数据printf(W25Qxx Write Finished!\r\n);Delay_Ms(500);printf(Start Read W25Qxx....\r\n);SPI_Flash_Read(buffer, 0x0, size); // 再次读取数据printf(%s\r\n, buffer);while(1) {}
}/******************** 以下为SPI1基本操作函数实现 ********************/// 初始化SPI1
void SPI1_Init(void) {// 初始化时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_SPI1, ENABLE);// 初始化GPIOGPIO_InitTypeDef GPIO_InitStructure { 0 };GPIO_InitStructure.GPIO_Pin GPIO_Pin_2;GPIO_InitStructure.GPIO_Mode GPIO_Mode_Out_PP;GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz;GPIO_Init(GPIOA, GPIO_InitStructure);GPIO_SetBits(GPIOA, GPIO_Pin_2); // CS引脚输出高电平不选中Flash芯片GPIO_InitStructure.GPIO_Pin GPIO_Pin_5 | GPIO_Pin_7;GPIO_InitStructure.GPIO_Mode GPIO_Mode_AF_PP;GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz;GPIO_Init(GPIOA, GPIO_InitStructure);GPIO_InitStructure.GPIO_Pin GPIO_Pin_6;GPIO_InitStructure.GPIO_Mode GPIO_Mode_IN_FLOATING;GPIO_Init(GPIOA, GPIO_InitStructure);// 初始化SPI1SPI_InitTypeDef SPI_InitStructure { 0 };SPI_InitStructure.SPI_Direction SPI_Direction_2Lines_FullDuplex; // 全双工模式SPI_InitStructure.SPI_Mode SPI_Mode_Master; // 作为主机SPI_InitStructure.SPI_DataSize SPI_DataSize_8b; // 8bit数据SPI_InitStructure.SPI_CPOL SPI_CPOL_High; // 时钟不通讯时高电平SPI_InitStructure.SPI_CPHA SPI_CPHA_2Edge; // 在第二个边缘进行采样结合上条这里相当于使用Mode3SPI_InitStructure.SPI_NSS SPI_NSS_Soft; // 片选引脚手动控制SPI_InitStructure.SPI_BaudRatePrescaler SPI_BaudRatePrescaler_4; // SPI外设时钟源4分频SPI_InitStructure.SPI_FirstBit SPI_FirstBit_MSB; // 传输时高比特在前SPI_InitStructure.SPI_CRCPolynomial 7;SPI_Init(SPI1, SPI_InitStructure); // 初始化SPI1设置SPI_Cmd(SPI1, ENABLE); // 启动SPI1
}// 很多库中SPI的读和写是封装在一起的根据需要选择写或读或读写
// 在SPI读数据时需要注意这时候虽然数据是从机发送的但时钟还是主机给出的
// 所以往往读数据时主机还需要通过写数据任意均可来给出时钟信号
// SPI1_Write_Read(0x00) 经常用来表示通过写一个字节来发送时钟从而接收从机一个字节数据
u8 SPI1_ReadWriteByte(u8 TxData) {u8 i 0;while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) RESET) { // 等待SPI1可写i; if(i 200) return 0;}SPI_I2S_SendData(SPI1, TxData); // 写数据i 0;while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) RESET) { // 等待SPI1可读i; if(i 200) return 0;}return SPI_I2S_ReceiveData(SPI1); // 读数据
}/******************** 以下为Falsh操作函数实现 ********************/// 读状态寄存器
// BIT7 6 5 4 3 2 1 0
// SPR RV TB BP2 BP1 BP0 WEL BUSY
u8 SPI_Flash_ReadSR(void) {u8 byte 0;GPIO_WriteBit(GPIOA, GPIO_Pin_2, 0); // 选中芯片SPI1_ReadWriteByte(W25X_ReadStatusReg); // 写命令byte SPI1_ReadWriteByte(0x00); // 读数据GPIO_WriteBit(GPIOA, GPIO_Pin_2, 1); // 释放芯片return byte;
}// 写状态寄存器
void SPI_FLASH_Write_SR(u8 sr) {GPIO_WriteBit(GPIOA, GPIO_Pin_2, 0); // 选中芯片SPI1_ReadWriteByte(W25X_WriteStatusReg); // 写命令SPI1_ReadWriteByte(sr); // 写数据GPIO_WriteBit(GPIOA, GPIO_Pin_2, 1); // 释放芯片
}// 等待Flash空闲
void SPI_Flash_Wait_Busy(void) {while((SPI_Flash_ReadSR() 0x01) 0x01) { }
}// 使能写入
void SPI_FLASH_Write_Enable(void) {GPIO_WriteBit(GPIOA, GPIO_Pin_2, 0); // 选中芯片SPI1_ReadWriteByte(W25X_WriteEnable); // 写命令GPIO_WriteBit(GPIOA, GPIO_Pin_2, 1); // 释放芯片
}// 失能写入
void SPI_FLASH_Write_Disable(void) {GPIO_WriteBit(GPIOA, GPIO_Pin_2, 0); // 选中芯片SPI1_ReadWriteByte(W25X_WriteDisable); // 写命令GPIO_WriteBit(GPIOA, GPIO_Pin_2, 1); // 释放芯片
}// 读取芯片ID
u16 SPI_Flash_ReadID(void) {GPIO_WriteBit(GPIOA, GPIO_Pin_2, 0); // 选中芯片SPI1_ReadWriteByte(W25X_ManufactDeviceID); // 写命令SPI1_ReadWriteByte(0x00); // 通过发送数据产生时钟等待数据可读SPI1_ReadWriteByte(0x00); // 同上SPI1_ReadWriteByte(0x00); // 同上u16 Temp 0;Temp | SPI1_ReadWriteByte(0xFF) 8; // 读数据Temp | SPI1_ReadWriteByte(0xFF); // 同上GPIO_WriteBit(GPIOA, GPIO_Pin_2, 1); // 释放芯片return Temp;
}// 擦除扇区
void SPI_Flash_Erase_Sector(u32 Dst_Addr) {SPI_FLASH_Write_Enable(); // 使能写入SPI_Flash_Wait_Busy(); // 等待操作完成GPIO_WriteBit(GPIOA, GPIO_Pin_2, 0); // 选中芯片SPI1_ReadWriteByte(W25X_SectorErase); // 发送命令Dst_Addr * 4096; // 每个扇区大小为4kSPI1_ReadWriteByte((u8)((Dst_Addr) 16)); // 写扇区地址SPI1_ReadWriteByte((u8)((Dst_Addr) 8)); // 同上SPI1_ReadWriteByte((u8)Dst_Addr); // 同上GPIO_WriteBit(GPIOA, GPIO_Pin_2, 1); // 释放芯片SPI_Flash_Wait_Busy(); // 等待操作完成
}// 读取数据
void SPI_Flash_Read(u8 *pBuffer, u32 ReadAddr, u16 size) {GPIO_WriteBit(GPIOA, GPIO_Pin_2, 0); // 选中芯片SPI1_ReadWriteByte(W25X_ReadData); // 写命令SPI1_ReadWriteByte((u8)((ReadAddr) 16)); // 写地址SPI1_ReadWriteByte((u8)((ReadAddr) 8)); // 同上SPI1_ReadWriteByte((u8)ReadAddr);for(u16 i 0; i size; i) {pBuffer[i] SPI1_ReadWriteByte(0x00); // 读数据}GPIO_WriteBit(GPIOA, GPIO_Pin_2, 1); // 释放芯片
}// 按页方式写入
void SPI_Flash_Write_Page(u8 *pBuffer, u32 WriteAddr, u16 size) {SPI_FLASH_Write_Enable(); // 使能写入GPIO_WriteBit(GPIOA, GPIO_Pin_2, 0); // 选中芯片SPI1_ReadWriteByte(W25X_PageProgram); // 写命令SPI1_ReadWriteByte((u8)((WriteAddr) 16)); // 写地址SPI1_ReadWriteByte((u8)((WriteAddr) 8)); // 同上SPI1_ReadWriteByte((u8)WriteAddr); // 同上for(u16 i 0; i size; i) {SPI1_ReadWriteByte(pBuffer[i]); // 写数据}GPIO_WriteBit(GPIOA, GPIO_Pin_2, 1); // 释放芯片SPI_Flash_Wait_Busy(); // 等待操作完成
}// 写入数据写入前需要先擦除
void SPI_Flash_Write_NoCheck(u8 *pBuffer, u32 WriteAddr, u16 size) {// 一次最多只能写一页256字节u16 pageremain 256 - WriteAddr % 256;if(size pageremain) pageremain size;while(1) {SPI_Flash_Write_Page(pBuffer, WriteAddr, pageremain);if(size pageremain) { break; }else {pBuffer pageremain;WriteAddr pageremain;size - pageremain;if(size 256) pageremain 256;else pageremain size;}}
}// 写入数据
void SPI_Flash_Write(u8 *pBuffer, u32 WriteAddr, u16 size) {u8 buffer[4096];u32 secpos;u16 secoff;u16 secremain;u16 i;secpos WriteAddr / 4096;secoff WriteAddr % 4096;secremain 4096 - secoff;if(size secremain) secremain size;while(1) {SPI_Flash_Read(buffer, secpos * 4096, 4096); // 读取数据for(i 0; i secremain; i) {if(buffer[secoff i] ! 0XFF) break; // 检查数据}if(i secremain) { // 需要先擦除SPI_Flash_Erase_Sector(secpos);for(i 0; i secremain; i) { // 擦除数据buffer[i secoff] pBuffer[i]; // 将要写入数据填充到缓存中}SPI_Flash_Write_NoCheck(buffer, secpos * 4096, 4096); // 写数据} else { // 不需要擦除SPI_Flash_Write_NoCheck(pBuffer, WriteAddr, secremain); // 直接写数据}if(size secremain) { break; }else {secpos;secoff 0;pBuffer secremain;WriteAddr secremain;size - secremain;if(size 4096) { secremain 4096; }else { secremain size; }}}
}// 全片擦除
void SPI_Flash_Erase_Chip(void) {SPI_FLASH_Write_Enable(); // 使能写入SPI_Flash_Wait_Busy(); // 等待操作完成GPIO_WriteBit(GPIOA, GPIO_Pin_2, 0); // 选中芯片SPI1_ReadWriteByte(W25X_ChipErase); // 写命令GPIO_WriteBit(GPIOA, GPIO_Pin_2, 1); // 释放芯片SPI_Flash_Wait_Busy(); // 等待操作完成
}// 进入省电模式
void SPI_Flash_PowerDown(void) {GPIO_WriteBit(GPIOA, GPIO_Pin_2, 0); // 选中芯片SPI1_ReadWriteByte(W25X_PowerDown); // 写命令GPIO_WriteBit(GPIOA, GPIO_Pin_2, 1); // 释放芯片Delay_Us(3); // 等待操作完成
}// 从省电模式唤醒
void SPI_Flash_WAKEUP(void) {GPIO_WriteBit(GPIOA, GPIO_Pin_2, 0); // 选中芯片SPI1_ReadWriteByte(W25X_ReleasePowerDown); // 写命令GPIO_WriteBit(GPIOA, GPIO_Pin_2, 1); // 释放芯片Delay_Us(3); // 等待操作完成
}上面代码演示结果如下
总结
Winbond华邦的 W25Qxx 系列 SPI FLASH 芯片使用上是比较简单的。上面的代码只是测试使用的代码可能存在很多不严谨或不完善的地方。实际使用中可以自行调整并抽象出相关功能使之可以适用各类平台。 文章转载自: http://www.morning.dthyq.cn.gov.cn.dthyq.cn http://www.morning.pcxgj.cn.gov.cn.pcxgj.cn http://www.morning.thwhn.cn.gov.cn.thwhn.cn http://www.morning.pshpx.cn.gov.cn.pshpx.cn http://www.morning.rfyk.cn.gov.cn.rfyk.cn http://www.morning.wblpn.cn.gov.cn.wblpn.cn http://www.morning.nwtmy.cn.gov.cn.nwtmy.cn http://www.morning.mdtfh.cn.gov.cn.mdtfh.cn http://www.morning.nytgk.cn.gov.cn.nytgk.cn http://www.morning.wchsx.cn.gov.cn.wchsx.cn http://www.morning.srzhm.cn.gov.cn.srzhm.cn http://www.morning.cctgww.cn.gov.cn.cctgww.cn http://www.morning.zqsnj.cn.gov.cn.zqsnj.cn http://www.morning.ptslx.cn.gov.cn.ptslx.cn http://www.morning.nzzws.cn.gov.cn.nzzws.cn http://www.morning.gsdbg.cn.gov.cn.gsdbg.cn http://www.morning.qtyfb.cn.gov.cn.qtyfb.cn http://www.morning.xqndf.cn.gov.cn.xqndf.cn http://www.morning.wkmrl.cn.gov.cn.wkmrl.cn http://www.morning.nkcfh.cn.gov.cn.nkcfh.cn http://www.morning.tjndb.cn.gov.cn.tjndb.cn http://www.morning.ckntb.cn.gov.cn.ckntb.cn http://www.morning.rzbgn.cn.gov.cn.rzbgn.cn http://www.morning.jwgmx.cn.gov.cn.jwgmx.cn http://www.morning.jrpmf.cn.gov.cn.jrpmf.cn http://www.morning.tsflw.cn.gov.cn.tsflw.cn http://www.morning.clfct.cn.gov.cn.clfct.cn http://www.morning.tlrxp.cn.gov.cn.tlrxp.cn http://www.morning.wqjpl.cn.gov.cn.wqjpl.cn http://www.morning.qdxkn.cn.gov.cn.qdxkn.cn http://www.morning.ssrjt.cn.gov.cn.ssrjt.cn http://www.morning.qcslh.cn.gov.cn.qcslh.cn http://www.morning.clndl.cn.gov.cn.clndl.cn http://www.morning.nzdks.cn.gov.cn.nzdks.cn http://www.morning.xhqr.cn.gov.cn.xhqr.cn http://www.morning.dgpxp.cn.gov.cn.dgpxp.cn http://www.morning.kqpq.cn.gov.cn.kqpq.cn http://www.morning.lgkbn.cn.gov.cn.lgkbn.cn http://www.morning.jxltk.cn.gov.cn.jxltk.cn http://www.morning.frfnb.cn.gov.cn.frfnb.cn http://www.morning.ynstj.cn.gov.cn.ynstj.cn http://www.morning.ldmtq.cn.gov.cn.ldmtq.cn http://www.morning.lqws.cn.gov.cn.lqws.cn http://www.morning.kbqbx.cn.gov.cn.kbqbx.cn http://www.morning.jsljr.cn.gov.cn.jsljr.cn http://www.morning.mkzdp.cn.gov.cn.mkzdp.cn http://www.morning.lpnb.cn.gov.cn.lpnb.cn http://www.morning.bmssj.cn.gov.cn.bmssj.cn http://www.morning.zmtrk.cn.gov.cn.zmtrk.cn http://www.morning.lzdbb.cn.gov.cn.lzdbb.cn http://www.morning.fhtbk.cn.gov.cn.fhtbk.cn http://www.morning.ywqsk.cn.gov.cn.ywqsk.cn http://www.morning.dnqliv.cn.gov.cn.dnqliv.cn http://www.morning.rmjxp.cn.gov.cn.rmjxp.cn http://www.morning.dbsch.cn.gov.cn.dbsch.cn http://www.morning.c7625.cn.gov.cn.c7625.cn http://www.morning.gtdf.cn.gov.cn.gtdf.cn http://www.morning.mspkz.cn.gov.cn.mspkz.cn http://www.morning.lkthj.cn.gov.cn.lkthj.cn http://www.morning.kdrly.cn.gov.cn.kdrly.cn http://www.morning.ktyww.cn.gov.cn.ktyww.cn http://www.morning.jhfkr.cn.gov.cn.jhfkr.cn http://www.morning.fbqr.cn.gov.cn.fbqr.cn http://www.morning.psqs.cn.gov.cn.psqs.cn http://www.morning.fpbj.cn.gov.cn.fpbj.cn http://www.morning.nqgds.cn.gov.cn.nqgds.cn http://www.morning.kpwcx.cn.gov.cn.kpwcx.cn http://www.morning.lveyue.com.gov.cn.lveyue.com http://www.morning.nbdtdjk.cn.gov.cn.nbdtdjk.cn http://www.morning.wlfxn.cn.gov.cn.wlfxn.cn http://www.morning.wynqg.cn.gov.cn.wynqg.cn http://www.morning.czrcf.cn.gov.cn.czrcf.cn http://www.morning.nwllb.cn.gov.cn.nwllb.cn http://www.morning.hlfnh.cn.gov.cn.hlfnh.cn http://www.morning.nfcxq.cn.gov.cn.nfcxq.cn http://www.morning.srmpc.cn.gov.cn.srmpc.cn http://www.morning.rjnrf.cn.gov.cn.rjnrf.cn http://www.morning.jmwrj.cn.gov.cn.jmwrj.cn http://www.morning.kybpj.cn.gov.cn.kybpj.cn http://www.morning.zfwjh.cn.gov.cn.zfwjh.cn