Embedded Swift running on the Raspberry Pi Pico

It works! I'm watching a blinking led :green_circle:

Apparantly, after installing the correct toolchain, I had to delete the build folder and recreate the build folder. Before, even after installing the toolchain and setting the correct ID, it still defaulted to the Xcode provided one. Lesson learned. ;)

Thank you for your help. You swiftc example command really helped find the problem. :pray:t2:

6 Likes

@maartene Glad that you got it working!

2 Likes

Congrats. Would like to read a tutorial showing all the steps, if you have the time to write one.

1 Like

Yeah, I’m definitely considering this.

However i do need to get a better grasp on it all. Don’t think I can write reliable tutorial just yet.

Also, I created two milestones to tackle first:

  • Understand which Swift features are available and which ones are not. At least how to work with strings and arrays;
  • Get serial communication working.
1 Like

Arrays (and even classes) are available when you provide an allocator, you'll see which C symbols to define in a linker error as soon as you try adding those into your codebase.

I also found that with latest snapshots even constant array literals don't require an allocator, apparently those get optimized into the .rodata section. It's dynamic arrays (re)allocation (or use of UnsafeBufferPointer.allocate, which is good substitute for arrays when you need to manage allocations manually) when it starts requiring a defined allocator.

You can also get quite far with StaticString, but string interpolation is much trickier. Assuming you have arrays working, one could come up with a #utf8 or even #ascii macros that transform string literals into [UInt8] literals. Similarly, macros for string interpolation would be needed. At that point you'd be working with byte strings, and of course no Unicode handling, unless you're ready to roll with your own Unicode support.

3 Likes

There are some interesting approaches in reduced usage cases:

For example if it is known that the interpolation will just be emitted via printing to a UART line or something like that you can still conform to the protocols for string interpolation. But instead of allocating, you can use the side effect of construction to emit the values on said printing output. This is obviously not a general purpose mechanism but can make some forward progress.

Instead we probably would want to have some sort of method to use the information in the compiler to build up an expected size and then use a stack allocation to pass down into each interpolated element (via some sort of alternative interpolation protocol) and have each element write itself into that buffer.

The more wide-spread issue for String proper is that it ends up requiring some relatively large data tables for normalization.

Here is a non-exhaustive list of things that have dependencies or are removed:

  • Existentials - e.g. any Thing are not allowed because they require type metadata. This also makes as? not as important and should be avoided even in class cases.
  • Embedded swift does not bring any runtime functions to the table - so things like allocating heap objects (Ala class) you end up needing to provide your own posix_memalign. Likewise for ref counting, you need to provide your own atomic primitives
  • String requires both heap allocations AND also needs NFC/NFD tables for Unicode normalization. We avoided bringing in String because even if someone provides the posix_memalign for the heap allocations that strings may use, they still need to have the normalization tables for things as simple as == or hash(into:). This is a known pain point and is something that probably should be investigated and iterated upon designing a method where folks can opt-in to using strings
  • crt0 and other bootstrapping from a entry point is not provided; this is a bring-your own type thing because it is very chip specific on how the setup must be done.
  • hardware interfaces are not something that is brought to the table by the compiler or swift stdlib - however the interfaces can be generated via swift-mmio and that can lead to a way to convert from register definitions to a hardware specific interface
  • hardware abstraction layers are also something that must be brought to the table . Working with a broad range of chips is something that the community will have to build.
  • Swift Concurrency is not something that will work out of the box.

The rest of Swift as a language and as the standard library should work; structures, enumerations, protocols, integer types, static strings all work without a hitch, classes, array, dictionary, set all require heap allocation and therefore need the memory allocation.

4 Likes

So, some progress. I got printing to serial working. So that’s great.

The use case I want to explore is this: you connect the box to serial and then you can interact with it while it runs some sort of (text based) game.

The “string interpolation by side effect” @Philippe_Hausler proposed is actually an interesting approach that might just work here.

@Max_Desiatov I guess for my use case missing strings is probably the biggest issue. I can define rooms using structs and static strings. And put them in an array literal.

It’s interesting working around the limitations here. :blush:

3 Likes

