admin管理员组

文章数量:1026989

[OTA

SPI总线协议的W25Q64主要是用来存放更新的程序

SPI协议是一个四线制的协议,是全双工的,具体的解释可以看看这篇文章

发送流程

SPI数据通信的流程可以分为以下几步:

1、主设备发起信号,将CS/SS拉低,启动通信。

2、主设备通过发送时钟信号,来告诉从设备进行写数据或者读数据操作(采集时机可能是时钟信号的上升沿(从低到高)或下降沿(从高到低),因为SPI有四种模式,后面会讲到),它将立即读取数据线上的信号,这样就得到了一位数据(1bit)。

3、主机(Master)将要发送的数据写到发送数据缓存区(Menory),缓存区经过移位寄存器(缓存长度不一定,看单片机配置),串行移位寄存器通过MOSI信号线将字节一位一位的移出去传送给从机,同时MISO接口接收到的数据经过移位寄存器一位一位的移到接收缓存区。

4、从机(Slave)也将自己的串行移位寄存器(缓存长度不一定,看单片机配置)中的内容通过MISO信号线返回给主机。同时通过MOSI信号线接收主机发送的数据,这样,两个移位寄存器中的内容就被交换。

在数据帧的第一位发送之后,TBE(发送缓冲区空)位置1。TBE(transmit buffer empty)标志位置 1,说明发送缓冲区为空,此时如果需要发送更多数据,软件应该继续写SPI_DATA寄存器。

在主机模式下,若想要实现连续发送功能,那么在当前数据帧发送完成前,软件应该将下一个 数据写入SPI_DATA 寄存器中。 接收流程 在最后一个采样时钟边沿之后,接收到的数据将从移位寄存器存入到接收缓冲区,且 RBNE (接收缓冲区非空)位置1 。软件通过读 SPI_DATA 寄存器获得接收的数据,此操作会自动清除 RBNE 标志位。在MRU 和 MRB 模式中,为了接收下一个数据帧,硬件需要连续发送时钟信号,而在全 双工主机模式(MFD )中,当发送缓冲区非空时,硬件只接收下一个数据帧。 注意: 当 SPI 处于从机模式,输入的时钟周期数不是 8 或 16 (配置的位宽决定)的整数倍个,片选关闭,此时SPI 不会清除计数,片选使能后会再等相应数量的时钟周期后才收发新的数据。 可以通过软件主动复位SPI 模块来解决此问题。

 NSS(CS)低电平有效

注意与IIC有区别IIC是高位在前低位在后的顺序发送数据,SPI是低位在前高位在后 

(144条消息) 一文搞懂SPI通信协议_spi协议_不脱发的程序猿的博客-CSDN博客(144条消息) SPI协议详解(图文并茂+超详细)_小麦大叔的博客-CSDN博客

接下来就按照数据手册来连线W25Q64

 spi.h

#ifndef SPI_H
#define SPI_H
#include <stdint.h>void SPI0_Init(void);
uint8_t SPI0_ReadWriteByte(uint8_t txd);
void SPI0_Write(uint8_t *wdata,uint16_t datalen);
void SPI0_Read(uint8_t *rdata,uint16_t datalen);#endif

 spi.c 

#include "gd32f10x.h"
#include "spi.h"void SPI0_Init(void)
{rcu_periph_clock_enable(RCU_GPIOA);		//开启GPIOB时钟													rcu_periph_clock_enable(RCU_SPI0);	//开启SPI0时钟		gpio_init(GPIOA,GPIO_MODE_OUT_PP,GPIO_OSPEED_50MHZ,GPIO_PIN_7|GPIO_PIN_5);	gpio_init(GPIOA,GPIO_MODE_IN_FLOATING,GPIO_OSPEED_50MHZ,GPIO_PIN_6);	spi_parameter_struct spi_parameter ={.device_mode 					= SPI_MASTER , // 主机模式.trans_mode  					= SPI_TRANSMODE_FULLDUPLEX ,// 全双工模式.frame_size  					= SPI_FRAMESIZE_8BIT , // 8位数据.nss							  	= SPI_NSS_SOFT	,	//软件片选.endian    					  = SPI_ENDIAN_MSB ,//配置字节序.clock_polarity_phase =	SPI_CK_PL_LOW_PH_1EDGE ,//空闲电平为低,第一个边沿上升沿采样.prescale							= SPI_PSC_2 //时钟二分频};spi_i2s_deinit(SPI0); //复位SPI0spi_init(SPI0,&spi_parameter);//初始化SPI0spi_enable(SPI0);//使能SPI0
}uint8_t SPI0_ReadWriteByte(uint8_t txd)
{while(spi_i2s_flag_get(SPI0,SPI_FLAG_TBE) != 1); //等待TBE标志位置1说明发送寄存器为空可以发送spi_i2s_data_transmit(SPI0,txd);while(spi_i2s_flag_get(SPI0,SPI_FLAG_RBNE) != 1); //等待RBNE标志位置1说了接收寄存器不为空,可以接收return spi_i2s_data_receive(SPI0);
}void SPI0_Write(uint8_t *wdata,uint16_t datalen)
{uint16_t i;for(i=0;i<datalen;i++){SPI0_ReadWriteByte(wdata[i]);}
}void SPI0_Read(uint8_t *rdata,uint16_t datalen)
{uint16_t i;for(i=0;i<datalen;i++){rdata[i] = SPI0_ReadWriteByte(0xff);}
}

w25q64 :是先发送下一步的命令再执行,且收发数据之前需要检查是否忙

地址的计算:地址编号*大小(比如64K = 64*1024)

 

W25Q64.h

#ifndef W25Q64_H
#define W25Q64_H#include <stdint.h>#define CS_ENABLE  		gpio_bit_reset(GPIOA,GPIO_PIN_4)
#define CS_DISENABLE  gpio_bit_set(GPIOA,GPIO_PIN_4)void W25Q64_Init(void);
void W25Q64_WaitBusy(void);
void W25Q64_WriteEnable(void);
void W25Q64_Erase64K(uint8_t BlockNum);
void W25Q64_PageWrite(uint8_t *wbuffer,uint16_t PageNum);
void W25Q64_Read(uint8_t *rbuffer,uint32_t addr,uint32_t datalen);#endif

W25Q64.c 

#include "gd32f10x.h"
#include "spi.h"
#include "w25q64.h"void W25Q64_Init(void){rcu_periph_clock_enable(RCU_GPIOA);gpio_init(GPIOA,GPIO_MODE_OUT_PP,GPIO_OSPEED_50MHZ,GPIO_PIN_4);CS_DISENABLE;SPI0_Init();	
}void W25Q64_WaitBusy(void){uint8_t res;do{CS_ENABLE;SPI0_ReadWriteByte(0x05);res = SPI0_ReadWriteByte(0xff);//不关心发送什么,在意的是返回回来的值,因为是全双工发送一个数必须返回一个数回来,SPI是先发送命令让他知道下一个动作想干嘛CS_DISENABLE;}while((res&0x01)==0x01);
}void W25Q64_Enable(void){W25Q64_WaitBusy();CS_ENABLE;SPI0_ReadWriteByte(0x06);CS_DISENABLE;
}void W25Q64_Erase64K(uint8_t blockNB){uint8_t wdata[4];wdata[0] = 0xD8;wdata[1] = (blockNB*64*1024)>>16; //根据数据手册可知,先传命令然后传输地址wdata[2] = (blockNB*64*1024)>>8;wdata[3] = (blockNB*64*1024)>>0;W25Q64_WaitBusy();W25Q64_Enable();CS_ENABLE;SPI0_Write(wdata,4);CS_DISENABLE;W25Q64_WaitBusy();
}void W25Q64_PageWrite(uint8_t *wbuff, uint16_t pageNB){uint8_t wdata[4];wdata[0] = 0x02;wdata[1] = (pageNB*256)>>16;wdata[2] = (pageNB*256)>>8;wdata[3] = (pageNB*256)>>0;W25Q64_WaitBusy();W25Q64_Enable();CS_ENABLE;SPI0_Write(wdata,4);SPI0_Write(wbuff,256);CS_DISENABLE;
}void W25Q64_Read(uint8_t *rbuff, uint32_t addr, uint32_t datalen){uint8_t wdata[4];wdata[0] = 0x03;wdata[1] = (addr)>>16;wdata[2] = (addr)>>8;wdata[3] = (addr)>>0;W25Q64_WaitBusy();CS_ENABLE;SPI0_Write(wdata,4);SPI0_Read(rbuff,datalen);CS_DISENABLE;
}

main.c

测试代码每个页输出不同的值

#include "gd32f10x.h"
#include "usart.h"
#include "sys.h"
#include "iic.h"
#include "AT24C02.h"
#include "w25q64.h"
#include "spi.h"uint8_t wdata[256];
uint8_t rdata[256];int main(void){uint16_t i,j;Sys_Init();Usart0_Init(921600);W25Q64_Init();W25Q64_Erase64K(0);for(i=0;i<256;i++){for(j=0;j<256;j++) wdata[j] = i;W25Q64_PageWrite(wdata,i);}Ms_Delay(50);for(i=0;i<256;i++){W25Q64_Read(rdata,i*256,256);for(j=0;j<256;j++) u0_printf("地址%d=%x\r\n",i*256+j,rdata[j]);}while(1){}
}

[OTA

SPI总线协议的W25Q64主要是用来存放更新的程序

SPI协议是一个四线制的协议,是全双工的,具体的解释可以看看这篇文章

发送流程

SPI数据通信的流程可以分为以下几步:

1、主设备发起信号,将CS/SS拉低,启动通信。

2、主设备通过发送时钟信号,来告诉从设备进行写数据或者读数据操作(采集时机可能是时钟信号的上升沿(从低到高)或下降沿(从高到低),因为SPI有四种模式,后面会讲到),它将立即读取数据线上的信号,这样就得到了一位数据(1bit)。

3、主机(Master)将要发送的数据写到发送数据缓存区(Menory),缓存区经过移位寄存器(缓存长度不一定,看单片机配置),串行移位寄存器通过MOSI信号线将字节一位一位的移出去传送给从机,同时MISO接口接收到的数据经过移位寄存器一位一位的移到接收缓存区。

4、从机(Slave)也将自己的串行移位寄存器(缓存长度不一定,看单片机配置)中的内容通过MISO信号线返回给主机。同时通过MOSI信号线接收主机发送的数据,这样,两个移位寄存器中的内容就被交换。

在数据帧的第一位发送之后,TBE(发送缓冲区空)位置1。TBE(transmit buffer empty)标志位置 1,说明发送缓冲区为空,此时如果需要发送更多数据,软件应该继续写SPI_DATA寄存器。

在主机模式下,若想要实现连续发送功能,那么在当前数据帧发送完成前,软件应该将下一个 数据写入SPI_DATA 寄存器中。 接收流程 在最后一个采样时钟边沿之后,接收到的数据将从移位寄存器存入到接收缓冲区,且 RBNE (接收缓冲区非空)位置1 。软件通过读 SPI_DATA 寄存器获得接收的数据,此操作会自动清除 RBNE 标志位。在MRU 和 MRB 模式中,为了接收下一个数据帧,硬件需要连续发送时钟信号,而在全 双工主机模式(MFD )中,当发送缓冲区非空时,硬件只接收下一个数据帧。 注意: 当 SPI 处于从机模式,输入的时钟周期数不是 8 或 16 (配置的位宽决定)的整数倍个,片选关闭,此时SPI 不会清除计数,片选使能后会再等相应数量的时钟周期后才收发新的数据。 可以通过软件主动复位SPI 模块来解决此问题。

 NSS(CS)低电平有效

注意与IIC有区别IIC是高位在前低位在后的顺序发送数据,SPI是低位在前高位在后 

(144条消息) 一文搞懂SPI通信协议_spi协议_不脱发的程序猿的博客-CSDN博客(144条消息) SPI协议详解(图文并茂+超详细)_小麦大叔的博客-CSDN博客

接下来就按照数据手册来连线W25Q64

 spi.h

#ifndef SPI_H
#define SPI_H
#include <stdint.h>void SPI0_Init(void);
uint8_t SPI0_ReadWriteByte(uint8_t txd);
void SPI0_Write(uint8_t *wdata,uint16_t datalen);
void SPI0_Read(uint8_t *rdata,uint16_t datalen);#endif

 spi.c 

#include "gd32f10x.h"
#include "spi.h"void SPI0_Init(void)
{rcu_periph_clock_enable(RCU_GPIOA);		//开启GPIOB时钟													rcu_periph_clock_enable(RCU_SPI0);	//开启SPI0时钟		gpio_init(GPIOA,GPIO_MODE_OUT_PP,GPIO_OSPEED_50MHZ,GPIO_PIN_7|GPIO_PIN_5);	gpio_init(GPIOA,GPIO_MODE_IN_FLOATING,GPIO_OSPEED_50MHZ,GPIO_PIN_6);	spi_parameter_struct spi_parameter ={.device_mode 					= SPI_MASTER , // 主机模式.trans_mode  					= SPI_TRANSMODE_FULLDUPLEX ,// 全双工模式.frame_size  					= SPI_FRAMESIZE_8BIT , // 8位数据.nss							  	= SPI_NSS_SOFT	,	//软件片选.endian    					  = SPI_ENDIAN_MSB ,//配置字节序.clock_polarity_phase =	SPI_CK_PL_LOW_PH_1EDGE ,//空闲电平为低,第一个边沿上升沿采样.prescale							= SPI_PSC_2 //时钟二分频};spi_i2s_deinit(SPI0); //复位SPI0spi_init(SPI0,&spi_parameter);//初始化SPI0spi_enable(SPI0);//使能SPI0
}uint8_t SPI0_ReadWriteByte(uint8_t txd)
{while(spi_i2s_flag_get(SPI0,SPI_FLAG_TBE) != 1); //等待TBE标志位置1说明发送寄存器为空可以发送spi_i2s_data_transmit(SPI0,txd);while(spi_i2s_flag_get(SPI0,SPI_FLAG_RBNE) != 1); //等待RBNE标志位置1说了接收寄存器不为空,可以接收return spi_i2s_data_receive(SPI0);
}void SPI0_Write(uint8_t *wdata,uint16_t datalen)
{uint16_t i;for(i=0;i<datalen;i++){SPI0_ReadWriteByte(wdata[i]);}
}void SPI0_Read(uint8_t *rdata,uint16_t datalen)
{uint16_t i;for(i=0;i<datalen;i++){rdata[i] = SPI0_ReadWriteByte(0xff);}
}

w25q64 :是先发送下一步的命令再执行,且收发数据之前需要检查是否忙

地址的计算:地址编号*大小(比如64K = 64*1024)

 

W25Q64.h

#ifndef W25Q64_H
#define W25Q64_H#include <stdint.h>#define CS_ENABLE  		gpio_bit_reset(GPIOA,GPIO_PIN_4)
#define CS_DISENABLE  gpio_bit_set(GPIOA,GPIO_PIN_4)void W25Q64_Init(void);
void W25Q64_WaitBusy(void);
void W25Q64_WriteEnable(void);
void W25Q64_Erase64K(uint8_t BlockNum);
void W25Q64_PageWrite(uint8_t *wbuffer,uint16_t PageNum);
void W25Q64_Read(uint8_t *rbuffer,uint32_t addr,uint32_t datalen);#endif

W25Q64.c 

#include "gd32f10x.h"
#include "spi.h"
#include "w25q64.h"void W25Q64_Init(void){rcu_periph_clock_enable(RCU_GPIOA);gpio_init(GPIOA,GPIO_MODE_OUT_PP,GPIO_OSPEED_50MHZ,GPIO_PIN_4);CS_DISENABLE;SPI0_Init();	
}void W25Q64_WaitBusy(void){uint8_t res;do{CS_ENABLE;SPI0_ReadWriteByte(0x05);res = SPI0_ReadWriteByte(0xff);//不关心发送什么,在意的是返回回来的值,因为是全双工发送一个数必须返回一个数回来,SPI是先发送命令让他知道下一个动作想干嘛CS_DISENABLE;}while((res&0x01)==0x01);
}void W25Q64_Enable(void){W25Q64_WaitBusy();CS_ENABLE;SPI0_ReadWriteByte(0x06);CS_DISENABLE;
}void W25Q64_Erase64K(uint8_t blockNB){uint8_t wdata[4];wdata[0] = 0xD8;wdata[1] = (blockNB*64*1024)>>16; //根据数据手册可知,先传命令然后传输地址wdata[2] = (blockNB*64*1024)>>8;wdata[3] = (blockNB*64*1024)>>0;W25Q64_WaitBusy();W25Q64_Enable();CS_ENABLE;SPI0_Write(wdata,4);CS_DISENABLE;W25Q64_WaitBusy();
}void W25Q64_PageWrite(uint8_t *wbuff, uint16_t pageNB){uint8_t wdata[4];wdata[0] = 0x02;wdata[1] = (pageNB*256)>>16;wdata[2] = (pageNB*256)>>8;wdata[3] = (pageNB*256)>>0;W25Q64_WaitBusy();W25Q64_Enable();CS_ENABLE;SPI0_Write(wdata,4);SPI0_Write(wbuff,256);CS_DISENABLE;
}void W25Q64_Read(uint8_t *rbuff, uint32_t addr, uint32_t datalen){uint8_t wdata[4];wdata[0] = 0x03;wdata[1] = (addr)>>16;wdata[2] = (addr)>>8;wdata[3] = (addr)>>0;W25Q64_WaitBusy();CS_ENABLE;SPI0_Write(wdata,4);SPI0_Read(rbuff,datalen);CS_DISENABLE;
}

main.c

测试代码每个页输出不同的值

#include "gd32f10x.h"
#include "usart.h"
#include "sys.h"
#include "iic.h"
#include "AT24C02.h"
#include "w25q64.h"
#include "spi.h"uint8_t wdata[256];
uint8_t rdata[256];int main(void){uint16_t i,j;Sys_Init();Usart0_Init(921600);W25Q64_Init();W25Q64_Erase64K(0);for(i=0;i<256;i++){for(j=0;j<256;j++) wdata[j] = i;W25Q64_PageWrite(wdata,i);}Ms_Delay(50);for(i=0;i<256;i++){W25Q64_Read(rdata,i*256,256);for(j=0;j<256;j++) u0_printf("地址%d=%x\r\n",i*256+j,rdata[j]);}while(1){}
}

本文标签: OTA