STM32之 W25Q128闪存(SPI协议)驱动代码(程序稳定,清晰明了)

第一部分:W25Q128代码头文件 (W25Q128.h)

#ifndef W25Q128_H
#define W25Q128_H
#include "stm32f10x.h"
#include "stdio.h"
#include "sys.h"
#include "delay.h"

#define W25Q128_CS   PBout(12)
#define W25Q128_SCLK PBout(13)
#define W25Q128_MOSI PBout(14)
#define W25Q128_MISO PBin(15)
extern u8 W25Q128_ID[2]; //制造商/设备 ID 
void W25Q128_Init(void);
u8 W25Q128_SPI_ReadWriteOneByte(u8 tx_data);
void W25Q128_WritePageData(u32 addr ,u8 * p ,u32 len );
void W25Q128_ReadData(u32 addr ,u8 * p ,u32 len );
void W25Q128_WriteDataNoCheck(u32 addr ,u8 * p ,u32 len);
void W25Q128_WriteData(u32 addr ,u8 * p ,u32 len);
void W25Q128_SectorErase(u32 addr,u8 cmd);
void W25Q128_BusyStateWait(void);
void W25Q128_WriteEnable(void);
void W25Q128_Read_ID(void);
#endif


第二部分:W25Q128代码文件 (W25Q128.c)

#include "W25Q128.h"

u8 W25Q128_ID[2];      //制造商/设备 ID
/*
硬件连接:
CS---->GPIOB_12
SCLK-->GPIOB_13
MOSI-->GPIOB_14
MISO-->GPIOB_15
*/

void W25Q128_Init(void)
{
	/*1.开时钟*/
	RCC->APB2ENR |= 1<<3; 
	/*2.配置GPIO口*/
	GPIOB->CRH &= 0x0000FFFF;
	GPIOB->CRH |= 0x83330000;
	/*3.SCLK,CS空闲时为高电平(上拉)*/
	GPIOB->ODR |= 0xF<<12;
	printf("W25Q128 GPIO初始化成功!\n");
}

/*
函数功能:  SPI底层时序、读写一个字节(第4种SPI协议 CPOL=1 CPHA=1 )
*/
u8 W25Q128_SPI_ReadWriteOneByte(u8 tx_data)
{
	u8 rx_data=0,i=0;
	W25Q128_SCLK=1;
	for(i=0;i<8;i++)
	{
		W25Q128_SCLK=0;					
		/*主机发送数据*/
		if(tx_data&0x80) W25Q128_MOSI=1;  //先发高位再发低位
		else W25Q128_MOSI=0;
		tx_data<<=1;
		/*从机接收数据*/									
		rx_data<<=1;											//默认接收为0
		if(W25Q128_MISO) rx_data |= 0x1;  //先接高位再接低位		
		W25Q128_SCLK=1;	
	}
	return rx_data;
}

/*
函数功能:在W25Q128指定位置写入指定长度数据(页写)
函数参数:
		addr:要写入W25Q128的起始地址
		p:源数据地址
		len:要写入的数据长度(字节)
*/
void W25Q128_WritePageData(u32 addr ,u8 * p ,u32 len )
{
	u32 i=0;
	W25Q128_WriteEnable(); //W25Q128写使能
	W25Q128_CS=0; //拉低片选脚,选中设备
	W25Q128_SPI_ReadWriteOneByte(0x02);  //页写命令
	W25Q128_SPI_ReadWriteOneByte(addr>>16); //A23~A16 要写入W25Q128的24位地址 (先发高位)
	W25Q128_SPI_ReadWriteOneByte(addr>>8); //A15~A8
	W25Q128_SPI_ReadWriteOneByte(addr); //A7~A0
	for(i=0;i<len;i++)   //必须要写len,不能填256。因为写数据不一定有256个。
	{
			W25Q128_SPI_ReadWriteOneByte(p[i]);
	}
	W25Q128_CS=1; //释放设备
	W25Q128_BusyStateWait();//等待W25Q128页写结束	
}

