STM32笔记归纳5:SPI

SPI

目录

SPI

一、电路结构和通信协议

1.1.SPI总线的电路结构

1.2.通信流程及波形

1.2.1.建立通信

1.2.2.发送时钟

1.2.3.发送数据

1.3.参数1:时钟信号的极性和相位

1.3.1.极性

1.3.2.相位

1.4.SPI的4种模式

1.4.1.模式0

1.4.2.模式1

1.4.3.模式2

1.4.4.模式3

1.5.参数2:传输顺序

1.5.1.低位先行

1.5.2.高位先行

1.6.参数3:数据宽度

[1.6.1. 8 bit](#1.6.1. 8 bit)

[1.6.2. 16 bit](#1.6.2. 16 bit)

二、按钮驱动程序编写

2.1.连接电路

2.2.初始化板载LED和按钮

2.3.按钮程序的基本原理

2.4.按钮消抖的原理

三、按钮代码的封装

3.1.搭建电路

3.2.串口的初始化

3.3.按钮的初始化

3.4.进程函数

3.5.回调函数

3.5.1.按钮点击函数

3.5.2.按钮长按函数

四、IO引脚初始化

4.1.SPI模块简介

4.2.W25Q64简介

4.3.定位SPI的引脚位置

4.4.选择IO引脚的模式

4.5.选择IO最大输出速度

4.6.初始化IO引脚

五、SPI模块的初始化

5.1.SPI模块的基本工作原理

5.2.SPI_Init

5.3.选择数据通信方向

[5.3.1. 2线全双工](#5.3.1. 2线全双工)

[5.3.2. 2线只读](#5.3.2. 2线只读)

[5.3.3. 1线发送](#5.3.3. 1线发送)

[5.3.4. 1线接收](#5.3.4. 1线接收)

5.4.数据宽度、极性、相位和比特位传输顺序

5.4.1.数据宽度

5.4.2.极性和相位

5.4.3.比特位传输顺序

​编辑

5.5.设置波特率

5.6.NSS的配置方式

5.6.1.多主机模式

5.6.2.NSS配置

5.7.总代码

六、数据收发

6.1.SPI数据收发的特点

6.2.声明数据收发的编程接口

6.3.SPI数据收发过程简介

6.4.代码编写

七、W25Q64实验1

7.1.PA15的重映射

7.2.W25Q64的存储结构

7.3.使用W25Q64存储数据

7.3.1.写使能

7.3.2.扇区擦除

7.3.3.等待空闲

7.3.4.页编程

7.3.5.总代码

7.4.使用W25Q64读取数据

7.5.测试代码

八、W25Q64实验2

8.1.初始化板载LED

8.2.初始化按钮

8.4.保存LED的当前状态

8.5.恢复LED状态

8.6.总代码


一、电路结构和通信协议

1.1.SPI总线的电路结构

**MOSI(Master Output Slave Input):**主发从收

**MISO(Master Input Slave Output):**主收从发

**SCK(Serial Clock):**串行时钟

**NSS(Negative Slave Select):**从机选择(低电压有效)

1.2.通信流程及波形

1.2.1.建立通信

主机通过IO引脚

向通信从机的NSS引脚发送低电压

向其他从机的NSS引脚发送高电压

1.2.2.发送时钟

通信时,主机通过SCK引脚

向通信从机的SCK引脚发送时钟信号

1.2.3.发送数据

每一个时钟周期在MOSI和MISO上各传输一个bit位

低电平表示0,高电平表示1

主机在发送一个bit位的同时,从机也会返回一个bit位

1.3.参数1:时钟信号的极性和相位

1.3.1.极性

**低极性:**空闲状态下,SCK引脚保持低电平

**高极性:**空闲状态下,SCK引脚保持高电平

1.3.2.相位

**第1边沿:**空闲状态后,第一个出现的边沿

**第2边沿:**空闲状态后,第二个出现的边沿

1.4.SPI的4种模式

1.4.1.模式0

低极性第一边沿采集

1.4.2.模式1

低极性第二边沿采集

1.4.3.模式2

高极性第一边沿采集

1.4.4.模式3

高极性第二边沿采集

1.5.参数2:传输顺序

每个字节比特位的传输顺序

1.5.1.低位先行

先传最低有效位(LSB First)

1.5.2.高位先行

先传最高有效位(MSB First)

1.6.参数3:数据宽度

每次传输比特位的个数

1.6.1. 8 bit
1.6.2. 16 bit

二、按钮驱动程序编写

使用按钮切换板载LED亮灭状态

2.1.连接电路

按钮接法:

PA0 输入上拉模式

  • 按钮按下:0
  • 按钮松开:1

板载LED接法:

PC13 输出开漏模式

  • 写1:LED熄灭
  • 写0:LED点亮

2.2.初始化板载LED和按钮

cpp 复制代码
#include "stm32f10x.h"

void App_Button_Init(void);
void App_OnBoardLED_Init(void);
	
int main(void)
{
	App_Button_Init();//按钮初始化
	App_OnBoardLED_Init();//LED初始化
	
	while(1)
	{
	}
}

void App_Button_Init(void)
{
	/*启动GPIOA时钟*/
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	/*声明GPIO结构变量*/
	GPIO_InitTypeDef GPIO_InitStruct;
	/*选择PA0引脚*/
	GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0;
	/*输入上拉模式*/
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU;
	/*初始化PA0引脚*/
	GPIO_Init(GPIOA,&GPIO_InitStruct);
}

void App_OnBoardLED_Init(void)
{
	/*启动GPIOC时钟*/
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC,ENABLE);
	/*声明GPIO结构变量*/
	GPIO_InitTypeDef GPIO_InitStruct;
	/*选择PC13引脚*/
	GPIO_InitStruct.GPIO_Pin = GPIO_Pin_13;
	/*输出开漏模式*/
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_OD;
	/*最大输出速率为2MHz*/
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_2MHz;
	/*初始化PA0引脚*/
	GPIO_Init(GPIOC,&GPIO_InitStruct);
}

2.3.按钮程序的基本原理

松开按钮时为高电平1

按下按钮时为低电平0

创建变量prev和cur,prev记录上次引脚电平,cur记录当前引脚电平

当上次为1,当前为0时:按下按钮

当上次为0,当前为1时:松开按钮

cpp 复制代码
int main(void)
{
	App_Button_Init();//按钮初始化
	App_OnBoardLED_Init();//LED初始化
	
	uint8_t previous = Bit_SET;
	uint8_t current = Bit_SET;
	
	while(1)
	{
		previous = current;//保存上次状态
		current = GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_0);//更新当前状态
		if(previous != current)//如果上次与当前不等
		{
			if(current == Bit_SET)//按钮松开
			{
				//改变LED的亮灭状态
			}
			else//按钮按下
			{
			}
		}
	}
}

改变LED亮灭状态

cpp 复制代码
void GPIO_WriteBit(GPIO_TypeDef* GPIOx,uint16_t GPIO_Pin,BitAction BitVal);

**作用:**向输出数据寄存器写0/1

cpp 复制代码
uint8_t GPIO_ReadOutputDataBit(GPIO_TypeDef* GPIOx,uint16_t GPIO_Pin);

**作用:**读取输出数据寄存器的一个比特位

cpp 复制代码
int main(void)
{
	App_Button_Init();//按钮初始化
	App_OnBoardLED_Init();//LED初始化
	
	uint8_t previous = Bit_SET;
	uint8_t current = Bit_SET;
	
	while(1)
	{
		previous = current;//保存上次状态
		current = GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_0);//更新当前状态
		if(previous != current)//如果上次与当前不等
		{
			if(current == Bit_SET)//按钮松开
			{
				//读取输出数据寄存器的值
				if(GPIO_ReadOutputDataBit(GPIOC,GPIO_Pin_13) == Bit_SET)//如果是1
				{
					GPIO_WriteBit(GPIOC,GPIO_Pin_13,Bit_RESET);//写0
				}
				else//如果是0
				{
					GPIO_WriteBit(GPIOC,GPIO_Pin_13,Bit_SET);//写1
				}
			}
			else//按钮按下
			{
			}
		}
	}
}

2.4.按钮消抖的原理

cpp 复制代码
#include "stm32f10x.h"
#include "delay.h"

void App_Button_Init(void);
void App_OnBoardLED_Init(void);
	
int main(void)
{
	App_Button_Init();//按钮初始化
	App_OnBoardLED_Init();//LED初始化
	
	uint8_t previous = Bit_SET;
	uint8_t current = Bit_SET;
	
	while(1)
	{
		previous = current;//保存上次状态
		current = GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_0);//更新当前状态
		if(previous != current)//如果上次与当前不等
		{
			if(current == Bit_SET)//按钮松开
			{
				//读取输出数据寄存器的值
				if(GPIO_ReadOutputDataBit(GPIOC,GPIO_Pin_13) == Bit_SET)//如果是1
				{
					GPIO_WriteBit(GPIOC,GPIO_Pin_13,Bit_RESET);//写0
				}
				else//如果是0
				{
					GPIO_WriteBit(GPIOC,GPIO_Pin_13,Bit_SET);//写1
				}
			}
			else//按钮按下
			{
			}
			Delay(10);
		}
	}
}

三、按钮代码的封装

3.1.搭建电路

3.2.串口的初始化

cpp 复制代码
#include "stm32f10x.h"
#include "usart.h"//引入串口头文件

/*声明串口初始化函数*/
void App_USART1_Init(void);

int main(void)
{
    /*初始化串口*/
	App_USART1_Init();
	
    /*测试串口*/
	My_USART_SendString(USART1,"Hello world\r\n");
	
	while(1)
	{
	}
}

/*创建串口初始化函数*/
void App_USART1_Init(void)
{
	//#1:初始化IO引脚 PA9 Tx PA10 Rx
	
	/*开启GPIOA时钟*/
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	/*声明GPIO结构变量*/
	GPIO_InitTypeDef GPIO_InitStruct;
	/*选择PA9引脚*/
	GPIO_InitStruct.GPIO_Pin = GPIO_Pin_9;
	/*复用输出推挽模式*/
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
	/*最大输出速率为2MHz*/
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_2MHz;
	/*初始化PA9引脚*/
	GPIO_Init(GPIOA,&GPIO_InitStruct);
	/*选择PA10引脚*/
	GPIO_InitStruct.GPIO_Pin = GPIO_Pin_10;
	/*输入上拉模式*/
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU;
	/*初始化PA9引脚*/
	GPIO_Init(GPIOA,&GPIO_InitStruct);
	
	//#2:初始化USART1
	
	/*开启USART1时钟*/
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);
	/*声明USART1结构变量*/
	USART_InitTypeDef USART_InitStruct;
	/*波特率为115200*/
	USART_InitStruct.USART_BaudRate = 115200;
	/*收发方向为双向*/
	USART_InitStruct.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;
	/*数据位为8位*/
	USART_InitStruct.USART_WordLength = USART_WordLength_8b;
	/*校验方式为无*/
	USART_InitStruct.USART_Parity = USART_Parity_No;
	/*停止位为1位*/
	USART_InitStruct.USART_StopBits = USART_StopBits_1;
	/*初始化USART1*/
	USART_Init(USART1,&USART_InitStruct);
	/*闭合USART1总开关*/
	USART_Cmd(USART1,ENABLE);
}

3.3.按钮的初始化

cpp 复制代码
void My_Button_Init(Button_TypeDef* Button,Button_InitTypeDef* Button_InistStruct);

解析:

  • 参数1:按钮结构地址
  • 参数2:初始化的参数结构体地址

**作用:**初始化按钮,配置按钮的各种参数(包括IO引脚)

**补充:**Button_InitTypeDef结构(按钮参数菜单)

cpp 复制代码
struct GPIO_InitTypeDef
{
    GPIO_TypeDef* GPIOx;
    uint16_t GPIO_Pin;
    uint32_t LongPressTime;
    uint32_t LongPressInterval;
    uint32_t ClickInterval;
    void(*button_pressed_cb)(void);
    void(*button_released_cb)(void);
    void(*button_clicked_cb)(uint8_t clicks);
    void(*button_Long_pressed_cb)(uint8_t clicks);
};

分析:

**1.GPIOx:**IO引脚的端口号

**2.GPIO_Pin:**IO引脚的引脚编号

**3.LongPressTime:**长按的时间阈值(单位:毫秒,0表示默认1000)

**4.LongPressInterval:**长按后持续触发的时间间隔(0表示默认100)

**5.ClickInterval:**连击的最大间隔(0表示默认200)

**6.button_pressed_cb:**按钮按下

**7.button_released_cb:**按钮抬起

**8.button_clicked_cb:**按钮点击

**9.button_Long_pressed_cb:**按钮长按

**注:**最后四个回调函数没有就填0

cpp 复制代码
#include "stm32f10x.h"
#include "usart.h"//引入串口头文件
#include "button.h"//引入按钮头文件

/*声明按钮结构变量*/
Button_TypeDef button1;
/*声明串口初始化函数*/
void App_USART1_Init(void);
/*声明按钮初始化函数*/
void App_Button_Init(void);

int main(void)
{
	/*初始化串口*/
	App_USART1_Init();
	
	/*初始化按钮1*/
	App_Button_Init();
	
	while(1)
	{
        /*按钮进程函数*/
        My_Button_Proc(&button1);
	}
}

/*创建串口初始化函数*/
void App_USART1_Init(void)
{
	//#1:初始化IO引脚 PA9 Tx PA10 Rx
	
	/*开启GPIOA时钟*/
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	/*声明GPIO结构变量*/
	GPIO_InitTypeDef GPIO_InitStruct;
	/*选择PA9引脚*/
	GPIO_InitStruct.GPIO_Pin = GPIO_Pin_9;
	/*复用输出推挽模式*/
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
	/*最大输出速率为2MHz*/
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_2MHz;
	/*初始化PA9引脚*/
	GPIO_Init(GPIOA,&GPIO_InitStruct);
	/*选择PA10引脚*/
	GPIO_InitStruct.GPIO_Pin = GPIO_Pin_10;
	/*输入上拉模式*/
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU;
	/*初始化PA9引脚*/
	GPIO_Init(GPIOA,&GPIO_InitStruct);
	
	//#2:初始化USART1
	
	/*开启USART1时钟*/
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);
	/*声明USART1结构变量*/
	USART_InitTypeDef USART_InitStruct;
	/*波特率为115200*/
	USART_InitStruct.USART_BaudRate = 115200;
	/*收发方向为双向*/
	USART_InitStruct.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;
	/*数据位为8位*/
	USART_InitStruct.USART_WordLength = USART_WordLength_8b;
	/*校验方式为无*/
	USART_InitStruct.USART_Parity = USART_Parity_No;
	/*停止位为1位*/
	USART_InitStruct.USART_StopBits = USART_StopBits_1;
	/*初始化USART1*/
	USART_Init(USART1,&USART_InitStruct);
	/*闭合USART1总开关*/
	USART_Cmd(USART1,ENABLE);
}

