STM32单片机学习(29) —— SPI引脚和外设初始化

文章目录

概述

到此为止,关于SPI通信的时序,STM32的SPI外设,这些内容,我们都基本搞清楚了。

所以我们先来写一部分代码,我们按照顺序来完成:

  1. 单片机SPI通信的引脚和工作模式选择
  2. 单片机与W25Q64通信实验接线
  3. SPI通信引脚和外设的初始化

除此之外,我们还需要来实现一个"与从机无关的、通用的"SPI通信数据交换函数。

引脚和工作模式选择

首先我们选择的SPI外设是:SPI1

它挂载在时钟频率默认是72MHz的APB2外设总线上。

《参考手册》的AFIO模块提供了各个外设的引脚复用、重定义选择。

比如SPI1外设,如下图所示:

直接查手册,而不是查引脚定义表,更方便一些。

所以我们选择的引脚是:

SPI1_SCK ---- PA5

SPI1_MISO ---- PA6

SPI1_MOSI ---- PA7

SPI1_NSS ---- 不使用,直接配置软件NSS即可

那么这些引脚都需要配置成什么工作模式呢?

《参考手册》的"GPIO外设配置"小节中,也对外设使用GPIO引脚时,设置的工作模式做了推荐。

SPI外设的推荐工作模式,如下图所示:

最终结论是:

SPI1_SCK,即单片机的PA5引脚,设置工作模式为推挽复用输出模式。

SPI1_MISO,即单片机的PA6引脚,设置工作模式为浮空输入模式。

SPI1_MOSI,即单片机的PA7引脚,设置工作模式为推挽复用输出模式。

其中SCK和MOSI引脚,都需要强推输出高低电平,所以选择推挽输出模式,将输出控制权交给SPI1外设,所以是复用推挽输出模式。

MISO引脚需要接收从机的输入,所以选择浮空输入模式是最常见、最推荐的。

上拉输入模式也可以选择的,和浮空输入区别不大。

但不能选择下拉输入模式,下拉输入模式中的下拉电阻对整个通信的过程是有害的。

最后还需要选择一个普通GPIO引脚,作为单片机连接从机片选NSS引脚的控制引脚。

W25Q64通信实验接线

首先,拿出设备盒中的W25Q64,如下图所示:

然后按照下列方式进行接线:

说明一下这张图:

  1. PA9和PA10是用于接入PC端USB转串口设备的,图中没有画出,但你要把线接好,我们做实验会用到串口。
  2. 关于W25Q64是什么,怎么用,会在下一文中阐述
  3. W25Q64的6个引脚都需要接入面包板,需要用六根"公对母"杜邦线。
  4. 接线的时候要认真仔细,不要接错了。

W25Q64的CS片选引脚 ---- 单片机PA4引脚(普通IO引脚)

W25Q64的DO输出引脚 ---- 单片机PA6引脚(MISO)

W25Q64的CLK时钟引脚 ---- 单片机PA5引脚(CLK)

SPI通信引脚和外设的初始化

到这里,我们就可以开始进行通信引脚的初始化工作,以及SPI外设的初始化工作。

当然,本章节还无法看到实验现象,只能完成一些基础的、关于SPI的初始化配置的工作。

具体的实验环节,还需要到下一文中

首先我们在工程模版的基础上,新建一个"W25Q64"模块,创建对应头文件和实现源文件。

代码部分

全部的代码先贴在这里,里面需要着重讲述的放在后面文字叙述

c 复制代码
// W25Q64.h头文件
#ifndef __W25Q64_H
#define __W25Q64_H
#include "stm32f10x.h"


void init_W25Q64(void);
#endif
c 复制代码
// W25Q64.c源文件

#include "./W25Q64.h"

#define NSS GPIO_Pin_4
#define SCK GPIO_Pin_5
#define MISO GPIO_Pin_6
#define MOSI GPIO_Pin_7
#define SPI_PORT GPIOA
#define GPIO_SPI_CLOCK RCC_APB2Periph_GPIOA
#define	SPI_CLOCK RCC_APB2Periph_SPI1
#define SPIX SPI1

