stm32-掌握SPI原理(一)

目录

[一 概述](#一 概述)

[二 介绍SPI](#二 介绍SPI)

(1)I2C和SPI的区别

(2)SPI的介绍

(3)SPI的物理架构

(4)工作原理

(5)工作模式

时钟极性CPOL

时钟相位CPHA

[三 底层SPI.c函数代码分析](#三 底层SPI.c函数代码分析)

头文件代码

SPI底层函数代码

发送接收字节函数代码


一 概述

在正式开始讲 SPI 之前,如果你之前看过我写的那篇**《别急着点亮 OLED,先把 STM32 的 I2C 通信搞明白》**,那你现在再来看 SPI 会轻松很多,因为两者在通信机制上有不少可以对比记忆的地方。不过就算你没学过 I2C也没关系,这篇文章我会从 SPI 的最基础内容讲起,帮助你一步一步建立起清晰的理解。

这篇文章不会直接跳到复杂的实战例子,而是先把 SPI 的核心原理和基本操作讲清楚,然后再把SPI底层的代码讲解清楚。等你掌握了这些"底层骨架",再去看像 W25Q64、NRF24L01 这些实际应用的代码,你会发现一切都顺理成章、好懂多了。

所以请你耐心看完,相信我,你一定能明白 SPI 到底是怎么回事。

二 介绍SPI

(1)I2C和SPI的区别

为了让大家更加清晰地了解I2C和SPI之间地区别,我给大家用表格形式写出来,我觉得这样子会更加地清晰明了。

比较项 I2C SPI
通讯方式 半双工通信,不能同时收发 全双工通信,可以同时收发
协议复杂度 协议较复杂,需要起始位、地址、应答等 协议较简单,直接通过时钟同步数据
从机选择方式 通过地址识别多个从机 每个从机一个片选引脚(NSS或SS或CS)
通讯速度 一般为 100kHz 左右 最高可达 50MHz(视具体芯片而定)
所需引脚数量 两根(SCL、SDA) 四根(SCK、MOSI、MISO、NSS或SS或CS)
抗干扰能力 强(基于总线结构,有确认机制) 弱(点对点高速传输,对时序要求高)
硬件资源开销 相对更大
[I2C 与 SPI 通讯协议对比表]

在这里面,要特别记住一个点:I2C是有应答机制的,SPI没有应答机制**。**面试官问到关于I2C和SPI的区别问题的时候,好多人都会忘记这一点,这一点大家要牢记!

(2)SPI的介绍

SPI(Serial Peripheral Interface) 中文名叫 串行外设接口,是一种常用的高速、全双工、主从式同步串行通信协议,常用于 MCU(如 STM32)与外设(如 Flash、OLED、NRF24L01、ADC 等)之间的数据交换。其中,同步就代表着共用同一个时钟SCK,到了下面讲SPI的物理架构,大家就会发现了。

(3)SPI的物理架构

SPI包含四条通信线,分别为SCK、MOSI、MISO、SS,他们的作用如下:

名称 方向 功能说明
SCK 主机输出 → 从机输入 由主机产生的时钟信号,用于同步通信节奏
MOSI 主机输出 → 从机输入 主机发送数据,从机接收数据
MISO 从机输出 → 主机输入 从机发送数据,主机接收数据
SS / CS 主机输出 → 从机输入 从机片选信号,低电平有效,主机控制哪个从机参与通信

单片机想要和哪一个从机通信,我们只需要拉低片选SS这个引脚处的电平即可~,具体的工作原理我在后面讲时序部分会进行详细介绍。

在本次的stm32f103c8t6这个芯片上,一共有三个SPI接口,SPI1-3,其中SPI1和SPI3是具有重映射功能。
摘自stm32f1xx的中文参考手册-第120页

(4)工作原理

在正式进入SPI的工作原理讲解前,我先给大家画个图,先去理解一个简单的主机从机和移位寄存器的工作原理:

在这张图片里面的主从机中的移位寄存器都是向左移位的,其中,单片机内部时钟会为两个移位寄存器提供时间基准。

现在请看上面这张图,我给大家口述功能:现在我想要主机给从机发送数据,也就是MOSI的功能,请看主机中移位寄存器的数据:1010 1010,这个时候,SPI 的波特率发生器(可以理解为一个定时器),每发出一个时钟脉冲,主机就会把最左边的那一位"推出去",通过MOSI 线,发给从机,1010 1010这个数最左边的1给到了从机里面的移位寄存器的最后一位,然后原先是1010 1010的数就整体向左移动一位,变成0101 0100(因为向左移动之后,最右边空了一个自动补0),循环八次,就把左侧主机中的1010 1010写到了右边从机上了,这就是MOSI。

MISO呢,也差不多是同样的道理,右侧的从机给主机发送数据,则是右边的移位寄存器的最左的数字1,传给主机的最后一位,然后剩下的111 0000向左移动一位,就成了1110 0000(右侧自动补零),不断循环八次,就将最初的1111 0000移动到了主机上的移位寄存器当中。

有了如上的思路之后,我们看《stm32f1xx的中文参考手册》:

不难看出,这里怎么多了两个东西,分别是发送缓冲区和接收缓冲区,事实上,我们是不能直接通过数据控制器把数据发送给移位寄存器的,而是先把数据发送给发送缓冲区TDR,然后再通过TDR再发送给移位寄存器的,发出去也是同样的道理,只不过到了接收缓冲区就是SPI的最后一步了,之后需要CPU或者DMA来主动读取,数据就一直放在接收缓冲区部分了,CPU读取接收缓冲区

RDR的数据之后,就可以放到RAM、处理逻辑、打印串口、写屏幕等等。

,关于CPOL和CPHA部分,请继续往下看,那里是关于工作模式部分的,于是,我进行了一个"简单的总结"(哭,我真的做了好久这张图(留下了不争气的眼泪))

(5)工作模式

引入两个全新的概念,时钟极性CPOL、时钟相位CPHA。

时钟极性CPOL

时钟极性,是指SPI通讯设备处于空闲状态时,SCK信号线的电平信号(即SPI通讯开始前、 NSS线为高电平时SCK的状态)。

CPOL = 0时,SCK在空闲状态为低电平

CPOL = 1时,SCK在空闲状态为高电平

时钟相位CPHA

时钟相位是指数据的采样的时刻。

CPHA=0时,MOSI或MISO数据线上的信号将会在SCK时钟线的"奇数边沿"被采样

CPHA=1时, 数据线在SCK的"偶数边沿"采样

也就有了如下的四种工作模式:

SPI工作模式 CPOL CPHA SCL空闲状态 采样边沿 采样时刻
模式0 0 0 低电平 上升沿 奇数边沿
模式1 0 1 低电平 下降沿 偶数边沿
模式2 1 0 高电平 下降沿 奇数边沿
模式3 1 1 高电平 上升沿 偶数边沿

大家可能看的比较生硬,给大家举个例子,模式0,也就是CPOL=0,CPHA=0。在图中为什么能在MOSI这根线上得出10100呢?是因为在上升沿处画一根竖线,穿过了MOSI,如果这根线穿过MOSI的高电平那就为1,如果是低电平那就为0。

三 底层SPI.c函数代码分析

头文件代码

cs 复制代码
#include "stm32f10x.h"                  // Device header
#include "spi.h"

以上是头文件部分的代码

SPI底层函数代码

cs 复制代码
//用SPI1处的引脚
void W25Q64_SPI_Init(void)
{
	
	GPIO_InitTypeDef GPIO_InitStructure;
    SPI_InitTypeDef SPI_InitStructure;

    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_SPI1, ENABLE);

    // CS 管脚(PA4)
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
    GPIO_SetBits(GPIOA, GPIO_Pin_4);  // 拉高片选

    // SCK (PA5), MOSI (PA7)
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_7;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
	/*
	  为什么要把cs配置成推挽输出,而把SCK和MOSI配置成复用推挽呢?
		答:这些引脚(如 PA5、PA7)是由 SPI1 外设控制的。
		你不能用 GPIO_SetBits/GPIO_ResetBits 控制它,而是通过 SPI_SendData 控制。
		而对于CS来说,他只是作为普通的IO口
	*/

    // MISO (PA6)
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
    /*
    为什么这里MISO引脚配置成浮空输入?
        答:因为MISO的情况是单片机作为从机,SPI外设作为主机,单片机是接收状态,有信号
        给单片机,单片机此刻就是接收(输入)
    */

    // SPI 配置
    SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
    SPI_InitStructure.SPI_Mode = SPI_Mode_Master;
    SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;
    SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;		// CPOL=0,空闲为低
    SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge;	// CPHA=0,第一个边沿采样
    SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;
    SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_256;
    SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;
    SPI_InitStructure.SPI_CRCPolynomial = 7;
    SPI_Init(SPI1, &SPI_InitStructure);

    /*
    SPI_CRCPolynomial = 7 是一个默认配置值,只有在启用 SPI_CRCCALCULATION_ENABLE 时
    才会用到。    你当前没启用 CRC 功能,所以可以无视它,留着也不影响。
    */

    SPI_Cmd(SPI1, ENABLE);
	
	
	
}

该函数我已经做好相应的注释了,大家直接进入到代码当中观看即可,同时我要说明一下为什么要选这几个引脚为CS、MOSI等等,大家请翻看《stm32fxx的中文参考手册》的第120页以及第111页
标题:SPI引脚(中文参考手册第120页)

标题:SPI的引脚该配置哪种模式(中文参考手册第111页)

我们看如上照片,由于官网手册已经表明SPI1的引脚为PA4、PA5、PA6、PA7这几个,并且配置成何种模式已在上面体现出来,我们就不能再随便更改了,所以我们只需要将对应的引脚插好即可~

发送接收字节函数代码

cs 复制代码
//发送/接收一个字节
uint8_t w25q64_spi_swap_byte(uint8_t data)
{
	//TXE 为 1 表示可以写入数据了
    while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET);
    SPI_I2S_SendData(SPI1, data);
	//RXNE 为 1 表示已经有数据被接收到,可以读取数据
    while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) == RESET);
	
	//再将SPI1读取的数据,再返还给这个函数的返回值
    return SPI_I2S_ReceiveData(SPI1);
}

