016 - STM32学习笔记 - SPI读写FLASH(一)

016 - STM32学习笔记 - SPI访问Flash(一)

之前csdn的名称是宥小稚,后来改成放学校门口见了,所以前面内容看到图片水印不要在意,都是自己学习过程中整理的,不涉及版权啥的。

1、什么是SPI?

SPI 协议是由摩托罗拉公司提出的通讯协议(Serial Peripheral Interface),即串行外围设备接口,是一种高速全双工的通信总线。

在这里插入图片描述

在SPI总线中,一共有四条线:

SCLK:同步时钟信号线,用于通讯数据同步。由通讯主机产生,决定了通讯的速率,不同的设备支持的最高时钟频率不一样,如 STM32 的 SPI 时钟频率最大为fpclk/2,两个设备之间通讯时,通讯速率由最低速率设备决定。

CS:片选信号线,或设备选择线,也称为SS、NSS,当多个SPI从机设备与SPI主机相连时,设备的其他信号线SCLK、MOSI、MISO同时并联到相同的SPI总线上,无论多少个从机设备,这三条总线都是公用的,唯独CS线都是从机设备与主机设备的一对一连接,SPI总线上有多少从机设备,就有多少根CS线。I2C中主机通过设备地址来寻址进行通讯;SPI中没有设备地址,只使用CS信号线进行片选,当主机要与某个从机设备进行通信时,则将该从机设备的CS信号线设置为低电平,就表示该设备被选中,接着就可以开始通讯了,当通讯结束后,CS线被拉高,则表示结束信号。

MOSI:Master Output,Slave Input,主设备输出/从设备输入引脚。主机的数据从这条信号线输出,从机由这条信号线读入主机发送的数据,数据方向为主机到从机

MISO:Master Input,Slave Output,主设备输入/从设备输出引脚。主机从这条信号线读入数据,从机的数据由这条信号线输出到主机,数据方向为从机到主机

2、SPI协议

在这里插入图片描述

SPI通讯过程

在SPI通讯过程中,CS、SCK、MOSI信号都是由主机产生,MISO信号是由从机产生,其中MOSI和MISO信号只有在CS片选信号为低电平时有效,在SCLK每个信号周期,MOSI和MISO都可以传输一位数据,因此SPI总线可以同时收发数据。

a、通讯起始与结束信号:

根据SPI通讯过程的图来看,当SPI总线无通信时,CS片选信号线一直保持高电平,当开始通信时,CS信号线跳变为低电平,此时SCLK、MOSI、MISO信号线上的信号才有效,通信结束后,CS跳变为高电平,表示此次通信结束了。

b、有效数据:

在SPI中,当CS片选信号低电平时,MOSI和MISO数据线在SCLK的每个时钟周期都会传输一位数据,并且MOSI和MISO上的信号时同时进行传输的,MSB(高位)先行或LSB(地位)先行并没有硬性规定,但要保证两个SPI设备之间使用同样的协定,就跟上图一样,采用MSB先行模式了。

c、CPOL/CPHA

下面要学习关于SPI的四种通讯模式,上图中的时序时SPI的其中一种通讯模式,四种通讯模式的主要区别在于总线空闲时,SCLK的时钟状态以及数据采样时刻。在此之前先看一下CPOL(时钟极性)和CPHA(时钟相位)。

CPOL:是指SPI通讯设备处于空闲状态时,SCK信号线的的电平信号(就是CS片选信号是高电平时,SCLK的状态),当CS信号线为高电平时,CPOL = 0,SCLK则为低电平;CPOL = 1,SCLK则为高电平;

CPHA:时钟相位实际指的就是数据采样的时间,当CPHA = 0的时候,采样信号在SCLK时钟线的奇数边沿,当CPHA = 1的时候,采样信号则是在SCLK时钟线的偶数边沿。

在这里插入图片描述

CPHA = 0

在这里插入图片描述

CPHA = 1

所以根据CPOL和CPHA的不同状态可以组合出四种模式,如下表

