Skip to content

add Arduino LED and Wi-Fi SDKs #98

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 14 commits into
base: main
Choose a base branch
from
Open

Conversation

maartin0
Copy link

Adds two esp32 example projects using the espressif/arduino-esp32 library to demonstrate Wi-Fi scanning and a simple LED blink example

I've also mentioned this in the READMEs: note that these examples require ESP-IDF v5.3.2 since the Arduino releases are paired with specific IDF versions

Resolves #42

iCMDdev and others added 10 commits January 14, 2025 11:00
Provide new examples demonstrating how to run Swift Embedded on the Raspberry Pi 4B and 5 single-board computers, in a baremetal fashion, without an underlying operating system. The provided examples showcase how to blink the onboard green LED using Swift MMIO.
As seen at FOSDEM 2025

Note: ignore formatting for now
Updates to match latest swift-format configuration in swift-mmio.
* Make stm32-blink sample building as both Mach-O and ELF
* Use STM_BOARD env var to select which board to target on stm32-blink example
* Fix python lint issues in Tools/elf2hex.py
Updates the stm32-uart-echo and stm32-neopixel examples to use a swiftpm
toolset.json to specify various compiler and linker flags. Also updates
the Makefiles to leverage swiftpm to link the executable instead of
manually linking using `ld` directly.
Instead of having users manually manage python venvs this commit
migrates the python scripts to use `uv` which can handle installing
script dependencies automatically. Additionally it doesn't require
sourcing a venv setup script in every shell where you want to build.

A further improvement on the swift swift side should also be made to use
swiftly to manage swift toolchain versions instead of the `TOOLCHAINS`
env variable.
Switches from manually running various compilers and linkers to using
SwiftPM for most of the leg work. Additionally switches from -Osize to
-O which has notably better runtime performance.
@ericlewis
Copy link

Blink works but I’ve been unable to get WiFi to work. I’m using a seeed studio C6, so perhaps there are some differences.

@maartin0
Copy link
Author

Blink works but I’ve been unable to get WiFi to work. I’m using a seeed studio C6, so perhaps there are some differences.

Hmm, what errors are you getting? Is it compiling but just not outputting anything?

@kubamracek
Copy link
Collaborator

The diff looks good, thanks!

Could I ask for a new GH workflow to be added so that the code is built in CI? (Just building is good enough, no need to try to run it.)

@maartin0
Copy link
Author

I've just written the actions now but there's a dependency issue:

Some options:

  1. Use the latest arduino-esp32 pre-release which is based on IDF v5.4 and update the Arduino version when the stable release comes out
  2. Manually build the latest version of GLIBC in the action (although this would cause the action to take a lot longer)
  3. Don't use the espressif/idf docker image and manually install esp-idf in the action (similar to above)
  4. Create a custom docker image with IDF v5.3 and ubuntu 24.04

@maartin0
Copy link
Author

Not sure what you think is best, but I've done (1) which compiles successfully (example run)

@eric-humane
Copy link

Blink works but I’ve been unable to get WiFi to work. I’m using a seeed studio C6, so perhaps there are some differences.

Hmm, what errors are you getting? Is it compiling but just not outputting anything?

I didn't realize you were using the hardware serial 😆
also fun is that Arduino's String collides with swift's string.

I sorta fixed that with this:

public typealias ArduinoString = String

extension ArduinoString: CustomStringConvertible {
  public var description: Swift.String {
    return Swift.String(cString: self.c_str())
  }
}

extension ArduinoString: Equatable {
  public static func == (lhs: ArduinoString, rhs: ArduinoString) -> Bool {
    lhs.equals(rhs)
  }
  
  public static func == <RHS: StringProtocol>(lhs: ArduinoString, rhs: RHS) -> Bool {
    lhs.equals(String(Swift.String(rhs)))
  }
  
  public static func == <LHS: StringProtocol>(lhs: LHS, rhs: ArduinoString) -> Bool {
    rhs.equals(String(Swift.String(lhs)))
  }
}

extension ArduinoString: Comparable {
  public static func < (lhs: ArduinoString, rhs: ArduinoString) -> Bool {
    lhs.compareTo(rhs) < 0
  }
  
  public static func < <RHS: StringProtocol>(lhs: ArduinoString, rhs: RHS) -> Bool {
    lhs.compareTo(String(Swift.String(rhs))) < 0
  }
  