I ended up implementing a terrible (I don't know what I am doing) solution to provide posix_memalign and atomic primitives. There might be a better way to implement the functions (maybe using mutex or hardware_sync)

This is how I am using the string support to display a button using LVGL and update the label when I click on it

...
var count: Int = 0

func buttonEventHandler(_ event: UnsafeMutablePointer<lv_event_t>?) {
    guard let event = event else {
        print("Event is null")
        return
    }
    if lv_event_get_code(event) == LV_EVENT_CLICKED {
        print_memory_stats()
        print("Event Clicked")
        guard let button = lv_event_get_target(event)?.assumingMemoryBound(to: lv_obj_t.self) else {
            print("Failed to get button")
            return
        }
        guard let label = lv_obj_get_child(button, 0) else {
            print("Failed to get label")
            return
        }
        count += 1
        let labelText = "Counter: \(count)"
        labelText.withCString { cString in
            lv_label_set_text(label, cString)
        }
        print("Label text updated")

        //lv_obj_invalidate(label)
    }
}

func lvExampleButton() {
    print("Creating Button")
    let button = lv_button_create(lv_screen_active())
    lv_obj_align(button, lv_align_t(LV_ALIGN_CENTER.rawValue), 0, -40)
    let label = lv_label_create(button)
    let myString: StaticString = "Click Meeee!"
    myString.withUTF8Buffer { buffer in
        lv_label_set_text(label, buffer.baseAddress)
    }
    lv_obj_center(label)

    lv_obj_add_event_cb(
        button,
        { event in
            buttonEventHandler(event)
        },
        LV_EVENT_ALL,
        nil
    )
    print("Button Created, printing stats")
    print_memory_stats()
}
...
3 Likes

What hardware are you using? AFAIK we don't currently have any published code samples or sample projects that use Embedded Swift to render structured graphics, text, UI elements, etc. on an embedded device -- any chance you'd be willing to publish your work?

I have a semi-working (I haven’t implemented all of the widgets yet) wrapper around LVGL

I have been testing it with this 3.5” display from Waveshare and Pico W. But there really isn’t anything hardware specific about the wrapper

Once I’m happy with the overall API design I will split up the wrapper into its own Swift package (so it can be used on macOS and Linux as well) and also publish a working project using it in embedded Swift. This is how the wrapper can be used right now:

        var myCounter: Int = 0
        let label = LVGLLabel("", alignment: .bottomMid)
        var button = LVGLButton("Click Me", eventType: .pressed) { event in
            myCounter += 1
            label.setText("You clicked the button \(myCounter) times")
        }

        let _ = LVGLButton("Delete", alignment: .leftMid) { event in
            if let event = event, event.eventCode == .pressed {
                print("Deleting button")
                if button.exists() {
                    button.delete()
                }
            }
        }
2 Likes

Excellent! I would love to incubate projects like this (similar to SSWG) so we have a collection of well-supported packages for developers to choose from!

There are a couple LVGL swift wrappers I have seen floating around so it may be good to see if any of those implementations have useful ideas to copy or if their authors would like to collaborate! (I even have a wrapper built for a previous private project :sweat_smile:)

I suggest also splitting this conversation out into its own thread so its both easier to find and not conflated with the RPI discussion

4 Likes

@navan Would you be able to share an example repository of your work? I'm struggling to do anything useful with swift-embedded as I keep running into undefined reference to posix_memalign

Thanks

As indicated in swift/docs/EmbeddedSwift/UserManual.md at main · swiftlang/swift, posix_memalign is an external dependency for which you need to provide an implementation yourself.

Depending on whether you're using an SDK or not, the implementation might differ. Searching for posix_memalign on this forums will provide some pointers e.g. Example Pico-W code works on SuperCon badge - Development / Embedded - Swift Forums

2 Likes

Like Eric mentioned you need to provide a few implementations yourself. I took a stab at this and posted them on this gist. Including this file in your build pipeline should fix the posix_memalign stuff.

I should also be able to piece together a really basic example repository from my codebase. I'll try to do that over this weekend.

Do go through the thread that Eric has linked as it is a pretty interesting read.

2 Likes

If you want to nuke that problem from orbit you can also turn on no allocations (-no-allocations flag), which will make your code very unswifty, but bam this memory issue goes away.

+1 to @ebariaux 's point that the posix_memalign issues tends to be interactions with SDKs (what they supply, what versions of C, etc) related.

Note this flag is aspirational and should not be expected to be fully functional.

1 Like

Did not realize! Noted!