prettyprint

2022年11月22日 星期二

[Raspberry Pi Pico (c-sdk)] Storage: Ep 1. Builtin XIP flash & external flash device with FatFs Filesystem

 本文章介紹Raspberry Pi Pico 使用內建的flash 與外加的SPI flash devicet存取資料。

Raspberry Pi Pico 內建Flash為2MB Winbond W25Q16JV, QSPI interface。外加一個Winbond W25Q128FV 16MB的Flash device。測試再沒有檔案系統與有檔案系統下存取資料。


一、Raspberry Pi Pico開發板內建Flash讀寫:

開發板內建為Winbond W25Q16JV 2MB的Flash。除了存放程式碼外,多餘的空間可用來存放少量的資料,如系統設定檔等。
使用c-sdk內建指令,使用hardware_flash library。
CMakeLists.txt:
include file:
指令:
要寫入新資料或是要覆蓋就資料前,必須先erase後才能成功。
下列程式碼為在XIP_BASE(0x10000000) offset 1M位置寫入第二的page後再讀出。讀出資料直接取得XIP_BASE+offset address 的內容。


二、外接Flash模組:

使用Winbond W25Q128FV 16MB Flash測試。Datasheet指令集如下:

spi clock如下圖:
根據Datasheet指令,相關程式碼如文末所示。
  • 測試一:沒有使用檔案系統下,直接寫入page與讀取。
新的page或是要修改舊的page資料,需要先erase該page的sector後才能再寫入。
程式碼:

測試結果:

  • 測試二、使用FatFs檔案系統架構在device driver上。
  1. http://elm-chan.org/fsw/ff/00index_e.html下載FatFs modules。
  2. 複製檔案到專案子資料夾FatFs下


  3. 實做FatFs MAI(disk_status, disk_initialize, disk_read, disk_write)
  4. 依據文件說明,建立檔案flash_diskio.c實做上述五個function。
  5. 更改ffconf.h設定檔

程式碼:請參閱文末詳細程式碼。
測試結果

三、成果影片:



四、程式碼:

  • 修改過的ffconf.h

/*---------------------------------------------------------------------------/
/  Configurations of FatFs Module
/---------------------------------------------------------------------------*/

#define FFCONF_DEF	80286	/* Revision ID */

/*---------------------------------------------------------------------------/
/ Function Configurations
/---------------------------------------------------------------------------*/

#define FF_FS_READONLY	0
/* This option switches read-only configuration. (0:Read/Write or 1:Read-only)
/  Read-only configuration removes writing API functions, f_write(), f_sync(),
/  f_unlink(), f_mkdir(), f_chmod(), f_rename(), f_truncate(), f_getfree()
/  and optional writing functions as well. */


#define FF_FS_MINIMIZE	0
/* This option defines minimization level to remove some basic API functions.
/
/   0: Basic functions are fully enabled.
/   1: f_stat(), f_getfree(), f_unlink(), f_mkdir(), f_truncate() and f_rename()
/      are removed.
/   2: f_opendir(), f_readdir() and f_closedir() are removed in addition to 1.
/   3: f_lseek() function is removed in addition to 2. */


#define FF_USE_FIND		1 // 0 --> 1
/* This option switches filtered directory read functions, f_findfirst() and
/  f_findnext(). (0:Disable, 1:Enable 2:Enable with matching altname[] too) */


#define FF_USE_MKFS		1 //   0 --> 1
/* This option switches f_mkfs() function. (0:Disable or 1:Enable) */


#define FF_USE_FASTSEEK	1 // 0 --> 1
/* This option switches fast seek function. (0:Disable or 1:Enable) */


#define FF_USE_EXPAND	0
/* This option switches f_expand function. (0:Disable or 1:Enable) */


#define FF_USE_CHMOD	0
/* This option switches attribute manipulation functions, f_chmod() and f_utime().
/  (0:Disable or 1:Enable) Also FF_FS_READONLY needs to be 0 to enable this option. */


#define FF_USE_LABEL	0
/* This option switches volume label functions, f_getlabel() and f_setlabel().
/  (0:Disable or 1:Enable) */


#define FF_USE_FORWARD	0
/* This option switches f_forward() function. (0:Disable or 1:Enable) */


#define FF_USE_STRFUNC	1 // 0 --> 1
#define FF_PRINT_LLI	1
#define FF_PRINT_FLOAT	1
#define FF_STRF_ENCODE	3
/* FF_USE_STRFUNC switches string functions, f_gets(), f_putc(), f_puts() and
/  f_printf().
/
/   0: Disable. FF_PRINT_LLI, FF_PRINT_FLOAT and FF_STRF_ENCODE have no effect.
/   1: Enable without LF-CRLF conversion.
/   2: Enable with LF-CRLF conversion.
/
/  FF_PRINT_LLI = 1 makes f_printf() support long long argument and FF_PRINT_FLOAT = 1/2
/  makes f_printf() support floating point argument. These features want C99 or later.
/  When FF_LFN_UNICODE >= 1 with LFN enabled, string functions convert the character
/  encoding in it. FF_STRF_ENCODE selects assumption of character encoding ON THE FILE
/  to be read/written via those functions.
/
/   0: ANSI/OEM in current CP
/   1: Unicode in UTF-16LE
/   2: Unicode in UTF-16BE
/   3: Unicode in UTF-8
*/


/*---------------------------------------------------------------------------/
/ Locale and Namespace Configurations
/---------------------------------------------------------------------------*/