  public static func < <LHS: StringProtocol>(lhs: LHS, rhs: ArduinoString) -> Bool {
    lhs < rhs.description
  }
}

extension ArduinoString: Hashable {
  public func hash(into hasher: inout Hasher) {
    hasher.combine(description)
  }
}

extension ArduinoString: ExpressibleByStringLiteral {
  public init(stringLiteral value: Swift.String) {
    let cString = Array(value.utf8CString)
    self = ArduinoString(cString)
  }
}

// MARK: - Operators (+, +=)

extension ArduinoString {
  public static func + (lhs: ArduinoString, rhs: ArduinoString) -> ArduinoString {
    var newString = lhs
    _ = newString.concat(rhs)
    return newString
  }
  
  /// ArduinoString + SomeString (e.g. String, Substring, etc.)
  public static func + <RHS: StringProtocol>(lhs: ArduinoString, rhs: RHS) -> ArduinoString {
    ArduinoString(lhs.description + Swift.String(rhs))
  }
  
  /// SomeString + ArduinoString
  public static func + <LHS: StringProtocol>(lhs: LHS, rhs: ArduinoString) -> ArduinoString {
    ArduinoString(Swift.String(lhs) + rhs.description)
  }
  
  /// ArduinoString += ArduinoString
  public static func += (lhs: inout ArduinoString, rhs: ArduinoString) {
    lhs = lhs + rhs
  }
  
  /// ArduinoString += SomeString
  public static func += <RHS: StringProtocol>(lhs: inout ArduinoString, rhs: RHS) {
    lhs = lhs + rhs
  }
}

extension Swift.String {
  public static func += (lhs: inout Swift.String, rhs: ArduinoString) {
    lhs.append(contentsOf: rhs.description)
  }
}

extension ArduinoString {
  // Access the underlying Swift.String's StringProtocol properties
  public var utf8: Swift.String.UTF8View { Swift.String(cString: self.c_str()).utf8 }
  public var utf16: Swift.String.UTF16View { Swift.String(cString: self.c_str()).utf16 }
  public var unicodeScalars: Swift.String.UnicodeScalarView { Swift.String(cString: self.c_str()).unicodeScalars }
  
  // Common StringProtocol methods
  public func hasPrefix(_ prefix: Swift.String) -> Bool {
    return Swift.String(cString: self.c_str()).hasPrefix(prefix)
  }
  
  public func hasSuffix(_ suffix: Swift.String) -> Bool {
    return Swift.String(cString: self.c_str()).hasSuffix(suffix)
  }
  
  public func contains(_ other: Swift.Character) -> Bool {
    return Swift.String(cString: self.c_str()).contains(other)
  }
  
  // Index-based access
  public subscript(position: Swift.String.Index) -> Character {
    return Swift.String(cString: self.c_str())[position]
  }
  
  public subscript(bounds: Range<Swift.String.Index>) -> Swift.Substring {
    return Swift.String(cString: self.c_str())[bounds]
  }
  
  public var startIndex: Swift.String.Index {
    return Swift.String(cString: self.c_str()).startIndex
  }
  
  public var endIndex: Swift.String.Index {
    return Swift.String(cString: self.c_str()).endIndex
  }
  
  public func index(after i: Swift.String.Index) -> Swift.String.Index {
    return Swift.String(cString: self.c_str()).index(after: i)
  }
  
  public func index(before i: Swift.String.Index) -> Swift.String.Index {
    return Swift.String(cString: self.c_str()).index(before: i)
  }
}

it's not quite StringProtocol conformance, but does allow for the example to use the usb monitor.

@maartin0
Copy link
Author

That's very neat - I was using Swift 6.0.3 not 6.1 so didn't realise print(_:separator:terminator:) was now available on embedded; do you want me to add you as a collaborator on my fork so you can commit that?

@maartin0 maartin0 marked this pull request as draft March 25, 2025 13:33
@maartin0 maartin0 force-pushed the arduino-pr branch 3 times, most recently from 68bf7ae to 5582710 Compare March 26, 2025 21:46
@maartin0
Copy link
Author

maartin0 commented Mar 26, 2025

I had to slightly modify that CustomStringConvertible implementation since String#c_str isn't available without an annotation in the C++ library but that compiles and works quite nicely (run)

