To do I2C with the sdk I'm going to need to make calls to a fairly complicated C structure which is nested in some other structures, which Swift is having trouble just bringing in magically like it typically is able to do (note: macro 'i2c_default' unavailable: structure not supported, and that macro has this struct nested)
It will take a minute to do a reorg to be a multifile Swift & C so I can write a bridge in C that Swift can import.
Added a helper.c to dig into the I2C pico-sdk files rather than a real bridge object for now, and I have proof of concept working for I2C.
Will be tidying this up and reproducing the default badge firmware over the course of the week just to finish the thought.
I am curious, because my current process involves writing a working version of firmware in C first, why the CMake for the Swift example imports so so much more of the SDK than what I need for the C only to work, but I'll loop around to that after. Working works! and I'm not used to CMake as much as plain make.
It features Buttons, LEDs, USBSerial, 2 I2C busses, I2C peripheral interaction code (a touchwheel and an LED spiral)
Theres a lot I would change going forward but this has been a soothing distraction this week.
In the "final" round there is a HAL folder (still pretty tied to the hardware, maybe a protocol based approach in the future) and an SDK_Bridge folder that I also stashed the lwipopts.h in.
CMake wrangling makes one appreciate the package manager all the more! I noticed there are pico and stm32 examples that have Package.swift files paired with either custom make files or custom build scripts that I might look at to improve this code. Ideally the HAL and the hardware layer could be libraries.
I am still going to go back to the Blink example and rip out as much of the networking stack as possible.
I'm not sure I want to stay in the Pico ecosystem after that though. The architecture is very nicely done but very vendor-specific-flavored. Trying to figure out if SAMD, STM32 arm chips or RISCV would be more interesting/needed.
We have quite the selection of Dev boards and misc peripherals, to be honest.
So success in writing a stripped down blink that will work on both a pico and pico_w using the SDK (update the build.sh). I cheated a little to keep the CMake file cleaner. My C file makes the direct SDK calls, and my Swift calls MY C file and not the SDK directly. The pico-sdk CMake setup takes care of all the C compiling so my call to swiftc doesn't have to.
It has USBSerial too because that's how I debug (don't judge), but it has to use -no-allocations mode for now, so only StaticStrings
Also for proof of concept I added using @_cdecl to pass a function from the Swift to the C, but I'm wondering since the C compiler already has the .o from the swift compile if I couldn't adjust the linker somehow... but that is also for another day.
@main
struct Main {
static func blink(led: some DigitalIndicator, onTime:Int32, offTime:Int32) {
led.set(isOn: true)
blocking_sleep(onTime);
led.set(isOn: false)
blocking_sleep(offTime);
}
static func main() {
//MARK: SETUP
let statusLED = OnboardLED()
USBSerial.initHardware()
blink_set_number() //uses function passed into the C from Swift
blocking_sleep(1000);
//MARK: LOOP
while (true) {
blink(led: statusLED, onTime:250, offTime: 250)
USBSerial.send("Hello World")
}
}
}
@_cdecl("PassToSDKModule")
func doesThisStillWork(x: Int32) -> Int32 {
5 + x
}
protocol DigitalIndicator {
func set(isOn:Bool)
}
struct OnboardLED:DigitalIndicator {
init() {
//handle the result code yourself.
//let _ = pico_onboard_led_init()
//will crash program if can't find LED
onboard_led_assert_init()
}
func set(isOn:Bool) {
onboard_led_set(isOn)
}
}
struct USBSerial {
static func initHardware() {
let _ = usb_init_hardware()
}
static func send(_ message:StaticString) {
message.withUTF8Buffer { bufferPointer in
usb_serial_send(bufferPointer.baseAddress!)
}
}
}
Somethings I learned along the way:
I started by seeing how much of the badge code would work by stripping things out of the CMake, and that was educational.
I think either the ico_lwip_arch ,pico_cyw43_arch_none library includes or explicit -Xcc -DCYW43_LWIP/ -Xcc - DPICO_CYW43_ARCH_THREADSAFE_BACKGROUND calls some how also bring pico-malloc with them because once they’re gone the posix_memalign function I was using can’t find malloc anymore, and yet brining in the pico memory helper libraries explicitly didn’t fix that. (ETA: Pretty sure its LWIP that sorted the ability to find malloc)
switching the compiler over to -no-allocations mode helps, but then cannot use any runtime allocating types at all, so the code gets very unswifty very fast.
no var Arrays. Like at all. Which means you can’t get the conveincece &myArray to pass in to the C. Vectors can’t come soon enough!!! (Yay!)
Also cant use the fancy let x = if/switch syntax either
also can't use UnsafePointer<CChar> for strings being passed to C
My I2C code used A LOT of a lot of &myArray and let my_func = switch{}, but I got tired of fixing it and went back to the pico-example blink code and went from there instead.
et voila.
This will likely be my last update on this thread, but I'll be cleaning up the README to make the repo easier to parse.