这一章我们来学习下SPI及其应用,SPI 是一种高速的,全双工,同步的通信总线,由于其高速、同步和简单的特性,被广泛应用于各种微控制器和外围设备之间的通信场景,如:EEPROM和Flash存储器、实时时钟(RTC)、数模转换器(ADC)、网络控制器、数字信号处理(DSP)、数字信号解码器;
串行外设接口有 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 芯片是应用非常广泛的存储材料,与之对应的是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
#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(®_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开发板上。
本章介绍了SPI相关内容,开发板使用的Flash芯片GD25LQ128C的一款存储空间为16MB的FLASH芯片,总共有4096个扇区,每个扇区有16页,每页是256字节,FLASH是能够断电保存数据的一种储存方式。