SPI协议是摩托罗拉公司开发的协议,它以主从方式工作,这种模式通常有一个主设备和一个或多个从设备,至少需要下列4根线:
(1)MISO– Master Input Slave Output,主设备数据输入,从设备数据输出;
(2)MOSI– Master Output Slave Input,主设备数据输出,从设备数据输入;
(3)SCLK – Serial Clock,时钟信号,由主设备产生;
(4)CS – Chip Select,从设备使能信号,由主设备控制。
SPI协议时序图
tips:不是每个器件的时序图都一样的,需要查看数据手册
使用STM32来做SPI协议可以通过IO模拟SPI和标准库SPI这两种方式实现SPI通信
IO模拟SPI
void SPI_Write(u8 Data)
{
CS_CLR; //拉低CS,SPI通信开始
SPI_WriteData(Data);
CS_SET; //拉高CS,SPI通信结束
}
void SPI_WriteData(u8 Data)
{
unsigned char i=0;
for(i=8;i>0;i--)
{
if(Data&0x80)
{SDA_SET; //判断该位为1,输出高电平}
else
{SDA_CLR;//判断该位为0,输出低电平}
//需要根据器件的数据手册来决定时钟设置的先后顺序
SCL_SET; //数据采样
SCL_CLR; //数据输出
Data<<=1; //数据左移一位,准备下一位数据的发送
}
}
标准库SPI
void SPI_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
SPI_InitTypeDef SPI1_InitStructure;
RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOB ,ENABLE);
RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOA ,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0| GPIO_Pin_10| GPIO_Pin_1;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_Init(GPIOB, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5| GPIO_Pin_7;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 ;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_SetBits(GPIOA,GPIO_Pin_4);
SPI1_InitStructure.SPI_Direction=SPI_Direction_2Lines_FullDuplex;
// 设置SPI 的通信方式
SPI1_InitStructure.SPI_Mode=SPI_Mode_Master;
// 设置SPI 的主从模式
SPI1_InitStructure.SPI_DataSize=SPI_DataSize_8b;
// 选择8 位还是16 位帧格式
SPI1_InitStructure.SPI_CPOL=SPI_CPOL_High ;
// 设置时钟极性
SPI1_InitStructure.SPI_CPHA=SPI_CPHA_2Edge;
// 设置时钟相位
SPI1_InitStructure.SPI_NSS=SPI_NSS_Soft;
//设置NSS 信号由硬件(NSS管脚)还是软件控制
SPI1_InitStructure.SPI_BaudRatePrescaler=SPI_BaudRatePrescaler_2;
//设置SPI 波特率预分频值
SPI1_InitStructure.SPI_FirstBit=SPI_FirstBit_MSB;
//设置数据传输顺序是MSB 位在前还是LSB 位在前
SPI1_InitStructure.SPI_CRCPolynomial=7;
//设置CRC 校验多项式,提高通信可靠性,大于1 即可
SPI_Init( SPI1, &SPI1_InitStructure);
//初始化SPI
SPI_I2S_ITConfig(SPI1, SPI_I2S_IT_TXE, ENABLE);
SPI_I2S_ITConfig(SPI1, SPI_I2S_IT_RXNE, ENABLE);
SPI_Cmd( SPI1, ENABLE );//命令使能SPI
}
u8 SPI_ReadWriteData(u8 Data)
{
while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET);
SPI_I2S_SendData(SPI1, Data);
while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) == RESET);
return SPI_I2S_ReceiveData(SPI1);
}
SPI1_InitStructure结构体按实际情况选择所需要的模式,值得注意的是SPI_CPOL和SPI_CPHL,
SPI_CPOL设置的是SCK时钟线空闲时是什么电平状态
SPI_CPHL设置的是SCK时钟线的第几个跳变沿采样数据
这两个设置需要认真看使用SPI协议模块的数据手册,设置不正确会导致写入代码不正确。
(这是个大坑,坑了我一上午`(*>﹏<*)′)
(实在不想看可以4种情况都是一下,总有一个合适的( ̄︶ ̄)↗ )
(ST7735R显示芯片的时钟极性为高电平,时钟相位为第二个跳变沿)
发送及接收数据
while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET);检测发送缓冲区是否为空
为空时SPI_I2S_GetFlagStatus函数返回SET,结束循环,运行下一步发送数据(SPI_I2S_GetFlagStatus函数可以换成SPI_I2S_GetITStatus,效果相同)
while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) == RESET);检测接收缓冲区是否填满,填满时SPI_I2S_GetFlagStatus函数返回SET,结束循环,运行下一步接收数据