Skip to main content

File system

If you use a computer, you're likely familiar with the concept of a file system. By sorting your files into various folders, you can keep everything neat and easy to find. In this tutorial, we'll explore how to use the file system to manage and organize your project files.

Learning goals

  • Understand how to download the Resources folder to your board.
  • Learn how to read files from SD/flash.

🔸Projects

1. Reading files

Let's begin by reading a text file and a WAV file.

Project overview

  • Read a text file and print its content.
  • Read a music file and play it using a speaker.

Example code

You can download the project source code here.

import SwiftIO
import MadBoard

@main
public struct ReadingFiles {
public static func main() {
let speaker = I2S(Id.I2S0, rate: 16_000)

do {
// let file = try FileDescriptor.open("/SD:/Resources/Document/hello.txt")
let file = try FileDescriptor.open("/lfs/Resources/Document/hello.txt")

// Repositions the offset to the end of the file.
// Get the current offset to get file size.
try file.seek(offset: 0, from: FileDescriptor.SeekOrigin.end)
let size = try file.tell()

// Reposition the offset to the beginning of the file and start reading.
var buffer = [UInt8](repeating: 0, count: size)
try file.read(fromAbsoluteOffest: 0, into: &buffer)

print(String(decoding: buffer[0..<size], as: UTF8.self))

try file.close()
} catch {
print(error)
}

do {
// let file = try FileDescriptor.open("/SD:/Resources/Music/twinkle-twinkle-little-star.wav")
let file = try FileDescriptor.open("/lfs/Resources/Music/twinkle-twinkle-little-star.wav")

try file.seek(offset: 0, from: FileDescriptor.SeekOrigin.end)

// WAV file header size.
let headerSize = 0x2C
let size = try file.tell() - headerSize

var buffer = [UInt8](repeating: 0, count: size)
try file.read(fromAbsoluteOffest: headerSize, into: &buffer, count: size)

try file.close()

speaker.write(buffer)
} catch {
print(error)
}

while true {
sleep(ms: 1000)
}
}
}

2. Writing CSV file

CSV (Comma-Separated Values) is a simple file format used to store tabular data. In this project, you will store temperature readings in a CSV file along with the corresponding time.

Project overview

  • Read the time and temperature at the start of every minute, when the seconds value is 0.
  • Write these values to the CSV file in the appropriate format.
  • Read the CSV file and print its content.

Example code

You can download the project source code here.

import SwiftIO
import MadBoard
import SHT3x
import PCF8563

@main
public struct WritingCSVFile {
public static func main() {
let i2c = I2C(Id.I2C0)
let humiture = SHT3x(i2c)
let rtc = PCF8563(i2c)

// Update the RTC time if it is not current.
let currentTime = PCF8563.Time(
year: 2024, month: 6, day: 3, hour: 15,
minute: 0, second: 0, dayOfWeek: 0
)
rtc.setTime(currentTime)

var written = false

do {
// Create a csv file on SD card.
let file = try FileDescriptor.open("/SD:/temperature.csv", options: .create)
// Create a table header.
try file.write("Time, Temperature\n")
try file.close()
} catch {
print(error)
}

while true {
let time = rtc.readTime()

// Read and store the temperature at the start of every minute.
if time.second == 0 && !written {
do {
let file = try FileDescriptor.open("/SD:/temperature.csv")
// Move file offset to the end in order to store new values.
try file.seek(offset: 0, from: FileDescriptor.SeekOrigin.end)

// Write time and temperature to the csv file.
// CSV uses commas to separate values and newlines to separate records.
let temp = humiture.readCelsius()
let string = formatDateTime(time) + ", " + String(temp) + "\n"
try file.write(string)

// Read the file content and print it out.
let size = try file.tell()
var buffer = [UInt8](repeating: 0, count: size)
try file.read(fromAbsoluteOffest: 0, into: &buffer)
print(String(decoding: buffer, as: UTF8.self))

try file.close()
} catch {
print(error)
}
written = true
} else if time.second == 59 && written {
written = false
}

sleep(ms: 10)
}

func formatNum(_ number: UInt8) -> String {
return number < 10 ? "0\(number)" : "\(number)"
}

func formatDateTime(_ time: PCF8563.Time) -> String {
var string = ""
string += "\(time.year)" + "/" + formatNum(time.month) + "/" + formatNum(time.day) + " "
string += formatNum(time.hour) + ":" + formatNum(time.minute) + ":" + formatNum(time.second)
return string
}
}
}

3. Album

In this project, we'll create a digital photo album and display your photo on the screen.