#define FF_CODE_PAGE	950
/* This option specifies the OEM code page to be used on the target system.
/  Incorrect code page setting can cause a file open failure.
/
/   437 - U.S.
/   720 - Arabic
/   737 - Greek
/   771 - KBL
/   775 - Baltic
/   850 - Latin 1
/   852 - Latin 2
/   855 - Cyrillic
/   857 - Turkish
/   860 - Portuguese
/   861 - Icelandic
/   862 - Hebrew
/   863 - Canadian French
/   864 - Arabic
/   865 - Nordic
/   866 - Russian
/   869 - Greek 2
/   932 - Japanese (DBCS)
/   936 - Simplified Chinese (DBCS)
/   949 - Korean (DBCS)
/   950 - Traditional Chinese (DBCS)
/     0 - Include all code pages above and configured by f_setcp()
*/


#define FF_USE_LFN		3 // 0->3
#define FF_MAX_LFN		255
/* The FF_USE_LFN switches the support for LFN (long file name).
/
/   0: Disable LFN. FF_MAX_LFN has no effect.
/   1: Enable LFN with static  working buffer on the BSS. Always NOT thread-safe.
/   2: Enable LFN with dynamic working buffer on the STACK.
/   3: Enable LFN with dynamic working buffer on the HEAP.
/
/  To enable the LFN, ffunicode.c needs to be added to the project. The LFN function
/  requiers certain internal working buffer occupies (FF_MAX_LFN + 1) * 2 bytes and
/  additional (FF_MAX_LFN + 44) / 15 * 32 bytes when exFAT is enabled.
/  The FF_MAX_LFN defines size of the working buffer in UTF-16 code unit and it can
/  be in range of 12 to 255. It is recommended to be set it 255 to fully support LFN
/  specification.
/  When use stack for the working buffer, take care on stack overflow. When use heap
/  memory for the working buffer, memory management functions, ff_memalloc() and
/  ff_memfree() exemplified in ffsystem.c, need to be added to the project. */


#define FF_LFN_UNICODE	0
/* This option switches the character encoding on the API when LFN is enabled.
/
/   0: ANSI/OEM in current CP (TCHAR = char)
/   1: Unicode in UTF-16 (TCHAR = WCHAR)
/   2: Unicode in UTF-8 (TCHAR = char)
/   3: Unicode in UTF-32 (TCHAR = DWORD)
/
/  Also behavior of string I/O functions will be affected by this option.
/  When LFN is not enabled, this option has no effect. */


#define FF_LFN_BUF		255
#define FF_SFN_BUF		12
/* This set of options defines size of file name members in the FILINFO structure
/  which is used to read out directory items. These values should be suffcient for
/  the file names to read. The maximum possible length of the read file name depends
/  on character encoding. When LFN is not enabled, these options have no effect. */


#define FF_FS_RPATH		0
/* This option configures support for relative path.
/
/   0: Disable relative path and remove related functions.
/   1: Enable relative path. f_chdir() and f_chdrive() are available.
/   2: f_getcwd() function is available in addition to 1.
*/


/*---------------------------------------------------------------------------/
/ Drive/Volume Configurations
/---------------------------------------------------------------------------*/

#define FF_VOLUMES		1
/* Number of volumes (logical drives) to be used. (1-10) */


#define FF_STR_VOLUME_ID	0
#define FF_VOLUME_STRS		"RAM","NAND","CF","SD","SD2","USB","USB2","USB3"
/* FF_STR_VOLUME_ID switches support for volume ID in arbitrary strings.
/  When FF_STR_VOLUME_ID is set to 1 or 2, arbitrary strings can be used as drive
/  number in the path name. FF_VOLUME_STRS defines the volume ID strings for each
/  logical drives. Number of items must not be less than FF_VOLUMES. Valid
/  characters for the volume ID strings are A-Z, a-z and 0-9, however, they are
/  compared in case-insensitive. If FF_STR_VOLUME_ID >= 1 and FF_VOLUME_STRS is
/  not defined, a user defined volume string table is needed as:
/
/  const char* VolumeStr[FF_VOLUMES] = {"ram","flash","sd","usb",...
*/


#define FF_MULTI_PARTITION	0
/* This option switches support for multiple volumes on the physical drive.
/  By default (0), each logical drive number is bound to the same physical drive
/  number and only an FAT volume found on the physical drive will be mounted.
/  When this function is enabled (1), each logical drive number can be bound to
/  arbitrary physical drive and partition listed in the VolToPart[]. Also f_fdisk()
/  function will be available. */


#define FF_MIN_SS		512
#define FF_MAX_SS		4096  // 512 --> 4096
/* This set of options configures the range of sector size to be supported. (512,
/  1024, 2048 or 4096) Always set both 512 for most systems, generic memory card and
/  harddisk, but a larger value may be required for on-board flash memory and some
/  type of optical media. When FF_MAX_SS is larger than FF_MIN_SS, FatFs is configured
/  for variable sector size mode and disk_ioctl() function needs to implement
/  GET_SECTOR_SIZE command. */


#define FF_LBA64		0
/* This option switches support for 64-bit LBA. (0:Disable or 1:Enable)
/  To enable the 64-bit LBA, also exFAT needs to be enabled. (FF_FS_EXFAT == 1) */


#define FF_MIN_GPT		0x10000000
/* Minimum number of sectors to switch GPT as partitioning format in f_mkfs and
/  f_fdisk function. 0x100000000 max. This option has no effect when FF_LBA64 == 0. */


#define FF_USE_TRIM		0
/* This option switches support for ATA-TRIM. (0:Disable or 1:Enable)
/  To enable Trim function, also CTRL_TRIM command should be implemented to the
/  disk_ioctl() function. */



/*---------------------------------------------------------------------------/
/ System Configurations
/---------------------------------------------------------------------------*/

