Skip to content

Commit 147b43a

Browse files
committed
Make stm32-blink sample building as both Mach-O and ELF
1 parent 5e21399 commit 147b43a

File tree

10 files changed

+451
-44
lines changed

10 files changed

+451
-44
lines changed

.github/workflows/build-stm.yml

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
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+
./build-elf.sh

Tools/elf2hex.py

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
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" file format suitable for flashing onto some
12+
# 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 os
23+
import pathlib
24+
import json
25+
import elftools.elf.elffile
26+
27+
def main():
28+
parser = argparse.ArgumentParser()
29+
parser.add_argument('input')
30+
parser.add_argument('output')
31+
parser.add_argument('--symbol-map')
32+
args = parser.parse_args()
33+
34+
inf = open(args.input, "rb")
35+
outf = open(args.output, "wb")
36+
37+
def emitrecord(record):
38+
checksum = 0
39+
pos = 0
40+
while pos < len(record):
41+
checksum = (checksum + int(record[pos:pos+2], 16)) % 256
42+
pos += 2
43+
checksum = (256 - checksum) % 256
44+
outf.write((":" + record + f"{checksum:02X}" + "\n").encode())
45+
46+
def emit(vmaddr, data):
47+
pos = 0
48+
while pos < len(data):
49+
chunklen = min(16, len(data) - pos)
50+
chunk = data[pos:pos+chunklen]
51+
chunkhex = chunk.hex().upper()
52+
53+
assert vmaddr < 0x100000000, f"vmaddr: {vmaddr:x}"
54+
vmaddr_high = (vmaddr >> 16) & 0xffff
55+
recordtype = "04" # Extended Linear Address
56+
emitrecord(f"{2:02X}{0:04X}{recordtype}{vmaddr_high:04X}")
57+
58+
vmaddr_low = vmaddr & 0xffff
59+
recordtype = "00" # Data
60+
emitrecord(f"{chunklen:02X}{vmaddr_low:04X}{recordtype}{chunkhex}")
61+
62+
pos += chunklen
63+
vmaddr += chunklen
64+
65+
elffile = elftools.elf.elffile.ELFFile(inf)
66+
for segment in elffile.iter_segments():
67+
if segment.header.p_type != "PT_LOAD": continue
68+
vmaddr = segment.header.p_paddr
69+
data = segment.data()
70+
emit(segment.header.p_paddr, data)
71+
72+
chunklen = 0
73+
vmaddr = 0
74+
recordtype = "01" # EOF
75+
emitrecord(f"{chunklen:02X}{vmaddr:04X}{recordtype}")
76+
77+
symbol_map = {}
78+
symtab_section = elffile.get_section_by_name(".symtab")
79+
for s in symtab_section.iter_symbols():
80+
if s.entry.st_info.type not in ["STT_FUNC", "STT_NOTYPE"]: continue
81+
if s.entry.st_shndx == "SHN_ABS": continue
82+
if s.name == "": continue
83+
symbol_map[s.name] = s.entry.st_value
84+
85+
if args.symbol_map is not None:
86+
pathlib.Path(args.symbol_map).write_text(json.dumps(symbol_map))
87+
88+
inf.close()
89+
outf.close()
90+
91+
if __name__ == '__main__':
92+
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/Main.swift

Lines changed: 108 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,22 @@
99
//
1010
//===----------------------------------------------------------------------===//
1111