模式CPOLCPHA空闲时SCLK采样时刻
000低电平奇数边沿
101低电平偶数边沿
210高电平奇数边沿
311高电平偶数边沿

在实际应用中,模式0模式3 用的比较多。

3、SPI框图

在这里插入图片描述

a、SPI引脚

前面已经介绍过SPI的四个引脚了,这里不多做赘述,主要看一下四个引脚在F429里面的分布,F429中,SPI一共由6组,其中SPI1、SPI4、SPI5、SPI6挂载在APB2总线上,最高通信速率可以达到45MBtis/s,SPI2、SPI3是挂载在APB1上,最高通信速率为22.5MBtis/s。

在这里插入图片描述

引脚SPI1 SPI2 SPI3 SPI4 SPI5 SPI6
MOSIPA7/PB5PB15/PC3/PI3PB5/PC12/PD6PE6/PE14PF9/PF11PG14
MISOPA6/PB4PB14/PC2/PI2PB4/PC11PE5/PE13PF8/PH7PG12
SCKPA5/PB3PB10/PB13/PD3PB3/PC10PE2/PE12PF7/PH6PG13
NSSPA4/PA15PB9/PB12/PI0PA4/PA15PE4/PE11PF6/PH5PG8
SPI引脚表

b、时钟控制逻辑

SPI的时钟信号由SCLK线发出,内部通过波特率发生器根据控制寄存器CR1中的BR[2:0]位控制,该位是对fpclk时钟的分频因子,对fpclk的分频结果就是SCLK引脚输出的频率。

在这里插入图片描述

BR[2:0]分频结果(SCLK频率)BR[2:0]分频结果(SCLK频率)
000fpclk/2100fpclk/32
001fpclk/4101fpclk/64
010fpclk/8110fpclk/128
011fpclk/16111fpclk/256

c、数据控制逻辑

在SPI框图中可以看到,SPI的MOSI和MISO都是连接到数据移位寄存器上,而数据移位寄存器中的数据,来源于接收缓冲区、发送缓冲区以及MOSI和MISO线,向外发送数据时,数据向外发送时,数据移位寄存器将“发送缓冲区”的数据作为数据源,将数据按位发送出去;接收数据时,数据移位寄存器从MISO数据线上将数据按位通过“数据移位寄存器”采集到“接收缓冲区”。

此处需要注意,数据帧的长度可以通过控制寄存器CR1DFF[11]位配置成8位或16位模式,通过配置“LSBFIRST [7]位可选择 MSB 先行还是 LSB 先行。

4、SPI结构体

SPI结构体声明在stm32f4xx_spi.h中,内容如下:

typedef struct
{uint16_t SPI_Direction;           /* SPI单、双向模式 */uint16_t SPI_Mode;                /* SPI主/从机模式 */uint16_t SPI_DataSize;            /* SPI数据帧长度 */uint16_t SPI_CPOL;                /* 时钟极性CPOL设置,可选高/低电平 */uint16_t SPI_CPHA;                /* 时钟相位CPHA设置,可选奇/偶边沿采样 */uint16_t SPI_NSS;                 /* 设置CS引脚由SPI硬件控制还是软件控制 */uint16_t SPI_BaudRatePrescaler;   /* 设置时钟分频因子,fpclk/分频=fsck */uint16_t SPI_FirstBit;            /* 设置MSB/LSB先行 */uint16_t SPI_CRCPolynomial;       /* 设置CRC校验的表达式 */
}SPI_InitTypeDef;

a、SPI单双向选择(SPI_Direction):

在固件库中提供了以下四种模式

  • 双线全双工(SPI_Direction_2Lines_FullDuplex)
  • 双线只接收(SPI_Direction_2Lines_RxOnly)
  • 单线只接收(SPI_Direction_1Line_Rx)
  • 单线只发送模式(SPI_Direction_1Line_Tx)

b、SPI片选信号控制选择(SPI_NSS)

可选有两种模式:

  • 硬件模式(SPI_NSS_Hard )
  • 软件模式(SPI_NSS_Soft )