#define FF_FS_TINY		0
/* This option switches tiny buffer configuration. (0:Normal or 1:Tiny)
/  At the tiny configuration, size of file object (FIL) is shrinked FF_MAX_SS bytes.
/  Instead of private sector buffer eliminated from the file object, common sector
/  buffer in the filesystem object (FATFS) is used for the file data transfer. */


#define FF_FS_EXFAT		0
/* This option switches support for exFAT filesystem. (0:Disable or 1:Enable)
/  To enable exFAT, also LFN needs to be enabled. (FF_USE_LFN >= 1)
/  Note that enabling exFAT discards ANSI C (C89) compatibility. */


#define FF_FS_NORTC		0
#define FF_NORTC_MON	1
#define FF_NORTC_MDAY	1
#define FF_NORTC_YEAR	2022
/* The option FF_FS_NORTC switches timestamp feature. If the system does not have
/  an RTC or valid timestamp is not needed, set FF_FS_NORTC = 1 to disable the
/  timestamp feature. Every object modified by FatFs will have a fixed timestamp
/  defined by FF_NORTC_MON, FF_NORTC_MDAY and FF_NORTC_YEAR in local time.
/  To enable timestamp function (FF_FS_NORTC = 0), get_fattime() function need to be
/  added to the project to read current time form real-time clock. FF_NORTC_MON,
/  FF_NORTC_MDAY and FF_NORTC_YEAR have no effect.
/  These options have no effect in read-only configuration (FF_FS_READONLY = 1). */


#define FF_FS_NOFSINFO	0
/* If you need to know correct free space on the FAT32 volume, set bit 0 of this
/  option, and f_getfree() function at the first time after volume mount will force
/  a full FAT scan. Bit 1 controls the use of last allocated cluster number.
/
/  bit0=0: Use free cluster count in the FSINFO if available.
/  bit0=1: Do not trust free cluster count in the FSINFO.
/  bit1=0: Use last allocated cluster number in the FSINFO if available.
/  bit1=1: Do not trust last allocated cluster number in the FSINFO.
*/


#define FF_FS_LOCK		0
/* The option FF_FS_LOCK switches file lock function to control duplicated file open
/  and illegal operation to open objects. This option must be 0 when FF_FS_READONLY
/  is 1.
/
/  0:  Disable file lock function. To avoid volume corruption, application program
/      should avoid illegal open, remove and rename to the open objects.
/  >0: Enable file lock function. The value defines how many files/sub-directories
/      can be opened simultaneously under file lock control. Note that the file
/      lock control is independent of re-entrancy. */


#define FF_FS_REENTRANT	0
#define FF_FS_TIMEOUT	1000
/* The option FF_FS_REENTRANT switches the re-entrancy (thread safe) of the FatFs
/  module itself. Note that regardless of this option, file access to different
/  volume is always re-entrant and volume control functions, f_mount(), f_mkfs()
/  and f_fdisk() function, are always not re-entrant. Only file/directory access
/  to the same volume is under control of this featuer.
/
/   0: Disable re-entrancy. FF_FS_TIMEOUT have no effect.
/   1: Enable re-entrancy. Also user provided synchronization handlers,
/      ff_mutex_create(), ff_mutex_delete(), ff_mutex_take() and ff_mutex_give()
/      function, must be added to the project. Samples are available in ffsystem.c.
/
/  The FF_FS_TIMEOUT defines timeout period in unit of O/S time tick.
*/



/*--- End of configuration options ---*/

  • CMakeLists.txt
# Generated Cmake Pico project file

cmake_minimum_required(VERSION 3.13)

set(CMAKE_C_STANDARD 11)
set(CMAKE_CXX_STANDARD 17)

# Initialise pico_sdk from installed location
# (note this can come from environment, CMake cache etc)
set(PICO_SDK_PATH "/home/duser/pico/pico-sdk")

set(PICO_BOARD pico CACHE STRING "Board type")

# Pull in Raspberry Pi Pico SDK (must be before project)
include(pico_sdk_import.cmake)

if (PICO_SDK_VERSION_STRING VERSION_LESS "1.4.0")
  message(FATAL_ERROR "Raspberry Pi Pico SDK version 1.3.0 (or later) required. Your version is ${PICO_SDK_VERSION_STRING}")
endif()

project(spi_flash_fatfs C CXX ASM)

# Initialise the Raspberry Pi Pico SDK
pico_sdk_init()

# Add executable. Default name is the project name, version 0.1

add_executable(spi_flash_fatfs spi_flash_fatfs.c W25Q.c)

pico_set_program_name(spi_flash_fatfs "spi_flash_fatfs")
pico_set_program_version(spi_flash_fatfs "0.1")

pico_enable_stdio_uart(spi_flash_fatfs 1)
pico_enable_stdio_usb(spi_flash_fatfs 0)

# Add the standard library to the build
target_link_libraries(spi_flash_fatfs
        pico_stdlib
        hardware_flash
        FatFs)

# Add the standard include files to the build
target_include_directories(spi_flash_fatfs PRIVATE
  ${CMAKE_CURRENT_LIST_DIR}
  ${CMAKE_CURRENT_LIST_DIR}/FatFs
  ${CMAKE_CURRENT_LIST_DIR}/.. # for our common lwipopts or any other standard includes, if required
)

# Add any user requested libraries
target_link_libraries(spi_flash_fatfs
        hardware_spi
        hardware_rtc
        )

# Tell CMake where to find other source code
add_subdirectory(FatFs build)

pico_add_extra_outputs(spi_flash_fatfs)

  • W25Q.c
#include "stdio.h"
#include "stdlib.h"
#include "W25Q.h"

#include "FatFs/ff.h"
#include "FatFs/ffconf.h"

uint8_t rxbuf[10];
uint8_t txbuf[10];

w25q_data_t w25q_data;

