打印
[应用相关]

STM32纯硬件SPI主/从模式 库函数版

[复制链接]
2202|12
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
1. STM32 SPI

1.1 STM32的SPI接口

SPI可以设置为主、从两种模式,并且支持全双工模式,而配置为主、从模式或软件、硬件NSS,在操作上有很大的区别。由于一个项目需求,笔者对STM32的硬件模式和主从模式进行了一些研究,走了很多弯路,也查询了很多资料,现在终于调通了,因此写一篇**记录调试心得,以及很多需要注意的地方。

以下是STM32 SPI接口的介绍:

3线全双工同步传输;

8或16位传输帧格式选择;

主或从操作,支持多主模式;

主模式和从模式下均可以由软件或硬件进行NSS管理:主/从操作模式的动态改变;

可编程的时钟极性和相位;

可编程的数据顺序,MSB在前或LSB在前;

可触发中断的专用发送和接收标志;

SPI总线忙状态标志;

支持可靠通信的硬件CRC;

可触发中断的主模式故障、过载以及CRC错误标志;

支持DMA功能的1字节发送和接收缓冲器:产生发送和接受请求。

本文主要探讨主模式和从模式NSS硬件和软件管理。


使用特权

评论回复
沙发
keaibukelian|  楼主 | 2021-7-3 11:34 | 只看该作者
2. SPI Master 初始化及测试

2.1 硬件NSS模式

以下是初始化代码

void SPI1_Configuration(void)
{
        SPI_InitTypeDef SPI_InitStructure;
        GPIO_InitTypeDef GPIO_InitStructure;
       
/************打开时钟*************/
        RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1,ENABLE);  
        RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOA, ENABLE );
        RCC_APB2PeriphClockCmd( RCC_APB2Periph_AFIO, ENABLE );
       
/************引脚配置*************/
        GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;  //SPI_NSS
        GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;  //复用推挽输出
        GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
        GPIO_Init(GPIOA, &GPIO_InitStructure);
        GPIO_SetBits(GPIOA,GPIO_Pin_4);
       
        GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;                //SPI_SCK
        GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;  //复用推挽输出
        GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
        GPIO_Init(GPIOA, &GPIO_InitStructure);
       
        GPIO_InitStructure.GPIO_Pin =  GPIO_Pin_6;                //SPI_MISO
        GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;  //浮空输入
        GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
        GPIO_Init(GPIOA, &GPIO_InitStructure);

        GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7;        //SPI_MOSI
        GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;  //复用推挽输出
        GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
        GPIO_Init(GPIOA, &GPIO_InitStructure);

/************SPI初始化配置*************/
        SPI_Cmd(SPI1, DISABLE);         //必须先禁用,才能改变MODE
        SPI_InitStructure.SPI_Direction =SPI_Direction_2Lines_FullDuplex; //两线全双工
        SPI_InitStructure.SPI_Mode =SPI_Mode_Master; //主
        SPI_InitStructure.SPI_DataSize =SPI_DataSize_8b; //8位
        SPI_InitStructure.SPI_CPOL =SPI_CPOL_Low; //CPOL=0
        SPI_InitStructure.SPI_CPHA =SPI_CPHA_1Edge; //CPHA=0
        SPI_InitStructure.SPI_NSS =SPI_NSS_Hard; //硬件NSS
        SPI_InitStructure.SPI_BaudRatePrescaler =SPI_BaudRatePrescaler_64; //64分频
        SPI_InitStructure.SPI_FirstBit =SPI_FirstBit_MSB; //高位在前
        SPI_InitStructure.SPI_CRCPolynomial =7; //CRC7
        SPI_Init(SPI1,&SPI_InitStructure);
       
//         SPI_Cmd(SPI1, ENABLE);                        //先不打开SPI
        SPI_SSOutputCmd(SPI1, ENABLE);                   //SPI的NSS引脚控制开启

}


SPI配置为主模式,采用硬件NSS有几点需要注意,若采用硬件NSS,一定要把NSS引脚输出设置为GPIO_Mode_AF_PP,否则程序无法正确控制。

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;


使用特权

评论回复
板凳
keaibukelian|  楼主 | 2021-7-3 11:34 | 只看该作者
2.2 软件NSS模式
软件NSS的初始化步骤大同小异,有几个地方不一样,需要注意一下

①NSS引脚修改为GPIO_Mode_Out_PP

    /************引脚配置*************/
            GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;  //SPI_NSS
            GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;  //推挽输出
            GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
            GPIO_Init(GPIOA, &GPIO_InitStructure);
            GPIO_SetBits(GPIOA,GPIO_Pin_4);