12+
enum GPIOBank: Int {
13+
case a, b, c, d, e, f, g, h, i, j, k
14+
}
15+
typealias GPIOPin = Int
16+
17+
// I1 pin on STM32F746 Discovery Board
18+
//let ledConfig: (GPIOBank, GPIOPin) = (.i, 1)
19+
20+
// A5 aka "Arduino D13" pin on Nucleo-64 boards
21+
let ledConfig: (GPIOBank, GPIOPin) = (.a, 5)
22+
23+
#if STM32F74_F75
24+
25+
typealias Board = STM32F746Board
1226
enum STM32F746Board {
1327
static func initialize() {
14-
// Configure pin I1 as an LED
15-
1628
// (1) AHB1ENR[i] = 1 ... enable clock
1729
setRegisterBit(
1830
baseAddress: RCC.BaseAddress, offset: RCC.Offsets.AHB1ENR, bit: 8,
@@ -58,10 +70,102 @@ enum STM32F746Board {
5870
}
5971
}
6072

73+
#elseif STM32F1
74+
75+
typealias Board = STM32F1Board
76+
enum STM32F1Board {
77+
static func initialize() {
78+
// (1) IOPENR[ledConfig.0] = 1 ... enable clock
79+
setRegisterBit(
80+
baseAddress: RCC.BaseAddress, offset: RCC.Offsets.APB2ENR, bit: RCC.APB2ENRBit(for: ledConfig.0),
81+
value: 1)
82+
// (2) MODE[1] = 0b11 ... set mode to output, high speed
83+
setRegisterTwoBitField(
84+
baseAddress: GPIO.GPIOBaseAddress(for: ledConfig.0), offset: GPIO.Offsets.CRL,
85+
bitsStartingAt: 4 * ledConfig.1, value: 3)
86+
// (3) CNF[1] = 0b00 ... general purpose, push-pull
87+
setRegisterTwoBitField(
88+
baseAddress: GPIO.GPIOBaseAddress(for: ledConfig.0), offset: GPIO.Offsets.CRL,
89+
bitsStartingAt: 4 * ledConfig.1 + 2, value: 0)
90+
91+
ledOff()
92+
}
93+
94+
static func ledOn() {
95+
// ODR[1] = 1
96+
setRegisterBit(
97+
baseAddress: GPIO.GPIOBaseAddress(for: ledConfig.0), offset: GPIO.Offsets.ODR, bit: ledConfig.1,
98+
value: 1)
99+
}
100+
101+
static func ledOff() {
102+
// ODR[1] = 0
103+
setRegisterBit(
104+
baseAddress: GPIO.GPIOBaseAddress(for: ledConfig.0), offset: GPIO.Offsets.ODR, bit: ledConfig.1,
105+
value: 0)
106+
}
107+
108+
static func delay(milliseconds: Int) {
109+
for _ in 0..<10_000 * milliseconds {
110+
nop()
111+
}
112+
}
113+
}
114+
115+
#elseif STM32C0
116+
117+
typealias Board = STM32C0Board
118+
enum STM32C0Board {
119+
static func initialize() {
120+
// (1) IOPENR[ledConfig.0] = 1 ... enable clock
121+
setRegisterBit(
122+
baseAddress: RCC.BaseAddress, offset: RCC.Offsets.IOPENR, bit: ledConfig.0.rawValue,
123+
value: 1)
124+
// (2) MODER[1] = 1 ... set mode to output
125+
setRegisterTwoBitField(
126+
baseAddress: GPIO.GPIOBaseAddress(for: ledConfig.0), offset: GPIO.Offsets.MODER,
127+
bitsStartingAt: 2 * ledConfig.1, value: 1)
128+
// (3) OTYPER[1] = 0 ... output type is push-pull
129+
setRegisterBit(
130+
baseAddress: GPIO.GPIOBaseAddress(for: ledConfig.0), offset: GPIO.Offsets.OTYPER, bit: ledConfig.1,
131+
value: 0)
132+
// (4) OSPEEDR[1] = 2 ... speed is high
133+
setRegisterTwoBitField(
134+
baseAddress: GPIO.GPIOBaseAddress(for: ledConfig.0), offset: GPIO.Offsets.OSPEEDR,
135+
bitsStartingAt: 2 * ledConfig.1, value: 2)
136+
// (5) PUPDR[1] = 2 ... set pull to down
137+
setRegisterTwoBitField(
138+
baseAddress: GPIO.GPIOBaseAddress(for: ledConfig.0), offset: GPIO.Offsets.PUPDR,
139+
bitsStartingAt: 2 * ledConfig.1, value: 2)
140+
141+
ledOff()
142+
}
143+
144+
static func ledOn() {
145+
// ODR[1] = 1
146+
setRegisterBit(
147+
baseAddress: GPIO.GPIOBaseAddress(for: ledConfig.0), offset: GPIO.Offsets.ODR, bit: ledConfig.1,
148+
value: 1)
149+
}
150+
151+
static func ledOff() {
152+
// ODR[1] = 0
153+
setRegisterBit(
154+
baseAddress: GPIO.GPIOBaseAddress(for: ledConfig.0), offset: GPIO.Offsets.ODR, bit: ledConfig.1,
155+
value: 0)
156+
}
157+
158+
static func delay(milliseconds: Int) {
159+
for _ in 0..<10_000 * milliseconds {
160+
nop()
161+
}
162+
}
163+
}
164+
165+
#endif
166+
61167
@main
62168
struct Main {
63-
typealias Board = STM32F746Board
64-
65169
static func main() {
66170
Board.initialize()
67171

stm32-blink/README.md

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,38 @@ 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+
$ ./build-macho.sh
1620
$ st-flash --reset write .build/blink.bin 0x08000000
1721
```
1822
- The green LED next to the RESET button should now be blinking in a pattern.
1923

24+
## Building and running the firmware as ELF (on either macOS or Linux)
25+
26+
- Build and upload the program to flash memory of the microcontroller:
27+
```console
28+
$ cd stm32-blink
29+
$ export TOOLCHAINS=$(plutil -extract CFBundleIdentifier raw /Library/Developer/Toolchains/swift-latest.xctoolchain/Info.plist)
30+
$ ./build-elf.sh
31+
$ st-util
32+
(then in a separate terminal)
33+
$ st-flash --reset write .build/blink.elf 0x08000000
34+
```
35+
- The green LED next to the RESET button should now be blinking in a pattern.
36+
37+
## Binary size
38+
2039
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:
2140

2241
```console

0 commit comments

Comments
 (0)