博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
STM32 SPI 通信
阅读量:5166 次
发布时间:2019-06-13

本文共 6843 字,大约阅读时间需要 22 分钟。

SPI  是英语 Serial Peripheral interface 的缩写,顾名思义就是串行外围设备接口。是 Motorola首先在其 MC68HCXX 系列处理器上定义的。 SPI 接口主要应用在  EEPROM, FLASH,实时时钟,AD 转换器,还有数字信号处理器和数字信号解码器之间。SPI,是一种高速的,全双工,同步的通信总线,并且在芯片的管脚上只占用四根线,节约了芯片的管脚,同时为 PCB 的布局上节省空间,提供方便,正是出于这种简单易用的特性,现在越来越多的芯片集成了这种通信协议,STM32 也有 SPI 接口。

SPI 接口一般使用 4 条线通信:

MISO  主设备数据输入,从设备数据输出。

MOSI  主设备数据输出,从设备数据输入。

SCLK 时钟信号,由主设备产生。

CS 从设备片选信号,由主设备控制。

SPI 主要特点有:可以同时发出和接收串行数据;可以当作主机或从机工作;提供频率可编程时钟;发送结束中断标志;写冲突保护;总线竞争保护等。

STM32 的 SPI 功能很强大,SPI 时钟最多可以到 18Mhz,支持 DMA,可以配置为 SPI 协议或者 I2S 协议

使用 STM32 的 SPI2 的主模式,下面就来看看 SPI2 部分的设置步骤吧。SPI 相关的库函数和定义分布在文件 stm32f10x_spi.c 以及头文件 stm32f10x_spi.h 中。STM32 的主模式配置步骤如下:

1)配置相关引脚的复用功能,使能 SPI2 时钟

我们要用 SPI2,第一步就要使能 SPI2 的时钟。其次要设置 SPI2 的相关引脚为复用输出,这样才会连接到 SPI2 上否则这些 IO 口还是默认的状态,也就是标准输入输出口。这里我们使用的是 PB13、14、15 这 3 个(SCK.、MISO、MOSI,CS 使用软件管理方式),所以设置这三个为复用 IO。

GPIO_InitTypeDef GPIO_InitStructure;

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE );//PORTB 时钟使能 

RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2, ENABLE );//SPI2 时钟使能   

GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13 | GPIO_Pin_14 | GPIO_Pin_15;

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;   //PB13/14/15 复用推挽输出 

GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

GPIO_Init(GPIOB, &GPIO_InitStructure);//初始化 GPIOB

2)初始化 SPI2,设置 SPI2 工作模式

接下来我们要初始化 SPI2,设置 SPI2 为主机模式,设置数据格式为 8 位,然设置 SCK 时钟极性及采样方式。并设置 SPI2 的时钟频率(最大 18Mhz),以及数据的格式(MSB 在前还是LSB 在前)。这在库函数中是通过 SPI_Init 函数来实现的。

void SPI_Init(SPI_TypeDef* SPIx, SPI_InitTypeDef* SPI_InitStruct);

跟其他外设初始化一样,第一个参数是 SPI 标号,这里我们是使用的 SPI2。下面我们来看看第二个参数结构体类型 SPI_InitTypeDef 的定义:

typedef struct

{

uint16_t SPI_Direction; // 设置 SPI 的通信方式,可以选择为半双工,全双工,以及串行发和串行收方式

uint16_t SPI_Mode; // 设置 SPI 的主从模式

uint16_t SPI_DataSize; // 为 8 位还是 16 位帧格式选择项

uint16_t SPI_CPOL; // 设置时钟极性

uint16_t SPI_CPHA; // 设置时钟相位

uint16_t SPI_NSS;   //设置 NSS 信号由硬件(NSS 管脚)还是软件控制

uint16_t SPI_BaudRatePrescaler;  //设置 SPI 波特率预分频值

uint16_t SPI_FirstBit;    //设置数据传输顺序是 MSB 位在前还是 LSB 位在前

uint16_t SPI_CRCPolynomial; //设置 CRC 校验多项式,提高通信可靠性,大于 1 即可

}SPI_InitTypeDef;

结构体成员变量比较多,这里我们挑取几个重要的成员变量说一下:

第一个参数 SPI_Direction 是用来设置 SPI 的通信方式,可以选择为半双工,全双工,以及串行发和串行收方式,这里我们选择全双工模式 SPI_Direction_2Lines_FullDuplex。

第二个参数 SPI_Mode 用来设置 SPI 的主从模式,这里我们设置为主机模式 SPI_Mode_Master,当然有需要你也可以选择为从机模式 SPI_Mode_Slave。

第三个参数 SPI_DataSiz 为 8 位还是 16 位帧格式选择项,这里我们是 8 位传输,选择SPI_DataSize_8b。

第四个参数 SPI_CPOL 用来设置时钟极性,我们设置串行同步时钟的空闲状态为高电平所以我们选择 SPI_CPOL_High。

