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

Merged
merged 1 commit into from
May 28, 2025
Merged
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
23 changes: 20 additions & 3 deletions .github/workflows/build-stm.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,16 @@ jobs:
strategy:
fail-fast: false
matrix:
example: [stm32-blink]
example: [stm32-blink, stm32-lvgl]

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 @@ -32,8 +36,21 @@ jobs:
- name: Install Swift
uses: ./.github/actions/install-swift

- name: Set environment variables
run: |
echo "STM_BOARD=STM32F746G_DISCOVERY" >> $GITHUB_ENV

- name: Build ${{ matrix.example }}
working-directory: ${{ matrix.example }}
run: |
export STM_BOARD=STM32F746G_DISCOVERY
./build-elf.sh
if [[ -f ./fetch-dependencies.sh ]]; then
./fetch-dependencies.sh
fi

if [[ -f ./build-elf.sh ]]; then
./build-elf.sh
fi

if [[ -f Makefile ]]; then
make
fi
2 changes: 2 additions & 0 deletions .swiftformatignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
./harmony/*
./stm32-lvgl/*
./stm32-lcd-logo/Sources/STM32F7X6/*
./stm32-lvgl/Sources/Registers/*
./stm32-neopixel/Sources/STM32F7X6/*
./stm32-uart-echo/Sources/STM32F7X6/*
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ Each example in this repository contains build and deployment instructions, howe
| [rpi-picow-blink-sdk](./rpi-picow-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"> |
| [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-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="https://github.com/user-attachments/assets/3b4fefd3-1656-4768-9c64-6cbcb3ff9665"> |
| [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">|

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
11 changes: 11 additions & 0 deletions stm32-lvgl/.sourcekit-lsp/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"swiftPM": {
"configuration": "release",
"triple": "armv7em-none-none-eabi",
"toolsets": ["toolset.json"],
"swiftCompilerFlags": [
"-enable-experimental-feature", "Embedded",
"-enable-experimental-feature", "Extern"
]
}
}
94 changes: 94 additions & 0 deletions stm32-lvgl/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
##===----------------------------------------------------------------------===##
##
## This source file is part of the Swift open source project
##
## Copyright (c) 2025 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..."

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
42 changes: 42 additions & 0 deletions stm32-lvgl/Package.resolved

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

59 changes: 59 additions & 0 deletions stm32-lvgl/Package.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// swift-tools-version: 5.10

import PackageDescription

let package = Package(
name: "stm32-lvgl",
platforms: [
.macOS(.v11)
],
products: [
.executable(name: "Application", targets: ["Application"])
],
dependencies: [
.package(url: "https://github.com/apple/swift-mmio", branch: "main"),
.package(url: "https://github.com/ctreffs/SwiftSDL2.git", from: "1.4.0"),
],
targets: [
//
// FIRMWARE TARGETS
//

.executableTarget(
name: "Application",
dependencies: [
"Registers",
"Support",
"CLVGL",
]),

// SVD2Swift \
// --input Tools/SVDs/stm32f7x6.patched.svd \
// --output stm32-lvgl/Sources/STM32F7x6 \
// --peripherals FLASH LTDC RCC PWR FMC SCB DBGMCU USART1 STK NVIC SYSCFG \
// GPIOA GPIOB GPIOC GPIOD GPIOE GPIOF GPIOG GPIOH GPIOI GPIOJ GPIOK \
// I2C1 I2C2 I2C3 I2C4 \
// --access-level public
.target(
name: "Registers",
dependencies: [
.product(name: "MMIO", package: "swift-mmio")
]),

.target(name: "Support"),

.target(name: "CLVGL"),

//
// HOST TARGETS
//

.executableTarget(
name: "HostSDLApp",
dependencies: [
.product(name: "SDL", package: "SwiftSDL2"),
"CLVGL",
],
swiftSettings: [.enableExperimentalFeature("Extern")],
linkerSettings: [.unsafeFlags(["-L.build/lvgl-host/lib", "-llvgl", "-llvgl_demos"])]),
])
Loading
Loading