STM32F103(二十六)SPI通信(+两块STM32之间的SPI通信)
Posted 独独白
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了STM32F103(二十六)SPI通信(+两块STM32之间的SPI通信)相关的知识,希望对你有一定的参考价值。
学习板:STM32F103ZET6
往期博客:
STM32F103五分钟入门系列(一)跑马灯(库函数+寄存器)+加编程模板+GPIO总结
STM32F103五分钟入门系列(二)GPIO的七大寄存器+GPIOx_LCKR作用和配置
STM32F103五分钟入门系列(三)GPIO的常用库函数使用方法总结+一个网络上的误区
STM32F103五分钟入门系列(四)蜂鸣器实验(库函数+寄存器)
STM32F103五分钟入门系列(五)按键实验(库函数+寄存器)
STM32F103五分钟入门系列(六)时钟框图+相关寄存器总结+系统时钟来源代码(寄存器)
STM32F103五分钟入门系列(七)SystemInit()函数、SetSysClock()函数
STM32F103五分钟入门系列(八)SysTick滴答定时器+SysTick中断实现跑马灯
STM32F103五分钟入门系列(九)延时函数(自己重写的底层)(上限:477218ms和477218588us)
STM32F103五分钟入门系列(十)NVIC中断优先级管理
STM32F103五分钟入门系列(十五)输出比较(PWM输出)+各类测试
STM32F103五分钟入门系列(十六)输入捕获(精雕细琢-.-)
STM32F103(十九)ADC相关的几个实验—内部温度传感器、内部参照电压、光敏传感器
STM32F103(二十三)通用同步异步收发器(USART)
STM32F103(二十五)【完美解决】USART发送、接收float、u16、u32数据
STM32F103(二十六)SPI通信(+两块STM32之间的SPI通信)
SPI通信
- 前言
- 一、原理
- 二、SPI相关库函数(各种细节总结都在这里)
- 1、SPI初始化函数SPI_Init()(重要细节)
- 2、SPI使能函数SPI_Cmd()
- 3、SPI中断配置函数SPI_I2S_ITConfig()
- 4、SPI的DMA使能函数SPI_I2S_DMACmd()
- 5、SPI发送数据函数SPI_I2S_SendData()
- 6、SPI发送数据函数SPI_I2S_ReceiveData()
- 7、软件片选函数SPI_NSSInternalSoftwareConfig()+加个注意
- 8、SPI传输数据大小设置函数SPI_DataSizeConfig()
- 9、发送CRC函数SPI_TransmitCRC()(重要细节)
- 10、启动CRC计算函数SPI_CalculateCRC()
- 11、获取CRC值函数SPI_GetCRC()
- 12、获取CRC多项式函数SPI_GetCRCPolynomial()
- 13、双线模式下输出设置函数SPI_BiDirectionalLineConfig()
- 13、SPI状态获取函数SPI_I2S_GetFlagStatus()
- 14、SPI清除状态标志函数SPI_I2S_ClearFlag()
- 15、另一种状态获取函数SPI_I2S_GetITStatus()
- 16、另一种状态清除函数SPI_I2S_ClearITPendingBit()
- 三、另外的注意细节
- 四、SPI编程顺序
- 五、例子(STM32F1与STM32F4之间的SPI通信)
- 六、NSS硬件片选测试
前言
本博总结一下SPI的相关内容,博客最后会写一下单片机之间的SPI通信方法。其实单片机之间的SPI通信很少见的,毕竟SPI太矫情,板间通信非常麻烦,远不如串口、485这类通信方便。SPI常用于单片机与各类高级外设的通信,如外部flash,后面博客也会一一去总结
一、原理
1、简述
SPI (Serial Peripheral interface),为串行外围设备接口,是一种高速的、全双工、同步通信的总线。
SPI通信由四根线组成,分别为:
① MISO :主机数据输入从机数据输出(Master Input Master Output)
②MOSI:主机数据输出从机数据输入(Master Output Master Input)
③SCLK :时钟信号,由主设备产生,并连接到从设备
④ CS:片选
2、原理
初始化SPI设备时,可以设置传输从高位开始还是低位开始。如从高位开始:(从低位开始类似)
在每一个时钟周期内,主设备的高位(1位数据)通过MOSI线传到从设备数据的低位,同一时间,从设备的高位(1位数据)通过MISO线传到主设备的低位。重复上面的操作,8个时钟周期后,完成一个8位数据的交换(或16个时钟周期完成16位数据交换)。
例:主设备:10101010,从设备:01010101
一个时钟周期后:主设备:01010100(从设备位7),从设备:10101011(主设备位7)
第二个时钟周期后:主设备:10101001(从设备位7、位6),从设备:01010110(主设备位7、位6)
…
表格如下:
初始: | 主设备:10101010 | 从设备:01010101 |
---|---|---|
第一个时钟周期后: | 主设备:01010100 | 从设备:10101011 |
第二个时钟周期后: | 主设备:10101001 | 从设备:01010110 |
第三个时钟周期后: | 主设备:01010010 | 从设备:10101101 |
第四个时钟周期后: | 主设备:10100101 | 从设备:01011010 |
第五个时钟周期后: | 主设备:01001010 | 从设备:10110101 |
第六个时钟周期后: | 主设备:10010101 | 从设备:01101010 |
第七个时钟周期后: | 主设备:00101010 | 从设备:11010101 |
第八个时钟周期后: | 主设备:01010101 | 从设备:10101010 |
通信往往由主设备发起,主设备发送1位数据,相应的从设备要同时返回1位数据。主从设备数据传输在一个同步时钟下完成,同步时钟由主设备SCK引脚提供。主设备可以随时启动发送数据,所以为了主设备发送数据的时候,从设备也能同时发送数据,从设备要在主设备启动传输之前一定要将数据准备好
二、SPI相关库函数(各种细节总结都在这里)
1、SPI初始化函数SPI_Init()(重要细节)
参数1:
SPI1、SPI2、SPI3
参数2:
结构体,成员变量为:
typedef struct
uint16_t SPI_Direction; //设置通信方向:2线全双工、2线只读、单线读、单线发
uint16_t SPI_Mode; //设置模式为:主设备、从设备
uint16_t SPI_DataSize; //设置发送字节大小:8位、16位
uint16_t SPI_CPOL; //设置极性:空闲时总线是高电平、低电平
uint16_t SPI_CPHA; //设置相位:第一个时钟跳变沿、第二个时钟跳变沿数据交换
uint16_t SPI_NSS; //片选信号软件触发还是硬件触发
uint16_t SPI_BaudRatePrescaler; //时钟信号频率的分频
uint16_t SPI_FirstBit; //设置从高位开始传输还是低位开始传输
uint16_t SPI_CRCPolynomial; //CRC冗余校验的多项式长度
SPI_InitTypeDef;
上面代码的注释部分已经很详细了,再着重看一下下面的几个东西:
①CPOL和CPHA
当时钟极性(CPOL)=1时,空闲状态时总线为高电平,当CPOL=0时,空闲状态时总线为低电平
当时钟相位(CPHA)=1时,在第二个边沿(CPOL=1时上升沿,CPOL=0时下降沿)采集数据;当CPHA=0时,在第一个边沿(CPOL=1时下降沿,CPOL=0时上升沿)采集数据。
②NSS片选
CR1寄存器的位9:8设置从选择设备
首先位9用来设置软件选择从设备管理还是硬件选择从设备。
当位9被置1,此时设置为软件选择从设备,这个时候位8有了意义,用位8来选择从设备(即内部NSS信号电平由位8来驱动)
当位9置0时,位8的软件选择失效,采用硬件来选择从设备。当设置为主设备唯一(CR2寄存器SSOE位),此时主设备的NSS引脚被拉低,与主NSS引脚相连的SPI设备自动变为从设备
通俗的讲:当在SPI_Init()
函数中设置NSS为软件驱动,则SPI通信的NSS引脚就不用管了,板子连线的时候,NSS引脚可以悬空,适用于只有一个从设备的情况,不存在从设备选择。当在SPI_Init()
函数中设置NSS为硬件驱动时,注意主设备的CR2寄存器SSOE=1一定要设置(允许NSS输出),此时主设备NSS引脚会被硬件置0输出,与主设备的连接的从设备NSS引脚检测到低电平时,主从设备配对。需要注意的是当有多个从设备时,多个从设备的NSS引脚都会检测到主设备NSS引脚输出的低电平,所以从设备端代码一定要选择,即设置某个想要用的从设备为从模式,其它设备取消从模式;或者其它从设备设置为软件从管理,总之要破坏其它从设备与主设备的匹配条件。或者多个从设备的NSS引脚不要与主设备的NSS引脚连接,从设备的NSS引脚使用另外的GPIO去控制。
上面也说到过,当主设备设置为硬件NSS管理时,NSS一定要设置为输出模式(不是GPIO初始化时的推挽输出模式),CR2寄存器SSOE置1,允许NSS输出。否则主设备会进入从模式,导致通信失败。
③冗余校验
冗余校验为了检测通信是否出错,具体操作:(可参考《通信原理》的线性码章节)
通过设置校验多项式的长度,确定一个二进制的0、1序列;需要传输的数据除以(模2运算)这个校验多项式,一般情况下会得到一个余数(校验码),将这个余数加在传输数据的后面(传输的数据末尾补零后数学加运算),这样传输的数据就变为:实际数据+校验码,接收完成后,可以与那个校验多项式再一次模2运算,没有传输出错的话,余数一定是零。
由于这个校验长度确定,但是不知道哪位是1(应该能查到相关资料),我就随便举个例子(模2运算我习惯用长除法)
2、SPI使能函数SPI_Cmd()
参数1:
SPI1、SPI2、SPI3
参数2:
ENABLE、DISABLE
3、SPI中断配置函数SPI_I2S_ITConfig()
参数1:
SPI1、SPI2、SPI3
参数2:
SPI_I2S_IT_TXE //发送缓冲器空闲中断
SPI_I2S_IT_RXNE //接收缓冲器非空中断
SPI_I2S_IT_ERR //错误中断,包括:主模式失效事件(MODF)、溢出错误(OVR)、CRC校验错误(CRCERR)
参数3:
ENABLE、DISABLE
4、SPI的DMA使能函数SPI_I2S_DMACmd()
参数1:
SPI1、SPI2、SPI3
参数2:
SPI_I2S_DMAReq_Tx //发送数据的DMA请求
SPI_I2S_DMAReq_Rx //接收数据的DMA请求
参数3:
ENABLE、DISABLE
5、SPI发送数据函数SPI_I2S_SendData()
参数1:
SPI1、SPI2、SPI3
参数2:
数据要与SPI_Init()
中定义的数据位数匹配,即定义了8位数据,则这个参数必须为8位数据。
6、SPI发送数据函数SPI_I2S_ReceiveData()
参数:
SPI1、SPI2、SPI3
返回:
返回一个u16数
7、软件片选函数SPI_NSSInternalSoftwareConfig()+加个注意
参数1:
SPI1、SPI2、SPI3
参数2:
SPI_NSSInternalSoft_Set //软件片选
SPI_NSSInternalSoft_Reset //软件不片选
CR1寄存器:
可以看到位8的描述,只有位9置1时位8置1才有效。而本函数SPI_NSSInternalSoftwareConfig()
只是对位8置1 。
真正对位9置1的是在SPI_Init()
函数中对NSS的设置。
当在SPI_Init()
函数中设置NSS为软件从管理时,该寄存器位9置1,此时不管本函数怎么设置都无所谓了,因为NSS引脚可以悬空,对从设备的选择不再有NSS引脚控制,本函数只是控制主设备NSS引脚输出高电平还是低电平,意义不大。
当在SPI_Init()
函数中设置NSS为硬件从管理时,该寄存器位9置0,此时本函数参数无论怎么设置,都无效,因为硬件从管理时,NSS引脚输出会自动被拉低。
8、SPI传输数据大小设置函数SPI_DataSizeConfig()
参数1:
SPI1、SPI2、SPI3
参数2:
SPI_DataSize_16b
SPI_DataSize_8b
9、发送CRC函数SPI_TransmitCRC()(重要细节)
参数:
SPI1、SPI2、SPI3
设置后,会复位CRC寄存器(RXCRC和TXCRCR),注意都是16位有效的寄存器,所以CRC多项式位数不能大于16,准确的来讲,传输16位数据时CRC多项式位数不能大于16;传输8位数据时,CRC多项式位数不能大于8 。
当设置CRC后,发送一次数据(8位或16位)后,再将TXCRC寄存器中的CRC数值发送出去;接收一次数据(8位或16位)后,将CRC数值读到RXCRC寄存器
10、启动CRC计算函数SPI_CalculateCRC()
参数1:
SPI1、SPI2、SPI3
参数2:
ENABLE、DISABLE
11、获取CRC值函数SPI_GetCRC()
参数1:
SPI1、SPI2、SPI3
参数2:
SPI_CRC_Tx
SPI_CRC_Rx
表示获取的是发送CRC寄存器中的CRC数值还是接收CRC寄存器中的CRC数值
12、获取CRC多项式函数SPI_GetCRCPolynomial()
返回CRC校验时的多项式,多项式的项数在SPI_Init()
中定义。
13、双线模式下输出设置函数SPI_BiDirectionalLineConfig()
参数1:
SPI1、SPI2、SPI3
参数2:
SPI_Direction_Rx
SPI_Direction_Tx
设置为双线只收模式、双线只发模式
13、SPI状态获取函数SPI_I2S_GetFlagStatus()
参数1:
SPI1、SPI2、SPI3
参数2:
SPI_I2S_FLAG_RXNE // 接收缓存器空标志(不空时,可读取数据,读操作清零)
SPI_I2S_FLAG_TXE //发送缓存器空标志(为空时可以给发送缓存器写入新的发送数据,写入数据清零)
I2S_FLAG_CHSIDE //声道标志位
I2S_FLAG_UDR //下溢标志 ,发送模式下,如果数据传输的第一个时钟边沿到达时,
//新的数据仍然没有写入SPI_DR寄存器,该标志位会被置’1’
SPI_I2S_FLAG_OVR //上溢标志位,如果还没有读出前一个接收到的数据时,
//又接收到新的数据,即产生上溢,该标志位置’1’
SPI_FLAG_CRCERR //CRC校验错误标志位
SPI_FLAG_MODF //主模式失效错误标志
SPI_I2S_FLAG_BSY //忙标志(检测传输是否结束)
14、SPI清除状态标志函数SPI_I2S_ClearFlag()
参数1:
SPI1、SPI2、SPI3
参数2:
SPI_I2S_FLAG_RXNE // 接收缓存器空标志(不空时,可读取数据,读操作清零)
SPI_I2S_FLAG_TXE //发送缓存器空标志(为空时可以给发送缓存器写入新的发送数据,写入数据清零)
I2S_FLAG_CHSIDE //声道标志位
I2S_FLAG_UDR //下溢标志 ,发送模式下,如果数据传输的第一个时钟边沿到达时,
//新的数据仍然没有写入SPI_DR寄存器,该标志位会被置’1’
SPI_I2S_FLAG_OVR //上溢标志位,如果还没有读出前一个接收到的数据时,
//又接收到新的数据,即产生上溢,该标志位置’1’
SPI_FLAG_CRCERR //CRC校验错误标志位
SPI_FLAG_MODF //主模式失效错误标志
SPI_I2S_FLAG_BSY //忙标志(检测传输是否结束)
15、另一种状态获取函数SPI_I2S_GetITStatus()
参数1:
SPI1、SPI2、SPI3
参数2与SPI_I2S_GetFlagStatus()
类似,不过没有了忙标志获取和声道标志获取
16、另一种状态清除函数SPI_I2S_ClearITPendingBit()
参数1:
SPI1、SPI2、SPI3
参数2与SPI_I2S_GetFlagStatus()
类似,不过没有了忙标志获取和声道标志获取
三、另外的注意细节
1、发送与接收
主设备发送,从设备接收如下图:(主设备接收、从设备发送类似)
当主设备发送数据时,先将数据并行的写入发送缓冲器,此时主设备的MOSI线上发送一位数据,当从设备收到时钟信号后,也给从设备的MISO线上发送一位数据(所以从设备的数据必须提前准备好),此时数据才算开始传输。(传输由主设备发起)
数据传输开始后,将发送缓冲器中剩下的位数(开始发送时,已经有一位数据到来传输线上)全部移入到移位寄存器,以后的数据传输都从移位寄存器中开始。此时如果设置了发送缓冲器空中断,则会进入中断,可以继续发送数据(即可以继续给发送缓冲器中填入数据)
主设备发送数据的同时,也会接收从设备传来的数据,并且无论是开始传输时传输线上的那位数据还是接下来传输的剩下位数,在主设备接收后,都会储存在移位寄存器中(注意主设备端发送数据时的移位寄存器和接收数据时的移位寄存器是同一个寄存器,理解前面彩色标注的那个表格,主设备高位到从设备低位、从设备高位到主设备低位的循环过程是在同一个移位寄存器中进行)
当一次完整的8位、16位数据传输完成后,移位寄存器的数据位移结束,数据会被传入到接收缓冲器,如果此时设置了接收缓冲器非空中断,则会发生中断。
再注意一个东西:发送缓冲器非空中断后,可以继续发送数据,但一定要手动清除中断标志;接收缓冲器非空中断后,可以读数据来清除中断标志,也可以手动清除中断标志。
2、时钟波特率
主设备可以通过SPI_Init()
函数的SPI_BaudRatePrescaler
参数来设置分频系数设置SCK引脚输出脉冲频率。从设备设置的时钟频率是无效的。
如果主设备和从设备都设置了时钟分频系数,以主设备设置的为主。
3、关闭SPI
不能直接失能SPI,这样会使目前正在传送的数据出错。
应该等待接收数据完成、发送数据完成、忙标志清零后再失能SPI。
四、SPI编程顺序
1、使能相关GPIO时钟、SPI时钟
相关GPIO:
(1)SPI1
NSS:PA4
SCK:PA5
MISO:PA6
MOSI:PA7
(2)SPI2
NSS:PB12
SCK:PB13
MISO:PB14
MOSI:PB15
(3)SPI3
NSS:PA15
SCK:PB3
MISO:PB4
MOSI:PB5
相关时钟:
SPI1 :APB2总线
SPI2、SPI3 :APB1总线
2、相关GPIO初始化
GPIO的模式参考下表:
3、初始化SPI
使用SPI_Init()
,具体函数设置前面已经总结到了。
4、使能SPI
使用SPI_Cmd()
使能SPI
5、发送、接收数据
使用SPI_I2S_SendData()
与SPI_I2S_ReceiveData()
6、状态获取
使用SPI_I2S_GetFlagStatus()
函数
7、面向其他外设
上面的几个步骤,只是应用于SPI与SPI之间的数据传输,还可以与其他高级外设进行SPI通信,比如与外部Flash的数据传输,此时需要根据外设的参考手册的指令和时序,编写对应代码,后面博客也会专门去总结。
五、例子(STM32F1与STM32F4之间的SPI通信)
1、题
完成STM32F103与STM32F407之间的SPI通信,其中F1使用SPI1,F4使用SPI2,并将各自接收到的数据打印在串口显示出来。
2、代码
(1)F1代码(主设备)
SPI.h代码:
#ifndef _SPI_
#define _SPI_
#include "stm32f10x.h"
void MySPI_Init(void);
u16 SPI1_Send_Data(u16 data);
#endif
SPI.c代码:
#include "spi.h"
#include "stm32f10x.h"
void MySPI_Init(void)
/*
PA4-----SPI1_NSS
PA5-----SPI1_SCK
PA6-----SPI1_MISO
PA7-----SPI1_MOSI
*/
GPIO_InitTypeDef GPIO_InitStructure;
SPI_InitTypeDef SPI_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_SPI1, ENABLE);
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_5|GPIO_Pin_7|GPIO_Pin_6; //都是复用推挽输出
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
SPI_InitStructure.SPI_BaudRatePrescaler=SPI_BaudRatePrescaler_4;
SPI_InitStructure.SPI_CPHA=SPI_CPHA_2Edge;
SPI_InitStructure.SPI_CPOL=SPI_CPOL_Low;
SPI_InitStructure.SPI_CRCPolynomial=7;
SPI_InitStructure.SPI_DataSize=SPI_DataSize_16b;
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);
u16 SPI1_Send_Data(u16 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);
main.c代码:
#include "stm32f10x.h"
#include "spi.h"
#include "delay.h"
#include "usart.h"
int main(void)
u16 data;
MySPI_Init();
delay_init();
uart_init(115200);
while(1)
data=SPI1_Send_Data(5678);
printf("%d ",data);//测试用
delay_ms(10);
(2)F4代码(从设备)
SPI.h代码:
#ifndef _SPI_
#define _SPI_
#include "stm32f4xx.h"
void MySPI_Init(void);
u16 SPI2_Send_Data(u16);
#endif
SPI.c代码:
#include "spi.h"
#include "stm32f4xx.h"
#include "usart.h"
void MySPI_Init(void)
/*
PB12-----SPI2_NSS
PB13-----SPI2_SCK
PB14-----SPI2_MISO
PB15-----SPI2_MOSI
*/
GPIO_InitTypeDef GPIO_InitStructure;
SPI_InitTypeDef SPI_InitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2, ENABLE);
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);
GPIO_PinAFConfig(GPIOB,GPIO_PinSource13, GPIO_AF_SPI2);
GPIO_PinAFConfig(GPIOB,GPIO_PinSource14, GPIO_AF_SPI2);
GPIO_PinAFConfig(GPIOB,GPIO_PinSource15, GPIO_AF_SPI2);
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF;
GPIO_InitStructure.GPIO_OType=GPIO_OType_PP;
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_14|GPIO_Pin_13|GPIO_Pin_15;
GPIO_InitStructure.GPIO_PuPd=GPIO_PuPd_UP;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_100MHz;
GPIO_Init(GPIOB,&GPIO_InitStructure);
SPI_InitStructure.SPI_BaudRatePrescaler=SPI_BaudRatePrescaler_4;
SPI_InitStructure.SPI_DataSize=SPI_DataSize_16b;
SPI_InitStructure.SPI_CPOL=SPI_CPOL_Low;
SPI_InitStructure.SPI_CPHA=SPI_CPHA_2Edge;
SPI_InitStructure.SPI_Direction=SPI_Direction_2Lines_FullDuplex;
SPI_InitStructure.SPI_FirstBit=SPI_FirstBit_MSB;
SPI_InitStructure.SPI_Mode=SPI_Mode_Slave;
SPI_InitStructure.SPI_NSS=SPI_NSS_Soft;
SPI_InitStructure.SPI_CRCPolynomial=7;
SPI_Init(SPI2,&SPI_InitStructure);
SPI_Cmd(SPI2, ENABLE);
u16 SPI2_Send_Data(u16 data)
while(SPI_I2S_GetFlagStatus(SPI2,SPI_I2S_FLAG_TXE)==RESET)
//等待发送缓存寄存器为空
SPI_I2S_SendData(SPI2,data);
while(SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_RXNE)==RESET)
//等待接收缓存器非空
return SPI_I2S_ReceiveData(SPI2);
main.c代码:
#include "stm32f4xx.h"
#include "usart.h"
#include "delay.h"
#include "spi.h"
#include "led.h"
#include "sys.h"
int main(void)
u16 data;
MySPI_Init();
uart_init(115200);
delay_init(168);
while(1)
data=SPI2_Send_Data(1234);
printf("%d ",data);//测试用
delay_ms(10);
3、接线
/*F1板子:
PA4-----SPI1_NSS
PA5-----SPI1_SCK
PA6-----SPI1_MISO
PA7-----SPI1_MOSI
*/
/*F4板子:
PB12-----SPI2_NSS
PB13-----SPI2_SCK
PB14-----SPI2_MISO
PB15-----SPI2_MOSI
*/
①SPI_CLK接SPI_CLK
②MOSI接MOSI
③MISO接MISO
④NSS引脚都悬空(软件片选)
⑤两块板子GND相连
4、分析
核心部分还是收发函数:
u16 SPI1_Send_Data(u16 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);
现分析一下传输过程:
首先传输是由主设备发起,主设备开始执行:
while(SPI_I2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_TXE)==RESET)
//等待发送缓存寄存器为空
此时应该是什么状态?本程序中传输的是16位数,所以主设备16位数的最高位即位15被发送到MOSI线上,剩下的15位数仍在发送缓冲寄存器中,等待传输开始。
怎么才算传输开始?当从设备的数据最高位也被发送到MISO线上时,表示传输开始。所以执行上面这个while语句其实也是在等待从设备执行这条while语句。
当从设备(本程序F4板子)执行下面程序的时候:
while(SPI_I2S_GetFlagStatus(SPI2,SPI_I2S_FLAG_TXE)==RESET)
//等待发送缓存寄存器为空
从设备数据的最高位被发送到MISO线上,此时MISO线与MOSI线上都有数据,传输开始!
接下来呢?接下来主设备的剩下15位数被送入主设备移位寄存器,此时发送缓冲器空;从设备剩下的15位数也被送入从设备移位寄存器,此时从设备发送缓冲器空。主从设备都执行下一条语句:发送新的数据到发送缓冲器:SPI_I2S_SendData(SPI1,data);
和SPI_I2S_SendData(SPI1,data);
当然程序刚开始执行时,主从设备的发送缓冲器都是空的,程序从发送数据到发送数据缓冲器这行代码开始执行。
发送新的数据后,继续等待上一次数据发送的完成(在移位寄存器中交换各位数据),即执行:
while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE)==RESET)
//等待接收缓存器非空
当上一次数据传输完成后,也意味着主从设备都同时收到了对方数据,接收到的数据在移位寄存器中。此时将主从设备各自
以上是关于STM32F103(二十六)SPI通信(+两块STM32之间的SPI通信)的主要内容,如果未能解决你的问题,请参考以下文章
STM32F103(二十六)SPI通信(+两块STM32之间的SPI通信)