STM32 SPI通信
SPI 通讯使用 3 条总线及片选线,3 条总线分别为 SCK、MOSI、MISO,片选线为NSS(CS)
-
NSS 信号线由高变低 ,是 SPI 通讯的起始信号 。 NSS 是每个从机各自独占的信号线,当从机在自己的 NSS 线检测到起始信号后,就知道自己被主机选 中了,开始准备与主机通讯。在图中的标号?处, NSS 信号由低变高 ,是 SPI 通讯的停止 信号 ,表示本次通讯结束,从机的选中状态被取消。 -
SPI 使用 MOSI 及 MISO 信号线来传输数据,使用 SCK 信号线进行数据同步。 MOSI 及 MISO 数据线在 SCK 的每个时钟周期传输一位数据,且数据输入输出是同时进行的。 -
SPI 一共有四种通讯模 式,它们的主要区别是总线空闲时 SCK 的时钟状态以及数据采样时刻。为方便说明,在此 引入“时钟极性 CPOL”和“时钟相位 CPHA”的概念。 -
时钟极性 CPOL 是指 SPI 通讯设备处于空闲状态时, SCK 信号线的电平信号(即 SPI 通 讯开始前、 NSS 线为高电平时 SCK 的状态)。 CPOL=0 时, SCK 在空闲状态时为低电平,CPOL=1 时,则相反。 -
时钟相位 CPHA 是指数据的采样的时刻,当 CPHA=0 时, MOSI 或 MISO 数据线上的信号将会在 SCK 时钟线的“奇数边沿” 被采样。当 CPHA=1 时,数据线在 SCK 的“偶数边沿” 采样。 CPHA=0 时的 SPI 通讯模式 CPHA=1 时的 SPI 通讯模式: 1、先将片选线NSS拉低,
2、将数据写入发送缓冲区,SCK时钟开始运行,MOSI会自动将发送缓冲区的数据发送过,每发完一帧数据,发送缓冲区为空时,TXE标志位会变1,才可继续发送数据。
3、通过判断接收缓冲区标志位是否为1(接收缓冲区非空),读取接收缓冲区的数据。
MSB 先行(高位数据在前)还是 LSB 先行(低位数据在前)的问题 ?
站在比特位角度看,MSB在SPI协议中,表示先以高比特位先发送。同理,LSB表示以低比特位先发送。不能与大小端混淆。
大端小端问题 (字节的角度看)
big endian是指低地址存放最高有效字节(大端 高位字节放在低地址),而little endian则是低地址存放最低有效字节(小端 低位放在低地址)。
比如以0x12345678为例在两种不同字节序CPU中的存储顺序
SPI_DEMO实践
STM32主从双机SPI通信测试 :
实现功能:主从设备可以互相接收到对方的数据
**stm32f103c8t6主< —————>stm32f103c8t6从**
接线:
主(SPI1) | 从(SPI1) |
---|
MOSI(PA7) | MOSI(PA7) | MISO(PA6) | MISO(PA6) | SPI_CLK(PA5) | SPI_CLK(PA5) | SPI_NSS(PA8) | SPI_NSS(PA4) |
连线是一一对应的,不能将MOSI接上MISO,且两个设备的配置参数速率、相位、极性、CRC和传输方向及位数要相同,切记,一定要共地 。
主设备主要代码:
/*主设备stmf103c8 SPI1 */
int main(void)
{
u16 t;
u8 send_data='A';
u8 led_flag = 0;
delay_init(); //延时函数初始化
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //设置NVIC中断分组2:2位抢占优先级,2位响应优先级
uart_init(115200); //串口初始化为115200
LED_Init(); //LED端口初始化
KEY_Init(); //按键PB9上拉 检测低电平
spi_M_init(); //SPI1
SPI_MCU_CS_LOW(); //起始信号 低电平选中 才能和从设备进行通信
while(1)
{
if(KEY0==0)
{
delay_ms(5);
if(KEY0==0){
/*检查指定的SPI标志位设置与否:发送缓存空标志位 没有数据*/
while (SPI_I2S_GetFlagStatus(MCU_SPIx, SPI_I2S_FLAG_TXE) == RESET); //检查指定的SPI标志位设置与否:发送缓存空标志位
SPI_I2S_SendData(MCU_SPIx, send_data); //通过外设SPIx发送一个数据
/*等待接收完一个byte*/
while (SPI_I2S_GetFlagStatus(MCU_SPIx, SPI_I2S_FLAG_RXNE) == RESET);
/*将从设备发送的数据 也就是主设备接收到数据 发送到串口发送缓冲区*/
USART1->DR = SPI_I2S_ReceiveData(MCU_SPIx);
led_flag++;
send_data++;}
while(KEY0==0);
}else{
}
if(led_flag%2)
GPIO_SetBits(GPIOC,GPIO_Pin_13);
else
GPIO_ResetBits(GPIOC,GPIO_Pin_13);
}
}
//spi主模式配置
void spi_M_init(void){
SPI_InitTypeDef SPI_InitStructure;
GPIO_InitTypeDef GPIO_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
/* 使能SPI时钟 */
MCU_SPI_APBxClock_FUN ( MCU_SPI_CLK, ENABLE );
/* 使能SPI引脚相关的时钟 */
MCU_SPI_CS_APBxClock_FUN ( MCU_SPI_CS_CLK|MCU_SPI_SCK_CLK|MCU_SPI_MISO_PIN|MCU_SPI_MOSI_PIN, ENABLE );
/* 配置SPI的 CS引脚,普通IO即可 */
GPIO_InitStructure.GPIO_Pin = MCU_SPI_CS_PIN;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_Init(MCU_SPI_CS_PORT, &GPIO_InitStructure);
/* 配置SPI的 SCK引脚*/
GPIO_InitStructure.GPIO_Pin = MCU_SPI_SCK_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_Init(MCU_SPI_SCK_PORT, &GPIO_InitStructure);
/* 配置SPI的 MISO引脚*/
GPIO_InitStructure.GPIO_Pin = MCU_SPI_MISO_PIN;
GPIO_Init(MCU_SPI_MISO_PORT, &GPIO_InitStructure);
/* 配置SPI的 MOSI引脚*/
GPIO_InitStructure.GPIO_Pin = MCU_SPI_MOSI_PIN;
GPIO_Init(MCU_SPI_MOSI_PORT, &GPIO_InitStructure);
/* 停止信号 MCU: CS引脚高电平*/
SPI_MCU_CS_HIGH();
/* SPI 模式配置 */
// MCU芯片 支持SPI模式0及模式3,据此设置CPOL CPHA
SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
SPI_InitStructure.SPI_Mode = SPI_Mode_Master;
SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;
SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;
SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;
SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;//需要注意
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_256;//
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;
SPI_InitStructure.SPI_CRCPolynomial = 7;
SPI_Init(MCU_SPIx , &SPI_InitStructure);
/* 使能 SPI */
SPI_Cmd(MCU_SPIx , ENABLE);
//SPI_I2S_ITConfig(MCU_SPIx, SPI_I2S_IT_RXNE, ENABLE); // 使能接收中断
/* NVIC中断控制器配置 */
// NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); // 中断优先级分组2
// NVIC_InitStructure.NVIC_IRQChannel = SPI1_IRQn; // SPI2中断
// NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=1; // 抢占优先级3
// NVIC_InitStructure.NVIC_IRQChannelSubPriority =3; // 子优先级3
// NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; // IRQ通道使能
// NVIC_Init(&NVIC_InitStructure); // 根据指定的参数初始化VIC寄存器
}
从设备主要代码:
- 为了能让主机一开始可以接收到有效数据,先把要发送的数据存放到发送缓冲区SPI_I2S_SendData(MCU_SPIx,‘n’);。有一点需要注意 ,此时,数据是还没发到主设备,因为从设备是不能主动发送数据,而是被动发送数据的,得等待主设备发送数据,说白了,就是要等一个周期的时钟信号。从设备接收到时钟信号(CLK),将发送缓冲区的数据移到移位寄存器,发送给主设备。
- 因为是从设备,将复用SPI1片选引脚,硬件片选。
//stm32f103c8 从设备
int main(void)
{
u16 retry;
u8 send_data='a';
u8 led_flag=0;
delay_init(); //延时函数初始化
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //设置NVIC中断分组2:2位抢占优先级,2位响应优先级
uart_init(115200); //串口初始化为115200
LED_Init(); //LED端口初始化
KEY_Init();
spi_S_init();
while (SPI_I2S_GetFlagStatus(MCU_SPIx, SPI_I2S_FLAG_TXE) == RESET); //检查指定的SPI标志位设置与否:接受缓存非空标志位
SPI_I2S_SendData(MCU_SPIx,'n');//
while(1)
{
// while (SPI_I2S_GetFlagStatus(MCU_SPIx, SPI_I2S_FLAG_TXE) == RESET); //检查指定的SPI标志位设置与否:接受缓存非空标志位
// SPI_I2S_SendData(MCU_SPIx,'n');
}
}
//spi从模式配置
void spi_S_init(void){
SPI_InitTypeDef SPI_InitStructure;
GPIO_InitTypeDef GPIO_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
/* 使能SPI时钟 */
MCU_SPI_APBxClock_FUN ( MCU_SPI_CLK, ENABLE );
/* 使能SPI引脚相关的时钟 */
MCU_SPI_CS_APBxClock_FUN ( MCU_SPI_CS_CLK|MCU_SPI_SCK_CLK|MCU_SPI_MISO_PIN|MCU_SPI_MOSI_PIN, ENABLE );
/* 配置SPI的 CS引脚,普通IO即可 */
GPIO_InitStructure.GPIO_Pin = MCU_SPI_CS_PIN;
// GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_Init(MCU_SPI_CS_PORT, &GPIO_InitStructure);
/* 配置SPI的 SCK引脚*/
GPIO_InitStructure.GPIO_Pin = MCU_SPI_SCK_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_Init(MCU_SPI_SCK_PORT, &GPIO_InitStructure);
/* 配置SPI的 MISO引脚*/
GPIO_InitStructure.GPIO_Pin = MCU_SPI_MISO_PIN;
GPIO_Init(MCU_SPI_MISO_PORT, &GPIO_InitStructure);
/* 配置SPI的 MOSI引脚*/
GPIO_InitStructure.GPIO_Pin = MCU_SPI_MOSI_PIN;
GPIO_Init(MCU_SPI_MOSI_PORT, &GPIO_InitStructure);
/* 停止信号 MCU: CS引脚高电平*/
// SPI_MCU_CS_HIGH();
/* SPI 模式配置 */
// MCU芯片 支持SPI模式0及模式3,据此设置CPOL CPHA
SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
SPI_InitStructure.SPI_Mode = SPI_Mode_Slave;
SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;
SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;
SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;
SPI_InitStructure.SPI_NSS = SPI_NSS_Hard;//作为从机 片选
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_256;
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;
SPI_InitStructure.SPI_CRCPolynomial = 7;
SPI_Init(MCU_SPIx , &SPI_InitStructure);
/* 使能 SPI */
SPI_Cmd(MCU_SPIx , ENABLE);
SPI_I2S_ITConfig(MCU_SPIx, SPI_I2S_IT_RXNE, ENABLE); // 使能接收中断
NVIC_InitStructure.NVIC_IRQChannel = SPIX_IRQ; // SPI1中断
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=1; // 抢占优先级3
NVIC_InitStructure.NVIC_IRQChannelSubPriority =3; // 子优先级3
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; // IRQ通道使能
NVIC_Init(&NVIC_InitStructure); // 根据指定的参数初始化VIC寄存器
}
u8 send_data='a';
u8 led_flag=0;
//中断处理函数
void handle_spi_fun(void)
{
/* 判断接收缓冲区是否为非空 */
if (SET == SPI_I2S_GetITStatus(MCU_SPIx, SPI_I2S_IT_RXNE))
{
USART1->DR = MCU_SPIx->DR; /* 读取接收缓冲区数据 */
while (SPI_I2S_GetFlagStatus(MCU_SPIx, SPI_I2S_FLAG_TXE) == RESET);
MCU_SPIx->DR = send_data;
if(led_flag%2)
GPIO_ResetBits(GPIOC,GPIO_Pin_13);
else
GPIO_SetBits(GPIOC,GPIO_Pin_13);
led_flag++;
send_data++;
/* 清中断标志 */
SPI_I2S_ClearITPendingBit(MCU_SPIx, SPI_I2S_IT_RXNE);
}
}
测试效果 : COM8是主设备接收到从设备的数据 COM4是从设备接收到主设备的数据
很明显的问题,主设备没能成功获取到从设备的数据,从设备接收到主设备的数据。
问题分析:
1、问题出现在主设备:主设备没接收或者没响应。
2、问题出现在从设备:从设备并没发送数据。
3、以上两个问题同时出现。
排插问题 :
1、首先,我将主设备的MOSI连上自身主设备的MISO,测试结果,主设备是可以接收数据。排除了第一种可能。
2、在主从设备进行通信时,利用逻辑分析仪来检测MISO引脚,没能检测到任何波形变化 。问题很可能就是从设备。
于是,我将从设备换成了STM32F429,主设备还是STM32F103C8,程序也一样。
stm32f103c8t6主< —————>stm32f429IG从
接线:
主(SPI1) | 从(SPI2) |
---|
MOSI(PA7) | MOSI(PB15) | MISO(PA6) | MISO(PB14) | SPI_CLK(PA5) | SPI_CLKP(B13) | SPI_NSS(PA8) | SPI_NSS(PB12) |
/*stm32F429IG SPI2 从设备*/
#define SELECT_MASTER 0
int main(void)
{
u8 send_data='a';
u8 led_flag=0;
LED_GPIO_Config();//PB0 PB1
Debug_USART_Config();//串口1 115200
SPI_MCU_Init();//SPI2
key_init();//PA0 按键
#if SELECT_MASTER //在bsp_spi_mcu.h文件设置
send_data='A';
#else
/*判断发送缓冲区为空 需要注意的一点
当做从设备,将数据放到发送缓冲区,是还没发送到主设备的,
要等主设备发送数据,也就是需要一个时钟信号(SCK)
*/
while (SPI_I2S_GetFlagStatus(MCU_SPI, SPI_I2S_FLAG_TXE) == RESET);
/* 写入数据寄存器,把要写入的数据写入发送缓冲区 */
SPI_I2S_SendData(MCU_SPI, send_data);
#endif
while(1){
//主设备
#if SELECT_MASTER //在bsp_spi_mcu.h文件设置
if(KEY==1)
{
delay(0x0F);
if(KEY==1){
/*判断发送缓冲区为空 没有数据*/
while (SPI_I2S_GetFlagStatus(MCU_SPI, SPI_I2S_FLAG_TXE) == RESET);
/* 写入数据寄存器,把要写入的数据写入发送缓冲区 */
SPI_I2S_SendData(MCU_SPI, send_data);
/*判断接收缓冲区为非空 有数据*/
while (SPI_I2S_GetFlagStatus(MCU_SPI, SPI_I2S_FLAG_RXNE) == RESET);
/*将接收到的数据发送串口发送缓冲区*/
USART1->DR = SPI_I2S_ReceiveData(MCU_SPI);
led_flag++;
send_data++;
while(KEY==1);
}else{}
}
#else
//从设备
/*判断接收缓冲区为非空 有数据*/
while(SPI_I2S_GetFlagStatus(MCU_SPI, SPI_I2S_FLAG_RXNE) == RESET);
/*将接收到的数据发送串口发送缓冲区*/
USART1->DR = SPI_I2S_ReceiveData(MCU_SPI);
/*判断发送缓冲区为空 没有数据*/
while (SPI_I2S_GetFlagStatus(MCU_SPI, SPI_I2S_FLAG_TXE) == RESET);
/*写入数据寄存器,把要写入的数据写入发送缓冲区*/
SPI_I2S_SendData(MCU_SPI, send_data);
led_flag++;
send_data++;
#endif
//从设备每发送一次数据或者主设备按键一次 LED1 PB0反转
if(led_flag%2)
LED1_OFF
else
LED1_ON
}
}
//SPI2配置
void SPI_MCU_Init(void)
{
SPI_InitTypeDef SPI_InitStructure;
GPIO_InitTypeDef GPIO_InitStructure;
/* 使能 MCU_SPI 及GPIO 时钟 */
/*!< SPI_MCU_SPI_CS_GPIO, SPI_MCU_SPI_MOSI_GPIO,
SPI_MCU_SPI_MISO_GPIO,SPI_MCU_SPI_SCK_GPIO 时钟使能 */
RCC_AHB1PeriphClockCmd (MCU_SPI_SCK_GPIO_CLK | MCU_SPI_MISO_GPIO_CLK|MCU_SPI_MOSI_GPIO_CLK|MCU_CS_GPIO_CLK, ENABLE);
/*!< SPI_MCU_SPI 时钟使能 */
MCU_SPI_CLK_INIT(MCU_SPI_CLK, ENABLE);
//设置引脚复用
GPIO_PinAFConfig(MCU_SPI_SCK_GPIO_PORT,MCU_SPI_SCK_PINSOURCE,MCU_SPI_SCK_AF);
GPIO_PinAFConfig(MCU_SPI_MISO_GPIO_PORT,MCU_SPI_MISO_PINSOURCE,MCU_SPI_MISO_AF);
GPIO_PinAFConfig(MCU_SPI_MOSI_GPIO_PORT,MCU_SPI_MOSI_PINSOURCE,MCU_SPI_MOSI_AF);
/*!< 配置 SPI_MCU_SPI 引脚: SCK */
GPIO_InitStructure.GPIO_Pin = MCU_SPI_SCK_PIN;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIO_Init(MCU_SPI_SCK_GPIO_PORT, &GPIO_InitStructure);
/*!< 配置 SPI_MCU_SPI 引脚: MISO */
GPIO_InitStructure.GPIO_Pin = MCU_SPI_MISO_PIN;
GPIO_Init(MCU_SPI_MISO_GPIO_PORT, &GPIO_InitStructure);
/*!< 配置 SPI_MCU_SPI 引脚: MOSI */
GPIO_InitStructure.GPIO_Pin = MCU_SPI_MOSI_PIN;
GPIO_Init(MCU_SPI_MOSI_GPIO_PORT, &GPIO_InitStructure);
/*!< 配置 SPI_MCU_SPI 引脚: CS */
GPIO_InitStructure.GPIO_Pin = MCU_CS_PIN;
#if SELECT_MASTER
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
#else
/*作为从设备 用到硬件片选引脚CS 需要将片选引脚复用*/
GPIO_PinAFConfig(MCU_CS_GPIO_PORT,MCU_SPI_CS_PINSOURCE,MCU_SPI_CS_AF);
#endif
GPIO_Init(MCU_CS_GPIO_PORT, &GPIO_InitStructure);
/* 停止信号 MCU: CS引脚高电平*/
// SPI_MCU_CS_HIGH();
/* MCU_SPI 模式配置 */
// MCU芯片 支持SPI模式0及模式3,据此设置CPOL CPHA
SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
#if SELECT_MASTER
SPI_InitStructure.SPI_Mode = SPI_Mode_Master;
SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;
#else
/*从机 设置硬件片选*/
SPI_InitStructure.SPI_Mode = SPI_Mode_Slave;
SPI_InitStructure.SPI_NSS = SPI_NSS_Hard;
#endif
SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;
SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;
SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_256;
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;
SPI_InitStructure.SPI_CRCPolynomial = 7;
SPI_Init(MCU_SPI, &SPI_InitStructure);
/* 使能 MCU_SPI */
SPI_Cmd(MCU_SPI, ENABLE);
}
![请添加图片描述](https://img-blog.csdnimg.cn/029afabf1290445b918b83f0a82f7ea1.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80Mzc0NjMyNQ==,size_16,color_FFFFFF,t_70)
效果 : COM8是主设备接收到从设备的数据 COM1是从设备接收到主设备的数据
从串口输出结果可以看出,测试成功了。
为了能进一步验证我的猜测,于是,我将STM32F1和STM32F4的主从模式调换,结果,和我的猜测是一样的,主设备(F4)没能成功接收从设备(F1)的发送的数据,但是,从设备却可以接收到数据。
结论 :
STM32F103当作为从机时,可以接收数据,但是发送不了数据或者出现数据移位的问题。
补充一点: 以上两款单片机的主频是不一样,F4主频是180MHz,F1主频是72MHz。F4的APB2时钟频率90MHz,APB1时钟频率45MHz;F1的APB2时钟频率72MHz,APB1时钟频率36MHz。SPI1设备属于高速设备,隶属APB2总线;而SPI2属于低速设备,隶属APB1总线。 因此在 同样的设置参数下,以F1为例,SPI1作为主机时的SCLK时钟频率是72MHz/256=2812.5 KHz,SPI2则是36MHz/256=140.625 KHz。
以上是我一天测试的结果,结论还需要进一步论证,先记录到这里了。 如有错误或者问题,请批评指正,谢谢! 以上代码可以在github下载 代码链接
|