/*创建按钮初始化函数*/
void App_Button_Init(void)
{
	/*声明按钮初始化结构变量*/
	Button_InitTypeDef Button_InitStruct;
	/*IO引脚的端口号设为GPIOA*/
	Button_InitStruct.GPIOx = GPIOA;
	/*IO引脚的引脚编号设为0*/
	Button_InitStruct.GPIO_Pin = GPIO_Pin_0;
	/*连击的最大间隔设为0*/
	Button_InitStruct.ClickInterval = 0;
	/*长按的时间阈值设为0*/
	Button_InitStruct.LongPressTime = 0;
	/*长按后持续触发的时间间隔设为0*/
	Button_InitStruct.LongPressTickInterval = 0; 
	/*按钮点击*/
	Button_InitStruct.button_clicked_cb = 0;
	/*按钮按下*/
	Button_InitStruct.button_pressed_cb = 0;
	/*按钮抬起*/
	Button_InitStruct.button_released_cb = 0;
	/*按钮长按*/
	Button_InitStruct.button_long_pressed_cb = 0;
	/*初始化按钮1*/
	My_Button_Init(&button1,&Button_InitStruct);
}

3.4.进程函数

cpp 复制代码
void My_Button_Proc(Button_TypeDef *Button);

**注:**该方法需要在main函数的while循环中调用

3.5.回调函数

3.5.1.按钮点击函数
cpp 复制代码
void button_clicked_cb(uint8_t clicks);

解析:

**参数:**点击按钮的次数

示例:

cpp 复制代码
/*创建按钮点击函数*/
void button_clicked_cb(uint8_t clicks)
{
	/*单击*/
	if(clicks == 1)
	{
		cnt++;//计次加一
	}
	else if(clicks == 2)//双击
	{
		cnt = 0;//计次清零
	}
}
3.5.2.按钮长按函数
cpp 复制代码
void button_long_pressed_cb(uint8_t ticks);

解析:

**参数:**长按触发的次数

示例:

cpp 复制代码
/*创建按钮长按函数*/
void button_long_pressed_cb(uint8_t ticks)
{
	cnt++;//计次加1
}

将设置好的回调函数赋给按钮结构体的成员变量

cpp 复制代码
/*按钮点击*/
Button_InitStruct.button_clicked_cb = button_clicked_cb;
/*按钮按下*/
Button_InitStruct.button_pressed_cb = 0;
/*按钮抬起*/
Button_InitStruct.button_released_cb = 0;
/*按钮长按*/
Button_InitStruct.button_long_pressed_cb = button_long_pressed_cb;

总代码:

cpp 复制代码
#include "stm32f10x.h"
#include "usart.h"//引入串口头文件
#include "button.h"//引入按钮头文件

/*声明按钮结构变量*/
Button_TypeDef button1;
/*实验计数变量*/
uint32_t cnt = 0;
/*声明串口初始化函数*/
void App_USART1_Init(void);
/*声明按钮初始化函数*/
void App_Button_Init(void);
/*声明按钮点击函数*/
void button_clicked_cb(uint8_t clicks);
/*声明按钮长按函数*/
void button_long_pressed_cb(uint8_t ticks);

int main(void)
{
	/*初始化串口*/
	App_USART1_Init();
	
	/*初始化按钮1*/
	App_Button_Init();
	
	while(1)
	{
		My_Button_Proc(&button1);
	}
}

/*创建串口初始化函数*/
void App_USART1_Init(void)
{
	//#1:初始化IO引脚 PA9 Tx PA10 Rx
	
	/*开启GPIOA时钟*/
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	/*声明GPIO结构变量*/
	GPIO_InitTypeDef GPIO_InitStruct;
	/*选择PA9引脚*/
	GPIO_InitStruct.GPIO_Pin = GPIO_Pin_9;
	/*复用输出推挽模式*/
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
	/*最大输出速率为2MHz*/
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_2MHz;
	/*初始化PA9引脚*/
	GPIO_Init(GPIOA,&GPIO_InitStruct);
	/*选择PA10引脚*/
	GPIO_InitStruct.GPIO_Pin = GPIO_Pin_10;
	/*输入上拉模式*/
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU;
	/*初始化PA9引脚*/
	GPIO_Init(GPIOA,&GPIO_InitStruct);
	
	//#2:初始化USART1
	
	/*开启USART1时钟*/
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);
	/*声明USART1结构变量*/
	USART_InitTypeDef USART_InitStruct;
	/*波特率为115200*/
	USART_InitStruct.USART_BaudRate = 115200;
	/*收发方向为双向*/
	USART_InitStruct.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;
	/*数据位为8位*/
	USART_InitStruct.USART_WordLength = USART_WordLength_8b;
	/*校验方式为无*/
	USART_InitStruct.USART_Parity = USART_Parity_No;
	/*停止位为1位*/
	USART_InitStruct.USART_StopBits = USART_StopBits_1;
	/*初始化USART1*/
	USART_Init(USART1,&USART_InitStruct);
	/*闭合USART1总开关*/
	USART_Cmd(USART1,ENABLE);
}

