1.SPI是一种高速全双工串行同步通信总线,在软件设计过程中,使用SPI总线必须有一个主机主要是指在数据传输时, 空闲时就不必强调主机和从机。 SPI 可以有多主机模式。
(1)接口
MOSI:数据线, Master Output Slave Input 主机输出, 从机输入
MISO:数据线, Master Input Slave Output 主机输入, 从机输出
/SS: 从器件使能信号, 由主器件控制,是片选线,一般不用
4 线 SPI 接口: SCLK MOSI MISO CS
3 线 SPI 接口: SCLK MOSI MISO
2.SPI接口如何连接
CPOL=0, 串行同步时钟的空闲状态为低电平
CPOL=1, 串行同步时钟的空闲状态为 高电平。
时钟相位(CPHA)能够配置用于选择两种不同的传输协议之一进行数据传输。
CPHA=0, 在串行同步时钟的第一个跳变沿(上升或下降)数据被采样;
CPHA=1, 在串行同步时钟的第二个跳变沿(上升或下降)数据被采样。
3.SPI特征
● 3 线全双工同步传输
● 带或不带第三根双向数据线的双线单工同步传输
● 8 或 16 位传输帧格式选择
● 主或从操作
● 支持多主模式
● 8 个主模式波特率预分频系数(最大为 fPCLK/2)
● 从模式频率 (最大为 fPCLK/2)
● 主模式和从模式的快速通信
● 主模式和从模式下均可以由软件或硬件进行 NSS 管理: 主/从操作模式的动态改变
● 可编程的时钟极性和相位
● 可编程的数据顺序, MSB 在前或 LSB 在前
● 可触发中断的专用发送和接收标志
● SPI 总线忙状态标志
● 支持可靠通信的硬件 CRC
─ 在发送模式下, CRC 值可以被作为最后一个字节发送
─ 在全双工模式中对接收到的最后一个字节自动进行 CRC 校验
● 可触发中断的主模式故障、 过载以及 CRC 错误标志
● 支持 DMA 功能的 1 字节发送和接收缓冲器: 产生发送和接受请求4.SPI结构框图通常 SPI 通过 4 个引脚与外部器件相连:
● MISO: 主设备输入/从设备输出引脚。 该引脚在从模式下发送数据, 在主模式下接收数据。
● MOSI: 主设备输出/从设备输入引脚。 该引脚在主模式下发送数据, 在从模式下接收数据。
● SCK: 串口时钟, 作为主设备的输出, 从设备的输入
● NSS: 从设备选择。 这是一个可选的引脚, 用来选择主/从设备。 它的功能是用来作为“ 片选引脚” , 让主设备可以单独地与特定从设备通讯,避免数据线上的冲突。
.NSS管理
NSS输入又分为硬件输入和软件控制输入两种模式:
软件模式:
对于SPI主机来说, 需要设置SPI_CR1寄存器的SSM为1和SSI位为1,SSM为1是为了使能软件管理。 NSS有内部和外部引脚。 这时候, 外部引脚留作他用( 可以用来作为GPIO驱动从设备的片选信号) 。
内部 NSS引脚电平则通过SPI_CRL寄存器的SSI位来驱动。 SSI位为1是为了使NSS内电平为高电平。
要保持MSTR和SPE位为1, 也就是说要保持主机模式,只有NSS接到高电平信号时, 这两位才能保持置1.也就是说对于STM32的SPI, 要保持为主机状态, 内部输入的NSS电平必须为高。
对于SPI 从机来说:
如果从机选择STM32的一个SPI, 譬如主机选为SPI1, 从机选为SPI2, 则要按照以下操作手册说, NSS引脚在完成字节传输之前必须连接到一个低电平信号。 在软件模式下, 则需要设置SPI_CR1寄存器的SSM为1( 软件管理使能) 和SSI位为0
硬件模式:
对于主机, 我们的NSS可以直接接到高电平, 对于从机, NSS接低就可以。
4.如何配置 SPI
A. 从模式配置:
1) 设置 DFF 位以定义数据帧格式为 8 位或 16 位。
2) 选择 CPOL 和 CPHA 位来定义数据传输和串行时钟之间的相位关系(见图 212)。 为保证正确的数据传输, 从设备和主设备的 CPOL 和 CPHA 位必须配置成相同的方式。
3) 帧格式(SPI_CR1 寄存器中的 LSBFIRST 位定义的” MSB 在前” 还是” LSB 在前” )必须与主设备相同
4) 硬件模式下(参考从选择(NSS)脚管理部分), 在完整的数据帧(8 位或 16 位)传输过程中,NSS 引脚必须为低电平。 在 NSS 软件模式下, 设置 SPI_CR1 寄存器中的 SSM 位并清除 SSI 位。
5) 清除 MSTR 位、 设置 SPE 位(SPI_CR1 寄存器), 使相应引脚工作于 SPI 模式下。在这个配置中, MOSI 引脚是数据输入, MISO 引脚是数据输出。
B. 主模式配置
1) 通过 SPI_CR1 寄存器的 BR[2:0]位定义串行时钟波特率。
2) 选择 CPOL 和 CPHA 位, 定义数据传输和串行时钟间的相位关系(见图 212)。
3) 设置 DFF 位来定义 8 位或 16 位数据帧格式。
4) 配置 SPI_CR1 寄存器的 LSBFIRST 位定义帧格式。
5) 如果需要 NSS 引脚工作在输入模式, 硬件模式下, 在整个数据帧传输期间应把 NSS脚连接到高电平; 在软件模式下, 需设置 SPI_CR1 寄存器的 SSM 位和 SSI 位。 如果 NSS引脚工作在输出模式, 则只需设置 SSOE 位。
6) 必须设置 MSTR 位和 SPE 位(只当 NSS 脚被连到高电平, 这些位才能保持置位)。在这个配置中, MOSI 引脚是数据输出, 而 MISO 引脚是数据输入。
1 #include "main.h" 2 /********************* 3 函数名称:SPI_Init 4 函数功能:SPI初始化 5 函数参数:无 6 函数返回值:无 7 备注:配置spi为主模式 8 CS--PB0---通用推挽输出 9 MISO--PA6---主机输入--浮空输入 10 MOSI--PA7---主机输出--复用推挽输出 11 SCLK--PA5---主机输出--复用推挽输出 12 **********************/ 13 void SPI_Config(void) 14 { 15 #if reg_progream 16 //1.时钟使能PA PB SPI1 17 RCC->APB2ENR |=(1<<2)|(1<<3)|(1<<12); 18 //2. CS PB0 通用推挽输出 19 GPIOB->CRL &=~(0xf<<0); 20 GPIOB->CRL|=(3<<0); 21 //MISO PA6 浮空输入 22 GPIOA->CRL &=~(0xfff<<20); 23 GPIOA->CRL |=(4<<24); 24 //SCLK PA5 主机输出--复用推挽输出 25 GPIOA->CRL |=(0xb<<20); 26 //MOSI PA7 主机输出--复用推挽输出 27 GPIOA->CRL |=(0xb<<28); 28 /***********SPI配置***************/ 29 //串行波特率--/2 30 SPI1->CR1 &=~(0x7<<3); 31 //CPOL -1 CPHA -1 32 SPI1->CR1 |=(1<<0); 33 SPI1->CR1 |=(1<<1); 34 //使用八位数据帧格式 35 SPI1->CR1 &=~(1<<11); 36 //高位在前 37 SPI1->CR1 &=~(1<<7); 38 //先开启LSB,SSI=1,启用软件从设备管理 39 SPI1->CR1 |=(1<<8); 40 SPI1->CR1 |=(1<<9); 41 //SSOE 42 SPI1->CR2 |=(1<<2); 43 //配置为主设备 44 SPI1->CR1 |=(1<<2); 45 //SPI使能 46 SPI1->CR1 |=(1<<6); 47 //双线模式 48 SPI1->CR1 &=~(1<<15); 49 //全双工 50 SPI1->CR1 &=~(1<<10); 51 // 52 #else 53 GPIO_InitTypeDef GPIO_InitStruct; 54 SPI_InitTypeDef SPI_InitStruct; 55 //使能时钟 PA PB SPI1 56 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOB|RCC_APB2Periph_SPI1,ENABLE); 57 //CS PB0 通用推挽输出 58 GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP; 59 GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; 60 GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0; 61 GPIO_Init(GPIOB,&GPIO_InitStruct); 62 //MISO PA6 浮空输入 63 GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING; 64 GPIO_InitStruct.GPIO_Pin = GPIO_Pin_6; 65 GPIO_Init(GPIOA,&GPIO_InitStruct); 66 //MOSI PA7 复用推挽输出 SCLK PA5 复用推挽输出 67 GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP; 68 GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; 69 GPIO_InitStruct.GPIO_Pin = GPIO_Pin_5|GPIO_Pin_7; 70 GPIO_Init(GPIOA,&GPIO_InitStruct); 71 72 SPI_InitStruct.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_2;//串行时钟波特率 fclk/2 73 SPI_InitStruct.SPI_CPOL = SPI_CPOL_High;//时钟极性:高 74 SPI_InitStruct.SPI_CPHA = SPI_CPHA_2Edge;//第2个跳边沿采样 75 SPI_InitStruct.SPI_DataSize = SPI_DataSize_8b;//数据帧:8位 76 SPI_InitStruct.SPI_FirstBit = SPI_FirstBit_MSB;//高位在前 77 SPI_InitStruct.SPI_NSS = SPI_NSS_Soft;//软件管理 78 SPI_InitStruct.SPI_Direction = SPI_Direction_2Lines_FullDuplex;//双线双向全双工 79 SPI_InitStruct.SPI_Mode = SPI_Mode_Master;//主机 80 // SPI_InitStruct.SPI_CRCPolynomial = 7;//CRC多项式复位值 81 SPI_Init(SPI1,&SPI_InitStruct); 82 SPI_Cmd(SPI1,ENABLE);//开启SPI 83 84 #endif 85 } 86 //接收 87 uint8_t SPI_ReadWrite(u8 data) 88 { 89 #if reg_progream 90 //发送数据 91 while(!(SPI3->SR &(1<<1)));//为1,为空 92 SPI1->DR = data; 93 //等待接收到数据 94 while((SPI1->SR &(1<<0))==0); 95 //保存数据 96 return SPI1->DR; //数据寄存器 97 #else 98 while((SPI_I2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_TXE))==RESET) 99 SPI_I2S_SendData(SPI1,data); 100 while((SPI_I2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_RXNE))==RESET) 101 return SPI_I2S_ReceiveData(SPI1); 102 #endif 103 }