Skip to content

Commit 840986f

Browse files
Add IntegrateWithZephyr guide showing off project structure and commands
1 parent 3cd0f63 commit 840986f

File tree

2 files changed

+310
-0
lines changed

2 files changed

+310
-0
lines changed

Sources/EmbeddedSwift/Documentation.docc/Documentation.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ Embedded Swift is a compilation and language mode that enables development of ba
4545
- <doc:Baremetal>
4646
- <doc:IntegrateWithESP>
4747
- <doc:IntegrateWithPico>
48+
- <doc:IntegrateWithZephyr>
4849

4950
### Compiler Development and Details
5051

Lines changed: 309 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,309 @@
1+
# Integrating with Zephyr
2+
3+
**⚠️ Embedded Swift is experimental. This document might be out of date with latest development.**
4+
5+
For an introduction and motivation into Embedded Swift, please see "[A Vision for Embedded Swift](https://github.com/swiftlang/swift-evolution/blob/main/visions/embedded-swift.md)", a Swift Evolution document highlighting the main goals and approaches.
6+
7+
The following document outlines how to setup a Swift to Zephyr project for an emulated ARM Cortex M0, explaining a few key concepts along the way. For a complete working example on real hardware, however, refer to the [nrfx-blink-sdk](../../../../nrfx-blink-sdk/) project that is compatible with nRF or other boards.
8+
9+
## Zephyr Setup
10+
11+
Before setting up a Swift project that works with Zephyr, you need to setup dependencies and a Zephyr workspace as per the [Getting Started Guide](https://docs.zephyrproject.org/latest/develop/getting_started/index.html). Regardless of your platform (macOS or Linux), ensure that you can build the blinky example without errors before starting with Swift integration:
12+
13+
```bash
14+
cd ~/zephyrproject/zephyr
15+
west build -p always -b reel_board samples/basic/blinky
16+
```
17+
18+
By default, the `main` revision of the Zephyr sources are checked out when calling `west init`, which contains pre-release and development changes that may cause instability and changing APIs that are not desirable. To checkout a specific release version of Zephyr, use the following commands:
19+
20+
```bash
21+
cd ~/zephyrproject/zephyr
22+
git checkout v4.1.0
23+
west update
24+
west packages pip --install
25+
26+
# For older versions of Zephyr (pre 4.1.0), use:
27+
pip install -r ~/zephyrproject/zephyr/scripts/requirements.txt
28+
```
29+
30+
Refer to the [Zephyr Releases](https://docs.zephyrproject.org/latest/releases/index.html) page for more information on current and LTS releases.
31+
32+
## Project Setup
33+
34+
Once Zephyr is setup, the next step is to setup a project with the following files included:
35+
36+
```plain
37+
SwiftZephyrProject/src/BridgingHeader.h
38+
SwiftZephyrProject/src/Main.swift
39+
SwiftZephyrProject/src/Stubs.c
40+
SwiftZephyrProject/CMakeLists.txt
41+
SwiftZephyrProject/prj.conf
42+
```
43+
44+
These are the minimum required files in order to build a Zephyr project. By convention, source files should be placed in the src/ subdirectory, but this is not a hard requirement. Also, `prj.conf` is required even if it is empty, or the project will not build.
45+
46+
Inside of `src/BridgingHeader.h`, add the following content:
47+
48+
```c
49+
#pragma once
50+
51+
#include <autoconf.h>
52+
#include <zephyr/kernel.h>
53+
```
54+
55+
The `src/Main.swift` file must contain a `static func main()` as follows:
56+
57+
```swift
58+
@main
59+
struct Main {
60+
static func main() {
61+
print("Hello Zephyr from Swift!")
62+
63+
while true {
64+
k_msleep(1000)
65+
print("Loop")
66+
}
67+
}
68+
}
69+
```
70+
71+
Since Embedded Swift requires `posix_memalign` to be defined, add the following to `src/Stubs.c` to define it:
72+
73+
```c
74+
#include <stdlib.h>
75+
#include <errno.h>
76+
77+
void *aligned_alloc(size_t alignment, size_t size);
78+
79+
// Embedded Swift currently requires posix_memalign, but the C libraries in the
80+
// Zephyr SDK do not provide it. Let's implement it and forward the calls to
81+
// aligned_alloc(3).
82+
int
83+
posix_memalign(void **memptr, size_t alignment, size_t size)
84+
{
85+
void *p = aligned_alloc(alignment, size);
86+
if (p) {
87+
*memptr = p;
88+
return 0;
89+
}
90+
91+
return errno;
92+
}
93+
```
94+
95+
Finally, add the following line to `prj.conf` so that the output of `print()` statements is sent to stdout:
96+
97+
```conf
98+
CONFIG_STDOUT_CONSOLE=y
99+
```
100+
101+
### CMakeLists.txt Setup
102+
103+
The `CMakeLists.txt` setup is more involved and complex since target, compilation flags, and library linking must be specified for Swift. First, some initial setup and flags:
104+
105+
```cmake
106+
cmake_minimum_required(VERSION 3.29)
107+
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
108+
109+
# Enable "wmo" as needed by Embedded Swift
110+
set(CMAKE_Swift_COMPILATION_MODE wholemodule)
111+
112+
# Create a new project called "SwiftZephyrProject" and enable "Swift" as a supported language
113+
project(SwiftZephyrProject Swift)
114+
```
115+
116+
Next, set the compiler target to the arch you are building for. For this example we use `armv6m-none-none-eabi` which is compatible with the [Cortex-M0](https://docs.zephyrproject.org/latest/boards/qemu/cortex_m0/doc/index.html) which we will compile for in a later step. The `mfloat-abi=soft`, `-fshort-enums`, and `-fno-pic` flags are specifically for 32-bit arm architectures, so for other architectures they can be removed. However, the other flags and required for building Swift for Embedded and against Zephyr:
117+
118+
```cmake
119+
# Use the armv6m-none-none-eabi target triple for Swift
120+
set(CMAKE_Swift_COMPILER_TARGET armv6m-none-none-eabi)
121+
122+
# Set global Swift compiler flags
123+
add_compile_options(
124+
# Enable Embedded Swift
125+
"$<$<COMPILE_LANGUAGE:Swift>:SHELL:-enable-experimental-feature Embedded>"
126+
127+
# Enable function sections to enable dead code stripping on elf
128+
"$<$<COMPILE_LANGUAGE:Swift>:SHELL:-Xfrontend -function-sections>"
129+
130+
# Use software floating point operations matching GCC
131+
"$<$<COMPILE_LANGUAGE:Swift>:SHELL:-Xcc -mfloat-abi=soft>"
132+
133+
# Use compacted C enums matching GCC
134+
"$<$<COMPILE_LANGUAGE:Swift>:SHELL:-Xcc -fshort-enums>"
135+
136+
# Disable PIC
137+
"$<$<COMPILE_LANGUAGE:Swift>:SHELL:-Xcc -fno-pic>"
138+
139+
# Add Libc include paths
140+
"$<$<COMPILE_LANGUAGE:Swift>:SHELL:-Xcc -I -Xcc ${ZEPHYR_SDK_INSTALL_DIR}/arm-zephyr-eabi/picolibc/include>"
141+
)
142+
```
143+
144+
The following block will automatically grab Zephyr compilation flags (such as `-D__ZEPHYR__=1` and `-DKERNEL`) and set them as Swift compiler definitions. This is required to successfully build Swift code that works with Zephyr:
145+
146+
```cmake
147+
# Add definitions from Zephyr to -Xcc flags
148+
get_target_property(ZEPHYR_DEFINES zephyr_interface INTERFACE_COMPILE_DEFINITIONS)
149+
if(ZEPHYR_DEFINES)
150+
foreach(flag ${ZEPHYR_DEFINES})
151+
# Ignore expressions like "$<SOMETHING>"
152+
string(FIND "${flag}" "$<" start_of_expression)
153+
if(NOT start_of_expression EQUAL -1)
154+
continue()
155+
endif()
156+
157+
add_compile_options("$<$<COMPILE_LANGUAGE:Swift>:SHELL:-Xcc -D${flag}>")
158+
endforeach()
159+
endif()
160+
```
161+
162+
Finally, setup targets, libraries, and additional compile options:
163+
164+
```cmake
165+
target_sources(app PRIVATE src/Stubs.c)
166+
167+
# The Swift code providing "main" needs to be in an OBJECT library (instead of STATIC library) to make sure it actually gets linker.
168+
# A STATIC library would get dropped from linking because Zephyr provides a default weak empty main definition.
169+
add_library(app_swift OBJECT src/Main.swift)
170+
171+
add_dependencies(app_swift syscall_list_h_target)
172+
target_compile_options(app_swift PRIVATE
173+
-parse-as-library
174+
175+
-Osize
176+
177+
-Xfrontend -disable-stack-protector
178+
179+
# FIXME: add dependency on BridgingHeader.h
180+
-import-bridging-header ${CMAKE_CURRENT_LIST_DIR}/src/BridgingHeader.h
181+
)
182+
183+
# Copy include paths from C target to Swift target
184+
target_include_directories(app_swift PRIVATE
185+
"$<TARGET_PROPERTY:app,INCLUDE_DIRECTORIES>"
186+
)
187+
188+
# Link the Swift target into the primary target
189+
target_link_libraries(app PRIVATE app_swift)
190+
```
191+
192+
### Building and Running
193+
194+
To build the project, ensure that the Zephyr workspace is sourced first:
195+
196+
```bash
197+
source ~/zephyrproject/.venv/bin/activate
198+
```
199+
200+
Run the following command to configure the project with CMake:
201+
202+
```console
203+
(.venv)> cmake -B build -G Ninja -DBOARD=qemu_cortex_m0 -DUSE_CCACHE=0 .
204+
Loading Zephyr default modules (Zephyr base (cached)).
205+
...
206+
-- Configuring done (7.6s)
207+
-- Generating done (0.2s)
208+
-- Build files have been written to: ~/SwiftZephyrProject/build
209+
```
210+
211+
Then, the project can be built using `cmake --build`:
212+
213+
```console
214+
(.venv)> cmake --build build
215+
[1/135] Preparing syscall dependency handling
216+
217+
[2/135] Generating include/generated/zephyr/version.h
218+
-- Zephyr version: 4.1.0 (~/zephyrproject/zephyr), build: v4.1.0
219+
[130/135] Linking C executable zephyr/zephyr_pre0.elf
220+
~/zephyr-sdk-0.17.0/arm-zephyr-eabi/bin/../lib/gcc/arm-zephyr-eabi/12.2.0/../../../../arm-zephyr-eabi/bin/ld.bfd: warning: orphan section `.swift_modhash' from `app/libapp.a(Main.swift.obj)' being placed in section `.swift_modhash'
221+
[135/135] Linking C executable zephyr/zephyr.elf
222+
~/zephyr-sdk-0.17.0/arm-zephyr-eabi/bin/../lib/gcc/arm-zephyr-eabi/12.2.0/../../../../arm-zephyr-eabi/bin/ld.bfd: warning: orphan section `.swift_modhash' from `app/libapp.a(Main.swift.obj)' being placed in section `.swift_modhash'
223+
Memory region Used Size Region Size %age Used
224+
FLASH: 14674 B 256 KB 5.60%
225+
RAM: 4032 B 16 KB 24.61%
226+
IDT_LIST: 0 GB 32 KB 0.00%
227+
Generating files from ~/SwiftZephyrProject/build/zephyr/zephyr.elf for board: qemu_cortex_m0
228+
```
229+
230+
Finally, to run the example in the qemu emulator, use `ninja run`:
231+
232+
```console
233+
(.venv)> ninja -C build run
234+
ninja: Entering directory `build'
235+
[0/1] To exit from QEMU enter: 'CTRL+a, x'[QEMU] CPU: cortex-m0
236+
*** Booting Zephyr OS build v4.1.0 ***
237+
Hello Zephyr from Swift!
238+
Loop
239+
Loop
240+
Loop
241+
```
242+
243+
Congrats, you now have a Swift project running using Zephyr!
244+
245+
## West Integration
246+
247+
Up to now we have setup a project that works perfectly fine when used with just CMake and Ninja. However, projects can also be integrated with West, which is the official CLI tool used for Zephyr projects. To use `west`, start by adding a `west.yml` file to the root of the project:
248+
249+
```yml
250+
manifest:
251+
remotes:
252+
- name: zephyrproject-rtos
253+
url-base: https://github.com/zephyrproject-rtos
254+
255+
projects:
256+
- name: zephyr
257+
remote: zephyrproject-rtos
258+
revision: v4.1.0
259+
import:
260+
name-allowlist:
261+
- cmsis # required by the ARM port
262+
```
263+
264+
It is recommended to set the `revision` to a tagged version of Zephyr instead of always getting the main revision, which could have changing APIs.
265+
266+
Next, set the `ZEPHYR_BASE` environment variable to tell `west` where the Zephyr workspace is located:
267+
268+
```bash
269+
(.venv)> export ZEPHYR_BASE=~/zephyrproject/zephyr
270+
```
271+
272+
This could even be set as a global env variable for the user in `~/.bashrc` or `~/.zshrc` if desired.
273+
274+
With this, `west` commands now can be run instead of having to use `cmake` and `ninja` to build and run:
275+
276+
```bash
277+
(.venv)> west build -b qemu_cortex_m0 . -p always
278+
...
279+
(.venv)> west build -t run
280+
-- west build: running target run
281+
[0/1] To exit from QEMU enter: 'CTRL+a, x'[QEMU] CPU: cortex-m0
282+
*** Booting Zephyr OS build v4.1.0 ***
283+
Hello Zephyr from Swift!
284+
Loop
285+
Loop
286+
Loop
287+
Loop
288+
```
289+
290+
This setup may also desirable since `west flash` is also available and can be used instead of invoking the flashing tools manually.
291+
292+
If compiling a firmware for a real/physical board such as the `nrf52840dk/nrf52840`, `west flash` will work if the SEGGER J-Link host tools are installed. As an example, with the [nrfx-blink-sdk](../../../../nrfx-blink-sdk/) project:
293+
294+
```console
295+
> cd nrfx-blink-sdk
296+
> source ~/zephyrproject/.venv/bin/activate
297+
(.venv)> export ZEPHYR_BASE=~/zephyrproject/zephyr
298+
(.venv)> west build -b nrf52840dk/nrf52840 . -p always
299+
...
300+
(.venv)> west flash -r jlink
301+
-- west flash: rebuilding
302+
ninja: no work to do.
303+
-- west flash: using runner jlink
304+
-- runners.jlink: reset after flashing requested
305+
-- runners.jlink: JLink version: 8.26
306+
-- runners.jlink: Flashing file: ~/swift-embedded-examples/nrfx-blink-sdk/build/zephyr/zephyr.hex
307+
```
308+
309+
The `-r jlink` param is needed for this example to use the J-Link tools instead of using `nrfjprog`, which is the default for this board and also [deprecated](https://www.nordicsemi.com/Products/Development-tools/nRF-Command-Line-Tools).

0 commit comments

Comments
 (0)