/*
函数功能:在W25Q128指定位置读取指定长度数据
函数参数:
		addr:读取数据源地址
		p:存放读取地址
		len:要读取的数据长度(字节)
*/
void W25Q128_ReadData(u32 addr ,u8 * p ,u32 len )
{
	u32 i=0;
	W25Q128_CS=0; //拉低片选脚,选中设备
	W25Q128_SPI_ReadWriteOneByte(0x03);  //页读取命令
	W25Q128_SPI_ReadWriteOneByte(addr>>16); //A23~A16 要写入W25Q128的24位地址 (先发高位)
	W25Q128_SPI_ReadWriteOneByte(addr>>8); //A15~A8
	W25Q128_SPI_ReadWriteOneByte(addr); //A7~A0
	for(i=0;i<len;i++)  //必须要写len,不能填256。因为读数据不一定读256个。
	{
			p[i]=W25Q128_SPI_ReadWriteOneByte(0xFF);
	}
	W25Q128_CS=1; //释放设备
}

/*
函数功能:在W25Q128指定位置写入指定长度数据(没有考虑扇区擦除也没有考虑数据误擦除)
函数参数:
		p:要写入的数据
		addr:写入数据的地址
		len:要写入的数据长度(字节)
*/

void W25Q128_WriteDataNoCheck(u32 addr ,u8 * p ,u32 len)
{
	u32  page_tmp;
	page_tmp=256-addr%256; //目标地址页剩余可以写的空间
	if(page_tmp>=len) page_tmp=len;
	while(1)  //写数据
	{
		W25Q128_WritePageData(addr,p,page_tmp);
		if(page_tmp==len) break;  //数据已经全部写入完成
		addr+=page_tmp;    //写地址偏移
		p+=page_tmp; //源数据地址偏移
		len-=page_tmp;  //计算剩下的数据
		if(len>=256)	page_tmp=256;  //计算下一次页写的数据大小
		else	page_tmp=len;
	}
}


/*
函数功能:在W25Q128指定位置写入指定长度数据(考虑到了数据误擦除)
函数参数:
		p:要写入的数据
		addr:写入数据的地址
		len:要写入的数据长度(字节)
*/

void W25Q128_WriteData(u32 addr ,u8 * p ,u32 len)
{
	u8 W25Q128_Buff[4096]; //备份缓冲区
	u32  page_tmp,sector_num,Erase_Len=len,Erase_Addr=addr,buff_len;
	sector_num=addr/4096; //计算当前扇区数
	buff_len=addr-sector_num*4096;
//	printf("sector_num=%d buff_len=%d \n",sector_num,buff_len);
	
	page_tmp=256-addr%256; //目标地址页剩余可以写的空间
	if(page_tmp>=len) page_tmp=len;
	
	W25Q128_ReadData(sector_num*4096,W25Q128_Buff,buff_len); //备份源扇区的数据
	
	while(1) //以扇区的方式擦除要写数据的地方
	{
		if(Erase_Len<=4096-Erase_Addr%4096)
		{
			W25Q128_SectorErase(Erase_Addr,0x20);
			break;
		}
		else if(Erase_Len) //如果擦除的空间不够写入数据,继续擦除空间
		{
			W25Q128_SectorErase(Erase_Addr,0x20); 
			Erase_Addr+=4096; //擦除地址偏移
			Erase_Len-=4096;  //还剩下待擦除的空间大小
		}
		else 
		{
			break;
		}
	}
	W25Q128_WriteDataNoCheck(sector_num*4096,W25Q128_Buff,buff_len); //还原之前备份的数据 
	while(1)  //写数据
	{
		W25Q128_WritePageData(addr,p,page_tmp);
		if(page_tmp==len) break;  //数据已经全部写入完成
		addr+=page_tmp;    //写地址偏移
		p+=page_tmp; //源数据地址偏移
		len-=page_tmp;  //计算剩下的数据
		if(len>=256)	page_tmp=256;  //计算下一次页写的数据大小
		else	page_tmp=len;
	}
}

/*
函数功能: 擦除W25Q128指定扇区
函数参数:
				addr:要擦除W25Q128的地址
说明:擦除命令不能字节擦除,只能块/扇区擦除(一个扇区4096字节)
			0~4095 :第一个扇区
			4096~..:第二个扇区
			.....  : ...

块擦除(64KB) D8h 
块擦除(32KB) 52h
扇区擦除(4KB) 20h
*/
void W25Q128_SectorErase(u32 addr,u8 cmd)
{
	W25Q128_WriteEnable(); //W25Q128写使能
	W25Q128_CS=0; //拉低片选脚,选中设备
	W25Q128_SPI_ReadWriteOneByte(cmd); //擦除扇区命令
	W25Q128_SPI_ReadWriteOneByte(addr>>16); //A23~A16 要擦除W25Q128的24位地址 (先发高位)
	W25Q128_SPI_ReadWriteOneByte(addr>>8); //A15~A8
	W25Q128_SPI_ReadWriteOneByte(addr); //A7~A0
	W25Q128_CS=1; //释放设备
	W25Q128_BusyStateWait();//等待W25Q128擦除扇区接结束
}

