基于STM32与NOR FLASH的SPI通信(有见地,学法可取)
2017-10-05 08:51
387 查看
基于STM32与NOR FLASH的SPI通信http://blog.csdn.net/qq_29344757/article/details/77046724
SPI的通信很容易实现,相比之下,驱动FLASH反而耗费了我学习SPI整个过程的大部分时间。下面是我学习过程的一些记录。
SPI通讯需要4个引脚,nSS、SCK、MISO和MOSI,
以STM32的SPI1为例,其关联GPIO如上图标(摘自《STM32中文参考手册_V10.pdf》-P120)。AFIO_MAPR寄存器的BIT0(SPI1_REMAP)为0时则不重映射SPI1的4个GPIO,nSS、SCK、MISO和MOSI依次为PA4、PA5、PA6、PA7。我们使用寄存器的复位值为0x00,即我们不重映射SPI1关联引脚。开发板原理图的设计也确实如此:
另外,一般在实际工程中,nSS引脚不采用硬件SPI专用的nSS引脚,而是用STM32的一个普通GPIO功能来控制。
2
3
4
5
6
7
8
9
10
11
12
(1) 使能SPI的时钟
GPIO、SCK、MISO、MOSI都是PA组,且SPI外设跟GPIO外设一样,隶属于APB2总线:所以:
(2) 初始化GPIO引脚
设置SPI1的相关引脚为复用输出,这样才会连接到SPI1上否则这些IO还是默认作为标准输入/输出。
2
3
4
5
(3) 初始化SPI1,设置其工作模式
2
3
4
5
6
7
8
9
10
11
(4) 使能SPI1
(5) SPI接收数据
(6) SPI发送数据
(7) 查看SPI传输过程状态
在SPI的传输过程中,若要判断数据是否传输完成,发送缓冲区是否为空等状态,可通过此函数实现。以判断是否发送完成为例:
ISO_V2开发板的板载FLASH型号为W25Q64,是一种使用SPI通讯协议的NOR FLASH存储器。
W25Q64除了上述的SPI关联的4个引脚之外,还有用于控制写保护功能的WP引脚、暂停通讯控制的HOLD引脚。当WP为低电平时禁止写入数据,HOLD为低电平时暂停通讯,硬件上将这两个引脚都接到3.3V,即不使用这两个功能。其他细节,到代码实现的时候补充。
实现功能:往板载W25Q64 FLASH写入数据,再读取出来,调试信息和运行结果通过串口打印到PC机串口工具。
基于具有串口BSP的工程,新建bsp_spi_flash.h和bsp_spi_flash.c。
函数声明:
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
该函数含超时机制,可用于发送/接收一字节数据。至于用于发送还是接收,看用户关注哪一个。
下来是驱动FLASH的相关函数。
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
FLASH_SPI_CS_LOW()和FLASH_SPI_CS_HIGH()是分别实现选中和不选中FLASH片选的宏,其实质就是控制nSS引脚为高/低电平:
2
W25X_JedecDeviceID也是一个宏定义,这是由FLASH定义的用来控制FLASH的指令,从W25Q64手册知,支持的指令有:
这些命令被宏定义在bsp_spi_flash.h文件中:
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
发送完W25X_JedecDeviceID指令后,调用SPI_FLASH_RecvSendByte()函数用于接收FLASH发来的3字节分3次发送的DeviceID,注意,该FLASH的发送顺序是MSB先行,所以接收到的第一、二、三字节需要依次左移16、8、0位。
当SPI_FLASH_RecvSendByte()用于发送数据(指令)时候我们并不关注返回的内容,所以不需要接收其返回值;
当SPI_FLASH_RecvSendByte()用于接收数据时候我们并不关注发送的内容,所以NOTUSEDAT宏是我们任意定义的:
2
3
4
5
6
7
8
9
10
11
12
13
14
获取FLASH的运行状态则是向FLASH发送获取状态的指令W25X_ReadStatusReg。跟上一个函数类似,不赘述。利用此函数的返回值,可以判断FLASH是否处于忙状态:
2
3
4
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
选中FLASH芯片后向其发送W25X_ReadData表示主机要读取FLASH的数据,接着发送要读取的目标地址,还是遵循MSB先行的发送规则,发送完毕就可以接收数据了。
为什么主机发送完指令后,发送的数据会被FLASH解析成目标地址?这是由FLASH定义的。主机要读取ReadCnt个数据,为什么主机不用事先告诉FLASH?这也是FLASH定义的,FLASH就是这么工作的,一旦接收到W25X_ReadData,它就会知道它接下来要收取到一个目标地址,接着只管把从目标地址后的数据发回主机,直至FLASH不被选中。
往FLASH写数据,有3种写范围,写一整个扇区、写一整页、写一个字节,当我们要从某个扇区的开始写入一整个扇区的数据(4096字节),程序需要将对这个扇区分为一页一页来写(256字节),对这一页的写又会转换成一字节一字节的写。
SPI_FLASH_Write(uint8_t* Buf, uint32_t WriteAddr, uint16_t WriteCnt)是用户调来写数据的函数,
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
这个函数是将用户要写的数据进行以扇区为单位写入,一个扇区为4096字节大小,以扇区为单位写,函数为:
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
该函数调用到以页为单位写的函数SPI_FLASH_Write_Page(),一页的空间大小为256字节。
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
以页为单位的写,会调用以字节为单位的写,即前面的SPI_FLASH_RecvSendByte()函数。
上面往FLASH写数据,在调用SPI_FLASH_RecvSendByte()写之前需要调用SPI_FLASH_WriteEnable()使能FLASH的写使能,这样才可以写入数据:
2
3
4
5
6
7
8
写FLASH时一个操作过程,FLASH提供操作命令W25X_ReadStatusReg供给用户判断是否写完成。在等待期写完成后用户才去进行对FLASH的其他操作。
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
读/写FLASH的函数实现完毕,看擦除FLASH的实现。注意,擦除FLASH只能按扇区擦除,该函数的参数为指定哪一块扇区。W25Q64容量为8M,分为128块,每块有16个扇区,每个扇区4096字节。所以参数Addr不能大于2048。
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
编译运行:
SPI的通信很容易实现,相比之下,驱动FLASH反而耗费了我学习SPI整个过程的大部分时间。下面是我学习过程的一些记录。
硬件平台:秉火ISO_V2开发板 实现功能:STM32使用SPI协议读写板载NOR FLASH
1. 通讯引脚
SPI通讯需要4个引脚,nSS、SCK、MISO和MOSI, 以STM32的SPI1为例,其关联GPIO如上图标(摘自《STM32中文参考手册_V10.pdf》-P120)。AFIO_MAPR寄存器的BIT0(SPI1_REMAP)为0时则不重映射SPI1的4个GPIO,nSS、SCK、MISO和MOSI依次为PA4、PA5、PA6、PA7。我们使用寄存器的复位值为0x00,即我们不重映射SPI1关联引脚。开发板原理图的设计也确实如此:
另外,一般在实际工程中,nSS引脚不采用硬件SPI专用的nSS引脚,而是用STM32的一个普通GPIO功能来控制。
2. 软件设计
2.1 SPI初始化结构体
typedef struct { uint16_t SPI_Direction; //SPI的单双向模式 uint16_t SPI_Mode; //SPI的主/从机模式 uint16_t SPI_DataSize; //SPI的数据帧长度,8/16位可选 uint16_t SPI_CPOL; //空闲时钟极性,高低电平 uint16_t SPI_CPHA; //时钟相位,即奇偶边沿采样 uint16_t SPI_NSS; //片选引脚nSS是交由硬件控制还是软件控制 uint16_t SPI_BaudRatePrescaler; //时钟分频系数,FSCK = FCLK / 分频系数 uint16_t SPI_FirstBit; //MSB/LSB先行 uint16_t SPI_CRCPolynomial; //CRC校验表达式 }SPI_InitTypeDef;1
2
3
4
5
6
7
8
9
10
11
12
2.2 操作函数
(1) 使能SPI的时钟 GPIO、SCK、MISO、MOSI都是PA组,且SPI外设跟GPIO外设一样,隶属于APB2总线:所以:
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_SPI1, ENABLE);1
(2) 初始化GPIO引脚
设置SPI1的相关引脚为复用输出,这样才会连接到SPI1上否则这些IO还是默认作为标准输入/输出。
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_6 | GPIO_Pin_7; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure); GPIO_SetBits(GPIOA,GPIO_Pin_5|GPIO_Pin_6|GPIO_Pin_7);1
2
3
4
5
(3) 初始化SPI1,设置其工作模式
SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex; //双线双向全双工 SPI_InitStructure.SPI_Mode = SPI_Mode_Master; //设置为主SPI SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; //SPI发送接收8位数据帧结构 SPI_InitStructure.SPI_CPOL = SPI_CPOL_High; //时钟悬空高 SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge; //数据捕获于第二个时钟沿 SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; //nSS信号软件控制 SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_256; //波特率预分频值为256 SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; //数据传输从MSB位开始 SPI_InitStructure.SPI_CRCPolynomial = 7; //CRC值计算的表达式 SPI_Init(SPI1, &SPI_InitStructure); //将上述设置信息初始化外设SPIx寄存器1
2
3
4
5
6
7
8
9
10
11
(4) 使能SPI1
SPI_Cmd(SPI1, ENABLE);1
(5) SPI接收数据
uint16_t SPI_I2S_ReceiveData(SPI_TypeDef* SPIx);1
(6) SPI发送数据
void SPI_I2S_SendData(SPI_TypeDef* SPIx, uint16_t Data);1
(7) 查看SPI传输过程状态
FlagStatus SPI_I2S_GetFlagStatus(SPI_TypeDef* SPIx, uint16_t SPI_I2S_FLAG);1
在SPI的传输过程中,若要判断数据是否传输完成,发送缓冲区是否为空等状态,可通过此函数实现。以判断是否发送完成为例:
while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET); //发送缓冲区不为空则一直阻塞1
3. FLASH相关特性
ISO_V2开发板的板载FLASH型号为W25Q64,是一种使用SPI通讯协议的NOR FLASH存储器。 W25Q64除了上述的SPI关联的4个引脚之外,还有用于控制写保护功能的WP引脚、暂停通讯控制的HOLD引脚。当WP为低电平时禁止写入数据,HOLD为低电平时暂停通讯,硬件上将这两个引脚都接到3.3V,即不使用这两个功能。其他细节,到代码实现的时候补充。
4. 软件实现
实现功能:往板载W25Q64 FLASH写入数据,再读取出来,调试信息和运行结果通过串口打印到PC机串口工具。基于具有串口BSP的工程,新建bsp_spi_flash.h和bsp_spi_flash.c。
函数声明:
//SPI通信相关函数 void SPI_GPIO_Init(void); void SPI_FLASH_Init(void); uint8_t SPI_FLASH_RecvSendByte(uint8_t byte); //驱动FLASH相关函数 uint32_t SPI_FLASH_ReadID(void); uint8_t SPI_FLASH_Read_SR(void); void SPI_FLASH_Wait_Busy(void); void SPI_FLASH_Read(uint8_t *Buf, uint32_t ReadAddr, uint16_t ReadCnt); void SPI_FLASH_Write(uint8_t* Buf, uint32_t WriteAddr, uint16_t WriteCnt); void SPI_FLASH_WriteEnable(void); void SPI_FLASH_Write_Page(uint8_t* Buf, uint32_t WriteAddr, uint16_t WriteCnt); void SPI_FLASH_Write_Sector(uint8_t* Buf, uint32_t WriteAddr, uint16_t WriteCnt); void SPI_FLASH_WaitForWriteEnd(void); void SPI_FLASH_Erase_Sector(uint32_t Addr);1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
4.1 设置SPI通讯的关联引脚和SPI的工作模式
//设置SPI通信的相关引脚 void SPI_GPIO_Init() { GPIO_InitTypeDef GPIO_InitStruct; //开启GPIOA以及SPI1外设的时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_SPI1, ENABLE); GPIO_InitStruct.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_6 | GPIO_Pin_7; //CLK、MISO、MOSI、 GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出 GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStruct); GPIO_InitStruct.GPIO_Pin = GPIO_Pin_4; //nSS GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出 GPIO_Init(GPIOA, &GPIO_InitStruct); FLASH_SPI_CS_HIGH(); } //设置SPI外设的工作模式 void SPI_FLASH_Init() { SPI_InitTypeDef SPI_InitStruct; SPI_GPIO_Init(); SPI_InitStruct.SPI_Mode = SPI_Mode_Master; //主机端工作模式 SPI_InitStruct.SPI_Direction = SPI_Direction_2Lines_FullDuplex; //双线全双工模式 SPI_InitStruct.SPI_DataSize = SPI_DataSize_8b; //8位数据帧长度 SPI_InitStruct.SPI_CPHA = SPI_CPHA_2Edge; //偶数边沿采SPI信号 SPI_InitStruct.SPI_CPOL = SPI_CPOL_High; //空闲时钟为空 SPI_InitStruct.SPI_FirstBit = SPI_FirstBit_MSB; //高位先行 SPI_InitStruct.SPI_NSS = SPI_NSS_Soft; //使用软件控制片选信号 SPI_InitStruct.SPI_CRCPolynomial = 7; //CRC校验 SPI_InitStruct.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_256; //256分频 SPI_Init(SPI1, &SPI_InitStruct); //打开SPI外设 SPI_Cmd(SPI1, ENABLE); }1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
4.2 通过SPI的硬件接口发送/接收数据
uint8_t SPI_FLASH_RecvSendByte(uint8_t byte) { uint8_t TimeCnt = 0; while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET) //发送缓冲区不为空说明还要数据待发送 { if (TimeCnt++ > 200) return 0; } SPI_I2S_SendData(SPI1, byte); //通过外设SPIx发送一个数据 TimeCnt = 0; while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) == RESET) { if (TimeCnt++ > 200) return 0; } return SPI_I2S_ReceiveData(SPI1); }1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
该函数含超时机制,可用于发送/接收一字节数据。至于用于发送还是接收,看用户关注哪一个。
下来是驱动FLASH的相关函数。
4.3 读取FLASH的存储器类型(ID)
uint32_t SPI_FLASH_ReadID(void) { uint32_t RET_ID; //选中FLASH的片选 FLASH_SPI_CS_LOW(); //发送读取FLASH存储器类型的指令 SPI_FLASH_RecvSendByte(W25X_JedecDeviceID); //接收FLASH发来的存储器类型,它是先发送高位的 RET_ID = SPI_FLASH_RecvSendByte(NOTUSEDAT) << 16; RET_ID |= SPI_FLASH_RecvSendByte(NOTUSEDAT) << 8; RET_ID |= SPI_FLASH_RecvSendByte(NOTUSEDAT); //放开FLASH的片选 FLASH_SPI_CS_HIGH(); return RET_ID; }1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
FLASH_SPI_CS_LOW()和FLASH_SPI_CS_HIGH()是分别实现选中和不选中FLASH片选的宏,其实质就是控制nSS引脚为高/低电平:
#define FLASH_SPI_CS_LOW() GPIO_ResetBits(GPIOA, GPIO_Pin_4) //选中FLASH #define FLASH_SPI_CS_HIGH() GPIO_SetBits(GPIOA, GPIO_Pin_4) //不选中FLASH1
2
W25X_JedecDeviceID也是一个宏定义,这是由FLASH定义的用来控制FLASH的指令,从W25Q64手册知,支持的指令有:
这些命令被宏定义在bsp_spi_flash.h文件中:
#define W25X_WriteEnable 0x06 #define W25X_WriteDisable 0x04 #define W25X_ReadStatusReg 0x05 #define W25X_WriteStatusReg 0x01 #define W25X_ReadData 0x03 #define W25X_FastReadData 0x0B #define W25X_FastReadDual 0x3B #define W25X_PageProgram 0x02 #define W25X_BlockErase 0xD8 #define W25X_SectorErase 0x20 #define W25X_ChipErase 0xC7 #define W25X_PowerDown 0xB9 #define W25X_ReleasePowerDown 0xAB #define W25X_DeviceID 0xAB #define W25X_ManufactDeviceID 0x90 #define W25X_JedecDeviceID 0x9F1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
发送完W25X_JedecDeviceID指令后,调用SPI_FLASH_RecvSendByte()函数用于接收FLASH发来的3字节分3次发送的DeviceID,注意,该FLASH的发送顺序是MSB先行,所以接收到的第一、二、三字节需要依次左移16、8、0位。
当SPI_FLASH_RecvSendByte()用于发送数据(指令)时候我们并不关注返回的内容,所以不需要接收其返回值;
当SPI_FLASH_RecvSendByte()用于接收数据时候我们并不关注发送的内容,所以NOTUSEDAT宏是我们任意定义的:
#define NOTUSEDAT 0xFF1
4.4 读取FLASH的当前运行状态
uint8_t SPI_FLASH_Read_SR(void) { uint8_t ret = 0; FLASH_SPI_CS_LOW(); SPI_FLASH_RecvSendByte(W25X_ReadStatusReg); ret = SPI_FLASH_RecvSendByte(NOTUSEDAT); FLASH_SPI_CS_HIGH(); return ret; }1
2
3
4
5
6
7
8
9
10
11
12
13
14
获取FLASH的运行状态则是向FLASH发送获取状态的指令W25X_ReadStatusReg。跟上一个函数类似,不赘述。利用此函数的返回值,可以判断FLASH是否处于忙状态:
void SPI_FLASH_Wait_Busy(void) { while ((SPI_FLASH_Read_SR() & 0x01) == 0x01); }1
2
3
4
4.5 读取FLASH的数据
void SPI_FLASH_Read(uint8_t *Buf, uint32_t ReadAddr, uint16_t ReadCnt) { uint16_t i; FLASH_SPI_CS_LOW(); SPI_FLASH_RecvSendByte(W25X_ReadData); SPI_FLASH_RecvSendByte(ReadAddr >> 16); SPI_FLASH_RecvSendByte(ReadAddr >> 8); SPI_FLASH_RecvSendByte(ReadAddr); for (i = 0; i < ReadCnt; i++) { Buf[i] = SPI_FLASH_RecvSendByte(NOTUSEDAT); } FLASH_SPI_CS_HIGH(); }1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
选中FLASH芯片后向其发送W25X_ReadData表示主机要读取FLASH的数据,接着发送要读取的目标地址,还是遵循MSB先行的发送规则,发送完毕就可以接收数据了。
为什么主机发送完指令后,发送的数据会被FLASH解析成目标地址?这是由FLASH定义的。主机要读取ReadCnt个数据,为什么主机不用事先告诉FLASH?这也是FLASH定义的,FLASH就是这么工作的,一旦接收到W25X_ReadData,它就会知道它接下来要收取到一个目标地址,接着只管把从目标地址后的数据发回主机,直至FLASH不被选中。
4.6 往FLASH写数据
往FLASH写数据,有3种写范围,写一整个扇区、写一整页、写一个字节,当我们要从某个扇区的开始写入一整个扇区的数据(4096字节),程序需要将对这个扇区分为一页一页来写(256字节),对这一页的写又会转换成一字节一字节的写。 SPI_FLASH_Write(uint8_t* Buf, uint32_t WriteAddr, uint16_t WriteCnt)是用户调来写数据的函数,
//写扇区 -> 写页 -> 按字节写 uint32_t SectorNum; uint16_t SectorOffset; uint16_t SectorRemainder; uint16_t i = 0; uint8_t FLASH_Buf[4096] = {0}; //整个扇区的副本 void SPI_FLASH_Write(uint8_t* Buf, uint32_t WriteAddr, uint16_t WriteCnt) { SectorNum = WriteAddr / 4096; //得到目标地址位于第几个扇区 SectorOffset = WriteAddr % 4096; //得到目标地址在扇区内的偏移量 SectorRemainder = 4096 - SectorOffset; //得到所在扇区还剩下多少空间 if (WriteCnt <= SectorRemainder) //剩下的空间足够写用户要写的数据 { SectorRemainder = WriteCnt; } while (1) { SPI_FLASH_Read(FLASH_Buf, SectorNum * 4096, 4096); //读出整个扇区的内容 //FLASH被擦除后的状态是都为1 for (i = 0; i < SectorRemainder; i++) { if (FLASH_Buf[i + SectorOffset] != 0xff) break; } //i < SectorRemainder说明存在没擦除的区域,那就擦除一整块 if (i < SectorRemainder) { SPI_FLASH_Erase_Sector(SectorNum); for (i = 0; i < SectorRemainder; i++) { FLASH_Buf[i + SectorOffset] = Buf[i]; } //写一整个扇区(其实现会自动转换成按照多页来写) SPI_FLASH_Write_Sector(FLASH_Buf, SectorNum * 4096, 4096); } else //写一整个扇区(其实现会自动转换成按照多页来写) SPI_FLASH_Write_Sector(FLASH_Buf, WriteAddr, SectorRemainder); if (WriteCnt == SectorRemainder) break; else { SectorNum++; SectorOffset = 0; Buf += SectorRemainder; WriteAddr += SectorRemainder; WriteCnt -= SectorRemainder; if (WriteCnt > 4096) { SectorRemainder = 4096; } else SectorRemainder = WriteCnt; } } }1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
这个函数是将用户要写的数据进行以扇区为单位写入,一个扇区为4096字节大小,以扇区为单位写,函数为:
void SPI_FLASH_Write_Sector(uint8_t* Buf, uint32_t WriteAddr, uint16_t WriteCnt) { //uint32_t PageNum; uint16_t PageOffset = WriteAddr % 256; //目标地址在单页的偏移 uint16_t PageRemainder = 256 - PageOffset; //单页剩余的空间 if (WriteCnt <= PageRemainder) PageRemainder = WriteCnt; while (1) { //按照页的区域来写,其内部实现会再以字节为单位去写 SPI_FLASH_Write_Page(Buf, WriteAddr, PageRemainder); if (PageRemainder == WriteCnt) break; else { WriteCnt -= PageRemainder; if (WriteCnt > 256) PageRemainder = 256; else PageRemainder = WriteCnt; } } }1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
该函数调用到以页为单位写的函数SPI_FLASH_Write_Page(),一页的空间大小为256字节。
//按页写 void SPI_FLASH_Write_Page(uint8_t* Buf, uint32_t WriteAddr, uint16_t WriteCnt) { int i; SPI_FLASH_WriteEnable(); FLASH_SPI_CS_LOW(); SPI_FLASH_RecvSendByte(W25X_PageProgram); SPI_FLASH_RecvSendByte(WriteAddr >> 16); SPI_FLASH_RecvSendByte(WriteAddr >> 8); SPI_FLASH_RecvSendByte(WriteAddr); for (i = 0; i < WriteCnt; i++) { SPI_FLASH_RecvSendByte(Buf[i]); } FLASH_SPI_CS_HIGH(); SPI_FLASH_Wait_Busy(); }1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
以页为单位的写,会调用以字节为单位的写,即前面的SPI_FLASH_RecvSendByte()函数。
4.7 使能FLASH写
上面往FLASH写数据,在调用SPI_FLASH_RecvSendByte()写之前需要调用SPI_FLASH_WriteEnable()使能FLASH的写使能,这样才可以写入数据:void SPI_FLASH_WriteEnable(void) { FLASH_SPI_CS_LOW(); SPI_FLASH_RecvSendByte(W25X_WriteEnable); FLASH_SPI_CS_HIGH(); }1
2
3
4
5
6
7
8
4.8 等待写操作完成
写FLASH时一个操作过程,FLASH提供操作命令W25X_ReadStatusReg供给用户判断是否写完成。在等待期写完成后用户才去进行对FLASH的其他操作。//等待写操作执行完毕 void SPI_FLASH_WaitForWriteEnd(void) { uint8_t ret; FLASH_SPI_CS_LOW(); SPI_FLASH_RecvSendByte(W25X_ReadStatusReg); do { ret = SPI_FLASH_RecvSendByte(NOTUSEDAT); }while (ret == RESET); FLASH_SPI_CS_HIGH(); }1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
4.9 擦除FLASH
读/写FLASH的函数实现完毕,看擦除FLASH的实现。注意,擦除FLASH只能按扇区擦除,该函数的参数为指定哪一块扇区。W25Q64容量为8M,分为128块,每块有16个扇区,每个扇区4096字节。所以参数Addr不能大于2048。//擦除整个扇区 void SPI_FLASH_Erase_Sector(uint32_t Addr) { Addr *= 4096; SPI_FLASH_WriteEnable(); SPI_FLASH_Wait_Busy(); FLASH_SPI_CS_LOW(); SPI_FLASH_RecvSendByte(W25X_SectorErase); SPI_FLASH_RecvSendByte(Addr >> 16); SPI_FLASH_RecvSendByte(Addr >> 8); SPI_FLASH_RecvSendByte(Addr); FLASH_SPI_CS_HIGH(); SPI_FLASH_Wait_Busy(); }1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
4.10 main()函数
uint8_t TxBuf[] = "HelloWorld"; #define BUFSZ (sizeof(TxBuf) / sizeof(uint8_t)) uint8_t RxBuf[BUFSZ]; int main(void) { LED_GPIO_Config(); USART1_Config(); SPI_FLASH_Init(); printf("SPI测试实践: \r\n\r\n"); if (SPI_FLASH_ReadID() == FLASHID) { printf("找到FLASH设备,型号为W25X64\r\n\r\n"); LED_Flicker(); //printf("擦出扇区中...\r\n\r\n"); //SPI_FLASH_Erase_Sector(0x00); //printf("擦除完毕!\r\n\r\n"); printf("写入的数据为: %s \r\n\r\n", TxBuf); SPI_FLASH_Write(TxBuf, 0x00000, BUFSZ); SPI_FLASH_Read(RxBuf, 0x00000, BUFSZ); printf("读出的数据为: %s \r\n\r\n", TxBuf); } else { printf("找不到FLASH设备\r\n\r\n"); } while (1); return 0; }1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
编译运行:
相关文章推荐
- STM32_SPI多机通信的实现——整理自网络
- FPGA作为从机与STM32进行SPI协议通信---Verilog实现
- 对STM32 HAL库的一些思考(一)SPI通信的数据格式问题
- DSP与STM32的SPI通信调试及浮点数据传输调试
- 基于STM32的无协议栈实现UDP通信
- 基于STM32的CAN总线通信学习笔记
- 【iCore4 双核心板_FPGA】例程十三:基于SPI的ARM与FPGA通信实验
- FPGA作为从机与STM32进行SPI协议通信---Verilog实现 [转]
- STM32的使用之SPI通信DMA模式
- STM32 基于串口RS485双机通信原理浅析
- 【iCore1S 双核心板_FPGA】例程十六:基于SPI的ARM与FPGA通信实验
- stm32 SPI通信[操作寄存器+库函数]
- 基于CC2530的SPI通信调试笔记
- 【iCore3 双核心板_FPGA】实验十六:基于SPI总线的ARM与FPGA通信实验
- [置顶] ARM开发(5)基于STM32的UART串口通信实验
- 寒假学习之stm32(17)----SPI通信协议
- 基于STM32的CAN通信网-----一种ID配置方法
- FPGA作为从机与STM32进行SPI协议通信---Verilog实现
- 两块STM32之间的SPI通信
- 基于STM32与NOR FLASH的SPI通信