// 初始化引脚
static void init_GPIO_W25Q64(void){
	// 开时钟
	RCC_APB2PeriphClockCmd(GPIO_SPI_CLOCK, ENABLE);
	
	GPIO_InitTypeDef GPIO_InitStruct;
	// 速度选择50MHz,保证SPI的通信速度
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
	// 初始化SCK,作为主机,所以选择复用推挽模式
	GPIO_InitStruct.GPIO_Pin = SCK;
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_Init(SPI_PORT, &GPIO_InitStruct);
	
	// 初始化MISO,作为主机,所以选择浮空输入模式	
	GPIO_InitStruct.GPIO_Pin = MISO;
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;
	GPIO_Init(SPI_PORT, &GPIO_InitStruct);
	
	// 初始化MOSI,作为主机,所以选择复用推挽模式	
	GPIO_InitStruct.GPIO_Pin = MOSI;
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_Init(SPI_PORT, &GPIO_InitStruct);
	
	// 初始化NSS, 选择通用推挽,输出高电平,表示不选中从机
	GPIO_InitStruct.GPIO_Pin = NSS;
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;
	GPIO_Init(SPI_PORT, &GPIO_InitStruct);

	GPIO_SetBits(SPI_PORT, NSS);
	
}
// 初始化外设
void init_SPI(void){
	// 初始化引脚
	init_GPIO_W25Q64();
	// 开启SPI时钟
	RCC_APB2PeriphClockCmd(SPI_CLOCK, ENABLE);
	// 初始化SPI
	SPI_InitTypeDef SPI_InitStruct;
	// SPI1时钟线分频系数:当前选择1/4分频
    // 时钟线频率为:72 / 4 = 18MHz
	SPI_InitStruct.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_4;
	// 设置第一边沿
	SPI_InitStruct.SPI_CPHA = SPI_CPHA_1Edge;
	// 设置时钟极性 低电平为空闲
	SPI_InitStruct.SPI_CPOL = SPI_CPOL_Low ;
	// 不设置校验,故不设置多项式校验
	//SPI_InitStruct.SPI_CRCPolynomial = ;
	// 外设移位寄存器为8bit
	SPI_InitStruct.SPI_DataSize = SPI_DataSize_8b ;
	// 全双工
	SPI_InitStruct.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
	// MSB
	SPI_InitStruct.SPI_FirstBit = SPI_FirstBit_MSB;
	// 主机模式
	SPI_InitStruct.SPI_Mode = SPI_Mode_Master;
	// 软件实现
	SPI_InitStruct.SPI_NSS = SPI_NSS_Soft;
	
	// 初始化SPI
	SPI_Init(SPIX, &SPI_InitStruct);
	
	// 选择软件NSS时,需要额外调用此函数,用于设置SSI为1,代表输入高电平
    SPI_NSSInternalSoftwareConfig(SPIX, SPI_NSSInternalSoft_Set);
	// 使能开启SPI1
	SPI_Cmd(SPIX, ENABLE);
}

// 数据交换
// SPI通信实质上是两个移位寄存器进行数据交换
static uint8_t SwapByte(uint8_t Byte){
	// 等待发送寄存器为空
	while( SPI_I2S_GetFlagStatus(SPIX, SPI_I2S_FLAG_TXE) == RESET);
	// 发送寄存器为空,发送数据
	SPI_I2S_SendData(SPIX, Byte);
	
	// 等待接收寄存器为空
	while( SPI_I2S_GetFlagStatus(SPIX, SPI_I2S_FLAG_RXNE) == RESET);
	// 接收数据
	return SPI_I2S_ReceiveData(SPIX);
}

// 选中W25Q64(CS = 0)
static void W25Q64_Select(void) {
    GPIO_ResetBits(SPI_PORT, NSS);
}