大家还记得函数前面有东西(指的是uint8_t)和后面有东西(uint8_t data)都代表着什么意思嘛,大家如果这里不懂大家请在评论区告诉我,如果有人我就重新写个详细的文章进行讲解。

这个函数后面有东西,也就代表着这个data是需要我们自己写data是什么,但是这里就不进行过多赘述哈,关于W25Q64的相关运动,会在下一篇文章当中讲解。当然哦,以上所有函数都要放到spi.h当中进行函数的声明哦~


SPI的原理很复杂,我写本篇文章还是希望大家能认真仔细看的,内容干货偏多,请大家慢慢吸收相应的知识,如果大家觉得我写的不错,请给我点赞收藏加关注,多多转发哦,目前我还是一名准大三的学生,如果我有写的不足的地方,还请多多指正(抱拳鞠躬),谢谢大家啦!!!

关于SPI的应用W25Q64,请大家耐心观看完本篇文章再进行后续关于W25Q64的实操内容哦

相关推荐
猫猫的小茶馆16 分钟前
【STM32】通用定时器基本原理
c语言·stm32·单片机·嵌入式硬件·mcu·51单片机
jingshaoqi_ccc1 小时前
stm32的USART使用DMA配置成循环模式时发送和接收有着本质区别
stm32·单片机·嵌入式硬件
玉树临风江流儿4 小时前
炸鸡派-定时器基础例程
单片机·嵌入式硬件
is08155 小时前
STM32的 syscalls.c 和 sysmem.c
c语言·stm32·嵌入式硬件
学不动CV了6 小时前
数据结构---链表结构体、指针深入理解(三)
c语言·arm开发·数据结构·stm32·单片机·链表
工业互联网专业10 小时前
汇编与接口技术:8259中断实验
汇编·单片机·嵌入式硬件·8259中断实验
brave and determined10 小时前
国产MCU学习Day6——CW32F030C8T6: I2C功能详解与应用案例
单片机·eeprom·i2c·cw32f030c8t6·cw32·cw32f030·中断读取eeprom
梁山1号11 小时前
【ESP32】3.串口的发送与接受
单片机·物联网
KaiGer66611 小时前
AUTOSAR进阶图解==>AUTOSAR_SWS_V2XFacilities
单片机·汽车·嵌入式·autosar