其中硬件模式中的片选信号是由SPI硬件自动产生,而软件模式则是由用户将GPIO的引脚电平拉高或者拉低产生信号,常用的为软件模式。

5、编程测试

在这里插入图片描述

在F429的核心板上可以看到,PF6、7、8、9引脚分别对应SPI的CS(片选)、CLK(时钟)、MOSI(输入)、MISO(输出)功能,另外Flash还有两个引脚WP(写保护控制)和HOLD(暂停通讯控制),当WP为低电平时,禁止数据写入,HOLD为低电平时,通讯暂停,MISO输出为高阻态,时钟和输入无效,这里用不到,所以直接接电源拉成高电平。

相关宏定义,在bsp_spi_flash.h中

//SPI 号及时钟初始化函数
#define FLASH_SPI                       SPI5									//根据SPI引脚表可以看到PF6-9都是挂载在SPI5下
#define FLASH_SPI_CLK                   RCC_APB2Periph_SPI5						 //SPI5时钟
//SCK 引脚PF7
#define FLASH_SPI_SCK_PIN               GPIO_Pin_7
#define FLASH_SPI_SCK_GPIO_PORT         GPIOF
#define FLASH_SPI_SCK_GPIO_CLK          RCC_AHB1Periph_GPIOF
#define FLASH_SPI_SCK_PINSOURCE         GPIO_PinSource7
#define FLASH_SPI_SCK_AF                GPIO_AF_SPI5
//MISO 引脚PF8
#define FLASH_SPI_MISO_PIN              GPIO_Pin_8
#define FLASH_SPI_MISO_GPIO_PORT        GPIOF
#define FLASH_SPI_MISO_GPIO_CLK         RCC_AHB1Periph_GPIOF
#define FLASH_SPI_MISO_PINSOURCE        GPIO_PinSource8
#define FLASH_SPI_MISO_AF               GPIO_AF_SPI5
//MOSI 引脚PF9
#define FLASH_SPI_MOSI_PIN              GPIO_Pin_9
#define FLASH_SPI_MOSI_GPIO_PORT        GPIOF
#define FLASH_SPI_MOSI_GPIO_CLK         RCC_AHB1Periph_GPIOF
#define FLASH_SPI_MOSI_PINSOURCE        GPIO_PinSource9
#define FLASH_SPI_MOSI_AF               GPIO_AF_SPI5
//CS(NSS)引脚PF6
#define FLASH_CS_PIN                    GPIO_Pin_6
#define FLASH_CS_GPIO_PORT              GPIOF
#define FLASH_CS_GPIO_CLK               RCC_AHB1Periph_GPIOF#define SPI_FLASH_CS_LOW()              {FLASH_CS_GPIO_PORT->BSRRH=FLASH_CS_PIN;} 	/*控制CS片选信号输出低电平*/
#define SPI_FLASH_CS_HIGH()             {FLASH_CS_GPIO_PORT->BSRRL=FLASH_CS_PIN;}	/*控制CS片选信号输出高电平*/

相关GPIO配置及SPI的配置和初始化

