文章目录
- 前言
- 一、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则不填充,如下图所示,这样就算断电之后,物质的性质也不会因为没有电而改变,所以再次读取数据的时候数据依然不变,这样就可以做到断电保存。
开发板使用的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的标准接口,这样做主要目的还是简化开发者的工作量;
硬件连接:
三、实验过程
这个实验我们实现对通过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 <string.h>
#include <stdio.h>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(®_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 <stdint.h>/* 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 <stdio.h>
#include "fpioa.h"
#include "sysctl.h"
#include "w25qxx.h"
#include "uarths.h"
#include "spi.h"#define BUF_LENGTH (40 * 1024 + 5)
#define DATA_ADDRESS 0xB00000uint8_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开发板上。
实验结果如下:
总结
本章介绍了SPI相关内容,开发板使用的Flash芯片GD25LQ128C的一款存储空间为16MB的FLASH芯片,总共有4096个扇区,每个扇区有16页,每页是256字节,FLASH是能够断电保存数据的一种储存方式。