From b23ff297945cc8b11a786b72d48c851dc35e75dd Mon Sep 17 00:00:00 2001 From: xushiwei Date: Thu, 3 Apr 2025 23:14:39 +0800 Subject: [PATCH 01/11] README --- README.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 3e1a2e75..97cdd3ac 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,8 @@ -# lib LLGo Standard Libraries +===== + +[![Build Status](https://github.com/goplus/lib/actions/workflows/go.yml/badge.svg)](https://github.com/goplus/lib/actions/workflows/go.yml) +[![Go Report Card](https://goreportcard.com/badge/github.com/goplus/lib)](https://goreportcard.com/report/github.com/goplus/lib) +[![GitHub release](https://img.shields.io/github/v/tag/goplus/lib.svg?label=release)](https://github.com/goplus/lib/releases) +[![Coverage Status](https://codecov.io/gh/goplus/lib/branch/main/graph/badge.svg)](https://codecov.io/gh/goplus/lib) +[![GoDoc](https://pkg.go.dev/badge/github.com/goplus/lib.svg)](https://pkg.go.dev/github.com/goplus/lib) From 0e1e95f4d9a3f38a751aed53cc97ba307cdc2b9e Mon Sep 17 00:00:00 2001 From: xushiwei Date: Thu, 3 Apr 2025 23:16:13 +0800 Subject: [PATCH 02/11] Create go.yml --- .github/workflows/go.yml | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 .github/workflows/go.yml diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml new file mode 100644 index 00000000..6821ca92 --- /dev/null +++ b/.github/workflows/go.yml @@ -0,0 +1,25 @@ +# This workflow will build a golang project +# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-go + +name: Go + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +jobs: + + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Set up Go + uses: actions/setup-go@v4 + with: + go-version: '1.20' + + - name: Build + run: go build -v ./... From e8d97e8db02e1ed0eb894ac97312db79c89b1988 Mon Sep 17 00:00:00 2001 From: xushiwei Date: Thu, 3 Apr 2025 23:18:02 +0800 Subject: [PATCH 03/11] Update README.md --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 97cdd3ac..9203b273 100644 --- a/README.md +++ b/README.md @@ -4,5 +4,7 @@ LLGo Standard Libraries [![Build Status](https://github.com/goplus/lib/actions/workflows/go.yml/badge.svg)](https://github.com/goplus/lib/actions/workflows/go.yml) [![Go Report Card](https://goreportcard.com/badge/github.com/goplus/lib)](https://goreportcard.com/report/github.com/goplus/lib) [![GitHub release](https://img.shields.io/github/v/tag/goplus/lib.svg?label=release)](https://github.com/goplus/lib/releases) -[![Coverage Status](https://codecov.io/gh/goplus/lib/branch/main/graph/badge.svg)](https://codecov.io/gh/goplus/lib) [![GoDoc](https://pkg.go.dev/badge/github.com/goplus/lib.svg)](https://pkg.go.dev/github.com/goplus/lib) + From 61b226414105a23852f74556e8ffe40f2b30ccf3 Mon Sep 17 00:00:00 2001 From: Li Jie Date: Thu, 3 Apr 2025 19:18:53 +0800 Subject: [PATCH 04/11] make lib/c don't use cgo --- c/c.go | 26 +++++++++++--------------- c/ctypes_32bit.go | 27 +++++++++++++++++++++++++++ c/ctypes_unix64.go | 27 +++++++++++++++++++++++++++ c/ctypes_wasm.go | 26 ++++++++++++++++++++++++++ c/ctypes_windows.go | 26 ++++++++++++++++++++++++++ c/{c_default.go => stdio_darwin.go} | 4 ++-- c/{c_linux.go => stdio_default.go} | 4 ++-- 7 files changed, 121 insertions(+), 19 deletions(-) create mode 100644 c/ctypes_32bit.go create mode 100644 c/ctypes_unix64.go create mode 100644 c/ctypes_wasm.go create mode 100644 c/ctypes_windows.go rename c/{c_default.go => stdio_darwin.go} (95%) rename c/{c_linux.go => stdio_default.go} (95%) diff --git a/c/c.go b/c/c.go index 918a6716..8408dd97 100644 --- a/c/c.go +++ b/c/c.go @@ -16,12 +16,9 @@ package c -// typedef unsigned int uint; -// typedef unsigned long ulong; -// typedef unsigned long long ulonglong; -// typedef long long longlong; -import "C" -import "unsafe" +import ( + "unsafe" +) const ( LLGoPackage = "decl" @@ -41,14 +38,16 @@ type FILE struct { } type ( - Int C.int - Uint C.uint + Int = int32 + Uint = uint32 - Long C.long - Ulong C.ulong + // Long and Ulong are defined in platform-specific files + // Windows (both 32-bit and 64-bit): int32/uint32 + // Unix/Linux/macOS 32-bit: int32/uint32 + // Unix/Linux/macOS 64-bit: int64/uint64 - LongLong C.longlong - UlongLong C.ulonglong + LongLong = int64 + UlongLong = uint64 ) type integer interface { @@ -308,6 +307,3 @@ func GetoptLong(argc Int, argv **Char, optstring *Char, longopts *Option, longin func GetoptLongOnly(argc Int, argv **Char, optstring *Char, longopts *Option, longindex *Int) Int // ----------------------------------------------------------------------------- - -//go:linkname Sysconf C.sysconf -func Sysconf(name Int) Long diff --git a/c/ctypes_32bit.go b/c/ctypes_32bit.go new file mode 100644 index 00000000..4b4335b7 --- /dev/null +++ b/c/ctypes_32bit.go @@ -0,0 +1,27 @@ +//go:build (linux || darwin || freebsd || netbsd || openbsd || solaris) && (386 || arm || mips || mipsle) +// +build linux darwin freebsd netbsd openbsd solaris +// +build 386 arm mips mipsle + +/* + * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package c + +// For 32-bit Unix/Linux/macOS, Long is 32-bit +type ( + Long = int32 + Ulong = uint32 +) diff --git a/c/ctypes_unix64.go b/c/ctypes_unix64.go new file mode 100644 index 00000000..1cbbd578 --- /dev/null +++ b/c/ctypes_unix64.go @@ -0,0 +1,27 @@ +//go:build (linux || darwin || freebsd || netbsd || openbsd || solaris) && (amd64 || arm64 || ppc64 || ppc64le || mips64 || mips64le || s390x || riscv64) +// +build linux darwin freebsd netbsd openbsd solaris +// +build amd64 arm64 ppc64 ppc64le mips64 mips64le s390x riscv64 + +/* + * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package c + +// For 64-bit Unix/Linux/macOS, Long is 64-bit +type ( + Long = int64 + Ulong = uint64 +) diff --git a/c/ctypes_wasm.go b/c/ctypes_wasm.go new file mode 100644 index 00000000..1c243746 --- /dev/null +++ b/c/ctypes_wasm.go @@ -0,0 +1,26 @@ +//go:build wasip1 || js +// +build wasip1 js + +/* + * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package c + +// For WebAssembly targets, Long is 32-bit per the spec +type ( + Long = int32 + Ulong = uint32 +) diff --git a/c/ctypes_windows.go b/c/ctypes_windows.go new file mode 100644 index 00000000..19fb0dcc --- /dev/null +++ b/c/ctypes_windows.go @@ -0,0 +1,26 @@ +//go:build windows +// +build windows + +/* + * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package c + +// For Windows (LLP64 model), Long is 32-bit, regardless of architecture +type ( + Long = int32 + Ulong = uint32 +) diff --git a/c/c_default.go b/c/stdio_darwin.go similarity index 95% rename from c/c_default.go rename to c/stdio_darwin.go index 45286fe9..324403f5 100644 --- a/c/c_default.go +++ b/c/stdio_darwin.go @@ -1,5 +1,5 @@ -//go:build !linux -// +build !linux +//go:build darwin +// +build darwin /* * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved. diff --git a/c/c_linux.go b/c/stdio_default.go similarity index 95% rename from c/c_linux.go rename to c/stdio_default.go index 70cc9bea..5f95fd27 100644 --- a/c/c_linux.go +++ b/c/stdio_default.go @@ -1,5 +1,5 @@ -//go:build linux -// +build linux +//go:build !darwin +// +build !darwin /* * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved. From 56339bf6f4110f2a4e85f6a54a1cd8c1230b0edd Mon Sep 17 00:00:00 2001 From: xushiwei Date: Thu, 26 Jun 2025 22:39:38 +0800 Subject: [PATCH 05/11] emb/runtime/volatile, emb/machine --- emb/machine/_usb/adc/doc.go | 2 + emb/machine/_usb/adc/midi/buffer.go | 52 + emb/machine/_usb/adc/midi/messages.go | 233 ++ emb/machine/_usb/adc/midi/midi.go | 127 + emb/machine/_usb/adc/midi/notes.go | 108 + emb/machine/_usb/cdc/buffer.go | 119 + emb/machine/_usb/cdc/cdc.go | 61 + emb/machine/_usb/cdc/doc.go | 2 + emb/machine/_usb/cdc/usbcdc.go | 191 ++ emb/machine/_usb/config.go | 16 + emb/machine/_usb/descriptor/cdc.go | 168 ++ emb/machine/_usb/descriptor/classspecific.go | 17 + emb/machine/_usb/descriptor/configuration.go | 49 + emb/machine/_usb/descriptor/descriptor.go | 63 + emb/machine/_usb/descriptor/device.go | 73 + emb/machine/_usb/descriptor/doc.go | 3 + emb/machine/_usb/descriptor/endpoint.go | 156 ++ emb/machine/_usb/descriptor/hid.go | 216 ++ emb/machine/_usb/descriptor/hidreport.go | 220 ++ emb/machine/_usb/descriptor/interface.go | 49 + .../_usb/descriptor/interfaceassociation.go | 45 + emb/machine/_usb/descriptor/joystick.go | 144 + emb/machine/_usb/descriptor/midi.go | 249 ++ emb/machine/_usb/descriptor/msc.go | 75 + emb/machine/_usb/doc.go | 4 + emb/machine/_usb/hid/buffer.go | 52 + emb/machine/_usb/hid/doc.go | 2 + emb/machine/_usb/hid/hid.go | 100 + emb/machine/_usb/hid/joystick/joystick.go | 125 + emb/machine/_usb/hid/joystick/state.go | 174 ++ emb/machine/_usb/hid/keyboard/keyboard.go | 525 ++++ emb/machine/_usb/hid/keyboard/keycode.go | 534 ++++ emb/machine/_usb/hid/mouse/mouse.go | 148 ++ emb/machine/_usb/msc/cbw.go | 62 + emb/machine/_usb/msc/csw/csw.go | 14 + emb/machine/_usb/msc/disk.go | 180 ++ emb/machine/_usb/msc/msc.go | 299 +++ emb/machine/_usb/msc/scsi.go | 301 +++ emb/machine/_usb/msc/scsi/scsi.go | 140 + emb/machine/_usb/msc/scsi_inquiry.go | 164 ++ emb/machine/_usb/msc/scsi_readwrite.go | 144 + emb/machine/_usb/msc/scsi_unmap.go | 91 + emb/machine/_usb/msc/setup.go | 172 ++ emb/machine/_usb/usb.go | 146 + emb/machine/adc.go | 13 + .../board_adafruit-esp32-feather-v2.go | 125 + emb/machine/board_ae_rp2040.go | 86 + emb/machine/board_arduino.go | 40 + emb/machine/board_arduino_leonardo.go | 39 + emb/machine/board_arduino_mega1280.go | 104 + emb/machine/board_arduino_mega2560.go | 165 ++ emb/machine/board_arduino_mkr1000.go | 91 + emb/machine/board_arduino_mkrwifi1010.go | 124 + emb/machine/board_arduino_nano.go | 40 + emb/machine/board_arduino_nano33.go | 135 + emb/machine/board_arduino_zero.go | 102 + emb/machine/board_atmega328p.go | 42 + emb/machine/board_atmega328pb.go | 51 + emb/machine/board_atsamd21.go | 76 + emb/machine/board_atsame54-xpro.go | 293 ++ emb/machine/board_badger2040-w.go | 95 + emb/machine/board_badger2040.go | 94 + emb/machine/board_bluemicro840.go | 80 + emb/machine/board_bluepill.go | 110 + emb/machine/board_btt_skr_pico.go | 121 + emb/machine/board_challenger_rp2040.go | 93 + emb/machine/board_circuitplay_bluefruit.go | 95 + emb/machine/board_circuitplay_express.go | 119 + emb/machine/board_clue_alpha.go | 125 + emb/machine/board_digispark.go | 19 + emb/machine/board_elecrow-rp2040-w5.go | 94 + emb/machine/board_elecrow-rp2350-w5.go | 94 + emb/machine/board_esp-c3-32s-kit.go | 40 + emb/machine/board_esp32-c3-devkit-rust-1.go | 81 + emb/machine/board_esp32-coreboard-v2.go | 88 + emb/machine/board_esp32c3-12f.go | 54 + emb/machine/board_esp32c3-supermini.go | 57 + emb/machine/board_fe310.go | 38 + emb/machine/board_feather-m0-express.go | 102 + emb/machine/board_feather-m0.go | 97 + emb/machine/board_feather-m4-can.go | 138 + emb/machine/board_feather-m4.go | 96 + emb/machine/board_feather-nrf52840-sense.go | 105 + emb/machine/board_feather-nrf52840.go | 105 + emb/machine/board_feather-stm32f405.go | 255 ++ emb/machine/board_feather_rp2040.go | 82 + emb/machine/board_gemma-m0.go | 97 + emb/machine/board_gnse.go | 88 + emb/machine/board_gopher-badge.go | 90 + emb/machine/board_grandcentral-m4.go | 267 ++ emb/machine/board_hifive1b.go | 61 + emb/machine/board_hifive1b_baremetal.go | 12 + emb/machine/board_hw-651.go | 77 + emb/machine/board_itsybitsy-m0.go | 110 + emb/machine/board_itsybitsy-m4.go | 94 + emb/machine/board_itsybitsy-nrf52840.go | 99 + emb/machine/board_k210.go | 354 +++ emb/machine/board_kb2040.go | 84 + emb/machine/board_lgt92.go | 97 + emb/machine/board_lorae5.go | 97 + emb/machine/board_m5paper.go | 112 + emb/machine/board_m5stack.go | 124 + emb/machine/board_m5stack_core2.go | 115 + emb/machine/board_m5stamp_c3.go | 51 + emb/machine/board_m5stick_c.go | 117 + emb/machine/board_macropad-rp2040.go | 87 + emb/machine/board_maixbit.go | 74 + emb/machine/board_maixbit_baremetal.go | 15 + emb/machine/board_makerfabs-esp32c3spi35.go | 103 + emb/machine/board_matrixportal-m4.go | 200 ++ emb/machine/board_mch2022.go | 28 + emb/machine/board_mdbt50qrx.go | 40 + emb/machine/board_metro-m4-airlift.go | 136 + emb/machine/board_microbit-v2.go | 119 + emb/machine/board_microbit.go | 78 + emb/machine/board_mksnanov3.go | 142 + emb/machine/board_nano-33-ble.go | 128 + emb/machine/board_nano-rp2040.go | 126 + emb/machine/board_nicenano.go | 79 + emb/machine/board_nodemcu.go | 35 + emb/machine/board_nrf51.go | 43 + emb/machine/board_nrf52840-mdk-usb-dongle.go | 48 + emb/machine/board_nrf52840-mdk.go | 43 + emb/machine/board_nrf52840.go | 55 + emb/machine/board_nrf52840_generic.go | 25 + emb/machine/board_nucleof103rb.go | 55 + emb/machine/board_nucleof722ze.go | 68 + emb/machine/board_nucleol031k6.go | 98 + emb/machine/board_nucleol432kc.go | 100 + emb/machine/board_nucleol476rg.go | 105 + emb/machine/board_nucleol552ze.go | 60 + emb/machine/board_nucleowl55jc.go | 96 + emb/machine/board_p1am-100.go | 140 + emb/machine/board_particle_argon.go | 106 + emb/machine/board_particle_boron.go | 109 + emb/machine/board_particle_xenon.go | 95 + emb/machine/board_pca10031.go | 41 + emb/machine/board_pca10040.go | 55 + emb/machine/board_pca10056.go | 65 + emb/machine/board_pca10059.go | 59 + emb/machine/board_pga2350.go | 98 + emb/machine/board_pico.go | 88 + emb/machine/board_pico2.go | 88 + emb/machine/board_pico_plus2.go | 93 + emb/machine/board_pinetime.go | 60 + emb/machine/board_pybadge.go | 162 ++ emb/machine/board_pygamer.go | 133 + emb/machine/board_pyportal.go | 165 ++ emb/machine/board_qtpy.go | 102 + emb/machine/board_qtpy_esp32c3.go | 60 + emb/machine/board_qtpy_rp2040.go | 93 + emb/machine/board_rak4631.go | 86 + emb/machine/board_reelboard.go | 74 + emb/machine/board_stm32f469disco.go | 79 + emb/machine/board_stm32f4disco.go | 106 + emb/machine/board_swan.go | 62 + emb/machine/board_teensy36.go | 103 + emb/machine/board_teensy40.go | 390 +++ emb/machine/board_teensy41.go | 319 +++ emb/machine/board_thingplus_rp2040.go | 103 + emb/machine/board_thumby.go | 76 + emb/machine/board_tiny2350.go | 82 + emb/machine/board_trinket.go | 88 + emb/machine/board_trinkey_qt2040.go | 62 + emb/machine/board_tufty2040.go | 85 + emb/machine/board_waveshare-rp2040-zero.go | 104 + emb/machine/board_waveshare_rp2040_tiny.go | 121 + emb/machine/board_wioterminal.go | 432 +++ emb/machine/board_x9pro.go | 30 + emb/machine/board_xiao-ble.go | 111 + emb/machine/board_xiao-esp32c3.go | 53 + emb/machine/board_xiao-rp2040.go | 90 + emb/machine/board_xiao.go | 103 + emb/machine/buffer.go | 50 + emb/machine/buffer_atmega.go | 5 + emb/machine/buffer_generic.go | 5 + emb/machine/deviceid.go | 17 + emb/machine/flash.go | 77 + emb/machine/i2c.go | 92 + emb/machine/i2s.go | 80 + emb/machine/machine.go | 64 + emb/machine/machine_atmega.go | 340 +++ emb/machine/machine_atmega1280.go | 937 +++++++ emb/machine/machine_atmega1284p.go | 81 + emb/machine/machine_atmega2560.go | 141 + emb/machine/machine_atmega328.go | 548 ++++ emb/machine/machine_atmega328p.go | 60 + emb/machine/machine_atmega328pb.go | 119 + emb/machine/machine_atmega32u4.go | 76 + emb/machine/machine_atsam.go | 31 + emb/machine/machine_atsamd21.go | 2090 +++++++++++++++ emb/machine/machine_atsamd21_simulator.go | 45 + emb/machine/machine_atsamd21_usb.go | 664 +++++ emb/machine/machine_atsamd21e18.go | 359 +++ emb/machine/machine_atsamd21g18.go | 606 +++++ emb/machine/machine_atsamd51.go | 2358 +++++++++++++++++ emb/machine/machine_atsamd51_usb.go | 495 ++++ emb/machine/machine_atsamd51g19.go | 98 + emb/machine/machine_atsamd51j19.go | 110 + emb/machine/machine_atsamd51j20.go | 110 + emb/machine/machine_atsamd51p19.go | 124 + emb/machine/machine_atsamd51p20.go | 124 + emb/machine/machine_atsame51j19.go | 110 + emb/machine/machine_atsame54p20.go | 124 + emb/machine/machine_atsame5x_can.go | 478 ++++ emb/machine/machine_attiny1616.go | 52 + emb/machine/machine_attiny85.go | 23 + emb/machine/machine_avr.go | 150 ++ emb/machine/machine_avrtiny.go | 62 + emb/machine/machine_cortexm.go | 10 + emb/machine/machine_esp32.go | 545 ++++ emb/machine/machine_esp32_i2c.go | 412 +++ emb/machine/machine_esp32c3.go | 637 +++++ emb/machine/machine_esp32c3_i2c.go | 353 +++ emb/machine/machine_esp32c3_spi.go | 323 +++ emb/machine/machine_esp8266.go | 194 ++ emb/machine/machine_fe310.go | 370 +++ emb/machine/machine_gameboyadvance.go | 64 + emb/machine/machine_generic.go | 366 +++ emb/machine/machine_generic_peripherals.go | 14 + emb/machine/machine_k210.go | 661 +++++ emb/machine/machine_mimxrt1062.go | 1064 ++++++++ emb/machine/machine_mimxrt1062_i2c.go | 642 +++++ emb/machine/machine_mimxrt1062_spi.go | 254 ++ emb/machine/machine_mimxrt1062_uart.go | 303 +++ emb/machine/machine_nrf.go | 449 ++++ emb/machine/machine_nrf51.go | 278 ++ emb/machine/machine_nrf52.go | 71 + emb/machine/machine_nrf52833.go | 92 + emb/machine/machine_nrf52840.go | 110 + .../machine_nrf52840_enter_bootloader.go | 38 + emb/machine/machine_nrf52840_lfxtal_false.go | 5 + emb/machine/machine_nrf52840_lfxtal_true.go | 5 + emb/machine/machine_nrf52840_simulator.go | 60 + emb/machine/machine_nrf52840_usb.go | 381 +++ .../machine_nrf52840_usb_reset_bossa.go | 9 + .../machine_nrf52840_usb_reset_none.go | 8 + emb/machine/machine_nrf52840_usb_reset_uf2.go | 9 + emb/machine/machine_nrf528xx.go | 259 ++ emb/machine/machine_nrf52xxx.go | 546 ++++ emb/machine/machine_nrf5x.go | 126 + emb/machine/machine_nrf_bare.go | 9 + emb/machine/machine_nrf_sd.go | 59 + emb/machine/machine_nxpmk66f18.go | 291 ++ emb/machine/machine_nxpmk66f18_uart.go | 309 +++ emb/machine/machine_rp2.go | 137 + emb/machine/machine_rp2040_rom.go | 254 ++ emb/machine/machine_rp2040_rtc.go | 240 ++ emb/machine/machine_rp2040_simulator.go | 96 + emb/machine/machine_rp2040_usb.go | 148 ++ ...e_rp2040_usb_fix_usb_device_enumeration.go | 120 + emb/machine/machine_rp2350_rom.go | 560 ++++ emb/machine/machine_rp2350_usb.go | 151 ++ emb/machine/machine_rp2_2040.go | 223 ++ emb/machine/machine_rp2_2350.go | 224 ++ emb/machine/machine_rp2_2350a.go | 14 + emb/machine/machine_rp2_2350b.go | 48 + emb/machine/machine_rp2_adc.go | 114 + emb/machine/machine_rp2_clocks.go | 236 ++ emb/machine/machine_rp2_flash.go | 111 + emb/machine/machine_rp2_gpio.go | 282 ++ emb/machine/machine_rp2_i2c.go | 639 +++++ emb/machine/machine_rp2_pins.go | 37 + emb/machine/machine_rp2_pll.go | 279 ++ emb/machine/machine_rp2_pwm.go | 420 +++ emb/machine/machine_rp2_resets.go | 31 + emb/machine/machine_rp2_rng.go | 41 + emb/machine/machine_rp2_spi.go | 404 +++ emb/machine/machine_rp2_sync.go | 39 + emb/machine/machine_rp2_timer.go | 103 + emb/machine/machine_rp2_uart.go | 159 ++ emb/machine/machine_rp2_usb.go | 304 +++ emb/machine/machine_rp2_watchdog.go | 62 + emb/machine/machine_rp2_xosc.go | 47 + emb/machine/machine_stm32.go | 104 + emb/machine/machine_stm32_adc_f1.go | 90 + emb/machine/machine_stm32_adc_f4.go | 114 + emb/machine/machine_stm32_exti_afio.go | 27 + emb/machine/machine_stm32_exti_exti.go | 26 + emb/machine/machine_stm32_exti_syscfg.go | 27 + .../machine_stm32_exti_syscfg_noenable.go | 26 + emb/machine/machine_stm32_flash.go | 115 + emb/machine/machine_stm32_gpio_reva.go | 92 + emb/machine/machine_stm32_gpio_revb.go | 79 + emb/machine/machine_stm32_gpio_revb_mp.go | 86 + emb/machine/machine_stm32_i2c_reva.go | 436 +++ emb/machine/machine_stm32_i2c_revb.go | 378 +++ emb/machine/machine_stm32_iwdg.go | 66 + emb/machine/machine_stm32_moder_gpio.go | 164 ++ emb/machine/machine_stm32_rng.go | 35 + emb/machine/machine_stm32_spi.go | 147 + emb/machine/machine_stm32_tim.go | 340 +++ emb/machine/machine_stm32_tim_moder.go | 10 + emb/machine/machine_stm32_uart.go | 85 + emb/machine/machine_stm32f103.go | 739 ++++++ emb/machine/machine_stm32f4.go | 936 +++++++ emb/machine/machine_stm32f40x.go | 13 + emb/machine/machine_stm32f469.go | 13 + emb/machine/machine_stm32f7.go | 725 +++++ emb/machine/machine_stm32f7x2.go | 70 + emb/machine/machine_stm32l0.go | 317 +++ emb/machine/machine_stm32l0x1.go | 197 ++ emb/machine/machine_stm32l0x2.go | 259 ++ emb/machine/machine_stm32l4.go | 650 +++++ emb/machine/machine_stm32l4x2.go | 36 + emb/machine/machine_stm32l4x5.go | 36 + emb/machine/machine_stm32l4x6.go | 36 + emb/machine/machine_stm32l5.go | 559 ++++ emb/machine/machine_stm32l5x2.go | 68 + emb/machine/machine_stm32wlx.go | 553 ++++ emb/machine/machine_tkey.go | 234 ++ emb/machine/machine_tkey_rom.go | 59 + emb/machine/pdm.go | 7 + emb/machine/pwm.go | 21 + emb/machine/runtime.go | 13 + emb/machine/serial-none.go | 10 + emb/machine/serial-rtt.go | 150 ++ emb/machine/serial-uart.go | 10 + emb/machine/serial-usb.go | 10 + emb/machine/serial.go | 48 + emb/machine/spi.go | 29 + emb/machine/spi_tx.go | 61 + emb/machine/uart.go | 106 + emb/machine/usb.go | 338 +++ emb/machine/virt.go | 189 ++ emb/machine/watchdog.go | 34 + emb/runtime/volatile/bitband_nxpmk66f18.go | 78 + emb/runtime/volatile/register.go | 254 ++ emb/runtime/volatile/volatile.go | 40 + 329 files changed, 53820 insertions(+) create mode 100644 emb/machine/_usb/adc/doc.go create mode 100644 emb/machine/_usb/adc/midi/buffer.go create mode 100644 emb/machine/_usb/adc/midi/messages.go create mode 100644 emb/machine/_usb/adc/midi/midi.go create mode 100644 emb/machine/_usb/adc/midi/notes.go create mode 100644 emb/machine/_usb/cdc/buffer.go create mode 100644 emb/machine/_usb/cdc/cdc.go create mode 100644 emb/machine/_usb/cdc/doc.go create mode 100644 emb/machine/_usb/cdc/usbcdc.go create mode 100644 emb/machine/_usb/config.go create mode 100644 emb/machine/_usb/descriptor/cdc.go create mode 100644 emb/machine/_usb/descriptor/classspecific.go create mode 100644 emb/machine/_usb/descriptor/configuration.go create mode 100644 emb/machine/_usb/descriptor/descriptor.go create mode 100644 emb/machine/_usb/descriptor/device.go create mode 100644 emb/machine/_usb/descriptor/doc.go create mode 100644 emb/machine/_usb/descriptor/endpoint.go create mode 100644 emb/machine/_usb/descriptor/hid.go create mode 100644 emb/machine/_usb/descriptor/hidreport.go create mode 100644 emb/machine/_usb/descriptor/interface.go create mode 100644 emb/machine/_usb/descriptor/interfaceassociation.go create mode 100644 emb/machine/_usb/descriptor/joystick.go create mode 100644 emb/machine/_usb/descriptor/midi.go create mode 100644 emb/machine/_usb/descriptor/msc.go create mode 100644 emb/machine/_usb/doc.go create mode 100644 emb/machine/_usb/hid/buffer.go create mode 100644 emb/machine/_usb/hid/doc.go create mode 100644 emb/machine/_usb/hid/hid.go create mode 100644 emb/machine/_usb/hid/joystick/joystick.go create mode 100644 emb/machine/_usb/hid/joystick/state.go create mode 100644 emb/machine/_usb/hid/keyboard/keyboard.go create mode 100644 emb/machine/_usb/hid/keyboard/keycode.go create mode 100644 emb/machine/_usb/hid/mouse/mouse.go create mode 100644 emb/machine/_usb/msc/cbw.go create mode 100644 emb/machine/_usb/msc/csw/csw.go create mode 100644 emb/machine/_usb/msc/disk.go create mode 100644 emb/machine/_usb/msc/msc.go create mode 100644 emb/machine/_usb/msc/scsi.go create mode 100644 emb/machine/_usb/msc/scsi/scsi.go create mode 100644 emb/machine/_usb/msc/scsi_inquiry.go create mode 100644 emb/machine/_usb/msc/scsi_readwrite.go create mode 100644 emb/machine/_usb/msc/scsi_unmap.go create mode 100644 emb/machine/_usb/msc/setup.go create mode 100644 emb/machine/_usb/usb.go create mode 100644 emb/machine/adc.go create mode 100644 emb/machine/board_adafruit-esp32-feather-v2.go create mode 100644 emb/machine/board_ae_rp2040.go create mode 100644 emb/machine/board_arduino.go create mode 100644 emb/machine/board_arduino_leonardo.go create mode 100644 emb/machine/board_arduino_mega1280.go create mode 100644 emb/machine/board_arduino_mega2560.go create mode 100644 emb/machine/board_arduino_mkr1000.go create mode 100644 emb/machine/board_arduino_mkrwifi1010.go create mode 100644 emb/machine/board_arduino_nano.go create mode 100644 emb/machine/board_arduino_nano33.go create mode 100644 emb/machine/board_arduino_zero.go create mode 100644 emb/machine/board_atmega328p.go create mode 100644 emb/machine/board_atmega328pb.go create mode 100644 emb/machine/board_atsamd21.go create mode 100644 emb/machine/board_atsame54-xpro.go create mode 100644 emb/machine/board_badger2040-w.go create mode 100644 emb/machine/board_badger2040.go create mode 100644 emb/machine/board_bluemicro840.go create mode 100644 emb/machine/board_bluepill.go create mode 100644 emb/machine/board_btt_skr_pico.go create mode 100644 emb/machine/board_challenger_rp2040.go create mode 100644 emb/machine/board_circuitplay_bluefruit.go create mode 100644 emb/machine/board_circuitplay_express.go create mode 100644 emb/machine/board_clue_alpha.go create mode 100644 emb/machine/board_digispark.go create mode 100644 emb/machine/board_elecrow-rp2040-w5.go create mode 100644 emb/machine/board_elecrow-rp2350-w5.go create mode 100644 emb/machine/board_esp-c3-32s-kit.go create mode 100644 emb/machine/board_esp32-c3-devkit-rust-1.go create mode 100644 emb/machine/board_esp32-coreboard-v2.go create mode 100644 emb/machine/board_esp32c3-12f.go create mode 100644 emb/machine/board_esp32c3-supermini.go create mode 100644 emb/machine/board_fe310.go create mode 100644 emb/machine/board_feather-m0-express.go create mode 100644 emb/machine/board_feather-m0.go create mode 100644 emb/machine/board_feather-m4-can.go create mode 100644 emb/machine/board_feather-m4.go create mode 100644 emb/machine/board_feather-nrf52840-sense.go create mode 100644 emb/machine/board_feather-nrf52840.go create mode 100644 emb/machine/board_feather-stm32f405.go create mode 100644 emb/machine/board_feather_rp2040.go create mode 100644 emb/machine/board_gemma-m0.go create mode 100644 emb/machine/board_gnse.go create mode 100644 emb/machine/board_gopher-badge.go create mode 100644 emb/machine/board_grandcentral-m4.go create mode 100644 emb/machine/board_hifive1b.go create mode 100644 emb/machine/board_hifive1b_baremetal.go create mode 100644 emb/machine/board_hw-651.go create mode 100644 emb/machine/board_itsybitsy-m0.go create mode 100644 emb/machine/board_itsybitsy-m4.go create mode 100644 emb/machine/board_itsybitsy-nrf52840.go create mode 100644 emb/machine/board_k210.go create mode 100644 emb/machine/board_kb2040.go create mode 100644 emb/machine/board_lgt92.go create mode 100644 emb/machine/board_lorae5.go create mode 100644 emb/machine/board_m5paper.go create mode 100644 emb/machine/board_m5stack.go create mode 100644 emb/machine/board_m5stack_core2.go create mode 100644 emb/machine/board_m5stamp_c3.go create mode 100644 emb/machine/board_m5stick_c.go create mode 100644 emb/machine/board_macropad-rp2040.go create mode 100644 emb/machine/board_maixbit.go create mode 100644 emb/machine/board_maixbit_baremetal.go create mode 100644 emb/machine/board_makerfabs-esp32c3spi35.go create mode 100644 emb/machine/board_matrixportal-m4.go create mode 100644 emb/machine/board_mch2022.go create mode 100644 emb/machine/board_mdbt50qrx.go create mode 100644 emb/machine/board_metro-m4-airlift.go create mode 100644 emb/machine/board_microbit-v2.go create mode 100644 emb/machine/board_microbit.go create mode 100644 emb/machine/board_mksnanov3.go create mode 100644 emb/machine/board_nano-33-ble.go create mode 100644 emb/machine/board_nano-rp2040.go create mode 100644 emb/machine/board_nicenano.go create mode 100644 emb/machine/board_nodemcu.go create mode 100644 emb/machine/board_nrf51.go create mode 100644 emb/machine/board_nrf52840-mdk-usb-dongle.go create mode 100644 emb/machine/board_nrf52840-mdk.go create mode 100644 emb/machine/board_nrf52840.go create mode 100644 emb/machine/board_nrf52840_generic.go create mode 100644 emb/machine/board_nucleof103rb.go create mode 100644 emb/machine/board_nucleof722ze.go create mode 100644 emb/machine/board_nucleol031k6.go create mode 100644 emb/machine/board_nucleol432kc.go create mode 100644 emb/machine/board_nucleol476rg.go create mode 100644 emb/machine/board_nucleol552ze.go create mode 100644 emb/machine/board_nucleowl55jc.go create mode 100644 emb/machine/board_p1am-100.go create mode 100644 emb/machine/board_particle_argon.go create mode 100644 emb/machine/board_particle_boron.go create mode 100644 emb/machine/board_particle_xenon.go create mode 100644 emb/machine/board_pca10031.go create mode 100644 emb/machine/board_pca10040.go create mode 100644 emb/machine/board_pca10056.go create mode 100644 emb/machine/board_pca10059.go create mode 100644 emb/machine/board_pga2350.go create mode 100644 emb/machine/board_pico.go create mode 100644 emb/machine/board_pico2.go create mode 100644 emb/machine/board_pico_plus2.go create mode 100644 emb/machine/board_pinetime.go create mode 100644 emb/machine/board_pybadge.go create mode 100644 emb/machine/board_pygamer.go create mode 100644 emb/machine/board_pyportal.go create mode 100644 emb/machine/board_qtpy.go create mode 100644 emb/machine/board_qtpy_esp32c3.go create mode 100644 emb/machine/board_qtpy_rp2040.go create mode 100644 emb/machine/board_rak4631.go create mode 100644 emb/machine/board_reelboard.go create mode 100644 emb/machine/board_stm32f469disco.go create mode 100644 emb/machine/board_stm32f4disco.go create mode 100644 emb/machine/board_swan.go create mode 100644 emb/machine/board_teensy36.go create mode 100644 emb/machine/board_teensy40.go create mode 100644 emb/machine/board_teensy41.go create mode 100644 emb/machine/board_thingplus_rp2040.go create mode 100644 emb/machine/board_thumby.go create mode 100644 emb/machine/board_tiny2350.go create mode 100644 emb/machine/board_trinket.go create mode 100644 emb/machine/board_trinkey_qt2040.go create mode 100644 emb/machine/board_tufty2040.go create mode 100644 emb/machine/board_waveshare-rp2040-zero.go create mode 100644 emb/machine/board_waveshare_rp2040_tiny.go create mode 100644 emb/machine/board_wioterminal.go create mode 100644 emb/machine/board_x9pro.go create mode 100644 emb/machine/board_xiao-ble.go create mode 100644 emb/machine/board_xiao-esp32c3.go create mode 100644 emb/machine/board_xiao-rp2040.go create mode 100644 emb/machine/board_xiao.go create mode 100644 emb/machine/buffer.go create mode 100644 emb/machine/buffer_atmega.go create mode 100644 emb/machine/buffer_generic.go create mode 100644 emb/machine/deviceid.go create mode 100644 emb/machine/flash.go create mode 100644 emb/machine/i2c.go create mode 100644 emb/machine/i2s.go create mode 100644 emb/machine/machine.go create mode 100644 emb/machine/machine_atmega.go create mode 100644 emb/machine/machine_atmega1280.go create mode 100644 emb/machine/machine_atmega1284p.go create mode 100644 emb/machine/machine_atmega2560.go create mode 100644 emb/machine/machine_atmega328.go create mode 100644 emb/machine/machine_atmega328p.go create mode 100644 emb/machine/machine_atmega328pb.go create mode 100644 emb/machine/machine_atmega32u4.go create mode 100644 emb/machine/machine_atsam.go create mode 100644 emb/machine/machine_atsamd21.go create mode 100644 emb/machine/machine_atsamd21_simulator.go create mode 100644 emb/machine/machine_atsamd21_usb.go create mode 100644 emb/machine/machine_atsamd21e18.go create mode 100644 emb/machine/machine_atsamd21g18.go create mode 100644 emb/machine/machine_atsamd51.go create mode 100644 emb/machine/machine_atsamd51_usb.go create mode 100644 emb/machine/machine_atsamd51g19.go create mode 100644 emb/machine/machine_atsamd51j19.go create mode 100644 emb/machine/machine_atsamd51j20.go create mode 100644 emb/machine/machine_atsamd51p19.go create mode 100644 emb/machine/machine_atsamd51p20.go create mode 100644 emb/machine/machine_atsame51j19.go create mode 100644 emb/machine/machine_atsame54p20.go create mode 100644 emb/machine/machine_atsame5x_can.go create mode 100644 emb/machine/machine_attiny1616.go create mode 100644 emb/machine/machine_attiny85.go create mode 100644 emb/machine/machine_avr.go create mode 100644 emb/machine/machine_avrtiny.go create mode 100644 emb/machine/machine_cortexm.go create mode 100644 emb/machine/machine_esp32.go create mode 100644 emb/machine/machine_esp32_i2c.go create mode 100644 emb/machine/machine_esp32c3.go create mode 100644 emb/machine/machine_esp32c3_i2c.go create mode 100644 emb/machine/machine_esp32c3_spi.go create mode 100644 emb/machine/machine_esp8266.go create mode 100644 emb/machine/machine_fe310.go create mode 100644 emb/machine/machine_gameboyadvance.go create mode 100644 emb/machine/machine_generic.go create mode 100644 emb/machine/machine_generic_peripherals.go create mode 100644 emb/machine/machine_k210.go create mode 100644 emb/machine/machine_mimxrt1062.go create mode 100644 emb/machine/machine_mimxrt1062_i2c.go create mode 100644 emb/machine/machine_mimxrt1062_spi.go create mode 100644 emb/machine/machine_mimxrt1062_uart.go create mode 100644 emb/machine/machine_nrf.go create mode 100644 emb/machine/machine_nrf51.go create mode 100644 emb/machine/machine_nrf52.go create mode 100644 emb/machine/machine_nrf52833.go create mode 100644 emb/machine/machine_nrf52840.go create mode 100644 emb/machine/machine_nrf52840_enter_bootloader.go create mode 100644 emb/machine/machine_nrf52840_lfxtal_false.go create mode 100644 emb/machine/machine_nrf52840_lfxtal_true.go create mode 100644 emb/machine/machine_nrf52840_simulator.go create mode 100644 emb/machine/machine_nrf52840_usb.go create mode 100644 emb/machine/machine_nrf52840_usb_reset_bossa.go create mode 100644 emb/machine/machine_nrf52840_usb_reset_none.go create mode 100644 emb/machine/machine_nrf52840_usb_reset_uf2.go create mode 100644 emb/machine/machine_nrf528xx.go create mode 100644 emb/machine/machine_nrf52xxx.go create mode 100644 emb/machine/machine_nrf5x.go create mode 100644 emb/machine/machine_nrf_bare.go create mode 100644 emb/machine/machine_nrf_sd.go create mode 100644 emb/machine/machine_nxpmk66f18.go create mode 100644 emb/machine/machine_nxpmk66f18_uart.go create mode 100644 emb/machine/machine_rp2.go create mode 100644 emb/machine/machine_rp2040_rom.go create mode 100644 emb/machine/machine_rp2040_rtc.go create mode 100644 emb/machine/machine_rp2040_simulator.go create mode 100644 emb/machine/machine_rp2040_usb.go create mode 100644 emb/machine/machine_rp2040_usb_fix_usb_device_enumeration.go create mode 100644 emb/machine/machine_rp2350_rom.go create mode 100644 emb/machine/machine_rp2350_usb.go create mode 100644 emb/machine/machine_rp2_2040.go create mode 100644 emb/machine/machine_rp2_2350.go create mode 100644 emb/machine/machine_rp2_2350a.go create mode 100644 emb/machine/machine_rp2_2350b.go create mode 100644 emb/machine/machine_rp2_adc.go create mode 100644 emb/machine/machine_rp2_clocks.go create mode 100644 emb/machine/machine_rp2_flash.go create mode 100644 emb/machine/machine_rp2_gpio.go create mode 100644 emb/machine/machine_rp2_i2c.go create mode 100644 emb/machine/machine_rp2_pins.go create mode 100644 emb/machine/machine_rp2_pll.go create mode 100644 emb/machine/machine_rp2_pwm.go create mode 100644 emb/machine/machine_rp2_resets.go create mode 100644 emb/machine/machine_rp2_rng.go create mode 100644 emb/machine/machine_rp2_spi.go create mode 100644 emb/machine/machine_rp2_sync.go create mode 100644 emb/machine/machine_rp2_timer.go create mode 100644 emb/machine/machine_rp2_uart.go create mode 100644 emb/machine/machine_rp2_usb.go create mode 100644 emb/machine/machine_rp2_watchdog.go create mode 100644 emb/machine/machine_rp2_xosc.go create mode 100644 emb/machine/machine_stm32.go create mode 100644 emb/machine/machine_stm32_adc_f1.go create mode 100644 emb/machine/machine_stm32_adc_f4.go create mode 100644 emb/machine/machine_stm32_exti_afio.go create mode 100644 emb/machine/machine_stm32_exti_exti.go create mode 100644 emb/machine/machine_stm32_exti_syscfg.go create mode 100644 emb/machine/machine_stm32_exti_syscfg_noenable.go create mode 100644 emb/machine/machine_stm32_flash.go create mode 100644 emb/machine/machine_stm32_gpio_reva.go create mode 100644 emb/machine/machine_stm32_gpio_revb.go create mode 100644 emb/machine/machine_stm32_gpio_revb_mp.go create mode 100644 emb/machine/machine_stm32_i2c_reva.go create mode 100644 emb/machine/machine_stm32_i2c_revb.go create mode 100644 emb/machine/machine_stm32_iwdg.go create mode 100644 emb/machine/machine_stm32_moder_gpio.go create mode 100644 emb/machine/machine_stm32_rng.go create mode 100644 emb/machine/machine_stm32_spi.go create mode 100644 emb/machine/machine_stm32_tim.go create mode 100644 emb/machine/machine_stm32_tim_moder.go create mode 100644 emb/machine/machine_stm32_uart.go create mode 100644 emb/machine/machine_stm32f103.go create mode 100644 emb/machine/machine_stm32f4.go create mode 100644 emb/machine/machine_stm32f40x.go create mode 100644 emb/machine/machine_stm32f469.go create mode 100644 emb/machine/machine_stm32f7.go create mode 100644 emb/machine/machine_stm32f7x2.go create mode 100644 emb/machine/machine_stm32l0.go create mode 100644 emb/machine/machine_stm32l0x1.go create mode 100644 emb/machine/machine_stm32l0x2.go create mode 100644 emb/machine/machine_stm32l4.go create mode 100644 emb/machine/machine_stm32l4x2.go create mode 100644 emb/machine/machine_stm32l4x5.go create mode 100644 emb/machine/machine_stm32l4x6.go create mode 100644 emb/machine/machine_stm32l5.go create mode 100644 emb/machine/machine_stm32l5x2.go create mode 100644 emb/machine/machine_stm32wlx.go create mode 100644 emb/machine/machine_tkey.go create mode 100644 emb/machine/machine_tkey_rom.go create mode 100644 emb/machine/pdm.go create mode 100644 emb/machine/pwm.go create mode 100644 emb/machine/runtime.go create mode 100644 emb/machine/serial-none.go create mode 100644 emb/machine/serial-rtt.go create mode 100644 emb/machine/serial-uart.go create mode 100644 emb/machine/serial-usb.go create mode 100644 emb/machine/serial.go create mode 100644 emb/machine/spi.go create mode 100644 emb/machine/spi_tx.go create mode 100644 emb/machine/uart.go create mode 100644 emb/machine/usb.go create mode 100644 emb/machine/virt.go create mode 100644 emb/machine/watchdog.go create mode 100644 emb/runtime/volatile/bitband_nxpmk66f18.go create mode 100644 emb/runtime/volatile/register.go create mode 100644 emb/runtime/volatile/volatile.go diff --git a/emb/machine/_usb/adc/doc.go b/emb/machine/_usb/adc/doc.go new file mode 100644 index 00000000..beed33c5 --- /dev/null +++ b/emb/machine/_usb/adc/doc.go @@ -0,0 +1,2 @@ +// package adc is for USB Audio Device Class devices. +package adc diff --git a/emb/machine/_usb/adc/midi/buffer.go b/emb/machine/_usb/adc/midi/buffer.go new file mode 100644 index 00000000..39cab2ca --- /dev/null +++ b/emb/machine/_usb/adc/midi/buffer.go @@ -0,0 +1,52 @@ +package midi + +import ( + "runtime/volatile" +) + +const bufferSize = 128 + +// RingBuffer is ring buffer implementation inspired by post at +// https://www.embeddedrelated.com/showthread/comp.arch.embedded/77084-1.php +type RingBuffer struct { + rxbuffer [bufferSize][4]byte + head volatile.Register8 + tail volatile.Register8 +} + +// NewRingBuffer returns a new ring buffer. +func NewRingBuffer() *RingBuffer { + return &RingBuffer{} +} + +// Used returns how many bytes in buffer have been used. +func (rb *RingBuffer) Used() uint8 { + return uint8(rb.head.Get() - rb.tail.Get()) +} + +// Put stores a byte in the buffer. If the buffer is already +// full, the method will return false. +func (rb *RingBuffer) Put(val []byte) bool { + if rb.Used() != bufferSize { + rb.head.Set(rb.head.Get() + 1) + copy(rb.rxbuffer[rb.head.Get()%bufferSize][:], val) + return true + } + return false +} + +// Get returns a byte from the buffer. If the buffer is empty, +// the method will return a false as the second value. +func (rb *RingBuffer) Get() ([]byte, bool) { + if rb.Used() != 0 { + rb.tail.Set(rb.tail.Get() + 1) + return rb.rxbuffer[rb.tail.Get()%bufferSize][:], true + } + return nil, false +} + +// Clear resets the head and tail pointer to zero. +func (rb *RingBuffer) Clear() { + rb.head.Set(0) + rb.tail.Set(0) +} diff --git a/emb/machine/_usb/adc/midi/messages.go b/emb/machine/_usb/adc/midi/messages.go new file mode 100644 index 00000000..c123acb7 --- /dev/null +++ b/emb/machine/_usb/adc/midi/messages.go @@ -0,0 +1,233 @@ +package midi + +import ( + "errors" +) + +// From USB-MIDI section 4.1 "Code Index Number (CIN) Classifications" +const ( + CINSystemCommon2 = 0x2 + CINSystemCommon3 = 0x3 + CINSysExStart = 0x4 + CINSysExEnd1 = 0x5 + CINSysExEnd2 = 0x6 + CINSysExEnd3 = 0x7 + CINNoteOff = 0x8 + CINNoteOn = 0x9 + CINPoly = 0xA + CINControlChange = 0xB + CINProgramChange = 0xC + CINChannelPressure = 0xD + CINPitchBendChange = 0xE + CINSingleByte = 0xF +) + +// Standard MIDI channel messages +const ( + MsgNoteOff = 0x80 + MsgNoteOn = 0x90 + MsgPolyAftertouch = 0xA0 + MsgControlChange = 0xB0 + MsgProgramChange = 0xC0 + MsgChannelAftertouch = 0xD0 + MsgPitchBend = 0xE0 + MsgSysExStart = 0xF0 + MsgSysExEnd = 0xF7 +) + +// Standard MIDI control change messages +const ( + CCModulationWheel = 0x01 + CCBreathController = 0x02 + CCFootPedal = 0x04 + CCPortamentoTime = 0x05 + CCDataEntry = 0x06 + CCVolume = 0x07 + CCBalance = 0x08 + CCPan = 0x0A + CCExpression = 0x0B + CCEffectControl1 = 0x0C + CCEffectControl2 = 0x0D + CCGeneralPurpose1 = 0x10 + CCGeneralPurpose2 = 0x11 + CCGeneralPurpose3 = 0x12 + CCGeneralPurpose4 = 0x13 + CCBankSelect = 0x20 + CCModulationDepthRange = 0x21 + CCBreathControllerDepth = 0x22 + CCFootPedalDepth = 0x24 + CCEffectsLevel = 0x5B + CCTremeloLevel = 0x5C + CCChorusLevel = 0x5D + CCCelesteLevel = 0x5E + CCPhaserLevel = 0x5F + CCDataIncrement = 0x60 + CCDataDecrement = 0x61 + CCNRPNLSB = 0x62 + CCNRPNMSB = 0x63 + CCRPNLSB = 0x64 + CCRPNMSB = 0x65 + CCAllSoundOff = 0x78 + CCResetAllControllers = 0x79 + CCAllNotesOff = 0x7B + CCChannelVolume = 0x7F +) + +var ( + errInvalidMIDICable = errors.New("invalid MIDI cable") + errInvalidMIDIChannel = errors.New("invalid MIDI channel") + errInvalidMIDIVelocity = errors.New("invalid MIDI velocity") + errInvalidMIDIControl = errors.New("invalid MIDI control number") + errInvalidMIDIControlValue = errors.New("invalid MIDI control value") + errInvalidMIDIPatch = errors.New("invalid MIDI patch number") + errInvalidMIDIPitchBend = errors.New("invalid MIDI pitch bend value") + errInvalidMIDISysExData = errors.New("invalid MIDI SysEx data") +) + +// NoteOn sends a channel note on message. +// The cable parameter is the cable number 0-15. +// The channel parameter is the MIDI channel number 1-16. +func (m *midi) NoteOn(cable, channel uint8, note Note, velocity uint8) error { + switch { + case cable > 15: + return errInvalidMIDICable + case channel == 0 || channel > 16: + return errInvalidMIDIChannel + case velocity > 127: + return errInvalidMIDIVelocity + } + + m.msg[0], m.msg[1], m.msg[2], m.msg[3] = ((cable&0xf)<<4)|CINNoteOn, MsgNoteOn|((channel-1)&0xf), byte(note)&0x7f, velocity&0x7f + _, err := m.Write(m.msg[:]) + return err +} + +// NoteOff sends a channel note off message. +// The cable parameter is the cable number 0-15. +// The channel parameter is the MIDI channel number 1-16. +func (m *midi) NoteOff(cable, channel uint8, note Note, velocity uint8) error { + switch { + case cable > 15: + return errInvalidMIDICable + case channel == 0 || channel > 16: + return errInvalidMIDIChannel + case velocity > 127: + return errInvalidMIDIVelocity + } + + m.msg[0], m.msg[1], m.msg[2], m.msg[3] = ((cable&0xf)<<4)|CINNoteOff, MsgNoteOff|((channel-1)&0xf), byte(note)&0x7f, velocity&0x7f + _, err := m.Write(m.msg[:]) + return err +} + +// ControlChange sends a channel continuous controller message. +// The cable parameter is the cable number 0-15. +// The channel parameter is the MIDI channel number 1-16. +// The control parameter is the controller number 0-127. +// The value parameter is the controller value 0-127. +func (m *midi) ControlChange(cable, channel, control, value uint8) error { + switch { + case cable > 15: + return errInvalidMIDICable + case channel == 0 || channel > 16: + return errInvalidMIDIChannel + case control > 127: + return errInvalidMIDIControl + case value > 127: + return errInvalidMIDIControlValue + } + + m.msg[0], m.msg[1], m.msg[2], m.msg[3] = ((cable&0xf)<<4)|CINControlChange, MsgControlChange|((channel-1)&0xf), control&0x7f, value&0x7f + _, err := m.Write(m.msg[:]) + return err +} + +// ProgramChange sends a channel program change message. +// The cable parameter is the cable number 0-15. +// The channel parameter is the MIDI channel number 1-16. +// The patch parameter is the program number 0-127. +func (m *midi) ProgramChange(cable, channel uint8, patch uint8) error { + switch { + case cable > 15: + return errInvalidMIDICable + case channel == 0 || channel > 16: + return errInvalidMIDIChannel + case patch > 127: + return errInvalidMIDIPatch + } + + m.msg[0], m.msg[1], m.msg[2] = ((cable&0xf)<<4)|CINProgramChange, MsgProgramChange|((channel-1)&0xf), patch&0x7f + _, err := m.Write(m.msg[:3]) + return err +} + +// PitchBend sends a channel pitch bend message. +// The cable parameter is the cable number 0-15. +// The channel parameter is the MIDI channel number 1-16. +// The bend parameter is the 14-bit pitch bend value (maximum 0x3FFF). +// Setting bend above 0x2000 (up to 0x3FFF) will increase the pitch. +// Setting bend below 0x2000 (down to 0x0000) will decrease the pitch. +func (m *midi) PitchBend(cable, channel uint8, bend uint16) error { + switch { + case cable > 15: + return errInvalidMIDICable + case channel == 0 || channel > 16: + return errInvalidMIDIChannel + case bend > 0x3FFF: + return errInvalidMIDIPitchBend + } + + m.msg[0], m.msg[1], m.msg[2], m.msg[3] = ((cable&0xf)<<4)|CINPitchBendChange, MsgPitchBend|((channel-1)&0xf), byte(bend&0x7f), byte(bend>>7)&0x7f + _, err := m.Write(m.msg[:]) + return err +} + +// SysEx sends a System Exclusive message. +// The cable parameter is the cable number 0-15. +// The data parameter is a slice with the data to send. +// It needs to start with the manufacturer ID, which is either +// 1 or 3 bytes in length. +// The data slice should not include the SysEx start (0xF0) or +// end (0xF7) bytes, only the data in between. +func (m *midi) SysEx(cable uint8, data []byte) error { + switch { + case cable > 15: + return errInvalidMIDICable + case len(data) < 3: + return errInvalidMIDISysExData + } + + // write start + m.msg[0], m.msg[1] = ((cable&0xf)<<4)|CINSysExStart, MsgSysExStart + m.msg[2], m.msg[3] = data[0], data[1] + if _, err := m.Write(m.msg[:]); err != nil { + return err + } + + // write middle + i := 2 + for ; i < len(data)-2; i += 3 { + m.msg[0], m.msg[1] = ((cable&0xf)<<4)|CINSysExStart, data[i] + m.msg[2], m.msg[3] = data[i+1], data[i+2] + if _, err := m.Write(m.msg[:]); err != nil { + return err + } + } + // write end + switch len(data) - i { + case 2: + m.msg[0], m.msg[1] = ((cable&0xf)<<4)|CINSysExEnd3, data[i] + m.msg[2], m.msg[3] = data[i+1], MsgSysExEnd + case 1: + m.msg[0], m.msg[1] = ((cable&0xf)<<4)|CINSysExEnd2, data[i] + m.msg[2], m.msg[3] = MsgSysExEnd, 0 + case 0: + m.msg[0], m.msg[1] = ((cable&0xf)<<4)|CINSysExEnd1, MsgSysExEnd + m.msg[2], m.msg[3] = 0, 0 + } + if _, err := m.Write(m.msg[:]); err != nil { + return err + } + + return nil +} diff --git a/emb/machine/_usb/adc/midi/midi.go b/emb/machine/_usb/adc/midi/midi.go new file mode 100644 index 00000000..8ead2e92 --- /dev/null +++ b/emb/machine/_usb/adc/midi/midi.go @@ -0,0 +1,127 @@ +package midi + +import ( + "machine" + "machine/usb" + "machine/usb/descriptor" +) + +const ( + midiEndpointOut = usb.MIDI_ENDPOINT_OUT // from PC + midiEndpointIn = usb.MIDI_ENDPOINT_IN // to PC +) + +var Midi *midi + +type midi struct { + msg [4]byte + buf *RingBuffer + rxHandler func([]byte) + txHandler func() + waitTxc bool +} + +func init() { + if Midi == nil { + Midi = newMidi() + } +} + +// New returns the USB MIDI port. +// Deprecated, better to just use Port() +func New() *midi { + return Port() +} + +// Port returns the USB midi port. +func Port() *midi { + return Midi +} + +func newMidi() *midi { + m := &midi{ + buf: NewRingBuffer(), + } + machine.ConfigureUSBEndpoint(descriptor.CDCMIDI, + []usb.EndpointConfig{ + { + Index: usb.MIDI_ENDPOINT_OUT, + IsIn: false, + Type: usb.ENDPOINT_TYPE_BULK, + RxHandler: m.RxHandler, + }, + { + Index: usb.MIDI_ENDPOINT_IN, + IsIn: true, + Type: usb.ENDPOINT_TYPE_BULK, + TxHandler: m.TxHandler, + }, + }, + []usb.SetupConfig{}, + ) + return m +} + +// SetHandler is now deprecated, please use SetRxHandler(). +func (m *midi) SetHandler(rxHandler func([]byte)) { + m.SetRxHandler(rxHandler) +} + +// SetRxHandler sets the handler function for incoming MIDI messages. +func (m *midi) SetRxHandler(rxHandler func([]byte)) { + m.rxHandler = rxHandler +} + +// SetTxHandler sets the handler function for outgoing MIDI messages. +func (m *midi) SetTxHandler(txHandler func()) { + m.txHandler = txHandler +} + +func (m *midi) Write(b []byte) (n int, err error) { + s, e := 0, 0 + for s = 0; s < len(b); s += 4 { + e = s + 4 + if e > len(b) { + e = len(b) + } + + m.tx(b[s:e]) + } + return e, nil +} + +// sendUSBPacket sends a MIDIPacket. +func (m *midi) sendUSBPacket(b []byte) { + machine.SendUSBInPacket(midiEndpointIn, b) +} + +// from BulkIn +func (m *midi) TxHandler() { + if m.txHandler != nil { + m.txHandler() + } + + m.waitTxc = false + if b, ok := m.buf.Get(); ok { + m.waitTxc = true + m.sendUSBPacket(b) + } +} + +func (m *midi) tx(b []byte) { + if machine.USBDev.InitEndpointComplete { + if m.waitTxc { + m.buf.Put(b) + } else { + m.waitTxc = true + m.sendUSBPacket(b) + } + } +} + +// from BulkOut +func (m *midi) RxHandler(b []byte) { + if m.rxHandler != nil { + m.rxHandler(b) + } +} diff --git a/emb/machine/_usb/adc/midi/notes.go b/emb/machine/_usb/adc/midi/notes.go new file mode 100644 index 00000000..05fa6819 --- /dev/null +++ b/emb/machine/_usb/adc/midi/notes.go @@ -0,0 +1,108 @@ +package midi + +// Note represents a MIDI note number. For example, Note(69) is A4 or 440Hz. +type Note uint8 + +// Define all the notes in a format similar to the Tone library in the Arduino +// IDE. +const ( + A0 Note = iota + 21 // 27.5Hz + AS0 + B0 + C1 + CS1 + D1 + DS1 + E1 + F1 + FS1 + G1 + GS1 + A1 // 55Hz + AS1 + B1 + C2 + CS2 + D2 + DS2 + E2 + F2 + FS2 + G2 + GS2 + A2 // 110Hz + AS2 + B2 + C3 + CS3 + D3 + DS3 + E3 + F3 + FS3 + G3 + GS3 + A3 // 220Hz + AS3 + B3 + C4 + CS4 + D4 + DS4 + E4 + F4 + FS4 + G4 + GS4 + A4 // 440Hz + AS4 + B4 + C5 + CS5 + D5 + DS5 + E5 + F5 + FS5 + G5 + GS5 + A5 // 880Hz + AS5 + B5 + C6 + CS6 + D6 + DS6 + E6 + F6 + FS6 + G6 + GS6 + A6 // 1760Hz + AS6 + B6 + C7 + CS7 + D7 + DS7 + E7 + F7 + FS7 + G7 + GS7 + A7 // 3520Hz + AS7 + B7 + C8 + CS8 + D8 + DS8 + E8 + F8 + FS8 + G8 + GS8 + A8 // 7040Hz + AS8 + B8 +) diff --git a/emb/machine/_usb/cdc/buffer.go b/emb/machine/_usb/cdc/buffer.go new file mode 100644 index 00000000..ad5eb364 --- /dev/null +++ b/emb/machine/_usb/cdc/buffer.go @@ -0,0 +1,119 @@ +package cdc + +import ( + "runtime/volatile" +) + +const rxRingBufferSize = 128 + +// rxRingBuffer is ring buffer implementation inspired by post at +// https://www.embeddedrelated.com/showthread/comp.arch.embedded/77084-1.php +type rxRingBuffer struct { + buffer [rxRingBufferSize]volatile.Register8 + head volatile.Register8 + tail volatile.Register8 +} + +// NewRxRingBuffer returns a new ring buffer. +func NewRxRingBuffer() *rxRingBuffer { + return &rxRingBuffer{} +} + +// Used returns how many bytes in buffer have been used. +func (rb *rxRingBuffer) Used() uint8 { + return uint8(rb.head.Get() - rb.tail.Get()) +} + +// Put stores a byte in the buffer. If the buffer is already +// full, the method will return false. +func (rb *rxRingBuffer) Put(val byte) bool { + if rb.Used() != rxRingBufferSize { + rb.head.Set(rb.head.Get() + 1) + rb.buffer[rb.head.Get()%rxRingBufferSize].Set(val) + return true + } + return false +} + +// Get returns a byte from the buffer. If the buffer is empty, +// the method will return a false as the second value. +func (rb *rxRingBuffer) Get() (byte, bool) { + if rb.Used() != 0 { + rb.tail.Set(rb.tail.Get() + 1) + return rb.buffer[rb.tail.Get()%rxRingBufferSize].Get(), true + } + return 0, false +} + +// Clear resets the head and tail pointer to zero. +func (rb *rxRingBuffer) Clear() { + rb.head.Set(0) + rb.tail.Set(0) +} + +const txRingBufferSize = 8 + +// txRingBuffer is ring buffer implementation inspired by post at +// https://www.embeddedrelated.com/showthread/comp.arch.embedded/77084-1.php +type txRingBuffer struct { + buffer [txRingBufferSize]struct { + buf [64]byte + size int + } + head volatile.Register8 + tail volatile.Register8 +} + +// NewTxRingBuffer returns a new ring buffer. +func NewTxRingBuffer() *txRingBuffer { + return &txRingBuffer{} +} + +// Used returns how many bytes in buffer have been used. +func (rb *txRingBuffer) Used() uint8 { + return uint8(rb.head.Get() - rb.tail.Get()) +} + +// Put stores a byte in the buffer. If the buffer is already +// full, the method will return false. +func (rb *txRingBuffer) Put(val []byte) bool { + if rb.Used() == txRingBufferSize { + return false + } + + if rb.Used() == 0 { + rb.head.Set(rb.head.Get() + 1) + rb.buffer[rb.head.Get()%txRingBufferSize].size = 0 + } + buf := &rb.buffer[rb.head.Get()%txRingBufferSize] + + for i := 0; i < len(val); i++ { + if buf.size == 64 { + // next + // TODO: Make sure that data is not corrupted even when the buffer is full + rb.head.Set(rb.head.Get() + 1) + buf = &rb.buffer[rb.head.Get()%txRingBufferSize] + rb.buffer[rb.head.Get()%txRingBufferSize].size = 0 + } + buf.buf[buf.size] = val[i] + buf.size++ + } + return true +} + +// Get returns a byte from the buffer. If the buffer is empty, +// the method will return a false as the second value. +func (rb *txRingBuffer) Get() ([]byte, bool) { + if rb.Used() != 0 { + rb.tail.Set(rb.tail.Get() + 1) + size := rb.buffer[rb.tail.Get()%txRingBufferSize].size + return rb.buffer[rb.tail.Get()%txRingBufferSize].buf[:size], true + } + return nil, false +} + +// Clear resets the head and tail pointer to zero. +func (rb *txRingBuffer) Clear() { + rb.head.Set(0) + rb.tail.Set(0) +} diff --git a/emb/machine/_usb/cdc/cdc.go b/emb/machine/_usb/cdc/cdc.go new file mode 100644 index 00000000..f180535d --- /dev/null +++ b/emb/machine/_usb/cdc/cdc.go @@ -0,0 +1,61 @@ +package cdc + +const ( + cdcEndpointACM = 1 + cdcEndpointOut = 2 + cdcEndpointIn = 3 +) + +// New returns USBCDC struct. +func New() *USBCDC { + if USB == nil { + USB = &USBCDC{ + rxBuffer: NewRxRingBuffer(), + txBuffer: NewTxRingBuffer(), + } + } + return USB +} + +const ( + // bmRequestType + usb_REQUEST_HOSTTODEVICE = 0x00 + usb_REQUEST_DEVICETOHOST = 0x80 + usb_REQUEST_DIRECTION = 0x80 + + usb_REQUEST_STANDARD = 0x00 + usb_REQUEST_CLASS = 0x20 + usb_REQUEST_VENDOR = 0x40 + usb_REQUEST_TYPE = 0x60 + + usb_REQUEST_DEVICE = 0x00 + usb_REQUEST_INTERFACE = 0x01 + usb_REQUEST_ENDPOINT = 0x02 + usb_REQUEST_OTHER = 0x03 + usb_REQUEST_RECIPIENT = 0x1F + + usb_REQUEST_DEVICETOHOST_CLASS_INTERFACE = (usb_REQUEST_DEVICETOHOST | usb_REQUEST_CLASS | usb_REQUEST_INTERFACE) + usb_REQUEST_HOSTTODEVICE_CLASS_INTERFACE = (usb_REQUEST_HOSTTODEVICE | usb_REQUEST_CLASS | usb_REQUEST_INTERFACE) + usb_REQUEST_DEVICETOHOST_STANDARD_INTERFACE = (usb_REQUEST_DEVICETOHOST | usb_REQUEST_STANDARD | usb_REQUEST_INTERFACE) + + // CDC Class requests + usb_CDC_SET_LINE_CODING = 0x20 + usb_CDC_GET_LINE_CODING = 0x21 + usb_CDC_SET_CONTROL_LINE_STATE = 0x22 + usb_CDC_SEND_BREAK = 0x23 + + usb_CDC_V1_10 = 0x0110 + usb_CDC_COMMUNICATION_INTERFACE_CLASS = 0x02 + + usb_CDC_CALL_MANAGEMENT = 0x01 + usb_CDC_ABSTRACT_CONTROL_MODEL = 0x02 + usb_CDC_HEADER = 0x00 + usb_CDC_ABSTRACT_CONTROL_MANAGEMENT = 0x02 + usb_CDC_UNION = 0x06 + usb_CDC_CS_INTERFACE = 0x24 + usb_CDC_CS_ENDPOINT = 0x25 + usb_CDC_DATA_INTERFACE_CLASS = 0x0A + + usb_CDC_LINESTATE_DTR = 0x01 + usb_CDC_LINESTATE_RTS = 0x02 +) diff --git a/emb/machine/_usb/cdc/doc.go b/emb/machine/_usb/cdc/doc.go new file mode 100644 index 00000000..0a375e46 --- /dev/null +++ b/emb/machine/_usb/cdc/doc.go @@ -0,0 +1,2 @@ +// package cdc is for USB Communication Device Class devices. +package cdc diff --git a/emb/machine/_usb/cdc/usbcdc.go b/emb/machine/_usb/cdc/usbcdc.go new file mode 100644 index 00000000..5b5ffbf7 --- /dev/null +++ b/emb/machine/_usb/cdc/usbcdc.go @@ -0,0 +1,191 @@ +package cdc + +import ( + "errors" + "machine" + "machine/usb" + "runtime/interrupt" +) + +var ( + ErrBufferEmpty = errors.New("USB-CDC buffer empty") +) + +const cdcLineInfoSize = 7 + +type cdcLineInfo struct { + dwDTERate uint32 + bCharFormat uint8 + bParityType uint8 + bDataBits uint8 + lineState uint8 +} + +// Read from the RX buffer. +func (usbcdc *USBCDC) Read(data []byte) (n int, err error) { + // check if RX buffer is empty + size := usbcdc.Buffered() + if size == 0 { + return 0, nil + } + + // Make sure we do not read more from buffer than the data slice can hold. + if len(data) < size { + size = len(data) + } + + // only read number of bytes used from buffer + for i := 0; i < size; i++ { + v, _ := usbcdc.ReadByte() + data[i] = v + } + + return size, nil +} + +// ReadByte reads a single byte from the RX buffer. +// If there is no data in the buffer, returns an error. +func (usbcdc *USBCDC) ReadByte() (byte, error) { + // check if RX buffer is empty + buf, ok := usbcdc.rxBuffer.Get() + if !ok { + return 0, ErrBufferEmpty + } + return buf, nil +} + +// Buffered returns the number of bytes currently stored in the RX buffer. +func (usbcdc *USBCDC) Buffered() int { + return int(usbcdc.rxBuffer.Used()) +} + +// Receive handles adding data to the UART's data buffer. +// Usually called by the IRQ handler for a machine. +func (usbcdc *USBCDC) Receive(data byte) { + usbcdc.rxBuffer.Put(data) +} + +// USBCDC is the USB CDC aka serial over USB interface. +type USBCDC struct { + rxBuffer *rxRingBuffer + txBuffer *txRingBuffer + waitTxc bool +} + +var ( + // USB is a USB CDC interface. + USB *USBCDC + + usbLineInfo = cdcLineInfo{115200, 0x00, 0x00, 0x08, 0x00} +) + +// Configure the USB CDC interface. The config is here for compatibility with the UART interface. +func (usbcdc *USBCDC) Configure(config machine.UARTConfig) error { + return nil +} + +// Flush flushes buffered data. +func (usbcdc *USBCDC) Flush() { + mask := interrupt.Disable() + if b, ok := usbcdc.txBuffer.Get(); ok { + machine.SendUSBInPacket(cdcEndpointIn, b) + } else { + usbcdc.waitTxc = false + } + interrupt.Restore(mask) +} + +// Write data to the USBCDC. +func (usbcdc *USBCDC) Write(data []byte) (n int, err error) { + if usbLineInfo.lineState > 0 { + mask := interrupt.Disable() + usbcdc.txBuffer.Put(data) + if !usbcdc.waitTxc { + usbcdc.waitTxc = true + usbcdc.Flush() + } + interrupt.Restore(mask) + } + return len(data), nil +} + +// WriteByte writes a byte of data to the USB CDC interface. +func (usbcdc *USBCDC) WriteByte(c byte) error { + usbcdc.Write([]byte{c}) + return nil +} + +func (usbcdc *USBCDC) DTR() bool { + return (usbLineInfo.lineState & usb_CDC_LINESTATE_DTR) > 0 +} + +func (usbcdc *USBCDC) RTS() bool { + return (usbLineInfo.lineState & usb_CDC_LINESTATE_RTS) > 0 +} + +func cdcCallbackRx(b []byte) { + for i := range b { + USB.Receive(b[i]) + } +} + +var cdcSetupBuff [cdcLineInfoSize]byte + +func cdcSetup(setup usb.Setup) bool { + if setup.BmRequestType == usb_REQUEST_DEVICETOHOST_CLASS_INTERFACE { + if setup.BRequest == usb_CDC_GET_LINE_CODING { + cdcSetupBuff[0] = byte(usbLineInfo.dwDTERate) + cdcSetupBuff[1] = byte(usbLineInfo.dwDTERate >> 8) + cdcSetupBuff[2] = byte(usbLineInfo.dwDTERate >> 16) + cdcSetupBuff[3] = byte(usbLineInfo.dwDTERate >> 24) + cdcSetupBuff[4] = byte(usbLineInfo.bCharFormat) + cdcSetupBuff[5] = byte(usbLineInfo.bParityType) + cdcSetupBuff[6] = byte(usbLineInfo.bDataBits) + + machine.SendUSBInPacket(0, cdcSetupBuff[:]) + return true + } + } + + if setup.BmRequestType == usb_REQUEST_HOSTTODEVICE_CLASS_INTERFACE { + if setup.BRequest == usb_CDC_SET_LINE_CODING { + b, err := machine.ReceiveUSBControlPacket() + if err != nil { + return false + } + + usbLineInfo.dwDTERate = uint32(b[0]) | uint32(b[1])<<8 | uint32(b[2])<<16 | uint32(b[3])<<24 + usbLineInfo.bCharFormat = b[4] + usbLineInfo.bParityType = b[5] + usbLineInfo.bDataBits = b[6] + } + + if setup.BRequest == usb_CDC_SET_CONTROL_LINE_STATE { + usbLineInfo.lineState = setup.WValueL + } + + if setup.BRequest == usb_CDC_SET_LINE_CODING || setup.BRequest == usb_CDC_SET_CONTROL_LINE_STATE { + // auto-reset into the bootloader + if usbLineInfo.dwDTERate == 1200 && usbLineInfo.lineState&usb_CDC_LINESTATE_DTR == 0 { + machine.EnterBootloader() + } else { + // TODO: cancel any reset + } + machine.SendZlp() + } + + if setup.BRequest == usb_CDC_SEND_BREAK { + // TODO: something with this value? + // breakValue = ((uint16_t)setup.wValueH << 8) | setup.wValueL; + // return false; + machine.SendZlp() + } + return true + } + return false +} + +func EnableUSBCDC() { + machine.USBCDC = New() + machine.EnableCDC(USB.Flush, cdcCallbackRx, cdcSetup) +} diff --git a/emb/machine/_usb/config.go b/emb/machine/_usb/config.go new file mode 100644 index 00000000..47cce9b2 --- /dev/null +++ b/emb/machine/_usb/config.go @@ -0,0 +1,16 @@ +package usb + +type EndpointConfig struct { + Index uint8 + IsIn bool + TxHandler func() + RxHandler func([]byte) + DelayRxHandler func([]byte) bool + StallHandler func(Setup) bool + Type uint8 +} + +type SetupConfig struct { + Index uint8 + Handler func(Setup) bool +} diff --git a/emb/machine/_usb/descriptor/cdc.go b/emb/machine/_usb/descriptor/cdc.go new file mode 100644 index 00000000..ec72186e --- /dev/null +++ b/emb/machine/_usb/descriptor/cdc.go @@ -0,0 +1,168 @@ +package descriptor + +const ( + cdcFunctionalHeader = 0 + cdcFunctionalCallManagement = 0x1 + cdcFunctionalACM = 0x2 + cdcFunctionalDirect = 0x3 + cdcFunctionalRinger = 0x4 + cdcFunctionalCall = 0x5 + cdcFunctionalUnion = 0x6 + cdcFunctionalCountry = 0x7 + cdcFunctionalOperational = 0x8 + cdcFunctionalUSB = 0x9 + cdcFunctionalNetwork = 0xa + cdcFunctionalProtocol = 0xb + cdcFunctionalExtension = 0xc + cdcFunctionalMulti = 0xd + cdcFunctionalCAPI = 0xe + cdcFunctionalEthernet = 0xf + cdcFunctionalATM = 0x10 +) + +var classSpecificCDCHeader = [classSpecificTypeLen]byte{ + classSpecificTypeLen, + TypeClassSpecific, + cdcFunctionalHeader, + 0x10, // + 0x1, // +} + +var ClassSpecificCDCHeader = ClassSpecificType{ + data: classSpecificCDCHeader[:], +} + +var classSpecificCDCCallManagement = [classSpecificTypeLen]byte{ + classSpecificTypeLen, + TypeClassSpecific, + cdcFunctionalCallManagement, + 0x0, // + 0x1, // +} + +var ClassSpecificCDCCallManagement = ClassSpecificType{ + data: classSpecificCDCCallManagement[:], +} + +var classSpecificCDCACM = [classSpecificTypeLen]byte{ + 4, + TypeClassSpecific, + cdcFunctionalACM, + 0x2, // +} + +var ClassSpecificCDCACM = ClassSpecificType{ + data: classSpecificCDCACM[:], +} + +var classSpecificCDCUnion = [classSpecificTypeLen]byte{ + classSpecificTypeLen, + TypeClassSpecific, + cdcFunctionalUnion, + 0x0, // + 0x1, // +} + +var ClassSpecificCDCUnion = ClassSpecificType{ + data: classSpecificCDCUnion[:], +} + +var interfaceAssociationCDC = [interfaceAssociationTypeLen]byte{ + interfaceAssociationTypeLen, + TypeInterfaceAssociation, + 0x00, // FirstInterface + 0x02, // InterfaceCount + 0x02, // FunctionClass + 0x02, // FunctionSubClass + 0x01, // FunctionProtocol + 0x00, // Function +} + +var InterfaceAssociationCDC = InterfaceAssociationType{ + data: interfaceAssociationCDC[:], +} + +var deviceCDC = [deviceTypeLen]byte{ + deviceTypeLen, + TypeDevice, + 0x00, 0x02, // USB version + 0xef, // device class + 0x02, // device subclass + 0x01, // protocol + 0x40, // maxpacketsize + 0x86, 0x28, // vendor id + 0x2d, 0x80, // product id + 0x00, 0x01, // device + 0x01, // manufacturer + 0x02, // product + 0x03, // SerialNumber + 0x01, // NumConfigurations +} + +var DeviceCDC = DeviceType{ + data: deviceCDC[:], +} + +var configurationCDC = [configurationTypeLen]byte{ + configurationTypeLen, + TypeConfiguration, + 0x4b, 0x00, // adjust length as needed + 0x02, // number of interfaces + 0x01, // configuration value + 0x00, // index to string description + 0xa0, // attributes + 0x32, // maxpower +} + +var ConfigurationCDC = ConfigurationType{ + data: configurationCDC[:], +} + +var interfaceCDCControl = [interfaceTypeLen]byte{ + interfaceTypeLen, + TypeInterface, + 0x00, // InterfaceNumber + 0x00, // AlternateSetting + 0x01, // NumEndpoints + 0x02, // InterfaceClass + 0x02, // InterfaceSubClass + 0x01, // InterfaceProtocol + 0x00, // Interface +} + +var InterfaceCDCControl = InterfaceType{ + data: interfaceCDCControl[:], +} + +var interfaceCDCData = [interfaceTypeLen]byte{ + interfaceTypeLen, + TypeInterface, + 0x01, // InterfaceNumber + 0x00, // AlternateSetting + 0x02, // NumEndpoints + 0x0a, // InterfaceClass + 0x00, // InterfaceSubClass + 0x00, // InterfaceProtocol + 0x00, // Interface +} + +var InterfaceCDCData = InterfaceType{ + data: interfaceCDCData[:], +} + +var CDC = Descriptor{ + Device: DeviceCDC.Bytes(), + Configuration: Append([][]byte{ + ConfigurationCDC.Bytes(), + InterfaceAssociationCDC.Bytes(), + InterfaceCDCControl.Bytes(), + ClassSpecificCDCHeader.Bytes(), + ClassSpecificCDCCallManagement.Bytes(), + ClassSpecificCDCACM.Bytes(), + ClassSpecificCDCUnion.Bytes(), + EndpointEP1IN.Bytes(), + InterfaceCDCData.Bytes(), + EndpointEP2OUT.Bytes(), + EndpointEP3IN.Bytes(), + }), +} diff --git a/emb/machine/_usb/descriptor/classspecific.go b/emb/machine/_usb/descriptor/classspecific.go new file mode 100644 index 00000000..d8ea937b --- /dev/null +++ b/emb/machine/_usb/descriptor/classspecific.go @@ -0,0 +1,17 @@ +package descriptor + +const ( + classSpecificTypeLen = 5 +) + +type ClassSpecificType struct { + data []byte +} + +func (d ClassSpecificType) Bytes() []byte { + return d.data[:d.data[0]] +} + +func (d ClassSpecificType) Length(v uint8) { + d.data[0] = byte(v) +} diff --git a/emb/machine/_usb/descriptor/configuration.go b/emb/machine/_usb/descriptor/configuration.go new file mode 100644 index 00000000..efb9ab1d --- /dev/null +++ b/emb/machine/_usb/descriptor/configuration.go @@ -0,0 +1,49 @@ +package descriptor + +import ( + "internal/binary" +) + +const ( + configurationTypeLen = 9 +) + +type ConfigurationType struct { + data []byte +} + +func (d ConfigurationType) Bytes() []byte { + return d.data +} + +func (d ConfigurationType) Length(v uint8) { + d.data[0] = byte(v) +} + +func (d ConfigurationType) Type(v uint8) { + d.data[1] = byte(v) +} + +func (d ConfigurationType) TotalLength(v uint16) { + binary.LittleEndian.PutUint16(d.data[2:4], v) +} + +func (d ConfigurationType) NumInterfaces(v uint8) { + d.data[4] = byte(v) +} + +func (d ConfigurationType) ConfigurationValue(v uint8) { + d.data[5] = byte(v) +} + +func (d ConfigurationType) Configuration(v uint8) { + d.data[6] = byte(v) +} + +func (d ConfigurationType) Attributes(v uint8) { + d.data[7] = byte(v) +} + +func (d ConfigurationType) MaxPower(v uint8) { + d.data[8] = byte(v) +} diff --git a/emb/machine/_usb/descriptor/descriptor.go b/emb/machine/_usb/descriptor/descriptor.go new file mode 100644 index 00000000..852ddab6 --- /dev/null +++ b/emb/machine/_usb/descriptor/descriptor.go @@ -0,0 +1,63 @@ +package descriptor + +import ( + "runtime/volatile" +) + +const ( + TypeDevice = 0x1 + TypeConfiguration = 0x2 + TypeString = 0x3 + TypeInterface = 0x4 + TypeEndpoint = 0x5 + TypeDeviceQualifier = 0x6 + TypeInterfaceAssociation = 0xb + TypeClassHID = 0x21 + TypeHIDReport = 0x22 + TypeClassSpecific = 0x24 + TypeClassSpecificEndpoint = 0x25 +) + +// DeviceDescBank is the USB device endpoint . +type DeviceDescBank struct { + ADDR volatile.Register32 + PCKSIZE volatile.Register32 + EXTREG volatile.Register16 + STATUS_BK volatile.Register8 + _reserved [5]volatile.Register8 +} + +type Device struct { + DeviceDescBank [2]DeviceDescBank +} + +type Descriptor struct { + Device []byte + Configuration []byte + HID map[uint16][]byte +} + +func (d *Descriptor) Configure(idVendor, idProduct uint16) { + dev := DeviceType{d.Device} + dev.VendorID(idVendor) + dev.ProductID(idProduct) + + conf := ConfigurationType{d.Configuration} + conf.TotalLength(uint16(len(d.Configuration))) +} + +func Append[T any](slices [][]T) []T { + var size, pos int + + for _, s := range slices { + size += len(s) + } + + result := make([]T, size) + + for _, s := range slices { + pos += copy(result[pos:], s) + } + + return result +} diff --git a/emb/machine/_usb/descriptor/device.go b/emb/machine/_usb/descriptor/device.go new file mode 100644 index 00000000..0c3ee92f --- /dev/null +++ b/emb/machine/_usb/descriptor/device.go @@ -0,0 +1,73 @@ +package descriptor + +import ( + "internal/binary" +) + +const ( + deviceTypeLen = 18 +) + +type DeviceType struct { + data []byte +} + +func (d DeviceType) Bytes() []byte { + return d.data +} + +func (d DeviceType) Length(v uint8) { + d.data[0] = byte(v) +} + +func (d DeviceType) Type(v uint8) { + d.data[1] = byte(v) +} + +func (d DeviceType) USB(v uint16) { + binary.LittleEndian.PutUint16(d.data[2:4], v) +} + +func (d DeviceType) DeviceClass(v uint8) { + d.data[4] = byte(v) +} + +func (d DeviceType) DeviceSubClass(v uint8) { + d.data[5] = byte(v) +} + +func (d DeviceType) DeviceProtocol(v uint8) { + d.data[6] = byte(v) +} + +func (d DeviceType) MaxPacketSize0(v uint8) { + d.data[7] = byte(v) +} + +func (d DeviceType) VendorID(v uint16) { + binary.LittleEndian.PutUint16(d.data[8:10], v) +} + +func (d DeviceType) ProductID(v uint16) { + binary.LittleEndian.PutUint16(d.data[10:12], v) +} + +func (d DeviceType) Device(v uint16) { + binary.LittleEndian.PutUint16(d.data[12:14], v) +} + +func (d DeviceType) Manufacturer(v uint8) { + d.data[14] = byte(v) +} + +func (d DeviceType) Product(v uint8) { + d.data[15] = byte(v) +} + +func (d DeviceType) SerialNumber(v uint8) { + d.data[16] = byte(v) +} + +func (d DeviceType) NumConfigurations(v uint8) { + d.data[17] = byte(v) +} diff --git a/emb/machine/_usb/descriptor/doc.go b/emb/machine/_usb/descriptor/doc.go new file mode 100644 index 00000000..f35a7681 --- /dev/null +++ b/emb/machine/_usb/descriptor/doc.go @@ -0,0 +1,3 @@ +// package descriptor is for the USB descriptor definitions. +// For the actual implementations, see the individual packages. +package descriptor diff --git a/emb/machine/_usb/descriptor/endpoint.go b/emb/machine/_usb/descriptor/endpoint.go new file mode 100644 index 00000000..57a17060 --- /dev/null +++ b/emb/machine/_usb/descriptor/endpoint.go @@ -0,0 +1,156 @@ +package descriptor + +import ( + "internal/binary" +) + +/* Endpoint Descriptor +USB 2.0 Specification: 9.6.6 Endpoint +*/ + +const ( + TransferTypeControl uint8 = iota + TransferTypeIsochronous + TransferTypeBulk + TransferTypeInterrupt +) + +var endpointEP1IN = [endpointTypeLen]byte{ + endpointTypeLen, + TypeEndpoint, + 0x81, // EndpointAddress + 0x03, // Attributes + 0x10, // MaxPacketSizeL + 0x00, // MaxPacketSizeH + 0x10, // Interval +} + +var EndpointEP1IN = EndpointType{ + data: endpointEP1IN[:], +} + +var endpointEP2OUT = [endpointTypeLen]byte{ + endpointTypeLen, + TypeEndpoint, + 0x02, // EndpointAddress + 0x02, // Attributes + 0x40, // MaxPacketSizeL + 0x00, // MaxPacketSizeH + 0x00, // Interval +} + +var EndpointEP2OUT = EndpointType{ + data: endpointEP2OUT[:], +} + +var endpointEP3IN = [endpointTypeLen]byte{ + endpointTypeLen, + TypeEndpoint, + 0x83, // EndpointAddress + 0x02, // Attributes + 0x40, // MaxPacketSizeL + 0x00, // MaxPacketSizeH + 0x00, // Interval +} + +var EndpointEP3IN = EndpointType{ + data: endpointEP3IN[:], +} + +var endpointEP4IN = [endpointTypeLen]byte{ + endpointTypeLen, + TypeEndpoint, + 0x84, // EndpointAddress + 0x03, // Attributes + 0x40, // MaxPacketSizeL + 0x00, // MaxPacketSizeH + 0x01, // Interval +} + +var EndpointEP4IN = EndpointType{ + data: endpointEP4IN[:], +} + +var endpointEP5OUT = [endpointTypeLen]byte{ + endpointTypeLen, + TypeEndpoint, + 0x05, // EndpointAddress + 0x03, // Attributes + 0x40, // MaxPacketSizeL + 0x00, // MaxPacketSizeH + 0x01, // Interval +} + +var EndpointEP5OUT = EndpointType{ + data: endpointEP5OUT[:], +} + +// Mass Storage Class bulk in endpoint +var endpointMSCIN = [endpointTypeLen]byte{ + endpointTypeLen, + TypeEndpoint, + 0x86, // EndpointAddress + TransferTypeBulk, // Attributes + 0x40, // MaxPacketSizeL (64 bytes) + 0x00, // MaxPacketSizeH + 0x00, // Interval +} + +var EndpointMSCIN = EndpointType{ + data: endpointMSCIN[:], +} + +// Mass Storage Class bulk out endpoint +var endpointMSCOUT = [endpointTypeLen]byte{ + endpointTypeLen, + TypeEndpoint, + 0x07, // EndpointAddress + TransferTypeBulk, // Attributes + 0x40, // MaxPacketSizeL (64 bytes) + 0x00, // MaxPacketSizeH + 0x00, // Interval +} + +var EndpointMSCOUT = EndpointType{ + data: endpointMSCOUT[:], +} + +const ( + endpointTypeLen = 7 +) + +type EndpointType struct { + data []byte +} + +func (d EndpointType) Bytes() []byte { + return d.data +} + +func (d EndpointType) Length(v uint8) { + d.data[0] = byte(v) +} + +func (d EndpointType) Type(v uint8) { + d.data[1] = byte(v) +} + +func (d EndpointType) EndpointAddress(v uint8) { + d.data[2] = byte(v) +} + +func (d EndpointType) Attributes(v uint8) { + d.data[3] = byte(v) +} + +func (d EndpointType) MaxPacketSize(v uint16) { + binary.LittleEndian.PutUint16(d.data[4:6], v) +} + +func (d EndpointType) Interval(v uint8) { + d.data[6] = byte(v) +} + +func (d EndpointType) GetMaxPacketSize() uint16 { + return binary.LittleEndian.Uint16(d.data[4:6]) +} diff --git a/emb/machine/_usb/descriptor/hid.go b/emb/machine/_usb/descriptor/hid.go new file mode 100644 index 00000000..06b98015 --- /dev/null +++ b/emb/machine/_usb/descriptor/hid.go @@ -0,0 +1,216 @@ +package descriptor + +import ( + "errors" + "internal/binary" + "internal/bytealg" +) + +var configurationCDCHID = [configurationTypeLen]byte{ + configurationTypeLen, + TypeConfiguration, + 0x64, 0x00, // adjust length as needed + 0x03, // number of interfaces + 0x01, // configuration value + 0x00, // index to string description + 0xa0, // attributes + 0x32, // maxpower +} + +var ConfigurationCDCHID = ConfigurationType{ + data: configurationCDCHID[:], +} + +var interfaceHID = [interfaceTypeLen]byte{ + interfaceTypeLen, + TypeInterface, + 0x02, // InterfaceNumber + 0x00, // AlternateSetting + 0x02, // NumEndpoints + 0x03, // InterfaceClass + 0x00, // InterfaceSubClass + 0x00, // InterfaceProtocol + 0x00, // Interface +} + +var InterfaceHID = InterfaceType{ + data: interfaceHID[:], +} + +const ( + ClassHIDTypeLen = 9 +) + +type ClassHIDType struct { + data []byte +} + +func (d ClassHIDType) Bytes() []byte { + return d.data[:] +} + +func (d ClassHIDType) Length(v uint8) { + d.data[0] = byte(v) +} + +func (d ClassHIDType) Type(v uint8) { + d.data[1] = byte(v) +} + +func (d ClassHIDType) HID(v uint16) { + binary.LittleEndian.PutUint16(d.data[2:4], v) +} + +func (d ClassHIDType) CountryCode(v uint8) { + d.data[4] = byte(v) +} + +func (d ClassHIDType) NumDescriptors(v uint8) { + d.data[5] = byte(v) +} + +func (d ClassHIDType) ClassType(v uint8) { + d.data[6] = byte(v) +} + +func (d ClassHIDType) ClassLength(v uint16) { + binary.LittleEndian.PutUint16(d.data[7:9], v) +} + +var errNoClassHIDFound = errors.New("no classHID found") + +// FindClassHIDType tries to find the ClassHID class in the descriptor. +func FindClassHIDType(des, class []byte) (ClassHIDType, error) { + if len(des) < ClassHIDTypeLen || len(class) == 0 { + return ClassHIDType{}, errNoClassHIDFound + } + + // search only for ClassHIDType without the ClassLength, + // in case it has already been set. + idx := bytealg.Index(des, class[:ClassHIDTypeLen-2]) + if idx == -1 { + return ClassHIDType{}, errNoClassHIDFound + } + + return ClassHIDType{data: des[idx : idx+ClassHIDTypeLen]}, nil +} + +var classHID = [ClassHIDTypeLen]byte{ + ClassHIDTypeLen, + TypeClassHID, + 0x11, // HID version L + 0x01, // HID version H + 0x00, // CountryCode + 0x01, // NumDescriptors + 0x22, // ClassType + 0x91, // ClassLength L + 0x00, // ClassLength H +} + +var ClassHID = ClassHIDType{ + data: classHID[:], +} + +var CDCHID = Descriptor{ + Device: DeviceCDC.Bytes(), + Configuration: Append([][]byte{ + ConfigurationCDCHID.Bytes(), + InterfaceAssociationCDC.Bytes(), + InterfaceCDCControl.Bytes(), + ClassSpecificCDCHeader.Bytes(), + ClassSpecificCDCACM.Bytes(), + ClassSpecificCDCUnion.Bytes(), + ClassSpecificCDCCallManagement.Bytes(), + EndpointEP1IN.Bytes(), + InterfaceCDCData.Bytes(), + EndpointEP2OUT.Bytes(), + EndpointEP3IN.Bytes(), + InterfaceHID.Bytes(), + ClassHID.Bytes(), + EndpointEP4IN.Bytes(), + EndpointEP5OUT.Bytes(), + }), + HID: map[uint16][]byte{ + 2: Append([][]byte{ // Update ClassLength in classHID whenever the array length is modified! + HIDUsagePageGenericDesktop, + HIDUsageDesktopKeyboard, + HIDCollectionApplication, + HIDReportID(2), + + HIDUsagePageKeyboard, + HIDUsageMinimum(224), + HIDUsageMaximum(231), + HIDLogicalMinimum(0), + HIDLogicalMaximum(1), + HIDReportSize(1), + HIDReportCount(8), + HIDInputDataVarAbs, + HIDReportCount(1), + HIDReportSize(8), + HIDInputConstVarAbs, + HIDReportCount(3), + HIDReportSize(1), + HIDUsagePageLED, + HIDUsageMinimum(1), + HIDUsageMaximum(3), + HIDOutputDataVarAbs, + HIDReportCount(5), + HIDReportSize(1), + HIDOutputConstVarAbs, + HIDReportCount(6), + HIDReportSize(8), + HIDLogicalMinimum(0), + HIDLogicalMaximum(255), + + HIDUsagePageKeyboard, + HIDUsageMinimum(0), + HIDUsageMaximum(255), + HIDInputDataAryAbs, + HIDCollectionEnd, + + HIDUsagePageGenericDesktop, + HIDUsageDesktopMouse, + HIDCollectionApplication, + HIDUsageDesktopPointer, + HIDCollectionPhysical, + HIDReportID(1), + + HIDUsagePageButton, + HIDUsageMinimum(1), + HIDUsageMaximum(5), + HIDLogicalMinimum(0), + HIDLogicalMaximum(1), + HIDReportCount(5), + HIDReportSize(1), + HIDInputDataVarAbs, + HIDReportCount(1), + HIDReportSize(3), + HIDInputConstVarAbs, + + HIDUsagePageGenericDesktop, + HIDUsageDesktopX, + HIDUsageDesktopY, + HIDUsageDesktopWheel, + HIDLogicalMinimum(-127), + HIDLogicalMaximum(127), + HIDReportSize(8), + HIDReportCount(3), + HIDInputDataVarRel, + HIDCollectionEnd, + HIDCollectionEnd, + + HIDUsagePageConsumer, + HIDUsageConsumerControl, + HIDCollectionApplication, + HIDReportID(3), + HIDLogicalMinimum(0), + HIDLogicalMaximum(8191), + HIDUsageMinimum(0), + HIDUsageMaximum(0x1FFF), + HIDReportSize(16), + HIDReportCount(1), + HIDInputDataAryAbs, + HIDCollectionEnd, + }), + }, +} diff --git a/emb/machine/_usb/descriptor/hidreport.go b/emb/machine/_usb/descriptor/hidreport.go new file mode 100644 index 00000000..625578f8 --- /dev/null +++ b/emb/machine/_usb/descriptor/hidreport.go @@ -0,0 +1,220 @@ +package descriptor + +import "math" + +const ( + hidUsagePage = 0x04 + hidUsage = 0x08 + hidLogicalMinimum = 0x14 + hidLogicalMaximum = 0x24 + hidUsageMinimum = 0x18 + hidUsageMaximum = 0x28 + hidPhysicalMinimum = 0x34 + hidPhysicalMaximum = 0x44 + hidUnitExponent = 0x54 + hidUnit = 0x64 + hidCollection = 0xA0 + hidInput = 0x80 + hidOutput = 0x90 + hidReportSize = 0x74 + hidReportCount = 0x94 + hidReportID = 0x84 +) + +const ( + hidSizeValue0 = 0x00 + hidSizeValue1 = 0x01 + hidSizeValue2 = 0x02 + hidSizeValue4 = 0x03 +) + +var ( + HIDUsagePageGenericDesktop = HIDUsagePage(0x01) + HIDUsagePageSimulationControls = HIDUsagePage(0x02) + HIDUsagePageVRControls = HIDUsagePage(0x03) + HIDUsagePageSportControls = HIDUsagePage(0x04) + HIDUsagePageGameControls = HIDUsagePage(0x05) + HIDUsagePageGenericControls = HIDUsagePage(0x06) + HIDUsagePageKeyboard = HIDUsagePage(0x07) + HIDUsagePageLED = HIDUsagePage(0x08) + HIDUsagePageButton = HIDUsagePage(0x09) + HIDUsagePageOrdinal = HIDUsagePage(0x0A) + HIDUsagePageTelephony = HIDUsagePage(0x0B) + HIDUsagePageConsumer = HIDUsagePage(0x0C) + HIDUsagePageDigitizers = HIDUsagePage(0x0D) + HIDUsagePageHaptics = HIDUsagePage(0x0E) + HIDUsagePagePhysicalInput = HIDUsagePage(0x0F) + HIDUsagePageUnicode = HIDUsagePage(0x10) + HIDUsagePageSoC = HIDUsagePage(0x11) + HIDUsagePageEyeHeadTrackers = HIDUsagePage(0x12) + HIDUsagePageAuxDisplay = HIDUsagePage(0x14) + HIDUsagePageSensors = HIDUsagePage(0x20) + HIDUsagePageMedicalInstrument = HIDUsagePage(0x40) + HIDUsagePageBrailleDisplay = HIDUsagePage(0x41) + HIDUsagePageLighting = HIDUsagePage(0x59) + HIDUsagePageMonitor = HIDUsagePage(0x80) + HIDUsagePageMonitorEnum = HIDUsagePage(0x81) + HIDUsagePageVESA = HIDUsagePage(0x82) + HIDUsagePagePower = HIDUsagePage(0x84) + HIDUsagePageBatterySystem = HIDUsagePage(0x85) + HIDUsagePageBarcodeScanner = HIDUsagePage(0x8C) + HIDUsagePageScales = HIDUsagePage(0x8D) + HIDUsagePageMagneticStripe = HIDUsagePage(0x8E) + HIDUsagePageCameraControl = HIDUsagePage(0x90) + HIDUsagePageArcade = HIDUsagePage(0x91) + HIDUsagePageGaming = HIDUsagePage(0x92) +) + +var ( + HIDUsageDesktopPointer = HIDUsage(0x01) + HIDUsageDesktopMouse = HIDUsage(0x02) + HIDUsageDesktopJoystick = HIDUsage(0x04) + HIDUsageDesktopGamepad = HIDUsage(0x05) + HIDUsageDesktopKeyboard = HIDUsage(0x06) + HIDUsageDesktopKeypad = HIDUsage(0x07) + HIDUsageDesktopMultiaxis = HIDUsage(0x08) + HIDUsageDesktopTablet = HIDUsage(0x09) + HIDUsageDesktopWaterCooling = HIDUsage(0x0A) + HIDUsageDesktopChassis = HIDUsage(0x0B) + HIDUsageDesktopWireless = HIDUsage(0x0C) + HIDUsageDesktopPortable = HIDUsage(0x0D) + HIDUsageDesktopSystemMultiaxis = HIDUsage(0x0E) + HIDUsageDesktopSpatial = HIDUsage(0x0F) + HIDUsageDesktopAssistive = HIDUsage(0x10) + HIDUsageDesktopDock = HIDUsage(0x11) + HIDUsageDesktopDockable = HIDUsage(0x12) + HIDUsageDesktopCallState = HIDUsage(0x13) + HIDUsageDesktopX = HIDUsage(0x30) + HIDUsageDesktopY = HIDUsage(0x31) + HIDUsageDesktopZ = HIDUsage(0x32) + HIDUsageDesktopRx = HIDUsage(0x33) + HIDUsageDesktopRy = HIDUsage(0x34) + HIDUsageDesktopRz = HIDUsage(0x35) + HIDUsageDesktopSlider = HIDUsage(0x36) + HIDUsageDesktopDial = HIDUsage(0x37) + HIDUsageDesktopWheel = HIDUsage(0x38) + HIDUsageDesktopHatSwitch = HIDUsage(0x39) + HIDUsageDesktopCountedBuffer = HIDUsage(0x3A) +) + +var ( + HIDUsageConsumerControl = HIDUsage(0x01) + HIDUsageConsumerNumericKeypad = HIDUsage(0x02) + HIDUsageConsumerProgrammableButtons = HIDUsage(0x03) + HIDUsageConsumerMicrophone = HIDUsage(0x04) + HIDUsageConsumerHeadphone = HIDUsage(0x05) + HIDUsageConsumerGraphicEqualizer = HIDUsage(0x06) +) + +var ( + HIDCollectionPhysical = HIDCollection(0x00) + HIDCollectionApplication = HIDCollection(0x01) + HIDCollectionEnd = []byte{0xC0} +) + +var ( + // Input (Data,Ary,Abs), Key arrays (6 bytes) + HIDInputDataAryAbs = HIDInput(0x00) + + // Input (Data, Variable, Absolute), Modifier byte + HIDInputDataVarAbs = HIDInput(0x02) + + // Input (Const,Var,Abs), Modifier byte + HIDInputConstVarAbs = HIDInput(0x03) + + // Input (Data, Variable, Relative), 2 position bytes (X & Y) + HIDInputDataVarRel = HIDInput(0x06) + + // Output (Data, Variable, Absolute), Modifier byte + HIDOutputDataVarAbs = HIDOutput(0x02) + + // Output (Const, Variable, Absolute), Modifier byte + HIDOutputConstVarAbs = HIDOutput(0x03) +) + +func hidShortItem(tag byte, value uint32) []byte { + switch { + case value <= math.MaxUint8: + return []byte{tag | hidSizeValue1, byte(value)} + case value <= math.MaxUint16: + return []byte{tag | hidSizeValue2, byte(value), byte(value >> 8)} + default: + return []byte{tag | hidSizeValue4, byte(value), byte(value >> 8), byte(value >> 16), byte(value >> 24)} + } +} + +func hidShortItemSigned(tag byte, value int32) []byte { + switch { + case math.MinInt8 <= value && value <= math.MaxInt8: + return []byte{tag | hidSizeValue1, byte(value)} + case math.MinInt16 <= value && value <= math.MaxInt16: + return []byte{tag | hidSizeValue2, byte(value), byte(value >> 8)} + default: + return []byte{tag | hidSizeValue4, byte(value), byte(value >> 8), byte(value >> 16), byte(value >> 24)} + } +} + +func HIDReportSize(size int) []byte { + return hidShortItem(hidReportSize, uint32(size)) +} + +func HIDReportCount(count int) []byte { + return hidShortItem(hidReportCount, uint32(count)) +} + +func HIDReportID(id int) []byte { + return hidShortItem(hidReportID, uint32(id)) +} + +func HIDLogicalMinimum(min int) []byte { + return hidShortItemSigned(hidLogicalMinimum, int32(min)) +} + +func HIDLogicalMaximum(max int) []byte { + return hidShortItemSigned(hidLogicalMaximum, int32(max)) +} + +func HIDUsageMinimum(min int) []byte { + return hidShortItem(hidUsageMinimum, uint32(min)) +} + +func HIDUsageMaximum(max int) []byte { + return hidShortItem(hidUsageMaximum, uint32(max)) +} + +func HIDPhysicalMinimum(min int) []byte { + return hidShortItemSigned(hidPhysicalMinimum, int32(min)) +} + +func HIDPhysicalMaximum(max int) []byte { + return hidShortItemSigned(hidPhysicalMaximum, int32(max)) +} + +func HIDUnitExponent(exp int) []byte { + // 4 Bit two's complement + return hidShortItem(hidUnitExponent, uint32(exp&0xF)) +} + +func HIDUnit(unit uint32) []byte { + return hidShortItem(hidUnit, unit) +} + +func HIDUsagePage(id uint16) []byte { + return hidShortItem(hidUsagePage, uint32(id)) +} + +func HIDUsage(id uint32) []byte { + return hidShortItem(hidUsage, id) +} + +func HIDCollection(id uint32) []byte { + return hidShortItem(hidCollection, id) +} + +func HIDInput(flags uint32) []byte { + return hidShortItem(hidInput, flags) +} + +func HIDOutput(flags uint32) []byte { + return hidShortItem(hidOutput, flags) +} diff --git a/emb/machine/_usb/descriptor/interface.go b/emb/machine/_usb/descriptor/interface.go new file mode 100644 index 00000000..52f537fe --- /dev/null +++ b/emb/machine/_usb/descriptor/interface.go @@ -0,0 +1,49 @@ +package descriptor + +const ( + interfaceTypeLen = 9 +) + +type InterfaceType struct { + data []byte +} + +func (d InterfaceType) Bytes() []byte { + return d.data +} + +func (d InterfaceType) Length(v uint8) { + d.data[0] = byte(v) +} + +func (d InterfaceType) Type(v uint8) { + d.data[1] = byte(v) +} + +func (d InterfaceType) InterfaceNumber(v uint8) { + d.data[2] = byte(v) +} + +func (d InterfaceType) AlternateSetting(v uint8) { + d.data[3] = byte(v) +} + +func (d InterfaceType) NumEndpoints(v uint8) { + d.data[4] = byte(v) +} + +func (d InterfaceType) InterfaceClass(v uint8) { + d.data[5] = byte(v) +} + +func (d InterfaceType) InterfaceSubClass(v uint8) { + d.data[6] = byte(v) +} + +func (d InterfaceType) InterfaceProtocol(v uint8) { + d.data[7] = byte(v) +} + +func (d InterfaceType) Interface(v uint8) { + d.data[8] = byte(v) +} diff --git a/emb/machine/_usb/descriptor/interfaceassociation.go b/emb/machine/_usb/descriptor/interfaceassociation.go new file mode 100644 index 00000000..f9280862 --- /dev/null +++ b/emb/machine/_usb/descriptor/interfaceassociation.go @@ -0,0 +1,45 @@ +package descriptor + +const ( + interfaceAssociationTypeLen = 8 +) + +type InterfaceAssociationType struct { + data []byte +} + +func (d InterfaceAssociationType) Bytes() []byte { + return d.data +} + +func (d InterfaceAssociationType) Length(v uint8) { + d.data[0] = byte(v) +} + +func (d InterfaceAssociationType) Type(v uint8) { + d.data[1] = byte(v) +} + +func (d InterfaceAssociationType) FirstInterface(v uint8) { + d.data[2] = byte(v) +} + +func (d InterfaceAssociationType) InterfaceCount(v uint8) { + d.data[3] = byte(v) +} + +func (d InterfaceAssociationType) FunctionClass(v uint8) { + d.data[4] = byte(v) +} + +func (d InterfaceAssociationType) FunctionSubClass(v uint8) { + d.data[5] = byte(v) +} + +func (d InterfaceAssociationType) FunctionProtocol(v uint8) { + d.data[6] = byte(v) +} + +func (d InterfaceAssociationType) Function(v uint8) { + d.data[7] = byte(v) +} diff --git a/emb/machine/_usb/descriptor/joystick.go b/emb/machine/_usb/descriptor/joystick.go new file mode 100644 index 00000000..65756e0d --- /dev/null +++ b/emb/machine/_usb/descriptor/joystick.go @@ -0,0 +1,144 @@ +package descriptor + +var deviceJoystick = [deviceTypeLen]byte{ + deviceTypeLen, + TypeDevice, + 0x00, 0x02, // USB version + 0xef, // device class + 0x02, // subclass + 0x01, // protocol + 0x40, // maxpacketsize + 0x41, 0x23, // vendor id + 0x36, 0x80, // product id + 0x00, 0x01, // device + 0x01, // manufacturer + 0x02, // product + 0x03, // SerialNumber + 0x01, // NumConfigurations +} + +var DeviceJoystick = DeviceType{ + data: deviceJoystick[:], +} + +var configurationCDCJoystick = [configurationTypeLen]byte{ + configurationTypeLen, + TypeConfiguration, + 0x6b, 0x00, // adjust length as needed + 0x03, // number of interfaces + 0x01, // configuration value + 0x00, // index to string description + 0xa0, // attributes + 0xfa, // maxpower +} + +var ConfigurationCDCJoystick = ConfigurationType{ + data: configurationCDCJoystick[:], +} + +var interfaceHIDJoystick = [interfaceTypeLen]byte{ + interfaceTypeLen, + TypeInterface, + 0x02, // InterfaceNumber + 0x00, // AlternateSetting + 0x02, // NumEndpoints + 0x03, // InterfaceClass + 0x00, // InterfaceSubClass + 0x00, // InterfaceProtocol + 0x00, // Interface +} + +var InterfaceHIDJoystick = InterfaceType{ + data: interfaceHIDJoystick[:], +} + +var classHIDJoystick = [ClassHIDTypeLen]byte{ + ClassHIDTypeLen, + TypeClassHID, + 0x11, // HID version L + 0x01, // HID version H + 0x00, // CountryCode + 0x01, // NumDescriptors + 0x22, // ClassType + 0x00, // ClassLength L + 0x00, // ClassLength H +} + +var ClassHIDJoystick = ClassHIDType{ + data: classHIDJoystick[:], +} + +var JoystickDefaultHIDReport = Append([][]byte{ + HIDUsagePageGenericDesktop, + HIDUsageDesktopJoystick, + HIDCollectionApplication, + HIDReportID(1), + HIDUsagePageButton, + HIDUsageMinimum(1), + HIDUsageMaximum(16), + HIDLogicalMinimum(0), + HIDLogicalMaximum(1), + HIDReportSize(1), + HIDReportCount(16), + HIDUnitExponent(0), + HIDUnit(0), + HIDInputDataVarAbs, + + HIDUsagePageGenericDesktop, + HIDUsageDesktopHatSwitch, + HIDLogicalMinimum(0), + HIDLogicalMaximum(7), + HIDPhysicalMinimum(0), + HIDPhysicalMaximum(315), + HIDUnit(0x14), + HIDReportCount(1), + HIDReportSize(4), + HIDInputDataVarAbs, + HIDUsageDesktopHatSwitch, + HIDReportCount(1), + HIDReportSize(4), + HIDInputConstVarAbs, + HIDUsageDesktopPointer, + HIDLogicalMinimum(-32767), + HIDLogicalMaximum(32767), + HIDPhysicalMinimum(0), + HIDPhysicalMaximum(0), + HIDUnit(0), + HIDReportCount(6), + HIDReportSize(16), + HIDCollectionPhysical, + HIDUsageDesktopX, + HIDUsageDesktopY, + HIDUsageDesktopZ, + HIDUsageDesktopRx, + HIDUsageDesktopRy, + HIDUsageDesktopRz, + HIDInputDataVarAbs, + HIDCollectionEnd, + HIDCollectionEnd, +}) + +// CDCJoystick requires that you append the JoystickDescriptor +// to the Configuration before using. This is in order to support +// custom configurations. +var CDCJoystick = Descriptor{ + Device: DeviceJoystick.Bytes(), + Configuration: Append([][]byte{ + ConfigurationCDCJoystick.Bytes(), + InterfaceAssociationCDC.Bytes(), + InterfaceCDCControl.Bytes(), + ClassSpecificCDCHeader.Bytes(), + ClassSpecificCDCACM.Bytes(), + ClassSpecificCDCUnion.Bytes(), + ClassSpecificCDCCallManagement.Bytes(), + EndpointEP1IN.Bytes(), + InterfaceCDCData.Bytes(), + EndpointEP2OUT.Bytes(), + EndpointEP3IN.Bytes(), + InterfaceHIDJoystick.Bytes(), + ClassHIDJoystick.Bytes(), + EndpointEP4IN.Bytes(), + EndpointEP5OUT.Bytes(), + }), + HID: map[uint16][]byte{}, +} diff --git a/emb/machine/_usb/descriptor/midi.go b/emb/machine/_usb/descriptor/midi.go new file mode 100644 index 00000000..fad81f31 --- /dev/null +++ b/emb/machine/_usb/descriptor/midi.go @@ -0,0 +1,249 @@ +package descriptor + +var interfaceAssociationMIDI = [interfaceAssociationTypeLen]byte{ + interfaceAssociationTypeLen, + TypeInterfaceAssociation, + 0x02, // EndpointAddress + 0x02, // Attributes + 0x01, // MaxPacketSizeL + 0x01, // MaxPacketSizeH + 0x00, // Interval + 0x00, // Interval +} + +var InterfaceAssociationMIDI = InterfaceAssociationType{ + data: interfaceAssociationMIDI[:], +} + +var interfaceAudio = [interfaceTypeLen]byte{ + interfaceTypeLen, + TypeInterface, + 0x02, // InterfaceNumber + 0x00, // AlternateSetting + 0x00, // NumEndpoints + 0x01, // InterfaceClass + 0x01, // InterfaceSubClass + 0x00, // InterfaceProtocol + 0x00, // Interface +} + +var InterfaceAudio = InterfaceType{ + data: interfaceAudio[:], +} + +var interfaceMIDIStreaming = [interfaceTypeLen]byte{ + interfaceTypeLen, + TypeInterface, + 0x03, // InterfaceNumber + 0x00, // AlternateSetting + 0x02, // NumEndpoints + 0x01, // InterfaceClass + 0x03, // InterfaceSubClass + 0x00, // InterfaceProtocol + 0x00, // Interface +} + +var InterfaceMIDIStreaming = InterfaceType{ + data: interfaceMIDIStreaming[:], +} + +const classSpecificAudioTypeLen = 9 + +var classSpecificAudioInterface = [classSpecificAudioTypeLen]byte{ + classSpecificAudioTypeLen, + TypeClassSpecific, + 0x01, + 0x00, // + 0x01, // + 0x09, + 0x00, + 0x01, + 0x03, +} + +var ClassSpecificAudioInterface = ClassSpecificType{ + data: classSpecificAudioInterface[:], +} + +const classSpecificMIDIHeaderLen = 7 + +var classSpecificMIDIHeader = [classSpecificMIDIHeaderLen]byte{ + classSpecificMIDIHeaderLen, + TypeClassSpecific, + 0x01, + 0x00, // + 0x01, // + 0x41, + 0x00, +} + +var ClassSpecificMIDIHeader = ClassSpecificType{ + data: classSpecificMIDIHeader[:], +} + +const classSpecificMIDIInJackLen = 6 + +var classSpecificMIDIInJack1 = [classSpecificMIDIInJackLen]byte{ + classSpecificMIDIInJackLen, + TypeClassSpecific, + 0x02, // MIDI In jack + 0x01, // Jack type (embedded) + 0x01, // Jack ID + 0x00, // iJack +} + +var ClassSpecificMIDIInJack1 = ClassSpecificType{ + data: classSpecificMIDIInJack1[:], +} + +var classSpecificMIDIInJack2 = [classSpecificMIDIInJackLen]byte{ + classSpecificMIDIInJackLen, + TypeClassSpecific, + 0x02, // MIDI In jack + 0x02, // Jack type (external) + 0x02, // Jack ID + 0x00, // iJack +} + +var ClassSpecificMIDIInJack2 = ClassSpecificType{ + data: classSpecificMIDIInJack2[:], +} + +const classSpecificMIDIOutJackLen = 9 + +var classSpecificMIDIOutJack1 = [classSpecificMIDIOutJackLen]byte{ + classSpecificMIDIOutJackLen, + TypeClassSpecific, + 0x03, // MIDI Out jack + 0x01, // Jack type (embedded) + 0x03, // Jack ID + 0x01, // number of input pins + 0x02, // source ID + 0x01, // source pin + 0x00, // iJack +} + +var ClassSpecificMIDIOutJack1 = ClassSpecificType{ + data: classSpecificMIDIOutJack1[:], +} + +var classSpecificMIDIOutJack2 = [classSpecificMIDIOutJackLen]byte{ + classSpecificMIDIOutJackLen, + TypeClassSpecific, + 0x03, // MIDI Out jack + 0x02, // Jack type (external) + 0x04, // Jack ID + 0x01, // number of input pins + 0x01, // source ID + 0x01, // source pin + 0x00, // iJack +} + +var ClassSpecificMIDIOutJack2 = ClassSpecificType{ + data: classSpecificMIDIOutJack2[:], +} + +const classSpecificMIDIEndpointLen = 5 + +var classSpecificMIDIOutEndpoint = [classSpecificMIDIEndpointLen]byte{ + classSpecificMIDIEndpointLen, + TypeClassSpecificEndpoint, + 0x01, // CS endpoint + 0x01, // number MIDI IN jacks + 0x01, // assoc Jack ID +} + +var ClassSpecificMIDIOutEndpoint = ClassSpecificType{ + data: classSpecificMIDIOutEndpoint[:], +} + +var classSpecificMIDIInEndpoint = [classSpecificMIDIEndpointLen]byte{ + classSpecificMIDIEndpointLen, + TypeClassSpecificEndpoint, + 0x01, // CS endpoint + 0x01, // number MIDI Out jacks + 0x03, // assoc Jack ID +} + +var ClassSpecificMIDIInEndpoint = ClassSpecificType{ + data: classSpecificMIDIInEndpoint[:], +} + +const endpointMIDITypeLen = 9 + +var endpointEP6IN = [endpointMIDITypeLen]byte{ + endpointMIDITypeLen, + TypeEndpoint, + 0x86, // EndpointAddress + 0x02, // Attributes + 0x40, // MaxPacketSizeL + 0x00, // MaxPacketSizeH + 0x00, // Interval + 0x00, // refresh + 0x00, // sync address +} + +var EndpointEP6IN = EndpointType{ + data: endpointEP6IN[:], +} + +var endpointEP7OUT = [endpointMIDITypeLen]byte{ + endpointMIDITypeLen, + TypeEndpoint, + 0x07, // EndpointAddress + 0x02, // Attributes + 0x40, // MaxPacketSizeL + 0x00, // MaxPacketSizeH + 0x00, // Interval + 0x00, // refresh + 0x00, // sync address +} + +var EndpointEP7OUT = EndpointType{ + data: endpointEP7OUT[:], +} + +var configurationCDCMIDI = [configurationTypeLen]byte{ + configurationTypeLen, + TypeConfiguration, + 0xaf, 0x00, // adjust length as needed + 0x04, // number of interfaces + 0x01, // configuration value + 0x00, // index to string description + 0xa0, // attributes + 0x32, // maxpower +} + +var ConfigurationCDCMIDI = ConfigurationType{ + data: configurationCDCMIDI[:], +} + +var CDCMIDI = Descriptor{ + Device: DeviceCDC.Bytes(), + Configuration: Append([][]byte{ + ConfigurationCDCMIDI.Bytes(), + InterfaceAssociationCDC.Bytes(), + InterfaceCDCControl.Bytes(), + ClassSpecificCDCHeader.Bytes(), + ClassSpecificCDCACM.Bytes(), + ClassSpecificCDCUnion.Bytes(), + ClassSpecificCDCCallManagement.Bytes(), + EndpointEP1IN.Bytes(), + InterfaceCDCData.Bytes(), + EndpointEP2OUT.Bytes(), + EndpointEP3IN.Bytes(), + InterfaceAssociationMIDI.Bytes(), + InterfaceAudio.Bytes(), + ClassSpecificAudioInterface.Bytes(), + InterfaceMIDIStreaming.Bytes(), + ClassSpecificMIDIHeader.Bytes(), + ClassSpecificMIDIInJack1.Bytes(), + ClassSpecificMIDIInJack2.Bytes(), + ClassSpecificMIDIOutJack1.Bytes(), + ClassSpecificMIDIOutJack2.Bytes(), + EndpointEP7OUT.Bytes(), + ClassSpecificMIDIOutEndpoint.Bytes(), + EndpointEP6IN.Bytes(), + ClassSpecificMIDIInEndpoint.Bytes(), + }), +} diff --git a/emb/machine/_usb/descriptor/msc.go b/emb/machine/_usb/descriptor/msc.go new file mode 100644 index 00000000..55c6ddd8 --- /dev/null +++ b/emb/machine/_usb/descriptor/msc.go @@ -0,0 +1,75 @@ +package descriptor + +const ( + interfaceClassMSC = 0x08 + mscSubclassSCSI = 0x06 + mscProtocolBOT = 0x50 +) + +var interfaceAssociationMSC = [interfaceAssociationTypeLen]byte{ + interfaceAssociationTypeLen, + TypeInterfaceAssociation, + 0x02, // FirstInterface + 0x01, // InterfaceCount + interfaceClassMSC, // FunctionClass + mscSubclassSCSI, // FunctionSubClass + mscProtocolBOT, // FunctionProtocol + 0x00, // Function +} + +var InterfaceAssociationMSC = InterfaceAssociationType{ + data: interfaceAssociationMSC[:], +} + +var interfaceMSC = [interfaceTypeLen]byte{ + interfaceTypeLen, // Length + TypeInterface, // DescriptorType + 0x02, // InterfaceNumber + 0x00, // AlternateSetting + 0x02, // NumEndpoints + interfaceClassMSC, // InterfaceClass (Mass Storage) + mscSubclassSCSI, // InterfaceSubClass (SCSI Transparent) + mscProtocolBOT, // InterfaceProtocol (Bulk-Only Transport) + 0x00, // Interface +} + +var InterfaceMSC = InterfaceType{ + data: interfaceMSC[:], +} + +var configurationMSC = [configurationTypeLen]byte{ + configurationTypeLen, + TypeConfiguration, + 0x6a, 0x00, // wTotalLength + 0x03, // number of interfaces (bNumInterfaces) + 0x01, // configuration value (bConfigurationValue) + 0x00, // index to string description (iConfiguration) + 0xa0, // attributes (bmAttributes) + 0x32, // maxpower (100 mA) (bMaxPower) +} + +var ConfigurationMSC = ConfigurationType{ + data: configurationMSC[:], +} + +// Mass Storage Class +var MSC = Descriptor{ + Device: DeviceCDC.Bytes(), + Configuration: Append([][]byte{ + ConfigurationMSC.Bytes(), + InterfaceAssociationCDC.Bytes(), + InterfaceCDCControl.Bytes(), + ClassSpecificCDCHeader.Bytes(), + ClassSpecificCDCACM.Bytes(), + ClassSpecificCDCUnion.Bytes(), + ClassSpecificCDCCallManagement.Bytes(), + EndpointEP1IN.Bytes(), + InterfaceCDCData.Bytes(), + EndpointEP2OUT.Bytes(), + EndpointEP3IN.Bytes(), + InterfaceAssociationMSC.Bytes(), + InterfaceMSC.Bytes(), + EndpointMSCIN.Bytes(), + EndpointMSCOUT.Bytes(), + }), +} diff --git a/emb/machine/_usb/doc.go b/emb/machine/_usb/doc.go new file mode 100644 index 00000000..03dbadc0 --- /dev/null +++ b/emb/machine/_usb/doc.go @@ -0,0 +1,4 @@ +// package usb contains the subpackages with USB descriptors and device +// implementations for standard USB device classes such as the Communcation +// Data Class (CDC), Human Interface Device (HID), and Audio Device Class (ADC). +package usb diff --git a/emb/machine/_usb/hid/buffer.go b/emb/machine/_usb/hid/buffer.go new file mode 100644 index 00000000..2674bbde --- /dev/null +++ b/emb/machine/_usb/hid/buffer.go @@ -0,0 +1,52 @@ +package hid + +import ( + "runtime/volatile" +) + +const bufferSize = 128 + +// RingBuffer is ring buffer implementation inspired by post at +// https://www.embeddedrelated.com/showthread/comp.arch.embedded/77084-1.php +type RingBuffer struct { + rxbuffer [bufferSize][9]byte + head volatile.Register8 + tail volatile.Register8 +} + +// NewRingBuffer returns a new ring buffer. +func NewRingBuffer() *RingBuffer { + return &RingBuffer{} +} + +// Used returns how many bytes in buffer have been used. +func (rb *RingBuffer) Used() uint8 { + return uint8(rb.head.Get() - rb.tail.Get()) +} + +// Put stores a byte in the buffer. If the buffer is already +// full, the method will return false. +func (rb *RingBuffer) Put(val []byte) bool { + if rb.Used() != bufferSize { + rb.head.Set(rb.head.Get() + 1) + copy(rb.rxbuffer[rb.head.Get()%bufferSize][:], val) + return true + } + return false +} + +// Get returns a byte from the buffer. If the buffer is empty, +// the method will return a false as the second value. +func (rb *RingBuffer) Get() ([]byte, bool) { + if rb.Used() != 0 { + rb.tail.Set(rb.tail.Get() + 1) + return rb.rxbuffer[rb.tail.Get()%bufferSize][:], true + } + return nil, false +} + +// Clear resets the head and tail pointer to zero. +func (rb *RingBuffer) Clear() { + rb.head.Set(0) + rb.tail.Set(0) +} diff --git a/emb/machine/_usb/hid/doc.go b/emb/machine/_usb/hid/doc.go new file mode 100644 index 00000000..82797a54 --- /dev/null +++ b/emb/machine/_usb/hid/doc.go @@ -0,0 +1,2 @@ +// package hid is for USB Human Interface Devices. +package hid diff --git a/emb/machine/_usb/hid/hid.go b/emb/machine/_usb/hid/hid.go new file mode 100644 index 00000000..791fd06a --- /dev/null +++ b/emb/machine/_usb/hid/hid.go @@ -0,0 +1,100 @@ +package hid + +import ( + "errors" + "machine" + "machine/usb" + "machine/usb/descriptor" +) + +// from usb-hid.go +var ( + ErrHIDInvalidPort = errors.New("invalid USB port") + ErrHIDInvalidCore = errors.New("invalid USB core") + ErrHIDReportTransfer = errors.New("failed to transfer HID report") +) + +const ( + hidEndpoint = usb.HID_ENDPOINT_IN + + REPORT_TYPE_INPUT = 1 + REPORT_TYPE_OUTPUT = 2 + REPORT_TYPE_FEATURE = 3 +) + +type hidDevicer interface { + TxHandler() bool + RxHandler([]byte) bool +} + +var devices [5]hidDevicer +var size int + +// SetHandler sets the handler. Only the first time it is called, it +// calls machine.EnableHID for USB configuration +func SetHandler(d hidDevicer) { + if size == 0 { + machine.ConfigureUSBEndpoint(descriptor.CDCHID, + []usb.EndpointConfig{ + { + Index: usb.HID_ENDPOINT_OUT, + IsIn: false, + Type: usb.ENDPOINT_TYPE_INTERRUPT, + RxHandler: rxHandler, + }, + { + Index: usb.HID_ENDPOINT_IN, + IsIn: true, + Type: usb.ENDPOINT_TYPE_INTERRUPT, + TxHandler: txHandler, + }, + }, + []usb.SetupConfig{ + { + Index: usb.HID_INTERFACE, + Handler: setupHandler, + }, + }) + } + + devices[size] = d + size++ +} + +func txHandler() { + for _, d := range devices { + if d == nil { + continue + } + if done := d.TxHandler(); done { + return + } + } +} + +func rxHandler(b []byte) { + for _, d := range devices { + if d == nil { + continue + } + if done := d.RxHandler(b); done { + return + } + } +} + +var DefaultSetupHandler = setupHandler + +func setupHandler(setup usb.Setup) bool { + ok := false + if setup.BmRequestType == usb.SET_REPORT_TYPE && setup.BRequest == usb.SET_IDLE { + machine.SendZlp() + ok = true + } + return ok +} + +// SendUSBPacket sends a HIDPacket. +func SendUSBPacket(b []byte) { + machine.SendUSBInPacket(hidEndpoint, b) +} diff --git a/emb/machine/_usb/hid/joystick/joystick.go b/emb/machine/_usb/hid/joystick/joystick.go new file mode 100644 index 00000000..2c2f719c --- /dev/null +++ b/emb/machine/_usb/hid/joystick/joystick.go @@ -0,0 +1,125 @@ +package joystick + +import ( + "machine" + "machine/usb" + "machine/usb/descriptor" + "machine/usb/hid" +) + +var Joystick *joystick + +type joystick struct { + State + buf *hid.RingBuffer + waitTxc bool + rxHandlerFunc func(b []byte) + setupFunc func(setup usb.Setup) bool +} + +func init() { + if Joystick == nil { + Joystick = newDefaultJoystick() + } +} + +// UseSettings overrides the Joystick settings. This function must be +// called from init(). +func UseSettings(def Definitions, rxHandlerFunc func(b []byte), setupFunc func(setup usb.Setup) bool, hidDesc []byte) *joystick { + js := &joystick{ + buf: hid.NewRingBuffer(), + State: def.NewState(), + } + if setupFunc == nil { + setupFunc = hid.DefaultSetupHandler + } + if rxHandlerFunc == nil { + rxHandlerFunc = js.rxHandlerFunc + } + if len(hidDesc) == 0 { + hidDesc = descriptor.JoystickDefaultHIDReport + } + class, err := descriptor.FindClassHIDType(descriptor.CDCJoystick.Configuration, descriptor.ClassHIDJoystick.Bytes()) + if err != nil { + // TODO: some way to notify about error + return nil + } + + class.ClassLength(uint16(len(hidDesc))) + descriptor.CDCJoystick.HID[2] = hidDesc + + machine.ConfigureUSBEndpoint(descriptor.CDCJoystick, + []usb.EndpointConfig{ + { + Index: usb.HID_ENDPOINT_OUT, + IsIn: false, + Type: usb.ENDPOINT_TYPE_INTERRUPT, + RxHandler: rxHandlerFunc, + }, + { + Index: usb.HID_ENDPOINT_IN, + IsIn: true, + Type: usb.ENDPOINT_TYPE_INTERRUPT, + TxHandler: js.handler, + }, + }, + []usb.SetupConfig{ + { + Index: usb.HID_INTERFACE, + Handler: setupFunc, + }, + }, + ) + Joystick = js + return js +} + +func newDefaultJoystick() *joystick { + def := DefaultDefinitions() + js := UseSettings(def, nil, nil, nil) + return js +} + +// Port returns the USB Joystick port. +func Port() *joystick { + return Joystick +} + +func (m *joystick) handler() { + m.waitTxc = false + if b, ok := m.buf.Get(); ok { + m.waitTxc = true + hid.SendUSBPacket(b) + } +} + +func (m *joystick) tx(b []byte) { + if machine.USBDev.InitEndpointComplete { + if m.waitTxc { + m.buf.Put(b) + } else { + m.waitTxc = true + hid.SendUSBPacket(b) + } + } +} + +func (m *joystick) ready() bool { + return true +} + +func (m *joystick) rxHandler(b []byte) { + if m.rxHandlerFunc != nil { + m.rxHandlerFunc(b) + } +} + +// to InterruptOut +func (m *joystick) SendReport(reportID byte, b []byte) { + m.tx(append([]byte{reportID}, b...)) +} + +func (m *joystick) SendState() { + b, _ := m.State.MarshalBinary() + m.tx(b) +} diff --git a/emb/machine/_usb/hid/joystick/state.go b/emb/machine/_usb/hid/joystick/state.go new file mode 100644 index 00000000..08265ab1 --- /dev/null +++ b/emb/machine/_usb/hid/joystick/state.go @@ -0,0 +1,174 @@ +package joystick + +import ( + "machine/usb/descriptor" + + "encoding/binary" +) + +type HatDirection uint8 + +const ( + HatUp HatDirection = iota + HatRightUp + HatRight + HatRightDown + HatDown + HatLeftDown + HatLeft + HatLeftUp + HatCenter +) + +type Constraint struct { + MinIn int + MaxIn int + MinOut int16 + MaxOut int16 +} + +type AxisValue struct { + constraint Constraint + Value int +} + +func fit(x, in_min, in_max int, out_min, out_max int16) int16 { + return int16((x-in_min)*(int(out_max)-int(out_min))/(in_max-in_min) + int(out_min)) +} + +func fitf(x, in_min, in_max, out_min, out_max float32) float32 { + return x - in_min*(out_max-out_min)/(in_max-in_min) + out_min +} + +func limit(v, max int) int { + if v > max { + v = max + } else if v < -max { + v = -max + } + return v +} + +type Definitions struct { + ReportID byte + ButtonCnt int + HatSwitchCnt int + AxisDefs []Constraint + descriptor []byte +} + +func (c Definitions) Descriptor() []byte { + if len(c.descriptor) > 0 { + return c.descriptor + } + // TODO: build hid descriptor + return nil +} + +func (c Definitions) NewState() State { + bufSize := 1 + hatSwitches := make([]HatDirection, c.HatSwitchCnt) + for i := range hatSwitches { + hatSwitches[i] = HatCenter + } + axises := make([]*AxisValue, 0, len(c.AxisDefs)) + for _, v := range c.AxisDefs { + + axises = append(axises, &AxisValue{ + constraint: v, + Value: 0, + }) + } + btnSize := (c.ButtonCnt + 7) / 8 + bufSize += btnSize + bufSize += (len(hatSwitches) + 1) / 2 + bufSize += len(axises) * 2 + initBuf := make([]byte, bufSize) + initBuf[0] = c.ReportID + return State{ + buf: initBuf, + Buttons: make([]byte, btnSize), + HatSwitches: hatSwitches, + Axises: axises, + } +} + +type State struct { + buf []byte + Buttons []byte + HatSwitches []HatDirection + Axises []*AxisValue +} + +func (s State) MarshalBinary() ([]byte, error) { + s.buf = s.buf[0:1] + s.buf = append(s.buf, s.Buttons...) + if len(s.HatSwitches) > 0 { + hat := byte(0) + for i, v := range s.HatSwitches { + if i != 0 && i%2 == 0 { + s.buf = append(s.buf, hat) + hat = 0 + } + hat <<= 4 + hat |= byte(v & 0xf) + } + s.buf = append(s.buf, hat) + } + for _, v := range s.Axises { + c := v.constraint + val := fit(v.Value, c.MinIn, c.MaxIn, c.MinOut, c.MaxOut) + s.buf = binary.LittleEndian.AppendUint16(s.buf, uint16(val)) + } + return s.buf, nil +} + +func (s State) Button(index int) bool { + idx := index / 8 + bit := uint8(1 << (index % 8)) + return s.Buttons[idx]&bit > 0 +} + +func (s State) SetButton(index int, push bool) { + idx := index / 8 + bit := uint8(1 << (index % 8)) + b := s.Buttons[idx] + b &= ^bit + if push { + b |= bit + } + s.Buttons[idx] = b +} + +func (s State) Hat(index int) HatDirection { + return s.HatSwitches[index] +} + +func (s State) SetHat(index int, dir HatDirection) { + s.HatSwitches[index] = dir +} + +func (s State) Axis(index int) int { + return s.Axises[index].Value +} + +func (s State) SetAxis(index int, v int) { + s.Axises[index].Value = v +} + +func DefaultDefinitions() Definitions { + return Definitions{ + ReportID: 1, + ButtonCnt: 16, + HatSwitchCnt: 1, + AxisDefs: []Constraint{ + {MinIn: -32767, MaxIn: 32767, MinOut: -32767, MaxOut: 32767}, + {MinIn: -32767, MaxIn: 32767, MinOut: -32767, MaxOut: 32767}, + {MinIn: -32767, MaxIn: 32767, MinOut: -32767, MaxOut: 32767}, + {MinIn: -32767, MaxIn: 32767, MinOut: -32767, MaxOut: 32767}, + {MinIn: -32767, MaxIn: 32767, MinOut: -32767, MaxOut: 32767}, + {MinIn: -32767, MaxIn: 32767, MinOut: -32767, MaxOut: 32767}, + }, + descriptor: descriptor.JoystickDefaultHIDReport, + } +} diff --git a/emb/machine/_usb/hid/keyboard/keyboard.go b/emb/machine/_usb/hid/keyboard/keyboard.go new file mode 100644 index 00000000..9f5f420e --- /dev/null +++ b/emb/machine/_usb/hid/keyboard/keyboard.go @@ -0,0 +1,525 @@ +package keyboard + +import ( + "errors" + "machine" + "machine/usb/hid" +) + +// from usb-hid-keyboard.go +var ( + ErrInvalidCodepoint = errors.New("invalid Unicode codepoint") + ErrInvalidKeycode = errors.New("invalid keyboard keycode") + ErrInvalidUTF8 = errors.New("invalid UTF-8 encoding") + ErrKeypressMaximum = errors.New("maximum keypresses exceeded") +) + +var Keyboard *keyboard + +// Keyboard represents a USB HID keyboard device with support for international +// layouts and various control, system, multimedia, and consumer keycodes. +// +// Keyboard implements the io.Writer interface that translates UTF-8 encoded +// byte strings into sequences of keypress events. +type keyboard struct { + // led holds the current state of all keyboard LEDs: + // 1=NumLock 2=CapsLock 4=ScrollLock 8=Compose 16=Kana + led uint8 + + // mod holds the current state of all keyboard modifier keys: + // 1=LeftCtrl 2=LeftShift 4=LeftAlt 8=LeftGUI + // 16=RightCtrl 32=RightShift 64=RightAlt 128=RightGUI + mod uint8 + + // key holds a list of all keyboard keys currently pressed. + key [hidKeyboardKeyCount]uint8 + con [hidKeyboardConCount]uint16 + sys [hidKeyboardSysCount]uint8 + + // decode holds the current state of the UTF-8 decoder. + decode decodeState + + // wideChar holds high bits for the UTF-8 decoder. + wideChar uint16 + + buf *hid.RingBuffer + waitTxc bool +} + +// decodeState represents a state in the UTF-8 decode state machine. +type decodeState uint8 + +// Constant enumerated values of type decodeState. +const ( + decodeReset decodeState = iota + decodeByte1 + decodeByte2 + decodeByte3 +) + +func init() { + if Keyboard == nil { + Keyboard = newKeyboard() + hid.SetHandler(Keyboard) + } +} + +// New returns the USB hid-keyboard port. +// Deprecated, better to just use Port() +func New() *keyboard { + return Port() +} + +// Port returns the USB hid-keyboard port. +func Port() *keyboard { + return Keyboard +} + +func newKeyboard() *keyboard { + return &keyboard{ + buf: hid.NewRingBuffer(), + } +} + +func (kb *keyboard) TxHandler() bool { + kb.waitTxc = false + if b, ok := kb.buf.Get(); ok { + kb.waitTxc = true + hid.SendUSBPacket(b) + return true + } + return false +} + +func (kb *keyboard) RxHandler(b []byte) bool { + if len(b) >= 2 && b[0] == 2 /* ReportID */ { + kb.led = b[1] + } + return false +} + +func (kb *keyboard) tx(b []byte) { + if machine.USBDev.InitEndpointComplete { + if kb.waitTxc { + kb.buf.Put(b) + } else { + kb.waitTxc = true + hid.SendUSBPacket(b) + } + } +} + +func (kb *keyboard) NumLockLed() bool { + return kb.led&1 != 0 +} + +func (kb *keyboard) CapsLockLed() bool { + return kb.led&2 != 0 +} + +func (kb *keyboard) ScrollLockLed() bool { + return kb.led&4 != 0 +} + +func (kb *keyboard) ready() bool { + return true +} + +// Write transmits press-and-release key sequences for each Keycode translated +// from the given UTF-8 byte string. Write implements the io.Writer interface +// and conforms to all documented conventions for arguments and return values. +func (kb *keyboard) Write(b []byte) (n int, err error) { + for _, c := range b { + if err = kb.WriteByte(c); nil != err { + break + } + n += 1 + } + return +} + +// WriteByte processes a single byte from a UTF-8 byte string. This method is a +// stateful method with respect to the receiver Keyboard, meaning that its exact +// behavior will depend on the current state of its UTF-8 decode state machine: +// +// 1. If the given byte is a valid ASCII encoding (0-127), then a keypress +// sequence is immediately transmitted for the respective Keycode. +// 2. If the given byte represents the final byte in a multi-byte codepoint, +// then a keypress sequence is immediately transmitted by translating the +// multi-byte codepoint to its respective Keycode. +// 3. If the given byte appears to represent high bits for a multi-byte +// codepoint, then the bits are copied to the receiver's internal state +// machine buffer for use by a subsequent call to WriteByte() (or Write()) +// that completes the codepoint. +// 4. If the given byte is out of range, or contains illegal bits for the +// current state of the UTF-8 decoder, then the UTF-8 decode state machine +// is reset to its initial state. +// +// In cases 3 and 4, a keypress sequence is not generated and no data is +// transmitted. In case 3, additional bytes must be received via WriteByte() +// (or Write()) to complete or discard the current codepoint. +func (kb *keyboard) WriteByte(b byte) error { + switch { + case b < 0x80: + // 1-byte encoding (0x00-0x7F) + kb.decode = decodeByte1 + return kb.write(uint16(b)) + + case b < 0xC0: + // 2nd, 3rd, or 4th byte (0x80-0xBF) + b = Keycode(b).key() + switch kb.decode { + case decodeByte2: + kb.decode = decodeByte1 + return kb.write(kb.wideChar | uint16(b)) + case decodeByte3: + kb.decode = decodeByte2 + kb.wideChar |= uint16(b) << 6 + } + + case b < 0xE0: + // 2-byte encoding (0xC2-0xDF), or illegal byte 2 (0xC0-0xC1) + kb.decode = decodeByte2 + kb.wideChar = uint16(b&0x1F) << 6 + + case b < 0xF0: + // 3-byte encoding (0xE0-0xEF) + kb.decode = decodeByte3 + kb.wideChar = uint16(b&0x0F) << 12 + + default: + // 4-byte encoding unsupported (0xF0-0xF4), or illegal byte 4 (0xF5-0xFF) + kb.decode = decodeReset + return ErrInvalidUTF8 + } + return nil +} + +func (kb *keyboard) write(p uint16) error { + c := keycode(p) + if 0 == c { + return ErrInvalidCodepoint + } + if d := deadkey(c); 0 != d { + if err := kb.writeKeycode(d); nil != err { + return err + } + } + return kb.writeKeycode(c) +} + +func (kb *keyboard) writeKeycode(c Keycode) error { + var b [9]byte + b[0] = 0x02 + b[1] = c.mod() + b[2] = 0 + b[3] = c.key() + b[4] = 0 + b[5] = 0 + b[6] = 0 + b[7] = 0 + b[8] = 0 + if !kb.sendKey(false, b[:]) { + return hid.ErrHIDReportTransfer + } + + b[1] = 0 + b[3] = 0 + if !kb.sendKey(false, b[:]) { + return hid.ErrHIDReportTransfer + } + return nil +} + +// Press transmits a press-and-release sequence for the given Keycode, which +// simulates a discrete keypress event. +// +// The following values of Keycode are supported: +// +// 0x0020 - 0x007F ASCII (U+0020 to U+007F) [USES LAYOUT] +// 0x0080 - 0xC1FF Unicode (U+0080 to U+C1FF) [USES LAYOUT] +// 0xC200 - 0xDFFF UTF-8 packed (U+0080 to U+07FF) [USES LAYOUT] +// 0xE000 - 0xE0FF Modifier key (bitmap, 8 keys, Shift/Ctrl/Alt/GUI) +// 0xE200 - 0xE2FF System key (HID usage code, page 1) +// 0xE400 - 0xE7FF Media/Consumer key (HID usage code, page 12) +// 0xF000 - 0xFFFF Normal key (HID usage code, page 7) +func (kb *keyboard) Press(c Keycode) error { + if err := kb.Down(c); nil != err { + return err + } + return kb.Up(c) +} + +func (kb *keyboard) sendKey(consumer bool, b []byte) bool { + kb.tx(b) + return true +} + +func (kb *keyboard) keyboardSendKeys(consumer bool) bool { + var b [9]byte + + if !consumer { + b[0] = 0x02 // REPORT_ID + b[1] = kb.mod + b[2] = 0x02 + b[3] = kb.key[0] + b[4] = kb.key[1] + b[5] = kb.key[2] + b[6] = kb.key[3] + b[7] = kb.key[4] + b[8] = kb.key[5] + return kb.sendKey(consumer, b[:]) + + } else { + b[0] = 0x03 // REPORT_ID + b[1] = uint8(kb.con[0]) + b[2] = uint8((kb.con[0] & 0x0300) >> 8) + + return kb.sendKey(consumer, b[:3]) + } +} + +// Down transmits a key-down event for the given Keycode. +// +// The host will interpret the key as being held down continuously until a +// corresponding key-up event is transmitted, e.g., via method Up(). +// +// See godoc comment on method Press() for details on what input is accepted and +// how it is interpreted. +func (kb *keyboard) Down(c Keycode) error { + var res uint8 + msb := c >> 8 + if msb >= 0xC2 { + if msb < 0xE0 { + c = ((msb & 0x1F) << 6) | Keycode(c.key()) + } else { + switch msb { + case 0xF0: + return kb.down(uint8(c), 0) + + case 0xE0: + return kb.down(0, uint8(c)) + + case 0xE2: + return kb.downSys(uint8(c)) + + default: + if 0xE4 <= msb && msb <= 0xE7 { + return kb.downCon(uint16(c & 0x03FF)) + } + return ErrInvalidKeycode + } + } + } + c = keycode(uint16(c)) + if 0 == c { + return ErrInvalidCodepoint + } + if d := deadkey(c); 0 != d { + res = kb.mod + if 0 != res { + kb.mod = 0 + kb.keyboardSendKeys(false) + } + kb.down(d.key(), d.mod()) + kb.up(d.key(), d.mod()) + } + return kb.down(c.key(), c.mod()|res) +} + +func (kb *keyboard) down(key uint8, mod uint8) error { + send := false + if 0 != mod { + if kb.mod&mod != mod { + kb.mod |= mod + send = true + } + } + if 0 != key { + for _, k := range kb.key { + if k == key { + goto end + } + } + for i, k := range kb.key { + if 0 == k { + kb.key[i] = key + send = true + goto end + } + } + return ErrKeypressMaximum + } +end: + if send { + if !kb.keyboardSendKeys(false) { + return hid.ErrHIDReportTransfer + } + } + return nil +} + +func (kb *keyboard) downCon(key uint16) error { + if 0 == key { + return ErrInvalidKeycode + } + for _, k := range kb.con { + if key == k { + return nil // already pressed + } + } + for i, k := range kb.con { + if 0 == k { + kb.con[i] = key + if !kb.keyboardSendKeys(true) { + return hid.ErrHIDReportTransfer + } + return nil + } + } + return ErrKeypressMaximum +} + +func (kb *keyboard) downSys(key uint8) error { + if 0 == key { + return ErrInvalidKeycode + } + for _, k := range kb.sys { + if key == k { + return nil // already pressed + } + } + for i, k := range kb.sys { + if 0 == k { + kb.sys[i] = key + if !kb.keyboardSendKeys(true) { + return hid.ErrHIDReportTransfer + } + return nil + } + } + return ErrKeypressMaximum +} + +// Up transmits a key-up event for the given Keycode. +// +// See godoc comment on method Press() for details on what input is accepted and +// how it is interpreted. +func (kb *keyboard) Up(c Keycode) error { + msb := c >> 8 + if msb >= 0xC2 { + if msb < 0xE0 { + c = ((msb & 0x1F) << 6) | Keycode(c.key()) + } else { + switch msb { + case 0xF0: + return kb.up(uint8(c), 0) + + case 0xE0: + return kb.up(0, uint8(c)) + + case 0xE2: + return kb.upSys(uint8(c)) + + default: + if 0xE4 <= msb && msb <= 0xE7 { + return kb.upCon(uint16(c & 0x03FF)) + } + return ErrInvalidKeycode + } + } + } + c = keycode(uint16(c)) + if 0 == c { + return ErrInvalidCodepoint + } + return kb.up(c.key(), c.mod()) +} + +// Release transmits a key-up event for all keyboard keys currently pressed as +// if the user removed his/her hands from the keyboard entirely. +func (kb *keyboard) Release() error { + + bits := uint16(kb.mod) + kb.mod = 0 + for i, k := range kb.key { + bits |= uint16(k) + kb.key[i] = 0 + } + if 0 != bits { + if !kb.keyboardSendKeys(false) { + return hid.ErrHIDReportTransfer + } + } + bits = 0 + for i, k := range kb.con { + bits |= k + kb.con[i] = 0 + } + for i, k := range kb.sys { + bits |= uint16(k) + kb.sys[i] = 0 + } + if 0 != bits { + if !kb.keyboardSendKeys(true) { + return hid.ErrHIDReportTransfer + } + } + return nil +} + +func (kb *keyboard) up(key uint8, mod uint8) error { + send := false + if 0 != mod { + if kb.mod&mod != 0 { + kb.mod &^= mod + send = true + } + } + if 0 != key { + for i, k := range kb.key { + if key == k { + kb.key[i] = 0 + send = true + } + } + } + if send { + if !kb.keyboardSendKeys(false) { + return hid.ErrHIDReportTransfer + } + } + return nil +} + +func (kb *keyboard) upCon(key uint16) error { + if 0 == key { + return ErrInvalidKeycode + } + for i, k := range kb.con { + if key == k { + kb.con[i] = 0 + if !kb.keyboardSendKeys(true) { + return hid.ErrHIDReportTransfer + } + return nil + } + } + return nil +} + +func (kb *keyboard) upSys(key uint8) error { + if 0 == key { + return ErrInvalidKeycode + } + for i, k := range kb.sys { + if key == k { + kb.sys[i] = 0 + if !kb.keyboardSendKeys(true) { + return hid.ErrHIDReportTransfer + } + return nil + } + } + return nil +} diff --git a/emb/machine/_usb/hid/keyboard/keycode.go b/emb/machine/_usb/hid/keyboard/keycode.go new file mode 100644 index 00000000..762f65b9 --- /dev/null +++ b/emb/machine/_usb/hid/keyboard/keycode.go @@ -0,0 +1,534 @@ +package keyboard + +// Keycode is a package-defined bitmap used to encode the value of a given key. +type Keycode uint16 + +// keycode returns the given Unicode codepoint translated to a Keycode sequence. +// Unicode codepoints greater than U+FFFF are unsupported. +// +//go:inline +func keycode(p uint16) Keycode { + if p < 0x80 { + return ascii[p] + } else if p >= 0xA0 && p < 0x0100 { + return iso88591[p-0xA0] + } else if uint16(UNICODE20AC) == p { + return UNICODE20AC.mask() + } + return 0 +} + +//go:inline +func deadkey(c Keycode) Keycode { + switch c & deadkeysMask { + case acuteAccentBits: + return deadkeyAcuteAccent + case circumflexBits: + return deadkeyCircumflex + case diaeresisBits: + return deadkeyDiaeresis + case graveAccentBits: + return deadkeyGraveAccent + case tildeBits: + return deadkeyTilde + } + return 0 +} + +//go:inline +func (c Keycode) mask() Keycode { return c & keycodeMask } + +//go:inline +func (c Keycode) key() uint8 { return uint8(c & keyMask) } + +//go:inline +func (c Keycode) mod() uint8 { + var m Keycode + if 0 != c&shiftMask { + m |= KeyModifierShift + } + if 0 != c&altgrMask { + m |= KeyModifierRightAlt + } + return uint8(m) +} + +//go:inline +func (c Keycode) Shift() Keycode { return c | KeyModifierShift } + +const ( + hidKeyboardKeyCount = 6 // Max number of simultaneous keypresses + hidKeyboardSysCount = 3 + hidKeyboardConCount = 4 +) + +// Keycodes common to all Keyboard layouts +const ( + KeyModifierCtrl Keycode = 0x01 | 0xE000 + KeyModifierShift Keycode = 0x02 | 0xE000 + KeyModifierAlt Keycode = 0x04 | 0xE000 + KeyModifierGUI Keycode = 0x08 | 0xE000 + KeyModifierLeftCtrl Keycode = 0x01 | 0xE000 + KeyModifierLeftShift Keycode = 0x02 | 0xE000 + KeyModifierLeftAlt Keycode = 0x04 | 0xE000 + KeyModifierLeftGUI Keycode = 0x08 | 0xE000 + KeyModifierRightCtrl Keycode = 0x10 | 0xE000 + KeyModifierRightShift Keycode = 0x20 | 0xE000 + KeyModifierRightAlt Keycode = 0x40 | 0xE000 + KeyModifierRightGUI Keycode = 0x80 | 0xE000 + + // KeySystemXXX is not supported now + KeySystemPowerDown Keycode = 0x81 | 0xE200 + KeySystemSleep Keycode = 0x82 | 0xE200 + KeySystemWakeUp Keycode = 0x83 | 0xE200 + + KeyMediaPlay Keycode = 0xB0 | 0xE400 + KeyMediaPause Keycode = 0xB1 | 0xE400 + KeyMediaRecord Keycode = 0xB2 | 0xE400 + KeyMediaFastForward Keycode = 0xB3 | 0xE400 + KeyMediaRewind Keycode = 0xB4 | 0xE400 + KeyMediaNextTrack Keycode = 0xB5 | 0xE400 + KeyMediaPrevTrack Keycode = 0xB6 | 0xE400 + KeyMediaStop Keycode = 0xB7 | 0xE400 + KeyMediaEject Keycode = 0xB8 | 0xE400 + KeyMediaRandomPlay Keycode = 0xB9 | 0xE400 + KeyMediaPlayPause Keycode = 0xCD | 0xE400 + KeyMediaPlaySkip Keycode = 0xCE | 0xE400 + KeyMediaMute Keycode = 0xE2 | 0xE400 + KeyMediaVolumeInc Keycode = 0xE9 | 0xE400 + KeyMediaVolumeDec Keycode = 0xEA | 0xE400 + + KeyA Keycode = 4 | 0xF000 + KeyB Keycode = 5 | 0xF000 + KeyC Keycode = 6 | 0xF000 + KeyD Keycode = 7 | 0xF000 + KeyE Keycode = 8 | 0xF000 + KeyF Keycode = 9 | 0xF000 + KeyG Keycode = 10 | 0xF000 + KeyH Keycode = 11 | 0xF000 + KeyI Keycode = 12 | 0xF000 + KeyJ Keycode = 13 | 0xF000 + KeyK Keycode = 14 | 0xF000 + KeyL Keycode = 15 | 0xF000 + KeyM Keycode = 16 | 0xF000 + KeyN Keycode = 17 | 0xF000 + KeyO Keycode = 18 | 0xF000 + KeyP Keycode = 19 | 0xF000 + KeyQ Keycode = 20 | 0xF000 + KeyR Keycode = 21 | 0xF000 + KeyS Keycode = 22 | 0xF000 + KeyT Keycode = 23 | 0xF000 + KeyU Keycode = 24 | 0xF000 + KeyV Keycode = 25 | 0xF000 + KeyW Keycode = 26 | 0xF000 + KeyX Keycode = 27 | 0xF000 + KeyY Keycode = 28 | 0xF000 + KeyZ Keycode = 29 | 0xF000 + Key1 Keycode = 30 | 0xF000 + Key2 Keycode = 31 | 0xF000 + Key3 Keycode = 32 | 0xF000 + Key4 Keycode = 33 | 0xF000 + Key5 Keycode = 34 | 0xF000 + Key6 Keycode = 35 | 0xF000 + Key7 Keycode = 36 | 0xF000 + Key8 Keycode = 37 | 0xF000 + Key9 Keycode = 38 | 0xF000 + Key0 Keycode = 39 | 0xF000 + KeyEnter Keycode = 40 | 0xF000 + KeyEsc Keycode = 41 | 0xF000 + KeyBackspace Keycode = 42 | 0xF000 + KeyTab Keycode = 43 | 0xF000 + KeySpace Keycode = 44 | 0xF000 + KeyMinus Keycode = 45 | 0xF000 + KeyEqual Keycode = 46 | 0xF000 + KeyLeftBrace Keycode = 47 | 0xF000 + KeyRightBrace Keycode = 48 | 0xF000 + KeyBackslash Keycode = 49 | 0xF000 + KeyNonUsNum Keycode = 50 | 0xF000 + KeySemicolon Keycode = 51 | 0xF000 + KeyQuote Keycode = 52 | 0xF000 + KeyTilde Keycode = 53 | 0xF000 + KeyComma Keycode = 54 | 0xF000 + KeyPeriod Keycode = 55 | 0xF000 + KeySlash Keycode = 56 | 0xF000 + KeyCapsLock Keycode = 57 | 0xF000 + KeyF1 Keycode = 58 | 0xF000 + KeyF2 Keycode = 59 | 0xF000 + KeyF3 Keycode = 60 | 0xF000 + KeyF4 Keycode = 61 | 0xF000 + KeyF5 Keycode = 62 | 0xF000 + KeyF6 Keycode = 63 | 0xF000 + KeyF7 Keycode = 64 | 0xF000 + KeyF8 Keycode = 65 | 0xF000 + KeyF9 Keycode = 66 | 0xF000 + KeyF10 Keycode = 67 | 0xF000 + KeyF11 Keycode = 68 | 0xF000 + KeyF12 Keycode = 69 | 0xF000 + KeyPrintscreen Keycode = 70 | 0xF000 + KeyScrollLock Keycode = 71 | 0xF000 + KeyPause Keycode = 72 | 0xF000 + KeyInsert Keycode = 73 | 0xF000 + KeyHome Keycode = 74 | 0xF000 + KeyPageUp Keycode = 75 | 0xF000 + KeyDelete Keycode = 76 | 0xF000 + KeyEnd Keycode = 77 | 0xF000 + KeyPageDown Keycode = 78 | 0xF000 + KeyRight Keycode = 79 | 0xF000 + KeyLeft Keycode = 80 | 0xF000 + KeyDown Keycode = 81 | 0xF000 + KeyUp Keycode = 82 | 0xF000 + KeyNumLock Keycode = 83 | 0xF000 + KeypadSlash Keycode = 84 | 0xF000 + KeypadAsterisk Keycode = 85 | 0xF000 + KeypadMinus Keycode = 86 | 0xF000 + KeypadPlus Keycode = 87 | 0xF000 + KeypadEnter Keycode = 88 | 0xF000 + Keypad1 Keycode = 89 | 0xF000 + Keypad2 Keycode = 90 | 0xF000 + Keypad3 Keycode = 91 | 0xF000 + Keypad4 Keycode = 92 | 0xF000 + Keypad5 Keycode = 93 | 0xF000 + Keypad6 Keycode = 94 | 0xF000 + Keypad7 Keycode = 95 | 0xF000 + Keypad8 Keycode = 96 | 0xF000 + Keypad9 Keycode = 97 | 0xF000 + Keypad0 Keycode = 98 | 0xF000 + KeypadPeriod Keycode = 99 | 0xF000 + KeyNonUSBS Keycode = 100 | 0xF000 + KeyMenu Keycode = 101 | 0xF000 + KeyF13 Keycode = 104 | 0xF000 + KeyF14 Keycode = 105 | 0xF000 + KeyF15 Keycode = 106 | 0xF000 + KeyF16 Keycode = 107 | 0xF000 + KeyF17 Keycode = 108 | 0xF000 + KeyF18 Keycode = 109 | 0xF000 + KeyF19 Keycode = 110 | 0xF000 + KeyF20 Keycode = 111 | 0xF000 + KeyF21 Keycode = 112 | 0xF000 + KeyF22 Keycode = 113 | 0xF000 + KeyF23 Keycode = 114 | 0xF000 + KeyF24 Keycode = 115 | 0xF000 + + KeyUpArrow Keycode = KeyUp + KeyDownArrow Keycode = KeyDown + KeyLeftArrow Keycode = KeyLeft + KeyRightArrow Keycode = KeyRight + KeyReturn Keycode = KeyEnter + KeyLeftCtrl Keycode = KeyModifierLeftCtrl + KeyLeftShift Keycode = KeyModifierLeftShift + KeyLeftAlt Keycode = KeyModifierLeftAlt + KeyLeftGUI Keycode = KeyModifierLeftGUI + KeyRightCtrl Keycode = KeyModifierRightCtrl + KeyRightShift Keycode = KeyModifierRightShift + KeyRightAlt Keycode = KeyModifierRightAlt + KeyRightGUI Keycode = KeyModifierRightGUI +) + +// Keycodes for layout US English (0x0904) +const ( + keycodeMask Keycode = 0x07FF + keyMask Keycode = 0x003F + + shiftMask Keycode = 0x0040 + altgrMask Keycode = 0x0080 + deadkeysMask Keycode = 0x0700 + circumflexBits Keycode = 0x0100 + acuteAccentBits Keycode = 0x0200 + graveAccentBits Keycode = 0x0300 + tildeBits Keycode = 0x0400 + diaeresisBits Keycode = 0x0500 + deadkeyCircumflex Keycode = Key6 | shiftMask + deadkeyAcuteAccent Keycode = KeyQuote + deadkeyGraveAccent Keycode = KeyTilde + deadkeyTilde Keycode = KeyTilde | shiftMask + deadkeyDiaeresis Keycode = KeyQuote | shiftMask + + ASCII00 Keycode = 0 // 0 NUL + ASCII01 Keycode = 0 // 1 SOH + ASCII02 Keycode = 0 // 2 STX + ASCII03 Keycode = 0 // 3 ETX + ASCII04 Keycode = 0 // 4 EOT + ASCII05 Keycode = 0 // 5 ENQ + ASCII06 Keycode = 0 // 6 ACK + ASCII07 Keycode = 0 // 7 BEL + ASCII08 Keycode = KeyBackspace // 8 BS + ASCII09 Keycode = KeyTab // 9 TAB + ASCII0A Keycode = KeyEnter // 10 LF + ASCII0B Keycode = 0 // 11 VT + ASCII0C Keycode = 0 // 12 FF + ASCII0D Keycode = 0 // 13 CR + ASCII0E Keycode = 0 // 14 SO + ASCII0F Keycode = 0 // 15 SI + ASCII10 Keycode = 0 // 16 DEL + ASCII11 Keycode = 0 // 17 DC1 + ASCII12 Keycode = 0 // 18 DC2 + ASCII13 Keycode = 0 // 19 DC3 + ASCII14 Keycode = 0 // 20 DC4 + ASCII15 Keycode = 0 // 21 NAK + ASCII16 Keycode = 0 // 22 SYN + ASCII17 Keycode = 0 // 23 ETB + ASCII18 Keycode = 0 // 24 CAN + ASCII19 Keycode = 0 // 25 EM + ASCII1A Keycode = 0 // 26 SUB + ASCII1B Keycode = 0 // 27 ESC + ASCII1C Keycode = 0 // 28 FS + ASCII1D Keycode = 0 // 29 GS + ASCII1E Keycode = 0 // 30 RS + ASCII1F Keycode = 0 // 31 US + + ASCII20 Keycode = KeySpace // 32 SPACE + ASCII21 Keycode = Key1 | shiftMask // 33 ! + ASCII22 Keycode = diaeresisBits | KeySpace // 34 " + ASCII23 Keycode = Key3 | shiftMask // 35 # + ASCII24 Keycode = Key4 | shiftMask // 36 $ + ASCII25 Keycode = Key5 | shiftMask // 37 % + ASCII26 Keycode = Key7 | shiftMask // 38 & + ASCII27 Keycode = acuteAccentBits | KeySpace // 39 ' + ASCII28 Keycode = Key9 | shiftMask // 40 ( + ASCII29 Keycode = Key0 | shiftMask // 41 ) + ASCII2A Keycode = Key8 | shiftMask // 42 * + ASCII2B Keycode = KeyEqual | shiftMask // 43 + + ASCII2C Keycode = KeyComma // 44 , + ASCII2D Keycode = KeyMinus // 45 - + ASCII2E Keycode = KeyPeriod // 46 . + ASCII2F Keycode = KeySlash // 47 / + ASCII30 Keycode = Key0 // 48 0 + ASCII31 Keycode = Key1 // 49 1 + ASCII32 Keycode = Key2 // 50 2 + ASCII33 Keycode = Key3 // 51 3 + ASCII34 Keycode = Key4 // 52 4 + ASCII35 Keycode = Key5 // 53 5 + ASCII36 Keycode = Key6 // 54 6 + ASCII37 Keycode = Key7 // 55 7 + ASCII38 Keycode = Key8 // 55 8 + ASCII39 Keycode = Key9 // 57 9 + ASCII3A Keycode = KeySemicolon | shiftMask // 58 : + ASCII3B Keycode = KeySemicolon // 59 ; + ASCII3C Keycode = KeyComma | shiftMask // 60 < + ASCII3D Keycode = KeyEqual // 61 = + ASCII3E Keycode = KeyPeriod | shiftMask // 62 > + ASCII3F Keycode = KeySlash | shiftMask // 63 ? + ASCII40 Keycode = Key2 | shiftMask // 64 @ + ASCII41 Keycode = KeyA | shiftMask // 65 A + ASCII42 Keycode = KeyB | shiftMask // 66 B + ASCII43 Keycode = KeyC | shiftMask // 67 C + ASCII44 Keycode = KeyD | shiftMask // 68 D + ASCII45 Keycode = KeyE | shiftMask // 69 E + ASCII46 Keycode = KeyF | shiftMask // 70 F + ASCII47 Keycode = KeyG | shiftMask // 71 G + ASCII48 Keycode = KeyH | shiftMask // 72 H + ASCII49 Keycode = KeyI | shiftMask // 73 I + ASCII4A Keycode = KeyJ | shiftMask // 74 J + ASCII4B Keycode = KeyK | shiftMask // 75 K + ASCII4C Keycode = KeyL | shiftMask // 76 L + ASCII4D Keycode = KeyM | shiftMask // 77 M + ASCII4E Keycode = KeyN | shiftMask // 78 N + ASCII4F Keycode = KeyO | shiftMask // 79 O + ASCII50 Keycode = KeyP | shiftMask // 80 P + ASCII51 Keycode = KeyQ | shiftMask // 81 Q + ASCII52 Keycode = KeyR | shiftMask // 82 R + ASCII53 Keycode = KeyS | shiftMask // 83 S + ASCII54 Keycode = KeyT | shiftMask // 84 T + ASCII55 Keycode = KeyU | shiftMask // 85 U + ASCII56 Keycode = KeyV | shiftMask // 86 V + ASCII57 Keycode = KeyW | shiftMask // 87 W + ASCII58 Keycode = KeyX | shiftMask // 88 X + ASCII59 Keycode = KeyY | shiftMask // 89 Y + ASCII5A Keycode = KeyZ | shiftMask // 90 Z + ASCII5B Keycode = KeyLeftBrace // 91 [ + ASCII5C Keycode = KeyBackslash // 92 \ + ASCII5D Keycode = KeyRightBrace // 93 ] + ASCII5E Keycode = circumflexBits | KeySpace // 94 ^ + ASCII5F Keycode = KeyMinus | shiftMask // 95 + ASCII60 Keycode = graveAccentBits | KeySpace // 96 ` + ASCII61 Keycode = KeyA // 97 a + ASCII62 Keycode = KeyB // 98 b + ASCII63 Keycode = KeyC // 99 c + ASCII64 Keycode = KeyD // 100 d + ASCII65 Keycode = KeyE // 101 e + ASCII66 Keycode = KeyF // 102 f + ASCII67 Keycode = KeyG // 103 g + ASCII68 Keycode = KeyH // 104 h + ASCII69 Keycode = KeyI // 105 i + ASCII6A Keycode = KeyJ // 106 j + ASCII6B Keycode = KeyK // 107 k + ASCII6C Keycode = KeyL // 108 l + ASCII6D Keycode = KeyM // 109 m + ASCII6E Keycode = KeyN // 110 n + ASCII6F Keycode = KeyO // 111 o + ASCII70 Keycode = KeyP // 112 p + ASCII71 Keycode = KeyQ // 113 q + ASCII72 Keycode = KeyR // 114 r + ASCII73 Keycode = KeyS // 115 s + ASCII74 Keycode = KeyT // 116 t + ASCII75 Keycode = KeyU // 117 u + ASCII76 Keycode = KeyV // 118 v + ASCII77 Keycode = KeyW // 119 w + ASCII78 Keycode = KeyX // 120 x + ASCII79 Keycode = KeyY // 121 y + ASCII7A Keycode = KeyZ // 122 z + ASCII7B Keycode = KeyLeftBrace | shiftMask // 123 { + ASCII7C Keycode = KeyBackslash | shiftMask // 124 | + ASCII7D Keycode = KeyRightBrace | shiftMask // 125 } + ASCII7E Keycode = tildeBits | KeySpace // 126 ~ + ASCII7F Keycode = KeyBackspace // 127 DEL + ISO88591A0 Keycode = KeySpace // 160 Nonbreakng Space + ISO88591A1 Keycode = Key1 | altgrMask // 161 ¡ Inverted Exclamation + ISO88591A2 Keycode = KeyC | altgrMask | shiftMask // 162 ¢ Cent SIGN + ISO88591A3 Keycode = Key4 | altgrMask | shiftMask // 163 £ Pound Sign + ISO88591A4 Keycode = Key4 | altgrMask // 164 ¤ Currency or Euro Sign + ISO88591A5 Keycode = KeyMinus | altgrMask // 165 ¥ YEN SIGN + ISO88591A6 Keycode = KeyBackslash | altgrMask | shiftMask // 166 ¦ BROKEN BAR ?? + ISO88591A7 Keycode = KeyS | altgrMask | shiftMask // 167 § SECTION SIGN + ISO88591A8 Keycode = KeyQuote | altgrMask | shiftMask // 168 ¨ DIAERESIS + ISO88591A9 Keycode = KeyC | altgrMask // 169 © COPYRIGHT SIGN + ISO88591AA Keycode = 0 // 170 ª FEMININE ORDINAL + ISO88591AB Keycode = KeyLeftBrace | altgrMask // 171 « LEFT DOUBLE ANGLE QUOTE + ISO88591AC Keycode = KeyBackslash | altgrMask // 172 ¬ NOT SIGN ?? + ISO88591AD Keycode = 0 // 173 SOFT HYPHEN + ISO88591AE Keycode = KeyR | altgrMask // 174 ® REGISTERED SIGN + ISO88591AF Keycode = 0 // 175 ¯ MACRON + ISO88591B0 Keycode = KeySemicolon | altgrMask | shiftMask // 176 ° DEGREE SIGN + ISO88591B1 Keycode = 0 // 177 ± PLUS-MINUS SIGN + ISO88591B2 Keycode = Key2 | altgrMask // 178 ² SUPERSCRIPT TWO + ISO88591B3 Keycode = Key3 | altgrMask // 179 ³ SUPERSCRIPT THREE + ISO88591B4 Keycode = KeyQuote | altgrMask // 180 ´ ACUTE ACCENT + ISO88591B5 Keycode = KeyM | altgrMask // 181 µ MICRO SIGN + ISO88591B6 Keycode = KeySemicolon | altgrMask // 182 ¶ PILCROW SIGN + ISO88591B7 Keycode = 0 // 183 · MIDDLE DOT + ISO88591B8 Keycode = 0 // 184 ¸ CEDILLA + ISO88591B9 Keycode = Key1 | altgrMask | shiftMask // 185 ¹ SUPERSCRIPT ONE + ISO88591BA Keycode = 0 // 186 º MASCULINE ORDINAL + ISO88591BB Keycode = KeyRightBrace | altgrMask // 187 » RIGHT DOUBLE ANGLE QUOTE + ISO88591BC Keycode = Key6 | altgrMask // 188 ¼ FRACTION ONE QUARTER + ISO88591BD Keycode = Key7 | altgrMask // 189 ½ FRACTION ONE HALF + ISO88591BE Keycode = Key8 | altgrMask // 190 ¾ FRACTION THREE QUARTERS + ISO88591BF Keycode = KeySlash | altgrMask // 191 ¿ INVERTED QUESTION MARK + ISO88591C0 Keycode = graveAccentBits | KeyA | shiftMask // 192 À A GRAVE + ISO88591C1 Keycode = KeyA | altgrMask | shiftMask // 193 Á A ACUTE + ISO88591C2 Keycode = circumflexBits | KeyA | shiftMask // 194 Â A CIRCUMFLEX + ISO88591C3 Keycode = tildeBits | KeyA | shiftMask // 195 Ã A TILDE + ISO88591C4 Keycode = KeyQ | altgrMask | shiftMask // 196 Ä A DIAERESIS + ISO88591C5 Keycode = KeyW | altgrMask | shiftMask // 197 Å A RING ABOVE + ISO88591C6 Keycode = KeyZ | altgrMask | shiftMask // 198 Æ AE + ISO88591C7 Keycode = KeyComma | altgrMask | shiftMask // 199 Ç C CEDILLA + ISO88591C8 Keycode = graveAccentBits | KeyE | shiftMask // 200 È E GRAVE + ISO88591C9 Keycode = KeyE | altgrMask | shiftMask // 201 É E ACUTE + ISO88591CA Keycode = circumflexBits | KeyE | shiftMask // 202 Ê E CIRCUMFLEX + ISO88591CB Keycode = diaeresisBits | KeyE | shiftMask // 203 Ë E DIAERESIS + ISO88591CC Keycode = graveAccentBits | KeyI | shiftMask // 204 Ì I GRAVE + ISO88591CD Keycode = KeyI | altgrMask | shiftMask // 205 Í I ACUTE + ISO88591CE Keycode = circumflexBits | KeyI | shiftMask // 206 Î I CIRCUMFLEX + ISO88591CF Keycode = diaeresisBits | KeyI | shiftMask // 207 Ï I DIAERESIS + ISO88591D0 Keycode = KeyD | altgrMask | shiftMask // 208 Ð ETH + ISO88591D1 Keycode = KeyN | altgrMask | shiftMask // 209 Ñ N TILDE + ISO88591D2 Keycode = graveAccentBits | KeyO | shiftMask // 210 Ò O GRAVE + ISO88591D3 Keycode = KeyO | altgrMask | shiftMask // 211 Ó O ACUTE + ISO88591D4 Keycode = circumflexBits | KeyO | shiftMask // 212 Ô O CIRCUMFLEX + ISO88591D5 Keycode = tildeBits | KeyO | shiftMask // 213 Õ O TILDE + ISO88591D6 Keycode = KeyP | altgrMask | shiftMask // 214 Ö O DIAERESIS + ISO88591D7 Keycode = KeyEqual | altgrMask // 215 × MULTIPLICATION + ISO88591D8 Keycode = KeyL | altgrMask | shiftMask // 216 Ø O STROKE + ISO88591D9 Keycode = graveAccentBits | KeyU | shiftMask // 217 Ù U GRAVE + ISO88591DA Keycode = KeyU | altgrMask | shiftMask // 218 Ú U ACUTE + ISO88591DB Keycode = circumflexBits | KeyU | shiftMask // 219 Û U CIRCUMFLEX + ISO88591DC Keycode = KeyY | altgrMask | shiftMask // 220 Ü U DIAERESIS + ISO88591DD Keycode = acuteAccentBits | KeyY | shiftMask // 221 Ý Y ACUTE + ISO88591DE Keycode = KeyT | altgrMask | shiftMask // 222 Þ THORN + ISO88591DF Keycode = KeyS | altgrMask // 223 ß SHARP S + ISO88591E0 Keycode = graveAccentBits | KeyA // 224 à a GRAVE + ISO88591E1 Keycode = KeyA | altgrMask // 225 á a ACUTE + ISO88591E2 Keycode = circumflexBits | KeyA // 226 â a CIRCUMFLEX + ISO88591E3 Keycode = tildeBits | KeyA // 227 ã a TILDE + ISO88591E4 Keycode = diaeresisBits | KeyA // 228 ä a DIAERESIS + ISO88591E5 Keycode = KeyW | altgrMask // 229 å a RING ABOVE + ISO88591E6 Keycode = KeyZ | altgrMask // 230 æ ae + ISO88591E7 Keycode = KeyComma | altgrMask // 231 ç c CEDILLA + ISO88591E8 Keycode = graveAccentBits | KeyE // 232 è e GRAVE + ISO88591E9 Keycode = acuteAccentBits | KeyE // 233 é e ACUTE + ISO88591EA Keycode = circumflexBits | KeyE // 234 ê e CIRCUMFLEX + ISO88591EB Keycode = diaeresisBits | KeyE // 235 ë e DIAERESIS + ISO88591EC Keycode = graveAccentBits | KeyI // 236 ì i GRAVE + ISO88591ED Keycode = KeyI | altgrMask // 237 í i ACUTE + ISO88591EE Keycode = circumflexBits | KeyI // 238 î i CIRCUMFLEX + ISO88591EF Keycode = diaeresisBits | KeyI // 239 ï i DIAERESIS + ISO88591F0 Keycode = KeyD | altgrMask // 240 ð ETH + ISO88591F1 Keycode = KeyN | altgrMask // 241 ñ n TILDE + ISO88591F2 Keycode = graveAccentBits | KeyO // 242 ò o GRAVE + ISO88591F3 Keycode = KeyO | altgrMask // 243 ó o ACUTE + ISO88591F4 Keycode = circumflexBits | KeyO // 244 ô o CIRCUMFLEX + ISO88591F5 Keycode = tildeBits | KeyO // 245 õ o TILDE + ISO88591F6 Keycode = KeyP | altgrMask // 246 ö o DIAERESIS + ISO88591F7 Keycode = KeyEqual | altgrMask | shiftMask // 247 ÷ DIVISION + ISO88591F8 Keycode = KeyL | altgrMask // 248 ø o STROKE + ISO88591F9 Keycode = graveAccentBits | KeyU // 249 ù u GRAVE + ISO88591FA Keycode = KeyU | altgrMask // 250 ú u ACUTE + ISO88591FB Keycode = circumflexBits | KeyU // 251 û u CIRCUMFLEX + ISO88591FC Keycode = KeyY | altgrMask // 252 ü u DIAERESIS + ISO88591FD Keycode = acuteAccentBits | KeyY // 253 ý y ACUTE + ISO88591FE Keycode = KeyT | altgrMask // 254 þ THORN + ISO88591FF Keycode = diaeresisBits | KeyY // 255 ÿ y DIAERESIS + UNICODE20AC Keycode = Key5 | altgrMask // 20AC € Euro Sign +) + +var ascii = [...]Keycode{ + ASCII00.mask(), ASCII01.mask(), ASCII02.mask(), ASCII03.mask(), + ASCII04.mask(), ASCII05.mask(), ASCII06.mask(), ASCII07.mask(), + ASCII08.mask(), ASCII09.mask(), ASCII0A.mask(), ASCII0B.mask(), + ASCII0C.mask(), ASCII0D.mask(), ASCII0E.mask(), ASCII0F.mask(), + ASCII10.mask(), ASCII11.mask(), ASCII12.mask(), ASCII13.mask(), + ASCII14.mask(), ASCII15.mask(), ASCII16.mask(), ASCII17.mask(), + ASCII18.mask(), ASCII19.mask(), ASCII1A.mask(), ASCII1B.mask(), + ASCII1C.mask(), ASCII1D.mask(), ASCII1E.mask(), ASCII1F.mask(), + ASCII20.mask(), ASCII21.mask(), ASCII22.mask(), ASCII23.mask(), + ASCII24.mask(), ASCII25.mask(), ASCII26.mask(), ASCII27.mask(), + ASCII28.mask(), ASCII29.mask(), ASCII2A.mask(), ASCII2B.mask(), + ASCII2C.mask(), ASCII2D.mask(), ASCII2E.mask(), ASCII2F.mask(), + ASCII30.mask(), ASCII31.mask(), ASCII32.mask(), ASCII33.mask(), + ASCII34.mask(), ASCII35.mask(), ASCII36.mask(), ASCII37.mask(), + ASCII38.mask(), ASCII39.mask(), ASCII3A.mask(), ASCII3B.mask(), + ASCII3C.mask(), ASCII3D.mask(), ASCII3E.mask(), ASCII3F.mask(), + ASCII40.mask(), ASCII41.mask(), ASCII42.mask(), ASCII43.mask(), + ASCII44.mask(), ASCII45.mask(), ASCII46.mask(), ASCII47.mask(), + ASCII48.mask(), ASCII49.mask(), ASCII4A.mask(), ASCII4B.mask(), + ASCII4C.mask(), ASCII4D.mask(), ASCII4E.mask(), ASCII4F.mask(), + ASCII50.mask(), ASCII51.mask(), ASCII52.mask(), ASCII53.mask(), + ASCII54.mask(), ASCII55.mask(), ASCII56.mask(), ASCII57.mask(), + ASCII58.mask(), ASCII59.mask(), ASCII5A.mask(), ASCII5B.mask(), + ASCII5C.mask(), ASCII5D.mask(), ASCII5E.mask(), ASCII5F.mask(), + ASCII60.mask(), ASCII61.mask(), ASCII62.mask(), ASCII63.mask(), + ASCII64.mask(), ASCII65.mask(), ASCII66.mask(), ASCII67.mask(), + ASCII68.mask(), ASCII69.mask(), ASCII6A.mask(), ASCII6B.mask(), + ASCII6C.mask(), ASCII6D.mask(), ASCII6E.mask(), ASCII6F.mask(), + ASCII70.mask(), ASCII71.mask(), ASCII72.mask(), ASCII73.mask(), + ASCII74.mask(), ASCII75.mask(), ASCII76.mask(), ASCII77.mask(), + ASCII78.mask(), ASCII79.mask(), ASCII7A.mask(), ASCII7B.mask(), + ASCII7C.mask(), ASCII7D.mask(), ASCII7E.mask(), ASCII7F.mask(), +} + +var iso88591 = [...]Keycode{ + ISO88591A0.mask(), ISO88591A1.mask(), ISO88591A2.mask(), ISO88591A3.mask(), + ISO88591A4.mask(), ISO88591A5.mask(), ISO88591A6.mask(), ISO88591A7.mask(), + ISO88591A8.mask(), ISO88591A9.mask(), ISO88591AA.mask(), ISO88591AB.mask(), + ISO88591AC.mask(), ISO88591AD.mask(), ISO88591AE.mask(), ISO88591AF.mask(), + ISO88591B0.mask(), ISO88591B1.mask(), ISO88591B2.mask(), ISO88591B3.mask(), + ISO88591B4.mask(), ISO88591B5.mask(), ISO88591B6.mask(), ISO88591B7.mask(), + ISO88591B8.mask(), ISO88591B9.mask(), ISO88591BA.mask(), ISO88591BB.mask(), + ISO88591BC.mask(), ISO88591BD.mask(), ISO88591BE.mask(), ISO88591BF.mask(), + ISO88591C0.mask(), ISO88591C1.mask(), ISO88591C2.mask(), ISO88591C3.mask(), + ISO88591C4.mask(), ISO88591C5.mask(), ISO88591C6.mask(), ISO88591C7.mask(), + ISO88591C8.mask(), ISO88591C9.mask(), ISO88591CA.mask(), ISO88591CB.mask(), + ISO88591CC.mask(), ISO88591CD.mask(), ISO88591CE.mask(), ISO88591CF.mask(), + ISO88591D0.mask(), ISO88591D1.mask(), ISO88591D2.mask(), ISO88591D3.mask(), + ISO88591D4.mask(), ISO88591D5.mask(), ISO88591D6.mask(), ISO88591D7.mask(), + ISO88591D8.mask(), ISO88591D9.mask(), ISO88591DA.mask(), ISO88591DB.mask(), + ISO88591DC.mask(), ISO88591DD.mask(), ISO88591DE.mask(), ISO88591DF.mask(), + ISO88591E0.mask(), ISO88591E1.mask(), ISO88591E2.mask(), ISO88591E3.mask(), + ISO88591E4.mask(), ISO88591E5.mask(), ISO88591E6.mask(), ISO88591E7.mask(), + ISO88591E8.mask(), ISO88591E9.mask(), ISO88591EA.mask(), ISO88591EB.mask(), + ISO88591EC.mask(), ISO88591ED.mask(), ISO88591EE.mask(), ISO88591EF.mask(), + ISO88591F0.mask(), ISO88591F1.mask(), ISO88591F2.mask(), ISO88591F3.mask(), + ISO88591F4.mask(), ISO88591F5.mask(), ISO88591F6.mask(), ISO88591F7.mask(), + ISO88591F8.mask(), ISO88591F9.mask(), ISO88591FA.mask(), ISO88591FB.mask(), + ISO88591FC.mask(), ISO88591FD.mask(), ISO88591FE.mask(), ISO88591FF.mask(), +} diff --git a/emb/machine/_usb/hid/mouse/mouse.go b/emb/machine/_usb/hid/mouse/mouse.go new file mode 100644 index 00000000..d790bdbb --- /dev/null +++ b/emb/machine/_usb/hid/mouse/mouse.go @@ -0,0 +1,148 @@ +package mouse + +import ( + "machine" + "machine/usb/hid" +) + +var Mouse *mouse + +type Button byte + +const ( + Left Button = 1 << iota + Right + Middle + Back + Forward +) + +type mouse struct { + buf *hid.RingBuffer + button Button + waitTxc bool +} + +func init() { + if Mouse == nil { + Mouse = newMouse() + hid.SetHandler(Mouse) + } +} + +// New returns the USB hid-mouse port. +// Deprecated, better to just use Port() +func New() *mouse { + return Port() +} + +// Port returns the USB hid-mouse port. +func Port() *mouse { + return Mouse +} + +func newMouse() *mouse { + return &mouse{ + buf: hid.NewRingBuffer(), + } +} + +func (m *mouse) TxHandler() bool { + m.waitTxc = false + if b, ok := m.buf.Get(); ok { + m.waitTxc = true + hid.SendUSBPacket(b[:5]) + return true + } + return false +} + +func (m *mouse) RxHandler(b []byte) bool { + return false +} + +func (m *mouse) tx(b []byte) { + if machine.USBDev.InitEndpointComplete { + if m.waitTxc { + m.buf.Put(b) + } else { + m.waitTxc = true + hid.SendUSBPacket(b) + } + } +} + +// Move is a function that moves the mouse cursor. +func (m *mouse) Move(vx, vy int) { + if vx == 0 && vy == 0 { + return + } + + if vx < -128 { + vx = -128 + } + if vx > 127 { + vx = 127 + } + + if vy < -128 { + vy = -128 + } + if vy > 127 { + vy = 127 + } + + m.tx([]byte{ + 0x01, byte(m.button), byte(vx), byte(vy), 0x00, + }) +} + +// Click clicks the mouse button. +func (m *mouse) Click(btn Button) { + m.Press(btn) + m.Release(btn) +} + +// Press presses the given mouse buttons. +func (m *mouse) Press(btn Button) { + m.button |= btn + m.tx([]byte{ + 0x01, byte(m.button), 0x00, 0x00, 0x00, + }) +} + +// Release releases the given mouse buttons. +func (m *mouse) Release(btn Button) { + m.button &= ^btn + m.tx([]byte{ + 0x01, byte(m.button), 0x00, 0x00, 0x00, + }) +} + +// Wheel controls the mouse wheel. +func (m *mouse) Wheel(v int) { + if v == 0 { + return + } + + if v < -128 { + v = -128 + } + if v > 127 { + v = 127 + } + + m.tx([]byte{ + 0x01, byte(m.button), 0x00, 0x00, byte(v), + }) +} + +// WheelDown turns the mouse wheel down. +func (m *mouse) WheelDown() { + m.Wheel(-1) +} + +// WheelUp turns the mouse wheel up. +func (m *mouse) WheelUp() { + m.Wheel(1) +} diff --git a/emb/machine/_usb/msc/cbw.go b/emb/machine/_usb/msc/cbw.go new file mode 100644 index 00000000..7ebf346c --- /dev/null +++ b/emb/machine/_usb/msc/cbw.go @@ -0,0 +1,62 @@ +package msc + +import ( + "encoding/binary" + "machine/usb/msc/csw" + "machine/usb/msc/scsi" +) + +const ( + cbwMsgLen = 31 // Command Block Wrapper (CBW) message length + Signature = 0x43425355 // "USBC" in little endian +) + +type CBW struct { + HasCmd bool + Data []byte +} + +func (c *CBW) Tag() uint32 { + return binary.LittleEndian.Uint32(c.Data[4:8]) +} + +func (c *CBW) length() int { + return len(c.Data) +} + +func (c *CBW) validLength() bool { + return len(c.Data) == cbwMsgLen +} + +func (c *CBW) validSignature() bool { + return binary.LittleEndian.Uint32(c.Data[:4]) == Signature +} + +func (c *CBW) SCSICmd() scsi.Cmd { + return scsi.Cmd{Data: c.Data[15:]} +} + +func (c *CBW) transferLength() uint32 { + return binary.LittleEndian.Uint32(c.Data[8:12]) +} + +// isIn returns true if the command direction is from the device to the host. +func (c *CBW) isIn() bool { + return c.Data[12]>>7 != 0 +} + +// isOut returns true if the command direction is from the host to the device. +func (c *CBW) isOut() bool { + return !c.isIn() +} + +func (c *CBW) CSW(status csw.Status, residue uint32, b []byte) { + // Signature: "USBS" 53425355h (little endian) + binary.LittleEndian.PutUint32(b[:4], csw.Signature) + // Tag: (same as CBW) + copy(b[4:8], c.Data[4:8]) + // Data Residue: (untransferred bytes) + binary.LittleEndian.PutUint32(b[8:12], residue) + // Status: + b[12] = byte(status) +} diff --git a/emb/machine/_usb/msc/csw/csw.go b/emb/machine/_usb/msc/csw/csw.go new file mode 100644 index 00000000..29f44ef0 --- /dev/null +++ b/emb/machine/_usb/msc/csw/csw.go @@ -0,0 +1,14 @@ +package csw + +type Status uint8 + +const ( + StatusPassed Status = iota + StatusFailed + StatusPhaseError +) + +const ( + MsgLen = 13 + Signature = 0x53425355 // "USBS" in little endian +) diff --git a/emb/machine/_usb/msc/disk.go b/emb/machine/_usb/msc/disk.go new file mode 100644 index 00000000..6624d38c --- /dev/null +++ b/emb/machine/_usb/msc/disk.go @@ -0,0 +1,180 @@ +package msc + +import ( + "encoding/binary" + "errors" + "fmt" + "machine" + "time" +) + +var ( + errWriteOutOfBounds = errors.New("WriteAt offset out of bounds") +) + +// RegisterBlockDevice registers a BlockDevice provider with the MSC driver +func (m *msc) RegisterBlockDevice(dev machine.BlockDevice) { + m.dev = dev + + if cap(m.blockCache) != int(dev.WriteBlockSize()) { + m.blockCache = make([]byte, dev.WriteBlockSize()) + m.buf = make([]byte, dev.WriteBlockSize()) + } + + m.blockSizeRaw = uint32(m.dev.WriteBlockSize()) + m.blockCount = uint32(m.dev.Size()) / m.blockSizeUSB + // Read/write/erase operations must be aligned to the underlying hardware blocks. In order to align + // them we assume the provided block device is aligned to the end of the underlying hardware block + // device and offset all reads/writes by the remaining bytes that don't make up a full block. + m.blockOffset = uint32(m.dev.Size()) % m.blockSizeUSB + // FIXME: Figure out what to do if the emulated write block size is larger than the erase block size + + // Set VPD UNMAP fields + for i := range vpdPages { + if vpdPages[i].PageCode == 0xb0 { + // 0xb0 - 5.4.5 Block Limits VPD page (B0h) + if len(vpdPages[i].Data) >= 28 { + // Set the OPTIMAL UNMAP GRANULARITY (write blocks per erase block) + granularity := uint32(dev.EraseBlockSize()) / m.blockSizeUSB + binary.BigEndian.PutUint32(vpdPages[i].Data[24:28], granularity) + } + if len(vpdPages[i].Data) >= 32 { + // Set the UNMAP GRANULARITY ALIGNMENT (first sector of first full erase block) + // The unmap granularity alignment is used to calculate an optimal unmap request starting LBA as follows: + // optimal unmap request starting LBA = (n * OPTIMAL UNMAP GRANULARITY) + UNMAP GRANULARITY ALIGNMENT + // where n is zero or any positive integer value + // https://www.seagate.com/files/staticfiles/support/docs/manual/Interface%20manuals/100293068j.pdf + + // We assume the block device is aligned to the end of the underlying block device + blockOffset := uint32(dev.EraseBlockSize()) % m.blockSizeUSB + // Set the UGAVALID bit to indicate that the UNMAP GRANULARITY ALIGNMENT is valid + blockOffset |= 0x80000000 + binary.BigEndian.PutUint32(vpdPages[i].Data[28:32], blockOffset) + } + break + } + } +} + +var _ machine.BlockDevice = (*RecorderDisk)(nil) + +// RecorderDisk is a block device that records actions taken on it +type RecorderDisk struct { + dev machine.BlockDevice + log []RecorderRecord + last time.Time + time time.Time +} + +type RecorderRecord struct { + OpCode RecorderOpCode + Offset int64 + Length int + Data []byte + Time int64 + valid bool +} + +type RecorderOpCode uint8 + +const ( + RecorderOpCodeRead RecorderOpCode = iota + RecorderOpCodeWrite + RecorderOpCodeEraseBlocks +) + +// NewRecorderDisk creates a new RecorderDisk instance +func NewRecorderDisk(dev machine.BlockDevice, count int) *RecorderDisk { + d := &RecorderDisk{ + dev: dev, + log: make([]RecorderRecord, 0, count), + last: time.Now(), + } + for i := 0; i < count; i++ { + d.log = append(d.log, RecorderRecord{ + OpCode: RecorderOpCodeRead, + Offset: 0, + Length: 0, + Data: make([]byte, dev.WriteBlockSize()), + Time: 0, + }) + } + return d +} + +func (d *RecorderDisk) Size() int64 { + return d.dev.Size() +} + +func (d *RecorderDisk) WriteBlockSize() int64 { + return d.dev.WriteBlockSize() +} + +func (d *RecorderDisk) EraseBlockSize() int64 { + return d.dev.EraseBlockSize() +} + +func (d *RecorderDisk) EraseBlocks(startBlock, numBlocks int64) error { + d.Record(RecorderOpCodeEraseBlocks, startBlock, int(numBlocks), []byte{}) + return d.dev.EraseBlocks(startBlock, numBlocks) +} + +func (d *RecorderDisk) ReadAt(buffer []byte, offset int64) (int, error) { + n, err := d.dev.ReadAt(buffer, offset) + d.Record(RecorderOpCodeRead, offset, n, buffer) + return n, err +} + +func (d *RecorderDisk) WriteAt(buffer []byte, offset int64) (int, error) { + n, err := d.dev.WriteAt(buffer, offset) + d.Record(RecorderOpCodeWrite, offset, n, buffer) + return n, err +} + +func (d *RecorderDisk) Record(opCode RecorderOpCode, offset int64, length int, data []byte) { + n := len(d.log) - 1 + // Shift the log entries up to make room for a new entry + for i := 0; i < n; i++ { + d.log[i].OpCode = d.log[i+1].OpCode + d.log[i].Offset = d.log[i+1].Offset + d.log[i].Length = d.log[i+1].Length + d.log[i].Data = d.log[i].Data[:len(d.log[i+1].Data)] + copy(d.log[i].Data, d.log[i+1].Data) + d.log[i].Time = d.log[i+1].Time + d.log[i].valid = d.log[i+1].valid + } + + // Append the new record + d.log[n].OpCode = opCode + d.log[n].Offset = offset + d.log[n].Length = length + d.log[n].Data = d.log[n].Data[:len(data)] + copy(d.log[n].Data, data) + d.log[n].Time = time.Since(d.time).Microseconds() + d.time = d.time.Add(time.Since(d.time)) + d.log[n].valid = true +} + +func (d *RecorderDisk) ClearLog() { + for i := range d.log { + d.log[i].valid = false + } + d.time = time.Now() +} + +func (d *RecorderDisk) GetLog() []RecorderRecord { + return d.log +} + +func (r *RecorderRecord) String() (string, bool) { + opCode := "Unknown" + switch r.OpCode { + case RecorderOpCodeRead: + opCode = "Read" + case RecorderOpCodeWrite: + opCode = "Write" + case RecorderOpCodeEraseBlocks: + opCode = "EraseBlocks" + } + return fmt.Sprintf("%s: %05d+%02d t:%d | % 0x", opCode, r.Offset, r.Length, r.Time, r.Data), r.valid +} diff --git a/emb/machine/_usb/msc/msc.go b/emb/machine/_usb/msc/msc.go new file mode 100644 index 00000000..d3bf8d6e --- /dev/null +++ b/emb/machine/_usb/msc/msc.go @@ -0,0 +1,299 @@ +package msc + +import ( + "machine" + "machine/usb" + "machine/usb/descriptor" + "machine/usb/msc/csw" + "machine/usb/msc/scsi" + "time" +) + +type mscState uint8 + +const ( + mscStateCmd mscState = iota + mscStateData + mscStateStatus + mscStateStatusSent + mscStateNeedReset +) + +const ( + mscInterface = 2 +) + +var MSC *msc + +type msc struct { + buf []byte // Buffer for incoming/outgoing data + blockCache []byte // Buffer for block read/write data + taskQueued bool // Flag to indicate if the buffer has a task queued + rxStalled bool // Flag to indicate if the RX endpoint is stalled + txStalled bool // Flag to indicate if the TX endpoint is stalled + maxPacketSize uint32 // Maximum packet size for the IN endpoint + respStatus csw.Status // Response status for the last command + sendZLP bool // Flag to indicate if a zero-length packet should be sent before sending CSW + + cbw *CBW // Last received Command Block Wrapper + queuedBytes uint32 // Number of bytes queued for sending + sentBytes uint32 // Number of bytes sent + totalBytes uint32 // Total bytes to send + cswBuf []byte // CSW response buffer + state mscState + + maxLUN uint8 // Maximum Logical Unit Number (n-1 for n LUNs) + dev machine.BlockDevice + blockCount uint32 // Number of blocks in the device + blockOffset uint32 // Byte offset of the first block in the device for aligned writes + blockSizeUSB uint32 // Write block size as presented to the host over USB + blockSizeRaw uint32 // Write block size of the underlying device hardware + readOnly bool + + vendorID [8]byte // Max 8 ASCII characters + productID [16]byte // Max 16 ASCII characters + productRev [4]byte // Max 4 ASCII characters + + senseKey scsi.Sense + addlSenseCode scsi.SenseCode + addlSenseQualifier uint8 +} + +// Port returns the USB Mass Storage port +func Port(dev machine.BlockDevice) *msc { + if MSC == nil { + MSC = newMSC(dev) + } + return MSC +} + +func newMSC(dev machine.BlockDevice) *msc { + // Size our buffer to match the maximum packet size of the IN endpoint + maxPacketSize := descriptor.EndpointMSCIN.GetMaxPacketSize() + m := &msc{ + // Some platforms require reads/writes to be aligned to the full underlying hardware block + blockCache: make([]byte, dev.WriteBlockSize()), + blockSizeUSB: 512, + buf: make([]byte, dev.WriteBlockSize()), + cswBuf: make([]byte, csw.MsgLen), + cbw: &CBW{Data: make([]byte, 31)}, + maxPacketSize: uint32(maxPacketSize), + } + m.RegisterBlockDevice(dev) + + // Set default inquiry data fields + m.SetVendorID("TinyGo") + m.SetProductID("Mass Storage") + m.SetProductRev("1.0") + + // Initialize the USB Mass Storage Class (MSC) port + machine.ConfigureUSBEndpoint(descriptor.MSC, + []usb.EndpointConfig{ + { + Index: usb.MSC_ENDPOINT_IN, + IsIn: true, + Type: usb.ENDPOINT_TYPE_BULK, + TxHandler: txHandler, + StallHandler: setupPacketHandler, + }, + { + Index: usb.MSC_ENDPOINT_OUT, + IsIn: false, + Type: usb.ENDPOINT_TYPE_BULK, + DelayRxHandler: rxHandler, + StallHandler: setupPacketHandler, + }, + }, + []usb.SetupConfig{ + { + Index: mscInterface, + Handler: setupPacketHandler, + }, + }, + ) + + go m.processTasks() + + return m +} + +func (m *msc) processTasks() { + // Process tasks that cannot be done in an interrupt context + for { + if m.taskQueued { + cmd := m.cbw.SCSICmd() + switch cmd.CmdType() { + case scsi.CmdWrite: + m.scsiWrite(cmd, m.buf) + case scsi.CmdUnmap: + m.scsiUnmap(m.buf) + } + + // Acknowledge the received data from the host + m.queuedBytes = 0 + m.taskQueued = false + machine.AckUsbOutTransfer(usb.MSC_ENDPOINT_OUT) + } + time.Sleep(100 * time.Microsecond) + } +} + +func (m *msc) ready() bool { + return m.dev != nil +} + +func (m *msc) resetBuffer(length int) { + // Reset the buffer to the specified length + m.buf = m.buf[:length] + for i := 0; i < length; i++ { + m.buf[i] = 0 + } +} + +func (m *msc) sendUSBPacket(b []byte) { + if machine.USBDev.InitEndpointComplete { + // Send the USB packet + machine.SendUSBInPacket(usb.MSC_ENDPOINT_IN, b) + } +} + +func (m *msc) sendCSW(status csw.Status) { + // Generate CSW packet into m.cswBuf and send it + residue := uint32(0) + if m.totalBytes >= m.sentBytes { + residue = m.totalBytes - m.sentBytes + } + m.cbw.CSW(status, residue, m.cswBuf) + m.state = mscStateStatusSent + m.sendUSBPacket(m.cswBuf) +} + +func txHandler() { + if MSC != nil { + MSC.txHandler() + } +} + +func (m *msc) txHandler() { + m.run([]byte{}, false) +} + +func rxHandler(b []byte) bool { + ack := true + if MSC != nil { + ack = MSC.run(b, true) + } + return ack +} + +/* + Connection Happy Path Overview: + +0. MSC starts out in mscStateCmd status. + +1. Host sends CBW (Command Block Wrapper) packet to MSC. + - CBW contains the SCSI command to be executed, the length of the data to be transferred, etc. + +2. MSC receives CBW. + - CBW is validated and saved. + - State is changed to mscStateData. + - MSC routes the command to the appropriate SCSI command handler. + +3. The MSC SCSI command handler responds with the initial data packet (if applicable). + - If no data packet is needed, state is changed to mscStateStatus and step 4 is skipped. + +4. The host acks the data packet and MSC calls m.scsiDataTransfer() to continue sending (or +receiving) data. + - This cycle continues until all data requested in the CBW is sent/received. + - State is changed to mscStateStatus. + - MSC waits for the host to ACK the final data packet. + +5. MSC then sends a CSW (Command Status Wrapper) to the host to report the final status of the +command execution and moves to mscStateStatusSent. + +6. The host ACKs the CSW and the MSC moves back to mscStateCmd, waiting for the next CBW. +*/ +func (m *msc) run(b []byte, isEpOut bool) bool { + ack := true + + switch m.state { + case mscStateCmd: + // Receiving a new command block wrapper (CBW) + + // IN endpoint transfer complete confirmation, no action needed + if !isEpOut { + return ack + } + + // Create a temporary CBW wrapper to validate the incoming data. Has to be temporary + // to avoid it escaping into the heap since we're in interrupt context + cbw := CBW{Data: b} + + // Verify size and signature + if !cbw.validLength() || !cbw.validSignature() { + // 6.6.1 CBW Not Valid + // https://usb.org/sites/default/files/usbmassbulk_10.pdf + m.state = mscStateNeedReset + m.stallEndpoint(usb.MSC_ENDPOINT_IN) + m.stallEndpoint(usb.MSC_ENDPOINT_OUT) + m.stallEndpoint(usb.CONTROL_ENDPOINT) + return ack + } + + // Save the validated CBW for later reference + copy(m.cbw.Data, b) + + // Move on to the data transfer phase next go around (after sending the first message) + m.state = mscStateData + m.totalBytes = cbw.transferLength() + m.queuedBytes = 0 + m.sentBytes = 0 + m.respStatus = csw.StatusPassed + + m.scsiCmdBegin() + + case mscStateData: + // Transfer data + ack = m.scsiDataTransfer(b) + + case mscStateStatus: + // Sending CSW status response + // Placed after the switch statement so we can send the CSW without having to send a packet + // to cycle back through this block, e.g. with TEST UNIT READY which sends only a CSW after + // setting the sense key/add'l code/qualifier internally + + case mscStateStatusSent: + // Wait for the status phase to complete + if !isEpOut && m.queuedBytes == csw.MsgLen { + // Status confirmed sent, wait for next CBW + m.state = mscStateCmd + } else { + // We're not expecting any data here, ignore it. Original log line: + // TU_LOG1(" Warning expect SCSI Status but received unknown data\r\n"); + } + + case mscStateNeedReset: + // Received an invalid CBW message, stop everything until we get reset + } + + // Send CSW status response + // Placed after the switch statement so we can send the CSW without having to send a packet + // to cycle back through this block, e.g. with TEST UNIT READY which sends only a CSW after + // setting the sense key/add'l code/qualifier internally + if m.state == mscStateStatus && !m.txStalled { + if m.totalBytes > m.sentBytes && m.cbw.isIn() { + // 6.7.2 The Thirteen Cases - Case 5 (Hi > Di): STALL before status + m.stallEndpoint(usb.MSC_ENDPOINT_IN) + } else if m.sendZLP { + // Send a zero-length packet to force the end of the transfer before we send a CSW + m.queuedBytes = 0 + m.sendZLP = false + m.sendUSBPacket(m.buf[:0]) + } else { + m.sendCSW(m.respStatus) + m.state = mscStateCmd + } + } + + return ack +} diff --git a/emb/machine/_usb/msc/scsi.go b/emb/machine/_usb/msc/scsi.go new file mode 100644 index 00000000..d7266ed4 --- /dev/null +++ b/emb/machine/_usb/msc/scsi.go @@ -0,0 +1,301 @@ +package msc + +import ( + "encoding/binary" + "machine/usb" + "machine/usb/msc/csw" + "machine/usb/msc/scsi" +) + +func (m *msc) scsiCmdBegin() { + cmd := m.cbw.SCSICmd() + cmdType := cmd.CmdType() + + // Handle multi-packet commands + switch cmdType { + case scsi.CmdRead, scsi.CmdWrite: + m.scsiCmdReadWrite(cmd) + return + case scsi.CmdUnmap: + m.scsiCmdUnmap(cmd) + return + } + + if m.totalBytes > 0 && m.cbw.isOut() { + // Reject any other multi-packet commands + if m.totalBytes > m.maxPacketSize { + m.sendScsiError(csw.StatusFailed, scsi.SenseIllegalRequest, scsi.SenseCodeInvalidCmdOpCode) + return + } else { + // Original comment from TinyUSB: + // Didn't check for case 9 (Ho > Dn), which requires examining scsi command first + // but it is OK to just receive data then responded with failed status + } + } + switch cmdType { + case scsi.CmdTestUnitReady: + m.scsiTestUnitReady() + case scsi.CmdReadCapacity: + m.scsiCmdReadCapacity(cmd) + case scsi.CmdReadFormatCapacity: + m.scsiCmdReadFormatCapacity(cmd) + case scsi.CmdInquiry: + m.scsiCmdInquiry(cmd) + case scsi.CmdModeSense6, scsi.CmdModeSense10: + m.scsiCmdModeSense(cmd) + case scsi.CmdRequestSense: + m.scsiCmdRequestSense() + case scsi.CmdPreventAllowMediumRemoval: + m.scsiCmdPreventAllowMediumRemoval(cmd) + default: + // We don't support this command, error out + m.sendScsiError(csw.StatusFailed, scsi.SenseIllegalRequest, scsi.SenseCodeInvalidCmdOpCode) + } + + if len(m.buf) == 0 { + if m.totalBytes > 0 { + // 6.7.2 The Thirteen Cases - Case 4 (Hi > Dn) + // https://usb.org/sites/default/files/usbmassbulk_10.pdf + m.sendScsiError(csw.StatusFailed, scsi.SenseIllegalRequest, 0) + } else { + // 6.7.1 The Thirteen Cases - Case 1 Hn = Dn: all good + // https://usb.org/sites/default/files/usbmassbulk_10.pdf + m.state = mscStateStatus + } + } else { + if m.totalBytes == 0 { + // 6.7.1 The Thirteen Cases - Case 2 (Hn < Di) + // https://usb.org/sites/default/files/usbmassbulk_10.pdf + m.sendScsiError(csw.StatusFailed, scsi.SenseIllegalRequest, 0) + } else { + // Make sure we don't return more data than the host is expecting + if m.cbw.transferLength() < uint32(len(m.buf)) { + m.buf = m.buf[:m.cbw.transferLength()] + } + m.sendUSBPacket(m.buf) + } + } +} + +func (m *msc) scsiDataTransfer(b []byte) bool { + cmd := m.cbw.SCSICmd() + cmdType := cmd.CmdType() + + switch cmdType { + case scsi.CmdWrite, scsi.CmdUnmap: + if m.readOnly { + m.sendScsiError(csw.StatusFailed, scsi.SenseDataProtect, scsi.SenseCodeWriteProtected) + return true + } + return m.scsiQueueTask(cmdType, b) + } + + // Update our sent bytes count to include the just-confirmed bytes + m.sentBytes += m.queuedBytes + + if m.sentBytes >= m.totalBytes { + // Transfer complete, send CSW after transfer confirmed + m.state = mscStateStatus + } else if cmdType == scsi.CmdRead { + m.scsiRead(cmd) + } else { + // Other multi-packet commands are rejected in m.scsiCmdBegin() + } + + return true +} + +func (m *msc) scsiTestUnitReady() { + m.resetBuffer(0) + m.queuedBytes = 0 + + // Check if the device is ready + if !m.ready() { + // If not ready set sense data + m.senseKey = scsi.SenseNotReady + m.addlSenseCode = scsi.SenseCodeMediumNotPresent + m.addlSenseQualifier = 0x00 + } else { + m.senseKey = 0 + m.addlSenseCode = 0 + m.addlSenseQualifier = 0 + } +} + +func (m *msc) scsiCmdReadCapacity(cmd scsi.Cmd) { + m.resetBuffer(scsi.ReadCapacityRespLen) + m.queuedBytes = scsi.ReadCapacityRespLen + + // Last LBA address (big endian) + binary.BigEndian.PutUint32(m.buf[:4], m.blockCount-1) + // Block size (big endian) + binary.BigEndian.PutUint32(m.buf[4:8], m.blockSizeUSB) +} + +func (m *msc) scsiCmdReadFormatCapacity(cmd scsi.Cmd) { + m.resetBuffer(scsi.ReadFormatCapacityRespLen) + m.queuedBytes = scsi.ReadFormatCapacityRespLen + + // bytes 0-2 - reserved + m.buf[3] = 8 // Capacity list length + + // Number of blocks (big endian) + binary.BigEndian.PutUint32(m.buf[4:8], m.blockCount) + // Block size (24-bit, big endian) + binary.BigEndian.PutUint32(m.buf[8:12], m.blockSizeUSB) + // Descriptor Type - formatted media + m.buf[8] = 2 +} + +// MODE SENSE(6) / MODE SENSE(10) - Only used here to indicate that the device is write protected +func (m *msc) scsiCmdModeSense(cmd scsi.Cmd) { + respLen := uint32(scsi.ModeSense6RespLen) + if cmd.CmdType() == scsi.CmdModeSense10 { + respLen = scsi.ModeSense10RespLen + } + m.resetBuffer(int(respLen)) + m.queuedBytes = respLen + + // The host allows a good amount of leeway in response size + // Reset total bytes to what we'll actually send + if m.totalBytes > respLen { + m.totalBytes = respLen + m.sendZLP = true + } + + readOnly := byte(0) + if m.readOnly { + readOnly = 0x80 + } + + switch cmd.CmdType() { + case scsi.CmdModeSense6: + // byte 0 - Number of bytes after this one + m.buf[0] = byte(respLen) - 1 + // byte 1 - Medium type (0x00 for direct access block device) + // Bit 7 indicates write protected + m.buf[2] = readOnly + // byte 3 - Block descriptor length: 0 (not supported) + case scsi.CmdModeSense10: + // bytes 0-1 - Number of bytes after this one + m.buf[1] = byte(respLen) - 2 + // byte 2 - Medium type (0x00 for direct access block device) + // Bit 7 indicates write protected + m.buf[3] = readOnly + } +} + +// PREVENT/ALLOW MEDIUM REMOVAL - A flash drive doesn't have a removable medium, so this is a no-op +func (m *msc) scsiCmdPreventAllowMediumRemoval(cmd scsi.Cmd) { + m.resetBuffer(0) + m.queuedBytes = 0 + + // Check if the device is ready + if !m.ready() { + // If not ready set sense data + m.senseKey = scsi.SenseNotReady + m.addlSenseCode = scsi.SenseCodeMediumNotPresent + m.addlSenseQualifier = 0x00 + } else { + m.senseKey = 0 + m.addlSenseCode = 0 + m.addlSenseQualifier = 0 + } + + m.state = mscStateStatus +} + +// REQUEST SENSE - Returns error status codes when an error status is sent +func (m *msc) scsiCmdRequestSense() { + // Set the buffer size to the SCSI sense message size and clear + m.resetBuffer(scsi.RequestSenseRespLen) + m.queuedBytes = scsi.RequestSenseRespLen + m.totalBytes = scsi.RequestSenseRespLen + + // 0x70 - current error, 0x71 - deferred error (not used) + m.buf[0] = 0xF0 // 0x70 for current error plus 0x80 for valid flag bit + // byte 1 - reserved + m.buf[2] = uint8(m.senseKey) & 0x0F // Incorrect Length Indicator bit not supported + // bytes 3-6 - Information (not used) + // byte 7 - Additional Sense Length (bytes remaining in the message) + m.buf[7] = scsi.RequestSenseRespLen - 8 + // bytes 8-11 - Command Specific Information (not used) + m.buf[12] = byte(m.addlSenseCode) // Additional Sense Code (optional) + m.buf[13] = m.addlSenseQualifier // Additional Sense Code Qualifier (optional) + // bytes 14-17 - reserved + + // Clear sense data after copied to buffer + m.senseKey = 0 + m.addlSenseCode = 0 + m.addlSenseQualifier = 0 +} + +func (m *msc) scsiCmdUnmap(cmd scsi.Cmd) { + // Unmap sends a header in the CBW and a parameter list in the data stage + // The parameter list has an 8 byte header and 16 bytes per item. If it's less than 24 bytes it's + // not the format we're expecting and we won't be able to decode it. Same for if there isn't a + // 8 byte header plus multiples of 16 bytes after that + paramLen := binary.BigEndian.Uint16(m.cbw.Data[7:9]) + if paramLen < 24 || (paramLen-8)%16 != 0 { + m.sendScsiError(csw.StatusFailed, scsi.SenseIllegalRequest, scsi.SenseCodeInvalidFieldInCDB) + return + } +} + +func (m *msc) scsiQueueTask(cmdType scsi.CmdType, b []byte) bool { + // Check if the incoming data is larger than our buffer + if int(m.queuedBytes)+len(b) > cap(m.buf) { + m.sendScsiError(csw.StatusFailed, scsi.SenseIllegalRequest, scsi.SenseCodeInvalidFieldInCDB) + return true + } + + // Save the incoming data in our buffer for processing outside of interrupt context. + if m.taskQueued { + // If we already have a full task queue we can't accept this data + m.sendScsiError(csw.StatusFailed, scsi.SenseAbortedCommand, scsi.SenseCodeMsgReject) + return true + } + + // Copy the queued task data into our buffer + start := m.queuedBytes + end := start + uint32(len(b)) + m.buf = m.buf[:end] + copy(m.buf[start:end], b) + m.queuedBytes += uint32(len(b)) + + switch cmdType { + case scsi.CmdWrite: + // If we're writing data wait until we have a full write block of data that can be processed. + if m.queuedBytes == uint32(cap(m.blockCache)) { + m.taskQueued = true + } + case scsi.CmdUnmap: + m.taskQueued = true + } + + // Don't acknowledge the incoming data until we can process it. + return !m.taskQueued +} + +func (m *msc) sendScsiError(status csw.Status, key scsi.Sense, code scsi.SenseCode) { + // Generate CSW into m.cswBuf + residue := m.totalBytes - m.sentBytes + + // Prepare to send CSW + m.sendZLP = true // Ensure the transaction is signaled as ended before a CSW is sent + m.respStatus = status + m.state = mscStateStatus + + // Set the sense data + m.senseKey = key + m.addlSenseCode = code + m.addlSenseQualifier = 0x00 // Not used + + if m.totalBytes > 0 && residue > 0 { + if m.cbw.isIn() { + m.stallEndpoint(usb.MSC_ENDPOINT_IN) + } else { + m.stallEndpoint(usb.MSC_ENDPOINT_OUT) + } + } +} diff --git a/emb/machine/_usb/msc/scsi/scsi.go b/emb/machine/_usb/msc/scsi/scsi.go new file mode 100644 index 00000000..5b63f67b --- /dev/null +++ b/emb/machine/_usb/msc/scsi/scsi.go @@ -0,0 +1,140 @@ +package scsi + +import ( + "encoding/binary" + "fmt" +) + +type Cmd struct { + Data []byte +} + +func (c *Cmd) CmdType() CmdType { + return CmdType(c.Data[0]) +} + +func (c *Cmd) BlockCount() uint32 { + return uint32(binary.BigEndian.Uint16(c.Data[7:9])) +} + +func (c *Cmd) LBA() uint32 { + return binary.BigEndian.Uint32(c.Data[2:6]) +} + +func (c Cmd) String() string { + cmdType := c.CmdType() + switch cmdType { + case CmdRead: + return fmt.Sprintf("%-28s LBA: % 3d, Block Count: %d", cmdType, c.LBA(), c.BlockCount()) + case CmdWrite: + return fmt.Sprintf("%-28s LBA: % 3d, Block Count: %d", cmdType, c.LBA(), c.BlockCount()) + default: + return fmt.Sprintf("%-28s % x", cmdType, c.Data) + } +} + +type CmdType uint8 + +const ( + CmdTestUnitReady CmdType = 0x00 // TEST UNIT READY is used to determine if a device is ready to transfer data (read/write). The device does not perform a self-test operation + CmdRequestSense CmdType = 0x03 // REQUEST SENSE returns the current sense data (status or error information) + CmdInquiry CmdType = 0x12 // INQUIRY is used to obtain basic information from a target device + CmdModeSelect6 CmdType = 0x15 // MODE SELECT (6) provides a means for the application client to specify medium, logical unit, or peripheral device parameters to the device server + CmdModeSelect10 CmdType = 0x55 // MODE SELECT (10) provides a means for the application client to specify medium, logical unit, or peripheral device parameters to the device server + CmdModeSense6 CmdType = 0x1A // MODE SENSE (6) provides a means for a device server to report parameters to an application client + CmdModeSense10 CmdType = 0x5A // MODE SENSE (10) provides a means for a device server to report parameters to an application client with 64-bit logical block addressing + CmdStartStopUnit CmdType = 0x1B // START STOP UNIT is used to start or stop the medium in a device server + CmdPreventAllowMediumRemoval CmdType = 0x1E // PREVENT ALLOW MEDIUM REMOVAL is used to prevent or allow the removal of storage medium from a device server + CmdReadFormatCapacity CmdType = 0x23 // READ FORMAT CAPACITY allows the Host to request a list of the possible format capacities for an installed writable media + CmdReadCapacity CmdType = 0x25 // READ CAPACITY command is used to obtain data capacity information from a target device + CmdRead CmdType = 0x28 // READ (10) requests that the device server read the specified logical block(s) and transfer them to the data-in buffer + CmdWrite CmdType = 0x2A // WRITE (10) requests that the device server transfer the specified logical block(s) from the data-out buffer and write them + CmdUnmap CmdType = 0x42 // UNMAP command is used to inform the device server that the specified logical block(s) are no longer in use +) + +func (c CmdType) String() string { + switch c { + case CmdTestUnitReady: + return "TEST UNIT READY" + case CmdRequestSense: + return "REQUEST SENSE" + case CmdInquiry: + return "INQUIRY" + case CmdModeSelect6: + return "MODE SELECT (6)" + case CmdModeSelect10: + return "MODE SELECT (10)" + case CmdModeSense6: + return "MODE SENSE (6)" + case CmdModeSense10: + return "MODE SENSE (10)" + case CmdStartStopUnit: + return "START STOP UNIT" + case CmdPreventAllowMediumRemoval: + return "PREVENT ALLOW MEDIUM REMOVAL" + case CmdReadFormatCapacity: + return "READ FORMAT CAPACITY" + case CmdReadCapacity: + return "READ CAPACITY" + case CmdRead: + return "READ (10)" + case CmdWrite: + return "WRITE (10)" + case CmdUnmap: + return "UNMAP" + default: + return fmt.Sprintf("Unknown Command (0x%0x)", byte(c)) + } +} + +type Sense uint8 + +const ( + // 4.5.6 Sense key and sense code definitions + // https://www.t10.org/ftp/t10/document.08/08-309r0.pdf + SenseNone Sense = 0x00 // No specific Sense Key. This indicates no error condition + SenseRecoveredError Sense = 0x01 // The last command completed successfully, but with some recovery action performed + SenseNotReady Sense = 0x02 // The LUN addressed is not ready to be accessed + SenseMediumError Sense = 0x03 // The command terminated with an unrecoverable error condition + SenseHardwareError Sense = 0x04 // The drive detected an unrecoverable hardware failure while performing the command or during a self test + SenseIllegalRequest Sense = 0x05 // An illegal parameter was provided in the command descriptor block or the additional parameters + SenseUnitAttention Sense = 0x06 // The disk drive may have been reset + SenseDataProtect Sense = 0x07 // A read or write command was attempted on a block that is protected from this operation and was not performed + SenseBlankCheck Sense = 0x08 // A write-once device or a sequential-access device encountered blank medium or format-defined end-of-data indication while reading or that a write-once device encountered a non-blank medium while writing + SenseFirmwareError Sense = 0x09 // Vendor specific sense key + SenseAbortedCommand Sense = 0x0B // The disk drive aborted the command + SenseVolumeOverflow Sense = 0x0D // A buffered peripheral device has reached the end of medium partition and data remains in the buffer that has not been written to the medium + SenseMiscompare Sense = 0x0E // The source data did not match the data read from the medium +) + +type SenseCode uint8 + +const ( + // SenseNotReady + SenseCodeMediumNotPresent SenseCode = 0x3A // The storage medium is not present in the device (e.g. empty CD-ROM drive or flash card reader) + + // SenseIllegalRequest + SenseCodeInvalidCmdOpCode SenseCode = 0x20 // The command operation code is not supported by the device + SenseCodeInvalidFieldInCDB SenseCode = 0x24 // The command descriptor block (CDB) contains an invalid field + + // SenseDataProtect + SenseCodeWriteProtected SenseCode = 0x27 // The media is write protected + + // SenseAbortedCommand + SenseCodeLUNCommFailure SenseCode = 0x08 // LUN communication failure + SenseCodeAbortedCmd SenseCode = 0x0B // The command was aborted by the device + SenseCodeMsgReject SenseCode = 0x43 // The command was rejected by the device + SenseCodeOverlapCmdAttempted SenseCode = 0x4E // The command was rejected by the device because it was overlapped by another command + + // SenseVolumeOverflow + SenseCodeLBAOutOfRange SenseCode = 0x21 // The logical block address (LBA) is beyond the end of the volume +) + +const ( + InquiryRespLen = 36 + ModeSense6RespLen = 4 + ModeSense10RespLen = 8 + ReadCapacityRespLen = 8 + ReadFormatCapacityRespLen = 12 + RequestSenseRespLen = 18 +) diff --git a/emb/machine/_usb/msc/scsi_inquiry.go b/emb/machine/_usb/msc/scsi_inquiry.go new file mode 100644 index 00000000..ae7028f6 --- /dev/null +++ b/emb/machine/_usb/msc/scsi_inquiry.go @@ -0,0 +1,164 @@ +package msc + +import ( + "encoding/binary" + "machine/usb/msc/csw" + "machine/usb/msc/scsi" +) + +type vpdPage struct { + PageCode uint8 + PageLength uint8 + // Page data + // First four bytes are always Device Type, Page Code, and Page Length (2 bytes) and are omitted here + Data []byte +} + +// These must be sorted in ascending order by PageCode +var vpdPages = []vpdPage{ + { + // 0xb0 - 5.4.5 Block Limits VPD page (B0h) + // https://www.seagate.com/files/staticfiles/support/docs/manual/Interface%20manuals/100293068j.pdf + PageCode: 0xb0, + PageLength: 0x3c, // 60 bytes + Data: []byte{ + 0x00, 0x00, // WSNZ, MAXIMUM COMPARE AND WRITE LENGTH - Not supported + 0x00, 0x00, // OPTIMAL TRANSFER LENGTH GRANULARITY - Not supported + 0x00, 0x00, 0x00, 0x00, // MAXIMUM TRANSFER LENGTH - Not supported + 0x00, 0x00, 0x00, 0x00, // OPTIMAL TRANSFER LENGTH - Not supported + 0x00, 0x00, 0x00, 0x00, // MAXIMUM PREFETCH LENGTH - Not supported + 0xFF, 0xFF, 0xFF, 0xFF, // MAXIMUM UNMAP LBA COUNT - Maximum count supported + 0x00, 0x00, 0x00, 0x03, // MAXIMUM UNMAP BLOCK DESCRIPTOR COUNT - Max 3 descriptors + 0x00, 0x00, 0x00, 0x00, // OPTIMAL UNMAP GRANULARITY + 0x00, 0x00, 0x00, 0x00, // UNMAP GRANULARITY ALIGNMENT (bit 7 on byte 28 sets UGAVALID) + // From here on all bytes are zero and can be omitted from the response + // 0x00, 0x00, 0x00, 0x00, // MAXIMUM WRITE SAME LENGTH - Not supported + // 0x00, 0x00, 0x00, 0x00, // (8-bytes) + // 0x00, 0x00, 0x00, 0x00, // MAXIMUM ATOMIC TRANSFER LENGTH - Not supported + // 0x00, 0x00, 0x00, 0x00, // ATOMIC ALIGNMENT - Not supported + // 0x00, 0x00, 0x00, 0x00, // ATOMIC TRANSFER LENGTH GRANULARITY - Not supported + // 0x00, 0x00, 0x00, 0x00, // MAXIMUM ATOMIC TRANSFER LENGTH WITH ATOMIC BOUNDARY - Not supported + // 0x00, 0x00, 0x00, 0x00, // MAXIMUM ATOMIC BOUNDARY SIZE - Not supported + }, + }, + { + // 0xb1 - 5.4.3 Block Device Characteristics VPD page (B1h) + // https://www.seagate.com/files/staticfiles/support/docs/manual/Interface%20manuals/100293068j.pdf + PageCode: 0xb1, + PageLength: 0x3c, // 60 bytes (bytes 9+ are all reserved/zero) + Data: []byte{ + 0x00, 0x01, // Rotation rate (0x0001 - non-rotating medium) + 0x00, // Product type - 0x00: Not indicated, 0x04: MMC/eMMC, 0x05: SD card + 0x00, // WABEREQ/WACEREQ/Form Factor - Not specified + 0x00, // ZBC/BOCS/FUAB/VBULS + // Reserved (55 bytes) + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, + }, + }, + { + // 0xb2 - 5.4.13 Logical Block Provisioning VPD page (B2h) + // https://www.seagate.com/files/staticfiles/support/docs/manual/Interface%20manuals/100293068j.pdf + PageCode: 0xB2, + PageLength: 0x04, + Data: []byte{ + 0x00, // Logical Block Provisioning Threshold Exponent + 0x80, // 0x80 - LBPU (UNMAP command supported) + 0x00, // Minimum percentage/Provisioning type - Not specified + 0x00, // Threshold percentage - Not supported + }, + }, +} + +func (m *msc) scsiCmdInquiry(cmd scsi.Cmd) { + evpd := cmd.Data[1] & 0x01 + pageCode := cmd.Data[2] + + // PAGE CODE (byte 2) can't be set if the EVPD bit is not set + if evpd == 0 { + if pageCode == 0 { + // Standard INQUIRY command + m.scsiStdInquiry(cmd) + } else { + // 3.6.1 INQUIRY command introduction + // https://www.seagate.com/files/staticfiles/support/docs/manual/Interface%20manuals/100293068j.pdf + m.sendScsiError(csw.StatusFailed, scsi.SenseIllegalRequest, scsi.SenseCodeInvalidFieldInCDB) + return + } + } else { + m.scsiEvpdInquiry(cmd, pageCode) + } +} + +func (m *msc) scsiEvpdInquiry(cmd scsi.Cmd, pageCode uint8) { + var pageLength int + switch pageCode { + case 0x00: + // 5.4.18 Supported Vital Product Data pages (00h) + // https://www.seagate.com/files/staticfiles/support/docs/manual/Interface%20manuals/100293068j.pdf + + pageLength = len(vpdPages) + 1 // Number of pages + 1 for 0x00 (excluded from vpdPages[]) + m.resetBuffer(pageLength + 4) // n+4 supported VPD pages + // bytes 4+ - Supported VPD pages in ascending order + for i := 0; i < len(vpdPages); i++ { + m.buf[4+i] = vpdPages[i].PageCode + } + default: + found := false + for i := range vpdPages { + if vpdPages[i].PageCode == pageCode { + // Our advertised page length is "for entertainment use only". Some pages have dozens of + // reserved (zero) bytes at the end that don't actually need to be sent. If we omit them + // from our response they are (correctly) presumed to be zero bytes by the host + pageLength = int(vpdPages[i].PageLength) + // We actually just send the length of the bytes we have plus the same four byte header, + // but declare the length of the response according to the spec as appropriate + m.resetBuffer(len(vpdPages[i].Data) + 4) + copy(m.buf[4:], vpdPages[i].Data) + found = true + break + } + } + if !found { + // VPD page not found, send error + m.sendScsiError(csw.StatusFailed, scsi.SenseIllegalRequest, scsi.SenseCodeInvalidFieldInCDB) + return + } + } + + // byte 0 - Peripheral Qualifier/Peripheral Device Type (0x00 for direct access block device) + m.buf[1] = pageCode + binary.BigEndian.PutUint16(m.buf[2:4], uint16(pageLength)) + + // Set total bytes to the length of our response + m.queuedBytes = uint32(len(m.buf)) + m.totalBytes = uint32(len(m.buf)) +} + +func (m *msc) scsiStdInquiry(cmd scsi.Cmd) { + m.resetBuffer(scsi.InquiryRespLen) + m.queuedBytes = scsi.InquiryRespLen + m.totalBytes = scsi.InquiryRespLen + + // byte 0 - Device Type (0x00 for direct access block device) + // byte 1 - Removable media bit + m.buf[1] = 0x80 + // byte 2 - Version 0x00 - We claim conformance to no standard + // byte 3 - Response data format + m.buf[3] = 2 + // byte 4 - Additional length (number of bytes after this one) + m.buf[4] = scsi.InquiryRespLen - 5 + // byte 5 - Not used + // byte 6 - Not used + // byte 7 - Not used + // bytes 8-15 - Vendor ID + copy(m.buf[8:16], m.vendorID[:]) + // bytes 16-31 - Product ID + copy(m.buf[16:32], m.productID[:]) + // bytes 32-35 - Product revision level + copy(m.buf[32:36], m.productRev[:]) +} diff --git a/emb/machine/_usb/msc/scsi_readwrite.go b/emb/machine/_usb/msc/scsi_readwrite.go new file mode 100644 index 00000000..1b09e134 --- /dev/null +++ b/emb/machine/_usb/msc/scsi_readwrite.go @@ -0,0 +1,144 @@ +package msc + +import ( + "errors" + "machine/usb/msc/csw" + "machine/usb/msc/scsi" +) + +var invalidWriteError = errors.New("invalid write offset or length") + +func (m *msc) scsiCmdReadWrite(cmd scsi.Cmd) { + status := m.validateScsiReadWrite(cmd) + if status != csw.StatusPassed { + m.sendScsiError(status, scsi.SenseIllegalRequest, scsi.SenseCodeInvalidCmdOpCode) + } else if m.totalBytes > 0 { + if cmd.CmdType() == scsi.CmdRead { + m.scsiRead(cmd) + } else { + // WRITE(10) and UNMAP commands don't take any action until the data stage begins + } + } else { + // Zero byte transfer. No practical use case + m.state = mscStateStatus + } +} + +// Validate SCSI READ(10) and WRITE(10) commands +func (m *msc) validateScsiReadWrite(cmd scsi.Cmd) csw.Status { + blockCount := cmd.BlockCount() + // CBW wrapper transfer length + if m.totalBytes == 0 { + // If the SCSI command's block count doesn't loosely match the wrapper's transfer length something's wrong + if blockCount > 0 { + return csw.StatusPhaseError + } + // Zero length transfer. No practical use case, but explicitly not an error according to the spec + return csw.StatusPassed + } + if (cmd.CmdType() == scsi.CmdRead && m.cbw.isOut()) || (cmd.CmdType() == scsi.CmdWrite && m.cbw.isIn()) { + // If the command is READ(10) and the data direction is from host to device that's a problem + // 6.7.3 The Thirteen Cases - Case 10 (Ho <> Di) + // If the command is WRITE(10) and the data direction is from device to host that's also a problem + // 6.7.2 The Thirteen Cases - Case 8 (Hi <> Do) + // https://usb.org/sites/default/files/usbmassbulk_10.pdf + return csw.StatusPhaseError + } + if blockCount == 0 { + // We already checked for zero length transfer above, so this is a problem + // 6.7.2 The Thirteen Cases - Case 4 (Hi > Dn) + // https://usb.org/sites/default/files/usbmassbulk_10.pdf + return csw.StatusFailed + } + if m.totalBytes/blockCount == 0 { + // Block size shouldn't be small enough to round to zero + // 6.7.2 The Thirteen Cases - Case 7 (Hi < Di) READ(10) or + // 6.7.3 The Thirteen Cases - Case 13 (Ho < Do) WRITE(10) + // https://usb.org/sites/default/files/usbmassbulk_10.pdf + return csw.StatusPhaseError + } + return csw.StatusPassed +} + +func (m *msc) usbToRawOffset(lba, offset uint32) (int64, int64) { + // Convert the emulated block address to the underlying hardware block's start and offset + rawLBA := (lba*m.blockSizeUSB + offset) / m.blockSizeRaw + rawBlockOffset := int64((lba*m.blockSizeUSB + offset) % m.blockSizeRaw) + return int64(m.blockOffset + rawLBA*m.blockSizeRaw), rawBlockOffset +} + +func (m *msc) readBlock(b []byte, lba, offset uint32) (n int, err error) { + // Convert the emulated block address to the underlying hardware block's start and offset + blockStart, blockOffset := m.usbToRawOffset(lba, offset) + + // Read a full block from the underlying device into the block cache + n, err = m.dev.ReadAt(m.blockCache, blockStart) + n -= int(blockOffset) + if n > len(b) { + n = len(b) + } + + copy(b, m.blockCache[blockOffset:]) + + return n, err +} + +func (m *msc) writeBlock(b []byte, lba, offset uint32) (n int, err error) { + // Convert the emulated block address to the underlying hardware block's start and offset + blockStart, blockOffset := m.usbToRawOffset(lba, offset) + + if blockOffset != 0 || len(b) != int(m.blockSizeRaw) { + return 0, invalidWriteError + } + + // Write the full block to the underlying device + n, err = m.dev.WriteAt(b, blockStart) + n -= int(blockOffset) + if n > len(b) { + n = len(b) + } + + return n, err +} + +func (m *msc) scsiRead(cmd scsi.Cmd) { + // Make sure we don't exceed the buffer size + readEnd := m.totalBytes - m.sentBytes + if readEnd > m.maxPacketSize { + readEnd = m.maxPacketSize + } + // Resize the buffer to fit the read size + m.resetBuffer(int(readEnd)) + + // Read data from the emulated block device + n, err := m.readBlock(m.buf[:readEnd], cmd.LBA(), m.sentBytes) + if err != nil || n == 0 { + m.sendScsiError(csw.StatusFailed, scsi.SenseNotReady, scsi.SenseCodeMediumNotPresent) + return + } + + m.queuedBytes = readEnd + m.sendUSBPacket(m.buf) +} + +func (m *msc) scsiWrite(cmd scsi.Cmd, b []byte) { + if m.readOnly { + m.sendScsiError(csw.StatusFailed, scsi.SenseDataProtect, scsi.SenseCodeWriteProtected) + return + } + + // Write data to the block device + n, err := m.writeBlock(b, cmd.LBA(), m.sentBytes) + if err != nil || n < len(b) { + m.sentBytes += uint32(n) + m.sendScsiError(csw.StatusFailed, scsi.SenseNotReady, scsi.SenseCodeMediumNotPresent) + } else { + m.sentBytes += uint32(len(b)) + } + + if m.sentBytes >= m.totalBytes { + // Data transfer is complete, send CSW + m.state = mscStateStatus + m.run([]byte{}, true) + } +} diff --git a/emb/machine/_usb/msc/scsi_unmap.go b/emb/machine/_usb/msc/scsi_unmap.go new file mode 100644 index 00000000..79c2426d --- /dev/null +++ b/emb/machine/_usb/msc/scsi_unmap.go @@ -0,0 +1,91 @@ +package msc + +import ( + "encoding/binary" + "machine/usb/msc/csw" + "machine/usb/msc/scsi" +) + +type Error int + +const ( + errorLBAOutOfRange Error = iota +) + +func (e Error) Error() string { + switch e { + case errorLBAOutOfRange: + return "LBA out of range" + default: + return "unknown error" + } +} + +func (m *msc) scsiUnmap(b []byte) { + // Execute Order 66 (0x42) to wipe out the blocks + // 3.54 Unmap Command (SBC-4) + // https://www.seagate.com/files/staticfiles/support/docs/manual/Interface%20manuals/100293068j.pdf + if m.readOnly { + m.sendScsiError(csw.StatusFailed, scsi.SenseDataProtect, scsi.SenseCodeWriteProtected) + return + } + + // blockDescLen is the remaining length of block descriptors in the message, offset 8 bytes from + // the start of this packet + var blockDescLen uint16 + + // Decode the parameter list + msgLen := binary.BigEndian.Uint16(b[:2]) + // Length of the block descriptor portion of the message + blockDescLen = binary.BigEndian.Uint16(b[2:4]) + // Do some sanity checks on the message lengths (max 3 block descriptors to fit in one 64 byte packet) + if msgLen < 8 || blockDescLen < 16 || msgLen-blockDescLen != 6 || blockDescLen > (3*16) { + m.sendScsiError(csw.StatusFailed, scsi.SenseIllegalRequest, scsi.SenseCodeInvalidFieldInCDB) + return + } + + // descEnd marks the end of the last full block descriptor in this packet + descEnd := int(blockDescLen + 8) + + // Unmap the blocks we can from this packet + for i := 8; i < descEnd; i += 16 { + err := m.unmapBlocksFromDescriptor(b[i:], uint64(m.blockCount)) + if err != nil { + // TODO: Might need a better error code here for device errors? + m.sendScsiError(csw.StatusFailed, scsi.SenseVolumeOverflow, scsi.SenseCodeLBAOutOfRange) + return + } + } + + // FIXME: We need to handle erase block alignment + + m.sentBytes += uint32(len(b)) + if m.sentBytes >= m.totalBytes { + // Order 66 complete, send CSW to establish galactic empire + m.state = mscStateStatus + m.run([]byte{}, true) + } +} + +func (m *msc) unmapBlocksFromDescriptor(b []byte, numBlocks uint64) error { + blockCount := binary.BigEndian.Uint32(b[8:12]) + if blockCount == 0 { + // No blocks to unmap. Explicitly not an error per the spec + return nil + } + // This is technically a 64-bit LBA, but we can't address that many bytes + // let alone blocks, so we just use the lower 32 bits + lba := binary.BigEndian.Uint32(b[4:8]) + + // Make sure the unmap command doesn't extend past the end of the volume + if lba+blockCount > m.blockCount { + return errorLBAOutOfRange + } + + // Convert the emulated block size to the underlying hardware erase block size + blockStart := int64(lba*m.blockSizeUSB) / m.dev.EraseBlockSize() + rawBlockCount := int64(blockCount*m.blockSizeUSB) / m.dev.EraseBlockSize() + + // Unmap the blocks + return m.dev.EraseBlocks(blockStart, rawBlockCount) +} diff --git a/emb/machine/_usb/msc/setup.go b/emb/machine/_usb/msc/setup.go new file mode 100644 index 00000000..3d5bef2d --- /dev/null +++ b/emb/machine/_usb/msc/setup.go @@ -0,0 +1,172 @@ +package msc + +import ( + "machine" + "machine/usb" + "machine/usb/msc/csw" +) + +func setupPacketHandler(setup usb.Setup) bool { + if MSC != nil { + return MSC.setupPacketHandler(setup) + } + return false +} + +func (m *msc) setupPacketHandler(setup usb.Setup) bool { + ok := false + wValue := (uint16(setup.WValueH) << 8) | uint16(setup.WValueL) + switch setup.BRequest { + case usb.CLEAR_FEATURE: + ok = m.handleClearFeature(setup, wValue) + case usb.GET_MAX_LUN: + ok = m.handleGetMaxLun(setup, wValue) + case usb.MSC_RESET: + ok = m.handleReset(setup, wValue) + } + return ok +} + +// Handles the CLEAR_FEATURE request for clearing ENDPOINT_HALT/stall +func (m *msc) handleClearFeature(setup usb.Setup, wValue uint16) bool { + ok := false + // wValue is the feature selector (0x00 for ENDPOINT_HALT) + // We aren't handling any other feature selectors + // https://wiki.osdev.org/Universal_Serial_Bus#CLEAR_FEATURE + if wValue != 0 { + return ok + } + // Clearing the stall is not enough, continue stalling until a reset is received first + // 6.6.1 CBW Not Valid + // If the CBW is not valid, the device shall STALL the Bulk-In pipe. Also, the device + // shall either STALL the Bulk-Out pipe, or the device shall accept and discard any + // Bulk-Out data. The device shall maintain this state until a Reset Recovery + // For Reset Recovery the host shall issue in the following order: : + // (a) a Bulk-Only Mass Storage Reset (handleReset()) + // (b) a Clear Feature HALT to the Bulk-In endpoint (clear stall IN) + // (c) a Clear Feature HALT to the Bulk-Out endpoint (clear stall OUT) + // https://usb.org/sites/default/files/usbmassbulk_10.pdf + if m.state == mscStateNeedReset { + wIndex := setup.WIndex & 0x7F // Clear the direction bit from the endpoint address for comparison + if wIndex == usb.MSC_ENDPOINT_IN { + m.stallEndpoint(usb.MSC_ENDPOINT_IN) + } else if wIndex == usb.MSC_ENDPOINT_OUT { + m.stallEndpoint(usb.MSC_ENDPOINT_OUT) + } + return ok + } + + // Clear the direction bit from the endpoint address for comparison + wIndex := setup.WIndex & 0x7F + + // Clear the IN/OUT stalls if addressed to the endpoint, or both if addressed to the interface + if wIndex == usb.MSC_ENDPOINT_IN || wIndex == mscInterface { + m.clearStallEndpoint(usb.MSC_ENDPOINT_IN) + ok = true + } + if wIndex == usb.MSC_ENDPOINT_OUT || wIndex == mscInterface { + m.clearStallEndpoint(usb.MSC_ENDPOINT_OUT) + ok = true + } + // Send a CSW if needed to resume after the IN endpoint stall is cleared + if m.state == mscStateStatus && wIndex == usb.MSC_ENDPOINT_IN { + m.sendCSW(csw.StatusPassed) + ok = true + } + + if ok { + machine.SendZlp() + } + return ok +} + +// 3.2 Get Max LUN +// https://usb.org/sites/default/files/usbmassbulk_10.pdf +func (m *msc) handleGetMaxLun(setup usb.Setup, wValue uint16) bool { + if setup.WIndex != mscInterface || setup.WLength != 1 || wValue != 0 { + return false + } + // Send the maximum LUN ID number (zero-indexed, so n-1) supported by the device + m.resetBuffer(1) // Shrink buffer to 1 byte + m.buf[0] = m.maxLUN + return machine.SendUSBInPacket(usb.CONTROL_ENDPOINT, m.buf) +} + +// 3.1 Bulk-Only Mass Storage Reset +// https://usb.org/sites/default/files/usbmassbulk_10.pdf +func (m *msc) handleReset(setup usb.Setup, wValue uint16) bool { + if setup.WIndex != mscInterface || setup.WLength != 0 || wValue != 0 { + return false + } + // Reset to command waiting state + m.state = mscStateCmd + + // Reset transfer state + m.resetBuffer(0) + m.senseKey = 0 + m.addlSenseCode = 0 + m.addlSenseQualifier = 0 + + // Send a zero-length packet (ZLP) to indicate the reset is complete + machine.SendZlp() + + // Return true to indicate successful reset + return true +} + +func (m *msc) stallEndpoint(ep uint8) { + if ep == usb.MSC_ENDPOINT_IN { + m.txStalled = true + machine.USBDev.SetStallEPIn(usb.MSC_ENDPOINT_IN) + } else if ep == usb.MSC_ENDPOINT_OUT { + m.rxStalled = true + machine.USBDev.SetStallEPOut(usb.MSC_ENDPOINT_OUT) + } else if ep == usb.CONTROL_ENDPOINT { + machine.USBDev.SetStallEPIn(usb.CONTROL_ENDPOINT) + } +} + +func (m *msc) clearStallEndpoint(ep uint8) { + if ep == usb.MSC_ENDPOINT_IN { + machine.USBDev.ClearStallEPIn(usb.MSC_ENDPOINT_IN) + m.txStalled = false + } else if ep == usb.MSC_ENDPOINT_OUT { + machine.USBDev.ClearStallEPOut(usb.MSC_ENDPOINT_OUT) + m.rxStalled = false + } +} + +func (m *msc) setStringField(field []byte, value string) { + copy(field, []byte(value)) + for i := len(value); i < len(field); i++ { + field[i] = 0x20 // Fill remaining bytes with spaces + } +} + +func (m *msc) SetVendorID(vendorID string) { + m.setStringField(m.vendorID[:], vendorID) +} + +func (m *msc) SetProductID(productID string) { + m.setStringField(m.productID[:], productID) +} + +func (m *msc) SetProductRev(productRev string) { + m.setStringField(m.productRev[:], productRev) +} + +func SetVendorID(vendorID string) { + if MSC != nil { + MSC.SetVendorID(vendorID) + } +} +func SetProductID(productID string) { + if MSC != nil { + MSC.SetProductID(productID) + } +} +func SetProductRev(productRev string) { + if MSC != nil { + MSC.SetProductRev(productRev) + } +} diff --git a/emb/machine/_usb/usb.go b/emb/machine/_usb/usb.go new file mode 100644 index 00000000..40983a9a --- /dev/null +++ b/emb/machine/_usb/usb.go @@ -0,0 +1,146 @@ +package usb + +var ( + // TODO: allow setting these + STRING_LANGUAGE = [2]uint16{(3 << 8) | (2 + 2), 0x0409} // English +) + +const ( + DescriptorConfigCDC = 1 << iota + DescriptorConfigHID + DescriptorConfigMIDI + DescriptorConfigJoystick +) + +const ( + IMANUFACTURER = 1 + IPRODUCT = 2 + ISERIAL = 3 + + ENDPOINT_TYPE_DISABLE = 0xFF + ENDPOINT_TYPE_CONTROL = 0x00 + ENDPOINT_TYPE_ISOCHRONOUS = 0x01 + ENDPOINT_TYPE_BULK = 0x02 + ENDPOINT_TYPE_INTERRUPT = 0x03 + + EndpointOut = 0x00 + EndpointIn = 0x80 + + EndpointPacketSize = 64 // 64 for Full Speed, EPT size max is 1024 + + // bRequest - standard requests + GET_STATUS = 0 + CLEAR_FEATURE = 1 + SET_FEATURE = 3 + SET_ADDRESS = 5 + GET_DESCRIPTOR = 6 + SET_DESCRIPTOR = 7 + GET_CONFIGURATION = 8 + SET_CONFIGURATION = 9 + GET_INTERFACE = 10 + SET_INTERFACE = 11 + + // bRequest - HID class-specific requests + GET_REPORT = 1 + GET_IDLE = 2 + GET_PROTOCOL = 3 + SET_REPORT = 9 + SET_IDLE = 10 + SET_PROTOCOL = 11 + SET_REPORT_TYPE = 33 + + // bRequest - MSC class-specific requests + GET_MAX_LUN = 0xFE + MSC_RESET = 0xFF + + DEVICE_CLASS_COMMUNICATIONS = 0x02 + DEVICE_CLASS_HUMAN_INTERFACE = 0x03 + DEVICE_CLASS_STORAGE = 0x08 + DEVICE_CLASS_VENDOR_SPECIFIC = 0xFF + + CONFIG_POWERED_MASK = 0x40 + CONFIG_BUS_POWERED = 0x80 + CONFIG_SELF_POWERED = 0xC0 + CONFIG_REMOTE_WAKEUP = 0x20 + + // Interface + NumberOfInterfaces = 3 + CDC_ACM_INTERFACE = 0 // CDC ACM + CDC_DATA_INTERFACE = 1 // CDC Data + CDC_FIRST_ENDPOINT = 1 + HID_INTERFACE = 2 // HID + + // Endpoint + CONTROL_ENDPOINT = 0 + CDC_ENDPOINT_ACM = 1 + CDC_ENDPOINT_OUT = 2 + CDC_ENDPOINT_IN = 3 + HID_ENDPOINT_IN = 4 // for Interrupt In + HID_ENDPOINT_OUT = 5 // for Interrupt Out + MIDI_ENDPOINT_IN = 6 // for Bulk In + MIDI_ENDPOINT_OUT = 7 // for Bulk Out + MSC_ENDPOINT_IN = 6 // for Bulk In + MSC_ENDPOINT_OUT = 7 // for Bulk Out + + // bmRequestType + REQUEST_HOSTTODEVICE = 0x00 + REQUEST_DEVICETOHOST = 0x80 + REQUEST_DIRECTION = 0x80 + + REQUEST_STANDARD = 0x00 + REQUEST_CLASS = 0x20 + REQUEST_VENDOR = 0x40 + REQUEST_TYPE = 0x60 + + REQUEST_DEVICE = 0x00 + REQUEST_INTERFACE = 0x01 + REQUEST_ENDPOINT = 0x02 + REQUEST_OTHER = 0x03 + REQUEST_RECIPIENT = 0x1F + + REQUEST_DEVICETOHOST_CLASS_INTERFACE = (REQUEST_DEVICETOHOST | REQUEST_CLASS | REQUEST_INTERFACE) + REQUEST_HOSTTODEVICE_CLASS_INTERFACE = (REQUEST_HOSTTODEVICE | REQUEST_CLASS | REQUEST_INTERFACE) + REQUEST_DEVICETOHOST_STANDARD_INTERFACE = (REQUEST_DEVICETOHOST | REQUEST_STANDARD | REQUEST_INTERFACE) +) + +type Setup struct { + BmRequestType uint8 + BRequest uint8 + WValueL uint8 + WValueH uint8 + WIndex uint16 + WLength uint16 +} + +func NewSetup(data []byte) Setup { + u := Setup{} + u.BmRequestType = uint8(data[0]) + u.BRequest = uint8(data[1]) + u.WValueL = uint8(data[2]) + u.WValueH = uint8(data[3]) + u.WIndex = uint16(data[4]) | (uint16(data[5]) << 8) + u.WLength = uint16(data[6]) | (uint16(data[7]) << 8) + return u +} + +var ( + // VendorID aka VID is the officially assigned vendor number + // for this USB device. Only set this if you know what you are doing, + // since changing it can make it difficult to reflash some devices. + VendorID uint16 + + // ProductID aka PID is the product number associated with the officially assigned + // vendor number for this USB device. Only set this if you know what you are doing, + // since changing it can make it difficult to reflash some devices. + ProductID uint16 + + // Manufacturer is the manufacturer name displayed for this USB device. + Manufacturer string + + // Product is the product name displayed for this USB device. + Product string + + // Serial is the serial value displayed for this USB device. Assign a value to + // transmit the serial to the host when requested. + Serial string +) diff --git a/emb/machine/adc.go b/emb/machine/adc.go new file mode 100644 index 00000000..e4b0cb32 --- /dev/null +++ b/emb/machine/adc.go @@ -0,0 +1,13 @@ +package machine + +// Hardware abstraction layer for the analog-to-digital conversion (ADC) +// peripheral. + +// ADCConfig holds ADC configuration parameters. If left unspecified, the zero +// value of each parameter will use the peripheral's default settings. +type ADCConfig struct { + Reference uint32 // analog reference voltage (AREF) in millivolts + Resolution uint32 // number of bits for a single conversion (e.g., 8, 10, 12) + Samples uint32 // number of samples for a single conversion (e.g., 4, 8, 16, 32) + SampleTime uint32 // sample time, in microseconds (µs) +} diff --git a/emb/machine/board_adafruit-esp32-feather-v2.go b/emb/machine/board_adafruit-esp32-feather-v2.go new file mode 100644 index 00000000..f971dcad --- /dev/null +++ b/emb/machine/board_adafruit-esp32-feather-v2.go @@ -0,0 +1,125 @@ +//go:build adafruit_esp32_feather_v2 + +package machine + +const GPIO20 Pin = 20 + +const ( + IO0 = GPIO0 + IO2 = GPIO2 + IO4 = GPIO4 + IO5 = GPIO5 + IO7 = GPIO7 + IO8 = GPIO8 + IO12 = GPIO12 + IO13 = GPIO13 + IO14 = GPIO14 + IO15 = GPIO15 + IO19 = GPIO19 + IO20 = GPIO20 + IO21 = GPIO21 + IO22 = GPIO22 + IO25 = GPIO25 + IO26 = GPIO26 + IO27 = GPIO27 + IO32 = GPIO32 + IO33 = GPIO33 + IO34 = GPIO34 + IO35 = GPIO35 + IO36 = GPIO36 + IO37 = GPIO37 + IO38 = GPIO38 + IO39 = GPIO39 +) + +// Digital pins +const ( + D12 = IO12 + D13 = IO13 + D14 = IO14 + D15 = IO15 + D27 = IO27 + D32 = IO32 + D33 = IO33 + D37 = IO37 +) + +// Analog pins +const ( + A0 = IO26 + A1 = IO25 + A2 = IO34 + A3 = IO39 + A4 = IO36 + A5 = IO4 +) + +// Built-in LEDs and Button +const ( + WS2812 = IO0 + NEOPIXEL = WS2812 + NEOPIXEL_I2C_POWER = IO2 + LED = IO13 + BUTTON = IO38 +) + +// SPI pins +const ( + SPI_SCK_PIN = IO5 + SPI_MOSI_PIN = IO19 + SPI_MISO_PIN = IO21 + + SPI_SDO_PIN = SPI_MOSI_PIN + SPI_SDI_PIN = SPI_MISO_PIN + + // Silk labels + SCK = SPI_SCK_PIN + MO = SPI_MOSI_PIN + MI = SPI_MISO_PIN +) + +// I2C pins +const ( + I2C_SCL_PIN = IO20 + I2C_SDA_PIN = IO22 + + // Silk labels + SCL = I2C_SCL_PIN + SDA = I2C_SDA_PIN +) + +// ADC pins +const ( + ADC1_0 = IO36 + ADC1_1 = IO37 + ADC1_2 = IO38 + ADC1_3 = IO39 + ADC1_4 = IO32 + ADC1_5 = IO33 + ADC1_6 = IO34 + ADC1_7 = IO35 + + ADC2_0 = IO4 + ADC2_1 = IO0 + ADC2_2 = IO2 + ADC2_3 = IO15 + ADC2_4 = IO13 + ADC2_5 = IO12 + ADC2_6 = IO14 + ADC2_7 = IO27 + ADC2_8 = IO25 + ADC2_9 = IO26 +) + +// UART pins +const ( + UART_TX_PIN = IO19 + UART_RX_PIN = IO22 + + UART2_TX_PIN = IO8 + UART2_RX_PIN = IO7 + + // Silk labels + RX = UART2_RX_PIN + TX = UART2_TX_PIN +) diff --git a/emb/machine/board_ae_rp2040.go b/emb/machine/board_ae_rp2040.go new file mode 100644 index 00000000..716cf72b --- /dev/null +++ b/emb/machine/board_ae_rp2040.go @@ -0,0 +1,86 @@ +//go:build ae_rp2040 + +package machine + +// GPIO pins +const ( + GP0 Pin = GPIO0 + GP1 Pin = GPIO1 + GP2 Pin = GPIO2 + GP3 Pin = GPIO3 + GP4 Pin = GPIO4 + GP5 Pin = GPIO5 + GP6 Pin = GPIO6 + GP7 Pin = GPIO7 + GP8 Pin = GPIO8 + GP9 Pin = GPIO9 + GP10 Pin = GPIO10 + GP11 Pin = GPIO11 + GP12 Pin = GPIO12 + GP13 Pin = GPIO13 + GP14 Pin = GPIO14 + GP15 Pin = GPIO15 + GP16 Pin = GPIO16 + GP17 Pin = GPIO17 + GP18 Pin = GPIO18 + GP19 Pin = GPIO19 + GP20 Pin = GPIO20 + GP21 Pin = GPIO21 + GP22 Pin = GPIO22 + GP26 Pin = GPIO26 + GP27 Pin = GPIO27 + GP28 Pin = GPIO28 + GP29 Pin = GPIO29 + + // Onboard crystal oscillator frequency, in MHz. + xoscFreq = 12 // MHz +) + +// I2C Default pins on Raspberry Pico. +const ( + I2C0_SDA_PIN = GP4 + I2C0_SCL_PIN = GP5 + + I2C1_SDA_PIN = GP2 + I2C1_SCL_PIN = GP3 +) + +// SPI default pins +const ( + // Default Serial Clock Bus 0 for SPI communications + SPI0_SCK_PIN = GPIO18 + // Default Serial Out Bus 0 for SPI communications + SPI0_SDO_PIN = GPIO19 // Tx + // Default Serial In Bus 0 for SPI communications + SPI0_SDI_PIN = GPIO16 // Rx + + // Default Serial Clock Bus 1 for SPI communications + SPI1_SCK_PIN = GPIO10 + // Default Serial Out Bus 1 for SPI communications + SPI1_SDO_PIN = GPIO11 // Tx + // Default Serial In Bus 1 for SPI communications + SPI1_SDI_PIN = GPIO12 // Rx +) + +// UART pins +const ( + UART0_TX_PIN = GPIO0 + UART0_RX_PIN = GPIO1 + UART1_TX_PIN = GPIO8 + UART1_RX_PIN = GPIO9 + UART_TX_PIN = UART0_TX_PIN + UART_RX_PIN = UART0_RX_PIN +) + +var DefaultUART = UART0 + +// USB identifiers +const ( + usb_STRING_PRODUCT = "AE-RP2040" + usb_STRING_MANUFACTURER = "AKIZUKI DENSHI" +) + +var ( + usb_VID uint16 = 0x2E8A + usb_PID uint16 = 0x000A +) diff --git a/emb/machine/board_arduino.go b/emb/machine/board_arduino.go new file mode 100644 index 00000000..a66889ae --- /dev/null +++ b/emb/machine/board_arduino.go @@ -0,0 +1,40 @@ +//go:build arduino + +package machine + +// Digital pins, marked as plain numbers on the board. +const ( + D0 = PD0 // RX + D1 = PD1 // TX + D2 = PD2 + D3 = PD3 + D4 = PD4 + D5 = PD5 + D6 = PD6 + D7 = PD7 + D8 = PB0 + D9 = PB1 + D10 = PB2 + D11 = PB3 + D12 = PB4 + D13 = PB5 +) + +// LED on the Arduino +const LED Pin = D13 + +// ADC on the Arduino +const ( + ADC0 Pin = PC0 + ADC1 Pin = PC1 + ADC2 Pin = PC2 + ADC3 Pin = PC3 + ADC4 Pin = PC4 // Used by TWI for SDA + ADC5 Pin = PC5 // Used by TWI for SCL +) + +// UART pins +const ( + UART_TX_PIN Pin = PD1 + UART_RX_PIN Pin = PD0 +) diff --git a/emb/machine/board_arduino_leonardo.go b/emb/machine/board_arduino_leonardo.go new file mode 100644 index 00000000..01f9570b --- /dev/null +++ b/emb/machine/board_arduino_leonardo.go @@ -0,0 +1,39 @@ +//go:build arduino_leonardo + +package machine + +// Return the current CPU frequency in hertz. +func CPUFrequency() uint32 { + return 16000000 +} + +// Digital pins, marked as plain numbers on the board. +const ( + D0 = PD2 // RX + D1 = PD3 // TX + D2 = PD1 + D3 = PD0 + D4 = PD4 + D5 = PC6 + D6 = PD7 + D7 = PE6 + D8 = PB4 + D9 = PB5 + D10 = PB6 + D11 = PB7 + D12 = PD6 + D13 = PC7 +) + +// LED on the Arduino +const LED Pin = D13 + +// ADC on the Arduino +const ( + ADC0 Pin = PF7 + ADC1 Pin = PF6 + ADC2 Pin = PF5 + ADC3 Pin = PF4 + ADC4 Pin = PF1 + ADC5 Pin = PF0 +) diff --git a/emb/machine/board_arduino_mega1280.go b/emb/machine/board_arduino_mega1280.go new file mode 100644 index 00000000..7bf6090e --- /dev/null +++ b/emb/machine/board_arduino_mega1280.go @@ -0,0 +1,104 @@ +//go:build arduino_mega1280 + +package machine + +// Return the current CPU frequency in hertz. +func CPUFrequency() uint32 { + return 16000000 +} + +const ( + A0 Pin = PF0 + A1 Pin = PF1 + A2 Pin = PF2 + A3 Pin = PF3 + A4 Pin = PF4 + A5 Pin = PF5 + A6 Pin = PF6 + A7 Pin = PF7 + A8 Pin = PK0 + A9 Pin = PK1 + A10 Pin = PK2 + A11 Pin = PK3 + A12 Pin = PK4 + A13 Pin = PK5 + A14 Pin = PK6 + A15 Pin = PK7 + + // Analog Input + ADC0 Pin = PF0 + ADC1 Pin = PF1 + ADC2 Pin = PF2 + ADC3 Pin = PF3 + ADC4 Pin = PF4 + ADC5 Pin = PF5 + ADC6 Pin = PF6 + ADC7 Pin = PF7 + ADC8 Pin = PK0 + ADC9 Pin = PK1 + ADC10 Pin = PK2 + ADC11 Pin = PK3 + ADC12 Pin = PK4 + ADC13 Pin = PK5 + ADC14 Pin = PK6 + ADC15 Pin = PK7 + + // Digital pins + D0 Pin = PE0 + D1 Pin = PE1 + D2 Pin = PE4 + D3 Pin = PE5 + D4 Pin = PG5 + D5 Pin = PE3 + D6 Pin = PH3 + D7 Pin = PH4 + D8 Pin = PH5 + D9 Pin = PH6 + D10 Pin = PB4 + D11 Pin = PB5 + D12 Pin = PB6 + D13 Pin = PB7 + D14 Pin = PJ1 + D15 Pin = PJ0 + D16 Pin = PH1 + D17 Pin = PH0 + D18 Pin = PD3 + D19 Pin = PD2 + D20 Pin = PD1 + D21 Pin = PD0 + D22 Pin = PA0 + D23 Pin = PA1 + D24 Pin = PA2 + D25 Pin = PA3 + D26 Pin = PA4 + D27 Pin = PA5 + D28 Pin = PA6 + D29 Pin = PA7 + D30 Pin = PC7 + D31 Pin = PC6 + D32 Pin = PC5 + D33 Pin = PC4 + D34 Pin = PC3 + D35 Pin = PC2 + D36 Pin = PC1 + D37 Pin = PC0 + D38 Pin = PD7 + D39 Pin = PG2 + D40 Pin = PG1 + D41 Pin = PG0 + D42 Pin = PL7 + D43 Pin = PL6 + D44 Pin = PL5 + D45 Pin = PL4 + D46 Pin = PL3 + D47 Pin = PL2 + D48 Pin = PL1 + D49 Pin = PL0 + D50 Pin = PB3 + D51 Pin = PB2 + D52 Pin = PB1 + D53 Pin = PB0 + + AREF Pin = NoPin + LED Pin = PB7 +) diff --git a/emb/machine/board_arduino_mega2560.go b/emb/machine/board_arduino_mega2560.go new file mode 100644 index 00000000..2e4aefd8 --- /dev/null +++ b/emb/machine/board_arduino_mega2560.go @@ -0,0 +1,165 @@ +//go:build arduino_mega2560 + +package machine + +import ( + "device/avr" + "runtime/interrupt" +) + +// Return the current CPU frequency in hertz. +func CPUFrequency() uint32 { + return 16000000 +} + +const ( + A0 Pin = PF0 + A1 Pin = PF1 + A2 Pin = PF2 + A3 Pin = PF3 + A4 Pin = PF4 + A5 Pin = PF5 + A6 Pin = PF6 + A7 Pin = PF7 + A8 Pin = PK0 + A9 Pin = PK1 + A10 Pin = PK2 + A11 Pin = PK3 + A12 Pin = PK4 + A13 Pin = PK5 + A14 Pin = PK6 + A15 Pin = PK7 + + // Analog Input + ADC0 Pin = PF0 + ADC1 Pin = PF1 + ADC2 Pin = PF2 + ADC3 Pin = PF3 + ADC4 Pin = PF4 + ADC5 Pin = PF5 + ADC6 Pin = PF6 + ADC7 Pin = PF7 + ADC8 Pin = PK0 + ADC9 Pin = PK1 + ADC10 Pin = PK2 + ADC11 Pin = PK3 + ADC12 Pin = PK4 + ADC13 Pin = PK5 + ADC14 Pin = PK6 + ADC15 Pin = PK7 + + // Digital pins + D0 Pin = PE0 + D1 Pin = PE1 + D2 Pin = PE4 + D3 Pin = PE5 + D4 Pin = PG5 + D5 Pin = PE3 + D6 Pin = PH3 + D7 Pin = PH4 + D8 Pin = PH5 + D9 Pin = PH6 + D10 Pin = PB4 + D11 Pin = PB5 + D12 Pin = PB6 + D13 Pin = PB7 + D14 Pin = PJ1 // TX3 + D15 Pin = PJ0 // RX3 + D16 Pin = PH1 // TX2 + D17 Pin = PH0 // RX2 + D18 Pin = PD3 // TX1 + D19 Pin = PD2 // RX1 + D20 Pin = PD1 + D21 Pin = PD0 + D22 Pin = PA0 + D23 Pin = PA1 + D24 Pin = PA2 + D25 Pin = PA3 + D26 Pin = PA4 + D27 Pin = PA5 + D28 Pin = PA6 + D29 Pin = PA7 + D30 Pin = PC7 + D31 Pin = PC6 + D32 Pin = PC5 + D33 Pin = PC4 + D34 Pin = PC3 + D35 Pin = PC2 + D36 Pin = PC1 + D37 Pin = PC0 + D38 Pin = PD7 + D39 Pin = PG2 + D40 Pin = PG1 + D41 Pin = PG0 + D42 Pin = PL7 + D43 Pin = PL6 + D44 Pin = PL5 + D45 Pin = PL4 + D46 Pin = PL3 + D47 Pin = PL2 + D48 Pin = PL1 + D49 Pin = PL0 + D50 Pin = PB3 + D51 Pin = PB2 + D52 Pin = PB1 + D53 Pin = PB0 + + AREF Pin = NoPin + LED Pin = PB7 +) + +// UART pins +const ( + UART_TX_PIN Pin = UART0_TX_PIN + UART_RX_PIN Pin = UART0_RX_PIN + UART0_TX_PIN Pin = D1 + UART0_RX_PIN Pin = D0 + UART1_TX_PIN Pin = D18 + UART1_RX_PIN Pin = D19 + UART2_TX_PIN Pin = D16 + UART2_RX_PIN Pin = D17 + UART3_TX_PIN Pin = D14 + UART3_RX_PIN Pin = D15 +) + +var ( + UART1 = &_UART1 + _UART1 = UART{ + Buffer: NewRingBuffer(), + + dataReg: avr.UDR1, + baudRegH: avr.UBRR1H, + baudRegL: avr.UBRR1L, + statusRegA: avr.UCSR1A, + statusRegB: avr.UCSR1B, + statusRegC: avr.UCSR1C, + } + UART2 = &_UART2 + _UART2 = UART{ + Buffer: NewRingBuffer(), + + dataReg: avr.UDR2, + baudRegH: avr.UBRR2H, + baudRegL: avr.UBRR2L, + statusRegA: avr.UCSR2A, + statusRegB: avr.UCSR2B, + statusRegC: avr.UCSR2C, + } + UART3 = &_UART3 + _UART3 = UART{ + Buffer: NewRingBuffer(), + + dataReg: avr.UDR3, + baudRegH: avr.UBRR3H, + baudRegL: avr.UBRR3L, + statusRegA: avr.UCSR3A, + statusRegB: avr.UCSR3B, + statusRegC: avr.UCSR3C, + } +) + +func init() { + interrupt.New(irq_USART1_RX, _UART1.handleInterrupt) + interrupt.New(irq_USART2_RX, _UART2.handleInterrupt) + interrupt.New(irq_USART3_RX, _UART3.handleInterrupt) +} diff --git a/emb/machine/board_arduino_mkr1000.go b/emb/machine/board_arduino_mkr1000.go new file mode 100644 index 00000000..f5130120 --- /dev/null +++ b/emb/machine/board_arduino_mkr1000.go @@ -0,0 +1,91 @@ +//go:build arduino_mkr1000 + +// This contains the pin mappings for the Arduino MKR1000 board. +// +// For more information, see: https://store.arduino.cc/usa/arduino-mkr1000-with-headers-mounted +package machine + +// used to reset into bootloader +const resetMagicValue = 0x07738135 + +// GPIO Pins +const ( + D0 Pin = PA22 // PWM available + D1 Pin = PA23 // PWM available + D2 Pin = PA10 // PWM available + D3 Pin = PA11 // PWM available + D4 Pin = PB10 // PWM available + D5 Pin = PB11 // PWM available + + D6 Pin = PA20 // PWM available + D7 Pin = PA21 // PWM available + D8 Pin = PA16 // PWM available + D9 Pin = PA17 + D10 Pin = PA19 // PWM available + D11 Pin = PA08 // SDA + D12 Pin = PA09 // PWM available, SCL + D13 Pin = PB23 // RX + D14 Pin = PB22 // TX + + RX0 Pin = PB23 // UART2 RX + TX1 Pin = PB22 // UART2 TX +) + +// Analog pins +const ( + A0 Pin = PA02 // ADC0/AIN[0] + A1 Pin = PB02 // AIN[10] + A2 Pin = PB03 // AIN[11] + A3 Pin = PA04 // AIN[04] + A4 Pin = PA05 // AIN[05] + A5 Pin = PA06 // AIN[06] + A6 Pin = PA07 // AIN[07] +) + +const ( + LED = D6 +) + +// USBCDC pins +const ( + USBCDC_DM_PIN Pin = PA24 + USBCDC_DP_PIN Pin = PA25 +) + +// UART1 pins +const ( + UART_TX_PIN Pin = PB22 + UART_RX_PIN Pin = PB23 +) + +// I2C pins +const ( + SDA_PIN Pin = D11 // SDA + SCL_PIN Pin = D12 // SCL +) + +// SPI pins +const ( + SPI0_SCK_PIN Pin = D9 // SCK: S1 + SPI0_SDO_PIN Pin = D8 // SDO: S1 + SPI0_SDI_PIN Pin = D10 // SDI: S1 +) + +// I2S pins +const ( + I2S_SCK_PIN Pin = PA10 + I2S_SDO_PIN Pin = PA07 + I2S_SDI_PIN = NoPin + I2S_WS_PIN = NoPin // TODO: figure out what this is on Arduino MKR1000 +) + +// USB CDC identifiers +const ( + usb_STRING_PRODUCT = "Arduino MKR1000" + usb_STRING_MANUFACTURER = "Arduino" +) + +var ( + usb_VID uint16 = 0x2341 + usb_PID uint16 = 0x804e +) diff --git a/emb/machine/board_arduino_mkrwifi1010.go b/emb/machine/board_arduino_mkrwifi1010.go new file mode 100644 index 00000000..c68da9b6 --- /dev/null +++ b/emb/machine/board_arduino_mkrwifi1010.go @@ -0,0 +1,124 @@ +//go:build arduino_mkrwifi1010 + +// This contains the pin mappings for the Arduino MKR WiFi 1010 board. +// +// For more information, see: https://store.arduino.cc/usa/mkr-wifi-1010 +package machine + +// used to reset into bootloader +const resetMagicValue = 0x07738135 + +// GPIO Pins +const ( + D0 Pin = PA22 // PWM available + D1 Pin = PA23 // PWM available + D2 Pin = PA10 // PWM available + D3 Pin = PA11 // PWM available + D4 Pin = PB10 // PWM available + D5 Pin = PB11 // PWM available + + D6 Pin = PA20 // PWM available + D7 Pin = PA21 // PWM available + D8 Pin = PA16 // PWM available + D9 Pin = PA17 + D10 Pin = PA19 // PWM available + D11 Pin = PA08 // SDA + D12 Pin = PA09 // PWM available, SCL + D13 Pin = PB23 // RX + D14 Pin = PB22 // TX + + RX0 Pin = PB23 // UART1 RX + TX1 Pin = PB22 // UART1 TX +) + +// Analog pins +const ( + A0 Pin = PA02 // ADC0/AIN[0] + A1 Pin = PB02 // AIN[10] + A2 Pin = PB03 // AIN[11] + A3 Pin = PA04 // AIN[04] + A4 Pin = PA05 // AIN[05] + A5 Pin = PA06 // AIN[06] + A6 Pin = PA07 // AIN[07] +) + +const ( + LED = D6 +) + +// USBCDC pins +const ( + USBCDC_DM_PIN Pin = PA24 + USBCDC_DP_PIN Pin = PA25 +) + +// UART1 pins +const ( + UART_TX_PIN Pin = PB22 + UART_RX_PIN Pin = PB23 +) + +// I2C pins +const ( + SDA_PIN Pin = D11 // SDA + SCL_PIN Pin = D12 // SCL +) + +// SPI pins +const ( + SPI0_SCK_PIN Pin = D9 // SCK: S1 + SPI0_SDO_PIN Pin = D8 // SDO: S1 + SPI0_SDI_PIN Pin = D10 // SDI: S1 +) + +// I2S pins +const ( + I2S_SCK_PIN Pin = PA10 + I2S_SDO_PIN Pin = PA07 + I2S_SDI_PIN = NoPin + I2S_WS_PIN = NoPin // TODO: figure out what this is on Arduino MKR WiFi 1010. +) + +// NINA-W102 Pins +const ( + NINA_SDO Pin = PA12 + NINA_SDI Pin = PA13 + NINA_CS Pin = PA14 + NINA_SCK Pin = PA15 + NINA_GPIO0 Pin = PA27 + NINA_RESETN Pin = PB08 + NINA_ACK Pin = PA28 + NINA_TX Pin = PA22 + NINA_RX Pin = PA23 +) + +// UART on the Arduino MKR WiFi 1010. +var UART1 = &sercomUSART5 + +// I2C on the Arduino MKR WiFi 1010. +var ( + I2C0 = sercomI2CM2 +) + +// SPI on the Arduino MKR WiFi 1010. +var ( + SPI0 = sercomSPIM1 + + SPI1 = sercomSPIM4 + NINA_SPI = SPI1 +) + +// USB CDC identifiers +const ( + usb_STRING_PRODUCT = "Arduino MKR WiFi 1010" + usb_STRING_MANUFACTURER = "Arduino" +) + +var ( + usb_VID uint16 = 0x2341 + usb_PID uint16 = 0x8054 +) + +var ( + DefaultUART = UART1 +) diff --git a/emb/machine/board_arduino_nano.go b/emb/machine/board_arduino_nano.go new file mode 100644 index 00000000..c6772195 --- /dev/null +++ b/emb/machine/board_arduino_nano.go @@ -0,0 +1,40 @@ +//go:build arduino_nano + +package machine + +// Digital pins. +const ( + D0 = PD0 // RX0 + D1 = PD1 // TX1 + D2 = PD2 + D3 = PD3 + D4 = PD4 + D5 = PD5 + D6 = PD6 + D7 = PD7 + D8 = PB0 + D9 = PB1 + D10 = PB2 + D11 = PB3 + D12 = PB4 + D13 = PB5 +) + +// LED on the Arduino +const LED Pin = D13 + +// ADC on the Arduino +const ( + ADC0 Pin = PC0 + ADC1 Pin = PC1 + ADC2 Pin = PC2 + ADC3 Pin = PC3 + ADC4 Pin = PC4 // Used by TWI for SDA + ADC5 Pin = PC5 // Used by TWI for SCL +) + +// UART pins +const ( + UART_TX_PIN Pin = PD1 + UART_RX_PIN Pin = PD0 +) diff --git a/emb/machine/board_arduino_nano33.go b/emb/machine/board_arduino_nano33.go new file mode 100644 index 00000000..9232d381 --- /dev/null +++ b/emb/machine/board_arduino_nano33.go @@ -0,0 +1,135 @@ +//go:build arduino_nano33 + +// This contains the pin mappings for the Arduino Nano33 IoT board. +// +// For more information, see: https://store.arduino.cc/nano-33-iot +package machine + +// used to reset into bootloader +const resetMagicValue = 0x07738135 + +// GPIO Pins +const ( + RX0 Pin = PB23 // UART2 RX + TX1 Pin = PB22 // UART2 TX + + D2 Pin = PB10 // PWM available + D3 Pin = PB11 // PWM available + D4 Pin = PA07 + D5 Pin = PA05 // PWM available + D6 Pin = PA04 // PWM available + D7 Pin = PA06 + + D8 Pin = PA18 + D9 Pin = PA20 // PWM available + D10 Pin = PA21 // PWM available + D11 Pin = PA16 // PWM available + D12 Pin = PA19 // PWM available + + D13 Pin = PA17 +) + +// Analog pins +const ( + A0 Pin = PA02 // ADC/AIN[0] + A1 Pin = PB02 // ADC/AIN[10] + A2 Pin = PA11 // ADC/AIN[19] + A3 Pin = PA10 // ADC/AIN[18], + A4 Pin = PB08 // ADC/AIN[2], SCL: SERCOM2/PAD[1] + A5 Pin = PB09 // ADC/AIN[3], SDA: SERCOM2/PAD[1] + A6 Pin = PA09 // ADC/AIN[17] + A7 Pin = PB03 // ADC/AIN[11] +) + +const ( + LED = D13 +) + +// USBCDC pins +const ( + USBCDC_DM_PIN Pin = PA24 + USBCDC_DP_PIN Pin = PA25 +) + +// UART1 pins +const ( + UART_TX_PIN Pin = PA22 + UART_RX_PIN Pin = PA23 +) + +// UART1 on the Arduino Nano 33 connects to the onboard NINA-W102 WiFi chip. +var UART1 = &sercomUSART3 + +// UART2 on the Arduino Nano 33 connects to the normal TX/RX pins. +var UART2 = &sercomUSART5 + +// UART_NINA on the Arduino Nano 33 connects to the NINA HCI. +var UART_NINA = &sercomUSART2 + +// I2C pins +const ( + SDA_PIN Pin = A4 // SDA: SERCOM4/PAD[1] + SCL_PIN Pin = A5 // SCL: SERCOM4/PAD[1] +) + +// I2C on the Arduino Nano 33. +var ( + I2C0 = sercomI2CM4 +) + +// SPI pins +const ( + SPI0_SCK_PIN Pin = D13 // SCK: SERCOM1/PAD[1] + SPI0_SDO_PIN Pin = D11 // SDO: SERCOM1/PAD[0] + SPI0_SDI_PIN Pin = D12 // SDI: SERCOM1/PAD[3] +) + +// SPI on the Arduino Nano 33. +var SPI0 = sercomSPIM1 + +// SPI1 is connected to the NINA-W102 chip on the Arduino Nano 33. +var ( + SPI1 = sercomSPIM2 + NINA_SPI = SPI1 +) + +// NINA-W102 Pins +const ( + NINA_SDO Pin = PA12 + NINA_SDI Pin = PA13 + NINA_CS Pin = PA14 + NINA_SCK Pin = PA15 + NINA_GPIO0 Pin = PA27 + NINA_RESETN Pin = PA08 + NINA_ACK Pin = PA28 + NINA_TX Pin = PA12 + NINA_RX Pin = PA13 + NINA_RTS Pin = PA14 + NINA_CTS Pin = PA15 +) + +// NINA-W102 settings +const ( + NINA_BAUDRATE = 912600 + NINA_RESET_INVERTED = true + NINA_SOFT_FLOWCONTROL = false +) + +// I2S pins +const ( + I2S_SCK_PIN Pin = PA10 + I2S_SDO_PIN Pin = PA08 + I2S_SDI_PIN = NoPin + I2S_WS_PIN = NoPin // TODO: figure out what this is on Arduino Nano 33. +) + +// USB CDC identifiers +const ( + usb_STRING_PRODUCT = "Arduino NANO 33 IoT" + usb_STRING_MANUFACTURER = "Arduino" +) + +var ( + usb_VID uint16 = 0x2341 + usb_PID uint16 = 0x8057 +) diff --git a/emb/machine/board_arduino_zero.go b/emb/machine/board_arduino_zero.go new file mode 100644 index 00000000..758fcb16 --- /dev/null +++ b/emb/machine/board_arduino_zero.go @@ -0,0 +1,102 @@ +//go:build sam && atsamd21 && arduino_zero + +package machine + +// used to reset into bootloader +const resetMagicValue = 0x07738135 + +// GPIO Pins - Digital Low +const ( + D0 = PA11 // RX + D1 = PA10 // TX + D2 = PA14 + D3 = PA09 // PWM available + D4 = PA08 // PWM available + D5 = PA15 // PWM available + D6 = PA20 // PWM available + D7 = PA21 +) + +// GPIO Pins - Digital High +const ( + D8 = PA06 // PWM available + D9 = PA07 // PWM available + D10 = PA18 // PWM available + D11 = PA16 // PWM available + D12 = PA19 // PWM available + D13 = PA17 // PWM available +) + +// ADC pins +const ( + AREF Pin = PA03 + ADC0 Pin = PA02 + ADC1 Pin = PB08 + ADC2 Pin = PB09 + ADC3 Pin = PA04 + ADC4 Pin = PA05 + ADC5 Pin = PB02 +) + +// LEDs on the Arduino Zero +const ( + LED = LED1 + LED1 Pin = D13 + LED2 Pin = PA27 // TX LED + LED3 Pin = PB03 // RX LED +) + +// SPI pins - EDBG connected +const ( + SPI0_SDO_PIN Pin = PA16 // MOSI: SERCOM1/PAD[0] + SPI0_SDI_PIN Pin = PA19 // MISO: SERCOM1/PAD[2] + SPI0_SCK_PIN Pin = PA17 // SCK: SERCOM1/PAD[3] +) + +// SPI pins (Legacy ICSP) +const ( + SPI1_SDO_PIN Pin = PB10 // MOSI: SERCOM4/PAD[2] - Pin 4 + SPI1_SDI_PIN Pin = PA12 // MISO: SERCOM4/PAD[0] - Pin 1 + SPI1_SCK_PIN Pin = PB11 // SCK: SERCOM4/PAD[3] - Pin 3 +) + +// I2C pins - EDBG connected +const ( + SDA_PIN Pin = PA22 // SDA: SERCOM3/PAD[0] - Pin 20 + SCL_PIN Pin = PA23 // SCL: SERCOM3/PAD[1] - Pin 21 +) + +// I2S pins - might not be exposed +const ( + I2S_SCK_PIN Pin = PA10 + I2S_SDO_PIN Pin = PA07 + I2S_SDI_PIN = NoPin + I2S_WS_PIN Pin = PA11 +) + +// UART0 pins - EDBG connected +const ( + UART_RX_PIN Pin = D0 + UART_TX_PIN Pin = D1 +) + +// 'native' USB port pins +const ( + USBCDC_DM_PIN Pin = PA24 + USBCDC_DP_PIN Pin = PA25 +) + +// USB CDC identifiers +const ( + usb_STRING_PRODUCT = "Arduino Zero" + usb_STRING_MANUFACTURER = "Arduino LLC" + + usb_VID uint16 = 0x2341 + usb_PID uint16 = 0x804d +) + +// 32.768 KHz Crystal +const ( + XIN32 Pin = PA00 + XOUT32 Pin = PA01 +) diff --git a/emb/machine/board_atmega328p.go b/emb/machine/board_atmega328p.go new file mode 100644 index 00000000..234bf315 --- /dev/null +++ b/emb/machine/board_atmega328p.go @@ -0,0 +1,42 @@ +//go:build (avr && atmega328p) || arduino || arduino_nano + +package machine + +// Return the current CPU frequency in hertz. +func CPUFrequency() uint32 { + return 16000000 +} + +const ( + // Note: start at port B because there is no port A. + portB Pin = iota * 8 + portC + portD +) + +const ( + PB0 = portB + 0 + PB1 = portB + 1 // peripherals: Timer1 channel A + PB2 = portB + 2 // peripherals: Timer1 channel B + PB3 = portB + 3 // peripherals: Timer2 channel A + PB4 = portB + 4 + PB5 = portB + 5 + PB6 = portB + 6 + PB7 = portB + 7 + PC0 = portC + 0 + PC1 = portC + 1 + PC2 = portC + 2 + PC3 = portC + 3 + PC4 = portC + 4 + PC5 = portC + 5 + PC6 = portC + 6 + PC7 = portC + 7 + PD0 = portD + 0 + PD1 = portD + 1 + PD2 = portD + 2 + PD3 = portD + 3 // peripherals: Timer2 channel B + PD4 = portD + 4 + PD5 = portD + 5 // peripherals: Timer0 channel B + PD6 = portD + 6 // peripherals: Timer0 channel A + PD7 = portD + 7 +) diff --git a/emb/machine/board_atmega328pb.go b/emb/machine/board_atmega328pb.go new file mode 100644 index 00000000..e3e45f01 --- /dev/null +++ b/emb/machine/board_atmega328pb.go @@ -0,0 +1,51 @@ +//go:build avr && atmega328pb + +package machine + +// Return the current CPU frequency in hertz. +func CPUFrequency() uint32 { + return 16000000 +} + +const ( + // Note: start at port B because there is no port A. + portB Pin = iota * 8 + portC + portD + portE +) + +const ( + PB0 = portB + 0 + PB1 = portB + 1 // peripherals: Timer1 channel A + PB2 = portB + 2 // peripherals: Timer1 channel B + PB3 = portB + 3 // peripherals: Timer2 channel A + PB4 = portB + 4 + PB5 = portB + 5 + PB6 = portB + 6 + PB7 = portB + 7 + PC0 = portC + 0 + PC1 = portC + 1 + PC2 = portC + 2 + PC3 = portC + 3 + PC4 = portC + 4 + PC5 = portC + 5 + PC6 = portC + 6 + PC7 = portC + 7 + PD0 = portD + 0 + PD1 = portD + 1 + PD2 = portD + 2 + PD3 = portD + 3 // peripherals: Timer2 channel B + PD4 = portD + 4 + PD5 = portD + 5 // peripherals: Timer0 channel B + PD6 = portD + 6 // peripherals: Timer0 channel A + PD7 = portD + 7 + PE0 = portE + 0 + PE1 = portE + 1 + PE2 = portE + 2 + PE3 = portE + 3 + PE4 = portE + 4 + PE5 = portE + 5 + PE6 = portE + 6 + PE7 = portE + 7 +) diff --git a/emb/machine/board_atsamd21.go b/emb/machine/board_atsamd21.go new file mode 100644 index 00000000..87822499 --- /dev/null +++ b/emb/machine/board_atsamd21.go @@ -0,0 +1,76 @@ +//go:build (sam && atsamd21) || arduino_nano33 || circuitplay_express + +package machine + +// Return the current CPU frequency in hertz. +func CPUFrequency() uint32 { + return 48000000 +} + +// Hardware pins +const ( + PA00 Pin = 0 // peripherals: TCC2 channel 0 + PA01 Pin = 1 // peripherals: TCC2 channel 1 + PA02 Pin = 2 + PA03 Pin = 3 + PA04 Pin = 4 // peripherals: TCC0 channel 0 + PA05 Pin = 5 // peripherals: TCC0 channel 1 + PA06 Pin = 6 // peripherals: TCC1 channel 0 + PA07 Pin = 7 // peripherals: TCC1 channel 1 + PA08 Pin = 8 // peripherals: TCC0 channel 0, TCC1 channel 2 + PA09 Pin = 9 // peripherals: TCC0 channel 1, TCC1 channel 3 + PA10 Pin = 10 // peripherals: TCC1 channel 0, TCC0 channel 2 + PA11 Pin = 11 // peripherals: TCC1 channel 1, TCC0 channel 3 + PA12 Pin = 12 // peripherals: TCC2 channel 0, TCC0 channel 2 + PA13 Pin = 13 // peripherals: TCC2 channel 1, TCC0 channel 3 + PA14 Pin = 14 // peripherals: TCC0 channel 0 + PA15 Pin = 15 // peripherals: TCC0 channel 1 + PA16 Pin = 16 // peripherals: TCC2 channel 0, TCC0 channel 2 + PA17 Pin = 17 // peripherals: TCC2 channel 1, TCC0 channel 3 + PA18 Pin = 18 // peripherals: TCC0 channel 2 + PA19 Pin = 19 // peripherals: TCC0 channel 3 + PA20 Pin = 20 // peripherals: TCC0 channel 2 + PA21 Pin = 21 // peripherals: TCC0 channel 3 + PA22 Pin = 22 // peripherals: TCC0 channel 0 + PA23 Pin = 23 // peripherals: TCC0 channel 1 + PA24 Pin = 24 // peripherals: TCC1 channel 2 + PA25 Pin = 25 // peripherals: TCC1 channel 3 + PA26 Pin = 26 + PA27 Pin = 27 + PA28 Pin = 28 + PA29 Pin = 29 + PA30 Pin = 30 // peripherals: TCC1 channel 0 + PA31 Pin = 31 // peripherals: TCC1 channel 1 + PB00 Pin = 32 + PB01 Pin = 33 + PB02 Pin = 34 + PB03 Pin = 35 + PB04 Pin = 36 + PB05 Pin = 37 + PB06 Pin = 38 + PB07 Pin = 39 + PB08 Pin = 40 + PB09 Pin = 41 + PB10 Pin = 42 // peripherals: TCC0 channel 0 + PB11 Pin = 43 // peripherals: TCC0 channel 1 + PB12 Pin = 44 // peripherals: TCC0 channel 2 + PB13 Pin = 45 // peripherals: TCC0 channel 3 + PB14 Pin = 46 + PB15 Pin = 47 + PB16 Pin = 48 // peripherals: TCC0 channel 0 + PB17 Pin = 49 // peripherals: TCC0 channel 1 + PB18 Pin = 50 + PB19 Pin = 51 + PB20 Pin = 52 + PB21 Pin = 53 + PB22 Pin = 54 + PB23 Pin = 55 + PB24 Pin = 56 + PB25 Pin = 57 + PB26 Pin = 58 + PB27 Pin = 59 + PB28 Pin = 60 + PB29 Pin = 61 + PB30 Pin = 62 // peripherals: TCC0 channel 0, TCC1 channel 2 + PB31 Pin = 63 // peripherals: TCC0 channel 1, TCC1 channel 3 +) diff --git a/emb/machine/board_atsame54-xpro.go b/emb/machine/board_atsame54-xpro.go new file mode 100644 index 00000000..ae1086d4 --- /dev/null +++ b/emb/machine/board_atsame54-xpro.go @@ -0,0 +1,293 @@ +//go:build atsame54_xpro + +package machine + +import ( + "device/sam" +) + +// Definition for compatibility, but not used +const resetMagicValue = 0x00000000 + +const ( + LED = PC18 + BUTTON = PB31 +) + +const ( + // https://ww1.microchip.com/downloads/en/DeviceDoc/70005321A.pdf + + // Extension Header EXT1 + EXT1_PIN3_ADC_P = PB04 + EXT1_PIN4_ADC_N = PB05 + EXT1_PIN5_GPIO1 = PA06 + EXT1_PIN6_GPIO2 = PA07 + EXT1_PIN7_PWM_P = PB08 + EXT1_PIN8_PWM_N = PB09 + EXT1_PIN9_IRQ = PB07 + EXT1_PIN9_GPIO = PB07 + EXT1_PIN10_SPI_SS_B = PA27 + EXT1_PIN10_GPIO = PA27 + EXT1_PIN11_TWI_SDA = PA22 + EXT1_PIN12_TWI_SCL = PA23 + EXT1_PIN13_UART_RX = PA05 + EXT1_PIN14_UART_TX = PA04 + EXT1_PIN15_SPI_SS_A = PB28 + EXT1_PIN16_SPI_SDO = PB27 + EXT1_PIN17_SPI_SDI = PB29 + EXT1_PIN18_SPI_SCK = PB26 + + // Extension Header EXT2 + EXT2_PIN3_ADC_P = PB00 + EXT2_PIN4_ADC_N = PA03 + EXT2_PIN5_GPIO1 = PB01 + EXT2_PIN6_GPIO2 = PB06 + EXT2_PIN7_PWM_P = PB14 + EXT2_PIN8_PWM_N = PB15 + EXT2_PIN9_IRQ = PD00 + EXT2_PIN9_GPIO = PD00 + EXT2_PIN10_SPI_SS_B = PB02 + EXT2_PIN10_GPIO = PB02 + EXT2_PIN11_TWI_SDA = PD08 + EXT2_PIN12_TWI_SCL = PD09 + EXT2_PIN13_UART_RX = PB17 + EXT2_PIN14_UART_TX = PB16 + EXT2_PIN15_SPI_SS_A = PC06 + EXT2_PIN16_SPI_SDO = PC04 + EXT2_PIN17_SPI_SDI = PC07 + EXT2_PIN18_SPI_SCK = PC05 + + // Extension Header EXT3 + EXT3_PIN3_ADC_P = PC02 + EXT3_PIN4_ADC_N = PC03 + EXT3_PIN5_GPIO1 = PC01 + EXT3_PIN6_GPIO2 = PC10 + EXT3_PIN7_PWM_P = PD10 + EXT3_PIN8_PWM_N = PD11 + EXT3_PIN9_IRQ = PC30 + EXT3_PIN9_GPIO = PC30 + EXT3_PIN10_SPI_SS_B = PC31 + EXT3_PIN10_GPIO = PC31 + EXT3_PIN11_TWI_SDA = PD08 + EXT3_PIN12_TWI_SCL = PD09 + EXT3_PIN13_UART_RX = PC23 + EXT3_PIN14_UART_TX = PC22 + EXT3_PIN15_SPI_SS_A = PC14 + EXT3_PIN16_SPI_SDO = PC04 + EXT3_PIN17_SPI_SDI = PC07 + EXT3_PIN18_SPI_SCK = PC05 + + // SD_CARD + SD_CARD_MCDA0 = PB18 + SD_CARD_MCDA1 = PB19 + SD_CARD_MCDA2 = PB20 + SD_CARD_MCDA3 = PB21 + SD_CARD_MCCK = PA21 + SD_CARD_MCCDA = PA20 + SD_CARD_DETECT = PD20 + SD_CARD_PROTECT = PD21 + + // I2C + I2C_SDA = PD08 + I2C_SCL = PD09 + + // CAN + CAN0_TX = PA22 + CAN0_RX = PA23 + + CAN1_STANDBY = PC13 + CAN1_TX = PB12 + CAN1_RX = PB13 + + CAN_STANDBY = CAN1_STANDBY + CAN_TX = CAN1_TX + CAN_RX = CAN1_RX + + // PDEC + PDEC_PHASE_A = PC16 + PDEC_PHASE_B = PC17 + PDEC_INDEX = PC18 + + // PCC + PCC_I2C_SDA = PD08 + PCC_I2C_SCL = PD09 + PCC_VSYNC_DEN1 = PA12 + PCC_HSYNC_DEN2 = PA13 + PCC_CLK = PA14 + PCC_XCLK = PA15 + PCC_DATA00 = PA16 + PCC_DATA01 = PA17 + PCC_DATA02 = PA18 + PCC_DATA03 = PA19 + PCC_DATA04 = PA20 + PCC_DATA05 = PA21 + PCC_DATA06 = PA22 + PCC_DATA07 = PA23 + PCC_DATA08 = PB14 + PCC_DATA09 = PB15 + PCC_RESET = PC12 + PCC_PWDN = PC11 + + // Ethernet + ETHERNET_TXCK = PA14 + ETHERNET_TXEN = PA17 + ETHERNET_TX0 = PA18 + ETHERNET_TX1 = PA19 + ETHERNET_RXER = PA15 + ETHERNET_RX0 = PA13 + ETHERNET_RX1 = PA12 + ETHERNET_RXDV = PC20 + ETHERNET_MDIO = PC12 + ETHERNET_MDC = PC11 + ETHERNET_INT = PD12 + ETHERNET_RESET = PC21 + + PIN_QT_BUTTON = PA16 + PIN_BTN0 = PB31 + PIN_ETH_LED = PC15 + PIN_LED0 = PC18 + PIN_ADC_DAC = PA02 + PIN_VBUS_DETECT = PC00 + PIN_USB_ID = PC19 +) + +// USBCDC pins +const ( + USBCDC_DM_PIN = PA24 + USBCDC_DP_PIN = PA25 +) + +// UART pins +const ( + // Extension Header EXT1 + UART_TX_PIN = PA04 // TX : SERCOM0/PAD[0] + UART_RX_PIN = PA05 // RX : SERCOM0/PAD[1] + + // Extension Header EXT2 + UART2_TX_PIN = PB16 // TX : SERCOM5/PAD[0] + UART2_RX_PIN = PB17 // RX : SERCOM5/PAD[1] + + // Extension Header EXT3 + UART3_TX_PIN = PC22 // TX : SERCOM1/PAD[0] + UART3_RX_PIN = PC23 // RX : SERCOM1/PAD[1] + + // Virtual COM Port + UART4_TX_PIN = PB25 // TX : SERCOM2/PAD[0] + UART4_RX_PIN = PB24 // RX : SERCOM2/PAD[1] +) + +// I2C pins +const ( + // Extension Header EXT1 + SDA0_PIN = PA22 // SDA: SERCOM3/PAD[0] + SCL0_PIN = PA23 // SCL: SERCOM3/PAD[1] + + // Extension Header EXT2 + SDA1_PIN = PD08 // SDA: SERCOM7/PAD[0] + SCL1_PIN = PD09 // SCL: SERCOM7/PAD[1] + + // Extension Header EXT3 + SDA2_PIN = PD08 // SDA: SERCOM7/PAD[0] + SCL2_PIN = PD09 // SCL: SERCOM7/PAD[1] + + // Data Gateway Interface + SDA_DGI_PIN = PD08 // SDA: SERCOM7/PAD[0] + SCL_DGI_PIN = PD09 // SCL: SERCOM7/PAD[1] + + SDA_PIN = SDA0_PIN + SCL_PIN = SCL0_PIN +) + +// SPI pins +const ( + // Extension Header EXT1 + SPI0_SCK_PIN = PB26 // SCK: SERCOM4/PAD[1] + SPI0_SDO_PIN = PB27 // SDO: SERCOM4/PAD[0] + SPI0_SDI_PIN = PB29 // SDI: SERCOM4/PAD[3] + SPI0_SS_PIN = PB28 // SS : SERCOM4/PAD[2] + + // Extension Header EXT2 + SPI1_SCK_PIN = PC05 // SCK: SERCOM6/PAD[1] + SPI1_SDO_PIN = PC04 // SDO: SERCOM6/PAD[0] + SPI1_SDI_PIN = PC07 // SDI: SERCOM6/PAD[3] + SPI1_SS_PIN = PC06 // SS : SERCOM6/PAD[2] + + // Extension Header EXT3 + SPI2_SCK_PIN = PC05 // SCK: SERCOM6/PAD[1] + SPI2_SDO_PIN = PC04 // SDO: SERCOM6/PAD[0] + SPI2_SDI_PIN = PC07 // SDI: SERCOM6/PAD[3] + SPI2_SS_PIN = PC14 // SS : GPIO + + // Data Gateway Interface + SPI_DGI_SCK_PIN = PC05 // SCK: SERCOM6/PAD[1] + SPI_DGI_SDO_PIN = PC04 // SDO: SERCOM6/PAD[0] + SPI_DGI_SDI_PIN = PC07 // SDI: SERCOM6/PAD[3] + SPI_DGI_SS_PIN = PD01 // SS : GPIO +) + +// USB CDC identifiers +const ( + usb_STRING_PRODUCT = "SAM E54 Xplained Pro" + usb_STRING_MANUFACTURER = "Atmel" +) + +var ( + usb_VID uint16 = 0x03EB + usb_PID uint16 = 0x2404 +) + +// UART on the SAM E54 Xplained Pro +var ( + // Extension Header EXT1 + UART1 = &sercomUSART0 + + // Extension Header EXT2 + UART2 = &sercomUSART5 + + // Extension Header EXT3 + UART3 = &sercomUSART1 + + // EDBG Virtual COM Port + UART4 = &sercomUSART2 +) + +// I2C on the SAM E54 Xplained Pro +var ( + // Extension Header EXT1 + I2C0 = sercomI2CM3 + + // Extension Header EXT2 + I2C1 = sercomI2CM7 + + // Extension Header EXT3 + I2C2 = sercomI2CM7 + + // Data Gateway Interface + I2C3 = sercomI2CM7 +) + +// SPI on the SAM E54 Xplained Pro +var ( + // Extension Header EXT1 + SPI0 = sercomSPIM4 + + // Extension Header EXT2 + SPI1 = sercomSPIM6 + + // Extension Header EXT3 + SPI2 = sercomSPIM6 + + // Data Gateway Interface + SPI3 = sercomSPIM6 +) + +// CAN on the SAM E54 Xplained Pro +var ( + CAN0 = CAN{ + Bus: sam.CAN0, + } + + CAN1 = CAN{ + Bus: sam.CAN1, + } +) diff --git a/emb/machine/board_badger2040-w.go b/emb/machine/board_badger2040-w.go new file mode 100644 index 00000000..d0982653 --- /dev/null +++ b/emb/machine/board_badger2040-w.go @@ -0,0 +1,95 @@ +//go:build badger2040_w + +// This contains the pin mappings for the Badger 2040 W board. +// +// For more information, see: https://shop.pimoroni.com/products/badger-2040-w +// Also +// - Badger 2040 W schematic: https://cdn.shopify.com/s/files/1/0174/1800/files/badger_w_schematic.pdf?v=1675859004 +package machine + +const ( + LED Pin = GPIO22 + + BUTTON_A Pin = GPIO12 + BUTTON_B Pin = GPIO13 + BUTTON_C Pin = GPIO14 + BUTTON_UP Pin = GPIO15 + BUTTON_DOWN Pin = GPIO11 + BUTTON_USER Pin = NoPin // Not available on Badger 2040 W + + EPD_BUSY_PIN Pin = GPIO26 + EPD_RESET_PIN Pin = GPIO21 + EPD_DC_PIN Pin = GPIO20 + EPD_CS_PIN Pin = GPIO17 + EPD_SCK_PIN Pin = GPIO18 + EPD_SDO_PIN Pin = GPIO19 + + VBUS_DETECT Pin = GPIO24 + VREF_POWER Pin = GPIO27 + VREF_1V24 Pin = GPIO28 + VBAT_SENSE Pin = GPIO29 + ENABLE_3V3 Pin = GPIO10 + + BATTERY = VBAT_SENSE + RTC_ALARM = GPIO8 +) + +// I2C pins +const ( + I2C0_SDA_PIN Pin = GPIO4 + I2C0_SCL_PIN Pin = GPIO5 + + I2C1_SDA_PIN Pin = NoPin + I2C1_SCL_PIN Pin = NoPin +) + +// SPI pins. +const ( + SPI0_SCK_PIN Pin = GPIO18 + SPI0_SDO_PIN Pin = GPIO19 + SPI0_SDI_PIN Pin = GPIO16 + + SPI1_SCK_PIN Pin = NoPin + SPI1_SDO_PIN Pin = NoPin + SPI1_SDI_PIN Pin = NoPin +) + +// QSPI pins¿? +const ( +/* + TODO + +SPI0_SD0_PIN Pin = QSPI_SD0 +SPI0_SD1_PIN Pin = QSPI_SD1 +SPI0_SD2_PIN Pin = QSPI_SD2 +SPI0_SD3_PIN Pin = QSPI_SD3 +SPI0_SCK_PIN Pin = QSPI_SCLKGPIO6 +SPI0_CS_PIN Pin = QSPI_CS +*/ +) + +// Onboard crystal oscillator frequency, in MHz. +const ( + xoscFreq = 12 // MHz +) + +// USB CDC identifiers +const ( + usb_STRING_PRODUCT = "Badger 2040 W" + usb_STRING_MANUFACTURER = "Pimoroni" +) + +var ( + usb_VID uint16 = 0x2e8a + usb_PID uint16 = 0x0003 +) + +// UART pins +const ( + UART0_TX_PIN = GPIO0 + UART0_RX_PIN = GPIO1 + UART_TX_PIN = UART0_TX_PIN + UART_RX_PIN = UART0_RX_PIN +) + +var DefaultUART = UART0 diff --git a/emb/machine/board_badger2040.go b/emb/machine/board_badger2040.go new file mode 100644 index 00000000..73f802a9 --- /dev/null +++ b/emb/machine/board_badger2040.go @@ -0,0 +1,94 @@ +//go:build badger2040 + +// This contains the pin mappings for the Badger 2040 board. +// +// For more information, see: https://shop.pimoroni.com/products/badger-2040 +// Also +// - Badger 2040 schematic: https://cdn.shopify.com/s/files/1/0174/1800/files/badger_2040_schematic.pdf?v=1645702148 +package machine + +const ( + LED Pin = GPIO25 + + BUTTON_A Pin = GPIO12 + BUTTON_B Pin = GPIO13 + BUTTON_C Pin = GPIO14 + BUTTON_UP Pin = GPIO15 + BUTTON_DOWN Pin = GPIO11 + BUTTON_USER Pin = GPIO23 + + EPD_BUSY_PIN Pin = GPIO26 + EPD_RESET_PIN Pin = GPIO21 + EPD_DC_PIN Pin = GPIO20 + EPD_CS_PIN Pin = GPIO17 + EPD_SCK_PIN Pin = GPIO18 + EPD_SDO_PIN Pin = GPIO19 + + VBUS_DETECT Pin = GPIO24 + VREF_POWER Pin = GPIO27 + VREF_1V24 Pin = GPIO28 + VBAT_SENSE Pin = GPIO29 + ENABLE_3V3 Pin = GPIO10 + + BATTERY = VBAT_SENSE +) + +// I2C pins +const ( + I2C0_SDA_PIN Pin = GPIO4 + I2C0_SCL_PIN Pin = GPIO5 + + I2C1_SDA_PIN Pin = NoPin + I2C1_SCL_PIN Pin = NoPin +) + +// SPI pins. +const ( + SPI0_SCK_PIN Pin = GPIO18 + SPI0_SDO_PIN Pin = GPIO19 + SPI0_SDI_PIN Pin = GPIO16 + + SPI1_SCK_PIN Pin = NoPin + SPI1_SDO_PIN Pin = NoPin + SPI1_SDI_PIN Pin = NoPin +) + +// QSPI pins¿? +const ( +/* + TODO + +SPI0_SD0_PIN Pin = QSPI_SD0 +SPI0_SD1_PIN Pin = QSPI_SD1 +SPI0_SD2_PIN Pin = QSPI_SD2 +SPI0_SD3_PIN Pin = QSPI_SD3 +SPI0_SCK_PIN Pin = QSPI_SCLKGPIO6 +SPI0_CS_PIN Pin = QSPI_CS +*/ +) + +// Onboard crystal oscillator frequency, in MHz. +const ( + xoscFreq = 12 // MHz +) + +// USB CDC identifiers +const ( + usb_STRING_PRODUCT = "Badger 2040" + usb_STRING_MANUFACTURER = "Pimoroni" +) + +var ( + usb_VID uint16 = 0x2e8a + usb_PID uint16 = 0x0003 +) + +// UART pins +const ( + UART0_TX_PIN = GPIO0 + UART0_RX_PIN = GPIO1 + UART_TX_PIN = UART0_TX_PIN + UART_RX_PIN = UART0_RX_PIN +) + +var DefaultUART = UART0 diff --git a/emb/machine/board_bluemicro840.go b/emb/machine/board_bluemicro840.go new file mode 100644 index 00000000..acca7091 --- /dev/null +++ b/emb/machine/board_bluemicro840.go @@ -0,0 +1,80 @@ +//go:build bluemicro840 + +package machine + +const HasLowFrequencyCrystal = true + +// GPIO Pins +const ( + D006 = P0_06 + D008 = P0_08 + D015 = P0_15 + D017 = P0_17 + D020 = P0_20 + D013 = P0_13 + D024 = P0_24 + D009 = P0_09 + D010 = P0_10 + D106 = P1_06 + + D031 = P0_31 // AIN7; P0.31 (AIN7) is used to read the voltage of the battery via ADC. It can’t be used for any other function. + D012 = P0_12 // VCC 3.3V; P0.12 on VCC shuts off the power to VCC when you set it to high; This saves on battery immensely for LEDs of all kinds that eat power even when off + + D030 = P0_30 + D026 = P0_26 + D029 = P0_29 + D002 = P0_02 + D113 = P1_13 + D003 = P0_03 + D028 = P0_28 + D111 = P1_11 +) + +// Analog Pins +const ( + AIN0 = P0_02 + AIN1 = P0_03 + AIN2 = P0_04 // Not Connected + AIN3 = P0_05 // Not Connected + AIN4 = P0_28 + AIN5 = P0_29 + AIN6 = P0_30 + AIN7 = P0_31 // Battery +) + +const ( + LED1 Pin = P1_04 // Red LED + LED2 Pin = P1_10 // Blue LED + LED Pin = LED1 +) + +// UART0 pins (logical UART1) - Maps to same location as Pro Micro +const ( + UART_RX_PIN = P0_08 + UART_TX_PIN = P0_06 +) + +// I2C pins +const ( + SDA_PIN = P0_15 // I2C0 external + SCL_PIN = P0_17 // I2C0 external +) + +// SPI pins +const ( + SPI0_SCK_PIN = P1_13 // SCK + SPI0_SDI_PIN = P0_03 // SDI + SPI0_SDO_PIN = P0_28 // SDO + +) + +// USB CDC identifiers +const ( + usb_STRING_PRODUCT = "bluemicro840" + usb_STRING_MANUFACTURER = "BlueMicro" +) + +var ( + usb_VID uint16 = 0x1d50 + usb_PID uint16 = 0x6161 +) diff --git a/emb/machine/board_bluepill.go b/emb/machine/board_bluepill.go new file mode 100644 index 00000000..83f527d5 --- /dev/null +++ b/emb/machine/board_bluepill.go @@ -0,0 +1,110 @@ +//go:build bluepill + +package machine + +import ( + "device/stm32" + "runtime/interrupt" +) + +// Pins printed on the silkscreen +const ( + C13 = PC13 + C14 = PC14 + C15 = PC15 + A0 = PA0 + A1 = PA1 + A2 = PA2 + A3 = PA3 + A4 = PA4 + A5 = PA5 + A6 = PA6 + A7 = PA7 + B0 = PB0 + B1 = PB1 + B10 = PB10 + B11 = PB11 + B12 = PB12 + B13 = PB13 + B14 = PB14 + B15 = PB15 + A8 = PA8 + A9 = PA9 + A10 = PA10 + A11 = PA11 + A12 = PA12 + A13 = PA13 + A14 = PA14 + A15 = PA15 + B3 = PB3 + B4 = PB4 + B5 = PB5 + B6 = PB6 + B7 = PB7 + B8 = PB8 + B9 = PB9 +) + +// Analog Pins +const ( + ADC0 = PA0 + ADC1 = PA1 + ADC2 = PA2 + ADC3 = PA3 + ADC4 = PA4 + ADC5 = PA5 + ADC6 = PA6 + ADC7 = PA7 + ADC8 = PB0 + ADC9 = PB1 +) + +const ( + // This board does not have a user button, so + // use first GPIO pin by default + BUTTON = PA0 + + LED = PC13 +) + +var DefaultUART = UART1 + +// UART pins +const ( + UART_TX_PIN = PA9 + UART_RX_PIN = PA10 + UART_ALT_TX_PIN = PB6 + UART_ALT_RX_PIN = PB7 +) + +var ( + // USART1 is the first hardware serial port on the STM32. + UART1 = &_UART1 + _UART1 = UART{ + Buffer: NewRingBuffer(), + Bus: stm32.USART1, + } + UART2 = &_UART2 + _UART2 = UART{ + Buffer: NewRingBuffer(), + Bus: stm32.USART2, + } +) + +func init() { + UART1.Interrupt = interrupt.New(stm32.IRQ_USART1, _UART1.handleInterrupt) + UART2.Interrupt = interrupt.New(stm32.IRQ_USART2, _UART2.handleInterrupt) +} + +// SPI pins +const ( + SPI0_SCK_PIN = PA5 + SPI0_SDO_PIN = PA7 + SPI0_SDI_PIN = PA6 +) + +// I2C pins +const ( + I2C0_SDA_PIN = PB7 + I2C0_SCL_PIN = PB6 +) diff --git a/emb/machine/board_btt_skr_pico.go b/emb/machine/board_btt_skr_pico.go new file mode 100644 index 00000000..6036ad96 --- /dev/null +++ b/emb/machine/board_btt_skr_pico.go @@ -0,0 +1,121 @@ +//go:build btt_skr_pico + +// This contains the pin mappings for the BigTreeTech SKR Pico. +// +// Purchase link: https://biqu.equipment/products/btt-skr-pico-v1-0 +// Board schematic: https://github.com/bigtreetech/SKR-Pico/blob/master/Hardware/BTT%20SKR%20Pico%20V1.0-SCH.pdf +// Pin diagram: https://github.com/bigtreetech/SKR-Pico/blob/master/Hardware/BTT%20SKR%20Pico%20V1.0-PIN.pdf + +package machine + +// TMC stepper driver motor direction. +// X/Y/Z/E refers to motors for X/Y/Z and the extruder. +const ( + X_DIR = GPIO10 + Y_DIR = GPIO5 + Z_DIR = GPIO28 + E_DIR = GPIO13 +) + +// TMC stepper driver motor step +const ( + X_STEP = GPIO11 + Y_STEP = GPIO6 + Z_STEP = GPIO19 + E_STEP = GPIO14 +) + +// TMC stepper driver enable +const ( + X_ENABLE = GPIO12 + Y_ENABLE = GPIO7 + Z_ENABLE = GPIO2 + E_ENABLE = GPIO15 +) + +// TMC stepper driver UART +const ( + TMC_UART_TX = UART1_TX_PIN + TMC_UART_RX = UART1_RX_PIN +) + +// Endstops +const ( + X_ENDSTOP = GPIO4 + Y_ENDSTOP = GPIO3 + Z_ENDSTOP = GPIO25 + E_ENDSTOP = GPIO16 +) + +// Fan PWM +const ( + FAN1_PWM = GPIO17 + FAN2_PWM = GPIO18 + FAN3_PWM = GPIO20 +) + +// Heater PWM +const ( + HEATER_BED_PWM = GPIO21 + HEATER_EXTRUDER_PWM = GPIO23 +) + +// Thermistors +const ( + THERM_BED = GPIO26 // Bed heater + THERM_EXTRUDER = GPIO27 // Toolhead heater +) + +// Misc +const ( + RGB = GPIO24 // Neopixel + SERVO = GPIO29 // Servo + PROBE = GPIO22 // Probe +) + +// Onboard crystal oscillator frequency, in MHz. +const ( + xoscFreq = 12 // MHz +) + +// I2C. We don't have this available +const ( + I2C0_SDA_PIN = NoPin + I2C0_SCL_PIN = NoPin + + I2C1_SDA_PIN = NoPin + I2C1_SCL_PIN = NoPin +) + +// SPI. We don't have this available +const ( + SPI0_SCK_PIN = NoPin + SPI0_SDO_PIN = NoPin + SPI0_SDI_PIN = NoPin + SPI1_SCK_PIN = NoPin + SPI1_SDO_PIN = NoPin + SPI1_SDI_PIN = NoPin +) + +// USB CDC identifiers +const ( + usb_STRING_PRODUCT = "SKR Pico" + usb_STRING_MANUFACTURER = "BigTreeTech" +) + +var ( + usb_VID uint16 = 0x2e8a + usb_PID uint16 = 0x0003 +) + +// UART pins +const ( + UART0_TX_PIN = GPIO0 + UART0_RX_PIN = GPIO1 + UART1_TX_PIN = GPIO8 + UART1_RX_PIN = GPIO9 + UART_TX_PIN = UART0_TX_PIN + UART_RX_PIN = UART0_RX_PIN +) + +var DefaultUART = UART0 diff --git a/emb/machine/board_challenger_rp2040.go b/emb/machine/board_challenger_rp2040.go new file mode 100644 index 00000000..9a85aa0a --- /dev/null +++ b/emb/machine/board_challenger_rp2040.go @@ -0,0 +1,93 @@ +//go:build challenger_rp2040 + +package machine + +const ( + LED = GPIO24 + + // Onboard crystal oscillator frequency, in MHz. + xoscFreq = 12 // MHz +) + +// GPIO Pins +const ( + D5 = GPIO2 + D6 = GPIO3 + D9 = GPIO4 + D10 = GPIO5 + D11 = GPIO6 + D12 = GPIO7 + D13 = GPIO8 +) + +// Analog pins +const ( + A0 = ADC0 + A1 = ADC1 + A2 = ADC2 + A3 = ADC3 +) + +// I2C Pins. +const ( + I2C0_SDA_PIN = GPIO24 + I2C0_SCL_PIN = GPIO25 + + I2C1_SDA_PIN = GPIO2 + I2C1_SCL_PIN = GPIO3 + + SDA_PIN = I2C1_SDA_PIN + SCL_PIN = I2C1_SCL_PIN +) + +// SPI default pins +const ( + // Default Serial Clock Bus 0 for SPI communications + SPI0_SCK_PIN = GPIO22 + // Default Serial Out Bus 0 for SPI communications + SPI0_SDO_PIN = GPIO23 // Tx + // Default Serial In Bus 0 for SPI communications + SPI0_SDI_PIN = GPIO20 // Rx + + // Default Serial Clock Bus 1 for SPI communications + SPI1_SCK_PIN = GPIO10 + // Default Serial Out Bus 1 for SPI communications + SPI1_SDO_PIN = GPIO11 // Tx + // Default Serial In Bus 1 for SPI communications + SPI1_SDI_PIN = GPIO12 // Rx +) + +// LoRa default pins +const ( + LORA_CS = GPIO9 + LORA_SCK = GPIO10 + LORA_SDO = GPIO11 + LORA_SDI = GPIO12 + LORA_RESET = GPIO13 + LORA_DIO0 = GPIO14 + LORA_DIO1 = GPIO15 + LORA_DIO2 = GPIO18 +) + +// UART pins +const ( + UART0_TX_PIN = GPIO16 + UART0_RX_PIN = GPIO17 + UART1_TX_PIN = GPIO4 + UART1_RX_PIN = GPIO5 + UART_TX_PIN = UART0_TX_PIN + UART_RX_PIN = UART0_RX_PIN +) + +var DefaultUART = UART0 + +// USB identifiers +const ( + usb_STRING_PRODUCT = "Challenger 2040 LoRa" + usb_STRING_MANUFACTURER = "iLabs" +) + +var ( + usb_VID uint16 = 0x2e8a + usb_PID uint16 = 0x1023 +) diff --git a/emb/machine/board_circuitplay_bluefruit.go b/emb/machine/board_circuitplay_bluefruit.go new file mode 100644 index 00000000..a133003c --- /dev/null +++ b/emb/machine/board_circuitplay_bluefruit.go @@ -0,0 +1,95 @@ +//go:build circuitplay_bluefruit + +package machine + +const HasLowFrequencyCrystal = false + +// GPIO Pins +const ( + D0 = P0_30 + D1 = P0_14 + D2 = P0_05 + D3 = P0_04 + D4 = P1_02 + D5 = P1_15 + D6 = P0_02 + D7 = P1_06 + D8 = P0_13 + D9 = P0_29 + D10 = P0_03 + D11 = P1_04 + D12 = P0_26 + D13 = P1_14 +) + +// Analog Pins +const ( + A1 = P0_02 + A2 = P0_29 + A3 = P0_03 + A4 = P0_04 + A5 = P0_05 + A6 = P0_30 + A7 = P0_14 + A8 = P0_28 + A9 = P0_31 +) + +const ( + LED = D13 + NEOPIXELS = D8 + WS2812 = D8 + + BUTTONA = D4 + BUTTONB = D5 + SLIDER = D7 // built-in slide switch + + BUTTON = BUTTONA + BUTTON1 = BUTTONB + + LIGHTSENSOR = A8 + TEMPSENSOR = A9 +) + +// UART0 pins (logical UART1) +const ( + UART_TX_PIN = P0_14 // PORTB + UART_RX_PIN = P0_30 // PORTB +) + +// I2C pins +const ( + SDA_PIN = P0_05 // I2C0 external + SCL_PIN = P0_04 // I2C0 external + + SDA1_PIN = P1_10 // I2C1 internal + SCL1_PIN = P1_12 // I2C1 internal +) + +// SPI pins (internal flash) +const ( + SPI0_SCK_PIN = P0_19 // SCK + SPI0_SDO_PIN = P0_21 // SDO + SPI0_SDI_PIN = P0_23 // SDI +) + +// PDM pins +const ( + PDM_CLK_PIN = P0_17 // CLK + PDM_DIN_PIN = P0_16 // DIN +) + +// USB CDC identifiers +const ( + usb_STRING_PRODUCT = "Adafruit Circuit Playground Bluefruit" + usb_STRING_MANUFACTURER = "Adafruit" +) + +var ( + usb_VID uint16 = 0x239A + usb_PID uint16 = 0x8045 +) + +var ( + DefaultUART = UART0 +) diff --git a/emb/machine/board_circuitplay_express.go b/emb/machine/board_circuitplay_express.go new file mode 100644 index 00000000..ce1f29c7 --- /dev/null +++ b/emb/machine/board_circuitplay_express.go @@ -0,0 +1,119 @@ +//go:build circuitplay_express + +package machine + +// used to reset into bootloader +const resetMagicValue = 0xf01669ef + +// GPIO Pins +const ( + D0 = PB09 + D1 = PB08 + D2 = PB02 + D3 = PB03 + D4 = PA28 + D5 = PA14 + D6 = PA05 + D7 = PA15 + D8 = PB23 + D9 = PA06 + D10 = PA07 + D11 = NoPin // does not seem to exist + D12 = PA02 + D13 = PA17 // PWM available +) + +// Analog Pins +const ( + A0 = PA02 // ADC/AIN[0] + A1 = PA05 // PWM available, also ADC/AIN[5] + A2 = PA06 // PWM available, also ADC/AIN[6] + A3 = PA07 // PWM available, also ADC/AIN[7] + A4 = PB03 // PORTB + A5 = PB02 // PORTB + A6 = PB09 // PORTB + A7 = PB08 // PORTB + A8 = PA11 // ADC/AIN[19] + A9 = PA09 // ADC/AIN[17] + A10 = PA04 +) + +const ( + LED = D13 + NEOPIXELS = D8 + WS2812 = D8 + + BUTTONA = D4 + BUTTONB = D5 + SLIDER = D7 // built-in slide switch + + BUTTON = BUTTONA + BUTTON1 = BUTTONB + + LIGHTSENSOR = A8 + TEMPSENSOR = A9 + PROXIMITY = A10 +) + +// USBCDC pins (logical UART0) +const ( + USBCDC_DM_PIN = PA24 + USBCDC_DP_PIN = PA25 +) + +// UART0 pins (logical UART1) +const ( + UART_TX_PIN = PB08 // PORTB + UART_RX_PIN = PB09 // PORTB +) + +// UART1 on the Circuit Playground Express. +var ( + UART1 = &sercomUSART4 + + DefaultUART = UART1 +) + +// I2C pins +const ( + SDA_PIN = PB02 // I2C0 external + SCL_PIN = PB03 // I2C0 external + + SDA1_PIN = PA00 // I2C1 internal + SCL1_PIN = PA01 // I2C1 internal +) + +// I2C on the Circuit Playground Express. +var ( + I2C0 = sercomI2CM5 // external device + I2C1 = sercomI2CM1 // internal device +) + +// SPI pins (internal flash) +const ( + SPI0_SCK_PIN = PA21 // SCK: SERCOM3/PAD[3] + SPI0_SDO_PIN = PA20 // SDO: SERCOM3/PAD[2] + SPI0_SDI_PIN = PA16 // SDI: SERCOM3/PAD[0] +) + +// SPI on the Circuit Playground Express. +var SPI0 = sercomSPIM3 + +// I2S pins +const ( + I2S_SCK_PIN = PA10 + I2S_SDO_PIN = PA08 + I2S_SDI_PIN = NoPin + I2S_WS_PIN = NoPin // no WS, instead uses SCK to sync +) + +// USB CDC identifiers +const ( + usb_STRING_PRODUCT = "Adafruit Circuit Playground Express" + usb_STRING_MANUFACTURER = "Adafruit" +) + +var ( + usb_VID uint16 = 0x239A + usb_PID uint16 = 0x8018 +) diff --git a/emb/machine/board_clue_alpha.go b/emb/machine/board_clue_alpha.go new file mode 100644 index 00000000..8d16f4ef --- /dev/null +++ b/emb/machine/board_clue_alpha.go @@ -0,0 +1,125 @@ +//go:build clue_alpha + +package machine + +const HasLowFrequencyCrystal = false + +// GPIO Pins +const ( + D0 = P0_04 + D1 = P0_05 + D2 = P0_03 + D3 = P0_28 + D4 = P0_02 + D5 = P1_02 + D6 = P1_09 + D7 = P0_07 + D8 = P1_07 + D9 = P0_27 + D10 = P0_30 + D11 = P1_10 + D12 = P0_31 + D13 = P0_08 + D14 = P0_06 + D15 = P0_26 + D16 = P0_29 + D17 = P1_01 + D18 = P0_16 + D19 = P0_25 + D20 = P0_24 + D29 = P0_14 + D30 = P0_15 + D31 = P0_12 + D32 = P0_13 + D33 = P1_03 + D34 = P1_05 + D35 = P0_00 + D36 = P0_01 + D37 = P0_19 + D38 = P0_20 + D39 = P0_17 + D40 = P0_22 + D41 = P0_23 + D42 = P0_21 + D43 = P0_10 + D44 = P0_09 + D45 = P1_06 + D46 = P1_00 +) + +// Analog Pins +const ( + A0 = D12 + A1 = D16 + A2 = D0 + A3 = D1 + A4 = D2 + A5 = D3 + A6 = D4 + A7 = D10 +) + +const ( + LED = D17 + LED1 = LED + LED2 = D43 + NEOPIXEL = D18 + WS2812 = D18 + + BUTTON_LEFT = D5 + BUTTON_RIGHT = D11 + + // 240x240 ST7789 display is connected to these pins (use RowOffset = 80) + TFT_SCK = D29 + TFT_SDO = D30 + TFT_CS = D31 + TFT_DC = D32 + TFT_RESET = D33 + TFT_LITE = D34 + + PDM_DAT = D35 + PDM_CLK = D36 + + QSPI_SCK = D37 + QSPI_CS = D38 + QSPI_DATA0 = D39 + QSPI_DATA1 = D40 + QSPI_DATA2 = D41 + QSPI_DATA3 = D42 + + SPEAKER = D46 +) + +// UART0 pins (logical UART1) +const ( + UART_RX_PIN = D0 + UART_TX_PIN = D1 +) + +// I2C pins +const ( + SDA_PIN = D20 // I2C0 external + SCL_PIN = D19 // I2C0 external +) + +// SPI pins +const ( + SPI0_SCK_PIN = D13 // SCK + SPI0_SDO_PIN = D15 // SDO + SPI0_SDI_PIN = D14 // SDI +) + +// USB CDC identifiers +const ( + usb_STRING_PRODUCT = "Adafruit CLUE" + usb_STRING_MANUFACTURER = "Adafruit" +) + +var ( + usb_VID uint16 = 0x239A + usb_PID uint16 = 0x8072 +) + +var ( + DefaultUART = UART0 +) diff --git a/emb/machine/board_digispark.go b/emb/machine/board_digispark.go new file mode 100644 index 00000000..f380aae8 --- /dev/null +++ b/emb/machine/board_digispark.go @@ -0,0 +1,19 @@ +//go:build digispark + +package machine + +// Return the current CPU frequency in hertz. +func CPUFrequency() uint32 { + return 16000000 +} + +const ( + P0 Pin = PB0 + P1 Pin = PB1 + P2 Pin = PB2 + P3 Pin = PB3 + P4 Pin = PB4 + P5 Pin = PB5 + + LED = P1 +) diff --git a/emb/machine/board_elecrow-rp2040-w5.go b/emb/machine/board_elecrow-rp2040-w5.go new file mode 100644 index 00000000..1ea0181d --- /dev/null +++ b/emb/machine/board_elecrow-rp2040-w5.go @@ -0,0 +1,94 @@ +//go:build elecrow_rp2040 + +// This file contains the pin mappings for the Elecrow Pico rp2040 W5 boards. +// +// Elecrow Pico rp2040 W5 is a microcontroller using the Raspberry Pi RP2040 +// chip and rtl8720d Wifi chip. +// +// - https://www.elecrow.com/wiki/PICO_W5_RP2040_Dev_Board.html +package machine + +// GPIO pins +const ( + GP0 Pin = GPIO0 + GP1 Pin = GPIO1 + GP2 Pin = GPIO2 + GP3 Pin = GPIO3 + GP4 Pin = GPIO4 + GP5 Pin = GPIO5 + GP6 Pin = GPIO6 + GP7 Pin = GPIO7 + GP8 Pin = GPIO8 + GP9 Pin = GPIO9 + GP10 Pin = GPIO10 + GP11 Pin = GPIO11 + GP12 Pin = GPIO12 + GP13 Pin = GPIO13 + GP14 Pin = GPIO14 + GP15 Pin = GPIO15 + GP16 Pin = GPIO16 + GP17 Pin = GPIO17 + GP18 Pin = GPIO18 + GP19 Pin = GPIO19 + GP20 Pin = GPIO20 + GP21 Pin = GPIO21 + GP22 Pin = GPIO22 + GP26 Pin = GPIO26 + GP27 Pin = GPIO27 + GP28 Pin = GPIO28 + + // Onboard LED + LED Pin = GPIO25 + + // Onboard crystal oscillator frequency, in MHz. + xoscFreq = 12 // MHz +) + +// I2C Default pins on Raspberry Pico. +const ( + I2C0_SDA_PIN = GP4 + I2C0_SCL_PIN = GP5 + + I2C1_SDA_PIN = GP2 + I2C1_SCL_PIN = GP3 +) + +// SPI default pins +const ( + // Default Serial Clock Bus 0 for SPI communications + SPI0_SCK_PIN = GPIO18 + // Default Serial Out Bus 0 for SPI communications + SPI0_SDO_PIN = GPIO19 // Tx + // Default Serial In Bus 0 for SPI communications + SPI0_SDI_PIN = GPIO16 // Rx + + // Default Serial Clock Bus 1 for SPI communications + SPI1_SCK_PIN = GPIO10 + // Default Serial Out Bus 1 for SPI communications + SPI1_SDO_PIN = GPIO11 // Tx + // Default Serial In Bus 1 for SPI communications + SPI1_SDI_PIN = GPIO12 // Rx +) + +// UART pins +const ( + UART0_TX_PIN = GPIO0 + UART0_RX_PIN = GPIO1 + UART1_TX_PIN = GPIO4 // Wired to rtl8720d UART1_Tx + UART1_RX_PIN = GPIO5 // Wired to rtl8720n UART1_Rx + UART_TX_PIN = UART0_TX_PIN + UART_RX_PIN = UART0_RX_PIN +) + +var DefaultUART = UART0 + +// USB identifiers +const ( + usb_STRING_PRODUCT = "Pico" + usb_STRING_MANUFACTURER = "Raspberry Pi" +) + +var ( + usb_VID uint16 = 0x2E8A + usb_PID uint16 = 0x000A +) diff --git a/emb/machine/board_elecrow-rp2350-w5.go b/emb/machine/board_elecrow-rp2350-w5.go new file mode 100644 index 00000000..80a84364 --- /dev/null +++ b/emb/machine/board_elecrow-rp2350-w5.go @@ -0,0 +1,94 @@ +//go:build elecrow_rp2350 + +// This file contains the pin mappings for the Elecrow Pico rp2350 W5 boards. +// +// Elecrow Pico rp2350 W5 is a microcontroller using the Raspberry Pi RP2350 +// chip and rtl8720d Wifi chip. +// +// - https://www.elecrow.com/pico-w5-microcontroller-development-boards-rp2350-microcontroller-board.html +package machine + +// GPIO pins +const ( + GP0 Pin = GPIO0 + GP1 Pin = GPIO1 + GP2 Pin = GPIO2 + GP3 Pin = GPIO3 + GP4 Pin = GPIO4 + GP5 Pin = GPIO5 + GP6 Pin = GPIO6 + GP7 Pin = GPIO7 + GP8 Pin = GPIO8 + GP9 Pin = GPIO9 + GP10 Pin = GPIO10 + GP11 Pin = GPIO11 + GP12 Pin = GPIO12 + GP13 Pin = GPIO13 + GP14 Pin = GPIO14 + GP15 Pin = GPIO15 + GP16 Pin = GPIO16 + GP17 Pin = GPIO17 + GP18 Pin = GPIO18 + GP19 Pin = GPIO19 + GP20 Pin = GPIO20 + GP21 Pin = GPIO21 + GP22 Pin = GPIO22 + GP26 Pin = GPIO26 + GP27 Pin = GPIO27 + GP28 Pin = GPIO28 + + // Onboard LED + LED Pin = GPIO25 + + // Onboard crystal oscillator frequency, in MHz. + xoscFreq = 12 // MHz +) + +// I2C Default pins on Raspberry Pico. +const ( + I2C0_SDA_PIN = GP4 + I2C0_SCL_PIN = GP5 + + I2C1_SDA_PIN = GP2 + I2C1_SCL_PIN = GP3 +) + +// SPI default pins +const ( + // Default Serial Clock Bus 0 for SPI communications + SPI0_SCK_PIN = GPIO18 + // Default Serial Out Bus 0 for SPI communications + SPI0_SDO_PIN = GPIO19 // Tx + // Default Serial In Bus 0 for SPI communications + SPI0_SDI_PIN = GPIO16 // Rx + + // Default Serial Clock Bus 1 for SPI communications + SPI1_SCK_PIN = GPIO10 + // Default Serial Out Bus 1 for SPI communications + SPI1_SDO_PIN = GPIO11 // Tx + // Default Serial In Bus 1 for SPI communications + SPI1_SDI_PIN = GPIO12 // Rx +) + +// UART pins +const ( + UART0_TX_PIN = GPIO0 + UART0_RX_PIN = GPIO1 + UART1_TX_PIN = GPIO4 // Wired to rtl8720d UART1_Tx + UART1_RX_PIN = GPIO5 // Wired to rtl8720n UART1_Rx + UART_TX_PIN = UART0_TX_PIN + UART_RX_PIN = UART0_RX_PIN +) + +var DefaultUART = UART0 + +// USB identifiers +const ( + usb_STRING_PRODUCT = "Pico2" + usb_STRING_MANUFACTURER = "Raspberry Pi" +) + +var ( + usb_VID uint16 = 0x2E8A + usb_PID uint16 = 0x000F +) diff --git a/emb/machine/board_esp-c3-32s-kit.go b/emb/machine/board_esp-c3-32s-kit.go new file mode 100644 index 00000000..09385aa3 --- /dev/null +++ b/emb/machine/board_esp-c3-32s-kit.go @@ -0,0 +1,40 @@ +//go:build esp_c3_32s_kit + +package machine + +// See: +// * https://www.waveshare.com/w/upload/8/8f/Esp32-c3s_specification.pdf +// * https://www.waveshare.com/w/upload/4/46/Nodemcu-esp-c3-32s-kit-schematics.pdf + +// Digital Pins +const ( + IO0 = GPIO0 + IO1 = GPIO1 + IO2 = GPIO2 + IO3 = GPIO3 + IO4 = GPIO4 + IO5 = GPIO5 + IO6 = GPIO6 + IO7 = GPIO7 + IO8 = GPIO8 + IO9 = GPIO9 + IO18 = GPIO18 + IO19 = GPIO19 +) + +const ( + LED_RED = IO3 + LED_GREEN = IO4 + LED_BLUE = IO5 + + LED = LED_RED + + LED1 = LED_RED + LED2 = LED_GREEN +) + +// I2C pins +const ( + SDA_PIN = NoPin + SCL_PIN = NoPin +) diff --git a/emb/machine/board_esp32-c3-devkit-rust-1.go b/emb/machine/board_esp32-c3-devkit-rust-1.go new file mode 100644 index 00000000..8e47269f --- /dev/null +++ b/emb/machine/board_esp32-c3-devkit-rust-1.go @@ -0,0 +1,81 @@ +//go:build esp32_c3_devkit_rust_1 + +// This file contains the pin mappings for the Espressif ESP32-C3 Development Board for Rust. +// +// The Espressif ESP32-C3-DevKit-RUST-1 development board is powered +// by the Espressif ESP32-C3 SoC featuring an open-source RISC-V architecture. +// +// Specifications: +// SoC: ESP32-C3-MINI-1, 4MB Flash, RISCV-32bit, 160MHz, 400KB SRAM +// Wireless: WiFi & Bluetooth 5.0 (BLE) +// ICM-42670-P 6-Axis IMU (I2C Addr 0x68) +// SHTC3 Humidity and Temperature Sensor (I2C Addr 0x70) +// WS2812B LED + +// GitHub: https://github.com/esp-rs/esp-rust-board +// Schematic: https://github.com/esp-rs/esp-rust-board/blob/master/hardware/esp-rust-board/schematic/esp-rust-board.pdf +// Datasheet: https://www.espressif.com/sites/default/files/documentation/esp32-c3_datasheet_en.pdf + +package machine + +// Digital pins +const ( + // Pin // Function + // ----- // --------------- + D0 = GPIO0 // + D1 = GPIO1 // + D2 = GPIO2 // WS2812 + D3 = GPIO3 // + D4 = GPIO4 // MTMS + D5 = GPIO5 // MTDI + D6 = GPIO6 // MTCK + D7 = GPIO7 // Red LED / MTDO + D8 = GPIO8 // I2C SCL + D9 = GPIO9 // Boot Button + D10 = GPIO10 // I2C SDA + D18 = GPIO18 // USB DM + D19 = GPIO19 // USB DP + D20 = GPIO20 // UART RX + D21 = GPIO21 // UART TX +) + +// Analog pins +const ( + A0 = GPIO0 + A1 = GPIO1 + A2 = GPIO2 + A3 = GPIO3 + A4 = GPIO4 + A5 = GPIO5 +) + +// Button pin +const ( + BUTTON = BUTTON_BOOT + BUTTON_BOOT = D9 +) + +// LED pins +const ( + LED = LED_BUILTIN + WS2812 = D2 + LED_BUILTIN = D7 +) + +// I2C pins +const ( + SCL_PIN = D8 + SDA_PIN = D10 +) + +// USBCDC pins +const ( + USBCDC_DM_PIN = D18 + USBCDC_DP_PIN = D19 +) + +// UART pins +const ( + UART_RX_PIN = D20 + UART_TX_PIN = D21 +) diff --git a/emb/machine/board_esp32-coreboard-v2.go b/emb/machine/board_esp32-coreboard-v2.go new file mode 100644 index 00000000..044d9060 --- /dev/null +++ b/emb/machine/board_esp32-coreboard-v2.go @@ -0,0 +1,88 @@ +//go:build esp32_coreboard_v2 + +package machine + +const ( + CLK = GPIO6 + CMD = GPIO11 + IO0 = GPIO0 + IO1 = GPIO1 + IO2 = GPIO2 + IO3 = GPIO3 + IO4 = GPIO4 + IO5 = GPIO5 + IO9 = GPIO9 + IO10 = GPIO10 + IO16 = GPIO16 + IO17 = GPIO17 + IO18 = GPIO18 + IO19 = GPIO19 + IO21 = GPIO21 + IO22 = GPIO22 + IO23 = GPIO23 + IO25 = GPIO25 + IO26 = GPIO26 + IO27 = GPIO27 + IO32 = GPIO32 + IO33 = GPIO33 + IO34 = GPIO34 + IO35 = GPIO35 + IO36 = GPIO36 + IO39 = GPIO39 + RXD = GPIO3 + SD0 = GPIO7 + SD1 = GPIO8 + SD2 = GPIO9 + SD3 = GPIO10 + SVN = GPIO39 + SVP = GPIO36 + TCK = GPIO13 + TD0 = GPIO15 + TDI = GPIO12 + TMS = GPIO14 + TXD = GPIO1 +) + +// Built-in LED on some ESP32 boards. +const LED = IO2 + +// SPI pins +const ( + SPI0_SCK_PIN = IO18 + SPI0_SDO_PIN = IO23 + SPI0_SDI_PIN = IO19 + SPI0_CS0_PIN = IO5 +) + +// I2C pins +const ( + SDA_PIN = IO21 + SCL_PIN = IO22 +) + +// ADC pins +const ( + ADC0 Pin = IO34 + ADC1 Pin = IO35 + ADC2 Pin = IO36 + ADC3 Pin = IO39 +) + +// UART0 pins +const ( + UART_TX_PIN = IO1 + UART_RX_PIN = IO3 +) + +// UART1 pins +const ( + UART1_TX_PIN = IO9 + UART1_RX_PIN = IO10 +) + +// PWM pins +const ( + PWM0_PIN Pin = IO2 + PWM1_PIN Pin = IO0 + PWM2_PIN Pin = IO4 +) diff --git a/emb/machine/board_esp32c3-12f.go b/emb/machine/board_esp32c3-12f.go new file mode 100644 index 00000000..f023bb9d --- /dev/null +++ b/emb/machine/board_esp32c3-12f.go @@ -0,0 +1,54 @@ +//go:build esp32c312f + +package machine + +// Built-in RGB LED +const ( + LED_RED = IO3 + LED_GREEN = IO4 + LED_BLUE = IO5 + LED = LED_RED +) + +const ( + IO0 = GPIO0 + IO1 = GPIO1 + IO2 = GPIO2 + IO3 = GPIO3 + IO4 = GPIO4 + IO5 = GPIO5 + IO6 = GPIO6 + IO7 = GPIO7 + IO8 = GPIO8 + IO9 = GPIO9 + IO10 = GPIO10 + IO18 = GPIO18 + IO19 = GPIO19 + RXD = GPIO20 + TXD = GPIO21 +) + +// ADC pins +const ( + ADC0 Pin = ADC1_0 + ADC1 Pin = ADC2_0 + + ADC1_0 Pin = IO0 + ADC1_1 Pin = IO1 + ADC1_2 Pin = IO2 + ADC1_3 Pin = IO3 + ADC1_4 Pin = IO4 + ADC2_0 Pin = IO5 +) + +// UART0 pins +const ( + UART_TX_PIN = TXD + UART_RX_PIN = RXD +) + +// I2C pins +const ( + SCL_PIN = NoPin + SDA_PIN = NoPin +) diff --git a/emb/machine/board_esp32c3-supermini.go b/emb/machine/board_esp32c3-supermini.go new file mode 100644 index 00000000..c180ff0e --- /dev/null +++ b/emb/machine/board_esp32c3-supermini.go @@ -0,0 +1,57 @@ +//go:build esp32c3_supermini + +// This file contains the pin mappings for the ESP32 supermini boards. +// +// - https://web.archive.org/web/20240805232453/https://dl.artronshop.co.th/ESP32-C3%20SuperMini%20datasheet.pdf + +package machine + +// Digital Pins +const ( + IO0 = GPIO0 + IO1 = GPIO1 + IO2 = GPIO2 + IO3 = GPIO3 + IO4 = GPIO4 + IO5 = GPIO5 + IO6 = GPIO6 + IO7 = GPIO7 + IO8 = GPIO8 + IO9 = GPIO9 + IO10 = GPIO10 + IO20 = GPIO20 + IO21 = GPIO21 +) + +// Built-in LED +const LED = GPIO8 + +// Analog pins +const ( + A0 = GPIO0 + A1 = GPIO1 + A2 = GPIO2 + A3 = GPIO3 + A4 = GPIO4 + A5 = GPIO5 +) + +// UART pins +const ( + UART_RX_PIN = GPIO20 + UART_TX_PIN = GPIO21 +) + +// I2C pins +const ( + SDA_PIN = GPIO8 + SCL_PIN = GPIO9 +) + +// SPI pins +const ( + SPI_MISO_PIN = GPIO5 + SPI_MOSI_PIN = GPIO6 + SPI_SS_PIN = GPIO7 + SPI_SCK_PIN = GPIO4 +) diff --git a/emb/machine/board_fe310.go b/emb/machine/board_fe310.go new file mode 100644 index 00000000..88810155 --- /dev/null +++ b/emb/machine/board_fe310.go @@ -0,0 +1,38 @@ +//go:build hifive1b + +package machine + +const ( + P00 Pin = 0 + P01 Pin = 1 + P02 Pin = 2 + P03 Pin = 3 + P04 Pin = 4 + P05 Pin = 5 + P06 Pin = 6 + P07 Pin = 7 + P08 Pin = 8 + P09 Pin = 9 + P10 Pin = 10 + P11 Pin = 11 + P12 Pin = 12 + P13 Pin = 13 + P14 Pin = 14 + P15 Pin = 15 + P16 Pin = 16 + P17 Pin = 17 + P18 Pin = 18 + P19 Pin = 19 + P20 Pin = 20 + P21 Pin = 21 + P22 Pin = 22 + P23 Pin = 23 + P24 Pin = 24 + P25 Pin = 25 + P26 Pin = 26 + P27 Pin = 27 + P28 Pin = 28 + P29 Pin = 29 + P30 Pin = 30 + P31 Pin = 31 +) diff --git a/emb/machine/board_feather-m0-express.go b/emb/machine/board_feather-m0-express.go new file mode 100644 index 00000000..226369ff --- /dev/null +++ b/emb/machine/board_feather-m0-express.go @@ -0,0 +1,102 @@ +//go:build sam && atsamd21 && feather_m0_express + +package machine + +// used to reset into bootloader +const resetMagicValue = 0xf01669ef + +// GPIO Pins +const ( + D0 = PA11 // UART0 RX + D1 = PA10 // UART0 TX + D2 = NoPin // does not seem to exist + D3 = NoPin // does not seem to exist + D4 = NoPin // does not seem to exist + D5 = PA15 + D6 = PA20 + D7 = NoPin // does not seem to exist + D8 = PA06 // NEOPIXEL + D9 = PA07 + D10 = PA18 + D11 = PA16 + D12 = PA19 + D13 = PA17 +) + +// Analog pins +const ( + A0 = PA02 // ADC/AIN[0] + A1 = PB08 // ADC/AIN[2] + A2 = PB09 // ADC/AIN[3] + A3 = PA04 // ADC/AIN[4] + A4 = PA05 // ADC/AIN[5] + A5 = PB02 // ADC/AIN[10] +) + +const ( + LED = D13 + NEOPIXEL = D8 +) + +// USBCDC pins +const ( + USBCDC_DM_PIN = PA24 + USBCDC_DP_PIN = PA25 +) + +// UART1 pins +const ( + UART_TX_PIN = D1 + UART_RX_PIN = D0 + + UART1_TX_PIN = D10 + UART1_RX_PIN = D12 +) + +// UART0 on the Feather M0 Express. +var UART0 = &sercomUSART0 +var UART1 = &sercomUSART1 + +// I2C pins +const ( + SDA_PIN = PA22 // SDA: SERCOM3/PAD[0] + SCL_PIN = PA23 // SCL: SERCOM3/PAD[1] +) + +// I2C on the Feather M0 Express. +var ( + I2C0 = sercomI2CM3 +) + +// SPI pins +const ( + SPI0_SCK_PIN = PB11 // SCK: SERCOM4/PAD[3] + SPI0_SDO_PIN = PB10 // SDO: SERCOM4/PAD[2] + SPI0_SDI_PIN = PA12 // SDI: SERCOM4/PAD[0] +) + +// SPI on the Feather M0. +var SPI0 = sercomSPIM4 + +// I2S pins +const ( + I2S_SCK_PIN = PA10 + I2S_SDO_PIN = PA07 + I2S_SDI_PIN = NoPin + I2S_WS_PIN = NoPin // TODO: figure out what this is on Feather M0 Express. +) + +// USB CDC identifiers +const ( + usb_STRING_PRODUCT = "Adafruit Feather M0 Express" + usb_STRING_MANUFACTURER = "Adafruit" +) + +var ( + usb_VID uint16 = 0x239A + usb_PID uint16 = 0x801B +) + +var ( + DefaultUART = UART0 +) diff --git a/emb/machine/board_feather-m0.go b/emb/machine/board_feather-m0.go new file mode 100644 index 00000000..f38d8ec8 --- /dev/null +++ b/emb/machine/board_feather-m0.go @@ -0,0 +1,97 @@ +//go:build sam && atsamd21 && feather_m0 + +package machine + +// used to reset into bootloader +const resetMagicValue = 0xf01669ef + +// GPIO Pins +const ( + D0 = PA11 // UART0 RX + D1 = PA10 // UART0 TX + D2 = NoPin // does not seem to exist + D3 = PA09 + D4 = PA08 + D5 = PA15 // PWM available + D6 = PA20 // PWM available + D7 = NoPin // does not seem to exist + D8 = PA06 + D9 = PA07 // PWM available + D10 = PA18 // can be used for PWM or UART1 TX + D11 = PA16 // can be used for PWM or UART1 RX + D12 = PA19 // PWM available + D13 = PA17 // PWM available +) + +// Analog pins +const ( + A0 = PA02 // ADC/AIN[0] + A1 = PB08 // ADC/AIN[2] + A2 = PB09 // ADC/AIN[3] + A3 = PA04 // ADC/AIN[4] + A4 = PA05 // ADC/AIN[5] + A5 = PB02 // ADC/AIN[10] +) + +const ( + LED = D13 +) + +// USBCDC pins +const ( + USBCDC_DM_PIN = PA24 + USBCDC_DP_PIN = PA25 +) + +// UART1 pins +const ( + UART_TX_PIN = D10 + UART_RX_PIN = D11 +) + +// UART1 on the Feather M0. +var UART1 = &sercomUSART1 + +// I2C pins +const ( + SDA_PIN = PA22 // SDA: SERCOM3/PAD[0] + SCL_PIN = PA23 // SCL: SERCOM3/PAD[1] +) + +// I2C on the Feather M0. +var ( + I2C0 = sercomI2CM3 +) + +// SPI pins +const ( + SPI0_SCK_PIN = PB11 // SCK: SERCOM4/PAD[3] + SPI0_SDO_PIN = PB10 // SDO: SERCOM4/PAD[2] + SPI0_SDI_PIN = PA12 // SDI: SERCOM4/PAD[0] +) + +// SPI on the Feather M0. +var SPI0 = sercomSPIM4 + +// I2S pins +const ( + I2S_SCK_PIN = PA10 + I2S_SDO_PIN = PA08 + I2S_SDI_PIN = NoPin + I2S_WS_PIN = NoPin // TODO: figure out what this is on Feather M0. +) + +// USB CDC identifiers +const ( + usb_STRING_PRODUCT = "Adafruit Feather M0 Express" + usb_STRING_MANUFACTURER = "Adafruit" +) + +var ( + usb_VID uint16 = 0x239A + usb_PID uint16 = 0x801B +) + +var ( + DefaultUART = UART1 +) diff --git a/emb/machine/board_feather-m4-can.go b/emb/machine/board_feather-m4-can.go new file mode 100644 index 00000000..6a395065 --- /dev/null +++ b/emb/machine/board_feather-m4-can.go @@ -0,0 +1,138 @@ +//go:build feather_m4_can + +package machine + +import ( + "device/sam" +) + +// used to reset into bootloader +const resetMagicValue = 0xf01669ef + +// GPIO Pins +const ( + D0 = PB17 // UART0 RX/PWM available + D1 = PB16 // UART0 TX/PWM available + D4 = PA14 // PWM available + D5 = PA16 // PWM available + D6 = PA18 // PWM available + D7 = PB03 // neopixel power + D8 = PB02 // built-in neopixel + D9 = PA19 // PWM available + D10 = PA20 // can be used for PWM or UART1 TX + D11 = PA21 // can be used for PWM or UART1 RX + D12 = PA22 // PWM available + D13 = PA23 // PWM available + D21 = PA13 // PWM available + D22 = PA12 // PWM available + D23 = PB22 // PWM available + D24 = PB23 // PWM available + D25 = PA17 // PWM available +) + +// Analog pins +const ( + A0 = PA02 // ADC/AIN[0] + A1 = PA05 // ADC/AIN[2] + A2 = PB08 // ADC/AIN[3] + A3 = PB09 // ADC/AIN[4] + A4 = PA04 // ADC/AIN[5] + A5 = PA06 // ADC/AIN[10] +) + +const ( + LED = D13 + NEOPIXELS = D8 + WS2812 = D8 +) + +// USBCDC pins +const ( + USBCDC_DM_PIN = PA24 + USBCDC_DP_PIN = PA25 +) + +const ( + UART_TX_PIN = D1 + UART_RX_PIN = D0 +) + +const ( + UART2_TX_PIN = A4 + UART2_RX_PIN = A5 +) + +// I2C pins +const ( + SDA_PIN = D22 // SDA: SERCOM2/PAD[0] + SCL_PIN = D21 // SCL: SERCOM2/PAD[1] +) + +// SPI pins +const ( + SPI0_SCK_PIN = D25 // SCK: SERCOM1/PAD[1] + SPI0_SDO_PIN = D24 // SDO: SERCOM1/PAD[3] + SPI0_SDI_PIN = D23 // SDI: SERCOM1/PAD[2] +) + +// CAN pins +const ( + CAN0_TX = PA22 + CAN0_RX = PA23 + + CAN1_STANDBY = PB12 + CAN1_TX = PB14 + CAN1_RX = PB15 + BOOST_EN = PB13 // power control of CAN1's TCAN1051HGV (H: enable) + + CAN_STANDBY = CAN1_STANDBY + CAN_S = CAN1_STANDBY + CAN_TX = CAN1_TX + CAN_RX = CAN1_RX +) + +// USB CDC identifiers +const ( + usb_STRING_PRODUCT = "Adafruit Feather M4 CAN" + usb_STRING_MANUFACTURER = "Adafruit" +) + +var ( + usb_VID uint16 = 0x239A + usb_PID uint16 = 0x80CD +) + +var ( + UART1 = &sercomUSART5 + + UART2 = &sercomUSART0 +) + +func init() { + // turn on neopixel + D7.Configure(PinConfig{Mode: PinOutput}) + D7.High() +} + +// I2C on the Feather M4 CAN. +var ( + I2C0 = sercomI2CM2 +) + +// SPI on the Feather M4 CAN. +var SPI0 = sercomSPIM1 + +// CAN on the Feather M4 CAN. +var ( + CAN0 = CAN{ + Bus: sam.CAN0, + } + + CAN1 = CAN{ + Bus: sam.CAN1, + } +) + +var ( + DefaultUART = UART1 +) diff --git a/emb/machine/board_feather-m4.go b/emb/machine/board_feather-m4.go new file mode 100644 index 00000000..fb88fb93 --- /dev/null +++ b/emb/machine/board_feather-m4.go @@ -0,0 +1,96 @@ +//go:build feather_m4 + +package machine + +// used to reset into bootloader +const resetMagicValue = 0xf01669ef + +// GPIO Pins +const ( + D0 = PB17 // UART0 RX/PWM available + D1 = PB16 // UART0 TX/PWM available + D4 = PA14 // PWM available + D5 = PA16 // PWM available + D6 = PA18 // PWM available + D8 = PB03 // built-in neopixel + D9 = PA19 // PWM available + D10 = PA20 // can be used for PWM or UART1 TX + D11 = PA21 // can be used for PWM or UART1 RX + D12 = PA22 // PWM available + D13 = PA23 // PWM available + D21 = PA13 // PWM available + D22 = PA12 // PWM available + D23 = PB22 // PWM available + D24 = PB23 // PWM available + D25 = PA17 // PWM available +) + +// Analog pins +const ( + A0 = PA02 // ADC/AIN[0] + A1 = PA05 // ADC/AIN[2] + A2 = PB08 // ADC/AIN[3] + A3 = PB09 // ADC/AIN[4] + A4 = PA04 // ADC/AIN[5] + A5 = PA06 // ADC/AIN[10] +) + +const ( + LED = D13 + WS2812 = D8 +) + +// USBCDC pins +const ( + USBCDC_DM_PIN = PA24 + USBCDC_DP_PIN = PA25 +) + +const ( + UART_TX_PIN = D1 + UART_RX_PIN = D0 +) + +const ( + UART2_TX_PIN = A4 + UART2_RX_PIN = A5 +) + +var ( + UART1 = &sercomUSART5 + UART2 = &sercomUSART0 + + DefaultUART = UART1 +) + +// I2C pins +const ( + SDA_PIN = D22 // SDA: SERCOM2/PAD[0] + SCL_PIN = D21 // SCL: SERCOM2/PAD[1] +) + +// I2C on the Feather M4. +var ( + I2C0 = sercomI2CM2 +) + +// SPI pins +const ( + SPI0_SCK_PIN = D25 // SCK: SERCOM1/PAD[1] + SPI0_SDO_PIN = D24 // SDO: SERCOM1/PAD[3] + SPI0_SDI_PIN = D23 // SDI: SERCOM1/PAD[2] +) + +// SPI on the Feather M4. +var SPI0 = sercomSPIM1 + +// USB CDC identifiers +const ( + usb_STRING_PRODUCT = "Adafruit Feather M4" + usb_STRING_MANUFACTURER = "Adafruit" +) + +var ( + usb_VID uint16 = 0x239A + usb_PID uint16 = 0x8022 +) diff --git a/emb/machine/board_feather-nrf52840-sense.go b/emb/machine/board_feather-nrf52840-sense.go new file mode 100644 index 00000000..ab1c3fbe --- /dev/null +++ b/emb/machine/board_feather-nrf52840-sense.go @@ -0,0 +1,105 @@ +//go:build feather_nrf52840_sense + +package machine + +const HasLowFrequencyCrystal = false + +// GPIO Pins +const ( + D0 = P0_25 // UART TX + D1 = P0_24 // UART RX + D2 = P0_10 // NFC2 + D3 = P1_11 + D4 = P1_10 // LED2 + D5 = P1_08 + D6 = P0_07 + D7 = P1_02 // Button + D8 = P0_16 // NeoPixel + D9 = P0_26 + D10 = P0_27 + D11 = P0_06 + D12 = P0_08 + D13 = P1_09 // LED1 + D14 = P0_04 // A0 + D15 = P0_05 // A1 + D16 = P0_30 // A2 + D17 = P0_28 // A3 + D18 = P0_02 // A4 + D19 = P0_03 // A5 + D20 = P0_29 // Battery + D21 = P0_31 // AREF + D22 = P0_12 // I2C SDA + D23 = P0_11 // I2C SCL + D24 = P0_15 // SPI MISO + D25 = P0_13 // SPI MOSI + D26 = P0_14 // SPI SCK + D27 = P0_19 // QSPI CLK + D28 = P0_20 // QSPI CS + D29 = P0_17 // QSPI Data 0 + D30 = P0_22 // QSPI Data 1 + D31 = P0_23 // QSPI Data 2 + D32 = P0_21 // QSPI Data 3 + D33 = P0_09 // NFC1 (test point on bottom of board) +) + +// Analog Pins +const ( + A0 = D14 + A1 = D15 + A2 = D16 + A3 = D17 + A4 = D18 + A5 = D19 + A6 = D20 // Battery + A7 = D21 // ARef +) + +const ( + LED = D13 + LED1 = LED + LED2 = D4 + NEOPIXEL = D8 + WS2812 = D8 + BUTTON = D7 + + QSPI_SCK = D27 + QSPI_CS = D28 + QSPI_DATA0 = D29 + QSPI_DATA1 = D30 + QSPI_DATA2 = D31 + QSPI_DATA3 = D32 +) + +// UART0 pins (logical UART1) +const ( + UART_RX_PIN = D1 + UART_TX_PIN = D0 +) + +// I2C pins +const ( + SDA_PIN = D22 // I2C0 external + SCL_PIN = D23 // I2C0 external +) + +// SPI pins +const ( + SPI0_SCK_PIN = D26 // SCK + SPI0_SDO_PIN = D25 // SDO + SPI0_SDI_PIN = D24 // SDI +) + +// USB CDC identifiers +const ( + usb_STRING_PRODUCT = "Feather nRF52840 Express" + usb_STRING_MANUFACTURER = "Adafruit Industries LLC" +) + +var ( + usb_VID uint16 = 0x239A + usb_PID uint16 = 0x8088 +) + +var ( + DefaultUART = UART0 +) diff --git a/emb/machine/board_feather-nrf52840.go b/emb/machine/board_feather-nrf52840.go new file mode 100644 index 00000000..02fd0536 --- /dev/null +++ b/emb/machine/board_feather-nrf52840.go @@ -0,0 +1,105 @@ +//go:build feather_nrf52840 + +package machine + +const HasLowFrequencyCrystal = true + +// GPIO Pins +const ( + D0 = P0_25 // UART TX + D1 = P0_24 // UART RX + D2 = P0_10 // NFC2 + D3 = P1_15 // LED1 + D4 = P1_10 // LED2 + D5 = P1_08 + D6 = P0_07 + D7 = P1_02 // Button + D8 = P0_16 // NeoPixel + D9 = P0_26 + D10 = P0_27 + D11 = P0_06 + D12 = P0_08 + D13 = P1_09 + D14 = P0_04 // A0 + D15 = P0_05 // A1 + D16 = P0_30 // A2 + D17 = P0_28 // A3 + D18 = P0_02 // A4 + D19 = P0_03 // A5 + D20 = P0_29 // Battery + D21 = P0_31 // AREF + D22 = P0_12 // I2C SDA + D23 = P0_11 // I2C SCL + D24 = P0_15 // SPI MISO + D25 = P0_13 // SPI MOSI + D26 = P0_14 // SPI SCK + D27 = P0_19 // QSPI CLK + D28 = P0_20 // QSPI CS + D29 = P0_17 // QSPI Data 0 + D30 = P0_22 // QSPI Data 1 + D31 = P0_23 // QSPI Data 2 + D32 = P0_21 // QSPI Data 3 + D33 = P0_09 // NFC1 (test point on bottom of board) +) + +// Analog Pins +const ( + A0 = D14 + A1 = D15 + A2 = D16 + A3 = D17 + A4 = D18 + A5 = D19 + A6 = D20 // Battery + A7 = D21 // ARef +) + +const ( + LED = D3 + LED1 = LED + LED2 = D4 + NEOPIXEL = D8 + WS2812 = D8 + BUTTON = D7 + + QSPI_SCK = D27 + QSPI_CS = D28 + QSPI_DATA0 = D29 + QSPI_DATA1 = D30 + QSPI_DATA2 = D31 + QSPI_DATA3 = D32 +) + +// UART0 pins (logical UART1) +const ( + UART_RX_PIN = D1 + UART_TX_PIN = D0 +) + +// I2C pins +const ( + SDA_PIN = D22 // I2C0 external + SCL_PIN = D23 // I2C0 external +) + +// SPI pins +const ( + SPI0_SCK_PIN = D26 // SCK + SPI0_SDO_PIN = D25 // SDO + SPI0_SDI_PIN = D24 // SDI +) + +// USB CDC identifiers +const ( + usb_STRING_PRODUCT = "Feather nRF52840 Express" + usb_STRING_MANUFACTURER = "Adafruit Industries LLC" +) + +var ( + usb_VID uint16 = 0x239A + usb_PID uint16 = 0x802A +) + +var ( + DefaultUART = UART0 +) diff --git a/emb/machine/board_feather-stm32f405.go b/emb/machine/board_feather-stm32f405.go new file mode 100644 index 00000000..4a184bad --- /dev/null +++ b/emb/machine/board_feather-stm32f405.go @@ -0,0 +1,255 @@ +//go:build feather_stm32f405 + +package machine + +import ( + "device/stm32" + "runtime/interrupt" +) + +const ( + NUM_DIGITAL_IO_PINS = 39 + NUM_ANALOG_IO_PINS = 7 +) + +// Digital pins +const ( + // Arduino pin = MCU port pin // primary functions (alternate functions) + D0 = PB11 // USART3 RX, PWM TIM2_CH4 (I2C2 SDA) + D1 = PB10 // USART3 TX, PWM TIM2_CH3 (I2C2 SCL, I2S2 BCK) + D2 = PB3 // GPIO, SPI3 FLASH SCK + D3 = PB4 // GPIO, SPI3 FLASH MISO + D4 = PB5 // GPIO, SPI3 FLASH MOSI + D5 = PC7 // GPIO, PWM TIM3_CH2 (USART6 RX, I2S3 MCK) + D6 = PC6 // GPIO, PWM TIM3_CH1 (USART6 TX, I2S2 MCK) + D7 = PA15 // GPIO, SPI3 FLASH CS + D8 = PC0 // GPIO, Neopixel + D9 = PB8 // GPIO, PWM TIM4_CH3 (CAN1 RX, I2C1 SCL) + D10 = PB9 // GPIO, PWM TIM4_CH4 (CAN1 TX, I2C1 SDA, I2S2 WSL) + D11 = PC3 // GPIO (I2S2 SD, SPI2 MOSI) + D12 = PC2 // GPIO (I2S2ext SD, SPI2 MISO) + D13 = PC1 // GPIO, Builtin LED + D14 = PB7 // I2C1 SDA, PWM TIM4_CH2 (USART1 RX) + D15 = PB6 // I2C1 SCL, PWM TIM4_CH1 (USART1 TX, CAN2 TX) + D16 = PA4 // A0 (DAC OUT1) + D17 = PA5 // A1 (DAC OUT2, SPI1 SCK) + D18 = PA6 // A2, PWM TIM3_CH1 (SPI1 MISO) + D19 = PA7 // A3, PWM TIM3_CH2 (SPI1 MOSI) + D20 = PC4 // A4 + D21 = PC5 // A5 + D22 = PA3 // A6 + D23 = PB13 // SPI2 SCK, PWM TIM1_CH1N (I2S2 BCK, CAN2 TX) + D24 = PB14 // SPI2 MISO, PWM TIM1_CH2N (I2S2ext SD) + D25 = PB15 // SPI2 MOSI, PWM TIM1_CH3N (I2S2 SD) + D26 = PC8 // SDIO + D27 = PC9 // SDIO + D28 = PC10 // SDIO + D29 = PC11 // SDIO + D30 = PC12 // SDIO + D31 = PD2 // SDIO + D32 = PB12 // SD Detect + D33 = PC14 // OSC32 + D34 = PC15 // OSC32 + D35 = PA11 // USB D+ + D36 = PA12 // USB D- + D37 = PA13 // SWDIO + D38 = PA14 // SWCLK +) + +// Analog pins +const ( + A0 = D16 // ADC12 IN4 + A1 = D17 // ADC12 IN5 + A2 = D18 // ADC12 IN6 + A3 = D19 // ADC12 IN7 + A4 = D20 // ADC12 IN14 + A5 = D21 // ADC12 IN15 + A6 = D22 // VBAT +) + +func init() { + initLED() + initUART() + initSPI() + initI2C() +} + +// -- LEDs --------------------------------------------------------------------- + +const ( + NUM_BOARD_LED = 1 + NUM_BOARD_NEOPIXEL = 1 + + LED_RED = D13 + LED_NEOPIXEL = D8 + LED_BUILTIN = LED_RED + LED = LED_BUILTIN + WS2812 = D8 +) + +func initLED() {} + +// -- UART --------------------------------------------------------------------- + +const ( + // #===========#==========#==============#============#=======#=======# + // | Interface | Hardware | Bus(Freq) | RX/TX Pins | AltFn | Alias | + // #===========#==========#==============#============#=======#=======# + // | UART1 | USART3 | APB1(42 MHz) | D0/D1 | 7 | ~ | + // | UART2 | USART6 | APB2(84 MHz) | D5/D6 | 8 | ~ | + // | UART3 | USART1 | APB2(84 MHz) | D14/D15 | 7 | ~ | + // | --------- | -------- | ------------ | ---------- | ----- | ----- | + // | UART0 | USART3 | APB1(42 MHz) | D0/D1 | 7 | UART1 | + // #===========#==========#==============#============#=======#=======# + NUM_UART_INTERFACES = 3 + + UART1_RX_PIN = D0 // UART1 = hardware: USART3 + UART1_TX_PIN = D1 // + + UART2_RX_PIN = D5 // UART2 = hardware: USART6 + UART2_TX_PIN = D6 // + + UART3_RX_PIN = D14 // UART3 = hardware: USART1 + UART3_TX_PIN = D15 // + + UART0_RX_PIN = UART1_RX_PIN // UART0 = alias: UART1 + UART0_TX_PIN = UART1_TX_PIN // + + UART_RX_PIN = UART0_RX_PIN // default/primary UART pins + UART_TX_PIN = UART0_TX_PIN // +) + +var ( + UART1 = &_UART1 + _UART1 = UART{ + Buffer: NewRingBuffer(), + Bus: stm32.USART3, + TxAltFuncSelector: AF7_USART1_2_3, + RxAltFuncSelector: AF7_USART1_2_3, + } + UART2 = &_UART2 + _UART2 = UART{ + Buffer: NewRingBuffer(), + Bus: stm32.USART6, + TxAltFuncSelector: AF8_USART4_5_6, + RxAltFuncSelector: AF8_USART4_5_6, + } + UART3 = &_UART3 + _UART3 = UART{ + Buffer: NewRingBuffer(), + Bus: stm32.USART1, + TxAltFuncSelector: AF7_USART1_2_3, + RxAltFuncSelector: AF7_USART1_2_3, + } + DefaultUART = UART1 +) + +func initUART() { + UART1.Interrupt = interrupt.New(stm32.IRQ_USART3, _UART1.handleInterrupt) + UART2.Interrupt = interrupt.New(stm32.IRQ_USART6, _UART2.handleInterrupt) + UART3.Interrupt = interrupt.New(stm32.IRQ_USART1, _UART3.handleInterrupt) +} + +// -- SPI ---------------------------------------------------------------------- + +const ( + // #===========#==========#==============#==================#=======#=======# + // | Interface | Hardware | Bus(Freq) | SCK/SDI/SDO Pins | AltFn | Alias | + // #===========#==========#==============#==================#=======#=======# + // | SPI1 | SPI2 | APB1(42 MHz) | D23/D24/D25 | 5 | ~ | + // | SPI2 | SPI3 | APB1(42 MHz) | D2/D3/D4 | 6 | ~ | + // | SPI3 | SPI1 | APB2(84 MHz) | D17/D18/D19 | 5 | ~ | + // | --------- | -------- | ------------ | ---------------- | ----- | ----- | + // | SPI0 | SPI2 | APB1(42 MHz) | D23/D24/D25 | 5 | SPI1 | + // #===========#==========#==============#==================#=======#=======# + NUM_SPI_INTERFACES = 3 + + SPI1_SCK_PIN = D23 // + SPI1_SDI_PIN = D24 // SPI1 = hardware: SPI2 + SPI1_SDO_PIN = D25 // + + SPI2_SCK_PIN = D2 // + SPI2_SDI_PIN = D3 // SPI2 = hardware: SPI3 + SPI2_SDO_PIN = D4 // + + SPI3_SCK_PIN = D17 // + SPI3_SDI_PIN = D18 // SPI3 = hardware: SPI1 + SPI3_SDO_PIN = D19 // + + SPI0_SCK_PIN = SPI1_SCK_PIN // + SPI0_SDI_PIN = SPI1_SDI_PIN // SPI0 = alias: SPI1 + SPI0_SDO_PIN = SPI1_SDO_PIN // + + SPI_SCK_PIN = SPI0_SCK_PIN // + SPI_SDI_PIN = SPI0_SDI_PIN // default/primary SPI pins + SPI_SDO_PIN = SPI0_SDO_PIN // +) + +var ( + SPI1 = &SPI{ + Bus: stm32.SPI2, + AltFuncSelector: AF5_SPI1_SPI2, + } + SPI2 = &SPI{ + Bus: stm32.SPI3, + AltFuncSelector: AF6_SPI3, + } + SPI3 = &SPI{ + Bus: stm32.SPI1, + AltFuncSelector: AF5_SPI1_SPI2, + } + SPI0 = SPI1 +) + +func initSPI() {} + +// -- I2C ---------------------------------------------------------------------- + +const ( + // #===========#==========#==============#==============#=======#=======# + // | Interface | Hardware | Bus(Freq) | SDA/SCL Pins | AltFn | Alias | + // #===========#==========#==============#==============#=======#=======# + // | I2C1 | I2C1 | APB1(42 MHz) | D14/D15 | 4 | ~ | + // | I2C2 | I2C2 | APB1(42 MHz) | D0/D1 | 4 | ~ | + // | I2C3 | I2C1 | APB1(42 MHz) | D9/D10 | 4 | ~ | + // | --------- | -------- | ------------ | ------------ | ----- | ----- | + // | I2C0 | I2C1 | APB1(42 MHz) | D14/D15 | 4 | I2C1 | + // #===========#==========#==============#==============#=======#=======# + NUM_I2C_INTERFACES = 3 + + I2C1_SDA_PIN = D14 // I2C1 = hardware: I2C1 + I2C1_SCL_PIN = D15 // + + I2C2_SDA_PIN = D0 // I2C2 = hardware: I2C2 + I2C2_SCL_PIN = D1 // + + I2C3_SDA_PIN = D9 // I2C3 = hardware: I2C1 + I2C3_SCL_PIN = D10 // (interface duplicated on second pair of pins) + + I2C0_SDA_PIN = I2C1_SDA_PIN // I2C0 = alias: I2C1 + I2C0_SCL_PIN = I2C1_SCL_PIN // + + I2C_SDA_PIN = I2C0_SDA_PIN // default/primary I2C pins + I2C_SCL_PIN = I2C0_SCL_PIN // + + SDA_PIN = I2C0_SDA_PIN + SCL_PIN = I2C0_SCL_PIN +) + +var ( + I2C1 = &I2C{ + Bus: stm32.I2C1, + AltFuncSelector: AF4_I2C1_2_3, + } + I2C2 = &I2C{ + Bus: stm32.I2C2, + AltFuncSelector: AF4_I2C1_2_3, + } + I2C3 = &I2C{ + Bus: stm32.I2C1, + AltFuncSelector: AF4_I2C1_2_3, + } + I2C0 = I2C1 +) + +func initI2C() {} diff --git a/emb/machine/board_feather_rp2040.go b/emb/machine/board_feather_rp2040.go new file mode 100644 index 00000000..44091e56 --- /dev/null +++ b/emb/machine/board_feather_rp2040.go @@ -0,0 +1,82 @@ +//go:build feather_rp2040 + +package machine + +// Onboard crystal oscillator frequency, in MHz. +const xoscFreq = 12 // MHz + +// GPIO Pins +const ( + D4 = GPIO6 + D5 = GPIO7 + D6 = GPIO8 + D9 = GPIO9 + D10 = GPIO10 + D11 = GPIO11 + D12 = GPIO12 + D13 = GPIO13 + D24 = GPIO24 + D25 = GPIO25 +) + +// Analog pins +const ( + A0 = GPIO26 + A1 = GPIO27 + A2 = GPIO28 + A3 = GPIO29 +) + +const LED = GPIO13 + +// I2C Pins. +const ( + I2C0_SDA_PIN = GPIO24 + I2C0_SCL_PIN = GPIO25 + + I2C1_SDA_PIN = GPIO2 + I2C1_SCL_PIN = GPIO3 + + SDA_PIN = I2C1_SDA_PIN + SCL_PIN = I2C1_SCL_PIN +) + +// SPI default pins +const ( + // Default Serial Clock Bus 0 for SPI communications + SPI0_SCK_PIN = GPIO18 + // Default Serial Out Bus 0 for SPI communications + SPI0_SDO_PIN = GPIO19 // Tx + // Default Serial In Bus 0 for SPI communications + SPI0_SDI_PIN = GPIO20 // Rx + + // Default Serial Clock Bus 1 for SPI communications + SPI1_SCK_PIN = GPIO10 + // Default Serial Out Bus 1 for SPI communications + SPI1_SDO_PIN = GPIO11 // Tx + // Default Serial In Bus 1 for SPI communications + SPI1_SDI_PIN = GPIO12 // Rx +) + +// UART pins +const ( + UART0_TX_PIN = GPIO0 + UART0_RX_PIN = GPIO1 + UART1_TX_PIN = GPIO8 + UART1_RX_PIN = GPIO9 + UART_TX_PIN = UART0_TX_PIN + UART_RX_PIN = UART0_RX_PIN +) + +var DefaultUART = UART0 + +// USB identifiers +const ( + usb_STRING_PRODUCT = "Feather RP2040" + usb_STRING_MANUFACTURER = "Adafruit" +) + +var ( + usb_VID uint16 = 0x239A + usb_PID uint16 = 0x80F1 +) diff --git a/emb/machine/board_gemma-m0.go b/emb/machine/board_gemma-m0.go new file mode 100644 index 00000000..af1caaad --- /dev/null +++ b/emb/machine/board_gemma-m0.go @@ -0,0 +1,97 @@ +//go:build sam && atsamd21 && gemma_m0 + +package machine + +// Used to reset into bootloader. +const resetMagicValue = 0xf01669ef + +// GPIO Pins. +const ( + D0 = PA04 // SERCOM0/PAD[0] + D1 = PA02 + D2 = PA05 // SERCOM0/PAD[1] + D3 = PA00 // DotStar LED: SERCOM1/PAD[0]: APA102/MOSI + D4 = PA01 // DotStar LED: SERCOM1/PAD[1]: APA102/SCK + D11 = PA30 // Flash Access: SERCOM1/PAD[2] + D12 = PA31 // Flash Access: SERCOM1/PAD[3] + D13 = PA23 // LED: SERCOM3/PAD[1] SERCOM5/PAD[1] +) + +// Analog pins. +const ( + A0 = D1 + A1 = D2 + A2 = D0 +) + +const ( + LED = PA23 +) + +// USBCDC pins. +const ( + USBCDC_DM_PIN = PA24 + USBCDC_DP_PIN = PA25 +) + +// UART0 pins. +const ( + UART_TX_PIN = PA04 // TX: SERCOM0/PAD[0] + UART_RX_PIN = PA05 // RX: SERCOM0/PAD[1] +) + +// UART0s on the Gemma M0. +var UART0 = &sercomUSART0 + +// SPI pins. +const ( + SPI0_SCK_PIN = PA05 // SCK: SERCOM0/PAD[1] + SPI0_SDO_PIN = PA04 // MOSI: SERCOM0/PAD[0] + SPI0_SDI_PIN = NoPin + SPI0_CS_PIN = NoPin +) + +// SPI on the Gemma M0. +var SPI0 = sercomSPIM0 + +// SPI pins for DotStar LED (using APA102 software SPI) and Flash. +const ( + SPI1_SCK_PIN = PA01 // SCK: SERCOM1/PAD[0] + SPI1_SDO_PIN = PA00 // MOSI: SERCOM1/PAD[1] + SPI1_SDI_PIN = PA31 // MISO: SERCOM1/PAD[3] + SPI1_CS_PIN = PA30 // CS: SERCOM1/PAD[2] +) + +// I2C pins. +const ( + SDA_PIN = PA04 // SDA: SERCOM0/PAD[0] + SCL_PIN = PA05 // SCL: SERCOM0/PAD[1] +) + +// I2C on the Gemma M0. +var ( + I2C0 = sercomI2CM0 +) + +// I2S (not connected, needed for atsamd21). +const ( + I2S_SCK_PIN = NoPin + I2S_SDO_PIN = NoPin + I2S_SDI_PIN = NoPin + I2S_WS_PIN = NoPin +) + +// USB CDC identifiers. +const ( + usb_STRING_PRODUCT = "Adafruit Gemma M0" + usb_STRING_MANUFACTURER = "Adafruit" +) + +var ( + usb_VID uint16 = 0x239A + usb_PID uint16 = 0x801E +) + +var ( + DefaultUART = UART0 +) diff --git a/emb/machine/board_gnse.go b/emb/machine/board_gnse.go new file mode 100644 index 00000000..8e78b43e --- /dev/null +++ b/emb/machine/board_gnse.go @@ -0,0 +1,88 @@ +//go:build gnse + +package machine + +import ( + "device/stm32" + "runtime/interrupt" +) + +const ( + LED_RED = PB5 + LED_GREEN = PB6 + LED_BLUE = PB7 + LED1 = LED_RED // Red + LED2 = LED_GREEN // Green + LED3 = LED_BLUE // Blue + LED = LED_GREEN // Default + + BUTTON = PB3 + BUZZER = PA15 + VBATT_ADC = PB2 + SENSOR_EN = PB12 + FLASH_EN = PC13 + + // SPI0 + SPI0_NSS_PIN = PA4 + SPI0_SCK_PIN = PA5 + SPI0_SDO_PIN = PA6 + SPI0_SDI_PIN = PA7 + + //MCU USART2 + UART2_RX_PIN = PA3 + UART2_TX_PIN = PA2 + + // DEFAULT USART + UART_RX_PIN = UART2_RX_PIN + UART_TX_PIN = UART2_TX_PIN + + // I2C1 pins + // I2C1 is connected to Flash, Accelerometer, Env. Sensor, Crypto Element) + I2C1_SCL_PIN = PA9 + I2C1_SDA_PIN = PA10 + I2C1_ALT_FUNC = 4 + + // I2C2 pins + // I2C2 is expansion J10 QWIIC Connector + I2C2_SCL_PIN = PA12 + I2C2_SDA_PIN = PA11 + I2C2_ALT_FUNC = 4 + + // I2C0 alias for I2C1 + I2C0_SDA_PIN = I2C1_SDA_PIN + I2C0_SCL_PIN = I2C1_SCL_PIN +) + +var ( + // STM32 UART2 is connected to the embedded STLINKV3 Virtual Com Port + UART0 = &_UART0 + _UART0 = UART{ + Buffer: NewRingBuffer(), + Bus: stm32.USART2, + TxAltFuncSelector: 7, + RxAltFuncSelector: 7, + } + + DefaultUART = UART0 + + // I2C Busses + I2C1 = &I2C{ + Bus: stm32.I2C1, + AltFuncSelector: I2C1_ALT_FUNC, + } + I2C2 = &I2C{ + Bus: stm32.I2C2, + AltFuncSelector: I2C2_ALT_FUNC, + } + I2C0 = I2C1 + + // SPI + SPI3 = &SPI{ + Bus: stm32.SPI3, + } +) + +func init() { + // Enable UARTs Interrupts + UART0.Interrupt = interrupt.New(stm32.IRQ_USART2, _UART0.handleInterrupt) +} diff --git a/emb/machine/board_gopher-badge.go b/emb/machine/board_gopher-badge.go new file mode 100644 index 00000000..7af27118 --- /dev/null +++ b/emb/machine/board_gopher-badge.go @@ -0,0 +1,90 @@ +//go:build gopher_badge + +// This contains the pin mappings for the Gopher Badge. +// +// For more information, see: https://gopherbadge.com/ +package machine + +const ( + /*ADC0 Pin = GPIO26 + ADC1 Pin = GPIO27 + ADC2 Pin = GPIO28 + GPIO4 Pin = GPIO4 + GPIO5 Pin = GPIO5 + GPIO6 Pin = GPIO6 + GPIO7 Pin = GPIO7 + GPIO8 Pin = GPIO8 + GPIO9 Pin = GPIO9*/ + + PENIRQ Pin = GPIO13 + + LED Pin = GPIO2 + NEOPIXELS Pin = GPIO15 + WS2812 Pin = GPIO15 + + BUTTON_A Pin = GPIO10 + BUTTON_B Pin = GPIO11 + BUTTON_LEFT Pin = GPIO25 + BUTTON_UP Pin = GPIO24 + BUTTON_RIGHT Pin = GPIO22 + BUTTON_DOWN Pin = GPIO23 + + TFT_RST Pin = GPIO21 + TFT_SDI Pin = GPIO19 + TFT_SDO Pin = GPIO16 + TFT_CS Pin = GPIO17 + TFT_SCL Pin = GPIO18 + TFT_WRX Pin = GPIO20 + TFT_BACKLIGHT Pin = GPIO12 + + SPEAKER Pin = GPIO14 + SPEAKER_ENABLE Pin = GPIO3 +) + +// I2C pins +const ( + I2C0_SDA_PIN Pin = GPIO0 + I2C0_SCL_PIN Pin = GPIO1 + + I2C1_SDA_PIN Pin = NoPin + I2C1_SCL_PIN Pin = NoPin +) + +// SPI pins. +const ( + SPI0_SCK_PIN Pin = GPIO18 + SPI0_SDO_PIN Pin = GPIO19 + SPI0_SDI_PIN Pin = GPIO16 + + SPI1_SCK_PIN Pin = NoPin + SPI1_SDO_PIN Pin = NoPin + SPI1_SDI_PIN Pin = NoPin +) + +// Onboard crystal oscillator frequency, in MHz. +const ( + xoscFreq = 12 // MHz +) + +// USB CDC identifiers +const ( + usb_STRING_PRODUCT = "Gopher Badge" + usb_STRING_MANUFACTURER = "TinyGo" +) + +var ( + usb_VID uint16 = 0x2e8a + usb_PID uint16 = 0x0003 +) + +// UART pins +const ( + UART0_TX_PIN = GPIO0 + UART0_RX_PIN = GPIO1 + UART1_TX_PIN = GPIO4 + UART1_RX_PIN = GPIO5 + UART_TX_PIN = UART0_TX_PIN + UART_RX_PIN = UART0_RX_PIN +) + +var DefaultUART = UART1 diff --git a/emb/machine/board_grandcentral-m4.go b/emb/machine/board_grandcentral-m4.go new file mode 100644 index 00000000..61ef6a89 --- /dev/null +++ b/emb/machine/board_grandcentral-m4.go @@ -0,0 +1,267 @@ +//go:build grandcentral_m4 + +package machine + +// Digital pins +const ( + // = Pin Alt. Function SERCOM PWM Timer Interrupt + // ------ -------------------- -------- ----------- ----------- + D0 = PB25 // UART1 RX 0[1] EXTI9 + D1 = PB24 // UART1 TX 0[0] EXTI8 + D2 = PC18 // TCC0[2] EXTI2 + D3 = PC19 // TCC0[3] EXTI3 + D4 = PC20 // TCC0[4] EXTI4 + D5 = PC21 // TCC0[5] EXTI5 + D6 = PD20 // TCC1[0] EXTI10 + D7 = PD21 // TCC1[1] EXTI11 + D8 = PB18 // TCC1[0] EXTI2 + D9 = PB02 // TC6[0] EXTI3 + D10 = PB22 // TC7[0] EXTI6 + D11 = PB23 // EXTI7 + D12 = PB00 // TC7[0] EXTI0 + D13 = PB01 // On-board LED TC7[1] EXTI1 + D14 = PB16 // UART4 TX, I2S0 SCK 5[0] TC6[0] EXTI0 + D15 = PB17 // UART4 RX, I2S0 MCK 5[1] EXTI1 + D16 = PC22 // UART3 TX 1[0] EXTI6 + D17 = PC23 // UART3 RX 1[1] EXTI6 + D18 = PB12 // UART2 TX 4[0] TCC3[0] EXTI12 + D19 = PB13 // UART2 RX 4[1] TCC3[1] EXTI13 + D20 = PB20 // I2C0 SDA 3[0] EXTI4 + D21 = PB21 // I2C0 SCL 3[1] EXTI5 + D22 = PD12 // EXTI7 + D23 = PA15 // TCC2[1] EXTI15 + D24 = PC17 // I2C1 SCL 6[1] TCC0[1] EXTI1 + D25 = PC16 // I2C1 SDA 6[0] TCC0[0] EXTI0 + D26 = PA12 // PCC DEN1 TC2[0] EXTI12 + D27 = PA13 // PCC DEN2 TC2[1] EXTI13 + D28 = PA14 // PCC CLK TCC2[0] EXTI14 + D29 = PB19 // PCC XCLK EXTI3 + D30 = PA23 // PCC D7 TC4[1] EXTI7 + D31 = PA22 // PCC D6, I2S0 SDI TC4[0] EXTI6 + D32 = PA21 // PCC D5, I2S0 SDO EXTI5 + D33 = PA20 // PCC D4, I2S0 FS EXTI4 + D34 = PA19 // PCC D3 TC3[1] EXTI3 + D35 = PA18 // PCC D2 TC3[0] EXTI2 + D36 = PA17 // PCC D1 EXTI1 + D37 = PA16 // PCC D0 EXTI0 + D38 = PB15 // PCC D9 TCC4[1] EXTI15 + D39 = PB14 // PCC D8 TCC4[0] EXTI14 + D40 = PC13 // PCC D11 EXTI13 + D41 = PC12 // PCC D10 EXTI12 + D42 = PC15 // PCC D13 EXTI15 + D43 = PC14 // PCC D12 EXTI14 + D44 = PC11 // EXTI11 + D45 = PC10 // EXTI10 + D46 = PC06 // EXTI6 + D47 = PC07 // EXTI5 + D48 = PC04 // EXTI4 + D49 = PC05 // EXTI5 + D50 = PD11 // SPI0 SDI 7[3] EXTI11 + D51 = PD08 // SPI0 SDO 7[0] EXTI8 + D52 = PD09 // SPI0 SCK 7[1] EXTI9 + D53 = PD10 // SPI0 CS EXTI10 + D54 = PB05 // ADC1 (A8) EXTI5 + D55 = PB06 // ADC1 (A9) EXTI6 + D56 = PB07 // ADC1 (A10) EXTI7 + D57 = PB08 // ADC1 (A11) EXTI8 + D58 = PB09 // ADC1 (A12) EXTI9 + D59 = PA04 // ADC0 (A13) TC0[0] EXTI4 + D60 = PA06 // ADC0 (A14) TC1[0] EXTI6 + D61 = PA07 // ADC0 (A15) TC1[1] EXTI7 + D62 = PB20 // I2C0 SDA 3[0] TCC1[2] EXTI4 + D63 = PB21 // I2C0 SCL 3[1] TCC1[3] EXTI5 + D64 = PD11 // SPI0 SDI 7[3] EXTI6 + D65 = PD08 // SPI0 SDO 7[0] EXTI3 + D66 = PD09 // SPI0 SCK 7[1] EXTI4 + D67 = PA02 // ADC0 (A0), DAC0 EXTI2 + D68 = PA05 // ADC0 (A1), DAC1 EXTI5 + D69 = PB03 // ADC0 (A2) TC6[1] EXTI3 + D70 = PC00 // ADC1 (A3) EXTI0 + D71 = PC01 // ADC1 (A4) EXTI1 + D72 = PC02 // ADC1 (A5) EXTI2 + D73 = PC03 // ADC1 (A6) EXTI3 + D74 = PB04 // ADC1 (A7) EXTI4 + D75 = PC31 // UART RX LED + D76 = PC30 // UART TX LED + D77 = PA27 // USB HOST EN + D78 = PA24 // USB DM EXTI8 + D79 = PA25 // USB DP EXTI9 + D80 = PB29 // SD/SPI1 SDI 2[3] + D81 = PB27 // SD/SPI1 SCK 2[1] + D82 = PB26 // SD/SPI1 SDO 2[0] + D83 = PB28 // SD/SPI1 CS + D84 = PA03 // AREF EXTI3 + D85 = PA02 // DAC0 EXTI2 + D86 = PA05 // DAC1 EXTI5 + D87 = PB01 // On-board LED (D13) TC7[1] EXTI1 + D88 = PC24 // On-board NeoPixel + D89 = PB10 // QSPI SCK EXTI10 + D90 = PB11 // QSPI CS EXTI11 + D91 = PA08 // QSPI ID0 EXTI(NMI) + D92 = PA09 // QSPI ID1 EXTI9 + D93 = PA10 // QSPI ID2 EXTI10 + D94 = PA11 // QSPI ID3 EXTI11 + D95 = PB31 // SD Detect EXTI15 + D96 = PB30 // SWO EXTI14 +) + +// Analog pins +const ( + A0 = D67 // (PA02) ADC0 ch. 0, + A1 = D68 // (PA05) ADC0 ch. 5, + A2 = D69 // (PB03) ADC0 ch. 15 + A3 = D70 // (PC00) ADC1 ch. 10 + A4 = D71 // (PC01) ADC1 ch. 11 + A5 = D72 // (PC02) ADC1 ch. 4 + A6 = D73 // (PC03) ADC1 ch. 5 + A7 = D74 // (PB04) ADC1 ch. 6 + A8 = D54 // (PB05) ADC1 ch. 7 + A9 = D55 // (PB06) ADC1 ch. 8 + A10 = D56 // (PB07) ADC1 ch. 9 + A11 = D57 // (PB08) ADC1 ch. 0 + A12 = D58 // (PB09) ADC1 ch. 1 + A13 = D59 // (PA04) ADC0 ch. 4 + A14 = D60 // (PA06) ADC0 ch. 6 + A15 = D61 // (PA07) ADC0 ch. 7 + + AREF = D84 // (PA03) +) + +// LED pins +const ( + LED_PIN = D13 // (PB01), also on D87 + UART_RX_LED_PIN = D75 // (PC31) + UART_TX_LED_PIN = D76 // (PC30) + NEOPIXEL_PIN = D88 // (PC24) + + // aliases used by examples and drivers + LED = LED_PIN + LED_RX = UART_RX_LED_PIN + LED_TX = UART_TX_LED_PIN + NEOPIXEL = NEOPIXEL_PIN + WS2812 = NEOPIXEL_PIN +) + +// UART pins +const ( + UART1_RX_PIN = D0 // (PB25) + UART1_TX_PIN = D1 // (PB24) + + UART2_RX_PIN = D19 // (PB13) + UART2_TX_PIN = D18 // (PB12) + + UART3_RX_PIN = D17 // (PC23) + UART3_TX_PIN = D16 // (PC22) + + UART4_RX_PIN = D15 // (PB17) + UART4_TX_PIN = D14 // (PB16) + + UART_RX_PIN = UART1_RX_PIN // default pins + UART_TX_PIN = UART1_TX_PIN // +) + +// UART on the Grand Central M4 +var ( + UART1 = &sercomUSART0 + UART2 = &sercomUSART4 + UART3 = &sercomUSART1 + UART4 = &sercomUSART5 + + DefaultUART = UART1 +) + +// SPI pins +const ( + SPI0_SCK_PIN = D66 // (PD09), also on D52 + SPI0_SDO_PIN = D65 // (PD08), also on D51 + SPI0_SDI_PIN = D64 // (PD11), also on D50 + SPI0_CS_PIN = D53 // (PD10) + + SPI1_SCK_PIN = D81 // (PB27) + SPI1_SDO_PIN = D82 // (PB26) + SPI1_SDI_PIN = D80 // (PB29) + + SPI_SCK_PIN = SPI0_SCK_PIN // default pins + SPI_SDO_PIN = SPI0_SDO_PIN // + SPI_SDI_PIN = SPI0_SDI_PIN // + SPI_CS_PIN = SPI0_CS_PIN // +) + +// SPI on the Grand Central M4 +var ( + SPI0 = sercomSPIM7 + SPI1 = sercomSPIM2 // SD card +) + +// I2C pins +const ( + I2C0_SDA_PIN = D62 // (PB20), also on D20 + I2C0_SCL_PIN = D63 // (PB21), also on D21 + + I2C1_SDA_PIN = D25 // (PC16) + I2C1_SCL_PIN = D24 // (PC17) + + I2C_SDA_PIN = I2C0_SDA_PIN // default pins + I2C_SCL_PIN = I2C0_SCL_PIN // + + SDA_PIN = I2C_SDA_PIN // unconventional pin names + SCL_PIN = I2C_SCL_PIN // (required by machine_atsamd51.go) +) + +// I2C on the Grand Central M4 +var ( + I2C0 = sercomI2CM3 + I2C1 = sercomI2CM6 +) + +// I2S pins +const ( + I2S0_SCK_PIN = D14 // (PB16) + I2S0_MCK_PIN = D15 // (PB17) + I2S0_FS_PIN = D33 // (PA20) + I2S0_SDO_PIN = D32 // (PA21) + I2S0_SDI_PIN = D31 // (PA22) + + I2S_SCK_PIN = I2S0_SCK_PIN // default pins + I2S_WS_PIN = I2S0_FS_PIN // + I2S_SDO_PIN = I2S0_SDO_PIN + I2S_SDI_PIN = NoPin +) + +// SD card pins +const ( + SD0_SCK_PIN = D81 // (PB27) + SD0_SDO_PIN = D82 // (PB26) + SD0_SDI_PIN = D80 // (PB29) + SD0_CS_PIN = D83 // (PB28) + SD0_DET_PIN = D95 // (PB31) + + SDCARD_SCK_PIN = SD0_SCK_PIN // default pins + SDCARD_SDO_PIN = SD0_SDO_PIN // + SDCARD_SDI_PIN = SD0_SDI_PIN // + SDCARD_CS_PIN = SD0_CS_PIN // + SDCARD_DET_PIN = SD0_DET_PIN // +) + +// Other peripheral constants +const ( + resetMagicValue = 0xF01669EF // Used to reset into bootloader +) + +// USB CDC pins +const ( + USBCDC_HOSTEN_PIN = D77 // (PA27) host enable + USBCDC_DM_PIN = D78 // (PA24) D- + USBCDC_DP_PIN = D79 // (PA25) D+ +) + +// USB CDC identifiers +const ( + usb_STRING_PRODUCT = "Adafruit Grand Central M4" + usb_STRING_MANUFACTURER = "Adafruit" +) + +var ( + usb_VID uint16 = 0x239A + usb_PID uint16 = 0x8031 +) diff --git a/emb/machine/board_hifive1b.go b/emb/machine/board_hifive1b.go new file mode 100644 index 00000000..a38b6c0a --- /dev/null +++ b/emb/machine/board_hifive1b.go @@ -0,0 +1,61 @@ +//go:build hifive1b + +package machine + +const ( + D0 = P16 + D1 = P17 + D2 = P18 + D3 = P19 // Green LED/PWM (PWM1_PWM1) + D4 = P20 // PWM (PWM1_PWM0) + D5 = P21 // Blue LED/PWM (PWM1_PWM2) + D6 = P22 // Red LED/PWM (PWM1_PWM3) + D7 = P16 + D8 = NoPin // PWM? + D9 = P01 + D10 = P02 // SPI1_CS0 + D11 = P03 // SPI1_DQ0 + D12 = P04 // SPI1_DQ1 + D13 = P05 // SPI1_SCK + D14 = NoPin // not connected + D15 = P09 // does not seem to work? + D16 = P10 // PWM (PWM2_PWM0) + D17 = P11 // PWM (PWM2_PWM1) + D18 = P12 // SDA (I2C0_SDA)/PWM (PWM2_PWM2) + D19 = P13 // SDL (I2C0_SCL)/PWM (PWM2_PWM3) +) + +const ( + LED = LED1 + LED1 = LED_RED + LED2 = LED_GREEN + LED3 = LED_BLUE + LED_RED = P22 + LED_GREEN = P19 + LED_BLUE = P21 +) + +var DefaultUART = UART0 + +const ( + // TODO: figure out the pin numbers for these. + UART_TX_PIN = D1 + UART_RX_PIN = D0 +) + +// SPI pins +const ( + SPI0_SCK_PIN = NoPin + SPI0_SDO_PIN = NoPin + SPI0_SDI_PIN = NoPin + + SPI1_SCK_PIN = D13 + SPI1_SDO_PIN = D11 + SPI1_SDI_PIN = D12 +) + +// I2C pins +const ( + I2C0_SDA_PIN = D18 + I2C0_SCL_PIN = D19 +) diff --git a/emb/machine/board_hifive1b_baremetal.go b/emb/machine/board_hifive1b_baremetal.go new file mode 100644 index 00000000..f621d3bc --- /dev/null +++ b/emb/machine/board_hifive1b_baremetal.go @@ -0,0 +1,12 @@ +//go:build fe310 && hifive1b + +package machine + +import "device/sifive" + +// SPI on the HiFive1. +var ( + SPI1 = &SPI{ + Bus: sifive.QSPI1, + } +) diff --git a/emb/machine/board_hw-651.go b/emb/machine/board_hw-651.go new file mode 100644 index 00000000..54731502 --- /dev/null +++ b/emb/machine/board_hw-651.go @@ -0,0 +1,77 @@ +//go:build hw_651 + +package machine + +// No-name brand board based on the nRF51822 chip with low frequency crystal on board. +// Pinout (reverse engineered from the board) can be found here: +// https://aviatorahmet.blogspot.com/2020/12/pinout-of-nrf51822-board.html +// https://cr0wg4n.medium.com/pinout-nrf51822-board-hw-651-78da2eda8894 + +const HasLowFrequencyCrystal = true + +var DefaultUART = UART0 + +// GPIO pins on header J1 +const ( + J1_01 = P0_21 + J1_03 = P0_23 + J1_04 = P0_22 + J1_05 = P0_25 + J1_06 = P0_24 + J1_09 = P0_29 + J1_10 = P0_28 + J1_11 = P0_30 + J1_13 = P0_00 + J1_15 = P0_02 + J1_17 = P0_04 + J1_16 = P0_01 + J1_18 = P0_03 +) + +// GPIO pins on header J2 +const ( + J2_01 = P0_20 + J2_03 = P0_18 + J2_04 = P0_19 + J2_07 = P0_16 + J2_08 = P0_15 + J2_09 = P0_14 + J2_10 = P0_13 + J2_11 = P0_12 + J2_12 = P0_11 + J2_13 = P0_10 + J2_14 = P0_09 + J2_15 = P0_08 + J2_16 = P0_07 + J2_17 = P0_06 + J2_18 = P0_05 +) + +// UART pins +const ( + UART_TX_PIN = P0_24 // J1_06 on the board + UART_RX_PIN = P0_25 // J1_05 on the board +) + +// ADC pins +const ( + ADC0 = P0_03 // J1_18 on the board + ADC1 = P0_02 // J1_15 on the board + ADC2 = P0_01 // J1_16 on the board + ADC3 = P0_04 // J1_17 on the board + ADC4 = P0_05 // J2_18 on the board + ADC5 = P0_06 // J2_17 on the board +) + +// I2C pins +const ( + SDA_PIN = P0_30 // J1_11 on the board + SCL_PIN = P0_00 // J1_13 on the board +) + +// SPI pins +const ( + SPI0_SCK_PIN = P0_23 // J1_03 on the board + SPI0_SDO_PIN = P0_21 // J1_01 on the board + SPI0_SDI_PIN = P0_22 // J1_04 on the board +) diff --git a/emb/machine/board_itsybitsy-m0.go b/emb/machine/board_itsybitsy-m0.go new file mode 100644 index 00000000..0cc6cad3 --- /dev/null +++ b/emb/machine/board_itsybitsy-m0.go @@ -0,0 +1,110 @@ +//go:build sam && atsamd21 && itsybitsy_m0 + +package machine + +// used to reset into bootloader +const resetMagicValue = 0xf01669ef + +// GPIO Pins +const ( + D0 = PA11 // UART0 RX + D1 = PA10 // UART0 TX + D2 = PA14 + D3 = PA09 // PWM available + D4 = PA08 // PWM available + D5 = PA15 // PWM available + D6 = PA20 // PWM available + D7 = PA21 // PWM available + D8 = PA06 // PWM available + D9 = PA07 // PWM available + D10 = PA18 // can be used for PWM or UART1 TX + D11 = PA16 // can be used for PWM or UART1 RX + D12 = PA19 // PWM available + D13 = PA17 // PWM available +) + +// Analog pins +const ( + A0 = PA02 // ADC/AIN[0] + A1 = PB08 // ADC/AIN[2] + A2 = PB09 // ADC/AIN[3] + A3 = PA04 // ADC/AIN[4] + A4 = PA05 // ADC/AIN[5] + A5 = PB02 // ADC/AIN[10] +) + +const ( + LED = D13 +) + +// USBCDC pins +const ( + USBCDC_DM_PIN = PA24 + USBCDC_DP_PIN = PA25 +) + +// UART1 pins +const ( + UART_TX_PIN = D10 + UART_RX_PIN = D11 +) + +// UART1 on the ItsyBitsy M0. +var ( + UART1 = &sercomUSART1 +) + +// I2C pins +const ( + SDA_PIN = PA22 // SDA: SERCOM3/PAD[0] + SCL_PIN = PA23 // SCL: SERCOM3/PAD[1] +) + +// I2C on the ItsyBitsy M0. +var ( + I2C0 = sercomI2CM3 +) + +// SPI pins +const ( + SPI0_SCK_PIN = PB11 // SCK: SERCOM4/PAD[3] + SPI0_SDO_PIN = PB10 // SDO: SERCOM4/PAD[2] + SPI0_SDI_PIN = PA12 // SDI: SERCOM4/PAD[0] +) + +// SPI on the ItsyBitsy M0. +var SPI0 = sercomSPIM4 + +// "Internal" SPI pins; SPI flash is attached to these on ItsyBitsy M0 +const ( + SPI1_CS_PIN = PA27 + SPI1_SCK_PIN = PB23 + SPI1_SDO_PIN = PB22 + SPI1_SDI_PIN = PB03 +) + +// "Internal" SPI on Sercom 5 +var SPI1 = sercomSPIM5 + +// I2S pins +const ( + I2S_SCK_PIN = PA10 + I2S_SDO_PIN = PA08 + I2S_SDI_PIN = NoPin + I2S_WS_PIN = NoPin // TODO: figure out what this is on ItsyBitsy M0. +) + +// USB CDC identifiers +const ( + usb_STRING_PRODUCT = "Adafruit ItsyBitsy M0 Express" + usb_STRING_MANUFACTURER = "Adafruit" +) + +var ( + usb_VID uint16 = 0x239A + usb_PID uint16 = 0x800F +) + +var ( + DefaultUART = UART1 +) diff --git a/emb/machine/board_itsybitsy-m4.go b/emb/machine/board_itsybitsy-m4.go new file mode 100644 index 00000000..687538e6 --- /dev/null +++ b/emb/machine/board_itsybitsy-m4.go @@ -0,0 +1,94 @@ +//go:build itsybitsy_m4 + +package machine + +// used to reset into bootloader +const resetMagicValue = 0xf01669ef + +// GPIO Pins +const ( + D0 = PA16 // UART0 RX/PWM available + D1 = PA17 // UART0 TX/PWM available + D2 = PA07 + D3 = PB22 + D4 = PA14 // PWM available + D5 = PA15 // PWM available + D6 = PB02 // dotStar clock + D7 = PA18 // PWM available + D8 = PB03 // dotStar data + D9 = PA19 // PWM available + D10 = PA20 // can be used for PWM or UART1 TX + D11 = PA21 // can be used for PWM or UART1 RX + D12 = PA23 // PWM available + D13 = PA22 // PWM available +) + +// Analog pins +const ( + A0 = PA02 // ADC/AIN[0] + A1 = PA05 // ADC/AIN[2] + A2 = PB08 // ADC/AIN[3] + A3 = PB09 // ADC/AIN[4] + A4 = PA04 // ADC/AIN[5] + A5 = PA06 // ADC/AIN[10] +) + +const ( + LED = D13 +) + +// USBCDC pins +const ( + USBCDC_DM_PIN = PA24 + USBCDC_DP_PIN = PA25 +) + +// UART1 pins +const ( + UART_TX_PIN = D1 + UART_RX_PIN = D0 +) + +const ( + UART2_TX_PIN = A4 + UART2_RX_PIN = D2 +) + +var ( + UART1 = &sercomUSART3 + UART2 = &sercomUSART0 + + DefaultUART = UART1 +) + +// I2C pins +const ( + SDA_PIN = PA12 // SDA: SERCOM2/PAD[0] + SCL_PIN = PA13 // SCL: SERCOM2/PAD[1] +) + +// I2C on the ItsyBitsy M4. +var ( + I2C0 = sercomI2CM2 +) + +// SPI pins +const ( + SPI0_SCK_PIN = PA01 // SCK: SERCOM1/PAD[1] + SPI0_SDO_PIN = PA00 // SDO: SERCOM1/PAD[0] + SPI0_SDI_PIN = PB23 // SDI: SERCOM1/PAD[3] +) + +// SPI on the ItsyBitsy M4. +var SPI0 = sercomSPIM1 + +// USB CDC identifiers +const ( + usb_STRING_PRODUCT = "Adafruit ItsyBitsy M4" + usb_STRING_MANUFACTURER = "Adafruit" +) + +var ( + usb_VID uint16 = 0x239A + usb_PID uint16 = 0x802B +) diff --git a/emb/machine/board_itsybitsy-nrf52840.go b/emb/machine/board_itsybitsy-nrf52840.go new file mode 100644 index 00000000..0e5818fa --- /dev/null +++ b/emb/machine/board_itsybitsy-nrf52840.go @@ -0,0 +1,99 @@ +//go:build itsybitsy_nrf52840 + +package machine + +const HasLowFrequencyCrystal = true + +// GPIO Pins +const ( + D0 = P0_25 // UART TX + D1 = P0_24 // UART RX + D2 = P1_02 + D3 = P0_06 // LED1 + D4 = P0_29 // Button + D5 = P0_27 + D6 = P1_09 // DotStar Clock + D7 = P1_08 + D8 = P0_08 // DotStar Data + D9 = P0_07 + D10 = P0_05 + D11 = P0_26 + D12 = P0_11 + D13 = P0_12 + D14 = P0_04 // A0 + D15 = P0_30 // A1 + D16 = P0_28 // A2 + D17 = P0_31 // A3 + D18 = P0_02 // A4 + D19 = P0_03 // A5 + D20 = P0_05 // A6 + D21 = P0_16 // I2C SDA + D22 = P0_14 // I2C SCL + D23 = P0_20 // SPI SDI + D24 = P0_15 // SPI SDO + D25 = P0_13 // SPI SCK + D26 = P0_19 // QSPI SCK + D27 = P0_23 // QSPI CS + D28 = P0_21 // QSPI Data 0 + D29 = P0_22 // QSPI Data 1 + D30 = P1_00 // QSPI Data 2 + D31 = P0_17 // QSPI Data 3 +) + +// Analog Pins +const ( + A0 = D14 + A1 = D15 + A2 = D16 + A3 = D17 + A4 = D18 + A5 = D19 + A6 = D20 +) + +const ( + LED = D3 + LED1 = LED + BUTTON = D4 + + QSPI_SCK = D26 + QSPI_CS = D27 + QSPI_DATA0 = D28 + QSPI_DATA1 = D29 + QSPI_DATA2 = D30 + QSPI_DATA3 = D31 +) + +// UART0 pins (logical UART1) +const ( + UART_RX_PIN = D0 + UART_TX_PIN = D1 +) + +// I2C pins +const ( + SDA_PIN = D21 // I2C0 external + SCL_PIN = D22 // I2C0 external +) + +// SPI pins +const ( + SPI0_SCK_PIN = D25 + SPI0_SDO_PIN = D24 + SPI0_SDI_PIN = D23 +) + +// USB CDC identifiers +const ( + usb_STRING_PRODUCT = "Adafruit ItsyBitsy nRF52840 Express" + usb_STRING_MANUFACTURER = "Adafruit" +) + +var ( + usb_VID uint16 = 0x239A + usb_PID uint16 = 0x8051 +) + +var ( + DefaultUART = UART0 +) diff --git a/emb/machine/board_k210.go b/emb/machine/board_k210.go new file mode 100644 index 00000000..862be255 --- /dev/null +++ b/emb/machine/board_k210.go @@ -0,0 +1,354 @@ +//go:build maixbit + +// Chip datasheet: https://s3.cn-north-1.amazonaws.com.cn/dl.kendryte.com/documents/kendryte_datasheet_20181011163248_en.pdf + +package machine + +// K210 IO pins. +const ( + P00 Pin = 0 + P01 Pin = 1 + P02 Pin = 2 + P03 Pin = 3 + P04 Pin = 4 + P05 Pin = 5 + P06 Pin = 6 + P07 Pin = 7 + P08 Pin = 8 + P09 Pin = 9 + P10 Pin = 10 + P11 Pin = 11 + P12 Pin = 12 + P13 Pin = 13 + P14 Pin = 14 + P15 Pin = 15 + P16 Pin = 16 + P17 Pin = 17 + P18 Pin = 18 + P19 Pin = 19 + P20 Pin = 20 + P21 Pin = 21 + P22 Pin = 22 + P23 Pin = 23 + P24 Pin = 24 + P25 Pin = 25 + P26 Pin = 26 + P27 Pin = 27 + P28 Pin = 28 + P29 Pin = 29 + P30 Pin = 30 + P31 Pin = 31 + P32 Pin = 32 + P33 Pin = 33 + P34 Pin = 34 + P35 Pin = 35 + P36 Pin = 36 + P37 Pin = 37 + P38 Pin = 38 + P39 Pin = 39 + P40 Pin = 40 + P41 Pin = 41 + P42 Pin = 42 + P43 Pin = 43 + P44 Pin = 44 + P45 Pin = 45 + P46 Pin = 46 + P47 Pin = 47 +) + +type FPIOAFunction uint8 + +// Every pin on the Kendryte K210 is assigned to an FPIOA function. +// Each pin can be configured with every function below. +const ( + FUNC_JTAG_TCLK FPIOAFunction = 0 // JTAG Test Clock + FUNC_JTAG_TDI FPIOAFunction = 1 // JTAG Test Data In + FUNC_JTAG_TMS FPIOAFunction = 2 // JTAG Test Mode Select + FUNC_JTAG_TDO FPIOAFunction = 3 // JTAG Test Data Out + FUNC_SPI0_D0 FPIOAFunction = 4 // SPI0 Data 0 + FUNC_SPI0_D1 FPIOAFunction = 5 // SPI0 Data 1 + FUNC_SPI0_D2 FPIOAFunction = 6 // SPI0 Data 2 + FUNC_SPI0_D3 FPIOAFunction = 7 // SPI0 Data 3 + FUNC_SPI0_D4 FPIOAFunction = 8 // SPI0 Data 4 + FUNC_SPI0_D5 FPIOAFunction = 9 // SPI0 Data 5 + FUNC_SPI0_D6 FPIOAFunction = 10 // SPI0 Data 6 + FUNC_SPI0_D7 FPIOAFunction = 11 // SPI0 Data 7 + FUNC_SPI0_SS0 FPIOAFunction = 12 // SPI0 Chip Select 0 + FUNC_SPI0_SS1 FPIOAFunction = 13 // SPI0 Chip Select 1 + FUNC_SPI0_SS2 FPIOAFunction = 14 // SPI0 Chip Select 2 + FUNC_SPI0_SS3 FPIOAFunction = 15 // SPI0 Chip Select 3 + FUNC_SPI0_ARB FPIOAFunction = 16 // SPI0 Arbitration + FUNC_SPI0_SCLK FPIOAFunction = 17 // SPI0 Serial Clock + FUNC_UARTHS_RX FPIOAFunction = 18 // UART High speed Receiver + FUNC_UARTHS_TX FPIOAFunction = 19 // UART High speed Transmitter + FUNC_RESV6 FPIOAFunction = 20 // Reserved function + FUNC_RESV7 FPIOAFunction = 21 // Reserved function + FUNC_CLK_SPI1 FPIOAFunction = 22 // Clock SPI1 + FUNC_CLK_I2C1 FPIOAFunction = 23 // Clock I2C1 + FUNC_GPIOHS0 FPIOAFunction = 24 // GPIO High speed 0 + FUNC_GPIOHS1 FPIOAFunction = 25 // GPIO High speed 1 + FUNC_GPIOHS2 FPIOAFunction = 26 // GPIO High speed 2 + FUNC_GPIOHS3 FPIOAFunction = 27 // GPIO High speed 3 + FUNC_GPIOHS4 FPIOAFunction = 28 // GPIO High speed 4 + FUNC_GPIOHS5 FPIOAFunction = 29 // GPIO High speed 5 + FUNC_GPIOHS6 FPIOAFunction = 30 // GPIO High speed 6 + FUNC_GPIOHS7 FPIOAFunction = 31 // GPIO High speed 7 + FUNC_GPIOHS8 FPIOAFunction = 32 // GPIO High speed 8 + FUNC_GPIOHS9 FPIOAFunction = 33 // GPIO High speed 9 + FUNC_GPIOHS10 FPIOAFunction = 34 // GPIO High speed 10 + FUNC_GPIOHS11 FPIOAFunction = 35 // GPIO High speed 11 + FUNC_GPIOHS12 FPIOAFunction = 36 // GPIO High speed 12 + FUNC_GPIOHS13 FPIOAFunction = 37 // GPIO High speed 13 + FUNC_GPIOHS14 FPIOAFunction = 38 // GPIO High speed 14 + FUNC_GPIOHS15 FPIOAFunction = 39 // GPIO High speed 15 + FUNC_GPIOHS16 FPIOAFunction = 40 // GPIO High speed 16 + FUNC_GPIOHS17 FPIOAFunction = 41 // GPIO High speed 17 + FUNC_GPIOHS18 FPIOAFunction = 42 // GPIO High speed 18 + FUNC_GPIOHS19 FPIOAFunction = 43 // GPIO High speed 19 + FUNC_GPIOHS20 FPIOAFunction = 44 // GPIO High speed 20 + FUNC_GPIOHS21 FPIOAFunction = 45 // GPIO High speed 21 + FUNC_GPIOHS22 FPIOAFunction = 46 // GPIO High speed 22 + FUNC_GPIOHS23 FPIOAFunction = 47 // GPIO High speed 23 + FUNC_GPIOHS24 FPIOAFunction = 48 // GPIO High speed 24 + FUNC_GPIOHS25 FPIOAFunction = 49 // GPIO High speed 25 + FUNC_GPIOHS26 FPIOAFunction = 50 // GPIO High speed 26 + FUNC_GPIOHS27 FPIOAFunction = 51 // GPIO High speed 27 + FUNC_GPIOHS28 FPIOAFunction = 52 // GPIO High speed 28 + FUNC_GPIOHS29 FPIOAFunction = 53 // GPIO High speed 29 + FUNC_GPIOHS30 FPIOAFunction = 54 // GPIO High speed 30 + FUNC_GPIOHS31 FPIOAFunction = 55 // GPIO High speed 31 + FUNC_GPIO0 FPIOAFunction = 56 // GPIO pin 0 + FUNC_GPIO1 FPIOAFunction = 57 // GPIO pin 1 + FUNC_GPIO2 FPIOAFunction = 58 // GPIO pin 2 + FUNC_GPIO3 FPIOAFunction = 59 // GPIO pin 3 + FUNC_GPIO4 FPIOAFunction = 60 // GPIO pin 4 + FUNC_GPIO5 FPIOAFunction = 61 // GPIO pin 5 + FUNC_GPIO6 FPIOAFunction = 62 // GPIO pin 6 + FUNC_GPIO7 FPIOAFunction = 63 // GPIO pin 7 + FUNC_UART1_RX FPIOAFunction = 64 // UART1 Receiver + FUNC_UART1_TX FPIOAFunction = 65 // UART1 Transmitter + FUNC_UART2_RX FPIOAFunction = 66 // UART2 Receiver + FUNC_UART2_TX FPIOAFunction = 67 // UART2 Transmitter + FUNC_UART3_RX FPIOAFunction = 68 // UART3 Receiver + FUNC_UART3_TX FPIOAFunction = 69 // UART3 Transmitter + FUNC_SPI1_D0 FPIOAFunction = 70 // SPI1 Data 0 + FUNC_SPI1_D1 FPIOAFunction = 71 // SPI1 Data 1 + FUNC_SPI1_D2 FPIOAFunction = 72 // SPI1 Data 2 + FUNC_SPI1_D3 FPIOAFunction = 73 // SPI1 Data 3 + FUNC_SPI1_D4 FPIOAFunction = 74 // SPI1 Data 4 + FUNC_SPI1_D5 FPIOAFunction = 75 // SPI1 Data 5 + FUNC_SPI1_D6 FPIOAFunction = 76 // SPI1 Data 6 + FUNC_SPI1_D7 FPIOAFunction = 77 // SPI1 Data 7 + FUNC_SPI1_SS0 FPIOAFunction = 78 // SPI1 Chip Select 0 + FUNC_SPI1_SS1 FPIOAFunction = 79 // SPI1 Chip Select 1 + FUNC_SPI1_SS2 FPIOAFunction = 80 // SPI1 Chip Select 2 + FUNC_SPI1_SS3 FPIOAFunction = 81 // SPI1 Chip Select 3 + FUNC_SPI1_ARB FPIOAFunction = 82 // SPI1 Arbitration + FUNC_SPI1_SCLK FPIOAFunction = 83 // SPI1 Serial Clock + FUNC_SPI_PERIPHERAL_D0 FPIOAFunction = 84 // SPI Peripheral Data 0 + FUNC_SPI_PERIPHERAL_SS FPIOAFunction = 85 // SPI Peripheral Select + FUNC_SPI_PERIPHERAL_SCLK FPIOAFunction = 86 // SPI Peripheral Serial Clock + FUNC_I2S0_MCLK FPIOAFunction = 87 // I2S0 Main Clock + FUNC_I2S0_SCLK FPIOAFunction = 88 // I2S0 Serial Clock(BCLK) + FUNC_I2S0_WS FPIOAFunction = 89 // I2S0 Word Select(LRCLK) + FUNC_I2S0_IN_D0 FPIOAFunction = 90 // I2S0 Serial Data Input 0 + FUNC_I2S0_IN_D1 FPIOAFunction = 91 // I2S0 Serial Data Input 1 + FUNC_I2S0_IN_D2 FPIOAFunction = 92 // I2S0 Serial Data Input 2 + FUNC_I2S0_IN_D3 FPIOAFunction = 93 // I2S0 Serial Data Input 3 + FUNC_I2S0_OUT_D0 FPIOAFunction = 94 // I2S0 Serial Data Output 0 + FUNC_I2S0_OUT_D1 FPIOAFunction = 95 // I2S0 Serial Data Output 1 + FUNC_I2S0_OUT_D2 FPIOAFunction = 96 // I2S0 Serial Data Output 2 + FUNC_I2S0_OUT_D3 FPIOAFunction = 97 // I2S0 Serial Data Output 3 + FUNC_I2S1_MCLK FPIOAFunction = 98 // I2S1 Main Clock + FUNC_I2S1_SCLK FPIOAFunction = 99 // I2S1 Serial Clock(BCLK) + FUNC_I2S1_WS FPIOAFunction = 100 // I2S1 Word Select(LRCLK) + FUNC_I2S1_IN_D0 FPIOAFunction = 101 // I2S1 Serial Data Input 0 + FUNC_I2S1_IN_D1 FPIOAFunction = 102 // I2S1 Serial Data Input 1 + FUNC_I2S1_IN_D2 FPIOAFunction = 103 // I2S1 Serial Data Input 2 + FUNC_I2S1_IN_D3 FPIOAFunction = 104 // I2S1 Serial Data Input 3 + FUNC_I2S1_OUT_D0 FPIOAFunction = 105 // I2S1 Serial Data Output 0 + FUNC_I2S1_OUT_D1 FPIOAFunction = 106 // I2S1 Serial Data Output 1 + FUNC_I2S1_OUT_D2 FPIOAFunction = 107 // I2S1 Serial Data Output 2 + FUNC_I2S1_OUT_D3 FPIOAFunction = 108 // I2S1 Serial Data Output 3 + FUNC_I2S2_MCLK FPIOAFunction = 109 // I2S2 Main Clock + FUNC_I2S2_SCLK FPIOAFunction = 110 // I2S2 Serial Clock(BCLK) + FUNC_I2S2_WS FPIOAFunction = 111 // I2S2 Word Select(LRCLK) + FUNC_I2S2_IN_D0 FPIOAFunction = 112 // I2S2 Serial Data Input 0 + FUNC_I2S2_IN_D1 FPIOAFunction = 113 // I2S2 Serial Data Input 1 + FUNC_I2S2_IN_D2 FPIOAFunction = 114 // I2S2 Serial Data Input 2 + FUNC_I2S2_IN_D3 FPIOAFunction = 115 // I2S2 Serial Data Input 3 + FUNC_I2S2_OUT_D0 FPIOAFunction = 116 // I2S2 Serial Data Output 0 + FUNC_I2S2_OUT_D1 FPIOAFunction = 117 // I2S2 Serial Data Output 1 + FUNC_I2S2_OUT_D2 FPIOAFunction = 118 // I2S2 Serial Data Output 2 + FUNC_I2S2_OUT_D3 FPIOAFunction = 119 // I2S2 Serial Data Output 3 + FUNC_RESV0 FPIOAFunction = 120 // Reserved function + FUNC_RESV1 FPIOAFunction = 121 // Reserved function + FUNC_RESV2 FPIOAFunction = 122 // Reserved function + FUNC_RESV3 FPIOAFunction = 123 // Reserved function + FUNC_RESV4 FPIOAFunction = 124 // Reserved function + FUNC_RESV5 FPIOAFunction = 125 // Reserved function + FUNC_I2C0_SCLK FPIOAFunction = 126 // I2C0 Serial Clock + FUNC_I2C0_SDA FPIOAFunction = 127 // I2C0 Serial Data + FUNC_I2C1_SCLK FPIOAFunction = 128 // I2C1 Serial Clock + FUNC_I2C1_SDA FPIOAFunction = 129 // I2C1 Serial Data + FUNC_I2C2_SCLK FPIOAFunction = 130 // I2C2 Serial Clock + FUNC_I2C2_SDA FPIOAFunction = 131 // I2C2 Serial Data + FUNC_CMOS_XCLK FPIOAFunction = 132 // DVP System Clock + FUNC_CMOS_RST FPIOAFunction = 133 // DVP System Reset + FUNC_CMOS_PWDN FPIOAFunction = 134 // DVP Power Down Mode + FUNC_CMOS_VSYNC FPIOAFunction = 135 // DVP Vertical Sync + FUNC_CMOS_HREF FPIOAFunction = 136 // DVP Horizontal Reference output + FUNC_CMOS_PCLK FPIOAFunction = 137 // Pixel Clock + FUNC_CMOS_D0 FPIOAFunction = 138 // Data Bit 0 + FUNC_CMOS_D1 FPIOAFunction = 139 // Data Bit 1 + FUNC_CMOS_D2 FPIOAFunction = 140 // Data Bit 2 + FUNC_CMOS_D3 FPIOAFunction = 141 // Data Bit 3 + FUNC_CMOS_D4 FPIOAFunction = 142 // Data Bit 4 + FUNC_CMOS_D5 FPIOAFunction = 143 // Data Bit 5 + FUNC_CMOS_D6 FPIOAFunction = 144 // Data Bit 6 + FUNC_CMOS_D7 FPIOAFunction = 145 // Data Bit 7 + FUNC_SCCB_SCLK FPIOAFunction = 146 // SCCB Serial Clock + FUNC_SCCB_SDA FPIOAFunction = 147 // SCCB Serial Data + FUNC_UART1_CTS FPIOAFunction = 148 // UART1 Clear To Send + FUNC_UART1_DSR FPIOAFunction = 149 // UART1 Data Set Ready + FUNC_UART1_DCD FPIOAFunction = 150 // UART1 Data Carrier Detect + FUNC_UART1_RI FPIOAFunction = 151 // UART1 Ring Indicator + FUNC_UART1_SIR_IN FPIOAFunction = 152 // UART1 Serial Infrared Input + FUNC_UART1_DTR FPIOAFunction = 153 // UART1 Data Terminal Ready + FUNC_UART1_RTS FPIOAFunction = 154 // UART1 Request To Send + FUNC_UART1_OUT2 FPIOAFunction = 155 // UART1 User-designated Output 2 + FUNC_UART1_OUT1 FPIOAFunction = 156 // UART1 User-designated Output 1 + FUNC_UART1_SIR_OUT FPIOAFunction = 157 // UART1 Serial Infrared Output + FUNC_UART1_BAUD FPIOAFunction = 158 // UART1 Transmit Clock Output + FUNC_UART1_RE FPIOAFunction = 159 // UART1 Receiver Output Enable + FUNC_UART1_DE FPIOAFunction = 160 // UART1 Driver Output Enable + FUNC_UART1_RS485_EN FPIOAFunction = 161 // UART1 RS485 Enable + FUNC_UART2_CTS FPIOAFunction = 162 // UART2 Clear To Send + FUNC_UART2_DSR FPIOAFunction = 163 // UART2 Data Set Ready + FUNC_UART2_DCD FPIOAFunction = 164 // UART2 Data Carrier Detect + FUNC_UART2_RI FPIOAFunction = 165 // UART2 Ring Indicator + FUNC_UART2_SIR_IN FPIOAFunction = 166 // UART2 Serial Infrared Input + FUNC_UART2_DTR FPIOAFunction = 167 // UART2 Data Terminal Ready + FUNC_UART2_RTS FPIOAFunction = 168 // UART2 Request To Send + FUNC_UART2_OUT2 FPIOAFunction = 169 // UART2 User-designated Output 2 + FUNC_UART2_OUT1 FPIOAFunction = 170 // UART2 User-designated Output 1 + FUNC_UART2_SIR_OUT FPIOAFunction = 171 // UART2 Serial Infrared Output + FUNC_UART2_BAUD FPIOAFunction = 172 // UART2 Transmit Clock Output + FUNC_UART2_RE FPIOAFunction = 173 // UART2 Receiver Output Enable + FUNC_UART2_DE FPIOAFunction = 174 // UART2 Driver Output Enable + FUNC_UART2_RS485_EN FPIOAFunction = 175 // UART2 RS485 Enable + FUNC_UART3_CTS FPIOAFunction = 176 // UART3 Clear To Send + FUNC_UART3_DSR FPIOAFunction = 177 // UART3 Data Set Ready + FUNC_UART3_DCD FPIOAFunction = 178 // UART3 Data Carrier Detect + FUNC_UART3_RI FPIOAFunction = 179 // UART3 Ring Indicator + FUNC_UART3_SIR_IN FPIOAFunction = 180 // UART3 Serial Infrared Input + FUNC_UART3_DTR FPIOAFunction = 181 // UART3 Data Terminal Ready + FUNC_UART3_RTS FPIOAFunction = 182 // UART3 Request To Send + FUNC_UART3_OUT2 FPIOAFunction = 183 // UART3 User-designated Output 2 + FUNC_UART3_OUT1 FPIOAFunction = 184 // UART3 User-designated Output 1 + FUNC_UART3_SIR_OUT FPIOAFunction = 185 // UART3 Serial Infrared Output + FUNC_UART3_BAUD FPIOAFunction = 186 // UART3 Transmit Clock Output + FUNC_UART3_RE FPIOAFunction = 187 // UART3 Receiver Output Enable + FUNC_UART3_DE FPIOAFunction = 188 // UART3 Driver Output Enable + FUNC_UART3_RS485_EN FPIOAFunction = 189 // UART3 RS485 Enable + FUNC_TIMER0_TOGGLE1 FPIOAFunction = 190 // TIMER0 Toggle Output 1 + FUNC_TIMER0_TOGGLE2 FPIOAFunction = 191 // TIMER0 Toggle Output 2 + FUNC_TIMER0_TOGGLE3 FPIOAFunction = 192 // TIMER0 Toggle Output 3 + FUNC_TIMER0_TOGGLE4 FPIOAFunction = 193 // TIMER0 Toggle Output 4 + FUNC_TIMER1_TOGGLE1 FPIOAFunction = 194 // TIMER1 Toggle Output 1 + FUNC_TIMER1_TOGGLE2 FPIOAFunction = 195 // TIMER1 Toggle Output 2 + FUNC_TIMER1_TOGGLE3 FPIOAFunction = 196 // TIMER1 Toggle Output 3 + FUNC_TIMER1_TOGGLE4 FPIOAFunction = 197 // TIMER1 Toggle Output 4 + FUNC_TIMER2_TOGGLE1 FPIOAFunction = 198 // TIMER2 Toggle Output 1 + FUNC_TIMER2_TOGGLE2 FPIOAFunction = 199 // TIMER2 Toggle Output 2 + FUNC_TIMER2_TOGGLE3 FPIOAFunction = 200 // TIMER2 Toggle Output 3 + FUNC_TIMER2_TOGGLE4 FPIOAFunction = 201 // TIMER2 Toggle Output 4 + FUNC_CLK_SPI2 FPIOAFunction = 202 // Clock SPI2 + FUNC_CLK_I2C2 FPIOAFunction = 203 // Clock I2C2 + FUNC_INTERNAL0 FPIOAFunction = 204 // Internal function signal 0 + FUNC_INTERNAL1 FPIOAFunction = 205 // Internal function signal 1 + FUNC_INTERNAL2 FPIOAFunction = 206 // Internal function signal 2 + FUNC_INTERNAL3 FPIOAFunction = 207 // Internal function signal 3 + FUNC_INTERNAL4 FPIOAFunction = 208 // Internal function signal 4 + FUNC_INTERNAL5 FPIOAFunction = 209 // Internal function signal 5 + FUNC_INTERNAL6 FPIOAFunction = 210 // Internal function signal 6 + FUNC_INTERNAL7 FPIOAFunction = 211 // Internal function signal 7 + FUNC_INTERNAL8 FPIOAFunction = 212 // Internal function signal 8 + FUNC_INTERNAL9 FPIOAFunction = 213 // Internal function signal 9 + FUNC_INTERNAL10 FPIOAFunction = 214 // Internal function signal 10 + FUNC_INTERNAL11 FPIOAFunction = 215 // Internal function signal 11 + FUNC_INTERNAL12 FPIOAFunction = 216 // Internal function signal 12 + FUNC_INTERNAL13 FPIOAFunction = 217 // Internal function signal 13 + FUNC_INTERNAL14 FPIOAFunction = 218 // Internal function signal 14 + FUNC_INTERNAL15 FPIOAFunction = 219 // Internal function signal 15 + FUNC_INTERNAL16 FPIOAFunction = 220 // Internal function signal 16 + FUNC_INTERNAL17 FPIOAFunction = 221 // Internal function signal 17 + FUNC_CONSTANT FPIOAFunction = 222 // Constant function + FUNC_INTERNAL18 FPIOAFunction = 223 // Internal function signal 18 + FUNC_DEBUG0 FPIOAFunction = 224 // Debug function 0 + FUNC_DEBUG1 FPIOAFunction = 225 // Debug function 1 + FUNC_DEBUG2 FPIOAFunction = 226 // Debug function 2 + FUNC_DEBUG3 FPIOAFunction = 227 // Debug function 3 + FUNC_DEBUG4 FPIOAFunction = 228 // Debug function 4 + FUNC_DEBUG5 FPIOAFunction = 229 // Debug function 5 + FUNC_DEBUG6 FPIOAFunction = 230 // Debug function 6 + FUNC_DEBUG7 FPIOAFunction = 231 // Debug function 7 + FUNC_DEBUG8 FPIOAFunction = 232 // Debug function 8 + FUNC_DEBUG9 FPIOAFunction = 233 // Debug function 9 + FUNC_DEBUG10 FPIOAFunction = 234 // Debug function 10 + FUNC_DEBUG11 FPIOAFunction = 235 // Debug function 11 + FUNC_DEBUG12 FPIOAFunction = 236 // Debug function 12 + FUNC_DEBUG13 FPIOAFunction = 237 // Debug function 13 + FUNC_DEBUG14 FPIOAFunction = 238 // Debug function 14 + FUNC_DEBUG15 FPIOAFunction = 239 // Debug function 15 + FUNC_DEBUG16 FPIOAFunction = 240 // Debug function 16 + FUNC_DEBUG17 FPIOAFunction = 241 // Debug function 17 + FUNC_DEBUG18 FPIOAFunction = 242 // Debug function 18 + FUNC_DEBUG19 FPIOAFunction = 243 // Debug function 19 + FUNC_DEBUG20 FPIOAFunction = 244 // Debug function 20 + FUNC_DEBUG21 FPIOAFunction = 245 // Debug function 21 + FUNC_DEBUG22 FPIOAFunction = 246 // Debug function 22 + FUNC_DEBUG23 FPIOAFunction = 247 // Debug function 23 + FUNC_DEBUG24 FPIOAFunction = 248 // Debug function 24 + FUNC_DEBUG25 FPIOAFunction = 249 // Debug function 25 + FUNC_DEBUG26 FPIOAFunction = 250 // Debug function 26 + FUNC_DEBUG27 FPIOAFunction = 251 // Debug function 27 + FUNC_DEBUG28 FPIOAFunction = 252 // Debug function 28 + FUNC_DEBUG29 FPIOAFunction = 253 // Debug function 29 + FUNC_DEBUG30 FPIOAFunction = 254 // Debug function 30 + FUNC_DEBUG31 FPIOAFunction = 255 // Debug function 31 +) + +// These are the default FPIOA values for each function. +// (source: https://github.com/kendryte/kendryte-standalone-sdk/blob/develop/lib/drivers/fpioa.c#L69) +var fpioaFuncDefaults [256]uint32 = [256]uint32{ + 0x00900000, 0x00900001, 0x00900002, 0x00001f03, 0x00b03f04, 0x00b03f05, 0x00b03f06, 0x00b03f07, 0x00b03f08, + 0x00b03f09, 0x00b03f0a, 0x00b03f0b, 0x00001f0c, 0x00001f0d, 0x00001f0e, 0x00001f0f, 0x03900010, 0x00001f11, + 0x00900012, 0x00001f13, 0x00900014, 0x00900015, 0x00001f16, 0x00001f17, 0x00901f18, 0x00901f19, 0x00901f1a, + 0x00901f1b, 0x00901f1c, 0x00901f1d, 0x00901f1e, 0x00901f1f, 0x00901f20, 0x00901f21, 0x00901f22, 0x00901f23, + 0x00901f24, 0x00901f25, 0x00901f26, 0x00901f27, 0x00901f28, 0x00901f29, 0x00901f2a, 0x00901f2b, 0x00901f2c, + 0x00901f2d, 0x00901f2e, 0x00901f2f, 0x00901f30, 0x00901f31, 0x00901f32, 0x00901f33, 0x00901f34, 0x00901f35, + 0x00901f36, 0x00901f37, 0x00901f38, 0x00901f39, 0x00901f3a, 0x00901f3b, 0x00901f3c, 0x00901f3d, 0x00901f3e, + 0x00901f3f, 0x00900040, 0x00001f41, 0x00900042, 0x00001f43, 0x00900044, 0x00001f45, 0x00b03f46, 0x00b03f47, + 0x00b03f48, 0x00b03f49, 0x00b03f4a, 0x00b03f4b, 0x00b03f4c, 0x00b03f4d, 0x00001f4e, 0x00001f4f, 0x00001f50, + 0x00001f51, 0x03900052, 0x00001f53, 0x00b03f54, 0x00900055, 0x00900056, 0x00001f57, 0x00001f58, 0x00001f59, + 0x0090005a, 0x0090005b, 0x0090005c, 0x0090005d, 0x00001f5e, 0x00001f5f, 0x00001f60, 0x00001f61, 0x00001f62, + 0x00001f63, 0x00001f64, 0x00900065, 0x00900066, 0x00900067, 0x00900068, 0x00001f69, 0x00001f6a, 0x00001f6b, + 0x00001f6c, 0x00001f6d, 0x00001f6e, 0x00001f6f, 0x00900070, 0x00900071, 0x00900072, 0x00900073, 0x00001f74, + 0x00001f75, 0x00001f76, 0x00001f77, 0x00000078, 0x00000079, 0x0000007a, 0x0000007b, 0x0000007c, 0x0000007d, + 0x0099107e, 0x0099107f, 0x00991080, 0x00991081, 0x00991082, 0x00991083, 0x00001f84, 0x00001f85, 0x00001f86, + 0x00900087, 0x00900088, 0x00900089, 0x0090008a, 0x0090008b, 0x0090008c, 0x0090008d, 0x0090008e, 0x0090008f, + 0x00900090, 0x00900091, 0x00993092, 0x00993093, 0x00900094, 0x00900095, 0x00900096, 0x00900097, 0x00900098, + 0x00001f99, 0x00001f9a, 0x00001f9b, 0x00001f9c, 0x00001f9d, 0x00001f9e, 0x00001f9f, 0x00001fa0, 0x00001fa1, + 0x009000a2, 0x009000a3, 0x009000a4, 0x009000a5, 0x009000a6, 0x00001fa7, 0x00001fa8, 0x00001fa9, 0x00001faa, + 0x00001fab, 0x00001fac, 0x00001fad, 0x00001fae, 0x00001faf, 0x009000b0, 0x009000b1, 0x009000b2, 0x009000b3, + 0x009000b4, 0x00001fb5, 0x00001fb6, 0x00001fb7, 0x00001fb8, 0x00001fb9, 0x00001fba, 0x00001fbb, 0x00001fbc, + 0x00001fbd, 0x00001fbe, 0x00001fbf, 0x00001fc0, 0x00001fc1, 0x00001fc2, 0x00001fc3, 0x00001fc4, 0x00001fc5, + 0x00001fc6, 0x00001fc7, 0x00001fc8, 0x00001fc9, 0x00001fca, 0x00001fcb, 0x00001fcc, 0x00001fcd, 0x00001fce, + 0x00001fcf, 0x00001fd0, 0x00001fd1, 0x00001fd2, 0x00001fd3, 0x00001fd4, 0x009000d5, 0x009000d6, 0x009000d7, + 0x009000d8, 0x009100d9, 0x00991fda, 0x009000db, 0x009000dc, 0x009000dd, 0x000000de, 0x009000df, 0x00001fe0, + 0x00001fe1, 0x00001fe2, 0x00001fe3, 0x00001fe4, 0x00001fe5, 0x00001fe6, 0x00001fe7, 0x00001fe8, 0x00001fe9, + 0x00001fea, 0x00001feb, 0x00001fec, 0x00001fed, 0x00001fee, 0x00001fef, 0x00001ff0, 0x00001ff1, 0x00001ff2, + 0x00001ff3, 0x00001ff4, 0x00001ff5, 0x00001ff6, 0x00001ff7, 0x00001ff8, 0x00001ff9, 0x00001ffa, 0x00001ffb, + 0x00001ffc, 0x00001ffd, 0x00001ffe, 0x00001fff, +} diff --git a/emb/machine/board_kb2040.go b/emb/machine/board_kb2040.go new file mode 100644 index 00000000..1a6f3536 --- /dev/null +++ b/emb/machine/board_kb2040.go @@ -0,0 +1,84 @@ +//go:build kb2040 + +package machine + +// Onboard crystal oscillator frequency, in MHz. +const xoscFreq = 12 // MHz + +// GPIO Pins +const ( + D0 = GPIO0 + D1 = GPIO1 + D2 = GPIO2 + D3 = GPIO3 + D4 = GPIO4 + D5 = GPIO5 + D6 = GPIO6 + D7 = GPIO7 + D8 = GPIO8 + D9 = GPIO9 + D10 = GPIO10 +) + +// Analog pins +const ( + A0 = GPIO26 + A1 = GPIO27 + A2 = GPIO28 + A3 = GPIO29 +) + +// Note: there is no user-controllable LED on the KB2040 board +// const LED = notConnected + +// I2C Pins. +const ( + I2C0_SDA_PIN = GPIO12 + I2C0_SCL_PIN = GPIO13 + + I2C1_SDA_PIN = GPIO2 + I2C1_SCL_PIN = GPIO3 + + SDA_PIN = I2C0_SDA_PIN + SCL_PIN = I2C0_SCL_PIN +) + +// SPI default pins +const ( + // Default Serial Clock Bus 0 for SPI communications + SPI0_SCK_PIN = GPIO18 + // Default Serial Out Bus 0 for SPI communications + SPI0_SDO_PIN = GPIO19 // Tx + // Default Serial In Bus 0 for SPI communications + SPI0_SDI_PIN = GPIO20 // Rx + + // Default Serial Clock Bus 1 for SPI communications + SPI1_SCK_PIN = GPIO26 + // Default Serial Out Bus 1 for SPI communications + SPI1_SDO_PIN = GPIO27 // Tx + // Default Serial In Bus 1 for SPI communications + SPI1_SDI_PIN = GPIO28 // Rx +) + +// UART pins +const ( + UART0_TX_PIN = GPIO0 + UART0_RX_PIN = GPIO1 + UART1_TX_PIN = GPIO8 + UART1_RX_PIN = GPIO9 + UART_TX_PIN = UART0_TX_PIN + UART_RX_PIN = UART0_RX_PIN +) + +var DefaultUART = UART0 + +// USB identifiers +const ( + usb_STRING_PRODUCT = "KB2040" + usb_STRING_MANUFACTURER = "Adafruit" +) + +var ( + usb_VID uint16 = 0x239A + usb_PID uint16 = 0x8106 +) diff --git a/emb/machine/board_lgt92.go b/emb/machine/board_lgt92.go new file mode 100644 index 00000000..71b05083 --- /dev/null +++ b/emb/machine/board_lgt92.go @@ -0,0 +1,97 @@ +//go:build lgt92 + +package machine + +import ( + "device/stm32" + "runtime/interrupt" +) + +const ( + LED1 = PA12 + LED2 = PA8 + LED3 = PA11 + + LED_RED = LED1 + LED_BLUE = LED2 + LED_GREEN = LED3 + + // Default led + LED = LED1 + + BUTTON = PB14 + + // LG GPS module + GPS_STANDBY_PIN = PB3 + GPS_RESET_PIN = PB4 + GPS_POWER_PIN = PB5 + + MEMS_ACCEL_CS = PE3 + MEMS_ACCEL_INT1 = PE0 + MEMS_ACCEL_INT2 = PE1 + + // SPI + SPI1_SCK_PIN = PA5 + SPI1_SDI_PIN = PA6 + SPI1_SDO_PIN = PA7 + SPI0_SCK_PIN = SPI1_SCK_PIN + SPI0_SDI_PIN = SPI1_SDI_PIN + SPI0_SDO_PIN = SPI1_SDO_PIN + + // LORA RFM95 Radio + RFM95_DIO0_PIN = PC13 + + // TinyGo UART is MCU LPUSART1 + UART_RX_PIN = PA13 + UART_TX_PIN = PA14 + + // TinyGo UART1 is MCU USART1 + UART1_RX_PIN = PB6 + UART1_TX_PIN = PB7 + + // MPU9250 Nine-Axis (Gyro + Accelerometer + Compass) + I2C0_SCL_PIN = PA9 + I2C0_SDA_PIN = PA10 +) + +var DefaultUART = UART0 + +var ( + + // Console UART (LPUSART1) + UART0 = &_UART0 + _UART0 = UART{ + Buffer: NewRingBuffer(), + Bus: stm32.LPUART1, + TxAltFuncSelector: 6, + RxAltFuncSelector: 6, + } + + // Gps UART + UART1 = &_UART1 + _UART1 = UART{ + Buffer: NewRingBuffer(), + Bus: stm32.USART1, + TxAltFuncSelector: 0, + RxAltFuncSelector: 0, + } + + // MPU9250 Nine-Axis (Gyro + Accelerometer + Compass) + I2C1 = &I2C{ + Bus: stm32.I2C1, + AltFuncSelector: 6, + } + I2C0 = I2C1 + + // SPI + SPI0 = &SPI{ + Bus: stm32.SPI1, + } + SPI1 = SPI0 +) + +func init() { + // Enable UARTs Interrupts + UART0.Interrupt = interrupt.New(stm32.IRQ_AES_RNG_LPUART1, _UART0.handleInterrupt) + UART1.Interrupt = interrupt.New(stm32.IRQ_USART1, _UART1.handleInterrupt) +} diff --git a/emb/machine/board_lorae5.go b/emb/machine/board_lorae5.go new file mode 100644 index 00000000..18b5d8e3 --- /dev/null +++ b/emb/machine/board_lorae5.go @@ -0,0 +1,97 @@ +//go:build lorae5 + +package machine + +import ( + "device/stm32" + "runtime/interrupt" +) + +const ( + // We assume a LED is connected on PB5 + LED = PB5 // Default LED + + // Set the POWER_EN3V3 pin to high to turn + // on the 3.3V power for all peripherals + POWER_EN3V3 = PA9 + + // Set the POWER_EN5V pin to high to turn + // on the 5V bus power for all peripherals + POWER_EN5V = PB10 +) + +// SubGhz (SPI3) +const ( + SPI0_NSS_PIN = PA4 + SPI0_SCK_PIN = PA5 + SPI0_SDO_PIN = PA6 + SPI0_SDI_PIN = PA7 +) + +// UARTS +const ( + // MCU USART1 + UART1_TX_PIN = PB6 + UART1_RX_PIN = PB7 + + // MCU USART2 + UART2_TX_PIN = PA2 + UART2_RX_PIN = PA3 + + // DEFAULT USART + UART_TX_PIN = UART1_TX_PIN + UART_RX_PIN = UART1_RX_PIN + + // I2C2 pins + I2C2_SCL_PIN = PB15 + I2C2_SDA_PIN = PA15 + I2C2_ALT_FUNC = 4 + + // I2C0 alias for I2C2 + I2C0_SDA_PIN = I2C2_SDA_PIN + I2C0_SCL_PIN = I2C2_SCL_PIN +) + +var ( + // Console UART + UART0 = &_UART0 + _UART0 = UART{ + Buffer: NewRingBuffer(), + Bus: stm32.USART1, + TxAltFuncSelector: AF7_USART1_2, + RxAltFuncSelector: AF7_USART1_2, + } + DefaultUART = UART0 + + // Since we treat UART1 as zero, let's also call it by the real name + UART1 = UART0 + + // UART2 + UART2 = &_UART2 + _UART2 = UART{ + Buffer: NewRingBuffer(), + Bus: stm32.USART2, + TxAltFuncSelector: AF7_USART1_2, + RxAltFuncSelector: AF7_USART1_2, + } + + // I2C Busses + I2C2 = &I2C{ + Bus: stm32.I2C2, + AltFuncSelector: I2C2_ALT_FUNC, + } + + // Set "default" I2C bus to I2C2 + I2C0 = I2C2 + + // SPI + SPI3 = &SPI{ + Bus: stm32.SPI3, + } +) + +func init() { + // Enable UARTs Interrupts + UART0.Interrupt = interrupt.New(stm32.IRQ_USART1, _UART0.handleInterrupt) + UART2.Interrupt = interrupt.New(stm32.IRQ_USART2, _UART2.handleInterrupt) +} diff --git a/emb/machine/board_m5paper.go b/emb/machine/board_m5paper.go new file mode 100644 index 00000000..7c20f4db --- /dev/null +++ b/emb/machine/board_m5paper.go @@ -0,0 +1,112 @@ +//go:build m5paper + +package machine + +const ( + IO0 = GPIO0 + IO1 = GPIO1 + IO2 = GPIO2 + IO3 = GPIO3 + IO4 = GPIO4 + IO5 = GPIO5 + IO6 = GPIO6 + IO7 = GPIO7 + IO8 = GPIO8 + IO9 = GPIO9 + IO10 = GPIO10 + IO11 = GPIO11 + IO12 = GPIO12 + IO13 = GPIO13 + IO14 = GPIO14 + IO15 = GPIO15 + IO16 = GPIO16 + IO17 = GPIO17 + IO18 = GPIO18 + IO19 = GPIO19 + IO21 = GPIO21 + IO22 = GPIO22 + IO23 = GPIO23 + IO25 = GPIO25 + IO26 = GPIO26 + IO27 = GPIO27 + IO32 = GPIO32 + IO33 = GPIO33 + IO34 = GPIO34 + IO35 = GPIO35 + IO36 = GPIO36 + IO37 = GPIO37 + IO38 = GPIO38 + IO39 = GPIO39 +) + +const ( + POWER_PIN = IO2 + EXT_POWER_PIN = IO5 + EPD_POWER_PIN = IO23 + + // Buttons + BUTTON_RIGHT = IO39 + BUTTON_PUSH = IO38 + BUTTON_LEFT = IO37 + BUTTON = BUTTON_PUSH + + // Touch Screen Interrupt + TOUCH_INT = IO36 +) + +// SPI pins +const ( + SPI0_SCK_PIN = IO14 + SPI0_SDO_PIN = IO12 + SPI0_SDI_PIN = IO13 + + // EPD (IT8951) + EPD_SCK_PIN = SPI0_SCK_PIN + EPD_SDO_PIN = SPI0_SDO_PIN + EPD_SDI_PIN = SPI0_SDI_PIN + EPD_CS_PIN = IO15 + EPD_BUSY_PIN = IO27 + + // SD CARD + SDCARD_SCK_PIN = SPI0_SCK_PIN + SDCARD_SDO_PIN = SPI0_SDO_PIN + SDCARD_SDI_PIN = SPI0_SDI_PIN + SDCARD_CS_PIN = IO4 +) + +// I2C pins +const ( + SDA0_PIN = IO21 + SCL0_PIN = IO22 + + SDA_PIN = SDA0_PIN + SCL_PIN = SCL0_PIN + + I2C_TEMP_ADDR = 0x44 // temperature sensor (SHT30) + I2C_CLOCK_ADDR = 0x51 // real time clock (BM8563) + I2C_TOUCH_ADDR = 0x5D // touch screen controller (GT911) +) + +// ADC pins +const ( + ADC1 Pin = IO35 + ADC2 Pin = IO36 + + BATTERY_ADC_PIN = ADC1 +) + +// DAC pins +const ( + DAC1 Pin = IO25 + DAC2 Pin = IO26 +) + +// UART pins +const ( + // UART0 (CP2104) + UART0_TX_PIN = IO1 + UART0_RX_PIN = IO3 + + UART_TX_PIN = UART0_TX_PIN + UART_RX_PIN = UART0_RX_PIN +) diff --git a/emb/machine/board_m5stack.go b/emb/machine/board_m5stack.go new file mode 100644 index 00000000..33c890a8 --- /dev/null +++ b/emb/machine/board_m5stack.go @@ -0,0 +1,124 @@ +//go:build m5stack + +package machine + +const ( + // GND | ADC G35 + // GND | ADC G36 + // GND | RST EN + // G23 MOSI | DAC/SPK G25 + // G19 MISO | DAC G26 + // G18 SCK | 3.3V + // G3 RXD1 | TXD1 G1 + // G16 RXD2 | TXD2 G17 + // G21 SDA | DCL G22 + // G2 GPIO | GPIO G5 + // G12 IIS_SK | IIS_WS G13 + // G15 IIS_OUT | IIS_MK G0 + // HPWR | IIS_IN G34 + // HPWR | 5V + // HPWR | BATTERY + + IO0 = GPIO0 + IO1 = GPIO1 + IO2 = GPIO2 + IO3 = GPIO3 + IO4 = GPIO4 + IO5 = GPIO5 + IO6 = GPIO6 + IO7 = GPIO7 + IO8 = GPIO8 + IO9 = GPIO9 + IO10 = GPIO10 + IO11 = GPIO11 + IO12 = GPIO12 + IO13 = GPIO13 + IO14 = GPIO14 + IO15 = GPIO15 + IO16 = GPIO16 + IO17 = GPIO17 + IO18 = GPIO18 + IO19 = GPIO19 + IO21 = GPIO21 + IO22 = GPIO22 + IO23 = GPIO23 + IO25 = GPIO25 + IO26 = GPIO26 + IO27 = GPIO27 + IO32 = GPIO32 + IO33 = GPIO33 + IO34 = GPIO34 + IO35 = GPIO35 + IO36 = GPIO36 + IO37 = GPIO37 + IO38 = GPIO38 + IO39 = GPIO39 +) + +const ( + // Buttons + BUTTON_A = IO39 + BUTTON_B = IO38 + BUTTON_C = IO37 + BUTTON = BUTTON_A + + // Speaker + SPEAKER_PIN = IO25 +) + +// SPI pins +const ( + SPI0_SCK_PIN = IO18 + SPI0_SDO_PIN = IO23 + SPI0_SDI_PIN = IO19 + SPI0_CS0_PIN = IO14 + + // LCD (ILI9342C) + LCD_SCK_PIN = SPI0_SCK_PIN + LCD_SDO_PIN = SPI0_SDO_PIN + LCD_SDI_PIN = SPI0_SDI_PIN // NoPin ? + LCD_SS_PIN = SPI0_CS0_PIN + LCD_DC_PIN = IO27 + LCD_RST_PIN = IO33 + LCD_BL_PIN = IO32 + + // SD CARD + SDCARD_SCK_PIN = SPI0_SCK_PIN + SDCARD_SDO_PIN = SPI0_SDO_PIN + SDCARD_SDI_PIN = SPI0_SDI_PIN + SDCARD_SS_PIN = IO4 +) + +// I2C pins +const ( + SDA0_PIN = IO21 + SCL0_PIN = IO22 + + SDA_PIN = SDA0_PIN + SCL_PIN = SCL0_PIN +) + +// ADC pins +const ( + ADC1 Pin = IO35 + ADC2 Pin = IO36 +) + +// DAC pins +const ( + DAC1 Pin = IO25 + DAC2 Pin = IO26 +) + +// UART pins +const ( + // UART0 (CP2104) + UART0_TX_PIN = IO1 + UART0_RX_PIN = IO3 + + UART1_TX_PIN = IO17 + UART1_RX_PIN = IO16 + + UART_TX_PIN = UART0_TX_PIN + UART_RX_PIN = UART0_RX_PIN +) diff --git a/emb/machine/board_m5stack_core2.go b/emb/machine/board_m5stack_core2.go new file mode 100644 index 00000000..773a0699 --- /dev/null +++ b/emb/machine/board_m5stack_core2.go @@ -0,0 +1,115 @@ +//go:build m5stack_core2 + +package machine + +const ( + // GND | ADC G35 + // GND | ADC G36 + // GND | RST EN + // G23 MOSI | DAC G25 + // G38 MISO | DAC G26 + // G18 SCK | 3.3V + // G3 RXD0 | TXD0 G1 + // G13 RXD2 | TXD2 G14 + // G21 intSDA | intSC G22 + // G32 PA_SDA | PA_SCL G33 + // G27 GPIO | GPIO G19 + // G2 I2S_DOUT | I2S_LRCKC G0 + // N/C | PDM_DAT G34 + // N/C | 5V + // N/C | BAT + + IO0 = GPIO0 + IO1 = GPIO1 // U0TXD + IO2 = GPIO2 + IO3 = GPIO3 // U0RXD + IO4 = GPIO4 + IO5 = GPIO5 + IO6 = GPIO6 // SD_CLK + IO7 = GPIO7 // SD_DATA0 + IO8 = GPIO8 // SD_DATA1 + IO9 = GPIO9 // SD_DATA2 + IO10 = GPIO10 // SD_DATA3 + IO11 = GPIO11 // SD_CMD + IO12 = GPIO12 + IO13 = GPIO13 // U0RXD + IO14 = GPIO14 // U1TXD + IO15 = GPIO15 + IO16 = GPIO16 + IO17 = GPIO17 + IO18 = GPIO18 // SPI0_SCK + IO19 = GPIO19 + IO21 = GPIO21 // SDA0 + IO22 = GPIO22 // SCL0 + IO23 = GPIO23 // SPI0_SDO + IO25 = GPIO25 + IO26 = GPIO26 + IO27 = GPIO27 + IO32 = GPIO32 // SDA1 + IO33 = GPIO33 // SCL1 + IO34 = GPIO34 + IO35 = GPIO35 // ADC1 + IO36 = GPIO36 // ADC2 + IO38 = GPIO38 // SPI0_SDI + IO39 = GPIO39 +) + +// SPI pins +const ( + SPI0_SCK_PIN = IO18 + SPI0_SDO_PIN = IO23 + SPI0_SDI_PIN = IO38 + SPI0_CS0_PIN = IO5 + + // LCD (ILI9342C) + LCD_SCK_PIN = SPI0_SCK_PIN + LCD_SDO_PIN = SPI0_SDO_PIN + LCD_SDI_PIN = SPI0_SDI_PIN + LCD_SS_PIN = SPI0_CS0_PIN + LCD_DC_PIN = IO15 + + // SD CARD + SDCARD_SCK_PIN = SPI0_SCK_PIN + SDCARD_SDO_PIN = SPI0_SDO_PIN + SDCARD_SDI_PIN = SPI0_SDI_PIN + SDCARD_SS_PIN = IO4 +) + +// I2C pins +const ( + // Internal I2C (AXP192 / FT6336U / BM8563 / MPU6886) + SDA0_PIN = IO21 + SCL0_PIN = IO22 + + // External I2C (PORT A) + SDA1_PIN = IO32 + SCL1_PIN = IO33 + + SDA_PIN = SDA1_PIN + SCL_PIN = SCL1_PIN +) + +// ADC pins +const ( + ADC1 Pin = IO35 + ADC2 Pin = IO36 +) + +// DAC pins +const ( + DAC1 Pin = IO25 + DAC2 Pin = IO26 +) + +// UART pins +const ( + // UART0 (CP2104) + UART0_TX_PIN = IO1 + UART0_RX_PIN = IO3 + + UART1_TX_PIN = IO14 + UART1_RX_PIN = IO13 + + UART_TX_PIN = UART0_TX_PIN + UART_RX_PIN = UART0_RX_PIN +) diff --git a/emb/machine/board_m5stamp_c3.go b/emb/machine/board_m5stamp_c3.go new file mode 100644 index 00000000..a77e5449 --- /dev/null +++ b/emb/machine/board_m5stamp_c3.go @@ -0,0 +1,51 @@ +//go:build m5stamp_c3 + +package machine + +const ( + IO0 = GPIO0 + IO1 = GPIO1 + IO2 = GPIO2 + IO3 = GPIO3 + IO4 = GPIO4 + IO5 = GPIO5 + IO6 = GPIO6 + IO7 = GPIO7 + IO8 = GPIO8 + IO9 = GPIO9 + IO10 = GPIO10 + IO11 = GPIO11 + IO12 = GPIO12 + IO13 = GPIO13 + IO14 = GPIO14 + IO15 = GPIO15 + IO16 = GPIO16 + IO17 = GPIO17 + IO18 = GPIO18 + IO19 = GPIO19 + IO20 = GPIO20 + IO21 = GPIO21 + + XTAL_32K_P = IO0 + XTAL_32K_N = IO1 + MTMS = IO4 + MTDI = IO5 + MTCK = IO6 + MTDO = IO7 + VDD_SPI = IO11 + SPIHD = IO12 + SPISP = IO13 + SPICS0 = IO14 + SPICLK = IO15 + SPID = IO16 + SPIQ = IO17 + U0RXD = IO20 + U0TXD = IO21 + + UART_TX_PIN = U0TXD + UART_RX_PIN = U0RXD +) + +const ( + WS2812 = IO2 +) diff --git a/emb/machine/board_m5stick_c.go b/emb/machine/board_m5stick_c.go new file mode 100644 index 00000000..2eeb522f --- /dev/null +++ b/emb/machine/board_m5stick_c.go @@ -0,0 +1,117 @@ +//go:build m5stick_c + +// This file contains the pin mapping for the M5STACK M5Stick-C device. +// doc: https://docs.m5stack.com/en/core/m5stickc + +package machine + +const ( + // HAT | HY2.0-4P + // | + // GND | GND + // 5V OUT | VOUT + // G26 | G32 SDA + // G36 | G33 SCL + // G0 | + // BAT | + // 3V3 | + // 5V IN | + + IO0 = GPIO0 // CLK + IO1 = GPIO1 // U0TXD + IO2 = GPIO2 + IO3 = GPIO3 // U0RXD + IO4 = GPIO4 + IO5 = GPIO5 // TFT_CS LCD_SS SPI0_SS + IO6 = GPIO6 + IO7 = GPIO7 + IO8 = GPIO8 + IO9 = GPIO9 // IR + IO10 = GPIO10 // LED + IO11 = GPIO11 + IO12 = GPIO12 + IO13 = GPIO13 // TFT_CLK LCD_SCK SPI0_SCK + IO14 = GPIO14 + IO15 = GPIO15 // TFT_MOSI LCD_MOSI SPI0_MOSI + IO16 = GPIO16 + IO17 = GPIO17 + IO18 = GPIO18 // TFT_RST LCD_RST + IO19 = GPIO19 + IO21 = GPIO21 // SDA0 + IO22 = GPIO22 // SCL0 + IO23 = GPIO23 // TFT_DC LCD_DC + IO25 = GPIO25 // - DAC1 + IO26 = GPIO26 // HAT DAC2 + IO27 = GPIO27 + IO32 = GPIO32 // SDA1 / PIN 32 / RXD2 + IO33 = GPIO33 // SCL1 / PIN 33 / TXD2 + IO34 = GPIO34 // MIC_DATA + IO35 = GPIO35 // IRQ0 ADC1 + IO36 = GPIO36 // HAT ADC2 LCD_MISO SPI0_MISO + IO37 = GPIO37 // BUTTON_A, BUTTON_HOME, BUTTON + IO38 = GPIO38 + IO39 = GPIO39 // BUTTON_B, BUTTON_RST +) + +const ( + // Buttons + BUTTON_A = IO37 + BUTTON_B = IO39 + BUTTON_HOME = BUTTON_A + BUTTON_RST = BUTTON_B + BUTTON = BUTTON_A + + // LED + IR = IO9 + LED = IO10 +) + +// SPI pins +const ( + SPI0_SCK_PIN = IO13 + SPI0_SDO_PIN = IO15 + SPI0_CS0_PIN = IO5 + + // LCD () + LCD_SCK_PIN = SPI0_SCK_PIN + LCD_SDO_PIN = SPI0_SDO_PIN + LCD_SS_PIN = SPI0_CS0_PIN + LCD_DC_PIN = IO23 + LCD_RST_PIN = IO18 +) + +// I2C pins +const ( + // Internal I2C (AXP192 / BM8563 / MPU6886) + SDA0_PIN = IO21 + SCL0_PIN = IO22 + + // External I2C (GROOVE PORT) + SDA1_PIN = IO32 + SCL1_PIN = IO33 + + SDA_PIN = SDA1_PIN + SCL_PIN = SCL1_PIN +) + +// ADC pins +const ( + ADC1 Pin = IO35 + ADC2 Pin = IO36 +) + +// DAC pins +const ( + DAC1 Pin = IO25 + DAC2 Pin = IO26 +) + +// UART pins +const ( + // UART0 (CP2104) + UART0_TX_PIN = IO1 + UART0_RX_PIN = IO3 + + UART_TX_PIN = UART0_TX_PIN + UART_RX_PIN = UART0_RX_PIN +) diff --git a/emb/machine/board_macropad-rp2040.go b/emb/machine/board_macropad-rp2040.go new file mode 100644 index 00000000..78bd2b74 --- /dev/null +++ b/emb/machine/board_macropad-rp2040.go @@ -0,0 +1,87 @@ +//go:build macropad_rp2040 + +package machine + +const ( + NeopixelCount = 12 + + // Onboard crystal oscillator frequency, in MHz. + xoscFreq = 12 // MHz +) + +const ( + SWITCH = GPIO0 + BUTTON = GPIO0 + + KEY1 = GPIO1 + KEY2 = GPIO2 + KEY3 = GPIO3 + KEY4 = GPIO4 + KEY5 = GPIO5 + KEY6 = GPIO6 + KEY7 = GPIO7 + KEY8 = GPIO8 + KEY9 = GPIO9 + KEY10 = GPIO10 + KEY11 = GPIO11 + KEY12 = GPIO12 + + LED = GPIO13 + + SPEAKER_ENABLE = GPIO14 + SPEAKER = GPIO16 + + ROT_A = GPIO18 + ROT_B = GPIO17 + + OLED_CS = GPIO22 + OLED_RST = GPIO23 + OLED_DC = GPIO24 + + NEOPIXEL = GPIO19 + WS2812 = NEOPIXEL +) + +// I2C Default pins on Raspberry Pico. +const ( + I2C0_SDA_PIN = GPIO20 + I2C0_SCL_PIN = GPIO21 + + I2C1_SDA_PIN = NoPin // not pinned out + I2C1_SCL_PIN = NoPin // not pinned out +) + +// SPI default pins +const ( + // Default Serial Clock Bus 1 for SPI communications + SPI1_SCK_PIN = GPIO26 + // Default Serial Out Bus 1 for SPI communications + SPI1_SDO_PIN = GPIO27 // Tx + // Default Serial In Bus 1 for SPI communications + SPI1_SDI_PIN = GPIO28 // Rx + + SPI0_SCK_PIN = NoPin // not pinned out + SPI0_SDO_PIN = NoPin // not pinned out + SPI0_SDI_PIN = NoPin // not pinned out +) + +// UART pins +const ( + UART0_TX_PIN = GPIO0 + UART0_RX_PIN = GPIO1 + UART_TX_PIN = UART0_TX_PIN + UART_RX_PIN = UART0_RX_PIN +) + +var DefaultUART = UART0 + +// USB identifiers +const ( + usb_STRING_PRODUCT = "MacroPad RP2040" + usb_STRING_MANUFACTURER = "Adafruit" +) + +var ( + usb_VID uint16 = 0x239A + usb_PID uint16 = 0x8107 +) diff --git a/emb/machine/board_maixbit.go b/emb/machine/board_maixbit.go new file mode 100644 index 00000000..359a9b8c --- /dev/null +++ b/emb/machine/board_maixbit.go @@ -0,0 +1,74 @@ +//go:build maixbit + +package machine + +// Pins on the MAix Bit. +const ( + D0 = P00 // JTAG_TCLK + D1 = P01 // JTAG_TDI + D2 = P02 // JTAG_TMS + D3 = P03 // JTAG_TDO + D4 = P04 // UARTHS_RX + D5 = P05 // UARTHS_TX + D6 = P06 // RESV0 + D7 = P07 // RESV0 + D8 = P08 // GPIO1 + D9 = P09 // GPIO2 + D10 = P10 // GPIO3 + D11 = P11 // GPIO4 + D12 = P12 // GPIO5 + D13 = P13 // GPIO6 + D14 = P14 // GPIO7 + D15 = P15 // GPIO8 + D16 = P16 // GPIOHS0 + D17 = P17 // GPIOHS1 + D18 = P18 // GPIOHS2 + D19 = P19 // GPIOHS3 + D20 = P20 // GPIOHS4 + D21 = P21 // GPIOHS5 + D22 = P22 // GPIOHS6 + D23 = P23 // GPIOHS7 + D24 = P24 // GPIOHS8 + D25 = P25 // GPIOHS9 + D26 = P26 // GPIOHS10 / SPI0_SDI + D27 = P27 // GPIOHS11 / SPI0_SCLK + D28 = P28 // GPIOHS12 / SPI0_SDO + D29 = P29 // GPIOHS13 + D30 = P30 // GPIOHS14 + D31 = P31 // GPIOHS15 + D32 = P32 // GPIOHS16 + D33 = P33 // GPIOHS17 + D34 = P34 // GPIOHS18 + D35 = P35 // GPIOHS19 +) + +const ( + LED = LED1 + LED1 = LED_RED + LED2 = LED_GREEN + LED3 = LED_BLUE + LED_RED = D13 + LED_GREEN = D12 + LED_BLUE = D14 +) + +var DefaultUART = UART0 + +// Default pins for UARTHS. +const ( + UART_TX_PIN = D5 + UART_RX_PIN = D4 +) + +// SPI pins. +const ( + SPI0_SCK_PIN = D27 + SPI0_SDO_PIN = D28 + SPI0_SDI_PIN = D26 +) + +// I2C pins. +const ( + I2C0_SDA_PIN = D34 + I2C0_SCL_PIN = D35 +) diff --git a/emb/machine/board_maixbit_baremetal.go b/emb/machine/board_maixbit_baremetal.go new file mode 100644 index 00000000..f5a7e8d4 --- /dev/null +++ b/emb/machine/board_maixbit_baremetal.go @@ -0,0 +1,15 @@ +//go:build k210 && maixbit + +package machine + +import "device/kendryte" + +// SPI on the MAix Bit. +var ( + SPI0 = &SPI{ + Bus: kendryte.SPI0, + } + SPI1 = &SPI{ + Bus: kendryte.SPI1, + } +) diff --git a/emb/machine/board_makerfabs-esp32c3spi35.go b/emb/machine/board_makerfabs-esp32c3spi35.go new file mode 100644 index 00000000..6e4e6f4b --- /dev/null +++ b/emb/machine/board_makerfabs-esp32c3spi35.go @@ -0,0 +1,103 @@ +//go:build makerfabs_esp32c3spi35 + +// This file contains the pin mappings for the Makerfabs ESP32C3SPI35 board. +// +// The Makerfabs ESP32C3SPI35 is an LCD Touchscreen development board powered +// by the Espressif ESP32-C3 SoC featuring an open-source RISC-V architecture. +// +// Specifications: +// SoC: ESP32-C3-MINI-1-N4, 4MB Flash, RISCV-32bit, 160MHz, 400KB SRAM +// Wireless: WiFi & Bluetooth 5.0 (BLE) +// LCD: 3.5inch TFT LCD (480x320) +// LCD Driver: ILI9488 SPI +// Touch Panel: Capacitive +// Touch Panel Driver: FT6236 +// MicroSD Card Slot +// Mabee Interface +// Dual USB Type-C (one for USB-to-UART and one for native USB) +// +// Website: https://www.makerfabs.com/ep32-c3-risc-v-spi-tft-touch.html +// Wiki: https://wiki.makerfabs.com/ESP32_C3_SPI_3.5_TFT_with_Touch.html +// GitHub: https://github.com/Makerfabs/Makerfabs-ESP32-C3-SPI-TFT-with-Touch +// Schematic: https://github.com/Makerfabs/Makerfabs-ESP32-C3-SPI-TFT-with-Touch/raw/main/Hardware/ESP32-C3%20TFT%20Touch%20v1.1(3.5''%20ili9488).PDF +// Datasheet: https://www.espressif.com/sites/default/files/documentation/esp32-c3-mini-1_datasheet_en.pdf + +package machine + +// Digital pins +const ( + // Pin // Function + // ----- // --------------------- + D0 = GPIO0 // Touchscreen CS + D1 = GPIO1 // MicroSD CS + D2 = GPIO2 // I2C SDA + D3 = GPIO3 // I2C SCL + D4 = GPIO4 // SPI CS + D5 = GPIO5 // SPI SCK + D6 = GPIO6 // SPI SDO + D7 = GPIO7 // SPI SDI + D8 = GPIO8 // Touchscreen Backlight + D9 = GPIO9 // Boot Button + D10 = GPIO10 // TFT D/C + D18 = GPIO18 // USB DM + D19 = GPIO19 // USB DP + D20 = GPIO20 // UART RX + D21 = GPIO21 // UART TX +) + +// Button pin +const ( + BUTTON = BUTTON_BOOT + BUTTON_BOOT = D9 +) + +// TFT pins +const ( + TFT_BL_PIN = D8 + TFT_CS_PIN = SPI_CS_PIN + TFT_DC_PIN = D10 + TFT_SCK_PIN = SPI_SCK_PIN + TFT_SDI_PIN = SPI_SDI_PIN + TFT_SDO_PIN = SPI_SDO_PIN +) + +// Touchscreen pins +const ( + TS_CS_PIN = D0 + TS_SDA_PIN = SDA_PIN + TS_SCL_PIN = SCL_PIN +) + +// MicroSD pins +const ( + SD_CS_PIN = D1 + SD_SCK_PIN = SPI_SCK_PIN + SD_SDI_PIN = SPI_SDI_PIN + SD_SDO_PIN = SPI_SDO_PIN +) + +// USBCDC pins +const ( + USBCDC_DM_PIN = D18 + USBCDC_DP_PIN = D19 +) + +// UART pins +const ( + UART_RX_PIN = D20 + UART_TX_PIN = D21 +) + +// I2C pins +const ( + SDA_PIN = D2 + SCL_PIN = D3 +) + +// SPI pins +const ( + SPI_CS_PIN = D4 + SPI_SCK_PIN = D5 + SPI_SDI_PIN = D7 + SPI_SDO_PIN = D6 +) diff --git a/emb/machine/board_matrixportal-m4.go b/emb/machine/board_matrixportal-m4.go new file mode 100644 index 00000000..b99fcb2f --- /dev/null +++ b/emb/machine/board_matrixportal-m4.go @@ -0,0 +1,200 @@ +//go:build matrixportal_m4 + +package machine + +// used to reset into bootloader +const resetMagicValue = 0xF01669EF + +// Digital pins +const ( + // Pin // Function SERCOM PWM Interrupt + // ---- // ---------------- ------ --- --------- + D0 = PA01 // UART RX 1[1] PWM EXTI1 + D1 = PA00 // UART TX 1[0] PWM EXTI0 + D2 = PB22 // Button "Up" EXTI6 + D3 = PB23 // Button "Down" EXTI7 + D4 = PA23 // NeoPixel EXTI7 + D5 = PB31 // I2C SDA 5[1] EXTI15 + D6 = PB30 // I2C SCL 5[0] EXTI14 + D7 = PB00 // HUB75 R1 EXTI0 + D8 = PB01 // HUB75 G1 EXTI1 + D9 = PB02 // HUB75 B1 EXTI2 + D10 = PB03 // HUB75 R2 EXTI3 + D11 = PB04 // HUB75 G2 EXTI4 + D12 = PB05 // HUB75 B2 EXTI5 + D13 = PA14 // LED PWM EXTI14 + D14 = PB06 // HUB75 CLK EXTI6 + D15 = PB14 // HUB75 LAT EXTI14 + D16 = PB12 // HUB75 OE EXTI12 + D17 = PB07 // HUB75 ADDR A EXTI7 + D18 = PB08 // HUB75 ADDR B EXTI8 + D19 = PB09 // HUB75 ADDR C EXTI9 + D20 = PB15 // HUB75 ADDR D EXTI15 + D21 = PB13 // HUB75 ADDR E EXTI13 + D22 = PA02 // ADC (A0) EXTI2 + D23 = PA05 // ADC (A1) EXTI5 + D24 = PA04 // ADC (A2) PWM EXTI4 + D25 = PA06 // ADC (A3) PWM EXTI6 + D26 = PA07 // ADC (A4) EXTI7 + D27 = PA12 // ESP32 UART RX 4[1] PWM EXTI12 + D28 = PA13 // ESP32 UART TX 4[0] PWM EXTI13 + D29 = PA20 // ESP32 GPIO0 PWM EXTI4 + D30 = PA21 // ESP32 Reset PWM EXTI5 + D31 = PA22 // ESP32 Busy PWM EXTI6 + D32 = PA18 // ESP32 RTS PWM EXTI2 + D33 = PB17 // ESP32 SPI CS PWM EXTI1 + D34 = PA16 // ESP32 SPI SCK 3[1] PWM EXTI0 + D35 = PA17 // ESP32 SPI SDI 3[0] PWM EXTI1 + D36 = PA19 // ESP32 SPI SDO 1[3] PWM EXTI3 + D37 = NoPin // USB Host enable + D38 = PA24 // USB DM + D39 = PA25 // USB DP + D40 = PA03 // DAC/VREFP + D41 = PB10 // Flash QSPI SCK + D42 = PB11 // Flash QSPI CS + D43 = PA08 // Flash QSPI I00 + D44 = PA09 // Flash QSPI IO1 + D45 = PA10 // Flash QSPI IO2 + D46 = PA11 // Flash QSPI IO3 + D47 = PA27 // LIS3DH IRQ EXTI11 + D48 = PA05 // SPI SCK 0[1] EXTI5 + D49 = PA04 // SPI SDO 0[0] PWM EXTI4 + D50 = PA07 // SPI SDI 0[3] EXTI7 +) + +// Analog pins +const ( + A0 = PA02 // ADC Channel 0 + A1 = PA05 // ADC Channel 5 + A2 = PA04 // ADC Channel 4 + A3 = PA06 // ADC Channel 6 + A4 = PA07 // ADC Channel 7 +) + +// LED pins +const ( + LED = D13 + NEOPIXEL = D4 + WS2812 = D4 +) + +// Button pins +const ( + BUTTON_UP = D2 + BUTTON_DOWN = D3 +) + +// UART pins +const ( + UART1_RX_PIN = D0 // SERCOM1[1] + UART1_TX_PIN = D1 // SERCOM1[0] + + UART2_RX_PIN = D27 // SERCOM4[1] (ESP32 RX) + UART2_TX_PIN = D28 // SERCOM4[0] (ESP32 TX) + + UART_RX_PIN = UART1_RX_PIN + UART_TX_PIN = UART1_TX_PIN +) + +// UART on the MatrixPortal M4 +var ( + UART1 = &sercomUSART1 + UART2 = &sercomUSART4 + + DefaultUART = UART1 +) + +// SPI pins +const ( + SPI0_SCK_PIN = D34 // SERCOM3[1] (ESP32 SCK) + SPI0_SDO_PIN = D36 // SERCOM1[3] (ESP32 SDO) + SPI0_SDI_PIN = D35 // SERCOM3[0] (ESP32 SDI) + + SPI1_SCK_PIN = D48 // SERCOM0[1] + SPI1_SDO_PIN = D49 // SERCOM0[0] + SPI1_SDI_PIN = D50 // SERCOM0[3] + + SPI_SCK_PIN = SPI0_SCK_PIN + SPI_SDO_PIN = SPI0_SDO_PIN + SPI_SDI_PIN = SPI0_SDI_PIN +) + +// I2C pins +const ( + I2C0_SDA_PIN = D5 // SERCOM5[1] + I2C0_SCL_PIN = D6 // SERCOM5[0] + + I2C_SDA_PIN = I2C0_SDA_PIN + I2C_SCL_PIN = I2C0_SCL_PIN + + SDA_PIN = I2C_SDA_PIN // awkward naming required by machine_atsamd51.go + SCL_PIN = I2C_SCL_PIN // +) + +// I2C on the MatrixPortal M4 +var ( + I2C0 = sercomI2CM5 +) + +// ESP32 pins +const ( + NINA_ACK = D31 + NINA_GPIO0 = D29 + NINA_RESETN = D30 + + NINA_RX = UART2_RX_PIN + NINA_TX = UART2_TX_PIN + NINA_RTS = D32 + + NINA_CS = D33 + NINA_SDO = SPI0_SDO_PIN + NINA_SDI = SPI0_SDI_PIN + NINA_SCK = SPI0_SCK_PIN +) + +// SPI on the MatrixPortal M4 +var ( + SPI0 = sercomSPIM3 // BUG: SDO on SERCOM1! + NINA_SPI = SPI0 + + SPI1 = sercomSPIM0 +) + +// HUB75 pins +const ( + HUB75_R1 = D7 + HUB75_G1 = D8 + HUB75_B1 = D9 + HUB75_R2 = D10 + HUB75_G2 = D11 + HUB75_B2 = D12 + + HUB75_CLK = D14 + HUB75_LAT = D15 + HUB75_OE = D16 + HUB75_ADDR_A = D17 + HUB75_ADDR_B = D18 + HUB75_ADDR_C = D19 + HUB75_ADDR_D = D20 + HUB75_ADDR_E = D21 +) + +// USB CDC pins (UART0) +const ( + USBCDC_DM_PIN = D38 + USBCDC_DP_PIN = D39 + + UART0_RX_PIN = USBCDC_DM_PIN + UART0_TX_PIN = USBCDC_DP_PIN +) + +// USB CDC identifiers +const ( + usb_STRING_PRODUCT = "Matrix Portal M4" + usb_STRING_MANUFACTURER = "Adafruit Industries" +) + +var ( + usb_VID uint16 = 0x239A + usb_PID uint16 = 0x80C9 +) diff --git a/emb/machine/board_mch2022.go b/emb/machine/board_mch2022.go new file mode 100644 index 00000000..bc6b6000 --- /dev/null +++ b/emb/machine/board_mch2022.go @@ -0,0 +1,28 @@ +//go:build mch2022 + +package machine + +// See: https://badge.team/docs/badges/mch2022/pinout/ + +const ( + UART_TX_PIN Pin = 1 + UART_RX_PIN Pin = 3 + + WS2812 Pin = 5 + + PowerOn Pin = 19 // Set high to enable power to LEDs and SD card + + // I2C pins + SDA_PIN Pin = 22 + SCL_PIN Pin = 21 + + // SPI and related pins (ICE40 and LCD). + LCD_RESET Pin = 25 + LCD_MODE Pin = 26 + LCD_DC Pin = 33 + SPI0_SCK_PIN Pin = 18 + SPI0_SDO_PIN Pin = 23 + SPI0_SDI_PIN Pin = 35 // connected to ICE40 + SPI0_CS_ICE40_PIN Pin = 27 + SPI0_CS_LCD_PIN Pin = 32 +) diff --git a/emb/machine/board_mdbt50qrx.go b/emb/machine/board_mdbt50qrx.go new file mode 100644 index 00000000..b93bee7c --- /dev/null +++ b/emb/machine/board_mdbt50qrx.go @@ -0,0 +1,40 @@ +//go:build mdbt50qrx + +package machine + +const HasLowFrequencyCrystal = false + +// GPIO Pins +const ( + D0 = P1_13 // LED1 + D1 = P1_11 // LED2 (not populated by default) + D2 = P0_15 // Button +) + +const ( + LED = D0 +) + +// MDBT50Q-RX dongle does not have pins broken out for the peripherals below, +// however the machine_nrf*.go implementations of I2C/SPI/etc expect the pin +// constants to be defined, so we are defining them all as NoPin +const ( + UART_TX_PIN = NoPin + UART_RX_PIN = NoPin + SDA_PIN = NoPin + SCL_PIN = NoPin + SPI0_SCK_PIN = NoPin + SPI0_SDO_PIN = NoPin + SPI0_SDI_PIN = NoPin +) + +// USB CDC identifiers +const ( + usb_STRING_PRODUCT = "Raytac MDBT50Q - RX" + usb_STRING_MANUFACTURER = "Raytac Corporation" +) + +var ( + usb_VID uint16 = 0x239A + usb_PID uint16 = 0x810B +) diff --git a/emb/machine/board_metro-m4-airlift.go b/emb/machine/board_metro-m4-airlift.go new file mode 100644 index 00000000..0fd4291a --- /dev/null +++ b/emb/machine/board_metro-m4-airlift.go @@ -0,0 +1,136 @@ +//go:build metro_m4_airlift + +package machine + +// used to reset into bootloader +const resetMagicValue = 0xf01669ef + +// GPIO Pins +const ( + D0 = PA23 // UART0 RX/PWM available + D1 = PA22 // UART0 TX/PWM available + D2 = PB17 // PWM available + D3 = PB16 // PWM available + D4 = PB13 // PWM available + D5 = PB14 // PWM available + D6 = PB15 // PWM available + D7 = PB12 // PWM available + + D8 = PA21 // PWM available + D9 = PA20 // PWM available + D10 = PA18 // can be used for PWM or UART1 TX + D11 = PA19 // can be used for PWM or UART1 RX + D12 = PA17 // PWM available + D13 = PA16 // PWM available + + D40 = PB22 // built-in neopixel +) + +// Analog pins +const ( + A0 = PA02 // ADC/AIN[0] + A1 = PA05 // ADC/AIN[2] + A2 = PB06 // ADC/AIN[3] + A3 = PB00 // ADC/AIN[4] // NOTE: different between "airlift" and non-airlift versions + A4 = PB08 // ADC/AIN[5] + A5 = PB09 // ADC/AIN[10] +) + +const ( + LED = D13 + WS2812 = D40 +) + +// USBCDC pins +const ( + USBCDC_DM_PIN = PA24 + USBCDC_DP_PIN = PA25 +) + +const ( + UART_TX_PIN = D1 + UART_RX_PIN = D0 +) + +const ( + UART2_TX_PIN = PA04 + UART2_RX_PIN = PA07 +) + +var ( + UART1 = &sercomUSART3 + UART2 = &sercomUSART0 + + DefaultUART = UART1 + + UART_NINA = UART2 +) + +// NINA-W102 settings +const ( + NINA_BAUDRATE = 115200 + NINA_RESET_INVERTED = true + NINA_SOFT_FLOWCONTROL = true +) + +const ( + NINA_CS = PA15 + NINA_ACK = PB04 + NINA_GPIO0 = PB01 + NINA_RESETN = PB05 + + // pins used for the ESP32 connection do not allow hardware + // flow control, which is required. have to emulate with software. + NINA_TX = PA04 + NINA_RX = PA07 + NINA_CTS = NINA_ACK + NINA_RTS = NINA_GPIO0 +) + +// I2C pins +const ( + SDA_PIN = PB02 // SDA: SERCOM5/PAD[0] + SCL_PIN = PB03 // SCL: SERCOM5/PAD[1] +) + +// I2C on the Metro M4. +var ( + I2C0 = sercomI2CM5 +) + +// SPI pins +const ( + SPI0_SCK_PIN = PA13 // SCK: SERCOM2/PAD[1] + SPI0_SDO_PIN = PA12 // SDO: SERCOM2/PAD[0] + SPI0_SDI_PIN = PA14 // SDI: SERCOM2/PAD[2] + + NINA_SDO = SPI0_SDO_PIN + NINA_SDI = SPI0_SDI_PIN + NINA_SCK = SPI0_SCK_PIN +) + +const ( + SPI1_SCK_PIN = D12 // SDI: SERCOM1/PAD[1] + SPI1_SDO_PIN = D11 // SDO: SERCOM1/PAD[3] + SPI1_SDI_PIN = D13 // SCK: SERCOM1/PAD[0] +) + +// SPI on the Metro M4. +var ( + SPI0 = sercomSPIM2 + NINA_SPI = SPI0 +) + +// SPI1 on the Metro M4 on pins 11,12,13 +var SPI1 = sercomSPIM1 + +// USB CDC identifiers +const ( + usb_STRING_PRODUCT = "Adafruit Metro M4 Airlift Lite" + usb_STRING_MANUFACTURER = "Adafruit" +) + +var ( + usb_VID uint16 = 0x239A + usb_PID uint16 = 0x8037 +) diff --git a/emb/machine/board_microbit-v2.go b/emb/machine/board_microbit-v2.go new file mode 100644 index 00000000..26cfb517 --- /dev/null +++ b/emb/machine/board_microbit-v2.go @@ -0,0 +1,119 @@ +//go:build microbit_v2 + +package machine + +// The micro:bit does not have a 32kHz crystal on board. +const HasLowFrequencyCrystal = false + +// Buttons on the micro:bit v2 (A and B) +const ( + BUTTON Pin = BUTTONA + BUTTONA Pin = P5 + BUTTONB Pin = P11 +) + +var DefaultUART = UART0 + +// UART pins +const ( + UART_TX_PIN Pin = P34 + UART_RX_PIN Pin = P33 +) + +// ADC pins +const ( + ADC0 Pin = P0 + ADC1 Pin = P1 + ADC2 Pin = P2 +) + +// I2C0 (internal) pins +const ( + SDA_PIN Pin = SDA0_PIN + SCL_PIN Pin = SCL0_PIN + SDA0_PIN Pin = P30 + SCL0_PIN Pin = P31 +) + +// I2C1 (external) pins +const ( + SDA1_PIN Pin = P20 + SCL1_PIN Pin = P19 +) + +// SPI pins +const ( + SPI0_SCK_PIN Pin = P13 + SPI0_SDO_PIN Pin = P15 + SPI0_SDI_PIN Pin = P14 +) + +// GPIO/Analog pins +const ( + P0 Pin = 2 + P1 Pin = 3 + P2 Pin = 4 + P3 Pin = 31 + P4 Pin = 28 + P5 Pin = 14 + P6 Pin = 37 + P7 Pin = 11 + P8 Pin = 10 + P9 Pin = 9 + P10 Pin = 30 + P11 Pin = 23 + P12 Pin = 12 + P13 Pin = 17 + P14 Pin = 1 + P15 Pin = 13 + P16 Pin = 34 + P19 Pin = 26 + P20 Pin = 32 + P21 Pin = 21 + P22 Pin = 22 + P23 Pin = 15 + P24 Pin = 24 + P25 Pin = 19 + P26 Pin = 36 + P27 Pin = 0 + P28 Pin = 20 + P29 Pin = 5 + P30 Pin = 16 + P31 Pin = 8 + P32 Pin = 25 + P33 Pin = 40 + P34 Pin = 6 +) + +// LED matrix pins +const ( + LED_COL_1 Pin = P0_28 + LED_COL_2 Pin = P0_11 + LED_COL_3 Pin = P0_31 + LED_COL_4 Pin = P1_05 + LED_COL_5 Pin = P0_30 + LED_ROW_1 Pin = P0_21 + LED_ROW_2 Pin = P0_22 + LED_ROW_3 Pin = P0_15 + LED_ROW_4 Pin = P0_24 + LED_ROW_5 Pin = P0_19 +) + +// Peripherals +const ( + BUZZER = P27 + CAP_TOUCH = P26 + MIC = P29 + MIC_LED = P28 +) + +// USB CDC identifiers +const ( + usb_STRING_PRODUCT = "BBC micro:bit V2" + usb_STRING_MANUFACTURER = "BBC" +) + +var ( + usb_VID uint16 = 0x0d28 + usb_PID uint16 = 0x0204 +) diff --git a/emb/machine/board_microbit.go b/emb/machine/board_microbit.go new file mode 100644 index 00000000..719bd1d4 --- /dev/null +++ b/emb/machine/board_microbit.go @@ -0,0 +1,78 @@ +//go:build microbit + +package machine + +// The micro:bit does not have a 32kHz crystal on board. +const HasLowFrequencyCrystal = false + +var DefaultUART = UART0 + +// GPIO/Analog pins +const ( + P0 = P0_03 + P1 = P0_02 + P2 = P0_01 + P3 = P0_04 + P4 = P0_05 + P5 = P0_17 + P6 = P0_12 + P7 = P0_11 + P8 = P0_18 + P9 = P0_10 + P10 = P0_06 + P11 = P0_26 + P12 = P0_20 + P13 = P0_23 + P14 = P0_22 + P15 = P0_21 + P16 = P0_16 +) + +// Buttons on the micro:bit (A and B) +const ( + BUTTONA = P0_17 + BUTTONB = P0_26 + BUTTON = BUTTONA +) + +// UART pins +const ( + UART_TX_PIN = P0_24 + UART_RX_PIN = P0_25 +) + +// ADC pins +const ( + ADC0 = P0_03 // P0 on the board + ADC1 = P0_02 // P1 on the board + ADC2 = P0_01 // P2 on the board +) + +// I2C pins +const ( + SDA_PIN = P0_30 // P20 on the board + SCL_PIN = P0_00 // P19 on the board +) + +// SPI pins +const ( + SPI0_SCK_PIN = P0_23 // P13 on the board + SPI0_SDO_PIN = P0_21 // P15 on the board + SPI0_SDI_PIN = P0_22 // P14 on the board +) + +// LED matrix pins +const ( + LED_COL_1 = P0_04 + LED_COL_2 = P0_05 + LED_COL_3 = P0_06 + LED_COL_4 = P0_07 + LED_COL_5 = P0_08 + LED_COL_6 = P0_09 + LED_COL_7 = P0_10 + LED_COL_8 = P0_11 + LED_COL_9 = P0_12 + LED_ROW_1 = P0_13 + LED_ROW_2 = P0_14 + LED_ROW_3 = P0_15 +) diff --git a/emb/machine/board_mksnanov3.go b/emb/machine/board_mksnanov3.go new file mode 100644 index 00000000..35fef682 --- /dev/null +++ b/emb/machine/board_mksnanov3.go @@ -0,0 +1,142 @@ +//go:build mksnanov3 + +// The MKS Robin Nano V3.X board. +// Documented at https://github.com/makerbase-mks/MKS-Robin-Nano-V3.X. + +package machine + +import ( + "device/stm32" + "runtime/interrupt" +) + +// LED is also wired to the SD card card detect (CD) pin. +const LED = PD12 + +// UART pins +const ( + UART_TX_PIN = PB10 + UART_RX_PIN = PB11 +) + +// EXP1 and EXP2 expansion ports for connecting +// the MKS TS35 V2.0 expansion board. +const ( + BEEPER = EXP1_1 + + // LCD pins. + LCD_DC = EXP1_8 + LCD_CS = EXP1_7 + LCD_RS = EXP1_4 + LCD_BACKLIGHT = EXP1_3 + + // Touch pins. Note that some pins are shared with the + // LCD SPI1 interface. + TOUCH_CLK = EXP2_2 + TOUCH_CS = EXP1_5 + TOUCH_DIN = EXP2_6 + TOUCH_DOUT = EXP2_1 + TOUCH_IRQ = EXP1_6 + + BUTTON = BUTTON_JOG + BUTTON_JOG = EXP1_2 + BUTTON_JOG_CCW = EXP2_3 + BUTTON_JOG_CW = EXP2_5 + + EXP1_1 = PC5 + EXP1_2 = PE13 + EXP1_3 = PD13 + EXP1_4 = PC6 + EXP1_5 = PE14 + EXP1_6 = PE15 + EXP1_7 = PD11 + EXP1_8 = PD10 + + EXP2_1 = PA6 + EXP2_2 = PA5 + EXP2_3 = PE8 + EXP2_4 = PE10 + EXP2_5 = PE11 + EXP2_6 = PA7 + EXP2_7 = PE12 +) + +var ( + UART3 = &_UART3 + _UART3 = UART{ + Buffer: NewRingBuffer(), + Bus: stm32.USART3, + TxAltFuncSelector: AF7_USART1_2_3, + RxAltFuncSelector: AF7_USART1_2_3, + } + DefaultUART = UART3 +) + +// set up RX IRQ handler. Follow similar pattern for other UARTx instances +func init() { + UART3.Interrupt = interrupt.New(stm32.IRQ_USART3, _UART3.handleInterrupt) +} + +// SPI pins +const ( + SPI1_SCK_PIN = EXP2_2 + SPI1_SDI_PIN = EXP2_1 + SPI1_SDO_PIN = EXP2_6 + SPI0_SCK_PIN = SPI1_SCK_PIN + SPI0_SDI_PIN = SPI1_SDI_PIN + SPI0_SDO_PIN = SPI1_SDO_PIN +) + +// Since the first interface is named SPI1, both SPI0 and SPI1 refer to SPI1. +var ( + SPI0 = &SPI{ + Bus: stm32.SPI1, + AltFuncSelector: AF5_SPI1_SPI2, + } + SPI1 = SPI0 +) + +const ( + I2C0_SCL_PIN = PB6 + I2C0_SDA_PIN = PB7 +) + +var ( + I2C0 = &I2C{ + Bus: stm32.I2C1, + AltFuncSelector: AF4_I2C1_2_3, + } +) + +// Motor control pins. +const ( + X_ENABLE = PE4 + X_STEP = PE3 + X_DIR = PE2 + X_DIAG = PA15 + X_UART = PD5 + + Y_ENABLE = PE1 + Y_STEP = PE0 + Y_DIR = PB9 + Y_DIAG = PD2 + Y_UART = PD7 + + Z_ENABLE = PB8 + Z_STEP = PB5 + Z_DIR = PB4 + Z_DIAG = PC8 + Z_UART = PD4 + + E0_ENABLE = PB3 + E0_STEP = PD6 + E0_DIR = PD3 + E0_DIAG = PC4 + E0_UART = PD9 + + E1_ENABLE = PA3 + E1_STEP = PD15 + E1_DIR = PA1 + E1_DIAG = PE7 + E1_UART = PD8 +) diff --git a/emb/machine/board_nano-33-ble.go b/emb/machine/board_nano-33-ble.go new file mode 100644 index 00000000..758d5434 --- /dev/null +++ b/emb/machine/board_nano-33-ble.go @@ -0,0 +1,128 @@ +//go:build nano_33_ble + +// This contains the pin mappings for the Arduino Nano 33 BLE [Sense] boards. +// - https://store.arduino.cc/arduino-nano-33-ble +// - https://store.arduino.cc/arduino-nano-33-ble-sense +// +// ---------------------------------------------------------------------------- +// Flashing +// +// Special version of bossac is required. +// This executable can be obtained two ways: +// 1. In Arduino IDE, install support for the board ("Arduino Mbed OS Nano Boards") +// Search for "tools/bossac/1.9.1-arduino2/bossac" in Arduino IDEs directory +// 2. Download https://downloads.arduino.cc/packages/package_index.json +// Search for "bossac-1.9.1-arduino2" in that file +// Download tarball for your OS and unpack it +// +// Once you have the executable, make it accessible in your PATH as "bossac_arduino2". +// +// It is possible to replace original bossac with this new one (this only adds support for nrf chip). +// In that case make "bossac_arduino2" symlink on it, for the board target to be able to find it. +// +// ---------------------------------------------------------------------------- +// Bluetooth +// +// SoftDevice (s140v7) must be flashed first to enable use of bluetooth on this board. +// See https://github.com/tinygo-org/bluetooth +// +// SoftDevice overwrites original bootloader and flashing method described above is not available anymore. +// Instead, please use debug probe and flash your code with "nano-33-ble-s140v7" target. +package machine + +const HasLowFrequencyCrystal = true + +// Digital Pins +const ( + D2 Pin = P1_11 + D3 Pin = P1_12 + D4 Pin = P1_15 + D5 Pin = P1_13 + D6 Pin = P1_14 + D7 Pin = P0_23 + D8 Pin = P0_21 + D9 Pin = P0_27 + D10 Pin = P1_02 + D11 Pin = P1_01 + D12 Pin = P1_08 + D13 Pin = P0_13 +) + +// Analog pins +const ( + A0 Pin = P0_04 + A1 Pin = P0_05 + A2 Pin = P0_30 + A3 Pin = P0_29 + A4 Pin = P0_31 + A5 Pin = P0_02 + A6 Pin = P0_28 + A7 Pin = P0_03 +) + +// Onboard LEDs +const ( + LED = LED_BUILTIN + LED1 = LED_RED + LED2 = LED_GREEN + LED3 = LED_BLUE + LED_BUILTIN = P0_13 + LED_RED = P0_24 + LED_GREEN = P0_16 + LED_BLUE = P0_06 + LED_PWR = P1_09 +) + +// UART0 pins +const ( + UART_RX_PIN = P1_10 + UART_TX_PIN = P1_03 +) + +// I2C pins +const ( + // Defaults to internal + SDA_PIN = SDA1_PIN + SCL_PIN = SCL1_PIN + + // I2C0 (external) pins + SDA0_PIN = P0_31 + SCL0_PIN = P0_02 + + // I2C1 (internal) pins + SDA1_PIN = P0_14 + SCL1_PIN = P0_15 + + I2C_PULLUP = P1_00 // Set high for I2C to work +) + +// SPI pins +const ( + SPI0_SCK_PIN = P0_13 + SPI0_SDO_PIN = P1_01 + SPI0_SDI_PIN = P1_08 +) + +// Peripherals +const ( + APDS_INT = P0_19 // Proximity (APDS9960) interrupt pin + + LSM_PWR = P0_22 // IMU (LSM9DS1) power + LPS_PWR = P0_22 // Pressure (LPS22HB) power + HTS_PWR = P0_22 // Humidity (HTS221) power + + MIC_PWR = P0_17 // Microphone (MP34DT06JTR) power + MIC_CLK = P0_26 + MIC_DIN = P0_25 +) + +// USB CDC identifiers +const ( + usb_STRING_PRODUCT = "Nano 33 BLE" + usb_STRING_MANUFACTURER = "Arduino" +) + +var ( + usb_VID uint16 = 0x2341 + usb_PID uint16 = 0x805a +) diff --git a/emb/machine/board_nano-rp2040.go b/emb/machine/board_nano-rp2040.go new file mode 100644 index 00000000..81555231 --- /dev/null +++ b/emb/machine/board_nano-rp2040.go @@ -0,0 +1,126 @@ +//go:build nano_rp2040 + +// This contains the pin mappings for the Arduino Nano RP2040 Connect board. +// +// Sometimes the board is not detected even when the board is connected to your computer. +// To solve this, place a jumper wire between the REC and GND pins, then connect the board to your computer. +// +// For more information, see: https://store.arduino.cc/nano-rp2040-connect +// Also +// - Datasheets: https://docs.arduino.cc/hardware/nano-rp2040-connect +// - Nano RP2040 Connect technical reference: https://docs.arduino.cc/tutorials/nano-rp2040-connect/rp2040-01-technical-reference +package machine + +// Digital Pins +const ( + D2 Pin = GPIO25 + D3 Pin = GPIO15 + D4 Pin = GPIO16 + D5 Pin = GPIO17 + D6 Pin = GPIO18 + D7 Pin = GPIO19 + D8 Pin = GPIO20 + D9 Pin = GPIO21 + D10 Pin = GPIO5 + D11 Pin = GPIO7 + D12 Pin = GPIO4 + D13 Pin = GPIO6 + D14 Pin = GPIO26 + D15 Pin = GPIO27 + D16 Pin = GPIO28 + D17 Pin = GPIO29 + D18 Pin = GPIO12 + D19 Pin = GPIO13 +) + +// Analog pins +const ( + A0 Pin = ADC0 + A1 Pin = ADC1 + A2 Pin = ADC2 + A3 Pin = ADC3 +) + +// Onboard LED +const ( + LED = GPIO6 +) + +// I2C pins +const ( + I2C0_SDA_PIN Pin = GPIO12 + I2C0_SCL_PIN Pin = GPIO13 + + I2C1_SDA_PIN Pin = GPIO18 + I2C1_SCL_PIN Pin = GPIO19 +) + +// SPI pins. SPI1 not available on Nano RP2040 Connect. +const ( + SPI0_SCK_PIN Pin = GPIO6 + SPI0_SDO_PIN Pin = GPIO7 + SPI0_SDI_PIN Pin = GPIO4 + + // GPIO22 does not have SPI functionality so we set it to avoid interfering with NINA. + SPI1_SCK_PIN Pin = GPIO22 + SPI1_SDO_PIN Pin = GPIO22 + SPI1_SDI_PIN Pin = GPIO22 +) + +var ( + NINA_SPI = SPI1 +) + +// NINA-W102 Pins +const ( + NINA_SCK Pin = GPIO14 + NINA_SDO Pin = GPIO11 + NINA_SDI Pin = GPIO8 + + NINA_CS Pin = GPIO9 + NINA_ACK Pin = GPIO10 + NINA_GPIO0 Pin = GPIO2 + NINA_RESETN Pin = GPIO3 + + NINA_TX Pin = GPIO8 + NINA_RX Pin = GPIO9 + NINA_CTS Pin = GPIO10 + NINA_RTS Pin = GPIO11 +) + +// NINA-W102 settings +const ( + NINA_BAUDRATE = 115200 + NINA_RESET_INVERTED = true + NINA_SOFT_FLOWCONTROL = false +) + +// Onboard crystal oscillator frequency, in MHz. +const ( + xoscFreq = 12 // MHz +) + +// USB CDC identifiers +// https://github.com/arduino/ArduinoCore-mbed/blob/master/variants/NANO_RP2040_CONNECT/pins_arduino.h +const ( + usb_STRING_PRODUCT = "Nano RP2040 Connect" + usb_STRING_MANUFACTURER = "Arduino" +) + +var ( + usb_VID uint16 = 0x2341 + usb_PID uint16 = 0x005e +) + +// UART pins +const ( + UART0_TX_PIN = GPIO0 + UART0_RX_PIN = GPIO1 + UART_TX_PIN = UART0_TX_PIN + UART_RX_PIN = UART0_RX_PIN +) + +// UART_NINA on the Arduino Nano RP2040 connects to the NINA HCI. +var UART_NINA = UART1 + +var DefaultUART = UART0 diff --git a/emb/machine/board_nicenano.go b/emb/machine/board_nicenano.go new file mode 100644 index 00000000..254e7805 --- /dev/null +++ b/emb/machine/board_nicenano.go @@ -0,0 +1,79 @@ +//go:build nicenano + +package machine + +const HasLowFrequencyCrystal = true + +// GPIO Pins +const ( + D006 = P0_06 + D008 = P0_08 + D017 = P0_17 + D020 = P0_20 + D022 = P0_22 + D024 = P0_24 + D100 = P1_00 + D011 = P0_11 + D104 = P1_04 + D106 = P1_06 + + D004 = P0_04 // AIN2; P0.04 (AIN2) is used to read the voltage of the battery via ADC. It can’t be used for any other function. + D013 = P0_13 // VCC 3.3V; P0.13 on VCC shuts off the power to VCC when you set it to high; This saves on battery immensely for LEDs of all kinds that eat power even when off + D115 = P1_15 + D113 = P1_13 + D031 = P0_31 // AIN7 + D029 = P0_29 // AIN5 + D002 = P0_02 // AIN0 + + D111 = P1_11 + D010 = P0_10 // NFC2 + D009 = P0_09 // NFC1 + + D026 = P0_26 + D012 = P0_12 + D101 = P1_01 + D102 = P1_02 + D107 = P1_07 +) + +// Analog Pins +const ( + AIN2 = P0_04 // Battery + AIN7 = P0_31 + AIN5 = P0_29 + AIN0 = P0_02 +) + +const ( + LED = P0_15 +) + +// UART0 pins (logical UART1) +const ( + UART_RX_PIN = P0_06 + UART_TX_PIN = P0_08 +) + +// I2C pins +const ( + SDA_PIN = P0_17 // I2C0 external + SCL_PIN = P0_20 // I2C0 external +) + +// SPI pins +const ( + SPI0_SCK_PIN = P0_22 // SCK + SPI0_SDO_PIN = P0_24 // SDO + SPI0_SDI_PIN = P1_00 // SDI +) + +// USB CDC identifiers +const ( + usb_STRING_PRODUCT = "nice!nano" + usb_STRING_MANUFACTURER = "Nice Keyboards" +) + +var ( + usb_VID uint16 = 0x239A + usb_PID uint16 = 0x0029 +) diff --git a/emb/machine/board_nodemcu.go b/emb/machine/board_nodemcu.go new file mode 100644 index 00000000..5157c82e --- /dev/null +++ b/emb/machine/board_nodemcu.go @@ -0,0 +1,35 @@ +//go:build nodemcu + +// Pinout for the NodeMCU dev kit. + +package machine + +// GPIO pins on the NodeMCU board. +const ( + D0 = GPIO16 + D1 = GPIO5 + D2 = GPIO4 + D3 = GPIO0 + D4 = GPIO2 + D5 = GPIO14 + D6 = GPIO12 + D7 = GPIO13 + D8 = GPIO15 +) + +// Onboard blue LED (on the AI-Thinker module). +const LED = D4 + +// SPI pins +const ( + SPI0_SCK_PIN = D5 + SPI0_SDO_PIN = D7 + SPI0_SDI_PIN = D6 + SPI0_CS0_PIN = D8 +) + +// I2C pins +const ( + SDA_PIN = D2 + SCL_PIN = D1 +) diff --git a/emb/machine/board_nrf51.go b/emb/machine/board_nrf51.go new file mode 100644 index 00000000..74e25811 --- /dev/null +++ b/emb/machine/board_nrf51.go @@ -0,0 +1,43 @@ +//go:build nrf51 || microbit + +package machine + +func CPUFrequency() uint32 { + return 16000000 +} + +// Hardware pins +const ( + P0_00 Pin = 0 + P0_01 Pin = 1 + P0_02 Pin = 2 + P0_03 Pin = 3 + P0_04 Pin = 4 + P0_05 Pin = 5 + P0_06 Pin = 6 + P0_07 Pin = 7 + P0_08 Pin = 8 + P0_09 Pin = 9 + P0_10 Pin = 10 + P0_11 Pin = 11 + P0_12 Pin = 12 + P0_13 Pin = 13 + P0_14 Pin = 14 + P0_15 Pin = 15 + P0_16 Pin = 16 + P0_17 Pin = 17 + P0_18 Pin = 18 + P0_19 Pin = 19 + P0_20 Pin = 20 + P0_21 Pin = 21 + P0_22 Pin = 22 + P0_23 Pin = 23 + P0_24 Pin = 24 + P0_25 Pin = 25 + P0_26 Pin = 26 + P0_27 Pin = 27 + P0_28 Pin = 28 + P0_29 Pin = 29 + P0_30 Pin = 30 + P0_31 Pin = 31 +) diff --git a/emb/machine/board_nrf52840-mdk-usb-dongle.go b/emb/machine/board_nrf52840-mdk-usb-dongle.go new file mode 100644 index 00000000..c30555ca --- /dev/null +++ b/emb/machine/board_nrf52840-mdk-usb-dongle.go @@ -0,0 +1,48 @@ +//go:build nrf52840_mdk_usb_dongle + +package machine + +const HasLowFrequencyCrystal = true + +// LEDs on the nrf52840-mdk-usb-dongle +const ( + LED Pin = LED_GREEN + LED_GREEN Pin = 22 + LED_RED Pin = 23 + LED_BLUE Pin = 24 +) + +// RESET/USR button, depending on value of PSELRESET UICR register +const ( + BUTTON Pin = 18 +) + +// UART pins +const ( + UART_TX_PIN Pin = NoPin + UART_RX_PIN Pin = NoPin +) + +// I2C pins (unused) +const ( + SDA_PIN = NoPin + SCL_PIN = NoPin +) + +// SPI pins (unused) +const ( + SPI0_SCK_PIN = NoPin + SPI0_SDO_PIN = NoPin + SPI0_SDI_PIN = NoPin +) + +// USB CDC identifiers +const ( + usb_STRING_PRODUCT = "Makerdiary nRF52840 MDK USB Dongle" + usb_STRING_MANUFACTURER = "Nordic Semiconductor ASA" +) + +var ( + usb_VID uint16 = 0x1915 + usb_PID uint16 = 0xCAFE +) diff --git a/emb/machine/board_nrf52840-mdk.go b/emb/machine/board_nrf52840-mdk.go new file mode 100644 index 00000000..20edde92 --- /dev/null +++ b/emb/machine/board_nrf52840-mdk.go @@ -0,0 +1,43 @@ +//go:build nrf52840_mdk + +package machine + +const HasLowFrequencyCrystal = true + +// LEDs on the nrf52840-mdk (nRF52840 dev board) +const ( + LED_GREEN Pin = 22 + LED_RED Pin = 23 + LED_BLUE Pin = 24 + LED Pin = LED_GREEN +) + +// UART pins +const ( + UART_TX_PIN Pin = 20 + UART_RX_PIN Pin = 19 +) + +// I2C pins (unused) +const ( + SDA_PIN = NoPin + SCL_PIN = NoPin +) + +// SPI pins (unused) +const ( + SPI0_SCK_PIN = NoPin + SPI0_SDO_PIN = NoPin + SPI0_SDI_PIN = NoPin +) + +// USB CDC identifiers +const ( + usb_STRING_PRODUCT = "Makerdiary nRF52840 MDK" + usb_STRING_MANUFACTURER = "Nordic Semiconductor ASA" +) + +var ( + usb_VID uint16 = 0x1915 + usb_PID uint16 = 0xCAFE +) diff --git a/emb/machine/board_nrf52840.go b/emb/machine/board_nrf52840.go new file mode 100644 index 00000000..e7f1f893 --- /dev/null +++ b/emb/machine/board_nrf52840.go @@ -0,0 +1,55 @@ +//go:build nrf52840 || circuitplay_bluefruit || reelboard || clue || itsybitsy_nrf52840 + +package machine + +// Hardware pins +const ( + P0_00 Pin = 0 + P0_01 Pin = 1 + P0_02 Pin = 2 + P0_03 Pin = 3 + P0_04 Pin = 4 + P0_05 Pin = 5 + P0_06 Pin = 6 + P0_07 Pin = 7 + P0_08 Pin = 8 + P0_09 Pin = 9 + P0_10 Pin = 10 + P0_11 Pin = 11 + P0_12 Pin = 12 + P0_13 Pin = 13 + P0_14 Pin = 14 + P0_15 Pin = 15 + P0_16 Pin = 16 + P0_17 Pin = 17 + P0_18 Pin = 18 + P0_19 Pin = 19 + P0_20 Pin = 20 + P0_21 Pin = 21 + P0_22 Pin = 22 + P0_23 Pin = 23 + P0_24 Pin = 24 + P0_25 Pin = 25 + P0_26 Pin = 26 + P0_27 Pin = 27 + P0_28 Pin = 28 + P0_29 Pin = 29 + P0_30 Pin = 30 + P0_31 Pin = 31 + P1_00 Pin = 32 + P1_01 Pin = 33 + P1_02 Pin = 34 + P1_03 Pin = 35 + P1_04 Pin = 36 + P1_05 Pin = 37 + P1_06 Pin = 38 + P1_07 Pin = 39 + P1_08 Pin = 40 + P1_09 Pin = 41 + P1_10 Pin = 42 + P1_11 Pin = 43 + P1_12 Pin = 44 + P1_13 Pin = 45 + P1_14 Pin = 46 + P1_15 Pin = 47 +) diff --git a/emb/machine/board_nrf52840_generic.go b/emb/machine/board_nrf52840_generic.go new file mode 100644 index 00000000..1cf30e2c --- /dev/null +++ b/emb/machine/board_nrf52840_generic.go @@ -0,0 +1,25 @@ +//go:build nrf52840 && nrf52840_generic + +package machine + +var ( + LED = NoPin + SDA_PIN = NoPin + SCL_PIN = NoPin + UART_TX_PIN = NoPin + UART_RX_PIN = NoPin + SPI0_SCK_PIN = NoPin + SPI0_SDO_PIN = NoPin + SPI0_SDI_PIN = NoPin + + // https://pid.codes/org/TinyGo/ + usb_VID uint16 = 0x1209 + usb_PID uint16 = 0x9090 + + usb_STRING_MANUFACTURER = "TinyGo" + usb_STRING_PRODUCT = "nRF52840 Generic board" +) + +var ( + DefaultUART = UART0 +) diff --git a/emb/machine/board_nucleof103rb.go b/emb/machine/board_nucleof103rb.go new file mode 100644 index 00000000..af7a99e9 --- /dev/null +++ b/emb/machine/board_nucleof103rb.go @@ -0,0 +1,55 @@ +//go:build nucleof103rb + +package machine + +import ( + "device/stm32" + "runtime/interrupt" +) + +const ( + LED = LED_BUILTIN + LED_BUILTIN = LED_GREEN + LED_GREEN = PA5 +) + +const ( + BUTTON = BUTTON_USER + BUTTON_USER = PC13 +) + +// UART pins +const ( + UART_TX_PIN = PA2 + UART_RX_PIN = PA3 + UART_ALT_TX_PIN = PD5 + UART_ALT_RX_PIN = PD6 +) + +var ( + // USART2 is the hardware serial port connected to the onboard ST-LINK + // debugger to be exposed as virtual COM port over USB on Nucleo boards. + UART2 = &_UART2 + _UART2 = UART{ + Buffer: NewRingBuffer(), + Bus: stm32.USART2, + } + DefaultUART = UART2 +) + +func init() { + UART2.Interrupt = interrupt.New(stm32.IRQ_USART2, _UART2.handleInterrupt) +} + +// SPI pins +const ( + SPI0_SCK_PIN = PA5 + SPI0_SDI_PIN = PA6 + SPI0_SDO_PIN = PA7 +) + +// I2C pins +const ( + I2C0_SCL_PIN = PB6 + I2C0_SDA_PIN = PB7 +) diff --git a/emb/machine/board_nucleof722ze.go b/emb/machine/board_nucleof722ze.go new file mode 100644 index 00000000..c9a0b01b --- /dev/null +++ b/emb/machine/board_nucleof722ze.go @@ -0,0 +1,68 @@ +//go:build nucleof722ze + +package machine + +import ( + "device/stm32" + "runtime/interrupt" +) + +const ( + LED = LED_BUILTIN + LED_BUILTIN = LED_GREEN + LED_GREEN = PB0 + LED_BLUE = PB7 + LED_RED = PB14 +) + +const ( + BUTTON = BUTTON_USER + BUTTON_USER = PC13 +) + +// UART pins +const ( + // PD8 and PD9 are connected to the ST-Link Virtual Com Port (VCP) + UART_TX_PIN = PD8 + UART_RX_PIN = PD9 + UART_ALT_FN = 7 // GPIO_AF7_UART3 +) + +var ( + // USART3 is the hardware serial port connected to the onboard ST-LINK + // debugger to be exposed as virtual COM port over USB on Nucleo boards. + UART1 = &_UART1 + _UART1 = UART{ + Buffer: NewRingBuffer(), + Bus: stm32.USART3, + TxAltFuncSelector: UART_ALT_FN, + RxAltFuncSelector: UART_ALT_FN, + } + DefaultUART = UART1 +) + +func init() { + UART1.Interrupt = interrupt.New(stm32.IRQ_USART3, _UART1.handleInterrupt) +} + +// SPI pins +const ( + SPI0_SCK_PIN = PA5 + SPI0_SDI_PIN = PA6 + SPI0_SDO_PIN = PA7 +) + +// I2C pins +const ( + I2C0_SCL_PIN = PB8 + I2C0_SDA_PIN = PB9 +) + +var ( + // I2C1 is documented, alias to I2C0 as well + I2C1 = &I2C{ + Bus: stm32.I2C1, + AltFuncSelector: 4, + } + I2C0 = I2C1 +) diff --git a/emb/machine/board_nucleol031k6.go b/emb/machine/board_nucleol031k6.go new file mode 100644 index 00000000..aea92d8b --- /dev/null +++ b/emb/machine/board_nucleol031k6.go @@ -0,0 +1,98 @@ +//go:build nucleol031k6 + +package machine + +import ( + "device/stm32" + "runtime/interrupt" +) + +const ( + // Arduino Pins + A0 = PA0 // ADC_IN0 + A1 = PA1 // ADC_IN1 + A2 = PA3 // ADC_IN3 + A3 = PA4 // ADC_IN4 + A4 = PA5 // ADC_IN5 || I2C1_SDA + A5 = PA6 // ADC_IN6 || I2C1_SCL + A6 = PA7 // ADC_IN7 + A7 = PA2 // ADC_IN2 + + D0 = PA10 // USART1_TX + D1 = PA9 // USART1_RX + D2 = PA12 + D3 = PB0 // TIM2_CH3 + D4 = PB7 + D5 = PB6 // TIM16_CH1N + D6 = PB1 // TIM14_CH1 + D9 = PA8 // TIM1_CH1 + D10 = PA11 // SPI_CS || TIM1_CH4 + D11 = PB5 // SPI1_MOSI || TIM3_CH2 + D12 = PB4 // SPI1_MISO + D13 = PB3 // SPI1_SCK +) + +const ( + LED = LED_BUILTIN + LED_BUILTIN = LED_GREEN + LED_GREEN = PB3 +) + +const ( + // This board does not have a user button, so + // use first GPIO pin by default + BUTTON = PA0 +) + +const ( + // UART pins + // PA2 and PA15 are connected to the ST-Link Virtual Com Port (VCP) + UART_TX_PIN = PA2 + UART_RX_PIN = PA15 + + // SPI + SPI1_SCK_PIN = PB3 + SPI1_SDI_PIN = PB5 + SPI1_SDO_PIN = PB4 + SPI0_SCK_PIN = SPI1_SCK_PIN + SPI0_SDI_PIN = SPI1_SDI_PIN + SPI0_SDO_PIN = SPI1_SDO_PIN + + // I2C pins + // PB6 and PB7 are mapped to CN4 pin 7 and CN4 pin 8 respectively with the + // default solder bridge settings + I2C0_SCL_PIN = PB7 + I2C0_SDA_PIN = PB6 + I2C0_ALT_FUNC = 1 +) + +var ( + // USART2 is the hardware serial port connected to the onboard ST-LINK + // debugger to be exposed as virtual COM port over USB on Nucleo boards. + UART1 = &_UART1 + _UART1 = UART{ + Buffer: NewRingBuffer(), + Bus: stm32.USART2, + TxAltFuncSelector: 4, + RxAltFuncSelector: 4, + } + DefaultUART = UART1 + + // I2C1 is documented, alias to I2C0 as well + I2C1 = &I2C{ + Bus: stm32.I2C1, + AltFuncSelector: 1, + } + I2C0 = I2C1 + + // SPI + SPI0 = &SPI{ + Bus: stm32.SPI1, + AltFuncSelector: 0, + } + SPI1 = SPI0 +) + +func init() { + UART1.Interrupt = interrupt.New(stm32.IRQ_USART2, _UART1.handleInterrupt) +} diff --git a/emb/machine/board_nucleol432kc.go b/emb/machine/board_nucleol432kc.go new file mode 100644 index 00000000..3d09b6c5 --- /dev/null +++ b/emb/machine/board_nucleol432kc.go @@ -0,0 +1,100 @@ +//go:build nucleol432kc + +package machine + +import ( + "device/stm32" + "runtime/interrupt" +) + +const ( + // Arduino Pins + A0 = PA0 + A1 = PA1 + A2 = PA3 + A3 = PA4 + A4 = PA5 + A5 = PA6 + A6 = PA7 + A7 = PA2 + + D0 = PA10 + D1 = PA9 + D2 = PA12 + D3 = PB0 + D4 = PB7 + D5 = PB6 + D6 = PB1 + D7 = PC14 + D8 = PC15 + D9 = PA8 + D10 = PA11 + D11 = PB5 + D12 = PB4 + D13 = PB3 +) + +const ( + LED = LED_BUILTIN + LED_BUILTIN = LED_GREEN + LED_GREEN = PB3 +) + +const ( + // This board does not have a user button, so + // use first GPIO pin by default + BUTTON = PA0 +) + +const ( + // UART pins + // PA2 and PA15 are connected to the ST-Link Virtual Com Port (VCP) + UART_TX_PIN = PA2 + UART_RX_PIN = PA15 + + // I2C pins + // With default solder bridge settings: + // PB6 / Arduino D5 / CN3 Pin 8 is SCL + // PB7 / Arduino D4 / CN3 Pin 7 is SDA + I2C0_SCL_PIN = PB6 + I2C0_SDA_PIN = PB7 + + // SPI pins + SPI1_SCK_PIN = PB3 + SPI1_SDI_PIN = PB5 + SPI1_SDO_PIN = PB4 + SPI0_SCK_PIN = SPI1_SCK_PIN + SPI0_SDI_PIN = SPI1_SDI_PIN + SPI0_SDO_PIN = SPI1_SDO_PIN +) + +var ( + // USART2 is the hardware serial port connected to the onboard ST-LINK + // debugger to be exposed as virtual COM port over USB on Nucleo boards. + UART1 = &_UART1 + _UART1 = UART{ + Buffer: NewRingBuffer(), + Bus: stm32.USART2, + TxAltFuncSelector: 7, + RxAltFuncSelector: 3, + } + DefaultUART = UART1 + + // I2C1 is documented, alias to I2C0 as well + I2C1 = &I2C{ + Bus: stm32.I2C1, + AltFuncSelector: 4, + } + I2C0 = I2C1 + + // SPI1 is documented, alias to SPI0 as well + SPI1 = &SPI{ + Bus: stm32.SPI1, + AltFuncSelector: 5, + } + SPI0 = SPI1 +) + +func init() { + UART1.Interrupt = interrupt.New(stm32.IRQ_USART2, _UART1.handleInterrupt) +} diff --git a/emb/machine/board_nucleol476rg.go b/emb/machine/board_nucleol476rg.go new file mode 100644 index 00000000..0a173afe --- /dev/null +++ b/emb/machine/board_nucleol476rg.go @@ -0,0 +1,105 @@ +//go:build nucleol476rg + +// Schematic: https://www.st.com/resource/en/user_manual/um1724-stm32-nucleo64-boards-mb1136-stmicroelectronics.pdf +// Datasheet: https://www.st.com/resource/en/datasheet/stm32l476je.pdf + +package machine + +import ( + "device/stm32" + "runtime/interrupt" +) + +const ( + // Arduino Pins + A0 = PA0 + A1 = PA1 + A2 = PA4 + A3 = PB0 + A4 = PC1 + A5 = PC0 + + D0 = PA3 + D1 = PA2 + D2 = PA10 + D3 = PB3 + D4 = PB5 + D5 = PB4 + D6 = PB10 + D7 = PA8 + D8 = PA9 + D9 = PC7 + D10 = PB6 + D11 = PA7 + D12 = PA6 + D13 = PA5 + D14 = PB9 + D15 = PB8 +) + +// User LD2: the green LED is a user LED connected to ARDUINO® signal D13 corresponding +// to STM32 I/O PA5 (pin 21) or PB13 (pin 34) depending on the STM32 target. +const ( + LED = LED_BUILTIN + LED_BUILTIN = LED_GREEN + LED_GREEN = PA5 +) + +const ( + // This board does not have a user button, so + // use first GPIO pin by default + BUTTON = PA0 +) + +const ( + // UART pins + // PA2 and PA3 are connected to the ST-Link Virtual Com Port (VCP) + UART_TX_PIN = PA2 + UART_RX_PIN = PA3 + + // I2C pins + // With default solder bridge settings: + // PB8 / Arduino D5 / CN3 Pin 8 is SCL + // PB7 / Arduino D4 / CN3 Pin 7 is SDA + I2C0_SCL_PIN = PB8 + I2C0_SDA_PIN = PB9 + + // SPI pins + SPI1_SCK_PIN = PA5 + SPI1_SDI_PIN = PA6 + SPI1_SDO_PIN = PA7 + SPI0_SCK_PIN = SPI1_SCK_PIN + SPI0_SDI_PIN = SPI1_SDI_PIN + SPI0_SDO_PIN = SPI1_SDO_PIN +) + +var ( + // USART2 is the hardware serial port connected to the onboard ST-LINK + // debugger to be exposed as virtual COM port over USB on Nucleo boards. + UART1 = &_UART1 + _UART1 = UART{ + Buffer: NewRingBuffer(), + Bus: stm32.USART2, + TxAltFuncSelector: AF7_USART1_2_3, + RxAltFuncSelector: AF7_USART1_2_3, + } + DefaultUART = UART1 + + // I2C1 is documented, alias to I2C0 as well + I2C1 = &I2C{ + Bus: stm32.I2C1, + AltFuncSelector: AF4_I2C1_2_3, + } + I2C0 = I2C1 + + // SPI1 is documented, alias to SPI0 as well + SPI1 = &SPI{ + Bus: stm32.SPI1, + AltFuncSelector: AF5_SPI1_2, + } + SPI0 = SPI1 +) + +func init() { + UART1.Interrupt = interrupt.New(stm32.IRQ_USART2, _UART1.handleInterrupt) +} diff --git a/emb/machine/board_nucleol552ze.go b/emb/machine/board_nucleol552ze.go new file mode 100644 index 00000000..6a13d9fb --- /dev/null +++ b/emb/machine/board_nucleol552ze.go @@ -0,0 +1,60 @@ +//go:build nucleol552ze + +package machine + +import ( + "device/stm32" + "runtime/interrupt" +) + +const ( + LED_GREEN = PC7 + LED_BLUE = PB7 + LED_RED = PA9 + LED_BUILTIN = LED_GREEN + LED = LED_BUILTIN +) + +const ( + BUTTON = BUTTON_USER + BUTTON_USER = PC13 +) + +// UART pins +const ( + // PG7 and PG8 are connected to the ST-Link Virtual Com Port (VCP) + UART_TX_PIN = PG7 + UART_RX_PIN = PG8 + UART_ALT_FN = 8 // GPIO_AF8_LPUART1 +) + +var ( + // LPUART1 is the hardware serial port connected to the onboard ST-LINK + // debugger to be exposed as virtual COM port over USB on Nucleo boards. + UART1 = &_UART1 + _UART1 = UART{ + Buffer: NewRingBuffer(), + Bus: stm32.LPUART1, + TxAltFuncSelector: UART_ALT_FN, + RxAltFuncSelector: UART_ALT_FN, + } + DefaultUART = UART1 +) + +const ( + I2C0_SCL_PIN = PB8 + I2C0_SDA_PIN = PB9 +) + +var ( + // I2C1 is documented, alias to I2C0 as well + I2C1 = &I2C{ + Bus: stm32.I2C1, + AltFuncSelector: 4, + } + I2C0 = I2C1 +) + +func init() { + UART1.Interrupt = interrupt.New(stm32.IRQ_LPUART1, _UART1.handleInterrupt) +} diff --git a/emb/machine/board_nucleowl55jc.go b/emb/machine/board_nucleowl55jc.go new file mode 100644 index 00000000..a8e88dd8 --- /dev/null +++ b/emb/machine/board_nucleowl55jc.go @@ -0,0 +1,96 @@ +//go:build nucleowl55jc + +package machine + +import ( + "device/stm32" + "runtime/interrupt" +) + +const ( + LED_BLUE = PB15 + LED_GREEN = PB9 + LED_RED = PB11 + LED = LED_RED + + BTN1 = PA0 + BTN2 = PA1 + BTN3 = PC6 + BUTTON = BTN1 + + // SubGhz (SPI3) + SPI0_NSS_PIN = PA4 + SPI0_SCK_PIN = PA5 + SPI0_SDO_PIN = PA6 + SPI0_SDI_PIN = PA7 + + //MCU USART1 + UART1_TX_PIN = PB6 + UART1_RX_PIN = PB7 + + //MCU USART2 + UART2_RX_PIN = PA3 + UART2_TX_PIN = PA2 + + // DEFAULT USART + UART_RX_PIN = UART2_RX_PIN + UART_TX_PIN = UART2_TX_PIN + + // I2C1 pins + I2C1_SCL_PIN = PA9 + I2C1_SDA_PIN = PA10 + I2C1_ALT_FUNC = 4 + + // I2C2 pins + I2C2_SCL_PIN = PA12 + I2C2_SDA_PIN = PA11 + I2C2_ALT_FUNC = 4 + + // I2C0 alias for I2C1 + I2C0_SDA_PIN = I2C1_SDA_PIN + I2C0_SCL_PIN = I2C1_SCL_PIN +) + +var ( + // STM32 UART2 is connected to the embedded STLINKV3 Virtual Com Port + UART0 = &_UART0 + _UART0 = UART{ + Buffer: NewRingBuffer(), + Bus: stm32.USART2, + TxAltFuncSelector: 7, + RxAltFuncSelector: 7, + } + + // UART1 is free + UART1 = &_UART1 + _UART1 = UART{ + Buffer: NewRingBuffer(), + Bus: stm32.USART1, + TxAltFuncSelector: 7, + RxAltFuncSelector: 7, + } + + DefaultUART = UART0 + + // I2C Busses + I2C1 = &I2C{ + Bus: stm32.I2C1, + AltFuncSelector: I2C1_ALT_FUNC, + } + I2C2 = &I2C{ + Bus: stm32.I2C2, + AltFuncSelector: I2C2_ALT_FUNC, + } + I2C0 = I2C1 + + // SPI + SPI3 = &SPI{ + Bus: stm32.SPI3, + } +) + +func init() { + // Enable UARTs Interrupts + UART0.Interrupt = interrupt.New(stm32.IRQ_USART2, _UART0.handleInterrupt) + UART1.Interrupt = interrupt.New(stm32.IRQ_USART1, _UART1.handleInterrupt) +} diff --git a/emb/machine/board_p1am-100.go b/emb/machine/board_p1am-100.go new file mode 100644 index 00000000..f2a7d13f --- /dev/null +++ b/emb/machine/board_p1am-100.go @@ -0,0 +1,140 @@ +//go:build p1am_100 + +// This contains the pin mappings for the ProductivityOpen P1AM-100 board. +// +// For more information, see: https://facts-engineering.github.io/ +package machine + +// used to reset into bootloader +const resetMagicValue = 0x07738135 + +// Note: On the P1AM-100, pins D8, D9, D10, A3, and A4 are used for +// communication with the base controller. + +// GPIO Pins +const ( + D0 Pin = PA22 // PWM available + D1 Pin = PA23 // PWM available + D2 Pin = PA10 // PWM available + D3 Pin = PA11 // PWM available + D4 Pin = PB10 // PWM available + D5 Pin = PB11 // PWM available + D6 Pin = PA20 // PWM available + D7 Pin = PA21 // PWM available + + D8 Pin = PA16 // PWM available + D9 Pin = PA17 + D10 Pin = PA19 // PWM available + D11 Pin = PA08 + D12 Pin = PA09 + D13 Pin = PB23 + D14 Pin = PB22 + + // Remaining pins are shared with analog pins + D15 Pin = PA02 + + D16 Pin = PB02 + D17 Pin = PB03 + D18 Pin = PA04 // PWM available + D19 Pin = PA05 // PWM available + D20 Pin = PA06 + D21 Pin = PA07 +) + +// Analog pins +const ( + A0 Pin = PA02 // ADC/AIN[0] + A1 Pin = PB02 // ADC/AIN[10] + A2 Pin = PB03 // ADC/AIN[11] + A3 Pin = PA04 // ADC/AIN[4] + A4 Pin = PA05 // ADC/AIN[5] + A5 Pin = PA06 // ADC/AIN[6] + A6 Pin = PA07 // ADC/AIN[7] +) + +const ( + SWITCH Pin = PA28 + LED Pin = PB08 + ADC_BATTERY Pin = PB09 // ADC/AIN[3] +) + +// P1AM Base Controller +const ( + BASE_SLAVE_SELECT_PIN Pin = A3 + BASE_SLAVE_ACK_PIN Pin = A4 + BASE_ENABLE_PIN Pin = PB09 +) + +// USBCDC pins +const ( + USBCDC_DM_PIN Pin = PA24 + USBCDC_DP_PIN Pin = PA25 + USBCDC_HOST_ENABLE_PIN Pin = PA18 +) + +// UART1 pins +const ( + UART_RX_PIN Pin = PB23 // RX: SERCOM5/PAD[3] + UART_TX_PIN Pin = PB22 // TX: SERCOM5/PAD[2] +) + +// UART1 on the P1AM-100 connects to the normal TX/RX pins. +var UART1 = &sercomUSART5 + +// I2C pins +const ( + SDA_PIN Pin = PA08 // SDA: SERCOM0/PAD[0] + SCL_PIN Pin = PA09 // SCL: SERCOM0/PAD[1] +) + +// I2C on the P1AM-100. +var ( + I2C0 = sercomI2CM0 +) + +// SPI pins +const ( + SPI0_SCK_PIN Pin = D9 // SCK: SERCOM1/PAD[1] + SPI0_SDO_PIN Pin = D8 // SDO: SERCOM1/PAD[0] + SPI0_SDI_PIN Pin = D10 // SDI: SERCOM1/PAD[3] +) + +// SD card pins +const ( + SDCARD_SDI_PIN Pin = PA15 // SDI: SERCOM2/PAD[3] + SDCARD_SDO_PIN Pin = PA12 // SDO: SERCOM2/PAD[0] + SDCARD_SCK_PIN Pin = PA13 // SCK: SERCOM2/PAD[1] + SDCARD_SS_PIN Pin = PA14 // SS: as GPIO + SDCARD_CD_PIN Pin = PA27 +) + +// SPI on the P1AM-100 is used for Base Controller. +var ( + SPI0 = sercomSPIM1 + BASE_CONTROLLER_SPI = SPI0 +) + +// SPI1 is connected to the SD card slot on the P1AM-100 +var ( + SPI1 = sercomSPIM2 + SDCARD_SPI = SPI1 +) + +// I2S pins +const ( + I2S_SCK_PIN Pin = D2 + I2S_SDO_PIN Pin = A6 + I2S_SDI_PIN = NoPin + I2S_WS_PIN = D3 +) + +// USB CDC identifiers +const ( + usb_STRING_PRODUCT = "P1AM-100" + usb_STRING_MANUFACTURER = "Facts Engineering" +) + +var ( + usb_VID uint16 = 0x1354 + usb_PID uint16 = 0x4000 +) diff --git a/emb/machine/board_particle_argon.go b/emb/machine/board_particle_argon.go new file mode 100644 index 00000000..efd86133 --- /dev/null +++ b/emb/machine/board_particle_argon.go @@ -0,0 +1,106 @@ +//go:build particle_argon + +package machine + +const HasLowFrequencyCrystal = true + +// More info: https://docs.particle.io/datasheets/wi-fi/argon-datasheet/ +// Board diagram: https://docs.particle.io/assets/images/argon/argon-block-diagram.png + +// GPIOs +const ( + A0 Pin = 3 + A1 Pin = 4 + A2 Pin = 28 + A3 Pin = 29 + A4 Pin = 30 + A5 Pin = 31 + D0 Pin = 26 // Also SDA + D1 Pin = 27 // Also SCL + D2 Pin = 33 + D3 Pin = 34 + D4 Pin = 40 + D5 Pin = 42 + D6 Pin = 43 + D7 Pin = 44 // Also LED + D8 Pin = 35 + D9 Pin = 6 // Also TX + D10 Pin = 8 // Also RX + D11 Pin = 46 // Also SDI + D12 Pin = 45 // Also SDO + D13 Pin = 47 // Also SCK +) + +// LEDs +const ( + LED Pin = 44 + LED_GREEN Pin = 14 + LED_RED Pin = 13 + LED_BLUE Pin = 15 +) + +// UART +var ( + DefaultUART = UART0 +) + +const ( + UART_TX_PIN Pin = 6 + UART_RX_PIN Pin = 8 +) + +// I2C pins +const ( + SDA_PIN Pin = 26 + SCL_PIN Pin = 27 +) + +// SPI pins +const ( + SPI0_SCK_PIN Pin = 47 + SPI0_SDO_PIN Pin = 45 + SPI0_SDI_PIN Pin = 46 +) + +// Internal 4MB SPI Flash +const ( + SPI1_SCK_PIN Pin = 19 + SPI1_SDO_PIN Pin = 20 + SPI1_SDI_PIN Pin = 21 + SPI1_CS_PIN Pin = 17 + SPI1_WP_PIN Pin = 22 + SPI1_HOLD_PIN Pin = 23 +) + +// ESP32 coprocessor +const ( + ESP32_TXD_PIN Pin = 36 + ESP32_RXD_PIN Pin = 37 + ESP32_CTS_PIN Pin = 39 + ESP32_RTS_PIN Pin = 38 + ESP32_BOOT_MODE_PIN Pin = 16 + ESP32_WIFI_EN_PIN Pin = 24 + ESP32_HOST_WK_PIN Pin = 7 +) + +// Other peripherals +const ( + MODE_BUTTON_PIN Pin = 11 + CHARGE_STATUS_PIN Pin = 41 + LIPO_VOLTAGE_PIN Pin = 5 + PCB_ANTENNA_PIN Pin = 2 + EXTERNAL_UFL_PIN Pin = 25 + NFC1_PIN Pin = 9 + NFC2_PIN Pin = 10 +) + +// USB CDC identifiers +const ( + usb_STRING_PRODUCT = "Argon" + usb_STRING_MANUFACTURER = "Particle" +) + +var ( + usb_VID uint16 = 0x2B04 + usb_PID uint16 = 0xD00C +) diff --git a/emb/machine/board_particle_boron.go b/emb/machine/board_particle_boron.go new file mode 100644 index 00000000..f986060b --- /dev/null +++ b/emb/machine/board_particle_boron.go @@ -0,0 +1,109 @@ +//go:build particle_boron + +package machine + +const HasLowFrequencyCrystal = true + +// More info: https://docs.particle.io/datasheets/cellular/boron-datasheet/ +// Board diagram: https://docs.particle.io/assets/images/boron/boron-block-diagram.png + +// GPIOs +const ( + A0 Pin = 3 + A1 Pin = 4 + A2 Pin = 28 + A3 Pin = 29 + A4 Pin = 30 + A5 Pin = 31 + D0 Pin = 26 // Also SDA + D1 Pin = 27 // Also SCL + D2 Pin = 33 + D3 Pin = 34 + D4 Pin = 40 + D5 Pin = 42 + D6 Pin = 43 + D7 Pin = 44 // Also LED + D8 Pin = 35 + D9 Pin = 6 // Also TX + D10 Pin = 8 // Also RX + D11 Pin = 46 // Also SDI + D12 Pin = 45 // Also SDO + D13 Pin = 47 // Also SCK +) + +// LEDs +const ( + LED Pin = 44 + LED_GREEN Pin = 14 + LED_RED Pin = 13 + LED_BLUE Pin = 15 +) + +// UART +var ( + DefaultUART = UART0 +) + +const ( + UART_TX_PIN Pin = 6 + UART_RX_PIN Pin = 8 +) + +// I2C pins +const ( + SDA_PIN Pin = 26 + SCL_PIN Pin = 27 + + // Internal I2C with MAX17043 (Fuel gauge) and BQ24195 (Power management) chips on it + SDA1_PIN Pin = 24 + SCL1_PIN Pin = 41 + INT1_PIN Pin = 5 +) + +// SPI pins +const ( + SPI0_SCK_PIN Pin = 47 + SPI0_SDO_PIN Pin = 45 + SPI0_SDI_PIN Pin = 46 +) + +// Internal 4MB SPI Flash +const ( + SPI1_SCK_PIN Pin = 19 + SPI1_SDO_PIN Pin = 20 + SPI1_SDI_PIN Pin = 21 + SPI1_CS_PIN Pin = 17 + SPI1_WP_PIN Pin = 22 + SPI1_HOLD_PIN Pin = 23 +) + +// u-blox SARA coprocessor +const ( + SARA_TXD_PIN Pin = 37 + SARA_RXD_PIN Pin = 36 + SARA_CTS_PIN Pin = 38 + SARA_RTS_PIN Pin = 39 + SARA_RESET_PIN Pin = 12 + SARA_POWER_ON_PIN Pin = 16 + SARA_BUFF_EN_PIN Pin = 25 + SARA_VINT_PIN Pin = 2 +) + +// Other peripherals +const ( + MODE_BUTTON_PIN Pin = 11 + ANTENNA_SEL_PIN Pin = 7 // Low: chip antenna, High: External uFL + NFC1_PIN Pin = 9 + NFC2_PIN Pin = 10 +) + +// USB CDC identifiers +const ( + usb_STRING_PRODUCT = "Boron" + usb_STRING_MANUFACTURER = "Particle" +) + +var ( + usb_VID uint16 = 0x2B04 + usb_PID uint16 = 0xD00D +) diff --git a/emb/machine/board_particle_xenon.go b/emb/machine/board_particle_xenon.go new file mode 100644 index 00000000..52c2b181 --- /dev/null +++ b/emb/machine/board_particle_xenon.go @@ -0,0 +1,95 @@ +//go:build particle_xenon + +package machine + +const HasLowFrequencyCrystal = true + +// More info: https://docs.particle.io/datasheets/discontinued/xenon-datasheet/ +// Board diagram: https://docs.particle.io/assets/images/xenon/xenon-block-diagram.png + +// GPIOs +const ( + A0 Pin = 3 + A1 Pin = 4 + A2 Pin = 28 + A3 Pin = 29 + A4 Pin = 30 + A5 Pin = 31 + D0 Pin = 26 // Also SDA + D1 Pin = 27 // Also SCL + D2 Pin = 33 + D3 Pin = 34 + D4 Pin = 40 + D5 Pin = 42 + D6 Pin = 43 + D7 Pin = 44 // Also LED + D8 Pin = 35 + D9 Pin = 6 // Also TX + D10 Pin = 8 // Also RX + D11 Pin = 46 // Also SDI + D12 Pin = 45 // Also SDO + D13 Pin = 47 // Also SCK +) + +// LEDs +const ( + LED Pin = 44 + LED_GREEN Pin = 14 + LED_RED Pin = 13 + LED_BLUE Pin = 15 +) + +// UART +var ( + DefaultUART = UART0 +) + +const ( + UART_TX_PIN Pin = 6 + UART_RX_PIN Pin = 8 +) + +// I2C pins +const ( + SDA_PIN Pin = 26 + SCL_PIN Pin = 27 +) + +// SPI pins +const ( + SPI0_SCK_PIN Pin = 47 + SPI0_SDO_PIN Pin = 45 + SPI0_SDI_PIN Pin = 46 +) + +// Internal 4MB SPI Flash +const ( + SPI1_SCK_PIN Pin = 19 + SPI1_SDO_PIN Pin = 20 + SPI1_SDI_PIN Pin = 21 + SPI1_CS_PIN Pin = 17 + SPI1_WP_PIN Pin = 22 + SPI1_HOLD_PIN Pin = 23 +) + +// Other peripherals +const ( + MODE_BUTTON_PIN Pin = 11 + CHARGE_STATUS_PIN Pin = 41 + LIPO_VOLTAGE_PIN Pin = 5 + PCB_ANTENNA_PIN Pin = 24 + EXTERNAL_UFL_PIN Pin = 25 + NFC1_PIN Pin = 9 + NFC2_PIN Pin = 10 +) + +// USB CDC identifiers +const ( + usb_STRING_PRODUCT = "Xenon" + usb_STRING_MANUFACTURER = "Particle" +) + +var ( + usb_VID uint16 = 0x2B04 + usb_PID uint16 = 0xD00E +) diff --git a/emb/machine/board_pca10031.go b/emb/machine/board_pca10031.go new file mode 100644 index 00000000..3d8f34fb --- /dev/null +++ b/emb/machine/board_pca10031.go @@ -0,0 +1,41 @@ +//go:build pca10031 + +// pca10031 is a nrf51 based dongle, intended for use in wireless applications. +// +// https://infocenter.nordicsemi.com/pdf/nRF51_Dongle_UG_v1.0.pdf +package machine + +// The pca10031 has a 32kHz crystal on board. +const HasLowFrequencyCrystal = true + +// LED on the pca10031 +const ( + LED1 = LED_RED + LED2 = LED_GREEN + LED3 = LED_BLUE + LED_RED = P0_21 + LED_GREEN = P0_22 + LED_BLUE = P0_23 + LED = LED_RED +) + +var DefaultUART = UART0 + +// UART pins +const ( + UART_TX_PIN = P0_09 + UART_RX_PIN = P0_11 +) + +// I2C pins (disabled) +const ( + SDA_PIN = NoPin + SCL_PIN = NoPin +) + +// SPI pins (unused) +const ( + SPI0_SCK_PIN = NoPin + SPI0_SDO_PIN = NoPin + SPI0_SDI_PIN = NoPin +) diff --git a/emb/machine/board_pca10040.go b/emb/machine/board_pca10040.go new file mode 100644 index 00000000..a9be6ad4 --- /dev/null +++ b/emb/machine/board_pca10040.go @@ -0,0 +1,55 @@ +//go:build pca10040 + +package machine + +// The PCA10040 has a low-frequency (32kHz) crystal oscillator on board. +const HasLowFrequencyCrystal = true + +// LEDs on the PCA10040 (nRF52832 dev board) +const ( + LED1 Pin = 17 + LED2 Pin = 18 + LED3 Pin = 19 + LED4 Pin = 20 + LED Pin = LED1 +) + +// Buttons on the PCA10040 (nRF52832 dev board) +const ( + BUTTON1 Pin = 13 + BUTTON2 Pin = 14 + BUTTON3 Pin = 15 + BUTTON4 Pin = 16 + BUTTON Pin = BUTTON1 +) + +var DefaultUART = UART0 + +// UART pins for NRF52840-DK +const ( + UART_TX_PIN Pin = 6 + UART_RX_PIN Pin = 8 +) + +// ADC pins +const ( + ADC0 Pin = 3 + ADC1 Pin = 4 + ADC2 Pin = 28 + ADC3 Pin = 29 + ADC4 Pin = 30 + ADC5 Pin = 31 +) + +// I2C pins +const ( + SDA_PIN Pin = 26 + SCL_PIN Pin = 27 +) + +// SPI pins +const ( + SPI0_SCK_PIN Pin = 25 + SPI0_SDO_PIN Pin = 23 + SPI0_SDI_PIN Pin = 24 +) diff --git a/emb/machine/board_pca10056.go b/emb/machine/board_pca10056.go new file mode 100644 index 00000000..44a2d539 --- /dev/null +++ b/emb/machine/board_pca10056.go @@ -0,0 +1,65 @@ +//go:build pca10056 + +package machine + +const HasLowFrequencyCrystal = true + +// LEDs on the pca10056 +const ( + LED1 Pin = 13 + LED2 Pin = 14 + LED3 Pin = 15 + LED4 Pin = 16 + LED Pin = LED1 +) + +// Buttons on the pca10056 +const ( + BUTTON1 Pin = 11 + BUTTON2 Pin = 12 + BUTTON3 Pin = 24 + BUTTON4 Pin = 25 + BUTTON Pin = BUTTON1 +) + +var DefaultUART = UART0 + +// UART pins +const ( + UART_TX_PIN Pin = 6 + UART_RX_PIN Pin = 8 +) + +// ADC pins +const ( + ADC0 Pin = 3 + ADC1 Pin = 4 + ADC2 Pin = 28 + ADC3 Pin = 29 + ADC4 Pin = 30 + ADC5 Pin = 31 +) + +// I2C pins +const ( + SDA_PIN Pin = 26 // P0.26 + SCL_PIN Pin = 27 // P0.27 +) + +// SPI pins +const ( + SPI0_SCK_PIN Pin = 47 // P1.15 + SPI0_SDO_PIN Pin = 45 // P1.13 + SPI0_SDI_PIN Pin = 46 // P1.14 +) + +// USB CDC identifiers +const ( + usb_STRING_PRODUCT = "Nordic nRF52840DK (PCA10056)" + usb_STRING_MANUFACTURER = "Nordic Semiconductor" +) + +var ( + usb_VID uint16 = 0x239A + usb_PID uint16 = 0x8029 +) diff --git a/emb/machine/board_pca10059.go b/emb/machine/board_pca10059.go new file mode 100644 index 00000000..bd115032 --- /dev/null +++ b/emb/machine/board_pca10059.go @@ -0,0 +1,59 @@ +//go:build pca10059 + +package machine + +// The PCA10040 has a low-frequency (32kHz) crystal oscillator on board. +const HasLowFrequencyCrystal = true + +// LEDs on the PCA10059 (nRF52840 dongle) +const ( + LED1 Pin = 6 + LED2 Pin = 8 + LED3 Pin = (1 << 5) | 9 + LED4 Pin = 12 + LED Pin = LED1 +) + +// Buttons on the PCA10059 (nRF52840 dongle) +const ( + BUTTON1 Pin = (1 << 5) | 6 + BUTTON Pin = BUTTON1 +) + +// ADC pins +const ( + ADC1 Pin = 2 + ADC2 Pin = 4 + ADC3 Pin = 29 + ADC4 Pin = 31 +) + +// UART pins +const ( + UART_TX_PIN Pin = NoPin + UART_RX_PIN Pin = NoPin +) + +// I2C pins (unused) +const ( + SDA_PIN = NoPin + SCL_PIN = NoPin +) + +// SPI pins (unused) +const ( + SPI0_SCK_PIN = NoPin + SPI0_SDO_PIN = NoPin + SPI0_SDI_PIN = NoPin +) + +// USB CDC identifiers +const ( + usb_STRING_PRODUCT = "nRF52840 Dongle" + usb_STRING_MANUFACTURER = "Nordic Semiconductor ASA" +) + +var ( + usb_VID uint16 = 0x1915 + usb_PID uint16 = 0xCAFE +) diff --git a/emb/machine/board_pga2350.go b/emb/machine/board_pga2350.go new file mode 100644 index 00000000..710f14d8 --- /dev/null +++ b/emb/machine/board_pga2350.go @@ -0,0 +1,98 @@ +//go:build pga2350 + +package machine + +// PGA2350 pin definitions. +const ( + GP0 = GPIO0 + GP1 = GPIO1 + GP2 = GPIO2 + GP3 = GPIO3 + GP4 = GPIO4 + GP5 = GPIO5 + GP6 = GPIO6 + GP7 = GPIO7 + GP8 = GPIO8 + GP9 = GPIO9 + GP10 = GPIO10 + GP11 = GPIO11 + GP12 = GPIO12 + GP13 = GPIO13 + GP14 = GPIO14 + GP15 = GPIO15 + GP16 = GPIO16 + GP17 = GPIO17 + GP18 = GPIO18 + GP19 = GPIO19 + GP20 = GPIO20 + GP21 = GPIO21 + GP22 = GPIO22 + GP26 = GPIO26 + GP27 = GPIO27 + GP28 = GPIO28 + GP29 = GPIO29 + GP30 = GPIO30 // peripherals: PWM7 channel A + GP31 = GPIO31 // peripherals: PWM7 channel B + GP32 = GPIO32 // peripherals: PWM8 channel A + GP33 = GPIO33 // peripherals: PWM8 channel B + GP34 = GPIO34 // peripherals: PWM9 channel A + GP35 = GPIO35 // peripherals: PWM9 channel B + GP36 = GPIO36 // peripherals: PWM10 channel A + GP37 = GPIO37 // peripherals: PWM10 channel B + GP38 = GPIO38 // peripherals: PWM11 channel A + GP39 = GPIO39 // peripherals: PWM11 channel B + GP40 = GPIO40 // peripherals: PWM8 channel A + GP41 = GPIO41 // peripherals: PWM8 channel B + GP42 = GPIO42 // peripherals: PWM9 channel A + GP43 = GPIO43 // peripherals: PWM9 channel B + GP44 = GPIO44 // peripherals: PWM10 channel A + GP45 = GPIO45 // peripherals: PWM10 channel B + GP46 = GPIO46 // peripherals: PWM11 channel A + GP47 = GPIO47 // peripherals: PWM11 channel B + +) + +var DefaultUART = UART0 + +// Peripheral defaults. +const ( + xoscFreq = 12 // MHz + + I2C0_SDA_PIN = GP4 + I2C0_SCL_PIN = GP5 + + I2C1_SDA_PIN = GP2 + I2C1_SCL_PIN = GP3 + + // Default Serial Clock Bus 0 for SPI communications + SPI0_SCK_PIN = GPIO18 + // Default Serial Out Bus 0 for SPI communications + SPI0_SDO_PIN = GPIO19 // Tx + // Default Serial In Bus 0 for SPI communications + SPI0_SDI_PIN = GPIO16 // Rx + + // Default Serial Clock Bus 1 for SPI communications + SPI1_SCK_PIN = GPIO10 + // Default Serial Out Bus 1 for SPI communications + SPI1_SDO_PIN = GPIO11 // Tx + // Default Serial In Bus 1 for SPI communications + SPI1_SDI_PIN = GPIO12 // Rx + + UART0_TX_PIN = GPIO0 + UART0_RX_PIN = GPIO1 + UART1_TX_PIN = GPIO8 + UART1_RX_PIN = GPIO9 + UART_TX_PIN = UART0_TX_PIN + UART_RX_PIN = UART0_RX_PIN +) + +// USB identifiers +const ( + usb_STRING_PRODUCT = "PGA2350" + usb_STRING_MANUFACTURER = "Pimoroni" +) + +var ( + usb_VID uint16 = 0x2E8A + usb_PID uint16 = 0x000A +) diff --git a/emb/machine/board_pico.go b/emb/machine/board_pico.go new file mode 100644 index 00000000..efbd6ef7 --- /dev/null +++ b/emb/machine/board_pico.go @@ -0,0 +1,88 @@ +//go:build pico + +package machine + +// GPIO pins +const ( + GP0 Pin = GPIO0 + GP1 Pin = GPIO1 + GP2 Pin = GPIO2 + GP3 Pin = GPIO3 + GP4 Pin = GPIO4 + GP5 Pin = GPIO5 + GP6 Pin = GPIO6 + GP7 Pin = GPIO7 + GP8 Pin = GPIO8 + GP9 Pin = GPIO9 + GP10 Pin = GPIO10 + GP11 Pin = GPIO11 + GP12 Pin = GPIO12 + GP13 Pin = GPIO13 + GP14 Pin = GPIO14 + GP15 Pin = GPIO15 + GP16 Pin = GPIO16 + GP17 Pin = GPIO17 + GP18 Pin = GPIO18 + GP19 Pin = GPIO19 + GP20 Pin = GPIO20 + GP21 Pin = GPIO21 + GP22 Pin = GPIO22 + GP26 Pin = GPIO26 + GP27 Pin = GPIO27 + GP28 Pin = GPIO28 + + // Onboard LED + LED Pin = GPIO25 + + // Onboard crystal oscillator frequency, in MHz. + xoscFreq = 12 // MHz +) + +// I2C Default pins on Raspberry Pico. +const ( + I2C0_SDA_PIN = GP4 + I2C0_SCL_PIN = GP5 + + I2C1_SDA_PIN = GP2 + I2C1_SCL_PIN = GP3 +) + +// SPI default pins +const ( + // Default Serial Clock Bus 0 for SPI communications + SPI0_SCK_PIN = GPIO18 + // Default Serial Out Bus 0 for SPI communications + SPI0_SDO_PIN = GPIO19 // Tx + // Default Serial In Bus 0 for SPI communications + SPI0_SDI_PIN = GPIO16 // Rx + + // Default Serial Clock Bus 1 for SPI communications + SPI1_SCK_PIN = GPIO10 + // Default Serial Out Bus 1 for SPI communications + SPI1_SDO_PIN = GPIO11 // Tx + // Default Serial In Bus 1 for SPI communications + SPI1_SDI_PIN = GPIO12 // Rx +) + +// UART pins +const ( + UART0_TX_PIN = GPIO0 + UART0_RX_PIN = GPIO1 + UART1_TX_PIN = GPIO8 + UART1_RX_PIN = GPIO9 + UART_TX_PIN = UART0_TX_PIN + UART_RX_PIN = UART0_RX_PIN +) + +var DefaultUART = UART0 + +// USB identifiers +const ( + usb_STRING_PRODUCT = "Pico" + usb_STRING_MANUFACTURER = "Raspberry Pi" +) + +var ( + usb_VID uint16 = 0x2E8A + usb_PID uint16 = 0x000A +) diff --git a/emb/machine/board_pico2.go b/emb/machine/board_pico2.go new file mode 100644 index 00000000..327c542f --- /dev/null +++ b/emb/machine/board_pico2.go @@ -0,0 +1,88 @@ +//go:build pico2 + +package machine + +// GPIO pins +const ( + GP0 Pin = GPIO0 + GP1 Pin = GPIO1 + GP2 Pin = GPIO2 + GP3 Pin = GPIO3 + GP4 Pin = GPIO4 + GP5 Pin = GPIO5 + GP6 Pin = GPIO6 + GP7 Pin = GPIO7 + GP8 Pin = GPIO8 + GP9 Pin = GPIO9 + GP10 Pin = GPIO10 + GP11 Pin = GPIO11 + GP12 Pin = GPIO12 + GP13 Pin = GPIO13 + GP14 Pin = GPIO14 + GP15 Pin = GPIO15 + GP16 Pin = GPIO16 + GP17 Pin = GPIO17 + GP18 Pin = GPIO18 + GP19 Pin = GPIO19 + GP20 Pin = GPIO20 + GP21 Pin = GPIO21 + GP22 Pin = GPIO22 + GP26 Pin = GPIO26 + GP27 Pin = GPIO27 + GP28 Pin = GPIO28 + + // Onboard LED + LED Pin = GPIO25 + + // Onboard crystal oscillator frequency, in MHz. + xoscFreq = 12 // MHz +) + +// I2C Default pins on Raspberry Pico. +const ( + I2C0_SDA_PIN = GP4 + I2C0_SCL_PIN = GP5 + + I2C1_SDA_PIN = GP2 + I2C1_SCL_PIN = GP3 +) + +// SPI default pins +const ( + // Default Serial Clock Bus 0 for SPI communications + SPI0_SCK_PIN = GPIO18 + // Default Serial Out Bus 0 for SPI communications + SPI0_SDO_PIN = GPIO19 // Tx + // Default Serial In Bus 0 for SPI communications + SPI0_SDI_PIN = GPIO16 // Rx + + // Default Serial Clock Bus 1 for SPI communications + SPI1_SCK_PIN = GPIO10 + // Default Serial Out Bus 1 for SPI communications + SPI1_SDO_PIN = GPIO11 // Tx + // Default Serial In Bus 1 for SPI communications + SPI1_SDI_PIN = GPIO12 // Rx +) + +// UART pins +const ( + UART0_TX_PIN = GPIO0 + UART0_RX_PIN = GPIO1 + UART1_TX_PIN = GPIO8 + UART1_RX_PIN = GPIO9 + UART_TX_PIN = UART0_TX_PIN + UART_RX_PIN = UART0_RX_PIN +) + +var DefaultUART = UART0 + +// USB identifiers +const ( + usb_STRING_PRODUCT = "Pico2" + usb_STRING_MANUFACTURER = "Raspberry Pi" +) + +var ( + usb_VID uint16 = 0x2E8A + usb_PID uint16 = 0x000A +) diff --git a/emb/machine/board_pico_plus2.go b/emb/machine/board_pico_plus2.go new file mode 100644 index 00000000..c21c9ea2 --- /dev/null +++ b/emb/machine/board_pico_plus2.go @@ -0,0 +1,93 @@ +//go:build pico_plus2 + +package machine + +// GPIO pins +const ( + GP0 Pin = GPIO0 + GP1 Pin = GPIO1 + GP2 Pin = GPIO2 + GP3 Pin = GPIO3 + GP4 Pin = GPIO4 + GP5 Pin = GPIO5 + GP6 Pin = GPIO6 + GP7 Pin = GPIO7 + GP8 Pin = GPIO8 + GP9 Pin = GPIO9 + GP10 Pin = GPIO10 + GP11 Pin = GPIO11 + GP12 Pin = GPIO12 + GP13 Pin = GPIO13 + GP14 Pin = GPIO14 + GP15 Pin = GPIO15 + GP16 Pin = GPIO16 + GP17 Pin = GPIO17 + GP18 Pin = GPIO18 + GP19 Pin = GPIO19 + GP20 Pin = GPIO20 + GP21 Pin = GPIO21 + GP22 Pin = GPIO22 + GP26 Pin = GPIO26 + GP27 Pin = GPIO27 + GP28 Pin = GPIO28 + GP32 Pin = GPIO32 + GP33 Pin = GPIO33 + GP34 Pin = GPIO34 + GP35 Pin = GPIO35 + GP36 Pin = GPIO36 + + // Onboard LED + LED Pin = GPIO25 + + // Onboard crystal oscillator frequency, in MHz. + xoscFreq = 12 // MHz +) + +// I2C Default pins on Raspberry Pico. +const ( + I2C0_SDA_PIN = GP4 + I2C0_SCL_PIN = GP5 + + I2C1_SDA_PIN = GP2 + I2C1_SCL_PIN = GP3 +) + +// SPI default pins +const ( + // Default Serial Clock Bus 0 for SPI communications + SPI0_SCK_PIN = GPIO18 + // Default Serial Out Bus 0 for SPI communications + SPI0_SDO_PIN = GPIO19 // Tx + // Default Serial In Bus 0 for SPI communications + SPI0_SDI_PIN = GPIO16 // Rx + + // Default Serial Clock Bus 1 for SPI communications + SPI1_SCK_PIN = GPIO10 + // Default Serial Out Bus 1 for SPI communications + SPI1_SDO_PIN = GPIO11 // Tx + // Default Serial In Bus 1 for SPI communications + SPI1_SDI_PIN = GPIO12 // Rx +) + +// UART pins +const ( + UART0_TX_PIN = GPIO0 + UART0_RX_PIN = GPIO1 + UART1_TX_PIN = GPIO8 + UART1_RX_PIN = GPIO9 + UART_TX_PIN = UART0_TX_PIN + UART_RX_PIN = UART0_RX_PIN +) + +var DefaultUART = UART0 + +// USB identifiers +const ( + usb_STRING_PRODUCT = "Pico Plus2" + usb_STRING_MANUFACTURER = "Pimoroni" +) + +var ( + usb_VID uint16 = 0x2E8A + usb_PID uint16 = 0x000F +) diff --git a/emb/machine/board_pinetime.go b/emb/machine/board_pinetime.go new file mode 100644 index 00000000..f7a137bc --- /dev/null +++ b/emb/machine/board_pinetime.go @@ -0,0 +1,60 @@ +//go:build pinetime + +package machine + +// Board pins for the PineTime. +// Details: https://wiki.pine64.org/index.php/PineTime + +// The PineTime has a low-frequency (32kHz) crystal oscillator on board. +const HasLowFrequencyCrystal = true + +// LEDs simply expose the three brightness level LEDs on the PineTime. They can +// be useful for simple "hello world" style programs. +const ( + LED1 = LCD_BACKLIGHT_HIGH + LED2 = LCD_BACKLIGHT_MID + LED3 = LCD_BACKLIGHT_LOW + LED = LED1 +) + +// The PineTime doesn't have a UART output. +// Additionally, leaving the UART on results in a pretty big current drain. +const ( + UART_TX_PIN Pin = NoPin + UART_RX_PIN Pin = NoPin +) + +// SPI pins for the PineTime. +const ( + SPI0_SCK_PIN Pin = 2 + SPI0_SDO_PIN Pin = 3 + SPI0_SDI_PIN Pin = 4 +) + +// I2C pins for the PineTime. +const ( + SDA_PIN Pin = 6 + SCL_PIN Pin = 7 +) + +// Button pins. For some reason, there are two pins for the button. +const ( + BUTTON_IN Pin = 13 + BUTTON_OUT Pin = 15 +) + +// Pin for the vibrator. +const VIBRATOR_PIN Pin = 16 + +// LCD pins, using the naming convention of the official docs: +// http://files.pine64.org/doc/PineTime/PineTime%20Port%20Assignment%20rev1.0.pdf +const ( + LCD_SCK = SPI0_SCK_PIN + LCD_SDI = SPI0_SDO_PIN + LCD_RS Pin = 18 + LCD_CS Pin = 25 + LCD_RESET Pin = 26 + LCD_BACKLIGHT_LOW Pin = 14 + LCD_BACKLIGHT_MID Pin = 22 + LCD_BACKLIGHT_HIGH Pin = 23 +) diff --git a/emb/machine/board_pybadge.go b/emb/machine/board_pybadge.go new file mode 100644 index 00000000..30d44d16 --- /dev/null +++ b/emb/machine/board_pybadge.go @@ -0,0 +1,162 @@ +//go:build pybadge + +package machine + +// used to reset into bootloader +const resetMagicValue = 0xf01669ef + +// GPIO Pins +const ( + D0 = PB17 // UART0 RX/PWM available + D1 = PB16 // UART0 TX/PWM available + D2 = PB03 + D3 = PB02 + D4 = PA14 // PWM available + D5 = PA16 // PWM available + D6 = PA18 // PWM available + D7 = PB14 + D8 = PA15 // built-in neopixel + D9 = PA19 // PWM available + D10 = PA20 // can be used for PWM or UART1 TX + D11 = PA21 // can be used for PWM or UART1 RX + D12 = PA22 // PWM available + D13 = PA23 // PWM available +) + +// Analog pins +const ( + A0 = PA02 // ADC/AIN[0] + A1 = PA05 // ADC/AIN[2] + A2 = PB08 // ADC/AIN[3] + A3 = PB09 // ADC/AIN[4] + A4 = PA04 // ADC/AIN[5] + A5 = PA06 // ADC/AIN[6] + A6 = PB01 // ADC/AIN[12]/VMEAS + A7 = PB04 // ADC/AIN[6]/LIGHT + A8 = D2 // ADC/AIN[14] + A9 = D3 // ADC/AIN[15] +) + +const ( + LED = D13 + NEOPIXELS = D8 + WS2812 = D8 + + LIGHTSENSOR = A7 + + BUTTON_LATCH = PB00 + BUTTON_OUT = PB30 + BUTTON_CLK = PB31 + + TFT_DC = PB05 + TFT_CS = PB07 + TFT_RST = PA00 + TFT_LITE = PA01 + + SPEAKER_ENABLE = PA27 +) + +const ( + BUTTON_LEFT_MASK = 1 + BUTTON_UP_MASK = 2 + BUTTON_DOWN_MASK = 4 + BUTTON_RIGHT_MASK = 8 + BUTTON_SELECT_MASK = 16 + BUTTON_START_MASK = 32 + BUTTON_A_MASK = 64 + BUTTON_B_MASK = 128 +) + +// USBCDC pins +const ( + USBCDC_DM_PIN = PA24 + USBCDC_DP_PIN = PA25 +) + +// UART1 pins +const ( + UART_TX_PIN = D1 + UART_RX_PIN = D0 +) + +const ( + UART2_TX_PIN = A4 + UART2_RX_PIN = A5 +) + +var ( + UART1 = &sercomUSART5 + UART2 = &sercomUSART0 + + DefaultUART = UART1 +) + +// I2C pins +const ( + SDA_PIN = PA12 // SDA: SERCOM2/PAD[0] + SCL_PIN = PA13 // SCL: SERCOM2/PAD[1] +) + +// I2C on the ItsyBitsy M4. +var I2C0 = sercomI2CM2 + +// SPI pins +const ( + SPI0_SCK_PIN = PA17 // SCK: SERCOM1/PAD[1] + SPI0_SDO_PIN = PB23 // SDO: SERCOM1/PAD[3] + SPI0_SDI_PIN = PB22 // SDI: SERCOM1/PAD[2] +) + +// TFT SPI pins +const ( + SPI1_SCK_PIN = PB13 // SCK: SERCOM4/PAD[1] + SPI1_SDO_PIN = PB15 // SDO: SERCOM4/PAD[3] + SPI1_SDI_PIN = NoPin +) + +// SPI on the PyBadge. +var SPI0 = sercomSPIM1 + +// TFT SPI on the PyBadge. +var SPI1 = sercomSPIM4 + +// USB CDC identifiers +const ( + usb_STRING_PRODUCT = "Adafruit pyBadge M4" + usb_STRING_MANUFACTURER = "Adafruit" +) + +var ( + usb_VID uint16 = 0x239A + usb_PID uint16 = 0x8033 +) + +// NINA-W102 settings when using AirLift WiFi FeatherWing +const ( + NINA_BAUDRATE = 115200 + NINA_RESET_INVERTED = true + NINA_SOFT_FLOWCONTROL = true +) + +const ( + NINA_CS = D13 + NINA_ACK = D11 + NINA_GPIO0 = D10 + NINA_RESETN = D12 + + // pins used for the ESP32 connection do not allow hardware + // flow control, which is required. have to emulate with software. + NINA_TX = UART_TX_PIN + NINA_RX = UART_RX_PIN + NINA_CTS = NINA_ACK + NINA_RTS = NINA_GPIO0 + + NINA_SDO = SPI0_SDO_PIN + NINA_SDI = SPI0_SDI_PIN + NINA_SCK = SPI0_SCK_PIN +) + +var ( + NINA_SPI = SPI0 + UART_NINA = UART1 +) diff --git a/emb/machine/board_pygamer.go b/emb/machine/board_pygamer.go new file mode 100644 index 00000000..84a74645 --- /dev/null +++ b/emb/machine/board_pygamer.go @@ -0,0 +1,133 @@ +//go:build sam && atsamd51 && pygamer + +package machine + +// used to reset into bootloader +const resetMagicValue = 0xf01669ef + +// GPIO Pins +const ( + D0 = PB17 // UART0 RX/PWM available + D1 = PB16 // UART0 TX/PWM available + D2 = PB03 + D3 = PB02 + D4 = PA14 // PWM available + D5 = PA16 // PWM available + D6 = PA18 // PWM available + D7 = PB14 // CS for microSD card slot + D8 = PA15 // built-in neopixel + D9 = PA19 // PWM available + D10 = PA20 // can be used for PWM or UART1 TX + D11 = PA21 // can be used for PWM or UART1 RX + D12 = PA22 // PWM available + D13 = PA23 // PWM available +) + +// Analog pins +const ( + A0 = PA02 // ADC/AIN[0] + A1 = PA05 // ADC/AIN[2] + A2 = PB08 // ADC/AIN[3] + A3 = PB09 // ADC/AIN[4] + A4 = PA04 // ADC/AIN[5] + A5 = PA06 // ADC/AIN[6] + A6 = PB01 // ADC/AIN[12]/VMEAS + A7 = PB04 // ADC/AIN[6]/LIGHT + A8 = D2 // ADC/AIN[14] + A9 = D3 // ADC/AIN[15] +) + +const ( + LED = D13 + NEOPIXELS = D8 + WS2812 = D8 + + SD_CS = D7 + + LIGHTSENSOR = A7 + + BUTTON_LATCH = PB00 + BUTTON_OUT = PB30 + BUTTON_CLK = PB31 + + JOYY = PB06 + JOYX = PB07 + + TFT_DC = PB05 + TFT_CS = PB12 + TFT_RST = PA00 + TFT_LITE = PA01 + + SPEAKER_ENABLE = PA27 +) + +const ( + BUTTON_SELECT_MASK = 16 + BUTTON_START_MASK = 32 + BUTTON_A_MASK = 64 + BUTTON_B_MASK = 128 +) + +// USBCDC pins +const ( + USBCDC_DM_PIN = PA24 + USBCDC_DP_PIN = PA25 +) + +// UART1 pins +const ( + UART_TX_PIN = D1 + UART_RX_PIN = D0 +) + +// UART1 var is on SERCOM3, defined in atsamd51.go + +// UART2 pins +const ( + UART2_TX_PIN = A4 + UART2_RX_PIN = A5 +) + +// UART2 var is on SERCOM0, defined in atsamd51.go + +// I2C pins +const ( + SDA_PIN = PA12 // SDA: SERCOM2/PAD[0] + SCL_PIN = PA13 // SCL: SERCOM2/PAD[1] +) + +// I2C on the PyGamer. +var ( + I2C0 = sercomI2CM2 +) + +// SPI pins +const ( + SPI0_SCK_PIN = PA17 // SCK: SERCOM1/PAD[1] + SPI0_SDO_PIN = PB23 // SDO: SERCOM1/PAD[3] + SPI0_SDI_PIN = PB22 // SDI: SERCOM1/PAD[2] +) + +// SPI on the PyGamer. +var SPI0 = sercomSPIM1 + +// TFT SPI pins +const ( + SPI1_SCK_PIN = PB13 // SCK: SERCOM4/PAD[1] + SPI1_SDO_PIN = PB15 // SDO: SERCOM4/PAD[3] + SPI1_SDI_PIN = NoPin +) + +// TFT SPI on the PyGamer. +var SPI1 = sercomSPIM4 + +// USB CDC identifiers +const ( + usb_STRING_PRODUCT = "Adafruit pyGamer M4" + usb_STRING_MANUFACTURER = "Adafruit" +) + +var ( + usb_VID uint16 = 0x239A + usb_PID uint16 = 0x8033 +) diff --git a/emb/machine/board_pyportal.go b/emb/machine/board_pyportal.go new file mode 100644 index 00000000..98ef01d0 --- /dev/null +++ b/emb/machine/board_pyportal.go @@ -0,0 +1,165 @@ +//go:build pyportal + +package machine + +// used to reset into bootloader +const resetMagicValue = 0xf01669ef + +// GPIO Pins +const ( + D0 = PB13 // NINA_RX + D1 = PB12 // NINA_TX + D2 = PB22 // built-in neopixel + D3 = PA04 // PWM available + D4 = PA05 // PWM available + D5 = PB16 // NINA_ACK + D6 = PB15 // NINA_GPIO0 + D7 = PB17 // NINA_RESETN + D8 = PB14 // NINA_CS + D9 = PB04 // TFT_RD + D10 = PB05 // TFT_DC + D11 = PB06 // TFT_CS + D12 = PB07 // TFT_TE + D13 = PB23 // built-in LED + D24 = PA00 // TFT_RESET + D25 = PB31 // TFT_BACKLIGHT + D26 = PB09 // TFT_WR + D27 = PB02 // SDA + D28 = PB03 // SCL + D29 = PA12 // SDO + D30 = PA13 // SCK + D31 = PA14 // SDI + D32 = PB30 // SD_CS + D33 = PA01 // SD_CARD_DETECT + D34 = PA16 // LCD_DATA0 + D35 = PA17 // LCD_DATA1 + D36 = PA18 // LCD_DATA2 + D37 = PA19 // LCD_DATA3 + D38 = PA20 // LCD_DATA4 + D39 = PA21 // LCD_DATA5 + D40 = PA22 // LCD_DATA6 + D41 = PA23 // LCD_DATA7 + D42 = PB10 // QSPI + D43 = PB11 // QSPI + D44 = PA08 // QSPI + D45 = PA09 // QSPI + D46 = PA10 // QSPI + D47 = PA11 // QSPI + D50 = PA02 // speaker amplifier shutdown + D51 = PA15 // NINA_RTS + + NINA_CS = D8 + NINA_ACK = D5 + NINA_GPIO0 = D6 + NINA_RESETN = D7 + + // pins used for the ESP32 connection do not allow hardware + // flow control, which is required. have to emulate with software. + NINA_TX = D1 + NINA_RX = D0 + NINA_CTS = NINA_ACK + NINA_RTS = NINA_GPIO0 + + LCD_DATA0 = D34 + + TFT_RD = D9 + TFT_DC = D10 + TFT_CS = D11 + TFT_TE = D12 + TFT_RESET = D24 + TFT_BACKLIGHT = D25 + TFT_WR = D26 + + NEOPIXEL = D2 + WS2812 = D2 + SPK_SD = D50 +) + +// Analog pins +const ( + A0 = PA02 // ADC0/AIN[0] + A1 = D3 // ADC0/AIN[4] + A2 = PA07 // ADC0/AIN[7] + A3 = D4 // ADC0/AIN[5] + A4 = PB00 // ADC0/AIN[12] + A5 = PB01 // ADC0/AIN[13] + A6 = PA06 // ADC0/AIN[6] + A7 = PB08 // ADC1/AIN[0] + + AUDIO_OUT = A0 + LIGHT = A2 + TOUCH_YD = A4 + TOUCH_XL = A5 + TOUCH_YU = A6 + TOUCH_XR = A7 +) + +const ( + LED = D13 +) + +// USBCDC pins +const ( + USBCDC_DM_PIN = PA24 + USBCDC_DP_PIN = PA25 +) + +// UART1 aka NINA_TX/NINA_RX +const ( + UART_TX_PIN = D1 + UART_RX_PIN = D0 +) + +var ( + UART1 = &sercomUSART4 + + DefaultUART = UART1 + + UART_NINA = UART1 +) + +// NINA-W102 settings +const ( + NINA_BAUDRATE = 115200 + NINA_RESET_INVERTED = true + NINA_SOFT_FLOWCONTROL = true +) + +// I2C pins +const ( + SDA_PIN = PB02 // SDA: SERCOM2/PAD[0] + SCL_PIN = PB03 // SCL: SERCOM2/PAD[1] +) + +// I2C on the PyPortal. +var ( + I2C0 = sercomI2CM5 +) + +// SPI pins +const ( + SPI0_SCK_PIN = PA13 // SCK: SERCOM1/PAD[1] + SPI0_SDO_PIN = PA12 // SDO: SERCOM1/PAD[3] + SPI0_SDI_PIN = PA14 // SDI: SERCOM1/PAD[2] + + NINA_SDO = SPI0_SDO_PIN + NINA_SDI = SPI0_SDI_PIN + NINA_SCK = SPI0_SCK_PIN +) + +// SPI on the PyPortal. +var ( + SPI0 = sercomSPIM2 + NINA_SPI = SPI0 +) + +// USB CDC identifiers +const ( + usb_STRING_PRODUCT = "Adafruit PyPortal M4" + usb_STRING_MANUFACTURER = "Adafruit" +) + +var ( + usb_VID uint16 = 0x239A + usb_PID uint16 = 0x8035 +) diff --git a/emb/machine/board_qtpy.go b/emb/machine/board_qtpy.go new file mode 100644 index 00000000..e8a93e38 --- /dev/null +++ b/emb/machine/board_qtpy.go @@ -0,0 +1,102 @@ +//go:build sam && atsamd21 && qtpy + +package machine + +// used to reset into bootloader +const resetMagicValue = 0xf01669ef + +// GPIO Pins +const ( + D0 = PA02 // PWM available + D1 = PA03 + D2 = PA04 // PWM available + D3 = PA05 // PWM available + D4 = PA16 // PWM available + D5 = PA17 // PWM available + D6 = PA06 + D7 = PA07 + D8 = PA11 + D9 = PA09 + D10 = PA10 + D11 = PA18 + D12 = PA15 + D13 = PA27 + D14 = PA23 + D15 = PA19 + D16 = PA22 + D17 = PA08 +) + +// Analog pins +const ( + A0 = D0 + A1 = D1 + A2 = D2 + A3 = D3 + A4 = D4 +) + +const ( + NEOPIXELS = D11 + WS2812 = D11 + NEOPIXELS_POWER = D12 +) + +// USBCDC pins +const ( + USBCDC_DM_PIN = PA24 + USBCDC_DP_PIN = PA25 +) + +// UART1 pins +const ( + UART_TX_PIN = D6 + UART_RX_PIN = D7 +) + +// UART1 on the QT Py M0. +var UART1 = &sercomUSART0 + +// SPI pins +const ( + SPI0_SCK_PIN = D8 + SPI0_SDO_PIN = D10 + SPI0_SDI_PIN = D9 +) + +// SPI on the QT Py M0. +var SPI0 = sercomSPIM0 + +// I2C pins +const ( + SDA_PIN = D4 // SDA + SCL_PIN = D5 // SCL +) + +// I2C on the QT Py M0. +var ( + I2C0 = sercomI2CM1 +) + +// I2S pins +const ( + I2S_SCK_PIN = PA10 + I2S_SDO_PIN = PA08 + I2S_SDI_PIN = NoPin // TODO: figure out what this is on QT Py M0. + I2S_WS_PIN = NoPin // TODO: figure out what this is on QT Py M0. +) + +// USB CDC identifiers +const ( + usb_STRING_PRODUCT = "Adafruit QTPy M0" + usb_STRING_MANUFACTURER = "Adafruit" +) + +var ( + usb_VID uint16 = 0x239A + usb_PID uint16 = 0x80CB +) + +var ( + DefaultUART = UART1 +) diff --git a/emb/machine/board_qtpy_esp32c3.go b/emb/machine/board_qtpy_esp32c3.go new file mode 100644 index 00000000..a2e3f48e --- /dev/null +++ b/emb/machine/board_qtpy_esp32c3.go @@ -0,0 +1,60 @@ +//go:build qtpy_esp32c3 + +// This file contains the pin mappings for the Adafruit QtPy ESP32C3 boards. +// +// https://learn.adafruit.com/adafruit-qt-py-esp32-c3-wifi-dev-board/pinouts +package machine + +// Digital Pins +const ( + D0 = GPIO4 + D1 = GPIO3 + D2 = GPIO1 + D3 = GPIO0 +) + +// Analog pins (ADC1) +const ( + A0 = GPIO4 + A1 = GPIO3 + A2 = GPIO1 + A3 = GPIO0 +) + +// UART pins +const ( + RX_PIN = GPIO20 + TX_PIN = GPIO21 + + UART_RX_PIN = RX_PIN + UART_TX_PIN = TX_PIN +) + +// I2C pins +const ( + SDA_PIN = GPIO5 + SCL_PIN = GPIO6 + + I2C0_SDA_PIN = SDA_PIN + I2C0_SCL_PIN = SCL_PIN +) + +// SPI pins +const ( + SCK_PIN = GPIO10 + MI_PIN = GPIO8 + MO_PIN = GPIO7 + + SPI_SCK_PIN = SCK_PIN + SPI_SDI_PIN = MI_PIN + SPI_SDO_PIN = MO_PIN +) + +const ( + NEOPIXEL = GPIO2 + WS2812 = GPIO2 + + // also used for boot button. + // set it to be an input-with-pullup + BUTTON = GPIO9 +) diff --git a/emb/machine/board_qtpy_rp2040.go b/emb/machine/board_qtpy_rp2040.go new file mode 100644 index 00000000..3eabf0c9 --- /dev/null +++ b/emb/machine/board_qtpy_rp2040.go @@ -0,0 +1,93 @@ +//go:build qtpy_rp2040 + +package machine + +// Onboard crystal oscillator frequency, in MHz. +const xoscFreq = 12 // MHz + +// GPIO Pins +const ( + SDA = GPIO24 + SCL = GPIO25 + TX = GPIO20 + MO = GPIO3 + MOSI = GPIO3 + MI = GPIO4 + MISO = GPIO4 + SCK = GPIO6 + RX = GPIO5 + + QT_SCL1 = GPIO23 + QT_SDA1 = GPIO22 +) + +// Analog pins +const ( + A0 = GPIO29 + A1 = GPIO28 + A2 = GPIO27 + A3 = GPIO26 +) + +const ( + NEOPIXEL = GPIO12 + WS2812 = GPIO12 + NEOPIXEL_POWER = GPIO11 +) + +// I2C Pins. +const ( + I2C0_SDA_PIN = GPIO24 + I2C0_SCL_PIN = GPIO25 + + I2C1_SDA_PIN = GPIO26 + I2C1_SCL_PIN = GPIO27 + + I2C1_QT_SDA_PIN = GPIO22 + I2C1_QT_SCL_PIN = GPIO23 + + SDA_PIN = GPIO24 + SCL_PIN = GPIO25 +) + +// SPI default pins +const ( + // Default Serial Clock Bus 0 for SPI communications + SPI0_SCK_PIN = GPIO6 + // Default Serial Out Bus 0 for SPI communications + SPI0_SDO_PIN = GPIO3 // Tx + // Default Serial In Bus 0 for SPI communications + SPI0_SDI_PIN = GPIO4 // Rx + SPI0_CS = GPIO5 + + // Default Serial Clock Bus 1 for SPI communications + SPI1_SCK_PIN = GPIO26 + // Default Serial Out Bus 1 for SPI communications + SPI1_SDO_PIN = GPIO27 // Tx + // Default Serial In Bus 1 for SPI communications + SPI1_SDI_PIN = GPIO24 // Rx + SPI1_CS = GPIO25 +) + +// UART pins +const ( + UART0_TX_PIN = GPIO28 + UART0_RX_PIN = GPIO29 + UART1_TX_PIN = GPIO20 + UART1_RX_PIN = GPIO5 + UART_TX_PIN = UART0_TX_PIN + UART_RX_PIN = UART0_RX_PIN +) + +var DefaultUART = UART0 + +// USB identifiers +const ( + usb_STRING_PRODUCT = "QT Py RP2040" + usb_STRING_MANUFACTURER = "Adafruit" +) + +var ( + usb_VID uint16 = 0x239A + usb_PID uint16 = 0x80F7 +) diff --git a/emb/machine/board_rak4631.go b/emb/machine/board_rak4631.go new file mode 100644 index 00000000..c59f3717 --- /dev/null +++ b/emb/machine/board_rak4631.go @@ -0,0 +1,86 @@ +//go:build rak4631 + +package machine + +const HasLowFrequencyCrystal = true + +// Digital Pins +const ( + D0 Pin = P0_28 + D1 Pin = P0_02 +) + +// Analog pins +const ( + A0 Pin = P0_17 + A1 Pin = P1_02 + A2 Pin = P0_21 +) + +// Onboard LEDs +const ( + LED = LED2 + LED1 = P1_03 + LED2 = P1_04 +) + +// UART pins +const ( + // Default to UART1 + UART_RX_PIN = UART0_RX_PIN + UART_TX_PIN = UART0_TX_PIN + + // UART1 + UART0_RX_PIN = P0_19 + UART0_TX_PIN = P0_20 + + // UART2 + UART1_RX_PIN = P0_15 + UART1_TX_PIN = P0_16 +) + +// I2C pins +const ( + SDA_PIN = SDA1_PIN + SCL_PIN = SCL1_PIN + + SDA1_PIN = P0_13 + SCL1_PIN = P0_14 + + SDA2_PIN = P0_24 + SCL2_PIN = P0_25 +) + +// SPI pins +const ( + SPI0_SCK_PIN = P0_03 + SPI0_SDO_PIN = P0_29 + SPI0_SDI_PIN = P0_30 +) + +// Peripherals +const ( + LORA_NSS = P1_10 + LORA_SCK = P1_11 + LORA_MOSI = P1_12 + LORA_MISO = P1_13 + LORA_BUSY = P1_14 + LORA_DIO1 = P1_15 + LORA_NRESET = P1_06 + LORA_POWER = P1_05 +) + +// USB CDC identifiers +const ( + usb_STRING_PRODUCT = "WisCore RAK4631 Board" + usb_STRING_MANUFACTURER = "RAKwireless" +) + +var ( + usb_VID uint16 = 0x239a + usb_PID uint16 = 0x8029 +) + +var ( + DefaultUART = UART0 +) diff --git a/emb/machine/board_reelboard.go b/emb/machine/board_reelboard.go new file mode 100644 index 00000000..3c84070f --- /dev/null +++ b/emb/machine/board_reelboard.go @@ -0,0 +1,74 @@ +//go:build reelboard + +package machine + +const HasLowFrequencyCrystal = true + +// Pins on the reel board +const ( + LED_RED Pin = 11 + LED_GREEN Pin = 12 + LED_BLUE Pin = 41 + LED_YELLOW Pin = 13 + LED1 Pin = LED_YELLOW + LED2 Pin = LED_RED + LED3 Pin = LED_GREEN + LED4 Pin = LED_BLUE + LED Pin = LED1 + EPD_BUSY_PIN Pin = 14 + EPD_RESET_PIN Pin = 15 + EPD_DC_PIN Pin = 16 + EPD_CS_PIN Pin = 17 + EPD_SCK_PIN Pin = 19 + EPD_SDO_PIN Pin = 20 + POWER_SUPPLY_PIN Pin = 32 +) + +// User "a" button on the reel board +const ( + BUTTON Pin = 7 +) + +var DefaultUART = UART0 + +// UART pins +const ( + UART_TX_PIN Pin = 6 + UART_RX_PIN Pin = 8 +) + +// I2C pins +const ( + SDA_PIN Pin = 26 + SCL_PIN Pin = 27 +) + +// SPI pins +const ( + SPI0_SCK_PIN Pin = 47 + SPI0_SDO_PIN Pin = 45 + SPI0_SDI_PIN Pin = 46 +) + +// PowerSupplyActive enables the supply voltages for nRF52840 and peripherals (true) or only for nRF52840 (false) +// This controls the TPS610981 boost converter. You must turn the power supply active in order to use the EPD and +// other onboard peripherals. +func PowerSupplyActive(active bool) { + POWER_SUPPLY_PIN.Configure(PinConfig{Mode: PinOutput}) + if active { + POWER_SUPPLY_PIN.High() + } else { + POWER_SUPPLY_PIN.Low() + } +} + +// USB CDC identifiers +const ( + usb_STRING_PRODUCT = "PHYTEC reelboard" + usb_STRING_MANUFACTURER = "PHYTEC" +) + +var ( + usb_VID uint16 = 0x2FE3 + usb_PID uint16 = 0x100 +) diff --git a/emb/machine/board_stm32f469disco.go b/emb/machine/board_stm32f469disco.go new file mode 100644 index 00000000..8fb5cde2 --- /dev/null +++ b/emb/machine/board_stm32f469disco.go @@ -0,0 +1,79 @@ +//go:build stm32f469disco + +package machine + +import ( + "device/stm32" + "runtime/interrupt" +) + +const ( + LED = LED_BUILTIN + LED1 = LED_GREEN + LED2 = LED_ORANGE + LED3 = LED_RED + LED4 = LED_BLUE + LED_BUILTIN = LED_GREEN + LED_GREEN = PG6 + LED_ORANGE = PD4 + LED_RED = PD5 + LED_BLUE = PK3 +) + +const ( + BUTTON = PA0 +) + +// UART pins +const ( + UART_TX_PIN = PB10 + UART_RX_PIN = PB11 +) + +var ( + UART3 = &_UART3 + _UART3 = UART{ + Buffer: NewRingBuffer(), + Bus: stm32.USART3, + TxAltFuncSelector: AF7_USART1_2_3, + RxAltFuncSelector: AF7_USART1_2_3, + } + DefaultUART = UART3 +) + +// set up RX IRQ handler. Follow similar pattern for other UARTx instances +func init() { + UART3.Interrupt = interrupt.New(stm32.IRQ_USART3, _UART3.handleInterrupt) +} + +// SPI pins +const ( + SPI1_SCK_PIN = PA5 + SPI1_SDI_PIN = PA6 + SPI1_SDO_PIN = PA7 + SPI0_SCK_PIN = SPI1_SCK_PIN + SPI0_SDI_PIN = SPI1_SDI_PIN + SPI0_SDO_PIN = SPI1_SDO_PIN +) + +// Since the first interface is named SPI1, both SPI0 and SPI1 refer to SPI1. +// TODO: implement SPI2 and SPI3. +var ( + SPI0 = &SPI{ + Bus: stm32.SPI1, + AltFuncSelector: AF5_SPI1_SPI2, + } + SPI1 = SPI0 +) + +const ( + I2C0_SCL_PIN = PB6 + I2C0_SDA_PIN = PB9 +) + +var ( + I2C0 = &I2C{ + Bus: stm32.I2C1, + AltFuncSelector: AF4_I2C1_2_3, + } +) diff --git a/emb/machine/board_stm32f4disco.go b/emb/machine/board_stm32f4disco.go new file mode 100644 index 00000000..d048fcac --- /dev/null +++ b/emb/machine/board_stm32f4disco.go @@ -0,0 +1,106 @@ +//go:build stm32f4disco + +package machine + +import ( + "device/stm32" + "runtime/interrupt" +) + +const ( + LED1 = LED_GREEN + LED2 = LED_ORANGE + LED3 = LED_RED + LED4 = LED_BLUE + LED_GREEN = PD12 + LED_ORANGE = PD13 + LED_RED = PD14 + LED_BLUE = PD15 + LED = LED_BUILTIN + LED_BUILTIN = LED_GREEN +) + +const ( + BUTTON = PA0 +) + +// Analog Pins +const ( + ADC0 = PA0 + ADC1 = PA1 + ADC2 = PA2 + ADC3 = PA3 + ADC4 = PA4 + ADC5 = PA5 + ADC6 = PA6 + ADC7 = PA7 + ADC8 = PB0 + ADC9 = PB1 + ADC10 = PC0 + ADC11 = PC1 + ADC12 = PC2 + ADC13 = PC3 + ADC14 = PC4 + ADC15 = PC5 +) + +// UART pins +const ( + UART_TX_PIN = PA2 + UART_RX_PIN = PA3 +) + +var ( + UART1 = &_UART1 + _UART1 = UART{ + Buffer: NewRingBuffer(), + Bus: stm32.USART2, + TxAltFuncSelector: AF7_USART1_2_3, + RxAltFuncSelector: AF7_USART1_2_3, + } + DefaultUART = UART1 +) + +// set up RX IRQ handler. Follow similar pattern for other UARTx instances +func init() { + UART1.Interrupt = interrupt.New(stm32.IRQ_USART2, _UART1.handleInterrupt) +} + +// SPI pins +const ( + SPI1_SCK_PIN = PA5 + SPI1_SDI_PIN = PA6 + SPI1_SDO_PIN = PA7 + SPI0_SCK_PIN = SPI1_SCK_PIN + SPI0_SDI_PIN = SPI1_SDI_PIN + SPI0_SDO_PIN = SPI1_SDO_PIN +) + +// MEMs accelerometer +const ( + MEMS_ACCEL_CS = PE3 + MEMS_ACCEL_INT1 = PE0 + MEMS_ACCEL_INT2 = PE1 +) + +// Since the first interface is named SPI1, both SPI0 and SPI1 refer to SPI1. +// TODO: implement SPI2 and SPI3. +var ( + SPI0 = &SPI{ + Bus: stm32.SPI1, + AltFuncSelector: AF5_SPI1_SPI2, + } + SPI1 = SPI0 +) + +const ( + I2C0_SCL_PIN = PB6 + I2C0_SDA_PIN = PB9 +) + +var ( + I2C0 = &I2C{ + Bus: stm32.I2C1, + AltFuncSelector: AF4_I2C1_2_3, + } +) diff --git a/emb/machine/board_swan.go b/emb/machine/board_swan.go new file mode 100644 index 00000000..7502dd6a --- /dev/null +++ b/emb/machine/board_swan.go @@ -0,0 +1,62 @@ +//go:build swan + +package machine + +import ( + "device/stm32" + "runtime/interrupt" +) + +const ( + // LED on the SWAN + LED = PE2 + + // UART pins + // PA9 and PA10 are connected to the SWAN Tx/Rx + UART_TX_PIN = PA9 + UART_RX_PIN = PA10 + + // I2C pins + // PB6 is SCL + // PB7 is SDA + I2C0_SCL_PIN = PB6 + I2C0_SDA_PIN = PB7 + + // SPI pins + SPI1_SCK_PIN = PD1 + SPI1_SDI_PIN = PB14 + SPI1_SDO_PIN = PB15 + SPI0_SCK_PIN = SPI1_SCK_PIN + SPI0_SDI_PIN = SPI1_SDI_PIN + SPI0_SDO_PIN = SPI1_SDO_PIN +) + +var ( + // USART1 is connected to the TX/RX pins + UART1 = &_UART1 + _UART1 = UART{ + Buffer: NewRingBuffer(), + Bus: stm32.USART1, + TxAltFuncSelector: 7, + RxAltFuncSelector: 7, + } + DefaultUART = UART1 + + // I2C1 is documented, alias to I2C0 as well + I2C1 = &I2C{ + Bus: stm32.I2C1, + AltFuncSelector: 4, + } + I2C0 = I2C1 + + // SPI1 is documented, alias to SPI0 as well + SPI1 = &SPI{ + Bus: stm32.SPI2, + AltFuncSelector: 5, + } + SPI0 = SPI1 +) + +func init() { + UART1.Interrupt = interrupt.New(stm32.IRQ_USART1, _UART1.handleInterrupt) +} diff --git a/emb/machine/board_teensy36.go b/emb/machine/board_teensy36.go new file mode 100644 index 00000000..4859154d --- /dev/null +++ b/emb/machine/board_teensy36.go @@ -0,0 +1,103 @@ +//go:build nxp && mk66f18 && teensy36 + +package machine + +// CPUFrequency returns the frequency of the ARM core clock (180MHz) +func CPUFrequency() uint32 { return 180000000 } + +// ClockFrequency returns the frequency of the external oscillator (16MHz) +func ClockFrequency() uint32 { return 16000000 } + +// digital IO +const ( + D00 = PB16 + D01 = PB17 + D02 = PD00 + D03 = PA12 + D04 = PA13 + D05 = PD07 + D06 = PD04 + D07 = PD02 + D08 = PD03 + D09 = PC03 + D10 = PC04 + D11 = PC06 + D12 = PC07 + D13 = PC05 + D14 = PD01 + D15 = PC00 + D16 = PB00 + D17 = PB01 + D18 = PB03 + D19 = PB02 + D20 = PD05 + D21 = PD06 + D22 = PC01 + D23 = PC02 + D24 = PE26 + D25 = PA05 + D26 = PA14 + D27 = PA15 + D28 = PA16 + D29 = PB18 + D30 = PB19 + D31 = PB10 + D32 = PB11 + D33 = PE24 + D34 = PE25 + D35 = PC08 + D36 = PC09 + D37 = PC10 + D38 = PC11 + D39 = PA17 + D40 = PA28 + D41 = PA29 + D42 = PA26 + D43 = PB20 + D44 = PB22 + D45 = PB23 + D46 = PB21 + D47 = PD08 + D48 = PD09 + D49 = PB04 + D50 = PB05 + D51 = PD14 + D52 = PD13 + D53 = PD12 + D54 = PD15 + D55 = PD11 + D56 = PE10 + D57 = PE11 + D58 = PE00 + D59 = PE01 + D60 = PE02 + D61 = PE03 + D62 = PE04 + D63 = PE05 +) + +// LED on the Teensy +const LED = PC05 + +var ( + TeensyUART1 = UART0 + TeensyUART2 = UART1 + TeensyUART3 = UART2 + TeensyUART4 = UART3 + TeensyUART5 = UART4 +) + +var DefaultUART = UART0 + +const ( + defaultUART0RX = D00 + defaultUART0TX = D01 + defaultUART1RX = D09 + defaultUART1TX = D10 + defaultUART2RX = D07 + defaultUART2TX = D08 + defaultUART3RX = D31 + defaultUART3TX = D32 + defaultUART4RX = D34 + defaultUART4TX = D33 +) diff --git a/emb/machine/board_teensy40.go b/emb/machine/board_teensy40.go new file mode 100644 index 00000000..22529a8d --- /dev/null +++ b/emb/machine/board_teensy40.go @@ -0,0 +1,390 @@ +//go:build teensy40 + +package machine + +import ( + "device/nxp" + "runtime/interrupt" +) + +// Digital pins +const ( + // = Pin // [Pad]: Alt Func 0 Alt Func 1 Alt Func 2 Alt Func 3 Alt Func 4 Alt Func 5 Alt Func 6 Alt Func 7 Alt Func 8 Alt Func 9 + // = ---- ----------- --------------- --------------- --------------- -------------- -------------------- ---------- -------------------- --------------------- --------------------- ---------------- + D0 = PA3 // [AD_B0_03]: FLEXCAN2_RX XBAR1_INOUT17 LPUART6_RX USB_OTG1_OC FLEXPWM1_PWMX01 GPIO1_IO03 REF_CLK_24M LPSPI3_PCS0 ~ ~ + D1 = PA2 // [AD_B0_02]: FLEXCAN2_TX XBAR1_INOUT16 LPUART6_TX USB_OTG1_PWR FLEXPWM1_PWMX00 GPIO1_IO02 LPI2C1_HREQ LPSPI3_SDI ~ ~ + D2 = PD4 // [EMC_04]: SEMC_DATA04 FLEXPWM4_PWMA02 SAI2_TX_DATA XBAR1_INOUT06 FLEXIO1_FLEXIO04 GPIO4_IO04 ~ ~ ~ ~ + D3 = PD5 // [EMC_05]: SEMC_DATA05 FLEXPWM4_PWMB02 SAI2_TX_SYNC XBAR1_INOUT07 FLEXIO1_FLEXIO05 GPIO4_IO05 ~ ~ ~ ~ + D4 = PD6 // [EMC_06]: SEMC_DATA06 FLEXPWM2_PWMA00 SAI2_TX_BCLK XBAR1_INOUT08 FLEXIO1_FLEXIO06 GPIO4_IO06 ~ ~ ~ ~ + D5 = PD8 // [EMC_08]: SEMC_DM00 FLEXPWM2_PWMA01 SAI2_RX_DATA XBAR1_INOUT17 FLEXIO1_FLEXIO08 GPIO4_IO08 ~ ~ ~ ~ + D6 = PB10 // [B0_10]: LCD_DATA06 QTIMER4_TIMER1 FLEXPWM2_PWMA02 SAI1_TX_DATA03 FLEXIO2_FLEXIO10 GPIO2_IO10 SRC_BOOT_CFG06 ENET2_CRS ~ ~ + D7 = PB17 // [B1_01]: LCD_DATA13 XBAR1_INOUT15 LPUART4_RX SAI1_TX_DATA00 FLEXIO2_FLEXIO17 GPIO2_IO17 FLEXPWM1_PWMB03 ENET2_RDATA00 FLEXIO3_FLEXIO17 ~ + D8 = PB16 // [B1_00]: LCD_DATA12 XBAR1_INOUT14 LPUART4_TX SAI1_RX_DATA00 FLEXIO2_FLEXIO16 GPIO2_IO16 FLEXPWM1_PWMA03 ENET2_RX_ER FLEXIO3_FLEXIO16 ~ + D9 = PB11 // [B0_11]: LCD_DATA07 QTIMER4_TIMER2 FLEXPWM2_PWMB02 SAI1_TX_DATA02 FLEXIO2_FLEXIO11 GPIO2_IO11 SRC_BOOT_CFG07 ENET2_COL ~ ~ + D10 = PB0 // [B0_00]: LCD_CLK QTIMER1_TIMER0 MQS_RIGHT LPSPI4_PCS0 FLEXIO2_FLEXIO00 GPIO2_IO00 SEMC_CSX01 ENET2_MDC ~ ~ + D11 = PB2 // [B0_02]: LCD_HSYNC QTIMER1_TIMER2 FLEXCAN1_TX LPSPI4_SDO FLEXIO2_FLEXIO02 GPIO2_IO02 SEMC_CSX03 ENET2_1588_EVENT0_OUT ~ ~ + D12 = PB1 // [B0_01]: LCD_ENABLE QTIMER1_TIMER1 MQS_LEFT LPSPI4_SDI FLEXIO2_FLEXIO01 GPIO2_IO01 SEMC_CSX02 ENET2_MDIO ~ ~ + D13 = PB3 // [B0_03]: LCD_VSYNC QTIMER2_TIMER0 FLEXCAN1_RX LPSPI4_SCK FLEXIO2_FLEXIO03 GPIO2_IO03 WDOG2_RESET_B_DEB ENET2_1588_EVENT0_IN ~ ~ + D14 = PA18 // [AD_B1_02]: USB_OTG1_ID QTIMER3_TIMER2 LPUART2_TX SPDIF_OUT ENET_1588_EVENT2_OUT GPIO1_IO18 USDHC1_CD_B KPP_ROW06 GPT2_CLK FLEXIO3_FLEXIO02 + D15 = PA19 // [AD_B1_03]: USB_OTG1_OC QTIMER3_TIMER3 LPUART2_RX SPDIF_IN ENET_1588_EVENT2_IN GPIO1_IO19 USDHC2_CD_B KPP_COL06 GPT2_CAPTURE1 FLEXIO3_FLEXIO03 + D16 = PA23 // [AD_B1_07]: FLEXSPIB_DATA00 LPI2C3_SCL LPUART3_RX SPDIF_EXT_CLK CSI_HSYNC GPIO1_IO23 USDHC2_DATA3 KPP_COL04 GPT2_COMPARE3 FLEXIO3_FLEXIO07 + D17 = PA22 // [AD_B1_06]: FLEXSPIB_DATA01 LPI2C3_SDA LPUART3_TX SPDIF_LOCK CSI_VSYNC GPIO1_IO22 USDHC2_DATA2 KPP_ROW04 GPT2_COMPARE2 FLEXIO3_FLEXIO06 + D18 = PA17 // [AD_B1_01]: USB_OTG1_PWR QTIMER3_TIMER1 LPUART2_RTS_B LPI2C1_SDA CCM_PMIC_READY GPIO1_IO17 USDHC1_VSELECT KPP_COL07 ENET2_1588_EVENT0_IN FLEXIO3_FLEXIO01 + D19 = PA16 // [AD_B1_00]: USB_OTG2_ID QTIMER3_TIMER0 LPUART2_CTS_B LPI2C1_SCL WDOG1_B GPIO1_IO16 USDHC1_WP KPP_ROW07 ENET2_1588_EVENT0_OUT FLEXIO3_FLEXIO00 + D20 = PA26 // [AD_B1_10]: FLEXSPIA_DATA03 WDOG1_B LPUART8_TX SAI1_RX_SYNC CSI_DATA07 GPIO1_IO26 USDHC2_WP KPP_ROW02 ENET2_1588_EVENT1_OUT FLEXIO3_FLEXIO10 + D21 = PA27 // [AD_B1_11]: FLEXSPIA_DATA02 EWM_OUT_B LPUART8_RX SAI1_RX_BCLK CSI_DATA06 GPIO1_IO27 USDHC2_RESET_B KPP_COL02 ENET2_1588_EVENT1_IN FLEXIO3_FLEXIO11 + D22 = PA24 // [AD_B1_08]: FLEXSPIA_SS1_B FLEXPWM4_PWMA00 FLEXCAN1_TX CCM_PMIC_READY CSI_DATA09 GPIO1_IO24 USDHC2_CMD KPP_ROW03 FLEXIO3_FLEXIO08 ~ + D23 = PA25 // [AD_B1_09]: FLEXSPIA_DQS FLEXPWM4_PWMA01 FLEXCAN1_RX SAI1_MCLK CSI_DATA08 GPIO1_IO25 USDHC2_CLK KPP_COL03 FLEXIO3_FLEXIO09 ~ + D24 = PA12 // [AD_B0_12]: LPI2C4_SCL CCM_PMIC_READY LPUART1_TX WDOG2_WDOG_B FLEXPWM1_PWMX02 GPIO1_IO12 ENET_1588_EVENT1_OUT NMI_GLUE_NMI ~ ~ + D25 = PA13 // [AD_B0_13]: LPI2C4_SDA GPT1_CLK LPUART1_RX EWM_OUT_B FLEXPWM1_PWMX03 GPIO1_IO13 ENET_1588_EVENT1_IN REF_CLK_24M ~ ~ + D26 = PA30 // [AD_B1_14]: FLEXSPIA_SCLK ACMP_OUT02 LPSPI3_SDO SAI1_TX_BCLK CSI_DATA03 GPIO1_IO30 USDHC2_DATA6 KPP_ROW00 ENET2_1588_EVENT3_OUT FLEXIO3_FLEXIO14 + D27 = PA31 // [AD_B1_15]: FLEXSPIA_SS0_B ACMP_OUT03 LPSPI3_SCK SAI1_TX_SYNC CSI_DATA02 GPIO1_IO31 USDHC2_DATA7 KPP_COL00 ENET2_1588_EVENT3_IN FLEXIO3_FLEXIO15 + D28 = PC18 // [EMC_32]: SEMC_DATA10 FLEXPWM3_PWMB01 LPUART7_RX CCM_PMIC_RDY CSI_DATA21 GPIO3_IO18 ENET2_TX_EN ~ ~ ~ + D29 = PD31 // [EMC_31]: SEMC_DATA09 FLEXPWM3_PWMA01 LPUART7_TX LPSPI1_PCS1 CSI_DATA22 GPIO4_IO31 ENET2_TDATA01 ~ ~ ~ + D30 = PC23 // [EMC_37]: SEMC_DATA15 XBAR1_IN23 GPT1_COMPARE3 SAI3_MCLK CSI_DATA16 GPIO3_IO23 USDHC2_WP ENET2_RX_EN FLEXCAN3_RX ~ + D31 = PC22 // [EMC_36]: SEMC_DATA14 XBAR1_IN22 GPT1_COMPARE2 SAI3_TX_DATA CSI_DATA17 GPIO3_IO22 USDHC1_WP ENET2_RDATA01 FLEXCAN3_TX ~ + D32 = PB12 // [B0_12]: LCD_DATA08 XBAR1_INOUT10 ARM_TRACE_CLK SAI1_TX_DATA01 FLEXIO2_FLEXIO12 GPIO2_IO12 SRC_BOOT_CFG08 ENET2_TDATA00 ~ ~ + D33 = PD7 // [EMC_07]: SEMC_DATA07 FLEXPWM2_PWMB00 SAI2_MCLK XBAR1_INOUT09 FLEXIO1_FLEXIO07 GPIO4_IO07 ~ ~ ~ ~ + D34 = PC15 // [SD_B0_03]: USDHC1_DATA1 FLEXPWM1_PWMB01 LPUART8_RTS_B XBAR1_INOUT07 LPSPI1_SDI GPIO3_IO15 ENET2_RDATA00 SEMC_CLK6 ~ ~ + D35 = PC14 // [SD_B0_02]: USDHC1_DATA0 FLEXPWM1_PWMA01 LPUART8_CTS_B XBAR1_INOUT06 LPSPI1_SDO GPIO3_IO14 ENET2_RX_ER SEMC_CLK5 ~ ~ + D36 = PC13 // [SD_B0_01]: USDHC1_CLK FLEXPWM1_PWMB00 LPI2C3_SDA XBAR1_INOUT05 LPSPI1_PCS0 GPIO3_IO13 FLEXSPIB_SS1_B ENET2_TX_CLK ENET2_REF_CLK2 ~ + D37 = PC12 // [SD_B0_00]: USDHC1_CMD FLEXPWM1_PWMA00 LPI2C3_SCL XBAR1_INOUT04 LPSPI1_SCK GPIO3_IO12 FLEXSPIA_SS1_B ENET2_TX_EN SEMC_DQS4 ~ + D38 = PC17 // [SD_B0_05]: USDHC1_DATA3 FLEXPWM1_PWMB02 LPUART8_RX XBAR1_INOUT09 FLEXSPIB_DQS GPIO3_IO17 CCM_CLKO2 ENET2_RX_EN ~ ~ + D39 = PC16 // [SD_B0_04]: USDHC1_DATA2 FLEXPWM1_PWMA02 LPUART8_TX XBAR1_INOUT08 FLEXSPIB_SS0_B GPIO3_IO16 CCM_CLKO1 ENET2_RDATA01 ~ ~ +) + +// Analog pins +const ( + // = Pin // Dig | [Pad] {ADC1/ADC2} + A0 = PA18 // D14 | [AD_B1_02] { 7 / 7 } + A1 = PA19 // D15 | [AD_B1_03] { 8 / 8 } + A2 = PA23 // D16 | [AD_B1_07] { 12 / 12 } + A3 = PA22 // D17 | [AD_B1_06] { 11 / 11 } + A4 = PA17 // D18 | [AD_B1_01] { 6 / 6 } + A5 = PA16 // D19 | [AD_B1_00] { 5 / 5 } + A6 = PA26 // D20 | [AD_B1_10] { 15 / 15 } + A7 = PA27 // D21 | [AD_B1_11] { 0 / 0 } + A8 = PA24 // D22 | [AD_B1_08] { 13 / 13 } + A9 = PA25 // D23 | [AD_B1_09] { 14 / 14 } + A10 = PA12 // D24 | [AD_B0_12] { 1 / - } + A11 = PA13 // D25 | [AD_B0_13] { 2 / - } + A12 = PA30 // D26 | [AD_B1_14] { - / 3 } + A13 = PA31 // D27 | [AD_B1_15] { - / 4 } +) + +// Default peripheral pins +const ( + LED = D13 + + UART_RX_PIN = UART1_RX_PIN // D0 + UART_TX_PIN = UART1_TX_PIN // D1 + + SPI_SDI_PIN = SPI1_SDI_PIN // D12 + SPI_SDO_PIN = SPI1_SDO_PIN // D11 + SPI_SCK_PIN = SPI1_SCK_PIN // D13 + SPI_CS_PIN = SPI1_CS_PIN // D10 + + I2C_SDA_PIN = I2C1_SDA_PIN // D18/A4 + I2C_SCL_PIN = I2C1_SCL_PIN // D19/A5 +) + +// Default peripherals +var ( + DefaultUART = UART1 +) + +func init() { + // register any interrupt handlers for this board's peripherals + _UART1.Interrupt = interrupt.New(nxp.IRQ_LPUART6, _UART1.handleInterrupt) + _UART2.Interrupt = interrupt.New(nxp.IRQ_LPUART4, _UART2.handleInterrupt) + _UART3.Interrupt = interrupt.New(nxp.IRQ_LPUART2, _UART3.handleInterrupt) + _UART4.Interrupt = interrupt.New(nxp.IRQ_LPUART3, _UART4.handleInterrupt) + _UART5.Interrupt = interrupt.New(nxp.IRQ_LPUART8, _UART5.handleInterrupt) + _UART6.Interrupt = interrupt.New(nxp.IRQ_LPUART1, _UART6.handleInterrupt) + _UART7.Interrupt = interrupt.New(nxp.IRQ_LPUART7, _UART7.handleInterrupt) +} + +// #=====================================================# +// | UART | +// #===========#===========#=============#===============# +// | Interface | Hardware | Clock(Freq) | RX/TX : Alt | +// #===========#===========#=============#=========-=====# +// | UART1 | LPUART6 | OSC(24 MHz) | D0/D1 : 2/2 | +// | UART2 | LPUART4 | OSC(24 MHz) | D7/D8 : 2/2 | +// | UART3 | LPUART2 | OSC(24 MHz) | D15/D14 : 2/2 | +// | UART4 | LPUART3 | OSC(24 MHz) | D16/D17 : 2/2 | +// | UART5 | LPUART8 | OSC(24 MHz) | D21/D20 : 2/2 | +// | UART6 | LPUART1 | OSC(24 MHz) | D25/D24 : 2/2 | +// | UART7 | LPUART7 | OSC(24 MHz) | D28/D29 : 2/2 | +// #===========#===========#=============#=========-=====# +const ( + UART1_RX_PIN = D0 + UART1_TX_PIN = D1 + + UART2_RX_PIN = D7 + UART2_TX_PIN = D8 + + UART3_RX_PIN = D15 + UART3_TX_PIN = D14 + + UART4_RX_PIN = D16 + UART4_TX_PIN = D17 + + UART5_RX_PIN = D21 + UART5_TX_PIN = D20 + + UART6_RX_PIN = D25 + UART6_TX_PIN = D24 + + UART7_RX_PIN = D28 + UART7_TX_PIN = D29 +) + +var ( + UART1 = &_UART1 + _UART1 = UART{ + Bus: nxp.LPUART6, + Buffer: NewRingBuffer(), + txBuffer: NewRingBuffer(), + muxRX: muxSelect{ // D0 (PA3 [AD_B0_03]) + mux: nxp.IOMUXC_LPUART6_RX_SELECT_INPUT_DAISY_GPIO_AD_B0_03_ALT2, + sel: &nxp.IOMUXC.LPUART6_RX_SELECT_INPUT, + }, + muxTX: muxSelect{ // D1 (PA2 [AD_B0_02]) + mux: nxp.IOMUXC_LPUART6_TX_SELECT_INPUT_DAISY_GPIO_AD_B0_02_ALT2, + sel: &nxp.IOMUXC.LPUART6_TX_SELECT_INPUT, + }, + } + UART2 = &_UART2 + _UART2 = UART{ + Bus: nxp.LPUART4, + Buffer: NewRingBuffer(), + txBuffer: NewRingBuffer(), + muxRX: muxSelect{ // D7 (PB17 [B1_01]) + mux: nxp.IOMUXC_LPUART4_RX_SELECT_INPUT_DAISY_GPIO_B1_01_ALT2, + sel: &nxp.IOMUXC.LPUART4_RX_SELECT_INPUT, + }, + muxTX: muxSelect{ // D8 (PB16 [B1_00]) + mux: nxp.IOMUXC_LPUART4_TX_SELECT_INPUT_DAISY_GPIO_B1_00_ALT2, + sel: &nxp.IOMUXC.LPUART4_TX_SELECT_INPUT, + }, + } + UART3 = &_UART3 + _UART3 = UART{ + Bus: nxp.LPUART2, + Buffer: NewRingBuffer(), + txBuffer: NewRingBuffer(), + muxRX: muxSelect{ // D15 (PA19 [AD_B1_03]) + mux: nxp.IOMUXC_LPUART2_RX_SELECT_INPUT_DAISY_GPIO_AD_B1_03_ALT2, + sel: &nxp.IOMUXC.LPUART2_RX_SELECT_INPUT, + }, + muxTX: muxSelect{ // D14 (PA18 [AD_B1_02]) + mux: nxp.IOMUXC_LPUART2_TX_SELECT_INPUT_DAISY_GPIO_AD_B1_02_ALT2, + sel: &nxp.IOMUXC.LPUART2_TX_SELECT_INPUT, + }, + } + UART4 = &_UART4 + _UART4 = UART{ + Bus: nxp.LPUART3, + Buffer: NewRingBuffer(), + txBuffer: NewRingBuffer(), + muxRX: muxSelect{ // D16 (PA23 [AD_B1_07]) + mux: nxp.IOMUXC_LPUART3_RX_SELECT_INPUT_DAISY_GPIO_AD_B1_07_ALT2, + sel: &nxp.IOMUXC.LPUART3_RX_SELECT_INPUT, + }, + muxTX: muxSelect{ // D17 (PA22 [AD_B1_06]) + mux: nxp.IOMUXC_LPUART3_TX_SELECT_INPUT_DAISY_GPIO_AD_B1_06_ALT2, + sel: &nxp.IOMUXC.LPUART3_TX_SELECT_INPUT, + }, + } + UART5 = &_UART5 + _UART5 = UART{ + Bus: nxp.LPUART8, + Buffer: NewRingBuffer(), + txBuffer: NewRingBuffer(), + muxRX: muxSelect{ // D21 (PA27 [AD_B1_11]) + mux: nxp.IOMUXC_LPUART8_RX_SELECT_INPUT_DAISY_GPIO_AD_B1_11_ALT2, + sel: &nxp.IOMUXC.LPUART8_RX_SELECT_INPUT, + }, + muxTX: muxSelect{ // D20 (PA26 [AD_B1_10]) + mux: nxp.IOMUXC_LPUART8_TX_SELECT_INPUT_DAISY_GPIO_AD_B1_10_ALT2, + sel: &nxp.IOMUXC.LPUART8_TX_SELECT_INPUT, + }, + } + UART6 = &_UART6 + _UART6 = UART{ + Bus: nxp.LPUART1, + Buffer: NewRingBuffer(), + txBuffer: NewRingBuffer(), + // LPUART1 not connected via IOMUXC + // RX: D24 (PA12 [AD_B0_12]) + // TX: D25 (PA13 [AD_B0_13]) + } + UART7 = &_UART7 + _UART7 = UART{ + Bus: nxp.LPUART7, + Buffer: NewRingBuffer(), + txBuffer: NewRingBuffer(), + muxRX: muxSelect{ // D28 (PC18 [EMC_32]) + mux: nxp.IOMUXC_LPUART7_RX_SELECT_INPUT_DAISY_GPIO_EMC_32_ALT2, + sel: &nxp.IOMUXC.LPUART7_RX_SELECT_INPUT, + }, + muxTX: muxSelect{ // D29 (PD31 [EMC_31]) + mux: nxp.IOMUXC_LPUART7_TX_SELECT_INPUT_DAISY_GPIO_EMC_31_ALT2, + sel: &nxp.IOMUXC.LPUART7_TX_SELECT_INPUT, + }, + } +) + +// #==================================================================# +// | SPI | +// #===========#==========#===============#===========================# +// | Interface | Hardware | Clock(Freq) | SDI/SDO/SCK/CS : Alt | +// #===========#==========#===============#=================-=========# +// | SPI1 | LPSPI4 | PLL2(132 MHz) | D12/D11/D13/D10 : 3/3/3/3 | +// | SPI2 | LPSPI3 | PLL2(132 MHz) | D1/D26/D27/D0 : 7/2/2/7 | +// | SPI3 | LPSPI1 | PLL2(132 MHz) | D34/D35/D37/D36 : 4/4/4/4 | +// #===========#==========#===============#=================-=========# +const ( + SPI1_SDI_PIN = D12 + SPI1_SDO_PIN = D11 + SPI1_SCK_PIN = D13 + SPI1_CS_PIN = D10 + + SPI2_SDI_PIN = D1 + SPI2_SDO_PIN = D26 + SPI2_SCK_PIN = D27 + SPI2_CS_PIN = D0 + + SPI3_SDI_PIN = D34 + SPI3_SDO_PIN = D35 + SPI3_SCK_PIN = D37 + SPI3_CS_PIN = D36 +) + +var ( + SPI0 = SPI1 // SPI0 is an alias of SPI1 (LPSPI4) + SPI1 = &SPI{ + Bus: nxp.LPSPI4, + muxSDI: muxSelect{ // D12 (PB1 [B0_01]) + mux: nxp.IOMUXC_LPSPI4_SDI_SELECT_INPUT_DAISY_GPIO_B0_01_ALT3, + sel: &nxp.IOMUXC.LPSPI4_SDI_SELECT_INPUT, + }, + muxSDO: muxSelect{ // D11 (PB2 [B0_02]) + mux: nxp.IOMUXC_LPSPI4_SDO_SELECT_INPUT_DAISY_GPIO_B0_02_ALT3, + sel: &nxp.IOMUXC.LPSPI4_SDO_SELECT_INPUT, + }, + muxSCK: muxSelect{ // D13 (PB3 [B0_03]) + mux: nxp.IOMUXC_LPSPI4_SCK_SELECT_INPUT_DAISY_GPIO_B0_03_ALT3, + sel: &nxp.IOMUXC.LPSPI4_SCK_SELECT_INPUT, + }, + muxCS: muxSelect{ // D10 (PB0 [B0_00]) + mux: nxp.IOMUXC_LPSPI4_PCS0_SELECT_INPUT_DAISY_GPIO_B0_00_ALT3, + sel: &nxp.IOMUXC.LPSPI4_PCS0_SELECT_INPUT, + }, + } + SPI2 = &SPI{ + Bus: nxp.LPSPI3, + muxSDI: muxSelect{ // D1 (PA2 [AD_B0_02]) + mux: nxp.IOMUXC_LPSPI3_SDI_SELECT_INPUT_DAISY_GPIO_AD_B0_02_ALT7, + sel: &nxp.IOMUXC.LPSPI3_SDI_SELECT_INPUT, + }, + muxSDO: muxSelect{ // D26 (PA30 [AD_B1_14]) + mux: nxp.IOMUXC_LPSPI3_SDO_SELECT_INPUT_DAISY_GPIO_AD_B1_14_ALT2, + sel: &nxp.IOMUXC.LPSPI3_SDO_SELECT_INPUT, + }, + muxSCK: muxSelect{ // D27 (PA31 [AD_B1_15]) + mux: nxp.IOMUXC_LPSPI3_SCK_SELECT_INPUT_DAISY_GPIO_AD_B1_15, + sel: &nxp.IOMUXC.LPSPI3_SCK_SELECT_INPUT, + }, + muxCS: muxSelect{ // D0 (PA3 [AD_B0_03]) + mux: nxp.IOMUXC_LPSPI3_PCS0_SELECT_INPUT_DAISY_GPIO_AD_B0_03_ALT7, + sel: &nxp.IOMUXC.LPSPI3_PCS0_SELECT_INPUT, + }, + } + SPI3 = &SPI{ + Bus: nxp.LPSPI1, + muxSDI: muxSelect{ // D34 (PC15 [SD_B0_03]) + mux: nxp.IOMUXC_LPSPI1_SDI_SELECT_INPUT_DAISY_GPIO_SD_B0_03_ALT4, + sel: &nxp.IOMUXC.LPSPI1_SDI_SELECT_INPUT, + }, + muxSDO: muxSelect{ // D35 (PC14 [SD_B0_02]) + mux: nxp.IOMUXC_LPSPI1_SDO_SELECT_INPUT_DAISY_GPIO_SD_B0_02_ALT4, + sel: &nxp.IOMUXC.LPSPI1_SDO_SELECT_INPUT, + }, + muxSCK: muxSelect{ // D37 (PC12 [SD_B0_00]) + mux: nxp.IOMUXC_LPSPI1_SCK_SELECT_INPUT_DAISY_GPIO_SD_B0_00_ALT4, + sel: &nxp.IOMUXC.LPSPI1_SCK_SELECT_INPUT, + }, + muxCS: muxSelect{ // D36 (PC13 [SD_B0_01]) + mux: nxp.IOMUXC_LPSPI1_PCS0_SELECT_INPUT_DAISY_GPIO_SD_B0_01_ALT4, + sel: &nxp.IOMUXC.LPSPI1_PCS0_SELECT_INPUT, + }, + } +) + +// #====================================================# +// | I2C | +// #===========#==========#=============#===============# +// | Interface | Hardware | Clock(Freq) | SDA/SCL : Alt | +// #===========#==========#=============#=========-=====# +// | I2C1 | LPI2C1 | OSC(24 MHz) | D18/D19 : 3/3 | +// | I2C2 | LPI2C3 | OSC(24 MHz) | D17/D16 : 1/1 | +// | I2C3 | LPI2C4 | OSC(24 MHz) | D25/D24 : 0/0 | +// #===========#==========#=============#=========-=====# +const ( + I2C1_SDA_PIN = D18 + I2C1_SCL_PIN = D19 + + I2C2_SDA_PIN = D17 + I2C2_SCL_PIN = D16 + + I2C3_SDA_PIN = D25 + I2C3_SCL_PIN = D24 +) + +var ( + I2C0 = I2C1 // I2C0 is an alias for I2C1 (LPI2C1) + I2C1 = &_I2C1 + _I2C1 = I2C{ + Bus: nxp.LPI2C1, + sda: I2C1_SDA_PIN, // D18 (PA17 [AD_B1_01]) + scl: I2C1_SCL_PIN, // D19 (PA16 [AD_B1_00]) + muxSDA: muxSelect{ + mux: nxp.IOMUXC_LPI2C1_SDA_SELECT_INPUT_DAISY_GPIO_AD_B1_01_ALT3, + sel: &nxp.IOMUXC.LPI2C1_SDA_SELECT_INPUT, + }, + muxSCL: muxSelect{ + mux: nxp.IOMUXC_LPI2C1_SCL_SELECT_INPUT_DAISY_GPIO_AD_B1_00_ALT3, + sel: &nxp.IOMUXC.LPI2C1_SCL_SELECT_INPUT, + }, + } + I2C2 = &_I2C2 + _I2C2 = I2C{ + Bus: nxp.LPI2C3, + sda: I2C2_SDA_PIN, // D17 (PA22 [AD_B1_06]) + scl: I2C2_SCL_PIN, // D16 (PA23 [AD_B1_07]) + muxSDA: muxSelect{ + mux: nxp.IOMUXC_LPI2C3_SDA_SELECT_INPUT_DAISY_GPIO_AD_B1_06_ALT1, + sel: &nxp.IOMUXC.LPI2C3_SDA_SELECT_INPUT, + }, + muxSCL: muxSelect{ + mux: nxp.IOMUXC_LPI2C3_SCL_SELECT_INPUT_DAISY_GPIO_AD_B1_07_ALT1, + sel: &nxp.IOMUXC.LPI2C3_SCL_SELECT_INPUT, + }, + } + I2C3 = &_I2C3 + _I2C3 = I2C{ + Bus: nxp.LPI2C4, + sda: I2C3_SDA_PIN, // D25 (PA13 [AD_B0_13]) + scl: I2C3_SCL_PIN, // D24 (PA12 [AD_B0_12]) + muxSDA: muxSelect{ + mux: nxp.IOMUXC_LPI2C4_SDA_SELECT_INPUT_DAISY_GPIO_AD_B0_13_ALT0, + sel: &nxp.IOMUXC.LPI2C4_SDA_SELECT_INPUT, + }, + muxSCL: muxSelect{ + mux: nxp.IOMUXC_LPI2C4_SCL_SELECT_INPUT_DAISY_GPIO_AD_B0_12_ALT0, + sel: &nxp.IOMUXC.LPI2C4_SCL_SELECT_INPUT, + }, + } +) diff --git a/emb/machine/board_teensy41.go b/emb/machine/board_teensy41.go new file mode 100644 index 00000000..436c1b9c --- /dev/null +++ b/emb/machine/board_teensy41.go @@ -0,0 +1,319 @@ +//go:build teensy41 + +package machine + +import ( + "device/nxp" + "runtime/interrupt" +) + +// Digital pins +const ( + // = Pin // [Pad]: Alt Func 0 Alt Func 1 Alt Func 2 Alt Func 3 Alt Func 4 Alt Func 5 Alt Func 6 Alt Func 7 Alt Func 8 Alt Func 9 + // = ---- ----------- --------------- --------------- --------------- -------------- -------------------- ---------- -------------------- --------------------- --------------------- ---------------- + D0 = PA3 // [AD_B0_03]: FLEXCAN2_RX XBAR1_INOUT17 LPUART6_RX USB_OTG1_OC FLEXPWM1_PWMX01 GPIO1_IO03 REF_CLK_24M LPSPI3_PCS0 ~ ~ + D1 = PA2 // [AD_B0_02]: FLEXCAN2_TX XBAR1_INOUT16 LPUART6_TX USB_OTG1_PWR FLEXPWM1_PWMX00 GPIO1_IO02 LPI2C1_HREQ LPSPI3_SDI ~ ~ + D2 = PD4 // [EMC_04]: SEMC_DATA04 FLEXPWM4_PWMA02 SAI2_TX_DATA XBAR1_INOUT06 FLEXIO1_FLEXIO04 GPIO4_IO04 ~ ~ ~ ~ + D3 = PD5 // [EMC_05]: SEMC_DATA05 FLEXPWM4_PWMB02 SAI2_TX_SYNC XBAR1_INOUT07 FLEXIO1_FLEXIO05 GPIO4_IO05 ~ ~ ~ ~ + D4 = PD6 // [EMC_06]: SEMC_DATA06 FLEXPWM2_PWMA00 SAI2_TX_BCLK XBAR1_INOUT08 FLEXIO1_FLEXIO06 GPIO4_IO06 ~ ~ ~ ~ + D5 = PD8 // [EMC_08]: SEMC_DM00 FLEXPWM2_PWMA01 SAI2_RX_DATA XBAR1_INOUT17 FLEXIO1_FLEXIO08 GPIO4_IO08 ~ ~ ~ ~ + D6 = PB10 // [B0_10]: LCD_DATA06 QTIMER4_TIMER1 FLEXPWM2_PWMA02 SAI1_TX_DATA03 FLEXIO2_FLEXIO10 GPIO2_IO10 SRC_BOOT_CFG06 ENET2_CRS ~ ~ + D7 = PB17 // [B1_01]: LCD_DATA13 XBAR1_INOUT15 LPUART4_RX SAI1_TX_DATA00 FLEXIO2_FLEXIO17 GPIO2_IO17 FLEXPWM1_PWMB03 ENET2_RDATA00 FLEXIO3_FLEXIO17 ~ + D8 = PB16 // [B1_00]: LCD_DATA12 XBAR1_INOUT14 LPUART4_TX SAI1_RX_DATA00 FLEXIO2_FLEXIO16 GPIO2_IO16 FLEXPWM1_PWMA03 ENET2_RX_ER FLEXIO3_FLEXIO16 ~ + D9 = PB11 // [B0_11]: LCD_DATA07 QTIMER4_TIMER2 FLEXPWM2_PWMB02 SAI1_TX_DATA02 FLEXIO2_FLEXIO11 GPIO2_IO11 SRC_BOOT_CFG07 ENET2_COL ~ ~ + D10 = PB0 // [B0_00]: LCD_CLK QTIMER1_TIMER0 MQS_RIGHT LPSPI4_PCS0 FLEXIO2_FLEXIO00 GPIO2_IO00 SEMC_CSX01 ENET2_MDC ~ ~ + D11 = PB2 // [B0_02]: LCD_HSYNC QTIMER1_TIMER2 FLEXCAN1_TX LPSPI4_SDO FLEXIO2_FLEXIO02 GPIO2_IO02 SEMC_CSX03 ENET2_1588_EVENT0_OUT ~ ~ + D12 = PB1 // [B0_01]: LCD_ENABLE QTIMER1_TIMER1 MQS_LEFT LPSPI4_SDI FLEXIO2_FLEXIO01 GPIO2_IO01 SEMC_CSX02 ENET2_MDIO ~ ~ + D13 = PB3 // [B0_03]: LCD_VSYNC QTIMER2_TIMER0 FLEXCAN1_RX LPSPI4_SCK FLEXIO2_FLEXIO03 GPIO2_IO03 WDOG2_RESET_B_DEB ENET2_1588_EVENT0_IN ~ ~ + D14 = PA18 // [AD_B1_02]: USB_OTG1_ID QTIMER3_TIMER2 LPUART2_TX SPDIF_OUT ENET_1588_EVENT2_OUT GPIO1_IO18 USDHC1_CD_B KPP_ROW06 GPT2_CLK FLEXIO3_FLEXIO02 + D15 = PA19 // [AD_B1_03]: USB_OTG1_OC QTIMER3_TIMER3 LPUART2_RX SPDIF_IN ENET_1588_EVENT2_IN GPIO1_IO19 USDHC2_CD_B KPP_COL06 GPT2_CAPTURE1 FLEXIO3_FLEXIO03 + D16 = PA23 // [AD_B1_07]: FLEXSPIB_DATA00 LPI2C3_SCL LPUART3_RX SPDIF_EXT_CLK CSI_HSYNC GPIO1_IO23 USDHC2_DATA3 KPP_COL04 GPT2_COMPARE3 FLEXIO3_FLEXIO07 + D17 = PA22 // [AD_B1_06]: FLEXSPIB_DATA01 LPI2C3_SDA LPUART3_TX SPDIF_LOCK CSI_VSYNC GPIO1_IO22 USDHC2_DATA2 KPP_ROW04 GPT2_COMPARE2 FLEXIO3_FLEXIO06 + D18 = PA17 // [AD_B1_01]: USB_OTG1_PWR QTIMER3_TIMER1 LPUART2_RTS_B LPI2C1_SDA CCM_PMIC_READY GPIO1_IO17 USDHC1_VSELECT KPP_COL07 ENET2_1588_EVENT0_IN FLEXIO3_FLEXIO01 + D19 = PA16 // [AD_B1_00]: USB_OTG2_ID QTIMER3_TIMER0 LPUART2_CTS_B LPI2C1_SCL WDOG1_B GPIO1_IO16 USDHC1_WP KPP_ROW07 ENET2_1588_EVENT0_OUT FLEXIO3_FLEXIO00 + D20 = PA26 // [AD_B1_10]: FLEXSPIA_DATA03 WDOG1_B LPUART8_TX SAI1_RX_SYNC CSI_DATA07 GPIO1_IO26 USDHC2_WP KPP_ROW02 ENET2_1588_EVENT1_OUT FLEXIO3_FLEXIO10 + D21 = PA27 // [AD_B1_11]: FLEXSPIA_DATA02 EWM_OUT_B LPUART8_RX SAI1_RX_BCLK CSI_DATA06 GPIO1_IO27 USDHC2_RESET_B KPP_COL02 ENET2_1588_EVENT1_IN FLEXIO3_FLEXIO11 + D22 = PA24 // [AD_B1_08]: FLEXSPIA_SS1_B FLEXPWM4_PWMA00 FLEXCAN1_TX CCM_PMIC_READY CSI_DATA09 GPIO1_IO24 USDHC2_CMD KPP_ROW03 FLEXIO3_FLEXIO08 ~ + D23 = PA25 // [AD_B1_09]: FLEXSPIA_DQS FLEXPWM4_PWMA01 FLEXCAN1_RX SAI1_MCLK CSI_DATA08 GPIO1_IO25 USDHC2_CLK KPP_COL03 FLEXIO3_FLEXIO09 ~ + D24 = PA12 // [AD_B0_12]: LPI2C4_SCL CCM_PMIC_READY LPUART1_TX WDOG2_WDOG_B FLEXPWM1_PWMX02 GPIO1_IO12 ENET_1588_EVENT1_OUT NMI_GLUE_NMI ~ ~ + D25 = PA13 // [AD_B0_13]: LPI2C4_SDA GPT1_CLK LPUART1_RX EWM_OUT_B FLEXPWM1_PWMX03 GPIO1_IO13 ENET_1588_EVENT1_IN REF_CLK_24M ~ ~ + D26 = PA30 // [AD_B1_14]: FLEXSPIA_SCLK ACMP_OUT02 LPSPI3_SDO SAI1_TX_BCLK CSI_DATA03 GPIO1_IO30 USDHC2_DATA6 KPP_ROW00 ENET2_1588_EVENT3_OUT FLEXIO3_FLEXIO14 + D27 = PA31 // [AD_B1_15]: FLEXSPIA_SS0_B ACMP_OUT03 LPSPI3_SCK SAI1_TX_SYNC CSI_DATA02 GPIO1_IO31 USDHC2_DATA7 KPP_COL00 ENET2_1588_EVENT3_IN FLEXIO3_FLEXIO15 + D28 = PC18 // [EMC_32]: SEMC_DATA10 FLEXPWM3_PWMB01 LPUART7_RX CCM_PMIC_RDY CSI_DATA21 GPIO3_IO18 ENET2_TX_EN ~ ~ ~ + D29 = PD31 // [EMC_31]: SEMC_DATA09 FLEXPWM3_PWMA01 LPUART7_TX LPSPI1_PCS1 CSI_DATA22 GPIO4_IO31 ENET2_TDATA01 ~ ~ ~ + D30 = PC23 // [EMC_37]: SEMC_DATA15 XBAR1_IN23 GPT1_COMPARE3 SAI3_MCLK CSI_DATA16 GPIO3_IO23 USDHC2_WP ENET2_RX_EN FLEXCAN3_RX ~ + D31 = PC22 // [EMC_36]: SEMC_DATA14 XBAR1_IN22 GPT1_COMPARE2 SAI3_TX_DATA CSI_DATA17 GPIO3_IO22 USDHC1_WP ENET2_RDATA01 FLEXCAN3_TX ~ + D32 = PB12 // [B0_12]: LCD_DATA08 XBAR1_INOUT10 ARM_TRACE_CLK SAI1_TX_DATA01 FLEXIO2_FLEXIO12 GPIO2_IO12 SRC_BOOT_CFG08 ENET2_TDATA00 ~ ~ + D33 = PD7 // [EMC_07]: SEMC_DATA07 FLEXPWM2_PWMB00 SAI2_MCLK XBAR1_INOUT09 FLEXIO1_FLEXIO07 GPIO4_IO07 ~ ~ ~ ~ + D34 = PB29 // [B1_13]: WDOG1_B LPUART5_RX CSI_VSYNC ENET_1588_EVENT0_OUT FLEXIO2_FLEXIO29 GPIO2_IO29 USDHC1_WP SEMC_DQS4 FLEXIO3_FLEXIO29 ~ + D35 = PB28 // [B1_12]: LPUART5_TX CSI_PIXCLK ENET_1588_EVENT0_IN FLEXIO2_FLEXIO28 GPIO2_IO28 USDHC1_CD_B FLEXIO3_FLEXIO28 ~ ~ ~ + D36 = PB18 // [B1_02]: LCD_DATA14 XBAR1_INOUT16 LPSPI4_PCS2 SAI1_TX_BCLK FLEXIO2_FLEXIO18 GPIO2_IO18 FLEXPWM2_PWMA03 ENET2_RDATA01 FLEXIO3_FLEXIO18 ~ + D37 = PB19 // [B1_03]: LCD_DATA15 XBAR1_INOUT17 LPSPI4_PCS1 SAI1_TX_SYNC FLEXIO2_FLEXIO19 GPIO2_IO19 FLEXPWM2_PWMB03 ENET2_RX_EN FLEXIO3_FLEXIO19 ~ + D38 = PA28 // [AD_B1_12]: FLEXSPIA_DATA01 ACMP_OUT00 LPSPI3_PCS0 SAI1_RX_DATA00 CSI_DATA05 GPIO1_IO28 USDHC2_DATA4 KPP_ROW01 ENET2_1588_EVENT2_OUT FLEXIO3_FLEXIO12 + D39 = PA29 // [AD_B1_13]: FLEXSPIA_DATA00 ACMP_OUT01 LPSPI3_SDI SAI1_TX_DATA00 CSI_DATA04 GPIO1_IO29 USDHC2_DATA5 KPP_COL01 ENET2_1588_EVENT2_IN FLEXIO3_FLEXIO13 + D40 = PA20 // [AD_B1_04]: FLEXSPIB_DATA03 ENET_MDC LPUART3_CTS_B SPDIF_SR_CLK CSI_PIXCLK GPIO1_IO20 USDHC2_DATA0 KPP_ROW05 GPT2_CAPTURE2 FLEXIO3_FLEXIO04 + D41 = PA21 // [AD_B1_05]: FLEXSPIB_DATA02 ENET_MDIO LPUART3_RTS_B SPDIF_OUT CSI_MCLK GPIO1_IO21 USDHC2_DATA1 KPP_COL05 GPT2_COMPARE1 FLEXIO3_FLEXIO05 + D42 = PC15 // [SD_B0_03]: USDHC1_DATA1 FLEXPWM1_PWMB01 LPUART8_RTS_B XBAR1_INOUT07 LPSPI1_SDI GPIO3_IO15 ENET2_RDATA00 SEMC_CLK6 ~ ~ + D43 = PC14 // [SD_B0_02]: USDHC1_DATA0 FLEXPWM1_PWMA01 LPUART8_CTS_B XBAR1_INOUT06 LPSPI1_SDO GPIO3_IO14 ENET2_RX_ER SEMC_CLK5 ~ ~ + D44 = PC13 // [SD_B0_01]: USDHC1_CLK FLEXPWM1_PWMB00 LPI2C3_SDA XBAR1_INOUT05 LPSPI1_PCS0 GPIO3_IO13 FLEXSPIB_SS1_B ENET2_TX_CLK ENET2_REF_CLK2 ~ + D45 = PC12 // [SD_B0_00]: USDHC1_CMD FLEXPWM1_PWMA00 LPI2C3_SCL XBAR1_INOUT04 LPSPI1_SCK GPIO3_IO12 FLEXSPIA_SS1_B ENET2_TX_EN SEMC_DQS4 ~ + D46 = PC17 // [SD_B0_05]: USDHC1_DATA3 FLEXPWM1_PWMB02 LPUART8_RX XBAR1_INOUT09 FLEXSPIB_DQS GPIO3_IO17 CCM_CLKO2 ENET2_RX_EN ~ ~ + D47 = PC16 // [SD_B0_04]: USDHC1_DATA2 FLEXPWM1_PWMA02 LPUART8_TX XBAR1_INOUT08 FLEXSPIB_SS0_B GPIO3_IO16 CCM_CLKO1 ENET2_RDATA01 ~ ~ + D48 = PD24 // [EMC_24]: SEMC_CAS FLEXPWM1_PWMB00 LPUART5_RX ENET_TX_EN GPT1_CAPTURE1 GPIO4_IO24 FLEXSPI2_A_SS0_B ~ ~ ~ + D49 = PD27 // [EMC_27]: SEMC_CKE FLEXPWM1_PWMA02 LPUART5_RTS_B LPSPI1_SCK FLEXIO1_FLEXIO13 GPIO4_IO27 FLEXSPI2_A_DATA01 ~ ~ ~ + D50 = PD28 // [EMC_28]: SEMC_WE FLEXPWM1_PWMB02 LPUART5_CTS_B LPSPI1_SDO FLEXIO1_FLEXIO14 GPIO4_IO28 FLEXSPI2_A_DATA02 ~ ~ ~ + D51 = PD22 // [EMC_22]: SEMC_BA1 FLEXPWM3_PWMB03 LPI2C3_SCL ENET_TDATA00 QTIMER2_TIMER3 GPIO4_IO22 FLEXSPI2_A_SS1_B ~ ~ ~ + D52 = PD26 // [EMC_26]: SEMC_CLK FLEXPWM1_PWMB01 LPUART6_RX ENET_RX_ER FLEXIO1_FLEXIO12 GPIO4_IO26 FLEXSPI2_A_DATA00 ~ ~ ~ + D53 = PD25 // [EMC_25]: SEMC_RAS FLEXPWM1_PWMA01 LPUART6_TX ENET_TX_CLK ENET_REF_CLK GPIO4_IO25 FLEXSPI2_A_SCLK ~ ~ ~ + D54 = PD29 // [EMC_29]: SEMC_CS0 FLEXPWM3_PWMA00 LPUART6_RTS_B LPSPI1_SDI FLEXIO1_FLEXIO15 GPIO4_IO29 FLEXSPI2_A_DATA03 ~ ~ ~ +) + +// Analog pins +const ( + // = Pin // Dig | [Pad] {ADC1/ADC2} + A0 = PA18 // D14 | [AD_B1_02] { 7 / 7 } + A1 = PA19 // D15 | [AD_B1_03] { 8 / 8 } + A2 = PA23 // D16 | [AD_B1_07] { 12 / 12 } + A3 = PA22 // D17 | [AD_B1_06] { 11 / 11 } + A4 = PA17 // D18 | [AD_B1_01] { 6 / 6 } + A5 = PA16 // D19 | [AD_B1_00] { 5 / 5 } + A6 = PA26 // D20 | [AD_B1_10] { 15 / 15 } + A7 = PA27 // D21 | [AD_B1_11] { 0 / 0 } + A8 = PA24 // D22 | [AD_B1_08] { 13 / 13 } + A9 = PA25 // D23 | [AD_B1_09] { 14 / 14 } + A10 = PA12 // D24 | [AD_B0_12] { 1 / - } + A11 = PA13 // D25 | [AD_B0_13] { 2 / - } + A12 = PA30 // D26 | [AD_B1_14] { - / 3 } + A13 = PA31 // D27 | [AD_B1_15] { - / 4 } + A14 = PA28 // D38 | [AD_B1_12] { ? / ? } // FIXME + A15 = PA29 // D39 | [AD_B1_13] { ? / ? } // FIXME + A16 = PA20 // D40 | [AD_B1_04] { ? / ? } // FIXME + A17 = PA21 // D41 | [AD_B1_05] { ? / ? } // FIXME +) + +// Default peripheral pins +const ( + LED = D13 + + UART_RX_PIN = UART1_RX_PIN // D0 + UART_TX_PIN = UART1_TX_PIN // D1 + + SPI_SDI_PIN = SPI1_SDI_PIN // D12 + SPI_SDO_PIN = SPI1_SDO_PIN // D11 + SPI_SCK_PIN = SPI1_SCK_PIN // D13 + SPI_CS_PIN = SPI1_CS_PIN // D10 + + I2C_SDA_PIN = I2C1_SDA_PIN // D18/A4 + I2C_SCL_PIN = I2C1_SCL_PIN // D19/A5 +) + +// Default peripherals +var ( + DefaultUART = UART1 +) + +func init() { + // register any interrupt handlers for this board's peripherals + _UART1.Interrupt = interrupt.New(nxp.IRQ_LPUART6, _UART1.handleInterrupt) + _UART2.Interrupt = interrupt.New(nxp.IRQ_LPUART4, _UART2.handleInterrupt) + _UART3.Interrupt = interrupt.New(nxp.IRQ_LPUART2, _UART3.handleInterrupt) + _UART4.Interrupt = interrupt.New(nxp.IRQ_LPUART3, _UART4.handleInterrupt) + _UART5.Interrupt = interrupt.New(nxp.IRQ_LPUART8, _UART5.handleInterrupt) + _UART6.Interrupt = interrupt.New(nxp.IRQ_LPUART1, _UART6.handleInterrupt) + _UART7.Interrupt = interrupt.New(nxp.IRQ_LPUART7, _UART7.handleInterrupt) + _UART8.Interrupt = interrupt.New(nxp.IRQ_LPUART5, _UART8.handleInterrupt) +} + +// #=====================================================# +// | UART | +// #===========#===========#=============#===============# +// | Interface | Hardware | Clock(Freq) | RX/TX : Alt | +// #===========#===========#=============#=========-=====# +// | UART1 | LPUART6 | OSC(24 MHz) | D0/D1 : 2/2 | +// | UART2 | LPUART4 | OSC(24 MHz) | D7/D8 : 2/2 | +// | UART3 | LPUART2 | OSC(24 MHz) | D15/D14 : 2/2 | +// | UART4 | LPUART3 | OSC(24 MHz) | D16/D17 : 2/2 | +// | UART5 | LPUART8 | OSC(24 MHz) | D21/D20 : 2/2 | +// | UART6 | LPUART1 | OSC(24 MHz) | D25/D24 : 2/2 | +// | UART7 | LPUART7 | OSC(24 MHz) | D28/D29 : 2/2 | +// | UART8 | LPUART5 | OSC(24 MHz) | D34/D35 : 1/1 | +// #===========#===========#=============#=========-=====# +const ( + UART1_RX_PIN = D0 + UART1_TX_PIN = D1 + + UART2_RX_PIN = D7 + UART2_TX_PIN = D8 + + UART3_RX_PIN = D15 + UART3_TX_PIN = D14 + + UART4_RX_PIN = D16 + UART4_TX_PIN = D17 + + UART5_RX_PIN = D21 + UART5_TX_PIN = D20 + + UART6_RX_PIN = D25 + UART6_TX_PIN = D24 + + UART7_RX_PIN = D28 + UART7_TX_PIN = D29 + + UART8_RX_PIN = D34 + UART8_TX_PIN = D35 +) + +var ( + UART1 = &_UART1 + _UART1 = UART{ + Bus: nxp.LPUART6, + Buffer: NewRingBuffer(), + txBuffer: NewRingBuffer(), + muxRX: muxSelect{ // D0 (PA3 [AD_B0_03]) + mux: nxp.IOMUXC_LPUART6_RX_SELECT_INPUT_DAISY_GPIO_AD_B0_03_ALT2, + sel: &nxp.IOMUXC.LPUART6_RX_SELECT_INPUT, + }, + muxTX: muxSelect{ // D1 (PA2 [AD_B0_02]) + mux: nxp.IOMUXC_LPUART6_TX_SELECT_INPUT_DAISY_GPIO_AD_B0_02_ALT2, + sel: &nxp.IOMUXC.LPUART6_TX_SELECT_INPUT, + }, + } + UART2 = &_UART2 + _UART2 = UART{ + Bus: nxp.LPUART4, + Buffer: NewRingBuffer(), + txBuffer: NewRingBuffer(), + muxRX: muxSelect{ // D7 (PB17 [B1_01]) + mux: nxp.IOMUXC_LPUART4_RX_SELECT_INPUT_DAISY_GPIO_B1_01_ALT2, + sel: &nxp.IOMUXC.LPUART4_RX_SELECT_INPUT, + }, + muxTX: muxSelect{ // D8 (PB16 [B1_00]) + mux: nxp.IOMUXC_LPUART4_TX_SELECT_INPUT_DAISY_GPIO_B1_00_ALT2, + sel: &nxp.IOMUXC.LPUART4_TX_SELECT_INPUT, + }, + } + UART3 = &_UART3 + _UART3 = UART{ + Bus: nxp.LPUART2, + Buffer: NewRingBuffer(), + txBuffer: NewRingBuffer(), + muxRX: muxSelect{ // D15 (PA19 [AD_B1_03]) + mux: nxp.IOMUXC_LPUART2_RX_SELECT_INPUT_DAISY_GPIO_AD_B1_03_ALT2, + sel: &nxp.IOMUXC.LPUART2_RX_SELECT_INPUT, + }, + muxTX: muxSelect{ // D14 (PA18 [AD_B1_02]) + mux: nxp.IOMUXC_LPUART2_TX_SELECT_INPUT_DAISY_GPIO_AD_B1_02_ALT2, + sel: &nxp.IOMUXC.LPUART2_TX_SELECT_INPUT, + }, + } + UART4 = &_UART4 + _UART4 = UART{ + Bus: nxp.LPUART3, + Buffer: NewRingBuffer(), + txBuffer: NewRingBuffer(), + muxRX: muxSelect{ // D16 (PA23 [AD_B1_07]) + mux: nxp.IOMUXC_LPUART3_RX_SELECT_INPUT_DAISY_GPIO_AD_B1_07_ALT2, + sel: &nxp.IOMUXC.LPUART3_RX_SELECT_INPUT, + }, + muxTX: muxSelect{ // D17 (PA22 [AD_B1_06]) + mux: nxp.IOMUXC_LPUART3_TX_SELECT_INPUT_DAISY_GPIO_AD_B1_06_ALT2, + sel: &nxp.IOMUXC.LPUART3_TX_SELECT_INPUT, + }, + } + UART5 = &_UART5 + _UART5 = UART{ + Bus: nxp.LPUART8, + Buffer: NewRingBuffer(), + txBuffer: NewRingBuffer(), + muxRX: muxSelect{ // D21 (PA27 [AD_B1_11]) + mux: nxp.IOMUXC_LPUART8_RX_SELECT_INPUT_DAISY_GPIO_AD_B1_11_ALT2, + sel: &nxp.IOMUXC.LPUART8_RX_SELECT_INPUT, + }, + muxTX: muxSelect{ // D20 (PA26 [AD_B1_10]) + mux: nxp.IOMUXC_LPUART8_TX_SELECT_INPUT_DAISY_GPIO_AD_B1_10_ALT2, + sel: &nxp.IOMUXC.LPUART8_TX_SELECT_INPUT, + }, + } + UART6 = &_UART6 + _UART6 = UART{ + Bus: nxp.LPUART1, + Buffer: NewRingBuffer(), + txBuffer: NewRingBuffer(), + // LPUART1 not connected via IOMUXC + // RX: D24 (PA12 [AD_B0_12]) + // TX: D25 (PA13 [AD_B0_13]) + } + UART7 = &_UART7 + _UART7 = UART{ + Bus: nxp.LPUART7, + Buffer: NewRingBuffer(), + txBuffer: NewRingBuffer(), + muxRX: muxSelect{ // D28 (PC18 [EMC_32]) + mux: nxp.IOMUXC_LPUART7_RX_SELECT_INPUT_DAISY_GPIO_EMC_32_ALT2, + sel: &nxp.IOMUXC.LPUART7_RX_SELECT_INPUT, + }, + muxTX: muxSelect{ // D29 (PD31 [EMC_31]) + mux: nxp.IOMUXC_LPUART7_TX_SELECT_INPUT_DAISY_GPIO_EMC_31_ALT2, + sel: &nxp.IOMUXC.LPUART7_TX_SELECT_INPUT, + }, + } + UART8 = &_UART8 + _UART8 = UART{ + Bus: nxp.LPUART5, + Buffer: NewRingBuffer(), + txBuffer: NewRingBuffer(), + muxRX: muxSelect{ // D34 (PB29 [B1_13]) + mux: nxp.IOMUXC_LPUART5_RX_SELECT_INPUT_DAISY_GPIO_B1_13_ALT1, + sel: &nxp.IOMUXC.LPUART5_RX_SELECT_INPUT, + }, + muxTX: muxSelect{ // D35 (PB28 [B1_12]) + mux: nxp.IOMUXC_LPUART5_TX_SELECT_INPUT_DAISY_GPIO_B1_12_ALT1, + sel: &nxp.IOMUXC.LPUART5_TX_SELECT_INPUT, + }, + } +) + +// #===========#==========#===============#===========================# +// | Interface | Hardware | Clock(Freq) | SDI/SDO/SCK/CS : Alt | +// #===========#==========#===============#=================-=========# +// | SPI1 | LPSPI4 | PLL2(132 MHz) | D12/D11/D13/D10 : 3/3/3/3 | +// | SPI2 | LPSPI3 | PLL2(132 MHz) | D1/D26/D27/D0 : 7/2/2/7 | +// | SPI3 | LPSPI1 | PLL2(132 MHz) | D34/D35/D37/D36 : 4/4/4/4 | +// #===========#==========#===============#=================-=========# +const ( + SPI1_SDI_PIN = D12 + SPI1_SDO_PIN = D11 + SPI1_SCK_PIN = D13 + SPI1_CS_PIN = D10 + + SPI2_SDI_PIN = D1 + SPI2_SDO_PIN = D26 + SPI2_SCK_PIN = D27 + SPI2_CS_PIN = D0 + + SPI3_SDI_PIN = D34 + SPI3_SDO_PIN = D35 + SPI3_SCK_PIN = D37 + SPI3_CS_PIN = D36 +) + +// #====================================================# +// | I2C | +// #===========#==========#=============#===============# +// | Interface | Hardware | Clock(Freq) | SDA/SCL : Alt | +// #===========#==========#=============#=========-=====# +// | I2C1 | LPI2C1 | OSC(24 MHz) | D18/D19 : 3/3 | +// | I2C2 | LPI2C3 | OSC(24 MHz) | D17/D16 : 1/1 | +// | I2C3 | LPI2C4 | OSC(24 MHz) | D25/D24 : 0/0 | +// #===========#==========#=============#=========-=====# +const ( + I2C1_SDA_PIN = D18 + I2C1_SCL_PIN = D19 + + I2C2_SDA_PIN = D17 + I2C2_SCL_PIN = D16 + + I2C3_SDA_PIN = D25 + I2C3_SCL_PIN = D24 +) diff --git a/emb/machine/board_thingplus_rp2040.go b/emb/machine/board_thingplus_rp2040.go new file mode 100644 index 00000000..48292d26 --- /dev/null +++ b/emb/machine/board_thingplus_rp2040.go @@ -0,0 +1,103 @@ +//go:build thingplus_rp2040 + +package machine + +// Onboard crystal oscillator frequency, in MHz. +const xoscFreq = 12 // MHz + +// GPIO Pins +const ( + GP0 Pin = GPIO0 // TX + GP1 Pin = GPIO1 // RX + GP2 Pin = GPIO2 // SCK + GP3 Pin = GPIO3 // COPI + GP4 Pin = GPIO4 // CIPO + + GP6 Pin = GPIO6 // SDA + GP7 Pin = GPIO7 // SCL (connected to GPIO23 as well) + GP8 Pin = GPIO8 // WS2812 RGB LED + GP9 Pin = GPIO9 // muSDcard DATA3 / CS + GP10 Pin = GPIO10 // muSDcard DATA2 + GP11 Pin = GPIO11 // muSDcard DATA1 + GP12 Pin = GPIO12 // muSDcard DATA0 / CIPO + + GP14 Pin = GPIO14 // muSDcard CLK /SCLK + GP15 Pin = GPIO15 // muSDcard CMD / COPI + GP16 Pin = GPIO16 // 16 + GP17 Pin = GPIO17 // 17 + GP18 Pin = GPIO18 // 18 + GP19 Pin = GPIO19 // 19 + GP20 Pin = GPIO20 // 20 + GP21 Pin = GPIO21 // 21 + GP22 Pin = GPIO22 // 22 + GP23 Pin = GPIO23 // Connected to GPIO7 + GP25 Pin = GPIO25 // Status blue LED + GP26 Pin = GPIO26 // ADC0 + GP27 Pin = GPIO27 // ADC1 + GP28 Pin = GPIO28 // ADC2 + GP29 Pin = GPIO29 // ADC3 +) + +// Analog pins +const ( + A0 = GPIO26 + A1 = GPIO27 + A2 = GPIO28 + A3 = GPIO29 +) + +// Onboard LEDs +const ( + LED = GPIO25 + WS2812 = GPIO8 +) + +// I2C Pins. +const ( + I2C0_SCL_PIN = GPIO6 // N/A + I2C0_SDA_PIN = GPIO7 // N/A + + I2C1_SDA_PIN = GPIO6 + I2C1_SCL_PIN = GPIO7 + + SDA_PIN = I2C1_SDA_PIN + SCL_PIN = I2C1_SCL_PIN +) + +// SPI default pins +const ( + // Default Serial Clock Bus 0 for SPI communications + SPI0_SCK_PIN = GPIO2 + // Default Serial Out Bus 0 for SPI communications + SPI0_SDO_PIN = GPIO3 // Tx + // Default Serial In Bus 0 for SPI communications + SPI0_SDI_PIN = GPIO4 // Rx + + // Default Serial Clock Bus 1 for SPI communications to muSDcard + SPI1_SCK_PIN = GPIO14 + // Default Serial Out Bus 1 for SPI communications to muSDcard + SPI1_SDO_PIN = GPIO15 // Tx + // Default Serial In Bus 1 for SPI communications to muSDcard + SPI1_SDI_PIN = GPIO12 // Rx +) + +// UART pins +const ( + UART0_TX_PIN = GPIO0 + UART0_RX_PIN = GPIO1 + UART_TX_PIN = UART0_TX_PIN + UART_RX_PIN = UART0_RX_PIN +) + +var DefaultUART = UART0 + +// USB identifiers +const ( + usb_STRING_PRODUCT = "Thing Plus RP2040" + usb_STRING_MANUFACTURER = "SparkFun" +) + +var ( + usb_VID uint16 = 0x1B4F + usb_PID uint16 = 0x0026 +) diff --git a/emb/machine/board_thumby.go b/emb/machine/board_thumby.go new file mode 100644 index 00000000..f89a8b70 --- /dev/null +++ b/emb/machine/board_thumby.go @@ -0,0 +1,76 @@ +//go:build thumby + +// This contains the pin mappings for the Thumby. +// +// https://thumby.us/ +package machine + +const ( + THUMBY_SCK_PIN = I2C1_SDA_PIN + THUMBY_SDA_PIN = I2C1_SCL_PIN + + THUMBY_CS_PIN = GPIO16 + THUMBY_DC_PIN = GPIO17 + THUMBY_RESET_PIN = GPIO20 + + THUMBY_LINK_TX_PIN = UART0_TX_PIN + THUMBY_LINK_RX_PIN = UART0_RX_PIN + THUMBY_LINK_PU_PIN = GPIO2 + + THUMBY_BTN_LDPAD_PIN = GPIO3 + THUMBY_BTN_RDPAD_PIN = GPIO5 + THUMBY_BTN_UDPAD_PIN = GPIO4 + THUMBY_BTN_DDPAD_PIN = GPIO6 + THUMBY_BTN_B_PIN = GPIO24 + THUMBY_BTN_A_PIN = GPIO27 + + THUMBY_AUDIO_PIN = GPIO28 + + THUMBY_SCREEN_RESET_PIN = GPIO20 +) + +// I2C pins +const ( + I2C0_SDA_PIN Pin = NoPin + I2C0_SCL_PIN Pin = NoPin + + I2C1_SDA_PIN Pin = GPIO18 + I2C1_SCL_PIN Pin = GPIO19 +) + +// SPI pins +const ( + SPI0_SCK_PIN = GPIO18 + SPI0_SDO_PIN = GPIO19 + SPI0_SDI_PIN = GPIO16 + + SPI1_SCK_PIN = NoPin + SPI1_SDO_PIN = NoPin + SPI1_SDI_PIN = NoPin +) + +// Onboard crystal oscillator frequency, in MHz. +const ( + xoscFreq = 12 // MHz +) + +// USB CDC identifiers +const ( + usb_STRING_PRODUCT = "Thumby" + usb_STRING_MANUFACTURER = "TinyCircuits" +) + +var ( + usb_VID uint16 = 0x2E8A + usb_PID uint16 = 0x0005 +) + +// UART pins +const ( + UART0_TX_PIN = GPIO0 + UART0_RX_PIN = GPIO1 + UART_TX_PIN = UART0_TX_PIN + UART_RX_PIN = UART0_RX_PIN +) + +var DefaultUART = UART0 diff --git a/emb/machine/board_tiny2350.go b/emb/machine/board_tiny2350.go new file mode 100644 index 00000000..f04fa061 --- /dev/null +++ b/emb/machine/board_tiny2350.go @@ -0,0 +1,82 @@ +//go:build tiny2350 + +package machine + +// GPIO pins +const ( + GP0 Pin = GPIO0 + GP1 Pin = GPIO1 + GP2 Pin = GPIO2 + GP3 Pin = GPIO3 + GP4 Pin = GPIO4 + GP5 Pin = GPIO5 + GP6 Pin = GPIO6 + GP7 Pin = GPIO7 + GP12 Pin = GPIO12 + GP13 Pin = GPIO13 + GP18 Pin = GPIO18 + GP19 Pin = GPIO19 + GP20 Pin = GPIO20 + GP26 Pin = GPIO26 + GP27 Pin = GPIO27 + GP28 Pin = GPIO28 + GP29 Pin = GPIO29 + + // Onboard LED + LED_RED Pin = GPIO18 + LED_GREEN Pin = GPIO19 + LED_BLUE Pin = GPIO20 + LED = LED_RED + + // Onboard crystal oscillator frequency, in MHz. + xoscFreq = 12 // MHz +) + +// I2C Default pins on Tiny2350. +const ( + I2C0_SDA_PIN = GP12 + I2C0_SCL_PIN = GP13 + + I2C1_SDA_PIN = GP2 + I2C1_SCL_PIN = GP3 +) + +// SPI default pins +const ( + // Default Serial Clock Bus 0 for SPI communications + SPI0_SCK_PIN = GPIO6 + // Default Serial Out Bus 0 for SPI communications + SPI0_SDO_PIN = GPIO7 // Tx + // Default Serial In Bus 0 for SPI communications + SPI0_SDI_PIN = GPIO4 // Rx + + // Default Serial Clock Bus 1 for SPI communications + SPI1_SCK_PIN = GPIO26 + // Default Serial Out Bus 1 for SPI communications + SPI1_SDO_PIN = GPIO27 // Tx + // Default Serial In Bus 1 for SPI communications + SPI1_SDI_PIN = GPIO28 // Rx +) + +// UART pins +const ( + UART0_TX_PIN = GPIO0 + UART0_RX_PIN = GPIO1 + UART1_TX_PIN = GPIO4 + UART1_RX_PIN = GPIO5 + UART_TX_PIN = UART0_TX_PIN + UART_RX_PIN = UART0_RX_PIN +) + +var DefaultUART = UART0 + +// USB identifiers +const ( + usb_STRING_PRODUCT = "Tiny2350" + usb_STRING_MANUFACTURER = "Pimoroni" +) + +var ( + usb_VID uint16 = 0x2E8A + usb_PID uint16 = 0x000F +) diff --git a/emb/machine/board_trinket.go b/emb/machine/board_trinket.go new file mode 100644 index 00000000..089eadbf --- /dev/null +++ b/emb/machine/board_trinket.go @@ -0,0 +1,88 @@ +//go:build sam && atsamd21 && trinket_m0 + +package machine + +// used to reset into bootloader +const resetMagicValue = 0xf01669ef + +// GPIO Pins +const ( + D0 = PA08 // PWM available + D1 = PA02 + D2 = PA09 // PWM available + D3 = PA07 // PWM available / UART0 RX + D4 = PA06 // PWM available / UART0 TX + D13 = PA10 // LED +) + +// Analog pins +const ( + A0 = D1 + A1 = D2 + A2 = D0 + A3 = D3 + A4 = D4 +) + +const ( + LED = D13 +) + +// USBCDC pins +const ( + USBCDC_DM_PIN = PA24 + USBCDC_DP_PIN = PA25 +) + +// UART1 pins +const ( + UART_TX_PIN = D4 + UART_RX_PIN = D3 +) + +// UART1 on the Trinket M0. +var UART1 = &sercomUSART0 + +// SPI pins +const ( + SPI0_SCK_PIN = D3 + SPI0_SDO_PIN = D4 + SPI0_SDI_PIN = D2 +) + +// SPI on the Trinket M0. +var SPI0 = sercomSPIM0 + +// I2C pins +const ( + SDA_PIN = D0 // SDA + SCL_PIN = D2 // SCL +) + +// I2C on the Trinket M0. +var ( + I2C0 = sercomI2CM2 +) + +// I2S pins +const ( + I2S_SCK_PIN = PA10 + I2S_SDO_PIN = PA08 + I2S_SDI_PIN = NoPin // TODO: figure out what this is on Trinket M0. + I2S_WS_PIN = NoPin // TODO: figure out what this is on Trinket M0. +) + +// USB CDC identifiers +const ( + usb_STRING_PRODUCT = "Adafruit Trinket M0" + usb_STRING_MANUFACTURER = "Adafruit" +) + +var ( + usb_VID uint16 = 0x239A + usb_PID uint16 = 0x801E +) + +var ( + DefaultUART = UART1 +) diff --git a/emb/machine/board_trinkey_qt2040.go b/emb/machine/board_trinkey_qt2040.go new file mode 100644 index 00000000..1a49c6d9 --- /dev/null +++ b/emb/machine/board_trinkey_qt2040.go @@ -0,0 +1,62 @@ +//go:build trinkey_qt2040 + +// This file contains the pin mappings for the Adafruit Trinkey QT2040 board. +// +// The Trinkey QT2040 is a small development board based on the RP2040 which +// plugs into a USB A port. The board has a minimal pinout: an integrated +// NeoPixel LED and a STEMMA QT I2C port. +// +// - Product: https://www.adafruit.com/product/5056 +// - Overview: https://learn.adafruit.com/adafruit-trinkey-qt2040 +// - Pinouts: https://learn.adafruit.com/adafruit-trinkey-qt2040/pinouts +// - Datasheets: https://learn.adafruit.com/adafruit-trinkey-qt2040/downloads + +package machine + +// Onboard crystal oscillator frequency, in MHz +const xoscFreq = 12 // MHz + +// Onboard LEDs +const ( + NEOPIXEL = GPIO27 + WS2812 = NEOPIXEL +) + +// I2C pins +const ( + I2C0_SDA_PIN = GPIO16 + I2C0_SCL_PIN = GPIO17 + + I2C1_SDA_PIN = NoPin + I2C1_SCL_PIN = NoPin +) + +// SPI pins +const ( + SPI0_SCK_PIN = NoPin + SPI0_SDO_PIN = NoPin + SPI0_SDI_PIN = NoPin + + SPI1_SCK_PIN = NoPin + SPI1_SDO_PIN = NoPin + SPI1_SDI_PIN = NoPin +) + +// UART pins +const ( + UART0_TX_PIN = NoPin + UART0_RX_PIN = NoPin + UART_TX_PIN = UART0_TX_PIN + UART_RX_PIN = UART0_RX_PIN +) + +// USB identifiers +const ( + usb_STRING_PRODUCT = "Trinkey QT2040" + usb_STRING_MANUFACTURER = "Adafruit" +) + +var ( + usb_VID uint16 = 0x239a + usb_PID uint16 = 0x8109 +) diff --git a/emb/machine/board_tufty2040.go b/emb/machine/board_tufty2040.go new file mode 100644 index 00000000..57d244f2 --- /dev/null +++ b/emb/machine/board_tufty2040.go @@ -0,0 +1,85 @@ +//go:build tufty2040 + +// This contains the pin mappings for the Badger 2040 Connect board. +// +// For more information, see: https://shop.pimoroni.com/products/tufty-2040 +// Also +// - Tufty 2040 schematic: https://cdn.shopify.com/s/files/1/0174/1800/files/tufty_schematic.pdf?v=1655385675 +package machine + +const ( + LED Pin = GPIO25 + + BUTTON_A Pin = GPIO7 + BUTTON_B Pin = GPIO8 + BUTTON_C Pin = GPIO9 + BUTTON_UP Pin = GPIO22 + BUTTON_DOWN Pin = GPIO6 + BUTTON_USER Pin = GPIO23 + + LCD_BACKLIGHT Pin = GPIO2 + LCD_CS Pin = GPIO10 + LCD_DC Pin = GPIO11 + LCD_WR Pin = GPIO12 + LCD_RD Pin = GPIO13 + LCD_DB0 Pin = GPIO14 + LCD_DB1 Pin = GPIO15 + LCD_DB2 Pin = GPIO16 + LCD_DB3 Pin = GPIO17 + LCD_DB4 Pin = GPIO18 + LCD_DB5 Pin = GPIO19 + LCD_DB6 Pin = GPIO20 + LCD_DB7 Pin = GPIO21 + + VBUS_DETECT Pin = GPIO24 + BATTERY Pin = GPIO29 + USER_LED Pin = GPIO25 + LIGHT_SENSE Pin = GPIO26 + SENSOR_POWER Pin = GPIO27 +) + +// I2C pins +const ( + I2C0_SDA_PIN Pin = GPIO4 + I2C0_SCL_PIN Pin = GPIO5 + + I2C1_SDA_PIN Pin = NoPin + I2C1_SCL_PIN Pin = NoPin +) + +// SPI pins. +const ( + SPI0_SCK_PIN Pin = NoPin + SPI0_SDO_PIN Pin = NoPin + SPI0_SDI_PIN Pin = NoPin + + SPI1_SCK_PIN Pin = NoPin + SPI1_SDO_PIN Pin = NoPin + SPI1_SDI_PIN Pin = NoPin +) + +// Onboard crystal oscillator frequency, in MHz. +const ( + xoscFreq = 12 // MHz +) + +// USB CDC identifiers +const ( + usb_STRING_PRODUCT = "Tufty 2040" + usb_STRING_MANUFACTURER = "Pimoroni" +) + +var ( + usb_VID uint16 = 0x2e8a + usb_PID uint16 = 0x1002 +) + +// UART pins +const ( + UART0_TX_PIN = GPIO0 + UART0_RX_PIN = GPIO1 + UART_TX_PIN = UART0_TX_PIN + UART_RX_PIN = UART0_RX_PIN +) + +var DefaultUART = UART0 diff --git a/emb/machine/board_waveshare-rp2040-zero.go b/emb/machine/board_waveshare-rp2040-zero.go new file mode 100644 index 00000000..00ddc53a --- /dev/null +++ b/emb/machine/board_waveshare-rp2040-zero.go @@ -0,0 +1,104 @@ +//go:build waveshare_rp2040_zero + +// This file contains the pin mappings for the Waveshare RP2040-Zero boards. +// +// Waveshare RP2040-Zero is a microcontroller using the Raspberry Pi RP2040 chip. +// +// - https://www.waveshare.com/wiki/RP2040-Zero +package machine + +// Digital Pins +const ( + D0 Pin = GPIO0 + D1 Pin = GPIO1 + D2 Pin = GPIO2 + D3 Pin = GPIO3 + D4 Pin = GPIO4 + D5 Pin = GPIO5 + D6 Pin = GPIO6 + D7 Pin = GPIO7 + D8 Pin = GPIO8 + D9 Pin = GPIO9 + D10 Pin = GPIO10 + D11 Pin = GPIO11 + D12 Pin = GPIO12 + D13 Pin = GPIO13 + D14 Pin = GPIO14 + D15 Pin = GPIO15 + D16 Pin = GPIO16 + D17 Pin = GPIO17 + D18 Pin = GPIO18 + D19 Pin = GPIO19 + D20 Pin = GPIO20 + D21 Pin = GPIO21 + D22 Pin = GPIO22 + D23 Pin = GPIO23 + D24 Pin = GPIO24 + D25 Pin = GPIO25 + D26 Pin = GPIO26 + D27 Pin = GPIO27 + D28 Pin = GPIO28 + D29 Pin = GPIO29 +) + +// Analog pins +const ( + A0 Pin = D26 + A1 Pin = D27 + A2 Pin = D28 + A3 Pin = D29 +) + +// Onboard LEDs +const ( + NEOPIXEL = GPIO16 + WS2812 = GPIO16 +) + +// I2C pins +const ( + I2C0_SDA_PIN Pin = D0 + I2C0_SCL_PIN Pin = D1 + + I2C1_SDA_PIN Pin = D2 + I2C1_SCL_PIN Pin = D3 +) + +// SPI pins +const ( + SPI0_SCK_PIN Pin = D6 + SPI0_SDO_PIN Pin = D3 + SPI0_SDI_PIN Pin = D4 + + SPI1_SCK_PIN Pin = D10 + SPI1_SDO_PIN Pin = D11 + SPI1_SDI_PIN Pin = D12 +) + +// Onboard crystal oscillator frequency, in MHz. +const ( + xoscFreq = 12 // MHz +) + +// UART pins +const ( + UART0_TX_PIN = GPIO0 + UART0_RX_PIN = GPIO1 + UART_TX_PIN = UART0_TX_PIN + UART_RX_PIN = UART0_RX_PIN + UART1_TX_PIN = GPIO8 + UART1_RX_PIN = GPIO9 +) + +var DefaultUART = UART0 + +// USB CDC identifiers +const ( + usb_STRING_PRODUCT = "RP2040-Zero" + usb_STRING_MANUFACTURER = "Waveshare" +) + +var ( + usb_VID uint16 = 0x2e8a + usb_PID uint16 = 0x0003 +) diff --git a/emb/machine/board_waveshare_rp2040_tiny.go b/emb/machine/board_waveshare_rp2040_tiny.go new file mode 100644 index 00000000..a3ef3540 --- /dev/null +++ b/emb/machine/board_waveshare_rp2040_tiny.go @@ -0,0 +1,121 @@ +//go:build waveshare_rp2040_tiny + +// This file contains the pin mappings for the Waveshare RP2040-Tiny boards. +// +// Waveshare RP2040-Tiny is a microcontroller using the Raspberry Pi RP2040 chip. +// +// - https://www.waveshare.com/wiki/RP2040-Tiny +package machine + +// Digital Pins +const ( + GP0 Pin = GPIO0 + GP1 Pin = GPIO1 + GP2 Pin = GPIO2 + GP3 Pin = GPIO3 + GP4 Pin = GPIO4 + GP5 Pin = GPIO5 + GP6 Pin = GPIO6 + GP7 Pin = GPIO7 + GP8 Pin = GPIO8 + GP9 Pin = GPIO9 + GP10 Pin = GPIO10 + GP11 Pin = GPIO11 + GP12 Pin = GPIO12 + GP13 Pin = GPIO13 + GP14 Pin = GPIO14 + GP15 Pin = GPIO15 + GP16 Pin = GPIO16 + GP17 Pin = NoPin + GP18 Pin = NoPin + GP19 Pin = NoPin + GP20 Pin = NoPin + GP21 Pin = NoPin + GP22 Pin = NoPin + GP23 Pin = NoPin + GP24 Pin = GPIO24 + GP25 Pin = GPIO25 + GP26 Pin = GPIO26 + GP27 Pin = GPIO27 + GP28 Pin = GPIO28 + GP29 Pin = GPIO29 +) + +// Analog pins +const ( + A0 Pin = GP26 + A1 Pin = GP27 + A2 Pin = GP28 + A3 Pin = GP29 +) + +// Onboard LEDs +const ( + LED = GP16 + WS2812 = GP16 +) + +// I2C pins +const ( + I2C0_SDA_PIN Pin = GP0 + I2C0_SCL_PIN Pin = GP1 + I2C1_SDA_PIN Pin = GP2 + I2C1_SCL_PIN Pin = GP3 + + // default I2C0 + I2C_SDA_PIN Pin = I2C0_SDA_PIN + I2C_SCL_PIN Pin = I2C0_SCL_PIN +) + +// SPI pins +const ( + SPI0_RX_PIN Pin = GP0 + SPI0_CSN_PIN Pin = GP1 + SPI0_SCK_PIN Pin = GP2 + SPI0_TX_PIN Pin = GP3 + SPI0_SDO_PIN Pin = SPI0_TX_PIN + SPI0_SDI_PIN Pin = SPI0_RX_PIN + + SPI1_RX_PIN Pin = GP8 + SPI1_CSN_PIN Pin = GP9 + SPI1_SCK_PIN Pin = GP10 + SPI1_TX_PIN Pin = GP11 + SPI1_SDO_PIN Pin = SPI1_TX_PIN + SPI1_SDI_PIN Pin = SPI1_RX_PIN + + // default SPI0 + SPI_RX_PIN Pin = SPI0_RX_PIN + SPI_CSN_PIN Pin = SPI0_CSN_PIN + SPI_SCK_PIN Pin = SPI0_SCK_PIN + SPI_TX_PIN Pin = SPI0_TX_PIN + SPI_SDO_PIN Pin = SPI0_TX_PIN + SPI_SDI_PIN Pin = SPI0_RX_PIN +) + +// Onboard crystal oscillator frequency, in MHz. +const ( + xoscFreq = 12 // MHz +) + +// UART pins +const ( + UART0_TX_PIN = GP0 + UART0_RX_PIN = GP1 + UART1_TX_PIN = GP8 + UART1_RX_PIN = GP9 + + // default UART0 + UART_TX_PIN = UART0_TX_PIN + UART_RX_PIN = UART0_RX_PIN +) + +// USB CDC identifiers +const ( + usb_STRING_PRODUCT = "RP2040-Tiny" + usb_STRING_MANUFACTURER = "Waveshare" +) + +var ( + usb_VID uint16 = 0x2e8a + usb_PID uint16 = 0x0003 +) diff --git a/emb/machine/board_wioterminal.go b/emb/machine/board_wioterminal.go new file mode 100644 index 00000000..6997120b --- /dev/null +++ b/emb/machine/board_wioterminal.go @@ -0,0 +1,432 @@ +//go:build wioterminal + +package machine + +// used to reset into bootloader +const resetMagicValue = 0xf01669ef + +const ( + ADC0 = A0 + ADC1 = A1 + ADC2 = A2 + ADC3 = A3 + ADC4 = A4 + ADC5 = A5 + ADC6 = A6 + ADC7 = A7 + ADC8 = A8 + + LED = PIN_LED + BUTTON = BUTTON_1 +) + +const ( + // https://github.com/Seeed-Studio/ArduinoCore-samd/blob/master/variants/wio_terminal/variant.h + + // LEDs + PIN_LED_13 = PA15 + PIN_LED_RXL = PA15 + PIN_LED_TXL = PA15 + PIN_LED = PIN_LED_13 + PIN_LED2 = PIN_LED_RXL + PIN_LED3 = PIN_LED_TXL + LED_BUILTIN = PIN_LED_13 + PIN_NEOPIXEL = PA15 + + //Digital PINs + D0 = PB08 + D1 = PB09 + D2 = PA07 + D3 = PB04 + D4 = PB05 + D5 = PB06 + D6 = PA04 + D7 = PB07 + D8 = PA06 + + //Analog PINs + A0 = PB08 // ADC/AIN[0] + A1 = PB09 // ADC/AIN[2] + A2 = PA07 // ADC/AIN[3] + A3 = PB04 // ADC/AIN[4] + A4 = PB05 // ADC/AIN[5] + A5 = PB06 // ADC/AIN[10] + A6 = PA04 // ADC/AIN[10] + A7 = PB07 // ADC/AIN[10] + A8 = PA06 // ADC/AIN[10] + + // 3.3V || 5V + // BCM2 || 5V + // BCM3 || GND + // BCM4 || BCM14 + // GND || BCM15 + // BCM17 || BCM18 + // BCM27 || GND + // BCM22 || BCM23 + // GND || BCM24 + // BCM10 || GND + // BCM9 || BCM25 + // BCM11 || BCM8 + // GND || BCM7 + // BCM0 || BCM1 + // BCM5 || GND + // BCM6 || BCM12 + // BCM13 || GND + // BCM19 || BCM16 + // BCM26 || BCM20 + // GND || BCM21 + + //PIN DEFINE FOR RPI + BCM0 = PA13 // I2C Wire1 + BCM1 = PA12 // I2C Wire1 + BCM2 = PA17 // I2C Wire2 + BCM3 = PA16 // I2C Wire2 + BCM4 = PB14 // GCLK + BCM5 = PB12 // GCLK + BCM6 = PB13 // GCLK + BCM7 = PA05 // DAC1 + BCM8 = PB01 // SPI SS + BCM9 = PB00 // SPI SDI + BCM10 = PB02 // SPI SDO + BCM11 = PB03 // SPI SCK + BCM12 = PB06 + BCM13 = PA04 + BCM14 = PB27 // UART Serial1 + BCM15 = PB26 // UART Serial1 + BCM16 = PB07 + BCM17 = PA02 // DAC0 + BCM18 = PB28 // FPC Digital & AD pins + BCM19 = PA20 // WIO_IR + BCM20 = PA21 // I2S SDO + BCM21 = PA22 // I2S SDI + BCM22 = PB09 + BCM23 = PA07 + BCM24 = PB04 + BCM25 = PB05 + BCM26 = PA06 + BCM27 = PB08 + + // FPC NEW DEFINE + FPC1 = PB28 // FPC Digital & AD pins + FPC2 = PB17 + FPC3 = PB29 + FPC4 = PA14 + FPC5 = PC01 + FPC6 = PC02 + FPC7 = PC03 + FPC8 = PC04 + FPC9 = PC31 + FPC10 = PD00 + + // RPI Analog RPIs + RPI_A0 = PB08 + RPI_A1 = PB09 + RPI_A2 = PA07 + RPI_A3 = PB04 + RPI_A4 = PB05 + RPI_A5 = PB06 + RPI_A6 = PA04 + RPI_A7 = PB07 + RPI_A8 = PA06 + + PIN_DAC0 = PA02 + PIN_DAC1 = PA05 + + // FPO Analog RPIs + //FPC_A7 = FPC_D7 + //FPC_A8 = FPC_D8 + //FPC_A9 = FPC_D9 + //FPC_A11 = FPC_D11 + //FPC_A12 = FPC_D12 + //FPC_A13 = FPC_D13 + + // USB + PIN_USB_DM = PA24 + PIN_USB_DP = PA25 + PIN_USB_HOST_ENABLE = PA27 + + // BUTTON + BUTTON_1 = PC26 + BUTTON_2 = PC27 + BUTTON_3 = PC28 + WIO_KEY_A = PC26 + WIO_KEY_B = PC27 + WIO_KEY_C = PC28 + + // SWITCH + SWITCH_X = PD20 + SWITCH_Y = PD12 + SWITCH_Z = PD09 + SWITCH_B = PD08 + SWITCH_U = PD10 + + WIO_5S_UP = PD20 + WIO_5S_LEFT = PD12 + WIO_5S_RIGHT = PD09 + WIO_5S_DOWN = PD08 + WIO_5S_PRESS = PD10 + + // IRQ0 : RTL8720D + IRQ0 = PC20 + + // BUZZER_CTR + BUZZER_CTR = PD11 + WIO_BUZZER = PD11 + + // MIC_INPUT + MIC_INPUT = PC30 + WIO_MIC = PC30 + + // GCLK + GCLK0 = PB14 + GCLK1 = PB12 + GCLK2 = PB13 + + // Serial interfaces + // Serial1 + PIN_SERIAL1_RX = PB27 + PIN_SERIAL1_TX = PB26 + + // Serial2 : RTL8720D + PIN_SERIAL2_RX = PC23 + PIN_SERIAL2_TX = PC22 + + // Wire Interfaces + // I2C Wire2 + // I2C1 + PIN_WIRE_SDA = PA17 + PIN_WIRE_SCL = PA16 + SDA = PIN_WIRE_SDA + SCL = PIN_WIRE_SCL + + // I2C Wire1 + // I2C0 : LIS3DHTR and ATECC608 + PIN_WIRE1_SDA = PA13 + PIN_WIRE1_SCL = PA12 + + SDA1 = PIN_WIRE1_SDA + SCL1 = PIN_WIRE1_SCL + + PIN_GYROSCOPE_WIRE_SDA = PIN_WIRE1_SDA + PIN_GYROSCOPE_WIRE_SCL = PIN_WIRE1_SCL + GYROSCOPE_INT1 = PC21 + + WIO_LIS3DH_SDA = PIN_WIRE1_SDA + WIO_LIS3DH_SCL = PIN_WIRE1_SCL + WIO_LIS3DH_INT = PC21 + + // SPI + PIN_SPI_SDI = PB00 + PIN_SPI_SDO = PB02 + PIN_SPI_SCK = PB03 + PIN_SPI_SS = PB01 + + SS = PIN_SPI_SS + SDO = PIN_SPI_SDO + SDI = PIN_SPI_SDI + SCK = PIN_SPI_SCK + + // SPI1 RTL8720D_SPI + PIN_SPI1_SDI = PC24 + PIN_SPI1_SDO = PB24 + PIN_SPI1_SCK = PB25 + PIN_SPI1_SS = PC25 + + SS1 = PIN_SPI1_SS + SDO1 = PIN_SPI1_SDO + SDI1 = PIN_SPI1_SDI + SCK1 = PIN_SPI1_SCK + + // SPI2 SD_SPI + PIN_SPI2_SDI = PC18 + PIN_SPI2_SDO = PC16 + PIN_SPI2_SCK = PC17 + PIN_SPI2_SS = PC19 + + SS2 = PIN_SPI2_SS + SDO2 = PIN_SPI2_SDO + SDI2 = PIN_SPI2_SDI + SCK2 = PIN_SPI2_SCK + + // SPI3 LCD_SPI + PIN_SPI3_SDI = PB18 + PIN_SPI3_SDO = PB19 + PIN_SPI3_SCK = PB20 + PIN_SPI3_SS = PB21 + + SS3 = PIN_SPI3_SS + SDO3 = PIN_SPI3_SDO + SDI3 = PIN_SPI3_SDI + SCK3 = PIN_SPI3_SCK + + // Needed for SD library + SDCARD_SDI_PIN = PIN_SPI2_SDI + SDCARD_SDO_PIN = PIN_SPI2_SDO + SDCARD_SCK_PIN = PIN_SPI2_SCK + SDCARD_SS_PIN = PIN_SPI2_SS + SDCARD_DET_PIN = PD21 + + LCD_SDI_PIN = PIN_SPI3_SDI + LCD_SDO_PIN = PIN_SPI3_SDO + LCD_SCK_PIN = PIN_SPI3_SCK + LCD_SS_PIN = PIN_SPI3_SS + LCD_DC = PC06 + LCD_RESET = PC07 + LCD_BACKLIGHT = PC05 + + // 4 WIRE LCD TOUCH + LCD_XL = PC10 + LCD_YU = PC11 + LCD_XR = PC12 + LCD_YD = PC13 + + // Needed for RTL8720D + RTL8720D_SDI_PIN = PIN_SPI1_SDI + RTL8720D_SDO_PIN = PIN_SPI1_SDO + RTL8720D_SCK_PIN = PIN_SPI1_SCK + RTL8720D_SS_PIN = PIN_SPI1_SS + + //QSPI Pins + PIN_QSPI_IO0 = PA08 + PIN_QSPI_IO1 = PA09 + PIN_QSPI_IO2 = PA10 + PIN_QSPI_IO3 = PA11 + PIN_QSPI_SCK = PB10 + PIN_QSPI_CS = PB11 + + // I2S Interfaces + PIN_I2S_FS = PA20 + PIN_I2S_SCK = PB16 + PIN_I2S_SDO = PA22 + PIN_I2S_SDI = PA21 + + I2S_LRCLK = PA20 + I2S_BLCK = PB16 + I2S_SDOUT = PA22 + I2S_SDIN = PA21 + + // RTL8720D Interfaces + RTL8720D_CHIP_PU = PA18 + RTL8720D_GPIO0 = PA19 // SYNC + + // SWD + SWDCLK = PA30 + SWDIO = PA31 + SWO = PB30 + + // light sensor + WIO_LIGHT = PD01 + + // ir sensor + WIO_IR = PB31 + + // OUTPUT_CTR + OUTPUT_CTR_5V = PC14 + OUTPUT_CTR_3V3 = PC15 +) + +// USBCDC pins +const ( + USBCDC_DM_PIN = PIN_USB_DM + USBCDC_DP_PIN = PIN_USB_DP +) + +// UART1 pins +const ( + UART_TX_PIN = PIN_SERIAL1_TX + UART_RX_PIN = PIN_SERIAL1_RX +) + +// UART2 pins RTL8720D +const ( + UART2_TX_PIN = PIN_SERIAL2_TX + UART2_RX_PIN = PIN_SERIAL2_RX +) + +var ( + DefaultUART = UART1 + + UART1 = &sercomUSART2 + + // RTL8720D (tx: PC22, rx: PC23) + UART2 = &sercomUSART1 + + // RTL8720D (tx: PB24, rx: PC24) + UART3 = &sercomUSART0 + + // Right-hand grove port (tx: D0, rx: D1) + UART4 = &sercomUSART4 +) + +// I2C pins +const ( + SDA1_PIN = PA17 // SDA: SERCOM3/PAD[0] + SCL1_PIN = PA16 // SCL: SERCOM3/PAD[1] + + SDA0_PIN = PA13 // SDA: SERCOM4/PAD[0] + SCL0_PIN = PA12 // SCL: SERCOM4/PAD[1] + + SDA_PIN = SDA1_PIN + SCL_PIN = SCL1_PIN +) + +// I2C on the Wio Terminal +var ( + I2C0 = sercomI2CM4 + I2C1 = sercomI2CM3 +) + +// I2S pins +const ( + I2S_SCK_PIN = BCM18 + I2S_SDO_PIN = BCM21 + I2S_SDI_PIN = BCM20 + I2S_WS_PIN = BCM19 +) + +// SPI pins +const ( + SPI0_SCK_PIN = SCK // SCK: SERCOM5/PAD[1] + SPI0_SDO_PIN = SDO // SDO: SERCOM5/PAD[0] + SPI0_SDI_PIN = SDI // SDI: SERCOM5/PAD[2] + + // RTL8720D + SPI1_SCK_PIN = SCK1 // SCK: SERCOM0/PAD[1] + SPI1_SDO_PIN = SDO1 // SDO: SERCOM0/PAD[0] + SPI1_SDI_PIN = SDI1 // SDI: SERCOM0/PAD[2] + + // SD + SPI2_SCK_PIN = SCK2 // SCK: SERCOM6/PAD[1] + SPI2_SDO_PIN = SDO2 // SDO: SERCOM6/PAD[0] + SPI2_SDI_PIN = SDI2 // SDI: SERCOM6/PAD[2] + + // LCD + SPI3_SCK_PIN = SCK3 // SCK: SERCOM7/PAD[1] + SPI3_SDO_PIN = SDO3 // SDO: SERCOM7/PAD[3] + SPI3_SDI_PIN = SDI3 // SDI: SERCOM7/PAD[2] +) + +// SPI on the Wio Terminal +var ( + SPI0 = sercomSPIM5 + + // RTL8720D + SPI1 = sercomSPIM0 + + // SD + SPI2 = sercomSPIM6 + + // LCD + SPI3 = sercomSPIM7 +) + +// USB CDC identifiers +const ( + usb_STRING_PRODUCT = "Seeed Wio Terminal" + usb_STRING_MANUFACTURER = "Seeed" +) + +var ( + usb_VID uint16 = 0x2886 + usb_PID uint16 = 0x802D +) diff --git a/emb/machine/board_x9pro.go b/emb/machine/board_x9pro.go new file mode 100644 index 00000000..88468e97 --- /dev/null +++ b/emb/machine/board_x9pro.go @@ -0,0 +1,30 @@ +//go:build x9pro + +package machine + +// https://hackaday.io/project/144350-hacking-wearables-for-mental-health-and-more/details +const ( + LED Pin = 4 // HR LED pin + UART_TX_PIN Pin = NoPin + UART_RX_PIN Pin = NoPin + SCL_PIN Pin = NoPin + SDA_PIN Pin = NoPin + SPI0_SCK_PIN Pin = 18 + SPI0_SDI_PIN Pin = 19 + SPI0_SDO_PIN Pin = 20 +) + +// LCD pins. +const ( + OLED_CS Pin = 15 // chip select + OLED_RES Pin = 14 // reset pin + OLED_DC Pin = 13 // data/command + OLED_SCK Pin = 12 // SPI clock + OLED_SDO Pin = 11 // SPI SDO (chip-out, peripheral-in) + OLED_LED_POW Pin = 16 + OLED_IC_POW Pin = 17 +) + +const HasLowFrequencyCrystal = true + +var DefaultUART = UART0 diff --git a/emb/machine/board_xiao-ble.go b/emb/machine/board_xiao-ble.go new file mode 100644 index 00000000..b4168d8d --- /dev/null +++ b/emb/machine/board_xiao-ble.go @@ -0,0 +1,111 @@ +//go:build xiao_ble + +// This file contains the pin mappings for the Seeed XIAO BLE nRF52840 [Sense] boards. +// +// Seeed XIAO BLE is an ultra-small size, ultra-low power Bluetooth development board based on the Nordic nRF52840. +// It features an onboard Bluetooth antenna, onboard battery charging chip, and 21*17.5mm thumb size, which makes it ideal for IoT projects. +// +// Seeed XIAO BLE nRF52840 Sense is a tiny Bluetooth LE development board designed for IoT and AI applications. +// It features an onboard antenna, 6 Dof IMU, microphone, all of which make it an ideal board to run AI using TinyML and TensorFlow Lite. +// +// SoftDevice (s140v7) is pre-flashed on this board already. +// See https://github.com/tinygo-org/bluetooth +// +// - https://www.seeedstudio.com/Seeed-XIAO-BLE-nRF52840-p-5201.html +// - https://www.seeedstudio.com/Seeed-XIAO-BLE-Sense-nRF52840-p-5253.html +// +// - https://wiki.seeedstudio.com/XIAO_BLE/ +// - https://github.com/Seeed-Studio/ArduinoCore-mbed/tree/master/variants/SEEED_XIAO_NRF52840_SENSE +package machine + +const HasLowFrequencyCrystal = true + +// Digital Pins +const ( + D0 Pin = P0_02 + D1 Pin = P0_03 + D2 Pin = P0_28 + D3 Pin = P0_29 + D4 Pin = P0_04 + D5 Pin = P0_05 + D6 Pin = P1_11 + D7 Pin = P1_12 + D8 Pin = P1_13 + D9 Pin = P1_14 + D10 Pin = P1_15 +) + +// Analog pins +const ( + A0 Pin = P0_02 + A1 Pin = P0_03 + A2 Pin = P0_28 + A3 Pin = P0_29 + A4 Pin = P0_04 + A5 Pin = P0_05 +) + +// Onboard LEDs +const ( + LED = LED_CHG + LED1 = LED_RED + LED2 = LED_GREEN + LED3 = LED_BLUE + LED_CHG = P0_17 + LED_RED = P0_26 + LED_GREEN = P0_30 + LED_BLUE = P0_06 +) + +// UART0 pins +const ( + UART_RX_PIN = P1_12 + UART_TX_PIN = P1_11 +) + +// I2C pins +const ( + // Defaults to internal + SDA_PIN = SDA1_PIN + SCL_PIN = SCL1_PIN + + // I2C0 (external) pins + SDA0_PIN = P0_04 + SCL0_PIN = P0_05 + + // I2C1 (internal) pins + SDA1_PIN = P0_07 + SCL1_PIN = P0_27 +) + +// SPI pins +const ( + SPI0_SCK_PIN = P1_13 + SPI0_SDO_PIN = P1_14 + SPI0_SDI_PIN = P1_15 +) + +// Peripherals +const ( + LSM_PWR = P1_08 // IMU (LSM6DS3TR) power + LSM_INT = P0_11 // IMU (LSM6DS3TR) interrupt + + MIC_PWR = P1_10 // Microphone (MSM261D3526H1CPM) power + MIC_CLK = P1_00 + MIC_DIN = P0_16 +) + +// USB CDC identifiers +const ( + usb_STRING_PRODUCT = "XIAO nRF52840 Sense" + usb_STRING_MANUFACTURER = "Seeed" +) + +var ( + usb_VID uint16 = 0x2886 + usb_PID uint16 = 0x8045 +) + +var ( + DefaultUART = UART0 +) diff --git a/emb/machine/board_xiao-esp32c3.go b/emb/machine/board_xiao-esp32c3.go new file mode 100644 index 00000000..5a2cf2ee --- /dev/null +++ b/emb/machine/board_xiao-esp32c3.go @@ -0,0 +1,53 @@ +//go:build xiao_esp32c3 + +// This file contains the pin mappings for the Seeed XIAO ESP32C3 boards. +// +// Seeed Studio XIAO ESP32C3 is an IoT mini development board based on +// the Espressif ESP32-C3 WiFi/Bluetooth dual-mode chip. +// +// - https://www.seeedstudio.com/Seeed-XIAO-ESP32C3-p-5431.html +// - https://wiki.seeedstudio.com/XIAO_ESP32C3_Getting_Started/ + +package machine + +// Digital Pins +const ( + D0 = GPIO2 + D1 = GPIO3 + D2 = GPIO4 + D3 = GPIO5 + D4 = GPIO6 + D5 = GPIO7 + D6 = GPIO21 + D7 = GPIO20 + D8 = GPIO8 + D9 = GPIO9 + D10 = GPIO10 +) + +// Analog pins +const ( + A0 = GPIO2 + A1 = GPIO3 + A2 = GPIO4 + A3 = GPIO5 +) + +// UART pins +const ( + UART_RX_PIN = GPIO20 + UART_TX_PIN = GPIO21 +) + +// I2C pins +const ( + SDA_PIN = GPIO6 + SCL_PIN = GPIO7 +) + +// SPI pins +const ( + SPI_SCK_PIN = GPIO8 + SPI_SDI_PIN = GPIO9 + SPI_SDO_PIN = GPIO10 +) diff --git a/emb/machine/board_xiao-rp2040.go b/emb/machine/board_xiao-rp2040.go new file mode 100644 index 00000000..b0103145 --- /dev/null +++ b/emb/machine/board_xiao-rp2040.go @@ -0,0 +1,90 @@ +//go:build xiao_rp2040 + +// This file contains the pin mappings for the Seeed XIAO RP2040 boards. +// +// XIAO RP2040 is a microcontroller using the Raspberry Pi RP2040 chip. +// +// - https://wiki.seeedstudio.com/XIAO-RP2040/ +package machine + +// Digital Pins +const ( + D0 Pin = GPIO26 + D1 Pin = GPIO27 + D2 Pin = GPIO28 + D3 Pin = GPIO29 + D4 Pin = GPIO6 + D5 Pin = GPIO7 + D6 Pin = GPIO0 + D7 Pin = GPIO1 + D8 Pin = GPIO2 + D9 Pin = GPIO4 + D10 Pin = GPIO3 +) + +// Analog pins +const ( + A0 Pin = D0 + A1 Pin = D1 + A2 Pin = D2 + A3 Pin = D3 +) + +// Onboard LEDs +const ( + NEOPIXEL = GPIO12 + WS2812 = GPIO12 + NEO_PWR = GPIO11 + NEOPIXEL_POWER = GPIO11 + + LED = GPIO17 + LED_RED = GPIO17 + LED_GREEN = GPIO16 + LED_BLUE = GPIO25 +) + +// I2C pins +const ( + I2C0_SDA_PIN Pin = D2 + I2C0_SCL_PIN Pin = D3 + + I2C1_SDA_PIN Pin = D4 + I2C1_SCL_PIN Pin = D5 +) + +// SPI pins +const ( + SPI0_SCK_PIN Pin = D8 + SPI0_SDO_PIN Pin = D10 + SPI0_SDI_PIN Pin = D9 + + SPI1_SCK_PIN Pin = NoPin + SPI1_SDO_PIN Pin = NoPin + SPI1_SDI_PIN Pin = NoPin +) + +// Onboard crystal oscillator frequency, in MHz. +const ( + xoscFreq = 12 // MHz +) + +// UART pins +const ( + UART0_TX_PIN = GPIO0 + UART0_RX_PIN = GPIO1 + UART_TX_PIN = UART0_TX_PIN + UART_RX_PIN = UART0_RX_PIN +) + +var DefaultUART = UART0 + +// USB CDC identifiers +const ( + usb_STRING_PRODUCT = "XIAO RP2040" + usb_STRING_MANUFACTURER = "Seeed" +) + +var ( + usb_VID uint16 = 0x2e8a + usb_PID uint16 = 0x000a +) diff --git a/emb/machine/board_xiao.go b/emb/machine/board_xiao.go new file mode 100644 index 00000000..5bbb34d6 --- /dev/null +++ b/emb/machine/board_xiao.go @@ -0,0 +1,103 @@ +//go:build sam && atsamd21 && xiao + +package machine + +// used to reset into bootloader +const resetMagicValue = 0xf01669ef + +// GPIO Pins +const ( + D0 = PA02 // can be used for PWM or DAC + D1 = PA04 // PWM available + D2 = PA10 // PWM available + D3 = PA11 // PWM available + D4 = PA08 // can be used for PWM or I2C SDA + D5 = PA09 // can be used for PWM or I2C SCL + D6 = PB08 // can be used for PWM or UART1 TX + D7 = PB09 // can be used for PWM or UART1 RX + D8 = PA07 // can be used for PWM or SPI SCK + D9 = PA05 // can be used for PWM or SPI SDI + D10 = PA06 // can be used for PWM or SPI SDO +) + +// Analog pins +const ( + A0 = PA02 // ADC/AIN[0] + A1 = PA04 // ADC/AIN[4] + A2 = PA10 // ADC/AIN[18] + A3 = PA11 // ADC/AIN[19] + A4 = PA08 // ADC/AIN[16] + A5 = PA09 // ADC/AIN[17] + A6 = PB08 // ADC/AIN[2] + A7 = PB09 // ADC/AIN[3] + A8 = PA07 // ADC/AIN[7] + A9 = PA05 // ADC/AIN[6] + A10 = PA06 // ADC/AIN[5] +) + +const ( + LED = PA17 + LED_RXL = PA18 + LED_TXL = PA19 + LED2 = LED_RXL + LED3 = LED_TXL +) + +// USBCDC pins +const ( + USBCDC_DM_PIN = PA24 + USBCDC_DP_PIN = PA25 +) + +// UART1 pins +const ( + UART_TX_PIN = D6 + UART_RX_PIN = D7 +) + +// UART1 on the Xiao +var UART1 = &sercomUSART4 + +// I2C pins +const ( + SDA_PIN = PA08 // SDA: SERCOM2/PAD[0] + SCL_PIN = PA09 // SCL: SERCOM2/PAD[1] +) + +// I2C on the Xiao +var ( + I2C0 = sercomI2CM2 +) + +// SPI pins +const ( + SPI0_SCK_PIN = PA07 // SCK: SERCOM0/PAD[3] + SPI0_SDO_PIN = PA06 // SDO: SERCOM0/PAD[2] + SPI0_SDI_PIN = PA05 // SDI: SERCOM0/PAD[1] +) + +// SPI on the Xiao +var SPI0 = sercomSPIM0 + +// I2S pins +const ( + I2S_SCK_PIN = PA10 + I2S_SDO_PIN = PA08 + I2S_SDI_PIN = NoPin // TODO: figure out what this is on Xiao + I2S_WS_PIN = NoPin // TODO: figure out what this is on Xiao +) + +// USB CDC identifiers +const ( + usb_STRING_PRODUCT = "Seeed XIAO M0" + usb_STRING_MANUFACTURER = "Seeed" +) + +var ( + usb_VID uint16 = 0x2886 + usb_PID uint16 = 0x802F +) + +var ( + DefaultUART = UART1 +) diff --git a/emb/machine/buffer.go b/emb/machine/buffer.go new file mode 100644 index 00000000..ff79157e --- /dev/null +++ b/emb/machine/buffer.go @@ -0,0 +1,50 @@ +package machine + +import ( + "github.com/goplus/lib/emb/runtime/volatile" +) + +// RingBuffer is ring buffer implementation inspired by post at +// https://www.embeddedrelated.com/showthread/comp.arch.embedded/77084-1.php +type RingBuffer struct { + rxbuffer [bufferSize]volatile.Register8 + head volatile.Register8 + tail volatile.Register8 +} + +// NewRingBuffer returns a new ring buffer. +func NewRingBuffer() *RingBuffer { + return &RingBuffer{} +} + +// Used returns how many bytes in buffer have been used. +func (rb *RingBuffer) Used() uint8 { + return uint8(rb.head.Get() - rb.tail.Get()) +} + +// Put stores a byte in the buffer. If the buffer is already +// full, the method will return false. +func (rb *RingBuffer) Put(val byte) bool { + if rb.Used() != bufferSize { + rb.head.Set(rb.head.Get() + 1) + rb.rxbuffer[rb.head.Get()%bufferSize].Set(val) + return true + } + return false +} + +// Get returns a byte from the buffer. If the buffer is empty, +// the method will return a false as the second value. +func (rb *RingBuffer) Get() (byte, bool) { + if rb.Used() != 0 { + rb.tail.Set(rb.tail.Get() + 1) + return rb.rxbuffer[rb.tail.Get()%bufferSize].Get(), true + } + return 0, false +} + +// Clear resets the head and tail pointer to zero. +func (rb *RingBuffer) Clear() { + rb.head.Set(0) + rb.tail.Set(0) +} diff --git a/emb/machine/buffer_atmega.go b/emb/machine/buffer_atmega.go new file mode 100644 index 00000000..a321eaec --- /dev/null +++ b/emb/machine/buffer_atmega.go @@ -0,0 +1,5 @@ +//go:build atmega + +package machine + +const bufferSize = 32 diff --git a/emb/machine/buffer_generic.go b/emb/machine/buffer_generic.go new file mode 100644 index 00000000..0d82b449 --- /dev/null +++ b/emb/machine/buffer_generic.go @@ -0,0 +1,5 @@ +//go:build !atmega + +package machine + +const bufferSize = 128 diff --git a/emb/machine/deviceid.go b/emb/machine/deviceid.go new file mode 100644 index 00000000..cb2e1d0c --- /dev/null +++ b/emb/machine/deviceid.go @@ -0,0 +1,17 @@ +//go:build rp2040 || nrf || sam + +package machine + +// DeviceID returns an identifier that is unique within +// a particular chipset. +// +// The identity is one burnt into the MCU itself, or the +// flash chip at time of manufacture. +// +// It's possible that two different vendors may allocate +// the same DeviceID, so callers should take this into +// account if needing to generate a globally unique id. +// +// The length of the hardware ID is vendor-specific, but +// 8 bytes (64 bits) and 16 bytes (128 bits) are common. +var _ = (func() []byte)(DeviceID) diff --git a/emb/machine/flash.go b/emb/machine/flash.go new file mode 100644 index 00000000..c89c091b --- /dev/null +++ b/emb/machine/flash.go @@ -0,0 +1,77 @@ +//go:build nrf || nrf51 || nrf52 || nrf528xx || stm32f4 || stm32l4 || stm32wlx || atsamd21 || atsamd51 || atsame5x || rp2040 || rp2350 + +package machine + +import ( + "errors" + "io" + "unsafe" +) + +//go:extern __flash_data_start +var flashDataStart [0]byte + +//go:extern __flash_data_end +var flashDataEnd [0]byte + +// Return the start of the writable flash area, aligned on a page boundary. This +// is usually just after the program and static data. +func FlashDataStart() uintptr { + pagesize := uintptr(eraseBlockSize()) + return (uintptr(unsafe.Pointer(&flashDataStart)) + pagesize - 1) &^ (pagesize - 1) +} + +// Return the end of the writable flash area. Usually this is the address one +// past the end of the on-chip flash. +func FlashDataEnd() uintptr { + return uintptr(unsafe.Pointer(&flashDataEnd)) +} + +var ( + errFlashCannotErasePage = errors.New("cannot erase flash page") + errFlashInvalidWriteLength = errors.New("write flash data must align to correct number of bits") + errFlashNotAllowedWriteData = errors.New("not allowed to write flash data") + errFlashCannotWriteData = errors.New("cannot write flash data") + errFlashCannotReadPastEOF = errors.New("cannot read beyond end of flash data") + errFlashCannotWritePastEOF = errors.New("cannot write beyond end of flash data") + errFlashCannotErasePastEOF = errors.New("cannot erase beyond end of flash data") +) + +// BlockDevice is the raw device that is meant to store flash data. +type BlockDevice interface { + // ReadAt reads the given number of bytes from the block device. + io.ReaderAt + + // WriteAt writes the given number of bytes to the block device. + io.WriterAt + + // Size returns the number of bytes in this block device. + Size() int64 + + // WriteBlockSize returns the block size in which data can be written to + // memory. It can be used by a client to optimize writes, non-aligned writes + // should always work correctly. + WriteBlockSize() int64 + + // EraseBlockSize returns the smallest erasable area on this particular chip + // in bytes. This is used for the block size in EraseBlocks. + // It must be a power of two, and may be as small as 1. A typical size is 4096. + EraseBlockSize() int64 + + // EraseBlocks erases the given number of blocks. An implementation may + // transparently coalesce ranges of blocks into larger bundles if the chip + // supports this. The start and len parameters are in block numbers, use + // EraseBlockSize to map addresses to blocks. + EraseBlocks(start, len int64) error +} + +// pad data if needed so it is long enough for correct byte alignment on writes. +func flashPad(p []byte, writeBlockSize int) []byte { + overflow := len(p) % writeBlockSize + if overflow != 0 { + for i := 0; i < writeBlockSize-overflow; i++ { + p = append(p, 0xff) + } + } + return p +} diff --git a/emb/machine/i2c.go b/emb/machine/i2c.go new file mode 100644 index 00000000..3b1b4dd4 --- /dev/null +++ b/emb/machine/i2c.go @@ -0,0 +1,92 @@ +//go:build !baremetal || atmega || nrf || sam || stm32 || fe310 || k210 || rp2040 || rp2350 || mimxrt1062 || (esp32c3 && !m5stamp_c3) || esp32 + +package machine + +import ( + "errors" +) + +// If you are getting a compile error on this line please check to see you've +// correctly implemented the methods on the I2C type. They must match +// the i2cController interface method signatures type to type perfectly. +// If not implementing the I2C type please remove your target from the build tags +// at the top of this file. +var _ interface { // 2 + Configure(config I2CConfig) error + Tx(addr uint16, w, r []byte) error + SetBaudRate(br uint32) error +} = (*I2C)(nil) + +// TWI_FREQ is the I2C bus speed. Normally either 100 kHz, or 400 kHz for high-speed bus. +// +// Deprecated: use 100 * machine.KHz or 400 * machine.KHz instead. +const ( + TWI_FREQ_100KHZ = 100000 + TWI_FREQ_400KHZ = 400000 +) + +var ( + errI2CWriteTimeout = errors.New("I2C timeout during write") + errI2CReadTimeout = errors.New("I2C timeout during read") + errI2CBusReadyTimeout = errors.New("I2C timeout on bus ready") + errI2CSignalStartTimeout = errors.New("I2C timeout on signal start") + errI2CSignalReadTimeout = errors.New("I2C timeout on signal read") + errI2CSignalStopTimeout = errors.New("I2C timeout on signal stop") + errI2CAckExpected = errors.New("I2C error: expected ACK not NACK") + errI2CBusError = errors.New("I2C bus error") + errI2COverflow = errors.New("I2C receive buffer overflow") + errI2COverread = errors.New("I2C transmit buffer overflow") + errI2CNotImplemented = errors.New("I2C operation not yet implemented") +) + +// I2CTargetEvent reflects events on the I2C bus +type I2CTargetEvent uint8 + +const ( + // I2CReceive indicates target has received a message from the controller. + I2CReceive I2CTargetEvent = iota + + // I2CRequest indicates the controller is expecting a message from the target. + I2CRequest + + // I2CFinish indicates the controller has ended the transaction. + // + // I2C controllers can chain multiple receive/request messages without + // relinquishing the bus by doing 'restarts'. I2CFinish indicates the + // bus has been relinquished by an I2C 'stop'. + I2CFinish +) + +// I2CMode determines if an I2C peripheral is in Controller or Target mode. +type I2CMode int + +const ( + // I2CModeController represents an I2C peripheral in controller mode. + I2CModeController I2CMode = iota + + // I2CModeTarget represents an I2C peripheral in target mode. + I2CModeTarget +) + +// WriteRegister transmits first the register and then the data to the +// peripheral device. +// +// Many I2C-compatible devices are organized in terms of registers. This method +// is a shortcut to easily write to such registers. Also, it only works for +// devices with 7-bit addresses, which is the vast majority. +func (i2c *I2C) WriteRegister(address uint8, register uint8, data []byte) error { + buf := make([]uint8, len(data)+1) + buf[0] = register + copy(buf[1:], data) + return i2c.Tx(uint16(address), buf, nil) +} + +// ReadRegister transmits the register, restarts the connection as a read +// operation, and reads the response. +// +// Many I2C-compatible devices are organized in terms of registers. This method +// is a shortcut to easily read such registers. Also, it only works for devices +// with 7-bit addresses, which is the vast majority. +func (i2c *I2C) ReadRegister(address uint8, register uint8, data []byte) error { + return i2c.Tx(uint16(address), []byte{register}, data) +} diff --git a/emb/machine/i2s.go b/emb/machine/i2s.go new file mode 100644 index 00000000..13dc80f6 --- /dev/null +++ b/emb/machine/i2s.go @@ -0,0 +1,80 @@ +//go:build sam && atsamd21 + +// This is the definition for I2S bus functions. +// Actual implementations if available for any given hardware +// are to be found in its the board definition. +// +// For more info about I2S, see: https://en.wikipedia.org/wiki/I%C2%B2S +// + +package machine + +import "errors" + +// If you are getting a compile error on this line please check to see you've +// correctly implemented the methods on the I2S type. They must match +// the interface method signatures type to type perfectly. +// If not implementing the I2S type please remove your target from the build tags +// at the top of this file. +var _ interface { + SetSampleFrequency(freq uint32) error + ReadMono(b []uint16) (int, error) + ReadStereo(b []uint32) (int, error) + WriteMono(b []uint16) (int, error) + WriteStereo(b []uint32) (int, error) + Enable(enabled bool) +} = (*I2S)(nil) + +type I2SMode uint8 +type I2SStandard uint8 +type I2SClockSource uint8 +type I2SDataFormat uint8 + +const ( + I2SModeSource I2SMode = iota + I2SModeReceiver + I2SModePDM + I2SModeSourceReceiver +) + +const ( + I2StandardPhilips I2SStandard = iota + I2SStandardMSB + I2SStandardLSB +) + +const ( + I2SClockSourceInternal I2SClockSource = iota + I2SClockSourceExternal +) + +const ( + I2SDataFormatDefault I2SDataFormat = 0 + I2SDataFormat8bit = 8 + I2SDataFormat16bit = 16 + I2SDataFormat24bit = 24 + I2SDataFormat32bit = 32 +) + +var ( + ErrInvalidSampleFrequency = errors.New("i2s: invalid sample frequency") +) + +// All fields are optional and may not be required or used on a particular platform. +type I2SConfig struct { + // clock + SCK Pin + // word select + WS Pin + // data out + SDO Pin + // data in + SDI Pin + Mode I2SMode + Standard I2SStandard + ClockSource I2SClockSource + DataFormat I2SDataFormat + AudioFrequency uint32 + MainClockOutput bool + Stereo bool +} diff --git a/emb/machine/machine.go b/emb/machine/machine.go new file mode 100644 index 00000000..def03958 --- /dev/null +++ b/emb/machine/machine.go @@ -0,0 +1,64 @@ +package machine + +import "errors" + +var ( + ErrTimeoutRNG = errors.New("machine: RNG Timeout") + ErrClockRNG = errors.New("machine: RNG Clock Error") + ErrSeedRNG = errors.New("machine: RNG Seed Error") + ErrInvalidInputPin = errors.New("machine: invalid input pin") + ErrInvalidOutputPin = errors.New("machine: invalid output pin") + ErrInvalidClockPin = errors.New("machine: invalid clock pin") + ErrInvalidDataPin = errors.New("machine: invalid data pin") + ErrNoPinChangeChannel = errors.New("machine: no channel available for pin interrupt") +) + +// Device is the running program's chip name, such as "ATSAMD51J19A" or +// "nrf52840". It is not the same as the CPU name. +// +// The constant is some hardcoded default value if the program does not target a +// particular chip but instead runs in WebAssembly for example. +const Device = deviceName + +// Generic constants. +const ( + KHz = 1000 + MHz = 1000_000 + GHz = 1000_000_000 +) + +// PinMode sets the direction and pull mode of the pin. For example, PinOutput +// sets the pin as an output and PinInputPullup sets the pin as an input with a +// pull-up. +type PinMode uint8 + +type PinConfig struct { + Mode PinMode +} + +// Pin is a single pin on a chip, which may be connected to other hardware +// devices. It can either be used directly as GPIO pin or it can be used in +// other peripherals like ADC, I2C, etc. +type Pin uint8 + +// NoPin explicitly indicates "not a pin". Use this pin if you want to leave one +// of the pins in a peripheral unconfigured (if supported by the hardware). +const NoPin = Pin(0xff) + +// High sets this GPIO pin to high, assuming it has been configured as an output +// pin. It is hardware dependent (and often undefined) what happens if you set a +// pin to high that is not configured as an output pin. +func (p Pin) High() { + p.Set(true) +} + +// Low sets this GPIO pin to low, assuming it has been configured as an output +// pin. It is hardware dependent (and often undefined) what happens if you set a +// pin to low that is not configured as an output pin. +func (p Pin) Low() { + p.Set(false) +} + +type ADC struct { + Pin Pin +} diff --git a/emb/machine/machine_atmega.go b/emb/machine/machine_atmega.go new file mode 100644 index 00000000..7a59e5e0 --- /dev/null +++ b/emb/machine/machine_atmega.go @@ -0,0 +1,340 @@ +//go:build avr && atmega + +package machine + +import ( + "device/avr" + "runtime/interrupt" + "runtime/volatile" + "unsafe" +) + +// I2C on AVR. +type I2C struct { + srReg *volatile.Register8 + brReg *volatile.Register8 + crReg *volatile.Register8 + drReg *volatile.Register8 + + srPS0 byte + srPS1 byte + crEN byte + crINT byte + crSTO byte + crEA byte + crSTA byte +} + +// I2CConfig is used to store config info for I2C. +type I2CConfig struct { + Frequency uint32 +} + +// Configure is intended to setup the I2C interface. +func (i2c *I2C) Configure(config I2CConfig) error { + // Default I2C bus speed is 100 kHz. + if config.Frequency == 0 { + config.Frequency = 100 * KHz + } + + // Activate internal pullups for twi. + avr.PORTC.SetBits((avr.DIDR0_ADC4D | avr.DIDR0_ADC5D)) + + return i2c.SetBaudRate(config.Frequency) +} + +// SetBaudRate sets the communication speed for I2C. +func (i2c *I2C) SetBaudRate(br uint32) error { + // Initialize twi prescaler and bit rate. + i2c.srReg.SetBits((i2c.srPS0 | i2c.srPS1)) + + // twi bit rate formula from atmega128 manual pg. 204: + // SCL Frequency = CPU Clock Frequency / (16 + (2 * TWBR)) + // NOTE: TWBR should be 10 or higher for controller mode. + // It is 72 for a 16mhz board with 100kHz TWI + i2c.brReg.Set(uint8(((CPUFrequency() / br) - 16) / 2)) + + // Enable twi module. + i2c.crReg.Set(i2c.crEN) + + return nil +} + +// Tx does a single I2C transaction at the specified address. +// It clocks out the given address, writes the bytes in w, reads back len(r) +// bytes and stores them in r, and generates a stop condition on the bus. +func (i2c *I2C) Tx(addr uint16, w, r []byte) error { + if len(w) != 0 { + i2c.start(uint8(addr), true) // start transmission for writing + for _, b := range w { + i2c.writeByte(b) + } + } + if len(r) != 0 { + i2c.start(uint8(addr), false) // re-start transmission for reading + for i := range r { // read each char + r[i] = i2c.readByte() + } + } + if len(w) != 0 || len(r) != 0 { + // Stop the transmission after it has been started. + i2c.stop() + } + return nil +} + +// start starts an I2C communication session. +func (i2c *I2C) start(address uint8, write bool) { + // Clear TWI interrupt flag, put start condition on SDA, and enable TWI. + i2c.crReg.Set((i2c.crINT | i2c.crSTA | i2c.crEN)) + + // Wait till start condition is transmitted. + for !i2c.crReg.HasBits(i2c.crINT) { + } + + // Write 7-bit shifted peripheral address. + address <<= 1 + if !write { + address |= 1 // set read flag + } + i2c.writeByte(address) +} + +// stop ends an I2C communication session. +func (i2c *I2C) stop() { + // Send stop condition. + i2c.crReg.Set(i2c.crEN | i2c.crINT | i2c.crSTO) + + // Wait for stop condition to be executed on bus. + for !i2c.crReg.HasBits(i2c.crSTO) { + } +} + +// writeByte writes a single byte to the I2C bus. +func (i2c *I2C) writeByte(data byte) error { + // Write data to register. + i2c.drReg.Set(data) + + // Clear TWI interrupt flag and enable TWI. + i2c.crReg.Set(i2c.crEN | i2c.crINT) + + // Wait till data is transmitted. + for !i2c.crReg.HasBits(i2c.crINT) { + } + return nil +} + +// readByte reads a single byte from the I2C bus. +func (i2c *I2C) readByte() byte { + // Clear TWI interrupt flag and enable TWI. + i2c.crReg.Set(i2c.crEN | i2c.crINT | i2c.crEA) + + // Wait till read request is transmitted. + for !i2c.crReg.HasBits(i2c.crINT) { + } + + return byte(i2c.drReg.Get()) +} + +// Always use UART0 as the serial output. +var DefaultUART = UART0 + +// UART +var ( + // UART0 is the hardware serial port on the AVR. + UART0 = &_UART0 + _UART0 = UART{ + Buffer: NewRingBuffer(), + + dataReg: avr.UDR0, + baudRegH: avr.UBRR0H, + baudRegL: avr.UBRR0L, + statusRegA: avr.UCSR0A, + statusRegB: avr.UCSR0B, + statusRegC: avr.UCSR0C, + } +) + +func init() { + // Register the UART interrupt. + interrupt.New(irq_USART0_RX, _UART0.handleInterrupt) +} + +// UART on the AVR. +type UART struct { + Buffer *RingBuffer + + dataReg *volatile.Register8 + baudRegH *volatile.Register8 + baudRegL *volatile.Register8 + + statusRegA *volatile.Register8 + statusRegB *volatile.Register8 + statusRegC *volatile.Register8 +} + +// Configure the UART on the AVR. Defaults to 9600 baud on Arduino. +func (uart *UART) Configure(config UARTConfig) { + if config.BaudRate == 0 { + config.BaudRate = 9600 + } + + // Prescale formula for u2x mode from AVR MiniCore source code. + // Same as formula from specification but taking into account rounding error. + ps := (CPUFrequency()/4/config.BaudRate - 1) / 2 + uart.statusRegA.SetBits(avr.UCSR0A_U2X0) + + // Hardcoded exception for 57600 for compatibility with older bootloaders. + // Also, prescale cannot be > 4095, so switch back to non-u2x mode if the baud rate is too low. + if (CPUFrequency() == 16000000 && config.BaudRate == 57600) || ps > 0xfff { + ps = (CPUFrequency()/8/config.BaudRate - 1) / 2 + uart.statusRegA.ClearBits(avr.UCSR0A_U2X0) + } + + uart.baudRegH.Set(uint8(ps >> 8)) + uart.baudRegL.Set(uint8(ps & 0xff)) + + // enable RX, TX and RX interrupt + uart.statusRegB.Set(avr.UCSR0B_RXEN0 | avr.UCSR0B_TXEN0 | avr.UCSR0B_RXCIE0) + + // 8-bits data + uart.statusRegC.Set(avr.UCSR0C_UCSZ01 | avr.UCSR0C_UCSZ00) +} + +func (uart *UART) handleInterrupt(intr interrupt.Interrupt) { + // Read register to clear it. + data := uart.dataReg.Get() + + // Ensure no error. + if !uart.statusRegA.HasBits(avr.UCSR0A_FE0 | avr.UCSR0A_DOR0 | avr.UCSR0A_UPE0) { + // Put data from UDR register into buffer. + uart.Receive(byte(data)) + } +} + +// WriteByte writes a byte of data to the UART. +func (uart *UART) writeByte(c byte) error { + // Wait until UART buffer is not busy. + for !uart.statusRegA.HasBits(avr.UCSR0A_UDRE0) { + } + uart.dataReg.Set(c) // send char + return nil +} + +func (uart *UART) flush() {} + +// SPIConfig is used to store config info for SPI. +type SPIConfig struct { + Frequency uint32 + LSBFirst bool + Mode uint8 +} + +// SPI is for the Serial Peripheral Interface +// Data is taken from http://ww1.microchip.com/downloads/en/DeviceDoc/ATmega48A-PA-88A-PA-168A-PA-328-P-DS-DS40002061A.pdf page 169 and following +type SPI struct { + // The registers for the SPIx port set by the chip + spcr *volatile.Register8 + spdr *volatile.Register8 + spsr *volatile.Register8 + + spcrR0 byte + spcrR1 byte + spcrCPHA byte + spcrCPOL byte + spcrDORD byte + spcrSPE byte + spcrMSTR byte + + spsrI2X byte + spsrSPIF byte + + // The io pins for the SPIx port set by the chip + sck Pin + sdi Pin + sdo Pin + cs Pin +} + +// Configure is intended to setup the SPI interface. +func (s *SPI) Configure(config SPIConfig) error { + + // This is only here to help catch a bug with the configuration + // where a machine missed a value. + if s.spcr == (*volatile.Register8)(unsafe.Pointer(uintptr(0))) || + s.spsr == (*volatile.Register8)(unsafe.Pointer(uintptr(0))) || + s.spdr == (*volatile.Register8)(unsafe.Pointer(uintptr(0))) || + s.sck == 0 || s.sdi == 0 || s.sdo == 0 || s.cs == 0 { + return errSPIInvalidMachineConfig + } + + // Make the defaults meaningful + if config.Frequency == 0 { + config.Frequency = 4000000 + } + + // Default all port configuration bits to 0 for simplicity + s.spcr.Set(0) + s.spsr.Set(0) + + // Setup pins output configuration + s.sck.Configure(PinConfig{Mode: PinOutput}) + s.sdi.Configure(PinConfig{Mode: PinInput}) + s.sdo.Configure(PinConfig{Mode: PinOutput}) + + // Prevent CS glitches if the pin is enabled Low (0, default) + s.cs.High() + // If the CS pin is not configured as output the SPI port operates in + // slave mode. + s.cs.Configure(PinConfig{Mode: PinOutput}) + + frequencyDivider := CPUFrequency() / config.Frequency + + switch { + case frequencyDivider >= 128: + s.spcr.SetBits(s.spcrR0 | s.spcrR1) + case frequencyDivider >= 64: + s.spcr.SetBits(s.spcrR1) + case frequencyDivider >= 32: + s.spcr.SetBits(s.spcrR1) + s.spsr.SetBits(s.spsrI2X) + case frequencyDivider >= 16: + s.spcr.SetBits(s.spcrR0) + case frequencyDivider >= 8: + s.spcr.SetBits(s.spcrR0) + s.spsr.SetBits(s.spsrI2X) + case frequencyDivider >= 4: + // The clock is already set to all 0's. + default: // defaults to fastest which is /2 + s.spsr.SetBits(s.spsrI2X) + } + + switch config.Mode { + case Mode1: + s.spcr.SetBits(s.spcrCPHA) + case Mode2: + s.spcr.SetBits(s.spcrCPHA) + case Mode3: + s.spcr.SetBits(s.spcrCPHA | s.spcrCPOL) + default: // default is mode 0 + } + + if config.LSBFirst { + s.spcr.SetBits(s.spcrDORD) + } + + // enable SPI, set controller, set clock rate + s.spcr.SetBits(s.spcrSPE | s.spcrMSTR) + + return nil +} + +// Transfer writes the byte into the register and returns the read content +func (s *SPI) Transfer(b byte) (byte, error) { + s.spdr.Set(uint8(b)) + + for !s.spsr.HasBits(s.spsrSPIF) { + } + + return byte(s.spdr.Get()), nil +} diff --git a/emb/machine/machine_atmega1280.go b/emb/machine/machine_atmega1280.go new file mode 100644 index 00000000..ad33dcf8 --- /dev/null +++ b/emb/machine/machine_atmega1280.go @@ -0,0 +1,937 @@ +//go:build avr && atmega1280 + +package machine + +import ( + "device/avr" + "runtime/interrupt" + "runtime/volatile" +) + +const irq_USART0_RX = avr.IRQ_USART0_RX + +const ( + portA Pin = iota * 8 + portB + portC + portD + portE + portF + portG + portH + portJ + portK + portL +) + +const ( + PA0 = portA + 0 + PA1 = portA + 1 + PA2 = portA + 2 + PA3 = portA + 3 + PA4 = portA + 4 + PA5 = portA + 5 + PA6 = portA + 6 + PA7 = portA + 7 + PB0 = portB + 0 + PB1 = portB + 1 + PB2 = portB + 2 + PB3 = portB + 3 + PB4 = portB + 4 // peripherals: Timer2 channel A + PB5 = portB + 5 // peripherals: Timer1 channel A + PB6 = portB + 6 // peripherals: Timer1 channel B + PB7 = portB + 7 // peripherals: Timer0 channel A + PC0 = portC + 0 + PC1 = portC + 1 + PC2 = portC + 2 + PC3 = portC + 3 + PC4 = portC + 4 + PC5 = portC + 5 + PC6 = portC + 6 + PC7 = portC + 7 + PD0 = portD + 0 + PD1 = portD + 1 + PD2 = portD + 2 + PD3 = portD + 3 + PD7 = portD + 7 + PE0 = portE + 0 + PE1 = portE + 1 + PE3 = portE + 3 // peripherals: Timer3 channel A + PE4 = portE + 4 // peripherals: Timer3 channel B + PE5 = portE + 5 // peripherals: Timer3 channel C + PE6 = portE + 6 + PF0 = portF + 0 + PF1 = portF + 1 + PF2 = portF + 2 + PF3 = portF + 3 + PF4 = portF + 4 + PF5 = portF + 5 + PF6 = portF + 6 + PF7 = portF + 7 + PG0 = portG + 0 + PG1 = portG + 1 + PG2 = portG + 2 + PG5 = portG + 5 // peripherals: Timer0 channel B + PH0 = portH + 0 + PH1 = portH + 1 + PH3 = portH + 3 // peripherals: Timer4 channel A + PH4 = portH + 4 // peripherals: Timer4 channel B + PH5 = portH + 5 // peripherals: Timer4 channel C + PH6 = portH + 6 // peripherals: Timer0 channel B + PJ0 = portJ + 0 + PJ1 = portJ + 1 + PK0 = portK + 0 + PK1 = portK + 1 + PK2 = portK + 2 + PK3 = portK + 3 + PK4 = portK + 4 + PK5 = portK + 5 + PK6 = portK + 6 + PK7 = portK + 7 + PL0 = portL + 0 + PL1 = portL + 1 + PL2 = portL + 2 + PL3 = portL + 3 // peripherals: Timer5 channel A + PL4 = portL + 4 // peripherals: Timer5 channel B + PL5 = portL + 5 // peripherals: Timer5 channel C + PL6 = portL + 6 + PL7 = portL + 7 +) + +// getPortMask returns the PORTx register and mask for the pin. +func (p Pin) getPortMask() (*volatile.Register8, uint8) { + switch { + case p >= PA0 && p <= PA7: + return avr.PORTA, 1 << uint8(p-portA) + case p >= PB0 && p <= PB7: + return avr.PORTB, 1 << uint8(p-portB) + case p >= PC0 && p <= PC7: + return avr.PORTC, 1 << uint8(p-portC) + case p >= PD0 && p <= PD7: + return avr.PORTD, 1 << uint8(p-portD) + case p >= PE0 && p <= PE6: + return avr.PORTE, 1 << uint8(p-portE) + case p >= PF0 && p <= PF7: + return avr.PORTF, 1 << uint8(p-portF) + case p >= PG0 && p <= PG5: + return avr.PORTG, 1 << uint8(p-portG) + case p >= PH0 && p <= PH6: + return avr.PORTH, 1 << uint8(p-portH) + case p >= PJ0 && p <= PJ1: + return avr.PORTJ, 1 << uint8(p-portJ) + case p >= PK0 && p <= PK7: + return avr.PORTK, 1 << uint8(p-portK) + case p >= PL0 && p <= PL7: + return avr.PORTL, 1 << uint8(p-portL) + default: + return avr.PORTA, 255 + } +} + +// PWM is one PWM peripheral, which consists of a counter and two output +// channels (that can be connected to two fixed pins). You can set the frequency +// using SetPeriod, but only for all the channels in this PWM peripheral at +// once. +type PWM struct { + num uint8 +} + +var ( + Timer0 = PWM{0} // 8 bit timer for PB7 and PG5 + Timer1 = PWM{1} // 16 bit timer for PB5 and PB6 + Timer2 = PWM{2} // 8 bit timer for PB4 and PH6 + Timer3 = PWM{3} // 16 bit timer for PE3, PE4 and PE5 + Timer4 = PWM{4} // 16 bit timer for PH3, PH4 and PH5 + Timer5 = PWM{5} // 16 bit timer for PL3, PL4 and PL5 +) + +// Configure enables and configures this PWM. +// +// For the two 8 bit timers, there is only a limited number of periods +// available, namely the CPU frequency divided by 256 and again divided by 1, 8, +// 64, 256, or 1024. For a MCU running at 16MHz, this would be a period of 16µs, +// 128µs, 1024µs, 4096µs, or 16384µs. +func (pwm PWM) Configure(config PWMConfig) error { + + switch pwm.num { + case 0, 2: // 8-bit timers (Timer/counter 0 and Timer/counter 2) + // Calculate the timer prescaler. + // While we could configure a flexible top, that would sacrifice one of + // the PWM output compare registers and thus a PWM channel. I've chosen + // to instead limit this timer to a fixed number of frequencies. + var prescaler uint8 + switch config.Period { + case 0, (uint64(1e9) * 256 * 1) / uint64(CPUFrequency()): + prescaler = 1 + case (uint64(1e9) * 256 * 8) / uint64(CPUFrequency()): + prescaler = 2 + case (uint64(1e9) * 256 * 64) / uint64(CPUFrequency()): + prescaler = 3 + case (uint64(1e9) * 256 * 256) / uint64(CPUFrequency()): + prescaler = 4 + case (uint64(1e9) * 256 * 1024) / uint64(CPUFrequency()): + prescaler = 5 + default: + return ErrPWMPeriodTooLong + } + + if pwm.num == 0 { + avr.TCCR0B.Set(prescaler) + // Set the PWM mode to fast PWM (mode = 3). + avr.TCCR0A.Set(avr.TCCR0A_WGM00 | avr.TCCR0A_WGM01) + // monotonic timer is using the same time as PWM:0 + // we must adjust internal settings of monotonic timer when PWM:0 settings changed + adjustMonotonicTimer() + } else { + avr.TCCR2B.Set(prescaler) + // Set the PWM mode to fast PWM (mode = 3). + avr.TCCR2A.Set(avr.TCCR2A_WGM20 | avr.TCCR2A_WGM21) + } + case 1, 3, 4, 5: + // The top value is the number of PWM ticks a PWM period takes. It is + // initially picked assuming an unlimited counter top and no PWM + // prescaler. + var top uint64 + if config.Period == 0 { + // Use a top appropriate for LEDs. Picking a relatively low period + // here (0xff) for consistency with the other timers. + top = 0xff + } else { + // The formula below calculates the following formula, optimized: + // top = period * (CPUFrequency() / 1e9) + // By dividing the CPU frequency first (an operation that is easily + // optimized away) the period has less chance of overflowing. + top = config.Period * (uint64(CPUFrequency()) / 1000000) / 1000 + } + + // The ideal PWM period may be larger than would fit in the PWM counter, + // which is 16 bits (see maxTop). Therefore, try to make the PWM clock + // speed lower with a prescaler to make the top value fit the maximum + // top value. + + const maxTop = 0x10000 + var prescalingTop uint8 + switch { + case top <= maxTop: + prescalingTop = 3<<3 | 1 // no prescaling + case top/8 <= maxTop: + prescalingTop = 3<<3 | 2 // divide by 8 + top /= 8 + case top/64 <= maxTop: + prescalingTop = 3<<3 | 3 // divide by 64 + top /= 64 + case top/256 <= maxTop: + prescalingTop = 3<<3 | 4 // divide by 256 + top /= 256 + case top/1024 <= maxTop: + prescalingTop = 3<<3 | 5 // divide by 1024 + top /= 1024 + default: + return ErrPWMPeriodTooLong + } + + // A top of 0x10000 is at 100% duty cycle. Subtract one because the + // counter counts from 0, not 1 (avoiding an off-by-one). + top -= 1 + + switch pwm.num { + case 1: + avr.TCCR1A.Set(avr.TCCR1A_WGM11) + avr.TCCR1B.Set(prescalingTop) + avr.ICR1H.Set(uint8(top >> 8)) + avr.ICR1L.Set(uint8(top)) + case 3: + avr.TCCR3A.Set(avr.TCCR3A_WGM31) + avr.TCCR3B.Set(prescalingTop) + avr.ICR3H.Set(uint8(top >> 8)) + avr.ICR3L.Set(uint8(top)) + case 4: + avr.TCCR4A.Set(avr.TCCR4A_WGM41) + avr.TCCR4B.Set(prescalingTop) + avr.ICR4H.Set(uint8(top >> 8)) + avr.ICR4L.Set(uint8(top)) + case 5: + avr.TCCR5A.Set(avr.TCCR5A_WGM51) + avr.TCCR5B.Set(prescalingTop) + avr.ICR5H.Set(uint8(top >> 8)) + avr.ICR5L.Set(uint8(top)) + } + } + return nil +} + +// SetPeriod updates the period of this PWM peripheral. +// To set a particular frequency, use the following formula: +// +// period = 1e9 / frequency +// +// If you use a period of 0, a period that works well for LEDs will be picked. +// +// SetPeriod will not change the prescaler, but also won't change the current +// value in any of the channels. This means that you may need to update the +// value for the particular channel. +// +// Note that you cannot pick any arbitrary period after the PWM peripheral has +// been configured. If you want to switch between frequencies, pick the lowest +// frequency (longest period) once when calling Configure and adjust the +// frequency here as needed. +func (pwm PWM) SetPeriod(period uint64) error { + if pwm.num == 0 || pwm.num == 2 { + return ErrPWMPeriodTooLong // TODO better error message + } + + // The top value is the number of PWM ticks a PWM period takes. It is + // initially picked assuming an unlimited counter top and no PWM + // prescaler. + var top uint64 + if period == 0 { + // Use a top appropriate for LEDs. Picking a relatively low period + // here (0xff) for consistency with the other timers. + top = 0xff + } else { + // The formula below calculates the following formula, optimized: + // top = period * (CPUFrequency() / 1e9) + // By dividing the CPU frequency first (an operation that is easily + // optimized away) the period has less chance of overflowing. + top = period * (uint64(CPUFrequency()) / 1000000) / 1000 + } + + var prescaler uint8 + + switch pwm.num { + case 1: + prescaler = avr.TCCR1B.Get() & 0x7 + case 3: + prescaler = avr.TCCR3B.Get() & 0x7 + case 4: + prescaler = avr.TCCR4B.Get() & 0x7 + case 5: + prescaler = avr.TCCR5B.Get() & 0x7 + } + + switch prescaler { + case 1: + top /= 1 + case 2: + top /= 8 + case 3: + top /= 64 + case 4: + top /= 256 + case 5: + top /= 1024 + } + + // A top of 0x10000 is at 100% duty cycle. Subtract one because the counter + // counts from 0, not 1 (avoiding an off-by-one). + top -= 1 + + if top > 0xffff { + return ErrPWMPeriodTooLong + } + + switch pwm.num { + case 1: + // Warning: this change is not atomic! + avr.ICR1H.Set(uint8(top >> 8)) + avr.ICR1L.Set(uint8(top)) + + // ... and because of that, set the counter back to zero to avoid most of + // the effects of this non-atomicity. + avr.TCNT1H.Set(0) + avr.TCNT1L.Set(0) + case 3: + // Warning: this change is not atomic! + avr.ICR3H.Set(uint8(top >> 8)) + avr.ICR3L.Set(uint8(top)) + + // ... and because of that, set the counter back to zero to avoid most of + // the effects of this non-atomicity. + avr.TCNT3H.Set(0) + avr.TCNT3L.Set(0) + case 4: + // Warning: this change is not atomic! + avr.ICR4H.Set(uint8(top >> 8)) + avr.ICR4L.Set(uint8(top)) + + // ... and because of that, set the counter back to zero to avoid most of + // the effects of this non-atomicity. + avr.TCNT4H.Set(0) + avr.TCNT4L.Set(0) + case 5: + // Warning: this change is not atomic! + avr.ICR5H.Set(uint8(top >> 8)) + avr.ICR5L.Set(uint8(top)) + + // ... and because of that, set the counter back to zero to avoid most of + // the effects of this non-atomicity. + avr.TCNT5H.Set(0) + avr.TCNT5L.Set(0) + } + + return nil +} + +// Top returns the current counter top, for use in duty cycle calculation. It +// will only change with a call to Configure or SetPeriod, otherwise it is +// constant. +// +// The value returned here is hardware dependent. In general, it's best to treat +// it as an opaque value that can be divided by some number and passed to Set +// (see Set documentation for more information). +func (pwm PWM) Top() uint32 { + switch pwm.num { + case 1: + // Timer 1 has a configurable top value. + low := avr.ICR1L.Get() + high := avr.ICR1H.Get() + return uint32(high)<<8 | uint32(low) + 1 + case 3: + // Timer 3 has a configurable top value. + low := avr.ICR3L.Get() + high := avr.ICR3H.Get() + return uint32(high)<<8 | uint32(low) + 1 + case 4: + // Timer 4 has a configurable top value. + low := avr.ICR4L.Get() + high := avr.ICR4H.Get() + return uint32(high)<<8 | uint32(low) + 1 + case 5: + // Timer 5 has a configurable top value. + low := avr.ICR5L.Get() + high := avr.ICR5H.Get() + return uint32(high)<<8 | uint32(low) + 1 + } + + // Other timers go from 0 to 0xff (0x100 or 256 in total). + return 256 +} + +// Counter returns the current counter value of the timer in this PWM +// peripheral. It may be useful for debugging. +func (pwm PWM) Counter() uint32 { + switch pwm.num { + case 0: + return uint32(avr.TCNT0.Get()) + case 1: + mask := interrupt.Disable() + low := avr.TCNT1L.Get() + high := avr.TCNT1H.Get() + interrupt.Restore(mask) + return uint32(high)<<8 | uint32(low) + case 2: + return uint32(avr.TCNT2.Get()) + case 3: + mask := interrupt.Disable() + low := avr.TCNT3L.Get() + high := avr.TCNT3H.Get() + interrupt.Restore(mask) + return uint32(high)<<8 | uint32(low) + case 4: + mask := interrupt.Disable() + low := avr.TCNT4L.Get() + high := avr.TCNT4H.Get() + interrupt.Restore(mask) + return uint32(high)<<8 | uint32(low) + case 5: + mask := interrupt.Disable() + low := avr.TCNT5L.Get() + high := avr.TCNT5H.Get() + interrupt.Restore(mask) + return uint32(high)<<8 | uint32(low) + } + + // Unknown PWM. + return 0 +} + +// Period returns the used PWM period in nanoseconds. It might deviate slightly +// from the configured period due to rounding. +func (pwm PWM) Period() uint64 { + var prescaler uint8 + switch pwm.num { + case 0: + prescaler = avr.TCCR0B.Get() & 0x7 + case 1: + prescaler = avr.TCCR1B.Get() & 0x7 + case 2: + prescaler = avr.TCCR2B.Get() & 0x7 + case 3: + prescaler = avr.TCCR3B.Get() & 0x7 + case 4: + prescaler = avr.TCCR4B.Get() & 0x7 + case 5: + prescaler = avr.TCCR5B.Get() & 0x7 + } + top := uint64(pwm.Top()) + switch prescaler { + case 1: // prescaler 1 + return 1 * top * 1000 / uint64(CPUFrequency()/1e6) + case 2: // prescaler 8 + return 8 * top * 1000 / uint64(CPUFrequency()/1e6) + case 3: // prescaler 64 + return 64 * top * 1000 / uint64(CPUFrequency()/1e6) + case 4: // prescaler 256 + return 256 * top * 1000 / uint64(CPUFrequency()/1e6) + case 5: // prescaler 1024 + return 1024 * top * 1000 / uint64(CPUFrequency()/1e6) + default: // unknown clock source + return 0 + } +} + +// Channel returns a PWM channel for the given pin. +func (pwm PWM) Channel(pin Pin) (uint8, error) { + pin.Configure(PinConfig{Mode: PinOutput}) + pin.Low() + switch pwm.num { + case 0: + switch pin { + case PB7: // channel A + avr.TCCR0A.SetBits(avr.TCCR0A_COM0A1) + return 0, nil + case PG5: // channel B + avr.TCCR0A.SetBits(avr.TCCR0A_COM0B1) + return 1, nil + } + case 1: + switch pin { + case PB5: // channel A + avr.TCCR1A.SetBits(avr.TCCR1A_COM1A1) + return 0, nil + case PB6: // channel B + avr.TCCR1A.SetBits(avr.TCCR1A_COM1B1) + return 1, nil + } + case 2: + switch pin { + case PB4: // channel A + avr.TCCR2A.SetBits(avr.TCCR2A_COM2A1) + return 0, nil + case PH6: // channel B + avr.TCCR2A.SetBits(avr.TCCR2A_COM2B1) + return 1, nil + } + case 3: + switch pin { + case PE3: // channel A + avr.TCCR3A.SetBits(avr.TCCR3A_COM3A1) + return 0, nil + case PE4: //channel B + avr.TCCR3A.SetBits(avr.TCCR3A_COM3B1) + return 1, nil + case PE5: //channel C + avr.TCCR3A.SetBits(avr.TCCR3A_COM3C1) + return 2, nil + } + case 4: + switch pin { + case PH3: // channel A + avr.TCCR4A.SetBits(avr.TCCR4A_COM4A1) + return 0, nil + case PH4: //channel B + avr.TCCR4A.SetBits(avr.TCCR4A_COM4B1) + return 1, nil + case PH5: //channel C + avr.TCCR4A.SetBits(avr.TCCR4A_COM4C1) + return 2, nil + } + case 5: + switch pin { + case PL3: // channel A + avr.TCCR5A.SetBits(avr.TCCR5A_COM5A1) + return 0, nil + case PL4: //channel B + avr.TCCR5A.SetBits(avr.TCCR5A_COM5B1) + return 1, nil + case PL5: //channel C + avr.TCCR5A.SetBits(avr.TCCR5A_COM5C1) + return 2, nil + } + } + return 0, ErrInvalidOutputPin +} + +// SetInverting sets whether to invert the output of this channel. +// Without inverting, a 25% duty cycle would mean the output is high for 25% of +// the time and low for the rest. Inverting flips the output as if a NOT gate +// was placed at the output, meaning that the output would be 25% low and 75% +// high with a duty cycle of 25%. +// +// Note: the invert state may not be applied on the AVR until the next call to +// ch.Set(). +func (pwm PWM) SetInverting(channel uint8, inverting bool) { + switch pwm.num { + case 0: + switch channel { + case 0: // channel A, PB7 + if inverting { + avr.PORTB.SetBits(1 << 7) // PB7 high + avr.TCCR0A.SetBits(avr.TCCR0A_COM0A0) + } else { + avr.PORTB.ClearBits(1 << 7) // PB7 low + avr.TCCR0A.ClearBits(avr.TCCR0A_COM0A0) + } + case 1: // channel B, PG5 + if inverting { + avr.PORTG.SetBits(1 << 5) // PG5 high + avr.TCCR0A.SetBits(avr.TCCR0A_COM0B0) + } else { + avr.PORTG.ClearBits(1 << 5) // PG5 low + avr.TCCR0A.ClearBits(avr.TCCR0A_COM0B0) + } + } + case 1: + // Note: the COM1A0/COM1B0 bit is not set with the configuration below. + // It will be set the following call to Set(), however. + switch channel { + case 0: // channel A, PB5 + if inverting { + avr.PORTB.SetBits(1 << 5) // PB5 high + } else { + avr.PORTB.ClearBits(1 << 5) // PB5 low + } + case 1: // channel B, PB6 + if inverting { + avr.PORTB.SetBits(1 << 6) // PB6 high + } else { + avr.PORTB.ClearBits(1 << 6) // PB6 low + } + } + case 2: + switch channel { + case 0: // channel A, PB4 + if inverting { + avr.PORTB.SetBits(1 << 4) // PB4 high + avr.TCCR2A.SetBits(avr.TCCR2A_COM2A0) + } else { + avr.PORTB.ClearBits(1 << 4) // PB4 low + avr.TCCR2A.ClearBits(avr.TCCR2A_COM2A0) + } + case 1: // channel B, PH6 + if inverting { + avr.PORTH.SetBits(1 << 6) // PH6 high + avr.TCCR2A.SetBits(avr.TCCR2A_COM2B0) + } else { + avr.PORTH.ClearBits(1 << 6) // PH6 low + avr.TCCR2A.ClearBits(avr.TCCR2A_COM2B0) + } + } + case 3: + // Note: the COM3A0/COM3B0 bit is not set with the configuration below. + // It will be set the following call to Set(), however. + switch channel { + case 0: // channel A, PE3 + if inverting { + avr.PORTE.SetBits(1 << 3) // PE3 high + } else { + avr.PORTE.ClearBits(1 << 3) // PE3 low + } + case 1: // channel B, PE4 + if inverting { + avr.PORTE.SetBits(1 << 4) // PE4 high + } else { + avr.PORTE.ClearBits(1 << 4) // PE4 low + } + case 2: // channel C, PE5 + if inverting { + avr.PORTE.SetBits(1 << 5) // PE4 high + } else { + avr.PORTE.ClearBits(1 << 5) // PE4 low + } + } + case 4: + // Note: the COM3A0/COM3B0 bit is not set with the configuration below. + // It will be set the following call to Set(), however. + switch channel { + case 0: // channel A, PH3 + if inverting { + avr.PORTH.SetBits(1 << 3) // PH3 high + } else { + avr.PORTH.ClearBits(1 << 3) // PH3 low + } + case 1: // channel B, PH4 + if inverting { + avr.PORTH.SetBits(1 << 4) // PH4 high + } else { + avr.PORTH.ClearBits(1 << 4) // PH4 low + } + case 2: // channel C, PH5 + if inverting { + avr.PORTH.SetBits(1 << 5) // PH4 high + } else { + avr.PORTH.ClearBits(1 << 5) // PH4 low + } + } + case 5: + // Note: the COM3A0/COM3B0 bit is not set with the configuration below. + // It will be set the following call to Set(), however. + switch channel { + case 0: // channel A, PL3 + if inverting { + avr.PORTL.SetBits(1 << 3) // PL3 high + } else { + avr.PORTL.ClearBits(1 << 3) // PL3 low + } + case 1: // channel B, PL4 + if inverting { + avr.PORTL.SetBits(1 << 4) // PL4 high + } else { + avr.PORTL.ClearBits(1 << 4) // PL4 low + } + case 2: // channel C, PH5 + if inverting { + avr.PORTL.SetBits(1 << 5) // PL4 high + } else { + avr.PORTL.ClearBits(1 << 5) // PL4 low + } + } + } +} + +// Set updates the channel value. This is used to control the channel duty +// cycle, in other words the fraction of time the channel output is high (or low +// when inverted). For example, to set it to a 25% duty cycle, use: +// +// pwm.Set(channel, pwm.Top() / 4) +// +// pwm.Set(channel, 0) will set the output to low and pwm.Set(channel, +// pwm.Top()) will set the output to high, assuming the output isn't inverted. +func (pwm PWM) Set(channel uint8, value uint32) { + switch pwm.num { + case 0: + value := uint16(value) + switch channel { + case 0: // channel A + if value == 0 { + avr.TCCR0A.ClearBits(avr.TCCR0A_COM0A1) + } else { + avr.OCR0A.Set(uint8(value - 1)) + avr.TCCR0A.SetBits(avr.TCCR0A_COM0A1) + } + case 1: // channel B + if value == 0 { + avr.TCCR0A.ClearBits(avr.TCCR0A_COM0B1) + } else { + avr.OCR0B.Set(uint8(value) - 1) + avr.TCCR0A.SetBits(avr.TCCR0A_COM0B1) + } + } + // monotonic timer is using the same time as PWM:0 + // we must adjust internal settings of monotonic timer when PWM:0 settings changed + adjustMonotonicTimer() + case 1: + mask := interrupt.Disable() + switch channel { + case 0: // channel A, PB5 + if value == 0 { + avr.TCCR1A.ClearBits(avr.TCCR1A_COM1A1 | avr.TCCR1A_COM1A0) + } else { + value := uint16(value) - 1 // yes, this is safe (it relies on underflow) + avr.OCR1AH.Set(uint8(value >> 8)) + avr.OCR1AL.Set(uint8(value)) + if avr.PORTB.HasBits(1 << 5) { // is PB1 high? + // Yes, set the inverting bit. + avr.TCCR1A.SetBits(avr.TCCR1A_COM1A1 | avr.TCCR1A_COM1A0) + } else { + // No, output is non-inverting. + avr.TCCR1A.SetBits(avr.TCCR1A_COM1A1) + } + } + case 1: // channel B, PB6 + if value == 0 { + avr.TCCR1A.ClearBits(avr.TCCR1A_COM1B1 | avr.TCCR1A_COM1B0) + } else { + value := uint16(value) - 1 // yes, this is safe (it relies on underflow) + avr.OCR1BH.Set(uint8(value >> 8)) + avr.OCR1BL.Set(uint8(value)) + if avr.PORTB.HasBits(1 << 6) { // is PB6 high? + // Yes, set the inverting bit. + avr.TCCR1A.SetBits(avr.TCCR1A_COM1B1 | avr.TCCR1A_COM1B0) + } else { + // No, output is non-inverting. + avr.TCCR1A.SetBits(avr.TCCR1A_COM1B1) + } + } + } + interrupt.Restore(mask) + case 2: + value := uint16(value) + switch channel { + case 0: // channel A + if value == 0 { + avr.TCCR2A.ClearBits(avr.TCCR2A_COM2A1) + } else { + avr.OCR2A.Set(uint8(value - 1)) + avr.TCCR2A.SetBits(avr.TCCR2A_COM2A1) + } + case 1: // channel B + if value == 0 { + avr.TCCR2A.ClearBits(avr.TCCR2A_COM2B1) + } else { + avr.OCR2B.Set(uint8(value - 1)) + avr.TCCR2A.SetBits(avr.TCCR2A_COM2B1) + } + } + case 3: + mask := interrupt.Disable() + switch channel { + case 0: // channel A, PE3 + if value == 0 { + avr.TCCR3A.ClearBits(avr.TCCR3A_COM3A1 | avr.TCCR3A_COM3A0) + } else { + value := uint16(value) - 1 // yes, this is safe (it relies on underflow) + avr.OCR3AH.Set(uint8(value >> 8)) + avr.OCR3AL.Set(uint8(value)) + if avr.PORTE.HasBits(1 << 3) { // is PE3 high? + // Yes, set the inverting bit. + avr.TCCR3A.SetBits(avr.TCCR3A_COM3A1 | avr.TCCR3A_COM3A0) + } else { + // No, output is non-inverting. + avr.TCCR3A.SetBits(avr.TCCR3A_COM3A1) + } + } + case 1: // channel B, PE4 + if value == 0 { + avr.TCCR3A.ClearBits(avr.TCCR3A_COM3B1 | avr.TCCR3A_COM3B0) + } else { + value := uint16(value) - 1 // yes, this is safe (it relies on underflow) + avr.OCR3BH.Set(uint8(value >> 8)) + avr.OCR3BL.Set(uint8(value)) + if avr.PORTE.HasBits(1 << 4) { // is PE4 high? + // Yes, set the inverting bit. + avr.TCCR3A.SetBits(avr.TCCR3A_COM3B1 | avr.TCCR3A_COM3B0) + } else { + // No, output is non-inverting. + avr.TCCR3A.SetBits(avr.TCCR3A_COM3B1) + } + } + case 2: // channel C, PE5 + if value == 0 { + avr.TCCR3A.ClearBits(avr.TCCR3A_COM3C1 | avr.TCCR3A_COM3C0) + } else { + value := uint16(value) - 1 // yes, this is safe (it relies on underflow) + avr.OCR3CH.Set(uint8(value >> 8)) + avr.OCR3CL.Set(uint8(value)) + if avr.PORTE.HasBits(1 << 5) { // is PE5 high? + // Yes, set the inverting bit. + avr.TCCR3A.SetBits(avr.TCCR3A_COM3C1 | avr.TCCR3A_COM3C0) + } else { + // No, output is non-inverting. + avr.TCCR3A.SetBits(avr.TCCR3A_COM3C1) + } + } + } + interrupt.Restore(mask) + case 4: + mask := interrupt.Disable() + switch channel { + case 0: // channel A, PH3 + if value == 0 { + avr.TCCR4A.ClearBits(avr.TCCR4A_COM4A1 | avr.TCCR4A_COM4A0) + } else { + value := uint16(value) - 1 // yes, this is safe (it relies on underflow) + avr.OCR4AH.Set(uint8(value >> 8)) + avr.OCR4AL.Set(uint8(value)) + if avr.PORTH.HasBits(1 << 3) { // is PH3 high? + // Yes, set the inverting bit. + avr.TCCR4A.SetBits(avr.TCCR4A_COM4A1 | avr.TCCR4A_COM4A0) + } else { + // No, output is non-inverting. + avr.TCCR4A.SetBits(avr.TCCR4A_COM4A1) + } + } + case 1: // channel B, PH4 + if value == 0 { + avr.TCCR4A.ClearBits(avr.TCCR4A_COM4B1 | avr.TCCR4A_COM4B0) + } else { + value := uint16(value) - 1 // yes, this is safe (it relies on underflow) + avr.OCR4BH.Set(uint8(value >> 8)) + avr.OCR4BL.Set(uint8(value)) + if avr.PORTH.HasBits(1 << 4) { // is PH4 high? + // Yes, set the inverting bit. + avr.TCCR4A.SetBits(avr.TCCR4A_COM4B1 | avr.TCCR4A_COM4B0) + } else { + // No, output is non-inverting. + avr.TCCR4A.SetBits(avr.TCCR4A_COM4B1) + } + } + case 2: // channel C, PH5 + if value == 0 { + avr.TCCR4A.ClearBits(avr.TCCR4A_COM4C1 | avr.TCCR4A_COM4C0) + } else { + value := uint16(value) - 1 // yes, this is safe (it relies on underflow) + avr.OCR4CH.Set(uint8(value >> 8)) + avr.OCR4CL.Set(uint8(value)) + if avr.PORTH.HasBits(1 << 5) { // is PH5 high? + // Yes, set the inverting bit. + avr.TCCR4A.SetBits(avr.TCCR4A_COM4C1 | avr.TCCR4A_COM4C0) + } else { + // No, output is non-inverting. + avr.TCCR4A.SetBits(avr.TCCR4A_COM4C1) + } + } + } + interrupt.Restore(mask) + case 5: + mask := interrupt.Disable() + switch channel { + case 0: // channel A, PL3 + if value == 0 { + avr.TCCR5A.ClearBits(avr.TCCR5A_COM5A1 | avr.TCCR5A_COM5A0) + } else { + value := uint16(value) - 1 // yes, this is safe (it relies on underflow) + avr.OCR5AH.Set(uint8(value >> 8)) + avr.OCR5AL.Set(uint8(value)) + if avr.PORTL.HasBits(1 << 3) { // is PL3 high? + // Yes, set the inverting bit. + avr.TCCR5A.SetBits(avr.TCCR5A_COM5A1 | avr.TCCR5A_COM5A0) + } else { + // No, output is non-inverting. + avr.TCCR5A.SetBits(avr.TCCR5A_COM5A1) + } + } + case 1: // channel B, PL4 + if value == 0 { + avr.TCCR5A.ClearBits(avr.TCCR5A_COM5B1 | avr.TCCR5A_COM5B0) + } else { + value := uint16(value) - 1 // yes, this is safe (it relies on underflow) + avr.OCR5BH.Set(uint8(value >> 8)) + avr.OCR5BL.Set(uint8(value)) + if avr.PORTL.HasBits(1 << 4) { // is PL4 high? + // Yes, set the inverting bit. + avr.TCCR5A.SetBits(avr.TCCR5A_COM5B1 | avr.TCCR5A_COM5B0) + } else { + // No, output is non-inverting. + avr.TCCR5A.SetBits(avr.TCCR5A_COM5B1) + } + } + case 2: // channel C, PL5 + if value == 0 { + avr.TCCR5A.ClearBits(avr.TCCR5A_COM5C1 | avr.TCCR5A_COM5C0) + } else { + value := uint16(value) - 1 // yes, this is safe (it relies on underflow) + avr.OCR5CH.Set(uint8(value >> 8)) + avr.OCR5CL.Set(uint8(value)) + if avr.PORTL.HasBits(1 << 5) { // is PL5 high? + // Yes, set the inverting bit. + avr.TCCR5A.SetBits(avr.TCCR5A_COM5C1 | avr.TCCR5A_COM5C0) + } else { + // No, output is non-inverting. + avr.TCCR5A.SetBits(avr.TCCR5A_COM5C1) + } + } + } + interrupt.Restore(mask) + } +} + +// SPI configuration +var SPI0 = &SPI{ + spcr: avr.SPCR, + spdr: avr.SPDR, + spsr: avr.SPSR, + sck: PB1, + sdo: PB2, + sdi: PB3, + cs: PB0} diff --git a/emb/machine/machine_atmega1284p.go b/emb/machine/machine_atmega1284p.go new file mode 100644 index 00000000..db8fd65a --- /dev/null +++ b/emb/machine/machine_atmega1284p.go @@ -0,0 +1,81 @@ +//go:build avr && atmega1284p + +package machine + +import ( + "device/avr" + "runtime/volatile" +) + +const irq_USART0_RX = avr.IRQ_USART0_RX + +// Return the current CPU frequency in hertz. +func CPUFrequency() uint32 { + return 20000000 +} + +const ( + portA Pin = iota * 8 + portB + portC + portD +) + +const ( + PA0 = portA + 0 + PA1 = portA + 1 + PA2 = portA + 2 + PA3 = portA + 3 + PA4 = portA + 4 + PA5 = portA + 5 + PA6 = portA + 6 + PA7 = portA + 7 + PB0 = portB + 0 + PB1 = portB + 1 + PB2 = portB + 2 + PB3 = portB + 3 + PB4 = portB + 4 + PB5 = portB + 5 + PB6 = portB + 6 + PB7 = portB + 7 + PC0 = portC + 0 + PC1 = portC + 1 + PC2 = portC + 2 + PC3 = portC + 3 + PC4 = portC + 4 + PC5 = portC + 5 + PC6 = portC + 6 + PC7 = portC + 7 + PD0 = portD + 0 + PD1 = portD + 1 + PD2 = portD + 2 + PD3 = portD + 3 + PD4 = portD + 4 + PD5 = portD + 5 + PD6 = portD + 6 + PD7 = portD + 7 +) + +// getPortMask returns the PORTx register and mask for the pin. +func (p Pin) getPortMask() (*volatile.Register8, uint8) { + switch { + case p >= PA0 && p <= PA7: + return avr.PORTA, 1 << uint8(p-portA) + case p >= PB0 && p <= PB7: + return avr.PORTB, 1 << uint8(p-portB) + case p >= PC0 && p <= PC7: + return avr.PORTC, 1 << uint8(p-portC) + default: + return avr.PORTD, 1 << uint8(p-portD) + } +} + +// SPI configuration +var SPI0 = &SPI{ + spcr: avr.SPCR, + spsr: avr.SPSR, + spdr: avr.SPDR, + sck: PB7, + sdo: PB5, + sdi: PB6, + cs: PB4} diff --git a/emb/machine/machine_atmega2560.go b/emb/machine/machine_atmega2560.go new file mode 100644 index 00000000..ede862a9 --- /dev/null +++ b/emb/machine/machine_atmega2560.go @@ -0,0 +1,141 @@ +//go:build avr && atmega2560 + +package machine + +import ( + "device/avr" + "runtime/volatile" +) + +const irq_USART0_RX = avr.IRQ_USART0_RX +const irq_USART1_RX = avr.IRQ_USART1_RX +const irq_USART2_RX = avr.IRQ_USART2_RX +const irq_USART3_RX = avr.IRQ_USART3_RX + +const ( + portA Pin = iota * 8 + portB + portC + portD + portE + portF + portG + portH + portJ + portK + portL +) + +const ( + PA0 = portA + 0 + PA1 = portA + 1 + PA2 = portA + 2 + PA3 = portA + 3 + PA4 = portA + 4 + PA5 = portA + 5 + PA6 = portA + 6 + PA7 = portA + 7 + PB0 = portB + 0 + PB1 = portB + 1 + PB2 = portB + 2 + PB3 = portB + 3 + PB4 = portB + 4 + PB5 = portB + 5 + PB6 = portB + 6 + PB7 = portB + 7 + PC0 = portC + 0 + PC1 = portC + 1 + PC2 = portC + 2 + PC3 = portC + 3 + PC4 = portC + 4 + PC5 = portC + 5 + PC6 = portC + 6 + PC7 = portC + 7 + PD0 = portD + 0 + PD1 = portD + 1 + PD2 = portD + 2 + PD3 = portD + 3 + PD7 = portD + 7 + PE0 = portE + 0 + PE1 = portE + 1 + PE3 = portE + 3 + PE4 = portE + 4 + PE5 = portE + 5 + PE6 = portE + 6 + PF0 = portF + 0 + PF1 = portF + 1 + PF2 = portF + 2 + PF3 = portF + 3 + PF4 = portF + 4 + PF5 = portF + 5 + PF6 = portF + 6 + PF7 = portF + 7 + PG0 = portG + 0 + PG1 = portG + 1 + PG2 = portG + 2 + PG5 = portG + 5 + PH0 = portH + 0 + PH1 = portH + 1 + PH3 = portH + 3 + PH4 = portH + 4 + PH5 = portH + 5 + PH6 = portH + 6 + PJ0 = portJ + 0 + PJ1 = portJ + 1 + PK0 = portK + 0 + PK1 = portK + 1 + PK2 = portK + 2 + PK3 = portK + 3 + PK4 = portK + 4 + PK5 = portK + 5 + PK6 = portK + 6 + PK7 = portK + 7 + PL0 = portL + 0 + PL1 = portL + 1 + PL2 = portL + 2 + PL3 = portL + 3 + PL4 = portL + 4 + PL5 = portL + 5 + PL6 = portL + 6 + PL7 = portL + 7 +) + +// getPortMask returns the PORTx register and mask for the pin. +func (p Pin) getPortMask() (*volatile.Register8, uint8) { + switch { + case p >= PA0 && p <= PA7: + return avr.PORTA, 1 << uint8(p-portA) + case p >= PB0 && p <= PB7: + return avr.PORTB, 1 << uint8(p-portB) + case p >= PC0 && p <= PC7: + return avr.PORTC, 1 << uint8(p-portC) + case p >= PD0 && p <= PD7: + return avr.PORTD, 1 << uint8(p-portD) + case p >= PE0 && p <= PE6: + return avr.PORTE, 1 << uint8(p-portE) + case p >= PF0 && p <= PF7: + return avr.PORTF, 1 << uint8(p-portF) + case p >= PG0 && p <= PG5: + return avr.PORTG, 1 << uint8(p-portG) + case p >= PH0 && p <= PH6: + return avr.PORTH, 1 << uint8(p-portH) + case p >= PJ0 && p <= PJ1: + return avr.PORTJ, 1 << uint8(p-portJ) + case p >= PK0 && p <= PK7: + return avr.PORTK, 1 << uint8(p-portK) + case p >= PL0 && p <= PL7: + return avr.PORTL, 1 << uint8(p-portL) + default: + return avr.PORTA, 255 + } +} + +// SPI configuration +var SPI0 = &SPI{ + spcr: avr.SPCR, + spdr: avr.SPDR, + spsr: avr.SPSR, + sck: PB1, + sdo: PB2, + sdi: PB3, + cs: PB0} diff --git a/emb/machine/machine_atmega328.go b/emb/machine/machine_atmega328.go new file mode 100644 index 00000000..c354ccb8 --- /dev/null +++ b/emb/machine/machine_atmega328.go @@ -0,0 +1,548 @@ +//go:build avr && (atmega328p || atmega328pb) + +package machine + +import ( + "device/avr" + "runtime/interrupt" + "runtime/volatile" +) + +// PWM is one PWM peripheral, which consists of a counter and two output +// channels (that can be connected to two fixed pins). You can set the frequency +// using SetPeriod, but only for all the channels in this PWM peripheral at +// once. +type PWM struct { + num uint8 +} + +var ( + Timer0 = PWM{0} // 8 bit timer for PD5 and PD6 + Timer1 = PWM{1} // 16 bit timer for PB1 and PB2 + Timer2 = PWM{2} // 8 bit timer for PB3 and PD3 +) + +// Configure enables and configures this PWM. +// +// For the two 8 bit timers, there is only a limited number of periods +// available, namely the CPU frequency divided by 256 and again divided by 1, 8, +// 64, 256, or 1024. For a MCU running at 16MHz, this would be a period of 16µs, +// 128µs, 1024µs, 4096µs, or 16384µs. +func (pwm PWM) Configure(config PWMConfig) error { + switch pwm.num { + case 0, 2: // 8-bit timers (Timer/counter 0 and Timer/counter 2) + // Calculate the timer prescaler. + // While we could configure a flexible top, that would sacrifice one of + // the PWM output compare registers and thus a PWM channel. I've chosen + // to instead limit this timer to a fixed number of frequencies. + var prescaler uint8 + switch config.Period { + case 0, (uint64(1e9) * 256 * 1) / uint64(CPUFrequency()): + prescaler = 1 + case (uint64(1e9) * 256 * 8) / uint64(CPUFrequency()): + prescaler = 2 + case (uint64(1e9) * 256 * 64) / uint64(CPUFrequency()): + prescaler = 3 + case (uint64(1e9) * 256 * 256) / uint64(CPUFrequency()): + prescaler = 4 + case (uint64(1e9) * 256 * 1024) / uint64(CPUFrequency()): + prescaler = 5 + default: + return ErrPWMPeriodTooLong + } + + if pwm.num == 0 { + avr.TCCR0B.Set(prescaler) + // Set the PWM mode to fast PWM (mode = 3). + avr.TCCR0A.Set(avr.TCCR0A_WGM00 | avr.TCCR0A_WGM01) + // monotonic timer is using the same time as PWM:0 + // we must adjust internal settings of monotonic timer when PWM:0 settings changed + adjustMonotonicTimer() + } else { + avr.TCCR2B.Set(prescaler) + // Set the PWM mode to fast PWM (mode = 3). + avr.TCCR2A.Set(avr.TCCR2A_WGM20 | avr.TCCR2A_WGM21) + } + case 1: // Timer/counter 1 + // The top value is the number of PWM ticks a PWM period takes. It is + // initially picked assuming an unlimited counter top and no PWM + // prescaler. + var top uint64 + if config.Period == 0 { + // Use a top appropriate for LEDs. Picking a relatively low period + // here (0xff) for consistency with the other timers. + top = 0xff + } else { + // The formula below calculates the following formula, optimized: + // top = period * (CPUFrequency() / 1e9) + // By dividing the CPU frequency first (an operation that is easily + // optimized away) the period has less chance of overflowing. + top = config.Period * (uint64(CPUFrequency()) / 1000000) / 1000 + } + + avr.TCCR1A.Set(avr.TCCR1A_WGM11) + + // The ideal PWM period may be larger than would fit in the PWM counter, + // which is 16 bits (see maxTop). Therefore, try to make the PWM clock + // speed lower with a prescaler to make the top value fit the maximum + // top value. + const maxTop = 0x10000 + switch { + case top <= maxTop: + avr.TCCR1B.Set(3<<3 | 1) // no prescaling + case top/8 <= maxTop: + avr.TCCR1B.Set(3<<3 | 2) // divide by 8 + top /= 8 + case top/64 <= maxTop: + avr.TCCR1B.Set(3<<3 | 3) // divide by 64 + top /= 64 + case top/256 <= maxTop: + avr.TCCR1B.Set(3<<3 | 4) // divide by 256 + top /= 256 + case top/1024 <= maxTop: + avr.TCCR1B.Set(3<<3 | 5) // divide by 1024 + top /= 1024 + default: + return ErrPWMPeriodTooLong + } + + // A top of 0x10000 is at 100% duty cycle. Subtract one because the + // counter counts from 0, not 1 (avoiding an off-by-one). + top -= 1 + + avr.ICR1H.Set(uint8(top >> 8)) + avr.ICR1L.Set(uint8(top)) + } + return nil +} + +// SetPeriod updates the period of this PWM peripheral. +// To set a particular frequency, use the following formula: +// +// period = 1e9 / frequency +// +// If you use a period of 0, a period that works well for LEDs will be picked. +// +// SetPeriod will not change the prescaler, but also won't change the current +// value in any of the channels. This means that you may need to update the +// value for the particular channel. +// +// Note that you cannot pick any arbitrary period after the PWM peripheral has +// been configured. If you want to switch between frequencies, pick the lowest +// frequency (longest period) once when calling Configure and adjust the +// frequency here as needed. +func (pwm PWM) SetPeriod(period uint64) error { + if pwm.num != 1 { + return ErrPWMPeriodTooLong // TODO better error message + } + + // The top value is the number of PWM ticks a PWM period takes. It is + // initially picked assuming an unlimited counter top and no PWM + // prescaler. + var top uint64 + if period == 0 { + // Use a top appropriate for LEDs. Picking a relatively low period + // here (0xff) for consistency with the other timers. + top = 0xff + } else { + // The formula below calculates the following formula, optimized: + // top = period * (CPUFrequency() / 1e9) + // By dividing the CPU frequency first (an operation that is easily + // optimized away) the period has less chance of overflowing. + top = period * (uint64(CPUFrequency()) / 1000000) / 1000 + } + + prescaler := avr.TCCR1B.Get() & 0x7 + switch prescaler { + case 1: + top /= 1 + case 2: + top /= 8 + case 3: + top /= 64 + case 4: + top /= 256 + case 5: + top /= 1024 + } + + // A top of 0x10000 is at 100% duty cycle. Subtract one because the counter + // counts from 0, not 1 (avoiding an off-by-one). + top -= 1 + + if top > 0xffff { + return ErrPWMPeriodTooLong + } + + // Warning: this change is not atomic! + avr.ICR1H.Set(uint8(top >> 8)) + avr.ICR1L.Set(uint8(top)) + + // ... and because of that, set the counter back to zero to avoid most of + // the effects of this non-atomicity. + avr.TCNT1H.Set(0) + avr.TCNT1L.Set(0) + + return nil +} + +// Top returns the current counter top, for use in duty cycle calculation. It +// will only change with a call to Configure or SetPeriod, otherwise it is +// constant. +// +// The value returned here is hardware dependent. In general, it's best to treat +// it as an opaque value that can be divided by some number and passed to Set +// (see Set documentation for more information). +func (pwm PWM) Top() uint32 { + if pwm.num == 1 { + // Timer 1 has a configurable top value. + low := avr.ICR1L.Get() + high := avr.ICR1H.Get() + return uint32(high)<<8 | uint32(low) + 1 + } + // Other timers go from 0 to 0xff (0x100 or 256 in total). + return 256 +} + +// Counter returns the current counter value of the timer in this PWM +// peripheral. It may be useful for debugging. +func (pwm PWM) Counter() uint32 { + switch pwm.num { + case 0: + return uint32(avr.TCNT0.Get()) + case 1: + mask := interrupt.Disable() + low := avr.TCNT1L.Get() + high := avr.TCNT1H.Get() + interrupt.Restore(mask) + return uint32(high)<<8 | uint32(low) + case 2: + return uint32(avr.TCNT2.Get()) + } + // Unknown PWM. + return 0 +} + +// Period returns the used PWM period in nanoseconds. It might deviate slightly +// from the configured period due to rounding. +func (pwm PWM) Period() uint64 { + var prescaler uint8 + switch pwm.num { + case 0: + prescaler = avr.TCCR0B.Get() & 0x7 + case 1: + prescaler = avr.TCCR1B.Get() & 0x7 + case 2: + prescaler = avr.TCCR2B.Get() & 0x7 + } + top := uint64(pwm.Top()) + switch prescaler { + case 1: // prescaler 1 + return 1 * top * 1000 / uint64(CPUFrequency()/1e6) + case 2: // prescaler 8 + return 8 * top * 1000 / uint64(CPUFrequency()/1e6) + case 3: // prescaler 64 + return 64 * top * 1000 / uint64(CPUFrequency()/1e6) + case 4: // prescaler 256 + return 256 * top * 1000 / uint64(CPUFrequency()/1e6) + case 5: // prescaler 1024 + return 1024 * top * 1000 / uint64(CPUFrequency()/1e6) + default: // unknown clock source + return 0 + } +} + +// Channel returns a PWM channel for the given pin. +func (pwm PWM) Channel(pin Pin) (uint8, error) { + pin.Configure(PinConfig{Mode: PinOutput}) + pin.Low() + switch pwm.num { + case 0: + switch pin { + case PD6: // channel A + avr.TCCR0A.SetBits(avr.TCCR0A_COM0A1) + return 0, nil + case PD5: // channel B + avr.TCCR0A.SetBits(avr.TCCR0A_COM0B1) + return 1, nil + } + case 1: + switch pin { + case PB1: // channel A + avr.TCCR1A.SetBits(avr.TCCR1A_COM1A1) + return 0, nil + case PB2: // channel B + avr.TCCR1A.SetBits(avr.TCCR1A_COM1B1) + return 1, nil + } + case 2: + switch pin { + case PB3: // channel A + avr.TCCR2A.SetBits(avr.TCCR2A_COM2A1) + return 0, nil + case PD3: // channel B + avr.TCCR2A.SetBits(avr.TCCR2A_COM2B1) + return 1, nil + } + } + return 0, ErrInvalidOutputPin +} + +// SetInverting sets whether to invert the output of this channel. +// Without inverting, a 25% duty cycle would mean the output is high for 25% of +// the time and low for the rest. Inverting flips the output as if a NOT gate +// was placed at the output, meaning that the output would be 25% low and 75% +// high with a duty cycle of 25%. +// +// Note: the invert state may not be applied on the AVR until the next call to +// ch.Set(). +func (pwm PWM) SetInverting(channel uint8, inverting bool) { + switch pwm.num { + case 0: + switch channel { + case 0: // channel A + if inverting { + avr.PORTB.SetBits(1 << 6) // PB6 high + avr.TCCR0A.SetBits(avr.TCCR0A_COM0A0) + } else { + avr.PORTB.ClearBits(1 << 6) // PB6 low + avr.TCCR0A.ClearBits(avr.TCCR0A_COM0A0) + } + case 1: // channel B + if inverting { + avr.PORTB.SetBits(1 << 5) // PB5 high + avr.TCCR0A.SetBits(avr.TCCR0A_COM0B0) + } else { + avr.PORTB.ClearBits(1 << 5) // PB5 low + avr.TCCR0A.ClearBits(avr.TCCR0A_COM0B0) + } + } + case 1: + // Note: the COM1A0/COM1B0 bit is not set with the configuration below. + // It will be set the following call to Set(), however. + switch channel { + case 0: // channel A, PB1 + if inverting { + avr.PORTB.SetBits(1 << 1) // PB1 high + } else { + avr.PORTB.ClearBits(1 << 1) // PB1 low + } + case 1: // channel B, PB2 + if inverting { + avr.PORTB.SetBits(1 << 2) // PB2 high + } else { + avr.PORTB.ClearBits(1 << 2) // PB2 low + } + } + case 2: + switch channel { + case 0: // channel A + if inverting { + avr.PORTB.SetBits(1 << 3) // PB3 high + avr.TCCR2A.SetBits(avr.TCCR2A_COM2A0) + } else { + avr.PORTB.ClearBits(1 << 3) // PB3 low + avr.TCCR2A.ClearBits(avr.TCCR2A_COM2A0) + } + case 1: // channel B + if inverting { + avr.PORTD.SetBits(1 << 3) // PD3 high + avr.TCCR2A.SetBits(avr.TCCR2A_COM2B0) + } else { + avr.PORTD.ClearBits(1 << 3) // PD3 low + avr.TCCR2A.ClearBits(avr.TCCR2A_COM2B0) + } + } + } +} + +// Set updates the channel value. This is used to control the channel duty +// cycle, in other words the fraction of time the channel output is high (or low +// when inverted). For example, to set it to a 25% duty cycle, use: +// +// pwm.Set(channel, pwm.Top() / 4) +// +// pwm.Set(channel, 0) will set the output to low and pwm.Set(channel, +// pwm.Top()) will set the output to high, assuming the output isn't inverted. +func (pwm PWM) Set(channel uint8, value uint32) { + switch pwm.num { + case 0: + value := uint16(value) + switch channel { + case 0: // channel A + if value == 0 { + avr.TCCR0A.ClearBits(avr.TCCR0A_COM0A1) + } else { + avr.OCR0A.Set(uint8(value - 1)) + avr.TCCR0A.SetBits(avr.TCCR0A_COM0A1) + } + case 1: // channel B + if value == 0 { + avr.TCCR0A.ClearBits(avr.TCCR0A_COM0B1) + } else { + avr.OCR0B.Set(uint8(value) - 1) + avr.TCCR0A.SetBits(avr.TCCR0A_COM0B1) + } + } + // monotonic timer is using the same time as PWM:0 + // we must adjust internal settings of monotonic timer when PWM:0 settings changed + adjustMonotonicTimer() + case 1: + mask := interrupt.Disable() + switch channel { + case 0: // channel A, PB1 + if value == 0 { + avr.TCCR1A.ClearBits(avr.TCCR1A_COM1A1 | avr.TCCR1A_COM1A0) + } else { + value := uint16(value) - 1 // yes, this is safe (it relies on underflow) + avr.OCR1AH.Set(uint8(value >> 8)) + avr.OCR1AL.Set(uint8(value)) + if avr.PORTB.HasBits(1 << 1) { // is PB1 high? + // Yes, set the inverting bit. + avr.TCCR1A.SetBits(avr.TCCR1A_COM1A1 | avr.TCCR1A_COM1A0) + } else { + // No, output is non-inverting. + avr.TCCR1A.SetBits(avr.TCCR1A_COM1A1) + } + } + case 1: // channel B, PB2 + if value == 0 { + avr.TCCR1A.ClearBits(avr.TCCR1A_COM1B1 | avr.TCCR1A_COM1B0) + } else { + value := uint16(value) - 1 // yes, this is safe (it relies on underflow) + avr.OCR1BH.Set(uint8(value >> 8)) + avr.OCR1BL.Set(uint8(value)) + if avr.PORTB.HasBits(1 << 2) { // is PB2 high? + // Yes, set the inverting bit. + avr.TCCR1A.SetBits(avr.TCCR1A_COM1B1 | avr.TCCR1A_COM1B0) + } else { + // No, output is non-inverting. + avr.TCCR1A.SetBits(avr.TCCR1A_COM1B1) + } + } + } + interrupt.Restore(mask) + case 2: + value := uint16(value) + switch channel { + case 0: // channel A + if value == 0 { + avr.TCCR2A.ClearBits(avr.TCCR2A_COM2A1) + } else { + avr.OCR2A.Set(uint8(value - 1)) + avr.TCCR2A.SetBits(avr.TCCR2A_COM2A1) + } + case 1: // channel B + if value == 0 { + avr.TCCR2A.ClearBits(avr.TCCR2A_COM2B1) + } else { + avr.OCR2B.Set(uint8(value - 1)) + avr.TCCR2A.SetBits(avr.TCCR2A_COM2B1) + } + } + } +} + +// Pin Change Interrupts +type PinChange uint8 + +const ( + PinRising PinChange = 1 << iota + PinFalling + PinToggle = PinRising | PinFalling +) + +func (pin Pin) SetInterrupt(pinChange PinChange, callback func(Pin)) (err error) { + + switch { + case pin >= PB0 && pin <= PB7: + // PCMSK0 - PCINT0-7 + pinStates[0] = avr.PINB.Get() + pinIndex := pin - PB0 + if pinChange&PinRising > 0 { + pinCallbacks[0][pinIndex][0] = callback + } + if pinChange&PinFalling > 0 { + pinCallbacks[0][pinIndex][1] = callback + } + if callback != nil { + avr.PCMSK0.SetBits(1 << pinIndex) + } else { + avr.PCMSK0.ClearBits(1 << pinIndex) + } + avr.PCICR.SetBits(avr.PCICR_PCIE0) + interrupt.New(avr.IRQ_PCINT0, handlePCINT0Interrupts) + case pin >= PC0 && pin <= PC7: + // PCMSK1 - PCINT8-14 + pinStates[1] = avr.PINC.Get() + pinIndex := pin - PC0 + if pinChange&PinRising > 0 { + pinCallbacks[1][pinIndex][0] = callback + } + if pinChange&PinFalling > 0 { + pinCallbacks[1][pinIndex][1] = callback + } + if callback != nil { + avr.PCMSK1.SetBits(1 << pinIndex) + } else { + avr.PCMSK1.ClearBits(1 << pinIndex) + } + avr.PCICR.SetBits(avr.PCICR_PCIE1) + interrupt.New(avr.IRQ_PCINT1, handlePCINT1Interrupts) + case pin >= PD0 && pin <= PD7: + // PCMSK2 - PCINT16-23 + pinStates[2] = avr.PIND.Get() + pinIndex := pin - PD0 + if pinChange&PinRising > 0 { + pinCallbacks[2][pinIndex][0] = callback + } + if pinChange&PinFalling > 0 { + pinCallbacks[2][pinIndex][1] = callback + } + if callback != nil { + avr.PCMSK2.SetBits(1 << pinIndex) + } else { + avr.PCMSK2.ClearBits(1 << pinIndex) + } + avr.PCICR.SetBits(avr.PCICR_PCIE2) + interrupt.New(avr.IRQ_PCINT2, handlePCINT2Interrupts) + default: + return ErrInvalidInputPin + } + + return nil +} + +var pinCallbacks [3][8][2]func(Pin) +var pinStates [3]uint8 + +func handlePCINTInterrupts(intr uint8, port *volatile.Register8) { + current := port.Get() + change := pinStates[intr] ^ current + pinStates[intr] = current + for i := uint8(0); i < 8; i++ { + if (change>>i)&0x01 != 0x01 { + continue + } + pin := Pin(intr*8 + i) + value := pin.Get() + if value && pinCallbacks[intr][i][0] != nil { + pinCallbacks[intr][i][0](pin) + } + if !value && pinCallbacks[intr][i][1] != nil { + pinCallbacks[intr][i][1](pin) + } + } +} + +func handlePCINT0Interrupts(intr interrupt.Interrupt) { + handlePCINTInterrupts(0, avr.PINB) +} + +func handlePCINT1Interrupts(intr interrupt.Interrupt) { + handlePCINTInterrupts(1, avr.PINC) +} + +func handlePCINT2Interrupts(intr interrupt.Interrupt) { + handlePCINTInterrupts(2, avr.PIND) +} diff --git a/emb/machine/machine_atmega328p.go b/emb/machine/machine_atmega328p.go new file mode 100644 index 00000000..5bacfb8f --- /dev/null +++ b/emb/machine/machine_atmega328p.go @@ -0,0 +1,60 @@ +//go:build avr && atmega328p + +package machine + +import ( + "device/avr" + "runtime/volatile" +) + +const irq_USART0_RX = avr.IRQ_USART_RX + +// I2C0 is the only I2C interface on most AVRs. +var I2C0 = &I2C{ + srReg: avr.TWSR, + brReg: avr.TWBR, + crReg: avr.TWCR, + drReg: avr.TWDR, + srPS0: avr.TWSR_TWPS0, + srPS1: avr.TWSR_TWPS1, + crEN: avr.TWCR_TWEN, + crINT: avr.TWCR_TWINT, + crSTO: avr.TWCR_TWSTO, + crEA: avr.TWCR_TWEA, + crSTA: avr.TWCR_TWSTA, +} + +// SPI configuration +var SPI0 = &SPI{ + spcr: avr.SPCR, + spdr: avr.SPDR, + spsr: avr.SPSR, + + spcrR0: avr.SPCR_SPR0, + spcrR1: avr.SPCR_SPR1, + spcrCPHA: avr.SPCR_CPHA, + spcrCPOL: avr.SPCR_CPOL, + spcrDORD: avr.SPCR_DORD, + spcrSPE: avr.SPCR_SPE, + spcrMSTR: avr.SPCR_MSTR, + + spsrI2X: avr.SPSR_SPI2X, + spsrSPIF: avr.SPSR_SPIF, + + sck: PB5, + sdo: PB3, + sdi: PB4, + cs: PB2, +} + +// getPortMask returns the PORTx register and mask for the pin. +func (p Pin) getPortMask() (*volatile.Register8, uint8) { + switch { + case p >= PB0 && p <= PB7: // port B + return avr.PORTB, 1 << uint8(p-portB) + case p >= PC0 && p <= PC7: // port C + return avr.PORTC, 1 << uint8(p-portC) + default: // port D + return avr.PORTD, 1 << uint8(p-portD) + } +} diff --git a/emb/machine/machine_atmega328pb.go b/emb/machine/machine_atmega328pb.go new file mode 100644 index 00000000..935c581d --- /dev/null +++ b/emb/machine/machine_atmega328pb.go @@ -0,0 +1,119 @@ +//go:build avr && atmega328pb + +package machine + +import ( + "device/avr" + "runtime/interrupt" + "runtime/volatile" +) + +const irq_USART0_RX = avr.IRQ_USART0_RX +const irq_USART1_RX = avr.IRQ_USART1_RX + +var ( + UART1 = &_UART1 + _UART1 = UART{ + Buffer: NewRingBuffer(), + + dataReg: avr.UDR1, + baudRegH: avr.UBRR1H, + baudRegL: avr.UBRR1L, + statusRegA: avr.UCSR1A, + statusRegB: avr.UCSR1B, + statusRegC: avr.UCSR1C, + } +) + +func init() { + // Register the UART interrupt. + interrupt.New(irq_USART1_RX, _UART1.handleInterrupt) +} + +// I2C0 is the only I2C interface on most AVRs. +var I2C0 = &I2C{ + srReg: avr.TWSR0, + brReg: avr.TWBR0, + crReg: avr.TWCR0, + drReg: avr.TWDR0, + srPS0: avr.TWSR0_TWPS0, + srPS1: avr.TWSR0_TWPS1, + crEN: avr.TWCR0_TWEN, + crINT: avr.TWCR0_TWINT, + crSTO: avr.TWCR0_TWSTO, + crEA: avr.TWCR0_TWEA, + crSTA: avr.TWCR0_TWSTA, +} + +var I2C1 = &I2C{ + srReg: avr.TWSR1, + brReg: avr.TWBR1, + crReg: avr.TWCR1, + drReg: avr.TWDR1, + srPS0: avr.TWSR1_TWPS10, + srPS1: avr.TWSR1_TWPS11, + crEN: avr.TWCR1_TWEN1, + crINT: avr.TWCR1_TWINT1, + crSTO: avr.TWCR1_TWSTO1, + crEA: avr.TWCR1_TWEA1, + crSTA: avr.TWCR1_TWSTA1, +} + +// SPI configuration +var SPI0 = &SPI{ + spcr: avr.SPCR0, + spdr: avr.SPDR0, + spsr: avr.SPSR0, + + spcrR0: avr.SPCR0_SPR0, + spcrR1: avr.SPCR0_SPR1, + spcrCPHA: avr.SPCR0_CPHA, + spcrCPOL: avr.SPCR0_CPOL, + spcrDORD: avr.SPCR0_DORD, + spcrSPE: avr.SPCR0_SPE, + spcrMSTR: avr.SPCR0_MSTR, + + spsrI2X: avr.SPSR0_SPI2X, + spsrSPIF: avr.SPSR0_SPIF, + + sck: PB5, + sdo: PB3, + sdi: PB4, + cs: PB2, +} + +var SPI1 = &SPI{ + spcr: avr.SPCR1, + spdr: avr.SPDR1, + spsr: avr.SPSR1, + + spcrR0: avr.SPCR1_SPR10, + spcrR1: avr.SPCR1_SPR11, + spcrCPHA: avr.SPCR1_CPHA1, + spcrCPOL: avr.SPCR1_CPOL1, + spcrDORD: avr.SPCR1_DORD1, + spcrSPE: avr.SPCR1_SPE1, + spcrMSTR: avr.SPCR1_MSTR1, + + spsrI2X: avr.SPSR1_SPI2X1, + spsrSPIF: avr.SPSR1_SPIF1, + + sck: PC1, + sdo: PE3, + sdi: PC0, + cs: PE2, +} + +// getPortMask returns the PORTx register and mask for the pin. +func (p Pin) getPortMask() (*volatile.Register8, uint8) { + switch { + case p >= PB0 && p <= PB7: // port B + return avr.PORTB, 1 << uint8(p-portB) + case p >= PC0 && p <= PC7: // port C + return avr.PORTC, 1 << uint8(p-portC) + case p >= PD0 && p <= PD7: // port D + return avr.PORTD, 1 << uint8(p-portD) + default: // port E + return avr.PORTE, 1 << uint8(p-portE) + } +} diff --git a/emb/machine/machine_atmega32u4.go b/emb/machine/machine_atmega32u4.go new file mode 100644 index 00000000..8b3faabb --- /dev/null +++ b/emb/machine/machine_atmega32u4.go @@ -0,0 +1,76 @@ +//go:build avr && atmega32u4 + +package machine + +import ( + "device/avr" + "runtime/volatile" +) + +const ( + // Note: start at port B because there is no port A. + portB Pin = iota * 8 + portC + portD + portE + portF +) + +const ( + PB0 = portB + 0 + PB1 = portB + 1 // peripherals: Timer1 channel A + PB2 = portB + 2 // peripherals: Timer1 channel B + PB3 = portB + 3 // peripherals: Timer2 channel A + PB4 = portB + 4 + PB5 = portB + 5 + PB6 = portB + 6 + PB7 = portB + 7 + PC0 = portC + 0 + PC1 = portC + 1 + PC2 = portC + 2 + PC3 = portC + 3 + PC4 = portC + 4 + PC5 = portC + 5 + PC6 = portC + 6 + PC7 = portC + 7 + PD0 = portD + 0 + PD1 = portD + 1 + PD2 = portD + 2 + PD3 = portD + 3 + PD4 = portD + 4 + PD5 = portD + 5 + PD6 = portD + 6 + PD7 = portD + 7 + PE0 = portE + 0 + PE1 = portE + 1 + PE2 = portE + 2 + PE3 = portE + 3 + PE4 = portE + 4 + PE5 = portE + 5 + PE6 = portE + 6 + PE7 = portE + 7 + PF0 = portF + 0 + PF1 = portF + 1 + PF2 = portF + 2 + PF3 = portF + 3 + PF4 = portF + 4 + PF5 = portF + 5 + PF6 = portF + 6 + PF7 = portF + 7 +) + +// getPortMask returns the PORTx register and mask for the pin. +func (p Pin) getPortMask() (*volatile.Register8, uint8) { + switch { + case p >= PB0 && p <= PB7: // port B + return avr.PORTB, 1 << uint8(p-portB) + case p >= PC0 && p <= PC7: // port C + return avr.PORTC, 1 << uint8(p-portC) + case p >= PD0 && p <= PD7: // port D + return avr.PORTD, 1 << uint8(p-portD) + case p >= PE0 && p <= PE7: // port E + return avr.PORTE, 1 << uint8(p-portE) + default: // port F + return avr.PORTF, 1 << uint8(p-portF) + } +} diff --git a/emb/machine/machine_atsam.go b/emb/machine/machine_atsam.go new file mode 100644 index 00000000..ad2f0737 --- /dev/null +++ b/emb/machine/machine_atsam.go @@ -0,0 +1,31 @@ +//go:build sam + +package machine + +import ( + "runtime/volatile" + "unsafe" +) + +var deviceID [16]byte + +// DeviceID returns an identifier that is unique within +// a particular chipset. +// +// The identity is one burnt into the MCU itself, or the +// flash chip at time of manufacture. +// +// It's possible that two different vendors may allocate +// the same DeviceID, so callers should take this into +// account if needing to generate a globally unique id. +// +// The length of the hardware ID is vendor-specific, but +// 8 bytes (64 bits) and 16 bytes (128 bits) are common. +func DeviceID() []byte { + for i := 0; i < len(deviceID); i++ { + word := (*volatile.Register32)(unsafe.Pointer(deviceIDAddr[i/4])).Get() + deviceID[i] = byte(word >> ((i % 4) * 8)) + } + + return deviceID[:] +} diff --git a/emb/machine/machine_atsamd21.go b/emb/machine/machine_atsamd21.go new file mode 100644 index 00000000..b46dcef9 --- /dev/null +++ b/emb/machine/machine_atsamd21.go @@ -0,0 +1,2090 @@ +//go:build sam && atsamd21 + +// Peripheral abstraction layer for the atsamd21. +// +// Datasheet: +// http://ww1.microchip.com/downloads/en/DeviceDoc/SAMD21-Family-DataSheet-DS40001882D.pdf +package machine + +import ( + "device/arm" + "device/sam" + "errors" + "internal/binary" + "runtime/interrupt" + "unsafe" +) + +const deviceName = sam.Device + +// DS40001882F, Section 10.3.3: Serial Number +var deviceIDAddr = []uintptr{0x0080A00C, 0x0080A040, 0x0080A044, 0x0080A048} + +const ( + PinAnalog PinMode = 1 + PinSERCOM PinMode = 2 + PinSERCOMAlt PinMode = 3 + PinTimer PinMode = 4 + PinTimerAlt PinMode = 5 + PinCom PinMode = 6 + //PinAC_CLK PinMode = 7 + PinDigital PinMode = 8 + PinInput PinMode = 9 + PinInputPullup PinMode = 10 + PinOutput PinMode = 11 + PinTCC PinMode = PinTimer + PinTCCAlt PinMode = PinTimerAlt + PinInputPulldown PinMode = 12 +) + +type PinChange uint8 + +// Pin change interrupt constants for SetInterrupt. +const ( + PinRising PinChange = sam.EIC_CONFIG_SENSE0_RISE + PinFalling PinChange = sam.EIC_CONFIG_SENSE0_FALL + PinToggle PinChange = sam.EIC_CONFIG_SENSE0_BOTH +) + +// Callbacks to be called for pins configured with SetInterrupt. Unfortunately, +// we also need to keep track of which interrupt channel is used by which pin, +// as the only alternative would be iterating through all pins. +// +// We're using the magic constant 16 here because the SAM D21 has 16 interrupt +// channels configurable for pins. +var ( + interruptPins [16]Pin // warning: the value is invalid when pinCallbacks[i] is not set! + pinCallbacks [16]func(Pin) +) + +const ( + pinPadMapSERCOM0Pad0 byte = (0x10 << 1) | 0x00 + pinPadMapSERCOM1Pad0 byte = (0x20 << 1) | 0x00 + pinPadMapSERCOM2Pad0 byte = (0x30 << 1) | 0x00 + pinPadMapSERCOM3Pad0 byte = (0x40 << 1) | 0x00 + pinPadMapSERCOM4Pad0 byte = (0x50 << 1) | 0x00 + pinPadMapSERCOM5Pad0 byte = (0x60 << 1) | 0x00 + pinPadMapSERCOM0Pad2 byte = (0x10 << 1) | 0x10 + pinPadMapSERCOM1Pad2 byte = (0x20 << 1) | 0x10 + pinPadMapSERCOM2Pad2 byte = (0x30 << 1) | 0x10 + pinPadMapSERCOM3Pad2 byte = (0x40 << 1) | 0x10 + pinPadMapSERCOM4Pad2 byte = (0x50 << 1) | 0x10 + pinPadMapSERCOM5Pad2 byte = (0x60 << 1) | 0x10 + + pinPadMapSERCOM0AltPad0 byte = (0x01 << 1) | 0x00 + pinPadMapSERCOM1AltPad0 byte = (0x02 << 1) | 0x00 + pinPadMapSERCOM2AltPad0 byte = (0x03 << 1) | 0x00 + pinPadMapSERCOM3AltPad0 byte = (0x04 << 1) | 0x00 + pinPadMapSERCOM4AltPad0 byte = (0x05 << 1) | 0x00 + pinPadMapSERCOM5AltPad0 byte = (0x06 << 1) | 0x00 + pinPadMapSERCOM0AltPad2 byte = (0x01 << 1) | 0x01 + pinPadMapSERCOM1AltPad2 byte = (0x02 << 1) | 0x01 + pinPadMapSERCOM2AltPad2 byte = (0x03 << 1) | 0x01 + pinPadMapSERCOM3AltPad2 byte = (0x04 << 1) | 0x01 + pinPadMapSERCOM4AltPad2 byte = (0x05 << 1) | 0x01 + pinPadMapSERCOM5AltPad2 byte = (0x06 << 1) | 0x01 +) + +// pinPadMapping lists which pins have which SERCOMs attached to them. +// The encoding is rather dense, with each byte encoding two pins and both +// SERCOM and SERCOM-ALT. +// +// Observations: +// - There are six SERCOMs. Those SERCOM numbers can be encoded in 3 bits. +// - Even pad numbers are always on even pins, and odd pad numbers are always on +// odd pins. +// - Pin pads come in pairs. If PA00 has pad 0, then PA01 has pad 1. +// +// With this information, we can encode SERCOM pin/pad numbers much more +// efficiently. First of all, due to pads coming in pairs, we can ignore half +// the pins: the information for an odd pin can be calculated easily from the +// preceding even pin. And second, if odd pads are always on odd pins and even +// pads on even pins, we can drop a single bit from the pad number. +// +// Each byte below is split in two nibbles. The 4 high bits are for SERCOM and +// the 4 low bits are for SERCOM-ALT. Of each nibble, the 3 high bits encode the +// SERCOM + 1 while the low bit encodes whether this is PAD0 or PAD2 (0 means +// PAD0, 1 means PAD2). It encodes SERCOM + 1 instead of just the SERCOM number, +// to make it easy to check whether a nibble is set at all. +var pinPadMapping = [32]byte{ + // page 21 + PA00 / 2: 0 | pinPadMapSERCOM1AltPad0, + PB08 / 2: 0 | pinPadMapSERCOM4AltPad0, + PA04 / 2: 0 | pinPadMapSERCOM0AltPad0, + PA06 / 2: 0 | pinPadMapSERCOM0AltPad2, + PA08 / 2: pinPadMapSERCOM0Pad0 | pinPadMapSERCOM2AltPad0, + PA10 / 2: pinPadMapSERCOM0Pad2 | pinPadMapSERCOM2AltPad2, + + // page 22 + PB10 / 2: 0 | pinPadMapSERCOM4AltPad2, + PB12 / 2: pinPadMapSERCOM4Pad0 | 0, + PB14 / 2: pinPadMapSERCOM4Pad2 | 0, + PA12 / 2: pinPadMapSERCOM2Pad0 | pinPadMapSERCOM4AltPad0, + PA14 / 2: pinPadMapSERCOM2Pad2 | pinPadMapSERCOM4AltPad2, + PA16 / 2: pinPadMapSERCOM1Pad0 | pinPadMapSERCOM3AltPad0, + PA18 / 2: pinPadMapSERCOM1Pad2 | pinPadMapSERCOM3AltPad2, + PB16 / 2: pinPadMapSERCOM5Pad0 | 0, + PA20 / 2: pinPadMapSERCOM5Pad2 | pinPadMapSERCOM3AltPad2, + PA22 / 2: pinPadMapSERCOM3Pad0 | pinPadMapSERCOM5AltPad0, + PA24 / 2: pinPadMapSERCOM3Pad2 | pinPadMapSERCOM5AltPad2, + + // page 23 + PB22 / 2: 0 | pinPadMapSERCOM5AltPad2, + PA30 / 2: 0 | pinPadMapSERCOM1AltPad2, + PB30 / 2: 0 | pinPadMapSERCOM5AltPad0, + PB00 / 2: 0 | pinPadMapSERCOM5AltPad2, + PB02 / 2: 0 | pinPadMapSERCOM5AltPad0, +} + +// findPinPadMapping looks up the pad number and the pinmode for a given pin, +// given a SERCOM number. The result can either be SERCOM, SERCOM-ALT, or "not +// found" (indicated by returning ok=false). The pad number is returned to +// calculate the DOPO/DIPO bitfields of the various serial peripherals. +func findPinPadMapping(sercom uint8, pin Pin) (pinMode PinMode, pad uint32, ok bool) { + if int(pin)/2 >= len(pinPadMapping) { + // This is probably NoPin, for which no mapping is available. + return + } + + nibbles := pinPadMapping[pin/2] + upper := nibbles >> 4 + lower := nibbles & 0xf + + if upper != 0 { + // SERCOM + if (upper>>1)-1 == sercom { + pinMode = PinSERCOM + pad |= uint32((upper & 1) << 1) + ok = true + } + } + if lower != 0 { + // SERCOM-ALT + if (lower>>1)-1 == sercom { + pinMode = PinSERCOMAlt + pad |= uint32((lower & 1) << 1) + ok = true + } + } + + if ok { + // The lower bit of the pad is the same as the lower bit of the pin number. + pad |= uint32(pin & 1) + } + return +} + +// SetInterrupt sets an interrupt to be executed when a particular pin changes +// state. The pin should already be configured as an input, including a pull up +// or down if no external pull is provided. +// +// This call will replace a previously set callback on this pin. You can pass a +// nil func to unset the pin change interrupt. If you do so, the change +// parameter is ignored and can be set to any value (such as 0). +func (p Pin) SetInterrupt(change PinChange, callback func(Pin)) error { + // Most pins follow a common pattern where the EXTINT value is the pin + // number modulo 16. However, there are a few exceptions, as you can see + // below. + extint := uint8(0) + switch p { + case PA08: + // Connected to NMI. This is not currently supported. + return ErrInvalidInputPin + case PA24: + extint = 12 + case PA25: + extint = 13 + case PA27: + extint = 15 + case PA28: + extint = 8 + case PA30: + extint = 10 + case PA31: + extint = 11 + default: + // All other pins follow a normal pattern. + extint = uint8(p) % 16 + } + + if callback == nil { + // Disable this pin interrupt (if it was enabled). + sam.EIC.INTENCLR.Set(1 << extint) + if pinCallbacks[extint] != nil { + pinCallbacks[extint] = nil + } + return nil + } + + if pinCallbacks[extint] != nil { + // The pin was already configured. + // To properly re-configure a pin, unset it first and set a new + // configuration. + return ErrNoPinChangeChannel + } + pinCallbacks[extint] = callback + interruptPins[extint] = p + + if sam.EIC.CTRL.Get() == 0 { + // EIC peripheral has not yet been initialized. Initialize it now. + + // The EIC needs two clocks: CLK_EIC_APB and GCLK_EIC. CLK_EIC_APB is + // enabled by default, so doesn't have to be re-enabled. The other is + // required for detecting edges and must be enabled manually. + sam.GCLK.CLKCTRL.Set(sam.GCLK_CLKCTRL_ID_EIC<= 8 { + addr = &sam.EIC.CONFIG1 + } + pos := (extint % 8) * 4 // bit position in register + addr.ReplaceBits(uint32(change), 0xf, pos) + + // Enable external interrupt for this pin. + sam.EIC.INTENSET.Set(1 << extint) + + // Set the PMUXEN flag, while keeping the INEN and PULLEN flags (if they + // were set before). This avoids clearing the pin pull mode while + // configuring the pin interrupt. + p.setPinCfg(sam.PORT_PINCFG0_PMUXEN | (p.getPinCfg() & (sam.PORT_PINCFG0_INEN | sam.PORT_PINCFG0_PULLEN))) + if p&1 > 0 { + // odd pin, so save the even pins + val := p.getPMux() & sam.PORT_PMUX0_PMUXE_Msk + p.setPMux(val | (sam.PORT_PMUX0_PMUXO_A << sam.PORT_PMUX0_PMUXO_Pos)) + } else { + // even pin, so save the odd pins + val := p.getPMux() & sam.PORT_PMUX0_PMUXO_Msk + p.setPMux(val | (sam.PORT_PMUX0_PMUXE_A << sam.PORT_PMUX0_PMUXE_Pos)) + } + + interrupt.New(sam.IRQ_EIC, func(interrupt.Interrupt) { + flags := sam.EIC.INTFLAG.Get() + sam.EIC.INTFLAG.Set(flags) // clear interrupt + for i := uint(0); i < 16; i++ { // there are 16 channels + if flags&(1<>3) & uint16(0x7) + + // ADC Linearity bits 4:0 + linearity0Fuse := *(*uint32)(unsafe.Pointer(uintptr(0x00806020))) + linearity := uint16(linearity0Fuse>>27) & uint16(0x1f) + + // ADC Linearity bits 7:5 + linearity1Fuse := *(*uint32)(unsafe.Pointer(uintptr(0x00806020) + 4)) + linearity |= uint16(linearity1Fuse) & uint16(0x7) << 5 + + // set calibration + sam.ADC.CALIB.Set((bias << 8) | linearity) +} + +// Configure configures a ADC pin to be able to be used to read data. +func (a ADC) Configure(config ADCConfig) { + + // Wait for synchronization + waitADCSync() + + var resolution uint32 + switch config.Resolution { + case 8: + resolution = sam.ADC_CTRLB_RESSEL_8BIT + case 10: + resolution = sam.ADC_CTRLB_RESSEL_10BIT + case 12: + resolution = sam.ADC_CTRLB_RESSEL_12BIT + case 16: + resolution = sam.ADC_CTRLB_RESSEL_16BIT + default: + resolution = sam.ADC_CTRLB_RESSEL_12BIT + } + // Divide Clock by 32 with 12 bits resolution as default + sam.ADC.CTRLB.Set((sam.ADC_CTRLB_PRESCALER_DIV32 << sam.ADC_CTRLB_PRESCALER_Pos) | + uint16(resolution<> sam.ADC_CTRLB_RESSEL_Pos { + case sam.ADC_CTRLB_RESSEL_8BIT: + val = val << 8 + case sam.ADC_CTRLB_RESSEL_10BIT: + val = val << 6 + case sam.ADC_CTRLB_RESSEL_16BIT: + val = val << 4 + case sam.ADC_CTRLB_RESSEL_12BIT: + val = val << 4 + } + return val +} + +func (a ADC) getADCChannel() uint8 { + switch a.Pin { + case PA02: + return 0 + case PA03: + return 1 + case PB04: + return 12 + case PB05: + return 13 + case PB06: + return 14 + case PB07: + return 15 + case PB08: + return 2 + case PB09: + return 3 + case PA04: + return 4 + case PA05: + return 5 + case PA06: + return 6 + case PA07: + return 7 + case PA08: + return 16 + case PA09: + return 17 + case PA10: + return 18 + case PA11: + return 19 + case PB00: + return 8 + case PB01: + return 9 + case PB02: + return 10 + case PB03: + return 11 + default: + return 0 + } +} + +func waitADCSync() { + for sam.ADC.STATUS.HasBits(sam.ADC_STATUS_SYNCBUSY) { + } +} + +// UART on the SAMD21. +type UART struct { + Buffer *RingBuffer + Bus *sam.SERCOM_USART_Type + SERCOM uint8 + Interrupt interrupt.Interrupt +} + +const ( + sampleRate16X = 16 + lsbFirst = 1 +) + +// Configure the UART. +func (uart *UART) Configure(config UARTConfig) error { + // Default baud rate to 115200. + if config.BaudRate == 0 { + config.BaudRate = 115200 + } + + // Use default pins if pins are not set. + if config.TX == 0 && config.RX == 0 { + // use default pins + config.TX = UART_TX_PIN + config.RX = UART_RX_PIN + } + + // Determine transmit pinout. + txPinMode, txPad, ok := findPinPadMapping(uart.SERCOM, config.TX) + if !ok { + return ErrInvalidOutputPin + } + var txPadOut uint32 + // See table 25-9 of the datasheet (page 459) for how pads are mapped to + // pinout values. + switch txPad { + case 0: + txPadOut = 0 + case 2: + txPadOut = 1 + default: + // this should be a flow control (RTS/CTS) pin + return ErrInvalidOutputPin + } + + // Determine receive pinout. + rxPinMode, rxPad, ok := findPinPadMapping(uart.SERCOM, config.RX) + if !ok { + return ErrInvalidInputPin + } + // As you can see in table 25-8 on page 459 of the datasheet, input pins + // are mapped directly. + rxPadOut := rxPad + + // configure pins + config.TX.Configure(PinConfig{Mode: txPinMode}) + config.RX.Configure(PinConfig{Mode: rxPinMode}) + + // configure RTS/CTS pins if provided + if config.RTS != 0 && config.CTS != 0 { + rtsPinMode, _, ok := findPinPadMapping(uart.SERCOM, config.RTS) + if !ok { + return ErrInvalidOutputPin + } + + ctsPinMode, _, ok := findPinPadMapping(uart.SERCOM, config.CTS) + if !ok { + return ErrInvalidInputPin + } + + // See table 25-9 of the datasheet (page 459) for how pads are mapped to + // pinout values. + if txPadOut == 1 { + return ErrInvalidOutputPin + } + txPadOut = 2 + + config.RTS.Configure(PinConfig{Mode: rtsPinMode}) + config.CTS.Configure(PinConfig{Mode: ctsPinMode}) + } + + // reset SERCOM0 + uart.Bus.CTRLA.SetBits(sam.SERCOM_USART_CTRLA_SWRST) + for uart.Bus.CTRLA.HasBits(sam.SERCOM_USART_CTRLA_SWRST) || + uart.Bus.SYNCBUSY.HasBits(sam.SERCOM_USART_SYNCBUSY_SWRST) { + } + + // set UART mode/sample rate + // SERCOM_USART_CTRLA_MODE(mode) | + // SERCOM_USART_CTRLA_SAMPR(sampleRate); + uart.Bus.CTRLA.Set((sam.SERCOM_USART_CTRLA_MODE_USART_INT_CLK << sam.SERCOM_USART_CTRLA_MODE_Pos) | + (1 << sam.SERCOM_USART_CTRLA_SAMPR_Pos)) // sample rate of 16x + + // Set baud rate + uart.SetBaudRate(config.BaudRate) + + // setup UART frame + // SERCOM_USART_CTRLA_FORM( (parityMode == SERCOM_NO_PARITY ? 0 : 1) ) | + // dataOrder << SERCOM_USART_CTRLA_DORD_Pos; + uart.Bus.CTRLA.SetBits((0 << sam.SERCOM_USART_CTRLA_FORM_Pos) | // no parity + (lsbFirst << sam.SERCOM_USART_CTRLA_DORD_Pos)) // data order + + // set UART stop bits/parity + // SERCOM_USART_CTRLB_CHSIZE(charSize) | + // nbStopBits << SERCOM_USART_CTRLB_SBMODE_Pos | + // (parityMode == SERCOM_NO_PARITY ? 0 : parityMode) << SERCOM_USART_CTRLB_PMODE_Pos; //If no parity use default value + uart.Bus.CTRLB.SetBits((0 << sam.SERCOM_USART_CTRLB_CHSIZE_Pos) | // 8 bits is 0 + (0 << sam.SERCOM_USART_CTRLB_SBMODE_Pos) | // 1 stop bit is zero + (0 << sam.SERCOM_USART_CTRLB_PMODE_Pos)) // no parity + + // set UART pads. This is not same as pins... + // SERCOM_USART_CTRLA_TXPO(txPad) | + // SERCOM_USART_CTRLA_RXPO(rxPad); + uart.Bus.CTRLA.SetBits((txPadOut << sam.SERCOM_USART_CTRLA_TXPO_Pos) | + (rxPadOut << sam.SERCOM_USART_CTRLA_RXPO_Pos)) + + // Enable Transceiver and Receiver + //sercom->USART.CTRLB.reg |= SERCOM_USART_CTRLB_TXEN | SERCOM_USART_CTRLB_RXEN ; + uart.Bus.CTRLB.SetBits(sam.SERCOM_USART_CTRLB_TXEN | sam.SERCOM_USART_CTRLB_RXEN) + + // Enable USART1 port. + // sercom->USART.CTRLA.bit.ENABLE = 0x1u; + uart.Bus.CTRLA.SetBits(sam.SERCOM_USART_CTRLA_ENABLE) + for uart.Bus.SYNCBUSY.HasBits(sam.SERCOM_USART_SYNCBUSY_ENABLE) { + } + + // setup interrupt on receive + uart.Bus.INTENSET.Set(sam.SERCOM_USART_INTENSET_RXC) + + // Enable RX IRQ. + uart.Interrupt.Enable() + + return nil +} + +// SetBaudRate sets the communication speed for the UART. +func (uart *UART) SetBaudRate(br uint32) { + // Asynchronous fractional mode (Table 24-2 in datasheet) + // BAUD = fref / (sampleRateValue * fbaud) + // (multiply by 8, to calculate fractional piece) + // uint32_t baudTimes8 = (SystemCoreClock * 8) / (16 * baudrate); + baud := (CPUFrequency() * 8) / (sampleRate16X * br) + + // sercom->USART.BAUD.FRAC.FP = (baudTimes8 % 8); + // sercom->USART.BAUD.FRAC.BAUD = (baudTimes8 / 8); + uart.Bus.BAUD.Set(uint16(((baud % 8) << sam.SERCOM_USART_BAUD_FRAC_MODE_FP_Pos) | + ((baud / 8) << sam.SERCOM_USART_BAUD_FRAC_MODE_BAUD_Pos))) +} + +// WriteByte writes a byte of data to the UART. +func (uart *UART) writeByte(c byte) error { + // wait until ready to receive + for !uart.Bus.INTFLAG.HasBits(sam.SERCOM_USART_INTFLAG_DRE) { + } + uart.Bus.DATA.Set(uint16(c)) + return nil +} + +func (uart *UART) flush() {} + +// handleInterrupt should be called from the appropriate interrupt handler for +// this UART instance. +func (uart *UART) handleInterrupt(interrupt.Interrupt) { + // should reset IRQ + uart.Receive(byte((uart.Bus.DATA.Get() & 0xFF))) + uart.Bus.INTFLAG.SetBits(sam.SERCOM_USART_INTFLAG_RXC) +} + +// I2C on the SAMD21. +type I2C struct { + Bus *sam.SERCOM_I2CM_Type + SERCOM uint8 +} + +// I2CConfig is used to store config info for I2C. +type I2CConfig struct { + Frequency uint32 + SCL Pin + SDA Pin +} + +const ( + // Default rise time in nanoseconds, based on 4.7K ohm pull up resistors + riseTimeNanoseconds = 125 + + // wire bus states + wireUnknownState = 0 + wireIdleState = 1 + wireOwnerState = 2 + wireBusyState = 3 + + // wire commands + wireCmdNoAction = 0 + wireCmdRepeatStart = 1 + wireCmdRead = 2 + wireCmdStop = 3 +) + +const i2cTimeout = 1000 + +// Configure is intended to setup the I2C interface. +func (i2c *I2C) Configure(config I2CConfig) error { + // Default I2C bus speed is 100 kHz. + if config.Frequency == 0 { + config.Frequency = 100 * KHz + } + if config.SDA == 0 && config.SCL == 0 { + config.SDA = SDA_PIN + config.SCL = SCL_PIN + } + + sclPinMode, sclPad, ok := findPinPadMapping(i2c.SERCOM, config.SCL) + if !ok || sclPad != 1 { + // SCL must be on pad 1, according to section 27.5 of the datasheet. + // Note: this is not an exhaustive test for I2C support on the pin: not + // all pins support I2C. + return ErrInvalidClockPin + } + sdaPinMode, sdaPad, ok := findPinPadMapping(i2c.SERCOM, config.SDA) + if !ok || sdaPad != 0 { + // SDA must be on pad 0, according to section 27.5 of the datasheet. + // Note: this is not an exhaustive test for I2C support on the pin: not + // all pins support I2C. + return ErrInvalidDataPin + } + + // reset SERCOM + i2c.Bus.CTRLA.SetBits(sam.SERCOM_I2CM_CTRLA_SWRST) + for i2c.Bus.CTRLA.HasBits(sam.SERCOM_I2CM_CTRLA_SWRST) || + i2c.Bus.SYNCBUSY.HasBits(sam.SERCOM_I2CM_SYNCBUSY_SWRST) { + } + + // Set i2c controller mode + //SERCOM_I2CM_CTRLA_MODE( I2C_MASTER_OPERATION ) + i2c.Bus.CTRLA.Set(sam.SERCOM_I2CM_CTRLA_MODE_I2C_MASTER << sam.SERCOM_I2CM_CTRLA_MODE_Pos) // | + + i2c.SetBaudRate(config.Frequency) + + // Enable I2CM port. + // sercom->USART.CTRLA.bit.ENABLE = 0x1u; + i2c.Bus.CTRLA.SetBits(sam.SERCOM_I2CM_CTRLA_ENABLE) + for i2c.Bus.SYNCBUSY.HasBits(sam.SERCOM_I2CM_SYNCBUSY_ENABLE) { + } + + // set bus idle mode + i2c.Bus.STATUS.SetBits(wireIdleState << sam.SERCOM_I2CM_STATUS_BUSSTATE_Pos) + for i2c.Bus.SYNCBUSY.HasBits(sam.SERCOM_I2CM_SYNCBUSY_SYSOP) { + } + + // enable pins + config.SDA.Configure(PinConfig{Mode: sdaPinMode}) + config.SCL.Configure(PinConfig{Mode: sclPinMode}) + + return nil +} + +// SetBaudRate sets the communication speed for I2C. +func (i2c *I2C) SetBaudRate(br uint32) error { + // Synchronous arithmetic baudrate, via Arduino SAMD implementation: + // SystemCoreClock / ( 2 * baudrate) - 5 - (((SystemCoreClock / 1000000) * WIRE_RISE_TIME_NANOSECONDS) / (2 * 1000)); + baud := CPUFrequency()/(2*br) - 5 - (((CPUFrequency() / 1000000) * riseTimeNanoseconds) / (2 * 1000)) + i2c.Bus.BAUD.Set(baud) + return nil +} + +// Tx does a single I2C transaction at the specified address. +// It clocks out the given address, writes the bytes in w, reads back len(r) +// bytes and stores them in r, and generates a stop condition on the bus. +func (i2c *I2C) Tx(addr uint16, w, r []byte) error { + var err error + if len(w) != 0 { + // send start/address for write + i2c.sendAddress(addr, true) + + // wait until transmission complete + timeout := i2cTimeout + for !i2c.Bus.INTFLAG.HasBits(sam.SERCOM_I2CM_INTFLAG_MB) { + timeout-- + if timeout == 0 { + return errI2CWriteTimeout + } + } + + // ACK received (0: ACK, 1: NACK) + if i2c.Bus.STATUS.HasBits(sam.SERCOM_I2CM_STATUS_RXNACK) { + return errI2CAckExpected + } + + // write data + for _, b := range w { + err = i2c.WriteByte(b) + if err != nil { + return err + } + } + + err = i2c.signalStop() + if err != nil { + return err + } + } + if len(r) != 0 { + // send start/address for read + i2c.sendAddress(addr, false) + + // wait transmission complete + for !i2c.Bus.INTFLAG.HasBits(sam.SERCOM_I2CM_INTFLAG_SB) { + // If the peripheral NACKS the address, the MB bit will be set. + // In that case, send a stop condition and return error. + if i2c.Bus.INTFLAG.HasBits(sam.SERCOM_I2CM_INTFLAG_MB) { + i2c.Bus.CTRLB.SetBits(wireCmdStop << sam.SERCOM_I2CM_CTRLB_CMD_Pos) // Stop condition + return errI2CAckExpected + } + } + + // ACK received (0: ACK, 1: NACK) + if i2c.Bus.STATUS.HasBits(sam.SERCOM_I2CM_STATUS_RXNACK) { + return errI2CAckExpected + } + + // read first byte + r[0] = i2c.readByte() + for i := 1; i < len(r); i++ { + // Send an ACK + i2c.Bus.CTRLB.ClearBits(sam.SERCOM_I2CM_CTRLB_ACKACT) + + i2c.signalRead() + + // Read data and send the ACK + r[i] = i2c.readByte() + } + + // Send NACK to end transmission + i2c.Bus.CTRLB.SetBits(sam.SERCOM_I2CM_CTRLB_ACKACT) + + err = i2c.signalStop() + if err != nil { + return err + } + } + + return nil +} + +// WriteByte writes a single byte to the I2C bus. +func (i2c *I2C) WriteByte(data byte) error { + // Send data byte + i2c.Bus.DATA.Set(data) + + // wait until transmission successful + timeout := i2cTimeout + for !i2c.Bus.INTFLAG.HasBits(sam.SERCOM_I2CM_INTFLAG_MB) { + // check for bus error + if sam.SERCOM3_I2CM.STATUS.HasBits(sam.SERCOM_I2CM_STATUS_BUSERR) { + return errI2CBusError + } + timeout-- + if timeout == 0 { + return errI2CWriteTimeout + } + } + + if i2c.Bus.STATUS.HasBits(sam.SERCOM_I2CM_STATUS_RXNACK) { + return errI2CAckExpected + } + + return nil +} + +// sendAddress sends the address and start signal +func (i2c *I2C) sendAddress(address uint16, write bool) error { + data := (address << 1) + if !write { + data |= 1 // set read flag + } + + // wait until bus ready + timeout := i2cTimeout + for !i2c.Bus.STATUS.HasBits(wireIdleState< 0 { + baudRate-- + } + spi.Bus.BAUD.Set(uint8(baudRate)) + + // Enable SPI port. + spi.Bus.CTRLA.SetBits(sam.SERCOM_SPI_CTRLA_ENABLE) + for spi.Bus.SYNCBUSY.HasBits(sam.SERCOM_SPI_SYNCBUSY_ENABLE) { + } + + return nil +} + +// Transfer writes/reads a single byte using the SPI interface. +func (spi *SPI) Transfer(w byte) (byte, error) { + // write data + spi.Bus.DATA.Set(uint32(w)) + + // wait for receive + for !spi.Bus.INTFLAG.HasBits(sam.SERCOM_SPI_INTFLAG_RXC) { + } + + // return data + return byte(spi.Bus.DATA.Get()), nil +} + +// Tx handles read/write operation for SPI interface. Since SPI is a synchronous write/read +// interface, there must always be the same number of bytes written as bytes read. +// The Tx method knows about this, and offers a few different ways of calling it. +// +// This form sends the bytes in tx buffer, putting the resulting bytes read into the rx buffer. +// Note that the tx and rx buffers must be the same size: +// +// spi.Tx(tx, rx) +// +// This form sends the tx buffer, ignoring the result. Useful for sending "commands" that return zeros +// until all the bytes in the command packet have been received: +// +// spi.Tx(tx, nil) +// +// This form sends zeros, putting the result into the rx buffer. Good for reading a "result packet": +// +// spi.Tx(nil, rx) +func (spi *SPI) Tx(w, r []byte) error { + switch { + case w == nil: + // read only, so write zero and read a result. + spi.rx(r) + case r == nil: + // write only + spi.tx(w) + + default: + // write/read + if len(w) != len(r) { + return ErrTxInvalidSliceSize + } + + spi.txrx(w, r) + } + + return nil +} + +func (spi *SPI) tx(tx []byte) { + for i := 0; i < len(tx); i++ { + for !spi.Bus.INTFLAG.HasBits(sam.SERCOM_SPI_INTFLAG_DRE) { + } + spi.Bus.DATA.Set(uint32(tx[i])) + } + for !spi.Bus.INTFLAG.HasBits(sam.SERCOM_SPI_INTFLAG_TXC) { + } + + // read to clear RXC register + for spi.Bus.INTFLAG.HasBits(sam.SERCOM_SPI_INTFLAG_RXC) { + spi.Bus.DATA.Get() + } +} + +func (spi *SPI) rx(rx []byte) { + spi.Bus.DATA.Set(0) + for !spi.Bus.INTFLAG.HasBits(sam.SERCOM_SPI_INTFLAG_DRE) { + } + + for i := 1; i < len(rx); i++ { + spi.Bus.DATA.Set(0) + for !spi.Bus.INTFLAG.HasBits(sam.SERCOM_SPI_INTFLAG_RXC) { + } + rx[i-1] = byte(spi.Bus.DATA.Get()) + } + for !spi.Bus.INTFLAG.HasBits(sam.SERCOM_SPI_INTFLAG_RXC) { + } + rx[len(rx)-1] = byte(spi.Bus.DATA.Get()) +} + +func (spi *SPI) txrx(tx, rx []byte) { + spi.Bus.DATA.Set(uint32(tx[0])) + for !spi.Bus.INTFLAG.HasBits(sam.SERCOM_SPI_INTFLAG_DRE) { + } + + for i := 1; i < len(rx); i++ { + spi.Bus.DATA.Set(uint32(tx[i])) + for !spi.Bus.INTFLAG.HasBits(sam.SERCOM_SPI_INTFLAG_RXC) { + } + rx[i-1] = byte(spi.Bus.DATA.Get()) + } + for !spi.Bus.INTFLAG.HasBits(sam.SERCOM_SPI_INTFLAG_RXC) { + } + rx[len(rx)-1] = byte(spi.Bus.DATA.Get()) +} + +// TCC is one timer/counter peripheral, which consists of a counter and multiple +// output channels (that can be connected to actual pins). You can set the +// frequency using SetPeriod, but only for all the channels in this TCC +// peripheral at once. +type TCC sam.TCC_Type + +// The SAM D21 has three TCC peripherals, which have PWM as one feature. +var ( + TCC0 = (*TCC)(sam.TCC0) + TCC1 = (*TCC)(sam.TCC1) + TCC2 = (*TCC)(sam.TCC2) +) + +//go:inline +func (tcc *TCC) timer() *sam.TCC_Type { + return (*sam.TCC_Type)(tcc) +} + +// Configure enables and configures this TCC. +func (tcc *TCC) Configure(config PWMConfig) error { + // Enable the clock source for this timer. + switch tcc.timer() { + case sam.TCC0: + sam.PM.APBCMASK.SetBits(sam.PM_APBCMASK_TCC0_) + // Use GCLK0 for TCC0/TCC1 + sam.GCLK.CLKCTRL.Set((sam.GCLK_CLKCTRL_ID_TCC0_TCC1 << sam.GCLK_CLKCTRL_ID_Pos) | + (sam.GCLK_CLKCTRL_GEN_GCLK0 << sam.GCLK_CLKCTRL_GEN_Pos) | + sam.GCLK_CLKCTRL_CLKEN) + for sam.GCLK.STATUS.HasBits(sam.GCLK_STATUS_SYNCBUSY) { + } + case sam.TCC1: + sam.PM.APBCMASK.SetBits(sam.PM_APBCMASK_TCC1_) + // Use GCLK0 for TCC0/TCC1 + sam.GCLK.CLKCTRL.Set((sam.GCLK_CLKCTRL_ID_TCC0_TCC1 << sam.GCLK_CLKCTRL_ID_Pos) | + (sam.GCLK_CLKCTRL_GEN_GCLK0 << sam.GCLK_CLKCTRL_GEN_Pos) | + sam.GCLK_CLKCTRL_CLKEN) + for sam.GCLK.STATUS.HasBits(sam.GCLK_STATUS_SYNCBUSY) { + } + case sam.TCC2: + sam.PM.APBCMASK.SetBits(sam.PM_APBCMASK_TCC2_) + // Use GCLK0 for TCC2/TC3 + sam.GCLK.CLKCTRL.Set((sam.GCLK_CLKCTRL_ID_TCC2_TC3 << sam.GCLK_CLKCTRL_ID_Pos) | + (sam.GCLK_CLKCTRL_GEN_GCLK0 << sam.GCLK_CLKCTRL_GEN_Pos) | + sam.GCLK_CLKCTRL_CLKEN) + for sam.GCLK.STATUS.HasBits(sam.GCLK_STATUS_SYNCBUSY) { + } + } + + // Disable timer (if it was enabled). This is necessary because + // tcc.setPeriod may want to change the prescaler bits in CTRLA, which is + // only allowed when the TCC is disabled. + tcc.timer().CTRLA.ClearBits(sam.TCC_CTRLA_ENABLE) + + // Use "Normal PWM" (single-slope PWM) + tcc.timer().WAVE.Set(sam.TCC_WAVE_WAVEGEN_NPWM) + + // Wait for synchronization of all changed registers. + for tcc.timer().SYNCBUSY.Get() != 0 { + } + + // Set the period and prescaler. + err := tcc.setPeriod(config.Period, true) + + // Enable the timer. + tcc.timer().CTRLA.SetBits(sam.TCC_CTRLA_ENABLE) + + // Wait for synchronization of all changed registers. + for tcc.timer().SYNCBUSY.Get() != 0 { + } + + // Return any error that might have occurred in the tcc.setPeriod call. + return err +} + +// SetPeriod updates the period of this TCC peripheral. +// To set a particular frequency, use the following formula: +// +// period = 1e9 / frequency +// +// If you use a period of 0, a period that works well for LEDs will be picked. +// +// SetPeriod will not change the prescaler, but also won't change the current +// value in any of the channels. This means that you may need to update the +// value for the particular channel. +// +// Note that you cannot pick any arbitrary period after the TCC peripheral has +// been configured. If you want to switch between frequencies, pick the lowest +// frequency (longest period) once when calling Configure and adjust the +// frequency here as needed. +func (tcc *TCC) SetPeriod(period uint64) error { + err := tcc.setPeriod(period, false) + if err == nil { + if tcc.Counter() >= tcc.Top() { + // When setting the timer to a shorter period, there is a chance + // that it passes the counter value and thus goes all the way to MAX + // before wrapping back to zero. + // To avoid this, reset the counter back to 0. + tcc.timer().COUNT.Set(0) + } + } + return err +} + +// setPeriod sets the period of this TCC, possibly updating the prescaler as +// well. The prescaler can only modified when the TCC is disabled, that is, in +// the Configure function. +func (tcc *TCC) setPeriod(period uint64, updatePrescaler bool) error { + var top uint64 + if period == 0 { + // Make sure the TOP value is at 0xffff (enough for a 16-bit timer). + top = 0xffff + } else { + // The formula below calculates the following formula, optimized: + // period * (48e6 / 1e9) + // This assumes that the chip is running at the (default) 48MHz speed. + top = period * 6 / 125 + } + + maxTop := uint64(0xffffff) + if tcc.timer() == sam.TCC2 { + // TCC2 is a 16-bit timer, not a 24-bit timer. + maxTop = 0xffff + } + + if updatePrescaler { + // This function was called during Configure(), with the timer disabled. + // Note that updating the prescaler can only happen while the peripheral + // is disabled. + var prescaler uint32 + switch { + case top <= maxTop: + prescaler = sam.TCC_CTRLA_PRESCALER_DIV1 + case top/2 <= maxTop: + prescaler = sam.TCC_CTRLA_PRESCALER_DIV2 + top = top / 2 + case top/4 <= maxTop: + prescaler = sam.TCC_CTRLA_PRESCALER_DIV4 + top = top / 4 + case top/8 <= maxTop: + prescaler = sam.TCC_CTRLA_PRESCALER_DIV8 + top = top / 8 + case top/16 <= maxTop: + prescaler = sam.TCC_CTRLA_PRESCALER_DIV16 + top = top / 16 + case top/64 <= maxTop: + prescaler = sam.TCC_CTRLA_PRESCALER_DIV64 + top = top / 64 + case top/256 <= maxTop: + prescaler = sam.TCC_CTRLA_PRESCALER_DIV256 + top = top / 256 + case top/1024 <= maxTop: + prescaler = sam.TCC_CTRLA_PRESCALER_DIV1024 + top = top / 1024 + default: + return ErrPWMPeriodTooLong + } + tcc.timer().CTRLA.Set((tcc.timer().CTRLA.Get() &^ sam.TCC_CTRLA_PRESCALER_Msk) | (prescaler << sam.TCC_CTRLA_PRESCALER_Pos)) + } else { + // Do not update the prescaler, but use the already-configured + // prescaler. This is the normal SetPeriod case, where the prescaler + // must not be changed. + prescaler := (tcc.timer().CTRLA.Get() & sam.TCC_CTRLA_PRESCALER_Msk) >> sam.TCC_CTRLA_PRESCALER_Pos + switch prescaler { + case sam.TCC_CTRLA_PRESCALER_DIV1: + top /= 1 // no-op + case sam.TCC_CTRLA_PRESCALER_DIV2: + top /= 2 + case sam.TCC_CTRLA_PRESCALER_DIV4: + top /= 4 + case sam.TCC_CTRLA_PRESCALER_DIV8: + top /= 8 + case sam.TCC_CTRLA_PRESCALER_DIV16: + top /= 16 + case sam.TCC_CTRLA_PRESCALER_DIV64: + top /= 64 + case sam.TCC_CTRLA_PRESCALER_DIV256: + top /= 256 + case sam.TCC_CTRLA_PRESCALER_DIV1024: + top /= 1024 + default: + // unreachable + } + if top > maxTop { + return ErrPWMPeriodTooLong + } + } + + // Set the period (the counter top). + tcc.timer().PER.Set(uint32(top) - 1) + + // Wait for synchronization of CTRLA.PRESCALER and PER registers. + for tcc.timer().SYNCBUSY.Get() != 0 { + } + + return nil +} + +// Top returns the current counter top, for use in duty cycle calculation. It +// will only change with a call to Configure or SetPeriod, otherwise it is +// constant. +// +// The value returned here is hardware dependent. In general, it's best to treat +// it as an opaque value that can be divided by some number and passed to Set +// (see Set documentation for more information). +func (tcc *TCC) Top() uint32 { + return tcc.timer().PER.Get() + 1 +} + +// Counter returns the current counter value of the timer in this TCC +// peripheral. It may be useful for debugging. +func (tcc *TCC) Counter() uint32 { + tcc.timer().CTRLBSET.Set(sam.TCC_CTRLBSET_CMD_READSYNC << sam.TCC_CTRLBSET_CMD_Pos) + for tcc.timer().SYNCBUSY.Get() != 0 { + } + return tcc.timer().COUNT.Get() +} + +// Some constants to make pinTimerMapping below easier to read. +const ( + pinTCC0 = 1 + pinTCC1 = 2 + pinTCC2 = 3 + pinTimerCh0 = 0 << 3 + pinTimerCh2 = 1 << 3 + pinTCC0Ch0 = pinTCC0 | pinTimerCh0 + pinTCC0Ch2 = pinTCC0 | pinTimerCh2 + pinTCC1Ch0 = pinTCC1 | pinTimerCh0 + pinTCC1Ch2 = pinTCC1 | pinTimerCh2 + pinTCC2Ch0 = pinTCC2 | pinTimerCh0 +) + +// Mapping from pin number to TCC peripheral and channel using a special +// encoding. Note that only TCC0-TCC2 are included, not TC3 and up. +// Every byte is split in two nibbles where the low nibble describes PinTCC and +// the high nibble describes PinTCCAlt. Within a nibble, there is one bit that +// indicates Ch0/Ch1 or Ch2/Ch3, and three other bits that contain the TCC +// peripheral number plus one (to distinguish between TCC0Ch0 and 0). +// +// The encoding can be so compact because all pins are configured in pairs, so +// if you know PA00 you can infer the configuration of PA01. And only channel 0 +// or 2 need to be included (taking up just one bit), because channel 0 and 2 +// are only ever used on odd pins and channel 1 and 3 on even pins, again using +// the pin pair pattern to reduce the amount of information needed to be stored. +// +// Datasheet: https://cdn.sparkfun.com/datasheets/Dev/Arduino/Boards/Atmel-42181-SAM-D21_Datasheet.pdf +var pinTimerMapping = [...]uint8{ + // page 21 + PA00 / 2: pinTCC2Ch0 | 0, + PA04 / 2: pinTCC0Ch0 | 0, + PA06 / 2: pinTCC1Ch0 | 0, + PA08 / 2: pinTCC0Ch0 | pinTCC1Ch2<<4, + PA10 / 2: pinTCC1Ch0 | pinTCC0Ch2<<4, + // page 22 + PB10 / 2: 0 | pinTCC0Ch0<<4, + PB12 / 2: 0 | pinTCC0Ch2<<4, + PA12 / 2: pinTCC2Ch0 | pinTCC0Ch2<<4, + PA14 / 2: 0 | pinTCC0Ch0<<4, + PA16 / 2: pinTCC2Ch0 | pinTCC0Ch2<<4, + PA18 / 2: 0 | pinTCC0Ch2<<4, + PB16 / 2: 0 | pinTCC0Ch0<<4, + PA20 / 2: 0 | pinTCC0Ch2<<4, + PA22 / 2: 0 | pinTCC0Ch0<<4, + PA24 / 2: 0 | pinTCC1Ch2<<4, + // page 23 + PA30 / 2: 0 | pinTCC1Ch0<<4, + PB30 / 2: pinTCC0Ch0 | pinTCC1Ch2<<4, +} + +// findPinTimerMapping returns the pin mode (PinTCC or PinTCCAlt) and the channel +// number for a given timer and pin. A zero PinMode is returned if no mapping +// could be found. +func findPinTimerMapping(timer uint8, pin Pin) (PinMode, uint8) { + mapping := pinTimerMapping[pin/2] + // evenChannel below indicates the channel 0 or 2, for the even part of the + // pin pair. The next pin will also have the next channel (1 or 3). + if mapping&0x07 == timer+1 { + // PWM output is on peripheral function E. + evenChannel := ((mapping >> 3) & 1) * 2 + return PinTCC, evenChannel + uint8(pin&1) + } + if (mapping&0x70)>>4 == timer+1 { + // PWM output is on peripheral function F. + evenChannel := ((mapping >> 7) & 1) * 2 + return PinTCCAlt, evenChannel + uint8(pin&1) + } + return 0, 0 +} + +// Channel returns a PWM channel for the given pin. Note that one channel may be +// shared between multiple pins, and so will have the same duty cycle. If this +// is not desirable, look for a different TCC peripheral or consider using a +// different pin. +func (tcc *TCC) Channel(pin Pin) (uint8, error) { + var pinMode PinMode + var channel uint8 + switch tcc.timer() { + case sam.TCC0: + pinMode, channel = findPinTimerMapping(0, pin) + case sam.TCC1: + pinMode, channel = findPinTimerMapping(1, pin) + case sam.TCC2: + pinMode, channel = findPinTimerMapping(2, pin) + } + + if pinMode == 0 { + // No pin could be found. + return 0, ErrInvalidOutputPin + } + + // Enable the port multiplexer for pin + pin.setPinCfg(sam.PORT_PINCFG0_PMUXEN) + + if pin&1 > 0 { + // odd pin, so save the even pins + val := pin.getPMux() & sam.PORT_PMUX0_PMUXE_Msk + pin.setPMux(val | uint8(pinMode<> 6) + syncDAC() + return nil +} + +func syncDAC() { + for sam.DAC.STATUS.HasBits(sam.DAC_STATUS_SYNCBUSY) { + } +} + +// Flash related code +const memoryStart = 0x0 + +// compile-time check for ensuring we fulfill BlockDevice interface +var _ BlockDevice = flashBlockDevice{} + +var Flash flashBlockDevice + +type flashBlockDevice struct { + initComplete bool +} + +// ReadAt reads the given number of bytes from the block device. +func (f flashBlockDevice) ReadAt(p []byte, off int64) (n int, err error) { + if FlashDataStart()+uintptr(off)+uintptr(len(p)) > FlashDataEnd() { + return 0, errFlashCannotReadPastEOF + } + + f.ensureInitComplete() + + waitWhileFlashBusy() + + data := unsafe.Slice((*byte)(unsafe.Add(unsafe.Pointer(FlashDataStart()), uintptr(off))), len(p)) + copy(p, data) + + return len(p), nil +} + +// WriteAt writes the given number of bytes to the block device. +// Data is written to the page buffer in 4-byte chunks, then saved to flash memory. +// See Atmel-42181G–SAM-D21_Datasheet–09/2015 page 359. +// If the length of p is not long enough it will be padded with 0xFF bytes. +// This method assumes that the destination is already erased. +func (f flashBlockDevice) WriteAt(p []byte, off int64) (n int, err error) { + if FlashDataStart()+uintptr(off)+uintptr(len(p)) > FlashDataEnd() { + return 0, errFlashCannotWritePastEOF + } + + f.ensureInitComplete() + + address := FlashDataStart() + uintptr(off) + padded := flashPad(p, int(f.WriteBlockSize())) + + waitWhileFlashBusy() + + for j := 0; j < len(padded); j += int(f.WriteBlockSize()) { + // page buffer is 64 bytes long, but only 4 bytes can be written at once + for k := 0; k < int(f.WriteBlockSize()); k += 4 { + *(*uint32)(unsafe.Pointer(address + uintptr(k))) = binary.LittleEndian.Uint32(padded[j+k : j+k+4]) + } + + sam.NVMCTRL.SetADDR(uint32(address >> 1)) + sam.NVMCTRL.CTRLA.Set(sam.NVMCTRL_CTRLA_CMD_WP | (sam.NVMCTRL_CTRLA_CMDEX_KEY << sam.NVMCTRL_CTRLA_CMDEX_Pos)) + + waitWhileFlashBusy() + + if err := checkFlashError(); err != nil { + return j, err + } + + address += uintptr(f.WriteBlockSize()) + } + + return len(padded), nil +} + +// Size returns the number of bytes in this block device. +func (f flashBlockDevice) Size() int64 { + return int64(FlashDataEnd() - FlashDataStart()) +} + +const writeBlockSize = 64 + +// WriteBlockSize returns the block size in which data can be written to +// memory. It can be used by a client to optimize writes, non-aligned writes +// should always work correctly. +func (f flashBlockDevice) WriteBlockSize() int64 { + return writeBlockSize +} + +const eraseBlockSizeValue = 256 + +func eraseBlockSize() int64 { + return eraseBlockSizeValue +} + +// EraseBlockSize returns the smallest erasable area on this particular chip +// in bytes. This is used for the block size in EraseBlocks. +func (f flashBlockDevice) EraseBlockSize() int64 { + return eraseBlockSize() +} + +// EraseBlocks erases the given number of blocks. An implementation may +// transparently coalesce ranges of blocks into larger bundles if the chip +// supports this. The start and len parameters are in block numbers, use +// EraseBlockSize to map addresses to blocks. +func (f flashBlockDevice) EraseBlocks(start, len int64) error { + f.ensureInitComplete() + + address := FlashDataStart() + uintptr(start*f.EraseBlockSize()) + waitWhileFlashBusy() + + for i := start; i < start+len; i++ { + sam.NVMCTRL.SetADDR(uint32(address >> 1)) + sam.NVMCTRL.CTRLA.Set(sam.NVMCTRL_CTRLA_CMD_ER | (sam.NVMCTRL_CTRLA_CMDEX_KEY << sam.NVMCTRL_CTRLA_CMDEX_Pos)) + + waitWhileFlashBusy() + + if err := checkFlashError(); err != nil { + return err + } + + address += uintptr(f.EraseBlockSize()) + } + + return nil +} + +func (f flashBlockDevice) ensureInitComplete() { + if f.initComplete { + return + } + + sam.NVMCTRL.SetCTRLB_READMODE(sam.NVMCTRL_CTRLB_READMODE_NO_MISS_PENALTY) + sam.NVMCTRL.SetCTRLB_SLEEPPRM(sam.NVMCTRL_CTRLB_SLEEPPRM_WAKEONACCESS) + + waitWhileFlashBusy() + + f.initComplete = true +} + +func waitWhileFlashBusy() { + for sam.NVMCTRL.GetINTFLAG_READY() != sam.NVMCTRL_INTFLAG_READY { + } +} + +var ( + errFlashPROGE = errors.New("errFlashPROGE") + errFlashLOCKE = errors.New("errFlashLOCKE") + errFlashNVME = errors.New("errFlashNVME") +) + +func checkFlashError() error { + switch { + case sam.NVMCTRL.GetSTATUS_PROGE() != 0: + return errFlashPROGE + case sam.NVMCTRL.GetSTATUS_LOCKE() != 0: + return errFlashLOCKE + case sam.NVMCTRL.GetSTATUS_NVME() != 0: + return errFlashNVME + } + + return nil +} + +// Watchdog provides access to the hardware watchdog available +// in the SAMD21. +var Watchdog = &watchdogImpl{} + +const ( + // WatchdogMaxTimeout in milliseconds (16s) + WatchdogMaxTimeout = (16384 * 1000) / 1024 +) + +type watchdogImpl struct{} + +// Configure the watchdog. +// +// This method should not be called after the watchdog is started and on +// some platforms attempting to reconfigure after starting the watchdog +// is explicitly forbidden / will not work. +func (wd *watchdogImpl) Configure(config WatchdogConfig) error { + // Use OSCULP32K as source for Generic Clock Generator 8, divided by 32 to get 1.024kHz + sam.GCLK.GENDIV.Set(sam.GCLK_CLKCTRL_GEN_GCLK8 | (32 << sam.GCLK_GENDIV_DIV_Pos)) + sam.GCLK.GENCTRL.Set(sam.GCLK_CLKCTRL_GEN_GCLK8 | (sam.GCLK_GENCTRL_SRC_OSCULP32K << sam.GCLK_GENCTRL_SRC_Pos) | sam.GCLK_GENCTRL_GENEN) + waitForSync() + + // Use GCLK8 for watchdog + sam.GCLK.CLKCTRL.Set(sam.GCLK_CLKCTRL_ID_WDT | (sam.GCLK_CLKCTRL_GEN_GCLK8 << sam.GCLK_CLKCTRL_GEN_Pos) | sam.GCLK_CLKCTRL_CLKEN) + + // Power on the watchdog peripheral + sam.PM.APBAMASK.SetBits(sam.PM_APBAMASK_WDT_) + + // 1.024kHz clock + cycles := int((int64(config.TimeoutMillis) * 1024) / 1000) + + // period is expressed as a power-of-two, starting at 8 / 1024ths of a second + period := uint8(0) + cfgCycles := 8 + for cfgCycles < cycles { + period++ + cfgCycles <<= 1 + + if period >= 0xB { + break + } + } + + sam.WDT.CONFIG.Set(period << sam.WDT_CONFIG_PER_Pos) + + return nil +} + +// Starts the watchdog. +func (wd *watchdogImpl) Start() error { + sam.WDT.CTRL.SetBits(sam.WDT_CTRL_ENABLE) + return nil +} + +// Update the watchdog, indicating that `source` is healthy. +func (wd *watchdogImpl) Update() { + sam.WDT.CLEAR.Set(sam.WDT_CLEAR_CLEAR_KEY) +} diff --git a/emb/machine/machine_atsamd21_simulator.go b/emb/machine/machine_atsamd21_simulator.go new file mode 100644 index 00000000..2a22533e --- /dev/null +++ b/emb/machine/machine_atsamd21_simulator.go @@ -0,0 +1,45 @@ +//go:build !baremetal && (gemma_m0 || qtpy || trinket_m0 || arduino_mkr1000 || arduino_mkrwifi1010 || arduino_nano33 || arduino_zero || circuitplay_express || feather_m0_express || feather_m0 || itsybitsy_m0 || p1am_100 || xiao) + +// Simulated atsamd21 chips. + +package machine + +// The timer channels/pins match the hardware, and encode the same information +// as pinTimerMapping but in a more generic (less efficient) way. + +var TCC0 = &timerType{ + instance: 0, + frequency: 48e6, + bits: 24, + prescalers: []int{1, 2, 4, 8, 16, 64, 256, 1024}, + channelPins: [][]Pin{ + {PA04, PA08, PB10, PA14, PB16, PA22, PB30}, // channel 0 + {PA05, PA09, PB11, PA15, PB17, PA23, PB31}, // channel 1 + {PA10, PB12, PA12, PA16, PA18, PA20}, // channel 2 + {PA11, PB13, PA13, PA17, PA19, PA21}, // channel 3 + }, +} + +var TCC1 = &timerType{ + instance: 1, + frequency: 48e6, + bits: 24, + prescalers: []int{1, 2, 4, 8, 16, 64, 256, 1024}, + channelPins: [][]Pin{ + {PA06, PA10, PA30}, // channel 0 + {PA07, PA11, PA31}, // channel 1 + {PA08, PA24, PB30}, // channel 2 + {PA09, PA25, PB31}, // channel 3 + }, +} + +var TCC2 = &timerType{ + instance: 2, + frequency: 48e6, + bits: 16, + prescalers: []int{1, 2, 4, 8, 16, 64, 256, 1024}, + channelPins: [][]Pin{ + {PA00, PA12, PA16}, // channel 0 + {PA01, PA13, PA17}, // channel 1 + }, +} diff --git a/emb/machine/machine_atsamd21_usb.go b/emb/machine/machine_atsamd21_usb.go new file mode 100644 index 00000000..7b9d2e14 --- /dev/null +++ b/emb/machine/machine_atsamd21_usb.go @@ -0,0 +1,664 @@ +//go:build sam && atsamd21 + +package machine + +import ( + "device/sam" + "machine/usb" + "runtime/interrupt" + "unsafe" +) + +const ( + // these are SAMD21 specific. + usb_DEVICE_PCKSIZE_BYTE_COUNT_Pos = 0 + usb_DEVICE_PCKSIZE_BYTE_COUNT_Mask = 0x3FFF + + usb_DEVICE_PCKSIZE_SIZE_Pos = 28 + usb_DEVICE_PCKSIZE_SIZE_Mask = 0x7 + + usb_DEVICE_PCKSIZE_MULTI_PACKET_SIZE_Pos = 14 + usb_DEVICE_PCKSIZE_MULTI_PACKET_SIZE_Mask = 0x3FFF + + NumberOfUSBEndpoints = 8 +) + +var ( + endPoints = []uint32{ + usb.CONTROL_ENDPOINT: usb.ENDPOINT_TYPE_CONTROL, + usb.CDC_ENDPOINT_ACM: (usb.ENDPOINT_TYPE_INTERRUPT | usb.EndpointIn), + usb.CDC_ENDPOINT_OUT: (usb.ENDPOINT_TYPE_BULK | usb.EndpointOut), + usb.CDC_ENDPOINT_IN: (usb.ENDPOINT_TYPE_BULK | usb.EndpointIn), + usb.HID_ENDPOINT_IN: (usb.ENDPOINT_TYPE_DISABLE), // Interrupt In + usb.HID_ENDPOINT_OUT: (usb.ENDPOINT_TYPE_DISABLE), // Interrupt Out + usb.MIDI_ENDPOINT_IN: (usb.ENDPOINT_TYPE_DISABLE), // Bulk In + usb.MIDI_ENDPOINT_OUT: (usb.ENDPOINT_TYPE_DISABLE), // Bulk Out + } +) + +// Configure the USB peripheral. The config is here for compatibility with the UART interface. +func (dev *USBDevice) Configure(config UARTConfig) { + if dev.initcomplete { + return + } + + // reset USB interface + sam.USB_DEVICE.CTRLA.SetBits(sam.USB_DEVICE_CTRLA_SWRST) + for sam.USB_DEVICE.SYNCBUSY.HasBits(sam.USB_DEVICE_SYNCBUSY_SWRST) || + sam.USB_DEVICE.SYNCBUSY.HasBits(sam.USB_DEVICE_SYNCBUSY_ENABLE) { + } + + sam.USB_DEVICE.DESCADD.Set(uint32(uintptr(unsafe.Pointer(&usbEndpointDescriptors)))) + + // configure pins + USBCDC_DM_PIN.Configure(PinConfig{Mode: PinCom}) + USBCDC_DP_PIN.Configure(PinConfig{Mode: PinCom}) + + // performs pad calibration from store fuses + handlePadCalibration() + + // run in standby + sam.USB_DEVICE.CTRLA.SetBits(sam.USB_DEVICE_CTRLA_RUNSTDBY) + + // set full speed + sam.USB_DEVICE.CTRLB.SetBits(sam.USB_DEVICE_CTRLB_SPDCONF_FS << sam.USB_DEVICE_CTRLB_SPDCONF_Pos) + + // attach + sam.USB_DEVICE.CTRLB.ClearBits(sam.USB_DEVICE_CTRLB_DETACH) + + // enable interrupt for end of reset + sam.USB_DEVICE.INTENSET.SetBits(sam.USB_DEVICE_INTENSET_EORST) + + // enable interrupt for start of frame + sam.USB_DEVICE.INTENSET.SetBits(sam.USB_DEVICE_INTENSET_SOF) + + // enable USB + sam.USB_DEVICE.CTRLA.SetBits(sam.USB_DEVICE_CTRLA_ENABLE) + + // enable IRQ + interrupt.New(sam.IRQ_USB, handleUSBIRQ).Enable() + + dev.initcomplete = true +} + +func handlePadCalibration() { + // Load Pad Calibration data from non-volatile memory + // This requires registers that are not included in the SVD file. + // Modeled after defines from samd21g18a.h and nvmctrl.h: + // + // #define NVMCTRL_OTP4 0x00806020 + // + // #define USB_FUSES_TRANSN_ADDR (NVMCTRL_OTP4 + 4) + // #define USB_FUSES_TRANSN_Pos 13 /**< \brief (NVMCTRL_OTP4) USB pad Transn calibration */ + // #define USB_FUSES_TRANSN_Msk (0x1Fu << USB_FUSES_TRANSN_Pos) + // #define USB_FUSES_TRANSN(value) ((USB_FUSES_TRANSN_Msk & ((value) << USB_FUSES_TRANSN_Pos))) + + // #define USB_FUSES_TRANSP_ADDR (NVMCTRL_OTP4 + 4) + // #define USB_FUSES_TRANSP_Pos 18 /**< \brief (NVMCTRL_OTP4) USB pad Transp calibration */ + // #define USB_FUSES_TRANSP_Msk (0x1Fu << USB_FUSES_TRANSP_Pos) + // #define USB_FUSES_TRANSP(value) ((USB_FUSES_TRANSP_Msk & ((value) << USB_FUSES_TRANSP_Pos))) + + // #define USB_FUSES_TRIM_ADDR (NVMCTRL_OTP4 + 4) + // #define USB_FUSES_TRIM_Pos 23 /**< \brief (NVMCTRL_OTP4) USB pad Trim calibration */ + // #define USB_FUSES_TRIM_Msk (0x7u << USB_FUSES_TRIM_Pos) + // #define USB_FUSES_TRIM(value) ((USB_FUSES_TRIM_Msk & ((value) << USB_FUSES_TRIM_Pos))) + // + fuse := *(*uint32)(unsafe.Pointer(uintptr(0x00806020) + 4)) + calibTransN := uint16(fuse>>13) & uint16(0x1f) + calibTransP := uint16(fuse>>18) & uint16(0x1f) + calibTrim := uint16(fuse>>23) & uint16(0x7) + + if calibTransN == 0x1f { + calibTransN = 5 + } + sam.USB_DEVICE.PADCAL.SetBits(calibTransN << sam.USB_DEVICE_PADCAL_TRANSN_Pos) + + if calibTransP == 0x1f { + calibTransP = 29 + } + sam.USB_DEVICE.PADCAL.SetBits(calibTransP << sam.USB_DEVICE_PADCAL_TRANSP_Pos) + + if calibTrim == 0x7 { + calibTrim = 3 + } + sam.USB_DEVICE.PADCAL.SetBits(calibTrim << sam.USB_DEVICE_PADCAL_TRIM_Pos) +} + +func handleUSBIRQ(intr interrupt.Interrupt) { + // reset all interrupt flags + flags := sam.USB_DEVICE.INTFLAG.Get() + sam.USB_DEVICE.INTFLAG.Set(flags) + + // End of reset + if (flags & sam.USB_DEVICE_INTFLAG_EORST) > 0 { + // Configure control endpoint + initEndpoint(0, usb.ENDPOINT_TYPE_CONTROL) + + usbConfiguration = 0 + + // ack the End-Of-Reset interrupt + sam.USB_DEVICE.INTFLAG.Set(sam.USB_DEVICE_INTFLAG_EORST) + } + + // Start of frame + if (flags & sam.USB_DEVICE_INTFLAG_SOF) > 0 { + // if you want to blink LED showing traffic, this would be the place... + } + + // Endpoint 0 Setup interrupt + if getEPINTFLAG(0)&sam.USB_DEVICE_EPINTFLAG_RXSTP > 0 { + // ack setup received + setEPINTFLAG(0, sam.USB_DEVICE_EPINTFLAG_RXSTP) + + // parse setup + setup := usb.NewSetup(udd_ep_out_cache_buffer[0][:]) + + // Clear the Bank 0 ready flag on Control OUT + usbEndpointDescriptors[0].DeviceDescBank[0].ADDR.Set(uint32(uintptr(unsafe.Pointer(&udd_ep_out_cache_buffer[0])))) + usbEndpointDescriptors[0].DeviceDescBank[0].PCKSIZE.ClearBits(usb_DEVICE_PCKSIZE_BYTE_COUNT_Mask << usb_DEVICE_PCKSIZE_BYTE_COUNT_Pos) + setEPSTATUSCLR(0, sam.USB_DEVICE_EPSTATUSCLR_BK0RDY) + + ok := false + if (setup.BmRequestType & usb.REQUEST_TYPE) == usb.REQUEST_STANDARD { + // Standard Requests + ok = handleStandardSetup(setup) + } else { + // Class Interface Requests + if setup.WIndex < uint16(len(usbSetupHandler)) && usbSetupHandler[setup.WIndex] != nil { + ok = usbSetupHandler[setup.WIndex](setup) + } + } + + if ok { + // set Bank1 ready + setEPSTATUSSET(0, sam.USB_DEVICE_EPSTATUSSET_BK1RDY) + } else { + // Stall endpoint + setEPSTATUSSET(0, sam.USB_DEVICE_EPINTFLAG_STALL1) + } + + if getEPINTFLAG(0)&sam.USB_DEVICE_EPINTFLAG_STALL1 > 0 { + // ack the stall + setEPINTFLAG(0, sam.USB_DEVICE_EPINTFLAG_STALL1) + + // clear stall request + setEPINTENCLR(0, sam.USB_DEVICE_EPINTENCLR_STALL1) + } + } + + // Now the actual transfer handlers, ignore endpoint number 0 (setup) + var i uint32 + for i = 1; i < uint32(len(endPoints)); i++ { + // Check if endpoint has a pending interrupt + epFlags := getEPINTFLAG(i) + setEPINTFLAG(i, epFlags) + if (epFlags & sam.USB_DEVICE_EPINTFLAG_TRCPT0) > 0 { + buf := handleEndpointRx(i) + if usbRxHandler[i] == nil || usbRxHandler[i](buf) { + AckUsbOutTransfer(i) + } + } else if (epFlags & sam.USB_DEVICE_EPINTFLAG_TRCPT1) > 0 { + if usbTxHandler[i] != nil { + usbTxHandler[i]() + } + } + } +} + +func initEndpoint(ep, config uint32) { + switch config { + case usb.ENDPOINT_TYPE_INTERRUPT | usb.EndpointIn: + // set packet size + usbEndpointDescriptors[ep].DeviceDescBank[1].PCKSIZE.SetBits(epPacketSize(64) << usb_DEVICE_PCKSIZE_SIZE_Pos) + + // set data buffer address + usbEndpointDescriptors[ep].DeviceDescBank[1].ADDR.Set(uint32(uintptr(unsafe.Pointer(&udd_ep_in_cache_buffer[ep])))) + + // set endpoint type + setEPCFG(ep, ((usb.ENDPOINT_TYPE_INTERRUPT + 1) << sam.USB_DEVICE_EPCFG_EPTYPE1_Pos)) + + setEPINTENSET(ep, sam.USB_DEVICE_EPINTENSET_TRCPT1) + + case usb.ENDPOINT_TYPE_BULK | usb.EndpointOut: + // set packet size + usbEndpointDescriptors[ep].DeviceDescBank[0].PCKSIZE.SetBits(epPacketSize(64) << usb_DEVICE_PCKSIZE_SIZE_Pos) + + // set data buffer address + usbEndpointDescriptors[ep].DeviceDescBank[0].ADDR.Set(uint32(uintptr(unsafe.Pointer(&udd_ep_out_cache_buffer[ep])))) + + // set endpoint type + setEPCFG(ep, ((usb.ENDPOINT_TYPE_BULK + 1) << sam.USB_DEVICE_EPCFG_EPTYPE0_Pos)) + + // receive interrupts when current transfer complete + setEPINTENSET(ep, sam.USB_DEVICE_EPINTENSET_TRCPT0) + + // set byte count to zero, we have not received anything yet + usbEndpointDescriptors[ep].DeviceDescBank[0].PCKSIZE.ClearBits(usb_DEVICE_PCKSIZE_BYTE_COUNT_Mask << usb_DEVICE_PCKSIZE_BYTE_COUNT_Pos) + + // ready for next transfer + setEPSTATUSCLR(ep, sam.USB_DEVICE_EPSTATUSCLR_BK0RDY) + + case usb.ENDPOINT_TYPE_INTERRUPT | usb.EndpointOut: + // set packet size + usbEndpointDescriptors[ep].DeviceDescBank[0].PCKSIZE.SetBits(epPacketSize(64) << usb_DEVICE_PCKSIZE_SIZE_Pos) + + // set data buffer address + usbEndpointDescriptors[ep].DeviceDescBank[0].ADDR.Set(uint32(uintptr(unsafe.Pointer(&udd_ep_out_cache_buffer[ep])))) + + // set endpoint type + setEPCFG(ep, ((usb.ENDPOINT_TYPE_INTERRUPT + 1) << sam.USB_DEVICE_EPCFG_EPTYPE0_Pos)) + + // receive interrupts when current transfer complete + setEPINTENSET(ep, sam.USB_DEVICE_EPINTENSET_TRCPT0) + + // set byte count to zero, we have not received anything yet + usbEndpointDescriptors[ep].DeviceDescBank[0].PCKSIZE.ClearBits(usb_DEVICE_PCKSIZE_BYTE_COUNT_Mask << usb_DEVICE_PCKSIZE_BYTE_COUNT_Pos) + + // ready for next transfer + setEPSTATUSCLR(ep, sam.USB_DEVICE_EPSTATUSCLR_BK0RDY) + + case usb.ENDPOINT_TYPE_BULK | usb.EndpointIn: + // set packet size + usbEndpointDescriptors[ep].DeviceDescBank[1].PCKSIZE.SetBits(epPacketSize(64) << usb_DEVICE_PCKSIZE_SIZE_Pos) + + // set data buffer address + usbEndpointDescriptors[ep].DeviceDescBank[1].ADDR.Set(uint32(uintptr(unsafe.Pointer(&udd_ep_in_cache_buffer[ep])))) + + // set endpoint type + setEPCFG(ep, ((usb.ENDPOINT_TYPE_BULK + 1) << sam.USB_DEVICE_EPCFG_EPTYPE1_Pos)) + + // NAK on endpoint IN, the bank is not yet filled in. + setEPSTATUSCLR(ep, sam.USB_DEVICE_EPSTATUSCLR_BK1RDY) + + setEPINTENSET(ep, sam.USB_DEVICE_EPINTENSET_TRCPT1) + + case usb.ENDPOINT_TYPE_CONTROL: + // Control OUT + // set packet size + usbEndpointDescriptors[ep].DeviceDescBank[0].PCKSIZE.SetBits(epPacketSize(64) << usb_DEVICE_PCKSIZE_SIZE_Pos) + + // set data buffer address + usbEndpointDescriptors[ep].DeviceDescBank[0].ADDR.Set(uint32(uintptr(unsafe.Pointer(&udd_ep_out_cache_buffer[ep])))) + + // set endpoint type + setEPCFG(ep, getEPCFG(ep)|((usb.ENDPOINT_TYPE_CONTROL+1)<> + usb_DEVICE_PCKSIZE_BYTE_COUNT_Pos) & usb_DEVICE_PCKSIZE_BYTE_COUNT_Mask) + + if bytesread != cdcLineInfoSize { + return b, ErrUSBBytesRead + } + + copy(b[:7], udd_ep_out_cache_buffer[0][:7]) + + return b, nil +} + +func handleEndpointRx(ep uint32) []byte { + // get data + count := int((usbEndpointDescriptors[ep].DeviceDescBank[0].PCKSIZE.Get() >> + usb_DEVICE_PCKSIZE_BYTE_COUNT_Pos) & usb_DEVICE_PCKSIZE_BYTE_COUNT_Mask) + + return udd_ep_out_cache_buffer[ep][:count] +} + +// AckUsbOutTransfer is called to acknowledge the completion of a USB OUT transfer. +func AckUsbOutTransfer(ep uint32) { + // set byte count to zero + usbEndpointDescriptors[ep].DeviceDescBank[0].PCKSIZE.ClearBits(usb_DEVICE_PCKSIZE_BYTE_COUNT_Mask << usb_DEVICE_PCKSIZE_BYTE_COUNT_Pos) + + // set multi packet size to 64 + usbEndpointDescriptors[ep].DeviceDescBank[0].PCKSIZE.SetBits(64 << usb_DEVICE_PCKSIZE_MULTI_PACKET_SIZE_Pos) + + // set ready for next data + setEPSTATUSCLR(ep, sam.USB_DEVICE_EPSTATUSCLR_BK0RDY) + +} + +func SendZlp() { + usbEndpointDescriptors[0].DeviceDescBank[1].PCKSIZE.ClearBits(usb_DEVICE_PCKSIZE_BYTE_COUNT_Mask << usb_DEVICE_PCKSIZE_BYTE_COUNT_Pos) +} + +func epPacketSize(size uint16) uint32 { + switch size { + case 8: + return 0 + case 16: + return 1 + case 32: + return 2 + case 64: + return 3 + case 128: + return 4 + case 256: + return 5 + case 512: + return 6 + case 1023: + return 7 + default: + return 0 + } +} + +func getEPCFG(ep uint32) uint8 { + switch ep { + case 0: + return sam.USB_DEVICE.EPCFG0.Get() + case 1: + return sam.USB_DEVICE.EPCFG1.Get() + case 2: + return sam.USB_DEVICE.EPCFG2.Get() + case 3: + return sam.USB_DEVICE.EPCFG3.Get() + case 4: + return sam.USB_DEVICE.EPCFG4.Get() + case 5: + return sam.USB_DEVICE.EPCFG5.Get() + case 6: + return sam.USB_DEVICE.EPCFG6.Get() + case 7: + return sam.USB_DEVICE.EPCFG7.Get() + default: + return 0 + } +} + +func setEPCFG(ep uint32, val uint8) { + switch ep { + case 0: + sam.USB_DEVICE.EPCFG0.Set(val) + case 1: + sam.USB_DEVICE.EPCFG1.Set(val) + case 2: + sam.USB_DEVICE.EPCFG2.Set(val) + case 3: + sam.USB_DEVICE.EPCFG3.Set(val) + case 4: + sam.USB_DEVICE.EPCFG4.Set(val) + case 5: + sam.USB_DEVICE.EPCFG5.Set(val) + case 6: + sam.USB_DEVICE.EPCFG6.Set(val) + case 7: + sam.USB_DEVICE.EPCFG7.Set(val) + default: + return + } +} + +func setEPSTATUSCLR(ep uint32, val uint8) { + switch ep { + case 0: + sam.USB_DEVICE.EPSTATUSCLR0.Set(val) + case 1: + sam.USB_DEVICE.EPSTATUSCLR1.Set(val) + case 2: + sam.USB_DEVICE.EPSTATUSCLR2.Set(val) + case 3: + sam.USB_DEVICE.EPSTATUSCLR3.Set(val) + case 4: + sam.USB_DEVICE.EPSTATUSCLR4.Set(val) + case 5: + sam.USB_DEVICE.EPSTATUSCLR5.Set(val) + case 6: + sam.USB_DEVICE.EPSTATUSCLR6.Set(val) + case 7: + sam.USB_DEVICE.EPSTATUSCLR7.Set(val) + default: + return + } +} + +func setEPSTATUSSET(ep uint32, val uint8) { + switch ep { + case 0: + sam.USB_DEVICE.EPSTATUSSET0.Set(val) + case 1: + sam.USB_DEVICE.EPSTATUSSET1.Set(val) + case 2: + sam.USB_DEVICE.EPSTATUSSET2.Set(val) + case 3: + sam.USB_DEVICE.EPSTATUSSET3.Set(val) + case 4: + sam.USB_DEVICE.EPSTATUSSET4.Set(val) + case 5: + sam.USB_DEVICE.EPSTATUSSET5.Set(val) + case 6: + sam.USB_DEVICE.EPSTATUSSET6.Set(val) + case 7: + sam.USB_DEVICE.EPSTATUSSET7.Set(val) + default: + return + } +} + +func getEPSTATUS(ep uint32) uint8 { + switch ep { + case 0: + return sam.USB_DEVICE.EPSTATUS0.Get() + case 1: + return sam.USB_DEVICE.EPSTATUS1.Get() + case 2: + return sam.USB_DEVICE.EPSTATUS2.Get() + case 3: + return sam.USB_DEVICE.EPSTATUS3.Get() + case 4: + return sam.USB_DEVICE.EPSTATUS4.Get() + case 5: + return sam.USB_DEVICE.EPSTATUS5.Get() + case 6: + return sam.USB_DEVICE.EPSTATUS6.Get() + case 7: + return sam.USB_DEVICE.EPSTATUS7.Get() + default: + return 0 + } +} + +func getEPINTFLAG(ep uint32) uint8 { + switch ep { + case 0: + return sam.USB_DEVICE.EPINTFLAG0.Get() + case 1: + return sam.USB_DEVICE.EPINTFLAG1.Get() + case 2: + return sam.USB_DEVICE.EPINTFLAG2.Get() + case 3: + return sam.USB_DEVICE.EPINTFLAG3.Get() + case 4: + return sam.USB_DEVICE.EPINTFLAG4.Get() + case 5: + return sam.USB_DEVICE.EPINTFLAG5.Get() + case 6: + return sam.USB_DEVICE.EPINTFLAG6.Get() + case 7: + return sam.USB_DEVICE.EPINTFLAG7.Get() + default: + return 0 + } +} + +func setEPINTFLAG(ep uint32, val uint8) { + switch ep { + case 0: + sam.USB_DEVICE.EPINTFLAG0.Set(val) + case 1: + sam.USB_DEVICE.EPINTFLAG1.Set(val) + case 2: + sam.USB_DEVICE.EPINTFLAG2.Set(val) + case 3: + sam.USB_DEVICE.EPINTFLAG3.Set(val) + case 4: + sam.USB_DEVICE.EPINTFLAG4.Set(val) + case 5: + sam.USB_DEVICE.EPINTFLAG5.Set(val) + case 6: + sam.USB_DEVICE.EPINTFLAG6.Set(val) + case 7: + sam.USB_DEVICE.EPINTFLAG7.Set(val) + default: + return + } +} + +func setEPINTENCLR(ep uint32, val uint8) { + switch ep { + case 0: + sam.USB_DEVICE.EPINTENCLR0.Set(val) + case 1: + sam.USB_DEVICE.EPINTENCLR1.Set(val) + case 2: + sam.USB_DEVICE.EPINTENCLR2.Set(val) + case 3: + sam.USB_DEVICE.EPINTENCLR3.Set(val) + case 4: + sam.USB_DEVICE.EPINTENCLR4.Set(val) + case 5: + sam.USB_DEVICE.EPINTENCLR5.Set(val) + case 6: + sam.USB_DEVICE.EPINTENCLR6.Set(val) + case 7: + sam.USB_DEVICE.EPINTENCLR7.Set(val) + default: + return + } +} + +func setEPINTENSET(ep uint32, val uint8) { + switch ep { + case 0: + sam.USB_DEVICE.EPINTENSET0.Set(val) + case 1: + sam.USB_DEVICE.EPINTENSET1.Set(val) + case 2: + sam.USB_DEVICE.EPINTENSET2.Set(val) + case 3: + sam.USB_DEVICE.EPINTENSET3.Set(val) + case 4: + sam.USB_DEVICE.EPINTENSET4.Set(val) + case 5: + sam.USB_DEVICE.EPINTENSET5.Set(val) + case 6: + sam.USB_DEVICE.EPINTENSET6.Set(val) + case 7: + sam.USB_DEVICE.EPINTENSET7.Set(val) + default: + return + } +} diff --git a/emb/machine/machine_atsamd21e18.go b/emb/machine/machine_atsamd21e18.go new file mode 100644 index 00000000..85d6853b --- /dev/null +++ b/emb/machine/machine_atsamd21e18.go @@ -0,0 +1,359 @@ +//go:build sam && atsamd21 && atsamd21e18 + +// Peripheral abstraction layer for the atsamd21. +// +// Datasheet: +// http://ww1.microchip.com/downloads/en/DeviceDoc/SAMD21-Family-DataSheet-DS40001882D.pdf +package machine + +import ( + "device/sam" + "runtime/interrupt" +) + +var ( + sercomUSART0 = UART{Buffer: NewRingBuffer(), Bus: sam.SERCOM0_USART, SERCOM: 0} + sercomUSART1 = UART{Buffer: NewRingBuffer(), Bus: sam.SERCOM1_USART, SERCOM: 1} + sercomUSART2 = UART{Buffer: NewRingBuffer(), Bus: sam.SERCOM2_USART, SERCOM: 2} + sercomUSART3 = UART{Buffer: NewRingBuffer(), Bus: sam.SERCOM3_USART, SERCOM: 3} + + sercomI2CM0 = &I2C{Bus: sam.SERCOM0_I2CM, SERCOM: 0} + sercomI2CM1 = &I2C{Bus: sam.SERCOM1_I2CM, SERCOM: 1} + sercomI2CM2 = &I2C{Bus: sam.SERCOM2_I2CM, SERCOM: 2} + sercomI2CM3 = &I2C{Bus: sam.SERCOM3_I2CM, SERCOM: 3} + + sercomSPIM0 = &SPI{Bus: sam.SERCOM0_SPI, SERCOM: 0} + sercomSPIM1 = &SPI{Bus: sam.SERCOM1_SPI, SERCOM: 1} + sercomSPIM2 = &SPI{Bus: sam.SERCOM2_SPI, SERCOM: 2} + sercomSPIM3 = &SPI{Bus: sam.SERCOM3_SPI, SERCOM: 3} +) + +func init() { + sercomUSART0.Interrupt = interrupt.New(sam.IRQ_SERCOM0, sercomUSART0.handleInterrupt) + sercomUSART1.Interrupt = interrupt.New(sam.IRQ_SERCOM1, sercomUSART1.handleInterrupt) + sercomUSART2.Interrupt = interrupt.New(sam.IRQ_SERCOM2, sercomUSART2.handleInterrupt) + sercomUSART3.Interrupt = interrupt.New(sam.IRQ_SERCOM3, sercomUSART3.handleInterrupt) +} + +// Return the register and mask to enable a given GPIO pin. This can be used to +// implement bit-banged drivers. +func (p Pin) PortMaskSet() (*uint32, uint32) { + return &sam.PORT.OUTSET0.Reg, 1 << uint8(p) +} + +// Return the register and mask to disable a given port. This can be used to +// implement bit-banged drivers. +func (p Pin) PortMaskClear() (*uint32, uint32) { + return &sam.PORT.OUTCLR0.Reg, 1 << uint8(p) +} + +// Set the pin to high or low. +// Warning: only use this on an output pin! +func (p Pin) Set(high bool) { + if high { + sam.PORT.OUTSET0.Set(1 << uint8(p)) + } else { + sam.PORT.OUTCLR0.Set(1 << uint8(p)) + } +} + +// Get returns the current value of a GPIO pin when configured as an input or as +// an output. +func (p Pin) Get() bool { + return (sam.PORT.IN0.Get()>>uint8(p))&1 > 0 +} + +// Configure this pin with the given configuration. +func (p Pin) Configure(config PinConfig) { + switch config.Mode { + case PinOutput: + sam.PORT.DIRSET0.Set(1 << uint8(p)) + // output is also set to input enable so pin can read back its own value + p.setPinCfg(sam.PORT_PINCFG0_INEN) + + case PinInput: + sam.PORT.DIRCLR0.Set(1 << uint8(p)) + p.setPinCfg(sam.PORT_PINCFG0_INEN) + + case PinInputPulldown: + sam.PORT.DIRCLR0.Set(1 << uint8(p)) + sam.PORT.OUTCLR0.Set(1 << uint8(p)) + p.setPinCfg(sam.PORT_PINCFG0_INEN | sam.PORT_PINCFG0_PULLEN) + + case PinInputPullup: + sam.PORT.DIRCLR0.Set(1 << uint8(p)) + sam.PORT.OUTSET0.Set(1 << uint8(p)) + p.setPinCfg(sam.PORT_PINCFG0_INEN | sam.PORT_PINCFG0_PULLEN) + + case PinSERCOM: + if uint8(p)&1 > 0 { + // odd pin, so save the even pins + val := p.getPMux() & sam.PORT_PMUX0_PMUXE_Msk + p.setPMux(val | (uint8(PinSERCOM) << sam.PORT_PMUX0_PMUXO_Pos)) + } else { + // even pin, so save the odd pins + val := p.getPMux() & sam.PORT_PMUX0_PMUXO_Msk + p.setPMux(val | (uint8(PinSERCOM) << sam.PORT_PMUX0_PMUXE_Pos)) + } + // enable port config + p.setPinCfg(sam.PORT_PINCFG0_PMUXEN | sam.PORT_PINCFG0_DRVSTR | sam.PORT_PINCFG0_INEN) + + case PinSERCOMAlt: + if uint8(p)&1 > 0 { + // odd pin, so save the even pins + val := p.getPMux() & sam.PORT_PMUX0_PMUXE_Msk + p.setPMux(val | (uint8(PinSERCOMAlt) << sam.PORT_PMUX0_PMUXO_Pos)) + } else { + // even pin, so save the odd pins + val := p.getPMux() & sam.PORT_PMUX0_PMUXO_Msk + p.setPMux(val | (uint8(PinSERCOMAlt) << sam.PORT_PMUX0_PMUXE_Pos)) + } + // enable port config + p.setPinCfg(sam.PORT_PINCFG0_PMUXEN | sam.PORT_PINCFG0_DRVSTR) + + case PinCom: + if uint8(p)&1 > 0 { + // odd pin, so save the even pins + val := p.getPMux() & sam.PORT_PMUX0_PMUXE_Msk + p.setPMux(val | (uint8(PinCom) << sam.PORT_PMUX0_PMUXO_Pos)) + } else { + // even pin, so save the odd pins + val := p.getPMux() & sam.PORT_PMUX0_PMUXO_Msk + p.setPMux(val | (uint8(PinCom) << sam.PORT_PMUX0_PMUXE_Pos)) + } + // enable port config + p.setPinCfg(sam.PORT_PINCFG0_PMUXEN) + case PinAnalog: + if uint8(p)&1 > 0 { + // odd pin, so save the even pins + val := p.getPMux() & sam.PORT_PMUX0_PMUXE_Msk + p.setPMux(val | (uint8(PinAnalog) << sam.PORT_PMUX0_PMUXO_Pos)) + } else { + // even pin, so save the odd pins + val := p.getPMux() & sam.PORT_PMUX0_PMUXO_Msk + p.setPMux(val | (uint8(PinAnalog) << sam.PORT_PMUX0_PMUXE_Pos)) + } + // enable port config + p.setPinCfg(sam.PORT_PINCFG0_PMUXEN | sam.PORT_PINCFG0_DRVSTR) + } +} + +// getPMux returns the value for the correct PMUX register for this pin. +func (p Pin) getPMux() uint8 { + switch p >> 1 { + case 0: + return sam.PORT.PMUX0_0.Get() + case 1: + return sam.PORT.PMUX0_1.Get() + case 2: + return sam.PORT.PMUX0_2.Get() + case 3: + return sam.PORT.PMUX0_3.Get() + case 4: + return sam.PORT.PMUX0_4.Get() + case 5: + return sam.PORT.PMUX0_5.Get() + case 6: + return sam.PORT.PMUX0_6.Get() + case 7: + return sam.PORT.PMUX0_7.Get() + case 8: + return sam.PORT.PMUX0_8.Get() + case 9: + return sam.PORT.PMUX0_9.Get() + case 10: + return sam.PORT.PMUX0_10.Get() + case 11: + return sam.PORT.PMUX0_11.Get() + case 12: + return sam.PORT.PMUX0_12.Get() + case 13: + return sam.PORT.PMUX0_13.Get() + case 14: + return sam.PORT.PMUX0_14.Get() + case 15: + return sam.PORT.PMUX0_15.Get() + default: + return 0 + } +} + +// setPMux sets the value for the correct PMUX register for this pin. +func (p Pin) setPMux(val uint8) { + switch p >> 1 { + case 0: + sam.PORT.PMUX0_0.Set(val) + case 1: + sam.PORT.PMUX0_1.Set(val) + case 2: + sam.PORT.PMUX0_2.Set(val) + case 3: + sam.PORT.PMUX0_3.Set(val) + case 4: + sam.PORT.PMUX0_4.Set(val) + case 5: + sam.PORT.PMUX0_5.Set(val) + case 6: + sam.PORT.PMUX0_6.Set(val) + case 7: + sam.PORT.PMUX0_7.Set(val) + case 8: + sam.PORT.PMUX0_8.Set(val) + case 9: + sam.PORT.PMUX0_9.Set(val) + case 10: + sam.PORT.PMUX0_10.Set(val) + case 11: + sam.PORT.PMUX0_11.Set(val) + case 12: + sam.PORT.PMUX0_12.Set(val) + case 13: + sam.PORT.PMUX0_13.Set(val) + case 14: + sam.PORT.PMUX0_14.Set(val) + case 15: + sam.PORT.PMUX0_15.Set(val) + } +} + +// getPinCfg returns the value for the correct PINCFG register for this pin. +func (p Pin) getPinCfg() uint8 { + switch p { + case 0: + return sam.PORT.PINCFG0_0.Get() + case 1: + return sam.PORT.PINCFG0_1.Get() + case 2: + return sam.PORT.PINCFG0_2.Get() + case 3: + return sam.PORT.PINCFG0_3.Get() + case 4: + return sam.PORT.PINCFG0_4.Get() + case 5: + return sam.PORT.PINCFG0_5.Get() + case 6: + return sam.PORT.PINCFG0_6.Get() + case 7: + return sam.PORT.PINCFG0_7.Get() + case 8: + return sam.PORT.PINCFG0_8.Get() + case 9: + return sam.PORT.PINCFG0_9.Get() + case 10: + return sam.PORT.PINCFG0_10.Get() + case 11: + return sam.PORT.PINCFG0_11.Get() + case 12: + return sam.PORT.PINCFG0_12.Get() + case 13: + return sam.PORT.PINCFG0_13.Get() + case 14: + return sam.PORT.PINCFG0_14.Get() + case 15: + return sam.PORT.PINCFG0_15.Get() + case 16: + return sam.PORT.PINCFG0_16.Get() + case 17: + return sam.PORT.PINCFG0_17.Get() + case 18: + return sam.PORT.PINCFG0_18.Get() + case 19: + return sam.PORT.PINCFG0_19.Get() + case 20: + return sam.PORT.PINCFG0_20.Get() + case 21: + return sam.PORT.PINCFG0_21.Get() + case 22: + return sam.PORT.PINCFG0_22.Get() + case 23: + return sam.PORT.PINCFG0_23.Get() + case 24: + return sam.PORT.PINCFG0_24.Get() + case 25: + return sam.PORT.PINCFG0_25.Get() + case 26: + return sam.PORT.PINCFG0_26.Get() + case 27: + return sam.PORT.PINCFG0_27.Get() + case 28: + return sam.PORT.PINCFG0_28.Get() + case 29: + return sam.PORT.PINCFG0_29.Get() + case 30: + return sam.PORT.PINCFG0_30.Get() + case 31: + return sam.PORT.PINCFG0_31.Get() + default: + return 0 + } +} + +// setPinCfg sets the value for the correct PINCFG register for this pin. +func (p Pin) setPinCfg(val uint8) { + switch p { + case 0: + sam.PORT.PINCFG0_0.Set(val) + case 1: + sam.PORT.PINCFG0_1.Set(val) + case 2: + sam.PORT.PINCFG0_2.Set(val) + case 3: + sam.PORT.PINCFG0_3.Set(val) + case 4: + sam.PORT.PINCFG0_4.Set(val) + case 5: + sam.PORT.PINCFG0_5.Set(val) + case 6: + sam.PORT.PINCFG0_6.Set(val) + case 7: + sam.PORT.PINCFG0_7.Set(val) + case 8: + sam.PORT.PINCFG0_8.Set(val) + case 9: + sam.PORT.PINCFG0_9.Set(val) + case 10: + sam.PORT.PINCFG0_10.Set(val) + case 11: + sam.PORT.PINCFG0_11.Set(val) + case 12: + sam.PORT.PINCFG0_12.Set(val) + case 13: + sam.PORT.PINCFG0_13.Set(val) + case 14: + sam.PORT.PINCFG0_14.Set(val) + case 15: + sam.PORT.PINCFG0_15.Set(val) + case 16: + sam.PORT.PINCFG0_16.Set(val) + case 17: + sam.PORT.PINCFG0_17.Set(val) + case 18: + sam.PORT.PINCFG0_18.Set(val) + case 19: + sam.PORT.PINCFG0_19.Set(val) + case 20: + sam.PORT.PINCFG0_20.Set(val) + case 21: + sam.PORT.PINCFG0_21.Set(val) + case 22: + sam.PORT.PINCFG0_22.Set(val) + case 23: + sam.PORT.PINCFG0_23.Set(val) + case 24: + sam.PORT.PINCFG0_24.Set(val) + case 25: + sam.PORT.PINCFG0_25.Set(val) + case 26: + sam.PORT.PINCFG0_26.Set(val) + case 27: + sam.PORT.PINCFG0_27.Set(val) + case 28: + sam.PORT.PINCFG0_28.Set(val) + case 29: + sam.PORT.PINCFG0_29.Set(val) + case 30: + sam.PORT.PINCFG0_30.Set(val) + case 31: + sam.PORT.PINCFG0_31.Set(val) + } +} diff --git a/emb/machine/machine_atsamd21g18.go b/emb/machine/machine_atsamd21g18.go new file mode 100644 index 00000000..9e845cf3 --- /dev/null +++ b/emb/machine/machine_atsamd21g18.go @@ -0,0 +1,606 @@ +//go:build sam && atsamd21 && atsamd21g18 + +// Peripheral abstraction layer for the atsamd21. +// +// Datasheet: +// http://ww1.microchip.com/downloads/en/DeviceDoc/SAMD21-Family-DataSheet-DS40001882D.pdf +package machine + +import ( + "device/sam" + "runtime/interrupt" +) + +var ( + sercomUSART0 = UART{Buffer: NewRingBuffer(), Bus: sam.SERCOM0_USART, SERCOM: 0} + sercomUSART1 = UART{Buffer: NewRingBuffer(), Bus: sam.SERCOM1_USART, SERCOM: 1} + sercomUSART2 = UART{Buffer: NewRingBuffer(), Bus: sam.SERCOM2_USART, SERCOM: 2} + sercomUSART3 = UART{Buffer: NewRingBuffer(), Bus: sam.SERCOM3_USART, SERCOM: 3} + sercomUSART4 = UART{Buffer: NewRingBuffer(), Bus: sam.SERCOM4_USART, SERCOM: 4} + sercomUSART5 = UART{Buffer: NewRingBuffer(), Bus: sam.SERCOM5_USART, SERCOM: 5} + + sercomI2CM0 = &I2C{Bus: sam.SERCOM0_I2CM, SERCOM: 0} + sercomI2CM1 = &I2C{Bus: sam.SERCOM1_I2CM, SERCOM: 1} + sercomI2CM2 = &I2C{Bus: sam.SERCOM2_I2CM, SERCOM: 2} + sercomI2CM3 = &I2C{Bus: sam.SERCOM3_I2CM, SERCOM: 3} + sercomI2CM4 = &I2C{Bus: sam.SERCOM4_I2CM, SERCOM: 4} + sercomI2CM5 = &I2C{Bus: sam.SERCOM5_I2CM, SERCOM: 5} + + sercomSPIM0 = &SPI{Bus: sam.SERCOM0_SPI, SERCOM: 0} + sercomSPIM1 = &SPI{Bus: sam.SERCOM1_SPI, SERCOM: 1} + sercomSPIM2 = &SPI{Bus: sam.SERCOM2_SPI, SERCOM: 2} + sercomSPIM3 = &SPI{Bus: sam.SERCOM3_SPI, SERCOM: 3} + sercomSPIM4 = &SPI{Bus: sam.SERCOM4_SPI, SERCOM: 4} + sercomSPIM5 = &SPI{Bus: sam.SERCOM5_SPI, SERCOM: 5} +) + +func init() { + sercomUSART0.Interrupt = interrupt.New(sam.IRQ_SERCOM0, sercomUSART0.handleInterrupt) + sercomUSART1.Interrupt = interrupt.New(sam.IRQ_SERCOM1, sercomUSART1.handleInterrupt) + sercomUSART2.Interrupt = interrupt.New(sam.IRQ_SERCOM2, sercomUSART2.handleInterrupt) + sercomUSART3.Interrupt = interrupt.New(sam.IRQ_SERCOM3, sercomUSART3.handleInterrupt) + sercomUSART4.Interrupt = interrupt.New(sam.IRQ_SERCOM4, sercomUSART4.handleInterrupt) + sercomUSART5.Interrupt = interrupt.New(sam.IRQ_SERCOM5, sercomUSART5.handleInterrupt) +} + +// Return the register and mask to enable a given GPIO pin. This can be used to +// implement bit-banged drivers. +func (p Pin) PortMaskSet() (*uint32, uint32) { + // Note: using PORT_IOBUS for faster pin accesses. + // The regular PORT registers appear to take around 4 clock cycles to store, + // which is longer than the ws2812 driver expects. The IOBUS is is fast + // enough to avoid this issue. + if p < 32 { + return &sam.PORT_IOBUS.OUTSET0.Reg, 1 << uint8(p) + } else { + return &sam.PORT_IOBUS.OUTSET1.Reg, 1 << uint8(p-32) + } +} + +// Return the register and mask to disable a given port. This can be used to +// implement bit-banged drivers. +func (p Pin) PortMaskClear() (*uint32, uint32) { + if p < 32 { + return &sam.PORT_IOBUS.OUTCLR0.Reg, 1 << uint8(p) + } else { + return &sam.PORT_IOBUS.OUTCLR1.Reg, 1 << uint8(p-32) + } +} + +// Set the pin to high or low. +// Warning: only use this on an output pin! +func (p Pin) Set(high bool) { + if p < 32 { + if high { + sam.PORT.OUTSET0.Set(1 << uint8(p)) + } else { + sam.PORT.OUTCLR0.Set(1 << uint8(p)) + } + } else { + if high { + sam.PORT.OUTSET1.Set(1 << uint8(p-32)) + } else { + sam.PORT.OUTCLR1.Set(1 << uint8(p-32)) + } + } +} + +// Get returns the current value of a GPIO pin when configured as an input or as +// an output. +func (p Pin) Get() bool { + if p < 32 { + return (sam.PORT.IN0.Get()>>uint8(p))&1 > 0 + } else { + return (sam.PORT.IN1.Get()>>uint8(p-32))&1 > 0 + } +} + +// Configure this pin with the given configuration. +func (p Pin) Configure(config PinConfig) { + switch config.Mode { + case PinOutput: + if p < 32 { + sam.PORT.DIRSET0.Set(1 << uint8(p)) + // output is also set to input enable so pin can read back its own value + p.setPinCfg(sam.PORT_PINCFG0_INEN) + } else { + sam.PORT.DIRSET1.Set(1 << uint8(p-32)) + // output is also set to input enable so pin can read back its own value + p.setPinCfg(sam.PORT_PINCFG0_INEN) + } + + case PinInput: + if p < 32 { + sam.PORT.DIRCLR0.Set(1 << uint8(p)) + p.setPinCfg(sam.PORT_PINCFG0_INEN) + } else { + sam.PORT.DIRCLR1.Set(1 << uint8(p-32)) + p.setPinCfg(sam.PORT_PINCFG0_INEN) + } + + case PinInputPulldown: + if p < 32 { + sam.PORT.DIRCLR0.Set(1 << uint8(p)) + sam.PORT.OUTCLR0.Set(1 << uint8(p)) + p.setPinCfg(sam.PORT_PINCFG0_INEN | sam.PORT_PINCFG0_PULLEN) + } else { + sam.PORT.DIRCLR1.Set(1 << uint8(p-32)) + sam.PORT.OUTCLR1.Set(1 << uint8(p-32)) + p.setPinCfg(sam.PORT_PINCFG0_INEN | sam.PORT_PINCFG0_PULLEN) + } + + case PinInputPullup: + if p < 32 { + sam.PORT.DIRCLR0.Set(1 << uint8(p)) + sam.PORT.OUTSET0.Set(1 << uint8(p)) + p.setPinCfg(sam.PORT_PINCFG0_INEN | sam.PORT_PINCFG0_PULLEN) + } else { + sam.PORT.DIRCLR1.Set(1 << uint8(p-32)) + sam.PORT.OUTSET1.Set(1 << uint8(p-32)) + p.setPinCfg(sam.PORT_PINCFG0_INEN | sam.PORT_PINCFG0_PULLEN) + } + + case PinSERCOM: + if p&1 > 0 { + // odd pin, so save the even pins + val := p.getPMux() & sam.PORT_PMUX0_PMUXE_Msk + p.setPMux(val | (uint8(PinSERCOM) << sam.PORT_PMUX0_PMUXO_Pos)) + } else { + // even pin, so save the odd pins + val := p.getPMux() & sam.PORT_PMUX0_PMUXO_Msk + p.setPMux(val | (uint8(PinSERCOM) << sam.PORT_PMUX0_PMUXE_Pos)) + } + // enable port config + p.setPinCfg(sam.PORT_PINCFG0_PMUXEN | sam.PORT_PINCFG0_DRVSTR | sam.PORT_PINCFG0_INEN) + + case PinSERCOMAlt: + if p&1 > 0 { + // odd pin, so save the even pins + val := p.getPMux() & sam.PORT_PMUX0_PMUXE_Msk + p.setPMux(val | (uint8(PinSERCOMAlt) << sam.PORT_PMUX0_PMUXO_Pos)) + } else { + // even pin, so save the odd pins + val := p.getPMux() & sam.PORT_PMUX0_PMUXO_Msk + p.setPMux(val | (uint8(PinSERCOMAlt) << sam.PORT_PMUX0_PMUXE_Pos)) + } + // enable port config + p.setPinCfg(sam.PORT_PINCFG0_PMUXEN | sam.PORT_PINCFG0_DRVSTR) + + case PinCom: + if p&1 > 0 { + // odd pin, so save the even pins + val := p.getPMux() & sam.PORT_PMUX0_PMUXE_Msk + p.setPMux(val | (uint8(PinCom) << sam.PORT_PMUX0_PMUXO_Pos)) + } else { + // even pin, so save the odd pins + val := p.getPMux() & sam.PORT_PMUX0_PMUXO_Msk + p.setPMux(val | (uint8(PinCom) << sam.PORT_PMUX0_PMUXE_Pos)) + } + // enable port config + p.setPinCfg(sam.PORT_PINCFG0_PMUXEN) + case PinAnalog: + if p&1 > 0 { + // odd pin, so save the even pins + val := p.getPMux() & sam.PORT_PMUX0_PMUXE_Msk + p.setPMux(val | (uint8(PinAnalog) << sam.PORT_PMUX0_PMUXO_Pos)) + } else { + // even pin, so save the odd pins + val := p.getPMux() & sam.PORT_PMUX0_PMUXO_Msk + p.setPMux(val | (uint8(PinAnalog) << sam.PORT_PMUX0_PMUXE_Pos)) + } + // enable port config + p.setPinCfg(sam.PORT_PINCFG0_PMUXEN | sam.PORT_PINCFG0_DRVSTR) + } +} + +// getPMux returns the value for the correct PMUX register for this pin. +func (p Pin) getPMux() uint8 { + switch uint8(p) >> 1 { + case 0: + return sam.PORT.PMUX0_0.Get() + case 1: + return sam.PORT.PMUX0_1.Get() + case 2: + return sam.PORT.PMUX0_2.Get() + case 3: + return sam.PORT.PMUX0_3.Get() + case 4: + return sam.PORT.PMUX0_4.Get() + case 5: + return sam.PORT.PMUX0_5.Get() + case 6: + return sam.PORT.PMUX0_6.Get() + case 7: + return sam.PORT.PMUX0_7.Get() + case 8: + return sam.PORT.PMUX0_8.Get() + case 9: + return sam.PORT.PMUX0_9.Get() + case 10: + return sam.PORT.PMUX0_10.Get() + case 11: + return sam.PORT.PMUX0_11.Get() + case 12: + return sam.PORT.PMUX0_12.Get() + case 13: + return sam.PORT.PMUX0_13.Get() + case 14: + return sam.PORT.PMUX0_14.Get() + case 15: + return sam.PORT.PMUX0_15.Get() + case 16: + return uint8(sam.PORT.PMUX1_0.Get()>>0) & 0xff + case 17: + return uint8(sam.PORT.PMUX1_0.Get()>>8) & 0xff + case 18: + return uint8(sam.PORT.PMUX1_0.Get()>>16) & 0xff + case 19: + return uint8(sam.PORT.PMUX1_0.Get()>>24) & 0xff + case 20: + return uint8(sam.PORT.PMUX1_4.Get()>>0) & 0xff + case 21: + return uint8(sam.PORT.PMUX1_4.Get()>>8) & 0xff + case 22: + return uint8(sam.PORT.PMUX1_4.Get()>>16) & 0xff + case 23: + return uint8(sam.PORT.PMUX1_4.Get()>>24) & 0xff + case 24: + return uint8(sam.PORT.PMUX1_8.Get()>>0) & 0xff + case 25: + return uint8(sam.PORT.PMUX1_8.Get()>>8) & 0xff + case 26: + return uint8(sam.PORT.PMUX1_8.Get()>>16) & 0xff + case 27: + return uint8(sam.PORT.PMUX1_8.Get()>>24) & 0xff + case 28: + return uint8(sam.PORT.PMUX1_12.Get()>>0) & 0xff + case 29: + return uint8(sam.PORT.PMUX1_12.Get()>>8) & 0xff + case 30: + return uint8(sam.PORT.PMUX1_12.Get()>>16) & 0xff + case 31: + return uint8(sam.PORT.PMUX1_12.Get()>>24) & 0xff + default: + return 0 + } +} + +// setPMux sets the value for the correct PMUX register for this pin. +func (p Pin) setPMux(val uint8) { + switch uint8(p) >> 1 { + case 0: + sam.PORT.PMUX0_0.Set(val) + case 1: + sam.PORT.PMUX0_1.Set(val) + case 2: + sam.PORT.PMUX0_2.Set(val) + case 3: + sam.PORT.PMUX0_3.Set(val) + case 4: + sam.PORT.PMUX0_4.Set(val) + case 5: + sam.PORT.PMUX0_5.Set(val) + case 6: + sam.PORT.PMUX0_6.Set(val) + case 7: + sam.PORT.PMUX0_7.Set(val) + case 8: + sam.PORT.PMUX0_8.Set(val) + case 9: + sam.PORT.PMUX0_9.Set(val) + case 10: + sam.PORT.PMUX0_10.Set(val) + case 11: + sam.PORT.PMUX0_11.Set(val) + case 12: + sam.PORT.PMUX0_12.Set(val) + case 13: + sam.PORT.PMUX0_13.Set(val) + case 14: + sam.PORT.PMUX0_14.Set(val) + case 15: + sam.PORT.PMUX0_15.Set(val) + case 16: + sam.PORT.PMUX1_0.ReplaceBits(uint32(val), 0xff, 0) + case 17: + sam.PORT.PMUX1_0.ReplaceBits(uint32(val), 0xff, 8) + case 18: + sam.PORT.PMUX1_0.ReplaceBits(uint32(val), 0xff, 16) + case 19: + sam.PORT.PMUX1_0.ReplaceBits(uint32(val), 0xff, 24) + case 20: + sam.PORT.PMUX1_4.ReplaceBits(uint32(val), 0xff, 0) + case 21: + sam.PORT.PMUX1_4.ReplaceBits(uint32(val), 0xff, 8) + case 22: + sam.PORT.PMUX1_4.ReplaceBits(uint32(val), 0xff, 16) + case 23: + sam.PORT.PMUX1_4.ReplaceBits(uint32(val), 0xff, 24) + case 24: + sam.PORT.PMUX1_8.ReplaceBits(uint32(val), 0xff, 0) + case 25: + sam.PORT.PMUX1_8.ReplaceBits(uint32(val), 0xff, 8) + case 26: + sam.PORT.PMUX1_8.ReplaceBits(uint32(val), 0xff, 16) + case 27: + sam.PORT.PMUX1_8.ReplaceBits(uint32(val), 0xff, 24) + case 28: + sam.PORT.PMUX1_12.ReplaceBits(uint32(val), 0xff, 0) + case 29: + sam.PORT.PMUX1_12.ReplaceBits(uint32(val), 0xff, 8) + case 30: + sam.PORT.PMUX1_12.ReplaceBits(uint32(val), 0xff, 16) + case 31: + sam.PORT.PMUX1_12.ReplaceBits(uint32(val), 0xff, 24) + } +} + +// getPinCfg returns the value for the correct PINCFG register for this pin. +func (p Pin) getPinCfg() uint8 { + switch p { + case 0: + return sam.PORT.PINCFG0_0.Get() + case 1: + return sam.PORT.PINCFG0_1.Get() + case 2: + return sam.PORT.PINCFG0_2.Get() + case 3: + return sam.PORT.PINCFG0_3.Get() + case 4: + return sam.PORT.PINCFG0_4.Get() + case 5: + return sam.PORT.PINCFG0_5.Get() + case 6: + return sam.PORT.PINCFG0_6.Get() + case 7: + return sam.PORT.PINCFG0_7.Get() + case 8: + return sam.PORT.PINCFG0_8.Get() + case 9: + return sam.PORT.PINCFG0_9.Get() + case 10: + return sam.PORT.PINCFG0_10.Get() + case 11: + return sam.PORT.PINCFG0_11.Get() + case 12: + return sam.PORT.PINCFG0_12.Get() + case 13: + return sam.PORT.PINCFG0_13.Get() + case 14: + return sam.PORT.PINCFG0_14.Get() + case 15: + return sam.PORT.PINCFG0_15.Get() + case 16: + return sam.PORT.PINCFG0_16.Get() + case 17: + return sam.PORT.PINCFG0_17.Get() + case 18: + return sam.PORT.PINCFG0_18.Get() + case 19: + return sam.PORT.PINCFG0_19.Get() + case 20: + return sam.PORT.PINCFG0_20.Get() + case 21: + return sam.PORT.PINCFG0_21.Get() + case 22: + return sam.PORT.PINCFG0_22.Get() + case 23: + return sam.PORT.PINCFG0_23.Get() + case 24: + return sam.PORT.PINCFG0_24.Get() + case 25: + return sam.PORT.PINCFG0_25.Get() + case 26: + return sam.PORT.PINCFG0_26.Get() + case 27: + return sam.PORT.PINCFG0_27.Get() + case 28: + return sam.PORT.PINCFG0_28.Get() + case 29: + return sam.PORT.PINCFG0_29.Get() + case 30: + return sam.PORT.PINCFG0_30.Get() + case 31: + return sam.PORT.PINCFG0_31.Get() + case 32: // PB00 + return uint8(sam.PORT.PINCFG1_0.Get()>>0) & 0xff + case 33: // PB01 + return uint8(sam.PORT.PINCFG1_0.Get()>>8) & 0xff + case 34: // PB02 + return uint8(sam.PORT.PINCFG1_0.Get()>>16) & 0xff + case 35: // PB03 + return uint8(sam.PORT.PINCFG1_0.Get()>>24) & 0xff + case 36: // PB04 + return uint8(sam.PORT.PINCFG1_4.Get()>>0) & 0xff + case 37: // PB05 + return uint8(sam.PORT.PINCFG1_4.Get()>>8) & 0xff + case 38: // PB06 + return uint8(sam.PORT.PINCFG1_4.Get()>>16) & 0xff + case 39: // PB07 + return uint8(sam.PORT.PINCFG1_4.Get()>>24) & 0xff + case 40: // PB08 + return uint8(sam.PORT.PINCFG1_8.Get()>>0) & 0xff + case 41: // PB09 + return uint8(sam.PORT.PINCFG1_8.Get()>>8) & 0xff + case 42: // PB10 + return uint8(sam.PORT.PINCFG1_8.Get()>>16) & 0xff + case 43: // PB11 + return uint8(sam.PORT.PINCFG1_8.Get()>>24) & 0xff + case 44: // PB12 + return uint8(sam.PORT.PINCFG1_12.Get()>>0) & 0xff + case 45: // PB13 + return uint8(sam.PORT.PINCFG1_12.Get()>>8) & 0xff + case 46: // PB14 + return uint8(sam.PORT.PINCFG1_12.Get()>>16) & 0xff + case 47: // PB15 + return uint8(sam.PORT.PINCFG1_12.Get()>>24) & 0xff + case 48: // PB16 + return uint8(sam.PORT.PINCFG1_16.Get()>>0) & 0xff + case 49: // PB17 + return uint8(sam.PORT.PINCFG1_16.Get()>>8) & 0xff + case 50: // PB18 + return uint8(sam.PORT.PINCFG1_16.Get()>>16) & 0xff + case 51: // PB19 + return uint8(sam.PORT.PINCFG1_16.Get()>>24) & 0xff + case 52: // PB20 + return uint8(sam.PORT.PINCFG1_20.Get()>>0) & 0xff + case 53: // PB21 + return uint8(sam.PORT.PINCFG1_20.Get()>>8) & 0xff + case 54: // PB22 + return uint8(sam.PORT.PINCFG1_20.Get()>>16) & 0xff + case 55: // PB23 + return uint8(sam.PORT.PINCFG1_20.Get()>>24) & 0xff + case 56: // PB24 + return uint8(sam.PORT.PINCFG1_24.Get()>>0) & 0xff + case 57: // PB25 + return uint8(sam.PORT.PINCFG1_24.Get()>>8) & 0xff + case 58: // PB26 + return uint8(sam.PORT.PINCFG1_24.Get()>>16) & 0xff + case 59: // PB27 + return uint8(sam.PORT.PINCFG1_24.Get()>>24) & 0xff + case 60: // PB28 + return uint8(sam.PORT.PINCFG1_28.Get()>>0) & 0xff + case 61: // PB29 + return uint8(sam.PORT.PINCFG1_28.Get()>>8) & 0xff + case 62: // PB30 + return uint8(sam.PORT.PINCFG1_28.Get()>>16) & 0xff + case 63: // PB31 + return uint8(sam.PORT.PINCFG1_28.Get()>>24) & 0xff + default: + return 0 + } +} + +// setPinCfg sets the value for the correct PINCFG register for this pin. +func (p Pin) setPinCfg(val uint8) { + switch p { + case 0: + sam.PORT.PINCFG0_0.Set(val) + case 1: + sam.PORT.PINCFG0_1.Set(val) + case 2: + sam.PORT.PINCFG0_2.Set(val) + case 3: + sam.PORT.PINCFG0_3.Set(val) + case 4: + sam.PORT.PINCFG0_4.Set(val) + case 5: + sam.PORT.PINCFG0_5.Set(val) + case 6: + sam.PORT.PINCFG0_6.Set(val) + case 7: + sam.PORT.PINCFG0_7.Set(val) + case 8: + sam.PORT.PINCFG0_8.Set(val) + case 9: + sam.PORT.PINCFG0_9.Set(val) + case 10: + sam.PORT.PINCFG0_10.Set(val) + case 11: + sam.PORT.PINCFG0_11.Set(val) + case 12: + sam.PORT.PINCFG0_12.Set(val) + case 13: + sam.PORT.PINCFG0_13.Set(val) + case 14: + sam.PORT.PINCFG0_14.Set(val) + case 15: + sam.PORT.PINCFG0_15.Set(val) + case 16: + sam.PORT.PINCFG0_16.Set(val) + case 17: + sam.PORT.PINCFG0_17.Set(val) + case 18: + sam.PORT.PINCFG0_18.Set(val) + case 19: + sam.PORT.PINCFG0_19.Set(val) + case 20: + sam.PORT.PINCFG0_20.Set(val) + case 21: + sam.PORT.PINCFG0_21.Set(val) + case 22: + sam.PORT.PINCFG0_22.Set(val) + case 23: + sam.PORT.PINCFG0_23.Set(val) + case 24: + sam.PORT.PINCFG0_24.Set(val) + case 25: + sam.PORT.PINCFG0_25.Set(val) + case 26: + sam.PORT.PINCFG0_26.Set(val) + case 27: + sam.PORT.PINCFG0_27.Set(val) + case 28: + sam.PORT.PINCFG0_28.Set(val) + case 29: + sam.PORT.PINCFG0_29.Set(val) + case 30: + sam.PORT.PINCFG0_30.Set(val) + case 31: + sam.PORT.PINCFG0_31.Set(val) + case 32: // PB00 + sam.PORT.PINCFG1_0.ReplaceBits(uint32(val), 0xff, 0) + case 33: // PB01 + sam.PORT.PINCFG1_0.ReplaceBits(uint32(val), 0xff, 8) + case 34: // PB02 + sam.PORT.PINCFG1_0.ReplaceBits(uint32(val), 0xff, 16) + case 35: // PB03 + sam.PORT.PINCFG1_0.ReplaceBits(uint32(val), 0xff, 24) + case 36: // PB04 + sam.PORT.PINCFG1_4.ReplaceBits(uint32(val), 0xff, 0) + case 37: // PB05 + sam.PORT.PINCFG1_4.ReplaceBits(uint32(val), 0xff, 8) + case 38: // PB06 + sam.PORT.PINCFG1_4.ReplaceBits(uint32(val), 0xff, 16) + case 39: // PB07 + sam.PORT.PINCFG1_4.ReplaceBits(uint32(val), 0xff, 24) + case 40: // PB08 + sam.PORT.PINCFG1_8.ReplaceBits(uint32(val), 0xff, 0) + case 41: // PB09 + sam.PORT.PINCFG1_8.ReplaceBits(uint32(val), 0xff, 8) + case 42: // PB10 + sam.PORT.PINCFG1_8.ReplaceBits(uint32(val), 0xff, 16) + case 43: // PB11 + sam.PORT.PINCFG1_8.ReplaceBits(uint32(val), 0xff, 24) + case 44: // PB12 + sam.PORT.PINCFG1_12.ReplaceBits(uint32(val), 0xff, 0) + case 45: // PB13 + sam.PORT.PINCFG1_12.ReplaceBits(uint32(val), 0xff, 8) + case 46: // PB14 + sam.PORT.PINCFG1_12.ReplaceBits(uint32(val), 0xff, 16) + case 47: // PB15 + sam.PORT.PINCFG1_12.ReplaceBits(uint32(val), 0xff, 24) + case 48: // PB16 + sam.PORT.PINCFG1_16.ReplaceBits(uint32(val), 0xff, 0) + case 49: // PB17 + sam.PORT.PINCFG1_16.ReplaceBits(uint32(val), 0xff, 8) + case 50: // PB18 + sam.PORT.PINCFG1_16.ReplaceBits(uint32(val), 0xff, 16) + case 51: // PB19 + sam.PORT.PINCFG1_16.ReplaceBits(uint32(val), 0xff, 24) + case 52: // PB20 + sam.PORT.PINCFG1_20.ReplaceBits(uint32(val), 0xff, 0) + case 53: // PB21 + sam.PORT.PINCFG1_20.ReplaceBits(uint32(val), 0xff, 8) + case 54: // PB22 + sam.PORT.PINCFG1_20.ReplaceBits(uint32(val), 0xff, 16) + case 55: // PB23 + sam.PORT.PINCFG1_20.ReplaceBits(uint32(val), 0xff, 24) + case 56: // PB24 + sam.PORT.PINCFG1_24.ReplaceBits(uint32(val), 0xff, 0) + case 57: // PB25 + sam.PORT.PINCFG1_24.ReplaceBits(uint32(val), 0xff, 8) + case 58: // PB26 + sam.PORT.PINCFG1_24.ReplaceBits(uint32(val), 0xff, 16) + case 59: // PB27 + sam.PORT.PINCFG1_24.ReplaceBits(uint32(val), 0xff, 24) + case 60: // PB28 + sam.PORT.PINCFG1_28.ReplaceBits(uint32(val), 0xff, 0) + case 61: // PB29 + sam.PORT.PINCFG1_28.ReplaceBits(uint32(val), 0xff, 8) + case 62: // PB30 + sam.PORT.PINCFG1_28.ReplaceBits(uint32(val), 0xff, 16) + case 63: // PB31 + sam.PORT.PINCFG1_28.ReplaceBits(uint32(val), 0xff, 24) + } +} diff --git a/emb/machine/machine_atsamd51.go b/emb/machine/machine_atsamd51.go new file mode 100644 index 00000000..576f4545 --- /dev/null +++ b/emb/machine/machine_atsamd51.go @@ -0,0 +1,2358 @@ +//go:build (sam && atsamd51) || (sam && atsame5x) + +// Peripheral abstraction layer for the atsamd51. +// +// Datasheet: +// http://ww1.microchip.com/downloads/en/DeviceDoc/60001507C.pdf +package machine + +import ( + "device/arm" + "device/sam" + "errors" + "internal/binary" + "runtime/interrupt" + "unsafe" +) + +const deviceName = sam.Device + +// DS60001507, Section 9.6: Serial Number +var deviceIDAddr = []uintptr{0x008061FC, 0x00806010, 0x00806014, 0x00806018} + +func CPUFrequency() uint32 { + return 120000000 +} + +const ( + PinAnalog PinMode = 1 + PinSERCOM PinMode = 2 + PinSERCOMAlt PinMode = 3 + PinTimer PinMode = 4 + PinTimerAlt PinMode = 5 + PinTCCPDEC PinMode = 6 + PinCom PinMode = 7 + PinSDHC PinMode = 8 + PinI2S PinMode = 9 + PinPCC PinMode = 10 + PinGMAC PinMode = 11 + PinACCLK PinMode = 12 + PinCCL PinMode = 13 + PinDigital PinMode = 14 + PinInput PinMode = 15 + PinInputPullup PinMode = 16 + PinOutput PinMode = 17 + PinTCCE PinMode = PinTimer + PinTCCF PinMode = PinTimerAlt + PinTCCG PinMode = PinTCCPDEC + PinInputPulldown PinMode = 18 + PinCAN PinMode = 19 + PinCAN0 PinMode = PinSDHC + PinCAN1 PinMode = PinCom +) + +type PinChange uint8 + +// Pin change interrupt constants for SetInterrupt. +const ( + PinRising PinChange = sam.EIC_CONFIG_SENSE0_RISE + PinFalling PinChange = sam.EIC_CONFIG_SENSE0_FALL + PinToggle PinChange = sam.EIC_CONFIG_SENSE0_BOTH +) + +// Callbacks to be called for pins configured with SetInterrupt. Unfortunately, +// we also need to keep track of which interrupt channel is used by which pin, +// as the only alternative would be iterating through all pins. +// +// We're using the magic constant 16 here because the SAM D21 has 16 interrupt +// channels configurable for pins. +var ( + interruptPins [16]Pin // warning: the value is invalid when pinCallbacks[i] is not set! + pinCallbacks [16]func(Pin) +) + +// Hardware pins +const ( + PA00 Pin = 0 + PA01 Pin = 1 + PA02 Pin = 2 + PA03 Pin = 3 + PA04 Pin = 4 + PA05 Pin = 5 + PA06 Pin = 6 + PA07 Pin = 7 + PA08 Pin = 8 // peripherals: TCC0 channel 0, TCC1 channel 4 + PA09 Pin = 9 // peripherals: TCC0 channel 1, TCC1 channel 5 + PA10 Pin = 10 // peripherals: TCC0 channel 2, TCC1 channel 6 + PA11 Pin = 11 // peripherals: TCC0 channel 3, TCC1 channel 7 + PA12 Pin = 12 // peripherals: TCC0 channel 6, TCC1 channel 2 + PA13 Pin = 13 // peripherals: TCC0 channel 7, TCC1 channel 3 + PA14 Pin = 14 // peripherals: TCC2 channel 0, TCC1 channel 2 + PA15 Pin = 15 // peripherals: TCC2 channel 1, TCC1 channel 3 + PA16 Pin = 16 // peripherals: TCC1 channel 0, TCC0 channel 4 + PA17 Pin = 17 // peripherals: TCC1 channel 1, TCC0 channel 5 + PA18 Pin = 18 // peripherals: TCC1 channel 2, TCC0 channel 6 + PA19 Pin = 19 // peripherals: TCC1 channel 3, TCC0 channel 7 + PA20 Pin = 20 // peripherals: TCC1 channel 4, TCC0 channel 0 + PA21 Pin = 21 // peripherals: TCC1 channel 5, TCC0 channel 1 + PA22 Pin = 22 // peripherals: TCC1 channel 6, TCC0 channel 2 + PA23 Pin = 23 // peripherals: TCC1 channel 7, TCC0 channel 3 + PA24 Pin = 24 // peripherals: TCC2 channel 2 + PA25 Pin = 25 // peripherals: TCC2 channel 3 + PA26 Pin = 26 + PA27 Pin = 27 + PA28 Pin = 28 + PA29 Pin = 29 + PA30 Pin = 30 // peripherals: TCC2 channel 0 + PA31 Pin = 31 // peripherals: TCC2 channel 1 + PB00 Pin = 32 + PB01 Pin = 33 + PB02 Pin = 34 // peripherals: TCC2 channel 2 + PB03 Pin = 35 // peripherals: TCC2 channel 3 + PB04 Pin = 36 + PB05 Pin = 37 + PB06 Pin = 38 + PB07 Pin = 39 + PB08 Pin = 40 + PB09 Pin = 41 + PB10 Pin = 42 // peripherals: TCC0 channel 4, TCC1 channel 0 + PB11 Pin = 43 // peripherals: TCC0 channel 5, TCC1 channel 1 + PB12 Pin = 44 // peripherals: TCC3 channel 0, TCC0 channel 0 + PB13 Pin = 45 // peripherals: TCC3 channel 1, TCC0 channel 1 + PB14 Pin = 46 // peripherals: TCC4 channel 0, TCC0 channel 2 + PB15 Pin = 47 // peripherals: TCC4 channel 1, TCC0 channel 3 + PB16 Pin = 48 // peripherals: TCC3 channel 0, TCC0 channel 4 + PB17 Pin = 49 // peripherals: TCC3 channel 1, TCC0 channel 5 + PB18 Pin = 50 // peripherals: TCC1 channel 0 + PB19 Pin = 51 // peripherals: TCC1 channel 1 + PB20 Pin = 52 // peripherals: TCC1 channel 2 + PB21 Pin = 53 // peripherals: TCC1 channel 3 + PB22 Pin = 54 + PB23 Pin = 55 + PB24 Pin = 56 + PB25 Pin = 57 + PB26 Pin = 58 // peripherals: TCC1 channel 2 + PB27 Pin = 59 // peripherals: TCC1 channel 3 + PB28 Pin = 60 // peripherals: TCC1 channel 4 + PB29 Pin = 61 // peripherals: TCC1 channel 5 + PB30 Pin = 62 // peripherals: TCC4 channel 0, TCC0 channel 6 + PB31 Pin = 63 // peripherals: TCC4 channel 1, TCC0 channel 7 + PC00 Pin = 64 + PC01 Pin = 65 + PC02 Pin = 66 + PC03 Pin = 67 + PC04 Pin = 68 // peripherals: TCC0 channel 0 + PC05 Pin = 69 // peripherals: TCC0 channel 1 + PC06 Pin = 70 + PC07 Pin = 71 + PC08 Pin = 72 + PC09 Pin = 73 + PC10 Pin = 74 // peripherals: TCC0 channel 0, TCC1 channel 4 + PC11 Pin = 75 // peripherals: TCC0 channel 1, TCC1 channel 5 + PC12 Pin = 76 // peripherals: TCC0 channel 2, TCC1 channel 6 + PC13 Pin = 77 // peripherals: TCC0 channel 3, TCC1 channel 7 + PC14 Pin = 78 // peripherals: TCC0 channel 4, TCC1 channel 0 + PC15 Pin = 79 // peripherals: TCC0 channel 5, TCC1 channel 1 + PC16 Pin = 80 // peripherals: TCC0 channel 0 + PC17 Pin = 81 // peripherals: TCC0 channel 1 + PC18 Pin = 82 // peripherals: TCC0 channel 2 + PC19 Pin = 83 // peripherals: TCC0 channel 3 + PC20 Pin = 84 // peripherals: TCC0 channel 4 + PC21 Pin = 85 // peripherals: TCC0 channel 5 + PC22 Pin = 86 // peripherals: TCC0 channel 6 + PC23 Pin = 87 // peripherals: TCC0 channel 7 + PC24 Pin = 88 + PC25 Pin = 89 + PC26 Pin = 90 + PC27 Pin = 91 + PC28 Pin = 92 + PC29 Pin = 93 + PC30 Pin = 94 + PC31 Pin = 95 + PD00 Pin = 96 + PD01 Pin = 97 + PD02 Pin = 98 + PD03 Pin = 99 + PD04 Pin = 100 + PD05 Pin = 101 + PD06 Pin = 102 + PD07 Pin = 103 + PD08 Pin = 104 // peripherals: TCC0 channel 1 + PD09 Pin = 105 // peripherals: TCC0 channel 2 + PD10 Pin = 106 // peripherals: TCC0 channel 3 + PD11 Pin = 107 // peripherals: TCC0 channel 4 + PD12 Pin = 108 // peripherals: TCC0 channel 5 + PD13 Pin = 109 // peripherals: TCC0 channel 6 + PD14 Pin = 110 + PD15 Pin = 111 + PD16 Pin = 112 + PD17 Pin = 113 + PD18 Pin = 114 + PD19 Pin = 115 + PD20 Pin = 116 // peripherals: TCC1 channel 0 + PD21 Pin = 117 // peripherals: TCC1 channel 1 + PD22 Pin = 118 + PD23 Pin = 119 + PD24 Pin = 120 + PD25 Pin = 121 + PD26 Pin = 122 + PD27 Pin = 123 + PD28 Pin = 124 + PD29 Pin = 125 + PD30 Pin = 126 + PD31 Pin = 127 +) + +const ( + pinPadMapSERCOM0Pad0 uint16 = 0x1000 + pinPadMapSERCOM1Pad0 uint16 = 0x2000 + pinPadMapSERCOM2Pad0 uint16 = 0x3000 + pinPadMapSERCOM3Pad0 uint16 = 0x4000 + pinPadMapSERCOM4Pad0 uint16 = 0x5000 + pinPadMapSERCOM5Pad0 uint16 = 0x6000 + pinPadMapSERCOM6Pad0 uint16 = 0x7000 + pinPadMapSERCOM7Pad0 uint16 = 0x8000 + pinPadMapSERCOM0Pad2 uint16 = 0x1200 + pinPadMapSERCOM1Pad2 uint16 = 0x2200 + pinPadMapSERCOM2Pad2 uint16 = 0x3200 + pinPadMapSERCOM3Pad2 uint16 = 0x4200 + pinPadMapSERCOM4Pad2 uint16 = 0x5200 + pinPadMapSERCOM5Pad2 uint16 = 0x6200 + pinPadMapSERCOM6Pad2 uint16 = 0x7200 + pinPadMapSERCOM7Pad2 uint16 = 0x8200 + + pinPadMapSERCOM0AltPad0 uint16 = 0x0010 + pinPadMapSERCOM1AltPad0 uint16 = 0x0020 + pinPadMapSERCOM2AltPad0 uint16 = 0x0030 + pinPadMapSERCOM3AltPad0 uint16 = 0x0040 + pinPadMapSERCOM4AltPad0 uint16 = 0x0050 + pinPadMapSERCOM5AltPad0 uint16 = 0x0060 + pinPadMapSERCOM6AltPad0 uint16 = 0x0070 + pinPadMapSERCOM7AltPad0 uint16 = 0x0080 + pinPadMapSERCOM0AltPad1 uint16 = 0x0011 + pinPadMapSERCOM1AltPad1 uint16 = 0x0021 + pinPadMapSERCOM2AltPad1 uint16 = 0x0031 + pinPadMapSERCOM3AltPad1 uint16 = 0x0041 + pinPadMapSERCOM4AltPad1 uint16 = 0x0051 + pinPadMapSERCOM5AltPad1 uint16 = 0x0061 + pinPadMapSERCOM6AltPad1 uint16 = 0x0071 + pinPadMapSERCOM7AltPad1 uint16 = 0x0081 + pinPadMapSERCOM0AltPad2 uint16 = 0x0012 + pinPadMapSERCOM1AltPad2 uint16 = 0x0022 + pinPadMapSERCOM2AltPad2 uint16 = 0x0032 + pinPadMapSERCOM3AltPad2 uint16 = 0x0042 + pinPadMapSERCOM4AltPad2 uint16 = 0x0052 + pinPadMapSERCOM5AltPad2 uint16 = 0x0062 + pinPadMapSERCOM6AltPad2 uint16 = 0x0072 + pinPadMapSERCOM7AltPad2 uint16 = 0x0082 +) + +// pinPadMapping lists which pins have which SERCOMs attached to them. +// The encoding is rather dense, with each uint16 encoding two pins and both +// SERCOM and SERCOM-ALT. +// +// Observations: +// - There are eight SERCOMs. Those SERCOM numbers can be encoded in 4 bits. +// - Even pad numbers are usually on even pins, and odd pad numbers are usually +// on odd pins. The exception is SERCOM-ALT, which sometimes swaps pad 0 and 1. +// With that, there is still an invariant that the pad number for an odd pin is +// the pad number for the corresponding even pin with the low bit toggled. +// - Pin pads come in pairs. If PA00 has pad 0, then PA01 has pad 1. +// +// With this information, we can encode SERCOM pin/pad numbers much more +// efficiently. Due to pads coming in pairs, we can ignore half the pins: the +// information for an odd pin can be calculated easily from the preceding even +// pin. +// +// Each word below is split in two bytes. The 8 high bytes are for SERCOM and +// the 8 low bits are for SERCOM-ALT. Of each byte, the 4 high bits encode the +// SERCOM + 1 while the two low bits encodes the pad number (the pad number for +// the odd pin can be trivially calculated by toggling the low bit of the pad +// number). It encodes SERCOM + 1 instead of just the SERCOM number, to make it +// easy to check whether a nibble is set at all. +// +// Datasheet: http://ww1.microchip.com/downloads/en/DeviceDoc/60001507E.pdf +var pinPadMapping = [64]uint16{ + // page 32 + PA00 / 2: 0 | pinPadMapSERCOM1AltPad0, + + // page 33 + PB08 / 2: 0 | pinPadMapSERCOM4AltPad0, + PA04 / 2: 0 | pinPadMapSERCOM0AltPad0, + PA06 / 2: 0 | pinPadMapSERCOM0AltPad2, + PC04 / 2: pinPadMapSERCOM6Pad0 | 0, + PC06 / 2: pinPadMapSERCOM6Pad2 | 0, + PA08 / 2: pinPadMapSERCOM0Pad0 | pinPadMapSERCOM2AltPad1, + PA10 / 2: pinPadMapSERCOM0Pad2 | pinPadMapSERCOM2AltPad2, + PB10 / 2: 0 | pinPadMapSERCOM4AltPad2, + PB12 / 2: pinPadMapSERCOM4Pad0 | 0, + PB14 / 2: pinPadMapSERCOM4Pad2 | 0, + PD08 / 2: pinPadMapSERCOM7Pad0 | pinPadMapSERCOM6AltPad1, + PD10 / 2: pinPadMapSERCOM7Pad2 | pinPadMapSERCOM6AltPad2, + PC10 / 2: pinPadMapSERCOM6Pad2 | pinPadMapSERCOM7AltPad2, + + // page 34 + PC12 / 2: pinPadMapSERCOM7Pad0 | pinPadMapSERCOM6AltPad1, + PC14 / 2: pinPadMapSERCOM7Pad2 | pinPadMapSERCOM6AltPad2, + PA12 / 2: pinPadMapSERCOM2Pad0 | pinPadMapSERCOM4AltPad1, + PA14 / 2: pinPadMapSERCOM2Pad2 | pinPadMapSERCOM4AltPad2, + PA16 / 2: pinPadMapSERCOM1Pad0 | pinPadMapSERCOM3AltPad1, + PA18 / 2: pinPadMapSERCOM1Pad2 | pinPadMapSERCOM3AltPad2, + PC16 / 2: pinPadMapSERCOM6Pad0 | pinPadMapSERCOM0AltPad1, + PC18 / 2: pinPadMapSERCOM6Pad2 | pinPadMapSERCOM0AltPad2, + PC22 / 2: pinPadMapSERCOM1Pad0 | pinPadMapSERCOM3AltPad1, + PD20 / 2: pinPadMapSERCOM1Pad2 | pinPadMapSERCOM3AltPad2, + PB16 / 2: pinPadMapSERCOM5Pad0 | 0, + PB18 / 2: pinPadMapSERCOM5Pad2 | pinPadMapSERCOM7AltPad2, + + // page 35 + PB20 / 2: pinPadMapSERCOM3Pad0 | pinPadMapSERCOM7AltPad1, + PA20 / 2: pinPadMapSERCOM5Pad2 | pinPadMapSERCOM3AltPad2, + PA22 / 2: pinPadMapSERCOM3Pad0 | pinPadMapSERCOM5AltPad1, + PA24 / 2: pinPadMapSERCOM3Pad2 | pinPadMapSERCOM5AltPad2, + PB22 / 2: pinPadMapSERCOM1Pad2 | pinPadMapSERCOM5AltPad2, + PB24 / 2: pinPadMapSERCOM0Pad0 | pinPadMapSERCOM2AltPad1, + PB26 / 2: pinPadMapSERCOM2Pad0 | pinPadMapSERCOM4AltPad1, + PB28 / 2: pinPadMapSERCOM2Pad2 | pinPadMapSERCOM4AltPad2, + PC24 / 2: pinPadMapSERCOM0Pad2 | pinPadMapSERCOM2AltPad2, + //PC26 / 2: pinPadMapSERCOM1Pad1 | 0, // note: PC26 doesn't support SERCOM, but PC27 does + //PC28 / 2: pinPadMapSERCOM1Pad1 | 0, // note: PC29 doesn't exist in the datasheet? + PA30 / 2: 0 | pinPadMapSERCOM1AltPad2, + + // page 36 + PB30 / 2: 0 | pinPadMapSERCOM5AltPad1, + PB00 / 2: 0 | pinPadMapSERCOM5AltPad2, + PB02 / 2: 0 | pinPadMapSERCOM5AltPad0, +} + +// findPinPadMapping looks up the pad number and the pinmode for a given pin and +// SERCOM number. The result can either be SERCOM, SERCOM-ALT, or "not found" +// (indicated by returning ok=false). The pad number is returned to calculate +// the DOPO/DIPO bitfields of the various serial peripherals. +func findPinPadMapping(sercom uint8, pin Pin) (pinMode PinMode, pad uint32, ok bool) { + if int(pin)/2 >= len(pinPadMapping) { + // This is probably NoPin, for which no mapping is available. + return + } + + bytes := pinPadMapping[pin/2] + upper := byte(bytes >> 8) + lower := byte(bytes & 0xff) + + if upper != 0 { + // SERCOM + if (upper>>4)-1 == sercom { + pinMode = PinSERCOM + pad |= uint32(upper % 4) + ok = true + } + } + if lower != 0 { + // SERCOM-ALT + if (lower>>4)-1 == sercom { + pinMode = PinSERCOMAlt + pad |= uint32(lower % 4) + ok = true + } + } + + if ok { + // If the pin is uneven, toggle the lowest bit of the pad number. + if pin&1 != 0 { + pad ^= 1 + } + } + return +} + +// SetInterrupt sets an interrupt to be executed when a particular pin changes +// state. The pin should already be configured as an input, including a pull up +// or down if no external pull is provided. +// +// This call will replace a previously set callback on this pin. You can pass a +// nil func to unset the pin change interrupt. If you do so, the change +// parameter is ignored and can be set to any value (such as 0). +func (p Pin) SetInterrupt(change PinChange, callback func(Pin)) error { + // Most pins follow a common pattern where the EXTINT value is the pin + // number modulo 16. However, there are a few exceptions, as you can see + // below. + extint := uint8(0) + + switch p { + case PA08: + // Connected to NMI. This is not currently supported. + return ErrInvalidInputPin + case PB26: + extint = 12 + case PB27: + extint = 13 + case PB28: + extint = 14 + case PB29: + extint = 15 + case PC07: + extint = 9 + case PD08: + extint = 3 + case PD09: + extint = 4 + case PD10: + extint = 5 + case PD11: + extint = 6 + case PD12: + extint = 7 + case PD20: + extint = 10 + case PD21: + extint = 11 + default: + // All other pins follow a normal pattern. + extint = uint8(p) % 16 + } + + if callback == nil { + // Disable this pin interrupt (if it was enabled). + sam.EIC.INTENCLR.Set(1 << extint) + if pinCallbacks[extint] != nil { + pinCallbacks[extint] = nil + } + return nil + } + + if pinCallbacks[extint] != nil { + // The pin was already configured. + // To properly re-configure a pin, unset it first and set a new + // configuration. + return ErrNoPinChangeChannel + } + pinCallbacks[extint] = callback + interruptPins[extint] = p + + if !sam.EIC.CTRLA.HasBits(sam.EIC_CTRLA_ENABLE) { + // EIC peripheral has not yet been initialized. Initialize it now. + + // The EIC needs two clocks: CLK_EIC_APB and GCLK_EIC. CLK_EIC_APB is + // enabled by default, so doesn't have to be re-enabled. The other is + // required for detecting edges and must be enabled manually. + sam.GCLK.PCHCTRL[4].Set((sam.GCLK_PCHCTRL_GEN_GCLK0 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) + + // should not be necessary (CLKCTRL is not synchronized) + for sam.GCLK.SYNCBUSY.HasBits(sam.GCLK_SYNCBUSY_GENCTRL_GCLK0 << sam.GCLK_SYNCBUSY_GENCTRL_Pos) { + } + } + + // CONFIG register is enable-protected, so disable EIC. + sam.EIC.CTRLA.ClearBits(sam.EIC_CTRLA_ENABLE) + + // Configure this pin. Set the 4 bits of the EIC.CONFIGx register to the + // sense value (filter bit set to 0, sense bits set to the change value). + addr := &sam.EIC.CONFIG[0] + if extint >= 8 { + addr = &sam.EIC.CONFIG[1] + } + pos := (extint % 8) * 4 // bit position in register + addr.ReplaceBits(uint32(change), 0xf, pos) + + // Enable external interrupt for this pin. + sam.EIC.INTENSET.Set(1 << extint) + + sam.EIC.CTRLA.Set(sam.EIC_CTRLA_ENABLE) + for sam.EIC.SYNCBUSY.HasBits(sam.EIC_SYNCBUSY_ENABLE) { + } + + // Set the PMUXEN flag, while keeping the INEN and PULLEN flags (if they + // were set before). This avoids clearing the pin pull mode while + // configuring the pin interrupt. + p.setPinCfg(sam.PORT_GROUP_PINCFG_PMUXEN | (p.getPinCfg() & (sam.PORT_GROUP_PINCFG_INEN | sam.PORT_GROUP_PINCFG_PULLEN))) + if p&1 > 0 { + // odd pin, so save the even pins + val := p.getPMux() & sam.PORT_GROUP_PMUX_PMUXE_Msk + p.setPMux(val | (0 << sam.PORT_GROUP_PMUX_PMUXO_Pos)) + } else { + // even pin, so save the odd pins + val := p.getPMux() & sam.PORT_GROUP_PMUX_PMUXO_Msk + p.setPMux(val | (0 << sam.PORT_GROUP_PMUX_PMUXE_Pos)) + } + + handleEICInterrupt := func(interrupt.Interrupt) { + flags := sam.EIC.INTFLAG.Get() + sam.EIC.INTFLAG.Set(flags) // clear interrupt + for i := uint(0); i < 16; i++ { // there are 16 channels + if flags&(1<>pin_in_group)&1 > 0 +} + +// Toggle switches an output pin from low to high or from high to low. +// Warning: only use this on an output pin! +func (p Pin) Toggle() { + group, pin_in_group := p.getPinGrouping() + sam.PORT.GROUP[group].OUTTGL.Set(1 << pin_in_group) +} + +// Configure this pin with the given configuration. +func (p Pin) Configure(config PinConfig) { + group, pin_in_group := p.getPinGrouping() + switch config.Mode { + case PinOutput: + sam.PORT.GROUP[group].DIRSET.Set(1 << pin_in_group) + // output is also set to input enable so pin can read back its own value + p.setPinCfg(sam.PORT_GROUP_PINCFG_INEN) + + case PinInput: + sam.PORT.GROUP[group].DIRCLR.Set(1 << pin_in_group) + p.setPinCfg(sam.PORT_GROUP_PINCFG_INEN) + + case PinInputPulldown: + sam.PORT.GROUP[group].DIRCLR.Set(1 << pin_in_group) + sam.PORT.GROUP[group].OUTCLR.Set(1 << pin_in_group) + p.setPinCfg(sam.PORT_GROUP_PINCFG_INEN | sam.PORT_GROUP_PINCFG_PULLEN) + + case PinInputPullup: + sam.PORT.GROUP[group].DIRCLR.Set(1 << pin_in_group) + sam.PORT.GROUP[group].OUTSET.Set(1 << pin_in_group) + p.setPinCfg(sam.PORT_GROUP_PINCFG_INEN | sam.PORT_GROUP_PINCFG_PULLEN) + + case PinSERCOM: + if p&1 > 0 { + // odd pin, so save the even pins + val := p.getPMux() & sam.PORT_GROUP_PMUX_PMUXE_Msk + p.setPMux(val | (uint8(PinSERCOM) << sam.PORT_GROUP_PMUX_PMUXO_Pos)) + } else { + // even pin, so save the odd pins + val := p.getPMux() & sam.PORT_GROUP_PMUX_PMUXO_Msk + p.setPMux(val | (uint8(PinSERCOM) << sam.PORT_GROUP_PMUX_PMUXE_Pos)) + } + // enable port config + p.setPinCfg(sam.PORT_GROUP_PINCFG_PMUXEN | sam.PORT_GROUP_PINCFG_DRVSTR | sam.PORT_GROUP_PINCFG_INEN) + + case PinSERCOMAlt: + if p&1 > 0 { + // odd pin, so save the even pins + val := p.getPMux() & sam.PORT_GROUP_PMUX_PMUXE_Msk + p.setPMux(val | (uint8(PinSERCOMAlt) << sam.PORT_GROUP_PMUX_PMUXO_Pos)) + } else { + // even pin, so save the odd pins + val := p.getPMux() & sam.PORT_GROUP_PMUX_PMUXO_Msk + p.setPMux(val | (uint8(PinSERCOMAlt) << sam.PORT_GROUP_PMUX_PMUXE_Pos)) + } + // enable port config + p.setPinCfg(sam.PORT_GROUP_PINCFG_PMUXEN | sam.PORT_GROUP_PINCFG_DRVSTR) + + case PinCom: + if p&1 > 0 { + // odd pin, so save the even pins + val := p.getPMux() & sam.PORT_GROUP_PMUX_PMUXE_Msk + p.setPMux(val | (uint8(PinCom) << sam.PORT_GROUP_PMUX_PMUXO_Pos)) + } else { + // even pin, so save the odd pins + val := p.getPMux() & sam.PORT_GROUP_PMUX_PMUXO_Msk + p.setPMux(val | (uint8(PinCom) << sam.PORT_GROUP_PMUX_PMUXE_Pos)) + } + // enable port config + p.setPinCfg(sam.PORT_GROUP_PINCFG_PMUXEN) + case PinAnalog: + if p&1 > 0 { + // odd pin, so save the even pins + val := p.getPMux() & sam.PORT_GROUP_PMUX_PMUXE_Msk + p.setPMux(val | (uint8(PinAnalog) << sam.PORT_GROUP_PMUX_PMUXO_Pos)) + } else { + // even pin, so save the odd pins + val := p.getPMux() & sam.PORT_GROUP_PMUX_PMUXO_Msk + p.setPMux(val | (uint8(PinAnalog) << sam.PORT_GROUP_PMUX_PMUXE_Pos)) + } + // enable port config + p.setPinCfg(sam.PORT_GROUP_PINCFG_PMUXEN | sam.PORT_GROUP_PINCFG_DRVSTR) + case PinSDHC: + if p&1 > 0 { + // odd pin, so save the even pins + val := p.getPMux() & sam.PORT_GROUP_PMUX_PMUXE_Msk + p.setPMux(val | (uint8(PinSDHC) << sam.PORT_GROUP_PMUX_PMUXO_Pos)) + } else { + // even pin, so save the odd pins + val := p.getPMux() & sam.PORT_GROUP_PMUX_PMUXO_Msk + p.setPMux(val | (uint8(PinSDHC) << sam.PORT_GROUP_PMUX_PMUXE_Pos)) + } + // enable port config + p.setPinCfg(sam.PORT_GROUP_PINCFG_PMUXEN) + } +} + +// getPMux returns the value for the correct PMUX register for this pin. +func (p Pin) getPMux() uint8 { + group, pin_in_group := p.getPinGrouping() + return sam.PORT.GROUP[group].PMUX[pin_in_group>>1].Get() +} + +// setPMux sets the value for the correct PMUX register for this pin. +func (p Pin) setPMux(val uint8) { + group, pin_in_group := p.getPinGrouping() + sam.PORT.GROUP[group].PMUX[pin_in_group>>1].Set(val) +} + +// getPinCfg returns the value for the correct PINCFG register for this pin. +func (p Pin) getPinCfg() uint8 { + group, pin_in_group := p.getPinGrouping() + return sam.PORT.GROUP[group].PINCFG[pin_in_group].Get() +} + +// setPinCfg sets the value for the correct PINCFG register for this pin. +func (p Pin) setPinCfg(val uint8) { + group, pin_in_group := p.getPinGrouping() + sam.PORT.GROUP[group].PINCFG[pin_in_group].Set(val) +} + +// getPinGrouping calculates the gpio group and pin id from the pin number. +// Pins are split into groups of 32, and each group has its own set of +// control registers. +func (p Pin) getPinGrouping() (uint8, uint8) { + group := uint8(p) >> 5 + pin_in_group := uint8(p) & 0x1f + return group, pin_in_group +} + +// InitADC initializes the ADC. +func InitADC() { + // ADC Bias Calibration + // NVMCTRL_SW0 0x00800080 + // #define ADC0_FUSES_BIASCOMP_ADDR NVMCTRL_SW0 + // #define ADC0_FUSES_BIASCOMP_Pos 2 /**< \brief (NVMCTRL_SW0) ADC Comparator Scaling */ + // #define ADC0_FUSES_BIASCOMP_Msk (_Ul(0x7) << ADC0_FUSES_BIASCOMP_Pos) + // #define ADC0_FUSES_BIASCOMP(value) (ADC0_FUSES_BIASCOMP_Msk & ((value) << ADC0_FUSES_BIASCOMP_Pos)) + + // #define ADC0_FUSES_BIASR2R_ADDR NVMCTRL_SW0 + // #define ADC0_FUSES_BIASR2R_Pos 8 /**< \brief (NVMCTRL_SW0) ADC Bias R2R ampli scaling */ + // #define ADC0_FUSES_BIASR2R_Msk (_Ul(0x7) << ADC0_FUSES_BIASR2R_Pos) + // #define ADC0_FUSES_BIASR2R(value) (ADC0_FUSES_BIASR2R_Msk & ((value) << ADC0_FUSES_BIASR2R_Pos)) + + // #define ADC0_FUSES_BIASREFBUF_ADDR NVMCTRL_SW0 + // #define ADC0_FUSES_BIASREFBUF_Pos 5 /**< \brief (NVMCTRL_SW0) ADC Bias Reference Buffer Scaling */ + // #define ADC0_FUSES_BIASREFBUF_Msk (_Ul(0x7) << ADC0_FUSES_BIASREFBUF_Pos) + // #define ADC0_FUSES_BIASREFBUF(value) (ADC0_FUSES_BIASREFBUF_Msk & ((value) << ADC0_FUSES_BIASREFBUF_Pos)) + + // #define ADC1_FUSES_BIASCOMP_ADDR NVMCTRL_SW0 + // #define ADC1_FUSES_BIASCOMP_Pos 16 /**< \brief (NVMCTRL_SW0) ADC Comparator Scaling */ + // #define ADC1_FUSES_BIASCOMP_Msk (_Ul(0x7) << ADC1_FUSES_BIASCOMP_Pos) + // #define ADC1_FUSES_BIASCOMP(value) (ADC1_FUSES_BIASCOMP_Msk & ((value) << ADC1_FUSES_BIASCOMP_Pos)) + + // #define ADC1_FUSES_BIASR2R_ADDR NVMCTRL_SW0 + // #define ADC1_FUSES_BIASR2R_Pos 22 /**< \brief (NVMCTRL_SW0) ADC Bias R2R ampli scaling */ + // #define ADC1_FUSES_BIASR2R_Msk (_Ul(0x7) << ADC1_FUSES_BIASR2R_Pos) + // #define ADC1_FUSES_BIASR2R(value) (ADC1_FUSES_BIASR2R_Msk & ((value) << ADC1_FUSES_BIASR2R_Pos)) + + // #define ADC1_FUSES_BIASREFBUF_ADDR NVMCTRL_SW0 + // #define ADC1_FUSES_BIASREFBUF_Pos 19 /**< \brief (NVMCTRL_SW0) ADC Bias Reference Buffer Scaling */ + // #define ADC1_FUSES_BIASREFBUF_Msk (_Ul(0x7) << ADC1_FUSES_BIASREFBUF_Pos) + // #define ADC1_FUSES_BIASREFBUF(value) (ADC1_FUSES_BIASREFBUF_Msk & ((value) << ADC1_FUSES_BIASREFBUF_Pos)) + + adcFuse := *(*uint32)(unsafe.Pointer(uintptr(0x00800080))) + + // uint32_t biascomp = (*((uint32_t *)ADC0_FUSES_BIASCOMP_ADDR) & ADC0_FUSES_BIASCOMP_Msk) >> ADC0_FUSES_BIASCOMP_Pos; + biascomp := (adcFuse & uint32(0x7<<2)) //>> 2 + + // uint32_t biasr2r = (*((uint32_t *)ADC0_FUSES_BIASR2R_ADDR) & ADC0_FUSES_BIASR2R_Msk) >> ADC0_FUSES_BIASR2R_Pos; + biasr2r := (adcFuse & uint32(0x7<<8)) //>> 8 + + // uint32_t biasref = (*((uint32_t *)ADC0_FUSES_BIASREFBUF_ADDR) & ADC0_FUSES_BIASREFBUF_Msk) >> ADC0_FUSES_BIASREFBUF_Pos; + biasref := (adcFuse & uint32(0x7<<5)) //>> 5 + + // calibrate ADC0 + sam.ADC0.CALIB.Set(uint16(biascomp | biasr2r | biasref)) + + // biascomp = (*((uint32_t *)ADC1_FUSES_BIASCOMP_ADDR) & ADC1_FUSES_BIASCOMP_Msk) >> ADC1_FUSES_BIASCOMP_Pos; + biascomp = (adcFuse & uint32(0x7<<16)) //>> 16 + + // biasr2r = (*((uint32_t *)ADC1_FUSES_BIASR2R_ADDR) & ADC1_FUSES_BIASR2R_Msk) >> ADC1_FUSES_BIASR2R_Pos; + biasr2r = (adcFuse & uint32(0x7<<22)) //>> 22 + + // biasref = (*((uint32_t *)ADC1_FUSES_BIASREFBUF_ADDR) & ADC1_FUSES_BIASREFBUF_Msk) >> ADC1_FUSES_BIASREFBUF_Pos; + biasref = (adcFuse & uint32(0x7<<19)) //>> 19 + + // calibrate ADC1 + sam.ADC1.CALIB.Set(uint16((biascomp | biasr2r | biasref) >> 16)) +} + +// Configure configures a ADCPin to be able to be used to read data. +func (a ADC) Configure(config ADCConfig) { + + for _, adc := range []*sam.ADC_Type{sam.ADC0, sam.ADC1} { + + for adc.SYNCBUSY.HasBits(sam.ADC_SYNCBUSY_CTRLB) { + } // wait for sync + + // Averaging (see datasheet table in AVGCTRL register description) + var resolution uint32 = sam.ADC_CTRLB_RESSEL_16BIT + var samples uint32 + switch config.Samples { + case 2: + samples = sam.ADC_AVGCTRL_SAMPLENUM_2 + case 4: + samples = sam.ADC_AVGCTRL_SAMPLENUM_4 + case 8: + samples = sam.ADC_AVGCTRL_SAMPLENUM_8 + case 16: + samples = sam.ADC_AVGCTRL_SAMPLENUM_16 + case 32: + samples = sam.ADC_AVGCTRL_SAMPLENUM_32 + case 64: + samples = sam.ADC_AVGCTRL_SAMPLENUM_64 + case 128: + samples = sam.ADC_AVGCTRL_SAMPLENUM_128 + case 256: + samples = sam.ADC_AVGCTRL_SAMPLENUM_256 + case 512: + samples = sam.ADC_AVGCTRL_SAMPLENUM_512 + case 1024: + samples = sam.ADC_AVGCTRL_SAMPLENUM_1024 + default: // 1 sample only (no oversampling nor averaging), adjusting result by 0 + // Resolutions less than 16 bits only make sense when sampling only + // once. Resulting ADC values become erratic when using both + // multi-sampling and less than 16 bits of resolution. + samples = sam.ADC_AVGCTRL_SAMPLENUM_1 + switch config.Resolution { + case 8: + resolution = sam.ADC_CTRLB_RESSEL_8BIT + case 10: + resolution = sam.ADC_CTRLB_RESSEL_10BIT + case 12: + resolution = sam.ADC_CTRLB_RESSEL_12BIT + case 16: + resolution = sam.ADC_CTRLB_RESSEL_16BIT + default: + resolution = sam.ADC_CTRLB_RESSEL_12BIT + } + } + + adc.AVGCTRL.Set(uint8(samples<> sam.ADC_CTRLB_RESSEL_Pos { + case sam.ADC_CTRLB_RESSEL_8BIT: + val = val << 8 + case sam.ADC_CTRLB_RESSEL_10BIT: + val = val << 6 + case sam.ADC_CTRLB_RESSEL_12BIT: + val = val << 4 + case sam.ADC_CTRLB_RESSEL_16BIT: + // Adjust for multiple samples. This is only configured when the + // resolution is 16 bits. + switch (bus.AVGCTRL.Get() & sam.ADC_AVGCTRL_SAMPLENUM_Msk) >> sam.ADC_AVGCTRL_SAMPLENUM_Pos { + case sam.ADC_AVGCTRL_SAMPLENUM_1: + val <<= 4 + case sam.ADC_AVGCTRL_SAMPLENUM_2: + val <<= 3 + case sam.ADC_AVGCTRL_SAMPLENUM_4: + val <<= 2 + case sam.ADC_AVGCTRL_SAMPLENUM_8: + val <<= 1 + default: + // These values are all shifted by the hardware so they fit exactly + // in a 16-bit integer, so they don't need to be shifted here. + } + } + return val +} + +func (a ADC) getADCBus() *sam.ADC_Type { + if (a.Pin >= PB04 && a.Pin <= PB07) || (a.Pin >= PC00) { + return sam.ADC1 + } + return sam.ADC0 +} + +func (a ADC) getADCChannel() uint8 { + switch a.Pin { + case PA02: + return 0 + case PB08: + return 2 + case PB09: + return 3 + case PA04: + return 4 + case PA05: + return 5 + case PA06: + return 6 + case PA07: + return 7 + case PB00: + return 12 + case PB01: + return 13 + case PB02: + return 14 + case PB03: + return 15 + case PA09: + return 17 + case PA11: + return 19 + + case PB04: + return 6 + case PB05: + return 7 + case PB06: + return 8 + case PB07: + return 9 + + case PC00: + return 10 + case PC01: + return 11 + case PC02: + return 4 + case PC03: + return 5 + case PC30: + return 12 + case PC31: + return 13 + + case PD00: + return 14 + case PD01: + return 15 + default: + panic("Invalid ADC pin") + } +} + +// UART on the SAMD51. +type UART struct { + Buffer *RingBuffer + Bus *sam.SERCOM_USART_INT_Type + SERCOM uint8 + Interrupt interrupt.Interrupt // RXC interrupt +} + +var ( + sercomUSART0 = UART{Buffer: NewRingBuffer(), Bus: sam.SERCOM0_USART_INT, SERCOM: 0} + sercomUSART1 = UART{Buffer: NewRingBuffer(), Bus: sam.SERCOM1_USART_INT, SERCOM: 1} + sercomUSART2 = UART{Buffer: NewRingBuffer(), Bus: sam.SERCOM2_USART_INT, SERCOM: 2} + sercomUSART3 = UART{Buffer: NewRingBuffer(), Bus: sam.SERCOM3_USART_INT, SERCOM: 3} + sercomUSART4 = UART{Buffer: NewRingBuffer(), Bus: sam.SERCOM4_USART_INT, SERCOM: 4} + sercomUSART5 = UART{Buffer: NewRingBuffer(), Bus: sam.SERCOM5_USART_INT, SERCOM: 5} +) + +func init() { + sercomUSART0.Interrupt = interrupt.New(sam.IRQ_SERCOM0_2, sercomUSART0.handleInterrupt) + sercomUSART1.Interrupt = interrupt.New(sam.IRQ_SERCOM1_2, sercomUSART1.handleInterrupt) + sercomUSART2.Interrupt = interrupt.New(sam.IRQ_SERCOM2_2, sercomUSART2.handleInterrupt) + sercomUSART3.Interrupt = interrupt.New(sam.IRQ_SERCOM3_2, sercomUSART3.handleInterrupt) + sercomUSART4.Interrupt = interrupt.New(sam.IRQ_SERCOM4_2, sercomUSART4.handleInterrupt) + sercomUSART5.Interrupt = interrupt.New(sam.IRQ_SERCOM5_2, sercomUSART5.handleInterrupt) +} + +const ( + sampleRate16X = 16 + lsbFirst = 1 +) + +// Configure the UART. +func (uart *UART) Configure(config UARTConfig) error { + // Default baud rate to 115200. + if config.BaudRate == 0 { + config.BaudRate = 115200 + } + + // determine pins + if config.TX == 0 && config.RX == 0 { + // use default pins + config.TX = UART_TX_PIN + config.RX = UART_RX_PIN + } + + // Determine transmit pinout. + txPinMode, txPad, ok := findPinPadMapping(uart.SERCOM, config.TX) + if !ok { + return ErrInvalidOutputPin + } + var txPadOut uint32 + // See CTRLA.RXPO bits of the SERCOM USART peripheral (page 945-946) for how + // pads are mapped to pinout values. + switch txPad { + case 0: + txPadOut = 0 + default: + // should be flow control (RTS/CTS) pin + return ErrInvalidOutputPin + } + + // Determine receive pinout. + rxPinMode, rxPad, ok := findPinPadMapping(uart.SERCOM, config.RX) + if !ok { + return ErrInvalidInputPin + } + // As you can see in the CTRLA.RXPO bits of the SERCOM USART peripheral + // (page 945), input pins are mapped directly. + rxPadOut := rxPad + + // configure pins + config.TX.Configure(PinConfig{Mode: txPinMode}) + config.RX.Configure(PinConfig{Mode: rxPinMode}) + + // configure RTS/CTS pins if provided + if config.RTS != 0 && config.CTS != 0 { + rtsPinMode, _, ok := findPinPadMapping(uart.SERCOM, config.RTS) + if !ok { + return ErrInvalidOutputPin + } + + ctsPinMode, _, ok := findPinPadMapping(uart.SERCOM, config.CTS) + if !ok { + return ErrInvalidInputPin + } + + // See CTRLA.RXPO bits of the SERCOM USART peripheral (page 945-946) for how + // pads are mapped to pinout values. + txPadOut = 2 + + config.RTS.Configure(PinConfig{Mode: rtsPinMode}) + config.CTS.Configure(PinConfig{Mode: ctsPinMode}) + } + + // reset SERCOM + uart.Bus.CTRLA.SetBits(sam.SERCOM_USART_INT_CTRLA_SWRST) + for uart.Bus.CTRLA.HasBits(sam.SERCOM_USART_INT_CTRLA_SWRST) || + uart.Bus.SYNCBUSY.HasBits(sam.SERCOM_USART_INT_SYNCBUSY_SWRST) { + } + + // set UART mode/sample rate + // SERCOM_USART_CTRLA_MODE(mode) | + // SERCOM_USART_CTRLA_SAMPR(sampleRate); + // sam.SERCOM_USART_CTRLA_MODE_USART_INT_CLK = 1? + uart.Bus.CTRLA.Set((1 << sam.SERCOM_USART_INT_CTRLA_MODE_Pos) | + (1 << sam.SERCOM_USART_INT_CTRLA_SAMPR_Pos)) // sample rate of 16x + + // set clock + setSERCOMClockGenerator(uart.SERCOM, sam.GCLK_PCHCTRL_GEN_GCLK1) + + // Set baud rate + uart.SetBaudRate(config.BaudRate) + + // setup UART frame + // SERCOM_USART_CTRLA_FORM( (parityMode == SERCOM_NO_PARITY ? 0 : 1) ) | + // dataOrder << SERCOM_USART_CTRLA_DORD_Pos; + uart.Bus.CTRLA.SetBits((0 << sam.SERCOM_USART_INT_CTRLA_FORM_Pos) | // no parity + (lsbFirst << sam.SERCOM_USART_INT_CTRLA_DORD_Pos)) // data order + + // set UART stop bits/parity + // SERCOM_USART_CTRLB_CHSIZE(charSize) | + // nbStopBits << SERCOM_USART_CTRLB_SBMODE_Pos | + // (parityMode == SERCOM_NO_PARITY ? 0 : parityMode) << SERCOM_USART_CTRLB_PMODE_Pos; //If no parity use default value + uart.Bus.CTRLB.SetBits((0 << sam.SERCOM_USART_INT_CTRLB_CHSIZE_Pos) | // 8 bits is 0 + (0 << sam.SERCOM_USART_INT_CTRLB_SBMODE_Pos) | // 1 stop bit is zero + (0 << sam.SERCOM_USART_INT_CTRLB_PMODE_Pos)) // no parity + + // set UART pads. This is not same as pins... + // SERCOM_USART_CTRLA_TXPO(txPad) | + // SERCOM_USART_CTRLA_RXPO(rxPad); + uart.Bus.CTRLA.SetBits((txPadOut << sam.SERCOM_USART_INT_CTRLA_TXPO_Pos) | + (rxPadOut << sam.SERCOM_USART_INT_CTRLA_RXPO_Pos)) + + // Enable Transceiver and Receiver + //sercom->USART.CTRLB.reg |= SERCOM_USART_CTRLB_TXEN | SERCOM_USART_CTRLB_RXEN ; + uart.Bus.CTRLB.SetBits(sam.SERCOM_USART_INT_CTRLB_TXEN | sam.SERCOM_USART_INT_CTRLB_RXEN) + + // Enable USART1 port. + // sercom->USART.CTRLA.bit.ENABLE = 0x1u; + uart.Bus.CTRLA.SetBits(sam.SERCOM_USART_INT_CTRLA_ENABLE) + for uart.Bus.SYNCBUSY.HasBits(sam.SERCOM_USART_INT_SYNCBUSY_ENABLE) { + } + + // setup interrupt on receive + uart.Bus.INTENSET.Set(sam.SERCOM_USART_INT_INTENSET_RXC) + + // Enable RX IRQ. + // This is a small note at the bottom of the NVIC section of the datasheet: + // > The integer number specified in the source refers to the respective bit + // > position in the INTFLAG register of respective peripheral. + // Therefore, if we only need to listen to the RXC interrupt source (in bit + // position 2), we only need interrupt source 2 for this SERCOM device. + uart.Interrupt.Enable() + + return nil +} + +// SetBaudRate sets the communication speed for the UART. +func (uart *UART) SetBaudRate(br uint32) { + // Asynchronous fractional mode (Table 24-2 in datasheet) + // BAUD = fref / (sampleRateValue * fbaud) + // (multiply by 8, to calculate fractional piece) + // uint32_t baudTimes8 = (SystemCoreClock * 8) / (16 * baudrate); + baud := (SERCOM_FREQ_REF * 8) / (sampleRate16X * br) + + // sercom->USART.BAUD.FRAC.FP = (baudTimes8 % 8); + // sercom->USART.BAUD.FRAC.BAUD = (baudTimes8 / 8); + uart.Bus.BAUD.Set(uint16(((baud % 8) << sam.SERCOM_USART_INT_BAUD_FRAC_MODE_FP_Pos) | + ((baud / 8) << sam.SERCOM_USART_INT_BAUD_FRAC_MODE_BAUD_Pos))) +} + +// WriteByte writes a byte of data to the UART. +func (uart *UART) writeByte(c byte) error { + // wait until ready to receive + for !uart.Bus.INTFLAG.HasBits(sam.SERCOM_USART_INT_INTFLAG_DRE) { + } + uart.Bus.DATA.Set(uint32(c)) + return nil +} + +func (uart *UART) flush() {} + +func (uart *UART) handleInterrupt(interrupt.Interrupt) { + // should reset IRQ + uart.Receive(byte((uart.Bus.DATA.Get() & 0xFF))) + uart.Bus.INTFLAG.SetBits(sam.SERCOM_USART_INT_INTFLAG_RXC) +} + +// I2C on the SAMD51. +type I2C struct { + Bus *sam.SERCOM_I2CM_Type + SERCOM uint8 +} + +// I2CConfig is used to store config info for I2C. +type I2CConfig struct { + Frequency uint32 + SCL Pin + SDA Pin +} + +const ( + // SERCOM_FREQ_REF is always reference frequency on SAMD51 regardless of CPU speed. + SERCOM_FREQ_REF = 48000000 + SERCOM_FREQ_REF_GCLK0 = 120000000 + + // Default rise time in nanoseconds, based on 4.7K ohm pull up resistors + riseTimeNanoseconds = 125 + + // wire bus states + wireUnknownState = 0 + wireIdleState = 1 + wireOwnerState = 2 + wireBusyState = 3 + + // wire commands + wireCmdNoAction = 0 + wireCmdRepeatStart = 1 + wireCmdRead = 2 + wireCmdStop = 3 +) + +const i2cTimeout = 28000 // about 210us + +// Configure is intended to setup the I2C interface. +func (i2c *I2C) Configure(config I2CConfig) error { + // Default I2C bus speed is 100 kHz. + if config.Frequency == 0 { + config.Frequency = 100 * KHz + } + + // Use default I2C pins if not set. + if config.SDA == 0 && config.SCL == 0 { + config.SDA = SDA_PIN + config.SCL = SCL_PIN + } + + sclPinMode, sclPad, ok := findPinPadMapping(i2c.SERCOM, config.SCL) + if !ok || sclPad != 1 { + // SCL must be on pad 1, according to section 36.4 of the datasheet. + // Note: this is not an exhaustive test for I2C support on the pin: not + // all pins support I2C. + return ErrInvalidClockPin + } + sdaPinMode, sdaPad, ok := findPinPadMapping(i2c.SERCOM, config.SDA) + if !ok || sdaPad != 0 { + // SDA must be on pad 0, according to section 36.4 of the datasheet. + // Note: this is not an exhaustive test for I2C support on the pin: not + // all pins support I2C. + return ErrInvalidDataPin + } + + // reset SERCOM + i2c.Bus.CTRLA.SetBits(sam.SERCOM_I2CM_CTRLA_SWRST) + for i2c.Bus.CTRLA.HasBits(sam.SERCOM_I2CM_CTRLA_SWRST) || + i2c.Bus.SYNCBUSY.HasBits(sam.SERCOM_I2CM_SYNCBUSY_SWRST) { + } + + // set clock + setSERCOMClockGenerator(i2c.SERCOM, sam.GCLK_PCHCTRL_GEN_GCLK1) + + // Set i2c controller mode + //SERCOM_I2CM_CTRLA_MODE( I2C_MASTER_OPERATION ) + // sam.SERCOM_I2CM_CTRLA_MODE_I2C_MASTER = 5? + i2c.Bus.CTRLA.Set(5 << sam.SERCOM_I2CM_CTRLA_MODE_Pos) // | + + i2c.SetBaudRate(config.Frequency) + + // Enable I2CM port. + // sercom->USART.CTRLA.bit.ENABLE = 0x1u; + i2c.Bus.CTRLA.SetBits(sam.SERCOM_I2CM_CTRLA_ENABLE) + for i2c.Bus.SYNCBUSY.HasBits(sam.SERCOM_I2CM_SYNCBUSY_ENABLE) { + } + + // set bus idle mode + i2c.Bus.STATUS.SetBits(wireIdleState << sam.SERCOM_I2CM_STATUS_BUSSTATE_Pos) + for i2c.Bus.SYNCBUSY.HasBits(sam.SERCOM_I2CM_SYNCBUSY_SYSOP) { + } + + // enable pins + config.SDA.Configure(PinConfig{Mode: sdaPinMode}) + config.SCL.Configure(PinConfig{Mode: sclPinMode}) + + return nil +} + +// SetBaudRate sets the communication speed for I2C. +func (i2c *I2C) SetBaudRate(br uint32) error { + // Synchronous arithmetic baudrate, via Adafruit SAMD51 implementation: + // sercom->I2CM.BAUD.bit.BAUD = SERCOM_FREQ_REF / ( 2 * baudrate) - 1 ; + baud := SERCOM_FREQ_REF/(2*br) - 1 + i2c.Bus.BAUD.Set(baud) + return nil +} + +// Tx does a single I2C transaction at the specified address. +// It clocks out the given address, writes the bytes in w, reads back len(r) +// bytes and stores them in r, and generates a stop condition on the bus. +func (i2c *I2C) Tx(addr uint16, w, r []byte) error { + var err error + if len(w) != 0 { + // send start/address for write + i2c.sendAddress(addr, true) + + // wait until transmission complete + timeout := i2cTimeout + for !i2c.Bus.INTFLAG.HasBits(sam.SERCOM_I2CM_INTFLAG_MB) { + timeout-- + if timeout == 0 { + return errI2CWriteTimeout + } + } + + // ACK received (0: ACK, 1: NACK) + if i2c.Bus.STATUS.HasBits(sam.SERCOM_I2CM_STATUS_RXNACK) { + return errI2CAckExpected + } + + // write data + for _, b := range w { + err = i2c.WriteByte(b) + if err != nil { + return err + } + } + + err = i2c.signalStop() + if err != nil { + return err + } + } + if len(r) != 0 { + // send start/address for read + i2c.sendAddress(addr, false) + + // wait transmission complete + for !i2c.Bus.INTFLAG.HasBits(sam.SERCOM_I2CM_INTFLAG_SB) { + // If the peripheral NACKS the address, the MB bit will be set. + // In that case, send a stop condition and return error. + if i2c.Bus.INTFLAG.HasBits(sam.SERCOM_I2CM_INTFLAG_MB) { + i2c.Bus.CTRLB.SetBits(wireCmdStop << sam.SERCOM_I2CM_CTRLB_CMD_Pos) // Stop condition + return errI2CAckExpected + } + } + + // ACK received (0: ACK, 1: NACK) + if i2c.Bus.STATUS.HasBits(sam.SERCOM_I2CM_STATUS_RXNACK) { + return errI2CAckExpected + } + + // read first byte + r[0] = i2c.readByte() + for i := 1; i < len(r); i++ { + // Send an ACK + i2c.Bus.CTRLB.ClearBits(sam.SERCOM_I2CM_CTRLB_ACKACT) + + i2c.signalRead() + + // Read data and send the ACK + r[i] = i2c.readByte() + } + + // Send NACK to end transmission + i2c.Bus.CTRLB.SetBits(sam.SERCOM_I2CM_CTRLB_ACKACT) + + err = i2c.signalStop() + if err != nil { + return err + } + } + + return nil +} + +// WriteByte writes a single byte to the I2C bus. +func (i2c *I2C) WriteByte(data byte) error { + // Send data byte + i2c.Bus.DATA.Set(data) + + // wait until transmission successful + timeout := i2cTimeout + for !i2c.Bus.INTFLAG.HasBits(sam.SERCOM_I2CM_INTFLAG_MB) { + // check for bus error + if i2c.Bus.STATUS.HasBits(sam.SERCOM_I2CM_STATUS_BUSERR) { + return errI2CBusError + } + timeout-- + if timeout == 0 { + return errI2CWriteTimeout + } + } + + if i2c.Bus.STATUS.HasBits(sam.SERCOM_I2CM_STATUS_RXNACK) { + return errI2CAckExpected + } + + return nil +} + +// sendAddress sends the address and start signal +func (i2c *I2C) sendAddress(address uint16, write bool) error { + data := (address << 1) + if !write { + data |= 1 // set read flag + } + + // wait until bus ready + timeout := i2cTimeout + for !i2c.Bus.STATUS.HasBits(wireIdleState< freqGCLK1 && uint32(uint8(baudRateGCLK0-1))+1 == baudRateGCLK0 { + // Pick this 120MHz clock if it results in a better frequency after + // division, and the baudRate value fits in the BAUD register. + setSERCOMClockGenerator(spi.SERCOM, sam.GCLK_PCHCTRL_GEN_GCLK0) + spi.Bus.BAUD.Set(uint8(baudRateGCLK0 - 1)) + } else { + // Use the 48MHz clock in other cases. + setSERCOMClockGenerator(spi.SERCOM, sam.GCLK_PCHCTRL_GEN_GCLK1) + spi.Bus.BAUD.Set(uint8(baudRateGCLK1 - 1)) + } + + // Enable SPI port. + spi.Bus.CTRLA.SetBits(sam.SERCOM_SPIM_CTRLA_ENABLE) + for spi.Bus.SYNCBUSY.HasBits(sam.SERCOM_SPIM_SYNCBUSY_ENABLE) { + } + + return nil +} + +// Transfer writes/reads a single byte using the SPI interface. +func (spi *SPI) Transfer(w byte) (byte, error) { + // write data + spi.Bus.DATA.Set(uint32(w)) + + // wait for receive + for !spi.Bus.INTFLAG.HasBits(sam.SERCOM_SPIM_INTFLAG_RXC) { + } + + // return data + return byte(spi.Bus.DATA.Get()), nil +} + +// Tx handles read/write operation for SPI interface. Since SPI is a synchronous write/read +// interface, there must always be the same number of bytes written as bytes read. +// The Tx method knows about this, and offers a few different ways of calling it. +// +// This form sends the bytes in tx buffer, putting the resulting bytes read into the rx buffer. +// Note that the tx and rx buffers must be the same size: +// +// spi.Tx(tx, rx) +// +// This form sends the tx buffer, ignoring the result. Useful for sending "commands" that return zeros +// until all the bytes in the command packet have been received: +// +// spi.Tx(tx, nil) +// +// This form sends zeros, putting the result into the rx buffer. Good for reading a "result packet": +// +// spi.Tx(nil, rx) +func (spi *SPI) Tx(w, r []byte) error { + switch { + case w == nil: + // read only, so write zero and read a result. + spi.rx(r) + case r == nil: + // write only + spi.tx(w) + + default: + // write/read + if len(w) != len(r) { + return ErrTxInvalidSliceSize + } + + spi.txrx(w, r) + } + + return nil +} + +func (spi *SPI) tx(tx []byte) { + for i := 0; i < len(tx); i++ { + for !spi.Bus.INTFLAG.HasBits(sam.SERCOM_SPIM_INTFLAG_DRE) { + } + spi.Bus.DATA.Set(uint32(tx[i])) + } + for !spi.Bus.INTFLAG.HasBits(sam.SERCOM_SPIM_INTFLAG_TXC) { + } + + // read to clear RXC register + for spi.Bus.INTFLAG.HasBits(sam.SERCOM_SPIM_INTFLAG_RXC) { + spi.Bus.DATA.Get() + } +} + +func (spi *SPI) rx(rx []byte) { + spi.Bus.DATA.Set(0) + for !spi.Bus.INTFLAG.HasBits(sam.SERCOM_SPIM_INTFLAG_DRE) { + } + + for i := 1; i < len(rx); i++ { + spi.Bus.DATA.Set(0) + for !spi.Bus.INTFLAG.HasBits(sam.SERCOM_SPIM_INTFLAG_RXC) { + } + rx[i-1] = byte(spi.Bus.DATA.Get()) + } + for !spi.Bus.INTFLAG.HasBits(sam.SERCOM_SPIM_INTFLAG_RXC) { + } + rx[len(rx)-1] = byte(spi.Bus.DATA.Get()) +} + +func (spi *SPI) txrx(tx, rx []byte) { + spi.Bus.DATA.Set(uint32(tx[0])) + for !spi.Bus.INTFLAG.HasBits(sam.SERCOM_SPIM_INTFLAG_DRE) { + } + + for i := 1; i < len(rx); i++ { + spi.Bus.DATA.Set(uint32(tx[i])) + for !spi.Bus.INTFLAG.HasBits(sam.SERCOM_SPIM_INTFLAG_RXC) { + } + rx[i-1] = byte(spi.Bus.DATA.Get()) + } + for !spi.Bus.INTFLAG.HasBits(sam.SERCOM_SPIM_INTFLAG_RXC) { + } + rx[len(rx)-1] = byte(spi.Bus.DATA.Get()) +} + +// The QSPI peripheral on ATSAMD51 is only available on the following pins +const ( + QSPI_SCK = PB10 + QSPI_CS = PB11 + QSPI_DATA0 = PA08 + QSPI_DATA1 = PA09 + QSPI_DATA2 = PA10 + QSPI_DATA3 = PA11 +) + +// TCC is one timer peripheral, which consists of a counter and multiple output +// channels (that can be connected to actual pins). You can set the frequency +// using SetPeriod, but only for all the channels in this timer peripheral at +// once. +type TCC sam.TCC_Type + +//go:inline +func (tcc *TCC) timer() *sam.TCC_Type { + return (*sam.TCC_Type)(tcc) +} + +// Configure enables and configures this TCC. +func (tcc *TCC) Configure(config PWMConfig) error { + // Enable the TCC clock to be able to use the TCC. + tcc.configureClock() + + // Disable timer (if it was enabled). This is necessary because + // tcc.setPeriod may want to change the prescaler bits in CTRLA, which is + // only allowed when the TCC is disabled. + tcc.timer().CTRLA.ClearBits(sam.TCC_CTRLA_ENABLE) + + // Use "Normal PWM" (single-slope PWM) + tcc.timer().WAVE.Set(sam.TCC_WAVE_WAVEGEN_NPWM) + + // Wait for synchronization of all changed registers. + for tcc.timer().SYNCBUSY.Get() != 0 { + } + + // Set the period and prescaler. + err := tcc.setPeriod(config.Period, true) + + // Enable the timer. + tcc.timer().CTRLA.SetBits(sam.TCC_CTRLA_ENABLE) + + // Wait for synchronization of all changed registers. + for tcc.timer().SYNCBUSY.Get() != 0 { + } + + // Return any error that might have occurred in the tcc.setPeriod call. + return err +} + +// SetPeriod updates the period of this TCC peripheral. +// To set a particular frequency, use the following formula: +// +// period = 1e9 / frequency +// +// If you use a period of 0, a period that works well for LEDs will be picked. +// +// SetPeriod will not change the prescaler, but also won't change the current +// value in any of the channels. This means that you may need to update the +// value for the particular channel. +// +// Note that you cannot pick any arbitrary period after the TCC peripheral has +// been configured. If you want to switch between frequencies, pick the lowest +// frequency (longest period) once when calling Configure and adjust the +// frequency here as needed. +func (tcc *TCC) SetPeriod(period uint64) error { + return tcc.setPeriod(period, false) +} + +// setPeriod sets the period of this TCC, possibly updating the prescaler as +// well. The prescaler can only modified when the TCC is disabled, that is, in +// the Configure function. +func (tcc *TCC) setPeriod(period uint64, updatePrescaler bool) error { + var top uint64 + if period == 0 { + // Make sure the TOP value is at 0xffff (enough for a 16-bit timer). + top = 0xffff + } else { + // The formula below calculates the following formula, optimized: + // period * (120e6 / 1e9) + // This assumes that the chip is running from generic clock generator 0 + // at 120MHz. + top = period * 3 / 25 + } + + maxTop := uint64(0xffff) + if tcc.timer() == sam.TCC0 || tcc.timer() == sam.TCC1 { + // Only TCC0 and TCC1 are 24-bit timers, the rest are 16-bit. + maxTop = 0xffffff + } + + if updatePrescaler { + // This function was called during Configure(), with the timer disabled. + // Note that updating the prescaler can only happen while the peripheral + // is disabled. + var prescaler uint32 + switch { + case top <= maxTop: + prescaler = sam.TCC_CTRLA_PRESCALER_DIV1 + case top/2 <= maxTop: + prescaler = sam.TCC_CTRLA_PRESCALER_DIV2 + top = top / 2 + case top/4 <= maxTop: + prescaler = sam.TCC_CTRLA_PRESCALER_DIV4 + top = top / 4 + case top/8 <= maxTop: + prescaler = sam.TCC_CTRLA_PRESCALER_DIV8 + top = top / 8 + case top/16 <= maxTop: + prescaler = sam.TCC_CTRLA_PRESCALER_DIV16 + top = top / 16 + case top/64 <= maxTop: + prescaler = sam.TCC_CTRLA_PRESCALER_DIV64 + top = top / 64 + case top/256 <= maxTop: + prescaler = sam.TCC_CTRLA_PRESCALER_DIV256 + top = top / 256 + case top/1024 <= maxTop: + prescaler = sam.TCC_CTRLA_PRESCALER_DIV1024 + top = top / 1024 + default: + return ErrPWMPeriodTooLong + } + tcc.timer().CTRLA.Set((tcc.timer().CTRLA.Get() &^ sam.TCC_CTRLA_PRESCALER_Msk) | (prescaler << sam.TCC_CTRLA_PRESCALER_Pos)) + } else { + // Do not update the prescaler, but use the already-configured + // prescaler. This is the normal SetPeriod case, where the prescaler + // must not be changed. + prescaler := (tcc.timer().CTRLA.Get() & sam.TCC_CTRLA_PRESCALER_Msk) >> sam.TCC_CTRLA_PRESCALER_Pos + switch prescaler { + case sam.TCC_CTRLA_PRESCALER_DIV1: + top /= 1 // no-op + case sam.TCC_CTRLA_PRESCALER_DIV2: + top /= 2 + case sam.TCC_CTRLA_PRESCALER_DIV4: + top /= 4 + case sam.TCC_CTRLA_PRESCALER_DIV8: + top /= 8 + case sam.TCC_CTRLA_PRESCALER_DIV16: + top /= 16 + case sam.TCC_CTRLA_PRESCALER_DIV64: + top /= 64 + case sam.TCC_CTRLA_PRESCALER_DIV256: + top /= 256 + case sam.TCC_CTRLA_PRESCALER_DIV1024: + top /= 1024 + default: + // unreachable + } + if top > maxTop { + return ErrPWMPeriodTooLong + } + } + + // Set the period (the counter top). + tcc.timer().PER.Set(uint32(top) - 1) + + // Wait for synchronization of CTRLA.PRESCALER and PER registers. + for tcc.timer().SYNCBUSY.Get() != 0 { + } + + return nil +} + +// Top returns the current counter top, for use in duty cycle calculation. It +// will only change with a call to Configure or SetPeriod, otherwise it is +// constant. +// +// The value returned here is hardware dependent. In general, it's best to treat +// it as an opaque value that can be divided by some number and passed to +// tcc.Set (see tcc.Set for more information). +func (tcc *TCC) Top() uint32 { + return tcc.timer().PER.Get() + 1 +} + +// Counter returns the current counter value of the timer in this TCC +// peripheral. It may be useful for debugging. +func (tcc *TCC) Counter() uint32 { + tcc.timer().CTRLBSET.Set(sam.TCC_CTRLBSET_CMD_READSYNC << sam.TCC_CTRLBSET_CMD_Pos) + for tcc.timer().SYNCBUSY.Get() != 0 { + } + return tcc.timer().COUNT.Get() +} + +// Constants that encode a TCC number and WO number together in a single byte. +const ( + pinTCC0 = 1 << 4 // keep the value 0 usable as "no value" + pinTCC1 = 2 << 4 + pinTCC2 = 3 << 4 + pinTCC3 = 4 << 4 + pinTCC4 = 5 << 4 + pinTCC0_0 = pinTCC0 | 0 + pinTCC0_1 = pinTCC0 | 1 + pinTCC0_2 = pinTCC0 | 2 + pinTCC0_3 = pinTCC0 | 3 + pinTCC0_4 = pinTCC0 | 4 + pinTCC0_5 = pinTCC0 | 5 + pinTCC0_6 = pinTCC0 | 6 + pinTCC1_0 = pinTCC1 | 0 + pinTCC1_2 = pinTCC1 | 2 + pinTCC1_4 = pinTCC1 | 4 + pinTCC1_6 = pinTCC1 | 6 + pinTCC2_0 = pinTCC2 | 0 + pinTCC2_2 = pinTCC2 | 2 + pinTCC3_0 = pinTCC3 | 0 + pinTCC4_0 = pinTCC4 | 0 +) + +// This is a copy of columns F and G (the TCC columns) of table 6-1 in the +// datasheet: +// http://ww1.microchip.com/downloads/en/DeviceDoc/60001507E.pdf +// For example, "TCC0/WO[2]" is converted to pinTCC0_2. +// Only the even pin numbers are stored here. The odd pin numbers are left out, +// because their PWM output can be determined from the even number: just add one +// to the wave output (WO) number. +var pinTimerMapping = [...]struct{ F, G uint8 }{ + // page 33 + PC04 / 2: {pinTCC0_0, 0}, + PA08 / 2: {pinTCC0_0, pinTCC1_4}, + PA10 / 2: {pinTCC0_2, pinTCC1_6}, + PB10 / 2: {pinTCC0_4, pinTCC1_0}, + PB12 / 2: {pinTCC3_0, pinTCC0_0}, + PB14 / 2: {pinTCC4_0, pinTCC0_2}, + PD08 / 2: {pinTCC0_1, 0}, + PD10 / 2: {pinTCC0_3, 0}, + PD12 / 2: {pinTCC0_5, 0}, + PC10 / 2: {pinTCC0_0, pinTCC1_4}, + // page 34 + PC12 / 2: {pinTCC0_2, pinTCC1_6}, + PC14 / 2: {pinTCC0_4, pinTCC1_0}, + PA12 / 2: {pinTCC0_6, pinTCC1_2}, + PA14 / 2: {pinTCC2_0, pinTCC1_2}, + PA16 / 2: {pinTCC1_0, pinTCC0_4}, + PA18 / 2: {pinTCC1_2, pinTCC0_6}, + PC16 / 2: {pinTCC0_0, 0}, + PC18 / 2: {pinTCC0_2, 0}, + PC20 / 2: {pinTCC0_4, 0}, + PC22 / 2: {pinTCC0_6, 0}, + PD20 / 2: {pinTCC1_0, 0}, + PB16 / 2: {pinTCC3_0, pinTCC0_4}, + PB18 / 2: {pinTCC1_0, 0}, + // page 35 + PB20 / 2: {pinTCC1_2, 0}, + PA20 / 2: {pinTCC1_4, pinTCC0_0}, + PA22 / 2: {pinTCC1_6, pinTCC0_2}, + PA24 / 2: {pinTCC2_2, 0}, + PB26 / 2: {pinTCC1_2, 0}, + PB28 / 2: {pinTCC1_4, 0}, + PA30 / 2: {pinTCC2_0, 0}, + // page 36 + PB30 / 2: {pinTCC4_0, pinTCC0_6}, + PB02 / 2: {pinTCC2_2, 0}, +} + +// findPinTimerMapping returns the pin mode (PinTCCF or PinTCCG) and the channel +// number for a given timer and pin. A zero PinMode is returned if no mapping +// could be found. +func findPinTimerMapping(timer uint8, pin Pin) (PinMode, uint8) { + if int(pin/2) >= len(pinTimerMapping) { + return 0, 0 // invalid pin number + } + + mapping := pinTimerMapping[pin/2] + + // Check for column F in the datasheet. + if mapping.F>>4-1 == timer { + return PinTCCF, mapping.F&0x0f + uint8(pin)&1 + } + + // Check for column G in the datasheet. + if mapping.G>>4-1 == timer { + return PinTCCG, mapping.G&0x0f + uint8(pin)&1 + } + + // Nothing found. + return 0, 0 +} + +// Channel returns a PWM channel for the given pin. Note that one channel may be +// shared between multiple pins, and so will have the same duty cycle. If this +// is not desirable, look for a different TCC or consider using a different pin. +func (tcc *TCC) Channel(pin Pin) (uint8, error) { + pinMode, woOutput := findPinTimerMapping(tcc.timerNum(), pin) + + if pinMode == 0 { + // No pin could be found. + return 0, ErrInvalidOutputPin + } + + // Convert from waveform output to channel, assuming WEXCTRL.OTMX equals 0. + // See table 49-4 "Output Matrix Channel Pin Routing Configuration" on page + // 1829 of the datasheet. + // The number of channels varies by TCC instance, hence the need to switch + // over them. For TCC2-4 the number of channels is equal to the number of + // waveform outputs, so the WO number maps directly to the channel number. + // For TCC0 and TCC1 this is not the case so they will need some special + // handling. + channel := woOutput + switch tcc.timer() { + case sam.TCC0: + channel = woOutput % 6 + case sam.TCC1: + channel = woOutput % 4 + } + + // Enable the port multiplexer for pin + pin.setPinCfg(sam.PORT_GROUP_PINCFG_PMUXEN) + + // Connect timer/mux to pin. + if pin&1 > 0 { + // odd pin, so save the even pins + val := pin.getPMux() & sam.PORT_GROUP_PMUX_PMUXE_Msk + pin.setPMux(val | uint8(pinMode<> 4) + dac.syncDAC() + return nil +} + +func (dac DAC) syncDAC() { + switch dac.Channel { + case 0: + for !sam.DAC.STATUS.HasBits(sam.DAC_STATUS_EOC0) { + } + for sam.DAC.SYNCBUSY.HasBits(sam.DAC_SYNCBUSY_DATA0) { + } + default: + for !sam.DAC.STATUS.HasBits(sam.DAC_STATUS_EOC1) { + } + for sam.DAC.SYNCBUSY.HasBits(sam.DAC_SYNCBUSY_DATA1) { + } + } +} + +// GetRNG returns 32 bits of cryptographically secure random data +func GetRNG() (uint32, error) { + if !sam.MCLK.APBCMASK.HasBits(sam.MCLK_APBCMASK_TRNG_) { + // Turn on clock for TRNG + sam.MCLK.APBCMASK.SetBits(sam.MCLK_APBCMASK_TRNG_) + + // enable + sam.TRNG.CTRLA.Set(sam.TRNG_CTRLA_ENABLE) + } + for !sam.TRNG.INTFLAG.HasBits(sam.TRNG_INTFLAG_DATARDY) { + } + ret := sam.TRNG.DATA.Get() + return ret, nil +} + +// Flash related code +const memoryStart = 0x0 + +// compile-time check for ensuring we fulfill BlockDevice interface +var _ BlockDevice = flashBlockDevice{} + +var Flash flashBlockDevice + +type flashBlockDevice struct { + initComplete bool +} + +// ReadAt reads the given number of bytes from the block device. +func (f flashBlockDevice) ReadAt(p []byte, off int64) (n int, err error) { + if FlashDataStart()+uintptr(off)+uintptr(len(p)) > FlashDataEnd() { + return 0, errFlashCannotReadPastEOF + } + + waitWhileFlashBusy() + + data := unsafe.Slice((*byte)(unsafe.Add(unsafe.Pointer(FlashDataStart()), uintptr(off))), len(p)) + copy(p, data) + + return len(p), nil +} + +// WriteAt writes the given number of bytes to the block device. +// Data is written to the page buffer in 4-byte chunks, then saved to flash memory. +// See SAM-D5x-E5x-Family-Data-Sheet-DS60001507.pdf page 591-592. +// If the length of p is not long enough it will be padded with 0xFF bytes. +// This method assumes that the destination is already erased. +func (f flashBlockDevice) WriteAt(p []byte, off int64) (n int, err error) { + if FlashDataStart()+uintptr(off)+uintptr(len(p)) > FlashDataEnd() { + return 0, errFlashCannotWritePastEOF + } + + address := FlashDataStart() + uintptr(off) + padded := flashPad(p, int(f.WriteBlockSize())) + + settings := disableFlashCache() + defer restoreFlashCache(settings) + + waitWhileFlashBusy() + + sam.NVMCTRL.CTRLB.Set(sam.NVMCTRL_CTRLB_CMD_PBC | (sam.NVMCTRL_CTRLB_CMDEX_KEY << sam.NVMCTRL_CTRLB_CMDEX_Pos)) + + waitWhileFlashBusy() + + for j := 0; j < len(padded); j += int(f.WriteBlockSize()) { + // page buffer is 512 bytes long, but only 4 bytes can be written at once + for k := 0; k < int(f.WriteBlockSize()); k += 4 { + *(*uint32)(unsafe.Pointer(address + uintptr(k))) = binary.LittleEndian.Uint32(padded[j+k : j+k+4]) + } + + sam.NVMCTRL.SetADDR(uint32(address)) + sam.NVMCTRL.CTRLB.Set(sam.NVMCTRL_CTRLB_CMD_WP | (sam.NVMCTRL_CTRLB_CMDEX_KEY << sam.NVMCTRL_CTRLB_CMDEX_Pos)) + + waitWhileFlashBusy() + + if err := checkFlashError(); err != nil { + return j, err + } + + address += uintptr(f.WriteBlockSize()) + } + + return len(padded), nil +} + +// Size returns the number of bytes in this block device. +func (f flashBlockDevice) Size() int64 { + return int64(FlashDataEnd() - FlashDataStart()) +} + +const writeBlockSize = 512 + +// WriteBlockSize returns the block size in which data can be written to +// memory. It can be used by a client to optimize writes, non-aligned writes +// should always work correctly. +func (f flashBlockDevice) WriteBlockSize() int64 { + return writeBlockSize +} + +const eraseBlockSizeValue = 8192 + +func eraseBlockSize() int64 { + return eraseBlockSizeValue +} + +// EraseBlockSize returns the smallest erasable area on this particular chip +// in bytes. This is used for the block size in EraseBlocks. +func (f flashBlockDevice) EraseBlockSize() int64 { + return eraseBlockSize() +} + +// EraseBlocks erases the given number of blocks. An implementation may +// transparently coalesce ranges of blocks into larger bundles if the chip +// supports this. The start and len parameters are in block numbers, use +// EraseBlockSize to map addresses to blocks. +func (f flashBlockDevice) EraseBlocks(start, len int64) error { + address := FlashDataStart() + uintptr(start*f.EraseBlockSize()) + + settings := disableFlashCache() + defer restoreFlashCache(settings) + + waitWhileFlashBusy() + + for i := start; i < start+len; i++ { + sam.NVMCTRL.SetADDR(uint32(address)) + sam.NVMCTRL.CTRLB.Set(sam.NVMCTRL_CTRLB_CMD_EB | (sam.NVMCTRL_CTRLB_CMDEX_KEY << sam.NVMCTRL_CTRLB_CMDEX_Pos)) + + waitWhileFlashBusy() + + if err := checkFlashError(); err != nil { + return err + } + + address += uintptr(f.EraseBlockSize()) + } + + return nil +} + +func disableFlashCache() uint16 { + settings := sam.NVMCTRL.CTRLA.Get() + + // disable caches + sam.NVMCTRL.SetCTRLA_CACHEDIS0(1) + sam.NVMCTRL.SetCTRLA_CACHEDIS1(1) + + waitWhileFlashBusy() + + return settings +} + +func restoreFlashCache(settings uint16) { + sam.NVMCTRL.CTRLA.Set(settings) + waitWhileFlashBusy() +} + +func waitWhileFlashBusy() { + for sam.NVMCTRL.GetSTATUS_READY() != sam.NVMCTRL_STATUS_READY { + } +} + +var ( + errFlashADDRE = errors.New("errFlashADDRE") + errFlashPROGE = errors.New("errFlashPROGE") + errFlashLOCKE = errors.New("errFlashLOCKE") + errFlashECCSE = errors.New("errFlashECCSE") + errFlashNVME = errors.New("errFlashNVME") + errFlashSEESOVF = errors.New("errFlashSEESOVF") +) + +func checkFlashError() error { + switch { + case sam.NVMCTRL.GetINTENSET_ADDRE() != 0: + return errFlashADDRE + case sam.NVMCTRL.GetINTENSET_PROGE() != 0: + return errFlashPROGE + case sam.NVMCTRL.GetINTENSET_LOCKE() != 0: + return errFlashLOCKE + case sam.NVMCTRL.GetINTENSET_ECCSE() != 0: + return errFlashECCSE + case sam.NVMCTRL.GetINTENSET_NVME() != 0: + return errFlashNVME + case sam.NVMCTRL.GetINTENSET_SEESOVF() != 0: + return errFlashSEESOVF + } + + return nil +} + +// Watchdog provides access to the hardware watchdog available +// in the SAMD51. +var Watchdog = &watchdogImpl{} + +const ( + // WatchdogMaxTimeout in milliseconds (16s) + WatchdogMaxTimeout = (16384 * 1000) / 1024 // CYC16384/1024kHz +) + +type watchdogImpl struct{} + +// Configure the watchdog. +// +// This method should not be called after the watchdog is started and on +// some platforms attempting to reconfigure after starting the watchdog +// is explicitly forbidden / will not work. +func (wd *watchdogImpl) Configure(config WatchdogConfig) error { + // 1.024kHz clock + cycles := int((int64(config.TimeoutMillis) * 1024) / 1000) + + // period is expressed as a power-of-two, starting at 8 / 1024ths of a second + period := uint8(0) + cfgCycles := 8 + for cfgCycles < cycles { + period++ + cfgCycles <<= 1 + + if period >= 0xB { + break + } + } + + sam.WDT.CONFIG.Set(period << sam.WDT_CONFIG_PER_Pos) + + return nil +} + +// Starts the watchdog. +func (wd *watchdogImpl) Start() error { + sam.WDT.CTRLA.SetBits(sam.WDT_CTRLA_ENABLE) + return nil +} + +// Update the watchdog, indicating that `source` is healthy. +func (wd *watchdogImpl) Update() { + sam.WDT.CLEAR.Set(sam.WDT_CLEAR_CLEAR_KEY) +} diff --git a/emb/machine/machine_atsamd51_usb.go b/emb/machine/machine_atsamd51_usb.go new file mode 100644 index 00000000..a95089f7 --- /dev/null +++ b/emb/machine/machine_atsamd51_usb.go @@ -0,0 +1,495 @@ +//go:build (sam && atsamd51) || (sam && atsame5x) + +package machine + +import ( + "device/sam" + "machine/usb" + "runtime/interrupt" + "unsafe" +) + +const ( + // these are SAMD51 specific. + usb_DEVICE_PCKSIZE_BYTE_COUNT_Pos = 0 + usb_DEVICE_PCKSIZE_BYTE_COUNT_Mask = 0x3FFF + + usb_DEVICE_PCKSIZE_SIZE_Pos = 28 + usb_DEVICE_PCKSIZE_SIZE_Mask = 0x7 + + usb_DEVICE_PCKSIZE_MULTI_PACKET_SIZE_Pos = 14 + usb_DEVICE_PCKSIZE_MULTI_PACKET_SIZE_Mask = 0x3FFF + + NumberOfUSBEndpoints = 8 +) + +var ( + endPoints = []uint32{ + usb.CONTROL_ENDPOINT: usb.ENDPOINT_TYPE_CONTROL, + usb.CDC_ENDPOINT_ACM: (usb.ENDPOINT_TYPE_INTERRUPT | usb.EndpointIn), + usb.CDC_ENDPOINT_OUT: (usb.ENDPOINT_TYPE_BULK | usb.EndpointOut), + usb.CDC_ENDPOINT_IN: (usb.ENDPOINT_TYPE_BULK | usb.EndpointIn), + usb.HID_ENDPOINT_IN: (usb.ENDPOINT_TYPE_DISABLE), // Interrupt In + usb.HID_ENDPOINT_OUT: (usb.ENDPOINT_TYPE_DISABLE), // Interrupt Out + usb.MIDI_ENDPOINT_IN: (usb.ENDPOINT_TYPE_DISABLE), // Bulk In + usb.MIDI_ENDPOINT_OUT: (usb.ENDPOINT_TYPE_DISABLE), // Bulk Out + } +) + +// Configure the USB peripheral. The config is here for compatibility with the UART interface. +func (dev *USBDevice) Configure(config UARTConfig) { + if dev.initcomplete { + return + } + + // reset USB interface + sam.USB_DEVICE.CTRLA.SetBits(sam.USB_DEVICE_CTRLA_SWRST) + for sam.USB_DEVICE.SYNCBUSY.HasBits(sam.USB_DEVICE_SYNCBUSY_SWRST) || + sam.USB_DEVICE.SYNCBUSY.HasBits(sam.USB_DEVICE_SYNCBUSY_ENABLE) { + } + + sam.USB_DEVICE.DESCADD.Set(uint32(uintptr(unsafe.Pointer(&usbEndpointDescriptors)))) + + // configure pins + USBCDC_DM_PIN.Configure(PinConfig{Mode: PinCom}) + USBCDC_DP_PIN.Configure(PinConfig{Mode: PinCom}) + + // performs pad calibration from store fuses + handlePadCalibration() + + // run in standby + sam.USB_DEVICE.CTRLA.SetBits(sam.USB_DEVICE_CTRLA_RUNSTDBY) + + // set full speed + sam.USB_DEVICE.CTRLB.SetBits(sam.USB_DEVICE_CTRLB_SPDCONF_FS << sam.USB_DEVICE_CTRLB_SPDCONF_Pos) + + // attach + sam.USB_DEVICE.CTRLB.ClearBits(sam.USB_DEVICE_CTRLB_DETACH) + + // enable interrupt for end of reset + sam.USB_DEVICE.INTENSET.SetBits(sam.USB_DEVICE_INTENSET_EORST) + + // enable interrupt for start of frame + sam.USB_DEVICE.INTENSET.SetBits(sam.USB_DEVICE_INTENSET_SOF) + + // enable USB + sam.USB_DEVICE.CTRLA.SetBits(sam.USB_DEVICE_CTRLA_ENABLE) + + // enable IRQ + interrupt.New(sam.IRQ_USB_OTHER, handleUSBIRQ).Enable() + interrupt.New(sam.IRQ_USB_SOF_HSOF, handleUSBIRQ).Enable() + interrupt.New(sam.IRQ_USB_TRCPT0, handleUSBIRQ).Enable() + interrupt.New(sam.IRQ_USB_TRCPT1, handleUSBIRQ).Enable() + + dev.initcomplete = true +} + +func handlePadCalibration() { + // Load Pad Calibration data from non-volatile memory + // This requires registers that are not included in the SVD file. + // Modeled after defines from samd21g18a.h and nvmctrl.h: + // + // #define NVMCTRL_OTP4 0x00806020 + // + // #define USB_FUSES_TRANSN_ADDR (NVMCTRL_OTP4 + 4) + // #define USB_FUSES_TRANSN_Pos 13 /**< \brief (NVMCTRL_OTP4) USB pad Transn calibration */ + // #define USB_FUSES_TRANSN_Msk (0x1Fu << USB_FUSES_TRANSN_Pos) + // #define USB_FUSES_TRANSN(value) ((USB_FUSES_TRANSN_Msk & ((value) << USB_FUSES_TRANSN_Pos))) + + // #define USB_FUSES_TRANSP_ADDR (NVMCTRL_OTP4 + 4) + // #define USB_FUSES_TRANSP_Pos 18 /**< \brief (NVMCTRL_OTP4) USB pad Transp calibration */ + // #define USB_FUSES_TRANSP_Msk (0x1Fu << USB_FUSES_TRANSP_Pos) + // #define USB_FUSES_TRANSP(value) ((USB_FUSES_TRANSP_Msk & ((value) << USB_FUSES_TRANSP_Pos))) + + // #define USB_FUSES_TRIM_ADDR (NVMCTRL_OTP4 + 4) + // #define USB_FUSES_TRIM_Pos 23 /**< \brief (NVMCTRL_OTP4) USB pad Trim calibration */ + // #define USB_FUSES_TRIM_Msk (0x7u << USB_FUSES_TRIM_Pos) + // #define USB_FUSES_TRIM(value) ((USB_FUSES_TRIM_Msk & ((value) << USB_FUSES_TRIM_Pos))) + // + fuse := *(*uint32)(unsafe.Pointer(uintptr(0x00806020) + 4)) + calibTransN := uint16(fuse>>13) & uint16(0x1f) + calibTransP := uint16(fuse>>18) & uint16(0x1f) + calibTrim := uint16(fuse>>23) & uint16(0x7) + + if calibTransN == 0x1f { + calibTransN = 5 + } + sam.USB_DEVICE.PADCAL.SetBits(calibTransN << sam.USB_DEVICE_PADCAL_TRANSN_Pos) + + if calibTransP == 0x1f { + calibTransP = 29 + } + sam.USB_DEVICE.PADCAL.SetBits(calibTransP << sam.USB_DEVICE_PADCAL_TRANSP_Pos) + + if calibTrim == 0x7 { + calibTrim = 3 + } + sam.USB_DEVICE.PADCAL.SetBits(calibTrim << sam.USB_DEVICE_PADCAL_TRIM_Pos) +} + +func handleUSBIRQ(intr interrupt.Interrupt) { + // reset all interrupt flags + flags := sam.USB_DEVICE.INTFLAG.Get() + sam.USB_DEVICE.INTFLAG.Set(flags) + + // End of reset + if (flags & sam.USB_DEVICE_INTFLAG_EORST) > 0 { + // Configure control endpoint + initEndpoint(0, usb.ENDPOINT_TYPE_CONTROL) + + usbConfiguration = 0 + + // ack the End-Of-Reset interrupt + sam.USB_DEVICE.INTFLAG.Set(sam.USB_DEVICE_INTFLAG_EORST) + } + + // Start of frame + if (flags & sam.USB_DEVICE_INTFLAG_SOF) > 0 { + // if you want to blink LED showing traffic, this would be the place... + } + + // Endpoint 0 Setup interrupt + if getEPINTFLAG(0)&sam.USB_DEVICE_ENDPOINT_EPINTFLAG_RXSTP > 0 { + // ack setup received + setEPINTFLAG(0, sam.USB_DEVICE_ENDPOINT_EPINTFLAG_RXSTP) + + // parse setup + setup := usb.NewSetup(udd_ep_out_cache_buffer[0][:]) + + // Clear the Bank 0 ready flag on Control OUT + usbEndpointDescriptors[0].DeviceDescBank[0].ADDR.Set(uint32(uintptr(unsafe.Pointer(&udd_ep_out_cache_buffer[0])))) + usbEndpointDescriptors[0].DeviceDescBank[0].PCKSIZE.ClearBits(usb_DEVICE_PCKSIZE_BYTE_COUNT_Mask << usb_DEVICE_PCKSIZE_BYTE_COUNT_Pos) + setEPSTATUSCLR(0, sam.USB_DEVICE_ENDPOINT_EPSTATUSCLR_BK0RDY) + + ok := false + if (setup.BmRequestType & usb.REQUEST_TYPE) == usb.REQUEST_STANDARD { + // Standard Requests + ok = handleStandardSetup(setup) + } else { + // Class Interface Requests + if setup.WIndex < uint16(len(usbSetupHandler)) && usbSetupHandler[setup.WIndex] != nil { + ok = usbSetupHandler[setup.WIndex](setup) + } + } + + if ok { + // set Bank1 ready + setEPSTATUSSET(0, sam.USB_DEVICE_ENDPOINT_EPSTATUSSET_BK1RDY) + } else { + // Stall endpoint + setEPSTATUSSET(0, sam.USB_DEVICE_ENDPOINT_EPINTFLAG_STALL1) + } + + if getEPINTFLAG(0)&sam.USB_DEVICE_ENDPOINT_EPINTFLAG_STALL1 > 0 { + // ack the stall + setEPINTFLAG(0, sam.USB_DEVICE_ENDPOINT_EPINTFLAG_STALL1) + + // clear stall request + setEPINTENCLR(0, sam.USB_DEVICE_ENDPOINT_EPINTENCLR_STALL1) + } + } + + // Now the actual transfer handlers, ignore endpoint number 0 (setup) + var i uint32 + for i = 1; i < uint32(len(endPoints)); i++ { + // Check if endpoint has a pending interrupt + epFlags := getEPINTFLAG(i) + setEPINTFLAG(i, epFlags) + if (epFlags & sam.USB_DEVICE_ENDPOINT_EPINTFLAG_TRCPT0) > 0 { + buf := handleEndpointRx(i) + if usbRxHandler[i] == nil || usbRxHandler[i](buf) { + AckUsbOutTransfer(i) + } + } else if (epFlags & sam.USB_DEVICE_ENDPOINT_EPINTFLAG_TRCPT1) > 0 { + if usbTxHandler[i] != nil { + usbTxHandler[i]() + } + } + } +} + +func initEndpoint(ep, config uint32) { + switch config { + case usb.ENDPOINT_TYPE_INTERRUPT | usb.EndpointIn: + // set packet size + usbEndpointDescriptors[ep].DeviceDescBank[1].PCKSIZE.SetBits(epPacketSize(64) << usb_DEVICE_PCKSIZE_SIZE_Pos) + + // set data buffer address + usbEndpointDescriptors[ep].DeviceDescBank[1].ADDR.Set(uint32(uintptr(unsafe.Pointer(&udd_ep_in_cache_buffer[ep])))) + + // set endpoint type + setEPCFG(ep, ((usb.ENDPOINT_TYPE_INTERRUPT + 1) << sam.USB_DEVICE_ENDPOINT_EPCFG_EPTYPE1_Pos)) + + setEPINTENSET(ep, sam.USB_DEVICE_ENDPOINT_EPINTENSET_TRCPT1) + + case usb.ENDPOINT_TYPE_BULK | usb.EndpointOut: + // set packet size + usbEndpointDescriptors[ep].DeviceDescBank[0].PCKSIZE.SetBits(epPacketSize(64) << usb_DEVICE_PCKSIZE_SIZE_Pos) + + // set data buffer address + usbEndpointDescriptors[ep].DeviceDescBank[0].ADDR.Set(uint32(uintptr(unsafe.Pointer(&udd_ep_out_cache_buffer[ep])))) + + // set endpoint type + setEPCFG(ep, ((usb.ENDPOINT_TYPE_BULK + 1) << sam.USB_DEVICE_ENDPOINT_EPCFG_EPTYPE0_Pos)) + + // receive interrupts when current transfer complete + setEPINTENSET(ep, sam.USB_DEVICE_ENDPOINT_EPINTENSET_TRCPT0) + + // set byte count to zero, we have not received anything yet + usbEndpointDescriptors[ep].DeviceDescBank[0].PCKSIZE.ClearBits(usb_DEVICE_PCKSIZE_BYTE_COUNT_Mask << usb_DEVICE_PCKSIZE_BYTE_COUNT_Pos) + + // ready for next transfer + setEPSTATUSCLR(ep, sam.USB_DEVICE_ENDPOINT_EPSTATUSCLR_BK0RDY) + + case usb.ENDPOINT_TYPE_INTERRUPT | usb.EndpointOut: + // set packet size + usbEndpointDescriptors[ep].DeviceDescBank[0].PCKSIZE.SetBits(epPacketSize(64) << usb_DEVICE_PCKSIZE_SIZE_Pos) + + // set data buffer address + usbEndpointDescriptors[ep].DeviceDescBank[0].ADDR.Set(uint32(uintptr(unsafe.Pointer(&udd_ep_out_cache_buffer[ep])))) + + // set endpoint type + setEPCFG(ep, ((usb.ENDPOINT_TYPE_INTERRUPT + 1) << sam.USB_DEVICE_ENDPOINT_EPCFG_EPTYPE0_Pos)) + + // receive interrupts when current transfer complete + setEPINTENSET(ep, sam.USB_DEVICE_ENDPOINT_EPINTENSET_TRCPT0) + + // set byte count to zero, we have not received anything yet + usbEndpointDescriptors[ep].DeviceDescBank[0].PCKSIZE.ClearBits(usb_DEVICE_PCKSIZE_BYTE_COUNT_Mask << usb_DEVICE_PCKSIZE_BYTE_COUNT_Pos) + + // ready for next transfer + setEPSTATUSCLR(ep, sam.USB_DEVICE_ENDPOINT_EPSTATUSCLR_BK0RDY) + + case usb.ENDPOINT_TYPE_BULK | usb.EndpointIn: + // set packet size + usbEndpointDescriptors[ep].DeviceDescBank[1].PCKSIZE.SetBits(epPacketSize(64) << usb_DEVICE_PCKSIZE_SIZE_Pos) + + // set data buffer address + usbEndpointDescriptors[ep].DeviceDescBank[1].ADDR.Set(uint32(uintptr(unsafe.Pointer(&udd_ep_in_cache_buffer[ep])))) + + // set endpoint type + setEPCFG(ep, ((usb.ENDPOINT_TYPE_BULK + 1) << sam.USB_DEVICE_ENDPOINT_EPCFG_EPTYPE1_Pos)) + + // NAK on endpoint IN, the bank is not yet filled in. + setEPSTATUSCLR(ep, sam.USB_DEVICE_ENDPOINT_EPSTATUSCLR_BK1RDY) + + setEPINTENSET(ep, sam.USB_DEVICE_ENDPOINT_EPINTENSET_TRCPT1) + + case usb.ENDPOINT_TYPE_CONTROL: + // Control OUT + // set packet size + usbEndpointDescriptors[ep].DeviceDescBank[0].PCKSIZE.SetBits(epPacketSize(64) << usb_DEVICE_PCKSIZE_SIZE_Pos) + + // set data buffer address + usbEndpointDescriptors[ep].DeviceDescBank[0].ADDR.Set(uint32(uintptr(unsafe.Pointer(&udd_ep_out_cache_buffer[ep])))) + + // set endpoint type + setEPCFG(ep, getEPCFG(ep)|((usb.ENDPOINT_TYPE_CONTROL+1)<> + usb_DEVICE_PCKSIZE_BYTE_COUNT_Pos) & usb_DEVICE_PCKSIZE_BYTE_COUNT_Mask) + + if bytesread != cdcLineInfoSize { + return b, ErrUSBBytesRead + } + + copy(b[:7], udd_ep_out_cache_buffer[0][:7]) + + return b, nil +} + +func handleEndpointRx(ep uint32) []byte { + // get data + count := int((usbEndpointDescriptors[ep].DeviceDescBank[0].PCKSIZE.Get() >> + usb_DEVICE_PCKSIZE_BYTE_COUNT_Pos) & usb_DEVICE_PCKSIZE_BYTE_COUNT_Mask) + + return udd_ep_out_cache_buffer[ep][:count] +} + +// AckUsbOutTransfer is called to acknowledge the completion of a USB OUT transfer. +func AckUsbOutTransfer(ep uint32) { + // set byte count to zero + usbEndpointDescriptors[ep].DeviceDescBank[0].PCKSIZE.ClearBits(usb_DEVICE_PCKSIZE_BYTE_COUNT_Mask << usb_DEVICE_PCKSIZE_BYTE_COUNT_Pos) + + // set multi packet size to 64 + usbEndpointDescriptors[ep].DeviceDescBank[0].PCKSIZE.SetBits(64 << usb_DEVICE_PCKSIZE_MULTI_PACKET_SIZE_Pos) + + // set ready for next data + setEPSTATUSCLR(ep, sam.USB_DEVICE_ENDPOINT_EPSTATUSCLR_BK0RDY) +} + +func SendZlp() { + usbEndpointDescriptors[0].DeviceDescBank[1].PCKSIZE.ClearBits(usb_DEVICE_PCKSIZE_BYTE_COUNT_Mask << usb_DEVICE_PCKSIZE_BYTE_COUNT_Pos) +} + +func epPacketSize(size uint16) uint32 { + switch size { + case 8: + return 0 + case 16: + return 1 + case 32: + return 2 + case 64: + return 3 + case 128: + return 4 + case 256: + return 5 + case 512: + return 6 + case 1023: + return 7 + default: + return 0 + } +} + +func getEPCFG(ep uint32) uint8 { + return sam.USB_DEVICE.DEVICE_ENDPOINT[ep].EPCFG.Get() +} + +func setEPCFG(ep uint32, val uint8) { + sam.USB_DEVICE.DEVICE_ENDPOINT[ep].EPCFG.Set(val) +} + +func setEPSTATUSCLR(ep uint32, val uint8) { + sam.USB_DEVICE.DEVICE_ENDPOINT[ep].EPSTATUSCLR.Set(val) +} + +func setEPSTATUSSET(ep uint32, val uint8) { + sam.USB_DEVICE.DEVICE_ENDPOINT[ep].EPSTATUSSET.Set(val) +} + +func getEPSTATUS(ep uint32) uint8 { + return sam.USB_DEVICE.DEVICE_ENDPOINT[ep].EPSTATUS.Get() +} + +func getEPINTFLAG(ep uint32) uint8 { + return sam.USB_DEVICE.DEVICE_ENDPOINT[ep].EPINTFLAG.Get() +} + +func setEPINTFLAG(ep uint32, val uint8) { + sam.USB_DEVICE.DEVICE_ENDPOINT[ep].EPINTFLAG.Set(val) +} + +func setEPINTENCLR(ep uint32, val uint8) { + sam.USB_DEVICE.DEVICE_ENDPOINT[ep].EPINTENCLR.Set(val) +} + +func setEPINTENSET(ep uint32, val uint8) { + sam.USB_DEVICE.DEVICE_ENDPOINT[ep].EPINTENSET.Set(val) +} diff --git a/emb/machine/machine_atsamd51g19.go b/emb/machine/machine_atsamd51g19.go new file mode 100644 index 00000000..f223f6eb --- /dev/null +++ b/emb/machine/machine_atsamd51g19.go @@ -0,0 +1,98 @@ +//go:build sam && atsamd51 && atsamd51g19 + +// Peripheral abstraction layer for the atsamd51. +// +// Datasheet: +// http://ww1.microchip.com/downloads/en/DeviceDoc/60001507C.pdf +package machine + +import "device/sam" + +const HSRAM_SIZE = 0x00030000 + +var ( + sercomI2CM0 = &I2C{Bus: sam.SERCOM0_I2CM, SERCOM: 0} + sercomI2CM1 = &I2C{Bus: sam.SERCOM1_I2CM, SERCOM: 1} + sercomI2CM2 = &I2C{Bus: sam.SERCOM2_I2CM, SERCOM: 2} + sercomI2CM3 = &I2C{Bus: sam.SERCOM3_I2CM, SERCOM: 3} + sercomI2CM4 = &I2C{Bus: sam.SERCOM4_I2CM, SERCOM: 4} + sercomI2CM5 = &I2C{Bus: sam.SERCOM5_I2CM, SERCOM: 5} + + sercomSPIM0 = &SPI{Bus: sam.SERCOM0_SPIM, SERCOM: 0} + sercomSPIM1 = &SPI{Bus: sam.SERCOM1_SPIM, SERCOM: 1} + sercomSPIM2 = &SPI{Bus: sam.SERCOM2_SPIM, SERCOM: 2} + sercomSPIM3 = &SPI{Bus: sam.SERCOM3_SPIM, SERCOM: 3} + sercomSPIM4 = &SPI{Bus: sam.SERCOM4_SPIM, SERCOM: 4} + sercomSPIM5 = &SPI{Bus: sam.SERCOM5_SPIM, SERCOM: 5} +) + +// setSERCOMClockGenerator sets the GCLK for sercom +func setSERCOMClockGenerator(sercom uint8, gclk uint32) { + switch sercom { + case 0: + sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM0_CORE].ClearBits(sam.GCLK_PCHCTRL_CHEN) + sam.MCLK.APBAMASK.SetBits(sam.MCLK_APBAMASK_SERCOM0_) + sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM0_CORE].Set((gclk << sam.GCLK_PCHCTRL_GEN_Pos) | + sam.GCLK_PCHCTRL_CHEN) + case 1: + sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM1_CORE].ClearBits(sam.GCLK_PCHCTRL_CHEN) + sam.MCLK.APBAMASK.SetBits(sam.MCLK_APBAMASK_SERCOM1_) + sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM1_CORE].Set((gclk << sam.GCLK_PCHCTRL_GEN_Pos) | + sam.GCLK_PCHCTRL_CHEN) + case 2: + sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM2_CORE].ClearBits(sam.GCLK_PCHCTRL_CHEN) + sam.MCLK.APBBMASK.SetBits(sam.MCLK_APBBMASK_SERCOM2_) + sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM2_CORE].Set((gclk << sam.GCLK_PCHCTRL_GEN_Pos) | + sam.GCLK_PCHCTRL_CHEN) + case 3: + sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM3_CORE].ClearBits(sam.GCLK_PCHCTRL_CHEN) + sam.MCLK.APBBMASK.SetBits(sam.MCLK_APBBMASK_SERCOM3_) + sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM3_CORE].Set((gclk << sam.GCLK_PCHCTRL_GEN_Pos) | + sam.GCLK_PCHCTRL_CHEN) + case 4: + sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM4_CORE].ClearBits(sam.GCLK_PCHCTRL_CHEN) + sam.MCLK.APBDMASK.SetBits(sam.MCLK_APBDMASK_SERCOM4_) + sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM4_CORE].Set((gclk << sam.GCLK_PCHCTRL_GEN_Pos) | + sam.GCLK_PCHCTRL_CHEN) + case 5: + sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM5_CORE].ClearBits(sam.GCLK_PCHCTRL_CHEN) + sam.MCLK.APBDMASK.SetBits(sam.MCLK_APBDMASK_SERCOM5_) + sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM5_CORE].Set((gclk << sam.GCLK_PCHCTRL_GEN_Pos) | + sam.GCLK_PCHCTRL_CHEN) + } +} + +// This chip has three TCC peripherals, which have PWM as one feature. +var ( + TCC0 = (*TCC)(sam.TCC0) + TCC1 = (*TCC)(sam.TCC1) + TCC2 = (*TCC)(sam.TCC2) +) + +func (tcc *TCC) configureClock() { + // Turn on timer clocks used for TCC and use generic clock generator 0. + switch tcc.timer() { + case sam.TCC0: + sam.MCLK.APBBMASK.SetBits(sam.MCLK_APBBMASK_TCC0_) + sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_TCC0].Set((sam.GCLK_PCHCTRL_GEN_GCLK0 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) + case sam.TCC1: + sam.MCLK.APBBMASK.SetBits(sam.MCLK_APBBMASK_TCC1_) + sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_TCC1].Set((sam.GCLK_PCHCTRL_GEN_GCLK0 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) + case sam.TCC2: + sam.MCLK.APBCMASK.SetBits(sam.MCLK_APBCMASK_TCC2_) + sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_TCC2].Set((sam.GCLK_PCHCTRL_GEN_GCLK0 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) + } +} + +func (tcc *TCC) timerNum() uint8 { + switch tcc.timer() { + case sam.TCC0: + return 0 + case sam.TCC1: + return 1 + case sam.TCC2: + return 2 + default: + return 0x0f // should not happen + } +} diff --git a/emb/machine/machine_atsamd51j19.go b/emb/machine/machine_atsamd51j19.go new file mode 100644 index 00000000..640e1ef2 --- /dev/null +++ b/emb/machine/machine_atsamd51j19.go @@ -0,0 +1,110 @@ +//go:build sam && atsamd51 && atsamd51j19 + +// Peripheral abstraction layer for the atsamd51. +// +// Datasheet: +// http://ww1.microchip.com/downloads/en/DeviceDoc/SAM_D5xE5x_Family_Data_Sheet_DS60001507F.pdf +package machine + +import "device/sam" + +const HSRAM_SIZE = 0x00030000 + +var ( + sercomI2CM0 = &I2C{Bus: sam.SERCOM0_I2CM, SERCOM: 0} + sercomI2CM1 = &I2C{Bus: sam.SERCOM1_I2CM, SERCOM: 1} + sercomI2CM2 = &I2C{Bus: sam.SERCOM2_I2CM, SERCOM: 2} + sercomI2CM3 = &I2C{Bus: sam.SERCOM3_I2CM, SERCOM: 3} + sercomI2CM4 = &I2C{Bus: sam.SERCOM4_I2CM, SERCOM: 4} + sercomI2CM5 = &I2C{Bus: sam.SERCOM5_I2CM, SERCOM: 5} + + sercomSPIM0 = &SPI{Bus: sam.SERCOM0_SPIM, SERCOM: 0} + sercomSPIM1 = &SPI{Bus: sam.SERCOM1_SPIM, SERCOM: 1} + sercomSPIM2 = &SPI{Bus: sam.SERCOM2_SPIM, SERCOM: 2} + sercomSPIM3 = &SPI{Bus: sam.SERCOM3_SPIM, SERCOM: 3} + sercomSPIM4 = &SPI{Bus: sam.SERCOM4_SPIM, SERCOM: 4} + sercomSPIM5 = &SPI{Bus: sam.SERCOM5_SPIM, SERCOM: 5} +) + +// setSERCOMClockGenerator sets the GCLK for sercom +func setSERCOMClockGenerator(sercom uint8, gclk uint32) { + switch sercom { + case 0: + sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM0_CORE].ClearBits(sam.GCLK_PCHCTRL_CHEN) + sam.MCLK.APBAMASK.SetBits(sam.MCLK_APBAMASK_SERCOM0_) + sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM0_CORE].Set((gclk << sam.GCLK_PCHCTRL_GEN_Pos) | + sam.GCLK_PCHCTRL_CHEN) + case 1: + sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM1_CORE].ClearBits(sam.GCLK_PCHCTRL_CHEN) + sam.MCLK.APBAMASK.SetBits(sam.MCLK_APBAMASK_SERCOM1_) + sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM1_CORE].Set((gclk << sam.GCLK_PCHCTRL_GEN_Pos) | + sam.GCLK_PCHCTRL_CHEN) + case 2: + sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM2_CORE].ClearBits(sam.GCLK_PCHCTRL_CHEN) + sam.MCLK.APBBMASK.SetBits(sam.MCLK_APBBMASK_SERCOM2_) + sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM2_CORE].Set((gclk << sam.GCLK_PCHCTRL_GEN_Pos) | + sam.GCLK_PCHCTRL_CHEN) + case 3: + sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM3_CORE].ClearBits(sam.GCLK_PCHCTRL_CHEN) + sam.MCLK.APBBMASK.SetBits(sam.MCLK_APBBMASK_SERCOM3_) + sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM3_CORE].Set((gclk << sam.GCLK_PCHCTRL_GEN_Pos) | + sam.GCLK_PCHCTRL_CHEN) + case 4: + sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM4_CORE].ClearBits(sam.GCLK_PCHCTRL_CHEN) + sam.MCLK.APBDMASK.SetBits(sam.MCLK_APBDMASK_SERCOM4_) + sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM4_CORE].Set((gclk << sam.GCLK_PCHCTRL_GEN_Pos) | + sam.GCLK_PCHCTRL_CHEN) + case 5: + sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM5_CORE].ClearBits(sam.GCLK_PCHCTRL_CHEN) + sam.MCLK.APBDMASK.SetBits(sam.MCLK_APBDMASK_SERCOM5_) + sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM5_CORE].Set((gclk << sam.GCLK_PCHCTRL_GEN_Pos) | + sam.GCLK_PCHCTRL_CHEN) + } +} + +// This chip has five TCC peripherals, which have PWM as one feature. +var ( + TCC0 = (*TCC)(sam.TCC0) + TCC1 = (*TCC)(sam.TCC1) + TCC2 = (*TCC)(sam.TCC2) + TCC3 = (*TCC)(sam.TCC3) + TCC4 = (*TCC)(sam.TCC4) +) + +func (tcc *TCC) configureClock() { + // Turn on timer clocks used for the TCC and use generic clock generator 0. + switch tcc.timer() { + case sam.TCC0: + sam.MCLK.APBBMASK.SetBits(sam.MCLK_APBBMASK_TCC0_) + sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_TCC0].Set((sam.GCLK_PCHCTRL_GEN_GCLK0 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) + case sam.TCC1: + sam.MCLK.APBBMASK.SetBits(sam.MCLK_APBBMASK_TCC1_) + sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_TCC1].Set((sam.GCLK_PCHCTRL_GEN_GCLK0 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) + case sam.TCC2: + sam.MCLK.APBCMASK.SetBits(sam.MCLK_APBCMASK_TCC2_) + sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_TCC2].Set((sam.GCLK_PCHCTRL_GEN_GCLK0 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) + case sam.TCC3: + sam.MCLK.APBCMASK.SetBits(sam.MCLK_APBCMASK_TCC3_) + sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_TCC3].Set((sam.GCLK_PCHCTRL_GEN_GCLK0 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) + case sam.TCC4: + sam.MCLK.APBDMASK.SetBits(sam.MCLK_APBDMASK_TCC4_) + sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_TCC4].Set((sam.GCLK_PCHCTRL_GEN_GCLK0 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) + } +} + +func (tcc *TCC) timerNum() uint8 { + switch tcc.timer() { + case sam.TCC0: + return 0 + case sam.TCC1: + return 1 + case sam.TCC2: + return 2 + case sam.TCC3: + return 3 + case sam.TCC4: + return 4 + default: + return 0x0f // should not happen + } +} diff --git a/emb/machine/machine_atsamd51j20.go b/emb/machine/machine_atsamd51j20.go new file mode 100644 index 00000000..d5822787 --- /dev/null +++ b/emb/machine/machine_atsamd51j20.go @@ -0,0 +1,110 @@ +//go:build sam && atsamd51 && atsamd51j20 + +// Peripheral abstraction layer for the atsamd51. +// +// Datasheet: +// http://ww1.microchip.com/downloads/en/DeviceDoc/60001507C.pdf +package machine + +import "device/sam" + +const HSRAM_SIZE = 0x00040000 + +var ( + sercomI2CM0 = &I2C{Bus: sam.SERCOM0_I2CM, SERCOM: 0} + sercomI2CM1 = &I2C{Bus: sam.SERCOM1_I2CM, SERCOM: 1} + sercomI2CM2 = &I2C{Bus: sam.SERCOM2_I2CM, SERCOM: 2} + sercomI2CM3 = &I2C{Bus: sam.SERCOM3_I2CM, SERCOM: 3} + sercomI2CM4 = &I2C{Bus: sam.SERCOM4_I2CM, SERCOM: 4} + sercomI2CM5 = &I2C{Bus: sam.SERCOM5_I2CM, SERCOM: 5} + + sercomSPIM0 = &SPI{Bus: sam.SERCOM0_SPIM, SERCOM: 0} + sercomSPIM1 = &SPI{Bus: sam.SERCOM1_SPIM, SERCOM: 1} + sercomSPIM2 = &SPI{Bus: sam.SERCOM2_SPIM, SERCOM: 2} + sercomSPIM3 = &SPI{Bus: sam.SERCOM3_SPIM, SERCOM: 3} + sercomSPIM4 = &SPI{Bus: sam.SERCOM4_SPIM, SERCOM: 4} + sercomSPIM5 = &SPI{Bus: sam.SERCOM5_SPIM, SERCOM: 5} +) + +// setSERCOMClockGenerator sets the GCLK for sercom +func setSERCOMClockGenerator(sercom uint8, gclk uint32) { + switch sercom { + case 0: + sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM0_CORE].ClearBits(sam.GCLK_PCHCTRL_CHEN) + sam.MCLK.APBAMASK.SetBits(sam.MCLK_APBAMASK_SERCOM0_) + sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM0_CORE].Set((gclk << sam.GCLK_PCHCTRL_GEN_Pos) | + sam.GCLK_PCHCTRL_CHEN) + case 1: + sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM1_CORE].ClearBits(sam.GCLK_PCHCTRL_CHEN) + sam.MCLK.APBAMASK.SetBits(sam.MCLK_APBAMASK_SERCOM1_) + sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM1_CORE].Set((gclk << sam.GCLK_PCHCTRL_GEN_Pos) | + sam.GCLK_PCHCTRL_CHEN) + case 2: + sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM2_CORE].ClearBits(sam.GCLK_PCHCTRL_CHEN) + sam.MCLK.APBBMASK.SetBits(sam.MCLK_APBBMASK_SERCOM2_) + sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM2_CORE].Set((gclk << sam.GCLK_PCHCTRL_GEN_Pos) | + sam.GCLK_PCHCTRL_CHEN) + case 3: + sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM3_CORE].ClearBits(sam.GCLK_PCHCTRL_CHEN) + sam.MCLK.APBBMASK.SetBits(sam.MCLK_APBBMASK_SERCOM3_) + sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM3_CORE].Set((gclk << sam.GCLK_PCHCTRL_GEN_Pos) | + sam.GCLK_PCHCTRL_CHEN) + case 4: + sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM4_CORE].ClearBits(sam.GCLK_PCHCTRL_CHEN) + sam.MCLK.APBDMASK.SetBits(sam.MCLK_APBDMASK_SERCOM4_) + sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM4_CORE].Set((gclk << sam.GCLK_PCHCTRL_GEN_Pos) | + sam.GCLK_PCHCTRL_CHEN) + case 5: + sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM5_CORE].ClearBits(sam.GCLK_PCHCTRL_CHEN) + sam.MCLK.APBDMASK.SetBits(sam.MCLK_APBDMASK_SERCOM5_) + sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM5_CORE].Set((gclk << sam.GCLK_PCHCTRL_GEN_Pos) | + sam.GCLK_PCHCTRL_CHEN) + } +} + +// This chip has five TCC peripherals, which have PWM as one feature. +var ( + TCC0 = (*TCC)(sam.TCC0) + TCC1 = (*TCC)(sam.TCC1) + TCC2 = (*TCC)(sam.TCC2) + TCC3 = (*TCC)(sam.TCC3) + TCC4 = (*TCC)(sam.TCC4) +) + +func (tcc *TCC) configureClock() { + // Turn on timer clocks used for TCC and use generic clock generator 0. + switch tcc.timer() { + case sam.TCC0: + sam.MCLK.APBBMASK.SetBits(sam.MCLK_APBBMASK_TCC0_) + sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_TCC0].Set((sam.GCLK_PCHCTRL_GEN_GCLK0 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) + case sam.TCC1: + sam.MCLK.APBBMASK.SetBits(sam.MCLK_APBBMASK_TCC1_) + sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_TCC1].Set((sam.GCLK_PCHCTRL_GEN_GCLK0 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) + case sam.TCC2: + sam.MCLK.APBCMASK.SetBits(sam.MCLK_APBCMASK_TCC2_) + sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_TCC2].Set((sam.GCLK_PCHCTRL_GEN_GCLK0 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) + case sam.TCC3: + sam.MCLK.APBCMASK.SetBits(sam.MCLK_APBCMASK_TCC3_) + sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_TCC3].Set((sam.GCLK_PCHCTRL_GEN_GCLK0 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) + case sam.TCC4: + sam.MCLK.APBDMASK.SetBits(sam.MCLK_APBDMASK_TCC4_) + sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_TCC4].Set((sam.GCLK_PCHCTRL_GEN_GCLK0 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) + } +} + +func (tcc *TCC) timerNum() uint8 { + switch tcc.timer() { + case sam.TCC0: + return 0 + case sam.TCC1: + return 1 + case sam.TCC2: + return 2 + case sam.TCC3: + return 3 + case sam.TCC4: + return 4 + default: + return 0x0f // should not happen + } +} diff --git a/emb/machine/machine_atsamd51p19.go b/emb/machine/machine_atsamd51p19.go new file mode 100644 index 00000000..bcd66a93 --- /dev/null +++ b/emb/machine/machine_atsamd51p19.go @@ -0,0 +1,124 @@ +//go:build sam && atsamd51 && atsamd51p19 + +// Peripheral abstraction layer for the atsamd51. +// +// Datasheet: +// http://ww1.microchip.com/downloads/en/DeviceDoc/60001507C.pdf +package machine + +import "device/sam" + +const HSRAM_SIZE = 0x00030000 + +var ( + sercomI2CM0 = &I2C{Bus: sam.SERCOM0_I2CM, SERCOM: 0} + sercomI2CM1 = &I2C{Bus: sam.SERCOM1_I2CM, SERCOM: 1} + sercomI2CM2 = &I2C{Bus: sam.SERCOM2_I2CM, SERCOM: 2} + sercomI2CM3 = &I2C{Bus: sam.SERCOM3_I2CM, SERCOM: 3} + sercomI2CM4 = &I2C{Bus: sam.SERCOM4_I2CM, SERCOM: 4} + sercomI2CM5 = &I2C{Bus: sam.SERCOM5_I2CM, SERCOM: 5} + sercomI2CM6 = &I2C{Bus: sam.SERCOM6_I2CM, SERCOM: 6} + sercomI2CM7 = &I2C{Bus: sam.SERCOM7_I2CM, SERCOM: 7} + + sercomSPIM0 = &SPI{Bus: sam.SERCOM0_SPIM, SERCOM: 0} + sercomSPIM1 = &SPI{Bus: sam.SERCOM1_SPIM, SERCOM: 1} + sercomSPIM2 = &SPI{Bus: sam.SERCOM2_SPIM, SERCOM: 2} + sercomSPIM3 = &SPI{Bus: sam.SERCOM3_SPIM, SERCOM: 3} + sercomSPIM4 = &SPI{Bus: sam.SERCOM4_SPIM, SERCOM: 4} + sercomSPIM5 = &SPI{Bus: sam.SERCOM5_SPIM, SERCOM: 5} + sercomSPIM6 = &SPI{Bus: sam.SERCOM6_SPIM, SERCOM: 6} + sercomSPIM7 = &SPI{Bus: sam.SERCOM7_SPIM, SERCOM: 7} +) + +// setSERCOMClockGenerator sets the GCLK for sercom +func setSERCOMClockGenerator(sercom uint8, gclk uint32) { + switch sercom { + case 0: + sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM0_CORE].ClearBits(sam.GCLK_PCHCTRL_CHEN) + sam.MCLK.APBAMASK.SetBits(sam.MCLK_APBAMASK_SERCOM0_) + sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM0_CORE].Set((gclk << sam.GCLK_PCHCTRL_GEN_Pos) | + sam.GCLK_PCHCTRL_CHEN) + case 1: + sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM1_CORE].ClearBits(sam.GCLK_PCHCTRL_CHEN) + sam.MCLK.APBAMASK.SetBits(sam.MCLK_APBAMASK_SERCOM1_) + sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM1_CORE].Set((gclk << sam.GCLK_PCHCTRL_GEN_Pos) | + sam.GCLK_PCHCTRL_CHEN) + case 2: + sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM2_CORE].ClearBits(sam.GCLK_PCHCTRL_CHEN) + sam.MCLK.APBBMASK.SetBits(sam.MCLK_APBBMASK_SERCOM2_) + sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM2_CORE].Set((gclk << sam.GCLK_PCHCTRL_GEN_Pos) | + sam.GCLK_PCHCTRL_CHEN) + case 3: + sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM3_CORE].ClearBits(sam.GCLK_PCHCTRL_CHEN) + sam.MCLK.APBBMASK.SetBits(sam.MCLK_APBBMASK_SERCOM3_) + sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM3_CORE].Set((gclk << sam.GCLK_PCHCTRL_GEN_Pos) | + sam.GCLK_PCHCTRL_CHEN) + case 4: + sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM4_CORE].ClearBits(sam.GCLK_PCHCTRL_CHEN) + sam.MCLK.APBDMASK.SetBits(sam.MCLK_APBDMASK_SERCOM4_) + sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM4_CORE].Set((gclk << sam.GCLK_PCHCTRL_GEN_Pos) | + sam.GCLK_PCHCTRL_CHEN) + case 5: + sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM5_CORE].ClearBits(sam.GCLK_PCHCTRL_CHEN) + sam.MCLK.APBDMASK.SetBits(sam.MCLK_APBDMASK_SERCOM5_) + sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM5_CORE].Set((gclk << sam.GCLK_PCHCTRL_GEN_Pos) | + sam.GCLK_PCHCTRL_CHEN) + case 6: + sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM6_CORE].ClearBits(sam.GCLK_PCHCTRL_CHEN) + sam.MCLK.APBDMASK.SetBits(sam.MCLK_APBDMASK_SERCOM6_) + sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM6_CORE].Set((gclk << sam.GCLK_PCHCTRL_GEN_Pos) | + sam.GCLK_PCHCTRL_CHEN) + case 7: + sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM7_CORE].ClearBits(sam.GCLK_PCHCTRL_CHEN) + sam.MCLK.APBDMASK.SetBits(sam.MCLK_APBDMASK_SERCOM7_) + sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM7_CORE].Set((gclk << sam.GCLK_PCHCTRL_GEN_Pos) | + sam.GCLK_PCHCTRL_CHEN) + } +} + +// This chip has five TCC peripherals, which have PWM as one feature. +var ( + TCC0 = (*TCC)(sam.TCC0) + TCC1 = (*TCC)(sam.TCC1) + TCC2 = (*TCC)(sam.TCC2) + TCC3 = (*TCC)(sam.TCC3) + TCC4 = (*TCC)(sam.TCC4) +) + +func (tcc *TCC) configureClock() { + // Turn on timer clocks used for TCC and use generic clock generator 0. + switch tcc.timer() { + case sam.TCC0: + sam.MCLK.APBBMASK.SetBits(sam.MCLK_APBBMASK_TCC0_) + sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_TCC0].Set((sam.GCLK_PCHCTRL_GEN_GCLK0 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) + case sam.TCC1: + sam.MCLK.APBBMASK.SetBits(sam.MCLK_APBBMASK_TCC1_) + sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_TCC1].Set((sam.GCLK_PCHCTRL_GEN_GCLK0 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) + case sam.TCC2: + sam.MCLK.APBCMASK.SetBits(sam.MCLK_APBCMASK_TCC2_) + sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_TCC2].Set((sam.GCLK_PCHCTRL_GEN_GCLK0 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) + case sam.TCC3: + sam.MCLK.APBCMASK.SetBits(sam.MCLK_APBCMASK_TCC3_) + sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_TCC3].Set((sam.GCLK_PCHCTRL_GEN_GCLK0 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) + case sam.TCC4: + sam.MCLK.APBDMASK.SetBits(sam.MCLK_APBDMASK_TCC4_) + sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_TCC4].Set((sam.GCLK_PCHCTRL_GEN_GCLK0 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) + } +} + +func (tcc *TCC) timerNum() uint8 { + switch tcc.timer() { + case sam.TCC0: + return 0 + case sam.TCC1: + return 1 + case sam.TCC2: + return 2 + case sam.TCC3: + return 3 + case sam.TCC4: + return 4 + default: + return 0x0f // should not happen + } +} diff --git a/emb/machine/machine_atsamd51p20.go b/emb/machine/machine_atsamd51p20.go new file mode 100644 index 00000000..40e435fa --- /dev/null +++ b/emb/machine/machine_atsamd51p20.go @@ -0,0 +1,124 @@ +//go:build sam && atsamd51 && atsamd51p20 + +// Peripheral abstraction layer for the atsamd51. +// +// Datasheet: +// http://ww1.microchip.com/downloads/en/DeviceDoc/60001507C.pdf +package machine + +import "device/sam" + +const HSRAM_SIZE = 0x00040000 + +var ( + sercomI2CM0 = &I2C{Bus: sam.SERCOM0_I2CM, SERCOM: 0} + sercomI2CM1 = &I2C{Bus: sam.SERCOM1_I2CM, SERCOM: 1} + sercomI2CM2 = &I2C{Bus: sam.SERCOM2_I2CM, SERCOM: 2} + sercomI2CM3 = &I2C{Bus: sam.SERCOM3_I2CM, SERCOM: 3} + sercomI2CM4 = &I2C{Bus: sam.SERCOM4_I2CM, SERCOM: 4} + sercomI2CM5 = &I2C{Bus: sam.SERCOM5_I2CM, SERCOM: 5} + sercomI2CM6 = &I2C{Bus: sam.SERCOM6_I2CM, SERCOM: 6} + sercomI2CM7 = &I2C{Bus: sam.SERCOM7_I2CM, SERCOM: 7} + + sercomSPIM0 = &SPI{Bus: sam.SERCOM0_SPIM, SERCOM: 0} + sercomSPIM1 = &SPI{Bus: sam.SERCOM1_SPIM, SERCOM: 1} + sercomSPIM2 = &SPI{Bus: sam.SERCOM2_SPIM, SERCOM: 2} + sercomSPIM3 = &SPI{Bus: sam.SERCOM3_SPIM, SERCOM: 3} + sercomSPIM4 = &SPI{Bus: sam.SERCOM4_SPIM, SERCOM: 4} + sercomSPIM5 = &SPI{Bus: sam.SERCOM5_SPIM, SERCOM: 5} + sercomSPIM6 = &SPI{Bus: sam.SERCOM6_SPIM, SERCOM: 6} + sercomSPIM7 = &SPI{Bus: sam.SERCOM7_SPIM, SERCOM: 7} +) + +// setSERCOMClockGenerator sets the GCLK for sercom +func setSERCOMClockGenerator(sercom uint8, gclk uint32) { + switch sercom { + case 0: + sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM0_CORE].ClearBits(sam.GCLK_PCHCTRL_CHEN) + sam.MCLK.APBAMASK.SetBits(sam.MCLK_APBAMASK_SERCOM0_) + sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM0_CORE].Set((gclk << sam.GCLK_PCHCTRL_GEN_Pos) | + sam.GCLK_PCHCTRL_CHEN) + case 1: + sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM1_CORE].ClearBits(sam.GCLK_PCHCTRL_CHEN) + sam.MCLK.APBAMASK.SetBits(sam.MCLK_APBAMASK_SERCOM1_) + sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM1_CORE].Set((gclk << sam.GCLK_PCHCTRL_GEN_Pos) | + sam.GCLK_PCHCTRL_CHEN) + case 2: + sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM2_CORE].ClearBits(sam.GCLK_PCHCTRL_CHEN) + sam.MCLK.APBBMASK.SetBits(sam.MCLK_APBBMASK_SERCOM2_) + sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM2_CORE].Set((gclk << sam.GCLK_PCHCTRL_GEN_Pos) | + sam.GCLK_PCHCTRL_CHEN) + case 3: + sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM3_CORE].ClearBits(sam.GCLK_PCHCTRL_CHEN) + sam.MCLK.APBBMASK.SetBits(sam.MCLK_APBBMASK_SERCOM3_) + sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM3_CORE].Set((gclk << sam.GCLK_PCHCTRL_GEN_Pos) | + sam.GCLK_PCHCTRL_CHEN) + case 4: + sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM4_CORE].ClearBits(sam.GCLK_PCHCTRL_CHEN) + sam.MCLK.APBDMASK.SetBits(sam.MCLK_APBDMASK_SERCOM4_) + sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM4_CORE].Set((gclk << sam.GCLK_PCHCTRL_GEN_Pos) | + sam.GCLK_PCHCTRL_CHEN) + case 5: + sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM5_CORE].ClearBits(sam.GCLK_PCHCTRL_CHEN) + sam.MCLK.APBDMASK.SetBits(sam.MCLK_APBDMASK_SERCOM5_) + sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM5_CORE].Set((gclk << sam.GCLK_PCHCTRL_GEN_Pos) | + sam.GCLK_PCHCTRL_CHEN) + case 6: + sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM6_CORE].ClearBits(sam.GCLK_PCHCTRL_CHEN) + sam.MCLK.APBDMASK.SetBits(sam.MCLK_APBDMASK_SERCOM6_) + sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM6_CORE].Set((gclk << sam.GCLK_PCHCTRL_GEN_Pos) | + sam.GCLK_PCHCTRL_CHEN) + case 7: + sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM7_CORE].ClearBits(sam.GCLK_PCHCTRL_CHEN) + sam.MCLK.APBDMASK.SetBits(sam.MCLK_APBDMASK_SERCOM7_) + sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM7_CORE].Set((gclk << sam.GCLK_PCHCTRL_GEN_Pos) | + sam.GCLK_PCHCTRL_CHEN) + } +} + +// This chip has five TCC peripherals, which have PWM as one feature. +var ( + TCC0 = (*TCC)(sam.TCC0) + TCC1 = (*TCC)(sam.TCC1) + TCC2 = (*TCC)(sam.TCC2) + TCC3 = (*TCC)(sam.TCC3) + TCC4 = (*TCC)(sam.TCC4) +) + +func (tcc *TCC) configureClock() { + // Turn on timer clocks used for TCC and use generic clock generator 0. + switch tcc.timer() { + case sam.TCC0: + sam.MCLK.APBBMASK.SetBits(sam.MCLK_APBBMASK_TCC0_) + sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_TCC0].Set((sam.GCLK_PCHCTRL_GEN_GCLK0 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) + case sam.TCC1: + sam.MCLK.APBBMASK.SetBits(sam.MCLK_APBBMASK_TCC1_) + sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_TCC1].Set((sam.GCLK_PCHCTRL_GEN_GCLK0 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) + case sam.TCC2: + sam.MCLK.APBCMASK.SetBits(sam.MCLK_APBCMASK_TCC2_) + sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_TCC2].Set((sam.GCLK_PCHCTRL_GEN_GCLK0 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) + case sam.TCC3: + sam.MCLK.APBCMASK.SetBits(sam.MCLK_APBCMASK_TCC3_) + sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_TCC3].Set((sam.GCLK_PCHCTRL_GEN_GCLK0 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) + case sam.TCC4: + sam.MCLK.APBDMASK.SetBits(sam.MCLK_APBDMASK_TCC4_) + sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_TCC4].Set((sam.GCLK_PCHCTRL_GEN_GCLK0 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) + } +} + +func (tcc *TCC) timerNum() uint8 { + switch tcc.timer() { + case sam.TCC0: + return 0 + case sam.TCC1: + return 1 + case sam.TCC2: + return 2 + case sam.TCC3: + return 3 + case sam.TCC4: + return 4 + default: + return 0x0f // should not happen + } +} diff --git a/emb/machine/machine_atsame51j19.go b/emb/machine/machine_atsame51j19.go new file mode 100644 index 00000000..29ea4117 --- /dev/null +++ b/emb/machine/machine_atsame51j19.go @@ -0,0 +1,110 @@ +//go:build sam && atsame51 && atsame51j19 + +// Peripheral abstraction layer for the atsame51. +// +// Datasheet: +// http://ww1.microchip.com/downloads/en/DeviceDoc/SAM_D5xE5x_Family_Data_Sheet_DS60001507F.pdf +package machine + +import "device/sam" + +const HSRAM_SIZE = 0x00030000 + +var ( + sercomI2CM0 = &I2C{Bus: sam.SERCOM0_I2CM, SERCOM: 0} + sercomI2CM1 = &I2C{Bus: sam.SERCOM1_I2CM, SERCOM: 1} + sercomI2CM2 = &I2C{Bus: sam.SERCOM2_I2CM, SERCOM: 2} + sercomI2CM3 = &I2C{Bus: sam.SERCOM3_I2CM, SERCOM: 3} + sercomI2CM4 = &I2C{Bus: sam.SERCOM4_I2CM, SERCOM: 4} + sercomI2CM5 = &I2C{Bus: sam.SERCOM5_I2CM, SERCOM: 5} + + sercomSPIM0 = &SPI{Bus: sam.SERCOM0_SPIM, SERCOM: 0} + sercomSPIM1 = &SPI{Bus: sam.SERCOM1_SPIM, SERCOM: 1} + sercomSPIM2 = &SPI{Bus: sam.SERCOM2_SPIM, SERCOM: 2} + sercomSPIM3 = &SPI{Bus: sam.SERCOM3_SPIM, SERCOM: 3} + sercomSPIM4 = &SPI{Bus: sam.SERCOM4_SPIM, SERCOM: 4} + sercomSPIM5 = &SPI{Bus: sam.SERCOM5_SPIM, SERCOM: 5} +) + +// setSERCOMClockGenerator sets the GCLK for sercom +func setSERCOMClockGenerator(sercom uint8, gclk uint32) { + switch sercom { + case 0: + sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM0_CORE].ClearBits(sam.GCLK_PCHCTRL_CHEN) + sam.MCLK.APBAMASK.SetBits(sam.MCLK_APBAMASK_SERCOM0_) + sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM0_CORE].Set((gclk << sam.GCLK_PCHCTRL_GEN_Pos) | + sam.GCLK_PCHCTRL_CHEN) + case 1: + sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM1_CORE].ClearBits(sam.GCLK_PCHCTRL_CHEN) + sam.MCLK.APBAMASK.SetBits(sam.MCLK_APBAMASK_SERCOM1_) + sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM1_CORE].Set((gclk << sam.GCLK_PCHCTRL_GEN_Pos) | + sam.GCLK_PCHCTRL_CHEN) + case 2: + sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM2_CORE].ClearBits(sam.GCLK_PCHCTRL_CHEN) + sam.MCLK.APBBMASK.SetBits(sam.MCLK_APBBMASK_SERCOM2_) + sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM2_CORE].Set((gclk << sam.GCLK_PCHCTRL_GEN_Pos) | + sam.GCLK_PCHCTRL_CHEN) + case 3: + sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM3_CORE].ClearBits(sam.GCLK_PCHCTRL_CHEN) + sam.MCLK.APBBMASK.SetBits(sam.MCLK_APBBMASK_SERCOM3_) + sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM3_CORE].Set((gclk << sam.GCLK_PCHCTRL_GEN_Pos) | + sam.GCLK_PCHCTRL_CHEN) + case 4: + sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM4_CORE].ClearBits(sam.GCLK_PCHCTRL_CHEN) + sam.MCLK.APBDMASK.SetBits(sam.MCLK_APBDMASK_SERCOM4_) + sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM4_CORE].Set((gclk << sam.GCLK_PCHCTRL_GEN_Pos) | + sam.GCLK_PCHCTRL_CHEN) + case 5: + sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM5_CORE].ClearBits(sam.GCLK_PCHCTRL_CHEN) + sam.MCLK.APBDMASK.SetBits(sam.MCLK_APBDMASK_SERCOM5_) + sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM5_CORE].Set((gclk << sam.GCLK_PCHCTRL_GEN_Pos) | + sam.GCLK_PCHCTRL_CHEN) + } +} + +// This chip has five TCC peripherals, which have PWM as one feature. +var ( + TCC0 = (*TCC)(sam.TCC0) + TCC1 = (*TCC)(sam.TCC1) + TCC2 = (*TCC)(sam.TCC2) + TCC3 = (*TCC)(sam.TCC3) + TCC4 = (*TCC)(sam.TCC4) +) + +func (tcc *TCC) configureClock() { + // Turn on timer clocks used for the TCC and use generic clock generator 0. + switch tcc.timer() { + case sam.TCC0: + sam.MCLK.APBBMASK.SetBits(sam.MCLK_APBBMASK_TCC0_) + sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_TCC0].Set((sam.GCLK_PCHCTRL_GEN_GCLK0 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) + case sam.TCC1: + sam.MCLK.APBBMASK.SetBits(sam.MCLK_APBBMASK_TCC1_) + sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_TCC1].Set((sam.GCLK_PCHCTRL_GEN_GCLK0 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) + case sam.TCC2: + sam.MCLK.APBCMASK.SetBits(sam.MCLK_APBCMASK_TCC2_) + sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_TCC2].Set((sam.GCLK_PCHCTRL_GEN_GCLK0 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) + case sam.TCC3: + sam.MCLK.APBCMASK.SetBits(sam.MCLK_APBCMASK_TCC3_) + sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_TCC3].Set((sam.GCLK_PCHCTRL_GEN_GCLK0 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) + case sam.TCC4: + sam.MCLK.APBDMASK.SetBits(sam.MCLK_APBDMASK_TCC4_) + sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_TCC4].Set((sam.GCLK_PCHCTRL_GEN_GCLK0 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) + } +} + +func (tcc *TCC) timerNum() uint8 { + switch tcc.timer() { + case sam.TCC0: + return 0 + case sam.TCC1: + return 1 + case sam.TCC2: + return 2 + case sam.TCC3: + return 3 + case sam.TCC4: + return 4 + default: + return 0x0f // should not happen + } +} diff --git a/emb/machine/machine_atsame54p20.go b/emb/machine/machine_atsame54p20.go new file mode 100644 index 00000000..d7cc31f6 --- /dev/null +++ b/emb/machine/machine_atsame54p20.go @@ -0,0 +1,124 @@ +//go:build sam && atsame5x && atsame54p20 + +// Peripheral abstraction layer for the atsame54. +// +// Datasheet: +// http://ww1.microchip.com/downloads/en/DeviceDoc/60001507C.pdf +package machine + +import "device/sam" + +const HSRAM_SIZE = 0x00040000 + +var ( + sercomI2CM0 = &I2C{Bus: sam.SERCOM0_I2CM, SERCOM: 0} + sercomI2CM1 = &I2C{Bus: sam.SERCOM1_I2CM, SERCOM: 1} + sercomI2CM2 = &I2C{Bus: sam.SERCOM2_I2CM, SERCOM: 2} + sercomI2CM3 = &I2C{Bus: sam.SERCOM3_I2CM, SERCOM: 3} + sercomI2CM4 = &I2C{Bus: sam.SERCOM4_I2CM, SERCOM: 4} + sercomI2CM5 = &I2C{Bus: sam.SERCOM5_I2CM, SERCOM: 5} + sercomI2CM6 = &I2C{Bus: sam.SERCOM6_I2CM, SERCOM: 6} + sercomI2CM7 = &I2C{Bus: sam.SERCOM7_I2CM, SERCOM: 7} + + sercomSPIM0 = &SPI{Bus: sam.SERCOM0_SPIM, SERCOM: 0} + sercomSPIM1 = &SPI{Bus: sam.SERCOM1_SPIM, SERCOM: 1} + sercomSPIM2 = &SPI{Bus: sam.SERCOM2_SPIM, SERCOM: 2} + sercomSPIM3 = &SPI{Bus: sam.SERCOM3_SPIM, SERCOM: 3} + sercomSPIM4 = &SPI{Bus: sam.SERCOM4_SPIM, SERCOM: 4} + sercomSPIM5 = &SPI{Bus: sam.SERCOM5_SPIM, SERCOM: 5} + sercomSPIM6 = &SPI{Bus: sam.SERCOM6_SPIM, SERCOM: 6} + sercomSPIM7 = &SPI{Bus: sam.SERCOM7_SPIM, SERCOM: 7} +) + +// setSERCOMClockGenerator sets the GCLK for sercom +func setSERCOMClockGenerator(sercom uint8, gclk uint32) { + switch sercom { + case 0: + sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM0_CORE].ClearBits(sam.GCLK_PCHCTRL_CHEN) + sam.MCLK.APBAMASK.SetBits(sam.MCLK_APBAMASK_SERCOM0_) + sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM0_CORE].Set((gclk << sam.GCLK_PCHCTRL_GEN_Pos) | + sam.GCLK_PCHCTRL_CHEN) + case 1: + sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM1_CORE].ClearBits(sam.GCLK_PCHCTRL_CHEN) + sam.MCLK.APBAMASK.SetBits(sam.MCLK_APBAMASK_SERCOM1_) + sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM1_CORE].Set((gclk << sam.GCLK_PCHCTRL_GEN_Pos) | + sam.GCLK_PCHCTRL_CHEN) + case 2: + sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM2_CORE].ClearBits(sam.GCLK_PCHCTRL_CHEN) + sam.MCLK.APBBMASK.SetBits(sam.MCLK_APBBMASK_SERCOM2_) + sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM2_CORE].Set((gclk << sam.GCLK_PCHCTRL_GEN_Pos) | + sam.GCLK_PCHCTRL_CHEN) + case 3: + sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM3_CORE].ClearBits(sam.GCLK_PCHCTRL_CHEN) + sam.MCLK.APBBMASK.SetBits(sam.MCLK_APBBMASK_SERCOM3_) + sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM3_CORE].Set((gclk << sam.GCLK_PCHCTRL_GEN_Pos) | + sam.GCLK_PCHCTRL_CHEN) + case 4: + sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM4_CORE].ClearBits(sam.GCLK_PCHCTRL_CHEN) + sam.MCLK.APBDMASK.SetBits(sam.MCLK_APBDMASK_SERCOM4_) + sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM4_CORE].Set((gclk << sam.GCLK_PCHCTRL_GEN_Pos) | + sam.GCLK_PCHCTRL_CHEN) + case 5: + sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM5_CORE].ClearBits(sam.GCLK_PCHCTRL_CHEN) + sam.MCLK.APBDMASK.SetBits(sam.MCLK_APBDMASK_SERCOM5_) + sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM5_CORE].Set((gclk << sam.GCLK_PCHCTRL_GEN_Pos) | + sam.GCLK_PCHCTRL_CHEN) + case 6: + sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM6_CORE].ClearBits(sam.GCLK_PCHCTRL_CHEN) + sam.MCLK.APBDMASK.SetBits(sam.MCLK_APBDMASK_SERCOM6_) + sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM6_CORE].Set((gclk << sam.GCLK_PCHCTRL_GEN_Pos) | + sam.GCLK_PCHCTRL_CHEN) + case 7: + sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM7_CORE].ClearBits(sam.GCLK_PCHCTRL_CHEN) + sam.MCLK.APBDMASK.SetBits(sam.MCLK_APBDMASK_SERCOM7_) + sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_SERCOM7_CORE].Set((gclk << sam.GCLK_PCHCTRL_GEN_Pos) | + sam.GCLK_PCHCTRL_CHEN) + } +} + +// This chip has five TCC peripherals, which have PWM as one feature. +var ( + TCC0 = (*TCC)(sam.TCC0) + TCC1 = (*TCC)(sam.TCC1) + TCC2 = (*TCC)(sam.TCC2) + TCC3 = (*TCC)(sam.TCC3) + TCC4 = (*TCC)(sam.TCC4) +) + +func (tcc *TCC) configureClock() { + // Turn on timer clocks used for TCC and use generic clock generator 0. + switch tcc.timer() { + case sam.TCC0: + sam.MCLK.APBBMASK.SetBits(sam.MCLK_APBBMASK_TCC0_) + sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_TCC0].Set((sam.GCLK_PCHCTRL_GEN_GCLK0 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) + case sam.TCC1: + sam.MCLK.APBBMASK.SetBits(sam.MCLK_APBBMASK_TCC1_) + sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_TCC1].Set((sam.GCLK_PCHCTRL_GEN_GCLK0 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) + case sam.TCC2: + sam.MCLK.APBCMASK.SetBits(sam.MCLK_APBCMASK_TCC2_) + sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_TCC2].Set((sam.GCLK_PCHCTRL_GEN_GCLK0 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) + case sam.TCC3: + sam.MCLK.APBCMASK.SetBits(sam.MCLK_APBCMASK_TCC3_) + sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_TCC3].Set((sam.GCLK_PCHCTRL_GEN_GCLK0 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) + case sam.TCC4: + sam.MCLK.APBDMASK.SetBits(sam.MCLK_APBDMASK_TCC4_) + sam.GCLK.PCHCTRL[sam.PCHCTRL_GCLK_TCC4].Set((sam.GCLK_PCHCTRL_GEN_GCLK0 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) + } +} + +func (tcc *TCC) timerNum() uint8 { + switch tcc.timer() { + case sam.TCC0: + return 0 + case sam.TCC1: + return 1 + case sam.TCC2: + return 2 + case sam.TCC3: + return 3 + case sam.TCC4: + return 4 + default: + return 0x0f // should not happen + } +} diff --git a/emb/machine/machine_atsame5x_can.go b/emb/machine/machine_atsame5x_can.go new file mode 100644 index 00000000..bf38cd89 --- /dev/null +++ b/emb/machine/machine_atsame5x_can.go @@ -0,0 +1,478 @@ +//go:build (sam && atsame51) || (sam && atsame54) + +package machine + +import ( + "device/sam" + "errors" + "runtime/interrupt" + "unsafe" +) + +const ( + CANRxFifoSize = 16 + CANTxFifoSize = 16 + CANEvFifoSize = 16 +) + +// Message RAM can only be located in the first 64 KB area of the system RAM. +// TODO: when the go:section pragma is merged, add the section configuration + +//go:align 4 +var CANRxFifo [2][(8 + 64) * CANRxFifoSize]byte + +//go:align 4 +var CANTxFifo [2][(8 + 64) * CANTxFifoSize]byte + +//go:align 4 +var CANEvFifo [2][(8) * CANEvFifoSize]byte + +type CAN struct { + Bus *sam.CAN_Type +} + +type CANTransferRate uint32 + +// CAN transfer rates for CANConfig +const ( + CANTransferRate125kbps CANTransferRate = 125000 + CANTransferRate250kbps CANTransferRate = 250000 + CANTransferRate500kbps CANTransferRate = 500000 + CANTransferRate1000kbps CANTransferRate = 1000000 + CANTransferRate2000kbps CANTransferRate = 2000000 + CANTransferRate4000kbps CANTransferRate = 4000000 +) + +// CANConfig holds CAN configuration parameters. Tx and Rx need to be +// specified with some pins. When the Standby Pin is specified, configure it +// as an output pin and output Low in Configure(). If this operation is not +// necessary, specify NoPin. +type CANConfig struct { + TransferRate CANTransferRate + TransferRateFD CANTransferRate + Tx Pin + Rx Pin + Standby Pin +} + +var ( + errCANInvalidTransferRate = errors.New("CAN: invalid TransferRate") + errCANInvalidTransferRateFD = errors.New("CAN: invalid TransferRateFD") +) + +// Configure this CAN peripheral with the given configuration. +func (can *CAN) Configure(config CANConfig) error { + if config.Standby != NoPin { + config.Standby.Configure(PinConfig{Mode: PinOutput}) + config.Standby.Low() + } + + mode := PinCAN0 + if can.instance() == 1 { + mode = PinCAN1 + } + + config.Rx.Configure(PinConfig{Mode: mode}) + config.Tx.Configure(PinConfig{Mode: mode}) + + can.Bus.CCCR.SetBits(sam.CAN_CCCR_INIT) + for !can.Bus.CCCR.HasBits(sam.CAN_CCCR_INIT) { + } + + can.Bus.CCCR.SetBits(sam.CAN_CCCR_CCE) + + can.Bus.CCCR.SetBits(sam.CAN_CCCR_BRSE | sam.CAN_CCCR_FDOE) + can.Bus.MRCFG.Set(sam.CAN_MRCFG_QOS_MEDIUM) + // base clock == 48 MHz + if config.TransferRate == 0 { + config.TransferRate = CANTransferRate500kbps + } + brp := uint32(6) + switch config.TransferRate { + case CANTransferRate125kbps: + brp = 32 + case CANTransferRate250kbps: + brp = 16 + case CANTransferRate500kbps: + brp = 8 + case CANTransferRate1000kbps: + brp = 4 + default: + return errCANInvalidTransferRate + } + can.Bus.NBTP.Set(8<> sam.CAN_TXFQS_TFQPI_Pos + + f := CANTxFifo[can.instance()][putIndex*(8+64) : (putIndex+1)*(8+64)] + id := e.ID + if !e.XTD { + // standard identifier is stored into ID[28:18] + id <<= 18 + } + + f[3] = byte(id>>24) & 0x1F + if e.ESI { + f[3] |= 0x80 + } + if e.XTD { + f[3] |= 0x40 + } + if e.RTR { + f[3] |= 0x20 + } + f[2] = byte(id >> 16) + f[1] = byte(id >> 8) + f[0] = byte(id) + f[7] = e.MM + f[6] = e.DLC + if e.EFC { + f[6] |= 0x80 + } + if e.FDF { + f[6] |= 0x20 + } + if e.BRS { + f[6] |= 0x10 + } + f[5] = 0x00 // reserved + f[4] = 0x00 // reserved + + length := CANDlcToLength(e.DLC, e.FDF) + for i := byte(0); i < length; i++ { + f[8+i] = e.DB[i] + } + + can.Bus.TXBAR.SetBits(1 << putIndex) +} + +// The Tx transmits CAN frames. It is easier to use than TxRaw, but not as +// flexible. +func (can *CAN) Tx(id uint32, data []byte, isFD, isExtendedID bool) { + length := byte(len(data)) + dlc := CANLengthToDlc(length, true) + + e := CANTxBufferElement{ + ESI: false, + XTD: isExtendedID, + RTR: false, + ID: id, + MM: 0x00, + EFC: true, + FDF: isFD, + BRS: isFD, + DLC: dlc, + } + + if !isFD { + if length > 8 { + length = 8 + } + } + for i := byte(0); i < length; i++ { + e.DB[i] = data[i] + } + + can.TxRaw(&e) +} + +// RxFifoSize returns the number of CAN Frames currently stored in the RXFifo. +func (can *CAN) RxFifoSize() int { + sz := (can.Bus.RXF0S.Get() & sam.CAN_RXF0S_F0FL_Msk) >> sam.CAN_RXF0S_F0FL_Pos + return int(sz) +} + +// RxFifoIsFull returns whether RxFifo is full or not. +func (can *CAN) RxFifoIsFull() bool { + sz := (can.Bus.RXF0S.Get() & sam.CAN_RXF0S_F0FL_Msk) >> sam.CAN_RXF0S_F0FL_Pos + return sz == CANRxFifoSize +} + +// RxFifoIsEmpty returns whether RxFifo is empty or not. +func (can *CAN) RxFifoIsEmpty() bool { + sz := (can.Bus.RXF0S.Get() & sam.CAN_RXF0S_F0FL_Msk) >> sam.CAN_RXF0S_F0FL_Pos + return sz == 0 +} + +// RxRaw copies the received CAN frame to CANRxBufferElement. +func (can *CAN) RxRaw(e *CANRxBufferElement) { + idx := (can.Bus.RXF0S.Get() & sam.CAN_RXF0S_F0GI_Msk) >> sam.CAN_RXF0S_F0GI_Pos + f := CANRxFifo[can.instance()][idx*(8+64):] + + e.ESI = false + if (f[3] & 0x80) != 0x00 { + e.ESI = true + } + + e.XTD = false + if (f[3] & 0x40) != 0x00 { + e.XTD = true + } + + e.RTR = false + if (f[3] & 0x20) != 0x00 { + e.RTR = true + } + + id := ((uint32(f[3]) << 24) + (uint32(f[2]) << 16) + (uint32(f[1]) << 8) + uint32(f[0])) & 0x1FFFFFFF + if !e.XTD { + id >>= 18 + id &= 0x000007FF + } + e.ID = id + + e.ANMF = false + if (f[7] & 0x80) != 0x00 { + e.ANMF = true + } + + e.FIDX = f[7] & 0x7F + + e.FDF = false + if (f[6] & 0x20) != 0x00 { + e.FDF = true + } + + e.BRS = false + if (f[6] & 0x10) != 0x00 { + e.BRS = true + } + + e.DLC = f[6] & 0x0F + + e.RXTS = (uint16(f[5]) << 8) + uint16(f[4]) + + for i := byte(0); i < CANDlcToLength(e.DLC, e.FDF); i++ { + e.DB[i] = f[i+8] + } + + can.Bus.RXF0A.ReplaceBits(idx, sam.CAN_RXF0A_F0AI_Msk, sam.CAN_RXF0A_F0AI_Pos) +} + +// Rx receives a CAN frame. It is easier to use than RxRaw, but not as +// flexible. +func (can *CAN) Rx() (id uint32, dlc byte, data []byte, isFd, isExtendedID bool) { + e := CANRxBufferElement{} + can.RxRaw(&e) + length := CANDlcToLength(e.DLC, e.FDF) + return e.ID, length, e.DB[:length], e.FDF, e.XTD +} + +func (can *CAN) instance() byte { + if can.Bus == sam.CAN0 { + return 0 + } else { + return 1 + } +} + +// CANTxBufferElement is a struct that corresponds to the same5x' Tx Buffer +// Element. +type CANTxBufferElement struct { + ESI bool + XTD bool + RTR bool + ID uint32 + MM uint8 + EFC bool + FDF bool + BRS bool + DLC uint8 + DB [64]uint8 +} + +// CANRxBufferElement is a struct that corresponds to the same5x Rx Buffer and +// FIFO Element. +type CANRxBufferElement struct { + ESI bool + XTD bool + RTR bool + ID uint32 + ANMF bool + FIDX uint8 + FDF bool + BRS bool + DLC uint8 + RXTS uint16 + DB [64]uint8 +} + +// Data returns the received data as a slice of the size according to dlc. +func (e CANRxBufferElement) Data() []byte { + return e.DB[:CANDlcToLength(e.DLC, e.FDF)] +} + +// Length returns its actual length. +func (e CANRxBufferElement) Length() byte { + return CANDlcToLength(e.DLC, e.FDF) +} + +// CANDlcToLength() converts a DLC value to its actual length. +func CANDlcToLength(dlc byte, isFD bool) byte { + length := dlc + if dlc == 0x09 { + length = 12 + } else if dlc == 0x0A { + length = 16 + } else if dlc == 0x0B { + length = 20 + } else if dlc == 0x0C { + length = 24 + } else if dlc == 0x0D { + length = 32 + } else if dlc == 0x0E { + length = 48 + } else if dlc == 0x0F { + length = 64 + } + return length + +} + +// CANLengthToDlc() converts its actual length to a DLC value. +func CANLengthToDlc(length byte, isFD bool) byte { + dlc := length + if length <= 0x08 { + } else if length <= 12 { + dlc = 0x09 + } else if length <= 16 { + dlc = 0x0A + } else if length <= 20 { + dlc = 0x0B + } else if length <= 24 { + dlc = 0x0C + } else if length <= 32 { + dlc = 0x0D + } else if length <= 48 { + dlc = 0x0E + } else if length <= 64 { + dlc = 0x0F + } + return dlc +} diff --git a/emb/machine/machine_attiny1616.go b/emb/machine/machine_attiny1616.go new file mode 100644 index 00000000..55007ab7 --- /dev/null +++ b/emb/machine/machine_attiny1616.go @@ -0,0 +1,52 @@ +//go:build attiny1616 + +package machine + +import ( + "device/avr" +) + +const ( + portA Pin = iota * 8 + portB + portC +) + +const ( + PA0 = portA + 0 + PA1 = portA + 1 + PA2 = portA + 2 + PA3 = portA + 3 + PA4 = portA + 4 + PA5 = portA + 5 + PA6 = portA + 6 + PA7 = portA + 7 + PB0 = portB + 0 + PB1 = portB + 1 + PB2 = portB + 2 + PB3 = portB + 3 + PB4 = portB + 4 + PB5 = portB + 5 + PB6 = portB + 6 + PB7 = portB + 7 + PC0 = portC + 0 + PC1 = portC + 1 + PC2 = portC + 2 + PC3 = portC + 3 + PC4 = portC + 4 + PC5 = portC + 5 + PC6 = portC + 6 + PC7 = portC + 7 +) + +// getPortMask returns the PORT peripheral and mask for the pin. +func (p Pin) getPortMask() (*avr.PORT_Type, uint8) { + switch { + case p >= PA0 && p <= PA7: // port A + return avr.PORTA, 1 << uint8(p-portA) + case p >= PB0 && p <= PB7: // port B + return avr.PORTB, 1 << uint8(p-portB) + default: // port C + return avr.PORTC, 1 << uint8(p-portC) + } +} diff --git a/emb/machine/machine_attiny85.go b/emb/machine/machine_attiny85.go new file mode 100644 index 00000000..33424c60 --- /dev/null +++ b/emb/machine/machine_attiny85.go @@ -0,0 +1,23 @@ +//go:build attiny85 + +package machine + +import ( + "device/avr" + "runtime/volatile" +) + +const ( + PB0 Pin = iota + PB1 + PB2 + PB3 + PB4 + PB5 +) + +// getPortMask returns the PORTx register and mask for the pin. +func (p Pin) getPortMask() (*volatile.Register8, uint8) { + // Very simple for the attiny85, which only has a single port. + return avr.PORTB, 1 << uint8(p) +} diff --git a/emb/machine/machine_avr.go b/emb/machine/machine_avr.go new file mode 100644 index 00000000..75252c37 --- /dev/null +++ b/emb/machine/machine_avr.go @@ -0,0 +1,150 @@ +//go:build avr && !avrtiny + +package machine + +import ( + "device/avr" + "runtime/volatile" + "unsafe" +) + +const deviceName = avr.DEVICE + +const ( + PinInput PinMode = iota + PinInputPullup + PinOutput +) + +// In all the AVRs I've looked at, the PIN/DDR/PORT registers followed a regular +// pattern: PINx, DDRx, PORTx in this order without registers in between. +// Therefore, if you know any of them, you can calculate the other two. +// +// For now, I've chosen to let the PORTx register be the one that is returned +// for each specific chip and to calculate the others from that one. Setting an +// output port (done using PORTx) is likely the most common operation and the +// one that is the most time critical. For others, the PINx and DDRx register +// can trivially be calculated using a subtraction. + +// Configure sets the pin to input or output. +func (p Pin) Configure(config PinConfig) { + port, mask := p.getPortMask() + // The DDRx register can be found by subtracting one from the PORTx + // register, as this appears to be the case for many (most? all?) AVR chips. + ddr := (*volatile.Register8)(unsafe.Pointer(uintptr(unsafe.Pointer(port)) - 1)) + if config.Mode == PinOutput { + // set output bit + ddr.SetBits(mask) + + // Note: if the pin was PinInputPullup before, it'll now be high. + // Otherwise it will be low. + } else { + // configure input: clear output bit + ddr.ClearBits(mask) + + if config.Mode == PinInput { + // No pullup (floating). + // The transition may be one of the following: + // output high -> input pullup -> input (safe: output high and input pullup are similar) + // output low -> input -> input (safe: no extra transition) + port.ClearBits(mask) + } else { + // Pullup. + // The transition may be one of the following: + // output high -> input pullup -> input pullup (safe: no extra transition) + // output low -> input -> input pullup (possibly problematic) + // For the last transition (output low -> input -> input pullup), + // the transition may be problematic in some cases because there is + // an intermediate floating state (which may cause irratic + // interrupts, for example). If this is a problem, the application + // should set the pin high before configuring it as PinInputPullup. + // We can't do that here because setting it to high as an + // intermediate state may have other problems. + port.SetBits(mask) + } + } +} + +// Get returns the current value of a GPIO pin when the pin is configured as an +// input or as an output. +func (p Pin) Get() bool { + port, mask := p.getPortMask() + // As noted above, the PINx register is always two registers below the PORTx + // register, so we can find it simply by subtracting two from the PORTx + // register address. + pin := (*volatile.Register8)(unsafe.Pointer(uintptr(unsafe.Pointer(port)) - 2)) // PINA, PINB, etc + return (pin.Get() & mask) > 0 +} + +// Set changes the value of the GPIO pin. The pin must be configured as output. +func (p Pin) Set(value bool) { + if value { // set bits + port, mask := p.PortMaskSet() + port.Set(mask) + } else { // clear bits + port, mask := p.PortMaskClear() + port.Set(mask) + } +} + +// Return the register and mask to enable a given GPIO pin. This can be used to +// implement bit-banged drivers. +// +// Warning: there are no separate pin set/clear registers on the AVR. The +// returned mask is only valid as long as no other pin in the same port has been +// changed. +func (p Pin) PortMaskSet() (*volatile.Register8, uint8) { + port, mask := p.getPortMask() + return port, port.Get() | mask +} + +// Return the register and mask to disable a given port. This can be used to +// implement bit-banged drivers. +// +// Warning: there are no separate pin set/clear registers on the AVR. The +// returned mask is only valid as long as no other pin in the same port has been +// changed. +func (p Pin) PortMaskClear() (*volatile.Register8, uint8) { + port, mask := p.getPortMask() + return port, port.Get() &^ mask +} + +// InitADC initializes the registers needed for ADC. +func InitADC() { + // set a2d prescaler so we are inside the desired 50-200 KHz range at 16MHz. + avr.ADCSRA.SetBits(avr.ADCSRA_ADPS2 | avr.ADCSRA_ADPS1 | avr.ADCSRA_ADPS0) + + // enable a2d conversions + avr.ADCSRA.SetBits(avr.ADCSRA_ADEN) +} + +// Configure configures a ADCPin to be able to be used to read data. +func (a ADC) Configure(ADCConfig) { + return // no pin specific setup on AVR machine. +} + +// Get returns the current value of a ADC pin, in the range 0..0xffff. The AVR +// has an ADC of 10 bits precision so the lower 6 bits will be zero. +func (a ADC) Get() uint16 { + // set the analog reference (high two bits of ADMUX) and select the + // channel (low 4 bits), masked to only turn on one ADC at a time. + // set the ADLAR bit (left-adjusted result) to get a value scaled to 16 + // bits. This has the same effect as shifting the return value left by 6 + // bits. + avr.ADMUX.Set(avr.ADMUX_REFS0 | avr.ADMUX_ADLAR | (uint8(a.Pin) & 0x07)) + + // start the conversion + avr.ADCSRA.SetBits(avr.ADCSRA_ADSC) + + // ADSC is cleared when the conversion finishes + for ok := true; ok; ok = avr.ADCSRA.HasBits(avr.ADCSRA_ADSC) { + } + + return uint16(avr.ADCL.Get()) | uint16(avr.ADCH.Get())<<8 +} + +// linked from runtime.adjustMonotonicTimer +func adjustMonotonicTimer() + +// linked from runtime.initMonotonicTimer +func initMonotonicTimer() diff --git a/emb/machine/machine_avrtiny.go b/emb/machine/machine_avrtiny.go new file mode 100644 index 00000000..78168f47 --- /dev/null +++ b/emb/machine/machine_avrtiny.go @@ -0,0 +1,62 @@ +//go:build avrtiny + +package machine + +import ( + "device/avr" + "runtime/volatile" + "unsafe" +) + +const deviceName = avr.DEVICE + +const ( + PinInput PinMode = iota + PinInputPullup + PinOutput +) + +// Configure sets the pin to input or output. +func (p Pin) Configure(config PinConfig) { + port, mask := p.getPortMask() + + if config.Mode == PinOutput { + // set output bit + port.DIRSET.Set(mask) + + // Note: the output state (high or low) is as it was before. + } else { + // Configure the pin as an input. + // First set up the configuration that will be used when it is an input. + pinctrl := uint8(0) + if config.Mode == PinInputPullup { + pinctrl |= avr.PORT_PIN0CTRL_PULLUPEN + } + // Find the PINxCTRL register for this pin. + ctrlAddress := (*volatile.Register8)(unsafe.Add(unsafe.Pointer(&port.PIN0CTRL), p%8)) + ctrlAddress.Set(pinctrl) + + // Configure the pin as input (if it wasn't an input pin before). + port.DIRCLR.Set(mask) + } +} + +// Get returns the current value of a GPIO pin when the pin is configured as an +// input or as an output. +func (p Pin) Get() bool { + port, mask := p.getPortMask() + // As noted above, the PINx register is always two registers below the PORTx + // register, so we can find it simply by subtracting two from the PORTx + // register address. + return (port.IN.Get() & mask) > 0 +} + +// Set changes the value of the GPIO pin. The pin must be configured as output. +func (p Pin) Set(high bool) { + port, mask := p.getPortMask() + if high { + port.OUTSET.Set(mask) + } else { + port.OUTCLR.Set(mask) + } +} diff --git a/emb/machine/machine_cortexm.go b/emb/machine/machine_cortexm.go new file mode 100644 index 00000000..0f8f2647 --- /dev/null +++ b/emb/machine/machine_cortexm.go @@ -0,0 +1,10 @@ +//go:build cortexm + +package machine + +import "device/arm" + +// CPUReset performs a hard system reset. +func CPUReset() { + arm.SystemReset() +} diff --git a/emb/machine/machine_esp32.go b/emb/machine/machine_esp32.go new file mode 100644 index 00000000..237a1292 --- /dev/null +++ b/emb/machine/machine_esp32.go @@ -0,0 +1,545 @@ +//go:build esp32 + +package machine + +import ( + "device/esp" + "errors" + "runtime/volatile" + "unsafe" +) + +const deviceName = esp.Device + +const peripheralClock = 80000000 // 80MHz + +// CPUFrequency returns the current CPU frequency of the chip. +// Currently it is a fixed frequency but it may allow changing in the future. +func CPUFrequency() uint32 { + return 160e6 // 160MHz +} + +var ( + ErrInvalidSPIBus = errors.New("machine: invalid SPI bus") +) + +const ( + PinOutput PinMode = iota + PinInput + PinInputPullup + PinInputPulldown +) + +// Hardware pin numbers +const ( + GPIO0 Pin = 0 + GPIO1 Pin = 1 + GPIO2 Pin = 2 + GPIO3 Pin = 3 + GPIO4 Pin = 4 + GPIO5 Pin = 5 + GPIO6 Pin = 6 + GPIO7 Pin = 7 + GPIO8 Pin = 8 + GPIO9 Pin = 9 + GPIO10 Pin = 10 + GPIO11 Pin = 11 + GPIO12 Pin = 12 + GPIO13 Pin = 13 + GPIO14 Pin = 14 + GPIO15 Pin = 15 + GPIO16 Pin = 16 + GPIO17 Pin = 17 + GPIO18 Pin = 18 + GPIO19 Pin = 19 + GPIO21 Pin = 21 + GPIO22 Pin = 22 + GPIO23 Pin = 23 + GPIO25 Pin = 25 + GPIO26 Pin = 26 + GPIO27 Pin = 27 + GPIO32 Pin = 32 + GPIO33 Pin = 33 + GPIO34 Pin = 34 + GPIO35 Pin = 35 + GPIO36 Pin = 36 + GPIO37 Pin = 37 + GPIO38 Pin = 38 + GPIO39 Pin = 39 +) + +// Configure this pin with the given configuration. +func (p Pin) Configure(config PinConfig) { + // Output function 256 is a special value reserved for use as a regular GPIO + // pin. Peripherals (SPI etc) can set a custom output function by calling + // lowercase configure() instead with a signal name. + p.configure(config, 256) +} + +// configure is the same as Configure, but allows for setting a specific input +// or output signal. +// Signals are always routed through the GPIO matrix for simplicity. Output +// signals are configured in FUNCx_OUT_SEL_CFG which selects a particular signal +// to output on a given pin. Input signals are configured in FUNCy_IN_SEL_CFG, +// which sets the pin to use for a particular input signal. +func (p Pin) configure(config PinConfig, signal uint32) { + if p == NoPin { + // This simplifies pin configuration in peripherals such as SPI. + return + } + + var muxConfig uint32 // The mux configuration. + + // Configure this pin as a GPIO pin. + const function = 3 // function 3 is GPIO for every pin + muxConfig |= (function - 1) << esp.IO_MUX_GPIO0_MCU_SEL_Pos + + // Make this pin an input pin (always). + muxConfig |= esp.IO_MUX_GPIO0_FUN_IE + + // Set drive strength: 0 is lowest, 3 is highest. + muxConfig |= 2 << esp.IO_MUX_GPIO0_FUN_DRV_Pos + + // Select pull mode. + if config.Mode == PinInputPullup { + muxConfig |= esp.IO_MUX_GPIO0_FUN_WPU + } else if config.Mode == PinInputPulldown { + muxConfig |= esp.IO_MUX_GPIO0_FUN_WPD + } + + // Configure the pad with the given IO mux configuration. + p.mux().Set(muxConfig) + + switch config.Mode { + case PinOutput: + // Set the 'output enable' bit. + if p < 32 { + esp.GPIO.ENABLE_W1TS.Set(1 << p) + } else { + esp.GPIO.ENABLE1_W1TS.Set(1 << (p - 32)) + } + // Set the signal to read the output value from. It can be a peripheral + // output signal, or the special value 256 which indicates regular GPIO + // usage. + p.outFunc().Set(signal) + case PinInput, PinInputPullup, PinInputPulldown: + // Clear the 'output enable' bit. + if p < 32 { + esp.GPIO.ENABLE_W1TC.Set(1 << p) + } else { + esp.GPIO.ENABLE1_W1TC.Set(1 << (p - 32)) + } + if signal != 256 { + // Signal is a peripheral function (not a simple GPIO). Connect this + // signal to the pin. + // Note that outFunc and inFunc work in the opposite direction. + // outFunc configures a pin to use a given output signal, while + // inFunc specifies a pin to use to read the signal from. + inFunc(signal).Set(esp.GPIO_FUNC_IN_SEL_CFG_SEL | uint32(p)< pad mapping. + // I couldn't find it. + switch p { + case 36: + return &esp.IO_MUX.GPIO36 + case 37: + return &esp.IO_MUX.GPIO37 + case 38: + return &esp.IO_MUX.GPIO38 + case 39: + return &esp.IO_MUX.GPIO39 + case 34: + return &esp.IO_MUX.GPIO34 + case 35: + return &esp.IO_MUX.GPIO35 + case 32: + return &esp.IO_MUX.GPIO32 + case 33: + return &esp.IO_MUX.GPIO33 + case 25: + return &esp.IO_MUX.GPIO25 + case 26: + return &esp.IO_MUX.GPIO26 + case 27: + return &esp.IO_MUX.GPIO27 + case 14: + return &esp.IO_MUX.GPIO14 + case 12: + return &esp.IO_MUX.GPIO12 + case 13: + return &esp.IO_MUX.GPIO13 + case 15: + return &esp.IO_MUX.GPIO15 + case 2: + return &esp.IO_MUX.GPIO2 + case 0: + return &esp.IO_MUX.GPIO0 + case 4: + return &esp.IO_MUX.GPIO4 + case 16: + return &esp.IO_MUX.GPIO16 + case 17: + return &esp.IO_MUX.GPIO17 + case 9: + return &esp.IO_MUX.GPIO9 + case 10: + return &esp.IO_MUX.GPIO10 + case 11: + return &esp.IO_MUX.GPIO11 + case 6: + return &esp.IO_MUX.GPIO6 + case 7: + return &esp.IO_MUX.GPIO7 + case 8: + return &esp.IO_MUX.GPIO8 + case 5: + return &esp.IO_MUX.GPIO5 + case 18: + return &esp.IO_MUX.GPIO18 + case 19: + return &esp.IO_MUX.GPIO19 + case 20: + return &esp.IO_MUX.GPIO20 + case 21: + return &esp.IO_MUX.GPIO21 + case 22: + return &esp.IO_MUX.GPIO22 + case 3: + return &esp.IO_MUX.GPIO3 + case 1: + return &esp.IO_MUX.GPIO1 + case 23: + return &esp.IO_MUX.GPIO23 + case 24: + return &esp.IO_MUX.GPIO24 + default: + return nil + } +} + +var DefaultUART = UART0 + +var ( + UART0 = &_UART0 + _UART0 = UART{Bus: esp.UART0, Buffer: NewRingBuffer()} + UART1 = &_UART1 + _UART1 = UART{Bus: esp.UART1, Buffer: NewRingBuffer()} + UART2 = &_UART2 + _UART2 = UART{Bus: esp.UART2, Buffer: NewRingBuffer()} +) + +type UART struct { + Bus *esp.UART_Type + Buffer *RingBuffer +} + +func (uart *UART) Configure(config UARTConfig) { + if config.BaudRate == 0 { + config.BaudRate = 115200 + } + uart.Bus.CLKDIV.Set(peripheralClock / config.BaudRate) +} + +func (uart *UART) writeByte(b byte) error { + for (uart.Bus.STATUS.Get()>>16)&0xff >= 128 { + // Read UART_TXFIFO_CNT from the status register, which indicates how + // many bytes there are in the transmit buffer. Wait until there are + // less than 128 bytes in this buffer (the default buffer size). + } + // Write to the TX_FIFO register. + (*volatile.Register8)(unsafe.Add(unsafe.Pointer(uart.Bus), 0x200C0000)).Set(b) + return nil +} + +func (uart *UART) flush() {} + +// Serial Peripheral Interface on the ESP32. +type SPI struct { + Bus *esp.SPI_Type +} + +var ( + // SPI0 and SPI1 are reserved for use by the caching system etc. + SPI2 = &SPI{esp.SPI2} + SPI3 = &SPI{esp.SPI3} +) + +// SPIConfig configures a SPI peripheral on the ESP32. Make sure to set at least +// SCK, SDO and SDI (possibly to NoPin if not in use). The default for LSBFirst +// (false) and Mode (0) are good for most applications. The frequency defaults +// to 1MHz if not set but can be configured up to 40MHz. Possible values are +// 40MHz and integer divisions from 40MHz such as 20MHz, 13.3MHz, 10MHz, 8MHz, +// etc. +type SPIConfig struct { + Frequency uint32 + SCK Pin + SDO Pin + SDI Pin + LSBFirst bool + Mode uint8 +} + +// Configure and make the SPI peripheral ready to use. +func (spi *SPI) Configure(config SPIConfig) error { + if config.Frequency == 0 { + config.Frequency = 4e6 // default to 4MHz + } + + // Configure the SPI clock. This assumes a peripheral clock of 80MHz. + var clockReg uint32 + if config.Frequency > 40e6 { + // Don't use a prescaler, but directly connect to the APB clock. This + // results in a SPI clock frequency of 40MHz. + clockReg |= esp.SPI_CLOCK_CLK_EQU_SYSCLK + } else { + // Use a prescaler for frequencies below 40MHz. They will get rounded + // down to the next possible frequency (20MHz, 13.3MHz, 10MHz, 8MHz, + // 6.7MHz, 5.7MHz, 5MHz, etc). + // This code is much simpler than how ESP-IDF configures the frequency, + // but should be just as accurate. The only exception is for frequencies + // below 4883Hz, which will need special support. + if config.Frequency < 4883 { + // The current lower limit is 4883Hz. + // The hardware supports lower frequencies by setting the h and n + // variables, but that's not yet implemented. + config.Frequency = 4883 + } + // The prescaler value is 40e6 / config.Frequency, but rounded up so + // that the actual frequency is never higher than the frequency + // requested in config.Frequency. + var ( + pre uint32 = (40e6 + config.Frequency - 1) / config.Frequency + n uint32 = 2 // this value seems to equal the number of ticks per SPI clock tick + h uint32 = 1 // must be half of n according to the formula in the reference manual + l uint32 = n // must equal n according to the reference manual + ) + clockReg |= (pre - 1) << esp.SPI_CLOCK_CLKDIV_PRE_Pos + clockReg |= (n - 1) << esp.SPI_CLOCK_CLKCNT_N_Pos + clockReg |= (h - 1) << esp.SPI_CLOCK_CLKCNT_H_Pos + clockReg |= (l - 1) << esp.SPI_CLOCK_CLKCNT_L_Pos + } + spi.Bus.CLOCK.Set(clockReg) + + // SPI_CTRL_REG controls bit order. + var ctrlReg uint32 + if config.LSBFirst { + ctrlReg |= esp.SPI_CTRL_WR_BIT_ORDER + ctrlReg |= esp.SPI_CTRL_RD_BIT_ORDER + } + spi.Bus.CTRL.Set(ctrlReg) + + // SPI_CTRL2_REG, SPI_USER_REG and SPI_PIN_REG control SPI clock polarity + // (mode), among others. + var ctrl2Reg, userReg, pinReg uint32 + // For mode configuration, see table 29 in the reference manual (page 128). + switch config.Mode { + case 0: + case 1: + userReg |= esp.SPI_USER_CK_OUT_EDGE + case 2: + userReg |= esp.SPI_USER_CK_OUT_EDGE + pinReg |= esp.SPI_PIN_CK_IDLE_EDGE + case 3: + pinReg |= esp.SPI_PIN_CK_IDLE_EDGE + } + // Enable full-duplex communication. + userReg |= esp.SPI_USER_DOUTDIN + userReg |= esp.SPI_USER_USR_MOSI + // Write values to registers. + spi.Bus.CTRL2.Set(ctrl2Reg) + spi.Bus.USER.Set(userReg) + spi.Bus.PIN.Set(pinReg) + + // Configure pins. + // TODO: use direct output if possible, if the configured pins match the + // possible direct configurations (e.g. for SPI2, when SCK is pin 14 etc). + if spi.Bus == esp.SPI2 { + config.SCK.configure(PinConfig{Mode: PinOutput}, 8) // HSPICLK + config.SDI.configure(PinConfig{Mode: PinInput}, 9) // HSPIQ + config.SDO.configure(PinConfig{Mode: PinOutput}, 10) // HSPID + } else if spi.Bus == esp.SPI3 { + config.SCK.configure(PinConfig{Mode: PinOutput}, 63) // VSPICLK + config.SDI.configure(PinConfig{Mode: PinInput}, 64) // VSPIQ + config.SDO.configure(PinConfig{Mode: PinOutput}, 65) // VSPID + } else { + // Don't know how to configure this bus. + return ErrInvalidSPIBus + } + + return nil +} + +// Transfer writes/reads a single byte using the SPI interface. If you need to +// transfer larger amounts of data, Tx will be faster. +func (spi *SPI) Transfer(w byte) (byte, error) { + spi.Bus.MISO_DLEN.Set(7 << esp.SPI_MISO_DLEN_USR_MISO_DBITLEN_Pos) + spi.Bus.MOSI_DLEN.Set(7 << esp.SPI_MOSI_DLEN_USR_MOSI_DBITLEN_Pos) + + spi.Bus.W0.Set(uint32(w)) + + // Send/receive byte. + spi.Bus.CMD.Set(esp.SPI_CMD_USR) + for spi.Bus.CMD.Get() != 0 { + } + + // The received byte is stored in W0. + return byte(spi.Bus.W0.Get()), nil +} + +// Tx handles read/write operation for SPI interface. Since SPI is a synchronous write/read +// interface, there must always be the same number of bytes written as bytes read. +// This is accomplished by sending zero bits if r is bigger than w or discarding +// the incoming data if w is bigger than r. +func (spi *SPI) Tx(w, r []byte) error { + toTransfer := len(w) + if len(r) > toTransfer { + toTransfer = len(r) + } + + for toTransfer != 0 { + // Do only 64 bytes at a time. + chunkSize := toTransfer + if chunkSize > 64 { + chunkSize = 64 + } + + // Fill tx buffer. + transferWords := (*[16]volatile.Register32)(unsafe.Pointer(uintptr(unsafe.Pointer(&spi.Bus.W0)))) + if len(w) >= 64 { + // We can fill the entire 64-byte transfer buffer with data. + // This loop is slightly faster than the loop below. + for i := 0; i < 16; i++ { + word := uint32(w[i*4])<<0 | uint32(w[i*4+1])<<8 | uint32(w[i*4+2])<<16 | uint32(w[i*4+3])<<24 + transferWords[i].Set(word) + } + } else { + // We can't fill the entire transfer buffer, so we need to be a bit + // more careful. + // Note that parts of the transfer buffer that aren't used still + // need to be set to zero, otherwise we might be transferring + // garbage from a previous transmission if w is smaller than r. + for i := 0; i < 16; i++ { + var word uint32 + if i*4+3 < len(w) { + word |= uint32(w[i*4+3]) << 24 + } + if i*4+2 < len(w) { + word |= uint32(w[i*4+2]) << 16 + } + if i*4+1 < len(w) { + word |= uint32(w[i*4+1]) << 8 + } + if i*4+0 < len(w) { + word |= uint32(w[i*4+0]) << 0 + } + transferWords[i].Set(word) + } + } + + // Do the transfer. + spi.Bus.MISO_DLEN.Set((uint32(chunkSize)*8 - 1) << esp.SPI_MISO_DLEN_USR_MISO_DBITLEN_Pos) + spi.Bus.MOSI_DLEN.Set((uint32(chunkSize)*8 - 1) << esp.SPI_MOSI_DLEN_USR_MOSI_DBITLEN_Pos) + spi.Bus.CMD.Set(esp.SPI_CMD_USR) + for spi.Bus.CMD.Get() != 0 { + } + + // Read rx buffer. + rxSize := 64 + if rxSize > len(r) { + rxSize = len(r) + } + for i := 0; i < rxSize; i++ { + r[i] = byte(transferWords[i/4].Get() >> ((i % 4) * 8)) + } + + // Cut off some part of the output buffer so the next iteration we will + // only send the remaining bytes. + if len(w) < chunkSize { + w = nil + } else { + w = w[chunkSize:] + } + if len(r) < chunkSize { + r = nil + } else { + r = r[chunkSize:] + } + toTransfer -= chunkSize + } + + return nil +} diff --git a/emb/machine/machine_esp32_i2c.go b/emb/machine/machine_esp32_i2c.go new file mode 100644 index 00000000..746e722d --- /dev/null +++ b/emb/machine/machine_esp32_i2c.go @@ -0,0 +1,412 @@ +//go:build esp32 + +package machine + +import ( + "device/esp" + "runtime/volatile" + "unsafe" +) + +var ( + I2C0 = &I2C{Bus: esp.I2C0, funcSCL: 29, funcSDA: 30} + I2C1 = &I2C{Bus: esp.I2C1, funcSCL: 95, funcSDA: 96} +) + +type I2C struct { + Bus *esp.I2C_Type + funcSCL, funcSDA uint32 + config I2CConfig +} + +// I2CConfig is used to store config info for I2C. +type I2CConfig struct { + Frequency uint32 // in Hz + SCL Pin + SDA Pin +} + +const ( + i2cClkSourceFrequency = uint32(80 * MHz) +) + +func (i2c *I2C) Configure(config I2CConfig) error { + if config.Frequency == 0 { + config.Frequency = 400 * KHz + } + if config.SCL == 0 { + config.SCL = SCL_PIN + } + if config.SDA == 0 { + config.SDA = SDA_PIN + } + i2c.config = config + + i2c.initAll() + return nil +} + +func (i2c *I2C) initAll() { + i2c.initClock() + i2c.initNoiseFilter() + i2c.initPins() + i2c.initFrequency() + i2c.startMaster() +} + +//go:inline +func (i2c *I2C) initClock() { + // reset I2C clock + if i2c.Bus == esp.I2C0 { + esp.DPORT.SetPERIP_RST_EN_I2C0_EXT0_RST(1) + esp.DPORT.SetPERIP_CLK_EN_I2C0_EXT0_CLK_EN(1) + esp.DPORT.SetPERIP_RST_EN_I2C0_EXT0_RST(0) + } else { + esp.DPORT.SetPERIP_RST_EN_I2C_EXT1_RST(1) + esp.DPORT.SetPERIP_CLK_EN_I2C_EXT1_CLK_EN(1) + esp.DPORT.SetPERIP_RST_EN_I2C_EXT1_RST(0) + } + // disable interrupts + i2c.Bus.INT_ENA.Set(0) + i2c.Bus.INT_CLR.Set(0x3fff) + + i2c.Bus.SetCTR_CLK_EN(1) +} + +//go:inline +func (i2c *I2C) initNoiseFilter() { + i2c.Bus.SCL_FILTER_CFG.Set(0xF) + i2c.Bus.SDA_FILTER_CFG.Set(0xF) +} + +//go:inline +func (i2c *I2C) initPins() { + var muxConfig uint32 + const function = 2 // function 2 is just GPIO + + // SDA + muxConfig = function << esp.IO_MUX_GPIO0_MCU_SEL_Pos + // Make this pin an input pin (always). + muxConfig |= esp.IO_MUX_GPIO0_FUN_IE + // Set drive strength: 0 is lowest, 3 is highest. + muxConfig |= 1 << esp.IO_MUX_GPIO0_FUN_DRV_Pos + i2c.config.SDA.mux().Set(muxConfig) + i2c.config.SDA.outFunc().Set(i2c.funcSDA) + inFunc(i2c.funcSDA).Set(uint32(esp.GPIO_FUNC_IN_SEL_CFG_SEL | i2c.config.SDA)) + i2c.config.SDA.Set(true) + // Configure the pad with the given IO mux configuration. + i2c.config.SDA.pinReg().SetBits(esp.GPIO_PIN_PAD_DRIVER) + + esp.GPIO.ENABLE_W1TS.Set(1 << int(i2c.config.SDA)) + i2c.Bus.SetCTR_SDA_FORCE_OUT(1) + + // SCL + muxConfig = function << esp.IO_MUX_GPIO0_MCU_SEL_Pos + // Make this pin an input pin (always). + muxConfig |= esp.IO_MUX_GPIO0_FUN_IE + // Set drive strength: 0 is lowest, 3 is highest. + muxConfig |= 1 << esp.IO_MUX_GPIO0_FUN_DRV_Pos + i2c.config.SCL.mux().Set(muxConfig) + i2c.config.SCL.outFunc().Set(i2c.funcSCL) + inFunc(i2c.funcSCL).Set(uint32(esp.GPIO_FUNC_IN_SEL_CFG_SEL | i2c.config.SCL)) + i2c.config.SCL.Set(true) + // Configure the pad with the given IO mux configuration. + i2c.config.SCL.pinReg().SetBits(esp.GPIO_PIN_PAD_DRIVER) + + esp.GPIO.ENABLE_W1TS.Set(1 << int(i2c.config.SCL)) + i2c.Bus.SetCTR_SCL_FORCE_OUT(1) +} + +//go:inline +func (i2c *I2C) initFrequency() { + clkmDiv := i2cClkSourceFrequency/(i2c.config.Frequency*1024) + 1 + sclkFreq := i2cClkSourceFrequency / clkmDiv + halfCycle := sclkFreq / i2c.config.Frequency / 2 + //SCL + sclLow := halfCycle + sclWaitHigh := uint32(0) + if i2c.config.Frequency > 50000 { + sclWaitHigh = halfCycle / 8 // compensate the time when freq > 50K + } + sclHigh := halfCycle - sclWaitHigh + // SDA + sdaHold := halfCycle / 4 + sda_sample := halfCycle / 2 + setup := halfCycle + hold := halfCycle + + i2c.Bus.SetSCL_LOW_PERIOD(sclLow - 1) + i2c.Bus.SetSCL_HIGH_PERIOD(sclHigh) + i2c.Bus.SetSCL_RSTART_SETUP_TIME(setup) + i2c.Bus.SetSCL_STOP_SETUP_TIME(setup) + i2c.Bus.SetSCL_START_HOLD_TIME(hold - 1) + i2c.Bus.SetSCL_STOP_HOLD_TIME(hold - 1) + i2c.Bus.SetSDA_SAMPLE_TIME(sda_sample) + i2c.Bus.SetSDA_HOLD_TIME(sdaHold) + // set timeout value + i2c.Bus.SetTO_TIME_OUT(20 * halfCycle) +} + +//go:inline +func (i2c *I2C) startMaster() { + // FIFO mode for data + i2c.Bus.SetFIFO_CONF_NONFIFO_EN(0) + // Reset TX & RX buffers + i2c.Bus.SetFIFO_CONF_RX_FIFO_RST(1) + i2c.Bus.SetFIFO_CONF_RX_FIFO_RST(0) + i2c.Bus.SetFIFO_CONF_TX_FIFO_RST(1) + i2c.Bus.SetFIFO_CONF_TX_FIFO_RST(0) + // enable master mode + i2c.Bus.SetCTR_MS_MODE(1) +} + +func (i2c *I2C) resetBus() { + // unlike esp32c3, the esp32 i2c modules do not have a reset fsm register, + // so we need to: + // 1. disconnect the pins + // 2. generate a stop condition manually + // 3. do a full reset + // 4. redo all configuration + + i2c.config.SDA.mux().Set(2< 0 && c.head < len(c.data); count, c.head = count-1, c.head+1 { + i2c.Bus.SetDATA_FIFO_RDATA(uint32(c.data[c.head])) + } + reg.Set(i2cCMD_WRITE | uint32(32-count)) + reg = nextAddress(reg) + + if c.head < len(c.data) { + reg.Set(i2cCMD_END) + reg = nil + } else { + cmdIdx++ + } + needRestart = true + + case i2cCMD_READ: + if needAddress { + needAddress = false + i2c.Bus.SetDATA_FIFO_RDATA((uint32(addr)&0x7f)<<1 | 1) + i2c.Bus.SLAVE_ADDR.Set(uint32(addr)) + reg.Set(i2cCMD_WRITE | 1) + reg = nextAddress(reg) + } + if needRestart { + // We need to send RESTART again after i2cCMD_WRITE. + reg.Set(i2cCMD_RSTART) + + reg = nextAddress(reg) + reg.Set(i2cCMD_WRITE | 1) + + reg = nextAddress(reg) + i2c.Bus.SetDATA_FIFO_RDATA((uint32(addr)&0x7f)<<1 | 1) + needRestart = false + } + count := 32 + bytes := len(c.data) - c.head + // Only last byte in sequence must be sent with ACK set to 1 to indicate end of data. + split := bytes <= count + if split { + bytes-- + } + if bytes > 32 { + bytes = 32 + } + if bytes > 0 { + reg.Set(i2cCMD_READ | uint32(bytes)) + reg = nextAddress(reg) + } + + if split { + readLast = true + reg.Set(i2cCMD_READLAST | 1) + reg = nextAddress(reg) + readTo = c.data[c.head : c.head+bytes+1] // read bytes + 1 last byte + cmdIdx++ + } else { + reg.Set(i2cCMD_END) + readTo = c.data[c.head : c.head+bytes] + reg = nil + } + + case i2cCMD_STOP: + reg.Set(i2cCMD_STOP) + reg = nil + cmdIdx++ + } + if reg == nil { + // transmit now + i2c.Bus.SetCTR_TRANS_START(1) + end := nanotime() + timeoutNS + var mask uint32 + for mask = i2c.Bus.INT_STATUS.Get(); mask&intMask == 0; mask = i2c.Bus.INT_STATUS.Get() { + if nanotime() > end { + // timeout leaves the bus in an undefined state, reset + i2c.resetBus() + if readTo != nil { + return errI2CReadTimeout + } + return errI2CWriteTimeout + } + } + switch { + case mask&esp.I2C_INT_STATUS_ACK_ERR_INT_ST_Msk != 0 && !readLast: + return errI2CAckExpected + case mask&esp.I2C_INT_STATUS_TIME_OUT_INT_ST_Msk != 0: + // timeout leaves the bus in an undefined state, reset + i2c.resetBus() + if readTo != nil { + return errI2CReadTimeout + } + return errI2CWriteTimeout + } + i2c.Bus.INT_CLR.SetBits(intMask) + for i := 0; i < len(readTo); i++ { + readTo[i] = byte(i2c.Bus.GetDATA_FIFO_RDATA() & 0xff) + c.head++ + } + readTo = nil + reg = &i2c.Bus.COMD0 + } + } + return nil +} + +// Tx does a single I2C transaction at the specified address. +// It clocks out the given address, writes the bytes in w, reads back len(r) +// bytes and stores them in r, and generates a stop condition on the bus. +func (i2c *I2C) Tx(addr uint16, w, r []byte) (err error) { + // timeout in microseconds. + const timeout = 40 // 40ms is a reasonable time for a real-time system. + + cmd := make([]i2cCommand, 0, 8) + cmd = append(cmd, i2cCommand{cmd: i2cCMD_RSTART}) + if len(w) > 0 { + cmd = append(cmd, i2cCommand{cmd: i2cCMD_WRITE, data: w}) + } + if len(r) > 0 { + cmd = append(cmd, i2cCommand{cmd: i2cCMD_READ, data: r}) + } + cmd = append(cmd, i2cCommand{cmd: i2cCMD_STOP}) + + return i2c.transmit(addr, cmd, timeout) +} + +func (i2c *I2C) SetBaudRate(br uint32) error { + return errI2CNotImplemented +} + +func (p Pin) pinReg() *volatile.Register32 { + return (*volatile.Register32)(unsafe.Pointer((uintptr(unsafe.Pointer(&esp.GPIO.PIN0)) + uintptr(p)*4))) +} + +func nextAddress(reg *volatile.Register32) *volatile.Register32 { + return (*volatile.Register32)(unsafe.Add(unsafe.Pointer(reg), 4)) +} + +// CheckDevice does an empty I2C transaction at the specified address. +// This can be used to find out if any device with that address is +// connected, e.g. for enumerating all devices on the bus. +func (i2c *I2C) CheckDevice(addr uint16) bool { + // timeout in microseconds. + const timeout = 40 // 40ms is a reasonable time for a real-time system. + + cmd := []i2cCommand{ + {cmd: i2cCMD_RSTART}, + {cmd: i2cCMD_WRITE}, + {cmd: i2cCMD_STOP}, + } + return i2c.transmit(addr, cmd, timeout) == nil +} diff --git a/emb/machine/machine_esp32c3.go b/emb/machine/machine_esp32c3.go new file mode 100644 index 00000000..727fcc1e --- /dev/null +++ b/emb/machine/machine_esp32c3.go @@ -0,0 +1,637 @@ +//go:build esp32c3 + +package machine + +import ( + "device/esp" + "device/riscv" + "errors" + "runtime/interrupt" + "runtime/volatile" + "sync" + "unsafe" +) + +const deviceName = esp.Device +const maxPin = 22 +const cpuInterruptFromPin = 6 + +// CPUFrequency returns the current CPU frequency of the chip. +// Currently it is a fixed frequency but it may allow changing in the future. +func CPUFrequency() uint32 { + return 160e6 // 160MHz +} + +const ( + PinOutput PinMode = iota + PinInput + PinInputPullup + PinInputPulldown +) + +const ( + GPIO0 Pin = 0 + GPIO1 Pin = 1 + GPIO2 Pin = 2 + GPIO3 Pin = 3 + GPIO4 Pin = 4 + GPIO5 Pin = 5 + GPIO6 Pin = 6 + GPIO7 Pin = 7 + GPIO8 Pin = 8 + GPIO9 Pin = 9 + GPIO10 Pin = 10 + GPIO11 Pin = 11 + GPIO12 Pin = 12 + GPIO13 Pin = 13 + GPIO14 Pin = 14 + GPIO15 Pin = 15 + GPIO16 Pin = 16 + GPIO17 Pin = 17 + GPIO18 Pin = 18 + GPIO19 Pin = 19 + GPIO20 Pin = 20 + GPIO21 Pin = 21 +) + +type PinChange uint8 + +// Pin change interrupt constants for SetInterrupt. +const ( + PinRising PinChange = iota + 1 + PinFalling + PinToggle +) + +// Configure this pin with the given configuration. +func (p Pin) Configure(config PinConfig) { + if p == NoPin { + // This simplifies pin configuration in peripherals such as SPI. + return + } + + var muxConfig uint32 + + // Configure this pin as a GPIO pin. + const function = 1 // function 1 is GPIO for every pin + muxConfig |= function << esp.IO_MUX_GPIO_MCU_SEL_Pos + + // Make this pin an input pin (always). + muxConfig |= esp.IO_MUX_GPIO_FUN_IE + + // Set drive strength: 0 is lowest, 3 is highest. + muxConfig |= 2 << esp.IO_MUX_GPIO_FUN_DRV_Pos + + // Select pull mode. + if config.Mode == PinInputPullup { + muxConfig |= esp.IO_MUX_GPIO_FUN_WPU + } else if config.Mode == PinInputPulldown { + muxConfig |= esp.IO_MUX_GPIO_FUN_WPD + } + + // Configure the pad with the given IO mux configuration. + p.mux().Set(muxConfig) + + // Set the output signal to the simple GPIO output. + p.outFunc().Set(0x80) + + switch config.Mode { + case PinOutput: + // Set the 'output enable' bit. + esp.GPIO.ENABLE_W1TS.Set(1 << p) + case PinInput, PinInputPullup, PinInputPulldown: + // Clear the 'output enable' bit. + esp.GPIO.ENABLE_W1TC.Set(1 << p) + } +} + +// outFunc returns the FUNCx_OUT_SEL_CFG register used for configuring the +// output function selection. +func (p Pin) outFunc() *volatile.Register32 { + return (*volatile.Register32)(unsafe.Add(unsafe.Pointer(&esp.GPIO.FUNC0_OUT_SEL_CFG), uintptr(p)*4)) +} + +func (p Pin) pinReg() *volatile.Register32 { + return (*volatile.Register32)(unsafe.Pointer((uintptr(unsafe.Pointer(&esp.GPIO.PIN0)) + uintptr(p)*4))) +} + +// inFunc returns the FUNCy_IN_SEL_CFG register used for configuring the input +// function selection. +func inFunc(signal uint32) *volatile.Register32 { + return (*volatile.Register32)(unsafe.Add(unsafe.Pointer(&esp.GPIO.FUNC0_IN_SEL_CFG), uintptr(signal)*4)) +} + +// mux returns the I/O mux configuration register corresponding to the given +// GPIO pin. +func (p Pin) mux() *volatile.Register32 { + return (*volatile.Register32)(unsafe.Add(unsafe.Pointer(&esp.IO_MUX.GPIO0), uintptr(p)*4)) +} + +// pin returns the PIN register corresponding to the given GPIO pin. +func (p Pin) pin() *volatile.Register32 { + return (*volatile.Register32)(unsafe.Add(unsafe.Pointer(&esp.GPIO.PIN0), uintptr(p)*4)) +} + +// Set the pin to high or low. +// Warning: only use this on an output pin! +func (p Pin) Set(value bool) { + if value { + reg, mask := p.portMaskSet() + reg.Set(mask) + } else { + reg, mask := p.portMaskClear() + reg.Set(mask) + } +} + +// Get returns the current value of a GPIO pin when configured as an input or as +// an output. +func (p Pin) Get() bool { + reg := &esp.GPIO.IN + return (reg.Get()>>p)&1 > 0 +} + +// Return the register and mask to enable a given GPIO pin. This can be used to +// implement bit-banged drivers. +// +// Warning: only use this on an output pin! +func (p Pin) PortMaskSet() (*uint32, uint32) { + reg, mask := p.portMaskSet() + return ®.Reg, mask +} + +// Return the register and mask to disable a given GPIO pin. This can be used to +// implement bit-banged drivers. +// +// Warning: only use this on an output pin! +func (p Pin) PortMaskClear() (*uint32, uint32) { + reg, mask := p.portMaskClear() + return ®.Reg, mask +} + +func (p Pin) portMaskSet() (*volatile.Register32, uint32) { + return &esp.GPIO.OUT_W1TS, 1 << p +} + +func (p Pin) portMaskClear() (*volatile.Register32, uint32) { + return &esp.GPIO.OUT_W1TC, 1 << p +} + +// SetInterrupt sets an interrupt to be executed when a particular pin changes +// state. The pin should already be configured as an input, including a pull up +// or down if no external pull is provided. +// +// You can pass a nil func to unset the pin change interrupt. If you do so, +// the change parameter is ignored and can be set to any value (such as 0). +// If the pin is already configured with a callback, you must first unset +// this pins interrupt before you can set a new callback. +func (p Pin) SetInterrupt(change PinChange, callback func(Pin)) (err error) { + if p >= maxPin { + return ErrInvalidInputPin + } + + if callback == nil { + // Disable this pin interrupt + p.pin().ClearBits(esp.GPIO_PIN_INT_TYPE_Msk | esp.GPIO_PIN_INT_ENA_Msk) + + if pinCallbacks[p] != nil { + pinCallbacks[p] = nil + } + return nil + } + + if pinCallbacks[p] != nil { + // The pin was already configured. + // To properly re-configure a pin, unset it first and set a new + // configuration. + return ErrNoPinChangeChannel + } + pinCallbacks[p] = callback + + onceSetupPinInterrupt.Do(func() { + err = setupPinInterrupt() + }) + if err != nil { + return err + } + + p.pin().Set( + (p.pin().Get() & ^uint32(esp.GPIO_PIN_INT_TYPE_Msk|esp.GPIO_PIN_INT_ENA_Msk)) | + uint32(change)< 1 { + return errWrongStopBitSize + } + // - data length + uart.Bus.SetCONF0_BIT_NUM(uint32(dataBits - 5)) + // - stop bit + uart.Bus.SetCONF0_STOP_BIT_NUM(uint32(stopBits)) + // - parity check + switch parity { + case ParityNone: + uart.Bus.SetCONF0_PARITY_EN(0) + case ParityEven: + uart.Bus.SetCONF0_PARITY_EN(1) + uart.Bus.SetCONF0_PARITY(0) + case ParityOdd: + uart.Bus.SetCONF0_PARITY_EN(1) + uart.Bus.SetCONF0_PARITY(1) + } + return nil +} + +func initUARTClock(bus *esp.UART_Type, regs registerSet) { + uartClock := &esp.SYSTEM.PERIP_CLK_EN0 + uartClockReset := &esp.SYSTEM.PERIP_RST_EN0 + + // Initialize/reset URATn (Ref: Initializing URATn) + // - enable the clock for UART RAM + uartClock.SetBits(esp.SYSTEM_PERIP_CLK_EN0_UART_MEM_CLK_EN) + // - enable APB_CLK for UARTn + uartClock.SetBits(regs.uartClockBitMask) + // - reset sequence + uartClockReset.ClearBits(regs.uartClockBitMask) + bus.SetCLK_CONF_RST_CORE(1) + uartClockReset.SetBits(regs.uartClockBitMask) + uartClockReset.ClearBits(regs.uartClockBitMask) + bus.SetCLK_CONF_RST_CORE(0) + // synchronize core register + bus.SetID_REG_UPDATE(0) + // enable RTC clock + esp.RTC_CNTL.SetCLK_CONF_DIG_CLK8M_EN(1) + // wait for Core Clock to ready for configuration + for bus.GetID_REG_UPDATE() > 0 { + riscv.Asm("nop") + } +} + +func (uart *UART) SetBaudRate(baudRate uint32) { + // based on esp-idf + max_div := uint32((1 << 12) - 1) + sclk_div := (pplClockFreq + (max_div * baudRate) - 1) / (max_div * baudRate) + clk_div := (pplClockFreq << 4) / (baudRate * sclk_div) + uart.Bus.SetCLKDIV(clk_div >> 4) + uart.Bus.SetCLKDIV_FRAG(clk_div & 0xf) + uart.Bus.SetCLK_CONF_SCLK_DIV_NUM(sclk_div - 1) +} + +func (uart *UART) setupPins(config UARTConfig, regs registerSet) { + config.RX.Configure(PinConfig{Mode: PinInputPullup}) + config.TX.Configure(PinConfig{Mode: PinInputPullup}) + + // link TX with GPIO signal X (technical reference manual 5.10) (this is not interrupt signal!) + config.TX.outFunc().Set(regs.gpioMatrixSignal) + // link RX with GPIO signal X and route signals via GPIO matrix (GPIO_SIGn_IN_SEL 0x40) + inFunc(regs.gpioMatrixSignal).Set(esp.GPIO_FUNC_IN_SEL_CFG_SEL | uint32(config.RX)) +} + +func (uart *UART) configureInterrupt(intrMapReg *volatile.Register32) { // Disable all UART interrupts + // Disable all UART interrupts + uart.Bus.INT_ENA.ClearBits(0x0ffff) + + intrMapReg.Set(7) + onceUart.Do(func() { + _ = interrupt.New(7, func(i interrupt.Interrupt) { + UART0.serveInterrupt(0) + UART1.serveInterrupt(1) + }).Enable() + }) +} + +func (uart *UART) serveInterrupt(num int) { + // get interrupt status + interrutFlag := uart.Bus.INT_ST.Get() + if (interrutFlag & uartInterrupts) == 0 { + return + } + + // block UART interrupts while processing + uart.Bus.INT_ENA.ClearBits(uartInterrupts) + + if interrutFlag&esp.UART_INT_ENA_RXFIFO_FULL_INT_ENA > 0 { + for uart.Bus.GetSTATUS_RXFIFO_CNT() > 0 { + b := uart.Bus.GetFIFO_RXFIFO_RD_BYTE() + if !uart.Buffer.Put(byte(b & 0xff)) { + uart.DataOverflowDetected = true + } + } + } + if interrutFlag&esp.UART_INT_ENA_PARITY_ERR_INT_ENA > 0 { + uart.ParityErrorDetected = true + } + if 0 != interrutFlag&esp.UART_INT_ENA_FRM_ERR_INT_ENA { + uart.DataErrorDetected = true + } + if 0 != interrutFlag&esp.UART_INT_ENA_RXFIFO_OVF_INT_ENA { + uart.DataOverflowDetected = true + } + if 0 != interrutFlag&esp.UART_INT_ENA_GLITCH_DET_INT_ENA { + uart.DataErrorDetected = true + } + + // Clear the UART interrupt status + uart.Bus.INT_CLR.SetBits(interrutFlag) + uart.Bus.INT_CLR.ClearBits(interrutFlag) + // Enable interrupts + uart.Bus.INT_ENA.Set(uartInterrupts) +} + +const uart_empty_thresh_default = 10 + +func (uart *UART) enableTransmitter() { + uart.Bus.SetCONF0_TXFIFO_RST(1) + uart.Bus.SetCONF0_TXFIFO_RST(0) + // TXINFO empty threshold is when txfifo_empty_int interrupt produced after the amount of data in Tx-FIFO is less than this register value. + uart.Bus.SetCONF1_TXFIFO_EMPTY_THRHD(uart_empty_thresh_default) + // we are not using interrupt on TX since write we are waiting for FIFO to have space. + // uart.Bus.INT_ENA.SetBits(esp.UART_INT_ENA_TXFIFO_EMPTY_INT_ENA) +} + +func (uart *UART) enableReceiver() { + uart.Bus.SetCONF0_RXFIFO_RST(1) + uart.Bus.SetCONF0_RXFIFO_RST(0) + // using value 1 so that we can start populate ring buffer with data as we get it + uart.Bus.SetCONF1_RXFIFO_FULL_THRHD(1) + // enable interrupts for: + uart.Bus.SetINT_ENA_RXFIFO_FULL_INT_ENA(1) + uart.Bus.SetINT_ENA_FRM_ERR_INT_ENA(1) + uart.Bus.SetINT_ENA_PARITY_ERR_INT_ENA(1) + uart.Bus.SetINT_ENA_GLITCH_DET_INT_ENA(1) + uart.Bus.SetINT_ENA_RXFIFO_OVF_INT_ENA(1) +} + +func (uart *UART) writeByte(b byte) error { + for (uart.Bus.STATUS.Get()&esp.UART_STATUS_TXFIFO_CNT_Msk)>>esp.UART_STATUS_TXFIFO_CNT_Pos >= 128 { + // Read UART_TXFIFO_CNT from the status register, which indicates how + // many bytes there are in the transmit buffer. Wait until there are + // less than 128 bytes in this buffer (the default buffer size). + } + uart.Bus.FIFO.Set(uint32(b)) + return nil +} + +func (uart *UART) flush() {} + +type Serialer interface { + WriteByte(c byte) error + Write(data []byte) (n int, err error) + Configure(config UARTConfig) error + Buffered() int + ReadByte() (byte, error) + DTR() bool + RTS() bool +} + +// USB Serial/JTAG Controller +// See esp32-c3_technical_reference_manual_en.pdf +// pg. 736 +type USB_DEVICE struct { + Bus *esp.USB_DEVICE_Type +} + +var ( + _USBCDC = &USB_DEVICE{ + Bus: esp.USB_DEVICE, + } + + USBCDC Serialer = _USBCDC +) + +var ( + errUSBWrongSize = errors.New("USB: invalid write size") + errUSBCouldNotWriteAllData = errors.New("USB: could not write all data") + errUSBBufferEmpty = errors.New("USB: read buffer empty") +) + +func (usbdev *USB_DEVICE) Configure(config UARTConfig) error { + return nil +} + +func (usbdev *USB_DEVICE) WriteByte(c byte) error { + if usbdev.Bus.GetEP1_CONF_SERIAL_IN_EP_DATA_FREE() == 0 { + return errUSBCouldNotWriteAllData + } + + usbdev.Bus.SetEP1_RDWR_BYTE(uint32(c)) + usbdev.flush() + + return nil +} + +func (usbdev *USB_DEVICE) Write(data []byte) (n int, err error) { + if len(data) == 0 || len(data) > 64 { + return 0, errUSBWrongSize + } + + for i, c := range data { + if usbdev.Bus.GetEP1_CONF_SERIAL_IN_EP_DATA_FREE() == 0 { + if i > 0 { + usbdev.flush() + } + + return i, errUSBCouldNotWriteAllData + } + usbdev.Bus.SetEP1_RDWR_BYTE(uint32(c)) + } + + usbdev.flush() + return len(data), nil +} + +func (usbdev *USB_DEVICE) Buffered() int { + return int(usbdev.Bus.GetEP1_CONF_SERIAL_OUT_EP_DATA_AVAIL()) +} + +func (usbdev *USB_DEVICE) ReadByte() (byte, error) { + if usbdev.Bus.GetEP1_CONF_SERIAL_OUT_EP_DATA_AVAIL() != 0 { + return byte(usbdev.Bus.GetEP1_RDWR_BYTE()), nil + } + + return 0, nil +} + +func (usbdev *USB_DEVICE) DTR() bool { + return false +} + +func (usbdev *USB_DEVICE) RTS() bool { + return false +} + +func (usbdev *USB_DEVICE) flush() { + usbdev.Bus.SetEP1_CONF_WR_DONE(1) + for usbdev.Bus.GetEP1_CONF_SERIAL_IN_EP_DATA_FREE() == 0 { + } +} + +// GetRNG returns 32-bit random numbers using the ESP32-C3 true random number generator, +// Random numbers are generated based on the thermal noise in the system and the +// asynchronous clock mismatch. +// For maximum entropy also make sure that the SAR_ADC is enabled. +// See esp32-c3_technical_reference_manual_en.pdf p.524 +func GetRNG() (ret uint32, err error) { + // ensure ADC clock is initialized + initADCClock() + + // ensure fast RTC clock is enabled + if esp.RTC_CNTL.GetCLK_CONF_DIG_CLK8M_EN() == 0 { + esp.RTC_CNTL.SetCLK_CONF_DIG_CLK8M_EN(1) + } + + return esp.APB_CTRL.GetRND_DATA(), nil +} + +func initADCClock() { + if esp.APB_SARADC.GetCLKM_CONF_CLK_EN() == 1 { + return + } + + // only support ADC_CTRL_CLK set to 1 + esp.APB_SARADC.SetCLKM_CONF_CLK_SEL(1) + + esp.APB_SARADC.SetCTRL_SARADC_SAR_CLK_GATED(1) + + esp.APB_SARADC.SetCLKM_CONF_CLKM_DIV_NUM(15) + esp.APB_SARADC.SetCLKM_CONF_CLKM_DIV_B(1) + esp.APB_SARADC.SetCLKM_CONF_CLKM_DIV_A(0) + + esp.APB_SARADC.SetCTRL_SARADC_SAR_CLK_DIV(1) + esp.APB_SARADC.SetCLKM_CONF_CLK_EN(1) +} diff --git a/emb/machine/machine_esp32c3_i2c.go b/emb/machine/machine_esp32c3_i2c.go new file mode 100644 index 00000000..dd334b0d --- /dev/null +++ b/emb/machine/machine_esp32c3_i2c.go @@ -0,0 +1,353 @@ +//go:build esp32c3 && !m5stamp_c3 + +package machine + +import ( + "device/esp" + "runtime/volatile" + "unsafe" +) + +var ( + I2C0 = &I2C{} +) + +type I2C struct{} + +// I2CConfig is used to store config info for I2C. +type I2CConfig struct { + Frequency uint32 // in Hz + SCL Pin + SDA Pin +} + +const ( + clkXTAL = 0 + clkFOSC = 1 + clkXTALFrequency = uint32(40e6) + clkFOSCFrequency = uint32(17.5e6) + i2cClkSourceFrequency = clkXTALFrequency + i2cClkSource = clkXTAL +) + +func (i2c *I2C) Configure(config I2CConfig) error { + if config.Frequency == 0 { + config.Frequency = 400 * KHz + } + if config.SCL == 0 { + config.SCL = SCL_PIN + } + if config.SDA == 0 { + config.SDA = SDA_PIN + } + + i2c.initClock(config) + i2c.initNoiseFilter() + i2c.initPins(config) + i2c.initFrequency(config) + i2c.startMaster() + return nil +} + +//go:inline +func (i2c *I2C) initClock(config I2CConfig) { + // reset I2C clock + esp.SYSTEM.SetPERIP_RST_EN0_I2C_EXT0_RST(1) + esp.SYSTEM.SetPERIP_CLK_EN0_I2C_EXT0_CLK_EN(1) + esp.SYSTEM.SetPERIP_RST_EN0_I2C_EXT0_RST(0) + // disable interrupts + esp.I2C0.INT_ENA.ClearBits(0x3fff) + esp.I2C0.INT_CLR.ClearBits(0x3fff) + + esp.I2C0.SetCLK_CONF_SCLK_SEL(i2cClkSource) + esp.I2C0.SetCLK_CONF_SCLK_ACTIVE(1) + esp.I2C0.SetCLK_CONF_SCLK_DIV_NUM(i2cClkSourceFrequency / (config.Frequency * 1024)) + esp.I2C0.SetCTR_CLK_EN(1) +} + +//go:inline +func (i2c *I2C) initNoiseFilter() { + esp.I2C0.FILTER_CFG.Set(0x377) +} + +//go:inline +func (i2c *I2C) initPins(config I2CConfig) { + var muxConfig uint32 + const function = 1 // function 1 is just GPIO + + // SDA + muxConfig = function << esp.IO_MUX_GPIO_MCU_SEL_Pos + // Make this pin an input pin (always). + muxConfig |= esp.IO_MUX_GPIO_FUN_IE + // Set drive strength: 0 is lowest, 3 is highest. + muxConfig |= 1 << esp.IO_MUX_GPIO_FUN_DRV_Pos + config.SDA.mux().Set(muxConfig) + config.SDA.outFunc().Set(54) + inFunc(54).Set(uint32(esp.GPIO_FUNC_IN_SEL_CFG_SEL | config.SDA)) + config.SDA.Set(true) + // Configure the pad with the given IO mux configuration. + config.SDA.pinReg().SetBits(esp.GPIO_PIN_PAD_DRIVER) + + esp.GPIO.ENABLE.SetBits(1 << int(config.SDA)) + esp.I2C0.SetCTR_SDA_FORCE_OUT(1) + + // SCL + muxConfig = function << esp.IO_MUX_GPIO_MCU_SEL_Pos + // Make this pin an input pin (always). + muxConfig |= esp.IO_MUX_GPIO_FUN_IE + // Set drive strength: 0 is lowest, 3 is highest. + muxConfig |= 1 << esp.IO_MUX_GPIO_FUN_DRV_Pos + config.SCL.mux().Set(muxConfig) + config.SCL.outFunc().Set(53) + inFunc(53).Set(uint32(config.SCL)) + config.SCL.Set(true) + // Configure the pad with the given IO mux configuration. + config.SCL.pinReg().SetBits(esp.GPIO_PIN_PAD_DRIVER) + + esp.GPIO.ENABLE.SetBits(1 << int(config.SCL)) + esp.I2C0.SetCTR_SCL_FORCE_OUT(1) +} + +//go:inline +func (i2c *I2C) initFrequency(config I2CConfig) { + + clkmDiv := i2cClkSourceFrequency/(config.Frequency*1024) + 1 + sclkFreq := i2cClkSourceFrequency / clkmDiv + halfCycle := sclkFreq / config.Frequency / 2 + //SCL + sclLow := halfCycle + sclWaitHigh := uint32(0) + if config.Frequency > 50000 { + sclWaitHigh = halfCycle / 8 // compensate the time when freq > 50K + } + sclHigh := halfCycle - sclWaitHigh + // SDA + sdaHold := halfCycle / 4 + sda_sample := halfCycle / 2 + setup := halfCycle + hold := halfCycle + + esp.I2C0.SetSCL_LOW_PERIOD(sclLow - 1) + esp.I2C0.SetSCL_HIGH_PERIOD(sclHigh) + esp.I2C0.SetSCL_HIGH_PERIOD_SCL_WAIT_HIGH_PERIOD(25) + esp.I2C0.SetSCL_RSTART_SETUP_TIME(setup) + esp.I2C0.SetSCL_STOP_SETUP_TIME(setup) + esp.I2C0.SetSCL_START_HOLD_TIME(hold - 1) + esp.I2C0.SetSCL_STOP_HOLD_TIME(hold - 1) + esp.I2C0.SetSDA_SAMPLE_TIME(sda_sample) + esp.I2C0.SetSDA_HOLD_TIME(sdaHold) +} + +//go:inline +func (i2c *I2C) startMaster() { + // FIFO mode for data + esp.I2C0.SetFIFO_CONF_NONFIFO_EN(0) + // Reset TX & RX buffers + esp.I2C0.SetFIFO_CONF_RX_FIFO_RST(1) + esp.I2C0.SetFIFO_CONF_RX_FIFO_RST(0) + esp.I2C0.SetFIFO_CONF_TX_FIFO_RST(1) + esp.I2C0.SetFIFO_CONF_TX_FIFO_RST(0) + // set timeout value + esp.I2C0.TO.Set(0x10) + // enable master mode + esp.I2C0.CTR.Set(0x113) + esp.I2C0.SetCTR_CONF_UPGATE(1) + resetMaster() +} + +//go:inline +func resetMaster() { + // reset FSM + esp.I2C0.SetCTR_FSM_RST(1) + // clear the bus + esp.I2C0.SetSCL_SP_CONF_SCL_RST_SLV_NUM(9) + esp.I2C0.SetSCL_SP_CONF_SCL_RST_SLV_EN(1) + esp.I2C0.SetSCL_STRETCH_CONF_SLAVE_SCL_STRETCH_EN(1) + esp.I2C0.SetCTR_CONF_UPGATE(1) + esp.I2C0.FILTER_CFG.Set(0x377) + // wait for SCL_RST_SLV_EN + for esp.I2C0.GetSCL_SP_CONF_SCL_RST_SLV_EN() != 0 { + } + esp.I2C0.SetSCL_SP_CONF_SCL_RST_SLV_NUM(0) +} + +type i2cCommandType = uint32 +type i2cAck = uint32 + +const ( + i2cCMD_RSTART i2cCommandType = 6 << 11 + i2cCMD_WRITE i2cCommandType = 1<<11 | 1<<8 // WRITE + ack_check_en + i2cCMD_READ i2cCommandType = 3<<11 | 1<<8 // READ + ack_check_en + i2cCMD_READLAST i2cCommandType = 3<<11 | 5<<8 // READ + ack_check_en + NACK + i2cCMD_STOP i2cCommandType = 2 << 11 + i2cCMD_END i2cCommandType = 4 << 11 +) + +type i2cCommand struct { + cmd i2cCommandType + data []byte + head int +} + +//go:linkname nanotime runtime.nanotime +func nanotime() int64 + +func (i2c *I2C) transmit(addr uint16, cmd []i2cCommand, timeoutMS int) error { + const intMask = esp.I2C_INT_STATUS_END_DETECT_INT_ST_Msk | esp.I2C_INT_STATUS_TRANS_COMPLETE_INT_ST_Msk | esp.I2C_INT_STATUS_TIME_OUT_INT_ST_Msk | esp.I2C_INT_STATUS_NACK_INT_ST_Msk + esp.I2C0.INT_CLR.SetBits(intMask) + esp.I2C0.INT_ENA.SetBits(intMask) + esp.I2C0.SetCTR_CONF_UPGATE(1) + + defer func() { + esp.I2C0.INT_CLR.SetBits(intMask) + esp.I2C0.INT_ENA.ClearBits(intMask) + }() + + timeoutNS := int64(timeoutMS) * 1000000 + needAddress := true + needRestart := false + readLast := false + var readTo []byte + for cmdIdx, reg := 0, &esp.I2C0.COMD0; cmdIdx < len(cmd); { + c := &cmd[cmdIdx] + + switch c.cmd { + case i2cCMD_RSTART: + reg.Set(i2cCMD_RSTART) + reg = nextAddress(reg) + cmdIdx++ + + case i2cCMD_WRITE: + count := 32 + if needAddress { + needAddress = false + esp.I2C0.SetDATA_FIFO_RDATA((uint32(addr) & 0x7f) << 1) + count-- + esp.I2C0.SLAVE_ADDR.Set(uint32(addr)) + esp.I2C0.SetCTR_CONF_UPGATE(1) + } + for ; count > 0 && c.head < len(c.data); count, c.head = count-1, c.head+1 { + esp.I2C0.SetDATA_FIFO_RDATA(uint32(c.data[c.head])) + } + reg.Set(i2cCMD_WRITE | uint32(32-count)) + reg = nextAddress(reg) + + if c.head < len(c.data) { + reg.Set(i2cCMD_END) + reg = nil + } else { + cmdIdx++ + } + needRestart = true + + case i2cCMD_READ: + if needAddress { + needAddress = false + esp.I2C0.SetDATA_FIFO_RDATA((uint32(addr)&0x7f)<<1 | 1) + esp.I2C0.SLAVE_ADDR.Set(uint32(addr)) + reg.Set(i2cCMD_WRITE | 1) + reg = nextAddress(reg) + } + if needRestart { + // We need to send RESTART again after i2cCMD_WRITE. + reg.Set(i2cCMD_RSTART) + + reg = nextAddress(reg) + reg.Set(i2cCMD_WRITE | 1) + + reg = nextAddress(reg) + esp.I2C0.SetDATA_FIFO_RDATA((uint32(addr)&0x7f)<<1 | 1) + needRestart = false + } + count := 32 + bytes := len(c.data) - c.head + // Only last byte in sequence must be sent with ACK set to 1 to indicate end of data. + split := bytes <= count + if split { + bytes-- + } + if bytes > 32 { + bytes = 32 + } + reg.Set(i2cCMD_READ | uint32(bytes)) + reg = nextAddress(reg) + + if split { + readLast = true + reg.Set(i2cCMD_READLAST | 1) + reg = nextAddress(reg) + readTo = c.data[c.head : c.head+bytes+1] // read bytes + 1 last byte + cmdIdx++ + } else { + reg.Set(i2cCMD_END) + readTo = c.data[c.head : c.head+bytes] + reg = nil + } + + case i2cCMD_STOP: + reg.Set(i2cCMD_STOP) + reg = nil + cmdIdx++ + } + if reg == nil { + // transmit now + esp.I2C0.SetCTR_CONF_UPGATE(1) + esp.I2C0.SetCTR_TRANS_START(1) + end := nanotime() + timeoutNS + var mask uint32 + for mask = esp.I2C0.INT_STATUS.Get(); mask&intMask == 0; mask = esp.I2C0.INT_STATUS.Get() { + if nanotime() > end { + if readTo != nil { + return errI2CReadTimeout + } + return errI2CWriteTimeout + } + } + switch { + case mask&esp.I2C_INT_STATUS_NACK_INT_ST_Msk != 0 && !readLast: + return errI2CAckExpected + case mask&esp.I2C_INT_STATUS_TIME_OUT_INT_ST_Msk != 0: + if readTo != nil { + return errI2CReadTimeout + } + return errI2CWriteTimeout + } + esp.I2C0.INT_CLR.SetBits(intMask) + for i := 0; i < len(readTo); i++ { + readTo[i] = byte(esp.I2C0.GetDATA_FIFO_RDATA() & 0xff) + c.head++ + } + readTo = nil + reg = &esp.I2C0.COMD0 + } + } + return nil +} + +// Tx does a single I2C transaction at the specified address. +// It clocks out the given address, writes the bytes in w, reads back len(r) +// bytes and stores them in r, and generates a stop condition on the bus. +func (i2c *I2C) Tx(addr uint16, w, r []byte) (err error) { + // timeout in microseconds. + const timeout = 40 // 40ms is a reasonable time for a real-time system. + + cmd := make([]i2cCommand, 0, 8) + cmd = append(cmd, i2cCommand{cmd: i2cCMD_RSTART}) + if len(w) > 0 { + cmd = append(cmd, i2cCommand{cmd: i2cCMD_WRITE, data: w}) + } + if len(r) > 0 { + cmd = append(cmd, i2cCommand{cmd: i2cCMD_READ, data: r}) + } + cmd = append(cmd, i2cCommand{cmd: i2cCMD_STOP}) + + return i2c.transmit(addr, cmd, timeout) +} + +func (i2c *I2C) SetBaudRate(br uint32) error { + return nil +} + +func nextAddress(reg *volatile.Register32) *volatile.Register32 { + return (*volatile.Register32)(unsafe.Add(unsafe.Pointer(reg), 4)) +} diff --git a/emb/machine/machine_esp32c3_spi.go b/emb/machine/machine_esp32c3_spi.go new file mode 100644 index 00000000..aec3ca77 --- /dev/null +++ b/emb/machine/machine_esp32c3_spi.go @@ -0,0 +1,323 @@ +//go:build esp32c3 + +package machine + +// On the C3 variant, SPI2 is a general purpose SPI controller. SPI0 and SPI1 +// are used internally to access the ESP32-C3’s attached flash memory. Due to +// different registers between SPI2 and the other SPI ports, this driver +// currently supports only the the general purpose FSPI SPI2 controller. +// https://docs.espressif.com/projects/esp-idf/en/latest/esp32c3/api-reference/peripherals/spi_master.html + +import ( + "device/esp" + "errors" + "runtime/volatile" + "unsafe" +) + +const ( + SPI_MODE0 = uint8(0) + SPI_MODE1 = uint8(1) + SPI_MODE2 = uint8(2) + SPI_MODE3 = uint8(3) + + FSPICLK_IN_IDX = uint32(63) + FSPICLK_OUT_IDX = uint32(63) + FSPIQ_IN_IDX = uint32(64) + FSPIQ_OUT_IDX = uint32(64) + FSPID_IN_IDX = uint32(65) + FSPID_OUT_IDX = uint32(65) + FSPIHD_IN_IDX = uint32(66) + FSPIHD_OUT_IDX = uint32(66) + FSPIWP_IN_IDX = uint32(67) + FSPIWP_OUT_IDX = uint32(67) + FSPICS0_IN_IDX = uint32(68) + FSPICS0_OUT_IDX = uint32(68) + FSPICS1_OUT_IDX = uint32(69) + FSPICS2_OUT_IDX = uint32(70) + FSPICS3_OUT_IDX = uint32(71) + FSPICS4_OUT_IDX = uint32(72) + FSPICS5_OUT_IDX = uint32(73) +) + +var ( + ErrInvalidSPIBus = errors.New("machine: SPI bus is invalid") + ErrInvalidSPIMode = errors.New("machine: SPI mode is invalid") +) + +// Serial Peripheral Interface on the ESP32-C3. +type SPI struct { + Bus *esp.SPI2_Type +} + +var ( + // SPI0 and SPI1 are reserved for use by the caching system etc. + SPI2 = &SPI{esp.SPI2} +) + +// SPIConfig is used to store config info for SPI. +type SPIConfig struct { + Frequency uint32 + SCK Pin // Serial Clock + SDO Pin // Serial Data Out (MOSI) + SDI Pin // Serial Data In (MISO) + CS Pin // Chip Select (optional) + LSBFirst bool // MSB is default + Mode uint8 // SPI_MODE0 is default +} + +// Compute the SPI bus frequency from the CPU frequency. +func freqToClockDiv(hz uint32) uint32 { + fcpu := CPUFrequency() + if hz >= fcpu { // maximum frequency + return 1 << 31 + } + if hz < (fcpu / (16 * 64)) { // minimum frequency + return 15<<18 | 63<<12 | 31<<6 | 63 // pre=15, n=63 + } + + // iterate looking for an exact match + // or iterate all 16 prescaler options + // looking for the smallest error + var bestPre, bestN, bestErr uint32 + bestN = 1 + bestErr = 0xffffffff + q := uint32(float32(pplClockFreq)/float32(hz) + float32(0.5)) + for p := uint32(0); p < 16; p++ { + n := q/(p+1) - 1 + if n < 1 { // prescaler became too large, stop enum + break + } + if n > 63 { // prescaler too small, skip to next + continue + } + + freq := fcpu / ((p + 1) * (n + 1)) + if freq == hz { // exact match + return p<<18 | n<<12 | (n/2)<<6 | n + } + + var err uint32 + if freq < hz { + err = hz - freq + } else { + err = freq - hz + } + if err < bestErr { + bestErr = err + bestPre = p + bestN = n + } + } + + return bestPre<<18 | bestN<<12 | (bestN/2)<<6 | bestN +} + +// Configure and make the SPI peripheral ready to use. +func (spi *SPI) Configure(config SPIConfig) error { + // right now this is only setup to work for the esp32c3 spi2 bus + if spi.Bus != esp.SPI2 { + return ErrInvalidSPIBus + } + + // periph module reset + esp.SYSTEM.SetPERIP_RST_EN0_SPI2_RST(1) + esp.SYSTEM.SetPERIP_RST_EN0_SPI2_RST(0) + + // periph module enable + esp.SYSTEM.SetPERIP_CLK_EN0_SPI2_CLK_EN(1) + esp.SYSTEM.SetPERIP_RST_EN0_SPI2_RST(0) + + // init the spi2 bus + spi.Bus.SLAVE.Set(0) + spi.Bus.MISC.Set(0) + spi.Bus.USER.Set(0) + spi.Bus.USER1.Set(0) + spi.Bus.CTRL.Set(0) + spi.Bus.CLK_GATE.Set(0) + spi.Bus.DMA_CONF.Set(0) + spi.Bus.SetDMA_CONF_RX_AFIFO_RST(1) + spi.Bus.SetDMA_CONF_BUF_AFIFO_RST(1) + spi.Bus.CLOCK.Set(0) + + // clear data buf + spi.Bus.SetW0(0) + spi.Bus.SetW1(0) + spi.Bus.SetW2(0) + spi.Bus.SetW3(0) + spi.Bus.SetW4(0) + spi.Bus.SetW5(0) + spi.Bus.SetW6(0) + spi.Bus.SetW7(0) + spi.Bus.SetW8(0) + spi.Bus.SetW9(0) + spi.Bus.SetW10(0) + spi.Bus.SetW11(0) + spi.Bus.SetW12(0) + spi.Bus.SetW13(0) + spi.Bus.SetW14(0) + spi.Bus.SetW15(0) + + // start the spi2 bus + spi.Bus.SetCLK_GATE_CLK_EN(1) + spi.Bus.SetCLK_GATE_MST_CLK_SEL(1) + spi.Bus.SetCLK_GATE_MST_CLK_ACTIVE(1) + spi.Bus.SetDMA_CONF_SLV_TX_SEG_TRANS_CLR_EN(1) + spi.Bus.SetDMA_CONF_SLV_RX_SEG_TRANS_CLR_EN(1) + spi.Bus.SetDMA_CONF_DMA_SLV_SEG_TRANS_EN(0) + spi.Bus.SetUSER_USR_MOSI(1) + spi.Bus.SetUSER_USR_MISO(1) + spi.Bus.SetUSER_DOUTDIN(1) + + // set spi2 data mode + switch config.Mode { + case SPI_MODE0: + spi.Bus.SetMISC_CK_IDLE_EDGE(0) + spi.Bus.SetUSER_CK_OUT_EDGE(0) + case SPI_MODE1: + spi.Bus.SetMISC_CK_IDLE_EDGE(0) + spi.Bus.SetUSER_CK_OUT_EDGE(1) + case SPI_MODE2: + spi.Bus.SetMISC_CK_IDLE_EDGE(1) + spi.Bus.SetUSER_CK_OUT_EDGE(1) + case SPI_MODE3: + spi.Bus.SetMISC_CK_IDLE_EDGE(1) + spi.Bus.SetUSER_CK_OUT_EDGE(0) + default: + return ErrInvalidSPIMode + } + + // set spi2 bit order + if config.LSBFirst { + spi.Bus.SetCTRL_WR_BIT_ORDER(1) // LSB first + spi.Bus.SetCTRL_RD_BIT_ORDER(1) + } else { + spi.Bus.SetCTRL_WR_BIT_ORDER(0) // MSB first + spi.Bus.SetCTRL_RD_BIT_ORDER(0) + } + + // configure SPI bus clock + spi.Bus.CLOCK.Set(freqToClockDiv(config.Frequency)) + + // configure esp32c3 gpio pin matrix + config.SDI.Configure(PinConfig{Mode: PinInput}) + inFunc(FSPIQ_IN_IDX).Set(esp.GPIO_FUNC_IN_SEL_CFG_SEL | uint32(config.SDI)) + config.SDO.Configure(PinConfig{Mode: PinOutput}) + config.SDO.outFunc().Set(FSPID_OUT_IDX) + config.SCK.Configure(PinConfig{Mode: PinOutput}) + config.SCK.outFunc().Set(FSPICLK_OUT_IDX) + if config.CS != NoPin { + config.CS.Configure(PinConfig{Mode: PinOutput}) + config.CS.outFunc().Set(FSPICS0_OUT_IDX) + } + + return nil +} + +// Transfer writes/reads a single byte using the SPI interface. If you need to +// transfer larger amounts of data, Tx will be faster. +func (spi *SPI) Transfer(w byte) (byte, error) { + spi.Bus.SetMS_DLEN_MS_DATA_BITLEN(7) + + spi.Bus.SetW0(uint32(w)) + + // Send/receive byte. + spi.Bus.SetCMD_UPDATE(1) + for spi.Bus.GetCMD_UPDATE() != 0 { + } + + spi.Bus.SetCMD_USR(1) + for spi.Bus.GetCMD_USR() != 0 { + } + + // The received byte is stored in W0. + return byte(spi.Bus.GetW0()), nil +} + +// Tx handles read/write operation for SPI interface. Since SPI is a synchronous write/read +// interface, there must always be the same number of bytes written as bytes read. +// This is accomplished by sending zero bits if r is bigger than w or discarding +// the incoming data if w is bigger than r. +func (spi *SPI) Tx(w, r []byte) error { + toTransfer := len(w) + if len(r) > toTransfer { + toTransfer = len(r) + } + + for toTransfer > 0 { + // Chunk 64 bytes at a time. + chunkSize := toTransfer + if chunkSize > 64 { + chunkSize = 64 + } + + // Fill tx buffer. + transferWords := (*[16]volatile.Register32)(unsafe.Pointer(uintptr(unsafe.Pointer(&spi.Bus.W0)))) + if len(w) >= 64 { + // We can fill the entire 64-byte transfer buffer with data. + // This loop is slightly faster than the loop below. + for i := 0; i < 16; i++ { + word := uint32(w[i*4]) | uint32(w[i*4+1])<<8 | uint32(w[i*4+2])<<16 | uint32(w[i*4+3])<<24 + transferWords[i].Set(word) + } + } else { + // We can't fill the entire transfer buffer, so we need to be a bit + // more careful. + // Note that parts of the transfer buffer that aren't used still + // need to be set to zero, otherwise we might be transferring + // garbage from a previous transmission if w is smaller than r. + for i := 0; i < 16; i++ { + var word uint32 + if i*4+3 < len(w) { + word |= uint32(w[i*4+3]) << 24 + } + if i*4+2 < len(w) { + word |= uint32(w[i*4+2]) << 16 + } + if i*4+1 < len(w) { + word |= uint32(w[i*4+1]) << 8 + } + if i*4+0 < len(w) { + word |= uint32(w[i*4+0]) << 0 + } + transferWords[i].Set(word) + } + } + + // Do the transfer. + spi.Bus.SetMS_DLEN_MS_DATA_BITLEN(uint32(chunkSize)*8 - 1) + + spi.Bus.SetCMD_UPDATE(1) + for spi.Bus.GetCMD_UPDATE() != 0 { + } + + spi.Bus.SetCMD_USR(1) + for spi.Bus.GetCMD_USR() != 0 { + } + + // Read rx buffer. + rxSize := 64 + if rxSize > len(r) { + rxSize = len(r) + } + for i := 0; i < rxSize; i++ { + r[i] = byte(transferWords[i/4].Get() >> ((i % 4) * 8)) + } + + // Cut off some part of the output buffer so the next iteration we will + // only send the remaining bytes. + if len(w) < chunkSize { + w = nil + } else { + w = w[chunkSize:] + } + if len(r) < chunkSize { + r = nil + } else { + r = r[chunkSize:] + } + toTransfer -= chunkSize + } + + return nil +} diff --git a/emb/machine/machine_esp8266.go b/emb/machine/machine_esp8266.go new file mode 100644 index 00000000..4edd4a53 --- /dev/null +++ b/emb/machine/machine_esp8266.go @@ -0,0 +1,194 @@ +//go:build esp8266 + +package machine + +import ( + "device/esp" + "runtime/volatile" +) + +const deviceName = esp.Device + +func CPUFrequency() uint32 { + return 80000000 // 80MHz +} + +const ( + PinOutput PinMode = iota + PinInput +) + +// Hardware pin numbers +const ( + GPIO0 Pin = iota + GPIO1 + GPIO2 + GPIO3 + GPIO4 + GPIO5 + GPIO6 + GPIO7 + GPIO8 + GPIO9 + GPIO10 + GPIO11 + GPIO12 + GPIO13 + GPIO14 + GPIO15 + GPIO16 +) + +// Pins that are fixed by the chip. +const ( + UART_TX_PIN Pin = 1 + UART_RX_PIN Pin = 3 +) + +// Pin functions are not trivial. The below array maps a pin number (GPIO +// number) to the pad as used in the IO mux. +// Tables with the mapping: +// https://www.esp8266.com/wiki/doku.php?id=esp8266_gpio_pin_allocations#pin_functions +// https://www.espressif.com/sites/default/files/documentation/ESP8266_Pin_List_0.xls +var pinPadMapping = [...]uint8{ + 12: 0, + 13: 1, + 14: 2, + 15: 3, + 3: 4, + 1: 5, + 6: 6, + 7: 7, + 8: 8, + 9: 9, + 10: 10, + 11: 11, + 0: 12, + 2: 13, + 4: 14, + 5: 15, +} + +// getPad returns the pad number and the register to configure this pad. +func (p Pin) getPad() (uint8, *volatile.Register32) { + pad := pinPadMapping[p] + var reg *volatile.Register32 + switch pad { + case 0: + reg = &esp.IO_MUX.IO_MUX_MTDI + case 1: + reg = &esp.IO_MUX.IO_MUX_MTCK + case 2: + reg = &esp.IO_MUX.IO_MUX_MTMS + case 3: + reg = &esp.IO_MUX.IO_MUX_MTDO + case 4: + reg = &esp.IO_MUX.IO_MUX_U0RXD + case 5: + reg = &esp.IO_MUX.IO_MUX_U0TXD + case 6: + reg = &esp.IO_MUX.IO_MUX_SD_CLK + case 7: + reg = &esp.IO_MUX.IO_MUX_SD_DATA0 + case 8: + reg = &esp.IO_MUX.IO_MUX_SD_DATA1 + case 9: + reg = &esp.IO_MUX.IO_MUX_SD_DATA2 + case 10: + reg = &esp.IO_MUX.IO_MUX_SD_DATA3 + case 11: + reg = &esp.IO_MUX.IO_MUX_SD_CMD + case 12: + reg = &esp.IO_MUX.IO_MUX_GPIO0 + case 13: + reg = &esp.IO_MUX.IO_MUX_GPIO2 + case 14: + reg = &esp.IO_MUX.IO_MUX_GPIO4 + case 15: + reg = &esp.IO_MUX.IO_MUX_GPIO5 + } + return pad, reg +} + +// Configure sets the given pin as output or input pin. +func (p Pin) Configure(config PinConfig) { + switch config.Mode { + case PinInput, PinOutput: + pad, reg := p.getPad() + if pad >= 12 { // pin 0, 2, 4, 5 + reg.Set(0 << 4) // function 0 at bit position 4 + } else { + reg.Set(3 << 4) // function 3 at bit position 4 + } + if config.Mode == PinOutput { + esp.GPIO.GPIO_ENABLE_W1TS.Set(1 << p) + } else { + esp.GPIO.GPIO_ENABLE_W1TC.Set(1 << p) + } + } +} + +// Get returns the current value of a GPIO pin when the pin is configured as an +// input or as an output. +func (p Pin) Get() bool { + // See this document for details + // https://www.espressif.com/sites/default/files/documentation/esp8266-technical_reference_en.pdf + + return esp.GPIO.GPIO_IN.Get()&(1<>16)&0xff >= 128 { + // Wait until the TX buffer has room. + } + esp.UART0.UART_FIFO.Set(uint32(c)) + return nil +} + +func (uart *UART) flush() {} diff --git a/emb/machine/machine_fe310.go b/emb/machine/machine_fe310.go new file mode 100644 index 00000000..2f716d61 --- /dev/null +++ b/emb/machine/machine_fe310.go @@ -0,0 +1,370 @@ +//go:build fe310 + +package machine + +import ( + "device/sifive" + "runtime/interrupt" + "unsafe" +) + +const deviceName = sifive.Device + +func CPUFrequency() uint32 { + return 320000000 // 320MHz +} + +const ( + PinInput PinMode = iota + PinOutput + PinPWM + PinSPI + PinI2C = PinSPI +) + +// Configure this pin with the given configuration. +func (p Pin) Configure(config PinConfig) { + sifive.GPIO0.INPUT_EN.SetBits(1 << uint8(p)) + switch config.Mode { + case PinInput: + sifive.GPIO0.OUTPUT_EN.ClearBits(1 << uint8(p)) + case PinOutput: + sifive.GPIO0.OUTPUT_EN.SetBits(1 << uint8(p)) + case PinPWM: + sifive.GPIO0.IOF_EN.SetBits(1 << uint8(p)) + sifive.GPIO0.IOF_SEL.SetBits(1 << uint8(p)) + case PinSPI: + sifive.GPIO0.IOF_EN.SetBits(1 << uint8(p)) + sifive.GPIO0.IOF_SEL.ClearBits(1 << uint8(p)) + } +} + +// Set the pin to high or low. +func (p Pin) Set(high bool) { + if high { + sifive.GPIO0.PORT.SetBits(1 << uint8(p)) + } else { + sifive.GPIO0.PORT.ClearBits(1 << uint8(p)) + } +} + +// Get returns the current value of a GPIO pin when the pin is configured as an +// input or as an output. +func (p Pin) Get() bool { + val := sifive.GPIO0.VALUE.Get() & (1 << uint8(p)) + return (val > 0) +} + +// Return the register and mask to enable a given GPIO pin. This can be used to +// implement bit-banged drivers. +// +// Warning: only use this on an output pin! +func (p Pin) PortMaskSet() (*uint32, uint32) { + return (*uint32)(unsafe.Pointer(&sifive.GPIO0.PORT)), sifive.GPIO0.PORT.Get() | (1 << uint8(p)) +} + +// Return the register and mask to disable a given GPIO pin. This can be used to +// implement bit-banged drivers. +// +// Warning: only use this on an output pin! +func (p Pin) PortMaskClear() (*uint32, uint32) { + return (*uint32)(unsafe.Pointer(&sifive.GPIO0.PORT)), sifive.GPIO0.PORT.Get() &^ (1 << uint8(p)) +} + +type UART struct { + Bus *sifive.UART_Type + Buffer *RingBuffer +} + +var ( + UART0 = &_UART0 + _UART0 = UART{Bus: sifive.UART0, Buffer: NewRingBuffer()} +) + +func (uart *UART) Configure(config UARTConfig) { + if config.BaudRate == 0 { + config.BaudRate = 115200 + } + // The divisor is: + // fbaud = fin / (div + 1) + // Restating to get the divisor: + // div = fin / fbaud - 1 + // But we're using integers, so we should take care of rounding: + // div = (fin + fbaud/2) / fbaud - 1 + divisor := (CPUFrequency()+config.BaudRate/2)/config.BaudRate - 1 + sifive.UART0.DIV.Set(divisor) + sifive.UART0.TXCTRL.Set(sifive.UART_TXCTRL_ENABLE) + sifive.UART0.RXCTRL.Set(sifive.UART_RXCTRL_ENABLE) + sifive.UART0.IE.Set(sifive.UART_IE_RXWM) // enable the receive interrupt (only) + intr := interrupt.New(sifive.IRQ_UART0, _UART0.handleInterrupt) + intr.SetPriority(5) + intr.Enable() +} + +func (uart *UART) handleInterrupt(interrupt.Interrupt) { + rxdata := uart.Bus.RXDATA.Get() + c := byte(rxdata) + if uint32(c) != rxdata { + // The rxdata has other bits set than just the low 8 bits. This probably + // means that the 'empty' flag is set, which indicates there is no data + // to be read and the byte is garbage. Ignore this byte. + return + } + uart.Receive(c) +} + +func (uart *UART) writeByte(c byte) error { + for sifive.UART0.TXDATA.Get()&sifive.UART_TXDATA_FULL != 0 { + } + + sifive.UART0.TXDATA.Set(uint32(c)) + return nil +} + +func (uart *UART) flush() {} + +// SPI on the FE310. The normal SPI0 is actually a quad-SPI meant for flash, so it is best +// to use SPI1 or SPI2 port for most applications. +type SPI struct { + Bus *sifive.QSPI_Type +} + +// SPIConfig is used to store config info for SPI. +type SPIConfig struct { + Frequency uint32 + SCK Pin + SDO Pin + SDI Pin + LSBFirst bool + Mode uint8 +} + +// Configure is intended to setup the SPI interface. +func (spi *SPI) Configure(config SPIConfig) error { + // Use default pins if not set. + if config.SCK == 0 && config.SDO == 0 && config.SDI == 0 { + config.SCK = SPI0_SCK_PIN + config.SDO = SPI0_SDO_PIN + config.SDI = SPI0_SDI_PIN + } + + // enable pins for SPI + config.SCK.Configure(PinConfig{Mode: PinSPI}) + config.SDO.Configure(PinConfig{Mode: PinSPI}) + config.SDI.Configure(PinConfig{Mode: PinSPI}) + + // set default frequency + if config.Frequency == 0 { + config.Frequency = 4000000 // 4MHz + } + + // div = (SPI_CFG(dev)->f_sys / (2 * frequency)) - 1; + div := CPUFrequency()/(2*config.Frequency) - 1 + spi.Bus.DIV.Set(div) + + // set mode + switch config.Mode { + case 0: + spi.Bus.MODE.ClearBits(sifive.QSPI_MODE_PHASE) + spi.Bus.MODE.ClearBits(sifive.QSPI_MODE_POLARITY) + case 1: + spi.Bus.MODE.SetBits(sifive.QSPI_MODE_PHASE) + spi.Bus.MODE.ClearBits(sifive.QSPI_MODE_POLARITY) + case 2: + spi.Bus.MODE.ClearBits(sifive.QSPI_MODE_PHASE) + spi.Bus.MODE.SetBits(sifive.QSPI_MODE_POLARITY) + case 3: + spi.Bus.MODE.SetBits(sifive.QSPI_MODE_PHASE | sifive.QSPI_MODE_POLARITY) + default: // to mode 0 + spi.Bus.MODE.ClearBits(sifive.QSPI_MODE_PHASE) + spi.Bus.MODE.ClearBits(sifive.QSPI_MODE_POLARITY) + } + + // frame length + spi.Bus.FMT.SetBits(8 << sifive.QSPI_FMT_LENGTH_Pos) + + // Set single line operation, by clearing all bits + spi.Bus.FMT.ClearBits(sifive.QSPI_FMT_PROTOCOL_Msk) + + // set bit transfer order + if config.LSBFirst { + spi.Bus.FMT.SetBits(sifive.QSPI_FMT_ENDIAN) + } else { + spi.Bus.FMT.ClearBits(sifive.QSPI_FMT_ENDIAN) + } + + return nil +} + +// Transfer writes/reads a single byte using the SPI interface. +func (spi *SPI) Transfer(w byte) (byte, error) { + // wait for tx ready + for spi.Bus.TXDATA.HasBits(sifive.QSPI_TXDATA_FULL) { + } + + // write data + spi.Bus.TXDATA.Set(uint32(w)) + + // wait until receive has data + data := spi.Bus.RXDATA.Get() + for data&sifive.QSPI_RXDATA_EMPTY > 0 { + data = spi.Bus.RXDATA.Get() + } + + // return data + return byte(data), nil +} + +// I2C on the FE310-G002. +type I2C struct { + Bus sifive.I2C_Type +} + +var ( + I2C0 = (*I2C)(unsafe.Pointer(sifive.I2C0)) +) + +// I2CConfig is used to store config info for I2C. +type I2CConfig struct { + Frequency uint32 + SCL Pin + SDA Pin +} + +var i2cClockFrequency uint32 = 32000000 + +// Configure is intended to setup the I2C interface. +func (i2c *I2C) Configure(config I2CConfig) error { + if config.Frequency == 0 { + config.Frequency = 100 * KHz + } + + if config.SDA == 0 && config.SCL == 0 { + config.SDA = I2C0_SDA_PIN + config.SCL = I2C0_SCL_PIN + } + + i2c.SetBaudRate(config.Frequency) + + config.SDA.Configure(PinConfig{Mode: PinI2C}) + config.SCL.Configure(PinConfig{Mode: PinI2C}) + + return nil +} + +// SetBaudRate sets the communication speed for I2C. +func (i2c *I2C) SetBaudRate(br uint32) error { + var prescaler = i2cClockFrequency/(5*br) - 1 + + // disable controller before setting the prescale registers + i2c.Bus.CTR.ClearBits(sifive.I2C_CTR_EN) + + // set prescaler registers + i2c.Bus.PRER_LO.Set(uint32(prescaler & 0xff)) + i2c.Bus.PRER_HI.Set(uint32((prescaler >> 8) & 0xff)) + + // enable controller + i2c.Bus.CTR.SetBits(sifive.I2C_CTR_EN) + + return nil +} + +// Tx does a single I2C transaction at the specified address. +// It clocks out the given address, writes the bytes in w, reads back len(r) +// bytes and stores them in r, and generates a stop condition on the bus. +func (i2c *I2C) Tx(addr uint16, w, r []byte) error { + var err error + if len(w) != 0 { + // send start/address for write + i2c.sendAddress(addr, true) + + // ACK received (0: ACK, 1: NACK) + if i2c.Bus.CR_SR.HasBits(sifive.I2C_SR_RX_ACK) { + return errI2CAckExpected + } + + // write data + for _, b := range w { + err = i2c.writeByte(b) + if err != nil { + return err + } + } + } + if len(r) != 0 { + // send start/address for read + i2c.sendAddress(addr, false) + + // ACK received (0: ACK, 1: NACK) + if i2c.Bus.CR_SR.HasBits(sifive.I2C_SR_RX_ACK) { + return errI2CAckExpected + } + + // read first byte + r[0] = i2c.readByte() + for i := 1; i < len(r); i++ { + // send an ACK + i2c.Bus.CR_SR.Set(^uint32(sifive.I2C_CR_ACK)) + + // read data and send the ACK + r[i] = i2c.readByte() + } + + // send NACK to end transmission + i2c.Bus.CR_SR.Set(sifive.I2C_CR_ACK) + } + + // generate stop condition + i2c.Bus.CR_SR.Set(sifive.I2C_CR_STO) + return nil +} + +// Writes a single byte to the I2C bus. +func (i2c *I2C) writeByte(data byte) error { + // Send data byte + i2c.Bus.TXR_RXR.Set(uint32(data)) + + i2c.Bus.CR_SR.Set(sifive.I2C_CR_WR) + + // wait until transmission complete + for i2c.Bus.CR_SR.HasBits(sifive.I2C_SR_TIP) { + } + + // ACK received (0: ACK, 1: NACK) + if i2c.Bus.CR_SR.HasBits(sifive.I2C_SR_RX_ACK) { + return errI2CAckExpected + } + + return nil +} + +// Reads a single byte from the I2C bus. +func (i2c *I2C) readByte() byte { + i2c.Bus.CR_SR.Set(sifive.I2C_CR_RD) + + // wait until transmission complete + for i2c.Bus.CR_SR.HasBits(sifive.I2C_SR_TIP) { + } + + return byte(i2c.Bus.TXR_RXR.Get()) +} + +// Sends the address and start signal. +func (i2c *I2C) sendAddress(address uint16, write bool) error { + data := (address << 1) + if !write { + data |= 1 // set read flag in transmit register + } + + // write address to transmit register + i2c.Bus.TXR_RXR.Set(uint32(data)) + + // generate start condition + i2c.Bus.CR_SR.Set((sifive.I2C_CR_STA | sifive.I2C_CR_WR)) + + // wait until transmission complete + for i2c.Bus.CR_SR.HasBits(sifive.I2C_SR_TIP) { + } + + return nil +} diff --git a/emb/machine/machine_gameboyadvance.go b/emb/machine/machine_gameboyadvance.go new file mode 100644 index 00000000..a5c0c06c --- /dev/null +++ b/emb/machine/machine_gameboyadvance.go @@ -0,0 +1,64 @@ +//go:build gameboyadvance + +package machine + +import ( + "device/gba" + + "image/color" + "runtime/volatile" + "unsafe" +) + +// Not sure what name to pick here. Not using ARM7TDMI because that's the CPU +// name, not the device name. +const deviceName = "GBA" + +// Interrupt numbers as used on the GameBoy Advance. Register them with +// runtime/interrupt.New. +const ( + IRQ_VBLANK = gba.IRQ_VBLANK + IRQ_HBLANK = gba.IRQ_HBLANK + IRQ_VCOUNT = gba.IRQ_VCOUNT + IRQ_TIMER0 = gba.IRQ_TIMER0 + IRQ_TIMER1 = gba.IRQ_TIMER1 + IRQ_TIMER2 = gba.IRQ_TIMER2 + IRQ_TIMER3 = gba.IRQ_TIMER3 + IRQ_COM = gba.IRQ_COM + IRQ_DMA0 = gba.IRQ_DMA0 + IRQ_DMA1 = gba.IRQ_DMA1 + IRQ_DMA2 = gba.IRQ_DMA2 + IRQ_DMA3 = gba.IRQ_DMA3 + IRQ_KEYPAD = gba.IRQ_KEYPAD + IRQ_GAMEPAK = gba.IRQ_GAMEPAK +) + +// Set has not been implemented. +func (p Pin) Set(value bool) { + // do nothing +} + +var Display = DisplayMode3{(*[160][240]volatile.Register16)(unsafe.Pointer(uintptr(gba.MEM_VRAM)))} + +type DisplayMode3 struct { + port *[160][240]volatile.Register16 +} + +func (d *DisplayMode3) Configure() { + // Use video mode 3 (in BG2, a 16bpp bitmap in VRAM) and Enable BG2 + gba.DISP.DISPCNT.Set(gba.DISPCNT_BGMODE_3<> 3) | ((uint16(c.G) >> 3) << 5) | ((uint16(c.B) >> 3) << 10)) +} + +func (d *DisplayMode3) Display() error { + // Nothing to do here. + return nil +} diff --git a/emb/machine/machine_generic.go b/emb/machine/machine_generic.go new file mode 100644 index 00000000..20bded88 --- /dev/null +++ b/emb/machine/machine_generic.go @@ -0,0 +1,366 @@ +//go:build !baremetal + +package machine + +import ( + "crypto/rand" +) + +// Dummy machine package that calls out to external functions. + +const deviceName = "generic" + +var ( + USB = &UART{100} +) + +// The Serial port always points to the default UART in a simulated environment. +// +// TODO: perhaps this should be a special serial object that outputs via WASI +// stdout calls. +var Serial = hardwareUART0 + +const ( + PinInput PinMode = iota + PinOutput + PinInputPullup + PinInputPulldown +) + +func (p Pin) Configure(config PinConfig) { + gpioConfigure(p, config) +} + +func (p Pin) Set(value bool) { + gpioSet(p, value) +} + +func (p Pin) Get() bool { + return gpioGet(p) +} + +//export __tinygo_gpio_configure +func gpioConfigure(pin Pin, config PinConfig) + +//export __tinygo_gpio_set +func gpioSet(pin Pin, value bool) + +//export __tinygo_gpio_get +func gpioGet(pin Pin) bool + +// Generic PWM/timer peripheral. Properties can be configured depending on the +// hardware. +type timerType struct { + // Static properties. + instance int32 + frequency uint64 + bits int + prescalers []int + channelPins [][]Pin + + // Configured 'top' value. + top uint32 +} + +// Configure the PWM/timer peripheral. +func (t *timerType) Configure(config PWMConfig) error { + // Note: for very large period values, this multiplication will overflow. + top := config.Period * t.frequency / 1e9 + if config.Period == 0 { + top = 0xffff // default for LEDs + } + + // The maximum value that can be stored with the given number of bits in + // this timer. + maxTop := uint64(1)< FUNC_GPIO7 { + return // The pin is not configured as GPIO or GPIOHS. + } + + // Configure pin. + kendryte.FPIOA.IO[uint8(p)].SetBits(kendryte.FPIOA_IO_OE_EN | kendryte.FPIOA_IO_IE_EN | kendryte.FPIOA_IO_ST | kendryte.FPIOA_IO_DS_Msk) + + switch config.Mode { + case PinInput: + p.setFPIOAIOPull(fpioaPullNone) + input = true + case PinInputPullup: + p.setFPIOAIOPull(fpioaPullUp) + input = true + case PinInputPulldown: + p.setFPIOAIOPull(fpioaPullDown) + input = true + case PinOutput: + p.setFPIOAIOPull(fpioaPullNone) + input = false + } + + if f >= FUNC_GPIO0 && f <= FUNC_GPIO7 { + // Converts the IO pin number in the effective GPIO number (based on the FPIOA function). + gpioPin := uint8(f - FUNC_GPIO0) + + if input { + kendryte.GPIO.DIRECTION.ClearBits(1 << gpioPin) + } else { + kendryte.GPIO.DIRECTION.SetBits(1 << gpioPin) + } + } else if f >= FUNC_GPIOHS0 && f <= FUNC_GPIOHS31 { + // Converts the IO pin number in the effective GPIOHS number (based on the FPIOA function). + gpioPin := uint8(f - FUNC_GPIOHS0) + + if input { + kendryte.GPIOHS.INPUT_EN.SetBits(1 << gpioPin) + kendryte.GPIOHS.OUTPUT_EN.ClearBits(1 << gpioPin) + } else { + kendryte.GPIOHS.OUTPUT_EN.SetBits(1 << gpioPin) + kendryte.GPIOHS.INPUT_EN.ClearBits(1 << gpioPin) + } + } +} + +// Set the pin to high or low. +func (p Pin) Set(high bool) { + + // Check if the current pin's FPIOA function is either GPIO or GPIOHS. + f := p.FPIOAFunction() + if f < FUNC_GPIOHS0 || f > FUNC_GPIO7 { + return // The pin is not configured as GPIO or GPIOHS. + } + + if f >= FUNC_GPIO0 && f <= FUNC_GPIO7 { + gpioPin := uint8(f - FUNC_GPIO0) + + if high { + kendryte.GPIO.DATA_OUTPUT.SetBits(1 << gpioPin) + } else { + kendryte.GPIO.DATA_OUTPUT.ClearBits(1 << gpioPin) + } + } else if f >= FUNC_GPIOHS0 && f <= FUNC_GPIOHS31 { + gpioPin := uint8(f - FUNC_GPIOHS0) + + if high { + kendryte.GPIOHS.OUTPUT_VAL.SetBits(1 << gpioPin) + } else { + kendryte.GPIOHS.OUTPUT_VAL.ClearBits(1 << gpioPin) + } + } +} + +// Get returns the current value of a GPIO pin. +func (p Pin) Get() bool { + + // Check if the current pin's FPIOA function is either GPIO or GPIOHS. + f := p.FPIOAFunction() + if f < FUNC_GPIOHS0 || f > FUNC_GPIO7 { + return false // The pin is not configured as GPIO or GPIOHS. + } + + var val uint32 + if f >= FUNC_GPIO0 && f <= FUNC_GPIO7 { + gpioPin := uint8(f - FUNC_GPIO0) + val = kendryte.GPIO.DATA_INPUT.Get() & (1 << gpioPin) + } else if f >= FUNC_GPIOHS0 && f <= FUNC_GPIOHS31 { + gpioPin := uint8(f - FUNC_GPIOHS0) + val = kendryte.GPIOHS.INPUT_VAL.Get() & (1 << gpioPin) + } + return (val > 0) +} + +// Callbacks to be called for GPIOHS pins configured with SetInterrupt. +var pinCallbacks [32]func(Pin) + +// SetInterrupt sets an interrupt to be executed when a particular pin changes +// state. The pin should already be configured as an input, including a pull up +// or down if no external pull is provided. +// +// You can pass a nil func to unset the pin change interrupt. If you do so, +// the change parameter is ignored and can be set to any value (such as 0). +// If the pin is already configured with a callback, you must first unset +// this pins interrupt before you can set a new callback. +func (p Pin) SetInterrupt(change PinChange, callback func(Pin)) error { + + // Check if the pin is a GPIOHS pin. + f := p.FPIOAFunction() + if f < FUNC_GPIOHS0 || f > FUNC_GPIOHS31 { + return ErrInvalidDataPin + } + + gpioPin := uint8(f - FUNC_GPIOHS0) + + // Clear all interrupts. + kendryte.GPIOHS.RISE_IE.ClearBits(1 << gpioPin) + kendryte.GPIOHS.FALL_IE.ClearBits(1 << gpioPin) + kendryte.GPIOHS.HIGH_IE.ClearBits(1 << gpioPin) + kendryte.GPIOHS.LOW_IE.ClearBits(1 << gpioPin) + + // Clear all the pending bits for this pin. + kendryte.GPIOHS.RISE_IP.SetBits(1 << gpioPin) + kendryte.GPIOHS.FALL_IP.SetBits(1 << gpioPin) + kendryte.GPIOHS.HIGH_IP.SetBits(1 << gpioPin) + kendryte.GPIOHS.LOW_IP.SetBits(1 << gpioPin) + + if callback == nil { + if pinCallbacks[gpioPin] != nil { + pinCallbacks[gpioPin] = nil + } + return nil + } + + if pinCallbacks[gpioPin] != nil { + // The pin was already configured. + // To properly re-configure a pin, unset it first and set a new + // configuration. + return ErrNoPinChangeChannel + } + + pinCallbacks[gpioPin] = callback + + // Enable interrupts. + if change&PinRising != 0 { + kendryte.GPIOHS.RISE_IE.SetBits(1 << gpioPin) + } + if change&PinFalling != 0 { + kendryte.GPIOHS.FALL_IE.SetBits(1 << gpioPin) + } + + handleInterrupt := func(inter interrupt.Interrupt) { + + pin := uint8(inter.GetNumber() - kendryte.IRQ_GPIOHS0) + + if kendryte.GPIOHS.RISE_IE.HasBits(1 << pin) { + kendryte.GPIOHS.RISE_IE.ClearBits(1 << pin) + // Acknowledge interrupt atomically. + riscv.AsmFull( + "amoor.w {}, {mask}, ({reg})", + map[string]interface{}{ + "mask": uint32(1 << pin), + "reg": uintptr(unsafe.Pointer(&kendryte.GPIOHS.RISE_IP.Reg)), + }) + kendryte.GPIOHS.RISE_IE.SetBits(1 << pin) + } + + if kendryte.GPIOHS.FALL_IE.HasBits(1 << pin) { + kendryte.GPIOHS.FALL_IE.ClearBits(1 << pin) + // Acknowledge interrupt atomically. + riscv.AsmFull( + "amoor.w {}, {mask}, ({reg})", + map[string]interface{}{ + "mask": uint32(1 << pin), + "reg": uintptr(unsafe.Pointer(&kendryte.GPIOHS.FALL_IP.Reg)), + }) + kendryte.GPIOHS.FALL_IE.SetBits(1 << pin) + } + + pinCallbacks[pin](Pin(pin)) + } + + var ir interrupt.Interrupt + + switch f { + case FUNC_GPIOHS0: + ir = interrupt.New(kendryte.IRQ_GPIOHS0, handleInterrupt) + case FUNC_GPIOHS1: + ir = interrupt.New(kendryte.IRQ_GPIOHS1, handleInterrupt) + case FUNC_GPIOHS2: + ir = interrupt.New(kendryte.IRQ_GPIOHS2, handleInterrupt) + case FUNC_GPIOHS3: + ir = interrupt.New(kendryte.IRQ_GPIOHS3, handleInterrupt) + case FUNC_GPIOHS4: + ir = interrupt.New(kendryte.IRQ_GPIOHS4, handleInterrupt) + case FUNC_GPIOHS5: + ir = interrupt.New(kendryte.IRQ_GPIOHS5, handleInterrupt) + case FUNC_GPIOHS6: + ir = interrupt.New(kendryte.IRQ_GPIOHS6, handleInterrupt) + case FUNC_GPIOHS7: + ir = interrupt.New(kendryte.IRQ_GPIOHS7, handleInterrupt) + case FUNC_GPIOHS8: + ir = interrupt.New(kendryte.IRQ_GPIOHS8, handleInterrupt) + case FUNC_GPIOHS9: + ir = interrupt.New(kendryte.IRQ_GPIOHS9, handleInterrupt) + case FUNC_GPIOHS10: + ir = interrupt.New(kendryte.IRQ_GPIOHS10, handleInterrupt) + case FUNC_GPIOHS11: + ir = interrupt.New(kendryte.IRQ_GPIOHS11, handleInterrupt) + case FUNC_GPIOHS12: + ir = interrupt.New(kendryte.IRQ_GPIOHS12, handleInterrupt) + case FUNC_GPIOHS13: + ir = interrupt.New(kendryte.IRQ_GPIOHS13, handleInterrupt) + case FUNC_GPIOHS14: + ir = interrupt.New(kendryte.IRQ_GPIOHS14, handleInterrupt) + case FUNC_GPIOHS15: + ir = interrupt.New(kendryte.IRQ_GPIOHS15, handleInterrupt) + case FUNC_GPIOHS16: + ir = interrupt.New(kendryte.IRQ_GPIOHS16, handleInterrupt) + case FUNC_GPIOHS17: + ir = interrupt.New(kendryte.IRQ_GPIOHS17, handleInterrupt) + case FUNC_GPIOHS18: + ir = interrupt.New(kendryte.IRQ_GPIOHS18, handleInterrupt) + case FUNC_GPIOHS19: + ir = interrupt.New(kendryte.IRQ_GPIOHS19, handleInterrupt) + case FUNC_GPIOHS20: + ir = interrupt.New(kendryte.IRQ_GPIOHS20, handleInterrupt) + case FUNC_GPIOHS21: + ir = interrupt.New(kendryte.IRQ_GPIOHS21, handleInterrupt) + case FUNC_GPIOHS22: + ir = interrupt.New(kendryte.IRQ_GPIOHS22, handleInterrupt) + case FUNC_GPIOHS23: + ir = interrupt.New(kendryte.IRQ_GPIOHS23, handleInterrupt) + case FUNC_GPIOHS24: + ir = interrupt.New(kendryte.IRQ_GPIOHS24, handleInterrupt) + case FUNC_GPIOHS25: + ir = interrupt.New(kendryte.IRQ_GPIOHS25, handleInterrupt) + case FUNC_GPIOHS26: + ir = interrupt.New(kendryte.IRQ_GPIOHS26, handleInterrupt) + case FUNC_GPIOHS27: + ir = interrupt.New(kendryte.IRQ_GPIOHS27, handleInterrupt) + case FUNC_GPIOHS28: + ir = interrupt.New(kendryte.IRQ_GPIOHS28, handleInterrupt) + case FUNC_GPIOHS29: + ir = interrupt.New(kendryte.IRQ_GPIOHS29, handleInterrupt) + case FUNC_GPIOHS30: + ir = interrupt.New(kendryte.IRQ_GPIOHS30, handleInterrupt) + case FUNC_GPIOHS31: + ir = interrupt.New(kendryte.IRQ_GPIOHS31, handleInterrupt) + } + + ir.SetPriority(5) + ir.Enable() + + return nil + +} + +type UART struct { + Bus *kendryte.UARTHS_Type + Buffer *RingBuffer +} + +var ( + UART0 = &_UART0 + _UART0 = UART{Bus: kendryte.UARTHS, Buffer: NewRingBuffer()} +) + +func (uart *UART) Configure(config UARTConfig) { + + // Use default baudrate if not set. + if config.BaudRate == 0 { + config.BaudRate = 115200 + } + + // Use default pins if not set. + if config.TX == 0 && config.RX == 0 { + config.TX = UART_TX_PIN + config.RX = UART_RX_PIN + } + + config.TX.SetFPIOAFunction(FUNC_UARTHS_TX) + config.RX.SetFPIOAFunction(FUNC_UARTHS_RX) + + div := CPUFrequency()/config.BaudRate - 1 + + uart.Bus.DIV.Set(div) + uart.Bus.TXCTRL.Set(kendryte.UARTHS_TXCTRL_TXEN) + uart.Bus.RXCTRL.Set(kendryte.UARTHS_RXCTRL_RXEN) + + // Enable interrupts on receive. + uart.Bus.IE.Set(kendryte.UARTHS_IE_RXWM) + + intr := interrupt.New(kendryte.IRQ_UARTHS, _UART0.handleInterrupt) + intr.SetPriority(5) + intr.Enable() +} + +func (uart *UART) handleInterrupt(interrupt.Interrupt) { + rxdata := uart.Bus.RXDATA.Get() + c := byte(rxdata) + if uint32(c) != rxdata { + // The rxdata has other bits set than just the low 8 bits. This probably + // means that the 'empty' flag is set, which indicates there is no data + // to be read and the byte is garbage. Ignore this byte. + return + } + uart.Receive(c) +} + +func (uart *UART) writeByte(c byte) error { + for uart.Bus.TXDATA.Get()&kendryte.UARTHS_TXDATA_FULL != 0 { + } + + uart.Bus.TXDATA.Set(uint32(c)) + return nil +} + +func (uart *UART) flush() {} + +type SPI struct { + Bus *kendryte.SPI_Type +} + +// SPIConfig is used to store config info for SPI. +type SPIConfig struct { + Frequency uint32 + SCK Pin + SDO Pin + SDI Pin + LSBFirst bool + Mode uint8 +} + +// Configure is intended to setup the SPI interface. +// Only SPI controller 0 and 1 can be used because SPI2 is a special +// peripheral-mode controller and SPI3 is used for flashing. +func (spi *SPI) Configure(config SPIConfig) error { + // Use default pins if not set. + if config.SCK == 0 && config.SDO == 0 && config.SDI == 0 { + config.SCK = SPI0_SCK_PIN + config.SDO = SPI0_SDO_PIN + config.SDI = SPI0_SDI_PIN + } + + // Enable APB2 clock. + kendryte.SYSCTL.CLK_EN_CENT.SetBits(kendryte.SYSCTL_CLK_EN_CENT_APB2_CLK_EN) + + switch spi.Bus { + case kendryte.SPI0: + // Initialize SPI clock. + kendryte.SYSCTL.CLK_EN_PERI.SetBits(kendryte.SYSCTL_CLK_EN_PERI_SPI0_CLK_EN) + kendryte.SYSCTL.CLK_TH1.ClearBits(kendryte.SYSCTL_CLK_TH1_SPI0_CLK_Msk) + + // Initialize pins. + config.SCK.SetFPIOAFunction(FUNC_SPI0_SCLK) + config.SDO.SetFPIOAFunction(FUNC_SPI0_D0) + config.SDI.SetFPIOAFunction(FUNC_SPI0_D1) + case kendryte.SPI1: + // Initialize SPI clock. + kendryte.SYSCTL.CLK_EN_PERI.SetBits(kendryte.SYSCTL_CLK_EN_PERI_SPI1_CLK_EN) + kendryte.SYSCTL.CLK_TH1.ClearBits(kendryte.SYSCTL_CLK_TH1_SPI1_CLK_Msk) + + // Initialize pins. + config.SCK.SetFPIOAFunction(FUNC_SPI1_SCLK) + config.SDO.SetFPIOAFunction(FUNC_SPI1_D0) + config.SDI.SetFPIOAFunction(FUNC_SPI1_D1) + default: + return errUnsupportedSPIController + } + + // Set default frequency. + if config.Frequency == 0 { + config.Frequency = 4000000 // 4MHz + } + + baudr := CPUFrequency() / config.Frequency + spi.Bus.BAUDR.Set(baudr) + + // Configure SPI mode 0, standard frame format, 8-bit data, little-endian. + spi.Bus.IMR.Set(0) + spi.Bus.DMACR.Set(0) + spi.Bus.DMATDLR.Set(0x10) + spi.Bus.DMARDLR.Set(0) + spi.Bus.SER.Set(0) + spi.Bus.SSIENR.Set(0) + spi.Bus.CTRLR0.Set((7 << 16)) + spi.Bus.SPI_CTRLR0.Set(0) + spi.Bus.ENDIAN.Set(0) + + return nil +} + +// Transfer writes/reads a single byte using the SPI interface. +func (spi *SPI) Transfer(w byte) (byte, error) { + spi.Bus.SSIENR.Set(0) + + // Set transfer-receive mode. + spi.Bus.CTRLR0.ClearBits(0x3 << 8) + + // Enable/disable SPI. + spi.Bus.SSIENR.Set(1) + defer spi.Bus.SSIENR.Set(0) + + // Enable/disable device. + spi.Bus.SER.Set(0x1) + defer spi.Bus.SER.Set(0) + + spi.Bus.DR0.Set(uint32(w)) + + // Wait for transfer. + for spi.Bus.SR.Get()&0x05 != 0x04 { + } + + // Wait for data. + for spi.Bus.RXFLR.Get() == 0 { + } + + return byte(spi.Bus.DR0.Get()), nil +} + +// I2C on the K210. +type I2C struct { + Bus kendryte.I2C_Type +} + +var ( + I2C0 = (*I2C)(unsafe.Pointer(kendryte.I2C0)) + I2C1 = (*I2C)(unsafe.Pointer(kendryte.I2C1)) + I2C2 = (*I2C)(unsafe.Pointer(kendryte.I2C2)) +) + +// I2CConfig is used to store config info for I2C. +type I2CConfig struct { + Frequency uint32 + SCL Pin + SDA Pin +} + +// Configure is intended to setup the I2C interface. +func (i2c *I2C) Configure(config I2CConfig) error { + + if config.Frequency == 0 { + config.Frequency = 100 * KHz + } + + if config.SDA == 0 && config.SCL == 0 { + config.SDA = I2C0_SDA_PIN + config.SCL = I2C0_SCL_PIN + } + + // Enable APB0 clock. + kendryte.SYSCTL.CLK_EN_CENT.SetBits(kendryte.SYSCTL_CLK_EN_CENT_APB0_CLK_EN) + + switch &i2c.Bus { + case kendryte.I2C0: + // Initialize I2C0 clock. + kendryte.SYSCTL.CLK_EN_PERI.SetBits(kendryte.SYSCTL_CLK_EN_PERI_I2C0_CLK_EN) + kendryte.SYSCTL.CLK_TH5.ReplaceBits(0x03, kendryte.SYSCTL_CLK_TH5_I2C0_CLK_Msk, kendryte.SYSCTL_CLK_TH5_I2C0_CLK_Pos) + + // Initialize pins. + config.SDA.SetFPIOAFunction(FUNC_I2C0_SDA) + config.SCL.SetFPIOAFunction(FUNC_I2C0_SCLK) + case kendryte.I2C1: + // Initialize I2C1 clock. + kendryte.SYSCTL.CLK_EN_PERI.SetBits(kendryte.SYSCTL_CLK_EN_PERI_I2C1_CLK_EN) + kendryte.SYSCTL.CLK_TH5.ReplaceBits(0x03, kendryte.SYSCTL_CLK_TH5_I2C1_CLK_Msk, kendryte.SYSCTL_CLK_TH5_I2C1_CLK_Pos) + + // Initialize pins. + config.SDA.SetFPIOAFunction(FUNC_I2C1_SDA) + config.SCL.SetFPIOAFunction(FUNC_I2C1_SCLK) + case kendryte.I2C2: + // Initialize I2C2 clock. + kendryte.SYSCTL.CLK_EN_PERI.SetBits(kendryte.SYSCTL_CLK_EN_PERI_I2C2_CLK_EN) + kendryte.SYSCTL.CLK_TH5.ReplaceBits(0x03, kendryte.SYSCTL_CLK_TH5_I2C2_CLK_Msk, kendryte.SYSCTL_CLK_TH5_I2C2_CLK_Pos) + + // Initialize pins. + config.SDA.SetFPIOAFunction(FUNC_I2C2_SDA) + config.SCL.SetFPIOAFunction(FUNC_I2C2_SCLK) + } + + i2c.SetBaudRate(config.Frequency) + + i2c.Bus.INTR_MASK.Set(0) + i2c.Bus.DMA_CR.Set(0x03) + i2c.Bus.DMA_RDLR.Set(0) + i2c.Bus.DMA_TDLR.Set(0x4) + + return nil +} + +// SetBaudRate sets the communication speed for I2C. +func (i2c *I2C) SetBaudRate(br uint32) error { + div := CPUFrequency() / br / 16 + + // Disable controller before setting the prescale register. + i2c.Bus.ENABLE.Set(0) + + i2c.Bus.CON.Set(0x63) + + // Set prescaler registers. + i2c.Bus.SS_SCL_HCNT.Set(uint32(div)) + i2c.Bus.SS_SCL_LCNT.Set(uint32(div)) + + return nil +} + +// Tx does a single I2C transaction at the specified address. +// It clocks out the given address, writes the bytes in w, reads back len(r) +// bytes and stores them in r, and generates a stop condition on the bus. +func (i2c *I2C) Tx(addr uint16, w, r []byte) error { + // Set peripheral address. + i2c.Bus.TAR.Set(uint32(addr)) + // Enable controller. + i2c.Bus.ENABLE.Set(1) + + if len(w) != 0 { + i2c.Bus.CLR_TX_ABRT.Set(i2c.Bus.CLR_TX_ABRT.Get()) + dataLen := uint32(len(w)) + di := 0 + + for dataLen != 0 { + fifoLen := 8 - i2c.Bus.TXFLR.Get() + if dataLen < fifoLen { + fifoLen = dataLen + } + + for i := uint32(0); i < fifoLen; i++ { + i2c.Bus.DATA_CMD.Set(uint32(w[di])) + di += 1 + } + if i2c.Bus.TX_ABRT_SOURCE.Get() != 0 { + return errI2CTxAbort + } + dataLen -= fifoLen + } + + // Wait for transmission to complete. + for i2c.Bus.STATUS.HasBits(kendryte.I2C_STATUS_ACTIVITY) || !i2c.Bus.STATUS.HasBits(kendryte.I2C_STATUS_TFE) { + } + + if i2c.Bus.TX_ABRT_SOURCE.Get() != 0 { + return errI2CTxAbort + } + } + if len(r) != 0 { + dataLen := uint32(len(r)) + cmdLen := uint32(len(r)) + di := 0 + + for dataLen != 0 || cmdLen != 0 { + fifoLen := i2c.Bus.RXFLR.Get() + if dataLen < fifoLen { + fifoLen = dataLen + } + for i := uint32(0); i < fifoLen; i++ { + r[di] = byte(i2c.Bus.DATA_CMD.Get()) + di += 1 + } + dataLen -= fifoLen + + fifoLen = 8 - i2c.Bus.TXFLR.Get() + if cmdLen < fifoLen { + fifoLen = cmdLen + } + for i := uint32(0); i < fifoLen; i++ { + i2c.Bus.DATA_CMD.Set(0x100) + } + if i2c.Bus.TX_ABRT_SOURCE.Get() != 0 { + return errI2CTxAbort + } + cmdLen -= fifoLen + } + } + + return nil +} diff --git a/emb/machine/machine_mimxrt1062.go b/emb/machine/machine_mimxrt1062.go new file mode 100644 index 00000000..74d01c76 --- /dev/null +++ b/emb/machine/machine_mimxrt1062.go @@ -0,0 +1,1064 @@ +//go:build mimxrt1062 + +package machine + +import ( + "device/nxp" + "math/bits" + "runtime/interrupt" + "runtime/volatile" +) + +// Peripheral abstraction layer for the MIMXRT1062 + +const deviceName = nxp.Device + +func CPUFrequency() uint32 { + return 600000000 +} + +const ( + // GPIO + PinInput PinMode = iota + PinInputPullup + PinInputPulldown + PinOutput + PinOutputOpenDrain + PinDisable + + // ADC + PinInputAnalog + + // UART + PinModeUARTTX + PinModeUARTRX + + // SPI + PinModeSPISDI + PinModeSPISDO + PinModeSPICLK + PinModeSPICS + + // I2C + PinModeI2CSDA + PinModeI2CSCL +) + +// Deprecated: use PinInputPullup and PinInputPulldown instead. +const ( + PinInputPullUp = PinInputPullup + PinInputPullDown = PinInputPulldown +) + +type PinChange uint8 + +const ( + PinRising PinChange = iota + 2 + PinFalling + PinToggle +) + +// pinJumpTable represents a function lookup table for all 128 GPIO pins. +// +// There are 4 GPIO ports (A-D) and 32 pins (0-31) on each port. The uint8 value +// of a Pin is used as table index. The number of pins with a defined (non-nil) +// function is recorded in the uint8 field numDefined. +type pinJumpTable struct { + lut [4 * 32]func(Pin) + numDefined uint8 +} + +// pinISR stores the interrupt callbacks for GPIO pins, and pinInterrupt holds +// an interrupt service routine that dispatches the interrupt callbacks. +var ( + pinISR pinJumpTable + pinInterrupt *interrupt.Interrupt +) + +// From the i.MXRT1062 Processor Reference Manual (Chapter 12 - GPIO): +// +// | High-speed GPIOs exist in this device: +// | - GPIO1-5 are standard-speed GPIOs that run off the IPG_CLK_ROOT, while +// | GPIO6-9 are high-speed GPIOs that run at the AHB_CLK_ROOT frequency. +// | See the table "System Clocks, Gating, and Override" in CCM chapter. +// | - Regular GPIO and high speed GPIO are paired (GPIO1 and GPIO6 share the +// | same pins, GPIO2 and GPIO7 share, etc). The IOMUXC_GPR_GPR26-29 +// | registers are used to determine if the regular or high-speed GPIO +// | module is used for the GPIO pins on a given port. +// +// Therefore, we do not even use GPIO1-5 and instead use their high-speed +// partner for all pins. This is configured at startup in the runtime package +// (func initPins() in `runtime_mimxrt1062.go`). +// We cannot declare 32 pins for all available ports (GPIO1-9) anyway, since Pin +// is only uint8, and 9*32=288 > 256, so something has to be sacrificed. + +const ( + portA Pin = iota * 32 // GPIO1(6) + portB // GPIO2(7) + portC // GPIO3(8) + portD // GPIO4(9) +) + +const ( + // [Pad]: Alt Func 0 Alt Func 1 Alt Func 2 Alt Func 3 Alt Func 4 Alt Func 5 Alt Func 6 Alt Func 7 Alt Func 8 Alt Func 9 + // ---------- --------------- --------------- ------------------- -------------------- -------------------- ----------- -------------------- -------------------- --------------------- ---------------- + PA0 = portA + 0 // [AD_B0_00]: FLEXPWM2_PWMA03 XBAR1_INOUT14 REF_CLK_32K USB_OTG2_ID LPI2C1_SCLS GPIO1_IO00 USDHC1_RESET_B LPSPI3_SCK ~ ~ + PA1 = portA + 1 // [AD_B0_01]: FLEXPWM2_PWMB03 XBAR1_INOUT15 REF_CLK_24M USB_OTG1_ID LPI2C1_SDAS GPIO1_IO01 EWM_OUT_B LPSPI3_SDO ~ ~ + PA2 = portA + 2 // [AD_B0_02]: FLEXCAN2_TX XBAR1_INOUT16 LPUART6_TX USB_OTG1_PWR FLEXPWM1_PWMX00 GPIO1_IO02 LPI2C1_HREQ LPSPI3_SDI ~ ~ + PA3 = portA + 3 // [AD_B0_03]: FLEXCAN2_RX XBAR1_INOUT17 LPUART6_RX USB_OTG1_OC FLEXPWM1_PWMX01 GPIO1_IO03 REF_CLK_24M LPSPI3_PCS0 ~ ~ + PA4 = portA + 4 // [AD_B0_04]: SRC_BOOT_MODE00 MQS_RIGHT ENET_TX_DATA03 SAI2_TX_SYNC CSI_DATA09 GPIO1_IO04 PIT_TRIGGER00 LPSPI3_PCS1 ~ ~ + PA5 = portA + 5 // [AD_B0_05]: SRC_BOOT_MODE01 MQS_LEFT ENET_TX_DATA02 SAI2_TX_BCLK CSI_DATA08 GPIO1_IO05 XBAR1_INOUT17 LPSPI3_PCS2 ~ ~ + PA6 = portA + 6 // [AD_B0_06]: JTAG_TMS GPT2_COMPARE1 ENET_RX_CLK SAI2_RX_BCLK CSI_DATA07 GPIO1_IO06 XBAR1_INOUT18 LPSPI3_PCS3 ~ ~ + PA7 = portA + 7 // [AD_B0_07]: JTAG_TCK GPT2_COMPARE2 ENET_TX_ER SAI2_RX_SYNC CSI_DATA06 GPIO1_IO07 XBAR1_INOUT19 ENET_1588_EVENT3_OUT ~ ~ + PA8 = portA + 8 // [AD_B0_08]: JTAG_MOD GPT2_COMPARE3 ENET_RX_DATA03 SAI2_RX_DATA CSI_DATA05 GPIO1_IO08 XBAR1_IN20 ENET_1588_EVENT3_IN ~ ~ + PA9 = portA + 9 // [AD_B0_09]: JTAG_TDI FLEXPWM2_PWMA03 ENET_RX_DATA02 SAI2_TX_DATA CSI_DATA04 GPIO1_IO09 XBAR1_IN21 GPT2_CLK SEMC_DQS4 ~ + PA10 = portA + 10 // [AD_B0_10]: JTAG_TDO FLEXPWM1_PWMA03 ENET_CRS SAI2_MCLK CSI_DATA03 GPIO1_IO10 XBAR1_IN22 ENET_1588_EVENT0_OUT FLEXCAN3_TX ARM_TRACE_SWO + PA11 = portA + 11 // [AD_B0_11]: JTAG_TRSTB FLEXPWM1_PWMB03 ENET_COL WDOG1_WDOG_B CSI_DATA02 GPIO1_IO11 XBAR1_IN23 ENET_1588_EVENT0_IN FLEXCAN3_RX SEMC_CLK6 + PA12 = portA + 12 // [AD_B0_12]: LPI2C4_SCL CCM_PMIC_READY LPUART1_TX WDOG2_WDOG_B FLEXPWM1_PWMX02 GPIO1_IO12 ENET_1588_EVENT1_OUT NMI_GLUE_NMI ~ ~ + PA13 = portA + 13 // [AD_B0_13]: LPI2C4_SDA GPT1_CLK LPUART1_RX EWM_OUT_B FLEXPWM1_PWMX03 GPIO1_IO13 ENET_1588_EVENT1_IN REF_CLK_24M ~ ~ + PA14 = portA + 14 // [AD_B0_14]: USB_OTG2_OC XBAR1_IN24 LPUART1_CTS_B ENET_1588_EVENT0_OUT CSI_VSYNC GPIO1_IO14 FLEXCAN2_TX FLEXCAN3_TX ~ ~ + PA15 = portA + 15 // [AD_B0_15]: USB_OTG2_PWR XBAR1_IN25 LPUART1_RTS_B ENET_1588_EVENT0_IN CSI_HSYNC GPIO1_IO15 FLEXCAN2_RX WDOG1_WDOG_RST_B_DEB FLEXCAN3_RX ~ + PA16 = portA + 16 // [AD_B1_00]: USB_OTG2_ID QTIMER3_TIMER0 LPUART2_CTS_B LPI2C1_SCL WDOG1_B GPIO1_IO16 USDHC1_WP KPP_ROW07 ENET2_1588_EVENT0_OUT FLEXIO3_FLEXIO00 + PA17 = portA + 17 // [AD_B1_01]: USB_OTG1_PWR QTIMER3_TIMER1 LPUART2_RTS_B LPI2C1_SDA CCM_PMIC_READY GPIO1_IO17 USDHC1_VSELECT KPP_COL07 ENET2_1588_EVENT0_IN FLEXIO3_FLEXIO01 + PA18 = portA + 18 // [AD_B1_02]: USB_OTG1_ID QTIMER3_TIMER2 LPUART2_TX SPDIF_OUT ENET_1588_EVENT2_OUT GPIO1_IO18 USDHC1_CD_B KPP_ROW06 GPT2_CLK FLEXIO3_FLEXIO02 + PA19 = portA + 19 // [AD_B1_03]: USB_OTG1_OC QTIMER3_TIMER3 LPUART2_RX SPDIF_IN ENET_1588_EVENT2_IN GPIO1_IO19 USDHC2_CD_B KPP_COL06 GPT2_CAPTURE1 FLEXIO3_FLEXIO03 + PA20 = portA + 20 // [AD_B1_04]: FLEXSPIB_DATA03 ENET_MDC LPUART3_CTS_B SPDIF_SR_CLK CSI_PIXCLK GPIO1_IO20 USDHC2_DATA0 KPP_ROW05 GPT2_CAPTURE2 FLEXIO3_FLEXIO04 + PA21 = portA + 21 // [AD_B1_05]: FLEXSPIB_DATA02 ENET_MDIO LPUART3_RTS_B SPDIF_OUT CSI_MCLK GPIO1_IO21 USDHC2_DATA1 KPP_COL05 GPT2_COMPARE1 FLEXIO3_FLEXIO05 + PA22 = portA + 22 // [AD_B1_06]: FLEXSPIB_DATA01 LPI2C3_SDA LPUART3_TX SPDIF_LOCK CSI_VSYNC GPIO1_IO22 USDHC2_DATA2 KPP_ROW04 GPT2_COMPARE2 FLEXIO3_FLEXIO06 + PA23 = portA + 23 // [AD_B1_07]: FLEXSPIB_DATA00 LPI2C3_SCL LPUART3_RX SPDIF_EXT_CLK CSI_HSYNC GPIO1_IO23 USDHC2_DATA3 KPP_COL04 GPT2_COMPARE3 FLEXIO3_FLEXIO07 + PA24 = portA + 24 // [AD_B1_08]: FLEXSPIA_SS1_B FLEXPWM4_PWMA00 FLEXCAN1_TX CCM_PMIC_READY CSI_DATA09 GPIO1_IO24 USDHC2_CMD KPP_ROW03 FLEXIO3_FLEXIO08 ~ + PA25 = portA + 25 // [AD_B1_09]: FLEXSPIA_DQS FLEXPWM4_PWMA01 FLEXCAN1_RX SAI1_MCLK CSI_DATA08 GPIO1_IO25 USDHC2_CLK KPP_COL03 FLEXIO3_FLEXIO09 ~ + PA26 = portA + 26 // [AD_B1_10]: FLEXSPIA_DATA03 WDOG1_B LPUART8_TX SAI1_RX_SYNC CSI_DATA07 GPIO1_IO26 USDHC2_WP KPP_ROW02 ENET2_1588_EVENT1_OUT FLEXIO3_FLEXIO10 + PA27 = portA + 27 // [AD_B1_11]: FLEXSPIA_DATA02 EWM_OUT_B LPUART8_RX SAI1_RX_BCLK CSI_DATA06 GPIO1_IO27 USDHC2_RESET_B KPP_COL02 ENET2_1588_EVENT1_IN FLEXIO3_FLEXIO11 + PA28 = portA + 28 // [AD_B1_12]: FLEXSPIA_DATA01 ACMP_OUT00 LPSPI3_PCS0 SAI1_RX_DATA00 CSI_DATA05 GPIO1_IO28 USDHC2_DATA4 KPP_ROW01 ENET2_1588_EVENT2_OUT FLEXIO3_FLEXIO12 + PA29 = portA + 29 // [AD_B1_13]: FLEXSPIA_DATA00 ACMP_OUT01 LPSPI3_SDI SAI1_TX_DATA00 CSI_DATA04 GPIO1_IO29 USDHC2_DATA5 KPP_COL01 ENET2_1588_EVENT2_IN FLEXIO3_FLEXIO13 + PA30 = portA + 30 // [AD_B1_14]: FLEXSPIA_SCLK ACMP_OUT02 LPSPI3_SDO SAI1_TX_BCLK CSI_DATA03 GPIO1_IO30 USDHC2_DATA6 KPP_ROW00 ENET2_1588_EVENT3_OUT FLEXIO3_FLEXIO14 + PA31 = portA + 31 // [AD_B1_15]: FLEXSPIA_SS0_B ACMP_OUT03 LPSPI3_SCK SAI1_TX_SYNC CSI_DATA02 GPIO1_IO31 USDHC2_DATA7 KPP_COL00 ENET2_1588_EVENT3_IN FLEXIO3_FLEXIO15 + + PB0 = portB + 0 // [B0_00]: LCD_CLK QTIMER1_TIMER0 MQS_RIGHT LPSPI4_PCS0 FLEXIO2_FLEXIO00 GPIO2_IO00 SEMC_CSX01 ENET2_MDC ~ ~ + PB1 = portB + 1 // [B0_01]: LCD_ENABLE QTIMER1_TIMER1 MQS_LEFT LPSPI4_SDI FLEXIO2_FLEXIO01 GPIO2_IO01 SEMC_CSX02 ENET2_MDIO ~ ~ + PB2 = portB + 2 // [B0_02]: LCD_HSYNC QTIMER1_TIMER2 FLEXCAN1_TX LPSPI4_SDO FLEXIO2_FLEXIO02 GPIO2_IO02 SEMC_CSX03 ENET2_1588_EVENT0_OUT ~ ~ + PB3 = portB + 3 // [B0_03]: LCD_VSYNC QTIMER2_TIMER0 FLEXCAN1_RX LPSPI4_SCK FLEXIO2_FLEXIO03 GPIO2_IO03 WDOG2_RESET_B_DEB ENET2_1588_EVENT0_IN ~ ~ + PB4 = portB + 4 // [B0_04]: LCD_DATA00 QTIMER2_TIMER1 LPI2C2_SCL ARM_TRACE0 FLEXIO2_FLEXIO04 GPIO2_IO04 SRC_BOOT_CFG00 ENET2_TDATA03 ~ ~ + PB5 = portB + 5 // [B0_05]: LCD_DATA01 QTIMER2_TIMER2 LPI2C2_SDA ARM_TRACE1 FLEXIO2_FLEXIO05 GPIO2_IO05 SRC_BOOT_CFG01 ENET2_TDATA02 ~ ~ + PB6 = portB + 6 // [B0_06]: LCD_DATA02 QTIMER3_TIMER0 FLEXPWM2_PWMA00 ARM_TRACE2 FLEXIO2_FLEXIO06 GPIO2_IO06 SRC_BOOT_CFG02 ENET2_RX_CLK ~ ~ + PB7 = portB + 7 // [B0_07]: LCD_DATA03 QTIMER3_TIMER1 FLEXPWM2_PWMB00 ARM_TRACE3 FLEXIO2_FLEXIO07 GPIO2_IO07 SRC_BOOT_CFG03 ENET2_TX_ER ~ ~ + PB8 = portB + 8 // [B0_08]: LCD_DATA04 QTIMER3_TIMER2 FLEXPWM2_PWMA01 LPUART3_TX FLEXIO2_FLEXIO08 GPIO2_IO08 SRC_BOOT_CFG04 ENET2_RDATA03 ~ ~ + PB9 = portB + 9 // [B0_09]: LCD_DATA05 QTIMER4_TIMER0 FLEXPWM2_PWMB01 LPUART3_RX FLEXIO2_FLEXIO09 GPIO2_IO09 SRC_BOOT_CFG05 ENET2_RDATA02 ~ ~ + PB10 = portB + 10 // [B0_10]: LCD_DATA06 QTIMER4_TIMER1 FLEXPWM2_PWMA02 SAI1_TX_DATA03 FLEXIO2_FLEXIO10 GPIO2_IO10 SRC_BOOT_CFG06 ENET2_CRS ~ ~ + PB11 = portB + 11 // [B0_11]: LCD_DATA07 QTIMER4_TIMER2 FLEXPWM2_PWMB02 SAI1_TX_DATA02 FLEXIO2_FLEXIO11 GPIO2_IO11 SRC_BOOT_CFG07 ENET2_COL ~ ~ + PB12 = portB + 12 // [B0_12]: LCD_DATA08 XBAR1_INOUT10 ARM_TRACE_CLK SAI1_TX_DATA01 FLEXIO2_FLEXIO12 GPIO2_IO12 SRC_BOOT_CFG08 ENET2_TDATA00 ~ ~ + PB13 = portB + 13 // [B0_13]: LCD_DATA09 XBAR1_INOUT11 ARM_TRACE_SWO SAI1_MCLK FLEXIO2_FLEXIO13 GPIO2_IO13 SRC_BOOT_CFG09 ENET2_TDATA01 ~ ~ + PB14 = portB + 14 // [B0_14]: LCD_DATA10 XBAR1_INOUT12 ARM_TXEV SAI1_RX_SYNC FLEXIO2_FLEXIO14 GPIO2_IO14 SRC_BOOT_CFG10 ENET2_TX_EN ~ ~ + PB15 = portB + 15 // [B0_15]: LCD_DATA11 XBAR1_INOUT13 ARM_RXEV SAI1_RX_BCLK FLEXIO2_FLEXIO15 GPIO2_IO15 SRC_BOOT_CFG11 ENET2_TX_CLK ENET2_REF_CLK2 ~ + PB16 = portB + 16 // [B1_00]: LCD_DATA12 XBAR1_INOUT14 LPUART4_TX SAI1_RX_DATA00 FLEXIO2_FLEXIO16 GPIO2_IO16 FLEXPWM1_PWMA03 ENET2_RX_ER FLEXIO3_FLEXIO16 ~ + PB17 = portB + 17 // [B1_01]: LCD_DATA13 XBAR1_INOUT15 LPUART4_RX SAI1_TX_DATA00 FLEXIO2_FLEXIO17 GPIO2_IO17 FLEXPWM1_PWMB03 ENET2_RDATA00 FLEXIO3_FLEXIO17 ~ + PB18 = portB + 18 // [B1_02]: LCD_DATA14 XBAR1_INOUT16 LPSPI4_PCS2 SAI1_TX_BCLK FLEXIO2_FLEXIO18 GPIO2_IO18 FLEXPWM2_PWMA03 ENET2_RDATA01 FLEXIO3_FLEXIO18 ~ + PB19 = portB + 19 // [B1_03]: LCD_DATA15 XBAR1_INOUT17 LPSPI4_PCS1 SAI1_TX_SYNC FLEXIO2_FLEXIO19 GPIO2_IO19 FLEXPWM2_PWMB03 ENET2_RX_EN FLEXIO3_FLEXIO19 ~ + PB20 = portB + 20 // [B1_04]: LCD_DATA16 LPSPI4_PCS0 CSI_DATA15 ENET_RX_DATA00 FLEXIO2_FLEXIO20 GPIO2_IO20 GPT1_CLK FLEXIO3_FLEXIO20 ~ ~ + PB21 = portB + 21 // [B1_05]: LCD_DATA17 LPSPI4_SDI CSI_DATA14 ENET_RX_DATA01 FLEXIO2_FLEXIO21 GPIO2_IO21 GPT1_CAPTURE1 FLEXIO3_FLEXIO21 ~ ~ + PB22 = portB + 22 // [B1_06]: LCD_DATA18 LPSPI4_SDO CSI_DATA13 ENET_RX_EN FLEXIO2_FLEXIO22 GPIO2_IO22 GPT1_CAPTURE2 FLEXIO3_FLEXIO22 ~ ~ + PB23 = portB + 23 // [B1_07]: LCD_DATA19 LPSPI4_SCK CSI_DATA12 ENET_TX_DATA00 FLEXIO2_FLEXIO23 GPIO2_IO23 GPT1_COMPARE1 FLEXIO3_FLEXIO23 ~ ~ + PB24 = portB + 24 // [B1_08]: LCD_DATA20 QTIMER1_TIMER3 CSI_DATA11 ENET_TX_DATA01 FLEXIO2_FLEXIO24 GPIO2_IO24 FLEXCAN2_TX GPT1_COMPARE2 FLEXIO3_FLEXIO24 ~ + PB25 = portB + 25 // [B1_09]: LCD_DATA21 QTIMER2_TIMER3 CSI_DATA10 ENET_TX_EN FLEXIO2_FLEXIO25 GPIO2_IO25 FLEXCAN2_RX GPT1_COMPARE3 FLEXIO3_FLEXIO25 ~ + PB26 = portB + 26 // [B1_10]: LCD_DATA22 QTIMER3_TIMER3 CSI_DATA00 ENET_TX_CLK FLEXIO2_FLEXIO26 GPIO2_IO26 ENET_REF_CLK FLEXIO3_FLEXIO26 ~ ~ + PB27 = portB + 27 // [B1_11]: LCD_DATA23 QTIMER4_TIMER3 CSI_DATA01 ENET_RX_ER FLEXIO2_FLEXIO27 GPIO2_IO27 LPSPI4_PCS3 FLEXIO3_FLEXIO27 ~ ~ + PB28 = portB + 28 // [B1_12]: LPUART5_TX CSI_PIXCLK ENET_1588_EVENT0_IN FLEXIO2_FLEXIO28 GPIO2_IO28 USDHC1_CD_B FLEXIO3_FLEXIO28 ~ ~ ~ + PB29 = portB + 29 // [B1_13]: WDOG1_B LPUART5_RX CSI_VSYNC ENET_1588_EVENT0_OUT FLEXIO2_FLEXIO29 GPIO2_IO29 USDHC1_WP SEMC_DQS4 FLEXIO3_FLEXIO29 ~ + PB30 = portB + 30 // [B1_14]: ENET_MDC FLEXPWM4_PWMA02 CSI_HSYNC XBAR1_IN02 FLEXIO2_FLEXIO30 GPIO2_IO30 USDHC1_VSELECT ENET2_TDATA00 FLEXIO3_FLEXIO30 ~ + PB31 = portB + 31 // [B1_15]: ENET_MDIO FLEXPWM4_PWMA03 CSI_MCLK XBAR1_IN03 FLEXIO2_FLEXIO31 GPIO2_IO31 USDHC1_RESET_B ENET2_TDATA01 FLEXIO3_FLEXIO31 ~ + + PC0 = portC + 0 // [SD_B1_00]: USDHC2_DATA3 FLEXSPIB_DATA03 FLEXPWM1_PWMA03 SAI1_TX_DATA03 LPUART4_TX GPIO3_IO00 SAI3_RX_DATA ~ ~ ~ + PC1 = portC + 1 // [SD_B1_01]: USDHC2_DATA2 FLEXSPIB_DATA02 FLEXPWM1_PWMB03 SAI1_TX_DATA02 LPUART4_RX GPIO3_IO01 SAI3_TX_DATA ~ ~ ~ + PC2 = portC + 2 // [SD_B1_02]: USDHC2_DATA1 FLEXSPIB_DATA01 FLEXPWM2_PWMA03 SAI1_TX_DATA01 FLEXCAN1_TX GPIO3_IO02 CCM_WAIT SAI3_TX_SYNC ~ ~ + PC3 = portC + 3 // [SD_B1_03]: USDHC2_DATA0 FLEXSPIB_DATA00 FLEXPWM2_PWMB03 SAI1_MCLK FLEXCAN1_RX GPIO3_IO03 CCM_PMIC_READY SAI3_TX_BCLK ~ ~ + PC4 = portC + 4 // [SD_B1_04]: USDHC2_CLK FLEXSPIB_SCLK LPI2C1_SCL SAI1_RX_SYNC FLEXSPIA_SS1_B GPIO3_IO04 CCM_STOP SAI3_MCLK ~ ~ + PC5 = portC + 5 // [SD_B1_05]: USDHC2_CMD FLEXSPIA_DQS LPI2C1_SDA SAI1_RX_BCLK FLEXSPIB_SS0_B GPIO3_IO05 SAI3_RX_SYNC ~ ~ ~ + PC6 = portC + 6 // [SD_B1_06]: USDHC2_RESET_B FLEXSPIA_SS0_B LPUART7_CTS_B SAI1_RX_DATA00 LPSPI2_PCS0 GPIO3_IO06 SAI3_RX_BCLK ~ ~ ~ + PC7 = portC + 7 // [SD_B1_07]: SEMC_CSX01 FLEXSPIA_SCLK LPUART7_RTS_B SAI1_TX_DATA00 LPSPI2_SCK GPIO3_IO07 ~ ~ ~ ~ + PC8 = portC + 8 // [SD_B1_08]: USDHC2_DATA4 FLEXSPIA_DATA00 LPUART7_TX SAI1_TX_BCLK LPSPI2_SD0 GPIO3_IO08 SEMC_CSX02 ~ ~ ~ + PC9 = portC + 9 // [SD_B1_09]: USDHC2_DATA5 FLEXSPIA_DATA01 LPUART7_RX SAI1_TX_SYNC LPSPI2_SDI GPIO3_IO09 ~ ~ ~ ~ + PC10 = portC + 10 // [SD_B1_10]: USDHC2_DATA6 FLEXSPIA_DATA02 LPUART2_RX LPI2C2_SDA LPSPI2_PCS2 GPIO3_IO10 ~ ~ ~ ~ + PC11 = portC + 11 // [SD_B1_11]: USDHC2_DATA7 FLEXSPIA_DATA03 LPUART2_TX LPI2C2_SCL LPSPI2_PCS3 GPIO3_IO11 ~ ~ ~ ~ + PC12 = portC + 12 // [SD_B0_00]: USDHC1_CMD FLEXPWM1_PWMA00 LPI2C3_SCL XBAR1_INOUT04 LPSPI1_SCK GPIO3_IO12 FLEXSPIA_SS1_B ENET2_TX_EN SEMC_DQS4 ~ + PC13 = portC + 13 // [SD_B0_01]: USDHC1_CLK FLEXPWM1_PWMB00 LPI2C3_SDA XBAR1_INOUT05 LPSPI1_PCS0 GPIO3_IO13 FLEXSPIB_SS1_B ENET2_TX_CLK ENET2_REF_CLK2 ~ + PC14 = portC + 14 // [SD_B0_02]: USDHC1_DATA0 FLEXPWM1_PWMA01 LPUART8_CTS_B XBAR1_INOUT06 LPSPI1_SDO GPIO3_IO14 ENET2_RX_ER SEMC_CLK5 ~ ~ + PC15 = portC + 15 // [SD_B0_03]: USDHC1_DATA1 FLEXPWM1_PWMB01 LPUART8_RTS_B XBAR1_INOUT07 LPSPI1_SDI GPIO3_IO15 ENET2_RDATA00 SEMC_CLK6 ~ ~ + PC16 = portC + 16 // [SD_B0_04]: USDHC1_DATA2 FLEXPWM1_PWMA02 LPUART8_TX XBAR1_INOUT08 FLEXSPIB_SS0_B GPIO3_IO16 CCM_CLKO1 ENET2_RDATA01 ~ ~ + PC17 = portC + 17 // [SD_B0_05]: USDHC1_DATA3 FLEXPWM1_PWMB02 LPUART8_RX XBAR1_INOUT09 FLEXSPIB_DQS GPIO3_IO17 CCM_CLKO2 ENET2_RX_EN ~ ~ + PC18 = portC + 18 // [EMC_32]: SEMC_DATA10 FLEXPWM3_PWMB01 LPUART7_RX CCM_PMIC_RDY CSI_DATA21 GPIO3_IO18 ENET2_TX_EN ~ ~ ~ + PC19 = portC + 19 // [EMC_33]: SEMC_DATA11 FLEXPWM3_PWMA02 USDHC1_RESET_B SAI3_RX_DATA CSI_DATA20 GPIO3_IO19 ENET2_TX_CLK ENET2_REF_CLK2 ~ ~ + PC20 = portC + 20 // [EMC_34]: SEMC_DATA12 FLEXPWM3_PWMB02 USDHC1_VSELECT SAI3_RX_SYNC CSI_DATA19 GPIO3_IO20 ENET2_RX_ER ~ ~ ~ + PC21 = portC + 21 // [EMC_35]: SEMC_DATA13 XBAR1_INOUT18 GPT1_COMPARE1 SAI3_RX_BCLK CSI_DATA18 GPIO3_IO21 USDHC1_CD_B ENET2_RDATA00 ~ ~ + PC22 = portC + 22 // [EMC_36]: SEMC_DATA14 XBAR1_IN22 GPT1_COMPARE2 SAI3_TX_DATA CSI_DATA17 GPIO3_IO22 USDHC1_WP ENET2_RDATA01 FLEXCAN3_TX ~ + PC23 = portC + 23 // [EMC_37]: SEMC_DATA15 XBAR1_IN23 GPT1_COMPARE3 SAI3_MCLK CSI_DATA16 GPIO3_IO23 USDHC2_WP ENET2_RX_EN FLEXCAN3_RX ~ + PC24 = portC + 24 // [EMC_38]: SEMC_DM01 FLEXPWM1_PWMA03 LPUART8_TX SAI3_TX_BCLK CSI_FIELD GPIO3_IO24 USDHC2_VSELECT ENET2_MDC ~ ~ + PC25 = portC + 25 // [EMC_39]: SEMC_DQS FLEXPWM1_PWMB03 LPUART8_RX SAI3_TX_SYNC WDOG1_WDOG_B GPIO3_IO25 USDHC2_CD_B ENET2_MDIO SEMC_DQS4 ~ + PC26 = portC + 26 // [EMC_40]: SEMC_RDY GPT2_CAPTURE2 LPSPI1_PCS2 USB_OTG2_OC ENET_MDC GPIO3_IO26 USDHC2_RESET_B SEMC_CLK5 ~ ~ + PC27 = portC + 27 // [EMC_41]: SEMC_CSX00 GPT2_CAPTURE1 LPSPI1_PCS3 USB_OTG2_PWR ENET_MDIO GPIO3_IO27 USDHC1_VSELECT ~ ~ ~ + _ = portC + 28 // + _ = portC + 29 // + _ = portC + 30 // + _ = portC + 31 // + + PD0 = portD + 0 // [EMC_00]: SEMC_DATA00 FLEXPWM4_PWMA00 LPSPI2_SCK XBAR1_XBAR_IN02 FLEXIO1_FLEXIO00 GPIO4_IO00 ~ ~ ~ ~ + PD1 = portD + 1 // [EMC_01]: SEMC_DATA01 FLEXPWM4_PWMB00 LPSPI2_PCS0 XBAR1_IN03 FLEXIO1_FLEXIO01 GPIO4_IO01 ~ ~ ~ ~ + PD2 = portD + 2 // [EMC_02]: SEMC_DATA02 FLEXPWM4_PWMA01 LPSPI2_SDO XBAR1_INOUT04 FLEXIO1_FLEXIO02 GPIO4_IO02 ~ ~ ~ ~ + PD3 = portD + 3 // [EMC_03]: SEMC_DATA03 FLEXPWM4_PWMB01 LPSPI2_SDI XBAR1_INOUT05 FLEXIO1_FLEXIO03 GPIO4_IO03 ~ ~ ~ ~ + PD4 = portD + 4 // [EMC_04]: SEMC_DATA04 FLEXPWM4_PWMA02 SAI2_TX_DATA XBAR1_INOUT06 FLEXIO1_FLEXIO04 GPIO4_IO04 ~ ~ ~ ~ + PD5 = portD + 5 // [EMC_05]: SEMC_DATA05 FLEXPWM4_PWMB02 SAI2_TX_SYNC XBAR1_INOUT07 FLEXIO1_FLEXIO05 GPIO4_IO05 ~ ~ ~ ~ + PD6 = portD + 6 // [EMC_06]: SEMC_DATA06 FLEXPWM2_PWMA00 SAI2_TX_BCLK XBAR1_INOUT08 FLEXIO1_FLEXIO06 GPIO4_IO06 ~ ~ ~ ~ + PD7 = portD + 7 // [EMC_07]: SEMC_DATA07 FLEXPWM2_PWMB00 SAI2_MCLK XBAR1_INOUT09 FLEXIO1_FLEXIO07 GPIO4_IO07 ~ ~ ~ ~ + PD8 = portD + 8 // [EMC_08]: SEMC_DM00 FLEXPWM2_PWMA01 SAI2_RX_DATA XBAR1_INOUT17 FLEXIO1_FLEXIO08 GPIO4_IO08 ~ ~ ~ ~ + PD9 = portD + 9 // [EMC_09]: SEMC_ADDR00 FLEXPWM2_PWMB01 SAI2_RX_SYNC FLEXCAN2_TX FLEXIO1_FLEXIO09 GPIO4_IO09 FLEXSPI2_B_SS1_B ~ ~ ~ + PD10 = portD + 10 // [EMC_10]: SEMC_ADDR01 FLEXPWM2_PWMA02 SAI2_RX_BCLK FLEXCAN2_RX FLEXIO1_FLEXIO10 GPIO4_IO10 FLEXSPI2_B_SS0_B ~ ~ ~ + PD11 = portD + 11 // [EMC_11]: SEMC_ADDR02 FLEXPWM2_PWMB02 LPI2C4_SDA USDHC2_RESET_B FLEXIO1_FLEXIO11 GPIO4_IO11 FLEXSPI2_B_DQS ~ ~ ~ + PD12 = portD + 12 // [EMC_12]: SEMC_ADDR03 XBAR1_IN24 LPI2C4_SCL USDHC1_WP FLEXPWM1_PWMA03 GPIO4_IO12 FLEXSPI2_B_SCLK ~ ~ ~ + PD13 = portD + 13 // [EMC_13]: SEMC_ADDR04 XBAR1_IN25 LPUART3_TX MQS_RIGHT FLEXPWM1_PWMB03 GPIO4_IO13 FLEXSPI2_B_DATA00 ~ ~ ~ + PD14 = portD + 14 // [EMC_14]: SEMC_ADDR05 XBAR1_INOUT19 LPUART3_RX MQS_LEFT LPSPI2_PCS1 GPIO4_IO14 FLEXSPI2_B_DATA01 ~ ~ ~ + PD15 = portD + 15 // [EMC_15]: SEMC_ADDR06 XBAR1_IN20 LPUART3_CTS_B SPDIF_OUT QTIMER3_TIMER0 GPIO4_IO15 FLEXSPI2_B_DATA02 ~ ~ ~ + PD16 = portD + 16 // [EMC_16]: SEMC_ADDR07 XBAR1_IN21 LPUART3_RTS_B SPDIF_IN QTIMER3_TIMER1 GPIO4_IO16 FLEXSPI2_B_DATA03 ~ ~ ~ + PD17 = portD + 17 // [EMC_17]: SEMC_ADDR08 FLEXPWM4_PWMA03 LPUART4_CTS_B FLEXCAN1_TX QTIMER3_TIMER2 GPIO4_IO17 ~ ~ ~ ~ + PD18 = portD + 18 // [EMC_18]: SEMC_ADDR09 FLEXPWM4_PWMB03 LPUART4_RTS_B FLEXCAN1_RX QTIMER3_TIMER3 GPIO4_IO18 SNVS_VIO_5_CTL ~ ~ ~ + PD19 = portD + 19 // [EMC_19]: SEMC_ADDR11 FLEXPWM2_PWMA03 LPUART4_TX ENET_RDATA01 QTIMER2_TIMER0 GPIO4_IO19 SNVS_VIO_5 ~ ~ ~ + PD20 = portD + 20 // [EMC_20]: SEMC_ADDR12 FLEXPWM2_PWMB03 LPUART4_RX ENET_RDATA00 QTIMER2_TIMER1 GPIO4_IO20 ~ ~ ~ ~ + PD21 = portD + 21 // [EMC_21]: SEMC_BA0 FLEXPWM3_PWMA03 LPI2C3_SDA ENET_TDATA01 QTIMER2_TIMER2 GPIO4_IO21 ~ ~ ~ ~ + PD22 = portD + 22 // [EMC_22]: SEMC_BA1 FLEXPWM3_PWMB03 LPI2C3_SCL ENET_TDATA00 QTIMER2_TIMER3 GPIO4_IO22 FLEXSPI2_A_SS1_B ~ ~ ~ + PD23 = portD + 23 // [EMC_23]: SEMC_ADDR10 FLEXPWM1_PWMA00 LPUART5_TX ENET_RX_EN GPT1_CAPTURE2 GPIO4_IO23 FLEXSPI2_A_DQS ~ ~ ~ + PD24 = portD + 24 // [EMC_24]: SEMC_CAS FLEXPWM1_PWMB00 LPUART5_RX ENET_TX_EN GPT1_CAPTURE1 GPIO4_IO24 FLEXSPI2_A_SS0_B ~ ~ ~ + PD25 = portD + 25 // [EMC_25]: SEMC_RAS FLEXPWM1_PWMA01 LPUART6_TX ENET_TX_CLK ENET_REF_CLK GPIO4_IO25 FLEXSPI2_A_SCLK ~ ~ ~ + PD26 = portD + 26 // [EMC_26]: SEMC_CLK FLEXPWM1_PWMB01 LPUART6_RX ENET_RX_ER FLEXIO1_FLEXIO12 GPIO4_IO26 FLEXSPI2_A_DATA00 ~ ~ ~ + PD27 = portD + 27 // [EMC_27]: SEMC_CKE FLEXPWM1_PWMA02 LPUART5_RTS_B LPSPI1_SCK FLEXIO1_FLEXIO13 GPIO4_IO27 FLEXSPI2_A_DATA01 ~ ~ ~ + PD28 = portD + 28 // [EMC_28]: SEMC_WE FLEXPWM1_PWMB02 LPUART5_CTS_B LPSPI1_SDO FLEXIO1_FLEXIO14 GPIO4_IO28 FLEXSPI2_A_DATA02 ~ ~ ~ + PD29 = portD + 29 // [EMC_29]: SEMC_CS0 FLEXPWM3_PWMA00 LPUART6_RTS_B LPSPI1_SDI FLEXIO1_FLEXIO15 GPIO4_IO29 FLEXSPI2_A_DATA03 ~ ~ ~ + PD30 = portD + 30 // [EMC_30]: SEMC_DATA08 FLEXPWM3_PWMB00 LPUART6_CTS_B LPSPI1_PCS0 CSI_DATA23 GPIO4_IO30 ENET2_TDATA00 ~ ~ ~ + PD31 = portD + 31 // [EMC_31]: SEMC_DATA09 FLEXPWM3_PWMA01 LPUART7_TX LPSPI1_PCS1 CSI_DATA22 GPIO4_IO31 ENET2_TDATA01 ~ ~ ~ +) + +func (p Pin) getPos() uint8 { return uint8(p % 32) } +func (p Pin) getMask() uint32 { return uint32(1) << p.getPos() } +func (p Pin) getPort() Pin { return Pin(p/32) * 32 } + +// Configure sets the GPIO pad and pin properties, and selects the appropriate +// alternate function, for a given Pin and PinConfig. +func (p Pin) Configure(config PinConfig) { + var ( + sre = uint32(0x01 << 0) + dse = func(n uint32) uint32 { return (n & 0x07) << 3 } + spd = func(n uint32) uint32 { return (n & 0x03) << 6 } + ode = uint32(0x01 << 11) + pke = uint32(0x01 << 12) + pue = uint32(0x01 << 13) + pup = func(n uint32) uint32 { return (n & 0x03) << 14 } + hys = uint32(0x01 << 16) + ) + + _, gpio := p.getGPIO() // use fast GPIO for all pins + pad, mux := p.getPad() + + // first configure the pad characteristics + switch config.Mode { + case PinInput: + gpio.GDIR.ClearBits(p.getMask()) + pad.Set(dse(7)) + + case PinInputPullup: + gpio.GDIR.ClearBits(p.getMask()) + pad.Set(dse(7) | pke | pue | pup(3) | hys) + + case PinInputPulldown: + gpio.GDIR.ClearBits(p.getMask()) + pad.Set(dse(7) | pke | pue | hys) + + case PinOutput: + gpio.GDIR.SetBits(p.getMask()) + pad.Set(dse(7)) + + case PinOutputOpenDrain: + gpio.GDIR.SetBits(p.getMask()) + pad.Set(dse(7) | ode) + + case PinDisable: + gpio.GDIR.ClearBits(p.getMask()) + pad.Set(dse(7) | hys) + + case PinInputAnalog: + gpio.GDIR.ClearBits(p.getMask()) + pad.Set(dse(7)) + + case PinModeUARTTX: + pad.Set(sre | dse(3) | spd(3)) + + case PinModeUARTRX: + pad.Set(dse(7) | pke | pue | pup(3) | hys) + + case PinModeSPISDI: + pad.Set(dse(7) | spd(2)) + + case PinModeSPISDO: + pad.Set(dse(7) | spd(2)) + + case PinModeSPICLK: + pad.Set(dse(7) | spd(2)) + + case PinModeSPICS: + pad.Set(dse(7)) + + case PinModeI2CSDA, PinModeI2CSCL: + pad.Set(ode | sre | dse(4) | spd(1) | pke | pue | pup(3)) + } + + // then configure the alternate function mux + mux.Set(p.getMuxMode(config)) +} + +// Get returns the current value of a GPIO pin. +func (p Pin) Get() bool { + _, gpio := p.getGPIO() // use fast GPIO for all pins + return gpio.PSR.HasBits(p.getMask()) +} + +// Set changes the value of the GPIO pin. The pin must be configured as output. +func (p Pin) Set(value bool) { + _, gpio := p.getGPIO() // use fast GPIO for all pins + if value { + gpio.DR_SET.Set(p.getMask()) + } else { + gpio.DR_CLEAR.Set(p.getMask()) + } +} + +// Toggle switches an output pin from low to high or from high to low. +func (p Pin) Toggle() { + _, gpio := p.getGPIO() // use fast GPIO for all pins + gpio.DR_TOGGLE.Set(p.getMask()) +} + +// dispatchInterrupt invokes the user-provided callback functions for external +// interrupts generated on the high-speed GPIO pins. +// +// Unfortunately, all four high-speed GPIO ports (A-D) are connected to just a +// single interrupt control line. Therefore, the interrupt status register (ISR) +// must be checked in all four GPIO ports on every interrupt. +func (jt *pinJumpTable) dispatchInterrupt(interrupt.Interrupt) { + handle := func(gpio *nxp.GPIO_Type, port Pin) { + if status := gpio.ISR.Get() & gpio.IMR.Get(); status != 0 { + gpio.ISR.Set(status) // clear interrupt + for status != 0 { + off := Pin(bits.TrailingZeros32(status)) // ctz + pin := Pin(port + off) + jt.lut[pin](pin) + status &^= 1 << off + } + } + } + if jt.numDefined > 0 { + handle(nxp.GPIO6, portA) + handle(nxp.GPIO7, portB) + handle(nxp.GPIO8, portC) + handle(nxp.GPIO9, portD) + } +} + +// set associates a function with a given Pin in the receiver lookup table. If +// the function is nil, the given Pin's associated function is removed. +func (jt *pinJumpTable) set(pin Pin, fn func(Pin)) { + if int(pin) < len(jt.lut) { + if nil != fn { + if nil == jt.lut[pin] { + jt.numDefined++ + } + jt.lut[pin] = fn + } else { + if nil != jt.lut[pin] { + jt.numDefined-- + } + jt.lut[pin] = nil + } + } +} + +// SetInterrupt sets an interrupt to be executed when a particular pin changes +// state. The pin should already be configured as an input, including a pull up +// or down if no external pull is provided. +// +// This call will replace a previously set callback on this pin. You can pass a +// nil func to unset the pin change interrupt. If you do so, the change +// parameter is ignored and can be set to any value (such as 0). +func (p Pin) SetInterrupt(change PinChange, callback func(Pin)) error { + _, gpio := p.getGPIO() // use fast GPIO for all pins + mask := p.getMask() + if nil != callback { + switch change { + case PinRising, PinFalling: + gpio.EDGE_SEL.ClearBits(mask) + var reg *volatile.Register32 + var pos uint8 + if pos = p.getPos(); pos < 16 { + reg = &gpio.ICR1 // ICR1 = pins 0-15 + } else { + reg = &gpio.ICR2 // ICR2 = pins 16-31 + pos -= 16 + } + reg.ReplaceBits(uint32(change), 0x3, pos*2) + case PinToggle: + gpio.EDGE_SEL.SetBits(mask) + } + pinISR.set(p, callback) // associate the callback with the pin + gpio.ISR.Set(mask) // clear any pending interrupt (W1C) + gpio.IMR.SetBits(mask) // enable external interrupt + } else { + pinISR.set(p, nil) // remove any associated callback from the pin + gpio.ISR.Set(mask) // clear any pending interrupt (W1C) + gpio.IMR.ClearBits(mask) // disable external interrupt + } + // enable or disable the interrupt based on number of defined callbacks + if pinISR.numDefined > 0 { + if nil == pinInterrupt { + // create the Interrupt if it is not yet defined + irq := interrupt.New(nxp.IRQ_GPIO6_7_8_9, pinISR.dispatchInterrupt) + pinInterrupt = &irq + pinInterrupt.Enable() + } + } else { + if nil != pinInterrupt { + // disable the interrupt if it is defined + pinInterrupt.Disable() + } + } + return nil +} + +// getGPIO returns both the normal (IPG_CLK_ROOT) and high-speed (AHB_CLK_ROOT) +// GPIO peripherals to which a given Pin is connected. +// +// Note that, currently, the device is configured to use high-speed GPIO for all +// pins (GPIO6-9), so the first return value should not be used (GPIO1-4). +// See the remarks and documentation reference in the comments preceding the +// const Pin definitions above. +func (p Pin) getGPIO() (norm *nxp.GPIO_Type, fast *nxp.GPIO_Type) { + switch p.getPort() { + case portA: + return nxp.GPIO1, nxp.GPIO6 + case portB: + return nxp.GPIO2, nxp.GPIO7 + case portC: + return nxp.GPIO3, nxp.GPIO8 + case portD: + return nxp.GPIO4, nxp.GPIO9 + default: + panic("machine: unknown port") + } +} + +// getPad returns both the pad and mux configuration registers for a given Pin. +func (p Pin) getPad() (pad *volatile.Register32, mux *volatile.Register32) { + switch p.getPort() { + case portA: + switch p.getPos() { + case 0: + return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_AD_B0_00, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_AD_B0_00 + case 1: + return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_AD_B0_01, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_AD_B0_01 + case 2: + return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_AD_B0_02, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_AD_B0_02 + case 3: + return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_AD_B0_03, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_AD_B0_03 + case 4: + return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_AD_B0_04, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_AD_B0_04 + case 5: + return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_AD_B0_05, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_AD_B0_05 + case 6: + return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_AD_B0_06, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_AD_B0_06 + case 7: + return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_AD_B0_07, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_AD_B0_07 + case 8: + return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_AD_B0_08, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_AD_B0_08 + case 9: + return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_AD_B0_09, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_AD_B0_09 + case 10: + return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_AD_B0_10, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_AD_B0_10 + case 11: + return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_AD_B0_11, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_AD_B0_11 + case 12: + return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_AD_B0_12, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_AD_B0_12 + case 13: + return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_AD_B0_13, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_AD_B0_13 + case 14: + return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_AD_B0_14, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_AD_B0_14 + case 15: + return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_AD_B0_15, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_AD_B0_15 + case 16: + return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_AD_B1_00, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_AD_B1_00 + case 17: + return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_AD_B1_01, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_AD_B1_01 + case 18: + return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_AD_B1_02, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_AD_B1_02 + case 19: + return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_AD_B1_03, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_AD_B1_03 + case 20: + return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_AD_B1_04, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_AD_B1_04 + case 21: + return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_AD_B1_05, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_AD_B1_05 + case 22: + return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_AD_B1_06, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_AD_B1_06 + case 23: + return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_AD_B1_07, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_AD_B1_07 + case 24: + return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_AD_B1_08, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_AD_B1_08 + case 25: + return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_AD_B1_09, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_AD_B1_09 + case 26: + return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_AD_B1_10, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_AD_B1_10 + case 27: + return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_AD_B1_11, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_AD_B1_11 + case 28: + return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_AD_B1_12, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_AD_B1_12 + case 29: + return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_AD_B1_13, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_AD_B1_13 + case 30: + return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_AD_B1_14, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_AD_B1_14 + case 31: + return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_AD_B1_15, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_AD_B1_15 + } + case portB: + switch p.getPos() { + case 0: + return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_B0_00, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_B0_00 + case 1: + return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_B0_01, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_B0_01 + case 2: + return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_B0_02, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_B0_02 + case 3: + return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_B0_03, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_B0_03 + case 4: + return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_B0_04, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_B0_04 + case 5: + return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_B0_05, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_B0_05 + case 6: + return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_B0_06, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_B0_06 + case 7: + return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_B0_07, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_B0_07 + case 8: + return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_B0_08, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_B0_08 + case 9: + return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_B0_09, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_B0_09 + case 10: + return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_B0_10, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_B0_10 + case 11: + return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_B0_11, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_B0_11 + case 12: + return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_B0_12, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_B0_12 + case 13: + return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_B0_13, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_B0_13 + case 14: + return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_B0_14, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_B0_14 + case 15: + return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_B0_15, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_B0_15 + case 16: + return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_B1_00, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_B1_00 + case 17: + return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_B1_01, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_B1_01 + case 18: + return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_B1_02, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_B1_02 + case 19: + return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_B1_03, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_B1_03 + case 20: + return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_B1_04, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_B1_04 + case 21: + return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_B1_05, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_B1_05 + case 22: + return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_B1_06, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_B1_06 + case 23: + return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_B1_07, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_B1_07 + case 24: + return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_B1_08, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_B1_08 + case 25: + return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_B1_09, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_B1_09 + case 26: + return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_B1_10, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_B1_10 + case 27: + return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_B1_11, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_B1_11 + case 28: + return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_B1_12, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_B1_12 + case 29: + return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_B1_13, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_B1_13 + case 30: + return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_B1_14, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_B1_14 + case 31: + return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_B1_15, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_B1_15 + } + case portC: + switch p.getPos() { + case 0: + return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_SD_B1_00, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_SD_B1_00 + case 1: + return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_SD_B1_01, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_SD_B1_01 + case 2: + return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_SD_B1_02, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_SD_B1_02 + case 3: + return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_SD_B1_03, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_SD_B1_03 + case 4: + return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_SD_B1_04, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_SD_B1_04 + case 5: + return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_SD_B1_05, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_SD_B1_05 + case 6: + return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_SD_B1_06, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_SD_B1_06 + case 7: + return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_SD_B1_07, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_SD_B1_07 + case 8: + return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_SD_B1_08, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_SD_B1_08 + case 9: + return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_SD_B1_09, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_SD_B1_09 + case 10: + return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_SD_B1_10, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_SD_B1_10 + case 11: + return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_SD_B1_11, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_SD_B1_11 + case 12: + return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_SD_B0_00, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_SD_B0_00 + case 13: + return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_SD_B0_01, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_SD_B0_01 + case 14: + return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_SD_B0_02, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_SD_B0_02 + case 15: + return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_SD_B0_03, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_SD_B0_03 + case 16: + return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_SD_B0_04, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_SD_B0_04 + case 17: + return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_SD_B0_05, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_SD_B0_05 + case 18: + return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_EMC_32, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_EMC_32 + case 19: + return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_EMC_33, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_EMC_33 + case 20: + return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_EMC_34, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_EMC_34 + case 21: + return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_EMC_35, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_EMC_35 + case 22: + return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_EMC_36, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_EMC_36 + case 23: + return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_EMC_37, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_EMC_37 + case 24: + return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_EMC_38, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_EMC_38 + case 25: + return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_EMC_39, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_EMC_39 + case 26: + return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_EMC_40, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_EMC_40 + case 27: + return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_EMC_41, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_EMC_41 + case 28, 29, 30, 31: + } + case portD: + switch p.getPos() { + case 0: + return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_EMC_00, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_EMC_00 + case 1: + return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_EMC_01, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_EMC_01 + case 2: + return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_EMC_02, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_EMC_02 + case 3: + return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_EMC_03, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_EMC_03 + case 4: + return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_EMC_04, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_EMC_04 + case 5: + return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_EMC_05, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_EMC_05 + case 6: + return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_EMC_06, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_EMC_06 + case 7: + return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_EMC_07, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_EMC_07 + case 8: + return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_EMC_08, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_EMC_08 + case 9: + return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_EMC_09, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_EMC_09 + case 10: + return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_EMC_10, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_EMC_10 + case 11: + return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_EMC_11, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_EMC_11 + case 12: + return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_EMC_12, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_EMC_12 + case 13: + return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_EMC_13, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_EMC_13 + case 14: + return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_EMC_14, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_EMC_14 + case 15: + return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_EMC_15, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_EMC_15 + case 16: + return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_EMC_16, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_EMC_16 + case 17: + return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_EMC_17, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_EMC_17 + case 18: + return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_EMC_18, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_EMC_18 + case 19: + return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_EMC_19, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_EMC_19 + case 20: + return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_EMC_20, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_EMC_20 + case 21: + return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_EMC_21, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_EMC_21 + case 22: + return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_EMC_22, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_EMC_22 + case 23: + return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_EMC_23, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_EMC_23 + case 24: + return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_EMC_24, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_EMC_24 + case 25: + return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_EMC_25, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_EMC_25 + case 26: + return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_EMC_26, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_EMC_26 + case 27: + return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_EMC_27, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_EMC_27 + case 28: + return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_EMC_28, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_EMC_28 + case 29: + return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_EMC_29, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_EMC_29 + case 30: + return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_EMC_30, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_EMC_30 + case 31: + return &nxp.IOMUXC.SW_PAD_CTL_PAD_GPIO_EMC_31, &nxp.IOMUXC.SW_MUX_CTL_PAD_GPIO_EMC_31 + } + } + panic("machine: invalid pin") +} + +// muxSelect is yet another level of indirection required to connect pins in an +// alternate function state to a desired peripheral (since more than one pin can +// provide a given alternate function). +// +// Once a pin is configured with a given alternate function mode, the IOMUXC +// device must then be configured to select which alternate function pin to +// route to the desired peripheral. +// +// The reference manual refers to this functionality as a "Daisy Chain". The +// associated docs are found in the i.MX RT1060 Processor Reference Manual: +// "Chapter 11.3.3 Daisy chain - multi pads driving same module input pin" +type muxSelect struct { + mux uint8 // AF mux selection (NOT a Pin type) + sel *volatile.Register32 // AF selection register +} + +// connect configures the IOMUXC controller to route a given pin with alternate +// function to a desired peripheral (see godoc comments on type muxSelect). +func (s muxSelect) connect() { + s.sel.Set(uint32(s.mux)) +} + +// getMuxMode acts as a callback from the `(Pin).Configure(PinMode)` routine to +// determine the alternate function setting for a given Pin and PinConfig. +// This value is used in the IOMUXC device's SW_MUX_CTL_PAD_GPIO_* registers. +func (p Pin) getMuxMode(config PinConfig) uint32 { + const forcePath = true // TODO: should be input parameter? + switch config.Mode { + + // GPIO + case PinInput, PinInputPullup, PinInputPulldown, + PinOutput, PinOutputOpenDrain, PinDisable: + mode := uint32(0x5) // GPIO is always alternate function 5 + if forcePath { + mode |= 0x10 // SION bit + } + return mode + + // ADC + case PinInputAnalog: + mode := uint32(0x5) // use alternate function 5 (GPIO) + if forcePath { + mode |= 0x10 // SION bit + } + return mode + + // UART RX/TX + case PinModeUARTRX, PinModeUARTTX: + mode := uint32(0x2) // UART is usually alternate function 2 on Teensy 4.x + // Teensy 4.1 has a UART (LPUART5) with alternate function 1 + if p == PB28 || p == PB29 { + mode = 0x1 + } + return mode + + // SPI SDI + case PinModeSPISDI: + var mode uint32 + switch p { + case PC15: // LPSPI1 SDI on PC15 alternate function 4 + mode = uint32(0x4) + case PA2: // LPSPI3 SDI on PA2 alternate function 7 + mode = uint32(0x7) + case PB1: // LPSPI4 SDI on PB1 alternate function 3 + mode = uint32(0x3) + default: + panic("machine: invalid SPI SDI pin") + } + if forcePath { + mode |= 0x10 // SION bit + } + return mode + + // SPI SDO + case PinModeSPISDO: + var mode uint32 + switch p { + case PC14: // LPSPI1 SDO on PC14 alternate function 4 + mode = uint32(0x4) + case PA30: // LPSPI3 SDO on PA30 alternate function 2 + mode = uint32(0x2) + case PB2: // LPSPI4 SDO on PB2 alternate function 3 + mode = uint32(0x3) + default: + panic("machine: invalid SPI SDO pin") + } + if forcePath { + mode |= 0x10 // SION bit + } + return mode + + // SPI SCK + case PinModeSPICLK: + var mode uint32 + switch p { + case PC12: // LPSPI1 SCK on PC12 alternate function 4 + mode = uint32(0x4) + case PA31: // LPSPI3 SCK on PA31 alternate function 2 + mode = uint32(0x2) + case PB3: // LPSPI4 SCK on PB3 alternate function 3 + mode = uint32(0x3) + default: + panic("machine: invalid SPI CLK pin") + } + if forcePath { + mode |= 0x10 // SION bit + } + return mode + + // SPI CS + case PinModeSPICS: + var mode uint32 + switch p { + case PC13: // LPSPI1 CS on PC13 alternate function 4 + mode = uint32(0x4) + case PA3: // LPSPI3 CS on PA3 alternate function 7 + mode = uint32(0x7) + case PB0: // LPSPI4 CS on PB0 alternate function 3 + mode = uint32(0x3) + default: // use alternate function 5 (GPIO) if non-CS pin selected + mode = uint32(0x5) + } + if forcePath { + mode |= 0x10 // SION bit + } + return mode + + // I2C SDA + case PinModeI2CSDA: + var mode uint32 + switch p { + case PA13: // LPI2C4 SDA on PA13 alternate function 0 + mode = uint32(0) + case PA17: // LPI2C1 SDA on PA17 alternate function 3 + mode = uint32(3) + case PA22: // LPI2C3 SDA on PA22 alternate function 1 + mode = uint32(1) + default: + panic("machine: invalid I2C SDA pin") + } + if forcePath { + mode |= 0x10 // SION bit + } + return mode + + // I2C SCL + case PinModeI2CSCL: + var mode uint32 + switch p { + case PA12: // LPI2C4 SCL on PA12 alternate function 0 + mode = uint32(0) + case PA16: // LPI2C1 SCL on PA16 alternate function 3 + mode = uint32(3) + case PA23: // LPI2C3 SCL on PA23 alternate function 1 + mode = uint32(1) + default: + panic("machine: invalid I2C SCL pin") + } + if forcePath { + mode |= 0x10 // SION bit + } + return mode + + default: + panic("machine: invalid pin mode") + } +} + +// maximum ADC value for the currently configured resolution (used for scaling) +var adcMaximum uint32 + +// InitADC is not used by this machine. Use `(ADC).Configure()`. +func InitADC() {} + +// Configure initializes the receiver's ADC peripheral and pin for analog input. +func (a ADC) Configure(config ADCConfig) { + // if not specified, use defaults: 10-bit resolution, 4 samples/conversion + const ( + defaultResolution = uint32(10) + defaultSamples = uint32(4) + ) + + a.Pin.Configure(PinConfig{Mode: PinInputAnalog}) + + resolution, samples := config.Resolution, config.Samples + if 0 == resolution { + resolution = defaultResolution + } + if 0 == samples { + samples = defaultSamples + } + if resolution > 12 { + resolution = 12 // maximum resolution of 12 bits + } + adcMaximum = (uint32(1) << resolution) - 1 + + mode, average := a.mode(resolution, samples) + + nxp.ADC1.CFG.Set(mode | nxp.ADC_CFG_ADHSC) // configure ADC1 + nxp.ADC2.CFG.Set(mode | nxp.ADC_CFG_ADHSC) // configure ADC2 + + // begin calibration + nxp.ADC1.GC.Set(average | nxp.ADC_GC_CAL) + nxp.ADC2.GC.Set(average | nxp.ADC_GC_CAL) + + for a.isCalibrating() { + } // wait for calibration +} + +// Get performs a single ADC conversion, returning a 16-bit unsigned integer. +// The value returned will be scaled (uniformly distributed) if necessary so +// that it is always in the range [0..65535], regardless of the ADC's configured +// bit size (resolution). +func (a ADC) Get() uint16 { + if ch1, ch2, ok := a.Pin.getADCChannel(); ok { + for a.isCalibrating() { + } // wait for calibration + var val uint32 + if noADCChannel != ch1 { + nxp.ADC1.HC0.Set(uint32(ch1)) + for !nxp.ADC1.HS.HasBits(nxp.ADC_HS_COCO0) { + } + val = nxp.ADC1.R0.Get() & 0xFFFF + } else { + nxp.ADC2.HC0.Set(uint32(ch2)) + for !nxp.ADC2.HS.HasBits(nxp.ADC_HS_COCO0) { + } + val = nxp.ADC2.R0.Get() & 0xFFFF + } + // should never be zero, but just in case, use UINT16_MAX so that the scalar + // gets factored out of the conversion result, leaving the original reading + // to be returned unaltered/unscaled. + if adcMaximum == 0 { + adcMaximum = 0xFFFF + } + // scale up to a 16-bit value + return uint16((val * 0xFFFF) / adcMaximum) + } + return 0 +} + +// mode constructs bit masks for mode and average - used in ADC configuration +// registers - from a given ADC bit size (resolution) and sample count. +func (a ADC) mode(resolution, samples uint32) (mode, average uint32) { + + // use asynchronous clock (ADACK) (0 = IPG, 1 = IPG/2, or 3 = ADACK) + mode = (nxp.ADC_CFG_ADICLK_ADICLK_3 << nxp.ADC_CFG_ADICLK_Pos) & nxp.ADC_CFG_ADICLK_Msk + + // input clock DIV2 (0 = DIV1, 1 = DIV2, 2 = DIV4, or 3 = DIV8) + mode |= (nxp.ADC_CFG_ADIV_ADIV_1 << nxp.ADC_CFG_ADIV_Pos) & nxp.ADC_CFG_ADIV_Msk + + switch resolution { + case 8: // 8-bit conversion, sample period (ADC clocks) = 8 + mode |= (nxp.ADC_CFG_MODE_MODE_0 << nxp.ADC_CFG_MODE_Pos) & nxp.ADC_CFG_MODE_Msk + mode |= (nxp.ADC_CFG_ADSTS_ADSTS_3 << nxp.ADC_CFG_ADSTS_Pos) & nxp.ADC_CFG_ADSTS_Msk + + case 12: // 12-bit conversion, sample period (ADC clocks) = 24 + mode |= (nxp.ADC_CFG_MODE_MODE_2 << nxp.ADC_CFG_MODE_Pos) & nxp.ADC_CFG_MODE_Msk + mode |= (nxp.ADC_CFG_ADSTS_ADSTS_3 << nxp.ADC_CFG_ADSTS_Pos) & nxp.ADC_CFG_ADSTS_Msk + mode |= nxp.ADC_CFG_ADLSMP + + default: // 10-bit conversion, sample period (ADC clocks) = 20 + mode |= (nxp.ADC_CFG_MODE_MODE_1 << nxp.ADC_CFG_MODE_Pos) & nxp.ADC_CFG_MODE_Msk + mode |= (nxp.ADC_CFG_ADSTS_ADSTS_2 << nxp.ADC_CFG_ADSTS_Pos) & nxp.ADC_CFG_ADSTS_Msk + mode |= nxp.ADC_CFG_ADLSMP + } + + if samples >= 4 { + if samples >= 32 { + // 32 samples averaged + mode |= (nxp.ADC_CFG_AVGS_AVGS_3 << nxp.ADC_CFG_AVGS_Pos) & nxp.ADC_CFG_AVGS_Msk + } else if samples >= 16 { + // 16 samples averaged + mode |= (nxp.ADC_CFG_AVGS_AVGS_2 << nxp.ADC_CFG_AVGS_Pos) & nxp.ADC_CFG_AVGS_Msk + } else if samples >= 8 { + // 8 samples averaged + mode |= (nxp.ADC_CFG_AVGS_AVGS_1 << nxp.ADC_CFG_AVGS_Pos) & nxp.ADC_CFG_AVGS_Msk + } else { + // 4 samples averaged + mode |= (nxp.ADC_CFG_AVGS_AVGS_0 << nxp.ADC_CFG_AVGS_Pos) & nxp.ADC_CFG_AVGS_Msk + } + average = nxp.ADC_GC_AVGE + } + + return mode, average +} + +// isCalibrating returns true if and only if either one (or both) of ADC1 and +// ADC2 have their calibrating flags set. ADC reads must wait until these flags +// are clear before attempting a conversion. +func (a ADC) isCalibrating() bool { + return nxp.ADC1.GC.HasBits(nxp.ADC_GC_CAL) || nxp.ADC2.GC.HasBits(nxp.ADC_GC_CAL) +} + +const noADCChannel = uint8(0xFF) + +// getADCChannel returns the input channel for ADC1/ADC2 of the receiver Pin p. +func (p Pin) getADCChannel() (adc1, adc2 uint8, ok bool) { + switch p { + case PA12: // [AD_B0_12]: ADC1_IN1 ~ + return 1, noADCChannel, true + case PA13: // [AD_B0_13]: ADC1_IN2 ~ + return 2, noADCChannel, true + case PA14: // [AD_B0_14]: ADC1_IN3 ~ + return 3, noADCChannel, true + case PA15: // [AD_B0_15]: ADC1_IN4 ~ + return 4, noADCChannel, true + case PA16: // [AD_B1_00]: ADC1_IN5 ADC2_IN5 + return 5, 5, true + case PA17: // [AD_B1_01]: ADC1_IN6 ADC2_IN6 + return 6, 6, true + case PA18: // [AD_B1_02]: ADC1_IN7 ADC2_IN7 + return 7, 7, true + case PA19: // [AD_B1_03]: ADC1_IN8 ADC2_IN8 + return 8, 8, true + case PA20: // [AD_B1_04]: ADC1_IN9 ADC2_IN9 + return 9, 9, true + case PA21: // [AD_B1_05]: ADC1_IN10 ADC2_IN10 + return 10, 10, true + case PA22: // [AD_B1_06]: ADC1_IN11 ADC2_IN11 + return 11, 11, true + case PA23: // [AD_B1_07]: ADC1_IN12 ADC2_IN12 + return 12, 12, true + case PA24: // [AD_B1_08]: ADC1_IN13 ADC2_IN13 + return 13, 13, true + case PA25: // [AD_B1_09]: ADC1_IN14 ADC2_IN14 + return 14, 14, true + case PA26: // [AD_B1_10]: ADC1_IN15 ADC2_IN15 + return 15, 15, true + case PA27: // [AD_B1_11]: ADC1_IN0 ADC2_IN0 + return 16, 16, true + case PA28: // [AD_B1_12]: ~ ADC2_IN1 + return noADCChannel, 1, true + case PA29: // [AD_B1_13]: ~ ADC2_IN2 + return noADCChannel, 2, true + case PA30: // [AD_B1_14]: ~ ADC2_IN3 + return noADCChannel, 3, true + case PA31: // [AD_B1_15]: ~ ADC2_IN4 + return noADCChannel, 4, true + default: + return noADCChannel, noADCChannel, false + } +} diff --git a/emb/machine/machine_mimxrt1062_i2c.go b/emb/machine/machine_mimxrt1062_i2c.go new file mode 100644 index 00000000..f3c46361 --- /dev/null +++ b/emb/machine/machine_mimxrt1062_i2c.go @@ -0,0 +1,642 @@ +//go:build mimxrt1062 + +package machine + +// I2C peripheral abstraction layer for the MIMXRT1062 + +import ( + "device/nxp" +) + +// I2CConfig is used to store config info for I2C. +type I2CConfig struct { + Frequency uint32 + SDA Pin + SCL Pin +} + +type I2C struct { + Bus *nxp.LPI2C_Type + + // these pins are initialized by each global I2C variable declared in the + // board_teensy4x.go file according to the board manufacturer's default pin + // mapping. they can be overridden with the I2CConfig argument given to + // (*I2C) Configure(I2CConfig). + sda, scl Pin + + // these hold the input selector ("daisy chain") values that select which pins + // are connected to the LPI2C device, and should be defined where the I2C + // instance is declared (e.g., in the board definition). see the godoc + // comments on type muxSelect for more details. + muxSDA, muxSCL muxSelect +} + +type i2cDirection bool + +const ( + directionWrite i2cDirection = false + directionRead i2cDirection = true +) + +func (dir i2cDirection) shift(addr uint16) uint32 { + if addr <<= 1; dir == directionRead { + addr |= 1 + } + return uint32(addr) & 0xFF +} + +// I2C enumerated types +type ( + resultFlag uint32 + statusFlag uint32 + transferFlag uint32 + commandFlag uint32 + stateFlag uint32 +) + +const ( + // general purpose results + resultSuccess resultFlag = 0x0 // success + resultFail resultFlag = 0x1 // fail + resultReadOnly resultFlag = 0x2 // read only failure + resultOutOfRange resultFlag = 0x3 // out of range access + resultInvalidArgument resultFlag = 0x4 // invalid argument check + // I2C-specific results + resultBusy resultFlag = 0x0384 + 0x0 // the controller is already performing a transfer + resultIdle resultFlag = 0x0384 + 0x1 // the peripheral driver is idle + resultNak resultFlag = 0x0384 + 0x2 // the peripheral device sent a NAK in response to a byte + resultFifoError resultFlag = 0x0384 + 0x3 // FIFO under run or overrun + resultBitError resultFlag = 0x0384 + 0x4 // transferred bit was not seen on the bus + resultArbitrationLost resultFlag = 0x0384 + 0x5 // arbitration lost error + resultPinLowTimeout resultFlag = 0x0384 + 0x6 // SCL or SDA were held low longer than the timeout + resultNoTransferInProgress resultFlag = 0x0384 + 0x7 // attempt to abort a transfer when one is not in progress + resultDmaRequestFail resultFlag = 0x0384 + 0x8 // DMA request failed + resultTimeout resultFlag = 0x0384 + 0x9 // timeout polling status flags +) + +const ( + statusTxReady statusFlag = nxp.LPI2C_MSR_TDF // transmit data flag + statusRxReady statusFlag = nxp.LPI2C_MSR_RDF // receive data flag + statusEndOfPacket statusFlag = nxp.LPI2C_MSR_EPF // end Packet flag + statusStopDetect statusFlag = nxp.LPI2C_MSR_SDF // stop detect flag + statusNackDetect statusFlag = nxp.LPI2C_MSR_NDF // NACK detect flag + statusArbitrationLost statusFlag = nxp.LPI2C_MSR_ALF // arbitration lost flag + statusFifoErr statusFlag = nxp.LPI2C_MSR_FEF // FIFO error flag + statusPinLowTimeout statusFlag = nxp.LPI2C_MSR_PLTF // pin low timeout flag + statusI2CDataMatch statusFlag = nxp.LPI2C_MSR_DMF // data match flag + statusBusy statusFlag = nxp.LPI2C_MSR_MBF // busy flag + statusBusBusy statusFlag = nxp.LPI2C_MSR_BBF // bus busy flag + + // all flags which are cleared by the driver upon starting a transfer + statusClear statusFlag = statusEndOfPacket | statusStopDetect | statusNackDetect | + statusArbitrationLost | statusFifoErr | statusPinLowTimeout | statusI2CDataMatch + + // IRQ sources enabled by the non-blocking transactional API + statusIrq statusFlag = statusArbitrationLost | statusTxReady | statusRxReady | + statusStopDetect | statusNackDetect | statusPinLowTimeout | statusFifoErr + + // errors to check for + statusError statusFlag = statusNackDetect | statusArbitrationLost | statusFifoErr | + statusPinLowTimeout +) + +// LPI2C transfer modes +const ( + transferDefault transferFlag = 0x0 // transfer starts with a start signal, stops with a stop signal + transferNoStart transferFlag = 0x1 // don't send a start condition, address, and sub address + transferRepeatedStart transferFlag = 0x2 // send a repeated start condition + transferNoStop transferFlag = 0x4 // don't send a stop condition +) + +// LPI2C FIFO commands +const ( + commandTxData commandFlag = (0x0 << nxp.LPI2C_MTDR_CMD_Pos) & nxp.LPI2C_MTDR_CMD_Msk // transmit + commandRxData commandFlag = (0x1 << nxp.LPI2C_MTDR_CMD_Pos) & nxp.LPI2C_MTDR_CMD_Msk // receive + commandStop commandFlag = (0x2 << nxp.LPI2C_MTDR_CMD_Pos) & nxp.LPI2C_MTDR_CMD_Msk // generate STOP condition + commandStart commandFlag = (0x4 << nxp.LPI2C_MTDR_CMD_Pos) & nxp.LPI2C_MTDR_CMD_Msk // generate (REPEATED)START and transmit +) + +// LPI2C transactional states +const ( + stateIdle stateFlag = 0x0 + stateSendCommand stateFlag = 0x1 + stateIssueReadCommand stateFlag = 0x2 + stateTransferData stateFlag = 0x3 + stateStop stateFlag = 0x4 + stateWaitForCompletion stateFlag = 0x5 +) + +func (i2c *I2C) setPins(c I2CConfig) (sda, scl Pin) { + // if both given pins are defined, or either receiver pin is undefined. + if 0 != c.SDA && 0 != c.SCL || 0 == i2c.sda || 0 == i2c.scl { + // override the receiver's pins. + i2c.sda, i2c.scl = c.SDA, c.SCL + } + // return the selected pins. + return i2c.sda, i2c.scl +} + +// Configure is intended to setup an I2C interface for transmit/receive. +func (i2c *I2C) Configure(config I2CConfig) error { + // init pins + sda, scl := i2c.setPins(config) + + // configure the mux and pad control registers + sda.Configure(PinConfig{Mode: PinModeI2CSDA}) + scl.Configure(PinConfig{Mode: PinModeI2CSCL}) + + // configure the mux input selector + i2c.muxSDA.connect() + i2c.muxSCL.connect() + + freq := config.Frequency + if 0 == freq { + freq = 100 * KHz + } + + // reset clock and registers, and enable LPI2C module interface + i2c.reset(freq) + + return nil +} + +// SetBaudRate sets the communication speed for I2C. +func (i2c I2C) SetBaudRate(br uint32) error { + // TODO: implement + return errI2CNotImplemented +} + +func (i2c I2C) Tx(addr uint16, w, r []byte) error { + // perform transmit transfer + if nil != w { + // generate start condition on bus + if result := i2c.start(addr, directionWrite); resultSuccess != result { + return errI2CSignalStartTimeout + } + // ensure TX FIFO is empty + if result := i2c.waitForTxEmpty(); resultSuccess != result { + return errI2CBusReadyTimeout + } + // check if communication was successful + if status := statusFlag(i2c.Bus.MSR.Get()); 0 != (status & statusNackDetect) { + return errI2CAckExpected + } + // send transmit data + if result := i2c.controllerTransmit(w); resultSuccess != result { + return errI2CWriteTimeout + } + } + + // perform receive transfer + if nil != r { + // generate (repeated-)start condition on bus + if result := i2c.start(addr, directionRead); resultSuccess != result { + return errI2CSignalStartTimeout + } + // read received data + if result := i2c.controllerReceive(r); resultSuccess != result { + return errI2CReadTimeout + } + } + + // generate stop condition on bus + if result := i2c.stop(); resultSuccess != result { + return errI2CSignalStopTimeout + } + + return nil +} + +// WriteRegisterEx transmits first the register and then the data to the +// peripheral device. +// +// Many I2C-compatible devices are organized in terms of registers. This method +// is a shortcut to easily write to such registers. Also, it only works for +// devices with 7-bit addresses, which is the vast majority. +func (i2c I2C) WriteRegisterEx(address uint8, register uint8, data []byte) error { + option := transferOption{ + flags: transferDefault, // transfer options bit mask (0 = normal transfer) + peripheral: uint16(address), // 7-bit peripheral address + direction: directionWrite, // directionRead or directionWrite + subaddress: uint16(register), // peripheral sub-address (transferred MSB first) + subaddressSize: 1, // byte length of sub-address (maximum = 4 bytes) + } + if result := i2c.controllerTransferPoll(option, data); resultSuccess != result { + return errI2CWriteTimeout + } + return nil +} + +// ReadRegisterEx transmits the register, restarts the connection as a read +// operation, and reads the response. +// +// Many I2C-compatible devices are organized in terms of registers. This method +// is a shortcut to easily read such registers. Also, it only works for devices +// with 7-bit addresses, which is the vast majority. +func (i2c I2C) ReadRegisterEx(address uint8, register uint8, data []byte) error { + option := transferOption{ + flags: transferDefault, // transfer options bit mask (0 = normal transfer) + peripheral: uint16(address), // 7-bit peripheral address + direction: directionRead, // directionRead or directionWrite + subaddress: uint16(register), // peripheral sub-address (transferred MSB first) + subaddressSize: 1, // byte length of sub-address (maximum = 4 bytes) + } + if result := i2c.controllerTransferPoll(option, data); resultSuccess != result { + return errI2CWriteTimeout + } + return nil +} + +func (i2c *I2C) reset(freq uint32) { + // disable interface + i2c.Bus.MCR.ClearBits(nxp.LPI2C_MCR_MEN) + + // software reset all interface registers + i2c.Bus.MCR.Set(nxp.LPI2C_MCR_RST) + + // RST remains set until manually cleared! + i2c.Bus.MCR.ClearBits(nxp.LPI2C_MCR_RST) + + // disable host request + i2c.Bus.MCFGR0.Set(0) + + // enable ACK, use I2C 2-pin open drain mode + i2c.Bus.MCFGR1.Set(0) + + // set FIFO watermarks (RX=1, TX=1) + mfcr := (uint32(0x1) << nxp.LPI2C_MFCR_RXWATER_Pos) & nxp.LPI2C_MFCR_RXWATER_Msk + mfcr |= (uint32(0x1) << nxp.LPI2C_MFCR_TXWATER_Pos) & nxp.LPI2C_MFCR_TXWATER_Msk + i2c.Bus.MFCR.Set(mfcr) + + // configure clock using receiver frequency + i2c.setFrequency(freq) + + // clear reset, and enable the interface + i2c.Bus.MCR.Set(nxp.LPI2C_MCR_MEN) + + // wait for the I2C bus to idle + for i2c.Bus.MSR.Get()&nxp.LPI2C_MSR_BBF != 0 { + } +} + +func (i2c *I2C) setFrequency(freq uint32) { + var ( + bestPre uint32 = 0 + bestClkHi uint32 = 0 + bestError uint32 = 0xFFFFFFFF + ) + + // disable interface + wasEnabled := i2c.Bus.MCR.HasBits(nxp.LPI2C_MCR_MEN) + i2c.Bus.MCR.ClearBits(nxp.LPI2C_MCR_MEN) + + // baud rate = (24MHz/(2^pre))/(CLKLO+1 + CLKHI+1 + FLOOR((2+FILTSCL)/(2^pre))) + // assume: CLKLO=2*CLKHI, SETHOLD=CLKHI, DATAVD=CLKHI/2 + for pre := uint32(1); pre <= 128; pre *= 2 { + if bestError == 0 { + break + } + for clkHi := uint32(1); clkHi < 32; clkHi++ { + var absError, rate uint32 + if clkHi == 1 { + rate = (24 * MHz / pre) / (1 + 3 + 2 + 2/pre) + } else { + rate = (24 * MHz / pre) / (3*clkHi + 2 + 2/pre) + } + if freq > rate { + absError = freq - rate + } else { + absError = rate - freq + } + if absError < bestError { + bestPre = pre + bestClkHi = clkHi + bestError = absError + // if the error is 0, then we can stop searching because we won't find a + // better match + if absError == 0 { + break + } + } + } + } + + var ( + clklo = func(n uint32) uint32 { return (n << nxp.LPI2C_MCCR0_CLKLO_Pos) & nxp.LPI2C_MCCR0_CLKLO_Msk } + clkhi = func(n uint32) uint32 { return (n << nxp.LPI2C_MCCR0_CLKHI_Pos) & nxp.LPI2C_MCCR0_CLKHI_Msk } + datavd = func(n uint32) uint32 { return (n << nxp.LPI2C_MCCR0_DATAVD_Pos) & nxp.LPI2C_MCCR0_DATAVD_Msk } + sethold = func(n uint32) uint32 { return (n << nxp.LPI2C_MCCR0_SETHOLD_Pos) & nxp.LPI2C_MCCR0_SETHOLD_Msk } + ) + // StandardMode, FastMode, FastModePlus, and UltraFastMode + mccr0 := clkhi(bestClkHi) + if bestClkHi < 2 { + mccr0 |= (clklo(3) | sethold(2) | datavd(1)) + } else { + mccr0 |= clklo(2*bestClkHi) | sethold(bestClkHi) | datavd(bestClkHi/2) + } + i2c.Bus.MCCR0.Set(mccr0) + i2c.Bus.MCCR1.Set(i2c.Bus.MCCR0.Get()) + + for i := uint32(0); i < 8; i++ { + if bestPre == (1 << i) { + bestPre = i + break + } + } + preMask := (bestPre << nxp.LPI2C_MCFGR1_PRESCALE_Pos) & nxp.LPI2C_MCFGR1_PRESCALE_Msk + i2c.Bus.MCFGR1.Set((i2c.Bus.MCFGR1.Get() & ^uint32(nxp.LPI2C_MCFGR1_PRESCALE_Msk)) | preMask) + + var ( + filtsda = func(n uint32) uint32 { return (n << nxp.LPI2C_MCFGR2_FILTSDA_Pos) & nxp.LPI2C_MCFGR2_FILTSDA_Msk } + filtscl = func(n uint32) uint32 { return (n << nxp.LPI2C_MCFGR2_FILTSCL_Pos) & nxp.LPI2C_MCFGR2_FILTSCL_Msk } + busidle = func(n uint32) uint32 { return (n << nxp.LPI2C_MCFGR2_BUSIDLE_Pos) & nxp.LPI2C_MCFGR2_BUSIDLE_Msk } + pinlow = func(n uint32) uint32 { return (n << nxp.LPI2C_MCFGR3_PINLOW_Pos) & nxp.LPI2C_MCFGR3_PINLOW_Msk } + + mcfgr2, mcfgr3 uint32 + ) + const i2cClockStretchTimeout = 15000 // microseconds + if freq >= 5*MHz { + // I2C UltraFastMode 5 MHz + mcfgr2 = 0 // disable glitch filters and timeout for UltraFastMode + mcfgr3 = 0 // + } else if freq >= 1*MHz { + // I2C FastModePlus 1 MHz + mcfgr2 = filtsda(1) | filtscl(1) | busidle(2400) // 100us timeout + mcfgr3 = pinlow(i2cClockStretchTimeout*24/256 + 1) + } else if freq >= 400*KHz { + // I2C FastMode 400 kHz + mcfgr2 = filtsda(2) | filtscl(2) | busidle(3600) // 150us timeout + mcfgr3 = pinlow(i2cClockStretchTimeout*24/256 + 1) + } else { + // I2C StandardMode 100 kHz + mcfgr2 = filtsda(5) | filtscl(5) | busidle(3000) // 250us timeout + mcfgr3 = pinlow(i2cClockStretchTimeout*12/256 + 1) + } + i2c.Bus.MCFGR2.Set(mcfgr2) + i2c.Bus.MCFGR3.Set(mcfgr3) + + // restore controller mode if it was enabled when called + if wasEnabled { + i2c.Bus.MCR.SetBits(nxp.LPI2C_MCR_MEN) + } +} + +// checkStatus converts the status register to a resultFlag for return, and +// clears any errors if present. +func (i2c *I2C) checkStatus(status statusFlag) resultFlag { + result := resultSuccess + // check for error. these errors cause a stop to be sent automatically. + // we must clear the errors before a new transfer can start. + if status &= statusError; 0 != status { + // select the correct error code ordered by severity, bus issues first. + if 0 != (status & statusPinLowTimeout) { + result = resultPinLowTimeout + } else if 0 != (status & statusArbitrationLost) { + result = resultArbitrationLost + } else if 0 != (status & statusNackDetect) { + result = resultNak + } else if 0 != (status & statusFifoErr) { + result = resultFifoError + } + // clear the flags + i2c.Bus.MSR.Set(uint32(status)) + // reset fifos. these flags clear automatically. + i2c.Bus.MCR.SetBits(nxp.LPI2C_MCR_RRF | nxp.LPI2C_MCR_RTF) + } + return result +} + +func (i2c *I2C) getFIFOSize() (rx, tx uint32) { return 4, 4 } +func (i2c *I2C) getFIFOCount() (rx, tx uint32) { + mfsr := i2c.Bus.MFSR.Get() + return (mfsr & nxp.LPI2C_MFSR_RXCOUNT_Msk) >> nxp.LPI2C_MFSR_RXCOUNT_Pos, + (mfsr & nxp.LPI2C_MFSR_TXCOUNT_Msk) >> nxp.LPI2C_MFSR_TXCOUNT_Pos +} + +func (i2c *I2C) waitForTxReady() resultFlag { + result := resultSuccess + _, txSize := i2c.getFIFOSize() + for { + _, txCount := i2c.getFIFOCount() + status := statusFlag(i2c.Bus.MSR.Get()) + if result = i2c.checkStatus(status); resultSuccess != result { + break + } + if txSize-txCount > 0 { + break + } + } + return result +} + +func (i2c *I2C) waitForTxEmpty() resultFlag { + result := resultSuccess + for { + _, txCount := i2c.getFIFOCount() + status := statusFlag(i2c.Bus.MSR.Get()) + if result = i2c.checkStatus(status); resultSuccess != result { + break + } + if 0 == txCount { + break + } + } + return result +} + +// isBusBusy checks if the I2C bus is busy, returning true if it is busy and we +// are not the ones driving it, otherwise false. +func (i2c *I2C) isBusBusy() bool { + status := statusFlag(i2c.Bus.MSR.Get()) + return (0 != (status & statusBusBusy)) && (0 == (status & statusBusy)) +} + +// start sends a START signal and peripheral address on the I2C bus. +// +// This function is used to initiate a new controller mode transfer. First, the +// bus state is checked to ensure that another controller is not occupying the +// bus. Then a START signal is transmitted, followed by the 7-bit peripheral +// address. Note that this function does not actually wait until the START and +// address are successfully sent on the bus before returning. +func (i2c *I2C) start(address uint16, dir i2cDirection) resultFlag { + // return an error if the bus is already in use by another controller + if i2c.isBusBusy() { + return resultBusy + } + // clear all flags + i2c.Bus.MSR.Set(uint32(statusClear)) + // turn off auto-stop + i2c.Bus.MCFGR1.ClearBits(nxp.LPI2C_MCFGR1_AUTOSTOP) + // wait until there is room in the FIFO + if result := i2c.waitForTxReady(); resultSuccess != result { + return result + } + + // issue start command + i2c.Bus.MTDR.Set(uint32(commandStart) | dir.shift(address)) + return resultSuccess +} + +// stop sends a STOP signal on the I2C bus. +// +// This function does not return until the STOP signal is seen on the bus, or +// an error occurs. +func (i2c *I2C) stop() resultFlag { + const tryMax = 0 // keep waiting forever + // wait until there is room in the FIFO + result := i2c.waitForTxReady() + if resultSuccess != result { + return result + } + // send the STOP signal + i2c.Bus.MTDR.Set(uint32(commandStop)) + // wait for the stop detected flag to set, indicating the transfer has + // completed on the bus. also check for errors while waiting. + try := 0 + for resultSuccess == result && (0 == tryMax || try < tryMax) { + status := statusFlag(i2c.Bus.MSR.Get()) + result = i2c.checkStatus(status) + if (0 != (status & statusStopDetect)) && (0 != (status & statusTxReady)) { + i2c.Bus.MSR.Set(uint32(statusStopDetect)) + break + } + try++ + } + if 0 != tryMax && try >= tryMax { + return resultTimeout + } + return result +} + +// controllerReceive performs a polling receive transfer on the I2C bus. +func (i2c *I2C) controllerReceive(rxBuffer []byte) resultFlag { + const tryMax = 0 // keep trying forever + rxSize := len(rxBuffer) + if rxSize == 0 { + return resultSuccess + } + // wait until there is room in the FIFO + result := i2c.waitForTxReady() + if resultSuccess != result { + return result + } + sizeMask := (uint32(rxSize-1) << nxp.LPI2C_MTDR_DATA_Pos) & nxp.LPI2C_MTDR_DATA_Msk + i2c.Bus.MTDR.Set(uint32(commandRxData) | sizeMask) + + // receive data + for rxSize > 0 { + // read LPI2C receive FIFO register. the register includes a flag to + // indicate whether the FIFO is empty, so we can both get the data and check + // if we need to keep reading using a single register read. + var data uint32 + try := 0 + for 0 == tryMax || try < tryMax { + // check for errors on the bus + status := statusFlag(i2c.Bus.MSR.Get()) + result = i2c.checkStatus(status) + if resultSuccess != result { + return result + } + // read received data, break if FIFO was non-empty + data = i2c.Bus.MRDR.Get() + if 0 == (data & nxp.LPI2C_MRDR_RXEMPTY_Msk) { + break + } + try++ + } + // ensure we didn't timeout waiting for data + if 0 != tryMax && try >= tryMax { + return resultTimeout + } + // copy data to RX buffer + rxBuffer[len(rxBuffer)-rxSize] = byte(data & nxp.LPI2C_MRDR_DATA_Msk) + rxSize-- + } + return result +} + +// controllerTransmit performs a polling transmit transfer on the I2C bus. +func (i2c *I2C) controllerTransmit(txBuffer []byte) resultFlag { + txSize := len(txBuffer) + for txSize > 0 { + // wait until there is room in the FIFO + result := i2c.waitForTxReady() + if resultSuccess != result { + return result + } + // write byte into LPI2C data register + i2c.Bus.MTDR.Set(uint32(txBuffer[len(txBuffer)-txSize] & nxp.LPI2C_MTDR_DATA_Msk)) + txSize-- + } + return resultSuccess +} + +type transferOption struct { + flags transferFlag // transfer options bit mask (0 = normal transfer) + peripheral uint16 // 7-bit peripheral address + direction i2cDirection // directionRead or directionWrite + subaddress uint16 // peripheral sub-address (transferred MSB first) + subaddressSize uint16 // byte length of sub-address (maximum = 4 bytes) +} + +func (i2c *I2C) controllerTransferPoll(option transferOption, data []byte) resultFlag { + // return an error if the bus is already in use by another controller + if i2c.isBusBusy() { + return resultBusy + } + // clear all flags + i2c.Bus.MSR.Set(uint32(statusClear)) + // turn off auto-stop + i2c.Bus.MCFGR1.ClearBits(nxp.LPI2C_MCFGR1_AUTOSTOP) + + cmd := make([]uint16, 0, 7) + size := len(data) + + direction := option.direction + if option.subaddressSize > 0 { + direction = directionWrite + } + // peripheral address + if 0 == (option.flags & transferNoStart) { + addr := direction.shift(option.peripheral) + cmd = append(cmd, uint16(uint32(commandStart)|addr)) + } + // sub-address (MSB-first) + rem := option.subaddressSize + for rem > 0 { + rem-- + cmd = append(cmd, (option.subaddress>>(8*rem))&0xFF) + } + // need to send repeated start if switching directions to read + if (0 != size) && (directionRead == option.direction) { + if directionWrite == direction { + addr := directionRead.shift(option.peripheral) + cmd = append(cmd, uint16(uint32(commandStart)|addr)) + } + } + // send command buffer + result := resultSuccess + for _, c := range cmd { + // wait until there is room in the FIFO + if result = i2c.waitForTxReady(); resultSuccess != result { + return result + } + // write byte into LPI2C controller data register + i2c.Bus.MTDR.Set(uint32(c)) + } + // send data + if option.direction == directionWrite && size > 0 { + result = i2c.controllerTransmit(data) + } + // receive data + if option.direction == directionRead && size > 0 { + result = i2c.controllerReceive(data) + } + if resultSuccess != result { + return result + } + if 0 == (option.flags & transferNoStop) { + result = i2c.stop() + } + return result +} diff --git a/emb/machine/machine_mimxrt1062_spi.go b/emb/machine/machine_mimxrt1062_spi.go new file mode 100644 index 00000000..d982eeaa --- /dev/null +++ b/emb/machine/machine_mimxrt1062_spi.go @@ -0,0 +1,254 @@ +//go:build mimxrt1062 + +package machine + +// SPI peripheral abstraction layer for the MIMXRT1062 + +import ( + "device/nxp" + "errors" + "unsafe" +) + +// SPIConfig is used to store config info for SPI. +type SPIConfig struct { + Frequency uint32 + SDI Pin + SDO Pin + SCK Pin + CS Pin + LSBFirst bool + Mode uint8 +} + +func (c SPIConfig) getPins() (di, do, ck, cs Pin) { + if 0 == c.SDI && 0 == c.SDO && 0 == c.SCK && 0 == c.CS { + // default pins if none specified + return SPI_SDI_PIN, SPI_SDO_PIN, SPI_SCK_PIN, SPI_CS_PIN + } + return c.SDI, c.SDO, c.SCK, c.CS +} + +type SPI struct { + Bus *nxp.LPSPI_Type + + // these hold the input selector ("daisy chain") values that select which pins + // are connected to the LPSPI device, and should be defined where the SPI + // instance is declared (e.g., in the board definition). see the godoc + // comments on type muxSelect for more details. + muxSDI, muxSDO, muxSCK, muxCS muxSelect + + // these are copied from SPIConfig, during (*SPI).Configure(SPIConfig), and + // should be considered read-only for internal reference (i.e., modifying them + // will have no desirable effect). + sdi, sdo, sck, cs Pin + frequency uint32 + + // auxiliary state data used internally + configured bool +} + +const ( + statusTxDataRequest = nxp.LPSPI_SR_TDF // Transmit data flag + statusRxDataReady = nxp.LPSPI_SR_RDF // Receive data flag + statusWordComplete = nxp.LPSPI_SR_WCF // Word Complete flag + statusFrameComplete = nxp.LPSPI_SR_FCF // Frame Complete flag + statusTransferComplete = nxp.LPSPI_SR_TCF // Transfer Complete flag + statusTransmitError = nxp.LPSPI_SR_TEF // Transmit Error flag (FIFO underrun) + statusReceiveError = nxp.LPSPI_SR_REF // Receive Error flag (FIFO overrun) + statusDataMatch = nxp.LPSPI_SR_DMF // Data Match flag + statusModuleBusy = nxp.LPSPI_SR_MBF // Module Busy flag + statusAll = nxp.LPSPI_SR_TDF | nxp.LPSPI_SR_RDF | + nxp.LPSPI_SR_WCF | nxp.LPSPI_SR_FCF | nxp.LPSPI_SR_TCF | nxp.LPSPI_SR_TEF | + nxp.LPSPI_SR_REF | nxp.LPSPI_SR_DMF | nxp.LPSPI_SR_MBF +) + +var ( + errSPINotConfigured = errors.New("SPI interface is not yet configured") +) + +// Configure is intended to setup an SPI interface for transmit/receive. +func (spi *SPI) Configure(config SPIConfig) error { + + const defaultSpiFreq = 4000000 // 4 MHz + + // init pins + spi.sdi, spi.sdo, spi.sck, spi.cs = config.getPins() + + // configure the mux and pad control registers + spi.sdi.Configure(PinConfig{Mode: PinModeSPISDI}) + spi.sdo.Configure(PinConfig{Mode: PinModeSPISDO}) + spi.sck.Configure(PinConfig{Mode: PinModeSPICLK}) + spi.cs.Configure(PinConfig{Mode: PinModeSPICS}) + + // configure the mux input selector + spi.muxSDI.connect() + spi.muxSDO.connect() + spi.muxSCK.connect() + spi.muxCS.connect() + + // software reset of LPSPI state registers + spi.Bus.CR.SetBits(nxp.LPSPI_CR_RST) + // also reset FIFOs (not performed by software reset above) + spi.Bus.CR.SetBits(nxp.LPSPI_CR_RRF | nxp.LPSPI_CR_RTF) + spi.Bus.CR.Set(0) + + // set controller mode, and input data is sampled on delayed SCK edge + spi.Bus.CFGR1.Set(nxp.LPSPI_CFGR1_MASTER | nxp.LPSPI_CFGR1_SAMPLE) + + spi.frequency = config.Frequency + if 0 == spi.frequency { + spi.frequency = defaultSpiFreq + } + + // configure LPSPI clock divisor and CS assertion delays + div := spi.getClockDivisor(config.Frequency) + ccr := (div << nxp.LPSPI_CCR_SCKDIV_Pos) & nxp.LPSPI_CCR_SCKDIV_Msk + ccr |= ((div / 2) << nxp.LPSPI_CCR_DBT_Pos) & nxp.LPSPI_CCR_DBT_Msk + ccr |= ((div / 2) << nxp.LPSPI_CCR_PCSSCK_Pos) & nxp.LPSPI_CCR_PCSSCK_Msk + spi.Bus.CCR.Set(ccr) + + // 8-bit frame size (words) + tcr := uint32(7) + if config.LSBFirst { + tcr |= nxp.LPSPI_TCR_LSBF + } + // set polarity and phase + switch config.Mode { + case Mode1: + tcr |= nxp.LPSPI_TCR_CPHA + case Mode2: + tcr |= nxp.LPSPI_TCR_CPOL + case Mode3: + tcr |= nxp.LPSPI_TCR_CPOL + tcr |= nxp.LPSPI_TCR_CPHA + } + spi.Bus.TCR.Set(tcr) + + // clear FIFO water marks + spi.setWatermark(0, 0) + + // enable LPSPI module + spi.Bus.CR.Set(nxp.LPSPI_CR_MEN) + + spi.configured = true + + return nil +} + +// Transfer writes/reads a single byte using the SPI interface. +func (spi *SPI) Transfer(w byte) (byte, error) { + if !spi.configured { + return 0, errSPINotConfigured + } + + const readTryMax = 10000 + + for spi.Bus.SR.HasBits(statusModuleBusy) { + } // wait for SPI busy bit to clear + + _, txFIFOSize := spi.getFIFOSize() + + spi.flushFIFO(true, true) + spi.Bus.SR.Set(statusAll) // clear all status flags (W1C) + + // enable LPSPI module + spi.Bus.CR.Set(nxp.LPSPI_CR_MEN) + + // TODO: unnecessary since we just flushed the FIFO? + for { // wait for TX FIFO to not be full + if _, txFIFO := spi.getFIFOCount(); txFIFO < txFIFOSize { + break + } + } + + // write out byte to TX FIFO + spi.Bus.TDR.Set(uint32(w)) + + // try to read from RX FIFO if anything exists + didRead := false + data := byte(0) + for i := 0; !didRead && (i < readTryMax); i++ { + rxFIFO, _ := spi.getFIFOCount() + didRead = rxFIFO > 0 + if didRead { + data = byte(spi.Bus.RDR.Get()) + } + } + + // if nothing was read, then wait for transfer complete flag to decide when + // we are finished + if !didRead { + for !spi.Bus.SR.HasBits(nxp.LPSPI_SR_TCF) { + } // wait for all transfers complete flag to set + } + + return data, nil +} + +func (spi *SPI) isHardwareCSPin(pin Pin) bool { + switch unsafe.Pointer(spi.Bus) { + case unsafe.Pointer(nxp.LPSPI1): + return SPI1_CS_PIN == pin + case unsafe.Pointer(nxp.LPSPI2): + return SPI2_CS_PIN == pin + case unsafe.Pointer(nxp.LPSPI3): + return SPI3_CS_PIN == pin + } + return false +} + +func (spi *SPI) hasHardwareCSPin() bool { + return spi.isHardwareCSPin(spi.cs) +} + +// getClockDivisor finds the SPI prescalar that minimizes the error between +// requested frequency and possible frequencies available with the LPSPI clock. +// this routine is based on Teensyduino (libraries/SPI/SPI.cpp): +// +// void SPIClass::setClockDivider_noInline(uint32_t clk) +func (spi *SPI) getClockDivisor(freq uint32) uint32 { + const clock = 132000000 // LPSPI root clock frequency (PLL2) + d := uint32(clock) + if freq > 0 { + d /= freq + } + if d > 0 && clock/d > freq { + d++ + } + if d > 257 { + return 255 + } + if d > 2 { + return d - 2 + } + return 0 +} + +func (spi *SPI) getFIFOSize() (rx, tx uint32) { + param := spi.Bus.PARAM.Get() + return uint32(1) << ((param & nxp.LPSPI_PARAM_RXFIFO_Msk) >> nxp.LPSPI_PARAM_RXFIFO_Pos), + uint32(1) << ((param & nxp.LPSPI_PARAM_TXFIFO_Msk) >> nxp.LPSPI_PARAM_TXFIFO_Pos) +} + +func (spi *SPI) getFIFOCount() (rx, tx uint32) { + fsr := spi.Bus.FSR.Get() + return (fsr & nxp.LPSPI_FSR_RXCOUNT_Msk) >> nxp.LPSPI_FSR_RXCOUNT_Pos, + (fsr & nxp.LPSPI_FSR_TXCOUNT_Msk) >> nxp.LPSPI_FSR_TXCOUNT_Pos +} + +func (spi *SPI) flushFIFO(rx, tx bool) { + var flush uint32 + if rx { + flush |= nxp.LPSPI_CR_RRF + } + if tx { + flush |= nxp.LPSPI_CR_RTF + } + spi.Bus.CR.SetBits(flush) +} + +func (spi *SPI) setWatermark(rx, tx uint32) { + spi.Bus.FCR.Set(((rx << nxp.LPSPI_FCR_RXWATER_Pos) & nxp.LPSPI_FCR_RXWATER_Msk) | + ((tx << nxp.LPSPI_FCR_TXWATER_Pos) & nxp.LPSPI_FCR_TXWATER_Msk)) +} diff --git a/emb/machine/machine_mimxrt1062_uart.go b/emb/machine/machine_mimxrt1062_uart.go new file mode 100644 index 00000000..6265b856 --- /dev/null +++ b/emb/machine/machine_mimxrt1062_uart.go @@ -0,0 +1,303 @@ +//go:build mimxrt1062 + +package machine + +import ( + "device/nxp" + "runtime/interrupt" + "runtime/volatile" +) + +// UART peripheral abstraction layer for the MIMXRT1062 + +type UART struct { + Bus *nxp.LPUART_Type + Buffer *RingBuffer + Interrupt interrupt.Interrupt + + // txBuffer should be allocated globally (such as when UART is created) to + // prevent it being reclaimed or cleaned up prematurely. + txBuffer *RingBuffer + + // these hold the input selector ("daisy chain") values that select which pins + // are connected to the LPUART device, and should be defined where the UART + // instance is declared. see the godoc comments on type muxSelect for more + // details. + muxRX, muxTX muxSelect + + // these are copied from UARTConfig, during (*UART).Configure(UARTConfig), and + // should be considered read-only for internal reference (i.e., modifying them + // will have no desirable effect). + rx, tx Pin + baud uint32 + + // auxiliary state data used internally + configured bool + transmitting volatile.Register32 +} + +func (uart *UART) isTransmitting() bool { return uart.transmitting.Get() != 0 } +func (uart *UART) startTransmitting() { uart.transmitting.Set(1) } +func (uart *UART) stopTransmitting() { uart.transmitting.Set(0) } +func (uart *UART) resetTransmitting() { + uart.stopTransmitting() + uart.Bus.GLOBAL.SetBits(nxp.LPUART_GLOBAL_RST) + uart.Bus.GLOBAL.ClearBits(nxp.LPUART_GLOBAL_RST) +} + +// Configure initializes a UART with the given UARTConfig and other default +// settings. +func (uart *UART) Configure(config UARTConfig) { + + const defaultUartFreq = 115200 + + // use default baud rate if not specified + if config.BaudRate == 0 { + config.BaudRate = defaultUartFreq + } + + // use default UART pins if not specified + if config.RX == 0 && config.TX == 0 { + config.RX = UART_RX_PIN + config.TX = UART_TX_PIN + } + + uart.baud = config.BaudRate + uart.rx = config.RX + uart.tx = config.TX + + // configure the mux and pad control registers + uart.rx.Configure(PinConfig{Mode: PinModeUARTRX}) + uart.tx.Configure(PinConfig{Mode: PinModeUARTTX}) + + // configure the mux input selector + uart.muxRX.connect() + uart.muxTX.connect() + + // reset all internal logic and registers + uart.resetTransmitting() + + // disable until we have finished configuring registers + uart.Bus.CTRL.Set(0) + + // determine the baud rate and over-sample divisors + sbr, osr := uart.getBaudRateDivisor(uart.baud) + + // set the baud rate, over-sample configuration, stop bits + baudBits := (((osr - 1) << nxp.LPUART_BAUD_OSR_Pos) & nxp.LPUART_BAUD_OSR_Msk) | + ((sbr << nxp.LPUART_BAUD_SBR_Pos) & nxp.LPUART_BAUD_SBR_Msk) + if osr <= 8 { + // if OSR less than or equal to 8, we must enable sampling on both edges + baudBits |= nxp.LPUART_BAUD_BOTHEDGE + } + uart.Bus.BAUD.Set(baudBits) + uart.Bus.PINCFG.Set(0) // disable triggers + + // configure watermarks, flush and enable TX/RX FIFOs + rxSize, txSize := uart.getFIFOSize() + rxWater := rxSize >> 1 + if rxWater > uint32(nxp.LPUART_FIFO_RXFIFOSIZE_Msk>>nxp.LPUART_FIFO_RXFIFOSIZE_Pos) { + rxWater = uint32(nxp.LPUART_FIFO_RXFIFOSIZE_Msk >> nxp.LPUART_FIFO_RXFIFOSIZE_Pos) + } + txWater := txSize >> 1 + if txWater > uint32(nxp.LPUART_FIFO_TXFIFOSIZE_Msk>>nxp.LPUART_FIFO_TXFIFOSIZE_Pos) { + txWater = uint32(nxp.LPUART_FIFO_TXFIFOSIZE_Msk >> nxp.LPUART_FIFO_TXFIFOSIZE_Pos) + } + uart.Bus.WATER.Set( + ((rxWater << nxp.LPUART_WATER_RXWATER_Pos) & nxp.LPUART_WATER_RXWATER_Msk) | + ((txWater << nxp.LPUART_WATER_TXWATER_Pos) & nxp.LPUART_WATER_TXWATER_Msk)) + uart.Bus.FIFO.SetBits(nxp.LPUART_FIFO_RXFE | nxp.LPUART_FIFO_TXFE | + nxp.LPUART_FIFO_RXFLUSH | nxp.LPUART_FIFO_TXFLUSH) + + // for now we assume some configuration. in particular: + // Data bits -> 8-bit + // Parity bit -> None (parity bit generation disabled) + // Stop bits -> 1 stop bit + // MSB first -> false + // RX idle type -> idle count starts after start bit + // RX idle config -> 1 idle character + // RX RTS enabled -> false + // TX CTS enabled -> false + + // enable transmitter, receiver functions + uart.Bus.CTRL.Set(nxp.LPUART_CTRL_TE | nxp.LPUART_CTRL_RE | + // enable receiver, idle line interrupts + nxp.LPUART_CTRL_RIE | nxp.LPUART_CTRL_ILIE) + + // clear all status flags + uart.Bus.STAT.Set(uart.Bus.STAT.Get()) + + // enable RX interrupt + uart.Interrupt.SetPriority(0xC0) + uart.Interrupt.Enable() + + uart.configured = true +} + +// Disable disables the UART interface. +// +// If any buffered data has not yet been transmitted, Disable waits until +// transmission completes before disabling the interface. The receiver UART's +// interrupt is also disabled, and the RX/TX pins are reconfigured for GPIO +// input (pull-up). +func (uart *UART) Disable() { + + // first ensure the device is enabled + if uart.configured { + + // wait for any buffered data to send + uart.Sync() + + // stop trapping RX interrupts + uart.Interrupt.Disable() + + // reset all internal registers + uart.resetTransmitting() + + // disable RX/TX functions + uart.Bus.CTRL.ClearBits(nxp.LPUART_CTRL_TE | nxp.LPUART_CTRL_RE) + + // put pins back into GPIO mode + uart.rx.Configure(PinConfig{Mode: PinInputPullup}) + uart.tx.Configure(PinConfig{Mode: PinInputPullup}) + } + uart.configured = false +} + +// Sync blocks the calling goroutine until all data in the output buffer has +// been transmitted. +func (uart *UART) Sync() error { + for uart.isTransmitting() { + } + return nil +} + +// WriteByte writes a single byte of data to the UART interface. +func (uart *UART) writeByte(c byte) error { + uart.startTransmitting() + for !uart.txBuffer.Put(c) { + } + uart.Bus.CTRL.SetBits(nxp.LPUART_CTRL_TIE) + return nil +} + +func (uart *UART) flush() {} + +// getBaudRateDivisor finds the greatest over-sampling factor (4..32) and +// corresponding baud rate divisor (1..8191) that best partition a given baud +// rate into equal intervals. +// +// This is an integral (non-floating point) translation of the logic at the +// beginning of: +// +// void HardwareSerial::begin(uint32_t baud, uint16_t format) +// +// (from Teensyduino: cores/teensy4/HardwareSerial.cpp) +// +// We don't want to use floating point here in case it gets called from an ISR +// or very early during system init. +func (uart *UART) getBaudRateDivisor(baudRate uint32) (sbr uint32, osr uint32) { + const clock = 24000000 // UART is muxed to 24 MHz OSC + err := uint32(0xFFFFFFFF) + sbr, osr = 0, 0 + for o := uint32(4); o <= 32; o++ { + s := ((clock*10)/(baudRate*o) + 5) / 10 + if s == 0 { + s = 1 + } + b := clock / (s * o) + var e uint32 + if b > baudRate { + e = b - baudRate + } else { + e = baudRate - b + } + if e <= err { + err = e + osr = o + sbr = s + } + } + return sbr, osr +} + +func (uart *UART) getFIFOSize() (rx, tx uint32) { + fifo := uart.Bus.FIFO.Get() + rx = uint32(1) << ((fifo & nxp.LPUART_FIFO_RXFIFOSIZE_Msk) >> nxp.LPUART_FIFO_RXFIFOSIZE_Pos) + if rx > 1 { + rx <<= 1 + } + tx = uint32(1) << ((fifo & nxp.LPUART_FIFO_TXFIFOSIZE_Msk) >> nxp.LPUART_FIFO_TXFIFOSIZE_Pos) + if tx > 1 { + tx <<= 1 + } + return rx, tx +} + +func (uart *UART) getStatus() uint32 { + return uart.Bus.STAT.Get() | + ((uart.Bus.FIFO.Get() & uint32(nxp.LPUART_FIFO_TXEMPT_Msk|nxp.LPUART_FIFO_RXEMPT_Msk| + nxp.LPUART_FIFO_TXOF_Msk|nxp.LPUART_FIFO_RXUF_Msk)) >> 16) +} + +func (uart *UART) getEnabledInterrupts() uint32 { + return ((uart.Bus.BAUD.Get() & uint32(nxp.LPUART_BAUD_LBKDIE_Msk|nxp.LPUART_BAUD_RXEDGIE_Msk)) >> 8) | + ((uart.Bus.FIFO.Get() & uint32(nxp.LPUART_FIFO_TXOFE_Msk|nxp.LPUART_FIFO_RXUFE_Msk)) >> 8) | + (uart.Bus.CTRL.Get() & uint32(0xFF0C000)) +} + +func (uart *UART) disableInterrupts(mask uint32) { + uart.Bus.BAUD.ClearBits((mask << 8) & uint32(nxp.LPUART_BAUD_LBKDIE_Msk|nxp.LPUART_BAUD_RXEDGIE_Msk)) + uart.Bus.FIFO.Set((uart.Bus.FIFO.Get() & ^uint32(nxp.LPUART_FIFO_TXOF_Msk|nxp.LPUART_FIFO_RXUF_Msk)) & + ^uint32((mask<<8)&(nxp.LPUART_FIFO_TXOFE_Msk|nxp.LPUART_FIFO_RXUFE_Msk))) + mask &= uint32(0xFFFFFF00) + uart.Bus.CTRL.ClearBits(mask) +} + +func (uart *UART) handleInterrupt(interrupt.Interrupt) { + + stat := uart.getStatus() + inte := uart.getEnabledInterrupts() + + _, txSize := uart.getFIFOSize() + + // check for and clear overrun, otherwise RX will not work + if (stat & uint32(nxp.LPUART_STAT_OR)) != 0 { + uart.Bus.STAT.Set((uart.Bus.STAT.Get() & uint32(0x3FE00000)) | nxp.LPUART_STAT_OR) + } + + // idle or receive data register is full + if (stat & uint32(nxp.LPUART_STAT_RDRF|nxp.LPUART_STAT_IDLE)) != 0 { + count := (uart.Bus.WATER.Get() & uint32(nxp.LPUART_WATER_RXCOUNT_Msk)) >> nxp.LPUART_WATER_RXCOUNT_Pos + for ; count > 0; count-- { + // read up to 8 bits of data at a time + // TODO: 7, 9, and 10-bit support? + uart.Buffer.Put(uint8(uart.Bus.DATA.Get() & uint32(0xFF))) + } + // if it was an IDLE status, clear the flag + if (stat & uint32(nxp.LPUART_STAT_IDLE)) != 0 { + uart.Bus.STAT.SetBits(nxp.LPUART_STAT_IDLE) + } + // disable idle line interrupts + uart.disableInterrupts(nxp.LPUART_CTRL_RIE | nxp.LPUART_CTRL_ORIE) + } + + // check if we have data to write + if ((inte & nxp.LPUART_CTRL_TIE) != 0) && ((stat & nxp.LPUART_STAT_TDRE) != 0) { + for ((uart.Bus.WATER.Get() & uint32(nxp.LPUART_WATER_TXCOUNT_Msk)) >> nxp.LPUART_WATER_TXCOUNT_Pos) < txSize { + if b, ok := uart.txBuffer.Get(); ok { + uart.Bus.DATA.Set(uint32(b)) + } else { + break + } + } + if uart.Bus.STAT.HasBits(nxp.LPUART_STAT_TDRE) { + uart.Bus.CTRL.Set((uart.Bus.CTRL.Get() & ^uint32(nxp.LPUART_CTRL_TIE)) | nxp.LPUART_CTRL_TCIE) + } + } + + if ((inte & nxp.LPUART_CTRL_TCIE) != 0) && ((stat & nxp.LPUART_STAT_TC) != 0) { + uart.stopTransmitting() + uart.Bus.CTRL.ClearBits(nxp.LPUART_CTRL_TCIE) + } +} diff --git a/emb/machine/machine_nrf.go b/emb/machine/machine_nrf.go new file mode 100644 index 00000000..d6d6349f --- /dev/null +++ b/emb/machine/machine_nrf.go @@ -0,0 +1,449 @@ +//go:build nrf + +package machine + +import ( + "device/nrf" + "internal/binary" + "runtime/interrupt" + "unsafe" +) + +const deviceName = nrf.Device + +var deviceID [8]byte + +// DeviceID returns an identifier that is unique within +// a particular chipset. +// +// The identity is one burnt into the MCU itself, or the +// flash chip at time of manufacture. +// +// It's possible that two different vendors may allocate +// the same DeviceID, so callers should take this into +// account if needing to generate a globally unique id. +// +// The length of the hardware ID is vendor-specific, but +// 8 bytes (64 bits) is common. +func DeviceID() []byte { + words := make([]uint32, 2) + words[0] = nrf.FICR.DEVICEID[0].Get() + words[1] = nrf.FICR.DEVICEID[1].Get() + + for i := 0; i < 8; i++ { + shift := (i % 4) * 8 + w := i / 4 + deviceID[i] = byte(words[w] >> shift) + } + + return deviceID[:] +} + +const ( + PinInput PinMode = (nrf.GPIO_PIN_CNF_DIR_Input << nrf.GPIO_PIN_CNF_DIR_Pos) | (nrf.GPIO_PIN_CNF_INPUT_Connect << nrf.GPIO_PIN_CNF_INPUT_Pos) + PinInputPullup PinMode = PinInput | (nrf.GPIO_PIN_CNF_PULL_Pullup << nrf.GPIO_PIN_CNF_PULL_Pos) + PinInputPulldown PinMode = PinInput | (nrf.GPIO_PIN_CNF_PULL_Pulldown << nrf.GPIO_PIN_CNF_PULL_Pos) + PinOutput PinMode = (nrf.GPIO_PIN_CNF_DIR_Output << nrf.GPIO_PIN_CNF_DIR_Pos) | (nrf.GPIO_PIN_CNF_INPUT_Connect << nrf.GPIO_PIN_CNF_INPUT_Pos) +) + +type PinChange uint8 + +// Pin change interrupt constants for SetInterrupt. +const ( + PinRising PinChange = nrf.GPIOTE_CONFIG_POLARITY_LoToHi + PinFalling PinChange = nrf.GPIOTE_CONFIG_POLARITY_HiToLo + PinToggle PinChange = nrf.GPIOTE_CONFIG_POLARITY_Toggle +) + +// Callbacks to be called for pins configured with SetInterrupt. +var pinCallbacks [len(nrf.GPIOTE.CONFIG)]func(Pin) + +// Configure this pin with the given configuration. +func (p Pin) Configure(config PinConfig) { + cfg := config.Mode | nrf.GPIO_PIN_CNF_DRIVE_S0S1 | nrf.GPIO_PIN_CNF_SENSE_Disabled + port, pin := p.getPortPin() + port.PIN_CNF[pin].Set(uint32(cfg)) +} + +// Set the pin to high or low. +// Warning: only use this on an output pin! +func (p Pin) Set(high bool) { + port, pin := p.getPortPin() + if high { + port.OUTSET.Set(1 << pin) + } else { + port.OUTCLR.Set(1 << pin) + } +} + +// Return the register and mask to enable a given GPIO pin. This can be used to +// implement bit-banged drivers. +func (p Pin) PortMaskSet() (*uint32, uint32) { + port, pin := p.getPortPin() + return &port.OUTSET.Reg, 1 << pin +} + +// Return the register and mask to disable a given port. This can be used to +// implement bit-banged drivers. +func (p Pin) PortMaskClear() (*uint32, uint32) { + port, pin := p.getPortPin() + return &port.OUTCLR.Reg, 1 << pin +} + +// Get returns the current value of a GPIO pin when the pin is configured as an +// input or as an output. +func (p Pin) Get() bool { + port, pin := p.getPortPin() + return (port.IN.Get()>>pin)&1 != 0 +} + +// SetInterrupt sets an interrupt to be executed when a particular pin changes +// state. The pin should already be configured as an input, including a pull up +// or down if no external pull is provided. +// +// This call will replace a previously set callback on this pin. You can pass a +// nil func to unset the pin change interrupt. If you do so, the change +// parameter is ignored and can be set to any value (such as 0). +func (p Pin) SetInterrupt(change PinChange, callback func(Pin)) error { + // Some variables to easily check whether a channel was already configured + // as an event channel for the given pin. + // This is not just an optimization, this is required: the datasheet says + // that configuring more than one channel for a given pin results in + // unpredictable behavior. + expectedConfigMask := uint32(nrf.GPIOTE_CONFIG_MODE_Msk | nrf.GPIOTE_CONFIG_PSEL_Msk) + expectedConfig := nrf.GPIOTE_CONFIG_MODE_Event<> nrf.GPIOTE_CONFIG_PSEL_Pos) + pinCallbacks[i](pin) + } + } + }).Enable() + + // Everything was configured correctly. + return nil +} + +// UART on the NRF. +type UART struct { + Buffer *RingBuffer +} + +// UART +var ( + // UART0 is the hardware UART on the NRF SoC. + _UART0 = UART{Buffer: NewRingBuffer()} + UART0 = &_UART0 +) + +// Configure the UART. +func (uart *UART) Configure(config UARTConfig) { + // Default baud rate to 115200. + if config.BaudRate == 0 { + config.BaudRate = 115200 + } + + uart.SetBaudRate(config.BaudRate) + + // Set TX and RX pins + if config.TX == 0 && config.RX == 0 { + // Use default pins + uart.setPins(UART_TX_PIN, UART_RX_PIN) + } else { + uart.setPins(config.TX, config.RX) + } + + nrf.UART0.ENABLE.Set(nrf.UART_ENABLE_ENABLE_Enabled) + nrf.UART0.TASKS_STARTTX.Set(1) + nrf.UART0.TASKS_STARTRX.Set(1) + nrf.UART0.INTENSET.Set(nrf.UART_INTENSET_RXDRDY_Msk) + + // Enable RX IRQ. + intr := interrupt.New(nrf.IRQ_UART0, _UART0.handleInterrupt) + intr.SetPriority(0xc0) // low priority + intr.Enable() +} + +// SetBaudRate sets the communication speed for the UART. +func (uart *UART) SetBaudRate(br uint32) { + // Magic: calculate 'baudrate' register from the input number. + // Every value listed in the datasheet will be converted to the + // correct register value, except for 192600. I suspect the value + // listed in the nrf52 datasheet (0x0EBED000) is incorrectly rounded + // and should be 0x0EBEE000, as the nrf51 datasheet lists the + // nonrounded value 0x0EBEDFA4. + // Some background: + // https://devzone.nordicsemi.com/f/nordic-q-a/391/uart-baudrate-register-values/2046#2046 + rate := uint32((uint64(br/400)*uint64(400*0xffffffff/16000000) + 0x800) & 0xffffff000) + + nrf.UART0.BAUDRATE.Set(rate) +} + +// WriteByte writes a byte of data to the UART. +func (uart *UART) writeByte(c byte) error { + nrf.UART0.EVENTS_TXDRDY.Set(0) + nrf.UART0.TXD.Set(uint32(c)) + for nrf.UART0.EVENTS_TXDRDY.Get() == 0 { + } + return nil +} + +func (uart *UART) flush() {} + +func (uart *UART) handleInterrupt(interrupt.Interrupt) { + if nrf.UART0.EVENTS_RXDRDY.Get() != 0 { + uart.Receive(byte(nrf.UART0.RXD.Get())) + nrf.UART0.EVENTS_RXDRDY.Set(0x0) + } +} + +const i2cTimeout = 0xffff // this is around 29ms on a nrf52 + +// I2CConfig is used to store config info for I2C. +type I2CConfig struct { + Frequency uint32 + SCL Pin + SDA Pin + Mode I2CMode +} + +// Configure is intended to setup the I2C interface. +func (i2c *I2C) Configure(config I2CConfig) error { + + i2c.disable() + + // Default I2C bus speed is 100 kHz. + if config.Frequency == 0 { + config.Frequency = 100 * KHz + } + // Default I2C pins if not set. + if config.SDA == 0 && config.SCL == 0 { + config.SDA = SDA_PIN + config.SCL = SCL_PIN + } + + // do config + sclPort, sclPin := config.SCL.getPortPin() + sclPort.PIN_CNF[sclPin].Set((nrf.GPIO_PIN_CNF_DIR_Input << nrf.GPIO_PIN_CNF_DIR_Pos) | + (nrf.GPIO_PIN_CNF_INPUT_Connect << nrf.GPIO_PIN_CNF_INPUT_Pos) | + (nrf.GPIO_PIN_CNF_PULL_Pullup << nrf.GPIO_PIN_CNF_PULL_Pos) | + (nrf.GPIO_PIN_CNF_DRIVE_S0D1 << nrf.GPIO_PIN_CNF_DRIVE_Pos) | + (nrf.GPIO_PIN_CNF_SENSE_Disabled << nrf.GPIO_PIN_CNF_SENSE_Pos)) + + sdaPort, sdaPin := config.SDA.getPortPin() + sdaPort.PIN_CNF[sdaPin].Set((nrf.GPIO_PIN_CNF_DIR_Input << nrf.GPIO_PIN_CNF_DIR_Pos) | + (nrf.GPIO_PIN_CNF_INPUT_Connect << nrf.GPIO_PIN_CNF_INPUT_Pos) | + (nrf.GPIO_PIN_CNF_PULL_Pullup << nrf.GPIO_PIN_CNF_PULL_Pos) | + (nrf.GPIO_PIN_CNF_DRIVE_S0D1 << nrf.GPIO_PIN_CNF_DRIVE_Pos) | + (nrf.GPIO_PIN_CNF_SENSE_Disabled << nrf.GPIO_PIN_CNF_SENSE_Pos)) + + i2c.setPins(config.SCL, config.SDA) + + i2c.mode = config.Mode + if i2c.mode == I2CModeController { + i2c.SetBaudRate(config.Frequency) + + i2c.enableAsController() + } else { + i2c.enableAsTarget() + } + + return nil +} + +// SetBaudRate sets the I2C frequency. It has the side effect of also +// enabling the I2C hardware if disabled beforehand. +// +//go:inline +func (i2c *I2C) SetBaudRate(br uint32) error { + switch { + case br >= 400*KHz: + i2c.Bus.SetFREQUENCY(nrf.TWI_FREQUENCY_FREQUENCY_K400) + case br >= 250*KHz: + i2c.Bus.SetFREQUENCY(nrf.TWI_FREQUENCY_FREQUENCY_K250) + default: + i2c.Bus.SetFREQUENCY(nrf.TWI_FREQUENCY_FREQUENCY_K100) + } + + return nil +} + +// signalStop sends a stop signal to the I2C peripheral and waits for confirmation. +func (i2c *I2C) signalStop() error { + tries := 0 + i2c.Bus.TASKS_STOP.Set(1) + for i2c.Bus.EVENTS_STOPPED.Get() == 0 { + tries++ + if tries >= i2cTimeout { + return errI2CSignalStopTimeout + } + } + i2c.Bus.EVENTS_STOPPED.Set(0) + return nil +} + +var rngStarted = false + +// getRNG returns 32 bits of non-deterministic random data based on internal thermal noise. +// According to Nordic's documentation, the random output is suitable for cryptographic purposes. +func getRNG() (ret uint32, err error) { + // There's no apparent way to check the status of the RNG peripheral's task, so simply start it + // to avoid deadlocking while waiting for output. + if !rngStarted { + nrf.RNG.TASKS_START.Set(1) + nrf.RNG.SetCONFIG_DERCEN(nrf.RNG_CONFIG_DERCEN_Enabled) + rngStarted = true + } + + // The RNG returns one byte at a time, so stack up four bytes into a single uint32 for return. + for i := 0; i < 4; i++ { + // Wait for data to be ready. + for nrf.RNG.EVENTS_VALRDY.Get() == 0 { + } + // Append random byte to output. + ret = (ret << 8) ^ nrf.RNG.GetVALUE() + // Unset the EVENTS_VALRDY register to avoid reading the same random output twice. + nrf.RNG.EVENTS_VALRDY.Set(0) + } + + return ret, nil +} + +// ReadTemperature reads the silicon die temperature of the chip. The return +// value is in milli-celsius. +func ReadTemperature() int32 { + nrf.TEMP.TASKS_START.Set(1) + for nrf.TEMP.EVENTS_DATARDY.Get() == 0 { + } + temp := int32(nrf.TEMP.TEMP.Get()) * 250 // the returned value is in units of 0.25°C + nrf.TEMP.EVENTS_DATARDY.Set(0) + return temp +} + +const memoryStart = 0x0 + +// compile-time check for ensuring we fulfill BlockDevice interface +var _ BlockDevice = flashBlockDevice{} + +var Flash flashBlockDevice + +type flashBlockDevice struct { +} + +// ReadAt reads the given number of bytes from the block device. +func (f flashBlockDevice) ReadAt(p []byte, off int64) (n int, err error) { + if FlashDataStart()+uintptr(off)+uintptr(len(p)) > FlashDataEnd() { + return 0, errFlashCannotReadPastEOF + } + + data := unsafe.Slice((*byte)(unsafe.Pointer(FlashDataStart()+uintptr(off))), len(p)) + copy(p, data) + + return len(p), nil +} + +// WriteAt writes the given number of bytes to the block device. +// Only double-word (64 bits) length data can be programmed. See rm0461 page 78. +// If the length of p is not long enough it will be padded with 0xFF bytes. +// This method assumes that the destination is already erased. +func (f flashBlockDevice) WriteAt(p []byte, off int64) (n int, err error) { + if FlashDataStart()+uintptr(off)+uintptr(len(p)) > FlashDataEnd() { + return 0, errFlashCannotWritePastEOF + } + + address := FlashDataStart() + uintptr(off) + padded := flashPad(p, int(f.WriteBlockSize())) + + waitWhileFlashBusy() + + nrf.NVMC.SetCONFIG_WEN(nrf.NVMC_CONFIG_WEN_Wen) + defer nrf.NVMC.SetCONFIG_WEN(nrf.NVMC_CONFIG_WEN_Ren) + + for j := 0; j < len(padded); j += int(f.WriteBlockSize()) { + // write word + *(*uint32)(unsafe.Pointer(address)) = binary.LittleEndian.Uint32(padded[j : j+int(f.WriteBlockSize())]) + address += uintptr(f.WriteBlockSize()) + waitWhileFlashBusy() + } + + return len(padded), nil +} + +// Size returns the number of bytes in this block device. +func (f flashBlockDevice) Size() int64 { + return int64(FlashDataEnd() - FlashDataStart()) +} + +const writeBlockSize = 4 + +// WriteBlockSize returns the block size in which data can be written to +// memory. It can be used by a client to optimize writes, non-aligned writes +// should always work correctly. +func (f flashBlockDevice) WriteBlockSize() int64 { + return writeBlockSize +} + +// EraseBlockSize returns the smallest erasable area on this particular chip +// in bytes. This is used for the block size in EraseBlocks. +// It must be a power of two, and may be as small as 1. A typical size is 4096. +func (f flashBlockDevice) EraseBlockSize() int64 { + return eraseBlockSize() +} + +// EraseBlocks erases the given number of blocks. An implementation may +// transparently coalesce ranges of blocks into larger bundles if the chip +// supports this. The start and len parameters are in block numbers, use +// EraseBlockSize to map addresses to blocks. +func (f flashBlockDevice) EraseBlocks(start, len int64) error { + address := FlashDataStart() + uintptr(start*f.EraseBlockSize()) + waitWhileFlashBusy() + + nrf.NVMC.SetCONFIG_WEN(nrf.NVMC_CONFIG_WEN_Een) + defer nrf.NVMC.SetCONFIG_WEN(nrf.NVMC_CONFIG_WEN_Ren) + + for i := start; i < start+len; i++ { + nrf.NVMC.ERASEPAGE.Set(uint32(address)) + waitWhileFlashBusy() + address += uintptr(f.EraseBlockSize()) + } + + return nil +} + +func waitWhileFlashBusy() { + for nrf.NVMC.GetREADY() != nrf.NVMC_READY_READY_Ready { + } +} diff --git a/emb/machine/machine_nrf51.go b/emb/machine/machine_nrf51.go new file mode 100644 index 00000000..d627d63c --- /dev/null +++ b/emb/machine/machine_nrf51.go @@ -0,0 +1,278 @@ +//go:build nrf51 + +package machine + +import ( + "device/nrf" +) + +const eraseBlockSizeValue = 1024 + +func eraseBlockSize() int64 { + return eraseBlockSizeValue +} + +// Get peripheral and pin number for this GPIO pin. +func (p Pin) getPortPin() (*nrf.GPIO_Type, uint32) { + return nrf.GPIO, uint32(p) +} + +func (uart *UART) setPins(tx, rx Pin) { + nrf.UART0.PSELTXD.Set(uint32(tx)) + nrf.UART0.PSELRXD.Set(uint32(rx)) +} + +func (i2c *I2C) setPins(scl, sda Pin) { + i2c.Bus.PSELSCL.Set(uint32(scl)) + i2c.Bus.PSELSDA.Set(uint32(sda)) +} + +// SPI on the NRF. +type SPI struct { + Bus *nrf.SPI_Type +} + +// There are 2 SPI interfaces on the NRF51. +var ( + SPI0 = &SPI{Bus: nrf.SPI0} + SPI1 = &SPI{Bus: nrf.SPI1} +) + +// SPIConfig is used to store config info for SPI. +type SPIConfig struct { + Frequency uint32 + SCK Pin + SDO Pin + SDI Pin + LSBFirst bool + Mode uint8 +} + +// Configure is intended to setup the SPI interface. +func (spi *SPI) Configure(config SPIConfig) error { + // Disable bus to configure it + spi.Bus.ENABLE.Set(nrf.SPI_ENABLE_ENABLE_Disabled) + + // set frequency + var freq uint32 + + if config.Frequency == 0 { + config.Frequency = 4000000 // 4MHz + } + + switch { + case config.Frequency >= 8000000: + freq = nrf.SPI_FREQUENCY_FREQUENCY_M8 + case config.Frequency >= 4000000: + freq = nrf.SPI_FREQUENCY_FREQUENCY_M4 + case config.Frequency >= 2000000: + freq = nrf.SPI_FREQUENCY_FREQUENCY_M2 + case config.Frequency >= 1000000: + freq = nrf.SPI_FREQUENCY_FREQUENCY_M1 + case config.Frequency >= 500000: + freq = nrf.SPI_FREQUENCY_FREQUENCY_K500 + case config.Frequency >= 250000: + freq = nrf.SPI_FREQUENCY_FREQUENCY_K250 + default: // below 250kHz, default to the lowest speed available + freq = nrf.SPI_FREQUENCY_FREQUENCY_K125 + } + spi.Bus.FREQUENCY.Set(freq) + + var conf uint32 + + // set bit transfer order + if config.LSBFirst { + conf = (nrf.SPI_CONFIG_ORDER_LsbFirst << nrf.SPI_CONFIG_ORDER_Pos) + } + + // set mode + switch config.Mode { + case 0: + conf &^= (nrf.SPI_CONFIG_CPOL_ActiveHigh << nrf.SPI_CONFIG_CPOL_Pos) + conf &^= (nrf.SPI_CONFIG_CPHA_Leading << nrf.SPI_CONFIG_CPHA_Pos) + case 1: + conf &^= (nrf.SPI_CONFIG_CPOL_ActiveHigh << nrf.SPI_CONFIG_CPOL_Pos) + conf |= (nrf.SPI_CONFIG_CPHA_Trailing << nrf.SPI_CONFIG_CPHA_Pos) + case 2: + conf |= (nrf.SPI_CONFIG_CPOL_ActiveLow << nrf.SPI_CONFIG_CPOL_Pos) + conf &^= (nrf.SPI_CONFIG_CPHA_Leading << nrf.SPI_CONFIG_CPHA_Pos) + case 3: + conf |= (nrf.SPI_CONFIG_CPOL_ActiveLow << nrf.SPI_CONFIG_CPOL_Pos) + conf |= (nrf.SPI_CONFIG_CPHA_Trailing << nrf.SPI_CONFIG_CPHA_Pos) + default: // to mode + conf &^= (nrf.SPI_CONFIG_CPOL_ActiveHigh << nrf.SPI_CONFIG_CPOL_Pos) + conf &^= (nrf.SPI_CONFIG_CPHA_Leading << nrf.SPI_CONFIG_CPHA_Pos) + } + spi.Bus.CONFIG.Set(conf) + + // set pins + if config.SCK == 0 && config.SDO == 0 && config.SDI == 0 { + config.SCK = SPI0_SCK_PIN + config.SDO = SPI0_SDO_PIN + config.SDI = SPI0_SDI_PIN + } + spi.Bus.PSELSCK.Set(uint32(config.SCK)) + spi.Bus.PSELMOSI.Set(uint32(config.SDO)) + spi.Bus.PSELMISO.Set(uint32(config.SDI)) + + // Re-enable bus now that it is configured. + spi.Bus.ENABLE.Set(nrf.SPI_ENABLE_ENABLE_Enabled) + + return nil +} + +// Transfer writes/reads a single byte using the SPI interface. +func (spi *SPI) Transfer(w byte) (byte, error) { + spi.Bus.TXD.Set(uint32(w)) + for spi.Bus.EVENTS_READY.Get() == 0 { + } + r := spi.Bus.RXD.Get() + spi.Bus.EVENTS_READY.Set(0) + + // TODO: handle SPI errors + return byte(r), nil +} + +// Tx handles read/write operation for SPI interface. Since SPI is a synchronous write/read +// interface, there must always be the same number of bytes written as bytes read. +// The Tx method knows about this, and offers a few different ways of calling it. +// +// This form sends the bytes in tx buffer, putting the resulting bytes read into the rx buffer. +// Note that the tx and rx buffers must be the same size: +// +// spi.Tx(tx, rx) +// +// This form sends the tx buffer, ignoring the result. Useful for sending "commands" that return zeros +// until all the bytes in the command packet have been received: +// +// spi.Tx(tx, nil) +// +// This form sends zeros, putting the result into the rx buffer. Good for reading a "result packet": +// +// spi.Tx(nil, rx) +func (spi *SPI) Tx(w, r []byte) error { + var err error + + switch { + case len(w) == 0: + // read only, so write zero and read a result. + for i := range r { + r[i], err = spi.Transfer(0) + if err != nil { + return err + } + } + case len(r) == 0: + // write only + spi.Bus.TXD.Set(uint32(w[0])) + w = w[1:] + for _, b := range w { + spi.Bus.TXD.Set(uint32(b)) + for spi.Bus.EVENTS_READY.Get() == 0 { + } + spi.Bus.EVENTS_READY.Set(0) + _ = spi.Bus.RXD.Get() + } + for spi.Bus.EVENTS_READY.Get() == 0 { + } + spi.Bus.EVENTS_READY.Set(0) + _ = spi.Bus.RXD.Get() + + default: + // write/read + if len(w) != len(r) { + return ErrTxInvalidSliceSize + } + + for i, b := range w { + r[i], err = spi.Transfer(b) + if err != nil { + return err + } + } + } + + return nil +} + +// InitADC initializes the registers needed for ADC. +func InitADC() { + return // no specific setup on nrf51 machine. +} + +// Configure configures an ADC pin to be able to read analog data. +func (a ADC) Configure(ADCConfig) { + return // no pin specific setup on nrf51 machine. +} + +// Get returns the current value of a ADC pin in the range 0..0xffff. +func (a ADC) Get() uint16 { + var value uint32 + + adcPin := a.getADCPin() + + // Enable ADC. + nrf.ADC.SetENABLE(nrf.ADC_ENABLE_ENABLE_Enabled) + + // Set pin to read. + nrf.ADC.SetCONFIG_PSEL(adcPin) + + // config ADC + nrf.ADC.SetCONFIG_RES(nrf.ADC_CONFIG_RES_10bit) + nrf.ADC.SetCONFIG_INPSEL(nrf.ADC_CONFIG_INPSEL_AnalogInputOneThirdPrescaling) + nrf.ADC.SetCONFIG_REFSEL(nrf.ADC_CONFIG_REFSEL_SupplyOneThirdPrescaling) + + // Start tasks. + nrf.ADC.TASKS_START.Set(1) + + // Wait until the sample task is done. + for nrf.ADC.EVENTS_END.Get() == 0 { + } + nrf.ADC.EVENTS_END.Set(0x00) + + value = nrf.ADC.GetRESULT() + + // Stop the ADC + nrf.ADC.TASKS_STOP.Set(1) + + // Disable ADC. + nrf.ADC.SetENABLE(nrf.ADC_ENABLE_ENABLE_Disabled) + + if value < 0 { + value = 0 + } + + // Return 16-bit result from 10-bit value. + return uint16(value << 6) +} + +func (a ADC) getADCPin() uint32 { + switch a.Pin { + case 1: + return nrf.ADC_CONFIG_PSEL_AnalogInput2 + + case 2: + return nrf.ADC_CONFIG_PSEL_AnalogInput3 + + case 3: + return nrf.ADC_CONFIG_PSEL_AnalogInput4 + + case 4: + return nrf.ADC_CONFIG_PSEL_AnalogInput5 + + case 5: + return nrf.ADC_CONFIG_PSEL_AnalogInput6 + + case 6: + return nrf.ADC_CONFIG_PSEL_AnalogInput7 + + case 26: + return nrf.ADC_CONFIG_PSEL_AnalogInput0 + + case 27: + return nrf.ADC_CONFIG_PSEL_AnalogInput1 + + default: + return 0 + } +} diff --git a/emb/machine/machine_nrf52.go b/emb/machine/machine_nrf52.go new file mode 100644 index 00000000..71c53432 --- /dev/null +++ b/emb/machine/machine_nrf52.go @@ -0,0 +1,71 @@ +//go:build nrf52 + +package machine + +import ( + "device/nrf" +) + +// Hardware pins +const ( + P0_00 Pin = 0 + P0_01 Pin = 1 + P0_02 Pin = 2 + P0_03 Pin = 3 + P0_04 Pin = 4 + P0_05 Pin = 5 + P0_06 Pin = 6 + P0_07 Pin = 7 + P0_08 Pin = 8 + P0_09 Pin = 9 + P0_10 Pin = 10 + P0_11 Pin = 11 + P0_12 Pin = 12 + P0_13 Pin = 13 + P0_14 Pin = 14 + P0_15 Pin = 15 + P0_16 Pin = 16 + P0_17 Pin = 17 + P0_18 Pin = 18 + P0_19 Pin = 19 + P0_20 Pin = 20 + P0_21 Pin = 21 + P0_22 Pin = 22 + P0_23 Pin = 23 + P0_24 Pin = 24 + P0_25 Pin = 25 + P0_26 Pin = 26 + P0_27 Pin = 27 + P0_28 Pin = 28 + P0_29 Pin = 29 + P0_30 Pin = 30 + P0_31 Pin = 31 +) + +// Get peripheral and pin number for this GPIO pin. +func (p Pin) getPortPin() (*nrf.GPIO_Type, uint32) { + return nrf.P0, uint32(p) +} + +func (uart *UART) setPins(tx, rx Pin) { + nrf.UART0.PSELTXD.Set(uint32(tx)) + nrf.UART0.PSELRXD.Set(uint32(rx)) +} + +func (i2c *I2C) setPins(scl, sda Pin) { + i2c.Bus.PSELSCL.Set(uint32(scl)) + i2c.Bus.PSELSDA.Set(uint32(sda)) +} + +// PWM +var ( + PWM0 = &PWM{PWM: nrf.PWM0} + PWM1 = &PWM{PWM: nrf.PWM1} + PWM2 = &PWM{PWM: nrf.PWM2} +) + +const eraseBlockSizeValue = 4096 + +func eraseBlockSize() int64 { + return eraseBlockSizeValue +} diff --git a/emb/machine/machine_nrf52833.go b/emb/machine/machine_nrf52833.go new file mode 100644 index 00000000..60558eb0 --- /dev/null +++ b/emb/machine/machine_nrf52833.go @@ -0,0 +1,92 @@ +//go:build nrf52833 + +package machine + +import ( + "device/nrf" +) + +// Hardware pins +const ( + P0_00 Pin = 0 + P0_01 Pin = 1 + P0_02 Pin = 2 + P0_03 Pin = 3 + P0_04 Pin = 4 + P0_05 Pin = 5 + P0_06 Pin = 6 + P0_07 Pin = 7 + P0_08 Pin = 8 + P0_09 Pin = 9 + P0_10 Pin = 10 + P0_11 Pin = 11 + P0_12 Pin = 12 + P0_13 Pin = 13 + P0_14 Pin = 14 + P0_15 Pin = 15 + P0_16 Pin = 16 + P0_17 Pin = 17 + P0_18 Pin = 18 + P0_19 Pin = 19 + P0_20 Pin = 20 + P0_21 Pin = 21 + P0_22 Pin = 22 + P0_23 Pin = 23 + P0_24 Pin = 24 + P0_25 Pin = 25 + P0_26 Pin = 26 + P0_27 Pin = 27 + P0_28 Pin = 28 + P0_29 Pin = 29 + P0_30 Pin = 30 + P0_31 Pin = 31 + P1_00 Pin = 32 + P1_01 Pin = 33 + P1_02 Pin = 34 + P1_03 Pin = 35 + P1_04 Pin = 36 + P1_05 Pin = 37 + P1_06 Pin = 38 + P1_07 Pin = 39 + P1_08 Pin = 40 + P1_09 Pin = 41 + P1_10 Pin = 42 + P1_11 Pin = 43 + P1_12 Pin = 44 + P1_13 Pin = 45 + P1_14 Pin = 46 + P1_15 Pin = 47 +) + +// Get peripheral and pin number for this GPIO pin. +func (p Pin) getPortPin() (*nrf.GPIO_Type, uint32) { + if p >= 32 { + return nrf.P1, uint32(p - 32) + } else { + return nrf.P0, uint32(p) + } +} + +func (uart *UART) setPins(tx, rx Pin) { + nrf.UART0.PSEL.TXD.Set(uint32(tx)) + nrf.UART0.PSEL.RXD.Set(uint32(rx)) +} + +func (i2c *I2C) setPins(scl, sda Pin) { + i2c.Bus.PSEL.SCL.Set(uint32(scl)) + i2c.Bus.PSEL.SDA.Set(uint32(sda)) +} + +// PWM +var ( + PWM0 = &PWM{PWM: nrf.PWM0} + PWM1 = &PWM{PWM: nrf.PWM1} + PWM2 = &PWM{PWM: nrf.PWM2} + PWM3 = &PWM{PWM: nrf.PWM3} +) + +const eraseBlockSizeValue = 4096 + +func eraseBlockSize() int64 { + return eraseBlockSizeValue +} diff --git a/emb/machine/machine_nrf52840.go b/emb/machine/machine_nrf52840.go new file mode 100644 index 00000000..21a43678 --- /dev/null +++ b/emb/machine/machine_nrf52840.go @@ -0,0 +1,110 @@ +//go:build nrf52840 + +package machine + +import ( + "device/nrf" + "errors" + "unsafe" +) + +// Get peripheral and pin number for this GPIO pin. +func (p Pin) getPortPin() (*nrf.GPIO_Type, uint32) { + if p >= 32 { + return nrf.P1, uint32(p - 32) + } else { + return nrf.P0, uint32(p) + } +} + +func (uart *UART) setPins(tx, rx Pin) { + nrf.UART0.PSEL.TXD.Set(uint32(tx)) + nrf.UART0.PSEL.RXD.Set(uint32(rx)) +} + +func (i2c *I2C) setPins(scl, sda Pin) { + i2c.Bus.PSEL.SCL.Set(uint32(scl)) + i2c.Bus.PSEL.SDA.Set(uint32(sda)) +} + +// PWM +var ( + PWM0 = &PWM{PWM: nrf.PWM0} + PWM1 = &PWM{PWM: nrf.PWM1} + PWM2 = &PWM{PWM: nrf.PWM2} + PWM3 = &PWM{PWM: nrf.PWM3} +) + +// PDM represents a PDM device +type PDM struct { + device *nrf.PDM_Type + defaultBuffer int16 +} + +// Configure is intended to set up the PDM interface prior to use. +func (pdm *PDM) Configure(config PDMConfig) error { + if config.DIN == 0 { + return errors.New("No DIN pin provided in configuration") + } + + if config.CLK == 0 { + return errors.New("No CLK pin provided in configuration") + } + + config.DIN.Configure(PinConfig{Mode: PinInput}) + config.CLK.Configure(PinConfig{Mode: PinOutput}) + pdm.device = nrf.PDM + pdm.device.PSEL.DIN.Set(uint32(config.DIN)) + pdm.device.PSEL.CLK.Set(uint32(config.CLK)) + pdm.device.PDMCLKCTRL.Set(nrf.PDM_PDMCLKCTRL_FREQ_Default) + pdm.device.RATIO.Set(nrf.PDM_RATIO_RATIO_Ratio64) + pdm.device.GAINL.Set(nrf.PDM_GAINL_GAINL_DefaultGain) + pdm.device.GAINR.Set(nrf.PDM_GAINR_GAINR_DefaultGain) + pdm.device.ENABLE.Set(nrf.PDM_ENABLE_ENABLE_Enabled) + + if config.Stereo { + pdm.device.MODE.Set(nrf.PDM_MODE_OPERATION_Stereo | nrf.PDM_MODE_EDGE_LeftRising) + } else { + pdm.device.MODE.Set(nrf.PDM_MODE_OPERATION_Mono | nrf.PDM_MODE_EDGE_LeftRising) + } + + pdm.device.SAMPLE.SetPTR(uint32(uintptr(unsafe.Pointer(&pdm.defaultBuffer)))) + pdm.device.SAMPLE.SetMAXCNT_BUFFSIZE(1) + pdm.device.SetTASKS_START(1) + return nil +} + +// Read stores a set of samples in the given target buffer. +func (pdm *PDM) Read(buf []int16) (uint32, error) { + pdm.device.SAMPLE.SetPTR(uint32(uintptr(unsafe.Pointer(&buf[0])))) + pdm.device.SAMPLE.MAXCNT.Set(uint32(len(buf))) + pdm.device.EVENTS_STARTED.Set(0) + + // Step 1: wait for new sampling to start for target buffer + for !pdm.device.EVENTS_STARTED.HasBits(nrf.PDM_EVENTS_STARTED_EVENTS_STARTED) { + } + pdm.device.EVENTS_END.Set(0) + + // Step 2: swap out buffers for next recording so we don't continue to + // write to the target buffer + pdm.device.EVENTS_STARTED.Set(0) + pdm.device.SAMPLE.SetPTR(uint32(uintptr(unsafe.Pointer(&pdm.defaultBuffer)))) + pdm.device.SAMPLE.MAXCNT.Set(1) + + // Step 3: wait for original event to end + for pdm.device.EVENTS_END.HasBits(nrf.PDM_EVENTS_STOPPED_EVENTS_STOPPED) { + } + + // Step 4: wait for default buffer to start recording before proceeding + // otherwise we see the contents of target buffer change later + for !pdm.device.EVENTS_STARTED.HasBits(nrf.PDM_EVENTS_STARTED_EVENTS_STARTED) { + } + + return uint32(len(buf)), nil +} + +const eraseBlockSizeValue = 4096 + +func eraseBlockSize() int64 { + return eraseBlockSizeValue +} diff --git a/emb/machine/machine_nrf52840_enter_bootloader.go b/emb/machine/machine_nrf52840_enter_bootloader.go new file mode 100644 index 00000000..d24152a5 --- /dev/null +++ b/emb/machine/machine_nrf52840_enter_bootloader.go @@ -0,0 +1,38 @@ +//go:build nrf52840 + +package machine + +import ( + "device/arm" + "device/nrf" +) + +const ( + dfuMagicSerialOnlyReset = 0x4e + dfuMagicUF2Reset = 0x57 + dfuMagicOTAReset = 0xA8 +) + +// EnterSerialBootloader resets the chip into the serial bootloader. After +// reset, it can be flashed using serial/nrfutil. +func EnterSerialBootloader() { + arm.DisableInterrupts() + nrf.POWER.GPREGRET.Set(dfuMagicSerialOnlyReset) + arm.SystemReset() +} + +// EnterUF2Bootloader resets the chip into the UF2 bootloader. After reset, it +// can be flashed via nrfutil or by copying a UF2 file to the mass storage device +func EnterUF2Bootloader() { + arm.DisableInterrupts() + nrf.POWER.GPREGRET.Set(dfuMagicUF2Reset) + arm.SystemReset() +} + +// EnterOTABootloader resets the chip into the bootloader so that it can be +// flashed via an OTA update +func EnterOTABootloader() { + arm.DisableInterrupts() + nrf.POWER.GPREGRET.Set(dfuMagicOTAReset) + arm.SystemReset() +} diff --git a/emb/machine/machine_nrf52840_lfxtal_false.go b/emb/machine/machine_nrf52840_lfxtal_false.go new file mode 100644 index 00000000..ba38a326 --- /dev/null +++ b/emb/machine/machine_nrf52840_lfxtal_false.go @@ -0,0 +1,5 @@ +//go:build nrf52840 && nrf52840_lfxtal_false + +package machine + +const HasLowFrequencyCrystal = false diff --git a/emb/machine/machine_nrf52840_lfxtal_true.go b/emb/machine/machine_nrf52840_lfxtal_true.go new file mode 100644 index 00000000..4bef553b --- /dev/null +++ b/emb/machine/machine_nrf52840_lfxtal_true.go @@ -0,0 +1,5 @@ +//go:build nrf52840 && ((nrf52840_generic && !nrf52840_lfxtal_false) || nrf52840_lfxtal_true) + +package machine + +const HasLowFrequencyCrystal = true diff --git a/emb/machine/machine_nrf52840_simulator.go b/emb/machine/machine_nrf52840_simulator.go new file mode 100644 index 00000000..f04f05c4 --- /dev/null +++ b/emb/machine/machine_nrf52840_simulator.go @@ -0,0 +1,60 @@ +//go:build !baremetal && (bluemicro840 || circuitplay_bluefruit || clue_alpha || feather_nrf52840_sense || feather_nrf52840 || itsybitsy_nrf52840 || mdbt50qrx || nano_33_ble || nicenano || nrf52840_mdk || particle_3rd_gen || pca10056 || pca10059 || rak4631 || reelboard || xiao_ble) + +// Simulator support for nrf52840 based boards. + +package machine + +// Channel values below are nil, so that they get filled in on the first use. +// This is the same as what happens on baremetal. + +var PWM0 = &timerType{ + instance: 0, + frequency: 16e6, + bits: 15, + prescalers: []int{1, 2, 4, 8, 16, 32, 64, 128}, + channelPins: [][]Pin{ + nil, // channel 0 + nil, // channel 1 + nil, // channel 2 + nil, // channel 3 + }, +} + +var PWM1 = &timerType{ + instance: 1, + frequency: 16e6, + bits: 15, + prescalers: []int{1, 2, 4, 8, 16, 32, 64, 128}, + channelPins: [][]Pin{ + nil, // channel 0 + nil, // channel 1 + nil, // channel 2 + nil, // channel 3 + }, +} + +var PWM2 = &timerType{ + instance: 2, + frequency: 16e6, + bits: 15, + prescalers: []int{1, 2, 4, 8, 16, 32, 64, 128}, + channelPins: [][]Pin{ + nil, // channel 0 + nil, // channel 1 + nil, // channel 2 + nil, // channel 3 + }, +} + +var PWM3 = &timerType{ + instance: 3, + frequency: 16e6, + bits: 15, + prescalers: []int{1, 2, 4, 8, 16, 32, 64, 128}, + channelPins: [][]Pin{ + nil, // channel 0 + nil, // channel 1 + nil, // channel 2 + nil, // channel 3 + }, +} diff --git a/emb/machine/machine_nrf52840_usb.go b/emb/machine/machine_nrf52840_usb.go new file mode 100644 index 00000000..1fa46945 --- /dev/null +++ b/emb/machine/machine_nrf52840_usb.go @@ -0,0 +1,381 @@ +//go:build nrf52840 + +package machine + +import ( + "device/arm" + "device/nrf" + "machine/usb" + "runtime/interrupt" + "runtime/volatile" + "unsafe" +) + +const NumberOfUSBEndpoints = 8 + +var ( + sendOnEP0DATADONE struct { + ptr *byte + count int + offset int + } + epinen uint32 + epouten uint32 + easyDMABusy volatile.Register8 + + endPoints = []uint32{ + usb.CONTROL_ENDPOINT: usb.ENDPOINT_TYPE_CONTROL, + usb.CDC_ENDPOINT_ACM: (usb.ENDPOINT_TYPE_INTERRUPT | usb.EndpointIn), + usb.CDC_ENDPOINT_OUT: (usb.ENDPOINT_TYPE_BULK | usb.EndpointOut), + usb.CDC_ENDPOINT_IN: (usb.ENDPOINT_TYPE_BULK | usb.EndpointIn), + usb.HID_ENDPOINT_IN: (usb.ENDPOINT_TYPE_DISABLE), // Interrupt In + usb.HID_ENDPOINT_OUT: (usb.ENDPOINT_TYPE_DISABLE), // Interrupt Out + usb.MIDI_ENDPOINT_IN: (usb.ENDPOINT_TYPE_DISABLE), // Bulk In + usb.MIDI_ENDPOINT_OUT: (usb.ENDPOINT_TYPE_DISABLE), // Bulk Out + } +) + +// enterCriticalSection is used to protect access to easyDMA - only one thing +// can be done with it at a time +func enterCriticalSection() { + waitForEasyDMA() + easyDMABusy.SetBits(1) +} + +func waitForEasyDMA() { + for easyDMABusy.HasBits(1) { + arm.Asm("wfi") + } +} + +func exitCriticalSection() { + easyDMABusy.ClearBits(1) +} + +// Configure the USB peripheral. The config is here for compatibility with the UART interface. +func (dev *USBDevice) Configure(config UARTConfig) { + if dev.initcomplete { + return + } + + state := interrupt.Disable() + defer interrupt.Restore(state) + + nrf.USBD.USBPULLUP.Set(0) + + // Enable IRQ. Make sure this is higher than the SWI2 interrupt handler so + // that it is possible to print to the console from a BLE interrupt. You + // shouldn't generally do that but it is useful for debugging and panic + // logging. + intr := interrupt.New(nrf.IRQ_USBD, handleUSBIRQ) + intr.SetPriority(0x40) // interrupt priority 2 (lower number means more important) + intr.Enable() + + // enable interrupt for end of reset and start of frame + nrf.USBD.INTEN.Set(nrf.USBD_INTENSET_USBEVENT) + + // errata 187 + // https://infocenter.nordicsemi.com/topic/errata_nRF52840_EngB/ERR/nRF52840/EngineeringB/latest/anomaly_840_187.html + (*volatile.Register32)(unsafe.Pointer(uintptr(0x4006EC00))).Set(0x00009375) + (*volatile.Register32)(unsafe.Pointer(uintptr(0x4006ED14))).Set(0x00000003) + (*volatile.Register32)(unsafe.Pointer(uintptr(0x4006EC00))).Set(0x00009375) + + // enable USB + nrf.USBD.ENABLE.Set(1) + + timeout := 300000 + for !nrf.USBD.EVENTCAUSE.HasBits(nrf.USBD_EVENTCAUSE_READY) { + timeout-- + if timeout == 0 { + return + } + } + nrf.USBD.EVENTCAUSE.ClearBits(nrf.USBD_EVENTCAUSE_READY) + + // errata 187 + (*volatile.Register32)(unsafe.Pointer(uintptr(0x4006EC00))).Set(0x00009375) + (*volatile.Register32)(unsafe.Pointer(uintptr(0x4006ED14))).Set(0x00000000) + (*volatile.Register32)(unsafe.Pointer(uintptr(0x4006EC00))).Set(0x00009375) + + dev.initcomplete = true +} + +func handleUSBIRQ(interrupt.Interrupt) { + if nrf.USBD.EVENTS_SOF.Get() == 1 { + nrf.USBD.EVENTS_SOF.Set(0) + + // if you want to blink LED showing traffic, this would be the place... + } + + // USBD ready event + if nrf.USBD.EVENTS_USBEVENT.Get() == 1 { + nrf.USBD.EVENTS_USBEVENT.Set(0) + if (nrf.USBD.EVENTCAUSE.Get() & nrf.USBD_EVENTCAUSE_READY) > 0 { + + // Configure control endpoint + initEndpoint(0, usb.ENDPOINT_TYPE_CONTROL) + nrf.USBD.USBPULLUP.Set(1) + + usbConfiguration = 0 + } + nrf.USBD.EVENTCAUSE.Set(0) + } + + if nrf.USBD.EVENTS_EP0DATADONE.Get() == 1 { + // done sending packet - either need to send another or enter status stage + nrf.USBD.EVENTS_EP0DATADONE.Set(0) + if sendOnEP0DATADONE.ptr != nil { + // previous data was too big for one packet, so send a second + ptr := sendOnEP0DATADONE.ptr + count := sendOnEP0DATADONE.count + if count > usb.EndpointPacketSize { + sendOnEP0DATADONE.offset += usb.EndpointPacketSize + sendOnEP0DATADONE.ptr = &udd_ep_control_cache_buffer[sendOnEP0DATADONE.offset] + count = usb.EndpointPacketSize + } + sendOnEP0DATADONE.count -= count + sendViaEPIn( + 0, + ptr, + count, + ) + + // clear, so we know we're done + if sendOnEP0DATADONE.count == 0 { + sendOnEP0DATADONE.ptr = nil + sendOnEP0DATADONE.offset = 0 + } + } else { + // no more data, so set status stage + SendZlp() // nrf.USBD.TASKS_EP0STATUS.Set(1) + } + return + } + + // Endpoint 0 Setup interrupt + if nrf.USBD.EVENTS_EP0SETUP.Get() == 1 { + // ack setup received + nrf.USBD.EVENTS_EP0SETUP.Set(0) + + // parse setup + setup := parseUSBSetupRegisters() + + ok := false + if (setup.BmRequestType & usb.REQUEST_TYPE) == usb.REQUEST_STANDARD { + // Standard Requests + ok = handleStandardSetup(setup) + } else { + // Class Interface Requests + if setup.WIndex < uint16(len(usbSetupHandler)) && usbSetupHandler[setup.WIndex] != nil { + ok = usbSetupHandler[setup.WIndex](setup) + } + } + + if !ok { + // Stall endpoint + nrf.USBD.TASKS_EP0STALL.Set(1) + } + } + + // Now the actual transfer handlers, ignore endpoint number 0 (setup) + if nrf.USBD.EVENTS_EPDATA.Get() > 0 { + nrf.USBD.EVENTS_EPDATA.Set(0) + epDataStatus := nrf.USBD.EPDATASTATUS.Get() + nrf.USBD.EPDATASTATUS.Set(epDataStatus) + var i uint32 + for i = 1; i < uint32(len(endPoints)); i++ { + // Check if endpoint has a pending interrupt + inDataDone := epDataStatus&(nrf.USBD_EPDATASTATUS_EPIN1<<(i-1)) > 0 + outDataDone := epDataStatus&(nrf.USBD_EPDATASTATUS_EPOUT1<<(i-1)) > 0 + if inDataDone { + if usbTxHandler[i] != nil { + usbTxHandler[i]() + } + } else if outDataDone { + enterCriticalSection() + nrf.USBD.EPOUT[i].PTR.Set(uint32(uintptr(unsafe.Pointer(&udd_ep_out_cache_buffer[i])))) + count := nrf.USBD.SIZE.EPOUT[i].Get() + nrf.USBD.EPOUT[i].MAXCNT.Set(count) + nrf.USBD.TASKS_STARTEPOUT[i].Set(1) + } + } + } + + // ENDEPOUT[n] events + for i := 0; i < len(endPoints); i++ { + if nrf.USBD.EVENTS_ENDEPOUT[i].Get() > 0 { + nrf.USBD.EVENTS_ENDEPOUT[i].Set(0) + buf := handleEndpointRx(uint32(i)) + if usbRxHandler[i] == nil || usbRxHandler[i](buf) { + AckUsbOutTransfer(uint32(i)) + } + exitCriticalSection() + } + } +} + +func parseUSBSetupRegisters() usb.Setup { + return usb.Setup{ + BmRequestType: uint8(nrf.USBD.BMREQUESTTYPE.Get()), + BRequest: uint8(nrf.USBD.BREQUEST.Get()), + WValueL: uint8(nrf.USBD.WVALUEL.Get()), + WValueH: uint8(nrf.USBD.WVALUEH.Get()), + WIndex: uint16((nrf.USBD.WINDEXH.Get() << 8) | nrf.USBD.WINDEXL.Get()), + WLength: uint16(((nrf.USBD.WLENGTHH.Get() & 0xff) << 8) | (nrf.USBD.WLENGTHL.Get() & 0xff)), + } +} + +func initEndpoint(ep, config uint32) { + switch config { + case usb.ENDPOINT_TYPE_INTERRUPT | usb.EndpointIn: + enableEPIn(ep) + + case usb.ENDPOINT_TYPE_BULK | usb.EndpointOut: + nrf.USBD.INTENSET.Set(nrf.USBD_INTENSET_ENDEPOUT0 << ep) + nrf.USBD.SIZE.EPOUT[ep].Set(0) + enableEPOut(ep) + + case usb.ENDPOINT_TYPE_INTERRUPT | usb.EndpointOut: + nrf.USBD.INTENSET.Set(nrf.USBD_INTENSET_ENDEPOUT0 << ep) + nrf.USBD.SIZE.EPOUT[ep].Set(0) + enableEPOut(ep) + + case usb.ENDPOINT_TYPE_BULK | usb.EndpointIn: + enableEPIn(ep) + + case usb.ENDPOINT_TYPE_CONTROL: + enableEPIn(0) + enableEPOut(0) + nrf.USBD.INTENSET.Set(nrf.USBD_INTENSET_ENDEPOUT0 | + nrf.USBD_INTENSET_EP0SETUP | + nrf.USBD_INTENSET_EPDATA | + nrf.USBD_INTENSET_EP0DATADONE) + SendZlp() // nrf.USBD.TASKS_EP0STATUS.Set(1) + } +} + +// SendUSBInPacket sends a packet for USBHID (interrupt in / bulk in). +func SendUSBInPacket(ep uint32, data []byte) bool { + sendUSBPacket(ep, data, 0) + + // clear transfer complete flag + nrf.USBD.INTENCLR.Set(nrf.USBD_INTENCLR_ENDEPOUT0 << 4) + + return true +} + +// Prevent file size increases: https://github.com/tinygo-org/tinygo/pull/998 +// +//go:noinline +func sendUSBPacket(ep uint32, data []byte, maxsize uint16) { + count := len(data) + if 0 < int(maxsize) && int(maxsize) < count { + count = int(maxsize) + } + + if ep == 0 { + copy(udd_ep_control_cache_buffer[:], data[:count]) + if count > usb.EndpointPacketSize { + sendOnEP0DATADONE.offset = usb.EndpointPacketSize + sendOnEP0DATADONE.ptr = &udd_ep_control_cache_buffer[sendOnEP0DATADONE.offset] + sendOnEP0DATADONE.count = count - usb.EndpointPacketSize + count = usb.EndpointPacketSize + } + sendViaEPIn( + ep, + &udd_ep_control_cache_buffer[0], + count, + ) + } else { + copy(udd_ep_in_cache_buffer[ep][:], data[:count]) + sendViaEPIn( + ep, + &udd_ep_in_cache_buffer[ep][0], + count, + ) + } +} + +func handleEndpointRx(ep uint32) []byte { + // get data + count := int(nrf.USBD.EPOUT[ep].AMOUNT.Get()) + + return udd_ep_out_cache_buffer[ep][:count] +} + +// AckUsbOutTransfer is called to acknowledge the completion of a USB OUT transfer. +func AckUsbOutTransfer(ep uint32) { + // set ready for next data + nrf.USBD.SIZE.EPOUT[ep].Set(0) +} + +func SendZlp() { + nrf.USBD.TASKS_EP0STATUS.Set(1) +} + +func sendViaEPIn(ep uint32, ptr *byte, count int) { + nrf.USBD.EPIN[ep].PTR.Set( + uint32(uintptr(unsafe.Pointer(ptr))), + ) + nrf.USBD.EPIN[ep].MAXCNT.Set(uint32(count)) + nrf.USBD.TASKS_STARTEPIN[ep].Set(1) +} + +func enableEPOut(ep uint32) { + epouten = epouten | (nrf.USBD_EPOUTEN_OUT0 << ep) + nrf.USBD.EPOUTEN.Set(epouten) +} + +func enableEPIn(ep uint32) { + epinen = epinen | (nrf.USBD_EPINEN_IN0 << ep) + nrf.USBD.EPINEN.Set(epinen) +} + +func handleUSBSetAddress(setup usb.Setup) bool { + // nrf USBD handles this + return true +} + +func ReceiveUSBControlPacket() ([cdcLineInfoSize]byte, error) { + var b [cdcLineInfoSize]byte + + nrf.USBD.TASKS_EP0RCVOUT.Set(1) + + nrf.USBD.EPOUT[0].PTR.Set(uint32(uintptr(unsafe.Pointer(&udd_ep_out_cache_buffer[0])))) + nrf.USBD.EPOUT[0].MAXCNT.Set(64) + + timeout := 300000 + count := 0 + for { + if nrf.USBD.EVENTS_EP0DATADONE.Get() == 1 { + nrf.USBD.EVENTS_EP0DATADONE.Set(0) + count = int(nrf.USBD.SIZE.EPOUT[0].Get()) + nrf.USBD.TASKS_STARTEPOUT[0].Set(1) + break + } + timeout-- + if timeout == 0 { + return b, ErrUSBReadTimeout + } + } + + timeout = 300000 + for { + if nrf.USBD.EVENTS_ENDEPOUT[0].Get() == 1 { + nrf.USBD.EVENTS_ENDEPOUT[0].Set(0) + break + } + + timeout-- + if timeout == 0 { + return b, ErrUSBReadTimeout + } + } + + nrf.USBD.TASKS_EP0STATUS.Set(1) + nrf.USBD.TASKS_EP0RCVOUT.Set(0) + + copy(b[:7], udd_ep_out_cache_buffer[0][:count]) + + return b, nil +} diff --git a/emb/machine/machine_nrf52840_usb_reset_bossa.go b/emb/machine/machine_nrf52840_usb_reset_bossa.go new file mode 100644 index 00000000..b790eb72 --- /dev/null +++ b/emb/machine/machine_nrf52840_usb_reset_bossa.go @@ -0,0 +1,9 @@ +//go:build nrf52840 && nrf52840_reset_bossa + +package machine + +// EnterBootloader resets the chip into the serial bootloader. After +// reset, it can be flashed using serial/nrfutil. +func EnterBootloader() { + EnterSerialBootloader() +} diff --git a/emb/machine/machine_nrf52840_usb_reset_none.go b/emb/machine/machine_nrf52840_usb_reset_none.go new file mode 100644 index 00000000..e3900e4d --- /dev/null +++ b/emb/machine/machine_nrf52840_usb_reset_none.go @@ -0,0 +1,8 @@ +//go:build nrf52840 && !nrf52840_reset_uf2 && !nrf52840_reset_bossa + +package machine + +// EnterBootloader resets the chip into the serial bootloader. +func EnterBootloader() { + // skip +} diff --git a/emb/machine/machine_nrf52840_usb_reset_uf2.go b/emb/machine/machine_nrf52840_usb_reset_uf2.go new file mode 100644 index 00000000..6573ebbe --- /dev/null +++ b/emb/machine/machine_nrf52840_usb_reset_uf2.go @@ -0,0 +1,9 @@ +//go:build nrf52840 && nrf52840_reset_uf2 + +package machine + +// EnterBootloader resets the chip into the UF2 bootloader. After reset, it +// can be flashed via nrfutil or by copying a UF2 file to the mass storage device +func EnterBootloader() { + EnterUF2Bootloader() +} diff --git a/emb/machine/machine_nrf528xx.go b/emb/machine/machine_nrf528xx.go new file mode 100644 index 00000000..f8937aec --- /dev/null +++ b/emb/machine/machine_nrf528xx.go @@ -0,0 +1,259 @@ +//go:build nrf52840 || nrf52833 + +package machine + +import ( + "device/nrf" + "unsafe" +) + +// I2C on the NRF528xx. +type I2C struct { + Bus *nrf.TWIM_Type // Called Bus to align with Bus field in nrf51 + BusT *nrf.TWIS_Type + mode I2CMode +} + +// There are 2 I2C interfaces on the NRF. +var ( + I2C0 = &I2C{Bus: nrf.TWIM0, BusT: nrf.TWIS0} + I2C1 = &I2C{Bus: nrf.TWIM1, BusT: nrf.TWIS1} +) + +func (i2c *I2C) enableAsController() { + i2c.Bus.ENABLE.Set(nrf.TWIM_ENABLE_ENABLE_Enabled) +} + +func (i2c *I2C) enableAsTarget() { + i2c.BusT.ENABLE.Set(nrf.TWIS_ENABLE_ENABLE_Enabled) +} + +func (i2c *I2C) disable() { + i2c.Bus.ENABLE.Set(0) +} + +// Tx does a single I2C transaction at the specified address (when in controller mode). +// +// It clocks out the given address, writes the bytes in w, reads back len(r) +// bytes and stores them in r, and generates a stop condition on the bus. +func (i2c *I2C) Tx(addr uint16, w, r []byte) (err error) { + i2c.Bus.ADDRESS.Set(uint32(addr)) + + i2c.Bus.EVENTS_STOPPED.Set(0) + i2c.Bus.EVENTS_ERROR.Set(0) + i2c.Bus.EVENTS_RXSTARTED.Set(0) + i2c.Bus.EVENTS_TXSTARTED.Set(0) + i2c.Bus.EVENTS_LASTRX.Set(0) + i2c.Bus.EVENTS_LASTTX.Set(0) + i2c.Bus.EVENTS_SUSPENDED.Set(0) + + // Configure for a single shot to perform both write and read (as applicable) + if len(w) != 0 { + i2c.Bus.TXD.PTR.Set(uint32(uintptr(unsafe.Pointer(&w[0])))) + i2c.Bus.TXD.MAXCNT.Set(uint32(len(w))) + + // If no read, immediately signal stop after TX + if len(r) == 0 { + i2c.Bus.SHORTS.Set(nrf.TWIM_SHORTS_LASTTX_STOP) + } + } + if len(r) != 0 { + i2c.Bus.RXD.PTR.Set(uint32(uintptr(unsafe.Pointer(&r[0])))) + i2c.Bus.RXD.MAXCNT.Set(uint32(len(r))) + + // Auto-start Rx after Tx and Stop after Rx + i2c.Bus.SHORTS.Set(nrf.TWIM_SHORTS_LASTTX_STARTRX | nrf.TWIM_SHORTS_LASTRX_STOP) + } + + // Fire the transaction + i2c.Bus.TASKS_RESUME.Set(1) + if len(w) != 0 { + i2c.Bus.TASKS_STARTTX.Set(1) + } else if len(r) != 0 { + i2c.Bus.TASKS_STARTRX.Set(1) + } + + // Wait until transaction stopped to ensure buffers fully processed + for i2c.Bus.EVENTS_STOPPED.Get() == 0 { + // Allow scheduler to run + gosched() + + // Handle errors by ensuring STOP sent on bus + if i2c.Bus.EVENTS_ERROR.Get() != 0 { + if i2c.Bus.EVENTS_STOPPED.Get() == 0 { + // STOP cannot be sent during SUSPEND + i2c.Bus.TASKS_RESUME.Set(1) + i2c.Bus.TASKS_STOP.Set(1) + } + err = twiCError(i2c.Bus.ERRORSRC.Get()) + } + } + + return +} + +// Listen starts listening for I2C requests sent to specified address +// +// addr is the address to listen to +func (i2c *I2C) Listen(addr uint8) error { + i2c.BusT.ADDRESS[0].Set(uint32(addr)) + i2c.BusT.CONFIG.Set(nrf.TWIS_CONFIG_ADDRESS0_Enabled) + + i2c.BusT.EVENTS_STOPPED.Set(0) + i2c.BusT.EVENTS_ERROR.Set(0) + i2c.BusT.EVENTS_RXSTARTED.Set(0) + i2c.BusT.EVENTS_TXSTARTED.Set(0) + i2c.BusT.EVENTS_WRITE.Set(0) + i2c.BusT.EVENTS_READ.Set(0) + + return nil +} + +// WaitForEvent blocks the current go-routine until an I2C event is received (when in Target mode). +// +// The passed buffer will be populated for receive events, with the number of bytes +// received returned in count. For other event types, buf is not modified and a count +// of zero is returned. +// +// For request events, the caller MUST call `Reply` to avoid hanging the i2c bus indefinitely. +func (i2c *I2C) WaitForEvent(buf []byte) (evt I2CTargetEvent, count int, err error) { + i2c.BusT.RXD.PTR.Set(uint32(uintptr(unsafe.Pointer(&buf[0])))) + i2c.BusT.RXD.MAXCNT.Set(uint32(len(buf))) + + i2c.BusT.TASKS_PREPARERX.Set(nrf.TWIS_TASKS_PREPARERX_TASKS_PREPARERX_Trigger) + + i2c.Bus.TASKS_RESUME.Set(1) + + for i2c.BusT.EVENTS_STOPPED.Get() == 0 && + i2c.BusT.EVENTS_READ.Get() == 0 { + gosched() + + if i2c.BusT.EVENTS_ERROR.Get() != 0 { + i2c.BusT.EVENTS_ERROR.Set(0) + return I2CReceive, 0, twisError(i2c.BusT.ERRORSRC.Get()) + } + } + + count = 0 + evt = I2CFinish + err = nil + + if i2c.BusT.EVENTS_WRITE.Get() != 0 { + i2c.BusT.EVENTS_WRITE.Set(0) + + // Data was sent to this target. We've waited for + // READ or STOPPED event, so transmission should be + // complete. + count = int(i2c.BusT.RXD.AMOUNT.Get()) + evt = I2CReceive + } else if i2c.BusT.EVENTS_READ.Get() != 0 { + i2c.BusT.EVENTS_READ.Set(0) + + // Data is requested from this target, hw will stretch + // the controller's clock until there is a reply to + // send + evt = I2CRequest + } else if i2c.BusT.EVENTS_STOPPED.Get() != 0 { + i2c.BusT.EVENTS_STOPPED.Set(0) + evt = I2CFinish + } + + return +} + +// Reply supplies the response data the controller. +func (i2c *I2C) Reply(buf []byte) error { + i2c.BusT.TXD.PTR.Set(uint32(uintptr(unsafe.Pointer(&buf[0])))) + i2c.BusT.TXD.MAXCNT.Set(uint32(len(buf))) + + i2c.BusT.EVENTS_STOPPED.Set(0) + + // Trigger Tx + i2c.BusT.TASKS_PREPARETX.Set(nrf.TWIS_TASKS_PREPARETX_TASKS_PREPARETX_Trigger) + + // Block, waiting for Tx to complete + for i2c.BusT.EVENTS_STOPPED.Get() == 0 { + gosched() + + if i2c.BusT.EVENTS_ERROR.Get() != 0 { + return twisError(i2c.BusT.ERRORSRC.Get()) + } + } + + i2c.BusT.EVENTS_STOPPED.Set(0) + + return nil +} + +// twiCError converts an I2C controller error to Go +func twiCError(val uint32) error { + if val == 0 { + return nil + } else if val&nrf.TWIM_ERRORSRC_OVERRUN_Msk == nrf.TWIM_ERRORSRC_OVERRUN { + return errI2CBusError + } else if val&nrf.TWIM_ERRORSRC_ANACK_Msk == nrf.TWIM_ERRORSRC_ANACK { + return errI2CAckExpected + } else if val&nrf.TWIM_ERRORSRC_DNACK_Msk == nrf.TWIM_ERRORSRC_DNACK { + return errI2CAckExpected + } + + return errI2CBusError +} + +// twisError converts an I2C target error to Go +func twisError(val uint32) error { + if val == 0 { + return nil + } else if val&nrf.TWIS_ERRORSRC_OVERFLOW_Msk == nrf.TWIS_ERRORSRC_OVERFLOW { + return errI2COverflow + } else if val&nrf.TWIS_ERRORSRC_DNACK_Msk == nrf.TWIS_ERRORSRC_DNACK { + return errI2CAckExpected + } else if val&nrf.TWIS_ERRORSRC_OVERREAD_Msk == nrf.TWIS_ERRORSRC_OVERREAD { + return errI2COverread + } + + return errI2CBusError +} + +var ( + Watchdog = &watchdogImpl{} +) + +const ( + // WatchdogMaxTimeout in milliseconds (approx 36h) + WatchdogMaxTimeout = (0xffffffff * 1000) / 32768 +) + +type watchdogImpl struct { +} + +// Configure the watchdog. +// +// This method should not be called after the watchdog is started and on +// some platforms attempting to reconfigure after starting the watchdog +// is explicitly forbidden / will not work. +func (wd *watchdogImpl) Configure(config WatchdogConfig) error { + // 32.768kHz counter + crv := int32((int64(config.TimeoutMillis) * 32768) / 1000) + nrf.WDT.CRV.Set(uint32(crv)) + + // One source + nrf.WDT.RREN.Set(0x1) + + // Run during sleep + nrf.WDT.CONFIG.Set(nrf.WDT_CONFIG_SLEEP_Run) + + return nil +} + +// Starts the watchdog. +func (wd *watchdogImpl) Start() error { + nrf.WDT.TASKS_START.Set(nrf.WDT_TASKS_START_TASKS_START) + return nil +} + +// Update the watchdog, indicating that `source` is healthy. +func (wd *watchdogImpl) Update() { + // 0x6E524635 = magic value from datasheet + nrf.WDT.RR[0].Set(0x6E524635) +} diff --git a/emb/machine/machine_nrf52xxx.go b/emb/machine/machine_nrf52xxx.go new file mode 100644 index 00000000..a582a7aa --- /dev/null +++ b/emb/machine/machine_nrf52xxx.go @@ -0,0 +1,546 @@ +//go:build nrf52 || nrf52840 || nrf52833 + +package machine + +import ( + "device/nrf" + "runtime/volatile" + "unsafe" +) + +func CPUFrequency() uint32 { + return 64000000 +} + +// InitADC initializes the registers needed for ADC. +func InitADC() { + // Enable ADC. + // The ADC does not consume a noticeable amount of current by being enabled. + nrf.SAADC.ENABLE.Set(nrf.SAADC_ENABLE_ENABLE_Enabled << nrf.SAADC_ENABLE_ENABLE_Pos) +} + +// Configure configures an ADC pin to be able to read analog data. +// Reference voltage can be 150, 300, 600, 1200, 1800, 2400, 3000(default), 3600 mV +// Resolution can be 8, 10, 12(default), 14 bits +// SampleTime will be ceiled to 3(default), 5, 10, 15, 20 or 40(max) µS respectively +// Samples can be 1(default), 2, 4, 8, 16, 32, 64, 128, 256 samples +func (a *ADC) Configure(config ADCConfig) { + var configVal uint32 = nrf.SAADC_CH_CONFIG_RESP_Bypass<= 8000000: + freq = nrf.SPIM_FREQUENCY_FREQUENCY_M8 + case config.Frequency >= 4000000: + freq = nrf.SPIM_FREQUENCY_FREQUENCY_M4 + case config.Frequency >= 2000000: + freq = nrf.SPIM_FREQUENCY_FREQUENCY_M2 + case config.Frequency >= 1000000: + freq = nrf.SPIM_FREQUENCY_FREQUENCY_M1 + case config.Frequency >= 500000: + freq = nrf.SPIM_FREQUENCY_FREQUENCY_K500 + case config.Frequency >= 250000: + freq = nrf.SPIM_FREQUENCY_FREQUENCY_K250 + default: // below 250kHz, default to the lowest speed available + freq = nrf.SPIM_FREQUENCY_FREQUENCY_K125 + } + spi.Bus.FREQUENCY.Set(freq) + + var conf uint32 + + // set bit transfer order + if config.LSBFirst { + conf = (nrf.SPIM_CONFIG_ORDER_LsbFirst << nrf.SPIM_CONFIG_ORDER_Pos) + } + + // set mode + switch config.Mode { + case 0: + conf &^= (nrf.SPIM_CONFIG_CPOL_ActiveHigh << nrf.SPIM_CONFIG_CPOL_Pos) + conf &^= (nrf.SPIM_CONFIG_CPHA_Leading << nrf.SPIM_CONFIG_CPHA_Pos) + case 1: + conf &^= (nrf.SPIM_CONFIG_CPOL_ActiveHigh << nrf.SPIM_CONFIG_CPOL_Pos) + conf |= (nrf.SPIM_CONFIG_CPHA_Trailing << nrf.SPIM_CONFIG_CPHA_Pos) + case 2: + conf |= (nrf.SPIM_CONFIG_CPOL_ActiveLow << nrf.SPIM_CONFIG_CPOL_Pos) + conf &^= (nrf.SPIM_CONFIG_CPHA_Leading << nrf.SPIM_CONFIG_CPHA_Pos) + case 3: + conf |= (nrf.SPIM_CONFIG_CPOL_ActiveLow << nrf.SPIM_CONFIG_CPOL_Pos) + conf |= (nrf.SPIM_CONFIG_CPHA_Trailing << nrf.SPIM_CONFIG_CPHA_Pos) + default: // to mode + conf &^= (nrf.SPIM_CONFIG_CPOL_ActiveHigh << nrf.SPIM_CONFIG_CPOL_Pos) + conf &^= (nrf.SPIM_CONFIG_CPHA_Leading << nrf.SPIM_CONFIG_CPHA_Pos) + } + spi.Bus.CONFIG.Set(conf) + + // set pins + if config.SCK == 0 && config.SDO == 0 && config.SDI == 0 { + config.SCK = SPI0_SCK_PIN + config.SDO = SPI0_SDO_PIN + config.SDI = SPI0_SDI_PIN + } + spi.Bus.PSEL.SCK.Set(uint32(config.SCK)) + spi.Bus.PSEL.MOSI.Set(uint32(config.SDO)) + spi.Bus.PSEL.MISO.Set(uint32(config.SDI)) + + // Re-enable bus now that it is configured. + spi.Bus.ENABLE.Set(nrf.SPIM_ENABLE_ENABLE_Enabled) + + return nil +} + +// Transfer writes/reads a single byte using the SPI interface. +func (spi *SPI) Transfer(w byte) (byte, error) { + buf := spi.buf[:] + buf[0] = w + err := spi.Tx(buf[:], buf[:]) + return buf[0], err +} + +// Tx handles read/write operation for SPI interface. Since SPI is a synchronous +// write/read interface, there must always be the same number of bytes written +// as bytes read. Therefore, if the number of bytes don't match it will be +// padded until they fit: if len(w) > len(r) the extra bytes received will be +// dropped and if len(w) < len(r) extra 0 bytes will be sent. +func (spi *SPI) Tx(w, r []byte) error { + // Unfortunately the hardware (on the nrf52832) only supports up to 255 + // bytes in the buffers, so if either w or r is longer than that the + // transfer needs to be broken up in pieces. + // The nrf52840 supports far larger buffers however, which isn't yet + // supported. + for len(r) != 0 || len(w) != 0 { + // Prepare the SPI transfer: set the DMA pointers and lengths. + // read buffer + nr := uint32(len(r)) + if nr > 0 { + if nr > 255 { + nr = 255 + } + spi.Bus.RXD.PTR.Set(uint32(uintptr(unsafe.Pointer(&r[0])))) + r = r[nr:] + } + spi.Bus.RXD.MAXCNT.Set(nr) + + // write buffer + nw := uint32(len(w)) + if nw > 0 { + if nw > 255 { + nw = 255 + } + spi.Bus.TXD.PTR.Set(uint32(uintptr(unsafe.Pointer(&w[0])))) + w = w[nw:] + } + spi.Bus.TXD.MAXCNT.Set(nw) + + // Do the transfer. + // Note: this can be improved by not waiting until the transfer is + // finished if the transfer is send-only (a common case). + spi.Bus.TASKS_START.Set(1) + for spi.Bus.EVENTS_END.Get() == 0 { + } + spi.Bus.EVENTS_END.Set(0) + } + + return nil +} + +// PWM is one PWM peripheral, which consists of a counter and multiple output +// channels (that can be connected to actual pins). You can set the frequency +// using SetPeriod, but only for all the channels in this PWM peripheral at +// once. +type PWM struct { + PWM *nrf.PWM_Type + + channelValues [4]volatile.Register16 +} + +// Configure enables and configures this PWM. +// On the nRF52 series, the maximum period is around 0.26s. +func (pwm *PWM) Configure(config PWMConfig) error { + // Enable the peripheral. + pwm.PWM.ENABLE.Set(nrf.PWM_ENABLE_ENABLE_Enabled << nrf.PWM_ENABLE_ENABLE_Pos) + + // Use up counting only. TODO: allow configuring as up-and-down. + pwm.PWM.MODE.Set(nrf.PWM_MODE_UPDOWN_Up << nrf.PWM_MODE_UPDOWN_Pos) + + // Indicate there are four channels that each have a different value. + pwm.PWM.DECODER.Set(nrf.PWM_DECODER_LOAD_Individual< maxTop { + return ErrPWMPeriodTooLong + } + } + pwm.PWM.COUNTERTOP.Set(uint32(top)) + + // Apparently this is needed to apply the new COUNTERTOP. + pwm.PWM.TASKS_SEQSTART[0].Set(1) + + return nil +} + +// Top returns the current counter top, for use in duty cycle calculation. It +// will only change with a call to Configure or SetPeriod, otherwise it is +// constant. +// +// The value returned here is hardware dependent. In general, it's best to treat +// it as an opaque value that can be divided by some number and passed to +// pwm.Set (see pwm.Set for more information). +func (pwm *PWM) Top() uint32 { + return pwm.PWM.COUNTERTOP.Get() +} + +// Channel returns a PWM channel for the given pin. +func (pwm *PWM) Channel(pin Pin) (uint8, error) { + config := uint32(pin) + for ch := uint8(0); ch < 4; ch++ { + channelConfig := pwm.PWM.PSEL.OUT[ch].Get() + if channelConfig == 0xffffffff { + // Unused channel. Configure it. + pwm.PWM.PSEL.OUT[ch].Set(config) + // Configure the pin (required by the reference manual). + pin.Configure(PinConfig{Mode: PinOutput}) + // Set channel to zero and non-inverting. + pwm.channelValues[ch].Set(0x8000) + return ch, nil + } else if channelConfig == config { + // This channel is already configured for this pin. + return ch, nil + } + } + + // All four pins are already in use with other pins. + return 0, ErrInvalidOutputPin +} + +// SetInverting sets whether to invert the output of this channel. +// Without inverting, a 25% duty cycle would mean the output is high for 25% of +// the time and low for the rest. Inverting flips the output as if a NOT gate +// was placed at the output, meaning that the output would be 25% low and 75% +// high with a duty cycle of 25%. +func (pwm *PWM) SetInverting(channel uint8, inverting bool) { + ptr := &pwm.channelValues[channel] + if inverting { + ptr.Set(ptr.Get() &^ 0x8000) + } else { + ptr.Set(ptr.Get() | 0x8000) + } +} + +// Set updates the channel value. This is used to control the channel duty +// cycle. For example, to set it to a 25% duty cycle, use: +// +// ch.Set(ch.Top() / 4) +// +// ch.Set(0) will set the output to low and ch.Set(ch.Top()) will set the output +// to high, assuming the output isn't inverted. +func (pwm *PWM) Set(channel uint8, value uint32) { + // Update the channel value while retaining the polarity bit. + ptr := &pwm.channelValues[channel] + ptr.Set(ptr.Get()&0x8000 | uint16(value)&0x7fff) + + // Start the PWM, if it isn't already running. + pwm.PWM.TASKS_SEQSTART[0].Set(1) +} diff --git a/emb/machine/machine_nrf5x.go b/emb/machine/machine_nrf5x.go new file mode 100644 index 00000000..4c731036 --- /dev/null +++ b/emb/machine/machine_nrf5x.go @@ -0,0 +1,126 @@ +//go:build nrf51 || nrf52 + +package machine + +import "device/nrf" + +// I2C on the NRF51 and NRF52. +type I2C struct { + Bus *nrf.TWI_Type + mode I2CMode +} + +// There are 2 I2C interfaces on the NRF. +var ( + I2C0 = &I2C{Bus: nrf.TWI0} + I2C1 = &I2C{Bus: nrf.TWI1} +) + +func (i2c *I2C) enableAsController() { + i2c.Bus.ENABLE.Set(nrf.TWI_ENABLE_ENABLE_Enabled) +} + +func (i2c *I2C) enableAsTarget() { + // Not supported on this hardware +} + +func (i2c *I2C) disable() { + i2c.Bus.ENABLE.Set(0) +} + +// Tx does a single I2C transaction at the specified address. +// It clocks out the given address, writes the bytes in w, reads back len(r) +// bytes and stores them in r, and generates a stop condition on the bus. +func (i2c *I2C) Tx(addr uint16, w, r []byte) (err error) { + + // Tricky stop condition. + // After reads, the stop condition is generated implicitly with a shortcut. + // After writes not followed by reads and in the case of errors, stop must be generated explicitly. + + i2c.Bus.ADDRESS.Set(uint32(addr)) + + if len(w) != 0 { + i2c.Bus.TASKS_STARTTX.Set(1) // start transmission for writing + for _, b := range w { + if err = i2c.writeByte(b); err != nil { + i2c.signalStop() + return + } + } + } + + if len(r) != 0 { + // To trigger suspend task when a byte is received + i2c.Bus.SHORTS.Set(nrf.TWI_SHORTS_BB_SUSPEND) + i2c.Bus.TASKS_STARTRX.Set(1) // re-start transmission for reading + for i := range r { // read each char + if i+1 == len(r) { + // To trigger stop task when last byte is received, set before resume task. + i2c.Bus.SHORTS.Set(nrf.TWI_SHORTS_BB_STOP) + } + if i > 0 { + i2c.Bus.TASKS_RESUME.Set(1) // re-start transmission for reading + } + if r[i], err = i2c.readByte(); err != nil { + i2c.Bus.SHORTS.Set(nrf.TWI_SHORTS_BB_SUSPEND_Disabled) + i2c.signalStop() + return + } + } + i2c.Bus.SHORTS.Set(nrf.TWI_SHORTS_BB_SUSPEND_Disabled) + } + + if len(r) == 0 { + // Stop the I2C transaction after the write. + err = i2c.signalStop() + } else { + // The last byte read has already stopped the transaction, via + // TWI_SHORTS_BB_STOP. But we still need to wait until we receive the + // STOPPED event. + tries := 0 + for i2c.Bus.EVENTS_STOPPED.Get() == 0 { + tries++ + if tries >= i2cTimeout { + return errI2CSignalStopTimeout + } + } + i2c.Bus.EVENTS_STOPPED.Set(0) + } + + return +} + +// writeByte writes a single byte to the I2C bus and waits for confirmation. +func (i2c *I2C) writeByte(data byte) error { + tries := 0 + i2c.Bus.TXD.Set(uint32(data)) + for i2c.Bus.EVENTS_TXDSENT.Get() == 0 { + if e := i2c.Bus.EVENTS_ERROR.Get(); e != 0 { + i2c.Bus.EVENTS_ERROR.Set(0) + return errI2CBusError + } + tries++ + if tries >= i2cTimeout { + return errI2CWriteTimeout + } + } + i2c.Bus.EVENTS_TXDSENT.Set(0) + return nil +} + +// readByte reads a single byte from the I2C bus when it is ready. +func (i2c *I2C) readByte() (byte, error) { + tries := 0 + for i2c.Bus.EVENTS_RXDREADY.Get() == 0 { + if e := i2c.Bus.EVENTS_ERROR.Get(); e != 0 { + i2c.Bus.EVENTS_ERROR.Set(0) + return 0, errI2CBusError + } + tries++ + if tries >= i2cTimeout { + return 0, errI2CReadTimeout + } + } + i2c.Bus.EVENTS_RXDREADY.Set(0) + return byte(i2c.Bus.RXD.Get()), nil +} diff --git a/emb/machine/machine_nrf_bare.go b/emb/machine/machine_nrf_bare.go new file mode 100644 index 00000000..b94886ed --- /dev/null +++ b/emb/machine/machine_nrf_bare.go @@ -0,0 +1,9 @@ +//go:build nrf && !softdevice + +package machine + +// GetRNG returns 32 bits of non-deterministic random data based on internal thermal noise. +// According to Nordic's documentation, the random output is suitable for cryptographic purposes. +func GetRNG() (ret uint32, err error) { + return getRNG() +} diff --git a/emb/machine/machine_nrf_sd.go b/emb/machine/machine_nrf_sd.go new file mode 100644 index 00000000..b816e62e --- /dev/null +++ b/emb/machine/machine_nrf_sd.go @@ -0,0 +1,59 @@ +//go:build nrf && softdevice + +package machine + +import ( + "device/arm" + "device/nrf" + + "errors" +) + +// avoid a heap allocation in GetRNG. +var ( + softdeviceEnabled uint8 + bytesAvailable uint8 + buf [4]uint8 + + errNoSoftDeviceSupport = errors.New("rng: softdevice not supported on this device") + errNotEnoughRandomData = errors.New("rng: not enough random data available") +) + +// GetRNG returns 32 bits of non-deterministic random data based on internal thermal noise. +// According to Nordic's documentation, the random output is suitable for cryptographic purposes. +func GetRNG() (ret uint32, err error) { + // First check whether the SoftDevice is enabled. + // sd_rand_application_bytes_available_get cannot be called when the SoftDevice is not enabled. + arm.SVCall1(0x12, &softdeviceEnabled) // sd_softdevice_is_enabled + + if softdeviceEnabled == 0 { + return getRNG() + } + + // call into the SoftDevice to get random data bytes available + switch nrf.Device { + case "nrf51": + // sd_rand_application_bytes_available_get: SOC_SVC_BASE_NOT_AVAILABLE + 4 + arm.SVCall1(0x2B+4, &bytesAvailable) + case "nrf52", "nrf52840", "nrf52833": + // sd_rand_application_bytes_available_get: SOC_SVC_BASE_NOT_AVAILABLE + 4 + arm.SVCall1(0x2C+4, &bytesAvailable) + default: + return 0, errNoSoftDeviceSupport + } + + if bytesAvailable < 4 { + return 0, errNotEnoughRandomData + } + + switch nrf.Device { + case "nrf51": + // sd_rand_application_vector_get: SOC_SVC_BASE_NOT_AVAILABLE + 5 + arm.SVCall2(0x2B+5, &buf, 4) + case "nrf52", "nrf52840", "nrf52833": + // sd_rand_application_vector_get: SOC_SVC_BASE_NOT_AVAILABLE + 5 + arm.SVCall2(0x2C+5, &buf, 4) + } + + return uint32(buf[0]) | uint32(buf[1])<<8 | uint32(buf[2])<<16 | uint32(buf[3])<<24, nil +} diff --git a/emb/machine/machine_nxpmk66f18.go b/emb/machine/machine_nxpmk66f18.go new file mode 100644 index 00000000..5dd54d97 --- /dev/null +++ b/emb/machine/machine_nxpmk66f18.go @@ -0,0 +1,291 @@ +// Derivative work of Teensyduino Core Library +// http://www.pjrc.com/teensy/ +// Copyright (c) 2017 PJRC.COM, LLC. +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// 1. The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// 2. If the Software is incorporated into a build system that allows +// selection among a list of target devices, then similar target +// devices manufactured by PJRC.COM must be included in the list of +// target devices and selectable in the same manner. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS +// BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +//go:build nxp && mk66f18 + +package machine + +import ( + "device/nxp" + "runtime/volatile" + "unsafe" +) + +const deviceName = nxp.Device + +const ( + PinInput PinMode = iota + PinInputPullup + PinInputPulldown + PinOutput + PinOutputOpenDrain + PinDisable +) + +// Deprecated: use PinInputPullup and PinInputPulldown instead. +const ( + PinInputPullUp = PinInputPullup + PinInputPullDown = PinInputPulldown +) + +const ( + PA00 Pin = iota + PA01 + PA02 + PA03 + PA04 + PA05 + PA06 + PA07 + PA08 + PA09 + PA10 + PA11 + PA12 + PA13 + PA14 + PA15 + PA16 + PA17 + PA18 + PA19 + PA20 + PA21 + PA22 + PA23 + PA24 + PA25 + PA26 + PA27 + PA28 + PA29 +) + +const ( + PB00 Pin = iota + 32 + PB01 + PB02 + PB03 + PB04 + PB05 + PB06 + PB07 + PB08 + PB09 + PB10 + PB11 + _ + _ + _ + _ + PB16 + PB17 + PB18 + PB19 + PB20 + PB21 + PB22 + PB23 +) + +const ( + PC00 Pin = iota + 64 + PC01 + PC02 + PC03 + PC04 + PC05 + PC06 + PC07 + PC08 + PC09 + PC10 + PC11 + PC12 + PC13 + PC14 + PC15 + PC16 + PC17 + PC18 + PC19 +) + +const ( + PD00 Pin = iota + 96 + PD01 + PD02 + PD03 + PD04 + PD05 + PD06 + PD07 + PD08 + PD09 + PD10 + PD11 + PD12 + PD13 + PD14 + PD15 +) + +const ( + PE00 Pin = iota + 128 + PE01 + PE02 + PE03 + PE04 + PE05 + PE06 + PE07 + PE08 + PE09 + PE10 + PE11 + PE12 + PE13 + PE14 + PE15 + PE16 + PE17 + PE18 + PE19 + PE20 + PE21 + PE22 + PE23 + PE24 + PE25 + PE26 + PE27 + PE28 +) + +//go:inline +func (p Pin) reg() (*nxp.GPIO_Type, *volatile.Register32, uint8) { + var gpio *nxp.GPIO_Type + var pcr *nxp.PORT_Type + + switch p / 32 { + case 0: + gpio, pcr = nxp.GPIOA, nxp.PORTA + case 1: + gpio, pcr = nxp.GPIOB, nxp.PORTB + case 2: + gpio, pcr = nxp.GPIOC, nxp.PORTC + case 3: + gpio, pcr = nxp.GPIOD, nxp.PORTD + case 5: + gpio, pcr = nxp.GPIOE, nxp.PORTE + default: + panic("invalid pin number") + } + + return gpio, &(*[32]volatile.Register32)(unsafe.Pointer(pcr))[p%32], uint8(p % 32) +} + +// Configure this pin with the given configuration. +func (p Pin) Configure(config PinConfig) { + gpio, pcr, pos := p.reg() + + switch config.Mode { + case PinOutput: + gpio.PDDR.SetBits(1 << pos) + pcr.Set((1 << nxp.PORT_PCR0_MUX_Pos) | nxp.PORT_PCR0_SRE | nxp.PORT_PCR0_DSE) + + case PinOutputOpenDrain: + gpio.PDDR.SetBits(1 << pos) + pcr.Set((1 << nxp.PORT_PCR0_MUX_Pos) | nxp.PORT_PCR0_SRE | nxp.PORT_PCR0_DSE | nxp.PORT_PCR0_ODE) + + case PinInput: + gpio.PDDR.ClearBits(1 << pos) + pcr.Set((1 << nxp.PORT_PCR0_MUX_Pos)) + + case PinInputPullup: + gpio.PDDR.ClearBits(1 << pos) + pcr.Set((1 << nxp.PORT_PCR0_MUX_Pos) | nxp.PORT_PCR0_PE | nxp.PORT_PCR0_PS) + + case PinInputPulldown: + gpio.PDDR.ClearBits(1 << pos) + pcr.Set((1 << nxp.PORT_PCR0_MUX_Pos) | nxp.PORT_PCR0_PE) + + case PinDisable: + gpio.PDDR.ClearBits(1 << pos) + pcr.Set((0 << nxp.PORT_PCR0_MUX_Pos)) + } +} + +// Set changes the value of the GPIO pin. The pin must be configured as output. +func (p Pin) Set(value bool) { + gpio, _, pos := p.reg() + if value { + gpio.PSOR.Set(1 << pos) + } else { + gpio.PCOR.Set(1 << pos) + } +} + +// Get returns the current value of a GPIO pin. +func (p Pin) Get() bool { + gpio, _, pos := p.reg() + return gpio.PDIR.HasBits(1 << pos) +} + +func (p Pin) Control() *volatile.Register32 { + _, pcr, _ := p.reg() + return pcr +} + +func (p Pin) Fast() FastPin { + gpio, _, pos := p.reg() + return FastPin{ + PDOR: gpio.PDOR.Bit(pos), + PSOR: gpio.PSOR.Bit(pos), + PCOR: gpio.PCOR.Bit(pos), + PTOR: gpio.PTOR.Bit(pos), + PDIR: gpio.PDIR.Bit(pos), + PDDR: gpio.PDDR.Bit(pos), + } +} + +type FastPin struct { + PDOR *volatile.BitRegister + PSOR *volatile.BitRegister + PCOR *volatile.BitRegister + PTOR *volatile.BitRegister + PDIR *volatile.BitRegister + PDDR *volatile.BitRegister +} + +func (p FastPin) Set() { p.PSOR.Set(true) } +func (p FastPin) Clear() { p.PCOR.Set(true) } +func (p FastPin) Toggle() { p.PTOR.Set(true) } +func (p FastPin) Write(v bool) { p.PDOR.Set(v) } +func (p FastPin) Read() bool { return p.PDIR.Get() } diff --git a/emb/machine/machine_nxpmk66f18_uart.go b/emb/machine/machine_nxpmk66f18_uart.go new file mode 100644 index 00000000..a14d18f5 --- /dev/null +++ b/emb/machine/machine_nxpmk66f18_uart.go @@ -0,0 +1,309 @@ +// Derivative work of Teensyduino Core Library +// http://www.pjrc.com/teensy/ +// Copyright (c) 2017 PJRC.COM, LLC. +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// 1. The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// 2. If the Software is incorporated into a build system that allows +// selection among a list of target devices, then similar target +// devices manufactured by PJRC.COM must be included in the list of +// target devices and selectable in the same manner. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS +// BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +//go:build nxp && mk66f18 + +package machine + +import ( + "device/arm" + "device/nxp" + "errors" + "runtime/interrupt" + "runtime/volatile" + + _ "unsafe" // for go:linkname +) + +const ( + uartC2Enable = nxp.UART_C2_TE | nxp.UART_C2_RE | nxp.UART_C2_RIE | nxp.UART_C2_ILIE + uartC2TXActive = uartC2Enable | nxp.UART_C2_TIE + uartC2TXCompleting = uartC2Enable | nxp.UART_C2_TCIE + uartC2TXInactive = uartC2Enable + + uartIRQPriority = 64 + + // determined from UARTx_PFIFO + uartRXFIFODepth = 8 + uartTXFIFODepth = 8 +) + +var ( + ErrNotImplemented = errors.New("device has not been implemented") + ErrNotConfigured = errors.New("device has not been configured") +) + +// PutcharUART writes a byte to the UART synchronously, without using interrupts +// or calling the scheduler +func PutcharUART(u *UART, c byte) { + // ensure the UART has been configured + if !u.SCGC.HasBits(u.SCGCMask) { + u.configure(UARTConfig{}, false) + } + + for u.TCFIFO.Get() > 0 { + // busy wait + } + u.D.Set(c) + u.C2.Set(uartC2TXActive) +} + +// PollUART manually checks a UART status and calls the ISR. This should only be +// called by runtime.abort. +func PollUART(u *UART) { + if u.SCGC.HasBits(u.SCGCMask) { + u.handleStatusInterrupt(u.Interrupt) + } +} + +type UART struct { + *nxp.UART_Type + SCGC *volatile.Register32 + SCGCMask uint32 + + DefaultRX Pin + DefaultTX Pin + + // state + Buffer RingBuffer // RX Buffer + TXBuffer RingBuffer + Configured bool + Transmitting volatile.Register8 + Interrupt interrupt.Interrupt +} + +var ( + UART0 = &_UART0 + UART1 = &_UART1 + UART2 = &_UART2 + UART3 = &_UART3 + UART4 = &_UART4 + _UART0 = UART{UART_Type: nxp.UART0, SCGC: &nxp.SIM.SCGC4, SCGCMask: nxp.SIM_SCGC4_UART0, DefaultRX: defaultUART0RX, DefaultTX: defaultUART0TX} + _UART1 = UART{UART_Type: nxp.UART1, SCGC: &nxp.SIM.SCGC4, SCGCMask: nxp.SIM_SCGC4_UART1, DefaultRX: defaultUART1RX, DefaultTX: defaultUART1TX} + _UART2 = UART{UART_Type: nxp.UART2, SCGC: &nxp.SIM.SCGC4, SCGCMask: nxp.SIM_SCGC4_UART2, DefaultRX: defaultUART2RX, DefaultTX: defaultUART2TX} + _UART3 = UART{UART_Type: nxp.UART3, SCGC: &nxp.SIM.SCGC4, SCGCMask: nxp.SIM_SCGC4_UART3, DefaultRX: defaultUART3RX, DefaultTX: defaultUART3TX} + _UART4 = UART{UART_Type: nxp.UART4, SCGC: &nxp.SIM.SCGC1, SCGCMask: nxp.SIM_SCGC1_UART4, DefaultRX: defaultUART4RX, DefaultTX: defaultUART4TX} +) + +func init() { + UART0.Interrupt = interrupt.New(nxp.IRQ_UART0_RX_TX, _UART0.handleStatusInterrupt) + UART1.Interrupt = interrupt.New(nxp.IRQ_UART1_RX_TX, _UART1.handleStatusInterrupt) + UART2.Interrupt = interrupt.New(nxp.IRQ_UART2_RX_TX, _UART2.handleStatusInterrupt) + UART3.Interrupt = interrupt.New(nxp.IRQ_UART3_RX_TX, _UART3.handleStatusInterrupt) + UART4.Interrupt = interrupt.New(nxp.IRQ_UART4_RX_TX, _UART4.handleStatusInterrupt) +} + +// Configure the UART. +func (u *UART) Configure(config UARTConfig) { + u.configure(config, true) +} + +func (u *UART) configure(config UARTConfig, canSched bool) { + // from: serial_begin + + if !u.Configured { + u.Transmitting.Set(0) + + // turn on the clock + u.SCGC.Set(u.SCGCMask) + + // configure pins + u.DefaultRX.Control().Set(nxp.PORT_PCR0_PE | nxp.PORT_PCR0_PS | nxp.PORT_PCR0_PFE | (3 << nxp.PORT_PCR0_MUX_Pos)) + u.DefaultTX.Control().Set(nxp.PORT_PCR0_DSE | nxp.PORT_PCR0_SRE | (3 << nxp.PORT_PCR0_MUX_Pos)) + u.C1.Set(nxp.UART_C1_ILT) + } + + // default to 115200 baud + if config.BaudRate == 0 { + config.BaudRate = 115200 + } + + // copied from teensy core's BAUD2DIV macro + divisor := ((CPUFrequency() * 2) + (config.BaudRate >> 1)) / config.BaudRate + if divisor < 32 { + divisor = 32 + } + + if u.Configured { + // don't change baud rate mid transmit + if canSched { + u.Flush() + } else { + for u.Transmitting.Get() != 0 { + // busy wait flush + } + } + } + + // set the divisor + u.BDH.Set(uint8((divisor >> 13) & 0x1F)) + u.BDL.Set(uint8((divisor >> 5) & 0xFF)) + u.C4.Set(uint8(divisor & 0x1F)) + + if !u.Configured { + u.Configured = true + + u.C1.Set(nxp.UART_C1_ILT) + + // configure TX and RX watermark + u.TWFIFO.Set(2) // causes bit TDRE of S1 to set + u.RWFIFO.Set(4) // causes bit RDRF of S1 to set + + // enable FIFOs + u.PFIFO.Set(nxp.UART_PFIFO_TXFE | nxp.UART_PFIFO_RXFE) + + // setup interrupts + u.C2.Set(uartC2TXInactive) + u.Interrupt.SetPriority(uartIRQPriority) + u.Interrupt.Enable() + } +} + +func (u *UART) Disable() { + // from: serial_end + + // check if the device has been enabled already + if !u.SCGC.HasBits(u.SCGCMask) { + return + } + + u.Flush() + + u.Interrupt.Disable() + u.C2.Set(0) + + // reconfigure pin + u.DefaultRX.Configure(PinConfig{Mode: PinInputPullup}) + u.DefaultTX.Configure(PinConfig{Mode: PinInputPullup}) + + // clear flags + u.S1.Get() + u.D.Get() + u.Buffer.Clear() +} + +func (u *UART) Flush() { + for u.Transmitting.Get() != 0 { + gosched() + } +} + +func (u *UART) handleStatusInterrupt(interrupt.Interrupt) { + // from: uart0_status_isr + + // receive + if u.S1.HasBits(nxp.UART_S1_RDRF | nxp.UART_S1_IDLE) { + intrs := arm.DisableInterrupts() + avail := u.RCFIFO.Get() + if avail == 0 { + // The only way to clear the IDLE interrupt flag is + // to read the data register. But reading with no + // data causes a FIFO underrun, which causes the + // FIFO to return corrupted data. If anyone from + // Freescale reads this, what a poor design! There + // write should be a write-1-to-clear for IDLE. + u.D.Get() + // flushing the fifo recovers from the underrun, + // but there's a possible race condition where a + // new character could be received between reading + // RCFIFO == 0 and flushing the FIFO. To minimize + // the chance, interrupts are disabled so a higher + // priority interrupt (hopefully) doesn't delay. + // TODO: change this to disabling the IDLE interrupt + // which won't be simple, since we already manage + // which transmit interrupts are enabled. + u.CFIFO.Set(nxp.UART_CFIFO_RXFLUSH) + arm.EnableInterrupts(intrs) + + } else { + arm.EnableInterrupts(intrs) + + for { + u.Buffer.Put(u.D.Get()) + avail-- + if avail <= 0 { + break + } + } + } + } + + // transmit + if u.C2.HasBits(nxp.UART_C2_TIE) && u.S1.HasBits(nxp.UART_S1_TDRE) { + data := make([]byte, 0, uartTXFIFODepth) + avail := uartTXFIFODepth - u.TCFIFO.Get() + + // get avail bytes from ring buffer + for len(data) < int(avail) { + if b, ok := u.TXBuffer.Get(); ok { + data = append(data, b) + } else { + break + } + } + + // write data to FIFO + l := len(data) + for i, b := range data { + if i == l-1 { + // only clear TDRE on last write, per the manual + u.S1.Get() + } + u.D.Set(b) + } + + // if FIFO still has room, disable TIE, enable TCIE + if u.S1.HasBits(nxp.UART_S1_TDRE) { + u.C2.Set(uartC2TXCompleting) + } + } + + // transmit complete + if u.C2.HasBits(nxp.UART_C2_TCIE) && u.S1.HasBits(nxp.UART_S1_TC) { + u.Transmitting.Set(0) + u.C2.Set(uartC2TXInactive) + } +} + +// WriteByte writes a byte of data to the UART. +func (u *UART) writeByte(c byte) error { + if !u.Configured { + return ErrNotConfigured + } + + for !u.TXBuffer.Put(c) { + gosched() + } + + u.Transmitting.Set(1) + u.C2.Set(uartC2TXActive) + return nil +} + +func (uart *UART) flush() {} diff --git a/emb/machine/machine_rp2.go b/emb/machine/machine_rp2.go new file mode 100644 index 00000000..9afefa53 --- /dev/null +++ b/emb/machine/machine_rp2.go @@ -0,0 +1,137 @@ +//go:build rp2040 || rp2350 + +package machine + +import ( + "device/rp" + "runtime/interrupt" + "runtime/volatile" + "unsafe" +) + +const deviceName = rp.Device + +const ( + // Number of spin locks available + // Note: On RP2350, most spinlocks are unusable due to Errata 2 + _NUMSPINLOCKS = 32 + _PICO_SPINLOCK_ID_IRQ = 9 + // is48Pin notes whether the chip is RP2040 with 32 pins or RP2350 with 48 pins. + is48Pin = _NUMBANK0_GPIOS == 48 +) + +// UART on the RP2040 +var ( + UART0 = &_UART0 + _UART0 = UART{ + Buffer: NewRingBuffer(), + Bus: rp.UART0, + } + + UART1 = &_UART1 + _UART1 = UART{ + Buffer: NewRingBuffer(), + Bus: rp.UART1, + } +) + +func init() { + UART0.Interrupt = interrupt.New(rp.IRQ_UART0_IRQ, _UART0.handleInterrupt) + UART1.Interrupt = interrupt.New(rp.IRQ_UART1_IRQ, _UART1.handleInterrupt) +} + +//go:linkname machineInit runtime.machineInit +func machineInit() { + // Reset all peripherals to put system into a known state, + // except for QSPI pads and the XIP IO bank, as this is fatal if running from flash + // and the PLLs, as this is fatal if clock muxing has not been reset on this boot + // and USB, syscfg, as this disturbs USB-to-SWD on core 1 + bits := ^uint32(initDontReset) + resetBlock(bits) + + // Remove reset from peripherals which are clocked only by clkSys and + // clkRef. Other peripherals stay in reset until we've configured clocks. + bits = ^uint32(initUnreset) + unresetBlockWait(bits) + + clocks.init() + + // Peripheral clocks should now all be running + unresetBlockWait(RESETS_RESET_Msk) +} + +//go:linkname ticks runtime.machineTicks +func ticks() uint64 { + return timer.timeElapsed() +} + +//go:linkname lightSleep runtime.machineLightSleep +func lightSleep(ticks uint64) { + timer.lightSleep(ticks) +} + +// CurrentCore returns the core number the call was made from. +func CurrentCore() int { + return int(rp.SIO.CPUID.Get()) +} + +// NumCores returns number of cores available on the device. +func NumCores() int { return 2 } + +// ChipVersion returns the version of the chip. 1 is returned for B0 and B1 +// chip. +func ChipVersion() uint8 { + const ( + SYSINFO_BASE = 0x40000000 + SYSINFO_CHIP_ID_OFFSET = 0x00000000 + SYSINFO_CHIP_ID_REVISION_BITS = 0xf0000000 + SYSINFO_CHIP_ID_REVISION_LSB = 28 + ) + + // First register of sysinfo is chip id + chipID := *(*uint32)(unsafe.Pointer(uintptr(SYSINFO_BASE + SYSINFO_CHIP_ID_OFFSET))) + // Version 1 == B0/B1 + version := (chipID & SYSINFO_CHIP_ID_REVISION_BITS) >> SYSINFO_CHIP_ID_REVISION_LSB + return uint8(version) +} + +// Single DMA channel. See rp.DMA_Type. +type dmaChannel struct { + READ_ADDR volatile.Register32 + WRITE_ADDR volatile.Register32 + TRANS_COUNT volatile.Register32 + CTRL_TRIG volatile.Register32 + _ [12]volatile.Register32 // aliases +} + +// Static assignment of DMA channels to peripherals. +// Allocating them statically is good enough for now. If lots of peripherals use +// DMA, these might need to be assigned at runtime. +const ( + spi0DMAChannel = iota + spi1DMAChannel +) + +// DMA channels usable on the RP2040. +var dmaChannels = (*[12 + 4*rp2350ExtraReg]dmaChannel)(unsafe.Pointer(rp.DMA)) + +//go:inline +func boolToBit(a bool) uint32 { + if a { + return 1 + } + return 0 +} + +//go:inline +func u32max(a, b uint32) uint32 { + if a > b { + return a + } + return b +} + +//go:inline +func isReservedI2CAddr(addr uint8) bool { + return (addr&0x78) == 0 || (addr&0x78) == 0x78 +} diff --git a/emb/machine/machine_rp2040_rom.go b/emb/machine/machine_rp2040_rom.go new file mode 100644 index 00000000..5541e2a9 --- /dev/null +++ b/emb/machine/machine_rp2040_rom.go @@ -0,0 +1,254 @@ +//go:build tinygo && rp2040 + +package machine + +import ( + "runtime/interrupt" + "unsafe" +) + +/* +// https://github.com/raspberrypi/pico-sdk +// src/rp2_common/pico_bootrom/include/pico/bootrom.h + +#define ROM_FUNC_POPCOUNT32 ROM_TABLE_CODE('P', '3') +#define ROM_FUNC_REVERSE32 ROM_TABLE_CODE('R', '3') +#define ROM_FUNC_CLZ32 ROM_TABLE_CODE('L', '3') +#define ROM_FUNC_CTZ32 ROM_TABLE_CODE('T', '3') +#define ROM_FUNC_MEMSET ROM_TABLE_CODE('M', 'S') +#define ROM_FUNC_MEMSET4 ROM_TABLE_CODE('S', '4') +#define ROM_FUNC_MEMCPY ROM_TABLE_CODE('M', 'C') +#define ROM_FUNC_MEMCPY44 ROM_TABLE_CODE('C', '4') +#define ROM_FUNC_RESET_USB_BOOT ROM_TABLE_CODE('U', 'B') +#define ROM_FUNC_CONNECT_INTERNAL_FLASH ROM_TABLE_CODE('I', 'F') +#define ROM_FUNC_FLASH_EXIT_XIP ROM_TABLE_CODE('E', 'X') +#define ROM_FUNC_FLASH_RANGE_ERASE ROM_TABLE_CODE('R', 'E') +#define ROM_FUNC_FLASH_RANGE_PROGRAM ROM_TABLE_CODE('R', 'P') +#define ROM_FUNC_FLASH_FLUSH_CACHE ROM_TABLE_CODE('F', 'C') +#define ROM_FUNC_FLASH_ENTER_CMD_XIP ROM_TABLE_CODE('C', 'X') + +#define ROM_TABLE_CODE(c1, c2) ((c1) | ((c2) << 8)) + +typedef unsigned char uint8_t; +typedef unsigned short uint16_t; +typedef unsigned long uint32_t; +typedef unsigned long size_t; +typedef unsigned long uintptr_t; + +#define false 0 +#define true 1 +typedef int bool; + +#define ram_func __attribute__((section(".ramfuncs"),noinline)) + +typedef void *(*rom_table_lookup_fn)(uint16_t *table, uint32_t code); +typedef void __attribute__((noreturn)) (*rom_reset_usb_boot_fn)(uint32_t, uint32_t); +typedef void (*flash_init_boot2_copyout_fn)(void); +typedef void (*flash_enable_xip_via_boot2_fn)(void); +typedef void (*flash_exit_xip_fn)(void); +typedef void (*flash_flush_cache_fn)(void); +typedef void (*flash_connect_internal_fn)(void); +typedef void (*flash_range_erase_fn)(uint32_t, size_t, uint32_t, uint16_t); +typedef void (*flash_range_program_fn)(uint32_t, const uint8_t*, size_t); + +static inline __attribute__((always_inline)) void __compiler_memory_barrier(void) { + __asm__ volatile ("" : : : "memory"); +} + +#define rom_hword_as_ptr(rom_address) (void *)(uintptr_t)(*(uint16_t *)(uintptr_t)(rom_address)) + +void *rom_func_lookup(uint32_t code) { + rom_table_lookup_fn rom_table_lookup = (rom_table_lookup_fn) rom_hword_as_ptr(0x18); + uint16_t *func_table = (uint16_t *) rom_hword_as_ptr(0x14); + return rom_table_lookup(func_table, code); +} + +void reset_usb_boot(uint32_t usb_activity_gpio_pin_mask, uint32_t disable_interface_mask) { + rom_reset_usb_boot_fn func = (rom_reset_usb_boot_fn) rom_func_lookup(ROM_FUNC_RESET_USB_BOOT); + func(usb_activity_gpio_pin_mask, disable_interface_mask); +} + +#define FLASH_BLOCK_ERASE_CMD 0xd8 + +#define FLASH_PAGE_SIZE (1u << 8) +#define FLASH_SECTOR_SIZE (1u << 12) +#define FLASH_BLOCK_SIZE (1u << 16) + +#define BOOT2_SIZE_WORDS 64 +#define XIP_BASE 0x10000000 + +static uint32_t boot2_copyout[BOOT2_SIZE_WORDS]; +static bool boot2_copyout_valid = false; + +static ram_func void flash_init_boot2_copyout() { + if (boot2_copyout_valid) + return; + for (int i = 0; i < BOOT2_SIZE_WORDS; ++i) + boot2_copyout[i] = ((uint32_t *)XIP_BASE)[i]; + __compiler_memory_barrier(); + boot2_copyout_valid = true; +} + +static ram_func void flash_enable_xip_via_boot2() { + ((void (*)(void))boot2_copyout+1)(); +} + +#define IO_QSPI_BASE 0x40018000 +#define IO_QSPI_GPIO_QSPI_SS_CTRL_OUTOVER_BITS 0x00000300 +#define IO_QSPI_GPIO_QSPI_SS_CTRL_OUTOVER_MSB 9 +#define IO_QSPI_GPIO_QSPI_SS_CTRL_OUTOVER_LSB 8 +#define IO_QSPI_GPIO_QSPI_SS_CTRL_OUTOVER_VALUE_LOW 0x2 +#define IO_QSPI_GPIO_QSPI_SS_CTRL_OUTOVER_VALUE_HIGH 0x3 + +#define XIP_SSI_BASE 0x18000000 +#define ssi_hw ((ssi_hw_t *)XIP_SSI_BASE) +#define SSI_SR_OFFSET 0x00000028 +#define SSI_DR0_OFFSET 0x00000060 +#define SSI_SR_TFNF_BITS 0x00000002 +#define SSI_SR_RFNE_BITS 0x00000008 + +void ram_func flash_cs_force(bool high) { + uint32_t field_val = high ? + IO_QSPI_GPIO_QSPI_SS_CTRL_OUTOVER_VALUE_HIGH : + IO_QSPI_GPIO_QSPI_SS_CTRL_OUTOVER_VALUE_LOW; + + // &ioqspi_hw->io[1].ctrl + uint32_t *addr = (uint32_t*)(IO_QSPI_BASE + (1 * 8) + 4); + + *addr = ((*addr) & !IO_QSPI_GPIO_QSPI_SS_CTRL_OUTOVER_BITS) + | (field_val << IO_QSPI_GPIO_QSPI_SS_CTRL_OUTOVER_LSB); + +} + +// See https://github.com/raspberrypi/pico-sdk/blob/master/src/rp2_common/hardware_flash/flash.c#L86 +void ram_func flash_range_write(uint32_t offset, const uint8_t *data, size_t count) +{ + flash_range_program_fn flash_range_program_func = (flash_range_program_fn) rom_func_lookup(ROM_FUNC_FLASH_RANGE_PROGRAM); + flash_connect_internal_fn flash_connect_internal_func = (flash_connect_internal_fn) rom_func_lookup(ROM_FUNC_CONNECT_INTERNAL_FLASH); + flash_exit_xip_fn flash_exit_xip_func = (flash_exit_xip_fn) rom_func_lookup(ROM_FUNC_FLASH_EXIT_XIP); + flash_flush_cache_fn flash_flush_cache_func = (flash_flush_cache_fn) rom_func_lookup(ROM_FUNC_FLASH_FLUSH_CACHE); + + flash_init_boot2_copyout(); + + __compiler_memory_barrier(); + + flash_connect_internal_func(); + flash_exit_xip_func(); + + flash_range_program_func(offset, data, count); + flash_flush_cache_func(); + flash_enable_xip_via_boot2(); +} + +void ram_func flash_erase_blocks(uint32_t offset, size_t count) +{ + flash_range_erase_fn flash_range_erase_func = (flash_range_erase_fn) rom_func_lookup(ROM_FUNC_FLASH_RANGE_ERASE); + flash_connect_internal_fn flash_connect_internal_func = (flash_connect_internal_fn) rom_func_lookup(ROM_FUNC_CONNECT_INTERNAL_FLASH); + flash_exit_xip_fn flash_exit_xip_func = (flash_exit_xip_fn) rom_func_lookup(ROM_FUNC_FLASH_EXIT_XIP); + flash_flush_cache_fn flash_flush_cache_func = (flash_flush_cache_fn) rom_func_lookup(ROM_FUNC_FLASH_FLUSH_CACHE); + + flash_init_boot2_copyout(); + + __compiler_memory_barrier(); + + flash_connect_internal_func(); + flash_exit_xip_func(); + + flash_range_erase_func(offset, count, FLASH_BLOCK_SIZE, FLASH_BLOCK_ERASE_CMD); + flash_flush_cache_func(); + flash_enable_xip_via_boot2(); +} + +void ram_func flash_do_cmd(const uint8_t *txbuf, uint8_t *rxbuf, size_t count) { + flash_connect_internal_fn flash_connect_internal_func = (flash_connect_internal_fn) rom_func_lookup(ROM_FUNC_CONNECT_INTERNAL_FLASH); + flash_exit_xip_fn flash_exit_xip_func = (flash_exit_xip_fn) rom_func_lookup(ROM_FUNC_FLASH_EXIT_XIP); + flash_flush_cache_fn flash_flush_cache_func = (flash_flush_cache_fn) rom_func_lookup(ROM_FUNC_FLASH_FLUSH_CACHE); + + flash_init_boot2_copyout(); + + __compiler_memory_barrier(); + + flash_connect_internal_func(); + flash_exit_xip_func(); + + flash_cs_force(0); + size_t tx_remaining = count; + size_t rx_remaining = count; + // We may be interrupted -- don't want FIFO to overflow if we're distracted. + const size_t max_in_flight = 16 - 2; + while (tx_remaining || rx_remaining) { + uint32_t flags = *(uint32_t*)(XIP_SSI_BASE + SSI_SR_OFFSET); + bool can_put = !!(flags & SSI_SR_TFNF_BITS); + bool can_get = !!(flags & SSI_SR_RFNE_BITS); + if (can_put && tx_remaining && rx_remaining - tx_remaining < max_in_flight) { + *(uint32_t*)(XIP_SSI_BASE + SSI_DR0_OFFSET) = *txbuf++; + --tx_remaining; + } + if (can_get && rx_remaining) { + *rxbuf++ = (uint8_t)*(uint32_t*)(XIP_SSI_BASE + SSI_DR0_OFFSET); + --rx_remaining; + } + } + flash_cs_force(1); + + flash_flush_cache_func(); + flash_enable_xip_via_boot2(); +} + +*/ +import "C" + +func enterBootloader() { + C.reset_usb_boot(0, 0) +} + +func doFlashCommand(tx []byte, rx []byte) error { + if len(tx) != len(rx) { + return errFlashInvalidWriteLength + } + + C.flash_do_cmd( + (*C.uint8_t)(unsafe.Pointer(&tx[0])), + (*C.uint8_t)(unsafe.Pointer(&rx[0])), + C.ulong(len(tx))) + + return nil +} + +// Flash related code +const memoryStart = C.XIP_BASE // memory start for purpose of erase + +func (f flashBlockDevice) writeAt(p []byte, off int64) (n int, err error) { + if writeAddress(off)+uintptr(C.XIP_BASE) > FlashDataEnd() { + return 0, errFlashCannotWritePastEOF + } + + state := interrupt.Disable() + defer interrupt.Restore(state) + + // rp2040 writes to offset, not actual address + // e.g. real address 0x10003000 is written to at + // 0x00003000 + address := writeAddress(off) + padded := flashPad(p, int(f.WriteBlockSize())) + + C.flash_range_write(C.uint32_t(address), + (*C.uint8_t)(unsafe.Pointer(&padded[0])), + C.ulong(len(padded))) + + return len(padded), nil +} + +func (f flashBlockDevice) eraseBlocks(start, length int64) error { + address := writeAddress(start * f.EraseBlockSize()) + if address+uintptr(C.XIP_BASE) > FlashDataEnd() { + return errFlashCannotErasePastEOF + } + + state := interrupt.Disable() + defer interrupt.Restore(state) + + C.flash_erase_blocks(C.uint32_t(address), C.ulong(length*f.EraseBlockSize())) + + return nil +} diff --git a/emb/machine/machine_rp2040_rtc.go b/emb/machine/machine_rp2040_rtc.go new file mode 100644 index 00000000..192e187c --- /dev/null +++ b/emb/machine/machine_rp2040_rtc.go @@ -0,0 +1,240 @@ +//go:build rp2040 + +// Implementation based on code located here: +// https://github.com/raspberrypi/pico-sdk/blob/master/src/rp2_common/hardware_rtc/rtc.c + +package machine + +import ( + "device/rp" + "errors" + "runtime/interrupt" + "unsafe" +) + +type rtcType rp.RTC_Type + +type rtcTime struct { + Year int16 + Month int8 + Day int8 + Dotw int8 + Hour int8 + Min int8 + Sec int8 +} + +var RTC = (*rtcType)(unsafe.Pointer(rp.RTC)) + +const ( + second = 1 + minute = 60 * second + hour = 60 * minute + day = 24 * hour +) + +var ( + rtcAlarmRepeats bool + rtcCallback func() + rtcEpoch = rtcTime{ + Year: 1970, Month: 1, Day: 1, Dotw: 4, Hour: 0, Min: 0, Sec: 0, + } +) + +var ( + ErrRtcDelayTooSmall = errors.New("RTC interrupt deplay is too small, shall be at least 1 second") + ErrRtcDelayTooLarge = errors.New("RTC interrupt deplay is too large, shall be no more than 1 day") +) + +// SetInterrupt configures delayed and optionally recurring interrupt by real time clock. +// +// Delay is specified in whole seconds, allowed range depends on platform. +// Zero delay disables previously configured interrupt, if any. +// +// RP2040 implementation allows delay to be up to 1 day, otherwise a respective error is emitted. +func (rtc *rtcType) SetInterrupt(delay uint32, repeat bool, callback func()) error { + + // Verify delay range + if delay > day { + return ErrRtcDelayTooLarge + } + + // De-configure delayed interrupt if delay is zero + if delay == 0 { + rtc.disableInterruptMatch() + return nil + } + + // Configure delayed interrupt + rtc.setDivider() + + rtcAlarmRepeats = repeat + rtcCallback = callback + + err := rtc.setTime(rtcEpoch) + if err != nil { + return err + } + rtc.setAlarm(toAlarmTime(delay), callback) + + return nil +} + +func toAlarmTime(delay uint32) rtcTime { + result := rtcEpoch + remainder := delay + 1 // needed "+1", otherwise alarm fires one second too early + if remainder >= hour { + result.Hour = int8(remainder / hour) + remainder %= hour + } + if remainder >= minute { + result.Min = int8(remainder / minute) + remainder %= minute + } + result.Sec = int8(remainder) + return result +} + +func (rtc *rtcType) setDivider() { + // Get clk_rtc freq and make sure it is running + rtcFreq := configuredFreq[clkRTC] + if rtcFreq == 0 { + panic("can not set RTC divider, clock is not running") + } + + // Take rtc out of reset now that we know clk_rtc is running + resetBlock(rp.RESETS_RESET_RTC) + unresetBlockWait(rp.RESETS_RESET_RTC) + + // Set up the 1 second divider. + // If rtc_freq is 400 then clkdiv_m1 should be 399 + rtcFreq -= 1 + + // Check the freq is not too big to divide + if rtcFreq > rp.RTC_CLKDIV_M1_CLKDIV_M1_Msk { + panic("can not set RTC divider, clock frequency is too big to divide") + } + + // Write divide value + rtc.CLKDIV_M1.Set(rtcFreq) +} + +// setTime configures RTC with supplied time, initialises and activates it. +func (rtc *rtcType) setTime(t rtcTime) error { + + // Disable RTC and wait while it is still running + rtc.CTRL.Set(0) + for rtc.isActive() { + } + + rtc.SETUP_0.Set((uint32(t.Year) << rp.RTC_SETUP_0_YEAR_Pos) | + (uint32(t.Month) << rp.RTC_SETUP_0_MONTH_Pos) | + (uint32(t.Day) << rp.RTC_SETUP_0_DAY_Pos)) + + rtc.SETUP_1.Set((uint32(t.Dotw) << rp.RTC_SETUP_1_DOTW_Pos) | + (uint32(t.Hour) << rp.RTC_SETUP_1_HOUR_Pos) | + (uint32(t.Min) << rp.RTC_SETUP_1_MIN_Pos) | + (uint32(t.Sec) << rp.RTC_SETUP_1_SEC_Pos)) + + // Load setup values into RTC clock domain + rtc.CTRL.SetBits(rp.RTC_CTRL_LOAD) + + // Enable RTC and wait for it to be running + rtc.CTRL.SetBits(rp.RTC_CTRL_RTC_ENABLE) + for !rtc.isActive() { + } + + return nil +} + +func (rtc *rtcType) isActive() bool { + return rtc.CTRL.HasBits(rp.RTC_CTRL_RTC_ACTIVE) +} + +// setAlarm configures alarm in RTC and arms it. +// The callback is executed in the context of an interrupt handler, +// so regular restructions for this sort of code apply: no blocking, no memory allocation, etc. +func (rtc *rtcType) setAlarm(t rtcTime, callback func()) { + + rtc.disableInterruptMatch() + + // Clear all match enable bits + rtc.IRQ_SETUP_0.ClearBits(rp.RTC_IRQ_SETUP_0_YEAR_ENA | rp.RTC_IRQ_SETUP_0_MONTH_ENA | rp.RTC_IRQ_SETUP_0_DAY_ENA) + rtc.IRQ_SETUP_1.ClearBits(rp.RTC_IRQ_SETUP_1_DOTW_ENA | rp.RTC_IRQ_SETUP_1_HOUR_ENA | rp.RTC_IRQ_SETUP_1_MIN_ENA | rp.RTC_IRQ_SETUP_1_SEC_ENA) + + // Only add to setup if it isn't -1 and set the match enable bits for things we care about + if t.Year >= 0 { + rtc.IRQ_SETUP_0.SetBits(uint32(t.Year) << rp.RTC_SETUP_0_YEAR_Pos) + rtc.IRQ_SETUP_0.SetBits(rp.RTC_IRQ_SETUP_0_YEAR_ENA) + } + + if t.Month >= 0 { + rtc.IRQ_SETUP_0.SetBits(uint32(t.Month) << rp.RTC_SETUP_0_MONTH_Pos) + rtc.IRQ_SETUP_0.SetBits(rp.RTC_IRQ_SETUP_0_MONTH_ENA) + } + + if t.Day >= 0 { + rtc.IRQ_SETUP_0.SetBits(uint32(t.Day) << rp.RTC_SETUP_0_DAY_Pos) + rtc.IRQ_SETUP_0.SetBits(rp.RTC_IRQ_SETUP_0_DAY_ENA) + } + + if t.Dotw >= 0 { + rtc.IRQ_SETUP_1.SetBits(uint32(t.Dotw) << rp.RTC_SETUP_1_DOTW_Pos) + rtc.IRQ_SETUP_1.SetBits(rp.RTC_IRQ_SETUP_1_DOTW_ENA) + } + + if t.Hour >= 0 { + rtc.IRQ_SETUP_1.SetBits(uint32(t.Hour) << rp.RTC_SETUP_1_HOUR_Pos) + rtc.IRQ_SETUP_1.SetBits(rp.RTC_IRQ_SETUP_1_HOUR_ENA) + } + + if t.Min >= 0 { + rtc.IRQ_SETUP_1.SetBits(uint32(t.Min) << rp.RTC_SETUP_1_MIN_Pos) + rtc.IRQ_SETUP_1.SetBits(rp.RTC_IRQ_SETUP_1_MIN_ENA) + } + + if t.Sec >= 0 { + rtc.IRQ_SETUP_1.SetBits(uint32(t.Sec) << rp.RTC_SETUP_1_SEC_Pos) + rtc.IRQ_SETUP_1.SetBits(rp.RTC_IRQ_SETUP_1_SEC_ENA) + } + + // Enable the IRQ at the proc + interrupt.New(rp.IRQ_RTC_IRQ, rtcHandleInterrupt).Enable() + + // Enable the IRQ at the peri + rtc.INTE.Set(rp.RTC_INTE_RTC) + + rtc.enableInterruptMatch() +} + +func (rtc *rtcType) enableInterruptMatch() { + // Set matching and wait for it to be enabled + rtc.IRQ_SETUP_0.SetBits(rp.RTC_IRQ_SETUP_0_MATCH_ENA) + for !rtc.IRQ_SETUP_0.HasBits(rp.RTC_IRQ_SETUP_0_MATCH_ACTIVE) { + } +} + +func (rtc *rtcType) disableInterruptMatch() { + // Disable matching and wait for it to stop being active + rtc.IRQ_SETUP_0.ClearBits(rp.RTC_IRQ_SETUP_0_MATCH_ENA) + for rtc.IRQ_SETUP_0.HasBits(rp.RTC_IRQ_SETUP_0_MATCH_ACTIVE) { + } +} + +func rtcHandleInterrupt(itr interrupt.Interrupt) { + // Always disable the alarm to clear the current IRQ. + // Even if it is a repeatable alarm, we don't want it to keep firing. + // If it matches on a second it can keep firing for that second. + RTC.disableInterruptMatch() + + // Call user callback function + if rtcCallback != nil { + rtcCallback() + } + + if rtcAlarmRepeats { + // If it is a repeatable alarm, reset time and re-enable the alarm. + RTC.setTime(rtcEpoch) + RTC.enableInterruptMatch() + } +} diff --git a/emb/machine/machine_rp2040_simulator.go b/emb/machine/machine_rp2040_simulator.go new file mode 100644 index 00000000..6c1d106a --- /dev/null +++ b/emb/machine/machine_rp2040_simulator.go @@ -0,0 +1,96 @@ +//go:build !baremetal && (ae_rp2040 || badger2040_w || badger2040 || challenger_rp2040 || elecrow_rp2040 || feather_rp2040 || gopher_badge || kb2040 || macropad_rp2040 || nano_rp2040 || pico || qtpy_rp2040 || thingplus_rp2040 || thumby || trinkey_qt2040 || tufty2040 || waveshare_rp2040_tiny || waveshare_rp2040_zero || xiao_rp2040) + +// Simulator support for the RP2040. +// +// This is *only* for the RP2040. RP2350 is a different chip with slightly +// different characteristics. + +package machine + +var PWM0 = &timerType{ + instance: 0, + frequency: 200e6, + bits: 16, + prescalers: []int{1, 2, 4, 8, 16, 32, 64, 128, 256}, // actually a continuing range, TODO + channelPins: [][]Pin{ + {GPIO0, GPIO16}, // channel A (0) + {GPIO1, GPIO17}, // channel B (1) + }, +} + +var PWM1 = &timerType{ + instance: 0, + frequency: 200e6, + bits: 16, + prescalers: []int{1, 2, 4, 8, 16, 32, 64, 128, 256}, + channelPins: [][]Pin{ + {GPIO2, GPIO18}, // channel A (0) + {GPIO3, GPIO19}, // channel B (1) + }, +} + +var PWM2 = &timerType{ + instance: 0, + frequency: 200e6, + bits: 16, + prescalers: []int{1, 2, 4, 8, 16, 32, 64, 128, 256}, + channelPins: [][]Pin{ + {GPIO4, GPIO20}, // channel A (0) + {GPIO5, GPIO21}, // channel B (1) + }, +} + +var PWM3 = &timerType{ + instance: 0, + frequency: 200e6, + bits: 16, + prescalers: []int{1, 2, 4, 8, 16, 32, 64, 128, 256}, + channelPins: [][]Pin{ + {GPIO6, GPIO22}, // channel A (0) + {GPIO7, GPIO23}, // channel B (1) + }, +} + +var PWM4 = &timerType{ + instance: 0, + frequency: 200e6, + bits: 16, + prescalers: []int{1, 2, 4, 8, 16, 32, 64, 128, 256}, + channelPins: [][]Pin{ + {GPIO8, GPIO24}, // channel A (0) + {GPIO9, GPIO25}, // channel B (1) + }, +} + +var PWM5 = &timerType{ + instance: 0, + frequency: 200e6, + bits: 16, + prescalers: []int{1, 2, 4, 8, 16, 32, 64, 128, 256}, + channelPins: [][]Pin{ + {GPIO10, GPIO26}, // channel A (0) + {GPIO11, GPIO27}, // channel B (1) + }, +} + +var PWM6 = &timerType{ + instance: 0, + frequency: 200e6, + bits: 16, + prescalers: []int{1, 2, 4, 8, 16, 32, 64, 128, 256}, + channelPins: [][]Pin{ + {GPIO12, GPIO28}, // channel A (0) + {GPIO13, GPIO29}, // channel B (1) + }, +} + +var PWM7 = &timerType{ + instance: 0, + frequency: 200e6, + bits: 16, + prescalers: []int{1, 2, 4, 8, 16, 32, 64, 128, 256}, + channelPins: [][]Pin{ + {GPIO14}, // channel A (0) + {GPIO15}, // channel B (1) + }, +} diff --git a/emb/machine/machine_rp2040_usb.go b/emb/machine/machine_rp2040_usb.go new file mode 100644 index 00000000..087e3bf0 --- /dev/null +++ b/emb/machine/machine_rp2040_usb.go @@ -0,0 +1,148 @@ +//go:build rp2040 + +package machine + +import ( + "device/rp" + "machine/usb" + "runtime/interrupt" +) + +// Configure the USB peripheral. The config is here for compatibility with the UART interface. +func (dev *USBDevice) Configure(config UARTConfig) { + // Reset usb controller + resetBlock(rp.RESETS_RESET_USBCTRL) + unresetBlockWait(rp.RESETS_RESET_USBCTRL) + + // Clear any previous state in dpram just in case + _usbDPSRAM.clear() + + // Enable USB interrupt at processor + rp.USBCTRL_REGS.INTE.Set(0) + intr := interrupt.New(rp.IRQ_USBCTRL_IRQ, handleUSBIRQ) + intr.SetPriority(0x00) + intr.Enable() + irqSet(rp.IRQ_USBCTRL_IRQ, true) + + // Mux the controller to the onboard usb phy + rp.USBCTRL_REGS.USB_MUXING.Set(rp.USBCTRL_REGS_USB_MUXING_TO_PHY | rp.USBCTRL_REGS_USB_MUXING_SOFTCON) + + // Force VBUS detect so the device thinks it is plugged into a host + rp.USBCTRL_REGS.USB_PWR.Set(rp.USBCTRL_REGS_USB_PWR_VBUS_DETECT | rp.USBCTRL_REGS_USB_PWR_VBUS_DETECT_OVERRIDE_EN) + + // Enable the USB controller in device mode. + rp.USBCTRL_REGS.MAIN_CTRL.Set(rp.USBCTRL_REGS_MAIN_CTRL_CONTROLLER_EN) + + // Enable an interrupt per EP0 transaction + rp.USBCTRL_REGS.SIE_CTRL.Set(rp.USBCTRL_REGS_SIE_CTRL_EP0_INT_1BUF) + + // Enable interrupts for when a buffer is done, when the bus is reset, + // and when a setup packet is received + rp.USBCTRL_REGS.INTE.Set(rp.USBCTRL_REGS_INTE_BUFF_STATUS | + rp.USBCTRL_REGS_INTE_BUS_RESET | + rp.USBCTRL_REGS_INTE_SETUP_REQ) + + // Present full speed device by enabling pull up on DP + rp.USBCTRL_REGS.SIE_CTRL.SetBits(rp.USBCTRL_REGS_SIE_CTRL_PULLUP_EN) +} + +func handleUSBIRQ(intr interrupt.Interrupt) { + status := rp.USBCTRL_REGS.INTS.Get() + + // Setup packet received + if (status & rp.USBCTRL_REGS_INTS_SETUP_REQ) > 0 { + rp.USBCTRL_REGS.SIE_STATUS.Set(rp.USBCTRL_REGS_SIE_STATUS_SETUP_REC) + setup := usb.NewSetup(_usbDPSRAM.setupBytes()) + + ok := false + if (setup.BmRequestType & usb.REQUEST_TYPE) == usb.REQUEST_STANDARD { + // Standard Requests + ok = handleStandardSetup(setup) + } else { + // Class Interface Requests + if setup.WIndex < uint16(len(usbSetupHandler)) && usbSetupHandler[setup.WIndex] != nil { + ok = usbSetupHandler[setup.WIndex](setup) + } + } + + if !ok { + // Stall endpoint? + USBDev.SetStallEPIn(0) + } + + } + + // Buffer status, one or more buffers have completed + if (status & rp.USBCTRL_REGS_INTS_BUFF_STATUS) > 0 { + if sendOnEP0DATADONE.offset > 0 { + ep := uint32(0) + data := sendOnEP0DATADONE.data + count := len(data) - sendOnEP0DATADONE.offset + if ep == 0 && count > usb.EndpointPacketSize { + count = usb.EndpointPacketSize + } + + sendViaEPIn(ep, data[sendOnEP0DATADONE.offset:], count) + sendOnEP0DATADONE.offset += count + if sendOnEP0DATADONE.offset == len(data) { + sendOnEP0DATADONE.offset = 0 + } + } + + s2 := rp.USBCTRL_REGS.BUFF_STATUS.Get() + + // OUT (PC -> rp2040) + for i := 0; i < 16; i++ { + if s2&(1<<(i*2+1)) > 0 { + buf := handleEndpointRx(uint32(i)) + if usbRxHandler[i] == nil || usbRxHandler[i](buf) { + AckUsbOutTransfer(uint32(i)) + } + } + } + + // IN (rp2040 -> PC) + for i := 0; i < 16; i++ { + if s2&(1<<(i*2)) > 0 { + if usbTxHandler[i] != nil { + usbTxHandler[i]() + } + } + } + + rp.USBCTRL_REGS.BUFF_STATUS.Set(s2) + } + + // Bus is reset + if (status & rp.USBCTRL_REGS_INTS_BUS_RESET) > 0 { + rp.USBCTRL_REGS.SIE_STATUS.Set(rp.USBCTRL_REGS_SIE_STATUS_BUS_RESET) + fixRP2040UsbDeviceEnumeration() + + rp.USBCTRL_REGS.ADDR_ENDP.Set(0) + initEndpoint(0, usb.ENDPOINT_TYPE_CONTROL) + } +} + +func handleUSBSetAddress(setup usb.Setup) bool { + // Using 570μs timeout which is exactly the same as SAMD21. + const ackTimeout = 570 + + rp.USBCTRL_REGS.SIE_STATUS.Set(rp.USBCTRL_REGS_SIE_STATUS_ACK_REC) + sendUSBPacket(0, []byte{}, 0) + + // Wait for transfer to complete with a timeout. + t := timer.timeElapsed() + for (rp.USBCTRL_REGS.SIE_STATUS.Get() & rp.USBCTRL_REGS_SIE_STATUS_ACK_REC) == 0 { + if dt := timer.timeElapsed() - t; dt >= ackTimeout { + return false + } + } + + // Set the device address to that requested by host. + rp.USBCTRL_REGS.ADDR_ENDP.Set(uint32(setup.WValueL) & rp.USBCTRL_REGS_ADDR_ENDP_ADDRESS_Msk) + return true +} + +func armEPZeroStall() { + rp.USBCTRL_REGS.EP_STALL_ARM.Set(rp.USBCTRL_REGS_EP_STALL_ARM_EP0_IN) +} diff --git a/emb/machine/machine_rp2040_usb_fix_usb_device_enumeration.go b/emb/machine/machine_rp2040_usb_fix_usb_device_enumeration.go new file mode 100644 index 00000000..f027f942 --- /dev/null +++ b/emb/machine/machine_rp2040_usb_fix_usb_device_enumeration.go @@ -0,0 +1,120 @@ +//go:build rp2040 + +package machine + +import ( + "device/arm" + "device/rp" +) + +// https://github.com/raspberrypi/pico-sdk/blob/master/src/rp2_common/pico_fix/rp2040_usb_device_enumeration/rp2040_usb_device_enumeration.c +// According to errata RP2040-E5: +// "It is safe (and inexpensive) to enable the software workaround even when using versions of RP2040 +// which include the fix in hardware." +// So let us always use the software fix. +func fixRP2040UsbDeviceEnumeration() { + // After coming out of reset, the hardware expects 800us of LS_J (linestate J) time + // before it will move to the connected state. However on a hub that broadcasts packets + // for other devices this isn't the case. The plan here is to wait for the end of the bus + // reset, force an LS_J for 1ms and then switch control back to the USB phy. Unfortunately + // this requires us to use GPIO15 as there is no other way to force the input path. + // We only need to force DP as DM can be left at zero. It will be gated off by GPIO + // logic if it isn't func selected. + + // Wait SE0 phase will call force ls_j phase which will call finish phase + hw_enumeration_fix_wait_se0() +} + +func hw_enumeration_fix_wait_se0() { + // Wait for SE0 to end (i.e. the host to stop resetting). This reset can last quite long. + // 10-15ms so we are going to set a timer callback. + + // if timer pool disabled, or no timer available, have to busy wait. + hw_enumeration_fix_busy_wait_se0() +} + +const ( + LS_SE0 = 0b00 + LS_J = 0b01 + LS_K = 0b10 + LS_SE1 = 0b11 +) + +const ( + dp = 15 +) + +var ( + gpioCtrlPrev uint32 + padCtrlPrev uint32 +) + +func hw_enumeration_fix_busy_wait_se0() { + for ((rp.USBCTRL_REGS.SIE_STATUS.Get() & rp.USBCTRL_REGS_SIE_STATUS_LINE_STATE_Msk) >> rp.USBCTRL_REGS_SIE_STATUS_LINE_STATE_Pos) == LS_SE0 { + } + + // Now force LS_J (next stage of fix) + hw_enumeration_fix_force_ls_j() +} + +func hw_enumeration_fix_force_ls_j() { + // DM must be 0 for this to work. This is true if it is selected + // to any other function. fn 8 on this pin is only for debug so shouldn't + // be selected + + // Before changing any pin state, take a copy of the current gpio control register + gpioCtrlPrev = ioBank0.io[dp].ctrl.Get() + // Also take a copy of the pads register + padCtrlPrev = padsBank0.io[dp].Get() + + // Enable bus keep and force pin to tristate, so USB DP muxing doesn't affect + // pin state + padsBank0.io[dp].SetBits(rp.PADS_BANK0_GPIO0_PUE | rp.PADS_BANK0_GPIO0_PDE) + ioBank0.io[dp].ctrl.ReplaceBits(rp.IO_BANK0_GPIO0_CTRL_OEOVER_DISABLE, rp.IO_BANK0_GPIO0_CTRL_OEOVER_Msk>>rp.IO_BANK0_GPIO0_CTRL_OEOVER_Pos, rp.IO_BANK0_GPIO0_CTRL_OEOVER_Pos) + + // Select function 8 (USB debug muxing) without disturbing other controls + ioBank0.io[dp].ctrl.ReplaceBits(8, rp.IO_BANK0_GPIO0_CTRL_FUNCSEL_Msk>>rp.IO_BANK0_GPIO0_CTRL_FUNCSEL_Pos, rp.IO_BANK0_GPIO0_CTRL_FUNCSEL_Pos) + + // J state is a differential 1 for a full speed device so + // DP = 1 and DM = 0. Don't actually need to set DM low as it + // is already gated assuming it isn't funcseld. + ioBank0.io[dp].ctrl.ReplaceBits(rp.IO_BANK0_GPIO1_CTRL_INOVER_HIGH, rp.IO_BANK0_GPIO1_CTRL_INOVER_Msk>>rp.IO_BANK0_GPIO1_CTRL_INOVER_Pos, rp.IO_BANK0_GPIO1_CTRL_INOVER_Pos) + + // Force PHY pull up to stay before switching away from the phy + rp.USBCTRL_REGS.USBPHY_DIRECT.SetBits(rp.USBCTRL_REGS_USBPHY_DIRECT_DP_PULLUP_EN) + rp.USBCTRL_REGS.USBPHY_DIRECT_OVERRIDE.SetBits(rp.USBCTRL_REGS_USBPHY_DIRECT_OVERRIDE_DP_PULLUP_EN_OVERRIDE_EN) + + // Switch to GPIO phy with LS_J forced + rp.USBCTRL_REGS.USB_MUXING.Set(rp.USBCTRL_REGS_USB_MUXING_TO_DIGITAL_PAD | rp.USBCTRL_REGS_USB_MUXING_SOFTCON) + + // LS_J is now forced but while loop to wait ~800us here just to check + waitCycles(25000) + + // if timer pool disabled, or no timer available, have to busy wait. + hw_enumeration_fix_finish() + +} + +func hw_enumeration_fix_finish() { + // Should think we are connected now + for (rp.USBCTRL_REGS.SIE_STATUS.Get() & rp.USBCTRL_REGS_SIE_STATUS_CONNECTED) != rp.USBCTRL_REGS_SIE_STATUS_CONNECTED { + } + + // Switch back to USB phy + rp.USBCTRL_REGS.USB_MUXING.Set(rp.USBCTRL_REGS_USB_MUXING_TO_PHY | rp.USBCTRL_REGS_USB_MUXING_SOFTCON) + + // Get rid of DP pullup override + rp.USBCTRL_REGS.USBPHY_DIRECT_OVERRIDE.ClearBits(rp.USBCTRL_REGS_USBPHY_DIRECT_OVERRIDE_DP_PULLUP_EN_OVERRIDE_EN) + + // Finally, restore the gpio ctrl value back to GPIO15 + ioBank0.io[dp].ctrl.Set(gpioCtrlPrev) + // Restore the pad ctrl value + padsBank0.io[dp].Set(padCtrlPrev) +} + +func waitCycles(n int) { + for n > 0 { + arm.Asm("nop") + n-- + } +} diff --git a/emb/machine/machine_rp2350_rom.go b/emb/machine/machine_rp2350_rom.go new file mode 100644 index 00000000..665464ae --- /dev/null +++ b/emb/machine/machine_rp2350_rom.go @@ -0,0 +1,560 @@ +//go:build tinygo && rp2350 + +package machine + +import ( + "runtime/interrupt" + "unsafe" +) + +/* +typedef unsigned char uint8_t; +typedef unsigned short uint16_t; +typedef unsigned long uint32_t; +typedef unsigned long size_t; +typedef unsigned long uintptr_t; +typedef long int intptr_t; + +typedef const volatile uint16_t io_ro_16; +typedef const volatile uint32_t io_ro_32; +typedef volatile uint16_t io_rw_16; +typedef volatile uint32_t io_rw_32; +typedef volatile uint32_t io_wo_32; + +#define false 0 +#define true 1 +typedef int bool; + +#define ram_func __attribute__((section(".ramfuncs"),noinline)) + +typedef void (*flash_exit_xip_fn)(void); +typedef void (*flash_flush_cache_fn)(void); +typedef void (*flash_connect_internal_fn)(void); +typedef void (*flash_range_erase_fn)(uint32_t, size_t, uint32_t, uint16_t); +typedef void (*flash_range_program_fn)(uint32_t, const uint8_t*, size_t); +static inline __attribute__((always_inline)) void __compiler_memory_barrier(void) { + __asm__ volatile ("" : : : "memory"); +} + +// https://datasheets.raspberrypi.com/rp2350/rp2350-datasheet.pdf +// 13.9. Predefined OTP Data Locations +// OTP_DATA: FLASH_DEVINFO Register + +#define OTP_DATA_FLASH_DEVINFO_CS0_SIZE_BITS 0x0F00 +#define OTP_DATA_FLASH_DEVINFO_CS0_SIZE_LSB 8 +#define OTP_DATA_FLASH_DEVINFO_CS1_SIZE_BITS 0xF000 +#define OTP_DATA_FLASH_DEVINFO_CS1_SIZE_LSB 12 + + +// https://github.com/raspberrypi/pico-sdk +// src/rp2350/hardware_regs/include/hardware/regs/addressmap.h + +#define REG_ALIAS_RW_BITS (0x0 << 12) +#define REG_ALIAS_XOR_BITS (0x1 << 12) +#define REG_ALIAS_SET_BITS (0x2 << 12) +#define REG_ALIAS_CLR_BITS (0x3 << 12) + +#define XIP_BASE 0x10000000 +#define XIP_QMI_BASE 0x400d0000 +#define IO_QSPI_BASE 0x40030000 +#define BOOTRAM_BASE 0x400e0000 + + +// https://github.com/raspberrypi/pico-sdk +// src/rp2_common/hardware_base/include/hardware/address_mapped.h + +#define hw_alias_check_addr(addr) ((uintptr_t)(addr)) +#define hw_set_alias_untyped(addr) ((void *)(REG_ALIAS_SET_BITS + hw_alias_check_addr(addr))) +#define hw_clear_alias_untyped(addr) ((void *)(REG_ALIAS_CLR_BITS + hw_alias_check_addr(addr))) +#define hw_xor_alias_untyped(addr) ((void *)(REG_ALIAS_XOR_BITS + hw_alias_check_addr(addr))) + +__attribute__((always_inline)) +static void hw_set_bits(io_rw_32 *addr, uint32_t mask) { + *(io_rw_32 *) hw_set_alias_untyped((volatile void *) addr) = mask; +} + +__attribute__((always_inline)) +static void hw_clear_bits(io_rw_32 *addr, uint32_t mask) { + *(io_rw_32 *) hw_clear_alias_untyped((volatile void *) addr) = mask; +} + +__attribute__((always_inline)) +static void hw_xor_bits(io_rw_32 *addr, uint32_t mask) { + *(io_rw_32 *) hw_xor_alias_untyped((volatile void *) addr) = mask; +} + +__attribute__((always_inline)) +static void hw_write_masked(io_rw_32 *addr, uint32_t values, uint32_t write_mask) { + hw_xor_bits(addr, (*addr ^ values) & write_mask); +} + + +// https://github.com/raspberrypi/pico-sdk +// src/rp2_common/pico_platform_compiler/include/pico/platform/compiler.h + +#define pico_default_asm_volatile(...) __asm volatile (".syntax unified\n" __VA_ARGS__) + + +// https://github.com/raspberrypi/pico-sdk +// src/rp2350/pico_platform/include/pico/platform.h + +static bool pico_processor_state_is_nonsecure(void) { +// // todo add a define to disable NS checking at all? +// // IDAU-Exempt addresses return S=1 when tested in the Secure state, +// // whereas executing a tt in the NonSecure state will always return S=0. +// uint32_t tt; +// pico_default_asm_volatile ( +// "movs %0, #0\n" +// "tt %0, %0\n" +// : "=r" (tt) : : "cc" +// ); +// return !(tt & (1u << 22)); + + return false; +} + + +// https://github.com/raspberrypi/pico-sdk +// src/rp2_common/pico_bootrom/include/pico/bootrom_constants.h + +// RP2040 & RP2350 +#define ROM_DATA_SOFTWARE_GIT_REVISION ROM_TABLE_CODE('G', 'R') +#define ROM_FUNC_FLASH_ENTER_CMD_XIP ROM_TABLE_CODE('C', 'X') +#define ROM_FUNC_FLASH_EXIT_XIP ROM_TABLE_CODE('E', 'X') +#define ROM_FUNC_FLASH_FLUSH_CACHE ROM_TABLE_CODE('F', 'C') +#define ROM_FUNC_CONNECT_INTERNAL_FLASH ROM_TABLE_CODE('I', 'F') +#define ROM_FUNC_FLASH_RANGE_ERASE ROM_TABLE_CODE('R', 'E') +#define ROM_FUNC_FLASH_RANGE_PROGRAM ROM_TABLE_CODE('R', 'P') + +// RP2350 only +#define ROM_FUNC_PICK_AB_PARTITION ROM_TABLE_CODE('A', 'B') +#define ROM_FUNC_CHAIN_IMAGE ROM_TABLE_CODE('C', 'I') +#define ROM_FUNC_EXPLICIT_BUY ROM_TABLE_CODE('E', 'B') +#define ROM_FUNC_FLASH_RUNTIME_TO_STORAGE_ADDR ROM_TABLE_CODE('F', 'A') +#define ROM_DATA_FLASH_DEVINFO16_PTR ROM_TABLE_CODE('F', 'D') +#define ROM_FUNC_FLASH_OP ROM_TABLE_CODE('F', 'O') +#define ROM_FUNC_GET_B_PARTITION ROM_TABLE_CODE('G', 'B') +#define ROM_FUNC_GET_PARTITION_TABLE_INFO ROM_TABLE_CODE('G', 'P') +#define ROM_FUNC_GET_SYS_INFO ROM_TABLE_CODE('G', 'S') +#define ROM_FUNC_GET_UF2_TARGET_PARTITION ROM_TABLE_CODE('G', 'U') +#define ROM_FUNC_LOAD_PARTITION_TABLE ROM_TABLE_CODE('L', 'P') +#define ROM_FUNC_OTP_ACCESS ROM_TABLE_CODE('O', 'A') +#define ROM_DATA_PARTITION_TABLE_PTR ROM_TABLE_CODE('P', 'T') +#define ROM_FUNC_FLASH_RESET_ADDRESS_TRANS ROM_TABLE_CODE('R', 'A') +#define ROM_FUNC_REBOOT ROM_TABLE_CODE('R', 'B') +#define ROM_FUNC_SET_ROM_CALLBACK ROM_TABLE_CODE('R', 'C') +#define ROM_FUNC_SECURE_CALL ROM_TABLE_CODE('S', 'C') +#define ROM_FUNC_SET_NS_API_PERMISSION ROM_TABLE_CODE('S', 'P') +#define ROM_FUNC_BOOTROM_STATE_RESET ROM_TABLE_CODE('S', 'R') +#define ROM_FUNC_SET_BOOTROM_STACK ROM_TABLE_CODE('S', 'S') +#define ROM_DATA_SAVED_XIP_SETUP_FUNC_PTR ROM_TABLE_CODE('X', 'F') +#define ROM_FUNC_FLASH_SELECT_XIP_READ_MODE ROM_TABLE_CODE('X', 'M') +#define ROM_FUNC_VALIDATE_NS_BUFFER ROM_TABLE_CODE('V', 'B') + +#define BOOTSEL_FLAG_GPIO_PIN_SPECIFIED 0x20 + +#define BOOTROM_FUNC_TABLE_OFFSET 0x14 + +// todo remove this (or #ifdef it for A1/A2) +#define BOOTROM_IS_A2() ((*(volatile uint8_t *)0x13) == 2) +#define BOOTROM_WELL_KNOWN_PTR_SIZE (BOOTROM_IS_A2() ? 2 : 4) + +#define BOOTROM_VTABLE_OFFSET 0x00 +#define BOOTROM_TABLE_LOOKUP_OFFSET (BOOTROM_FUNC_TABLE_OFFSET + BOOTROM_WELL_KNOWN_PTR_SIZE) + + +// https://github.com/raspberrypi/pico-sdk +// src/common/boot_picoboot_headers/include/boot/picoboot_constants.h + +// values 0-7 are secure/non-secure +#define REBOOT2_FLAG_REBOOT_TYPE_NORMAL 0x0 // param0 = diagnostic partition +#define REBOOT2_FLAG_REBOOT_TYPE_BOOTSEL 0x2 // param0 = bootsel_flags, param1 = gpio_config +#define REBOOT2_FLAG_REBOOT_TYPE_RAM_IMAGE 0x3 // param0 = image_base, param1 = image_end +#define REBOOT2_FLAG_REBOOT_TYPE_FLASH_UPDATE 0x4 // param0 = update_base + +#define REBOOT2_FLAG_NO_RETURN_ON_SUCCESS 0x100 + +#define RT_FLAG_FUNC_ARM_SEC 0x0004 +#define RT_FLAG_FUNC_ARM_NONSEC 0x0010 +#define RT_FLAG_DATA 0x0040 + + +// https://github.com/raspberrypi/pico-sdk +// src/rp2_common/pico_bootrom/include/pico/bootrom.h + +#define ROM_TABLE_CODE(c1, c2) ((c1) | ((c2) << 8)) + +typedef void *(*rom_table_lookup_fn)(uint32_t code, uint32_t mask); + +__attribute__((always_inline)) +static void *rom_func_lookup_inline(uint32_t code) { + rom_table_lookup_fn rom_table_lookup = (rom_table_lookup_fn) (uintptr_t)*(uint16_t*)(BOOTROM_TABLE_LOOKUP_OFFSET); + if (pico_processor_state_is_nonsecure()) { + return rom_table_lookup(code, RT_FLAG_FUNC_ARM_NONSEC); + } else { + return rom_table_lookup(code, RT_FLAG_FUNC_ARM_SEC); + } +} + +__attribute__((always_inline)) +static void *rom_data_lookup_inline(uint32_t code) { + rom_table_lookup_fn rom_table_lookup = (rom_table_lookup_fn) (uintptr_t)*(uint16_t*)(BOOTROM_TABLE_LOOKUP_OFFSET); + return rom_table_lookup(code, RT_FLAG_DATA); +} + +typedef int (*rom_reboot_fn)(uint32_t flags, uint32_t delay_ms, uint32_t p0, uint32_t p1); + +__attribute__((always_inline)) +int rom_reboot(uint32_t flags, uint32_t delay_ms, uint32_t p0, uint32_t p1) { + rom_reboot_fn func = (rom_reboot_fn) rom_func_lookup_inline(ROM_FUNC_REBOOT); + return func(flags, delay_ms, p0, p1); +} + + +// https://github.com/raspberrypi/pico-sdk +// src/rp2_common/pico_bootrom/bootrom.c + +void reset_usb_boot(uint32_t usb_activity_gpio_pin_mask, uint32_t disable_interface_mask) { + uint32_t flags = disable_interface_mask; + if (usb_activity_gpio_pin_mask) { + flags |= BOOTSEL_FLAG_GPIO_PIN_SPECIFIED; + // the parameter is actually the gpio number, but we only care if BOOTSEL_FLAG_GPIO_PIN_SPECIFIED + usb_activity_gpio_pin_mask = (uint32_t)__builtin_ctz(usb_activity_gpio_pin_mask); + } + rom_reboot(REBOOT2_FLAG_REBOOT_TYPE_BOOTSEL | REBOOT2_FLAG_NO_RETURN_ON_SUCCESS, 10, flags, usb_activity_gpio_pin_mask); + __builtin_unreachable(); +} + + +// https://github.com/raspberrypi/pico-sdk +// src/rp2350/hardware_regs/include/hardware/regs/qmi.h + +#define QMI_DIRECT_CSR_EN_BITS 0x00000001 +#define QMI_DIRECT_CSR_RXEMPTY_BITS 0x00010000 +#define QMI_DIRECT_CSR_TXFULL_BITS 0x00000400 +#define QMI_M1_WFMT_RESET 0x00001000 +#define QMI_M1_WCMD_RESET 0x0000a002 + + +// https://github.com/raspberrypi/pico-sdk +// src/rp2350/hardware_regs/include/hardware/regs/io_qspi.h + +#define IO_QSPI_GPIO_QSPI_SS_CTRL_OUTOVER_BITS 0x00003000 +#define IO_QSPI_GPIO_QSPI_SS_CTRL_OUTOVER_LSB 12 +#define IO_QSPI_GPIO_QSPI_SS_CTRL_OUTOVER_VALUE_LOW 0x2 +#define IO_QSPI_GPIO_QSPI_SS_CTRL_OUTOVER_VALUE_HIGH 0x3 + + +// https://github.com/raspberrypi/pico-sdk +// src/rp2350/hardware_structs/include/hardware/structs/io_qspi.h + +typedef struct { + io_rw_32 inte; // IO_QSPI_PROC0_INTE + io_rw_32 intf; // IO_QSPI_PROC0_INTF + io_ro_32 ints; // IO_QSPI_PROC0_INTS +} io_qspi_irq_ctrl_hw_t; + +typedef struct { + io_ro_32 status; // IO_QSPI_GPIO_QSPI_SCLK_STATUS + io_rw_32 ctrl; // IO_QSPI_GPIO_QSPI_SCLK_CTRL +} io_qspi_status_ctrl_hw_t; + +typedef struct { + io_ro_32 usbphy_dp_status; // IO_QSPI_USBPHY_DP_STATUS + io_rw_32 usbphy_dp_ctrl; // IO_QSPI_USBPHY_DP_CTRL + io_ro_32 usbphy_dm_status; // IO_QSPI_USBPHY_DM_STATUS + io_rw_32 usbphy_dm_ctrl; // IO_QSPI_USBPHY_DM_CTRL + io_qspi_status_ctrl_hw_t io[6]; + uint32_t _pad0[112]; + io_ro_32 irqsummary_proc0_secure; // IO_QSPI_IRQSUMMARY_PROC0_SECURE + io_ro_32 irqsummary_proc0_nonsecure; // IO_QSPI_IRQSUMMARY_PROC0_NONSECURE + io_ro_32 irqsummary_proc1_secure; // IO_QSPI_IRQSUMMARY_PROC1_SECURE + io_ro_32 irqsummary_proc1_nonsecure; // IO_QSPI_IRQSUMMARY_PROC1_NONSECURE + io_ro_32 irqsummary_dormant_wake_secure; // IO_QSPI_IRQSUMMARY_DORMANT_WAKE_SECURE + io_ro_32 irqsummary_dormant_wake_nonsecure; // IO_QSPI_IRQSUMMARY_DORMANT_WAKE_NONSECURE + io_rw_32 intr; // IO_QSPI_INTR + + union { + struct { + io_qspi_irq_ctrl_hw_t proc0_irq_ctrl; + io_qspi_irq_ctrl_hw_t proc1_irq_ctrl; + io_qspi_irq_ctrl_hw_t dormant_wake_irq_ctrl; + }; + io_qspi_irq_ctrl_hw_t irq_ctrl[3]; + }; +} io_qspi_hw_t; + +#define io_qspi_hw ((io_qspi_hw_t *)IO_QSPI_BASE) + + +// https://github.com/raspberrypi/pico-sdk +// src/rp2350/hardware_structs/include/hardware/structs/qmi.h + +typedef struct { + io_rw_32 timing; // QMI_M0_TIMING + io_rw_32 rfmt; // QMI_M0_RFMT + io_rw_32 rcmd; // QMI_M0_RCMD + io_rw_32 wfmt; // QMI_M0_WFMT + io_rw_32 wcmd; // QMI_M0_WCMD +} qmi_mem_hw_t; + +typedef struct { + io_rw_32 direct_csr; // QMI_DIRECT_CSR + io_wo_32 direct_tx; // QMI_DIRECT_TX + io_ro_32 direct_rx; // QMI_DIRECT_RX + qmi_mem_hw_t m[2]; + io_rw_32 atrans[8]; // QMI_ATRANS0 +} qmi_hw_t; + +#define qmi_hw ((qmi_hw_t *)XIP_QMI_BASE) + + +// https://github.com/raspberrypi/pico-sdk +// src/rp2_common/hardware_xip_cache/include/hardware/xip_cache.h + +// Noop unless using XIP Cache-as-SRAM +// Non-noop version in src/rp2_common/hardware_xip_cache/xip_cache.c +static inline void xip_cache_clean_all(void) {} + + +// https://github.com/raspberrypi/pico-sdk +// src/rp2_common/hardware_flash/include/hardware/flash.h + +#define FLASH_PAGE_SIZE (1u << 8) +#define FLASH_SECTOR_SIZE (1u << 12) +#define FLASH_BLOCK_SIZE (1u << 16) + + +// https://github.com/raspberrypi/pico-sdk +// src/rp2_common/hardware_flash/flash.c + +#define BOOT2_SIZE_WORDS 64 +#define FLASH_BLOCK_ERASE_CMD 0xd8 + +static uint32_t boot2_copyout[BOOT2_SIZE_WORDS]; +static bool boot2_copyout_valid = false; + +static ram_func void flash_init_boot2_copyout(void) { + if (boot2_copyout_valid) + return; + for (int i = 0; i < BOOT2_SIZE_WORDS; ++i) + boot2_copyout[i] = ((uint32_t *)BOOTRAM_BASE)[i]; + __compiler_memory_barrier(); + boot2_copyout_valid = true; +} + +static ram_func void flash_enable_xip_via_boot2(void) { + ((void (*)(void))((intptr_t)boot2_copyout+1))(); +} + +// This is a static symbol because the layout of FLASH_DEVINFO is liable to change from device to +// device, so fields must have getters/setters. +static io_rw_16 * ram_func flash_devinfo_ptr(void) { + // Note the lookup returns a pointer to a 32-bit pointer literal in the ROM + io_rw_16 **p = (io_rw_16 **) rom_data_lookup_inline(ROM_DATA_FLASH_DEVINFO16_PTR); + return *p; +} + +// This is a RAM function because may be called during flash programming to enable save/restore of +// QMI window 1 registers on RP2350: +uint8_t ram_func flash_devinfo_get_cs_size(uint8_t cs) { + io_ro_16 *devinfo = (io_ro_16 *) flash_devinfo_ptr(); + if (cs == 0u) { + return (uint8_t) ( + (*devinfo & OTP_DATA_FLASH_DEVINFO_CS0_SIZE_BITS) >> OTP_DATA_FLASH_DEVINFO_CS0_SIZE_LSB + ); + } else { + return (uint8_t) ( + (*devinfo & OTP_DATA_FLASH_DEVINFO_CS1_SIZE_BITS) >> OTP_DATA_FLASH_DEVINFO_CS1_SIZE_LSB + ); + } +} + +// This is specifically for saving/restoring the registers modified by RP2350 +// flash_exit_xip() ROM func, not the entirety of the QMI window state. +typedef struct flash_rp2350_qmi_save_state { + uint32_t timing; + uint32_t rcmd; + uint32_t rfmt; +} flash_rp2350_qmi_save_state_t; + +static ram_func void flash_rp2350_save_qmi_cs1(flash_rp2350_qmi_save_state_t *state) { + state->timing = qmi_hw->m[1].timing; + state->rcmd = qmi_hw->m[1].rcmd; + state->rfmt = qmi_hw->m[1].rfmt; +} + +static ram_func void flash_rp2350_restore_qmi_cs1(const flash_rp2350_qmi_save_state_t *state) { + if (flash_devinfo_get_cs_size(1) == 0) { + // Case 1: The RP2350 ROM sets QMI to a clean (03h read) configuration + // during flash_exit_xip(), even though when CS1 is not enabled via + // FLASH_DEVINFO it does not issue an XIP exit sequence to CS1. In + // this case, restore the original register config for CS1 as it is + // still the correct config. + qmi_hw->m[1].timing = state->timing; + qmi_hw->m[1].rcmd = state->rcmd; + qmi_hw->m[1].rfmt = state->rfmt; + } else { + // Case 2: If RAM is attached to CS1, and the ROM has issued an XIP + // exit sequence to it, then the ROM re-initialisation of the QMI + // registers has actually not gone far enough. The old XIP write mode + // is no longer valid when the QSPI RAM is returned to a serial + // command state. Restore the default 02h serial write command config. + qmi_hw->m[1].wfmt = QMI_M1_WFMT_RESET; + qmi_hw->m[1].wcmd = QMI_M1_WCMD_RESET; + } +} + +void ram_func flash_cs_force(bool high) { + uint32_t field_val = high ? + IO_QSPI_GPIO_QSPI_SS_CTRL_OUTOVER_VALUE_HIGH : + IO_QSPI_GPIO_QSPI_SS_CTRL_OUTOVER_VALUE_LOW; + hw_write_masked(&io_qspi_hw->io[1].ctrl, + field_val << IO_QSPI_GPIO_QSPI_SS_CTRL_OUTOVER_LSB, + IO_QSPI_GPIO_QSPI_SS_CTRL_OUTOVER_BITS + ); +} + +// Adapted from flash_range_program() +void ram_func flash_range_write(uint32_t offset, const uint8_t *data, size_t count) { + flash_connect_internal_fn flash_connect_internal_func = (flash_connect_internal_fn)rom_func_lookup_inline(ROM_FUNC_CONNECT_INTERNAL_FLASH); + flash_exit_xip_fn flash_exit_xip_func = (flash_exit_xip_fn)rom_func_lookup_inline(ROM_FUNC_FLASH_EXIT_XIP); + flash_range_program_fn flash_range_program_func = (flash_range_program_fn)rom_func_lookup_inline(ROM_FUNC_FLASH_RANGE_PROGRAM); + flash_flush_cache_fn flash_flush_cache_func = (flash_flush_cache_fn)rom_func_lookup_inline(ROM_FUNC_FLASH_FLUSH_CACHE); + flash_init_boot2_copyout(); + xip_cache_clean_all(); + flash_rp2350_qmi_save_state_t qmi_save; + flash_rp2350_save_qmi_cs1(&qmi_save); + + __compiler_memory_barrier(); + + flash_connect_internal_func(); + flash_exit_xip_func(); + flash_range_program_func(offset, data, count); + flash_flush_cache_func(); // Note this is needed to remove CSn IO force as well as cache flushing + flash_enable_xip_via_boot2(); + flash_rp2350_restore_qmi_cs1(&qmi_save); +} + +// Adapted from flash_range_erase() +void ram_func flash_erase_blocks(uint32_t offset, size_t count) { + flash_connect_internal_fn flash_connect_internal_func = (flash_connect_internal_fn)rom_func_lookup_inline(ROM_FUNC_CONNECT_INTERNAL_FLASH); + flash_exit_xip_fn flash_exit_xip_func = (flash_exit_xip_fn)rom_func_lookup_inline(ROM_FUNC_FLASH_EXIT_XIP); + flash_range_erase_fn flash_range_erase_func = (flash_range_erase_fn)rom_func_lookup_inline(ROM_FUNC_FLASH_RANGE_ERASE); + flash_flush_cache_fn flash_flush_cache_func = (flash_flush_cache_fn)rom_func_lookup_inline(ROM_FUNC_FLASH_FLUSH_CACHE); + flash_init_boot2_copyout(); + // Commit any pending writes to external RAM, to avoid losing them in the subsequent flush: + xip_cache_clean_all(); + flash_rp2350_qmi_save_state_t qmi_save; + flash_rp2350_save_qmi_cs1(&qmi_save); + + // No flash accesses after this point + __compiler_memory_barrier(); + + flash_connect_internal_func(); + flash_exit_xip_func(); + flash_range_erase_func(offset, count, FLASH_BLOCK_SIZE, FLASH_BLOCK_ERASE_CMD); + flash_flush_cache_func(); // Note this is needed to remove CSn IO force as well as cache flushing + flash_enable_xip_via_boot2(); + flash_rp2350_restore_qmi_cs1(&qmi_save); +} + +void ram_func flash_do_cmd(const uint8_t *txbuf, uint8_t *rxbuf, size_t count) { + flash_connect_internal_fn flash_connect_internal_func = (flash_connect_internal_fn)rom_func_lookup_inline(ROM_FUNC_CONNECT_INTERNAL_FLASH); + flash_exit_xip_fn flash_exit_xip_func = (flash_exit_xip_fn)rom_func_lookup_inline(ROM_FUNC_FLASH_EXIT_XIP); + flash_flush_cache_fn flash_flush_cache_func = (flash_flush_cache_fn)rom_func_lookup_inline(ROM_FUNC_FLASH_FLUSH_CACHE); + flash_init_boot2_copyout(); + xip_cache_clean_all(); + + flash_rp2350_qmi_save_state_t qmi_save; + flash_rp2350_save_qmi_cs1(&qmi_save); + + __compiler_memory_barrier(); + flash_connect_internal_func(); + flash_exit_xip_func(); + + flash_cs_force(0); + size_t tx_remaining = count; + size_t rx_remaining = count; + + // QMI version -- no need to bound FIFO contents as QMI stalls on full DIRECT_RX. + hw_set_bits(&qmi_hw->direct_csr, QMI_DIRECT_CSR_EN_BITS); + while (tx_remaining || rx_remaining) { + uint32_t flags = qmi_hw->direct_csr; + bool can_put = !(flags & QMI_DIRECT_CSR_TXFULL_BITS); + bool can_get = !(flags & QMI_DIRECT_CSR_RXEMPTY_BITS); + if (can_put && tx_remaining) { + qmi_hw->direct_tx = *txbuf++; + --tx_remaining; + } + if (can_get && rx_remaining) { + *rxbuf++ = (uint8_t)qmi_hw->direct_rx; + --rx_remaining; + } + } + hw_clear_bits(&qmi_hw->direct_csr, QMI_DIRECT_CSR_EN_BITS); + + flash_cs_force(1); + + flash_flush_cache_func(); + flash_enable_xip_via_boot2(); + flash_rp2350_restore_qmi_cs1(&qmi_save); +} + +*/ +import "C" + +func enterBootloader() { + C.reset_usb_boot(0, 0) +} + +func doFlashCommand(tx []byte, rx []byte) error { + if len(tx) != len(rx) { + return errFlashInvalidWriteLength + } + + C.flash_do_cmd( + (*C.uint8_t)(unsafe.Pointer(&tx[0])), + (*C.uint8_t)(unsafe.Pointer(&rx[0])), + C.ulong(len(tx))) + + return nil +} + +// Flash related code +const memoryStart = C.XIP_BASE // memory start for purpose of erase + +func (f flashBlockDevice) writeAt(p []byte, off int64) (n int, err error) { + if writeAddress(off)+uintptr(C.XIP_BASE) > FlashDataEnd() { + return 0, errFlashCannotWritePastEOF + } + + state := interrupt.Disable() + defer interrupt.Restore(state) + + // rp2350 writes to offset, not actual address + // e.g. real address 0x10003000 is written to at + // 0x00003000 + address := writeAddress(off) + padded := flashPad(p, int(f.WriteBlockSize())) + + C.flash_range_write(C.uint32_t(address), + (*C.uint8_t)(unsafe.Pointer(&padded[0])), + C.ulong(len(padded))) + + return len(padded), nil +} + +func (f flashBlockDevice) eraseBlocks(start, length int64) error { + address := writeAddress(start * f.EraseBlockSize()) + if address+uintptr(C.XIP_BASE) > FlashDataEnd() { + return errFlashCannotErasePastEOF + } + + state := interrupt.Disable() + defer interrupt.Restore(state) + + C.flash_erase_blocks(C.uint32_t(address), C.ulong(length*f.EraseBlockSize())) + + return nil +} diff --git a/emb/machine/machine_rp2350_usb.go b/emb/machine/machine_rp2350_usb.go new file mode 100644 index 00000000..ca09262f --- /dev/null +++ b/emb/machine/machine_rp2350_usb.go @@ -0,0 +1,151 @@ +//go:build rp2350 + +package machine + +import ( + "device/rp" + "machine/usb" + "runtime/interrupt" +) + +// Configure the USB peripheral. The config is here for compatibility with the UART interface. +func (dev *USBDevice) Configure(config UARTConfig) { + // Reset usb controller + resetBlock(rp.RESETS_RESET_USBCTRL) + unresetBlockWait(rp.RESETS_RESET_USBCTRL) + + // Clear any previous state in dpram just in case + _usbDPSRAM.clear() + + // Enable USB interrupt at processor + rp.USB.INTE.Set(0) + intr := interrupt.New(rp.IRQ_USBCTRL_IRQ, handleUSBIRQ) + intr.SetPriority(0x00) + intr.Enable() + irqSet(rp.IRQ_USBCTRL_IRQ, true) + + // Mux the controller to the onboard usb phy + rp.USB.USB_MUXING.Set(rp.USB_USB_MUXING_TO_PHY | rp.USB_USB_MUXING_SOFTCON) + + // Force VBUS detect so the device thinks it is plugged into a host + rp.USB.USB_PWR.Set(rp.USB_USB_PWR_VBUS_DETECT | rp.USB_USB_PWR_VBUS_DETECT_OVERRIDE_EN) + + // Enable the USB controller in device mode. + rp.USB.MAIN_CTRL.Set(rp.USB_MAIN_CTRL_CONTROLLER_EN) + + // Enable an interrupt per EP0 transaction + rp.USB.SIE_CTRL.Set(rp.USB_SIE_CTRL_EP0_INT_1BUF) + + // Enable interrupts for when a buffer is done, when the bus is reset, + // and when a setup packet is received + rp.USB.INTE.Set(rp.USB_INTE_BUFF_STATUS | + rp.USB_INTE_BUS_RESET | + rp.USB_INTE_SETUP_REQ) + + // Present full speed device by enabling pull up on DP + rp.USB.SIE_CTRL.SetBits(rp.USB_SIE_CTRL_PULLUP_EN) + + // 12.7.2 Disable phy isolation + rp.USB.SetMAIN_CTRL_PHY_ISO(0x0) +} + +func handleUSBIRQ(intr interrupt.Interrupt) { + status := rp.USB.INTS.Get() + + // Setup packet received + if (status & rp.USB_INTS_SETUP_REQ) > 0 { + rp.USB.SIE_STATUS.Set(rp.USB_SIE_STATUS_SETUP_REC) + setup := usb.NewSetup(_usbDPSRAM.setupBytes()) + + ok := false + if (setup.BmRequestType & usb.REQUEST_TYPE) == usb.REQUEST_STANDARD { + // Standard Requests + ok = handleStandardSetup(setup) + } else { + // Class Interface Requests + if setup.WIndex < uint16(len(usbSetupHandler)) && usbSetupHandler[setup.WIndex] != nil { + ok = usbSetupHandler[setup.WIndex](setup) + } + } + + if !ok { + // Stall endpoint? + USBDev.SetStallEPIn(0) + } + + } + + // Buffer status, one or more buffers have completed + if (status & rp.USB_INTS_BUFF_STATUS) > 0 { + if sendOnEP0DATADONE.offset > 0 { + ep := uint32(0) + data := sendOnEP0DATADONE.data + count := len(data) - sendOnEP0DATADONE.offset + if ep == 0 && count > usb.EndpointPacketSize { + count = usb.EndpointPacketSize + } + + sendViaEPIn(ep, data[sendOnEP0DATADONE.offset:], count) + sendOnEP0DATADONE.offset += count + if sendOnEP0DATADONE.offset == len(data) { + sendOnEP0DATADONE.offset = 0 + } + } + + s2 := rp.USB.BUFF_STATUS.Get() + + // OUT (PC -> rp2350) + for i := 0; i < 16; i++ { + if s2&(1<<(i*2+1)) > 0 { + buf := handleEndpointRx(uint32(i)) + if usbRxHandler[i] == nil || usbRxHandler[i](buf) { + AckUsbOutTransfer(uint32(i)) + } + } + } + + // IN (rp2350 -> PC) + for i := 0; i < 16; i++ { + if s2&(1<<(i*2)) > 0 { + if usbTxHandler[i] != nil { + usbTxHandler[i]() + } + } + } + + rp.USB.BUFF_STATUS.Set(s2) + } + + // Bus is reset + if (status & rp.USB_INTS_BUS_RESET) > 0 { + rp.USB.SIE_STATUS.Set(rp.USB_SIE_STATUS_BUS_RESET) + //fixRP2040UsbDeviceEnumeration() + + rp.USB.ADDR_ENDP.Set(0) + initEndpoint(0, usb.ENDPOINT_TYPE_CONTROL) + } +} + +func handleUSBSetAddress(setup usb.Setup) bool { + // Using 570μs timeout which is exactly the same as SAMD21. + const ackTimeout = 570 + + rp.USB.SIE_STATUS.Set(rp.USB_SIE_STATUS_ACK_REC) + sendUSBPacket(0, []byte{}, 0) + + // Wait for transfer to complete with a timeout. + t := timer.timeElapsed() + for (rp.USB.SIE_STATUS.Get() & rp.USB_SIE_STATUS_ACK_REC) == 0 { + if dt := timer.timeElapsed() - t; dt >= ackTimeout { + return false + } + } + + // Set the device address to that requested by host. + rp.USB.ADDR_ENDP.Set(uint32(setup.WValueL) & rp.USB_ADDR_ENDP_ADDRESS_Msk) + return true +} + +func armEPZeroStall() { + rp.USB.EP_STALL_ARM.Set(rp.USB_EP_STALL_ARM_EP0_IN) +} diff --git a/emb/machine/machine_rp2_2040.go b/emb/machine/machine_rp2_2040.go new file mode 100644 index 00000000..9cdb3a07 --- /dev/null +++ b/emb/machine/machine_rp2_2040.go @@ -0,0 +1,223 @@ +//go:build rp2040 + +package machine + +import ( + "device/rp" + "runtime/volatile" + "unsafe" +) + +const ( + cpuFreq = 200 * MHz + _NUMBANK0_GPIOS = 30 + _NUMBANK0_IRQS = 4 + _NUMIRQ = 32 + rp2350ExtraReg = 0 + RESETS_RESET_Msk = 0x01ffffff + initUnreset = rp.RESETS_RESET_ADC | + rp.RESETS_RESET_RTC | + rp.RESETS_RESET_SPI0 | + rp.RESETS_RESET_SPI1 | + rp.RESETS_RESET_UART0 | + rp.RESETS_RESET_UART1 | + rp.RESETS_RESET_USBCTRL + initDontReset = rp.RESETS_RESET_IO_QSPI | + rp.RESETS_RESET_PADS_QSPI | + rp.RESETS_RESET_PLL_USB | + rp.RESETS_RESET_USBCTRL | + rp.RESETS_RESET_SYSCFG | + rp.RESETS_RESET_PLL_SYS + padEnableMask = rp.PADS_BANK0_GPIO0_IE_Msk | + rp.PADS_BANK0_GPIO0_OD_Msk +) + +const ( + PinOutput PinMode = iota + PinInput + PinInputPulldown + PinInputPullup + PinAnalog + PinUART + PinPWM + PinI2C + PinSPI + PinPIO0 + PinPIO1 +) + +// Analog pins on RP2040. +const ( + ADC0 Pin = GPIO26 + ADC1 Pin = GPIO27 + ADC2 Pin = GPIO28 + ADC3 Pin = GPIO29 + + thermADC = 30 +) + +const ( + clkGPOUT0 clockIndex = iota // GPIO Muxing 0 + clkGPOUT1 // GPIO Muxing 1 + clkGPOUT2 // GPIO Muxing 2 + clkGPOUT3 // GPIO Muxing 3 + clkRef // Watchdog and timers reference clock + clkSys // Processors, bus fabric, memory, memory mapped registers + clkPeri // Peripheral clock for UART and SPI + clkUSB // USB clock + clkADC // ADC clock + clkRTC // Real time clock + numClocks +) + +func calcClockDiv(srcFreq, freq uint32) uint32 { + // Div register is 24.8 int.frac divider so multiply by 2^8 (left shift by 8) + return uint32((uint64(srcFreq) << 8) / uint64(freq)) +} + +type clocksType struct { + clk [numClocks]clockType + resus struct { + ctrl volatile.Register32 + status volatile.Register32 + } + fc0 fc + wakeEN0 volatile.Register32 + wakeEN1 volatile.Register32 + sleepEN0 volatile.Register32 + sleepEN1 volatile.Register32 + enabled0 volatile.Register32 + enabled1 volatile.Register32 + intR volatile.Register32 + intE volatile.Register32 + intF volatile.Register32 + intS volatile.Register32 +} + +// GPIO function selectors +const ( + fnJTAG pinFunc = 0 + fnSPI pinFunc = 1 // Connect one of the internal PL022 SPI peripherals to GPIO + fnUART pinFunc = 2 + fnI2C pinFunc = 3 + // Connect a PWM slice to GPIO. There are eight PWM slices, + // each with two outputchannels (A/B). The B pin can also be used as an input, + // for frequency and duty cyclemeasurement + fnPWM pinFunc = 4 + // Software control of GPIO, from the single-cycle IO (SIO) block. + // The SIO function (F5)must be selected for the processors to drive a GPIO, + // but the input is always connected,so software can check the state of GPIOs at any time. + fnSIO pinFunc = 5 + // Connect one of the programmable IO blocks (PIO) to GPIO. PIO can implement a widevariety of interfaces, + // and has its own internal pin mapping hardware, allowing flexibleplacement of digital interfaces on bank 0 GPIOs. + // The PIO function (F6, F7) must beselected for PIO to drive a GPIO, but the input is always connected, + // so the PIOs canalways see the state of all pins. + fnPIO0, fnPIO1 pinFunc = 6, 7 + // General purpose clock inputs/outputs. Can be routed to a number of internal clock domains onRP2040, + // e.g. Input: to provide a 1 Hz clock for the RTC, or can be connected to an internalfrequency counter. + // e.g. Output: optional integer divide + fnGPCK pinFunc = 8 + // USB power control signals to/from the internal USB controller + fnUSB pinFunc = 9 + fnNULL pinFunc = 0x1f + + fnXIP pinFunc = 0 +) + +// Configure configures the gpio pin as per mode. +func (p Pin) Configure(config PinConfig) { + if p == NoPin { + return + } + p.init() + mask := uint32(1) << p + switch config.Mode { + case PinOutput: + p.setFunc(fnSIO) + rp.SIO.GPIO_OE_SET.Set(mask) + case PinInput: + p.setFunc(fnSIO) + p.pulloff() + case PinInputPulldown: + p.setFunc(fnSIO) + p.pulldown() + case PinInputPullup: + p.setFunc(fnSIO) + p.pullup() + case PinAnalog: + p.setFunc(fnNULL) + p.pulloff() + case PinUART: + p.setFunc(fnUART) + case PinPWM: + p.setFunc(fnPWM) + case PinI2C: + // IO config according to 4.3.1.3 of rp2040 datasheet. + p.setFunc(fnI2C) + p.pullup() + p.setSchmitt(true) + p.setSlew(false) + case PinSPI: + p.setFunc(fnSPI) + case PinPIO0: + p.setFunc(fnPIO0) + case PinPIO1: + p.setFunc(fnPIO1) + } +} + +var ( + timer = (*timerType)(unsafe.Pointer(rp.TIMER)) +) + +// Enable or disable a specific interrupt on the executing core. +// num is the interrupt number which must be in [0,31]. +func irqSet(num uint32, enabled bool) { + if num >= _NUMIRQ { + return + } + irqSetMask(1<= 32 { + mask := uint32(1) << (p % 32) + rp.SIO.GPIO_HI_OE_SET.Set(mask) + } else { + mask := uint32(1) << p + rp.SIO.GPIO_OE_SET.Set(mask) + } + case PinInput: + p.setFunc(fnSIO) + p.pulloff() + case PinInputPulldown: + p.setFunc(fnSIO) + p.pulldown() + case PinInputPullup: + p.setFunc(fnSIO) + p.pullup() + case PinAnalog: + p.setFunc(fnNULL) + p.pulloff() + case PinUART: + p.setFunc(fnUART) + case PinPWM: + p.setFunc(fnPWM) + case PinI2C: + // IO config according to 4.3.1.3 of rp2040 datasheet. + p.setFunc(fnI2C) + p.pullup() + p.setSchmitt(true) + p.setSlew(false) + case PinSPI: + p.setFunc(fnSPI) + case PinPIO0: + p.setFunc(fnPIO0) + case PinPIO1: + p.setFunc(fnPIO1) + case PinPIO2: + p.setFunc(fnPIO2) + } +} + +var ( + timer = (*timerType)(unsafe.Pointer(rp.TIMER0)) +) + +// Enable or disable a specific interrupt on the executing core. +// num is the interrupt number which must be in [0,_NUMIRQ). +func irqSet(num uint32, enabled bool) { + if num >= _NUMIRQ { + return + } + + register_index := num / 32 + var mask uint32 = 1 << (num % 32) + + if enabled { + // Clear pending before enable + //(if IRQ is actually asserted, it will immediately re-pend) + if register_index == 0 { + rp.PPB.NVIC_ICPR0.Set(mask) + rp.PPB.NVIC_ISER0.Set(mask) + } else { + rp.PPB.NVIC_ICPR1.Set(mask) + rp.PPB.NVIC_ISER1.Set(mask) + } + } else { + if register_index == 0 { + rp.PPB.NVIC_ICER0.Set(mask) + } else { + rp.PPB.NVIC_ICER1.Set(mask) + } + } +} + +func (clks *clocksType) initRTC() {} // No RTC on RP2350. + +func (clks *clocksType) initTicks() { + rp.TICKS.SetTIMER0_CTRL_ENABLE(0) + rp.TICKS.SetTIMER0_CYCLES(12) + rp.TICKS.SetTIMER0_CTRL_ENABLE(1) +} + +// startTick starts the watchdog tick. +// On RP2040, the watchdog contained a tick generator used to generate a 1μs tick for the watchdog. This was also +// distributed to the system timer. On RP2350, the watchdog instead takes a tick input from the system-level ticks block. See Section 8.5. +func (wd *watchdogImpl) startTick(cycles uint32) { + rp.TICKS.WATCHDOG_CTRL.SetBits(1) +} + +func adjustCoreVoltage() bool { + return false +} diff --git a/emb/machine/machine_rp2_2350a.go b/emb/machine/machine_rp2_2350a.go new file mode 100644 index 00000000..09ec8a11 --- /dev/null +++ b/emb/machine/machine_rp2_2350a.go @@ -0,0 +1,14 @@ +//go:build rp2350 && !rp2350b + +package machine + +// Analog pins on RP2350a. +const ( + ADC0 Pin = GPIO26 + ADC1 Pin = GPIO27 + ADC2 Pin = GPIO28 + ADC3 Pin = GPIO29 + + // fifth ADC channel. + thermADC = 30 +) diff --git a/emb/machine/machine_rp2_2350b.go b/emb/machine/machine_rp2_2350b.go new file mode 100644 index 00000000..bd5ceebe --- /dev/null +++ b/emb/machine/machine_rp2_2350b.go @@ -0,0 +1,48 @@ +//go:build rp2350b + +package machine + +// RP2350B has additional pins. + +const ( + GPIO30 Pin = 30 // peripherals: PWM7 channel A + GPIO31 Pin = 31 // peripherals: PWM7 channel B + GPIO32 Pin = 32 // peripherals: PWM8 channel A + GPIO33 Pin = 33 // peripherals: PWM8 channel B + GPIO34 Pin = 34 // peripherals: PWM9 channel A + GPIO35 Pin = 35 // peripherals: PWM9 channel B + GPIO36 Pin = 36 // peripherals: PWM10 channel A + GPIO37 Pin = 37 // peripherals: PWM10 channel B + GPIO38 Pin = 38 // peripherals: PWM11 channel A + GPIO39 Pin = 39 // peripherals: PWM11 channel B + GPIO40 Pin = 40 // peripherals: PWM8 channel A + GPIO41 Pin = 41 // peripherals: PWM8 channel B + GPIO42 Pin = 42 // peripherals: PWM9 channel A + GPIO43 Pin = 43 // peripherals: PWM9 channel B + GPIO44 Pin = 44 // peripherals: PWM10 channel A + GPIO45 Pin = 45 // peripherals: PWM10 channel B + GPIO46 Pin = 46 // peripherals: PWM11 channel A + GPIO47 Pin = 47 // peripherals: PWM11 channel B +) + +// Analog pins on 2350b. +const ( + ADC0 Pin = GPIO40 + ADC1 Pin = GPIO41 + ADC2 Pin = GPIO42 + ADC3 Pin = GPIO43 + ADC4 Pin = GPIO44 + ADC5 Pin = GPIO45 + ADC6 Pin = GPIO46 + ADC7 Pin = GPIO47 + // Ninth ADC channel. + thermADC = 48 +) + +// Additional PWMs on the RP2350B. +var ( + PWM8 = getPWMGroup(8) + PWM9 = getPWMGroup(9) + PWM10 = getPWMGroup(10) + PWM11 = getPWMGroup(11) +) diff --git a/emb/machine/machine_rp2_adc.go b/emb/machine/machine_rp2_adc.go new file mode 100644 index 00000000..fc9a8268 --- /dev/null +++ b/emb/machine/machine_rp2_adc.go @@ -0,0 +1,114 @@ +//go:build rp2040 || rp2350 + +package machine + +import ( + "device/rp" + "errors" + "sync" +) + +// ADCChannel is the ADC peripheral mux channel. 0-4. +type ADCChannel uint8 + +// Used to serialise ADC sampling +var adcLock sync.Mutex + +// ADC peripheral reference voltage (mV) +var adcAref uint32 + +// InitADC resets the ADC peripheral. +func InitADC() { + rp.RESETS.RESET.SetBits(rp.RESETS_RESET_ADC) + rp.RESETS.RESET.ClearBits(rp.RESETS_RESET_ADC) + for !rp.RESETS.RESET_DONE.HasBits(rp.RESETS_RESET_ADC) { + } + // enable ADC + rp.ADC.CS.Set(rp.ADC_CS_EN) + adcAref = 3300 + waitForReady() +} + +// Configure sets the ADC pin to analog input mode. +func (a ADC) Configure(config ADCConfig) error { + c, err := a.GetADCChannel() + if err != nil { + return err + } + return c.Configure(config) +} + +// Get returns a one-shot ADC sample reading. +func (a ADC) Get() uint16 { + if c, err := a.GetADCChannel(); err == nil { + return c.getOnce() + } + // Not an ADC pin! + return 0 +} + +// GetADCChannel returns the channel associated with the ADC pin. +func (a ADC) GetADCChannel() (c ADCChannel, err error) { + if a.Pin < ADC0 { + return 0, errors.New("no ADC channel for pin value") + } + return ADCChannel(a.Pin - ADC0), nil +} + +// Configure sets the channel's associated pin to analog input mode. +// The powered on temperature sensor increases ADC_AVDD current by approximately 40 μA. +func (c ADCChannel) Configure(config ADCConfig) error { + if config.Reference != 0 { + adcAref = config.Reference + } + p, err := c.Pin() + if err != nil { + return err + } + p.Configure(PinConfig{Mode: PinAnalog}) + return nil +} + +// getOnce returns a one-shot ADC sample reading from an ADC channel. +func (c ADCChannel) getOnce() uint16 { + // Make it safe to sample multiple ADC channels in separate go routines. + adcLock.Lock() + rp.ADC.CS.ReplaceBits(uint32(c), 0b111, rp.ADC_CS_AINSEL_Pos) + rp.ADC.CS.SetBits(rp.ADC_CS_START_ONCE) + + waitForReady() + adcLock.Unlock() + + // rp2040 is a 12-bit ADC, scale raw reading to 16-bits. + return uint16(rp.ADC.RESULT.Get()) << 4 +} + +// getVoltage does a one-shot sample and returns a millivolts reading. +// Integer portion is stored in the high 16 bits and fractional in the low 16 bits. +func (c ADCChannel) getVoltage() uint32 { + return (adcAref << 16) / (1 << 12) * uint32(c.getOnce()>>4) +} + +// ReadTemperature does a one-shot sample of the internal temperature sensor and returns a milli-celsius reading. +func ReadTemperature() (millicelsius int32) { + if rp.ADC.CS.Get()&rp.ADC_CS_EN == 0 { + InitADC() + } + thermChan, _ := ADC{Pin: thermADC}.GetADCChannel() + // Enable temperature sensor bias source + rp.ADC.CS.SetBits(rp.ADC_CS_TS_EN) + + // T = 27 - (ADC_voltage - 0.706)/0.001721 + return (27000<<16 - (int32(thermChan.getVoltage())-706<<16)*581) >> 16 +} + +// waitForReady spins waiting for the ADC peripheral to become ready. +func waitForReady() { + for !rp.ADC.CS.HasBits(rp.ADC_CS_READY) { + } +} + +// The Pin method returns the GPIO Pin associated with the ADC mux channel, if it has one. +func (c ADCChannel) Pin() (p Pin, err error) { + return Pin(c) + ADC0, nil +} diff --git a/emb/machine/machine_rp2_clocks.go b/emb/machine/machine_rp2_clocks.go new file mode 100644 index 00000000..dafebbe5 --- /dev/null +++ b/emb/machine/machine_rp2_clocks.go @@ -0,0 +1,236 @@ +//go:build rp2040 || rp2350 + +package machine + +import ( + "device/arm" + "device/rp" + "runtime/volatile" + "unsafe" +) + +func CPUFrequency() uint32 { + return cpuFreq +} + +// clockIndex identifies a hardware clock +type clockIndex uint8 + +type clockType struct { + ctrl volatile.Register32 + div volatile.Register32 + selected volatile.Register32 +} + +type fc struct { + refKHz volatile.Register32 + minKHz volatile.Register32 + maxKHz volatile.Register32 + delay volatile.Register32 + interval volatile.Register32 + src volatile.Register32 + status volatile.Register32 + result volatile.Register32 +} + +var clocks = (*clocksType)(unsafe.Pointer(rp.CLOCKS)) + +var configuredFreq [numClocks]uint32 + +type clock struct { + *clockType + cix clockIndex +} + +// The delay in seconds for core voltage adjustments to +// settle. Taken from the Pico SDK. +const _VREG_VOLTAGE_AUTO_ADJUST_DELAY = 1 / 1e3 + +// clock returns the clock identified by cix. +func (clks *clocksType) clock(cix clockIndex) clock { + return clock{ + &clks.clk[cix], + cix, + } +} + +// hasGlitchlessMux returns true if clock contains a glitchless multiplexer. +// +// Clock muxing consists of two components: +// +// A glitchless mux, which can be switched freely, but whose inputs must be +// free-running. +// +// An auxiliary (glitchy) mux, whose output glitches when switched, but has +// no constraints on its inputs. +// +// Not all clocks have both types of mux. +func (clk *clock) hasGlitchlessMux() bool { + return clk.cix == clkSys || clk.cix == clkRef +} + +// configure configures the clock by selecting the main clock source src +// and the auxiliary clock source auxsrc +// and finally setting the clock frequency to freq +// given the input clock source frequency srcFreq. +func (clk *clock) configure(src, auxsrc, srcFreq, freq uint32) { + if freq > srcFreq { + panic("clock frequency cannot be greater than source frequency") + } + + div := calcClockDiv(srcFreq, freq) + + // If increasing divisor, set divisor before source. Otherwise set source + // before divisor. This avoids a momentary overspeed when e.g. switching + // to a faster source and increasing divisor to compensate. + if div > clk.div.Get() { + clk.div.Set(div) + } + + // If switching a glitchless slice (ref or sys) to an aux source, switch + // away from aux *first* to avoid passing glitches when changing aux mux. + // Assume (!!!) glitchless source 0 is no faster than the aux source. + if clk.hasGlitchlessMux() && src == rp.CLOCKS_CLK_SYS_CTRL_SRC_CLKSRC_CLK_SYS_AUX { + clk.ctrl.ClearBits(rp.CLOCKS_CLK_REF_CTRL_SRC_Msk) + for !clk.selected.HasBits(1) { + } + } else + // If no glitchless mux, cleanly stop the clock to avoid glitches + // propagating when changing aux mux. Note it would be a really bad idea + // to do this on one of the glitchless clocks (clkSys, clkRef). + { + // Disable clock. On clkRef and ClkSys this does nothing, + // all other clocks have the ENABLE bit in the same position. + clk.ctrl.ClearBits(rp.CLOCKS_CLK_GPOUT0_CTRL_ENABLE_Msk) + if configuredFreq[clk.cix] > 0 { + // Delay for 3 cycles of the target clock, for ENABLE propagation. + // Note XOSC_COUNT is not helpful here because XOSC is not + // necessarily running, nor is timer... so, 3 cycles per loop: + delayCyc := configuredFreq[clkSys]/configuredFreq[clk.cix] + 1 + for delayCyc != 0 { + // This could be done more efficiently but TinyGo inline + // assembly is not yet capable enough to express that. In the + // meantime, this forces at least 3 cycles per loop. + delayCyc-- + arm.Asm("nop\nnop\nnop") + } + } + } + + // Set aux mux first, and then glitchless mux if this clock has one. + clk.ctrl.ReplaceBits(auxsrc< FlashDataEnd() { + return 0, errFlashCannotReadPastEOF + } + + data := unsafe.Slice((*byte)(unsafe.Pointer(readAddress(off))), len(p)) + copy(p, data) + + return len(p), nil +} + +// WriteAt writes the given number of bytes to the block device. +// Only word (32 bits) length data can be programmed. +// If the length of p is not long enough it will be padded with 0xFF bytes. +// This method assumes that the destination is already erased. +func (f flashBlockDevice) WriteAt(p []byte, off int64) (n int, err error) { + return f.writeAt(p, off) +} + +// Size returns the number of bytes in this block device. +func (f flashBlockDevice) Size() int64 { + return int64(FlashDataEnd() - FlashDataStart()) +} + +const writeBlockSize = 1 << 8 + +// WriteBlockSize returns the block size in which data can be written to +// memory. It can be used by a client to optimize writes, non-aligned writes +// should always work correctly. +func (f flashBlockDevice) WriteBlockSize() int64 { + return writeBlockSize +} + +const eraseBlockSizeValue = 1 << 12 + +func eraseBlockSize() int64 { + return eraseBlockSizeValue +} + +// EraseBlockSize returns the smallest erasable area on this particular chip +// in bytes. This is used for the block size in EraseBlocks. +func (f flashBlockDevice) EraseBlockSize() int64 { + return eraseBlockSize() +} + +// EraseBlocks erases the given number of blocks. An implementation may +// transparently coalesce ranges of blocks into larger bundles if the chip +// supports this. The start and len parameters are in block numbers, use +// EraseBlockSize to map addresses to blocks. +func (f flashBlockDevice) EraseBlocks(start, length int64) error { + return f.eraseBlocks(start, length) +} + +// return the correct address to be used for write +func writeAddress(off int64) uintptr { + return readAddress(off) - uintptr(memoryStart) +} + +// return the correct address to be used for reads +func readAddress(off int64) uintptr { + return FlashDataStart() + uintptr(off) +} diff --git a/emb/machine/machine_rp2_gpio.go b/emb/machine/machine_rp2_gpio.go new file mode 100644 index 00000000..25d76261 --- /dev/null +++ b/emb/machine/machine_rp2_gpio.go @@ -0,0 +1,282 @@ +//go:build rp2040 || rp2350 + +package machine + +import ( + "device/rp" + "runtime/interrupt" + "runtime/volatile" + "unsafe" +) + +type ioType struct { + status volatile.Register32 + ctrl volatile.Register32 +} + +type irqCtrl struct { + intE [_NUMBANK0_IRQS]volatile.Register32 + intF [_NUMBANK0_IRQS]volatile.Register32 + intS [_NUMBANK0_IRQS]volatile.Register32 +} + +type irqSummary struct { + proc [2]struct { + secure [2]volatile.Register32 + nonsecure [2]volatile.Register32 + } + comaWake struct { + secure [2]volatile.Register32 + nonsecure [2]volatile.Register32 + } +} + +type ioBank0Type struct { + io [_NUMBANK0_GPIOS]ioType + _ [rp2350ExtraReg][128]byte + irqsum [rp2350ExtraReg]irqSummary + intR [_NUMBANK0_IRQS]volatile.Register32 + proc0IRQctrl irqCtrl + proc1IRQctrl irqCtrl + dormantWakeIRQctrl irqCtrl +} + +var ioBank0 = (*ioBank0Type)(unsafe.Pointer(rp.IO_BANK0)) + +type padsBank0Type struct { + voltageSelect volatile.Register32 + io [_NUMBANK0_GPIOS]volatile.Register32 +} + +var padsBank0 = (*padsBank0Type)(unsafe.Pointer(rp.PADS_BANK0)) + +// pinFunc represents a GPIO function. +// +// Each GPIO can have one function selected at a time. +// Likewise, each peripheral input (e.g. UART0 RX) should only be selected +// on one GPIO at a time. If the same peripheral input is connected to multiple GPIOs, +// the peripheral sees the logical OR of these GPIO inputs. +type pinFunc uint8 + +func (p Pin) PortMaskSet() (*uint32, uint32) { + return (*uint32)(unsafe.Pointer(&rp.SIO.GPIO_OUT_SET)), 1 << p +} + +// set drives the pin high +func (p Pin) set() { + if is48Pin && p >= 32 { + mask := uint32(1) << (p % 32) + rp.SIO.GPIO_HI_OUT_SET.Set(mask) + } else { + mask := uint32(1) << p + rp.SIO.GPIO_OUT_SET.Set(mask) + } +} + +func (p Pin) PortMaskClear() (*uint32, uint32) { + return (*uint32)(unsafe.Pointer(&rp.SIO.GPIO_OUT_CLR)), 1 << p +} + +// clr drives the pin low +func (p Pin) clr() { + if is48Pin && p >= 32 { + mask := uint32(1) << (p % 32) + rp.SIO.GPIO_HI_OUT_CLR.Set(mask) + } else { + mask := uint32(1) << p + rp.SIO.GPIO_OUT_CLR.Set(mask) + } +} + +// xor toggles the pin +func (p Pin) xor() { + if is48Pin && p >= 32 { + mask := uint32(1) << (p % 32) + rp.SIO.GPIO_HI_OUT_XOR.Set(mask) + } else { + mask := uint32(1) << p + rp.SIO.GPIO_OUT_XOR.Set(mask) + } +} + +// get returns the pin value +func (p Pin) get() bool { + if is48Pin && p >= 32 { + return rp.SIO.GPIO_HI_IN.HasBits(1 << (p % 32)) + } + return rp.SIO.GPIO_IN.HasBits(1 << p) +} + +func (p Pin) ioCtrl() *volatile.Register32 { + return &ioBank0.io[p].ctrl +} + +func (p Pin) padCtrl() *volatile.Register32 { + return &padsBank0.io[p] +} + +func (p Pin) pullup() { + p.padCtrl().SetBits(rp.PADS_BANK0_GPIO0_PUE) + p.padCtrl().ClearBits(rp.PADS_BANK0_GPIO0_PDE) +} + +func (p Pin) pulldown() { + p.padCtrl().SetBits(rp.PADS_BANK0_GPIO0_PDE) + p.padCtrl().ClearBits(rp.PADS_BANK0_GPIO0_PUE) +} + +func (p Pin) pulloff() { + p.padCtrl().ClearBits(rp.PADS_BANK0_GPIO0_PDE) + p.padCtrl().ClearBits(rp.PADS_BANK0_GPIO0_PUE) +} + +// setSlew sets pad slew rate control. +// true sets to fast. false sets to slow. +func (p Pin) setSlew(sr bool) { + p.padCtrl().ReplaceBits(boolToBit(sr)<= 32 { + mask := uint32(1) << (p % 32) + rp.SIO.GPIO_HI_OE_CLR.Set(mask) + } else { + mask := uint32(1) << p + rp.SIO.GPIO_OE_CLR.Set(mask) + } + p.clr() +} + +// Set drives the pin high if value is true else drives it low. +func (p Pin) Set(value bool) { + if p == NoPin { + return + } + if value { + p.set() + } else { + p.clr() + } +} + +// Get reads the pin value. +func (p Pin) Get() bool { + return p.get() +} + +// PinChange represents one or more trigger events that can happen on a given GPIO pin +// on the RP2040. ORed PinChanges are valid input to most IRQ functions. +type PinChange uint8 + +// Pin change interrupt constants for SetInterrupt. +const ( + // Edge falling + PinFalling PinChange = 4 << iota + // Edge rising + PinRising + + PinToggle = PinFalling | PinRising +) + +// Callbacks to be called for pins configured with SetInterrupt. +var ( + pinCallbacks [2][_NUMBANK0_GPIOS]func(Pin) + setInt [2][_NUMBANK0_GPIOS]bool +) + +// SetInterrupt sets an interrupt to be executed when a particular pin changes +// state. The pin should already be configured as an input, including a pull up +// or down if no external pull is provided. +// +// This call will replace a previously set callback on this pin. You can pass a +// nil func to unset the pin change interrupt. If you do so, the change +// parameter is ignored and can be set to any value (such as 0). +func (p Pin) SetInterrupt(change PinChange, callback func(Pin)) error { + if p == NoPin { + return nil + } + if p > 31 || p < 0 { + return ErrInvalidInputPin + } + core := CurrentCore() + if callback == nil { + // disable current interrupt + p.setInterrupt(change, false) + pinCallbacks[core][p] = nil + return nil + } + + if pinCallbacks[core][p] != nil { + // Callback already configured. Should disable callback by passing a nil callback first. + return ErrNoPinChangeChannel + } + p.setInterrupt(change, true) + pinCallbacks[core][p] = callback + + if setInt[core][p] { + // interrupt has already been set. Exit. + return nil + } + interrupt.New(rp.IRQ_IO_IRQ_BANK0, gpioHandleInterrupt).Enable() + irqSet(rp.IRQ_IO_IRQ_BANK0, true) + return nil +} + +// gpioHandleInterrupt finds the corresponding pin for the interrupt. +// C SDK equivalent of gpio_irq_handler +func gpioHandleInterrupt(intr interrupt.Interrupt) { + + core := CurrentCore() + var gpio Pin + for gpio = 0; gpio < _NUMBANK0_GPIOS; gpio++ { + var base *irqCtrl + switch core { + case 0: + base = &ioBank0.proc0IRQctrl + case 1: + base = &ioBank0.proc1IRQctrl + } + + statreg := base.intS[gpio>>3].Get() + change := getIntChange(gpio, statreg) + if change != 0 { + gpio.acknowledgeInterrupt(change) + callback := pinCallbacks[core][gpio] + if callback != nil { + callback(gpio) + } + } + } +} + +// events returns the bit representation of the pin change for the rp2040. +func (change PinChange) events() uint32 { + return uint32(change) +} + +// intBit is the bit storage form of a PinChange for a given Pin +// in the IO_BANK0 interrupt registers (page 269 RP2040 Datasheet). +func (p Pin) ioIntBit(change PinChange) uint32 { + return change.events() << (4 * (p % 8)) +} + +// Acquire interrupt data from a INT status register. +func getIntChange(p Pin, status uint32) PinChange { + return PinChange(status>>(4*(p%8))) & 0xf +} diff --git a/emb/machine/machine_rp2_i2c.go b/emb/machine/machine_rp2_i2c.go new file mode 100644 index 00000000..54a5e535 --- /dev/null +++ b/emb/machine/machine_rp2_i2c.go @@ -0,0 +1,639 @@ +//go:build rp2040 || rp2350 + +package machine + +import ( + "device/rp" + "errors" + "internal/itoa" +) + +// I2C on the RP2040/RP2350 +var ( + I2C0 = &_I2C0 + _I2C0 = I2C{ + Bus: rp.I2C0, + } + I2C1 = &_I2C1 + _I2C1 = I2C{ + Bus: rp.I2C1, + } +) + +// The I2C target implementation is based on the C implementation from +// here: https://github.com/vmilea/pico_i2c_slave + +// Features: Taken from datasheet. +// Default controller mode, with target mode available (not simultaneously). +// Default target address of RP2040: 0x055 +// Supports 10-bit addressing in controller mode +// 16-element transmit buffer +// 16-element receive buffer +// Can be driven from DMA +// Can generate interrupts +// Fast mode plus max transfer speed (1000kb/s) + +// GPIO config +// Each controller must connect its clock SCL and data SDA to one pair of GPIOs. +// The I2C standard requires that drivers drivea signal low, or when not driven the signal will be pulled high. +// This applies to SCL and SDA. The GPIO pads should be configured for: +// Pull-up enabled +// Slew rate limited +// Schmitt trigger enabled +// Note: There should also be external pull-ups on the board as the internal pad pull-ups may not be strong enough to pull upexternal circuits. + +// I2CConfig is used to store config info for I2C. +type I2CConfig struct { + Frequency uint32 + // SDA/SCL Serial Data and clock pins. Refer to datasheet to see + // which pins match the desired bus. + SDA, SCL Pin + Mode I2CMode +} + +type I2C struct { + Bus *rp.I2C0_Type + mode I2CMode + txInProgress bool +} + +var ( + errInvalidI2CBaudrate = errors.New("i2c: invalid baudrate") + errInvalidTgtAddr = errors.New("i2c: invalid target address: not in 0..0x80 or is reserved") + errI2CGeneric = errors.New("i2c: generic error") + errI2CDisable = errors.New("i2c: peripheral timeout in disable") + errInvalidI2CSDA = errors.New("i2c: invalid SDA pin") + errInvalidI2CSCL = errors.New("i2c: invalid SCL pin") + errI2CAlreadyListening = errors.New("i2c: already listening") + errI2CWrongMode = errors.New("i2c: wrong mode") + errI2CUnderflow = errors.New("i2c: underflow") +) + +// Tx performs a write and then a read transfer placing the result in +// in r. +// +// Passing a nil value for w or r skips the transfer corresponding to write +// or read, respectively. +// +// i2c.Tx(addr, nil, r) +// +// Performs only a read transfer. +// +// i2c.Tx(addr, w, nil) +// +// Performs only a write transfer. +func (i2c *I2C) Tx(addr uint16, w, r []byte) error { + if i2c.mode != I2CModeController { + return errI2CWrongMode + } + return i2c.tx(uint8(addr), w, r) +} + +// Listen starts listening for I2C requests sent to specified address +// +// addr is the address to listen to +func (i2c *I2C) Listen(addr uint16) error { + if i2c.mode != I2CModeTarget { + return errI2CWrongMode + } + + return i2c.listen(uint8(addr)) +} + +// Configure initializes i2c peripheral and configures I2C config's pins passed. +// Here's a list of valid SDA and SCL GPIO pins on bus I2C0 of the rp2040: +// +// SDA: 0, 4, 8, 12, 16, 20 +// SCL: 1, 5, 9, 13, 17, 21 +// +// Same as above for I2C1 bus: +// +// SDA: 2, 6, 10, 14, 18, 26 +// SCL: 3, 7, 11, 15, 19, 27 +func (i2c *I2C) Configure(config I2CConfig) error { + const defaultBaud uint32 = 100_000 // 100kHz standard mode + if config.SCL == 0 && config.SDA == 0 { + // If config pins are zero valued or clock pin is invalid then we set default values. + switch i2c.Bus { + case rp.I2C0: + config.SCL = I2C0_SCL_PIN + config.SDA = I2C0_SDA_PIN + case rp.I2C1: + config.SCL = I2C1_SCL_PIN + config.SDA = I2C1_SDA_PIN + } + } + var okSCL, okSDA bool + switch i2c.Bus { + case rp.I2C0: + okSCL = (config.SCL+3)%4 == 0 + okSDA = (config.SDA+4)%4 == 0 + case rp.I2C1: + okSCL = (config.SCL+1)%4 == 0 + okSDA = (config.SDA+2)%4 == 0 + } + + switch { + case !okSCL: + return errInvalidI2CSCL + case !okSDA: + return errInvalidI2CSDA + } + + if config.Frequency == 0 { + config.Frequency = defaultBaud + } + config.SDA.Configure(PinConfig{PinI2C}) + config.SCL.Configure(PinConfig{PinI2C}) + return i2c.init(config) +} + +// SetBaudRate sets the I2C frequency. It has the side effect of also +// enabling the I2C hardware if disabled beforehand. +// +//go:inline +func (i2c *I2C) SetBaudRate(br uint32) error { + + if br == 0 { + return errInvalidI2CBaudrate + } + + // I2C is synchronous design that runs from clk_sys + freqin := CPUFrequency() + + // TODO there are some subtleties to I2C timing which we are completely ignoring here + period := (freqin + br/2) / br + lcnt := period * 3 / 5 // oof this one hurts + hcnt := period - lcnt + // Check for out-of-range divisors: + if hcnt > rp.I2C0_IC_FS_SCL_HCNT_IC_FS_SCL_HCNT_Msk || hcnt < 8 || lcnt > rp.I2C0_IC_FS_SCL_LCNT_IC_FS_SCL_LCNT_Msk || lcnt < 8 { + return errInvalidI2CBaudrate + } + + // Per I2C-bus specification a device in standard or fast mode must + // internally provide a hold time of at least 300ns for the SDA signal to + // bridge the undefined region of the falling edge of SCL. A smaller hold + // time of 120ns is used for fast mode plus. + + // sda_tx_hold_count = freq_in [cycles/s] * 300ns * (1s / 1e9ns) + // Reduce 300/1e9 to 3/1e7 to avoid numbers that don't fit in uint. + // Add 1 to avoid division truncation. + sdaTxHoldCnt := ((freqin * 3) / 10000000) + 1 + if br >= 1_000_000 { + // sda_tx_hold_count = freq_in [cycles/s] * 120ns * (1s / 1e9ns) + // Reduce 120/1e9 to 3/25e6 to avoid numbers that don't fit in uint. + // Add 1 to avoid division truncation. + sdaTxHoldCnt = ((freqin * 3) / 25000000) + 1 + } + + if sdaTxHoldCnt > lcnt-2 { + return errInvalidI2CBaudrate + } + err := i2c.disable() + if err != nil { + return err + } + // Always use "fast" mode (<= 400 kHz, works fine for standard mode too) + + i2c.Bus.IC_CON.ReplaceBits(rp.I2C0_IC_CON_SPEED_FAST< deadline { + return errI2CDisable + } + } + return nil +} + +//go:inline +func (i2c *I2C) init(config I2CConfig) error { + i2c.reset() + if err := i2c.disable(); err != nil { + return err + } + + i2c.mode = config.Mode + + // Configure as fast-mode with RepStart support, 7-bit addresses + mode := uint32(rp.I2C0_IC_CON_SPEED_FAST<= 0x80 || isReservedI2CAddr(addr) { + return errInvalidTgtAddr + } + txlen := len(tx) + rxlen := len(rx) + // Quick return if possible. + if txlen == 0 && rxlen == 0 { + return nil + } + + err = i2c.disable() + if err != nil { + return err + } + i2c.Bus.IC_TAR.Set(uint32(addr)) + i2c.enable() + abort := false + var abortReason i2cAbortError + txStop := rxlen == 0 + for txCtr := 0; txCtr < txlen; txCtr++ { + if abort { + break + } + first := txCtr == 0 + last := txCtr == txlen-1 && rxlen == 0 + i2c.Bus.IC_DATA_CMD.Set( + (boolToBit(first) << rp.I2C0_IC_DATA_CMD_RESTART_Pos) | + (boolToBit(last && txStop) << rp.I2C0_IC_DATA_CMD_STOP_Pos) | + uint32(tx[txCtr])) + + // Wait until the transmission of the address/data from the internal + // shift register has completed. For this to function correctly, the + // TX_EMPTY_CTRL flag in IC_CON must be set. The TX_EMPTY_CTRL flag + // was set in i2c_init. + + // IC_RAW_INTR_STAT_TX_EMPTY: This bit is set to 1 when the transmit buffer is at or below + // the threshold value set in the IC_TX_TL register and the + // transmission of the address/data from the internal shift + // register for the most recently popped command is + // completed. It is automatically cleared by hardware when + // the buffer level goes above the threshold. When + // IC_ENABLE[0] is set to 0, the TX FIFO is flushed and held + // in reset. There the TX FIFO looks like it has no data within + // it, so this bit is set to 1, provided there is activity in the + // controller or target state machines. When there is no longer + // any activity, then with ic_en=0, this bit is set to 0. + for !i2c.interrupted(rp.I2C0_IC_RAW_INTR_STAT_TX_EMPTY) { + if ticks() > deadline { + return errI2CWriteTimeout // If there was a timeout, don't attempt to do anything else. + } + + before := ticks() + gosched() + deadline += ticks() - before + } + + abortReason = i2c.getAbortReason() + if abortReason != 0 { + i2c.clearAbortReason() + abort = true + } + if abort || last { + // If the transaction was aborted or if it completed + // successfully wait until the STOP condition has occurred. + + // TODO Could there be an abort while waiting for the STOP + // condition here? If so, additional code would be needed here + // to take care of the abort. + for !i2c.interrupted(rp.I2C0_IC_RAW_INTR_STAT_STOP_DET) { + if ticks() > deadline { + if abort { + return abortReason + } + return errI2CWriteTimeout + } + + before := ticks() + gosched() + deadline += ticks() - before + } + i2c.Bus.IC_CLR_STOP_DET.Get() + } + } + + // Midway check for abort. Related issue https://github.com/tinygo-org/tinygo/issues/3671. + // The root cause for an abort after writing registers was "tx data no ack" (abort code=8). + // If the abort code was not registered then the whole peripheral would remain in disabled state forever. + abortReason = i2c.getAbortReason() + if abortReason != 0 { + i2c.clearAbortReason() + abort = true + } + + rxStart := txlen == 0 + if rxlen > 0 && !abort { + for rxCtr := 0; rxCtr < rxlen; rxCtr++ { + first := rxCtr == 0 + last := rxCtr == rxlen-1 + for i2c.writeAvailable() == 0 { + before := ticks() + gosched() + deadline += ticks() - before + } + i2c.Bus.IC_DATA_CMD.Set( + boolToBit(first && rxStart)< 1 for read + + for !abort && i2c.readAvailable() == 0 { + abortReason = i2c.getAbortReason() + if abortReason != 0 { + i2c.clearAbortReason() + abort = true + } + if ticks() > deadline { + return errI2CReadTimeout // If there was a timeout, don't attempt to do anything else. + } + + before := ticks() + gosched() + deadline += ticks() - before + } + if abort { + break + } + rx[rxCtr] = uint8(i2c.Bus.IC_DATA_CMD.Get()) + } + } + // From Pico SDK: A lot of things could have just happened due to the ingenious and + // creative design of I2C. Try to figure things out. + if abort { + switch { + case abortReason == 0 || abortReason&rp.I2C0_IC_TX_ABRT_SOURCE_ABRT_7B_ADDR_NOACK != 0: + // No reported errors - seems to happen if there is nothing connected to the bus. + // Address byte not acknowledged + err = errI2CGeneric + case abortReason&rp.I2C0_IC_TX_ABRT_SOURCE_ABRT_TXDATA_NOACK != 0: + // Address acknowledged, some data not acknowledged + fallthrough + default: + err = abortReason + } + } + return err +} + +// listen sets up for async handling of requests on the I2C bus. +func (i2c *I2C) listen(addr uint8) error { + if addr >= 0x80 || isReservedI2CAddr(addr) { + return errInvalidTgtAddr + } + + err := i2c.disable() + if err != nil { + return err + } + + i2c.Bus.IC_SAR.Set(uint32(addr)) + + i2c.enable() + + return nil +} + +func (i2c *I2C) WaitForEvent(buf []byte) (evt I2CTargetEvent, count int, err error) { + rxPtr := 0 + for { + stat := i2c.Bus.IC_RAW_INTR_STAT.Get() + + if stat&rp.I2C0_IC_INTR_MASK_M_RX_FULL != 0 { + b := uint8(i2c.Bus.IC_DATA_CMD.Get()) + if rxPtr < len(buf) { + buf[rxPtr] = b + rxPtr++ + } + } + + // Stop + if stat&rp.I2C0_IC_INTR_MASK_M_STOP_DET != 0 { + if rxPtr > 0 { + return I2CReceive, rxPtr, nil + } + + i2c.Bus.IC_CLR_STOP_DET.Get() // clear + return I2CFinish, 0, nil + } + + // Start or restart - ignore start, return on restart + if stat&rp.I2C0_IC_INTR_MASK_M_START_DET != 0 { + i2c.Bus.IC_CLR_START_DET.Get() // clear restart + + // Restart + if rxPtr > 0 { + return I2CReceive, rxPtr, nil + } + } + + // Read request - leave flag set until we start to reply. + if stat&rp.I2C0_IC_INTR_MASK_M_RD_REQ != 0 { + return I2CRequest, 0, nil + } + + gosched() + } +} + +func (i2c *I2C) Reply(buf []byte) error { + txPtr := 0 + + stat := i2c.Bus.IC_RAW_INTR_STAT.Get() + + if stat&rp.I2C0_IC_INTR_MASK_M_RD_REQ == 0 { + return errI2CWrongMode + } + i2c.Bus.IC_CLR_RD_REQ.Get() // clear restart + + // Clear any dangling TX abort + if stat&rp.I2C0_IC_INTR_MASK_M_TX_ABRT != 0 { + i2c.Bus.IC_CLR_TX_ABRT.Get() + } + + for txPtr < len(buf) { + if i2c.Bus.GetIC_RAW_INTR_STAT_TX_EMPTY() != 0 { + i2c.Bus.SetIC_DATA_CMD_DAT(uint32(buf[txPtr])) + txPtr++ + // The DW_apb_i2c flushes/resets/empties the + // TX_FIFO and RX_FIFO whenever there is a transmit abort + // caused by any of the events tracked by the + // IC_TX_ABRT_SOURCE register. + // In other words, it's safe to block until TX FIFO is + // EMPTY--it will empty from being transmitted or on error. + for i2c.Bus.GetIC_RAW_INTR_STAT_TX_EMPTY() == 0 { + } + } + + // This Tx abort is a normal case - we're sending more + // data than controller wants to receive + if i2c.Bus.GetIC_RAW_INTR_STAT_TX_ABRT() != 0 { + i2c.Bus.GetIC_CLR_TX_ABRT_CLR_TX_ABRT() + return nil + } + + gosched() + } + + return nil +} + +// writeAvailable determines non-blocking write space available +// +//go:inline +func (i2c *I2C) writeAvailable() uint32 { + return rp.I2C0_IC_COMP_PARAM_1_TX_BUFFER_DEPTH_Pos - i2c.Bus.IC_TXFLR.Get() +} + +// readAvailable determines number of bytes received +// +//go:inline +func (i2c *I2C) readAvailable() uint32 { + return i2c.Bus.IC_RXFLR.Get() +} + +// Equivalent to IC_CLR_TX_ABRT.Get() (side effect clears ABORT_REASON) +// +//go:inline +func (i2c *I2C) clearAbortReason() { + // Note clearing the abort flag also clears the reason, and + // this instance of flag is clear-on-read! Note also the + // IC_CLR_TX_ABRT register always reads as 0. + i2c.Bus.IC_CLR_TX_ABRT.Get() +} + +// getAbortReason reads IC_TX_ABRT_SOURCE register. +// +//go:inline +func (i2c *I2C) getAbortReason() i2cAbortError { + return i2cAbortError(i2c.Bus.IC_TX_ABRT_SOURCE.Get()) +} + +// returns true if RAW_INTR_STAT bits in mask are all set. performs: +// +// RAW_INTR_STAT & mask == mask +// +//go:inline +func (i2c *I2C) interrupted(mask uint32) bool { + reg := i2c.Bus.IC_RAW_INTR_STAT.Get() + return reg&mask == mask +} + +type i2cAbortError uint32 + +func (b i2cAbortError) Error() string { + return "i2c abort, reason " + itoa.Uitoa(uint(b)) +} + +func (b i2cAbortError) Reasons() (reasons []string) { + if b == 0 { + return nil + } + if b&rp.I2C0_IC_TX_ABRT_SOURCE_ABRT_7B_ADDR_NOACK != 0 { + reasons = append(reasons, "7-bit address no ack") + } + if b&rp.I2C0_IC_TX_ABRT_SOURCE_ABRT_10ADDR1_NOACK != 0 { + reasons = append(reasons, "10-bit address first byte no ack") + } + if b&rp.I2C0_IC_TX_ABRT_SOURCE_ABRT_10ADDR2_NOACK != 0 { + reasons = append(reasons, "10-bit address second byte no ack") + } + if b&rp.I2C0_IC_TX_ABRT_SOURCE_ABRT_TXDATA_NOACK != 0 { + reasons = append(reasons, "tx data no ack") + } + if b&rp.I2C0_IC_TX_ABRT_SOURCE_ABRT_GCALL_NOACK != 0 { + reasons = append(reasons, "general call no ack") + } + if b&rp.I2C0_IC_TX_ABRT_SOURCE_ABRT_GCALL_READ != 0 { + reasons = append(reasons, "general call read") + } + if b&rp.I2C0_IC_TX_ABRT_SOURCE_ABRT_HS_ACKDET != 0 { + reasons = append(reasons, "high speed ack detect") + } + if b&rp.I2C0_IC_TX_ABRT_SOURCE_ABRT_SBYTE_ACKDET != 0 { + reasons = append(reasons, "start byte ack detect") + } + if b&rp.I2C0_IC_TX_ABRT_SOURCE_ABRT_HS_NORSTRT != 0 { + reasons = append(reasons, "high speed no restart") + } + if b&rp.I2C0_IC_TX_ABRT_SOURCE_ABRT_SBYTE_NORSTRT != 0 { + reasons = append(reasons, "start byte no restart") + } + if b&rp.I2C0_IC_TX_ABRT_SOURCE_ABRT_10B_RD_NORSTRT != 0 { + reasons = append(reasons, "10-bit read no restart") + } + if b&rp.I2C0_IC_TX_ABRT_SOURCE_ABRT_MASTER_DIS != 0 { + reasons = append(reasons, "master disabled") + } + if b&rp.I2C0_IC_TX_ABRT_SOURCE_ARB_LOST != 0 { + reasons = append(reasons, "arbitration lost") + } + if b&rp.I2C0_IC_TX_ABRT_SOURCE_ABRT_SLVFLUSH_TXFIFO != 0 { + reasons = append(reasons, "slave flush tx fifo") + } + if b&rp.I2C0_IC_TX_ABRT_SOURCE_ABRT_SLV_ARBLOST != 0 { + reasons = append(reasons, "slave arbitration lost") + } + if b&rp.I2C0_IC_TX_ABRT_SOURCE_ABRT_SLVRD_INTX != 0 { + reasons = append(reasons, "slave read while inactive") + } + if b&rp.I2C0_IC_TX_ABRT_SOURCE_ABRT_USER_ABRT != 0 { + reasons = append(reasons, "user abort") + } + return reasons +} diff --git a/emb/machine/machine_rp2_pins.go b/emb/machine/machine_rp2_pins.go new file mode 100644 index 00000000..36e9bd62 --- /dev/null +++ b/emb/machine/machine_rp2_pins.go @@ -0,0 +1,37 @@ +//go:build rp2040 || rp2350 || gopher_badge || pico + +package machine + +const ( + // GPIO pins + GPIO0 Pin = 0 // peripherals: PWM0 channel A + GPIO1 Pin = 1 // peripherals: PWM0 channel B + GPIO2 Pin = 2 // peripherals: PWM1 channel A + GPIO3 Pin = 3 // peripherals: PWM1 channel B + GPIO4 Pin = 4 // peripherals: PWM2 channel A + GPIO5 Pin = 5 // peripherals: PWM2 channel B + GPIO6 Pin = 6 // peripherals: PWM3 channel A + GPIO7 Pin = 7 // peripherals: PWM3 channel B + GPIO8 Pin = 8 // peripherals: PWM4 channel A + GPIO9 Pin = 9 // peripherals: PWM4 channel B + GPIO10 Pin = 10 // peripherals: PWM5 channel A + GPIO11 Pin = 11 // peripherals: PWM5 channel B + GPIO12 Pin = 12 // peripherals: PWM6 channel A + GPIO13 Pin = 13 // peripherals: PWM6 channel B + GPIO14 Pin = 14 // peripherals: PWM7 channel A + GPIO15 Pin = 15 // peripherals: PWM7 channel B + GPIO16 Pin = 16 // peripherals: PWM0 channel A + GPIO17 Pin = 17 // peripherals: PWM0 channel B + GPIO18 Pin = 18 // peripherals: PWM1 channel A + GPIO19 Pin = 19 // peripherals: PWM1 channel B + GPIO20 Pin = 20 // peripherals: PWM2 channel A + GPIO21 Pin = 21 // peripherals: PWM2 channel B + GPIO22 Pin = 22 // peripherals: PWM3 channel A + GPIO23 Pin = 23 // peripherals: PWM3 channel B + GPIO24 Pin = 24 // peripherals: PWM4 channel A + GPIO25 Pin = 25 // peripherals: PWM4 channel B + GPIO26 Pin = 26 // peripherals: PWM5 channel A + GPIO27 Pin = 27 // peripherals: PWM5 channel B + GPIO28 Pin = 28 // peripherals: PWM6 channel A + GPIO29 Pin = 29 // peripherals: PWM6 channel B +) diff --git a/emb/machine/machine_rp2_pll.go b/emb/machine/machine_rp2_pll.go new file mode 100644 index 00000000..d5760842 --- /dev/null +++ b/emb/machine/machine_rp2_pll.go @@ -0,0 +1,279 @@ +//go:build rp2040 || rp2350 + +package machine + +import ( + "device/rp" + "errors" + "math" + "math/bits" + "runtime/volatile" + "unsafe" +) + +type pll struct { + cs volatile.Register32 + pwr volatile.Register32 + fbDivInt volatile.Register32 + prim volatile.Register32 +} + +var ( + pllSys = (*pll)(unsafe.Pointer(rp.PLL_SYS)) + pllUSB = (*pll)(unsafe.Pointer(rp.PLL_USB)) +) + +// init initializes pll (Sys or USB) given the following parameters. +// +// Input clock divider, refdiv. +// +// Requested output frequency from the VCO (voltage controlled oscillator), vcoFreq. +// +// Post Divider 1, postDiv1 with range 1-7 and be >= postDiv2. +// +// Post Divider 2, postDiv2 with range 1-7. +func (pll *pll) init(refdiv, fbdiv, postDiv1, postDiv2 uint32) { + refFreq := xoscFreq / refdiv + + // What are we multiplying the reference clock by to get the vco freq + // (The regs are called div, because you divide the vco output and compare it to the refclk) + + // Check fbdiv range + if !(fbdiv >= 16 && fbdiv <= 320) { + panic("fbdiv should be in the range [16,320]") + } + + // Check divider ranges + if !((postDiv1 >= 1 && postDiv1 <= 7) && (postDiv2 >= 1 && postDiv2 <= 7)) { + panic("postdiv1, postdiv1 should be in the range [1,7]") + } + + // postDiv1 should be >= postDiv2 + // from appnote page 11 + // postdiv1 is designed to operate with a higher input frequency + // than postdiv2 + if postDiv1 < postDiv2 { + panic("postdiv1 should be greater than or equal to postdiv2") + } + + // Check that reference frequency is no greater than vcoFreq / 16 + vcoFreq := calcVCO(xoscFreq, fbdiv, refdiv) + if refFreq > vcoFreq/16 { + panic("reference frequency should not be greater than vco frequency divided by 16") + } + + // div1 feeds into div2 so if div1 is 5 and div2 is 2 then you get a divide by 10 + pdiv := uint32(postDiv1)< maxVCO { + break + } + calcPD12 := vco / targetFreq + if calcPD12 < 1 { + calcPD12 = 1 + } else if calcPD12 > 49 { + calcPD12 = 49 + } + iters++ + pd1 = pdTable[calcPD12].hivco[0] + pd2 = pdTable[calcPD12].hivco[1] + fout, err := pllFreqOutPostdiv(xoscRef, fbdiv, MHz, refdiv, pd1, pd2) + found := false + margin := abs(int64(fout) - int64(targetFreq)) + if err == nil && margin <= bestMargin { + found = true + bestFreq = fout + bestFbdiv = fbdiv + bestpd1 = pd1 + bestpd2 = pd2 + bestRefdiv = refdiv + bestMargin = margin + } + pd1 = pdTable[calcPD12].lovco[0] + pd2 = pdTable[calcPD12].lovco[1] + fout, err = pllFreqOutPostdiv(xoscRef, fbdiv, MHz, refdiv, pd1, pd2) + margin = abs(int64(fout) - int64(targetFreq)) + if err == nil && margin <= bestMargin { + found = true + bestFreq = fout + bestFbdiv = fbdiv + bestpd1 = pd1 + bestpd2 = pd2 + bestRefdiv = refdiv + bestMargin = margin + } + if found && ps.LowerVCO { + break + } + } + } + if bestFreq == 0 { + return fbdiv, refdiv, pd1, pd2, errors.New("no best frequency found") + } + return bestFbdiv, bestRefdiv, bestpd1, bestpd2, nil +} + +func abs(a int64) int64 { + if a == math.MinInt64 { + return math.MaxInt64 + } else if a < 0 { + return -a + } + return a +} + +func pllFreqOutPostdiv(xosc, fbdiv, MHz uint64, refdiv, postdiv1, postdiv2 uint8) (foutpostdiv uint64, err error) { + // testing grounds. + const ( + mhz = 1 + cfref = 12 * mhz // given by crystal oscillator selection. + crefd = 1 + cfbdiv = 100 + cvco = cfref * cfbdiv / crefd + cpd1 = 6 + cpd2 = 2 + foutpd = (cfref / crefd) * cfbdiv / (cpd1 * cpd2) + ) + refFreq := xosc / uint64(refdiv) + overflow, vco := bits.Mul64(xosc, fbdiv) + vco /= uint64(refdiv) + foutpostdiv = vco / uint64(postdiv1*postdiv2) + switch { + case refdiv < 1 || refdiv > 63: + err = errors.New("reference divider out of range") + case fbdiv < 16 || fbdiv > 320: + err = errors.New("feedback divider out of range") + case postdiv1 < 1 || postdiv1 > 7: + err = errors.New("postdiv1 out of range") + case postdiv2 < 1 || postdiv2 > 7: + err = errors.New("postdiv2 out of range") + case postdiv1 < postdiv2: + err = errors.New("user error: use higher value for postdiv1 for lower power consumption") + case vco < 750*MHz || vco > 1600*MHz: + err = errors.New("VCO out of range") + case refFreq < 5*MHz: + err = errors.New("minimum reference frequency breach") + case refFreq > vco/16: + err = errors.New("maximum reference frequency breach") + case vco > 1200*MHz && vco < 1600*MHz && xosc < 75*MHz && refdiv != 1: + err = errors.New("refdiv should be 1 for given VCO and reference frequency") + case overflow != 0: + err = errVCOOverflow + } + if err != nil { + return 0, err + } + return foutpostdiv, nil +} + +func calcVCO(xoscFreq, fbdiv, refdiv uint32) uint32 { + const maxXoscMHz = math.MaxUint32 / 320 / MHz // 13MHz maximum xosc apparently. + if fbdiv > 320 || xoscFreq > math.MaxUint32/320 { + panic("invalid VCO calculation args") + } + return xoscFreq * fbdiv / refdiv +} + +var pdTable = [50]struct { + hivco [2]uint8 + lovco [2]uint8 +}{} + +func genTable() { + if pdTable[1].hivco[1] != 0 { + return // Already generated. + } + for product := 1; product < len(pdTable); product++ { + bestProdhi := 255 + bestProdlo := 255 + for pd1 := 7; pd1 > 0; pd1-- { + for pd2 := pd1; pd2 > 0; pd2-- { + gotprod := pd1 * pd2 + if abs(int64(gotprod-product)) < abs(int64(bestProdlo-product)) { + bestProdlo = gotprod + pdTable[product].lovco[0] = uint8(pd1) + pdTable[product].lovco[1] = uint8(pd2) + } + } + } + for pd1 := 1; pd1 < 8; pd1++ { + for pd2 := 1; pd2 <= pd1; pd2++ { + gotprod := pd1 * pd2 + if abs(int64(gotprod-product)) < abs(int64(bestProdhi-product)) { + bestProdhi = gotprod + pdTable[product].hivco[0] = uint8(pd1) + pdTable[product].hivco[1] = uint8(pd2) + } + } + } + } +} diff --git a/emb/machine/machine_rp2_pwm.go b/emb/machine/machine_rp2_pwm.go new file mode 100644 index 00000000..772811e3 --- /dev/null +++ b/emb/machine/machine_rp2_pwm.go @@ -0,0 +1,420 @@ +//go:build rp2040 || rp2350 + +package machine + +import ( + "device/rp" + "errors" + "math" + "runtime/volatile" + "unsafe" +) + +var ( + ErrBadPeriod = errors.New("period outside valid range 8ns..268ms") +) + +const ( + maxPWMPins = _NUMBANK0_GPIOS - 1 +) + +// pwmGroup is one PWM peripheral, which consists of a counter and two output +// channels. You can set the frequency using SetPeriod, +// but only for all the channels in this PWM peripheral at once. +// +// div: integer value to reduce counting rate by. Must be greater than or equal to 1. +// +// cc: counter compare level. Contains 2 channel levels. The 16 LSBs are Channel A's level (Duty Cycle) +// and the 16 MSBs are Channel B's level. +// +// top: Wrap. Highest number counter will reach before wrapping over. usually 0xffff. +// +// csr: Clock mode. PWM_CH0_CSR_DIVMODE_xxx registers have 4 possible modes, of which Free-running is used. +// csr contains output polarity bit at PWM_CH0_CSR_x_INV where x is the channel. +// csr contains phase correction bit at PWM_CH0_CSR_PH_CORRECT_Msk. +// csr contains PWM enable bit at PWM_CH0_CSR_EN. If not enabled PWM will not be active. +// +// ctr: PWM counter value. +type pwmGroup struct { + CSR volatile.Register32 + DIV volatile.Register32 + CTR volatile.Register32 + CC volatile.Register32 + TOP volatile.Register32 +} + +// Equivalent of +// +// var pwmSlice []pwmGroup = (*[8]pwmGroup)(unsafe.Pointer(rp.PWM))[:] +// return &pwmSlice[index] +// +// 0x14 is the size of a pwmGroup. +func getPWMGroup(index uintptr) *pwmGroup { + return (*pwmGroup)(unsafe.Add(unsafe.Pointer(rp.PWM), 0x14*index)) +} + +// Hardware Pulse Width Modulation (PWM) API +// PWM peripherals available on RP2040. Each peripheral has 2 pins available for +// a total of 16 available PWM outputs. Some pins may not be available on some boards. +// +// The RP2040 PWM block has 8 identical slices. Each slice can drive two PWM output signals, or +// measure the frequency or duty cycle of an input signal. This gives a total of up to 16 controllable +// PWM outputs. All 30 GPIOs can be driven by the PWM block +// +// The PWM hardware functions by continuously comparing the input value to a free-running counter. This produces a +// toggling output where the amount of time spent at the high output level is proportional to the input value. The fraction of +// time spent at the high signal level is known as the duty cycle of the signal. +// +// The default behaviour of a PWM slice is to count upward until the wrap value (\ref pwm_config_set_wrap) is reached, and then +// immediately wrap to 0. PWM slices also offer a phase-correct mode, where the counter starts to count downward after +// reaching TOP, until it reaches 0 again. +var ( + PWM0 = getPWMGroup(0) + PWM1 = getPWMGroup(1) + PWM2 = getPWMGroup(2) + PWM3 = getPWMGroup(3) + PWM4 = getPWMGroup(4) + PWM5 = getPWMGroup(5) + PWM6 = getPWMGroup(6) + PWM7 = getPWMGroup(7) +) + +// Configure enables and configures this PWM. +func (pwm *pwmGroup) Configure(config PWMConfig) error { + return pwm.init(config, true) +} + +// Channel returns a PWM channel for the given pin. If pin does +// not belong to PWM peripheral ErrInvalidOutputPin error is returned. +// It also configures pin as PWM output. +func (pwm *pwmGroup) Channel(pin Pin) (channel uint8, err error) { + if pin > maxPWMPins || pwmGPIOToSlice(pin) != pwm.peripheral() { + return 3, ErrInvalidOutputPin + } + pin.Configure(PinConfig{PinPWM}) + return pwmGPIOToChannel(pin), nil +} + +// Peripheral returns the RP2040 PWM peripheral which ranges from 0 to 7. Each +// PWM peripheral has 2 channels, A and B which correspond to 0 and 1 in the program. +// This number corresponds to the package's PWM0 throughout PWM7 handles +func PWMPeripheral(pin Pin) (sliceNum uint8, err error) { + if pin > maxPWMPins { + return 0, ErrInvalidOutputPin + } + return pwmGPIOToSlice(pin), nil +} + +// returns the number of the pwm peripheral (0-7) +func (pwm *pwmGroup) peripheral() uint8 { + return uint8((uintptr(unsafe.Pointer(pwm)) - uintptr(unsafe.Pointer(rp.PWM))) / 0x14) +} + +// SetPeriod updates the period of this PWM peripheral in nanoseconds. +// To set a particular frequency, use the following formula: +// +// period = 1e9 / frequency +// +// Where frequency is in hertz. If you use a period of 0, a period +// that works well for LEDs will be picked. +// +// SetPeriod will try not to modify TOP if possible to reach the target period. +// If the period is unattainable with current TOP SetPeriod will modify TOP +// by the bare minimum to reach the target period. It will also enable phase +// correct to reach periods above 130ms. +func (p *pwmGroup) SetPeriod(period uint64) error { + if period == 0 { + period = 1e5 + } + return p.setPeriod(period) +} + +// Top returns the current counter top, for use in duty cycle calculation. +// +// The value returned here is hardware dependent. In general, it's best to treat +// it as an opaque value that can be divided by some number and passed to Set +// (see Set documentation for more information). +func (p *pwmGroup) Top() uint32 { + return p.getWrap() +} + +// Counter returns the current counter value of the timer in this PWM +// peripheral. It may be useful for debugging. +func (p *pwmGroup) Counter() uint32 { + return (p.CTR.Get() & rp.PWM_CH0_CTR_CH0_CTR_Msk) >> rp.PWM_CH0_CTR_CH0_CTR_Pos +} + +// Period returns the used PWM period in nanoseconds. +func (p *pwmGroup) Period() uint64 { + // Lines below can overflow if operations done without care. + // maxInt=255, maxFrac=15, maxTop=65536, maxPHC=1 => maxProduct= (16*255+15) * (65536*2*1e9) = 5.3673e17 < MaxUint64=1.8e19 (close call.) + const compileTimeCheckPeriod uint64 = (255*16 + 15) * (65535 + 1) * 2 * 1e9 + freq := uint64(CPUFrequency()) + top := p.getWrap() + phc := p.getPhaseCorrect() + Int, frac := p.getClockDiv() + return (16*uint64(Int) + uint64(frac)) * uint64((top+1)*(phc+1)*1e9) / (16 * freq) // cycles = (TOP+1) * (CSRPHCorrect + 1) * (DIV_INT + DIV_FRAC/16) +} + +// SetInverting sets whether to invert the output of this channel. +// Without inverting, a 25% duty cycle would mean the output is high for 25% of +// the time and low for the rest. Inverting flips the output as if a NOT gate +// was placed at the output, meaning that the output would be 25% low and 75% +// high with a duty cycle of 25%. +func (p *pwmGroup) SetInverting(channel uint8, inverting bool) { + channel &= 1 + p.setInverting(channel, inverting) +} + +// Set updates the channel value. This is used to control the channel duty +// cycle, in other words the fraction of time the channel output is high (or low +// when inverted). For example, to set it to a 25% duty cycle, use: +// +// pwm.Set(channel, pwm.Top() / 4) +// +// pwm.Set(channel, 0) will set the output to low and pwm.Set(channel, +// pwm.Top()) will set the output to high, assuming the output isn't inverted. +func (p *pwmGroup) Set(channel uint8, value uint32) { + val := uint16(value) + channel &= 1 + p.setChanLevel(channel, val) +} + +// Get current level (last set by Set). Default value on initialization is 0. +func (p *pwmGroup) Get(channel uint8) (value uint32) { + channel &= 1 + return uint32(p.getChanLevel(channel)) +} + +// SetTop sets TOP control register. Max value is 16bit (0xffff). +func (p *pwmGroup) SetTop(top uint32) { + p.setWrap(uint16(top)) +} + +// SetCounter sets counter control register. Max value is 16bit (0xffff). +// Useful for synchronising two different PWM peripherals. +func (p *pwmGroup) SetCounter(ctr uint32) { + p.CTR.Set(ctr) +} + +// Enable enables or disables PWM peripheral channels. +func (p *pwmGroup) Enable(enable bool) { + p.enable(enable) +} + +// IsEnabled returns true if peripheral is enabled. +func (p *pwmGroup) IsEnabled() (enabled bool) { + return (p.CSR.Get()&rp.PWM_CH0_CSR_EN_Msk)>>rp.PWM_CH0_CSR_EN_Pos != 0 +} + +// Initialise a PWM with settings from a configuration object. +// If start is true then PWM starts on initialization. +func (pwm *pwmGroup) init(config PWMConfig, start bool) error { + // Not enable Phase correction + pwm.setPhaseCorrect(false) + + // Clock mode set by default to Free running + pwm.setDivMode(rp.PWM_CH0_CSR_DIVMODE_DIV) + + // Set Output polarity (false/false) + pwm.setInverting(0, false) + pwm.setInverting(1, false) + + // Set wrap. The highest value the counter will reach before returning to zero, also known as TOP. + pwm.setWrap(0xffff) + // period is set after TOP (Wrap). + err := pwm.SetPeriod(config.Period) + if err != nil { + return err + } + // period already set beforea + // Reset counter and compare (pwm level set to zero) + pwm.CTR.ReplaceBits(0, rp.PWM_CH0_CTR_CH0_CTR_Msk, 0) // PWM_CH0_CTR_RESET + pwm.CC.Set(0) // PWM_CH0_CC_RESET + + pwm.enable(start) + return nil +} + +func (pwm *pwmGroup) setPhaseCorrect(correct bool) { + pwm.CSR.ReplaceBits(boolToBit(correct)< maxPeriod || period < 8 { + return ErrBadPeriod + } + if period > maxPeriod/2 { + pwm.setPhaseCorrect(true) // Must enable Phase correct to reach large periods. + } + + // clearing above expression: + // DIV_INT + DIV_FRAC/16 = cycles / ( (TOP+1) * (CSRPHCorrect+1) ) // DIV_FRAC/16 is always 0 in this equation + // where cycles must be converted to time: + // target_period = cycles * period_per_cycle ==> cycles = target_period/period_per_cycle + var ( + freq = uint64(CPUFrequency()) + phc = uint64(pwm.getPhaseCorrect()) + rhs = 16 * period * freq / ((1 + phc) * 1e9 * (1 + topStart)) // right-hand-side of equation, scaled so frac is not divided + whole = rhs / 16 + frac = rhs % 16 + ) + switch { + case whole > 0xff: + whole = 0xff + case whole == 0: + // whole calculation underflowed so setting to minimum + // permissible value in DIV_INT register. + whole = 1 + frac = 0 + } + + // Step 2 is acquiring a better top value. Clearing the equation: + // TOP = cycles / ( (DIVINT+DIVFRAC/16) * (CSRPHCorrect+1) ) - 1 + top := 16*period*freq/((1+phc)*1e9*(16*whole+frac)) - 1 + if top > maxTop { + top = maxTop + } + pwm.SetTop(uint32(top)) + pwm.setClockDiv(uint8(whole), uint8(frac)) + return nil +} + +// Int is integer value to reduce counting rate by. Must be greater than or equal to 1. DIV_INT is bits 4:11 (8 bits). +// frac's (DIV_FRAC) default value on reset is 0. Max value for frac is 15 (4 bits). This is known as a fixed-point +// fractional number. +// +// cycles = (TOP+1) * (CSRPHCorrect + 1) * (DIV_INT + DIV_FRAC/16) +func (pwm *pwmGroup) setClockDiv(Int, frac uint8) { + pwm.DIV.ReplaceBits((uint32(frac)<> pos) + return level +} + +func (pwm *pwmGroup) getWrap() (top uint32) { + return (pwm.TOP.Get() & rp.PWM_CH0_TOP_CH0_TOP_Msk) >> rp.PWM_CH0_TOP_CH0_TOP_Pos +} + +func (pwm *pwmGroup) getPhaseCorrect() (phCorrect uint32) { + return (pwm.CSR.Get() & rp.PWM_CH0_CSR_PH_CORRECT_Msk) >> rp.PWM_CH0_CSR_PH_CORRECT_Pos +} + +func (pwm *pwmGroup) getClockDiv() (Int, frac uint8) { + div := pwm.DIV.Get() + return uint8((div & rp.PWM_CH0_DIV_INT_Msk) >> rp.PWM_CH0_DIV_INT_Pos), uint8((div & rp.PWM_CH0_DIV_FRAC_Msk) >> rp.PWM_CH0_DIV_FRAC_Pos) +} + +// pwmGPIOToSlice Determine the PWM channel that is attached to the specified GPIO. +// gpio must be less than 30. Returns the PWM slice number that controls the specified GPIO. +func pwmGPIOToSlice(gpio Pin) (slicenum uint8) { + if is48Pin && gpio >= 32 { + return uint8(8 + ((gpio-32)/2)%4) + } + return (uint8(gpio) >> 1) & 7 +} + +// Determine the PWM channel that is attached to the specified GPIO. +// Each slice 0 to 7 has two channels, A and B. +func pwmGPIOToChannel(gpio Pin) (channel uint8) { + return uint8(gpio) & 1 +} diff --git a/emb/machine/machine_rp2_resets.go b/emb/machine/machine_rp2_resets.go new file mode 100644 index 00000000..245436c4 --- /dev/null +++ b/emb/machine/machine_rp2_resets.go @@ -0,0 +1,31 @@ +//go:build rp2040 || rp2350 + +package machine + +import ( + "device/rp" + "unsafe" +) + +var resets = (*rp.RESETS_Type)(unsafe.Pointer(rp.RESETS)) + +// resetBlock resets hardware blocks specified +// by the bit pattern in bits. +func resetBlock(bits uint32) { + resets.RESET.SetBits(bits) +} + +// unresetBlock brings hardware blocks specified by the +// bit pattern in bits out of reset. +func unresetBlock(bits uint32) { + resets.RESET.ClearBits(bits) +} + +// unresetBlockWait brings specified hardware blocks +// specified by the bit pattern in bits +// out of reset and wait for completion. +func unresetBlockWait(bits uint32) { + unresetBlock(bits) + for !resets.RESET_DONE.HasBits(bits) { + } +} diff --git a/emb/machine/machine_rp2_rng.go b/emb/machine/machine_rp2_rng.go new file mode 100644 index 00000000..e619f050 --- /dev/null +++ b/emb/machine/machine_rp2_rng.go @@ -0,0 +1,41 @@ +//go:build rp2040 || rp2350 + +// Implementation based on code located here: +// https://github.com/raspberrypi/pico-sdk/blob/master/src/rp2_common/pico_lwip/random.c + +package machine + +import ( + "device/rp" +) + +const numberOfCycles = 32 + +// GetRNG returns 32 bits of semi-random data based on ring oscillator. +// +// Unlike some other implementations of GetRNG, these random numbers are not +// cryptographically secure and must not be used for cryptographic operations +// (nonces, etc). +func GetRNG() (uint32, error) { + var val uint32 + for i := 0; i < 4; i++ { + val = (val << 8) | uint32(roscRandByte()) + } + return val, nil +} + +var randomByte uint8 + +func roscRandByte() uint8 { + var poly uint8 + for i := 0; i < numberOfCycles; i++ { + if randomByte&0x80 != 0 { + poly = 0x35 + } else { + poly = 0 + } + randomByte = ((randomByte << 1) | uint8(rp.ROSC.GetRANDOMBIT()) ^ poly) + // TODO: delay a little because the random bit is a little slow + } + return randomByte +} diff --git a/emb/machine/machine_rp2_spi.go b/emb/machine/machine_rp2_spi.go new file mode 100644 index 00000000..d9cfc11d --- /dev/null +++ b/emb/machine/machine_rp2_spi.go @@ -0,0 +1,404 @@ +//go:build rp2040 || rp2350 + +package machine + +import ( + "device/rp" + "errors" + "unsafe" +) + +// SPI on the RP2040 +var ( + SPI0 = &SPI{ + Bus: rp.SPI0, + } + SPI1 = &SPI{ + Bus: rp.SPI1, + } +) + +// SPIConfig is used to store config info for SPI. +type SPIConfig struct { + Frequency uint32 + // LSB not supported on rp2040. + LSBFirst bool + // Mode's two most LSB are CPOL and CPHA. i.e. Mode==2 (0b10) is CPOL=1, CPHA=0 + Mode uint8 + // Serial clock pin + SCK Pin + // TX or Serial Data Out (MOSI if rp2040 is master) + SDO Pin + // RX or Serial Data In (MISO if rp2040 is master) + SDI Pin +} + +var ( + ErrLSBNotSupported = errors.New("SPI LSB unsupported on PL022") + ErrSPITimeout = errors.New("SPI timeout") + ErrSPIBaud = errors.New("SPI baud too low or above 66.5Mhz") + errSPIInvalidSDI = errors.New("invalid SPI SDI pin") + errSPIInvalidSDO = errors.New("invalid SPI SDO pin") + errSPIInvalidSCK = errors.New("invalid SPI SCK pin") +) + +type SPI struct { + Bus *rp.SPI0_Type +} + +// Tx handles read/write operation for SPI interface. Since SPI is a synchronous write/read +// interface, there must always be the same number of bytes written as bytes read. +// The Tx method knows about this, and offers a few different ways of calling it. +// +// This form sends the bytes in tx buffer, putting the resulting bytes read into the rx buffer. +// Note that the tx and rx buffers must be the same size: +// +// spi.Tx(tx, rx) +// +// This form sends the tx buffer, ignoring the result. Useful for sending "commands" that return zeros +// until all the bytes in the command packet have been received: +// +// spi.Tx(tx, nil) +// +// This form sends zeros, putting the result into the rx buffer. Good for reading a "result packet": +// +// spi.Tx(nil, rx) +// +// Remark: This implementation (RP2040) allows reading into buffer with a custom repeated +// value on tx. +// +// spi.Tx([]byte{0xff}, rx) // may cause unwanted heap allocations. +// +// This form sends 0xff and puts the result into rx buffer. Useful for reading from SD cards +// which require 0xff input on SI. +func (spi *SPI) Tx(w, r []byte) (err error) { + switch { + case w == nil: + // read only, so write zero and read a result. + err = spi.rx(r, 0) + case r == nil: + // write only + err = spi.tx(w) + case len(w) == 1 && len(r) > 1: + // Read with custom repeated value. + err = spi.rx(r, w[0]) + default: + // write/read + err = spi.txrx(w, r) + } + return err +} + +// Write a single byte and read a single byte from TX/RX FIFO. +func (spi *SPI) Transfer(w byte) (byte, error) { + for !spi.isWritable() { + } + + spi.Bus.SSPDR.Set(uint32(w)) + + for !spi.isReadable() { + } + return uint8(spi.Bus.SSPDR.Get()), nil +} + +func (spi *SPI) SetBaudRate(br uint32) error { + const maxBaud uint32 = 66.5 * MHz // max output frequency is 66.5MHz on rp2040. see Note page 527. + // Find smallest prescale value which puts output frequency in range of + // post-divide. Prescale is an even number from 2 to 254 inclusive. + var prescale, postdiv uint32 + freq := CPUFrequency() + for prescale = 2; prescale < 255; prescale += 2 { + if freq < (prescale+2)*256*br { + break + } + } + if prescale > 254 || br > maxBaud { + return ErrSPIBaud + } + // Find largest post-divide which makes output <= baudrate. Post-divide is + // an integer in the range 1 to 256 inclusive. + for postdiv = 256; postdiv > 1; postdiv-- { + if freq/(prescale*(postdiv-1)) > br { + break + } + } + spi.Bus.SSPCPSR.Set(prescale) + spi.Bus.SSPCR0.ReplaceBits((postdiv-1)<> rp.SPI0_SSPCR0_SCR_Pos) + 1 + return freqin / (prescale * postdiv) +} + +// Configure is intended to setup/initialize the SPI interface. +// Default baudrate of 4MHz is used if Frequency == 0. Default +// word length (data bits) is 8. +// Below is a list of GPIO pins corresponding to SPI0 bus on the rp2040: +// +// SI : 0, 4, 17 a.k.a RX and MISO (if rp2040 is master) +// SO : 3, 7, 19 a.k.a TX and MOSI (if rp2040 is master) +// SCK: 2, 6, 18 +// +// SPI1 bus GPIO pins: +// +// SI : 8, 12 +// SO : 11, 15 +// SCK: 10, 14 +// +// No pin configuration is needed of SCK, SDO and SDI needed after calling Configure. +func (spi *SPI) Configure(config SPIConfig) error { + const defaultBaud uint32 = 4 * MHz + if config.SCK == 0 && config.SDO == 0 && config.SDI == 0 { + // set default pins if config zero valued or invalid clock pin supplied. + switch spi.Bus { + case rp.SPI0: + config.SCK = SPI0_SCK_PIN + config.SDO = SPI0_SDO_PIN + config.SDI = SPI0_SDI_PIN + case rp.SPI1: + config.SCK = SPI1_SCK_PIN + config.SDO = SPI1_SDO_PIN + config.SDI = SPI1_SDI_PIN + } + } + var okSDI, okSDO, okSCK bool + switch spi.Bus { + case rp.SPI0: + okSDI = config.SDI == 0 || config.SDI == 4 || config.SDI == 16 || config.SDI == 20 + okSDO = config.SDO == 3 || config.SDO == 7 || config.SDO == 19 || config.SDO == 23 + okSCK = config.SCK == 2 || config.SCK == 6 || config.SCK == 18 || config.SCK == 22 + case rp.SPI1: + okSDI = config.SDI == 8 || config.SDI == 12 || config.SDI == 24 || config.SDI == 28 + okSDO = config.SDO == 11 || config.SDO == 15 || config.SDO == 27 + okSCK = config.SCK == 10 || config.SCK == 14 || config.SCK == 26 + } + + switch { + case !okSDI: + return errSPIInvalidSDI + case !okSDO: + return errSPIInvalidSDO + case !okSCK: + return errSPIInvalidSCK + } + + if config.Frequency == 0 { + config.Frequency = defaultBaud + } + // SPI pin configuration + config.SCK.setFunc(fnSPI) + config.SDO.setFunc(fnSPI) + config.SDI.setFunc(fnSPI) + + return spi.initSPI(config) +} + +func (spi *SPI) initSPI(config SPIConfig) (err error) { + spi.reset() + // LSB-first not supported on PL022: + if config.LSBFirst { + return ErrLSBNotSupported + } + err = spi.SetBaudRate(config.Frequency) + // Set SPI Format (CPHA and CPOL) and frame format (default is Motorola) + spi.setFormat(config.Mode) + + // Always enable DREQ signals -- harmless if DMA is not listening + spi.Bus.SSPDMACR.SetBits(rp.SPI0_SSPDMACR_TXDMAE | rp.SPI0_SSPDMACR_RXDMAE) + // Finally enable the SPI + spi.Bus.SSPCR1.SetBits(rp.SPI0_SSPCR1_SSE) + return err +} + +//go:inline +func (spi *SPI) setFormat(mode uint8) { + cpha := uint32(mode) & 1 + cpol := uint32(mode>>1) & 1 + spi.Bus.SSPCR0.ReplaceBits( + (cpha<>3].Set(p.ioIntBit(change)) +} + +// Basic interrupt setting via ioBANK0 for GPIO interrupts. +func (p Pin) setInterrupt(change PinChange, enabled bool) { + // Separate mask/force/status per-core, so check which core called, and + // set the relevant IRQ controls. + switch CurrentCore() { + case 0: + p.ctrlSetInterrupt(change, enabled, &ioBank0.proc0IRQctrl) + case 1: + p.ctrlSetInterrupt(change, enabled, &ioBank0.proc1IRQctrl) + } +} + +// ctrlSetInterrupt acknowledges any pending interrupt and enables or disables +// the interrupt for a given IRQ control bank (IOBANK, DormantIRQ, QSPI). +// +// pico-sdk calls this the _gpio_set_irq_enabled, not to be confused with +// gpio_set_irq_enabled (no leading underscore). +func (p Pin) ctrlSetInterrupt(change PinChange, enabled bool, base *irqCtrl) { + p.acknowledgeInterrupt(change) + enReg := &base.intE[p>>3] + if enabled { + enReg.SetBits(p.ioIntBit(change)) + } else { + enReg.ClearBits(p.ioIntBit(change)) + } +} diff --git a/emb/machine/machine_rp2_timer.go b/emb/machine/machine_rp2_timer.go new file mode 100644 index 00000000..a78ed70b --- /dev/null +++ b/emb/machine/machine_rp2_timer.go @@ -0,0 +1,103 @@ +//go:build rp2040 || rp2350 + +package machine + +import ( + "device/arm" + "runtime/interrupt" + "runtime/volatile" +) + +const numTimers = 4 + +// Alarm0 is reserved for sleeping by tinygo runtime code for RP2040. +// Alarm0 is also IRQ0 +const sleepAlarm = 0 +const sleepAlarmIRQ = 0 + +// The minimum sleep duration in μs (ticks) +const minSleep = 10 + +type timerType struct { + timeHW volatile.Register32 + timeLW volatile.Register32 + timeHR volatile.Register32 + timeLR volatile.Register32 + alarm [numTimers]volatile.Register32 + armed volatile.Register32 + timeRawH volatile.Register32 + timeRawL volatile.Register32 + dbgPause volatile.Register32 + pause volatile.Register32 + locked [rp2350ExtraReg]volatile.Register32 + source [rp2350ExtraReg]volatile.Register32 + intR volatile.Register32 + intE volatile.Register32 + intF volatile.Register32 + intS volatile.Register32 +} + +// TimeElapsed returns time elapsed since power up, in microseconds. +func (tmr *timerType) timeElapsed() (us uint64) { + // Need to make sure that the upper 32 bits of the timer + // don't change, so read that first + hi := tmr.timeRawH.Get() + var lo, nextHi uint32 + for { + // Read the lower 32 bits + lo = tmr.timeRawL.Get() + // Now read the upper 32 bits again and + // check that it hasn't incremented. If it has, loop around + // and read the lower 32 bits again to get an accurate value + nextHi = tmr.timeRawH.Get() + if hi == nextHi { + break + } + hi = nextHi + } + return uint64(hi)<<32 | uint64(lo) +} + +// lightSleep will put the processor into a sleep state a short period +// (up to approx 72mins per RP2040 datasheet, 4.6.3. Alarms). +// +// This function is a 'light' sleep and will return early if another +// interrupt or event triggers. This is intentional since the +// primary use-case is for use by the TinyGo scheduler which will +// re-sleep if needed. +func (tmr *timerType) lightSleep(us uint64) { + // minSleep is a way to avoid race conditions for short + // sleeps by ensuring there is enough time to setup the + // alarm before sleeping. For very short sleeps, this + // effectively becomes a 'busy loop'. + if us < minSleep { + return + } + + // Interrupt handler is essentially a no-op, we're just relying + // on the side-effect of waking the CPU from "wfe" + intr := interrupt.New(sleepAlarmIRQ, func(interrupt.Interrupt) { + // Clear the IRQ + timer.intR.Set(1 << sleepAlarm) + }) + + // Reset interrupt flag + tmr.intR.Set(1 << sleepAlarm) + + // Enable interrupt + tmr.intE.SetBits(1 << sleepAlarm) + intr.Enable() + + // Only the low 32 bits of time can be used for alarms + target := uint64(tmr.timeRawL.Get()) + us + tmr.alarm[sleepAlarm].Set(uint32(target)) + + // Wait for sleep (or any other) interrupt + arm.Asm("wfe") + + // Disarm timer + tmr.armed.Set(1 << sleepAlarm) + + // Disable interrupt + intr.Disable() +} diff --git a/emb/machine/machine_rp2_uart.go b/emb/machine/machine_rp2_uart.go new file mode 100644 index 00000000..03ebb9d8 --- /dev/null +++ b/emb/machine/machine_rp2_uart.go @@ -0,0 +1,159 @@ +//go:build rp2040 || rp2350 + +package machine + +import ( + "device/rp" + "runtime/interrupt" +) + +// UART on the RP2040. +type UART struct { + Buffer *RingBuffer + Bus *rp.UART0_Type + Interrupt interrupt.Interrupt +} + +// Configure the UART. +func (uart *UART) Configure(config UARTConfig) error { + initUART(uart) + + // Default baud rate to 115200. + if config.BaudRate == 0 { + config.BaudRate = 115200 + } + + // Use default pins if pins are not set. + if config.TX == 0 && config.RX == 0 { + // use default pins + config.TX = UART_TX_PIN + config.RX = UART_RX_PIN + } + + uart.SetBaudRate(config.BaudRate) + + // default to 8-1-N + uart.SetFormat(8, 1, ParityNone) + + // Enable the UART, both TX and RX + settings := uint32(rp.UART0_UARTCR_UARTEN | + rp.UART0_UARTCR_RXE | + rp.UART0_UARTCR_TXE) + const bits = rp.UART0_UARTCR_UARTEN | rp.UART0_UARTCR_TXE + if config.RTS != 0 { + settings |= rp.UART0_UARTCR_RTSEN + } + if config.CTS != 0 { + settings |= rp.UART0_UARTCR_CTSEN + } + + uart.Bus.UARTCR.SetBits(settings) + + // set GPIO mux to UART for the pins + if config.TX != NoPin { + config.TX.Configure(PinConfig{Mode: PinUART}) + } + if config.RX != NoPin { + config.RX.Configure(PinConfig{Mode: PinUART}) + } + if config.RTS != 0 { + config.RTS.Configure(PinConfig{Mode: PinOutput}) + } + if config.CTS != 0 { + config.CTS.Configure(PinConfig{Mode: PinInput}) + } + + // Enable RX IRQ. + uart.Interrupt.SetPriority(0x80) + uart.Interrupt.Enable() + + // Setup interrupt on receive. + uart.Bus.UARTIMSC.Set(rp.UART0_UARTIMSC_RXIM) + + return nil +} + +// SetBaudRate sets the baudrate to be used for the UART. +func (uart *UART) SetBaudRate(br uint32) { + div := 8 * CPUFrequency() / br + + ibrd := div >> 7 + var fbrd uint32 + + switch { + case ibrd == 0: + ibrd = 1 + fbrd = 0 + case ibrd >= 65535: + ibrd = 65535 + fbrd = 0 + default: + fbrd = ((div & 0x7f) + 1) / 2 + } + + // set PL011 baud divisor registers + uart.Bus.UARTIBRD.Set(ibrd) + uart.Bus.UARTFBRD.Set(fbrd) + + // PL011 needs a (dummy) line control register write. + // See https://github.com/raspberrypi/pico-sdk/blob/master/src/rp2_common/hardware_uart/uart.c#L93-L95 + uart.Bus.UARTLCR_H.SetBits(0) +} + +// WriteByte writes a byte of data to the UART. +func (uart *UART) writeByte(c byte) error { + // wait until buffer is not full + for uart.Bus.UARTFR.HasBits(rp.UART0_UARTFR_TXFF) { + gosched() + } + + // write data + uart.Bus.UARTDR.Set(uint32(c)) + return nil +} + +func (uart *UART) flush() { + for uart.Bus.UARTFR.HasBits(rp.UART0_UARTFR_BUSY) { + gosched() + } +} + +// SetFormat for number of data bits, stop bits, and parity for the UART. +func (uart *UART) SetFormat(databits, stopbits uint8, parity UARTParity) error { + var pen, pev uint8 + if parity != ParityNone { + pen = rp.UART0_UARTLCR_H_PEN + } + if parity == ParityEven { + pev = rp.UART0_UARTLCR_H_EPS + } + uart.Bus.UARTLCR_H.SetBits(uint32((databits-5)< usb.EndpointPacketSize { + count = usb.EndpointPacketSize + + sendOnEP0DATADONE.offset = count + sendOnEP0DATADONE.data = data + } else { + sendOnEP0DATADONE.offset = 0 + } + epXdata0[ep] = true + } + + sendViaEPIn(ep, data, count) +} + +func ReceiveUSBControlPacket() ([cdcLineInfoSize]byte, error) { + var b [cdcLineInfoSize]byte + ep := 0 + + for !_usbDPSRAM.EPxBufferControl[ep].Out.HasBits(usbBuf0CtrlFull) { + // TODO: timeout + } + + ctrl := _usbDPSRAM.EPxBufferControl[ep].Out.Get() + _usbDPSRAM.EPxBufferControl[ep].Out.Set(usbBufferLen & usbBuf0CtrlLenMask) + sz := ctrl & usbBuf0CtrlLenMask + + copy(b[:], _usbDPSRAM.EPxBuffer[ep].Buffer0[:sz]) + + _usbDPSRAM.EPxBufferControl[ep].Out.SetBits(usbBuf0CtrlData1Pid) + _usbDPSRAM.EPxBufferControl[ep].Out.SetBits(usbBuf0CtrlAvail) + + return b, nil +} + +func handleEndpointRx(ep uint32) []byte { + ctrl := _usbDPSRAM.EPxBufferControl[ep].Out.Get() + _usbDPSRAM.EPxBufferControl[ep].Out.Set(usbBufferLen & usbBuf0CtrlLenMask) + sz := ctrl & usbBuf0CtrlLenMask + + return _usbDPSRAM.EPxBuffer[ep].Buffer0[:sz] +} + +// AckUsbOutTransfer is called to acknowledge the completion of a USB OUT transfer. +func AckUsbOutTransfer(ep uint32) { + ep = ep & 0x7F + setEPDataPID(ep, !epXdata0[ep]) +} + +// Set the USB endpoint Packet ID to DATA0 or DATA1. +func setEPDataPID(ep uint32, dataOne bool) { + epXdata0[ep] = dataOne + if epXdata0[ep] || ep == 0 { + _usbDPSRAM.EPxBufferControl[ep].Out.SetBits(usbBuf0CtrlData1Pid) + } + + _usbDPSRAM.EPxBufferControl[ep].Out.SetBits(usbBuf0CtrlAvail) +} + +func SendZlp() { + sendUSBPacket(0, []byte{}, 0) +} + +func sendViaEPIn(ep uint32, data []byte, count int) { + // Prepare buffer control register value + val := uint32(count) | usbBuf0CtrlAvail + + // DATA0 or DATA1 + epXdata0[ep&0x7F] = !epXdata0[ep&0x7F] + if !epXdata0[ep&0x7F] { + val |= usbBuf0CtrlData1Pid + } + + // Mark as full + val |= usbBuf0CtrlFull + + copy(_usbDPSRAM.EPxBuffer[ep&0x7F].Buffer0[:], data[:count]) + _usbDPSRAM.EPxBufferControl[ep&0x7F].In.Set(val) +} + +// Set ENDPOINT_HALT/stall status on a USB IN endpoint. +func (dev *USBDevice) SetStallEPIn(ep uint32) { + ep = ep & 0x7F + // Prepare buffer control register value + if ep == 0 { + armEPZeroStall() + } + val := uint32(usbBuf0CtrlFull) + _usbDPSRAM.EPxBufferControl[ep].In.Set(val) + val |= uint32(usbBuf0CtrlStall) + _usbDPSRAM.EPxBufferControl[ep].In.Set(val) +} + +// Set ENDPOINT_HALT/stall status on a USB OUT endpoint. +func (dev *USBDevice) SetStallEPOut(ep uint32) { + ep = ep & 0x7F + if ep == 0 { + panic("SetStallEPOut: EP0 OUT not valid") + } + val := uint32(usbBuf0CtrlStall) + _usbDPSRAM.EPxBufferControl[ep].Out.Set(val) +} + +// Clear the ENDPOINT_HALT/stall on a USB IN endpoint. +func (dev *USBDevice) ClearStallEPIn(ep uint32) { + ep = ep & 0x7F + val := uint32(usbBuf0CtrlStall) + _usbDPSRAM.EPxBufferControl[ep].In.ClearBits(val) + if epXPIDReset[ep] { + // Reset the PID to DATA0 + setEPDataPID(ep, false) + } +} + +// Clear the ENDPOINT_HALT/stall on a USB OUT endpoint. +func (dev *USBDevice) ClearStallEPOut(ep uint32) { + ep = ep & 0x7F + val := uint32(usbBuf0CtrlStall) + _usbDPSRAM.EPxBufferControl[ep].Out.ClearBits(val) + if epXPIDReset[ep] { + // Reset the PID to DATA0 + setEPDataPID(ep, false) + } +} + +type usbDPSRAM struct { + // Note that EPxControl[0] is not EP0Control but 8-byte setup data. + EPxControl [16]usbEndpointControlRegister + + EPxBufferControl [16]usbBufferControlRegister + + EPxBuffer [16]usbBuffer +} + +type usbEndpointControlRegister struct { + In volatile.Register32 + Out volatile.Register32 +} +type usbBufferControlRegister struct { + In volatile.Register32 + Out volatile.Register32 +} + +type usbBuffer struct { + Buffer0 [usbBufferLen]byte + Buffer1 [usbBufferLen]byte +} + +var ( + _usbDPSRAM = (*usbDPSRAM)(unsafe.Pointer(uintptr(0x50100000))) + epXdata0 [16]bool + epXPIDReset [16]bool + setupBytes [8]byte +) + +func (d *usbDPSRAM) setupBytes() []byte { + + data := d.EPxControl[usb.CONTROL_ENDPOINT].In.Get() + setupBytes[0] = byte(data) + setupBytes[1] = byte(data >> 8) + setupBytes[2] = byte(data >> 16) + setupBytes[3] = byte(data >> 24) + + data = d.EPxControl[usb.CONTROL_ENDPOINT].Out.Get() + setupBytes[4] = byte(data) + setupBytes[5] = byte(data >> 8) + setupBytes[6] = byte(data >> 16) + setupBytes[7] = byte(data >> 24) + + return setupBytes[:] +} + +func (d *usbDPSRAM) clear() { + for i := 0; i < len(d.EPxControl); i++ { + d.EPxControl[i].In.Set(0) + d.EPxControl[i].Out.Set(0) + d.EPxBufferControl[i].In.Set(0) + d.EPxBufferControl[i].Out.Set(0) + } +} + +const ( + // DPRAM : Endpoint control register + usbEpControlEnable = 0x80000000 + usbEpControlDoubleBuffered = 0x40000000 + usbEpControlInterruptPerBuff = 0x20000000 + usbEpControlInterruptPerDoubleBuff = 0x10000000 + usbEpControlEndpointType = 0x0c000000 + usbEpControlInterruptOnStall = 0x00020000 + usbEpControlInterruptOnNak = 0x00010000 + usbEpControlBufferAddress = 0x0000ffff + + usbEpControlEndpointTypeControl = 0x00000000 + usbEpControlEndpointTypeISO = 0x04000000 + usbEpControlEndpointTypeBulk = 0x08000000 + usbEpControlEndpointTypeInterrupt = 0x0c000000 + + // Endpoint buffer control bits + usbBuf1CtrlFull = 0x80000000 + usbBuf1CtrlLast = 0x40000000 + usbBuf1CtrlData0Pid = 0x20000000 + usbBuf1CtrlData1Pid = 0x00000000 + usbBuf1CtrlSel = 0x10000000 + usbBuf1CtrlStall = 0x08000000 + usbBuf1CtrlAvail = 0x04000000 + usbBuf1CtrlLenMask = 0x03FF0000 + usbBuf0CtrlFull = 0x00008000 + usbBuf0CtrlLast = 0x00004000 + usbBuf0CtrlData0Pid = 0x00000000 + usbBuf0CtrlData1Pid = 0x00002000 + usbBuf0CtrlSel = 0x00001000 + usbBuf0CtrlStall = 0x00000800 + usbBuf0CtrlAvail = 0x00000400 + usbBuf0CtrlLenMask = 0x000003FF + + usbBufferLen = 64 +) diff --git a/emb/machine/machine_rp2_watchdog.go b/emb/machine/machine_rp2_watchdog.go new file mode 100644 index 00000000..f776c5ca --- /dev/null +++ b/emb/machine/machine_rp2_watchdog.go @@ -0,0 +1,62 @@ +//go:build rp2040 || rp2350 + +package machine + +import ( + "device/rp" +) + +// Watchdog provides access to the hardware watchdog available +// in the RP2040. +var Watchdog = &watchdogImpl{} + +const ( + // WatchdogMaxTimeout in milliseconds (approx 8.3s). + // + // Nominal 1us per watchdog tick, 24-bit counter, + // but due to errata two ticks consumed per 1us. + // See: Errata RP2040-E1 + WatchdogMaxTimeout = (rp.WATCHDOG_LOAD_LOAD_Msk / 1000) / 2 +) + +type watchdogImpl struct { + // The value to reset the counter to on each Update + loadValue uint32 +} + +// Configure the watchdog. +// +// This method should not be called after the watchdog is started and on +// some platforms attempting to reconfigure after starting the watchdog +// is explicitly forbidden / will not work. +func (wd *watchdogImpl) Configure(config WatchdogConfig) error { + // x2 due to errata RP2040-E1 + wd.loadValue = config.TimeoutMillis * 1000 * 2 + if wd.loadValue > rp.WATCHDOG_LOAD_LOAD_Msk { + wd.loadValue = rp.WATCHDOG_LOAD_LOAD_Msk + } + + rp.WATCHDOG.CTRL.ClearBits(rp.WATCHDOG_CTRL_ENABLE) + + // Reset everything apart from ROSC and XOSC + rp.PSM.WDSEL.Set(0x0001ffff &^ (rp.PSM_WDSEL_ROSC | rp.PSM_WDSEL_XOSC)) + + // Pause watchdog during debug + rp.WATCHDOG.CTRL.SetBits(rp.WATCHDOG_CTRL_PAUSE_DBG0 | rp.WATCHDOG_CTRL_PAUSE_DBG1 | rp.WATCHDOG_CTRL_PAUSE_JTAG) + + // Load initial counter + rp.WATCHDOG.LOAD.Set(wd.loadValue) + + return nil +} + +// Starts the watchdog. +func (wd *watchdogImpl) Start() error { + rp.WATCHDOG.CTRL.SetBits(rp.WATCHDOG_CTRL_ENABLE) + return nil +} + +// Update the watchdog, indicating that the app is healthy. +func (wd *watchdogImpl) Update() { + rp.WATCHDOG.LOAD.Set(wd.loadValue) +} diff --git a/emb/machine/machine_rp2_xosc.go b/emb/machine/machine_rp2_xosc.go new file mode 100644 index 00000000..c9ce5830 --- /dev/null +++ b/emb/machine/machine_rp2_xosc.go @@ -0,0 +1,47 @@ +//go:build rp2040 || rp2350 + +package machine + +import ( + "device/rp" + "runtime/volatile" + "unsafe" +) + +// On some boards, the XOSC can take longer than usual to stabilize. On such +// boards, this is needed to avoid a hard fault on boot/reset. Refer to +// PICO_XOSC_STARTUP_DELAY_MULTIPLIER in the Pico SDK for additional details. +const XOSC_STARTUP_DELAY_MULTIPLIER = 64 + +type xoscType struct { + ctrl volatile.Register32 + status volatile.Register32 + dormant volatile.Register32 + startup volatile.Register32 + reserved [3 - 3*rp2350ExtraReg]volatile.Register32 + count volatile.Register32 +} + +var xosc = (*xoscType)(unsafe.Pointer(rp.XOSC)) + +// init initializes the crystal oscillator system. +// +// This function will block until the crystal oscillator has stabilised. +func (osc *xoscType) init() { + // Assumes 1-15 MHz input + if xoscFreq > 15 { + panic("xosc frequency cannot be greater than 15MHz") + } + osc.ctrl.Set(rp.XOSC_CTRL_FREQ_RANGE_1_15MHZ) + + // Set xosc startup delay + delay := (((xoscFreq * MHz) / 1000) + 128) / 256 * XOSC_STARTUP_DELAY_MULTIPLIER + osc.startup.Set(uint32(delay)) + + // Set the enable bit now that we have set freq range and startup delay + osc.ctrl.SetBits(rp.XOSC_CTRL_ENABLE_ENABLE << rp.XOSC_CTRL_ENABLE_Pos) + + // Wait for xosc to be stable + for !osc.status.HasBits(rp.XOSC_STATUS_STABLE) { + } +} diff --git a/emb/machine/machine_stm32.go b/emb/machine/machine_stm32.go new file mode 100644 index 00000000..1edaa2cd --- /dev/null +++ b/emb/machine/machine_stm32.go @@ -0,0 +1,104 @@ +//go:build stm32 + +package machine + +import ( + "device/stm32" + + "runtime/volatile" + "unsafe" +) + +const deviceName = stm32.Device + +// Peripheral abstraction layer for the stm32. + +const ( + portA Pin = iota * 16 + portB + portC + portD + portE + portF + portG + portH + portI + portJ + portK +) + +// Peripheral operations sequence: +// 1. Enable the clock to the alternate function. +// 2. Enable clock to corresponding GPIO +// 3. Attach the alternate function. +// 4. Configure the input-output port and pins (of the corresponding GPIOx) to match the AF . +// 5. If desired enable the nested vector interrupt control to generate interrupts. +// 6. Program the AF/peripheral for the required configuration (eg baud rate for a USART) . + +// Given that the stm32 family has the AF and GPIO on different registers based on the chip, +// use the main function here for configuring, and use hooks in the more specific chip +// definition files +// Also, the stm32f1xx series handles things differently from the stm32f0/2/3/4 + +// ---------- General pin operations ---------- +type PinChange uint8 + +const ( + PinRising PinChange = 1 << iota + PinFalling + PinToggle = PinRising | PinFalling +) + +// Set the pin to high or low. +// Warning: only use this on an output pin! +func (p Pin) Set(high bool) { + port := p.getPort() + pin := uint8(p) % 16 + if high { + port.BSRR.Set(1 << pin) + } else { + port.BSRR.Set(1 << (pin + 16)) + } +} + +// Get returns the current value of a GPIO pin when the pin is configured as an +// input or as an output. +func (p Pin) Get() bool { + port := p.getPort() + pin := uint8(p) % 16 + val := port.IDR.Get() & (1 << pin) + return (val > 0) +} + +// PortMaskSet returns the register and mask to enable a given GPIO pin. This +// can be used to implement bit-banged drivers. +func (p Pin) PortMaskSet() (*uint32, uint32) { + port := p.getPort() + pin := uint8(p) % 16 + return &port.BSRR.Reg, 1 << pin +} + +// PortMaskClear returns the register and mask to disable a given port. This can +// be used to implement bit-banged drivers. +func (p Pin) PortMaskClear() (*uint32, uint32) { + port := p.getPort() + pin := uint8(p) % 16 + return &port.BSRR.Reg, 1 << (pin + 16) +} + +var deviceID [12]byte + +// DeviceID returns an identifier that is unique within +// a particular chipset. +// +// The identity is one burnt into the MCU itself. +// +// The length of the device ID for STM32 is 12 bytes (96 bits). +func DeviceID() []byte { + for i := 0; i < len(deviceID); i++ { + word := (*volatile.Register32)(unsafe.Pointer(deviceIDAddr[i/4])).Get() + deviceID[i] = byte(word >> ((i % 4) * 8)) + } + + return deviceID[:] +} diff --git a/emb/machine/machine_stm32_adc_f1.go b/emb/machine/machine_stm32_adc_f1.go new file mode 100644 index 00000000..7076bdd8 --- /dev/null +++ b/emb/machine/machine_stm32_adc_f1.go @@ -0,0 +1,90 @@ +//go:build stm32f103 + +package machine + +import ( + "device/stm32" + "unsafe" +) + +const ( + Cycles_1_5 = 0x0 + Cycles_7_5 = 0x1 + Cycles_13_5 = 0x2 + Cycles_28_5 = 0x3 + Cycles_41_5 = 0x4 + Cycles_55_5 = 0x5 + Cycles_71_5 = 0x6 + Cycles_239_5 = 0x7 +) + +// InitADC initializes the registers needed for ADC1. +func InitADC() { + // Enable ADC clock + enableAltFuncClock(unsafe.Pointer(stm32.ADC1)) + + // enable + stm32.ADC1.CR2.SetBits(stm32.ADC_CR2_ADON | stm32.ADC_CR2_ALIGN) + + return +} + +// Configure configures an ADC pin to be able to read analog data. +func (a ADC) Configure(ADCConfig) { + a.Pin.Configure(PinConfig{Mode: PinInputModeAnalog}) + + // set sample time + ch := a.getChannel() + if ch > 9 { + stm32.ADC1.SMPR1.SetBits(Cycles_28_5 << (ch - 10) * stm32.ADC_SMPR1_SMP11_Pos) + } else { + stm32.ADC1.SMPR2.SetBits(Cycles_28_5 << (ch * stm32.ADC_SMPR2_SMP1_Pos)) + } + + return +} + +// Get returns the current value of a ADC pin in the range 0..0xffff. +// TODO: DMA based implementation. +func (a ADC) Get() uint16 { + // set rank + ch := uint32(a.getChannel()) + stm32.ADC1.SetSQR3_SQ1(ch) + + // start conversion + stm32.ADC1.CR2.SetBits(stm32.ADC_CR2_ADON) + + // wait for conversion to complete + for !stm32.ADC1.SR.HasBits(stm32.ADC_SR_EOC) { + } + + // read result as 16 bit value + return uint16(stm32.ADC1.DR.Get()) +} + +func (a ADC) getChannel() uint8 { + switch a.Pin { + case PA0: + return 0 + case PA1: + return 1 + case PA2: + return 2 + case PA3: + return 3 + case PA4: + return 4 + case PA5: + return 5 + case PA6: + return 6 + case PA7: + return 7 + case PB0: + return 8 + case PB1: + return 9 + } + + return 0 +} diff --git a/emb/machine/machine_stm32_adc_f4.go b/emb/machine/machine_stm32_adc_f4.go new file mode 100644 index 00000000..df4984c1 --- /dev/null +++ b/emb/machine/machine_stm32_adc_f4.go @@ -0,0 +1,114 @@ +//go:build stm32f4 + +package machine + +import ( + "device/stm32" + "unsafe" +) + +// InitADC initializes the registers needed for ADC1. +func InitADC() { + // Enable ADC clock + enableAltFuncClock(unsafe.Pointer(stm32.ADC1)) + + // stop scan, and clear scan resolution + stm32.ADC1.CR1.ClearBits(stm32.ADC_CR1_SCAN | stm32.ADC_CR1_RES_Msk) + + // set conversion mode and resolution + stm32.ADC1.CR1.SetBits(stm32.ADC_CR1_RES_TwelveBit) + + // clear CONT, ALIGN, EXTEN and EXTSEL bits from CR2 + stm32.ADC1.CR2.ClearBits(stm32.ADC_CR2_CONT | stm32.ADC_CR2_ALIGN | stm32.ADC_CR2_EXTEN_Msk | stm32.ADC_CR2_EXTSEL_Msk) + + // set CONT, ALIGN, EXTEN and EXTSEL bits from CR2 + stm32.ADC1.CR2.SetBits(stm32.ADC_CR2_CONT_Single | stm32.ADC_CR2_ALIGN_Right) + + stm32.ADC1.SQR1.ClearBits(stm32.ADC_SQR1_L_Msk) + stm32.ADC1.SQR1.SetBits(2 << stm32.ADC_SQR1_L_Pos) // 2 means 3 conversions + + // enable + stm32.ADC1.CR2.SetBits(stm32.ADC_CR2_ADON) + + return +} + +// Configure configures an ADC pin to be able to read analog data. +func (a ADC) Configure(ADCConfig) { + a.Pin.ConfigureAltFunc(PinConfig{Mode: PinInputAnalog}, 0) + + // set sample time + ch := a.getChannel() + if ch > 9 { + stm32.ADC1.SMPR1.SetBits(stm32.ADC_SMPR1_SMP11_Cycles84 << (ch - 10) * stm32.ADC_SMPR1_SMP11_Pos) + } else { + stm32.ADC1.SMPR2.SetBits(stm32.ADC_SMPR2_SMP1_Cycles84 << (ch * stm32.ADC_SMPR2_SMP1_Pos)) + } + + return +} + +// Get returns the current value of a ADC pin in the range 0..0xffff. +// TODO: DMA based implementation. +func (a ADC) Get() uint16 { + // set rank + ch := uint32(a.getChannel()) + stm32.ADC1.SQR3.SetBits(ch) + + // start conversion + stm32.ADC1.CR2.SetBits(stm32.ADC_CR2_SWSTART) + + // wait for conversion to complete + for !stm32.ADC1.SR.HasBits(stm32.ADC_SR_EOC) { + } + + // read 12-bit result as 16 bit value + result := uint16(stm32.ADC1.DR.Get()) << 4 + + // clear flag + stm32.ADC1.SR.ClearBits(stm32.ADC_SR_EOC) + + // clear rank + stm32.ADC1.SQR3.ClearBits(ch) + + return result +} + +func (a ADC) getChannel() uint8 { + switch a.Pin { + case PA0: + return 0 + case PA1: + return 1 + case PA2: + return 2 + case PA3: + return 3 + case PA4: + return 4 + case PA5: + return 5 + case PA6: + return 6 + case PA7: + return 7 + case PB0: + return 8 + case PB1: + return 9 + case PC0: + return 10 + case PC1: + return 11 + case PC2: + return 12 + case PC3: + return 13 + case PC4: + return 14 + case PC5: + return 15 + } + + return 0 +} diff --git a/emb/machine/machine_stm32_exti_afio.go b/emb/machine/machine_stm32_exti_afio.go new file mode 100644 index 00000000..89a95067 --- /dev/null +++ b/emb/machine/machine_stm32_exti_afio.go @@ -0,0 +1,27 @@ +//go:build stm32f1 + +package machine + +import ( + "device/stm32" + "runtime/volatile" +) + +func getEXTIConfigRegister(pin uint8) *volatile.Register32 { + switch (pin & 0xf) / 4 { + case 0: + return &stm32.AFIO.EXTICR1 + case 1: + return &stm32.AFIO.EXTICR2 + case 2: + return &stm32.AFIO.EXTICR3 + case 3: + return &stm32.AFIO.EXTICR4 + } + return nil +} + +func enableEXTIConfigRegisters() { + // Enable AFIO + stm32.RCC.APB2ENR.SetBits(stm32.RCC_APB2ENR_AFIOEN) +} diff --git a/emb/machine/machine_stm32_exti_exti.go b/emb/machine/machine_stm32_exti_exti.go new file mode 100644 index 00000000..874a0438 --- /dev/null +++ b/emb/machine/machine_stm32_exti_exti.go @@ -0,0 +1,26 @@ +//go:build stm32l5 + +package machine + +import ( + "device/stm32" + "runtime/volatile" +) + +func getEXTIConfigRegister(pin uint8) *volatile.Register32 { + switch (pin & 0xf) / 4 { + case 0: + return &stm32.EXTI.EXTICR1 + case 1: + return &stm32.EXTI.EXTICR2 + case 2: + return &stm32.EXTI.EXTICR3 + case 3: + return &stm32.EXTI.EXTICR4 + } + return nil +} + +func enableEXTIConfigRegisters() { + // No-op +} diff --git a/emb/machine/machine_stm32_exti_syscfg.go b/emb/machine/machine_stm32_exti_syscfg.go new file mode 100644 index 00000000..f7c91f81 --- /dev/null +++ b/emb/machine/machine_stm32_exti_syscfg.go @@ -0,0 +1,27 @@ +//go:build stm32 && !stm32f1 && !stm32l5 && !stm32wlx + +package machine + +import ( + "device/stm32" + "runtime/volatile" +) + +func getEXTIConfigRegister(pin uint8) *volatile.Register32 { + switch (pin & 0xf) / 4 { + case 0: + return &stm32.SYSCFG.EXTICR1 + case 1: + return &stm32.SYSCFG.EXTICR2 + case 2: + return &stm32.SYSCFG.EXTICR3 + case 3: + return &stm32.SYSCFG.EXTICR4 + } + return nil +} + +func enableEXTIConfigRegisters() { + // Enable SYSCFG + stm32.RCC.APB2ENR.SetBits(stm32.RCC_APB2ENR_SYSCFGEN) +} diff --git a/emb/machine/machine_stm32_exti_syscfg_noenable.go b/emb/machine/machine_stm32_exti_syscfg_noenable.go new file mode 100644 index 00000000..85f47fa9 --- /dev/null +++ b/emb/machine/machine_stm32_exti_syscfg_noenable.go @@ -0,0 +1,26 @@ +//go:build stm32wlx + +package machine + +import ( + "device/stm32" + "runtime/volatile" +) + +func getEXTIConfigRegister(pin uint8) *volatile.Register32 { + switch (pin & 0xf) / 4 { + case 0: + return &stm32.SYSCFG.EXTICR1 + case 1: + return &stm32.SYSCFG.EXTICR2 + case 2: + return &stm32.SYSCFG.EXTICR3 + case 3: + return &stm32.SYSCFG.EXTICR4 + } + return nil +} + +func enableEXTIConfigRegisters() { + // No registers to enable +} diff --git a/emb/machine/machine_stm32_flash.go b/emb/machine/machine_stm32_flash.go new file mode 100644 index 00000000..280dc898 --- /dev/null +++ b/emb/machine/machine_stm32_flash.go @@ -0,0 +1,115 @@ +//go:build stm32f4 || stm32l4 || stm32wlx + +package machine + +import ( + "device/stm32" + + "unsafe" +) + +// compile-time check for ensuring we fulfill BlockDevice interface +var _ BlockDevice = flashBlockDevice{} + +var Flash flashBlockDevice + +type flashBlockDevice struct { +} + +// ReadAt reads the given number of bytes from the block device. +func (f flashBlockDevice) ReadAt(p []byte, off int64) (n int, err error) { + if FlashDataStart()+uintptr(off)+uintptr(len(p)) > FlashDataEnd() { + return 0, errFlashCannotReadPastEOF + } + + data := unsafe.Slice((*byte)(unsafe.Pointer(FlashDataStart()+uintptr(off))), len(p)) + copy(p, data) + + return len(p), nil +} + +// WriteAt writes the given number of bytes to the block device. +// Only double-word (64 bits) length data can be programmed. See rm0461 page 78. +// If the length of p is not long enough it will be padded with 0xFF bytes. +// This method assumes that the destination is already erased. +func (f flashBlockDevice) WriteAt(p []byte, off int64) (n int, err error) { + if FlashDataStart()+uintptr(off)+uintptr(len(p)) > FlashDataEnd() { + return 0, errFlashCannotWritePastEOF + } + + unlockFlash() + defer lockFlash() + + p = flashPad(p, int(f.WriteBlockSize())) + return writeFlashData(FlashDataStart()+uintptr(off), p) +} + +// Size returns the number of bytes in this block device. +func (f flashBlockDevice) Size() int64 { + return int64(FlashDataEnd() - FlashDataStart()) +} + +// WriteBlockSize returns the block size in which data can be written to +// memory. It can be used by a client to optimize writes, non-aligned writes +// should always work correctly. +func (f flashBlockDevice) WriteBlockSize() int64 { + return writeBlockSize +} + +func eraseBlockSize() int64 { + return eraseBlockSizeValue +} + +// EraseBlockSize returns the smallest erasable area on this particular chip +// in bytes. This is used for the block size in EraseBlocks. +// It must be a power of two, and may be as small as 1. A typical size is 4096. +// TODO: correctly handle processors that have differently sized blocks +// in different areas of memory like the STM32F40x and STM32F1x. +func (f flashBlockDevice) EraseBlockSize() int64 { + return eraseBlockSize() +} + +// EraseBlocks erases the given number of blocks. An implementation may +// transparently coalesce ranges of blocks into larger bundles if the chip +// supports this. The start and len parameters are in block numbers, use +// EraseBlockSize to map addresses to blocks. +// Note that block 0 should map to the address of FlashDataStart(). +func (f flashBlockDevice) EraseBlocks(start, len int64) error { + var address uintptr = uintptr(start*f.EraseBlockSize()) + FlashDataStart() + blk := int64(address-uintptr(memoryStart)) / f.EraseBlockSize() + + unlockFlash() + defer lockFlash() + + for i := blk; i < blk+len; i++ { + if err := eraseBlock(uint32(i)); err != nil { + return err + } + } + + return nil +} + +const memoryStart = 0x08000000 + +func unlockFlash() { + // keys as described rm0461 page 76 + var fkey1 uint32 = 0x45670123 + var fkey2 uint32 = 0xCDEF89AB + + // Wait for the flash memory not to be busy + for stm32.FLASH.GetSR_BSY() != 0 { + } + + // Check if the controller is unlocked already + if stm32.FLASH.GetCR_LOCK() != 0 { + // Write the first key + stm32.FLASH.SetKEYR(fkey1) + // Write the second key + stm32.FLASH.SetKEYR(fkey2) + } +} + +func lockFlash() { + stm32.FLASH.SetCR_LOCK(1) +} diff --git a/emb/machine/machine_stm32_gpio_reva.go b/emb/machine/machine_stm32_gpio_reva.go new file mode 100644 index 00000000..2fb5a034 --- /dev/null +++ b/emb/machine/machine_stm32_gpio_reva.go @@ -0,0 +1,92 @@ +//go:build stm32 && !stm32l4 && !stm32l5 && !stm32wlx + +package machine + +import ( + "device/stm32" +) + +// This variant of the GPIO input interrupt logic is for +// chips with a smaller number of interrupt channels +// (that fits in a single register). + +// +// STM32 allows one interrupt source per pin number, with +// the same pin number in different ports sharing a single +// interrupt source (so PA0, PB0, PC0 all share). Only a +// single physical pin can be connected to each interrupt +// line. +// +// To call interrupt callbacks, we record here for each +// pin number the callback and the actual associated pin. +// + +// Callbacks for pin interrupt events +var pinCallbacks [16]func(Pin) + +// The pin currently associated with interrupt callback +// for a given slot. +var interruptPins [16]Pin + +// SetInterrupt sets an interrupt to be executed when a particular pin changes +// state. The pin should already be configured as an input, including a pull up +// or down if no external pull is provided. +// +// This call will replace a previously set callback on this pin. You can pass a +// nil func to unset the pin change interrupt. If you do so, the change +// parameter is ignored and can be set to any value (such as 0). +func (p Pin) SetInterrupt(change PinChange, callback func(Pin)) error { + port := uint32(uint8(p) / 16) + pin := uint8(p) % 16 + + enableEXTIConfigRegisters() + + if callback == nil { + stm32.EXTI.IMR.ClearBits(1 << pin) + pinCallbacks[pin] = nil + return nil + } + + if pinCallbacks[pin] != nil { + // The pin was already configured. + // To properly re-configure a pin, unset it first and set a new + // configuration. + return ErrNoPinChangeChannel + } + + // Set the callback now (before the interrupt is enabled) to avoid + // possible race condition + pinCallbacks[pin] = callback + interruptPins[pin] = p + + crReg := getEXTIConfigRegister(pin) + shift := (pin & 0x3) * 4 + crReg.ReplaceBits(port, 0xf, shift) + + if (change & PinRising) != 0 { + stm32.EXTI.RTSR.SetBits(1 << pin) + } + if (change & PinFalling) != 0 { + stm32.EXTI.FTSR.SetBits(1 << pin) + } + stm32.EXTI.IMR.SetBits(1 << pin) + + intr := p.registerInterrupt() + intr.SetPriority(0) + intr.Enable() + + return nil +} + +func handlePinInterrupt(pin uint8) { + if stm32.EXTI.PR.HasBits(1 << pin) { + // Writing 1 to the pending register clears the + // pending flag for that bit + stm32.EXTI.PR.Set(1 << pin) + + callback := pinCallbacks[pin] + if callback != nil { + callback(interruptPins[pin]) + } + } +} diff --git a/emb/machine/machine_stm32_gpio_revb.go b/emb/machine/machine_stm32_gpio_revb.go new file mode 100644 index 00000000..49094c73 --- /dev/null +++ b/emb/machine/machine_stm32_gpio_revb.go @@ -0,0 +1,79 @@ +//go:build stm32l4 || stm32l5 + +package machine + +import ( + "device/stm32" +) + +// This variant of the GPIO input interrupt logic is for +// chips with a larger number of interrupt channels (more +// than fits in a single register). + +// +// STM32 allows one interrupt source per pin number, with +// the same pin number in different ports sharing a single +// interrupt source (so PA0, PB0, PC0 all share). Only a +// single physical pin can be connected to each interrupt +// line. +// +// To call interrupt callbacks, we record here for each +// pin number the callback and the actual associated pin. +// + +// Callbacks for pin interrupt events +var pinCallbacks [16]func(Pin) + +// The pin currently associated with interrupt callback +// for a given slot. +var interruptPins [16]Pin + +// SetInterrupt sets an interrupt to be executed when a particular pin changes +// state. The pin should already be configured as an input, including a pull up +// or down if no external pull is provided. +// +// This call will replace a previously set callback on this pin. You can pass a +// nil func to unset the pin change interrupt. If you do so, the change +// parameter is ignored and can be set to any value (such as 0). +func (p Pin) SetInterrupt(change PinChange, callback func(Pin)) error { + port := uint32(uint8(p) / 16) + pin := uint8(p) % 16 + + enableEXTIConfigRegisters() + + if callback == nil { + stm32.EXTI.IMR1.ClearBits(1 << pin) + pinCallbacks[pin] = nil + return nil + } + + if pinCallbacks[pin] != nil { + // The pin was already configured. + // To properly re-configure a pin, unset it first and set a new + // configuration. + return ErrNoPinChangeChannel + } + + // Set the callback now (before the interrupt is enabled) to avoid + // possible race condition + pinCallbacks[pin] = callback + interruptPins[pin] = p + + crReg := getEXTIConfigRegister(pin) + shift := (pin & 0x3) * 4 + crReg.ReplaceBits(port, 0xf, shift) + + if (change & PinRising) != 0 { + stm32.EXTI.RTSR1.SetBits(1 << pin) + } + if (change & PinFalling) != 0 { + stm32.EXTI.FTSR1.SetBits(1 << pin) + } + stm32.EXTI.IMR1.SetBits(1 << pin) + + intr := p.registerInterrupt() + intr.SetPriority(0) + intr.Enable() + + return nil +} diff --git a/emb/machine/machine_stm32_gpio_revb_mp.go b/emb/machine/machine_stm32_gpio_revb_mp.go new file mode 100644 index 00000000..2124b162 --- /dev/null +++ b/emb/machine/machine_stm32_gpio_revb_mp.go @@ -0,0 +1,86 @@ +//go:build stm32wlx + +package machine + +import ( + "device/stm32" +) + +// +// This variant of the GPIO input interrupt logic is for +// multi-core chips with a larger number of interrupt +// channels (more than fits in a single register). +// +// This logic is currently used by the single-core stm32wle5 +// due to a patch in stm32-rs project that has renamed the +// registers to match the dual-core names. This renaming is +// being discussed and may change in future. +// + +// +// STM32 allows one interrupt source per pin number, with +// the same pin number in different ports sharing a single +// interrupt source (so PA0, PB0, PC0 all share). Only a +// single physical pin can be connected to each interrupt +// line. +// +// To call interrupt callbacks, we record here for each +// pin number the callback and the actual associated pin. +// + +// Callbacks for pin interrupt events +var pinCallbacks [16]func(Pin) + +// The pin currently associated with interrupt callback +// for a given slot. +var interruptPins [16]Pin + +// SetInterrupt sets an interrupt to be executed when a particular pin changes +// state. The pin should already be configured as an input, including a pull up +// or down if no external pull is provided. +// +// This call will replace a previously set callback on this pin. You can pass a +// nil func to unset the pin change interrupt. If you do so, the change +// parameter is ignored and can be set to any value (such as 0). +func (p Pin) SetInterrupt(change PinChange, callback func(Pin)) error { + port := uint32(uint8(p) / 16) + pin := uint8(p) % 16 + + enableEXTIConfigRegisters() + + if callback == nil { + stm32.EXTI.C1IMR1.ClearBits(1 << pin) + pinCallbacks[pin] = nil + return nil + } + + if pinCallbacks[pin] != nil { + // The pin was already configured. + // To properly re-configure a pin, unset it first and set a new + // configuration. + return ErrNoPinChangeChannel + } + + // Set the callback now (before the interrupt is enabled) to avoid + // possible race condition + pinCallbacks[pin] = callback + interruptPins[pin] = p + + crReg := getEXTIConfigRegister(pin) + shift := (pin & 0x3) * 4 + crReg.ReplaceBits(port, 0xf, shift) + + if (change & PinRising) != 0 { + stm32.EXTI.RTSR1.SetBits(1 << pin) + } + if (change & PinFalling) != 0 { + stm32.EXTI.FTSR1.SetBits(1 << pin) + } + stm32.EXTI.C1IMR1.SetBits(1 << pin) + + intr := p.registerInterrupt() + intr.SetPriority(0) + intr.Enable() + + return nil +} diff --git a/emb/machine/machine_stm32_i2c_reva.go b/emb/machine/machine_stm32_i2c_reva.go new file mode 100644 index 00000000..1e4fd282 --- /dev/null +++ b/emb/machine/machine_stm32_i2c_reva.go @@ -0,0 +1,436 @@ +//go:build stm32f4 || stm32f1 + +package machine + +// I2C implementation for 'older' STM32 MCUs, including the F1 and F4 series +// of MCUs. + +import ( + "device/stm32" + "unsafe" +) + +const ( + flagOVR = 0x00010800 + flagAF = 0x00010400 + flagARLO = 0x00010200 + flagBERR = 0x00010100 + flagTXE = 0x00010080 + flagRXNE = 0x00010040 + flagSTOPF = 0x00010010 + flagADD10 = 0x00010008 + flagBTF = 0x00010004 + flagADDR = 0x00010002 + flagSB = 0x00010001 + flagDUALF = 0x00100080 + flagGENCALL = 0x00100010 + flagTRA = 0x00100004 + flagBUSY = 0x00100002 + flagMSL = 0x00100001 +) + +func (i2c *I2C) hasFlag(flag uint32) bool { + const mask = 0x0000FFFF + if uint8(flag>>16) == 1 { + return i2c.Bus.SR1.HasBits(flag & mask) + } else { + return i2c.Bus.SR2.HasBits(flag & mask) + } +} + +func (i2c *I2C) clearFlag(flag uint32) { + const mask = 0x0000FFFF + i2c.Bus.SR1.Set(^(flag & mask)) +} + +// clearFlagADDR reads both status registers to clear any pending ADDR flags. +func (i2c *I2C) clearFlagADDR() { + i2c.Bus.SR1.Get() + i2c.Bus.SR2.Get() +} + +func (i2c *I2C) waitForFlag(flag uint32, set bool) bool { + const tryMax = 10000 + hasFlag := false + for i := 0; !hasFlag && i < tryMax; i++ { + hasFlag = i2c.hasFlag(flag) == set + } + return hasFlag +} + +func (i2c *I2C) waitForFlagOrError(flag uint32, set bool) bool { + const tryMax = 10000 + hasFlag := false + for i := 0; !hasFlag && i < tryMax; i++ { + if hasFlag = i2c.hasFlag(flag) == set; !hasFlag { + // check for ACK failure + if i2c.hasFlag(flagAF) { + // generate stop condition + i2c.Bus.CR1.SetBits(stm32.I2C_CR1_STOP) + // clear pending flags + i2c.clearFlag(flagAF) + return false + } else if i2c.hasFlag(flagSTOPF) { + // clear stop flag + i2c.clearFlag(flagSTOPF) + return false + } + } + } + return hasFlag +} + +type transferOption uint32 + +const ( + frameFirst = 0x00000001 + frameFirstAndNext = 0x00000002 + frameNext = 0x00000004 + frameFirstAndLast = 0x00000008 + frameLastNoStop = 0x00000010 + frameLast = 0x00000020 + frameNoOption = 0xFFFF0000 +) + +// I2C fast mode (Fm) duty cycle +const ( + DutyCycle2 = 0 + DutyCycle16x9 = 1 +) + +// I2CConfig is used to store config info for I2C. +type I2CConfig struct { + Frequency uint32 + SCL Pin + SDA Pin + DutyCycle uint8 +} + +// Configure is intended to setup the STM32 I2C interface. +func (i2c *I2C) Configure(config I2CConfig) error { + + // The following is the required sequence in controller mode. + // 1. Program the peripheral input clock in I2C_CR2 Register in order to + // generate correct timings + // 2. Configure the clock control registers + // 3. Configure the rise time register + // 4. Program the I2C_CR1 register to enable the peripheral + // 5. Set the START bit in the I2C_CR1 register to generate a Start condition + + // disable I2C interface before any configuration changes + i2c.Bus.CR1.ClearBits(stm32.I2C_CR1_PE) + + // reset I2C bus + i2c.Bus.CR1.SetBits(stm32.I2C_CR1_SWRST) + i2c.Bus.CR1.ClearBits(stm32.I2C_CR1_SWRST) + + // enable clock for I2C + enableAltFuncClock(unsafe.Pointer(i2c.Bus)) + + // init pins + if config.SCL == 0 && config.SDA == 0 { + config.SCL = I2C0_SCL_PIN + config.SDA = I2C0_SDA_PIN + } + i2c.configurePins(config) + + // default to 100 kHz (Sm, standard mode) if no frequency is set + if config.Frequency == 0 { + config.Frequency = 100 * KHz + } + + // configure I2C input clock + i2c.Bus.CR2.SetBits(i2c.getFreqRange(config)) + + // configure rise time + i2c.Bus.TRISE.Set(i2c.getRiseTime(config)) + + // configure clock control + i2c.Bus.CCR.Set(i2c.getSpeed(config)) + + // disable GeneralCall and NoStretch modes + i2c.Bus.CR1.ClearBits(stm32.I2C_CR1_ENGC | stm32.I2C_CR1_NOSTRETCH) + + // enable I2C interface + i2c.Bus.CR1.SetBits(stm32.I2C_CR1_PE) + + return nil +} + +// SetBaudRate sets the communication speed for I2C. +func (i2c *I2C) SetBaudRate(br uint32) error { + // TODO: implement + return errI2CNotImplemented +} + +func (i2c *I2C) Tx(addr uint16, w, r []byte) error { + + if err := i2c.controllerTransmit(addr, w); nil != err { + return err + } + + if len(r) > 0 { + if err := i2c.controllerReceive(addr, r); nil != err { + return err + } + } + + return nil +} + +func (i2c *I2C) controllerTransmit(addr uint16, w []byte) error { + + if !i2c.waitForFlag(flagBUSY, false) { + return errI2CBusReadyTimeout + } + + // disable POS + i2c.Bus.CR1.ClearBits(stm32.I2C_CR1_POS) + + pos := 0 + rem := len(w) + + // send peripheral address + if err := i2c.controllerRequestWrite(addr, frameNoOption); nil != err { + return err + } + + // clear ADDR flag + i2c.clearFlagADDR() + + for rem > 0 { + // wait for TXE flag set + if !i2c.waitForFlagOrError(flagTXE, true) { + return errI2CAckExpected + } + + // write data to DR + i2c.Bus.DR.Set(uint32(w[pos])) + // update counters + pos++ + rem-- + + if i2c.hasFlag(flagBTF) && rem != 0 { + // write data to DR + i2c.Bus.DR.Set(uint32(w[pos])) + // update counters + pos++ + rem-- + } + + // wait for transfer finished flag BTF set + if !i2c.waitForFlagOrError(flagBTF, true) { + return errI2CWriteTimeout + } + } + + // generate stop condition + i2c.Bus.CR1.SetBits(stm32.I2C_CR1_STOP) + + return nil +} + +func (i2c *I2C) controllerRequestWrite(addr uint16, option transferOption) error { + + if frameFirstAndLast == option || frameFirst == option || frameNoOption == option { + // generate start condition + i2c.Bus.CR1.SetBits(stm32.I2C_CR1_START) + } else if false /* (hi2c->PreviousState == I2C_STATE_MASTER_BUSY_RX) */ { + // generate restart condition + i2c.Bus.CR1.SetBits(stm32.I2C_CR1_START) + } + + // ensure start bit is set + if !i2c.waitForFlag(flagSB, true) { + return errI2CSignalStartTimeout + } + + // send peripheral address + i2c.Bus.DR.Set(uint32(addr) << 1) + + // wait for address ACK from peripheral + if !i2c.waitForFlagOrError(flagADDR, true) { + return errI2CSignalStartTimeout + } + + return nil +} + +func (i2c *I2C) controllerReceive(addr uint16, r []byte) error { + + if !i2c.waitForFlag(flagBUSY, false) { + return errI2CBusReadyTimeout + } + + // disable POS + i2c.Bus.CR1.ClearBits(stm32.I2C_CR1_POS) + + pos := 0 + rem := len(r) + + // send peripheral address + if err := i2c.controllerRequestRead(addr, frameNoOption); nil != err { + return err + } + + switch rem { + case 0: + // clear ADDR flag + i2c.clearFlagADDR() + // generate stop condition + i2c.Bus.CR1.SetBits(stm32.I2C_CR1_STOP) + + case 1: + // disable ACK + i2c.Bus.CR1.ClearBits(stm32.I2C_CR1_ACK) + // clear ADDR flag + i2c.clearFlagADDR() + // generate stop condition + i2c.Bus.CR1.SetBits(stm32.I2C_CR1_STOP) + + case 2: + // disable ACK + i2c.Bus.CR1.ClearBits(stm32.I2C_CR1_ACK) + // enable POS + i2c.Bus.CR1.SetBits(stm32.I2C_CR1_POS) + // clear ADDR flag + i2c.clearFlagADDR() + + default: + // enable ACK + i2c.Bus.CR1.SetBits(stm32.I2C_CR1_ACK) + // clear ADDR flag + i2c.clearFlagADDR() + } + + for rem > 0 { + switch rem { + case 1: + // wait until RXNE flag is set + if !i2c.waitForFlagOrError(flagRXNE, true) { + return errI2CReadTimeout + } + + // read data from DR + r[pos] = byte(i2c.Bus.DR.Get()) + + // update counters + pos++ + rem-- + + case 2: + // wait until transfer finished flag BTF is set + if !i2c.waitForFlag(flagBTF, true) { + return errI2CReadTimeout + } + + // generate stop condition + i2c.Bus.CR1.SetBits(stm32.I2C_CR1_STOP) + + // read data from DR + r[pos] = byte(i2c.Bus.DR.Get()) + + // update counters + pos++ + rem-- + + // read data from DR + r[pos] = byte(i2c.Bus.DR.Get()) + + // update counters + pos++ + rem-- + + case 3: + // wait until transfer finished flag BTF is set + if !i2c.waitForFlag(flagBTF, true) { + return errI2CReadTimeout + } + + // disable ACK + i2c.Bus.CR1.ClearBits(stm32.I2C_CR1_ACK) + + // read data from DR + r[pos] = byte(i2c.Bus.DR.Get()) + + // update counters + pos++ + rem-- + + // wait until transfer finished flag BTF is set + if !i2c.waitForFlag(flagBTF, true) { + return errI2CReadTimeout + } + + // generate stop condition + i2c.Bus.CR1.SetBits(stm32.I2C_CR1_STOP) + + // read data from DR + r[pos] = byte(i2c.Bus.DR.Get()) + + // update counters + pos++ + rem-- + + // read data from DR + r[pos] = byte(i2c.Bus.DR.Get()) + + // update counters + pos++ + rem-- + + default: + // wait until RXNE flag is set + if !i2c.waitForFlagOrError(flagRXNE, true) { + return errI2CReadTimeout + } + + // read data from DR + r[pos] = byte(i2c.Bus.DR.Get()) + + // update counters + pos++ + rem-- + + if i2c.hasFlag(flagBTF) { + // read data from DR + r[pos] = byte(i2c.Bus.DR.Get()) + + // update counters + pos++ + rem-- + } + } + } + + return nil +} + +func (i2c *I2C) controllerRequestRead(addr uint16, option transferOption) error { + + // enable ACK + i2c.Bus.CR1.SetBits(stm32.I2C_CR1_ACK) + + if frameFirstAndLast == option || frameFirst == option || frameNoOption == option { + // generate start condition + i2c.Bus.CR1.SetBits(stm32.I2C_CR1_START) + } else if false /* (hi2c->PreviousState == I2C_STATE_MASTER_BUSY_TX) */ { + // generate restart condition + i2c.Bus.CR1.SetBits(stm32.I2C_CR1_START) + } + + // ensure start bit is set + if !i2c.waitForFlag(flagSB, true) { + return errI2CSignalStartTimeout + } + + // send peripheral address + i2c.Bus.DR.Set(uint32(addr)<<1 | 1) + + // wait for address ACK from peripheral + if !i2c.waitForFlagOrError(flagADDR, true) { + return errI2CSignalStartTimeout + } + + return nil +} diff --git a/emb/machine/machine_stm32_i2c_revb.go b/emb/machine/machine_stm32_i2c_revb.go new file mode 100644 index 00000000..006661f9 --- /dev/null +++ b/emb/machine/machine_stm32_i2c_revb.go @@ -0,0 +1,378 @@ +//go:build stm32l5 || stm32f7 || stm32l4 || stm32l0 || stm32wlx + +package machine + +import ( + "device/stm32" + "unsafe" +) + +//go:linkname ticks runtime.ticks +func ticks() int64 + +// I2C implementation for 'newer' STM32 MCUs, including the F7, L5 and L4 +// series of MCUs. +// +// Currently, only 100KHz mode is supported + +const ( + flagBUSY = stm32.I2C_ISR_BUSY + flagTCR = stm32.I2C_ISR_TCR + flagRXNE = stm32.I2C_ISR_RXNE + flagSTOPF = stm32.I2C_ISR_STOPF + flagAF = stm32.I2C_ISR_NACKF + flagTXIS = stm32.I2C_ISR_TXIS + flagTXE = stm32.I2C_ISR_TXE +) + +const ( + MAX_NBYTE_SIZE = 255 + + // 100ms delay = 100e6ns / 16ns + // In runtime_stm32_timers.go, tick is fixed at 16ns per tick + TIMEOUT_TICKS = 100e6 / 16 + + I2C_NO_STARTSTOP = 0x0 + I2C_GENERATE_START_WRITE = 0x80000000 | stm32.I2C_CR2_START + I2C_GENERATE_START_READ = 0x80000000 | stm32.I2C_CR2_START | stm32.I2C_CR2_RD_WRN + I2C_GENERATE_STOP = 0x80000000 | stm32.I2C_CR2_STOP +) + +type I2C struct { + Bus *stm32.I2C_Type + AltFuncSelector uint8 +} + +// I2CConfig is used to store config info for I2C. +type I2CConfig struct { + Frequency uint32 + SCL Pin + SDA Pin +} + +func (i2c *I2C) Configure(config I2CConfig) error { + // Frequency range + switch config.Frequency { + case 0: + config.Frequency = 100 * KHz + case 10 * KHz, 100 * KHz, 400 * KHz, 500 * KHz: + default: + return errI2CNotImplemented + } + + // disable I2C interface before any configuration changes + i2c.Bus.CR1.ClearBits(stm32.I2C_CR1_PE) + + // enable clock for I2C + enableAltFuncClock(unsafe.Pointer(i2c.Bus)) + + // init pins + if config.SCL == 0 && config.SDA == 0 { + config.SCL = I2C0_SCL_PIN + config.SDA = I2C0_SDA_PIN + } + i2c.configurePins(config) + + i2c.Bus.TIMINGR.Set(i2c.getFreqRange(config.Frequency)) + + // Disable Own Address1 before set the Own Address1 configuration + i2c.Bus.OAR1.ClearBits(stm32.I2C_OAR1_OA1EN) + + // 7 bit addressing, no self address + i2c.Bus.OAR1.Set(stm32.I2C_OAR1_OA1EN) + + // Enable the AUTOEND by default, and enable NACK (should be disable only during Slave process + i2c.Bus.CR2.Set(stm32.I2C_CR2_AUTOEND | stm32.I2C_CR2_NACK) + + // Disable Own Address2 / Dual Addressing + i2c.Bus.OAR2.Set(0) + + // Disable Generalcall and NoStretch, Enable peripheral + i2c.Bus.CR1.Set(stm32.I2C_CR1_PE) + + return nil +} + +// SetBaudRate sets the communication speed for I2C. +func (i2c *I2C) SetBaudRate(br uint32) error { + switch br { + case 10 * KHz, 100 * KHz, 400 * KHz, 500 * KHz: + default: + return errI2CNotImplemented + } + + // disable I2C interface before any configuration changes + i2c.Bus.CR1.ClearBits(stm32.I2C_CR1_PE) + + i2c.Bus.TIMINGR.Set(i2c.getFreqRange(br)) + + // Disable Generalcall and NoStretch, Enable peripheral + i2c.Bus.CR1.Set(stm32.I2C_CR1_PE) + + return nil +} + +func (i2c *I2C) Tx(addr uint16, w, r []byte) error { + if len(w) > 0 { + if err := i2c.controllerTransmit(addr, w); nil != err { + return err + } + } + + if len(r) > 0 { + if err := i2c.controllerReceive(addr, r); nil != err { + return err + } + } + + return nil +} + +func (i2c *I2C) configurePins(config I2CConfig) { + config.SCL.ConfigureAltFunc(PinConfig{Mode: PinModeI2CSCL}, i2c.AltFuncSelector) + config.SDA.ConfigureAltFunc(PinConfig{Mode: PinModeI2CSDA}, i2c.AltFuncSelector) +} + +func (i2c *I2C) controllerTransmit(addr uint16, w []byte) error { + start := ticks() + + if !i2c.waitOnFlagUntilTimeout(flagBUSY, false, start) { + return errI2CBusReadyTimeout + } + + pos := 0 + xferCount := len(w) + xferSize := uint8(xferCount) + if xferCount > MAX_NBYTE_SIZE { + // Large write, indicate reload + xferSize = MAX_NBYTE_SIZE + i2c.transferConfig(addr, xferSize, stm32.I2C_CR2_RELOAD, I2C_GENERATE_START_WRITE) + } else { + // Small write, auto-end + i2c.transferConfig(addr, xferSize, stm32.I2C_CR2_AUTOEND, I2C_GENERATE_START_WRITE) + } + + for xferCount > 0 { + if !i2c.waitOnTXISFlagUntilTimeout(start) { + return errI2CWriteTimeout + } + + i2c.Bus.TXDR.Set(uint32(w[pos])) + pos++ + xferCount-- + xferSize-- + + // If we've written the last byte of this chunk + if xferCount != 0 && xferSize == 0 { + // Wait for Transfer Complete Reload to be flagged + if !i2c.waitOnFlagUntilTimeout(flagTCR, true, start) { + return errI2CWriteTimeout + } + + if xferCount > MAX_NBYTE_SIZE { + // Large write remaining, indicate reload + xferSize = MAX_NBYTE_SIZE + i2c.transferConfig(addr, xferSize, stm32.I2C_CR2_RELOAD, I2C_NO_STARTSTOP) + } else { + // Small write, auto-end + xferSize = uint8(xferCount) + i2c.transferConfig(addr, xferSize, stm32.I2C_CR2_AUTOEND, I2C_NO_STARTSTOP) + } + } + } + + if !i2c.waitOnStopFlagUntilTimeout(start) { + return errI2CWriteTimeout + } + + i2c.clearFlag(stm32.I2C_ISR_STOPF) + + i2c.resetCR2() + + return nil +} + +func (i2c *I2C) controllerReceive(addr uint16, r []byte) error { + start := ticks() + + if !i2c.waitOnFlagUntilTimeout(flagBUSY, false, start) { + return errI2CBusReadyTimeout + } + + pos := 0 + xferCount := len(r) + xferSize := uint8(xferCount) + if xferCount > MAX_NBYTE_SIZE { + // Large read, indicate reload + xferSize = MAX_NBYTE_SIZE + i2c.transferConfig(addr, xferSize, stm32.I2C_CR2_RELOAD, I2C_GENERATE_START_READ) + } else { + // Small read, auto-end + i2c.transferConfig(addr, xferSize, stm32.I2C_CR2_AUTOEND, I2C_GENERATE_START_READ) + } + + for xferCount > 0 { + if !i2c.waitOnRXNEFlagUntilTimeout(start) { + return errI2CWriteTimeout + } + + r[pos] = uint8(i2c.Bus.RXDR.Get()) + pos++ + xferCount-- + xferSize-- + + // If we've read the last byte of this chunk + if xferCount != 0 && xferSize == 0 { + // Wait for Transfer Complete Reload to be flagged + if !i2c.waitOnFlagUntilTimeout(flagTCR, true, start) { + return errI2CWriteTimeout + } + + if xferCount > MAX_NBYTE_SIZE { + // Large read remaining, indicate reload + xferSize = MAX_NBYTE_SIZE + i2c.transferConfig(addr, xferSize, stm32.I2C_CR2_RELOAD, I2C_NO_STARTSTOP) + } else { + // Small read, auto-end + xferSize = uint8(xferCount) + i2c.transferConfig(addr, xferSize, stm32.I2C_CR2_AUTOEND, I2C_NO_STARTSTOP) + } + } + } + + if !i2c.waitOnStopFlagUntilTimeout(start) { + return errI2CWriteTimeout + } + + i2c.clearFlag(stm32.I2C_ISR_STOPF) + + i2c.resetCR2() + + return nil +} + +func (i2c *I2C) waitOnFlagUntilTimeout(flag uint32, set bool, startTicks int64) bool { + for i2c.hasFlag(flag) != set { + if (ticks() - startTicks) > TIMEOUT_TICKS { + return false + } + } + return true +} + +func (i2c *I2C) waitOnRXNEFlagUntilTimeout(startTicks int64) bool { + for !i2c.hasFlag(flagRXNE) { + if i2c.isAcknowledgeFailed(startTicks) { + return false + } + + if i2c.hasFlag(flagSTOPF) { + i2c.clearFlag(flagSTOPF) + i2c.resetCR2() + return false + } + + if (ticks() - startTicks) > TIMEOUT_TICKS { + return false + } + } + + return true +} + +func (i2c *I2C) waitOnTXISFlagUntilTimeout(startTicks int64) bool { + for !i2c.hasFlag(flagTXIS) { + if i2c.isAcknowledgeFailed(startTicks) { + return false + } + + if (ticks() - startTicks) > TIMEOUT_TICKS { + return false + } + } + + return true +} + +func (i2c *I2C) waitOnStopFlagUntilTimeout(startTicks int64) bool { + for !i2c.hasFlag(flagSTOPF) { + if i2c.isAcknowledgeFailed(startTicks) { + return false + } + + if (ticks() - startTicks) > TIMEOUT_TICKS { + return false + } + } + + return true +} + +func (i2c *I2C) isAcknowledgeFailed(startTicks int64) bool { + if i2c.hasFlag(flagAF) { + // Wait until STOP Flag is reset + // AutoEnd should be initiate after AF + for !i2c.hasFlag(flagSTOPF) { + if (ticks() - startTicks) > TIMEOUT_TICKS { + return true + } + } + + i2c.clearFlag(flagAF) + i2c.clearFlag(flagSTOPF) + i2c.flushTXDR() + i2c.resetCR2() + + return true + } + + return false +} + +func (i2c *I2C) flushTXDR() { + // If a pending TXIS flag is set, write a dummy data in TXDR to clear it + if i2c.hasFlag(flagTXIS) { + i2c.Bus.TXDR.Set(0) + } + + // Flush TX register if not empty + if !i2c.hasFlag(flagTXE) { + i2c.clearFlag(flagTXE) + } +} + +func (i2c *I2C) resetCR2() { + i2c.Bus.CR2.ClearBits(stm32.I2C_CR2_SADD_Msk | + stm32.I2C_CR2_HEAD10R_Msk | + stm32.I2C_CR2_NBYTES_Msk | + stm32.I2C_CR2_RELOAD_Msk | + stm32.I2C_CR2_RD_WRN_Msk) +} + +func (i2c *I2C) transferConfig(addr uint16, size uint8, mode uint32, request uint32) { + mask := uint32(stm32.I2C_CR2_SADD_Msk | + stm32.I2C_CR2_NBYTES_Msk | + stm32.I2C_CR2_RELOAD_Msk | + stm32.I2C_CR2_AUTOEND_Msk | + (stm32.I2C_CR2_RD_WRN & uint32(request>>(31-stm32.I2C_CR2_RD_WRN_Pos))) | + stm32.I2C_CR2_START_Msk | + stm32.I2C_CR2_STOP_Msk) + + value := (uint32(addr<<1) & stm32.I2C_CR2_SADD_Msk) | + ((uint32(size) << stm32.I2C_CR2_NBYTES_Pos) & stm32.I2C_CR2_NBYTES_Msk) | + mode | request + + i2c.Bus.CR2.ReplaceBits(value, mask, 0) +} + +func (i2c *I2C) hasFlag(flag uint32) bool { + return i2c.Bus.ISR.HasBits(flag) +} + +func (i2c *I2C) clearFlag(flag uint32) { + if flag == stm32.I2C_ISR_TXE { + i2c.Bus.ISR.SetBits(flag) + } else { + i2c.Bus.ICR.SetBits(flag) + } +} diff --git a/emb/machine/machine_stm32_iwdg.go b/emb/machine/machine_stm32_iwdg.go new file mode 100644 index 00000000..1139d546 --- /dev/null +++ b/emb/machine/machine_stm32_iwdg.go @@ -0,0 +1,66 @@ +//go:build stm32 + +package machine + +import "device/stm32" + +var ( + Watchdog = &watchdogImpl{} +) + +const ( + // WatchdogMaxTimeout in milliseconds (32.768s) + // + // Timeout is based on 12-bit counter with /256 divider on + // 32.768kHz clock. See 21.3.3 of RM0090 for table. + WatchdogMaxTimeout = ((0xfff + 1) * 256 * 1024) / 32768 +) + +const ( + // Enable access to PR, RLR and WINR registers (0x5555) + iwdgKeyEnable = 0x5555 + // Reset the watchdog value (0xAAAA) + iwdgKeyReset = 0xaaaa + // Start the watchdog (0xCCCC) + iwdgKeyStart = 0xcccc + // Divide by 256 + iwdgDiv256 = 6 +) + +type watchdogImpl struct { +} + +// Configure the watchdog. +// +// This method should not be called after the watchdog is started and on +// some platforms attempting to reconfigure after starting the watchdog +// is explicitly forbidden / will not work. +func (wd *watchdogImpl) Configure(config WatchdogConfig) error { + + // Enable configuration of IWDG + stm32.IWDG.KR.Set(iwdgKeyEnable) + + // Unconditionally divide by /256 since we don't really need accuracy + // less than 8ms + stm32.IWDG.PR.Set(iwdgDiv256) + + timeout := config.TimeoutMillis + if timeout > WatchdogMaxTimeout { + timeout = WatchdogMaxTimeout + } + + // Set reload value based on /256 divider + stm32.IWDG.RLR.Set(((config.TimeoutMillis*32768 + (256 * 1024) - 1) / (256 * 1024)) - 1) + return nil +} + +// Starts the watchdog. +func (wd *watchdogImpl) Start() error { + stm32.IWDG.KR.Set(iwdgKeyStart) + return nil +} + +// Update the watchdog, indicating that `source` is healthy. +func (wd *watchdogImpl) Update() { + stm32.IWDG.KR.Set(iwdgKeyReset) +} diff --git a/emb/machine/machine_stm32_moder_gpio.go b/emb/machine/machine_stm32_moder_gpio.go new file mode 100644 index 00000000..b8585c28 --- /dev/null +++ b/emb/machine/machine_stm32_moder_gpio.go @@ -0,0 +1,164 @@ +//go:build stm32 && !stm32f103 + +package machine + +import ( + "device/stm32" +) + +// GPIO for the stm32 families except the stm32f1xx which uses a simpler but +// less flexible mechanism. Extend the go:build directive above to exclude other +// models in the stm32f1xx series as necessary + +const ( + // Mode Flag + PinOutput PinMode = 0 + PinInput PinMode = PinInputFloating + PinInputFloating PinMode = 1 + PinInputPulldown PinMode = 2 + PinInputPullup PinMode = 3 + + // for UART + PinModeUARTTX PinMode = 4 + PinModeUARTRX PinMode = 5 + + // for I2C + PinModeI2CSCL PinMode = 6 + PinModeI2CSDA PinMode = 7 + + // for SPI + PinModeSPICLK PinMode = 8 + PinModeSPISDO PinMode = 9 + PinModeSPISDI PinMode = 10 + + // for analog/ADC + PinInputAnalog PinMode = 11 + + // for PWM + PinModePWMOutput PinMode = 12 +) + +// Define several bitfields that have different names across chip families but +// essentially have the same meaning. +const ( + // MODER bitfields. + gpioModeInput = 0 + gpioModeOutput = 1 + gpioModeAlternate = 2 + gpioModeAnalog = 3 + gpioModeMask = 0x3 + + // PUPDR bitfields. + gpioPullFloating = 0 + gpioPullUp = 1 + gpioPullDown = 2 + gpioPullMask = 0x3 + + // OSPEED bitfields. + gpioOutputSpeedVeryHigh = 3 + gpioOutputSpeedHigh = 2 + gpioOutputSpeedMedium = 1 + gpioOutputSpeedLow = 0 + gpioOutputSpeedMask = 0x3 +) + +// Configure this pin with the given configuration +func (p Pin) Configure(config PinConfig) { + // Use the default system alternate function; this + // will only be used if you try to call this with + // one of the peripheral modes instead of vanilla GPIO. + p.ConfigureAltFunc(config, 0) +} + +// Configure this pin with the given configuration including alternate +// +// function mapping if necessary. +func (p Pin) ConfigureAltFunc(config PinConfig, altFunc uint8) { + // Configure the GPIO pin. + p.enableClock() + port := p.getPort() + pos := (uint8(p) % 16) * 2 // assume each field is two bits in size (with mask 0x3) + + switch config.Mode { + + // GPIO + case PinInputFloating: + port.MODER.ReplaceBits(gpioModeInput, gpioModeMask, pos) + port.PUPDR.ReplaceBits(gpioPullFloating, gpioPullMask, pos) + case PinInputPulldown: + port.MODER.ReplaceBits(gpioModeInput, gpioModeMask, pos) + port.PUPDR.ReplaceBits(gpioPullDown, gpioPullMask, pos) + case PinInputPullup: + port.MODER.ReplaceBits(gpioModeInput, gpioModeMask, pos) + port.PUPDR.ReplaceBits(gpioPullUp, gpioPullMask, pos) + case PinOutput: + port.MODER.ReplaceBits(gpioModeOutput, gpioModeMask, pos) + port.OSPEEDR.ReplaceBits(gpioOutputSpeedHigh, gpioOutputSpeedMask, pos) + + // UART + case PinModeUARTTX: + port.MODER.ReplaceBits(gpioModeAlternate, gpioModeMask, pos) + port.OSPEEDR.ReplaceBits(gpioOutputSpeedHigh, gpioOutputSpeedMask, pos) + port.PUPDR.ReplaceBits(gpioPullUp, gpioPullMask, pos) + p.SetAltFunc(altFunc) + case PinModeUARTRX: + port.MODER.ReplaceBits(gpioModeAlternate, gpioModeMask, pos) + port.PUPDR.ReplaceBits(gpioPullFloating, gpioPullMask, pos) + p.SetAltFunc(altFunc) + + // I2C + case PinModeI2CSCL: + port.MODER.ReplaceBits(gpioModeAlternate, gpioModeMask, pos) + port.OTYPER.ReplaceBits(stm32.GPIO_OTYPER_OT0_OpenDrain, stm32.GPIO_OTYPER_OT0_Msk, pos/2) + port.OSPEEDR.ReplaceBits(gpioOutputSpeedLow, gpioOutputSpeedMask, pos) + port.PUPDR.ReplaceBits(gpioPullUp, gpioPullMask, pos) + p.SetAltFunc(altFunc) + case PinModeI2CSDA: + port.MODER.ReplaceBits(gpioModeAlternate, gpioModeMask, pos) + port.OTYPER.ReplaceBits(stm32.GPIO_OTYPER_OT0_OpenDrain, stm32.GPIO_OTYPER_OT0_Msk, pos/2) + port.OSPEEDR.ReplaceBits(gpioOutputSpeedLow, gpioOutputSpeedMask, pos) + port.PUPDR.ReplaceBits(gpioPullUp, gpioPullMask, pos) + p.SetAltFunc(altFunc) + + // SPI + case PinModeSPICLK: + port.MODER.ReplaceBits(gpioModeAlternate, gpioModeMask, pos) + port.OSPEEDR.ReplaceBits(gpioOutputSpeedHigh, gpioOutputSpeedMask, pos) + port.PUPDR.ReplaceBits(gpioPullFloating, gpioPullMask, pos) + p.SetAltFunc(altFunc) + case PinModeSPISDO: + port.MODER.ReplaceBits(gpioModeAlternate, gpioModeMask, pos) + port.OSPEEDR.ReplaceBits(gpioOutputSpeedLow, gpioOutputSpeedMask, pos) + port.PUPDR.ReplaceBits(gpioPullFloating, gpioPullMask, pos) + p.SetAltFunc(altFunc) + case PinModeSPISDI: + port.MODER.ReplaceBits(gpioModeAlternate, gpioModeMask, pos) + port.OSPEEDR.ReplaceBits(gpioOutputSpeedLow, gpioOutputSpeedMask, pos) + port.PUPDR.ReplaceBits(gpioPullFloating, gpioPullMask, pos) + p.SetAltFunc(altFunc) + + // PWM + case PinModePWMOutput: + port.MODER.ReplaceBits(gpioModeAlternate, gpioModeMask, pos) + port.OSPEEDR.ReplaceBits(gpioOutputSpeedHigh, gpioOutputSpeedMask, pos) + port.PUPDR.ReplaceBits(gpioPullFloating, gpioPullMask, pos) + p.SetAltFunc(altFunc) + + // ADC + case PinInputAnalog: + port.MODER.ReplaceBits(gpioModeAnalog, gpioModeMask, pos) + port.PUPDR.ReplaceBits(gpioPullFloating, gpioPullMask, pos) + } +} + +// SetAltFunc maps the given alternative function to the I/O pin +func (p Pin) SetAltFunc(af uint8) { + port := p.getPort() + pin := uint8(p) % 16 + pos := (pin % 8) * 4 + if pin < 8 { + port.AFRL.ReplaceBits(uint32(af), 0xf, pos) + } else { + port.AFRH.ReplaceBits(uint32(af), 0xf, pos) + } +} diff --git a/emb/machine/machine_stm32_rng.go b/emb/machine/machine_stm32_rng.go new file mode 100644 index 00000000..5eaff961 --- /dev/null +++ b/emb/machine/machine_stm32_rng.go @@ -0,0 +1,35 @@ +//go:build stm32 && !(stm32f103 || stm32l0x1) + +package machine + +import "device/stm32" + +var rngInitDone = false + +const RNG_MAX_READ_RETRIES = 1000 + +// GetRNG returns 32 bits of cryptographically secure random data +func GetRNG() (uint32, error) { + if !rngInitDone { + initRNG() + rngInitDone = true + } + + if stm32.RNG.SR.HasBits(stm32.RNG_SR_CECS) { + return 0, ErrClockRNG + } + if stm32.RNG.SR.HasBits(stm32.RNG_SR_SECS) { + return 0, ErrSeedRNG + } + + cnt := RNG_MAX_READ_RETRIES + for !stm32.RNG.SR.HasBits(stm32.RNG_SR_DRDY) { + cnt-- + if cnt == 0 { + return 0, ErrTimeoutRNG + } + } + + ret := stm32.RNG.DR.Get() + return ret, nil +} diff --git a/emb/machine/machine_stm32_spi.go b/emb/machine/machine_stm32_spi.go new file mode 100644 index 00000000..3c6e0b6a --- /dev/null +++ b/emb/machine/machine_stm32_spi.go @@ -0,0 +1,147 @@ +//go:build stm32 && !stm32f7x2 && !stm32l5x2 + +package machine + +// Peripheral abstraction layer for SPI on the stm32 family + +import ( + "device/stm32" + "runtime/volatile" + "unsafe" +) + +// SPIConfig is used to store config info for SPI. +type SPIConfig struct { + Frequency uint32 + SCK Pin + SDO Pin + SDI Pin + LSBFirst bool + Mode uint8 +} + +// Configure is intended to setup the STM32 SPI1 interface. +func (spi *SPI) Configure(config SPIConfig) error { + + // -- CONFIGURING THE SPI IN MASTER MODE -- + // + // 1. Select the BR[2:0] bits to define the serial clock baud rate (see + // SPI_CR1 register). + // 2. Select the CPOL and CPHA bits to define one of the four relationships + // between the data transfer and the serial clock (see Figure 248). This + // step is not required when the TI mode is selected. + // 3. Set the DFF bit to define 8- or 16-bit data frame format + // 4. Configure the LSBFIRST bit in the SPI_CR1 register to define the frame + // format. This step is not required when the TI mode is selected. + // 5. If the NSS pin is required in input mode, in hardware mode, connect the + // NSS pin to a high-level signal during the complete byte transmit + // sequence. In NSS software mode, set the SSM and SSI bits in the SPI_CR1 + // register. If the NSS pin is required in output mode, the SSOE bit only + // should be set. This step is not required when the TI mode is selected. + // 6. Set the FRF bit in SPI_CR2 to select the TI protocol for serial + // communications. + // 7. The MSTR and SPE bits must be set (they remain set only if the NSS pin + // is connected to a high-level signal). + + // disable SPI interface before any configuration changes + spi.Bus.CR1.ClearBits(stm32.SPI_CR1_SPE) + + // enable clock for SPI + enableAltFuncClock(unsafe.Pointer(spi.Bus)) + + // init pins + if config.SCK == 0 && config.SDO == 0 && config.SDI == 0 { + config.SCK = SPI0_SCK_PIN + config.SDO = SPI0_SDO_PIN + config.SDI = SPI0_SDI_PIN + } + spi.configurePins(config) + + // Get SPI baud rate based on the bus speed it's attached to + var conf uint32 = spi.getBaudRate(config) + + // set bit transfer order + if config.LSBFirst { + conf |= stm32.SPI_CR1_LSBFIRST + } + + // set polarity and phase on the SPI interface + switch config.Mode { + case Mode1: + conf |= stm32.SPI_CR1_CPHA + case Mode2: + conf |= stm32.SPI_CR1_CPOL + case Mode3: + conf |= stm32.SPI_CR1_CPOL + conf |= stm32.SPI_CR1_CPHA + } + + // configure as SPI master + conf |= stm32.SPI_CR1_MSTR | stm32.SPI_CR1_SSI + + // enable the SPI interface + conf |= stm32.SPI_CR1_SPE + + // use software CS (GPIO) by default + conf |= stm32.SPI_CR1_SSM + + // now set the configuration + spi.Bus.CR1.Set(conf) + + // Series-specific configuration to set 8-bit transfer mode + spi.config8Bits() + + // enable SPI + spi.Bus.CR1.SetBits(stm32.SPI_CR1_SPE) + + return nil +} + +// Transfer writes/reads a single byte using the SPI interface. +func (spi *SPI) Transfer(w byte) (byte, error) { + + // 1. Enable the SPI by setting the SPE bit to 1. + // 2. Write the first data item to be transmitted into the SPI_DR register + // (this clears the TXE flag). + // 3. Wait until TXE=1 and write the second data item to be transmitted. Then + // wait until RXNE=1 and read the SPI_DR to get the first received data + // item (this clears the RXNE bit). Repeat this operation for each data + // item to be transmitted/received until the n–1 received data. + // 4. Wait until RXNE=1 and read the last received data. + // 5. Wait until TXE=1 and then wait until BSY=0 before disabling the SPI. + + // put output word (8-bit) in data register (DR), which is parallel-loaded + // into shift register, and shifted out on MOSI. Some series have 16-bit + // register but writes must be strictly 8-bit to output a byte. Writing + // 16-bits indicates a packed transfer (2 bytes). + (*volatile.Register8)(unsafe.Pointer(&spi.Bus.DR.Reg)).Set(w) + + // wait for SPI bus receive buffer not empty bit (RXNE) to be set. + // warning: blocks forever until this condition is met. + for !spi.Bus.SR.HasBits(stm32.SPI_SR_RXNE) { + } + + // copy input word (8-bit) in data register (DR), which was shifted in on MISO + // and parallel-loaded into register. + data := byte(spi.Bus.DR.Get()) + + // wait for SPI bus transmit buffer empty bit (TXE) to be set. + // warning: blocks forever until this condition is met. + for !spi.Bus.SR.HasBits(stm32.SPI_SR_TXE) { + } + + // wait for SPI bus busy bit (BSY) to be clear to indicate synchronous + // transfer complete. this will effectively prevent this Transfer() function + // from being capable of maintaining high-bandwidth communication throughput, + // but it will help guarantee stability on the bus. + for spi.Bus.SR.HasBits(stm32.SPI_SR_BSY) { + } + + // clear the overrun flag (only in full-duplex mode) + if !spi.Bus.CR1.HasBits(stm32.SPI_CR1_RXONLY | stm32.SPI_CR1_BIDIMODE | stm32.SPI_CR1_BIDIOE) { + spi.Bus.SR.Get() + } + + // Return received data from SPI data register + return data, nil +} diff --git a/emb/machine/machine_stm32_tim.go b/emb/machine/machine_stm32_tim.go new file mode 100644 index 00000000..4898ed9f --- /dev/null +++ b/emb/machine/machine_stm32_tim.go @@ -0,0 +1,340 @@ +//go:build stm32 + +package machine + +// The type alias `arrtype` should be defined to either uint32 or uint16 +// depending on the size of that register in the MCU's TIM_Type structure. + +import ( + "device/stm32" + "runtime/interrupt" + "runtime/volatile" +) + +const PWM_MODE1 = 0x6 + +type TimerCallback func() +type ChannelCallback func(channel uint8) + +type PinFunction struct { + Pin Pin + AltFunc uint8 +} + +type TimerChannel struct { + Pins []PinFunction +} + +type TIM struct { + EnableRegister *volatile.Register32 + EnableFlag uint32 + Device *stm32.TIM_Type + Channels [4]TimerChannel + UpInterrupt interrupt.Interrupt + OCInterrupt interrupt.Interrupt + + wraparoundCallback TimerCallback + channelCallbacks [4]ChannelCallback + + busFreq uint64 +} + +// Configure enables and configures this PWM. +func (t *TIM) Configure(config PWMConfig) error { + // Enable device + t.EnableRegister.SetBits(t.EnableFlag) + + err := t.setPeriod(config.Period, true) + if err != nil { + return err + } + + // Auto-repeat + t.Device.EGR.SetBits(stm32.TIM_EGR_UG) + + // Enable the timer + t.Device.CR1.SetBits(stm32.TIM_CR1_CEN | stm32.TIM_CR1_ARPE) + + return nil +} + +func (t *TIM) Count() uint32 { + return uint32(t.Device.CNT.Get()) +} + +// SetWraparoundInterrupt configures a callback to be called each +// time the timer 'wraps-around'. +// +// For example, if `Configure(PWMConfig{Period:1000000})` is used, +// to set the timer period to 1ms, this callback will be called every +// 1ms. +func (t *TIM) SetWraparoundInterrupt(callback TimerCallback) error { + // Disable this interrupt to prevent race conditions + //t.UpInterrupt.Disable() + + // Ensure the interrupt handler for Update events is registered + t.UpInterrupt = t.registerUPInterrupt() + + // Clear update flag + t.Device.SR.ClearBits(stm32.TIM_SR_UIF) + + t.wraparoundCallback = callback + t.UpInterrupt.SetPriority(0xc1) + t.UpInterrupt.Enable() + + // Enable the hardware interrupt + t.Device.DIER.SetBits(stm32.TIM_DIER_UIE) + + return nil +} + +// Sets a callback to be called when a channel reaches it's set-point. +// +// For example, if `t.Set(ch, t.Top() / 4)` is used then the callback will +// be called every quarter-period of the timer's base Period. +func (t *TIM) SetMatchInterrupt(channel uint8, callback ChannelCallback) error { + t.channelCallbacks[channel] = callback + + // Ensure the interrupt handler for Output Compare events is registered + t.OCInterrupt = t.registerOCInterrupt() + + // Clear the interrupt flag + t.Device.SR.ClearBits(stm32.TIM_SR_CC1IF << channel) + + // Enable the interrupt + t.OCInterrupt.SetPriority(0xc1) + t.OCInterrupt.Enable() + + // Enable the hardware interrupt + t.Device.DIER.SetBits(stm32.TIM_DIER_CC1IE << channel) + + return nil +} + +// SetPeriod updates the period of this PWM peripheral. +// To set a particular frequency, use the following formula: +// +// period = 1e9 / frequency +// +// If you use a period of 0, a period that works well for LEDs will be picked. +// +// SetPeriod will not change the prescaler, but also won't change the current +// value in any of the channels. This means that you may need to update the +// value for the particular channel. +// +// Note that you cannot pick any arbitrary period after the PWM peripheral has +// been configured. If you want to switch between frequencies, pick the lowest +// frequency (longest period) once when calling Configure and adjust the +// frequency here as needed. +func (t *TIM) SetPeriod(period uint64) error { + return t.setPeriod(period, false) +} + +func (t *TIM) setPeriod(period uint64, updatePrescaler bool) error { + var top uint64 + if period == 0 { + top = ARR_MAX + } else { + top = (period / 1000) * (t.busFreq / 1000) / 1000 + } + + var psc uint64 + if updatePrescaler { + if top > ARR_MAX*PSC_MAX { + return ErrPWMPeriodTooLong + } + + // Select the minimum PSC that scales the ARR value into + // range to maintain precision in ARR for changing frequencies + // later + psc = ceil(top, ARR_MAX) + top = top / psc + + t.Device.PSC.Set(uint32(psc - 1)) + } else { + psc = uint64(t.Device.PSC.Get()) + 1 + top = top / psc + + if top > ARR_MAX { + return ErrPWMPeriodTooLong + } + } + + t.Device.ARR.Set(arrtype(top - 1)) + return nil +} + +// Top returns the current counter top, for use in duty cycle calculation. It +// will only change with a call to Configure or SetPeriod, otherwise it is +// constant. +// +// The value returned here is hardware dependent. In general, it's best to treat +// it as an opaque value that can be divided by some number and passed to +// pwm.Set (see pwm.Set for more information). +func (t *TIM) Top() uint32 { + return uint32(t.Device.ARR.Get()) + 1 +} + +// Channel returns a PWM channel for the given pin. +func (t *TIM) Channel(pin Pin) (uint8, error) { + + for chi, ch := range t.Channels { + for _, p := range ch.Pins { + if p.Pin == pin { + t.configurePin(uint8(chi), p) + //p.Pin.ConfigureAltFunc(PinConfig{Mode: PinModePWMOutput}, p.AltFunc) + return uint8(chi), nil + } + } + } + + return 0, ErrInvalidOutputPin +} + +// Set updates the channel value. This is used to control the channel duty +// cycle. For example, to set it to a 25% duty cycle, use: +// +// t.Set(ch, t.Top() / 4) +// +// ch.Set(0) will set the output to low and ch.Set(ch.Top()) will set the output +// to high, assuming the output isn't inverted. +func (t *TIM) Set(channel uint8, value uint32) { + t.enableMainOutput() + + ccr := t.channelCCR(channel) + ccmr, offset := t.channelCCMR(channel) + + // Disable interrupts whilst programming to prevent spurious OC interrupts + mask := interrupt.Disable() + + // Set the PWM to Mode 1 (active below set value, inactive above) + // Preload is disabled so we can change OC value within one update period. + var ccmrVal uint32 + ccmrVal |= PWM_MODE1 << stm32.TIM_CCMR1_Output_OC1M_Pos + ccmr.ReplaceBits(ccmrVal, 0xFF, offset) + + // Set the compare value + ccr.Set(arrtype(value)) + + // Enable the channel (if not already) + t.Device.CCER.ReplaceBits(stm32.TIM_CCER_CC1E, 0xD, channel*4) + + // Force update + t.Device.EGR.SetBits(stm32.TIM_EGR_CC1G << channel) + + // Reset Interrupt Flag + t.Device.SR.ClearBits(stm32.TIM_SR_CC1IF << channel) + + // Restore interrupts + interrupt.Restore(mask) +} + +// Unset disables a channel, including any configured interrupts. +func (t *TIM) Unset(channel uint8) { + // Disable interrupts whilst programming to prevent spurious OC interrupts + mask := interrupt.Disable() + + // Disable the channel + t.Device.CCER.ReplaceBits(0, 0xD, channel*4) + + // Reset to zero value + ccr := t.channelCCR(channel) + ccr.Set(0) + + // Disable the hardware interrupt + t.Device.DIER.ClearBits(stm32.TIM_DIER_CC1IE << channel) + + // Clear the interrupt flag + t.Device.SR.ClearBits(stm32.TIM_SR_CC1IF << channel) + + // Restore interrupts + interrupt.Restore(mask) +} + +// SetInverting sets whether to invert the output of this channel. +// Without inverting, a 25% duty cycle would mean the output is high for 25% of +// the time and low for the rest. Inverting flips the output as if a NOT gate +// was placed at the output, meaning that the output would be 25% low and 75% +// high with a duty cycle of 25%. +func (t *TIM) SetInverting(channel uint8, inverting bool) { + // Enable the channel (if not already) + + var val = uint32(0) + if inverting { + val |= stm32.TIM_CCER_CC1P + } + + t.Device.CCER.ReplaceBits(val, stm32.TIM_CCER_CC1P_Msk, channel*4) +} + +func (t *TIM) handleUPInterrupt(interrupt.Interrupt) { + if t.Device.SR.HasBits(stm32.TIM_SR_UIF) { + // clear the update flag + t.Device.SR.ClearBits(stm32.TIM_SR_UIF) + + if t.wraparoundCallback != nil { + t.wraparoundCallback() + } + } +} + +func (t *TIM) handleOCInterrupt(interrupt.Interrupt) { + if t.Device.SR.HasBits(stm32.TIM_SR_CC1IF) { + if t.channelCallbacks[0] != nil { + t.channelCallbacks[0](0) + } + } + if t.Device.SR.HasBits(stm32.TIM_SR_CC2IF) { + if t.channelCallbacks[1] != nil { + t.channelCallbacks[1](1) + } + } + if t.Device.SR.HasBits(stm32.TIM_SR_CC3IF) { + if t.channelCallbacks[2] != nil { + t.channelCallbacks[2](2) + } + } + if t.Device.SR.HasBits(stm32.TIM_SR_CC4IF) { + if t.channelCallbacks[3] != nil { + t.channelCallbacks[3](3) + } + } + + // Reset interrupt flags + t.Device.SR.ClearBits(stm32.TIM_SR_CC1IF | stm32.TIM_SR_CC2IF | stm32.TIM_SR_CC3IF | stm32.TIM_SR_CC4IF) +} + +func (t *TIM) channelCCR(channel uint8) *arrRegType { + switch channel { + case 0: + return &t.Device.CCR1 + case 1: + return &t.Device.CCR2 + case 2: + return &t.Device.CCR3 + case 3: + return &t.Device.CCR4 + } + + return nil +} + +func (t *TIM) channelCCMR(channel uint8) (reg *volatile.Register32, offset uint8) { + switch channel { + case 0: + return &t.Device.CCMR1_Output, 0 + case 1: + return &t.Device.CCMR1_Output, 8 + case 2: + return &t.Device.CCMR2_Output, 0 + case 3: + return &t.Device.CCMR2_Output, 8 + } + + return nil, 0 +} + +//go:inline +func ceil(num uint64, denom uint64) uint64 { + return (num + denom - 1) / denom +} diff --git a/emb/machine/machine_stm32_tim_moder.go b/emb/machine/machine_stm32_tim_moder.go new file mode 100644 index 00000000..5cb360d9 --- /dev/null +++ b/emb/machine/machine_stm32_tim_moder.go @@ -0,0 +1,10 @@ +//go:build stm32 && !stm32f1 + +package machine + +// Configuration of a GPIO pin for PWM output for STM32 MCUs with MODER +// register (most MCUs except STM32F1 series). + +func (t *TIM) configurePin(channel uint8, pf PinFunction) { + pf.Pin.ConfigureAltFunc(PinConfig{Mode: PinModePWMOutput}, pf.AltFunc) +} diff --git a/emb/machine/machine_stm32_uart.go b/emb/machine/machine_stm32_uart.go new file mode 100644 index 00000000..6e8806c8 --- /dev/null +++ b/emb/machine/machine_stm32_uart.go @@ -0,0 +1,85 @@ +//go:build stm32 + +package machine + +// Peripheral abstraction layer for UARTs on the stm32 family. + +import ( + "device/stm32" + "runtime/interrupt" + "runtime/volatile" + "unsafe" +) + +// UART representation +type UART struct { + Buffer *RingBuffer + Bus *stm32.USART_Type + Interrupt interrupt.Interrupt + TxAltFuncSelector uint8 + RxAltFuncSelector uint8 + + // Registers specific to the chip + rxReg *volatile.Register32 + txReg *volatile.Register32 + statusReg *volatile.Register32 + txEmptyFlag uint32 +} + +// Configure the UART. +func (uart *UART) Configure(config UARTConfig) { + // Default baud rate to 115200. + if config.BaudRate == 0 { + config.BaudRate = 115200 + } + + // Set the GPIO pins to defaults if they're not set + if config.TX == 0 && config.RX == 0 { + config.TX = UART_TX_PIN + config.RX = UART_RX_PIN + } + + // STM32 families have different, but compatible, registers for + // basic UART functions. For each family populate the registers + // into `uart`. + uart.setRegisters() + + // Enable USART clock + enableAltFuncClock(unsafe.Pointer(uart.Bus)) + + uart.configurePins(config) + + // Set baud rate + uart.SetBaudRate(config.BaudRate) + + // Enable USART port, tx, rx and rx interrupts + uart.Bus.CR1.Set(stm32.USART_CR1_TE | stm32.USART_CR1_RE | stm32.USART_CR1_RXNEIE | stm32.USART_CR1_UE) + + // Enable RX IRQ + uart.Interrupt.SetPriority(0xc0) + uart.Interrupt.Enable() +} + +// handleInterrupt should be called from the appropriate interrupt handler for +// this UART instance. +func (uart *UART) handleInterrupt(interrupt.Interrupt) { + uart.Receive(byte((uart.rxReg.Get() & 0xFF))) +} + +// SetBaudRate sets the communication speed for the UART. Defer to chip-specific +// routines for calculation +func (uart *UART) SetBaudRate(br uint32) { + divider := uart.getBaudRateDivisor(br) + uart.Bus.BRR.Set(divider) +} + +// WriteByte writes a byte of data to the UART. +func (uart *UART) writeByte(c byte) error { + uart.txReg.Set(uint32(c)) + + for !uart.statusReg.HasBits(uart.txEmptyFlag) { + } + return nil +} + +func (uart *UART) flush() {} diff --git a/emb/machine/machine_stm32f103.go b/emb/machine/machine_stm32f103.go new file mode 100644 index 00000000..9e7bb347 --- /dev/null +++ b/emb/machine/machine_stm32f103.go @@ -0,0 +1,739 @@ +//go:build stm32 && stm32f103 + +package machine + +// Peripheral abstraction layer for the stm32. + +import ( + "device/stm32" + "runtime/interrupt" + "runtime/volatile" + "unsafe" +) + +func CPUFrequency() uint32 { + return 72000000 +} + +var deviceIDAddr = []uintptr{0x1FFFF7E8, 0x1FFFF7EC, 0x1FFFF7F0} + +// Internal use: configured speed of the APB1 and APB2 timers, this should be kept +// in sync with any changes to runtime package which configures the oscillators +// and clock frequencies +const APB1_TIM_FREQ = 72e6 // 72MHz +const APB2_TIM_FREQ = 72e6 // 72MHz + +const ( + PinInput PinMode = 0 // Input mode + PinOutput10MHz PinMode = 1 // Output mode, max speed 10MHz + PinOutput2MHz PinMode = 2 // Output mode, max speed 2MHz + PinOutput50MHz PinMode = 3 // Output mode, max speed 50MHz + PinOutput PinMode = PinOutput2MHz + + PinInputModeAnalog PinMode = 0 // Input analog mode + PinInputModeFloating PinMode = 4 // Input floating mode + PinInputModePullUpDown PinMode = 8 // Input pull up/down mode + PinInputModeReserved PinMode = 12 // Input mode (reserved) + + PinOutputModeGPPushPull PinMode = 0 // Output mode general purpose push/pull + PinOutputModeGPOpenDrain PinMode = 4 // Output mode general purpose open drain + PinOutputModeAltPushPull PinMode = 8 // Output mode alt. purpose push/pull + PinOutputModeAltOpenDrain PinMode = 12 // Output mode alt. purpose open drain + + // Pull-up vs Pull down is not part of the CNF0 / CNF1 bits, but is + // controlled by PxODR. Encoded using the 'spare' bit 5. + PinInputPulldown PinMode = PinInputModePullUpDown + PinInputPullup PinMode = PinInputModePullUpDown | 0x10 +) + +// Pin constants for all stm32f103 package sizes +const ( + PA0 = portA + 0 + PA1 = portA + 1 + PA2 = portA + 2 + PA3 = portA + 3 + PA4 = portA + 4 + PA5 = portA + 5 + PA6 = portA + 6 + PA7 = portA + 7 + PA8 = portA + 8 + PA9 = portA + 9 + PA10 = portA + 10 + PA11 = portA + 11 + PA12 = portA + 12 + PA13 = portA + 13 + PA14 = portA + 14 + PA15 = portA + 15 + + PB0 = portB + 0 + PB1 = portB + 1 + PB2 = portB + 2 + PB3 = portB + 3 + PB4 = portB + 4 + PB5 = portB + 5 + PB6 = portB + 6 + PB7 = portB + 7 + PB8 = portB + 8 + PB9 = portB + 9 + PB10 = portB + 10 + PB11 = portB + 11 + PB12 = portB + 12 + PB13 = portB + 13 + PB14 = portB + 14 + PB15 = portB + 15 + + PC0 = portC + 0 + PC1 = portC + 1 + PC2 = portC + 2 + PC3 = portC + 3 + PC4 = portC + 4 + PC5 = portC + 5 + PC6 = portC + 6 + PC7 = portC + 7 + PC8 = portC + 8 + PC9 = portC + 9 + PC10 = portC + 10 + PC11 = portC + 11 + PC12 = portC + 12 + PC13 = portC + 13 + PC14 = portC + 14 + PC15 = portC + 15 + + PD0 = portD + 0 + PD1 = portD + 1 + PD2 = portD + 2 + PD3 = portD + 3 + PD4 = portD + 4 + PD5 = portD + 5 + PD6 = portD + 6 + PD7 = portD + 7 + PD8 = portD + 8 + PD9 = portD + 9 + PD10 = portD + 10 + PD11 = portD + 11 + PD12 = portD + 12 + PD13 = portD + 13 + PD14 = portD + 14 + PD15 = portD + 15 + + PE0 = portE + 0 + PE1 = portE + 1 + PE2 = portE + 2 + PE3 = portE + 3 + PE4 = portE + 4 + PE5 = portE + 5 + PE6 = portE + 6 + PE7 = portE + 7 + PE8 = portE + 8 + PE9 = portE + 9 + PE10 = portE + 10 + PE11 = portE + 11 + PE12 = portE + 12 + PE13 = portE + 13 + PE14 = portE + 14 + PE15 = portE + 15 + + PF0 = portF + 0 + PF1 = portF + 1 + PF2 = portF + 2 + PF3 = portF + 3 + PF4 = portF + 4 + PF5 = portF + 5 + PF6 = portF + 6 + PF7 = portF + 7 + PF8 = portF + 8 + PF9 = portF + 9 + PF10 = portF + 10 + PF11 = portF + 11 + PF12 = portF + 12 + PF13 = portF + 13 + PF14 = portF + 14 + PF15 = portF + 15 +) + +// Configure this pin with the given I/O settings. +// stm32f1xx uses different technique for setting the GPIO pins than the stm32f407 +func (p Pin) Configure(config PinConfig) { + // Configure the GPIO pin. + p.enableClock() + port := p.getPort() + pin := uint8(p) % 16 + pos := (pin % 8) * 4 + if pin < 8 { + port.CRL.ReplaceBits(uint32(config.Mode), 0xf, pos) + } else { + port.CRH.ReplaceBits(uint32(config.Mode), 0xf, pos) + } + + // If configured for input pull-up or pull-down, set ODR + // for desired pull-up or pull-down. + if (config.Mode & 0xf) == PinInputModePullUpDown { + var pullup uint32 + if config.Mode == PinInputPullup { + pullup = 1 + } + port.ODR.ReplaceBits(pullup, 0x1, pin) + } +} + +func (p Pin) getPort() *stm32.GPIO_Type { + switch p / 16 { + case 0: + return stm32.GPIOA + case 1: + return stm32.GPIOB + case 2: + return stm32.GPIOC + case 3: + return stm32.GPIOD + case 4: + return stm32.GPIOE + case 5: + return stm32.GPIOF + case 6: + return stm32.GPIOG + default: + panic("machine: unknown port") + } +} + +// enableClock enables the clock for this desired GPIO port. +func (p Pin) enableClock() { + switch p / 16 { + case 0: + stm32.RCC.APB2ENR.SetBits(stm32.RCC_APB2ENR_IOPAEN) + case 1: + stm32.RCC.APB2ENR.SetBits(stm32.RCC_APB2ENR_IOPBEN) + case 2: + stm32.RCC.APB2ENR.SetBits(stm32.RCC_APB2ENR_IOPCEN) + case 3: + stm32.RCC.APB2ENR.SetBits(stm32.RCC_APB2ENR_IOPDEN) + case 4: + stm32.RCC.APB2ENR.SetBits(stm32.RCC_APB2ENR_IOPEEN) + case 5: + stm32.RCC.APB2ENR.SetBits(stm32.RCC_APB2ENR_IOPFEN) + case 6: + stm32.RCC.APB2ENR.SetBits(stm32.RCC_APB2ENR_IOPGEN) + default: + panic("machine: unknown port") + } +} + +// Enable peripheral clock. Expand to include all the desired peripherals +func enableAltFuncClock(bus unsafe.Pointer) { + switch bus { + case unsafe.Pointer(stm32.USART1): + stm32.RCC.APB2ENR.SetBits(stm32.RCC_APB2ENR_USART1EN) + case unsafe.Pointer(stm32.USART2): + stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_USART2EN) + case unsafe.Pointer(stm32.I2C1): + stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_I2C1EN) + case unsafe.Pointer(stm32.SPI1): + stm32.RCC.APB2ENR.SetBits(stm32.RCC_APB2ENR_SPI1EN) + case unsafe.Pointer(stm32.ADC1): + stm32.RCC.APB2ENR.SetBits(stm32.RCC_APB2ENR_ADC1EN) + default: + panic("machine: unknown peripheral") + } +} + +func (p Pin) registerInterrupt() interrupt.Interrupt { + pin := uint8(p) % 16 + + switch pin { + case 0: + return interrupt.New(stm32.IRQ_EXTI0, func(interrupt.Interrupt) { handlePinInterrupt(0) }) + case 1: + return interrupt.New(stm32.IRQ_EXTI1, func(interrupt.Interrupt) { handlePinInterrupt(1) }) + case 2: + return interrupt.New(stm32.IRQ_EXTI2, func(interrupt.Interrupt) { handlePinInterrupt(2) }) + case 3: + return interrupt.New(stm32.IRQ_EXTI3, func(interrupt.Interrupt) { handlePinInterrupt(3) }) + case 4: + return interrupt.New(stm32.IRQ_EXTI4, func(interrupt.Interrupt) { handlePinInterrupt(4) }) + case 5: + return interrupt.New(stm32.IRQ_EXTI9_5, func(interrupt.Interrupt) { handlePinInterrupt(5) }) + case 6: + return interrupt.New(stm32.IRQ_EXTI9_5, func(interrupt.Interrupt) { handlePinInterrupt(6) }) + case 7: + return interrupt.New(stm32.IRQ_EXTI9_5, func(interrupt.Interrupt) { handlePinInterrupt(7) }) + case 8: + return interrupt.New(stm32.IRQ_EXTI9_5, func(interrupt.Interrupt) { handlePinInterrupt(8) }) + case 9: + return interrupt.New(stm32.IRQ_EXTI9_5, func(interrupt.Interrupt) { handlePinInterrupt(9) }) + case 10: + return interrupt.New(stm32.IRQ_EXTI15_10, func(interrupt.Interrupt) { handlePinInterrupt(10) }) + case 11: + return interrupt.New(stm32.IRQ_EXTI15_10, func(interrupt.Interrupt) { handlePinInterrupt(11) }) + case 12: + return interrupt.New(stm32.IRQ_EXTI15_10, func(interrupt.Interrupt) { handlePinInterrupt(12) }) + case 13: + return interrupt.New(stm32.IRQ_EXTI15_10, func(interrupt.Interrupt) { handlePinInterrupt(13) }) + case 14: + return interrupt.New(stm32.IRQ_EXTI15_10, func(interrupt.Interrupt) { handlePinInterrupt(14) }) + case 15: + return interrupt.New(stm32.IRQ_EXTI15_10, func(interrupt.Interrupt) { handlePinInterrupt(15) }) + } + + return interrupt.Interrupt{} +} + +//---------- UART related code + +// Configure the TX and RX pins +func (uart *UART) configurePins(config UARTConfig) { + + // pins + switch config.TX { + case UART_ALT_TX_PIN: + // use alternate TX/RX pins via AFIO mapping + stm32.RCC.APB2ENR.SetBits(stm32.RCC_APB2ENR_AFIOEN) + if uart.Bus == stm32.USART1 { + stm32.AFIO.MAPR.SetBits(stm32.AFIO_MAPR_USART1_REMAP) + } else if uart.Bus == stm32.USART2 { + stm32.AFIO.MAPR.SetBits(stm32.AFIO_MAPR_USART2_REMAP) + } + default: + // use standard TX/RX pins PA9 and PA10 + } + config.TX.Configure(PinConfig{Mode: PinOutput50MHz + PinOutputModeAltPushPull}) + config.RX.Configure(PinConfig{Mode: PinInputModeFloating}) +} + +// Determine the divisor for USARTs to get the given baudrate +func (uart *UART) getBaudRateDivisor(br uint32) uint32 { + + // Note: PCLK2 (from APB2) used for USART1 and PCLK1 for USART2, 3, 4, 5 + var divider uint32 + if uart.Bus == stm32.USART1 { + // first divide by PCLK2 prescaler (div 1) and then desired baudrate + divider = CPUFrequency() / br + } else { + // first divide by PCLK1 prescaler (div 2) and then desired baudrate + divider = CPUFrequency() / 2 / br + } + return divider +} + +// Register names vary by ST processor, these are for STM F103xx +func (uart *UART) setRegisters() { + uart.rxReg = &uart.Bus.DR + uart.txReg = &uart.Bus.DR + uart.statusReg = &uart.Bus.SR + uart.txEmptyFlag = stm32.USART_SR_TXE +} + +//---------- SPI related types and code + +type SPI struct { + Bus *stm32.SPI_Type +} + +// There are 3 SPI interfaces on the STM32F103xx. +// Since the first interface is named SPI1, both SPI0 and SPI1 refer to SPI1. +// TODO: implement SPI2 and SPI3. +var ( + SPI1 = &SPI{Bus: stm32.SPI1} + SPI0 = SPI1 +) + +func (spi *SPI) config8Bits() { + // no-op on this series +} + +// Set baud rate for SPI +func (spi *SPI) getBaudRate(config SPIConfig) uint32 { + var conf uint32 + + // set frequency dependent on PCLK2 prescaler (div 1) + switch { + case config.Frequency < 125000: + // Note: impossible to achieve lower frequency with current PCLK2! + conf |= stm32.SPI_CR1_BR_Div256 + case config.Frequency < 250000: + conf |= stm32.SPI_CR1_BR_Div256 + case config.Frequency < 500000: + conf |= stm32.SPI_CR1_BR_Div128 + case config.Frequency < 1000000: + conf |= stm32.SPI_CR1_BR_Div64 + case config.Frequency < 2000000: + conf |= stm32.SPI_CR1_BR_Div32 + case config.Frequency < 4000000: + conf |= stm32.SPI_CR1_BR_Div16 + default: + // When its bigger than Div16, just round to the maximum frequency. + conf |= stm32.SPI_CR1_BR_Div8 + } + return conf << stm32.SPI_CR1_BR_Pos +} + +// Configure SPI pins for input output and clock +func (spi *SPI) configurePins(config SPIConfig) { + config.SCK.Configure(PinConfig{Mode: PinOutput50MHz + PinOutputModeAltPushPull}) + config.SDO.Configure(PinConfig{Mode: PinOutput50MHz + PinOutputModeAltPushPull}) + config.SDI.Configure(PinConfig{Mode: PinInputModeFloating}) +} + +//---------- I2C related types and code + +// There are 2 I2C interfaces on the STM32F103xx. +// Since the first interface is named I2C1, both I2C0 and I2C1 refer to I2C1. +// TODO: implement I2C2. + +type I2C struct { + Bus *stm32.I2C_Type +} + +var ( + I2C1 = &I2C{Bus: stm32.I2C1} + I2C0 = I2C1 +) + +func (i2c *I2C) configurePins(config I2CConfig) { + if config.SDA == PB9 { + // use alternate I2C1 pins PB8/PB9 via AFIO mapping + stm32.RCC.APB2ENR.SetBits(stm32.RCC_APB2ENR_AFIOEN) + stm32.AFIO.MAPR.SetBits(stm32.AFIO_MAPR_I2C1_REMAP) + } + + config.SDA.Configure(PinConfig{Mode: PinOutput50MHz + PinOutputModeAltOpenDrain}) + config.SCL.Configure(PinConfig{Mode: PinOutput50MHz + PinOutputModeAltOpenDrain}) +} + +func (i2c *I2C) getFreqRange(config I2CConfig) uint32 { + // pclk1 clock speed is main frequency divided by PCLK1 prescaler (div 2) + pclk1 := CPUFrequency() / 2 + + // set frequency range to PCLK1 clock speed in MHz + // aka setting the value 36 means to use 36 MHz clock + return pclk1 / 1000000 +} + +func (i2c *I2C) getRiseTime(config I2CConfig) uint32 { + // These bits must be programmed with the maximum SCL rise time given in the + // I2C bus specification, incremented by 1. + // For instance: in Sm mode, the maximum allowed SCL rise time is 1000 ns. + // If, in the I2C_CR2 register, the value of FREQ[5:0] bits is equal to 0x08 + // and PCLK1 = 125 ns, therefore the TRISE[5:0] bits must be programmed with + // 09h (1000 ns / 125 ns = 8 + 1) + freqRange := i2c.getFreqRange(config) + if config.Frequency > 100000 { + // fast mode (Fm) adjustment + freqRange *= 300 + freqRange /= 1000 + } + return (freqRange + 1) << stm32.I2C_TRISE_TRISE_Pos +} + +func (i2c *I2C) getSpeed(config I2CConfig) uint32 { + ccr := func(pclk uint32, freq uint32, coeff uint32) uint32 { + return (((pclk - 1) / (freq * coeff)) + 1) & stm32.I2C_CCR_CCR_Msk + } + sm := func(pclk uint32, freq uint32) uint32 { // standard mode (Sm) + if s := ccr(pclk, freq, 2); s < 4 { + return 4 + } else { + return s + } + } + fm := func(pclk uint32, freq uint32, duty uint8) uint32 { // fast mode (Fm) + if duty == DutyCycle2 { + return ccr(pclk, freq, 3) + } else { + return ccr(pclk, freq, 25) | stm32.I2C_CCR_DUTY + } + } + clock := CPUFrequency() / 2 + if config.Frequency <= 100000 { + return sm(clock, config.Frequency) + } else { + s := fm(clock, config.Frequency, config.DutyCycle) + if (s & stm32.I2C_CCR_CCR_Msk) == 0 { + return 1 + } else { + return s | stm32.I2C_CCR_F_S + } + } +} + +//---------- Timer related code + +// For Pin Mappings see RM0008, pg 179 +// https://www.st.com/resource/en/reference_manual/cd00171190-stm32f101xx-stm32f102xx-stm32f103xx-stm32f105xx-and-stm32f107xx-advanced-arm-based-32-bit-mcus-stmicroelectronics.pdf +// +// Note: for STM32F1 series the pin mapping is done 'per timer' not per channel, +// not all channels on a timer have the same degrees of flexibility, and some +// combinations are only available on some packages - so care is needed at app +// level to ensure valid combinations of pins are used. +// + +var ( + TIM1 = TIM{ + EnableRegister: &stm32.RCC.APB2ENR, + EnableFlag: stm32.RCC_APB2ENR_TIM1EN, + Device: stm32.TIM1, + Channels: [4]TimerChannel{ + TimerChannel{Pins: []PinFunction{{PE9, 0b11}, {PA8, 0b00}}}, + TimerChannel{Pins: []PinFunction{{PE11, 0b11}, {PA9, 0b00}}}, + TimerChannel{Pins: []PinFunction{{PE13, 0b11}, {PA10, 0b00}}}, + TimerChannel{Pins: []PinFunction{{PE14, 0b11}, {PA11, 0b00}}}, + }, + busFreq: APB2_TIM_FREQ, + } + + TIM2 = TIM{ + EnableRegister: &stm32.RCC.APB1ENR, + EnableFlag: stm32.RCC_APB1ENR_TIM2EN, + Device: stm32.TIM2, + Channels: [4]TimerChannel{ + TimerChannel{Pins: []PinFunction{{PA0, 0b00}, {PA15, 0b01}}}, + TimerChannel{Pins: []PinFunction{{PA1, 0b00}, {PB3, 0b01}}}, + TimerChannel{Pins: []PinFunction{{PA2, 0b00}, {PB10, 0b10}}}, + TimerChannel{Pins: []PinFunction{{PA3, 0b00}, {PB11, 0b10}}}, + }, + busFreq: APB1_TIM_FREQ, + } + + TIM3 = TIM{ + EnableRegister: &stm32.RCC.APB1ENR, + EnableFlag: stm32.RCC_APB1ENR_TIM3EN, + Device: stm32.TIM3, + Channels: [4]TimerChannel{ + TimerChannel{Pins: []PinFunction{{PA6, 0b00}, {PC6, 0b11}, {PB4, 0b10}}}, + TimerChannel{Pins: []PinFunction{{PA7, 0b00}, {PC7, 0b11}, {PB5, 0b10}}}, + TimerChannel{Pins: []PinFunction{{PB0, 0b00}, {PC8, 0b11}}}, + TimerChannel{Pins: []PinFunction{{PB1, 0b00}, {PC9, 0b11}}}, + }, + busFreq: APB1_TIM_FREQ, + } + + TIM4 = TIM{ + EnableRegister: &stm32.RCC.APB1ENR, + EnableFlag: stm32.RCC_APB1ENR_TIM4EN, + Device: stm32.TIM4, + Channels: [4]TimerChannel{ + TimerChannel{Pins: []PinFunction{{PD12, 0b1}, {PB6, 0}}}, + TimerChannel{Pins: []PinFunction{{PD13, 0b1}, {PB7, 0}}}, + TimerChannel{Pins: []PinFunction{{PD14, 0b1}, {PB8, 0}}}, + TimerChannel{Pins: []PinFunction{{PD15, 0b1}, {PB9, 0}}}, + }, + busFreq: APB1_TIM_FREQ, + } + + TIM5 = TIM{ + EnableRegister: &stm32.RCC.APB1ENR, + EnableFlag: stm32.RCC_APB1ENR_TIM5EN, + Device: stm32.TIM5, + Channels: [4]TimerChannel{ + TimerChannel{Pins: []PinFunction{}}, + TimerChannel{Pins: []PinFunction{}}, + TimerChannel{Pins: []PinFunction{}}, + TimerChannel{Pins: []PinFunction{{PA3, 0b0}}}, + }, + busFreq: APB1_TIM_FREQ, + } + + TIM6 = TIM{ + EnableRegister: &stm32.RCC.APB1ENR, + EnableFlag: stm32.RCC_APB1ENR_TIM6EN, + Device: stm32.TIM6, + Channels: [4]TimerChannel{ + TimerChannel{Pins: []PinFunction{}}, + TimerChannel{Pins: []PinFunction{}}, + TimerChannel{Pins: []PinFunction{}}, + TimerChannel{Pins: []PinFunction{}}, + }, + busFreq: APB1_TIM_FREQ, + } + + TIM7 = TIM{ + EnableRegister: &stm32.RCC.APB1ENR, + EnableFlag: stm32.RCC_APB1ENR_TIM7EN, + Device: stm32.TIM7, + Channels: [4]TimerChannel{ + TimerChannel{Pins: []PinFunction{}}, + TimerChannel{Pins: []PinFunction{}}, + TimerChannel{Pins: []PinFunction{}}, + TimerChannel{Pins: []PinFunction{}}, + }, + busFreq: APB1_TIM_FREQ, + } + + TIM8 = TIM{ + EnableRegister: &stm32.RCC.APB2ENR, + EnableFlag: stm32.RCC_APB2ENR_TIM8EN, + Device: stm32.TIM8, + Channels: [4]TimerChannel{ + TimerChannel{Pins: []PinFunction{}}, + TimerChannel{Pins: []PinFunction{}}, + TimerChannel{Pins: []PinFunction{}}, + TimerChannel{Pins: []PinFunction{}}, + }, + busFreq: APB2_TIM_FREQ, + } + + TIM9 = TIM{ + EnableRegister: &stm32.RCC.APB2ENR, + EnableFlag: stm32.RCC_APB2ENR_TIM9EN, + Device: stm32.TIM9, + Channels: [4]TimerChannel{ + TimerChannel{Pins: []PinFunction{{PA2, 0b0}, {PE5, 0b1}}}, + TimerChannel{Pins: []PinFunction{{PA3, 0b0}, {PE6, 0b1}}}, + TimerChannel{Pins: []PinFunction{}}, + TimerChannel{Pins: []PinFunction{}}, + }, + busFreq: APB2_TIM_FREQ, + } + + TIM10 = TIM{ + EnableRegister: &stm32.RCC.APB2ENR, + EnableFlag: stm32.RCC_APB2ENR_TIM10EN, + Device: stm32.TIM10, + Channels: [4]TimerChannel{ + TimerChannel{Pins: []PinFunction{{PB8, 0b0}, {PF6, 0b1}}}, + TimerChannel{Pins: []PinFunction{}}, + TimerChannel{Pins: []PinFunction{}}, + TimerChannel{Pins: []PinFunction{}}, + }, + busFreq: APB2_TIM_FREQ, + } + + TIM11 = TIM{ + EnableRegister: &stm32.RCC.APB2ENR, + EnableFlag: stm32.RCC_APB2ENR_TIM11EN, + Device: stm32.TIM11, + Channels: [4]TimerChannel{ + TimerChannel{Pins: []PinFunction{{PB9, 0b0}, {PF7, 0b1}}}, + TimerChannel{Pins: []PinFunction{}}, + TimerChannel{Pins: []PinFunction{}}, + TimerChannel{Pins: []PinFunction{}}, + }, + busFreq: APB2_TIM_FREQ, + } + + TIM12 = TIM{ + EnableRegister: &stm32.RCC.APB1ENR, + EnableFlag: stm32.RCC_APB1ENR_TIM12EN, + Device: stm32.TIM12, + Channels: [4]TimerChannel{ + TimerChannel{Pins: []PinFunction{{}}}, + TimerChannel{Pins: []PinFunction{}}, + TimerChannel{Pins: []PinFunction{}}, + TimerChannel{Pins: []PinFunction{}}, + }, + busFreq: APB1_TIM_FREQ, + } + + TIM13 = TIM{ + EnableRegister: &stm32.RCC.APB1ENR, + EnableFlag: stm32.RCC_APB1ENR_TIM13EN, + Device: stm32.TIM13, + Channels: [4]TimerChannel{ + TimerChannel{Pins: []PinFunction{{PA6, 0b0}, {PF8, 0b1}}}, + TimerChannel{Pins: []PinFunction{}}, + TimerChannel{Pins: []PinFunction{}}, + TimerChannel{Pins: []PinFunction{}}, + }, + busFreq: APB1_TIM_FREQ, + } + + TIM14 = TIM{ + EnableRegister: &stm32.RCC.APB1ENR, + EnableFlag: stm32.RCC_APB1ENR_TIM14EN, + Device: stm32.TIM14, + Channels: [4]TimerChannel{ + TimerChannel{Pins: []PinFunction{{PA7, 0b0}, {PF9, 0b1}}}, + TimerChannel{Pins: []PinFunction{}}, + TimerChannel{Pins: []PinFunction{}}, + TimerChannel{Pins: []PinFunction{}}, + }, + busFreq: APB1_TIM_FREQ, + } +) + +func (t *TIM) registerUPInterrupt() interrupt.Interrupt { + switch t { + case &TIM1: + return interrupt.New(stm32.IRQ_TIM1_UP, TIM1.handleUPInterrupt) + case &TIM2: + return interrupt.New(stm32.IRQ_TIM2, TIM2.handleUPInterrupt) + case &TIM3: + return interrupt.New(stm32.IRQ_TIM3, TIM3.handleUPInterrupt) + case &TIM4: + return interrupt.New(stm32.IRQ_TIM4, TIM4.handleUPInterrupt) + case &TIM5: + return interrupt.New(stm32.IRQ_TIM5, TIM5.handleUPInterrupt) + case &TIM6: + return interrupt.New(stm32.IRQ_TIM6, TIM6.handleUPInterrupt) + case &TIM7: + return interrupt.New(stm32.IRQ_TIM7, TIM7.handleUPInterrupt) + case &TIM8: + return interrupt.New(stm32.IRQ_TIM8_UP, TIM8.handleUPInterrupt) + } + + return interrupt.Interrupt{} +} + +func (t *TIM) registerOCInterrupt() interrupt.Interrupt { + switch t { + case &TIM1: + return interrupt.New(stm32.IRQ_TIM1_CC, TIM1.handleOCInterrupt) + case &TIM2: + return interrupt.New(stm32.IRQ_TIM2, TIM2.handleOCInterrupt) + case &TIM3: + return interrupt.New(stm32.IRQ_TIM3, TIM3.handleOCInterrupt) + case &TIM4: + return interrupt.New(stm32.IRQ_TIM4, TIM4.handleOCInterrupt) + case &TIM5: + return interrupt.New(stm32.IRQ_TIM5, TIM5.handleOCInterrupt) + case &TIM6: + return interrupt.New(stm32.IRQ_TIM6, TIM6.handleOCInterrupt) + case &TIM7: + return interrupt.New(stm32.IRQ_TIM7, TIM7.handleOCInterrupt) + case &TIM8: + return interrupt.New(stm32.IRQ_TIM8_CC, TIM8.handleOCInterrupt) + } + + return interrupt.Interrupt{} +} + +func (t *TIM) configurePin(channel uint8, pf PinFunction) { + remap := uint32(pf.AltFunc) + + switch t { + case &TIM1: + stm32.AFIO.MAPR.ReplaceBits(remap< clock { + freq = clock + } + + // calculate the exact clock divisor (freq=clock/div -> div=clock/freq). + // truncation is fine, since it produces a less-than-or-equal divisor, and + // thus a greater-than-or-equal frequency. + // divisors only come in consecutive powers of 2, so we can use log2 (or, + // equivalently, bits.Len - 1) to convert to respective enum value. + div := bits.Len32(clock/freq) - 1 + + // but DIV1 (2^0) is not permitted, as the least divisor is DIV2 (2^1), so + // subtract 1 from the log2 value, keeping a lower bound of 0 + if div < 0 { + div = 0 + } else if div > 0 { + div-- + } + + // finally, shift the enumerated value into position for SPI CR1 + return uint32(div) << stm32.SPI_CR1_BR_Pos +} + +// -- I2C ---------------------------------------------------------------------- + +type I2C struct { + Bus *stm32.I2C_Type + AltFuncSelector uint8 +} + +func (i2c *I2C) configurePins(config I2CConfig) { + config.SCL.ConfigureAltFunc(PinConfig{Mode: PinModeI2CSCL}, i2c.AltFuncSelector) + config.SDA.ConfigureAltFunc(PinConfig{Mode: PinModeI2CSDA}, i2c.AltFuncSelector) +} + +func (i2c *I2C) getFreqRange(config I2CConfig) uint32 { + // all I2C interfaces are on APB1 + clock := CPUFrequency() / 4 + // convert to MHz + clock /= 1000000 + // must be between 2 MHz (or 4 MHz for fast mode (Fm)) and 50 MHz, inclusive + var min, max uint32 = 2, 50 + if config.Frequency > 100000 { + min = 4 // fast mode (Fm) + } + if clock < min { + clock = min + } else if clock > max { + clock = max + } + return clock << stm32.I2C_CR2_FREQ_Pos +} + +func (i2c *I2C) getRiseTime(config I2CConfig) uint32 { + // These bits must be programmed with the maximum SCL rise time given in the + // I2C bus specification, incremented by 1. + // For instance: in Sm mode, the maximum allowed SCL rise time is 1000 ns. + // If, in the I2C_CR2 register, the value of FREQ[5:0] bits is equal to 0x08 + // and PCLK1 = 125 ns, therefore the TRISE[5:0] bits must be programmed with + // 09h (1000 ns / 125 ns = 8 + 1) + freqRange := i2c.getFreqRange(config) + if config.Frequency > 100000 { + // fast mode (Fm) adjustment + freqRange *= 300 + freqRange /= 1000 + } + return (freqRange + 1) << stm32.I2C_TRISE_TRISE_Pos +} + +func (i2c *I2C) getSpeed(config I2CConfig) uint32 { + ccr := func(pclk uint32, freq uint32, coeff uint32) uint32 { + return (((pclk - 1) / (freq * coeff)) + 1) & stm32.I2C_CCR_CCR_Msk + } + sm := func(pclk uint32, freq uint32) uint32 { // standard mode (Sm) + if s := ccr(pclk, freq, 2); s < 4 { + return 4 + } else { + return s + } + } + fm := func(pclk uint32, freq uint32, duty uint8) uint32 { // fast mode (Fm) + if duty == DutyCycle2 { + return ccr(pclk, freq, 3) + } else { + return ccr(pclk, freq, 25) | stm32.I2C_CCR_DUTY + } + } + // all I2C interfaces are on APB1 + clock := CPUFrequency() / 4 + if config.Frequency <= 100000 { + return sm(clock, config.Frequency) + } else { + s := fm(clock, config.Frequency, config.DutyCycle) + if (s & stm32.I2C_CCR_CCR_Msk) == 0 { + return 1 + } else { + return s | stm32.I2C_CCR_F_S + } + } +} + +//---------- Flash related code + +// the block size actually depends on the sector. +// TODO: handle this correctly for sectors > 3 +const eraseBlockSizeValue = 16384 + +// see RM0090 page 75 +func sectorNumber(address uintptr) uint32 { + switch { + // 0x0800 0000 - 0x0800 3FFF + case address >= 0x08000000 && address <= 0x08003FFF: + return 0 + // 0x0800 4000 - 0x0800 7FFF + case address >= 0x08004000 && address <= 0x08007FFF: + return 1 + // 0x0800 8000 - 0x0800 BFFF + case address >= 0x08008000 && address <= 0x0800BFFF: + return 2 + // 0x0800 C000 - 0x0800 FFFF + case address >= 0x0800C000 && address <= 0x0800FFFF: + return 3 + // 0x0801 0000 - 0x0801 FFFF + case address >= 0x08010000 && address <= 0x0801FFFF: + return 4 + // 0x0802 0000 - 0x0803 FFFF + case address >= 0x08020000 && address <= 0x0803FFFF: + return 5 + // 0x0804 0000 - 0x0805 FFFF + case address >= 0x08040000 && address <= 0x0805FFFF: + return 6 + case address >= 0x08060000 && address <= 0x0807FFFF: + return 7 + case address >= 0x08080000 && address <= 0x0809FFFF: + return 8 + case address >= 0x080A0000 && address <= 0x080BFFFF: + return 9 + case address >= 0x080C0000 && address <= 0x080DFFFF: + return 10 + case address >= 0x080E0000 && address <= 0x080FFFFF: + return 11 + default: + return 0 + } +} + +// calculate sector number from address +// var sector uint32 = sectorNumber(address) + +// see RM0090 page 85 +// eraseBlock at the passed in block number +func eraseBlock(block uint32) error { + waitUntilFlashDone() + + // clear any previous errors + stm32.FLASH.SR.SetBits(0xF0) + + // set SER bit + stm32.FLASH.SetCR_SER(1) + defer stm32.FLASH.SetCR_SER(0) + + // set the block (aka sector) to be erased + stm32.FLASH.SetCR_SNB(block) + defer stm32.FLASH.SetCR_SNB(0) + + // start the page erase + stm32.FLASH.SetCR_STRT(1) + + waitUntilFlashDone() + + if err := checkError(); err != nil { + return err + } + + return nil +} + +const writeBlockSize = 2 + +// see RM0090 page 86 +// must write data in word-length +func writeFlashData(address uintptr, data []byte) (int, error) { + if len(data)%writeBlockSize != 0 { + return 0, errFlashInvalidWriteLength + } + + waitUntilFlashDone() + + // clear any previous errors + stm32.FLASH.SR.SetBits(0xF0) + + // set parallelism to x32 + stm32.FLASH.SetCR_PSIZE(2) + + for i := 0; i < len(data); i += writeBlockSize { + // start write operation + stm32.FLASH.SetCR_PG(1) + + *(*uint16)(unsafe.Pointer(address)) = binary.LittleEndian.Uint16(data[i : i+writeBlockSize]) + + waitUntilFlashDone() + + if err := checkError(); err != nil { + return i, err + } + + // end write operation + stm32.FLASH.SetCR_PG(0) + } + + return len(data), nil +} + +func waitUntilFlashDone() { + for stm32.FLASH.GetSR_BSY() != 0 { + } +} + +var ( + errFlashPGS = errors.New("errFlashPGS") + errFlashPGP = errors.New("errFlashPGP") + errFlashPGA = errors.New("errFlashPGA") + errFlashWRP = errors.New("errFlashWRP") +) + +func checkError() error { + switch { + case stm32.FLASH.GetSR_PGSERR() != 0: + return errFlashPGS + case stm32.FLASH.GetSR_PGPERR() != 0: + return errFlashPGP + case stm32.FLASH.GetSR_PGAERR() != 0: + return errFlashPGA + case stm32.FLASH.GetSR_WRPERR() != 0: + return errFlashWRP + } + + return nil +} diff --git a/emb/machine/machine_stm32f40x.go b/emb/machine/machine_stm32f40x.go new file mode 100644 index 00000000..953c7fab --- /dev/null +++ b/emb/machine/machine_stm32f40x.go @@ -0,0 +1,13 @@ +//go:build stm32f4 && (stm32f405 || stm32f407) + +package machine + +func CPUFrequency() uint32 { + return 168000000 +} + +// Internal use: configured speed of the APB1 and APB2 timers, this should be kept +// in sync with any changes to runtime package which configures the oscillators +// and clock frequencies +const APB1_TIM_FREQ = 42000000 * 2 +const APB2_TIM_FREQ = 84000000 * 2 diff --git a/emb/machine/machine_stm32f469.go b/emb/machine/machine_stm32f469.go new file mode 100644 index 00000000..dfda91fc --- /dev/null +++ b/emb/machine/machine_stm32f469.go @@ -0,0 +1,13 @@ +//go:build stm32f4 && stm32f469 + +package machine + +func CPUFrequency() uint32 { + return 180000000 +} + +// Internal use: configured speed of the APB1 and APB2 timers, this should be kept +// in sync with any changes to runtime package which configures the oscillators +// and clock frequencies +const APB1_TIM_FREQ = 45000000 * 2 +const APB2_TIM_FREQ = 90000000 * 2 diff --git a/emb/machine/machine_stm32f7.go b/emb/machine/machine_stm32f7.go new file mode 100644 index 00000000..11eff110 --- /dev/null +++ b/emb/machine/machine_stm32f7.go @@ -0,0 +1,725 @@ +//go:build stm32f7 + +package machine + +// Peripheral abstraction layer for the stm32f4 + +import ( + "device/stm32" + "runtime/interrupt" + "runtime/volatile" + "unsafe" +) + +var deviceIDAddr = []uintptr{0x1FF0F420, 0x1FF0F424, 0x1FF0F428} + +// Alternative peripheral pin functions +const ( + AF0_SYSTEM = 0 + AF1_TIM1_2 = 1 + AF2_TIM3_4_5 = 2 + AF3_TIM8_9_10_11_LPTIM1 = 3 + AF4_I2C1_2_3_USART1 = 4 + AF5_SPI1_2_3_4_5_I2S1_2_3 = 5 + AF6_SPI2_3_I2S2_3_SAI1_UART4 = 6 + AF7_SPI2_3_I2S2_3_USART1_2_3_UART5 = 7 + AF8_SAI2_USART6_UART4_5_7_8_OTG1_FS = 8 + AF9_CAN1_TIM12_13_14_QUADSPI_FMC_OTG2_HS = 9 + AF10_SAI2_QUADSPI_SDMMC2_OTG2_HS_OTG1_FS = 10 + AF11_SDMMC2 = 11 + AF12_UART7_FMC_SDMMC1_OTG2_FS = 12 + AF15_EVENTOUT = 15 +) + +const ( + PA0 = portA + 0 + PA1 = portA + 1 + PA2 = portA + 2 + PA3 = portA + 3 + PA4 = portA + 4 + PA5 = portA + 5 + PA6 = portA + 6 + PA7 = portA + 7 + PA8 = portA + 8 + PA9 = portA + 9 + PA10 = portA + 10 + PA11 = portA + 11 + PA12 = portA + 12 + PA13 = portA + 13 + PA14 = portA + 14 + PA15 = portA + 15 + + PB0 = portB + 0 + PB1 = portB + 1 + PB2 = portB + 2 + PB3 = portB + 3 + PB4 = portB + 4 + PB5 = portB + 5 + PB6 = portB + 6 + PB7 = portB + 7 + PB8 = portB + 8 + PB9 = portB + 9 + PB10 = portB + 10 + PB11 = portB + 11 + PB12 = portB + 12 + PB13 = portB + 13 + PB14 = portB + 14 + PB15 = portB + 15 + + PC0 = portC + 0 + PC1 = portC + 1 + PC2 = portC + 2 + PC3 = portC + 3 + PC4 = portC + 4 + PC5 = portC + 5 + PC6 = portC + 6 + PC7 = portC + 7 + PC8 = portC + 8 + PC9 = portC + 9 + PC10 = portC + 10 + PC11 = portC + 11 + PC12 = portC + 12 + PC13 = portC + 13 + PC14 = portC + 14 + PC15 = portC + 15 + + PD0 = portD + 0 + PD1 = portD + 1 + PD2 = portD + 2 + PD3 = portD + 3 + PD4 = portD + 4 + PD5 = portD + 5 + PD6 = portD + 6 + PD7 = portD + 7 + PD8 = portD + 8 + PD9 = portD + 9 + PD10 = portD + 10 + PD11 = portD + 11 + PD12 = portD + 12 + PD13 = portD + 13 + PD14 = portD + 14 + PD15 = portD + 15 + + PE0 = portE + 0 + PE1 = portE + 1 + PE2 = portE + 2 + PE3 = portE + 3 + PE4 = portE + 4 + PE5 = portE + 5 + PE6 = portE + 6 + PE7 = portE + 7 + PE8 = portE + 8 + PE9 = portE + 9 + PE10 = portE + 10 + PE11 = portE + 11 + PE12 = portE + 12 + PE13 = portE + 13 + PE14 = portE + 14 + PE15 = portE + 15 + + PF0 = portF + 0 + PF1 = portF + 1 + PF2 = portF + 2 + PF3 = portF + 3 + PF4 = portF + 4 + PF5 = portF + 5 + PF6 = portF + 6 + PF7 = portF + 7 + PF8 = portF + 8 + PF9 = portF + 9 + PF10 = portF + 10 + PF11 = portF + 11 + PF12 = portF + 12 + PF13 = portF + 13 + PF14 = portF + 14 + PF15 = portF + 15 + + PG0 = portG + 0 + PG1 = portG + 1 + PG2 = portG + 2 + PG3 = portG + 3 + PG4 = portG + 4 + PG5 = portG + 5 + PG6 = portG + 6 + PG7 = portG + 7 + PG8 = portG + 8 + PG9 = portG + 9 + PG10 = portG + 10 + PG11 = portG + 11 + PG12 = portG + 12 + PG13 = portG + 13 + PG14 = portG + 14 + PG15 = portG + 15 + + PH0 = portH + 0 + PH1 = portH + 1 + PH2 = portH + 2 + PH3 = portH + 3 + PH4 = portH + 4 + PH5 = portH + 5 + PH6 = portH + 6 + PH7 = portH + 7 + PH8 = portH + 8 + PH9 = portH + 9 + PH10 = portH + 10 + PH11 = portH + 11 + PH12 = portH + 12 + PH13 = portH + 13 + PH14 = portH + 14 + PH15 = portH + 15 + + PI0 = portI + 0 + PI1 = portI + 1 + PI2 = portI + 2 + PI3 = portI + 3 + PI4 = portI + 4 + PI5 = portI + 5 + PI6 = portI + 6 + PI7 = portI + 7 + PI8 = portI + 8 + PI9 = portI + 9 + PI10 = portI + 10 + PI11 = portI + 11 + PI12 = portI + 12 + PI13 = portI + 13 + PI14 = portI + 14 + PI15 = portI + 15 +) + +func (p Pin) getPort() *stm32.GPIO_Type { + switch p / 16 { + case 0: + return stm32.GPIOA + case 1: + return stm32.GPIOB + case 2: + return stm32.GPIOC + case 3: + return stm32.GPIOD + case 4: + return stm32.GPIOE + case 5: + return stm32.GPIOF + case 6: + return stm32.GPIOG + case 7: + return stm32.GPIOH + case 8: + return stm32.GPIOI + default: + panic("machine: unknown port") + } +} + +// enableClock enables the clock for this desired GPIO port. +func (p Pin) enableClock() { + switch p / 16 { + case 0: + stm32.RCC.AHB1ENR.SetBits(stm32.RCC_AHB1ENR_GPIOAEN) + case 1: + stm32.RCC.AHB1ENR.SetBits(stm32.RCC_AHB1ENR_GPIOBEN) + case 2: + stm32.RCC.AHB1ENR.SetBits(stm32.RCC_AHB1ENR_GPIOCEN) + case 3: + stm32.RCC.AHB1ENR.SetBits(stm32.RCC_AHB1ENR_GPIODEN) + case 4: + stm32.RCC.AHB1ENR.SetBits(stm32.RCC_AHB1ENR_GPIOEEN) + case 5: + stm32.RCC.AHB1ENR.SetBits(stm32.RCC_AHB1ENR_GPIOFEN) + case 6: + stm32.RCC.AHB1ENR.SetBits(stm32.RCC_AHB1ENR_GPIOGEN) + case 7: + stm32.RCC.AHB1ENR.SetBits(stm32.RCC_AHB1ENR_GPIOHEN) + case 8: + stm32.RCC.AHB1ENR.SetBits(stm32.RCC_AHB1ENR_GPIOIEN) + default: + panic("machine: unknown port") + } +} + +// Enable peripheral clock +func enableAltFuncClock(bus unsafe.Pointer) { + switch bus { + case unsafe.Pointer(stm32.DAC): // DAC interface clock enable + stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_DACEN) + case unsafe.Pointer(stm32.PWR): // Power interface clock enable + stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_PWREN) + case unsafe.Pointer(stm32.CAN1): // CAN 1 clock enable + stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_CAN1EN) + case unsafe.Pointer(stm32.I2C3): // I2C3 clock enable + stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_I2C3EN) + case unsafe.Pointer(stm32.I2C2): // I2C2 clock enable + stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_I2C2EN) + case unsafe.Pointer(stm32.I2C1): // I2C1 clock enable + stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_I2C1EN) + case unsafe.Pointer(stm32.UART5): // UART5 clock enable + stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_UART5EN) + case unsafe.Pointer(stm32.UART4): // UART4 clock enable + stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_UART4EN) + case unsafe.Pointer(stm32.USART3): // USART3 clock enable + stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_USART3EN) + case unsafe.Pointer(stm32.USART2): // USART2 clock enable + stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_USART2EN) + case unsafe.Pointer(stm32.SPI3): // SPI3 clock enable + stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_SPI3EN) + case unsafe.Pointer(stm32.SPI2): // SPI2 clock enable + stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_SPI2EN) + case unsafe.Pointer(stm32.WWDG): // Window watchdog clock enable + stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_WWDGEN) + case unsafe.Pointer(stm32.TIM14): // TIM14 clock enable + stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_TIM14EN) + case unsafe.Pointer(stm32.TIM13): // TIM13 clock enable + stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_TIM13EN) + case unsafe.Pointer(stm32.TIM12): // TIM12 clock enable + stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_TIM12EN) + case unsafe.Pointer(stm32.TIM7): // TIM7 clock enable + stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_TIM7EN) + case unsafe.Pointer(stm32.TIM6): // TIM6 clock enable + stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_TIM6EN) + case unsafe.Pointer(stm32.TIM5): // TIM5 clock enable + stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_TIM5EN) + case unsafe.Pointer(stm32.TIM4): // TIM4 clock enable + stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_TIM4EN) + case unsafe.Pointer(stm32.TIM3): // TIM3 clock enable + stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_TIM3EN) + case unsafe.Pointer(stm32.TIM2): // TIM2 clock enable + stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_TIM2EN) + case unsafe.Pointer(stm32.TIM11): // TIM11 clock enable + stm32.RCC.APB2ENR.SetBits(stm32.RCC_APB2ENR_TIM11EN) + case unsafe.Pointer(stm32.TIM10): // TIM10 clock enable + stm32.RCC.APB2ENR.SetBits(stm32.RCC_APB2ENR_TIM10EN) + case unsafe.Pointer(stm32.TIM9): // TIM9 clock enable + stm32.RCC.APB2ENR.SetBits(stm32.RCC_APB2ENR_TIM9EN) + case unsafe.Pointer(stm32.SYSCFG): // System configuration controller clock enable + stm32.RCC.APB2ENR.SetBits(stm32.RCC_APB2ENR_SYSCFGEN) + case unsafe.Pointer(stm32.SPI1): // SPI1 clock enable + stm32.RCC.APB2ENR.SetBits(stm32.RCC_APB2ENR_SPI1EN) + case unsafe.Pointer(stm32.ADC3): // ADC3 clock enable + stm32.RCC.APB2ENR.SetBits(stm32.RCC_APB2ENR_ADC3EN) + case unsafe.Pointer(stm32.ADC2): // ADC2 clock enable + stm32.RCC.APB2ENR.SetBits(stm32.RCC_APB2ENR_ADC2EN) + case unsafe.Pointer(stm32.ADC1): // ADC1 clock enable + stm32.RCC.APB2ENR.SetBits(stm32.RCC_APB2ENR_ADC1EN) + case unsafe.Pointer(stm32.USART6): // USART6 clock enable + stm32.RCC.APB2ENR.SetBits(stm32.RCC_APB2ENR_USART6EN) + case unsafe.Pointer(stm32.USART1): // USART1 clock enable + stm32.RCC.APB2ENR.SetBits(stm32.RCC_APB2ENR_USART1EN) + case unsafe.Pointer(stm32.TIM8): // TIM8 clock enable + stm32.RCC.APB2ENR.SetBits(stm32.RCC_APB2ENR_TIM8EN) + case unsafe.Pointer(stm32.TIM1): // TIM1 clock enable + stm32.RCC.APB2ENR.SetBits(stm32.RCC_APB2ENR_TIM1EN) + } +} + +func (p Pin) registerInterrupt() interrupt.Interrupt { + pin := uint8(p) % 16 + + switch pin { + case 0: + return interrupt.New(stm32.IRQ_EXTI0, func(interrupt.Interrupt) { handlePinInterrupt(0) }) + case 1: + return interrupt.New(stm32.IRQ_EXTI1, func(interrupt.Interrupt) { handlePinInterrupt(1) }) + case 2: + return interrupt.New(stm32.IRQ_EXTI2, func(interrupt.Interrupt) { handlePinInterrupt(2) }) + case 3: + return interrupt.New(stm32.IRQ_EXTI3, func(interrupt.Interrupt) { handlePinInterrupt(3) }) + case 4: + return interrupt.New(stm32.IRQ_EXTI4, func(interrupt.Interrupt) { handlePinInterrupt(4) }) + case 5: + return interrupt.New(stm32.IRQ_EXTI9_5, func(interrupt.Interrupt) { handlePinInterrupt(5) }) + case 6: + return interrupt.New(stm32.IRQ_EXTI9_5, func(interrupt.Interrupt) { handlePinInterrupt(6) }) + case 7: + return interrupt.New(stm32.IRQ_EXTI9_5, func(interrupt.Interrupt) { handlePinInterrupt(7) }) + case 8: + return interrupt.New(stm32.IRQ_EXTI9_5, func(interrupt.Interrupt) { handlePinInterrupt(8) }) + case 9: + return interrupt.New(stm32.IRQ_EXTI9_5, func(interrupt.Interrupt) { handlePinInterrupt(9) }) + case 10: + return interrupt.New(stm32.IRQ_EXTI15_10, func(interrupt.Interrupt) { handlePinInterrupt(10) }) + case 11: + return interrupt.New(stm32.IRQ_EXTI15_10, func(interrupt.Interrupt) { handlePinInterrupt(11) }) + case 12: + return interrupt.New(stm32.IRQ_EXTI15_10, func(interrupt.Interrupt) { handlePinInterrupt(12) }) + case 13: + return interrupt.New(stm32.IRQ_EXTI15_10, func(interrupt.Interrupt) { handlePinInterrupt(13) }) + case 14: + return interrupt.New(stm32.IRQ_EXTI15_10, func(interrupt.Interrupt) { handlePinInterrupt(14) }) + case 15: + return interrupt.New(stm32.IRQ_EXTI15_10, func(interrupt.Interrupt) { handlePinInterrupt(15) }) + } + + return interrupt.Interrupt{} +} + +//---------- Timer related code + +var ( + TIM1 = TIM{ + EnableRegister: &stm32.RCC.APB2ENR, + EnableFlag: stm32.RCC_APB2ENR_TIM1EN, + Device: stm32.TIM1, + Channels: [4]TimerChannel{ + TimerChannel{Pins: []PinFunction{ + {PA8, AF1_TIM1_2}, + {PE9, AF1_TIM1_2}, + }}, + TimerChannel{Pins: []PinFunction{ + {PA9, AF1_TIM1_2}, + {PE11, AF1_TIM1_2}, + }}, + TimerChannel{Pins: []PinFunction{ + {PA10, AF1_TIM1_2}, + {PE13, AF1_TIM1_2}, + }}, + TimerChannel{Pins: []PinFunction{ + {PA11, AF1_TIM1_2}, + {PE14, AF1_TIM1_2}, + }}, + }, + busFreq: APB2_TIM_FREQ, + } + + TIM2 = TIM{ + EnableRegister: &stm32.RCC.APB1ENR, + EnableFlag: stm32.RCC_APB1ENR_TIM2EN, + Device: stm32.TIM2, + Channels: [4]TimerChannel{ + TimerChannel{Pins: []PinFunction{ + {PA0, AF1_TIM1_2}, + {PA5, AF1_TIM1_2}, + {PA15, AF1_TIM1_2}, + }}, + TimerChannel{Pins: []PinFunction{ + {PA1, AF1_TIM1_2}, + {PB3, AF1_TIM1_2}, + }}, + TimerChannel{Pins: []PinFunction{ + {PA2, AF1_TIM1_2}, + {PB10, AF1_TIM1_2}, + }}, + TimerChannel{Pins: []PinFunction{ + {PA3, AF1_TIM1_2}, + {PB11, AF1_TIM1_2}, + }}, + }, + busFreq: APB1_TIM_FREQ, + } + + TIM3 = TIM{ + EnableRegister: &stm32.RCC.APB1ENR, + EnableFlag: stm32.RCC_APB1ENR_TIM3EN, + Device: stm32.TIM3, + Channels: [4]TimerChannel{ + TimerChannel{Pins: []PinFunction{ + {PA6, AF2_TIM3_4_5}, + {PB4, AF2_TIM3_4_5}, + {PC6, AF2_TIM3_4_5}, + }}, + TimerChannel{Pins: []PinFunction{ + {PA7, AF2_TIM3_4_5}, + {PB5, AF2_TIM3_4_5}, + {PC7, AF2_TIM3_4_5}, + }}, + TimerChannel{Pins: []PinFunction{ + {PB0, AF2_TIM3_4_5}, + {PC8, AF2_TIM3_4_5}, + }}, + TimerChannel{Pins: []PinFunction{ + {PB1, AF2_TIM3_4_5}, + {PC9, AF2_TIM3_4_5}, + }}, + }, + busFreq: APB1_TIM_FREQ, + } + + TIM4 = TIM{ + EnableRegister: &stm32.RCC.APB1ENR, + EnableFlag: stm32.RCC_APB1ENR_TIM4EN, + Device: stm32.TIM4, + Channels: [4]TimerChannel{ + TimerChannel{Pins: []PinFunction{ + {PB6, AF2_TIM3_4_5}, + {PD12, AF2_TIM3_4_5}, + }}, + TimerChannel{Pins: []PinFunction{ + {PB7, AF2_TIM3_4_5}, + {PD13, AF2_TIM3_4_5}, + }}, + TimerChannel{Pins: []PinFunction{ + {PB8, AF2_TIM3_4_5}, + {PD14, AF2_TIM3_4_5}, + }}, + TimerChannel{Pins: []PinFunction{ + {PB9, AF2_TIM3_4_5}, + {PD15, AF2_TIM3_4_5}, + }}, + }, + busFreq: APB1_TIM_FREQ, + } + + TIM5 = TIM{ + EnableRegister: &stm32.RCC.APB1ENR, + EnableFlag: stm32.RCC_APB1ENR_TIM5EN, + Device: stm32.TIM5, + Channels: [4]TimerChannel{ + TimerChannel{Pins: []PinFunction{ + {PA0, AF2_TIM3_4_5}, + {PH10, AF2_TIM3_4_5}, + }}, + TimerChannel{Pins: []PinFunction{ + {PA1, AF2_TIM3_4_5}, + {PH11, AF2_TIM3_4_5}, + }}, + TimerChannel{Pins: []PinFunction{ + {PA2, AF2_TIM3_4_5}, + {PH12, AF2_TIM3_4_5}, + }}, + TimerChannel{Pins: []PinFunction{ + {PA3, AF2_TIM3_4_5}, + {PI0, AF2_TIM3_4_5}, + }}, + }, + busFreq: APB1_TIM_FREQ, + } + + TIM6 = TIM{ + EnableRegister: &stm32.RCC.APB1ENR, + EnableFlag: stm32.RCC_APB1ENR_TIM6EN, + Device: stm32.TIM6, + Channels: [4]TimerChannel{ + TimerChannel{Pins: []PinFunction{}}, + TimerChannel{Pins: []PinFunction{}}, + TimerChannel{Pins: []PinFunction{}}, + TimerChannel{Pins: []PinFunction{}}, + }, + busFreq: APB1_TIM_FREQ, + } + + TIM7 = TIM{ + EnableRegister: &stm32.RCC.APB1ENR, + EnableFlag: stm32.RCC_APB1ENR_TIM7EN, + Device: stm32.TIM7, + Channels: [4]TimerChannel{ + TimerChannel{Pins: []PinFunction{}}, + TimerChannel{Pins: []PinFunction{}}, + TimerChannel{Pins: []PinFunction{}}, + TimerChannel{Pins: []PinFunction{}}, + }, + busFreq: APB1_TIM_FREQ, + } + + TIM8 = TIM{ + EnableRegister: &stm32.RCC.APB2ENR, + EnableFlag: stm32.RCC_APB2ENR_TIM8EN, + Device: stm32.TIM8, + Channels: [4]TimerChannel{ + TimerChannel{Pins: []PinFunction{ + {PC6, AF3_TIM8_9_10_11_LPTIM1}, + {PI5, AF3_TIM8_9_10_11_LPTIM1}, + }}, + TimerChannel{Pins: []PinFunction{ + {PC7, AF3_TIM8_9_10_11_LPTIM1}, + {PI6, AF3_TIM8_9_10_11_LPTIM1}, + }}, + TimerChannel{Pins: []PinFunction{ + {PC8, AF3_TIM8_9_10_11_LPTIM1}, + {PI7, AF3_TIM8_9_10_11_LPTIM1}, + }}, + TimerChannel{Pins: []PinFunction{ + {PC9, AF3_TIM8_9_10_11_LPTIM1}, + {PI2, AF3_TIM8_9_10_11_LPTIM1}, + }}, + }, + busFreq: APB2_TIM_FREQ, + } + + TIM9 = TIM{ + EnableRegister: &stm32.RCC.APB2ENR, + EnableFlag: stm32.RCC_APB2ENR_TIM9EN, + Device: stm32.TIM9, + Channels: [4]TimerChannel{ + TimerChannel{Pins: []PinFunction{ + {PA2, AF3_TIM8_9_10_11_LPTIM1}, + {PE5, AF3_TIM8_9_10_11_LPTIM1}, + }}, + TimerChannel{Pins: []PinFunction{ + {PA3, AF3_TIM8_9_10_11_LPTIM1}, + {PE6, AF3_TIM8_9_10_11_LPTIM1}, + }}, + TimerChannel{Pins: []PinFunction{}}, + TimerChannel{Pins: []PinFunction{}}, + }, + busFreq: APB2_TIM_FREQ, + } + + TIM10 = TIM{ + EnableRegister: &stm32.RCC.APB2ENR, + EnableFlag: stm32.RCC_APB2ENR_TIM10EN, + Device: stm32.TIM10, + Channels: [4]TimerChannel{ + TimerChannel{Pins: []PinFunction{ + {PB8, AF3_TIM8_9_10_11_LPTIM1}, + {PF6, AF3_TIM8_9_10_11_LPTIM1}, + }}, + TimerChannel{Pins: []PinFunction{}}, + TimerChannel{Pins: []PinFunction{}}, + TimerChannel{Pins: []PinFunction{}}, + }, + busFreq: APB2_TIM_FREQ, + } + + TIM11 = TIM{ + EnableRegister: &stm32.RCC.APB2ENR, + EnableFlag: stm32.RCC_APB2ENR_TIM11EN, + Device: stm32.TIM11, + Channels: [4]TimerChannel{ + TimerChannel{Pins: []PinFunction{ + {PB9, AF3_TIM8_9_10_11_LPTIM1}, + {PF7, AF3_TIM8_9_10_11_LPTIM1}, + }}, + TimerChannel{Pins: []PinFunction{}}, + TimerChannel{Pins: []PinFunction{}}, + TimerChannel{Pins: []PinFunction{}}, + }, + busFreq: APB2_TIM_FREQ, + } + + TIM12 = TIM{ + EnableRegister: &stm32.RCC.APB1ENR, + EnableFlag: stm32.RCC_APB1ENR_TIM12EN, + Device: stm32.TIM12, + Channels: [4]TimerChannel{ + TimerChannel{Pins: []PinFunction{ + {PB14, AF9_CAN1_TIM12_13_14_QUADSPI_FMC_OTG2_HS}, + {PH6, AF9_CAN1_TIM12_13_14_QUADSPI_FMC_OTG2_HS}, + }}, + TimerChannel{Pins: []PinFunction{ + {PB15, AF9_CAN1_TIM12_13_14_QUADSPI_FMC_OTG2_HS}, + {PH9, AF9_CAN1_TIM12_13_14_QUADSPI_FMC_OTG2_HS}, + }}, + TimerChannel{Pins: []PinFunction{}}, + TimerChannel{Pins: []PinFunction{}}, + }, + busFreq: APB1_TIM_FREQ, + } + + TIM13 = TIM{ + EnableRegister: &stm32.RCC.APB1ENR, + EnableFlag: stm32.RCC_APB1ENR_TIM13EN, + Device: stm32.TIM13, + Channels: [4]TimerChannel{ + TimerChannel{Pins: []PinFunction{ + {PA6, AF9_CAN1_TIM12_13_14_QUADSPI_FMC_OTG2_HS}, + {PF8, AF9_CAN1_TIM12_13_14_QUADSPI_FMC_OTG2_HS}, + }}, + TimerChannel{Pins: []PinFunction{}}, + TimerChannel{Pins: []PinFunction{}}, + TimerChannel{Pins: []PinFunction{}}, + }, + busFreq: APB1_TIM_FREQ, + } + + TIM14 = TIM{ + EnableRegister: &stm32.RCC.APB1ENR, + EnableFlag: stm32.RCC_APB1ENR_TIM14EN, + Device: stm32.TIM14, + Channels: [4]TimerChannel{ + TimerChannel{Pins: []PinFunction{ + {PA7, AF9_CAN1_TIM12_13_14_QUADSPI_FMC_OTG2_HS}, + {PF9, AF9_CAN1_TIM12_13_14_QUADSPI_FMC_OTG2_HS}, + }}, + TimerChannel{Pins: []PinFunction{}}, + TimerChannel{Pins: []PinFunction{}}, + TimerChannel{Pins: []PinFunction{}}, + }, + busFreq: APB1_TIM_FREQ, + } +) + +func (t *TIM) registerUPInterrupt() interrupt.Interrupt { + switch t { + case &TIM1: + return interrupt.New(stm32.IRQ_TIM1_UP_TIM10, TIM1.handleUPInterrupt) + case &TIM2: + return interrupt.New(stm32.IRQ_TIM2, TIM2.handleUPInterrupt) + case &TIM3: + return interrupt.New(stm32.IRQ_TIM3, TIM3.handleUPInterrupt) + case &TIM4: + return interrupt.New(stm32.IRQ_TIM4, TIM4.handleUPInterrupt) + case &TIM5: + return interrupt.New(stm32.IRQ_TIM5, TIM5.handleUPInterrupt) + case &TIM6: + return interrupt.New(stm32.IRQ_TIM6_DAC, TIM6.handleUPInterrupt) + case &TIM7: + return interrupt.New(stm32.IRQ_TIM7, TIM7.handleUPInterrupt) + case &TIM8: + return interrupt.New(stm32.IRQ_TIM8_UP_TIM13, TIM8.handleUPInterrupt) + case &TIM9: + return interrupt.New(stm32.IRQ_TIM1_BRK_TIM9, TIM9.handleUPInterrupt) + case &TIM10: + return interrupt.New(stm32.IRQ_TIM1_UP_TIM10, TIM10.handleUPInterrupt) + case &TIM11: + return interrupt.New(stm32.IRQ_TIM1_TRG_COM_TIM11, TIM11.handleUPInterrupt) + case &TIM12: + return interrupt.New(stm32.IRQ_TIM8_BRK_TIM12, TIM12.handleUPInterrupt) + case &TIM13: + return interrupt.New(stm32.IRQ_TIM8_UP_TIM13, TIM13.handleUPInterrupt) + case &TIM14: + return interrupt.New(stm32.IRQ_TIM8_TRG_COM_TIM14, TIM14.handleUPInterrupt) + } + + return interrupt.Interrupt{} +} + +func (t *TIM) registerOCInterrupt() interrupt.Interrupt { + switch t { + case &TIM1: + return interrupt.New(stm32.IRQ_TIM1_CC, TIM1.handleUPInterrupt) + case &TIM2: + return interrupt.New(stm32.IRQ_TIM2, TIM2.handleOCInterrupt) + case &TIM3: + return interrupt.New(stm32.IRQ_TIM3, TIM3.handleOCInterrupt) + case &TIM4: + return interrupt.New(stm32.IRQ_TIM4, TIM4.handleOCInterrupt) + case &TIM5: + return interrupt.New(stm32.IRQ_TIM5, TIM5.handleOCInterrupt) + case &TIM6: + return interrupt.New(stm32.IRQ_TIM6_DAC, TIM6.handleOCInterrupt) + case &TIM7: + return interrupt.New(stm32.IRQ_TIM7, TIM7.handleOCInterrupt) + case &TIM8: + return interrupt.New(stm32.IRQ_TIM8_CC, TIM8.handleOCInterrupt) + case &TIM9: + return interrupt.New(stm32.IRQ_TIM1_BRK_TIM9, TIM9.handleOCInterrupt) + case &TIM10: + return interrupt.New(stm32.IRQ_TIM1_UP_TIM10, TIM10.handleOCInterrupt) + case &TIM11: + return interrupt.New(stm32.IRQ_TIM1_TRG_COM_TIM11, TIM11.handleOCInterrupt) + case &TIM12: + return interrupt.New(stm32.IRQ_TIM8_BRK_TIM12, TIM12.handleOCInterrupt) + case &TIM13: + return interrupt.New(stm32.IRQ_TIM8_UP_TIM13, TIM13.handleOCInterrupt) + case &TIM14: + return interrupt.New(stm32.IRQ_TIM8_TRG_COM_TIM14, TIM14.handleOCInterrupt) + } + + return interrupt.Interrupt{} +} + +func (t *TIM) enableMainOutput() { + t.Device.BDTR.SetBits(stm32.TIM_BDTR_MOE) +} + +type arrtype = uint32 +type arrRegType = volatile.Register32 + +const ( + ARR_MAX = 0x10000 + PSC_MAX = 0x10000 +) + +func initRNG() { + stm32.RCC.AHB2ENR.SetBits(stm32.RCC_AHB2ENR_RNGEN) + stm32.RNG.CR.SetBits(stm32.RNG_CR_RNGEN) +} diff --git a/emb/machine/machine_stm32f7x2.go b/emb/machine/machine_stm32f7x2.go new file mode 100644 index 00000000..7da40707 --- /dev/null +++ b/emb/machine/machine_stm32f7x2.go @@ -0,0 +1,70 @@ +//go:build stm32f7x2 + +package machine + +// Peripheral abstraction layer for the stm32f407 + +import ( + "device/stm32" +) + +func CPUFrequency() uint32 { + return 216000000 +} + +// Internal use: configured speed of the APB1 and APB2 timers, this should be kept +// in sync with any changes to runtime package which configures the oscillators +// and clock frequencies +const APB1_TIM_FREQ = 54e6 // 54MHz +const APB2_TIM_FREQ = 216e6 // 216MHz + +//---------- UART related code + +// Configure the UART. +func (uart *UART) configurePins(config UARTConfig) { + // enable the alternate functions on the TX and RX pins + config.TX.ConfigureAltFunc(PinConfig{Mode: PinModeUARTTX}, uart.TxAltFuncSelector) + config.RX.ConfigureAltFunc(PinConfig{Mode: PinModeUARTRX}, uart.RxAltFuncSelector) +} + +// UART baudrate calc based on the bus and clockspeed +// NOTE: keep this in sync with the runtime/runtime_stm32f7x2.go clock init code +func (uart *UART) getBaudRateDivisor(baudRate uint32) uint32 { + var clock uint32 + switch uart.Bus { + case stm32.USART1, stm32.USART6: + clock = CPUFrequency() / 2 // APB2 Frequency + case stm32.USART2, stm32.USART3, stm32.UART4, stm32.UART5: + clock = CPUFrequency() / 8 // APB1 Frequency + } + return clock / baudRate +} + +// Register names vary by ST processor, these are for STM F7x2 +func (uart *UART) setRegisters() { + uart.rxReg = &uart.Bus.RDR + uart.txReg = &uart.Bus.TDR + uart.statusReg = &uart.Bus.ISR + uart.txEmptyFlag = stm32.USART_ISR_TXE +} + +//---------- I2C related code + +// Gets the value for TIMINGR register +func (i2c *I2C) getFreqRange(br uint32) uint32 { + // This is a 'magic' value calculated by STM32CubeMX + // for 27MHz PCLK1 (216MHz CPU Freq / 8). + // TODO: Do calculations based on PCLK1 + switch br { + case 10 * KHz: + return 0x5010C0FF + case 100 * KHz: + return 0x00606A9B + case 400 * KHz: + return 0x00201625 + case 500 * KHz: + return 0x00100429 + default: + return 0 + } +} diff --git a/emb/machine/machine_stm32l0.go b/emb/machine/machine_stm32l0.go new file mode 100644 index 00000000..1ecd958b --- /dev/null +++ b/emb/machine/machine_stm32l0.go @@ -0,0 +1,317 @@ +//go:build stm32l0 + +package machine + +// Peripheral abstraction layer for the stm32l0 + +import ( + "device/stm32" + "runtime/interrupt" +) + +func CPUFrequency() uint32 { + return 32000000 +} + +var deviceIDAddr = []uintptr{0x1FF80050, 0x1FF80054, 0x1FF80058} + +// Internal use: configured speed of the APB1 and APB2 timers, this should be kept +// in sync with any changes to runtime package which configures the oscillators +// and clock frequencies +const APB1_TIM_FREQ = 32e6 // 32MHz +const APB2_TIM_FREQ = 32e6 // 32MHz + +const ( + PA0 = portA + 0 + PA1 = portA + 1 + PA2 = portA + 2 + PA3 = portA + 3 + PA4 = portA + 4 + PA5 = portA + 5 + PA6 = portA + 6 + PA7 = portA + 7 + PA8 = portA + 8 + PA9 = portA + 9 + PA10 = portA + 10 + PA11 = portA + 11 + PA12 = portA + 12 + PA13 = portA + 13 + PA14 = portA + 14 + PA15 = portA + 15 + + PB0 = portB + 0 + PB1 = portB + 1 + PB2 = portB + 2 + PB3 = portB + 3 + PB4 = portB + 4 + PB5 = portB + 5 + PB6 = portB + 6 + PB7 = portB + 7 + PB8 = portB + 8 + PB9 = portB + 9 + PB10 = portB + 10 + PB11 = portB + 11 + PB12 = portB + 12 + PB13 = portB + 13 + PB14 = portB + 14 + PB15 = portB + 15 + + PC0 = portC + 0 + PC1 = portC + 1 + PC2 = portC + 2 + PC3 = portC + 3 + PC4 = portC + 4 + PC5 = portC + 5 + PC6 = portC + 6 + PC7 = portC + 7 + PC8 = portC + 8 + PC9 = portC + 9 + PC10 = portC + 10 + PC11 = portC + 11 + PC12 = portC + 12 + PC13 = portC + 13 + PC14 = portC + 14 + PC15 = portC + 15 + + PD0 = portD + 0 + PD1 = portD + 1 + PD2 = portD + 2 + PD3 = portD + 3 + PD4 = portD + 4 + PD5 = portD + 5 + PD6 = portD + 6 + PD7 = portD + 7 + PD8 = portD + 8 + PD9 = portD + 9 + PD10 = portD + 10 + PD11 = portD + 11 + PD12 = portD + 12 + PD13 = portD + 13 + PD14 = portD + 14 + PD15 = portD + 15 + + PE0 = portE + 0 + PE1 = portE + 1 + PE2 = portE + 2 + PE3 = portE + 3 + PE4 = portE + 4 + PE5 = portE + 5 + PE6 = portE + 6 + PE7 = portE + 7 + PE8 = portE + 8 + PE9 = portE + 9 + PE10 = portE + 10 + PE11 = portE + 11 + PE12 = portE + 12 + PE13 = portE + 13 + PE14 = portE + 14 + PE15 = portE + 15 + + PH0 = portH + 0 + PH1 = portH + 1 +) + +func (p Pin) getPort() *stm32.GPIO_Type { + switch p / 16 { + case 0: + return stm32.GPIOA + case 1: + return stm32.GPIOB + case 2: + return stm32.GPIOC + case 3: + return stm32.GPIOD + case 4: + return stm32.GPIOE + case 7: + return stm32.GPIOH + default: + panic("machine: unknown port") + } +} + +// enableClock enables the clock for this desired GPIO port. +func (p Pin) enableClock() { + switch p / 16 { + case 0: + stm32.RCC.IOPENR.SetBits(stm32.RCC_IOPENR_IOPAEN) + case 1: + stm32.RCC.IOPENR.SetBits(stm32.RCC_IOPENR_IOPBEN) + case 2: + stm32.RCC.IOPENR.SetBits(stm32.RCC_IOPENR_IOPCEN) + case 3: + stm32.RCC.IOPENR.SetBits(stm32.RCC_IOPENR_IOPDEN) + case 4: + stm32.RCC.IOPENR.SetBits(stm32.RCC_IOPENR_IOPEEN) + case 7: + stm32.RCC.IOPENR.SetBits(stm32.RCC_IOPENR_IOPHEN) + default: + panic("machine: unknown port") + } +} + +func (p Pin) registerInterrupt() interrupt.Interrupt { + pin := uint8(p) % 16 + + switch pin { + case 0: + return interrupt.New(stm32.IRQ_EXTI0_1, func(interrupt.Interrupt) { handlePinInterrupt(0) }) + case 1: + return interrupt.New(stm32.IRQ_EXTI0_1, func(interrupt.Interrupt) { handlePinInterrupt(1) }) + case 2: + return interrupt.New(stm32.IRQ_EXTI2_3, func(interrupt.Interrupt) { handlePinInterrupt(2) }) + case 3: + return interrupt.New(stm32.IRQ_EXTI2_3, func(interrupt.Interrupt) { handlePinInterrupt(3) }) + case 4: + return interrupt.New(stm32.IRQ_EXTI4_15, func(interrupt.Interrupt) { handlePinInterrupt(4) }) + case 5: + return interrupt.New(stm32.IRQ_EXTI4_15, func(interrupt.Interrupt) { handlePinInterrupt(5) }) + case 6: + return interrupt.New(stm32.IRQ_EXTI4_15, func(interrupt.Interrupt) { handlePinInterrupt(6) }) + case 7: + return interrupt.New(stm32.IRQ_EXTI4_15, func(interrupt.Interrupt) { handlePinInterrupt(7) }) + case 8: + return interrupt.New(stm32.IRQ_EXTI4_15, func(interrupt.Interrupt) { handlePinInterrupt(8) }) + case 9: + return interrupt.New(stm32.IRQ_EXTI4_15, func(interrupt.Interrupt) { handlePinInterrupt(9) }) + case 10: + return interrupt.New(stm32.IRQ_EXTI4_15, func(interrupt.Interrupt) { handlePinInterrupt(10) }) + case 11: + return interrupt.New(stm32.IRQ_EXTI4_15, func(interrupt.Interrupt) { handlePinInterrupt(11) }) + case 12: + return interrupt.New(stm32.IRQ_EXTI4_15, func(interrupt.Interrupt) { handlePinInterrupt(12) }) + case 13: + return interrupt.New(stm32.IRQ_EXTI4_15, func(interrupt.Interrupt) { handlePinInterrupt(13) }) + case 14: + return interrupt.New(stm32.IRQ_EXTI4_15, func(interrupt.Interrupt) { handlePinInterrupt(14) }) + case 15: + return interrupt.New(stm32.IRQ_EXTI4_15, func(interrupt.Interrupt) { handlePinInterrupt(15) }) + } + + return interrupt.Interrupt{} +} + +//---------- UART related types and code + +// Configure the UART. +func (uart *UART) configurePins(config UARTConfig) { + // enable the alternate functions on the TX and RX pins + config.TX.ConfigureAltFunc(PinConfig{Mode: PinModeUARTTX}, uart.TxAltFuncSelector) + config.RX.ConfigureAltFunc(PinConfig{Mode: PinModeUARTRX}, uart.RxAltFuncSelector) +} + +// UART baudrate calc based on the bus and clockspeed +func (uart *UART) getBaudRateDivisor(baudRate uint32) uint32 { + var clock, rate uint32 + switch uart.Bus { + case stm32.LPUART1: + clock = CPUFrequency() / 2 // APB1 Frequency + rate = uint32((256 * clock) / baudRate) + case stm32.USART1: + clock = CPUFrequency() / 2 // APB2 Frequency + rate = uint32(clock / baudRate) + case stm32.USART2: + clock = CPUFrequency() / 2 // APB1 Frequency + rate = uint32(clock / baudRate) + } + + return rate +} + +// Register names vary by ST processor, these are for STM L0 family +func (uart *UART) setRegisters() { + uart.rxReg = &uart.Bus.RDR + uart.txReg = &uart.Bus.TDR + uart.statusReg = &uart.Bus.ISR + uart.txEmptyFlag = stm32.USART_ISR_TXE +} + +//---------- SPI related types and code + +// SPI on the STM32Fxxx using MODER / alternate function pins +type SPI struct { + Bus *stm32.SPI_Type + AltFuncSelector uint8 +} + +func (spi *SPI) config8Bits() { + // no-op on this series +} + +// Set baud rate for SPI +func (spi *SPI) getBaudRate(config SPIConfig) uint32 { + var conf uint32 + + localFrequency := config.Frequency + + // Default + if config.Frequency == 0 { + config.Frequency = 4e6 + } + + if spi.Bus != stm32.SPI1 { + // Assume it's SPI2 or SPI3 on APB1 at 1/2 the clock frequency of APB2, so + // we want to pretend to request 2x the baudrate asked for + localFrequency = localFrequency * 2 + } + + // set frequency dependent on PCLK prescaler. Since these are rather weird + // speeds due to the CPU frequency, pick a range up to that frequency for + // clients to use more human-understandable numbers, e.g. nearest 100KHz + + // These are based on APB2 clock frequency (84MHz on the discovery board) + // TODO: also include the MCU/APB clock setting in the equation + switch { + case localFrequency < 328125: + conf = stm32.SPI_CR1_BR_Div256 + case localFrequency < 656250: + conf = stm32.SPI_CR1_BR_Div128 + case localFrequency < 1312500: + conf = stm32.SPI_CR1_BR_Div64 + case localFrequency < 2625000: + conf = stm32.SPI_CR1_BR_Div32 + case localFrequency < 5250000: + conf = stm32.SPI_CR1_BR_Div16 + case localFrequency < 10500000: + conf = stm32.SPI_CR1_BR_Div8 + // NOTE: many SPI components won't operate reliably (or at all) above 10MHz + // Check the datasheet of the part + case localFrequency < 21000000: + conf = stm32.SPI_CR1_BR_Div4 + case localFrequency < 42000000: + conf = stm32.SPI_CR1_BR_Div2 + default: + // None of the specific baudrates were selected; choose the lowest speed + conf = stm32.SPI_CR1_BR_Div256 + } + + return conf << stm32.SPI_CR1_BR_Pos +} + +// Configure SPI pins for input output and clock +func (spi *SPI) configurePins(config SPIConfig) { + config.SCK.ConfigureAltFunc(PinConfig{Mode: PinModeSPICLK}, spi.AltFuncSelector) + config.SDO.ConfigureAltFunc(PinConfig{Mode: PinModeSPISDO}, spi.AltFuncSelector) + config.SDI.ConfigureAltFunc(PinConfig{Mode: PinModeSPISDI}, spi.AltFuncSelector) +} + +//---------- I2C related types and code + +// Gets the value for TIMINGR register +func (i2c I2C) getFreqRange(br uint32) uint32 { + // This is a 'magic' value calculated by STM32CubeMX + // for 16MHz PCLK1. + // TODO: Do calculations based on PCLK1 + switch br { + case 10 * KHz: + return 0x40003EFF + case 100 * KHz: + return 0x00303D5B + case 400 * KHz: + return 0x0010061A + case 500 * KHz: + return 0x00000117 + default: + return 0 + } +} diff --git a/emb/machine/machine_stm32l0x1.go b/emb/machine/machine_stm32l0x1.go new file mode 100644 index 00000000..f0d23ca4 --- /dev/null +++ b/emb/machine/machine_stm32l0x1.go @@ -0,0 +1,197 @@ +//go:build stm32l0x1 + +package machine + +// Peripheral abstraction layer for the stm32l0 + +import ( + "device/stm32" + "runtime/interrupt" + "runtime/volatile" + "unsafe" +) + +const ( + AF0_SYSTEM_SPI1_USART2_LPTIM_TIM21 = 0 + AF1_SPI1_I2C1_LPTIM = 1 + AF2_LPTIM_TIM2 = 2 + AF3_I2C1 = 3 + AF4_I2C1_USART2_LPUART1_TIM22 = 4 + AF5_TIM2_21_22 = 5 + AF6_LPUART1 = 6 + AF7_COMP1_2 = 7 +) + +// Enable peripheral clock +func enableAltFuncClock(bus unsafe.Pointer) { + switch bus { + case unsafe.Pointer(stm32.PWR): // Power interface clock enable + stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_PWREN) + case unsafe.Pointer(stm32.I2C3): // I2C3 clock enable + stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_I2C3EN) + case unsafe.Pointer(stm32.I2C2): // I2C2 clock enable + stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_I2C2EN) + case unsafe.Pointer(stm32.I2C1): // I2C1 clock enable + stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_I2C1EN) + case unsafe.Pointer(stm32.USART5): // UART5 clock enable + stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_USART5EN) + case unsafe.Pointer(stm32.USART4): // UART4 clock enable + stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_USART4EN) + case unsafe.Pointer(stm32.USART2): // USART2 clock enable + stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_USART2EN) + case unsafe.Pointer(stm32.SPI2): // SPI2 clock enable + stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_SPI2EN) + case unsafe.Pointer(stm32.LPUART1): // LPUART1 clock enable + stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_LPUART1EN) + case unsafe.Pointer(stm32.WWDG): // Window watchdog clock enable + stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_WWDGEN) + case unsafe.Pointer(stm32.TIM7): // TIM7 clock enable + stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_TIM7EN) + case unsafe.Pointer(stm32.TIM6): // TIM6 clock enable + stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_TIM6EN) + case unsafe.Pointer(stm32.TIM3): // TIM3 clock enable + stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_TIM3EN) + case unsafe.Pointer(stm32.TIM2): // TIM2 clock enable + stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_TIM2EN) + case unsafe.Pointer(stm32.SYSCFG): // System configuration controller clock enable + stm32.RCC.APB2ENR.SetBits(stm32.RCC_APB2ENR_SYSCFGEN) + case unsafe.Pointer(stm32.SPI1): // SPI1 clock enable + stm32.RCC.APB2ENR.SetBits(stm32.RCC_APB2ENR_SPI1EN) + case unsafe.Pointer(stm32.ADC): // ADC clock enable + stm32.RCC.APB2ENR.SetBits(stm32.RCC_APB2ENR_ADCEN) + case unsafe.Pointer(stm32.USART1): // USART1 clock enable + stm32.RCC.APB2ENR.SetBits(stm32.RCC_APB2ENR_USART1EN) + } +} + +//---------- Timer related code + +var ( + TIM2 = TIM{ + EnableRegister: &stm32.RCC.APB1ENR, + EnableFlag: stm32.RCC_APB1ENR_TIM2EN, + Device: stm32.TIM2, + Channels: [4]TimerChannel{ + TimerChannel{Pins: []PinFunction{{PA0, AF2_LPTIM_TIM2}, {PA5, AF5_TIM2_21_22}, {PA8, AF5_TIM2_21_22}, {PA15, AF5_TIM2_21_22}}}, + TimerChannel{Pins: []PinFunction{{PA1, AF2_LPTIM_TIM2}, {PB3, AF2_LPTIM_TIM2}}}, + TimerChannel{Pins: []PinFunction{{PA2, AF2_LPTIM_TIM2}, {PB0, AF5_TIM2_21_22}, {PB10, AF2_LPTIM_TIM2}}}, + TimerChannel{Pins: []PinFunction{{PA3, AF2_LPTIM_TIM2}, {PB1, AF5_TIM2_21_22}, {PB11, AF2_LPTIM_TIM2}}}, + }, + busFreq: APB1_TIM_FREQ, + } + + TIM3 = TIM{ + EnableRegister: &stm32.RCC.APB1ENR, + EnableFlag: stm32.RCC_APB1ENR_TIM3EN, + Device: stm32.TIM3, + Channels: [4]TimerChannel{ + TimerChannel{Pins: []PinFunction{}}, + TimerChannel{Pins: []PinFunction{}}, + TimerChannel{Pins: []PinFunction{}}, + TimerChannel{Pins: []PinFunction{}}, + }, + busFreq: APB1_TIM_FREQ, + } + + TIM6 = TIM{ + EnableRegister: &stm32.RCC.APB1ENR, + EnableFlag: stm32.RCC_APB1ENR_TIM6EN, + Device: stm32.TIM6, + Channels: [4]TimerChannel{ + TimerChannel{Pins: []PinFunction{}}, + TimerChannel{Pins: []PinFunction{}}, + TimerChannel{Pins: []PinFunction{}}, + TimerChannel{Pins: []PinFunction{}}, + }, + busFreq: APB1_TIM_FREQ, + } + + TIM7 = TIM{ + EnableRegister: &stm32.RCC.APB1ENR, + EnableFlag: stm32.RCC_APB1ENR_TIM7EN, + Device: stm32.TIM7, + Channels: [4]TimerChannel{ + TimerChannel{Pins: []PinFunction{}}, + TimerChannel{Pins: []PinFunction{}}, + TimerChannel{Pins: []PinFunction{}}, + TimerChannel{Pins: []PinFunction{}}, + }, + busFreq: APB1_TIM_FREQ, + } + + TIM21 = TIM{ + EnableRegister: &stm32.RCC.APB2ENR, + EnableFlag: stm32.RCC_APB2ENR_TIM21EN, + Device: stm32.TIM21, + Channels: [4]TimerChannel{ + TimerChannel{Pins: []PinFunction{}}, + TimerChannel{Pins: []PinFunction{}}, + TimerChannel{Pins: []PinFunction{}}, + TimerChannel{Pins: []PinFunction{}}, + }, + busFreq: APB2_TIM_FREQ, + } + + TIM22 = TIM{ + EnableRegister: &stm32.RCC.APB2ENR, + EnableFlag: stm32.RCC_APB2ENR_TIM22EN, + Device: stm32.TIM2, + Channels: [4]TimerChannel{ + TimerChannel{Pins: []PinFunction{}}, + TimerChannel{Pins: []PinFunction{}}, + TimerChannel{Pins: []PinFunction{}}, + TimerChannel{Pins: []PinFunction{}}, + }, + busFreq: APB2_TIM_FREQ, + } +) + +func (t *TIM) registerUPInterrupt() interrupt.Interrupt { + switch t { + case &TIM2: + return interrupt.New(stm32.IRQ_TIM2, TIM2.handleUPInterrupt) + case &TIM3: + return interrupt.New(stm32.IRQ_TIM3, TIM3.handleUPInterrupt) + case &TIM6: + return interrupt.New(stm32.IRQ_TIM6, TIM6.handleUPInterrupt) + case &TIM7: + return interrupt.New(stm32.IRQ_TIM7, TIM7.handleUPInterrupt) + case &TIM21: + return interrupt.New(stm32.IRQ_TIM21, TIM21.handleUPInterrupt) + case &TIM22: + return interrupt.New(stm32.IRQ_TIM22, TIM22.handleUPInterrupt) + } + + return interrupt.Interrupt{} +} + +func (t *TIM) registerOCInterrupt() interrupt.Interrupt { + switch t { + case &TIM2: + return interrupt.New(stm32.IRQ_TIM2, TIM2.handleOCInterrupt) + case &TIM3: + return interrupt.New(stm32.IRQ_TIM3, TIM3.handleOCInterrupt) + case &TIM6: + return interrupt.New(stm32.IRQ_TIM6, TIM6.handleOCInterrupt) + case &TIM7: + return interrupt.New(stm32.IRQ_TIM7, TIM7.handleOCInterrupt) + case &TIM21: + return interrupt.New(stm32.IRQ_TIM21, TIM21.handleOCInterrupt) + case &TIM22: + return interrupt.New(stm32.IRQ_TIM22, TIM22.handleOCInterrupt) + } + + return interrupt.Interrupt{} +} + +func (t *TIM) enableMainOutput() { + // nothing to do - no BDTR register +} + +type arrtype = uint16 +type arrRegType = volatile.Register16 + +const ( + ARR_MAX = 0x10000 + PSC_MAX = 0x10000 +) diff --git a/emb/machine/machine_stm32l0x2.go b/emb/machine/machine_stm32l0x2.go new file mode 100644 index 00000000..2a747920 --- /dev/null +++ b/emb/machine/machine_stm32l0x2.go @@ -0,0 +1,259 @@ +//go:build stm32l0x2 + +package machine + +// Peripheral abstraction layer for the stm32l0 + +import ( + "device/stm32" + "runtime/interrupt" + "runtime/volatile" + "unsafe" +) + +const ( + AF0_SYSTEM_SPI1_2_I2S2_USART1_2_LPUART1_USB_LPTIM1_TSC_TIM2_21_22 = 0 + AF1_SPI1_2_I2S2_I2C1_TIM2_21 = 1 + AF2_SPI1_2_I2S2_LPUART1_USART5_USB_LPTIM1_TIM2_3 = 2 + AF3_I2C1_TSC = 3 + AF4_I2C1_USART1_2_LPUART1_TIM3_22 = 4 + AF5_SPI2_I2S2_I2C2_USART1_TIM2_21_22 = 5 + AF6_I2C1_2_LPUART1_USART4_5_TIM21 = 6 + AF7_I2C3_LPUART1_COMP1_2_TIM3 = 7 +) + +// Enable peripheral clock +func enableAltFuncClock(bus unsafe.Pointer) { + switch bus { + case unsafe.Pointer(stm32.DAC): // DAC interface clock enable + stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_DACEN) + case unsafe.Pointer(stm32.PWR): // Power interface clock enable + stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_PWREN) + case unsafe.Pointer(stm32.I2C3): // I2C3 clock enable + stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_I2C3EN) + case unsafe.Pointer(stm32.I2C2): // I2C2 clock enable + stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_I2C2EN) + case unsafe.Pointer(stm32.I2C1): // I2C1 clock enable + stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_I2C1EN) + case unsafe.Pointer(stm32.USART5): // UART5 clock enable + stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_USART5EN) + case unsafe.Pointer(stm32.USART4): // UART4 clock enable + stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_USART4EN) + case unsafe.Pointer(stm32.USART2): // USART2 clock enable + stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_USART2EN) + case unsafe.Pointer(stm32.SPI2): // SPI2 clock enable + stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_SPI2EN) + case unsafe.Pointer(stm32.LPUART1): // LPUART1 clock enable + stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_LPUART1EN) + case unsafe.Pointer(stm32.WWDG): // Window watchdog clock enable + stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_WWDGEN) + case unsafe.Pointer(stm32.TIM7): // TIM7 clock enable + stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_TIM7EN) + case unsafe.Pointer(stm32.TIM6): // TIM6 clock enable + stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_TIM6EN) + case unsafe.Pointer(stm32.TIM3): // TIM3 clock enable + stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_TIM3EN) + case unsafe.Pointer(stm32.TIM2): // TIM2 clock enable + stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_TIM2EN) + case unsafe.Pointer(stm32.SYSCFG): // System configuration controller clock enable + stm32.RCC.APB2ENR.SetBits(stm32.RCC_APB2ENR_SYSCFGEN) + case unsafe.Pointer(stm32.SPI1): // SPI1 clock enable + stm32.RCC.APB2ENR.SetBits(stm32.RCC_APB2ENR_SPI1EN) + case unsafe.Pointer(stm32.ADC): // ADC clock enable + stm32.RCC.APB2ENR.SetBits(stm32.RCC_APB2ENR_ADCEN) + case unsafe.Pointer(stm32.USART1): // USART1 clock enable + stm32.RCC.APB2ENR.SetBits(stm32.RCC_APB2ENR_USART1EN) + } +} + +//---------- Timer related code + +var ( + TIM2 = TIM{ + EnableRegister: &stm32.RCC.APB1ENR, + EnableFlag: stm32.RCC_APB1ENR_TIM2EN, + Device: stm32.TIM2, + Channels: [4]TimerChannel{ + TimerChannel{Pins: []PinFunction{ + {PA0, AF2_SPI1_2_I2S2_LPUART1_USART5_USB_LPTIM1_TIM2_3}, + {PA5, AF5_SPI2_I2S2_I2C2_USART1_TIM2_21_22}, + {PA15, AF5_SPI2_I2S2_I2C2_USART1_TIM2_21_22}, + {PE9, AF0_SYSTEM_SPI1_2_I2S2_USART1_2_LPUART1_USB_LPTIM1_TSC_TIM2_21_22}, + }}, + TimerChannel{Pins: []PinFunction{ + {PA1, AF2_SPI1_2_I2S2_LPUART1_USART5_USB_LPTIM1_TIM2_3}, + {PB3, AF2_SPI1_2_I2S2_LPUART1_USART5_USB_LPTIM1_TIM2_3}, + {PE10, AF0_SYSTEM_SPI1_2_I2S2_USART1_2_LPUART1_USB_LPTIM1_TSC_TIM2_21_22}, + }}, + TimerChannel{Pins: []PinFunction{ + {PA2, AF2_SPI1_2_I2S2_LPUART1_USART5_USB_LPTIM1_TIM2_3}, + {PB10, AF2_SPI1_2_I2S2_LPUART1_USART5_USB_LPTIM1_TIM2_3}, + {PE11, AF0_SYSTEM_SPI1_2_I2S2_USART1_2_LPUART1_USB_LPTIM1_TSC_TIM2_21_22}, + }}, + TimerChannel{Pins: []PinFunction{ + {PA3, AF2_SPI1_2_I2S2_LPUART1_USART5_USB_LPTIM1_TIM2_3}, + {PB11, AF2_SPI1_2_I2S2_LPUART1_USART5_USB_LPTIM1_TIM2_3}, + {PE12, AF0_SYSTEM_SPI1_2_I2S2_USART1_2_LPUART1_USB_LPTIM1_TSC_TIM2_21_22}, + }}, + }, + busFreq: APB1_TIM_FREQ, + } + + TIM3 = TIM{ + EnableRegister: &stm32.RCC.APB1ENR, + EnableFlag: stm32.RCC_APB1ENR_TIM3EN, + Device: stm32.TIM3, + Channels: [4]TimerChannel{ + TimerChannel{Pins: []PinFunction{ + {PA6, AF2_SPI1_2_I2S2_LPUART1_USART5_USB_LPTIM1_TIM2_3}, + {PB4, AF2_SPI1_2_I2S2_LPUART1_USART5_USB_LPTIM1_TIM2_3}, + {PC6, AF2_SPI1_2_I2S2_LPUART1_USART5_USB_LPTIM1_TIM2_3}, + {PE3, AF2_SPI1_2_I2S2_LPUART1_USART5_USB_LPTIM1_TIM2_3}, + }}, + TimerChannel{Pins: []PinFunction{ + {PA7, AF2_SPI1_2_I2S2_LPUART1_USART5_USB_LPTIM1_TIM2_3}, + {PB5, AF4_I2C1_USART1_2_LPUART1_TIM3_22}, + {PC7, AF2_SPI1_2_I2S2_LPUART1_USART5_USB_LPTIM1_TIM2_3}, + {PE4, AF2_SPI1_2_I2S2_LPUART1_USART5_USB_LPTIM1_TIM2_3}, + }}, + TimerChannel{Pins: []PinFunction{ + {PB0, AF2_SPI1_2_I2S2_LPUART1_USART5_USB_LPTIM1_TIM2_3}, + {PC8, AF2_SPI1_2_I2S2_LPUART1_USART5_USB_LPTIM1_TIM2_3}, + {PE5, AF2_SPI1_2_I2S2_LPUART1_USART5_USB_LPTIM1_TIM2_3}, + }}, + TimerChannel{Pins: []PinFunction{ + {PB1, AF2_SPI1_2_I2S2_LPUART1_USART5_USB_LPTIM1_TIM2_3}, + {PC9, AF2_SPI1_2_I2S2_LPUART1_USART5_USB_LPTIM1_TIM2_3}, + {PE6, AF2_SPI1_2_I2S2_LPUART1_USART5_USB_LPTIM1_TIM2_3}, + }}, + }, + busFreq: APB1_TIM_FREQ, + } + + TIM6 = TIM{ + EnableRegister: &stm32.RCC.APB1ENR, + EnableFlag: stm32.RCC_APB1ENR_TIM6EN, + Device: stm32.TIM6, + Channels: [4]TimerChannel{ + TimerChannel{Pins: []PinFunction{}}, + TimerChannel{Pins: []PinFunction{}}, + TimerChannel{Pins: []PinFunction{}}, + TimerChannel{Pins: []PinFunction{}}, + }, + busFreq: APB1_TIM_FREQ, + } + + TIM7 = TIM{ + EnableRegister: &stm32.RCC.APB1ENR, + EnableFlag: stm32.RCC_APB1ENR_TIM7EN, + Device: stm32.TIM7, + Channels: [4]TimerChannel{ + TimerChannel{Pins: []PinFunction{}}, + TimerChannel{Pins: []PinFunction{}}, + TimerChannel{Pins: []PinFunction{}}, + TimerChannel{Pins: []PinFunction{}}, + }, + busFreq: APB1_TIM_FREQ, + } + + TIM21 = TIM{ + EnableRegister: &stm32.RCC.APB2ENR, + EnableFlag: stm32.RCC_APB2ENR_TIM21EN, + Device: stm32.TIM21, + Channels: [4]TimerChannel{ + TimerChannel{Pins: []PinFunction{ + {PA2, AF0_SYSTEM_SPI1_2_I2S2_USART1_2_LPUART1_USB_LPTIM1_TSC_TIM2_21_22}, + {PB13, AF6_I2C1_2_LPUART1_USART4_5_TIM21}, + {PD0, AF0_SYSTEM_SPI1_2_I2S2_USART1_2_LPUART1_USB_LPTIM1_TSC_TIM2_21_22}, + {PE5, AF0_SYSTEM_SPI1_2_I2S2_USART1_2_LPUART1_USB_LPTIM1_TSC_TIM2_21_22}, + }}, + TimerChannel{Pins: []PinFunction{ + {PA3, AF0_SYSTEM_SPI1_2_I2S2_USART1_2_LPUART1_USB_LPTIM1_TSC_TIM2_21_22}, + {PB14, AF6_I2C1_2_LPUART1_USART4_5_TIM21}, + {PD7, AF1_SPI1_2_I2S2_I2C1_TIM2_21}, + {PE6, AF0_SYSTEM_SPI1_2_I2S2_USART1_2_LPUART1_USB_LPTIM1_TSC_TIM2_21_22}, + }}, + TimerChannel{Pins: []PinFunction{}}, + TimerChannel{Pins: []PinFunction{}}, + }, + busFreq: APB2_TIM_FREQ, + } + + TIM22 = TIM{ + EnableRegister: &stm32.RCC.APB2ENR, + EnableFlag: stm32.RCC_APB2ENR_TIM22EN, + Device: stm32.TIM2, + Channels: [4]TimerChannel{ + TimerChannel{Pins: []PinFunction{ + {PA6, AF5_SPI2_I2S2_I2C2_USART1_TIM2_21_22}, + {PB4, AF4_I2C1_USART1_2_LPUART1_TIM3_22}, + {PC6, AF0_SYSTEM_SPI1_2_I2S2_USART1_2_LPUART1_USB_LPTIM1_TSC_TIM2_21_22}, + {PE3, AF0_SYSTEM_SPI1_2_I2S2_USART1_2_LPUART1_USB_LPTIM1_TSC_TIM2_21_22}, + }}, + TimerChannel{Pins: []PinFunction{ + {PA7, AF5_SPI2_I2S2_I2C2_USART1_TIM2_21_22}, + {PB5, AF4_I2C1_USART1_2_LPUART1_TIM3_22}, + {PC7, AF0_SYSTEM_SPI1_2_I2S2_USART1_2_LPUART1_USB_LPTIM1_TSC_TIM2_21_22}, + {PE4, AF0_SYSTEM_SPI1_2_I2S2_USART1_2_LPUART1_USB_LPTIM1_TSC_TIM2_21_22}, + }}, + TimerChannel{Pins: []PinFunction{}}, + TimerChannel{Pins: []PinFunction{}}, + }, + busFreq: APB2_TIM_FREQ, + } +) + +func (t *TIM) registerUPInterrupt() interrupt.Interrupt { + switch t { + case &TIM2: + return interrupt.New(stm32.IRQ_TIM2, TIM2.handleUPInterrupt) + case &TIM3: + return interrupt.New(stm32.IRQ_TIM3, TIM3.handleUPInterrupt) + case &TIM6: + return interrupt.New(stm32.IRQ_TIM6_DAC, TIM6.handleUPInterrupt) + case &TIM7: + return interrupt.New(stm32.IRQ_TIM7, TIM7.handleUPInterrupt) + case &TIM21: + return interrupt.New(stm32.IRQ_TIM21, TIM21.handleUPInterrupt) + case &TIM22: + return interrupt.New(stm32.IRQ_TIM22, TIM22.handleUPInterrupt) + } + + return interrupt.Interrupt{} +} + +func (t *TIM) registerOCInterrupt() interrupt.Interrupt { + switch t { + case &TIM2: + return interrupt.New(stm32.IRQ_TIM2, TIM2.handleOCInterrupt) + case &TIM3: + return interrupt.New(stm32.IRQ_TIM3, TIM3.handleOCInterrupt) + case &TIM6: + return interrupt.New(stm32.IRQ_TIM6_DAC, TIM6.handleOCInterrupt) + case &TIM7: + return interrupt.New(stm32.IRQ_TIM7, TIM7.handleOCInterrupt) + case &TIM21: + return interrupt.New(stm32.IRQ_TIM21, TIM21.handleOCInterrupt) + case &TIM22: + return interrupt.New(stm32.IRQ_TIM22, TIM22.handleOCInterrupt) + } + + return interrupt.Interrupt{} +} + +func (t *TIM) enableMainOutput() { + // nothing to do - no BDTR register +} + +type arrtype = uint16 +type arrRegType = volatile.Register16 + +const ( + ARR_MAX = 0x10000 + PSC_MAX = 0x10000 +) + +func initRNG() { + stm32.RCC.AHBENR.SetBits(stm32.RCC_AHBENR_RNGEN) + stm32.RNG.CR.SetBits(stm32.RNG_CR_RNGEN) +} diff --git a/emb/machine/machine_stm32l4.go b/emb/machine/machine_stm32l4.go new file mode 100644 index 00000000..b5babc0b --- /dev/null +++ b/emb/machine/machine_stm32l4.go @@ -0,0 +1,650 @@ +//go:build stm32l4 + +package machine + +import ( + "device/stm32" + "errors" + "internal/binary" + "runtime/interrupt" + "runtime/volatile" + "unsafe" +) + +// Peripheral abstraction layer for the stm32l4 + +var deviceIDAddr = []uintptr{0x1FFF7590, 0x1FFF7594, 0x1FFF7598} + +const ( + AF0_SYSTEM = 0 + AF1_TIM1_2_LPTIM1 = 1 + AF2_TIM1_2 = 2 + AF3_USART2 = 3 + AF4_I2C1_2_3 = 4 + AF5_SPI1_2 = 5 + AF6_SPI3 = 6 + AF7_USART1_2_3 = 7 + AF8_LPUART1 = 8 + AF9_CAN1_TSC = 9 + AF10_USB_QUADSPI = 10 + AF12_COMP1_2_SWPMI1 = 12 + AF13_SAI1 = 13 + AF14_TIM2_15_16_LPTIM2 = 14 + AF15_EVENTOUT = 15 +) + +const ( + PA0 = portA + 0 + PA1 = portA + 1 + PA2 = portA + 2 + PA3 = portA + 3 + PA4 = portA + 4 + PA5 = portA + 5 + PA6 = portA + 6 + PA7 = portA + 7 + PA8 = portA + 8 + PA9 = portA + 9 + PA10 = portA + 10 + PA11 = portA + 11 + PA12 = portA + 12 + PA13 = portA + 13 + PA14 = portA + 14 + PA15 = portA + 15 + + PB0 = portB + 0 + PB1 = portB + 1 + PB2 = portB + 2 + PB3 = portB + 3 + PB4 = portB + 4 + PB5 = portB + 5 + PB6 = portB + 6 + PB7 = portB + 7 + PB8 = portB + 8 + PB9 = portB + 9 + PB10 = portB + 10 + PB11 = portB + 11 + PB12 = portB + 12 + PB13 = portB + 13 + PB14 = portB + 14 + PB15 = portB + 15 + + PC0 = portC + 0 + PC1 = portC + 1 + PC2 = portC + 2 + PC3 = portC + 3 + PC4 = portC + 4 + PC5 = portC + 5 + PC6 = portC + 6 + PC7 = portC + 7 + PC8 = portC + 8 + PC9 = portC + 9 + PC10 = portC + 10 + PC11 = portC + 11 + PC12 = portC + 12 + PC13 = portC + 13 + PC14 = portC + 14 + PC15 = portC + 15 + + PD0 = portD + 0 + PD1 = portD + 1 + PD2 = portD + 2 + PD3 = portD + 3 + PD4 = portD + 4 + PD5 = portD + 5 + PD6 = portD + 6 + PD7 = portD + 7 + PD8 = portD + 8 + PD9 = portD + 9 + PD10 = portD + 10 + PD11 = portD + 11 + PD12 = portD + 12 + PD13 = portD + 13 + PD14 = portD + 14 + PD15 = portD + 15 + + PE0 = portE + 0 + PE1 = portE + 1 + PE2 = portE + 2 + PE3 = portE + 3 + PE4 = portE + 4 + PE5 = portE + 5 + PE6 = portE + 6 + PE7 = portE + 7 + PE8 = portE + 8 + PE9 = portE + 9 + PE10 = portE + 10 + PE11 = portE + 11 + PE12 = portE + 12 + PE13 = portE + 13 + PE14 = portE + 14 + PE15 = portE + 15 +) + +// IRQs are defined here as they vary in the SVDs, but do have consistent mapping +// to Timer Interrupts. +const ( + irq_TIM1_BRK_TIM15 = 24 + irq_TIM1_UP_TIM16 = 25 + irq_TIM1_TRG_COM_TIM17 = 26 + irq_TIM1_CC = 27 + irq_TIM2 = 28 + irq_TIM3 = 29 + irq_TIM4 = 30 + irq_TIM5 = 50 + irq_TIM6 = 54 + irq_TIM7 = 55 + irq_TIM8_BRK = 43 + irq_TIM8_UP = 44 + irq_TIM8_TRG_COM = 45 + irq_TIM8_CC = 46 +) + +func (p Pin) getPort() *stm32.GPIO_Type { + switch p / 16 { + case 0: + return stm32.GPIOA + case 1: + return stm32.GPIOB + case 2: + return stm32.GPIOC + case 3: + return stm32.GPIOD + case 4: + return stm32.GPIOE + default: + panic("machine: unknown port") + } +} + +// enableClock enables the clock for this desired GPIO port. +func (p Pin) enableClock() { + switch p / 16 { + case 0: + stm32.RCC.AHB2ENR.SetBits(stm32.RCC_AHB2ENR_GPIOAEN) + case 1: + stm32.RCC.AHB2ENR.SetBits(stm32.RCC_AHB2ENR_GPIOBEN) + case 2: + stm32.RCC.AHB2ENR.SetBits(stm32.RCC_AHB2ENR_GPIOCEN) + case 3: + stm32.RCC.AHB2ENR.SetBits(stm32.RCC_AHB2ENR_GPIODEN) + case 4: + stm32.RCC.AHB2ENR.SetBits(stm32.RCC_AHB2ENR_GPIOEEN) + default: + panic("machine: unknown port") + } +} + +// Enable peripheral clock +func enableAltFuncClock(bus unsafe.Pointer) { + switch bus { + case unsafe.Pointer(stm32.PWR): // Power interface clock enable + stm32.RCC.APB1ENR1.SetBits(stm32.RCC_APB1ENR1_PWREN) + case unsafe.Pointer(stm32.I2C3): // I2C3 clock enable + stm32.RCC.APB1ENR1.SetBits(stm32.RCC_APB1ENR1_I2C3EN) + case unsafe.Pointer(stm32.I2C2): // I2C2 clock enable + stm32.RCC.APB1ENR1.SetBits(stm32.RCC_APB1ENR1_I2C2EN) + case unsafe.Pointer(stm32.I2C1): // I2C1 clock enable + stm32.RCC.APB1ENR1.SetBits(stm32.RCC_APB1ENR1_I2C1EN) + case unsafe.Pointer(stm32.UART4): // UART4 clock enable + stm32.RCC.APB1ENR1.SetBits(stm32.RCC_APB1ENR1_UART4EN) + case unsafe.Pointer(stm32.USART3): // USART3 clock enable + stm32.RCC.APB1ENR1.SetBits(stm32.RCC_APB1ENR1_USART3EN) + case unsafe.Pointer(stm32.USART2): // USART2 clock enable + stm32.RCC.APB1ENR1.SetBits(stm32.RCC_APB1ENR1_USART2EN) + case unsafe.Pointer(stm32.SPI3): // SPI3 clock enable + stm32.RCC.APB1ENR1.SetBits(stm32.RCC_APB1ENR1_SPI3EN) + case unsafe.Pointer(stm32.SPI2): // SPI2 clock enable + stm32.RCC.APB1ENR1.SetBits(stm32.RCC_APB1ENR1_SPI2EN) + case unsafe.Pointer(stm32.WWDG): // Window watchdog clock enable + stm32.RCC.APB1ENR1.SetBits(stm32.RCC_APB1ENR1_WWDGEN) + case unsafe.Pointer(stm32.TIM7): // TIM7 clock enable + stm32.RCC.APB1ENR1.SetBits(stm32.RCC_APB1ENR1_TIM7EN) + case unsafe.Pointer(stm32.TIM6): // TIM6 clock enable + stm32.RCC.APB1ENR1.SetBits(stm32.RCC_APB1ENR1_TIM6EN) + case unsafe.Pointer(stm32.TIM3): // TIM3 clock enable + stm32.RCC.APB1ENR1.SetBits(stm32.RCC_APB1ENR1_TIM3EN) + case unsafe.Pointer(stm32.TIM2): // TIM2 clock enable + stm32.RCC.APB1ENR1.SetBits(stm32.RCC_APB1ENR1_TIM2EN) + case unsafe.Pointer(stm32.LPTIM2): // LPTIM2 clock enable + stm32.RCC.APB1ENR2.SetBits(stm32.RCC_APB1ENR2_LPTIM2EN) + case unsafe.Pointer(stm32.LPUART1): // LPUART1 clock enable + stm32.RCC.APB1ENR2.SetBits(stm32.RCC_APB1ENR2_LPUART1EN) + case unsafe.Pointer(stm32.TIM16): // TIM16 clock enable + stm32.RCC.APB2ENR.SetBits(stm32.RCC_APB2ENR_TIM16EN) + case unsafe.Pointer(stm32.TIM15): // TIM15 clock enable + stm32.RCC.APB2ENR.SetBits(stm32.RCC_APB2ENR_TIM15EN) + case unsafe.Pointer(stm32.SYSCFG): // System configuration controller clock enable + stm32.RCC.APB2ENR.SetBits(stm32.RCC_APB2ENR_SYSCFGEN) + case unsafe.Pointer(stm32.SPI1): // SPI1 clock enable + stm32.RCC.APB2ENR.SetBits(stm32.RCC_APB2ENR_SPI1EN) + case unsafe.Pointer(stm32.USART1): // USART1 clock enable + stm32.RCC.APB2ENR.SetBits(stm32.RCC_APB2ENR_USART1EN) + case unsafe.Pointer(stm32.TIM1): // TIM1 clock enable + stm32.RCC.APB2ENR.SetBits(stm32.RCC_APB2ENR_TIM1EN) + } +} + +func handlePinInterrupt(pin uint8) { + if stm32.EXTI.PR1.HasBits(1 << pin) { + // Writing 1 to the pending register clears the + // pending flag for that bit + stm32.EXTI.PR1.Set(1 << pin) + + callback := pinCallbacks[pin] + if callback != nil { + callback(interruptPins[pin]) + } + } +} + +func (p Pin) registerInterrupt() interrupt.Interrupt { + pin := uint8(p) % 16 + + switch pin { + case 0: + return interrupt.New(stm32.IRQ_EXTI0, func(interrupt.Interrupt) { handlePinInterrupt(0) }) + case 1: + return interrupt.New(stm32.IRQ_EXTI1, func(interrupt.Interrupt) { handlePinInterrupt(1) }) + case 2: + return interrupt.New(stm32.IRQ_EXTI2, func(interrupt.Interrupt) { handlePinInterrupt(2) }) + case 3: + return interrupt.New(stm32.IRQ_EXTI3, func(interrupt.Interrupt) { handlePinInterrupt(3) }) + case 4: + return interrupt.New(stm32.IRQ_EXTI4, func(interrupt.Interrupt) { handlePinInterrupt(4) }) + case 5: + return interrupt.New(stm32.IRQ_EXTI9_5, func(interrupt.Interrupt) { handlePinInterrupt(5) }) + case 6: + return interrupt.New(stm32.IRQ_EXTI9_5, func(interrupt.Interrupt) { handlePinInterrupt(6) }) + case 7: + return interrupt.New(stm32.IRQ_EXTI9_5, func(interrupt.Interrupt) { handlePinInterrupt(7) }) + case 8: + return interrupt.New(stm32.IRQ_EXTI9_5, func(interrupt.Interrupt) { handlePinInterrupt(8) }) + case 9: + return interrupt.New(stm32.IRQ_EXTI9_5, func(interrupt.Interrupt) { handlePinInterrupt(9) }) + case 10: + return interrupt.New(stm32.IRQ_EXTI15_10, func(interrupt.Interrupt) { handlePinInterrupt(10) }) + case 11: + return interrupt.New(stm32.IRQ_EXTI15_10, func(interrupt.Interrupt) { handlePinInterrupt(11) }) + case 12: + return interrupt.New(stm32.IRQ_EXTI15_10, func(interrupt.Interrupt) { handlePinInterrupt(12) }) + case 13: + return interrupt.New(stm32.IRQ_EXTI15_10, func(interrupt.Interrupt) { handlePinInterrupt(13) }) + case 14: + return interrupt.New(stm32.IRQ_EXTI15_10, func(interrupt.Interrupt) { handlePinInterrupt(14) }) + case 15: + return interrupt.New(stm32.IRQ_EXTI15_10, func(interrupt.Interrupt) { handlePinInterrupt(15) }) + } + + return interrupt.Interrupt{} +} + +//---------- UART related code + +// Configure the UART. +func (uart *UART) configurePins(config UARTConfig) { + // enable the alternate functions on the TX and RX pins + config.TX.ConfigureAltFunc(PinConfig{Mode: PinModeUARTTX}, uart.TxAltFuncSelector) + config.RX.ConfigureAltFunc(PinConfig{Mode: PinModeUARTRX}, uart.RxAltFuncSelector) +} + +// UART baudrate calc based on the bus and clockspeed +// NOTE: keep this in sync with the runtime/runtime_stm32l5x2.go clock init code +func (uart *UART) getBaudRateDivisor(baudRate uint32) uint32 { + return (CPUFrequency() / baudRate) +} + +// Register names vary by ST processor, these are for STM L5 +func (uart *UART) setRegisters() { + uart.rxReg = &uart.Bus.RDR + uart.txReg = &uart.Bus.TDR + uart.statusReg = &uart.Bus.ISR + uart.txEmptyFlag = stm32.USART_ISR_TXE +} + +//---------- SPI related types and code + +// SPI on the STM32Fxxx using MODER / alternate function pins +type SPI struct { + Bus *stm32.SPI_Type + AltFuncSelector uint8 +} + +func (spi *SPI) config8Bits() { + // Set rx threshold to 8-bits, so RXNE flag is set for 1 byte + // (common STM32 SPI implementation does 8-bit transfers only) + spi.Bus.CR2.SetBits(stm32.SPI_CR2_FRXTH) +} + +// Set baud rate for SPI +func (spi *SPI) getBaudRate(config SPIConfig) uint32 { + var conf uint32 + + // Default + if config.Frequency == 0 { + config.Frequency = 4e6 + } + + localFrequency := config.Frequency + + // set frequency dependent on PCLK prescaler. Since these are rather weird + // speeds due to the CPU frequency, pick a range up to that frequency for + // clients to use more human-understandable numbers, e.g. nearest 100KHz + + // These are based on 80MHz peripheral clock frequency + switch { + case localFrequency < 312500: + conf = stm32.SPI_CR1_BR_Div256 + case localFrequency < 625000: + conf = stm32.SPI_CR1_BR_Div128 + case localFrequency < 1250000: + conf = stm32.SPI_CR1_BR_Div64 + case localFrequency < 2500000: + conf = stm32.SPI_CR1_BR_Div32 + case localFrequency < 5000000: + conf = stm32.SPI_CR1_BR_Div16 + case localFrequency < 10000000: + conf = stm32.SPI_CR1_BR_Div8 + // NOTE: many SPI components won't operate reliably (or at all) above 10MHz + // Check the datasheet of the part + case localFrequency < 20000000: + conf = stm32.SPI_CR1_BR_Div4 + case localFrequency < 40000000: + conf = stm32.SPI_CR1_BR_Div2 + default: + // None of the specific baudrates were selected; choose the lowest speed + conf = stm32.SPI_CR1_BR_Div256 + } + + return conf << stm32.SPI_CR1_BR_Pos +} + +// Configure SPI pins for input output and clock +func (spi *SPI) configurePins(config SPIConfig) { + config.SCK.ConfigureAltFunc(PinConfig{Mode: PinModeSPICLK}, spi.AltFuncSelector) + config.SDO.ConfigureAltFunc(PinConfig{Mode: PinModeSPISDO}, spi.AltFuncSelector) + config.SDI.ConfigureAltFunc(PinConfig{Mode: PinModeSPISDI}, spi.AltFuncSelector) +} + +//---------- Timer related code + +var ( + TIM1 = TIM{ + EnableRegister: &stm32.RCC.APB2ENR, + EnableFlag: stm32.RCC_APB2ENR_TIM1EN, + Device: stm32.TIM1, + Channels: [4]TimerChannel{ + TimerChannel{Pins: []PinFunction{ + {PA8, AF1_TIM1_2_LPTIM1}, + }}, + TimerChannel{Pins: []PinFunction{ + {PA9, AF1_TIM1_2_LPTIM1}, + }}, + TimerChannel{Pins: []PinFunction{ + {PA10, AF1_TIM1_2_LPTIM1}, + }}, + TimerChannel{Pins: []PinFunction{ + {PA11, AF1_TIM1_2_LPTIM1}, + }}, + }, + busFreq: APB2_TIM_FREQ, + } + + TIM2 = TIM{ + EnableRegister: &stm32.RCC.APB1ENR1, + EnableFlag: stm32.RCC_APB1ENR1_TIM2EN, + Device: stm32.TIM2, + Channels: [4]TimerChannel{ + TimerChannel{Pins: []PinFunction{ + {PA0, AF1_TIM1_2_LPTIM1}, + {PA5, AF1_TIM1_2_LPTIM1}, + {PA15, AF1_TIM1_2_LPTIM1}, + }}, + TimerChannel{Pins: []PinFunction{ + {PA1, AF1_TIM1_2_LPTIM1}, + {PB3, AF1_TIM1_2_LPTIM1}, + }}, + TimerChannel{Pins: []PinFunction{ + {PA2, AF1_TIM1_2_LPTIM1}, + }}, + TimerChannel{Pins: []PinFunction{ + {PA3, AF1_TIM1_2_LPTIM1}, + }}, + }, + busFreq: APB1_TIM_FREQ, + } + + TIM3 = TIM{ + EnableRegister: &stm32.RCC.APB1ENR1, + EnableFlag: stm32.RCC_APB1ENR1_TIM3EN, + Device: stm32.TIM3, + Channels: [4]TimerChannel{ + TimerChannel{Pins: []PinFunction{}}, + TimerChannel{Pins: []PinFunction{}}, + TimerChannel{Pins: []PinFunction{}}, + TimerChannel{Pins: []PinFunction{}}, + }, + busFreq: APB1_TIM_FREQ, + } + + TIM6 = TIM{ + EnableRegister: &stm32.RCC.APB1ENR1, + EnableFlag: stm32.RCC_APB1ENR1_TIM6EN, + Device: stm32.TIM6, + Channels: [4]TimerChannel{ + TimerChannel{Pins: []PinFunction{}}, + TimerChannel{Pins: []PinFunction{}}, + TimerChannel{Pins: []PinFunction{}}, + TimerChannel{Pins: []PinFunction{}}, + }, + busFreq: APB1_TIM_FREQ, + } + + TIM7 = TIM{ + EnableRegister: &stm32.RCC.APB1ENR1, + EnableFlag: stm32.RCC_APB1ENR1_TIM7EN, + Device: stm32.TIM7, + Channels: [4]TimerChannel{ + TimerChannel{Pins: []PinFunction{}}, + TimerChannel{Pins: []PinFunction{}}, + TimerChannel{Pins: []PinFunction{}}, + TimerChannel{Pins: []PinFunction{}}, + }, + busFreq: APB1_TIM_FREQ, + } + + TIM15 = TIM{ + EnableRegister: &stm32.RCC.APB2ENR, + EnableFlag: stm32.RCC_APB2ENR_TIM15EN, + Device: stm32.TIM15, + Channels: [4]TimerChannel{ + TimerChannel{Pins: []PinFunction{ + {PA2, AF14_TIM2_15_16_LPTIM2}, + }}, + TimerChannel{Pins: []PinFunction{ + {PA3, AF14_TIM2_15_16_LPTIM2}, + }}, + TimerChannel{Pins: []PinFunction{}}, + TimerChannel{Pins: []PinFunction{}}, + }, + busFreq: APB2_TIM_FREQ, + } + + TIM16 = TIM{ + EnableRegister: &stm32.RCC.APB2ENR, + EnableFlag: stm32.RCC_APB2ENR_TIM16EN, + Device: stm32.TIM16, + Channels: [4]TimerChannel{ + TimerChannel{Pins: []PinFunction{ + {PA6, AF14_TIM2_15_16_LPTIM2}, + }}, + TimerChannel{Pins: []PinFunction{}}, + TimerChannel{Pins: []PinFunction{}}, + TimerChannel{Pins: []PinFunction{}}, + }, + busFreq: APB2_TIM_FREQ, + } +) + +func (t *TIM) registerUPInterrupt() interrupt.Interrupt { + switch t { + case &TIM1: + return interrupt.New(irq_TIM1_UP_TIM16, TIM1.handleUPInterrupt) + case &TIM2: + return interrupt.New(irq_TIM2, TIM2.handleUPInterrupt) + case &TIM3: + return interrupt.New(irq_TIM3, TIM3.handleUPInterrupt) + case &TIM6: + return interrupt.New(irq_TIM6, TIM6.handleUPInterrupt) + case &TIM7: + return interrupt.New(irq_TIM7, TIM7.handleUPInterrupt) + case &TIM15: + return interrupt.New(irq_TIM1_BRK_TIM15, TIM15.handleUPInterrupt) + case &TIM16: + return interrupt.New(irq_TIM1_UP_TIM16, TIM16.handleUPInterrupt) + } + + return interrupt.Interrupt{} +} + +func (t *TIM) registerOCInterrupt() interrupt.Interrupt { + switch t { + case &TIM1: + return interrupt.New(irq_TIM1_CC, TIM1.handleUPInterrupt) + case &TIM2: + return interrupt.New(irq_TIM2, TIM2.handleOCInterrupt) + case &TIM3: + return interrupt.New(irq_TIM3, TIM3.handleOCInterrupt) + case &TIM6: + return interrupt.New(irq_TIM6, TIM6.handleOCInterrupt) + case &TIM7: + return interrupt.New(irq_TIM7, TIM7.handleOCInterrupt) + case &TIM15: + return interrupt.New(irq_TIM1_BRK_TIM15, TIM15.handleOCInterrupt) + case &TIM16: + return interrupt.New(irq_TIM1_UP_TIM16, TIM16.handleOCInterrupt) + } + + return interrupt.Interrupt{} +} + +func (t *TIM) enableMainOutput() { + // nothing to do - no BDTR register +} + +type arrtype = uint32 +type arrRegType = volatile.Register32 + +const ( + ARR_MAX = 0x10000 + PSC_MAX = 0x10000 +) + +func initRNG() { + stm32.RCC.CRRCR.SetBits(stm32.RCC_CRRCR_HSI48ON) + for !stm32.RCC.CRRCR.HasBits(stm32.RCC_CRRCR_HSI48RDY) { + } + + stm32.RCC.AHB2ENR.SetBits(stm32.RCC_AHB2ENR_RNGEN) + stm32.RNG.CR.SetBits(stm32.RNG_CR_RNGEN) +} + +//---------- Flash related code + +const eraseBlockSizeValue = 2048 + +// see RM0394 page 83 +// eraseBlock of the passed in block number +func eraseBlock(block uint32) error { + waitUntilFlashDone() + + // clear any previous errors + stm32.FLASH.SR.SetBits(0x3FA) + + // page erase operation + stm32.FLASH.SetCR_PER(1) + defer stm32.FLASH.SetCR_PER(0) + + // set the page to be erased + stm32.FLASH.SetCR_PNB(block) + + // start the page erase + stm32.FLASH.SetCR_START(1) + + waitUntilFlashDone() + + if err := checkError(); err != nil { + return err + } + + return nil +} + +const writeBlockSize = 8 + +// see RM0394 page 84 +// It is only possible to program double word (2 x 32-bit data). +func writeFlashData(address uintptr, data []byte) (int, error) { + if len(data)%writeBlockSize != 0 { + return 0, errFlashInvalidWriteLength + } + + waitUntilFlashDone() + + // clear any previous errors + stm32.FLASH.SR.SetBits(0x3FA) + + for j := 0; j < len(data); j += writeBlockSize { + // start page write operation + stm32.FLASH.SetCR_PG(1) + + // write second word using double-word high order word + *(*uint32)(unsafe.Pointer(address)) = binary.LittleEndian.Uint32(data[j : j+writeBlockSize/2]) + + address += writeBlockSize / 2 + + // write first word using double-word low order word + *(*uint32)(unsafe.Pointer(address)) = binary.LittleEndian.Uint32(data[j+writeBlockSize/2 : j+writeBlockSize]) + + waitUntilFlashDone() + + if err := checkError(); err != nil { + return j, err + } + + // end flash write + stm32.FLASH.SetCR_PG(0) + address += writeBlockSize / 2 + } + + return len(data), nil +} + +func waitUntilFlashDone() { + for stm32.FLASH.GetSR_BSY() != 0 { + } +} + +var ( + errFlashPGS = errors.New("errFlashPGS") + errFlashSIZE = errors.New("errFlashSIZE") + errFlashPGA = errors.New("errFlashPGA") + errFlashWRP = errors.New("errFlashWRP") + errFlashPROG = errors.New("errFlashPROG") +) + +func checkError() error { + switch { + case stm32.FLASH.GetSR_PGSERR() != 0: + return errFlashPGS + case stm32.FLASH.GetSR_SIZERR() != 0: + return errFlashSIZE + case stm32.FLASH.GetSR_PGAERR() != 0: + return errFlashPGA + case stm32.FLASH.GetSR_WRPERR() != 0: + return errFlashWRP + case stm32.FLASH.GetSR_PROGERR() != 0: + return errFlashPROG + } + + return nil +} diff --git a/emb/machine/machine_stm32l4x2.go b/emb/machine/machine_stm32l4x2.go new file mode 100644 index 00000000..142a8f5f --- /dev/null +++ b/emb/machine/machine_stm32l4x2.go @@ -0,0 +1,36 @@ +//go:build stm32l4x2 + +package machine + +// Peripheral abstraction layer for the stm32l4x2 + +func CPUFrequency() uint32 { + return 80000000 +} + +// Internal use: configured speed of the APB1 and APB2 timers, this should be kept +// in sync with any changes to runtime package which configures the oscillators +// and clock frequencies +const APB1_TIM_FREQ = 80e6 // 80MHz +const APB2_TIM_FREQ = 80e6 // 80MHz + +//---------- I2C related code + +// Gets the value for TIMINGR register +func (i2c *I2C) getFreqRange(br uint32) uint32 { + // These are 'magic' values calculated by STM32CubeMX + // for 80MHz PCLK1. + // TODO: Do calculations based on PCLK1 + switch br { + case 10 * KHz: + return 0xF010F3FE + case 100 * KHz: + return 0x10909CEC + case 400 * KHz: + return 0x00702991 + case 500 * KHz: + return 0x00300E84 + default: + return 0 + } +} diff --git a/emb/machine/machine_stm32l4x5.go b/emb/machine/machine_stm32l4x5.go new file mode 100644 index 00000000..c8c550c3 --- /dev/null +++ b/emb/machine/machine_stm32l4x5.go @@ -0,0 +1,36 @@ +//go:build stm32l4x5 + +package machine + +// Peripheral abstraction layer for the stm32l4x5 + +func CPUFrequency() uint32 { + return 120e6 +} + +// Internal use: configured speed of the APB1 and APB2 timers, this should be kept +// in sync with any changes to runtime package which configures the oscillators +// and clock frequencies +const APB1_TIM_FREQ = 120e6 // 120MHz +const APB2_TIM_FREQ = 120e6 // 120MHz + +//---------- I2C related code + +// Gets the value for TIMINGR register +func (i2c *I2C) getFreqRange(br uint32) uint32 { + // This is a 'magic' value calculated by STM32CubeMX + // for 120MHz PCLK1. + // TODO: Do calculations based on PCLK1 + switch br { + case 10 * KHz: + return 0x0 // does this even work? zero is weird here. + case 100 * KHz: + return 0x307075B1 + case 400 * KHz: + return 0x00B03FDB + case 500 * KHz: + return 0x005017C7 + default: + return 0 + } +} diff --git a/emb/machine/machine_stm32l4x6.go b/emb/machine/machine_stm32l4x6.go new file mode 100644 index 00000000..de878ebe --- /dev/null +++ b/emb/machine/machine_stm32l4x6.go @@ -0,0 +1,36 @@ +//go:build stm32l4x6 + +package machine + +// Peripheral abstraction layer for the stm32l4x6 + +func CPUFrequency() uint32 { + return 80e6 +} + +// Internal use: configured speed of the APB1 and APB2 timers, this should be kept +// in sync with any changes to runtime package which configures the oscillators +// and clock frequencies +const APB1_TIM_FREQ = 80e6 // 80MHz +const APB2_TIM_FREQ = 80e6 // 80MHz + +//---------- I2C related code + +// Gets the value for TIMINGR register +func (i2c *I2C) getFreqRange(br uint32) uint32 { + // This is a 'magic' value calculated by STM32CubeMX + // for 80MHz PCLK1. + // TODO: Do calculations based on PCLK1 + switch br { + case 10 * KHz: + return 0xF010F3FE + case 100 * KHz: + return 0x10909CEC + case 400 * KHz: + return 0x00702991 + case 500 * KHz: + return 0x00300E84 + default: + return 0 + } +} diff --git a/emb/machine/machine_stm32l5.go b/emb/machine/machine_stm32l5.go new file mode 100644 index 00000000..faa583c9 --- /dev/null +++ b/emb/machine/machine_stm32l5.go @@ -0,0 +1,559 @@ +//go:build stm32l5 + +package machine + +// Peripheral abstraction layer for the stm32l5 + +import ( + "device/stm32" + "runtime/interrupt" + "runtime/volatile" + "unsafe" +) + +var deviceIDAddr = []uintptr{0x0BFA0590, 0x0BFA0594, 0x0BFA0598} + +const ( + AF0_SYSTEM = 0 + AF1_TIM1_2_5_8_LPTIM1 = 1 + AF2_TIM1_2_3_4_5_LPTIM3 = 2 + AF3_SPI2_SAI1_I2C4_USART2_TIM1_8_OCTOSPI1 = 3 + AF4_I2C1_2_3_4 = 4 + AF5_SPI1_2_3_I2C4_DFSDM1_OCTOSPI1 = 5 + AF6_SPI3_I2C3_DFSDM1_COMP1 = 6 + AF7_USART1_2_3 = 7 + AF8_UART4_5_LPUART1_SDMMC1 = 8 + AF9_FDCAN1_TSC = 9 + AF10_USB_OCTOSPI1 = 10 + AF11_UCPD1 = 11 + AF12_SDMMC1_COMP1_2_TIM1_8_FMC = 12 + AF13_SAI1_2_TIM8 = 13 + AF14_TIM2_8_15_16_17_LPTIM2 = 14 + AF15_EVENTOUT = 15 +) + +const ( + PA0 = portA + 0 + PA1 = portA + 1 + PA2 = portA + 2 + PA3 = portA + 3 + PA4 = portA + 4 + PA5 = portA + 5 + PA6 = portA + 6 + PA7 = portA + 7 + PA8 = portA + 8 + PA9 = portA + 9 + PA10 = portA + 10 + PA11 = portA + 11 + PA12 = portA + 12 + PA13 = portA + 13 + PA14 = portA + 14 + PA15 = portA + 15 + + PB0 = portB + 0 + PB1 = portB + 1 + PB2 = portB + 2 + PB3 = portB + 3 + PB4 = portB + 4 + PB5 = portB + 5 + PB6 = portB + 6 + PB7 = portB + 7 + PB8 = portB + 8 + PB9 = portB + 9 + PB10 = portB + 10 + PB11 = portB + 11 + PB12 = portB + 12 + PB13 = portB + 13 + PB14 = portB + 14 + PB15 = portB + 15 + + PC0 = portC + 0 + PC1 = portC + 1 + PC2 = portC + 2 + PC3 = portC + 3 + PC4 = portC + 4 + PC5 = portC + 5 + PC6 = portC + 6 + PC7 = portC + 7 + PC8 = portC + 8 + PC9 = portC + 9 + PC10 = portC + 10 + PC11 = portC + 11 + PC12 = portC + 12 + PC13 = portC + 13 + PC14 = portC + 14 + PC15 = portC + 15 + + PD0 = portD + 0 + PD1 = portD + 1 + PD2 = portD + 2 + PD3 = portD + 3 + PD4 = portD + 4 + PD5 = portD + 5 + PD6 = portD + 6 + PD7 = portD + 7 + PD8 = portD + 8 + PD9 = portD + 9 + PD10 = portD + 10 + PD11 = portD + 11 + PD12 = portD + 12 + PD13 = portD + 13 + PD14 = portD + 14 + PD15 = portD + 15 + + PE0 = portE + 0 + PE1 = portE + 1 + PE2 = portE + 2 + PE3 = portE + 3 + PE4 = portE + 4 + PE5 = portE + 5 + PE6 = portE + 6 + PE7 = portE + 7 + PE8 = portE + 8 + PE9 = portE + 9 + PE10 = portE + 10 + PE11 = portE + 11 + PE12 = portE + 12 + PE13 = portE + 13 + PE14 = portE + 14 + PE15 = portE + 15 + + PF0 = portF + 0 + PF1 = portF + 1 + PF2 = portF + 2 + PF3 = portF + 3 + PF4 = portF + 4 + PF5 = portF + 5 + PF6 = portF + 6 + PF7 = portF + 7 + PF8 = portF + 8 + PF9 = portF + 9 + PF10 = portF + 10 + PF11 = portF + 11 + PF12 = portF + 12 + PF13 = portF + 13 + PF14 = portF + 14 + PF15 = portF + 15 + + PG0 = portG + 0 + PG1 = portG + 1 + PG2 = portG + 2 + PG3 = portG + 3 + PG4 = portG + 4 + PG5 = portG + 5 + PG6 = portG + 6 + PG7 = portG + 7 + PG8 = portG + 8 + PG9 = portG + 9 + PG10 = portG + 10 + PG11 = portG + 11 + PG12 = portG + 12 + PG13 = portG + 13 + PG14 = portG + 14 + PG15 = portG + 15 + + PH0 = portH + 0 + PH1 = portH + 1 +) + +func (p Pin) getPort() *stm32.GPIO_Type { + switch p / 16 { + case 0: + return stm32.GPIOA + case 1: + return stm32.GPIOB + case 2: + return stm32.GPIOC + case 3: + return stm32.GPIOD + case 4: + return stm32.GPIOE + case 5: + return stm32.GPIOF + case 6: + return stm32.GPIOG + case 7: + return stm32.GPIOH + default: + panic("machine: unknown port") + } +} + +// enableClock enables the clock for this desired GPIO port. +func (p Pin) enableClock() { + switch p / 16 { + case 0: + stm32.RCC.AHB2ENR.SetBits(stm32.RCC_AHB2ENR_GPIOAEN) + case 1: + stm32.RCC.AHB2ENR.SetBits(stm32.RCC_AHB2ENR_GPIOBEN) + case 2: + stm32.RCC.AHB2ENR.SetBits(stm32.RCC_AHB2ENR_GPIOCEN) + case 3: + stm32.RCC.AHB2ENR.SetBits(stm32.RCC_AHB2ENR_GPIODEN) + case 4: + stm32.RCC.AHB2ENR.SetBits(stm32.RCC_AHB2ENR_GPIOEEN) + case 5: + stm32.RCC.AHB2ENR.SetBits(stm32.RCC_AHB2ENR_GPIOFEN) + case 6: + stm32.RCC.AHB2ENR.SetBits(stm32.RCC_AHB2ENR_GPIOGEN) + case 7: + stm32.RCC.AHB2ENR.SetBits(stm32.RCC_AHB2ENR_GPIOHEN) + default: + panic("machine: unknown port") + } +} + +// Enable peripheral clock +func enableAltFuncClock(bus unsafe.Pointer) { + switch bus { + case unsafe.Pointer(stm32.DAC): // DAC interface clock enable + stm32.RCC.APB1ENR1.SetBits(stm32.RCC_APB1ENR1_DAC1EN) + case unsafe.Pointer(stm32.PWR): // Power interface clock enable + stm32.RCC.APB1ENR1.SetBits(stm32.RCC_APB1ENR1_PWREN) + case unsafe.Pointer(stm32.I2C3): // I2C3 clock enable + stm32.RCC.APB1ENR1.SetBits(stm32.RCC_APB1ENR1_I2C3EN) + case unsafe.Pointer(stm32.I2C2): // I2C2 clock enable + stm32.RCC.APB1ENR1.SetBits(stm32.RCC_APB1ENR1_I2C2EN) + case unsafe.Pointer(stm32.I2C1): // I2C1 clock enable + stm32.RCC.APB1ENR1.SetBits(stm32.RCC_APB1ENR1_I2C1EN) + case unsafe.Pointer(stm32.UART5): // UART5 clock enable + stm32.RCC.APB1ENR1.SetBits(stm32.RCC_APB1ENR1_UART5EN) + case unsafe.Pointer(stm32.UART4): // UART4 clock enable + stm32.RCC.APB1ENR1.SetBits(stm32.RCC_APB1ENR1_UART4EN) + case unsafe.Pointer(stm32.USART3): // USART3 clock enable + stm32.RCC.APB1ENR1.SetBits(stm32.RCC_APB1ENR1_USART3EN) + case unsafe.Pointer(stm32.USART2): // USART2 clock enable + stm32.RCC.APB1ENR1.SetBits(stm32.RCC_APB1ENR1_USART2EN) + case unsafe.Pointer(stm32.SPI3): // SPI3 clock enable + stm32.RCC.APB1ENR1.SetBits(stm32.RCC_APB1ENR1_SP3EN) + case unsafe.Pointer(stm32.SPI2): // SPI2 clock enable + stm32.RCC.APB1ENR1.SetBits(stm32.RCC_APB1ENR1_SPI2EN) + case unsafe.Pointer(stm32.WWDG): // Window watchdog clock enable + stm32.RCC.APB1ENR1.SetBits(stm32.RCC_APB1ENR1_WWDGEN) + case unsafe.Pointer(stm32.TIM7): // TIM7 clock enable + stm32.RCC.APB1ENR1.SetBits(stm32.RCC_APB1ENR1_TIM7EN) + case unsafe.Pointer(stm32.TIM6): // TIM6 clock enable + stm32.RCC.APB1ENR1.SetBits(stm32.RCC_APB1ENR1_TIM6EN) + case unsafe.Pointer(stm32.TIM5): // TIM5 clock enable + stm32.RCC.APB1ENR1.SetBits(stm32.RCC_APB1ENR1_TIM5EN) + case unsafe.Pointer(stm32.TIM4): // TIM4 clock enable + stm32.RCC.APB1ENR1.SetBits(stm32.RCC_APB1ENR1_TIM4EN) + case unsafe.Pointer(stm32.TIM3): // TIM3 clock enable + stm32.RCC.APB1ENR1.SetBits(stm32.RCC_APB1ENR1_TIM3EN) + case unsafe.Pointer(stm32.TIM2): // TIM2 clock enable + stm32.RCC.APB1ENR1.SetBits(stm32.RCC_APB1ENR1_TIM2EN) + case unsafe.Pointer(stm32.UCPD1): // UCPD1 clock enable + stm32.RCC.APB1ENR2.SetBits(stm32.RCC_APB1ENR2_UCPD1EN) + case unsafe.Pointer(stm32.FDCAN1): // FDCAN1 clock enable + stm32.RCC.APB1ENR2.SetBits(stm32.RCC_APB1ENR2_FDCAN1EN) + case unsafe.Pointer(stm32.LPTIM3): // LPTIM3 clock enable + stm32.RCC.APB1ENR2.SetBits(stm32.RCC_APB1ENR2_LPTIM3EN) + case unsafe.Pointer(stm32.LPTIM2): // LPTIM2 clock enable + stm32.RCC.APB1ENR2.SetBits(stm32.RCC_APB1ENR2_LPTIM2EN) + case unsafe.Pointer(stm32.I2C4): // I2C4 clock enable + stm32.RCC.APB1ENR2.SetBits(stm32.RCC_APB1ENR2_I2C4EN) + case unsafe.Pointer(stm32.LPUART1): // LPUART1 clock enable + stm32.RCC.APB1ENR2.SetBits(stm32.RCC_APB1ENR2_LPUART1EN) + case unsafe.Pointer(stm32.TIM17): // TIM17 clock enable + stm32.RCC.APB2ENR.SetBits(stm32.RCC_APB2ENR_TIM17EN) + case unsafe.Pointer(stm32.TIM16): // TIM16 clock enable + stm32.RCC.APB2ENR.SetBits(stm32.RCC_APB2ENR_TIM16EN) + case unsafe.Pointer(stm32.TIM15): // TIM15 clock enable + stm32.RCC.APB2ENR.SetBits(stm32.RCC_APB2ENR_TIM15EN) + case unsafe.Pointer(stm32.SYSCFG): // System configuration controller clock enable + stm32.RCC.APB2ENR.SetBits(stm32.RCC_APB2ENR_SYSCFGEN) + case unsafe.Pointer(stm32.SPI1): // SPI1 clock enable + stm32.RCC.APB2ENR.SetBits(stm32.RCC_APB2ENR_SPI1EN) + case unsafe.Pointer(stm32.USART1): // USART1 clock enable + stm32.RCC.APB2ENR.SetBits(stm32.RCC_APB2ENR_USART1EN) + case unsafe.Pointer(stm32.TIM8): // TIM8 clock enable + stm32.RCC.APB2ENR.SetBits(stm32.RCC_APB2ENR_TIM8EN) + case unsafe.Pointer(stm32.TIM1): // TIM1 clock enable + stm32.RCC.APB2ENR.SetBits(stm32.RCC_APB2ENR_TIM1EN) + } +} + +func (p Pin) registerInterrupt() interrupt.Interrupt { + pin := uint8(p) % 16 + + switch pin { + case 0: + return interrupt.New(stm32.IRQ_EXTI0, func(interrupt.Interrupt) { handlePinInterrupt(0) }) + case 1: + return interrupt.New(stm32.IRQ_EXTI1, func(interrupt.Interrupt) { handlePinInterrupt(1) }) + case 2: + return interrupt.New(stm32.IRQ_EXTI2, func(interrupt.Interrupt) { handlePinInterrupt(2) }) + case 3: + return interrupt.New(stm32.IRQ_EXTI3, func(interrupt.Interrupt) { handlePinInterrupt(3) }) + case 4: + return interrupt.New(stm32.IRQ_EXTI4, func(interrupt.Interrupt) { handlePinInterrupt(4) }) + case 5: + return interrupt.New(stm32.IRQ_EXTI5, func(interrupt.Interrupt) { handlePinInterrupt(5) }) + case 6: + return interrupt.New(stm32.IRQ_EXTI6, func(interrupt.Interrupt) { handlePinInterrupt(6) }) + case 7: + return interrupt.New(stm32.IRQ_EXTI7, func(interrupt.Interrupt) { handlePinInterrupt(7) }) + case 8: + return interrupt.New(stm32.IRQ_EXTI8, func(interrupt.Interrupt) { handlePinInterrupt(8) }) + case 9: + return interrupt.New(stm32.IRQ_EXTI9, func(interrupt.Interrupt) { handlePinInterrupt(9) }) + case 10: + return interrupt.New(stm32.IRQ_EXTI10, func(interrupt.Interrupt) { handlePinInterrupt(10) }) + case 11: + return interrupt.New(stm32.IRQ_EXTI11, func(interrupt.Interrupt) { handlePinInterrupt(11) }) + case 12: + return interrupt.New(stm32.IRQ_EXTI12, func(interrupt.Interrupt) { handlePinInterrupt(12) }) + case 13: + return interrupt.New(stm32.IRQ_EXTI13, func(interrupt.Interrupt) { handlePinInterrupt(13) }) + case 14: + return interrupt.New(stm32.IRQ_EXTI14, func(interrupt.Interrupt) { handlePinInterrupt(14) }) + case 15: + return interrupt.New(stm32.IRQ_EXTI15, func(interrupt.Interrupt) { handlePinInterrupt(15) }) + } + + return interrupt.Interrupt{} +} + +func handlePinInterrupt(pin uint8) { + // The pin abstraction doesn't differentiate pull-up + // events from pull-down events, so combine them to + // a single call here. + + if stm32.EXTI.RPR1.HasBits(1< clock { + freq = clock + } + + // calculate the exact clock divisor (freq=clock/div -> div=clock/freq). + // truncation is fine, since it produces a less-than-or-equal divisor, and + // thus a greater-than-or-equal frequency. + // divisors only come in consecutive powers of 2, so we can use log2 (or, + // equivalently, bits.Len - 1) to convert to respective enum value. + div := bits.Len32(clock/freq) - 1 + + // but DIV1 (2^0) is not permitted, as the least divisor is DIV2 (2^1), so + // subtract 1 from the log2 value, keeping a lower bound of 0 + if div < 0 { + div = 0 + } else if div > 0 { + div-- + } + + // finally, shift the enumerated value into position for SPI CR1 + return uint32(div) << stm32.SPI_CR1_BR_Pos +} + +//---------- I2C related code + +// Gets the value for TIMINGR register +func (i2c *I2C) getFreqRange(br uint32) uint32 { + // This is a 'magic' value calculated by STM32CubeMX + // for 48Mhz PCLK1. + // TODO: Do calculations based on PCLK1 + switch br { + case 10 * KHz: + return 0x9010DEFF + case 100 * KHz: + return 0x20303E5D + case 400 * KHz: + return 0x2010091A + case 500 * KHz: + return 0x00201441 + default: + return 0 + } +} + +//---------- UART related code + +// Configure the UART. +func (uart UART) configurePins(config UARTConfig) { + // enable the alternate functions on the TX and RX pins + config.TX.ConfigureAltFunc(PinConfig{Mode: PinModeUARTTX}, uart.TxAltFuncSelector) + config.RX.ConfigureAltFunc(PinConfig{Mode: PinModeUARTRX}, uart.RxAltFuncSelector) +} + +// UART baudrate calc based on the bus and clockspeed +// NOTE: keep this in sync with the runtime/runtime_stm32wle5.go clock init code +func (uart *UART) getBaudRateDivisor(baudRate uint32) uint32 { + var br uint32 + uartClock := CPUFrequency() // No Prescaler configuration + br = uint32((uartClock + baudRate/2) / baudRate) + return (br) +} + +// Register names vary by ST processor, these are for STM L5 +func (uart *UART) setRegisters() { + uart.rxReg = &uart.Bus.RDR + uart.txReg = &uart.Bus.TDR + uart.statusReg = &uart.Bus.ISR + uart.txEmptyFlag = stm32.USART_ISR_TXFNF //(TXFNF == TXE == bit 7, but depends alternate RM0461/1094) +} + +//---------- Timer related code + +var ( + TIM1 = TIM{ + EnableRegister: &stm32.RCC.APB2ENR, + EnableFlag: stm32.RCC_APB2ENR_TIM1EN, + Device: stm32.TIM1, + Channels: [4]TimerChannel{ + TimerChannel{Pins: []PinFunction{{PA8, AF1_TIM1_2_LPTIM1}}}, + TimerChannel{Pins: []PinFunction{{PA9, AF1_TIM1_2_LPTIM1}}}, + TimerChannel{Pins: []PinFunction{{PA10, AF1_TIM1_2_LPTIM1}}}, + TimerChannel{Pins: []PinFunction{{PA11, AF1_TIM1_2_LPTIM1}}}, + }, + busFreq: APB2_TIM_FREQ, + } + TIM2 = TIM{ + EnableRegister: &stm32.RCC.APB1ENR1, + EnableFlag: stm32.RCC_APB1ENR1_TIM2EN, + Device: stm32.TIM2, + Channels: [4]TimerChannel{ + TimerChannel{Pins: []PinFunction{{PA0, AF1_TIM1_2_LPTIM1}, {PA5, AF1_TIM1_2_LPTIM1}, {PA15, AF1_TIM1_2_LPTIM1}}}, + TimerChannel{Pins: []PinFunction{{PA1, AF1_TIM1_2_LPTIM1}, {PB3, AF1_TIM1_2_LPTIM1}}}, + TimerChannel{Pins: []PinFunction{{PA2, AF1_TIM1_2_LPTIM1}, {PB10, AF1_TIM1_2_LPTIM1}}}, + TimerChannel{Pins: []PinFunction{{PA3, AF1_TIM1_2_LPTIM1}, {PB11, AF1_TIM1_2_LPTIM1}}}, + }, + busFreq: APB1_TIM_FREQ, + } + TIM16 = TIM{ + EnableRegister: &stm32.RCC.APB2ENR, + EnableFlag: stm32.RCC_APB2ENR_TIM16EN, + Device: stm32.TIM16, + Channels: [4]TimerChannel{ + TimerChannel{Pins: []PinFunction{{PA6, AF14_TIM2_16_17_LPTIM2}}}, + TimerChannel{Pins: []PinFunction{}}, + TimerChannel{Pins: []PinFunction{}}, + TimerChannel{Pins: []PinFunction{}}, + }, + busFreq: APB2_TIM_FREQ, + } + TIM17 = TIM{ + EnableRegister: &stm32.RCC.APB2ENR, + EnableFlag: stm32.RCC_APB2ENR_TIM17EN, + Device: stm32.TIM17, + Channels: [4]TimerChannel{ + TimerChannel{Pins: []PinFunction{{PA7, AF1_TIM1_2_LPTIM1}, {PB9, AF1_TIM1_2_LPTIM1}}}, + TimerChannel{Pins: []PinFunction{}}, + TimerChannel{Pins: []PinFunction{}}, + TimerChannel{Pins: []PinFunction{}}, + }, + busFreq: APB2_TIM_FREQ, + } +) + +func (t *TIM) registerUPInterrupt() interrupt.Interrupt { + switch t { + case &TIM1: + return interrupt.New(stm32.IRQ_TIM1_UP, TIM1.handleUPInterrupt) + case &TIM2: + return interrupt.New(stm32.IRQ_TIM2, TIM2.handleUPInterrupt) + case &TIM16: + return interrupt.New(stm32.IRQ_TIM16, TIM16.handleUPInterrupt) + case &TIM17: + return interrupt.New(stm32.IRQ_TIM17, TIM17.handleUPInterrupt) + } + + return interrupt.Interrupt{} +} + +func (t *TIM) registerOCInterrupt() interrupt.Interrupt { + switch t { + case &TIM1: + return interrupt.New(stm32.IRQ_TIM1_CC, TIM1.handleOCInterrupt) + case &TIM2: + return interrupt.New(stm32.IRQ_TIM2, TIM2.handleOCInterrupt) + case &TIM16: + return interrupt.New(stm32.IRQ_TIM16, TIM16.handleOCInterrupt) + case &TIM17: + return interrupt.New(stm32.IRQ_TIM17, TIM17.handleOCInterrupt) + } + + return interrupt.Interrupt{} +} + +func (t *TIM) enableMainOutput() { + t.Device.BDTR.SetBits(stm32.TIM_BDTR_MOE) +} + +func initRNG() { + stm32.RCC.AHB3ENR.SetBits(stm32.RCC_AHB3ENR_RNGEN) + + // Enable RNG with config.A (See RM0453 22.6.2) + stm32.RNG.CR.Set(0x40F00D40) // RNG Config. A + stm32.RNG.HTCR.Set(0x17590ABC) // MAGIC NUMBER + stm32.RNG.HTCR.Set(0x0000AA74) // HTCR VALUE + stm32.RNG.CR.Set(0x00F00D4C) // CONFIG A + RNG_EN=1 + IE=1 +} + +//---------- + +type arrtype = uint32 +type arrRegType = volatile.Register32 + +const ( + ARR_MAX = 0x10000 + PSC_MAX = 0x10000 +) + +//---------- Flash related code + +const eraseBlockSizeValue = 2048 + +// eraseBlock of the passed in block number +func eraseBlock(block uint32) error { + waitUntilFlashDone() + + // check if operation is allowed. + if stm32.FLASH.GetSR_PESD() != 0 { + return errFlashCannotErasePage + } + + // clear any previous errors + stm32.FLASH.SR.SetBits(0x3FA) + + // page erase operation + stm32.FLASH.SetCR_PER(1) + defer stm32.FLASH.SetCR_PER(0) + + // set the address to the page to be written + stm32.FLASH.SetCR_PNB(block) + defer stm32.FLASH.SetCR_PNB(0) + + // start the page erase + stm32.FLASH.SetCR_STRT(1) + + waitUntilFlashDone() + + if err := checkError(); err != nil { + return err + } + + return nil +} + +const writeBlockSize = 8 + +func writeFlashData(address uintptr, data []byte) (int, error) { + if len(data)%writeBlockSize != 0 { + return 0, errFlashInvalidWriteLength + } + + waitUntilFlashDone() + + // check if operation is allowed + if stm32.FLASH.GetSR_PESD() != 0 { + return 0, errFlashNotAllowedWriteData + } + + // clear any previous errors + stm32.FLASH.SR.SetBits(0x3FA) + + for j := 0; j < len(data); j += writeBlockSize { + // start page write operation + stm32.FLASH.SetCR_PG(1) + + // write first word using double-word high order word + *(*uint32)(unsafe.Pointer(address)) = binary.LittleEndian.Uint32(data[j : j+writeBlockSize/2]) + + address += writeBlockSize / 2 + + // write second word using double-word low order word + *(*uint32)(unsafe.Pointer(address)) = binary.LittleEndian.Uint32(data[j+writeBlockSize/2 : j+writeBlockSize]) + + waitUntilFlashDone() + + if err := checkError(); err != nil { + return j, err + } + + // end flash write + stm32.FLASH.SetCR_PG(0) + address += writeBlockSize / 2 + } + + return len(data), nil +} + +func waitUntilFlashDone() { + for stm32.FLASH.GetSR_BSY() != 0 { + } + + for stm32.FLASH.GetSR_CFGBSY() != 0 { + } +} + +var ( + errFlashPGS = errors.New("errFlashPGS") + errFlashSIZE = errors.New("errFlashSIZE") + errFlashPGA = errors.New("errFlashPGA") + errFlashWRP = errors.New("errFlashWRP") + errFlashPROG = errors.New("errFlashPROG") +) + +func checkError() error { + switch { + case stm32.FLASH.GetSR_PGSERR() != 0: + return errFlashPGS + case stm32.FLASH.GetSR_SIZERR() != 0: + return errFlashSIZE + case stm32.FLASH.GetSR_PGAERR() != 0: + return errFlashPGA + case stm32.FLASH.GetSR_WRPERR() != 0: + return errFlashWRP + case stm32.FLASH.GetSR_PROGERR() != 0: + return errFlashPROG + } + + return nil +} diff --git a/emb/machine/machine_tkey.go b/emb/machine/machine_tkey.go new file mode 100644 index 00000000..78863d84 --- /dev/null +++ b/emb/machine/machine_tkey.go @@ -0,0 +1,234 @@ +//go:build tkey + +package machine + +import ( + "device/tkey" + "errors" + "strconv" +) + +const deviceName = "TKey" + +// GPIO pins modes are only here to match the Pin interface. +// The actual configuration is fixed in the hardware. +const ( + PinOutput PinMode = iota + PinInput + PinInputPullup + PinInputPulldown +) + +const ( + LED_BLUE = Pin(tkey.TK1_MMIO_TK1_LED_B_BIT) + LED_GREEN = Pin(tkey.TK1_MMIO_TK1_LED_G_BIT) + LED_RED = Pin(tkey.TK1_MMIO_TK1_LED_R_BIT) + + LED = LED_GREEN + + TKEY_TOUCH = Pin(3) // 3 is unused, but we need a value here to match the Pin interface. + BUTTON = TKEY_TOUCH + + GPIO1 = Pin(tkey.TK1_MMIO_TK1_GPIO1_BIT + 8) + GPIO2 = Pin(tkey.TK1_MMIO_TK1_GPIO2_BIT + 8) + GPIO3 = Pin(tkey.TK1_MMIO_TK1_GPIO3_BIT + 8) + GPIO4 = Pin(tkey.TK1_MMIO_TK1_GPIO4_BIT + 8) +) + +var touchConfig, gpio1Config, gpio2Config PinConfig + +// No config needed for TKey, just to match the Pin interface. +func (p Pin) Configure(config PinConfig) { + switch p { + case BUTTON: + touchConfig = config + + // Clear any pending touch events. + tkey.TOUCH.STATUS.Set(0) + case GPIO1: + gpio1Config = config + case GPIO2: + gpio2Config = config + } +} + +// Set pin to high or low. +func (p Pin) Set(high bool) { + switch p { + case LED_BLUE, LED_GREEN, LED_RED: + if high { + tkey.TK1.LED.SetBits(1 << uint(p)) + } else { + tkey.TK1.LED.ClearBits(1 << uint(p)) + } + case GPIO3, GPIO4: + if high { + tkey.TK1.GPIO.SetBits(1 << uint(p-8)) + } else { + tkey.TK1.GPIO.ClearBits(1 << uint(p-8)) + } + } +} + +// Get returns the current value of a pin. +func (p Pin) Get() bool { + pushed := false + mode := PinInput + + switch p { + case BUTTON: + mode = touchConfig.Mode + if tkey.TOUCH.STATUS.HasBits(1) { + tkey.TOUCH.STATUS.Set(0) + pushed = true + } + case GPIO1: + mode = gpio1Config.Mode + pushed = tkey.TK1.GPIO.HasBits(1 << uint(p-8)) + case GPIO2: + mode = gpio2Config.Mode + pushed = tkey.TK1.GPIO.HasBits(1 << uint(p-8)) + case GPIO3, GPIO4: + mode = PinOutput + pushed = tkey.TK1.GPIO.HasBits(1 << uint(p-8)) + case LED_BLUE, LED_GREEN, LED_RED: + mode = PinOutput + pushed = tkey.TK1.LED.HasBits(1 << uint(p)) + } + + switch mode { + case PinInputPullup: + return !pushed + case PinInput, PinInputPulldown, PinOutput: + return pushed + } + + return false +} + +type UART struct { + Bus *tkey.UART_Type +} + +var ( + DefaultUART = UART0 + UART0 = &_UART0 + _UART0 = UART{Bus: tkey.UART} +) + +// The TKey UART is fixed at 62500 baud, 8N1. +func (uart *UART) Configure(config UARTConfig) error { + if !(config.BaudRate == 62500 || config.BaudRate == 0) { + return errors.New("uart: only 62500 baud rate is supported") + } + + return nil +} + +// Write a slice of data bytes to the UART. +func (uart *UART) Write(data []byte) (n int, err error) { + for _, c := range data { + if err := uart.WriteByte(c); err != nil { + return n, err + } + } + return len(data), nil +} + +// WriteByte writes a byte of data to the UART. +func (uart *UART) WriteByte(c byte) error { + for uart.Bus.TX_STATUS.Get() == 0 { + } + + uart.Bus.TX_DATA.Set(uint32(c)) + + return nil +} + +// Buffered returns the number of bytes buffered in the UART. +func (uart *UART) Buffered() int { + return int(uart.Bus.RX_BYTES.Get()) +} + +// ReadByte reads a byte of data from the UART. +func (uart *UART) ReadByte() (byte, error) { + for uart.Bus.RX_STATUS.Get() == 0 { + } + + return byte(uart.Bus.RX_DATA.Get()), nil +} + +// DTR is not available on the TKey. +func (uart *UART) DTR() bool { + return false +} + +// RTS is not available on the TKey. +func (uart *UART) RTS() bool { + return false +} + +// GetRNG returns 32 bits of cryptographically secure random data +func GetRNG() (uint32, error) { + for tkey.TRNG.STATUS.Get() == 0 { + } + + return uint32(tkey.TRNG.ENTROPY.Get()), nil +} + +// DesignName returns the FPGA design name. +func DesignName() (string, string) { + n0 := tkey.TK1.NAME0.Get() + name0 := string([]byte{byte(n0 >> 24), byte(n0 >> 16), byte(n0 >> 8), byte(n0)}) + n1 := tkey.TK1.NAME1.Get() + name1 := string([]byte{byte(n1 >> 24), byte(n1 >> 16), byte(n1 >> 8), byte(n1)}) + + return name0, name1 +} + +// DesignVersion returns the FPGA design version. +func DesignVersion() string { + version := tkey.TK1.VERSION.Get() + + return strconv.Itoa(int(version)) +} + +// CDI returns 8 words of Compound Device Identifier (CDI) generated and written by the firmware when the application is loaded. +func CDI() []byte { + cdi := make([]byte, 32) + for i := 0; i < 8; i++ { + c := tkey.TK1.CDI_FIRST[i].Get() + cdi[i*4] = byte(c >> 24) + cdi[i*4+1] = byte(c >> 16) + cdi[i*4+2] = byte(c >> 8) + cdi[i*4+3] = byte(c) + } + return cdi +} + +// UDI returns 2 words of Unique Device Identifier (UDI). Only available in firmware mode. +func UDI() []byte { + udi := make([]byte, 8) + for i := 0; i < 2; i++ { + c := tkey.TK1.UDI_FIRST[i].Get() + udi[i*4] = byte(c >> 24) + udi[i*4+1] = byte(c >> 16) + udi[i*4+2] = byte(c >> 8) + udi[i*4+3] = byte(c) + } + return udi +} + +// UDS returns 8 words of Unique Device Secret. Part of the FPGA design, changed when provisioning a TKey. +// Only available in firmware mode. UDS is only readable once per power cycle. +func UDS() []byte { + uds := make([]byte, 32) + for i := 0; i < 8; i++ { + c := tkey.UDS.DATA[i].Get() + uds[i*4] = byte(c >> 24) + uds[i*4+1] = byte(c >> 16) + uds[i*4+2] = byte(c >> 8) + uds[i*4+3] = byte(c) + } + return uds +} diff --git a/emb/machine/machine_tkey_rom.go b/emb/machine/machine_tkey_rom.go new file mode 100644 index 00000000..bc7162e0 --- /dev/null +++ b/emb/machine/machine_tkey_rom.go @@ -0,0 +1,59 @@ +//go:build tkey + +package machine + +/* + #define TK1_MMIO_TK1_BLAKE2S 0xff000040 + + typedef unsigned char uint8_t; + typedef unsigned long uint32_t; + typedef unsigned long size_t; + + // blake2s state context + typedef struct { + uint8_t b[64]; // input buffer + uint32_t h[8]; // chained state + uint32_t t[2]; // total number of bytes + size_t c; // pointer for b[] + size_t outlen; // digest size + } blake2s_ctx; + + typedef int (*fw_blake2s_p)(void *out, unsigned long outlen, const void *key, + unsigned long keylen, const void *in, + unsigned long inlen, blake2s_ctx *ctx); + + int blake2s(void *out, unsigned long outlen, const void *key, unsigned long keylen, const void *in, unsigned long inlen) + { + fw_blake2s_p const fw_blake2s = + (fw_blake2s_p) * (volatile uint32_t *)TK1_MMIO_TK1_BLAKE2S; + blake2s_ctx ctx; + + return fw_blake2s(out, outlen, key, keylen, in, inlen, &ctx); + } +*/ +import "C" +import ( + "errors" + "unsafe" +) + +var ( + ErrBLAKE2sInvalid = errors.New("invalid params for call to BLAKE2s") + ErrBLAKE2sFailed = errors.New("call to BLAKE2s failed") +) + +func BLAKE2s(output []byte, key []byte, input []byte) error { + if len(output) == 0 || len(input) == 0 { + return ErrBLAKE2sInvalid + } + + op := unsafe.Pointer(&output[0]) + kp := unsafe.Pointer(&key[0]) + ip := unsafe.Pointer(&input[0]) + + if res := C.blake2s(op, C.size_t(len(output)), kp, C.size_t(len(key)), ip, C.size_t(len(input))); res != 0 { + return ErrBLAKE2sFailed + } + + return nil +} diff --git a/emb/machine/pdm.go b/emb/machine/pdm.go new file mode 100644 index 00000000..e57ad0f6 --- /dev/null +++ b/emb/machine/pdm.go @@ -0,0 +1,7 @@ +package machine + +type PDMConfig struct { + Stereo bool + DIN Pin + CLK Pin +} diff --git a/emb/machine/pwm.go b/emb/machine/pwm.go new file mode 100644 index 00000000..06d61b9a --- /dev/null +++ b/emb/machine/pwm.go @@ -0,0 +1,21 @@ +package machine + +import "errors" + +var ( + ErrPWMPeriodTooLong = errors.New("pwm: period too long") +) + +// PWMConfig allows setting some configuration while configuring a PWM +// peripheral. A zero PWMConfig is ready to use for simple applications such as +// dimming LEDs. +type PWMConfig struct { + // PWM period in nanosecond. Leaving this zero will pick a reasonable period + // value for use with LEDs. + // If you want to configure a frequency instead of a period, you can use the + // following formula to calculate a period from a frequency: + // + // period = 1e9 / frequency + // + Period uint64 +} diff --git a/emb/machine/runtime.go b/emb/machine/runtime.go new file mode 100644 index 00000000..928a39cf --- /dev/null +++ b/emb/machine/runtime.go @@ -0,0 +1,13 @@ +package machine + +import ( + _ "unsafe" +) + +// +// This file provides access to runtime package that would not otherwise +// be permitted due to linker dependencies. +// + +//go:linkname gosched runtime.Gosched +func gosched() diff --git a/emb/machine/serial-none.go b/emb/machine/serial-none.go new file mode 100644 index 00000000..7b65c328 --- /dev/null +++ b/emb/machine/serial-none.go @@ -0,0 +1,10 @@ +//go:build baremetal && serial.none + +package machine + +// Serial is a null device: writes to it are ignored. +var Serial = NullSerial{} + +func InitSerial() { + Serial.Configure(UARTConfig{}) +} diff --git a/emb/machine/serial-rtt.go b/emb/machine/serial-rtt.go new file mode 100644 index 00000000..15f6463d --- /dev/null +++ b/emb/machine/serial-rtt.go @@ -0,0 +1,150 @@ +//go:build baremetal && serial.rtt + +// Implement Segger RTT support. +// This is mostly useful for targets that only have a debug connection +// available, and no serial output (or input). It is somewhat like semihosting, +// but not unusably slow. +// It was originally specified by Segger, but support is available in OpenOCD +// for at least the DAPLink debuggers so I assume it works on any SWD debugger. + +package machine + +import ( + "runtime/interrupt" + "runtime/volatile" + "unsafe" +) + +// This symbol name is known by the compiler, see monitor.go. +var rttSerialInstance rttSerial + +var Serial = &rttSerialInstance + +func InitSerial() { + Serial.Configure(UARTConfig{}) +} + +const ( + // Some constants, see: + // https://github.com/SEGGERMicro/RTT/blob/master/RTT/SEGGER_RTT.h + + rttMaxNumUpBuffers = 1 + rttMaxNumDownBuffers = 1 + rttBufferSizeUp = 1024 + rttBufferSizeDown = 16 + + rttModeNoBlockSkip = 0 + rttModeNoBlockTrim = 1 + rttModeBlockIfFifoFull = 2 +) + +// The debugger knows about the layout of this struct, so it must not change. +// This is SEGGER_RTT_CB. +type rttControlBlock struct { + id [16]volatile.Register8 + maxNumUpBuffers int32 + maxNumDownBuffers int32 + buffersUp [rttMaxNumUpBuffers]rttBuffer + buffersDown [rttMaxNumDownBuffers]rttBuffer +} + +// Up or down buffer. +// This is SEGGER_RTT_BUFFER_UP and SEGGER_RTT_BUFFER_DOWN. +type rttBuffer struct { + name *byte + buffer *volatile.Register8 + bufferSize uint32 + writeOffset volatile.Register32 + readOffset volatile.Register32 + flags uint32 +} + +// Static buffers, for the default up and down buffer. +var ( + rttBufferUpData [rttBufferSizeUp]volatile.Register8 + rttBufferDownData [rttBufferSizeDown]volatile.Register8 +) + +type rttSerial struct { + rttControlBlock +} + +func (s *rttSerial) Configure(config UARTConfig) error { + s.maxNumUpBuffers = rttMaxNumUpBuffers + s.maxNumDownBuffers = rttMaxNumDownBuffers + + s.buffersUp[0].name = &[]byte("Terminal\x00")[0] + s.buffersUp[0].buffer = &rttBufferUpData[0] + s.buffersUp[0].bufferSize = rttBufferSizeUp + s.buffersUp[0].flags = rttModeNoBlockSkip + + s.buffersDown[0].name = &[]byte("Terminal\x00")[0] + s.buffersDown[0].buffer = &rttBufferDownData[0] + s.buffersDown[0].bufferSize = rttBufferSizeDown + s.buffersDown[0].flags = rttModeNoBlockSkip + + id := "SEGGER RTT" + for i := 0; i < len(id); i++ { + s.id[i].Set(id[i]) + } + + return nil +} + +func (b *rttBuffer) writeByte(c byte) { + state := interrupt.Disable() + readOffset := b.readOffset.Get() + writeOffset := b.writeOffset.Get() + newWriteOffset := writeOffset + 1 + if newWriteOffset == b.bufferSize { + newWriteOffset = 0 + } + if newWriteOffset != readOffset { + unsafe.Slice(b.buffer, b.bufferSize)[writeOffset].Set(c) + b.writeOffset.Set(newWriteOffset) + } + interrupt.Restore(state) +} + +func (b *rttBuffer) readByte() byte { + readOffset := b.readOffset.Get() + writeOffset := b.writeOffset.Get() + for readOffset == writeOffset { + readOffset = b.readOffset.Get() + } + c := unsafe.Slice(b.buffer, b.bufferSize)[readOffset].Get() + b.readOffset.Set(readOffset + 1) + return c +} + +func (b *rttBuffer) buffered() int { + readOffset := b.readOffset.Get() + writeOffset := b.writeOffset.Get() + return int((writeOffset - readOffset) % rttBufferSizeDown) +} + +// Write a single byte to the RTT output buffer. +// +// This method is set to not be inlined, to avoid blowing up binary size as a +// result of inlining writeByte everywhere a println exists. +// +//go:noinline +func (s *rttSerial) WriteByte(b byte) error { + s.buffersUp[0].writeByte(b) + return nil +} + +func (s *rttSerial) ReadByte() (byte, error) { + return s.buffersDown[0].readByte(), errNoByte +} + +func (s *rttSerial) Buffered() int { + return s.buffersDown[0].buffered() +} + +func (s *rttSerial) Write(data []byte) (n int, err error) { + for _, v := range data { + s.WriteByte(v) + } + return len(data), nil +} diff --git a/emb/machine/serial-uart.go b/emb/machine/serial-uart.go new file mode 100644 index 00000000..e9388659 --- /dev/null +++ b/emb/machine/serial-uart.go @@ -0,0 +1,10 @@ +//go:build baremetal && serial.uart + +package machine + +// Serial is implemented via the default (usually the first) UART on the chip. +var Serial = DefaultUART + +func InitSerial() { + Serial.Configure(UARTConfig{}) +} diff --git a/emb/machine/serial-usb.go b/emb/machine/serial-usb.go new file mode 100644 index 00000000..65870829 --- /dev/null +++ b/emb/machine/serial-usb.go @@ -0,0 +1,10 @@ +//go:build baremetal && serial.usb + +package machine + +// Serial is implemented via USB (USB-CDC). +var Serial Serialer + +func InitSerial() { + Serial = USBCDC +} diff --git a/emb/machine/serial.go b/emb/machine/serial.go new file mode 100644 index 00000000..4aacc502 --- /dev/null +++ b/emb/machine/serial.go @@ -0,0 +1,48 @@ +package machine + +import "errors" + +var errNoByte = errors.New("machine: no byte read") + +// UARTConfig is a struct with which a UART (or similar object) can be +// configured. The baud rate is usually respected, but TX and RX may be ignored +// depending on the chip and the type of object. +type UARTConfig struct { + BaudRate uint32 + TX Pin + RX Pin + RTS Pin + CTS Pin +} + +// NullSerial is a serial version of /dev/null (or null router): it drops +// everything that is written to it. +type NullSerial struct { +} + +// Configure does nothing: the null serial has no configuration. +func (ns NullSerial) Configure(config UARTConfig) error { + return nil +} + +// WriteByte is a no-op: the null serial doesn't write bytes. +func (ns NullSerial) WriteByte(b byte) error { + return nil +} + +// ReadByte always returns an error because there aren't any bytes to read. +func (ns NullSerial) ReadByte() (byte, error) { + return 0, errNoByte +} + +// Buffered returns how many bytes are buffered in the UART. It always returns 0 +// as there are no bytes to read. +func (ns NullSerial) Buffered() int { + return 0 +} + +// Write is a no-op: none of the data is being written and it will not return an +// error. +func (ns NullSerial) Write(p []byte) (n int, err error) { + return len(p), nil +} diff --git a/emb/machine/spi.go b/emb/machine/spi.go new file mode 100644 index 00000000..9a1033ca --- /dev/null +++ b/emb/machine/spi.go @@ -0,0 +1,29 @@ +//go:build !baremetal || atmega || esp32 || fe310 || k210 || nrf || (nxp && !mk66f18) || rp2040 || rp2350 || sam || (stm32 && !stm32f7x2 && !stm32l5x2) + +package machine + +import "errors" + +// SPI phase and polarity configs CPOL and CPHA +const ( + Mode0 = 0 + Mode1 = 1 + Mode2 = 2 + Mode3 = 3 +) + +var ( + ErrTxInvalidSliceSize = errors.New("SPI write and read slices must be same size") + errSPIInvalidMachineConfig = errors.New("SPI port was not configured properly by the machine") +) + +// If you are getting a compile error on this line please check to see you've +// correctly implemented the methods on the SPI type. They must match +// the interface method signatures type to type perfectly. +// If not implementing the SPI type please remove your target from the build tags +// at the top of this file. +var _ interface { // 2 + Configure(config SPIConfig) error + Tx(w, r []byte) error + Transfer(w byte) (byte, error) +} = (*SPI)(nil) diff --git a/emb/machine/spi_tx.go b/emb/machine/spi_tx.go new file mode 100644 index 00000000..97385bb5 --- /dev/null +++ b/emb/machine/spi_tx.go @@ -0,0 +1,61 @@ +//go:build atmega || fe310 || k210 || (nxp && !mk66f18) || (stm32 && !stm32f7x2 && !stm32l5x2) + +// This file implements the SPI Tx function for targets that don't have a custom +// (faster) implementation for it. + +package machine + +// Tx handles read/write operation for SPI interface. Since SPI is a synchronous write/read +// interface, there must always be the same number of bytes written as bytes read. +// The Tx method knows about this, and offers a few different ways of calling it. +// +// This form sends the bytes in tx buffer, putting the resulting bytes read into the rx buffer. +// Note that the tx and rx buffers must be the same size: +// +// spi.Tx(tx, rx) +// +// This form sends the tx buffer, ignoring the result. Useful for sending "commands" that return zeros +// until all the bytes in the command packet have been received: +// +// spi.Tx(tx, nil) +// +// This form sends zeros, putting the result into the rx buffer. Good for reading a "result packet": +// +// spi.Tx(nil, rx) +func (spi *SPI) Tx(w, r []byte) error { + var err error + + switch { + case w == nil: + // read only, so write zero and read a result. + for i := range r { + r[i], err = spi.Transfer(0) + if err != nil { + return err + } + } + case r == nil: + // write only + for _, b := range w { + _, err = spi.Transfer(b) + if err != nil { + return err + } + } + + default: + // write/read + if len(w) != len(r) { + return ErrTxInvalidSliceSize + } + + for i, b := range w { + r[i], err = spi.Transfer(b) + if err != nil { + return err + } + } + } + + return nil +} diff --git a/emb/machine/uart.go b/emb/machine/uart.go new file mode 100644 index 00000000..32462587 --- /dev/null +++ b/emb/machine/uart.go @@ -0,0 +1,106 @@ +//go:build atmega || esp || nrf || sam || sifive || stm32 || k210 || nxp || rp2040 || rp2350 + +package machine + +import "errors" + +var errUARTBufferEmpty = errors.New("UART buffer empty") + +// UARTParity is the parity setting to be used for UART communication. +type UARTParity uint8 + +const ( + // ParityNone means to not use any parity checking. This is + // the most common setting. + ParityNone UARTParity = iota + + // ParityEven means to expect that the total number of 1 bits sent + // should be an even number. + ParityEven + + // ParityOdd means to expect that the total number of 1 bits sent + // should be an odd number. + ParityOdd +) + +// To implement the UART interface for a board, you must declare a concrete type as follows: +// +// type UART struct { +// Buffer *RingBuffer +// } +// +// You can also add additional members to this struct depending on your implementation, +// but the *RingBuffer is required. +// When you are declaring your UARTs for your board, make sure that you also declare the +// RingBuffer using the NewRingBuffer() function when you declare your UART: +// +// UART{Buffer: NewRingBuffer()} +// + +// Read from the RX buffer. +func (uart *UART) Read(data []byte) (n int, err error) { + // check if RX buffer is empty + size := uart.Buffered() + if size == 0 { + return 0, nil + } + + // Make sure we do not read more from buffer than the data slice can hold. + if len(data) < size { + size = len(data) + } + + // only read number of bytes used from buffer + for i := 0; i < size; i++ { + v, _ := uart.ReadByte() + data[i] = v + } + + return size, nil +} + +// WriteByte writes a byte of data over the UART's Tx. +// This function blocks until the data is finished being sent. +func (uart *UART) WriteByte(c byte) error { + err := uart.writeByte(c) + if err != nil { + return err + } + uart.flush() // flush() blocks until all data has been transmitted. + return nil +} + +// Write data over the UART's Tx. +// This function blocks until the data is finished being sent. +func (uart *UART) Write(data []byte) (n int, err error) { + for i, v := range data { + err = uart.writeByte(v) + if err != nil { + return i, err + } + } + uart.flush() // flush() blocks until all data has been transmitted. + return len(data), nil +} + +// ReadByte reads a single byte from the RX buffer. +// If there is no data in the buffer, returns an error. +func (uart *UART) ReadByte() (byte, error) { + // check if RX buffer is empty + buf, ok := uart.Buffer.Get() + if !ok { + return 0, errUARTBufferEmpty + } + return buf, nil +} + +// Buffered returns the number of bytes currently stored in the RX buffer. +func (uart *UART) Buffered() int { + return int(uart.Buffer.Used()) +} + +// Receive handles adding data to the UART's data buffer. +// Usually called by the IRQ handler for a machine. +func (uart *UART) Receive(data byte) { + uart.Buffer.Put(data) +} diff --git a/emb/machine/usb.go b/emb/machine/usb.go new file mode 100644 index 00000000..434ee0f1 --- /dev/null +++ b/emb/machine/usb.go @@ -0,0 +1,338 @@ +//go:build sam || nrf52840 || rp2040 || rp2350 + +package machine + +import ( + "machine/usb" + "machine/usb/descriptor" + + "errors" +) + +type USBDevice struct { + initcomplete bool + InitEndpointComplete bool +} + +var ( + USBDev = &USBDevice{} + USBCDC Serialer +) + +type Serialer interface { + WriteByte(c byte) error + Write(data []byte) (n int, err error) + Configure(config UARTConfig) error + Buffered() int + ReadByte() (byte, error) + DTR() bool + RTS() bool +} + +var usbDescriptor descriptor.Descriptor + +func usbVendorID() uint16 { + if usb.VendorID != 0 { + return usb.VendorID + } + + return usb_VID +} + +func usbProductID() uint16 { + if usb.ProductID != 0 { + return usb.ProductID + } + + return usb_PID +} + +func usbManufacturer() string { + if usb.Manufacturer != "" { + return usb.Manufacturer + } + + return usb_STRING_MANUFACTURER +} + +func usbProduct() string { + if usb.Product != "" { + return usb.Product + } + + return usb_STRING_PRODUCT +} + +func usbSerial() string { + if usb.Serial != "" { + return usb.Serial + } + return "" +} + +// strToUTF16LEDescriptor converts a utf8 string into a string descriptor +// note: the following code only converts ascii characters to UTF16LE. In order +// to do a "proper" conversion, we would need to pull in the 'unicode/utf16' +// package, which at the time this was written added 512 bytes to the compiled +// binary. +func strToUTF16LEDescriptor(in string, out []byte) { + out[0] = byte(len(out)) + out[1] = descriptor.TypeString + for i, rune := range in { + out[(i<<1)+2] = byte(rune) + out[(i<<1)+3] = 0 + } + return +} + +const cdcLineInfoSize = 7 + +var ( + ErrUSBReadTimeout = errors.New("USB read timeout") + ErrUSBBytesRead = errors.New("USB invalid number of bytes read") + ErrUSBBytesWritten = errors.New("USB invalid number of bytes written") +) + +var ( + usbEndpointDescriptors [NumberOfUSBEndpoints]descriptor.Device + + isEndpointHalt = false + isRemoteWakeUpEnabled = false + + usbConfiguration uint8 + usbSetInterface uint8 +) + +//go:align 4 +var udd_ep_control_cache_buffer [256]uint8 + +//go:align 4 +var udd_ep_in_cache_buffer [NumberOfUSBEndpoints][64]uint8 + +//go:align 4 +var udd_ep_out_cache_buffer [NumberOfUSBEndpoints][64]uint8 + +// usb_trans_buffer max size is 255 since that is max size +// for a descriptor (bLength is 1 byte), and the biggest use +// for this buffer is to transmit string descriptors. If +// this buffer is used for new purposes in future the length +// must be revisited. +var usb_trans_buffer [255]uint8 + +var ( + usbTxHandler [NumberOfUSBEndpoints]func() + usbRxHandler [NumberOfUSBEndpoints]func([]byte) bool + usbSetupHandler [usb.NumberOfInterfaces]func(usb.Setup) bool + usbStallHandler [NumberOfUSBEndpoints]func(usb.Setup) bool +) + +// sendDescriptor creates and sends the various USB descriptor types that +// can be requested by the host. +func sendDescriptor(setup usb.Setup) { + switch setup.WValueH { + case descriptor.TypeConfiguration: + sendUSBPacket(0, usbDescriptor.Configuration, setup.WLength) + return + case descriptor.TypeDevice: + usbDescriptor.Configure(usbVendorID(), usbProductID()) + sendUSBPacket(0, usbDescriptor.Device, setup.WLength) + return + + case descriptor.TypeString: + switch setup.WValueL { + case 0: + usb_trans_buffer[0] = 0x04 + usb_trans_buffer[1] = 0x03 + usb_trans_buffer[2] = 0x09 + usb_trans_buffer[3] = 0x04 + sendUSBPacket(0, usb_trans_buffer[:4], setup.WLength) + + case usb.IPRODUCT: + b := usb_trans_buffer[:(len(usbProduct())<<1)+2] + strToUTF16LEDescriptor(usbProduct(), b) + sendUSBPacket(0, b, setup.WLength) + + case usb.IMANUFACTURER: + b := usb_trans_buffer[:(len(usbManufacturer())<<1)+2] + strToUTF16LEDescriptor(usbManufacturer(), b) + sendUSBPacket(0, b, setup.WLength) + + case usb.ISERIAL: + sz := len(usbSerial()) + if sz == 0 { + SendZlp() + } else { + b := usb_trans_buffer[:(sz<<1)+2] + strToUTF16LEDescriptor(usbSerial(), b) + sendUSBPacket(0, b, setup.WLength) + } + } + return + case descriptor.TypeHIDReport: + if h, ok := usbDescriptor.HID[setup.WIndex]; ok { + sendUSBPacket(0, h, setup.WLength) + return + } + case descriptor.TypeDeviceQualifier: + // skip + default: + } + + // do not know how to handle this message, so return zero + SendZlp() + return +} + +func handleStandardSetup(setup usb.Setup) bool { + switch setup.BRequest { + case usb.GET_STATUS: + usb_trans_buffer[0] = 0 + usb_trans_buffer[1] = 0 + + if setup.BmRequestType != 0 { // endpoint + if isEndpointHalt { + usb_trans_buffer[0] = 1 + } + } + + sendUSBPacket(0, usb_trans_buffer[:2], setup.WLength) + return true + + case usb.CLEAR_FEATURE: + if setup.WValueL == 1 { // DEVICEREMOTEWAKEUP + isRemoteWakeUpEnabled = false + } else if setup.WValueL == 0 { // ENDPOINTHALT + if idx := setup.WIndex & 0x7F; idx < NumberOfUSBEndpoints && usbStallHandler[idx] != nil { + // Host has requested to clear an endpoint stall. If the request is addressed to + // an endpoint with a configured StallHandler, forward the message on. + // The 0x7F mask is used to clear the direction bit from the endpoint number + return usbStallHandler[idx](setup) + } + isEndpointHalt = false + } + SendZlp() + return true + + case usb.SET_FEATURE: + if setup.WValueL == 1 { // DEVICEREMOTEWAKEUP + isRemoteWakeUpEnabled = true + } else if setup.WValueL == 0 { // ENDPOINTHALT + if idx := setup.WIndex & 0x7F; idx < NumberOfUSBEndpoints && usbStallHandler[idx] != nil { + // Host has requested to stall an endpoint. If the request is addressed to + // an endpoint with a configured StallHandler, forward the message on. + // The 0x7F mask is used to clear the direction bit from the endpoint number + return usbStallHandler[idx](setup) + } + isEndpointHalt = true + } + SendZlp() + return true + + case usb.SET_ADDRESS: + return handleUSBSetAddress(setup) + + case usb.GET_DESCRIPTOR: + sendDescriptor(setup) + return true + + case usb.SET_DESCRIPTOR: + return false + + case usb.GET_CONFIGURATION: + usb_trans_buffer[0] = usbConfiguration + sendUSBPacket(0, usb_trans_buffer[:1], setup.WLength) + return true + + case usb.SET_CONFIGURATION: + if setup.BmRequestType&usb.REQUEST_RECIPIENT == usb.REQUEST_DEVICE { + for i := 1; i < len(endPoints); i++ { + initEndpoint(uint32(i), endPoints[i]) + } + + usbConfiguration = setup.WValueL + USBDev.InitEndpointComplete = true + + SendZlp() + return true + } else { + return false + } + + case usb.GET_INTERFACE: + usb_trans_buffer[0] = usbSetInterface + sendUSBPacket(0, usb_trans_buffer[:1], setup.WLength) + return true + + case usb.SET_INTERFACE: + usbSetInterface = setup.WValueL + + SendZlp() + return true + + default: + return true + } +} + +func EnableCDC(txHandler func(), rxHandler func([]byte), setupHandler func(usb.Setup) bool) { + if len(usbDescriptor.Device) == 0 { + usbDescriptor = descriptor.CDC + } + // Initialization of endpoints is required even for non-CDC + ConfigureUSBEndpoint(usbDescriptor, + []usb.EndpointConfig{ + { + Index: usb.CDC_ENDPOINT_ACM, + IsIn: true, + Type: usb.ENDPOINT_TYPE_INTERRUPT, + }, + { + Index: usb.CDC_ENDPOINT_OUT, + IsIn: false, + Type: usb.ENDPOINT_TYPE_BULK, + RxHandler: rxHandler, + }, + { + Index: usb.CDC_ENDPOINT_IN, + IsIn: true, + Type: usb.ENDPOINT_TYPE_BULK, + TxHandler: txHandler, + }, + }, + []usb.SetupConfig{ + { + Index: usb.CDC_ACM_INTERFACE, + Handler: setupHandler, + }, + }) +} + +func ConfigureUSBEndpoint(desc descriptor.Descriptor, epSettings []usb.EndpointConfig, setup []usb.SetupConfig) { + usbDescriptor = desc + + for _, ep := range epSettings { + if ep.IsIn { + endPoints[ep.Index] = uint32(ep.Type | usb.EndpointIn) + if ep.TxHandler != nil { + usbTxHandler[ep.Index] = ep.TxHandler + } + } else { + endPoints[ep.Index] = uint32(ep.Type | usb.EndpointOut) + if ep.RxHandler != nil { + usbRxHandler[ep.Index] = func(b []byte) bool { + ep.RxHandler(b) + return true + } + } else if ep.DelayRxHandler != nil { + usbRxHandler[ep.Index] = ep.DelayRxHandler + } + } + if ep.StallHandler != nil { + usbStallHandler[ep.Index] = ep.StallHandler + } + } + + for _, s := range setup { + usbSetupHandler[s.Index] = s.Handler + } +} diff --git a/emb/machine/virt.go b/emb/machine/virt.go new file mode 100644 index 00000000..2b28ae61 --- /dev/null +++ b/emb/machine/virt.go @@ -0,0 +1,189 @@ +//go:build tinygo.riscv32 && virt + +// Machine implementation for VirtIO targets. +// At the moment only QEMU RISC-V is supported, but support for ARM for example +// should not be difficult to add with a change to virtioFindDevice. + +package machine + +import ( + "errors" + "runtime/volatile" + "sync" + "unsafe" +) + +const deviceName = "riscv-qemu" + +func (p Pin) Set(high bool) { + // no pins defined +} + +var rngLock sync.Mutex +var rngDevice *virtioDevice1 +var rngBuf volatile.Register32 + +var errNoRNG = errors.New("machine: no entropy source found") +var errNoRNGData = errors.New("machine: entropy source didn't return enough data") + +// GetRNG returns random numbers from a VirtIO entropy source. +// When running in QEMU, it requires adding the RNG device: +// +// -device virtio-rng-device +func GetRNG() (uint32, error) { + rngLock.Lock() + + // Initialize the device on first use. + if rngDevice == nil { + // Search for an available RNG. + rngDevice = virtioFindDevice(virtioDeviceEntropySource) + if rngDevice == nil { + rngLock.Unlock() + return 0, errNoRNG + } + + // Initialize the device. + rngDevice.status.Set(0) // reset device + rngDevice.status.Set(virtioDeviceStatusAcknowledge) + rngDevice.status.Set(virtioDeviceStatusAcknowledge | virtioDeviceStatusDriver) + rngDevice.hostFeaturesSel.Set(0) + rngDevice.status.Set(virtioDeviceStatusAcknowledge | virtioDeviceStatusDriver | virtioDeviceStatusDriverOk) + rngDevice.guestPageSize.Set(4096) + + // Configure queue, according to section 4.2.4 "Legacy interface". + // Note: we're skipping checks for queuePFM and queueNumMax. + rngDevice.queueSel.Set(0) // use queue 0 (the only queue) + rngDevice.queueNum.Set(1) // use a single buffer in the queue + rngDevice.queueAlign.Set(4096) // default alignment appears to be 4096 + rngDevice.queuePFN.Set(uint32(uintptr(unsafe.Pointer(&rngQueue))) / 4096) + + // Configure the only buffer in the queue (but don't increment + // rngQueue.available yet). + rngQueue.buffers[0].address = uint64(uintptr(unsafe.Pointer(&rngBuf))) + rngQueue.buffers[0].length = uint32(unsafe.Sizeof(rngBuf)) + rngQueue.buffers[0].flags = 2 // 2 means write-only buffer + } + + // Increment the available ring buffer. This doesn't actually change the + // buffer index (it's a ring with a single entry), but the number needs to + // be incremented otherwise the device won't recognize a new buffer. + index := rngQueue.available.index + rngQueue.available.index = index + 1 + rngDevice.queueNotify.Set(0) // notify the device of the 'new' (reused) buffer + for rngQueue.used.index.Get() != index+1 { + // Busy wait until the RNG buffer is filled. + // A better way would be to wait for an interrupt, but since this driver + // implementation is mostly used for testing it's good enough for now. + } + + // Check that we indeed got 4 bytes back. + if rngQueue.used.ring[0].length != 4 { + rngLock.Unlock() + return 0, errNoRNGData + } + + // Read the resulting random numbers. + result := rngBuf.Get() + + rngLock.Unlock() + + return result, nil +} + +// Implement a driver for the VirtIO entropy device. +// https://docs.oasis-open.org/virtio/virtio/v1.2/csd01/virtio-v1.2-csd01.html +// http://wiki.osdev.org/Virtio +// http://www.dumais.io/index.php?article=aca38a9a2b065b24dfa1dee728062a12 + +const ( + virtioDeviceStatusAcknowledge = 1 + virtioDeviceStatusDriver = 2 + virtioDeviceStatusDriverOk = 4 + virtioDeviceStatusFeaturesOk = 8 + virtioDeviceStatusFailed = 128 +) + +const ( + virtioDeviceReserved = iota + virtioDeviceNetworkCard + virtioDeviceBlockDevice + virtioDeviceConsole + virtioDeviceEntropySource + // there are more device types +) + +// VirtIO device version 1 +type virtioDevice1 struct { + magic volatile.Register32 // always 0x74726976 + version volatile.Register32 + deviceID volatile.Register32 + vendorID volatile.Register32 + hostFeatures volatile.Register32 + hostFeaturesSel volatile.Register32 + _ [2]uint32 + guestFeatures volatile.Register32 + guestFeaturesSel volatile.Register32 + guestPageSize volatile.Register32 + _ uint32 + queueSel volatile.Register32 + queueNumMax volatile.Register32 + queueNum volatile.Register32 + queueAlign volatile.Register32 + queuePFN volatile.Register32 + _ [3]uint32 + queueNotify volatile.Register32 + _ [3]uint32 + interruptStatus volatile.Register32 + interruptAck volatile.Register32 + _ [2]uint32 + status volatile.Register32 +} + +// VirtIO queue, with a single buffer. +type virtioQueue struct { + buffers [1]struct { + address uint64 + length uint32 + flags uint16 + next uint16 + } // 16 bytes + + available struct { + flags uint16 + index uint16 + ring [1]uint16 + eventIndex uint16 + } // 8 bytes + + _ [4096 - 16*1 - 8*1]byte // padding (to align on a 4096 byte boundary) + + used struct { + flags uint16 + index volatile.Register16 + ring [1]struct { + index uint32 + length uint32 + } + availEvent uint16 + } +} + +func virtioFindDevice(deviceID uint32) *virtioDevice1 { + // On RISC-V, QEMU defines 8 VirtIO devices starting at 0x10001000 and + // repeating every 0x1000 bytes. + // The memory map can be seen in the QEMU source code: + // https://github.com/qemu/qemu/blob/master/hw/riscv/virt.c + for i := 0; i < 8; i++ { + dev := (*virtioDevice1)(unsafe.Pointer(uintptr(0x10001000 + i*0x1000))) + if dev.magic.Get() != 0x74726976 || dev.version.Get() != 1 || dev.deviceID.Get() != deviceID { + continue + } + return dev + } + return nil +} + +// A VirtIO queue needs to be page-aligned. +// +//go:align 4096 +var rngQueue virtioQueue diff --git a/emb/machine/watchdog.go b/emb/machine/watchdog.go new file mode 100644 index 00000000..7dbf676a --- /dev/null +++ b/emb/machine/watchdog.go @@ -0,0 +1,34 @@ +//go:build nrf52840 || nrf52833 || rp2040 || rp2350 || atsamd21 || atsamd51 || atsame5x || stm32 + +package machine + +// WatchdogConfig holds configuration for the watchdog timer. +type WatchdogConfig struct { + // The timeout (in milliseconds) before the watchdog fires. + // + // If the requested timeout exceeds `MaxTimeout` it will be rounded + // down. + TimeoutMillis uint32 +} + +// watchdog must be implemented by any platform supporting watchdog functionality +type watchdog interface { + // Configure the watchdog. + // + // This method should not be called after the watchdog is started and on + // some platforms attempting to reconfigure after starting the watchdog + // is explicitly forbidden / will not work. + Configure(config WatchdogConfig) error + + // Starts the watchdog. + Start() error + + // Update the watchdog, indicating that the app is healthy. + Update() +} + +// Ensure required public symbols var exists and meets interface spec +var _ = watchdog(Watchdog) + +// Ensure required public constants exist +const _ = WatchdogMaxTimeout diff --git a/emb/runtime/volatile/bitband_nxpmk66f18.go b/emb/runtime/volatile/bitband_nxpmk66f18.go new file mode 100644 index 00000000..289596ba --- /dev/null +++ b/emb/runtime/volatile/bitband_nxpmk66f18.go @@ -0,0 +1,78 @@ +//go:build nxp && mk66f18 + +package volatile + +import "unsafe" + +const registerBase = 0x40000000 +const registerEnd = 0x40100000 +const bitbandBase = 0x42000000 + +//go:inline +func bitbandAddress(reg uintptr, bit uint8) uintptr { + if uintptr(bit) > 32 { + panic("invalid bit position") + } + if reg < registerBase || reg >= registerEnd { + panic("register is out of range") + } + return (reg-registerBase)*32 + uintptr(bit)*4 + bitbandBase +} + +// Special types that causes loads/stores to be volatile (necessary for +// memory-mapped registers). +type BitRegister struct { + Reg uint32 +} + +// Get returns the of the mapped register bit. It is the volatile equivalent of: +// +// *r.Reg +// +//go:inline +func (r *BitRegister) Get() bool { + return LoadUint32(&r.Reg) != 0 +} + +// Set sets the mapped register bit. It is the volatile equivalent of: +// +// *r.Reg = 1 +// +//go:inline +func (r *BitRegister) Set(v bool) { + var i uint32 + if v { + i = 1 + } + StoreUint32(&r.Reg, i) +} + +// Bit maps bit N of register R to the corresponding bitband address. Bit panics +// if R is not an AIPS or GPIO register or if N is out of range (greater than +// the number of bits in a register minus one). +// +//go:inline +func (r *Register8) Bit(bit uint8) *BitRegister { + ptr := bitbandAddress(uintptr(unsafe.Pointer(&r.Reg)), bit) + return (*BitRegister)(unsafe.Pointer(ptr)) +} + +// Bit maps bit N of register R to the corresponding bitband address. Bit panics +// if R is not an AIPS or GPIO register or if N is out of range (greater than +// the number of bits in a register minus one). +// +//go:inline +func (r *Register16) Bit(bit uint8) *BitRegister { + ptr := bitbandAddress(uintptr(unsafe.Pointer(&r.Reg)), bit) + return (*BitRegister)(unsafe.Pointer(ptr)) +} + +// Bit maps bit N of register R to the corresponding bitband address. Bit panics +// if R is not an AIPS or GPIO register or if N is out of range (greater than +// the number of bits in a register minus one). +// +//go:inline +func (r *Register32) Bit(bit uint8) *BitRegister { + ptr := bitbandAddress(uintptr(unsafe.Pointer(&r.Reg)), bit) + return (*BitRegister)(unsafe.Pointer(ptr)) +} diff --git a/emb/runtime/volatile/register.go b/emb/runtime/volatile/register.go new file mode 100644 index 00000000..d89bb6fa --- /dev/null +++ b/emb/runtime/volatile/register.go @@ -0,0 +1,254 @@ +package volatile + +// This file defines Register{8,16,32,64} types, which are convenience types for +// volatile register accesses. + +// Special types that causes loads/stores to be volatile (necessary for +// memory-mapped registers). +type Register8 struct { + Reg uint8 +} + +// Get returns the value in the register. It is the volatile equivalent of: +// +// *r.Reg +// +//go:inline +func (r *Register8) Get() uint8 { + return LoadUint8(&r.Reg) +} + +// Set updates the register value. It is the volatile equivalent of: +// +// *r.Reg = value +// +//go:inline +func (r *Register8) Set(value uint8) { + StoreUint8(&r.Reg, value) +} + +// SetBits reads the register, sets the given bits, and writes it back. It is +// the volatile equivalent of: +// +// r.Reg |= value +// +//go:inline +func (r *Register8) SetBits(value uint8) { + StoreUint8(&r.Reg, LoadUint8(&r.Reg)|value) +} + +// ClearBits reads the register, clears the given bits, and writes it back. It +// is the volatile equivalent of: +// +// r.Reg &^= value +// +//go:inline +func (r *Register8) ClearBits(value uint8) { + StoreUint8(&r.Reg, LoadUint8(&r.Reg)&^value) +} + +// HasBits reads the register and then checks to see if the passed bits are set. It +// is the volatile equivalent of: +// +// (*r.Reg & value) > 0 +// +//go:inline +func (r *Register8) HasBits(value uint8) bool { + return (r.Get() & value) > 0 +} + +// ReplaceBits is a helper to simplify setting multiple bits high and/or low at +// once. It is the volatile equivalent of: +// +// r.Reg = (r.Reg & ^(mask << pos)) | value << pos +// +//go:inline +func (r *Register8) ReplaceBits(value uint8, mask uint8, pos uint8) { + StoreUint8(&r.Reg, LoadUint8(&r.Reg)&^(mask< 0 +// +//go:inline +func (r *Register16) HasBits(value uint16) bool { + return (r.Get() & value) > 0 +} + +// ReplaceBits is a helper to simplify setting multiple bits high and/or low at +// once. It is the volatile equivalent of: +// +// r.Reg = (r.Reg & ^(mask << pos)) | value << pos +// +//go:inline +func (r *Register16) ReplaceBits(value uint16, mask uint16, pos uint8) { + StoreUint16(&r.Reg, LoadUint16(&r.Reg)&^(mask< 0 +// +//go:inline +func (r *Register32) HasBits(value uint32) bool { + return (r.Get() & value) > 0 +} + +// ReplaceBits is a helper to simplify setting multiple bits high and/or low at +// once. It is the volatile equivalent of: +// +// r.Reg = (r.Reg & ^(mask << pos)) | value << pos +// +//go:inline +func (r *Register32) ReplaceBits(value uint32, mask uint32, pos uint8) { + StoreUint32(&r.Reg, LoadUint32(&r.Reg)&^(mask< 0 +// +//go:inline +func (r *Register64) HasBits(value uint64) bool { + return (r.Get() & value) > 0 +} + +// ReplaceBits is a helper to simplify setting multiple bits high and/or low at +// once. It is the volatile equivalent of: +// +// r.Reg = (r.Reg & ^(mask << pos)) | value << pos +// +//go:inline +func (r *Register64) ReplaceBits(value uint64, mask uint64, pos uint8) { + StoreUint64(&r.Reg, LoadUint64(&r.Reg)&^(mask< Date: Thu, 26 Jun 2025 22:45:36 +0800 Subject: [PATCH 06/11] emb/runtime/volatile: linkname --- emb/runtime/volatile/volatile.go | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/emb/runtime/volatile/volatile.go b/emb/runtime/volatile/volatile.go index ce5e6977..ead2e7d5 100644 --- a/emb/runtime/volatile/volatile.go +++ b/emb/runtime/volatile/volatile.go @@ -15,26 +15,48 @@ // and https://blog.regehr.org/archives/28. package volatile +import _ "unsafe" + +const ( + LLGoPackage = "decl" +) + // LoadUint8 loads the volatile value *addr. +// +//go:linkname LoadUint8 llgo.atomicLoad func LoadUint8(addr *uint8) (val uint8) // LoadUint16 loads the volatile value *addr. +// +//go:linkname LoadUint16 llgo.atomicLoad func LoadUint16(addr *uint16) (val uint16) // LoadUint32 loads the volatile value *addr. +// +//go:linkname LoadUint32 llgo.atomicLoad func LoadUint32(addr *uint32) (val uint32) // LoadUint64 loads the volatile value *addr. +// +//go:linkname LoadUint64 llgo.atomicLoad func LoadUint64(addr *uint64) (val uint64) // StoreUint8 stores val to the volatile value *addr. +// +//go:linkname StoreUint8 llgo.atomicStore func StoreUint8(addr *uint8, val uint8) // StoreUint16 stores val to the volatile value *addr. +// +//go:linkname StoreUint16 llgo.atomicStore func StoreUint16(addr *uint16, val uint16) // StoreUint32 stores val to the volatile value *addr. +// +//go:linkname StoreUint32 llgo.atomicStore func StoreUint32(addr *uint32, val uint32) // StoreUint64 stores val to the volatile value *addr. +// +//go:linkname StoreUint64 llgo.atomicStore func StoreUint64(addr *uint64, val uint64) From 235eefd2c42ef2b1231b93ba3f001121aec8f24c Mon Sep 17 00:00:00 2001 From: xushiwei Date: Thu, 26 Jun 2025 22:58:22 +0800 Subject: [PATCH 07/11] export => go:linkname --- emb/machine/machine_generic.go | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/emb/machine/machine_generic.go b/emb/machine/machine_generic.go index 20bded88..82206f9b 100644 --- a/emb/machine/machine_generic.go +++ b/emb/machine/machine_generic.go @@ -4,6 +4,7 @@ package machine import ( "crypto/rand" + _ "unsafe" ) // Dummy machine package that calls out to external functions. @@ -39,13 +40,13 @@ func (p Pin) Get() bool { return gpioGet(p) } -//export __tinygo_gpio_configure +//go:linkname gpioConfigure __llgo_gpio_configure func gpioConfigure(pin Pin, config PinConfig) -//export __tinygo_gpio_set +//go:linkname gpioSet __llgo_gpio_set func gpioSet(pin Pin, value bool) -//export __tinygo_gpio_get +//go:linkname gpioGet __llgo_gpio_get func gpioGet(pin Pin) bool // Generic PWM/timer peripheral. Properties can be configured depending on the @@ -136,13 +137,13 @@ func (t *timerType) Top() uint32 { return t.top } -//export __tinygo_pwm_configure +//go:linkname pwmConfigure __llgo_pwm_configure func pwmConfigure(instance int32, frequency float64, top uint32) -//export __tinygo_pwm_channel_configure +//go:linkname pwmChannelConfigure __llgo_pwm_channel_configure func pwmChannelConfigure(instance, channel int32, pin Pin) -//export __tinygo_pwm_channel_set +//go:linkname pwmChannelSet __llgo_pwm_channel_set func pwmChannelSet(instance int32, channel uint8, value uint32) type SPI struct { @@ -199,13 +200,13 @@ func (spi *SPI) Tx(w, r []byte) error { return nil } -//export __tinygo_spi_configure +//go:linkname spiConfigure __llgo_spi_configure func spiConfigure(bus uint8, sck Pin, SDO Pin, SDI Pin) -//export __tinygo_spi_transfer +//go:linkname spiTransfer __llgo_spi_transfer func spiTransfer(bus uint8, w uint8) uint8 -//export __tinygo_spi_tx +//go:linkname spiTX __llgo_spi_tx func spiTX(bus uint8, wptr *byte, wlen int, rptr *byte, rlen int) uint8 // InitADC enables support for ADC peripherals. @@ -222,7 +223,7 @@ func (adc ADC) Get() uint16 { return adcRead(adc.Pin) } -//export __tinygo_adc_read +//go:linkname adcRead __llgo_adc_read func adcRead(pin Pin) uint16 // I2C is a generic implementation of the Inter-IC communication protocol. @@ -266,13 +267,13 @@ func (i2c *I2C) Tx(addr uint16, w, r []byte) error { return nil } -//export __tinygo_i2c_configure +//go:linkname i2cConfigure __llgo_i2c_configure func i2cConfigure(bus uint8, scl Pin, sda Pin) -//export __tinygo_i2c_set_baud_rate +//go:linkname i2cSetBaudRate __llgo_i2c_set_baud_rate func i2cSetBaudRate(bus uint8, br uint32) -//export __tinygo_i2c_transfer +//go:linkname i2cTransfer __llgo_i2c_transfer func i2cTransfer(bus uint8, w *byte, wlen int, r *byte, rlen int) int type UART struct { @@ -312,13 +313,13 @@ func (uart *UART) WriteByte(b byte) error { return nil } -//export __tinygo_uart_configure +//go:linkname uartConfigure __llgo_uart_configure func uartConfigure(bus uint8, tx Pin, rx Pin) -//export __tinygo_uart_read +//go:linkname uartRead __llgo_uart_read func uartRead(bus uint8, buf *byte, bufLen int) int -//export __tinygo_uart_write +//go:linkname uartWrite __llgo_uart_write func uartWrite(bus uint8, buf *byte, bufLen int) int var ( From 3bf4515127e4dc19d4c9911c8cd240d4678137d3 Mon Sep 17 00:00:00 2001 From: visualfc Date: Thu, 21 Aug 2025 15:24:49 +0800 Subject: [PATCH 08/11] c/clang: remove wrap --- c/clang/_wrap/cursor.cpp | 224 --------------- c/clang/_wrap/llgo_check.cpp | 7 - c/clang/_wrap/llgo_check.go | 16 -- c/clang/clang.go | 514 +++++++++-------------------------- 4 files changed, 136 insertions(+), 625 deletions(-) delete mode 100644 c/clang/_wrap/cursor.cpp delete mode 100644 c/clang/_wrap/llgo_check.cpp delete mode 100644 c/clang/_wrap/llgo_check.go diff --git a/c/clang/_wrap/cursor.cpp b/c/clang/_wrap/cursor.cpp deleted file mode 100644 index bec59d97..00000000 --- a/c/clang/_wrap/cursor.cpp +++ /dev/null @@ -1,224 +0,0 @@ -#include -#include - -typedef enum CXChildVisitResult (*wrap_CXCursorVisitor)(CXCursor *cursor, CXCursor *parent, CXClientData client_data); - -typedef struct { - CXClientData data; - wrap_CXCursorVisitor visitor; -} wrap_data; - -CXChildVisitResult wrap_visitor(CXCursor cursor, CXCursor parent, CXClientData data) { - wrap_data *d = (wrap_data *)(data); - return d->visitor(&cursor, &parent, d->data); -} - -extern "C" { - -void wrap_clang_getLocation(CXTranslationUnit tu, CXFile file, unsigned line, unsigned column, CXSourceLocation *loc) { - *loc = clang_getLocation(tu, file, line, column); -} - -void wrap_clang_getLocationForOffset(CXTranslationUnit tu, CXFile file, unsigned offset, CXSourceLocation *loc) { - *loc = clang_getLocationForOffset(tu, file, offset); -} - -void wrap_clang_getTranslationUnitCursor(CXTranslationUnit uint, CXCursor *cur) { - *cur = clang_getTranslationUnitCursor(uint); -} - -unsigned wrap_clang_equalCursors(CXCursor *cursor1, CXCursor *cursor2) { - return clang_equalCursors(*cursor1, *cursor2); -} - -int wrap_clang_Cursor_isNull(CXCursor *cursor) { return clang_Cursor_isNull(*cursor); } - -void wrap_clang_getCursorSemanticParent(CXCursor *C, CXCursor *parent) { *parent = clang_getCursorSemanticParent(*C); } - -void wrap_clang_getCursorDefinition(CXCursor *C, CXCursor *def) { *def = clang_getCursorDefinition(*C); } - -void wrap_clang_getCursorLexicalParent(CXCursor *C, CXCursor *parent) { *parent = clang_getCursorLexicalParent(*C); } - -void wrap_clang_getOverriddenCursors(CXCursor *cursor, CXCursor **overridden, unsigned *num_overridden) { - clang_getOverriddenCursors(*cursor, overridden, num_overridden); -} - -CXFile wrap_clang_getIncludedFile(CXCursor *cursor) { return clang_getIncludedFile(*cursor); } - -void wrap_clang_getCursor(CXTranslationUnit uint, CXSourceLocation *loc, CXCursor *cur) { - *cur = clang_getCursor(uint, *loc); -} - -void wrap_clang_getCursorLocation(CXCursor *cur, CXSourceLocation *loc) { *loc = clang_getCursorLocation(*cur); } - -void wrap_clang_getCursorExtent(CXCursor *cur, CXSourceRange *range) { *range = clang_getCursorExtent(*cur); } - -void wrap_clang_getCursorType(CXCursor *cur, CXType *typ) { *typ = clang_getCursorType(*cur); } - -CXString wrap_clang_getTypeSpelling(CXType *typ) { return clang_getTypeSpelling(*typ); } - -void wrap_clang_getTypedefDeclUnderlyingType(CXCursor *cur, CXType *typ) { - *typ = clang_getTypedefDeclUnderlyingType(*cur); -} - -long long wrap_clang_getEnumConstantDeclValue(CXCursor *cur) { return clang_getEnumConstantDeclValue(*cur); } - -int wrap_clang_Cursor_getNumArguments(CXCursor *cur) { return clang_Cursor_getNumArguments(*cur); } - -void wrap_clang_Cursor_getArgument(CXCursor *C, unsigned i, CXCursor *argCur) { - *argCur = clang_Cursor_getArgument(*C, i); -} - -void wrap_clang_getCanonicalType(CXType *typ, CXType *canonicalType) { *canonicalType = clang_getCanonicalType(*typ); } - -unsigned wrap_clang_isConstQualifiedType(CXType *typ) { return clang_isConstQualifiedType(*typ); } - -unsigned wrap_clang_Cursor_isMacroFunctionLike(CXCursor *cur) { return clang_Cursor_isMacroFunctionLike(*cur); } - -unsigned wrap_clang_Cursor_isMacroBuiltin(CXCursor *cur) { return clang_Cursor_isMacroBuiltin(*cur); } - -unsigned wrap_clang_Cursor_isFunctionInlined(CXCursor *cur) { return clang_Cursor_isFunctionInlined(*cur); } - -unsigned wrap_clang_isVolatileQualifiedType(CXType *T) { return clang_isVolatileQualifiedType(*T); } - -unsigned wrap_clang_isRestrictQualifiedType(CXType *T) { return clang_isRestrictQualifiedType(*T); } - -void wrap_clang_getPointeeType(CXType *pointerTyp, CXType *pointeeTyp) { - *pointeeTyp = clang_getPointeeType(*pointerTyp); -} - -void wrap_clang_getNonReferenceType(CXType *typ, CXType *nonRefTyp) { *nonRefTyp = clang_getNonReferenceType(*typ); } - -void wrap_clang_getTypeDeclaration(CXType *typ, CXCursor *cur) { *cur = clang_getTypeDeclaration(*typ); } - -void wrap_clang_getResultType(CXType *typ, CXType *resultTyp) { *resultTyp = clang_getResultType(*typ); } - -int wrap_clang_getNumArgTypes(CXType *typ) { return clang_getNumArgTypes(*typ); } - -void wrap_clang_getArgType(CXType *typ, unsigned i, CXType *argTyp) { *argTyp = clang_getArgType(*typ, i); } - -unsigned wrap_clang_isFunctionTypeVariadic(CXType *typ) { return clang_isFunctionTypeVariadic(*typ); } - -void wrap_clang_getCursorResultType(CXCursor *cur, CXType *typ) { *typ = clang_getCursorResultType(*cur); } - -void wrap_clang_getElementType(CXType *Typ, CXType *elemTyp) { *elemTyp = clang_getElementType(*Typ); } - -void wrap_clang_getArrayElementType(CXType *arrayTyp, CXType *elemTyp) { - *elemTyp = clang_getArrayElementType(*arrayTyp); -} - -long long wrap_clang_getArraySize(CXType *arrayTyp) { return clang_getArraySize(*arrayTyp); } - -void wrap_clang_Type_getNamedType(CXType *typ, CXType *namedTyp) { *namedTyp = clang_Type_getNamedType(*typ); } - -long long wrap_clang_Type_getSizeOf(CXType *typ) { return clang_Type_getSizeOf(*typ); } - -unsigned wrap_clang_Cursor_isAnonymous(CXCursor *cursor) { return clang_Cursor_isAnonymous(*cursor); } - -unsigned wrap_clang_Cursor_isAnonymousRecordDecl(CXCursor *cursor) { - return clang_Cursor_isAnonymousRecordDecl(*cursor); -} - -enum CX_CXXAccessSpecifier wrap_clang_getCXXAccessSpecifier(CXCursor *cursor) { - return clang_getCXXAccessSpecifier(*cursor); -} - -enum CX_StorageClass wrap_clang_Cursor_getStorageClass(CXCursor *cursor) { - return clang_Cursor_getStorageClass(*cursor); -} - -CXString wrap_clang_getCursorUSR(CXCursor *cur) { return clang_getCursorUSR(*cur); } - -CXString wrap_clang_getCursorSpelling(CXCursor *cur) { return clang_getCursorSpelling(*cur); } - -CXString wrap_clang_getCursorDisplayName(CXCursor *cur) { return clang_getCursorDisplayName(*cur); } - -void wrap_clang_getCursorReferenced(CXCursor *cur, CXCursor *referenced) { - *referenced = clang_getCursorReferenced(*cur); -} - -unsigned wrap_clang_Cursor_isVariadic(CXCursor *cur) { return clang_Cursor_isVariadic(*cur); } - -void wrap_clang_Cursor_getCommentRange(CXCursor *cur, CXSourceRange *range) { - *range = clang_Cursor_getCommentRange(*cur); -} - -CXString wrap_clang_Cursor_getRawCommentText(CXCursor *cursor) { return clang_Cursor_getRawCommentText(*cursor); } - -CXString wrap_clang_Cursor_getMangling(CXCursor *cur) { return clang_Cursor_getMangling(*cur); } - -unsigned wrap_clang_CXXConstructor_isConvertingConstructor(CXCursor *cursor) { - return clang_CXXConstructor_isConvertingConstructor(*cursor); -} - -unsigned wrap_clang_CXXConstructor_isCopyConstructor(CXCursor *cursor) { - return clang_CXXConstructor_isCopyConstructor(*cursor); -} - -unsigned wrap_clang_CXXConstructor_isDefaultConstructor(CXCursor *cursor) { - return clang_CXXConstructor_isDefaultConstructor(*cursor); -} - -unsigned wrap_clang_CXXConstructor_isMoveConstructor(CXCursor *cursor) { - return clang_CXXConstructor_isMoveConstructor(*cursor); -} - -unsigned wrap_clang_CXXField_isMutable(CXCursor *cursor) { return clang_CXXField_isMutable(*cursor); } - -unsigned wrap_clang_CXXMethod_isDefaulted(CXCursor *cursor) { return clang_CXXMethod_isDefaulted(*cursor); } - -unsigned wrap_clang_CXXMethod_isDeleted(CXCursor *cursor) { return clang_CXXMethod_isDeleted(*cursor); } - -unsigned wrap_clang_CXXMethod_isPureVirtual(CXCursor *cursor) { return clang_CXXMethod_isPureVirtual(*cursor); } - -unsigned wrap_clang_CXXMethod_isStatic(CXCursor *cursor) { return clang_CXXMethod_isStatic(*cursor); } - -unsigned wrap_clang_CXXMethod_isVirtual(CXCursor *cursor) { return clang_CXXMethod_isVirtual(*cursor); } - -unsigned wrap_clang_CXXMethod_isCopyAssignmentOperator(CXCursor *cursor) { - return clang_CXXMethod_isCopyAssignmentOperator(*cursor); -} - -unsigned wrap_clang_CXXMethod_isMoveAssignmentOperator(CXCursor *cursor) { - return clang_CXXMethod_isMoveAssignmentOperator(*cursor); -} - -unsigned wrap_clang_CXXMethod_isExplicit(CXCursor *cursor) { return clang_CXXMethod_isExplicit(*cursor); } - -unsigned wrap_clang_CXXRecord_isAbstract(CXCursor *cursor) { return clang_CXXRecord_isAbstract(*cursor); } - -unsigned wrap_clang_EnumDecl_isScoped(CXCursor *cursor) { return clang_EnumDecl_isScoped(*cursor); } - -unsigned wrap_clang_CXXMethod_isConst(CXCursor *cursor) { return clang_CXXMethod_isConst(*cursor); } - -CXTokenKind wrap_clang_getTokenKind(CXToken *token) { return clang_getTokenKind(*token); } - -CXString wrap_clang_getTokenSpelling(CXTranslationUnit unit, CXToken *token) { - return clang_getTokenSpelling(unit, *token); -} - -void wrap_clang_tokenize(CXTranslationUnit unit, CXSourceRange *Range, CXToken **Tokens, unsigned *NumTokens) { - clang_tokenize(unit, *Range, Tokens, NumTokens); -} - -unsigned wrap_clang_visitChildren(CXCursor *parent, wrap_CXCursorVisitor visitor, CXClientData client_data) { - wrap_data data = {client_data, visitor}; - return clang_visitChildren(*parent, wrap_visitor, CXClientData(&data)); -} - -int wrap_clang_Location_isInSystemHeader(CXSourceLocation *loc) { return clang_Location_isInSystemHeader(*loc); } - -void wrap_clang_getSpellingLocation(CXSourceLocation *loc, CXFile *file, unsigned *line, unsigned *column, - unsigned *offset) { - clang_getSpellingLocation(*loc, file, line, column, offset); -} - -void wrap_clang_getPresumedLocation(CXSourceLocation *loc, CXString *filename, unsigned *line, unsigned *column) { - clang_getPresumedLocation(*loc, filename, line, column); -} - -void wrap_clang_getRangeStart(CXSourceRange *range, CXSourceLocation *loc) { *loc = clang_getRangeStart(*range); } - -void wrap_clang_getRangeEnd(CXSourceRange *range, CXSourceLocation *loc) { *loc = clang_getRangeEnd(*range); } - -} // extern "C" diff --git a/c/clang/_wrap/llgo_check.cpp b/c/clang/_wrap/llgo_check.cpp deleted file mode 100644 index 0eecbb9f..00000000 --- a/c/clang/_wrap/llgo_check.cpp +++ /dev/null @@ -1,7 +0,0 @@ -#include -#include - -int main() { - printf("sizeof(clang.Cursor) = %lu\n", sizeof(CXCursor)); - return 0; -} diff --git a/c/clang/_wrap/llgo_check.go b/c/clang/_wrap/llgo_check.go deleted file mode 100644 index d806ae6e..00000000 --- a/c/clang/_wrap/llgo_check.go +++ /dev/null @@ -1,16 +0,0 @@ -package main - -import ( - "unsafe" - - "github.com/goplus/lib/c" - "github.com/goplus/lib/c/clang" -) - -const ( - LLGoCFlags = "-I$(llvm-config --includedir)" -) - -func main() { - c.Printf(c.Str("sizeof(clang.Cursor) = %lu\n"), unsafe.Sizeof(clang.Cursor{})) -} diff --git a/c/clang/clang.go b/c/clang/clang.go index 04d2f137..d8459a10 100644 --- a/c/clang/clang.go +++ b/c/clang/clang.go @@ -17,13 +17,12 @@ package clang import ( - "unsafe" + _ "unsafe" "github.com/goplus/lib/c" ) const ( - LLGoFiles = "$(llvm-config --cflags): _wrap/cursor.cpp" LLGoPackage = "link: -L$(llvm-config --libdir) -lclang; -lclang" ) @@ -1161,11 +1160,8 @@ type UnsavedFile struct { * Retrieves the source location associated with a given file/line/column * in a particular translation unit. */ -// llgo:link (*TranslationUnit).wrapGetLocation C.wrap_clang_getLocation -func (t *TranslationUnit) wrapGetLocation(file File, line, column c.Uint, loc *SourceLocation) {} - +// llgo:link (*TranslationUnit).GetLocation C.clang_getLocation func (t *TranslationUnit) GetLocation(file File, line, column c.Uint) (ret SourceLocation) { - t.wrapGetLocation(file, line, column, &ret) return } @@ -1173,11 +1169,8 @@ func (t *TranslationUnit) GetLocation(file File, line, column c.Uint) (ret Sourc * Retrieves the source location associated with a given character offset * in a particular translation unit. */ -// llgo:link (*TranslationUnit).wrapGetLocationForOffset C.wrap_clang_getLocationForOffset -func (t *TranslationUnit) wrapGetLocationForOffset(file File, offset c.Uint, loc *SourceLocation) {} - +// llgo:link (*TranslationUnit).GetLocationForOffset C.clang_getLocationForOffset func (t *TranslationUnit) GetLocationForOffset(file File, offset c.Uint) (ret SourceLocation) { - t.wrapGetLocationForOffset(file, offset, &ret) return } @@ -1299,12 +1292,8 @@ func (*TranslationUnit) Dispose() {} * The translation unit cursor can be used to start traversing the * various declarations within the given translation unit. */ - -//llgo:link (*TranslationUnit).wrapCursor C.wrap_clang_getTranslationUnitCursor -func (t *TranslationUnit) wrapCursor(cursor *Cursor) {} - +//llgo:link (*TranslationUnit).Cursor C.clang_getTranslationUnitCursor func (t *TranslationUnit) Cursor() (ret Cursor) { - t.wrapCursor(&ret) return } @@ -1579,20 +1568,18 @@ type Token struct { /** * Determine whether two cursors are equivalent. */ -// llgo:link (*Cursor).wrapEqual C.wrap_clang_equalCursors -func (*Cursor) wrapEqual(cursor *Cursor) c.Uint { return 0 } - -func (c Cursor) Equal(cursor Cursor) c.Uint { - return c.wrapEqual(&cursor) +// llgo:link Cursor.Equal C.clang_equalCursors +func (c Cursor) Equal(cursor Cursor) (ret c.Uint) { + return } /** * Returns non-zero if \p cursor is null. */ -// llgo:link (*Cursor).wrapIsNull C.wrap_clang_Cursor_isNull -func (*Cursor) wrapIsNull() c.Int { return 0 } - -func (c Cursor) IsNull() c.Int { return c.wrapIsNull() } +// llgo:link Cursor.IsNull C.clang_Cursor_isNull +func (c Cursor) IsNull() (ret c.Int) { + return +} /** * Determine the semantic parent of the given cursor. @@ -1627,19 +1614,13 @@ func (c Cursor) IsNull() c.Int { return c.wrapIsNull() } * * For global declarations, the semantic parent is the translation unit. */ -// llgo:link (*Cursor).wrapSemanticParent C.wrap_clang_getCursorSemanticParent -func (*Cursor) wrapSemanticParent(parent *Cursor) {} - +// llgo:link Cursor.SemanticParent C.clang_getCursorSemanticParent func (c Cursor) SemanticParent() (parent Cursor) { - c.wrapSemanticParent(&parent) return } -// llgo:link (*Cursor).wrapDefinition C.wrap_clang_getCursorDefinition -func (*Cursor) wrapDefinition(def *Cursor) {} - +// llgo:link Cursor.Definition C.clang_getCursorDefinition func (c Cursor) Definition() (def Cursor) { - c.wrapDefinition(&def) return } @@ -1677,11 +1658,8 @@ func (c Cursor) Definition() (def Cursor) { * For declarations written in the global scope, the lexical parent is * the translation unit. */ -// llgo:link (*Cursor).wrapLexicalParent C.wrap_clang_getCursorLexicalParent -func (*Cursor) wrapLexicalParent(parent *Cursor) {} - +// llgo:link Cursor.LexicalParent C.clang_getCursorLexicalParent func (c Cursor) LexicalParent() (parent Cursor) { - c.wrapLexicalParent(&parent) return } @@ -1728,11 +1706,8 @@ func (c Cursor) LexicalParent() (parent Cursor) { * functions, will be set to the number of overridden functions in the * array pointed to by \p overridden. */ -// llgo:link (*Cursor).wrapOverriddenCursors C.wrap_clang_getOverriddenCursors -func (c *Cursor) wrapOverriddenCursors(overridden **Cursor, numOverridden *c.Uint) {} - +// llgo:link Cursor.OverriddenCursors C.clang_getOverriddenCursors func (c Cursor) OverriddenCursors(overridden **Cursor, numOverridden *c.Uint) { - c.wrapOverriddenCursors(overridden, numOverridden) } /** @@ -1746,13 +1721,9 @@ func (c *Cursor) DisposeOverriddenCursors() {} * Retrieve the file that is included by the given inclusion directive * cursor. */ -// llgo:link (*Cursor).wrapIncludedFile C.wrap_clang_getIncludedFile -func (c *Cursor) wrapIncludedFile() File { - return 0 -} - +// llgo:link Cursor.IncludedFile C.clang_getIncludedFile func (c Cursor) IncludedFile() (file File) { - return c.wrapIncludedFile() + return } /** @@ -1770,12 +1741,8 @@ func (c Cursor) IncludedFile() (file File) { * \returns a cursor representing the entity at the given source location, or * a NULL cursor if no such entity can be found. */ - -// llgo:link (*TranslationUnit).wrapGetCursor C.wrap_clang_getCursor -func (l *TranslationUnit) wrapGetCursor(loc *SourceLocation, cur *Cursor) {} - +// llgo:link (*TranslationUnit).GetCursor C.clang_getCursor func (l *TranslationUnit) GetCursor(loc *SourceLocation) (cur Cursor) { - l.wrapGetCursor(loc, &cur) return } @@ -1789,11 +1756,8 @@ func (l *TranslationUnit) GetCursor(loc *SourceLocation) (cur Cursor) { * The location of a reference is where that reference occurs within the * source code. */ -// llgo:link (*Cursor).wrapLocation C.wrap_clang_getCursorLocation -func (c *Cursor) wrapLocation(loc *SourceLocation) {} - +// llgo:link Cursor.Location C.clang_getCursorLocation func (c Cursor) Location() (loc SourceLocation) { - c.wrapLocation(&loc) return } @@ -1808,22 +1772,16 @@ func (c Cursor) Location() (loc SourceLocation) { * the extent covers the location of the reference (e.g., where the referenced * entity was actually used). */ -// llgo:link (*Cursor).wrapExtent C.wrap_clang_getCursorExtent -func (c *Cursor) wrapExtent(loc *SourceRange) {} - +// llgo:link Cursor.Extent C.clang_getCursorExtent func (c Cursor) Extent() (loc SourceRange) { - c.wrapExtent(&loc) return } /** * Retrieve the type of a CXCursor (if any). */ -// llgo:link (*Cursor).wrapType C.wrap_clang_getCursorType -func (c *Cursor) wrapType(ret *Type) {} - +// llgo:link Cursor.Type C.clang_getCursorType func (c Cursor) Type() (ret Type) { - c.wrapType(&ret) return } @@ -1833,13 +1791,9 @@ func (c Cursor) Type() (ret Type) { * * If the type is invalid, an empty string is returned. */ -// llgo:link (*Type).wrapString C.wrap_clang_getTypeSpelling -func (t *Type) wrapString() (ret String) { - return -} - +// llgo:link Type.String C.clang_getTypeSpelling func (t Type) String() (ret String) { - return t.wrapString() + return } /** @@ -1848,11 +1802,8 @@ func (t Type) String() (ret String) { * If the cursor does not reference a typedef declaration, an invalid type is * returned. */ -// llgo:link (*Cursor).wrapTypedefDeclUnderlyingType C.wrap_clang_getTypedefDeclUnderlyingType -func (c *Cursor) wrapTypedefDeclUnderlyingType(ret *Type) { return } - +// llgo:link Cursor.TypedefDeclUnderlyingType C.clang_getTypedefDeclUnderlyingType func (c Cursor) TypedefDeclUnderlyingType() (ret Type) { - c.wrapTypedefDeclUnderlyingType(&ret) return } @@ -1864,12 +1815,9 @@ func (c Cursor) TypedefDeclUnderlyingType() (ret Type) { * returned. Since this is also potentially a valid constant value, the kind of * the cursor must be verified before calling this function. */ -// llgo:link (*Cursor).wrapEnumConstantDeclValue C.wrap_clang_getEnumConstantDeclValue -func (*Cursor) wrapEnumConstantDeclValue() (ret c.LongLong) { - return 0 -} +// llgo:link Cursor.EnumConstantDeclValue C.clang_getEnumConstantDeclValue func (c Cursor) EnumConstantDeclValue() (ret c.LongLong) { - return c.wrapEnumConstantDeclValue() + return } /** @@ -1879,13 +1827,9 @@ func (c Cursor) EnumConstantDeclValue() (ret c.LongLong) { * The number of arguments can be determined for calls as well as for * declarations of functions or methods. For other cursors -1 is returned. */ -// llgo:link (*Cursor).wrapNumArguments C.wrap_clang_Cursor_getNumArguments -func (*Cursor) wrapNumArguments() (num c.Int) { - return 0 -} - +// llgo:link Cursor.NumArguments C.clang_Cursor_getNumArguments func (c Cursor) NumArguments() (num c.Int) { - return c.wrapNumArguments() + return } /** @@ -1895,11 +1839,8 @@ func (c Cursor) NumArguments() (num c.Int) { * of functions or methods. For other cursors and for invalid indices, an * invalid cursor is returned. */ -// llgo:link (*Cursor).wrapArgument C.wrap_clang_Cursor_getArgument -func (*Cursor) wrapArgument(index c.Uint, arg *Cursor) {} - +// llgo:link Cursor.Argument C.clang_Cursor_getArgument func (c Cursor) Argument(index c.Uint) (arg Cursor) { - c.wrapArgument(index, &arg) return } @@ -1911,11 +1852,8 @@ func (c Cursor) Argument(index c.Uint) (arg Cursor) { * type with all the "sugar" removed. For example, if 'T' is a typedef * for 'int', the canonical type for 'T' would be 'int'. */ -// llgo:link (*Type).wrapCanonicalType C.wrap_clang_getCanonicalType -func (t *Type) wrapCanonicalType(ret *Type) { return } - +// llgo:link Type.CanonicalType C.clang_getCanonicalType func (t Type) CanonicalType() (ret Type) { - t.wrapCanonicalType(&ret) return } @@ -1924,44 +1862,36 @@ func (t Type) CanonicalType() (ret Type) { * without looking through typedefs that may have added "const" at a * different level. */ -// llgo:link (*Type).wrapIsConstQualifiedType C.wrap_clang_isConstQualifiedType -func (t *Type) wrapIsConstQualifiedType() (ret c.Uint) { return 0 } - +// llgo:link Type.IsConstQualifiedType C.clang_isConstQualifiedType func (t Type) IsConstQualifiedType() (ret c.Uint) { - return t.wrapIsConstQualifiedType() + return } /** * Determine whether a CXCursor that is a macro, is * function like. */ -// llgo:link (*Cursor).wrapIsMacroFunctionLike C.wrap_clang_Cursor_isMacroFunctionLike -func (c *Cursor) wrapIsMacroFunctionLike() (ret c.Uint) { return 0 } - +// llgo:link Cursor.IsMacroFunctionLike C.clang_Cursor_isMacroFunctionLike func (c Cursor) IsMacroFunctionLike() (ret c.Uint) { - return c.wrapIsMacroFunctionLike() + return } /** * Determine whether a CXCursor that is a macro, is a * builtin one. */ -// llgo:link (*Cursor).wrapIsMacroBuiltin C.wrap_clang_Cursor_isMacroBuiltin -func (c *Cursor) wrapIsMacroBuiltin() (ret c.Uint) { return 0 } - +// llgo:link Cursor.IsMacroBuiltin C.clang_Cursor_isMacroBuiltin func (c Cursor) IsMacroBuiltin() (ret c.Uint) { - return c.wrapIsMacroBuiltin() + return } /** * Determine whether a CXCursor that is a function declaration, is an * inline declaration. */ -// llgo:link (*Cursor).wrapIsFunctionInlined C.wrap_clang_Cursor_isFunctionInlined -func (c *Cursor) wrapIsFunctionInlined() (ret c.Uint) { return 0 } - +// llgo:link Cursor.IsFunctionInlined C.clang_Cursor_isFunctionInlined func (c Cursor) IsFunctionInlined() (ret c.Uint) { - return c.wrapIsFunctionInlined() + return } /** @@ -1969,11 +1899,9 @@ func (c Cursor) IsFunctionInlined() (ret c.Uint) { * without looking through typedefs that may have added "volatile" at * a different level. */ -// llgo:link (*Type).wrapIsVolatileQualifiedType C.wrap_clang_isVolatileQualifiedType -func (t *Type) wrapIsVolatileQualifiedType() (ret c.Uint) { return 0 } - +// llgo:link Type.IsVolatileQualifiedType C.clang_isVolatileQualifiedType func (t Type) IsVolatileQualifiedType() (ret c.Uint) { - return t.wrapIsVolatileQualifiedType() + return } /** @@ -1981,21 +1909,16 @@ func (t Type) IsVolatileQualifiedType() (ret c.Uint) { * without looking through typedefs that may have added "restrict" at a * different level. */ -// llgo:link (*Type).wrapIsRestrictQualifiedType C.wrap_clang_isRestrictQualifiedType -func (t *Type) wrapIsRestrictQualifiedType() (ret c.Uint) { return 0 } - +// llgo:link Type.IsRestrictQualifiedType C.clang_isRestrictQualifiedType func (t Type) IsRestrictQualifiedType() (ret c.Uint) { - return t.wrapIsRestrictQualifiedType() + return } /** * For pointer types, returns the type of the pointee. */ -// llgo:link (*Type).wrapPointeeType C.wrap_clang_getPointeeType -func (t *Type) wrapPointeeType(ret *Type) { return } - +// llgo:link Type.PointeeType C.clang_getPointeeType func (t Type) PointeeType() (ret Type) { - t.wrapPointeeType(&ret) return } @@ -2008,22 +1931,16 @@ func (t Type) PointeeType() (ret Type) { * A type that has kind \c CXType_LValueReference or * \c CXType_RValueReference is a reference type. */ -// llgo:link (*Type).wrapNonReferenceType C.wrap_clang_getNonReferenceType -func (t *Type) wrapNonReferenceType(ret *Type) { return } - +// llgo:link Type.NonReferenceType C.clang_getNonReferenceType func (t Type) NonReferenceType() (ret Type) { - t.wrapNonReferenceType(&ret) return } /** * Return the cursor for the declaration of the given type. */ -// llgo:link (*Type).wrapTypeDeclaration C.wrap_clang_getTypeDeclaration -func (t *Type) wrapTypeDeclaration(ret *Cursor) { return } - +// llgo:link Type.TypeDeclaration C.clang_getTypeDeclaration func (t Type) TypeDeclaration() (ret Cursor) { - t.wrapTypeDeclaration(&ret) return } @@ -2040,11 +1957,8 @@ func (TypeKind) String() (ret String) { * * If a non-function type is passed in, an invalid type is returned. */ -// llgo:link (*Type).wrapResultType C.wrap_clang_getResultType -func (t *Type) wrapResultType(ret *Type) { return } - +// llgo:link Type.ResultType C.clang_getResultType func (t Type) ResultType() (ret Type) { - t.wrapResultType(&ret) return } @@ -2054,11 +1968,9 @@ func (t Type) ResultType() (ret Type) { * * If a non-function type is passed in, -1 is returned. */ -// llgo:link (*Type).wrapNumArgTypes C.wrap_clang_getNumArgTypes -func (t *Type) wrapNumArgTypes() (num c.Int) { return 0 } - +// llgo:link Type.NumArgTypes C.clang_getNumArgTypes func (t Type) NumArgTypes() (num c.Int) { - return t.wrapNumArgTypes() + return } /** @@ -2067,32 +1979,26 @@ func (t Type) NumArgTypes() (num c.Int) { * If a non-function type is passed in or the function does not have enough * parameters, an invalid type is returned. */ -// llgo:link (*Type).wrapArgType C.wrap_clang_getArgType -func (t *Type) wrapArgType(index c.Uint, argTyp *Type) { return } - +// llgo:link Type.ArgType C.clang_getArgType func (t Type) ArgType(index c.Uint) (ret Type) { - t.wrapArgType(index, &ret) return } /** * Return 1 if the CXType is a variadic function type, and 0 otherwise. */ -// llgo:link (*Type).wrapIsFunctionTypeVariadic C.wrap_clang_isFunctionTypeVariadic -func (t *Type) wrapIsFunctionTypeVariadic() (ret c.Uint) { return 0 } - -func (t Type) IsFunctionTypeVariadic() (ret c.Uint) { return t.wrapIsFunctionTypeVariadic() } +// llgo:link Type.IsFunctionTypeVariadic C.clang_isFunctionTypeVariadic +func (t Type) IsFunctionTypeVariadic() (ret c.Uint) { + return +} /** * Retrieve the return type associated with a given cursor. * * This only returns a valid type if the cursor refers to a function or method. */ -// llgo:link (*Cursor).wrapResultType C.wrap_clang_getCursorResultType -func (c *Cursor) wrapResultType(ret *Type) {} - +// llgo:link Cursor.ResultType C.clang_getCursorResultType func (c Cursor) ResultType() (ret Type) { - c.wrapResultType(&ret) return } @@ -2102,11 +2008,8 @@ func (c Cursor) ResultType() (ret Type) { * If a type is passed in that is not an array, complex, or vector type, * an invalid type is returned. */ -// llgo:link (*Type).wrapElementType C.wrap_clang_getElementType -func (t *Type) wrapElementType(ret *Type) { return } - +// llgo:link Type.ElementType C.clang_getElementType func (t Type) ElementType() (ret Type) { - t.wrapElementType(&ret) return } @@ -2115,11 +2018,8 @@ func (t Type) ElementType() (ret Type) { * * If a non-array type is passed in, an invalid type is returned. */ -// llgo:link (*Type).wrapArrayElementType C.wrap_clang_getArrayElementType -func (t *Type) wrapArrayElementType(ret *Type) { return } - +// llgo:link Type.ArrayElementType C.clang_getArrayElementType func (t Type) ArrayElementType() (ret Type) { - t.wrapArrayElementType(&ret) return } @@ -2128,11 +2028,9 @@ func (t Type) ArrayElementType() (ret Type) { * * If a non-array type is passed in, -1 is returned. */ -// llgo:link (*Type).wrapArraySize C.wrap_clang_getArraySize -func (t *Type) wrapArraySize() (ret c.LongLong) { return 0 } - +// llgo:link Type.ArraySize C.clang_getArraySize func (t Type) ArraySize() (ret c.LongLong) { - return t.wrapArraySize() + return } /** @@ -2140,11 +2038,8 @@ func (t Type) ArraySize() (ret c.LongLong) { * * If a non-elaborated type is passed in, an invalid type is returned. */ -// llgo:link (*Type).wrapNamedType C.wrap_clang_Type_getNamedType -func (t *Type) wrapNamedType(ret *Type) { return } - +// llgo:link Type.NamedType C.clang_Type_getNamedType func (t Type) NamedType() (ret Type) { - t.wrapNamedType(&ret) return } @@ -2194,39 +2089,27 @@ const ( * If the type declaration is a dependent type, CXTypeLayoutError_Dependent is * returned. */ -// llgo:link (*Type).wrapSizeOf C.wrap_clang_Type_getSizeOf -func (t *Type) wrapSizeOf() (ret c.LongLong) { - return 0 -} - +// llgo:link Type.SizeOf C.clang_Type_getSizeOf func (t Type) SizeOf() (ret c.LongLong) { - return t.wrapSizeOf() + return } /** * Determine whether the given cursor represents an anonymous * tag or namespace */ -// llgo:link (*Cursor).wrapIsAnonymous C.wrap_clang_Cursor_isAnonymous -func (c *Cursor) wrapIsAnonymous() (ret c.Uint) { - return 0 -} - +// llgo:link Cursor.IsAnonymous C.clang_Cursor_isAnonymous func (c Cursor) IsAnonymous() (ret c.Uint) { - return c.wrapIsAnonymous() + return } /** * Determine whether the given cursor represents an anonymous record * declaration. */ -// llgo:link (*Cursor).wrapIsAnonymousRecordDecl C.wrap_clang_Cursor_isAnonymousRecordDecl -func (c *Cursor) wrapIsAnonymousRecordDecl() (ret c.Uint) { - return 0 -} - +// llgo:link Cursor.IsAnonymousRecordDecl C.clang_Cursor_isAnonymousRecordDecl func (c Cursor) IsAnonymousRecordDecl() (ret c.Uint) { - return c.wrapIsAnonymousRecordDecl() + return } /** @@ -2249,13 +2132,9 @@ const ( * its parent scope is returned. Otherwise, if the cursor refers to a base * specifier or access specifier, the specifier itself is returned. */ -// llgo:link (*Cursor).wrapCXXAccessSpecifier C.wrap_clang_getCXXAccessSpecifier -func (*Cursor) wrapCXXAccessSpecifier() (spec CXXAccessSpecifier) { - return 0 -} - -func (c Cursor) CXXAccessSpecifier() CXXAccessSpecifier { - return c.wrapCXXAccessSpecifier() +// llgo:link Cursor.CXXAccessSpecifier C.clang_getCXXAccessSpecifier +func (c Cursor) CXXAccessSpecifier() (ret CXXAccessSpecifier) { + return } type StorageClass c.Int @@ -2281,13 +2160,9 @@ const ( * If the passed in Cursor is not a function or variable declaration, * CX_SC_Invalid is returned else the storage class. */ -// llgo:link (*Cursor).wrapStorageClass C.wrap_clang_Cursor_getStorageClass -func (*Cursor) wrapStorageClass() (ret StorageClass) { - return 0 -} - +// llgo:link Cursor.StorageClass C.clang_Cursor_getStorageClass func (c Cursor) StorageClass() (ret StorageClass) { - return c.wrapStorageClass() + return } /** @@ -2299,25 +2174,17 @@ func (c Cursor) StorageClass() (ret StorageClass) { * compared across translation units to determine, e.g., when references in * one translation refer to an entity defined in another translation unit. */ -// llgo:link (*Cursor).wrapUSR C.wrap_clang_getCursorUSR -func (*Cursor) wrapUSR() (ret String) { - return -} - +// llgo:link Cursor.USR C.clang_getCursorUSR func (c Cursor) USR() (ret String) { - return c.wrapUSR() + return } /** * Retrieve a name for the entity referenced by this cursor. */ -// llgo:link (*Cursor).wrapString C.wrap_clang_getCursorSpelling -func (*Cursor) wrapString() (ret String) { - return -} - +// llgo:link Cursor.String C.clang_getCursorSpelling func (c Cursor) String() (ret String) { - return c.wrapString() + return } /** @@ -2327,12 +2194,9 @@ func (c Cursor) String() (ret String) { * such as the parameters of a function or template or the arguments of a * class template specialization. */ -// llgo:link (*Cursor).wrapDisplayName C.wrap_clang_getCursorDisplayName -func (*Cursor) wrapDisplayName() (ret String) { - return -} +// llgo:link Cursor.DisplayName C.clang_getCursorDisplayName func (c Cursor) DisplayName() (ret String) { - return c.wrapDisplayName() + return } /** For a cursor that is a reference, retrieve a cursor representing the @@ -2345,32 +2209,26 @@ func (c Cursor) DisplayName() (ret String) { * definition, it returns that declaration or definition unchanged. * Otherwise, returns the NULL cursor. */ -// llgo:link (*Cursor).wrapReferenced C.wrap_clang_getCursorReferenced -func (*Cursor) wrapReferenced(referenced *Cursor) {} - +// llgo:link Cursor.Referenced C.clang_getCursorReferenced func (c Cursor) Referenced() (referenced Cursor) { - c.wrapReferenced(&referenced) return } /** * Returns non-zero if the given cursor is a variadic function or method. */ -// llgo:link (*Cursor).wrapIsVariadic C.wrap_clang_Cursor_isVariadic -func (*Cursor) wrapIsVariadic() (ret c.Uint) { return 0 } - -func (c Cursor) IsVariadic() (ret c.Uint) { return c.wrapIsVariadic() } +// llgo:link Cursor.IsVariadic C.clang_Cursor_isVariadic +func (c Cursor) IsVariadic() (ret c.Uint) { + return +} /** * Given a cursor that represents a declaration, return the associated * comment's source range. The range may include multiple consecutive comments * with whitespace in between. */ -// llgo:link (*Cursor).wrapCommentRange C.wrap_clang_Cursor_getCommentRange -func (c *Cursor) wrapCommentRange(ret *SourceRange) {} - +// llgo:link Cursor.CommentRange C.clang_Cursor_getCommentRange func (c Cursor) CommentRange() (loc SourceRange) { - c.wrapCommentRange(&loc) return } @@ -2378,127 +2236,91 @@ func (c Cursor) CommentRange() (loc SourceRange) { * Given a cursor that represents a declaration, return the associated * comment text, including comment markers. */ -// llgo:link (*Cursor).wrapRawCommentText C.wrap_clang_Cursor_getRawCommentText -func (c *Cursor) wrapRawCommentText() (ret String) { - return -} - +// llgo:link Cursor.RawCommentText C.clang_Cursor_getRawCommentText func (c Cursor) RawCommentText() (ret String) { - return c.wrapRawCommentText() + return } /** * Retrieve a name for the entity referenced by this cursor. */ -// llgo:link (*Cursor).wrapMangling C.wrap_clang_Cursor_getMangling -func (*Cursor) wrapMangling() (ret String) { - return -} - +// llgo:link Cursor.Mangling C.clang_Cursor_getMangling func (c Cursor) Mangling() (ret String) { - return c.wrapMangling() + return } /** * Determine if a C++ constructor is a converting constructor. */ -// llgo:link (*Cursor).wrapIsConvertingConstructor C.wrap_clang_CXXConstructor_isConvertingConstructor -func (c *Cursor) wrapIsConvertingConstructor() (ret c.Uint) { - return 0 -} - +// llgo:link Cursor.IsConvertingConstructor C.clang_CXXConstructor_isConvertingConstructor func (c Cursor) IsConvertingConstructor() (ret c.Uint) { - return c.wrapIsConvertingConstructor() + return } /** * Determine if a C++ constructor is a copy constructor. */ -// llgo:link (*Cursor).wrapIsCopyConstructor C.wrap_clang_CXXConstructor_isCopyConstructor -func (c *Cursor) wrapIsCopyConstructor() (ret c.Uint) { - return 0 -} +// llgo:link Cursor.IsCopyConstructor C.clang_CXXConstructor_isCopyConstructor func (c Cursor) IsCopyConstructor() (ret c.Uint) { - return c.wrapIsCopyConstructor() + return } /** * Determine if a C++ constructor is the default constructor. */ -// llgo:link (*Cursor).wrapIsDefaultConstructor C.wrap_clang_CXXConstructor_isDefaultConstructor -func (c *Cursor) wrapIsDefaultConstructor() (ret c.Uint) { - return 0 -} +// llgo:link Cursor.IsDefaultConstructor C.clang_CXXConstructor_isDefaultConstructor func (c Cursor) IsDefaultConstructor() (ret c.Uint) { - return c.wrapIsDefaultConstructor() + return } /** * Determine if a C++ constructor is a move constructor. */ -// llgo:link (*Cursor).wrapIsMoveConstructor C.wrap_clang_CXXConstructor_isMoveConstructor -func (c *Cursor) wrapIsMoveConstructor() (ret c.Uint) { - return 0 -} +// llgo:link Cursor.IsMoveConstructor C.clang_CXXConstructor_isMoveConstructor func (c Cursor) IsMoveConstructor() (ret c.Uint) { - return c.wrapIsMoveConstructor() + return } /** * Determine if a C++ field is declared 'mutable'. */ -// llgo:link (*Cursor).wrapIsMutable C.wrap_clang_CXXField_isMutable -func (c *Cursor) wrapIsMutable() (ret c.Uint) { - return 0 -} +// llgo:link Cursor.IsMutable C.clang_CXXField_isMutable func (c Cursor) IsMutable() (ret c.Uint) { - return c.wrapIsMutable() + return } /** * Determine if a C++ method is declared '= default'. */ -// llgo:link (*Cursor).wrapIsDefaulted C.wrap_clang_CXXMethod_isDefaulted -func (c *Cursor) wrapIsDefaulted() (ret c.Uint) { - return 0 -} +// llgo:link Cursor.IsDefaulted C.clang_CXXMethod_isDefaulted func (c Cursor) IsDefaulted() (ret c.Uint) { - return c.wrapIsDefaulted() + return } /** * Determine if a C++ method is declared '= delete'. */ -// llgo:link (*Cursor).wrapIsDeleted C.wrap_clang_CXXMethod_isDeleted -func (c *Cursor) wrapIsDeleted() (ret c.Uint) { - return 0 -} +// llgo:link Cursor.IsDeleted C.clang_CXXMethod_isDeleted func (c Cursor) IsDeleted() (ret c.Uint) { - return c.wrapIsDeleted() + return } /** * Determine if a C++ member function or member function template is * pure virtual. */ -// llgo:link (*Cursor).wrapIsPureVirtual C.wrap_clang_CXXMethod_isPureVirtual -func (c *Cursor) wrapIsPureVirtual() (ret c.Uint) { - return 0 -} +// llgo:link Cursor.IsPureVirtual C.clang_CXXMethod_isPureVirtual func (c Cursor) IsPureVirtual() (ret c.Uint) { - return c.wrapIsPureVirtual() + return } /** * Determine if a C++ member function or member function template is * declared 'static'. */ -// llgo:link (*Cursor).wrapIsStatic C.wrap_clang_CXXMethod_isStatic -func (c *Cursor) wrapIsStatic() (ret c.Uint) { - return 0 -} +// llgo:link Cursor.IsStatic C.clang_CXXMethod_isStatic func (c Cursor) IsStatic() (ret c.Uint) { - return c.wrapIsStatic() + return } /** @@ -2506,12 +2328,9 @@ func (c Cursor) IsStatic() (ret c.Uint) { * explicitly declared 'virtual' or if it overrides a virtual method from * one of the base classes. */ -// llgo:link (*Cursor).wrapIsVirtual C.wrap_clang_CXXMethod_isVirtual -func (c *Cursor) wrapIsVirtual() (ret c.Uint) { - return 0 -} +// llgo:link Cursor.IsVirtual C.clang_CXXMethod_isVirtual func (c Cursor) IsVirtual() (ret c.Uint) { - return c.wrapIsVirtual() + return } /** @@ -2537,12 +2356,9 @@ func (c Cursor) IsVirtual() (ret c.Uint) { * * Is not. */ -// llgo:link (*Cursor).wrapIsCopyAssignmentOperator C.wrap_clang_CXXMethod_isCopyAssignmentOperator -func (c *Cursor) wrapIsCopyAssignmentOperator() (ret c.Uint) { - return 0 -} +// llgo:link Cursor.IsCopyAssignmentOperator C.clang_CXXMethod_isCopyAssignmentOperator func (c Cursor) IsCopyAssignmentOperator() (ret c.Uint) { - return c.wrapIsCopyAssignmentOperator() + return } /** @@ -2568,12 +2384,9 @@ func (c Cursor) IsCopyAssignmentOperator() (ret c.Uint) { * * Is not. */ -// llgo:link (*Cursor).wrapIsMoveAssignmentOperator C.wrap_clang_CXXMethod_isMoveAssignmentOperator -func (c *Cursor) wrapIsMoveAssignmentOperator() (ret c.Uint) { - return 0 -} +// llgo:link Cursor.IsMoveAssignmentOperator C.clang_CXXMethod_isMoveAssignmentOperator func (c Cursor) IsMoveAssignmentOperator() (ret c.Uint) { - return c.wrapIsMoveAssignmentOperator() + return } /** @@ -2619,59 +2432,43 @@ func (c Cursor) IsMoveAssignmentOperator() (ret c.Uint) { * This function will return 0 for the constructor and 1 for * the conversion function. */ -// llgo:link (*Cursor).wrapIsExplicit C.wrap_clang_CXXMethod_isExplicit -func (c *Cursor) wrapIsExplicit() (ret c.Uint) { - return 0 -} +// llgo:link Cursor.IsExplicit C.clang_CXXMethod_isExplicit func (c Cursor) IsExplicit() (ret c.Uint) { - return c.wrapIsExplicit() + return } /** * Determine if a C++ record is abstract, i.e. whether a class or struct * has a pure virtual member function. */ -// llgo:link (*Cursor).wrapIsAbstract C.wrap_clang_CXXRecord_isAbstract -func (c *Cursor) wrapIsAbstract() (ret c.Uint) { - return 0 -} +// llgo:link Cursor.IsAbstract C.clang_CXXRecord_isAbstract func (c Cursor) IsAbstract() (ret c.Uint) { - return c.wrapIsAbstract() + return } /** * Determine if an enum declaration refers to a scoped enum. */ -// llgo:link (*Cursor).wrapIsScoped C.wrap_clang_EnumDecl_isScoped -func (c *Cursor) wrapIsScoped() (ret c.Uint) { - return 0 -} +// llgo:link Cursor.IsScoped C.clang_EnumDecl_isScoped func (c Cursor) IsScoped() (ret c.Uint) { - return c.wrapIsScoped() + return } /** * Determine if a C++ member function or member function template is * declared 'const'. */ -// llgo:link (*Cursor).wrapIsConst C.wrap_clang_CXXMethod_isConst -func (c *Cursor) wrapIsConst() (ret c.Uint) { - return 0 -} +// llgo:link Cursor.IsConst C.clang_CXXMethod_isConst func (c Cursor) IsConst() (ret c.Uint) { - return c.wrapIsConst() + return } /** * Determine the kind of the given token. */ -// llgo:link (*Token).wrapKind C.wrap_clang_getTokenKind -func (c *Token) wrapKind() (ret TokenKind) { - return -} - +// llgo:link Token.Kind C.clang_getTokenKind func (c Token) Kind() (ret TokenKind) { - return c.wrapKind() + return } /** @@ -2680,13 +2477,9 @@ func (c Token) Kind() (ret TokenKind) { * The spelling of a token is the textual representation of that token, e.g., * the text of an identifier or keyword. */ -// llgo:link (*TranslationUnit).wrapToken C.wrap_clang_getTokenSpelling -func (*TranslationUnit) wrapToken(token *Token) (ret String) { - return -} - +// llgo:link (*TranslationUnit).Token C.clang_getTokenSpelling func (c *TranslationUnit) Token(token Token) (ret String) { - return c.wrapToken(&token) + return } /** @@ -2737,30 +2530,12 @@ const ( * \returns a non-zero value if the traversal was terminated * prematurely by the visitor returning \c CXChildVisit_Break. */ -//go:linkname wrapVisitChildren C.wrap_clang_visitChildren -func wrapVisitChildren( - cusor *Cursor, - fn wrapVisitor, - clientData ClientData) c.Uint { - return 0 -} - -//llgo:type C -type wrapVisitor func(cursor, parent *Cursor, clientData ClientData) ChildVisitResult - -type wrapData struct { - data ClientData - fn Visitor -} - +//go:linkname VisitChildren C.clang_visitChildren func VisitChildren( root Cursor, fn Visitor, - clientData ClientData) c.Uint { - return wrapVisitChildren(&root, func(cursor, parent *Cursor, data ClientData) ChildVisitResult { - p := (*wrapData)(data) - return p.fn(*cursor, *parent, p.data) - }, unsafe.Pointer(&wrapData{clientData, fn})) + clientData ClientData) (ret c.Uint) { + return } //llgo:type C @@ -2806,11 +2581,8 @@ func GetInclusions(tu *TranslationUnit, visitor InclusionVisitor, client_data Cl * array. * */ -// llgo:link (*TranslationUnit).wrapTokenize C.wrap_clang_tokenize -func (t *TranslationUnit) wrapTokenize(ran *SourceRange, tokens **Token, numTokens *c.Uint) {} - +// llgo:link (*TranslationUnit).Tokenize C.clang_tokenize func (t *TranslationUnit) Tokenize(ran SourceRange, tokens **Token, numTokens *c.Uint) { - t.wrapTokenize(&ran, tokens, numTokens) } /** @@ -2822,11 +2594,9 @@ func (t *TranslationUnit) DisposeTokens(tokens *Token, numTokens c.Uint) {} /** * Returns non-zero if the given source location is in a system header. */ -// llgo:link (*SourceLocation).wrapIsInSystemHeader C.wrap_clang_Location_isInSystemHeader -func (l *SourceLocation) wrapIsInSystemHeader() (ret c.Uint) { return 0 } - +// llgo:link SourceLocation.IsInSystemHeader C.clang_Location_isInSystemHeader func (l SourceLocation) IsInSystemHeader() (ret c.Uint) { - return l.wrapIsInSystemHeader() + return } /** @@ -2851,30 +2621,27 @@ func (l SourceLocation) IsInSystemHeader() (ret c.Uint) { * \param offset [out] if non-NULL, will be set to the offset into the * buffer to which the given source location points. */ -// llgo:link (*SourceLocation).wrapSpellingLocation C.wrap_clang_getSpellingLocation -func (l *SourceLocation) wrapSpellingLocation(file *File, line, column, offset *c.Uint) {} - +// llgo:link SourceLocation.SpellingLocation C.clang_getSpellingLocation func (l SourceLocation) SpellingLocation(file *File, line, column, offset *c.Uint) { - l.wrapSpellingLocation(file, line, column, offset) } func (l SourceLocation) File() (ret File) { - l.wrapSpellingLocation(&ret, nil, nil, nil) + l.SpellingLocation(&ret, nil, nil, nil) return } func (l SourceLocation) Line() (ret c.Uint) { - l.wrapSpellingLocation(nil, &ret, nil, nil) + l.SpellingLocation(nil, &ret, nil, nil) return } func (l SourceLocation) Column() (ret c.Uint) { - l.wrapSpellingLocation(nil, nil, &ret, nil) + l.SpellingLocation(nil, nil, &ret, nil) return } func (l SourceLocation) Offset() (ret c.Uint) { - l.wrapSpellingLocation(nil, nil, nil, &ret) + l.SpellingLocation(nil, nil, nil, &ret) return } @@ -2918,22 +2685,16 @@ func (l SourceLocation) Offset() (ret c.Uint) { * \param column [out] if non-NULL, will be set to the column number of the * source location. For an invalid source location, zero is returned. */ -// llgo:link (*SourceLocation).wrapPresumedLocation C.wrap_clang_getPresumedLocation -func (l *SourceLocation) wrapPresumedLocation(filename *String, line, column *c.Uint) {} - +// llgo:link SourceLocation.PresumedLocation C.clang_getPresumedLocation func (l SourceLocation) PresumedLocation(filename *String, line, column *c.Uint) { - l.wrapPresumedLocation(filename, line, column) } /** * Retrieve a source location representing the first character within a * source range. */ -// llgo:link (*SourceRange).wrapRangeStart C.wrap_clang_getRangeStart -func (r *SourceRange) wrapRangeStart(loc *SourceLocation) { return } - +// llgo:link SourceRange.RangeStart C.clang_getRangeStart func (r SourceRange) RangeStart() (loc SourceLocation) { - r.wrapRangeStart(&loc) return } @@ -2941,11 +2702,8 @@ func (r SourceRange) RangeStart() (loc SourceLocation) { * Retrieve a source location representing the last character within a * source range. */ -// llgo:link (*SourceRange).wrapRangeEnd C.wrap_clang_getRangeEnd -func (r *SourceRange) wrapRangeEnd(loc *SourceLocation) { return } - +// llgo:link SourceRange.RangeEnd C.clang_getRangeEnd func (r SourceRange) RangeEnd() (loc SourceLocation) { - r.wrapRangeEnd(&loc) return } From 11599c5a54a0b1f5976afbc1af52f32171c2e28f Mon Sep 17 00:00:00 2001 From: visualfc Date: Wed, 27 Aug 2025 17:54:55 +0800 Subject: [PATCH 09/11] c/os: fix for drawin amd64 --- c/os/dir.go | 17 ++++++ c/os/dir_darwin_amd64.go | 17 ++++++ c/os/dir_unix.go | 20 +++++++ c/os/os.go | 91 +++++++----------------------- c/os/{os_linux.go => os_darwin.go} | 24 ++++++-- c/os/os_nonwasm.go | 69 ++++++++++++++++++++++ c/os/os_other.go | 24 ++++++-- c/os/os_wasm.go | 1 + c/os/stat.go | 19 +++++++ c/os/stat_darwin.go | 16 ++++++ 10 files changed, 217 insertions(+), 81 deletions(-) create mode 100644 c/os/dir.go create mode 100644 c/os/dir_darwin_amd64.go create mode 100644 c/os/dir_unix.go rename c/os/{os_linux.go => os_darwin.go} (76%) create mode 100644 c/os/os_nonwasm.go create mode 100644 c/os/os_wasm.go create mode 100644 c/os/stat.go create mode 100644 c/os/stat_darwin.go diff --git a/c/os/dir.go b/c/os/dir.go new file mode 100644 index 00000000..ba4569ef --- /dev/null +++ b/c/os/dir.go @@ -0,0 +1,17 @@ +package os + +import ( + _ "unsafe" + + "github.com/goplus/lib/c" +) + +type DIR struct { + Unused [0]byte +} + +//go:linkname Opendir C.opendir +func Opendir(name *c.Char) *DIR + +//go:linkname Closedir C.closedir +func Closedir(dir *DIR) c.Int diff --git a/c/os/dir_darwin_amd64.go b/c/os/dir_darwin_amd64.go new file mode 100644 index 00000000..7b287614 --- /dev/null +++ b/c/os/dir_darwin_amd64.go @@ -0,0 +1,17 @@ +package os + +import ( + _ "unsafe" + + "github.com/goplus/lib/c" + "github.com/goplus/lib/c/syscall" +) + +//go:linkname Fdopendir C.fdopendir$INODE64 +func Fdopendir(fd c.Int) *DIR + +//go:linkname Readdir C.readdir$INODE64 +func Readdir(dir *DIR) *syscall.Dirent + +//go:linkname Fstatat C.fstatat$INODE64 +func Fstatat(dirfd c.Int, path *c.Char, buf *StatT, flags c.Int) c.Int diff --git a/c/os/dir_unix.go b/c/os/dir_unix.go new file mode 100644 index 00000000..e6208b89 --- /dev/null +++ b/c/os/dir_unix.go @@ -0,0 +1,20 @@ +//go:build !(darwin && amd64) +// +build !darwin !amd64 + +package os + +import ( + _ "unsafe" + + "github.com/goplus/lib/c" + "github.com/goplus/lib/c/syscall" +) + +//go:linkname Fdopendir C.fdopendir +func Fdopendir(fd c.Int) *DIR + +//go:linkname Readdir C.readdir +func Readdir(dir *DIR) *syscall.Dirent + +//go:linkname Fstatat C.fstatat +func Fstatat(dirfd c.Int, path *c.Char, buf *StatT, flags c.Int) c.Int diff --git a/c/os/os.go b/c/os/os.go index e3c3ddc6..cd126447 100644 --- a/c/os/os.go +++ b/c/os/os.go @@ -16,10 +16,6 @@ package os -// #include -// #include -import "C" - import ( _ "unsafe" @@ -27,10 +23,6 @@ import ( "github.com/goplus/lib/c/syscall" ) -const ( - PATH_MAX = C.PATH_MAX -) - const ( /* get file status flags */ F_GETFL = 3 @@ -58,14 +50,6 @@ const ( EAGAIN = 35 ) -type ( - ModeT C.mode_t - UidT C.uid_t - GidT C.gid_t - OffT C.off_t - DevT C.dev_t -) - type ( StatT = syscall.Stat_t ) @@ -94,30 +78,21 @@ func Readlink(path *c.Char, buf c.Pointer, bufsize uintptr) int //go:linkname Unlink C.unlink func Unlink(path *c.Char) c.Int +//go:linkname Unlinkat C.unlinkat +func Unlinkat(dirfd c.Int, path *c.Char, flags c.Int) c.Int + //go:linkname Remove C.remove func Remove(path *c.Char) c.Int //go:linkname Rename C.rename func Rename(oldpath *c.Char, newpath *c.Char) c.Int -//go:linkname Stat C.stat -func Stat(path *c.Char, buf *StatT) c.Int - -//go:linkname Lstat C.lstat -func Lstat(path *c.Char, buf *StatT) c.Int - //go:linkname Truncate C.truncate func Truncate(path *c.Char, length OffT) c.Int //go:linkname Chmod C.chmod func Chmod(path *c.Char, mode ModeT) c.Int -//go:linkname Chown C.chown -func Chown(path *c.Char, owner UidT, group GidT) c.Int - -//go:linkname Lchown C.lchown -func Lchown(path *c.Char, owner UidT, group GidT) c.Int - // ----------------------------------------------------------------------------- //go:linkname Getcwd C.getcwd @@ -160,9 +135,6 @@ func Fchmodat(dirfd c.Int, path *c.Char, mode ModeT, flags c.Int) c.Int //go:linkname Fchownat C.fchownat func Fchownat(dirfd c.Int, path *c.Char, owner UidT, group GidT, flags c.Int) c.Int -//go:linkname Fstatat C.fstatat -func Fstatat(dirfd c.Int, path *c.Char, buf *StatT, flags c.Int) c.Int - // ----------------------------------------------------------------------------- //go:linkname Open C.open @@ -183,15 +155,8 @@ func Dup(fd c.Int) c.Int //go:linkname Dup2 C.dup2 func Dup2(oldfd c.Int, newfd c.Int) c.Int -/* TODO(xsw): -On Alpha, IA-64, MIPS, SuperH, and SPARC/SPARC64, pipe() has the following prototype: -struct fd_pair { - long fd[2]; -}; -struct fd_pair pipe(void); -*/ -//go:linkname Pipe C.pipe -func Pipe(fds *[2]c.Int) c.Int +//go:linkname Dup3 C.dup3 +func Dup3(oldfd c.Int, newfd c.Int, flags c.Int) c.Int //go:linkname Mkfifo C.mkfifo func Mkfifo(path *c.Char, mode ModeT) c.Int @@ -223,9 +188,6 @@ func Fchmod(fd c.Int, mode ModeT) c.Int //go:linkname Fchown C.fchown func Fchown(fd c.Int, owner UidT, group GidT) c.Int -//go:linkname Fstat C.fstat -func Fstat(fd c.Int, buf *StatT) c.Int - //go:linkname Isatty C.isatty func Isatty(fd c.Int) c.Int @@ -264,17 +226,25 @@ func Execvp(file *c.Char, argv **c.Char) c.Int type PidT c.Int -//go:linkname Fork C.fork -func Fork() PidT - //go:linkname Getpid C.getpid func Getpid() PidT //go:linkname Getppid C.getppid func Getppid() PidT -//go:linkname Kill C.kill -func Kill(pid PidT, sig c.Int) c.Int +// Invoke `system call' number SYSNO, passing it the remaining arguments. +// This is completely system-dependent, and not often useful. + +// In Unix, `syscall' sets `errno' for all errors and most calls return -1 +// for errors; in many systems you cannot pass arguments or get return +// values for all system calls (`pipe', `fork', and `getppid' typically +// among them). + +// In Mach, all system calls take normal arguments and always return an +// error code (zero for success). +// +//go:linkname Syscall C.syscall +func Syscall(sysno c.Long, __llgo_va_list ...any) c.Long // If wait() returns due to a stopped or terminated child process, the process ID // of the child is returned to the calling process. Otherwise, a value of -1 is @@ -293,9 +263,6 @@ func Wait(statLoc *c.Int) PidT //go:linkname Wait3 C.wait3 func Wait3(statLoc *c.Int, options c.Int, rusage *syscall.Rusage) PidT -//go:linkname Wait4 C.wait4 -func Wait4(pid PidT, statLoc *c.Int, options c.Int, rusage *syscall.Rusage) PidT - //go:linkname Waitpid C.waitpid func Waitpid(pid PidT, statLoc *c.Int, options c.Int) PidT @@ -304,29 +271,9 @@ func Waitpid(pid PidT, statLoc *c.Int, options c.Int) PidT //go:linkname Exit C.exit func Exit(c.Int) -//go:linkname Getuid C.getuid -func Getuid() UidT - -//go:linkname Geteuid C.geteuid -func Geteuid() UidT - -//go:linkname Getgid C.getgid -func Getgid() GidT - -//go:linkname Getegid C.getegid -func Getegid() GidT - -// ----------------------------------------------------------------------------- - -//go:linkname Getrlimit C.getrlimit -func Getrlimit(resource c.Int, rlp *syscall.Rlimit) c.Int - -//go:linkname Setrlimit C.setrlimit -func Setrlimit(resource c.Int, rlp *syscall.Rlimit) c.Int - // ----------------------------------------------------------------------------- -// Upon successful completion, the value 0 is returned; otherwise the value -1 +// Upon successful completion, the value 0 is returned; otherwise the value -1 // is returned and the global variable errno is set to indicate the error. // //go:linkname Sysctl C.sysctl diff --git a/c/os/os_linux.go b/c/os/os_darwin.go similarity index 76% rename from c/os/os_linux.go rename to c/os/os_darwin.go index 6886a3f2..3b703862 100644 --- a/c/os/os_linux.go +++ b/c/os/os_darwin.go @@ -1,5 +1,3 @@ -//go:build linux - /* * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved. * @@ -18,12 +16,28 @@ package os -import "C" +import ( + _ "unsafe" + + "github.com/goplus/lib/c" +) const ( LLGoFiles = "_os/os.c" LLGoPackage = "link" ) -//go:linkname Clearenv C.clearenv -func Clearenv() +const ( + PATH_MAX = 1024 +) + +type ( + ModeT uint16 + UidT uint32 + GidT uint32 + OffT int64 + DevT int32 +) + +//go:linkname Clearenv C.llgoClearenv +func Clearenv() c.Int diff --git a/c/os/os_nonwasm.go b/c/os/os_nonwasm.go new file mode 100644 index 00000000..2a4fed0c --- /dev/null +++ b/c/os/os_nonwasm.go @@ -0,0 +1,69 @@ +//go:build !wasm + +/* + * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package os + +import ( + _ "unsafe" + + "github.com/goplus/lib/c" + "github.com/goplus/lib/c/syscall" +) + +//go:linkname Getuid C.getuid +func Getuid() UidT + +//go:linkname Geteuid C.geteuid +func Geteuid() UidT + +//go:linkname Getgid C.getgid +func Getgid() GidT + +//go:linkname Getegid C.getegid +func Getegid() GidT + +//go:linkname Chown C.chown +func Chown(path *c.Char, owner UidT, group GidT) c.Int + +//go:linkname Lchown C.lchown +func Lchown(path *c.Char, owner UidT, group GidT) c.Int + +//go:linkname Getrlimit C.getrlimit +func Getrlimit(resource c.Int, rlp *syscall.Rlimit) c.Int + +//go:linkname Setrlimit C.setrlimit +func Setrlimit(resource c.Int, rlp *syscall.Rlimit) c.Int + +//go:linkname Wait4 C.wait4 +func Wait4(pid PidT, statLoc *c.Int, options c.Int, rusage *syscall.Rusage) PidT + +/* TODO(xsw): + On Alpha, IA-64, MIPS, SuperH, and SPARC/SPARC64, pipe() has the following prototype: + struct fd_pair { + long fd[2]; + }; + struct fd_pair pipe(void); +*/ +//go:linkname Pipe C.pipe +func Pipe(fds *[2]c.Int) c.Int + +//go:linkname Fork C.fork +func Fork() PidT + +//go:linkname Kill C.kill +func Kill(pid PidT, sig c.Int) c.Int diff --git a/c/os/os_other.go b/c/os/os_other.go index ae78cd41..4c3b1cc6 100644 --- a/c/os/os_other.go +++ b/c/os/os_other.go @@ -1,4 +1,4 @@ -//go:build !linux +//go:build !darwin /* * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved. @@ -18,12 +18,28 @@ package os -import "C" +import ( + _ "unsafe" + + "github.com/goplus/lib/c" +) const ( LLGoFiles = "_os/os.c" LLGoPackage = "link" ) -//go:linkname Clearenv C.llgoClearenv -func Clearenv() +const ( + PATH_MAX = 4096 +) + +type ( + ModeT uint32 + UidT uint32 + GidT uint32 + OffT int64 + DevT uint64 +) + +//go:linkname Clearenv C.clearenv +func Clearenv() c.Int diff --git a/c/os/os_wasm.go b/c/os/os_wasm.go new file mode 100644 index 00000000..cf1f67e0 --- /dev/null +++ b/c/os/os_wasm.go @@ -0,0 +1 @@ +package os diff --git a/c/os/stat.go b/c/os/stat.go new file mode 100644 index 00000000..a113e95d --- /dev/null +++ b/c/os/stat.go @@ -0,0 +1,19 @@ +//go:build !darwin +// +build !darwin + +package os + +import ( + _ "unsafe" + + "github.com/goplus/lib/c" +) + +//go:linkname Stat C.stat +func Stat(path *c.Char, buf *StatT) c.Int + +//go:linkname Lstat C.lstat +func Lstat(path *c.Char, buf *StatT) c.Int + +//go:linkname Fstat C.fstat +func Fstat(fd c.Int, buf *StatT) c.Int diff --git a/c/os/stat_darwin.go b/c/os/stat_darwin.go new file mode 100644 index 00000000..75e774c0 --- /dev/null +++ b/c/os/stat_darwin.go @@ -0,0 +1,16 @@ +package os + +import ( + _ "unsafe" + + "github.com/goplus/lib/c" +) + +//go:linkname Stat C.stat64 +func Stat(path *c.Char, buf *StatT) c.Int + +//go:linkname Lstat C.lstat64 +func Lstat(path *c.Char, buf *StatT) c.Int + +//go:linkname Fstat C.fstat64 +func Fstat(fd c.Int, buf *StatT) c.Int From 6eb9ff137a0d672bec40a2a0264922fca5742ad3 Mon Sep 17 00:00:00 2001 From: visualfc Date: Thu, 28 Aug 2025 10:09:58 +0800 Subject: [PATCH 10/11] c/os: demo/readdir --- c/os/_demo/readdir/main.go | 46 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 c/os/_demo/readdir/main.go diff --git a/c/os/_demo/readdir/main.go b/c/os/_demo/readdir/main.go new file mode 100644 index 00000000..8b6bb3fd --- /dev/null +++ b/c/os/_demo/readdir/main.go @@ -0,0 +1,46 @@ +package main + +import ( + "github.com/goplus/lib/c" + "github.com/goplus/lib/c/os" +) + +func main() { + fd := os.Open(c.Str("."), os.O_RDONLY) + if fd == 0 { + c.Perror(c.Str("open error")) + os.Exit(1) + } + dir := os.Fdopendir(fd) + if dir == nil { + c.Perror(c.Str("fdopendir error")) + os.Exit(1) + } + var check int + for { + entry := os.Readdir(dir) + if entry == nil { + break + } + c.Printf(c.Str("%s %d %d\n"), &entry.Name[0], entry.Namlen, entry.Type) + switch entry.Namlen { + case 1: + if entry.Name[0] == '.' { + check++ + } + case 2: + if entry.Name[0] == '.' && entry.Name[1] == '.' { + check++ + } + case 7: + if c.Strncmp(&entry.Name[0], c.Str("main.go"), 7) == 0 { + check++ + } + } + } + os.Closedir(dir) + if check != 3 { + c.Printf(c.Str("readdir error\n")) + os.Exit(1) + } +} From d7433eee33218f2f8e21238899035af2a1f4e425 Mon Sep 17 00:00:00 2001 From: Li Jie Date: Tue, 14 Oct 2025 13:35:57 +0800 Subject: [PATCH 11/11] tls: add GC-safe pthread TLS handle and docs --- c/bdwgc/bdwgc.go | 6 +++ c/tls/tls.go | 125 ++++++++++++++++++++++++++++++++++++++++++++ c/tls/tls_nogc.go | 88 +++++++++++++++++++++++++++++++ c/tls/tls_test.go | 89 +++++++++++++++++++++++++++++++ docs/gc-safe-tls.md | 58 ++++++++++++++++++++ 5 files changed, 366 insertions(+) create mode 100644 c/tls/tls.go create mode 100644 c/tls/tls_nogc.go create mode 100644 c/tls/tls_test.go create mode 100644 docs/gc-safe-tls.md diff --git a/c/bdwgc/bdwgc.go b/c/bdwgc/bdwgc.go index 5d90607c..ecbad7f9 100644 --- a/c/bdwgc/bdwgc.go +++ b/c/bdwgc/bdwgc.go @@ -40,6 +40,12 @@ func Realloc(ptr c.Pointer, size uintptr) c.Pointer //go:linkname Free C.GC_free func Free(ptr c.Pointer) +//go:linkname AddRoots C.GC_add_roots +func AddRoots(start, end c.Pointer) + +//go:linkname RemoveRoots C.GC_remove_roots +func RemoveRoots(start, end c.Pointer) + // ----------------------------------------------------------------------------- //go:linkname RegisterFinalizer C.GC_register_finalizer diff --git a/c/tls/tls.go b/c/tls/tls.go new file mode 100644 index 00000000..c52b0978 --- /dev/null +++ b/c/tls/tls.go @@ -0,0 +1,125 @@ +//go:build !nogc + +package tls + +import ( + "unsafe" + + "github.com/goplus/lib/c" + "github.com/goplus/lib/c/bdwgc" + "github.com/goplus/lib/c/pthread" +) + +const slotRegistered = 1 << iota + +type slot[T any] struct { + value T + state uintptr + destructor func(*T) +} + +type Handle[T any] struct { + key pthread.Key + destructor func(*T) +} + +// Alloc creates a TLS handle whose storage is visible to the Boehm GC. +func Alloc[T any](destructor func(*T)) Handle[T] { + var key pthread.Key + if ret := key.Create(slotDestructor[T]); ret != 0 { + c.Fprintf(c.Stderr, c.Str("tls: pthread_key_create failed (errno=%d)\n"), ret) + panic("tls: failed to create thread local storage key") + } + return Handle[T]{key: key, destructor: destructor} +} + +// Get returns the value stored in the current thread's slot. +func (h Handle[T]) Get() T { + if ptr := h.key.Get(); ptr != nil { + return (*slot[T])(ptr).value + } + var zero T + return zero +} + +// Set stores v in the current thread's slot, creating it if necessary. +func (h Handle[T]) Set(v T) { + s := h.ensureSlot() + s.value = v +} + +// Clear zeroes the current thread's slot value without freeing the slot. +func (h Handle[T]) Clear() { + if ptr := h.key.Get(); ptr != nil { + s := (*slot[T])(ptr) + var zero T + s.value = zero + } +} + +func (h Handle[T]) ensureSlot() *slot[T] { + if ptr := h.key.Get(); ptr != nil { + return (*slot[T])(ptr) + } + size := unsafe.Sizeof(slot[T]{}) + mem := c.Calloc(1, size) + if mem == nil { + panic("tls: failed to allocate thread slot") + } + s := (*slot[T])(mem) + s.destructor = h.destructor + if existing := h.key.Get(); existing != nil { + c.Free(mem) + return (*slot[T])(existing) + } + if ret := h.key.Set(mem); ret != 0 { + c.Free(mem) + c.Fprintf(c.Stderr, c.Str("tls: pthread_setspecific failed (errno=%d)\n"), ret) + panic("tls: failed to set thread local storage value") + } + registerSlot(s) + return s +} + +func slotDestructor[T any](ptr c.Pointer) { + s := (*slot[T])(ptr) + if s == nil { + return + } + if s.destructor != nil { + s.destructor(&s.value) + } + deregisterSlot(s) + var zero T + s.value = zero + s.destructor = nil + c.Free(ptr) +} + +func registerSlot[T any](s *slot[T]) { + if s.state&slotRegistered != 0 { + return + } + start, end := s.rootRange() + if uintptr(end) > uintptr(start) { + bdwgc.AddRoots(start, end) + } + s.state |= slotRegistered +} + +func deregisterSlot[T any](s *slot[T]) { + if s == nil || s.state&slotRegistered == 0 { + return + } + start, end := s.rootRange() + if uintptr(end) > uintptr(start) { + bdwgc.RemoveRoots(start, end) + } + s.state &^= slotRegistered +} + +func (s *slot[T]) rootRange() (start, end c.Pointer) { + begin := unsafe.Pointer(s) + endPtr := unsafe.Pointer(uintptr(begin) + unsafe.Sizeof(*s)) + return c.Pointer(begin), c.Pointer(endPtr) +} diff --git a/c/tls/tls_nogc.go b/c/tls/tls_nogc.go new file mode 100644 index 00000000..dc296aad --- /dev/null +++ b/c/tls/tls_nogc.go @@ -0,0 +1,88 @@ +//go:build nogc +// +build nogc + +package tls + +import ( + "unsafe" + + "github.com/goplus/lib/c" + "github.com/goplus/lib/c/pthread" +) + +type slot[T any] struct { + value T + destructor func(*T) +} + +type Handle[T any] struct { + key pthread.Key + destructor func(*T) +} + +func Alloc[T any](destructor func(*T)) Handle[T] { + var key pthread.Key + if ret := key.Create(slotDestructor[T]); ret != 0 { + c.Fprintf(c.Stderr, c.Str("tls: pthread_key_create failed (errno=%d)\n"), ret) + panic("tls: failed to create thread local storage key") + } + return Handle[T]{key: key, destructor: destructor} +} + +func (h Handle[T]) Get() T { + if ptr := h.key.Get(); ptr != nil { + return (*slot[T])(ptr).value + } + var zero T + return zero +} + +func (h Handle[T]) Set(v T) { + s := h.ensureSlot() + s.value = v +} + +func (h Handle[T]) Clear() { + if ptr := h.key.Get(); ptr != nil { + s := (*slot[T])(ptr) + var zero T + s.value = zero + } +} + +func (h Handle[T]) ensureSlot() *slot[T] { + if ptr := h.key.Get(); ptr != nil { + return (*slot[T])(ptr) + } + size := unsafe.Sizeof(slot[T]{}) + mem := c.Calloc(1, size) + if mem == nil { + panic("tls: failed to allocate thread slot") + } + s := (*slot[T])(mem) + s.destructor = h.destructor + if existing := h.key.Get(); existing != nil { + c.Free(mem) + return (*slot[T])(existing) + } + if ret := h.key.Set(mem); ret != 0 { + c.Free(mem) + c.Fprintf(c.Stderr, c.Str("tls: pthread_setspecific failed (errno=%d)\n"), ret) + panic("tls: failed to set thread local storage value") + } + return s +} + +func slotDestructor[T any](ptr c.Pointer) { + s := (*slot[T])(ptr) + if s == nil { + return + } + if s.destructor != nil { + s.destructor(&s.value) + } + var zero T + s.value = zero + s.destructor = nil + c.Free(ptr) +} diff --git a/c/tls/tls_test.go b/c/tls/tls_test.go new file mode 100644 index 00000000..28803f6e --- /dev/null +++ b/c/tls/tls_test.go @@ -0,0 +1,89 @@ +//go:build llgo +// +build llgo + +package tls_test + +import ( + "sync" + "testing" + + "github.com/goplus/lib/c/tls" +) + +// slotValue is the type stored in the TLS tests. +type slotValue struct { + calls int +} + +func TestAllocSetGetClear(t *testing.T) { + var mu sync.Mutex + var destructCount int + handle := tls.Alloc[*slotValue](func(v **slotValue) { + mu.Lock() + defer mu.Unlock() + destructCount++ + if v != nil && *v != nil { + (*v).calls = -1 + } + }) + + if v := handle.Get(); v != nil { + t.Fatalf("unexpected initial value: %v", v) + } + + val := &slotValue{calls: 1} + handle.Set(val) + if got := handle.Get(); got != val { + t.Fatalf("Get() returned %p; want %p", got, val) + } + + handle.Clear() + if got := handle.Get(); got != nil { + t.Fatalf("expected nil after Clear, got %p", got) + } + + // Ensure destructor runs when the goroutine exits. + done := make(chan struct{}) + go func() { + defer close(done) + tmp := &slotValue{calls: 42} + handle.Set(tmp) + if got := handle.Get(); got != tmp { + t.Errorf("nested goroutine Get = %p; want %p", got, tmp) + } + }() + <-done + + mu.Lock() + deferCount := destructCount + mu.Unlock() + if deferCount == 0 { + t.Fatalf("expected destructor to run for goroutine TLS slot") + } +} + +func TestAllocIsThreadLocal(t *testing.T) { + handle := tls.Alloc[int](nil) + handle.Set(7) + if got := handle.Get(); got != 7 { + t.Fatalf("main goroutine value = %d; want 7", got) + } + + const want = 99 + done := make(chan struct{}) + go func() { + defer close(done) + if got := handle.Get(); got != 0 { + t.Errorf("new goroutine initial value = %d; want 0", got) + } + handle.Set(want) + if got := handle.Get(); got != want { + t.Errorf("new goroutine value = %d; want %d", got, want) + } + }() + <-done + + if got := handle.Get(); got != 7 { + t.Fatalf("main goroutine value changed to %d", got) + } +} diff --git a/docs/gc-safe-tls.md b/docs/gc-safe-tls.md new file mode 100644 index 00000000..aeadd383 --- /dev/null +++ b/docs/gc-safe-tls.md @@ -0,0 +1,58 @@ +# GC-safe Thread Local Storage + +The `c/tls` package implements a small helper around pthread TLS that keeps the +slot visible to the Boehm garbage collector when it is enabled. It is useful for +runtime features (such as defer stacks) that keep GC-managed pointers inside the +pthread-specific area, which Boehm does not scan by default. + +## Key ideas + +- The package exposes a generic `tls.Handle[T]` type. `tls.Alloc` creates a + handle and registers the slot with Boehm (`GC_add_roots`) in GC builds, or + falls back to a plain `pthread_setspecific` slot in `-tags nogc` builds. +- A slot stores a value of type `T` directly, so even non-pointer data (for + example counters) can be stored without an additional allocation. +- An optional destructor can be supplied when allocating the handle. It is + invoked whenever the owning thread exits, allowing callers to release + resources or reset global state. + +## Example + +```go +package main + +import ( + "log" + + "github.com/goplus/lib/c/tls" +) + +type stats struct { + handled int +} + +var threadStats = tls.Alloc[*stats](func(s **stats) { + // Destructor runs when the thread exits. + if *s != nil { + log.Printf("thread handled %d requests", (*s).handled) + } +}) + +func recordRequest() { + cur := threadStats.Get() + if cur == nil { + cur = &stats{} + } + cur.handled++ + threadStats.Set(cur) +} + +func resetStats() { + threadStats.Clear() +} +``` + +In GC builds the slot is automatically registered as a root, which prevents +Boehm from reclaiming any GC-managed memory reachable from the `stats` +instances. In builds that use `-tags nogc`, the helper still relies on pthread +TLS but simply skips the root registration step.