(HAL)STM32F407ZGT6——25-1 通信协议SPI软件读写W25Q128实验

一、实验内容

    通过KEY1 按键来控制 norflash 的写入,通过按键 KEY0 来控制norflash 的读取。并在LCD模块上显示相关信息。我们还可以通过 USMART 控制读取 norflash 的 ID、擦除某个扇区或整片擦除。LED0 闪烁用于提示程序正在运行。
        本次实验使用的板为正点原子STM32F4探索版V2,SPI配置为全双工通信。
(HAL)STM32F407ZGT6——25-1 通信协议SPI软件读写W25Q128实验_第1张图片

1、SPI简介

SPI:串行外设设备接口,是一种高速的、全双工的、同步的通信总线。

(HAL)STM32F407ZGT6——25-1 通信协议SPI软件读写W25Q128实验_第2张图片

(HAL)STM32F407ZGT6——25-1 通信协议SPI软件读写W25Q128实验_第3张图片
存在问题:实际操作时,输入引脚仍然配置为推挽输出。
(HAL)STM32F407ZGT6——25-1 通信协议SPI软件读写W25Q128实验_第4张图片
同时往外传输,读写同步。
SPI具有四种模式,依靠CPOL与CPHA进行配置。本次实验配置如下:

2、NORFLASH

W25Q128接线如下:
(HAL)STM32F407ZGT6——25-1 通信协议SPI软件读写W25Q128实验_第5张图片
(HAL)STM32F407ZGT6——25-1 通信协议SPI软件读写W25Q128实验_第6张图片
(HAL)STM32F407ZGT6——25-1 通信协议SPI软件读写W25Q128实验_第7张图片

二、读、擦除、写操作配置步骤

 其中,NORFLASH_CS(0)将PB14(CS引脚)写低,NORFLASH_CS(1)将PB14(CS引脚)写高。

#define NORFLASH_CS(x)      do{ x ? \
                                  HAL_GPIO_WritePin(GPIOB, GPIO_PIN_14, GPIO_PIN_SET) : \
                                  HAL_GPIO_WritePin(GPIOB, GPIO_PIN_14, GPIO_PIN_RESET); \
                            }while(0)
(HAL)STM32F407ZGT6——25-1 通信协议SPI软件读写W25Q128实验_第8张图片
    /* 写使能 */
    NORFLASH_CS(0);
    spi1_read_write_byte(0x06);
    NORFLASH_CS(1);
(HAL)STM32F407ZGT6——25-1 通信协议SPI软件读写W25Q128实验_第9张图片
/* 读状态寄存器1 */
uint8_t norflash_rd_sr1(void)
{
    uint8_t rec_data = 0;
    
    NORFLASH_CS(0);
    spi1_read_write_byte(0x05); /* 读状态寄存器1 */
    rec_data = spi1_read_write_byte(0xFF);
    NORFLASH_CS(1);
    
    
    return rec_data;
}

(HAL)STM32F407ZGT6——25-1 通信协议SPI软件读写W25Q128实验_第10张图片

/* 读操作 */
uint8_t norflash_read_data(uint32_t addr)
{
    uint8_t rec_data;

    NORFLASH_CS(0);
    
    /* 1 发送读指令 */
    spi1_read_write_byte(0x03);
    
    /* 2 发送地址 */
    spi1_read_write_byte(addr>>16);
    spi1_read_write_byte(addr>>8);
    spi1_read_write_byte(addr);
    
    /* 3 读取数据 */
    rec_data = spi1_read_write_byte(0xFF);
    
    NORFLASH_CS(1);
    return rec_data;
}

(HAL)STM32F407ZGT6——25-1 通信协议SPI软件读写W25Q128实验_第11张图片

/* 擦除操作,3-5对应上边擦除步骤,注意擦除前需要写使能 */
void norflash_erase_sector(uint32_t addr)
{
    /* 1 写使能 */
    NORFLASH_CS(0);
    spi1_read_write_byte(0x06);
    NORFLASH_CS(1);
    
    /* 2 等待空闲 */
    while(norflash_rd_sr1()&0x01);      /* 接收寄存器是否空闲 */
    
    /* 3 发送扇区擦除指令 */
    NORFLASH_CS(0);
    spi1_read_write_byte(0x20);
    
    /* 4 发送地址 */
    spi1_read_write_byte(addr>>16);
    spi1_read_write_byte(addr>>8);
    spi1_read_write_byte(addr);
    NORFLASH_CS(1);
    
    /* 5 等待空闲 */
    while(norflash_rd_sr1()&0x01);      /* 接收寄存器是否空闲 */
    
}

