赞
踩
目录
SPI(Serial Peripheral Interface)是由Motorola公司开发的一种通用数据总线。同步,全双工。支持总线挂载多设备(一主多从)。
四根通信线:SCK(Serial Clock)串行时钟线;
MOSI(Master Output Slave Input)主机输出从机输入;
MISO(Master Input Slave Output)主机输入从机输出;
SS(Slave Select)从机选择(若是有多个从机,有几个从机就有几条SS线,可见硬件电路中的连接图)。
所有SPI设备的SCK、MOSI、MISO分别连在一起;
主机另外引出多条SS控制线,分别接到各从机的SS引脚;
输出引脚配置为推挽输出,输入引脚配置为浮空或上拉输入。
工作原理,假如主机想要发送一个字节给从机,从机也想发送一个字节给主机,开始,当SCK处于上升沿移位寄存器最左边数据移出,例如SPI主机中移位寄存器最左边“1”,移出到MOSI引脚上,而SPI从机的移位寄存器的最左边的数据“0”,移出到MISO引脚上,当SCK处于下降沿,MOSI上的数据进入到SPI从机的移位寄存器最右边,MISO上的数据进入到SPI主机的移位寄存器最右边。往复八次经过时钟的上升沿和下降沿,即可完成相互发送一个字节数据。
当多个从机输出连在一起,如果同时开启输出,会造成冲突,解决方法是,当SS未被选中的状态,从机的MISO引脚必须关断输出,即配置为高阻态。
SS从高电平切换到低电平
SS从低电平切换到高电平
CPOL=0:空闲状态时,SCK为低电平
CPHA=0:SCK第一个边沿移入数据,第二个边沿移出数据
CPOL=0:空闲状态时,SCK为低电平
CPHA=1:SCK第一个边沿移出数据,第二个边沿移入数据
CPOL=1:空闲状态时,SCK为高电平
CPHA=0:SCK第一个边沿移入数据,第二个边沿移出数据
CPOL=1:空闲状态时,SCK为高电平
CPHA=1:SCK第一个边沿移出数据,第二个边沿移入数据
- void MySPI_Init(void)
- {
- /*开启时钟*/
- RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //开启GPIOA的时钟
-
- /*GPIO初始化*/
- 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); //将PA4、PA5和PA7引脚初始化为推挽输出
-
- 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); //将PA6引脚初始化为上拉输入
-
- /*设置默认电平*/
- MySPI_W_SS(1); //SS默认高电平
- MySPI_W_SCK(0); //SCK默认低电平
- }
其中,MySPI_W_SS(1); 和 MySPI_W_SCK(0);为封装函数,可以参照下一条。
为了后续代码的编写方便,我们可以将,初始化的引脚进行封装。
此函数需要用户实现内容,当BitValue为0时,需要置SS为低电平,当BitValue为1时,需要置SS为高电平。
- void MySPI_W_SS(uint8_t BitValue)
- {
- GPIO_WriteBit(GPIOA, GPIO_Pin_4, (BitAction)BitValue); //根据BitValue,设置SS引脚的电平
- }
此函数需要用户实现内容,当BitValue为0时,需要置SCK为低电平,当BitValue为1时,需要置SCK为高电平。
- void MySPI_W_SCK(uint8_t BitValue)
- {
- GPIO_WriteBit(GPIOA, GPIO_Pin_5, (BitAction)BitValue); //根据BitValue,设置SCK引脚的电平
- }
此函数需要用户实现内容,当BitValue为0时,需要置MOSI为低电平,当BitValue非0时,需要置MOSI为高电平。
- void MySPI_W_MOSI(uint8_t BitValue)
- {
- GPIO_WriteBit(GPIOA, GPIO_Pin_7, (BitAction)BitValue); //根据BitValue,设置MOSI引脚的电平,BitValue要实现非0即1的特性
- }
此函数需要用户实现内容,当前MISO为低电平时,返回0,当前MISO为高电平时,返回1。
- uint8_t MySPI_R_MISO(void)
- {
- return GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_6); //读取MISO电平并返回
- }
也可以参考:
进行其他方法也能实现同样功能。
根据3.1我们可以看出SPI起始只需要将SS拉低就可以开始时序。
- void MySPI_Start(void)
- {
- MySPI_W_SS(0); //拉低SS,开始时序
- }
同理,根据3.2我们可以看出SPI起始只需要将SS拉高就可以结束时序。
- void MySPI_Stop(void)
- {
- MySPI_W_SS(1); //拉高SS,终止时序
- }
这里需要注意一下,在SPI中对于硬件SPI来说,由于使用了硬件的移位寄存器电路,所以下图中黄色部分几乎是同时发生的,但是对于软件SPI来说程序执行需要一条一条执行,有一个先后顺序,因此我们可以将这里看成一个先后执行的逻辑:
因此我们可以将其传送一位数据的流程如下,先SS下降,再移出数据,在SCK上升沿,在移入数据,在SCK下降沿,再移出数据。
- uint8_t MySPI_SwapByte(uint8_t ByteSend)
- {
- uint8_t ByteReceive = 0x00; //定义接收的数据,并赋初值0x00,此处必须赋初值0x00,后面会用到
-
- MySPI_W_MOSI(ByteSend & 0x80); //使用掩码的方式取出ByteSend的指定一位数据并写入到MOSI线
- MySPI_W_SCK(1); //拉高SCK,上升沿移出数据
- if (MySPI_R_MISO() == 1){ByteReceive |= 0x80;} //读取MISO数据,并存储到Byte变量
- //当MISO为1时,置变量指定位为1,当MISO为0时,不做处理,指定位为默认的初值0
- MySPI_W_SCK(0); //拉低SCK,下降沿移入数据
-
- MySPI_W_MOSI(ByteSend & 0x40); //使用掩码的方式取出ByteSend的指定一位数据并写入到MOSI线
- MySPI_W_SCK(1); //拉高SCK,上升沿移出数据
- if (MySPI_R_MISO() == 1){ByteReceive |= 0x40;} //读取MISO数据,并存储到Byte变量
- //当MISO为1时,置变量指定位为1,当MISO为0时,不做处理,指定位为默认的初值0
- MySPI_W_SCK(0);
-
- MySPI_W_MOSI(ByteSend & 0x20); //使用掩码的方式取出ByteSend的指定一位数据并写入到MOSI线
- MySPI_W_SCK(1); //拉高SCK,上升沿移出数据
- if (MySPI_R_MISO() == 1){ByteReceive |= 0x20;} //读取MISO数据,并存储到Byte变量
- //当MISO为1时,置变量指定位为1,当MISO为0时,不做处理,指定位为默认的初值0
- MySPI_W_SCK(0);
-
- MySPI_W_MOSI(ByteSend & 0x10); //使用掩码的方式取出ByteSend的指定一位数据并写入到MOSI线
- MySPI_W_SCK(1); //拉高SCK,上升沿移出数据
- if (MySPI_R_MISO() == 1){ByteReceive |= 0x10;} //读取MISO数据,并存储到Byte变量
- //当MISO为1时,置变量指定位为1,当MISO为0时,不做处理,指定位为默认的初值0
- MySPI_W_SCK(0);
-
- MySPI_W_MOSI(ByteSend & 0x08); //使用掩码的方式取出ByteSend的指定一位数据并写入到MOSI线
- MySPI_W_SCK(1); //拉高SCK,上升沿移出数据
- if (MySPI_R_MISO() == 1){ByteReceive |= 0x08;} //读取MISO数据,并存储到Byte变量
- //当MISO为1时,置变量指定位为1,当MISO为0时,不做处理,指定位为默认的初值0
- MySPI_W_SCK(0);
-
- //......一直移出到第八位
-
- return ByteReceive; //返回接收到的一个字节数据
- }
以上代码太过冗余,我们可以使用for循环来进行实现:
- 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时,不做处理,指定位为默认的初值0
- MySPI_W_SCK(0); //拉低SCK,下降沿移入数据
- }
-
- return ByteReceive; //返回接收到的一个字节数据
- }
我们还可以根据“2.硬件电路”中的移位示意图中的数据进行操作,编写代码:
- uint8_t MySPI_SwapByte(uint8_t ByteSend)
- {
- uint8_t i;
-
- for (i = 0; i < 8; i ++) //循环8次,依次交换每一位数据
- {
- MySPI_W_MOSI(ByteSend & 0x80);
- ByteSend <<=1;
- MySPI_W_SCK(1); //拉高SCK,上升沿移出数据
- if (MySPI_R_MISO() == 1){ByteReceive |= 0x01;}
- MySPI_W_SCK(0); //拉低SCK,下降沿移入数据
- }
-
- return ByteReceive; //返回接收到的一个字节数据
- }
这种方式相较于上一种代码效率更高,但是原始数据ByteSend会发生改变,因为这种方法是用移位数据本身进行操作的,效率跟高,但是原始数据ByteSend会在移位过程中发生改变,对于上一种方式编写的代码是还有掩码一次提取数据每一位,不会改变参数本身,两种方法皆可使用。
我们也可以将其传送一位数据的流程描述如下,先SS下降之后,在SCK上升沿,再移出数据,在SCK下降沿,在移入数据。
- uint8_t MySPI_SwapByte(uint8_t ByteSend)
- {
- uint8_t i, ByteReceive = 0x00; //定义接收的数据,并赋初值0x00,此处必须赋初值0x00,后面会用到
-
- for (i = 0; i < 8; i ++) //循环8次,依次交换每一位数据
- {
- MySPI_W_SCK(1);
- MySPI_W_MOSI(ByteSend & (0x80 >> i)); //使用掩码的方式取出ByteSend的指定一位数据并写入到MOSI线
- MySPI_W_SCK(0);
- if (MySPI_R_MISO() == 1){ByteReceive |= (0x80 >> i);} //读取MISO数据,并存储到Byte变量
- //当MISO为1时,置变量指定位为1,当MISO为0时,不做处理,指定位为默认的初值0
-
- }
-
- return ByteReceive; //返回接收到的一个字节数据
- }
可以对比模式0,可以发现只是SCK极性相反,只需要将模式0的代码中出现SCK的地方“1”改为“0”,“0”改为“1”即可,将极性翻转一下。(注意初始化中的极性也要进行修改)
同理,可以对比模式1,可以发现只是SCK极性相反,只需要将模式0的代码中出现SCK的地方“1”改为“0”,“0”改为“1”即可,将极性翻转一下。(注意初始化中的极性也要进行修改)
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。