AI嵌入式K210项目(11)-SPI Flash读写

文章目录

  • 前言
  • 一、K210的SPI
  • 二、Flash介绍
  • 三、实验过程
  • 总结


前言

这一章我们来学习下SPI及其应用,SPI 是一种高速的,全双工,同步的通信总线,由于其高速、同步和简单的特性,被广泛应用于各种微控制器和外围设备之间的通信场景,如:EEPROM和Flash存储器、实时时钟(RTC)、数模转换器(ADC)、网络控制器、数字信号处理(DSP)、数字信号解码器;

一、K210的SPI

串行外设接口有 4 组 SPI 接口,其中 SPI0、SPI1、SPI3 只能工作在 MASTER 模式,SPI2 只能工作在SLAVE 模式,他们有如下特性:
• 支持 1/2/4/8 线全双工模式
• SPI0、SPI1、SPI2 可支持 25MHz 时钟(待测更新)
• SPI3 最高可支持 100MHz 时钟(待测更新)
• 支持 32 位宽、32BYTE 深的 FIFO
• 独立屏蔽中断 - 主机冲突,发送 FIFO 溢出,发送 FIFO 空,接收 FIFO 满,接收 FIFO 下溢,接收 FIFO 溢出中断都可以被屏蔽独立
• 支持 DMA 功能
• 支持双沿的 DDR 传输模式
• SPI3 支持 XIP

对应的头文件 spi.h
为用户提供以下接口
• spi_init
• spi_init_non_standard
• spi_send_data_standard
• spi_send_data_standard_dma
• spi_receive_data_standard
• spi_receive_data_standard_dma
• spi_send_data_multiple
• spi_send_data_multiple_dma
• spi_receive_data_multiple
• spi_receive_data_multiple_dma
• spi_fill_data_dma
• spi_send_data_normal_dma
• spi_set_clk_rate
• spi_handle_data_dma

二、Flash介绍

FLASH 芯片是应用非常广泛的存储材料,与之对应的是RAM芯片,区别在于FLASH芯片断电后数据可以保存,而RAM芯片断电后数据不会保存。那么FLASH是如何工作的呢?计算机的储存方式是二进制,也就是0和1,在二进制中,0和1可以组成任何数。FLASH芯片对0和1的处理方式是用物质填充,1则填充,0则不填充,如下图所示,这样就算断电之后,物质的性质也不会因为没有电而改变,所以再次读取数据的时候数据依然不变,这样就可以做到断电保存。
AI嵌入式K210项目(11)-SPI Flash读写_第1张图片
开发板使用的flash芯片GD25LQ128C是通过SPI串行闪存的芯片,具有128M-bit(16兆字节MByte)空间,能够储存声音、文本和数据等,设备运行电源为2.7V~3.6V,低功耗模式电流低至1uA。

写数据,每次向GD25LQ128C写入数据都需要按照页或者扇区或者簇为单位进行,一页为256个字节,一个扇区为4K个字节(16页),一次最多写一页,也就是一次最多写256个字节,如果超过一页数据长度,则分多次完成。

读数据,可以从任何地址读出。

擦除数据,最小单位为一个扇区,也可以直接擦除整个flash芯片。

flash使用的是SPI的通讯方式,所以对应的头文件是spi.h,而由于各家flash芯片存在差异,往往导致适配起来会存在一定问题,所以kendryte官方对市面上常用的flash做了一些库,对应的头文件是w25qxx.h。

为用户提供以下接口:

• w25qxx_init:初始化flash芯片,主要是设置SPI的设备、通道和速率等。

• w25qxx_read_id:读取flash的ID。

• w25qxx_write_data:向flash写入数据。

• w25qxx_read_data:从flash读取数据。
这里可能部分同学会感觉很疑惑,为什么前面提供了SPI的接口,这里又提供了一些那?可以这样理解,这些接口是对上面那么多接口的封装,简化;这些接口最终还是会调用SPI的标准接口,这样做主要目的还是简化开发者的工作量;

硬件连接:
AI嵌入式K210项目(11)-SPI Flash读写_第2张图片
AI嵌入式K210项目(11)-SPI Flash读写_第3张图片

三、实验过程

这个实验我们实现对通过SPI接口对flash芯片进行读写操作,新建flash文件夹,新建main.c文件,然后将w25qxx.h和w25qxx.c放进去
w25qxx.c