(HAL)STM32F407ZGT6——25-1 通信协议SPI软件读写W25Q128实验_第12张图片

/* 写操作步骤,其中3-5对应上边页写时序,注意最开始需要先擦拭扇区、写使能。 */
void norflash_write_page1(uint8_t data, uint32_t addr)
{
    /* 1 擦除扇区 */
    norflash_erase_sector(addr);
    
    /* 2 写使能 */
    NORFLASH_CS(0);
    spi1_read_write_byte(0x06);
    NORFLASH_CS(1);
    
    /* 3 发送页写指令 */
    NORFLASH_CS(0);
    spi1_read_write_byte(0x02);
    
    /* 4 发送地址 */
    spi1_read_write_byte(addr>>16);
    spi1_read_write_byte(addr>>8);
    spi1_read_write_byte(addr);
    
    /* 5 要写入的数据 */
    spi1_read_write_byte(data);
    NORFLASH_CS(1);
    
    /* 6 等待写入完成 */
    while(norflash_rd_sr1()&0x01);      /* 接收寄存器是否空闲 */
    
    
}

三、文件代码

1、spi.c文件

#include "./BSP/SPI/spi.h"


SPI_HandleTypeDef g_spi1_handler; /* SPI1句柄 */

/**
 * @brief       SPI初始化代码
 *   @note      主机模式,8位数据,禁止硬件片选
 * @param       无
 * @retval      无
 */
void spi1_init(void)
{
    /* SPI1初始化 */
    g_spi1_handler.Instance = SPI1;                                         /* SPI1 */
    g_spi1_handler.Init.Mode = SPI_MODE_MASTER;                             /* 设置SPI模式(主机模式) */
    g_spi1_handler.Init.Direction = SPI_DIRECTION_2LINES;                   /* 设置SPI工作方式(全双工) */
    g_spi1_handler.Init.DataSize = SPI_DATASIZE_8BIT;                       /* 设置数据格式(8bit长度) */
    g_spi1_handler.Init.CLKPolarity = SPI_POLARITY_HIGH;                    /* 设置时钟极性(CPOL = 1) */
    g_spi1_handler.Init.CLKPhase = SPI_PHASE_2EDGE;                         /* 设置时钟相位(CPHA = 1) */
    g_spi1_handler.Init.NSS = SPI_NSS_SOFT;                                 /* 设置片选方式(软件片选,自定义GPIO) */
    g_spi1_handler.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_256;      /* 设置SPI时钟波特率分频(256分频) */
    g_spi1_handler.Init.FirstBit = SPI_FIRSTBIT_MSB;                        /* 设置大小端模式(MSB高位在前) */
    g_spi1_handler.Init.TIMode = SPI_TIMODE_DISABLE;                        /* 设置帧格式(关闭TI模式) */
    g_spi1_handler.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;        /* 设置CRC校验(关闭CRC校验) */
    g_spi1_handler.Init.CRCPolynomial = 7;                                  /* 设置CRC校验多项式(范围:1~65535) */
    HAL_SPI_Init(&g_spi1_handler);
    
    __HAL_SPI_ENABLE(&g_spi1_handler); /* 使能SPI1 */
}