/*=================*/


const uint8_t i_uniqueid=0x4b;
const uint8_t i_page_program=0x02;
const uint8_t i_read_data=0x03;
const uint8_t i_fast_read_data=0x0b;
const uint8_t i_write_disable=0x04;
const uint8_t i_read_status_r1=0x05;
const uint8_t i_read_status_r2=0x35;
const uint8_t i_read_status_r3=0x15;
const uint8_t i_write_status_r1=0x01;
const uint8_t i_write_status_r2=0x31;
const uint8_t i_write_status_r3=0x11;
const uint8_t i_sector_erase=0x20;
const uint8_t i_block_erase_32k=0x52;
const uint8_t i_block_erase_64k=0xd8;
const uint8_t i_write_enable=0x06;
const uint8_t i_erase_chip=0xc7;

const uint8_t i_device_id=0x90;
const uint8_t i_JEDEC_ID=0x9f;



void w25q_spi_cs_low() {
    gpio_put(w25q_data.cs_pin,0);
}
void w25q_spi_cs_high(){
    gpio_put(w25q_data.cs_pin,1);
}
void w25q_send_cmd_read(uint8_t cmd, uint32_t address, uint8_t *buf, uint32_t len, bool is_fast) {
    uint8_t addr[4];
    int addr_len=3;
    addr[3] = 0x00;
    if (is_fast) addr_len=4;
    addr[0] = (address & 0x00ff0000) >> 16;
    addr[1] = (address & 0x0000ff00) >> 8;
    addr[2] = (address & 0x000000ff);
    w25q_spi_cs_low();
    spi_write_blocking(w25q_data.spi, &cmd, 1);
    spi_write_blocking(w25q_data.spi, addr, addr_len);
    spi_read_blocking(w25q_data.spi, 0x00, buf, len);
    w25q_spi_cs_high();
}

void w25q_send_cmd_write(uint8_t cmd, uint32_t address, uint8_t *buf, uint32_t len) {
    uint8_t addr[3];
    
    addr[0] = (address & 0x00ff0000) >> 16;
    addr[1] = (address & 0x0000ff00) >> 8;
    addr[2] = (address & 0x000000ff);
    w25q_write_enable();
    w25q_spi_cs_low();
    spi_write_blocking(w25q_data.spi, &cmd, 1);
    spi_write_blocking(w25q_data.spi, addr, 3);
    spi_write_blocking(w25q_data.spi, buf, len);
    w25q_spi_cs_high();

}

void w25q_send_cmd_addr(uint8_t cmd, uint32_t address) {
    uint8_t addr[3];
    addr[0] = (address & 0x00ff0000) >> 16;
    addr[1] = (address & 0x0000ff00) >> 8;
    addr[2] = (address & 0x000000ff);
    w25q_spi_cs_low();
    spi_write_blocking(w25q_data.spi, &cmd, 1);
    spi_write_blocking(w25q_data.spi, addr, 3);
    w25q_spi_cs_high();
}

void w25q_send_cmd(uint8_t cmd, uint8_t *buf, uint32_t len) {
    w25q_spi_cs_low();
    spi_write_blocking(w25q_data.spi, &cmd, 1);
    spi_read_blocking(w25q_data.spi, 0x00, buf, len);
    w25q_spi_cs_high();
}

void w25q_send_simple_cmd(uint8_t cmd) {
    w25q_spi_cs_low();
    spi_write_blocking(w25q_data.spi, &cmd, 1);
    w25q_spi_cs_high();
}

void w25q_write_enable() {
    w25q_send_simple_cmd(i_write_enable);
    sleep_ms(1);
}
void w25q_write_disable() {
    w25q_send_simple_cmd(i_write_disable);
    sleep_ms(1);
}

/*==================*/


bool w25q_spi_init(spi_inst_t *spi, uint cs_pin) {
    w25q_data.spi = spi;
    w25q_data.cs_pin = cs_pin;
    gpio_set_dir(PIN_CS, GPIO_OUT);

    spi_init(SPI_PORT, 5000*1000);
    gpio_set_function(PIN_MISO, GPIO_FUNC_SPI);
    gpio_set_function(PIN_CS,   GPIO_FUNC_SIO);
    gpio_set_function(PIN_SCK,  GPIO_FUNC_SPI);
    gpio_set_function(PIN_MOSI, GPIO_FUNC_SPI);

    w25q_get_JEDEC_ID();
    w25q_data.lock = 1;
	sleep_ms(100);
	switch (w25q_data.jedec_id & 0x000000FF)
	{
	    case 0x20: // 	w25q512
		    w25q_data.blockCount = 1024;
		break;
	    case 0x19: // 	w25q256
		    w25q_data.blockCount = 512;
		break;
	    case 0x18: // 	w25q128
		    w25q_data.blockCount = 256;
		break;
	    case 0x17: //	w25q64
		    w25q_data.blockCount = 128;
		break;
	    case 0x16: //	w25q32
		    w25q_data.blockCount = 64;
		break;
        case 0x15: //	w25q16
            w25q_data.blockCount = 32;
            break;
        case 0x14: //	w25q80
            w25q_data.blockCount = 16;
            break;
        case 0x13: //	w25q40
            w25q_data.blockCount = 8;
        case 0x12: //	w25q20
            w25q_data.blockCount = 4;
            break;
        case 0x11: //	w25q10
            w25q_data.blockCount = 2;
            break;
        default:
            w25q_data.lock = 0;
            return false;
    }
	w25q_data.pageSize = 256;
	w25q_data.sectorSize = 0x1000;
	w25q_data.sectorCount = w25q_data.blockCount * 16;
	w25q_data.pageCount = (w25q_data.sectorCount * w25q_data.sectorSize) / w25q_data.pageSize;
	w25q_data.blockSize = w25q_data.sectorSize * 16;
	w25q_data.capacityKB = (w25q_data.sectorCount * w25q_data.sectorSize) / 1024;
	w25q_get_uid();
    w25q_read_status_register_1();
    w25q_read_status_register_2();
    w25q_read_status_register_3();
	w25q_data.lock = 0;
	return true;
}