@maartin0 maartin0 marked this pull request as ready for review March 26, 2025 21:55
Replaces the register definitions used by the stm32-lcd-logo example to
use ones generated by svd2swift leveraging swift-mmio. Additionally
refactors much of the example code to have fewer layers of abstraction
as they make the code much harder to follow.
@kubamracek
Copy link
Collaborator

Can you elaborate on why we need the ArduinoString extensions at all? I.e. where are those extensions used from? I don't immediately see that in the code.

@maartin0
Copy link
Author

Yes they're not used for anything in the example; as @eric-humane suggested it brings ArduinoString closer to conforming to StringProtocol but I guess you could just convert them to Swift.String and use the existing functionality.

Do you think it would be neater to not include them to keep the example more minimal? The only one that's actually needed is the CustomStringConvertible extension.

@ericlewis
Copy link

ericlewis commented Mar 31, 2025

Yes they're not used for anything in the example; as @eric-humane suggested it brings ArduinoString closer to conforming to StringProtocol but I guess you could just convert them to Swift.String and use the existing functionality.

Do you think it would be neater to not include them to keep the example more minimal? The only one that's actually needed is the CustomStringConvertible extension.

That’s a common misconception about CustomStringConvertible actually. String concat won’t work etc. the other issue is it’s sort of “invisible” and hard to understand why it’s occurring. The better thing to do would be to use SWIFT_NAME, probably. But then you’re still having to convert these to swift strings for swift functions and it’s not really clear when or why you have to do that.

option: conform to string protocol
optimal option: swift rename the arduino string class
Most optimal option (?): Swift.String supports most of the arduino api string api (it’s much smaller than the option one) and strip out the arduino string class.

Either way, it’s a tough problem and really hard to debug. Even writing wrappers you run into this a bit.

Edit: I’m also not suggesting adding my code above. But just demonstrating how I fixed the problem.

Edit 2: or make arduino lib a proper module (import Arduino), probably best. But I was just going for quick and dirty here. I haven’t spent much time reading all the docs on this stuff here, just happened to have a C6 laying around.

@maartin0
Copy link
Author

That’s a common misconception about CustomStringConvertible actually. String concat won’t work etc. the other issue is it’s sort of “invisible” and hard to understand why it’s occurring. The better thing to do would be to use SWIFT_NAME, probably. But then you’re still having to convert these to swift strings for swift functions and it’s not really clear when or why you have to do that.

Interesting - I've only used string formatting in the example which works fine without the other extensions. I can see why WiFi.RSSI(i) + "dBm" might not work for example, but wouldn't Swift.String(WiFi.RSSI(i)) + "dBm", "\(WiFi.RSSI(i))dBm" and "\(WiFi.RSSI(i))" + "dBm" still work?

Edit: I’m also not suggesting adding my code above.

Sorry, should have probably waited for your reply. I'll strip out the extensions then

@eric-humane
Copy link

eric-humane commented Apr 1, 2025

That’s a common misconception about CustomStringConvertible actually. String concat won’t work etc. the other issue is it’s sort of “invisible” and hard to understand why it’s occurring. The better thing to do would be to use SWIFT_NAME, probably. But then you’re still having to convert these to swift strings for swift functions and it’s not really clear when or why you have to do that.

Interesting - I've only used string formatting in the example which works fine without the other extensions. I can see why WiFi.RSSI(i) + "dBm" might not work for example, but wouldn't Swift.String(WiFi.RSSI(i)) + "dBm", "\(WiFi.RSSI(i))dBm" and "\(WiFi.RSSI(i))" + "dBm" still work?

Edit: I’m also not suggesting adding my code above.

Sorry, should have probably waited for your reply. I'll strip out the extensions then

it would, it just sucks to have to do that 😆 that said if we keep this really concise i.e. no extra extensions etc, we could land it

@maartin0 maartin0 force-pushed the arduino-pr branch 2 times, most recently from 3daba1d to 1355e7d Compare April 11, 2025 10:53
@maartin0
Copy link
Author

I just realised there was a full arduino-esp32 release 2 weeks ago based on ESP-IDF v5.4 so I've rebased and updated it to use that version

Adds two esp32 example projects using the espressif/arduino-esp32 library to demonstrate Wi-Fi scanning and a simple LED blink example
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Support for Arduino libraries?
6 participants