/* Copyright 2018 Canaan Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
#include "w25qxx.h"
#include "fpioa.h"
#include "spi.h"
#include "sysctl.h"
#include "dmac.h"
#include 
#include 

uint32_t spi_bus_no = 0;
uint32_t spi_chip_select = 0;

static w25qxx_status_t w25qxx_receive_data(uint8_t *cmd_buff, uint8_t cmd_len, uint8_t *rx_buff, uint32_t rx_len)
{
    spi_init(spi_bus_no, SPI_WORK_MODE_0, SPI_FF_STANDARD, DATALENGTH, 0);
    spi_receive_data_standard(spi_bus_no, spi_chip_select, cmd_buff, cmd_len, rx_buff, rx_len);
    return W25QXX_OK;
}

static w25qxx_status_t w25qxx_send_data(uint8_t *cmd_buff, uint8_t cmd_len, uint8_t *tx_buff, uint32_t tx_len)
{
    spi_init(spi_bus_no, SPI_WORK_MODE_0, SPI_FF_STANDARD, DATALENGTH, 0);
    spi_send_data_standard(spi_bus_no, spi_chip_select, cmd_buff, cmd_len, tx_buff, tx_len);
    return W25QXX_OK;
}

static w25qxx_status_t w25qxx_receive_data_enhanced_dma(uint32_t *cmd_buff, uint8_t cmd_len, uint8_t *rx_buff, uint32_t rx_len)
{
    spi_receive_data_multiple_dma(DMAC_CHANNEL0, DMAC_CHANNEL1, spi_bus_no, spi_chip_select, cmd_buff, cmd_len, rx_buff, rx_len);
    return W25QXX_OK;
}

static w25qxx_status_t w25qxx_send_data_enhanced_dma(uint32_t *cmd_buff, uint8_t cmd_len, uint8_t *tx_buff, uint32_t tx_len)
{
    spi_send_data_multiple_dma(DMAC_CHANNEL0, spi_bus_no, spi_chip_select, cmd_buff, cmd_len, tx_buff, tx_len);
    return W25QXX_OK;
}

w25qxx_status_t w25qxx_read_id(uint8_t *manuf_id, uint8_t *device_id)
{
    uint8_t cmd[4] = {READ_ID, 0x00, 0x00, 0x00};
    uint8_t data[2] = {0};

    w25qxx_receive_data(cmd, 4, data, 2);
    *manuf_id = data[0];
    *device_id = data[1];
    return W25QXX_OK;
}

static w25qxx_status_t w25qxx_write_enable(void)
{
    uint8_t cmd[1] = {WRITE_ENABLE};

    w25qxx_send_data(cmd, 1, 0, 0);
    return W25QXX_OK;
}

static w25qxx_status_t w25qxx_write_status_reg(uint8_t reg1_data, uint8_t reg2_data)
{
    uint8_t cmd[3] = {WRITE_REG1, reg1_data, reg2_data};

    w25qxx_write_enable();
    w25qxx_send_data(cmd, 3, 0, 0);
    return W25QXX_OK;
}

static w25qxx_status_t w25qxx_read_status_reg1(uint8_t *reg_data)
{
    uint8_t cmd[1] = {READ_REG1};
    uint8_t data[1] = {0};

    w25qxx_receive_data(cmd, 1, data, 1);
    *reg_data = data[0];
    return W25QXX_OK;
}

static w25qxx_status_t w25qxx_read_status_reg2(uint8_t *reg_data)
{
    uint8_t cmd[1] = {READ_REG2};
    uint8_t data[1] = {0};

    w25qxx_receive_data(cmd, 1, data, 1);
    *reg_data = data[0];
    return W25QXX_OK;
}

static w25qxx_status_t w25qxx_enable_quad_mode(void)
{
    uint8_t reg_data = 0;

    w25qxx_read_status_reg2(&reg_data);
    if (!(reg_data & REG2_QUAL_MASK))
    {
        reg_data |= REG2_QUAL_MASK;
        w25qxx_write_status_reg(0x00, reg_data);
    }
    return W25QXX_OK;
}

static w25qxx_status_t w25qxx_is_busy(void)
{
    uint8_t status = 0;

    w25qxx_read_status_reg1(&status);
    if (status & REG1_BUSY_MASK)
        return W25QXX_BUSY;
    return W25QXX_OK;
}

w25qxx_status_t w25qxx_sector_erase(uint32_t addr)
{
    uint8_t cmd[4] = {SECTOR_ERASE};

    cmd[1] = (uint8_t)(addr >> 16);
    cmd[2] = (uint8_t)(addr >> 8);
    cmd[3] = (uint8_t)(addr);
    w25qxx_write_enable();
    w25qxx_send_data(cmd, 4, 0, 0);
    return W25QXX_OK;
}

static w25qxx_status_t w25qxx_quad_page_program(uint32_t addr, uint8_t *data_buf, uint32_t length)
{
    uint32_t cmd[2] = {0};

    cmd[0] = QUAD_PAGE_PROGRAM;
    cmd[1] = addr;
    w25qxx_write_enable();
    spi_init(spi_bus_no, SPI_WORK_MODE_0, SPI_FF_QUAD, 32/*DATALENGTH*/, 0);
    spi_init_non_standard(spi_bus_no, 8/*instrction length*/, 24/*address length*/, 0/*wait cycles*/,
                          SPI_AITM_STANDARD/*spi address trans mode*/);
    w25qxx_send_data_enhanced_dma(cmd, 2, data_buf, length);
    while (w25qxx_is_busy() == W25QXX_BUSY)
        ;
    return W25QXX_OK;
}