void SPI_GPIO_Config(void)
{GPIO_InitTypeDef SPI_GPIO_Initstructure;SPI_InitTypeDef SPI_InitStructure;/*  SPI四个引脚时钟使能,只要是外设,第一步一定是开启时钟!!! */RCC_AHB1PeriphClockCmd(FLASH_SPI_SCK_GPIO_CLK | FLASH_SPI_MISO_GPIO_CLK|FLASH_SPI_MOSI_GPIO_CLK|FLASH_CS_GPIO_CLK, ENABLE);/* 将SCLK、MISO、MOSI引脚都连接到复用功能,CS片选信号由软件控制,不需要 */GPIO_PinAFConfig(FLASH_SPI_SCK_GPIO_PORT,FLASH_SPI_SCK_PINSOURCE,FLASH_SPI_SCK_AF);GPIO_PinAFConfig(FLASH_SPI_MISO_GPIO_PORT,FLASH_SPI_MISO_PINSOURCE,FLASH_SPI_MISO_AF);GPIO_PinAFConfig(FLASH_SPI_MOSI_GPIO_PORT,FLASH_SPI_MOSI_PINSOURCE,FLASH_SPI_MOSI_AF);/* 配置SCLK信号引脚 */SPI_GPIO_Initstructure.GPIO_Pin = FLASH_SPI_SCK_PIN;SPI_GPIO_Initstructure.GPIO_Speed = GPIO_Speed_50MHz;SPI_GPIO_Initstructure.GPIO_Mode = GPIO_Mode_AF;SPI_GPIO_Initstructure.GPIO_OType = GPIO_OType_PP;SPI_GPIO_Initstructure.GPIO_PuPd = GPIO_PuPd_NOPULL;/* 初始化SCLK引脚 */GPIO_Init(FLASH_SPI_SCK_GPIO_PORT, &SPI_GPIO_Initstructure);/* 配置并初始化MISO引脚 */SPI_GPIO_Initstructure.GPIO_Pin = FLASH_SPI_MISO_PIN;GPIO_Init(FLASH_SPI_MISO_GPIO_PORT, &SPI_GPIO_Initstructure);/* 配置并初始化MOSI引脚 */SPI_GPIO_Initstructure.GPIO_Pin = FLASH_SPI_MOSI_PIN;GPIO_Init(FLASH_SPI_MOSI_GPIO_PORT, &SPI_GPIO_Initstructure);/* 配置CS引脚为输出模式并初始化,这里片选由软件自行控制,因此此处模式选择为输出 */SPI_GPIO_Initstructure.GPIO_Pin = FLASH_CS_PIN;SPI_GPIO_Initstructure.GPIO_Mode = GPIO_Mode_OUT;GPIO_Init(FLASH_CS_GPIO_PORT, &SPI_GPIO_Initstructure);/* 将CS片选信号置位高电平,非选中状态 */SPI_FLASH_CS_HIGH();/*------------------------以下为SPI配置内容------------------------*//* SPI时钟使能,只要是外设,第一步一定是开启时钟!!! *//* PF6-9是挂载在SPI5上,因此使能SPI5的时钟 */RCC_APB2PeriphClockCmd(FLASH_SPI_CLK, ENABLE);/* 配置SPI为双线全双工 */SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;/* 配置SPI为主机模式 */SPI_InitStructure.SPI_Mode = SPI_Mode_Master;/* 设置数据帧为8bit */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;/* 设置为2分频 *//*F429的主频是180MHz,那么APB2的时钟fpclk1就是90MHz,而SP1、4、5、6最高频率为45MHz,因此需要2分频;同理如果要用到SPI2、3,最高频率为22.5MHz,	而SPI2、3挂载在AP1,总线时钟为45MHz,如果要达到最高速度,同样要2分频需要注意的是,此处SPI的最大速度取决于总线中设备通讯速率最低的设备 */SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_2;/* 设置MSB先行 */SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;/* 设置CRC校验多项式,保证通信质量,大于1就行 */SPI_InitStructure.SPI_CRCPolynomial = 7;/* 初始化SPI */SPI_Init(FLASH_SPI, &SPI_InitStructure);/* 使能 FLASH_SPI */SPI_Cmd(FLASH_SPI, ENABLE);
}

首先测试一下通过SPI读取FALSH的Device_ID号,根据FALSH手册,可以看到设备ID是0xEF4018,如下图

在这里插入图片描述

另外在看一下SPI的指令表,在指令表中,可以看到查看FALSH的指令为0x9F:

在这里插入图片描述

如此根据指令表编写读取FALSH的设备ID,可以看到,读取设备ID时,需要下发指令0x9F,之后Byte2-Byte4中的内容组合则为设备ID,时序如下图:

在这里插入图片描述