第五个参数 SPI_CPHA 用来设置时钟相位,也就是选择在串行同步时钟的第几个跳变沿(上升或下降)数据被采样,可以为第一个或者第二个条边沿采集,这里我们选择第二个跳变沿,所以选择 SPI_CPHA_2Edge

第六个参数 SPI_NSS 设置 NSS 信号由硬件(NSS 管脚)还是软件控制,这里我们通过软件控制 NSS 关键,而不是硬件自动控制,所以选择 SPI_NSS_Soft。

第七个参数 SPI_BaudRatePrescaler 很关键,就是设置 SPI 波特率预分频值也就是决定 SPI 的时钟的参数 , 从不分频道 256 分频 8 个可选值,初始化的时候我们选择 256 分频值SPI_BaudRatePrescaler_256,  传输速度为 36M/256=140.625KHz。

第八个参数 SPI_FirstBit 设置数据传输顺序是 MSB 位在前还是 LSB 位在前, ,这里我们选择SPI_FirstBit_MSB 高位在前。

第九个参数 SPI_CRCPolynomial 是用来设置 CRC 校验多项式,提高通信可靠性,大于 1 即可。

设置好上面 9 个参数,我们就可以初始化 SPI 外设了。初始化的范例格式为:

SPI_InitTypeDef   SPI_InitStructure;

SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;   //双线双向全双工

SPI_InitStructure.SPI_Mode = SPI_Mode_Master;    //主 SPI

SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;  // SPI 发送接收 8 位帧结构

SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;//串行同步时钟的空闲状态为高电平 

SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;//第二个跳变沿数据被采样

SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;  //NSS 信号由软件控制

SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_256;  //预分频 256

SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;  //数据传输从 MSB 位开始

SPI_InitStructure.SPI_CRCPolynomial = 7;  //CRC 值计算的多项式

SPI_Init(SPI2, &SPI_InitStructure);   //根据指定的参数初始化外设 SPIx 寄存器

3)使能 SPI2

初始化完成之后接下来是要使能 SPI2 通信了,在使能 SPI2 之后,我们就可以开始 SPI 通讯了。使能 SPI2 的方法是:

SPI_Cmd(SPI2, ENABLE); //使能 SPI 外设

4)SPI 传输数据

通信接口当然需要有发送数据和接受数据的函数,固件库提供的发送数据函数原型为:

void SPI_I2S_SendData(SPI_TypeDef* SPIx, uint16_t Data);

这个函数很好理解,往 SPIx 数据寄存器写入数据  Data,从而实现发送。

固件库提供的接受数据函数原型为:

uint16_t SPI_I2S_ReceiveData(SPI_TypeDef* SPIx)  ;

这个函数也不难理解,从 SPIx 数据寄存器读出接受到的数据。

5)查看 SPI 传输状态

在 SPI 传输过程中,我们经常要判断数据是否传输完成,发送区是否为空等等状态,这是通过函数 SPI_I2S_GetFlagStatus 实现的,这个函数很简单就不详细讲解,判断发送是否完成的方法是:

SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_RXNE);

 

接下来介绍一下 W25Q64。W25Q64 是华邦公司推出的大容量SPI  FLASH 产品,W25Q64 的容量为 64Mb,该系列还有 W25Q80/16/32 等。ALIENTEK 所选择的 W25Q64 容量为 64Mb,也就是 8M 字节。

W25Q64 将 8M 的容量分为 128 个块(Block),每个块大小为 64K 字节,每个块又分为 16个扇区(Sector),每个扇区 4K 个字节。W25Q64 的最少擦除单位为一个扇区,也就是每次必须擦除 4K 个字节。这样我们需要给 W25Q64 开辟一个至少 4K 的缓存区,这样对 SRAM 要求比较高,要求芯片必须有 4K 以上 SRAM 才能很好的操作。