/*创建按钮初始化函数*/
void App_Button_Init(void)
{
	/*声明按钮结构变量*/
	Button_InitTypeDef Button_InitStruct;
	/*IO引脚的端口号设为GPIOA*/
	Button_InitStruct.GPIOx = GPIOA;
	/*IO引脚的引脚编号设为0*/
	Button_InitStruct.GPIO_Pin = GPIO_Pin_0;
	/*连击的最大间隔设为0*/
	Button_InitStruct.ClickInterval = 0;
	/*长按的时间阈值设为0*/
	Button_InitStruct.LongPressTime = 0;
	/*长按后持续触发的时间间隔设为0*/
	Button_InitStruct.LongPressTickInterval = 0; 
	/*按钮点击*/
	Button_InitStruct.button_clicked_cb = button_clicked_cb;
	/*按钮按下*/
	Button_InitStruct.button_pressed_cb = 0;
	/*按钮抬起*/
	Button_InitStruct.button_released_cb = 0;
	/*按钮长按*/
	Button_InitStruct.button_long_pressed_cb = button_long_pressed_cb;
	/*初始化按钮1*/
	My_Button_Init(&button1,&Button_InitStruct);
}

/*创建按钮点击函数*/
void button_clicked_cb(uint8_t clicks)
{
	/*单击*/
	if(clicks == 1)
	{
		cnt++;//计次加一
		My_USART_Printf(USART1,"%d",cnt);
	}
	else if(clicks == 2)//双击
	{
		cnt = 0;//计次清零
		My_USART_Printf(USART1,"%d",cnt);
	}
}

/*创建按钮长按函数*/
void button_long_pressed_cb(uint8_t ticks)
{
	cnt++;//计次加1
	My_USART_Printf(USART1,"%d",cnt);
}

四、IO引脚初始化

4.1.SPI模块简介

SPI模块是单片机上的片上外设,给单片机通过SPI通信接口

4.2.W25Q64简介

单片机内部Flash:类似于内部硬盘,存放编写的程序

W25Q64的Flash:类似于移动硬盘,存放单独的数据

通过SPI总线与单片机通信

**VCC:**接电源+

**GND:**接地

**DI(Slave Data Input MOSI):**接主机的MOSI

**DO(Slave Data Output MISO):**接主机的MISO

**CLK(Serial Clock SCK):**接主机的SCK

4.3.定位SPI的引脚位置

4.4.选择IO引脚的模式

4.5.选择IO最大输出速度

4.6.初始化IO引脚

cpp 复制代码
#include "stm32f10x.h"

//声明SPI1初始化函数
void App_SPI1_Init(void);

int main(void)
{
	App_SPI1_Init();
	
	while(1)
	{
	}
}

//创建SPI1初始化函数
void App_SPI1_Init(void)
{
	//#1:初始化IO引脚
	
	/*开启AFIO的时钟*/
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
	/*开启SPI1重映射*/
	GPIO_PinRemapConfig(GPIO_Remap_SPI1,ENABLE);
	/*声明GPIO结构变量*/
	GPIO_InitTypeDef GPIO_InitStruct;
	
	/*PB3 SCK AF_PP 2MHz*/
	/*开启GPIOB的时钟*/
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
	/*选择PB3引脚*/
	GPIO_InitStruct.GPIO_Pin = GPIO_Pin_3;
	/*复用输出推挽模式*/
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
	/*最大输出速率为2MHz*/
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_2MHz;
	/*初始化PB3*/
	GPIO_Init(GPIOB,&GPIO_InitStruct);
	
	/*PB4 MISO IPU*/
	/*开启GPIOB的时钟*/
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
	/*选择PB4引脚*/
	GPIO_InitStruct.GPIO_Pin = GPIO_Pin_4;
	/*输入上拉模式*/
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU;
	/*初始化PB4*/
	GPIO_Init(GPIOB,&GPIO_InitStruct);
	
	/*PB5 MOSI AF_PP 2MHz*/
	/*开启GPIOB的时钟*/
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
	/*选择PB5引脚*/
	GPIO_InitStruct.GPIO_Pin = GPIO_Pin_5;
	/*复用输出推挽模式*/
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
	/*最大输出速率为2MHz*/
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_2MHz;
	/*初始化PB3*/
	GPIO_Init(GPIOB,&GPIO_InitStruct);
	
	/*PA15 普通IO Out_PP 2MHz*/
	/*开启GPIOA的时钟*/
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	/*选择PA15引脚*/
	GPIO_InitStruct.GPIO_Pin = GPIO_Pin_15;
	/*通用输出推挽模式*/
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;
	/*最大输出速率为2MHz*/
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_2MHz;
	/*初始化PA15*/
	GPIO_Init(GPIOA,&GPIO_InitStruct);
}

五、SPI模块的初始化

5.1.SPI模块的基本工作原理

**MOSI:**主发从收

**MISO:**主收从发

**SCK:**串行时钟

**NSS(只在从机模式使用):**从机选择

**发送数据:**将数据写入发送数据寄存器,再由移位寄存器逐个bit位通过MOSI向外发送

**接收数据:**通过MISO接收波形,逐个bit位进行解析,移位寄存器填满后写入接收数据寄存器

**时钟信号:**单片机内部产生的时钟信号经过分频器产生不同速度的波特率

**时钟控制:**控制时钟的极性、相位、波特率在SCK引脚输出相应的波形

**NSS:**作主机时NSS引脚也需配置

**SPI标志位:**TxE,RxNE,ORV

5.2.SPI_Init

cpp 复制代码
void SPI_Init(SPI_TypeDef* SPIx,SPI_InitTypeDef* SPI_InitStruct);

解析:

  • 参数1:SPI的名称 SPI1,SPI2
  • 参数2:初始化的参数结构体地址

作用: 初始化SPI,配置SPI的各种参数

**补充:**SPI_InitTypeDef结构(SPI参数菜单)

cpp 复制代码
struct SPI_InitTypeDef
{
	uint16_t SPI_Direction;
	uint16_t SPI_Mode;
	uint16_t SPI_DataSize;
	uint16_t SPI_CPOL;
	uint16_t SPI_CPHA;
	uint16_t SPI_NSS;
	uint16_t SPI_BaudRatePrescaler;
	uint16_t SPI_FirstBit;
};

分析:

**SPI_Direction:**选择SPI通信方向

  • SPI_Direction_2Lines_FullDuplex:2线全双工
  • SPI_Direction_2Lines_ReadOnly:2线只读
  • SPI_Direction_1Lines_Rx:单线接收
  • SPI_Direction_1Lines_Tx:单线发送

**SPI_Mode:**选择SPI模式

  • SPI_Master:主机模式
  • SPI_Slave:从机模式

**SPI_DataSize:**数据宽度

  • SPI_DataSize_8b:8bit
  • SPI_DataSize_16b:16bit

**SPI_CPOL:**时钟的极性

**SPI_CPHA:**时钟的相位

**SPI_NSS:**软件NSS/硬件NSS

**SPI_BaudRatePrescaler:**波特率分频器的分频系数

**SPI_FirstBit:**比特位的传输顺序

  • SPI_FirstBit_MSB:高位先行
  • SPI_FirstBit_LSB:低位先行

5.3.选择数据通信方向

5.3.1. 2线全双工

标准的SPI总线格式

主机MOSI发送数据,从机MOSI接收数据

主机MISO接收数据,从机MISO发送数据

**全双工:**通信接口既能发送数据、也能接收数据,并且数据收发同时进行

5.3.2. 2线只读

一般用于从机

主机MOSI发送数据,从机MOSI接收数据

但从机不会通过MISO发送数据(主机向从机广播)

5.3.3. 1线发送

单线、方向是发送

主机单线发送、从机单线接收

5.3.4. 1线接收

单向、方向是接收

主机单线接收、从机单线发送

5.4.数据宽度、极性、相位和比特位传输顺序

5.4.1.数据宽度

8bit(一个字节):

16bit(一个字长):

5.4.2.极性和相位
5.4.3.比特位传输顺序

5.5.设置波特率

由于使用面包板作实验,只需将波特率设为1MHz

分频系数设置为64

5.6.NSS的配置方式

5.6.1.多主机模式

NSS被拉低时主机身份变为从机

5.6.2.NSS配置

作主机时NSS应输入高电压

作从机时NSS作为片选输入

主机通过普通IO引脚向从机NSS输入低电压表示选中

主机的NSS需要外接高电压

硬件NSS:外接3.3V高电压

软件NSS:向内部NSS写0为低电压,写1为高电压

配置软件NSS

cpp 复制代码
SPI_NSSInternalSoftwareConfig(SPI1,SPI_NSSInternalSoft_Set);

SET:写1、为高电压

RESET:写0、为低电压

5.7.总代码

cpp 复制代码
#include "stm32f10x.h"

//声明SPI1初始化函数
void App_SPI1_Init(void);

int main(void)
{
	App_SPI1_Init();
	
	while(1)
	{
	}
}