Project overview

  • Get a binary file of your photo.
  • Read the image data and send it to the screen to display the photo.

Example code

You can download the project source code here.

import SwiftIO
import MadBoard
import ST7789

@main
public struct Album {
public static func main() {
let bl = DigitalOut(Id.D2)
let rst = DigitalOut(Id.D12)
let dc = DigitalOut(Id.D13)
let cs = DigitalOut(Id.D5)
let spi = SPI(Id.SPI0, speed: 30_000_000)
let screen = ST7789(spi: spi, cs: cs, dc: dc, rst: rst, bl: bl, rotation: .angle90)

// Read the image from the specified path and display it on the screen.
do {
// You can get the binary file of your photo here: https://lvgl.io/tools/imageconverter.
// The output format should be Binary RGB565 Swap.
let file = try FileDescriptor.open("/lfs/Resources/Photo/cat.bin")

var buffer = [UInt16](repeating: 0, count: 240 * 240)

try buffer.withUnsafeMutableBytes() {
try file.read(fromAbsoluteOffest: 0, into: $0)
}

screen.writeScreen(buffer)

try file.close()
} catch {
print(error)
}

while true {
sleep(ms: 1000)
}
}
}

The Online Image Converter on the LVGL website allows you to convert your image to a binary file with raw pixel data. The data can be directly used to draw on a screen. Ensure that the output format matches the screen, which is Binary RGB565 Swap.

🔸API

FileDescriptor

This class allows you to read from and write to a file specified by its file path.

static func open(
_ path: String,
_ mode: FileDescriptor.AccessMode = .readWrite,
options: FileDescriptor.OpenOptions = FileDescriptor.OpenOptions()
) throws -> FileDescriptor

Open or create the file located at the specified path. To create a file, set options to .create. Ensure the directory exists at the given path.

Parameter:

  • path: the file path
  • mode: the read and write access to use.
  • options: the behavior for opening the file.

Return value:

  • A FileDescriptor for the file.
func read(
fromAbsoluteOffest offset: Int,
into buffer: inout [UInt8],
count: Int? = nil
) throws -> Int

Reads bytes at the specified offset from the file.

Parameter:

  • offset: the file offset where reading begins.
  • buffer: the buffer to read into.
  • count: the number of bytes to read. If nil, it equals the buffer count.

Return value:

  • The number of bytes that were read.
func write(_ string: String) throws -> Int

Writes a string at the current file offset.

Parameter:

  • string: a string to write.

Return value:

  • The number of bytes that were written.
func close() throws

Close the file.

func seek(
offset: Int,
from whence: FileDescriptor.SeekOrigin = .start
) throws -> Int

Reposition the file offset. The file's current offset will be whence + offset.

Parameter:

  • offset: the number of bytes to seek.
  • whence: the reference position used to calculate the new offset: start, end or current.

Return value:

  • 0 on success or an error code on failure.
func tell() throws -> Int

Get the current offset.

Return value:

  • Current file offset.

🔸Background

File system

Just like a real cabinet helps you organize your physical items, a file system is essential for managing your digital content.

Think of folders as virtual drawers in your digital cabinet. You can group related files together in a folder to keep everything tidy and easy to access. For instance, you might have a "Photos" folder for all your pictures or a "Documents" folder for your work files. With an organized file system, you can quickly locate files based on their names or their folder locations.

In projects, we use the file system to manage resource files like music, images, fonts and more. You have the option to store these resource files either on the onboard flash or on an SD card.

  • Onboard Flash: The SwiftIO Micro's onboard flash stores the binary file for your project. Additionally, it offers around 8MB of space for a filesystem, mounted at /lfs.
  • SD Card: Alternatively, you can utilize an SD card with the board. SD cards typically come with a built-in file system, such as FAT16, FAT32, or exFAT, mounted at /SD:.

How to download resources

  1. Create a Resources folder within your project folder to store all necessary files.
  2. Click the Copy Resources button in the MadMachine extension to copy the entire Resources folder to your desired location.
Click Copy Resources
  1. Choose the destination for your resources:
  • flash: mounted at /lfs.
  • SD card: mounted at /SD:.

There are two modes to copy resources to the specified location:

  • sync: ensures that the Resources folder on the destination becomes an exact replica of the source by copying new files, updating existing ones, and deleting any extra files in the destination.
  • merge: updates the Resources folder on the destination with new and changed files from the source while keeping any extra files in the destination.
Click Copy Resources

After a while, your resources will be successfully downloaded.

Click Copy Resources