②将NSS配置改为SPI_NSS_Soft,然后打开SPI,屏蔽SPI_SSOutputCmd()这个函数。

    /************SPI初始化配置*************/
            SPI_Cmd(SPI1, DISABLE);         //必须先禁用,才能改变MODE
            SPI_InitStructure.SPI_Direction =SPI_Direction_2Lines_FullDuplex; //两线全双工
            SPI_InitStructure.SPI_Mode =SPI_Mode_Master; //主
            SPI_InitStructure.SPI_DataSize =SPI_DataSize_8b; //8位
            SPI_InitStructure.SPI_CPOL =SPI_CPOL_Low; //CPOL=0
            SPI_InitStructure.SPI_CPHA =SPI_CPHA_1Edge; //CPHA=0
            SPI_InitStructure.SPI_NSS =SPI_NSS_Soft; //软件NSS
            SPI_InitStructure.SPI_BaudRatePrescaler =SPI_BaudRatePrescaler_64; //64分频
            SPI_InitStructure.SPI_FirstBit =SPI_FirstBit_MSB; //高位在前
            SPI_InitStructure.SPI_CRCPolynomial =7; //CRC7
            SPI_Init(SPI1,&SPI_InitStructure);
           
             SPI_Cmd(SPI1, ENABLE);                        //打开SPI
             //SPI_SSOutputCmd(SPI1, ENABLE);                   //SPI的NSS引脚控制开启



使用特权

评论回复
地板
keaibukelian|  楼主 | 2021-7-3 11:34 | 只看该作者
2.3 主函数

2.3.1 硬件NSS模式

硬件NSS模式的操作步骤和软件NSS模式可谓天差地别,首先初始化的时候不需要打开SPI,而且需要单独配置打开NSS引脚,否则NSS引脚会一直输出低电平,无法控制。
其次是操作步骤,硬件NSS模式下,每一次数据读写都需要先打开SPI,操作完成后再关闭SPI,必须要按照这个步骤来,否则NSS引脚一直会是低电平。
这也许就是很多人认为硬件NSS有BUG,无法正确选通,而我也在网上查了很多资料,发现很少有人用硬件NSS。

    int main()
    {
            u8 SPI_TX=10,SPI_RX=0;                 //
            SPI1_Configuration();                                //spi初始化
            while(1)          
            {
                       SPI_Cmd(SPI1,ENABLE);                 //启动SPI
                      
                    /**************向从设备发送一个字节*************/
                    while(SPI_I2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_TXE) == RESET);
                    SPI_I2S_SendData(SPI1,SPI_TX);       
                   
                  /**************保存将接收到的数据*************/
                    while(SPI_I2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_RXNE) == RESET);
                    SPI_RX = SPI_I2S_ReceiveData(SPI1);
                   
                    SPI_Cmd(SPI1,DISABLE);                 //关闭SPI
                     
                    SPI_TX++;                        //发送的值+1
                     delay_100ms(10);                //延时1秒
                    if(SPI_TX>50)                {SPI_TX=10;}
            }
    }


使用特权

评论回复
5
keaibukelian|  楼主 | 2021-7-3 11:35 | 只看该作者
效果测试

上图是刚刚的代码运行的效果,用逻辑分析仪抓取的SPI波形。
从设备同样是STM32,设置为硬件从模式,采用硬件NSS控制,设置方法后文会讲。
可以看到NSS能正确拉低,主设备能正确收到从设备返回的数据。

从设备设置的是收到什么就会返回什么,由于SPI的特性是在发送的同时接收数据,且从设备不能主动向主设备发送数据,因此在发送新数据的时候才能收到上此次发送的旧数据。


使用特权

评论回复
6
keaibukelian|  楼主 | 2021-7-3 11:35 | 只看该作者
2.3.2 软件NSS模式

由于代码差不多,就只贴while(1)的函数

    while(1)          
    {
               GPIO_ResetBits(GPIOA,GPIO_Pin_4);                 //拉低CS
                /**************向从设备发送一个字节*************/
                while(SPI_I2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_TXE) == RESET);
                SPI_I2S_SendData(SPI1,SPI_TX);       
               
              /**************保存将接收到的数据*************/
                while(SPI_I2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_RXNE) == RESET);
                SPI_RX = SPI_I2S_ReceiveData(SPI1);
               
                GPIO_SetBits(GPIOA,GPIO_Pin_4);                 //拉高CS
               
                SPI_TX++;                        //发送的值+1
                delay_100ms(10);                //延时1秒
                if(SPI_TX>50)         {SPI_TX=10;}
        }


使用特权

评论回复
7
keaibukelian|  楼主 | 2021-7-3 11:35 | 只看该作者
  • 效果测试

软件NSS模式下,SPI是常开的,读写数据的时候需要人为控制CS引脚
可以看到CS线在最后一个CLK脉冲发送完成后就立即拉高了,和硬件模式有一点区别

如果去掉这两个函数会怎样呢?

while(SPI_I2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_TXE) == RESET);
while(SPI_I2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_RXNE) == RESET);



使用特权