/*
函数功能:等待 W25Q128直到为空闲状态
*/
void W25Q128_BusyStateWait(void)
{
	u8 tmp=1;
	u16 cnt=0;	
	while(tmp&0x01) 
	{
		W25Q128_CS=0; //拉低片选脚,选中设备
		W25Q128_SPI_ReadWriteOneByte(0x05); //读状态寄存器命令
		tmp=W25Q128_SPI_ReadWriteOneByte(0xFF);  //读状态寄存器
		W25Q128_CS=1; //释放设备
		DelayMs(1);
		if(cnt++>=500) break;  //防止卡死
	} 
}

/*
函数功能: W25Q128写使能
*/	
void W25Q128_WriteEnable(void)
{
	W25Q128_CS=0; //拉低片选脚,选中设备
	W25Q128_SPI_ReadWriteOneByte(0x06);  //写使能
	W25Q128_CS=1; //释放设备
}

/*函数功能:读取制造商/芯片 ID
制造商ID= EF
设备ID= 17   (W25Q64 的是16H)
*/
void W25Q128_Read_ID(void)
{
	W25Q128_CS=0;												//拉低片选脚,选中设备
	W25Q128_SPI_ReadWriteOneByte(0x90); //读取制造商/设备 ID 指令
 	W25Q128_SPI_ReadWriteOneByte(0x00); 
	W25Q128_SPI_ReadWriteOneByte(0x00);
	W25Q128_SPI_ReadWriteOneByte(0x00); 
	W25Q128_ID[0] = W25Q128_SPI_ReadWriteOneByte(0xFF); //0xFF不是一个指令 只是为了不让函数报错
	W25Q128_ID[1] = W25Q128_SPI_ReadWriteOneByte(0xFF);
	W25Q128_CS=1;												//释放片选脚
	printf("制造商ID= %X\n设备ID= %X \n",W25Q128_ID[0],W25Q128_ID[1]);
}

 以下部分非W25Q128的驱动代码,只是一些延时还有IO口重定义文件!!!

第三部分:延时代码头文件 (delay.h)

#ifndef DELAY_H
#define DELAY_H

void DelayMs(int ms);
void DelayUs(int us);

#endif

第四部分:延时代码文件 (delay.c)

#include "delay.h"
#include "sys.h"
#include "config.h"

/*函数功能: 延时ms单位*/
void DelayMs(int ms)
{
	int i,j,n;
	for(i=0;i<ms;i++)
		for(j=0;j<100;j++)
			for(n=0;n<100;n++);
}

/*函数功能: 延时us单位*/
void DelayUs(int us)
{
#ifdef	_SYSTICK_IRQ_
	int i,j;
	for(i=0;i<us;i++)
		for(j=0;j<72;j++);
#else
	u32 tmp;
	SysTick->VAL=0;         //CNT计数器值	
	SysTick->LOAD=9*us;     //9表示1us
	SysTick->CTRL|=1<<0;    //开启定时器	
	do
	{
			tmp=SysTick->CTRL; //读取状态
	}while((!(tmp&1<<16))&&(tmp&1<<0));
	SysTick->VAL=0;         //CNT计数器值	
	SysTick->CTRL&=~(1<<0); //关闭定时器	
#endif	
}

第五部分:系统代码头文件 (sys.h)

#ifndef SYS_H
#define SYS_H
#include "stm32f10x.h"

//公式参考: Cortex-M3权威指南92页
//完成位带地址的转换
//addr表示寄存器的基地址
//bitnum表示寄存器里的第几位
#define BITBAND(addr,bitnum) ((addr&0xF0000000)+0x2000000+((addr&0xFFFFF)<<5)+(bitnum<<2))
//将地址转换为指针类型
#define MEM_ADDR(addr) *((volatile u32 *)addr)

/*定义GPIO口输入输出寄存器的地址*/
#define GPIOA_IDR (0x40010800+0x8)
#define GPIOA_ODR (0x40010800+0xC)
#define GPIOB_IDR (0x40010C00+0x8)
#define GPIOB_ODR (0x40010C00+0xC)
#define GPIOC_IDR (0x40011000+0x8)
#define GPIOC_ODR (0x40011000+0xC)
#define GPIOD_IDR (0x40011400+0x8)
#define GPIOD_ODR (0x40011400+0xC)
#define GPIOE_IDR (0x40011800+0x8)
#define GPIOE_ODR (0x40011800+0xC)
#define GPIOF_IDR (0x40011C00+0x8)
#define GPIOF_ODR (0x40011C00+0xC)
#define GPIOG_IDR (0x40012000+0x8)
#define GPIOG_ODR (0x40012000+0xC)