//创建SPI1初始化函数
void App_SPI1_Init(void)
{
	//#1:初始化IO引脚
	
	/*开启AFIO的时钟*/
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
	/*开启SPI1重映射*/
	GPIO_PinRemapConfig(GPIO_Remap_SPI1,ENABLE);
	/*声明GPIO结构变量*/
	GPIO_InitTypeDef GPIO_InitStruct;
	
	/*PB3 SCK AF_PP 2MHz*/
	/*开启GPIOB的时钟*/
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
	/*选择PB3引脚*/
	GPIO_InitStruct.GPIO_Pin = GPIO_Pin_3;
	/*复用输出推挽模式*/
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
	/*最大输出速率为2MHz*/
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_2MHz;
	/*初始化PB3*/
	GPIO_Init(GPIOB,&GPIO_InitStruct);
	
	/*PB4 MISO IPU*/
	/*开启GPIOB的时钟*/
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
	/*选择PB4引脚*/
	GPIO_InitStruct.GPIO_Pin = GPIO_Pin_4;
	/*输入上拉模式*/
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU;
	/*初始化PB4*/
	GPIO_Init(GPIOB,&GPIO_InitStruct);
	
	/*PB5 MOSI AF_PP 2MHz*/
	/*开启GPIOB的时钟*/
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
	/*选择PB5引脚*/
	GPIO_InitStruct.GPIO_Pin = GPIO_Pin_5;
	/*复用输出推挽模式*/
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
	/*最大输出速率为2MHz*/
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_2MHz;
	/*初始化PB3*/
	GPIO_Init(GPIOB,&GPIO_InitStruct);
	
	/*PA15 普通IO Out_PP 2MHz*/
	/*开启GPIOA的时钟*/
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	/*选择PA15引脚*/
	GPIO_InitStruct.GPIO_Pin = GPIO_Pin_15;
	/*通用输出推挽模式*/
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;
	/*最大输出速率为2MHz*/
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_2MHz;
	/*初始化PA15*/
	GPIO_Init(GPIOA,&GPIO_InitStruct);
	
	//#2:初始化SPI
	
	/*开启SPI1的时钟*/
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1,ENABLE);
	/*声明SPI结构变量*/
	SPI_InitTypeDef SPI_InitStruct;
	/*选择主机模式*/
	SPI_InitStruct.SPI_Mode = SPI_Mode_Master;
	/*2线全双工*/
	SPI_InitStruct.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
	/*8bit数据宽度*/
	SPI_InitStruct.SPI_DataSize  = SPI_DataSize_8b;
	/*高极性*/
	SPI_InitStruct.SPI_CPOL = SPI_CPOL_High;
	/*第二边沿采集*/
	SPI_InitStruct.SPI_CPHA = SPI_CPHA_2Edge;
	/*高位先行*/
	SPI_InitStruct.SPI_FirstBit = SPI_FirstBit_MSB;
	/*分频系数为64*/
	SPI_InitStruct.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_64;
	/*选择软件NSS*/
	SPI_InitStruct.SPI_NSS = SPI_NSS_Soft;
	/*初始化SPI1*/
	SPI_Init(SPI1,&SPI_InitStruct);
	/*配置软件NSS*/
	SPI_NSSInternalSoftwareConfig(SPI1,SET);
}

六、数据收发

6.1.SPI数据收发的特点

SPI数据收发是双向的、同时的

主机每次发送一个bit位给从机

必然接收到从机发送一个bit位

6.2.声明数据收发的编程接口

cpp 复制代码
void App_SPI_MasterTransmitReceive(SPI_TypeDef* SPIx,const uint8_t* pDataTx,uint8_t *pDataRx,uint16_t Size);

解析:

  • 参数1:SPI的名称 SPI1 SPI2
  • 参数2:要发送的数据
  • 参数3:接收到的数据
  • 参数4:收发数据的数量

**作用:**使用SPI总线收发数据

6.3.SPI数据收发过程简介

  • 闭合总开关、开启时钟
  • 将第一个字节提前写入发送数据寄存器
  • 等待发送数据寄存器空标志位为1(无数据)
  • 向发送数据寄存器写下一个字节(i+1)
  • 等待接收数据寄存器非空标志位为1(有数据)
  • 从接收数据寄存器读当前的字节(i)
  • 等待接收数据寄存器非空标志位为1(有数据)
  • 读取最后一个字节
  • 断开总开关、关闭时钟

6.4.代码编写

cpp 复制代码
//创建SPI收发数据函数
void App_SPI_MasterTransmitReceive(SPI_TypeDef* SPIx,const uint8_t* pDataTx,uint8_t *pDataRx,uint16_t Size)
{
	//#1:闭合总开关
	SPI_Cmd(SPIx,ENABLE);
	
	//#2:发送第一个字节
	SPI_I2S_SendData(SPIx,pDataTx[0]);
	
	//#3:循环发送数据
	
	for(uint16_t i = 0;i < Size - 1;i++)
	{
		/*等待发送数据寄存器空标志位为1*/
		while(SPI_I2S_GetFlagStatus(SPIx,SPI_I2S_FLAG_TXE) == RESET);
		/*发送一个字节*/
		SPI_I2S_SendData(SPIx,pDataTx[i+1]);
		/*等待接收数据寄存器非空标志位为1*/
		while(SPI_I2S_GetFlagStatus(SPIx,SPI_I2S_FLAG_RXNE) == RESET);
		/*接收一个字节*/
		pDataRx[i] = SPI_I2S_ReceiveData(SPIx);
	}
	
	//#4:读取最后一个字节
	
	/*等待接收数据寄存器非空标志位为1*/
	while(SPI_I2S_GetFlagStatus(SPIx,SPI_I2S_FLAG_RXNE) == RESET);
	/*读取最后一个字节*/
	pDataRx[Size - 1] = SPI_I2S_ReceiveData(SPIx);
	
	//#5:断开总开关
	SPI_Cmd(SPIx,DISABLE);
}

七、W25Q64实验1

7.1.PA15的重映射

由于PA15引脚上电后默认的功能是JTDI(调试)

所以刚上电时,这个引脚会被调试接口占用

需要对PA15进行重映射,才能对普通PA15进行使用

cpp 复制代码
/*开启PA15重映射*/
GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable,ENABLE);

初始状态下还需要向PA15输出高电压:

时钟信号需要选择模式0:

低极性第一边沿采集

修改后的代码:

cpp 复制代码
//创建SPI1初始化函数
void App_SPI1_Init(void)
{
	//#1:初始化IO引脚
	
	/*开启AFIO的时钟*/
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
	/*开启SPI1重映射*/
	GPIO_PinRemapConfig(GPIO_Remap_SPI1,ENABLE);
	/*开启PA15重映射*/
	GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable,ENABLE);
	/*声明GPIO结构变量*/
	GPIO_InitTypeDef GPIO_InitStruct;
	
	/*PB3 SCK AF_PP 2MHz*/
	/*开启GPIOB的时钟*/
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
	/*选择PB3引脚*/
	GPIO_InitStruct.GPIO_Pin = GPIO_Pin_3;
	/*复用输出推挽模式*/
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
	/*最大输出速率为2MHz*/
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_2MHz;
	/*初始化PB3*/
	GPIO_Init(GPIOB,&GPIO_InitStruct);
	
	/*PB4 MISO IPU*/
	/*开启GPIOB的时钟*/
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
	/*选择PB4引脚*/
	GPIO_InitStruct.GPIO_Pin = GPIO_Pin_4;
	/*输入上拉模式*/
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU;
	/*初始化PB4*/
	GPIO_Init(GPIOB,&GPIO_InitStruct);
	
	/*PB5 MOSI AF_PP 2MHz*/
	/*开启GPIOB的时钟*/
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
	/*选择PB5引脚*/
	GPIO_InitStruct.GPIO_Pin = GPIO_Pin_5;
	/*复用输出推挽模式*/
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
	/*最大输出速率为2MHz*/
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_2MHz;
	/*初始化PB3*/
	GPIO_Init(GPIOB,&GPIO_InitStruct);
	
	/*PA15 普通IO Out_PP 2MHz*/
	/*开启GPIOA的时钟*/
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	/*选择PA15引脚*/
	GPIO_InitStruct.GPIO_Pin = GPIO_Pin_15;
	/*通用输出推挽模式*/
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;
	/*最大输出速率为2MHz*/
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_2MHz;
	/*初始化PA15*/
	GPIO_Init(GPIOA,&GPIO_InitStruct);
	/*将PA15设置为高电平*/
	GPIO_WriteBit(GPIOA,GPIO_Pin_15,Bit_SET);
	
	//#2:初始化SPI
	
	/*开启SPI1的时钟*/
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1,ENABLE);
	/*声明SPI结构变量*/
	SPI_InitTypeDef SPI_InitStruct;
	/*选择主机模式*/
	SPI_InitStruct.SPI_Mode = SPI_Mode_Master;
	/*2线全双工*/
	SPI_InitStruct.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
	/*8bit数据宽度*/
	SPI_InitStruct.SPI_DataSize  = SPI_DataSize_8b;
	/*低极性*/
	SPI_InitStruct.SPI_CPOL = SPI_CPOL_Low;
	/*第一边沿采集*/
	SPI_InitStruct.SPI_CPHA = SPI_CPHA_1Edge;
	/*高位先行*/
	SPI_InitStruct.SPI_FirstBit = SPI_FirstBit_MSB;
	/*分频系数为64*/
	SPI_InitStruct.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_64;
	/*选择软件NSS*/
	SPI_InitStruct.SPI_NSS = SPI_NSS_Soft;
	/*初始化SPI1*/
	SPI_Init(SPI1,&SPI_InitStruct);
	/*配置软件NSS*/
	SPI_NSSInternalSoftwareConfig(SPI1,SET);
}

7.2.W25Q64的存储结构

64:存储容量为64MBit(8MByte)

Flash存储数据:先擦除原有数据,再编写存储数据

  • 以扇区为单位擦除数据
  • 以页为单位编程数据

写使能:

打开Flash模块的锁(避免数据遭到损坏、维护数据安全),执行完数据操作后自动锁上

等待空闲:

擦除需要50ms,编程需要10ms,执行数据操作后要等待操作完成

当SR1寄存器中BUSY标志位从1变成0时,说明数据操作完成,处于空闲状态

7.3.使用W25Q64存储数据

7.3.1.写使能

单片机通过SPI总线向W25Q64发送0x60(指令)

代码编写:

cpp 复制代码
//#1:写使能	
/*声明存储数据数组*/
uint8_t buffer[10];
/*存放指令0x06*/
buffer[0] = 0x06;	
/*通过主机PA15引脚向从机NSS引脚发送0*/
GPIO_WriteBit(GPIOA,GPIO_Pin_15,Bit_RESET);	
/*向从机收发数据*/
App_SPI_MasterTransmitReceive(SPI1,buffer,buffer,1);	
/*通过主机PA15引脚向从机NSS引脚发送1*/
GPIO_WriteBit(GPIOA,GPIO_Pin_15,Bit_SET);
7.3.2.扇区擦除

单片机通过SPI总线向W25Q64发送0x20(指令)+24位地址(3个0x00)

cpp 复制代码
//#2:扇区擦除
/*存放指令0x20*/
buffer[0] = 0x20;
/*存放24位地址*/
buffer[1] = 0x00;
buffer[2] = 0x00;
buffer[3] = 0x00;
	
/*通过主机PA15引脚向从机NSS引脚发送0*/
GPIO_WriteBit(GPIOA,GPIO_Pin_15,Bit_RESET);
/*向从机收发数据*/
App_SPI_MasterTransmitReceive(SPI1,buffer,buffer,4);
/*通过主机PA15引脚向从机NSS引脚发送1*/
GPIO_WriteBit(GPIOA,GPIO_Pin_15,Bit_SET);
7.3.3.等待空闲