void HAL_SPI_MspInit(SPI_HandleTypeDef *hspi)
{

    /* 时钟使能 */
    __HAL_RCC_SPI1_CLK_ENABLE();
    __HAL_RCC_GPIOB_CLK_ENABLE();
    
    /* 引脚初始化 */
    GPIO_InitTypeDef gpio_init_struct;
    /* PB3复用输出 */
    gpio_init_struct.Pin = GPIO_PIN_3;                   
    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(GPIOB, &gpio_init_struct);       
    
    /* PB4复用输出 */
    gpio_init_struct.Pin = GPIO_PIN_4;
    HAL_GPIO_Init(GPIOB, &gpio_init_struct);       
    
    /*PB5复用输出 */
    gpio_init_struct.Pin = GPIO_PIN_5;
    HAL_GPIO_Init(GPIOB, &gpio_init_struct);       

    

}
/* 发送和接收数据 */
/* data为要写入的字节 */
/* rec_data为读到的字节 */
uint8_t spi1_read_write_byte(uint8_t data)
{
    uint8_t rec_data = 0;
    
    HAL_SPI_TransmitReceive(&g_spi1_handler, &data, &rec_data, 1, 1000);
    
    return rec_data;
}

2、norflash.c文件

#include "./BSP/SPI/spi.h"
#include "./BSP/NORFLASH/norflash.h"


/**
 * @brief       初始化SPI NOR FLASH
 * @param       无
 * @retval      无
 */
void norflash_init(void)
{
    /* 时钟使能 */
    __HAL_RCC_GPIOB_CLK_ENABLE();
    /* 引脚初始化 */
    GPIO_InitTypeDef gpio_init_struct;
    /* PB14推挽输出 */
    gpio_init_struct.Pin = GPIO_PIN_14;                     /* NORFLASH CS引脚 */
    gpio_init_struct.Mode = GPIO_MODE_OUTPUT_PP;            /* 推挽输出 */
    gpio_init_struct.Pull = GPIO_PULLUP;                    /* 上拉 */
    gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH;          /* 高速 */
    HAL_GPIO_Init(GPIOB, &gpio_init_struct);
    
    spi1_init();
    
    spi1_read_write_byte(0xFF); /* 清除DR的作用 */
    
    NORFLASH_CS(1);
    

}

/* 读状态寄存器1 */
uint8_t norflash_rd_sr1(void)
{
    uint8_t rec_data = 0;
    
    NORFLASH_CS(0);
    spi1_read_write_byte(0x05); /* 读状态寄存器1 */
    rec_data = spi1_read_write_byte(0xFF);
    NORFLASH_CS(1);
    
    
    return rec_data;
}

/* 读操作 */
uint8_t norflash_read_data(uint32_t addr)
{
    uint8_t rec_data;

    NORFLASH_CS(0);
    
    /* 1 发送读指令 */
    spi1_read_write_byte(0x03);
    
    /* 2 发送地址 */
    spi1_read_write_byte(addr>>16);
    spi1_read_write_byte(addr>>8);
    spi1_read_write_byte(addr);
    
    /* 3 读取数据 */
    rec_data = spi1_read_write_byte(0xFF);
    
    NORFLASH_CS(1);
    return rec_data;
}
/* 擦除操作 */
void norflash_erase_sector(uint32_t addr)
{
    /* 1 写使能 */
    NORFLASH_CS(0);
    spi1_read_write_byte(0x06);
    NORFLASH_CS(1);
    
    /* 2 等待空闲 */
    while(norflash_rd_sr1()&0x01);      /* 接收寄存器是否空闲 */
    
    /* 3 发送扇区擦除指令 */
    NORFLASH_CS(0);
    spi1_read_write_byte(0x20);
    
    /* 4 发送地址 */
    spi1_read_write_byte(addr>>16);
    spi1_read_write_byte(addr>>8);
    spi1_read_write_byte(addr);
    NORFLASH_CS(1);
    
    /* 5 等待空闲 */
    while(norflash_rd_sr1()&0x01);      /* 接收寄存器是否空闲 */
    
}

/* 写操作 */
void norflash_write_page1(uint8_t data, uint32_t addr)
{
    /* 1 擦除扇区 */
    norflash_erase_sector(addr);
    
    /* 2 写使能 */
    NORFLASH_CS(0);
    spi1_read_write_byte(0x06);
    NORFLASH_CS(1);
    
    /* 3 发送页写指令 */
    NORFLASH_CS(0);
    spi1_read_write_byte(0x02);
    
    /* 4 发送地址 */
    spi1_read_write_byte(addr>>16);
    spi1_read_write_byte(addr>>8);
    spi1_read_write_byte(addr);
    
    /* 5 要写入的数据 */
    spi1_read_write_byte(data);
    NORFLASH_CS(1);
    
    /* 6 等待写入完成 */
    while(norflash_rd_sr1()&0x01);      /* 接收寄存器是否空闲 */
    
    
}