void w25q_read_status_register_1(){
    w25q_send_cmd(i_read_status_r1, &w25q_data.statusRegister1, 1);
}
void w25q_read_status_register_2(){
    w25q_send_cmd(i_read_status_r2, &w25q_data.statusRegister2, 1);
}
void w25q_read_status_register_3(){
    w25q_send_cmd(i_read_status_r3, &w25q_data.statusRegister3, 1);
}

void w25q_write_status_register_1(){
    w25q_send_cmd(i_write_status_r1, &w25q_data.statusRegister1, 1);
}
void w25q_write_status_register_2(){
    w25q_send_cmd(i_write_status_r2, &w25q_data.statusRegister2, 1);
}
void w25q_write_status_register_3(){
    w25q_send_cmd(i_write_status_r3, &w25q_data.statusRegister3, 1);
}

void w25q_wait_for_write_end(void)
{
	sleep_ms(1);
	w25q_spi_cs_low();
	spi_write_blocking(w25q_data.spi, &i_read_status_r1,1);
	do
	{
		spi_read_blocking(w25q_data.spi, 0x00, &w25q_data.statusRegister1,1);
		sleep_ms(1);
	} while ((w25q_data.statusRegister1 & 0x01) == 0x01);
	w25q_spi_cs_high();
}

void w25q_erase_chip() {
    while (w25q_data.lock) sleep_ms(1);
    w25q_data.lock=1;
    w25q_write_enable();
    w25q_send_simple_cmd(i_erase_chip);
    w25q_wait_for_write_end();
    sleep_ms(10);
    w25q_data.lock=0;
}

void w25q_page_program(uint32_t page_addr, uint16_t offset, uint8_t *buf, uint32_t len) {
    while (w25q_data.lock) sleep_ms(1);
    w25q_data.lock=1;
    if (offset + len > w25q_data.pageSize) {
        len = w25q_data.pageSize - offset;
    }
    page_addr = (page_addr * w25q_data.pageSize) + offset;
    w25q_wait_for_write_end();
    w25q_write_enable();
    w25q_send_cmd_write(i_page_program, page_addr, buf, len);
    w25q_wait_for_write_end();
    sleep_ms(1);
    w25q_data.lock=0;
}
/*===========================*/
uint32_t w25_page_to_sector_address(uint32_t pageAddress)
{
	return ((pageAddress * w25q_data.pageSize) / w25q_data.sectorSize);
}
uint32_t w25q_page_to_block_address(uint32_t pageAddress)
{
	return ((pageAddress * w25q_data.pageSize) / w25q_data.blockSize);
}
uint32_t w25q_data_sector_to_block_address(uint32_t sectorAddress)
{
	return ((sectorAddress * w25q_data.sectorSize) / w25q_data.blockSize);
}
uint32_t w25q_sector_to_page_address(uint32_t sectorAddress)
{
	return (sectorAddress * w25q_data.sectorSize) / w25q_data.pageSize;
}
uint32_t w25q_block_to_page_address(uint32_t blockAddress)
{
	return (blockAddress * w25q_data.blockSize) / w25q_data.pageSize;
}
/*============================*/

void w25q_write_sector(uint32_t sect_addr, uint32_t offset, uint8_t *buf,  uint32_t len) {
	if (offset >= w25q_data.sectorSize) return;
    if (offset + len  > w25q_data.sectorSize) 
		len = w25q_data.sectorSize - offset;
	uint32_t startPage;
	int32_t bytesToWrite;
	uint32_t localOffset;


    startPage = w25q_sector_to_page_address(sect_addr) + (offset / w25q_data.pageSize);
	localOffset = offset % w25q_data.pageSize;
    bytesToWrite = len;

	do
	{
        w25q_page_program(startPage, localOffset, buf, bytesToWrite);
		startPage++;
		bytesToWrite -= w25q_data.pageSize - localOffset;
		buf += w25q_data.pageSize - localOffset;
		localOffset = 0;
	} while (bytesToWrite > 0);

}

void w25q_write_block_64k(uint32_t blk_addr, uint32_t offset, uint8_t *buf,  uint32_t len) {
	if ((len > w25q_data.blockSize) || (len == 0))
		len = w25q_data.blockSize;
	if (offset >= w25q_data.blockSize)
		return;
	uint32_t startPage;
	int32_t bytesToWrite;
	uint32_t localOffset;
	if ((offset + len) > w25q_data.blockSize)
		bytesToWrite = w25q_data.blockSize - offset;
	else
		bytesToWrite = len;
	startPage = w25q_block_to_page_address(blk_addr) + (offset / w25q_data.pageSize);
	localOffset = offset % w25q_data.pageSize;
	do
	{
		w25q_page_program(startPage, localOffset, buf, len);
		startPage++;
		bytesToWrite -= w25q_data.pageSize - localOffset;
		buf += w25q_data.pageSize - localOffset;
		localOffset = 0;
	} while (bytesToWrite > 0);
   
}

void w25q_read_bytes(uint32_t address, uint8_t *buf, uint32_t len) {
	while (w25q_data.lock == 1) sleep_ms(1);
	w25q_data.lock = 1;
    w25q_send_cmd_read(i_fast_read_data, address, buf, len, true);
	sleep_ms(1);
	w25q_data.lock = 0;
}

