stm32——SPI入门学习

SPI是串行外设接口(Serial Peripheral Interface)的缩写,是一种高速的,全双工同步的通信总 线,并且在芯片的管脚上只占用四根线,节约了芯片的管脚,同时为PCB的布局上节省空间,提 供方便,正是出于这种简单易用的特性,越来越多的芯片集成了这种通信协议,比如 AT91RM9200

SPI 工作模式

时钟极性(CPOL):

没有数据传输时时钟线的空闲状态电平
0:SCK在空闲状态保持低电平
1:SCK在空闲状态保持高 电平

时钟相位(CPHA):

时钟线在第几个时钟边沿采样数据
0:SCK的第一(奇数)边沿进行数据位采样,数据在第一个时 钟边沿被锁存
1:SCK的第二(偶数)边沿进行数据位采样,数据在第二个时钟边沿被锁存

工作原理

类似于佛珠
image.png

w25Q128

W25Q128 是华邦公司推出的一款 SPI 接口的 NOR Flash 芯片,其存储空间为 128 Mbit,相当于 16M 字节。 Flash 是常用的用于储存数据的半导体器件,它具有容量大,可重复擦写、按“扇区/块”擦除、掉 电后数据可继续保存的特性。 Flash 是有一个物理特性:只能写 0 ,不能写 1 ,写 1 靠擦除

W25Q128 常用指令

W25Q128 全部指令非常多,但常用的如下几个指令:

  1. 0X06 写使能 写入数据/擦除之前,必须先发送该指令
  2. 0X05 读SR1 判定FLASH是否处于空闲状态,擦除用
  3. 0X03 读数据 用于读取NOR FLASH数据
  4. 0X02 页写 用于写入NOR FLASH数据,最多写256字节
  5. 0X20 扇区擦除 扇区擦除指令,最小擦除单位(4096字节)

写使能 (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片选

示例

image.png

入门demo代码图分解

初始化函数的图分解

image.png

向SPI设备写入数据图分析

image.png

读取数据数据图分析

image.png

总体图

image.png
里面漏了部分的拉高和拉低片选,但结合代码应该不会特别难以理解。

代码

image.png
w25q128.h

#ifndef __W25Q128_H__
#define __W25Q128_H__


#include "stdio.h"
#include "stdint.h"
#include "spi.h"
#include "usart.h"


#define W25Q128_CS_GPIO_PORT GPIOA
#define W25Q128_CS_GPIO_PIN GPIO_PIN_4

#define W25Q128_CS(x) x ? \
			HAL_GPIO_WritePin(W25Q128_CS_GPIO_PORT,W25Q128_CS_GPIO_PIN,GPIO_PIN_SET):\
			HAL_GPIO_WritePin(W25Q128_CS_GPIO_PORT,W25Q128_CS_GPIO_PIN,GPIO_PIN_RESET)


#define W25Q128 0xEF17 /*W25Q128芯片ID*/

/*指令表*/
#define FLASH_WriteEnable 			0x06
#define FLASH_ReadStatusReg1 		0x05
#define FLASH_ReadData 					0x03
#define FLASH_PageProgram 			0x02
#define FLASH_SectorErase 			0x20
#define FLASH_ChipErase 				0xC7												
#define FLASH_ManufactDeviceID 	0x90


void w25q128_write(uint8_t *pbuf, uint32_t addr, uint16_t datalen);
void w25q128_write_page(uint8_t *pbuf, uint32_t addr, uint16_t datalen);
void w25q128_read(uint8_t *pbuf, uint32_t addr, uint16_t datalen);
uint16_t  w25q128_read_id(void);
void w25q128_erase_sector(uint32_t saddr);
void w25q128_erase_chip(void);
void w25q128_write_enable(void);
uint8_t w25q128_rd_sr1(void);				
void w25q128_init(void);



#endif

w25q128.c

#include "w25q128.h"

/*初始化w25q128*/
void w25q128_init(void)
{
	uint16_t flash_type;
	
	spi1_read_write_byte(0xFF);//清除数据缓冲寄存器
	W25Q128_CS(1);//拉高CS
	
	flash_type = w25q128_read_id();//读取FLASH的ID
	printf("flash_type = %x\r\n",flash_type);
	if(flash_type == W25Q128){
		printf("check W25Q128\r\n");
	}
	
}

/*等待空闲*/
static void w25q128_wait_busy(void)
{
	//状态寄存器的最后一位BUSY是表示忙等待的,通过&运算进行获得最后一位
	while((w25q128_rd_sr1() & 0x01) == 0x01);
}

/*读取W25Q128的状态寄存器1的值*/
uint8_t w25q128_rd_sr1(void)
{
	uint8_t rec_data=0;
	
	W25Q128_CS(0);
	//发送读取指定状态寄存器的指令
	spi1_read_write_byte(FLASH_ReadStatusReg1);
	//0xff无效数据,只是为了发送而发送,不需要纠结
	rec_data = spi1_read_write_byte(0xff);
	W25Q128_CS(1);
	
	return rec_data;

}

/*W25Q128写使能*/
void w25q128_write_enable(void)
{
	W25Q128_CS(0);
	//发送写使能指令
	spi1_read_write_byte(FLASH_WriteEnable);
	W25Q128_CS(1);
}

/*w25q128发送地址*/
static void w25q128_send_address(uint32_t address)
{
	//w25q128的最大地址是24位的,
	//即(256个块*16扇区*16页*256个字) - 1=0xFF FFFF,减1是因为地址从0开始
	//而数据缓冲寄存器是8位的,所以要通过右移,来不断的进行数据传输
	//SPI那边应该是不断的左移,然后得到最终地址
	spi1_read_write_byte((uint8_t)(address >> 16));
	spi1_read_write_byte((uint8_t)(address >> 8));
	spi1_read_write_byte((uint8_t)address);
}

/*擦除整个芯片*/
void w25q128_erase_chip(void)
{
	w25q128_write_enable();//写使能
	w25q128_wait_busy();//忙等待
	W25Q128_CS(0);
	
	spi1_read_write_byte(FLASH_ChipErase);//发送擦除整个芯片的命令:0xC7
	W25Q128_CS(1);
	w25q128_wait_busy();
}

/*擦除一个扇区*/
void w25q128_erase_sector(uint32_t saddr)
{
	saddr *= 4096;//因为一个扇区的空间大小就是16页*256字=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();//等待空闲
}



/*读取芯片ID*/
uint16_t  w25q128_read_id(void)
{
	uint16_t deviceid=0;

	W25Q128_CS(0);
	spi1_read_write_byte(FLASH_ManufactDeviceID);//固定写法
	spi1_read_write_byte(0);//固定写法
	spi1_read_write_byte(0);//固定写法
	spi1_read_write_byte(0);//固定写法

	
	deviceid = spi1_read_write_byte(0xff) << 8;//因为寄存器总共只有8位,而芯片ID是16位
	deviceid |= spi1_read_write_byte(0xff);
	
	W25Q128_CS(1);
	
	return deviceid;
}

/*读取SPI FLASH*/
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);

}

