Skip to content

Commit 58ee0d5

Browse files
authored
Make stm32-blink sample building as both Mach-O and ELF (#87)
* Make stm32-blink sample building as both Mach-O and ELF * Use STM_BOARD env var to select which board to target on stm32-blink example * Fix python lint issues in Tools/elf2hex.py
1 parent 01cb60f commit 58ee0d5

File tree

13 files changed

+543
-144
lines changed

13 files changed

+543
-144
lines changed

.github/workflows/build-stm.yml

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
name: Build STM32 Examples
2+
3+
on:
4+
push:
5+
branches: ["main"]
6+
pull_request:
7+
branches: ["main"]
8+
schedule:
9+
# Build on Mondays at 9am PST every week
10+
- cron: '0 17 * * 1'
11+
12+
jobs:
13+
build-stm32:
14+
runs-on: ubuntu-24.04
15+
16+
strategy:
17+
fail-fast: false
18+
matrix:
19+
example: [stm32-blink]
20+
swift: [swift-DEVELOPMENT-SNAPSHOT-2024-12-04-a]
21+
22+
steps:
23+
- name: Checkout repo
24+
uses: actions/checkout@v4
25+
26+
- name: Set up Python
27+
uses: actions/setup-python@v5
28+
with:
29+
python-version: 3.11
30+
31+
- name: Install ${{ matrix.swift }}
32+
run: |
33+
wget -q https://download.swift.org/development/ubuntu2404/${{ matrix.swift }}/${{ matrix.swift }}-ubuntu24.04.tar.gz
34+
tar xzf ${{ matrix.swift }}-ubuntu24.04.tar.gz
35+
export PATH="`pwd`/${{ matrix.swift }}-ubuntu24.04/usr/bin/:$PATH"
36+
echo "PATH=$PATH" >> $GITHUB_ENV
37+
swiftc --version
38+
39+
- name: Build ${{ matrix.example }}
40+
working-directory: ${{ matrix.example }}
41+
run: |
42+
pip3 install -r ../Tools/requirements.txt
43+
export STM_BOARD=STM32F746G_DISCOVERY
44+
./build-elf.sh

Tools/elf2hex.py

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
#!/usr/bin/env -S python3 -u -tt
2+
3+
# This source file is part of the Swift open source project
4+
#
5+
# Copyright (c) 2023 Apple Inc. and the Swift project authors.
6+
# Licensed under Apache License v2.0 with Runtime Library Exception
7+
#
8+
# See https://swift.org/LICENSE.txt for license information
9+
10+
#
11+
# elf2hex -- Converts a statically-linked ELF executable into an "Intel HEX"
12+
# file format suitable for flashing onto some embedded devices.
13+
#
14+
# Usage:
15+
# $ elf2hex.py <input> <output> [--symbol-map <output>]
16+
#
17+
# Example:
18+
# $ elf2hex.py ./blink ./blink.hex --symbol-map ./blink.symbols
19+
#
20+
21+
import argparse
22+
import json
23+
import pathlib
24+
25+
import elftools.elf.elffile
26+
27+
28+
def main():
29+
parser = argparse.ArgumentParser()
30+
parser.add_argument('input')
31+
parser.add_argument('output')
32+
parser.add_argument('--symbol-map')
33+
args = parser.parse_args()
34+
35+
inf = open(args.input, "rb")
36+
outf = open(args.output, "wb")
37+
38+
def emitrecord(record):
39+
checksum = 0
40+
pos = 0
41+
while pos < len(record):
42+
checksum = (checksum + int(record[pos:pos + 2], 16)) % 256
43+
pos += 2
44+
checksum = (256 - checksum) % 256
45+
outf.write((":" + record + f"{checksum:02X}" + "\n").encode())
46+
47+
def emit(vmaddr, data):
48+
pos = 0
49+
while pos < len(data):
50+
chunklen = min(16, len(data) - pos)
51+
chunk = data[pos:pos + chunklen]
52+
chunkhex = chunk.hex().upper()
53+
54+
assert vmaddr < 0x100000000, f"vmaddr: {vmaddr:x}"
55+
vmaddr_high = (vmaddr >> 16) & 0xffff
56+
recordtype = "04" # Extended Linear Address
57+
emitrecord(f"{2:02X}{0:04X}{recordtype}{vmaddr_high:04X}")
58+
59+
vmaddr_low = vmaddr & 0xffff
60+
recordtype = "00" # Data
61+
emitrecord(f"{chunklen:02X}{vmaddr_low:04X}{recordtype}{chunkhex}")
62+
63+
pos += chunklen
64+
vmaddr += chunklen
65+
66+
elffile = elftools.elf.elffile.ELFFile(inf)
67+
for segment in elffile.iter_segments():
68+
if segment.header.p_type != "PT_LOAD":
69+
continue
70+
vmaddr = segment.header.p_paddr
71+
data = segment.data()
72+
emit(segment.header.p_paddr, data)
73+
74+
chunklen = 0
75+
vmaddr = 0
76+
recordtype = "01" # EOF
77+
emitrecord(f"{chunklen:02X}{vmaddr:04X}{recordtype}")
78+
79+
symbol_map = {}
80+
symtab_section = elffile.get_section_by_name(".symtab")
81+
for s in symtab_section.iter_symbols():
82+
if s.entry.st_info.type not in ["STT_FUNC", "STT_NOTYPE"]:
83+
continue
84+
if s.entry.st_shndx == "SHN_ABS":
85+
continue
86+
if s.name == "":
87+
continue
88+
symbol_map[s.name] = s.entry.st_value
89+
90+
if args.symbol_map is not None:
91+
pathlib.Path(args.symbol_map).write_text(json.dumps(symbol_map))
92+
93+
inf.close()
94+
outf.close()
95+
96+
97+
if __name__ == '__main__':
98+
main()

Tools/requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
macholib==1.16.3
2+
pyelftools==0.31

stm32-blink/Board.swift

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift open source project
4+
//
5+
// Copyright (c) 2023 Apple Inc. and the Swift project authors.
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
//
10+
//===----------------------------------------------------------------------===//
11+
12+
#if STM32F746G_DISCOVERY
13+
14+
typealias Board = STM32F746Board
15+
enum STM32F746Board {
16+
static func initialize() {
17+
// (1) AHB1ENR[lecConfig.0] = 1 ... enable clock
18+
setRegisterBit(
19+
baseAddress: RCC.BaseAddress, offset: RCC.Offsets.AHB1ENR,
20+
bit: RCC.AHB1ENRBit(for: ledConfig.0),
21+
value: 1)
22+
// (2) MODER[1] = 1 ... set mode to output
23+
setRegisterTwoBitField(
24+
baseAddress: GPIO.GPIOi_BaseAddress, offset: GPIO.Offsets.MODER,
25+
bitsStartingAt: 2 * ledConfig.1, value: 1)
26+
// (3) OTYPER[1] = 0 ... output type is push-pull
27+
setRegisterBit(
28+
baseAddress: GPIO.GPIOi_BaseAddress, offset: GPIO.Offsets.OTYPER,
29+
bit: ledConfig.1,
30+
value: 0)
31+
// (4) OSPEEDR[1] = 2 ... speed is high
32+
setRegisterTwoBitField(
33+
baseAddress: GPIO.GPIOi_BaseAddress, offset: GPIO.Offsets.OSPEEDR,
34+
bitsStartingAt: 2 * ledConfig.1, value: 2)
35+
// (5) PUPDR[1] = 2 ... set pull to down
36+
setRegisterTwoBitField(
37+
baseAddress: GPIO.GPIOi_BaseAddress, offset: GPIO.Offsets.PUPDR,
38+
bitsStartingAt: 2 * ledConfig.1, value: 2)
39+
40+
ledOff()
41+
}
42+
43+
static func ledOn() {
44+
// ODR[1] = 1
45+
setRegisterBit(
46+
baseAddress: GPIO.GPIOi_BaseAddress, offset: GPIO.Offsets.ODR, bit: 1,
47+
value: 1)
48+
}
49+
50+
static func ledOff() {
51+
// ODR[1] = 0
52+
setRegisterBit(
53+
baseAddress: GPIO.GPIOi_BaseAddress, offset: GPIO.Offsets.ODR, bit: 1,
54+
value: 0)
55+
}
56+
57+
static func delay(milliseconds: Int) {
58+
for _ in 0..<10_000 * milliseconds {
59+
nop()
60+
}
61+
}
62+
}
63+
64+
#elseif NUCLEO_F103RB
65+
66+
typealias Board = STM32F1Board
67+
enum STM32F1Board {
68+
static func initialize() {
69+
// (1) APB2ENR[ledConfig.0] = 1 ... enable clock
70+
setRegisterBit(
71+
baseAddress: RCC.BaseAddress, offset: RCC.Offsets.APB2ENR,
72+
bit: RCC.APB2ENRBit(for: ledConfig.0),
73+
value: 1)
74+
// (2) CRL.MODE[ledConfig.1] = 0b11 ... set mode to output, high speed
75+
setRegisterTwoBitField(
76+
baseAddress: GPIO.GPIOBaseAddress(for: ledConfig.0),
77+
offset: GPIO.Offsets.CRL,
78+
bitsStartingAt: 4 * ledConfig.1, value: 3)
79+
// (3) CRL.CNF[ledConfig.1] = 0b00 ... general purpose, push-pull
80+
setRegisterTwoBitField(
81+
baseAddress: GPIO.GPIOBaseAddress(for: ledConfig.0),
82+
offset: GPIO.Offsets.CRL,
83+
bitsStartingAt: 4 * ledConfig.1 + 2, value: 0)
84+
85+
ledOff()
86+
}
87+
88+
static func ledOn() {
89+
// ODR[ledConfig.1] = 1
90+
setRegisterBit(
91+
baseAddress: GPIO.GPIOBaseAddress(for: ledConfig.0),
92+
offset: GPIO.Offsets.ODR, bit: ledConfig.1,
93+
value: 1)
94+
}
95+
96+
static func ledOff() {
97+
// ODR[ledConfig.1] = 0
98+
setRegisterBit(
99+
baseAddress: GPIO.GPIOBaseAddress(for: ledConfig.0),
100+
offset: GPIO.Offsets.ODR, bit: ledConfig.1,
101+
value: 0)
102+
}
103+
104+
static func delay(milliseconds: Int) {
105+
for _ in 0..<10_000 * milliseconds {
106+
nop()
107+
}
108+
}
109+
}
110+
111+
#else
112+
113+
#error("Unknown board")
114+
115+
#endif

stm32-blink/BridgingHeader.h

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,16 +11,6 @@
1111

1212
#pragma once
1313

14-
#include <stdint.h>
15-
16-
static inline __attribute((always_inline)) uint32_t volatile_load_uint32_t(const volatile uint32_t * _Nonnull source) {
17-
return *((const volatile uint32_t * _Nonnull) source);
18-
}
19-
20-
static inline __attribute((always_inline)) void volatile_store_uint32_t(volatile uint32_t * _Nonnull destination, uint32_t value) {
21-
*((volatile uint32_t * _Nonnull) destination) = value;
22-
}
23-
2414
static inline __attribute((always_inline)) void nop() {
2515
asm volatile("nop");
2616
}

stm32-blink/Main.swift

Lines changed: 12 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -9,59 +9,26 @@
99
//
1010
//===----------------------------------------------------------------------===//
1111

12-
enum STM32F746Board {
13-
static func initialize() {
14-
// Configure pin I1 as an LED
12+
#if STM32F746G_DISCOVERY
1513

16-
// (1) AHB1ENR[i] = 1 ... enable clock
17-
setRegisterBit(
18-
baseAddress: RCC.BaseAddress, offset: RCC.Offsets.AHB1ENR, bit: 8,
19-
value: 1)
20-
// (2) MODER[1] = 1 ... set mode to output
21-
setRegisterTwoBitField(
22-
baseAddress: GPIO.GPIOi_BaseAddress, offset: GPIO.Offsets.MODER,
23-
bitsStartingAt: 2, value: 1)
24-
// (3) OTYPER[1] = 0 ... output type is push-pull
25-
setRegisterBit(
26-
baseAddress: GPIO.GPIOi_BaseAddress, offset: GPIO.Offsets.OTYPER, bit: 1,
27-
value: 0)
28-
// (4) OSPEEDR[1] = 2 ... speed is high
29-
setRegisterTwoBitField(
30-
baseAddress: GPIO.GPIOi_BaseAddress, offset: GPIO.Offsets.OSPEEDR,
31-
bitsStartingAt: 2, value: 2)
32-
// (5) PUPDR[1] = 2 ... set pull to down
33-
setRegisterTwoBitField(
34-
baseAddress: GPIO.GPIOi_BaseAddress, offset: GPIO.Offsets.PUPDR,
35-
bitsStartingAt: 2, value: 2)
14+
// I1 pin aka "Arduino D13" pin on STM32F746 Discovery Board
15+
// https://www.st.com/resource/en/schematic_pack/mb1191-f746ngh6-c01_schematic.pdf
16+
let ledConfig: (GPIOBank, GPIOPin) = (.i, 1)
3617

37-
ledOff()
38-
}
18+
#elseif NUCLEO_F103RB
3919

40-
static func ledOn() {
41-
// ODR[1] = 1
42-
setRegisterBit(
43-
baseAddress: GPIO.GPIOi_BaseAddress, offset: GPIO.Offsets.ODR, bit: 1,
44-
value: 1)
45-
}
20+
// A5 pin aka "Arduino D13" pin on Nucleo-64 boards
21+
// https://www.st.com/resource/en/user_manual/um1724-stm32-nucleo64-boards-mb1136-stmicroelectronics.pdf
22+
let ledConfig: (GPIOBank, GPIOPin) = (.a, 5)
4623

47-
static func ledOff() {
48-
// ODR[1] = 0
49-
setRegisterBit(
50-
baseAddress: GPIO.GPIOi_BaseAddress, offset: GPIO.Offsets.ODR, bit: 1,
51-
value: 0)
52-
}
24+
#else
5325

54-
static func delay(milliseconds: Int) {
55-
for _ in 0..<10_000 * milliseconds {
56-
nop()
57-
}
58-
}
59-
}
26+
#error("Unknown board")
27+
28+
#endif
6029

6130
@main
6231
struct Main {
63-
typealias Board = STM32F746Board
64-
6532
static func main() {
6633
Board.initialize()
6734

stm32-blink/README.md

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,41 @@ This example shows a simple baremetal firmware for an STM32 board that blinks an
44

55
<img src="https://github.com/apple/swift-embedded-examples/assets/1186214/739e98fd-a438-4a64-a7aa-9dddee25034b">
66

7-
## How to build and run this example:
7+
## Requirements
88

99
- Connect the STM32F746G-DISCO board via the ST-LINK USB port to your Mac.
10-
- Make sure you have a recent nightly Swift toolchain that has Embedded Swift support.
10+
- Download and install a [recent nightly Swift toolchain](https://swift.org/download). Use the "Development Snapshot" from "main".
1111
- Install the [`stlink`](https://github.com/stlink-org/stlink) command line tools, e.g. via `brew install stlink`.
12+
13+
## Building and running the firmware as Mach-O on macOS
14+
1215
- Build and upload the program to flash memory of the microcontroller:
1316
```console
1417
$ cd stm32-blink
15-
$ TOOLCHAINS='<toolchain-identifier>' ./build.sh
18+
$ export TOOLCHAINS=$(plutil -extract CFBundleIdentifier raw /Library/Developer/Toolchains/swift-latest.xctoolchain/Info.plist)
19+
$ export STM_BOARD=STM32F746G_DISCOVERY # or NUCLEO_F103RB
20+
$ ./build-macho.sh
1621
$ st-flash --reset write .build/blink.bin 0x08000000
1722
```
1823
- The green LED next to the RESET button should now be blinking in a pattern.
1924

25+
## Building and running the firmware as ELF (on either macOS or Linux)
26+
27+
- Build and upload the program to flash memory of the microcontroller:
28+
```console
29+
$ cd stm32-blink
30+
31+
# If on macOS, select the right latest nightly toolchain (on Linux this is not needed):
32+
$ export TOOLCHAINS=$(plutil -extract CFBundleIdentifier raw /Library/Developer/Toolchains/swift-latest.xctoolchain/Info.plist)
33+
34+
$ export STM_BOARD=STM32F746G_DISCOVERY # or NUCLEO_F103RB
35+
$ ./build-elf.sh
36+
$ st-flash --format ihex --reset write .build/blink.hex
37+
```
38+
- The green LED next to the RESET button should now be blinking in a pattern.
39+
40+
## Binary size
41+
2042
The resulting size of the compiled and linked binary is very small (which shouldn't be surprising given that this toy example only blinks an LED), and demonstrates how the Embedded Swift compilation mode doesn't include unnecessary code or data in the resulting program:
2143

2244
```console

0 commit comments

Comments
 (0)