Puya PY32F MCU support (#5106)

* pinout yamls removed

* machine: implement default UART pin configuration for Embedfire boards

* flash command moved to chip level so VS Code plugin can select bare chip

* machine: add support for alternate pin mode configuration

* add build targets for embedfire on py32

* add support for embedfire target in GNUmakefile for py32

* lib/py32-svd: remove duplicate DBGMCU.IDCODE CODE field

Update submodule to fix duplicate SetIDCODE/GetIDCODE method
declarations in the generated py32f002bxx.go device file.

* update subproject commit reference in py32-svd

* refactor: update CPU frequency handling in machine and runtime packages

* refactor: replace ConfigureUARTPin function with direct pin configuration in UART setup

* SetAltFunc documentation for GPIO pin alternate functions

* refactor: improve UART write and flush error handling with timeout

* machine/py32: generalize UART driver to any USART

Add a per-instance setup func to the UART type so the driver is no longer
hardwired to USART1. DefaultUART stays USART1; add UART2 (USART2) in a
separately build-tagged file since py32f002x parts lack USART2. RX IRQ
handlers reference runtime-assigned vars to break the init cycle, and setup
is assigned in the var initializer so it runs before InitSerial.

Add the py32f003_32k_4k target (32K flash / 4K RAM).

* Add canonical PY32F002, F003, and F030 density targets

Use CMSIS/pyocd device names for target files and build tags. Add all F003 and F030 densities plus F002A/B, correct F002B to 24K flash and its own startup file, fix the F030x8 pyocd target, and update Embedfire inheritance.

* Add targets for all PY32 CMSIS devices

* Support all PY32 register layout variants

* machine/py32: use generated register definitions

* machine/py32: fix alternate-function documentation

* machine/py32: report the configured CPU frequency

* machine/py32: use unsafe.Add for GPIO ports

* machine/py32: bound and yield UART polling

* machine/py32: configure UART pins from UARTConfig

* build: use the current PY32 SVD repository URL

* test: cover PY32 UART register layouts

* machine/py32: restore dynamic CPU frequency tracking

* machine/py32: share the USART TX-ready bit

* targets/py32: scale system stacks with RAM

* machine/py32: keep clock state internal

* targets/py32: normalize generated metadata

* machine/py32: normalize GPIO configuration

* machine/py32: fix T020 UART setup

* machine/py32: reduce clock variant files

* machine/py32: clean up UART variants

* machine/py32: decouple UART clock capability

* lib/py32-svd: use organization repository

* runtime/py32: name 24MHz HSI encodings

* machine/py32: derive startup clock from capability

* machine/py32: use generated USART clock mask

* machine/py32: name T020 8-bit UART mode

* ci: include PY32 in sharded smoke tests

* runtime: remove PY32 HSI frequency workaround
This commit is contained in:
Pavel Burgr
2026-08-29 08:41:43 +02:00
committed by GitHub
parent f6e502e121
commit b420a8be17
272 changed files with 3744 additions and 9 deletions
+158
View File
@@ -0,0 +1,158 @@
package main
import (
"encoding/json"
"os"
"path/filepath"
"strings"
"testing"
)
func TestDeviceTable(t *testing.T) {
devices, err := readDevices("devices.csv")
if err != nil {
t.Fatal(err)
}
if got, want := len(devices), 87; got != want {
t.Fatalf("got %d concrete devices, want %d", got, want)
}
families := make(map[string]bool)
for family := range orphanFamilies {
families[family] = true
}
m4 := 0
for _, device := range devices {
families[device.Family] = true
if device.Core == "m4" {
m4++
}
}
if got, want := len(families), 41; got != want {
t.Fatalf("got %d SVD families, want %d", got, want)
}
if got, want := m4, 7; got != want {
t.Fatalf("got %d Cortex-M4 devices, want %d", got, want)
}
}
func TestSystemStackSize(t *testing.T) {
tests := []struct {
ramSize uint64
want uint64
}{
{2 * 1024, 1024},
{4 * 1024, 1024},
{4*1024 + 1, 2 * 1024},
{16 * 1024, 2 * 1024},
{16*1024 + 1, 4 * 1024},
{144 * 1024, 4 * 1024},
}
for _, test := range tests {
if got := systemStackSize(test.ramSize); got != test.want {
t.Errorf("systemStackSize(%d) = %d, want %d", test.ramSize, got, test.want)
}
}
}
func TestGenerate(t *testing.T) {
devices, err := readDevices("devices.csv")
if err != nil {
t.Fatal(err)
}
out := t.TempDir()
if err := generate(out, devices); err != nil {
t.Fatal(err)
}
entries, err := os.ReadDir(out)
if err != nil {
t.Fatal(err)
}
// 41 family JSON files, 87 generated concrete JSON files, and linker
// scripts for all 87 concrete targets.
if got, want := len(entries), 215; got != want {
t.Fatalf("generated %d files, want %d", got, want)
}
checkTarget(t, filepath.Join(out, "py32e407xc.json"), "py32e407xx", "targets/py32e407xc.ld", "")
checkTarget(t, filepath.Join(out, "py32f001xx.json"), "py32", "", "")
checkTarget(t, filepath.Join(out, "py32f002ax5.json"), "py32f002axx", "targets/py32f002ax5.ld", "pyocd load -t py32f002ax5 {bin}")
checkTarget(t, filepath.Join(out, "py32f030x8.json"), "py32f030xx", "targets/py32f030x8.ld", "pyocd load -t py32f030x8 {bin}")
checkTarget(t, filepath.Join(out, "py32f410xx.json"), "py32-m4", "", "")
checkBuildTags(t, filepath.Join(out, "py32e407xx.json"), "py32e407xx", "py32_gpio_ospdder", "py32_gpio_clock_ahb2", "py32_no_hsi_fs", "py32_usart_split_data", "py32_uart_clock_apb2")
checkBuildTags(t, filepath.Join(out, "py32f001cxx.json"), "py32f001cxx", "py32_no_gpio_afrh", "py32_usart1_clock_literal")
checkBuildTags(t, filepath.Join(out, "py32f001xx.json"), "py32f001xx", "py32_usart1_clock_literal")
checkBuildTags(t, filepath.Join(out, "py32f002cxx.json"), "py32f002cxx", "py32_no_gpio_afrh")
checkBuildTags(t, filepath.Join(out, "py32f032xx.json"), "py32f032xx")
checkBuildTags(t, filepath.Join(out, "py32f410xx.json"), "py32f410xx", "py32_gpio_ospdder", "py32_gpio_clock_ahb", "py32_no_hsi_fs", "py32_usart_unnumbered", "py32_uart_clock_apb2", "py32_uart_no_interrupt")
checkBuildTags(t, filepath.Join(out, "py32t020xx.json"), "py32t020xx", "py32_uart_type")
data, err := os.ReadFile(filepath.Join(out, "py32e407xc.ld"))
if err != nil {
t.Fatal(err)
}
want := "ORIGIN = 0x20000000, LENGTH = 0x1c000"
if !contains(string(data), want) {
t.Fatalf("E407 linker script does not contain combined contiguous RAM %q", want)
}
checkFileContains(t, filepath.Join(out, "py32f002ax5.ld"), "_stack_size = 1K;")
checkFileContains(t, filepath.Join(out, "py32f030x8.ld"), "_stack_size = 2K;")
checkFileContains(t, filepath.Join(out, "py32l090xc.ld"), "_stack_size = 4K;")
}
func checkFileContains(t *testing.T, path, want string) {
t.Helper()
data, err := os.ReadFile(path)
if err != nil {
t.Fatal(err)
}
if !strings.Contains(string(data), want) {
t.Errorf("%s does not contain %q", path, want)
}
}
func checkBuildTags(t *testing.T, path string, want ...string) {
t.Helper()
data, err := os.ReadFile(path)
if err != nil {
t.Fatal(err)
}
var spec target
if err := json.Unmarshal(data, &spec); err != nil {
t.Fatal(err)
}
if got := strings.Join(spec.BuildTags, ","); got != strings.Join(want, ",") {
t.Errorf("%s build tags are %q, want %q", path, got, strings.Join(want, ","))
}
}
func checkTarget(t *testing.T, path, inherited, linker, flashCommand string) {
t.Helper()
data, err := os.ReadFile(path)
if err != nil {
t.Fatal(err)
}
var spec target
if err := json.Unmarshal(data, &spec); err != nil {
t.Fatal(err)
}
if len(spec.Inherits) != 1 || spec.Inherits[0] != inherited {
t.Errorf("%s inherits %v, want %q", path, spec.Inherits, inherited)
}
if spec.LinkerScript != linker {
t.Errorf("%s linker script is %q, want %q", path, spec.LinkerScript, linker)
}
if spec.FlashCommand != flashCommand {
t.Errorf("%s flash command is %q, want %q", path, spec.FlashCommand, flashCommand)
}
}
func contains(value, substring string) bool {
for i := 0; i+len(substring) <= len(value); i++ {
if value[i:i+len(substring)] == substring {
return true
}
}
return false
}