/*SPI在一页内写入少于256个字节的数据*/
//指向数据的指针、起始地址、数据长度
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);//发送页写指令:0x02
	w25q128_send_address(addr);//发送数据起始地址
	
	//循环写入
	for(i=0; i<datalen;i++)
	{
		spi1_read_write_byte(pbuf[i]);
	}
	W25Q128_CS(1);
	w25q128_wait_busy();//忙等待
}

/*无检验写SPI_FLASH*/
static void w25q128_write_nocheck(uint8_t *pbuf, uint32_t addr, uint16_t datalen)
{
	uint16_t pageremain;
	
	pageremain = 256 - addr % 256;//获取当前地址到当前页末尾剩余的地址空间大小
	
	if(datalen <= pageremain){
		pageremain = datalen;//如果剩余需要写入的长度小于页空间大小,直接赋值写入即可
	}
	
	while(1){
		//当写入字节比页内剩余地址还少的时候,一次性写完
		//多的话,先写一部分
		w25q128_write_page(pbuf, addr, pageremain);
		
		if(datalen == pageremain){
			break;
		}else {
			//如果没写完,那么更新数据指针的指向
			pbuf += pageremain;
			//更新起始地址
			addr += pageremain;
			//剩余数据长度
			datalen -= pageremain;
			
			//判断下次循环能否一次性写完
			if(datalen > 256){
				pageremain = 256;
			}else {
				pageremain = datalen;
			}
		}
	}
}

/*写SPI_FLASH*/
uint8_t g_w25q128_buf[4096];

//	w25q128_write(datatemp,FLASH_WriteAddress,TEXT_SIZE);
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;//扇区剩余空间大小
	
	if(datalen <= secremain){
		secremain = datalen;
	}
	
	while(1){
		w25q128_read(w25q128_buf, secpos * 4096, 4096);//备份扇区数据
		
		//遍历扇区剩余空间的数据,检测是否需要进行擦除
		//因为写入1只能靠擦除
		for(i=0; i<secremain; i++){
			if(w25q128_buf[secoff + i] != 0xFF){
				break;
			}
		}
		
		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){
			/*
				if(datalen <= secremain){
					secremain = datalen;
				}
			*/
			//如果剩余空间刚好《=要写入的数据的大小,那么直接break,因为已经写完了
			break;
		}else {
			//如果剩余空间不够
			secpos++;//扇区地址加1,相当于加了4096B
			secoff = 0;//偏移量设置为0
			pbuf += secremain;//数据指针移动到剩余数据的头
			addr += secremain;//数据写入SPI设备的地址
			datalen -= secremain;//数据长度降低
			
			if(datalen > 4096){
				//如果还是大于4096,那么代表下一个扇区直接覆盖
				secremain = 4096;
			}else {
				//如果不是,那么赋值
				secremain = datalen;
			}
		}
	}
}

spi.c

uint8_t spi1_read_write_byte(uint8_t data)
{
	uint8_t rec_data=0;
	
	HAL_SPI_TransmitReceive(&hspi1,&data,&rec_data,1,1000);
	
	return rec_data;
}

main.c

int fputc(int ch, FILE *f)
{
	unsigned char temp[1]={ch};
	HAL_UART_Transmit(&huart1,temp,1,0xffff);
	return ch;
}
#define TEXT_SIZE 16
#define FLASH_WriteAddress 0x00000
#define FLASH_ReadAddress FLASH_WriteAddress

char datatemp[TEXT_SIZE];

int main(){
...............
    w25q128_init();
	sprintf((char *)datatemp,"huanghaibo");
	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);
while(1){.........};
...... 
}
	

附件

总体图如下,用Visio画的。如果看不清可以考虑下载
SPI入门流程图

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值