Skip to content
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

Make stm32-blink sample building as both Mach-O and ELF #87

Open
wants to merge 3 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
44 changes: 44 additions & 0 deletions .github/workflows/build-stm.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
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-stm32:
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
export STM_BOARD=STM32F746G_DISCOVERY
./build-elf.sh
98 changes: 98 additions & 0 deletions Tools/elf2hex.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
#!/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 json
import pathlib

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
115 changes: 115 additions & 0 deletions stm32-blink/Board.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
//===----------------------------------------------------------------------===//
//
// 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
//
//===----------------------------------------------------------------------===//

#if STM32F746G_DISCOVERY

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

ledOff()
}

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

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

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

#elseif NUCLEO_F103RB

typealias Board = STM32F1Board
enum STM32F1Board {
static func initialize() {
// (1) APB2ENR[ledConfig.0] = 1 ... enable clock
setRegisterBit(
baseAddress: RCC.BaseAddress, offset: RCC.Offsets.APB2ENR,
bit: RCC.APB2ENRBit(for: ledConfig.0),
value: 1)
// (2) CRL.MODE[ledConfig.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) CRL.CNF[ledConfig.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[ledConfig.1] = 1
setRegisterBit(
baseAddress: GPIO.GPIOBaseAddress(for: ledConfig.0),
offset: GPIO.Offsets.ODR, bit: ledConfig.1,
value: 1)
}

static func ledOff() {
// ODR[ledConfig.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()
}
}
}

#else

#error("Unknown board")

#endif
10 changes: 0 additions & 10 deletions stm32-blink/BridgingHeader.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,6 @@

#pragma once

#include <stdint.h>

static inline __attribute((always_inline)) uint32_t volatile_load_uint32_t(const volatile uint32_t * _Nonnull source) {
return *((const volatile uint32_t * _Nonnull) source);
}

static inline __attribute((always_inline)) void volatile_store_uint32_t(volatile uint32_t * _Nonnull destination, uint32_t value) {
*((volatile uint32_t * _Nonnull) destination) = value;
}

static inline __attribute((always_inline)) void nop() {
asm volatile("nop");
}
57 changes: 12 additions & 45 deletions stm32-blink/Main.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,59 +9,26 @@
//
//===----------------------------------------------------------------------===//

enum STM32F746Board {
static func initialize() {
// Configure pin I1 as an LED
#if STM32F746G_DISCOVERY

// (1) AHB1ENR[i] = 1 ... enable clock
setRegisterBit(
baseAddress: RCC.BaseAddress, offset: RCC.Offsets.AHB1ENR, bit: 8,
value: 1)
// (2) MODER[1] = 1 ... set mode to output
setRegisterTwoBitField(
baseAddress: GPIO.GPIOi_BaseAddress, offset: GPIO.Offsets.MODER,
bitsStartingAt: 2, value: 1)
// (3) OTYPER[1] = 0 ... output type is push-pull
setRegisterBit(
baseAddress: GPIO.GPIOi_BaseAddress, offset: GPIO.Offsets.OTYPER, bit: 1,
value: 0)
// (4) OSPEEDR[1] = 2 ... speed is high
setRegisterTwoBitField(
baseAddress: GPIO.GPIOi_BaseAddress, offset: GPIO.Offsets.OSPEEDR,
bitsStartingAt: 2, value: 2)
// (5) PUPDR[1] = 2 ... set pull to down
setRegisterTwoBitField(
baseAddress: GPIO.GPIOi_BaseAddress, offset: GPIO.Offsets.PUPDR,
bitsStartingAt: 2, value: 2)
// I1 pin aka "Arduino D13" pin on STM32F746 Discovery Board
// https://www.st.com/resource/en/schematic_pack/mb1191-f746ngh6-c01_schematic.pdf
let ledConfig: (GPIOBank, GPIOPin) = (.i, 1)

ledOff()
}
#elseif NUCLEO_F103RB

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

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

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

#endif

@main
struct Main {
typealias Board = STM32F746Board

static func main() {
Board.initialize()

Expand Down
28 changes: 25 additions & 3 deletions stm32-blink/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,41 @@ 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)
$ export STM_BOARD=STM32F746G_DISCOVERY # or NUCLEO_F103RB
$ ./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

# If on macOS, select the right latest nightly toolchain (on Linux this is not needed):
$ export TOOLCHAINS=$(plutil -extract CFBundleIdentifier raw /Library/Developer/Toolchains/swift-latest.xctoolchain/Info.plist)

$ export STM_BOARD=STM32F746G_DISCOVERY # or NUCLEO_F103RB
$ ./build-elf.sh
$ st-flash --format ihex --reset write .build/blink.hex
```
- 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
Loading