W25Q64 的擦写周期多达 10W 次,具有 20 年的数据保存期限,支持电压为 2.7~3.6V,W25Q64 支持标准的 SPI,还支持双输出/四输出的 SPI,最大 SPI 时钟可以到 80Mhz(双输出时相当于 160Mhz,四输出时相当于 320M),更多的 W25Q64 的介绍,请参考 W25Q64 的DATASHEET。

 

    1. /**
    2.  * 以下是SPI模块的初始化代码,配置成主机模式,访问SD Card/W25Q64/NRF24L01                         
    3.  * SPI口初始化
    4.  * 这里针是对SPI2的初始化
    5.  */
    6. void SPI2_Init(void)
    7. {
    8.      GPIO_InitTypeDef GPIO_InitStructure;
    9.       SPI_InitTypeDef SPI_InitStructure;
    10.     
    11.     RCC_APB2PeriphClockCmd(    RCC_APB2Periph_GPIOB, ENABLE );//PORTB时钟使能 
    12.     RCC_APB1PeriphClockCmd(    RCC_APB1Periph_SPI2, ENABLE );//SPI2时钟使能     
    13.  
    14.     GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13 | GPIO_Pin_14 | GPIO_Pin_15;
    15.     GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //PB13/14/15复用推挽输出 
    16.     GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    17.     GPIO_Init(GPIOB, &GPIO_InitStructure);//初始化GPIOB
    18.      GPIO_SetBits(GPIOB,GPIO_Pin_13|GPIO_Pin_14|GPIO_Pin_15); //PB13/14/15上拉
    19.     
    20.     SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex; //设置SPI单向或者双向的数据模式:SPI设置为双线双向全双工
    21.     SPI_InitStructure.SPI_Mode = SPI_Mode_Master;        //设置SPI工作模式:设置为主SPI
    22.     SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;        //设置SPI的数据大小:SPI发送接收8位帧结构
    23.     SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;        //串行同步时钟的空闲状态为高电平
    24.     SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;    //串行同步时钟的第二个跳变沿(上升或下降)数据被采样
    25.     SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;        //NSS信号由硬件(NSS管脚)还是软件(使用SSI位)管理:内部NSS信号有SSI位控制
    26.     SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_256;        //定义波特率预分频的值:波特率预分频值为256
    27.     SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;    //指定数据传输从MSB位还是LSB位开始:数据传输从MSB位开始
    28.     SPI_InitStructure.SPI_CRCPolynomial = 7;    //CRC值计算的多项式
    29.     SPI_Init(SPI2, &SPI_InitStructure); //根据SPI_InitStruct中指定的参数初始化外设SPIx寄存器
    30.  
    31.     SPI_Cmd(SPI2, ENABLE); //使能SPI外设
    32.     
    33.     SPI2_ReadWriteByte(0xff);//启动传输        
    34.     
    35. }
    36. //SPI 速度设置函数
    37. //SpeedSet:
    38. //SPI_BaudRatePrescaler_2 2分频 
    39. //SPI_BaudRatePrescaler_8 8分频 
    40. //SPI_BaudRatePrescaler_16 16分频 
    41. //SPI_BaudRatePrescaler_256 256分频 
    42.   
    43. void SPI2_SetSpeed(u8 SPI_BaudRatePrescaler)
    44. {
    45.       assert_param(IS_SPI_BAUDRATE_PRESCALER(SPI_BaudRatePrescaler));
    46.     SPI2->CR1&=0XFFC7;
    47.     SPI2->CR1|=SPI_BaudRatePrescaler;    //设置SPI2速度 
    48.     SPI_Cmd(SPI2,ENABLE); 
    49. //SPIx 读写一个字节
    50. //TxData:要写入的字节
    51. //返回值:读取到的字节
    52. u8 SPI2_ReadWriteByte(u8 TxData)
    53. {
              
    54.     u8 retry=0;                     
    55.     while (SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_TXE) == RESET) //检查指定的SPI标志位设置与否:发送缓存空标志位
    56.         {
    57.         retry++;
    58.         if(retry>200)return 0;
    59.         }             
    60.     SPI_I2S_SendData(SPI2, TxData); //通过外设SPIx发送一个数据
    61.     retry=0;
    62.     while (SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_RXNE) == RESET) //检查指定的SPI标志位设置与否:接受缓存非空标志位
    63.         {
    64.         retry++;
    65.         if(retry>200)return 0;
    66.         }                              
    67.     return SPI_I2S_ReceiveData(SPI2); //返回通过SPIx最近接收的数据                     
    68. }

转载于:https://www.cnblogs.com/todd1992/p/3784251.html

你可能感兴趣的文章
一些命令的记录
查看>>
转载:Debugging OpenJDK 8 with NetBeans on Ubuntu
查看>>
真机调试
查看>>
leetcode 34 Search for a Range
查看>>
hdu5375 Gray code(DP)
查看>>
TI C66x DSP 系统events及其应用 - 5.9(IST重定位)
查看>>
java导出csv、excel
查看>>
BZOJ.3546.[ONTAK2010]Life of the Party(二分图匹配 ISAP)
查看>>
Codeforces.567E.President and Roads(最短路 Dijkstra)
查看>>
BZOJ.1758.[WC2010]重建计划(分数规划 点分治 单调队列/长链剖分 线段树)
查看>>
Ext.data.SimpleStore的使用方法
查看>>
[数据分析]excel带名称的四象限散点图制作
查看>>
BZOJ 1997[Hnoi2010]Planar
查看>>
Nginx与Ribbon的区别
查看>>
Kafka部署与代码实例(转)
查看>>
sql中not exists的用法
查看>>
【转】UGUI研究院之Mask裁切UI粒子特效或者3D模型(十七)
查看>>
PAT_B_1088_三人行
查看>>
05 数字 - 《Python 核心编程》
查看>>
HTML学习----------DAY2第四节
查看>>