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终止
    }

    作者:陽临

    物联沃分享整理
    物联沃-IOTWORD物联网 » STM32 硬件spi通信篇:读写W25Q128

    发表回复