查看: 3095|回复: 10

STM32接口篇之SPI接口和W25Q16初始化详解

  [复制链接]
  • TA的每日心情
    开心
    2019-11-4 13:48
  • 签到天数: 14 天

    连续签到: 1 天

    [LV.3]偶尔看看II

    发表于 2020-4-30 09:30:49 | 显示全部楼层 |阅读模式
    分享到:
    STM32接口篇之SPI接口
                                                            ---SPI接口操作FLASH存储芯片W25Q16初始化代码详细分析

    我的其它两篇帖子请参考:
    STM32库函数注释翻译,每个函数使用说明及使用示例
    STM32库函数翻译,每个函数说明及示例-RCC(时钟)篇(上)


    文档目的:
    学习STM32F4SPI接口和FLASH存储芯片W25Q16,通过对初始化代码分析,更好的了解STM32SPI接口编程中的一些关键知识点。

    学习素材:
    l  W25Q16数据手册
    l  电路原理图
    l  STM32F4应用手册
    针对W25Q16SPI接口初始化代码


    1 硬件电路说明
    (1)=关键原理图

    FLASH芯片接口图



    GPIO接口图


    (2)   原理图说明
    首先我们看到上面第一个图,第一个图展示了W25Q16的原理图信息,我们配合W25Q16的数据手册(第六页 见下图),我们分析具体IO的连接和功能,分析结果见下表


      
    管脚号
      
    名称
    连接的io或者信号
    功能
      
    1
      
    CS
    PB0(不复用)
    片选信号 该信号低有效  即当该信号为低电平的时候对W25Q16的操作是有效的
      
    2
      
    DO
    PB4(复用SPI1_MOSI或者SPI3_MOSI
    数据输出信号
      
    3
      
    WP
    3.3V
    写保护输入 当该信号为低电平是开启写保护  无法写入数据 从图中我们可以看出 这个管脚直接接在高电平3.3V上面 始终不开启写保护(不过做一个上拉应该会更好)
      
    5
      
    DI
    PB5(复用SPI1_MISO或者SPI3_MISO
    数据输入信号
      
    6
      
    CLK
    PB3(复用SPI1_CLK或者SPI3_CLK)
    时钟信号
    注意:配合(1)中的GPIO接口图,我们可以得到具体IO的复用方法,在上表中已经给出。

    (3)   编程需求
    通过上表的分析,我们可以得出如下编程需求:
    l  PB3(CLK),PB4(MOSI),PB5(MISO)初始化为复用功能(可以使用复用SPI接口或者SPI3接口,因为SPI1和SPI3控制器的相关引脚都在这三个IO上,见GPIO接口图)
    l  PB0初始化为普通IO,该IO的功能是在操作SPI接口的时候,拉低该IO,操作结束以后,拉高该IO。
    l  初始化配置SPI控制器,该控制器的初始化要配合W25Q16芯片手册进行。


    2 初始化程序分析(1)   代码展示
    void SPI1_Init(void)
    {                 
    GPIO_InitTypeDef  GPIO_InitStructure;         
    SPI_InitTypeDef  SPI_InitStructure;         
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB,ENABLE);//使能GPIOB时钟        
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1,ENABLE);//使能SPI1时钟         
    //GPIOFB3,4,5初始化设置         
    GPIO_InitStructure.GPIO_Pin= GPIO_Pin_3|GPIO_Pin_4|GPIO_Pin_5;        
    GPIO_InitStructure.GPIO_Mode= GPIO_Mode_AF;//复用功能        
    GPIO_InitStructure.GPIO_OType= GPIO_OType_PP;//推挽输出         
    GPIO_InitStructure.GPIO_Speed= GPIO_Speed_100MHz;//100MHz        
    GPIO_InitStructure.GPIO_PuPd= GPIO_PuPd_UP;//上拉         
    GPIO_Init(GPIOB,&GPIO_InitStructure);//初始化        
    GPIO_PinAFConfig(GPIOB,GPIO_PinSource3,GPIO_AF_SPI1);//PB3复用为 SPI1         
    GPIO_PinAFConfig(GPIOB,GPIO_PinSource4,GPIO_AF_SPI1);//PB4复用为 SPI1        
    GPIO_PinAFConfig(GPIOB,GPIO_PinSource5,GPIO_AF_SPI1);//PB5复用为 SPI1              
    RCC_APB2PeriphResetCmd(RCC_APB2Periph_SPI1,ENABLE);//复位SPI1        
    RCC_APB2PeriphResetCmd(RCC_APB2Periph_SPI1,DISABLE);//停止复位SPI1         
    SPI_InitStructure.SPI_Direction= SPI_Direction_2Lines_FullDuplex;           
    SPI_InitStructure.SPI_Mode= SPI_Mode_Master;                   //设置SPI工作模式:设置为主SPI         
    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;               //NSS信号由硬件(NSS管脚)还是软件(使用SSI位)管理:内部NSS信号有SSI位控制         SPI_InitStructure.SPI_BaudRatePrescaler= SPI_BaudRatePrescaler_256;            //定义波特率预分频的值:波特率预分频值为256         SPI_InitStructure.SPI_FirstBit= SPI_FirstBit_MSB;         //指定数据传输从MSB位还是LSB位开始:数据传输从MSB位开始         SPI_InitStructure.SPI_CRCPolynomial= 7;      //CRC值计算的多项式         
    SPI_Init(SPI1,&SPI_InitStructure);        
    SPI_Cmd(SPI1, ENABLE); //使能SPI外设         
    SPI1_ReadWriteByte(0xff);//启动传输            
    }     

    void W25QXX_Init(void){     
    GPIO_InitTypeDef  GPIO_InitStructure;   
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB,ENABLE);//使能GPIOB时钟
    //GPIOB0    GPIO_InitStructure.GPIO_Pin =GPIO_Pin_0;//PB0   
    GPIO_InitStructure.GPIO_Mode =GPIO_Mode_OUT;//输出   
    GPIO_InitStructure.GPIO_OType =GPIO_OType_PP;//推挽输出   
    GPIO_InitStructure.GPIO_Speed =GPIO_Speed_100MHz;//100MHz   
    GPIO_InitStructure.GPIO_PuPd =GPIO_PuPd_UP;//上拉   
    GPIO_Init(GPIOB, &GPIO_InitStructure);//初始化         
    W25QXX_CS=1;                           //SPI FLASH不选中        
    SPI1_Init();                                         //初始化SPI         
    SPI1_SetSpeed(SPI_BaudRatePrescaler_2);             //设置为42M时钟,高速模式         
    W25QXX_TYPE=W25QXX_ReadID();        //读取FLASH ID.
    }  
    (2)   代码分析1.  初始化代码总体分析由展示的代码,我们可以看出,对W25Q16的初始化主要使用了两个函数,主要调用函数void W25QXX_Init(void)对W25Q16进行初始化,而该函数又调用了SPI1_Init()函数对SPI1接口进行初始化。代码主要流程图如下:

    通过以上流程图,我们可以看出对W25Q16的初始化,主要是初始化SPI接口。下面对代码具体说明。
    2.  代码详细分析(我们主要分析SPI1接口的初始化函数)我们先分析函数voidSPI1_Init(void),对该函数里面的没一行代码进行详细说明l  GPIO_InitTypeDef GPIO_InitStructure;
    SPI_InitTypeDef  SPI_InitStructure;
    代码分析:首先定义了两个结构体变量,这两个结构体的类型定义在GPIO和SPI的库函数中,主要作为GPIO和SPI初始化调用的参数,我们一会详细分析。
    l  RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);//使能GPIOB时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1,ENABLE);//使能SPI1时钟
    代码分析:STM32有着强大且细分的时钟系统,我们可以单独的对每一个控制器的时钟进行使能,这样可以更好的降低功耗。因此,这两句分别使能GPIOB和SPI1的时钟。关于时钟库函数的说明,请看我的《STM32库函数说明及示例-RCC篇》
    l  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3|GPIO_Pin_4|GPIO_Pin_5;
    GPIO_InitStructure.GPIO_Mode= GPIO_Mode_AF;//复用功能         
    GPIO_InitStructure.GPIO_OType= GPIO_OType_PP;//推挽输出         
    GPIO_InitStructure.GPIO_Speed= GPIO_Speed_100MHz;//100MHz         
    GPIO_InitStructure.GPIO_PuPd= GPIO_PuPd_UP;//上拉GPIO_Init(GPIOB,&GPIO_InitStructure);

    //初始化代码分析:这几行代码主要是初始化结构体参数GPIO_InitStructure,该参数指定了GPIO的一些具体的配置,注意第二行代码,我们把PB3,PB4和PB5设置成复用功能。第三行我们采用推挽输出,可以得到更大的驱动能力。第四行我们把IO的最大翻转速度配置成100MHZ,因为考虑PB3作为SPI的时钟口,最高的频率可达42MHZ。其它关于GPIO库函数GPIO_Init(GPIOB, &GPIO_InitStructure)的详细解释,请见我的《STM32库函数说明及示例-GPIO篇》。

    l  GPIO_PinAFConfig(GPIOB,GPIO_PinSource3,GPIO_AF_SPI1); //PB3复用为 SPI1         GPIO_PinAFConfig(GPIOB,GPIO_PinSource4,GPIO_AF_SPI1);//PB4复用为
    SPI1GPIO_PinAFConfig(GPIOB,GPIO_PinSource5,GPIO_AF_SPI1);//PB5复用为 SPI1

    代码分析:以上三行实现管脚复用功能的具体配置,我们分别把PB3,PB4和PB5配置成SPI1接口的复用功能。关于该函数的详细说明,请见我的《STM32库函数说明及示例-GPIO篇》。

    l  RCC_APB2PeriphResetCmd(RCC_APB2Periph_SPI1,ENABLE);        
    RCC_APB2PeriphResetCmd(RCC_APB2Periph_SPI1,DISABLE);         对SPI1接口的时钟进行复位,以便进行SPI接口的初始化操作l  SPI_InitStructure.SPI_Direction =SPI_Direction_2Lines_FullDuplex;           
    SPI_InitStructure.SPI_Mode= SPI_Mode_Master;                   //设置SPI工作模式:设置为主SPI        
    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;      //CRC值计算的多项式         
    SPI_Init(SPI1,&SPI_InitStructure);   
       
    代码分析:这一段代码是SPI接口初始化的关键代码,在该代码中,对结构体变量SPI_InitStructure里面的成员变量进行了初始化。以下分别进行说明:
    Ø  第一行使用宏SPI_Direction_2Lines_FullDuplex将SPI接口配置成双线全双工模式,因为硬件接口使用了输入和输出两个IO,所以应该是双线模式,而且W25Q16的数据是在收发两个方向同时传输,所以是全双工模式。
    Ø  第二行设置STM32的SPI接口工作在主模式(SPI_Mode_Master)。因为读写数据的请求都是由STM32主动发出,所以是主设备。这里,大家要搞清楚主从设备的区别,通信的发起方就是主设备。
    Ø  第三行设置数据长度为8位(SPI_DataSize_8b),因为W25Q16的最小传输数据单位(或者说每个地址对应的数据是1个字节)是1个字节。1个字节是8位,所以选择8位。
    Ø  第四行和第五行分别设置极性和相位为SPI_CPOL_High和SPI_CPHA_2Edge。SPI_CPOL_High表示时钟的初始电平位高电平,SPI_CPHA_2Edge表示在第二个边沿进行数据采样(见下图1)。这样的组合是SPI数据传输模式中的模式3。根据W25Q16的数据手册,W25Q16支持MODE0(MODE0是SPI的初识电平为低电平,在第一个边沿进行数据采样配置组合是SPI_CPOL_Low,SPI_CPHA_1Edge)和MODE3。请见图2黄色部分。因此这里我们选择MODE3,当然选择MODE0也是可以的。
    图1:SPI MODE3时序图


    图2

    Ø  第六行设置SPI_NSS信号,由软件产生。因为我们没有使用硬件NSS信号,所以选择由软件产生NSS信号。大家注意我们的CS管脚是连接到普通IO上面的,因此没有使用硬件NSS。
    Ø  第七行配置SPI时钟的分频值为256(SPI_BaudRatePrescaler_256),因此实际的时钟值是APB2的时钟/256 Ø  第八行配置SPI的第一位是最高有效位(SPI_FirstBit_MSB)。这个也是和W25Q16相关的。请见下图红色箭头部分。

          
    Ø  第九行配置SPI的CRC校验多项式为7
    Ø  第十行调用SPI_Init(SPI1, &SPI_InitStructure);对SPI1接口进行初始化。
    l  SPI_Cmd(SPI1, ENABLE); //使能SPI外设SPI1_ReadWriteByte(0xff);//启动传输  
    以上第一行使能SPI接口,第二行使用SPI接口写入一个0XFF,以启动数据的传输。相当于启动数据发送器。



    SPI
    游客,如果您要查看本帖隐藏内容请回复


    回复

    使用道具 举报

  • TA的每日心情
    开心
    2024-7-21 08:16
  • 签到天数: 1515 天

    连续签到: 1 天

    [LV.Master]伴坛终老

    发表于 2020-5-2 22:04:13 | 显示全部楼层
    必须回复呀还有隐藏内容呢
    回复 支持 反对

    使用道具 举报

    该用户从未签到

    发表于 2021-6-15 17:36:38 | 显示全部楼层
    写得真好,学习学习,正好在做SPI 读写外部FLASH
    回复 支持 反对

    使用道具 举报

  • TA的每日心情
    开心
    1 小时前
  • 签到天数: 1722 天

    连续签到: 19 天

    [LV.Master]伴坛终老

    发表于 2022-8-17 08:28:15 | 显示全部楼层
    谢谢楼主分享  写得真好,学习一下
    回复 支持 反对

    使用道具 举报

    您需要登录后才可以回帖 注册/登录

    本版积分规则

    关闭

    站长推荐上一条 /4 下一条



    手机版|小黑屋|与非网

    GMT+8, 2024-11-24 11:10 , Processed in 0.215198 second(s), 35 queries , MemCache On.

    ICP经营许可证 苏B2-20140176  苏ICP备14012660号-2   苏州灵动帧格网络科技有限公司 版权所有.

    苏公网安备 32059002001037号

    Powered by Discuz! X3.4

    Copyright © 2001-2024, Tencent Cloud.