自学内容网 自学内容网

STM32基础之SPI及例程说明

目录

一、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");
        }
    }

}


原文地址:https://blog.csdn.net/qq_35379989/article/details/144089197

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!