static w25qxx_status_t w25qxx_sector_program(uint32_t addr, uint8_t *data_buf)
{
    uint8_t index = 0;

    for (index = 0; index < w25qxx_FLASH_PAGE_NUM_PER_SECTOR; index++)
    {
        w25qxx_quad_page_program(addr, data_buf, w25qxx_FLASH_PAGE_SIZE);
        addr += w25qxx_FLASH_PAGE_SIZE;
        data_buf += w25qxx_FLASH_PAGE_SIZE;
    }
    return W25QXX_OK;
}

w25qxx_status_t w25qxx_write_data(uint32_t addr, uint8_t *data_buf, uint32_t length)
{
    uint32_t sector_addr = 0;
    uint32_t sector_offset = 0;
    uint32_t sector_remain = 0;
    uint32_t write_len = 0;
    uint32_t index = 0;
    uint8_t *pread = NULL;
    uint8_t *pwrite = NULL;
    uint8_t swap_buf[w25qxx_FLASH_SECTOR_SIZE] = {0};

    while (length)
    {
        sector_addr = addr & (~(w25qxx_FLASH_SECTOR_SIZE - 1));
        sector_offset = addr & (w25qxx_FLASH_SECTOR_SIZE - 1);
        sector_remain = w25qxx_FLASH_SECTOR_SIZE - sector_offset;
        write_len = ((length < sector_remain) ? length : sector_remain);
        w25qxx_read_data(sector_addr, swap_buf, w25qxx_FLASH_SECTOR_SIZE);
        pread = swap_buf + sector_offset;
        pwrite = data_buf;
        for (index = 0; index < write_len; index++)
        {
            if ((*pwrite) != ((*pwrite) & (*pread)))
            {
                w25qxx_sector_erase(sector_addr);
                while (w25qxx_is_busy() == W25QXX_BUSY)
                    ;
                break;
            }
            pwrite++;
            pread++;
        }
        if (write_len == w25qxx_FLASH_SECTOR_SIZE)
        {
            w25qxx_sector_program(sector_addr, data_buf);
        }
        else
        {
            pread = swap_buf + sector_offset;
            pwrite = data_buf;
            for (index = 0; index < write_len; index++)
                *pread++ = *pwrite++;
            w25qxx_sector_program(sector_addr, swap_buf);
        }
        length -= write_len;
        addr += write_len;
        data_buf += write_len;
    }
    return W25QXX_OK;
}

static w25qxx_status_t _w25qxx_read_data(uint32_t addr, uint8_t *data_buf, uint32_t length)
{
    uint32_t cmd[2] = {0};
    cmd[0] = FAST_READ_QUAL_IO;
    cmd[1] = addr << 8;
    spi_init(spi_bus_no, SPI_WORK_MODE_0, SPI_FF_QUAD, 32, 0);
    spi_init_non_standard(spi_bus_no, 8/*instrction length*/, 32/*address length*/, 4/*wait cycles*/,
                          SPI_AITM_ADDR_STANDARD/*spi address trans mode*/);
    w25qxx_receive_data_enhanced_dma(cmd, 2, data_buf, length);
    return W25QXX_OK;
}

w25qxx_status_t w25qxx_read_data(uint32_t addr, uint8_t *data_buf, uint32_t length)
{
    uint32_t v_remain = length % 4;
    if(v_remain != 0)
    {
        length = length / 4 * 4;
    }

    uint32_t len = 0;

    while (length)
    {
        len = ((length >= 0x010000) ? 0x010000 : length);
        _w25qxx_read_data(addr, data_buf, len);
        addr += len;
        data_buf += len;
        length -= len;
    }

    if(v_remain)
    {
        uint8_t v_recv_buf[4];
        _w25qxx_read_data(addr, v_recv_buf, 4);
        memcpy(data_buf, v_recv_buf, v_remain);
    }
    return W25QXX_OK;
}

