Skip to content

Add a demo for using LVGL, DRAM, LLVM Toolchain, ELF on an STM32F746G discovery board #104

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 13 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 15 additions & 4 deletions .github/workflows/build-stm.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,17 @@ jobs:
strategy:
fail-fast: false
matrix:
example: [stm32-blink]
swift: [swift-DEVELOPMENT-SNAPSHOT-2025-03-04-a]
example: [stm32-blink, stm32-lvgl]
swift: [swift-DEVELOPMENT-SNAPSHOT-2025-04-12-a]

steps:
- name: Checkout repo
uses: actions/checkout@v4

- name: Fixup for running locally in act
if: ${{ env.ACT }}
run: echo /opt/acttoolcache/node/18.20.8/x64/bin >> $GITHUB_PATH

- name: Set up Python
uses: actions/setup-python@v5
with:
Expand All @@ -42,5 +46,12 @@ jobs:
- name: Build ${{ matrix.example }}
working-directory: ${{ matrix.example }}
run: |
export STM_BOARD=STM32F746G_DISCOVERY
./build-elf.sh
if [[ "${{ matrix.example }}" == "stm32-blink" ]]; then
export STM_BOARD=STM32F746G_DISCOVERY
./build-elf.sh
elif [[ "${{ matrix.example }}" == "stm32-lvgl" ]]; then
./fetch-dependencies.sh
make
else
exit 1
fi
1 change: 1 addition & 0 deletions .swiftformatignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
./harmony/*
./stm32-lcd-logo/Sources/Application/Registers/*
./stm32-lvgl/Sources/Registers/*
./stm32-neopixel/Sources/Application/Registers/*
./stm32-uart-echo/Sources/Application/Registers/*
19 changes: 10 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,18 +30,19 @@ Each example in this repository contains build and deployment instructions, howe
| [esp32-led-blink-sdk](./esp32-led-blink-sdk) | ESP32-C6-Bug | ESP-IDF SDK | Blink an LED repeatedly with Swift & the ESP-IDF. | <img width="300" src="esp32-led-blink-sdk/assets/images/ledon.jpg"> |
| [esp32-led-strip-sdk](./esp32-led-strip-sdk) | ESP32-C6-DevKitC-1 | ESP-IDF SDK | Control NeoPixel LEDs with Swift & the ESP-IDF. | <img width="300" src="https://github.com/apple/swift-embedded-examples/assets/1186214/15f8a3e0-953e-426d-ad2d-3902baf859be"> |
| [nrfx-blink-sdk](./nrfx-blink-sdk) | nRF52840-DK | Zephyr SDK | Blink an LED repeatedly with Swift & Zephyr. | <img width="300" src="https://github.com/apple/swift-embedded-examples/assets/1186214/ae3ff153-dd33-4460-8a08-4eac442bf7b0"> |
| [nuttx-riscv-blink](./nuttx-riscv-blink) | QEMU | NuttX | Blink a virualized led in QEMU using the Apache NuttX RTOS | |
| [nuttx-riscv-blink](./nuttx-riscv-blink) | QEMU | NuttX | Blink a virtualized led in QEMU using the Apache NuttX RTOS | |
| [pico-blink-sdk](./pico-blink-sdk) | Raspberry Pi Pico, Pico 2 | Pico SDK | Blink an LED repeatedly with Swift & the Pico SDK. | <img width="300" src="https://github.com/apple/swift-embedded-examples/assets/1186214/f2c45c18-f9a4-48b4-a941-1298ecc942cb"> |
| [pico-blink](./pico-blink) | Raspberry Pi Pico | None | Blink an LED repeatedly. | <img width="300" src="https://github.com/apple/swift-embedded-examples/assets/1186214/f2c45c18-f9a4-48b4-a941-1298ecc942cb"> |
| [pico-blink](./pico-blink) | Raspberry Pi Pico | | Blink an LED repeatedly. | <img width="300" src="https://github.com/apple/swift-embedded-examples/assets/1186214/f2c45c18-f9a4-48b4-a941-1298ecc942cb"> |
| [pico-w-blink-sdk](./pico-w-blink-sdk) | Raspberry Pi Pico W | Pico SDK | Blink an LED to signal 'SOS' in Morse code repeatedly with Swift & the Pico SDK. | <img width="300" src="https://github.com/apple/swift-embedded-examples/assets/26223064/a4949a2e-1887-4325-8f5f-a681963c93d7"> |
| [harmony](./harmony) | Raspberry Pi Pico W | Pico SDK | A bluetooth speaker and ferrofluidic music visualizer. Firmware, Electrical, and Mechanical designs fully available. | <img width="300" src="harmony/assets/harmony.jpeg"> |
| [pico2-neopixel](./pico2-neopixel) | Raspberry Pi Pico 2 | None | Control Neopixel LEDs using the RP2350 PIO. | <img width="300" src="pico2-neopixel/assets/images/example.jpg"> |
| [rpi4b-blink](./rpi4b-blink) | Raspberry Pi 4B | None | Blink the Pi's status green LED repeatedly using Swift MMIO. | <img width="300" src="rpi4b-blink/assets/rpi4.png"> |
| [rpi5-blink](./rpi5-blink) | Raspberry Pi 5 | None | Blink the Pi's status green LED repeatedly with Swift MMIO. | <img width="300" src="rpi5-blink/assets/raspi5.png"> |
| [stm32-blink](./stm32-blink) | STM32F746G-DISCO | None | Blink an LED repeatedly. | <img width="300" src="https://github.com/apple/swift-embedded-examples/assets/1186214/739e98fd-a438-4a64-a7aa-9dddee25034b"> |
| [stm32-lcd-logo](./stm32-lcd-logo) | STM32F746G-DISCO | None | Animate the Swift Logo on the built-in LCD. | <img width="300" src="https://github.com/apple/swift-embedded-examples/assets/1186214/9e117d81-e808-493e-a20c-7284ea630f37"> |
| [stm32-neopixel](./stm32-neopixel) | STM32F746G-DISCO | None | Control NeoPixel LEDs using SPI. | <img width="300" src="https://github.com/apple/swift-embedded-examples/assets/1186214/9c5d8f74-f8aa-4632-831e-212a3e35e75a"> |
| [stm32-uart-echo](./stm32-uart-echo) | STM32F746G-DISCO | None | Echo user input using UART. | <img width="300" src="https://github.com/apple/swift-embedded-examples/assets/1186214/97d3c465-9a07-4b86-9654-0c2aaaa43b3d">|
| [pico2-neopixel](./pico2-neopixel) | Raspberry Pi Pico 2 | – | Control Neopixel LEDs using the RP2350 PIO. | <img width="300" src="pico2-neopixel/assets/images/example.jpg"> |
| [rpi4b-blink](./rpi4b-blink) | Raspberry Pi 4B | – | Blink the Pi's status green LED repeatedly using Swift MMIO. | <img width="300" src="rpi4b-blink/assets/rpi4.png"> |
| [rpi5-blink](./rpi5-blink) | Raspberry Pi 5 | – | Blink the Pi's status green LED repeatedly with Swift MMIO. | <img width="300" src="rpi5-blink/assets/raspi5.png"> |
| [stm32-blink](./stm32-blink) | STM32F746G-DISCO | – | Blink an LED repeatedly. | <img width="300" src="https://github.com/apple/swift-embedded-examples/assets/1186214/739e98fd-a438-4a64-a7aa-9dddee25034b"> |
| [stm32-lcd-logo](./stm32-lcd-logo) | STM32F746G-DISCO | – | Animate the Swift Logo on the built-in LCD. | <img width="300" src="https://github.com/apple/swift-embedded-examples/assets/1186214/9e117d81-e808-493e-a20c-7284ea630f37"> |
| [stm32-lvgl](./stm32-lvgl) | STM32F746G-DISCO | – | Baremetal setup of LCD, touch panel, DRAM, using the LLVM Embedded toolchain for ARM. Renders graphics, animations, and reacts to user input via LVGL. Includes a macOS/Linux SDL based host simulation app. | <img width="300" src="stm32-lvgl/assets/thumbnail.gif"> |
| [stm32-neopixel](./stm32-neopixel) | STM32F746G-DISCO | – | Control NeoPixel LEDs using SPI. | <img width="300" src="https://github.com/apple/swift-embedded-examples/assets/1186214/9c5d8f74-f8aa-4632-831e-212a3e35e75a"> |
| [stm32-uart-echo](./stm32-uart-echo) | STM32F746G-DISCO | – | Echo user input using UART. | <img width="300" src="https://github.com/apple/swift-embedded-examples/assets/1186214/97d3c465-9a07-4b86-9654-0c2aaaa43b3d">|

Note that the SDK integration examples (Pico SDK, Zephyr SDK, etc.) are not recommendations or endorsement, the same is true for build system choice (Make, CMake, SwiftPM, shell scripts). Embedded Swift aims to be versatile and allowing integration into existing SDKs and build systems, and the example projects show some of the possibilities.

Expand Down
63 changes: 49 additions & 14 deletions Tools/elf2hex.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,23 @@
# file format suitable for flashing onto some embedded devices.
#
# Usage:
# $ elf2hex.py <input> <output> [--symbol-map <output>]
# $ elf2hex.py <input> <output> [--symbol-map <output>] [--relocate-data-segment]
#
# Example:
# $ elf2hex.py ./blink ./blink.hex --symbol-map ./blink.symbols
#
# The --relocate-data-segment option expects to be able to locate symbols with names
# - __data_start
# - __flash_data_start
# - __flash_data_len
# and then it physically relocates a segment located at __data_start to
# __flash_data_start, without changing virtual/physical addresses of any ELF
# headers. This means that the .hex file is not validly mapped until a boot-time
# reverse relocation step.
#
# See the linker script used in a particular demo folder for a detailed
# explanation of the linking, packing, and runtime relocation scheme.
#

import argparse
import json
Expand All @@ -36,6 +48,7 @@ def main():
parser.add_argument('input')
parser.add_argument('output')
parser.add_argument('--symbol-map')
parser.add_argument('--relocate-data-segment', action='store_true')
args = parser.parse_args()

inf = open(args.input, "rb")
Expand Down Expand Up @@ -70,32 +83,54 @@ def emit(vmaddr, data):
vmaddr += chunklen

elffile = elftools.elf.elffile.ELFFile(inf)
for segment in elffile.iter_segments():
if segment.header.p_type != "PT_LOAD":
continue
vmaddr = segment.header.p_paddr
data = segment.data()
emit(segment.header.p_paddr, data)

chunklen = 0
vmaddr = 0
recordtype = "01" # EOF
emitrecord(f"{chunklen:02X}{vmaddr:04X}{recordtype}")

symbol_map = {}
symtab_section = elffile.get_section_by_name(".symtab")
for s in symtab_section.iter_symbols():
if s.entry.st_info.type not in ["STT_FUNC", "STT_NOTYPE"]:
continue
if s.entry.st_shndx == "SHN_ABS":
continue
if s.name == "":
continue
symbol_map[s.name] = s.entry.st_value

if args.symbol_map is not None:
pathlib.Path(args.symbol_map).write_text(json.dumps(symbol_map))

relocations = {}
if args.relocate_data_segment:
__flash_data_start = symbol_map["__flash_data_start"]
__data_start = symbol_map["__data_start"]
__flash_data_len = symbol_map["__flash_data_len"]
print("Relocation info:")
print(f" __flash_data_start = 0x{__flash_data_start:08x}")
print(f" __data_start = 0x{__data_start:08x}")
print(f" __flash_data_len = 0x{__flash_data_len:08x}")
relocations = {__data_start: __flash_data_start}

for segment in elffile.iter_segments():
if segment.header.p_type != "PT_LOAD":
continue
vmaddr = segment.header.p_paddr
data = segment.data()
flags = ""
flags += "r" if segment.header.p_flags & 0x4 else "-"
flags += "w" if segment.header.p_flags & 0x2 else "-"
flags += "x" if segment.header.p_flags & 0x1 else "-"
print(f"PT_LOAD {flags} at 0x{segment.header.p_paddr:08x} - "
f"0x{segment.header.p_paddr + len(data):08x}, "
f"size {len(data)} "
f"(0x{len(data):04x})")
placement_addr = segment.header.p_paddr
if segment.header.p_paddr in relocations:
placement_addr = relocations[segment.header.p_paddr]
print(f" ... relocating to 0x{placement_addr:08x}")
emit(placement_addr, data)

chunklen = 0
vmaddr = 0
recordtype = "01" # EOF
emitrecord(f"{chunklen:02X}{vmaddr:04X}{recordtype}")

inf.close()
outf.close()

Expand Down
2 changes: 2 additions & 0 deletions stm32-lvgl/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
lvgl
llvm-toolchain
14 changes: 14 additions & 0 deletions stm32-lvgl/.sourcekit-lsp/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"swiftPM": {
"configuration": "release",
"triple": "armv7em-none-none-eabi",

"__comment": "XXX SourceKit-LSP seems to ignore the toolset (relative or absolute path)...",
"toolset": ".../toolset.json",

"swiftCompilerFlags": [
"-enable-experimental-feature", "Embedded",
"-enable-experimental-feature", "Extern",
]
}
}
71 changes: 71 additions & 0 deletions stm32-lvgl/.swift-format
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
{
"fileScopedDeclarationPrivacy" : {
"accessLevel" : "private"
},
"indentation" : {
"spaces" : 2
},
"indentConditionalCompilationBlocks" : false,
"indentSwitchCaseLabels" : false,
"lineBreakAroundMultilineExpressionChainComponents" : false,
"lineBreakBeforeControlFlowKeywords" : false,
"lineBreakBeforeEachArgument" : false,
"lineBreakBeforeEachGenericRequirement" : false,
"lineLength" : 120,
"maximumBlankLines" : 1,
"multiElementCollectionTrailingCommas" : true,
"noAssignmentInExpressions" : {
"allowedFunctions" : [
"XCTAssertNoThrow"
]
},
"prioritizeKeepingFunctionOutputTogether" : false,
"respectsExistingLineBreaks" : true,
"rules" : {
"AllPublicDeclarationsHaveDocumentation" : false,
"AlwaysUseLiteralForEmptyCollectionInit" : true,
"AlwaysUseLowerCamelCase" : false,
"AmbiguousTrailingClosureOverload" : false,
"BeginDocumentationCommentWithOneLineSummary" : false,
"DoNotUseSemicolons" : true,
"DontRepeatTypeInStaticProperties" : true,
"FileScopedDeclarationPrivacy" : true,
"FullyIndirectEnum" : true,
"GroupNumericLiterals" : true,
"IdentifiersMustBeASCII" : true,
"NeverForceUnwrap" : false,
"NeverUseForceTry" : true,
"NeverUseImplicitlyUnwrappedOptionals" : false,
"NoAccessLevelOnExtensionDeclaration" : true,
"NoAssignmentInExpressions" : true,
"NoBlockComments" : false,
"NoCasesWithOnlyFallthrough" : true,
"NoEmptyTrailingClosureParentheses" : true,
"NoLabelsInCasePatterns" : true,
"NoLeadingUnderscores" : false,
"NoParensAroundConditions" : true,
"NoPlaygroundLiterals" : true,
"NoVoidReturnOnFunctionSignature" : true,
"OmitExplicitReturns" : false,
"OneCasePerLine" : true,
"OneVariableDeclarationPerLine" : true,
"OnlyOneTrailingClosureArgument" : true,
"OrderedImports" : true,
"ReplaceForEachWithForLoop" : true,
"ReturnVoidInsteadOfEmptyTuple" : true,
"TypeNamesShouldBeCapitalized" : true,
"UseEarlyExits" : false,
"UseExplicitNilCheckInConditions" : true,
"UseLetInEveryBoundCaseVariable" : true,
"UseShorthandTypeNames" : true,
"UseSingleLinePropertyGetter" : true,
"UseSynthesizedInitializer" : true,
"UseTripleSlashForDocumentationComments" : true,
"UseWhereClausesInForLoops" : false,
"ValidateDocumentationComments" : true
},
"spacesBeforeEndOfLineComments": 2,
"spacesAroundRangeFormationOperators" : false,
"tabWidth" : 2,
"version" : 1
}
1 change: 1 addition & 0 deletions stm32-lvgl/.swift-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
main-snapshot-2025-03-28
96 changes: 96 additions & 0 deletions stm32-lvgl/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
##===----------------------------------------------------------------------===##
##
## This source file is part of the Swift open source project
##
## Copyright (c) 2023 Apple Inc. and the Swift project authors.
## Licensed under Apache License v2.0 with Runtime Library Exception
##
## See https://swift.org/LICENSE.txt for license information
##
##===----------------------------------------------------------------------===##

# Paths
REPOROOT := $(shell git rev-parse --show-toplevel)
TOOLSROOT := $(REPOROOT)/Tools
TOOLSET := $(PWD)/toolset.json
ELF2HEX := $(TOOLSROOT)/elf2hex.py
SWIFT_BUILD := swift build
NM := nm
LLVM_TOOLCHAIN := $(PWD)/llvm-toolchain

# Flags
ARCH := armv7em
TARGET := $(ARCH)-none-none-eabi
SWIFT_BUILD_ARGS := \
--configuration release \
--triple $(TARGET) \
--toolset $(TOOLSET) \
--product Application
BUILDROOT := $(shell $(SWIFT_BUILD) $(SWIFT_BUILD_ARGS) --show-bin-path)

.PHONY: build
build:
@echo "checking dependencies..."

# TODO: Check that we have swiftly and recent Swift main toolchain

if [[ ! -d $(PWD)/lvgl ]]; then echo "\n *** LVGL checkout not found, please run ./fetch-dependencies.sh\n" ; exit 1 ; fi
if [[ ! -d $(PWD)/llvm-toolchain ]]; then echo "\n *** LLVM toolchain checkout not found, please run ./fetch-dependencies.sh\n" ; exit 1 ; fi

mkdir -p .build

@echo "configuring LVGL..."
cmake -B .build/lvgl -G Ninja ./lvgl \
-DCMAKE_EXPORT_COMPILE_COMMANDS=On \
-DTOOLCHAIN_PATH=$(LLVM_TOOLCHAIN) \
-DCMAKE_TOOLCHAIN_FILE=../clang-arm-toolchain.cmake \
-DLV_CONF_PATH=../Sources/CLVGL/include/lv_conf.h

@echo "building LVGL..."
cmake --build .build/lvgl

@echo "building..."
$(SWIFT_BUILD) \
$(SWIFT_BUILD_ARGS) \
--verbose

@echo "disassembling..."
$(LLVM_TOOLCHAIN)/bin/llvm-objdump --all-headers --disassemble --mcpu=cortex-m7 \
$(BUILDROOT)/Application \
| c++filt | swift demangle > $(BUILDROOT)/Application.disassembly

@echo "extracting binary..."
$(ELF2HEX) \
$(BUILDROOT)/Application $(BUILDROOT)/Application.hex --relocate
ls -al $(BUILDROOT)/Application.hex
@echo "\n *** All done, build succeeded!\n"

flash:
@echo "flashing..."
st-flash --reset --format ihex write $(BUILDROOT)/Application.hex

simulator:
mkdir -p .build

@echo "configuring LVGL..."
cmake -B .build/lvgl-host -G Ninja ./lvgl \
-DCMAKE_EXPORT_COMPILE_COMMANDS=On \
-DLV_CONF_PATH=../Sources/CLVGL/include/lv_conf.h

@echo "building LVGL..."
cmake --build .build/lvgl-host

@echo "building..."
$(SWIFT_BUILD) \
--configuration release \
--product HostSDLApp \
--verbose

@echo "running..."
$(PWD)/.build/release/HostSDLApp

.PHONY: clean
clean:
@echo "cleaning..."
@swift package clean
@rm -rf .build
Loading
Loading