目录
SPI简介
硬件电路
SPI模式
软件模拟初始化
时序基本单元
起始条件
终止条件
发送与接收
SPI基本单元代码
MySPI.h
MySPI.c
SPI简介
SPI(Serial Peripheral Interface),即串行外围设备接口,是由Motorola公司开发的一种高速全双工通用数据总线。它被广泛地使用在要求通讯速率较高的场合。
特征
- 同步,全双工,串行
- 通讯速率较高
- 支持总线挂载多设备(一主多从)
- 四条通信线
- 短距离
通信线
SCK(Serial Clock):串行时钟线,主机生成时钟,主机和从机根据电平边沿(上升沿或下降沿)发送数据位、接收数据位
MOSI(Master Output Slave Input):主机输出,从机输入
MISO(Master Input Slave Output):主机输入,从机输出
SS(Slave Select):指定主机与哪个从机通信,同一时间主机只能选择一个从机,故只能设置一个SS低为电平。未被选中(NSS引脚为高电平)的从设备会忽略总线上的数据传输
传输速度
SPI总线上的主设备必须在通信开始时候配置并生成相应的时钟信号。从理论上讲,只要实际可行,时钟速率就可以是你想要的任何速率,当然这个速率受限于每个系统能提供多大的系统时钟频率,以及最大的速率。
硬件电路
所有SPI设备的SCK、MOSI、MISO分别连在一起
主机另外引出多条SS控制线,分别接到各从机的SS引脚
输出引脚配置为推挽输出,输入引脚配置为浮空或上拉输入
当从机的SS引l脚为高电平,也就是从机未被选中时,它的MISO引脚,必须切换为高阻态,在SS为低电平时,MISO才允许变为推挽输出。在SS未被选中的状态,从机的MISO引脚必须关断输出,即配置输出为高阻态。
SPI模式
SPI 根据时钟极性和时钟相位的不同可以有4种工作模式,通常在编写 SPI 设备驱动程序时需要特别注意从设备的时钟相位与时钟极性,如果与主设备不一致时,一般都不能进行正常的通信。
-
模式0(CKPL=0,CKPH=0):
- 时钟极性(Clock Polarity)为0,表示时钟空闲状态为低电平。
- 时钟相位(Clock Phase)为0,表示数据在时钟信号的奇数边沿(时钟上升沿)进行采样和稳定。
-
模式1(CKPL=0,CKPH=1):
- 时钟极性为0,时钟空闲状态为低电平。
- 时钟相位为1,数据在时钟信号的偶数边沿(时钟下降沿)进行采样和稳定。
-
模式2(CKPL=1,CKPH=0):
- 时钟极性为1,时钟空闲状态为高电平。
- 时钟相位为0,数据在时钟信号的奇数边沿(时钟下降沿)进行采样和稳定。
-
模式3(CKPL=1,CKPH=1):
- 时钟极性为1,时钟空闲状态为高电平。
- 时钟相位为1,数据在时钟信号的偶数边沿(时钟上升沿)进行采样和稳定。
软件模拟初始化
根据SPI硬件规定:
MISO(Master Input Slave Output),SCK(Serial Clock),SS(Slave Select)均配置为推挽输出
MISO(Master Input Slave Output)配置为上拉输入
GPIO的其它参数的理解可以阅读下方博客,这里不再赘述。
【STM32】GPIO和AFIO标准库使用框架_gpio afio-CSDN博客
本篇软件模拟使用SPI的模式0
void MySPI_W_SS(uint8_t BitValue)
{
GPIO_WriteBit(GPIOA, GPIO_Pin_4, (BitAction)BitValue);
}
void MySPI_W_SCK(uint8_t BitValue)
{
GPIO_WriteBit(GPIOA, GPIO_Pin_5, (BitAction)BitValue);
}
void MySPI_W_MOSI(uint8_t BitValue)
{
GPIO_WriteBit(GPIOA, GPIO_Pin_7, (BitAction)BitValue);
}
uint8_t MySPI_R_MISO(void)
{
return GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_6);
}
void MySPI_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
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);//推挽输出默认输出低电平
MySPI_W_SS(1);//不选择从机,空闲状态
MySPI_W_SCK(0);//模式0空闲时的SCK
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
}
时序基本单元
起始条件
SS(Slave Select)从高电平切换到低电平
void MySPI_Start(void)
{
//初始化时已经配置空闲状态时为高电平,现拉低选择从机
MySPI_W_SS(0);
}
终止条件
SS从低电平切换到高电平
void MySPI_Stop(void)
{
//通讯过程中,SS为低电平,拉高代表通讯结束
MySPI_W_SS(1);
}
发送与接收
SPI只有主模式和从模式之分,没有读和写的说法,外设的写操作和读操作是同步完成的。如果只进行写操作,主机只需忽略接收到的字节;反之,若主机要读取从机的一个字节,就必须发送一个空字节来引发从机的传输。也就是说,你发一个数据必然会收到一个数据;你要收一个数据必须也要先发一个数据。
硬件SPI,主机SCK电平变化与数据读写几乎同时进行,但软件SPI,程序是一条一条执行的,软件模拟就先变化时钟,再读写数据位,不是同时进行的
交换一个字节(模式0)
CPOL=0:空闲状态时,SCK为低电平
CPHA=0:SCK第一个边沿移入数据,第二个边沿移出数据
在SS下降沿的时候,主机和从机就需要移出数据,那样SCK的第一个边沿才能移入数据
uint8_t MySPI_SwapByte(uint8_t ByteSend)
{
uint8_t i, ByteReceive = 0x00;
for (i = 0; i < 8; i ++)
{
MySPI_W_MOSI(ByteSend & (0x80 >> i));//第一次在SS下降沿时发送数据为位,之后SCK下降沿时发送数据位
MySPI_W_SCK(1);
if (MySPI_R_MISO() == 1){ByteReceive |= (0x80 >> i);}//SCK上升沿时接收数据位
MySPI_W_SCK(0);
}
return ByteReceive;
}
SPI基本单元代码
MySPI.h
#ifndef __MYSPI_H
#define __MYSPI_H
void MySPI_Init(void);
void MySPI_Start(void);
void MySPI_Stop(void);
uint8_t MySPI_SwapByte(uint8_t ByteSend);
#endif
MySPI.c
#include "stm32f10x.h" // Device header
void MySPI_W_SS(uint8_t BitValue)
{
GPIO_WriteBit(GPIOA, GPIO_Pin_4, (BitAction)BitValue);
}
void MySPI_W_SCK(uint8_t BitValue)
{
GPIO_WriteBit(GPIOA, GPIO_Pin_5, (BitAction)BitValue);
}
void MySPI_W_MOSI(uint8_t BitValue)
{
GPIO_WriteBit(GPIOA, GPIO_Pin_7, (BitAction)BitValue);
}
uint8_t MySPI_R_MISO(void)
{
return GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_6);
}
void MySPI_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
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);//推挽输出默认输出低电平
MySPI_W_SS(1);
MySPI_W_SCK(0);
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
}
void MySPI_Start(void)
{
MySPI_W_SS(0);
}
void MySPI_Stop(void)
{
MySPI_W_SS(1);
}
uint8_t MySPI_SwapByte(uint8_t ByteSend)
{
uint8_t i, ByteReceive = 0x00;
for (i = 0; i < 8; i ++)
{
MySPI_W_MOSI(ByteSend & (0x80 >> i));
MySPI_W_SCK(1);
if (MySPI_R_MISO() == 1){ByteReceive |= (0x80 >> i);}
MySPI_W_SCK(0);
}
return ByteReceive;
}