top of page

Mastering Swift's Development Blog

Follow Us On Twitter
  • Writer's pictureJon Hoffman

print(“Hello BeagleBone AI-64”)

As long as I can remember I have had a fascinating with electronics and robotics. As a kid I would take apart my transistor radio or little handheld video games just to see what made them work.

Around ten years ago, about the time the first Raspberry Pi was released, I started tinkering with the Pi and the various other single board computers like the Arduino. Since then, I have worked on numerous boards and created hundreds of various projects with them. I even wrote a book on the Arduino which covers everything from the boards and electronics to programming and wireless communications.

One of my favorite boards has always been the BeagleBone Black. What I really like about it, was it could be a mini-Linux computer, like the Raspberry Pi, but it has more GPIO like the Arduino Mega. With the extra GPIO pins, especially the PWM pins, I was always able to do larger projects than I would be unable to do with the Raspberry Pi.

Recently, I wanted to get back into doing some robotic/maker projects using Swift but I found that Swift for Arm only supports the 64 bit versions. I could use Swift 5.1 with the Beaglebone Black but I was not able to use the latest 5.7 version, therefore I decided to give the newest Beagleboard, the Beaglebone AI-64 board, a try. The expansion headers are supposed to be compatible with the Beaglebone Black but everything else seems to be upgraded. This post will be to give a quick introduction on how to get started with the Beaglebone AI-64, how to install Swift and what to expect from Swift on Arm.

One thing to note, the instructions in the post, on setting up Swift and the Swift code, should work similarly, if not the same, on other 64-bit boards like the Raspberry Pi 4.

The beagleboard getting started page has great instructions on how to do the initial setup of the board. I would recommend using a 32GB or higher micro-SD card with the latest image, as described in the getting started page, rather than booting from the onboard eMMC. It makes it easy to have multiple images for different projects.

One of the first things that you will need to decided is if you want to run the board tethered from your computer or attached to a screen/keyboard/mouse. Since the board can be powered from the USB port on your computer, all you need for tethering is a USB cable with a type C connector on one end to connect to the board.

If you are tethering your board to your computer, once you connect the board to the computer, and give it a few seconds to boot, you can ssh into the board using ‘debian’ as the username and ‘temppwd’ as the password. The ip address to use is based on the operating system you are running and you can find the correct ip address list on the beagleboard getting started page.

If you would like attached a monitor/keyboard/mouse to the board, and your monitor uses a HDMI port, you can get a mini display port to HDMI adapter but make sure you get an ‘Active’ and not a ‘Passive’ adapter. A passive adapter relies on the source to do the conversion, but the boards display port is not setup to do that therefore you need an active one where the adapter does the conversion. Any standard USB keyboard or mouse should work with the board.

In order to install Swift we will need to have a connection to the internet. The Beagleboard AI-64 comes with a standard Gigabit Ethernet port. If you would like to use WiFi, most USB WiFi adapters that work with Debian Linux should work with this board but I would recommend doing some research before purchasing one. I just use the ethernet port.

Now that we have our Beaglebone AI-64 setup, either tethered to our computer or stand alone, we are ready to install Swift. Lucky for us the people at and have done the work for us. You can find detailed instructions in the user guide on the site.

I am putting links rather than instructions on this page because instructions can change over time, therefore by going to the original site you can see the latest instructions.

To follow these instructions and the rest of this post, you will need to be in a command shell (terminal). You can do this by either using ssh to access the board from your computer or using the terminal emulator application.

Once Swift is installed you can use the swiftc compiler to manually compile your file(s) or the Swift package manager to set up a project. I usually prefer the package manager, which is what I will use in the example here.

If we wish to create an executable package, we can initiate the package with the following commands:

mkdir <name of project>
cd <name of project>
swift package init --type executable

If we are creating a package to be used by other projects we can just leave off the ‘--type executable’. The first command creates the directory that our project will be in, and the second command puts us in that directory. The third command runs the swift package manager to create the project. The package manager will use the name of the directory that it is run in as the name for the project.

Before we start creating Swift projects, lets create a directory to hold all our projects in. Run the following command, in your home directory, to do this.

mkdir swift_code
cd swift_code

The first command will make a ‘swift_code’ directory and the second command will put us in that directory. Now let’s create the standard Hello World application. We can do this with the following commands.

mkdir hello_beaglebone
cd hello_beaglebone
swift package init --type executable

When the package manager creates an application it creates it as a hello world application therefore after the package manager is finished we can run the application with the following command.

swift run

After Swift builds the application we should see the standard ‘Hello World!’ Message printed to the console. When the package manager creates the package, it creates a complete file structures like this one:

├── Package.swift


├── .gitignore

├── Sources

│ └── hello_beaglebone

│ └── hello_beaglebone.swift

└── Tests

├── hello_beagleboneTests

└── hello_beagleboneTests.swift

Under the Sources/hello_beaglebone/ directory, the hello_beaglebone.swift is the main source file. The file should look something like this:

public struct hello_beaglebone {
    public private(set) var text = "Hello, World!"

    public static func main() {

Just for fun, lets change the “Hello, World!” to “Hello BeagleBone AI-64” and run the application again. Now we should see this for the output:

Hello BeagleBone AI-64

We could also build our project into an executable by running the following command:

swift build

Once we run this command the following directory structure will be made under the made project directory:

├── .build (A lot more underneath but we are looking for executable)

│ └── debug

│ └── hello_beaglebone

Therefore after we run the ‘swift build’ command we could then run the executable application like this:


Let’s do something a bit more exciting than a simple Hello World application. We will take the code from last week’s post on async and await and try to run it on the Beaglebone. If you have not read the post and are not familiar with async and await, you may want to take a look at the post before going further in this post. To begin with, let’s go back to our ‘swift_code’ directory and run the following commands.

mkdir concurrency
cd concurrency
swift package init --type executable

This starts our new project which is call concurrency. We will now create a new file in the Sources/concurrency/ directory called utils.swift and add the following code to it.

func doCalc() {
    let x = 100
    let y = x*x
    _ = y/x

func performCalculation(_ iterations: Int, tag: String) async {
    let clock = ContinuousClock()
    let time = clock.measure {
        for _ in 0 ..< iterations {
    print("\(tag) Took \(Double(time.components.attoseconds)/1.0e18) seconds")

func concurrency() async {
    let clock = ContinuousClock()
    let time = await clock.measure { 
        await performCalculation(100_000, tag: "first_Concurrent")
        await performCalculation(1_000_000, tag: "second_Concurrent")
        await performCalculation(10_000, tag: "third_Concurrent")
    print("----Concurrency took \(Double(time.components.attoseconds)/1.0e18) seconds")

func parallelism() async {
    let clock = ContinuousClock()
    let time = await clock.measure {
        async let one = performCalculation(100_000, tag: "first_Parallel")
        async let two = performCalculation(1_000_000, tag: "second_Parallel")
        async let three = performCalculation(10_000, tag: "third_Parallel")
        let _ = await [one, two, three]
    print("----Parallelism took \(Double(time.components.attoseconds)/1.0e18) seconds")

One thing to note, any file that ends in .swift, under the Sources/concurrency directory will automatically be compile and built into the finished project.

This is essentially the same code as last week’s post with one exception. We are using the new ContinuousClock() function that comes with Swift 5.7 rather than the CFAbsoluteTimeGetCurrent()function. The reason for this isn’t just that it is new and cool to use, but the CFAbsoluteTimeGetCurrent()function does not work with Swift for Arm, even if you import CoreFoundation.

You will find that there are a number of ‘niceties’ that we can use with iOS and Mac development that we cannot use on other platforms. For example the Combine framework is not present for Swift-Arm by default. We may be able to import it as a dependency but I have not tried it yet. SwiftUI is another thing we take for granted if we are developing within the Apple platforms that does not work on other environments.

Now let’s update our Sources/concurrency/concurrency.swift file with the following code:

public struct concurrency {
    public private(set) var text = "Hello, World!"

    public static func main() async {
            await concurrency()
            await parallelism() 

Notice that we added the async tag to our main() function. This will enable us to use await within this function. We are then able to call the concurrency() and parallelism() functions from our utils.swift file. Once again, if you are not familiar with async and await, please read our post from last week for an introduction.

If we run this code using the ‘swift run’ command, we should see output similar to what we see here:

Hello, World!
first_Concurrent Took 0.001955586 seconds
second_Concurrent Took 0.01967232 seconds
third_Concurrent Took 0.000196676 seconds
----Concurrency took 0.024195247 seconds
first_Parallel Took 0.002387521 seconds
third_Parallel Took 0.000168166 seconds
second_Parallel Took 0.019540875 seconds
----Parallelism took 0.022125807 seconds

Everything we did, with Swift, in this introduction should work similarly on other boards like the Raspberry Pi 4. Just remember if you want to use a version of Swift later than 5.1, you will need to make sure the board has a 64 bit processor.

We would like to resurrect th SwiftyBones library that enabled access to the GPIO, PWM and Analog pins of the BeagleBone Black but have it work with Swift 5 and the Beaglebone AI-64. We would also like to make the code asynchronous using Swift’s new concurrency framework as demonstrated in this post. The key will be as time allows because we have recently discovered we will need another method besides writing to the /sys directory to interact with the GPIO, PWM and Analog ports but we have recently, with the help of silver2row on the Beagleboard forums, a way to interact with the GPIO ports. With that, next week’s post will be on how to toggle an LED on and off with Swift. If you follow us on Twitter you can see the video now.

Going to be fun to see what Swift and the Beaglebone AI-64 can do together.



Mastering Swift 5.3

The sixth edition of this bestselling book, updated to cover through version 5.3 of the Swift programming language




Protocol Oriented Programming

Embrace the Protocol-Oriented design paradigm, for better code maintainability and increased performance, with Swift.



bottom of page