w25qxx_status_t w25qxx_init(uint8_t spi_index, uint8_t spi_ss, uint32_t rate)
{
    spi_bus_no = spi_index;
    spi_chip_select = spi_ss;
    spi_init(spi_bus_no, SPI_WORK_MODE_0, SPI_FF_STANDARD, DATALENGTH, 0);
    spi_set_clk_rate(spi_bus_no, rate);
    w25qxx_enable_quad_mode();
    return W25QXX_OK;
}

w25qxx.h

/* Copyright 2018 Canaan Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
#ifndef _W25QXX_H
#define _W25QXX_H

#include 

/* clang-format off */
#define DATALENGTH                          8

#define SPI_SLAVE_SELECT                    (0x01)

#define w25qxx_FLASH_PAGE_SIZE              256
#define w25qxx_FLASH_SECTOR_SIZE            4096
#define w25qxx_FLASH_PAGE_NUM_PER_SECTOR    16
#define w25qxx_FLASH_CHIP_SIZE              (16777216 UL)

#define WRITE_ENABLE                        0x06
#define WRITE_DISABLE                       0x04
#define READ_REG1                           0x05
#define READ_REG2                           0x35
#define READ_REG3                           0x15
#define WRITE_REG1                          0x01
#define WRITE_REG2                          0x31
#define WRITE_REG3                          0x11
#define READ_DATA                           0x03
#define FAST_READ                           0x0B
#define FAST_READ_DUAL_OUTPUT               0x3B
#define FAST_READ_QUAL_OUTPUT               0x6B
#define FAST_READ_DUAL_IO                   0xBB
#define FAST_READ_QUAL_IO                   0xEB
#define DUAL_READ_RESET                     0xFFFF
#define QUAL_READ_RESET                     0xFF
#define PAGE_PROGRAM                        0x02
#define QUAD_PAGE_PROGRAM                   0x32
#define SECTOR_ERASE                        0x20
#define BLOCK_32K_ERASE                     0x52
#define BLOCK_64K_ERASE                     0xD8
#define CHIP_ERASE                          0x60
#define READ_ID                             0x90
#define ENABLE_QPI                          0x38
#define EXIT_QPI                            0xFF
#define ENABLE_RESET                        0x66
#define RESET_DEVICE                        0x99

#define REG1_BUSY_MASK                      0x01
#define REG2_QUAL_MASK                      0x02

#define LETOBE(x)     ((x >> 24) | ((x & 0x00FF0000) >> 8) | ((x & 0x0000FF00) << 8) | (x << 24))
/* clang-format on */

/**
 * @brief      w25qxx operating status enumerate
 */
typedef enum _w25qxx_status
{
    W25QXX_OK = 0,
    W25QXX_BUSY,
    W25QXX_ERROR,
} w25qxx_status_t;

/**
 * @brief      w25qxx read operating enumerate
 */
typedef enum _w25qxx_read
{
    W25QXX_STANDARD = 0,
    W25QXX_STANDARD_FAST,
    W25QXX_DUAL,
    W25QXX_DUAL_FAST,
    W25QXX_QUAD,
    W25QXX_QUAD_FAST,
} w25qxx_read_t;

w25qxx_status_t w25qxx_init(uint8_t spi_index, uint8_t spi_ss, uint32_t rate);
w25qxx_status_t w25qxx_read_id(uint8_t *manuf_id, uint8_t *device_id);
w25qxx_status_t w25qxx_write_data(uint32_t addr, uint8_t *data_buf, uint32_t length);
w25qxx_status_t w25qxx_read_data(uint32_t addr, uint8_t *data_buf, uint32_t length);

#endif

main.c

/**
* @file         main.c
* @brief        flash实验
* @details      
* @par History  见如下说明
*                 
* version:	V1.0: 先向flash写入数据,然后读取出来,对比写入和读取的数据是否一致,
*                 如果不一致则打印错误信息。
*/
#include 
#include "fpioa.h"
#include "sysctl.h"
#include "w25qxx.h"
#include "uarths.h"
#include "spi.h"

#define BUF_LENGTH (40 * 1024 + 5)
#define DATA_ADDRESS 0xB00000