单片机通过SPI总线向W25Q64发送0x05(指令),再发送0xff,保证MOSI上的电平一直为高电平

使读取更加稳定,然后读取一个字节,等待BUSY标志位从1变成0,操作结束,退出循环

cpp 复制代码
//#3:等待空闲
while(1)
{
	/*通过主机PA15引脚向从机NSS引脚发送0*/
    GPIO_WriteBit(GPIOA,GPIO_Pin_15,Bit_RESET);
	/*存放指令0x05*/
	buffer[0] = 0x05;
	/*向从机收发数据*/
	App_SPI_MasterTransmitReceive(SPI1,buffer,buffer,1);
	/*存放数据0xff*/
	buffer[0] = 0xff;
	/*读取一个字节*/
	App_SPI_MasterTransmitReceive(SPI1,buffer,buffer,1);
	/*通过主机PA15引脚向从机NSS引脚发送1*/
	GPIO_WriteBit(GPIOA,GPIO_Pin_15,Bit_SET);
	/*等待读取结束*/
	if((buffer[0] & 0x01) == 0) break;
}
7.3.4.页编程

单片机通过SPI总线向W25Q64发送0x02(指令)+24位地址+要写的数据(最多256字节)

cpp 复制代码
//#5:页编程
/*通过主机PA15引脚向从机NSS引脚发送0*/
GPIO_WriteBit(GPIOA,GPIO_Pin_15,Bit_RESET);
/*存放指令0x02*/
buffer[0] = 0x02;
/*存放24位地址*/
buffer[1] = 0x00;
buffer[2] = 0x00;
buffer[3] = 0x00;
/*存放发送数据*/
buffer[4] = Byte;
/*向从机收发数据*/
App_SPI_MasterTransmitReceive(SPI1,buffer,buffer,5);
/*通过主机PA15引脚向从机NSS引脚发送1*/
GPIO_WriteBit(GPIOA,GPIO_Pin_15,Bit_SET);
7.3.5.总代码
cpp 复制代码
//创建W25Q64存储数据函数
void App_W25Q64_SaveByte(uint8_t Byte)
{
	//#1:写使能
	/*声明存储数据数组*/
	uint8_t buffer[10];
	/*存放指令0x06*/
	buffer[0] = 0x06;
	/*通过主机PA15引脚向从机NSS引脚发送0*/
	GPIO_WriteBit(GPIOA,GPIO_Pin_15,Bit_RESET);
	/*向从机收发数据*/
	App_SPI_MasterTransmitReceive(SPI1,buffer,buffer,1);
	/*通过主机PA15引脚向从机NSS引脚发送1*/
	GPIO_WriteBit(GPIOA,GPIO_Pin_15,Bit_SET);
	
	//#2:扇区擦除
	/*存放指令0x20*/
	buffer[0] = 0x20;
	/*存放24位地址*/
	buffer[1] = 0x00;
	buffer[2] = 0x00;
	buffer[3] = 0x00;
	/*通过主机PA15引脚向从机NSS引脚发送0*/
	GPIO_WriteBit(GPIOA,GPIO_Pin_15,Bit_RESET);
	/*向从机收发数据*/
	App_SPI_MasterTransmitReceive(SPI1,buffer,buffer,4);
	/*通过主机PA15引脚向从机NSS引脚发送1*/
	GPIO_WriteBit(GPIOA,GPIO_Pin_15,Bit_SET);
	
	//#3:等待空闲
	while(1)
	{
		/*通过主机PA15引脚向从机NSS引脚发送0*/
	    GPIO_WriteBit(GPIOA,GPIO_Pin_15,Bit_RESET);
		/*存放指令0x05*/
		buffer[0] = 0x05;
		/*向从机收发数据*/
	    App_SPI_MasterTransmitReceive(SPI1,buffer,buffer,1);
		/*存放数据0xff*/
		buffer[0] = 0xff;
		/*读取一个字节*/
		App_SPI_MasterTransmitReceive(SPI1,buffer,buffer,1);
		/*通过主机PA15引脚向从机NSS引脚发送1*/
	    GPIO_WriteBit(GPIOA,GPIO_Pin_15,Bit_SET);
		/*等待读取结束*/
		if((buffer[0] & 0x01) == 0) break;
	}
	
	//#4:写使能
	/*存放指令0x06*/
	buffer[0] = 0x06;
	/*通过主机PA15引脚向从机NSS引脚发送0*/
	GPIO_WriteBit(GPIOA,GPIO_Pin_15,Bit_RESET);
	/*向从机收发数据*/
	App_SPI_MasterTransmitReceive(SPI1,buffer,buffer,1);
	/*通过主机PA15引脚向从机NSS引脚发送1*/
	GPIO_WriteBit(GPIOA,GPIO_Pin_15,Bit_SET);
	
	//#5:页编程
	/*通过主机PA15引脚向从机NSS引脚发送0*/
	GPIO_WriteBit(GPIOA,GPIO_Pin_15,Bit_RESET);
	/*存放指令0x02*/
	buffer[0] = 0x02;
	/*存放24位地址*/
	buffer[1] = 0x00;
	buffer[2] = 0x00;
	buffer[3] = 0x00;
	/*存放发送数据*/
	buffer[4] = Byte;
	/*向从机收发数据*/
	App_SPI_MasterTransmitReceive(SPI1,buffer,buffer,5);
	/*通过主机PA15引脚向从机NSS引脚发送1*/
	GPIO_WriteBit(GPIOA,GPIO_Pin_15,Bit_SET);
	
	//#6:等待空闲
	while(1)
	{
		/*通过主机PA15引脚向从机NSS引脚发送0*/
	    GPIO_WriteBit(GPIOA,GPIO_Pin_15,Bit_RESET);
		/*存放指令0x05*/
		buffer[0] = 0x05;
		/*向从机收发数据*/
	    App_SPI_MasterTransmitReceive(SPI1,buffer,buffer,1);
		/*存放数据0xff*/
		buffer[0] = 0xff;
		/*读取一个字节*/
		App_SPI_MasterTransmitReceive(SPI1,buffer,buffer,1);
		/*通过主机PA15引脚向从机NSS引脚发送1*/
	    GPIO_WriteBit(GPIOA,GPIO_Pin_15,Bit_SET);
		/*等待读取结束*/
		if((buffer[0] & 0x01) == 0) break;
	}
}

7.4.使用W25Q64读取数据

单片机通过SPI总线向W25Q64发送0x03(指令)+24位地址,然后读取数据

cpp 复制代码
//创建W25Q64读取数据函数
uint8_t App_W25Q64_LoadByte(void)
{
	/*声明存储数据数组*/
	uint8_t buffer[10];
	/*存放指令0x03*/
	buffer[0] = 0x03;
	/*存放24位地址*/
	buffer[1] = 0x00;
	buffer[2] = 0x00;
	buffer[3] = 0x00;
	/*通过主机PA15引脚向从机NSS引脚发送0*/
	GPIO_WriteBit(GPIOA,GPIO_Pin_15,Bit_RESET);
	/*向从机收发数据*/
	App_SPI_MasterTransmitReceive(SPI1,buffer,buffer,4);
	/*存放数据0xff*/
	buffer[0] = 0xff;
	/*读取一个字节*/
	App_SPI_MasterTransmitReceive(SPI1,buffer,buffer,1);
	/*通过主机PA15引脚向从机NSS引脚发送1*/
    GPIO_WriteBit(GPIOA,GPIO_Pin_15,Bit_SET);
	/*返回接收的数据*/
	return buffer[0];
}

7.5.测试代码

cpp 复制代码
#include "stm32f10x.h"

//声明SPI1初始化函数
void App_SPI1_Init(void);
//声明SPI数据收发函数
void App_SPI_MasterTransmitReceive(SPI_TypeDef* SPIx,const uint8_t* pDataTx,uint8_t *pDataRx,uint16_t Size);
//声明W25Q64存储数据函数
void App_W25Q64_SaveByte(uint8_t Byte);
//声明W25Q64读取数据函数
uint8_t App_W25Q64_LoadByte(void);

uint8_t a = 0;

int main(void)
{
	App_SPI1_Init();
	
	/*将字节0x12保存在W25Q64的0x000000*/
	App_W25Q64_SaveByte(0x12);
	/*将保存的字节读出来*/
	a = App_W25Q64_LoadByte();
	
	while(1)
	{
	}
}

//创建SPI1初始化函数
void App_SPI1_Init(void)
{
	//#1:初始化IO引脚
	
	/*开启AFIO的时钟*/
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
	/*开启SPI1重映射*/
	GPIO_PinRemapConfig(GPIO_Remap_SPI1,ENABLE);
	/*开启PA15重映射*/
	GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable,ENABLE);
	/*声明GPIO结构变量*/
	GPIO_InitTypeDef GPIO_InitStruct;
	
	/*PB3 SCK AF_PP 2MHz*/
	/*开启GPIOB的时钟*/
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
	/*选择PB3引脚*/
	GPIO_InitStruct.GPIO_Pin = GPIO_Pin_3;
	/*复用输出推挽模式*/
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
	/*最大输出速率为2MHz*/
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_2MHz;
	/*初始化PB3*/
	GPIO_Init(GPIOB,&GPIO_InitStruct);
	
	/*PB4 MISO IPU*/
	/*开启GPIOB的时钟*/
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
	/*选择PB4引脚*/
	GPIO_InitStruct.GPIO_Pin = GPIO_Pin_4;
	/*输入上拉模式*/
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU;
	/*初始化PB4*/
	GPIO_Init(GPIOB,&GPIO_InitStruct);
	
	/*PB5 MOSI AF_PP 2MHz*/
	/*开启GPIOB的时钟*/
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
	/*选择PB5引脚*/
	GPIO_InitStruct.GPIO_Pin = GPIO_Pin_5;
	/*复用输出推挽模式*/
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
	/*最大输出速率为2MHz*/
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_2MHz;
	/*初始化PB3*/
	GPIO_Init(GPIOB,&GPIO_InitStruct);
	
	/*PA15 普通IO Out_PP 2MHz*/
	/*开启GPIOA的时钟*/
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	/*选择PA15引脚*/
	GPIO_InitStruct.GPIO_Pin = GPIO_Pin_15;
	/*通用输出推挽模式*/
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;
	/*最大输出速率为2MHz*/
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_2MHz;
	/*初始化PA15*/
	GPIO_Init(GPIOA,&GPIO_InitStruct);
	/*将PA15设置为高电平*/
	GPIO_WriteBit(GPIOA,GPIO_Pin_15,Bit_SET);
	
	//#2:初始化SPI
	
	/*开启SPI1的时钟*/
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1,ENABLE);
	/*声明SPI结构变量*/
	SPI_InitTypeDef SPI_InitStruct;
	/*选择主机模式*/
	SPI_InitStruct.SPI_Mode = SPI_Mode_Master;
	/*2线全双工*/
	SPI_InitStruct.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
	/*8bit数据宽度*/
	SPI_InitStruct.SPI_DataSize  = SPI_DataSize_8b;
	/*低极性*/
	SPI_InitStruct.SPI_CPOL = SPI_CPOL_Low;
	/*第一边沿采集*/
	SPI_InitStruct.SPI_CPHA = SPI_CPHA_1Edge;
	/*高位先行*/
	SPI_InitStruct.SPI_FirstBit = SPI_FirstBit_MSB;
	/*分频系数为64*/
	SPI_InitStruct.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_64;
	/*选择软件NSS*/
	SPI_InitStruct.SPI_NSS = SPI_NSS_Soft;
	/*初始化SPI1*/
	SPI_Init(SPI1,&SPI_InitStruct);
	/*配置软件NSS*/
	SPI_NSSInternalSoftwareConfig(SPI1,SET);
}

