写在前面:在单片机的学习过程中,各种通信协议的学习是必不可少的,在前面我们学习了串口通信、IIC通信,本节我们来认识一下SPI通信协议。包括其SPI基本概念、NORFLASH芯片的介绍以及相关的例程实验。
目录
一、SPI介绍
1.1什么是SPI?
1.2SPI结构框图
1.3SPI工作模式
1.4SPI工作寄存器
二、NORFLASH介绍
2.1NOR FLASH简介
2.2NM25Q128简介
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
#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
#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
#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
#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进行通信,实现了数据的读取。
创作不易,还请大家多多点赞支持!!!