1.SPI通信简介
SCK:串行时钟线
MOSI:主机输出、从机输入(主机向从机发送数据)
MISO:主机输入、从机输出(主机从从机接收数据)
SS:从机选择(从机寻址)
2.SPI硬件电路
SS线都是低电平有效的,同一时间只有一条SS线能被置低电平(即同一时间只能选中一个从机)
当从机被选中时(即SS线被置低电平时),MISO才会是推挽输出,否则会被设置成高阻态(被断开)
3.SPI时序基本单元
1.起始和发送
就是起始就是选择从机,结束从机选中状态
2.收发数据时序(模式功能都一样)
1.模式0
MISO初始时为高阻态,结束时要重新配置为高阻态
数据提前移出和移入(MOSI在第0个边沿移出,在第1个边沿移入)
原因:数据要先移出才能移入
2.模式1
MISO初始时为高阻态,结束时要重新配置为高阻态
3.模式2
MISO初始时为高阻态,结束时要重新配置为高阻态
4.模式3
MISO初始时为高阻态,结束时要重新配置为高阻态
3.收发SPI时序格式(以芯片W25Q64作为参考)
基本格式:起始+指令码(读写等功能)+数据将被写入的地址+数据
4.基本配置格式(手动实现时序)
//从机选择(写SS的引脚) 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); //将SCK、MOSI、SS配置成推挽输出 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); //将主机输入、从机输出的MISO配置成上拉输入 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); } //交换一个字节(模式0收发数据) //注意:从机的操作是从机自动进行的,软件代码只需管主机 //方法一:使用掩码,取出每一位数据进行操作 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()){ByteReceive |= (0x80 >> i);} MySPI_W_SCK(0); } return ByteReceive; } //方法二:将数据本身进行移位,相当于将主机数据一位一位移出再一位一位移入从机数据 //uint8_t MySPI_SwapByte(uint8_t ByteSend) //{ // uint8_t i; // // for (i = 0; i < 8; i ++) // { // MySPI_W_MOSI(!!(ByteSend & 0x80); // ByteSend<<=1; // MySPI_W_SCK(1); // if (MySPI_R_MISO()){ByteSend |= 0x01;} // MySPI_W_SCK(0); // } // // return ByteSend; //}4.硬件实现SPI通信(STM32内部的SPI外设)
1.SPI外设简介
注意:SPI1挂载在APB2,PCLK是72MHz;而SPI2挂载在APB1,PCLK是36MHz
2.STM32中SPI外设的内部结构图
LSBFIRST:帧格式,可以选择数据是低位先行还是高位先行(给0,先发送MSB即高位先行;给1,先发送LSB即低位先行)
TXE:发送寄存器空
RXNE:接受寄存器非空
NSS:从机选择(低电平有效) 当SSOE置1时,NSS配置成输出(即成为主机);当SSOE清0后,NSS变为输入(即成为从机)
3.SPI基本结构图
4.硬件SPI的操作流程
1.主模式全双工连续传输(效率高)
示例为模式三
发送数据解释:开始时TXE=1表示发送寄存器空,TXE为0时,数据进入发送寄存器;TXE再次为1时,数据由发送寄存器进入移位寄存器,同时下一个数据紧接着进入发送寄存器
接收数据解释:开始时RXNE=0表示接受寄存器空,RXNE为1时,数据由移位寄存器进入接受寄存器,完成后清除RXNE(即直接置0)
这种数据传输模式是交叉的,并不是发送一个数据后马上接收此数据
2.非连续传输
基本逻辑:发送一个数据,然后等待此数据被接收,最后发送下一个字节。
注意:相较主模式全双工连续传输传输速度明显更慢
3.硬件SPI的实战代码
1.部分函数功能
//写DR数据寄存器 void SPI_I2S_SendData(SPI_TypeDef* SPIx, uint16_t Data); //读DR数据寄存器 uint16_t SPI_I2S_ReceiveData(SPI_TypeDef* SPIx);2.配置思路
1.RCC开启时钟(把SPI外设和对应的GPIO口的时钟打开)
2.配置GPIO(SCK、MOSI配置成复用推挽输出模式。MISO配置成上拉输入模式,SS配置成通用推挽输出)
3.配置SPI外设
4.开启SPI(使能)
3.基本配置格式(使用库函数实现时序)
//从机选择 void MySPI_W_SS(uint8_t BitValue) { GPIO_WriteBit(GPIOA, GPIO_Pin_4, (BitAction)BitValue); } //初始化 void MySPI_Init(void) { RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //SPI1是APB2上的外设 RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE); //开启SS从机选择时钟 GPIO_InitTypeDef GPIO_InitStructure; 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); //SCK和MOSI复用推挽输出 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); //MISO上拉输入模式 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); //SPI初始化 SPI_InitTypeDef SPI_InitStructure; SPI_InitStructure.SPI_Mode = SPI_Mode_Master;//SPI模式,决定SPI是主机还是从机 SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;//数据传输模式(此处为双线全双工) SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;//8位数据帧 SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;//高位先行 SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_128;//128分频(SPI1外设72/128) //配置模式(模式0、1、2、3) SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low; SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge; SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;//选择NSS模式(硬件NSS或软件NSS,此处为软件NSS) SPI_InitStructure.SPI_CRCPolynomial = 7; SPI_Init(SPI1, &SPI_InitStructure); SPI_Cmd(SPI1, ENABLE); MySPI_W_SS(1); } //起始时序 void MySPI_Start(void) { MySPI_W_SS(0); } //终止时序 void MySPI_Stop(void) { MySPI_W_SS(1); } //交换一个字节 uint8_t MySPI_SwapByte(uint8_t ByteSend) { while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) != SET);//等待TXE SPI_I2S_SendData(SPI1, ByteSend);//将数据写入DR while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) != SET);//等待RNXE return SPI_I2S_ReceiveData(SPI1);//将DR中的数据读出 }