//创建SPI收发数据函数
void App_SPI_MasterTransmitReceive(SPI_TypeDef* SPIx,const uint8_t* pDataTx,uint8_t *pDataRx,uint16_t Size)
{
	//#1:闭合总开关
	SPI_Cmd(SPIx,ENABLE);
	
	//#2:发送第一个字节
	SPI_I2S_SendData(SPIx,pDataTx[0]);
	
	//#3:循环发送数据
	
	for(uint16_t i = 0;i < Size - 1;i++)
	{
		/*等待发送数据寄存器空标志位为1*/
		while(SPI_I2S_GetFlagStatus(SPIx,SPI_I2S_FLAG_TXE) == RESET);
		/*发送一个字节*/
		SPI_I2S_SendData(SPIx,pDataTx[i+1]);
		/*等待接收数据寄存器非空标志位为1*/
		while(SPI_I2S_GetFlagStatus(SPIx,SPI_I2S_FLAG_RXNE) == RESET);
		/*接收一个字节*/
		pDataRx[i] = SPI_I2S_ReceiveData(SPIx);
	}
	
	//#4:读取最后一个字节
	
	/*等待接收数据寄存器非空标志位为1*/
	while(SPI_I2S_GetFlagStatus(SPIx,SPI_I2S_FLAG_RXNE) == RESET);
	/*读取最后一个字节*/
	pDataRx[Size - 1] = SPI_I2S_ReceiveData(SPIx);
	
	//#5:断开总开关
	SPI_Cmd(SPIx,DISABLE);
}

//创建W25Q64存储数据函数
void App_W25Q64_SaveByte(uint8_t Byte)
{
	//#1:写使能
	/*声明存储数据数组*/
	uint8_t buffer[10];
	/*存放指令0x06*/
	buffer[0] = 0x06;
	/*通过主机PA15引脚向从机NSS引脚发送0*/
	GPIO_WriteBit(GPIOA,GPIO_Pin_15,Bit_RESET);
	/*向从机收发数据*/
	App_SPI_MasterTransmitReceive(SPI1,buffer,buffer,1);
	/*通过主机PA15引脚向从机NSS引脚发送1*/
	GPIO_WriteBit(GPIOA,GPIO_Pin_15,Bit_SET);
	
	//#2:扇区擦除
	/*存放指令0x20*/
	buffer[0] = 0x20;
	/*存放24位地址*/
	buffer[1] = 0x00;
	buffer[2] = 0x00;
	buffer[3] = 0x00;
	/*通过主机PA15引脚向从机NSS引脚发送0*/
	GPIO_WriteBit(GPIOA,GPIO_Pin_15,Bit_RESET);
	/*向从机收发数据*/
	App_SPI_MasterTransmitReceive(SPI1,buffer,buffer,4);
	/*通过主机PA15引脚向从机NSS引脚发送1*/
	GPIO_WriteBit(GPIOA,GPIO_Pin_15,Bit_SET);
	
	//#3:等待空闲
	while(1)
	{
		/*通过主机PA15引脚向从机NSS引脚发送0*/
	    GPIO_WriteBit(GPIOA,GPIO_Pin_15,Bit_RESET);
		/*存放指令0x05*/
		buffer[0] = 0x05;
		/*向从机收发数据*/
	    App_SPI_MasterTransmitReceive(SPI1,buffer,buffer,1);
		/*存放数据0xff*/
		buffer[0] = 0xff;
		/*读取一个字节*/
		App_SPI_MasterTransmitReceive(SPI1,buffer,buffer,1);
		/*通过主机PA15引脚向从机NSS引脚发送1*/
	    GPIO_WriteBit(GPIOA,GPIO_Pin_15,Bit_SET);
		/*等待读取结束*/
		if((buffer[0] & 0x01) == 0) break;
	}
	
	//#4:写使能
	/*存放指令0x06*/
	buffer[0] = 0x06;
	/*通过主机PA15引脚向从机NSS引脚发送0*/
	GPIO_WriteBit(GPIOA,GPIO_Pin_15,Bit_RESET);
	/*向从机收发数据*/
	App_SPI_MasterTransmitReceive(SPI1,buffer,buffer,1);
	/*通过主机PA15引脚向从机NSS引脚发送1*/
	GPIO_WriteBit(GPIOA,GPIO_Pin_15,Bit_SET);
	
	//#5:页编程
	/*通过主机PA15引脚向从机NSS引脚发送0*/
	GPIO_WriteBit(GPIOA,GPIO_Pin_15,Bit_RESET);
	/*存放指令0x02*/
	buffer[0] = 0x02;
	/*存放24位地址*/
	buffer[1] = 0x00;
	buffer[2] = 0x00;
	buffer[3] = 0x00;
	/*存放发送数据*/
	buffer[4] = Byte;
	/*向从机收发数据*/
	App_SPI_MasterTransmitReceive(SPI1,buffer,buffer,5);
	/*通过主机PA15引脚向从机NSS引脚发送1*/
	GPIO_WriteBit(GPIOA,GPIO_Pin_15,Bit_SET);
	
	//#6:等待空闲
	while(1)
	{
		/*通过主机PA15引脚向从机NSS引脚发送0*/
	    GPIO_WriteBit(GPIOA,GPIO_Pin_15,Bit_RESET);
		/*存放指令0x05*/
		buffer[0] = 0x05;
		/*向从机收发数据*/
	    App_SPI_MasterTransmitReceive(SPI1,buffer,buffer,1);
		/*存放数据0xff*/
		buffer[0] = 0xff;
		/*读取一个字节*/
		App_SPI_MasterTransmitReceive(SPI1,buffer,buffer,1);
		/*通过主机PA15引脚向从机NSS引脚发送1*/
	    GPIO_WriteBit(GPIOA,GPIO_Pin_15,Bit_SET);
		/*等待读取结束*/
		if((buffer[0] & 0x01) == 0) break;
	}
}

//创建W25Q64读取数据函数
uint8_t App_W25Q64_LoadByte(void)
{
	/*声明存储数据数组*/
	uint8_t buffer[10];
	/*存放指令0x03*/
	buffer[0] = 0x03;
	/*存放24位地址*/
	buffer[1] = 0x00;
	buffer[2] = 0x00;
	buffer[3] = 0x00;
	/*通过主机PA15引脚向从机NSS引脚发送0*/
	GPIO_WriteBit(GPIOA,GPIO_Pin_15,Bit_RESET);
	/*向从机收发数据*/
	App_SPI_MasterTransmitReceive(SPI1,buffer,buffer,4);
	/*存放数据0xff*/
	buffer[0] = 0xff;
	/*读取一个字节*/
	App_SPI_MasterTransmitReceive(SPI1,buffer,buffer,1);
	/*通过主机PA15引脚向从机NSS引脚发送1*/
    GPIO_WriteBit(GPIOA,GPIO_Pin_15,Bit_SET);
	/*返回接收的数据*/
	return buffer[0];
}

八、W25Q64实验2

8.1.初始化板载LED

将PC13引脚设置为通用输出开漏模式

cpp 复制代码
//创建板载LED初始化函数
void App_OnBoardLED_Init(void)
{
	/*启动GPIOC时钟*/
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC,ENABLE);
	/*声明GPIO结构变量*/
	GPIO_InitTypeDef GPIO_InitStruct;
	/*选择PC13引脚*/
	GPIO_InitStruct.GPIO_Pin = GPIO_Pin_13;
	/*输出开漏模式*/
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_OD;
	/*最大输出速率为2MHz*/
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_2MHz;
	/*初始化PA0引脚*/
	GPIO_Init(GPIOC,&GPIO_InitStruct);
}

8.2.初始化按钮

cpp 复制代码
#include "stm32f10x.h"
#include "button.h"//引入按钮头文件

/*声明按钮结构变量*/
Button_TypeDef button;
//声明SPI1初始化函数
void App_SPI1_Init(void);
//声明SPI数据收发函数
void App_SPI_MasterTransmitReceive(SPI_TypeDef* SPIx,const uint8_t* pDataTx,uint8_t *pDataRx,uint16_t Size);
//声明W25Q64存储数据函数
void App_W25Q64_SaveByte(uint8_t Byte);
//声明W25Q64读取数据函数
uint8_t App_W25Q64_LoadByte(void);
//声明板载LED初始化函数
void App_OnBoardLED_Init(void);
//声明按钮初始化函数
void App_Button_Init(void);

int main(void)
{
	App_SPI1_Init();
	App_OnBoardLED_Init();
	App_Button_Init();
	
	while(1)
	{
		/*按钮进程函数*/
        My_Button_Proc(&button);
	}
}

