├── .gitmodules ├── CMakeLists.txt ├── LICENSE ├── example0.c ├── example2.c ├── example1.c └── README.md /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "littlefs-lib"] 2 | path = littlefs-lib 3 | url = https://github.com/lurk101/littlefs-lib.git 4 | [submodule "stdinit-lib"] 5 | path = stdinit-lib 6 | url = https://github.com/lurk101/stdinit-lib.git 7 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.13) 2 | set(CMAKE_C_STANDARD 11) 3 | set(CMAKE_CXX_STANDARD 17) 4 | include($ENV{PICO_SDK_PATH}/external/pico_sdk_import.cmake) 5 | project(example C CXX ASM) 6 | pico_sdk_init() 7 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DPICO_USE_MALLOC_MUTEX=1") 8 | add_subdirectory(littlefs-lib) 9 | add_subdirectory(stdinit-lib) 10 | foreach(name IN ITEMS example0 example1 example2) 11 | add_executable(${name} ${name}.c) 12 | pico_enable_stdio_uart(${name} 1) 13 | pico_enable_stdio_usb(${name} 0) 14 | target_link_libraries(${name} PRIVATE pico_stdlib littlefs-lib stdinit-lib) 15 | pico_add_extra_outputs(${name}) 16 | endforeach() 17 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g") 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2021, lurk101 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | 3. Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /example0.c: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 1883 Thomas Edison - All Rights Reserved 2 | * You may use, distribute and modify this code under the 3 | * terms of the BSD 3 clause license, which unfortunately 4 | * won't be written for another century. 5 | * 6 | * SPDX-License-Identifier: BSD-3-Clause 7 | * 8 | * A little flash file system for the Raspberry Pico 9 | * 10 | */ 11 | 12 | #include "pico/stdio.h" 13 | 14 | #include "pico_hal.h" 15 | #include "stdinit.h" 16 | 17 | /* mount counter. 18 | * In this example we'll check increment the mount count file content. 19 | */ 20 | 21 | int main() { 22 | // Initialize the console 23 | stdio_init(); 24 | // Optionally format, the mount the file system 25 | printf("format (N/y) ? "); // try to clear the screen 26 | char c = getchar(); 27 | printf("\n"); 28 | if (pico_mount((c | ' ') == 'y') != LFS_ERR_OK) { 29 | printf("Error mounting FS\n"); 30 | exit(-1); 31 | } 32 | // Show file system sizes 33 | struct pico_fsstat_t stat; 34 | pico_fsstat(&stat); 35 | printf("FS: blocks %d, block size %d, used %d\n", (int)stat.block_count, (int)stat.block_size, 36 | (int)stat.blocks_used); 37 | // increment the boot count with each invocation 38 | lfs_size_t boot_count; 39 | // Open (create if doesn't exist) the boot count file 40 | int file = pico_open("boot_count", LFS_O_RDWR | LFS_O_CREAT); 41 | boot_count = 0; 42 | // Read previous boot count. If file was just created read will return 0 bytes 43 | pico_read(file, &boot_count, sizeof(boot_count)); 44 | // Increment the count 45 | boot_count += 1; 46 | // Write it back after seeking to start of file 47 | pico_rewind(file); 48 | pico_write(file, &boot_count, sizeof(boot_count)); 49 | // save the file size 50 | int pos = pico_lseek(file, 0, LFS_SEEK_CUR); 51 | // Close the file, making sure all buffered data is flushed 52 | pico_close(file); 53 | // Unmount the file system, freeing memory 54 | pico_unmount(); 55 | // Report 56 | printf("Boot count: %d\n", (int)boot_count); 57 | printf("File size (should be 4) : %d\n", pos); 58 | } 59 | -------------------------------------------------------------------------------- /example2.c: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 1883 Thomas Edison - All Rights Reserved 2 | * You may use, distribute and modify this code under the 3 | * terms of the BSD 3 clause license, which unfortunately 4 | * won't be written for another century. 5 | * 6 | * SPDX-License-Identifier: BSD-3-Clause 7 | * 8 | * A little flash file system for the Raspberry Pico 9 | * 10 | */ 11 | 12 | #include "hardware/regs/addressmap.h" 13 | #include "pico/stdio.h" 14 | #include "pico/stdlib.h" 15 | #include "pico/time.h" 16 | 17 | #include "pico_hal.h" 18 | #include "stdinit.h" 19 | 20 | #define FILE_SIZE (200 * 1024) 21 | #define BUF_WRDS (1024 / sizeof(uint32_t)) 22 | 23 | static uint32_t buf[BUF_WRDS]; 24 | 25 | // application entry point 26 | int main(void) { 27 | // Initialize the console 28 | stdio_init(); 29 | // Set the RNG seed 30 | printf("Hit any key\n"); 31 | getchar(); 32 | unsigned seed = time_us_32(); 33 | srand(seed); 34 | // Mount the file system 35 | pico_mount(false); 36 | // Display file system sizes 37 | struct pico_fsstat_t stat; 38 | pico_fsstat(&stat); 39 | printf("FS: blocks %d, block size %d, used %d\n", (int)stat.block_count, (int)stat.block_size, 40 | (int)stat.blocks_used); 41 | // Create a big file 42 | const char* fn = "big_file"; 43 | int file = -1; 44 | uint32_t i, n; 45 | printf("Creating %dK file\n", (int)(FILE_SIZE / 1024)); 46 | file = pico_open(fn, LFS_O_WRONLY | LFS_O_CREAT); 47 | int err = 0; 48 | if (file < 0) { 49 | printf("open fails\n"); 50 | goto fail; 51 | } 52 | n = FILE_SIZE; 53 | for (n = 0; n < FILE_SIZE; n += sizeof(buf)) { 54 | for (i = 0; i < BUF_WRDS; i++) 55 | buf[i] = rand(); 56 | lfs_size_t len = pico_write(file, buf, sizeof(buf)); 57 | if (sizeof(buf) != (uint32_t)len) { 58 | printf("write fails at %d returned %d\n", (int)n, (int)len); 59 | goto fail; 60 | } 61 | } 62 | pico_close(file); 63 | // Display file system sizes 64 | pico_fsstat(&stat); 65 | printf("FS: blocks %d, block size %d, used %d\n", (int)stat.block_count, (int)stat.block_size, 66 | (int)stat.blocks_used); 67 | // Read back and verify the contensts 68 | printf("reading %dK file\n", (int)(FILE_SIZE / 1024)); 69 | file = pico_open(fn, LFS_O_RDONLY); 70 | if (file < 0) { 71 | printf("open fails\n"); 72 | goto fail; 73 | } 74 | n = FILE_SIZE; 75 | srand(seed); 76 | for (n = 0; n < FILE_SIZE; n += sizeof(buf)) { 77 | lfs_size_t len = pico_read(file, buf, sizeof(buf)); 78 | if (sizeof(buf) != (uint32_t)len) { 79 | printf("read fails at %d returned %d\n", (int)n, (int)len); 80 | goto fail; 81 | } 82 | for (i = 0; i < BUF_WRDS; i++) 83 | if (buf[i] != (uint32_t)rand()) { 84 | printf("data mismatch at %d\n", (int)n); 85 | goto fail; 86 | } 87 | } 88 | pico_close(file); 89 | // Delete the file 90 | printf("removing file\n"); 91 | if (pico_remove(fn) < 0) { 92 | printf("remove fails\n"); 93 | goto fail; 94 | } 95 | pico_fsstat(&stat); 96 | printf("FS: blocks %d, block size %d, used %d\n", (int)stat.block_count, (int)stat.block_size, 97 | (int)stat.blocks_used); 98 | // release any resources we were using 99 | pico_unmount(); 100 | printf("Pass\n"); 101 | return 0; 102 | fail: 103 | if (file >= 0) 104 | pico_close(file); 105 | pico_unmount(); 106 | printf("Fail\n"); 107 | return -1; 108 | } 109 | -------------------------------------------------------------------------------- /example1.c: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 1883 Thomas Edison - All Rights Reserved 2 | * You may use, distribute and modify this code under the 3 | * terms of the BSD 3 clause license, which unfortunately 4 | * won't be written for another century. 5 | * 6 | * SPDX-License-Identifier: BSD-3-Clause 7 | * 8 | * A little flash file system for the Raspberry Pico 9 | * 10 | */ 11 | 12 | #include "pico/stdio.h" 13 | #include "pico/stdlib.h" 14 | 15 | #include "pico_hal.h" 16 | #include "stdinit.h" 17 | 18 | /* We'll create 32 files with randon names, verify them, rename them, reverify, 19 | * and delete them. 20 | */ 21 | 22 | void dump_dir(void) { 23 | // display each directory entry name 24 | printf("File list\n"); 25 | int dir = pico_dir_open("/"); 26 | if (dir < 0) 27 | return; 28 | struct lfs_info info; 29 | while (pico_dir_read(dir, &info) > 0) 30 | printf("%s\n", info.name); 31 | pico_dir_close(dir); 32 | printf("End of list\n"); 33 | } 34 | 35 | int main() { 36 | const char* fn_templ1 = "1%u.tst"; 37 | const char* fn_templ2 = "2%u.tst"; 38 | 39 | // Initialize console 40 | stdio_init(); 41 | // Seed the random number generator 42 | printf("Hit any key\n"); 43 | getchar(); 44 | unsigned seed = time_us_32(); 45 | srand(seed); 46 | // Mount the file stsrem 47 | if (pico_mount(false) < 0) { 48 | printf("Mount failed\n"); 49 | exit(-1); 50 | } 51 | dump_dir(); 52 | // File handle and file name 53 | int file; 54 | char fn[32], fn2[32]; 55 | // Create 32 file 56 | printf("Creating 32 files\n"); 57 | for (int i = 0; i < 32; i++) { 58 | // Get numeric part of file name 59 | unsigned n = rand(); 60 | // Create file name string 61 | sprintf(fn, fn_templ1, n); 62 | // Create the file 63 | file = pico_open(fn, LFS_O_WRONLY | LFS_O_CREAT); 64 | if ((int)file < 0) { 65 | printf("open fails\n"); 66 | goto fail; 67 | } 68 | // Write the file name to the file 69 | if ((strlen(fn) + 1) != (uint32_t)pico_write(file, fn, strlen(fn) + 1)) { 70 | printf("write fails\n"); 71 | goto fail; 72 | } 73 | // flush and close the file 74 | pico_close(file); 75 | } 76 | dump_dir(); 77 | // Unmount & remount 78 | pico_unmount(); 79 | pico_mount(false); 80 | // Display file system sizes 81 | struct pico_fsstat_t stat; 82 | pico_fsstat(&stat); 83 | printf("FS: blocks %d, block size %d, used %d\n", (int)stat.block_count, (int)stat.block_size, 84 | (int)stat.blocks_used); 85 | printf("Renaming 32 files\n"); 86 | // Reset the random sequence 87 | srand(seed); 88 | for (int i = 0; i < 32; i++) { 89 | // Get numeric part of file name 90 | unsigned n = rand(); 91 | unsigned n2 = n ^ 0xffff; 92 | sprintf(fn, fn_templ1, n); 93 | sprintf(fn2, fn_templ2, n2); 94 | // rename 95 | if (pico_rename(fn, fn2) < 0) { 96 | printf("rename fails\n"); 97 | goto fail; 98 | } 99 | } 100 | dump_dir(); 101 | // Display file system sizes 102 | pico_fsstat(&stat); 103 | printf("FS: blocks %d, block size %d, used %d\n", (int)stat.block_count, (int)stat.block_size, 104 | (int)stat.blocks_used); 105 | printf("Verifying then removing 32 files\n"); 106 | char buf[32]; 107 | // Reset the random sequence 108 | srand(seed); 109 | for (int i = 0; i < 32; i++) { 110 | // Get numeric part of file name 111 | unsigned n = rand(); 112 | unsigned n2 = n ^ 0xffff; 113 | sprintf(fn, fn_templ1, n); 114 | sprintf(fn2, fn_templ2, n2); 115 | // verify the file's content 116 | file = pico_open(fn2, LFS_O_RDONLY); 117 | if (file < 0) { 118 | printf("open fails\n"); 119 | goto fail; 120 | } 121 | lfs_size_t len = pico_read(file, buf, sizeof(buf)); 122 | if (strcmp(fn, buf) != 0) { 123 | printf("read fails\n"); 124 | goto fail; 125 | } 126 | pico_close(file); 127 | // Delete the file 128 | if (pico_remove(fn2) < 0) { 129 | printf("remove fails\n"); 130 | goto fail; 131 | } 132 | } 133 | dump_dir(); 134 | // Display file system sizes 135 | pico_fsstat(&stat); 136 | printf("FS: blocks %d, block size %d, used %d\n", (int)stat.block_count, (int)stat.block_size, 137 | (int)stat.blocks_used); 138 | // Release any resources we were using 139 | pico_unmount(); 140 | printf("Pass\n"); 141 | return 0; 142 | fail: 143 | pico_unmount(); 144 | printf("Fail\n"); 145 | return -1; 146 | } 147 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pico-littlefs 2 | 3 | A small C/C++ Posix like journaling file system for the Raspberry Pico using a size configurable 4 | portion of its SPI flash. Adapted from the [little-fs ARM project](https://github.com/littlefs-project/littlefs.git). 5 | 6 | Building 7 | ``` 8 | git clone https://github.com/lurk101/pico-littlefs.git 9 | cd pico-littlefs.git 10 | git submodule update --init 11 | mkdir b 12 | cd b 13 | cmake .. 14 | make 15 | ``` 16 | Pertinent define near the top of file lfs/pico_hal.c determines the size of the flash file system 17 | located to the top of flash. 18 | ``` 19 | #define FS_SIZE (256 * 1024) 20 | ``` 21 | Functions 22 | ``` 23 | // Mounts a file system 24 | // 25 | // Optionally formats a new file system. 26 | // 27 | // Returns a negative error code on failure. 28 | int pico_mount(bool format); 29 | 30 | // Unmounts a file system 31 | // 32 | // Returns a negative error code on failure. 33 | int pico_unmount(void); 34 | 35 | // Removes a file or directory 36 | // 37 | // If removing a directory, the directory must be empty. 38 | // Returns a negative error code on failure. 39 | int pico_remove(const char* path); 40 | 41 | // Open a file 42 | // 43 | // The mode that the file is opened in is determined by the flags, which 44 | // are values from the enum lfs_open_flags that are bitwise-ored together. 45 | // 46 | // Returns opened file handle. Returns a negative error code on failure. 47 | int pico_open(const char* path, int flags); 48 | 49 | // Close a file 50 | // 51 | // Any pending writes are written out to storage as though 52 | // sync had been called and releases any allocated resources. 53 | // 54 | // Returns a negative error code on failure. 55 | int pico_close(int file); 56 | 57 | // Return file system statistics 58 | // 59 | // Fills out the pico_fsstat_t structure, based on the specified file or 60 | // directory. Returns a negative error code on failure. 61 | int pico_fsstat(struct pico_fsstat_t* stat); 62 | 63 | // Change the position of the file to the beginning of the file 64 | // 65 | // Equivalent to pico_lseek(lfs, file, 0, LFS_SEEK_SET) 66 | // Returns a negative error code on failure. 67 | int pico_rewind(int file); 68 | 69 | // Rename or move a file or directory 70 | // 71 | // If the destination exists, it must match the source in type. 72 | // If the destination is a directory, the directory must be empty. 73 | // 74 | // Returns a negative error code on failure. 75 | int pico_rename(const char* oldpath, const char* newpath); 76 | 77 | // Read data from file 78 | // 79 | // Takes a buffer and size indicating where to store the read data. 80 | // Returns the number of bytes read, or a negative error code on failure. 81 | lfs_size_t pico_read(int file, void* buffer, lfs_size_t size); 82 | 83 | // Write data to file 84 | // 85 | // Takes a buffer and size indicating the data to write. The file will not 86 | // actually be updated on the storage until either sync or close is called. 87 | // 88 | // Returns the number of bytes written, or a negative error code on failure. 89 | lfs_size_t pico_write(int file, const void* buffer, lfs_size_t size); 90 | 91 | // Change the position of the file 92 | // 93 | // The change in position is determined by the offset and whence flag. 94 | // Returns the new position of the file, or a negative error code on failure. 95 | lfs_soff_t pico_lseek(int file, lfs_soff_t off, int whence); 96 | 97 | // Truncates the size of the file to the specified size 98 | // 99 | // Returns a negative error code on failure. 100 | int pico_truncate(int file, lfs_off_t size); 101 | 102 | // Return the position of the file 103 | // 104 | // Equivalent to pico_lseek(file, 0, LFS_SEEK_CUR) 105 | // Returns the position of the file, or a negative error code on failure. 106 | lfs_soff_t pico_tell(int file); 107 | 108 | // Find info about a file or directory 109 | // 110 | // Fills out the info structure, based on the specified file or directory. 111 | // Returns a negative error code on failure. 112 | int pico_stat(const char* path, struct lfs_info* info); 113 | 114 | // Get a custom attribute 115 | // 116 | // Custom attributes are uniquely identified by an 8-bit type and limited 117 | // to LFS_ATTR_MAX bytes. When read, if the stored attribute is smaller than 118 | // the buffer, it will be padded with zeros. If the stored attribute is larger, 119 | // then it will be silently truncated. If no attribute is found, the error 120 | // LFS_ERR_NOATTR is returned and the buffer is filled with zeros. 121 | // 122 | // Returns the size of the attribute, or a negative error code on failure. 123 | // Note, the returned size is the size of the attribute on disk, irrespective 124 | // of the size of the buffer. This can be used to dynamically allocate a buffer 125 | // or check for existance. 126 | lfs_ssize_t pico_getattr(const char* path, uint8_t type, void* buffer, lfs_size_t size); 127 | 128 | // Set custom attributes 129 | // 130 | // Custom attributes are uniquely identified by an 8-bit type and limited 131 | // to LFS_ATTR_MAX bytes. If an attribute is not found, it will be 132 | // implicitly created. 133 | // 134 | // Returns a negative error code on failure. 135 | int pico_setattr(const char* path, uint8_t type, const void* buffer, lfs_size_t size); 136 | 137 | // Removes a custom attribute 138 | // 139 | // If an attribute is not found, nothing happens. 140 | // 141 | // Returns a negative error code on failure. 142 | int pico_removeattr(const char* path, uint8_t type); 143 | 144 | // Open a file with extra configuration 145 | // 146 | // The mode that the file is opened in is determined by the flags, which 147 | // are values from the enum lfs_open_flags that are bitwise-ored together. 148 | // 149 | // The config struct provides additional config options per file as described 150 | // above. The config struct must be allocated while the file is open, and the 151 | // config struct must be zeroed for defaults and backwards compatibility. 152 | // 153 | // Returns a negative error code on failure. 154 | int pico_opencfg(int file, const char* path, int flags, const struct lfs_file_config* config); 155 | 156 | // Synchronize a file and storage 157 | // 158 | // Any pending writes are written out to storage. 159 | // Returns a negative error code on failure. 160 | int pico_fflush(int file); 161 | 162 | // Return the size of the file 163 | // 164 | // Similar to pico_lseek(file, 0, LFS_SEEK_END) 165 | // Returns the size of the file, or a negative error code on failure. 166 | lfs_soff_t pico_size(int file); 167 | 168 | // Create a directory 169 | // 170 | // Returns a negative error code on failure. 171 | int pico_mkdir(const char* path); 172 | 173 | // Open a directory 174 | // 175 | // Once open a directory can be used with read to iterate over files. 176 | // Returns a negative error code on failure. 177 | int pico_dir_open(int dir, const char* path); 178 | 179 | // Close a directory 180 | // 181 | // Releases any allocated resources. 182 | // Returns a negative error code on failure. 183 | int pico_dir_close(int dir); 184 | 185 | // Read an entry in the directory 186 | // 187 | // Fills out the info structure, based on the specified file or directory. 188 | // Returns a positive value on success, 0 at the end of directory, 189 | // or a negative error code on failure. 190 | int pico_dir_read(int dir, struct lfs_info* info); 191 | 192 | // Change the position of the directory 193 | // 194 | // The new off must be a value previous returned from tell and specifies 195 | // an absolute offset in the directory seek. 196 | // 197 | // Returns a negative error code on failure. 198 | int pico_dir_seek(int dir, lfs_off_t off); 199 | 200 | // Return the position of the directory 201 | // 202 | // The returned offset is only meant to be consumed by seek and may not make 203 | // sense, but does indicate the current position in the directory iteration. 204 | // 205 | // Returns the position of the directory, or a negative error code on failure. 206 | lfs_soff_t pico_dir_tell(int dir); 207 | 208 | // Change the position of the directory to the beginning of the directory 209 | // 210 | // Returns a negative error code on failure. 211 | int pico_dir_rewind(int dir); 212 | 213 | // Return pointer to string representation of error code. 214 | // 215 | // Returns a negative error code on failure. 216 | const char* pico_errmsg(int err); 217 | 218 | ``` 219 | 220 | --------------------------------------------------------------------------------