void w25q_read_page(uint32_t page_addr, uint32_t offset, uint8_t *buf,  uint32_t len) {
	while (w25q_data.lock == 1) sleep_ms(1);
	w25q_data.lock = 1;
    if (offset >= w25q_data.pageSize) return;
	if ((offset + len) >= w25q_data.pageSize)
		len = w25q_data.pageSize - offset;
	page_addr = page_addr * w25q_data.pageSize + offset;
    w25q_send_cmd_read(i_fast_read_data, page_addr, buf, len, true);
	
	sleep_ms(1);
	w25q_data.lock = 0;
}

void w25q_read_sector(uint32_t sect_addr, uint32_t offset, uint8_t *buf,  uint32_t len) {
	if (offset >= w25q_data.sectorSize) return;
    if (offset + len > w25q_data.sectorSize)
		len = w25q_data.sectorSize - offset;
	uint32_t startPage;
	int32_t bytesToRead;
	uint32_t localOffset;
    bytesToRead = len;
	
    startPage = w25q_sector_to_page_address(sect_addr) + (offset / w25q_data.pageSize);
	localOffset = offset % w25q_data.pageSize;

	do
	{
		w25q_read_page(startPage, localOffset, buf, bytesToRead);
		startPage++;
		bytesToRead -= w25q_data.pageSize - localOffset;
		buf += w25q_data.pageSize - localOffset;
		localOffset = 0;
	} while (bytesToRead > 0);

}
void w25q_read_block(uint32_t blk_addr, uint32_t offset, uint8_t *buf, uint32_t len) {
	if (offset+len > w25q_data.blockSize)
		len = w25q_data.blockSize-offset;

	uint32_t startPage;
	int32_t bytesToRead;
	uint32_t localOffset;
    bytesToRead = len;

	startPage = w25q_block_to_page_address(blk_addr) + (offset / w25q_data.pageSize);
	localOffset = offset % w25q_data.pageSize;
	do
	{
		w25q_read_page(startPage, localOffset, buf, bytesToRead);
		startPage++;
		bytesToRead -= w25q_data.pageSize - localOffset;
		buf += w25q_data.pageSize - localOffset;
		localOffset = 0;
	} while (bytesToRead > 0);

}


void w25q_sector_erase(uint32_t sect_addr) {
    while(w25q_data.lock) sleep_ms(1);
    w25q_data.lock=1;
    sect_addr = sect_addr * w25q_data.sectorSize;
    w25q_wait_for_write_end();
    w25q_write_enable();
    w25q_send_cmd_addr(i_sector_erase, sect_addr);
    w25q_wait_for_write_end();
    sleep_ms(1);
    w25q_data.lock=0;
}
void w25q_block_erase_32k(uint32_t blk_addr) {
    while(w25q_data.lock) sleep_ms(1);
    w25q_data.lock=1;
    blk_addr = blk_addr * w25q_data.sectorSize * 8;
    w25q_wait_for_write_end();
    w25q_write_enable();
    w25q_send_cmd_addr(i_block_erase_32k, blk_addr);
    w25q_wait_for_write_end();
    sleep_ms(1);
    w25q_data.lock=0;
}
void w25q_block_erase_64k(uint32_t blk_addr) {
    while(w25q_data.lock) sleep_ms(1);
    w25q_data.lock=1;
    blk_addr = blk_addr * w25q_data.sectorSize * 16;
    w25q_wait_for_write_end();
    w25q_write_enable();
    w25q_send_cmd_addr(i_block_erase_64k, blk_addr);
    w25q_wait_for_write_end();
    sleep_ms(1);
    w25q_data.lock=0;
}
void w25q_get_manufacter_device_id(uint8_t *mid){
    assert(w25q_data.spi);
    w25q_send_cmd_read(i_device_id, 0x000000, mid, 2, false);
}



void w25q_get_JEDEC_ID() {
    uint8_t temp[3];
    w25q_send_cmd(i_JEDEC_ID, temp, 3);
    w25q_data.jedec_id = ((uint32_t)temp[0] << 16) | ((uint32_t)temp[1] << 8) | (uint32_t)temp[2];
}
void w25q_get_uid() {
    assert(w25q_data.spi);
    txbuf[0]= 0x4b;
    txbuf[1] = 0x00; txbuf[2] = 0x00; txbuf[3] = 0x00;txbuf[4]=0x00;
    w25q_spi_cs_low();
    spi_write_blocking(w25q_data.spi, txbuf, 5);
    spi_read_blocking(w25q_data.spi, 0x00, w25q_data.uuid, 8);
    w25q_spi_cs_high();
}
  • W25Q.h
#ifndef W25Q_H
#define W25Q_H
#include "stdio.h"
#include "stdlib.h"
#include "pico/stdlib.h"
#include "hardware/spi.h"

#define SPI_PORT spi0
#define PIN_MISO 16
#define PIN_CS   17
#define PIN_SCK  18
#define PIN_MOSI 19

typedef struct {
    spi_inst_t *spi;
    uint        cs_pin;
    uint8_t     uuid[8];
    uint32_t    jedec_id;
    uint32_t    blockCount;
    uint32_t    pageCount;
    uint32_t    sectorCount;
    uint16_t    pageSize;
    uint32_t    sectorSize;
    uint32_t    blockSize;
    uint8_t     statusRegister1;
    uint8_t     statusRegister2;
    uint8_t     statusRegister3;
    uint32_t    capacityKB;
    uint8_t     lock;
} w25q_data_t;