uint8_t write_buf[BUF_LENGTH];
uint8_t read_buf[BUF_LENGTH];

/**
* Function       flash_init
* @brief         flash初始化
* @param[in]     void
* @param[out]    void
* @retval        void
* @par History   无
*/
int flash_init(void)
{
    uint8_t manuf_id, device_id;
    uint8_t spi_index = 3, spi_ss = 0;
    printf("flash init \n");

    w25qxx_init(spi_index, spi_ss, 60000000);
    /* 读取flash的ID */
    w25qxx_read_id(&manuf_id, &device_id);
    printf("manuf_id:0x%02x, device_id:0x%02x\n", manuf_id, device_id);
    if ((manuf_id != 0xEF && manuf_id != 0xC8) || (device_id != 0x17 && device_id != 0x16))
    {
        /* flash初始化失败 */
        printf("w25qxx_read_id error\n");
        printf("manuf_id:0x%02x, device_id:0x%02x\n", manuf_id, device_id);
        return 0;
    }
    else
    {
        return 1;
    } 
}

/**
* Function       flash_write_data
* @brief         flash写入数据
* @param[in]     data_buf
* @param[in]     length
* @param[out]    void
* @retval        void
* @par History   无
*/
void flash_write_data(uint8_t *data_buf, uint32_t length)
{
    uint64_t start = sysctl_get_time_us();
    /* flash写入数据 */
    w25qxx_write_data(DATA_ADDRESS, data_buf, length);
    uint64_t stop = sysctl_get_time_us();
    /* 打印写入数据的时间(us) */
    printf("write data finish:%ld us\n", (stop - start));
}

/**
* Function       flash_read_data
* @brief         flash读取数据
* @param[in]     data_buf
* @param[in]     length
* @param[out]    void
* @retval        void
* @par History   无
*/
void flash_read_data(uint8_t *data_buf, uint32_t length)
{
    uint64_t start = sysctl_get_time_us();
    /* flash读取数据 */
    w25qxx_read_data(DATA_ADDRESS, data_buf, length);
    uint64_t stop = sysctl_get_time_us();
    /* 打印读取数据的时间(us) */
    printf("read data finish:%ld us\n", (stop - start));
}


/**
* Function       main
* @brief         主函数,程序的入口
* @param[in]     void
* @param[out]    void
* @retval        0
* @par History   无
*/
int main(void)
{
    /* 设置新PLL0频率 */
    sysctl_pll_set_freq(SYSCTL_PLL0, 800000000);
    uarths_init();
    
    /* 初始化flash */
    uint8_t res = 0;
    res = flash_init();
    if (res == 0) return 0;

    /* 给缓存写入的数据赋值 */
    for (int i = 0; i < BUF_LENGTH; i++)
        write_buf[i] = (uint8_t)(i);
    
    /* 清空读取的缓存数据 */
    for(int i = 0; i < BUF_LENGTH; i++)
        read_buf[i] = 0;

    printf("flash start write data\n");

    /* flash写入数据 */
    flash_write_data(write_buf, BUF_LENGTH);

    /*flash读取数据*/
    flash_read_data(read_buf, BUF_LENGTH);

    /* 比较数据,如果有不同则打印错误信息 */
    for (int i = 0; i < BUF_LENGTH; i++)
    {
        if (read_buf[i] != write_buf[i])
        {
            printf("flash read error\n");
            return 0;
        }      
    }
    printf("spi3 flash master test ok\n");
    while (1)
        ;
    return 0;
}

代码写好后,我们开始编译,注意:如果你编译过程中出现错误,可以先make clean掉之前生成的过程文件,重新生成

cd build
//注意这里的目标文件目录改成flash,和刚才新建的文件夹名称一致
cmake .. -DPROJ=flash  -G "MinGW Makefiles"
make

编译完成后,在build文件夹下会生成flash.bin文件。

使用type-C数据线连接电脑与K210开发板,打开kflash,选择对应的设备,再将程序固件烧录到K210开发板上。
AI嵌入式K210项目(11)-SPI Flash读写_第4张图片

实验结果如下:
AI嵌入式K210项目(11)-SPI Flash读写_第5张图片


总结

本章介绍了SPI相关内容,开发板使用的Flash芯片GD25LQ128C的一款存储空间为16MB的FLASH芯片,总共有4096个扇区,每个扇区有16页,每页是256字节,FLASH是能够断电保存数据的一种储存方式。

你可能感兴趣的:(K210开发板,人工智能,AI,K210,嵌入式AI,嵌入式,flash,SPI)