// 取消选中W25Q64(CS = 1)
static void W25Q64_Deselect(void) {
    GPIO_ResetBits(SPI_PORT, NSS);
}

为什么设置为mode0和MSB First

我们在前面讲时序结构时,提到过:SPI通信协议提供了多种时序结构可选择,主要目的就是为了适配不同从机对时序的要求。

所以,之所以这么设置时序结构,都是从机W25Q64 的要求。

那我怎么知道这是W25Q64 从机的要求呢?

当然不是我猜的,而是通过查阅W25Q64 的官方文档得知的。如下图所示:

我们还设置了SPI时钟频率是18MHz,这个设置相对比较保守,主要考虑到我们使用面包板接线,电路不是非常稳定。

实际上你完全可以设置得更高一些(36MHz),因为W25Q64 的官方文档中,有如下内容:

数据收发函数

在串口和I2C通信中,完成基础的初始化配置工作后,紧接着就需要编写基本的发送和接收数据的函数了。

SPI通信也不例外,但稍有区别的是:

SPI通信是全双工,进行的是数据交换,发送1个字节,就要收到1个字节。

整个过程在上一章节已经分析过了,SPI外设收发处理一个字节数据的过程是这样的:

  1. 主机判断和等待TXE标志位置位,也就是等待发送数据寄存器为空。
  2. 在 TXE = 1 的前提下,主机将 1 个字节写入发送数据寄存器(TDR)
  3. 该1个字节的数据进入主机移位寄存器,被逐bit移除发送。
  4. 与此同时,从机也会逐bit发送数据,主机的移位寄存器也会同步接收这些bit数据。
  5. 等待主机接收数据寄存器非空,当主机接收数据寄存器非空时:
  6. 意味着主机已经将1个字节数据完全发给从机。
  7. 意味着从机已经将1个字节数据完全发给主机。
  8. 主机读接收数据寄存器。

如此一轮操作下来,主机就完成了发送1个字节数据,同时也收到1个字节数据。

此函数用于W25Q64内部逻辑实现,所以不需要将它暴露在外界,它不需要将声明写入头文件。

可以在源文件中实现它,并用static修饰函数。

下面我们再提供两个用于选中/取消选中W25Q64从机通信。

从机片选与取消片选函数

选中就是拉低CS,取消选中就是拉高CS。

这两个函数用于W25Q64内部逻辑实现,所以不需要将它们暴露在外界,它们不需要写入头文件。

可以在源文件中实现它们,并用static修饰它们的函数。

以上,所有的准备工作都搞定了,下一文中我们来学习一个W25Q64的使用,并完成相关实验。

相关推荐
小黑随笔17 小时前
Python asyncio 模块学习总结:从“等着”到“切出去干点别的”
开发语言·python·学习
zhaokuangkuang_17 小时前
Java学习
java·学习·算法
希冀12317 小时前
【CSS学习第十三篇】
前端·css·学习
我能坚持多久17 小时前
STL详解——stack以及queue的模拟实现
开发语言·c++·学习
Harm灬小海17 小时前
【云计算学习之路】企业常用服务搭建:MySQL 8.0
linux·运维·学习·mysql·云计算
weixin_5500831517 小时前
PyTorch 实战:从零搭建手写数字识别系统(CNN 卷积神经网络)从理论到实践,手把手教你用 PyTorch 实现 99.38% 准确率的手写数字识别
开发语言·python·学习·cnn·课程设计·手写数字识别
私人珍藏库17 小时前
【Android】小思AI2.1学习工具-内置海量学习游戏-帮助学 -最强一对一辅导补习
android·学习·游戏·app·工具·软件·多功能
xiangw@GZ17 小时前
DDR专题-CK 时钟、MT/s 与带宽的关系
单片机·嵌入式硬件
AI职业加油站17 小时前
从政策到实战:人工智能算法工程师证书的完整价值分析
人工智能·python·学习·算法·职场和发展