STM32标准库学习笔记(十)SPI

server/2025/1/14 9:28:21/

前言


学习永无止境!本篇是嵌入式开发之片上外设SPI,了解基本硬件原理以及通信协议。
注:本文章为学习笔记,部分图片与文字来源于网络/江协科技课程/手册,如侵权请联系!谢谢!


一、SPI通信概述


1.1 SPI基本概念

        SPI(Serial Peripheral Interface)是由摩托罗拉公司提出的通信协议,是一种高速全双工、同步串行通信总线,主要应用在Flash、ADC、编码器等要求速率较高的场合。

1.2 SPI物理层

①信号线:

  • SCLK(Serial Clock):时钟线,用于数据同步,由主机产生,决定了通信速率,不同设备的之间的通信速率受限与低速设备;
  • MOSI(Master Output,Slave Input):主出从进,主设备发送数据,从设备接收数据线,数据方向由主机向从机;
  • MISO(Master Input,Slave Output):主进从出,主设备接收数据,从设备发送数据线,数据方向由从机到主机;
  • CS(Chip Select):片选线,也叫SS(Slave Select),用来寻址,指定要通信设备,一般有几个从设备,就要有几根CS。

②电平:

        TTL电平,时钟极性(CPOL)与时钟相位(CPHA)的的选择,决定数据是在时钟上升沿采样还是下降沿采样,共四种模式,下面具体介绍。

1.3 SPI协议层

①基本时序:

  • 起始信号:CS信号由高变低,表示选中从机,相应从机检测到起始信号,知道被主机选中;
  • 数据发送与接收:数据高位先行(MSB),双方数据在时钟上升沿或下降沿发送(接收);
  • 停止信号:CS信号由低变高,表示通信结束。

②SPI模式决定因素:

  • CPOL:时钟极性,SCLK时钟线在空闲状态是高电平还是低电平由此决定,当CPOL=0时,SCLK空闲低电平。当CPOL=1时,SCLK空闲状态高电平;
  • CPHA:时钟相位,数据的采样时刻,当CPHA=0时,MOSI或MISO数据线上的信号将会在时钟线的奇数边沿被采样。当CPHA=1时,MOSI或MISO数据线上的信号将会在时钟线的偶数边沿被采样。

③SPI四种通信模式:

  • CPHA=0,CPOL=0:数据在时钟奇数边沿采样,时钟默认空闲电平;
  • CPHA=0,CPOL=1:数据在时钟奇数边沿采样,时钟默认空闲电平;

  • CPHA=1,CPOL=0:数据在时钟偶数边沿采样,时钟默认空闲电平;
  • CPHA=1,CPOL=1:数据在时钟偶数边沿采样,时钟默认空闲电平;


二、STM32的SPI外设


2.1 基本简介

  • 主从机选择:可作为SPI通讯主机,也可作为从机;
  • 时钟频率:最大SCLK的频率为fpclk/2(f103的fpclk1为36MHZ,fpclk2为76MHZ);
  • 数据格式:可选MSB先行,也可选LSB先行;
  • 模式:除了支持正常双线全双工的四种模式,还有双向单线(同时向一个方向传输,速度提高一倍)以及单线模式(减少硬件接线);

2.2 硬件基本结构框图

  • ①外部引脚:四个通信口与GPIO口的对应关系,需要查表;
  • ②时钟控制:根据控制寄存器CR1BR[0:2],对fpclk进行分频;
  • ③数据控制:数据移位寄存器以发送缓冲区作为数据源,把数据一位一位发送,当从外部接收数据时,数据移位寄存器把数据线数据采样一位位存储到接收缓冲区。数据帧长度设定GFF位可为8或16位,配置LSBFIRST,可选择MSB先行或LSB先行;
  • ④主控:通过改变CR1/2的参数,可以配置SPI模式、波特率、LSB/MSB、主从模式、单双向模式,状态寄存器SR实时反馈SPI工作状态,除此之外还有中断、DMA请求等控制。

2.3 主机发送与接收

  • ①CS片选:当要进行通信,首先选择从机地址,CS由高电平置为低电平;
  • ②SCLK:CS片选同时时钟进行工作;
  • ③数据发送:MOSI把发送缓冲区数据一位位传输出去,当发送完一帧数据,SR置TXE标志1(表示发送缓冲区已清空),若要再次发送数据,在TXE为1时,写入DR数据即可;
  • ④数据接收:MISO移位寄存器把数据线数据采样一位位存储到接收缓冲区,当接收完一帧数据,SR置RXNE标志1(表示接收缓冲区非空),即可读取DR数据,每次等待RXNE标志位1,即可读取接收数据。


三、应用


3.1 软件模拟SPI

①本次示例说明:使用软件模拟CS、SCLK、MOSI、MISO,利用I/O高低电平翻转实现SPI时序。