//创建按钮初始化函数
void App_Button_Init(void)
{
	/*声明按钮初始化结构变量*/
	Button_InitTypeDef Button_InitStruct = {0};
	/*IO引脚的端口号设为GPIOA*/
	Button_InitStruct.GPIOx = GPIOA;
	/*IO引脚的引脚编号设为0*/
	Button_InitStruct.GPIO_Pin = GPIO_Pin_0;
	/*初始化按钮*/
	My_Button_Init(&button,&Button_InitStruct);
}

注:

按钮初始化结构变量要先将它初始化为0,因为其他的回调函数并没有创建,否则实验会失败

8.3.使用按钮切换LED的亮灭

cpp 复制代码
#include "stm32f10x.h"
#include "button.h"//引入按钮头文件

/*声明按钮结构变量*/
Button_TypeDef button;
//声明SPI1初始化函数
void App_SPI1_Init(void);
//声明SPI数据收发函数
void App_SPI_MasterTransmitReceive(SPI_TypeDef* SPIx,const uint8_t* pDataTx,uint8_t *pDataRx,uint16_t Size);
//声明W25Q64存储数据函数
void App_W25Q64_SaveByte(uint8_t Byte);
//声明W25Q64读取数据函数
uint8_t App_W25Q64_LoadByte(void);
//声明板载LED初始化函数
void App_OnBoardLED_Init(void);
//声明按钮初始化函数
void App_Button_Init(void);
//声明按钮点击回调函数
void button_clicked_cb(uint8_t clicks);

int main(void)
{
	App_SPI1_Init();
	App_OnBoardLED_Init();
	App_Button_Init();
	
	while(1)
	{
		/*按钮进程函数*/
        My_Button_Proc(&button);
	}
}

//创建按钮初始化函数
void App_Button_Init(void)
{
	/*声明按钮初始化结构变量*/
	Button_InitTypeDef Button_InitStruct = {0};
	/*IO引脚的端口号设为GPIOA*/
	Button_InitStruct.GPIOx = GPIOA;
	/*IO引脚的引脚编号设为0*/
	Button_InitStruct.GPIO_Pin = GPIO_Pin_0;
	/*按钮点击*/
	Button_InitStruct.button_clicked_cb = button_clicked_cb;
	/*初始化按钮*/
	My_Button_Init(&button,&Button_InitStruct);
}

//创建按钮点击回调函数
void button_clicked_cb(uint8_t clicks)
{
	/*如果是单击*/
	if(clicks == 1)
	{
		/*如果上次输出的是1*/
		if(GPIO_ReadOutputDataBit(GPIOC,GPIO_Pin_13) == Bit_SET)
		{
			/*这次就输出0*/
			GPIO_WriteBit(GPIOC,GPIO_Pin_13,Bit_RESET);
		}
		else/*如果上次输出的是0*/
		{
			/*这次就输出1*/
			GPIO_WriteBit(GPIOC,GPIO_Pin_13,Bit_SET);
		}
	}
}

8.4.保存LED的当前状态

cpp 复制代码
#include "stm32f10x.h"
#include "button.h"//引入按钮头文件

/*声明按钮结构变量*/
Button_TypeDef button;
//声明SPI1初始化函数
void App_SPI1_Init(void);
//声明SPI数据收发函数
void App_SPI_MasterTransmitReceive(SPI_TypeDef* SPIx,const uint8_t* pDataTx,uint8_t *pDataRx,uint16_t Size);
//声明W25Q64存储数据函数
void App_W25Q64_SaveByte(uint8_t Byte);
//声明W25Q64读取数据函数
uint8_t App_W25Q64_LoadByte(void);
//声明板载LED初始化函数
void App_OnBoardLED_Init(void);
//声明按钮初始化函数
void App_Button_Init(void);
//声明按钮点击回调函数
void button_clicked_cb(uint8_t clicks);

int main(void)
{
	App_SPI1_Init();
	App_OnBoardLED_Init();
	App_Button_Init();
	
	while(1)
	{
		/*按钮进程函数*/
    My_Button_Proc(&button);
	}
}

//创建按钮点击回调函数
void button_clicked_cb(uint8_t clicks)
{
	/*如果是单击*/
	if(clicks == 1)
	{
		/*如果上次输出的是1*/
		if(GPIO_ReadOutputDataBit(GPIOC,GPIO_Pin_13) == Bit_SET)
		{
			/*这次就输出0*/
			GPIO_WriteBit(GPIOC,GPIO_Pin_13,Bit_RESET);
			/*保存0x01,表示LED点亮*/
			App_W25Q64_SaveByte(0x01);
		}
		else/*如果上次输出的是0*/
		{
			/*这次就输出1*/
			GPIO_WriteBit(GPIOC,GPIO_Pin_13,Bit_SET);
			/*保存0x00,表示LED熄灭*/
			App_W25Q64_SaveByte(0x00);
		}
	}
}

8.5.恢复LED状态

cpp 复制代码
int main(void)
{
	App_SPI1_Init();
	App_OnBoardLED_Init();
	App_Button_Init();
	
	uint8_t byte = App_W25Q64_LoadByte();
	
	/*如果断电前读的是0*/
	if(byte == 0)
	{
		/*熄灭LED*/
		GPIO_WriteBit(GPIOC,GPIO_Pin_13,Bit_SET);
	}
	else/*如果断电前读的是1*/
	{
		/*点亮LED*/
		GPIO_WriteBit(GPIOC,GPIO_Pin_13,Bit_RESET);
	}

	while(1)
	{
		/*按钮进程函数*/
        My_Button_Proc(&button);
	}
}

8.6.总代码

cpp 复制代码
#include "stm32f10x.h"
#include "button.h"//引入按钮头文件

/*声明按钮结构变量*/
Button_TypeDef button;
//声明SPI1初始化函数
void App_SPI1_Init(void);
//声明SPI数据收发函数
void App_SPI_MasterTransmitReceive(SPI_TypeDef* SPIx,const uint8_t* pDataTx,uint8_t *pDataRx,uint16_t Size);
//声明W25Q64存储数据函数
void App_W25Q64_SaveByte(uint8_t Byte);
//声明W25Q64读取数据函数
uint8_t App_W25Q64_LoadByte(void);
//声明板载LED初始化函数
void App_OnBoardLED_Init(void);
//声明按钮初始化函数
void App_Button_Init(void);
//声明按钮点击回调函数
void button_clicked_cb(uint8_t clicks);

int main(void)
{
	App_SPI1_Init();
	App_OnBoardLED_Init();
	App_Button_Init();
	
	uint8_t byte = App_W25Q64_LoadByte();
	
	/*如果断电前读的是0*/
	if(byte == 0)
	{
		/*熄灭LED*/
		GPIO_WriteBit(GPIOC,GPIO_Pin_13,Bit_SET);
	}
	else/*如果断电前读的是1*/
	{
		/*点亮LED*/
		GPIO_WriteBit(GPIOC,GPIO_Pin_13,Bit_RESET);
	}
	
	while(1)
	{
		/*按钮进程函数*/
        My_Button_Proc(&button);
	}
}

//创建按钮点击回调函数
void button_clicked_cb(uint8_t clicks)
{
	/*如果是单击*/
	if(clicks == 1)
	{
		/*如果上次输出的是1*/
		if(GPIO_ReadOutputDataBit(GPIOC,GPIO_Pin_13) == Bit_SET)
		{
			/*这次就输出0*/
			GPIO_WriteBit(GPIOC,GPIO_Pin_13,Bit_RESET);
			/*保存0x01,表示LED点亮*/
			App_W25Q64_SaveByte(0x01);
		}
		else/*如果上次输出的是0*/
		{
			/*这次就输出1*/
			GPIO_WriteBit(GPIOC,GPIO_Pin_13,Bit_SET);
			/*保存0x00,表示LED熄灭*/
			App_W25Q64_SaveByte(0x00);
		}
	}
}

//创建按钮初始化函数
void App_Button_Init(void)
{
	/*声明按钮初始化结构变量*/
	Button_InitTypeDef Button_InitStruct = {0};
	/*IO引脚的端口号设为GPIOA*/
	Button_InitStruct.GPIOx = GPIOA;
	/*IO引脚的引脚编号设为0*/
	Button_InitStruct.GPIO_Pin = GPIO_Pin_0;
	/*按钮点击*/
	Button_InitStruct.button_clicked_cb = button_clicked_cb;
	/*初始化按钮*/
	My_Button_Init(&button,&Button_InitStruct);
}

//创建SPI1初始化函数
void App_SPI1_Init(void)
{
	//#1:初始化IO引脚
	
	/*开启AFIO的时钟*/
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
	/*开启SPI1重映射*/
	GPIO_PinRemapConfig(GPIO_Remap_SPI1,ENABLE);
	/*开启PA15重映射*/
	GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable,ENABLE);
	/*声明GPIO结构变量*/
	GPIO_InitTypeDef GPIO_InitStruct;
	
	/*PB3 SCK AF_PP 2MHz*/
	/*开启GPIOB的时钟*/
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
	/*选择PB3引脚*/
	GPIO_InitStruct.GPIO_Pin = GPIO_Pin_3;
	/*复用输出推挽模式*/
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
	/*最大输出速率为2MHz*/
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_2MHz;
	/*初始化PB3*/
	GPIO_Init(GPIOB,&GPIO_InitStruct);
	
	/*PB4 MISO IPU*/
	/*开启GPIOB的时钟*/
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
	/*选择PB4引脚*/
	GPIO_InitStruct.GPIO_Pin = GPIO_Pin_4;
	/*输入上拉模式*/
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU;
	/*初始化PB4*/
	GPIO_Init(GPIOB,&GPIO_InitStruct);
	
	/*PB5 MOSI AF_PP 2MHz*/
	/*开启GPIOB的时钟*/
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
	/*选择PB5引脚*/
	GPIO_InitStruct.GPIO_Pin = GPIO_Pin_5;
	/*复用输出推挽模式*/
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
	/*最大输出速率为2MHz*/
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_2MHz;
	/*初始化PB3*/
	GPIO_Init(GPIOB,&GPIO_InitStruct);
	
	/*PA15 普通IO Out_PP 2MHz*/
	/*开启GPIOA的时钟*/
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	/*选择PA15引脚*/
	GPIO_InitStruct.GPIO_Pin = GPIO_Pin_15;
	/*通用输出推挽模式*/
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;
	/*最大输出速率为2MHz*/
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_2MHz;
	/*初始化PA15*/
	GPIO_Init(GPIOA,&GPIO_InitStruct);
	/*将PA15设置为高电平*/
	GPIO_WriteBit(GPIOA,GPIO_Pin_15,Bit_SET);
	
	//#2:初始化SPI
	
	/*开启SPI1的时钟*/
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1,ENABLE);
	/*声明SPI结构变量*/
	SPI_InitTypeDef SPI_InitStruct;
	/*选择主机模式*/
	SPI_InitStruct.SPI_Mode = SPI_Mode_Master;
	/*2线全双工*/
	SPI_InitStruct.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
	/*8bit数据宽度*/
	SPI_InitStruct.SPI_DataSize  = SPI_DataSize_8b;
	/*低极性*/
	SPI_InitStruct.SPI_CPOL = SPI_CPOL_Low;
	/*第一边沿采集*/
	SPI_InitStruct.SPI_CPHA = SPI_CPHA_1Edge;
	/*高位先行*/
	SPI_InitStruct.SPI_FirstBit = SPI_FirstBit_MSB;
	/*分频系数为64*/
	SPI_InitStruct.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_64;
	/*选择软件NSS*/
	SPI_InitStruct.SPI_NSS = SPI_NSS_Soft;
	/*初始化SPI1*/
	SPI_Init(SPI1,&SPI_InitStruct);
	/*配置软件NSS*/
	SPI_NSSInternalSoftwareConfig(SPI1,SET);
}