u32 SPI_FLASH_ReadID(void)
{u8 temp[3] = {0x00,0x00,0x00};/* 开始通讯:拉低片选信号CS */SPI_FLASH_CS_LOW();/* 发送 JEDEC 指令,读取 ID */SPI_FLASH_SendByte(0x9F);/* 读取一个字节数据 */temp[0] = SPI_FLASH_SendByte(Dummy_Byte);/* 读取一个字节数据 */temp[1] = SPI_FLASH_SendByte(Dummy_Byte);/* 读取一个字节数据 */temp[2] = SPI_FLASH_SendByte(Dummy_Byte);/* 停止通讯:拉高片选信号CS */SPI_FLASH_CS_HIGH();/* 组合数据返回 */return (temp[0] << 16) | (temp[1] << 8) | temp[2];
}

这段程序中,如果直接使用库函数SPI_I2S_ReceiveData去读取数据的话,发现读取回来的数据不是我们要的0xEF4018,因为在每次数据发送完成后,需要等待发送缓冲区为空时,才可以发送下一个要发送的数据,因此跟I2C的一样,我们需要去检测一下发送缓冲区是否为空,当发送缓冲区为空时,硬件会将TXE标志置1,因此需要调用SPI_GetFlagStatus去获取TXE状态,为此在这里实现SPI_FLASH_SendByte来尝试读取设备ID:

u8 SPI_FLASH_SendByte(u8 byte)
{SPITimeout = SPIT_FLAG_TIMEOUT;/* 等待发送缓冲区为空, TXE 事件 */while (SPI_GetFlagStatus(FLASH_SPI, SPI_I2S_FLAG_TXE) == RESET){if ((SPITimeout--) == 0) return SPI_TIMEOUT_UserCallback(0);}/* 写入数据寄存器,把要写入的数据写入发送缓冲区 */SPI_I2S_SendData(FLASH_SPI, byte);SPITimeout = SPIT_FLAG_TIMEOUT;/* 等待接收缓冲区非空, RXNE 事件 */while (SPI_GetFlagStatus(FLASH_SPI, SPI_I2S_FLAG_RXNE) == RESET){if ((SPITimeout--) == 0) return SPI_TIMEOUT_UserCallback(1);}/* 读取数据寄存器,获取接收缓冲区数据 */return SPI_I2S_ReceiveData(FLASH_SPI);
}

实现方法有点类似I2C,代码大家看一下I2C那节的内容就能理解,关于超时检测和SPI_TIMEOUT_UserCallback回调函数的实现也可以看一下I2C那节的内容。

到这里在到main函数中测试一下:

#include "stm32f4xx.h"
#include "bsp_led.h"
#include "bsp_systick.h"
#include "bsp_usart_dma.h"
#include "bsp_spi_flash.h"
#include <stdio.h>int main(void)
{u32 device_id = 0;LED_Config();DEBUG_USART1_Config();SysTick_Init();  SPI_GPIO_Config();printf("这是SPI读取FLASH_Device_ID的测试实验!\n");device_id = SPI_FLASH_ReadID();printf("device_id = 0x%X\n",device_id);while(1){if(device_id == 0xef4018){LED_G_TOGGLEDelay_ms(1000);}else{LED_R_TOGGLEDelay_ms(1000);}}
}

在这里插入图片描述

OK,这节内容学习完毕!

根据上述内容,总给下SPI的操作顺序:

1、配置SPI对应的四个GPIO;(只要是外设,第一步一定要先打开时钟!!!)

2、将三个GPIO连接到SPI复用功能(CS引脚我们用软件自己控制,就不需要连接了!)

3、配置SPI相关参数(模式按照模式0或者模式3来配置,速度按照SPI最大速度来执行,记得要打开SPI的时钟!)

4、编写发送和读取数据的功能函数。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/15581.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

LabVIEW FPGA利用响应式数字电子板快速开发空间应用程序

LabVIEW FPGA利用响应式数字电子板快速开发空间应用程序 与传统的基于文本的语言相比&#xff0c;LabVIEW的编程和设计已被证明可以缩短开发时间。各种研究表明&#xff0c;生产率的提高在3到10倍之间。LabVIEW通过图形语言、集成开发环境和多个编译器的组合来实现这一点。 图…

Gateway服务集成Nacos2021.0.4错误解决

