Skip to content

Commit 4c4ebad

Browse files
Add IntegrateWithZephyr document next to IntegrateWithPico (#140)
- Add IntegrateWithZephyr guide showing off project structure and commands - Add Zephyr section to IntegratingWithPlatforms page - Add Zephyr target compatibility section, improve CMakeLists.txt setup
1 parent 1ce88ec commit 4c4ebad

File tree

3 files changed

+342
-0
lines changed

3 files changed

+342
-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: 337 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,337 @@
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 Target Architecture Compatibility
10+
11+
Zephyr [supports quite a few target architectures](https://docs.zephyrproject.org/latest/introduction/index.html), but not all are supported by Embedded Swift. Please refer to the following table for an overview of Zephyr-supported architectures that are supported by Swift, along with the correct target triple to use:
12+
13+
| Architecture | Details | Swift Triple |
14+
|--------------|---------------------|-------------------------|
15+
| ARMv6-M | Cortex M0, M1, M3 | armv6m-none-none-eabi |
16+
| ARMv7-M | Cortex M4, M7 | armv7em-none-none-eabi |
17+
| ARMv8-M | Cortex M23-85 | aarch64-none-none-elf |
18+
| Intel | 32-bit (i686) | i686-unknown-none-elf |
19+
| Intel | 64-bit (x86_64) | x86_64-unknown-none-elf |
20+
| RISC-V | 32-bit | riscv32-none-none-eabi |
21+
| RISC-V | 64-bit | riscv64-none-none-eabi |
22+
23+
## Zephyr Setup
24+
25+
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:
26+
27+
```bash
28+
cd ~/zephyrproject/zephyr
29+
west build -p always -b reel_board samples/basic/blinky
30+
```
31+
32+
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:
33+
34+
```bash
35+
cd ~/zephyrproject/zephyr
36+
git checkout v4.1.0
37+
west update
38+
west packages pip --install
39+
40+
# For older versions of Zephyr (pre 4.1.0), use:
41+
pip install -r ~/zephyrproject/zephyr/scripts/requirements.txt
42+
```
43+
44+
Refer to the [Zephyr Releases](https://docs.zephyrproject.org/latest/releases/index.html) page for more information on current and LTS releases.
45+
46+
## Project Setup
47+
48+
Once Zephyr is setup, the next step is to setup a project with the following files included:
49+
50+
```plain
51+
SwiftZephyrProject/src/BridgingHeader.h
52+
SwiftZephyrProject/src/Main.swift
53+
SwiftZephyrProject/src/Stubs.c
54+
SwiftZephyrProject/CMakeLists.txt
55+
SwiftZephyrProject/prj.conf
56+
```
57+
58+
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.
59+
60+
Inside of `src/BridgingHeader.h`, add the following content:
61+
62+
```c
63+
#pragma once
64+
65+
#include <autoconf.h>
66+
#include <zephyr/kernel.h>
67+
```
68+
69+
The `src/Main.swift` file must contain a `static func main()` as follows:
70+
71+
```swift
72+
@main
73+
struct Main {
74+
static func main() {
75+
print("Hello Zephyr from Swift!")
76+
77+
while true {
78+
k_msleep(1000)
79+
print("Loop")
80+
}
81+
}
82+
}
83+
```
84+
85+
Since Embedded Swift requires `posix_memalign` to be defined, add the following to `src/Stubs.c` to define it:
86+
87+
```c
88+
#include <stdlib.h>
89+
#include <errno.h>
90+
91+
void *aligned_alloc(size_t alignment, size_t size);
92+
93+
// Embedded Swift currently requires posix_memalign, but the C libraries in the
94+
// Zephyr SDK do not provide it. Let's implement it and forward the calls to
95+
// aligned_alloc(3).
96+
int
97+
posix_memalign(void **memptr, size_t alignment, size_t size)
98+
{
99+
void *p = aligned_alloc(alignment, size);
100+
if (p) {
101+
*memptr = p;
102+
return 0;
103+
}
104+
105+
return errno;
106+
}
107+
```
108+
109+
Finally, add the following line to `prj.conf` so that the output of `print()` statements is sent to stdout:
110+
111+
```conf
112+
CONFIG_STDOUT_CONSOLE=y
113+
```
114+
115+
### CMakeLists.txt Setup
116+
117+
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:
118+
119+
```cmake
120+
cmake_minimum_required(VERSION 3.29)
121+
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
122+
123+
# Enable "wmo" as needed by Embedded Swift
124+
set(CMAKE_Swift_COMPILATION_MODE wholemodule)
125+
126+
# Create a new project called "SwiftZephyrProject" and enable "Swift" as a supported language
127+
project(SwiftZephyrProject Swift)
128+
```
129+
130+
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. If you are targeting a different architecture, see the [Zephyr Target Architecture Compatibility](#zephyr-target-architecture-compatibility) to see which target triple to use.
131+
132+
```cmake
133+
# Use the armv6m-none-none-eabi target triple for Swift
134+
set(CMAKE_Swift_COMPILER_TARGET armv6m-none-none-eabi)
135+
```
136+
137+
After setting the target triple, some additional additional Swift compiler flags need to be defined:
138+
139+
```cmake
140+
# Set global Swift compiler flags
141+
add_compile_options(
142+
# Enable Embedded Swift
143+
"$<$<COMPILE_LANGUAGE:Swift>:SHELL:-enable-experimental-feature Embedded>"
144+
145+
# Enable function sections to enable dead code stripping on elf
146+
"$<$<COMPILE_LANGUAGE:Swift>:SHELL:-Xfrontend -function-sections>"
147+
148+
# Disable PIC
149+
"$<$<COMPILE_LANGUAGE:Swift>:SHELL:-Xcc -fno-pic>"
150+
151+
# Disable PIE
152+
"$<$<COMPILE_LANGUAGE:Swift>:SHELL:-Xcc -fno-pie>"
153+
)
154+
```
155+
156+
There are quite a few other Zephyr flags that must also be imported in order to get Zephyr include paths and flags such `-mcpu`, `-mfloat-abi`, and so on:
157+
158+
```cmake
159+
# Import TOOLCHAIN_C_FLAGS from Zephyr as -Xcc flags
160+
foreach(flag ${TOOLCHAIN_C_FLAGS})
161+
# Skip flags that are not known to swiftc
162+
string(FIND "${flag}" "-imacro" is_imacro)
163+
string(FIND "${flag}" "-mfp16-format" is_mfp16)
164+
if(NOT is_imacro EQUAL -1 OR NOT is_mfp16 EQUAL -1)
165+
continue()
166+
endif()
167+
168+
add_compile_options("$<$<COMPILE_LANGUAGE:Swift>:SHELL:-Xcc ${flag}>")
169+
endforeach()
170+
```
171+
172+
Next, add the following block to 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:
173+
174+
```cmake
175+
# Add definitions from Zephyr to -Xcc flags
176+
get_target_property(ZEPHYR_DEFINES zephyr_interface INTERFACE_COMPILE_DEFINITIONS)
177+
if(ZEPHYR_DEFINES)
178+
foreach(flag ${ZEPHYR_DEFINES})
179+
# Ignore expressions like "$<SOMETHING>"
180+
string(FIND "${flag}" "$<" start_of_expression)
181+
if(NOT start_of_expression EQUAL -1)
182+
continue()
183+
endif()
184+
185+
add_compile_options("$<$<COMPILE_LANGUAGE:Swift>:SHELL:-Xcc -D${flag}>")
186+
endforeach()
187+
endif()
188+
```
189+
190+
Finally, setup targets, libraries, and additional compile options:
191+
192+
```cmake
193+
target_sources(app PRIVATE src/Stubs.c)
194+
195+
# The Swift code providing "main" needs to be in an OBJECT library (instead of STATIC library) to make sure it actually gets linker.
196+
# A STATIC library would get dropped from linking because Zephyr provides a default weak empty main definition.
197+
add_library(app_swift OBJECT src/Main.swift)
198+
199+
add_dependencies(app_swift syscall_list_h_target)
200+
target_compile_options(app_swift PRIVATE
201+
-parse-as-library
202+
203+
-Osize
204+
205+
-Xfrontend -disable-stack-protector
206+
207+
# FIXME: add dependency on BridgingHeader.h
208+
-import-bridging-header ${CMAKE_CURRENT_LIST_DIR}/src/BridgingHeader.h
209+
)
210+
211+
# Copy include paths from C target to Swift target
212+
target_include_directories(app_swift PRIVATE
213+
"$<TARGET_PROPERTY:app,INCLUDE_DIRECTORIES>"
214+
)
215+
216+
# Link the Swift target into the primary target
217+
target_link_libraries(app PRIVATE app_swift)
218+
```
219+
220+
### Building and Running
221+
222+
To build the project, ensure that the Zephyr workspace is sourced first:
223+
224+
```bash
225+
source ~/zephyrproject/.venv/bin/activate
226+
```
227+
228+
Run the following command to configure the project with CMake:
229+
230+
```console
231+
(.venv)> cmake -B build -G Ninja -DBOARD=qemu_cortex_m0 -DUSE_CCACHE=0 .
232+
Loading Zephyr default modules (Zephyr base (cached)).
233+
...
234+
-- Configuring done (7.6s)
235+
-- Generating done (0.2s)
236+
-- Build files have been written to: ~/SwiftZephyrProject/build
237+
```
238+
239+
Then, the project can be built using `cmake --build`:
240+
241+
```console
242+
(.venv)> cmake --build build
243+
[1/135] Preparing syscall dependency handling
244+
245+
[2/135] Generating include/generated/zephyr/version.h
246+
-- Zephyr version: 4.1.0 (~/zephyrproject/zephyr), build: v4.1.0
247+
[130/135] Linking C executable zephyr/zephyr_pre0.elf
248+
~/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'
249+
[135/135] Linking C executable zephyr/zephyr.elf
250+
~/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'
251+
Memory region Used Size Region Size %age Used
252+
FLASH: 14674 B 256 KB 5.60%
253+
RAM: 4032 B 16 KB 24.61%
254+
IDT_LIST: 0 GB 32 KB 0.00%
255+
Generating files from ~/SwiftZephyrProject/build/zephyr/zephyr.elf for board: qemu_cortex_m0
256+
```
257+
258+
Finally, to run the example in the qemu emulator, use `ninja run`:
259+
260+
```console
261+
(.venv)> ninja -C build run
262+
ninja: Entering directory `build'
263+
[0/1] To exit from QEMU enter: 'CTRL+a, x'[QEMU] CPU: cortex-m0
264+
*** Booting Zephyr OS build v4.1.0 ***
265+
Hello Zephyr from Swift!
266+
Loop
267+
Loop
268+
Loop
269+
```
270+
271+
Congrats, you now have a Swift project running using Zephyr!
272+
273+
## West Integration
274+
275+
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:
276+
277+
```yml
278+
manifest:
279+
remotes:
280+
- name: zephyrproject-rtos
281+
url-base: https://github.com/zephyrproject-rtos
282+
283+
projects:
284+
- name: zephyr
285+
remote: zephyrproject-rtos
286+
revision: v4.1.0
287+
import:
288+
name-allowlist:
289+
- cmsis # required by the ARM port
290+
```
291+
292+
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.
293+
294+
Next, set the `ZEPHYR_BASE` environment variable to tell `west` where the Zephyr workspace is located:
295+
296+
```bash
297+
(.venv)> export ZEPHYR_BASE=~/zephyrproject/zephyr
298+
```
299+
300+
This could even be set as a global env variable for the user in `~/.bashrc` or `~/.zshrc` if desired.
301+
302+
With this, `west` commands now can be run instead of having to use `cmake` and `ninja` to build and run:
303+
304+
```bash
305+
(.venv)> west build -b qemu_cortex_m0 . -p always
306+
...
307+
(.venv)> west build -t run
308+
-- west build: running target run
309+
[0/1] To exit from QEMU enter: 'CTRL+a, x'[QEMU] CPU: cortex-m0
310+
*** Booting Zephyr OS build v4.1.0 ***
311+
Hello Zephyr from Swift!
312+
Loop
313+
Loop
314+
Loop
315+
Loop
316+
```
317+
318+
This setup may also be desirable since `west flash` is also available and can be used instead of invoking the flashing tools manually.
319+
320+
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:
321+
322+
```console
323+
> cd nrfx-blink-sdk
324+
> source ~/zephyrproject/.venv/bin/activate
325+
(.venv)> export ZEPHYR_BASE=~/zephyrproject/zephyr
326+
(.venv)> west build -b nrf52840dk/nrf52840 . -p always
327+
...
328+
(.venv)> west flash -r jlink
329+
-- west flash: rebuilding
330+
ninja: no work to do.
331+
-- west flash: using runner jlink
332+
-- runners.jlink: reset after flashing requested
333+
-- runners.jlink: JLink version: 8.26
334+
-- runners.jlink: Flashing file: ~/swift-embedded-examples/nrfx-blink-sdk/build/zephyr/zephyr.hex
335+
```
336+
337+
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).

Sources/EmbeddedSwift/Documentation.docc/SDKSupport/IntegratingWithPlatforms.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,10 @@ STM32 microcontrollers can be programmed with Embedded Swift in two ways:
7575
1. Using the STM32Cube HAL/LL libraries - This requires proper integration with the STM32Cube build system
7676
2. Bare-metal approach - See <doc:STM32BaremetalGuide> for details
7777

78+
### Zephyr
79+
80+
For detailed documentation on how to integrate Embedded Swift with Zephyr, see <doc:IntegrateWithZephyr>.
81+
7882
### Bare-metal Development
7983

8084
For completely bare-metal development without any SDK, see <doc:Baremetal> for guidance on implementing the necessary startup code and hardware initialization.

0 commit comments

Comments
 (0)