bool w25q_spi_init(spi_inst_t *spi, uint cs_pin);
void w25q_get_manufacter_device_id(uint8_t *mid);
void w25q_get_JEDEC_ID();
void w25q_erase_chip();
void w25q_page_program(uint32_t page_addr, uint16_t offset, uint8_t *buf, uint32_t len);
void w25q_write_sector(uint32_t sect_addr, uint32_t offset, uint8_t *buf,  uint32_t len);
void w25q_write_block_64k(uint32_t blk_addr, uint32_t offset, uint8_t *buf,  uint32_t len);
void w25q_read_bytes(uint32_t address, uint8_t *buf, uint32_t len);
void w25q_read_page(uint32_t page_addr, uint32_t offset, uint8_t *buf,  uint32_t len);
void w25q_read_sector(uint32_t sect_addr, uint32_t offset, uint8_t *buf,  uint32_t len);
void w25q_read_block(uint32_t blk_addr, uint32_t offset, uint8_t *buf, uint32_t len);
//void w25q_read_data(uint32_t address, uint8_t *buf, uint32_t len);
//void w25q_fast_read_data(uint32_t address, uint8_t *buf, uint32_t len);
void w25q_read_status_register_1();
void w25q_read_status_register_2();
void w25q_read_status_register_3();
void w25q_write_status_register_1();
void w25q_write_status_register_2();
void w25q_write_status_register_3();
void w25q_sector_erase(uint32_t sect_addr);
void w25q_block_erase_32k(uint32_t blk_addr);
void w25q_block_erase_64k(uint32_t blk_addr);
void w25q_get_uid();
void w25q_write_enable();
void w25q_write_diable();

#endif
  • spi_flash_fatfs.c
#include <stdio.h>
#include "pico/stdlib.h"
#include "hardware/spi.h"
#include "W25Q.h"
#include "ff.h"
#include "diskio.h"
#include "hardware/flash.h"

int main()
{
    stdio_init_all();
/*
    uint8_t testbuf[256];
    uint32_t offset=0x100000; // 1M
    
    for (uint32_t i=0; i < 256; i++) {
        uint8_t c = rand() % 127;
        if (c < 33) c='A';
        testbuf[i] = c;
    }
    flash_range_erase(offset, 4096); // one sector
    printf("write into flash page:");
    for (uint32_t i=0; i < 256; i++) {
        if (i % 100 == 0) printf("\n");
        printf("%c", testbuf[i]);
    }
    printf("\n\n");
    flash_range_program(offset+256, testbuf, 256);   // second page
    printf("read out flash page:");
    for (uint32_t i=0; i < 256; i++) {
        if (i %100 == 0) printf("\n");
        printf("%c", *(uint8_t*)(XIP_BASE+offset+256+i)); //XIP_BASE + (byte offset) read out
    }
    printf("\nread finished\n\n");
//===========//

    uint8_t testbuf[4096*2];
    w25q_spi_init(SPI_PORT, PIN_CS);

    for (uint32_t i=0; i < 4096*2; i++) {
        uint8_t c = rand() % 127;
        if (c < 33) c+=33;
        testbuf[i] = c;
    }

    w25q_sector_erase(0);
    w25q_sector_erase(1);

    w25q_write_sector(0, 0, testbuf, 4096);
    w25q_write_sector(1, 0, testbuf+4096, 4096);
    printf("write data:sector 0, page 0");
    for (int i =0; i< 256; i++) {
        if (i % 100 == 0) printf("\n");
        printf("%c",testbuf[i]);
    }
    printf("\n\nwrite data:sector 1, page 0");
    for (int i =4096; i< 4096+256; i++) {
        if ((i-4096) % 100 == 0) printf("\n");
        printf("%c",testbuf[i]);
    }
    printf("\n\nclear buffer\n");
    for (uint32_t i=0; i < 4096*2; i++) {
        testbuf[i] = 0;
    }
    w25q_read_page(0, 0, testbuf,  256);
    printf("\n\nread data:sector 0, page 0");
    for (int i =0; i< 256; i++) {
        if (i % 100 == 0) printf("\n");
        printf("%c",testbuf[i]);
    }
    w25q_read_page(16, 0, testbuf, 256);
    printf("\n\nread data:sector 1, page 0");
    for (int i =0; i< 256; i++) {
        if (i % 100 == 0) printf("\n");
        printf("%c",testbuf[i]);
    }
    printf("\ntest finished\n");
   */ 


    FRESULT res;
    FATFS fs;
    FIL fil;
    UINT br;
    uint8_t work[4096*2];
    uint8_t readbuff[4096*2];
 
    res = f_mkfs("0:", 0, work, 4096);
    if(res != FR_OK) {
        printf("mkfs error\n");
        return 1;
    }
    printf("mkfs successfully(only run once)\n");

    for (uint32_t i=0; i < 4096*2; i++) {
        uint8_t c = rand() % 127;
        if (c < 31) c+=31;
        work[i] = c;
    }

    printf("write buffer:200-300\n");
    for (uint32_t i = 200; i <= 300; i++) {
        printf("%c",work[i]);
    }
    printf("\n");

    res = f_mount(&fs, "0:", 1);
    if (res != FR_OK) {
        printf("mount error\n");
        return 1;
    }
    res = f_open(&fil, "test.txt", FA_CREATE_ALWAYS|FA_WRITE);
    if (res != FR_OK) {
        printf("open error\n");
        return 1;
    }
    
    f_write(&fil, work, 4096*2, &br);
    f_close(&fil);
    
    res = f_open(&fil, "test.txt", FA_READ);
    if (res != FR_OK) {
        printf("read open error\n");
        return 1;
    }
  
    f_read(&fil, readbuff, 4096*2, &br);
    printf("read buff:200-300\n");
    for (uint32_t i = 200; i <= 300; i++) {
        printf("%c",readbuff[i]);
    }
    printf("\n\n\n");
    f_close(&fil);
    
    return 0;
}
  • FatFs/CMakeLists.txt
