目录
一、SPI基础
1.1. SPI介绍
1.2. 一主机连接多从机
1.3. SPI结构
1.4. 数据接收和数据发送
1.5. 传输协议
1.6. SPI相关寄存器(F1/F4)
二、SPI相关HAL库驱动介绍
2.1 SPI相关函数
2.2 SPI结构体
三、NOR FLASH读取和擦写实验
3.1 NOR FLASH介绍
3.2 NM25Q128简介
3.3 电路连接
3.4 NM258Q128常用指令
3.5 输入输出序列
3.6 状态寄存器表
3.7 NOR FLASH基本驱动步骤
3.8 程序编写
一、SPI基础
1.1. SPI介绍
SPI:串行外设设备接口(Serial Peripheral Interface),是一种高速,全双工,同步的通信总线(SCK线)。
这里对比IIC总线:
(1)SPI总线,为同步 串行 全双工,接口为MOSI(发送数据) MISO(接收数据) SCL(时钟线) CS(片选线),一主多从或者一主一从,片选引脚选择,通信速率50MHz,数据格式8位或16位,MSB/LSB(高位先发/低位先发)
(2)IIC总线,同步 串行 半双工,接口为SDA(数据线) SCL(时钟线),多主从,SDA上设备地址片选,通信速率100kHz、400kHz、3.4MHz,数据格式8位,MSB(高位先发)
SPI接口主要应用于存储芯片、AD转换器以及LCD中
1.2. 一主机连接多从机
将STM32的SCK、MOSI、MISO分别连接至外部芯片的SCK、MOSI和MISO,将多个片选CS1-CSn分别连接至各个芯片得CS端。
1.3. SPI结构
SPI相关引脚:MOSI(输出数据线)、MISO(输入数据线)、SCK(时钟)、NSS(片选 用软件管理NSS)
1.4. 数据接收和数据发送
(1)数据发送:主机模式->通过地址和数据总线->发送缓冲区(通过SPI_SR查询TXE位是否发送完成)->移位寄存器->MOSI
(2)数据接收:MISO->移位寄存器->接收缓冲区(查询SPI_SR的RXNE位是否接受完成)->地址&数据总线
1.5. 传输协议
(1)SPI:边沿协议(IIC:电平协议)
时钟极性(CPOL):没有数据传输时时钟的空闲状态电平,0:SCK在空闲状态保持低电平,1:SCK在空闲状态保持高电平;
时钟相位(CPHA):时钟线在第几个时钟边沿采样数据,0:SCK的第一边沿进行采样(数据在第一个时钟边沿被锁存),1:SCK的第二边沿进行数据采样(数据在第二个时钟边沿被锁存)
(2)工作模式
0:CPOL0 CPHA0,SCL空闲状态低电平,采样边沿 上升沿,采样时刻 奇数边沿;
1:CPOL0 CPHA1,SCL空闲状态低电平,采样边沿 下降沿,采样时刻 偶数边沿;
2:CPOL1 CPHA0,SCL空闲状态高电平,采样边沿 上升沿,采样时刻 奇数边沿;
3:CPOL1 CPHA1,SCL空闲状态高电平,采样边沿 下降沿,采样时刻 偶数边沿;
1.6. SPI相关寄存器(F1/F4)
SPI_CR1:SPI控制寄存器1,用于配置SPI工作参数
SPI_SR:SPI状态寄存器,用于查询SPI传输状态(TXE、RXNE)
SPI_DR:SPI数据寄存器,用于存放待发送数据或接收数据,有两个缓冲区(TX/RX)
(1)SPI控制寄存器(SPI_CR1):
SPI模式(位2):主设备1(本设计配置为主设备),从设备0;
方向(位10):全双工0,禁止输出1;
SSM(位9):进制软件从设备管理0,启用软件从设备管理1(NSS引脚的电平由SSL位的值决定);
SSL(位8):内部从设备选择,只有在SSM置1才有意义,决定了NSS的电平;
DFF(位11):数据帧格式,使用8位数据帧进行发送/接收0,使用16位接收/发送1;
LSBFIRST(位7):先发送MSB高位0,先发送LSB低位1;
SPE(位6):SPI使能,0禁止SPI,1启用SPI设备,在配置所有工作参数后配置;
BR(位5:3):波特率控制,000 f_PCLK/2(72m/2=36m),001 f_PCLK/4,010 f_PCLK/8,011 f_PCLK/16,100 f_PCLK/32,101 f_PCLK/64,110 f_PCLK/128,111 f_PCLK/256;
CPOL(位1):时钟极性,0空闲状态SCK低电平,1空闲状态SCK高电平;
CPHA(位0):时钟相位,0数据从第一个边沿开始,1数据从第二个边沿开始。
(2)SPI状态寄存器
TXE(位1):发送缓冲区非空0,发送缓冲区为空1(数据已发送);
EXNE(位0):接收缓冲区为空0,接收缓冲区非空1(已接受到数据)。
(3)数据寄存器
DR[15:0]:数据寄存器,对于8位的数据,发送和接收只会用[7:0],[15:8]被强制0。对于16位会用到整个数据寄存器。
二、SPI相关HAL库驱动介绍
2.1 SPI相关函数
(1)__HAL_RCC_SPIx_CLK_ENABLE(...):寄存器RCC_APB2ENR,功能描述使能SPIx时钟(x为第几个SPI);
(2)HAL_SPI_Init(...):寄存器SPI_CR1,初始化SPI;
(3)HAL_SPI_MspInit(...):初始化回调,初始化SPI相关引脚;
(4)__HAL_SPI_ENABLE(...):寄存器SPI_CR1(SPE),使能SPI外设;
(5)__HAL_SPI_DISABLE(...):寄存器SPI_CR1(SPE),失能SPI外设(修改SPI速度需先失能);
(6)HAL_SPI_Transmit(...):寄存器SPI_DR和SPI_SR,用于SPI发送;
(7)HAL_SPI_Receive(...):寄存器SPI_DR和SPI_SR,用于SPI接收;
(8)HAL_SPI_TransmitRecieve(...):寄存器SPI_DR和SPI_SR,用于SPI同时接收发送;
2.2 SPI结构体
SPI_HandleTypeDef:
(1)句柄结构体SPI_TypeDef *Instance(基地址,第几个SPI);
(2)初始化结构体SPI_InitTypeDef Init
SPI_InitTypeDef (括号内为本设计采用的设定)
uint32_t Mode SPI模式(主机);
uint32_T Direction 工作方式(全双工);
uint32_t DataSize 帧格式(8位);
uint32_t CLKPolarity 时钟极性(CPOL=0);
uint32_t CLKPhase 时钟相位(CPHA=0);
uint32_t NSS SS控制方式(软件);
uint32_t BaudRatePrescaler SPI波特率预分频值;
uint32_t FirstBit 数据传输顺序(MSB)
unit32_t TIMode 帧格式:Motorla/ TI(默认选择摩托罗拉)
uint32_t CRCCalculation 设置硬件CRC校验
unit32_t CRCPolynomial 设置CRC校验多项式
三、NOR FLASH读取和擦写实验
3.1 NOR FLASH介绍
FlASH常用的存储数据半导体器件,可重复擦写、按“扇区/块”擦除、掉电后数据可保存;
FLASH有一个物理特性:只能写0,不能写1,写1靠擦除;
FLASH有NOR FLASH和NAND FLASH两种
NOR FLASH:基于字节读取,读取速度块,独立地址/数据线,无坏快,支持XIP(可存放程序运行),25Qxx、程序ROM
NAND FLASH:基于块读写,读取速度稍慢,地址线共用,有坏快,不支持XIP(程序需要读取到RAM运行),EMMC、SSD、U盘等
3.2 NM25Q128简介
串行闪存器件,属于NOR FLASH的一种,容量为128Mb,擦写周期达10W次,数据保持达20年。
SPI数据传输模式:支持模式0,和模式3;
数据格式:数据程度8位大小,先发高位,再发低位(MSB);
传输速度:支持标准模式104Mbit/s
地址范围:0x0~0xFFFFFF
3.3 电路连接
(1)CS:输入片选信号,这里可以选择任意一个GPIO口,电平拉低则表示选中该芯片;
(2)SO:信号输出,连接STM32的输入;
(3)WP:写保护,接3.3V可读可写,当拉低时只可读;
(4)HOLD:暂停通讯;
(5)CLK:时钟输入(接SCK);
(6)SI:数据输入(接MOSI)。
3.4 NM258Q128常用指令
(1)写使能(0x06):写入数据/擦除之前,必需先发送该指令;
(2)读SR1(0x05):判定FLASH是否处于空闲状态;
(3)读数据(0x03):用于读取NOR FLASH数据;
(4)页写(0x02):用于写入NOR FLASH数据,最多写256字节;
(5)扇区擦除(0x20):扇区擦除指令,最小擦除单位4096字节;
(6)状态寄存器1:BUSY位(0空闲状态,硬件自动清除;1当前处于忙碌状态),WEL位(执行WriteEnable指令该位为1,0则写禁止)
3.5 输入输出序列
(1)写使能(06H)
片选线CS拉低(发送脉冲)->主机发送06h->拉高片选
(2)读状态寄存器1(05H)
拉低CS片选(发送脉冲)->发送05h->返回SR1的值
(3)读时序
拉低CS片选(发送脉冲)->发送03h->发送24位地址(分3次)->读取数据1(继续给予时钟信号,可以往后一直读)->拉高片选
(4)页写时序(02H)
拉低片选(发送脉冲)->发送02H->发送24位地址->发送数据1(可以不拉高给时钟持续写,最多传输256个字节)->拉高片选
(5)扇区擦除时序(20H)
写入数据前,检查内存空间情况是否满足,不满足需擦除
3.6 状态寄存器表
0位(BUSY):0空闲状态(硬件自动清楚),1处于忙碌状态;
1位(WEL):0写禁止,1可以写/擦除
3.7 NOR FLASH基本驱动步骤
SPI配置步骤:
(1)SPI工作参数配置初始化(工作模式、时钟极性、时钟相位等,HAL_SPI_Init);
(2)使能SPI时钟和初始化相关引脚(GPIO设为复用推挽输出模式,HAL_SPI_MspInit);
(3)使能SPI(__HAL_SPI_ENABLE);
(4)SPI传输数据(HAL_SPI_Transmit发送数据、HAL_SPI_Receive接收数据、HAL_SPI_TransmitRecieve进行发送与接收);
(5)设置SPI传输速度(操作SPI_CR11寄存器中的波特率控制,修改前需失能SPI,。修改完后再使能)
3.8 程序编写
(1)spi.c文件编写
#include "./BSP/SPI/spi.h"SPI_HandleTypeDef g_spi1_handler; /* SPI1句柄 */void spi1_init(void) /*定义SPI初始化函数*/
{g_spi1_handler.Instance=SPI1_SPI; /*选择SPI1*/g_spi1_handler.Init.Mode=SPI_MODE_MASTER; /*选用作为主机*/g_spi1_handler.Init.Direction=SPI_DIRECTION_2LINES; /*双线全双工*/g_spi1_handler.Init.DataSize=SPI_DATASIZE_8BIT; /*进行8位传输*/g_spi1_handler.Init.CLKPolarity=SPI_POLARITY_HIGH; /*设置时钟极性*/g_spi1_handler.Init.CLKPhase=SPI_PHASE_2EDGE; /*设置时钟相位*/g_spi1_handler.Init.NSS=SPI_NSS_SOFT; /*选择片选方式为软件*/g_spi1_handler.Init.BaudRatePrescaler=SPI_BAUDRATEPRESCALER_64; /*设置SPI分频,103为72mHz/分频数*/g_spi1_handler.Init.FirstBit=SPI_FIRSTBIT_MSB; /*设置高位先传*/g_spi1_handler.Init.TIMode=SPI_TIMODE_DISABLE; /*设置帧格式*/g_spi1_handler.Init.CRCCalculation=SPI_CRCCALCULATION_DISABLE; /*关闭CRC校验*/g_spi1_handler.Init.CRCPolynomial=7; /*设置CRC校验多项式*/HAL_SPI_Init(&g_spi1_handler); /*对SPI进行初始化*/
}void HAL_SPI_MspInit(SPI_HandleTypeDef *hspi) /*定义SPI回调函数*/
{SPI1_SPI_CLK_ENABLE(); /*SPI时钟使能*/GPIO_InitTypeDef gpio_init_struct; /*SPI引脚初始化结构体*/if (hspi->Instance == SPI1_SPI) /*判断是否为SPI1*/{SPI1_SCK_GPIO_CLK_ENABLE(); /* SPI1_SCK角时钟使能 */SPI1_MISO_GPIO_CLK_ENABLE(); /* SPI1_MISO角时钟使能 */SPI1_MOSI_GPIO_CLK_ENABLE(); /* SPI1_MOSI角时钟使能 *//* SCK引脚模式设置(复用输出) */gpio_init_struct.Pin = SPI1_SCK_GPIO_PIN; /*GPIOB,SCK时钟信号,PIN3*/gpio_init_struct.Mode = GPIO_MODE_AF_PP; /*推挽复用。可输出高低电平*/gpio_init_struct.Pull = GPIO_PULLUP; /*内部上拉,默认是高电平*/gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH;gpio_init_struct.Alternate = GPIO_AF5_SPI1;HAL_GPIO_Init(SPI1_SCK_GPIO_PORT, &gpio_init_struct);/* MISO引脚模式(复用输出) */gpio_init_struct.Pin = SPI1_MISO_GPIO_PIN;HAL_GPIO_Init(SPI1_MISO_GPIO_PORT, &gpio_init_struct);/* MOSI引脚模式(复用输出) */gpio_init_struct.Pin = SPI1_MOSI_GPIO_PIN;HAL_GPIO_Init(SPI1_MOSI_GPIO_PORT, &gpio_init_struct);}}uint8_t spi1_read_write_byte(uint8_t txdata) /*定义读写函数,txdata为要写入的数据*/
{uint8_t rec_data=0; /*定义要写入的数据*/HAL_SPI_TransmitReceive(&g_spi1_handler, &txdata, &rec_data, 1, 1000); /*长度1字节,1000为超时时间*/return rec_data;}
(2)spi.h编写
#ifndef __SPI_H
#define __SPI_H#include "./SYSTEM/sys/sys.h"/******************************************************************************************/
/* SPI1 引脚 定义 */#define SPI1_SCK_GPIO_PORT GPIOB
#define SPI1_SCK_GPIO_PIN GPIO_PIN_3
#define SPI1_SCK_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOB_CLK_ENABLE(); }while(0) /* PB口时钟使能 */#define SPI1_MISO_GPIO_PORT GPIOB
#define SPI1_MISO_GPIO_PIN GPIO_PIN_4
#define SPI1_MISO_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOB_CLK_ENABLE(); }while(0) /* PB口时钟使能 */#define SPI1_MOSI_GPIO_PORT GPIOB
#define SPI1_MOSI_GPIO_PIN GPIO_PIN_5
#define SPI1_MOSI_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOB_CLK_ENABLE(); }while(0) /* PB口时钟使能 *//* SPI1相关定义 */
#define SPI1_SPI SPI1
#define SPI1_SPI_CLK_ENABLE() do{ __HAL_RCC_SPI1_CLK_ENABLE(); }while(0) /* SPI1时钟使能 */void spi1_init(void);
uint8_t spi1_read_write_byte(uint8_t txdata);#endif
(3)norflash.c程序编写
#include "./BSP/SPI/spi.h"
#include "./SYSTEM/delay/delay.h"
#include "./BSP/NORFLASH/norflash.h"void norflash_init(void) /*初始化芯片函数*/
{NORFLASH_CS_GPIO_CLK_ENABLE(); /*NORFLASH CS角 时钟使能*/GPIO_InitTypeDef gpio_init_struct; /* 定义引脚初始化结构体 */gpio_init_struct.Pin=NORFLASH_CS_GPIO_PIN; /* 片选引脚为.h文件定义引脚 */gpio_init_struct.Mode=GPIO_MODE_OUTPUT_PP; /* 输出模式为推挽输出 */gpio_init_struct.Pull=GPIO_PULLUP; /* 设定输出为上拉模式,即空闲为高电平 */gpio_init_struct.Speed=GPIO_SPEED_FREQ_HIGH; /*高速模式,25mHz~100mHz *//* 初始化片选引脚 */HAL_GPIO_Init(NORFLASH_CS_GPIO_PORT, &gpio_init_struct);spi1_init(); /* 初始化spi1 */spi1_read_write_byte(0xFF); /* 清楚DR(数据寄存器,存放发送和接收的数据)作用 */NORFLASH_CS(1); /* 默认情况拉高片选 */
}/* NORFLASH 的读函数 */
uint8_t norflash_read_data(uint32_t addr) /* addr为要读取数据的地址 */
{uint8_t rec_data=0; /* 接收数据 */NORFLASH_CS(0); /* 拉低片选 *//* 发送读命令 */spi1_read_write_byte(0x03); /* 读命令 *//* 发送地址 地址为24位 分三次发送 */spi1_read_write_byte(addr >> 16); /* 发送高8位 */spi1_read_write_byte(addr >> 8); /* 发送中8位 */spi1_read_write_byte(addr); /* 发送低8位 *//* 读取数据 */rec_data=spi1_read_write_byte(0xFF); /* 发送空字节,接收数据 */NORFLASH_CS(1); /* 接收完数据拉高 */return rec_data;
}/* 判断是否空闲函数 */
uint8_t norflash_sr1(void) /* 读取状态寄存器1的值 */
{uint8_t rec_data;NORFLASH_CS(0); /* 拉低电平使能 */spi1_read_write_byte(0x05); /* 输入读取SR1指令 */rec_data=spi1_read_write_byte(0xFF); /* 读取SR1的值 */NORFLASH_CS(1);return rec_data;
}/* 擦除扇区 */
void norflash_erase_sector(uint32_t addr)
{/* 写使能 */NORFLASH_CS(0); /* 拉低电平使能 */spi1_read_write_byte(0x06); /* 输入擦除指令 */NORFLASH_CS(1);/* 等待空闲 */while(norflash_sr1()&0x01); /* 等到0时,即空闲时执行下条 *//* 发送扇区擦除指令 */NORFLASH_CS(0); /* 拉低电平使能 */spi1_read_write_byte(0x20); /* 发送擦除指令 */spi1_read_write_byte(addr >> 16); /* 发送高8位 */spi1_read_write_byte(addr >> 8); /* 发送中8位 */spi1_read_write_byte(addr); /* 发送低8位 */NORFLASH_CS(1);while(norflash_sr1()&0x01); /* 等到0时,即空闲时执行下条 */
}static void norflash_write_page(uint8_t data, uint32_t addr)
{/* 擦除扇区 */norflash_erase_sector(addr);/* 写使能 */NORFLASH_CS(0); /* 拉低电平使能 */spi1_read_write_byte(0x06); /* 输入擦除指令 */NORFLASH_CS(1);/* 发送页写指令 */NORFLASH_CS(0); /* 拉低电平使能 */spi1_read_write_byte(0x20); /* 发送擦除指令 */spi1_read_write_byte(addr >> 16); /* 发送高8位 */spi1_read_write_byte(addr >> 8); /* 发送中8位 */spi1_read_write_byte(addr); /* 发送低8位 */spi1_read_write_byte(data); /* 发送要写入的数据 */NORFLASH_CS(1);}
(4)norflash.h程序编写
#ifndef __norflash_H
#define __norflash_H#include "./SYSTEM/sys/sys.h"/******************************************************************************************/
/* NORFLASH 片选 引脚 定义 */#define NORFLASH_CS_GPIO_PORT GPIOB
#define NORFLASH_CS_GPIO_PIN GPIO_PIN_14
#define NORFLASH_CS_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOB_CLK_ENABLE(); }while(0) /* PB口时钟使能 *//******************************************************************************************//* NORFLASH 片选信号 0拉低,1拉高*/
#define NORFLASH_CS(x) do{ x ? \HAL_GPIO_WritePin(NORFLASH_CS_GPIO_PORT, NORFLASH_CS_GPIO_PIN, GPIO_PIN_SET) : \HAL_GPIO_WritePin(NORFLASH_CS_GPIO_PORT, NORFLASH_CS_GPIO_PIN, GPIO_PIN_RESET); \}while(0)/* 静态函数 */static void norflash_write_page(uint8_t data, uint32_t addr);//static void norflash_wait_busy(void); /* 等待空闲 */
//static void norflash_send_address(uint32_t address);/* 发送地址 */
//static void norflash_write_nocheck(uint8_t *pbuf, uint32_t addr, uint16_t datalen); /* 写flash,不带擦除 *//* 普通函数 */
void norflash_init(void); /*初始化芯片函数*/
void norflash_erase_sector(uint32_t addr); /* 扇区擦除 */
uint8_t norflash_sr1(void); /* 读取状态寄存器1的值 */
uint8_t norflash_read_data(uint32_t addr); /* addr为要读取数据的地址 */#endif
(5)main.c 程序编写
#include "./SYSTEM/sys/sys.h"
#include "./SYSTEM/usart/usart.h"
#include "./SYSTEM/delay/delay.h"
#include "./BSP/LED/led.h"
#include "./BSP/LCD/lcd.h"
#include "./BSP/KEY/key.h"
#include "./BSP/NORFLASH/norflash.h"/* 要写入到FLASH的字符串数组 */
const uint8_t g_text_buf[] = {"STM32 SPI TEST"};#define TEXT_SIZE sizeof(g_text_buf) /* TEXT字符串长度 */int main(void)
{uint8_t key;uint16_t i=0;HAL_Init();sys_stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟,72Mhz */delay_init(72); /* 延时初始化 */usart_init(115200); /* 串口初始化115200 */key_init(); /* 初始化按键 */norflash_init(); /* norflash初始化 */uint8_t rec_data=0;while(1){key=key_scan(0);if(key==KEY1_PRES) /* 按键按下 */{norflash_write_page('A',0x123456); /* 地址范围0~0xFFFFFF */printf("write finish \r\n");}if(key==KEY0_PRES) /* 按键按下 */{rec_data=norflash_read_data(0x123456); /* 地址范围0~0xFFFFFF */printf("read finish \r\n");}}}