Skip to content

Commit

Permalink
Make stm32-blink sample building as both Mach-O and ELF
Browse files Browse the repository at this point in the history
  • Loading branch information
kubamracek committed Jan 14, 2025
1 parent ac91524 commit 0e0014b
Show file tree
Hide file tree
Showing 10 changed files with 451 additions and 44 deletions.
43 changes: 43 additions & 0 deletions .github/workflows/build-stm.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
name: Build STM32 Examples

on:
push:
branches: ["main"]
pull_request:
branches: ["main"]
schedule:
# Build on Mondays at 9am PST every week
- cron: '0 17 * * 1'

jobs:
build-zephyr:
runs-on: ubuntu-24.04

strategy:
fail-fast: false
matrix:
example: [stm32-blink]
swift: [swift-DEVELOPMENT-SNAPSHOT-2024-12-04-a]

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

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: 3.11

- name: Install ${{ matrix.swift }}
run: |
wget -q https://download.swift.org/development/ubuntu2404/${{ matrix.swift }}/${{ matrix.swift }}-ubuntu24.04.tar.gz
tar xzf ${{ matrix.swift }}-ubuntu24.04.tar.gz
export PATH="`pwd`/${{ matrix.swift }}-ubuntu24.04/usr/bin/:$PATH"
echo "PATH=$PATH" >> $GITHUB_ENV
swiftc --version
- name: Build ${{ matrix.example }}
working-directory: ${{ matrix.example }}
run: |
pip3 install -r ../Tools/requirements.txt
./build-elf.sh
92 changes: 92 additions & 0 deletions Tools/elf2hex.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
#!/usr/bin/env -S python3 -u -tt

# 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

#
# elf2hex -- Converts a statically-linked ELF executable into an "Intel HEX" file format suitable for flashing onto some
# embedded devices.
#
# Usage:
# $ elf2hex.py <input> <output> [--symbol-map <output>]
#
# Example:
# $ elf2hex.py ./blink ./blink.hex --symbol-map ./blink.symbols
#

import argparse
import os
import pathlib
import json
import elftools.elf.elffile

def main():
parser = argparse.ArgumentParser()
parser.add_argument('input')
parser.add_argument('output')
parser.add_argument('--symbol-map')
args = parser.parse_args()

inf = open(args.input, "rb")
outf = open(args.output, "wb")

def emitrecord(record):
checksum = 0
pos = 0
while pos < len(record):
checksum = (checksum + int(record[pos:pos+2], 16)) % 256
pos += 2
checksum = (256 - checksum) % 256
outf.write((":" + record + f"{checksum:02X}" + "\n").encode())

def emit(vmaddr, data):
pos = 0
while pos < len(data):
chunklen = min(16, len(data) - pos)
chunk = data[pos:pos+chunklen]
chunkhex = chunk.hex().upper()

assert vmaddr < 0x100000000, f"vmaddr: {vmaddr:x}"
vmaddr_high = (vmaddr >> 16) & 0xffff
recordtype = "04" # Extended Linear Address
emitrecord(f"{2:02X}{0:04X}{recordtype}{vmaddr_high:04X}")

vmaddr_low = vmaddr & 0xffff
recordtype = "00" # Data
emitrecord(f"{chunklen:02X}{vmaddr_low:04X}{recordtype}{chunkhex}")

pos += chunklen
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))

inf.close()
outf.close()

if __name__ == '__main__':
main()
1 change: 1 addition & 0 deletions Tools/requirements.txt
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
macholib==1.16.3
pyelftools==0.31
112 changes: 108 additions & 4 deletions stm32-blink/Main.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,22 @@
//
//===----------------------------------------------------------------------===//

enum GPIOBank: Int {
case a, b, c, d, e, f, g, h, i, j, k
}
typealias GPIOPin = Int

// I1 pin on STM32F746 Discovery Board
//let ledConfig: (GPIOBank, GPIOPin) = (.i, 1)

// A5 aka "Arduino D13" pin on Nucleo-64 boards
let ledConfig: (GPIOBank, GPIOPin) = (.a, 5)

#if STM32F74_F75