配置CS\SCLK\MOSI为推挽输出(PA4\5\7),MISO为上拉输入(PA6),选用模式1(SCLK初始为低电平,数据在奇数边沿采样)。

②配置步骤:

  • 开启RCC时钟:开启GPIOA外设时钟;
  • 配置GPIO:CS(GPIOA_Pin4)、SCLK(GPIOA_Pin5)、MOSI(GPIOA_Pin7)推挽输出,MISO(GPIOA_Pin6)上拉输出;
  • 起始信号;
  • 停止信号;
  • 发送字bit;
  • 接收bit;
  • 发送与接收字节:通过循环移位进行写入与读取,将数据从高位依次取出放在发送地址,每取一位,置SCLK位1,并读取MISO电平信号,读取完毕,置SCLK为0。

③代码实战:

MSPI.c:

#include "stm32f10x.h"                  // Device header/*设置CS电平*/
void MySPI_W_CS(uint8_t BitValue)
{GPIO_WriteBit(GPIOA, GPIO_Pin_4, (BitAction)BitValue);		
}/*写SCLK*/
void MySPI_W_SCK(uint8_t BitValue)
{GPIO_WriteBit(GPIOA, GPIO_Pin_5, (BitAction)BitValue);		
}/*发送MOSI电平*/
void MySPI_W_MOSI(uint8_t BitValue)
{GPIO_WriteBit(GPIOA, GPIO_Pin_7, (BitAction)BitValue);		
}/*接收MISO电平*/
uint8_t MySPI_R_MISO(void)
{return GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_6);			
}/*初始化引脚配置*/
void MySPI_Init(void)
{/*开启GPIOA外设时钟*/RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);	/*GPIO初始化*/GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;//将PA4、PA5和PA7引脚初始化为推挽输出GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_7;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA, &GPIO_InitStructure);					GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;//将PA6引脚初始化为上拉输入GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA, &GPIO_InitStructure);					/*设置默认电平*/MySPI_W_CS(1);	//CS默认高电平										MySPI_W_SCK(0);	//SCLK默认低电平										
}/*协议层*//*起始信号*/
void MySPI_Start(void)
{MySPI_W_CS(0);				
}/*结束信号*/
void MySPI_Stop(void)
{MySPI_W_CS(1);				
}/*发送接收数据字节*/
uint8_t MySPI_SwapByte(uint8_t ByteSend)
{uint8_t i, ByteReceive = 0x00;					//定义接收的数据,并赋初值0x00,此处必须赋初值0x00,后面会用到for (i = 0; i < 8; i ++)						//循环8次,依次交换每一位数据{MySPI_W_MOSI(ByteSend & (0x80 >> i));		//使用掩码的方式取出ByteSend的指定一位数据并写入到MOSI线MySPI_W_SCK(1);								//拉高SCK,上升沿移出数据if (MySPI_R_MISO() == 1){ByteReceive |= (0x80 >> i);}	//读取MISO数据,并存储到Byte变量//当MISO为1时,置变量指定位为1,当MISO为0时,不做处理,指定位为默认的初值0MySPI_W_SCK(0);								//拉低SCLK,下降沿移入数据}return ByteReceive;								//返回接收到的一个字节数据
}

3.2 硬件SPI

①本次示例说明:使用STM32硬件SPI配置,选用SPI1外设,配置时钟极性为低电平,相位为上升沿采样。

②配置步骤:

  • 开启RCC时钟:开启GPIOA、SPI1外设时钟;
  • 配置GPIO:CS(GPIOA_Pin4)为推挽输出,SCLK(GPIOA_Pin5)、MOSI(GPIOA_Pin7)复用推挽输出,MISO(GPIOA_Pin6)上拉输出;
  • 起始信号;
  • 停止信号;
  • 发送与接收字节:等待TXE为空,写入字节,等待RXNE非空,读取字节。

③代码实战:

MSPI.c:

#include "stm32f10x.h"                  // Device header/*设置CS*/
void MySPI_W_CS(uint8_t BitValue)
{GPIO_WriteBit(GPIOA, GPIO_Pin_4, (BitAction)BitValue);		
}/*初始化SPI*/
void MySPI_Init(void)
{/*开启GPIOA\SPI1的外设时钟*/RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);	/*GPIO初始化*/GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;//将PA4引脚初始化为推挽输出GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA, &GPIO_InitStructure);					GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_7;//将PA5和PA7引脚初始化为复用推挽输出GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA, &GPIO_InitStructure);					GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;//将PA6引脚初始化为上拉输入GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA, &GPIO_InitStructure);					/*SPI初始化*/SPI_InitTypeDef SPI_InitStructure;						//定义结构体变量SPI_InitStructure.SPI_Mode = SPI_Mode_Master;			//模式,选择为SPI主模式SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;	//方向,选择2线全双工SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;		//数据宽度,选择为8位SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;		//先行位,选择高位先行SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_128;	//波特率分频,选择128分频SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;				//SPI极性,选择低极性SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge;			//SPI相位,选择第一个时钟边沿采样,极性和相位决定选择SPI模式0SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;				//NSS,选择由软件控制SPI_InitStructure.SPI_CRCPolynomial = 7;				//CRC多项式,暂时用不到,给默认值7SPI_Init(SPI1, &SPI_InitStructure);						//将结构体变量交给SPI_Init,配置SPI1/*SPI使能*/SPI_Cmd(SPI1, ENABLE);									/*设置默认电平*/MySPI_W_CS(1);											
}/*起始信号*/
void MySPI_Start(void)
{MySPI_W_CS(0);				
}/*结束信号*/
void MySPI_Stop(void)
{MySPI_W_CS(1);				
}/*** 函    数:SPI交换传输一个字节,使用SPI模式0* 参    数:ByteSend 要发送的一个字节* 返 回 值:接收的一个字节*/
uint8_t MySPI_SwapByte(uint8_t ByteSend)
{while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) != SET);	//等待发送数据寄存器空SPI_I2S_SendData(SPI1, ByteSend);								//写入数据到发送数据寄存器,开始产生时序while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) != SET);	//等待接收数据寄存器非空return SPI_I2S_ReceiveData(SPI1);								//读取接收到的数据并返回
}

待续...


http://www.ppmy.cn/server/158246.html

相关文章

Node.js——path(路径操作)模块

个人简介 &#x1f440;个人主页&#xff1a; 前端杂货铺 &#x1f64b;‍♂️学习方向&#xff1a; 主攻前端方向&#xff0c;正逐渐往全干发展 &#x1f4c3;个人状态&#xff1a; 研发工程师&#xff0c;现效力于中国工业软件事业 &#x1f680;人生格言&#xff1a; 积跬步…

基于Android的校园自助打印系统的设计与实现

博主介绍&#xff1a;java高级开发&#xff0c;从事互联网行业多年&#xff0c;熟悉各种主流语言&#xff0c;精通java、python、php、爬虫、web开发&#xff0c;已经做了多年的设计程序开发&#xff0c;开发过上千套设计程序&#xff0c;没有什么华丽的语言&#xff0c;只有实…

使用 Python 实现自动化办公(邮件、Excel)

目录 一、Python 自动化办公的准备工作 1.1 安装必要的库 1.2 设置邮件服务 二、邮件自动化处理 2.1 发送邮件 示例代码 注意事项 2.2 接收和读取邮件 示例代码 三、Excel 自动化处理 3.1 读取和写入 Excel 文件 示例代码 3.2 数据处理和分析 示例代码 四、综合…

docker的学习

理解 我对docker的理解&#xff1a;docker其实就是一个服务&#xff0c;需要进行启动还有关闭。 对镜像的理解&#xff1a;镜像相当于一个安装包&#xff08;可以理解为压缩文件&#xff0c;所以需要从网络上进行下载&#xff09;&#xff0c;镜像下载完之后就要对其运行。运…

【机器学习:十一、神经网络的数学表达式】

神经网络的数学表达式是机器学习的重要理论基础&#xff0c;通过数学语言描述神经网络的结构和工作原理&#xff0c;有助于理解其运算过程、优化方法和性能改进。以下从背景意义、数学表达的重要性、隐藏层与输出层的数学表达&#xff0c;再到二层神经网络的数学表达&#xff0…

【机器学习:十九、反向传播】

1. 计算图和导数 计算图的概念 计算图&#xff08;Computation Graph&#xff09;是一种有向无环图&#xff0c;用于表示数学表达式中的计算过程。每个节点表示一个操作或变量&#xff0c;每条边表示操作的依赖关系。通过计算图&#xff0c;可以轻松理解和实现反向传播。 计算…

基于单片机的公交车报站系统设计

摘 要 目前&#xff0c;我国经济快速发展&#xff0c;城市化进程不断加快。公交车作为居民日常出行的重要交通工具&#xff0c;公交车的服务质量直接影响到乘客的乘车体验&#xff0c;关系到城市智能交通的发展。为了解决传统公交车上车载终端信息闭塞的问题&#xff0c;提高公…

《零基础Go语言算法实战》【题目 2-26】goroutine 的执行效率问题

《零基础Go语言算法实战》 【题目 2-26】goroutine 的执行效率问题 请说出下面的代码存在什么问题。 package main import "fmt" type Func func(string) string func exec(name string, vs ...Func) string { ch : make(chan string) fn : func(i int) { ch &…