STM32 硬件spi通信篇:读写W25Q128

SPI外设简介

  • STM32内部集成了硬件SPI收发电路,可以由硬件自动执行时钟生成、数据收发等功能,减轻CPU的负担
  • 可配置8/16位数据帧、高位先行/低位先行,常用8位&高位先行
  • 时钟频率: fPCLK / (2, 4, 8, 16, 32, 64, 128, 256)
  • 支持多主机模型、主或从操作
  • 可精简为半双工/单工通信
  • 支持DMA
  • 兼容I2S协议

SPI框图

         左上角移位寄存器,右边的数据低位一位一位从MOSI移出,左边的数据高位一位一位的从MISO移入(低位先行);

        其左侧MOSI和MISO在作为主机时,方框中的交叉线无需使用,MOSI输出,MISO输入;做从机时,MOSI从机输入后经过交叉进入移位寄存器,MISO从机输出,数据从移位寄存器经过交叉后输出到MISO(箭头应该画反了,也许是往下指的);

        移位寄存器上下的接收缓存区RDR和发送缓存区TDR编程上为同一个地址,统一为DR;在具体数据流转运的过程中(作为主机),要发送的数据先进入发送缓存区TDR,当移位寄存器没有数据移位时,TDR中的数据整体转入移位寄存器,并开始移位至MOSI,同时MISO移入移位寄存器,随时钟信号进行一字节的数据交换,在TDR数据转入移位寄存器时,状态寄存器TXE会立刻置1,随后TDR传入下一字节数据;当移位寄存器中一字节数据交换完毕后,移位寄存器中的接收数据会转入接收缓存区RDR中,并置状态寄存器RXNE为1,表示接受寄存器非空,此时需要在下一个数据到来之前读出RDR的数据;

        需注意,TDR用于发送数据,RDR用于接收数据,它们在内核硬件内部是不同的物理寄存器,编程时映射到同一个地址,这是通过外设的硬件逻辑设计实现的。

/************************************************************************************/

与IIC、USART对比:

由于SPI为全双工,发送和接收同步进行,故数据寄存器缓冲区,发送和接收是分离的,移位寄存器则是共用的;

IIC为半双工,发送和接收不同时进行,故发送和接收缓冲区、移位寄存器都是共用的

串口为全双工,发送和接收异步进行,故发送和接收缓冲区、移位寄存器都是分离的

/************************************************************************************/

        波特率发生器用于产生SCK时钟,内部为分频器;右边的SPI_CR1寄存器的三个位:BR0、BR1、BR2用于控制分频系数

        SPI_CR1寄存器:LSB FIRST 决定高位/低位先行        SPE 为SPI使能位

                                     BR 配置波特率                                 MSTR 配置主从模式 1主0从

                                     CPOL、CPHA 选择SPI四种模式     NSS 从机选择,低电平有效,一般用于多主机选择

                                     TEX 发送寄存器空                           RXNE 接受寄存器非空

硬件SPI移位示意图、基本时序单元、模式选择与软件SPI一致:

STM32 软件spi通信篇

硬件SPI运行时序图

主模式全双工连续传输(下图为模式3):

发送运行逻辑:

1、TXE为1表示TDR为空,软件写入数据(0xF1)至TDR寄存器(发送数据缓存区),TXE为0,此时移位寄存器无数据,故TDR中的数据立刻转入移位寄存器并开始发送;

2、数据(0xF1)转入移位寄存器后,TXE置1表示TDR为空,软件写入下一数据(0xF2)至TDR寄存器(发送数据缓存区),等待移位寄存器中的数据发送/接收完毕;

3、移位寄存器中的数据发送/接收完毕后,TDR中的数据(0xF2)立刻转入移位寄存器并开始发送,同时TXE置1,软件写入下一数据(0xF3)至TDR寄存器(发送数据缓存区),等待移位寄存器中的数据发送/接收完毕,以此类推;

接收运行逻辑:

1、在第一个字节(0xF1)发送完毕时,移位寄存器中,第一个字节(0xA1)的接收也同步完成,此时移位寄存器的数据(0xA1)整体转入RDR,同时软件等待RXNE标志位置1,表示RDR中已存入数据,随后从RDR中读出数据(0xA1),同时软件清除RXNE标志位;

2、当下一个数据(0xF2)发送完毕时,移位寄存器中第二个字节的接收(0xA2)也同步完成,后面的步骤同上直至数据交换结束;需注意,RDR存入数据后需及时读出,避免后续数据覆盖导致数据丢失;

连续传输对软件的配合度要求较高,在标志位产生后需及时处理,通信时每个字节之间没有任何空袭,传输效率高

非连续传输

运行逻辑:

 1、TXE为1表示TDR为空,软件写入数据(0xF1)至TDR寄存器(发送数据缓存区),TXE为0,此时移位寄存器无数据,故TDR中的数据立刻转入移位寄存器并开始发送(同连续传输);

2、与连续传输不同,数据(0xF1)转入移位寄存器后,TXE置1表示TDR为空后,不再要求立刻将下一数据写入TDR寄存器,而是等待第一个交换字节的时序结束,随后等待RXNE置1;

3、RXNE为1后,将第一个接收到的数据读出后,再在TDR中写入下一数据(0xF2),随后进入移位寄存器并交换字节;以此类推,重复上述时序直至通信完毕;

代码实现

初始化流程:

        1、开启SPI、GPIO时钟;

        2、初始化GPIO口:SCK、MOSI为输出口,配置为复用推挽输出;MISO为硬件外设的输入信号,可配置为上拉输入;SS为软件控制的输出信号,配置为通用推挽输出即可;

        3、配置SPI外设;

        4、开关控制,调用SPI_Cmd,给SPI使能

SPI相关库函数

//恢复缺省配置
void SPI_I2S_DeInit(SPI_TypeDef* SPIx);
//初始化
void SPI_Init(SPI_TypeDef* SPIx, SPI_InitTypeDef* SPI_InitStruct);
//结构体变量初始化
void SPI_StructInit(SPI_InitTypeDef* SPI_InitStruct);
//外设使能
void SPI_Cmd(SPI_TypeDef* SPIx, FunctionalState NewState);
//中断使能
void SPI_I2S_ITConfig(SPI_TypeDef* SPIx, uint8_t SPI_I2S_IT, FunctionalState NewState);
//DMA使能
void SPI_I2S_DMACmd(SPI_TypeDef* SPIx, uint16_t SPI_I2S_DMAReq, FunctionalState NewState);
//写DR数据寄存器
void SPI_I2S_SendData(SPI_TypeDef* SPIx, uint16_t Data);
//读DR数据寄存器
uint16_t SPI_I2S_ReceiveData(SPI_TypeDef* SPIx);

//获取和清除标志位
FlagStatus SPI_I2S_GetFlagStatus(SPI_TypeDef* SPIx, uint16_t SPI_I2S_FLAG);
void SPI_I2S_ClearFlag(SPI_TypeDef* SPIx, uint16_t SPI_I2S_FLAG);
ITStatus SPI_I2S_GetITStatus(SPI_TypeDef* SPIx, uint8_t SPI_I2S_IT);
void SPI_I2S_ClearITPendingBit(SPI_TypeDef* SPIx, uint8_t SPI_I2S_IT);

实现代码:

main:

#include "stm32f10x.h"                  // Device header
#include "delay.h"
#include "LED.h"
#include "KEY.h"
#include "OLED.h"
#include "W25Q128.h"