typealias Board = STM32F746Board
enum STM32F746Board {
static func initialize() {
// Configure pin I1 as an LED

// (1) AHB1ENR[i] = 1 ... enable clock
setRegisterBit(
baseAddress: RCC.BaseAddress, offset: RCC.Offsets.AHB1ENR, bit: 8,
Expand Down Expand Up @@ -58,10 +70,102 @@ enum STM32F746Board {
}
}

#elseif STM32F1

typealias Board = STM32F1Board
enum STM32F1Board {
static func initialize() {
// (1) IOPENR[ledConfig.0] = 1 ... enable clock
setRegisterBit(
baseAddress: RCC.BaseAddress, offset: RCC.Offsets.APB2ENR, bit: RCC.APB2ENRBit(for: ledConfig.0),
value: 1)
// (2) MODE[1] = 0b11 ... set mode to output, high speed
setRegisterTwoBitField(
baseAddress: GPIO.GPIOBaseAddress(for: ledConfig.0), offset: GPIO.Offsets.CRL,
bitsStartingAt: 4 * ledConfig.1, value: 3)
// (3) CNF[1] = 0b00 ... general purpose, push-pull
setRegisterTwoBitField(
baseAddress: GPIO.GPIOBaseAddress(for: ledConfig.0), offset: GPIO.Offsets.CRL,
bitsStartingAt: 4 * ledConfig.1 + 2, value: 0)

ledOff()
}

static func ledOn() {
// ODR[1] = 1
setRegisterBit(
baseAddress: GPIO.GPIOBaseAddress(for: ledConfig.0), offset: GPIO.Offsets.ODR, bit: ledConfig.1,
value: 1)
}

static func ledOff() {
// ODR[1] = 0
setRegisterBit(
baseAddress: GPIO.GPIOBaseAddress(for: ledConfig.0), offset: GPIO.Offsets.ODR, bit: ledConfig.1,
value: 0)
}

static func delay(milliseconds: Int) {
for _ in 0..<10_000 * milliseconds {
nop()
}
}
}

#elseif STM32C0

typealias Board = STM32C0Board
enum STM32C0Board {
static func initialize() {
// (1) IOPENR[ledConfig.0] = 1 ... enable clock
setRegisterBit(
baseAddress: RCC.BaseAddress, offset: RCC.Offsets.IOPENR, bit: ledConfig.0.rawValue,
value: 1)
// (2) MODER[1] = 1 ... set mode to output
setRegisterTwoBitField(
baseAddress: GPIO.GPIOBaseAddress(for: ledConfig.0), offset: GPIO.Offsets.MODER,
bitsStartingAt: 2 * ledConfig.1, value: 1)
// (3) OTYPER[1] = 0 ... output type is push-pull
setRegisterBit(
baseAddress: GPIO.GPIOBaseAddress(for: ledConfig.0), offset: GPIO.Offsets.OTYPER, bit: ledConfig.1,
value: 0)
// (4) OSPEEDR[1] = 2 ... speed is high
setRegisterTwoBitField(
baseAddress: GPIO.GPIOBaseAddress(for: ledConfig.0), offset: GPIO.Offsets.OSPEEDR,
bitsStartingAt: 2 * ledConfig.1, value: 2)
// (5) PUPDR[1] = 2 ... set pull to down
setRegisterTwoBitField(
baseAddress: GPIO.GPIOBaseAddress(for: ledConfig.0), offset: GPIO.Offsets.PUPDR,
bitsStartingAt: 2 * ledConfig.1, value: 2)

ledOff()
}

static func ledOn() {
// ODR[1] = 1
setRegisterBit(
baseAddress: GPIO.GPIOBaseAddress(for: ledConfig.0), offset: GPIO.Offsets.ODR, bit: ledConfig.1,
value: 1)
}

static func ledOff() {
// ODR[1] = 0
setRegisterBit(
baseAddress: GPIO.GPIOBaseAddress(for: ledConfig.0), offset: GPIO.Offsets.ODR, bit: ledConfig.1,
value: 0)
}

static func delay(milliseconds: Int) {
for _ in 0..<10_000 * milliseconds {
nop()
}
}
}

#endif

@main
struct Main {
typealias Board = STM32F746Board

static func main() {
Board.initialize()

Expand Down
25 changes: 22 additions & 3 deletions stm32-blink/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,38 @@ This example shows a simple baremetal firmware for an STM32 board that blinks an

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

## How to build and run this example:
## Requirements

- Connect the STM32F746G-DISCO board via the ST-LINK USB port to your Mac.
- Make sure you have a recent nightly Swift toolchain that has Embedded Swift support.
- Download and install a [recent nightly Swift toolchain](https://swift.org/download). Use the "Development Snapshot" from "main".
- Install the [`stlink`](https://github.com/stlink-org/stlink) command line tools, e.g. via `brew install stlink`.

## Building and running the firmware as Mach-O on macOS

- Build and upload the program to flash memory of the microcontroller:
```console
$ cd stm32-blink
$ TOOLCHAINS='<toolchain-identifier>' ./build.sh
$ export TOOLCHAINS=$(plutil -extract CFBundleIdentifier raw /Library/Developer/Toolchains/swift-latest.xctoolchain/Info.plist)
$ ./build-macho.sh
$ st-flash --reset write .build/blink.bin 0x08000000
```
- The green LED next to the RESET button should now be blinking in a pattern.

## Building and running the firmware as ELF (on either macOS or Linux)

- Build and upload the program to flash memory of the microcontroller:
```console
$ cd stm32-blink
$ export TOOLCHAINS=$(plutil -extract CFBundleIdentifier raw /Library/Developer/Toolchains/swift-latest.xctoolchain/Info.plist)
$ ./build-elf.sh
$ st-util
(then in a separate terminal)
$ st-flash --reset write .build/blink.elf 0x08000000
```
- The green LED next to the RESET button should now be blinking in a pattern.

## Binary size

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:

```console
Expand Down
Loading

0 comments on commit 0e0014b

Please sign in to comment.