From e695e17c54d66d3d2d2a5c66ec4f522ad3908c7f Mon Sep 17 00:00:00 2001 From: andryblack Date: Thu, 20 Jan 2022 13:37:35 +0100 Subject: [PATCH 01/13] [ext] Add PICO-SDK module --- .gitmodules | 3 + ext/rp/README.md | 45 ++++++++++ ext/rp/address_mapped.h | 194 ++++++++++++++++++++++++++++++++++++++++ ext/rp/device.hpp.in | 32 +++++++ ext/rp/module.lb | 48 ++++++++++ ext/rp/pico-sdk | 1 + 6 files changed, 323 insertions(+) create mode 100644 ext/rp/README.md create mode 100644 ext/rp/address_mapped.h create mode 100644 ext/rp/device.hpp.in create mode 100644 ext/rp/module.lb create mode 160000 ext/rp/pico-sdk diff --git a/.gitmodules b/.gitmodules index 5af60003c3..2ade0e5db4 100644 --- a/.gitmodules +++ b/.gitmodules @@ -37,3 +37,6 @@ [submodule "ext/etlcpp/etl"] path = ext/etlcpp/etl url = https://github.com/modm-ext/etl-partial.git +[submodule "ext/rp/pico-sdk"] + path = ext/rp/pico-sdk + url = https://github.com/modm-ext/pico-sdk-partial.git diff --git a/ext/rp/README.md b/ext/rp/README.md new file mode 100644 index 0000000000..ebb4f30886 --- /dev/null +++ b/ext/rp/README.md @@ -0,0 +1,45 @@ +## RP2040 device headers + +This folder includes the CMSIS device headers for RP2040 devices. +The files included here are part of the CMSIS libraries for the particular chip. +Only the device header and system init header from the `inc` folder are copied. + +## License + +The Raspberry Pi header files in this directory are available under the BSD-3-Clause License: +``` +Copyright (c) 2021 Raspberry Pi (Trading) Ltd. + +SPDX-License-Identifier: BSD-3-Clause +``` + +The Raspberry Pi CMSIS header files in this directory are available under +the Apache-2.0/BSD-3-Clause License: +``` +Copyright (c) 2009-2021 Arm Limited. All rights reserved. +Copyright (c) 2020 Raspberry Pi (Trading) Ltd. + +SPDX-License-Identifier: Apache-2.0 + +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 + +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. + +SPDX-License-Identifier: BSD-3-Clause + +``` + +The Raspberry Pi bootloader files available under BSD-3-Clause License: +``` +Second stage boot code +Copyright (c) 2019-2021 Raspberry Pi (Trading) Ltd. +SPDX-License-Identifier: BSD-3-Clause +``` \ No newline at end of file diff --git a/ext/rp/address_mapped.h b/ext/rp/address_mapped.h new file mode 100644 index 0000000000..b2d3d67c33 --- /dev/null +++ b/ext/rp/address_mapped.h @@ -0,0 +1,194 @@ +/* + * Copyright (c) 2020 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef _HARDWARE_ADDRESS_MAPPED_H +#define _HARDWARE_ADDRESS_MAPPED_H + +#ifdef __cplusplus +#include +#include +#endif + +#define invalid_params_if(x, test) \ + if (0) {} +#define valid_params_if(x, test) \ + if (0) {} +#define hard_assert_if(x, test) \ + if (0) {} + +#include "hardware/regs/addressmap.h" + +/** \file address_mapped.h + * \defgroup hardware_base hardware_base + * + * Low-level types and (atomic) accessors for memory-mapped hardware registers + * + * `hardware_base` defines the low level types and access functions for memory mapped hardware + registers. It is included + * by default by all other hardware libraries. + * + * The following register access typedefs codify the access type (read/write) and the bus size + (8/16/32) of the hardware register. + * The register type names are formed by concatenating one from each of the 3 parts A, B, C + + * A | B | C | Meaning + * ------|---|---|-------- + * io_ | | | A Memory mapped IO register + *  |ro_| | read-only access + *  |rw_| | read-write access + *  |wo_| | write-only access (can't actually be enforced via C API) + *  | | 8| 8-bit wide access + *  | | 16| 16-bit wide access + *  | | 32| 32-bit wide access + * + * When dealing with these types, you will always use a pointer, i.e. `io_rw_32 *some_reg` is a + pointer to a read/write + * 32 bit register that you can write with `*some_reg = value`, or read with `value = *some_reg`. + * + * RP2040 hardware is also aliased to provide atomic setting, clear or flipping of a subset of the + bits within + * a hardware register so that concurrent access by two cores is always consistent with one atomic + operation + * being performed first, followed by the second. + * + * See hw_set_bits(), hw_clear_bits() and hw_xor_bits() provide for atomic access via a pointer to + a 32 bit register + * + * Additionally given a pointer to a structure representing a piece of hardware (e.g. `dma_hw_t + *dma_hw` for the DMA controller), you can + * get an alias to the entire structure such that writing any member (register) within the + structure is equivalent + * to an atomic operation via hw_set_alias(), hw_clear_alias() or hw_xor_alias()... + * + * For example `hw_set_alias(dma_hw)->inte1 = 0x80;` will set bit 7 of the INTE1 register of the + DMA controller, + * leaving the other bits unchanged. + */ + +#ifdef __cplusplus +extern "C" { +#endif + +#define check_hw_layout(type, member, offset) \ + static_assert(offsetof(type, member) == (offset), "hw offset mismatch") +#define check_hw_size(type, size) static_assert(sizeof(type) == (size), "hw size mismatch") + +typedef volatile uint32_t io_rw_32; +typedef const volatile uint32_t io_ro_32; +typedef volatile uint32_t io_wo_32; +typedef volatile uint16_t io_rw_16; +typedef const volatile uint16_t io_ro_16; +typedef volatile uint16_t io_wo_16; +typedef volatile uint8_t io_rw_8; +typedef const volatile uint8_t io_ro_8; +typedef volatile uint8_t io_wo_8; + +typedef volatile uint8_t *const ioptr; +typedef ioptr const const_ioptr; + +// A non-functional (empty) helper macro to help IDEs follow links from the autogenerated +// hardware struct headers in hardware/structs/xxx.h to the raw register definitions +// in hardware/regs/xxx.h. A preprocessor define such as TIMER_TIMEHW_OFFSET (a timer register +// offset) is not generally clickable (in an IDE) if placed in a C comment, so +// _REG_(TIMER_TIMEHW_OFFSET) is included outside of a comment instead +#define _REG_(x) + +// Helper method used by hw_alias macros to optionally check input validity +#define hw_alias_check_addr(addr) ((uintptr_t)(addr)) +// can't use the following impl as it breaks existing static declarations using hw_alias, so would +// be a backwards incompatibility +// static __force_inline uint32_t hw_alias_check_addr(volatile void *addr) { +// uint32_t rc = (uintptr_t)addr; +// invalid_params_if(ADDRESS_ALIAS, rc < 0x40000000); // catch likely non HW pointer types +// return rc; +//} + +// Helper method used by xip_alias macros to optionally check input validity +modm_always_inline static uint32_t +xip_alias_check_addr(const void *addr) +{ + uint32_t rc = (uintptr_t)addr; + valid_params_if(ADDRESS_ALIAS, rc >= XIP_MAIN_BASE && rc < XIP_NOALLOC_BASE); + return rc; +} + +// Untyped conversion alias pointer generation macros +#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))) +#define xip_noalloc_alias_untyped(addr) ((void *)(XIP_NOALLOC_BASE | xip_alias_check_addr(addr))) +#define xip_nocache_alias_untyped(addr) ((void *)(XIP_NOCACHE_BASE | xip_alias_check_addr(addr))) +#define xip_nocache_noalloc_alias_untyped(addr) \ + ((void *)(XIP_NOCACHE_NOALLOC_BASE | xip_alias_check_addr(addr))) + +// Typed conversion alias pointer generation macros +#define hw_set_alias(p) ((typeof(p))hw_set_alias_untyped(p)) +#define hw_clear_alias(p) ((typeof(p))hw_clear_alias_untyped(p)) +#define hw_xor_alias(p) ((typeof(p))hw_xor_alias_untyped(p)) +#define xip_noalloc_alias(p) ((typeof(p))xip_noalloc_alias_untyped(p)) +#define xip_nocache_alias(p) ((typeof(p))xip_nocache_alias_untyped(p)) +#define xip_nocache_noalloc_alias(p) ((typeof(p))xip_nocache_noalloc_alias_untyped(p)) + +/*! \brief Atomically set the specified bits to 1 in a HW register + * \ingroup hardware_base + * + * \param addr Address of writable register + * \param mask Bit-mask specifying bits to set + */ +modm_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; +} + +/*! \brief Atomically clear the specified bits to 0 in a HW register + * \ingroup hardware_base + * + * \param addr Address of writable register + * \param mask Bit-mask specifying bits to clear + */ +modm_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; +} + +/*! \brief Atomically flip the specified bits in a HW register + * \ingroup hardware_base + * + * \param addr Address of writable register + * \param mask Bit-mask specifying bits to invert + */ +modm_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; +} + +/*! \brief Set new values for a sub-set of the bits in a HW register + * \ingroup hardware_base + * + * Sets destination bits to values specified in \p values, if and only if corresponding bit in \p + * write_mask is set + * + * Note: this method allows safe concurrent modification of *different* bits of + * a register, but multiple concurrent access to the same bits is still unsafe. + * + * \param addr Address of writable register + * \param values Bits values + * \param write_mask Mask of bits to change + */ +modm_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); +} + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/ext/rp/device.hpp.in b/ext/rp/device.hpp.in new file mode 100644 index 0000000000..20811e464d --- /dev/null +++ b/ext/rp/device.hpp.in @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2009, Martin Rosekeit + * Copyright (c) 2009, Thorsten Lajewski + * Copyright (c) 2009-2010, 2016, Fabian Greif + * Copyright (c) 2012-2013, 2016, Niklas Hauser + * Copyright (c) 2013, Kevin Laeufer + * + * This file is part of the modm project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +// ---------------------------------------------------------------------------- + +#ifndef MODM_DEVICE_HPP +#define MODM_DEVICE_HPP + +%% for define in defines: +#define {{ define }} 1 +%% endfor + +#include +// Defines for example the modm_always_inline macro +#include + +// Include external device headers: +%% for header in headers +#include <{{ header }}> +%% endfor + +#endif // MODM_DEVICE_HPP diff --git a/ext/rp/module.lb b/ext/rp/module.lb new file mode 100644 index 0000000000..559c18c03a --- /dev/null +++ b/ext/rp/module.lb @@ -0,0 +1,48 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# Copyright (c) 2022, Andrey Kunitsyn +# +# This file is part of the modm project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# ----------------------------------------------------------------------------- + +import os + +def init(module): + module.name = ":cmsis:device" + +def prepare(module, options): + device = options[":target"] + if not device.identifier["platform"] == "rp": + return False + + module.depends(":cmsis:core") + return True + +def build(env): + env.collect(":build:path.include", "modm/ext/cmsis_device") + + device = env[":target"] + + env.outbasepath = "modm/ext/cmsis_device" + + defines = [] + headers = [ + device.identifier.string.upper() + ".h" + ] + + env.copy("pico-sdk/include", "") + env.copy("address_mapped.h", "hardware/address_mapped.h") + + env.substitutions = { + "headers": headers, + "defines": defines, + } + env.outbasepath = "modm/src/modm/platform" + env.template("device.hpp.in") + + diff --git a/ext/rp/pico-sdk b/ext/rp/pico-sdk new file mode 160000 index 0000000000..36cdca52f3 --- /dev/null +++ b/ext/rp/pico-sdk @@ -0,0 +1 @@ +Subproject commit 36cdca52f341713296b96c5ecbb8185fb4f596a8 From c468fa51a3d2171d216f23ba5e44aa4dfc0a7c33 Mon Sep 17 00:00:00 2001 From: andryblack Date: Thu, 20 Jan 2022 15:00:41 +0100 Subject: [PATCH 02/13] [rp] Add core support for RP2040 device --- .github/workflows/linux.yml | 8 +- ext/modm-devices | 2 +- repo.lb | 3 +- src/modm/platform/clock/systick/module.lb | 3 + .../core/cortex/atomic_lock_impl.hpp.in | 6 + src/modm/platform/core/cortex/linker.macros | 14 +- src/modm/platform/core/cortex/module.lb | 1 + src/modm/platform/core/cortex/module.md | 5 +- src/modm/platform/core/rp/flash.ld.in | 102 ++++++++ src/modm/platform/core/rp/module.lb | 73 ++++++ src/modm/platform/core/rp/module.md | 102 ++++++++ src/modm/platform/core/rp/multicore.cpp | 87 +++++++ src/modm/platform/core/rp/multicore.hpp.in | 242 ++++++++++++++++++ src/modm/platform/core/rp/multicore.lb | 28 ++ src/modm/platform/core/rp/multicore.md | 54 ++++ src/modm/platform/core/rp/resets.hpp | 37 +++ .../platform/core/rp/startup_platform.cpp.in | 50 ++++ test/all/Makefile | 3 + tools/scripts/generate_hal_matrix.py | 13 +- 19 files changed, 813 insertions(+), 20 deletions(-) create mode 100644 src/modm/platform/core/rp/flash.ld.in create mode 100644 src/modm/platform/core/rp/module.lb create mode 100644 src/modm/platform/core/rp/module.md create mode 100644 src/modm/platform/core/rp/multicore.cpp create mode 100644 src/modm/platform/core/rp/multicore.hpp.in create mode 100644 src/modm/platform/core/rp/multicore.lb create mode 100644 src/modm/platform/core/rp/multicore.md create mode 100644 src/modm/platform/core/rp/resets.hpp create mode 100644 src/modm/platform/core/rp/startup_platform.cpp.in diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 790050743a..2bf8d90f70 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -225,7 +225,7 @@ jobs: - name: Quick compile HAL for Cortex-M Part 1 if: always() run: | - (cd test/all && python3 run_all.py stm32 sam --quick --split 4 --part 0) + (cd test/all && python3 run_all.py stm32 sam rp --quick --split 4 --part 0) - name: Upload log artifacts uses: actions/upload-artifact@v2 with: @@ -245,7 +245,7 @@ jobs: - name: Quick compile HAL for Cortex-M Part 2 if: always() run: | - (cd test/all && python3 run_all.py stm32 sam --quick --split 4 --part 1) + (cd test/all && python3 run_all.py stm32 sam rp --quick --split 4 --part 1) - name: Upload log artifacts uses: actions/upload-artifact@v2 with: @@ -265,7 +265,7 @@ jobs: - name: Quick compile HAL for Cortex-M Part 3 if: always() run: | - (cd test/all && python3 run_all.py stm32 sam --quick --split 4 --part 2) + (cd test/all && python3 run_all.py stm32 sam rp --quick --split 4 --part 2) - name: Upload log artifacts uses: actions/upload-artifact@v2 with: @@ -285,7 +285,7 @@ jobs: - name: Quick compile HAL for Cortex-M Part 4 if: always() run: | - (cd test/all && python3 run_all.py stm32 sam --quick --split 4 --part 3) + (cd test/all && python3 run_all.py stm32 sam rp --quick --split 4 --part 3) - name: Upload log artifacts uses: actions/upload-artifact@v2 with: diff --git a/ext/modm-devices b/ext/modm-devices index 13e82a8511..76c5025686 160000 --- a/ext/modm-devices +++ b/ext/modm-devices @@ -1 +1 @@ -Subproject commit 13e82a8511877879bf99ff6b8d5af3b29d5b3204 +Subproject commit 76c5025686a95f3ceb97f658f0ddf1cd4ebdfc6f diff --git a/repo.lb b/repo.lb index 0cfa12c275..3f739868a0 100644 --- a/repo.lb +++ b/repo.lb @@ -87,6 +87,7 @@ class DevicesCache(dict): "stm32l0", "stm32l1", "stm32l4", "stm32l5", "at90", "attiny", "atmega", "samd21", "samg55", "samv70", + "rp2040", "hosted"] device_file_names = [dfn for dfn in device_file_names if any(s in dfn for s in supported)] # These files are ignored due to various issues @@ -178,7 +179,7 @@ class TargetOption(EnumerationOption): targets = self._enumeration.keys() # check if input string is part of keys OR longer - while(len(target) > len("attiny4-")): + while(len(target) >= len("rp2040")): if target in targets: target = self._enumeration[target] target._identifier.set("revision", revision.lower()) diff --git a/src/modm/platform/clock/systick/module.lb b/src/modm/platform/clock/systick/module.lb index 6d811371ba..cb0681e145 100644 --- a/src/modm/platform/clock/systick/module.lb +++ b/src/modm/platform/clock/systick/module.lb @@ -46,6 +46,9 @@ def build(env): # H742/43: Prescaler not implemented in revY elif target.family == "h7" and target.name in ["42", "43", "50", "53"] and target.revision == "y": div = 1 + # RP2040 external reference clock need additional configuration + elif target.platform == "rp": + div = 1 env.substitutions = { "systick_frequency": env.get(":freertos:frequency", freq), diff --git a/src/modm/platform/core/cortex/atomic_lock_impl.hpp.in b/src/modm/platform/core/cortex/atomic_lock_impl.hpp.in index 453a9ed70c..be4103f49b 100644 --- a/src/modm/platform/core/cortex/atomic_lock_impl.hpp.in +++ b/src/modm/platform/core/cortex/atomic_lock_impl.hpp.in @@ -16,12 +16,18 @@ #include "../device.hpp" #include +%% if with_multicore +#include "multicore.hpp" +%% endif /// @cond namespace modm::atomic { class Lock +%% if with_multicore + : public modm::platform::multicore::CoreLock +%% endif { public: modm_always_inline diff --git a/src/modm/platform/core/cortex/linker.macros b/src/modm/platform/core/cortex/linker.macros index 20f4b57559..ea7f71769e 100644 --- a/src/modm/platform/core/cortex/linker.macros +++ b/src/modm/platform/core/cortex/linker.macros @@ -69,20 +69,20 @@ MAIN_STACK_SIZE = {{ options[":platform:cortex-m:main_stack_size"] }}; %% endmacro -%% macro section_stack(memory, start=None) - /* Main stack in {{memory}} */ - .stack (NOLOAD) : +%% macro section_stack(memory, start=None, suffix="") + /* Main stack{{suffix}} in {{memory}} */ + .stack{{suffix}} (NOLOAD) : { %% if start != None - . += {{ start }}; + . += {{start}}; %% endif - __stack_start = .; + __stack{{suffix}}_start = .; . += MAIN_STACK_SIZE; . = ALIGN(8); - __main_stack_top = .; + __main{{suffix}}_stack_top = .; - __stack_end = .; + __stack{{suffix}}_end = .; } >{{memory}} %% endmacro diff --git a/src/modm/platform/core/cortex/module.lb b/src/modm/platform/core/cortex/module.lb index 2ba6d76aa7..0a25932caa 100644 --- a/src/modm/platform/core/cortex/module.lb +++ b/src/modm/platform/core/cortex/module.lb @@ -286,6 +286,7 @@ def build(env): "with_memory_traits": env.has_module(":architecture:memory"), "with_assert": env.has_module(":architecture:assert"), "with_fpu": env.get("float-abi", "soft") != "soft", + "with_multicore": env.has_module(":platform:multicore"), }) env.outbasepath = "modm/src/modm/platform/core" diff --git a/src/modm/platform/core/cortex/module.md b/src/modm/platform/core/cortex/module.md index e38d786f43..d0f5f021c6 100644 --- a/src/modm/platform/core/cortex/module.md +++ b/src/modm/platform/core/cortex/module.md @@ -142,8 +142,9 @@ The following macros are available: - `section_load(memory, table_copy, sections)`: place each `.{section}` in `sections` into `memory` and add them the copy table. -- `section_stack(memory, start=None)`: place the main stack into `memory` after - moving the location counter to `start`. +- `section_stack(memory, start=None, suffix="")`: place the main stack into + `memory` after moving the location counter to `start`. `suffix` can be used + to add multiple `.stack{suffix}` sections. - `section_heap(memory, name, placement=None, sections=[])`: Add the noload `sections` to `memory` and fill up remaining space in `memory` with heap diff --git a/src/modm/platform/core/rp/flash.ld.in b/src/modm/platform/core/rp/flash.ld.in new file mode 100644 index 0000000000..26ff7ed33e --- /dev/null +++ b/src/modm/platform/core/rp/flash.ld.in @@ -0,0 +1,102 @@ +%% import "../cortex/linker.macros" as linker with context +{{ linker.copyright() }} + +{{ linker.prefix() }} +%% set table_heap = [] +%% set table_copy = [] +%% set table_zero = [] + +SECTIONS +{ +{{ linker.section_rom_start("FLASH") }} + + .boot2 : + { + __boot2_start__ = .; + KEEP (*(.boot2)) + __boot2_end__ = .; + } > FLASH + ASSERT(__boot2_end__ - __boot2_start__ == 256, + "ERROR: Pico second stage bootloader must be 256 bytes in size!") + + .text : + { + __vector_table_rom_start = .; + __vector_table_ram_load = .; + KEEP(*(.vector_rom)) + __vector_table_rom_end = .; + } > FLASH + +{{ linker.section_rom("FLASH") }} + + +%% if vector_table_location == "ram" +{{ linker.section_vector_ram("RAM", table_copy) }} +%% endif + +{{ linker.section_ram(cont_ram_regions[0].cont_name|upper, "FLASH", table_copy, table_zero, + sections_data=["fastdata", "fastcode", "data_" + cont_ram_regions[0].contains[0].name], + sections_bss=["bss_" + cont_ram_regions[0].contains[0].name], + sections_noinit=["faststack"]) }} + +{{ linker.all_heap_sections(table_copy, table_zero, table_heap) }} + +%% if with_crashcatcher + %# + /* Bottom of crash stack for `modm:platform:fault` */ + g_crashCatcherStack = . - 500; + %# +%% endif + +%% macro section_mem_bank(memory, section, alt_name) + /* Sections in {{memory}} */ + .data_{{memory | lower}} : + { + __{{section}}_data_load = LOADADDR(.data_{{memory | lower}}); + __{{section}}_data_start = .; + *(.data_{{section}} .data_{{section}}.*) + *(.{{alt_name}}) + . = ALIGN(4); + __{{section}}_data_end = .; + } >{{memory}} AT > FLASH + %% do table_copy.append(section + "_data") + %# + .bss_{{memory | lower}} (NOLOAD) : + { + __{{section}}_bss_start = . ; + *(.bss_{{section}} .bss_{{section}}.*) + . = ALIGN(4); + __{{section}}_bss_end = . ; + } >{{memory}} + %% do table_zero.append(section + "_bss") + %# + .noinit_{{memory | lower}} (NOLOAD) : + { + __{{section}}_noinit_start = . ; + *(.noinit_{{section}} .noinit_{{section}}.*) + . = ALIGN(4); + __{{section}}_noinit_end = . ; + } >{{memory}} +%% endmacro + +%% if with_multicore +{{ linker.section_stack("CORE1", suffix="1") }} +%% endif +{{ section_mem_bank("CORE1", "core1", "scratch_x") }} + + +{{ linker.section_stack("CORE0") }} + +{{ section_mem_bank("CORE0", "core0", "scratch_y") }} + +%% if linkerscript_sections +{{ linkerscript_sections | indent(first=True) }} + %# +%% endif + +{{ linker.section_tables("FLASH", table_copy, table_zero, table_heap) }} + +{{ linker.section_rom_end("FLASH") }} + +{{ linker.section_debug() }} +} diff --git a/src/modm/platform/core/rp/module.lb b/src/modm/platform/core/rp/module.lb new file mode 100644 index 0000000000..0df0c91681 --- /dev/null +++ b/src/modm/platform/core/rp/module.lb @@ -0,0 +1,73 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# Copyright (c) 2019, Ethan Slattery +# Copyright (c) 2021, Niklas Hauser +# Copyright (c) 2022, Andrey Kunitsyn +# +# This file is part of the modm project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# ----------------------------------------------------------------------------- + +import math + +def init(module): + module.name = ":platform:core" + module.description = FileReader("module.md") + +def prepare(module, options): + if options[":target"].identifier.platform != "rp": + return False + + module.depends(":platform:cortex-m") + module.add_option( + EnumerationOption( + name="boot2", + description="Second-stage bootloader variant", + enumeration=["generic_03h", "at25sf128a", "is25lp080", "w25q080", "w25x10cl"], + default="generic_03h")) + return True + + +def build(env): + target = env[":target"].identifier + + env.substitutions = {"with_multicore": env.has_module(":platform:multicore")} + env.outbasepath = "modm/src/modm/platform/core" + # startup helper code + env.template("startup_platform.cpp.in") + + # delay code that must be tuned for each family + # (cycles per loop, setup cost in loops, max CPU freq) + tuning = { + "20": (3, 4, 133000000) + }[target.family] + + # us_shift is an optimization to limit error via fractional math + us_shift = 32 - math.ceil(math.log2(tuning[2])) + + env.substitutions.update({ + "with_cm0": env[":target"].has_driver("core:cortex-m0*"), + "with_cm7": env[":target"].has_driver("core:cortex-m7*"), + "loop": tuning[0], + "shift": int(math.log2(tuning[1])), + "us_shift": us_shift, + "with_assert": env.has_module(":architecture:assert") + }) + env.template("../cortex/delay_ns.cpp.in", "delay_ns.cpp") + env.template("../cortex/delay_ns.hpp.in", "delay_ns.hpp") + env.template("../cortex/delay_impl.hpp.in", "delay_impl.hpp") + env.copy("resets.hpp") + + env.copy(repopath("ext/rp/pico-sdk/src/boot2_{}.cpp".format(env["boot2"])),"boot2.cpp") + + +def post_build(env): + env.substitutions = env.query("::cortex-m:linkerscript") + env.substitutions.update(env.query("::cortex-m:vector_table")) + env.substitutions["with_multicore"] = env.has_module(":platform:multicore") + env.outbasepath = "modm/link" + env.template("flash.ld.in", "linkerscript.ld") diff --git a/src/modm/platform/core/rp/module.md b/src/modm/platform/core/rp/module.md new file mode 100644 index 0000000000..c01905004c --- /dev/null +++ b/src/modm/platform/core/rp/module.md @@ -0,0 +1,102 @@ +# RP2040 core module + +This module specializes the generic `modm:platform:cortex-m` module with +RP2040-specific startup code and linkerscripts. + +!!! note "Second-stage Bootloader" + The external Flash memory requires additional setup, which is provided by + the second-stage bootloader called boot2. You can select a pre-defined + configuration via the `modm:platform:core:boot2` option. + + +## Startup + +The `__modm_initialize_platform()` callback resets all hardware to a known +initial state. + + +## Linkerscript + +The RP2040 currently only has one linkerscript template for internal SRAM and +external Flash. You can place static objects in sections via the +`modm_section` attribute: + +```cpp +// .data sections get copied from flash to RAM during startup +modm_section(".data_core0") +uint64_t data = 0x1234567812345678ull; + +// .bss sections are not stored in flash but get zeroed during startup +modm_section(".bss_core0") +uint8_t buffer[1024]; + +// .noinit sections are left uninitialized +modm_section(".noinit") +uint8_t display_buffer[480][320]; +``` + + +### Dual-Core Static RAM (SRAM) + +The RP2040 memory map consists out of the main RAM (SRAM0-3 with striped bank +access) and the dedicated memories for each core (SRAM4-5). +Note that the stack size for both cores is the same and can be changed with the +`modm:platform:cortex-m:main_stack_size` option, however, the main stack for +CPU1 is only added if modm is build with the `modm:platform:multicore` module +to free up the unused memory. + +``` + ┌────────────────────────┐◄ __core0_end + │ .noinit_core0 │ + │ .bss_core0 │ + │ .data_core0 │ + CORE0 │ +MAIN_STACK_SIZE │◄ __main_stack_top +0x2004 1000 ├────────────────────────┤◄ __core1_end, __core0_start + │ .noinit_core1 │ + │ .bss_core1 │ + │ .data_core1 │ + CORE1 │ (+MAIN_STACK_SIZE) │◄ __main1_stack_top (only with :platform:multicore) +0x2004 0000 ├────────────────────────┤◄ __ram_end, __core1_start + │ +HEAP_RAM │ + │ .noinit_ram │ + │ .noinit │ + │ .faststack │ + │ .bss_ram │ + │ .bss │ + │ .data_ram │ + │ .data │ + │ .fastdata │ + │ .fastcode │ + RAM │ (.vector_ram) │◄ only if remapped into RAM +0x2000 0000 └────────────────────────┘◄ __ram_start + + ┌────────────────────────┐◄ __flash_end + │ (unused) │ + ├────────────────────────┤◄ __rom_end + │ .table.heap │ + │ .table.copy.extern │ + tables │ .table.zero.extern │ + │ .table.copy.intern │ + │ .table.zero.intern │ + │ │ + │ .data_core1 │ + │ .data_core0 │ + copy │ .data_ram │ + only │ .data │ + │ .fastcode │ + │ .fastdata │ + │ │ + │ .note.gnu.build-id │ + │ .assertion │ + │ .hardware_init │ + │ (.eh_frame) │ + read │ (.ARM.exidx) │ only with C++ exceptions enabled + only │ (.ARM.extab) │ + │ .init_array │ + │ .init │ + │ .rodata │ + │ .text │ + │ .vector_rom │ + FLASH │ .boot2 │◄ see :platform:core:boot2 option +0x1000 0000 └────────────────────────┘◄ __rom_start, __flash_start +``` diff --git a/src/modm/platform/core/rp/multicore.cpp b/src/modm/platform/core/rp/multicore.cpp new file mode 100644 index 0000000000..211389345c --- /dev/null +++ b/src/modm/platform/core/rp/multicore.cpp @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2022, Andrey Kunitsyn + * + * This file is part of the modm project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +// ---------------------------------------------------------------------------- + +#include "multicore.hpp" +#include "../device.hpp" + +using namespace modm::platform::multicore; + +modm_weak void +modm_initialize_core1(void) +{} + +static void +core1_wrapper(void (*entry)(void)) +{ + modm_initialize_core1(); + (*entry)(); +} + +static void __attribute__((naked)) core1_trampoline(void) +{ + asm volatile ("pop {r0, pc}"); // r0 = entry, pc = core1_wrapper +} + +static void +runCore1(void (*entry)(), uint32_t *stack_bottom, uint32_t stack_size) +{ + // assert(!(stack_size & 3u)); + uint32_t *stack_ptr = stack_bottom + (stack_size / sizeof(uint32_t)); + // push 1 value onto top of stack for core1_trampoline + stack_ptr -= 2; + stack_ptr[0] = (uintptr_t)entry; + stack_ptr[1] = (uintptr_t)core1_wrapper; + + // Allow for the fact that the caller may have already enabled the FIFO IRQ for their + // own purposes (expecting FIFO content after core 1 is launched). We must disable + // the IRQ during the handshake, then restore afterwards. + const bool enabled = NVIC_GetEnableIRQ(SIO_IRQ_PROC0_IRQn); + NVIC_DisableIRQ(SIO_IRQ_PROC0_IRQn); + + // Boot code start core 1 at waiting following protocol on Mailbox + // Values to be sent in order over the FIFO from core 0 to core 1 + // + // value for VTOR register + // initial stack pointer (SP) + // initial program counter (PC) (don't forget to set the thumb bit!) + const uint32_t cmd_sequence[] = { + 0, 0, 1, (uintptr_t)SCB->VTOR, (uintptr_t)stack_ptr, (uintptr_t)core1_trampoline}; + + uint32_t seq = 0; + do { + uint32_t cmd = cmd_sequence[seq]; + // Always drain the READ FIFO (from core 1) before sending a 0 + if (!cmd) + { + Mailbox::drain(); + // Execute a SEV as core 1 may be waiting for FIFO space via WFE + __SEV(); + } + Mailbox::pushBlocked(cmd); + uint32_t response = Mailbox::popBlocked(); + // Move to next state on correct response (echo-d value) otherwise start over + seq = cmd == response ? seq + 1 : 0; + } + while (seq < sizeof(cmd_sequence) / sizeof(cmd_sequence[0])); + + if (enabled) NVIC_EnableIRQ(SIO_IRQ_PROC0_IRQn); + else NVIC_DisableIRQ(SIO_IRQ_PROC0_IRQn); +} + +extern "C" uint32_t __stack1_start; +extern "C" uint32_t __main1_stack_top; + +void +Core1::run(void (*func)()) +{ + uint32_t *stack = &__main1_stack_top; + runCore1(func, stack, (stack - &__stack1_start) * sizeof(uint32_t)); +} diff --git a/src/modm/platform/core/rp/multicore.hpp.in b/src/modm/platform/core/rp/multicore.hpp.in new file mode 100644 index 0000000000..81b2b0fbf5 --- /dev/null +++ b/src/modm/platform/core/rp/multicore.hpp.in @@ -0,0 +1,242 @@ +/* + * Copyright (c) 2022, Andrey Kunitsyn + * + * This file is part of the modm project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +// ---------------------------------------------------------------------------- +#pragma once + +#include +#include +#include + +namespace modm::platform::multicore +{ + +/** + * The RP2040 contains two FIFOs for passing data, messages or ordered events between the two cores. Each FIFO is 32 bits + * wide, and 8 entries deep. One of the FIFOs can only be written by core 0, and read by core 1. The other can only be written + * by core 1, and read by core 0. + * @ingroup modm_platform_multicore + */ +struct Mailbox +{ + static inline bool canRead() + { + return sio_hw->fifo_st & SIO_FIFO_ST_VLD_BITS; + } + static inline bool canWrite() + { + return sio_hw->fifo_st & SIO_FIFO_ST_RDY_BITS; + } + static inline bool push(uint32_t msg) + { + if (!canWrite()) return false; + sio_hw->fifo_wr = msg; + __SEV(); + return true; + } + static inline bool pop(uint32_t& msg) + { + if (!canRead()) return false; + msg = sio_hw->fifo_rd; + return true; + } + static inline void pushBlocked(uint32_t msg) + { + while (!canWrite()) __NOP(); + sio_hw->fifo_wr = msg; + __SEV(); + } + static inline uint32_t popBlocked() + { + while (!canRead()) __WFE(); + return sio_hw->fifo_rd; + } + static inline void drain() + { + while (canRead()) + (void) sio_hw->fifo_rd; + } +}; + +/** + * The RP2040 provides 32 hardware spin locks, which can be used to manage mutually-exclusive access to shared software + * and hardware resources. + * + * Instance 0 allocated for modm usage. + * + * Acquire a spin lock without disabling interrupts (hence unsafe) + * @ingroup modm_platform_multicore + */ +template +class SpinLockUnsafe +{ + static_assert(instance < 32); + using LockType = volatile uint32_t; + +private: + static inline LockType* getInstance() + { + return (LockType *) (SIO_BASE + SIO_SPINLOCK0_OFFSET + instance * 4); + } + +public: + static void init() + { + unlock(); + } + static inline void lock() + { + auto l = getInstance(); + while (*l == 0); + std::atomic_thread_fence(std::memory_order_acquire); + } + static inline void unlock() + { + auto l = getInstance(); + std::atomic_thread_fence(std::memory_order_release); + *l = 0; + } +}; + +/// @cond +// for use in modm::atomic::Lock +struct CoreLock +{ + CoreLock() { SpinLockUnsafe<0>::lock(); } + ~CoreLock() { SpinLockUnsafe<0>::unlock(); } +}; +/// @endcond + +/// This class will disable interrupts prior to acquiring the spinlock +/// @ingroup modm_platform_multicore +template +class SpinLockBlocking : public SpinLockUnsafe +{ + using Base = SpinLockUnsafe; + +public: + static inline uint32_t lock() + { + uint32_t is = __get_PRIMASK(); + __disable_irq(); + Base::lock(); + return is; + } + static inline void unlock(uint32_t is) + { + Base::unlock(); + __set_PRIMASK(is); + } +}; +/// @ingroup modm_platform_multicore +using SystemSpinLock = SpinLockBlocking<0>; + + +/// RAI lock acquiring and releasing +/// @ingroup modm_platform_multicore +template +class SpinLockGuard +{ +private: + SpinLockGuard(const SpinLockGuard& ) = delete; + SpinLockGuard(SpinLockGuard&&) = delete; +public: + inline SpinLockGuard() { SpinLock::lock(); } + inline ~SpinLockGuard() { SpinLock::unlock(); } +}; + +/// @ingroup modm_platform_multicore +template +class SpinLockGuard> +{ +private: + using SpinLock = SpinLockBlocking; + uint32_t is; + SpinLockGuard(const SpinLockGuard& ) = delete; + SpinLockGuard(SpinLockGuard&&) = delete; +public: + inline SpinLockGuard() : is(SpinLock::lock()) {} + inline ~SpinLockGuard() { SpinLock::unlock(is); } +}; +/// @ingroup modm_platform_multicore +using SystemSpinLockGuard = SpinLockGuard; + +/// @ingroup modm_platform_multicore +class Mutex +{ +private: + volatile bool locked = false; + Mutex(const Mutex& ) = delete; + Mutex(Mutex&&) = delete; +public: + Mutex() {} + void lock() + { + while(true) + { + { + SystemSpinLockGuard g; + if (!locked) { + locked = true; + return; + } + } + __WFE(); + } + } + void unlock() + { + { + SystemSpinLockGuard g; + locked = false; + } + __SEV(); + } +}; + +/// @ingroup modm_platform_multicore +struct Core +{ + static inline uint32_t cpuId() + { + return sio_hw->cpuid; + } +}; +/// Wake up (a previously reset) core 1 and enter the given function on +/// core 1 using the default core 1 stack (below core 0 stack). +/// @ingroup modm_platform_multicore +struct Core1 : Core +{ + static void run(void(*func)()); +}; + +} // namespace modm::platform::multicore + +/// @ingroup modm_platform_multicore +/// @{ +%% for i in range(num_cores) +/// Places initialized objects into the core{{i}}-coupled memory +#define modm_core{{i}}_data modm_section(".data_core{{i}}") +%% endfor +%% for i in range(num_cores) +/// Places zeroed objects into the core{{i}}-coupled memory +#define modm_core{{i}}_bss modm_section(".bss_core{{i}}") +%% endfor +%% for i in range(num_cores) +/// Places uninitialized objects into the core{{i}}-coupled memory +#define modm_core{{i}}_noinit modm_section(".noinit_core{{i}}") +%% endfor +%% for i in range(num_cores) +/// Places functions into the core{{i}}-coupled memory +#define modm_core{{i}}_code modm_core{{i}}_data +%% endfor +/// Weak callback to initialize CPU1 +extern "C" void +modm_initialize_core1(void); +/// @} diff --git a/src/modm/platform/core/rp/multicore.lb b/src/modm/platform/core/rp/multicore.lb new file mode 100644 index 0000000000..d5eb9c4f58 --- /dev/null +++ b/src/modm/platform/core/rp/multicore.lb @@ -0,0 +1,28 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# Copyright (c) 2022, Andrey Kunitsyn +# +# This file is part of the modm project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# ----------------------------------------------------------------------------- + +def init(module): + module.name = ":platform:multicore" + module.description = FileReader("multicore.md") + + +def prepare(module, options): + if options[":target"].identifier.platform != "rp": + return False + return True + + +def build(env): + env.outbasepath = "modm/src/modm/platform/core" + env.substitutions = {"num_cores": int(env[":target"].identifier.cores)} + env.template('multicore.hpp.in') + env.copy('multicore.cpp') diff --git a/src/modm/platform/core/rp/multicore.md b/src/modm/platform/core/rp/multicore.md new file mode 100644 index 0000000000..d73b8940ec --- /dev/null +++ b/src/modm/platform/core/rp/multicore.md @@ -0,0 +1,54 @@ +# RP2040 multi-core module + +This module adds the ability to run code on CPU1 and provides mailbox, spinlock +and mutex implementations, and additionally a stack of size +`modm:platform:cortex-m:main_stack_size` is added to the CORE1 memory and the +`modm::atomic::Lock` is made multicore safe. +You can use attributes for placing objects into core-coupled memories: + +```cpp +modm_core0_data uint8_t data = 0xab; +modm_core1_bss uint16_t zero; +modm_core0_noinit uint32_t uninitialized; + +modm_core1_code +void function() +{ + data = 0xcd; + zero = 100; + uninitialized = 200; +} +``` + +We provide a symmetric multiprocessing (SMP) abstraction, where the same binary +is used for both cores. By default CPU0 is used to boot the device and code +running on CPU1 needs to be explicitly started: + +```cpp +void function() +{ + uint8_t cpuid = Core::cpuId(); +} +function(); // cpuid == 0 +multicore::Core1::run(function); // cpuid == 1 +// Before CPU1 executes code, this function is called +// so that you can customize the CPU1 environment +extern "C" void modm_initialize_core1() +{ + // set a custom vector table for CPU1 + SCB->VTOR = custom_vector_table; +} +``` + +When using the `modm:processing:fiber` module you can now schedule fibers on +both cores, each with their own scheduler: + +```cpp +modm_core0_noinit modm::fiber::Stack<256> stack0; +modm_core0_data modm::Fiber fiber0(stack0, []() { function(); }, /* core= */0); + +modm_core1_noinit modm::fiber::Stack<256> stack1; +modm_core1_data modm::Fiber fiber1(stack1, []() { function(); }, /* core= */1); +``` + + diff --git a/src/modm/platform/core/rp/resets.hpp b/src/modm/platform/core/rp/resets.hpp new file mode 100644 index 0000000000..b1703d3d72 --- /dev/null +++ b/src/modm/platform/core/rp/resets.hpp @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2022, Andrey Kunitsyn + * + * This file is part of the modm project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +// ---------------------------------------------------------------------------- +#pragma once + +#include + +#include +#include + +namespace modm::platform +{ + +/// @ingroup modm_platform_core +struct Resets +{ + static inline void + reset(uint32_t bits) + { + hw_set_bits(&resets_hw->reset, bits); + } + static inline void + unresetWait(uint32_t bits) + { + hw_clear_bits(&resets_hw->reset, bits); + while (~resets_hw->reset_done & bits) __NOP(); + } +}; + +} // namespace modm::platform diff --git a/src/modm/platform/core/rp/startup_platform.cpp.in b/src/modm/platform/core/rp/startup_platform.cpp.in new file mode 100644 index 0000000000..0862b1a6f0 --- /dev/null +++ b/src/modm/platform/core/rp/startup_platform.cpp.in @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2022, Andrey Kunitsyn + * + * This file is part of the modm project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +// ---------------------------------------------------------------------------- + +#include "../device.hpp" +#include "resets.hpp" +%% if with_multicore +#include "multicore.hpp" +%% endif + +extern "C" void +__modm_initialize_platform(void) +{ +%% if with_multicore + modm::platform::multicore::SystemSpinLock::init(); +%% endif + + // 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 + modm::platform::Resets::reset(~( + RESETS_RESET_IO_QSPI_BITS | + RESETS_RESET_PADS_QSPI_BITS | + RESETS_RESET_PLL_USB_BITS | + RESETS_RESET_USBCTRL_BITS | + RESETS_RESET_SYSCFG_BITS | + RESETS_RESET_PLL_SYS_BITS + )); + + // Remove reset from peripherals which are clocked only by clk_sys and + // clk_ref. Other peripherals stay in reset until we've configured clocks. + modm::platform::Resets::unresetWait(RESETS_RESET_BITS & ~( + RESETS_RESET_ADC_BITS | + RESETS_RESET_RTC_BITS | + RESETS_RESET_SPI0_BITS | + RESETS_RESET_SPI1_BITS | + RESETS_RESET_UART0_BITS | + RESETS_RESET_UART1_BITS | + RESETS_RESET_USBCTRL_BITS + )); + +} diff --git a/test/all/Makefile b/test/all/Makefile index ab39457a5a..b17ba66a32 100644 --- a/test/all/Makefile +++ b/test/all/Makefile @@ -18,5 +18,8 @@ run-sam: run-stm32: python3 run_all.py stm32 +run-rp: + python3 run_all.py rp + run-failed: python3 run_all.py failed diff --git a/tools/scripts/generate_hal_matrix.py b/tools/scripts/generate_hal_matrix.py index 84a5cef629..498316d42d 100755 --- a/tools/scripts/generate_hal_matrix.py +++ b/tools/scripts/generate_hal_matrix.py @@ -76,10 +76,11 @@ def hal_get_modules(): modules = set() # We only care about some modm:platform:* modules here - not_interested = {"bitbang", "common", "heap", "clock", "core", "fault", "cortex-m", "uart.spi", "itm", "rtt"} + not_interested = {"bitbang", "common", "heap", "core", "fault", "cortex-m", "uart.spi", "itm", "rtt"} imodules = (m for (repo, mfile) in mfiles for m in lbuild.module.load_module_from_file(repo, mfile) if m.available and m.fullname.startswith("modm:platform:") and - all(p not in m.fullname for p in not_interested)) + all(p not in m.fullname for p in not_interested) and + m.fullname not in {"modm:platform:clock"}) for module in imodules: # lookup the mapping of a module onto the hardware names if module.filename not in file_cache: @@ -97,6 +98,8 @@ def hal_get_modules(): "id": "Unique ID", "rcc": "System Clock", "gclk": "System Clock", + "clocks": "System Clock", + "clockgen": "System Clock", "extint": "External Interrupt", "exti": "External Interrupt", "fsmc": "External Memory", @@ -122,7 +125,7 @@ def hal_get_modules(): mapping["Ethernet"].add("gmac") mapping["Random Generator"].add("trng") mapping["UART"].update({"usi", "sercom"}) - mapping["Timer"].update({"tc", "tcc"}) + mapping["Timer"].update({"tc", "tcc", "timer"}) mapping["SPI"].add("sercom") mapping["I2C"].update({"sercom", "twihs"}) mapping["USB"].update({"usb", "usbhs"}) @@ -130,7 +133,7 @@ def hal_get_modules(): mapping["DMA"].update({"dmac", "xdmac"}) mapping["Comparator"].update({"ac", "acc"}) mapping["Internal Flash"].update({"efc", "nvmctrl"}) - mapping["External Memory"].update({"sdramc", "smc", "quadspi"}) + mapping["External Memory"].update({"sdramc", "smc", "quadspi", "xip_ssi"}) print(); print() return (all_targets, mapping) @@ -220,7 +223,7 @@ def hal_format_tables(): # tables["stm32"] = hal_create_table(targets, ["stm32"]) # tables["sam"] = hal_create_table(targets, ["sam"]) - tables["all"] = hal_create_table(targets, ["avr", "stm32", "sam"]) + tables["all"] = hal_create_table(targets, ["avr", "stm32", "sam", "rp"]) return tables From ee6a5126760baa7bd487e79c1683bedf63c939cf Mon Sep 17 00:00:00 2001 From: andryblack Date: Thu, 20 Jan 2022 13:49:40 +0100 Subject: [PATCH 03/13] [build] Add ELF2UF2 convertion tool --- tools/build_script_generator/make/module.lb | 3 + tools/build_script_generator/make/module.md | 17 ++ .../make/resources/Makefile.in | 9 + .../make/resources/config.mk.in | 1 + tools/build_script_generator/module.lb | 2 +- tools/build_script_generator/scons/module.lb | 2 +- tools/build_script_generator/scons/module.md | 17 ++ .../scons/resources/build_target.py.in | 3 + .../scons/site_tools/comstr.py | 1 + .../scons/site_tools/elf2uf2.py | 44 +++ tools/modm_tools/elf2uf2.py | 287 ++++++++++++++++++ 11 files changed, 384 insertions(+), 2 deletions(-) create mode 100644 tools/build_script_generator/scons/site_tools/elf2uf2.py create mode 100755 tools/modm_tools/elf2uf2.py diff --git a/tools/build_script_generator/make/module.lb b/tools/build_script_generator/make/module.lb index 036673f62a..387ffe6c32 100644 --- a/tools/build_script_generator/make/module.lb +++ b/tools/build_script_generator/make/module.lb @@ -52,6 +52,9 @@ def post_build(env): if subs["core"].startswith("cortex-m"): # get memory information subs["memories"] = env.query("::memories") + subs["uf2mem"] = ["0x{}:0x{}:{}".format(hex(m["start"]), hex(m["start"] + m["size"]), + "CONTENTS" if "flash" in m["name"] else "NO_CONTENTS") + for m in subs["memories"]] else: subs["memories"] = [] # Add SCons specific data diff --git a/tools/build_script_generator/make/module.md b/tools/build_script_generator/make/module.md index 33652b2663..4ce2e0cbdb 100644 --- a/tools/build_script_generator/make/module.md +++ b/tools/build_script_generator/make/module.md @@ -485,6 +485,23 @@ Hex File······· {debug|release}/blink.hex ``` +#### make uf2 + +``` +make uf2 profile={debug|release} [firmware={hash or file}] +``` + +Creates a UF2 compatible file of your executable. UF2 is a +[bootloader by Microsoft](https://github.com/microsoft/uf2). + +``` + $ make uf2 +UF2 File······· {debug|release}/blink.uf2 +``` + +(\* *only ARM Cortex-M targets*) + + ## Information Tool This tool generates a set of header files containing information about the diff --git a/tools/build_script_generator/make/resources/Makefile.in b/tools/build_script_generator/make/resources/Makefile.in index a1906caf73..2126321852 100644 --- a/tools/build_script_generator/make/resources/Makefile.in +++ b/tools/build_script_generator/make/resources/Makefile.in @@ -108,6 +108,15 @@ delay?=5 program-dfu: bin @dfu-util -v -E$(delay) -R -i 0 -a 0 -s 0x08000000:leave -D $(BIN_FILE) +.PHONY: uf2 +uf2: build + @echo "UF2 File·······" $(UF2_FILE) + @python3 $(MODM_PATH)/modm_tools/elf2uf2.py $(ELF_FILE) -o $(UF2_FILE) \ + --target $(CONFIG_DEVICE_NAME) \ +%% for range in uf2mem + --range {{range}}{% if not loop.last %} \{% endif %} +%% endfor +%# %% if platform in ["sam"] .PHONY: program-bossac program-bossac: bin diff --git a/tools/build_script_generator/make/resources/config.mk.in b/tools/build_script_generator/make/resources/config.mk.in index 6d489b2c8f..666f7e5458 100644 --- a/tools/build_script_generator/make/resources/config.mk.in +++ b/tools/build_script_generator/make/resources/config.mk.in @@ -16,6 +16,7 @@ BIN_FILE := $(BUILDPATH)/$(MODM_PROJECT_NAME).bin HEX_FILE := $(BUILDPATH)/$(MODM_PROJECT_NAME).hex LSS_FILE := $(BUILDPATH)/$(MODM_PROJECT_NAME).lss MAP_FILE := $(BUILDPATH)/$(MODM_PROJECT_NAME).map +UF2_FILE := $(BUILDPATH)/$(MODM_PROJECT_NAME).uf2 CLEAN_FILES += $(BUILDPATH) # Device configuration diff --git a/tools/build_script_generator/module.lb b/tools/build_script_generator/module.lb index 5b9d7738cc..3807678fe7 100644 --- a/tools/build_script_generator/module.lb +++ b/tools/build_script_generator/module.lb @@ -206,7 +206,7 @@ def build(env): tools.add("unit_test") if is_cortex_m: tools.update({"bmp", "openocd", "crashdebug", "gdb", "backend", - "log", "build_id", "size"}) + "log", "build_id", "size", "elf2uf2"}) if platform in ["sam"]: tools.update({"bossac"}) elif platform in ["avr"]: diff --git a/tools/build_script_generator/scons/module.lb b/tools/build_script_generator/scons/module.lb index 49a8a2cecb..2e5f8c224e 100644 --- a/tools/build_script_generator/scons/module.lb +++ b/tools/build_script_generator/scons/module.lb @@ -102,7 +102,7 @@ def build(env): device = env.query("::device") if device["core"].startswith("cortex-m"): tools.update({"size", "log_cortex", "artifact", "openocd", - "openocd_remote", "bmp", "crashdebug", "dfu"}) + "elf2uf2", "openocd_remote", "bmp", "crashdebug", "dfu"}) if device["platform"] in ["sam"]: tools.update({"bossac"}) elif device["core"].startswith("avr"): diff --git a/tools/build_script_generator/scons/module.md b/tools/build_script_generator/scons/module.md index 1eeca35a4b..0b1946156a 100644 --- a/tools/build_script_generator/scons/module.md +++ b/tools/build_script_generator/scons/module.md @@ -557,6 +557,23 @@ Hex File······· {debug|release}/blink.hex ``` +#### scons uf2 + +``` +scons uf2 profile={debug|release} [firmware={hash or file}] +``` + +Creates a UF2 compatible file of your executable. UF2 is a +[bootloader by Microsoft](https://github.com/microsoft/uf2). + +``` + $ scons uf2 +UF2 File······· {debug|release}/blink.uf2 +``` + +(\* *only ARM Cortex-M targets*) + + #### scons artifact ``` diff --git a/tools/build_script_generator/scons/resources/build_target.py.in b/tools/build_script_generator/scons/resources/build_target.py.in index 206d5298f8..e0425673a2 100644 --- a/tools/build_script_generator/scons/resources/build_target.py.in +++ b/tools/build_script_generator/scons/resources/build_target.py.in @@ -20,6 +20,7 @@ def build_target(env, sources): env.Clean(program, join(env["BUILDPATH"], env["CONFIG_PROJECT_NAME"]+".bin")) env.Clean(program, join(env["BUILDPATH"], env["CONFIG_PROJECT_NAME"]+".hex")) env.Clean(program, join(env["BUILDPATH"], env["CONFIG_PROJECT_NAME"]+".lss")) + env.Clean(program, join(env["BUILDPATH"], env["CONFIG_PROJECT_NAME"]+".uf2")) %% if with_compilation_db env.Command("compile_commands.json", sources, @@ -42,6 +43,8 @@ def build_target(env, sources): env.Depends(target=program, dependency="{{ linkerscript }}") env.Alias("size", env.Size(chosen_program)) %% if core.startswith("cortex-m") + env.Alias("uf2", env.UF2(chosen_program)) + env.Alias("log-itm", env.LogItmOpenOcd()) env.Alias("log-rtt", env.LogRttOpenOcd()) diff --git a/tools/build_script_generator/scons/site_tools/comstr.py b/tools/build_script_generator/scons/site_tools/comstr.py index f507d6e48b..883b4b5326 100644 --- a/tools/build_script_generator/scons/site_tools/comstr.py +++ b/tools/build_script_generator/scons/site_tools/comstr.py @@ -70,6 +70,7 @@ def generate(env, **kw): env['HEXCOMSTR'] = "%sHex File······· %s$TARGET%s" % default env['BINCOMSTR'] = "%sBinary File···· %s$TARGET%s" % default env['LSSCOMSTR'] = "%sListing········ %s$TARGET%s" % default + env['UF2COMSTR'] = "%sUF2 File······· %s$TARGET%s" % default # modm tools format strings env['BITMAPCOMSTR'] = "%sBitmap········· %s${str(TARGET).replace(BUILDPATH,CONFIG_PROFILE)}%s" % default diff --git a/tools/build_script_generator/scons/site_tools/elf2uf2.py b/tools/build_script_generator/scons/site_tools/elf2uf2.py new file mode 100644 index 0000000000..c4725d262f --- /dev/null +++ b/tools/build_script_generator/scons/site_tools/elf2uf2.py @@ -0,0 +1,44 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# Copyright (c) 2016-2017, German Aerospace Center (DLR) +# Copyright (c) 2018, Niklas Hauser +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# Authors: +# - 2022, Andrey Kunitsyn + +import os + +from SCons.Script import * +from modm_tools import elf2uf2 + +def elf2uf2_action(target, source, env): + memranges = [{ + "start": m["start"], + "end": m["start"] + m["size"], + "type": "CONTENTS" if "flash" in m["name"] else "NO_CONTENTS" + } + for m in env["CONFIG_DEVICE_MEMORY"]] + elf2uf2.convert(str(source[0]), + str(target[0]), + env["CONFIG_DEVICE_NAME"], + memranges) + +def generate(env, **kw): + builder_elf2uf2 = Builder( + action=Action(elf2uf2_action, cmdstr="$UF2COMSTR"), + suffix=".uf2", + src_suffix=".elf") + + env.Append(BUILDERS={ + "UF2": builder_elf2uf2, + }) + + +def exists(env): + return True + diff --git a/tools/modm_tools/elf2uf2.py b/tools/modm_tools/elf2uf2.py new file mode 100755 index 0000000000..73ee9bf052 --- /dev/null +++ b/tools/modm_tools/elf2uf2.py @@ -0,0 +1,287 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# Copyright (c) 2022, Andrey Kunitsyn +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# Authors: +# - 2022, Andrey Kunitsyn + +""" +### UF2 Converter + +UF2 is a [Microsoft file format](https://github.com/microsoft/uf2) to pass +to a on-device bootloader. + +```sh +python3 modm/modm_tools/elf2uf2.py firmware.elf -o firmware.uf2 --target rp2040 \ + --range 0x10000000:0x15000000:CONTENTS \ + --range 0x20000000:0x20042000:NO_CONTENTS +``` + +(\* *only ARM Cortex-M targets*) +""" + +import os +import struct + +from pathlib import Path + +verbose = False + + + +UF2_FLAG_NOT_MAIN_FLASH = 0x00000001 +UF2_FLAG_FILE_CONTAINER = 0x00001000 +UF2_FLAG_FAMILY_ID_PRESENT = 0x00002000 +UF2_FLAG_MD5_PRESENT = 0x00004000 + + +uf2_config = { + "rp2040": { + "MAGIC_START0": 0x0A324655, + "MAGIC_START1": 0x9E5D5157, + "MAGIC_END": 0x0AB16F30, + "FAMILY_ID": 0xe48bff56, + "PAGE_SIZE": (1 << 8), + } +} + +#struct uf2_block { +# // 32 byte header +# uint32_t magic_start0; +# uint32_t magic_start1; +# uint32_t flags; +# uint32_t target_addr; +# uint32_t payload_size; +# uint32_t block_no; +# uint32_t num_blocks; +# uint32_t file_size; // or familyID; +uf2_block = "= (addr+size): + if range["type"] == "NO_CONTENTS" and not uninitialized: + raise Exception("ELF contains memory contents for uninitialized memory") + if verbose: + print(("{} segment {:08x}->{:08x} ({:08x}->{:08x})").format(uninitialized and "Uninitialized" or "Mapped", addr, addr + size, vaddr, vaddr+size)) + return range + raise Exception("Memory segment {:08x}->{:08x} is outside of valid address range for device".format(addr, addr+size)) + +def read_and_check_elf32_ph_entries(buffer, eh, valid_ranges, pages, page_size): + for i in range(eh[6]): + entry = struct.unpack_from(elf32_ph_entry,buffer,eh[1]+i*elf32_ph_entry_size) + if entry[0] == PT_LOAD and entry[5] !=0: + mapped_size = min(entry[5],entry[4]) + if mapped_size != 0: + ar = check_address_range(valid_ranges,entry[3],entry[2],mapped_size,False) + # we don"t download uninitialized, generally it is BSS and should be zero-ed by crt0.S, or it may be COPY areas which are undefined + if ar["type"] != "CONTENTS": + if verbose: + print(" ignored"); + continue + addr = entry[3]; + remaining = mapped_size; + file_offset = entry[1]; + while remaining > 0: + off = addr & (page_size - 1) + chlen = min(remaining, page_size - off) + key = addr - off + fragments = [] + if key in pages: + fragments = pages[key] + for fragment in fragments: + if (off < fragment["page_offset"] + fragment["bytes"]) != ((off + chlen) <= fragment["page_offset"]): + raise Exception("In memory segments overlap") + + fragments.append({"file_offset":file_offset,"page_offset":off,"bytes":chlen}) + addr += chlen + file_offset += chlen + remaining -= chlen + pages[key] = fragments + + if entry[5] > entry[4]: + # we have some uninitialized data too + check_address_range(valid_ranges, entry[3] + entry[4], entry[2] + entry[4], entry[5] - entry[4], True); + +def realize_page(buffer, fragments): + result = bytes(476) + for frag in fragments: + data = buffer[frag["file_offset"]:frag["file_offset"]+frag["bytes"]] + if len(data) != frag["bytes"]: + raise Exception("failed extract") + if frag["page_offset"] == 0: + result = data + result[frag["page_offset"]+frag["bytes"]:] + else: + result = result[:frag["page_offset"]] + data + result[frag["page_offset"]+frag["bytes"]:] + if len(result) != 476: + raise Exception("failed concat") + return result + + +def convert_data(source_bytes,target,ranges): + eh = read_header(source_bytes) + config = uf2_config[target] + if verbose: + print("Build for chip:{}".format(target)) + pages = {} + read_and_check_elf32_ph_entries(source_bytes,eh,ranges,pages,config["PAGE_SIZE"]) + if len(pages) == 0: + raise Exception("The input file has no memory pages") + + num_blocks = len(pages) + page_num = 0 + file_content = bytes(0) + for target_addr,pages in pages.items(): + if verbose: + print("Page {} / {} {:08x}".format( page_num, num_blocks, target_addr)) + + data = realize_page(source_bytes,pages) + block = struct.pack(uf2_block, + config["MAGIC_START0"], + config["MAGIC_START1"], + UF2_FLAG_FAMILY_ID_PRESENT, + target_addr, + config["PAGE_SIZE"], + page_num, + num_blocks, + config["FAMILY_ID"]) + data + struct.pack(" Date: Thu, 20 Jan 2022 13:50:25 +0100 Subject: [PATCH 04/13] [rp] Add GPIO module --- .../platform/gpio/common/connector.hpp.in | 10 ++ src/modm/platform/gpio/common/pins.hpp.in | 14 +- src/modm/platform/gpio/common/unused.hpp.in | 6 +- src/modm/platform/gpio/rp/base.hpp.in | 134 ++++++++++++++++++ src/modm/platform/gpio/rp/data.hpp.in | 52 +++++++ src/modm/platform/gpio/rp/module.lb | 110 ++++++++++++++ src/modm/platform/gpio/rp/module.md | 125 ++++++++++++++++ src/modm/platform/gpio/rp/peripherals.hpp.in | 27 ++++ src/modm/platform/gpio/rp/port.hpp.in | 79 +++++++++++ src/modm/platform/gpio/rp/set.hpp.in | 133 +++++++++++++++++ .../platform/gpio/rp/software_port.hpp.in | 107 ++++++++++++++ src/modm/platform/gpio/rp/static.hpp.in | 78 ++++++++++ .../i2c/bitbang/bitbang_i2c_master.hpp.in | 4 +- .../one_wire/bitbang/bitbang_master.hpp.in | 4 +- 14 files changed, 873 insertions(+), 10 deletions(-) create mode 100644 src/modm/platform/gpio/rp/base.hpp.in create mode 100644 src/modm/platform/gpio/rp/data.hpp.in create mode 100644 src/modm/platform/gpio/rp/module.lb create mode 100644 src/modm/platform/gpio/rp/module.md create mode 100644 src/modm/platform/gpio/rp/peripherals.hpp.in create mode 100644 src/modm/platform/gpio/rp/port.hpp.in create mode 100644 src/modm/platform/gpio/rp/set.hpp.in create mode 100644 src/modm/platform/gpio/rp/software_port.hpp.in create mode 100644 src/modm/platform/gpio/rp/static.hpp.in diff --git a/src/modm/platform/gpio/common/connector.hpp.in b/src/modm/platform/gpio/common/connector.hpp.in index 1fb4dcbd41..0ee46e9c1a 100644 --- a/src/modm/platform/gpio/common/connector.hpp.in +++ b/src/modm/platform/gpio/common/connector.hpp.in @@ -1,5 +1,6 @@ /* * Copyright (c) 2017, 2021, Niklas Hauser + * Copyright (c) 2022, Andrey Kunitsyn * * This file is part of the modm project. * @@ -77,6 +78,15 @@ struct GpioConnector %% endif %% elif target.platform in ["avr"] static Connection check [[maybe_unused]]; +%% elif target.platform in ["rp"] + using Pin = GpioStatic; + if constexpr(Connection::func == -1) { + Pin::setInput(); + Pin::disconnect(); + } + if constexpr (Connection::func >= 0) { + Pin::setFunction(Connection::func); + } %% endif } diff --git a/src/modm/platform/gpio/common/pins.hpp.in b/src/modm/platform/gpio/common/pins.hpp.in index 8547ba4c25..34d72b61fb 100644 --- a/src/modm/platform/gpio/common/pins.hpp.in +++ b/src/modm/platform/gpio/common/pins.hpp.in @@ -1,5 +1,6 @@ /* * Copyright (c) 2021, Niklas Hauser + * Copyright (c) 2022, Andrey Kunitsyn * * This file is part of the modm project. * @@ -20,11 +21,14 @@ namespace modm::platform /// @ingroup modm_platform_gpio /// @{ %% for gpio in gpios -%% set port = gpio.gpio.port | upper -%% set pin = gpio.gpio.pin -using Gpio{{ port ~ pin }} = GpioStatic; -using GpioOutput{{ port ~ pin }} = Gpio{{ port ~ pin }}; -using GpioInput{{ port ~ pin }} = Gpio{{ port ~ pin }}; +%% if target.platform in ["rp"] +%% set name = gpio.gpio.name | capitalize +%% else +%% set name = gpio.gpio.port | upper ~ gpio.gpio.pin +%% endif +using Gpio{{ name }} = GpioStatic; +using GpioOutput{{ name }} = Gpio{{ name }}; +using GpioInput{{ name }} = Gpio{{ name }}; %# %% endfor /// @} diff --git a/src/modm/platform/gpio/common/unused.hpp.in b/src/modm/platform/gpio/common/unused.hpp.in index fdf2c93372..80c861aa62 100644 --- a/src/modm/platform/gpio/common/unused.hpp.in +++ b/src/modm/platform/gpio/common/unused.hpp.in @@ -1,5 +1,6 @@ /* * Copyright (c) 2017-2018, 2021, Niklas Hauser + * Copyright (c) 2022, Andrey Kunitsyn * * This file is part of the modm project. * @@ -70,6 +71,9 @@ protected: static void setAlternateFunction(uint8_t) {} static void setAlternateFunction() {} static void setAnalogInput() {} +%% elif target.platform in ["rp"] + static void setFunction(uint8_t) {} + static void setDriveStrength(DriveStrength) {} %% endif public: // GpioOutput @@ -108,7 +112,7 @@ public: using Data = detail::DataUnused; static constexpr platform::Gpio::Signal Signal = platform::Gpio::Signal::BitBang; }; -%% for name in all_signals +%% for name in all_signals | sort struct {{ name }} { using Data = detail::DataUnused; diff --git a/src/modm/platform/gpio/rp/base.hpp.in b/src/modm/platform/gpio/rp/base.hpp.in new file mode 100644 index 0000000000..809e32751c --- /dev/null +++ b/src/modm/platform/gpio/rp/base.hpp.in @@ -0,0 +1,134 @@ +/* + * Copyright (c) 2022, Andrey Kunitsyn + * Copyright (c) 2022, Niklas Hauser + * + * This file is part of the modm project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +// ---------------------------------------------------------------------------- + +#pragma once + +#include +#include +#include +%% for port in ports +#include +#include +%% endfor +#include + +namespace modm::platform +{ + +/// @ingroup modm_platform_gpio +struct Gpio +{ + enum class + InputType + { + Floating = 0x0, ///< floating on input + PullUp = 0x1, ///< pull-up on input + PullDown = 0x2, ///< pull-down on input + }; + + enum class + OutputType + { + PushPull + }; + + enum class + OutputSpeed + { + Slow = 0, + Fast = 1, + }; + enum class + DriveStrength + { + mA_2 = 0, + mA_4 = 1, + mA_8 = 2, + mA_12 = 3, + }; + + enum class + Port + { +%% for port in ports + {{ port }}, +%% endfor + }; + + /// @cond + enum class + Signal + { + BitBang, +%% for signal in all_signals | sort + {{ signal }}, +%% endfor + }; + + template + struct PortRegs; + /// @endcond +}; + +/// @cond +%% for port in ports +template <> +struct Gpio::PortRegs +{ + using status_ctrl_hw_t = io{{port | lower}}_status_ctrl_hw_t; + static uint32_t sio_in() { return sio_hw->gpio{{sio_names[port]}}_in; } + static uint32_t sio_out() { return sio_hw->gpio{{sio_names[port]}}_out; } + static uint32_t sio_oe() { return sio_hw->gpio{{sio_names[port]}}_oe; } + static void sio_set(uint32_t mask) { sio_hw->gpio{{sio_names[port]}}_set = mask; } + static void sio_clr(uint32_t mask) { sio_hw->gpio{{sio_names[port]}}_clr = mask; } + static void sio_togl(uint32_t mask) { sio_hw->gpio{{sio_names[port]}}_togl = mask; } + static void sio_oe_set(uint32_t mask) { sio_hw->gpio{{sio_names[port]}}_oe_set = mask; } + static void sio_oe_clr(uint32_t mask) { sio_hw->gpio{{sio_names[port]}}_oe_clr = mask; } + static uint8_t funcsel(uint8_t pin) + { return (io{{port | lower}}_hw->io[pin].ctrl & IO_BANK0_GPIO0_CTRL_FUNCSEL_BITS) >> IO_BANK0_GPIO0_CTRL_FUNCSEL_LSB; } + static void set_funcsel(uint8_t pin, uint32_t fn) + { + hw_write_masked(&pads{{(pads_names[port] or port) | lower}}_hw->io[pin], + PADS_BANK0_GPIO0_IE_BITS, + PADS_BANK0_GPIO0_IE_BITS | PADS_BANK0_GPIO0_OD_BITS + ); + io{{port | lower}}_hw->io[pin].ctrl = fn << IO_BANK0_GPIO0_CTRL_FUNCSEL_LSB; + } + static void set_pue_pde(uint8_t pin, bool up, bool down) + { + hw_write_masked( + &pads{{(pads_names[port] or port) | lower}}_hw->io[pin], + ((up?1:0) << PADS_BANK0_GPIO0_PUE_LSB) | ((down?1:0) << PADS_BANK0_GPIO0_PDE_LSB), + PADS_BANK0_GPIO0_PUE_BITS | PADS_BANK0_GPIO0_PDE_BITS + ); + } + static void set_drive(uint8_t pin, uint8_t strength) + { + hw_write_masked( + &pads{{(pads_names[port] or port) | lower}}_hw->io[pin], + strength << PADS_BANK0_GPIO0_DRIVE_LSB, + PADS_BANK0_GPIO0_DRIVE_BITS + ); + } + static void set_slewfast(uint8_t pin, bool fast) + { + hw_write_masked( + &pads{{(pads_names[port] or port) | lower}}_hw->io[pin], + (fast?1:0) << PADS_BANK0_GPIO0_SLEWFAST_LSB, + PADS_BANK0_GPIO0_SLEWFAST_BITS + ); + } +}; +%% endfor +/// @endcond + +} // namespace modm::platform diff --git a/src/modm/platform/gpio/rp/data.hpp.in b/src/modm/platform/gpio/rp/data.hpp.in new file mode 100644 index 0000000000..3212997811 --- /dev/null +++ b/src/modm/platform/gpio/rp/data.hpp.in @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2022, Andrey Kunitsyn + * Copyright (c) 2022, Niklas Hauser + * + * This file is part of the modm project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +// ---------------------------------------------------------------------------- + +#pragma once + +#include "base.hpp" + +/// @cond +namespace modm::platform::detail +{ + +template struct SignalConnection; + +struct DataUnused {}; +%% for gpio in gpios +%% set name = gpio.gpio.name | capitalize +%# +struct Data{{ name }} +{ + static constexpr Gpio::Port port = Gpio::Port::{{ gpio.gpio.port | capitalize }}; + static constexpr uint8_t pin = {{ gpio.gpio.pin }}; + struct BitBang { using Data = Data{{ name }}; static constexpr Gpio::Signal Signal = Gpio::Signal::BitBang; }; +%% for signal in gpio.signals + struct {{ signal }} { using Data = Data{{ name }}; static constexpr Gpio::Signal Signal = Gpio::Signal::{{ signal }}; }; +%% endfor +}; +template struct SignalConnection { + static_assert(p == Peripheral::BitBang, "Gpio{{ name }}::BitBang only connects to software drivers!"); }; +%% for signal_name, signal_group in gpio.signals.items() +template struct SignalConnection { + static_assert((p == Peripheral::{{ signal_group | map(attribute="driver") | join(") or (p == Peripheral::") }}),{% if signal_group | length > 1 %} + {% endif %}"Gpio{{ name }}::{{ signal_name }} only connects to {{ signal_group | map(attribute="driver") | join(" or ") }}!"); }; +%% endfor +template<> struct SignalConnection { static constexpr int8_t func = -1; }; +%% for signal_group in gpio.signals.values() + %% for signal in signal_group +template<> struct SignalConnection { static constexpr int8_t func = {{ signal.af }}; }; + %% endfor +%% endfor +%% endfor + +} // namespace modm::platform::detail +/// @endcond diff --git a/src/modm/platform/gpio/rp/module.lb b/src/modm/platform/gpio/rp/module.lb new file mode 100644 index 0000000000..6ae1fa611b --- /dev/null +++ b/src/modm/platform/gpio/rp/module.lb @@ -0,0 +1,110 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# Copyright (c) 2022, Andrey Kunitsyn +# Copyright (c) 2022, Niklas Hauser +# +# This file is part of the modm project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# ----------------------------------------------------------------------------- + +import copy +from collections import defaultdict, OrderedDict + +def port_ranges(gpios): + ports = {p: (32, 0) for p in set(p["port"] for p in gpios)} + for gpio in gpios: + pin = int(gpio["pin"]) + pmin, pmax = ports[gpio["port"]] + ports[gpio["port"]] = (min(pin, pmin), max(pin, pmax)) + + ports = [{"name": k.capitalize(), "start": v[0], "width": v[1] - v[0] + 1} for k,v in ports.items()] + ports.sort(key=lambda p: p["name"]) + return ports + +def translate(s): + return "".join(p.capitalize() for p in s.split("_")) +# ----------------------------------------------------------------------------- + + +def init(module): + module.name = ":platform:gpio" + module.description = "General Purpose I/O (GPIO)" + + +def prepare(module, options): + module.depends(":architecture:gpio", ":cmsis:device", ":math:utils") + return options[":target"].has_driver("gpio:rp*") + + +def build(env): + device = env[":target"] + driver = device.get_driver("gpio") + + subs = { + "ranges": port_ranges(driver["gpio"]), + "sio_names": { + "Bank0": "", + "Qspi": "_hi" + }, + "pads_names": { + "Bank0": "bank0", + "Qspi": "_qspi" + }, + "port_width": 32 + } + subs["ports"] = OrderedDict([(k, i) for i, k in enumerate([p["name"].capitalize() for p in subs["ranges"]])]) + + peripherals = [] + all_drivers = [d for d in device._properties["driver"] if d["name"] not in ["gpio", "core"]] + for d in all_drivers: + dname = translate(d["name"]) + if "instance" in d: + peripherals.extend([dname + translate(i) for i in d["instance"]]) + else: + peripherals.append(dname) + subs["all_peripherals"] = sorted(list(set(peripherals))) + + all_signals = set() + for gpio in driver["gpio"]: + gpio["signals"] = set([translate(s["name"]) for s in gpio["signal"]]) + all_signals.update(gpio["signals"]) + signals = defaultdict(list) + for sig in gpio["signal"]: + signals[translate(sig["name"])].append( + {"driver": sig["driver"].capitalize() + sig.get("instance", ""), + "name": translate(sig["name"]), + "af": sig["af"]}) + subs[gpio["name"].capitalize()] = { + "gpio": gpio, + "signals": signals, + } + + subs["target"] = device.identifier + subs["all_signals"] = all_signals + subs["gpios"] = [subs[gpio["name"].capitalize()] for gpio in driver["gpio"]] + + env.substitutions = subs + env.outbasepath = "modm/src/modm/platform/gpio" + + env.template("base.hpp.in") + env.template("data.hpp.in") + env.template("static.hpp.in") + env.template("set.hpp.in") + env.template("software_port.hpp.in") + env.template("port.hpp.in") + + env.copy("../common/inverted.hpp", "inverted.hpp") + env.copy("../common/open_drain.hpp", "open_drain.hpp") + env.template("../common/connector.hpp.in", "connector.hpp", + filters={"formatPeripheral": "", "printSignalMap": ""}) + env.template("../common/unused.hpp.in", "unused.hpp") + env.template("../common/pins.hpp.in", "pins.hpp") + env.template("../common/port_shim.hpp.in", "port_shim.hpp") + + # FIXME: Move to modm:platform:core! + env.outbasepath = "modm/src/modm/platform/core" + env.template("peripherals.hpp.in") diff --git a/src/modm/platform/gpio/rp/module.md b/src/modm/platform/gpio/rp/module.md new file mode 100644 index 0000000000..b0f1c9a158 --- /dev/null +++ b/src/modm/platform/gpio/rp/module.md @@ -0,0 +1,125 @@ +# General Purpose I/O (GPIO) + +This module provides register access to GPIO and connect their signals to the +respective peripherals in a compile-time verified way. + +Each GPIO is represented as its own class with only static methods, which +implement the `modm::GpioIO` interface and provide additional platform-specific +methods. + +```cpp +using namespace modm::platform; + +using Button = GpioInput0; +Button::setInput(Gpio::InputType::PullUp); +bool input = Button::read(); + +using Led = GpioInverted; // inverts the IO logic of the pin + +Led::setOutput(Gpio::OutputType::PushPull, Gpio::OutputSpeed::Fast); +Led::set(input); + +using Signal = Gpio4; +Signal::setFunction(4); // For func id see datasheet +Signal::disconnect(); // Switch back to floating input + +using OpenDrain = GpioOpenDrain; +OpenDrain::setInput(Gpio::InputType::PullUp); +OpenDrain::set(); // Input with Pullup +OpenDrain::reset(); // Output with PushPull Low +``` + +You can also use an unordered set of GPIOs, which is useful when configuring a +large number of pins, since the register accesses will be bundled and thus less +code is generated. + +```cpp +using Set = GpioSet; +Set::setInput(); +``` + +To write and read a set of GPIOs, you need to use an ordered implementation, +which defines the pins from MSB to LSB, left-to-right. You can also check the +number of ports in case your use-case requires atomic reads/writes. + +```cpp +using Port = SoftwareGpioPort; +static_assert(Port::number_of_ports == 1, "Read/write needs to be atomic"); +Port::setInput(Gpio::InputType::PullUp); +uint8_t nibble = Port::read(); +Port::setOutput(); +Port::write(nibble); +``` + +For efficient access you can use a strictly-ordered implementation with a start +pin and width. Note that you can reverse the data order with a negative width. + +```cpp +using Port = GpioPort; +Port::setOutput(); +Port::write(data); + +using ReversePort = GpioPort; +ReversePort::setInput(); +uint8_t data = ReversePort::read(); +``` + +Finally, you can use an empty GPIO implementation in cases where the API +requires a GPIO, but you don't need one, for example, a bit-banged SPI without +MISO pin: + +```cpp +// write only SPI +using SPI = modm::platform::BitBangSpiMaster; +``` + + +## Function Signals + +To make it easier to connect pins with peripherals, this module implements a +compile-time map of (pin, signal, peripheral) to Function ID (func). +Note that you must provide both peripherals and signals to be unambiguous. + +```cpp +GpioConnector::connect(); +``` + +However, it is recommended to wrap this functionality into a separate function +`Driver::connect(config)`, so that additional driver specific pin +configuration can be done: + +```cpp +template< class... Signals > +void Uart1::connect() +{ + Connector = GpioConnector; + Connector::disconnect(); // reset to floating input + + // extract pins from signals + using Rx = Connector::GetSignal; + using Tx = Connector::GetSignal; + // if not found, returns GpioUnused, you can check for this case + static_assert(not Connector::isValid, + "This UART driver requires the Tx signal"); + + // configure both pins + Rx::configure(Gpio::InputType::PullUp); + Tx::configure(Gpio::OutputType::PushPull); + + // connect both pins to alternate functions + // This will static assert if signals do not make sense + Connector::connect(); +} +// Connect these pin signals to Uart1 +Uart1::connect(); +``` + +Note that you may pass a *variable* number of signals to this connect function, +leaving out signals you don't need and adding signals that are not required. + +```cpp +// Connect only one signal +Uart1::connect(); +// Connect more signals than required +Uart1::connect(); +``` diff --git a/src/modm/platform/gpio/rp/peripherals.hpp.in b/src/modm/platform/gpio/rp/peripherals.hpp.in new file mode 100644 index 0000000000..2368486d9f --- /dev/null +++ b/src/modm/platform/gpio/rp/peripherals.hpp.in @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2022, Andrey Kunitsyn + * + * This file is part of the modm project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +// ---------------------------------------------------------------------------- + +#pragma once + +namespace modm::platform +{ + +/// @ingroup modm_platform_core +enum class +Peripheral +{ + BitBang, +%% for per in all_peripherals + {{ per }}, +%% endfor +}; + +} diff --git a/src/modm/platform/gpio/rp/port.hpp.in b/src/modm/platform/gpio/rp/port.hpp.in new file mode 100644 index 0000000000..9396be8997 --- /dev/null +++ b/src/modm/platform/gpio/rp/port.hpp.in @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2016-2018, 2022, Niklas Hauser + * + * This file is part of the modm project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +// ---------------------------------------------------------------------------- + +#pragma once + +#include "port_shim.hpp" +#include "set.hpp" +#include +#include + +namespace modm::platform +{ + +/// @ingroup modm_platform_gpio +template< class StartGpio, int8_t Width > +class GpioPort : public ::modm::GpioPort/** @cond */, public detail::GpioSetShim /** @endcond */ +{ + using PinSet = detail::GpioSetShim; +public: + using PinSet::width; + static_assert(width <= 16, "Only a maximum of 16 pins are supported by GpioPort!"); + using PortType = std::conditional_t< (width > 8), uint16_t, uint8_t >; + static constexpr DataOrder getDataOrder() + { return Width > 0 ? GpioPort::DataOrder::Normal : GpioPort::DataOrder::Reversed; } + +protected: + using PinSet::mask; + using PinSet::inverted; + static constexpr uint8_t StartPin = Width > 0 ? StartGpio::pin : StartGpio::pin - width + 1; + static constexpr uint8_t StartPinReversed = (8 - StartPin - width) + 8; + +public: + static PortType isSet() + { + uint16_t r{0}; +%% for port, id in ports.items() + if constexpr (mask({{id}})) r = (Gpio::PortRegs::sio_out() & mask({{id}})) ^ inverted({{id}}); +%% endfor + if constexpr (getDataOrder() == modm::GpioPort::DataOrder::Reversed) + return bitReverse(r) >> StartPinReversed; + else return r >> StartPin; + } + + static PortType read() + { + uint16_t r{0}; +%% for port, id in ports.items() + if constexpr (mask({{id}})) r = (Gpio::PortRegs::sio_in() & mask({{id}})) ^ inverted({{id}}); +%% endfor + if constexpr (getDataOrder() == modm::GpioPort::DataOrder::Reversed) + return bitReverse(r) >> StartPinReversed; + else return r >> StartPin; + } + + static void write(PortType data) + { + uint16_t p; + if constexpr (getDataOrder() == modm::GpioPort::DataOrder::Reversed) + p = bitReverse(uint16_t(uint16_t(data) << StartPinReversed)); + else p = uint16_t(data) << StartPin; +%% for port, id in ports.items() + if constexpr (mask({{id}})) { + p ^= inverted({{id}}); + Gpio::PortRegs::sio_set(p); + Gpio::PortRegs::sio_clr(~p & mask({{id}})); + } +%% endfor + } +}; + +} // namespace modm::platform diff --git a/src/modm/platform/gpio/rp/set.hpp.in b/src/modm/platform/gpio/rp/set.hpp.in new file mode 100644 index 0000000000..52f895b8c9 --- /dev/null +++ b/src/modm/platform/gpio/rp/set.hpp.in @@ -0,0 +1,133 @@ +/* + * Copyright (c) 2022, Andrey Kunitsyn + * Copyright (c) 2022, Niklas Hauser + * + * This file is part of the modm project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +// ---------------------------------------------------------------------------- + +#pragma once + +#include "../device.hpp" +#include "base.hpp" + +namespace modm::platform +{ + +/// @ingroup modm_platform_gpio +template< class... Gpios > +class GpioSet : public Gpio +{ + static constexpr uint32_t inverteds[{{ ports | length }}] = { +%% for port in ports + (((Gpios::port == Port::{{port}} and Gpios::isInverted) ? Gpios::mask : 0) | ...), +%% endfor + }; + + static constexpr uint32_t masks[{{ ports | length }}] = { +%% for port in ports + (((Gpios::port == Port::{{port}}) ? Gpios::mask : 0) | ...), +%% endfor + }; + static constexpr uint8_t numberOfPorts() { + uint8_t r{0}; + for (const auto &m: masks) r += (m) ? 1 : 0; + return r; + } + +protected: + static constexpr uint32_t inverted(uint8_t id) { return inverteds[id]; } + static constexpr uint32_t mask(uint8_t id) { return masks[id]; } + +public: + static constexpr uint8_t width = sizeof...(Gpios); + static constexpr uint8_t number_of_ports = numberOfPorts(); + +public: + static void setOutput() + { + disconnect(); +%% for port, id in ports.items() + if constexpr (mask({{id}})) PortRegs::sio_oe_set(mask({{id}})); +%% endfor + } + + static void setOutput(bool status) + { + set(status); + setOutput(); + } + + static void setOutput(OutputType type, OutputSpeed speed = OutputSpeed::Slow) + { + setOutput(); + configure(type, speed); + } + + static void configure(OutputType, OutputSpeed speed = OutputSpeed::Slow) + { + (PortRegs::set_speed(Gpios::pin, speed), ...); + } + + static void setInput() + { + setInput(InputType::Floating); + } + + static void setInput(InputType type) + { +%% for port, id in ports.items() + if constexpr (mask({{id}})) PortRegs::sio_oe_clr(mask({{id}})); +%% endfor + configure(type); + } + + static void configure(InputType type) + { + (PortRegs::set_pue_pde(Gpios::pin, type==InputType::PullUp, type==InputType::PullDown), ...); + } + + static void set() + { +%% for port, id in ports.items() + if constexpr (mask({{id}}) & ~inverted({{id}})) + PortRegs::sio_set(mask({{id}}) & ~inverted({{id}})); + if constexpr (mask({{id}}) & inverted({{id}})) + PortRegs::sio_clr(mask({{id}}) & inverted({{id}})); +%% endfor + } + + static void set(bool status) + { + if (status) set(); + else reset(); + } + + static void reset() + { +%% for port, id in ports.items() + if constexpr (mask({{id}}) & inverted({{id}})) + PortRegs::sio_set(mask({{id}}) & inverted({{id}})); + if constexpr (mask({{id}}) & ~inverted({{id}})) + PortRegs::sio_clr(mask({{id}}) & ~inverted({{id}})); +%% endfor + } + + static void toggle() + { +%% for port, id in ports.items() + if constexpr (mask({{id}})) PortRegs::sio_togl(mask({{id}})); +%% endfor + } + + static void disconnect() + { + (Gpios::disconnect(), ...); + } +}; + +} // namespace modm::platform diff --git a/src/modm/platform/gpio/rp/software_port.hpp.in b/src/modm/platform/gpio/rp/software_port.hpp.in new file mode 100644 index 0000000000..8699e620e4 --- /dev/null +++ b/src/modm/platform/gpio/rp/software_port.hpp.in @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2018, 2022, Niklas Hauser + * Copyright (c) 2022, Andrey Kunitsyn + * + * This file is part of the modm project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +// ---------------------------------------------------------------------------- + +#pragma once + +#include "set.hpp" +#include + +namespace modm::platform +{ + +/** + * Create an up to 32-bit port from arbitrary pins. + * + * This class optimizes the data type for the `read()` and `write()` methods. + * Supplying up to 8 Gpios will use `uint8_t`, up to 16 Gpios `uint16_t` and + * up to 32 Gpios `uint32_t`. + * + * @note Since the bit order is explicitly given by the order of arguments, + * this class only supports `DataOrder::Normal`. + * If you need reverse bit order, reverse the order of `Gpios`! + * + * @tparam Gpios Up to 32 GpioIO classes, ordered MSB to LSB + * + * @author Niklas Hauser + * @ingroup modm_platform_gpio + */ +template< class... Gpios > +class SoftwareGpioPort : public ::modm::GpioPort, public GpioSet +{ + using Set = GpioSet; +public: + using Set::width; + static_assert(width <= 32, "Only a maximum of 32 pins are supported by this Port!"); + using PortType = std::conditional_t< (width > 8), + std::conditional_t< (width > 16), + uint32_t, + uint16_t >, + uint8_t >; + static constexpr DataOrder getDataOrder() + { return ::modm::GpioPort::DataOrder::Normal; } + +protected: + static constexpr int8_t shift_masks[{{ ports | length }}][width] = { +%% for port in ports + {(Gpios::port == Gpio::Port::{{port}} ? Gpios::pin : -1)...}, +%% endfor + }; + static constexpr int8_t shift_mask(uint8_t id, uint8_t pos) { return shift_masks[id][width - 1 - pos]; } + using Set::mask; + using Set::inverted; + +public: + static PortType isSet() + { + PortType r{0}; +%% for port, id in ports.items() + if constexpr (mask({{id}})) { + const uint32_t p = (Gpio::PortRegs::sio_out() & mask({{id}})) ^ inverted({{id}}); + %% for pos in range(32) + if constexpr ({{pos}} < width) if constexpr (constexpr auto pos = shift_mask({{id}}, {{pos}}); pos >= 0) r |= ((p >> pos) & 1) << {{pos}}; + %% endfor + } +%% endfor + return r; + } + + static void write(PortType data) + { +%% for port, id in ports.items() + if constexpr (mask({{id}})) { uint32_t ps{0}; uint32_t pr{0}; + %% for pos in range(32) + if constexpr ({{pos}} < width) if constexpr (constexpr auto pos = shift_mask({{id}}, {{pos}}); pos >= 0) { if (data & (1ul << {{pos}})) { ps |= (1ul << pos); } else { pr |= (1ul << pos); }} + %% endfor + ps ^= inverted({{id}}); + pr ^= inverted({{id}}); + if (ps) Gpio::PortRegs::sio_set(ps); + if (pr) Gpio::PortRegs::sio_clr(pr); + } +%% endfor + } + + static PortType read() + { + PortType r{0}; +%% for port, id in ports.items() + if constexpr (mask({{id}})) { + const uint32_t p = (Gpio::PortRegs::sio_in() & mask({{id}})) ^ inverted({{id}}); + %% for pos in range(32) + if constexpr ({{pos}} < width) if constexpr (constexpr auto pos = shift_mask({{id}}, {{pos}}); pos >= 0) r |= ((p >> pos) & 1) << {{pos}}; + %% endfor + } +%% endfor + return r; + } +}; + +} // namespace modm::platform diff --git a/src/modm/platform/gpio/rp/static.hpp.in b/src/modm/platform/gpio/rp/static.hpp.in new file mode 100644 index 0000000000..aa274ec2e0 --- /dev/null +++ b/src/modm/platform/gpio/rp/static.hpp.in @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2022, Andrey Kunitsyn + * Copyright (c) 2022, Niklas Hauser + * + * This file is part of the modm project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +// ---------------------------------------------------------------------------- + +#pragma once + +#include + +#include "../device.hpp" +#include "set.hpp" +#include + +namespace modm::platform +{ + +/// @ingroup modm_platform_gpio +template +class GpioStatic : public Gpio, public GpioData +{ + template friend class GpioSet; + using PinSet = GpioSet>; + using Regs = PortRegs; + static constexpr uint8_t SioFunc = 5; + +public: + using Direction = ::modm::Gpio::Direction; + using Type = GpioStatic; + using Output = Type; + using Input = Type; + using IO = Type; + using Data = GpioData; + using GpioData::port; + using GpioData::pin; + static constexpr Direction direction = Direction::InOut; + static constexpr bool isInverted = false; + static constexpr uint32_t mask = 1lu << pin; + + static void setFunction(uint8_t func) { Regs::set_funcsel(pin, func); } + static void setDriveStrength(DriveStrength strength) + { Regs::set_drive(pin, strength); } + +public: + static void setOutput() { PinSet::setOutput(); } + static void setOutput(bool value) { PinSet::setOutput(value); } + static void setOutput(OutputType type, OutputSpeed speed=OutputSpeed::Slow) + { PinSet::setOutput(type, speed); } + static void configure(OutputType type, OutputSpeed speed=OutputSpeed::Slow) + { PinSet::configure(type, speed); } + + static void set() { PinSet::set(); } + static void set(bool status) { PinSet::set(status); } + static void reset() { PinSet::reset(); } + static bool isSet() { return Regs::sio_out() & mask; } + static void toggle() { PinSet::toggle(); } + + static void setInput() { PinSet::setInput(); } + static void setInput(InputType type) { PinSet::setInput(type); } + static void configure(InputType type) { PinSet::configure(type); } + + static bool read() { return Regs::sio_in() & mask; } + + static Direction getDirection() + { + if (Regs::funcsel(pin) != SioFunc) return Direction::Special; + return (Regs::sio_oe() & mask) ? Direction::Out : Direction::In; + } + static void disconnect() { setFunction(SioFunc); } +}; + +} // namespace modm::platform diff --git a/src/modm/platform/i2c/bitbang/bitbang_i2c_master.hpp.in b/src/modm/platform/i2c/bitbang/bitbang_i2c_master.hpp.in index 50f987cc97..9027020e94 100644 --- a/src/modm/platform/i2c/bitbang/bitbang_i2c_master.hpp.in +++ b/src/modm/platform/i2c/bitbang/bitbang_i2c_master.hpp.in @@ -20,7 +20,7 @@ #include #include #include -%% if target.platform in ["avr"] +%% if target.platform in ["avr", "rp"] #include %% endif @@ -41,7 +41,7 @@ template< class Scl, class Sda > class BitBangI2cMaster : public modm::I2cMaster { -%% if target.platform in ["avr"] +%% if target.platform in ["avr", "rp"] using SCL = platform::GpioOpenDrain; using SDA = platform::GpioOpenDrain; %% else diff --git a/src/modm/platform/one_wire/bitbang/bitbang_master.hpp.in b/src/modm/platform/one_wire/bitbang/bitbang_master.hpp.in index 0846eab5ac..eb64a4c591 100644 --- a/src/modm/platform/one_wire/bitbang/bitbang_master.hpp.in +++ b/src/modm/platform/one_wire/bitbang/bitbang_master.hpp.in @@ -17,7 +17,7 @@ #include #include #include -%% if target.platform in ["avr"] +%% if target.platform in ["avr", "rp"] #include %% endif @@ -48,7 +48,7 @@ namespace platform template class BitBangOneWireMaster { -%% if target.platform in ["avr"] +%% if target.platform in ["avr", "rp"] using PIN = platform::GpioOpenDrain; %% else using PIN = Pin; From e3d333330e5726947c0cc788842f59938df0a9e4 Mon Sep 17 00:00:00 2001 From: andryblack Date: Thu, 20 Jan 2022 13:50:49 +0100 Subject: [PATCH 05/13] [rp] Add clocks module --- src/modm/platform/clock/rp/clocks.cpp | 148 ++++++++++++++++ src/modm/platform/clock/rp/clocks.hpp.in | 204 +++++++++++++++++++++++ src/modm/platform/clock/rp/module.lb | 70 ++++++++ 3 files changed, 422 insertions(+) create mode 100644 src/modm/platform/clock/rp/clocks.cpp create mode 100644 src/modm/platform/clock/rp/clocks.hpp.in create mode 100644 src/modm/platform/clock/rp/module.lb diff --git a/src/modm/platform/clock/rp/clocks.cpp b/src/modm/platform/clock/rp/clocks.cpp new file mode 100644 index 0000000000..d40139f41c --- /dev/null +++ b/src/modm/platform/clock/rp/clocks.cpp @@ -0,0 +1,148 @@ +/* + * Copyright (c) 2022, Andrey Kunitsyn + * + * This file is part of the modm project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +// ---------------------------------------------------------------------------- + +#include "clocks.hpp" + +#include + +#include "../device.hpp" + +#ifndef PICO_XOSC_STARTUP_DELAY_MULTIPLIER +#define PICO_XOSC_STARTUP_DELAY_MULTIPLIER 1 +#endif + +// CMSIS Core compliance +constinit uint32_t modm_fastdata SystemCoreClock(modm::platform::ClockControl::BootFrequency); + +namespace modm::platform +{ +constinit uint16_t modm_fastdata delay_fcpu_MHz(computeDelayMhz(ClockControl::BootFrequency)); +constinit uint16_t modm_fastdata delay_ns_per_loop(computeDelayNsPerLoop(ClockControl::BootFrequency)); + +static uint32_t configured_freq[CLK_COUNT]; + +void +ClockControl::enableExternalCrystal(uint32_t freq) +{ + // Assumes 1-15 MHz input, checked above. + xosc_hw->ctrl = XOSC_CTRL_FREQ_RANGE_VALUE_1_15MHZ; + + uint32_t delay = ((((freq / 1000) + 128) / 256) * PICO_XOSC_STARTUP_DELAY_MULTIPLIER); + + // Set xosc startup delay + xosc_hw->startup = delay; + + // Set the enable bit now that we have set freq range and startup delay + hw_set_bits(&xosc_hw->ctrl, XOSC_CTRL_ENABLE_VALUE_ENABLE << XOSC_CTRL_ENABLE_LSB); + + // Wait for XOSC to be stable + while (!(xosc_hw->status & XOSC_STATUS_STABLE_BITS)) __NOP(); +} + +void +ClockControl::configureImpl(Clock clk, uint32_t src, uint32_t auxsrc, uint32_t div, uint32_t freq) +{ + clock_hw_t *clock = &clocks_hw->clk[uint32_t(clk)]; + // 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 > clock->div) clock->div = div; + // If switching a glitchless slice 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 (src == CLOCKS_CLK_SYS_CTRL_SRC_VALUE_CLKSRC_CLK_SYS_AUX) + { + hw_clear_bits(&clock->ctrl, CLOCKS_CLK_REF_CTRL_SRC_BITS); + while (!(clock->selected & 1u)) __NOP(); + } + // 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. + else + { + // Disable clock. On clk_ref and clk_sys this does nothing, + // all other clocks have the ENABLE bit in the same position. + hw_clear_bits(&clock->ctrl, CLOCKS_CLK_GPOUT0_CTRL_ENABLE_BITS); + if (configured_freq[uint32_t(clk)] > 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: + uint32_t delay_cyc = configured_freq[clk_sys] / configured_freq[uint32_t(clk)] + 1; + asm volatile( + ".syntax unified \n\t" + "1: \n\t" + "subs %0, #1 \n\t" + "bne 1b" + : "+r"(delay_cyc)); + } + } + + // Set aux mux first, and then glitchless mux + hw_write_masked(&clock->ctrl, (auxsrc << CLOCKS_CLK_SYS_CTRL_AUXSRC_LSB), + CLOCKS_CLK_SYS_CTRL_AUXSRC_BITS); + + hw_write_masked(&clock->ctrl, src << CLOCKS_CLK_REF_CTRL_SRC_LSB, + CLOCKS_CLK_REF_CTRL_SRC_BITS); + while (!(clock->selected & (1u << src))) __NOP(); + + // Enable clock. On clk_ref and clk_sys this does nothing, + // all other clocks have the ENABLE bit in the same position. + hw_set_bits(&clock->ctrl, CLOCKS_CLK_GPOUT0_CTRL_ENABLE_BITS); + + // Now that the source is configured, we can trust that the user-supplied + // divisor is a safe value. + clock->div = div; + // Store the configured frequency + configured_freq[uint32_t(clk)] = freq; +} + +void +ClockControl::configureImpl(Clock clk, uint32_t auxsrc, uint32_t div, uint32_t freq) +{ + + clock_hw_t *clock = &clocks_hw->clk[uint32_t(clk)]; + // 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 > clock->div) clock->div = div; + // Disable clock. On clk_ref and clk_sys this does nothing, + // all other clocks have the ENABLE bit in the same position. + hw_clear_bits(&clock->ctrl, CLOCKS_CLK_GPOUT0_CTRL_ENABLE_BITS); + if (configured_freq[uint32_t(clk)] > 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: + uint32_t delay_cyc = configured_freq[clk_sys] / configured_freq[uint32_t(clk)] + 1; + asm volatile( + ".syntax unified \n\t" + "1: \n\t" + "subs %0, #1 \n\t" + "bne 1b" + : "+r"(delay_cyc)); + } + + // Set aux mux + hw_write_masked(&clock->ctrl, (auxsrc << CLOCKS_CLK_SYS_CTRL_AUXSRC_LSB), + CLOCKS_CLK_SYS_CTRL_AUXSRC_BITS); + + // Enable clock. On clk_ref and clk_sys this does nothing, + // all other clocks have the ENABLE bit in the same position. + hw_set_bits(&clock->ctrl, CLOCKS_CLK_GPOUT0_CTRL_ENABLE_BITS); + + // Now that the source is configured, we can trust that the user-supplied + // divisor is a safe value. + clock->div = div; + // Store the configured frequency + configured_freq[uint32_t(clk)] = freq; +} +} // namespace modm::platform diff --git a/src/modm/platform/clock/rp/clocks.hpp.in b/src/modm/platform/clock/rp/clocks.hpp.in new file mode 100644 index 0000000000..1e3da295b7 --- /dev/null +++ b/src/modm/platform/clock/rp/clocks.hpp.in @@ -0,0 +1,204 @@ +/* + * Copyright (c) 2022, Andrey Kunitsyn + * + * This file is part of the modm project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +// ---------------------------------------------------------------------------- + +#pragma once + +#include +#include +#include + +#include +#include +#include + +namespace modm::platform +{ + +/// @ingroup modm_platform_clockgen +class ClockControl +{ +public: + enum class Clock + { +%% for clk in clocks + {{ clk['name'] | capitalize }} = {{ clk['idx'] | int }}, +%% endfor + }; + enum class ClockSrc + { +%% for src in sources + {{ src }}, +%% endfor + }; + + enum class Pll + { + Sys, + Usb, + }; + + static constexpr uint32_t BootFrequency = 125'000'000; // after bootloader + +protected: + /// @cond + struct InvalidConfig + { + static constexpr uint32_t src = 0xffffffff; + static constexpr uint32_t aux_src = 0xffffffff; + }; + + template + struct SrcConfig : InvalidConfig + {}; + + static void + configureImpl(Clock clk, uint32_t src, uint32_t auxsrc, uint32_t div, uint32_t freq); + static void + configureImpl(Clock clk, uint32_t auxsrc, uint32_t div, uint32_t freq); + + static constexpr bool + isGlitchless(Clock clk) + { +%% for clk in clocks if clk['glitchless'] == "true" + if (clk == Clock::{{ clk['name'] | capitalize }}) return true; +%% endfor + return false; + } + /// @endcond + +public: + static inline void + disableResus() + { + clocks_hw->resus.ctrl = 0; + } + + static void + enableExternalCrystal(uint32_t freq); + + template + static void + disableAux() + { + static_assert(isGlitchless(clk),"This clock has only aux sources"); +%% for clk in clocks if clk['glitchless'] == "true" + {% if not loop.first %}else {% endif %}if constexpr (clk == Clock::{{ clk['name'] | capitalize }}) + { + hw_clear_bits(&clocks_hw->clk[uint32_t(clk)].ctrl, CLOCKS_CLK_{{ clk['name'] | upper }}_CTRL_SRC_BITS); + } +%% endfor + while (clocks_hw->clk[uint32_t(clk)].selected != 0x1) __NOP(); + } + + template + static void + initPll() + { + pll_hw_t* pll = pll_name == Pll::Sys ? pll_sys_hw : pll_usb_hw; + static_assert(pll_mul >= 16 and pll_mul <= 320, "invalid pll_mul"); + // Check divider ranges + static_assert((post_div1 >= 1 and post_div1 <= 7) and (post_div2 >= 1 and post_div2 <= 7), + "invalid post div"); + // post_div1 should be >= post_div2 + // from appnote page 11 + // postdiv1 is designed to operate with a higher input frequency + // than postdiv2 + static_assert(post_div2 <= post_div1, "post_div2 vs post_div1"); + uint32_t pdiv = (post_div1 << PLL_PRIM_POSTDIV1_LSB) | (post_div2 << PLL_PRIM_POSTDIV2_LSB); + if ((pll->cs & PLL_CS_LOCK_BITS) and (refdiv == (pll->cs & PLL_CS_REFDIV_BITS)) and + (pll_mul == (pll->fbdiv_int & PLL_FBDIV_INT_BITS)) and + (pdiv == (pll->prim & (PLL_PRIM_POSTDIV1_BITS & PLL_PRIM_POSTDIV2_BITS)))) + { + // do not disrupt PLL that is already correctly configured and operating + return; + } + uint32_t pll_reset = (Pll::Usb == pll_name) ? RESETS_RESET_PLL_USB_BITS : RESETS_RESET_PLL_SYS_BITS; + Resets::reset(pll_reset); + Resets::unresetWait(pll_reset); + + // Load VCO-related dividers before starting VCO + pll->cs = refdiv; + pll->fbdiv_int = pll_mul; + + // Turn on PLL + uint32_t power = PLL_PWR_PD_BITS | // Main power + PLL_PWR_VCOPD_BITS; // VCO Power + + hw_clear_bits(&pll->pwr, power); + + // Wait for PLL to lock + while (!(pll->cs & PLL_CS_LOCK_BITS)) __NOP(); + + // Set up post dividers + pll->prim = pdiv; + + // Turn on post divider + hw_clear_bits(&pll->pwr, PLL_PWR_POSTDIVPD_BITS); + } + + /** + * Valid connections are: +%% for clk in clocks +%% for src in clk.source + * - Clock::{{ clk.name | capitalize }} -> ClockSrc::{{ src.cname }} +%% endfor +%% endfor + */ + template + static void + configureClock() + { + using Config = SrcConfig; + constexpr uint32_t div = (uint64_t(SrcFreq) << 8) / TargetFreq; + static_assert(((uint64_t(SrcFreq) << 8) / div) == TargetFreq, "invalid freq"); + + if constexpr (isGlitchless(clk)) + { + static_assert(Config::src != InvalidConfig::src and + Config::aux_src != InvalidConfig::aux_src, + "Invalid clock config"); + configureImpl(clk, Config::src, Config::aux_src, div, TargetFreq); + } + else { + static_assert(Config::aux_src != InvalidConfig::aux_src, "Invalid clock config"); + configureImpl(clk, Config::aux_src, div, TargetFreq); + } + } + + template + static void + updateCoreFrequency() + { + SystemCoreClock = Core_Hz; + delay_fcpu_MHz = computeDelayMhz(Core_Hz); + delay_ns_per_loop = computeDelayNsPerLoop(Core_Hz); + } +}; + +/// @cond +// clocks connections matrix +%% for clk in clocks +// {{ clk.name | capitalize }} +%% for src in clk.source +template<> +struct ClockControl::SrcConfig +{ +%% if clk.glitchless == 'true' + static constexpr uint32_t src = {{ src.src | int }}; +%% endif + static constexpr uint32_t aux_src = {{ src.aux | int }}; +}; +%% endfor +%% endfor +/// @endcond + +} // namespace modm::platform diff --git a/src/modm/platform/clock/rp/module.lb b/src/modm/platform/clock/rp/module.lb new file mode 100644 index 0000000000..190fd73dee --- /dev/null +++ b/src/modm/platform/clock/rp/module.lb @@ -0,0 +1,70 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# Copyright (c) 2022, Andrey Kunitsyn +# +# This file is part of the modm project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# ----------------------------------------------------------------------------- + +import re + +def init(module): + module.name = ":platform:clockgen" + module.description = "Clock Generator and Source" + +def prepare(module, options): + if not options[":target"].has_driver("clocks:rp*"): + return False + + module.depends(":cmsis:device", ":utils", ":platform:clock") + return True + +def _space_numbers(match): + return f'{match.group(1)} {match.group(2)} {match.group(3)}' + +def _add_word_boundaries_to_numbers(string): + pattern = re.compile(r'([a-zA-Z])(\d+)([a-zA-Z]?)') + return pattern.sub(_space_numbers, string) + +def to_camel(string): + string = _add_word_boundaries_to_numbers(string) + string = string.strip(" ") + n = "" + cap_next = True + for v in string: + if (v >= 'A' and v <= 'Z') or (v >= '0' and v <= '9'): + n += v + if v >= 'a' and v <= 'z': + if cap_next: + n += v.upper() + else: + n += v + if v == '_' or v == ' ' or v == '-': + cap_next = True + else: + cap_next = False + return n + + +def build(env): + device = env[":target"] + env.outbasepath = "modm/src/modm/platform/clock" + properties = {} + properties["target"] = device.identifier + clocks = device.get_driver("clocks") + properties["clocks"] = clocks["clock"] + + sources = set() + for clk in clocks["clock"]: + for src in clk['source']: + src['cname'] = to_camel(src['name']) + sources.add(src['cname']) + properties['sources'] = sorted(sources) + + env.substitutions = properties + env.copy("clocks.cpp") + env.template("clocks.hpp.in") From 11cf50762203b863475b399ed542438abae28701 Mon Sep 17 00:00:00 2001 From: andryblack Date: Thu, 20 Jan 2022 13:51:04 +0100 Subject: [PATCH 06/13] [rp] Add UART module --- src/modm/platform/uart/rp/module.lb | 72 +++++++ src/modm/platform/uart/rp/uart.cpp.in | 275 ++++++++++++++++++++++++++ src/modm/platform/uart/rp/uart.hpp.in | 186 +++++++++++++++++ 3 files changed, 533 insertions(+) create mode 100644 src/modm/platform/uart/rp/module.lb create mode 100644 src/modm/platform/uart/rp/uart.cpp.in create mode 100644 src/modm/platform/uart/rp/uart.hpp.in diff --git a/src/modm/platform/uart/rp/module.lb b/src/modm/platform/uart/rp/module.lb new file mode 100644 index 0000000000..720943752f --- /dev/null +++ b/src/modm/platform/uart/rp/module.lb @@ -0,0 +1,72 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# Copyright (c) 2022, Andrey Kunitsyn +# +# This file is part of the modm project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# ----------------------------------------------------------------------------- + + +class Instance(Module): + def __init__(self, instance): + self.instance = instance + + def init(self, module): + module.name = str(self.instance) + module.description = "UART {} instance".format(self.instance) + + def prepare(self, module, options): + module.add_option( + NumericOption( + name="buffer.tx", + description="", + minimum=32, maximum=2 ** 16 - 2, + default=32)) + module.add_option( + NumericOption( + name="buffer.rx", + description="", + minimum=32, maximum=2 ** 16 - 2, + default=32)) + + return True + + def build(self, env): + properties = { + "id": self.instance, + "fifo_size": 32, + } + env.substitutions = properties + env.outbasepath = "modm/src/modm/platform/uart" + + env.template("uart.hpp.in", "uart_{}.hpp".format(self.instance)) + env.template("uart.cpp.in", "uart_{}.cpp".format(self.instance)) + + +def init(module): + module.name = ":platform:uart" + module.description = "Universal Asynchronous Receiver Transmitter (UART)" + +def prepare(module, options): + device = options[":target"] + if not device.has_driver("uart:rp*"): + return False + + module.depends( + ":math:algorithm", + ":platform:gpio", + ":platform:clockgen", + ":architecture:uart", + ":architecture:interrupt") + + for instance in listify(device.get_driver("uart")["instance"]): + module.add_submodule(Instance(instance)) + + return True + +def build(env): + pass diff --git a/src/modm/platform/uart/rp/uart.cpp.in b/src/modm/platform/uart/rp/uart.cpp.in new file mode 100644 index 0000000000..180978fec2 --- /dev/null +++ b/src/modm/platform/uart/rp/uart.cpp.in @@ -0,0 +1,275 @@ +/* + * Copyright (c) 2012, 2016-2017, Sascha Schade + * Copyright (c) 2013-2014, Kevin Läufer + * Copyright (c) 2013-2017, Niklas Hauser + * Copyright (c) 2017, Fabian Greif + * Copyright (c) 2022, Andrey Kunitsyn + * + * This file is part of the modm project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +// ---------------------------------------------------------------------------- + +#include +#include +#include +#include + +#include "../device.hpp" + +#include "uart_{{ id }}.hpp" + +// ---------------------------------------------------------------------------- + +namespace +{ +// If requested buffer size is larger than hardware buffer size create a +// software queue for the remaining bytes. +%% if options["buffer.rx"] > fifo_size + static modm::atomic::Queue rxBuffer; +%% endif +%% if options["buffer.tx"] > fifo_size + static modm::atomic::Queue txBuffer; +%% endif + +} + +static inline bool tx_fifo_full() +{ + return uart{{ id }}_hw->fr & UART_UARTFR_TXFF_BITS; +} + +static inline bool rx_fifo_empty() +{ + return uart{{ id }}_hw->fr & UART_UARTFR_RXFE_BITS; +} + +void modm::platform::Uart{{ id }}::reset() +{ + Resets::reset(RESETS_RESET_UART{{ id }}_BITS); +} + +void modm::platform::Uart{{ id }}::unreset() +{ + Resets::unresetWait(RESETS_RESET_UART{{ id }}_BITS); +} + +void +modm::platform::Uart{{ id }}::writeBlocking(uint8_t data) +{ + while (not write(data)); +} + +void +modm::platform::Uart{{ id }}::writeBlocking(const uint8_t *data, std::size_t length) +{ + while (length--) writeBlocking(*data++); +} + +void +modm::platform::Uart{{ id }}::flushWriteBuffer() +{ + while(!isWriteFinished()); +} + +// ---------------------------------------------------------------------------- +bool +modm::platform::Uart{{ id }}::write(uint8_t data) +{ +%% if options["buffer.tx"] <= fifo_size + // No software buffer necessary, use hardware buffer only. + if (tx_fifo_full()) return false; + uart{{ id }}_hw->dr = data; + return true; +%% else + atomic::Lock lock; + // Use hardware buffer and additional software buffer. + uart{{ id }}_hw->imsc |= UART_UARTIMSC_TXIM_BITS; + + if (txBuffer.isEmpty() and !tx_fifo_full()) + { + // If software buffer is empty try to write to hardware buffer directly. + // Do not write to hardware buffer while there is some data in the + // software buffer to maintain byte order. + // There is at least charsLeft bytes free in the FIFO + uart{{ id }}_hw->dr = data; + return true; // success + } + + // Hardware buffer is full, so try software buffer. + // Software buffer is empty so this will succeed. + // Hardware buffer is not empty so at least one Tx interrupt + // will be generated soon. + + return txBuffer.push(data); +%% endif +} + +// ---------------------------------------------------------------------------- +std::size_t +modm::platform::Uart{{ id }}::write(const uint8_t *buffer, std::size_t length) +{ + std::size_t written(0); + +%% if options["buffer.tx"] <= fifo_size + // No software buffer necessary, use hardware buffer only. + while ( !tx_fifo_full() and writtendr = *buffer++; + written++; + } +%% else + atomic::Lock lock; + // Use hardware buffer and additional software buffer. + uart{{ id }}_hw->imsc |= UART_UARTIMSC_TXIM_BITS; + + if (txBuffer.isEmpty()) + { + // If software buffer is completly empty try to write to hardware buffer directly. + // Do not write to hardware buffer while there is some data in the + // software buffer to maintain byte order. + + // First Copy max(length) chars from buffer to hardware FIFO. + while (written < length and !tx_fifo_full()) + { + uart{{ id }}_hw->dr = *buffer++; + written++; + } + } + + // If there is remaining data, put it into the software buffer + while (written < length) + { + if (not txBuffer.push(*buffer++)) break; + written++; + } +%% endif + return written; +} + +bool +modm::platform::Uart{{ id }}::isWriteFinished() +{ +%% if options["buffer.tx"] > fifo_size + return txBuffer.isEmpty() and ((uart{{ id }}_hw->fr & UART_UARTFR_BUSY_BITS) == 0); +%% else + return (uart{{ id }}_hw->fr & UART_UARTFR_BUSY_BITS) == 0; +%% endif +} + +std::size_t +modm::platform::Uart{{ id }}::discardTransmitBuffer() +{ +%% if options["buffer.tx"] > fifo_size + atomic::Lock lock; + std::size_t count(0); + // disable interrupt since buffer will be cleared + uart{{ id }}_hw->imsc &= UART_UARTIMSC_TXIM_BITS; + while(not txBuffer.isEmpty()) + { + ++count; + txBuffer.pop(); + } + return count; +%% else + return 0; +%% endif +} + +bool +modm::platform::Uart{{ id }}::read(uint8_t& data) +{ +%% if options["buffer.rx"] > fifo_size + if (not rxBuffer.isEmpty()) + { + data = rxBuffer.get(); + rxBuffer.pop(); + // if the hardware buffer has been used, copy all into rxBuffer + while(!rx_fifo_empty() and rxBuffer.isNotFull()) + { + rxBuffer.push(uart{{ id }}_hw->dr); + } + if (rxBuffer.isNotFull()) + { + atomic::Lock lock; + uart{{ id }}_hw->imsc |= UART_UARTIMSC_RXIM_BITS; + } + return true; + } +%% else + if ((uart{{ id }}_hw->fr & UART_UARTFR_RXFE_BITS) == 0) + { + // Receive data available + data = uart{{ id }}_hw->dr; + return true; + } +%% endif + return false; +} + +std::size_t +modm::platform::Uart{{ id }}::read(uint8_t *buffer, std::size_t length) +{ + std::size_t ret = 0; + + while (ret < length) + { + if (not read(*buffer++)) break; + ret++; + } + + return ret; +} + +std::size_t +modm::platform::Uart{{ id }}::discardReceiveBuffer() +{ + std::size_t count(0); +%% if options["buffer.rx"] > fifo_size + while(!rxBuffer.isEmpty()) + { + ++count; + rxBuffer.pop(); + } +%% endif + while(!rx_fifo_empty()) + { + (void)uart{{ id }}_hw->dr; + ++count; + } + return count; +} + +// ---------------------------------------------------------------------------- +MODM_ISR(UART{{ id }}_IRQ) +{ + // read Masked Interrupt Status Register, UARTMIS + uint32_t IValue = uart{{ id }}_hw->mis; + // clear + uart{{ id }}_hw->icr = IValue; +%% if options["buffer.tx"] > fifo_size + if (IValue & UART_UARTMIS_TXMIS_BITS) + { + while (!txBuffer.isEmpty() and !tx_fifo_full()) + { + // Write to the hardware buffer + uart{{ id }}_hw->dr = txBuffer.get(); + txBuffer.pop(); + } + } +%% endif +%% if options["buffer.rx"] > fifo_size + if (IValue & UART_UARTMIS_RXMIS_BITS) + { + while(!rx_fifo_empty()) { + rxBuffer.push(uart{{ id }}_hw->dr); + } + if (rxBuffer.isFull()) { + uart{{ id }}_hw->imsc &= ~UART_UARTIMSC_RXIM_BITS; + } + } +%% endif +} diff --git a/src/modm/platform/uart/rp/uart.hpp.in b/src/modm/platform/uart/rp/uart.hpp.in new file mode 100644 index 0000000000..f45085b7ef --- /dev/null +++ b/src/modm/platform/uart/rp/uart.hpp.in @@ -0,0 +1,186 @@ +/* + * Copyright (c) 2022, Andrey Kunitsyn + * + * This file is part of the modm project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +// ---------------------------------------------------------------------------- + +#pragma once + +#include +#include +#include +#include + +#include + +namespace modm::platform +{ + + +/** + * Universal asynchronous receiver transmitter (UART) + * + * This implementation uses the hardware buffer and the software buffer. + * A software buffer is only used if more than {{ fifo_size }} bytes of buffering + * is requested. + * + * + * There is no FIFO-not-full flag. It's only possible to check if the + * FIFO is completely empty. This makes it impossible to add data + * to the FIFO after the first byte is put into the FIFO. + * + * After detecting that the FIFO is empty (THRE interrupt) the + * charsLeft variable is set to {{ fifo_size }} (= size of FIFO) and some accounting + * is done in this class. + * + * @ingroup modm_platform_uart + * @author Andrey Kunitsyn + */ +class Uart{{ id }} : public modm::Uart +{ + static void + initialize(uint32_t baudrate); + +public: + // Expose jinja template parameters to be checked by e.g. drivers or application + static constexpr size_t RxBufferSize = {{ options["buffer.rx"] }}; + static constexpr size_t TxBufferSize = {{ options["buffer.tx"] }}; + +public: + enum class + Parity : uint32_t + { + Even = UART_UARTLCR_H_PEN_BITS | UART_UARTLCR_H_EPS_BITS, + Odd = UART_UARTLCR_H_PEN_BITS, + Disabled = 0, + }; + + enum class + WordLength : uint32_t + { + Bit5 = 0 << UART_UARTLCR_H_WLEN_LSB, + Bit6 = 1 << UART_UARTLCR_H_WLEN_LSB, + Bit7 = 2 << UART_UARTLCR_H_WLEN_LSB, + Bit8 = 3 << UART_UARTLCR_H_WLEN_LSB, + }; + + enum class + StopBits : uint32_t + { + Bit1 = 0, + Bit2 = UART_UARTLCR_H_STP2_BITS, + }; + + template< class... Signals > + static void + connect() + { + using Connector = GpioConnector; + using Tx = typename Connector::template GetSignal< Gpio::Signal::Tx >; + using Rx = typename Connector::template GetSignal< Gpio::Signal::Rx >; + static_assert(((Connector::template IsValid and Connector::template IsValid) and sizeof...(Signals) == 2) or + ((Connector::template IsValid or Connector::template IsValid) and sizeof...(Signals) == 1), + "Uart{{ id }}::connect() requires one Tx and/or one Rx signal!"); + + Connector::connect(); + } + + static void reset(); + static void unreset(); + + static void + setFormat(Parity parity, WordLength length, StopBits stop) + { + hw_write_masked(&uart{{ id }}_hw->lcr_h, + uint32_t(parity) | uint32_t(length) | uint32_t(stop), + UART_UARTLCR_H_WLEN_BITS | UART_UARTLCR_H_STP2_BITS | + UART_UARTLCR_H_PEN_BITS | UART_UARTLCR_H_EPS_BITS); + } + + template< class SystemClock, baudrate_t baudrate, percent_t tolerance=pct(1) > + static baudrate_t + setBaudrate() + { + static_assert(baudrate * 16 <= SystemClock::PeriFrequency and + SystemClock::PeriFrequency <= baudrate * 16 * 65535ull, + "SystemClock::PeriFrequency must be in the range [16 x baudrate, 16 x 65535 x baudrate]."); + // 16.6 fractional baudrate generator with 16x oversampling + constexpr uint32_t min = (1ul << 7); + constexpr uint32_t max = (1ul << 22) - 1ul; + constexpr auto result = Prescaler::from_range(SystemClock::PeriFrequency*4, baudrate, min, max); + modm::PeripheralDriver::assertBaudrateInTolerance< result.frequency, baudrate, tolerance >(); + // Load PL011's baud divisor registers + uart{{ id }}_hw->ibrd = result.prescaler >> 6; + uart{{ id }}_hw->fbrd = result.prescaler & 0x3f; + // PL011 needs a (dummy) line control register write to latch in the + // divisors. We don't want to actually change LCR contents here. + hw_set_bits(&uart{{ id }}_hw->lcr_h, 0); + + return result.frequency; + } + + template< class SystemClock, baudrate_t baudrate, percent_t tolerance=pct(1) > + static void + initialize(Parity parity=Parity::Disabled, WordLength length=WordLength::Bit8, StopBits stop=StopBits::Bit1) + { + reset(); + unreset(); + setBaudrate(); + setFormat(parity, length, stop); + // Enable the UART, both TX and RX + uart{{ id }}_hw->cr = UART_UARTCR_UARTEN_BITS | UART_UARTCR_TXE_BITS | UART_UARTCR_RXE_BITS; + // Enable FIFOs + hw_set_bits(&uart{{ id }}_hw->lcr_h, UART_UARTLCR_H_FEN_BITS); + // Always enable DREQ signals -- no harm in this if DMA is not listening + uart{{ id }}_hw->dmacr = UART_UARTDMACR_TXDMAE_BITS | UART_UARTDMACR_RXDMAE_BITS; + +%% if options["buffer.rx"] > fifo_size + /* If RX buffering with more than {{ fifo_size }} bytes is requested a software queue + * must be used for receiving. This involves the Rx Interrupt only. */ + uart{{ id }}_hw->imsc = UART_UARTIMSC_RXIM_BITS; +%% endif + +%% if options["buffer.tx"] > fifo_size or options["buffer.rx"] > fifo_size + /* Enable the UART Interrupt */ + NVIC_EnableIRQ(UART{{ id }}_IRQ_IRQn); +%% endif + } + + static void + writeBlocking(uint8_t data); + + static void + writeBlocking(const uint8_t *data, std::size_t length); + + static void + flushWriteBuffer(); + + static bool + write(uint8_t data); + + static std::size_t + write(const uint8_t *data, std::size_t length); + + static bool + isWriteFinished(); + + static std::size_t + discardTransmitBuffer(); + + static bool + read(uint8_t &data); + + static std::size_t + read(uint8_t *buffer, std::size_t length); + + static std::size_t + discardReceiveBuffer(); +}; + +} // namespace modm::platform + From cd5c74008e56a776e8871376b82b71a8226ade4a Mon Sep 17 00:00:00 2001 From: andryblack Date: Thu, 20 Jan 2022 13:51:14 +0100 Subject: [PATCH 07/13] [rp] Add SPI module --- src/modm/platform/spi/rp/module.lb | 60 +++++ src/modm/platform/spi/rp/spi_master.cpp.in | 153 ++++++++++++ src/modm/platform/spi/rp/spi_master.hpp.in | 225 ++++++++++++++++++ .../platform/spi/rp/spi_master_dma.hpp.in | 93 ++++++++ .../spi/rp/spi_master_dma_impl.hpp.in | 165 +++++++++++++ 5 files changed, 696 insertions(+) create mode 100644 src/modm/platform/spi/rp/module.lb create mode 100644 src/modm/platform/spi/rp/spi_master.cpp.in create mode 100644 src/modm/platform/spi/rp/spi_master.hpp.in create mode 100644 src/modm/platform/spi/rp/spi_master_dma.hpp.in create mode 100644 src/modm/platform/spi/rp/spi_master_dma_impl.hpp.in diff --git a/src/modm/platform/spi/rp/module.lb b/src/modm/platform/spi/rp/module.lb new file mode 100644 index 0000000000..089f9de88a --- /dev/null +++ b/src/modm/platform/spi/rp/module.lb @@ -0,0 +1,60 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# Copyright (c) 2022, Andrey Kunitsyn +# +# This file is part of the modm project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# ----------------------------------------------------------------------------- + +class Instance(Module): + def __init__(self, driver, instance): + self.instance = int(instance) + self.driver = driver + + def init(self, module): + module.name = str(self.instance) + module.description = "Instance {}".format(self.instance) + + def prepare(self, module, options): + return True + + def build(self, env): + env.substitutions = {"id": self.instance} + env.outbasepath = "modm/src/modm/platform/spi" + + env.template("spi_master.hpp.in", "spi_master_{}.hpp".format(self.instance)) + env.template("spi_master.cpp.in", "spi_master_{}.cpp".format(self.instance)) + + if env.has_module(":platform:dma"): + env.template("spi_master_dma.hpp.in", "spi_master_{}_dma.hpp".format(self.instance)) + env.template("spi_master_dma_impl.hpp.in", "spi_master_{}_dma_impl.hpp".format(self.instance)) + + +def init(module): + module.name = ":platform:spi" + module.description = "Serial Peripheral Interface (SPI)" + +def prepare(module, options): + device = options[":target"] + if not device.has_driver("spi:rp20*"): + return False + + module.depends( + ":architecture:spi", + ":cmsis:device", + ":math:algorithm", + ":platform:gpio", + ":platform:clockgen") + + for driver in device.get_all_drivers("spi:rp20*"): + for instance in driver["instance"]: + module.add_submodule(Instance(driver, instance)) + + return True + +def build(env): + pass diff --git a/src/modm/platform/spi/rp/spi_master.cpp.in b/src/modm/platform/spi/rp/spi_master.cpp.in new file mode 100644 index 0000000000..7385a6ff9e --- /dev/null +++ b/src/modm/platform/spi/rp/spi_master.cpp.in @@ -0,0 +1,153 @@ +/* + * Copyright (c) 2022, Andrey Kunitsyn + * + * This file is part of the modm project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +// ---------------------------------------------------------------------------- + +#include "spi_master_{{id}}.hpp" +#include + +// ---------------------------------------------------------------------------- + + +void modm::platform::SpiMaster{{ id }}::reset() +{ + Resets::reset(RESETS_RESET_SPI{{ id }}_BITS); +} + +void modm::platform::SpiMaster{{ id }}::unreset() +{ + Resets::unresetWait(RESETS_RESET_SPI{{ id }}_BITS); +} + +uint8_t +modm::platform::SpiMaster{{ id }}::acquire(void *ctx, ConfigurationHandler handler) +{ + if (context == nullptr) + { + context = ctx; + count = 1; + // if handler is not nullptr and is different from previous configuration + if (handler and configuration != handler) { + configuration = handler; + configuration(); + } + return 1; + } + + if (ctx == context) + return ++count; + + return 0; +} + +uint8_t +modm::platform::SpiMaster{{ id }}::release(void *ctx) +{ + if (ctx == context) + { + if (--count == 0) + context = nullptr; + } + return count; +} +// ---------------------------------------------------------------------------- + +modm::ResumableResult +modm::platform::SpiMaster{{ id }}::transfer(uint8_t data) +{ + // this is a manually implemented "fast resumable function" + // there is no context or nesting protection, since we don't need it. + // there are only two states encoded into 1 bit (LSB of state): + // 1. waiting to start, and + // 2. waiting to finish. + + // LSB != Bit0 ? + if ( !(state & Bit0) ) + { + // wait for previous transfer to finish + if (txFifoFull()) + return {modm::rf::Running}; + + // start transfer by copying data into register + write(data); + + // set LSB = Bit0 + state |= Bit0; + } + + if (rxFifoEmpty()) + return {modm::rf::Running}; + + // transfer finished + state &= ~Bit0; + return {modm::rf::Stop, read()}; +} + +modm::ResumableResult +modm::platform::SpiMaster{{ id }}::transfer( + const uint8_t * tx, uint8_t * rx, std::size_t length) +{ + // this is a manually implemented "fast resumable function" + // there is no context or nesting protection, since we don't need it. + // there are only two states encoded into 1 bit (Bit1 of state): + // 1. initialize index, and + // 2. wait for 1-byte transfer to finish. + + // we need to globally remember which byte we are currently transferring + static std::size_t index = 0; + + // we are only interested in Bit1 + switch(state & Bit1) + { + case 0: + // we will only visit this state once + state |= Bit1; + + // initialize index and check range + index = 0; + while (index < length) + { + default: + { + // if tx == 0, we use a dummy byte 0x00 + // else we copy it from the array + // call the resumable function + modm::ResumableResult result = transfer(tx ? tx[index] : 0); + + // if the resumable function is still running, so are we + if (result.getState() > modm::rf::NestingError) + return {modm::rf::Running}; + + // if rx != 0, we copy the result into the array + if (rx) rx[index] = result.getResult(); + } + index++; + } + + // clear the state + state &= ~Bit1; + return {modm::rf::Stop}; + } +} + +void modm::platform::SpiMaster{{ id }}::transferBlocking( + const uint8_t *tx, std::size_t length) +{ + uint8_t index = 0; + while(index < length) { + // Wait for tx empty + while(txFifoFull()) __NOP(); + // Write next byte + write(tx ? tx[index] : 0); + index++; + } + + drainRx(); +} + diff --git a/src/modm/platform/spi/rp/spi_master.hpp.in b/src/modm/platform/spi/rp/spi_master.hpp.in new file mode 100644 index 0000000000..614e977784 --- /dev/null +++ b/src/modm/platform/spi/rp/spi_master.hpp.in @@ -0,0 +1,225 @@ +/** + * Copyright (c) 2022, Andrey Kunitsyn + * + * This file is part of the modm project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +// ---------------------------------------------------------------------------- +#pragma once + +#include +#include +#include + +#include + +namespace modm::platform +{ + +/** + * Serial peripheral interface SPI{{ id }}. + * + * @author Andrey Kunitsyn + * @ingroup modm_platform_spi modm_platform_spi_{{id}} + */ +class SpiMaster{{ id }} : public modm::SpiMaster +{ + // Bit0: single transfer state + // Bit1: block transfer state + static inline uint8_t state{0}; + static inline uint8_t count{0}; + static inline void *context{nullptr}; + static inline ConfigurationHandler configuration{nullptr}; + +protected: + static inline bool isBusy() { + return (spi{{ id }}_hw->sr & SPI_SSPSR_BSY_BITS); + } + + static inline bool txFifoFull() { + return (spi{{ id }}_hw->sr & SPI_SSPSR_TNF_BITS) == 0; + } + static inline bool txFifoEmpty() { + return spi{{ id }}_hw->sr & SPI_SSPSR_TFE_BITS; + } + + static inline bool rxFifoEmpty() { + return (spi{{ id }}_hw->sr & SPI_SSPSR_RNE_BITS) == 0; + } + + static inline void drainRx() + { + // Drain RX FIFO, then wait for shifting to finish (which may be *after* + // TX FIFO drains), then drain RX FIFO again + while (!rxFifoEmpty()) + (void)read(); + while (isBusy()) + __NOP(); + while (!rxFifoEmpty()) + (void)read();; + + // Don't leave overrun flag set + spi{{ id }}_hw->icr = SPI_SSPICR_RORIC_BITS; + } + /* + * Find smallest prescale value which puts output frequency in range of + * post-divide. Prescale is an even number from 2 to 254 inclusive. + */ + template + constexpr static uint32_t + calcPrescale() + { + uint32_t prescale; + for (prescale = 2; prescale <= 254; prescale += 2) { + if (freq_in < (prescale + 2) * 256 * (uint64_t) baudrate) + break; + } + return prescale; + } +public: + template< class... Signals > + static void + connect() + { + using Connector = GpioConnector; + using Sclk = typename Connector::template GetSignal< Gpio::Signal::Sclk >; + using Mosi = typename Connector::template GetSignal< Gpio::Signal::Tx >; + /*using Miso = typename Connector::template GetSignal< Gpio::Signal::Rx >;*/ + static_assert(Connector::template IsValid); + static_assert(Connector::template IsValid); + + Connector::connect(); + } + + static void reset(); + static void unreset(); + + static void + setFormat(uint8_t data_bits, DataMode mode, DataOrder order) + { + modm_assert(data_bits >= 4 && data_bits <= 16, "SPI data_bits", "SPI data bits out of range"); + // LSB-first not supported on PL022: + modm_assert(order == DataOrder::MsbFirst, "SPI DataOrder", "SPI hardware does not support alternate transmit order"); + hw_write_masked(&spi{{ id }}_hw->cr0, + ((uint32_t(data_bits - 1)) << SPI_SSPCR0_DSS_LSB) | + (((uint32_t(mode)&2)>>1) << SPI_SSPCR0_SPO_LSB) | + ((uint32_t(mode)&1) << SPI_SSPCR0_SPH_LSB), + SPI_SSPCR0_DSS_BITS | + SPI_SSPCR0_SPO_BITS | + SPI_SSPCR0_SPH_BITS); + } + + template< class SystemClock, baudrate_t baudrate, percent_t tolerance=pct(5)> + static uint32_t + setBaudrate() + { + constexpr uint32_t freq_in = SystemClock::PeriFrequency; + constexpr uint32_t prescale = calcPrescale(); + static_assert(prescale <= 254, "Frequency too low"); + static_assert(freq_in / prescale <= 256 * (uint64_t) baudrate, "Failed calc prescale"); + + // Find largest post-divide which makes output closest to baudrate. Post-divide is + // an integer in the range 1 to 256 inclusive. + constexpr uint32_t postdiv = Prescaler::from_range(freq_in / prescale, baudrate, 1, 256).prescaler; + + constexpr uint32_t result_baudrate = freq_in / (prescale * postdiv); + + assertBaudrateInTolerance< result_baudrate, baudrate, tolerance >(); + + spi{{ id }}_hw->cpsr = prescale; + hw_write_masked(&spi{{ id }}_hw->cr0, (postdiv - 1) << SPI_SSPCR0_SCR_LSB, SPI_SSPCR0_SCR_BITS); + + return result_baudrate; + } + + // start documentation inherited + template< class SystemClock, baudrate_t baudrate, percent_t tolerance=5_pct > + static void + initialize() + { + reset(); + unreset(); + + setBaudrate(); + setFormat(8,DataMode::Mode0, DataOrder::MsbFirst); + // Set master mode + hw_clear_bits(&spi{{ id }}_hw->cr1, SPI_SSPCR1_MS_BITS); + // Finally enable the SPI + hw_set_bits(&spi{{ id }}_hw->cr1, SPI_SSPCR1_SSE_BITS); + } + + static void + setDataMode(DataMode mode) + { + hw_write_masked(&spi{{ id }}_hw->cr0, + (((uint32_t(mode)&2)>>1) << SPI_SSPCR0_SPO_LSB) | + ((uint32_t(mode)&1) << SPI_SSPCR0_SPH_LSB), + SPI_SSPCR0_SPO_BITS | + SPI_SSPCR0_SPH_BITS); + } + + static void + setDataOrder(DataOrder order) + { + modm_assert(order == DataOrder::MsbFirst, "SPI DataOrder", "SPI hardware does not support alternate transmit order"); + } + + static uint8_t + acquire(void *ctx, ConfigurationHandler handler = nullptr); + + static uint8_t + release(void *ctx); + + static uint8_t + transferBlocking(uint8_t data) + { + return RF_CALL_BLOCKING(transfer(data)); + } + + static void + transferBlocking(const uint8_t *tx, uint8_t *rx, std::size_t length) + { + // If we do not need to receive data, use a more efficient + // transmit-only routine to increase throughput + if(rx) { + RF_CALL_BLOCKING(transfer(tx, rx, length)); + } else { + transferBlocking(tx, length); + } + } + + static modm::ResumableResult + transfer(uint8_t data); + + static modm::ResumableResult + transfer(const uint8_t *tx, uint8_t *rx, std::size_t length); + // end documentation inherited + +protected: + /** Perform transmit-only transaction + * + * A faster version of blocking transfer when transmitting only. + * + * If no receive is needed, the next byte can be loaded while the + * current transfer is in progress. + */ + static void + transferBlocking(const uint8_t *tx, std::size_t length); + + static inline void + write(uint8_t data) + { + spi{{ id }}_hw->dr = uint32_t(data); + } + + static inline uint8_t + read() + { + return uint8_t(spi{{ id }}_hw->dr); + } +}; + +} // namespace modm::platform diff --git a/src/modm/platform/spi/rp/spi_master_dma.hpp.in b/src/modm/platform/spi/rp/spi_master_dma.hpp.in new file mode 100644 index 0000000000..0b59e3f2f3 --- /dev/null +++ b/src/modm/platform/spi/rp/spi_master_dma.hpp.in @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2022, Andrey Kunitsyn + * + * This file is part of the modm project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +// ---------------------------------------------------------------------------- + +#pragma once + +#include +#include "spi_master_{{ id }}.hpp" + +namespace modm::platform +{ + +/** + * Serial peripheral interface (SPI{{ id }}) with DMA support. + * + * This class uses the DMA controller for sending and receiving data, which + * greatly reduces the CPU load. Beside passing the DMA channels as template + * parameters the class can be used in the same way like the SpiMaster{{ id }}. + * + * @tparam DmaChannelRX DMA channel for receiving + * @tparam DmaChannelTX DMA channel for sending + * + * @author Andrey Kunitsyn + * @ingroup modm_platform_spi modm_platform_spi_{{id}} + */ +template +class SpiMaster{{ id }}_Dma : public SpiMaster{{ id }} +{ + struct Dma + { + using RxChannel = DmaChannelRx; + using TxChannel = DmaChannelTx; + static constexpr auto RxRequest = DmaBase::Request::Spi{{id}}_Rx; + static constexpr auto TxRequest = DmaBase::Request::Spi{{id}}_Tx; + static constexpr uint32_t DreqTx = SPI_SSPDMACR_TXDMAE_BITS; + static constexpr uint32_t DreqRx = SPI_SSPDMACR_RXDMAE_BITS; + static_assert(TxChannel::mask, "Tx Channel cannot be a dummy channel!"); + }; +protected: + static inline void disableDreq(uint32_t mask) { + hw_clear_bits(&spi{{ id }}_hw->dmacr,mask); + } + static inline void enableDreq(uint32_t mask) { + hw_set_bits(&spi{{ id }}_hw->dmacr,mask); + } +public: + /// Initialize DMA and SPI{{ id }} + template + static void + initialize(); + + static uint8_t + transferBlocking(uint8_t data) + { + return RF_CALL_BLOCKING(transfer(data)); + } + + static void + transferBlocking(const uint8_t *tx, uint8_t *rx, std::size_t length) + { + RF_CALL_BLOCKING(transfer(tx, rx, length)); + } + + static modm::ResumableResult + transfer(uint8_t data); + + static modm::ResumableResult + transfer(const uint8_t *tx, uint8_t *rx, std::size_t length); + + static void + startTransfer(const uint8_t *tx, uint8_t *rx, std::size_t length); + + template + static void waitCompleted(Wait wait); + static void waitCompleted() + { + waitCompleted([](){}); + } + +private: + // needed for transfers where no RX or TX buffers are given + static inline uint8_t dmaDummy{0}; +}; + +} // namespace modm::platform +#include "spi_master_{{ id }}_dma_impl.hpp" diff --git a/src/modm/platform/spi/rp/spi_master_dma_impl.hpp.in b/src/modm/platform/spi/rp/spi_master_dma_impl.hpp.in new file mode 100644 index 0000000000..8cfd58222c --- /dev/null +++ b/src/modm/platform/spi/rp/spi_master_dma_impl.hpp.in @@ -0,0 +1,165 @@ +/* + * Copyright (c) 2022, Andrey Kunitsyn + * + * This file is part of the modm project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +// ---------------------------------------------------------------------------- +#pragma once +#include "spi_master_{{ id }}_dma.hpp" + +template +template +void +modm::platform::SpiMaster{{ id }}_Dma::initialize() +{ + // Configure the DMA channels, then calls SpiMaster{{ id }}::initialzie(). + Dma::RxChannel::configure(DmaBase::TransferDataSize::Byte, false, true); + Dma::RxChannel::setReadAddress(uint32_t(&spi{{id}}_hw->dr)); + Dma::RxChannel::template setPeripheralRequest(); + + Dma::TxChannel::configure(DmaBase::TransferDataSize::Byte, true, false); + Dma::TxChannel::setWriteAddress(uint32_t(&spi{{id}}_hw->dr)); + Dma::TxChannel::template setPeripheralRequest(); + + SpiMaster{{ id }}::initialize(); +} + +template +modm::ResumableResult +modm::platform::SpiMaster{{ id }}_Dma::transfer(uint8_t data) +{ + // this is a manually implemented "fast resumable function" + // there is no context or nesting protection, since we don't need it. + // there are only two states encoded into 1 bit (LSB of state): + // 1. waiting to start, and + // 2. waiting to finish. + // LSB != Bit0 ? + if ( !(state & Bit0) ) + { + // disable DMA for single byte transfer + disableDreq(Dma::DreqTx|Dma::DreqRx); + } + return SpiMaster{{ id }}::transfer(data); +} + +template +void +modm::platform::SpiMaster{{ id }}_Dma::startTransfer(const uint8_t *tx, + uint8_t *rx, std::size_t length) +{ + if ( (!rx && !tx) or length == 0) { + return; + } + if constexpr (Dma::RxChannel::mask == 0) { + rx = nullptr; + enableDreq(Dma::DreqTx); + } else { + enableDreq((tx ? Dma::DreqTx : 0)|(rx ? Dma::DreqRx : 0)); + } + + if (tx) { + Dma::TxChannel::setReadAddress(uint32_t(tx)); + Dma::TxChannel::setReadIncrementMode(true); + } else { + Dma::TxChannel::setReadAddress(uint32_t(&dmaDummy)); + Dma::TxChannel::setReadIncrementMode(false); + } + Dma::TxChannel::setDataLength(length); + if constexpr (Dma::RxChannel::mask == 0) { + Dma::TxChannel::start(); + } else { + if (rx) { + Dma::RxChannel::setWriteAddress(uint32_t(rx)); + Dma::RxChannel::setWriteIncrementMode(true); + } else { + Dma::RxChannel::setWriteAddress(uint32_t(&dmaDummy)); + Dma::RxChannel::setWriteIncrementMode(false); + } + Dma::RxChannel::setDataLength(length); + ::modm::platform::Dma::template start(); + } +} + +template +template +void +modm::platform::SpiMaster{{ id }}_Dma::waitCompleted(Wait wait) +{ + while (Dma::TxChannel::isBusy() or Dma::RxChannel::isBusy()) + wait(); + while (!txFifoEmpty() or isBusy()) + wait(); + if constexpr (Dma::RxChannel::mask == 0) { + // Drain RX FIFO, then wait for shifting to finish (which may be *after* + // TX FIFO drains), then drain RX FIFO again + while (!rxFifoEmpty()) + (void)read(); + while (isBusy()) + wait(); + while (!rxFifoEmpty()) + (void)read(); + + // Don't leave overrun flag set + spi{{ id }}_hw->icr = SPI_SSPICR_RORIC_BITS; + } else { + while (!rxFifoEmpty() or isBusy()) + wait(); + } +} + +template +modm::ResumableResult +modm::platform::SpiMaster{{ id }}_Dma::transfer(const uint8_t *tx, + uint8_t *rx, std::size_t length) +{ + if ( (!rx && !tx) || length == 0) { + return {modm::rf::Stop}; + } + if constexpr (Dma::RxChannel::mask == 0) rx = nullptr; + // this is a manually implemented "fast resumable function" + // there is no context or nesting protection, since we don't need it. + // there are only two states encoded into 1 bit (Bit1 of state): + // 1. initialize index, and + // 2. wait for 1-byte transfer to finish. + + // we are only interested in Bit1 + switch(state & Bit1) + { + case 0: + // we will only visit this state once + state |= Bit1; + startTransfer(tx,rx,length); + + [[fallthrough]]; + + default: + while (true) { + if (Dma::TxChannel::isBusy() or (rx && Dma::RxChannel::isBusy())) + return { modm::rf::Running }; + if (!txFifoEmpty() or (rx && !rxFifoEmpty()) or isBusy()) + return { modm::rf::Running }; + break; + } + if (!rx) { + // Drain RX FIFO, then wait for shifting to finish (which may be *after* + // TX FIFO drains), then drain RX FIFO again + while (!rxFifoEmpty()) + (void)read(); + while (isBusy()) + return { modm::rf::Running }; + + // Don't leave overrun flag set + spi{{ id }}_hw->icr = SPI_SSPICR_RORIC_BITS; + } + + + disableDreq(Dma::DreqTx|Dma::DreqRx); + // clear the state + state &= ~Bit1; + return {modm::rf::Stop}; + } +} From 91233ba88bf6b016e530cac73343cb95bb1993d6 Mon Sep 17 00:00:00 2001 From: andryblack Date: Thu, 20 Jan 2022 13:51:23 +0100 Subject: [PATCH 08/13] [rp] Add DMA module --- src/modm/platform/dma/rp/dma.hpp.in | 345 ++++++++++++++++++++++++++++ src/modm/platform/dma/rp/module.lb | 27 +++ 2 files changed, 372 insertions(+) create mode 100644 src/modm/platform/dma/rp/dma.hpp.in create mode 100644 src/modm/platform/dma/rp/module.lb diff --git a/src/modm/platform/dma/rp/dma.hpp.in b/src/modm/platform/dma/rp/dma.hpp.in new file mode 100644 index 0000000000..e0668458d8 --- /dev/null +++ b/src/modm/platform/dma/rp/dma.hpp.in @@ -0,0 +1,345 @@ +/* + * Copyright (c) 2022, Andrey Kunitsyn + * + * This file is part of the modm project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +// ---------------------------------------------------------------------------- + +#pragma once + +#include +#include +#include "../device.hpp" +#include +#include + +namespace modm::platform +{ + +/// @ingroup modm_platform_dma +struct DmaBase +{ + using IrqHandler = void (*)(void); + + enum class Channel : uint32_t + { +%% for channel in dma.channel + Channel{{channel.name}}, +%% endfor + }; + enum class Request : uint32_t + { + Pio0_Tx0 = DREQ_PIO0_TX0, + Pio0_Tx1 = DREQ_PIO0_TX1, + Pio0_Tx2 = DREQ_PIO0_TX2, + Pio0_Tx3 = DREQ_PIO0_TX3, + Pio0_Rx0 = DREQ_PIO0_RX0, + Pio0_Rx1 = DREQ_PIO0_RX1, + Pio0_Rx2 = DREQ_PIO0_RX2, + Pio0_Rx3 = DREQ_PIO0_RX3, + Pio1_Tx0 = DREQ_PIO1_TX0, + Pio1_Tx1 = DREQ_PIO1_TX1, + Pio1_Tx2 = DREQ_PIO1_TX2, + Pio1_Tx3 = DREQ_PIO1_TX3, + Pio1_Rx0 = DREQ_PIO1_RX0, + Pio1_Rx1 = DREQ_PIO1_RX1, + Pio1_Rx2 = DREQ_PIO1_RX2, + Pio1_Rx3 = DREQ_PIO1_RX3, + Spi0_Tx = DREQ_SPI0_TX, + Spi0_Rx = DREQ_SPI0_RX, + Spi1_Tx = DREQ_SPI1_TX, + Spi1_Rx = DREQ_SPI1_RX, + Uart0_Tx = DREQ_UART0_TX, + Uart0_Rx = DREQ_UART0_RX, + Uart1_Tx = DREQ_UART1_TX, + Uart1_Rx = DREQ_UART1_RX, + Pwm_Wrap0 = DREQ_PWM_WRAP0, + Pwm_Wrap1 = DREQ_PWM_WRAP1, + Pwm_Wrap2 = DREQ_PWM_WRAP2, + Pwm_Wrap3 = DREQ_PWM_WRAP3, + Pwm_Wrap4 = DREQ_PWM_WRAP4, + Pwm_Wrap5 = DREQ_PWM_WRAP5, + Pwm_Wrap6 = DREQ_PWM_WRAP6, + Pwm_Wrap7 = DREQ_PWM_WRAP7, + I2c0_Tx = DREQ_I2C0_TX, + I2c0_Rx = DREQ_I2C0_RX, + I2c1_Tx = DREQ_I2C1_TX, + I2c1_Rx = DREQ_I2C1_RX, + Adc = DREQ_ADC, + Xip_Stream = DREQ_XIP_STREAM, + Xip_SsiTx = DREQ_XIP_SSITX, + Xip_SsiRx = DREQ_XIP_SSIRX, + Dma_Timer0 = DMA_CH0_CTRL_TRIG_TREQ_SEL_VALUE_TIMER0, + Dma_Timer1 = DMA_CH0_CTRL_TRIG_TREQ_SEL_VALUE_TIMER1, + Dma_Timer2 = DMA_CH0_CTRL_TRIG_TREQ_SEL_VALUE_TIMER2, + Dma_Timer3 = DMA_CH0_CTRL_TRIG_TREQ_SEL_VALUE_TIMER3, + Force = DMA_CH0_CTRL_TRIG_TREQ_SEL_VALUE_PERMANENT, + }; + + enum class + TransferDataSize : uint32_t + { + Byte = 0, + Bit8 = Byte, + HalfWord = 1, + Bit16 = HalfWord, + Word = 2, + Bit32 = Word, + }; + + enum class + Priority : uint32_t + { + Normal = 0, + High = 1, + }; +}; + +/// @author Andrey Kunitsyn +/// @ingroup modm_platform_dma +class DmaController : public DmaBase +{ +public: + /** + * Start multiple channels simultaneously + */ + template + static inline void start() { + dma_hw->multi_channel_trigger = (Channels::mask | ...); + } + + class ChannelDummy + { + public: + static constexpr uint32_t mask = 0; + static void + configure(TransferDataSize transferDataSize, + bool readIncrement, + bool writeIncrement) { + (void)transferDataSize; + (void)readIncrement; + (void)writeIncrement; + } + static void + start() {} + static void + setReadAddress(uintptr_t address) { + (void)address; + } + static void + setWriteAddress(uintptr_t address) { + (void)address; + } + static void + setReadIncrementMode(bool increment) { + (void)increment; + } + static void + setWriteIncrementMode(bool increment) { + (void)increment; + } + static void + setDataLength(std::size_t length) { + (void)length; + } + + template + static void + setPeripheralRequest() {} + static bool isBusy() { return false; } + }; + + /// Class representing a DMA channel/stream + template + class Channel + { + public: + static constexpr DmaBase::Channel name = ChannelID; + static constexpr uint32_t idx = uint32_t(ChannelID); + static constexpr uint32_t mask = (1u << idx); + + /** + * Configure the DMA channel + * + * Stops the DMA channel and writes the new values to its control register. + * + * @param[in] transferDataSize Size of data in transfer (byte, halfword, word) + * @param[in] readIncrement Defines whether the read address is incremented + * after a transfer completed + * @param[in] writeIncrement Defines whether the write address is + * incremented after a transfer completed + */ + static void + configure(TransferDataSize transferDataSize, bool readIncrement, bool writeIncrement) + { + const uint32_t ctrl = + (uint32_t(transferDataSize) << DMA_CH0_CTRL_TRIG_DATA_SIZE_LSB) | + (readIncrement ? DMA_CH0_CTRL_TRIG_INCR_READ_BITS : 0) | + (writeIncrement ? DMA_CH0_CTRL_TRIG_INCR_WRITE_BITS : 0) | + (idx << DMA_CH0_CTRL_TRIG_CHAIN_TO_LSB) | + DMA_CH0_CTRL_TRIG_EN_BITS | + DMA_CH0_CTRL_TRIG_IRQ_QUIET_BITS; + // write to alt1, dnt start + dma_hw->ch[idx].al1_ctrl = ctrl; + } + + /// Start the transfer of the DMA channel + static void + start() + { + dma_hw->multi_channel_trigger = mask; + } + + /// Stop a DMA channel transfer + static void + stop() + { + dma_hw->abort = mask; + while (dma_hw->abort & mask) __NOP(); + } + + /** + * Set the read address of the DMA channel + * + * @note In Mem2Mem mode use this method to set the memory source address. + * + * @param[in] address Source address + */ + static void + setReadAddress(uintptr_t address) + { + dma_hw->ch[idx].read_addr = address; + } + + /** + * Set the write address of the DMA channel + * + * @note In Mem2Mem mode use this method to set the memory destination address. + * + * @param[in] address Destination address + */ + static void + setWriteAddress(uintptr_t address) + { + dma_hw->ch[idx].write_addr = address; + } + + /** + * Enable/disable read increment + * + * When enabled, the read address is incremented by the size of the data + * (e.g. 1 for byte transfers, 4 for word transfers) after the transfer + * completed. + * + * @param[in] increment Enable/disable + */ + static void + setReadIncrementMode(bool increment) + { + if (increment) { + hw_set_bits(&dma_hw->ch[idx].al1_ctrl,DMA_CH0_CTRL_TRIG_INCR_READ_BITS); + } else { + hw_clear_bits(&dma_hw->ch[idx].al1_ctrl,DMA_CH0_CTRL_TRIG_INCR_READ_BITS); + } + } + + /** + * Enable/disable write increment + * + * When enabled, the write address is incremented by the size of the data + * (e.g. 1 for byte transfers, 4 for word transfers) after the transfer + * completed. + * + * @param[in] increment Enable/disable + */ + static void + setWriteIncrementMode(bool increment) + { + if (increment) { + hw_set_bits(&dma_hw->ch[idx].al1_ctrl,DMA_CH0_CTRL_TRIG_INCR_WRITE_BITS); + } else { + hw_clear_bits(&dma_hw->ch[idx].al1_ctrl,DMA_CH0_CTRL_TRIG_INCR_WRITE_BITS); + } + } + + /// Set the length of data to be transfered (num transactions, not bytes) + static void + setDataLength(std::size_t length) + { + dma_hw->ch[idx].transfer_count = length; + } + + /// Set the peripheral that operates the channel + template + static void + setPeripheralRequest() + { + hw_write_masked(&dma_hw->ch[idx].al1_ctrl, + (uint32_t(dmaRequest) << DMA_CH0_CTRL_TRIG_TREQ_SEL_LSB), + DMA_CH0_CTRL_TRIG_TREQ_SEL_BITS); + } + + /// Enable the specified interrupt of the channel + static void + enableInterrupt() + { + hw_clear_bits(&dma_hw->ch[idx].al1_ctrl,DMA_CH0_CTRL_TRIG_IRQ_QUIET_BITS); + } + + /// Disable the specified interrupt of the channel + static void + disableInterrupt() + { + hw_set_bits(&dma_hw->ch[idx].al1_ctrl,DMA_CH0_CTRL_TRIG_IRQ_QUIET_BITS); + } + + static inline bool isBusy() + { + return dma_hw->ch[idx].al1_ctrl & DMA_CH0_CTRL_TRIG_BUSY_BITS; + } + + static inline bool isCompleted() + { + return !isBusy(); + } + + static inline void startWrite(const void* src,std::size_t count) + { + dma_hw->ch[idx].read_addr = (uint32_t)src; + dma_hw->ch[idx].al1_transfer_count_trig = count; + } + + static inline void startRead(void* dst,std::size_t count) + { + dma_hw->ch[idx].write_addr = (uint32_t)dst; + dma_hw->ch[idx].al1_transfer_count_trig = count; + } + + template + static inline void chainTo() + { + hw_write_masked(&dma_hw->ch[idx].al1_ctrl, + (OtherChannel::idx << DMA_CH0_CTRL_TRIG_CHAIN_TO_LSB), + DMA_CH0_CTRL_TRIG_CHAIN_TO_BITS); + } + }; +}; + +/** + * Derive DMA controller classes for convenience. Every derived class defines + * the channels available on that controller. + * @ingroup modm_platform_dma + */ +class Dma: public DmaController +{ +public: +%% for channel in dma.channel + using Channel{{ channel.name }} = DmaController::Channel; +%% endfor +}; + +} // namespace modm::platform diff --git a/src/modm/platform/dma/rp/module.lb b/src/modm/platform/dma/rp/module.lb new file mode 100644 index 0000000000..b5e8b5682e --- /dev/null +++ b/src/modm/platform/dma/rp/module.lb @@ -0,0 +1,27 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# Copyright (c) 2022, Andrey Kunitsyn +# +# This file is part of the modm project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# ----------------------------------------------------------------------------- + +def init(module): + module.name = ":platform:dma" + module.description = "Direct Memory Access (DMA)" + + +def prepare(module, options): + module.depends(":cmsis:device", ":platform:clockgen") + return options[":target"].has_driver("dma:rp20*") + + +def build(env): + env.substitutions = {"dma": env[":target"].get_driver("dma")} + env.outbasepath = "modm/src/modm/platform/dma" + env.template("dma.hpp.in") + From 8297cf350ae2410d4c74f280075e7fdfd763e4ff Mon Sep 17 00:00:00 2001 From: andryblack Date: Thu, 20 Jan 2022 13:52:04 +0100 Subject: [PATCH 09/13] [rp] Add USB module and TinyUSB port --- ext/hathach/module.lb | 7 +++++ ext/hathach/tusb_port.cpp.in | 4 +++ ext/rp/irq.h | 32 +++++++++++++++++++++++ ext/rp/module.lb | 9 +++++++ ext/rp/pico.cpp.in | 31 ++++++++++++++++++++++ ext/rp/pico.h | 35 +++++++++++++++++++++++++ ext/rp/resets.h | 30 +++++++++++++++++++++ src/modm/platform/usb/rp/module.lb | 35 +++++++++++++++++++++++++ src/modm/platform/usb/rp/usb.hpp | 42 ++++++++++++++++++++++++++++++ 9 files changed, 225 insertions(+) create mode 100644 ext/rp/irq.h create mode 100644 ext/rp/pico.cpp.in create mode 100644 ext/rp/pico.h create mode 100644 ext/rp/resets.h create mode 100644 src/modm/platform/usb/rp/module.lb create mode 100644 src/modm/platform/usb/rp/usb.hpp diff --git a/ext/hathach/module.lb b/ext/hathach/module.lb index 9acb160e9f..75dc0c24ae 100644 --- a/ext/hathach/module.lb +++ b/ext/hathach/module.lb @@ -104,6 +104,11 @@ def build(env): tusb_config["CFG_TUSB_MCU"] = "OPT_MCU_SAM{}{}".format(target.family.upper(), target.series.upper()) env.copy("tinyusb/src/portable/microchip/samd/", "portable/microchip/samd/") + elif target.platform == "rp": + if target.family == "20": + tusb_config["CFG_TUSB_MCU"] = "OPT_MCU_RP2040" + env.copy("tinyusb/src/portable/raspberrypi/rp2040/", "portable/raspberrypi/rp2040/") + if env.has_module(":freertos"): tusb_config["CFG_TUSB_OS"] = "OPT_OS_FREERTOS" env.copy("tinyusb/src/osal/osal_freertos.h", "osal/osal_freertos.h") @@ -165,6 +170,8 @@ def build(env): irqs = irq_data["port_irqs"][speed] elif target.platform == "sam" and target.family in ["g"]: irqs = ["UDP"] + elif target.platform == "rp" and target.family in ["20"]: + irqs = ["USBCTRL_IRQ"] else: irqs = ["USB"] diff --git a/ext/hathach/tusb_port.cpp.in b/ext/hathach/tusb_port.cpp.in index 01693d699a..37b8a669c2 100644 --- a/ext/hathach/tusb_port.cpp.in +++ b/ext/hathach/tusb_port.cpp.in @@ -40,6 +40,10 @@ tusb_get_device_serial(uint16_t *serial_str) (uint32_t *)0x0080A00C, (uint32_t *)0x0080A040, (uint32_t *)0x0080A044, (uint32_t *)0x0080A048 %% endif +%% elif target.platform in ["rp"] + /* sysinfo CHIP_ID and GIT_REV */ + ((uint32_t *)0x40000000),((uint32_t *)0x40000040), + ((uint32_t *)0x40000000),((uint32_t *)0x40000040), %% endif }; diff --git a/ext/rp/irq.h b/ext/rp/irq.h new file mode 100644 index 0000000000..8b687a8554 --- /dev/null +++ b/ext/rp/irq.h @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2022, Andrey Kunitsyn + * + * This file is part of the modm project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +// ---------------------------------------------------------------------------- + +#pragma once + +/** + * Pico SDK compatibility for tinyusb + */ +#include "hardware/regs/intctrl.h" + +static inline void +irq_set_enabled(int irqn, bool enable) +{ + if (enable) NVIC_EnableIRQ(irqn); + else NVIC_DisableIRQ(irqn); +} + +static inline void +irq_set_exclusive_handler(int irqn, void (*handler)()) +{ + (void)irqn; + (void)handler; + // do nothing, irq implemented at modm +} diff --git a/ext/rp/module.lb b/ext/rp/module.lb index 559c18c03a..2992164efe 100644 --- a/ext/rp/module.lb +++ b/ext/rp/module.lb @@ -38,6 +38,15 @@ def build(env): env.copy("pico-sdk/include", "") env.copy("address_mapped.h", "hardware/address_mapped.h") + if env.has_module(":tinyusb"): + env.substitutions = { + "with_debug": env.has_module(":debug") + } + env.copy("pico.h") + env.template("pico.cpp.in", "pico.cpp") + env.copy("irq.h", "hardware/irq.h") + env.copy("resets.h", "hardware/resets.h") + env.substitutions = { "headers": headers, "defines": defines, diff --git a/ext/rp/pico.cpp.in b/ext/rp/pico.cpp.in new file mode 100644 index 0000000000..8b83b36c5b --- /dev/null +++ b/ext/rp/pico.cpp.in @@ -0,0 +1,31 @@ +#include "pico.h" +#include "hardware/resets.h" + +#include +%% if with_debug +#include +%% endif + +/** + * Pico SDK compatibility for tinyusb + */ + +extern "C" void reset_block(uint32_t blocks) +{ + modm::platform::Resets::reset(blocks); +} +extern "C" void unreset_block_wait(uint32_t blocks) +{ + modm::platform::Resets::unresetWait(blocks); +} + +extern "C" void panic(const char *fmt, ...) +{ +%% if with_debug + va_list va; + va_start(va, fmt); + modm::log::info.vprintf(fmt, va); + va_end(va); +%% endif + modm_assert(0, "pico", "Pico-SDK panic!"); +} diff --git a/ext/rp/pico.h b/ext/rp/pico.h new file mode 100644 index 0000000000..b7e26d9641 --- /dev/null +++ b/ext/rp/pico.h @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2022, Andrey Kunitsyn + * + * This file is part of the modm project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +// ---------------------------------------------------------------------------- + +#pragma once + +/** + * Pico SDK compatibility for tinyusb + */ + +#include +#include +/* need for static_assert at C compilation */ +#include +#define TUD_OPT_RP2040_USB_DEVICE_ENUMERATION_FIX 0 + +#ifdef __cplusplus +extern "C" { +#endif +/* override expensive assert implementation */ +#undef assert +#define assert(X) modm_assert((X), "pico", __FILE__ ":" MODM_STRINGIFY(__LINE__) " -> \"" #X "\"") +void +panic(const char *fmt, ...); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/ext/rp/resets.h b/ext/rp/resets.h new file mode 100644 index 0000000000..2a867b0278 --- /dev/null +++ b/ext/rp/resets.h @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2022, Andrey Kunitsyn + * + * This file is part of the modm project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +// ---------------------------------------------------------------------------- + +#pragma once + +/** + * Pico SDK compatibility for tinyusb + */ +#include "hardware/structs/resets.h" + +#ifdef __cplusplus +extern "C" { +#endif + +void +reset_block(uint32_t blocks); +void +unreset_block_wait(uint32_t blocks); + +#ifdef __cplusplus +} +#endif diff --git a/src/modm/platform/usb/rp/module.lb b/src/modm/platform/usb/rp/module.lb new file mode 100644 index 0000000000..34681d27d3 --- /dev/null +++ b/src/modm/platform/usb/rp/module.lb @@ -0,0 +1,35 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# Copyright (c) 2022, Andrey Kunitsyn +# +# This file is part of the modm project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# ----------------------------------------------------------------------------- + +# ----------------------------------------------------------------------------- +def init(module): + module.name = ":platform:usb" + module.description = "Universal Serial Bus (USB)" + +def prepare(module, options): + device = options[":target"] + if not device.has_driver("usb:rp20*"): + return False + + + module.depends( + ":architecture:interrupt", + ":cmsis:device", + ":platform:gpio") + + return True + +def build(env): + env.outbasepath = "modm/src/modm/platform/usb" + env.copy('usb.hpp') + if env.has_module(":tinyusb:device:cdc"): + env.copy(repopath("ext/hathach/uart.hpp"), "uart.hpp") diff --git a/src/modm/platform/usb/rp/usb.hpp b/src/modm/platform/usb/rp/usb.hpp new file mode 100644 index 0000000000..5db0ea8728 --- /dev/null +++ b/src/modm/platform/usb/rp/usb.hpp @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2022, Andrey Kunitsyn + * + * This file is part of the modm project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +// ---------------------------------------------------------------------------- + +#pragma once + +#include +#include + +namespace modm::platform +{ + +/// @ingroup modm_platform_usb +class Usb +{ +public: + template + static void + initialize(uint8_t priority = 3) + { + static_assert(SystemClock::UsbFrequency == 48_MHz, "USB must have a 48MHz clock!"); + NVIC_SetPriority(USBCTRL_IRQ_IRQn, priority); + } + + template + static void + connect() + { + using Connector = GpioConnector; + // Dp, Dm use dedicated pins, but Vbus and Overcurrent can be connected + Connector::connect(); + } +}; + +} // namespace modm::platform From 250da09624b36be31b16d37a67b7c312ff442ec3 Mon Sep 17 00:00:00 2001 From: andryblack Date: Thu, 20 Jan 2022 13:54:17 +0100 Subject: [PATCH 10/13] [fiber] Adapt for multicore processing --- .../fiber/{fiber.hpp => fiber.hpp.in} | 27 +-- src/modm/processing/fiber/module.lb | 9 +- src/modm/processing/fiber/scheduler.hpp | 113 ----------- src/modm/processing/fiber/scheduler.hpp.in | 179 ++++++++++++++++++ 4 files changed, 201 insertions(+), 127 deletions(-) rename src/modm/processing/fiber/{fiber.hpp => fiber.hpp.in} (81%) delete mode 100644 src/modm/processing/fiber/scheduler.hpp create mode 100644 src/modm/processing/fiber/scheduler.hpp.in diff --git a/src/modm/processing/fiber/fiber.hpp b/src/modm/processing/fiber/fiber.hpp.in similarity index 81% rename from src/modm/processing/fiber/fiber.hpp rename to src/modm/processing/fiber/fiber.hpp.in index c1aeef8382..accbcb94a8 100644 --- a/src/modm/processing/fiber/fiber.hpp +++ b/src/modm/processing/fiber/fiber.hpp.in @@ -9,15 +9,22 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ // ---------------------------------------------------------------------------- +%% if multicore +%% set CoreDecl = ", size_t core=0" +%% set CoreArg = ", size_t core" +%% set CoreForward = ", core" +%% else +%% set CoreDecl = "" +%% set CoreArg = "" +%% set CoreForward = "" +%% endif #pragma once #include "context.h" #include "stack.hpp" #include - namespace modm { - namespace fiber { @@ -50,11 +57,11 @@ class Fiber public: template - Fiber(fiber::Stack& stack, void(*fn)()); + Fiber(fiber::Stack& stack, void(*fn)(){{CoreDecl}}); template requires requires { &std::decay_t::operator(); } - Fiber(fiber::Stack& stack, T&& closure); + Fiber(fiber::Stack& stack, T&& closure{{CoreDecl}}); protected: inline void @@ -75,21 +82,20 @@ class Fiber namespace modm { - template -Fiber::Fiber(fiber::Stack& stack, void(*fn)()) +Fiber::Fiber(fiber::Stack& stack, void(*fn)(){{CoreArg}}) { ctx = modm_context_init((uintptr_t) stack.memory, (uintptr_t) stack.memory + stack.size, (uintptr_t) fn, (uintptr_t) fiber::Scheduler::deregisterFiber); // register this fiber to be scheduled - fiber::Scheduler::registerFiber(this); + fiber::Scheduler::registerFiber(this{{CoreForward}}); } template requires requires { &std::decay_t::operator(); } -Fiber::Fiber(fiber::Stack& stack, T&& closure) +Fiber::Fiber(fiber::Stack& stack, T&& closure{{CoreArg}}) { // Find a suitable aligned area at the top of stack to allocate the closure uintptr_t ptr = uintptr_t(stack.memory) + stack.size; @@ -106,14 +112,13 @@ Fiber::Fiber(fiber::Stack& stack, T&& closure) ctx = modm_context_init((uintptr_t) stack.memory, ptr, function, (uintptr_t) fiber::Scheduler::deregisterFiber); // register this fiber to be scheduled - fiber::Scheduler::registerFiber(this); + fiber::Scheduler::registerFiber(this{{CoreForward}}); } void Fiber::jump(Fiber& other) { - fiber::Scheduler::current = &other; - modm_context_jump(&(ctx.sp), other.ctx.sp); + fiber::Scheduler::jump(*this,other); } } // namespace modm diff --git a/src/modm/processing/fiber/module.lb b/src/modm/processing/fiber/module.lb index 27373ff5d6..f43565baf7 100644 --- a/src/modm/processing/fiber/module.lb +++ b/src/modm/processing/fiber/module.lb @@ -29,9 +29,6 @@ def build(env): env.outbasepath = "modm/src/modm/processing/fiber" env.copy("../fiber.hpp") - env.copy("fiber.hpp") - env.copy("scheduler.hpp") - env.copy("context.h") core = env[":target"].get_driver("core")["type"] @@ -41,7 +38,13 @@ def build(env): "with_fpu": env.get(":platform:cortex-m:float-abi", "soft") != "soft", "with_windows": env[":target"].identifier.family == "windows", "target": env[":target"].identifier, + "multicore": env.has_module(":platform:multicore"), } + if env.has_module(":platform:multicore"): + cores = int(env[":target"].identifier.cores) + env.substitutions["num_cores"] = cores + env.template("fiber.hpp.in") + env.template("scheduler.hpp.in") env.template("stack.hpp.in") diff --git a/src/modm/processing/fiber/scheduler.hpp b/src/modm/processing/fiber/scheduler.hpp deleted file mode 100644 index e54443dcd6..0000000000 --- a/src/modm/processing/fiber/scheduler.hpp +++ /dev/null @@ -1,113 +0,0 @@ -/* - * Copyright (c) 2020, Erik Henriksson - * - * This file is part of the modm project. - * - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - */ -// ---------------------------------------------------------------------------- - -#pragma once - -#include "fiber.hpp" - -namespace modm::fiber -{ - -void yield(); - -class Scheduler -{ - friend class ::modm::Fiber; - friend class Waitable; - friend void yield(); - Scheduler(const Scheduler&) = delete; - Scheduler() = delete; - -protected: - /// Last fiber in the ready queue. - static inline Fiber* last{nullptr}; - /// Current running fiber - static inline Fiber* current{nullptr}; - -public: - // Should be called by the main() function. - static inline bool - run() - { - if (empty()) return false; - current = last->next; - modm_context_start(current->ctx.sp); - return true; - } - - static inline bool - empty() - { - return last == nullptr; - } - - static inline Fiber* - removeCurrent() - { - if (current == last) last = nullptr; - else last->next = current->next; - current->next = nullptr; - return current; - } - - static inline void - runNext(Fiber* fiber) - { - fiber->next = current->next; - current->next = fiber; - } - - static inline void - runLast(Fiber* fiber) - { - fiber->next = last->next; - last->next = fiber; - last = fiber; - } - -protected: - static inline void - registerFiber(Fiber* fiber) - { - if (last == nullptr) - { - fiber->next = fiber; - last = fiber; - return; - } - runLast(fiber); - } - - static inline void - deregisterFiber() - { - Fiber* next = current->next; - removeCurrent(); - if (empty()) - { - current = nullptr; - modm_context_end(); - } - current->jump(*next); - } -}; - -inline void -yield() -{ - if (Scheduler::current == nullptr) return; - Fiber* next = Scheduler::current->next; - if (next == Scheduler::current) return; - Scheduler::last = Scheduler::current; - Scheduler::current->jump(*next); -} - -} // namespace modm::fiber diff --git a/src/modm/processing/fiber/scheduler.hpp.in b/src/modm/processing/fiber/scheduler.hpp.in new file mode 100644 index 0000000000..fd9c8d2955 --- /dev/null +++ b/src/modm/processing/fiber/scheduler.hpp.in @@ -0,0 +1,179 @@ +/* + * Copyright (c) 2020, Erik Henriksson + * Copyright (c) 2022, Andrey Kunitsyn + * + * This file is part of the modm project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +// ---------------------------------------------------------------------------- + +#pragma once + +#include "fiber.hpp" +%% if multicore +#include +%% endif + +namespace modm::fiber +{ + +void yield(); + +class Scheduler +{ + friend class ::modm::Fiber; + friend class Waitable; + friend void yield(); + Scheduler(const Scheduler&) = delete; + Scheduler() = delete; + +protected: + struct Data + { + /// Last fiber in the ready queue. + Fiber* last = nullptr; + /// Current running fiber + Fiber* current = nullptr; + + inline void registerFiber(Fiber* fiber) + { + if (last == nullptr) + { + fiber->next = fiber; + last = fiber; + return; + } + runLast(fiber); + } + inline void runLast(Fiber* fiber) + { + fiber->next = last->next; + last->next = fiber; + last = fiber; + } + inline Fiber* removeCurrent() + { + if (current == last) last = nullptr; + else last->next = current->next; + current->next = nullptr; + return current; + } + inline bool empty() const + { + return last == nullptr; + } + inline void jump(Fiber& other) + { + auto from = current; + current = &other; + modm_context_jump(&(from->ctx.sp), other.ctx.sp); + } + }; +%% if multicore + %% for i in range(num_cores) + modm_core{{i}}_bss static inline Data data{{i}} = {nullptr, nullptr}; + %% endfor + static constexpr Data* lookup[] = { + %% for i in range(num_cores) + &data{{i}}, + %% endfor + }; + static inline Data& getData() + { return *lookup[::modm::platform::multicore::Core::cpuId()]; } +%% else + static inline Data data = {nullptr,nullptr}; + static inline Data& getData() { return data; } +%% endif + static inline void jump(Fiber& from, Fiber& to) + { + auto& d = getData(); + d.current = &to; + modm_context_jump(&(from.ctx.sp), to.ctx.sp); + } +public: + // Should be called by the main() function. + static inline bool + run() + { + auto& d = getData(); + if (d.last == nullptr) return false; + d.current = d.last->next; + modm_context_start(d.current->ctx.sp); + return true; + } + + static inline bool + empty() + { + return getData().empty(); + } + + static inline Fiber* + removeCurrent() + { + return getData().removeCurrent(); + } + + static inline void + runNext(Fiber* fiber) + { + auto& d = getData(); + fiber->next = d.current->next; + d.current->next = fiber; + } + + static inline void + runLast(Fiber* fiber) + { + getData().runLast(fiber); + } + +protected: +%% if multicore + static inline void + registerFiber(Fiber* fiber, size_t core) + { + ::modm::platform::multicore::SystemSpinLockGuard g; + if (core != ::modm::platform::multicore::Core::cpuId()) + { + lookup[core]->registerFiber(fiber); + return; + } +%% else + static inline void + registerFiber(Fiber* fiber) + { +%% endif + getData().registerFiber(fiber); + } + + static inline void + deregisterFiber() + { + auto& d = getData(); + Fiber* next = d.current->next; + d.removeCurrent(); + if (d.empty()) + { + d.current = nullptr; + modm_context_end(); + } + d.jump(*next); + } +}; + +inline void +yield() +{ + auto& d = Scheduler::getData(); + if (d.current == nullptr) return; + Fiber* next = d.current->next; + if (next == d.current) return; + d.last = d.current; + d.jump(*next); +} + +} // namespace modm::fiber From db9c84c3d2fdf8508887bc7fe42b6e82307a6994 Mon Sep 17 00:00:00 2001 From: andryblack Date: Thu, 20 Jan 2022 13:53:00 +0100 Subject: [PATCH 11/13] [board] Add RP-PICO board module --- src/modm/board/rp_pico/board.hpp | 100 +++++++++++++++++++++++++++++++ src/modm/board/rp_pico/board.xml | 15 +++++ src/modm/board/rp_pico/module.lb | 51 ++++++++++++++++ 3 files changed, 166 insertions(+) create mode 100644 src/modm/board/rp_pico/board.hpp create mode 100644 src/modm/board/rp_pico/board.xml create mode 100644 src/modm/board/rp_pico/module.lb diff --git a/src/modm/board/rp_pico/board.hpp b/src/modm/board/rp_pico/board.hpp new file mode 100644 index 0000000000..715828225e --- /dev/null +++ b/src/modm/board/rp_pico/board.hpp @@ -0,0 +1,100 @@ +/* + * + * Copyright (c) 2022, Andrey Kunitsyn + * + * This file is part of the modm project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +// ---------------------------------------------------------------------------- + +#pragma once + +#include +#include + +using namespace modm::platform; + +namespace Board +{ +using namespace modm::literals; + +/// RP2040 running at 125MHz generated from the external 12MHz crystal +/// @ingroup modm_board_rp_pico +struct SystemClock +{ + static constexpr uint32_t Frequency = 125_MHz; + static constexpr uint32_t XOSCFrequency = 12_MHz; + static constexpr uint32_t PllSysFrequency = Frequency; + static constexpr uint32_t PllUsbFrequency = 48_MHz; + static constexpr uint32_t SysPLLMul = 125; + static constexpr uint32_t UsbPLLMul = 40; + static constexpr uint32_t RefFrequency = XOSCFrequency; + static constexpr uint32_t UsbFrequency = PllUsbFrequency; + static constexpr uint32_t SysFrequency = Frequency; + static constexpr uint32_t PeriFrequency = SysFrequency; + + static bool inline enable() + { + ClockControl::disableResus(); + ClockControl::enableExternalCrystal(XOSCFrequency); + ClockControl::disableAux(); + ClockControl::disableAux(); + // PLL SYS: 12MHz / 1 = 12MHz * 125 = 1500MHZ / 6 / 2 = 125MHz + ClockControl::initPll(); + // PLL USB: 12MHz / 1 = 12MHz * 40 = 480 MHz / 5 / 2 = 48MHz + ClockControl::initPll(); + + // CLK_REF = XOSC (12MHz) / 1 = 12MHz + ClockControl::configureClock(); + // CLK SYS = PLL SYS (125MHz) / 1 = 125MHz + ClockControl::configureClock(); + // CLK USB = PLL USB (48MHz) / 1 = 48MHz + ClockControl::configureClock(); + // CLK PERI = clk_sys. Used as reference clock for Peripherals. No dividers so just select + // and enable Normally choose clk_sys or clk_usb + ClockControl::configureClock(); + + ClockControl::updateCoreFrequency(); + return true; + } +}; + +namespace usb +{ +/// @ingroup modm_board_rp_pico +using Device = Usb; +} + +/// @ingroup modm_board_rp_pico +/// @{ +// User LED +using LedGreen = GpioOutput25; +using Leds = SoftwareGpioPort; + +using Button = GpioUnused; + +inline void +initialize() +{ + SystemClock::enable(); + SysTickTimer::initialize(); + + LedGreen::setOutput(); +} + +inline void +initializeUsbFs() +{ + usb::Device::initialize(); + usb::Device::connect<>(); +} +/// @} + +} // namespace Board diff --git a/src/modm/board/rp_pico/board.xml b/src/modm/board/rp_pico/board.xml new file mode 100644 index 0000000000..07dbffeab5 --- /dev/null +++ b/src/modm/board/rp_pico/board.xml @@ -0,0 +1,15 @@ + + + + ../../../../repo.lb + + + + + + + + + modm:board:rp-pico + + diff --git a/src/modm/board/rp_pico/module.lb b/src/modm/board/rp_pico/module.lb new file mode 100644 index 0000000000..2e3dbe5114 --- /dev/null +++ b/src/modm/board/rp_pico/module.lb @@ -0,0 +1,51 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# Copyright (c) 2022, Andrey Kunitsyn +# +# This file is part of the modm project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# ----------------------------------------------------------------------------- + +def init(module): + module.name = ":board:rp-pico" + module.description = """\ +# Raspberry Pi Pico + +RP2040 Official System Development Board +See https://www.raspberrypi.com/products/raspberry-pi-pico + + +## Programming + +The RP2040 ships with a [UF2 bootloader in ROM](https://github.com/microsoft/uf2). + +To upload your application, connect the RP2040 via USB, convert the ELF to UF2 +format by calling `scons uf2` or `make uf2` and copy the generated `.uf2` file +to the mounted virtual disk. +""" + +def prepare(module, options): + if not options[":target"].partname.startswith("rp2040"): + return False + + module.depends( + ":platform:clock", + ":platform:core", + ":platform:gpio", + ":platform:usb", + ":platform:clockgen") + return True + +def build(env): + env.outbasepath = "modm/src/modm/board" + env.substitutions = { + "with_logger": False, + "with_assert": env.has_module(":architecture:assert") + } + env.template("../board.cpp.in", "board.cpp") + env.copy('.') + From f933c1921ac15e91561a71c1e3850446027c4d5c Mon Sep 17 00:00:00 2001 From: andryblack Date: Thu, 20 Jan 2022 13:54:45 +0100 Subject: [PATCH 12/13] [example] Add RP2040 examples --- .github/workflows/linux.yml | 4 + examples/generic/delay/project.xml | 1 + examples/generic/usb/project.xml | 1 + examples/rp_pico/blink/main.cpp | 39 ++++++++ examples/rp_pico/blink/project.xml | 10 ++ examples/rp_pico/fiber/main.cpp | 125 ++++++++++++++++++++++++ examples/rp_pico/fiber/project.xml | 15 +++ examples/rp_pico/logger/main.cpp | 90 ++++++++++++++++++ examples/rp_pico/logger/project.xml | 14 +++ examples/rp_pico/mclogger/main.cpp | 132 ++++++++++++++++++++++++++ examples/rp_pico/mclogger/project.xml | 15 +++ examples/rp_pico/spi_dma/main.cpp | 57 +++++++++++ examples/rp_pico/spi_dma/project.xml | 15 +++ 13 files changed, 518 insertions(+) create mode 100644 examples/rp_pico/blink/main.cpp create mode 100644 examples/rp_pico/blink/project.xml create mode 100644 examples/rp_pico/fiber/main.cpp create mode 100644 examples/rp_pico/fiber/project.xml create mode 100644 examples/rp_pico/logger/main.cpp create mode 100644 examples/rp_pico/logger/project.xml create mode 100644 examples/rp_pico/mclogger/main.cpp create mode 100644 examples/rp_pico/mclogger/project.xml create mode 100644 examples/rp_pico/spi_dma/main.cpp create mode 100644 examples/rp_pico/spi_dma/project.xml diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 2bf8d90f70..fc8bcd34fe 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -80,6 +80,10 @@ jobs: if: always() run: | (cd examples && ../tools/scripts/examples_compile.py samv) + - name: Examples RP20 Devices + if: always() + run: | + (cd examples && ../tools/scripts/examples_compile.py rp_pico) - name: Execute Python Scripts if: always() run: | diff --git a/examples/generic/delay/project.xml b/examples/generic/delay/project.xml index 39819a8090..44b756ceb7 100644 --- a/examples/generic/delay/project.xml +++ b/examples/generic/delay/project.xml @@ -17,6 +17,7 @@ + diff --git a/examples/generic/usb/project.xml b/examples/generic/usb/project.xml index 18a8b42256..aa57bea6b0 100644 --- a/examples/generic/usb/project.xml +++ b/examples/generic/usb/project.xml @@ -9,6 +9,7 @@ + diff --git a/examples/rp_pico/blink/main.cpp b/examples/rp_pico/blink/main.cpp new file mode 100644 index 0000000000..d2015d36b8 --- /dev/null +++ b/examples/rp_pico/blink/main.cpp @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2016, Sascha Schade + * Copyright (c) 2017, Niklas Hauser + * + * This file is part of the modm project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +// ---------------------------------------------------------------------------- + +#include + +using namespace Board; + +/* + * Blinks the green user LED with 1 Hz. + * It is on for 90% of the time and off for 10% of the time. + */ + +int +main() +{ + Board::initialize(); + + LedGreen::setOutput(); + + while (true) + { + LedGreen::set(); + modm::delay(900ms); + + LedGreen::reset(); + modm::delay(100ms); + } + + return 0; +} diff --git a/examples/rp_pico/blink/project.xml b/examples/rp_pico/blink/project.xml new file mode 100644 index 0000000000..8aff40d49a --- /dev/null +++ b/examples/rp_pico/blink/project.xml @@ -0,0 +1,10 @@ + + modm:rp-pico + + + + + + modm:build:scons + + diff --git a/examples/rp_pico/fiber/main.cpp b/examples/rp_pico/fiber/main.cpp new file mode 100644 index 0000000000..e322e5eb6e --- /dev/null +++ b/examples/rp_pico/fiber/main.cpp @@ -0,0 +1,125 @@ +/* + * Copyright (c) 2022, Andrey Kunitsyn + * + * This file is part of the modm project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +// ---------------------------------------------------------------------------- + +#include +#include +#include +#include + +using namespace Board; +using namespace std::chrono_literals; + +// Create an IODeviceWrapper around the Uart Peripheral we want to use +modm::IODeviceWrapper loggerDevice; + +// Set all four logger streams to use the UART +modm::log::Logger modm::log::debug(loggerDevice); +modm::log::Logger modm::log::info(loggerDevice); +modm::log::Logger modm::log::warning(loggerDevice); +modm::log::Logger modm::log::error(loggerDevice); + +constexpr uint32_t cycles = 1'000'000; + +static multicore::Mutex log_mutex; + +struct CoreData +{ + uint32_t total_counter = 0; + uint32_t f1counter = 0; + uint32_t f2counter = 0; +}; + +void +fiber_function1(CoreData& d) +{ + while (++d.f1counter < cycles) + { + modm::fiber::yield(); + d.total_counter++; + } +} + +void +fiber_function2(CoreData& d) +{ + while (++d.f2counter < cycles) + { + modm::fiber::yield(); + d.total_counter++; + } +} + +// put cores to mostly equalent environment +modm_core0_data CoreData d0; +modm_core1_data CoreData d1; + +modm_core0_noinit modm::fiber::Stack<384> stack01; +modm_core0_noinit modm::fiber::Stack<384> stack02; +modm_core1_noinit modm::fiber::Stack<384> stack11; +modm_core1_noinit modm::fiber::Stack<384> stack12; + +modm_core0_data +modm::Fiber fiber01(stack01, []() { fiber_function1(d0); }, 0); +modm_core0_data +modm::Fiber fiber02(stack02, []() { fiber_function2(d0); }, 0); +modm_core1_data +modm::Fiber fiber11(stack11, []() { fiber_function1(d1); }, 1); +modm_core1_data +modm::Fiber fiber12(stack12, []() { fiber_function2(d1); }, 1); + +template +static void +print_result(const CoreData& d, TimeDiff diff) +{ + std::lock_guard g(log_mutex); + MODM_LOG_INFO << "Benchmark for core" << multicore::Core::cpuId() << " done!" << modm::endl; + MODM_LOG_INFO << "Executed " << d.total_counter << " yields in " << diff << modm::endl; + MODM_LOG_INFO << ((d.total_counter * 1'000'000ull) / std::chrono::microseconds(diff).count()); + MODM_LOG_INFO << " yields per second, "; + MODM_LOG_INFO << (std::chrono::nanoseconds(diff).count() / d.total_counter); + MODM_LOG_INFO << "ns per yield" << modm::endl; + MODM_LOG_INFO.flush(); +} + +void +core1_main() +{ + const modm::PreciseTimestamp start = modm::PreciseClock::now(); + modm::fiber::Scheduler::run(); + const auto diff = (modm::PreciseClock::now() - start); + + print_result(d1, diff); + while (true) __NOP(); +} + +int +main() +{ + Board::initialize(); + + // initialize Uart0 for MODM_LOG_* + Uart0::connect(); + Uart0::initialize(); + + MODM_LOG_INFO << "Starting fiber modm::yield benchmark..." << modm::endl; + MODM_LOG_INFO.flush(); + + multicore::Core1::run(core1_main); + + const modm::PreciseTimestamp start = modm::PreciseClock::now(); + modm::fiber::Scheduler::run(); + const auto diff = (modm::PreciseClock::now() - start); + + print_result(d0, diff); + while (true) __NOP(); + + return 0; +} diff --git a/examples/rp_pico/fiber/project.xml b/examples/rp_pico/fiber/project.xml new file mode 100644 index 0000000000..48fe39bab5 --- /dev/null +++ b/examples/rp_pico/fiber/project.xml @@ -0,0 +1,15 @@ + + modm:rp-pico + + + + + + modm:debug + modm:platform:uart:0 + modm:platform:multicore + modm:build:scons + modm:processing:timer + modm:processing:fiber + + diff --git a/examples/rp_pico/logger/main.cpp b/examples/rp_pico/logger/main.cpp new file mode 100644 index 0000000000..21f840fab6 --- /dev/null +++ b/examples/rp_pico/logger/main.cpp @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2011, Fabian Greif + * Copyright (c) 2013, Kevin Läufer + * Copyright (c) 2013-2017, Niklas Hauser + * Copyright (c) 2014, 2016, Sascha Schade + * Copyright (c) 2022, Andrey Kunitsyn + * + * This file is part of the modm project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +// ---------------------------------------------------------------------------- + +#include +#include +#include +#include + +// ---------------------------------------------------------------------------- +// Set the log level +#undef MODM_LOG_LEVEL +#define MODM_LOG_LEVEL modm::log::INFO + +// Create an IODeviceWrapper around the Uart Peripheral we want to use +modm::IODeviceWrapper loggerDevice; + +// Set all four logger streams to use the UART +modm::log::Logger modm::log::debug(loggerDevice); +modm::log::Logger modm::log::info(loggerDevice); +modm::log::Logger modm::log::warning(loggerDevice); +modm::log::Logger modm::log::error(loggerDevice); + +class BlinkThread : public modm::pt::Protothread +{ +public: + BlinkThread() { timeout.restart(100ms); } + + bool + update() + { + PT_BEGIN(); + + while (true) + { + Board::LedGreen::reset(); + + PT_WAIT_UNTIL(timeout.isExpired()); + timeout.restart(100ms); + + Board::LedGreen::set(); + + PT_WAIT_UNTIL(timeout.isExpired()); + timeout.restart(900ms); + + MODM_LOG_INFO << "Seconds since reboot: " << ++uptime << modm::endl; + } + + PT_END(); + } + +private: + modm::ShortTimeout timeout; + uint32_t uptime; +}; + +BlinkThread blinkThread; + +// ---------------------------------------------------------------------------- +int +main() +{ + Board::initialize(); + + // initialize Uart0 for MODM_LOG_* + Uart0::connect(); + Uart0::initialize(); + + // Use the logging streams to print some messages. + // Change MODM_LOG_LEVEL above to enable or disable these messages + MODM_LOG_DEBUG << "debug" << modm::endl; + MODM_LOG_INFO << "info" << modm::endl; + MODM_LOG_WARNING << "warning" << modm::endl; + MODM_LOG_ERROR << "error" << modm::endl; + + while (true) { blinkThread.update(); } + + return 0; +} diff --git a/examples/rp_pico/logger/project.xml b/examples/rp_pico/logger/project.xml new file mode 100644 index 0000000000..82492276ab --- /dev/null +++ b/examples/rp_pico/logger/project.xml @@ -0,0 +1,14 @@ + + modm:rp-pico + + + + + modm:debug + modm:platform:gpio + modm:platform:uart:0 + modm:processing:timer + modm:processing:protothread + modm:build:scons + + diff --git a/examples/rp_pico/mclogger/main.cpp b/examples/rp_pico/mclogger/main.cpp new file mode 100644 index 0000000000..17fec27ffa --- /dev/null +++ b/examples/rp_pico/mclogger/main.cpp @@ -0,0 +1,132 @@ +/* + * Copyright (c) 2022, Andrey Kunitsyn + * + * This file is part of the modm project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +// ---------------------------------------------------------------------------- + +#include +#include +#include +#include +#include + +// ---------------------------------------------------------------------------- +// Set the log level +#undef MODM_LOG_LEVEL +#define MODM_LOG_LEVEL modm::log::INFO + +// Create an IODeviceWrapper around the Uart Peripheral we want to use +modm::IODeviceWrapper loggerDevice; + +// Set all four logger streams to use the UART +modm::log::Logger modm::log::debug(loggerDevice); +modm::log::Logger modm::log::info(loggerDevice); +modm::log::Logger modm::log::warning(loggerDevice); +modm::log::Logger modm::log::error(loggerDevice); + +// +#if 0 +// with simple case we can use directly SpinLockUnsafe +using LockMutex = modm::platform::multicore::SpinLockUnsafe<1>; +#define LOG_GUARD() modm::platform::multicore::SpinLockGuard g +#define INIT_GUARD() LockMutex::init() +#else +// more extensive, but work at any case +static multicore::Mutex log_mutex; +#define LOG_GUARD() std::lock_guard g(log_mutex) +#define INIT_GUARD() \ + do { \ + } while (false) +#endif + +template +class Thread : public modm::pt::Protothread +{ + static constexpr auto delay = 10ms + 1ms * Instance; + +public: + Thread() { timeout.restart(delay); } + + bool + update() + { + PT_BEGIN(); + while (true) + { + PT_WAIT_UNTIL(timeout.isExpired()); + timeout.restart(delay); + { + // try without this line for intermixed output + LOG_GUARD(); + MODM_LOG_INFO << "Core: " << multicore::Core::cpuId() + << " thread: " << Instance << " uptime: " << ++uptime << modm::endl; + } + } + + PT_END(); + } + +private: + modm::ShortTimeout timeout; + uint32_t uptime; +}; + +template +class Threads +{ +private: + Thread t0; + Thread t1; + Thread t2; + Thread t3; + +public: + void + update() + { + t0.update(); + t1.update(); + t2.update(); + t3.update(); + } +}; + +Threads<0> core0; +Threads<1> core1; + +void +core1_main() +{ + while (true) { core1.update(); } +} + +// ---------------------------------------------------------------------------- +int +main() +{ + Board::initialize(); + + // initialize Uart0 for MODM_LOG_* + Uart0::connect(); + Uart0::initialize(); + + // Use the logging streams to print some messages. + // Change MODM_LOG_LEVEL above to enable or disable these messages + MODM_LOG_DEBUG << "debug" << modm::endl; + MODM_LOG_INFO << "info" << modm::endl; + MODM_LOG_WARNING << "warning" << modm::endl; + MODM_LOG_ERROR << "error" << modm::endl; + + INIT_GUARD(); + + multicore::Core1::run(core1_main); + + while (true) { core0.update(); } + + return 0; +} diff --git a/examples/rp_pico/mclogger/project.xml b/examples/rp_pico/mclogger/project.xml new file mode 100644 index 0000000000..8f05615fd8 --- /dev/null +++ b/examples/rp_pico/mclogger/project.xml @@ -0,0 +1,15 @@ + + modm:rp-pico + + + + + modm:debug + modm:platform:gpio + modm:platform:uart:0 + modm:platform:multicore + modm:processing:timer + modm:processing:protothread + modm:build:scons + + diff --git a/examples/rp_pico/spi_dma/main.cpp b/examples/rp_pico/spi_dma/main.cpp new file mode 100644 index 0000000000..8f4adf58ba --- /dev/null +++ b/examples/rp_pico/spi_dma/main.cpp @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2020, Mike Wolfram + * Copyright (c) 2021, Raphael Lehmann + * Copyright (c) 2022, Andrey Kunitsyn + * + * This file is part of the modm project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +// ---------------------------------------------------------------------------- + +#include +#include + +// Create an IODeviceWrapper around the UsbCDC +modm::IODeviceWrapper usb_io_device0; +modm::IOStream usb_stream0(usb_io_device0); + +// connect Pin3 to Pin4 for loopback +using Mosi = GpioOutput3; +using Miso = GpioInput4; +using Sck = GpioOutput2; +using DmaRx = Dma::Channel1; +using DmaTx = Dma::Channel2; +using Spi = SpiMaster0_Dma; + +int main() +{ + Board::initialize(); + Board::initializeUsbFs(); + tusb_init(); + + Spi::connect(); + Spi::initialize(); + + while (true) + { + uint8_t sendBuffer[13] { "data to send" }; + uint8_t receiveBuffer[13]; + usb_stream0 << "Spi tx only" << modm::endl; + tud_task(); + // send out 12 bytes, don't care about response + Spi::transferBlocking(sendBuffer, nullptr, 12); + + usb_stream0 << "Spi send \"" << reinterpret_cast(sendBuffer) << "\"" << modm::endl; + tud_task(); + // send out 12 bytes, read in 12 bytes + Spi::transferBlocking(sendBuffer, receiveBuffer, 12); + receiveBuffer[12] = 0; + usb_stream0 << "Spi received \"" << reinterpret_cast(receiveBuffer) << "\"" << modm::endl; + tud_task(); + } + + return 0; +} diff --git a/examples/rp_pico/spi_dma/project.xml b/examples/rp_pico/spi_dma/project.xml new file mode 100644 index 0000000000..b1c9b09f08 --- /dev/null +++ b/examples/rp_pico/spi_dma/project.xml @@ -0,0 +1,15 @@ + + modm:rp-pico + + + + + + modm:platform:gpio + modm:platform:dma + modm:platform:spi:0 + modm:tinyusb + modm:io + modm:build:scons + + From 65ac60ade147891dd1e12fa281eebd3fcfbcb287 Mon Sep 17 00:00:00 2001 From: andryblack Date: Thu, 20 Jan 2022 13:55:07 +0100 Subject: [PATCH 13/13] [docs] Update for RP2040 --- README.md | 28 +++++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 950327cb99..950dd1cd5e 100644 --- a/README.md +++ b/README.md @@ -101,6 +101,7 @@ Please [discover modm's peripheral drivers for your specific device][discover]. STM32 SAM +RP AT Peripheral @@ -120,6 +121,7 @@ Please [discover modm's peripheral drivers for your specific device][discover]. D21 G55 V70 +20 90 Mega Tiny @@ -142,6 +144,7 @@ Please [discover modm's peripheral drivers for your specific device][discover]. ✅ ○ ○ +○ ✅ ✅ @@ -162,6 +165,7 @@ Please [discover modm's peripheral drivers for your specific device][discover]. ✕ ✕ ○ +✕ ○ ○ ✕ @@ -183,6 +187,7 @@ Please [discover modm's peripheral drivers for your specific device][discover]. ○ ✕ ○ +✕ ○ ○ ○ @@ -205,6 +210,7 @@ Please [discover modm's peripheral drivers for your specific device][discover]. ✕ ○ ✕ +✕ ○ ✕ @@ -225,6 +231,7 @@ Please [discover modm's peripheral drivers for your specific device][discover]. ○ ✕ ○ +✅ ✕ ✕ ✕ @@ -249,6 +256,7 @@ Please [discover modm's peripheral drivers for your specific device][discover]. ✕ ✕ ✕ +✕ External Interrupt ✅ @@ -267,6 +275,7 @@ Please [discover modm's peripheral drivers for your specific device][discover]. ✅ ○ ○ +○ ✅ ✅ ✅ @@ -288,6 +297,7 @@ Please [discover modm's peripheral drivers for your specific device][discover]. ✕ ✕ ○ +○ ✕ ✕ ✕ @@ -312,6 +322,7 @@ Please [discover modm's peripheral drivers for your specific device][discover]. ✅ ✅ ✅ +✅ I2C ✅ @@ -330,6 +341,7 @@ Please [discover modm's peripheral drivers for your specific device][discover]. ○ ○ ○ +○ ✅ ✅ ✅ @@ -354,6 +366,7 @@ Please [discover modm's peripheral drivers for your specific device][discover]. ✕ ✕ ✕ +✕ Random Generator ✕ @@ -375,6 +388,7 @@ Please [discover modm's peripheral drivers for your specific device][discover]. ✕ ✕ ✕ +✕ SPI ✅ @@ -396,6 +410,7 @@ Please [discover modm's peripheral drivers for your specific device][discover]. ✅ ✅ ✅ +✅ System Clock ✅ @@ -412,8 +427,9 @@ Please [discover modm's peripheral drivers for your specific device][discover]. ✅ ✅ ✅ -✕ -✕ +✅ +✅ +✅ ✕ ✕ ✕ @@ -438,6 +454,7 @@ Please [discover modm's peripheral drivers for your specific device][discover]. ○ ○ ○ +○ UART ✅ @@ -458,6 +475,7 @@ Please [discover modm's peripheral drivers for your specific device][discover]. ○ ✅ ✅ +✅ ○ Unique ID @@ -480,6 +498,7 @@ Please [discover modm's peripheral drivers for your specific device][discover]. ✕ ✕ ✕ +✕ USB ✅ @@ -498,6 +517,7 @@ Please [discover modm's peripheral drivers for your specific device][discover]. ✅ ✅ ○ +✅ ✕ ✕ ✕ @@ -585,13 +605,15 @@ We have out-of-box support for many development boards including documentation. NUCLEO-L552ZE-Q OLIMEXINO-STM32 +RP-PICO Raspberry Pi SAMD21-MINI -SAMG55-XPLAINED-PRO +SAMG55-XPLAINED-PRO STM32-F4VE STM32F030-DEMO Smart Response XE +