问题 gateway服务集成nacos&#xff0c;启动后报错&#xff1a; Caused by: com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.netty.channel.AbstractChannel$AnnotatedConnectException: Connection refused: no further information:; 版本&#xff1a; jdk:1.8 spring-b…

营销人累了看看这5部影片吧!保你再燃激情

市场瞬息万变&#xff0c;做营销需不断学习充电&#xff0c;除了看书听课之外看电影也是学习营销的有效方式。今天小马识途营销顾问给大家推荐5部市场营销人员必看的高评分电影&#xff0c;相信看完之后&#xff0c;会对你今后的发展影响深远&#xff01;话不多说直接上干货&am…

C++常用库函数 3.数据转换函数

函数名&#xff1a;abs 函数原型&#xff1a;int abs(int n)&#xff1b; 参数&#xff1a;n 整数值。 所需头文件&#xff1a;<cstdlib> 功能&#xff1a;求绝对值。 返回值&#xff1a;返回 n 的绝对值。函数名&#xff1a;atof&#xff0c;atoi&#xff0c;atol …

spring.profiles的使用详解

本文来说下spring.profiles.active和spring.profiles.include的使用与区别 文章目录 业务场景spring.profiles.active属性启动时指定 spring.profiles.include属性配置方法配置位置配置区别 用示例来使用和区分测试一测试二测试三 本文小结 业务场景 我们在开发Spring Boot应用…

Oracle之Scott用户

Oracle增删改查&#xff0c;事务与序列 前言 1、解锁scott用户 2、雇员表&#xff08;emp&#xff09; 3、部门表&#xff08;dept&#xff09; 4、工资等级表&#xff08;salgrade&#xff09;了解 5、奖金表&#xff08;bonus&#xff09;了解 1、解锁scott用户 --解锁scot…

API安全基础理论

1.什么是API API(Application Programming Interface,应用程序编程接口)是一些预先定义的函数&#xff0c;目的是提供应用程序与开发人员基于某软件或硬件的以访问一组例程的能力&#xff0c;而又无需访问源码&#xff0c;或理解内部工作机制的细节。通过淘宝API&#xff0c;就…

Python获取指定路径下所有文件的绝对路径

import osdef get_file_path_by_name(file_dir, format.JPG):获取指定路径下所有文件的绝对路径:param file_dir::return:L []for root, dirs, files in os.walk(file_dir): # 获取所有文件for file in files: # 遍历所有文件名if os.path.splitext(file)[1] format: L.ap…

Vue组件库Element-常见组件-表格

对于Element组件的使用&#xff0c;最主要的就是明确自己想要达到的效果&#xff0c;从官网中将对应代码复制粘贴即可&#xff0c;最重要的是要读懂不同组件官网中提供的文档&#xff0c;以便实现自己想要的效果 常见组件-表格 Table&#xff1a;表格&#xff1a;用于展示多条…

JAVA leetCode 13. 罗马数字转整数

方法一&#xff1a;1.通过hashmap来保存字符与数字之间的关系 2&#xff1a;根据罗马数字转整数的特点&#xff0c;当前字符比右边的字符小并且不是最后一个字符就说明在计算总数时该字符的符号是负&#xff0c;反之即为正 代码展示&#xff1a; public static int romanToIn…

数据结构之图

7 图的存储 &#xff08;1&#xff09;图的邻接矩阵存储 对于无向图&#xff0c;邻接矩阵第i行/列上非零元素个数是顶点vi的度。 对于有向图&#xff0c;邻接矩阵第i行上非零元素个数是顶点vi的出度&#xff0c;第i列 上非零元素个数是顶点vi的入度。 对于带权有向图有边则…

如何用Stable Diffusion模型生成个人专属创意名片?

目录 1 什么是二维码&#xff1f;2 什么是扩散模型&#xff1f;3 Stable Diffusion环境搭建4 开始制作创意名片结语 1 什么是二维码&#xff1f; 二维码是一种用于存储和传输信息的方便而广泛使用的图像编码技术。它是由黑色方块和白色空白区域组成的二维图形&#xff0c;可以…