//创建SPI收发数据函数
void App_SPI_MasterTransmitReceive(SPI_TypeDef* SPIx,const uint8_t* pDataTx,uint8_t *pDataRx,uint16_t Size)
{
	//#1:闭合总开关
	SPI_Cmd(SPIx,ENABLE);
	
	//#2:发送第一个字节
	SPI_I2S_SendData(SPIx,pDataTx[0]);
	
	//#3:循环发送数据
	
	for(uint16_t i = 0;i < Size - 1;i++)
	{
		/*等待发送数据寄存器空标志位为1*/
		while(SPI_I2S_GetFlagStatus(SPIx,SPI_I2S_FLAG_TXE) == RESET);
		/*发送一个字节*/
		SPI_I2S_SendData(SPIx,pDataTx[i+1]);
		/*等待接收数据寄存器非空标志位为1*/
		while(SPI_I2S_GetFlagStatus(SPIx,SPI_I2S_FLAG_RXNE) == RESET);
		/*接收一个字节*/
		pDataRx[i] = SPI_I2S_ReceiveData(SPIx);
	}
	
	//#4:读取最后一个字节
	
	/*等待接收数据寄存器非空标志位为1*/
	while(SPI_I2S_GetFlagStatus(SPIx,SPI_I2S_FLAG_RXNE) == RESET);
	/*读取最后一个字节*/
	pDataRx[Size - 1] = SPI_I2S_ReceiveData(SPIx);
	
	//#5:断开总开关
	SPI_Cmd(SPIx,DISABLE);
}

//创建W25Q64存储数据函数
void App_W25Q64_SaveByte(uint8_t Byte)
{
	//#1:写使能
	/*声明存储数据数组*/
	uint8_t buffer[10];
	/*存放指令0x06*/
	buffer[0] = 0x06;
	/*通过主机PA15引脚向从机NSS引脚发送0*/
	GPIO_WriteBit(GPIOA,GPIO_Pin_15,Bit_RESET);
	/*向从机收发数据*/
	App_SPI_MasterTransmitReceive(SPI1,buffer,buffer,1);
	/*通过主机PA15引脚向从机NSS引脚发送1*/
	GPIO_WriteBit(GPIOA,GPIO_Pin_15,Bit_SET);
	
	//#2:扇区擦除
	/*存放指令0x20*/
	buffer[0] = 0x20;
	/*存放24位地址*/
	buffer[1] = 0x00;
	buffer[2] = 0x00;
	buffer[3] = 0x00;
	/*通过主机PA15引脚向从机NSS引脚发送0*/
	GPIO_WriteBit(GPIOA,GPIO_Pin_15,Bit_RESET);
	/*向从机收发数据*/
	App_SPI_MasterTransmitReceive(SPI1,buffer,buffer,4);
	/*通过主机PA15引脚向从机NSS引脚发送1*/
	GPIO_WriteBit(GPIOA,GPIO_Pin_15,Bit_SET);
	
	//#3:等待空闲
	while(1)
	{
		/*通过主机PA15引脚向从机NSS引脚发送0*/
	  GPIO_WriteBit(GPIOA,GPIO_Pin_15,Bit_RESET);
		/*存放指令0x05*/
		buffer[0] = 0x05;
		/*向从机收发数据*/
	  App_SPI_MasterTransmitReceive(SPI1,buffer,buffer,1);
		/*存放数据0xff*/
		buffer[0] = 0xff;
		/*读取一个字节*/
		App_SPI_MasterTransmitReceive(SPI1,buffer,buffer,1);
		/*通过主机PA15引脚向从机NSS引脚发送1*/
	  GPIO_WriteBit(GPIOA,GPIO_Pin_15,Bit_SET);
		/*等待读取结束*/
		if((buffer[0] & 0x01) == 0) break;
	}
	
	//#4:写使能
	/*存放指令0x06*/
	buffer[0] = 0x06;
	/*通过主机PA15引脚向从机NSS引脚发送0*/
	GPIO_WriteBit(GPIOA,GPIO_Pin_15,Bit_RESET);
	/*向从机收发数据*/
	App_SPI_MasterTransmitReceive(SPI1,buffer,buffer,1);
	/*通过主机PA15引脚向从机NSS引脚发送1*/
	GPIO_WriteBit(GPIOA,GPIO_Pin_15,Bit_SET);
	
	//#5:页编程
	/*通过主机PA15引脚向从机NSS引脚发送0*/
	GPIO_WriteBit(GPIOA,GPIO_Pin_15,Bit_RESET);
	/*存放指令0x02*/
	buffer[0] = 0x02;
	/*存放24位地址*/
	buffer[1] = 0x00;
	buffer[2] = 0x00;
	buffer[3] = 0x00;
	/*存放发送数据*/
	buffer[4] = Byte;
	/*向从机收发数据*/
	App_SPI_MasterTransmitReceive(SPI1,buffer,buffer,5);
	/*通过主机PA15引脚向从机NSS引脚发送1*/
	GPIO_WriteBit(GPIOA,GPIO_Pin_15,Bit_SET);
	
	//#6:等待空闲
	while(1)
	{
		/*通过主机PA15引脚向从机NSS引脚发送0*/
	  GPIO_WriteBit(GPIOA,GPIO_Pin_15,Bit_RESET);
		/*存放指令0x05*/
		buffer[0] = 0x05;
		/*向从机收发数据*/
	  App_SPI_MasterTransmitReceive(SPI1,buffer,buffer,1);
		/*存放数据0xff*/
		buffer[0] = 0xff;
		/*读取一个字节*/
		App_SPI_MasterTransmitReceive(SPI1,buffer,buffer,1);
		/*通过主机PA15引脚向从机NSS引脚发送1*/
	  GPIO_WriteBit(GPIOA,GPIO_Pin_15,Bit_SET);
		/*等待读取结束*/
		if((buffer[0] & 0x01) == 0) break;
	}
}

//创建W25Q64读取数据函数
uint8_t App_W25Q64_LoadByte(void)
{
	/*声明存储数据数组*/
	uint8_t buffer[10];
	/*存放指令0x03*/
	buffer[0] = 0x03;
	/*存放24位地址*/
	buffer[1] = 0x00;
	buffer[2] = 0x00;
	buffer[3] = 0x00;
	/*通过主机PA15引脚向从机NSS引脚发送0*/
	GPIO_WriteBit(GPIOA,GPIO_Pin_15,Bit_RESET);
	/*向从机收发数据*/
	App_SPI_MasterTransmitReceive(SPI1,buffer,buffer,4);
	/*存放数据0xff*/
	buffer[0] = 0xff;
	/*读取一个字节*/
	App_SPI_MasterTransmitReceive(SPI1,buffer,buffer,1);
	/*通过主机PA15引脚向从机NSS引脚发送1*/
  GPIO_WriteBit(GPIOA,GPIO_Pin_15,Bit_SET);
	/*返回接收的数据*/
	return buffer[0];
}

//创建板载LED初始化函数
void App_OnBoardLED_Init(void)
{
	/*启动GPIOC时钟*/
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC,ENABLE);
	/*声明GPIO结构变量*/
	GPIO_InitTypeDef GPIO_InitStruct;
	/*选择PC13引脚*/
	GPIO_InitStruct.GPIO_Pin = GPIO_Pin_13;
	/*输出开漏模式*/
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_OD;
	/*最大输出速率为2MHz*/
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_2MHz;
	/*初始化PA0引脚*/
	GPIO_Init(GPIOC,&GPIO_InitStruct);
}
相关推荐
DLGXY2 小时前
STM32——DMA(十四)
stm32·单片机·嵌入式硬件
恒锐丰小吕2 小时前
屹晶微 EG2121 中压250V半桥驱动芯片技术解析
嵌入式硬件·硬件工程
LeoZY_2 小时前
CH347 USB转JTAG功能使用笔记:CH347根据SVF文件实现任意FPGA下载
笔记·stm32·嵌入式硬件·fpga开发·硬件架构·硬件工程
孞㐑¥2 小时前
算法—字符串
开发语言·c++·经验分享·笔记·算法
【 STM32开发 】2 小时前
【STM32 + CubeMX】 CAN 通信
stm32·cubemx·can·hal·通信·f407
一起养小猫2 小时前
Flutter for OpenHarmony 实战:打造功能完整的云笔记应用
网络·笔记·spring·flutter·json·harmonyos
__万波__2 小时前
STM32L475定时器实验
stm32·单片机·嵌入式硬件
AI视觉网奇2 小时前
static mesh 转skeleton mesh
笔记·学习·ue5