|
| 1 | +# Embedded Swift -- Integrating with embedded SDKs |
| 2 | + |
| 3 | +**⚠️ Embedded Swift is experimental. This document might be out of date with latest development.** |
| 4 | + |
| 5 | +**‼️ Use the latest downloadable 'Trunk Development' snapshot from swift.org to use Embedded Swift. Public releases of Swift do not yet support Embedded Swift.** |
| 6 | + |
| 7 | +For an introduction and motivation into Embedded Swift, please see "[A Vision for Embedded Swift](https://github.com/apple/swift-evolution/blob/main/visions/embedded-swift.md)", a Swift Evolution document highlighting the main goals and approaches. |
| 8 | + |
| 9 | +The following document sketches how to integrate Swift code into some popular embedded platforms' SDKs and build systems. |
| 10 | + |
| 11 | +## Integrating with Raspberry Pi Pico (W) build system: |
| 12 | + |
| 13 | +Development for [Raspberry Pi Pico and Pico W](https://www.raspberrypi.com/products/raspberry-pi-pico/) normally uses the [Pico SDK](https://github.com/raspberrypi/pico-sdk) and the vendor provides several [sample projects in the pico-examples repository](https://github.com/raspberrypi/pico-examples). The SDK and sample project setup is described in: |
| 14 | + |
| 15 | +- https://www.raspberrypi.com/documentation/microcontrollers/c_sdk.html#sdk-setup |
| 16 | +- https://datasheets.raspberrypi.com/pico/getting-started-with-pico.pdf |
| 17 | + |
| 18 | +Before trying to use Swift with the Pico SDK, make sure your environment works and can build the provided C/C++ sample projects. |
| 19 | + |
| 20 | +### CMake setup with a bridging header |
| 21 | + |
| 22 | +The Pico SDK is using CMake as its build system, and so the simplest way to integrate with it is to also use CMake to build a Swift firmware application on top of the SDK and the libraries from it. The following describes an example set up of that on a "blinky" example (code that just blinks the built-in LED). |
| 23 | + |
| 24 | +Let's create a directory with a Swift source file, a bridging header, and a CMake definition file: |
| 25 | + |
| 26 | +``` |
| 27 | +./SwiftPicoBlinky/Main.swift |
| 28 | +./SwiftPicoBlinky/BridgingHeader.h |
| 29 | +./SwiftPicoBlinky/CMakeLists.txt |
| 30 | +``` |
| 31 | + |
| 32 | +In `Main.swift`, let's add basic logic to initialize the GPIO port for the Pico's built-in LED, and then turn it on and off in a loop: |
| 33 | + |
| 34 | +```swift |
| 35 | +@main |
| 36 | +struct Main { |
| 37 | + static func main() { |
| 38 | + let led = UInt32(PICO_DEFAULT_LED_PIN) |
| 39 | + gpio_init(led) |
| 40 | + gpio_set_dir(led, /*out*/true) |
| 41 | + while true { |
| 42 | + gpio_put(led, true) |
| 43 | + sleep_ms(250) |
| 44 | + gpio_put(led, false) |
| 45 | + sleep_ms(250) |
| 46 | + } |
| 47 | + } |
| 48 | +} |
| 49 | +``` |
| 50 | + |
| 51 | +Notice that we're using functions and variables defined in C in the Pico SDK. For that to be possible, the Swift compiler needs to have access to the C header files that define these functions and variables. The cleanest option would be to define a modulemap, but for simplicity let's just use a bridging header to make declarations visible in Swift without a module. `BridgingHeader.h` should contain: |
| 52 | + |
| 53 | +```c |
| 54 | +#pragma once |
| 55 | + |
| 56 | +#include "pico/stdlib.h" |
| 57 | +``` |
| 58 | + |
| 59 | +Finally, we need to define the application's build rules in CMake that will be using CMake logic from the Pico SDK. The following content of `CMakeLists.txt` shows how to *manually call swiftc, the Swift compiler* instead of using the recently added CMake native support for Swift, so that we can see the full Swift compilation command. |
| 60 | + |
| 61 | +```cmake |
| 62 | +cmake_minimum_required(VERSION 3.13) |
| 63 | +include($ENV{PICO_SDK_PATH}/external/pico_sdk_import.cmake) |
| 64 | +
|
| 65 | +project(swift-blinky) |
| 66 | +pico_sdk_init() |
| 67 | +execute_process(COMMAND xcrun -f swiftc OUTPUT_VARIABLE SWIFTC OUTPUT_STRIP_TRAILING_WHITESPACE) |
| 68 | +
|
| 69 | +add_executable(swift-blinky) |
| 70 | +add_custom_command( |
| 71 | + OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/_swiftcode.o |
| 72 | + COMMAND |
| 73 | + ${SWIFTC} |
| 74 | + -target armv6m-none-none-eabi -Xcc -mfloat-abi=soft -Xcc -fshort-enums |
| 75 | + -Xfrontend -function-sections -enable-experimental-feature Embedded -wmo -parse-as-library |
| 76 | + $$\( echo '$<TARGET_PROPERTY:swift-blinky,INCLUDE_DIRECTORIES>' | tr '\;' '\\n' | sed -e 's/\\\(.*\\\)/-Xcc -I\\1/g' \) |
| 77 | + $$\( echo '${CMAKE_C_IMPLICIT_INCLUDE_DIRECTORIES}' | tr ' ' '\\n' | sed -e 's/\\\(.*\\\)/-Xcc -I\\1/g' \) |
| 78 | + -import-bridging-header ${CMAKE_CURRENT_LIST_DIR}/BridgingHeader.h |
| 79 | + ${CMAKE_CURRENT_LIST_DIR}/Main.swift |
| 80 | + -c -o ${CMAKE_CURRENT_BINARY_DIR}/_swiftcode.o |
| 81 | + DEPENDS |
| 82 | + ${CMAKE_CURRENT_LIST_DIR}/BridgingHeader.h |
| 83 | + ${CMAKE_CURRENT_LIST_DIR}/Main.swift |
| 84 | +) |
| 85 | +add_custom_target(swift-blinky-swiftcode DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/_swiftcode.o) |
| 86 | +
|
| 87 | +target_link_libraries(swift-blinky |
| 88 | + pico_stdlib hardware_uart hardware_gpio |
| 89 | + ${CMAKE_CURRENT_BINARY_DIR}/_swiftcode.o |
| 90 | +) |
| 91 | +add_dependencies(swift-blinky swift-blinky-swiftcode) |
| 92 | +pico_add_extra_outputs(swift-blinky) |
| 93 | +``` |
| 94 | + |
| 95 | +With these three files, we can now configure and build a Swift firmware for the Pico: |
| 96 | + |
| 97 | +```bash |
| 98 | +$ export TOOLCHAINS=org.swift.59202401301a |
| 99 | +$ export PICO_BOARD=pico |
| 100 | +$ export PICO_SDK_PATH=<path_to_pico_sdk> |
| 101 | +$ export PICO_TOOLCHAIN_PATH=<path_to_arm_toolchain> |
| 102 | +$ ls -al |
| 103 | +-rw-r--r-- 1 kuba staff 39B Feb 2 22:08 BridgingHeader.h |
| 104 | +-rw-r--r-- 1 kuba staff 1.3K Feb 2 22:08 CMakeLists.txt |
| 105 | +-rw-r--r-- 1 kuba staff 262B Feb 2 22:08 Main.swift |
| 106 | +$ mkdir build |
| 107 | +$ cd build |
| 108 | +$ cmake -S ../ -B . -G Ninja |
| 109 | +$ ninja -v |
| 110 | +``` |
| 111 | + |
| 112 | +This should produce several build artifacts in the `build/` subdirectory, include `swift-blinky.uf2`, which can be directly uploaded to the Pico by copying it into the fake Mass Storage Volume that the device presents when plugged over USB in BOOTSEL mode. |
0 commit comments