STM32- SPI
1. SPI概述
1.1 SPI概念
SPI(Serial Peripheral Interface,串行外围设备接口),是一种高速的、全双工、同步的通信总线,有四根通信线:
SCK(Serial Clock):时钟信号,由主设备产生
MOSI(Master Output/ Slave Input):主机数据输出,从机输入
MISO(Master Input Slave Output):主机数据输入,从机输出
SS(Slave Select):从机片选信号,由主机产生
特点:① 总线上允许连接多个能作主机的设备,但在任一时刻只允许有一个设备作为主机。② 总线的时钟线 SCK 由主机控制。
1.2 SPI工作原理
- 字节交换:主机的串行移位寄存器通过
MOSI
信号线将字节传送给从机,从机也将自己的串行移位寄存器中的内容通过MISO
信号线返回给主机,(高位先行)。 - 数据采集(移入数据):在SCK下降沿或上升沿(看具体配置)时,主机移出的高位通过MOSI信号线传到从机的地位,从机的低位通过MISO信号线传到主机地位。每个时钟周期传输一位。
- 波特率发送器:产出时钟信号,由主机产生。
1.3 SPI时序
- 起始条件:SS从高电平切换到低电平
- 终止条件:SS从低电平切换到高电平
- 从机在被选中的状态中,SS 要始终保持为低电平
1.4 工作模式
- CPOL/CKP(Clock Polarity,时钟极性):时钟信号在空闲状态下的电平。当CPOL=1,时钟信号为高电平;当CPOL = 0,时钟空闲状态为低电平 。
- CPHA/ CKE(Clock Phase,时钟相位):数据的采样时刻。当CPHA = 0 时,在时钟信号的第一个边沿进行数据采样;当CPHA =1 时,在时钟信号的第二个边沿进行数据采样。
工作模式 | CPOL | SPHA | SCK空闲电平 | 采样边沿 | 采样时刻 |
---|---|---|---|---|---|
0(00) | 0 | 0 | 低 | 上升沿 | 第一个边沿 |
1(01) | 0 | 1 | 低 | 下降沿 | 第二个边沿 |
2(10) | 1 | 0 | 高 | 下降沿 | 第一个边沿 |
3(11) | 1 | 1 | 高 | 上升沿 | 第二个边沿 |
工作模式0:空闲状态的时钟信号为低电平,且在第一个边沿时刻进行数据移入,所以只能是上升沿移入数据(从空闲低电平转为高电平),在第二个边沿移出数据。
工作模式1:空闲状态的时钟信号为低电平,且在第二个边沿时刻进行数据移入,所以只能是下降沿移入数据(第二个边沿只能是下降了),第一个边沿移出数据。
工作模式2:空闲状态的时钟信号为高电平,且在第一个边沿时刻进行数据移入,所以只能是下降沿移入数据(从空闲高电平转为低电平),第二个边沿移出数据。
工作模式3:空闲状态的时钟信号为高电平,且在第二个边沿时刻进行数据移入,所以只能是上升沿移入数据(第二个边沿只能是上升了),第一个边沿移出数据。
2 代码配置
软件SPI代码设置
// 看引脚定义图
void MySPI_W_SS(uint8_t Value)
{
GPIO_WriteBit(GPIOA,GPIO_Pin_4,(BitAction)Value);
}
void MySPI_W_SCK(uint8_t Value)
{
GPIO_WriteBit(GPIOA,GPIO_Pin_5,(BitAction)Value);
}
void MySPI_W_MOSI(uint8_t Value)
{
GPIO_WriteBit(GPIOA,GPIO_Pin_7,(BitAction)Value);
}
uint8_t MySPI_R_MISO_Byte(void)
{
return GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_6);
}
void MySPI_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
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);
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);
MySPI_W_SS(1);
MySPI_W_SCK(0);
}
void MySPI_Start(void)
{
MySPI_W_SS(0);
}
void MySPI_Stop(void)
{
MySPI_W_SS(1);
}
uint8_t MySPI_SwapData(uint8_t ReturnData)//模式0,有点类似与I2C的接受字节取高位
{
uint8_t i,ReceiveData = 0x00;
for(i=0;i<8;i++)
{
MySPI_W_MOSI(ReturnData & (0x80 >> i));//取特定位
MySPI_W_SCK(1);
if(MySPI_R_MISO_Byte() == 1){ReceiveData |= (0x80 >> i);}//高位先行
MySPI_W_SCK(0);
}
return ReceiveData;
}
硬件SPI初始化
void MySPI_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
SPI_InitTypeDef SPI_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1,ENABLE);
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP;//主机输出引脚位推挽输出
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_4;
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;
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;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
SPI_InitStructure.SPI_BaudRatePrescaler=SPI_BaudRatePrescaler_128;
SPI_InitStructure.SPI_CPHA=SPI_CPHA_1Edge;//第一个边沿开始采样,CPHA=0
SPI_InitStructure.SPI_CPOL=SPI_CPOL_Low;//设置SCK空闲低电平
SPI_InitStructure.SPI_CRCPolynomial=7;
SPI_InitStructure.SPI_DataSize=SPI_DataSize_8b;//8为数据帧
SPI_InitStructure.SPI_Direction=SPI_Direction_2Lines_FullDuplex;//双线全双工
SPI_InitStructure.SPI_FirstBit=SPI_FirstBit_MSB;
SPI_InitStructure.SPI_Mode=SPI_Mode_Master;//设置设备为主机
SPI_InitStructure.SPI_NSS=SPI_NSS_Soft;
SPI_Init(SPI1,&SPI_InitStructure);
SPI_Cmd(SPI1,ENABLE);
MySPI_W_SS(1);//默认设置SCK为高电平,不选择为从机
}
uint8_t MySPI_SwapData(uint8_t ReturnData)//模式0
{
while(SPI_I2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_TXE) != SET);//TXE会在写入数据后自动清除
SPI_I2S_SendData(SPI1,ReturnData);//数据转移到移位寄存器
while(SPI_I2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_RXNE) != SET);//RXNE会在读取数据后自动清除
return SPI_I2S_ReceiveData(SPI1);
}