/*定义GPIO口位操作宏*/
#define PAin(bitnum)  MEM_ADDR(BITBAND(GPIOA_IDR,bitnum))
#define PAout(bitnum) MEM_ADDR(BITBAND(GPIOA_ODR,bitnum))
#define PBin(bitnum)  MEM_ADDR(BITBAND(GPIOB_IDR,bitnum))
#define PBout(bitnum) MEM_ADDR(BITBAND(GPIOB_ODR,bitnum))
#define PCin(bitnum)  MEM_ADDR(BITBAND(GPIOC_IDR,bitnum))
#define PCout(bitnum) MEM_ADDR(BITBAND(GPIOC_ODR,bitnum))
#define PDin(bitnum)  MEM_ADDR(BITBAND(GPIOD_IDR,bitnum))
#define PDout(bitnum) MEM_ADDR(BITBAND(GPIOD_ODR,bitnum))
#define PEin(bitnum)  MEM_ADDR(BITBAND(GPIOE_IDR,bitnum))
#define PEout(bitnum) MEM_ADDR(BITBAND(GPIOE_ODR,bitnum))
#define PFin(bitnum)  MEM_ADDR(BITBAND(GPIOF_IDR,bitnum))
#define PFout(bitnum) MEM_ADDR(BITBAND(GPIOF_ODR,bitnum))
#define PGin(bitnum)  MEM_ADDR(BITBAND(GPIOG_IDR,bitnum))
#define PGout(bitnum) MEM_ADDR(BITBAND(GPIOG_ODR,bitnum))



/*NVIC中断优先级分组定义*/

#define NVIC_PriorityGroup_0         ((uint32_t)0x700) /*!< 0 bits for pre-emption priority
                                                            4 bits for subpriority */
#define NVIC_PriorityGroup_1         ((uint32_t)0x600) /*!< 1 bits for pre-emption priority
                                                            3 bits for subpriority */
#define NVIC_PriorityGroup_2         ((uint32_t)0x500) /*!< 2 bits for pre-emption priority
                                                            2 bits for subpriority */
#define NVIC_PriorityGroup_3         ((uint32_t)0x400) /*!< 3 bits for pre-emption priority
                                                            1 bits for subpriority */
#define NVIC_PriorityGroup_4         ((uint32_t)0x300) /*!< 4 bits for pre-emption priority
                                                            0 bits for subpriority */
																														
void STM32_NVIC_SetPriority(IRQn_Type IRQn,uint32_t PreemptPriority, uint32_t SubPriority);//设置中断优先级
void SysTick_Init(void);//嘀嗒定时器初始化函数

#endif

/**
@code  
 The table below gives the allowed values of the pre-emption priority and subpriority according
 to the Priority Grouping configuration performed by NVIC_PriorityGroupConfig function
  ============================================================================================================================
    NVIC_PriorityGroup   | NVIC_IRQChannelPreemptionPriority | NVIC_IRQChannel | Description
  ============================================================================================================================
   NVIC_PriorityGroup_0  |                0                  |            0-15             |   0 bits for pre-emption priority
                         |                                   |                             |   4 bits for subpriority
  ----------------------------------------------------------------------------------------------------------------------------
   NVIC_PriorityGroup_1  |                0-1                |            0-7              |   1 bits for pre-emption priority
                         |                                   |                             |   3 bits for subpriority
  ----------------------------------------------------------------------------------------------------------------------------    
   NVIC_PriorityGroup_2  |                0-3                |            0-3              |   2 bits for pre-emption priority
                         |                                   |                             |   2 bits for subpriority
  ----------------------------------------------------------------------------------------------------------------------------    
   NVIC_PriorityGroup_3  |                0-7                |            0-1              |   3 bits for pre-emption priority
                         |                                   |                             |   1 bits for subpriority
  ----------------------------------------------------------------------------------------------------------------------------    
   NVIC_PriorityGroup_4  |                0-15               |            0                |   4 bits for pre-emption priority
                         |                                   |                             |   0 bits for subpriority                       
  ============================================================================================================================
@endcode
*/

 

  • 14
    点赞
  • 88
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值