STM32---SPI通信协议(小白入、含源码)

**写在前面:**在单片机的学习过程中,各种通信协议的学习是必不可少的,在前面我们学习了串口通信、IIC通信,本节我们来认识一下SPI通信协议。包括其SPI基本概念、NORFLASH芯片的介绍以及相关的例程实验。

目录

一、SPI介绍

1.1什么是SPI?

1.2SPI结构框图

1.3SPI工作模式

1.4SPI工作寄存器

二、NORFLASH介绍

[2.1NOR FLASH简介](#2.1NOR FLASH简介)

2.2NM25Q128简介

[2.3NOR FLASH的工作时序](#2.3NOR FLASH的工作时序)

三、硬件设计

四、程序设计

4.1SPI配置步骤

4.2NM25Q128配置步骤

4.3相关代码

五、现象

一、SPI介绍

1.1什么是SPI?

SPI全称为: Serial Peripheral interface;其中文翻译为:串行外设设备接口,是一种高速的、全双工的、同步的通信总线;

我们前面学习了IIC通信,STM32---IIC通信协议(含源码,小白进)_stm32 iic-CSDN博客再此,将SPI同IIC做出对比:

|----------|------------------|----------------------|
| | SPI | IIC |
| 通信方式 | 同步、串行、全双工 | 同步、串行、半双工 |
| 总线接口 | MOSI、MISO、SCL、CS | SDA、SCL |
| 拓扑结构 | 一主多从、一主一从 | 多主从 |
| 从机选择 | 片选引脚 | SDA设备地址片选 |
| 通信速率 | 一般500MHz以下 | 100KHz、400KHz、3.4MHz |
| 数据格式 | 8/16位 | 8位 |
| 传输顺序 | MSB/LSB | MSB |

1.2SPI结构框图

相关引脚:

MOSI:输出数据线;

MISO:输入数据线;

SCK:时钟线,由主设备产生。

NSS/CS:从设备片选信号,由主设备产生。

STM32F103引脚对应SPI:

|----------|----------|----------|----------|
| 引脚 | SPI1 | SPI2 | SPI3 |
| NSS | PA4 | PB12 | PA15 |
| CLK | PA5 | PB13 | PB3 |
| MISO | PA6 | PB14 | PB4 |
| MOSI | PA7 | PB15 | PB5 |

SPI工作原理:

在从机与主机内部都含有一个移位寄存器,主机通过它的SPI串行寄存器写入一个字节来发起一次传输。串行移位寄存器通过MOSI将字节按位进行传输给从机,从机也将自己的串行移位寄存器的内容通过MISO信号线返回给主机。这样,两个移位寄存器中的内容就能够发生交换。

外设的写与读操作时同步进行的,如果只是写操作,主机可以忽略接受到的数据;如果只是读操作,则需要发送一个空字节引发进行从机传输。
SPI具有三种传输方式:全双工、半双工以及单工;

全双工通信,就是在任何时刻,主机与从机之间都可以同时进行数据的发送和接收。

单工通信,就是在同一时刻,只有一个传输的方向,发送或者是接收。

半双工通信,就是在同一时刻,只能为一个方向传输数据 。

1.3SPI工作模式

在前面学习IIC通信时,STM32与具有IIC接口的设备进行通信时,必须遵循IIC的通信协议。那同理我们使用SPI接口进行通信时,也需要遵循对应的通信协议,也就是对应的读与写时序。SPI通信协议具有4种工作模式。

首先,在学习4种工作模式之前,先了解两个概念:时钟极性(CPOL)与时钟相位(CPHA);

时钟极性: 是指当主机没有数据传输时即空闲状态下,SCL线的电平状态;若空闲状态为高电平 ,CPOL为1;若空闲状态为低电平,则CPOL为0;
**时钟相位:**在同步通信时,数据的采集与变化都是在时钟边沿进行的,也称为边沿协议(了解边沿协议与电平协议的区别),那每个时钟周期具有两个边沿,分别为上升沿与下降沿,那么数据的变化与采样就安排在两个不同的边沿,由于数据的产生和采样需要一定的时间,那么如果我们在第一个边沿把数据输出了,从机只能在第二个边沿进行采样。

CPHA的实质是指:采样时刻,如果CPHA=0就表示采样是从第一个边沿信号进行采样的;如果是CPHA=1,则表示采样是在第2个边沿信号进行的。

由于CPOL与CPHA都有两种状态,所以SPI分为4中工作模式:

|-------------|----------|----------|-------------|----------|----------|
| SPI工作模式 | CPOL | CPHA | SCL空闲状态 | 采样边沿 | 采样时刻 |
| 0 | 0 | 0 | 低电平 | 上升沿 | 奇数边沿 |
| 1 | 0 | 1 | 低电平 | 下降沿 | 偶数边沿 |
| 2 | 1 | 0 | 高电平 | 下降沿 | 奇数边沿 |
| 3 | 1 | 1 | 高电平 | 上升沿 | 偶数边沿 |

工作模式1时序图:

工作模式2时序图:

工作模式3时序图:

工作模式4时序图:

1.4SPI工作寄存器

在使用SPI时,我们所涉及的相关寄存器主要包括:

SPI_CR1(SPI控制寄存器):用于配置SPI工作参数;

SPI_SR(SPI状态寄存器):用于查询当前SPI传输状态;

SPI_DR(SPI数据寄存器):用于存放待发送的数据或者是接受到的数据;

1.SPI_CR1控制寄存器

CPHA:时钟相位------1;SCL空闲时刻为高电平;

CPOL:时钟极性------1;采样时刻为偶数边沿;

MSTR:主从设备选择------1;设置为主设备;

BR:波特率控制------111;速度设置为最低;

SPE:SPI使能------1;开启SPI设备;

LSBFIRST:帧格式------0;MSB先传输,高位在前,低位在后;

SMM:软件从设备管理------1:软件片选从设备;

RXONLY:只接受------0;全双工通信;

DFF:数据帧格式------0;采用8位数据帧格式;

2.SPI_SR状态寄存器

该寄存器的主要作用:用于查询SPI状态,TXE与RXNE即发送完成和接收完成是否标记;

3.SPI数据寄存器

该寄存器为SPI的数据寄存器,是一个双寄存器,包括了发送缓存与接受缓存。

二、NORFLASH介绍

2.1NOR FLASH简介

FLASH是一种常见的用于存储数据的半导体器件,特点:容量大、可重复擦写、按"扇区/块"擦除、掉点后数据继续保存;分类:NOR FLASH与NAND FLASH;

|-------------|---------------|----------------|
| 特性 | NOR FLASH | NAND FLASH |
| 容量 | 较小 | 较大 |
| 成本 | 较贵 | 较便宜 |
| 擦除单元 | 按扇区/块擦除 | 按扇区/块擦除 |
| 读取速度 | 较高 | 较低 |
| 读写单元 | 基于字节读写 | 基于块读写 |
| 写入速度 | 较低 | 较高 |
| 集成度 | 较低 | 较高 |
| 介质类型 | 随机存储 | 连续存储 |
| 地址线与数据线 | 独立分开 | 公用 |
| 坏块 | 较少 | 较多 |
| 是否支持XIP | 支持 | 不支持 |

NOR与NAND在数据写入之前都需要进行擦除操作

NOR FLASH的物理特性:只能由1写为0,不能由0写为1;所以写之前需要进行擦除,即使只写一个字节,也需要对整个扇区/块进行擦除;

NAND FLASH 对于由1写为0,由0写为1都需要进行擦除,此处不详细说明;

缺陷:FLASH也有对应的缺点:寿命短以及位翻转;

**寿命短:**FLASH的擦除次数是有限(10万次左右),当接近时,可能会出现写操作失败;

**位翻转:**数据位写入时为 1,但经过一定时间的环境变化后可能实际变为 0 的情况。
NOR FLASH芯片具有多种:W25Q128、BY25Q128、NM25Q128等等,内存都是128M即16M字节,他们的很多参数、操作都是一样的。其实验也是兼容;

2.2NM25Q128简介

NM25Q128是一款大容量的SPIFLASH产品,其容量为16M,它将16M字节的容量分为256块,每个块大小为64K字节,每个块又分为16个扇区,每个扇区16页,每页256个字节,即每个扇区4K字节。NM25Q128的最小擦除单位为扇区,也就是每次最少擦出4K字节。

NM25Q128 的擦写周期多达 10W 次,具有 20 年的数据保存期限,支持电压为 2.7~3.6V,NM25Q128 支持标准的 SPI,还支持双输出/四输出的 SPI,最大 SPI 时钟可以到 104Mhz(双输

出时相当于 208Mhz,四输出时相当于 416Mhz)。其主要工作在SPI的模式0和模式3;

引脚连接:

CS:片选信号输入,低电平有效;

DO:MISO数据输出信号线;

WP:写保护------高电平可读可写,低电平仅仅可读;

HOLD:保持管脚,低电平有效;

CLK:时钟输入;

DI:MOSI数据输入信号线;

2.3NOR FLASH的工作时序

在介绍工作时序之前,我们需要先对基本操作的指令做出了解:

0x06:写使能,写数据与擦除之前,必须先发送该指令;

0x05:读SR1,判断FLASH是否处于空闲状态,擦除用;

0x03:读数据,用于读取NOR FLASH;

0x02:页写:用于写入NOR FLASH;

0x20:扇区擦除:用于扇区擦除指令;

1.读时序

2.写时序

3.擦除扇区

三、硬件设计

实现功能:

通过KEY0按键控制NOR FLASH的写入,通过控制KEY1按键控制NOR FLASH的读取,并将读取到的的数据通过串口显示调试助手。

原理图:

由上图可知:NM25Q128的引脚:

CS--PB12; CLK--PB13;MISO--PB14;MOSI--PB15;

四、程序设计

4.1SPI配置步骤

1、SPI工作参数配置以及初始化

包括:工作模式、时钟极性、时钟相位;

涉及的函数HAL_SPI_Init();

2、使能SPI时钟和初始化相关引脚

包括:时钟配置、GPIO模式配置;

涉及的函数HAL_SPIMspinit();

3、使能SPI

涉及的宏定义: __HAL_SPI_ENABLE(HANDLE) 实际操作位CR1控制寄存器中的位6SPE;

4、数据的传输

通过 HAL_SPI_Transmit 函数进行发送数据。

通过 HAL_SPI_Receive 函数进行接收数据。

也可以通过 HAL_SPI_TransmitReceive 函数进行发送与接收操作。

5、设置SPI传输速度

SPI 初始化结构体 SPI_InitTypeDef 有一个成员变量是 BaudRatePrescaler,该成员变量用来设置 SPI 的预分频系数,从而决定了 SPI 的传输速度。但是 HAL 库并没有提供单独的 SPI 分频系数修改函数,如果我们需要在程序中偶尔修改速度,那么我们就要通过设置 SPI_CR1 寄存器来修改。

4.2NM25Q128配置步骤

在前面我们已经对于SPI通信所需要的协议都已经封装好了,接着我们只需要在SPI通信的基础上,通过对NM24Q128的工作时序进行拟定即可。

1、初始化片选引脚与SPI接口

包括相关GPIO初始化与SPI初始化;

2、NM24Q128读取

包括读时序。

3、NM24Q128扇区擦除

包括等待函数。

4、NM24Q128写入

包括:是否需要擦除、是否需要换页、遵循读、改、写。

4.3相关代码

程序源码:

链接:https://pan.baidu.com/s/1Mzfl_LS1ou4BmAbxX11hxA
提取码:1022

spi.c

cpp 复制代码
#include "./BSP/SPI/spi.h"

SPI_HandleTypeDef spi_handle;
void spi_init(void)
{
   spi_handle.Instance=SPI2;    // 基地址;
   spi_handle.Init.Mode=SPI_MODE_MASTER;    //模式:主从模式,主模式(SPI_MODE_MASTER),从模式(SPI_MODE_SLAVE);
   spi_handle.Init.Direction=SPI_DIRECTION_2LINES;  //方式:1、只接受模式;2、单线双向通信数据模式;3、全双工模式;    
   spi_handle.Init.DataSize=SPI_DATASIZE_8BIT;   //数据帧格式:8位/16位;
   spi_handle.Init.CLKPolarity=SPI_POLARITY_HIGH;   //时钟极性;
   spi_handle.Init.CLKPhase=SPI_PHASE_2EDGE;    //时钟相位;
   spi_handle.Init.NSS = SPI_NSS_SOFT;  //SS信号由硬件还是软件控制;
   spi_handle.Init.BaudRatePrescaler=SPI_BAUDRATEPRESCALER_256; //设置SPI波特率预分频;
   spi_handle.Init.FirstBit=SPI_FIRSTBIT_MSB;   //起始位是MSB还是LSB;
   spi_handle.Init.TIMode=SPI_TIMODE_DISABLE;   //帧格式 SPImotorla模式还是TI模式;
   spi_handle.Init.CRCCalculation=SPI_CRCCALCULATION_DISABLE; //硬件CRC是否使能;
   spi_handle.Init.CRCPolynomial=7; //CRC多项式;
     HAL_SPI_Init(&spi_handle); //初始化;
     __HAL_SPI_ENABLE(&spi_handle);//使能SPI2
}
void HAL_SPI_MspInit(SPI_HandleTypeDef *hspi) 
{
        __HAL_RCC_SPI2_CLK_ENABLE();//使能SPI2时钟;
        __HAL_RCC_GPIOB_CLK_ENABLE();//使能GPIOB时钟;
    
        GPIO_InitTypeDef gpio_init_struct;
    
        gpio_init_struct.Mode=GPIO_MODE_AF_PP;//复用推挽输出;
        gpio_init_struct.Pull=GPIO_PULLUP;//上拉;
        gpio_init_struct.Pin=GPIO_PIN_13 |GPIO_PIN_14 |GPIO_PIN_15;//引脚;
        gpio_init_struct.Speed=GPIO_SPEED_FREQ_HIGH ;//高速;
        HAL_GPIO_Init(GPIOB, &gpio_init_struct);     
}

uint8_t spi_readwrite_byte(uint8_t txdata)
{
    uint8_t rxdata;
    HAL_SPI_TransmitReceive(&spi_handle, &txdata, &rxdata, 1,  1000);
    return rxdata;
}

key.c

cpp 复制代码
#include "./BSP/KEY1/key.h"

void key_init(void)
{ 
   GPIO_InitTypeDef  gpio_init_struct;
    
    __HAL_RCC_GPIOE_CLK_ENABLE();
    gpio_init_struct.Pin=GPIO_PIN_4;
    gpio_init_struct.Mode=GPIO_MODE_INPUT;
    gpio_init_struct.Pull=GPIO_PULLUP; 
    HAL_GPIO_Init(GPIOE,&gpio_init_struct);
    
    __HAL_RCC_GPIOE_CLK_ENABLE();
    gpio_init_struct.Pin=GPIO_PIN_3;
    gpio_init_struct.Mode=GPIO_MODE_INPUT;
    gpio_init_struct.Pull=GPIO_PULLUP; 
    HAL_GPIO_Init(GPIOE,&gpio_init_struct);
}

uint32_t key_scan()
{
    if( HAL_GPIO_ReadPin(GPIOE,GPIO_PIN_4)==0)   
    {
        delay_ms(10);
        if( HAL_GPIO_ReadPin(GPIOE,GPIO_PIN_4)==0)
        {
            while( HAL_GPIO_ReadPin(GPIOE,GPIO_PIN_4)==0);
            return 1;
        }  
    }    
    return 0;    
}
uint32_t key_scan1()
{
    if( HAL_GPIO_ReadPin(GPIOE,GPIO_PIN_3)==0)   
    {
        delay_ms(10);
        if( HAL_GPIO_ReadPin(GPIOE,GPIO_PIN_3)==0)
        {
            while( HAL_GPIO_ReadPin(GPIOE,GPIO_PIN_3)==0);
            return 2;
        }     
    }    
    return 0;    
} 
   

nm24q128.c

cpp 复制代码
#include "./BSP/SPI/nm25q128.h"

void norflash_init(void)
{
     __HAL_RCC_GPIOB_CLK_ENABLE();/*片选引脚时钟*/
    GPIO_InitTypeDef gpiob_init_struct;
    gpiob_init_struct.Mode=GPIO_MODE_OUTPUT_PP;
    gpiob_init_struct.Pin=GPIO_PIN_12;
    gpiob_init_struct.Speed= GPIO_SPEED_FREQ_HIGH;
    HAL_GPIO_Init(GPIOB, &gpiob_init_struct);/*片选引脚初始化*/
    spi_init();/*SPI初始化*/
    spi_readwrite_byte(0Xff);//清空数据寄存器DR作用;
    NORFLASH_CS(1); /*拉高片选*/
}

uint8_t nm25q128_read_data(uint32_t address)
{
    uint8_t data=0;
    NORFLASH_CS(0);/*拉低片选*/
    
    /*1、发送读命令*/
    spi_readwrite_byte(0X03);
    
    /*2、发送地址,分三次发送,高位在前*/
    spi_readwrite_byte(address >> 16);
    spi_readwrite_byte(address >> 8);
    spi_readwrite_byte(address);
    
    /*3、读取数据*/
    data = spi_readwrite_byte(0xff);
    
    NORFLASH_CS(1);/*拉高片选*/
    return data;

}
uint8_t  norflash_wait(void)
{
    uint8_t rec_data=0;
    NORFLASH_CS(0);/*拉低片选*/
    spi_readwrite_byte(0X05);
    rec_data=spi_readwrite_byte(0Xff);
    NORFLASH_CS(1);/*拉高片选*/
    return  rec_data;
}

void norflash_erase_sector(uint32_t addr)
{
   
    /*1、写使能*/
    NORFLASH_CS(0);
    spi_readwrite_byte(0X06);
    NORFLASH_CS(1);/*拉高片选*/
    
    /*2、等待空闲*/
    while(norflash_wait() & 0x01) ;
    
    /*3、发送擦除扇区命令*/
     NORFLASH_CS(0);/*拉低片选*/
    spi_readwrite_byte(0X20);
    
     /*4、发送地址*/
    spi_readwrite_byte(addr >> 16);
    spi_readwrite_byte(addr >> 8);
    spi_readwrite_byte(addr);
     NORFLASH_CS(1);/*拉高片选*/
    
     /*5、等待空闲*/
    while(norflash_wait() & 0x01);
    

}

void norflash_write_page(uint8_t data,uint32_t addre)
{
    /*1、擦除扇区*/
    norflash_erase_sector(addre);
    
    /*2、写使能*/
    NORFLASH_CS(0);/*拉低片选*/
    spi_readwrite_byte(0X06);
    NORFLASH_CS(1);/*拉高片选*/
    
    /*3、页写使能*/
    NORFLASH_CS(0);/*拉低片选*/
    spi_readwrite_byte(0X02);
    
    
    /*4、发送地址,分三次发送,高位在前*/
    spi_readwrite_byte(addre >> 16);
    spi_readwrite_byte(addre >> 8);
    spi_readwrite_byte(addre);
    
    spi_readwrite_byte(data);
    
     NORFLASH_CS(1);/*拉高片选*/
      
     while(norflash_wait() & 0x01);
}

main.c

cpp 复制代码
#include "./SYSTEM/sys/sys.h"
#include "./SYSTEM/usart/usart.h"
#include "./SYSTEM/delay/delay.h"
#include "./BSP/SPI/nm25q128.h"
#include "./BSP/LED/led.h"
#include "./BSP/LCD/lcd.h"
#include "./BSP/KEY1/key.h"
    uint8_t data=0;
int main(void)
{
    HAL_Init();                              /* 初始化HAL库 */
    sys_stm32_clock_init(RCC_PLL_MUL9);      /* 设置时钟, 72Mhz */
    delay_init(72);                          /* 延时初始化 */
    LED_init();                              /* LED初始化 */
    usart_init(115200);                      /* 串口初始化 */
    key_init();                              /* 按键初始化 */
    norflash_init();                         /* NOR FLASH初始化 */
    while(1)
    { 
        if(key_scan()==1)                    /* K0按键按下 */
        {
            norflash_write_page('A',0X223236);
            printf("write finish\r\n");
        }
        
        if(key_scan1()==2)                  /* K1按键按下 */
        {
         data=nm25q128_read_data(0X223236);
            printf("DATA:%c\n",data);
        }     

    }
}

五、现象

KEY0与KEY1分别按下。

总结:本节我们介绍了SPI的基本概念、使用,以及NOR FLASH 的基本概念,完成了利用NM24Q128芯片通过SPI与STM32进行通信,实现了数据的读取。

创作不易,还请大家多多点赞支持!!!

相关推荐
blessing。。12 分钟前
I2C学习
linux·单片机·嵌入式硬件·嵌入式
嵌新程2 小时前
day03(单片机高级)RTOS
stm32·单片机·嵌入式硬件·freertos·rtos·u575
Lin2012302 小时前
STM32 Keil5 attribute 关键字的用法
stm32·单片机·嵌入式硬件
电工小王(全国可飞)2 小时前
STM32 RAM在Memory Map中被分为3个区域
stm32·单片机·嵌入式硬件
maxiumII2 小时前
Diving into the STM32 HAL-----DAC笔记
笔记·stm32·嵌入式硬件
美式小田5 小时前
单片机学习笔记 9. 8×8LED点阵屏
笔记·单片机·嵌入式硬件·学习
兰_博5 小时前
51单片机-独立按键与数码管联动
单片机·嵌入式硬件·51单片机
时光の尘6 小时前
C语言菜鸟入门·关键字·float以及double的用法
运维·服务器·c语言·开发语言·stm32·单片机·c
嵌入式大圣7 小时前
单片机结合OpenCV
单片机·嵌入式硬件·opencv
日晨难再9 小时前
嵌入式:STM32的启动(Startup)文件解析
stm32·单片机·嵌入式硬件