add_library(FatFs INTERFACE)
target_sources(FatFs INTERFACE
    ${CMAKE_CURRENT_LIST_DIR}/flash_diskio.c
    ${CMAKE_CURRENT_LIST_DIR}/ff.c
    ${CMAKE_CURRENT_LIST_DIR}/ffsystem.c
    ${CMAKE_CURRENT_LIST_DIR}/ffunicode.c
)
target_include_directories(FatFs INTERFACE
    ${CMAKE_CURRENT_LIST_DIR}/FatFs
)
target_link_libraries(FatFs INTERFACE
        hardware_spi
        hardware_dma
        pico_stdlib
)
  • FatFs/flash_diskio.c
#include "stdio.h"
#include "ff.h"			/* Obtains integer types */
#include "diskio.h"		/* Declarations of disk functions */
#include "W25Q.h"
#include "hardware/rtc.h"
#include "pico/util/datetime.h"

DSTATUS Stat = STA_NOINIT;

extern w25q_data_t w25q_data;

/*-----------------------------------------------------------------------*/
/* Get Drive Status                                                      */
/*-----------------------------------------------------------------------*/

DSTATUS disk_status (
	BYTE pdrv		/* Physical drive nmuber to identify the drive */
)
{
	return Stat;
}

/*-----------------------------------------------------------------------*/
/* Inidialize a Drive                                                    */
/*-----------------------------------------------------------------------*/

DSTATUS disk_initialize (
	BYTE pdrv				/* Physical drive nmuber to identify the drive */
)
{
	DSTATUS stat;
	Stat=STA_NOINIT;
	
	if (w25q_spi_init(SPI_PORT, PIN_CS))
		Stat = RES_OK;
	return Stat;
}

/*-----------------------------------------------------------------------*/
/* Read Sector(s)                                                        */
/*-----------------------------------------------------------------------*/

DRESULT disk_read (
	BYTE pdrv,		/* Physical drive nmuber to identify the drive */
	BYTE *buff,		/* Data buffer to store read data */
	LBA_t sector,	/* Start sector in LBA */
	UINT count		/* Number of sectors to read */
)
{
	DRESULT res;

	/* pdrv should be 0 */
	if (pdrv || !count) return RES_PARERR;

	/* no disk */
	if (Stat & STA_NOINIT) return RES_NOTRDY;
	
	w25q_read_sector(sector, 0, buff, count*w25q_data.sectorSize);

	return RES_OK;
}

/*-----------------------------------------------------------------------*/
/* Write Sector(s)                                                       */
/*-----------------------------------------------------------------------*/

#if FF_FS_READONLY == 0

DRESULT disk_write (
	BYTE pdrv,			/* Physical drive nmuber to identify the drive */
	const BYTE *buff,	/* Data to be written */
	LBA_t sector,		/* Start sector in LBA */
	UINT count			/* Number of sectors to write */
)
{
	BYTE *tbuf=(BYTE*)buff;
	while(count > 1)
	{
		w25q_sector_erase(sector);
		w25q_write_sector(sector, 0, tbuf, w25q_data.sectorSize);
		count--;
		tbuf += w25q_data.sectorSize;
		sector++;
	}
	if (count == 1)
	{
		w25q_sector_erase(sector);
		w25q_write_sector(sector, 0, tbuf, w25q_data.sectorSize);
		count--;
	}

	return count ? RES_ERROR : RES_OK;
}

#endif

/*-----------------------------------------------------------------------*/
/* Miscellaneous Functions                                               */
/*-----------------------------------------------------------------------*/

DRESULT disk_ioctl (
	BYTE pdrv,		/* Physical drive nmuber (0..) */
	BYTE cmd,		/* Control code */
	void *buff		/* Buffer to send/receive control data */
)
{
	DRESULT res = RES_ERROR;
	
    switch(cmd) {
    case CTRL_SYNC:
    	res = RES_OK;
    	break;
    case GET_SECTOR_SIZE:
    	*(WORD*)buff = (WORD)w25q_data.sectorSize; // in f_mkfs() [WORD ss] 
    	res = RES_OK;
    	break;
    case GET_BLOCK_SIZE:
    	*(DWORD*)buff = w25q_data.blockSize;
    	res = RES_OK;
    	break;
    case GET_SECTOR_COUNT:
    	*(DWORD*)buff = w25q_data.sectorCount;
    	res = RES_OK;
    	break;
    default:
    	res = RES_PARERR;
    	break;
    }

    return res;
}

DWORD get_fattime(void) {
    datetime_t t = {0, 0, 0, 0, 0, 0, 0};
    bool rc = rtc_get_datetime(&t);
    if (!rc) return 0;

    DWORD fattime = 0;
    // bit31:25
    // Year origin from the 1980 (0..127, e.g. 37 for 2017)
    uint8_t yr = t.year - 1980;
    fattime |= (0b01111111 & yr) << 25;
    // bit24:21
    // Month (1..12)
    uint8_t mo = t.month;
    fattime |= (0b00001111 & mo) << 21;
    // bit20:16
    // Day of the month (1..31)
    uint8_t da = t.day;
    fattime |= (0b00011111 & da) << 16;
    // bit15:11
    // Hour (0..23)
    uint8_t hr = t.hour;
    fattime |= (0b00011111 & hr) << 11;
    // bit10:5
    // Minute (0..59)
    uint8_t mi = t.min;
    fattime |= (0b00111111 & mi) << 5;
    // bit4:0
    // Second / 2 (0..29, e.g. 25 for 50)
    uint8_t sd = t.sec / 2;
    fattime |= (0b00011111 & sd);
    return fattime;
}

沒有留言:

張貼留言