int main(void)
{
	delay_init();
	OLED_Init();
	W25Q128_Init();
	
	uint8_t MID;
	uint16_t DID;
	
	uint8_t ArrayWrite[] = {0x01, 0x02, 0x03, 0x04};	//定义要写入数据的测试数组
	uint8_t ArrayRead[4];								//定义要读取数据的测试数组
	
	
	/*显示静态字符串*/
	OLED_ShowString(1, 1, "MID:   DID:");
	OLED_ShowString(2, 1, "W:");
	OLED_ShowString(3, 1, "R:");
	
	/*显示ID号*/
	W25Q128_ReadID(&MID, &DID);			//获取W25Q128的ID号
	OLED_ShowHexNum(1, 5, MID, 2);		
	OLED_ShowHexNum(1, 12, DID, 4);		
	
	/*W25Q64功能函数测试*/
	W25Q128_SectorErase(0x000000);					//指定扇区起始地址来扇区擦除
	W25Q128_PageProgram(0x000000, ArrayWrite, 4);	//将写入数据的测试数组写入到W25Q128中。起始地址:0x000000。写入数组:把ArrayWrite传进去。写入数量:4个字节。
	                                                //这里00是最后2个16进制数的页内地址,前面4位是页地址
	W25Q128_ReadData(0x000000, ArrayRead, 4);		//读取刚写入的测试数据到读取数据的测试数组中。读取数组:把ArrayRead传进去。读取数量:4个字节
	
	/*显示数据*/
	OLED_ShowHexNum(2, 3, ArrayWrite[0], 2);		//显示写入数据的测试数组
	OLED_ShowHexNum(2, 6, ArrayWrite[1], 2);
	OLED_ShowHexNum(2, 9, ArrayWrite[2], 2);
	OLED_ShowHexNum(2, 12, ArrayWrite[3], 2);
	
	OLED_ShowHexNum(3, 3, ArrayRead[0], 2);			//显示读取数据的测试数组
	OLED_ShowHexNum(3, 6, ArrayRead[1], 2);
	OLED_ShowHexNum(3, 9, ArrayRead[2], 2);
	OLED_ShowHexNum(3, 12, ArrayRead[3], 2);
	
	while (1)
	{
		
	}
}

My_SPI.c:

#include "stm32f10x.h"                  // Device header

void MySPI_W_CS(uint8_t BitValue)
{
	GPIO_WriteBit(GPIOB, GPIO_Pin_12, (BitAction)BitValue);
}


void MySPI_Init(void)
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB1Periph_SPI2, ENABLE);
	
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB, &GPIO_InitStructure);
	
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;	
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13 | GPIO_Pin_15;	//MOSI、SCK
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB, &GPIO_InitStructure);

	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_14;					//MISO
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB, &GPIO_InitStructure);
	
	SPI_InitTypeDef SPI_InitStructure;
	SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_64;//64分频
	SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge;				//模式0
	SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;
	SPI_InitStructure.SPI_CRCPolynomial = 7;					//CRC校验的多项式?
	SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;			//8位数据帧
	SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;//全双工
	SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;			//高位先行
	SPI_InitStructure.SPI_Mode = SPI_Mode_Master;				//配置为主机
	SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;					//软件模拟
	SPI_Init(SPI2, &SPI_InitStructure);
	
	SPI_Cmd(SPI2, ENABLE);
	
	MySPI_W_CS(1);
}

void MySPI_Start(void)			//起始信号
{
	MySPI_W_CS(0);	
}

void MySPI_Stop(void)			//中止信号
{
	MySPI_W_CS(1);	
}

uint8_t MySPI_SwapByte(uint8_t ByteSend)//交换一字节
{
	while(SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_TXE) != SET);//等待TXE为1,无需手动清除
	SPI_I2S_SendData(SPI2, ByteSend);
	
	while(SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_RXNE) != SET);//等待RXNE为1,无需手动清除
	return SPI_I2S_ReceiveData(SPI2);
}

W25Q127.c:

#include "stm32f10x.h"                  // Device header
#include "My_SPI.h"
#include "W25Q128_Ins.h"


void W25Q128_Init(void)
{
	MySPI_Init();
}

void W25Q128_ReadID(uint8_t *MID, uint16_t *DID)//读取W25Q128的厂商ID和设备ID
{	
	MySPI_Start();
	MySPI_SwapByte(W25Q128_JEDEC_ID);			//根据指令集,9H为读ID号的指令
	*MID = MySPI_SwapByte(W25Q128_DUMMY_BYTE);	//交换数据,用FF交换厂商ID
	*DID = MySPI_SwapByte(W25Q128_DUMMY_BYTE);	//交换数据,用FF交换设备ID高8位
	*DID <<= 8;
	*DID |= MySPI_SwapByte(W25Q128_DUMMY_BYTE);	//交换数据,用FF交换设备ID低8位
	MySPI_Stop();
}//由于寄存器只读,故FF不会产生影响,且地址指针跟随时钟信号递增