3、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 "./USMART/usmart.h"
#include "./BSP/KEY/key.h"
#include "./BSP/NORFLASH/norflash.h"


int main(void)
{
    uint8_t key;
    uint16_t i = 0;
    uint8_t rec_data = 0;
    //uint8_t datatemp[TEXT_SIZE];
    //uint32_t flashsize;
    //uint16_t id = 0;

    HAL_Init();                             /* 初始化HAL库 */
    sys_stm32_clock_init(336, 8, 2, 7);     /* 设置时钟,168Mhz */
    delay_init(168);                        /* 延时初始化 */
    usart_init(115200);                     /* 串口初始化为115200 */
    usmart_dev.init(84);                    /* 初始化USMART */
    led_init();                             /* 初始化LED */
    key_init();                             /* 初始化按键 */
    norflash_init();                        /* 初始化NORFLASH */



    while (1)
    {
        key = key_scan(0);

        if (key == KEY1_PRES)   /* KEY1按下,写入 */
        {            
            norflash_write_page1('A', 0x123457); /* 地址范围0~0xFFFFFF,地址和发送数据可做修改 */
            printf("write finish \r\n");
        }

        if (key == KEY0_PRES)   /* KEY0按下,读取字符串并显示 */
        {
            rec_data = norflash_read_data(0x123456);    /*接收地址可做修改*/
            printf("read data : %c \r\n", rec_data);
        }

        i++;

        if (i == 20)
        {
            LED0_TOGGLE();      /* LED0闪烁 */
            i = 0;
        }

        delay_ms(10);
    }
}

四、实验现象

(HAL)STM32F407ZGT6——25-1 通信协议SPI软件读写W25Q128实验_第13张图片
main中代码为读取地址0x123456,往0x123456写入5。
出现现象为:0x123456内容被擦除,写入之后再读为5。
(HAL)STM32F407ZGT6——25-1 通信协议SPI软件读写W25Q128实验_第14张图片
main中代码为读取地址0x123456,往0x123456写入A。
出现现象为:0x123456内容不变,写入之后内容变为A。
(HAL)STM32F407ZGT6——25-1 通信协议SPI软件读写W25Q128实验_第15张图片
main中代码为读取地址0x123456,往0x123457写入A。
出现现象为:0x123456地址中有用的内容被擦除。

五、问题与讨论

1、为何接收来自slave的数据时,总是需要先写0xFF?

rec_data = spi1_read_write_byte(0xFF);
        原因是SPI通信是双向的且同时进行的,接收slave的数据时,master原先移位寄存器的值就会自动移入slave的移位寄存器中去。如果该值是slave能够识别的一个命令值,就会对slave产生影响,故需要在接收之前写入0xFF(slave不会影响的值),故不一定需要写入0xFF,写入slave不会识别的命令值即可。
         由下面执行代码可知道,data(0xFF)是先被写入之后,再接收rec_data。
/* 发送和接收数据 */
/* data为要写入的字节 */
/* rec_data为读到的字节 */
uint8_t spi1_read_write_byte(uint8_t data)
{
    uint8_t rec_data = 0;
    
    HAL_SPI_TransmitReceive(&g_spi1_handler, &data, &rec_data, 1, 1000);
    
    return rec_data;
}

2、RAM与NOR FLASH、NAND FLASH三者的区别

(1)RAM指哪打哪,可以覆盖写入,写比较简单,速度快。缺点为数据易丢失。

(2)NOR 与NAND 在数据写入前都需要有擦除操作,但实际上NOR Flash 的一个bit 可以从1变成0,而要从0 变 1 就要擦除后再写入,NAND Flash 这两种情况都需要擦除。擦除操作的最小单位为“扇区/块”,这意味着有时候即使只写一字节的数据,则这个“扇区/块”上之前的数据都可能会被擦除。写入速度较慢,但有掉电不丢失的特点。

你可能感兴趣的:(stm32,嵌入式硬件,单片机)