diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index acd434c..00eb05d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -6,20 +6,14 @@ on: branches: - 'feature/**' - 'fix/**' - - develop - - main - - 'release/**' paths: - 'Sources/**' - 'Examples/**' - 'Tests/**' - pull_request: - branches: - - main - schedule: - - cron: '1 0 * * 4' jobs: build: - uses: madmachineio/actions/.github/workflows/build.yml@main \ No newline at end of file + uses: madmachineio/actions/.github/workflows/build.yml@main + with: + runners: '["ubuntu-24.04", "macos-13"]' diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..1753542 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,20 @@ +name: release + + +on: + push: + branches: + - main + - 'release/**' + pull_request: + branches: + - main + schedule: + - cron: '35 20 * * 5' + + +jobs: + build: + uses: madmachineio/actions/.github/workflows/build.yml@main + with: + runners: '["ubuntu-22.04", "ubuntu-24.04", "macos-13", "macos-14", "macos-15"]' diff --git a/Examples/SwiftIOPlayground/13MoreProjects/AnimatedEyes/.gitignore b/Examples/SwiftIOPlayground/13MoreProjects/AnimatedEyes/.gitignore new file mode 100644 index 0000000..966161a --- /dev/null +++ b/Examples/SwiftIOPlayground/13MoreProjects/AnimatedEyes/.gitignore @@ -0,0 +1,91 @@ +# Xcode +# +# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore + +## User settings +xcuserdata/ + +## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) +*.xcscmblueprint +*.xccheckout + +## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) +build/ +DerivedData/ +*.moved-aside +*.pbxuser +!default.pbxuser +*.mode1v3 +!default.mode1v3 +*.mode2v3 +!default.mode2v3 +*.perspectivev3 +!default.perspectivev3 + +## Obj-C/Swift specific +*.hmap + +## App packaging +*.ipa +*.dSYM.zip +*.dSYM + + +## Playgrounds +timeline.xctimeline +playground.xcworkspace + +# Swift Package Manager +# +# Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. +# Packages/ +# Package.pins +Package.resolved +# *.xcodeproj +# +# Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata +# hence it is not needed unless you have added a package configuration file to your project +.swiftpm +.build/ +.DS_Store + +# CocoaPods +# +# We recommend against adding the Pods directory to your .gitignore. However +# you should judge for yourself, the pros and cons are mentioned at: +# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control +# +# Pods/ +# +# Add this line if you want to avoid checking in source code from the Xcode workspace +# *.xcworkspace + +# Carthage +# +# Add this line if you want to avoid checking in source code from Carthage dependencies. +# Carthage/Checkouts + +Carthage/Build/ + +# Accio dependency management +Dependencies/ +.accio/ + +# fastlane +# +# It is recommended to not store the screenshots in the git repo. +# Instead, use fastlane to re-generate the screenshots whenever they are needed. +# For more information about the recommended setup visit: +# https://docs.fastlane.tools/best-practices/source-control/#source-control + +fastlane/report.xml +fastlane/Preview.html +fastlane/screenshots/**/*.png +fastlane/test_output + +# Code Injection +# +# After new code Injection tools there's a generated folder /iOSInjectionProject +# https://github.com/johnno1962/injectionforxcode + +iOSInjectionProject/ diff --git a/Examples/SwiftIOPlayground/13MoreProjects/AnimatedEyes/Package.mmp b/Examples/SwiftIOPlayground/13MoreProjects/AnimatedEyes/Package.mmp new file mode 100644 index 0000000..cfcf88e --- /dev/null +++ b/Examples/SwiftIOPlayground/13MoreProjects/AnimatedEyes/Package.mmp @@ -0,0 +1,28 @@ +# This is a MadMachine project file in TOML format +# This file contains parameters that cannot be managed by SwiftPM +# Editing this file will alter the behavior of the build/download process +# Project files within dependent libraries will be IGNORED + +# Specify the board name below +# Supported boards are listed as follows +# "SwiftIOBoard" +# "SwiftIOMicro" +board = "SwiftIOMicro" + +# Specify the target triple below +# Supported architectures are listed as follows +# "thumbv7em-unknown-none-eabi" +# "thumbv7em-unknown-none-eabihf" +# "armv7em-none-none-eabi" +triple = "armv7em-none-none-eabi" + +# Enable or disable hardware floating-point support below +# If your code involves significant floating-point calculations, please set it to 'true' +hard-float = true + +# Enable or disable float register below +# If your code involves significant floating-point calculations, please set it to 'true' +float-abi = false + +# Reserved for future use +version = 1 diff --git a/Examples/SwiftIOPlayground/13MoreProjects/AnimatedEyes/Package.swift b/Examples/SwiftIOPlayground/13MoreProjects/AnimatedEyes/Package.swift new file mode 100644 index 0000000..e20ab7e --- /dev/null +++ b/Examples/SwiftIOPlayground/13MoreProjects/AnimatedEyes/Package.swift @@ -0,0 +1,28 @@ +// swift-tools-version: 5.9 +// The swift-tools-version declares the minimum version of Swift required to build this package. + +import PackageDescription + +let package = Package( + name: "AnimatedEyes", + dependencies: [ + // Dependencies declare other packages that this package depends on. + .package(url: "https://github.com/madmachineio/SwiftIO.git", branch: "main"), + .package(url: "https://github.com/madmachineio/MadBoards.git", branch: "main"), + .package(url: "https://github.com/madmachineio/MadDrivers.git", branch: "main"), + .package(url: "https://github.com/madmachineio/CFreeType.git", from: "2.13.2"), + ], + targets: [ + // Targets are the basic building blocks of a package, defining a module or a test suite. + // Targets can depend on other targets in this package and products from dependencies. + .executableTarget( + name: "AnimatedEyes", + dependencies: [ + "SwiftIO", + "MadBoards", + // Use specific library name rather than "MadDrivers" would speed up the build procedure. + .product(name: "ST7789", package: "MadDrivers"), + "CFreeType", + ]), + ] +) diff --git a/Examples/SwiftIOPlayground/13MoreProjects/AnimatedEyes/Sources/AnimatedEyes/AnimatedEyes.swift b/Examples/SwiftIOPlayground/13MoreProjects/AnimatedEyes/Sources/AnimatedEyes/AnimatedEyes.swift new file mode 100644 index 0000000..a2b0c9d --- /dev/null +++ b/Examples/SwiftIOPlayground/13MoreProjects/AnimatedEyes/Sources/AnimatedEyes/AnimatedEyes.swift @@ -0,0 +1,34 @@ +import SwiftIO +import MadBoard +import MadGraphics +import ST7789 + + +@main +public struct AnimatedEyes { + public static func main() { + // Initialize the SPI pin and the digital pins for the LCD. + 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) + // Initialize the LCD using the pins above. Rotate the screen to keep the original at the upper left. + let screen = ST7789(spi: spi, cs: cs, dc: dc, rst: rst, bl: bl, rotation: .angle90) + + var eyes = Eyes.close + let animatedEyes = EyesAnimation(screen: screen) + + while true { + animatedEyes.draw(eyes: eyes) + + var rawValue = eyes.rawValue + 1 + if rawValue == Eyes.allCases.count { + rawValue = 0 + } + eyes = Eyes(rawValue: rawValue)! + + sleep(ms: 1000) + } + } +} \ No newline at end of file diff --git a/Examples/SwiftIOPlayground/13MoreProjects/AnimatedEyes/Sources/AnimatedEyes/Eyes.swift b/Examples/SwiftIOPlayground/13MoreProjects/AnimatedEyes/Sources/AnimatedEyes/Eyes.swift new file mode 100644 index 0000000..b0f475e --- /dev/null +++ b/Examples/SwiftIOPlayground/13MoreProjects/AnimatedEyes/Sources/AnimatedEyes/Eyes.swift @@ -0,0 +1,323 @@ +import MadGraphics +import SwiftIO +import ST7789 + +enum Eyes: Int, CaseIterable { + case close + case normal + case blink + case happy + case sad + case upset + case angry + case wonder + case move + case saccade +} + +class EyesAnimation { + let screen: ST7789 + var screenBuffer: [UInt16] + let canvas: Canvas + let eyeColor: Pixel + let backgroundColor: Pixel + + init(screen: ST7789) { + self.screen = screen + canvas = Canvas(width: screen.width, height: screen.height) + screenBuffer = [UInt16](repeating: 0, count: screen.width * screen.height) + backgroundColor = Pixel.black + eyeColor = Pixel.aqua + } + + func draw(eyes: Eyes) { + switch eyes { + case .close: + close() + updateDisplay(canvas: canvas, screenBuffer: &screenBuffer, screen: screen) + case .normal: + normal() + updateDisplay(canvas: canvas, screenBuffer: &screenBuffer, screen: screen) + case .blink: + blink() + case .happy: + happy() + case .sad: + sad() + case .upset: + upset() + case .angry: + angry() + case .wonder: + wonder() + case .move: + move(right: false) + sleep(ms: 200) + move(right: true) + case .saccade: + saccade(right: 0, up: 1) + sleep(ms: 200) + saccade(right: -1, up: 0) + sleep(ms: 200) + saccade(right: 0, up: -1) + sleep(ms: 200) + saccade(right: 1, up: 0) + sleep(ms: 200) + } + } + + func close() { + _ = canvas.fillRectangle(at: Point.zero, width: canvas.width, height: canvas.height, data: backgroundColor) + _ = canvas.fillRoundedRectangle(at: Point(10, 105), width: 100, height: 30, radius: 10, data: eyeColor) + _ = canvas.fillRoundedRectangle(at: Point(130, 105), width: 100, height: 30, radius: 10, data: eyeColor) + } + + func normal() { + _ = canvas.fillRectangle(at: Point.zero, width: canvas.width, height: canvas.height, data: backgroundColor) + _ = canvas.fillRoundedRectangle(at: Point(20, 85), width: 80, height: 70, radius: 24, data: eyeColor) + _ = canvas.fillRoundedRectangle(at: Point(140, 85), width: 80, height: 70, radius: 24, data: eyeColor) + } + + func blink() { + normal() + updateDisplay(canvas: canvas, screenBuffer: &screenBuffer, screen: screen) + sleep(ms: 500) + + close() + updateDisplay(canvas: canvas, screenBuffer: &screenBuffer, screen: screen) + sleep(ms: 100) + + normal() + updateDisplay(canvas: canvas, screenBuffer: &screenBuffer, screen: screen) + } + + func happy() { + for i in 0...5 { + normal() + _ = canvas.fillCircle(at: Point(60, 200 - i * 5), radius: 60, data: backgroundColor) + _ = canvas.fillCircle(at: Point(180, 200 - i * 5), radius: 60, data: backgroundColor) + updateDisplay(canvas: canvas, screenBuffer: &screenBuffer, screen: screen) + sleep(ms: 50) + } + } + + func sad() { + for i in 0...5 { + normal() + _ = canvas.fillTriangle(Point(15, 85), Point(120, 85), Point(15, 90 + i * 6), data: backgroundColor) + _ = canvas.fillTriangle(Point(120, 85), Point(225, 85), Point(225, 90 + i * 6), data: backgroundColor) + updateDisplay(canvas: canvas, screenBuffer: &screenBuffer, screen: screen) + sleep(ms: 50) + } + } + + func upset() { + for i in 0...5 { + normal() + _ = canvas.fillRectangle(at: Point(20, 85), width: 85, height: 10 + i * 5, data: backgroundColor) + _ = canvas.fillRectangle(at: Point(140, 85), width: 85, height: 10 + i * 5, data: backgroundColor) + updateDisplay(canvas: canvas, screenBuffer: &screenBuffer, screen: screen) + sleep(ms: 50) + } + } + + func angry() { + for i in 0...5 { + normal() + _ = canvas.fillTriangle(Point(15, 85), Point(225, 85), Point(120, 90 + i * 6), data: backgroundColor) + updateDisplay(canvas: canvas, screenBuffer: &screenBuffer, screen: screen) + sleep(ms: 50) + } + } + + func wonder() { + for i in 0...5 { + _ = canvas.fillRectangle(at: Point.zero, width: canvas.width, height: canvas.height, data: backgroundColor) + let change = i * 4 + _ = canvas.fillRoundedRectangle(at: Point(20, 105 - change), width: 80, height: 50 + change, radius: 20, data: eyeColor) + _ = canvas.fillRoundedRectangle(at: Point(140, 85 + change), width: 80, height: 70 - change, radius: 20, data: eyeColor) + updateDisplay(canvas: canvas, screenBuffer: &screenBuffer, screen: screen) + sleep(ms: 50) + } + + for i in 0...5 { + _ = canvas.fillRectangle(at: Point.zero, width: canvas.width, height: canvas.height, data: backgroundColor) + let change = i * 4 + _ = canvas.fillRoundedRectangle(at: Point(20, 85 + change), width: 80, height: 70 - change, radius: 20, data: eyeColor) + _ = canvas.fillRoundedRectangle(at: Point(140, 85), width: 80, height: 70, radius: 20, data: eyeColor) + updateDisplay(canvas: canvas, screenBuffer: &screenBuffer, screen: screen) + sleep(ms: 50) + } + } + + func move(right: Bool) { + let direction = right ? 1 : -1 + + var leftEyePos = Point(20, 85) + var leftEyeSize = Size(width: 80, height: 70) + var rightEyePos = Point(140, 85) + var rightEyeSize = Size(width: 80, height: 70) + + // Move the eyes to the left or right and simulate the action of closing the eyes. + for _ in 0..<3 { + leftEyePos.x += 2 * direction + rightEyePos.x += 2 * direction + leftEyeSize.height -= 6 + rightEyeSize.height -= 6 + + if right { + rightEyeSize.width += 1 + rightEyeSize.height += 3 + } else { + leftEyeSize.width += 1 + leftEyeSize.height += 3 + } + + leftEyePos.y = (canvas.height - leftEyeSize.height) / 2 + rightEyePos.y = (canvas.height - rightEyeSize.height) / 2 + + _ = canvas.fillRectangle(at: Point.zero, width: canvas.width, height: canvas.height, data: backgroundColor) + drawEyes(leftEyePos: leftEyePos, leftEyeSize: leftEyeSize, rightEyePos: rightEyePos, rightEyeSize: rightEyeSize) + sleep(ms: 100) + } + + // Move the eyes to the left or right and simulate the action of opening the eyes. + for _ in 0..<3 { + leftEyePos.x += 2 * direction + rightEyePos.x += 2 * direction + leftEyeSize.height += 6 + rightEyeSize.height += 6 + + if right { + rightEyeSize.width += 1 + rightEyeSize.height += 3 + } else { + leftEyeSize.width += 1 + leftEyeSize.height += 3 + } + + leftEyePos.y = (canvas.height - leftEyeSize.height) / 2 + rightEyePos.y = (canvas.height - rightEyeSize.height) / 2 + drawEyes(leftEyePos: leftEyePos, leftEyeSize: leftEyeSize, rightEyePos: rightEyePos, rightEyeSize: rightEyeSize) + sleep(ms: 100) + } + + // Move the eyes back to their original position and and simulate the action of closing the eyes. + for _ in 0..<3 { + leftEyePos.x -= 2 * direction + rightEyePos.x -= 2 * direction + leftEyeSize.height -= 6 + rightEyeSize.height -= 6 + + if right { + rightEyeSize.width -= 1 + rightEyeSize.height -= 3 + } else { + leftEyeSize.width -= 1 + leftEyeSize.height -= 3 + } + + leftEyePos.y = (canvas.height - leftEyeSize.height) / 2 + rightEyePos.y = (canvas.height - rightEyeSize.height) / 2 + drawEyes(leftEyePos: leftEyePos, leftEyeSize: leftEyeSize, rightEyePos: rightEyePos, rightEyeSize: rightEyeSize) + sleep(ms: 100) + } + + // Move the eyes back to their original position and and simulate the action of opening the eyes. + for _ in 0..<3 { + leftEyePos.x -= 2 * direction + rightEyePos.x -= 2 * direction + leftEyeSize.height += 6 + rightEyeSize.height += 6 + + if right { + rightEyeSize.width -= 1 + rightEyeSize.height -= 3 + } else { + leftEyeSize.width -= 1 + leftEyeSize.height -= 3 + } + + leftEyePos.y = (canvas.height - leftEyeSize.height) / 2 + rightEyePos.y = (canvas.height - rightEyeSize.height) / 2 + drawEyes(leftEyePos: leftEyePos, leftEyeSize: leftEyeSize, rightEyePos: rightEyePos, rightEyeSize: rightEyeSize) + sleep(ms: 100) + } + } + + func drawEyes(leftEyePos: Point, leftEyeSize: Size, rightEyePos: Point, rightEyeSize: Size) { + _ = canvas.fillRectangle(at: Point.zero, width: canvas.width, height: canvas.height, data: backgroundColor) + _ = canvas.fillRoundedRectangle(at: leftEyePos, width: leftEyeSize.width, height: leftEyeSize.height, radius: 24, data: eyeColor) + _ = canvas.fillRoundedRectangle(at: rightEyePos, width: rightEyeSize.width, height: rightEyeSize.height, radius: 24, data: eyeColor) + updateDisplay(canvas: canvas, screenBuffer: &screenBuffer, screen: screen) + } + + func saccade(right: Int, up: Int) { + var leftEyePos = Point(20, 85) + var leftEyeSize = Size(width: 80, height: 70) + var rightEyePos = Point(140, 85) + var rightEyeSize = Size(width: 80, height: 70) + + var directionX: Int + var directionY: Int + + if right >= 1 { + directionX = 1 + } else if right == 0 { + directionX = 0 + } else { + directionX = -1 + } + + if up >= 1 { + directionY = -1 + } else if up == 0 { + directionY = 0 + } else { + directionY = 1 + } + + for _ in 0..<2 { + leftEyePos.x += 6 * directionX + leftEyePos.y += 10 * directionY + rightEyePos.x += 6 * directionX + rightEyePos.y += 10 * directionY + + rightEyeSize.height -= 8 + leftEyeSize.height -= 8 + + drawEyes(leftEyePos: leftEyePos, leftEyeSize: leftEyeSize, rightEyePos: rightEyePos, rightEyeSize: rightEyeSize) + sleep(ms: 100) + + leftEyePos.x += 6 * directionX + leftEyePos.y += 10 * directionY + rightEyePos.x += 6 * directionX + rightEyePos.y += 10 * directionY + + rightEyeSize.height += 8 + leftEyeSize.height += 8 + + drawEyes(leftEyePos: leftEyePos, leftEyeSize: leftEyeSize, rightEyePos: rightEyePos, rightEyeSize: rightEyeSize) + sleep(ms: 100) + + directionX = -directionX + directionY = -directionY + } + } + + func updateDisplay(canvas: Canvas, screenBuffer: inout [UInt16], screen: ST7789) { + let dirty = Rect(Point.zero, width: canvas.width, height: canvas.height) + var index = 0 + let stride = canvas.width + let canvasBuffer = canvas.buffer + + for y in dirty.minY..") + point = Point(gridSize * (column - 1) + (gridSize - rect.width) / 2 + xOffset, (yOffset - rect.height) / 2) + let rightArrow = TextLayer(at: point, string: ">", font: font, foregroundColor: Pixel.white) + rootLayer.append(rightArrow) + + let todayIndicator = Layer(at: Point(0, 0), width: gridSize, height: gridSize) + todayIndicator.isOpaque = false + todayIndicator.draw() { canvas in + canvas.fillCircle(at: Point(gridSize / 2, gridSize / 2), radius: gridSize / 2, data: Pixel.orange) + //canvas.fillCircle(at: Point(gridSize / 2, gridSize / 2), radius: gridSize / 2, data: 0xFFFF_0000) + } + + rootLayer.append(todayIndicator) + + var textLayers = [TextLayer]() + + initCalendar(calendarGrid: calendarGrid, today: calendarTime, calendarTime: calendarTime) + rootLayer.render(into: &frameBuffer, output: &screenBuffer, transform: Pixel.toRGB565LE) { dirty, data in + screen.writeBitmap(x: dirty.x, y: dirty.y, width: dirty.width, height: dirty.height, data: data) + } + + var changeMonth = 0 + + leftButton.setInterrupt(.rising) { + changeMonth = -1 + } + + rightButton.setInterrupt(.rising) { + changeMonth = 1 + } + + var sleepCount = 0 + + while true { + // Update the calendar for the new day. + if sleepCount == 100 { + let current = rtc.readTime() + sleepCount = 0 + + if current.day != time.day { + time = current + calendarTime = Time(year: Int(time.year), month: Int(time.month), day: Int(time.day)) + calendarGrid = generateCalendar(year: calendarTime.year, month: calendarTime.month) + updateCalendar(calendarGrid: calendarGrid, today: calendarTime, calendarTime: calendarTime) + rootLayer.render(into: &frameBuffer, output: &screenBuffer, transform: Pixel.toRGB565LE) { dirty, data in + screen.writeBitmap(x: dirty.x, y: dirty.y, width: dirty.width, height: dirty.height, data: data) + } + } + } + + calendarTime.month += changeMonth + if calendarTime.month == 13 { + calendarTime.year += 1 + calendarTime.month = 1 + } else if calendarTime.month == 0 { + calendarTime.year -= 1 + calendarTime.month = 12 + } + + if changeMonth != 0 { + let monthString = months[calendarTime.month - 1] + " " + String(calendarTime.year) + let rect = font.getRect(monthString) + let point = Point((rootLayer.frame.width - rect.width) / 2, (yOffset - rect.height) / 2) + + monthLayer.string = monthString + monthLayer.position = point + + calendarGrid = generateCalendar(year: calendarTime.year, month: calendarTime.month) + let today = Time(year: Int(time.year), month: Int(time.month), day: Int(time.day)) + updateCalendar(calendarGrid: calendarGrid, today: today, calendarTime: calendarTime) + rootLayer.render(into: &frameBuffer, output: &screenBuffer, transform: Pixel.toRGB565LE) { dirty, data in + screen.writeBitmap(x: dirty.x, y: dirty.y, width: dirty.width, height: dirty.height, data: data) + } + changeMonth = 0 + } + + sleep(ms: 10) + sleepCount += 1 + } + + struct Time { + var year: Int + var month: Int + var day: Int + } + + func updateCalendar(calendarGrid: [[Int]], today: Time, calendarTime: Time) { + for y in 1.. [[Int]] { + var calendarGrid = [[Int]](repeating: [Int](repeating: 0, count: 7), count: 6) + + var totalDays = daysInMonth[month - 1] + + if month == 2 && isLeapYear(year: year) { + totalDays = 29 + } + + var currentDay = 1 + + // Calculate the day of the week for the first day of the month + let firstDayOfWeek = getDayOfWeek(year: year, month: month, day: 1) + + // Determine the starting position in the grid + var row = 0 + var column = firstDayOfWeek + + // Place the days of the month in the grid. + while currentDay <= totalDays { + calendarGrid[row][column] = currentDay + currentDay += 1 + column += 1 + + // Move to the next row if necessary + if column == 7 { + column = 0 + row += 1 + } + } + + return calendarGrid +} + +func isLeapYear(year: Int) -> Bool { + return (year % 4 == 0 && year % 100 != 0) || year % 400 == 0 +} + +// Determine which day of the week corresponds to the given date. +// Sunday is represented as 0. +func getDayOfWeek(year: Int, month: Int, day: Int) -> Int { + // Calculate days until this year. + var days = year * 365 + + // Count leap days until this year. + for i in stride(from: 4, to: year, by: 4) { + if isLeapYear(year: i) { + days += 1 + } + } + + // If this year is a leap year and the month is after february, add 1 day. + if month > 2 && isLeapYear(year: year) { + days += 1 + } + + // Add the days of this year. + days += daysInMonth[0..= 0 + } + ioLock.unlock() + + for (key, value) in ioSendState { + if key == a0Module { + let dutycycle = Float(value) / 100.0 + led.setDutycycle(dutycycle) + rootLayer.draw() { canvas in + canvas.fillRectangle(at: Point(x: 10, y: 106), width: 100, height: 28, data: Pixel.white) + } + if value != 0 { + rootLayer.draw() { canvas in + canvas.fillRectangle(at: Point(x: 10, y: 106), width: value, height: 28, data: Pixel(0xFFFF5E5E)) + } + } + } + + if key == a11Module { + let fre = 400 + 40 * value + if value > 1 { + buzzer.set(frequency: fre, dutycycle: 0.5) + buzzerBar.frame.size.width = value + } else { + buzzer.set(frequency: fre, dutycycle: 0) + buzzerBar.frame.size.width = 1 + } + } + + if key == d1Module { + let color = colors[colorIndex] + colorBar.backgroundColor = color + colorIndex += 1 + if colorIndex >= colors.count { + colorIndex = 0 + } + } + + if key == d19Module && value == 2 { + soundState.foregroundColor = Pixel.lime + } + + if key == d19Module && value == 0 { + soundState.foregroundColor = Pixel.red + } + } + + ioLock.lock() + for (key, value) in ioSendState { + if key == d19Module { + if value == 2 || value == 0 { + let newValue = globalIOValue[key]! - 1 + globalIOValue[key] = newValue + } + } else { + globalIOValue[key] = -1 + } + } + ioLock.unlock() + + + i2cLock.lock() + let i2cSendState = globalI2CValue.filter { (step, value) in + value >= 0 + } + i2cLock.unlock() + + for (key, value) in i2cSendState { + if key == temperature { + temperatureText.string = String(value / 10) + "." + String(value % 10) + "°C" + } + + if key == humidityKey { + humidityText.string = String(value / 10) + "." + String(value % 10) + "%" + } + + if key == accValue { + let x = value & 0xFF + accBar.position.x = 130 + x + } + } + + i2cLock.lock() + for key in i2cSendState.keys { + globalI2CValue[key] = -1 + } + i2cLock.unlock() + + + let currentTime = getSystemUptimeInMilliseconds() / 1000 + if uptime != currentTime { + uptime = currentTime + let hour = uptime / 3600 + let minute = (uptime / 60) % 60 + let second = uptime % 60 + + let hourStr = (hour < 10 ? String(0) + String(hour) : String(hour)) + ":" + let minStr = (minute < 10 ? String(0) + String(minute) : String(minute)) + ":" + let secStr = (second < 10 ? String(0) + String(second) : String(second)) + + timeText.string = hourStr + minStr + secStr + } + + rootLayer.render(into: &frameBuffer, output: &screenBuffer, transform: Pixel.toRGB565LE) { dirty, data in + screen.writeBitmap(x: dirty.x, y: dirty.y, width: dirty.width, height: dirty.height, data: data) + } + } + } +} + + +func rootBackgroundInit(font: Font) -> Layer { + let layer = Layer(at: Point.zero, width: 240, height: 240) + + layer.draw() { canvas in + canvas.fill(Pixel.white) + canvas.drawLine(from: Point(x: 120, y: 0), to: Point(x: 120, y: 239), stroke: 2, data: Pixel.pink) + canvas.drawLine(from: Point(x: 120, y: 40), to: Point(x: 239, y: 40), stroke: 2, data: Pixel.pink) + canvas.drawLine(from: Point(x: 0, y: 80), to: Point(x: 239, y: 80), stroke: 2, data: Pixel.pink) + canvas.drawLine(from: Point(x: 0, y: 160), to: Point(x: 239, y: 160), stroke: 2, data: Pixel.pink) + } + + layer.draw() { canvas in + canvas.drawRectangle(at: Point(x: 10, y: 105), width: 100, height: 30, stroke: 3, data: Pixel(0xFF252525)) + canvas.drawRectangle(at: Point(x: 130, y: 105), width: 100, height: 30, stroke: 3, data: Pixel(0xFF252525)) + canvas.drawRectangle(at: Point(x: 130, y: 185), width: 100, height: 30, stroke: 2, data: Pixel(0xFF252525)) + } + + print("display init 0") + let time = TextLayer(at: Point(124, 2), string: "Uptime", font: font, foregroundColor: Pixel.black) + layer.append(time) + + print("display init 1") + let soundAction = TextLayer(at: Point(124, 65), string: "Press Button D19", font: font, foregroundColor: Pixel.gray) + soundAction.pointSize = 5 + layer.append(soundAction) + + + print("display init 2") + let led = TextLayer(at: Point(0, 82), string: "LED brightness", font: font, foregroundColor: Pixel(0xFF252525)) + let ledAction = TextLayer(at: Point(0, 142), string: "Rotate Knob A0", font: font, foregroundColor: Pixel.gray) + ledAction.pointSize = 5 + layer.append(led) + layer.append(ledAction) + + print("display init 3") + let buzzer = TextLayer(at: Point(124, 82), string: "Buzzer pitch", font: font, foregroundColor: Pixel(0xFF252525)) + let buzzerAction = TextLayer(at: Point(124, 142), string: "Rotate Knob A11", font: font, foregroundColor: Pixel.gray) + buzzerAction.pointSize = 5 + layer.append(buzzer) + layer.append(buzzerAction) + + + print("display init 4") + let color = TextLayer(at: Point(0, 165), string: "Color picker", font: font, foregroundColor: Pixel(0xFF252525)) + let colorAction = TextLayer(at: Point(0, 225), string: "Press Button D1", font: font, foregroundColor: Pixel.gray) + colorAction.pointSize = 5 + layer.append(color) + layer.append(colorAction) + + print("display init 5") + let acc = TextLayer(at: Point(124, 165), string: "Acceleration", font: font, foregroundColor: Pixel(0xFF252525)) + let accAction = TextLayer(at: Point(124, 225), string: "Tilt the board", font: font, foregroundColor: Pixel.gray) + accAction.pointSize = 5 + layer.append(acc) + layer.append(accAction) + + return layer.presentation() +} \ No newline at end of file diff --git a/Examples/SwiftIOPlayground/13MoreProjects/DefaultApp/Sources/DefaultApp/I2CThread.swift b/Examples/SwiftIOPlayground/13MoreProjects/DefaultApp/Sources/DefaultApp/I2CThread.swift new file mode 100644 index 0000000..2bbdfb7 --- /dev/null +++ b/Examples/SwiftIOPlayground/13MoreProjects/DefaultApp/Sources/DefaultApp/I2CThread.swift @@ -0,0 +1,88 @@ +import SwiftIO +import MadBoard + +import SHT3x +import LIS3DH +import PCF8563 + +func i2cIOThread(_ a: UnsafeMutableRawPointer?, _ b: UnsafeMutableRawPointer?, _ c: UnsafeMutableRawPointer?) -> () { + sleep(ms: 500) + + let i2c = I2C(Id.I2C0, speed: .fast) + sleep(ms: 10) + + let sht = SHT3x(i2c) + sleep(ms: 10) + var temp = 0 + var humidity = 0 + var tempCount = 0 + var humidityCount = 0 + var previousTemp = -1 + var previousHumidity = -1 + + + let acc = LIS3DH(i2c) + sleep(ms: 10) + var x = 50 + + var accCount = 0 + var previousX = 50 + + + + + + + + while true { + sleep(ms: 10) + + let ret = acc.readXYZ() + x = 100 - (Int(ret.x * 100.0) + 50) + x = min(93, x) + x = max(1, x) + + if x != previousX { + accCount += 1 + if accCount > 2 { + accCount = 0 + i2cLock.lock() + globalI2CValue[accValue] = (previousX << 8) | x + i2cLock.unlock() + previousX = x + } + } + + + + + temp = Int(sht.readCelsius() * 10) + humidity = Int(sht.readHumidity() * 10) + + if temp != previousTemp { + tempCount += 1 + + if tempCount > 5 { + i2cLock.lock() + globalI2CValue[temperature] = temp + i2cLock.unlock() + previousTemp = temp + tempCount = 0 + } + } + + if humidity != previousHumidity { + humidityCount += 1 + + if humidityCount > 5 { + i2cLock.lock() + globalI2CValue[humidityKey] = humidity + i2cLock.unlock() + previousHumidity = humidity + humidityCount = 0 + } + } + + } + +} \ No newline at end of file diff --git a/Examples/SwiftIOPlayground/13MoreProjects/DefaultApp/Sources/DefaultApp/IOThread.swift b/Examples/SwiftIOPlayground/13MoreProjects/DefaultApp/Sources/DefaultApp/IOThread.swift new file mode 100644 index 0000000..928620c --- /dev/null +++ b/Examples/SwiftIOPlayground/13MoreProjects/DefaultApp/Sources/DefaultApp/IOThread.swift @@ -0,0 +1,92 @@ +import SwiftIO +import MadBoard + +func ioThread(_ a: UnsafeMutableRawPointer?, _ b: UnsafeMutableRawPointer?, _ c: UnsafeMutableRawPointer?) -> () { + sleep(ms: 100) + + let a0 = AnalogIn(Id.A0) + var a0Count = 0 + var a0PreviousValue = -1 + + let a11 = AnalogIn(Id.A11) + var a11Count = 0 + var a11PreviousValue = -1 + + let d1 = DigitalIn(Id.D1) + var d1PressCount = 0 + + let d19 = DigitalIn(Id.D19) + var d19PressCount = 0 + + + + while true { + sleep(ms: 4) + + + let a0Value = Int(a0.readPercentage() * 100) + if a0Value != a0PreviousValue { + a0Count += 1 + } else { + a0Count = 0 + } + + if a0Count > 5 { + a0Count = 0 + a0PreviousValue = a0Value + ioLock.lock() + globalIOValue[a0Module] = a0Value + ioLock.unlock() + } + + + + let a11Value = Int(a11.readPercentage() * 100) + if a11Value != a11PreviousValue { + a11Count += 1 + } else { + a11Count = 0 + } + + if a11Count > 5 { + a11Count = 0 + a11PreviousValue = a11Value + ioLock.lock() + globalIOValue[a11Module] = a11Value + ioLock.unlock() + } + + + + if d1.read() { + d1PressCount += 1 + } else { + d1PressCount -= 1 + d1PressCount = max(0, d1PressCount) + } + + if d1PressCount > 10 && !d1.read() { + d1PressCount = 0 + ioLock.lock() + globalIOValue[d1Module] = 1 + ioLock.unlock() + } + + + + if d19.read() { + d19PressCount += 1 + } else { + d19PressCount -= 1 + d19PressCount = max(0, d19PressCount) + } + if d19PressCount > 10 && !d19.read() { + d19PressCount = 0 + ioLock.lock() + if globalIOValue[d19Module]! < 0 { + globalIOValue[d19Module] = 3 + } + ioLock.unlock() + } + } +} \ No newline at end of file diff --git a/Examples/SwiftIOPlayground/13MoreProjects/DefaultApp/Sources/DefaultApp/SoundThread.swift b/Examples/SwiftIOPlayground/13MoreProjects/DefaultApp/Sources/DefaultApp/SoundThread.swift new file mode 100644 index 0000000..cd5ea92 --- /dev/null +++ b/Examples/SwiftIOPlayground/13MoreProjects/DefaultApp/Sources/DefaultApp/SoundThread.swift @@ -0,0 +1,31 @@ +import SwiftIO +import MadBoard + +func soundThread(_ a: UnsafeMutableRawPointer?, _ b: UnsafeMutableRawPointer?, _ c: UnsafeMutableRawPointer?) -> () { + + +// BPM is beat count per minute. +// Timer signature specifies beats per bar and note value of a beat. +let player = Speaker() + + + +var startPlay = false + +while true { + sleep(ms: 100) + ioLock.lock() + if globalIOValue[d19Module]! == 3 { + globalIOValue[d19Module] = 2 + startPlay = true + } + ioLock.unlock() + if startPlay { + player.play() + startPlay = false + ioLock.lock() + globalIOValue[d19Module] = 0 + ioLock.unlock() + } +} +} \ No newline at end of file diff --git a/Examples/SwiftIOPlayground/13MoreProjects/DefaultApp/Sources/DefaultApp/Speaker.swift b/Examples/SwiftIOPlayground/13MoreProjects/DefaultApp/Sources/DefaultApp/Speaker.swift new file mode 100644 index 0000000..83ab4b5 --- /dev/null +++ b/Examples/SwiftIOPlayground/13MoreProjects/DefaultApp/Sources/DefaultApp/Speaker.swift @@ -0,0 +1,102 @@ +import SwiftIO +import MadBoard + + +public final class Speaker { + let speaker: I2S + // The frequencies of note C to B in octave 4. + let frequency: [Float] = [ + 261.626, + 293.665, + 329.628, + 349.228, + 391.995, + 440.000, + 493.883, + 523.250 + ] + + // Set the samples of the waveforms. + let sampleRate = 16_000 + let rawSampleLength = 1000 + var rawSamples: [Int16] + var amplitude: Int16 = 10_000 + + + public init() { + // Initialize the speaker using I2S communication. + // The default setting is 16k sample rate, 16bit sample bits. + speaker = I2S(Id.I2S0) + rawSamples = [Int16](repeating: 0, count: rawSampleLength) + } + + + public func play() { + + let duration: Float = 0.25 + + // Iterate through the frequencies from C to B to play a scale. + // The sound waveform is a square wave, so you will hear a buzzing sound. + // generateSquare(amplitude: amplitude, &rawSamples) + // for f in frequency { + // playWave(samples: rawSamples, frequency: f, duration: duration) + // } + // sleep(ms: 1000) + + // Iterate through the frequencies from C to B to play a scale. + // The sound waveform is a triangle wave, and the sound is much softer. + generateTriangle(amplitude: amplitude, &rawSamples) + for f in frequency { + playWave(samples: rawSamples, frequency: f, duration: duration) + } + + } + + // Generate samples for a square wave with a specified amplitude and store them in an array. + func generateSquare(amplitude: Int16, _ samples: inout [Int16]) { + let count = samples.count + for i in 0.. UInt16 { + static func toRGB565BE(_ color: UInt32) -> UInt16 { return (UInt16((color & 0xF80000) >> 8) | UInt16((color & 0xFC00) >> 5) | UInt16((color & 0xF8) >> 3)).byteSwapped } } diff --git a/Examples/SwiftIOPlayground/13MoreProjects/Fireworks/.gitignore b/Examples/SwiftIOPlayground/13MoreProjects/Fireworks/.gitignore new file mode 100644 index 0000000..0023a53 --- /dev/null +++ b/Examples/SwiftIOPlayground/13MoreProjects/Fireworks/.gitignore @@ -0,0 +1,8 @@ +.DS_Store +/.build +/Packages +xcuserdata/ +DerivedData/ +.swiftpm/configuration/registries.json +.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata +.netrc diff --git a/Examples/SwiftIOPlayground/13MoreProjects/Fireworks/Package.mmp b/Examples/SwiftIOPlayground/13MoreProjects/Fireworks/Package.mmp new file mode 100644 index 0000000..8af5322 --- /dev/null +++ b/Examples/SwiftIOPlayground/13MoreProjects/Fireworks/Package.mmp @@ -0,0 +1,28 @@ +# This is a MadMachine project file in TOML format +# This file contains parameters that cannot be managed by SwiftPM +# Editing this file will alter the behavior of the build/download process +# Project files within dependent libraries will be IGNORED + +# Specify the board name below +# Supported boards are listed as follows +# "SwiftIOBoard" +# "SwiftIOMicro" +board = "SwiftIOMicro" + +# Specify the target triple below +# Supported architectures are listed as follows +# "thumbv7em-unknown-none-eabi" +# "thumbv7em-unknown-none-eabihf" +# "armv7em-none-none-eabi" +triple = "armv7em-none-none-eabi" + +# Enable or disable hardware floating-point support below +# If your code involves significant floating-point calculations, please set it to 'true' +hard-float = true + +# Enable or disable float register below +# If your code involves significant floating-point calculations, please set it to 'true' +float-abi = false + +# Reserved for future use +version = 1 \ No newline at end of file diff --git a/Examples/SwiftIOPlayground/13MoreProjects/Fireworks/Package.swift b/Examples/SwiftIOPlayground/13MoreProjects/Fireworks/Package.swift new file mode 100644 index 0000000..02efd9d --- /dev/null +++ b/Examples/SwiftIOPlayground/13MoreProjects/Fireworks/Package.swift @@ -0,0 +1,28 @@ +// swift-tools-version: 5.9 +// The swift-tools-version declares the minimum version of Swift required to build this package. + +import PackageDescription + +let package = Package( + name: "Fireworks", + dependencies: [ + // Dependencies declare other packages that this package depends on. + .package(url: "https://github.com/madmachineio/SwiftIO.git", branch: "main"), + .package(url: "https://github.com/madmachineio/MadBoards.git", branch: "main"), + .package(url: "https://github.com/madmachineio/MadDrivers.git", branch: "main"), + .package(url: "https://github.com/madmachineio/CFreeType.git", from: "2.13.2"), + ], + targets: [ + // Targets are the basic building blocks of a package, defining a module or a test suite. + // Targets can depend on other targets in this package and products from dependencies. + .executableTarget( + name: "Fireworks", + dependencies: [ + "SwiftIO", + "MadBoards", + // Use specific library name rather than "MadDrivers" would speed up the build procedure. + .product(name: "ST7789", package: "MadDrivers"), + "CFreeType", + ]), + ] +) diff --git a/Examples/SwiftIOPlayground/13MoreProjects/Fireworks/Resources/Fonts/Roboto-Regular.ttf b/Examples/SwiftIOPlayground/13MoreProjects/Fireworks/Resources/Fonts/Roboto-Regular.ttf new file mode 100644 index 0000000..ddf4bfa Binary files /dev/null and b/Examples/SwiftIOPlayground/13MoreProjects/Fireworks/Resources/Fonts/Roboto-Regular.ttf differ diff --git a/Examples/SwiftIOPlayground/13MoreProjects/Fireworks/Resources/Sounds/boom.wav b/Examples/SwiftIOPlayground/13MoreProjects/Fireworks/Resources/Sounds/boom.wav new file mode 100644 index 0000000..b67d344 Binary files /dev/null and b/Examples/SwiftIOPlayground/13MoreProjects/Fireworks/Resources/Sounds/boom.wav differ diff --git a/Examples/SwiftIOPlayground/13MoreProjects/Fireworks/Sources/Firework.swift b/Examples/SwiftIOPlayground/13MoreProjects/Fireworks/Sources/Firework.swift new file mode 100644 index 0000000..5499ea9 --- /dev/null +++ b/Examples/SwiftIOPlayground/13MoreProjects/Fireworks/Sources/Firework.swift @@ -0,0 +1,89 @@ +import MadGraphics +import ST7789 +import SwiftIO + +struct Firework { + var particle: Particle + var sparks: [Spark] = [] + + var exploded = false + + let color: Pixel + let size = 2 + + // Create a firework at the bottom of the screen. + init(color: Pixel, maxWidth: Int) { + self.color = color + + let x = Array(0..<(maxWidth - 1)).shuffled().randomElement()! + particle = Particle(pos: Point(x: x, y: maxWidth - 1)) + } + + // Update firework's position. + // If it explodes, it will return true to indicate it's time to play sound. + mutating func update(_ layer: Layer) -> Bool { + if exploded { + for spark in sparks { + layer.draw() { canvas in + canvas.fillCircle(at: spark.pos, radius: size, data: Pixel.black) + } + } + + updateSparks() + + for spark in sparks { + let data = Pixel.blend(foreground: color, background: Pixel.black, with: spark.lifespan) + layer.draw() { canvas in + canvas.fillCircle(at: spark.pos, radius: size, data: data) + } + } + } else { + layer.draw() { canvas in + canvas.fillCircle(at: particle.pos, radius: size, data: Pixel.black) + } + + let playSound = updateParticle() + + layer.draw() { canvas in + canvas.fillCircle(at: particle.pos, radius: size, data: color) + } + return playSound + } + + return false + } + + // Update sparks' position and speed over time. + mutating func updateSparks() { + for i in sparks.indices.reversed() { + sparks[i].update() + // If + if sparks[i].done() { + sparks.remove(at: i) + } + } + } + + // Update particle's position and speed over time. + mutating func updateParticle() -> Bool { + particle.update() + if particle.willExplode() { + exploded = true + explode() + return true + } + + return false + } + + // Generate firework sparks after explosion. + mutating func explode() { + for _ in 0..<100 { + sparks.append(Spark(pos: particle.pos)) + } + } + + func done() -> Bool { + return exploded && sparks.count == 0 + } +} \ No newline at end of file diff --git a/Examples/SwiftIOPlayground/13MoreProjects/Fireworks/Sources/Fireworks.swift b/Examples/SwiftIOPlayground/13MoreProjects/Fireworks/Sources/Fireworks.swift new file mode 100644 index 0000000..c324d5f --- /dev/null +++ b/Examples/SwiftIOPlayground/13MoreProjects/Fireworks/Sources/Fireworks.swift @@ -0,0 +1,94 @@ +import SwiftIO +import MadBoard +import ST7789 +import MadGraphics + +// Use lock to protect data from simultaneous access by multiple threads. +let i2sLock = Mutex() +// Whether the speaker will play sound. +var playSound = false + +@main +public struct Fireworks { + public static func main() { + // Initialize the SPI pin and the digital pins for the LCD. + 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) + + // Initialize the LCD using the pins above. Rotate the screen to keep the original at the upper left. + let screen = ST7789(spi: spi, cs: cs, dc: dc, rst: rst, bl: bl, rotation: .angle90) + var screenBuffer = [UInt16](repeating: 0, count: screen.width * screen.width) + var frameBuffer = [UInt32](repeating: 0, count: screen.width * screen.width) + + var colorIndex = 0 + let colors: [Pixel] = [ + .pink, .red, .lime, .blue, .cyan, + .purple, .magenta, .orange, .yellow + ] + + let layer = Layer(at: Point.zero, width: screen.width, height: screen.height) + + + let font = Font(path: "/lfs/Resources/Fonts/Roboto-Regular.ttf", pointSize: 10, dpi: 220) + let text1 = TextLayer(at: Point(x: layer.bounds.size.halfWidth, y: 40), anchorPoint: UnitPoint.center, string: "Happy", font: font, foregroundColor: Pixel.red) + let text2 = TextLayer(at: Point(x: layer.bounds.size.halfWidth, y: 80), anchorPoint: UnitPoint.center, string: "Christmas", font: font, foregroundColor: Pixel.red) + + layer.append(text1) + layer.append(text2) + + var fireworks: [Firework] = [] + var exploded = false + + createThread( + name: "play_sound", + priority: 3, + stackSize: 1024 * 64, + soundThread + ) + + sleep(ms: 10) + + while true { + if Int.random(in: 0..<100) < 10 { + fireworks.append(Firework(color: colors[colorIndex], maxWidth: layer.bounds.width)) + // Update firwork's color. + colorIndex += 1 + if colorIndex == colors.count { + colorIndex = 0 + } + } + + var i = 0 + while i < fireworks.count { + if fireworks[i].update(layer) { + exploded = true + } + + // If a all sparks of a firework disppear, remove the firework. + if fireworks[i].done() { + fireworks.remove(at: i) + } else { + i += 1 + } + } + + // If any firework has exploded, update the global variable. + if exploded { + i2sLock.lock() + playSound = true + i2sLock.unlock() + + exploded = false + } + + layer.render(into: &frameBuffer, output: &screenBuffer, transform: Pixel.toRGB565LE) { dirty, data in + screen.writeBitmap(x: dirty.x, y: dirty.y, width: dirty.width, height: dirty.height, data: data) + } + + sleep(ms: 10) + } + } +} \ No newline at end of file diff --git a/Examples/SwiftIOPlayground/13MoreProjects/Fireworks/Sources/Particle.swift b/Examples/SwiftIOPlayground/13MoreProjects/Fireworks/Sources/Particle.swift new file mode 100644 index 0000000..6aa613c --- /dev/null +++ b/Examples/SwiftIOPlayground/13MoreProjects/Fireworks/Sources/Particle.swift @@ -0,0 +1,29 @@ +import MadGraphics + +// The firework before explosion. +struct Particle { + var pos: Point + var velocity: (x: Float, y: Float) + var acceleration: (x: Float, y: Float) + + // Create a particle with a random velocity and acceleration. + init(pos: Point) { + self.pos = pos + velocity = (0, Float.random(in: (-14.0)...(-10.0))) + acceleration = (0, Float.random(in: 0.3..<0.4)) + } + + // Update the particle's position and velocity over time. + mutating func update() { + pos.x += Int(velocity.x) + pos.y += Int(velocity.y) + + velocity.x += acceleration.x + velocity.y += acceleration.y + } + + // Whether the particle reaches its maximum height. + func willExplode() -> Bool { + return velocity.y > 0 + } +} \ No newline at end of file diff --git a/Examples/SwiftIOPlayground/13MoreProjects/Fireworks/Sources/SoundThread.swift b/Examples/SwiftIOPlayground/13MoreProjects/Fireworks/Sources/SoundThread.swift new file mode 100644 index 0000000..69ff853 --- /dev/null +++ b/Examples/SwiftIOPlayground/13MoreProjects/Fireworks/Sources/SoundThread.swift @@ -0,0 +1,51 @@ +import SwiftIO +import MadBoard + +func soundThread(_ a: UnsafeMutableRawPointer?, _ b: UnsafeMutableRawPointer?, _ c: UnsafeMutableRawPointer?) -> () { + let speaker = I2S(Id.I2S0, rate: 44_100) + + // Read sound data from file. + let sound = readSoundData(from: "/lfs/Resources/Sounds/boom.wav") + var startPlay = false + + while true { + sleep(ms: 100) + + // Check the global variable to see if the speaker need to play sound. + i2sLock.lock() + if playSound { + startPlay = true + } + i2sLock.unlock() + + if startPlay { + speaker.write(sound) + startPlay = false + + // Update the global variable. + i2sLock.lock() + playSound = false + i2sLock.unlock() + } + } + + func readSoundData(from path: String) -> [UInt8] { + let headerSize = 0x2C + var buffer = [UInt8]() + + do { + let file = try FileDescriptor.open(path) + try file.seek(offset: 0, from: FileDescriptor.SeekOrigin.end) + let size = try file.tell() - headerSize + + buffer = [UInt8](repeating: 0, count: size) + try file.read(fromAbsoluteOffest: headerSize, into: &buffer, count: size) + try file.close() + } catch { + print("File \(path) handle error: \(error)") + return [] + } + + return buffer + } +} \ No newline at end of file diff --git a/Examples/SwiftIOPlayground/13MoreProjects/Fireworks/Sources/Spark.swift b/Examples/SwiftIOPlayground/13MoreProjects/Fireworks/Sources/Spark.swift new file mode 100644 index 0000000..9a76ea3 --- /dev/null +++ b/Examples/SwiftIOPlayground/13MoreProjects/Fireworks/Sources/Spark.swift @@ -0,0 +1,42 @@ +import MadGraphics + +// The firework after explosion. +struct Spark { + var pos: Point + var velocity: (x: Float, y: Float) + var acceleration: (x: Float, y: Float) + // How long the spark shows on the screen. + var lifespan: UInt8 = 255 + + // Create a particle with a random velocity and acceleration. + // Its initial position is the maximum height of the firework particle. + init(pos: Point) { + self.pos = pos + + // The speed of the spark at a random direction. + velocity.x = Float(Array(-100..<100).shuffled().randomElement()!) / 50 + velocity.y = Float(Array(-100..<100).shuffled().randomElement()!) / 50 + velocity.x *= Float.random(in: 1...4) + velocity.y *= Float.random(in: 1...4) + + acceleration = (0, Float.random(in: 0.3..<0.4)) + } + + // Update the particle's position and velocity over time. + mutating func update() { + velocity.x *= 0.8 + velocity.y *= 0.8 + lifespan -= 5 + + pos.x += Int(velocity.x) + pos.y += Int(velocity.y) + + velocity.x += acceleration.x + velocity.y += acceleration.y + } + + // Whether the spark will disappear. + func done() -> Bool { + return lifespan <= 0 + } +} \ No newline at end of file diff --git a/Examples/SwiftIOPlayground/13MoreProjects/HilbertCurve/.gitignore b/Examples/SwiftIOPlayground/13MoreProjects/HilbertCurve/.gitignore new file mode 100644 index 0000000..0023a53 --- /dev/null +++ b/Examples/SwiftIOPlayground/13MoreProjects/HilbertCurve/.gitignore @@ -0,0 +1,8 @@ +.DS_Store +/.build +/Packages +xcuserdata/ +DerivedData/ +.swiftpm/configuration/registries.json +.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata +.netrc diff --git a/Examples/SwiftIOPlayground/13MoreProjects/HilbertCurve/Package.mmp b/Examples/SwiftIOPlayground/13MoreProjects/HilbertCurve/Package.mmp new file mode 100644 index 0000000..8af5322 --- /dev/null +++ b/Examples/SwiftIOPlayground/13MoreProjects/HilbertCurve/Package.mmp @@ -0,0 +1,28 @@ +# This is a MadMachine project file in TOML format +# This file contains parameters that cannot be managed by SwiftPM +# Editing this file will alter the behavior of the build/download process +# Project files within dependent libraries will be IGNORED + +# Specify the board name below +# Supported boards are listed as follows +# "SwiftIOBoard" +# "SwiftIOMicro" +board = "SwiftIOMicro" + +# Specify the target triple below +# Supported architectures are listed as follows +# "thumbv7em-unknown-none-eabi" +# "thumbv7em-unknown-none-eabihf" +# "armv7em-none-none-eabi" +triple = "armv7em-none-none-eabi" + +# Enable or disable hardware floating-point support below +# If your code involves significant floating-point calculations, please set it to 'true' +hard-float = true + +# Enable or disable float register below +# If your code involves significant floating-point calculations, please set it to 'true' +float-abi = false + +# Reserved for future use +version = 1 \ No newline at end of file diff --git a/Examples/SwiftIOPlayground/13MoreProjects/HilbertCurve/Package.swift b/Examples/SwiftIOPlayground/13MoreProjects/HilbertCurve/Package.swift new file mode 100644 index 0000000..d794e83 --- /dev/null +++ b/Examples/SwiftIOPlayground/13MoreProjects/HilbertCurve/Package.swift @@ -0,0 +1,28 @@ +// swift-tools-version: 5.9 +// The swift-tools-version declares the minimum version of Swift required to build this package. + +import PackageDescription + +let package = Package( + name: "HilbertCurve", + dependencies: [ + // Dependencies declare other packages that this package depends on. + .package(url: "https://github.com/madmachineio/SwiftIO.git", branch: "main"), + .package(url: "https://github.com/madmachineio/MadBoards.git", branch: "main"), + .package(url: "https://github.com/madmachineio/MadDrivers.git", branch: "main"), + .package(url: "https://github.com/madmachineio/CFreeType.git", from: "2.13.2"), + ], + targets: [ + // Targets are the basic building blocks of a package, defining a module or a test suite. + // Targets can depend on other targets in this package and products from dependencies. + .executableTarget( + name: "HilbertCurve", + dependencies: [ + "SwiftIO", + "MadBoards", + // Use specific library name rather than "MadDrivers" would speed up the build procedure. + .product(name: "ST7789", package: "MadDrivers"), + "CFreeType", + ]), + ] +) diff --git a/Examples/SwiftIOPlayground/13MoreProjects/HilbertCurve/Sources/HilbertCurve/Hilbert.swift b/Examples/SwiftIOPlayground/13MoreProjects/HilbertCurve/Sources/HilbertCurve/Hilbert.swift new file mode 100644 index 0000000..ba3d481 --- /dev/null +++ b/Examples/SwiftIOPlayground/13MoreProjects/HilbertCurve/Sources/HilbertCurve/Hilbert.swift @@ -0,0 +1,71 @@ +import MadGraphics + +struct Hilbert { + // The order of the Hilbert curve. + let order: Int + // The points of the Hilbert curve. + var points = [Point]() + + let size: Int + let total: Int + + // The four corners of a unit square. + let unitSquarePoints = [Point(0, 0), Point(0, 1), Point(1, 1), Point(1, 0)] + + // Generate a Hilbert curve with a specified order. + init(order: Int) { + self.order = order + + // Total number of points in the curve. + size = Int(Float.pow(2, Float(order))) + total = size * size + for i in 0.. Point { + var i = i + // One of the four corners of the unit square, which serves as the initial point. + var point = unitSquarePoints[i % 4] + + // Update the point iteratively. + for j in 1.. Float + +private extension Float { + @_transparent + static func pow(_ x: Float, _ y: Float) -> Float { + guard x >= 0 else { return .nan } + if x == 0 && y == 0 { return .nan } + return powf(x, y) + } +} \ No newline at end of file diff --git a/Examples/SwiftIOPlayground/13MoreProjects/HilbertCurve/Sources/HilbertCurve/HilbertCurve.swift b/Examples/SwiftIOPlayground/13MoreProjects/HilbertCurve/Sources/HilbertCurve/HilbertCurve.swift new file mode 100644 index 0000000..957e98a --- /dev/null +++ b/Examples/SwiftIOPlayground/13MoreProjects/HilbertCurve/Sources/HilbertCurve/HilbertCurve.swift @@ -0,0 +1,112 @@ +import SwiftIO +import MadBoard +import ST7789 +import MadGraphics + +@main +public struct HilbertCurve { + public static func main() { + // Initialize the SPI pin and the digital pins for the LCD. + 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) + + // Initialize the LCD using the pins above. Rotate the screen to keep the original at the upper left. + let screen = ST7789(spi: spi, cs: cs, dc: dc, rst: rst, bl: bl, rotation: .angle90) + + let width = 240 + let height = 240 + + var screenBuffer = [UInt16](repeating: 0, count: width * height) + + // layer used to draw the Hilbert curve. + let layer = Layer(at: Point.zero, width: width, height: height) + var frameBuffer = [UInt32](repeating: 0, count: width * height) + + var colorIndex = 0 + let colors: [Pixel] = [ + .red, .orange, .yellow, .lime, .blue, + Pixel(0xFF4B_0082), Pixel(0xFF94_00D3) + ] + + let minOrder = 1 + let maxOrder = 6 + var order = minOrder + var increaseOrder = true + + var hilbert = Hilbert(order: order) + // Scale and position each point to fit the layer. + var length: Int { width / hilbert.size } + + var pointIndex = 1 + + drawBorder() + + while true { + if pointIndex == hilbert.total { + // Generate a new Hilbert curve. + order = increaseOrder ? order + 1 : order - 1 + + if order == maxOrder { + increaseOrder = false + } else if order == minOrder { + increaseOrder = true + } + + hilbert = Hilbert(order: order) + // Clear the canvas. + layer.draw() { canvas in + canvas.fill(Pixel.black) + } + drawBorder() + + colorIndex = 0 + pointIndex = 1 + + sleep(ms: 500) + } else { + // Draw a single line each time. + drawLine(pointIndex) + + colorIndex = (pointIndex / 4) % colors.count + pointIndex += 1 + + sleep(ms: 2) + } + + layer.render(into: &frameBuffer, output: &screenBuffer, transform: Pixel.toRGB565LE) { dirty, data in + screen.writeBitmap(x: dirty.x, y: dirty.y, width: dirty.width, height: dirty.height, data: data) + } + } + + // Connect the given point with its preceding point on the curve. + func drawLine(_ index: Int) { + let x1 = hilbert.points[index - 1].x * length + length / 2 + let y1 = hilbert.points[index - 1].y * length + length / 2 + let x2 = hilbert.points[index].x * length + length / 2 + let y2 = hilbert.points[index].y * length + length / 2 + + layer.draw() { canvas in + canvas.drawLine(from: Point(x1, y1), to: Point(x2, y2), data: colors[colorIndex]) + } + } + + // Outline the canvas with a border. + func drawBorder() { + layer.draw() { canvas in + canvas.drawLine(from: Point(0, 0), to: Point(width - 1, 0), data: Pixel.silver) + } + layer.draw() { canvas in + canvas.drawLine(from: Point(0, height - 1), to: Point(width - 1, height - 1), data: Pixel.silver) + } + layer.draw() { canvas in + canvas.drawLine(from: Point(0, 0), to: Point(0, height - 1), data: Pixel.silver) + } + layer.draw() { canvas in + canvas.drawLine(from: Point(width - 1, 0), to: Point(width - 1, height - 1), data: Pixel.silver) + } + } + } +} \ No newline at end of file diff --git a/Examples/SwiftIOPlayground/13MoreProjects/MazeGame/.gitignore b/Examples/SwiftIOPlayground/13MoreProjects/MazeGame/.gitignore new file mode 100644 index 0000000..0023a53 --- /dev/null +++ b/Examples/SwiftIOPlayground/13MoreProjects/MazeGame/.gitignore @@ -0,0 +1,8 @@ +.DS_Store +/.build +/Packages +xcuserdata/ +DerivedData/ +.swiftpm/configuration/registries.json +.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata +.netrc diff --git a/Examples/SwiftIOPlayground/13MoreProjects/MazeGame/Package.mmp b/Examples/SwiftIOPlayground/13MoreProjects/MazeGame/Package.mmp new file mode 100644 index 0000000..8af5322 --- /dev/null +++ b/Examples/SwiftIOPlayground/13MoreProjects/MazeGame/Package.mmp @@ -0,0 +1,28 @@ +# This is a MadMachine project file in TOML format +# This file contains parameters that cannot be managed by SwiftPM +# Editing this file will alter the behavior of the build/download process +# Project files within dependent libraries will be IGNORED + +# Specify the board name below +# Supported boards are listed as follows +# "SwiftIOBoard" +# "SwiftIOMicro" +board = "SwiftIOMicro" + +# Specify the target triple below +# Supported architectures are listed as follows +# "thumbv7em-unknown-none-eabi" +# "thumbv7em-unknown-none-eabihf" +# "armv7em-none-none-eabi" +triple = "armv7em-none-none-eabi" + +# Enable or disable hardware floating-point support below +# If your code involves significant floating-point calculations, please set it to 'true' +hard-float = true + +# Enable or disable float register below +# If your code involves significant floating-point calculations, please set it to 'true' +float-abi = false + +# Reserved for future use +version = 1 \ No newline at end of file diff --git a/Examples/SwiftIOPlayground/13MoreProjects/MazeGame/Package.swift b/Examples/SwiftIOPlayground/13MoreProjects/MazeGame/Package.swift new file mode 100644 index 0000000..ea5cc7b --- /dev/null +++ b/Examples/SwiftIOPlayground/13MoreProjects/MazeGame/Package.swift @@ -0,0 +1,29 @@ +// swift-tools-version: 5.9 +// The swift-tools-version declares the minimum version of Swift required to build this package. + +import PackageDescription + +let package = Package( + name: "MazeGame", + dependencies: [ + // Dependencies declare other packages that this package depends on. + .package(url: "https://github.com/madmachineio/SwiftIO.git", branch: "main"), + .package(url: "https://github.com/madmachineio/MadBoards.git", branch: "main"), + .package(url: "https://github.com/madmachineio/MadDrivers.git", branch: "main"), + .package(url: "https://github.com/madmachineio/CFreeType.git", from: "2.13.2"), + ], + targets: [ + // Targets are the basic building blocks of a package, defining a module or a test suite. + // Targets can depend on other targets in this package and products from dependencies. + .executableTarget( + name: "MazeGame", + dependencies: [ + "SwiftIO", + "MadBoards", + // Use specific library name rather than "MadDrivers" would speed up the build procedure. + .product(name: "ST7789", package: "MadDrivers"), + .product(name: "LIS3DH", package: "MadDrivers"), + "CFreeType", + ]), + ] +) diff --git a/Examples/SwiftIOPlayground/13MoreProjects/MazeGame/Resources/Fonts/Roboto-Regular.ttf b/Examples/SwiftIOPlayground/13MoreProjects/MazeGame/Resources/Fonts/Roboto-Regular.ttf new file mode 100644 index 0000000..ddf4bfa Binary files /dev/null and b/Examples/SwiftIOPlayground/13MoreProjects/MazeGame/Resources/Fonts/Roboto-Regular.ttf differ diff --git a/Examples/SwiftIOPlayground/13MoreProjects/MazeGame/Sources/MazeGame/Ball.swift b/Examples/SwiftIOPlayground/13MoreProjects/MazeGame/Sources/MazeGame/Ball.swift new file mode 100644 index 0000000..8f31d63 --- /dev/null +++ b/Examples/SwiftIOPlayground/13MoreProjects/MazeGame/Sources/MazeGame/Ball.swift @@ -0,0 +1,20 @@ +import MadGraphics + +struct Ball { + var x1: Int + var y1: Int + let size: Int + + var x2: Int { + x1 + size + } + var y2: Int { + y1 + size + } + + init(at point: Point, size: Int) { + x1 = point.x + y1 = point.y + self.size = size + } +} \ No newline at end of file diff --git a/Examples/SwiftIOPlayground/13MoreProjects/MazeGame/Sources/MazeGame/Game.swift b/Examples/SwiftIOPlayground/13MoreProjects/MazeGame/Sources/MazeGame/Game.swift new file mode 100644 index 0000000..adac88c --- /dev/null +++ b/Examples/SwiftIOPlayground/13MoreProjects/MazeGame/Sources/MazeGame/Game.swift @@ -0,0 +1,241 @@ +import MadGraphics +import ST7789 +import SwiftIO + +// Place a ball at the upper left corner of the maze and move it based on acceleration. +// If the ball reaches the destination (bottom right), the game ends. +// Press the D1 button to restart the game. +class Game { + var maze: Maze + var ball: Ball + let ballColor = Pixel(0xFFEF_E891) + + let width = 20 + var speed = 2 + + let screen: ST7789 + let layer: Layer + var frameBuffer: [UInt32] + var screenBuffer: [UInt16] + + init(screen: ST7789, layer: Layer) { + ball = Ball(at: Point(x: 1, y: 1), size: 7) + maze = Maze(width: width, layer: layer) + frameBuffer = [UInt32](repeating: 0, count: layer.bounds.width * layer.bounds.height) + screenBuffer = [UInt16](repeating: 0, count: layer.bounds.width * layer.bounds.height) + self.screen = screen + self.layer = layer + + maze.generate(layer) + + layer.draw() { canvas in + canvas.fillRectangle(at: Point(ball.x1, ball.y1), width: ball.size, height: ball.size, data: ballColor) + } + + layer.render(into: &frameBuffer, output: &screenBuffer, transform: Pixel.toRGB565LE) { dirty, data in + screen.writeBitmap(x: dirty.x, y: dirty.y, width: dirty.width, height: dirty.height, data: data) + } + } + + // Create a new maze and place the ball at the starting point. + func reset() { + maze.reset(layer) + maze.generate(layer) + + layer.draw() { canvas in + canvas.fillRectangle(at: Point(ball.x1, ball.y1), width: ball.size, height: ball.size, data: maze.bgColor) + } + ball = Ball(at: Point(x: 1, y: 1), size: 7) + + layer.draw() { canvas in + canvas.fillRectangle(at: Point(ball.x1, ball.y1), width: ball.size, height: ball.size, data: ballColor) + } + + layer.render(into: &frameBuffer, output: &screenBuffer, transform: Pixel.toRGB565LE) { dirty, data in + screen.writeBitmap(x: dirty.x, y: dirty.y, width: dirty.width, height: dirty.height, data: data) + } + } + + // Update the display to show that the game has finished. + func finishGame() { + layer.draw() { canvas in + canvas.fillRectangle(at: Point(0, 0), width: canvas.width, height: canvas.height, data: Pixel.red) + } + + let font = Font(path: "/lfs/Resources/Fonts/Roboto-Regular.ttf", pointSize: 10, dpi: 220) + + let text1 = TextLayer(at: Point(x: layer.bounds.size.halfWidth, y: 60), anchorPoint: UnitPoint.center, string: "Good Job!", font: font, foregroundColor: Pixel.white) + + font.setSize(pointSize: 6) + let text2 = TextLayer(at: Point(x: layer.bounds.size.halfWidth, y: 140), anchorPoint: UnitPoint.center, string: "Press D1 to continue", font: font, foregroundColor: Pixel.white) + + layer.append(text1) + layer.append(text2) + + layer.render(into: &frameBuffer, output: &screenBuffer, transform: Pixel.toRGB565LE) { dirty, data in + screen.writeBitmap(x: dirty.x, y: dirty.y, width: dirty.width, height: dirty.height, data: data) + } + + layer.removeAll() + } + + // Verify if the ball has reached the bottom right corner of the maze. + func finished() -> Bool { + return ball.x1 / width == maze.column - 1 && ball.y1 / width == maze.row - 1 + } + + // Update the ball's position based on the acceleration. + func update(_ acceleration: (x: Float, y: Float, z: Float)) { + guard !finished() else { + finishGame() + return + } + + let lastBallPos = Point(ball.x1, ball.y1) + + // Move to the left. + if acceleration.x > 0.25 { + ball.x1 -= speed + + let gridXmin = max(ball.x1 / width, 0) + let gridXmax = min(ball.x2 / width, maze.column - 1) + let gridYmin = max(ball.y1 / width, 0) + let gridYmax = min(ball.y2 / width, maze.row - 1) + + // Check if the ball collides with any walls. + // If it does, reposition it close to the wall. + for y in gridYmin...gridYmax { + for x in gridXmin...gridXmax { + let result = checkGridWalls(ballPos: Point(ball.x1, ball.y1), gridPos: Point(x, y)) + + if result.top || result.bottom || result.right { + ball.x1 = (x + 1) * width + 1 + } + + if result.left { + ball.x1 = x * width + 1 + } + } + } + } + + // Move to the right + if acceleration.x < -0.25 { + ball.x1 += speed + + let gridXmin = max(ball.x1 / width, 0) + let gridXmax = min(ball.x2 / width, maze.column - 1) + let gridYmin = max(ball.y1 / width, 0) + let gridYmax = min(ball.y2 / width, maze.row - 1) + + for y in gridYmin...gridYmax { + for x in gridXmin...gridXmax { + let result = checkGridWalls(ballPos: Point(ball.x1, ball.y1), gridPos: Point(x, y)) + + if result.top || result.bottom || result.left { + ball.x1 = x * width - ball.size - 1 + } + + if result.right { + ball.x1 = (x + 1) * width - ball.size - 1 + } + } + } + } + + // Move downwards. + if acceleration.y > 0.25 { + ball.y1 += speed + + let gridXmin = max(ball.x1 / width, 0) + let gridXmax = min(ball.x2 / width, maze.column - 1) + let gridYmin = max(ball.y1 / width, 0) + let gridYmax = min(ball.y2 / width, maze.row - 1) + + for y in gridYmin...gridYmax { + for x in gridXmin...gridXmax { + let result = checkGridWalls(ballPos: Point(ball.x1, ball.y1), gridPos: Point(x, y)) + + if result.bottom { + ball.y1 = (y + 1) * width - 1 - ball.size + } + + if result.top || result.right || result.left { + ball.y1 = y * width - 1 - ball.size + } + } + } + } + + // Move upwards. + if acceleration.y < -0.25 { + ball.y1 -= speed + + let gridXmin = max(ball.x1 / width, 0) + let gridXmax = min(ball.x2 / width, maze.column - 1) + let gridYmin = max(ball.y1 / width, 0) + let gridYmax = min(ball.y2 / width, maze.row - 1) + + for y in gridYmin...gridYmax { + for x in gridXmin...gridXmax { + let result = checkGridWalls(ballPos: Point(ball.x1, ball.y1), gridPos: Point(x, y)) + + if result.top { + ball.y1 = y * width + 1 + } + + if result.bottom || result.right || result.left { + ball.y1 = (y + 1) * width + 1 + } + } + } + } + + // If the ball's position has changed, update the display. + if lastBallPos.x != ball.x1 || lastBallPos.y != ball.y1 { + layer.draw() { canvas in + canvas.fillRectangle(at: lastBallPos, width: ball.size, height: ball.size, data: maze.bgColor) + } + layer.draw() { canvas in + canvas.fillRectangle(at: Point(ball.x1, ball.y1), width: ball.size, height: ball.size, data: ballColor) + } + layer.render(into: &frameBuffer, output: &screenBuffer, transform: Pixel.toRGB565LE) { dirty, data in + screen.writeBitmap(x: dirty.x, y: dirty.y, width: dirty.width, height: dirty.height, data: data) + } + } + } + + // Check if the ball collides with a wall. + func checkCollision(ballPos: Point, wallP1: Point, wallP2: Point) -> Bool { + return ball.x1 <= wallP2.x && ball.x2 >= wallP1.x && ball.y1 <= wallP2.y && ball.y2 >= wallP1.y + } + + // Check if the ball collides with any wall of a cell in the maze grid. + func checkGridWalls(ballPos: Point, gridPos: Point) -> Wall { + let walls = maze.grids[maze.getIndex(gridPos)].walls + var result = Wall(top: false, right: false, bottom: false, left: false) + + if walls.top && + checkCollision(ballPos: ballPos, wallP1: Point(gridPos.x * width, gridPos.y * width), wallP2: Point((gridPos.x + 1) * width, gridPos.y * width)) { + result.top = true + } + + if walls.right && + checkCollision(ballPos: ballPos, wallP1: Point((gridPos.x + 1) * width, gridPos.y * width), wallP2: Point((gridPos.x + 1) * width, (gridPos.y + 1) * width)) { + result.right = true + + } + + if walls.bottom && + checkCollision(ballPos: ballPos, wallP1: Point(gridPos.x * width, (gridPos.y + 1) * width), wallP2: Point((gridPos.x + 1) * width, (gridPos.y + 1) * width)) { + result.bottom = true + } + + if walls.left && + checkCollision(ballPos: ballPos, wallP1: Point(gridPos.x * width, gridPos.y * width), wallP2: Point(gridPos.x * width, (gridPos.y + 1) * width)) { + result.left = true + } + + return result + } +} \ No newline at end of file diff --git a/Examples/SwiftIOPlayground/13MoreProjects/MazeGame/Sources/MazeGame/Grid.swift b/Examples/SwiftIOPlayground/13MoreProjects/MazeGame/Sources/MazeGame/Grid.swift new file mode 100644 index 0000000..9ccabe5 --- /dev/null +++ b/Examples/SwiftIOPlayground/13MoreProjects/MazeGame/Sources/MazeGame/Grid.swift @@ -0,0 +1,13 @@ +struct Wall { + var top: Bool + var right: Bool + var bottom: Bool + var left: Bool +} + +struct Grid { + let x: Int + let y: Int + var walls = Wall(top: true, right: true, bottom: true, left: true) + var visited = false +} \ No newline at end of file diff --git a/Examples/SwiftIOPlayground/13MoreProjects/MazeGame/Sources/MazeGame/Maze.swift b/Examples/SwiftIOPlayground/13MoreProjects/MazeGame/Sources/MazeGame/Maze.swift new file mode 100644 index 0000000..225c1aa --- /dev/null +++ b/Examples/SwiftIOPlayground/13MoreProjects/MazeGame/Sources/MazeGame/Maze.swift @@ -0,0 +1,186 @@ +import MadGraphics +import ST7789 +import SwiftIO + +// Generate a random maze. +struct Maze { + let width: Int + let column: Int + let row: Int + + let bgColor = Pixel(0xFF3F_52E3) + let wallColor = Pixel.white + let canvasSize: Size + + var current = Point(0, 0) + var grids: [Grid] = [] + var stack: [Point] = [] + + init(width: Int, layer: Layer) { + self.canvasSize = layer.bounds.size + self.width = width + column = canvasSize.width / width + row = canvasSize.height / width + reset(layer) + } + + // Reset all walls to their default state. + mutating func reset(_ layer: Layer) { + grids = [] + stack = [] + current = Point(0, 0) + + // Initially, all walls are present. + for y in 0.. Bool { + return grids.filter { !$0.visited }.count == 0 + } + + // Calculate the index of a cell in the array. + func getIndex(_ point: Point) -> Int { + return point.x + point.y * column + } + + // Remove the wall between two cells. + mutating func removeWall(_ current: Point, _ next: Point) { + let x = current.x - next.x + + if x == 1 { + grids[getIndex(current)].walls.left = false + grids[getIndex(next)].walls.right = false + } else if x == -1 { + grids[getIndex(current)].walls.right = false + grids[getIndex(next)].walls.left = false + } + + let y = current.y - next.y + + if y == 1 { + grids[getIndex(current)].walls.top = false + grids[getIndex(next)].walls.bottom = false + } else if y == -1 { + grids[getIndex(current)].walls.bottom = false + grids[getIndex(next)].walls.top = false + } + } + + // Find nearby cells that haven't been visited yet, and select one randomly. + func getNext() -> Point? { + var neighbors: [Point] = [] + + if current.y > 0 { + let top = Point(current.x, current.y - 1) + + if !grids[getIndex(top)].visited { + neighbors.append(top) + } + } + if current.x < column - 1 { + let right = Point(current.x + 1, current.y) + + if !grids[getIndex(right)].visited { + neighbors.append(right) + } + } + + if current.y < row - 1 { + let bottom = Point(current.x, current.y + 1) + if !grids[getIndex(bottom)].visited { + neighbors.append(bottom) + } + } + + if current.x > 0 { + let left = Point(current.x - 1, current.y) + if !grids[getIndex(left)].visited { + neighbors.append(left) + } + } + + return neighbors.randomElement() + } +} \ No newline at end of file diff --git a/Examples/SwiftIOPlayground/13MoreProjects/MazeGame/Sources/MazeGame/MazeGame.swift b/Examples/SwiftIOPlayground/13MoreProjects/MazeGame/Sources/MazeGame/MazeGame.swift new file mode 100644 index 0000000..6b4539b --- /dev/null +++ b/Examples/SwiftIOPlayground/13MoreProjects/MazeGame/Sources/MazeGame/MazeGame.swift @@ -0,0 +1,53 @@ +import SwiftIO +import MadBoard +import ST7789 +import MadGraphics +import LIS3DH + +@main +public struct MazeGame { + public static func main() { + // Initialize the SPI pin and the digital pins for the LCD. + 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) + + // Initialize the LCD using the pins above. Rotate the screen to keep the original at the upper left. + let screen = ST7789(spi: spi, cs: cs, dc: dc, rst: rst, bl: bl, rotation: .angle90) + + let i2c = I2C(Id.I2C0) + let accelerometer = LIS3DH(i2c) + + let layer = Layer(at: Point.zero, width: screen.width, height: screen.height) + let mazeGame = Game(screen: screen, layer: layer) + + let resetButton = DigitalIn(Id.D1) + + var reset = false + resetButton.setInterrupt(.falling) { + reset = true + } + + var sleepTime: Float = 0 + let maxTime: Float = 20 + let minTime: Float = 5 + + while true { + // If the reset button is pressed, restart the game. + if reset { + mazeGame.reset() + reset = false + } + + // Update ball's position based on the acceleration. + let acceleration = accelerometer.readXYZ() + mazeGame.update(acceleration) + + // Map the acceleration into a sleep time in order to control the speed of the ball. + sleepTime = min(max(abs(acceleration.x), abs(acceleration.y)), 1) * (minTime - maxTime) + maxTime + sleep(ms: Int(sleepTime)) + } + } +} \ No newline at end of file diff --git a/Examples/SwiftIOPlayground/13MoreProjects/MorseCode/.gitignore b/Examples/SwiftIOPlayground/13MoreProjects/MorseCode/.gitignore index 0023a53..966161a 100644 --- a/Examples/SwiftIOPlayground/13MoreProjects/MorseCode/.gitignore +++ b/Examples/SwiftIOPlayground/13MoreProjects/MorseCode/.gitignore @@ -1,8 +1,91 @@ -.DS_Store -/.build -/Packages +# Xcode +# +# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore + +## User settings xcuserdata/ + +## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) +*.xcscmblueprint +*.xccheckout + +## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) +build/ DerivedData/ -.swiftpm/configuration/registries.json -.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata -.netrc +*.moved-aside +*.pbxuser +!default.pbxuser +*.mode1v3 +!default.mode1v3 +*.mode2v3 +!default.mode2v3 +*.perspectivev3 +!default.perspectivev3 + +## Obj-C/Swift specific +*.hmap + +## App packaging +*.ipa +*.dSYM.zip +*.dSYM + + +## Playgrounds +timeline.xctimeline +playground.xcworkspace + +# Swift Package Manager +# +# Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. +# Packages/ +# Package.pins +Package.resolved +# *.xcodeproj +# +# Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata +# hence it is not needed unless you have added a package configuration file to your project +.swiftpm +.build/ +.DS_Store + +# CocoaPods +# +# We recommend against adding the Pods directory to your .gitignore. However +# you should judge for yourself, the pros and cons are mentioned at: +# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control +# +# Pods/ +# +# Add this line if you want to avoid checking in source code from the Xcode workspace +# *.xcworkspace + +# Carthage +# +# Add this line if you want to avoid checking in source code from Carthage dependencies. +# Carthage/Checkouts + +Carthage/Build/ + +# Accio dependency management +Dependencies/ +.accio/ + +# fastlane +# +# It is recommended to not store the screenshots in the git repo. +# Instead, use fastlane to re-generate the screenshots whenever they are needed. +# For more information about the recommended setup visit: +# https://docs.fastlane.tools/best-practices/source-control/#source-control + +fastlane/report.xml +fastlane/Preview.html +fastlane/screenshots/**/*.png +fastlane/test_output + +# Code Injection +# +# After new code Injection tools there's a generated folder /iOSInjectionProject +# https://github.com/johnno1962/injectionforxcode + +iOSInjectionProject/ diff --git a/Examples/SwiftIOPlayground/13MoreProjects/MorseCode/Package.mmp b/Examples/SwiftIOPlayground/13MoreProjects/MorseCode/Package.mmp index 102d0e2..cfcf88e 100644 --- a/Examples/SwiftIOPlayground/13MoreProjects/MorseCode/Package.mmp +++ b/Examples/SwiftIOPlayground/13MoreProjects/MorseCode/Package.mmp @@ -1,17 +1,28 @@ # This is a MadMachine project file in TOML format -# This file holds those parameters that could not be managed by SwiftPM -# Edit this file would change the behavior of the building/downloading procedure -# Those project files in the dependent libraries would be IGNORED +# This file contains parameters that cannot be managed by SwiftPM +# Editing this file will alter the behavior of the build/download process +# Project files within dependent libraries will be IGNORED # Specify the board name below -# There are "SwiftIOBoard" and "SwiftIOMicro" now +# Supported boards are listed as follows +# "SwiftIOBoard" +# "SwiftIOMicro" board = "SwiftIOMicro" -# Specifiy the target triple below -# There are "thumbv7em-unknown-none-eabi" and "thumbv7em-unknown-none-eabihf" now -# If your code use significant floating-point calculation, -# plz set it to "thumbv7em-unknown-none-eabihf" -triple = "thumbv7em-unknown-none-eabi" +# Specify the target triple below +# Supported architectures are listed as follows +# "thumbv7em-unknown-none-eabi" +# "thumbv7em-unknown-none-eabihf" +# "armv7em-none-none-eabi" +triple = "armv7em-none-none-eabi" + +# Enable or disable hardware floating-point support below +# If your code involves significant floating-point calculations, please set it to 'true' +hard-float = true + +# Enable or disable float register below +# If your code involves significant floating-point calculations, please set it to 'true' +float-abi = false # Reserved for future use version = 1 diff --git a/Examples/SwiftIOPlayground/13MoreProjects/MorseCode/Package.swift b/Examples/SwiftIOPlayground/13MoreProjects/MorseCode/Package.swift index 7b6cc68..b8c5a8d 100644 --- a/Examples/SwiftIOPlayground/13MoreProjects/MorseCode/Package.swift +++ b/Examples/SwiftIOPlayground/13MoreProjects/MorseCode/Package.swift @@ -1,23 +1,26 @@ // swift-tools-version: 5.9 // The swift-tools-version declares the minimum version of Swift required to build this package. - import PackageDescription - let package = Package( name: "MorseCode", dependencies: [ // Dependencies declare other packages that this package depends on. .package(url: "https://github.com/madmachineio/SwiftIO.git", branch: "main"), .package(url: "https://github.com/madmachineio/MadBoards.git", branch: "main"), + .package(url: "https://github.com/madmachineio/MadDrivers.git", branch: "main"), + .package(url: "https://github.com/madmachineio/CFreeType.git", from: "2.13.2"), ], targets: [ - // Targets are the basic building blocks of a package, defining a module or a test suite. - // Targets can depend on other targets in this package and products from dependencies. + // Targets are the basic building blocks of a package. A target can define a module or a test suite. + // Targets can depend on other targets in this package, and on products in packages this package depends on. .executableTarget( name: "MorseCode", dependencies: [ "SwiftIO", "MadBoards", + // use specific library would speed up the compile procedure + .product(name: "ST7789", package: "MadDrivers"), + "CFreeType", ]), ] ) diff --git a/Examples/SwiftIOPlayground/13MoreProjects/MorseCode/README.md b/Examples/SwiftIOPlayground/13MoreProjects/MorseCode/README.md new file mode 100644 index 0000000..71a7cb1 --- /dev/null +++ b/Examples/SwiftIOPlayground/13MoreProjects/MorseCode/README.md @@ -0,0 +1,3 @@ +# MorseCode + +A description of this package. diff --git a/Examples/SwiftIOPlayground/13MoreProjects/MorseCode/Resources/Fonts/Roboto-Italic.ttf b/Examples/SwiftIOPlayground/13MoreProjects/MorseCode/Resources/Fonts/Roboto-Italic.ttf new file mode 100644 index 0000000..1b5eaa3 Binary files /dev/null and b/Examples/SwiftIOPlayground/13MoreProjects/MorseCode/Resources/Fonts/Roboto-Italic.ttf differ diff --git a/Examples/SwiftIOPlayground/13MoreProjects/MorseCode/Resources/Fonts/Roboto-Regular.ttf b/Examples/SwiftIOPlayground/13MoreProjects/MorseCode/Resources/Fonts/Roboto-Regular.ttf new file mode 100644 index 0000000..ddf4bfa Binary files /dev/null and b/Examples/SwiftIOPlayground/13MoreProjects/MorseCode/Resources/Fonts/Roboto-Regular.ttf differ diff --git a/Examples/SwiftIOPlayground/13MoreProjects/MorseCode/Sources/MorseCode/MorseCode.swift b/Examples/SwiftIOPlayground/13MoreProjects/MorseCode/Sources/MorseCode/MorseCode.swift index f636722..9298a02 100644 --- a/Examples/SwiftIOPlayground/13MoreProjects/MorseCode/Sources/MorseCode/MorseCode.swift +++ b/Examples/SwiftIOPlayground/13MoreProjects/MorseCode/Sources/MorseCode/MorseCode.swift @@ -1,6 +1,9 @@ // Import the SwiftIO library to set input/output and MadBoard to use pin id. import SwiftIO import MadBoard +// Import the driver for the screen. +import ST7789 +import MadGraphics @main public struct MorseCode { @@ -12,8 +15,17 @@ public struct MorseCode { // Initialize a buzzer used to tell typing states. let buzzer = PWMOut(Id.PWM5A) - // Create a periodic timer that alerts every 10s. - let timer = Timer(period: 10) + // Initialize the SPI pin and the digital pins for the LCD. + 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) + + // Initialize the LCD using the pins above. Rotate the screen to keep the original at the upper left. + let screen = ST7789(spi: spi, cs: cs, dc: dc, rst: rst, bl: bl, rotation: .angle90) + var screenBuffer = [UInt16](repeating: 0, count: 240 * 240) + var frameBuffer = [UInt32](repeating: 0, count: 240 * 240) // Store the morse code for 26 letters and 0-9. let dict: [String: String] = [ @@ -36,11 +48,11 @@ public struct MorseCode { var text = "" // A threshold for a long press which matches a dah. - let longPressCount = 30 + let longPressCount = 15 // Thresholds to decide if you have finished type a character or a word. - let letterReleaseCount = 60 - let wordReleaseCount = 150 + let letterReleaseCount = 30 + let wordReleaseCount = 100 // Store the duration of a specified button state to compare with the thresholds above. var pressCount = 0 @@ -50,10 +62,36 @@ public struct MorseCode { var justPressed = false var justReleased = false + var lastLetter = "" + + var drawPixel = false + var drawLow = true + var drawLine = true + var buzzerCount = 0 - var lastLetter = "" + let font = Font(path: "/lfs/Resources/Fonts/Roboto-Regular.ttf" , pointSize: 12, dpi: 220) + let rootLayer = Layer(at: Point.zero, width: 240, height: 240) + + let animationLayer = Layer(at: Point(0, 229), width: 240, height: 10) + rootLayer.append(animationLayer) + + let colors = [ + Pixel.red, + Pixel.orange, + Pixel.yellow, + Pixel.lime, + Pixel.cyan, + Pixel.blue, + Pixel.purple + ] + var colorIndex = 0 + + var lineText = TextLayer(at: Point.zero, font: font, foregroundColor: colors[colorIndex]) + rootLayer.append(lineText) + // Create a periodic timer that alerts every 10ms. + let timer = Timer(period: 10) timer.setInterrupt() { if button.read() { // If button is pressed, store the duration to judge if it's long press or short press. @@ -62,7 +100,6 @@ public struct MorseCode { justPressed = true } pressCount += 1 - led.write(true) } else { // If the button is not pressed, store the duration to judge if // you have finished typing a character or a word. @@ -82,6 +119,8 @@ public struct MorseCode { if buzzerCount > 10 { buzzer.suspend() } + + drawPixel = true } while true { @@ -91,13 +130,18 @@ public struct MorseCode { // add a space after what you have typed. // The buzzer produces a higher sound as a notification. if lastLetter != "" && lastLetter != " "{ - text += " " - - print("Message: \"\(text)\"") + text = "" buzzer.set(frequency: 2000, dutycycle: 0.5) buzzerCount = 0 lastLetter = " " + + colorIndex += 1 + colorIndex = colorIndex % colors.count + + let y = lineText.position.y + Int(Float(lineText.frame.size.height) * 1.2) + lineText = TextLayer(at: Point(0, y), font: font, foregroundColor: colors[colorIndex]) + rootLayer.append(lineText) } morseCode = "" } else if releaseCount > letterReleaseCount { @@ -105,11 +149,11 @@ public struct MorseCode { // If so, display it on the LCD and make the buzzer to produce a sound. if let letter = dict[morseCode] { text += letter - print("Message: \"\(text)\"") lastLetter = letter + lineText.string = text morseCode = "" buzzer.set(frequency: 1200, dutycycle: 0.5) buzzerCount = 0 @@ -123,6 +167,10 @@ public struct MorseCode { } else { morseCode += dit } + led.write(false) + + drawLine = true + drawLow = true justReleased = false pressCount = 0 @@ -130,11 +178,50 @@ public struct MorseCode { // Check if you have press the buuton. If so, it will get ready to store the duration after the button is release. if justPressed { + led.write(true) + + drawLine = true + drawLow = false + justPressed = false releaseCount = 0 } + if drawPixel { + drawAnimationLayer(layer: animationLayer, low: drawLow, line: drawLine) + + drawPixel = false + drawLine = false + } + + rootLayer.render(into: &frameBuffer, output: &screenBuffer, transform: Pixel.toRGB565LE) { dirty, data in + screen.writeBitmap(x: dirty.x, y: dirty.y, width: dirty.width, height: dirty.height, data: data) + } + sleep(ms: 1) } } } + +func drawAnimationLayer(layer: Layer, low: Bool, line: Bool) { + let newContent = Canvas(width: layer.bounds.width, height: layer.bounds.height) + + var oldRect = layer.bounds + oldRect.size.width -= 1 + oldRect.origin.x = 1 + newContent.merge(from: layer.contents!, in: oldRect, to: Point.zero) + + if line { + newContent.drawLine(from: Point(layer.bounds.width - 1, 0), to: Point(layer.bounds.width - 1, layer.bounds.height - 1), data: Pixel.red) + } else { + if low { + newContent.setPixel(at: Point(layer.bounds.width - 1, layer.bounds.height - 1), Pixel.red) + } else { + newContent.setPixel(at: Point(layer.bounds.width - 1, 0), Pixel.red) + } + } + + layer.draw() { canvas in + canvas.merge(from: newContent) + } +} \ No newline at end of file diff --git a/Examples/SwiftIOPlayground/13MoreProjects/Pong/Package.mmp b/Examples/SwiftIOPlayground/13MoreProjects/Pong/Package.mmp index 102d0e2..cfcf88e 100644 --- a/Examples/SwiftIOPlayground/13MoreProjects/Pong/Package.mmp +++ b/Examples/SwiftIOPlayground/13MoreProjects/Pong/Package.mmp @@ -1,17 +1,28 @@ # This is a MadMachine project file in TOML format -# This file holds those parameters that could not be managed by SwiftPM -# Edit this file would change the behavior of the building/downloading procedure -# Those project files in the dependent libraries would be IGNORED +# This file contains parameters that cannot be managed by SwiftPM +# Editing this file will alter the behavior of the build/download process +# Project files within dependent libraries will be IGNORED # Specify the board name below -# There are "SwiftIOBoard" and "SwiftIOMicro" now +# Supported boards are listed as follows +# "SwiftIOBoard" +# "SwiftIOMicro" board = "SwiftIOMicro" -# Specifiy the target triple below -# There are "thumbv7em-unknown-none-eabi" and "thumbv7em-unknown-none-eabihf" now -# If your code use significant floating-point calculation, -# plz set it to "thumbv7em-unknown-none-eabihf" -triple = "thumbv7em-unknown-none-eabi" +# Specify the target triple below +# Supported architectures are listed as follows +# "thumbv7em-unknown-none-eabi" +# "thumbv7em-unknown-none-eabihf" +# "armv7em-none-none-eabi" +triple = "armv7em-none-none-eabi" + +# Enable or disable hardware floating-point support below +# If your code involves significant floating-point calculations, please set it to 'true' +hard-float = true + +# Enable or disable float register below +# If your code involves significant floating-point calculations, please set it to 'true' +float-abi = false # Reserved for future use version = 1 diff --git a/Examples/SwiftIOPlayground/13MoreProjects/Pong/Sources/Pong/Game.swift b/Examples/SwiftIOPlayground/13MoreProjects/Pong/Sources/Pong/Game.swift index 70146a3..bfe083c 100644 --- a/Examples/SwiftIOPlayground/13MoreProjects/Pong/Sources/Pong/Game.swift +++ b/Examples/SwiftIOPlayground/13MoreProjects/Pong/Sources/Pong/Game.swift @@ -3,7 +3,7 @@ import SwiftIO typealias Point = (x: Int, y: Int) -struct PongGame { +class PongGame { var leftPlayer: Paddle var rightPlayer: Paddle @@ -89,7 +89,7 @@ struct PongGame { } - mutating func play() { + func play() { if leftPlayer.score < Constants.targetScore && rightPlayer.score < Constants.targetScore { @@ -253,7 +253,7 @@ struct PongGame { } // Reset the game. - mutating func reset() { + func reset() { screen.clearScreen(Constants.bgColor) window.draw() diff --git a/Examples/SwiftIOPlayground/13MoreProjects/Pong/Sources/Pong/Pong.swift b/Examples/SwiftIOPlayground/13MoreProjects/Pong/Sources/Pong/Pong.swift index e148a41..46c4cde 100644 --- a/Examples/SwiftIOPlayground/13MoreProjects/Pong/Sources/Pong/Pong.swift +++ b/Examples/SwiftIOPlayground/13MoreProjects/Pong/Sources/Pong/Pong.swift @@ -30,7 +30,7 @@ public struct Pong { let resetButton = DigitalIn(Id.D1) // Start the game. - var game = PongGame(leftPot: leftPot, rightPot: rightPot, screen: screen, speaker: speaker) + let game = PongGame(leftPot: leftPot, rightPot: rightPot, screen: screen, speaker: speaker) var lastButtonState = false diff --git a/Examples/SwiftIOPlayground/13MoreProjects/SandSimulation/.gitignore b/Examples/SwiftIOPlayground/13MoreProjects/SandSimulation/.gitignore new file mode 100644 index 0000000..0023a53 --- /dev/null +++ b/Examples/SwiftIOPlayground/13MoreProjects/SandSimulation/.gitignore @@ -0,0 +1,8 @@ +.DS_Store +/.build +/Packages +xcuserdata/ +DerivedData/ +.swiftpm/configuration/registries.json +.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata +.netrc diff --git a/Examples/SwiftIOPlayground/13MoreProjects/SandSimulation/Package.mmp b/Examples/SwiftIOPlayground/13MoreProjects/SandSimulation/Package.mmp new file mode 100644 index 0000000..8af5322 --- /dev/null +++ b/Examples/SwiftIOPlayground/13MoreProjects/SandSimulation/Package.mmp @@ -0,0 +1,28 @@ +# This is a MadMachine project file in TOML format +# This file contains parameters that cannot be managed by SwiftPM +# Editing this file will alter the behavior of the build/download process +# Project files within dependent libraries will be IGNORED + +# Specify the board name below +# Supported boards are listed as follows +# "SwiftIOBoard" +# "SwiftIOMicro" +board = "SwiftIOMicro" + +# Specify the target triple below +# Supported architectures are listed as follows +# "thumbv7em-unknown-none-eabi" +# "thumbv7em-unknown-none-eabihf" +# "armv7em-none-none-eabi" +triple = "armv7em-none-none-eabi" + +# Enable or disable hardware floating-point support below +# If your code involves significant floating-point calculations, please set it to 'true' +hard-float = true + +# Enable or disable float register below +# If your code involves significant floating-point calculations, please set it to 'true' +float-abi = false + +# Reserved for future use +version = 1 \ No newline at end of file diff --git a/Examples/SwiftIOPlayground/13MoreProjects/SandSimulation/Package.swift b/Examples/SwiftIOPlayground/13MoreProjects/SandSimulation/Package.swift new file mode 100644 index 0000000..b70a4f1 --- /dev/null +++ b/Examples/SwiftIOPlayground/13MoreProjects/SandSimulation/Package.swift @@ -0,0 +1,29 @@ +// swift-tools-version: 5.9 +// The swift-tools-version declares the minimum version of Swift required to build this package. + +import PackageDescription + +let package = Package( + name: "SandSimulation", + dependencies: [ + // Dependencies declare other packages that this package depends on. + .package(url: "https://github.com/madmachineio/SwiftIO.git", branch: "main"), + .package(url: "https://github.com/madmachineio/MadBoards.git", branch: "main"), + .package(url: "https://github.com/madmachineio/MadDrivers.git", branch: "main"), + .package(url: "https://github.com/madmachineio/CFreeType.git", from: "2.13.2"), + ], + targets: [ + // Targets are the basic building blocks of a package, defining a module or a test suite. + // Targets can depend on other targets in this package and products from dependencies. + .executableTarget( + name: "SandSimulation", + dependencies: [ + "SwiftIO", + "MadBoards", + // Use specific library name rather than "MadDrivers" would speed up the build procedure. + .product(name: "ST7789", package: "MadDrivers"), + .product(name: "LIS3DH", package: "MadDrivers"), + "CFreeType", + ]), + ] +) diff --git a/Examples/SwiftIOPlayground/13MoreProjects/SandSimulation/Sources/SandSimulation/Sand.swift b/Examples/SwiftIOPlayground/13MoreProjects/SandSimulation/Sources/SandSimulation/Sand.swift new file mode 100644 index 0000000..5d2bcb4 --- /dev/null +++ b/Examples/SwiftIOPlayground/13MoreProjects/SandSimulation/Sources/SandSimulation/Sand.swift @@ -0,0 +1,180 @@ +import MadGraphics + +struct Particle { + var x: Int + var y: Int + var xSpeed: Float + var ySpeed: Float + let color: Pixel +} + +struct Sand { + let count = 1000 + let size = 3 + var particles: [Particle] = [] + + let colors: [Pixel] = [.red, .orange, .yellow, .lime, .cyan, .blue, .purple, .magenta] + var grid: [Bool] + + let column: Int + let row: Int + + init(layer: Layer, _ acceleration: (x: Float, y: Float, z: Float)) { + column = layer.bounds.width / size + row = layer.bounds.height / size + grid = [Bool](repeating: false, count: column * row) + + // Draw the particles on the top of the layer. + var index = 0 + + while index < count { + let x = index % column + let y = index / column + + particles.append(Particle(x: x, y: y, xSpeed: 0, ySpeed: 0, color: colors.randomElement()!)) + layer.draw() { canvas in + canvas.fillRectangle(at: Point(x * size, y * size), width: size, height: size, data: particles[y * column + x].color) + } + grid[y * column + x] = true + + index += 1 + } + } + + mutating func update(layer: Layer, _ acceleration: (x: Float, y: Float, z: Float)) { + updateSpeed(acceleration) + + for i in particles.indices { + var xSpeed = particles[i].xSpeed + var ySpeed = particles[i].ySpeed + + let lastX = particles[i].x + let lastY = particles[i].y + + var newX = particles[i].x + Int(xSpeed) + var newY = particles[i].y + Int(ySpeed) + + // If the particle collides with a wall, bounce it. + if newX > column - 1 { + newX = column - 1 + xSpeed /= -2 + } else if newX < 0 { + newX = 0 + xSpeed /= -2 + } + + if newY > row - 1 { + newY = row - 1 + ySpeed /= -2 + } else if newY < 0 { + newY = 0 + ySpeed /= -2 + } + + let lastIndex = lastY * column + lastX + let newIndex = newY * column + newX + + // The particle moves and collide with other particle. + if lastIndex != newIndex && grid[newIndex] { + if abs(lastIndex - newIndex) == 1 { + // The particle moves horizontally. + xSpeed /= -2 + newX = lastX + } else if abs(lastIndex - newIndex) == column { + // The particle moves vertically. + ySpeed /= -2 + newY = lastY + } else { + // If the particle moves diagonally, find an available position adjacent to the particle to place it. + if abs(xSpeed) >= abs(ySpeed) { + // The speed on the x-axis is greater than the speed on the y-axis. + if !grid[lastY * column + newX] { + // Suppress movement along the y-axis. + newY = lastY + ySpeed /= -2 + } else if !grid[newY * column + lastX] { + // Suppress movement along the x-axis. + newX = lastX + xSpeed /= -2 + } else { + // Remain still. + newX = lastX + newY = lastY + xSpeed /= -2 + ySpeed /= -2 + } + } else { + // The speed on the x-axis is less than the speed on the y-axis. + if !grid[newY * column + lastX] { + // Suppress movement along the x-axis. + newX = lastX + xSpeed /= -2 + } else if !grid[lastY * column + newX] { + // Suppress movement along the y-axis. + newY = lastY + ySpeed /= -2 + } else { + // Remain still. + newX = lastX + newY = lastY + xSpeed /= -2 + ySpeed /= -2 + } + } + } + } + + particles[i].x = newX + particles[i].y = newY + particles[i].xSpeed = xSpeed + particles[i].ySpeed = ySpeed + grid[lastY * column + lastX] = false + grid[newY * column + newX] = true + layer.draw() { canvas in + canvas.fillRectangle(at: Point(lastX * size, lastY * size), width: size, height: size, data: Pixel.black) + } + layer.draw() { canvas in + canvas.fillRectangle(at: Point(newX * size, newY * size), width: size, height: size, data: particles[i].color) + } + } + } + + mutating func updateSpeed(_ acceleration: (x: Float, y: Float, z: Float)) { + var xAccel = -acceleration.x + var yAccel = acceleration.y + var zAccel = min(abs(acceleration.z), 1) + + if abs(xAccel) <= 0.1 && abs(yAccel) <= 0.1 { + // Prevent the particles from moving when the board is lying on the table not completely level. + for i in particles.indices { + particles[i].xSpeed = 0 + particles[i].ySpeed = 0 + } + } else { + // Acceleration on z-axis simulates the effect of gravitational on the motion of the particles. + // When z-axis acceleration is close to 1, sensor is flat, and gravity barely affects xy movement. + // Lower z-axis acceleration means more gravity influence on xy motion. + zAccel = 0.5 - zAccel / 2 + xAccel -= zAccel + yAccel -= zAccel + + //A slight random motion is added to each particle according to the z-axis acceleration. + // Their speed stays below 1 to avoid overlap. + // However, the rapid iteration speed creates the illusion of smooth particle movement. + for i in particles.indices { + var xSpeed = particles[i].xSpeed + xAccel + Float.random(in: 0...zAccel) + if abs(xSpeed) > 1 { + xSpeed /= abs(xSpeed) + } + + var ySpeed = particles[i].ySpeed + yAccel + Float.random(in: 0...zAccel) + if abs(ySpeed) > 1 { + ySpeed /= abs(ySpeed) + } + + particles[i].xSpeed = xSpeed + particles[i].ySpeed = ySpeed + } + } + } +} \ No newline at end of file diff --git a/Examples/SwiftIOPlayground/13MoreProjects/SandSimulation/Sources/SandSimulation/SandSimulation.swift b/Examples/SwiftIOPlayground/13MoreProjects/SandSimulation/Sources/SandSimulation/SandSimulation.swift new file mode 100644 index 0000000..4e48e73 --- /dev/null +++ b/Examples/SwiftIOPlayground/13MoreProjects/SandSimulation/Sources/SandSimulation/SandSimulation.swift @@ -0,0 +1,43 @@ +import SwiftIO +import MadBoard +import ST7789 +import MadGraphics +import LIS3DH + +@main +public struct SandSimulation { + public static func main() { + // Initialize the SPI pin and the digital pins for the LCD. + 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) + + // Initialize the LCD using the pins above. Rotate the screen to keep the original at the upper left. + let screen = ST7789(spi: spi, cs: cs, dc: dc, rst: rst, bl: bl, rotation: .angle90) + var screenBuffer = [UInt16](repeating: 0, count: screen.width * screen.width) + var frameBuffer = [UInt32](repeating: 0, count: screen.width * screen.width) + + let layer = Layer(at: Point.zero, width: screen.width, height: screen.height) + + // Initialize the accelerometer. + let i2c = I2C(Id.I2C0) + let accelerometer = LIS3DH(i2c) + + // Draw the sand particle. + var sand = Sand(layer: layer, accelerometer.readXYZ()) + layer.render(into: &frameBuffer, output: &screenBuffer, transform: Pixel.toRGB565LE) { dirty, data in + screen.writeBitmap(x: dirty.x, y: dirty.y, width: dirty.width, height: dirty.height, data: data) + } + + // Update sand particle positions based on movement. + while true { + sand.update(layer: layer, accelerometer.readXYZ()) + layer.render(into: &frameBuffer, output: &screenBuffer, transform: Pixel.toRGB565LE) { dirty, data in + screen.writeBitmap(x: dirty.x, y: dirty.y, width: dirty.width, height: dirty.height, data: data) + } + sleep(ms: 1) + } + } +} \ No newline at end of file diff --git a/Examples/SwiftIOPlayground/13MoreProjects/SpinningCube/.gitignore b/Examples/SwiftIOPlayground/13MoreProjects/SpinningCube/.gitignore new file mode 100644 index 0000000..0023a53 --- /dev/null +++ b/Examples/SwiftIOPlayground/13MoreProjects/SpinningCube/.gitignore @@ -0,0 +1,8 @@ +.DS_Store +/.build +/Packages +xcuserdata/ +DerivedData/ +.swiftpm/configuration/registries.json +.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata +.netrc diff --git a/Examples/SwiftIOPlayground/13MoreProjects/SpinningCube/Package.mmp b/Examples/SwiftIOPlayground/13MoreProjects/SpinningCube/Package.mmp new file mode 100644 index 0000000..8af5322 --- /dev/null +++ b/Examples/SwiftIOPlayground/13MoreProjects/SpinningCube/Package.mmp @@ -0,0 +1,28 @@ +# This is a MadMachine project file in TOML format +# This file contains parameters that cannot be managed by SwiftPM +# Editing this file will alter the behavior of the build/download process +# Project files within dependent libraries will be IGNORED + +# Specify the board name below +# Supported boards are listed as follows +# "SwiftIOBoard" +# "SwiftIOMicro" +board = "SwiftIOMicro" + +# Specify the target triple below +# Supported architectures are listed as follows +# "thumbv7em-unknown-none-eabi" +# "thumbv7em-unknown-none-eabihf" +# "armv7em-none-none-eabi" +triple = "armv7em-none-none-eabi" + +# Enable or disable hardware floating-point support below +# If your code involves significant floating-point calculations, please set it to 'true' +hard-float = true + +# Enable or disable float register below +# If your code involves significant floating-point calculations, please set it to 'true' +float-abi = false + +# Reserved for future use +version = 1 \ No newline at end of file diff --git a/Examples/SwiftIOPlayground/13MoreProjects/SpinningCube/Package.swift b/Examples/SwiftIOPlayground/13MoreProjects/SpinningCube/Package.swift new file mode 100644 index 0000000..3e8db3e --- /dev/null +++ b/Examples/SwiftIOPlayground/13MoreProjects/SpinningCube/Package.swift @@ -0,0 +1,28 @@ +// swift-tools-version: 5.9 +// The swift-tools-version declares the minimum version of Swift required to build this package. + +import PackageDescription + +let package = Package( + name: "SpinningCube", + dependencies: [ + // Dependencies declare other packages that this package depends on. + .package(url: "https://github.com/madmachineio/SwiftIO.git", branch: "main"), + .package(url: "https://github.com/madmachineio/MadBoards.git", branch: "main"), + .package(url: "https://github.com/madmachineio/MadDrivers.git", branch: "main"), + .package(url: "https://github.com/madmachineio/CFreeType.git", from: "2.13.2"), + ], + targets: [ + // Targets are the basic building blocks of a package, defining a module or a test suite. + // Targets can depend on other targets in this package and products from dependencies. + .executableTarget( + name: "SpinningCube", + dependencies: [ + "SwiftIO", + "MadBoards", + // Use specific library name rather than "MadDrivers" would speed up the build procedure. + .product(name: "ST7789", package: "MadDrivers"), + "CFreeType", + ]), + ] +) diff --git a/Examples/SwiftIOPlayground/13MoreProjects/SpinningCube/Sources/SpinningCube/SpinningCube.swift b/Examples/SwiftIOPlayground/13MoreProjects/SpinningCube/Sources/SpinningCube/SpinningCube.swift new file mode 100644 index 0000000..c1eee06 --- /dev/null +++ b/Examples/SwiftIOPlayground/13MoreProjects/SpinningCube/Sources/SpinningCube/SpinningCube.swift @@ -0,0 +1,99 @@ +import SwiftIO +import MadBoard +import ST7789 +import MadGraphics + + +@main +public struct SpinningCube { + public static func main() { + // Initialize the SPI pin and the digital pins for the LCD. + 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) + + // Initialize the LCD using the pins above. Rotate the screen to keep the original at the upper left. + let screen = ST7789(spi: spi, cs: cs, dc: dc, rst: rst, bl: bl, rotation: .angle90) + var screenBuffer = [UInt16](repeating: 0, count: screen.width * screen.height) + + let layer = Layer(at: Point.zero, width: screen.width, height: screen.height) + var frameBuffer = [UInt32](repeating: 0, count: screen.width * screen.height) + + let colors: [Pixel] = [.red, .orange, .yellow, .lime, .blue, Pixel(0xFF4B_0082), .purple] + + // The vertices of the cube in 3D space. + let points: [[Float]] = [ + [-0.5, -0.5, -0.5], + [0.5, -0.5, -0.5], + [0.5, 0.5, -0.5], + [-0.5, 0.5, -0.5], + + [-0.5, -0.5, 0.5], + [0.5, -0.5, 0.5], + [0.5, 0.5, 0.5], + [-0.5, 0.5, 0.5] + ] + + // The coordinates on 2D plan of cube vertices. + var projectedPoints = [Point](repeating: Point.zero, count: points.count) + var lastProjectedPoints = projectedPoints + + var angle: Float = 0 + let width: Float = 200 + let offset = Point(x: 120, y: 120) + + while true { + // Rotate vertices of the cube and project them onto a 2D plane using perspective projection. + for i in points.indices { + let rotated = rotate([[points[i][0]], [points[i][1]], [points[i][2]]], angle: angle) + let projected = project(distance: 2, point: rotated) + projectedPoints[i] = Point(x: Int(projected[0][0] * width), y: Int(projected[1][0] * width)) + } + + // Draw the cube in its current position. + for i in 0..<4 { + layer.draw() { canvas in + canvas.drawLine(from: lastProjectedPoints[i] + offset, + to: lastProjectedPoints[(i + 1) % 4] + offset, + data: colors[(3 * i) % colors.count]) + + canvas.drawLine(from: lastProjectedPoints[i + 4] + offset, + to: lastProjectedPoints[(i + 1) % 4 + 4] + offset, + data: colors[(3 * i + 1) % colors.count]) + + canvas.drawLine(from: lastProjectedPoints[i] + offset, + to: lastProjectedPoints[i + 4] + offset, + data: colors[(3 * i + 2) % colors.count]) + } + } + + layer.render(into: &frameBuffer, output: &screenBuffer, transform: Pixel.toRGB565LE) { dirty, data in + screen.writeBitmap(x: dirty.x, y: dirty.y, width: dirty.width, height: dirty.height, data: data) + } + + sleep(ms: 10) + + // Clear the cube from its last position + for i in 0..<4 { + layer.draw() { canvas in + canvas.drawLine(from: lastProjectedPoints[i] + offset, + to: lastProjectedPoints[(i + 1) % 4] + offset, + data: Pixel.black) + + canvas.drawLine(from: lastProjectedPoints[i + 4] + offset, + to: lastProjectedPoints[(i + 1) % 4 + 4] + offset, + data: Pixel.black) + + canvas.drawLine(from: lastProjectedPoints[i] + offset, + to: lastProjectedPoints[i + 4] + offset, + data: Pixel.black) + } + } + + lastProjectedPoints = projectedPoints + angle += 0.02 + } + } +} \ No newline at end of file diff --git a/Examples/SwiftIOPlayground/13MoreProjects/SpinningCube/Sources/SpinningCube/Uitls.swift b/Examples/SwiftIOPlayground/13MoreProjects/SpinningCube/Sources/SpinningCube/Uitls.swift new file mode 100644 index 0000000..c0f4de1 --- /dev/null +++ b/Examples/SwiftIOPlayground/13MoreProjects/SpinningCube/Sources/SpinningCube/Uitls.swift @@ -0,0 +1,68 @@ +// Calculate the projection of a point onto a 2D plane using perspective projection. +// `distance` refers to the distance from the viewer. +func project(distance: Float, point: [[Float]]) -> [[Float]] { + let z = 1 / (distance - point[2][0]) + let projectionMatrix: [[Float]] = [ + [z, 0, 0], + [0, z, 0] + ] + + return matrixMultiply(projectionMatrix, point) +} + +// Rotate a point around the x, y, and z axes by a given angle. +func rotate(_ point: [[Float]], angle: Float) -> [[Float]] { + var rotated = matrixMultiply(rotateX(angle), point) + rotated = matrixMultiply(rotateY(angle), rotated) + rotated = matrixMultiply(rotateZ(angle), rotated) + return rotated +} + +// Rotate around x-axis. +func rotateX(_ angle: Float) -> [[Float]] { + return [[1, 0, 0], + [0, cosf(angle), -sinf(angle)], + [0, sinf(angle), cosf(angle)]] +} + +// Rotate around y-axis. +func rotateY(_ angle: Float) -> [[Float]] { + return [[cosf(angle), 0, sinf(angle)], + [0, 1, 0], + [-sinf(angle), 0, cosf(angle)]] +} + +// Rotate around z-axis. +func rotateZ(_ angle: Float) -> [[Float]] { + return [[cosf(angle), -sinf(angle), 0], + [sinf(angle), cosf(angle), 0], + [0, 0, 1]] +} + +func matrixMultiply(_ matrix1: [[Float]], _ matrix2: [[Float]]) -> [[Float]] { + // Check if matrices are compatible for multiplication + guard matrix1[0].count == matrix2.count else { + return [[]] + } + + // Initialize result matrix with zeros + var result = Array(repeating: Array(repeating: Float(0), count: matrix2[0].count), count: matrix1.count) + + // Perform matrix multiplication + for i in 0.. Float + +@_extern(c, "sinf") +func sinf(_: Float) -> Float \ No newline at end of file diff --git a/Examples/SwiftIOPlayground/13MoreProjects/WordClock/.gitignore b/Examples/SwiftIOPlayground/13MoreProjects/WordClock/.gitignore new file mode 100644 index 0000000..0023a53 --- /dev/null +++ b/Examples/SwiftIOPlayground/13MoreProjects/WordClock/.gitignore @@ -0,0 +1,8 @@ +.DS_Store +/.build +/Packages +xcuserdata/ +DerivedData/ +.swiftpm/configuration/registries.json +.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata +.netrc diff --git a/Examples/SwiftIOPlayground/13MoreProjects/WordClock/Package.mmp b/Examples/SwiftIOPlayground/13MoreProjects/WordClock/Package.mmp new file mode 100644 index 0000000..8af5322 --- /dev/null +++ b/Examples/SwiftIOPlayground/13MoreProjects/WordClock/Package.mmp @@ -0,0 +1,28 @@ +# This is a MadMachine project file in TOML format +# This file contains parameters that cannot be managed by SwiftPM +# Editing this file will alter the behavior of the build/download process +# Project files within dependent libraries will be IGNORED + +# Specify the board name below +# Supported boards are listed as follows +# "SwiftIOBoard" +# "SwiftIOMicro" +board = "SwiftIOMicro" + +# Specify the target triple below +# Supported architectures are listed as follows +# "thumbv7em-unknown-none-eabi" +# "thumbv7em-unknown-none-eabihf" +# "armv7em-none-none-eabi" +triple = "armv7em-none-none-eabi" + +# Enable or disable hardware floating-point support below +# If your code involves significant floating-point calculations, please set it to 'true' +hard-float = true + +# Enable or disable float register below +# If your code involves significant floating-point calculations, please set it to 'true' +float-abi = false + +# Reserved for future use +version = 1 \ No newline at end of file diff --git a/Examples/SwiftIOPlayground/13MoreProjects/WordClock/Package.swift b/Examples/SwiftIOPlayground/13MoreProjects/WordClock/Package.swift new file mode 100644 index 0000000..bf1befa --- /dev/null +++ b/Examples/SwiftIOPlayground/13MoreProjects/WordClock/Package.swift @@ -0,0 +1,29 @@ +// swift-tools-version: 5.9 +// The swift-tools-version declares the minimum version of Swift required to build this package. + +import PackageDescription + +let package = Package( + name: "WordClock", + dependencies: [ + // Dependencies declare other packages that this package depends on. + .package(url: "https://github.com/madmachineio/SwiftIO.git", branch: "main"), + .package(url: "https://github.com/madmachineio/MadBoards.git", branch: "main"), + .package(url: "https://github.com/madmachineio/MadDrivers.git", branch: "main"), + .package(url: "https://github.com/madmachineio/CFreeType.git", from: "2.13.2"), + ], + targets: [ + // Targets are the basic building blocks of a package, defining a module or a test suite. + // Targets can depend on other targets in this package and products from dependencies. + .executableTarget( + name: "WordClock", + dependencies: [ + "SwiftIO", + "MadBoards", + // Use specific library name rather than "MadDrivers" would speed up the build procedure. + .product(name: "ST7789", package: "MadDrivers"), + .product(name: "PCF8563", package: "MadDrivers"), + "CFreeType", + ]), + ] +) diff --git a/Examples/SwiftIOPlayground/13MoreProjects/WordClock/Resources/Fonts/Graduate-Regular.ttf b/Examples/SwiftIOPlayground/13MoreProjects/WordClock/Resources/Fonts/Graduate-Regular.ttf new file mode 100644 index 0000000..29061d9 Binary files /dev/null and b/Examples/SwiftIOPlayground/13MoreProjects/WordClock/Resources/Fonts/Graduate-Regular.ttf differ diff --git a/Examples/SwiftIOPlayground/13MoreProjects/WordClock/Sources/WordClock/WordClock.swift b/Examples/SwiftIOPlayground/13MoreProjects/WordClock/Sources/WordClock/WordClock.swift new file mode 100644 index 0000000..03891e7 --- /dev/null +++ b/Examples/SwiftIOPlayground/13MoreProjects/WordClock/Sources/WordClock/WordClock.swift @@ -0,0 +1,107 @@ +import SwiftIO +import MadBoard +import ST7789 +import MadGraphics +import PCF8563 + +@main +public struct WordClock { + public static func main() { + // Initialize the SPI pin and the digital pins for the LCD. + 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) + + // Initialize the LCD using the pins above. Rotate the screen to keep the original at the upper left. + let screen = ST7789(spi: spi, cs: cs, dc: dc, rst: rst, bl: bl, rotation: .angle90) + var screenBuffer = [UInt16](repeating: 0, count: screen.width * screen.width) + var frameBuffer = [UInt32](repeating: 0, count: screen.width * screen.width) + + // Initialize the rtc. + let i2c = I2C(Id.I2C0) + let rtc = PCF8563(i2c) + + // Update the RTC with your current time. + let currentTime = PCF8563.Time( + year: 2024, month: 12, day: 5, hour: 18, + minute: 29, second: 0, dayOfWeek: 4 + ) + + sleep(ms: 500) + rtc.setTime(currentTime) + + let layer = Layer(at: Point.zero, width: screen.width, height: screen.height) + + // Calculate the point size for each character. + // Get masks from the font file for all characters of the word clock. + let dpi = 220 + let pointSize = min(screen.width / Words.column, screen.height / Words.row) * 72 / dpi * 4 / 5 + let characterMasks = getCharacterMasks(path: "/lfs/Resources/Fonts/Graduate-Regular.ttf", pointSize: pointSize, dpi: dpi) + + let clock = WordView(layer: layer, characterMasks: characterMasks) + + // Define the colors to be used for displaying the words. + let colors: [Pixel] = [.red, .orange, .yellow, .lime, .blue, .magenta, .cyan, Pixel(UInt32(0xFE679A))] + + // Highlight each column with different colors in turn. + var index = 0 + for x in 0.. [[Mask]] { + var characterMasks = [[Mask]](repeating: [Mask](), count: Words.row) + + let font = Font(path: path, pointSize: pointSize, dpi: dpi) + for y in 0.. (minute: [Minute], prep: Preposition?, hour: Hour) { + static func getWords(hourNumber: Int, minuteNumber: Int) -> Words { + var minuteWord: [MinuteWord] = [] + var hourWord = HourWord(rawValue: hourNumber < 12 ? hourNumber : hourNumber % 12)! + var prepWord: PrepositionWord? = nil + + switch minuteNumber { + case 5..<10: + minuteWord.append(.five) + prepWord = .past + case 10..<15: + minuteWord.append(.ten) + prepWord = .past + case 15..<20: + minuteWord.append(.quarter) + prepWord = .past + case 20..<25: + minuteWord.append(.twenty) + prepWord = .past + case 25..<30: + minuteWord.append(.twenty) + minuteWord.append(.five) + prepWord = .past + case 30..<35: + minuteWord.append(.half) + prepWord = .past + case 35..<40: + //minuteWord.append(contentsOf: [.twenty, .five]) + minuteWord.append(.twenty) + minuteWord.append(.five) + prepWord = .to + hourWord = HourWord(rawValue: hourNumber + 1 < 12 ? (hourNumber + 1) : (hourNumber + 1) % 12)! + case 40..<45: + minuteWord.append(.twenty) + prepWord = .to + hourWord = HourWord(rawValue: hourNumber + 1 < 12 ? (hourNumber + 1) : (hourNumber + 1) % 12)! + case 45..<50: + minuteWord.append(.quarter) + prepWord = .to + hourWord = HourWord(rawValue: hourNumber + 1 < 12 ? (hourNumber + 1) : (hourNumber + 1) % 12)! + case 50..<55: + minuteWord.append(.ten) + prepWord = .to + hourWord = HourWord(rawValue: hourNumber + 1 < 12 ? (hourNumber + 1) : (hourNumber + 1) % 12)! + case 55..<60: + minuteWord.append(.five) + prepWord = .to + hourWord = HourWord(rawValue: hourNumber + 1 < 12 ? (hourNumber + 1) : (hourNumber + 1) % 12)! + default: break + } + + return Words(hourWord: hourWord, prepWord: prepWord, minuteWord: minuteWord) + } +} \ No newline at end of file