void W25Q128_WriteEnable(void)
{
	MySPI_Start();								//SPI起始
	MySPI_SwapByte(W25Q128_WRITE_ENABLE);		//交换发送写使能的指令
	MySPI_Stop();								//SPI终止
}


void W25Q128_WaitBusy(void)       //等待Busy为0
{
	uint32_t Timeout;
	MySPI_Start();								//SPI起始
	MySPI_SwapByte(W25Q128_READ_STATUS_REGISTER_1);				//交换发送读状态寄存器1的指令
	Timeout = 100000;							//给定超时计数时间
	while ((MySPI_SwapByte(W25Q128_DUMMY_BYTE) & 0x01) == 0x01)	//循环等待忙标志位。   //& 0x01,用掩码取出最低位;==0x01就是BUSY为1。BUSY为1,进入While死循环,进行等待,在次读出一次状态寄存器。BUSY为0,跳出循环。
	{
		Timeout --;								//等待时,计数值自减
		if (Timeout == 0)						//自减到0后,等待超时
		{
			/*超时的错误处理代码,可以添加到此处*/
			break;								//跳出等待,不等了
		}
	}
	MySPI_Stop();								//SPI终止
}


void W25Q128_PageProgram(uint32_t Address, uint8_t *DataArray, uint16_t Count)
{
	uint16_t i;                                 //定义变量
	
	W25Q128_WriteEnable();						//写使能,写使能仅对之后跟随一条时序有效
	
	MySPI_Start();								//SPI起始
	MySPI_SwapByte(W25Q128_PAGE_PROGRAM);		//交换发送页编程的指令
	MySPI_SwapByte(Address >> 16);				//交换发送地址23~16位
	MySPI_SwapByte(Address >> 8);				//交换发送地址15~8位
	MySPI_SwapByte(Address);					//交换发送地址7~0位
	for (i = 0; i < Count; i ++)				//循环Count次
	{
		MySPI_SwapByte(DataArray[i]);			//依次在起始地址后写入数据
	}
	MySPI_Stop();								//SPI终止
	
	W25Q128_WaitBusy();							//事后等待忙,就是写入后,立刻等待,不忙了再退出
}


void W25Q128_SectorErase(uint32_t Address)
{
	W25Q128_WriteEnable();						//写使能,写使能仅对之后跟随一条时序有效
	
	MySPI_Start();								//SPI起始
	MySPI_SwapByte(W25Q128_SECTOR_ERASE_4KB);	//交换发送扇区擦除的指令
	MySPI_SwapByte(Address >> 16);				//交换发送地址23~16位
	MySPI_SwapByte(Address >> 8);				//交换发送地址15~8位
	MySPI_SwapByte(Address);					//交换发送地址7~0位
	MySPI_Stop();								//SPI终止
	
	W25Q128_WaitBusy();							//事后等待忙,就是写入后,立刻等待,不忙了再退出
}


void W25Q128_ReadData(uint32_t Address, uint8_t *DataArray, uint32_t Count)
{
	uint32_t i;
	MySPI_Start();								//SPI起始
	MySPI_SwapByte(W25Q128_READ_DATA);			//交换发送读取数据的指令
	MySPI_SwapByte(Address >> 16);				//交换发送地址23~16位
	MySPI_SwapByte(Address >> 8);				//交换发送地址15~8位
	MySPI_SwapByte(Address);					//交换发送地址7~0位
	for (i = 0; i < Count; i ++)				//循环Count次
	{
		DataArray[i] = MySPI_SwapByte(W25Q128_DUMMY_BYTE);	//依次在起始地址后读取数据
	}
	MySPI_Stop();								//SPI终止
}

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值