评论回复
8
keaibukelian|  楼主 | 2021-7-3 11:36 | 只看该作者
主函数改为:


    while(1)          
    {
               GPIO_ResetBits(GPIOA,GPIO_Pin_4);                 //拉低CS
                /**************向从设备发送一个字节*************/
                SPI_I2S_SendData(SPI1,SPI_TX);       
               
              /**************保存将接收到的数据*************/
                SPI_RX = SPI_I2S_ReceiveData(SPI1);
               
                delay_us(2);                        //延时2微秒
                GPIO_SetBits(GPIOA,GPIO_Pin_4);                 //拉高CS
               
                SPI_TX++;                        //发送的值+1
                delay_100ms(10);                //延时1秒
                if(SPI_TX>50)         {SPI_TX=10;}
        }


使用特权

评论回复
9
keaibukelian|  楼主 | 2021-7-3 11:36 | 只看该作者
效果测试

可以看到,程序似乎没有按照预想顺序进行,延时函数好像没有在SPI_I2S_ReceiveData()结束后才执行。
这也是使用硬件SPI常犯的错误,因为在使用硬件SPI时,外设在进行数据读写的时候是不占用内核时间的,内核把数据丢给外设寄存器就完事了,内核只需要等待外设返回结束标志位,刚才屏蔽掉的两个函数就是在等待外设返回结束标志,而这个等待时间其实也可以做很多事情。
经过测试,内核对外设的操作只用了大概0.4uS。
若不注意这点,可能会出现SPI正在读取芯片数据过程中,芯片的CS线被拉高,芯片可能就停止发送数据了,导致最后读取的数据异常。

以上就是SPI Master模式中的硬件NSS和软件NSS的设置和控制,接下来是SPI Slave模式的设置步骤。


使用特权

评论回复
10
keaibukelian|  楼主 | 2021-7-3 11:37 | 只看该作者
3. SPI Slave 初始化及测试
3.1 硬件NSS模式
以下是初始化代码

void SPI1_Config_Slave(void)               
{
/************打开时钟*************/
        SPI_InitTypeDef SPI_InitStructure;
        GPIO_InitTypeDef GPIO_InitStructure;
       
        RCC_APB2PeriphClockCmd( RCC_APB2Periph_SPI1 , ENABLE );
        RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOA, ENABLE );
        RCC_APB2PeriphClockCmd( RCC_APB2Periph_AFIO , ENABLE );
       
/************引脚配置*************/
        GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;  //SPI_NSS
        GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;  //上拉输入
//         GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
        GPIO_Init(GPIOA, &GPIO_InitStructure);
        GPIO_SetBits(GPIOA,GPIO_Pin_4);
       
        GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;                //SPI_SCK
        GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;  //上拉输入
//         GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
        GPIO_Init(GPIOA, &GPIO_InitStructure);
       
        GPIO_InitStructure.GPIO_Pin =  GPIO_Pin_6;                //SPI_MISO
        GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD;  //复用开漏输出(多从机)
        GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
        GPIO_Init(GPIOA, &GPIO_InitStructure);

        GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7;        //SPI_MOSI
        GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;  //上拉输入
//         GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
        GPIO_Init(GPIOA, &GPIO_InitStructure);

/************SPI初始化配置*************/
        SPI_Cmd(SPI1, DISABLE);                 //必须先禁能,才能改变MODE
        SPI_InitStructure.SPI_Direction =SPI_Direction_2Lines_FullDuplex; //两线全双工
        SPI_InitStructure.SPI_Mode =SPI_Mode_Slave; //从机
        SPI_InitStructure.SPI_DataSize =SPI_DataSize_16b; //8位
        SPI_InitStructure.SPI_CPOL =SPI_CPOL_Low; //CPOL=1时钟悬空高
        SPI_InitStructure.SPI_CPHA =SPI_CPHA_1Edge; //CPHA=1 数据捕获第2个
        SPI_InitStructure.SPI_NSS =SPI_NSS_Hard; //硬件NSS
        SPI_InitStructure.SPI_BaudRatePrescaler =SPI_BaudRatePrescaler_32; //32分频(从机没用)
        SPI_InitStructure.SPI_FirstBit =SPI_FirstBit_MSB; //高位在前
        SPI_InitStructure.SPI_CRCPolynomial =7; //CRC7
        SPI_Init(SPI1,&SPI_InitStructure);
       
        SPI_Cmd(SPI1, ENABLE);


使用特权

评论回复
11
keaibukelian|  楼主 | 2021-7-3 11:37 | 只看该作者
从机硬件NSS初始化和软件NSS初始化大同小异,有几个注意的地方是:

不同于主机,从机很多IO口都要改为输入模式。

然后在SPI_MISO引脚的配置上,笔者这里用的是开漏输出,因为一个主机上挂了很多从机,如果用上拉输出的话可能会造成很多问题,在这条总线上用的是外部电阻上拉。

但是如果是单从机的话,如果主机设置的是浮空输入,那就很有可能导致主机无法收到数据,因此要根据自己的实际使用情况灵活设置


使用特权

评论回复
12
caizhiwei| | 2021-9-1 11:48 | 只看该作者
帮顶一下,这么好的帖子呢

使用特权

评论回复
13
littlelida| | 2021-9-1 16:09 | 只看该作者
一般nss,会选择IO

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

63

主题

4075

帖子

5

粉丝